<!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]
}
},
});
This Pen doesn't use any external CSS resources.