Let's say that we have the following simple SVG element:
const svg = `<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M100 100C100 44.7715 55.2285 -1.95703e-06 0 -4.37114e-06L-4.37114e-06 100L100 100Z" fill="current"/>
</g>
</svg>`
The svg variable is a string and I want to extract all the child nodes from the svg (the <g> element for the specific example but can be more elements).
How can I do that without using a DOM manipulation library like JQuery?
Version 2
You can use str.split() to extract all the child elements from the <svg> as a string.
Working Example:
const svg = `<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M100 100C100 44.7715 55.2285 -1.95703e-06 0 -4.37114e-06L-4.37114e-06 100L100 100Z" fill="current"/>
</g>
</svg>`;
let g;
g = svg.split('xmlns="http://www.w3.org/2000/svg">')[1];
g = g.split('</svg>')[0];
g = g.trim();
console.log(g);
Version 1
I want to extract the content from the <svg> which is the
element inside it.
If I've understood correctly:
you want to extract only the <g> element
you know that the <svg> contains a <g> element
you know that there is only one <g> element
you can use str.split() to extract the <g> from the <svg>.
Working Example:
const svg = `<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M100 100C100 44.7715 55.2285 -1.95703e-06 0 -4.37114e-06L-4.37114e-06 100L100 100Z" fill="current"/>
</g>
</svg>`;
let g;
g = svg.split('<g ')[1];
g = g.split('</g>')[0];
g = '<g ' + g + '</g>'
console.log(g);
Either use JavaScripts built in DOMParser
var p= new DOMParser()
var d= p.parseFromString(svg,"text/html")
var g= d.querySelector("svg g")
OR make your own html/xml parser from scratch by looping through each character, checking for <, if found look for some letters next for the tag, if found look for other >, get all content in between for attributes, rinse and repeat
Related
I have this code snippet:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="d3.min.js"></script>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" fill="none" fill-rule="evenodd">
<path d="M377.4528 264.0669q3.7795-113.3858 151.1811-113.3858m0 0q0 11.3386 0 11.3386 0 139.8425-41.5748 139.8425m3.7795 0q-37.7953 0-37.7953 0 56.6929 0 56.6929 113.3858-132.2835 0-132.2835-79.3701"/>
<path d="M377.4528 335.878q0 79.3701-132.2835 79.37-3.7795-113.3857 56.6929-113.3857-75.5905 0-75.5905-151.1812 151.1811 0 151.1811 113.3859"/>
<ellipse cx="377.4528" cy="284.8543" rx="3mm" ry="16.5mm"/>
</svg>
<script type="text/javascript">
d3.json("./data.json").then(function(data){
var svg = d3.select("body")
.selectAll("svg")
.... something here
})
</script>
</body>
I cannot update, with values from data.json file, for example cx, cy, rx etc... and most of all values contained in path string. Someone can help me? Thanks to all.
In the example below I'm using a mock API to simulate your d3.json() request and then making up a new value for rx and ry for the ellipse. I've used timeout just for demonstration purposes so that you can see the ellipse changes - you wouldn't need to do that in your code.
So, once you have the new data, you can use a selection e.g. d3.select("svg ellipse") so you can then update the attributes of the selection.
If your new data has new d attributes for the path elements, then you can use the same approach e.g. path.attr("d", newD). If you want to change datapoints within the path definition, it would depend on your incoming data and better asked as a separate question.
// mock api to make this snippet work
const url = "https://jsonplaceholder.typicode.com/todos/1";
// wait for 1.5s and apply new attributes to ellipse
setTimeout(function() {
// get the new data
d3.json(url).then(function(data) {
// mock up some data
data = [{rx: "5mm", ry: "20mm"}];
changeEllipse(data[0]);
});
}, 1500);
function changeEllipse(data) {
// select the ellipse with a selector
const ellipse = d3.select("svg ellipse");
// change attributes
ellipse
.attr("rx", data.rx)
.attr("ry", data.ry);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" stroke="#000" stroke- linecap="round" stroke-linejoin="round" fill="none" fill-rule="evenodd">
<path d="M377.4528 264.0669q3.7795-113.3858 151.1811-113.3858m0 0q0 11.3386 0 11.3386 0 139.8425-41.5748 139.8425m3.7795 0q-37.7953 0-37.7953 0 56.6929 0 56.6929 113.3858-132.2835 0-132.2835-79.3701"/>
<path d="M377.4528 335.878q0 79.3701-132.2835 79.37-3.7795-113.3857 56.6929-113.3857-75.5905 0-75.5905-151.1812 151.1811 0 151.1811 113.3859"/>
<ellipse cx="377.4528" cy="284.8543" rx="3mm" ry="16.5mm"/>
</svg>
So I create a paperjs path object and put it in a group:
let path = new paper.Path.Rectangle(originpoint, deviceWidth, deviceHeight);
let ret = new paper.Group();
ret.addChild(path);
And then I export the ret to SVG as follows:
let svg = ret.exportSVG({
asString:true
});
But the output of this is only coming as :
<g xmlns="http://www.w3.org/2000/svg" fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path d="M0,85000v-85000h135000v85000z"/></g>
As you can notice there is no <svg> element here. How can I get the standard SVG header without having to manually splice it into the code. I'm obviously missing something because the project I am working on generates SVG's of whats displayed on the canvas with the <svg> elements with the following header:
<svg width="135mm" height="85mm" viewBox="0 0 135000 85000" xmlns="http://www.w3.org/2000/svg">
Im sure I'm missing some detail of how paper.js works, its seems like paper.js has specific objects that it puts into the svg container. Does anyone know what it is ? or What I might be doing wrong....
Thanks!
I am not a paper.js user or something but just playing around with the library I realized that exportSVG is inherited from paper prototype, so if you call it from current project paper.project then it seems to work as intended:
paper.setup(new paper.Size(300, 200));
let path = new paper.Path.Rectangle([0,0], 100, 100);
let ret = new paper.Group();
ret.addChild(path);
let svg = paper.project.exportSVG({
asString:true
});
console.log(svg);
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200"><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g><path d="M0,100v-100h100v100z"/></g></g></svg>
FIDDLE: https://jsfiddle.net/ibowankenobi/juem7f80/1/
Scroll to the bottom to see the snippet.
How would I go about creating an SVG DOM element from a String?
Example:
var svgStr = '<svg width="500" height="400" xmlns="http://www.w3.org/2000/svg"><!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ --><g><title>background</title><rect fill="#fff" id="canvas_background" height="402" width="502" y="-1" x="-1"/><g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid"><rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/></g></g><g><title>Layer 1</title><path id="svg_1" d="m118,242l64,-153l63,157c0,0 45,-71 49,-68c4,3 11,146 12,146c1,0 -173,-7 -173,-7c0,0 -61,-72 -61,-72c0,0 110,-156 46,-3z" fill-opacity="0.7" stroke-width="2" stroke="#995757" fill="#995757"/></g></svg>';
You can use DOMParser to parse an XML string.
var parser = new DOMParser();
var doc = parser.parseFromString(stringContainingXMLSource, "image/svg+xml");
The root element for the parsed string will be doc.documentElement
For this to work properly cross-browser you'll need to set the html namespace i.e. your string will need to look like this...
var svg2='<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" ...
Assuming you are using JavaScript, you can simply pass that string as the innerHTML of an existing element obtained via the DOM API:
var svg2 = "<svg ...> ... </svg>";
var container = document.getElementById("container");
container.innerHTML = svg2;
See: JSFiddle
I was building SVG chart and needed to enable user to pass any SVG to make it into a chart annotation. The solution was:
index.html - the root SVG element I am attaching child SVGs to
<svg id="chart_SVG" width="900" height="600" role="img" xmlns="http://www.w3.org/2000/svg"></svg>
api.ts - API to add annotation (written in TypeScript). x, y - coordinates where to place the annotation
function drawAnnotation(x: number, y: number, svgContent: string, svgId: string): SVGElement {
const svgRoot = document.getElementById("chart_SVG");
const svgNode = document.createRange().createContextualFragment(svgString);
svgRoot.appendChild(svgNode);
const newNode = this.svgRoot.lastChild as SVGElement;
newNode.id = svgId;
newNode.setAttribute("x", x.toString());
newNode.setAttribute("y", y.toString());
return newNode;
}
example.ts
drawAnnotation(
100,
100,
'<svg><g><rect x="0" y="0" width="100%" height="100%" stroke="red" stroke-width="10" fill="orange"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="Verdana" font-size="24" fill="blue">TEXT</text></g></svg>',
"myNewNode"
)
Reading and writing the innerHTML of an SVG within HTML seems to work well except in Internet Explorer (9-11): http://cs.sru.edu/~ddailey/svg/IframeSVG.htm . If one needs IE compatibility (as in for a real web app) then use DOM methods to create a suitable container (object, iframe or embed) and build the SVG, one childNode at a time, through DOM methods within that container. ) It's a bit of a chore, but the basics are covered at http://www.w3.org/Graphics/SVG/IG/resources/svgprimer.html#SVG_HTML.
instead of returning with parent, you can do the following trick.
const template = `
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 13L13 1M1 1L13 13" stroke="#111827" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`
const span = document.createElement('span')
span.innerHTML = template
return span.firstChild
Here is a JS function example to return a SVGELement instance:
function svgElementFromString(str) {
const div = document.createElement('DIV');
div.innerHTML = str;
const svg = div.querySelector('svg');
if (!svg) {
throw Error('<svg> tag not found');
}
return svg;
}
I have an svg file containing a group with a single line element. I can make use of the use element and make several reference copies in any position I want them. However, I want to use javascript to add and remove the use element dynamically. Is there a way to use javascript to insert an svg use element of my line into my group?
<svg version="1.1" id="ex1-3rds-quarter-s" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" width="323.333px" height="55.333px" viewBox="0 0 323.333 55.333" enable-background="new 0 0 323.333 55.333"
xml:space="preserve">
<g id="ledgerlines">
<line id="MidCLine1" fill="none" stroke="#000000" stroke-width="0.75" stroke-miterlimit="10" x1="48.09" y1="41.694" x2="57.924" y2="41.694"/>
</g>
</svg>
var svgns = "http://www.w3.org/2000/svg";
var xlinkns = "http://www.w3.org/1999/xlink";
// Get a reference to the <g> element
var g = document.getElementById("ledgerlines");
// Create a <use> element
var use = document.createElementNS(svgns, "use");
// Add an 'href' attribute (using the "xlink" namespace)
use.setAttributeNS(xlinkns, "href", "#MidCLine1");
// Offset the line down by 10
use.setAttribute("y", "10"); // offset = y+10
// Add the <use> to the <g>
g.appendChild(use);
Demo here
I'm creating several click-able maps - and have been unsuccessful in trying to find how to access a tag for a path so that I can make all the different counties in the map clickable. (the SVG file is incredibly complicated... this is the structure and I can't change it - it's getting automatically generated for all the different states):
<svg width="932.25827pt" height="569.25354pt" viewBox="0 0 932.25827 569.25354" enable-background="new 0 0 932.25827 569.25354"
version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<g id="Layers">
<g id="Ohio_Counties">
<clipPath id="SVG_CP_1">
<path d="M0,569.25354L0,0L932.25827,0L932.25827,569.25354L0,569.25354z"/>
</clipPath>
<path clip-path="url(#SVG_CP_1)" fill="none" stroke="#4E4E4E" stroke-width="0.48003" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" d="
M374.18483,441.14215L375.38491,440.90214L375.62493,440.90214L376.58499,440.66213L376.82501,440.66213L377.54505,440.42212
L378.02508,440.42212L378.2651,440.42212L378.74513,440.42212L379.46518,440.66213L381.14529,440.66213L381.38531,440.66213L
381.62532,440.66213L382.10536,440.66213L382.8254,440.66213L384.50551,440.90214L384.74553,440.90214L386.18563,440.90214L386.42564,441.38217
L386.42564,441.62218L386.42564,441.86219L386.66566,441.86219L386.90567,441.86219L386.90567,442.1022L386.90567,442.34221L"/>
</path>
</g>
</g>
/* There are about 87 more paths in the file... this is just a part of one.*/
I'm using jquery.svg - but haven't found the solution in the documentation.
Here's the javascript:
function changeState(newState){
nS = newState.replace(/\s/g, '');
try {
var map = 'mapLayers/AEP'+nS+'Counties.svg';
$("#contentCounties").empty();
var currentCountyMap = $('#contentCounties').svg({loadURL: loc, onLoad: addClickEvents(this)});
} catch(err){
alert("the map " + currentCountyMap + " does not exist");
}
}
function addClickEvents(){
// this is where I want to put the code to
// add click events to the individual paths.
// Whether it's running a loop or using a jquery selector
// I'm fine with either.
// This is, in theory, what I think I want - but alas, it is wrong:
var svg=document.getElementByTagName("path");
alert(svg);
}
Looks like you've just left the s out of getElementsByTagName