jQuery-added svg elements do not show up - javascript

Sorry if this has already been answered, I am new to SO.
I am trying to create svg elements using jquery, and I have this code as part of an HTML page:
<svg viewBox="0 0 1000 500">
<defs>
<clipPath id="clip">
<ellipse cx="100" cy="250" rx="200" ry="50" />
</clipPath>
</defs>
<g>
<path d="M 0,0 L 1000,0 1000,500 0,500"
fill="#9ADEFF" />
<path id="boat" stroke="none" fill="red"
d="M 100,175 L 300,175 300,325 100,325"
clip-path="url(#clip)" />
</g>
<g id="0002" width="100" height="100%"
transform="translate(1000)">
<line x1="50" y1="0" x2="50" y2="300"
stroke="green" stroke-width="100" />
</g>
</svg>
and this Javascript (with jQuery 1.9):
var id = 10000,
coinArray = []
function generateNextLine(type) {
$('svg').append($(type()))
return $('svg')[0]
}
function idNo() {
id++
return ((id-1)+"").substr(-4)
}
function random(x,y) {
if (!y) {
y=x
x=0
}
x=parseInt(x)
y=parseInt(y)
return (Math.floor(Math.random()*(y-x+1))+x)
}
function coins() {
coinArray[id%10000]=[]
var gID = idNo(), x,
g=$(document.createElement('g')).attr({
id: gID,
width: "100",
height: "100%"
})
while (3<=random(10)) {
var randomPos=random(50,450)
coinArray[(id-1)%10000][x] = randomPos
$(g).append(
$(document.createElement('circle'))
.attr({
cx: "50",
cy: randomPos,
r: "50",
fill: "yellow"
})
)
x++
}
return $(g)[0]
}
When I run generateNextLine(coins);, the svg adds this element:
<g id="0000" width="100" height="100%">
<circle cx="50" cy="90" r="50" fill="yellow"></circle>
</g>
However, the actual display of the svg doesn't change. If I add this code directly to the svg, it renders as I would expect, but running my javascript function does not seem to do anything to the display. I am using Chrome 28, on OS X Lion.

You must create SVG elements in the SVG namespace which means you can't do
document.createElement('g')
but in instead you must write
document.createElementNS('http://www.w3.org/2000/svg', 'g')
same for circle etc.

Related

Scripted SVG animation no-longer working in some browsers

I am trying to work out why the code below no-longer works in Firefox or Chrome.
The files were last modified over ten years ago.
The static svg dispays, but the script does not run. To my considerable surprise, it does work as it should in Edge, as does a more complicated diagram with interactive elements.
There is probably some obscure setting I need to doctor in Firefox, but I don't know where to look. I don't know when I last tried one of these files, but I would be fairly sure they still worked a couple of years ago.
The code is probably full of daftnesses, as I have done very little javascript, and I probably should now be using requestanimationframe, but that is not the point - it has worked, and still does in Edge.
(Question edited to remove link to irrelevant SMIL version of the animation.)
This is the html file:
<html>
<head>
<title>
SVG slider-crank animated by script
</title>
</head>
<body onload="main()">
<script type="text/javascript">
<!--
var svgdoc = null;
var crank = null;
var crosshead = null;
var conrod = null;
var pi = Math.PI;
function main()
{
var timer = null;
var angle = 0;
var diagram = document.getElementById('svg');
if (diagram && diagram.contentDocument)
{
svgdoc = diagram.contentDocument;
}
else
{
try
{
svgdoc = diagram.getSVGDocument();
}
catch(exception)
{
alert("Unable to get SVG document");
}
}
crank = svgdoc.getElementById('ShowCrank');
crosshead = svgdoc.getElementById('ShowCrosshead');
conrod = svgdoc.getElementById('ShowConRod');
timer = setInterval(function(){(angle = rotation(angle))}, 25);
}
function rotation(angle)
{
var step = 3;
var theta = angle * pi / 180;
var alpha = Math.asin(Math.sin(theta) / 5);
var offset = 100 * (Math.cos(theta) -1) - 500 * (Math.cos(alpha) - 1);
crank.setAttributeNS(null, 'transform', ("rotate(" + angle + ", 800, 300)"));
crosshead.setAttributeNS(null, 'transform', ("translate(" + offset + ", 0)"));
conrod.setAttributeNS(null, 'transform', ("translate(" + offset + ", 0) rotate(" + (alpha * 180 / pi) + ", 400, 300)"));
angle = angle < 360 - step ? angle + step : 0;
return angle;
}
-->
</script>
<object id="svg" type="image/svg+xml" data="Slider_Crank.svg" width="1200" height="800">
<param name="src" value="Slider_Crank.svg">
</object>
</body>
</html>
This is the svg file:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
baseProfile="full"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ev="http://www.w3.org/2001/xml-events"
width="1200"
height="800">
<title> Slider-Crank </title>
<defs>
<rect
id="Slidebar"
stroke-width="1"
stroke="black"
fill="silver"
fill-opacity="1"
x="0"
y="-12"
width="300"
height="24"
/>
<g id="Crosshead" stroke-width="1" stroke="black" fill-opacity="1">
<rect
fill="gold"
x="-50"
y="-25"
width="100"
height="50"
/>
<circle cx="0" cy="0" r="15" fill="white"/>
</g>
<g id="Crank" stroke-width="1" stroke="black" fill-opacity="1">
<path fill="silver"
d="M 99.959 40.000
A 40 40 0 0 0 99.959, -40.000
A 450 450 0 0 1 9.950, -49.000
A 50 50 0 1 0 9.950, 49.000
A 450 450 0 0 1 99.959, 40.000
z"/>
<circle cx="100" cy="0" r="25" fill="white"/>
<circle cx="0" cy="0" r="30" fill="lightgrey"/>
</g>
<g id="ConRod" stroke-width="1" stroke="black" fill-opacity="0.7">
<path fill="silver"
d="M 12.387 21.715
A 30 30 0 0 1 27.551 17.776
L 453.475 22.035
A 30 30 0 0 1 473.243 29.733
A 40 40 0 0 1 473.243 -29.733
A 30 30 0 0 1 453.475 -22.035
L 27.551 -17.776
A 30 30 0 0 1 12.387 -21.715
A 25 25 0 0 1 12.387 21.715
z"/>
<circle cx="0" cy="0" r="25" fill="silver"/>
<circle cx="0" cy="0" r="15" fill="white"/>
<circle cx="500" cy="0" r="40" fill="silver"/>
<circle cx="500" cy="0" r="25" fill="white"/>
</g>
</defs>
<use id="ShowTopSlidebar" xlink:href="#Slidebar" x="150" y="263"/>
<use id="ShowBottomSlidebar" xlink:href="#Slidebar" x="150" y="337"/>
<use id="ShowCrosshead" xlink:href="#Crosshead" x="400" y="300"/>
<use id="ShowCrank" xlink:href="#Crank" x="800" y="300"/>
<use id="ShowConRod" xlink:href="#ConRod" x="400" y="300"/>
</svg>
Thanks to Robert Longson: Firefox about:config setting 'security.fileuri.strict_origin_policy'

Add a prefix to mutliple elements from the parent attribute

I'm trying to figure out how to add a unique prefix to ID's and other reference links.
I have multiple identical SVGs on a page. These SVGs serve as a wrapper (they're devices - laptop, phone, etc.) which have image link that is inserted into the SVG after load. The problem is, the ID's for these svgs are identical so they all conflict with each other. What I'm trying to do (but am open to better solutions) is to insert a unique ID to each id,xlink:href, url(#, etc. but pass over the <image> href attribute and fill and stroke attributes.
EDIT: I added/tweaked the code provided by #Temani, which got me closer to my desired result, however, I'm now getting all the ID's added to each SVG element. Formatted the code to be executable.
Desired output:
<div class="device" data-screen="[[IMAGE TO USE]]" data-name"[[UNIQUE ID]]">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="635" height="420" viewBox="0 0 635 420">
<defs>
<path id="[[UNIQUE ID]]-path-1"/>
<rect id="[[UNIQUE ID]]-path-3" />
<pattern id="[[UNIQUE ID]]-pattern-4">
<use transform="scale(13.1875)" xlink:href="#[[UNIQUE ID]]-image-5"/>
</pattern>
<image id="[[UNIQUE ID]]-image-5" href="[[IMAGE LINK THAT IS INSERTED AFTER LOAD]]"/>
</defs>
<g fill="none" fill-rule="evenodd">
<rect fill="#fff" stroke="#2D8EFF" />
<g >
<mask id="[[UNIQUE ID]]-mask-2" fill="#fff">
<use xlink:href="#[[UNIQUE ID]]-path-1"/>
</mask>
<g mask="url(#[[UNIQUE ID]]-mask-2)">
<mask id="mask-6" fill="#fff">
<use xlink:href="#[[UNIQUE ID]]-path-3"/>
</mask>
</g>
</g>
</g>
</svg>
</div>
Right now - both ID's (first and second) gets added to each SVG element. I also
$('.device').each(function() {
//we get the needed id using $(this) that refer to actual device
var id = $(this).data('name');
//we check all the element with ID
$(this).find("path, rect, pattern, image, mask").each(function() {
//now (this) refer to the actual element
if($(this).attr("id"))
$(this).attr("id", id+"-"+$(this).attr("id"));
});
//we update the <use>
$(this).find("use").each(function() {
//now (this) refer to the actual element
$(this).attr("xlink:href","#"+id+"-"+$(this).attr("xlink:href").substring(1));
});
// Also, hoping to combine g[mask], u[fill], etc into 1 function.
// Basically any attribute that starts with "url(#"
$(this).find("g[mask^='url']").each(function() {
$(this).attr("mask","url(#"+id+"-"+$(this).attr("mask").substring(5));
});
$(this).find("use[fill^='url']").each(function() {
$(this).attr("fill","url(#"+id+"-"+$(this).attr("fill").substring(5));
});
$(this).find("use[filter^='url']").each(function() {
$(this).attr("filter","url(#"+id+"-"+$(this).attr("filter").substring(5));
});
// grab data-screen value
var data = $(this).data('screen');
// replace this with the link inside svg
if (data != '') {
$(this).find("svg defs image").attr("href", data).attr("xlink:href", data);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="device" data-name="first" data-screen="image-1.png">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
<defs>
<rect id="w-circle-stroke-a" width="24" height="24"/>
<path id="w-circle-stroke-b" d="M12,2 C17.52,2 22,6.48 22,12 C22,17.52 17.52,22 12,22 C6.48,22 2,17.52 2,12 C2,6.48 6.48,2 12,2 Z M12,3.81818182 C7.48415409,3.81818182 3.81818182,7.48415409 3.81818182,12 C3.81818182,16.5158459 7.48415409,20.1818182 12,20.1818182 C16.5158459,20.1818182 20.1818182,16.5158459 20.1818182,12 C20.1818182,7.48415409 16.5158459,3.81818182 12,3.81818182 Z M10.5553177,13.4773237 L15.155405,8.80967806 C15.5597962,8.4027095 16.222261,8.39598875 16.6350615,8.79466684 C16.6382917,8.79778661 16.6600317,8.81952282 16.7002813,8.85987545 C17.0999062,9.26113743 17.0999062,9.90402237 16.7002813,10.3052843 L10.5553177,16.5 L7.29971874,13.2228714 C6.90252847,12.8240541 6.8997633,12.1859262 7.29348277,11.7837778 L7.33224151,11.7441893 C7.73340831,11.3344341 8.39555055,11.3228774 8.8111776,11.7183766 C8.81566955,11.722651 9.39704957,12.3089667 10.5553177,13.4773237 Z"/>
<image id="image-5" href="image-to-be-inserted.jpg"/>
</defs>
<g fill="none" fill-rule="evenodd">
<mask id="w-circle-stroke-c" fill="#fff">
<use xlink:href="#w-circle-stroke-b"/>
</mask>
<g fill="#2D8EFF" mask="url(#w-circle-stroke-c)">
<rect width="24" height="24"/>
</g>
<use fill="url(#pattern-4)" xlink:href="#path-3"/>
<use fill="#000" filter="url(#filter-10)" xlink:href="#path-9"/>
</g>
</svg>
</div>
<div class="device" data-name="second" data-screen="image-1.png">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
<defs>
<rect id="w-circle-stroke-a" width="24" height="24"/>
<path id="w-circle-stroke-b" d="M12,2 C17.52,2 22,6.48 22,12 C22,17.52 17.52,22 12,22 C6.48,22 2,17.52 2,12 C2,6.48 6.48,2 12,2 Z M12,3.81818182 C7.48415409,3.81818182 3.81818182,7.48415409 3.81818182,12 C3.81818182,16.5158459 7.48415409,20.1818182 12,20.1818182 C16.5158459,20.1818182 20.1818182,16.5158459 20.1818182,12 C20.1818182,7.48415409 16.5158459,3.81818182 12,3.81818182 Z M10.5553177,13.4773237 L15.155405,8.80967806 C15.5597962,8.4027095 16.222261,8.39598875 16.6350615,8.79466684 C16.6382917,8.79778661 16.6600317,8.81952282 16.7002813,8.85987545 C17.0999062,9.26113743 17.0999062,9.90402237 16.7002813,10.3052843 L10.5553177,16.5 L7.29971874,13.2228714 C6.90252847,12.8240541 6.8997633,12.1859262 7.29348277,11.7837778 L7.33224151,11.7441893 C7.73340831,11.3344341 8.39555055,11.3228774 8.8111776,11.7183766 C8.81566955,11.722651 9.39704957,12.3089667 10.5553177,13.4773237 Z"/>
<image id="image-5" href="image-to-be-inserted.jpg"/>
</defs>
<g fill="none" fill-rule="evenodd">
<mask id="w-circle-stroke-c" fill="#fff">
<use xlink:href="#w-circle-stroke-b"/>
</mask>
<g fill="#2D8EFF" mask="url(#w-circle-stroke-c)">
<rect width="24" height="24"/>
</g>
<use fill="url(#pattern-4)" xlink:href="#path-3"/>
<use fill="#000" filter="url(#filter-10)" xlink:href="#path-9"/>
</g>
</svg>
</div>
The issue is that you don't have to use $('.device') when inside and you need to refer to $(this) and since you have 2 each() nested you need to pay attention to the scope of this.
$('.device').each(function() {
//we get the needed id using $(this) that refer to actual device
var id = $(this).data('name');
//we check all the element with ID
$(this).find("path, rect, pattern, image, mask").each(function() {
//now (this) refer to the actual element
if($(this).attr("id"))
$(this).attr("id", id+"-"+$(this).attr("id"));
});
//we update the <use>
$(this).find("use").each(function() {
//now (this) refer to the actual element
$(this).attr("xlink:href","#"+id+"-"+$(this).attr("xlink:href").substring(1));
});
// grab data-screen value
var data = $(this).data('screen');
// replace this with the link inside svg
if (data != '') {
$(this).find("svg defs image").attr("href", data).attr("xlink:href", data);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="device" data-screen="[[IMAGE TO USE]]" data-name="[[UNIQUE ID]]">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="635" height="420" viewBox="0 0 635 420">
<defs>
<path id="path-1"/>
<rect id="path-3" />
<pattern id="pattern-4">
<use transform="scale(13.1875)" xlink:href="#image-5"/>
</pattern>
<image id="image-5" href="[[IMAGE LINK THAT IS INSERTED AFTER LOAD]]"/>
</defs>
<g fill="none" fill-rule="evenodd">
<rect fill="#fff" stroke="#2D8EFF" />
<g >
<mask id="mask-2" fill="#fff">
<use xlink:href="#path-1"/>
</mask>
<g mask="url(#mask-2)">
<mask id="mask-6" fill="#fff">
<use xlink:href="#path-3"/>
</mask>
</g>
</g>
</g>
</svg>
</div>

Change fill on circle, within an SVG, within an object

So I chose to create an object to put an SVG object into, because I want to replicate it 100 times or more. Here is the SVG:
<svg xmlns="http://www.w3.org/2000/svg" class="ballsvg" viewBox="0 0 98 98">
<g>
<circle class="changeCircle" cx="50%" cy="51%" r="48%" stroke="red" stroke-width="3" fill="blue" />
<text class="changeText" text-anchor="middle" x="50%" y="65%" font-family="Verdana" font-size="30" fill="white" ><tspan>0</tspan> </text>
</g>
</svg>
And my JavaScript that builds out the objects to the div #svgMain:
for(var i=0; i<addingTo; i++){
var obj = '<div class="clickBall" data-id="'+String(i+1+totalBalls)+'" ><object class="ball" data="ball.svg"></object></div>'
appendHTMLNodes += obj;
}
Then I found online how to dynamically change the text on that object:
$( "object.ball" ).each(function( index ) {
var textNode = $(this.contentDocument);
textNode = textNode.find(".changeText");
textNode = textNode.children().first();
textNode.text(index+1);
});
Then I wanted to change the fill color when the item is clicked:
$(document).on('click', '.clickBall', function(event) {
var mySVG = $(this).find("object.ball");
mySVG = $(mySVG.contentDocument);
mySVG = mySVG.find(".changeCircle");
console.log(mySVG);
mySVG.attr("fill", "#FF0000");
});
But I cannot figure out how to get the right element, because it never changes the fill, even though I could change the text before.
Thanks in advance for any help.
You can directly select the .changeCircle and use $(this) property to fill svg. Try this code:
$(".changeCircle").on('click', function() {
$(this).css({ fill: "#ff0000" });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" class="ballsvg" viewBox="0 0 98 98">
<g>
<circle class="changeCircle" cx="50%" cy="51%" r="48%" stroke="red" stroke-width="3" fill="blue" />
<text class="changeText" text-anchor="middle" x="50%" y="65%" font-family="Verdana" font-size="30" fill="white" ><tspan>0</tspan> </text>
</g>
</svg>
UPDATE:
tcoulson wanted to update SVG inside a object tag. Here is how you can access SVG object:
$(function(){
$("#obj").contents().find(".changeCircle").attr({"fill":"lime"});
console.log($("#obj").contents().find(".changeCircle"));
});

Change an inline SVG's x and y with ecmascript

I am using an inline SVG in an SVG and have set some default x and y values. When I change them, the inline SVG moves accordingly. I am trying to change it with
var inlineSVG = document.getElementById("inlineSVG");
inlineSVG.style.x = "90";
and that adds style="x:90px;" but that doesn't actually affect the element.
It's weird (in my head) because this works with a rect but not with an svg.
Here is my actual code:
<?xml version='1.0' encoding='UTF-8'?>
<svg width='1000' height='360'
xmlns='http://www.w3.org/2000/svg'
xmlns:xlink="http://www.w3.org/1999/xlink"
onload='init(evt)'
>
<script type='text/ecmascript'>
function init(event){
var wing1 = document.getElementById("wing1");
wing1.style.x = "90";
}
</script>
<circle cx="200" cy="140" r="5" fill="red" />
<circle cx="220" cy="170" r="5" fill="red" />
<circle cx="180" cy="170" r="5" fill="red" />
<circle cx="220" cy="220" r="5" fill="red" />
<circle cx="180" cy="220" r="5" fill="red" />
<svg id="wing1" x="280" y="100" viewBox="0 0 350 300">
<g>
<g>
<g>
<ellipse fill="#E6E7E8" cx="229.505" cy="117.813" rx="5.862" ry="4.547"/>
</g>
<g>
<ellipse fill="#E6E7E8" cx="265.931" cy="117.819" rx="5.862" ry="4.547"/>
</g>
</g>
<g>
<g>
<ellipse fill="#E6E7E8" cx="229.191" cy="125.538" rx="5.862" ry="4.547"/>
</g>
<g>
<ellipse fill="#E6E7E8" cx="265.617" cy="125.531" rx="5.861" ry="4.547"/>
</g>
</g>
</g>
<ellipse fill="#E6E7E8" cx="247.244" cy="121.796" rx="20.635" ry="38.017"/>
</svg>
<rect id="square" x="0" y="470" width="50" height="50" fill="#BADA55" style="fill-opacity : 0.5" />
<line x1="0" y1="0" x2="1000" y2="360" style="stroke: yellowgreen;
stroke-width: 1;
stroke-dasharray: 10 1;"></line>
<line x1="0" y1="360" x2="1000" y2="0" style="stroke: yellowgreen;
stroke-width: 1;
stroke-dasharray: 10 1;"></line>
I tried adding !important to the value but it didn't work ( because I guess it doesn't count it as a valid number? ).
The solution is to directly change the x attribute like so:
selector.setAttribute("attr",val);

in svg: translate vs position x and y

When I want to position simple objects such as rect or line should I use transform attribute or x and y attributes?
// this
d3.selectAll('rect')
.attr('x', d => d)
.attr('y', 0)
// or this?
d3.selectAll('rect')
.attr("transform", d => `translate(${d}, 0)`);
What is the performance difference?
In SVG transform is not hardware accelerated. They have around the same performance for single elements (in my experience). However, I use transform more to move thing around because in SVG not all elements have a x or y attributes, consider...
<line x1="0" y1="0" x2="100" y2="100" />
<circle cx="100" cy="100" r="100" />
<path d="M 0 0 L 100 100" />
<rect x="0" y="0" width="100" height="100" />
You must write a different implementation for each of these elements if you are not using transform. One area where transform is indeed faster is moving a large number of elements, if you have...
<g transform="translate(100, 100)">
<line x1="0" y1="0" x2="100" y2="100" />
<circle cx="100" cy="100" r="100" />
<path d="M 0 0 L 100 100" />
<rect x="0" y="0" width="100" height="100" />
</g>
It will be less processing intensive than moving each element individually

Categories

Resources