D3 transition not working - javascript

I have the following SVG element:
<svg id='svgTest' xmlns="http://www.w3.org/2000/svg" version="1.1">
<g id="test">
<rect height="20" width="50" fill="blue"/>
</g>
</svg>
I want to add a transition for the blue rectangle. I tried with the following code with D3:
var rect = d3.select("#test");
rect.transition().duration(5000).attr('height',200);
But it doesn't seem to do anything. What's wrong?

You need to select the 'rect' element. Try this:
var rect = d3.select("#test rect");
rect.transition().duration(5000).attr('height',200);
If you want to update multiple elements, use d3.selectAll().

Related

How to add color to a path element in an svg dynamically with d3 and react

I have an SVG icon:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="12" />
<path class="svg-fill"
d="M12,2A10,10,0,1,0,22,12,10,10,0,0,0,12,2Zm1,3.3,1.35-1a8,8,0,0,1,4.38,3.34L18.34,9,17,9.49,13,6.7Zm-3.35-1L11,5.3V6.7L7,9.49,5.66,9,5.27,7.69A8.1,8.1,0,0,1,9.65,4.35ZM7.08,17.11l-1.14.1A7.94,7.94,0,0,1,4,12c0-.12,0-.23,0-.35l1-.73,1.38.48,1.46,4.34Zm7.42,2.48a7.83,7.83,0,0,1-5,0L8.81,18.1,9.45,17h5.11l.64,1.11ZM14.27,15H9.73L8.38,11,12,8.44,15.63,11Zm3.79,2.21-1.14-.1-.79-1.37,1.46-4.34L19,10.93l1,.73c0,.11,0,.22,0,.34A7.94,7.94,0,0,1,18.06,17.21Z" />
</svg>
Which I'm trying to render on a D3 graph by using:
svg
.append('image')
.attr('xlink:href', MyIcon)
How can I change the color of the path in the svg dynamically, as I will be running the append code from a function something like:
const renderIcon = (iconColor) => {
svg
.append('image')
.attr('xlink:href', MyIcon)
// code to apply color param to the icon path
}
Couldn't find a nice way of actually getting the above working. The solution that I ended up finding was to instead define my icons in an defs element within an svg element in the return statement of my component:
return (
<svg>
<defs>
<MyIcon color={myColor1} id="myIcon1" />
<MyIcon color={myColor2} id="myIcon2" />
</defs>
</svg>
)
Where MyIcon would look something like:
<g fill={color} id={id}>
// other svg elements
</g>
And then use them by appending a use element to the svg element and passing in the icons id attribute:
svg.append('use').attr('href', '#myIcon1');
svg.append('use').attr('href', '#myIcon2');

How do I get the fill color of an svg element without rendering it?

I want to access a svg element online which I have the URL for but without showing it on the screen. The svg has only one fill color. Is there a way with javascript to grab this color? ("#013299")
<svg width="50px" height="50px" viewBox="0 0 50 50" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="mysvg" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Background" fill="#013299" x="0" y="0" width="50" height="50"></rect>
</g>
</svg>
You can use fetch to download the SVG and parse it as text, then set the innerHTML of a new DIV to that text, and then it'll be just like any other SVG in a div, except you don't need to append it to the DOM anywhere:
const container = document.createElement('div');
fetch(SVG_URL)
.then(response => response.text())
.then(text => {
container.innerHTML = text;
const fillColor = container.getElementById('Background').getAttribute('fill');
})
.catch(console.error);
The console.error can be changed to your own error handling callback if the SVG can't be downloaded for some reason.
You can easily get the background fill using basic DOM manipulation:
let backgroundFill = document.getElementById("Background").getAttribute("fill")

Set X and Y value for g element of SVG

I am relatively new in SVG drawing with HTML5.
What I want to do is to make a group of elements in SVG with g element so that all elements inside of that g element can work like a group and all the element's base x and y value can be received from the upper g element.
So, what I have done is something like this-
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<g x="1000" y="1000">
<title>SVG Title Demo example</title>
<rect width="200" height="50"
style="fill:wheat; stroke:blue; stroke-width:1px"/>
<text style="text-anchor: middle;" class="small">My Text</text>
</g>
</svg>
What I expected is all the elements inside the g element will get x="1000" and y="1000" so my expected output is like this-
But I am getting this-
Re-
I don't like to set x and y element in text element. I just want to set relative x and y into the text element if needed, but that should be relative to g element.
Can anyone help me what I need to do to achieve my target with a group in SVG?
<g> elements don't support x or y attributes. You can use a transform instead though.
I've decreased the values from 1000 to 100 as otherwise the output is outside the 500 x 300 canvas of the outer <svg> element.
I've provided additional x and y attributes on the text element so it appears positioned as in your example. If wanted you could put the text itself in a <g> element or an <svg> element.
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(100, 100)">
<title>SVG Title Demo example</title>
<rect width="200" height="50"
style="fill:wheat; stroke:blue; stroke-width:1px"/>
<text x="100" y="30" style="text-anchor: middle;" class="small">My Text</text>
</g>
</svg>
or using an additional <g> element to avoid x and y on the text itself.
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(100, 100)">
<title>SVG Title Demo example</title>
<rect width="200" height="50"
style="fill:wheat; stroke:blue; stroke-width:1px"/>
<g transform="translate(100, 30)">
<text style="text-anchor: middle;" class="small">My Text</text>
</g>
</g>
</svg>
Alternatively you could use an inner <svg> element instead of a <g> element as that does support x and y attributes
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<svg x="100" y="100">
<title>SVG Title Demo example</title>
<rect width="200" height="50"
style="fill:wheat; stroke:blue; stroke-width:1px"/>
<text x="100" y="30" style="text-anchor: middle;" class="small">My Text</text>
</svg>
</svg>

Copy and Insert in d3 selection

In a d3 program I need to get a node (with d3.selection) and then I want to insert in the same svg.
I know there are some functions like append, and insert, but these functions are for new elements.
var node = d3.select("rect#someId"); //node with some attributes and listeners
Now my var node got the following attributes:
{_groups, _parents}
var anotherNode = d3.select("anotherNode").insert(node); //It work but it would be great a similar function or a workaround
Note. I need to preserve the listeners of the node
New answer
D3 v5.0 introduced selection.clone, which:
Inserts clones of the selected elements immediately following the selected elements and returns a selection of the newly added clones.
Here is a demo:
var copy = d3.select("#group").clone(true).attr("transform", "translate(120,100)");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="200" height="200">
<g id="group">
<rect x="10" y="10" width="50" height="20" fill="teal"></rect>
<circle cx="35" cy="40" r="20" fill="red"></circle>
</g>
</svg>
Note that, just as the solution in the original answer, selection.clone will not clone the listeners.
Original answer
Use this function to clone your selection:
function clone(selector) {
var node = d3.select(selector).node();
return d3.select(node.parentNode.insertBefore(node.cloneNode(true), node.nextSibling));
}
Then, you can call it with clone("#foo") (by ID) or clone(".foo") (by class).
Here is an example, where the group (one rect and one circle) with ID "group" is cloned (the translate is just to better see the clone):
function clone(selector) {
var node = d3.select(selector).node();
return d3.select(node.parentNode.insertBefore(node.cloneNode(true),
node.nextSibling));
}
var copy = clone("#group").attr("transform", "translate(120,100)");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="200" height="200">
<g id="group">
<rect x="10" y="10" width="50" height="20" fill="teal"></rect>
<circle cx="35" cy="40" r="20" fill="red"></circle>
</g>
</svg>
PS: This will not clone the listeners. Also, this function is not mine, it was written by Bostock.

Repeatedly selecting groups out of loaded SVG with Snap.svg

I'm kinda new with the whole SVG business, now I'm stumbling upon a problem that I really don't understand.
I'm animating icons at the moment and noticed that repeatedly selecting g's and applying them doesn't work for me. For the sake of the question I've made an example. What I would like it to do is: Load svg > Apply the first 'G' with mask > on click animate the child 'G' upwards > clear > Select a specific 'G' (this time again the first one) > animate it from the bottom to the center
For some reason in stead of grabbing the first 'G' it iterates over the 'G's in the SVG. I've tried this with using Select with Id's, but it's giving me the same troubles.
Sooo what am I doing wrong?
html
<svg id="svg"></svg>
js
var s = Snap("#svg");
s.attr({ viewBox: "0 0 300 300" });
var bigCircle = s.circle(150, 150, 100);
var bigCircle2 = s.circle(150, 150, 100);
bigCircle2.attr({
fill:"none",
stroke: "#0000e6",
strokeWidth: 6
});
bigCircle.attr({
fill: "#fff",
stroke: "#fff",
strokeWidth: 6
});
Snap.load("images/numbers.svg", function (f) {
var apply = function(number){
g = f.select("svg g:nth-child("+number+")");
s.append(g);
g.attr({
mask: bigCircle,
});
p = g.select("g");
};
apply(1); //Please give me the first g in the svg
var g_animate = function(){
p.animate({ transform: 't0,-200' }, 500, mina.easeout, function(){
p.remove();
apply(1); //Let's repeat the first g in the svg
p.attr({transform: 't0,200'});
p.animate({ transform: 't0,0' }, 500, mina.easein)
})
};
$("#svg").click(function(){
g_animate();
});
});
loaded svg
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="300px" height="300px" viewBox="0 0 300 300" enable-background="new 0 0 300 300" xml:space="preserve">
<g>
<g>
<path d="M162.367,212.803v-34.396h-58.358v-11.016l56.04-80.193h18.357v78.068h17.584v13.141h-17.584v34.396H162.367z
M162.367,165.266v-41.932c0-6.57,0.193-13.14,0.58-19.71h-0.58c-3.865,7.343-6.957,12.753-10.436,18.551l-30.724,42.705v0.387
H162.367z"/>
<rect fill="none" width="300" height="300"/>
</g>
</g>
<g>
<g>
<path d="M115.411,193.479c4.83,2.898,15.846,7.73,27.826,7.73c21.642,0,28.599-13.721,28.406-24.35
c-0.193-17.584-16.039-25.119-32.464-25.119h-9.469v-12.754h9.469c12.367,0,28.019-6.377,28.019-21.256
c0-10.049-6.377-18.938-22.029-18.938c-10.048,0-19.71,4.444-25.12,8.309l-4.638-12.367c6.764-4.83,19.517-9.662,33.043-9.662
c24.734,0,35.943,14.687,35.943,29.952c0,13.141-7.924,24.155-23.189,29.759v0.387c15.459,2.898,27.826,14.493,28.02,32.077
c0,20.098-15.846,37.682-45.796,37.682c-14.106,0-26.474-4.443-32.657-8.502L115.411,193.479z"/>
<rect fill="none" width="300" height="300"/>
</g>
</g>
<g>
<g>
<path d="M109.903,213.865v-10.435l13.334-12.947c32.077-30.531,46.764-46.764,46.764-65.7c0-12.754-5.991-24.542-24.735-24.542
c-11.4,0-20.869,5.798-26.666,10.628l-5.411-11.98c8.503-7.149,21.062-12.754,35.362-12.754c27.054,0,38.454,18.551,38.454,36.522
c0,23.188-16.812,41.933-43.285,67.439l-9.854,9.275v0.387h56.231v14.106H109.903z"/>
<rect fill="none" width="300" height="300"/>
</g>
</g>
<g>
<g>
<path d="M154.541,103.043h-0.387l-21.835,11.787l-3.285-12.946l27.439-14.687h14.494v125.605h-16.426V103.043z"/>
<rect fill="none" width="300" height="300"/>
</g>
</g>
</svg>
It won't keep repeating the same g element, as you remove it from the DOM with this...
p.remove();
So next time it selects, it the 2nd initial g will now be the first one.
Edit:
Its also worth looking at what happens with 'append'. If you append an element from a fragment (the data loaded from the file) into the DOM, then that element is no longer in the fragment f. So if you now select a new element, it will be the next g element (as the other one has been removed and added to the DOM). So if you wanted the SAME element, you want to use a css selector on s rather than f.
I'd be tempted to use a selectAll initially on the svg to get all of the g elements into an array and use those to reference them, it may feel more intuitive.

Categories

Resources