Calculate dashed progress path in SVG circle - css

I'm building a progress circle that represents a users level up to three. It contains three dashes around the perimeter. A darker line that overlaps each dash will represent the user's progress. If a user is level one, the first dash should be overlapped by an amount equal to (their current experience / total experience to next level). If the user is level two, the first dash should be fully overlapped and the second dash should be the same as the above and so on. I've built my SVG circle with the following code:
<svg
width="240"
height="240"
viewBox="0 0 240 240"
strokeWidth="2px"
strokeDasharray="220 10"
strokeDashoffset="50"
>
<circle cx="120" cy="120" r="110" fill="white" stroke="turquoise" />
</svg>
This image represents a user that is on level two and about halfway through to level three.
How could I calculate a <path> to overlap the circle and display the user's progress?

This is how you'd do it with an arc path. Draw the arc from the bottom of the circle to +1 unit in the x axis. Set the pathLength="100" so you can calculate the required dash array appropriately. Then make sure your dash-array adds up to 100.
Note - you can't use a round line-cap for this method - there is a bug in Chrome that adds an incorrect line-cap at the end of the stroke-dash array.
<svg
width="240px"
height="240px"
viewBox="0 0 240 240"
stroke-width="4"
stroke-dasharray="220 10"
stroke-dashoffset="50"
stroke-linecap ="none">
<circle cx="120" cy="120" r="110" fill="white" stroke="turquoise" />
<path pathLength="100" d="M120 230 a 110 110 0 1 1 1 0" stroke="red" fill="none" stroke-width="8" stroke-dasharray="0 1 32 1 15 50" stroke-dashoffset="0"/>
</svg>

I question your design for readability:
This design is based on Google Analystics graphs:
Dev To Blogpost on how to built Graphs with Web Components
Web Component source: https://progress-circle.github.io/element.js:
All HTML Required:
<script src="https://progress-circle.github.io/element.js"></script>
<progress-circle edge="grey">
<style>
svg {
font: 16px arial;
height: 180px;
}
</style>
<progress value="75%" stroke="green">Level I</progress>
<progress value="60%" stroke="orange">Level II</progress>
<progress value="50%" stroke="orangered">Level III</progress>
</progress-circle>

Here you can control the length of each path by setting the stroke-dasharray. The first number is the length (between 0 and 94 (2*PI*45/3 = 94)) and the second number is the space between strokes (this should just be more than 284).
In this example the red path is 50% length. You need to experiment a bit with the numbers.
var dist = 6;
console.log('Length of 1/3 circle:', Math.round(2 * Math.PI * 45 / 3)-dist);
console.log('Rotate <g> slightly:',-Math.round(2 * Math.PI * 45 / 12)-(dist/2));
<div id="progress">
<svg viewBox="0 0 100 100" width="300" height="300">
<g transform="translate(50 50) rotate(-27)">
<path transform="rotate(0)" d="M 45,0 A 45,45 0 0 1 0,45 45,45 0 0 1 -45,0 45,45 0 0 1 0,-45 45,45 0 0 1 45,0 Z" stroke-width="5" stroke-dasharray="44 400" stroke-dashoffset="0" stroke="red" fill="none" stroke-linecap="round"/>
<path transform="rotate(120)" d="M 45,0 A 45,45 0 0 1 0,45 45,45 0 0 1 -45,0 45,45 0 0 1 0,-45 45,45 0 0 1 45,0 Z" stroke-width="5" stroke-dasharray="88 400" stroke-dashoffset="0" stroke="navy" fill="none" stroke-linecap="round"/>
<path transform="rotate(240)" d="M 45,0 A 45,45 0 0 1 0,45 45,45 0 0 1 -45,0 45,45 0 0 1 0,-45 45,45 0 0 1 45,0 Z" stroke-width="5" stroke-dasharray="88 400" stroke-dashoffset="0" stroke="orange" fill="none" stroke-linecap="round"/>
</g>
</svg>
</div>

Related

Can I style individual dashes in an SVG path?

Can I style dashes in my SVG path, so that each of them would be slightly irregular? As in the image above?
Tried looking for a CSS-only solution. But adding individual elements along the path would work as well
You can make them irregular with a displacement map (see below), or you can find a font with a glyph that you can use and use text along a textPath with that font to make it look like a customized dash. But you can't style each individual dash in a normal path.
<svg width="600px" height="400px">
<filter id="custom-stroke">
<feTurbulence baseFrequency= "0.02" type="turbulence" numOctaves="2"/>
<feDisplacementMap in="SourceGraphic" xChannelSelector="R" yChannelSelector="G" scale="15"/>
<feMorphology operator="erode" radius="1"/>
<feMorphology operator="dilate" radius="1" />
<feGaussianBlur stdDeviation="1.5" />
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 11 -7" result="goo" />
</filter>
<g filter="url(#custom-stroke)">
<path fill="none" stroke="blue" stroke-dasharray="10 10" stroke-width="8" d="M 10 10 a 100 80 0 0 1 100 40 200 300 0 0 0 200 100"/>
</g>
</svg>
<animateMotion rotate="auto"> rotates an SVG element on a <path>
I took my yesterdays answer (see that SO answer for explanation),
simplified it, added Michael his filter, and dabbled a bit with the filter settings.
Question for the SVG experts: Can we get that rotate="auto" value for each "marker" with JS?
If so, each "marker" could be customized to make it "curve" better
JSFiddle: https://jsfiddle.net/WebComponents/7muns9La/
<svg-path-markers count="25" filter>
<svg viewBox="0 0 500 300" style="background:pink;max-height:180px">
<defs><g id="marker"><path fill="green" d="m0 -10q29 4 0 10a8 7 90 110-10z"/></g></defs>
<filter id="F">
<feTurbulence baseFrequency= "0.001" type="turbulence" numOctaves="1"/>
<feDisplacementMap in="SourceGraphic" xChannelSelector="R" yChannelSelector="G" scale="15"/>
<feMorphology operator="erode" radius=".1"/><feMorphology operator="dilate" radius=".1" />
<feGaussianBlur stdDeviation="1"/><feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 11 -7"/>
</filter>
<path fill="none" stroke="black" d="m50 20a10 10 90 00230 100c30-140-160 0-90 120 30 50 370 40 260-60" />
<g id="markers"/>
</svg>
</svg-path-markers>
<script>
customElements.define("svg-path-markers", class extends HTMLElement {
connectedCallback() {
setTimeout(() => { // wait till innerHTML is parsed
this.querySelectorAll("path").forEach( path => {
let duration = "2"; // set to 0.00001 for instant display
let count = ~~this.getAttribute("count");
let filter = this.hasAttribute("filter") ? 'filter="url(#F)"' : '';
let id = path.id || (path.id = this.localName + Math.random()*1e9);
let marker = dist =>
`<use href="#marker" ${filter}>
<animateMotion dur="${duration}" fill="freeze"calcMode="linear"
rotate="auto" keyPoints="0;${dist}" keyTimes="0;1">
<mpath href="#${id}"/></animateMotion></use>`;
this.querySelector("#markers").innerHTML =Array(count).fill(0)
.map((_,i) => marker(i*(1/(count-1)))).join("");
} )
})}})
</script>

How to resize only 1 part of a SVG in order to fill the content

I understand that SVG actually doesn't have a content, but I am struglying with this problem and I can't solve during days.
I have a Figma (so I can get the SVG) with this design:
The problem is that "Your Collection" text needs to grow to the right in some situations, for example: When I translate the app to Spanish and I have to show: "Tu Coleccion", or some other languages with even longer texts.
In the Figma that shape is made by 2 shapes + an "UNION" rule from Figma:
body { background-color: #7fb6ff80;}
<svg width="188" height="71" viewBox="0 0 188 71" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1_193_1631" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M72.3203 50.6719C68.8459 50.6719 65.7635 52.7341 63.7839 55.5894C57.4655 64.7034 46.9298 70.6719 35 70.6719C15.67 70.6719 0 55.0019 0 35.6719C0 16.342 15.67 0.671936 35 0.671936C46.9298 0.671936 57.4655 6.64051 63.7839 15.7544C65.7635 18.6098 68.8459 20.6719 72.3203 20.6719H173C181.284 20.6719 188 27.3877 188 35.6719C188 43.9562 181.284 50.6719 173 50.6719H72.3203Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M72.3203 50.6719C68.8459 50.6719 65.7635 52.7341 63.7839 55.5894C57.4655 64.7034 46.9298 70.6719 35 70.6719C15.67 70.6719 0 55.0019 0 35.6719C0 16.342 15.67 0.671936 35 0.671936C46.9298 0.671936 57.4655 6.64051 63.7839 15.7544C65.7635 18.6098 68.8459 20.6719 72.3203 20.6719H173C181.284 20.6719 188 27.3877 188 35.6719C188 43.9562 181.284 50.6719 173 50.6719H72.3203Z" fill="white"/>
<path d="M63.7839 15.7544L62.9621 16.3242L63.7839 15.7544ZM62.9621 55.0197C56.8225 63.8757 46.588 69.6719 35 69.6719V71.6719C47.2715 71.6719 58.1085 65.531 64.6057 56.1592L62.9621 55.0197ZM35 69.6719C16.2223 69.6719 1 54.4496 1 35.6719H-1C-1 55.5542 15.1177 71.6719 35 71.6719V69.6719ZM1 35.6719C1 16.8943 16.2223 1.67194 35 1.67194V-0.328064C15.1177 -0.328064 -1 15.7897 -1 35.6719H1ZM35 1.67194C46.588 1.67194 56.8225 7.4682 62.9621 16.3242L64.6057 15.1847C58.1085 5.81283 47.2715 -0.328064 35 -0.328064V1.67194ZM72.3203 21.6719H173V19.6719H72.3203V21.6719ZM173 21.6719C180.732 21.6719 187 27.9399 187 35.6719H189C189 26.8354 181.837 19.6719 173 19.6719V21.6719ZM187 35.6719C187 43.4039 180.732 49.6719 173 49.6719V51.6719C181.837 51.6719 189 44.5085 189 35.6719H187ZM173 49.6719H72.3203V51.6719H173V49.6719ZM62.9621 16.3242C65.0754 19.3724 68.4347 21.6719 72.3203 21.6719V19.6719C69.2571 19.6719 66.4515 17.8471 64.6057 15.1847L62.9621 16.3242ZM64.6057 56.1592C66.4515 53.4968 69.2571 51.6719 72.3203 51.6719V49.6719C68.4347 49.6719 65.0754 51.9714 62.9621 55.0197L64.6057 56.1592Z" fill="#F2F2F2" mask="url(#path-1-inside-1_193_1631)"/>
</svg>
Do you have any idea how could I change the width of that second part in order to fill the content, without changing the circle part?
Do you have any idea how to get this with CSS (without SVG)? The harder part with CSS is the curve that is in the union between the circle and the rectangle. The other part is simple.
I think it would be a good idea to have a "sliding doors" effect. So, here I made patterns for both the circle and the "text label". Switching between patterns with different sizes makes the text label resize.
This can be done dynamically if you look for the size of the text and then update the pattern based on that.
body { background-color: #7fb6ff80;}
svg {
display: block;
}
<svg width="0" height="0" viewBox="0 0 400 104" xmlns="http://www.w3.org/2000/svg">
<pattern id="circle" viewBox="0 0 104 104" width="100%" height="100%">
<path transform="translate(2 2)" stroke="black" stroke-width="2" fill="none"
d="M 102 30 Q 97 30 92 23 Q 76 0 50 0 A 10 10 90 0 0 50 100 Q 76 100 92 77 Q 97 70 102 70"/>
</pattern>
<pattern id="p1_120" viewBox="0 0 400 104" width="100%" height="100%">
<path transform="translate(120 2)" stroke="gray" stroke-width="2" fill="none"
d="M -200 30 H 0 A 1 1 0 0 1 0 70 H -200"/>
</pattern>
<pattern id="p1_140" viewBox="0 0 400 104" width="100%" height="100%">
<path transform="translate(140 2)" stroke="black" stroke-width="2" fill="none"
d="M -200 30 H 0 A 1 1 0 0 1 0 70 H -200"/>
</pattern>
<pattern id="p1_160" viewBox="0 0 400 104" width="100%" height="100%">
<path transform="translate(160 2)" stroke="black" stroke-width="2" fill="none"
d="M -200 30 H 0 A 1 1 0 0 1 0 70 H -200"/>
</pattern>
</svg>
<svg id="svg01" viewBox="0 0 400 104" xmlns="http://www.w3.org/2000/svg">
<rect x="104" width="400" height="104" fill="url(#p1_160)" />
<rect width="104" height="104" fill="url(#circle)"/>
<text x="110" y="53" dominant-baseline="middle" font-size="20"
font-family="sans-serif">Your Collection</text>
</svg>
<svg id="svg02" viewBox="0 0 400 104" xmlns="http://www.w3.org/2000/svg">
<rect x="104" width="400" height="104" fill="url(#p1_140)" />
<rect width="104" height="104" fill="url(#circle)"/>
<text x="110" y="53" dominant-baseline="middle" font-size="20"
font-family="sans-serif">Tu Coleccion</text>
</svg>

How to draw a pointed SVG bezier curves

I am new to SVG and want to have a curve with pointed ends like the following image in svg code (using a python svg library):
Is there any way to achieve this using a bezier curve, or is there a possibility to draw a normal bezier curve and modify the ends with css to become pointed? I would appreciate a code example.
You can just use a closed path with two curves, one slightly "bulgier" than the other:
<svg height="100" width="500" viewBox="0 40 100 60">
<path d="M 0 60 Q 50 40 100 60 Q 50 45 0 60 Z" />
</svg>
In this, the first curve is M 0 60 Q 50 40 100 60, i.e. a quadratic curve from 0,60 to 100,600 over the control point 50,40, and the second curve is the ... 100 60 Q 50 45 0 60 part, defining a curve starting at "wherever we already were", i.e. 100,60 (the end point of our first curve), ending at 0,60 (the start point of our first curve) and controlled by 50,45
The Z is technically not necessary here, but since this is a closed path, we might as well explicitly encode that this is a closed path.
And if you want "tapered ends" then you can use two cubic curves instead, with the control points near the end points.
<svg height="100" width="500" viewBox="0 40 100 60">
<path d="M 0 60 C 10 40 90 40 100 60 C 80 45 20 45 0 60 Z" />
</svg>
Of course, this is only an approximation, but it'll get you pretty much what you need in almost all "just a bow" cases. And for the cases where this doesn't suffice, solve those on their own.
It's possible to generate something like this by using a gooey effect on a line that's been filled with an appropriate alpha gradient to get a variable width. But the results take quite a bit of fiddling with the gradient alphas, blur radius, gooey parameters in the filter, and then the "fix-up" feComponentTransfer. I would recommend explicitly drawing it with a filled shape.
<svg width="400px" height="400px" viewBox="0 0 55 55">
<defs>
<filter id="taper" filterUnits="userSpaceOnUse" width="55" height="55">
<feGaussianBlur stdDeviation="0.8"/>
<feColorMatrix type="matrix" values="
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 9 -2"/>
<feComponentTransfer>
<feFuncA type="table" tableValues="0 1 1 1 1 1"/>
</feComponentTransfer>
</filter>
<linearGradient id="myGradient">
<stop offset="0%" stop-color="rgba(0,0,0,0.3)" />
<stop offset="50%" stop-color="rgba(0,0,0,1)" />
<stop offset="100%" stop-color="rgba(0,0,0,0.3)" />
</linearGradient>
</defs>
<path d="M 10 10 C 20 20, 40 20, 50 10" stroke="url(#myGradient)" stroke-width="2" fill="none" filter="url(#taper)" stroke-linecap="round"/>
</svg>

How to make an Scale from the center while hovering using framer motion

below i got an svg icon that scale up in size while hovering but it doesnt seem to scale from the center , how i can fix that? appreciate your feedback
<motion.svg
xmlns="http://www.w3.org/2000/svg"
height="42px"
viewBox="0 0 24 24"
width="42px"
fill="#FFFFFF"
whileHover={{
scale: 1.3,
}}
>
<path d="M0 0h24v24H0V0zm0 0h24v24H0V0z" fill="none" />
<path d="M9 21h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.58 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2zM9 9l4.34-4.34L12 10h9v2l-3 7H9V9zM1 9h5v12H1z" />
</motion.svg>
The width and height don't match the viewBox properties (24 vs 42). If you make those match it should scale from the center.
Edit:
You might have better luck wrapping the SVG in a div and using that to set the size you want:
<motion.div
whileHover={{
scale: 1.3
}}
style={{ width: 64, height: 64 }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
height="100%"
width="100%"
viewBox="0 0 24 24"
fill="#000"
>
<path d="M0 0h24v24H0V0zm0 0h24v24H0V0z" fill="none" />
<path d="M9 21h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.58 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2zM9 9l4.34-4.34L12 10h9v2l-3 7H9V9zM1 9h5v12H1z" />
</svg>
</motion.div>

SVG how to make container bigger than svg

Have this problem
The blue circle (#Mark) should have black round border. For some reason it is not. Although if it is not a svg sprite but two separate svg elements everything is ok.
SVG:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path id="r8qna" d="M1351 35.438c0 .983-.815 1.562-1.821 1.562h-4.857c0 .984-.816 1.781-1.822 1.781-1.006 0-1.821-.797-1.821-1.781h-4.857c-1.007 0-1.822-.579-1.822-1.562 0-.846.376-1.649 1.03-2.202 1.17-.99 2.006-3.618 2.006-6.705 0-2.337 1.536-4.318 3.673-5.044A1.806 1.806 0 0 1 1342.5 20c.903 0 1.647.644 1.791 1.487 2.137.726 3.673 2.707 3.673 5.044 0 3.088.837 5.716 2.007 6.705a2.882 2.882 0 0 1 1.03 2.202zm-1.568-.103c0-.34-.15-.663-.414-.886-1.688-1.428-2.737-4.296-2.737-8.024 0-2.039-1.696-3.697-3.78-3.697-2.086 0-3.782 1.658-3.782 3.697 0 3.728-1.049 6.596-2.737 8.023a1.162 1.162 0 0 0-.414.887z"/>
<path id="rfoga" d="M1074.5 101a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5z"/>
</defs>
<symbol id="Bell" viewBox="0 0 17 19">
<g>
<g transform="translate(-1334 -20)">
<use fill="#a56ea3" xlink:href="#r8qna"/>
<use fill="#e0e0e0" xlink:href="#r8qna"/>
</g>
</g>
</symbol>
<symbol id="Mark" viewBox="0 0 5 5">
<g>
<g transform="translate(-1072 -101)">
<use fill="#30a1d6" xlink:href="#rfoga"/>
</g>
</g>
</symbol>
</svg>
CSS code
&__bell-icon {
fill: #e0e0e0;
height: 19px;
width: 17px;
}
&__circle-icon {
position: absolute;
visibility: visible;
height: 5px;
width: 5px;
stroke:$icon-stroke-light;
}
HTML code:
<span class="notifications__icons">
<svg class="notifications__bell-icon"><use href="../../../images/BellWithMark.svg#Bell"></svg>
<svg class="notifications__circle-icon"><use href="../../../images/BellWithMark.svg#Mark"></svg>
</span>
Your #Mark symbol is too small. You need to add some space for the stroke. Use <symbol id="Mark" viewBox="-1 -1 7 7"> instead of <symbol id="Mark" viewBox="0 0 5 5">. If needed change CSS accordingly.
I needed to see what happens so I've changed the size of your icons.
.notifications__bell-icon {
fill: #e0e0e0;
height: 190px;
width: 170px;
position: absolute;
}
.notifications__circle-icon {
position: absolute;
visibility: visible;
height: 50px;
width: 50px;
stroke:black;
}
<span class="notifications__icons">
<svg class="notifications__bell-icon"><use href="#Bell"></svg>
<svg class="notifications__circle-icon"><use href="#Mark"></svg>
</span>
<svg>
<defs>
<path id="r8qna" d="M1351 35.438c0 .983-.815 1.562-1.821 1.562h-4.857c0 .984-.816 1.781-1.822 1.781-1.006 0-1.821-.797-1.821-1.781h-4.857c-1.007 0-1.822-.579-1.822-1.562 0-.846.376-1.649 1.03-2.202 1.17-.99 2.006-3.618 2.006-6.705 0-2.337 1.536-4.318 3.673-5.044A1.806 1.806 0 0 1 1342.5 20c.903 0 1.647.644 1.791 1.487 2.137.726 3.673 2.707 3.673 5.044 0 3.088.837 5.716 2.007 6.705a2.882 2.882 0 0 1 1.03 2.202zm-1.568-.103c0-.34-.15-.663-.414-.886-1.688-1.428-2.737-4.296-2.737-8.024 0-2.039-1.696-3.697-3.78-3.697-2.086 0-3.782 1.658-3.782 3.697 0 3.728-1.049 6.596-2.737 8.023a1.162 1.162 0 0 0-.414.887z"/>
<path id="rfoga" d="M1074.5 101a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5z"/>
</defs>
<symbol id="Bell" viewBox="0 0 17 19">
<g>
<g transform="translate(-1334 -20)">
<use fill="#a56ea3" xlink:href="#r8qna"/>
<use fill="#e0e0e0" xlink:href="#r8qna"/>
</g>
</g>
</symbol>
<symbol id="Mark" viewBox="-1 -1 7 7">
<g>
<g transform="translate(-1072 -101)">
<use fill="#30a1d6" xlink:href="#rfoga"/>
</g>
</g>
</symbol>
</svg>

Resources