<!DOCTYPE html>
<html>
<head>
</head>
<body>
	<div id="app">
		<donut-chart :initial-values="values"></donut-chart>
	</div>
	<script type="text/x-template" id="donutTemplate">
		<svg height="160" width="160" viewBox="0 0 160 160">
			<g v-for="(value, index) in sortedValues">
				<circle :cx="cx" :cy="cy" :r="radius" :stroke="colors[index]" :stroke-width="strokeWidth" :stroke-dasharray="adjustedCircumference" :stroke-dashoffset="calculateStrokeDashOffset(value, circumference)" :transform="returnCircleTransformValue(index)" fill="transparent" />
				<text :x="chartData[index].textX" :y="chartData[index].textY">{{ percentageLabel(value) }}</text>
			</g>
		</svg>
	</script>	
</body>
</html>
Vue.component('donutChart', {
	template: '#donutTemplate',
	props: ["initialValues"],
	data() {
		return {
			angleOffset: -90,
			chartData: [],
			colors: ["#6495ED", "goldenrod", "#cd5c5c", "thistle", "lightgray"],
			cx: 80,
			cy: 80,		      
			radius: 60,
			sortedValues: [],
			strokeWidth: 30,
		}
	},
	computed: {
		adjustedCircumference() {
			return this.circumference - 2
		},
    calculateChartData() {
      this.sortedValues.forEach((dataVal, index) => {
        const { x, y } = this.calculateTextCoords(dataVal, this.angleOffset)        
        const data = {
          degrees: this.angleOffset,
          textX: x,
          textY: y
        }
        this.chartData.push(data)
        this.angleOffset = this.dataPercentage(dataVal) * 360 + this.angleOffset
      })
    },   
		circumference() {
			return 2 * Math.PI * this.radius
		},
    dataTotal() {
      return this.sortedValues.reduce((acc, val) => acc + val)
    },
		sortInitialValues() {
			return this.sortedValues = this.initialValues.sort((a,b) => b-a)
		}
	},
  methods: {
  calculateTextCoords(dataVal, angleOffset) {
    const angle = (this.dataPercentage(dataVal) * 360) / 2 + this.angleOffset
    const radians = this.degreesToRadians(angle)

    const textCoords = {
      x: (this.radius * Math.cos(radians) + this.cx),
      y: (this.radius * Math.sin(radians) + this.cy)
    }
    return textCoords
  },    
  calculateStrokeDashOffset(dataVal, circumference) {
    const strokeDiff = this.dataPercentage(dataVal) * circumference
    return circumference - strokeDiff
  },    
    dataPercentage(dataVal) {
      return dataVal / this.dataTotal
    },
    degreesToRadians(angle) {
      return angle * (Math.PI / 180)
    },    
    percentageLabel(dataVal) {
      return `${Math.round(this.dataPercentage(dataVal) * 100)}%`
    },
    returnCircleTransformValue(index) {
      return `rotate(${this.chartData[index].degrees}, ${this.cx}, ${this.cy})`
    },    
  },  
	mounted() {
		this.sortInitialValues
    this.calculateChartData
	}
})
new Vue({
	el: "#app",
	data() {
		return {
			values: [230, 308, 520, 130, 200]
		}
	},
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js