Ezra Free
Ezra Free

Reputation: 754

How to position an SVG circle along another circle's path

I'm building a Gauge chart in a presentational component in React.

I just need to pass it a percentage and let the component do the rest. I can't use any animations because I'm taking a screenshot of the component to place the image in a Powerpoint presentation.

Here's a screenshot of what I'm trying to do:

enter image description here

As you can see in my code snippet, the circle <marker> is being positioned at the end of the grey <path> instead of at the end of the green <path>. How could I position the circle so it sits at the stroke-linecap of the green <path> as in the image above?

Here's the HTML code I have so far:

<div style="width:400px">
  <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <defs>
      <marker
        id="dot"
        viewBox="0 0 10 10"
        refX="4"
        refY="4"
        markerWidth="4"
        markerHeight="4"
      >
        <circle
          cx="4"
          cy="4"
          r="2"
          fill="#FFFFFF"
          stroke="#008000"
          stroke-width="2"
        />
      </marker>
    </defs>
    <path d="M20,60a35,35 0 1,1 60,0" stroke="#D3D7DB" stroke-width="4" fill="none" stroke-linecap="round"></path>
    <path d="M20,60a35,35 0 1,1 60,0" stroke="#008000" stroke-width="6" pathLength="100" fill="none" stroke-linecap="round" stroke-dasharray="75 35" marker-end="url(#dot)"></path>
  </svg>
</div>

Upvotes: 3

Views: 5500

Answers (2)

You can do it all in SVG by setting pathLength and using animationMotion to position the circle.

Some JavaScript and a W3C standard Web Component (JSWC supported in all modern Browsers) help in putting multiple gauges on screen and making them dynamic.

customElements.define("svg-gauge", class extends HTMLElement {
  connectedCallback() {
    let speed = 0.5; // set to 0.0001 for instant display!
    let arc = "M20,60a35,35 0 1,1 60,0";
    let col = this.getAttribute("color") || "green";
    this.innerHTML = 
    `<input type="range" min="0" max="100" step="5" value="0"`+ // delete 2 lines
    ` oninput="this.parentNode.percent=this.value" /><br>`+ // just for demo
    `<svg viewbox="0 0 100 100">
       <path d="${arc}" stroke="grey" stroke-width="6" fill="none" stroke-linecap="round"></path>
       <path id="P" d="${arc}" stroke="${col}" stroke-width="8" pathLength="100" fill="none" stroke-linecap="round" stroke-dasharray="75 35"/>
       <circle stroke="${col}" cx="0" cy="0" fill="#fff" r="4" stroke-width="3">
         <animateMotion dur="${speed}s" path="${arc}" keyPoints="0;0.75" fill="freeze" keyTimes="0;1" calcMode="linear"/>
       </circle>
       <text x="50%" y="40%" text-anchor="middle">XXX</text>
     </svg>`;
    this.style.display='inline-block';
    this.percent = this.getAttribute("value") || "50";
  }
  set percent(val = 0) {
    this.setAttribute("value", val);
    let dash = val + " " + (105 - val);
    this.querySelector("#P").setAttribute('stroke-dasharray', dash);
    this.querySelector("animateMotion").setAttribute('keyPoints', '0;'+val/100);
    this.querySelector("text").innerHTML =`${val} %`;
    this.querySelector("animateMotion").beginElement();
    this.querySelector("input").value = val;
  }
})
drag sliders
<svg-gauge value="35" color="red"   ></svg-gauge>
<svg-gauge value="50" color="orange"></svg-gauge>
<svg-gauge value="75" color="green" ></svg-gauge>

Upvotes: 4

Ezra Free
Ezra Free

Reputation: 754

I went ahead and accepted Danny '365CSI' Engelman's answer above, but just in case anyone wants to do this without the animations here is how I ended up implementing it:

<div style="width:400px">
  <svg viewBox="0 -10 100 100" xmlns="http://www.w3.org/2000/svg">
    <path d="M20,60a35,35 0 1,1 60,0" stroke="#D3D7DB" stroke-width="4" fill="none" stroke-linecap="round"></path>
    <path d="M20,60a35,35 0 1,1 60,0" stroke="#008000" stroke-width="6" pathLength="100" fill="none" stroke-linecap="round" stroke-dasharray="50 85"></path>
    <circle
          cx="0"
          cy="0"
          r="6"
          stroke-width="6"
          fill="#FFFFFF"
          stroke="#008000"
        >
          <animateMotion
            begin="0s"
            dur="infinite"
            repeatCount="infinite"
            keyPoints="0.5;0.5"
            fill="freeze"
            keyTimes="0;1"
            calcMode="linear"
            path="M20,60a35,35 0 1,1 60,0"
          ></animateMotion>
        </circle>
  </svg>
</div>

Upvotes: 2

Related Questions