Skip to content

Commit 8438c0f

Browse files
committed
Sort scenarios in comparisons consistently wherever displayed
1 parent 5686c84 commit 8438c0f

7 files changed

Lines changed: 113 additions & 87 deletions

File tree

components/Charts/Compare/CompareCostsChart.client.vue

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { costAsPercentOfGdp } from "@/components/utils/formatters";
2222
import { CostBasis } from "@/types/unitTypes";
2323
import { debounce } from "perfect-debounce";
2424
import { diffAgainstBaseline } from "~/components/utils/comparisons";
25+
import useSortedScenarios from "~/composables/useSortedScenarios";
2526
2627
const props = defineProps<{
2728
diffing: boolean
@@ -32,11 +33,12 @@ let chart: Highcharts.Chart;
3233
const seriesSummary = ref<Highcharts.SeriesColumnOptions[]>([]); // This only exists for testing purposes
3334
const chartContainer = ref<HTMLElement | null>(null);
3435
const chartParentEl = computed(() => chartContainer.value?.parentElement);
35-
const scenarios = computed(() => {
36-
return props.diffing
37-
? appStore.currentComparison.scenarios.filter(s => s.runId !== appStore.baselineScenario?.runId)
38-
: appStore.currentComparison.scenarios;
39-
});
36+
37+
const scenarios = computed(() => props.diffing
38+
? appStore.currentComparison.scenarios.filter(s => s.runId !== appStore.baselineScenario?.runId)
39+
: appStore.currentComparison.scenarios);
40+
const { sortedScenarios } = useSortedScenarios(scenarios);
41+
4042
const costBasis = computed(() => appStore.preferences.costBasis);
4143
const chartTitle = computed(() => {
4244
const firstScenarioTimeSeries = appStore.currentComparison.scenarios[0].result?.data?.time_series;
@@ -54,15 +56,15 @@ const chartTitle = computed(() => {
5456
// For example, one series should comprise each scenario's GDP.
5557
const getSeries = (): Highcharts.SeriesColumnOptions[] => {
5658
// Take the first scenario's costs as an example to find out what the second-level breakdowns are.
57-
const secondLevelCostIds = scenarios.value[0].result.data?.costs[0].children?.map(c => c.id) || [];
59+
const secondLevelCostIds = sortedScenarios.value[0].result.data?.costs[0].children?.map(c => c.id) || [];
5860
const allSeries = secondLevelCostIds?.map((costId, index) => {
5961
return {
6062
type: "column" as Highcharts.SeriesColumnOptions["type"],
6163
name: appStore.getCostLabel(costId),
6264
borderWidth: 1,
6365
borderColor: costsChartPalette[index].rgb,
6466
zIndex: secondLevelCostIds.length - index, // Ensure that stack segments are in front of each other from top to bottom.
65-
data: scenarios.value.map((scenario) => {
67+
data: sortedScenarios.value.map((scenario) => {
6668
const subCost = appStore.getScenarioCostById(scenario, costId)!;
6769
const yUSD = props.diffing ? diffAgainstBaseline(subCost, USD_METRIC) : getValueFromCost(subCost, USD_METRIC);
6870
// yGdpPercent is calculated here since the national GDP may vary by scenario if the axis is 'country'.
@@ -136,7 +138,7 @@ const chartInitialOptions = () => {
136138
},
137139
},
138140
xAxis: {
139-
categories: scenarios.value.map(s => appStore.getScenarioAxisValue(s)),
141+
categories: sortedScenarios.value.map(s => appStore.getScenarioAxisValue(s)),
140142
title: { text: appStore.axisMetadata?.label },
141143
labels: {
142144
style: {
@@ -211,7 +213,7 @@ watch(() => props.diffing, () => {
211213
},
212214
title: { text: chartTitle.value },
213215
xAxis: {
214-
categories: scenarios.value.map(s => appStore.getScenarioAxisValue(s) || ""),
216+
categories: sortedScenarios.value.map(s => appStore.getScenarioAxisValue(s) || ""),
215217
},
216218
yAxis: {
217219
min: props.diffing ? undefined : 0,

components/Charts/Compare/CompareTimeSeriesLegend.vue

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,17 @@ import { timeSeriesColors } from "../utils/timeSeriesCharts";
2020
2121
const appStore = useAppStore();
2222
23-
const items = computed((): LegendItem[] => {
24-
const all = appStore.currentComparison.scenarios.map((scenario: Scenario, index: number) => {
25-
const isBaseline = scenario === appStore.baselineScenario;
26-
return {
27-
color: timeSeriesColors[index],
28-
label: `${appStore.getScenarioAxisLabel(scenario)} ${(isBaseline ? " (baseline)" : "")}`,
29-
shape: LegendShape.Line,
30-
};
31-
}) || [];
23+
const scenarios = computed(() => appStore.currentComparison.scenarios);
24+
const { sortedScenarios } = useSortedScenarios(scenarios);
3225
33-
return all;
34-
});
26+
const items = computed((): LegendItem[] => sortedScenarios.value.map((scenario: Scenario, index: number) => {
27+
const isBaseline = scenario === appStore.baselineScenario;
28+
return {
29+
color: timeSeriesColors[index],
30+
label: `${appStore.getScenarioAxisLabel(scenario)} ${(isBaseline ? " (baseline)" : "")}`,
31+
shape: LegendShape.Line,
32+
};
33+
}) || []);
3534
3635
// Lay out items in columns manually since the implementation of 'flex-direction: column'
3736
// while applying flex wrap differs between Safari/Firefox/Chrome.

components/CostsTable.vue

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
</CButton>
2424
</th>
2525
<th
26-
v-for="scenario in scenariosToDisplay"
26+
v-for="scenario in sortedScenarios"
2727
:key="scenario.runId"
2828
class="pt-0"
2929
>
@@ -46,7 +46,7 @@
4646
{{ appStore.preferences.costBasis === CostBasis.PercentGDP ? "as % of GDP" : "(USD)" }}
4747
</td>
4848
<td
49-
v-for="(scenario) in scenariosToDisplay"
49+
v-for="(scenario) in sortedScenarios"
5050
:key="scenario.runId"
5151
:class="scenarioClass(scenario)"
5252
>
@@ -62,7 +62,7 @@
6262
{{ appStore.getCostLabel(childCost.id) }}{{ childCost.id === 'life_years' ? '*' : '' }}
6363
</td>
6464
<td
65-
v-for="(scenario) in scenariosToDisplay"
65+
v-for="(scenario) in sortedScenarios"
6666
:key="scenario.runId"
6767
:class="scenarioClass(scenario)"
6868
>
@@ -77,7 +77,7 @@
7777
>
7878
<td>{{ appStore.getCostLabel(grandChildCost.id) }}</td>
7979
<td
80-
v-for="(scenario) in scenariosToDisplay"
80+
v-for="(scenario) in sortedScenarios"
8181
:key="scenario.runId"
8282
:class="scenarioClass(scenario)"
8383
>
@@ -93,7 +93,7 @@
9393
Life years lost {{ props.diffing ? "relative to baseline" : "" }}
9494
</td>
9595
<td
96-
v-for="(scenario) in scenariosToDisplay"
96+
v-for="(scenario) in sortedScenarios"
9797
:key="scenario.runId"
9898
:class="scenarioClass(scenario)"
9999
>
@@ -108,7 +108,7 @@
108108
>
109109
<td>{{ appStore.getCostLabel(ageSectorCost.id) }}</td>
110110
<td
111-
v-for="(scenario) in scenariosToDisplay"
111+
v-for="(scenario) in sortedScenarios"
112112
:key="scenario.runId"
113113
:class="scenarioClass(scenario)"
114114
>
@@ -123,7 +123,7 @@
123123
Deaths {{ props.diffing ? "relative to baseline" : "" }}
124124
</td>
125125
<td
126-
v-for="(scenario) in scenariosToDisplay"
126+
v-for="(scenario) in sortedScenarios"
127127
:key="scenario.runId"
128128
:class="scenarioClass(scenario)"
129129
>
@@ -142,6 +142,7 @@ import { CostBasis } from "~/types/unitTypes";
142142
import type { Scenario } from "~/types/storeTypes";
143143
import { diffAgainstBaseline } from "./utils/comparisons";
144144
import { CUMULATIVE_DEATHS_SERIES_ID } from "./Charts/utils/timeSeriesData";
145+
import useSortedScenarios from "~/composables/useSortedScenarios";
145146
146147
const props = defineProps<{
147148
scenarios: Scenario[]
@@ -152,11 +153,10 @@ const accordioned = ref(true);
152153
const appStore = useAppStore();
153154
154155
const multiScenario = computed(() => props.scenarios.length > 1);
155-
const scenariosToDisplay = computed(() => {
156-
return props.diffing
157-
? props.scenarios.filter(s => s.runId !== appStore.baselineScenario?.runId)
158-
: props.scenarios;
159-
});
156+
const scenarios = computed(() => props.diffing
157+
? props.scenarios.filter(s => s.runId !== appStore.baselineScenario?.runId)
158+
: props.scenarios);
159+
const { sortedScenarios } = useSortedScenarios(scenarios);
160160
161161
const scenarioLabel = (scenario: Scenario) => appStore.getScenarioAxisLabel(scenario);
162162

composables/useSortedScenarios.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { sortOptions } from "~/components/utils/parameters";
2+
import type { Scenario } from "~/types/storeTypes";
3+
4+
export default (scenarios: MaybeRefOrGetter<Scenario[]>) => {
5+
const appStore = useAppStore();
6+
7+
const sortedScenarios = computed(() => {
8+
const scens = toValue(scenarios);
9+
if (scens.length === 1) {
10+
return scens;
11+
}
12+
if (!appStore.axisMetadata) {
13+
return scens;
14+
}
15+
const scenarioAxisValues = scens.map(s => appStore.getScenarioAxisValue(s)).filter(o => o !== undefined);
16+
const sortedOptions = sortOptions(appStore.axisMetadata, scenarioAxisValues);
17+
scens.sort((a, b) => {
18+
const aValue = appStore.getScenarioAxisValue(a)!;
19+
const bValue = appStore.getScenarioAxisValue(b)!;
20+
return sortedOptions.indexOf(aValue) - sortedOptions.indexOf(bValue);
21+
});
22+
return scens;
23+
});
24+
25+
return { sortedScenarios };
26+
};

0 commit comments

Comments
 (0)