I am making a visualization program with d3 javascript.
There is a big group of lines. all the lines should start with opacity of 0.1. In this big group, there are some small groups. When an event is triggered, one of small groups visible and all other small groups invisible. Which small group is visible is dependent on the trigger event. I want to make one of small groups visible and all other small groups invisible by setting the opacity attribute.
So is there any keyword to make the group override the attribute or the style from its parent? Or is there any other similar attribute or style which can be overridden? Otherwise I may have to use for loop to traverse and modify every small groups, which is not very efficient.
<svg width="1000" height="500">
<g class="big_group" style="opacity: 0;">
<g class='small_group inherit_opacity'>
<line x1="100" y1="100" x2="200" y2="200" style="stroke: black;"></line>
</g>
<g class='small_group inherit_opacity'>
<line x1="200" y1="100" x2="200" y2="200" style="stroke: black;"></line>
</g>
<g class='small_group' id='try_to_override_opacity_but_fail' style="opacity: 1;">
<line x1="300" y1="200" x2="200" y2="200" style="stroke: black;"></line>
</g>
</g>
</svg>
I intuitively thought there might be some kind of "override" keyword in "try_to_override_opacity_but_fail" to override the opacity in parent group.
Only if you define the initial opacity with CSS, not with a style attribute:
<svg viewBox="0 0 1000 500">
<style>
g{
opacity:0;
stroke:black;
stroke-width:10;
}
</style>
<g>
<line x1="100" y1="100" x2="200" y2="200"></line>
</g>
<g>
<line x1="200" y1="100" x2="200" y2="200"></line>
</g>
<g style="opacity:1;">
<line x1="300" y1="200" x2="200" y2="200"></line>
</g>
</svg>
the .svg files works fine with css
so you can style them like:
svg {
width: 200px;
& > g {
path:nth-last-of-type(2) {
opacity: 0;
}
}
}
if you need to made changes dynamically, should read about style the elements by javascript ;)
Let's say I have an SVG with a structure similar to this:
<svg>
<defs>
<linearGradient id="gradient-red">...</linearGradient>
<linearGradient id="gradient-blue">...</linearGradient>
</defs>
<g class="node">
<circle r="50" style="fill: url('#gradient-red');"></circle>
</g>
<g class="node">
<circle r="100" style="fill: url('#gradient-red');"></circle>
</g>
<g class="node">
<circle r="150" style="fill: url('#gradient-red');"></circle>
</g>
<g class="node">
<circle r="200" style="fill: url('#gradient-red');"></circle>
</g>
<g class="node">
<circle r="250" style="fill: url('#gradient-red');"></circle>
</g>
</svg>
I now have five circles with reddish gradients. I understand how to change the color of a selected node -- I just target it (via d3.select) and alter its style to 'fill', 'url("#gradient-blue"). But how would I go about transitioning the gradient fill from red to blue for that one node?
Something like this results in no tween/transition and instead causes an instant color swap:
d3.transition().duration(1000)
.tween('start', () => {
let test = d3.select(currentTarget);
test.transition().duration(1000).style('fill', 'url("#gradient-blue")');
And if I were to transition the stop-color of the gradients themselves, it changes all of the nodes/circles (because you're altering the <defs>).
What am I doing wrong?
Transition's interpolation
In D3, a transition basically interpolates a start value to an end value. This can be easy to demonstrate if we interpolate numbers. For instance, let's transition from 50 to 2000:
const interpolator = d3.interpolate(50, 2000);
d3.range(0, 1.05, 0.05).forEach(function(d) {
console.log(interpolator(d))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
We can also interpolate strings:
const interpolator = d3.interpolate("March, 2000", "March, 2020");
d3.range(0, 1.05, 0.05).forEach(function(d) {
console.log(interpolator(d))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
The problem
Now, let's have a look at your case: you want to interpolate from this:
url("#gradient-red")
To this:
url("#gradient-blue")
What are the possible intermediates here? Can you see that this is impossible? Here is the proof:
const interpolator = d3.interpolate("url(#gradient-red)", "url(#gradient-blue)");
d3.range(0, 1.1, 0.1).forEach(function(d) {
console.log(interpolator(d))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
As you can see, the very first interpolation will instantly lead to the end value.
Possible solutions
The most obvious solution is interpolating the stop colour. However, as you just discovered, this will change the gradient of all circles.
So, the naive fix is creating several gradients, one for each circle, with unique IDs. While this may be an adequate solution for 3 or 4 circles, it's clearly not a clever solution if you have tens or hundreds of elements.
That being said, this is my suggestion:
Create a temporary gradient, let's give it the ID #gradient-temporary, just like the red one.
Then, when you select (or filter it somehow) a circle, change it's fill from "url(#gradient-red)" to "url(#gradient-temporary)". This change is immediate, no effect is obvious on the screen.
Do the transition on the stop colour of this temporary gradient.
When the transition finishes, change the circle's fill from "url(#gradient-temporary)" to "url(#gradient-blue)". Again, this is immediate. Also, change the stop colour of the temporary gradient back to red.
That way, you can have hundreds of circles, but you just need 3 gradients to transition them.
Here is a demo with that approach, click on each circle to transition it:
const circles = d3.selectAll("circle");
circles.on("click", function() {
const element = this;
d3.select(element).style("fill", "url(#gradient-temporary)");
d3.select("#gradient-temporary").select("stop:nth-child(2)")
.transition()
.duration(1000)
.style("stop-color", "rgb(0,0,255)")
.on("end", function() {
d3.select(element).style("fill", "url(#gradient-blue)");
d3.select("#gradient-temporary").select("stop:nth-child(2)")
.style("stop-color", "rgb(255,0,0)")
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg>
<defs>
<linearGradient id="gradient-red" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:rgb(211,211,211);stop-opacity:1" />
<stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
</linearGradient>
<linearGradient id="gradient-temporary" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:rgb(211,211,211);stop-opacity:1" />
<stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
</linearGradient>
<linearGradient id="gradient-blue" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:rgb(211,211,211);stop-opacity:1" />
<stop offset="100%" style="stop-color:rgb(0,0,255);stop-opacity:1" />
</linearGradient>
</defs>
<g class="node">
<circle r="20" cx="20" cy="70" style="fill: url('#gradient-red');"></circle>
</g>
<g class="node">
<circle r="20" cx="80" cy="70" style="fill: url('#gradient-red');"></circle>
</g>
<g class="node">
<circle r="20" cx="140" cy="70" style="fill: url('#gradient-red');"></circle>
</g>
<g class="node">
<circle r="20" cx="200" cy="70" style="fill: url('#gradient-red');"></circle>
</g>
<g class="node">
<circle r="20" cx="260" cy="70" style="fill: url('#gradient-red');"></circle>
</g>
</svg>
i have 3 glowing blub and 2 dotted line joining them.i have to pass a light glow(like every dash is glowing one by one) through the lines from one to other blub in a repeating manner.So far i am able to achieve this.
https://jsfiddle.net/hsfxS/3856/..
<div class="mr-glow-1">
<svg width="401" height="332" version="1.1" xmlns="http://www.w3.org/2000/svg">
<line stroke-dasharray="10, 5" x1="0" y1="1" x2="600" y2="600" style="stroke-width: 2px; stroke: rgb(0, 0, 0);"></line>
</svg>
</div>
The dotted line actually comes from a background image. i just have to pass a pink light glow through the dotted lines showing that transition is happening from one to other.How do i achieve this?I am working in angular 4 but can do the animation by pure javascript.
You can use <animate> elements to animate a change of X and Y position
https://codepen.io/danjiro/post/how-to-make-svg-loop-animation
<animate attributeName="cx" from="50" to="250"
dur="5s" repeatCount="indefinite" />
So you can make a glowing circle and animate its changing x and y position with the animate element
There are a number of ways to do what you want. It all depends on how you want the effect to look and how fancy you want to get.
For instance, here is one way. It animates stroke-dashoffset to moves a small dash along a second line so that it appears to follow the first line.
<svg width="401" height="332" version="1.1" xmlns="http://www.w3.org/2000/svg">
<line stroke-dasharray="10, 5" x1="0" y1="1" x2="600" y2="600" style="stroke-width: 2px; stroke: rgb(0, 0, 0);"/>
<line stroke-dasharray="14, 1000" x1="0" y1="1" x2="600" y2="600" style="stroke-width: 8px; stroke: rgba(192, 64, 64, 0.5);">
<animate attributeName="stroke-dashoffset" from="0" to="-848" dur="1s" repeatCount="indefinite" />
</line>
</svg>
If we have a background and a foreground line, using the same dash-array, we can set the x2 value of the 2nd to get a progress-bar looking image.
<svg width="1000" height="40">
<g stroke-width="40" stroke-dasharray="0 5 90 5">
<line stroke="gray" x1="0" x2="1000" y1="20" y2="20" />
<line stroke="green" x1="0" x2="750" y1="20" y2="20" />
</g>
</svg>
But if we change the x2 value, it change size directly, it isn't animated with the css transition: all 0.30s;
Played around with clippath, transform-scale, transform-translate, but can't get it to animate it.
A setInterval may solve it, but would rather have a css soultion.
Can it be done?
I notice that svg adds some gradient borders in very tiny pixels around elements. Here's the jsfiddle for it:
http://jsfiddle.net/XrkRT/
<rect x="1" y="1" width="1198" height="398"
fill="none" stroke="blue" stroke-width="10" />
<g stroke="green" >
<line x1="100" y1="300" x2="300" y2="300"
stroke-width="20" fill="none" />
</g>
How do I draw solid color line and rect. It's hard to see with normal zoom. I take a screenshot and zoom it in pixlr.com. Here's the image:
That's antialiasing. You can turn it off with shape-rendering="crispEdges" but be aware that any diagonal lines will look rougher.