165
common/resources/client/charts/data/format-report-data.ts
Executable file
165
common/resources/client/charts/data/format-report-data.ts
Executable file
@@ -0,0 +1,165 @@
|
||||
import {DateFormatter, parseAbsoluteToLocal} from '@internationalized/date';
|
||||
import memoize from 'nano-memoize';
|
||||
import {ChartType} from 'chart.js';
|
||||
import {
|
||||
FormattedDatasetItem,
|
||||
FormattedReportData,
|
||||
} from './formatted-dataset-item';
|
||||
import {
|
||||
DatasetItem,
|
||||
RangedDatasetGranularity,
|
||||
ReportMetric,
|
||||
} from '../../admin/analytics/report-metric';
|
||||
import {shallowEqual} from '../../utils/shallow-equal';
|
||||
|
||||
interface Options {
|
||||
localeCode: string;
|
||||
shareFirstDatasetLabels?: boolean;
|
||||
}
|
||||
|
||||
type FormattedDatasetLabels = Omit<FormattedDatasetItem, 'value'>;
|
||||
|
||||
export function formatReportData(
|
||||
report: ReportMetric<DatasetItem> | undefined,
|
||||
{localeCode = 'en', shareFirstDatasetLabels = true}: Options,
|
||||
): FormattedReportData {
|
||||
if (!report) return {datasets: []};
|
||||
|
||||
const firstDatasetLabels: FormattedDatasetLabels[] = [];
|
||||
|
||||
return {
|
||||
...report,
|
||||
datasets: report.datasets.map((dataset, datasetIndex) => {
|
||||
const data = dataset.data.map((datasetItem, itemIndex) => {
|
||||
let label: FormattedDatasetLabels;
|
||||
// when there are multiple datasets, we'll need to use labels from the first dataset, so charts are
|
||||
// overlapped over one another, otherwise they will be side by side, if labels in all datasets are not identical.
|
||||
if (datasetIndex === 0 || !shareFirstDatasetLabels) {
|
||||
label = generateDatasetLabels(
|
||||
datasetItem,
|
||||
report.granularity,
|
||||
localeCode,
|
||||
);
|
||||
firstDatasetLabels[itemIndex] = label;
|
||||
} else {
|
||||
label = firstDatasetLabels[itemIndex];
|
||||
}
|
||||
return {
|
||||
...label,
|
||||
value: datasetItem.value,
|
||||
};
|
||||
});
|
||||
|
||||
return {...dataset, data};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function generateDatasetLabels<T extends ChartType = ChartType>(
|
||||
datum: DatasetItem,
|
||||
granularity: RangedDatasetGranularity | undefined,
|
||||
locale: string,
|
||||
): FormattedDatasetLabels {
|
||||
if (datum.label) {
|
||||
return {label: datum.label};
|
||||
}
|
||||
|
||||
if (!datum.date) {
|
||||
return {label: ''};
|
||||
}
|
||||
|
||||
return generateTimeLabels(datum, granularity, locale);
|
||||
}
|
||||
|
||||
function generateTimeLabels(
|
||||
{date: isoDate, endDate: isoEndDate}: DatasetItem,
|
||||
granularity: RangedDatasetGranularity | undefined = 'day',
|
||||
locale: string,
|
||||
): Omit<FormattedDatasetItem, 'value'> {
|
||||
const date = parseAbsoluteToLocal(isoDate!).toDate();
|
||||
const endDate = isoEndDate ? parseAbsoluteToLocal(isoEndDate).toDate() : null;
|
||||
|
||||
switch (granularity) {
|
||||
case 'minute':
|
||||
return {
|
||||
label: getFormatter(locale, {
|
||||
second: '2-digit',
|
||||
}).format(date),
|
||||
tooltipTitle: getFormatter(locale, {
|
||||
day: '2-digit',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: '2-digit',
|
||||
}).format(date),
|
||||
};
|
||||
case 'hour':
|
||||
return {
|
||||
label: getFormatter(locale, {
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
}).format(date),
|
||||
tooltipTitle: getFormatter(locale, {
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
}).format(date),
|
||||
};
|
||||
case 'day':
|
||||
return {
|
||||
label: getFormatter(locale, {
|
||||
day: '2-digit',
|
||||
weekday: 'short',
|
||||
}).format(date),
|
||||
tooltipTitle: getFormatter(locale, {
|
||||
day: '2-digit',
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
}).format(date),
|
||||
};
|
||||
case 'week':
|
||||
return {
|
||||
label: getFormatter(locale, {
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
}).format(date),
|
||||
tooltipTitle: getFormatter(locale, {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
}).formatRange(date, endDate as Date),
|
||||
};
|
||||
case 'month':
|
||||
return {
|
||||
label: getFormatter(locale, {
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
}).format(date),
|
||||
tooltipTitle: getFormatter(locale, {
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
}).format(date),
|
||||
};
|
||||
case 'year':
|
||||
return {
|
||||
label: getFormatter(locale, {
|
||||
year: 'numeric',
|
||||
}).format(date),
|
||||
tooltipTitle: getFormatter(locale, {
|
||||
year: 'numeric',
|
||||
}).format(date),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const getFormatter = memoize(
|
||||
(locale, options: Intl.DateTimeFormatOptions) => {
|
||||
return new DateFormatter(locale, options);
|
||||
},
|
||||
{
|
||||
equals: (a, b) => {
|
||||
return shallowEqual(a, b);
|
||||
},
|
||||
callTimeout: undefined as any,
|
||||
},
|
||||
);
|
||||
11
common/resources/client/charts/data/formatted-dataset-item.ts
Executable file
11
common/resources/client/charts/data/formatted-dataset-item.ts
Executable file
@@ -0,0 +1,11 @@
|
||||
import {ReportMetric} from '../../admin/analytics/report-metric';
|
||||
|
||||
export interface FormattedReportData extends Omit<ReportMetric, 'datasets'> {
|
||||
datasets: {label: string; data: FormattedDatasetItem[]}[];
|
||||
}
|
||||
|
||||
export interface FormattedDatasetItem {
|
||||
label: string;
|
||||
value: number;
|
||||
tooltipTitle?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user