Skip to content

Commit 080f6d1

Browse files
Legend checkbox (#130)
* In legend replace line with inert checkbox * Add checkboxes to legends, remove line in legend * Hide lines in vertical profiles plot when unchecked * Correct name * Legend formatting --------- Co-authored-by: Peter Kalverla <[email protected]>
1 parent 640ed74 commit 080f6d1

File tree

4 files changed

+178
-39
lines changed

4 files changed

+178
-39
lines changed

apps/class-solid/src/components/Analysis.tsx

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import {
88
For,
99
Match,
1010
type Setter,
11+
Show,
1112
Switch,
1213
createEffect,
1314
createMemo,
1415
createUniqueId,
1516
} from "solid-js";
17+
import { createStore } from "solid-js/store";
1618
import type { Observation } from "~/lib/experiment_config";
1719
import {
1820
getThermodynamicProfiles,
@@ -142,15 +144,34 @@ export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
142144
};
143145
});
144146

147+
const [toggles, setToggles] = createStore<Record<string, boolean>>({});
148+
149+
// Initialize all lines as visible
150+
createEffect(() => {
151+
for (const d of chartData()) {
152+
setToggles(d.label, true);
153+
}
154+
});
155+
156+
function toggleLine(label: string, value: boolean) {
157+
setToggles(label, value);
158+
}
159+
145160
return (
146161
<>
147162
{/* TODO: get label for yVariable from model config */}
148163
<ChartContainer>
149-
<Legend entries={chartData} />
164+
<Legend entries={chartData} toggles={toggles} onChange={toggleLine} />
150165
<Chart title="Timeseries plot" formatX={formatSeconds}>
151166
<AxisBottom domain={xLim} label="Time [s]" />
152167
<AxisLeft domain={yLim} label={analysis.yVariable} />
153-
<For each={chartData()}>{(d) => Line(d)}</For>
168+
<For each={chartData()}>
169+
{(d) => (
170+
<Show when={toggles[d.label]}>
171+
<Line {...d} />
172+
</Show>
173+
)}
174+
</For>
154175
</Chart>
155176
</ChartContainer>
156177
<div class="flex justify-around">
@@ -223,16 +244,49 @@ export function VerticalProfilePlot({
223244
};
224245
});
225246

247+
function chartData() {
248+
return [...profileData(), ...observations()];
249+
}
250+
251+
const [toggles, setToggles] = createStore<Record<string, boolean>>({});
252+
253+
// Initialize all lines as visible
254+
createEffect(() => {
255+
for (const d of chartData()) {
256+
setToggles(d.label, true);
257+
}
258+
});
259+
260+
function toggleLine(label: string, value: boolean) {
261+
setToggles(label, value);
262+
}
263+
226264
return (
227265
<>
228266
<div class="flex flex-col gap-2">
229267
<ChartContainer>
230-
<Legend entries={() => [...profileData(), ...observations()]} />
268+
<Legend
269+
entries={() => [...profileData(), ...observations()]}
270+
toggles={toggles}
271+
onChange={toggleLine}
272+
/>
231273
<Chart title="Vertical profile plot">
232274
<AxisBottom domain={xLim} label={analysis.variable} />
233275
<AxisLeft domain={yLim} label="Height[m]" />
234-
<For each={profileData()}>{(d) => Line(d)}</For>
235-
<For each={observations()}>{(d) => Line(d)}</For>
276+
<For each={profileData()}>
277+
{(d) => (
278+
<Show when={toggles[d.label]}>
279+
<Line {...d} />
280+
</Show>
281+
)}
282+
</For>
283+
<For each={observations()}>
284+
{(d) => (
285+
<Show when={toggles[d.label]}>
286+
<Line {...d} />
287+
</Show>
288+
)}
289+
</For>
236290
</Chart>
237291
</ChartContainer>
238292
<Picker

apps/class-solid/src/components/plots/Legend.tsx

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,42 @@
11
import { For } from "solid-js";
2-
import { cn } from "~/lib/utils";
2+
import { createUniqueId } from "solid-js";
33
import type { ChartData } from "./ChartContainer";
44
import { useChartContext } from "./ChartContainer";
55

66
export interface LegendProps<T> {
77
entries: () => ChartData<T>[];
8+
toggles: Record<string, boolean>;
9+
onChange: (key: string, value: boolean) => void;
810
}
911

1012
export function Legend<T>(props: LegendProps<T>) {
1113
const [chart, updateChart] = useChartContext();
1214

1315
return (
1416
<div
15-
class={cn(
16-
"flex flex-wrap justify-end text-sm tracking-tight",
17-
`w-[${chart.width}px]`,
18-
)}
17+
class={"flex flex-wrap justify-end gap-2 text-sm tracking-tight"}
18+
style={`max-width: ${chart.width}px;`}
1919
>
2020
<For each={props.entries()}>
21-
{(d) => (
22-
<>
23-
<span class="flex items-center">
24-
<svg
25-
width="1.5rem"
26-
height="1rem"
27-
overflow="visible"
28-
viewBox="0 0 50 20"
29-
>
30-
<title>legend</title>
31-
<path
32-
fill="none"
33-
stroke={d.color}
34-
stroke-dasharray={d.linestyle}
35-
stroke-width="4"
36-
d="M 0 12 L 45 12"
37-
/>
38-
</svg>
39-
<p style={`color: ${d.color}`}>{d.label}</p>
40-
</span>
41-
</>
42-
)}
21+
{(d) => {
22+
const id = createUniqueId();
23+
return (
24+
<div
25+
class="flex items-center gap-1"
26+
style={`color: ${d.color}; accent-color: ${d.color}`}
27+
>
28+
<input
29+
type="checkbox"
30+
checked={props.toggles[d.label]}
31+
onChange={(v) =>
32+
props.onChange(d.label, v.currentTarget.checked)
33+
}
34+
id={id}
35+
/>
36+
<label for={id}>{d.label}</label>
37+
</div>
38+
);
39+
}}
4340
</For>
4441
</div>
4542
);

apps/class-solid/src/components/plots/skewTlogP.tsx

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Code modified from https://github.com/rsobash/d3-skewt/ (MIT license)
22
import * as d3 from "d3";
3-
import { For, createSignal } from "solid-js";
3+
import { For, Show, createEffect, createSignal } from "solid-js";
4+
import { createStore } from "solid-js/store";
45
import { AxisBottom, AxisLeft } from "./Axes";
56
import type { ChartData, SupportedScaleTypes } from "./ChartContainer";
67
import {
@@ -149,9 +150,7 @@ function Sounding(data: ChartData<SoundingRecord>) {
149150

150151
// Note: using temperatures in Kelvin as that's easiest to get from CLASS, but
151152
// perhaps not the most interoperable with other sounding data sources.
152-
export function SkewTPlot({
153-
data,
154-
}: { data: () => ChartData<SoundingRecord>[] }) {
153+
export function SkewTPlot(props: { data: () => ChartData<SoundingRecord>[] }) {
155154
const pressureLines = [1000, 850, 700, 500, 300, 200, 100];
156155
const temperatureLines = d3.range(-100, 45, 10);
157156

@@ -161,9 +160,30 @@ export function SkewTPlot({
161160
pressureGrid.map((pressure) => [pressure, temperature]),
162161
);
163162

163+
const [toggles, setToggles] = createStore<Record<string, boolean>>({});
164+
165+
// Initialize all lines as visible
166+
createEffect(() => {
167+
for (const d of props.data()) {
168+
setToggles(d.label, true);
169+
}
170+
});
171+
172+
function toggleLine(label: string, value: boolean) {
173+
setToggles(label, value);
174+
}
175+
176+
function showSounding(i: number) {
177+
const cd = props.data()[i];
178+
if (!toggles || !cd) {
179+
return true;
180+
}
181+
return toggles[cd.label];
182+
}
183+
164184
return (
165185
<ChartContainer>
166-
<Legend entries={data} />
186+
<Legend entries={props.data} toggles={toggles} onChange={toggleLine} />
167187
<Chart
168188
title="Thermodynamic diagram"
169189
formatX={d3.format(".0d")}
@@ -184,8 +204,14 @@ export function SkewTPlot({
184204
<ClipPath />
185205
<For each={temperatureLines}>{(t) => SkewTGridLine(t)}</For>
186206
<For each={pressureLines}>{(p) => LogPGridLine(p)}</For>
187-
<For each={dryAdiabats}>{(d) => DryAdiabat(d)}</For>
188-
<For each={data()}>{(d) => Sounding(d)}</For>
207+
<For each={dryAdiabats}>{(d) => <DryAdiabat {...d} />}</For>
208+
<For each={props.data()}>
209+
{(d, i) => (
210+
<Show when={showSounding(i())}>
211+
<Sounding {...d} />
212+
</Show>
213+
)}
214+
</For>
189215
</Chart>
190216
</ChartContainer>
191217
);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type { ValidComponent } from "solid-js";
2+
import { Match, Switch, splitProps } from "solid-js";
3+
4+
import * as CheckboxPrimitive from "@kobalte/core/checkbox";
5+
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
6+
7+
import { cn } from "~/lib/utils";
8+
9+
type CheckboxRootProps<T extends ValidComponent = "div"> =
10+
CheckboxPrimitive.CheckboxRootProps<T> & { class?: string | undefined };
11+
12+
const Checkbox = <T extends ValidComponent = "div">(
13+
props: PolymorphicProps<T, CheckboxRootProps<T>>,
14+
) => {
15+
const [local, others] = splitProps(props as CheckboxRootProps, ["class"]);
16+
return (
17+
<CheckboxPrimitive.Root
18+
class={cn("items-top group relative flex space-x-2", local.class)}
19+
{...others}
20+
>
21+
<CheckboxPrimitive.Input class="peer" />
22+
<CheckboxPrimitive.Control class="size-4 shrink-0 rounded-sm border border-primary ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 data-[checked]:border-none data-[indeterminate]:border-none data-[checked]:bg-primary data-[indeterminate]:bg-primary data-[checked]:text-primary-foreground data-[indeterminate]:text-primary-foreground">
23+
<CheckboxPrimitive.Indicator>
24+
<Switch>
25+
<Match when={!others.indeterminate}>
26+
{/* biome-ignore lint/a11y/noSvgWithoutTitle: want to use tooltip for something else */}
27+
<svg
28+
xmlns="http://www.w3.org/2000/svg"
29+
viewBox="0 0 24 24"
30+
fill="none"
31+
stroke="currentColor"
32+
stroke-width="2"
33+
stroke-linecap="round"
34+
stroke-linejoin="round"
35+
class="size-4"
36+
>
37+
<path d="M5 12l5 5l10 -10" />
38+
</svg>
39+
</Match>
40+
<Match when={others.indeterminate}>
41+
{/* biome-ignore lint/a11y/noSvgWithoutTitle: want to use tooltip for something else */}
42+
<svg
43+
xmlns="http://www.w3.org/2000/svg"
44+
viewBox="0 0 24 24"
45+
fill="none"
46+
stroke="currentColor"
47+
stroke-width="2"
48+
stroke-linecap="round"
49+
stroke-linejoin="round"
50+
class="size-4"
51+
>
52+
<path d="M5 12l14 0" />
53+
</svg>
54+
</Match>
55+
</Switch>
56+
</CheckboxPrimitive.Indicator>
57+
</CheckboxPrimitive.Control>
58+
</CheckboxPrimitive.Root>
59+
);
60+
};
61+
62+
export { Checkbox };

0 commit comments

Comments
 (0)