I am working with d3.js library and need to draw some svg elements inside another child svg element. For example, I have a container element in the svg, which is a "rect". I want too draw some lines inside that "rect". But I am having issue viewing those lines.
If I add those line to the main svg container, it works fine. But the lines are not visible when I am adding those to the "rect". I guess, coordinate is the issue.
So, can anyone let me know, how the coordinate should be calculated while drawing inside a child element? Is it automatically offset by it's parent coordinate? or the "rect" will have it's own coordinate system?
<line> is not rendered inside a <rect>.
If you look at https://www.w3.org/TR/SVG/shapes.html#RectElement, only animation and descriptive elements are allowed as contents of <rect>
You might want to restructure your code to use the g element as a container element for grouping together related graphics elements (such as <rect> and <line>).
If you need to position the grouping element, you can use a transform.
<svg width="1000" height="500">
<g transform="translate(10,30)">
<rect x="0" y="0" width="100" height="50" style="fill:black;"></rect>
<line x1="20" y1="0" x2="20" y2="20" stroke="white" stroke-width="2"></line>
<line x1="40" y1="0" x2="40" y2="20" stroke="white" stroke-width="2"></line>
<line x1="60" y1="0" x2="60" y2="20" stroke="white" stroke-width="2"></line>
<line x1="80" y1="0" x2="80" y2="20" stroke="white" stroke-width="2"></line>
</g>
</svg>
https://jsfiddle.net/ksav/wq6mvv9v/
Related
I am studying some basic image manipulations with SVG and trying to find optimal approach for the following challenge:
We have one SVG file which has various SVG elements (circles, rectangle, triangle). They all are overlapping each other creating new "areas" of different forms (see pic).
So filling actual Elements - no problem there. But what if I want to fill with color only specific intersect area?
My current thinking was:
Consider drawing all elements as Paths, then see if I can treat overall composition as One large path and then play with fill-rule.
Consider calculating the area shape and drawing a new shape on top of it, then fill it.
Consider something else?
Michael's filter method is cool and tricky, but perhaps a little hard to understand.
You can also do it with masks.
<svg width="391" height="400">
<defs>
<!-- define the shapes in the image, which we will use for the outlines
and for creating intersection masks -->
<rect id="square" x="92" y="48" width="218" height="218"/>
<polygon id="triangle" points="54,366 277,366 165,142"/>
<circle id="circle" cx="256" cy="264" r="85"/>
<!-- the masks -->
<!-- white parts are visible, black parts are invisible -->
<mask id="square-minus-triangle">
<!-- square with triangle cut out of it -->
<use xlink:href="#square" fill="white"/>
<use xlink:href="#triangle" fill="black"/>
</mask>
<mask id="triangle-minus-square">
<!-- triangle with square cut out of it -->
<use xlink:href="#triangle" fill="white"/>
<use xlink:href="#square" fill="black"/>
</mask>
</defs>
<!-- background -->
<rect width="100%" height="100%" fill="#e5e4da"/>
<!-- the intersection shapes (yellow) -->
<!-- first draw the circle, but use the square-minus-triangle mask.-->
<use xlink:href="#circle" fill="#e4e400" mask="url(#square-minus-triangle)"/>
<!-- draw the circle again, but use the triangle-minus-square mask.-->
<use xlink:href="#circle" fill="#e4e400" mask="url(#triangle-minus-square)"/>
<!-- draw the outlined shapes -->
<g fill="none" stroke="black" stroke-width="6">
<use xlink:href="#square"/>
<use xlink:href="#triangle"/>
<use xlink:href="#circle"/>
</g>
</svg>
You can do this with filters. An easy way to do is to use near transparent fill and then use a filter to dial the non-overlapping areas to fully transparent and the overlapping areas to fully opaque. It makes the stroke a little crispy though.
<svg height="600px" width="800px">
<defs>
<filter id="opacitychange">
<feComponentTransfer>
<feFuncA type="linear" intercept="-.05"/>
</feComponentTransfer>
<feComponentTransfer>
<feFuncA type="gamma" amplitude="4" exponent=".4"/>
</feComponentTransfer>
</filter>
</defs>
<g filter="url(#opacitychange)">
<circle stroke="black" fill="blue" fill-opacity="0.05" cx="150" cy="150" r="100"/>
<rect stroke="black" x="200" y="100" width="100" height="300" fill="blue" fill-opacity="0.05"/>
<polygon stroke="black" points="50,50 50,400 300,400" fill="blue" fill-opacity="0.05"/>
</g>
</svg>
Here's a storyboard of the CSS/JS/SVG animation I'm trying to accomplish. Two triangle masks enter from either side, then intersect resulting in a negative mask:
The point where the triangles intersect is where it gets tricky. When I export the mask for panel 4 to SVG, it looks like this:
<svg width="416px" height="289px" viewBox="0 0 416 289" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path d="M211.503681,65.6626347 L507.009604,-138.787586 L507.009604,425.787586 L211.507182,221.339788 L-84,425.792431 L-84,-138.787586 L211.503681,65.6626347 Z M211.503681,65.6626347 L99,143.5 L211.507182,221.339788 L324.01001,143.502422 L211.503681,65.6626347 Z" id="path-1"></path>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle-1-Copy-3" fill="#F6A623" x="0" y="0" width="416" height="289"></rect>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Combined-Shape" fill="#000000" xlink:href="#path-1"></use>
<rect id="Rectangle-1-Copy-2" fill="#4990E2" mask="url(#mask-2)" x="0" y="0" width="416" height="289"></rect>
</g>
</svg>
It looks like it's basically drawing two shapes, the negative-space diamond mask in the center and the remainder of the outer triangles.
So the static mask itself appears to be possible with SVG, but I don't know how to animate it. Is there a library that can simplify this kind of SVG transition/tweening, or a fancy math equation that can calculate the paths dynamically?
Or am I looking at this the wrong way entirely and there's a much easier way to do it altogether?
So the solution was to make it both more simple AND more complicated.
Instead of two layers on top of each other with one mask, I needed three layers. One on the bottom to show behind the first mask, the second to be masked by the incoming triangles, and a third layer above that, duplicate to the first, where a diamond-shaped mask is applied.
<svg width="500" height="300" viewbox="0 0 500 300">
<defs>
<clipPath id="triangles">
<path id="left" d="M-250,-150 L250,150 L-250,450 Z" fill="black" />
<path id="right" d="M250,150 L750,-150 L750,450 Z" fill="black" />
</clipPath>
<clipPath id="diamond">
<path id="diamond-path" d="M250,0 L500,150 L250,300 L0,150 Z" fill="black" />
</clipPath>
</defs>
<!-- bottom -->
<g id="bottom">
<rect fill="darkorange" x="0" y="0" width="500" height="300" />
<text x="50%" y="65%" text-anchor="middle" class="text">Text</text>
</g>
<!-- middle/triangles -->
<g id="middle" clip-path="url(#triangles)">
<rect fill="dodgerblue" x="0" y="0" width="500" height="300" />
<text x="50%" y="65%" text-anchor="middle" class="text">Text</text>
</g>
<!-- top/diamond -->
<g id="top" clip-path="url(#diamond)">
<rect fill="darkorange" x="0" y="0" width="500" height="300" />
<text x="50%" y="65%" text-anchor="middle" class="text">Text</text>
</g>
</svg>
The top layer with the diamond path starts out scaled to 0, making it invisible. The two triangle clip paths are animated in towards each other, showing the bottom layer underneath. When the two triangle points meet, the diamond clip path on the top layer is scaled up, revealing the top layer which is a duplicate of the bottom.
I also switched to clip paths instead of masks because they're a) better supported and b) allow for multiple paths.
Here's a Codepen using CSS for the animations (only works in WebKit for the moment).
UPDATE: Here's a Codepen using GSAP that works in all browsers: http://s.codepen.io/kgrote/debug/mPxzZY
Is it possible to create a variable gradient with SVG objects? Here is what I am trying to do:
I have a a horizontal bar chart and I want to apply shading to it. So the top most bar would have the most shading, and the bar after that less shading and so on. However, the number of bars is variable.
I don't want to create a gradient for every bar (plus with a variable number of bars thats hard), what I would like to do is use one gradient for each bar. In the selectall statement, can I edit the gradient that is in the def section? Something like:
.attr("stop-color", function(d,i) "rgb("+50*i+","+50*i+","+50*i+")")"?
I think I need to reference the name of the gradient somewhere. Is this even possible?
If you mean you want to have a gradient that applies across a group of objects, then yes. That is possible. You just have to define the gradient with absolute coordinates. To do that, use gradientUnits="userSpaceOnUse".
Below I am defining a gradient that starts at (0,0) and goes to (0,400). All objects that fall betwee y=0 and y=400 will be drawn using an appropriate part of that gradient.
<svg width="400px" height="400px">
<defs>
<linearGradient id="mygradient" gradientUnits="userSpaceOnUse"
x1="0" y1="0" x2="0" y2="400">
<stop offset="0" stop-color="red"/>
<stop offset="1" stop-color="blue"/>
</linearGradient>
</defs>
<g fill="url(#mygradient)" stroke="black" stroke-width="4">
<rect x="5" y="5" width="390" height="90"/>
<rect x="5" y="105" width="390" height="90"/>
<rect x="5" y="205" width="390" height="90"/>
<rect x="5" y="305" width="390" height="90"/>
</g>
</svg>
Graphviz is giving me output that looks basically like this:
<svg>
<g id="graph0" class="graph" transform="scale(0.138865 0.138865) rotate(0) translate(4 4648)">
<title>G</title>
...
<g id="node36" class="node">
<title>SomeTitle</title>
<g id="a_node36">
<a>
<ellipse cx="1225" cy="-3546" rx="85.1304" ry="18">
</ellipse>
<text x="1225" y="-3541.8">
sometext</text>
</a>
</g>
</g>
...
</g>
</svg>
I am no Svg expert, but it seems that since the contents of the nodes are positioned relative to the graph group and not the node group, doing a scale on a node is going to have undesired results. Do I have to walk the graph and "normalize" everything? Is there a switch on Graphviz to do this?
Does the normalization algorithm take the the upper left of the bounding box described by the x and y of the groups elements, and use that as a translate for the group and adjust the members to the new coordinate system?
I am using jQuery Svg, as well as D3 -- do either of those have a normalize function that would help in this situation?
This seems a pain, so I have to ask why is this the reality.
Since the origin of the document is, by default, at the top left, doing a straight scale of the node will also cause it to move.
However that would be true in all but the specific situation in which the node is centred on the origin. Correcting that problem is fairly simple. SVG transforms can be built up from a series of basic transformation operations.
To scale an element around its centre point you have all you have to do is:
translate the centre point to the origin: translate(-1225,3546)
scale: scale(4)
translate it back to its original location: translate(1225,-3546)
<svg width="400" height="400">
<g id="graph0" class="graph" transform="scale(0.138865 0.138865) rotate(0) translate(4 4648)">
<title>G</title>
<g id="node36" class="node">
<title>SomeTitle</title>
<g id="a_node36" transform="translate(1225,-3546) scale(4) translate(-1225,3546)">
<a>
<ellipse cx="1225" cy="-3546" rx="85.1304" ry="18" fill="blue">
</ellipse>
<text x="1225" y="-3541.8" fill="white">
sometext</text>
</a>
</g>
</g>
<g id="node36" class="node">
<title>SomeTitle</title>
<g id="a_node36_orig">
<a>
<ellipse cx="1225" cy="-3546" rx="85.1304" ry="18" fill="red">
</ellipse>
<text x="1225" y="-3541.8" fill="white">
sometext</text>
</a>
</g>
</g>
</g>
</svg>
Note that, due to the way transforms work, the operations are listed in reverse order in the transform attribute.
I have the following svg:
<svg
width="1750"
height="1125"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<g id="svg_4">
<g id="imgG_4">
<image
transform="rotate(35.3608 608.333 503.301)"
xlink:href="https://www.google.com/images/srpr/logo4w.png"
id="img_4"
height="188.79927"
width="188.79927"
y="408.90001"
x="706.21582"/>
</g>
<rect
transform="rotate(35.3608 783.333 667.587)"
id="border_4"
height="264.31644"
width="360.92146"
y="535.42838"
x="602.87256"
fill-opacity="0"
stroke-width="5"
stroke="#000000"
fill="#000000"/>
</g>
</g>
</svg>
I'd like to change the angle of the both the rect and image. I cannot however, figure out how to adjust the image such that its spacing within the rect is consistent as i rotate the rect.
E.g. After rotating both the rect and the image the image has the same amount of whitespace above and to the left as it did prior to rotation of both elements.
I cannot put the rotation on the container groups due to other technical restraints.
I'd like to end up with something like:
<svg
width="1750"
height="1125"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<g
id="svg_4">
<g
id="imgG_4">
<image
transform="rotate(70.3608 608.333 503.301)"
xlink:href="https://www.google.com/images/srpr/logo4w.png"
id="img_4"
height="188.79927"
width="188.79927"
y="408.90001"
x="706.21582"
/>
</g>
<rect
transform="rotate(70.3608 783.333 667.587)"
id="border_4"
height="264.31644"
width="360.92146"
y="535.42838"
x="602.87256"
fill-opacity="0"
stroke-width="5"
stroke="#000000"
fill="#000000"
/>
</g>
</g>
</svg>
The caveat is that the x,y values have to change on the image in order to get the layout correct and I have know idea how to calculate them.
Any idea on how I would go about this? I will be using javascript to do the math involved...
Plnkr is here
Assuming you wanted to rotate both elements around the same rotation point as the rectangle (ie. 783.333,667.587), then all you need to is apply the additional rotation to the front of both element transforms. So:
<image transform="rotate(90 783.333 667.587) rotate(70.3608 608.333 503.301)"
<rect transform="rotate(90 783.333 667.587) rotate(70.3608 783.333 667.587)"
would rotate both elements an additional 90deg round the above centre of rotation.
However you said you want to do the maths yourself in Javascript. So to get you started, it might help to know that the transform:
rotate(r x y)
is equivalent to:
translate(x y) rotate(r) translate(-x -y)