SVG linear gradient showing on one path but not the other [duplicate] - html

This question already has answers here:
SVG Line with Gradient Stroke Won't Display if vertical or horizontal
(3 answers)
Closed 4 years ago.
I have two paths that are incredibly similar except for the ending number in the d attribute. The first path displays correctly but the second path does not show up. What's weird is if I change the stroke of the second path to anything other than my defined gradient it'll appear. The gradient also appears if I truncate the decimals from the last number. Is there any reason why the second one doesn't appear with the gradient?
For what it's worth I'm trying to get this to work on google chrome.
<svg height="0" width="0">
<defs>
<linearGradient id="pageSearchGradient" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="#a0c3d2" stop-opacity="0.75"></stop><stop offset="40%" stop-color="#F59B23" stop-opacity="0.85"></stop><stop offset="100%" stop-color="#F59B23" stop-opacity="1"></stop>
</defs>
</svg>
<svg width="1600" height="500" class="sankey-diagram "><g width="1450" height="500" transform="translate(150, 0)"><g class="sankey-links">
<!-- Only their decimal points differ for the last number and yet this one is the one that shows up -->
<path d="M12,436.7529384197002C365.5,436.7529384197002,365.5,436.75293841969994,719,436.38406598523085" class="sankey-link " style="stroke: url("#pageSearchGradient"); opacity: 0.3; stroke-width: 126.494;"></path>
<path d="M12,436.7529384197002C365.5,436.7529384197002,365.5,436.75293841969994,719,436.75293841969994" class="sankey-link " style="stroke: url("#pageSearchGradient"); opacity: 0.3; stroke-width: 126.494;"></path>
</svg>
Link to
https://codepen.io/anon/pen/OvEzNJ?editors=1000#0

The difference lies in the height of the bounding box for the two paths:
> document.querySelector('path:nth-child(1)').getBBox().height
> 0.368865966796875
> document.querySelector('path:nth-child(2)').getBBox().height
> 0
You are using percentage units for the gradient vector, and you are not specifying gradientUnits:
<linearGradient id="pageSearchGradient" x1="0%" y1="0%" x2="100%" y2="0%">
The spec has the following to say about these conditions:
If gradientUnits="objectBoundingBox", the user coordinate system for attributes x1, y1, x2 and y2 is established using the bounding box of the element to which the gradient is applied and then applying the transform specified by attribute gradientTransform. Percentages represent values relative to the bounding box for the object.
When gradientUnits="objectBoundingBox" and gradientTransform is the identity matrix, the normal of the linear gradient is perpendicular to the gradient vector in object bounding box space (i.e., the abstract coordinate system where (0,0) is at the top/left of the object bounding box and (1,1) is at the bottom/right of the object bounding box).
If that bounding box has no height, the top (defining 0 in bounding box space) and the bottom (defining 1) are the same value. It seems that browsers (and I can also see it in Firefox) get confused about that "abstract coordinate system" and no longer know how to compute the gradient vector and its normal.
I'd call that a bug. The best workaround I can come up with would be using gradientUnits="userSpaceOnUse", or making sure your path bounding box always has a non-zero width and height. (Threshold seems to lie around the seventh significant digit.)
Here is a test case showing the effect. There should be three rectangles, the first drawn with <rect>, the other two with <line stroke-width=...>. For the middle line, y1 and y2 are identical, for the bottom one they differ.
<svg width="200" height="150">
<linearGradient id="lg" x1="0%" y1="0%" x2="100%" y2="0%">
<stop stop-color="yellow" offset="0" />
<stop stop-color="green" offset="1" />
</linearGradient>
<rect x="5" y="0" width="200" height="40" style="fill:url(#lg)" />
<line x1="0" y1="75" x2="200" y2="75" style="stroke-width:40;stroke:url(#lg)" />
<line x1="0" y1="125" x2="200" y2="126" style="stroke-width:40;stroke:url(#lg)" />
</svg>

Related

SVG line with gradient doesn't render [duplicate]

I'm attempting to replicate an <hr> with SVG. Now, making a straight line with SVG works perfectly, but the second I stroke it with a gradient it will no longer display in a straight line.
The following code works, but take note, y1 and y2 must be 1 unit apart. If I set y1 and y2 to 210 for instance, the line will disappear.
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:rgba(0,0,0,0);stop-opacity:0" />
<stop offset="50%" style="stop-color:rgba(0,0,0,0.75);stop-opacity:1" />
<stop offset="100%" style="stop-color:rgba(0,0,0,0);stop-opacity:0" />
</linearGradient>
</defs>
<line x1="40" y1="210" x2="460" y2="211" stroke="url(#grad1)" stroke-width="1" />
I'm probably just missing some obvious function of linear gradients, and the line looks alright, but I'd much rather just have it straight. Thanks.
You are getting caught out by the last paragraph in this part of the SVG specification
Keyword objectBoundingBox should not be used when the geometry of the applicable element has no width or no height, such as the case of a horizontal or vertical line, even when the line has actual thickness when viewed due to having a non-zero stroke width since stroke width is ignored for bounding box calculations. When the geometry of the applicable element has no width or height and objectBoundingBox is specified, then the given effect (e.g., a gradient or a filter) will be ignored.
objectBoundingBox is the default for gradientUnits so you need to use gradientUnits="userSpaceOnUse" and then adjust the values to be appropriate for the different coordinate system.
Robert Longson gives an excellent explanation. But sometimes userSpaceOnUse is a pain, because it spreads the interpolation over the entire canvas, instead of just the line.
Instead, you could add a tiny amount to the values, to ensure the bbox size is not zero.
For example,
<line x1="40" y1="210" x2="460" y2="210.001" stroke="url(#grad1)" stroke-width="1" />
will draw a straight line with gradient.
Assuming you do not want any in-accuracy at all, you can change the line to a filled path as below
<path d='M 40 209.5 L 460 209.5 L 460 210.5 L 40 210.5' fill='url(#grad1)' />
or alternatively to a filled rect as below
<rect x=40 y=209.5 width=420 height=1 fill='url(#grad1)' />
It is also interesting to note that this issue only affects vertical and horizontal lines. Slanted line display correctly.

Where can I find transform-origin's definition and how does transform-origin work?

I have a simple html with svg as below:
<svg class="svg" style="border:1px solid black" width="200" height="200">
<rect transform="translate(100, 100)" width="20" height="20" fill="red"></rect>
<text transform="translate(100, 100) rotate(90)" transform-origin="0 0">TEST</text>
</svg>
The text element is rotated 90 degree in clockwise base on text's left-bottom corner (left-upper corner of red rect)
If I change text element to <text x="50" y="50" transform="translate(50, 50) rotate(90)" transform-origin="0 0">TEST</text>, the text is rotated to (0, 100) in svg. A reasonable guess is that transform-origin does not only affect "rotate" but also "translate". But I don't understand where "0 0" is in this situation, but it is not left-bottom corner of text element anymore.
I also don't understand how it works when I set "transform-origin" in percentage (e.g. 50% 50%) or text (e.g. left bottom). It doesn't work the way I thought it would. I have not been able to find a detailed tutorial on the definition of "transform-origin". Can someone with experience in this field give me some pointers? Thanks!
It is always easier in SVG contexts to read transformations as transformations of the coordinate system. Each of the steps builds on its predecessor. Your element
<text x="50" y="50" transform="translate(50, 50) rotate(90)" transform-origin="0 0">TEST</text>
can be understood as the following sequence:
translate(50, 50): Move the origin of the coordinate system to (50, 50) of the initial coordinate system
rotate(90) and transform-origin="0 0": Rotate the coordinate system found in the previous step by 90° clockwise around its own origin.
text x="50" y="50": Draw text at point (50, 50) of the coordinate system found in the previous step.
In relation to the initial coordinate system, the last one has its origin at (50, 50), the x-axis points left and the y-axis down. The blue lines show its position and orientation. Therefore, the point (50, 50) in that system is the same as (0, 100) in the initial coordinate system:
<svg class="svg" style="border:1px solid black" width="200" height="200">
<rect transform="translate(100, 100)" width="20" height="20" fill="red"></rect>
<text x="50" y="50" transform="translate(50, 50) rotate(90)" transform-origin="0 0">TEST</text>
<!-- draw the moved coordinate system -->
<path transform="translate(50, 50) rotate(90)" transform-origin="0 0"
d="M-5,0H100M0,-5V100" fill="none" stroke="blue" />
</svg>
So why does transform-origin="0 0" refer to the coordinate system? The CSS Transforms specification says:
The value for the horizontal and vertical offset represent an offset from the top left corner of the reference box.
The reference box is defined by property transform-box. If not explicitely set, its initial value is view-box:
Uses the nearest SVG viewport as reference box.
In your case, this is the box that defines the <svg> element and outlined by the black border, but with the defined sequence of transforms applied. (If there was a viewBox attribute present, it would represent a further implicit transform, to be applied first.)

SVG Pattern with Gradient and Smooth Tile Transitions

I’m trying to understand how to apply an SVG pattern with a gradient that starts near the top of a web page and extends to the bottom.
I haven’t been able to apply the gradient to the entire pattern, only to the individual tiles that make up the pattern. I’ve tried applying the gradient to the tiles, to the pattern, to a element, and to a CSS background image.
Also, when the pattern is tiled, the rounded corners should only appear for the top tile, as the tiles overlap. For some reason the transparency is piercing from the overlapping tile all the way through to the background, even though the tile underneath has a fill color.
a. Is there any way to apply a gradient to the entire pattern, perhaps by wrapping it in another SVG?
b. How do I get rid of the display of rounded corners between the overlapping tiles, except for the top tile, which does not overlap another tile? Can the z-index of the tiles or the tile stacking order be changed?
I also tried creating a very long column of tiles in illustrator, which solves the problem of displaying the rounded corners between overlapping tiles and allows for applying a gradient to the entire pattern. The long SVG solution had several other issues:
c. Apparently if I’m using CSS parallax on a webpage, it’s impossible to hide the overflow and preserve3D, so the entire column has to be displayed.
d. The gradient is applied to the entire column, not just the pattern as rendered on the page, so most of the gradient is out of view most of the time.
e. Several of the tiles have single pixel lines of transparency separating them, even though they were all snapped in Illustrator. Apparently creating an SVG tile pattern in Illustrator will require manually entering the coordinates so that at least 1 pixel overlaps for each tile.
Any suggestions on how to create a pattern with a gradient that extends from near the top of a page all the way to the bottom with no transparent artifacts between the tiles would be welcome.
Here’s an image of an SVG pattern test, and the codepen.
Thanks for your help!
https://codepen.io/ripmurdock/pen/JjOLXqL?editors=1000
SVG pattern with gradient
* {
margin:0;
padding:0;
}
body{
background-color:tomato;
}
.gradient-1{
color: white;
}
<svg width="100%" height="99.5vh">
<defs>
<pattern id="cc_vert_tiles-1" x="" y="" width="300" height="176" patternUnits="userSpaceOnUse" fill="url(#SVGID_1_)">
<use href="#cp_3_ring_rnd4_uneq_1_" />
</pattern>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="100.0005" y1="200" x2="100.0005" y2="4.882813e-004">
<stop offset="0.0443" style="stop-color:#FFFF00"/>
<stop offset="0.1669" style="stop-color:#FFFD00"/>
<stop offset="0.2387" style="stop-color:#FFF400"/>
<stop offset="0.2975" style="stop-color:#FFE600"/>
<stop offset="0.3491" style="stop-color:#FFD200"/>
<stop offset="0.3961" style="stop-color:#FFB800"/>
<stop offset="0.4392" style="stop-color:#FF9900"/>
<stop offset="0.468" style="stop-color:#FF7F00"/>
<stop offset="0.5948" style="stop-color:#BC7F00"/>
<stop offset="0.7949" style="stop-color:#587F00"/>
<stop offset="0.9343" style="stop-color:#197F00"/>
<stop offset="1" style="stop-color:#007F00"/>
</linearGradient>
<path id="cp_3_ring_rnd4_uneq_1_" height="200" width="200" d="M200,190c0,5.522-4.478,10-10,10H10c-5.523,0-10-4.478-10-10V10C0,4.477,4.477,0,10,0h180
c5.522,0,10,4.477,10,10V190z M5,99.621c0,52.467,42.533,95,95,95c52.467,0,95-42.533,95-95c0-52.467-42.533-95-95-95
C47.533,4.621,5,47.154,5,99.621z"/>
</defs>
<g class="gradient-1">
<rect id="cc_vert_pat-1" width="200" height="110vh" fill="url(#cc_vert_tiles-1)" />
</g>
</svg>
a. Is there any way to apply a gradient to the entire pattern, perhaps by wrapping it in another SVG?
Every copy of a pattern tile will be identical. If you need a gradient to cover the whole document, then it would need to be a separate element.
b. How do I get rid of the display of rounded corners between the overlapping tiles, except for the top tile, which does not overlap another tile? Can the z-index of the tiles or the tile stacking order be changed?
The tiles in a pattern never overlap each other. Your pattern tiles are specified to be 300x176. Anything outside that region will not be rendered. However the path that you are using in your tile is 200x200. So 24 units of the bottom of the path are being clipped off (200-176). That is the reason why the bottom rounded corners are not showing.
If you want the whole of the path to be visible, then it needs to be no bigger than 300x176.
If you need your tiles to actually overlap, then patterns are not useful to you. You would need to draw all the tiles yourself. As per your Illustrator experiment you mentioned.
In the following example, I've changed the pattern dimensions to match the area that the path covers (200x200).
* {
margin:0;
padding:0;
}
body{
background-color:tomato;
}
.gradient-1{
color: white;
}
<svg width="100%" height="99.5vh">
<defs>
<pattern id="cc_vert_tiles-1" x="" y="" width="200" height="200" patternUnits="userSpaceOnUse" fill="url(#SVGID_1_)">
<use href="#cp_3_ring_rnd4_uneq_1_" />
</pattern>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="100.0005" y1="200" x2="100.0005" y2="4.882813e-004">
<stop offset="0.0443" style="stop-color:#FFFF00"/>
<stop offset="0.1669" style="stop-color:#FFFD00"/>
<stop offset="0.2387" style="stop-color:#FFF400"/>
<stop offset="0.2975" style="stop-color:#FFE600"/>
<stop offset="0.3491" style="stop-color:#FFD200"/>
<stop offset="0.3961" style="stop-color:#FFB800"/>
<stop offset="0.4392" style="stop-color:#FF9900"/>
<stop offset="0.468" style="stop-color:#FF7F00"/>
<stop offset="0.5948" style="stop-color:#BC7F00"/>
<stop offset="0.7949" style="stop-color:#587F00"/>
<stop offset="0.9343" style="stop-color:#197F00"/>
<stop offset="1" style="stop-color:#007F00"/>
</linearGradient>
<path id="cp_3_ring_rnd4_uneq_1_" d="M200,190c0,5.522-4.478,10-10,10H10c-5.523,0-10-4.478-10-10V10C0,4.477,4.477,0,10,0h180
c5.522,0,10,4.477,10,10V190z M5,99.621c0,52.467,42.533,95,95,95c52.467,0,95-42.533,95-95c0-52.467-42.533-95-95-95
C47.533,4.621,5,47.154,5,99.621z"/>
</defs>
<g class="gradient-1">
<rect id="cc_vert_pat-1" width="200" height="110vh" fill="url(#cc_vert_tiles-1)" />
</g>
</svg>
It is possible to have a gradient that covers all the tiles in the pattern. What you can do is apply the gradient to the rectangle, and turn the pattern into a mask.
* {
margin:0;
padding:0;
}
body{
background-color:tomato;
}
<svg width="100%" height="99.5vh">
<defs>
<pattern id="cc_vert_tiles-1" x="" y="" width="200" height="200" patternUnits="userSpaceOnUse" fill="url(#SVGID_1_)">
<use href="#cp_3_ring_rnd4_uneq_1_" fill="white" />
</pattern>
<linearGradient id="SVGID_1_" gradientUnits="objectBoundingBox" x1="0" y1="1" x2="0" y2="0">
<stop offset="0.0443" style="stop-color:#FFFF00"/>
<stop offset="0.1669" style="stop-color:#FFFD00"/>
<stop offset="0.2387" style="stop-color:#FFF400"/>
<stop offset="0.2975" style="stop-color:#FFE600"/>
<stop offset="0.3491" style="stop-color:#FFD200"/>
<stop offset="0.3961" style="stop-color:#FFB800"/>
<stop offset="0.4392" style="stop-color:#FF9900"/>
<stop offset="0.468" style="stop-color:#FF7F00"/>
<stop offset="0.5948" style="stop-color:#BC7F00"/>
<stop offset="0.7949" style="stop-color:#587F00"/>
<stop offset="0.9343" style="stop-color:#197F00"/>
<stop offset="1" style="stop-color:#007F00"/>
</linearGradient>
<path id="cp_3_ring_rnd4_uneq_1_" d="M200,190c0,5.522-4.478,10-10,10H10c-5.523,0-10-4.478-10-10V10C0,4.477,4.477,0,10,0h180
c5.522,0,10,4.477,10,10V190z M5,99.621c0,52.467,42.533,95,95,95c52.467,0,95-42.533,95-95c0-52.467-42.533-95-95-95
C47.533,4.621,5,47.154,5,99.621z"/>
<mask id="pat-mask">
<rect id="cc_vert_pat-1" width="200" height="110vh" fill="url(#cc_vert_tiles-1)" />
</mask>
</defs>
<g class="gradient-1">
<rect id="cc_vert_pat-1" width="200" height="110vh" fill="url(#SVGID_1_)" mask="url(#pat-mask)"/>
</g>
</svg>
c. Apparently if I’m using CSS parallax on a webpage, it’s impossible to hide the overflow and preserve3D, so the entire column has to be displayed.
d. The gradient is applied to the entire column, not just the pattern as rendered on the page, so most of the gradient is out of view most of the time.
I'm not sure what you mean for these. You probably need to ask a separate question for these.
e. Several of the tiles have single pixel lines of transparency separating them, even though they were all snapped in Illustrator. Apparently creating an SVG tile pattern in Illustrator will require manually entering the coordinates so that at least 1 pixel overlaps for each tile.
This is normally due to antialiasing. It can be avoided by making sure that your tiles match screen pixel boundaries. If you do that, then you shouldn't need to overlap your tiles.

SVG path with equal height positions will not render [duplicate]

I'm attempting to replicate an <hr> with SVG. Now, making a straight line with SVG works perfectly, but the second I stroke it with a gradient it will no longer display in a straight line.
The following code works, but take note, y1 and y2 must be 1 unit apart. If I set y1 and y2 to 210 for instance, the line will disappear.
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:rgba(0,0,0,0);stop-opacity:0" />
<stop offset="50%" style="stop-color:rgba(0,0,0,0.75);stop-opacity:1" />
<stop offset="100%" style="stop-color:rgba(0,0,0,0);stop-opacity:0" />
</linearGradient>
</defs>
<line x1="40" y1="210" x2="460" y2="211" stroke="url(#grad1)" stroke-width="1" />
I'm probably just missing some obvious function of linear gradients, and the line looks alright, but I'd much rather just have it straight. Thanks.
You are getting caught out by the last paragraph in this part of the SVG specification
Keyword objectBoundingBox should not be used when the geometry of the applicable element has no width or no height, such as the case of a horizontal or vertical line, even when the line has actual thickness when viewed due to having a non-zero stroke width since stroke width is ignored for bounding box calculations. When the geometry of the applicable element has no width or height and objectBoundingBox is specified, then the given effect (e.g., a gradient or a filter) will be ignored.
objectBoundingBox is the default for gradientUnits so you need to use gradientUnits="userSpaceOnUse" and then adjust the values to be appropriate for the different coordinate system.
Robert Longson gives an excellent explanation. But sometimes userSpaceOnUse is a pain, because it spreads the interpolation over the entire canvas, instead of just the line.
Instead, you could add a tiny amount to the values, to ensure the bbox size is not zero.
For example,
<line x1="40" y1="210" x2="460" y2="210.001" stroke="url(#grad1)" stroke-width="1" />
will draw a straight line with gradient.
Assuming you do not want any in-accuracy at all, you can change the line to a filled path as below
<path d='M 40 209.5 L 460 209.5 L 460 210.5 L 40 210.5' fill='url(#grad1)' />
or alternatively to a filled rect as below
<rect x=40 y=209.5 width=420 height=1 fill='url(#grad1)' />
It is also interesting to note that this issue only affects vertical and horizontal lines. Slanted line display correctly.

Create a svg rectangle filled with different background color by percentage or pixel

I want to create svg rectangles dynamically with javascript.
Those rectangles should be kind of a 2D diagramm bar filled with multiple background colors just like so:
Does there exist any shape for svg which can handle multiple background colors ?
I do not want to use multiple rectangles and try to position them...
Event better would be if there would exist kind of a stackpanel where I can put into child elements...
because then I would like to bind those elements to knockoutjs.
You can do it with a linearGradient, if you set the stop colours such that the gradient is an instant change in colour at the stops. E.g.
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="MyGradient" x2="0%" y2="100%">
<stop offset="33%" stop-color="white" />
<stop offset="33%" stop-color="#FF6" />
<stop offset="66%" stop-color="#FF6" />
<stop offset="66%" stop-color="#F60" />
</linearGradient>
</defs>
<rect fill="url(#MyGradient)" stroke="black" stroke-width="5"
x="100" y="100" width="200" height="600"/>
</svg>
Alternatively, and perhaps more simply, you could dynamically create 3 rects with 3 different fills. If you put the rectangles as children of a <g> element you can set a transform on the <g> element and it will position all the rectangles together wherever you want.