@ministryofjustice/hmpps-digital-prison-reporting-frontend
Version:
The Digital Prison Reporting Frontend contains templates and code to help display data effectively in UI applications.
322 lines (269 loc) • 9.36 kB
text/typescript
import {
ChartData,
MoJTable,
ChartCardData,
ChartDataset,
ChartType,
ChartDetails,
ChartMetaData,
} from '../../types/Charts'
import { DashboardDataResponse } from '../../types/Metrics'
import {
BarChartVisualisationColumn,
DashboardVisualisation,
DashboardVisualisationColumns,
DashboardVisualisationType,
} from '../_dashboards/dashboard/types'
import DatasetHelper from '../../utils/datasetHelper'
import DashboardListUtils from '../_dashboards/dashboard-list/utils'
const createChart = (chartDefinition: DashboardVisualisation, rawData: DashboardDataResponse[]): ChartCardData => {
const timeseriesChartTypes = [DashboardVisualisationType.BAR_TIMESERIES, DashboardVisualisationType.LINE_TIMESERIES]
const { type } = chartDefinition
let table: MoJTable
let chart: ChartData
let details: ChartDetails
if (timeseriesChartTypes.includes(type)) {
const { latestData, dataSetRows, timeseriesData } = getDataForTimeseriesCharts(chartDefinition, rawData)
if (dataSetRows.length) {
chart = createTimeseriesChart(chartDefinition, timeseriesData)
table = createTimeseriesTable(chartDefinition, timeseriesData)
details = getChartDetails(chartDefinition, latestData, true)
}
} else {
const { dataSetRows, snapshotData } = getDataForSnapshotCharts(chartDefinition, rawData)
if (dataSetRows.length) {
chart = createSnapshotChart(chartDefinition, snapshotData)
table = createSnapshotTable(chartDefinition, dataSetRows)
details = getChartDetails(chartDefinition, dataSetRows)
}
}
return {
details,
table,
chart,
}
}
export default {
createChart,
}
const getDataForSnapshotCharts = (chartDefinition: DashboardVisualisation, rawData: DashboardDataResponse[]) => {
const data = DatasetHelper.getLastestDataset(rawData)
const dataSetRows = DatasetHelper.getDatasetRows(chartDefinition, data)
const snapshotData = DatasetHelper.filterRowsByDisplayColumns(chartDefinition, dataSetRows, true)
return {
dataSetRows,
snapshotData,
}
}
const getDataForTimeseriesCharts = (chartDefinition: DashboardVisualisation, rawData: DashboardDataResponse[]) => {
const latestData = DatasetHelper.getLastestDataset(rawData)
const dataSetRows = DatasetHelper.getDatasetRows(chartDefinition, rawData)
const timeseriesData = DatasetHelper.filterRowsByDisplayColumns(chartDefinition, dataSetRows, true)
return {
latestData,
dataSetRows,
timeseriesData,
}
}
const getChartDetails = (
chartDefinition: DashboardVisualisation,
data: DashboardDataResponse[],
timeseries = false,
): ChartDetails => {
const meta: ChartMetaData[] = []
const headlines: ChartMetaData[] = createHeadlines(chartDefinition, data, timeseries)
if (data[0]?.ts) {
meta.push({
label: 'Values for:',
value: data[0]?.ts.raw,
})
}
return {
meta,
headlines,
}
}
const createHeadlines = (
chartDefinition: DashboardVisualisation,
data: DashboardDataResponse[],
timeseries = false,
) => {
const headlines: ChartMetaData[] = []
const { columns } = chartDefinition
const { measures } = columns
const isListChart = !!(<BarChartVisualisationColumn[]>measures).find((col) => col.axis)
let headline: ChartMetaData
let headlineColumn: BarChartVisualisationColumn
let value: number
let label: string
if (timeseries) {
headlineColumn = <BarChartVisualisationColumn>measures.find((col) => col.id !== 'ts')
if (headlineColumn) {
label = `${data[0].ts.raw}`
value = +data[0][headlineColumn.id].raw
headline = {
label,
value,
}
}
} else {
headlineColumn = !isListChart
? <BarChartVisualisationColumn>measures[0]
: measures.find((col: BarChartVisualisationColumn) => col.axis && col.axis === 'y')
if (headlineColumn) {
label = `Total ${headlineColumn.display.toLowerCase()}`
value = data.reduce((acc: number, d: DashboardDataResponse) => acc + +d[headlineColumn.id].raw, 0)
headline = {
label,
value,
}
}
}
headlines.push(headline)
return headlines
}
const createSnapshotChart = (
chartDefinition: DashboardVisualisation,
snapshotData: DashboardDataResponse[],
): ChartData => {
const { type, columns } = chartDefinition
const { measures } = columns
const isListChart = !!(<BarChartVisualisationColumn[]>measures).find((col) => col.axis)
let labels: string[]
let unit
let datasets: ChartDataset[]
if (!isListChart) {
;({ labels, unit, datasets } = buildChart(columns, snapshotData))
} else {
;({ labels, unit, datasets } = buildChartFromListData(columns, snapshotData))
}
return {
type: type as unknown as ChartType,
unit,
data: {
labels,
datasets,
},
}
}
const buildChart = (columns: DashboardVisualisationColumns, rawData: DashboardDataResponse[]) => {
const { keys, measures } = columns
const labels = measures.map((col) => col.display)
const labelId = keys[keys.length - 1]?.id as keyof DashboardDataResponse
const unit = measures[0].unit ? measures[0].unit : undefined
const datasets = rawData.map((row) => {
const label = row[labelId] ? `${row[labelId].raw}` : 'All'
const data = measures.map((c) => {
const rowId = c.id as keyof DashboardDataResponse
return row[rowId] ? +row[rowId].raw : 0
})
const total = data.reduce((acc: number, val: number) => acc + val, 0)
return { label, data, total } as ChartDataset
})
return {
labels,
unit,
datasets,
}
}
const buildChartFromListData = (columns: DashboardVisualisationColumns, rawData: DashboardDataResponse[]) => {
const { measures, keys } = columns
const xAxisColumn = (<BarChartVisualisationColumn[]>measures).find((col) => col.axis === 'x')
const yAxisColumn = (<BarChartVisualisationColumn[]>measures).find((col) => col.axis === 'y')
const unit = yAxisColumn?.unit || undefined
const groupKey = DatasetHelper.getGroupKey(keys, rawData)
const groupsData = groupKey ? DatasetHelper.groupRowsByKey(rawData, groupKey.id) : [rawData]
const labels = groupsData[0]?.map((row) => {
return `${row[xAxisColumn.id].raw}`
})
const datasets: ChartDataset[] = groupsData.map((groupData) => {
const data = groupData.map((row) => +row[yAxisColumn.id].raw)
return {
label: groupKey ? `${groupData[0][groupKey.id].raw}` : yAxisColumn.display,
data,
total: data.reduce((acc: number, val: number) => acc + val, 0),
}
})
return {
labels,
unit,
datasets,
}
}
const createSnapshotTable = (chartDefinition: DashboardVisualisation, data: DashboardDataResponse[]): MoJTable => {
const { columns } = chartDefinition
const { keys, measures } = columns
const headerColumns = [...keys, ...measures]
const head = headerColumns.map((column) => {
return { text: column.display }
})
const filteredRowData = DatasetHelper.filterRowsByDisplayColumns(chartDefinition, data, true)
const rows = DashboardListUtils.createTableRows(filteredRowData)
return {
head,
rows,
}
}
const createTimeseriesChart = (
chartDefinition: DashboardVisualisation,
timeseriesData: DashboardDataResponse[],
): ChartData => {
const { columns } = chartDefinition
const { keys, measures } = columns
const unit = measures[0].unit ? measures[0].unit : undefined
const type = chartDefinition.type === DashboardVisualisationType.BAR_TIMESERIES ? 'bar' : 'line'
const groupKey = DatasetHelper.getGroupKey(keys, timeseriesData)
const labelId = groupKey.id as keyof DashboardDataResponse
const timeBlockData = DatasetHelper.groupRowsByTimestamp(timeseriesData)
const labels = timeBlockData.map((d: DashboardDataResponse[]) => d[0].ts.raw as unknown as string)
const datasetCount = timeBlockData[0].length
const datasets: ChartDataset[] = []
for (let index = 0; index < datasetCount; index += 1) {
const data = timeBlockData.map((timeperiod) => {
return +timeperiod[index][measures[1].id].raw
})
const total = data.reduce((a, c) => a + c, 0)
const label = timeBlockData[0][index][labelId].raw as string
datasets.push({
data,
label,
total,
})
}
return {
type: type as unknown as ChartType,
unit,
timeseries: true,
data: {
labels,
datasets,
},
}
}
const createTimeseriesTable = (
chartDefinition: DashboardVisualisation,
timeseriesData: DashboardDataResponse[],
): MoJTable => {
const { columns } = chartDefinition
const { keys, measures } = columns
let flatTimeseriesData = timeseriesData.flat()
const hasMultipleRowsPerTimePeriod = timeseriesData.length > 1
let headerColumns = [...measures]
if (hasMultipleRowsPerTimePeriod) {
const timestampIndex = headerColumns.findIndex((m) => m.id === 'ts')
const timestampCol = headerColumns[timestampIndex]
headerColumns.splice(timestampIndex, 1)
headerColumns = [...keys, ...headerColumns]
headerColumns.unshift(timestampCol)
} else {
flatTimeseriesData = DatasetHelper.filterRowsByDisplayColumns(chartDefinition, flatTimeseriesData)
}
const head = headerColumns.map((column) => {
return { text: column.display }
})
const rows = DashboardListUtils.createTableRows(flatTimeseriesData)
return {
head,
rows,
} as MoJTable
}