Multiples transformations matrix on SVG, get cursor point - javascript

This question is probably more related to math than svg itself.
Inside a main svg, got multiples transformed svg, (from different events), by a viewBox attribute.
Inside those svg, others elements are grouped in a g, modified using matrix transformations.
How to obtain the points on the elements transformed from the mouse pointer.
The goal could be to draw a point or obtain the related point, as a graph chart.
let point = document.getElementById("main").createSVGPoint();
// The mouse cursor points
point.x = 180
point.y = 63
mouse = point.matrixTransform(sub.getCTM())
// console.log(mouse)
// Output
// "x": 611.75439453125,
// "y": 68.71578979492188
// Testing:
circle.setAttribute("cx", 611.75439453125)
circle.setAttribute("cy", 68.71578979492188)
// Not working
<!-- Parent svg -->
<!-- Not preserving aspect ratios -->
<svg id="main" viewBox="0 0 300 400">
<!-- Includes others svg, transformed with a viewBox -->
<!-- Not preserving aspect ratios -->
<svg id="group1" viewBox="7 54 10 570">
<!-- Group element modified with a matrix -->
<!-- Using css to define the matrix behave identicaly -->
<!-- All elements in this group are transformed by this matrix -->
<g id="sub" transform="matrix(4.5,0,0,0.84,-140,99)">
<!-- Exemple element in group -->
<polyline points="4 65.94338623003772 5 78 6 50.10565885410098 7 40.95007190251531 8 53.698867220021675 9 49.43265131064406 10 44.36112722960851 11 56.329540580770356 12 49.785452985846554 13 44.10803037565898 14 40.537830814642945 15 41.84933269419995 16 38.33857254585345 17 43.590332265307744 18 49.16421525342487 19 49.49017332290519 20 42.51658803643061 21 46.943865580139814 22 36.27544970608283 23 38.070136488634255 24 43.46186643792423 25 42.20788657062835 26 48.37424628503659 27 25.58210762671243 28 23.927391073996347 29 22.349370537628886 30 30.592274894669004 31 21.97356005752208 32 24.960869894290738 33 23.221787723591348 34 17.41781668642936 35 2 36 19.335217095138404 37 39.60405681560149 38 38.49579937936788 39 32.47132729520408 40 25.016474506143126 41 27.037414536922626 42 27.541690844412955 43 20.37253071624997 44 9.872846078159213 45 17.79362716653617 46 13.107500567651172 47 24.955117693064494 48 24.247596942250766 49 19.728284178923616 50 11.341574791230315 51 8.248807931982782 52 10.697328253903962 " ></polyline>
<!-- This circle should be at the cursor -->
<circle id="circle" cx="50" cy="50" r="50"fill="blue">
</g>
</svg>
<!-- Rectangles symbolizing the mouse cursor -->
<rect width="1000" height="1" x="0" y="63" ></rect>
<rect width="1" height="500" x="180" y="0"></rect>
</svg>
Svg has numerous bindings related to transformations, we can retrieve the transformation matrix of each elements with getCTM() and getBBox(), and use matrixTransform.
This works for one level of transformation?
How to chain multiples matrix transformations?

If you want the point relative to the transformed area, then it should get reflected as the offsetX and offsetY properties of their corresponding MouseEvents.
However, there seems to be a bug in Webkit / Blink browsers with this regard, so this actually only works in Firefox (and maybe IE?)...
const poly = document.querySelector('polyline');
poly.addEventListener('mousemove', evt => {
circle.setAttribute("cx", evt.offsetX + 2.5);
circle.setAttribute("cy", evt.offsetY + 2.5);
});
<!-- Parent svg -->
<!-- Not preserving aspect ratios -->
<svg id="main" viewBox="0 0 300 400">
<!-- Includes others svg, transformed with a viewBox -->
<!-- Not preserving aspect ratios -->
<svg id="group1" viewBox="7 54 10 570">
<!-- Group element modified with a matrix -->
<!-- Using css to define the matrix behave identicaly -->
<!-- All elements in this group are transformed by this matrix -->
<g id="sub" transform="matrix(4.5,0,0,0.84,-140,99)">
<!-- Exemple element in group -->
<polyline points="4 65.94338623003772 5 78 6 50.10565885410098 7 40.95007190251531 8 53.698867220021675 9 49.43265131064406 10 44.36112722960851 11 56.329540580770356 12 49.785452985846554 13 44.10803037565898 14 40.537830814642945 15 41.84933269419995 16 38.33857254585345 17 43.590332265307744 18 49.16421525342487 19 49.49017332290519 20 42.51658803643061 21 46.943865580139814 22 36.27544970608283 23 38.070136488634255 24 43.46186643792423 25 42.20788657062835 26 48.37424628503659 27 25.58210762671243 28 23.927391073996347 29 22.349370537628886 30 30.592274894669004 31 21.97356005752208 32 24.960869894290738 33 23.221787723591348 34 17.41781668642936 35 2 36 19.335217095138404 37 39.60405681560149 38 38.49579937936788 39 32.47132729520408 40 25.016474506143126 41 27.037414536922626 42 27.541690844412955 43 20.37253071624997 44 9.872846078159213 45 17.79362716653617 46 13.107500567651172 47 24.955117693064494 48 24.247596942250766 49 19.728284178923616 50 11.341574791230315 51 8.248807931982782 52 10.697328253903962 " ></polyline>
<!-- This circle should be at the cursor -->
<circle id="circle" cx="5" cy="5" r="5" fill="blue" pointer-events="none">
</g>
</svg>
<!-- Rectangles symbolizing the mouse cursor -->
<rect width="1000" height="1" x="0" y="63" ></rect>
<rect width="1" height="500" x="180" y="0"></rect>
</svg>
If you wish to transform arbitrary values, then you use the technique described in this Answer which consists in dispatching such a MouseEvent on your element:
const point = {x:180, y:63};
const poly = document.querySelector('polyline');
poly.addEventListener('mousemove', evt => {
point.x = evt.offsetX;
point.y = evt.offsetY;
}, {once: true});
const evt = new MouseEvent('mousemove', {
clientX: point.x,
clientY: point.y
});
poly.dispatchEvent(evt);
console.log(point);
circle.setAttribute("cx", point.x);
circle.setAttribute("cy", point.y);
<!-- Parent svg -->
<!-- Not preserving aspect ratios -->
<svg id="main" viewBox="0 0 300 400">
<!-- Includes others svg, transformed with a viewBox -->
<!-- Not preserving aspect ratios -->
<svg id="group1" viewBox="7 54 10 570">
<!-- Group element modified with a matrix -->
<!-- Using css to define the matrix behave identicaly -->
<!-- All elements in this group are transformed by this matrix -->
<g id="sub" transform="matrix(4.5,0,0,0.84,-140,99)">
<!-- Exemple element in group -->
<polyline points="4 65.94338623003772 5 78 6 50.10565885410098 7 40.95007190251531 8 53.698867220021675 9 49.43265131064406 10 44.36112722960851 11 56.329540580770356 12 49.785452985846554 13 44.10803037565898 14 40.537830814642945 15 41.84933269419995 16 38.33857254585345 17 43.590332265307744 18 49.16421525342487 19 49.49017332290519 20 42.51658803643061 21 46.943865580139814 22 36.27544970608283 23 38.070136488634255 24 43.46186643792423 25 42.20788657062835 26 48.37424628503659 27 25.58210762671243 28 23.927391073996347 29 22.349370537628886 30 30.592274894669004 31 21.97356005752208 32 24.960869894290738 33 23.221787723591348 34 17.41781668642936 35 2 36 19.335217095138404 37 39.60405681560149 38 38.49579937936788 39 32.47132729520408 40 25.016474506143126 41 27.037414536922626 42 27.541690844412955 43 20.37253071624997 44 9.872846078159213 45 17.79362716653617 46 13.107500567651172 47 24.955117693064494 48 24.247596942250766 49 19.728284178923616 50 11.341574791230315 51 8.248807931982782 52 10.697328253903962 " ></polyline>
<!-- This circle should be at the cursor -->
<circle id="circle" cx="5" cy="5" r="5" fill="blue" pointer-events="none">
</g>
</svg>
<!-- Rectangles symbolizing the mouse cursor -->
<rect width="1000" height="1" x="0" y="63" ></rect>
<rect width="1" height="500" x="180" y="0"></rect>
</svg>
Note that WebKit / Blink do set it correctly on HTML elements as demonstrated in this Q/A...

The problem is that getCTM() only returns the transformation up to the nearest enclosing <svg> element. Instead you can use getScreenCTM() to calculate the transformation relative to the window. Except we actually need to take the inverse of this transformation so it will be subtracted it from the mouse point. Then we make sure everything is relative to the top-level svg container.
This will be accurate no matter how many layers of transformations the target element is nested under.
<svg id="main" viewBox="0 0 300 400">
<svg id="group1" viewBox="7 54 10 570">
<g id="sub" transform="matrix(4.5,0,0,0.84,-140,99)">
<polyline points="4 65.94338623003772 5 78 6 50.10565885410098 7 40.95007190251531 8 53.698867220021675 9 49.43265131064406 10 44.36112722960851 11 56.329540580770356 12 49.785452985846554 13 44.10803037565898 14 40.537830814642945 15 41.84933269419995 16 38.33857254585345 17 43.590332265307744 18 49.16421525342487 19 49.49017332290519 20 42.51658803643061 21 46.943865580139814 22 36.27544970608283 23 38.070136488634255 24 43.46186643792423 25 42.20788657062835 26 48.37424628503659 27 25.58210762671243 28 23.927391073996347 29 22.349370537628886 30 30.592274894669004 31 21.97356005752208 32 24.960869894290738 33 23.221787723591348 34 17.41781668642936 35 2 36 19.335217095138404 37 39.60405681560149 38 38.49579937936788 39 32.47132729520408 40 25.016474506143126 41 27.037414536922626 42 27.541690844412955 43 20.37253071624997 44 9.872846078159213 45 17.79362716653617 46 13.107500567651172 47 24.955117693064494 48 24.247596942250766 49 19.728284178923616 50 11.341574791230315 51 8.248807931982782 52 10.697328253903962 " />
<circle id="circle" cx="50" cy="50" r="50" fill="blue" />
</g>
</svg>
<rect width="1000" height="1" x="0" y="63" />
<rect width="1" height="500" x="180" y="0" />
<script>
let sub = document.getElementById('sub');
let circle = document.getElementById('circle');
let main = document.getElementById("main");
let mouse = main.createSVGPoint();
mouse.x = 180; mouse.y = 63;
function getMatrix(el) {
let matrix = el.getScreenCTM().inverse();
matrix = matrix.multiply(main.getScreenCTM());
return matrix;
}
let result = mouse.matrixTransform(getMatrix(sub));
circle.setAttribute("cx", result.x);
circle.setAttribute("cy", result.y);
</script>
</svg>

Related

Make animate execute again after svg path changes

I want to animate an svg path such that the animation only runs once, however if the d values change then animation should run again once. I'm having trouble having the animation run every time the path values change, if I set the repeatCount value to 1 it won't run again after the svg values change and the svg "rerenders". I included some code below that replicates what I'm trying to do, any help is appreciated. Here is a code demo with just the animation, no react: https://codepen.io/shak8/pen/RwLLjNm
const SVGAnimate = () =>{
const [width, setWidth] = useState(213)
const [prevWidth, setPrevWidth] = useState(213)
return (
<div>
<svg width="400" height="500">
<path style="stroke-width:3;stroke:rgb(0,0,0)"
fill="black"
d=`M 0 212 S 226 212, ${width} 20 V 212 H 0`>
<animate
attributeName="d"
dur="5s"
from=`M 0 212 S 226 212,${prevWidth} 20 V 212 H 0`
to=`M 0 212 S 226 212, ${width} 20 V 212 H 0`
repeatCount="1"
fill="black"
/>
</path>
<button onClick={() => {
// These should trigger svg path to change, desired
// effect is for path to change with animation.
setPrevWidth(width == width)
setWidth(width == 213 ? 320 : 213)
}
}> Change</button>
</div>
)
}
I've also tried using transition on the path, which works perfectly on chrome but doesn't work on safari.
You can use onClick="animation.beginElement()"
As an observation: you have a fill="black" attribute for the animation. I suppose you meant it to be fill="freeze". This would freeze the animation similar to animation-fill-mode: forwards.
<div>
<svg width="400" height="200">
<path style="stroke-width:3;stroke:rgb(0,0,0)"
fill="black"
d="M 0 212 S 226 212, 100 20 V 212 H 0">
<animate id="animation"
attributeName="d"
dur="5s"
from="M 0 212 S 226 212,100 20 V 212 H 0"
to="M 0 212 S 226 212, 300 20 V 212 H 0"
repeatCount="1"
fill="freeze"
/>
</path>
</svg>
<button onClick="animation.beginElement()"> Change</button>
</div>

Svg button path animation

I have this SVG button that I'm trying to animate on hover . I want the button to have a blob effect on the hover. I hope you guys can help me out. Here is the link to the SVG
https://codepen.io/haroldhall/pen/ZEJWJqo
<svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px" viewBox="0 0 169.5451 69.2823">
<defs>
<style>
.fbfb67fb-85bf-41e7-ba32-d1082da05d29 {
fill: #ff83b5;
}
</style>
</defs>
<g id="ee4ac112-3396-4e4b-9bd2-9cf074846425" data-name="Layer 2">
<g id="b0f8822e-1e1e-4dce-a2e6-9a8bebf20196" data-name="Background">
<path id="original" class="fbfb67fb-85bf-41e7-ba32-d1082da05d29" d="M76.9857,59.1319C52.7693,55.9614,1.16,89.3257.01,40.4447-.7354,8.7354,38.7113-11.033,76.1673,6.64c33.8056,15.9511,84.1836-17.2117,85.0374,23.6112C162.2335,79.4411,111.2431,65.3513,76.9857,59.1319Z" />
</g>
</g>
</svg>
CSS rule d: path (" M76 .....); not currently supported by the W3C SVG spec.
This is so far only an experimental technology of browsers based on the Blink engine.
Therefore, a solution based on rule d: path` will not be cross-browser, for example Firefox it does not work.
Consider a solution with SVG SMIL.
SMIL support
To morph the contours of the button, you need to create a final path in the vector editor in the form of a drop
The figures below show the process of getting the final path in the vector editor.
Grab the anchor point and drag it down until you get the desired shape.
Save the svg file and copy the final path to the morph animation command.
Below is the code for animating the morphing of the button outline into a drop shape:
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" width="50px" height="50px" viewBox="0 0 169.5451 69.2823">
<defs>
<style>
.fbfb67fb-85bf-41e7-ba32-d1082da05d29 {
fill: #ff83b5;
}
</style>
</defs>
<g id="ee4ac112-3396-4e4b-9bd2-9cf074846425" data-name="Layer 2">
<g id="b0f8822e-1e1e-4dce-a2e6-9a8bebf20196" data-name="Background">
<path id="original" class="fbfb67fb-85bf-41e7-ba32-d1082da05d29" d="M77 59C53 56 1 89 0 40-1 9 39-11 76 7c34 16 84-18 85 23 1 49-50 35-84 29Z" >
<animate
attributeName="d"
begin="original.mouseover"
dur="1s"
fill="freeze"
restart="WhenNotActive"
values="
M77 59C53 56 1 89 0 40-1 9 39-11 76 7c34 16 84-18 85 23 1 49-50 35-84 29Z;
M81 113C57 110 1 89 0 40-1 9 39-11 76 7c34 16 84-18 85 23 1 49-45 89-80 83z"
/>
</path>
</g>
</g>
</svg>
not sure what kind of blob effect you're looking to have, but you can do this either with JS or CSS. Both involve changing the path to the new desired positions. I am including an example that changes a bit on hover. If you need help creating a blob I recommend https://www.blobmaker.app/
svg #original {
transition: all 0.5s;
}
svg:hover #original {
d: path("M76.9857,59.1319C52.7693,55.9614,1.16,189.3257.01,40.4447-.7354,8.7354,38.7113-11.033,76.1673,6.64c33.8056,15.9511,84.1836-17.2117,85.0374,23.6112C162.2335,179.4411,11.2431,65.3513,76.9857,59.1319Z");
}
<svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px" viewBox="0 0 169.5451 69.2823"><defs><style>.fbfb67fb-85bf-41e7-ba32-d1082da05d29{fill:#ff83b5;}</style></defs><g id="ee4ac112-3396-4e4b-9bd2-9cf074846425" data-name="Layer 2"><g id="b0f8822e-1e1e-4dce-a2e6-9a8bebf20196" data-name="Background"><path id="original" class="fbfb67fb-85bf-41e7-ba32-d1082da05d29" d="M76.9857,59.1319C52.7693,55.9614,1.16,89.3257.01,40.4447-.7354,8.7354,38.7113-11.033,76.1673,6.64c33.8056,15.9511,84.1836-17.2117,85.0374,23.6112C162.2335,79.4411,111.2431,65.3513,76.9857,59.1319Z"/>
</g></g></svg>

How to align a group (instead of text) along a path in svg?

Lets assume I have programmatically created a circle and some text,
that I want to align along the circle.
I am able to do so using the textPath element.
In addition I have a rectangular image (or any svg group <g>),
that I also would like to align to the circle
(the red rectangle in the image below is just an example; actually I would like to be able to align arbitrary svg groups <g> as image labels on the nodes of a chord diagram.).
However, textPath only seems to work for text elements.
=> Is there something similar that works for group elements?
Or do I need to manually calculate the transformation for my group
(following from some tangent of the circle)?
(A workaround could be to create some hidden text with a single letter and
the same size as my group ... align it and grab its transformation. However, that feels ugly.)
In addition I have a rectangular image (or any svg group ), that I
also would like to align to the circle (see red rectangle in the
example image below).
However, textPath only seems to work for text elements.
You can use the rectangle unicode character ▮
In this case you will be able to include it in one textPath command along with other words
<svg width="400" height="400" viewBox="0 0 400 400">
<path id="pathChain" d="M87 199C87 114 141 64 200 64 258 64 310 122 313 199 315 273 258 335 200 335 141 335 87 279 87 199Z" style="fill:#089421;"/>
<text font-size="36" font-family="Times New Roman" fill="grey" >
<textPath id="result" startOffset="7%" xlink:href="#pathChain">
<tspan dx="0" dy="-5" fill="black" font-size="48px"> Hello World </tspan> <tspan dx="-20" dy="-10" fill="red" font-size="72px"> ▮</tspan>
</textPath>
</text>
</svg>
You can use any unicode character that suits you
<svg width="400" height="400" viewBox="0 0 400 400">
<path id="pathChain" d="M87 199C87 114 141 64 200 64 258 64 310 122 313 199 315 273 258 335 200 335 141 335 87 279 87 199Z" style="fill:#089421;"/>
<text font-size="36" font-family="Times New Roman" fill="grey" >
<textPath id="result" startOffset="7%" xlink:href="#pathChain">
<tspan dx="0" dy="-5" fill="black" font-size="48px"> Hello World </tspan> <tspan dx="-5" fill="red" font-size="72px"> ⮔</tspan>
</textPath>
</text>
</svg>
If necessary, you can make the animation of the letters
<svg width="400" height="400" viewBox="0 0 400 400">
<path id="pathChain" d="M87 199C87 114 141 64 200 64 258 64 310 122 313 199 315 273 258 335 200 335 141 335 87 279 87 199Z" style="fill:#089421;"/>
<text font-size="36" font-family="Times New Roman" fill="grey" >
<textPath id="result" xlink:href="#pathChain">
<tspan dx="0" dy="-5" fill="black" font-size="48px"> Hello World </tspan> <tspan dx="-5" fill="red" font-size="72px"> ⮔</tspan>
<animate dur="10s" repeatCount="5" attributeName="startOffset" values="5%;50%;50%;5%;5%"/>
</textPath>
</text>
</svg>
As #Paul LeBeau commented:
No there is no automatic way to do that. You have to position it
yourself
Consider adding svg images to text using absolute positioning
Since any text in SVG is a vector object, it has absolute coordinates x, y, as the first character of the word and the last.
Using this you can position an icon or any other vector image to the beginning or end of the text.
I put the icon in the <symbol> tag and position it at the end of the word using the <use> tag
<use xlink:href="#speaker" x="245" y="35" />
<svg width="400" height="400" viewBox="0 0 400 400">
<symbol>
<g id="speaker" style="transform-origin:center;transform-box: fill-box;transform:rotate(15deg);" >
<path fill="#089421" d="M28,7.1v2c7.3,1,13,7.3,13,14.9s-5.7,13.9-13,14.9v2c8.4-1,15-8.2,15-16.9S36.4,8.1,28,7.1z"/>
<path fill="#546E7A" d="M14,32H7c-1.1,0-2-0.9-2-2V18c0-1.1,0.9-2,2-2h7V32z"/>
<polygon fill="#78909C" points="26,42 14,32 14,16 26,6"/>
<path fill="#089421" d="M28,17.3v2.1c1.8,0.8,3,2.5,3,4.6s-1.2,3.8-3,4.6v2.1c2.9-0.9,5-3.5,5-6.7S30.9,18.2,28,17.3z"/>
<path fill="#089421" d="M28,12.2v2c4.6,0.9,8,5,8,9.8s-3.4,8.9-8,9.8v2c5.7-1,10-5.9,10-11.8S33.7,13.1,28,12.2z"/>
</g>
</symbol>
<path id="pathChain" d="M87 199C87 114 141 64 200 64 258 64 310 122 313 199 315 273 258 335 200 335 141 335 87 279 87 199Z" style="fill:#089421;"/>
<text font-size="36" font-family="Times New Roman" fill="grey" >
<textPath id="result" xlink:href="#pathChain">
<tspan dx="0" dy="-5" fill="black" font-size="48px"> Hello Wordl </tspan>
</textPath>
</text>
<use xlink:href="#speaker" x="245" y="35" />
</svg>
An example with a growing line on which text and an icon are located
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400" viewBox="0 0 400 400" version="1">
<symbol id="grow">
<g>
<circle fill="#FF9800" cx="28" cy="9" r="5"/>
</g>
<path fill="#00796B" d="M29,27.3l-9.2-4.1c-1-0.5-1.5,1-2,2c-0.5,1-4.1,7.2-3.8,8.3c0.3,0.9,1.1,1.4,1.9,1.4c0.2,0,0.4,0,0.6-0.1 L28.8,31c0.8-0.2,1.4-1,1.4-1.8C30.2,28.4,29.7,27.6,29,27.3z"/>
<path fill="#009688" d="M26.8,15.2l-2.2-1c-1.3-0.6-2.9,0-3.5,1.3L9.2,41.1c-0.5,1,0,2.2,1,2.7c0.3,0.1,0.6,0.2,0.9,0.2 c0.8,0,1.5-0.4,1.8-1.1c0,0,9.6-13.3,10.4-14.9s4.9-9.3,4.9-9.3C28.7,17.4,28.2,15.8,26.8,15.2z"/>
<path fill="#FF9800" d="M40.5,15.7c-0.7-0.8-2-1-2.8-0.3l-5,4.2l-6.4-3.5c-1.1-0.6-2.6-0.4-3.3,0.9c-0.8,1.3-0.4,2.9,0.8,3.4 l8.3,3.4c0.3,0.1,0.6,0.2,0.9,0.2c0.5,0,0.9-0.2,1.3-0.5l6-5C41.1,17.8,41.2,16.6,40.5,15.7z"/>
<path fill="#FF9800" d="M11.7,23.1l3.4-5.1l4.6,0.6l1.5-3.1c0.4-0.9,1.2-1.4,2.1-1.5c-0.1,0-0.2,0-0.2,0h-9c-0.7,0-1.3,0.3-1.7,0.9 l-4,6c-0.6,0.9-0.4,2.2,0.6,2.8C9.2,23.9,9.6,24,10,24C10.6,24,11.3,23.7,11.7,23.1z"/>
</symbol>
<path id="txtPath" d="m22 366c0 0 59-24 74-50C132 253 129 213 128 161 125 33 200 2 200 2" style="fill:none;stroke:#00796B;stroke-width:3"/>
<use xlink:href="#grow" x="123" y="10" />
<text dx="0" dy="-10px" font-size="20" font-family="Times New Roman" fill="#414141" >
<textPath id="result" startOffset="5%" xlink:href="#txtPath"> Stock growth in the first half of the year </textPath>
</text>
</svg>
One more example
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400" viewBox="0 0 400 400" version="1">
<symbol id="Cactus" style="transform-origin:center;transform-box: fill-box;transform:rotate(15deg);">
<path fill="#7CB342" d="M35.3,9.6c-1.6-0.2-1.3,0.9-1.9,2.7l-1,4.3c-0.3,1-0.7,1.4-2,1.3L25,17.3c-1.6-0.2-3.1,1-3.2,2.6 c-0.2,1.6,1,3.1,2.6,3.2l6.3,0.7c3,0.3,5.5-0.9,6-5.4c0,0,0.6-4.7,0.3-6.6C36.7,10.2,36.6,9.7,35.3,9.6z"/>
<path fill="#7CB342" d="M12.2,13.6c1.6-0.1,1.3,0.9,1.9,2.8l1.3,6.3c0.3,1,0.7,1.4,2,1.3l5.4-0.4c1.6-0.1,3,1.1,3.2,2.7 c0.1,1.6-1.1,3-2.7,3.2l-6.3,0.5c-3,0.2-5.5-1-5.9-5.5c0,0-0.8-6.7-0.5-8.5C10.7,14.2,10.8,13.7,12.2,13.6z"/>
<path fill="#8BC34A" d="M24,5c-2.4,0-4,0.6-4.5,3.2c0,0-2.1,16.9,0,32.1c0.3,3.2,2,1.2,4.4,1.2s3.8,2.7,4.4-0.8 c2.5-15.1,0-32.6,0-32.6C27.7,5.2,26.4,5,24,5z"/>
<path fill="#FFB74D" d="M38.8,43H9.2c0,0,4.9-2.3,14.8-2.3S38.8,43,38.8,43z"/>
</symbol>
<path id="txtPath" d="m200 2c0 0 36 31 57 42 15 8 34 6 47 16 15 11 26 27 33 43 8 18 2 41 11 58 9 17 41 40 41 40" fill="none" stroke="green" stroke-width="2"/>
<use xlink:href="#Cactus" x="355" y="150" />
<text font-size="20" font-family="Times New Roman" fill="grey" >
<textPath id="result" startOffset="7%" xlink:href="#txtPath">
<tspan dx="0" dy="-5" fill="black" >Decrease in stocks per year </tspan>
</textPath>
</text>
</svg>
As a starting point here is the workaround I mentioned.
It uses some invisible placeholder text and extracts the transformation information with
placeHolder.getBoundingClientRect();
and
placeHolder.getRotationOfChar(0);
The result is only an approximation that depends on the font size and on the character of the placeholder.
var svg = document.getElementById('svg');
var text = document.createElement('text');
text.setAttribute('fill','black');
svg.appendChild(text);
var placeHolder = document.getElementById('placeholder');
var placeHolderBounds = placeHolder.getBoundingClientRect();
var angle = placeHolder.getRotationOfChar(0);
var group = document.getElementById('my-group');
var groupBounds = group.getBoundingClientRect();
var dx = 0; //groupBounds.width/2;
var dy = -groupBounds.height;
var x = placeHolderBounds.x;
var y = placeHolderBounds.y;
var transform = 'translate(' + x +','+ y +') '+
'rotate('+ angle +') '+
'translate('+ dx +','+ dy +')';
group.setAttribute('transform', transform);
<svg id='svg' width="400" height="400" viewBox="0 0 400 400">
<path id="my-path" d="M87 199C87 114 141 64 200 64 258 64 310 122 313 199 315 273 258 335 200 335 141 335 87 279 87 199Z" style="fill:#089421;"/>
<g id="my-group">
<rect width="30" height="20" style="fill:red"/>
</g>
<text font-size="12px" >
<textPath startOffset="10%" xlink:href="#my-path">
<tspan>Hello World</tspan><tspan id='placeholder' visibility="hidden" >-</tspan>
</textPath>
</text>
</svg>

Svg path not closing correctly

I'm trying to create a bridge-like shape with an svg using javascript. With the following html I get the overall shape, but the close path is wrong:
<svg height="897" width="414" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 414 897">
<path stroke-width="3" stroke="blue" style="fill:transparent; fill-opacity: 1"
d="M 0 0 V 207 H 30
M 30 207 q 0 -177 177 -177
M 207 30 q 177 0 177 177
H 414 V 0 H 0 z">
</path>
</svg>
This produces the following shape:
It is closing in a strange way which means that it isn't filling correctly. The strange vertical line from the top of the arch to the top left shouldn't be there. How do I get it to close properly and fill properly?
The path will close from your last point to the last declared M. You can fix this by removing all the M calls except the first one. Since you are making a continuous line you don't need to move to a new point every time.
<svg height="897" width="414" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 414 897">
<path stroke-width="3" stroke="blue" style="fill:transparent; fill-opacity: 1"
d="M 0 0 V 207 H 30
q 0 -177 177 -177
q 177 0 177 177
H 414 V 0 H 0 z">
</path>
</svg>

SVG path arc elements not working

I am trying to draw a donut with an SVG path. I do this by drawing two arcs and a line. The first arc is the outer circle. It works fine until the radius is high. When the outer circle radius decreases, the doughnut does not appear.
<svg height="400" width="400">
<path d="M 200 143 A 57 57 0 1 1 199.99994299999997 143 L 199.9999772 177.2000000000114 A 22.8 22.8 1 1 0 200 177.2 z" fill="transparent" stroke="blue" />
</svg>
The following screen shot shows the original image.
My code is in this jsfiddle.
The problem is that you're only closing the second arc. You should close the first arc before you draw the second arc.
Demonstration:
<svg height="400" width="400">
<path d="M 200 143 A 57 57 0 1 1 199 143 z M 199.9999772 177.2000000000114 A 22.8 22.8 1 1 0 200 177.2 z" fill="transparent" stroke="blue" />
</svg>
I'm not 100% sure what you are intending to draw, but one issue that comes up with SVG and arcs is that when you try to draw a complete circle, you will end up with beginning and ending points coinciding, which for the rendering engine will mean that the path segment is a null op (yeah weird) and will not be rendered. To get around this, use 2 Arcs.
<!DOCTYPE html>
<html>
<body>
<svg height="400" width="400">
<path d="M 200 143 A 57 57 0 1 1 200 257 A 57 57 0 1 1 200 143Z L 200 177 A 22.8 22.8 0 1 0 200 222.6 A 22.8 22.8 0 1 0 200 177 z" fill="transparent" stroke="blue" />
</svg>
</body>
</html>
Also your inner circle had a small (1 degree) tilt (3rd parameter) in it which I assume was unintentional. And if you don't want that vertical line, replace the L with an M.

Categories

Resources