How to create an svg element programmatically in an svg document? - svg

I know that we can use the document.createElementNS("http://www.w3.org/2000/svg","line"); to create a svg element embeded in a html page.
But, this method doesn't seems to work in a standalone svg document.
Actually, I am trying to draw the national flag of India in svg, but, drawing the 24 spokes in the wheel of the flag would be very time consuming. So, I thought of drawing them programmatically through JavaScript.
Any help on how to create elements programmatically in a standalone svg document will be appreciated.

You can use javascript inside an svg element. I've done only the center of the flag.
Please observe the viewBox="-50 -50 100 100". The point {x:0,y:0} is in the center of the SVG canvas.
svg{border:1px solid; width:90vh;}
<svg viewBox="-50 -50 100 100" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" >
<g id="center" fill="blue" stroke="blue">
<circle r="5" />
<circle id="outline" stroke-width="4" fill="none" r="48" />
<polygon id="spoke" points="0,0 20,1.5 48,0 20,-1.5 0,0" fill="blue" stroke="none" />
<circle id="dot" r="2.5" cx="46" transform="rotate(7.5)" />
</g>
<script>
<![CDATA[
const SVG_XLINK = "http://www.w3.org/1999/xlink";
const SVG_NS = 'http://www.w3.org/2000/svg';
// a function that creates a new `<use>` element and rotates it around the origin of the SVG canvas
function reuse(used,parent,i) {
let use = document.createElementNS(SVG_NS, 'use');
use.setAttributeNS(SVG_XLINK, 'xlink:href', used); use.setAttributeNS(null,"transform" ,`rotate(${360*i/24})`);
parent.appendChild(use);
}
// a loop that creates 24 use elements
for(let i = 0;i < 24; i++ ){
reuse("#spoke",document.querySelector("#center"),i);
reuse("#dot",document.querySelector("#center"),i);
}
]]>
</script>
</svg>

You can't do what you're trying to do since Javascript shouldn't be included in, and isn't understood or processed inside a SVG file.

Related

How do I <use> a named <tspan> element in an SVG document?

I'm trying to define a tspan early in an SVG document so it can be transcluded later inside a text. At least in Firefox, the following code does not produce this result.
<svg version="1.1" width="500" height="500" xmlns="http://www.w3.org/2000/svg">
<defs>
<tspan id="transcluded-text">This is a text</tspan>
</defs>
<text>
<use href="#transcluded-text"/>
</text>
</svg>
Using Firefox's Inspect tool, the use element contains a shadow DOM (#shadow-root) as expected, but the shadow DOM itself is empty.
Without using Javascript, is it possible to transclude a tspan inside a text like this?
The text element can only contain text content child elements (so, not <use>) and the only text related element the defs element can contain is <text>. So, it all points to that the <tspan> cannot be used in this way.
<svg version="1.1" width="500" height="500" xmlns="http://www.w3.org/2000/svg">
<defs>
<text x="10" y="20" font-size="20" id="transcluded-text">This is a text</text>
</defs>
<use href="#transcluded-text"/>
</svg>
Since you can not use <use> elements within a <text> element;
or a bare <tspan> within <defs>
A modern Native Web Component can do the replace job:
(this will cause a FOUC you might want to deal with)
<script>
customElements.define("svg-use-replacer", class extends HTMLElement {
connectedCallback() {
setTimeout(() => { // make sure SVG is parsed
this.querySelectorAll('use').forEach(use => {
let href = use.getAttribute("href");
let tspan = this.querySelector(href);
use.replaceWith(tspan); // .replaceWith was not available in IE
});
// if the bare <svg> is required in the DOM:
// this.replaceWith(this.querySelector("svg"));
});
}
});
</script>
<svg-use-replacer>
<svg version="1.1" width="500" height="500" xmlns="http://www.w3.org/2000/svg">
<defs>
<tspan id="foo" stroke="green">Wonderful</tspan>
</defs>
<text x="10" y="20" font-size="20">
Hello
<use href="#foo" /> Web Component
</text>
</svg>
</svg-use-replacer>
Notes
The oldskool way is to attach a class and after that Element is parsed run JavaScript on it.
With Web Components it totally does not matter WHEN the Web Component is defined.
You can execute the above script any time you want.

Hitting a break wall with SVG

Why a light colored rectangle background showed up on the area behind xlink every time it is touched or clicked?
Here is my SVG code:
<svg version="1.1" viewBox="0 0 147.86 258.44" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g class="bglyr" fill="none" transform="translate(-30.575,-20.926)">
<a xlink:href="https://google.com">
<path id="link1" fill="none" onmouseover="change(this)" onmouseout="unchange(this)" d="m104.43 242.44 11.742 6.1664 57.094 29.884-137.62 0.0617z" />
<a />
</g>
</svg>
JavaScript:
function change(item) {
item.style.fill = "#42d46b";
item.style.opacity = "1.0";
}
function unchange(item) {
item.style.fill = "none";
item.style.opacity = "0.0";
}
Just in case your question is actually about why your example code doesn't work, then I can explain that for you.
Mouse events won't be triggered if your <path> has nothing to hover over. When the fill is "none" or the opacity is 0. No mouse events will be triggered. To fix this, use fill="transparent" instead. This behaves the same way, but is detected as if it was any other non-transparent fill colour.
function change(item) {
item.style.fill = "#42d46b";
}
function unchange(item) {
item.style.fill = "transparent";
}
<svg version="1.1" viewBox="0 0 147.86 258.44" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g class="bglyr" fill="none" transform="translate(-30.575,-20.926)">
<a xlink:href="https://google.com">
<path id="link1" fill="transparent" onmouseover="change(this)" onmouseout="unchange(this)" d="m104.43 242.44 11.742 6.1664 57.094 29.884-137.62 0.0617z" />
</a>
</g>
</svg>

Dragging wrong riot component

I'm creating a draggable riot js based SVG element.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g name="green" data-is="r-rect" x="30" y="230" width="256" height="64" rx="5" ry="5" draggable="true" fill="green"></g>
<g name="blue" data-is="r-rect" x="10" y="110" width="256" height="64" rx="5" ry="5" draggable="true" fill="blue"></g>
</svg>
r-rect.tag
<r-rect>
<rect ref="rect" onmousedown={hold} />
<script>
tag = this;
tag.hold = hold;
tag.x= Number.parseInt(opts.x);
tag.y= Number.parseInt(opts.y);
tag._name= opts.name;
tag.on("mount", function(e) {
tag.refs.rect.setAttribute("x", opts.x);
tag.refs.rect.setAttribute("y", opts.y);
tag.refs.rect.setAttribute("width", opts.width);
tag.refs.rect.setAttribute("height", opts.height);
opts.rx && (tag.refs.rect.setAttribute("rx", opts.rx) );
opts.ry && (tag.refs.rect.setAttribute("ry", opts.ry) );
})
function hold(e){
console.log(tag._name)
}
</script>
</r-rect>
I add 2 r-rect tags. Try to drag blue rectangle. But mousedown event of green rectangle triggers always .
http://jsfiddle.net/1k2gacy1/1/
I found the issue. It was happening because I was assigning the tag instance in global variable. So I've changed this line tag = this; to var tag = this;. And it solved the issue.

Using predefined SVG file for creating a custom JointJS shape with ports

I have a series of pre-created SVG symbols I want to use in JointJS.
I've searched about using precreated SVGs and I found to be possible to create a complete custom elements using SVG by putting the SVG in the 'markup' property - (https://groups.google.com/forum/#!topic/jointjs/pQvN_0lXPVk).
Below is the example of an SVG. Your help about how can I embed this definition in the markup property and add ports to it will be highly appreciated.
Thanks
<?xml version="1.0" standalone="no"?>
<svg viewBox="0 0 1024 768" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" stroke-linecap="round" stroke-linejoin="round" fill-rule="evenodd" xml:space="preserve" >
<defs >
<clipPath id="clipId0" >
<path d="M0,768 1024,768 1024,0 0,0 z" />
</clipPath>
</defs>
<g stroke-width="0.1" clip-path="url(#clipId0)" fill="none" stroke="rgb(0,0,0)" />
<g stroke-width="0.25" clip-path="url(#clipId0)" fill="rgb(0,0,0)" stroke="none" >
<path d="M1013.96,634.98 10.0392,634.98 1013.96,133.02 z" />
</g>
<g stroke-width="0.25" clip-path="url(#clipId0)" fill="none" stroke="rgb(0,0,0)" >
<polyline points="10.0392,133.02 1013.96,133.02 1013.96,634.98 10.0392,634.98 10.0392,133.02 " />
<polyline points="10.0392,634.98 1013.96,133.02 " />
</g>
</svg>
You can add the SVGImageElement to your markup to display arbitrary SVG in your shapes. Just convert the SVG file content to dataURL and set the xlink:href attribute.
var shape = new joint.shapes.basic.Image({
// markup: '<g class="rotatable"><g class="scalable"><image/></g><text/></g>',
attrs: {
image: {
'xlink:href': 'data:image/svg+xml;utf8,' + encodeURLComponent(svgFileContent)
}
}
});
http://jsfiddle.net/kumilingus/eqen3pdf/
In order to create a shape showing an SVG image and yet having ports you can e.g. use devs.Model shape and replace the only SVGRectElement in its markup with an SVGImageElement.
new joint.shapes.devs.Model({
markup: '<g class="rotatable"><g class="scalable"><image class="body"/></g><text class="label"/><g class="inPorts"/><g class="outPorts"/></g>',
attrs: {
'.body': {
'xlink:href': 'data:image/svg+xml;utf8,' + encodeURLComponent(svgFileContent)
},
inPorts: ['in'],
outPorts: ['out']
});
http://jsfiddle.net/kumilingus/tm2ctvxq/
Note, that it's possible to insert the SVG file content directly into your markup (without <?xml version="1.0" standalone="no"?>). I would not recommended it for more complex SVG though.
For instance when SVG contains an SVGClipPathElement with an id. Creating 2 instances of your shape breaks condition that all the IDs in the SVG must be unique.

XMLSerializer mess up links

I have a svg object with defs:
defs.append('svg:pattern')
.attr('id', 'blue')
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', '40')
.attr('height', '39')
.append('svg:image')
.attr('xlink:href', 'images/blue.png')
.attr('width', '40')
.attr('height', '39');
In my code I use XMLSerializer to POST data:
var svg1 = tmp.getElementsByTagName("svg");
var svg_xml = (new XMLSerializer).serializeToString(svg1[0]);
In Safari I get the desired result:
<svg width="850" height="200">
<pattern id="blue" patternUnits="userSpaceOnUse" width="40" height="39">
<image xlink:href="images/blue.png" width="40" height="39"></image>
</pattern>
In Firefox or IE I get strange results:
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="200">
<defs>
<pattern id="blue" patternUnits="userSpaceOnUse" width="40" height="39">
<image a0:href="images/plateColor/blue.png" xmlns:a0="http://www.w3.org/1999/xlink" width="40" height="39"/>
</pattern>
What am I doing wrong?
Thanks
Rolf
This is not a bug in D3.js. Here's how you can solve the problem for any SVG:
First, grab your SVG element:
svg = document.querySelector("svg");
Now, an SVG element can have more than one of these attributes in different namespaces, so we have to remove these first to prevent duplicate attributes. Otherwise the SVG is no longer valid after being serialized to a string by XMLSerializer:
svg.removeAttribute("xmlns");
svg.removeAttribute("xmlns:xlink");
Finally, add the same attributes, but with the proper namespace:
prefix = "http://www.w3.org/2000/xmlns/";
if (!svg.hasAttributeNS(prefix, "xmlns")) {
svg.setAttributeNS(prefix, "xmlns", "http://www.w3.org/2000/svg");
}
if (!svg.hasAttributeNS(prefix, "xmlns:xlink")) {
svg.setAttributeNS(prefix, "xmlns:xlink", "http://www.w3.org/1999/xlink");
}
Now the XMLSerializer should no longer produce a weird output.

Resources