Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
af5323b
Add select box to vertical profiles; data updates but plot doesn't
Peter9192 Dec 13, 2024
8057616
Make plot label reactive
Peter9192 Dec 13, 2024
c329745
Export supportedscales
Peter9192 Dec 13, 2024
4bdb0bc
Able to update scale's domain, but updating scale in place doesn't tr…
Peter9192 Dec 13, 2024
485d9f8
Separate scale from scaleprops; now scale is updated as an effect of …
Peter9192 Dec 13, 2024
0941892
Make more plot elements reactive; everything works at this point
Peter9192 Dec 13, 2024
ac70cb4
Extract variable picker into reusable component
Peter9192 Dec 13, 2024
27aaa08
Relax type of variable and prep for also making time configurable
Peter9192 Dec 13, 2024
036654a
Nicer formatting
Peter9192 Dec 13, 2024
dfb4140
suppress linter for generated code
Peter9192 Dec 13, 2024
06c3ac8
Install solid-ui slider component
Peter9192 Dec 13, 2024
b0b9cb3
Add slider to set time
Peter9192 Dec 13, 2024
6a8158c
Slightly cleaner code
Peter9192 Dec 16, 2024
581daf1
Create flatExperiments derived store + make time slider work for mult…
Peter9192 Dec 16, 2024
61ddf95
Axis limits now fixed during sliding, but extent calculating still wonky
Peter9192 Dec 16, 2024
c376c9a
Refactor timeseries plot using new flatExperiments store
Peter9192 Dec 16, 2024
fe85d66
Fix issue with selecting same variable twice
Peter9192 Dec 16, 2024
38c9b36
Reuse flatExperiments for skewT plot data
Peter9192 Dec 16, 2024
cb9e243
Also add time slider to skew-T plot
Peter9192 Dec 16, 2024
f0cbdbe
Get output var names from BMI model - isn't this a bit too entangled?
Peter9192 Dec 16, 2024
876529c
Picker must have value
sverhoeven Jan 6, 2025
df85e38
fix type issue with suggestion from code review
Peter9192 Jan 9, 2025
5aa6e60
use nice description in dropdown for vertical profiles; add todo for …
Peter9192 Jan 9, 2025
3e6e339
use analyses store for variable picker
Peter9192 Jan 9, 2025
1ac2441
also for vertical profiles
Peter9192 Jan 9, 2025
baaea00
Merge branch 'variable-picker' into time-slider
Peter9192 Jan 10, 2025
7b82d88
format
Peter9192 Jan 10, 2025
00b9827
remove dead code
Peter9192 Jan 10, 2025
b3a5163
also lift time signal to analysis store
Peter9192 Jan 10, 2025
e0d1baa
prevent overflow of time slider
Peter9192 Jan 10, 2025
ad7909a
Reuse time formatter on timeseries x-axis
Peter9192 Jan 10, 2025
491554f
Rename lineplot to line
Peter9192 Jan 10, 2025
6b2d69f
Merge remote-tracking branch 'origin/main' into time-slider
Peter9192 Jan 10, 2025
c1c0c31
Revert "Merge remote-tracking branch 'origin/main' into time-slider"
Peter9192 Jan 10, 2025
670dcf7
undo accidental deletion of Line.tsx
Peter9192 Jan 13, 2025
4adaf72
format
Peter9192 Jan 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
378 changes: 195 additions & 183 deletions apps/class-solid/src/components/Analysis.tsx

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions apps/class-solid/src/components/plots/Axes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type AxisProps = {
domain?: () => [number, number]; // TODO: is this needed for reactivity?
label?: string;
tickValues?: number[];
tickFormat?: (n: number | { valueOf(): number }) => string;
tickFormat?: (n: number) => string;
};

export const AxisBottom = (props: AxisProps) => {
Expand Down Expand Up @@ -80,14 +80,17 @@ export const AxisLeft = (props: AxisProps) => {
* Calculate a "nice" step size by rounding up to the nearest power of 10
* Snap the min and max to the nearest multiple of step
*/
export function getNiceAxisLimits(data: number[]): [number, number] {
export function getNiceAxisLimits(
data: number[],
extraMargin = 0,
): [number, number] {
const max = Math.max(...data);
const min = Math.min(...data);
const range = max - min;
const step = 10 ** Math.floor(Math.log10(range));

const niceMin = Math.floor(min / step) * step;
const niceMax = Math.ceil(max / step) * step;
const niceMin = Math.floor(min / step) * step - extraMargin * step;
const niceMax = Math.ceil(max / step) * step + extraMargin * step;

return [niceMin, niceMax];
}
Expand Down
2 changes: 1 addition & 1 deletion apps/class-solid/src/components/plots/ChartContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function ChartContainer(props: {
const scaleX = supportedScales[chart.scalePropsX.type]()
.range(chart.scalePropsX.range)
.domain(chart.scalePropsX.domain);
// .nice(); // TODO: could use this instead of getNiceAxisLimits
// .nice(); // TODO: could use this instead of getNiceAxisLimits but messes up skewT
updateChart("scaleX", () => scaleX);
});

Expand Down
32 changes: 32 additions & 0 deletions apps/class-solid/src/components/plots/Line.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as d3 from "d3";
import { createSignal } from "solid-js";
import type { ChartData } from "./ChartContainer";
import { useChartContext } from "./ChartContainer";

export interface Point {
x: number;
y: number;
}

export function Line(d: ChartData<Point>) {
const [chart, updateChart] = useChartContext();
const [hovered, setHovered] = createSignal(false);

const l = d3.line<Point>(
(d) => chart.scaleX(d.x),
(d) => chart.scaleY(d.y),
);
return (
<path
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
fill="none"
stroke={d.color}
stroke-dasharray={d.linestyle}
stroke-width={hovered() ? 5 : 3}
d={l(d.data) || ""}
>
<title>{d.label}</title>
</path>
);
}
59 changes: 0 additions & 59 deletions apps/class-solid/src/components/plots/LinePlot.tsx

This file was deleted.

19 changes: 19 additions & 0 deletions apps/class-solid/src/components/ui/label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Component, ComponentProps } from "solid-js";
import { splitProps } from "solid-js";

import { cn } from "~/lib/utils";

const Label: Component<ComponentProps<"label">> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<label
class={cn(
"font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
local.class,
)}
{...others}
/>
);
};

export { Label };
112 changes: 112 additions & 0 deletions apps/class-solid/src/components/ui/slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type { JSX, ValidComponent } from "solid-js";
import { splitProps } from "solid-js";

import type { PolymorphicProps } from "@kobalte/core/polymorphic";
import * as SliderPrimitive from "@kobalte/core/slider";

import { Label } from "~/components/ui/label";
import { cn } from "~/lib/utils";

type SliderRootProps<T extends ValidComponent = "div"> =
SliderPrimitive.SliderRootProps<T> & {
class?: string | undefined;
};

const Slider = <T extends ValidComponent = "div">(
props: PolymorphicProps<T, SliderRootProps<T>>,
) => {
const [local, others] = splitProps(props as SliderRootProps, ["class"]);
return (
<SliderPrimitive.Root
class={cn(
"relative flex w-full touch-none select-none flex-col items-center",
local.class,
)}
{...others}
/>
);
};

type SliderTrackProps<T extends ValidComponent = "div"> =
SliderPrimitive.SliderTrackProps<T> & {
class?: string | undefined;
};

const SliderTrack = <T extends ValidComponent = "div">(
props: PolymorphicProps<T, SliderTrackProps<T>>,
) => {
const [local, others] = splitProps(props as SliderTrackProps, ["class"]);
return (
<SliderPrimitive.Track
class={cn(
"relative h-2 w-full grow rounded-full bg-secondary",
local.class,
)}
{...others}
/>
);
};

type SliderFillProps<T extends ValidComponent = "div"> =
SliderPrimitive.SliderFillProps<T> & {
class?: string | undefined;
};

const SliderFill = <T extends ValidComponent = "div">(
props: PolymorphicProps<T, SliderFillProps<T>>,
) => {
const [local, others] = splitProps(props as SliderFillProps, ["class"]);
return (
<SliderPrimitive.Fill
class={cn("absolute h-full rounded-full bg-primary", local.class)}
{...others}
/>
);
};

type SliderThumbProps<T extends ValidComponent = "span"> =
SliderPrimitive.SliderThumbProps<T> & {
class?: string | undefined;
children?: JSX.Element;
};

const SliderThumb = <T extends ValidComponent = "span">(
props: PolymorphicProps<T, SliderThumbProps<T>>,
) => {
const [local, others] = splitProps(props as SliderThumbProps, [
"class",
"children",
]);
return (
<SliderPrimitive.Thumb
class={cn(
"top-[-6px] block size-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
local.class,
)}
{...others}
>
<SliderPrimitive.Input />
</SliderPrimitive.Thumb>
);
};

const SliderLabel = <T extends ValidComponent = "label">(
props: PolymorphicProps<T, SliderPrimitive.SliderLabelProps<T>>,
) => {
return <SliderPrimitive.Label as={Label} {...props} />;
};

const SliderValueLabel = <T extends ValidComponent = "label">(
props: PolymorphicProps<T, SliderPrimitive.SliderValueLabelProps<T>>,
) => {
return <SliderPrimitive.ValueLabel as={Label} {...props} />;
};

export {
Slider,
SliderTrack,
SliderFill,
SliderThumb,
SliderLabel,
SliderValueLabel,
};
4 changes: 2 additions & 2 deletions apps/class-solid/src/lib/profiles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ClassOutput } from "@classmodel/class/runner";
import type { PartialConfig } from "@classmodel/class/validate";
import type { Point } from "~/components/plots/LinePlot";
import type { Point } from "~/components/plots/Line";

// Get vertical profiles for a single class run
export function getVerticalProfiles(
Expand All @@ -16,7 +16,7 @@ export function getVerticalProfiles(

// Extract height profile
const height = output.h.slice(t)[0];
const dh = 400; // how much free troposphere to display?
const dh = 1600; // how much free troposphere to display?
const hProfile = [0, height, height, height + dh];

if (variable === "theta") {
Expand Down
4 changes: 2 additions & 2 deletions apps/class-solid/src/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ export function addAnalysis(name: string) {
type: "profiles",
name: "Vertical profiles",
variable: "Potential temperature [K]",
time: -1,
time: Number.POSITIVE_INFINITY,
} as ProfilesAnalysis;
break;
case "Thermodynamic diagram":
Expand All @@ -321,7 +321,7 @@ export function addAnalysis(name: string) {
description: "",
type: "skewT",
name: "Thermodynamic diagram",
time: -1,
time: Number.POSITIVE_INFINITY,
} as SkewTAnalysis;
break;
default:
Expand Down
14 changes: 1 addition & 13 deletions apps/class-solid/tests/share.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,7 @@ test("Create share link from an experiment", async ({ page }) => {
const config1 = await parseDownload(downloadPromise1);
expect(config1.reference.initialState?.h_0).toEqual(800);

// // Check that shared experiment has been run by
// // checking height in final height analysis
// const finalHeightAnalysis = sharedPage.getByRole("article", {
// name: "Final height",
// });
// const finalHeightOfExperiment = finalHeightAnalysis.getByRole("listitem", {
// name: "My experiment 1",
// exact: true,
// });
// expect(await finalHeightOfExperiment.textContent()).toMatch(
// /My experiment 1: \d+ m/,
// );
// TODO: implement alternative check to see that experiment finished
// TODO: finalheight is gone; implement alternative check to see that experiment finished
});

test("Given large app state, sharing is not possible", async ({ page }) => {
Expand Down
Loading