I would like to add text inside an element such that my intro is behind a rectangular, transparent element.
I tried adding text inside an SVG like this
<svg height="150" width="500">
<text x="100" y="30" fill="red">I love SVG!</text>
<defs>
<radialGradient id="grad1" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
<stop offset="0%" style="stop-color:rgb(255,255,255);
stop-opacity:0" />
<stop offset="100%" style="stop-color:rgb(0,0,255);stop-opacity:1" />
</radialGradient>
</defs>
<ellipse cx="200" cy="70" rx="85" ry="55" fill="url(#grad1)" />
</svg>
However I can't Send the object backwards. Is this even possible?
Is there a better approach I can use to meet this objective rather than the solution I'm trying to use?
Are you just talking about whether the text is behind the oval, or on top of it?
If so, just changing the order of elements fixes that:
<svg height="150" width="500">
<defs>
<radialGradient id="grad1" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
<stop offset="0%" style="stop-color:rgb(255,255,255);
stop-opacity:0" />
<stop offset="100%" style="stop-color:rgb(0,0,255);stop-opacity:1" />
</radialGradient>
</defs>
<ellipse cx="200" cy="70" rx="85" ry="55" fill="url(#grad1)" />
<text x="100" y="30" fill="red">I love SVG!</text>
</svg>
Or is there something else you're trying to do? (Please note, even with the text on top of the oval, the contrast isn't that great, and it's still hard to read.)
Related
I have some SVG-s that are equipped with scripts inside (for handling the onClick event), like:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" width="200" height="200">
<defs>
<linearGradient id="borderColor" x1="0%" y1="0%" x2="90%" y2="70%">
<stop stop-opacity="0.8" stop-color="#def0ff" offset="0"/>
<stop stop-opacity="0.8" stop-color="#9bb0c2" offset="0.3"/>
<stop stop-opacity="0.8" stop-color="#def0ff" offset="0.4"/>
<stop stop-opacity="0.8" stop-color="#9bb0c2" offset="0.6"/>
<stop stop-opacity="0.8" stop-color="#def0ff" offset="0.7"/>
<stop stop-opacity="0.8" stop-color="#9bb0c2" offset="1"/>
</linearGradient>
<linearGradient x1="0" y1="0" x2="0.9" y2="0.7" id="fillColor">
<stop stop-opacity="0.4" stop-color="#dbdfe2" offset="0"/>
<stop stop-opacity="0.4" stop-color="#d8d8e1" offset="0.3"/>
<stop stop-opacity="0.4" stop-color="#c9ced3" offset="0.3"/>
<stop stop-opacity="0.5" stop-color="#dae2e9" offset="0.6"/>
<stop stop-opacity="0.5" stop-color="#e3e9ed" offset="0.6"/>
<stop stop-opacity="0.5" stop-color="#e2e9ed" offset="1"/>
</linearGradient>
<radialGradient id="circleGrad">
<stop offset="0%" stop-color="black" />
<stop offset="100%" stop-color="white" />
</radialGradient>
<mask id="myMask">
<rect width="100%" height="100%" fill="white"/>
<circle cx="20" cy="180" r="20"
fill="url(#circleGrad)"/>
<circle cx="100" cy="20" r="20"
fill="url(#circleGrad)"/>
<circle cx="180" cy="180" r="20"
fill="url(#circleGrad)"/>
</mask>
<radialGradient id="circleGrad2">
<stop offset="0%" stop-color="white" />
<stop offset="100%" stop-color="black" />
</radialGradient>
<mask id="myMask2">
<rect width="100%" height="100%" fill="black"/>
<circle cx="20" cy="180" r="20"
fill="url(#circleGrad2)"/>
<circle cx="100" cy="20" r="20"
fill="url(#circleGrad2)"/>
<circle cx="180" cy="180" r="20"
fill="url(#circleGrad2)"/>
</mask>
</defs>
<script type="application/ecmascript"><![CDATA[
function triangle_click(evt){
const fgPath = evt.target;
const newVal = fgPath.getAttribute('stroke-dasharray') === '0' ? '30 20' : 0;
fgPath.setAttribute('stroke-dasharray',newVal);
}
]]> </script>
<path d="M 20,180 L 100,20 L 180,180 Z"
stroke="#00ffff" stroke-width="3"
fill="url(#fillColor)"
mask="url(#myMask2)">
<animate
attributeName="stroke-dashoffset"
values="0;500"
dur="5s"
repeatCount="indefinite"
/>
</path>
<path id="fgPath" d="M 20,180 L 100,20 L 180,180 Z"
stroke="url(#borderColor)" stroke-width="3"
stroke-dasharray="0"
stroke-linecap="round"
fill="url(#fillColor)"
mask="url(#myMask)"
onclick="triangle_click(evt)"
>
<animate
attributeName="stroke-dashoffset"
values="0;500"
dur="5s"
repeatCount="indefinite"
/>
</path>
</svg>
Now, when I add this SVG into my react app, it throws an error on the 'onClick' event, unless I copy and paste the <script> tag in the inspector, anywhere (Uncaught ReferenceError: triangle_click is not defined at onclick).
Does that mean this script tag was not read initially, that's why it doesn't find it at first place?
Thanks,
M
I found out the issue was that I inserted the element as innerHTML. The answer can be found here: Executing <script> elements inserted with .innerHTML
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1158 696">
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:red;stop-opacity:1" />
<stop offset="50%" style="stop-color:blue;stop-opacity:1" />
<stop offset="100%" style="stop-color:red;stop-opacity:1" />
</linearGradient>
</defs>
<g><g><g><path fill="none" stroke="url(#grad1)" stroke-miterlimit="50" stroke-width="4" d="M0 212.4c241.4 0 274.174-213.852 579-210 304.826 1.853 345.472 211 581 210"/></g></g></g>
<g>
<g opacity=".76">
<path fill="#011134" d="M.25 214.001c239.653 0 274.65-213.852 580.005-210 305.355 3.853 340.062 210 580.005 210v486H.25v-486z"/>
</g>
</g>
<g transform="translate(0, 198)" >
<filter id="blurMe">
<feGaussianBlur in="SourceGraphic" stdDeviation="2" />
</filter>
<circle filter="url(#blurMe)" cx="16" cy="16" r="12" fill="red" />
</g>
</svg>
I've tried to use a formula of Normal distribution
I need to achive a result where the circle moves along the curve depends on the current time for that i need to find a formula which represents the graph described above, but the Gaussian distribution does not give me a desirable graph, the Gaussian distribution gives me next:
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'm writing a simple webpage that displays a graph and shows dependencies. I found an unexpected behavior in how path elements are rendered within svg.
Here's the full HTML of the example:
<html>
<body>
<svg id="svgConnections" xmlns="http://www.w3.org/2000/svg" style="width: 300px; height: 120px">
<defs>
<linearGradient id="grad1" >
<stop offset="0%" style="stop-color:yellow;stop-opacity:1" />
<stop offset="100%" style="stop-color:red;stop-opacity:1" />
</linearGradient>
</defs>
<path d="M40,40 L100,100 Z" stroke="url(#grad1)" strokeWidth="1px" />
<path d="M200,100 L140,40 Z" stroke="url(#grad1)" strokeWidth="1px" />
</svg>
</body>
</html>
The same example is on https://jsfiddle.net/4fLjm0e2/
What bugs me is that the first line, which goes from top left to bottom right corner, looks exactly like the second line, which goes "in reverse": from bottom right corner to the top left.
How do I make the path always start with yellow and end with red?
This is not a bug. This is problem in understanding.
The default behavior of a linear gradient is to transition along a horizontal line from the left side of an object to its right side. It doesn't matter if you draw a path from left to right or from right to left. In both cases gradient will appear as from left to right as per default settings.
Consider the demo below:
<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad1" gradientUnits="userSpaceOnUse">
<stop offset="0%" style="stop-color:yellow;stop-opacity:1" />
<stop offset="100%" style="stop-color:red;stop-opacity:1" />
</linearGradient>
</defs>
<g stroke-width="2">
<path d="M10,40 L110,40 Z" stroke="url(#grad1)" />
<path d="M110,70 L10,70 Z" stroke="url(#grad1)" />
</g>
</svg>
If you wants the transition of colors to occur across a vertical line or a line at an angle, you must specify the line's starting point with the x1 and
y1 attributes and its ending points with the x2 and y2 attributes.
Rather than duplicate the stops into each <linearGradient> element, we'll use the xlink:href attribute to refer to the original gradient. The stops will be inherited, but the x- and y-coordinates will be overridden by each individual gradient.
<linearGradient id="grad1" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
<stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
</linearGradient>
<linearGradient id="grad2" xlink:href="#grad1" x1="1" y1="1" x2="0" y2="0"></linearGradient>
Extending the above example:
<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad1" gradientUnits="userSpaceOnUse">
<stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
<stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
</linearGradient>
<linearGradient id="grad2" xlink:href="#grad1" x1="120" y1="0" x2="0" y2="0"></linearGradient>
</defs>
<g stroke-width="2">
<path d="M10,40 L110,40" stroke="url(#grad1)" />
<path d="M110,70 L10,70 Z" stroke="url(#grad2)" />
</g>
</svg>
As in your example, you are using diagonal paths so we need to override x1, y1, x2 and y2 attribute of both <linearGradient> elements.
These values on the first <linearGradient> element will override the default left to right settings to produce a diagonal gradient from top left to the right bottom.
While on the <linearGradient> element these values will change the direction of gradient i.e from bottom to top.
Now we can apply these gradients to the respective paths.
<svg width="300" height="120" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad1" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
<stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
</linearGradient>
<linearGradient id="grad2" xlink:href="#grad1" x1="1" y1="1" x2="0" y2="0"></linearGradient>
</defs>
<g stroke-width="2">
<path d="M40,40 L100,100 Z" stroke="url(#grad1)" />
<path d="M200,100 L140,40 Z" stroke="url(#grad2)" />
</g>
</svg>
Note: This Question can be useful in context with the current problem.
I'm using javascript to change the color of a svg. This changes my <linearGradient> filling :
My problem is, that it is changing very rapidly.
Is there any way to have a "smooth" flow between the colors? I tried to use the jquery anim() method but it wouldn't work because of the SVG-DOM.
Edit: More source code. In the end, it's pretty simple. I get the stop elements of my svg and calculate a new rgb value. I then set the rgb value as the new stop-color of the stop element
js:
var gradient = $('#upper').children('stop');
var firstValue = 'stop-color:rgb('+top[0]+','+top[1]+','+top[2]+')';
var secondValue = 'stop-color:rgb('+bottom[0]+','+bottom[1]+','+bottom[2]+')';
gradient[0].setAttribute('style',firstValue);
gradient[1].setAttribute('style',secondValue);
html
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">
<defs>
<linearGradient id="upper" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%">
<stop style="stop-color:rgb(107,186,112);stop-opacity:1" offset="0"/>
<stop style="stop-color:rgb(107,186,112);stop-opacity:1" offset="1"/>
</linearGradient>
</defs>
<rect x="0" y="0" width="1" height="1" fill="url(#upper)" opacity="0.6" />
</svg>
Depending on the actual scenario, this could be solved with pure SMIL animation:
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">
<defs>
<linearGradient id="upper" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%">
<stop stop-color="rgb(107,186,112)" offset="0">
<animate attributeType="CSS" attributeName="stop-color" id="animate0"
dur="2s" begin="rect0.click" fill="freeze" to="rgb(0,255,0)"/>
</stop>
<stop stop-color="rgb(107,186,112)" offset="1">
<animate attributeType="CSS" attributeName="stop-color"
dur="2s" begin="animate0.begin" fill="freeze" to="rgb(255,0,0)"/>
</stop>
</linearGradient>
</defs>
<rect x="0" y="0" width="1" height="1" fill="url(#upper)" opacity="0.6" id="rect0"/>
</svg>
I this case the animation is triggered by a click on the rectangle.
Specific colors can also be set by JavaScript and the animation can be triggered by JavaScript:
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">
<defs>
<linearGradient id="upper" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%">
<stop stop-color="rgb(107,186,112)" offset="0">
<animate attributeType="CSS" attributeName="stop-color" id="animate0"
dur="2s" begin="indefinite" fill="freeze"/>
</stop>
<stop stop-color="rgb(107,186,112)" offset="1">
<animate attributeType="CSS" attributeName="stop-color"
dur="2s" begin="animate0.begin" fill="freeze"/>
</stop>
</linearGradient>
</defs>
<rect x="0" y="0" width="1" height="1" fill="url(#upper)" opacity="0.6"/>
<script type="text/javascript">
var gradientColors = [[255,0,0],[255,255,0]];
var animateElements = document.getElementsByTagName("animate");
for (var i=0; i<2; i++) {
animateElements[i].setAttribute(
"to",
"rgb("+gradientColors[i].join(",")+")"
);
}
animateElements[0].beginElement();
</script>
</svg>
Whether these solutions are practical for you depends on whether the targeted browsers support SMIL animation.