Skip to content

Commit

Permalink
charts: also use chart legend for treemaps
Browse files Browse the repository at this point in the history
  • Loading branch information
yagebu committed Jul 30, 2023
1 parent 8cd17c0 commit fe0a099
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 95 deletions.
1 change: 1 addition & 0 deletions frontend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ module.exports = {
rules: {
"svelte/button-has-type": ON,
"no-self-assign": OFF,
"no-undef": OFF,
"no-undef-init": OFF,
"import/no-mutable-exports": OFF,
"@typescript-eslint/no-unsafe-argument": OFF,
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/charts/BarChart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import { extent } from "d3-array";
import { axisBottom, axisLeft } from "d3-axis";
import { scaleBand, scaleLinear, scaleOrdinal } from "d3-scale";
import { getContext } from "svelte";
import type { Writable } from "svelte/store";
import { urlForAccount } from "../helpers";
Expand All @@ -23,6 +22,7 @@
export let chart: BarChart;
export let width: number;
export let legend: Writable<[string, string | null][]>;
const today = new Date();
const maxColumnWidth = 100;
Expand Down Expand Up @@ -59,11 +59,10 @@
$: colorScale = scaleOrdinal(hclColorRange(accounts.length)).domain(accounts);
const legend: Writable<[string, string][]> = getContext("chart-legend");
$: legend.set(
chart.currencies.map((c) => [
c,
showStackedBars ? "#bbb" : $currenciesScale(c),
showStackedBars ? null : $currenciesScale(c),
])
);
Expand Down
67 changes: 19 additions & 48 deletions frontend/src/charts/Chart.svelte
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
<script lang="ts">
import { setContext } from "svelte";
import { writable } from "svelte/store";
import type { Writable } from "svelte/store";
import { _ } from "../i18n";
import {
barChartMode,
chartCurrency,
chartToggledCurrencies,
hierarchyChartMode,
lineChartMode,
showCharts,
treemapCurrency,
} from "../stores/chart";
import BarChart from "./BarChart.svelte";
Expand All @@ -31,55 +29,28 @@
*/
let width: number;
const currencies: Writable<string[]> = writable([]);
setContext("chart-currencies", currencies);
const legend: Writable<[string, string][]> = writable([]);
setContext("chart-legend", legend);
$: if (chart) {
// Reset the chart legend on chart change.
legend.set([]);
}
const treemap_currencies = writable<string[]>([]);
const legend = writable<[string, string | null][]>([]);
</script>

<div class="flex-row">
{#if $showCharts}
<div>
<ChartLegend legend={$legend} />
</div>
{#if chart.type === "barchart" || chart.type === "linechart"}
<ChartLegend legend={$legend} toggled={chartToggledCurrencies} />
{/if}
{#if chart.type === "hierarchy" && $hierarchyChartMode === "treemap"}
<ChartLegend
legend={$treemap_currencies.map((c) => [c, null])}
active={treemapCurrency}
/>
{/if}
<span class="spacer" />
{#if chart.type === "hierarchy"}
{#if $hierarchyChartMode === "treemap"}
<select bind:value={$chartCurrency}>
{#each $currencies as currency}
<option value={currency}>{currency}</option>
{/each}
</select>
{/if}
<ModeSwitch
bind:value={$hierarchyChartMode}
options={[
["treemap", _("Treemap")],
["sunburst", _("Sunburst")],
]}
/>
<ModeSwitch store={hierarchyChartMode} />
{:else if chart.type === "linechart"}
<ModeSwitch
bind:value={$lineChartMode}
options={[
["line", _("Line chart")],
["area", _("Area chart")],
]}
/>
<ModeSwitch store={lineChartMode} />
{:else if chart.type === "barchart" && chart.hasStackedData}
<ModeSwitch
bind:value={$barChartMode}
options={[
["stacked", _("Stacked Bars")],
["single", _("Single Bars")],
]}
/>
<ModeSwitch store={barChartMode} />
{/if}
{:else}<span class="spacer" />{/if}
<slot />
Expand All @@ -95,11 +66,11 @@
<div hidden={!$showCharts} bind:clientWidth={width}>
{#if width}
{#if chart.type === "barchart"}
<BarChart {chart} {width} />
<BarChart {chart} {width} {legend} />
{:else if chart.type === "hierarchy"}
<HierarchyContainer {chart} {width} />
<HierarchyContainer {chart} {width} {treemap_currencies} />
{:else if chart.type === "linechart"}
<LineChart {chart} {width} />
<LineChart {chart} {width} {legend} />
{:else if chart.type === "scatterplot"}
<ScatterPlot {chart} {width} />
{/if}
Expand Down
48 changes: 27 additions & 21 deletions frontend/src/charts/ChartLegend.svelte
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
<script lang="ts">
import { chartToggledCurrencies } from "../stores/chart";
import type { Writable } from "svelte/store";
export let legend: [string, string][];
/** The chart legend to show. */
export let legend: [string, string | null][];
/** A list of elements that are toggled. */
export let toggled: Writable<string[]> | null = null;
/** Alternatively, a single active element, all others are toggled. */
export let active: Writable<string | null> | null = null;
</script>

{#each legend as [item, color]}
{@const isActive = !$chartToggledCurrencies.includes(item)}
<button
type="button"
on:click={() => {
if (isActive) {
$chartToggledCurrencies = [...$chartToggledCurrencies, item];
} else {
$chartToggledCurrencies = $chartToggledCurrencies.filter(
(i) => i !== item
);
}
}}
class:inactive={!isActive}
>
<i style="background-color: {color}" />
<span>{item}</span>
</button>
{/each}
<div>
{#each legend as [item, color]}
<button
type="button"
on:click={() => {
if (active) {
active.set(item);
} else if (toggled) {
toggled.update((v) =>
v.includes(item) ? v.filter((i) => i !== item) : [...v, item]
);
}
}}
class:inactive={active ? item !== $active : $toggled?.includes(item)}
>
<i style="background-color: {color ?? '#bbb'}" />
<span>{item}</span>
</button>
{/each}
</div>

<style>
button {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/charts/ChartSwitcher.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
onMount(() => bindKey("C", previousChart));
</script>

{#if charts.length > 0 && active_chart}
{#if active_chart}
<Chart chart={active_chart}>
<ConversionAndInterval />
</Chart>
Expand Down
18 changes: 10 additions & 8 deletions frontend/src/charts/HierarchyContainer.svelte
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
<script lang="ts">
import { getContext } from "svelte";
import type { Writable } from "svelte/store";
import { _ } from "../i18n";
import { chartCurrency, hierarchyChartMode } from "../stores/chart";
import { hierarchyChartMode, treemapCurrency } from "../stores/chart";
import type { HierarchyChart } from "./hierarchy";
import Sunburst from "./Sunburst.svelte";
import Treemap from "./Treemap.svelte";
const context: Writable<string[]> = getContext("chart-currencies");
export let chart: HierarchyChart
export let chart: HierarchyChart;
export let width: number;
export let treemap_currencies: Writable<string[]>;
$: data = chart.data;
$: currencies = [...data.keys()];
$: currency = $chartCurrency || currencies[0];
$: context.set(currencies);
$: treemap_currencies.set(currencies);
$: if ($treemapCurrency === null) {
$treemapCurrency = $treemapCurrency ?? currencies[0] ?? null;
}
$: currency = $treemapCurrency;
$: mode = $hierarchyChartMode;
$: treemap = mode === "treemap" && data.get(currency ?? "");
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/charts/LineChart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import { quadtree } from "d3-quadtree";
import { scaleLinear, scaleUtc } from "d3-scale";
import { area, curveStepAfter, line } from "d3-shape";
import { getContext } from "svelte";
import type { Writable } from "svelte/store";
import { chartToggledCurrencies, lineChartMode } from "../stores/chart";
Expand All @@ -18,6 +17,7 @@
export let chart: LineChart;
export let width: number;
export let legend: Writable<[string, string | null][]>;
const today = new Date();
const margin = { top: 10, right: 10, bottom: 30, left: 40 };
Expand All @@ -28,7 +28,6 @@
$: data = chart.filter($chartToggledCurrencies);
$: series_names = chart.series_names;
const legend: Writable<[string, string][]> = getContext("chart-legend");
$: legend.set(series_names.map((c) => [c, $currenciesScale(c)]));
// Scales and quadtree
Expand Down
16 changes: 9 additions & 7 deletions frontend/src/charts/ModeSwitch.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<script lang="ts">
export let value: string;
export let options: [string, string][];
<script lang="ts" generics="T extends string">
import type { LocalStoreSyncedStore } from "../lib/store";
/** The store to show a switch for. */
export let store: LocalStoreSyncedStore<T>;
</script>

<span>
{#each options as [option, name]}
<label class="button" class:muted={value !== option}>
<input type="radio" bind:group={value} value={option} />
{#each store.values() as [option, name]}
<label class="button" class:muted={$store !== option}>
<input type="radio" bind:group={$store} value={option} />
{name}
</label>
{/each}
Expand All @@ -18,6 +20,6 @@
}
label + label {
margin-left: 0.25rem;
margin-left: 0.125rem;
}
</style>
15 changes: 11 additions & 4 deletions frontend/src/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,25 @@ export function derived_array<S, T extends StrictEquality>(
);
}

/** A store that has its value synced to localStorage. */
export type LocalStoreSyncedStore<T> = Writable<T> & {
/** List all the values that this store can take. */
values: () => [T, string][];
};

/**
* Create a store that syncs its value to localStorage.
* @param key - The key to save this with in localStorage.
* @param validator - A Validator to check the loaded value.
* @param init - A default to initialise the store with if localStorage is
* empty.
* @param init - A default to initialise the store with if localStorage is empty.
* @param values - An optional enumerator of all possible values and descriptions.
*/
export function localStorageSyncedStore<T>(
key: string,
validator: Validator<T>,
init: () => T,
): Writable<T> {
values: () => [T, string][] = () => [],
): LocalStoreSyncedStore<T> {
const fullKey = `fava-${key}`;

// Create a store which is empty first but reads the value from
Expand All @@ -61,5 +68,5 @@ export function localStorageSyncedStore<T>(
});
});

return store;
return { ...store, values };
}
15 changes: 14 additions & 1 deletion frontend/src/stores/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,32 @@ export const hierarchyChartMode = localStorageSyncedStore<
"hierarchy-chart-mode",
union(constant("treemap"), constant("sunburst")),
() => "treemap",
() => [
["treemap", _("Treemap")],
["sunburst", _("Sunburst")],
],
);

/** The currently selected line chart mode. */
export const lineChartMode = localStorageSyncedStore<"line" | "area">(
"line-chart-mode",
union(constant("line"), constant("area")),
() => "line",
() => [
["line", _("Line chart")],
["area", _("Area chart")],
],
);

/** The currently selected bar chart mode. */
export const barChartMode = localStorageSyncedStore<"stacked" | "single">(
"bar-chart-mode",
union(constant("stacked"), constant("single")),
() => "stacked",
() => [
["stacked", _("Stacked Bars")],
["single", _("Single Bars")],
],
);

/** The currencies that are currently not shown in the bar and line charts. */
Expand All @@ -47,7 +59,8 @@ export const chartToggledCurrencies = localStorageSyncedStore<string[]>(
() => [],
);

export const chartCurrency = writable("");
/** The currency to show the treemap of. */
export const treemapCurrency = writable<string | null>(null);

const currencySuggestions = derived(
[operating_currency, currencies_sorted, conversion_currencies],
Expand Down

0 comments on commit fe0a099

Please sign in to comment.