Skip to content

Instantly share code, notes, and snippets.

@sertraline
Created January 27, 2025 19:54
Show Gist options
  • Save sertraline/799102be7e6efb4b5664a637f17a93d6 to your computer and use it in GitHub Desktop.
Save sertraline/799102be7e6efb4b5664a637f17a93d6 to your computer and use it in GitHub Desktop.
chartjs doughnut with labels outside
// setup
const data = {
labels: [
'Improved Screening (15%)',
'Reduced Dependence (20%)',
'Efficient Decisions (15%)',
'Proactive Prevention (30%)',
'Other (Remaining Savings)',
],
datasets: [
{
label: 'Cost',
data: [15, 20, 15, 30, 20],
backgroundColor: [
'rgba(251,192,45, 0.4)',
'rgba(56,142,60, 0.4)',
'rgba(211,47,47, 0.4)',
'rgba(30,136,229, 0.4)',
'rgba(142,36,170, 0.4)',
'rgba(255, 159, 64, 0.4)',
'rgba(0, 0, 0, 0.4)',
],
borderColor: [
'rgba(251,192,45, 1)',
'rgba(56,142,60, 1)',
'rgba(211,47,47, 1)',
'rgba(30,136,229, 1)',
'rgba(142,36,170, 1)',
'rgba(255, 159, 64, 1)',
'rgba(0, 0, 0, 1)',
],
borderWidth: 1,
offset: 15,
},
],
}
let vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
const doughnutLabelsLine = {
id: 'doughnutLabelsLine',
afterDraw(chart, args, options) {
const {
ctx,
chartArea: { top, bottom, left, right, width, height },
} = chart
chart.data.datasets.forEach((dataset, i) => {
//chart.getDatasetMeta(i)
chart.getDatasetMeta(i).data.forEach((datapoint, index) => {
const { x, y } = datapoint.tooltipPosition()
const halfwidth = width / 2
const halfheight = height / 2
let xWidth = 35
if (vw < 600) {
xWidth = 15
}
let xLine = x >= halfwidth ? x + xWidth : x - xWidth
let yLine = y >= halfwidth ? y + 45 : y - 45
if (chart.data.labels[index] === 'Efficient Decisions (15%)') {
yLine = y + 45
}
const xxLine = x >= halfwidth ? xWidth : -xWidth
if (chart.data.labels[index] === 'Other (Remaining Savings)') {
xLine = x - xWidth
yLine = y - 45
}
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(xLine, yLine)
ctx.lineTo(xLine + xxLine, yLine)
ctx.strokeStyle = dataset.borderColor[index]
ctx.stroke()
const textWidth = ctx.measureText(chart.data.labels[index]).width
texts = chart.data.labels[index].split(' ')
ctx.font = '14px Alexandria'
const textXPosition = x >= halfwidth ? 'left' : 'right'
const plusFive = x >= halfwidth ? 5 : -5
ctx.textAlign = textXPosition
ctx.textBaseline = 'middle'
ctx.fillStyle = 'black'
let offset = 0
texts.forEach((text) => {
ctx.fillText(text, xLine + xxLine + plusFive, yLine - offset)
offset -= 14
})
})
})
},
}
console.log(vw)
// config
let p = 45
if (vw >= 600 && vw <= 900) {
p = 55
}
const scaleChart = {
id: 'scaleChart',
beforeDatasetsDraw(chart, args, plugins) {
const { ctx } = chart
ctx.save()
if (!chart.originalOuterRadius) {
chart.originalOuterRadius = chart.getDatasetMeta(0).data[0].outerRadius
}
const scale = plugins.scaleFactor || 1
chart.getDatasetMeta(0).data.forEach((dataPoint, index) => {
dataPoint.outerRadius = chart.originalOuterRadius * scale
})
},
}
const config = {
type: 'doughnut',
data,
options: {
maintainAspectRatio: false,
layout: {
padding: p,
},
plugins: {
scaleChart: {
scaleFactor: 0.8,
},
legend: {
display: false,
},
datalabels: {
formatter: (value) => {
return value + '%'
},
color: '#000',
font: {
weight: 'bold',
size: 16,
},
},
},
scales: {
yAxes: [
{
gridLines: {
drawOnChartArea: false,
},
},
],
},
},
plugins: [doughnutLabelsLine, scaleChart],
}
// render init block
const myChart = new Chart(document.getElementById('costs_pie'), config)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment