UNPKG

@ministryofjustice/hmpps-digital-prison-reporting-frontend

Version:

The Digital Prison Reporting Frontend contains templates and code to help display data effectively in UI applications.

239 lines (236 loc) 9.23 kB
import dayjs from 'dayjs'; import weekOfYear from '../../../_virtual/weekOfYear.js'; import DatasetHelper, { getTimestampColumn, getDateValue, getTimestampMeasure } from '../../utils/Dashboards/VisualisationDatasetHelper.js'; import { mapUnitToSymbol } from '../../utils/Dashboards/VisualisationUnitHelper.js'; import DashboardListUtils from '../_dashboards/dashboard-list/utils.js'; import { DashboardVisualisationType } from '../_dashboards/dashboard-visualisation/types.js'; import { Granularity } from '../_inputs/granular-date-range/types.js'; import { BarTimeseriesChart } from './chart/bar-timeseries/BarTimeseriesChart.js'; import { BarChart } from './chart/bar/BarChart.js'; import { DoughnutChart } from './chart/doughnut/DoughnutChart.js'; import { HeatmapChart } from './chart/heatmap/HeatmapChart.js'; import { LineTimeseriesChart } from './chart/line-timeseries/LineTimeseriesChart.js'; import { LineChart } from './chart/line/LineChart.js'; dayjs.extend(weekOfYear); const createChart = (chartDefinition, rawData, type) => { let table; let chart; let details; const { dataSetRows, snapshotData } = getDataForSnapshotCharts(chartDefinition, rawData); if (dataSetRows.length) { switch (type) { case DashboardVisualisationType.BAR: chart = new BarChart().withDefinition(chartDefinition).withData(snapshotData).build(); break; case DashboardVisualisationType.DONUT: chart = new DoughnutChart().withDefinition(chartDefinition).withData(snapshotData).build(); break; case DashboardVisualisationType.LINE: chart = new LineChart().withDefinition(chartDefinition).withData(snapshotData).build(); break; } table = createSnapshotTable(chartDefinition, dataSetRows); details = getChartDetails(chartDefinition, dataSetRows); } return { details, table, chart, }; }; const createTimeseriesCharts = (chartDefinition, rawData, type, query, partialDate) => { let table; let chart; let details; let granularity = Granularity.DAILY; if (query) { Object.keys(query).forEach(key => { if (key.includes('granularity')) { granularity = query[key]; } }); } const { latestData, dataSetRows, timeseriesData } = getDataForTimeseriesCharts(chartDefinition, rawData); if (dataSetRows.length) { switch (type) { case DashboardVisualisationType.MATRIX_TIMESERIES: chart = new HeatmapChart() .withDefinition(chartDefinition) .withGranularity(granularity) .withData(timeseriesData) .build(); break; case DashboardVisualisationType.LINE_TIMESERIES: chart = new LineTimeseriesChart() .withDefinition(chartDefinition) .withData(timeseriesData) .withPartialDate(partialDate) .build(); break; case DashboardVisualisationType.BAR_TIMESERIES: chart = new BarTimeseriesChart() .withDefinition(chartDefinition) .withData(timeseriesData) .withPartialDate(partialDate) .build(); break; } table = createTimeseriesTable(chartDefinition, timeseriesData); details = getChartDetails(chartDefinition, latestData, true); } return { details, table, chart, }; }; const getDataForSnapshotCharts = (chartDefinition, rawData) => { const { columns } = chartDefinition; const dateColumn = DatasetHelper.getTimestampColumn(columns); const data = DatasetHelper.getLastestDataset(rawData, dateColumn); const dataSetRows = DatasetHelper.getDatasetRows(chartDefinition, data); const snapshotData = DatasetHelper.filterRowsByDisplayColumns(chartDefinition, dataSetRows, true); return { dataSetRows, snapshotData, }; }; const getDataForTimeseriesCharts = (chartDefinition, rawData) => { const dateMeasure = DatasetHelper.getTimestampMeasure(chartDefinition.columns.measures); const latestData = DatasetHelper.getLastestDataset(rawData, dateMeasure); const dataSetRows = DatasetHelper.getDatasetRows(chartDefinition, rawData); const timeseriesData = DatasetHelper.filterRowsByDisplayColumns(chartDefinition, dataSetRows, true); return { latestData, dataSetRows, timeseriesData, }; }; const getChartDetails = (chartDefinition, data, timeseries = false) => { const { columns } = chartDefinition; const meta = []; const headlines = createHeadlines(chartDefinition, data, timeseries); const dateColumn = getTimestampColumn(columns); const dateData = getDateValue(data, dateColumn); if (dateData) { const { value } = dateData; meta.push({ label: 'Values for:', value, }); } return { meta, headlines, }; }; const createHeadlines = (chartDefinition, data, timeseries = false) => { const headlines = []; const { columns } = chartDefinition; const { measures } = columns; const isListChart = !!measures.find(col => col.axis); let headline; let headlineColumn; let value; let label = ''; if (timeseries) { headlineColumn = measures.find(col => col.type !== 'timestamp'); if (headlineColumn) { const { id } = headlineColumn; const rawValue = data[0][id]?.raw; const dateColumn = getTimestampMeasure(measures); const dateData = getDateValue(data, dateColumn); if (dateData) { label = dateData.value; } const numericValue = Number(rawValue); value = Number.isNaN(numericValue) ? undefined : numericValue; if (value) { headline = { label, value, }; } } } else { headlineColumn = !isListChart ? measures[0] : measures.find(col => col.axis && col.axis === 'y'); if (headlineColumn) { const display = headlineColumn.display?.toLowerCase(); label = display ? `Total ${display}` : 'Total'; value = data.reduce((acc, d) => { if (headlineColumn) { const { id } = headlineColumn; const { raw } = d[id]; if (raw) { return acc + Number(raw); } } return acc; }, 0); headline = { label, value, }; } } if (headline) headlines.push(headline); return headlines; }; const mapTableHead = (headerColumns) => { return headerColumns.map(column => { const unitSymbol = mapUnitToSymbol(column.unit); const headText = unitSymbol ? `${column.display} (${unitSymbol})` : column.display; return { text: headText || '' }; }); }; const createSnapshotTable = (chartDefinition, data) => { const { columns } = chartDefinition; const { measures } = columns; const keys = columns.keys || []; const head = mapTableHead([...keys, ...measures]); const filteredRowData = DatasetHelper.filterRowsByDisplayColumns(chartDefinition, data, true); const rows = DashboardListUtils.createTableRows(filteredRowData); return { head, rows, }; }; /** * Creates the a table representation for a timeseries chart * - ensures the Date column is always at index 0 when there are multiple rows * * @param {components['schemas']['DashboardVisualisationDefinition']} chartDefinition * @param {DashboardDataResponse[]} timeseriesData * @return {*} {MoJTable} */ const createTimeseriesTable = (chartDefinition, timeseriesData) => { const { columns } = chartDefinition; const { keys, measures = [] } = columns; const safeKeys = keys ?? []; let flatTimeseriesData = timeseriesData.flat(); let tableColumns; if (timeseriesData.length > 1) { const tsMeasures = measures.filter(m => m.type === 'timestamp'); if (tsMeasures.length !== 1) { throw new Error('Multi-timeseries tables require exactly one date measure'); } const [tsColumn] = tsMeasures; const valueColumns = measures.filter(m => m.id !== tsColumn.id); tableColumns = [tsColumn, ...safeKeys, ...valueColumns]; } else { flatTimeseriesData = DatasetHelper.filterRowsByDisplayColumns(chartDefinition, flatTimeseriesData); tableColumns = measures; } const head = mapTableHead(tableColumns); const rows = DashboardListUtils.createTableRows(flatTimeseriesData, tableColumns); return { head, rows }; }; var ChartUtils = { createChart, createTimeseriesCharts, }; export { createChart, createTimeseriesCharts, ChartUtils as default }; //# sourceMappingURL=utils.js.map