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

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.

Related

Adding title element to paperjs SVG (using node.js)

I would like to add a <title> element to an SVG file generate with paper.js and its exportSVG function, within a node.js application (using paperjs-jsdom package)
So far, my files look like this :
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="642" height="377" viewBox="0,0,642,377">
<g>
...
</g></svg>
And I would like it to look like this :
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="642" height="377" viewBox="0,0,642,377">
<title>toto</title>
<g>
...
</g></svg>
I have looked into jsdom (which should be included in the paperjs-jsdom package), trying to load <svg> content in a DOM, but I have not been able to make it work.
If your concern is just adding a <title> tag after the <svg> tag, a workaround without even using JSDOM could be to use a RegExp.
Here is a sketch demonstrating the solution.
// Draw a circle.
new Path.Circle({
center: view.center,
radius: 50,
fillColor: 'orange'
})
// Export Paper.js project as SVG string.
var svg = project.exportSVG({asString:true});
// Add title after <svg> tag.
svg = svg.replace(/(^<svg[^>]+>)/, '$1<title>My Title</title>');
// Show result in console.
console.log(svg);
Result is:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1151" height="937" viewBox="0,0,1151,937">
<title>My Title</title>
<g fill="#ffa500" 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="M525.5,468.5c0,-27.61424 22.38576,-50 50,-50c27.61424,0 50,22.38576 50,50c0,27.61424 -22.38576,50 -50,50c-27.61424,0 -50,-22.38576 -50,-50z"/>
</g>
</svg>

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

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.

making different parts of an SVG image clickable

I have searched through the net and have come to discover that images are made clickable when they are converted to SVG format, however I am still not sure how to do this.
the image below for example, I need every box to be clickable, how do you go about doing this and is there any app that can help me, thank you in advance
enter image description here
You could do this with an SVG. But you would probably find it much easier to do with an HTML image map.
If you're able to edit the SVG itself you can wrap each element with an anchor tag.
<svg>
<a href="www.link1.com">
<rect x="10" y="10" width="60" height="50" fill="#ddd">
</a>
<a href="www.link2.com">
<rect x="75" y="10" width="60" height="50" fill="#ddd">
</a>
</svg>
Otherwise you can use Javascript to add a click handler to each element.
document.getElementById("rect1").addEventListener("click", function() {
window.open('www.link1.com')
});
document.getElementById("rect2").addEventListener("click", function() {
window.open('www.link2.com')
});
<svg>
<rect id="rect1" x="10" y="10" width="60" height="50" fill="#ddd"/>
<rect id="rect2" x="75" y="10" width="60" height="50" fill="#ddd"/>
</svg>

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.

SVG Foreign Object not displaying with SVG Namespace

I have the following SVG file.
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!--<svg width="100%" height="500">-->
<svg id="deviceImage" class="DeviceImage" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMinYMin" currentScale="1" width="3000px" height="1500px" oncontextmenu="return false">
<script type="text/ecmascript">
<![CDATA[
function ExmpleFunction(event, componentGroup)
{
alert('Hello World');
}
]]>
</script>
<defs>
<!-->>>TestShape>>>-->
<symbol id="TestShape">
<polygon points="1,1 650,1 630,175 21,175"></polygon>
</symbol>
<!--<<<TestShape<<<-->
</defs>
<!--TestShape-->
<g id="p1" transform="translate(50, 35)" visibility="visible">
<use id="p2Basic" xlink:href = "#TestShape"/>
</g>
<rect x="500" y="10" width="100" height="150" fill="blue"/>
<foreignObject x="0" y="0" width="200" height="250">
<div xmlns="http://www.w3.org/1999/xhtml">
<embed id="wert" src="TestModule-Basic.svg" type="image/svg+xml" />
</div>
</foreignObject>
</svg>
The aim is to embed another svg image which supports the internal script (so this can't be done as an image) so I am using foreignObject
When run the above code displays the test shape and the rectangle, but it does not display the svg image in the foreignObject.
By experimenting I discovered that if I remove the xmlns="http://www.w3.org/2000/svg" from the main SVG node, the svg image in the foreignObject is displayed but the test shape and rectangle are not displayed. I also see the text within the script tag as part of the image.
I obviously need to include the svg namespace but I am not sure how I get both to display?
All help appreciated!

Resources