svg ring chart

by imys

HTML

<div id="app">
  <Ring :percent="percent" color="#4caf50"></Ring>
  <input type="range" v-model.number="percent" min="0" max="100" step="1">
  {{percent}}
</div>

SCSS

.ring-svg {
    position: relative;
    cursor: pointer;
    path {
        fill: none;
        stroke-linecap: round;
        transition: stroke-dashoffset 0.2s ease 0s, stroke 0.2s ease
    }
}

Vue

const Ring = {
	template: `
  	<div :style="sizeStyle"
         class="ring-svg">
        <svg viewBox="0 0 100 100">
            <path stroke="#ddd"
                  transform="translate(50, 50)"
                  :stroke-width="strokeWidth"
                  :d="pathString"
                  class="back" />
            <path transform="translate(50, 50)"
                  :stroke="color"
                  :stroke-width="strokeWidth"
                  :d="pathString"
                  :style="pathStyle"
                  class="front" />
        </svg>
    </div>
  `,
	props: {
    percent: {
      type: Number,
      default: 0
    },
    color: {
      type: String,
      default: '#fff'
    },
    size: {
      type: Number,
      default: 120
    },
    strokeWidth: {
      type: Number,
      default: 8
    },
  },
  data() {
  	return {
    	startAngle: 216,
      endAngle: 144,
    }
  },
  computed: {
  	sizeStyle() {
      return {
        width: `${this.size}px`,
        height: `${this.size}px`
      }
    },
    radius() {
      return 50 - (this.strokeWidth / 2)
    },
    len() {
      return Math.PI * 2 * this.radius * 0.8
    },
    pathString() {
      return this.calcPath(this.endAngle)
    },
    pathStyle() {
      const offset = (100 - this.percent) / 100 * this.len
      return {
        'stroke-dasharray': `${this.len}px ${this.len}px`,
        'stroke-dashoffset': `${offset}px`,
      }
    },
  },
  methods: {
    position(angle) {
      const rad = angle * (Math.PI / 180)
      const x = (Math.sin(rad) * this.radius).toFixed(2)
      const y = -(Math.cos(rad) * this.radius).toFixed(2)
      return [x, y]
    },
    calcPath(endAngle) {
      const { radius, startAngle } = this
      const start = this.position(startAngle)
      const end = this.position(endAngle)
      return `M ${start[0]} ${start[1]} A ${radius} ${radius} 0 1 1 ${end[0]} ${end[1]}`
    }
  }
}

new Vue({
	el: '#app',
  components: {
  	Ring
  },
  data: {
  	percent:...