@ministryofjustice/hmpps-digital-prison-reporting-frontend
Version:
The Digital Prison Reporting Frontend contains templates and code to help display data effectively in UI applications.
202 lines (178 loc) • 5.44 kB
JavaScript
/* eslint-disable class-methods-use-this */
import ChartVisualisation from '../clientClass.mjs'
export default class DoughnutChartVisualisation extends ChartVisualisation {
static getModuleName() {
return 'doughnut-chart'
}
initialise() {
this.setupCanvas()
this.settings = this.initSettings()
this.chartData = this.generateChartData(this.settings)
this.initChart(this.chartData)
}
initSettings() {
return {
options: this.setOptions(),
styling: this.setDatasetStyling(),
datalabels: this.setDataLabels(),
pluginsOptions: this.setPluginsOptions(),
toolTipOptions: this.setToolTipOptions(),
plugins: this.setPlugins(),
}
}
setDatasetStyling() {
const pallette = this.getColourPallette()
const backgroundColor = pallette.map((p) => p.hex)
return [
{
backgroundColor: [...backgroundColor, ...backgroundColor],
hoverOffset: 4,
datalabels: {
anchor: 'center',
borderWidth: 0,
},
},
]
}
setOptions() {
const cutoutValue = this.chartParams.datasets.length === 1 ? '50%' : '20%'
return {
cutout: cutoutValue,
}
}
setPluginsOptions() {
return {
legend: {
display: true,
position: 'bottom',
},
}
}
setPlugins() {
const plugins = []
if (this.chartParams.datasets.length === 1 && !this.isPercentage) {
plugins.push(this.setCentralText())
}
return plugins
}
setCentralText() {
return {
// Put the total in the center of the donut
id: 'text',
beforeDraw(chart) {
const { width } = chart
const { height } = chart
const { ctx } = chart
ctx.textBaseline = 'middle'
let fontSize = 2.5
ctx.font = `100 ${fontSize}em GDS Transport`
ctx.fillStyle = ' #505a5f'
// Accumulated total
const total = chart.data.datasets[0].data.reduce((a, c) => a + c, 0)
const text = total
const textX = Math.round((width - ctx.measureText(text).width) / 2)
const textY = height / 2
ctx.fillText(text, textX, textY)
ctx.save()
ctx.textBaseline = 'middle'
fontSize = 1
ctx.font = `100 ${fontSize}em GDS Transport`
ctx.fillStyle = ' #505a5f'
// Chart title
const title = 'Total'
const titleX = Math.round((width - ctx.measureText(title).width) / 2)
const titleY = textY + 30
ctx.fillText(title, titleX, titleY)
ctx.save()
},
}
}
setLegend() {
const classContext = this
const { legend, suffix } = this
return {
id: 'legend',
beforeInit(chart) {
const ul = document.createElement('ul')
const { labels } = chart.data
const { backgroundColor, data } = chart.data.datasets[0]
labels.forEach((label, i) => {
const colourIndex = i % backgroundColor.length
const colour = backgroundColor[colourIndex]
const colourName = classContext.mapHexColourToName(colour, classContext)
const value = chart.data.datasets.length === 1 ? `${data[i]}${suffix}` : ''
ul.innerHTML += `
<li aria-label="${label} ${value}">
<span class="chart-colour__${colourName}">${value}</span>
${label}
</li>
`
})
legend.appendChild(ul)
return legend.appendChild(ul)
},
}
}
setToolTipOptions() {
const ctx = this
return {
callbacks: {
title(context) {
const { label, dataset } = context[0]
const { label: establishmentId } = dataset
const title = ctx.singleDataset ? `${label}` : `${establishmentId}: ${label}`
return title
},
label(context) {
const { label, parsed: value, dataset } = context
const { label: legend } = dataset
const dataArr = dataset.data
let toolipValue = `${value}${ctx.suffix}`
if (!ctx.isPercentage) {
const val = dataArr.reduce((sum, d) => sum + Number(d), 0)
const percentage = `${((value * 100) / val).toFixed(2)}%`
toolipValue = ctx.singleDataset
? `${toolipValue} (${percentage})`
: `${legend}: ${toolipValue} (${percentage})`
ctx.setHoverValue({ label, value: toolipValue, legend, ctx })
} else {
toolipValue = `${toolipValue}`
ctx.setHoverValue({ label, value: toolipValue, legend, ctx })
}
return toolipValue
},
},
}
}
setDataLabels() {
const ctx = this
return {
textAlign: 'center',
color: '#FFF',
display: (context) => {
const { dataset, dataIndex } = context
const value = dataset.data[dataIndex]
const total = dataset.data.reduce((a, c) => a + c, 0)
const percentage = (value / total) * 100
return percentage > 4
},
formatter: (value, context) => {
const { dataset } = context
const label = ctx.singleDataset
? `${value}${this.suffix}`
: `${value}${this.suffix}
${dataset.label}`
return label
},
labels: {
title: {
font: {
weight: 'bold',
size: 16,
color: '#FFF',
},
},
},
}
}
}