Skip to content

Commit ef32883

Browse files
committed
feat: simple dual axis plot
1 parent 19b6096 commit ef32883

File tree

2 files changed

+74
-10
lines changed

2 files changed

+74
-10
lines changed

src/modes/dashboard/widgets/RegionLineChartWidget.svelte

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
...DEFAULT_WIDGET_STATE,
44
width: 3,
55
height: 2,
6+
zero: true,
67
};
78
</script>
89

@@ -11,14 +12,20 @@
1112
import WidgetCard, { DEFAULT_WIDGET_STATE } from './WidgetCard.svelte';
1213
import { getContext } from 'svelte';
1314
import DownloadMenu from '../../../components/DownloadMenu.svelte';
14-
import { generateCompareLineSpec, resolveHighlightedDate, patchHighlightTuple } from '../../../specs/lineSpec';
15+
import {
16+
generateCompareLineSpec,
17+
resolveHighlightedDate,
18+
patchHighlightTuple,
19+
generateDualAxisSpec,
20+
} from '../../../specs/lineSpec';
1521
import { formatDateISO, formatWeek } from '../../../formats';
1622
import { WidgetHighlight } from '../highlight';
1723
import isEqual from 'lodash-es/isEqual';
1824
import { createEventDispatcher } from 'svelte';
1925
import { EpiWeek } from '../../../data/EpiWeek';
2026
import { isComparableAcrossRegions } from '../../../data/sensor';
2127
import HistoryLineTooltip from '../../../blocks/HistoryLineTooltip.svelte';
28+
import Toggle from '../../../components/Toggle.svelte';
2229
2330
const dispatch = createEventDispatcher();
2431
@@ -43,6 +50,7 @@
4350
export let highlight = null;
4451
4552
export let initialState = DEFAULT_STATE;
53+
let zoom = !initialState.zero;
4654
4755
$: canCompare = isComparableAcrossRegions(sensor.value);
4856
$: visibleRegions = canCompare ? regions : regions.slice(0, Math.min(2, regions.length));
@@ -51,6 +59,7 @@
5159
$: state = {
5260
...initialState,
5361
...superState,
62+
zero: !zoom,
5463
};
5564
$: {
5665
dispatch('state', { id, state });
@@ -75,17 +84,17 @@
7584
* @param {import('../../../stores/params').SensorParam} sensor
7685
* @param {import('../../../stores/params').RegionParam[]} regions
7786
* @param {import('../../../stores/params').TimeFrame} timeFrame
78-
* @param {{zero: boolean, raw: boolean}} options
87+
* @param {{zero: boolean, canCompare: boolean}} options
7988
*/
80-
function genSpec(sensor, regions, timeFrame) {
89+
function genSpec(sensor, regions, timeFrame, { canCompare, zero }) {
8190
const isWeekly = sensor.value.isWeeklySignal;
8291
/**
8392
* @type {import('../../../specs/lineSpec').LineSpecOptions}
8493
*/
8594
const options = {
8695
initialDate: highlightToDate(highlight) || timeFrame.max,
8796
domain: timeFrame.domain,
88-
zero: false,
97+
zero,
8998
valueFormat: sensor.value.formatSpecifier,
9099
xTitle: sensor.xAxis,
91100
title: [`${sensor.name}`, timeFrame.toNiceString(isWeekly)],
@@ -95,14 +104,14 @@
95104
autoAlignOffset: 60,
96105
paddingTop: 80,
97106
isWeeklySignal: isWeekly,
98-
legend: true,
99107
compareField: 'displayName',
100108
tooltip: true,
101109
};
102-
return generateCompareLineSpec(
103-
regions.map((region) => region.displayName),
104-
options,
105-
);
110+
const names = regions.map((region) => region.displayName);
111+
if (canCompare) {
112+
return generateCompareLineSpec(names, options);
113+
}
114+
return generateDualAxisSpec(names.slice(0, 1), names.slice(1), options);
106115
}
107116
108117
/**
@@ -127,7 +136,7 @@
127136
}-${sensor.isWeeklySignal ? formatWeek(timeFrame.max_week) : formatDateISO(timeFrame.max)}${suffix}`;
128137
}
129138
130-
$: spec = genSpec(sensor, visibleRegions, timeFrame);
139+
$: spec = genSpec(sensor, visibleRegions, timeFrame, { canCompare, zero: !zoom });
131140
$: data = loadData(sensor, visibleRegions, timeFrame);
132141
$: fileName = generateFileName(sensor, visibleRegions, timeFrame);
133142
@@ -215,6 +224,7 @@
215224
tooltipProps={{ sensor }}
216225
/>
217226
<svelte:fragment slot="toolbar">
227+
<Toggle bind:checked={zoom} noPadding>Rescale Y-axis</Toggle>
218228
<DownloadMenu {fileName} {vegaRef} {data} {sensor} advanced={false} />
219229
</svelte:fragment>
220230
</WidgetCard>

src/specs/lineSpec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,60 @@ export function generateCompareLineSpec(
588588
return spec;
589589
}
590590

591+
export function generateDualAxisSpec(
592+
leftAxis: string[],
593+
rightAxis: string[],
594+
{ compareField = 'displayName', ...options }: LineSpecOptions & { compareField?: string; legend?: boolean } = {},
595+
): TopLevelSpec {
596+
const spec = generateLineChartSpec(options);
597+
(spec.padding! as { bottom: number }).bottom = 66;
598+
(spec.padding! as { right: number }).right = (spec.padding! as { left: number }).left;
599+
600+
const leftLayer = spec.layer.splice(0, 2);
601+
leftLayer[0].encoding!.color = {
602+
field: compareField,
603+
type: 'nominal',
604+
scale: {
605+
domain: [...leftAxis, ...rightAxis],
606+
},
607+
legend: {
608+
direction: 'horizontal',
609+
orient: 'bottom',
610+
title: null,
611+
symbolType: 'stroke',
612+
},
613+
};
614+
leftLayer[1].encoding!.color = {
615+
field: compareField,
616+
type: 'nominal',
617+
scale: {
618+
domain: [...leftAxis, ...rightAxis],
619+
},
620+
};
621+
spec.layer.unshift(
622+
{
623+
transform: [
624+
{
625+
filter: `indexof(${JSON.stringify(leftAxis)}, datum.${compareField}) >= 0`,
626+
},
627+
],
628+
layer: leftLayer,
629+
},
630+
{
631+
transform: [
632+
{
633+
filter: `indexof(${JSON.stringify(rightAxis)}, datum.${compareField}) >= 0`,
634+
},
635+
],
636+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
637+
layer: [JSON.parse(JSON.stringify(leftLayer[0]))],
638+
},
639+
);
640+
spec.resolve = { scale: { y: 'independent' } };
641+
642+
return spec;
643+
}
644+
591645
export function generateLineAndBarSpec(options: LineSpecOptions = {}): TopLevelSpec & LayerSpec<Field> {
592646
const spec = generateLineChartSpec({ ...options, stderr: false });
593647
const point = spec.layer[1] as NormalizedUnitSpec;

0 commit comments

Comments
 (0)