@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
JavaScript
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