possible to use calc() in svg line? - svg

I'm not sure if this is the best approach. I'm trying to make an SVG that has parts that scale and parts that are fixed. It looks like this:
When the web page loads, I don't know what the height of the container for it will be but I know the width. I want the joining lines to scale based on the height, but keep the box with the plus centered like this:
I've played around with the line settings for x1, y1, etc., but I can't figure out a way to do it without resorting to javascript. Is SVG not the best option here? Here's what I have so far:
<svg class="s2">
<line x1="50%" y1="0" x2="50%" y2="10%" style="stroke:rgba(255,0,0,.9);stroke-width:1px;"></line> <!-- top joining line -->
<g id="square" x="50%" y="50%" width="16px" height="16px">
<line x1="5" y1="8" x2="11" y2="8" style="stroke:rgba(255,0,0,.9);stroke-width:1px;"></line> <!-- plus horizontal line -->
<line x1="8" y1="5" x2="8" y2="11" style="stroke:rgba(255,0,0,.9);stroke-width:1px;"></line> <!-- plus vertical line -->
<rect x="4" y="4" width="8" height="8" style="fill:transparent;stroke:rgba(0,0,0,.5);"></rect>
</g>
<line x1="50%" y1="90%" x2="50%" y2="100%" style="stroke:rgba(255,0,0,.9);stroke-width:1px;"></line> <!-- bottom joining line -->
<line x1="90%" y1="50%" x2="100%" y2="50%" style="stroke:rgba(255,0,0,.9);stroke-width:1px;"></line> <!-- right joining line -->
</svg>
Would javascript be my only option here? I tried using values like
calc(50% - 5px)
for the line positioning but it looks like it's not supported. If it was that would fix the problem.

For the solution, you have to combine two techniques:
masking parts of the lines, and
combine positioning in px with CSS translations in percentage
You start by positioning your rect centered on the coordinate origin, giving sizes in pixels. The joining lines are first simply drawn without interruption from -50% to +50%. Then the parts behind your central rect are masked out, the sizing again in px.
Finally, everything is moved by transform:translate(50%, 50&) to fill the SVG. It is important to note that this is the CSS transform property that can have units, while the SVG transform presentation attribute can only have unitless numbers. It therefore has to be written in a style attribute (or in a stylesheet).
#outermost {
transform:translate(50%, 50%);
}
g line {
stroke:rgba(255,0,0,.9);
stroke-width:1px;
}
g rect {
fill:none;
stroke:rgba(0,0,0,.5);
}
<svg xmlns="http://www.w3.org/2000/svg" class="s2" width="24" height="100">
<mask id="cp">
<rect x="-50%" y="-50%" width="100%" height="100%" fill="white"></rect>
<rect x="-6" y="-6" width="12" height="12" fill="black"></rect>
</mask>
<g id="outermost">
<g mask="url(#cp)">
<line x1="0" y1="-50%" x2="0" y2="50%"></line>
<line x1="0" y1="0" x2="50%" y2="0"></line>
</g>
<line x1="-3" y1="0" x2="3" y2="0"></line>
<line x1="0" y1="-3" x2="0" y2="3"></line>
<rect x="-4" y="-4" width="8" height="8"></rect>
</g>
</svg>

Related

GSAP: SVG not centering as expected

2 questions about this CodePen
Why isn't the red balloon ending up centered on the crosshairs, given that I've set transformOrigin:"50% 50%"?
Why does the green balloon seem to have its origin set to "left top" when, according to this doc, it should default to "50% 50%"?
Relevant code (I think)
HTML
<svg class="container" fill="#f0c0c0" style="background: linear-gradient(to top, #ddfdff, #6dd5fa, #2980b9);
;" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
<g id="green-balloon">
<path … />
<path … />
<text …>🕊</text>
</g>
<g id="red-balloon">
<path …/>
<path …/>
<text …>⚡️</text>
</g>
<line x1="0" y1="100" x2="200" y2="100" stroke="white" stroke-width=".5px"/>
<line x1="100" y1="0" x2="100" y2="200" stroke="white" stroke-width=".5px" />
<defs>…</defs>
</svg>
JS
var redBalloon = $("#red-balloon");
var greenBalloon = $("#green-balloon");
var tl = new TimelineLite({onUpdate:updateSlider});
tl.set(greenBalloon, {x:100, y:200})
.set(redBalloon, {transformOrigin:"50% 50%", x:100,y:200})
.to(greenBalloon, 1, {scale:2, y:100})
.to(redBalloon, 1, {scale:2, y:100})
CSS
Not applicable.
Per OUSblake's answer on the GreenSock forums:
transformOrigin/svgOrigin affect scale, rotation, and skew. And svgOrigin uses the <svg> element's coordinate system. So svgOrigin: 50% 50% means everything is going to transform around 100,150 in the svg. transformOrigin: 50% 50% would be the center of the balloon. To center your element, use xPercent: -50 and yPercent: -50.
After providing a demo on Codepen, he continues:
It sounds like you were expecting transformOrigin to behave like in Adobe products, where changing the registration point causes the element to move. It doesn't, but that's what xPercent/yPercent are for. 😃
Just use those with a transformOrigin: 50% 50%, and everything should be pretty easy with curves.
He even went so far as to demonstrate motion along a path. Super helpful!

Responsive svg with percentage and pixel values

I can create a svg with line at any given percentage "0%-100%" so that the rounded borders (in pixels) are not included in the "percentage width" with the help of calc calc(100% - 25px)
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="50">
<rect fill="lightblue" y="0" x="0" width="100%" height="50" rx="25" ry="25"></rect>
<g class="percentage">
<line class="100pct" x1="calc(100% - 25px)" x2="calc(100% - 25px)" y1="0" y2="50" stroke="red" stroke-width="4"></line>
</g>
</svg>
But the question is, can this same svg be created without calc for legacy browsers?
I can use transform and translate to take one rounded side into account, but I can't figure out how to limit the width / add some kind of margin.
The percentage change, so one shared translate gets me only halfway there, here the red 100% line is out of bounds:
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="50">
<rect fill="lightblue" y="0" x="0" width="100%" height="50" rx="25" ry="25"></rect>
<g class="percentage" transform="translate(25, 0)">
<line class="0pct" x1="0%" x2="0%" y1="0" y2="50" stroke="blue" stroke-width="4"></line>
<line class="100pct" x1="100%" x2="100%" y1="0" y2="50" stroke="red" stroke-width="4"></line>
</g>
</svg>
Is there really any browser supporting the above syntax? If yes, it is in violation of even the SVG2 spec:
A future specification may convert the ‘x1’, ‘y1’, ‘x2’, and ‘y2’ attributes to geometric properties. Currently, they can only be specified via element attributes, and not CSS.
(And since calc() is a CSS function, it can only be used in CSS contexts.)
What will work in all browsers supporting SVG is to combine x/y values with transforms; unitless = px values go to the transform attribute, units (percentages) go to the x/y attribute.
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="50">
<rect fill="lightblue" y="0" x="0" width="100%" height="50" rx="25" ry="25"></rect>
<g class="percentage" >
<line class="0pct" x1="100%" x2="100%" y1="0" y2="50" transform="translate(-25, 0)" stroke="red" stroke-width="4"></line>
</g>
</svg>
In addition to the SVG 1.1 transform attribute, there is also the CSS transform property and its 2D functions which are implemented quite fairly (exeption: IE and Edge < 17). They must use unit identifiers, and should also support nested calc() functions. I have no compatibility data for that combination, but there are also no bug reports mentioned in caniuse.com.
What currently does not work is using the CSS transform syntax as a presentation attribute (CSS transform spec is not yet implemented in that regard), so you need to use them in style attributes.
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="50">
<rect fill="lightblue" y="0" x="0" width="100%" height="50" rx="25" ry="25"></rect>
<g class="percentage" stroke="red" stroke-width="4" >
<line class="0pct" x1="0" x2="0" y1="0" y2="50"
style="transform:translate(calc(0 * (100% - 50px) + 25px))" />
<line class="50pct" x1="0" x2="0" y1="0" y2="50"
style="transform:translate(calc(0.5 * (100% - 50px) + 25px))" />
<line class="100pct" x1="0" x2="0" y1="0" y2="50"
style="transform:translate(calc(1 * (100% - 50px) + 25px))" />
</g>
</svg>
As you can see, the positional value is no longer a percentage (multiplying pixels with percentages does not work), but a fraction of 1. I hope that works for you.

How to cut out a dashed line in SVG?

Here is an example:
<svg width="200" height="200">
<rect x="50" y="50" width="100" height="100"/>
<line x1="0" y1="0" x2="200" y2="200" stroke="red" stroke-width="5" stroke-dasharray="5"/>
</svg>
https://codepen.io/anon/pen/oyqYKZ
I want the red line to be cut out from the rect, so that where there are red dashes on the rect there should be holes in the rect.
I tried to use a clipPath: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Clipping_and_masking. But that seems to only cut out the "fill" rather than the stroke.
What you need is a mask.
For masks, color is important. Imagine the content of the mask element being converted to greyscale. White then will give the masked content full opacity, black zero opacity, and the greys inbetween partial opacity. Empty areas are considered black = transparent. The mask must therefore contain a white background and the dashed line in black in the foreground.
<svg width="200" height="200">
<mask id="dash">
<rect width="100%" height="100%" fill="white"/>
<line x1="0" y1="0" x2="200" y2="200" stroke="black" stroke-width="5" stroke-dasharray="5"/>
</mask>
<rect x="50" y="50" width="100" height="100" mask="url(#dash)"/>
</svg>

Coloring the area between four lines in svg

Is there any way to color the area between four points except using 'fill' in polygon?
I have drawn a polygon using four lines as,
<line x1="0" y1="0" x2="300" y2="0" style="stroke:rgb(255,0,0);stroke-width:2"></line>
<line x1="300" y1="0" x2="300" y2="150" style="stroke:rgb(255,0,0);stroke-width:2"></line>
<line x1="300" y1="150" x2="0" y2="150" style="stroke:rgb(255,0,0);stroke-width:2"></line>
<line x1="0" y1="150" x2="0" y2="0" style="stroke:rgb(255,0,0);stroke-width:2"></line>
and I want to color the area between these lines.
No there isn't as you don't really have a filled in shape. You just have four lines that happen to meet up.
Using a rect would be a better option for this:
<rect x="0" y="0" width="300" height="150" fill="pink"/>
http://jsfiddle.net/nfxTE/
Or instead of doing four independent lines, you could use a polyline and add a fill to that:
<polyline points="0,0 300,0 300,150 0,150 0,0"
style="fill: pink; stroke:red; stroke-width: 1"/>
http://jsfiddle.net/nfxTE/2/
The only other way without using a polygon, polyline (or similar) and fill, is to do one line with a wide stroke:
<line x1="0" y1="75" x2="300" y2="75" style="stroke:red ;stroke-width:150"></line>
http://jsfiddle.net/nfxTE/1/
This assumes that the fill will be the same colour as the stroke. As a stroke is half inside and half outside the line/shape, you have to set the co-ordinates of the line to be half the distance between the desired starting point and the width of the stroke. Here the stroke is 150 and we want it to start at 0 so the y1 and y2 points are set to 75.

How to make an SVG "line" with a border around it?

I have a little svg widget whose purpose is to display a list of angles (see image).
Right now, the angles are line elements, which only have a stroke and no fill. But now I'd like to have an "inside fill" color and a "stroke/border" around it. I'm guessing the line element can't handle this, so what should I use instead?
Notice that the line-endcap of the line's stroke is rounded. I'd like to maintain this effect in the solution.
<svg height="160" version="1.1" viewBox="-0.6 -0.6 1.2 1.2" width="160" xmlns="http://www.w3.org/2000/svg">
<g>
<g>
<circle class="sensorShape" cx="0" cy="0" fill="#FFF" r="0.4" stroke="black" stroke-width="0.015"/>
<line stroke="black" stroke-width="0.015" x1="0" x2="0" y1="-0.4" y2="0.4"/>
<line stroke="black" stroke-width="0.015" x1="-0.4" x2="0.4" y1="0" y2="0"/>
</g>
<g class="lsNorthAngleHandsContainer">
<line data-angle="348" stroke="red" stroke-linecap="round" stroke-width="0.04" transform="rotate(348)" x1="0" x2="0" y1="0" y2="-0.4"/>
<text font-size="0.08" transform="translate(-0.02316467632710395,-0.45125904029352226)">
348
</text>
</g>
</g>
</svg>
Add a second line, with same coordinates but thinner line width:
<g class="lsNorthAngleHandsContainer">
<line data-angle="348" stroke="red" stroke-linecap="round" stroke-width="0.04" transform="rotate(348)" x1="0" x2="0" y1="0" y2="-0.4"/>
<line data-angle="348" stroke="yellow" stroke-linecap="round" stroke-width="0.025" transform="rotate(348)" x1="0" x2="0" y1="0" y2="-0.4"/>
I found elegant solution inspired by illustration to W3C article about filling and stroking. Basically, you move path to definitions and use this definition to draw two elements:
<svg width="200" height="200" viewBox="0 0 100 100">
<defs>
<line id="line1" x1="25" x2="75" y1="25" y2="75"/>
</defs>
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line1" class="stroke"></use>
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line1" class="line"></use>
</svg>
By using <defs> and <use> you can change only path element to update both lines. JSFiddle demo
It looks like your line is opaque, so you can just draw a thin line with the "inside" color on top of the thicker line with the "outside" color.
You could use a rect with rounded corners, but the math changes a bit:
<rect data-angle="348" stroke="red" stroke-linecap="round" stroke-width="0.02" fill="#FF0" transform="rotate(348, 0, 0)" x="-0.02" y="-0.4" width=".06" height=".4" rx=".03" ry=".03"/>
http://jsfiddle.net/RNAuP/
You can also do it with a path, even though it's tricky around the round bits:
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!-- I often use entities to be able to change lot of numbers at once in static SVG, also kind of makes the paths more readable.
Obvisouly, if you're generating the path you can use the same variables in code to append to d -->
<!ENTITY handFill "0.01">
<!ENTITY handFill2 "0.02"><!-- Should be 2 * handFill to be centered -->
<!ENTITY handStroke "0.005"><!-- Should be less than handFill2 to not hide fill -->
]>
<svg height="160" version="1.1" viewBox="-0.6 -0.6 1.2 1.2" width="160" xmlns="http://www.w3.org/2000/svg">
<g>
<g>
<circle class="sensorShape" cx="0" cy="0" fill="#FFF" r="0.4" stroke="black" stroke-width="0.015"/>
<line stroke="black" stroke-width="0.015" x1="0" x2="0" y1="-0.4" y2="0.4"/>
<line stroke="black" stroke-width="0.015" x1="-0.4" x2="0.4" y1="0" y2="0"/>
</g>
<g class="lsNorthAngleHandsContainer">
<path d="
M -&handFill;,0 l0,-0.4
a &handFill;,&handFill; 0 0,1 &handFill2;,0
l 0,0.4
a &handFill;,&handFill; 0 0,1 -&handFill2;,0
" stroke="red" stroke-linecap="round" stroke-width="&handStroke;" fill="yellow" transform="rotate(348)" />
<text font-size="0.08" transform="translate(-0.02316467632710395,-0.45125904029352226)">
348
</text>
</g>
</g>
</svg>

Resources