SVG content rendering outside viewBox? - svg

I'm trying to use SVG to graph some data. My timestamps are relatively long integers (at least ten digits). To my understanding, if I have data from tmin to tmax with measurements ranging from ymin to ymax, my viewbox should be
<svg viewBox=" t_min y_min (t_max-t_min) (y_max-y_min)" />
<!-- [x_min] [y_min] [width] [height] -->
For small values, it seems to hold well enough, though the wacky output could reveal already that I'm misunderstanding something.
<svg preserveAspectRatio="none"
width="500" height="300"
viewBox="6000 -5 3600 10">
<line x1="6000" y1="-5"
x2="9600" y2="5"
stroke-width="2" stroke="black"/>
</svg>
With large values though, things break down :-( What am I doing wrong?
<svg preserveAspectRatio="none"
width="500" height="300"
viewBox="5573826000 -5 3600 10">
<line x1="5573826000" y1="-5"
x2="5573829600" y2="5"
stroke-width="2" stroke="black"/>
</svg>

Related

Drawing a partial circle, with varying height, without gradients (for leaflet map markers)

I'm trying to generate an svg icon to use as Leaflet map markers. There's a circle which should be partially filled (vertically) based on a percentage variable between 0 and 1 (0 = no fill, 1 = full circle). I managed to accomplish what I need using a linear gradient, and that was working fine in Google Maps. Now I'm migrating to Leaflet, and apparently Leaflet doesn't support linear gradients in icons, as it doesn't render it properly. Or maybe it just doesn't support referencing elements with url().
This is what I have been using, using 0.4 as the fill percentage:
<svg width="25px" height="25px" viewBox="0 0 100 100" version="1.1">
<linearGradient id="perc" x1="0.4" y1="1" x2="0.4" y2="0">
<stop stop-color="#80C000" offset="0.4"/>
<stop stop-color="#fff" />
</linearGradient>
<ellipse id="outsideCircle" fill="#80C000" cx="50" cy="50" rx="42.8" ry="42.8"/>
<ellipse id="middleCircle" fill="url(#perc)" cx="50" cy="50" rx="41.2" ry="41.2"/>
<ellipse id="insideCircle" stroke="#80C000" fill="#EFFADC" stroke-width="1.6" cx="50" cy="50" rx="32" ry="32"/>
<text text-anchor="middle" font-family="Verdana" font-size="320%" font-weight="bold" dominant-baseline="central" x="50" y="48" fill="#80C000">A</text>
</svg>
Which renders as (enlarged for clarity):
However, using the exact same code in Leaflet always renders the full green circle, and I'm not sure why. So I'm looking for another way to accomplish this, maybe using Arcs? It would be great if I could generate the icons based on the same percentage variable, but I'm also open to just using 11 static icons (0, 0.1, 0.2, ..., 0.9, 1) and selecting one to use based on the percentage, rounding it.
I've found this question with some promising results, but I've been unable to adapt it to my use case.
An alternative solution would be using a line path that you clip with the circle. Now you can use the stroke-dasharray attribute to set the stroke and the gap length of the path.
I'm using an input type range only to show how the result for different values.
let len = line.getTotalLength();
itr.addEventListener("input",()=>{
let dash = itr.value * len/100;
line.setAttribute("stroke-dasharray",`${dash},${100 - dash}`);
console.clear();console.log(itr.value)
})
<P><input type="range" value="25" id="itr"/></p>
<svg width="100" viewBox="0 0 100 100" version="1.1">
<text text-anchor="middle" font-family="Verdana" font-size="320%" font-weight="bold" dominant-baseline="central" x="50" y="48" fill="#80C000">A</text>
<clipPath id="clip">
<path id="circ" d="M92.8,50A42.8, 42.8 0 1 1 7.2,50A42.8,42.8 0 1 1 92.8,50 M82,50A32,32 0 1 0 18,50 A32,32 0 1 0 82,50" />
</clipPath>
<path id="line" stroke="#80C000" stroke-width="86" d="M50,92.8V7.2" clip-path="url(#clip)" stroke-dasharray="25 75" />
<use href="#circ" fill="none" stroke="#80C000" />
</svg>
Observation: I'm rewriting thecircle as a path with a hole in the middle like so:
<svg width="100" viewBox="0 0 100 100" version="1.1">
<path id="circ" d="M92.8,50A42.8, 42.8 0 1 1 7.2,50A42.8,42.8 0 1 1 92.8,50 M82,50A32,32 0 1 0 18,50 A32,32 0 1 0 82,50" />
</svg>
In order to draw a hole into the circle I'm drawing first the outer circle clockwise and nextnthe inner circle counterclockwise. I will use this shape for the clipPath but also for a <use> element to draw the green stroke of the circle.

How to smooth SVG line

Exmaple is by link
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<line x1="0" y1="80" x2="100" y2="20" stroke="black" />
</svg>
As you can notice the end of line has cut corner, not filled to the end of border. How to smooth this?
The corner is truncated because it goes beyond your canvas. Just make your canvas a little bigger or your line a little smaller and it will work
Here's an exemple where i moved the line by 1 unit to the right and made the canvas sligtly bigger. You can see that the corners are as they sould be
<svg viewBox="0 0 101 100" xmlns="http://www.w3.org/2000/svg">
<line x1="1" y1="80" x2="100" y2="20" stroke="black" />
</svg>
You need to clarify what you mean by "filled to end of border".
If you mean that you want the line to go all the way to the SVG border without seeing the end of it, then there are two solutions.
Draw your line a little longer. So it goes all the way off the SVG. Right now it stops right at the border, and because of the angle, part of the end is visible.
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<line x1="-2" y1="80" x2="102" y2="20" stroke="black" />
</svg>
Alternatively, you could give the line square end caps. That will extend the line a little, so it will go just far enough. However this will only work if your line isn't too steep. If it gets steeper than 45 degrees, the end of the line will start to become visible again.
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<line x1="0" y1="80" x2="100" y2="20" stroke="black" stroke-linecap="square" />
</svg>

Add svg icon in the middle of an svg curved line

I have a curved svg line like this
<path d="M70,260 C105,260 126,330 160,330"
style="stroke: #ff4444;stroke-width:2; fill:none;"/>
what I want is to add another svg (like https://www.flaticon.com/free-icon/play-button_149657) in the middle of my line pointing to the end point.
any ideas?
One way to achieve the result is a degenerate animation:
Define the marker shape (obj1 in the example below)
Position the marker at the beginning of the curve (track1 below; this is the path definition from your example).
Specify an animated motion of the marker shape along the curve with some particular settings:
Explicit positioning along the track using keyTimes, keyPoints attributes, limiting the range of positions to exactly one point: the midpoint of the curve
Infinite duration, infinite repeat
Auto-rotation of the shape according to the orientation of the track curve ( rotate attribute )
Effectively there is no animation at all but the shape is positioned at the center of the curve, properly oriented.
Example
<html>
<head>
<title>SVG object centered on path</title>
</head>
<body>
<svg width="200px" height="200px"
viewBox="0 0 500 500"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs>
<path
id="obj1"
d="M11.18,0 L-2.5,10 -2.5,-10 Z"
stroke="black" stroke-width="1" fill="green"
>
</path>
<path
id="track1"
d="M70,260 C105,260 126,330 160,330"
stroke="#ff4444" stroke-width="2" fill="none"
/>
</defs>
<use xlink:href="#track1"/>
<use xlink:href="#obj1">
<animateMotion
calcMode="linear"
dur="infinite"
repeatCount="infinite"
rotate="auto"
keyPoints="0.5;0.5"
keyTimes="0.0;1.0"
>
<mpath xlink:href="#track1"/>
</animateMotion>
</use>
</svg>
</body>
</html>
There are a number of ways to do this.
One way is to "cheat" a little and use a <textPath> and an arrow character.
SVG marker-mid on specific point on path
This is a little hacky, and may not work reliably on all browsers, but it may be good enough for your needs.
Another way is split the path in two (using De Casteljau's algorithm), and use a <marker>.
<svg viewBox="0 200 200 200" width="400">
<defs>
<marker id="Triangle"
viewBox="0 0 10 10" refX="0" refY="5"
markerUnits="strokeWidth"
markerWidth="4" markerHeight="3"
orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
</defs>
<path d="M 70,260
C 87.5,260 101.5,277.5 115.375,295
C 129.25,312.5 143,330 160,330"
style="stroke: #ff4444; stroke-width:2; fill:none; marker-mid:url(#Triangle)"/>
</svg>
There are other ways using Javascript. For example, you could use the SVGPathElement.getPointAtLength() method to find the coordinates of the centre of the path. Then position a triangle at that location.

Improve SVG so pin is centered inside circle, without multiple viewboxes

I have a pin that needs to be shown inside a circle in Svg.
My current code is the following:
<svg viewBox="0 0 20 20" preserveAspectRatio="xMinYMin meet">
<circle cx="50%" cy="1.5" r="1.5" style="fill: green;"></circle>
<svg x="47.5%" y="5%" viewBox="0 0 10000 10000" fill="#fff" preserveAspectRatio="none">
<g>
<path d="M250,124.3c-35,0-63.4,28.8-63.4,64.1c0,35.3,28.5,64,63.4,64s63.4-28.8,63.4-64.1C313.4,153,285,124.3,250,124.3z
M250,222c-18.3,0-33.2-15.1-33.2-33.7s14.9-33.7,33.2-33.7s33.2,15.1,33.2,33.7S268.3,222,250,222z">
</path>
<path d="M250,50.9c-74.9,0-135.8,61.6-135.8,137.4c0,31.3,22.5,84.4,66.9,157.7c32.9,54.4,66.2,100.3,66.6,100.7l2.4,3.3l2.4-3.3
c0.3-0.5,33.7-46.3,66.6-100.7c44.4-73.3,66.9-126.4,66.9-157.7C385.8,112.5,324.9,50.9,250,50.9z M250,397.6
c-16.5-24.3-45.5-68.4-69.9-114c-23.5-44-35.9-77-35.9-95.4c0-59,47.4-107,105.8-107s105.8,48,105.8,107
c0,18.4-12.4,51.4-35.9,95.4C295.4,329.3,266.5,373.4,250,397.6z">
</path>
</g>
</svg>
</svg>
which works somewhat but seems inelegant, and perhaps also buggy. What I would like is a better way to center the group 'inside' the circle without using JavaScript
It would be nice if I could get rid of the extra SVG element in the middle with its really big viewBox that I'm using to place the pin. So if you can show me how to do it with just a g and make a scaling function that would be good.
If you want to use coordinates that contain percentage values, you need an element that has x and y attributes. <use> is such an element, <g> is not.
Your live will be easier if you draw your pin centered on the origin of the coordinate system: translate(-250 -230).
After that, you can easily scale it to the size you need: scale(0.0025) (remember: multiple transform commands are processed right-to-left.)
Finally, you use the pin template with the same x and y coordinates as your circle.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 20 20" preserveAspectRatio="xMinYMin meet">
<defs>
<!--center the pin around the origin and scale it to final size-->
<g id="pin" transform="scale(0.0025) translate(-250 -230)">
<path d="M250,124.3c-35,0-63.4,28.8-63.4,64.1c0,35.3,28.5,64,63.4,64s63.4-28.8,63.4-64.1C313.4,153,285,124.3,250,124.3z
M250,222c-18.3,0-33.2-15.1-33.2-33.7s14.9-33.7,33.2-33.7s33.2,15.1,33.2,33.7S268.3,222,250,222z" />
<path d="M250,50.9c-74.9,0-135.8,61.6-135.8,137.4c0,31.3,22.5,84.4,66.9,157.7c32.9,54.4,66.2,100.3,66.6,100.7l2.4,3.3l2.4-3.3
c0.3-0.5,33.7-46.3,66.6-100.7c44.4-73.3,66.9-126.4,66.9-157.7C385.8,112.5,324.9,50.9,250,50.9z M250,397.6
c-16.5-24.3-45.5-68.4-69.9-114c-23.5-44-35.9-77-35.9-95.4c0-59,47.4-107,105.8-107s105.8,48,105.8,107
c0,18.4-12.4,51.4-35.9,95.4C295.4,329.3,266.5,373.4,250,397.6z" />
</g>
</defs>
<!--use the same coordinates for the center of the circle and the pin-->
<circle cx="50%" cy="1.5" r="1.5" fill="green" />
<use xlink:href="#pin" x="50%" y="1.5" fill="white" />
</svg>

SVG absolute line marker size

I've got a bunch of SVGs representing a trip using a polyline.
I'm using non-scaling-stroke vector effect to make sure that each of the polylines is rendered with an absolute width. So when the viewBox's dimension changes, the width of the polyline does not.
At the ends of the mentioned polylines, I wanted to put markers. I would like to have the markers absolutely sized as well. I thought this should be easy by setting the marker units to strokeWidth.
In contrast to what happens with the stroke of the polyline, the size of the markers at the end of a polyline, does change along with the size of the viewBox.
Below I've included an example in where I used simple circles for markers.
<svg preserveAspectRatio="xMidYMid" xmlns="http://www.w3.org/2000/svg" viewBox="131890.80533333332 85178.93015415945 198.25991111111944 205.10300513348193">
<defs>
<marker id="end" markerHeight="4" markerUnits="strokeWidth" markerWidth="4" orient="0deg" refX="8" refY="16" viewBox="0 0 16 20">
<circle cx="8" cy="16" fill="#000" r="4"></circle>
</marker>
<marker id="start" markerHeight="4" markerUnits="strokeWidth" markerWidth="4" orient="0deg" refX="8" refY="16" viewBox="0 0 16 20">
<circle cx="8" cy="16" fill="#000" r="4"></circle>
</marker>
</defs>
<g>
<polyline fill="none" marker-end="url(#end)" marker-start="url(#start)" stroke="#000" stroke-linejoin="round" stroke-width="3" vector-effect="non-scaling-stroke" points="132089.06524444444, 85384.03315929293 131890.80533333332, 85178.93015415945">
</polyline>
</g>
</svg>
<svg preserveAspectRatio="xMidYMid" xmlns="http://www.w3.org/2000/svg" viewBox="132038.56071111112 85364.68902323228 50.557866666669725 19.330493533576373">
<defs>
<marker id="end" markerHeight="4" markerUnits="strokeWidth" markerWidth="4" orient="0deg" refX="8" refY="16" viewBox="0 0 16 20">
<circle cx="8" cy="16" fill="#000" r="4"></circle>
</marker>
<marker id="start" markerHeight="4" markerUnits="strokeWidth" markerWidth="4" orient="0deg" refX="8" refY="16" viewBox="0 0 16 20">
<circle cx="8" cy="16" fill="#000" r="4"></circle>
</marker>
</defs>
<g>
<polyline fill="none" marker-end="url(#end)" marker-start="url(#start)" stroke="#000" stroke-linejoin="round" stroke-width="3" vector-effect="non-scaling-stroke" points="132038.56071111112, 85364.68902323228 132089.1185777778, 85384.01951676585">
</polyline>
</g>
</svg>
See also: https://jsfiddle.net/u4bmupza/3/
Am I missing some SVG attributes or should I calculate the size of the markers manually?
vector-effect="non-scaling-stroke was first introduced in SVG 1.2 Tiny. That was a subset of SVG intended for for mobile devices with limited power. SVG 1.2 didn't have markers, so this issue wasn't a problem.
vector-effect was about the only thing from SVG 1.2 Tiny that browsers ended up implementing. Unfortunately, this problem was obviously missed at that time, and I guess no one has bothered to report it as a bug. Though I've seen it asked about on S.O. previously.
The good news is that the SVG2 spec specifically notes it as something that should be addressed, though that doesn't help you now.

Resources