From 2a3b07d3d9d9649df54d45f261600ea26a784ac3 Mon Sep 17 00:00:00 2001 From: Ian Reynolds Date: Mon, 30 Oct 2023 09:17:08 -0400 Subject: [PATCH] feature: Unified `TimeSeriesChart` component (#879) * dev: Update storybook * feature: Unified TimeSeriesChart * move some stuff from other PR * I accidentally a --- .../TimeSeriesChart.stories.tsx | 261 ++++++++++++++++ .../TimeSeriesChart/TimeSeriesChart.tsx | 289 ++++++++++++++++++ .../charts/TimeSeriesChart/defaults.ts | 45 +++ .../charts/TimeSeriesChart/helpers.ts | 96 ++++++ .../charts/TimeSeriesChart/index.ts | 2 + .../charts/TimeSeriesChart/types.ts | 87 ++++++ common/constants/colors.ts | 1 + 7 files changed, 781 insertions(+) create mode 100644 common/components/charts/TimeSeriesChart/TimeSeriesChart.stories.tsx create mode 100644 common/components/charts/TimeSeriesChart/TimeSeriesChart.tsx create mode 100644 common/components/charts/TimeSeriesChart/defaults.ts create mode 100644 common/components/charts/TimeSeriesChart/helpers.ts create mode 100644 common/components/charts/TimeSeriesChart/index.ts create mode 100644 common/components/charts/TimeSeriesChart/types.ts diff --git a/common/components/charts/TimeSeriesChart/TimeSeriesChart.stories.tsx b/common/components/charts/TimeSeriesChart/TimeSeriesChart.stories.tsx new file mode 100644 index 000000000..97da81d8b --- /dev/null +++ b/common/components/charts/TimeSeriesChart/TimeSeriesChart.stories.tsx @@ -0,0 +1,261 @@ +import React from 'react'; + +import { CHART_COLORS, COLORS } from '../../../constants/colors'; +import { TimeSeriesChart } from './TimeSeriesChart'; + +export default { + title: 'TimeSeriesChart', + component: TimeSeriesChart, +}; + +const simpleDataByWeek = [ + { + label: 'Things', + data: [ + { date: '2023-01-01', value: 115 }, + { date: '2023-01-08', value: 100 }, + { date: '2023-01-15', value: 85 }, + { date: '2023-01-22', value: 70 }, + { date: '2023-01-29', value: 75 }, + { date: '2023-02-05', value: 90 }, + { date: '2023-02-12', value: 105 }, + { date: '2023-02-19', value: 115 }, + { date: '2023-02-26', value: 120 }, + { date: '2023-03-05', value: 115 }, + { date: '2023-03-12', value: 100 }, + { date: '2023-03-19', value: 85 }, + { date: '2023-03-26', value: 70 }, + { date: '2023-04-02', value: 75 }, + { date: '2023-04-09', value: 90 }, + { date: '2023-04-16', value: 105 }, + { date: '2023-04-23', value: 115 }, + { date: '2023-04-30', value: 120 }, + ], + }, + { + label: 'Stuff', + data: [ + { date: '2023-01-01', value: 125 }, + { date: '2023-01-08', value: 115 }, + { date: '2023-01-15', value: 105 }, + { date: '2023-01-22', value: 100 }, + { date: '2023-01-29', value: 105 }, + { date: '2023-02-05', value: 120 }, + { date: '2023-02-12', value: 130 }, + { date: '2023-02-19', value: 135 }, + { date: '2023-02-26', value: 140 }, + { date: '2023-03-05', value: 135 }, + { date: '2023-03-12', value: 125 }, + { date: '2023-03-19', value: 115 }, + { date: '2023-03-26', value: 110 }, + { date: '2023-04-02', value: 115 }, + { date: '2023-04-09', value: 125 }, + { date: '2023-04-16', value: 135 }, + { date: '2023-04-23', value: 140 }, + { date: '2023-04-30', value: 145 }, + ], + style: { + fillPattern: 'striped' as const, + }, + }, +]; + +const simpleDataByDay = [ + { + label: '25th percentile', + data: [ + { date: '2023-10-01', value: 810.0 / 60 }, + { date: '2023-10-02', value: 490.0 / 60 }, + { date: '2023-10-03', value: 511.5 / 60 }, + { date: '2023-10-04', value: 478.8 / 60 }, + { date: '2023-10-05', value: 505.5 / 60 }, + { date: '2023-10-06', value: 492.0 / 60 }, + { date: '2023-10-07', value: 801.3 / 60 }, + { date: '2023-10-08', value: 803.0 / 60 }, + { date: '2023-10-09', value: 607.0 / 60 }, + { date: '2023-10-10', value: 496.0 / 60 }, + { date: '2023-10-11', value: 490.3 / 60 }, + { date: '2023-10-12', value: 509.0 / 60 }, + { date: '2023-10-13', value: 484.8 / 60 }, + { date: '2023-10-14', value: 764.3 / 60 }, + { date: '2023-10-15', value: 778.0 / 60 }, + { date: '2023-10-16', value: 628.0 / 60 }, + { date: '2023-10-17', value: 620.8 / 60 }, + { date: '2023-10-18', value: 602.3 / 60 }, + { date: '2023-10-19', value: 628.8 / 60 }, + ], + style: { + tension: 0.4, + fillColor: '#ffffff', + }, + }, + { + label: '50th percentile', + data: [ + { date: '2023-10-01', value: 841.0 / 60 }, + { date: '2023-10-02', value: 550.5 / 60 }, + { date: '2023-10-03', value: 547.5 / 60 }, + { date: '2023-10-04', value: 550.0 / 60 }, + { date: '2023-10-05', value: 547.5 / 60 }, + { date: '2023-10-06', value: 538.0 / 60 }, + { date: '2023-10-07', value: 843.5 / 60 }, + { date: '2023-10-08', value: 841.0 / 60 }, + { date: '2023-10-09', value: 655.5 / 60 }, + { date: '2023-10-10', value: 560.0 / 60 }, + { date: '2023-10-11', value: 544.0 / 60 }, + { date: '2023-10-12', value: 561.0 / 60 }, + { date: '2023-10-13', value: 548.0 / 60 }, + { date: '2023-10-14', value: 819.0 / 60 }, + { date: '2023-10-15', value: 815.0 / 60 }, + { date: '2023-10-16', value: 679.0 / 60 }, + { date: '2023-10-17', value: 667.0 / 60 }, + { date: '2023-10-18', value: 665.0 / 60 }, + { date: '2023-10-19', value: 653.0 / 60 }, + ], + style: { + tension: 0.1, + pointRadius: 3, + color: '#bbb', + pointColor: 'black', + }, + }, + { + label: '75th percentile', + data: [ + { date: '2023-10-01', value: 885.3 / 60 }, + { date: '2023-10-02', value: 618.3 / 60 }, + { date: '2023-10-03', value: 611.5 / 60 }, + { date: '2023-10-04', value: 632.3 / 60 }, + { date: '2023-10-05', value: 606.3 / 60 }, + { date: '2023-10-06', value: 586.0 / 60 }, + { date: '2023-10-07', value: 885.8 / 60 }, + { date: '2023-10-08', value: 904.8 / 60 }, + { date: '2023-10-09', value: 701.0 / 60 }, + { date: '2023-10-10', value: 634.0 / 60 }, + { date: '2023-10-11', value: 588.5 / 60 }, + { date: '2023-10-12', value: 618.0 / 60 }, + { date: '2023-10-13', value: 614.0 / 60 }, + { date: '2023-10-14', value: 856.8 / 60 }, + { date: '2023-10-15', value: 858.0 / 60 }, + { date: '2023-10-16', value: 727.0 / 60 }, + { date: '2023-10-17', value: 716.8 / 60 }, + { date: '2023-10-18', value: 714.3 / 60 }, + { date: '2023-10-19', value: 694.3 / 60 }, + ], + style: { + tension: 0.4, + fillColor: CHART_COLORS.FILL, + }, + }, +]; + +const simpleDataByTime = [ + { + label: 'Travel Times', + data: [ + { time: '2023-10-19T08:15:32.000Z', value: 6.731 }, + { time: '2023-10-19T09:23:17.000Z', value: 7.942 }, + { time: '2023-10-19T10:46:58.000Z', value: 5.367 }, + { time: '2023-10-19T11:33:45.000Z', value: 5.812 }, + { time: '2023-10-19T13:04:20.000Z', value: 6.238 }, + { time: '2023-10-19T14:29:57.000Z', value: 5.451 }, + { time: '2023-10-19T15:18:12.000Z', value: 7.123 }, + { time: '2023-10-19T16:57:09.000Z', value: 5.891 }, + { time: '2023-10-19T17:40:38.000Z', value: 6.802 }, + { time: '2023-10-19T18:52:46.000Z', value: 7.512 }, + { time: '2023-10-19T19:27:14.000Z', value: 6.229 }, + { time: '2023-10-19T20:38:05.000Z', value: 5.284 }, + { time: '2023-10-19T21:17:33.000Z', value: 5.991 }, + { time: '2023-10-19T22:45:29.000Z', value: 5.482 }, + { time: '2023-10-19T23:09:41.000Z', value: 6.927 }, + { time: '2023-10-20T00:31:12.000Z', value: 6.148 }, + { time: '2023-10-20T01:21:29.000Z', value: 5.687 }, + { time: '2023-10-20T02:53:07.000Z', value: 5.316 }, + { time: '2023-10-20T03:37:28.000Z', value: 12.431 }, + { time: '2023-10-20T04:59:16.000Z', value: 5.723 }, + { time: '2023-10-20T05:12:45.000Z', value: 6.543 }, + { time: '2023-10-20T06:47:02.000Z', value: 7.654 }, + { time: '2023-10-20T07:38:20.000Z', value: 6.891 }, + { time: '2023-10-20T08:57:38.000Z', value: 7.912 }, + { time: '2023-10-20T09:46:01.000Z', value: 6.316 }, + { time: '2023-10-20T10:27:03.000Z', value: 6.752 }, + { time: '2023-10-20T11:58:47.000Z', value: 6.921 }, + { time: '2023-10-20T12:19:50.000Z', value: 5.813 }, + { time: '2023-10-20T13:47:37.000Z', value: 5.267 }, + { time: '2023-10-20T14:36:53.000Z', value: 16.012 }, + { time: '2023-10-20T15:57:08.000Z', value: 7.123 }, + { time: '2023-10-20T16:34:39.000Z', value: 5.891 }, + { time: '2023-10-20T17:10:54.000Z', value: 6.802 }, + { time: '2023-10-20T18:21:06.000Z', value: 7.512 }, + { time: '2023-10-20T19:43:45.000Z', value: 6.229 }, + { time: '2023-10-20T20:15:20.000Z', value: 5.284 }, + { time: '2023-10-20T21:33:46.000Z', value: 5.991 }, + { time: '2023-10-20T22:19:15.000Z', value: 5.482 }, + { time: '2023-10-20T23:54:23.000Z', value: 6.927 }, + { time: '2023-10-21T00:21:01.000Z', value: 6.148 }, + { time: '2023-10-21T01:35:28.000Z', value: 5.687 }, + { time: '2023-10-21T02:07:14.000Z', value: 5.316 }, + { time: '2023-10-21T03:09:19.000Z', value: 5.894 }, + { time: '2023-10-21T04:27:04.000Z', value: 5.723 }, + { time: '2023-10-21T05:05:59.000Z', value: 6.543 }, + { time: '2023-10-21T06:43:57.000Z', value: 7.654 }, + ], + style: { + color: '#ccc', + pointRadius: 4, + pointColor: { + byPoint: (point) => { + const { value } = point; + if (value > 10) { + return CHART_COLORS.RED; + } + if (value > 7) { + return CHART_COLORS.YELLOW; + } + return CHART_COLORS.GREEN; + }, + }, + }, + }, +]; + +export const ByWeek = () => { + return ( + + ); +}; + +export const ByDay = () => { + return ( + + `${label}: ${Math.round(10 * value) / 10} min`, + }} + /> + ); +}; + +export const ByTime = () => { + return ( + + ); +}; diff --git a/common/components/charts/TimeSeriesChart/TimeSeriesChart.tsx b/common/components/charts/TimeSeriesChart/TimeSeriesChart.tsx new file mode 100644 index 000000000..575a077d2 --- /dev/null +++ b/common/components/charts/TimeSeriesChart/TimeSeriesChart.tsx @@ -0,0 +1,289 @@ +/* eslint-disable import/no-unused-modules */ +import React, { useMemo } from 'react'; +import { Line as LineChart } from 'react-chartjs-2'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + TimeScale, + PointElement, + LineElement, + Filler, + Title, + Tooltip, + Legend, + BarElement, + BarController, + LineController, +} from 'chart.js'; +import Annotation from 'chartjs-plugin-annotation'; +import ChartDataLabels from 'chartjs-plugin-datalabels'; +import ChartjsPluginWatermark from 'chartjs-plugin-watermark'; +import 'chartjs-adapter-date-fns'; +import type { ChartData } from 'chart.js'; + +import { enUS } from 'date-fns/locale'; +import { useBreakpoint } from '../../../hooks/useBreakpoint'; +import { ChartBorder } from '../ChartBorder'; +import { ChartDiv } from '../ChartDiv'; +import { CHART_COLORS, COLORS } from '../../../constants/colors'; + +import type { + AppliedDisplayStyle, + Benchmark, + Block, + DataPoint, + Dataset, + DisplayStyle, + GridOptions, + LegendOptions, + ProvidedTimeAxis, + ResolvedTimeAxis, + ValueAxis, +} from './types'; +import { + mergeAndApplyStyles, + getLabelsForData, + getFillProps, + getGranularityForAgg, + getDefaultTimeAxis, +} from './helpers'; + +ChartJS.register( + BarController, + BarElement, + LineController, + CategoryScale, + TimeScale, + LinearScale, + PointElement, + LineElement, + Annotation, + ChartDataLabels, + Filler, + Title, + Tooltip, + Legend +); + +interface Props { + data: Data; + benchmarks?: Benchmark[]; + blocks?: Block[]; + legend?: LegendOptions; + grid?: GridOptions; + style?: Partial>; + timeAxis?: Partial; + valueAxis: ValueAxis; +} + +export const TimeSeriesChart = (props: Props) => { + const { + data, + style: globalStyle, + timeAxis: providedTimeAxis = {}, + valueAxis, + benchmarks = [], + blocks = [], + grid = { zIndex: 0 }, + legend = { visible: true }, + } = props; + + const isMobile = !useBreakpoint('md'); + + const appliedStyles = useMemo(() => { + const styles: Map> = new Map(); + data.forEach((dataset) => { + const appliedStyle = mergeAndApplyStyles([globalStyle, dataset.style], dataset.data); + styles.set(dataset, appliedStyle); + }); + return styles; + }, [data, globalStyle]); + + const chartJsData: ChartData<'line', number[], string> = useMemo(() => { + const labels = getLabelsForData(data); + const datasets = data.map((dataset) => { + const style = appliedStyles.get(dataset)!; + const data = dataset.data.map((point) => point.value); + const { color, width, pointRadius, pointHitRadius, stepped, tension } = style; + return { + label: dataset.label, + borderColor: color, + borderWidth: width, + pointRadius: pointRadius, + pointHitRadius: pointHitRadius, + stepped, + tension, + data, + pointBorderWidth: 0, + pointHoverBorderWidth: 0, + ...getFillProps(style), + }; + }); + const benchmarkDummyDatasets = benchmarks.map((benchmark) => { + const color = benchmark.color || CHART_COLORS.ANNOTATIONS; + return { + label: benchmark.label, + backgroundColor: 'transparent', + borderColor: color, + data: null as unknown as number[], + }; + }); + return { labels, datasets: [...datasets, ...benchmarkDummyDatasets] }; + }, [data, benchmarks, appliedStyles]); + + const timeAxis = useMemo((): ResolvedTimeAxis => { + const providedGranularity = + 'granularity' in providedTimeAxis + ? providedTimeAxis.granularity + : 'agg' in providedTimeAxis && providedTimeAxis.agg + ? getGranularityForAgg(providedTimeAxis.agg) + : null; + return { ...getDefaultTimeAxis(providedGranularity ?? null), ...providedTimeAxis }; + }, [providedTimeAxis]); + + const scales = useMemo(() => { + const unit = timeAxis.axisUnit ?? timeAxis.granularity; + const time = unit !== 'time' && { + unit, + tooltipFormat: timeAxis.tooltipFormat ?? timeAxis.format, + displayFormats: { + [unit]: timeAxis.format, + }, + }; + + return { + x: { + display: true, + type: 'time' as const, + adapters: { + date: { + locale: enUS, + }, + }, + ticks: { + color: COLORS.design.subtitleGrey, + }, + grid: { + z: grid.zIndex, + }, + ...(time ? { time } : {}), + }, + y: { + min: valueAxis.min, + max: valueAxis.max, + display: true, + title: { + display: true, + text: valueAxis.label, + color: COLORS.design.subtitleGrey, + }, + ticks: { + color: COLORS.design.subtitleGrey, + }, + grid: { + z: grid.zIndex, + }, + }, + }; + }, [timeAxis, valueAxis, grid.zIndex]); + + const chartJsOptions = useMemo(() => { + const benchmarkAnnotations = benchmarks.map((benchmark, index) => { + const datasetIndex = data.length + index; + return { + type: 'line' as const, + yMin: benchmark.value, + yMax: benchmark.value, + borderColor: benchmark.color || CHART_COLORS.ANNOTATIONS, + display: (ctx) => ctx.chart.isDatasetVisible(datasetIndex), + borderWidth: 2, + }; + }); + + const blockAnnotations = blocks.map((block) => { + const { from, to, fillColor, textColor, label } = block; + return { + type: 'box' as const, + xMin: from, + xMax: to, + backgroundColor: fillColor || CHART_COLORS.BLOCKS_SOLID, + borderWidth: 0, + label: { + content: label || 'No data', + rotation: -90, + color: textColor || 'white', + display: true, + }, + }; + }); + + return { + scales, + responsive: true, + maintainAspectRatio: false, + interaction: { + intersect: false, + }, + plugins: { + datalabels: { + display: false, + }, + tooltip: { + mode: 'index' as const, + position: 'nearest' as const, + callbacks: { + title: (datasets) => { + const { label } = datasets[0]; + if (timeAxis.granularity === 'week') { + return `Week of ${label}`; + } + return label; + }, + label: (context) => { + const dataset = data[context.datasetIndex]; + const style = appliedStyles.get(dataset)!; + const point = dataset.data[context.dataIndex]; + if (style.tooltipLabel) { + return style.tooltipLabel(point, context); + } + return `${dataset.label}: ${point.value}`; + }, + }, + }, + legend: { + display: legend.visible, + position: 'bottom' as const, + labels: { + boxWidth: 15, + }, + }, + annotation: { + annotations: [...benchmarkAnnotations, ...blockAnnotations], + }, + }, + }; + }, [scales, data, benchmarks, blocks, timeAxis.granularity, legend.visible, appliedStyles]); + + const chartJsPlugins = useMemo(() => { + return [Annotation, ChartjsPluginWatermark]; + }, []); + + const chart = useMemo(() => { + return ( + + ); + }, [isMobile, chartJsData, chartJsOptions, chartJsPlugins]); + + return ( + + {chart} + + ); +}; diff --git a/common/components/charts/TimeSeriesChart/defaults.ts b/common/components/charts/TimeSeriesChart/defaults.ts new file mode 100644 index 000000000..0f51f540a --- /dev/null +++ b/common/components/charts/TimeSeriesChart/defaults.ts @@ -0,0 +1,45 @@ +import type { DataPoint, DisplayStyle, ResolvedTimeAxis } from './types'; + +export const defaultStyle: DisplayStyle = { + color: '#ddd', + width: 2, + fillColor: null, + fillPattern: 'slightly-transparent', + pointColor: null, + pointRadius: 0, + pointHitRadius: 0, + stepped: false, + tension: 0, + tooltipLabel: (point, context) => { + return `${context.dataset.label}: ${point.value.toString()}`; + }, +}; + +export const defaultTimeAxis: ResolvedTimeAxis = { + granularity: 'time', + label: 'Time', + format: 'h a', +}; + +export const defaultDayAxis: ResolvedTimeAxis = { + granularity: 'day', + label: 'Date', + format: 'MMM d', + tooltipFormat: 'MMM d, yyyy', +}; + +export const defaultWeekAxis: ResolvedTimeAxis = { + granularity: 'week', + axisUnit: 'month', + label: 'Date', + format: 'MMM', + tooltipFormat: 'MMM d, yyyy', +}; + +export const defaultMonthAxis: ResolvedTimeAxis = { + granularity: 'month', + axisUnit: 'month', + label: 'Month', + format: 'yyyy', + tooltipFormat: 'MMM yyyy', +}; diff --git a/common/components/charts/TimeSeriesChart/helpers.ts b/common/components/charts/TimeSeriesChart/helpers.ts new file mode 100644 index 000000000..e119339fc --- /dev/null +++ b/common/components/charts/TimeSeriesChart/helpers.ts @@ -0,0 +1,96 @@ +import pattern from 'patternomaly'; +import { hexWithAlpha } from '../../../utils/general'; +import type { AggType } from '../../../../modules/speed/constants/speeds'; +import type { + Granularity, + Dataset, + DisplayStyle, + AppliedDisplayStyle, + DataPoint, + ResolvedTimeAxis, +} from './types'; +import { + defaultTimeAxis, + defaultDayAxis, + defaultMonthAxis, + defaultStyle, + defaultWeekAxis, +} from './defaults'; + +export const getDefaultTimeAxis = ( + unit: null | Unit +): ResolvedTimeAxis => { + if (unit === 'time') { + return defaultTimeAxis; + } + if (unit === 'day') { + return defaultDayAxis; + } + if (unit === 'week') { + return defaultWeekAxis; + } + if (unit === 'month') { + return defaultMonthAxis; + } + return defaultTimeAxis; +}; + +export const getGranularityForAgg = (agg: AggType): Granularity => { + if (agg === 'daily') { + return 'day'; + } + if (agg === 'weekly') { + return 'week'; + } + return 'month'; +}; + +export const getLabelsForData = (data: Data) => { + const allPoints = data + .map((dataset) => dataset.data.map((point) => ('date' in point ? point.date : point.time))) + .flat(); + const uniqueAndSortedPoints = [...new Set(allPoints)].sort(); + return uniqueAndSortedPoints; +}; + +export const mergeAndApplyStyles = < + Point extends DataPoint, + Styles extends Partial>, +>( + styles: (undefined | Styles)[], + data: Point[] +): AppliedDisplayStyle => { + return styles.reduce((merged, style) => { + const entries = Object.entries(style ?? {}).map(([key, value]) => { + if (typeof value === 'object' && value && 'byPoint' in value) { + const dataByPoint = data.map((point) => value.byPoint(point)); + return [key, dataByPoint]; + } + return [key, value]; + }); + const appliedStyle: Partial> = Object.fromEntries(entries); + return { + ...merged, + ...appliedStyle, + }; + }, defaultStyle as AppliedDisplayStyle); +}; + +export const getFillProps = (style: AppliedDisplayStyle) => { + const { fillColor, fillPattern, fill, color: lineColor, pointColor } = style; + const resolvedFillColor = fillColor ?? (fill ? lineColor : null); + if (resolvedFillColor) { + const backgroundColor = + fillPattern === 'solid' + ? resolvedFillColor + : fillPattern === 'slightly-transparent' + ? hexWithAlpha(resolvedFillColor, 0.8) + : pattern.draw('diagonal', 'transparent', resolvedFillColor, 5); + return { + fill: true, + backgroundColor, + pointBackgroundColor: pointColor || backgroundColor, + }; + } + return { pointBackgroundColor: pointColor || lineColor }; +}; diff --git a/common/components/charts/TimeSeriesChart/index.ts b/common/components/charts/TimeSeriesChart/index.ts new file mode 100644 index 000000000..b3ad17f84 --- /dev/null +++ b/common/components/charts/TimeSeriesChart/index.ts @@ -0,0 +1,2 @@ +export { TimeSeriesChart } from './TimeSeriesChart'; +export * from './types'; diff --git a/common/components/charts/TimeSeriesChart/types.ts b/common/components/charts/TimeSeriesChart/types.ts new file mode 100644 index 000000000..ad1835d07 --- /dev/null +++ b/common/components/charts/TimeSeriesChart/types.ts @@ -0,0 +1,87 @@ +import type { ChartDataset } from 'chart.js'; +import type { AggType } from '../../../../modules/speed/constants/speeds'; + +export type DataPoint = ({ date: string } | { time: string }) & { value: number }; + +type ResolvedFromDataPoint< + Applied extends boolean, + Point extends DataPoint, + ReturnType = string, +> = Applied extends true ? ReturnType[] : { byPoint: (point: Point) => ReturnType }; + +type FillPattern = 'solid' | 'slightly-transparent' | 'striped'; + +export type DisplayStyle = { + color: string; + width: number; + fill?: boolean; // Use to fill with lineColor by default + fillColor: null | string; + fillPattern: FillPattern; + pointColor: null | string | ResolvedFromDataPoint; + pointRadius: number; + pointHitRadius: number; + tooltipLabel: ( + point: Point, + context: { + datasetIndex: number; + dataIndex: number; + dataset: ChartDataset; + } + ) => string; + stepped: boolean; + tension: number; +}; + +export type AppliedDisplayStyle = DisplayStyle; + +export type Granularity = 'time' | 'day' | 'week' | 'month'; +export type AxisUnit = 'day' | 'month'; + +export type ProvidedTimeAxis = { + label: string; + format?: string; + tooltipFormat?: string; + axisUnit?: AxisUnit; +} & ({ granularity: Granularity } | { agg: AggType }); + +export type ResolvedTimeAxis = { + granularity: Granularity; + label: string; + axisUnit?: AxisUnit; + format?: string; + tooltipFormat?: string; +}; + +export type ValueAxis = { + label: string; + min?: number; + max?: number; +}; + +export type LegendOptions = { + visible: boolean; +}; + +export type GridOptions = { + zIndex?: number; +}; + +export type Dataset = { + data: Point[]; + label: string; + style?: Partial>; +}; + +export type Benchmark = { + label: string; + value: number; + color?: string; +}; + +export type Block = { + from: string; + to: string; + label?: string; + fillColor?: string; + textColor?: string; +}; diff --git a/common/constants/colors.ts b/common/constants/colors.ts index 0711c3da6..e54ac0fcf 100644 --- a/common/constants/colors.ts +++ b/common/constants/colors.ts @@ -39,6 +39,7 @@ export const CHART_COLORS = { DARK_LINE: '#303030a0', ANNOTATIONS: hexWithAlpha('#202020', 0.4), BLOCKS: hexWithAlpha('#202020', 0.2), + BLOCKS_SOLID: '#d2d2d2', }; export const LINE_COLORS = {