Combine two SVG elements into one using Javascript? - javascript

Let's say I have the following html context, which I don't have access to its creation :
<div id="outer">
<div id="chart-div">
<svg id="chart"></svg>
</div>
<div id="legend-div">
<svg id="legend"></svg>
</div>
</div>
What I'm trying to do is export this SVG to an image using canvg library, the problem here is that they are separated and so I get two canvas, and canvg library accepts a SVG definition string as input.
How can I modify the html elements above so that they are only one SVG? Using Javascript as this is a browser extension.
I have tried just switching the DIVs tags to SVG but it just broke everything and the SVG became blank

This is my solution to your problem: I make the outer div display:none, I'm creating another svg element (you may do it dynamicaly) end inside the #new svg I'm using the #chart and the #legend. I hope it helps.
svg{border:1px solid;}
#outer {display:none}
#outer div{position:absolute; width:500px;}
<div id="outer">
<div id="chart-div">
<svg id="chart" viewBox="0 0 300 150">
<circle stroke="gold" fill="none" cx="100" cy="75" stroke-width="40" stroke-dasharray="124.85" stroke-dashoffset="20" r="20" />
</svg>
</div>
<div id="legend-div">
<svg id="legend" viewBox="0 0 300 150">
<rect fill="skyBlue" x="200" y="100" width="80" height ="30" />
</svg>
</div>
</div>
<svg id="new" viewBox="0 0 300 150" width="500">
<use xlink:href="#chart" />
<use xlink:href="#legend" />
</svg>

This is a Javascript solution for merging two svg accessibles in the document through DOM manipulation.
var svgNS = "http://www.w3.org/2000/svg";
var outer = document.getElementById('outer');
// get chart content
var chart = document.getElementById('chart-div');
var chartSvg = chart.getElementsByTagName('svg')[0];
var chartContent = Array.from(chartSvg.childNodes);
// get legend content
var legend = document.getElementById('legend-div');
var legendSvg = legend.getElementsByTagName('svg')[0];
var legendContent = Array.from(legendSvg.childNodes);
// create a merged-div where we are going to merge the svgs
var merged = document.createElement('div');
merged.setAttribute('id', 'merged-div');
outer.appendChild(merged);
// createElementNS for svg
var mergedSvg = document.createElementNS(svgNS, 'svg');
mergedSvg.setAttribute('id', 'merged');
// keep the viewBox of the chart
mergedSvg.setAttribute('viewBox', chartSvg.getAttribute('viewBox'));
merged.appendChild(mergedSvg);
// adding the content of both svgs
for (let i = 0; i < chartContent.length; i++) {
mergedSvg.appendChild(chartContent[i]);
}
for (let i = 0; i < legendContent.length; i++) {
mergedSvg.appendChild(legendContent[i]);
}
// the unmerged svgs can be removed
chart.remove();
legend.remove();
<div id="outer">
<div id="chart-div">
<svg id="chart" viewBox="0 0 300 150">
<circle stroke="gold" fill="none" cx="100" cy="75" stroke-width="40" stroke-dasharray="124.85" stroke-dashoffset="20" r="20" />
</svg>
</div>
<div id="legend-div">
<svg id="legend" viewBox="0 0 300 150">
<rect fill="skyBlue" x="200" y="100" width="80" height ="30" />
</svg>
</div>
</div>
Resulting markup:
<div id="outer">
<div id="merged-div">
<svg id="merged" viewBox="0 0 300 150">
<circle stroke="gold" fill="none" cx="100" cy="75" stroke-width="40" stroke-dasharray="124.85" stroke-dashoffset="20" r="20"></circle>
<rect fill="skyBlue" x="200" y="100" width="80" height="30"></rect>
</svg>
</div>
</div>

Related

How to get the offsetLeft/offsetTop of the <g id="iqaluit"> and <circle id="circle"> elements on the page?

How to get offsetLeft/offsetTop of **<g id="iqaluit"> and <circle id="circle"> elements in javascript.
I tried this: but it gives an error TypeError: bar is null.
JS
let bar = document.getElementById('iqaluit');
console.log(bar.offsetLeft, bar.offsetTop)
let circle = document.getElementById('circle');
console.log(circle.offsetLeft, circle.offsetTop);
HTML
<div class="section-map">
<canvas class="svgfix__canvas" width="500" height="488"> </canvas>
<svg id="MAP" data-name="MAP" data-province="MAP" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 1072.93">
<g id="City_Labels_Canada" data-name="City Labels Canada">
<g id="iqaluit" data-name="Iqaluit" data-province="nunavut" class="city nohover">
<circle id="circle" cx="839.32" cy="390.34" r="3.1"></circle>
<text x="847" y="394" data-name="Iqaluit" class="en de fr es">Iqaluit</text>
</g>
</g>
</svg>
</div>
#Dio, you're on the right track. The svg elements do not support the properties you are looking for but you can access the elements, just a little differently from how you attempted, with the document. See below.
Also, the suggestion from #Syed Qasim Ahmed provides an object, but I'm not sure what you're trying to achieve with offsetLeft/offsetTop, so I can't say if it's equivalent or not.
const map = document.getElementById('MAP');
const bar = map?.querySelector('#iqaluit');
/**
* You can see the available
* methods and properties
* with a for in loop
*/
if (bar) {
for (let i in bar) {
console.log('what is i?', i);
console.log('what is bar[i]?', bar[i]);
}
const getBoundingClientRect = bar.getBoundingClientRect();
console.log({ getBoundingClientRect });
}
<div class="section-map">
<canvas class="svgfix__canvas" width="500" height="488"> </canvas>
<svg id="MAP" data-name="MAP" data-province="MAP" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 1072.93">
<g id="City_Labels_Canada" data-name="City Labels Canada">
<g id="iqaluit" data-name="Iqaluit" data-province="nunavut" class="city nohover">
<circle id="circle" cx="839.32" cy="390.34" r="3.1"></circle>
<text x="847" y="394" data-name="Iqaluit" class="en de fr es">Iqaluit</text>
</g>
</g>
</svg>
</div>

External SVG file in clippath

I have SVG file that i created it in photoshop. I would like to use it in my html page with clip-path property. I am trying to implement it as using clip-path:url(#mysvg); and paste the svg code to my html page. But i does not work. How can i do that?
My purpose is like this with css:
Here is the .svg file:
https://svgshare.com/i/dfw.svg
Here is the svg code
<!--IMAGE-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 814 506" >
<image id="image" class="image__svg-image" width="100%" height="100%" clip-path="url(#mask)" x="-100px" xlink:href="https://res.cloudinary.com/alvarosaburido/image/upload/v1589435086/blog/The%20Magic%20of%20SVG%20Clip-path/pic_yo5eyq.png" />
</svg>
<!--MY SVG FILE-->
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1920" viewBox="0 0 1920 1920">
<metadata><?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c142 79.160924, 2017/07/13-01:06:39 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""/>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?></metadata>
<defs>
<style>
.cls-1 {
fill: #fff;
fill-rule: evenodd;
}
</style>
</defs>
<path id="rect1" class="cls-1" d="M133,333.637L1426.05,171.265a157.557,157.557,0,0,1,175.99,136.647l157.93,1256.5L466.929,1726.79a157.574,157.574,0,0,1-176-136.65Z"/>
</svg>
In this example the viewBox of the <svg> is 100 in width and the image also takes up 100% of the width. So, no matter the actual width of the image it will always fill the entire SVG.
The <clipPath> fits in the size of the viewBox of the <svg> that holds the image. I know the width is 100, so I made the clippath 70 in height and width plus the extra height that the rotation takes up. This matches kind of the height of the images (unknown at this point).
I replaced the content of the <clipPath>. It is more "transparent" what the clip path does and easier to manipulate.
<!--IMAGE-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="300">
<image width="100%" href="https://res.cloudinary.com/alvarosaburido/image/upload/v1589435086/blog/The%20Magic%20of%20SVG%20Clip-path/pic_yo5eyq.png" clip-path="url(#mask)" />
</svg>
<!--MY SVG FILE-->
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
<defs>
<clipPath id="mask" transform="translate(15 0) rotate(-10 60 0)">
<rect width="50" height="50" />
<rect x="20" y="20" width="50" height="50" />
<rect x="20" width="50" height="50" rx="10" />
<rect y="20" width="50" height="50" rx="10" />
</clipPath>
</defs>
</svg>
Update
OP asks if the original path can be used as a clip-path. It can, but the viewBox needs to be modified accordingly. So, if the viewbox 0 0 2300 1800 is used the path fits the image.
<!--IMAGE-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2300 1800" width="300">
<image width="100%" href="https://res.cloudinary.com/alvarosaburido/image/upload/v1589435086/blog/The%20Magic%20of%20SVG%20Clip-path/pic_yo5eyq.png" clip-path="url(#mask)" />
</svg>
<!--MY SVG FILE-->
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
<defs>
<clipPath id="mask" transform="translate(350 0)">
<path id="rect1" class="cls-1" d="M133,333.637L1426.05,171.265a157.557,157.557,0,0,1,175.99,136.647l157.93,1256.5L466.929,1726.79a157.574,157.574,0,0,1-176-136.65Z"/>
</clipPath>
</defs>
</svg>
Update
To "path" or not to "path", that is the question. This third example is a better solution. The path is simpler and there are not that many child elements in <clipPath>.
<!--IMAGE-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11 11" width="300">
<image width="100%" href="https://res.cloudinary.com/alvarosaburido/image/upload/v1589435086/blog/The%20Magic%20of%20SVG%20Clip-path/pic_yo5eyq.png" clip-path="url(#mask)" />
</svg>
<!--MY SVG FILE-->
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
<defs>
<clipPath id="mask" transform="translate(2 0) rotate(-10 7 0)">
<path d="M 0 0 L 6 0 A 1 1 90 0 1 7 1 L 7 7 L 1 7 A 1 1 90 0 1 0 6 Z"/>
</clipPath>
</defs>
</svg>
Update
This fourth example is using the original path, BUT defined in a <clipPath> and used as an external reference in CSS. The external SVG file have the following content:
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="mask" transform="translate(350 0)">
<path id="rect1" class="cls-1" d="M133,333.637L1426.05,171.265a157.557,157.557,0,0,1,175.99,136.647l157.93,1256.5L466.929,1726.79a157.574,157.574,0,0,1-176-136.65Z"/>
</clipPath>
</defs>
</svg>
But for this example I replace the URL (like https://svgshare.com/i/dfw.svg#rect) to the SVG file with a data URI.
svg>image {
clip-path: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxkZWZzPgogICAgPGNsaXBQYXRoIGlkPSJtYXNrIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzNTAgMCkiPgogICAgICA8cGF0aCBpZD0icmVjdDEiIGNsYXNzPSJjbHMtMSIgZD0iTTEzMywzMzMuNjM3TDE0MjYuMDUsMTcxLjI2NWExNTcuNTU3LDE1Ny41NTcsMCwwLDEsMTc1Ljk5LDEzNi42NDdsMTU3LjkzLDEyNTYuNUw0NjYuOTI5LDE3MjYuNzlhMTU3LjU3NCwxNTcuNTc0LDAsMCwxLTE3Ni0xMzYuNjVaIi8+CiAgICA8L2NsaXBQYXRoPgogIDwvZGVmcz4KPC9zdmc+#mask');
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2300 1800" width="300">
<image width="100%" href="https://res.cloudinary.com/alvarosaburido/image/upload/v1589435086/blog/The%20Magic%20of%20SVG%20Clip-path/pic_yo5eyq.png" />
</svg>

cannot change height of a svg group element with javascript

<svg>
<!--SOME SVG CODE HERE-->
<g id="bg"> <!--More svg code-->
</g>
</svg>
<!--Imports-->
<script src="script.js"></script>
<link rel="stylesheet" href="st.css">
</html>
CSS:
#bg{
height:100%;
}
When i use javascript to change the height:
document.getElementById("bg").style.height = "500px"
nothing happens, and, when i try to print the height in screen, it's returns nothing:
alert(document.getElementById("bg").style.height)
what can i do to change the height with javascript.
if you need to give a width and a height to an element inside an svg element you have to use a <symbol> with a viewBox instead of a group. Next you have to use the <symbol>and you can give a width and a height to the <use> element
svg{border:solid}
<svg viewBox="0 0 200 100" width="300">
<symbol id="c" viewBox="0 0 20 20">
<circle cx="10" cy="10" r="5"/>
</symbol>
<use xlink:href="#c" x="10" y="10" width="40" height="40" />
</svg>

Export style with <svg> to .svg

I have an HTML file, that has a link to export all the <svg> inside to one .SVG. That's perfect, it's what I need and the community helped me a lot.
Now I have a problem that all the SVG inside the file, are clogged on top of each other.
I need them to keep the same 'display' they have on the webpage. I tried using internal, inline and external style... But nothing worked.
The X and Y I have in the <svg> is executed when I open the .SVG file in any program, but it doesn't affect anything on the HTML document.
I'm using the svg-converter.js to get all the .svg inside the <img> and convert to <svg>.
For some reason, inside that JS, something don't let me group all the <svg> inside another <svg>, only inside <div>. I have no idea if that is somehow causing the problem of the style not being exported.
Here is the working link.
Any help will be much appreciated.
Ps: I'd love to know why my question is downvoted.
You need to use x and y attributes to arrange your inner <svg> elements inside your root <svg> element.
Something like this (I've replaced the SVG contents with circles for brevity):
<svg xmlns="http://www.w3.org/3000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%" height="100%" viewBox="0 0 1224 1224">
<svg x="0" y="0" width="50%" height="50%" viewBox="0 0 612 612">
<circle fill="#7AB344" cx="306" cy="306" r="250"/>
</svg>
<svg x="50%" y="0" width="50%" height="50%" viewBox="0 0 612 612">
<circle fill="#8DD4F0" cx="306" cy="306" r="250"/>
</svg>
<svg x="0" y="50%" width="50%" height="50%" viewBox="0 0 612 612">
<circle fill="orange" cx="306" cy="306" r="250"/>
</svg>
<svg x="50%" y="50%" width="50%" height="50%" viewBox="0 0 612 612">
<circle fill="rebeccapurple" cx="306" cy="306" r="250"/>
</svg>
</svg>
THIS IS THE FINAL WORKING CODE:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/3000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<head>
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://brand.express/projects/teste/svg-converter.js"></script>
</head>
<body>
<main id="content">
<div id="mySVG" style="width:100%;padding:0;margin:0;">
<img src='img/leafers-sapling.svg' class='svg-convert' x="0" y="0" width="286px">
<img src='img/aqualine-sapling.svg' class='svg-convert' x="286px" y="0" width="286px">
<img src='img/leafers-sapling.svg' class='svg-convert' x="572px" y="0" width="286px">
<img src='img/aqualine-sapling.svg' class='svg-convert' x="858px" y="0" width="286px">
</div>
</main>
<script>
//transforma os .svg para <svg>
jQuery(document).ready(function($) {
$('.svg-convert').svgConvert({
onComplete: function() {
exportSVG(document.getElementById('mySVG'));
},
svgCleanupAttr: []
});
});
</script>
<script>
var exportSVG = function(divContainer) {
var svgContainer = document.createElementNS("http://www.w3.org/1999/xhtml", "svg");
svgContainer.setAttribute('width','1200px');
$('#mySVG').find("svg").clone().appendTo(svgContainer);
var svgData = svgContainer.outerHTML;
var a = document.createElement('a');
a.href = 'data:image/svg+xml; charset=utf8, ' + encodeURIComponent(svgData);
a.download = 'finalSVG.svg';
a.innerHTML = 'download the .SVG file';
document.body.appendChild(a);
};
</script>
</body>
</html>

How to pack text inside svg rect

I wish to fit svg text inside a rect. I could use an approach to compare the widths and add line breaks to the text, but it's not tedious.
Is there a more elegant way than this? Maybe by using CSS or d3?
UPDATE:the following code appends foreignObject using d3 but the div is not displayed. (it is there in the code inspecter)
var group = d3.select("#package");
var fo = group.append("foreignObject").attr("x", 15).attr("y", 15).attr("width", 190).attr("height", 90);
fo.append("div").attr("xmlns", "http://www.w3.org/1999/xhtml").attr("style", "width:190px; height:90px; overflow-y:auto").text("Thiggfis the dgdexsgsggs wish to fit insidegssgsgs");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<p id="p"></p>
<svg width="220" height="120" viewBox="0 0 220 120" id="package">
<rect x="10" y="10" width="200" height="100" fill="none" stroke="black"/>
</svg>
A namespace cannot be assigned by attr, it's a side effect of element creation. You need an html div so you need to tell d3 that by calling the element xhtml:div, once you do that, d3 will do the rest.
var group = d3.select("#package");
var fo = group.append("foreignObject").attr("x", 15).attr("y", 15).attr("width", 190).attr("height", 90);
fo.append("xhtml:div").attr("style", "width:190px; height:90px; overflow-y:auto").text("Thiggfis the dgdexsgsggs wish to fit insidegssgsgs");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<p id="p"></p>
<svg width="220" height="120" viewBox="0 0 220 120" id="package">
<rect x="10" y="10" width="200" height="100" fill="none" stroke="black"/>
</svg>
Here's a simple example of a foreignObject used to insert HTML markup into an SVG:
<svg width="220" height="120" viewBox="0 0 220 120">
<rect x="10" y="10" width="200" height="100" fill="none" stroke="black" />
<foreignObject x="15" y="15" width="190" height="90">
<div xmlns="http://www.w3.org/1999/xhtml" style="width:190px; height:90px; overflow-y:auto"><b>This</b> is the <i>text</i> I wish to fit inside <code>rect</code></div>
</foreignObject>
</svg>

Categories

Resources