Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
15b1562
Implement basic zoom; TODO: fix angle in skewT
Peter9192 Apr 11, 2025
4b9625c
Add logarithmic zoom for skew-T diagram; TODO: T calculations should …
Peter9192 Apr 11, 2025
89f028b
Fix skew-T lines responding to original extent instead of actual; now…
Peter9192 Apr 11, 2025
8e29a5a
Add panning effect, but it is stroboscopic and doesn't work for skewT…
Peter9192 Apr 11, 2025
53f2cb1
combine side-effects for both axes in a single callback
Peter9192 Apr 11, 2025
23f4dae
Merge remote-tracking branch 'origin/main' into pan-zoom
Peter9192 Apr 18, 2025
ca46f1f
Remove animationframe
Peter9192 Apr 18, 2025
8be9635
Use produce to update both scales in a single call
Peter9192 Apr 18, 2025
859557b
Don't update panstart; this fixes the jittering
Peter9192 Apr 18, 2025
9c32384
Also work in log space
Peter9192 Apr 18, 2025
d3fae43
Make consistent for x-direction
Peter9192 Apr 18, 2025
ea696fc
zoom towards cursor
Peter9192 Apr 18, 2025
b65f516
Add reset plot button
Peter9192 Apr 18, 2025
69e93d4
Thinner lines
Peter9192 Apr 18, 2025
1ca0f68
Higher resolution plot
Peter9192 Apr 18, 2025
8953c43
Round time to steps of 10 minutes
Peter9192 Apr 18, 2025
0d72ef1
Use runClass from package, skipping BMI altogher + rich metadata for …
Peter9192 Apr 18, 2025
d072e56
Fix hanging issue: wrap only once...
Peter9192 Apr 18, 2025
cd10da8
Make sure initial state is included in output
Peter9192 Apr 18, 2025
f085554
Reset pan/zoom when variable changes
Peter9192 Apr 18, 2025
202d144
formatting
Peter9192 Apr 18, 2025
1e1399a
Use output metadata in plot labels and variable pickers
Peter9192 Apr 18, 2025
eeb62df
ditch BMI
Peter9192 Apr 18, 2025
8e9dddf
Fix xlabel in timeseries plot; fix axes extent for non-time on x-axis
Peter9192 Apr 22, 2025
3a74a3d
Expose output freq + document runClass
sverhoeven Apr 24, 2025
9123896
Merge remote-tracking branch 'origin/main' into update-class
Peter9192 Apr 24, 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
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,8 @@ by [Kobalte](https://kobalte.dev/docs/core/overview/introduction) and
application and tweaked further as seen fit. It can also do charts, using
[chart.js](https://www.chartjs.org/), though we might deviate from that later.

To expose the model in a standard way we use the [Basic Model Interface (BMI)](https://bmi.readthedocs.io/).

To prevent the user interface from getting blocked by running the model we use a [Web worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker) to run the computation in a background task/thread.
A Web worker uses messages passing to communicate, this does not fit with the Basic Model Interface so we use [comlink](https://github.com/GoogleChromeLabs/comlink) to wrap the Web Worker in a BMI class.
We use [comlink](https://github.com/GoogleChromeLabs/comlink) to wrap the Web Worker so it behaves the same as if the runner was used directly inside the main thread.

To format and lint the code, we use [biome](https://biomejs.dev/) as it combines eslint, prettier in one package.

Expand Down
66 changes: 49 additions & 17 deletions apps/class-solid/src/components/Analysis.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BmiClass } from "@classmodel/class/bmi";
import type { Config } from "@classmodel/class/config";
import type { ClassOutput } from "@classmodel/class/runner";
import { type ClassOutput, outputVariables } from "@classmodel/class/runner";
import * as d3 from "d3";
import { saveAs } from "file-saver";
import { toBlob } from "html-to-image";
import {
Expand Down Expand Up @@ -115,9 +115,15 @@ const uniqueTimes = () => [...new Set(_allTimes())].sort((a, b) => a - b);

// TODO: could memoize all reactive elements here, would it make a difference?
export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
const xVariableOptions = ["t"]; // TODO: separate plot types for timeseries and x-vs-y? Use time axis?
// TODO: add nice description from config as title and dropdown option for the variable picker.
const yVariableOptions = new BmiClass().get_output_var_names();
const symbols = Object.fromEntries(
outputVariables.map((v) => [v.key, v.symbol]),
);
const getKey = Object.fromEntries(
outputVariables.map((v) => [v.symbol, v.key]),
);
const labels = Object.fromEntries(
outputVariables.map((v) => [v.key, `${v.symbol} [${v.unit}]`]),
);

const allX = () =>
flatExperiments().flatMap((e) =>
Expand All @@ -128,7 +134,8 @@ export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
e.output ? e.output[analysis.yVariable] : [],
);

const xLim = () => getNiceAxisLimits(allX(), 0, 600);
const granularity = () => (analysis.xVariable === "t" ? 600 : undefined);
const xLim = () => getNiceAxisLimits(allX(), 0, granularity());
const yLim = () => getNiceAxisLimits(allY());

const chartData = () =>
Expand Down Expand Up @@ -158,14 +165,34 @@ export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
setToggles(label, value);
}

const setXVar = (symbol: string) => {
updateAnalysis(analysis, { xVariable: getKey[symbol] });
setResetPlot(analysis.id);
};

const setYVar = (symbol: string) => {
updateAnalysis(analysis, { yVariable: getKey[symbol] });
setResetPlot(analysis.id);
};

const formatX = () =>
analysis.xVariable === "t" ? formatSeconds : d3.format(".4");
const formatY = () =>
analysis.yVariable === "t" ? formatSeconds : d3.format(".4");

return (
<>
{/* TODO: get label for yVariable from model config */}
<ChartContainer>
<Legend entries={chartData} toggles={toggles} onChange={toggleLine} />
<Chart id={analysis.id} title="Timeseries plot" formatX={formatSeconds}>
<AxisBottom domain={xLim} label="Time [s]" />
<AxisLeft domain={yLim} label={analysis.yVariable} />
<Chart
id={analysis.id}
title="Timeseries plot"
formatX={formatX}
formatY={formatY}
>
<AxisBottom domain={xLim} label={labels[analysis.xVariable]} />
<AxisLeft domain={yLim} label={labels[analysis.yVariable]} />
<For each={chartData()}>
{(d) => (
<Show when={toggles[d.label]}>
Expand All @@ -177,15 +204,15 @@ export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
</ChartContainer>
<div class="flex justify-around">
<Picker
value={() => analysis.xVariable}
setValue={(v) => updateAnalysis(analysis, { xVariable: v })}
options={xVariableOptions}
value={() => symbols[analysis.xVariable]}
setValue={(v) => setXVar(v)}
options={Object.values(symbols)}
label="x-axis"
/>
<Picker
value={() => analysis.yVariable}
setValue={(v) => updateAnalysis(analysis, { yVariable: v })}
options={yVariableOptions}
value={() => symbols[analysis.yVariable]}
setValue={(v) => setYVar(v)}
options={Object.values(symbols)}
label="y-axis"
/>
</div>
Expand Down Expand Up @@ -261,6 +288,11 @@ export function VerticalProfilePlot({
setToggles(label, value);
}

function changeVar(v: string) {
updateAnalysis(analysis, { variable: v });
setResetPlot(analysis.id);
}

return (
<>
<div class="flex flex-col gap-2">
Expand Down Expand Up @@ -291,7 +323,7 @@ export function VerticalProfilePlot({
</ChartContainer>
<Picker
value={() => analysis.variable}
setValue={(v) => updateAnalysis(analysis, { variable: v })}
setValue={(v) => changeVar(v)}
options={Object.keys(variableOptions)}
label="variable: "
/>
Expand All @@ -307,7 +339,7 @@ export function VerticalProfilePlot({

type PickerProps = {
value: Accessor<string>;
setValue: Setter<string>;
setValue: (value: string) => void;
options: string[];
label?: string;
};
Expand Down
21 changes: 13 additions & 8 deletions apps/class-solid/src/components/plots/ChartContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ export function Chart(props: {
children: JSX.Element;
id: string;
title?: string;
formatX?: (value: number) => string;
formatY?: (value: number) => string;
formatX?: () => (value: number) => string;
formatY?: () => (value: number) => string;
transformX?: (x: number, y: number, scaleY: SupportedScaleTypes) => number;
}) {
const [hovering, setHovering] = createSignal(false);
Expand All @@ -142,12 +142,17 @@ export function Chart(props: {
}
});

if (props.formatX) {
updateChart("formatX", () => props.formatX);
}
if (props.formatY) {
updateChart("formatY", () => props.formatY);
}
createEffect(() => {
if (props.formatX) {
updateChart("formatX", () => props.formatX?.());
}
});
createEffect(() => {
if (props.formatY) {
updateChart("formatY", () => props.formatY?.());
}
});

if (props.transformX) {
updateChart("transformX", () => props.transformX);
}
Expand Down
4 changes: 2 additions & 2 deletions apps/class-solid/src/components/plots/skewTlogP.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ export function SkewTPlot(props: {
<Chart
id={props.id}
title="Thermodynamic diagram"
formatX={d3.format(".0d")}
formatY={d3.format(".0d")}
formatX={() => d3.format(".0d")}
formatY={() => d3.format(".0d")}
transformX={getTempAtCursor}
>
<AxisBottom
Expand Down
16 changes: 5 additions & 11 deletions apps/class-solid/src/lib/runner.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import { BmiClass } from "@classmodel/class/bmi";
import type { Config } from "@classmodel/class/config";
import type { ClassOutput } from "@classmodel/class/runner";
import { parse } from "@classmodel/class/validate";
import type { ClassOutput, runClass } from "@classmodel/class/runner";
import { wrap } from "comlink";

const worker = new Worker(new URL("./worker.ts", import.meta.url), {
type: "module",
});
export const AsyncBmiClass = wrap<typeof BmiClass>(worker);

export async function runClass(config: Config): Promise<ClassOutput> {
const asyncRunner = wrap<typeof runClass>(worker);

export async function runClassAsync(config: Config): Promise<ClassOutput> {
try {
const parsedConfig: Config = parse(config);
const model = await new AsyncBmiClass();
await model.initialize(parsedConfig);
const output = await model.run({
var_names: new BmiClass().get_output_var_names(),
});
const output = asyncRunner(config);
return output;
} catch (error) {
console.error({ config, error });
Expand Down
6 changes: 3 additions & 3 deletions apps/class-solid/src/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { decodeAppState } from "./encode";
import { parseExperimentConfig } from "./experiment_config";
import type { ExperimentConfig } from "./experiment_config";
import { findPresetByName } from "./presets";
import { runClass } from "./runner";
import { runClassAsync } from "./runner";

interface ExperimentOutput {
reference?: ClassOutput;
Expand All @@ -38,7 +38,7 @@ export async function runExperiment(id: number) {

// Run reference
const referenceConfig = unwrap(exp.config.reference);
const newOutput = await runClass(referenceConfig);
const newOutput = await runClassAsync(referenceConfig);

setExperiments(id, "output", "reference", newOutput);

Expand All @@ -50,7 +50,7 @@ export async function runExperiment(id: number) {
referenceConfig,
permConfig,
) as Config;
const newOutput = await runClass(combinedConfig);
const newOutput = await runClassAsync(combinedConfig);
setExperiments(id, "output", "permutations", permCounter, newOutput);
permCounter++;
}
Expand Down
4 changes: 2 additions & 2 deletions apps/class-solid/src/lib/worker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BmiClass } from "@classmodel/class/bmi";
import { runClass } from "@classmodel/class/runner";
import { expose } from "comlink";

expose(BmiClass);
expose(runClass);
6 changes: 0 additions & 6 deletions packages/class/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@
"types": "./dist/runner.d.ts"
}
},
"./bmi": {
"import": {
"default": "./dist/bmi.js",
"types": "./dist/bmi.d.ts"
}
},
"./validate": {
"import": {
"default": "./dist/validate.js",
Expand Down
19 changes: 0 additions & 19 deletions packages/class/src/bmi.test.ts

This file was deleted.

Loading