I am trying to design a logo with some text around a circle and I can't get the text to be correctly oriented. I am using plain SVG written by hand without JS. Would you know how to solve this? Here what I have so far:
.full {
fill:none;
stroke:#000000;
stroke-width:0.6px;
}
.letters {
font-size: 4px;
text-align: center;
}
.letters textPath {
/*dominant-baseline: middle;*/
text-anchor: middle;
}
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
preserveAspectRatio="xMidYMid meet" viewBox="0 0 30 30" width="150mm" height="150mm">
<g transform="translate(+0,+25) scale(+1,-1)">
<g transform="translate(+05,+05)">
<path class="full" d="M17.696252,2.152991 A 11 11 0 0 0 -3.554116,2.152991" />
<path class="full" d="M-1.355421,12.070664 A 11 11 0 0 0 3.308846,15.336619" />
<path class="full" d="M12.571068,14.526279 A 11 11 0 0 0 16.597347,10.500000" />
<path id="txt1" fill="none" d="M-3.554116,2.152991 A 11 11 0 0 0 -1.355421,12.070664" />
<path id="txt2" fill="none" d="M3.308846,15.336619 A 11 11 0 0 0 12.571068,14.526279" />
<path id="txt3" fill="none" d="M16.597347,10.500000 A 11 11 0 0 0 17.696252,2.152991" />
<text class="letters"><textPath xlink:href="#txt1" startOffset="50%">txt1</textPath></text>
<text class="letters"><textPath xlink:href="#txt2" startOffset="50%">txt2</textPath></text>
<text class="letters"><textPath xlink:href="#txt3" startOffset="50%">txt3</textPath></text>
</g>
</g>
</svg>
transform="scale(+1,-1)" causes vertical flip below of its descendants; you'll have to either omit that or negate this by adding extra transform="scale(+1,-1)" to each text node and its path.
I faced the same question as Rufus. I had to draw a circle made of different slices and the slices had text on them. I didn't found a solution on the internet, so i had to figure out a way to accomplish this. I did it this way.
The first question was how to draw the slices. I found the excellent work of Daniel Pataki on this matter (https://danielpataki.com/svg-pie-chart-javascript/). I calculated the values as described in the page, for a slices with an angle of 30 degrees. I used the <g> tag to rotate the slices together with the text.
Then I rotated the (with the slices and the text) 12 times (12 x 30 = 360):
<circle cx="350" cy="350" r="300" fill="transparent" />
<? $t = 1; for($t == 1; $t <= 12; $t++) {
$transform = ($t > 1) ? "transform='rotate(" . ($t-1) * 30 . ", 350, 350)'" : "";
$fillcolor = "#912184";
?>
<g <?echo $transform ?> >
<path fill="<?echo $fillcolor ?>" d="M350,350 L350,50 A350,350 1 0,1 500, 90.19238 z"></path>
<text style="font-family: 'Roboto', sans-serif; font-size: 22px" x="350" y="350" fill="#fff" dx="130" transform="rotate(-73, 350, 350)"><?echo $captions[$t-1] ?></text>
</g>
<? } ?>
Now I have 12 same-sized slices filling up the circle. The text (from some array $captions) is placed in the slices, but on the left half of the circle, up-side-down. That's not very readable. I tried to rotate in different ways, but without success. But what did the trick for me, was a) change the rotation angle and b) shift the text more along the path using the dx parameter only for the texts on the left side of the circle (i.e $t > 6):
<circle cx="350" cy="350" r="340" fill="#FA864D" />
<circle cx="350" cy="350" r="300" fill="transparent" />
<? $t = 1; for($t == 1; $t <= 12; $t++) {
$transform = ($t > 1) ? "transform='rotate(" . ($t-1) * 30 . ", 350, 350)'" : "";
$fillcolor = '#912184';}
?>
<g <?echo $transform ?> >
<path fill="<?echo $fillcolor ?>" d="M350,350 L350,50 A350,350 1 0,1 500, 90.19238 z"></path>
<? if ($t <= 6) { ?>
<text style="font-family: 'Roboto', sans-serif; font-size: 22px" x="350" y="350" fill="#fff" dx="130" transform="rotate(-73, 350, 350)"><?echo $captions[$t-1] ?></text>
<? } else { ?>
// combination of shift and rotation changed only for the left half of the circle did the trick
<text style="text-anchor: start; font-family: 'Roboto', sans-serif; font-size: 22px" x="350" y="350" fill="#fff" dx="-250" transform="rotate(103, 350, 350)"><?echo $captions[$t-1] ?></text>
<? } ?>
</g>
<? } ?>
Now it looks good and the text is readable. If you wish, you can change the text-anchor parameter, to position the text in the slices to your demand. I hope this will help others too.
Regards,
I had a similar issue, I found that swapping the positions of the x,y and x1,y1 parameters in the path element fixed it. The order that these were listed in in the path element effected the orientation of the text (at least in my case).
Related
This question already has an answer here:
Declaratively stroke a line with a composite line symbol using SVG
(1 answer)
Closed 7 months ago.
Is there a better solution to add to a path element a dashed border (lets say the border should have an offset of 2px in each direction)
I am looking for a general solution for a lot of path elements
For example my initial path element would be
<path stroke="black" fill="none" d="M10 10 L 50 10 L 50 80 L 10 80 Z"></path>
and at the moment I am creating another path element to add the border around the initial path element
<path stroke="black" stroke-dasharray="3" fill="none" d="M6 6 L 54 6 L 54 84 L 6 84 Z"></path>
<svg height="1000" width="1000">
<path stroke="black" fill="none"
d="M10 10 L 50 10 L 50 80 L 10 80 Z"></path>
<path stroke="black" stroke-dasharray="3"
fill="none" d="M6 6 L 54 6 L 54 84 L 6 84 Z"></path>
</svg>
"a general solution for a lot of path elements" is pretty vague. I'll handle simple closed paths in this answer.
This way may actually be more of an example how not to do it, but I think it shows a general problem with what you try to achieve. It uses only one place to define a path, and then re-uses it in three other places:
first, to draw the inner border,
then, to draw a much wider dashed border,
and finally, as a mask to hide that part of the dashed border that would otherwise overlap the inner one.
This has the advantage of not having to create extra paths, but the dashed border looks strange. The corners either show gaps or exta-long dashes, and in curved sections the length of the dashes are differing.
.distance {
stroke-width: 6;
}
.inner {
fill:none;
stroke: black;
stroke-width: 2;
}
.outer {
stroke: black;
stroke-width: 10;
stroke-dasharray: 4;
stroke-dashoffset: 2;
}
<svg viewBox="0 0 100 100" width="200" height="200">
<defs>
<path id="src" d="M 32,13 20,42 Q 30,90 85,90 L 92,53 Q 60,53 60,13 Z" />
<mask id="mask">
<rect width="100%" height="100%" fill="white" />
<use class="distance" href="#src" stroke="black" />
</mask>
</defs>
<use class="inner" href="#src" />
<use class="outer" href="#src" mask="url(#mask)" />
</svg>
Why is that so? The dashes are computed in relation to where the original path is, but what is shown is only the outer fringe of the whole stroke, at an offset. (or to put it the other way round, the path defining where dashes start and end is at an offset from the middle of the shown dashed line.) For concave sections, dashes get longer, and for convex sections, shorter.
The only way the dash length can be stable is when the path used to compute dashes sits in the middle of the dashes. You could change the order around and define the dashes on the inner border:
.distance {
stroke-width: 6;
}
.inner {
fill:none;
stroke: black;
stroke-width: 2;
stroke-dasharray: 4;
}
.outer {
stroke: black;
stroke-width: 10;
}
<svg viewBox="0 0 100 100" width="200" height="200">
<defs>
<path id="src" d="M 32,13 20,42 Q 30,90 85,90 L 92,53 Q 60,53 60,13 Z" />
<mask id="mask">
<rect width="100%" height="100%" fill="white" />
<use class="distance" href="#src" stroke="black" />
</mask>
</defs>
<use class="inner" href="#src" />
<use class="outer" href="#src" mask="url(#mask)" />
</svg>
..but that is as far as you get. The bottom line remains: you need to have a path where the dashed line is, not at an offset.
Use a native JavaScript Web Component <svg-outline> (you define once)
to do the work on an <svg>
<svg-outline>
<svg viewBox="0 0 100 100" height="180">
<path outline="blue" fill="none" d="M5 5 L 50 30 L 50 40 L 10 80 Z"/>
</svg>
</svg-outline>
The Web Component clones your original shapes (marked with "outline" attribute)
sets a stroke and stroke-dasharray on it
removes any existing fill
transforms clone
translates clone to account for scale(1.2)
scales clone to 1.2 size
then corrects translate
SO snippet output:
See JSFiddle: https://jsfiddle.net/WebComponents/2goahcqv/
<svg-outline>
<style> circle[outline] { stroke: blue } </style>
<svg viewBox="0 0 100 100" height="180">
<rect outline="green" x="15" y="15" width="50%" height="50%" stroke="blue" fill="teal"/>
<circle outline fill="lightcoral" cx="50" cy="50" r="10"/>
</svg>
</svg-outline>
<svg-outline>
<svg viewBox="0 0 100 100" height="180">
<path outline="blue" fill="pink" d="M15 10 L 50 30 L 50 40 L 20 70 Z"/>
</svg>
</svg-outline>
<script>
customElements.define("svg-outline", class extends HTMLElement {
connectedCallback() {
setTimeout(() => { // make sure innerHTML is parsed
let svg = this.querySelector("svg");
svg.querySelectorAll('[outline]').forEach(original => {
let outlined = svg.appendChild(original.cloneNode(true));
original.after(outlined); // so we don't create "z-index" issues
let outline_stroke = outlined.getAttribute("outline") || false;
if (outline_stroke) outlined.setAttribute("stroke", outline_stroke );
original.removeAttribute("outline"); // so we can use CSS on outlines
let {x,y,width,height} = original.getBBox();
let cx = x + width/2;
let cy = y + height/2;
outlined.setAttribute("fill", "none"); // outlines never filled
outlined.setAttribute("stroke-dasharray", 3); // or read from your own attribute, like "outline"
outlined.setAttribute("transform", `translate(${cx} ${cy}) scale(${1.2}) translate(-${cx} -${cy})`);
});
// (optional) whack everything into shadowDOM so styles don't conflict
this.attachShadow({mode:"open"}).append(...this.children);
})
}
})
</script>
I been struggling for hours to solve this, I'm trying to draw elliptical arc to fill up the spaces circled in red, can I have some sample code for it?
EDIT:
Since shape is generated using JavaScript, I will add SVG Elements generated from browser:
<svg width="500" height="500" style="background-color: white; padding: 0px 10px; user-select: none;" id="chart">
<path d="M 375,300 a 1 1 0 0 0 -300 0" stroke="lightgray" fill="lightgray"></path>
<path id="__currentVal__" d="M 75,300 l 150,0 l -106.06601717798213,-106.06601717798212 " stroke="skyblue" fill="skyblue"></path>
</svg>
The blue shape will be the element with id __currentVal__, the elliptical arc command will inserted at end of command M 75,300 l 150,0 l -106.06601717798213,-106.06601717798212
The expected output will be like this (edited using MS Paint):
This is the d attribute you are using: d="M 75,300 l 150,0 l -106.06601717798213,-106.06601717798212 "
M 75,300 means that you are moving to the point x:75, y:300.
Since you are using lower case commands those are relative commands. For instance l 150,0 means that you are moving 150 units in x from 75 to 225. The y doesn't change meaning that the y:300. The center of the arc is in this point: x:225,y:300.
This part of the command is also letting me know that the radius of the arc is 150.
Next the path (l -106.06601717798213,-106.06601717798212) goes from the previous point x:225 y:300 to the point x:225-106.06601717798213 = 118.93398282201787, y:300-106.06601717798212 = 193.93398282201787
I'm drawing a small circle to visualise this point (x:118.93398282201, y:193.93398282201787 ). The arc will start here. Also I'm drawing another small circle in the point x:75,y:300. The arc will end here. Now I can draw the arc: M118.93398282201,193.93398282201787 A 150,150 0 0 0 75,300
<svg viewBox="0 0 500 500" style="background-color: white; padding: 0px 10px; user-select: none;" id="chart">
<path d="M 375,300 a 150 150 0 0 0 -300 0" stroke="lightgray" fill="lightgray"></path>
<path id="__currentVal__"d="M 75,300 l 150,0 l -106.06601717798213,-106.06601717798212 " stroke="skyblue" fill="none" ></path>
<circle cx="75" cy="300" r="5"/>
<circle cx="118.93398282201" cy="193.93398282201787" r="5"/>
<path d="M118.93398282201,193.93398282201787A150,150 0 0 0 75,300" />
</svg>
If you happen to need to draw the arc in the same path as the actual blue triangle you can do this: you start your path with the arc, then you use the d attribute of your path without the first (move to) command. You don't need to move to this point since you are already there (the arc is ending in this point)
<svg viewBox="0 0 500 500" style="background-color: white; padding: 0px 10px; user-select: none;" id="chart">
<path d="M 375,300 a 150 150 0 0 0 -300 0" stroke="lightgray" fill="lightgray"></path>
<circle cx="75" cy="300" r="5"/>
<circle cx="118.93398282201" cy="193.93398282201787" r="5"/>
<path d="M118.93398282201,193.93398282201787A150,150 0 0 0 75,300
l 150,0 l -106.06601717798213,-106.06601717798212 " stroke="skyblue" fill="none" />
</svg>
You can remove those 2 small circles I've drawn as helpers to let me visualise the points of your path.
This SVG is intended to appear in a 30x30 square, and I've gotten it to look right in this code pen:
https://codepen.io/pgblu/pen/RwrYXXm
HTML
<span class="foo">
<svg height="30" width="30" viewBox="0 0 450 450">
<g
id="layer1"
transform="translate(-2887.5916,-192.36151)"
>
<g transform="matrix(1.3580428, 0, 0, 1.3580428, 2065.078, 44.928302)" id="layer2" />
<path
transform="matrix(1.1703715, 0, 0, 1.1703715, 1772.3948, -189.9379)"
d="M 1338.5714, 519.50507 A 192.85715, 192.85715 0 0 1 1145.7142, 712.36221 192.85715, 192.85715 0 0 1 952.85709, 519.50507 192.85715, 192.85715 0 0 1 1145.7142, 326.64792 192.85715, 192.85715 0 0 1 1338.5714, 519.50507 Z"
id="redCircle001"
style="fill:#dd0e00;fill-opacity:1;fill-rule:nonzero;stroke:none"
/>
<g transform="matrix(-3.1435529, 0, 0, -3.1435529, 10926.161, -138.49162)">
<path
style="fill:#ffffff"
d="m2494.1163, -202.23709 l4, 84.92415 l-25.67715, 0 l4, -84.92415 z"
id="whiteBeam001"
/>
<circle
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="whiteDot001"
transform="matrix(0.3181114, 0, 0, 0.3181114, 2440.9065, -251.44719)"
cx="139.73659"
cy="101.28652"
r="36.22654"
/>
</g>
</g>
</svg>
</span>
CSS:
.foo {
border: 1px solid #ffaa22;
height: 30px;
width: 30px;
}
...but I had to trial-and-error the viewBox directive and ended up with "0 0 450 450". I am clearly doing this wrong. Shouldn't the viewBox correspond to the target size, i.e., "0 0 30 30"? Is there something in the SVG code itself that is making the resizing counterintuitive?
I'm using Chartist.js to work on a pie chart.
I am trying to add a simple circle in the background, but for some reason the circle element just ends up in the top left corner with 0x0 dimensions.
Here is the SVG:
<div class="contents" id="chart-container">
<svg xmlns:ct="http://gionkunz.github.com/chartist-js/ct" width="100%" height="100%" class="ct-chart-pie" style="width: 100%; height: 100%;" viewBox="0 0 359 219">
<g class="basecircle">
<cirlce class="basecircleelement" cx="10" cy="10" fill="black" cr="181.76999999999998">
</cirlce>
</g>
<g ct:series-name="value" class="ct-series ct-series-a">
<path d="M147.071,10.108A104.375,104.375,0,1,0,179.325,5L179.325,109.375Z" class="ct-slice-pie" ct:value="95" style="fill: gray; stroke: white; stroke-width: #fff;"></path>
</g>
<g ct:series-name="rest" class="ct-series ct-series-b">
<path d="M179.325,5A104.375,104.375,0,0,0,146.725,10.222L179.325,109.375Z" class="ct-slice-pie" ct:value="5" style="fill: transparent;"></path>
</g>
</svg>
</div>
I expected the "g.basecircle" and "circle.basecircleelement" should be showing up(It's supposed to be in the center of the container but I didn't calculate the cx and cy yet) with radius of 181.76999999999998 -this value is calculated to be half of the clientWidth-, but it ends up in the upper left corner of the SVG element with dimension of 0 x 0
Is there anything I am missing?
this value is calculated to be half of the clientWidth-, but it ends
up in the upper left corner of the SVG element with dimension of 0 x 0
Typos must be fixed:
circle instead ofcirlce
Radius designation -r instead ofcr
Svg canvas borders are bounded by a red square style = "border: 1px solid red;"
It is very convenient to visually see the borders of SVG. When you finish debugging your application, this line can be removed.
To place the black circle in the center of the svg canvas, you need to set the coordinates cx =" 359/2 " cy =" 219/2 "
<div class="contents" id="chart-container">
<svg xmlns:ct="http://gionkunz.github.com/chartist-js/ct" width="100%" height="100%" class="ct-chart-pie" viewBox="0 0 359 219" style="border:1px solid red;">
<g class="basecircle">
<circle class="basecircleelement" cx="179.5" cy="109.5" fill="black" r="179.5">
</circle>
</g>
<g ct:series-name="value" class="ct-series ct-series-a">
<path d="M147.071,10.108A104.375,104.375,0,1,0,179.325,5L179.325,109.375Z" class="ct-slice-pie" ct:value="95" style="fill: gray; stroke: white; stroke-width: #fff;"></path>
</g>
<g ct:series-name="rest" class="ct-series ct-series-b">
<path d="M179.325,5A104.375,104.375,0,0,0,146.725,10.222L179.325,109.375Z" class="ct-slice-pie" ct:value="5" style="fill: transparent;"></path>
</g>
</svg>
</div>
Update
If you want the black circle to not form and completely fit on the canvas svg you need to set its radius equal to half the height of the canvas r= 219 / 2 = 109.5
<div class="contents" id="chart-container">
<svg xmlns:ct="http://gionkunz.github.com/chartist-js/ct" width="100%" height="100%" class="ct-chart-pie" viewBox="0 0 359 219" style="border:1px solid red;">
<g class="basecircle">
<circle class="basecircleelement" cx="179.5" cy="109.5" fill="black" r="109.5">
</circle>
</g>
<g ct:series-name="value" class="ct-series ct-series-a">
<path d="M147.071,10.108A104.375,104.375,0,1,0,179.325,5L179.325,109.375Z" class="ct-slice-pie" ct:value="95" style="fill: gray; stroke: white; stroke-width: #fff;"></path>
</g>
<g ct:series-name="rest" class="ct-series ct-series-b">
<path d="M179.325,5A104.375,104.375,0,0,0,146.725,10.222L179.325,109.375Z" class="ct-slice-pie" ct:value="5" style="fill: transparent;"></path>
</g>
</svg>
</div>
I want to have a curved text along a path (half circle) in SVG. I have followed this tutorial, which is great: https://css-tricks.com/snippets/svg/curved-text-along-path/
The problem is that the path presented there works only for this specific text - Dangerous Curves Ahead. If you leave only Dangerous word that's what happens: https://codepen.io/anon/pen/pqqVGa - it no longer works (the text is no more evenly spreaded across the path).
I want to have it work regardless of text length. How to achieve that?
Using the attributes lengthAdjust and textLength you can adjust the length of the text and the height of the letters, thereby placing the text of the desired length on a segment of a fixed length
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="300" viewBox="0 0 500 300">
<path id="path1" fill="none" stroke="black" d="M30,151 Q215,21 443,152 " />
<text id="txt1" lengthAdjust="spacingAndGlyphs" textLength="400" font-size="24">
<textPath id="result" method="align" spacing="auto" startOffset="1%" xlink:href="#path1">
<tspan dy="-10"> very long text very long text very long text </tspan>
</textPath>
</text>
</svg>
Using the attribute startOffset =" 10% " you can adjust the position of the first character of the phrase
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="300" viewBox="0 0 500 300" >
<path id="path1" fill="none" stroke="black" d="M30,151 Q215,21 443,152 " />
<text id="txt1" lengthAdjust="spacingAndGlyphs" textLength="400" font-size="24">
<textPath id="result" method="align" spacing="auto" startOffset="15%" xlink:href="#path1">
<tspan dy="-10"> very long text very long text very long text </tspan>
</textPath>
</text>
</svg>
and make animation using this attribute (click canvas)
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="300" viewBox="0 0 500 300">
<path id="path1" fill="none" stroke="black" d="M30,151 Q215,21 443,152 " />
<text id="txt1" lengthAdjust="spacingAndGlyphs" textLength="200" font-size="24">
<textPath id="result" method="align" spacing="auto" startOffset="-100%" xlink:href="#path1">
<tspan dy="-10"> Very long text Very long text Very long text </tspan>
<animate
begin="svg1.click"
dur="15s"
attributeName="startOffset"
values="-100%;1%;1%;100%;1%;1%;-100%"
repeatCount="5"/>
</textPath>
</text>
<text x="200" y="150" font-size="24" fill="orange" >Click me </text>
</svg>
This is assuming that the initial text size (35) is too small.
let curveLength = curve.getTotalLength();
let fs = 35;//the initial font size
test.setAttributeNS(null, "style", `font-size:${fs}px`)
while(test.getComputedTextLength() < curveLength){
fs++
test.setAttributeNS(null, "style", `font-size:${fs}px`)
}
body {
background-color: #333;
}
text {
fill: #FF9800;
}
<svg viewBox="0 0 500 500">
<path id="curve" d="M73.2,148.6c4-6.1,65.5-96.8,178.6-95.6c111.3,1.2,170.8,90.3,175.1,97" />
<text id="test">
<textPath xlink:href="#curve">
Dangerous
</textPath>
</text>
</svg>
UPDATE
The OP is commenting:
Thanks for the response. Instead of adjusting the font size, I would prefer to create a new path that is longer / smaller and matches the text width. Not sure how to do this tho. – feerlay
Please read the comments in the code. In base of the length of the text I'm calculating the new path, but I'm assuming a lot of things: I'm assuming the new path starts in the same point as the old one.
let textLength = test.getComputedTextLength();
// the center of the black circle
let c = {x:250,y:266}
// radius of the black circle
let r = 211;
// the black arc starts at point p1
let p1 = {x:73.2,y:150}
// the black arc ends at point p2
let p2 = {x:426.8,y:150}
// distance between p1 and p2
let d = dist(p1, p2);
// the angle of the are begining at p1 and ending at p2
let angle = Math.asin(.5*d/r);
// the radius of the new circle
let newR = textLength / angle;
// the distance between p1 and the new p2
let newD = 2 * Math.sin(angle/2) * newR;
// the new attribute c for the path #curve
let D = `M${p1.x},${p1.y} A`
D += `${newR}, ${newR} 0 0 1 ${p1.x + newD},${p1.y} `
document.querySelector("#curve").setAttributeNS(null,"d",D);
// a function to calculate the distance between two points
function dist(p1, p2) {
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
body {
background-color: #333;
}
text {
fill: #FF9800;
};
<svg viewBox="0 0 500 500">
<path id="black_circle" d="M73.2,148.6c4-6.1,65.5-96.8,178.6-95.6c111.3,1.2,170.8,90.3,175.1,97" />
<path id ="curve" d="M73.2,150 A 211,211 0 0 1 426.8,150" fill="#777" />
<text id="test">
<textPath xlink:href="#curve">
Dangerous curves
</textPath>
</text>
</svg>