circular arc paths with svg - svg

I am pretty new to SVG and wanted to ask for a best approach to the following design:
I believe SVG is the way to go here since I need hover and click effects on each of the red arc pieces. These values and this design are essentially hardcoded and will not change. Are there any tools / libraries (D3 or Raphael) that would make this easier for me?
Thanks In Advance.

meetamit's suggestion is a good one. Or you could look into the 'sector' method shown here:
Half circle using raphael

You don't really need d3 or even an editor. It's a design that is easy to code by hand.
I was bored, so I whipped this up in about 10 minutes.
document.getElementById("band4").addEventListener("click", function(e) {
alert("50+");
}, false);
document.getElementById("band3").addEventListener("click", function(e) {
alert("20-50");
}, false);
document.getElementById("band2").addEventListener("click", function(e) {
alert("10-20");
}, false);
document.getElementById("band1").addEventListener("click", function(e) {
alert("Less than 10");
}, false);
svg {
position: absolute;
top: 0px;
}
circle.band {
fill: #a20c3e;
}
circle.band:hover {
fill: #ca3f5e;
}
text {
font-family: sans-serif;
font-size: 12px;
font-weight: bold;
fill: white;
}
tspan.sup {
font-size: 6px;
}
text.sub {
font-size: 5px;
font-weight: normal;
}
<img src="http://lorempixel.com/400/200/" width="100%"/>
<svg viewBox="-100 -100 200 100">
<defs>
<mask id="target">
<rect x="-100" width="100%" height="100%" fill="black"/>
<circle r="97" fill="white"/>
<circle r="77" fill="black"/>
<circle r="74" fill="white"/>
<circle r="54" fill="black"/>
<circle r="51" fill="white"/>
<circle r="31" fill="black"/>
<circle r="28" fill="white"/>
</mask>
</defs>
<circle id="band4" class="band" r="98" mask="url(#target)"/>
<circle id="band3" class="band" r="75" mask="url(#target)"/>
<circle id="band2" class="band" r="52" mask="url(#target)"/>
<circle id="band1" class="band" r="29" mask="url(#target)"/>
<text y="-82" text-anchor="middle" pointer-events="none">50<tspan class="sup" font-size="50%" dy="-0.7em">+</tspan></text>
<text y="-59" text-anchor="middle" pointer-events="none">20-50</text>
<text y="-36" text-anchor="middle" pointer-events="none">10-20</text>
<text y="-17" text-anchor="middle" class="sub" pointer-events="none">Less than</text>
<text y="-6" text-anchor="middle" pointer-events="none">10</text>
</svg>

Related

Reusing SVG elements while inserting different text?

I want to display balls with 1,2,3,4 inside. Can I use <use>? or I must duplicate the stone <g>?
#stone text {
fill: grey;
dominant-baseline: middle;
text-anchor: middle;
font-size: 0.33pt;
}
#pane {
background-color: yellow;
width: 100%;
height: 100%;
}
.stone-white {
fill: #F2F4F4;
}
.stone-black {
fill: #273746;
}
<svg id="pane" viewBox="0 0 22 22" preserveAspectRatio="none">
<defs>
<g id="stone">
<circle cx="0" cy="0" r="0.45" />
<text>333</text>
</g>
</defs>
<use href="#stone" class="stone-white" x="1" y="1"/>
<use href="#stone" class="stone-white" x="2" y="2"/>
<use href="#stone" class="stone-black" x="3" y="2"/>
<use href="#stone" class="stone-black" x="2" y="4"/>
</svg>

How to make a circular bullet point containing a number where the number text must scale

So I have been asked to make a section number that is constructed as a non-filled circle with a 2px border containing a number. The range of the numbers is 1 to 999.
As you can see from the example below, it looks pleasing at one & two digit section numbers, but when we hit three digits the number is clipped.
My thinking is that there needs to be a process where the text is drawn, measured, then scaled to fit into the target space inside the circle, where the target space is effectively a square rect 60% of the diameter of the circle.
However, no JS is allowed in the solution.
I thought it might be possible using SVG and its scaling capabilities via the 'preserveAspectRatio' parameter. However the image above is a screen grab of my SVG results. Working snippet below.
My intention with the code was to have the inner SVG containing the text resize proportionally so that it would fit the width of the parent, with the height set to auto so that it would change in proportion.
Can anyone tell me where I am going wrong?
PS. I am using Chrome on PC to test.
Note: In the following snippet the output looks like the image, so 8, then 88, then 888. The markup is the same for each case - only the text content changes.
body {
padding: 20px;
overflow: hidden;
background-color: #f0f0f0;
}
.counterDiv {
width: 44px;
max-width: 44px;
min-width: 44px;
height: 44px;
max-height: 44px;
min-height: 44px;
margin: 0;
padding: 0;
}
svg {
width: 100%;
height: 100%;
}
text {
font: normal normal 18pt Helvetica, Arial, Verdana;
}
<div class='counterDiv' style='position: relative;'>
<svg viewbox="0 0 44 44">
<g>
<circle cx="22" cy="22" r="20" stroke="#cd1041" stroke-width="2px" fill-opacity="0" />
<svg viewBox="0 0 100 auto" x="20%" width="60%" preserveAspectRatio="xMidYMid meet">
<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle" fill="#cd1041" dy=".1em">8</text>
</svg>
</g>
</svg>
</div>
<div class='counterDiv' style='position: relative;'>
<svg viewbox="0 0 44 44">
<g>
<circle cx="22" cy="22" r="20" stroke="#cd1041" stroke-width="2px" fill-opacity="0" />
<svg viewBox="0 0 100 auto" x="20%" width="60%" preserveAspectRatio="xMidYMid meet">
<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle" fill="#cd1041" dy=".1em">88</text>
</svg>
</g>
</svg>
</div>
<div class='counterDiv' style='position: relative;'>
<svg viewbox="0 0 44 44">
<g>
<circle cx="22" cy="22" r="20" stroke="#cd1041" stroke-width="2px" fill-opacity="0" />
<svg viewBox="0 0 100 auto" x="20%" width="60%" preserveAspectRatio="xMidYMid meet">
<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle" fill="#cd1041" dy=".1em">888</text>
</svg>
</g>
</svg>
</div>
A posible solution would be using textLength and lengthAdjust. The lengthAdjust attribute controls how the text is stretched into the length defined by the textLength attribute. One inconvinient would be that the 1 digit numbers would be stretched.
An alternative solution would be using a smaller font size.
Also you may want to use javascript to target only the 3 digit text elements.
body {
padding: 20px;
overflow: hidden;
background-color: #f0f0f0;
}
.counterDiv {
width: 44px;
max-width: 44px;
min-width: 44px;
height: 44px;
max-height: 44px;
min-height: 44px;
margin: 0;
padding: 0;
}
svg {
width: 100%;
height: 100%;
}
text {
font: normal normal 18pt Helvetica, Arial, Verdana;
}
<div class='counterDiv' style='position: relative;'>
<svg viewbox="0 0 44 44">
<g>
<circle cx="22" cy="22" r="20" stroke="#cd1041" stroke-width="2px" fill-opacity="0" />
<svg viewBox="0 0 100 auto" x="20%" width="60%" preserveAspectRatio="xMidYMid meet">
<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle" fill="#cd1041" dy=".1em" textLength="25" lengthAdjust="spacingAndGlyphs">8</text>
</svg>
</g>
</svg>
</div>
<div class='counterDiv' style='position: relative;'>
<svg viewbox="0 0 44 44">
<g>
<circle cx="22" cy="22" r="20" stroke="#cd1041" stroke-width="2px" fill-opacity="0" />
<svg viewBox="0 0 100 auto" x="20%" width="60%" preserveAspectRatio="xMidYMid meet">
<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle" fill="#cd1041" dy=".1em" textLength="25" lengthAdjust="spacingAndGlyphs">88</text>
</svg>
</g>
</svg>
</div>
<div class='counterDiv' style='position: relative;'>
<svg viewbox="0 0 44 44">
<g>
<circle cx="22" cy="22" r="20" stroke="#cd1041" stroke-width="2px" fill-opacity="0" />
<svg viewBox="0 0 100 auto" x="20%" width="60%" preserveAspectRatio="xMidYMid meet">
<text x="50%" y="50%" alignment-baseline="middle" text-anchor="middle" fill="#cd1041" dy=".1em" textLength="25" lengthAdjust="spacingAndGlyphs">888</text>
</svg>
</g>
</svg>
</div>
Not for OP,
but for all those SVG loving people who have an inner-guide (or sensible boss)
that tells them: "JavaScript is fine, when applied with common sense"
customElements.define("svg-counter", class extends HTMLElement {
connectedCallback() {
this.render();
}
render(
val = this.getAttribute("value") || "888",
color = "green",
circlestrokewidth = 4,
circlestroke = "red",
circlefill = "none"
){
let id = "P" + (new Date() / 1); // uniqueid
let singleDigit = val.length == 1;
this.innerHTML = `
<svg viewbox="0 0 100 100">
<circle cx="50" cy="50" r="${50-circlestrokewidth}" stroke="${circlestroke}"
fill="${circlefill}" stroke-width="${circlestrokewidth}"/>
<path id="${id}" pathLength="100" d="M0 60H100" stroke="none"/>
<text transform="scale(${singleDigit?1:.7})" transform-origin="50 50">
<textPath href="#${id}" method="stretch"
textlength="100" lengthAdjust="${singleDigit?"":"spacingAndGlyphs"}"
startoffset="50" text-anchor="middle" dominant-baseline="middle"
font-family="Helvetica"
fill="${color}" font-size="${100}px">${val}</textPath>
</text>
</svg>`;
}
});
svg {
width: 20%;
}
<svg-counter value="8"></svg-counter>
<svg-counter value="88"></svg-counter>
<svg-counter value="888"></svg-counter>
<svg-counter value="8888"></svg-counter>
Alas FireFox has some issues, slight tweaked version to make it work in FireFox:
Alas I have a boss who says "We don't care about FireFox customers"
customElements.define("svg-counter", class extends HTMLElement {
connectedCallback() {
this.render();
}
render(
val = this.getAttribute("value") || "888",
color = "green",
circlestrokewidth = 4,
circlestroke = "red",
circlefill = "none"
){
let id = "P" + (new Date() / 1) + val.length; // uniqueid
let singleDigit = val.length == 1;
this.innerHTML = `
<svg viewbox="0 0 100 100">
<circle cx="50" cy="50" r="${50-circlestrokewidth}" stroke="${circlestroke}"
fill="${circlefill}" stroke-width="${circlestrokewidth}"/>
<path id="${id}" pathLength="100" d="M0 55H100" stroke="blue"/>
<text transform="scale(${[0,1.7,1.2,.9,.7][val.length]})" transform-origin="50 50">
<textPath href="#${id}" method="stretch"
textlength="100" lengthAdjust="${singleDigit?"":"spacingAndGlyphs"}"
startoffset="50" text-anchor="middle" dominant-baseline="middle"
font-family="Helvetica"
fill="${color}" font-size="50px">${val}</textPath>
</text>
</svg>`;
}
});
svg {
width: 20%;
}
<svg-counter value="8"></svg-counter>
<svg-counter value="88"></svg-counter>
<svg-counter value="888"></svg-counter>
<svg-counter value="8888"></svg-counter>

How to style an image tag in a SVG?

I would like to know if it is possible to target the below image tag, located in an SVG, and style it externally with CSS.
<g id="Vector_Smart_Object_xA0_Image_1_" class="trapSVG" >
<image style="overflow:visible;" width="15" height="15" id="Vector_Smart_Object_xA0_Image" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA
GXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAZlJREFUeNqcUz1LA0EQfbN3SYwf
+IEWBpSQRhAtLCy0EWws/QGCP8JKbEUri1QWaiEGLewsbQQb2xQJNoKIQgwqaooEk0t2nDlyEE1C
jAOzO+x7b3Znd5bQbEZ8zgVmZR6vAhGJyxI/S5yROS1uA7L7SzwWAtY8YHmSzGycKBYBhcvgygNz
7p5tRvArwU+F+6oCahQ7wNYQ0fqqcUcWyUGCDHoFKImLGDdcw4Wtvn8yn9SA3SCJf2w5yvYwUXnH
jdjHcD9zeKDJdV1x5Sm/Xq5v8xK9bzhhW2whbHTFlad81TmqlmEzBlo6DEVp9EdVzSZ3gGnj0HnN
ixaBL1O/iJUlKWKigzgw5SlfdX4CeZP4DBl0Y8pXXXeqNk2jw0OWbVdC5avOT8DA5bWt4kmjP5jy
lK86P4E0RSoH/kjWKlzqIFZcecpXnVNfz0vQdwu7YAgmToYGW7yI7nxgPd63niftnJSiz4IEegNZ
WexJs53Kg6NvQi4I8CLznfiVtHHKeji23keB+Uh23xO4SO0+U+Ifn+lf3/lbgAEAuN/KQ8s2cHgA
AAAASUVORK5CYII=" transform="matrix(0.75 0 0 0.75 913.5 276.75)">
</image>
</g>
</svg>
Sure, if you are using inline SVG.
#Vector_Smart_Object_xA0_Image {
transform: rotate(45deg);
}
#svg-rect {
fill: transparent;
stroke: gray;
stroke-width: .5px;
transition: fill .25s;
}
#svg-rect:hover {
fill: yellow;
}
<g id="Vector_Smart_Object_xA0_Image_1_" class="trapSVG" >
<image style="overflow:visible;" width="15" height="15" id="Vector_Smart_Object_xA0_Image" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA
GXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAZlJREFUeNqcUz1LA0EQfbN3SYwf
+IEWBpSQRhAtLCy0EWws/QGCP8JKbEUri1QWaiEGLewsbQQb2xQJNoKIQgwqaooEk0t2nDlyEE1C
jAOzO+x7b3Znd5bQbEZ8zgVmZR6vAhGJyxI/S5yROS1uA7L7SzwWAtY8YHmSzGycKBYBhcvgygNz
7p5tRvArwU+F+6oCahQ7wNYQ0fqqcUcWyUGCDHoFKImLGDdcw4Wtvn8yn9SA3SCJf2w5yvYwUXnH
jdjHcD9zeKDJdV1x5Sm/Xq5v8xK9bzhhW2whbHTFlad81TmqlmEzBlo6DEVp9EdVzSZ3gGnj0HnN
ixaBL1O/iJUlKWKigzgw5SlfdX4CeZP4DBl0Y8pXXXeqNk2jw0OWbVdC5avOT8DA5bWt4kmjP5jy
lK86P4E0RSoH/kjWKlzqIFZcecpXnVNfz0vQdwu7YAgmToYGW7yI7nxgPd63niftnJSiz4IEegNZ
WexJs53Kg6NvQi4I8CLznfiVtHHKeji23keB+Uh23xO4SO0+U+Ifn+lf3/lbgAEAuN/KQ8s2cHgA
AAAASUVORK5CYII=" transform="matrix(0.75 0 0 0.75 913.5 276.75)">
</image>
</g>
</svg>
<svg width="20" height="20">
<rect id="svg-rect" width="15" height="15" x="2" y="2"></rect>
</svg>
Is this just example image? It would be much simpler to use SVG rect element for this.

Issue when zooming in on SVG donut chart in Safari

I'm attempting to create a donut chart using SVG, and am running into a problem when viewing it in Safari. Here's a fiddle that shows the issue; I'll describe it in detail below:
https://jsfiddle.net/nijhazer/phy2ossh/
This fiddle shows a graphic comprised of two circles overlaid atop one another. The problem becomes apparent when a Safari user increases zoom size in her browser:
Relevant HTML from the example:
<div class="donut-chart">
<svg width="200" height="200">
<circle class="backdrop" cx="100" cy="100" r="65" fill="#d5d8d5" stroke="none" stroke-width="0"></circle>
<circle class="progress" cx="100" cy="100" r="75" fill="none" stroke="lightgreen" stroke-width="20" style="stroke-dashoffset: 353.428875px;"></circle>
<circle class="outer-ring" cx="100" cy="100" r="85" fill="none" stroke="#d5d8d5" stroke-width="1"></circle>
</svg>
</div>
Relevant CSS from the example:
body {
background-color: white;
}
.donut-chart {
width: 200px;
height: 200px;
position: relative;
}
svg {
width: 200px;
height: 200px;
}
.progress {
stroke-dasharray: 471.24;
transform: rotate(-90deg);
transform-origin: 50% 50%;
background-color: transparent;
}
I don't know if this helps, but at I can read from the comments that people are blaming the CSS property transform-origin. In this example I moved the styling to attributes on the circle element. I also added pathLength to make it easier to control the progress bar.
body {
background-color: white;
}
.donut-chart {
width: 200px;
height: 200px;
position: relative;
}
svg {
width: 200px;
height: 200px;
}
<div class="donut-chart">
<svg viewBox="0 0 200 200" xmlns="http//www.w3.org/2000/svg">
<circle class="backdrop" cx="100" cy="100" r="65" fill="#d5d8d5"
stroke="none" stroke-width="0" />
<circle class="progress" cx="100" cy="100" r="75" fill="none"
stroke="lightgreen" stroke-width="20" stroke-dasharray="25 100"
transform="rotate(-90 100 100)" pathLength="100" />
<circle class="outer-ring" cx="100" cy="100" r="85" fill="none"
stroke="#d5d8d5" stroke-width="1" />
</svg>
</div>
I can confirm chrwahl's approach is working (at least in MacOS/IOS safari versions (15.4) I've tested - Unfortunately, the support of functions may vary from version to version)
Another workaround could be to add a translateX offset before rotating like so:
.donut-chart {
width: 200px;
height: 200px;
position: relative;
display: block;
}
svg {
width: 200px;
height: 200px;
}
.progress {
stroke-dasharray: 471.24;
background-color: transparent;
transform: translate(0px, 200px) rotate(-90deg);
}
<div class="donut-chart">
<svg width="200" height="200" viewBox="0 0 200 200">
<circle class="backdrop" cx="50%" cy="50%" r="65" fill="#d5d8d5" stroke="none" stroke-width="0"></circle>
<circle class="progress" cx="50%" cy="50%" r="75" fill="none" stroke="lightgreen" stroke-width="20" style="stroke-dashoffset: 353.428875px;"></circle>
<circle class="outer-ring" cx="100" cy="100" r="85" fill="none" stroke="#d5d8d5" stroke-width="1"></circle>
</svg>
</div>
This "hack" won't work combined with transform-origin: 50% 50%

SVG moves out of position when screen size changes or doesn't scale

I've created an SVG and put the width as a percentage because I want it to resize to fit different screen widths, but when I resize the screen, the svg moves up and down and doesn't move left/right to stay in the centre. If I use pixels instead of percentages, it doesn't resize with the screen.
Preview didn't work on here so here's the codepen link
.
HTML
<svg height="100%" width="100%" id="main">
<circle class="graph line line-1" cx="50%" cy="50%" r="25%" stroke-width="5%" stroke="#f1c40f" fill="none" />
<circle class="graph line line-2" cx="50%" cy="50%" r="20%" stroke-width="5%" stroke="#e67e22" fill="none" />
<circle class="graph line line-3" cx="50%" cy="50%" r="15%" stroke-width="5%" stroke="#00c0df" fill="none" />
</svg>
CSS
#main {
padding: 100px 0;
margin-top: 100px;
height: 200px;
background-color: pink;
}
.graph {
transform: rotate(270deg);
}
.graph.line {
transform-origin: center;
stroke-dasharray: 160%;
animation: graph 1.5s ease-in-out infinite alternate;
}
#keyframes graph {
from {
stroke-dashoffset: 160%;
}
to {
stroke-dashoffset: 90%;
}
}
That's what viewBox is for. With a viewBox, you establish a local coordinate system, which scales with your image. In your svg you simply use your local coordinates, and the image scales to any size...
#main {
position:absolute;
top:0px;left:0px;
right:0px;bottom:0px;
background:pink
}
.graph {
transform: rotate(270deg);
}
.graph.line {
transform-origin: center;
stroke-dasharray: 160%;
animation: graph 1.5s ease-in-out infinite alternate;
}
#keyframes graph {
from {
stroke-dashoffset: 160%;
}
to {
stroke-dashoffset: 90%;
}
}
<svg viewBox="0 0 100 100" id="main">
<circle class="graph line line-1" cx="50" cy="50" r="25" stroke-width="5" stroke="#f1c40f" fill="none" />
<circle class="graph line line-2" cx="50" cy="50" r="20" stroke-width="5" stroke="#e67e22" fill="none" />
<circle class="graph line line-3" cx="50" cy="50" r="15" stroke-width="5" stroke="#00c0df" fill="none" />
</svg>

Resources