SVG Text and Rect position - svg

I have this code:
<g pointer-events="default">
<g>
<g x="0" y="0" width="145" height="47" regroup="false">
<text x="0" y="0">
<tspan x="0" y="0">some text</tspan>
</text>
</g>
<g x="0" y="0" width="80" height="60" regroup="false">
<rect x="0" y="0" width="80" height="60" fill="white" />
</g>
</g>
</g>
There is one css rule box-sizing: border-box;which is apply to all elements.
It produce that:
The grid is coming from a sliding element from the first <g>.
I don't understand why the text element and the rect are not displayed at the same y.
Does anyone have any idea?

Related

why does <g> not work in <clipPath> in svg?

The following code does not work:
<svg>
<defs>
<clipPath id="test">
<g>
<rect x="0" y="0" width="10" height="10"></rect>
</g>
</clipPath>
</defs>
<rect x="0" y="0" width="100" height="100" clip-path="url(#test)"></rect>
</svg>
But this does work:
<svg>
<defs>
<clipPath id="test">
<rect x="0" y="0" width="10" height="10"></rect>
</clipPath>
</defs>
<rect x="0" y="0" width="100" height="100" clip-path="url(#test)"></rect>
</svg>
In my project, I have some paths in the group tag and I want to reuse the group as a clipPath target and show the path at the same time. For example:
<svg>
<defs>
<g id="group">
<path d="..."></path>
<path d="..."></path>
<path d="..."></path>
<path d="..."></path>
</g>
<clipPath id="test">
<use xlink:href="#group" fill="#000"></use>
</clipPath>
</defs>
<!-- show the stroke of the group -->
<use xlink:href="#group" stroke="#000"></use>
<!-- at same time, clip the rect to make some animation as the background -->
<rect x="0" y="0" width="100" height="100" clip-path="url(#test)"></rect>
</svg>
Because the SVG specification says so
Content model:
Any number of the following elements, in any order:
    descriptive elements
    animation elements
    shape elements
    ‘text’
    ‘use’
Unfortunately <g> elements are not in that list.
Firefox used to support clipping <g> elements a long time ago till we noticed that we weren't a) acting per the specification above and b) compatible with other implementations so we restricted our clipPath implementation to be consistent. So if you got Chrome and Safari on board for a specification change we'd likely be OK with that.
Note that you can work around this by clipping a <use> element that points to a <g> element.

Add inline labels to a horizontal bar with pure SVG

I use the bellow SVG code to build a horizontal bar chart. It is OK, but I need also two inline labels before and after the chart. I know how to add them with HTML and CSS, but I want to solve this only with pure SVG. How to do this?
<svg class="chart" width="300px" height="40">
<g transform="translate(0,0)">
<rect width="82%" height="22" fill="lightskyblue"></rect>
<rect width="100%" height="22" style="fill:none; stroke-width:1; stroke:gray;"></rect>
<text y="30" dx="0.25em" dy=".35em">0</text>
<text x="20%" y="10" dy=".35em" fill="red">|</text>
<text x="20%" y="30" dx="-0.25em" dy=".35em" fill="red">13</text>
<text x="100%" y="30" dx="-1.25em" dy=".35em">63</text>
</g>
</svg>
This is what I have now:
And this is what I want:
In order to make it work I'm using your code as a nested svg inside a larger svg element. Please observe I'm using a viewBox attribute where the x component has a negative value (-50) making space for the text.
svg{border:solid;}
<svg class="chart" width="300px" viewBox="-50 0 400 40">
<text y="20" x="-45">TXT</text>
<text x="345" y="20" text-anchor="end">TXT</text>
<svg viewBox="0 0 300 40" width="300">
<rect width="82%" height="22" fill="lightskyblue"></rect>
<rect width="100%" height="22" style="fill:none; stroke-width:1; stroke:gray;"></rect>
<text y="30" dx="0.25em" dy=".35em">0</text>
<text x="20%" y="10" dy=".35em" fill="red">|</text>
<text x="20%" y="30" dx="-0.25em" dy=".35em" fill="red">13</text>
<text x="100%" y="30" dx="-1.25em" dy=".35em">63</text>
</svg>
</svg>
I must tell you that I wouldn't percentages for the position and size of the rects. I would have done it using user units (without unit identifier) and I wouldn't have needed to wrap it in another svg element.
UPDATE
the OP is commenting
Can you give another example, without percentages for the position and size of the rects and without wrapping it in another svg element
svg{border:solid}
<svg class="chart" width="300px" viewBox="-50 0 400 40">
<text y="20" x="-45">TXT</text>
<text x="345" y="20" text-anchor="end">TXT</text>
<rect width="246" height="22" fill="lightskyblue"></rect>
<rect width="300" height="22" style="fill:none; stroke-width:1; stroke:gray;"></rect>
<text y="30" dx="0.25em" dy=".35em">0</text>
<text x="60" y="10" dy=".35em" fill="red">|</text>
<text x="60" y="30" dx="-0.25em" dy=".35em" fill="red">13</text>
<text x="300" y="30" dx="-1.25em" dy=".35em">63</text>
<svg>

How to mask a portion of a stroked path in SVG?

I'm looking to mask a portion of a straight line in SVG and can really only figure out how to do it one way, but would rather do another because the line lengths will be dynamically generated and the mask portion won't.
Let me explain.
Assume I have a line that is <path d="M0,0 L0,100" stroke="blue" stroke-width="20"/>, I would like to mask with transparency the first 10 pixels, meaning just the d="M0,0 L0,10" portion.
I can do this, which produces the results I'd like:
<svg width="100" height="100">
<rect stroke="black" stroke-width="2" width="100" height="100" fill="yellow"/>
<svg x="10" y="0" width="200" height="200" >
<defs>
<rect x="0" y="0" width="20" height="10" stroke="none"/>
<mask id="chopmask" maskUnits="userSpaceOnUse">
<rect width="20" height="90" x="0" y="10" fill="white"/>
</mask>
</defs>
<path d="M0,0 L0,100" mask="url(#chopmask)" stroke="blue" stroke-width="20"/>
</svg>
</svg>
But the issue is that I can't seem to do the opposite with the rect in the mask, wherein I simply define the it as <rect width="20" height="10" x="0" y="0" fill="white"/> (notice only height and y are different).
Am I missing something on how do define a 10x20 rectangle and have it's mask simply hide a portion of a stroked path, or is this impossible?
If I understood the question correctly, then you need to have a mask in the form of a rectangle of fixed size 10Х20, which will be applied to the line with variable length.
In this case, you can try on a combined mask, one part of which will be opaque fill = "black" and the second part will be transparent fill = "white" and show the rest of the line.
<svg width="100" height="100">
<rect stroke="black" stroke-width="2" width="100" height="100" fill="yellow"/>
<svg x="10" y="0" width="200" height="200" >
<defs>
<mask id="chopmask" maskUnits="userSpaceOnUse">
<rect width="20" height="100" x="0" y="0" fill="white"/>
<rect width="20" height="10" x="0" y="0" fill="black"/>
</mask>
</defs>
<path d="M0,0 L0,100" mask="url(#chopmask)" stroke="blue" stroke-width="20"/>
</svg>
</svg>
An example of animating the line masking process with a rectangle 10 x 20px
<svg width="100" height="100">
<rect stroke="black" stroke-width="2" width="100" height="100" fill="yellow"/>
<svg x="10" y="0" width="200" height="200" >
<defs>
<rect x="0" y="0" width="20" height="10" stroke="none"/>
<mask id="chopmask" maskUnits="userSpaceOnUse">
<rect width="20" height="100" x="0" y="0" fill="white"/>
<rect width="20" height="10" x="0" y="0" fill="black">
<animate attributeName="y" dur="2s" values="-10;0" fill="freeze" />
</rect>
</mask>
</defs>
<path d="M0,0 L0,100" mask="url(#chopmask)" stroke="blue" stroke-width="20"/>
</svg>
</svg>
Works for me ... am I misunderstanding what you're trying to do?
<svg width="100" height="100">
<rect stroke="black" stroke-width="2" width="100" height="100" fill="yellow"/>
<svg x="10" y="0" width="200" height="200" >
<defs>
<rect x="0" y="0" width="20" height="10" stroke="none"/>
<mask id="chopmask" maskUnits="userSpaceOnUse">
<rect width="20" height="10" x="0" y="0" fill="white"/>
</mask>
</defs>
<path d="M0,0 L0,100" mask="url(#chopmask)" stroke="blue" stroke-width="20"/>
</svg>
</svg>

Alter coordinates system by flipping X axis (start at top right corner)

I'm looking for a simple way to make the coordinate system in a given svg element start from the top right corner, instead of the top left. This means the X axis is flipped, thus increasing the x attribute of an element renders it further to the left, and increasing the y attribute renders it further to the bottom as usual.
I've played around with scale and viewBox, however:
scale almost solves the problem, but it doesn't really work for my use case because it also flips the text I've got rendered
viewBox doesn't seem work with height="100%" and width="100%". For my use case I don't think I can hard code the height and width of the SVG because I need it to be usable across many different resolutions and screen sizes.
This question says it solves the same problem for the Y axis with a matrix transformation. I looked around and tried to calculate the equivalent for the X axis, but with no success.
Here is an example of what I'm trying to achieve:
<svg style="border: 1px black solid;" height="100%" width="100%">
<g>
<g>
<rect fill="#F0BC40" width="70" height="12" x="0" y="30"></rect>
<text fill="black" font-size="10px" text-anchor="middle" x="35" y="29">7</text>
</g>
<g>
<rect fill="orange" width="50" height="12" x="72" y="30"></rect>
<text fill="black" font-size="10px" text-anchor="middle" x="97" y="29">5</text>
</g>
<g>
<rect fill="orange" width="40" height="12" x="124" y="30"></rect>
<text fill="black" font-size="10px" text-anchor="middle" x="144" y="29">4</text>
</g>
<g>
<rect fill="red" width="50" height="12" x="166" y="30"></rect>
<text fill="black" font-size="10px" text-anchor="middle" x="191" y="29">5</text>
</g>
<rect fill="#52575E" width="2" height="16" x="70" y="28"></rect>
<rect fill="#52575E" width="2" height="16" x="122" y="28"></rect>
<rect fill="#52575E" width="2" height="16" x="164" y="28"></rect>
</g>
</svg>
As you can see I'd like this stacked bar to be rendered from the right, with the red bar being the furthest to the left (so essentially the stacked bar would be flipped)
Also I'm doing this in Elm, so I can't access the DOM to check widths, heights or coordinates of elements (I'm calculating everything in a functional way).
If anyone could help me achieve this I'd be greatly thankful.
The way I would think about this is drawing your bars from x="0" to the left, and then setting the viewBox with a negative x value and a width that lets it end at x="0".
For the text elements, add a negative sign to the x value. For the rects, set the x value as x -> -x - width.
Define a viewBox such that the lowest x value is still inside, or whatever is appropriate.
<svg style="border: 1px black solid;" height="100%" width="100%" viewBox="-500 0 500 100">
<g>
<g>
<rect fill="#F0BC40" width="70" height="12" x="-70" y="30"></rect>
<text fill="black" font-size="10px" text-anchor="middle" x="-35" y="29">7</text>
</g>
<g>
<rect fill="orange" width="50" height="12" x="-122" y="30"></rect>
<text fill="black" font-size="10px" text-anchor="middle" x="-97" y="29">5</text>
</g>
<g>
<rect fill="orange" width="40" height="12" x="-164" y="30"></rect>
<text fill="black" font-size="10px" text-anchor="middle" x="-144" y="29">4</text>
</g>
<g>
<rect fill="red" width="50" height="12" x="-216" y="30"></rect>
<text fill="black" font-size="10px" text-anchor="middle" x="-191" y="29">5</text>
</g>
<rect fill="#52575E" width="2" height="16" x="-72" y="28"></rect>
<rect fill="#52575E" width="2" height="16" x="-124" y="28"></rect>
<rect fill="#52575E" width="2" height="16" x="-166" y="28"></rect>
</g>
</svg>
This will scale the text and the bars; if you need to avoid that, there is a trick. You can surround the content with two <svg> elements and use the inner one to move everything 100% to the right. overflow="visible" (or style="overflow:visible") makes sure the content is visible although it is formally outside the viewport of the inner <svg>.
<svg style="border: 1px black solid;" height="100%" width="100%">
<svg x="100%" overflow="visible">
<g>
<g>
<rect fill="#F0BC40" width="70" height="12" x="-70" y="30"></rect>
<text fill="black" font-size="10px" text-anchor="middle" x="-35" y="29">7</text>
</g>
<g>
<rect fill="orange" width="50" height="12" x="-122" y="30"></rect>
<text fill="black" font-size="10px" text-anchor="middle" x="-97" y="29">5</text>
</g>
<g>
<rect fill="orange" width="40" height="12" x="-164" y="30"></rect>
<text fill="black" font-size="10px" text-anchor="middle" x="-144" y="29">4</text>
</g>
<g>
<rect fill="red" width="50" height="12" x="-216" y="30"></rect>
<text fill="black" font-size="10px" text-anchor="middle" x="-191" y="29">5</text>
</g>
<rect fill="#52575E" width="2" height="16" x="-72" y="28"></rect>
<rect fill="#52575E" width="2" height="16" x="-124" y="28"></rect>
<rect fill="#52575E" width="2" height="16" x="-166" y="28"></rect>
</g>
</svg>
</svg>
As you said, scale "almost works". You can use scale again to unflip the text. Use nested transforms to get the flipping style working correctly with horizontal text placement. If you want to switch back to the unflipped version just change the -1 in the scale to a 1 (or get rid of the transform in the flipping style).
<head>
<style TYPE="text/css">
<!--
.flipped {
transform: scale(-1,1);
}
-->
</style>
</head>
<svg class=flipped style="border: 1px black solid;" height="100%" width="100%">
<g>
<g>
<rect fill="#F0BC40" width="70" height="12" x="0" y="30"></rect>
<g transform="translate(35,29)">
<g class=flipped >
<text fill="black" font-size="10px" text-anchor="middle" >7</text>
</g>
</g>
</g>
<g>
<rect fill="orange" width="50" height="12" x="72" y="30"></rect>
<g transform="translate(97,29)">
<g class=flipped >
<text fill="black" font-size="10px" text-anchor="middle" >5</text>
</g>
</g>
</g>
<g>
<rect fill="orange" width="40" height="12" x="124" y="30"></rect>
<g transform="translate(144,29)">
<g class=flipped >
<text fill="black" font-size="10px" text-anchor="middle" >4</text>
</g>
</g>
</g>
<g>
<rect fill="red" width="50" height="12" x="166" y="30"></rect>
<g transform="translate(191,29)">
<g class=flipped >
<text fill="black" font-size="10px" text-anchor="middle" >5</text>
</g>
</g>
</g>
<rect fill="#52575E" width="2" height="16" x="70" y="28"></rect>
<rect fill="#52575E" width="2" height="16" x="122" y="28"></rect>
<rect fill="#52575E" width="2" height="16" x="164" y="28"></rect>
</g>
</svg>

Cut off half of the SVG's text element

How can I make this
to look like this
So I want to halve the text element. I don't want to hide half of the text outside of SVG. Hiding it outside of g would be ok, but haven't found solution.
<svg width="500" height="500">
<g transform="translate(50,50)">
<rect width="80" height="50" style="fill:rgb(0,0,255);"/>
<text font-size="40" x="0" y="15" fill="black">SVG</text>
</g>
</svg>
JSFIDDLE:
http://jsfiddle.net/64nkLcdy/
Use the clip-path property :
<svg width="500" height="500">
<defs>
<clipPath id="myClip">
<rect width="80" height="50" />
</clipPath>
</defs>
<g transform="translate(50,50)">
<rect width="80" height="50" style="fill:rgb(0,0,255);" />
<text font-size="40" x="0" y="15" fill="black" clip-path="url(#myClip)">SVG</text>
</g>
</svg>
Use an <svg> element rather than a <g> as the svg element will clip its contents by default. The overflow property controls clipping i.e overflow="visible" doesn't clip but overflow="hidden" does.
<svg width="500" height="500">
<svg transform="translate(50,50)" width="80" height="50" overflow="hidden">
<rect width="80" height="50" style="fill:rgb(0,0,255);"/>
<text font-size="40" x="0" y="15" fill="black">SVG</text>
</svg>
</svg>

Resources