Skip to content

Commit 75874d4

Browse files
committed
Start refactoring plotting code
1 parent 34e651e commit 75874d4

File tree

8 files changed

+93
-87
lines changed

8 files changed

+93
-87
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { For, Match, Show, Switch, createMemo, createUniqueId } from "solid-js";
22
import { getThermodynamicProfiles, getVerticalProfiles } from "~/lib/profiles";
33
import { type Analysis, deleteAnalysis, experiments } from "~/lib/store";
4-
import LinePlot from "./LinePlot";
54
import { MdiCog, MdiContentCopy, MdiDelete, MdiDownload } from "./icons";
6-
import { SkewTPlot } from "./skewTlogP";
5+
import LinePlot from "./plots/LinePlot";
6+
import { SkewTPlot } from "./plots/skewTlogP";
77
import { Button } from "./ui/button";
88
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
99

apps/class-solid/src/components/Axes.tsx renamed to apps/class-solid/src/components/plots/Axes.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ export const AxisBottom = (props: AxisProps) => {
5656
};
5757

5858
export const AxisLeft = (props: AxisProps) => {
59-
const labelpos = props.scale.range().reduce((a, b) => a + b) / 2;
6059
const format = props.tickFormat ? props.tickFormat : d3.format(".0f");
6160
const yAnchor = props.decreasing ? 0 : 1;
6261
return (
@@ -88,3 +87,19 @@ export const AxisLeft = (props: AxisProps) => {
8887
</g>
8988
);
9089
};
90+
91+
/**
92+
* Calculate a "nice" step size by rounding up to the nearest power of 10
93+
* Snap the min and max to the nearest multiple of step
94+
*/
95+
export function getNiceAxisLimits(data: number[]): [number, number] {
96+
const max = Math.max(...data);
97+
const min = Math.min(...data);
98+
const range = max - min;
99+
const step = 10 ** Math.floor(Math.log10(range));
100+
101+
const niceMin = Math.floor(min / step) * step;
102+
const niceMax = Math.ceil(max / step) * step;
103+
104+
return [niceMin, niceMax];
105+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface ChartData<T> {
2+
label: string;
3+
color: string;
4+
linestyle: string;
5+
data: T[];
6+
}
7+
8+
// TODO: would be nice to create a chartContainer/context that manages logic like
9+
// width/height/margins etc. that should be consistent across different plots.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { For } from "solid-js";
2+
import { cn } from "~/lib/utils";
3+
import type { ChartData } from "./Base";
4+
5+
export interface LegendProps<T> {
6+
entries: () => ChartData<T>[];
7+
width: string;
8+
}
9+
10+
export function Legend<T>(props: LegendProps<T>) {
11+
return (
12+
// {/* Legend */}
13+
<div
14+
class={cn(
15+
"flex flex-wrap justify-end text-sm tracking-tight",
16+
props.width,
17+
)}
18+
>
19+
<For each={props.entries()}>
20+
{(d) => (
21+
<>
22+
<span class="flex items-center">
23+
<svg
24+
width="1.5rem"
25+
height="1rem"
26+
overflow="visible"
27+
viewBox="0 0 50 20"
28+
>
29+
<title>legend</title>
30+
<path
31+
fill="none"
32+
stroke={d.color}
33+
stroke-dasharray={d.linestyle}
34+
stroke-width="4"
35+
d="M 0 12 L 45 12"
36+
/>
37+
</svg>
38+
<p style={`color: ${d.color}`}>{d.label}</p>
39+
</span>
40+
</>
41+
)}
42+
</For>
43+
</div>
44+
);
45+
}

apps/class-solid/src/components/LinePlot.tsx renamed to apps/class-solid/src/components/plots/LinePlot.tsx

Lines changed: 3 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,14 @@
11
import * as d3 from "d3";
22
import { For } from "solid-js";
3-
import { cn } from "~/lib/utils";
4-
import { AxisBottom, AxisLeft } from "./Axes";
5-
6-
export interface ChartData<T> {
7-
label: string;
8-
color: string;
9-
linestyle: string;
10-
data: T[];
11-
}
3+
import { AxisBottom, AxisLeft, getNiceAxisLimits } from "./Axes";
4+
import type { ChartData } from "./Base";
5+
import { Legend } from "./Legend";
126

137
export interface Point {
148
x: number;
159
y: number;
1610
}
1711

18-
/**
19-
* Calculate a "nice" step size by rounding up to the nearest power of 10
20-
* Snap the min and max to the nearest multiple of step
21-
*/
22-
function getNiceAxisLimits(data: number[]): [number, number] {
23-
const max = Math.max(...data);
24-
const min = Math.min(...data);
25-
const range = max - min;
26-
const step = 10 ** Math.floor(Math.log10(range));
27-
28-
const niceMin = Math.floor(min / step) * step;
29-
const niceMax = Math.ceil(max / step) * step;
30-
31-
return [niceMin, niceMax];
32-
}
33-
34-
export interface LegendProps<T> {
35-
entries: () => ChartData<T>[];
36-
width: string;
37-
}
38-
39-
export function Legend<T>(props: LegendProps<T>) {
40-
return (
41-
// {/* Legend */}
42-
<div
43-
class={cn(
44-
"flex flex-wrap justify-end text-sm tracking-tight",
45-
props.width,
46-
)}
47-
>
48-
<For each={props.entries()}>
49-
{(d) => (
50-
<>
51-
<span class="flex items-center">
52-
<svg
53-
width="1.5rem"
54-
height="1rem"
55-
overflow="visible"
56-
viewBox="0 0 50 20"
57-
>
58-
<title>legend</title>
59-
<path
60-
fill="none"
61-
stroke={d.color}
62-
stroke-dasharray={d.linestyle}
63-
stroke-width="4"
64-
d="M 0 12 L 45 12"
65-
/>
66-
</svg>
67-
<p style={`color: ${d.color}`}>{d.label}</p>
68-
</span>
69-
</>
70-
)}
71-
</For>
72-
</div>
73-
);
74-
}
75-
7612
export default function LinePlot({
7713
data,
7814
xlabel,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import * as d3 from "d3";
33
import { For, createSignal } from "solid-js";
44
import { AxisBottom, AxisLeft } from "./Axes";
5-
import { type ChartData, Legend } from "./LinePlot";
5+
import type { ChartData } from "./Base";
6+
import { Legend } from "./Legend";
67

78
interface SoundingRecord {
89
p: number;

apps/class-solid/src/lib/profiles.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import type { ClassOutput } from "@classmodel/class/runner";
22
import type { PartialConfig } from "@classmodel/class/validate";
3-
import type { Point } from "~/components/LinePlot";
3+
import type { Point } from "~/components/plots/LinePlot";
44

55
// Get vertical profiles for a single class run
66
export function getVerticalProfiles(
77
output: ClassOutput | undefined,
88
config: PartialConfig,
99
variable: "theta" | "q" = "theta",
10-
t = -1,
10+
t = -1
1111
): Point[] {
1212
// Guard against undefined output
1313
if (output === undefined) {
@@ -102,7 +102,7 @@ const thickness = (T: number, q: number, p: number, dp: number) => {
102102
export function getThermodynamicProfiles(
103103
output: ClassOutput | undefined,
104104
config: PartialConfig,
105-
t = -1,
105+
t = -1
106106
) {
107107
// Guard against undefined output
108108
if (output === undefined) {

apps/class-solid/src/lib/store.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ function findExperiment(index: number) {
9898
export async function addExperiment(
9999
config: PartialConfig = {},
100100
name?: string,
101-
description?: string,
101+
description?: string
102102
) {
103103
const newExperiment: Experiment = {
104104
name: name ?? `My experiment ${experiments.length}`,
@@ -149,7 +149,7 @@ export async function modifyExperiment(
149149
index: number,
150150
newConfig: PartialConfig,
151151
name: string,
152-
description: string,
152+
description: string
153153
) {
154154
setExperiments(
155155
index,
@@ -160,14 +160,14 @@ export async function modifyExperiment(
160160
e.permutations = e.permutations.map((perm) => {
161161
const config = mergeConfigurations(
162162
newConfig,
163-
pruneDefaults(perm.config),
163+
pruneDefaults(perm.config)
164164
);
165165
return {
166166
...perm,
167167
config,
168168
};
169169
});
170-
}),
170+
})
171171
);
172172
await runExperiment(index);
173173
}
@@ -176,25 +176,25 @@ export async function setPermutationConfigInExperiment(
176176
experimentIndex: number,
177177
permutationIndex: number,
178178
config: PartialConfig,
179-
name: string,
179+
name: string
180180
) {
181181
setExperiments(
182182
experimentIndex,
183183
"permutations",
184184
permutationIndex === -1
185185
? findExperiment(experimentIndex).permutations.length
186186
: permutationIndex,
187-
{ config, name },
187+
{ config, name }
188188
);
189189
await runExperiment(experimentIndex);
190190
}
191191

192192
export async function deletePermutationFromExperiment(
193193
experimentIndex: number,
194-
permutationIndex: number,
194+
permutationIndex: number
195195
) {
196196
setExperiments(experimentIndex, "permutations", (perms) =>
197-
perms.filter((_, i) => i !== permutationIndex),
197+
perms.filter((_, i) => i !== permutationIndex)
198198
);
199199
}
200200

@@ -208,7 +208,7 @@ export function findPermutation(exp: Experiment, permutationName: string) {
208208

209209
export function promotePermutationToExperiment(
210210
experimentIndex: number,
211-
permutationIndex: number,
211+
permutationIndex: number
212212
) {
213213
const exp = findExperiment(experimentIndex);
214214
const perm = exp.permutations[permutationIndex];
@@ -220,22 +220,22 @@ export function promotePermutationToExperiment(
220220

221221
export function duplicatePermutation(
222222
experimentIndex: number,
223-
permutationIndex: number,
223+
permutationIndex: number
224224
) {
225225
const exp = findExperiment(experimentIndex);
226226
const perm = exp.permutations[permutationIndex];
227227
setPermutationConfigInExperiment(
228228
experimentIndex,
229229
-1,
230230
structuredClone(perm.config),
231-
`Copy of ${perm.name}`,
231+
`Copy of ${perm.name}`
232232
);
233233
runExperiment(experimentIndex);
234234
}
235235

236236
export function swapPermutationAndReferenceConfiguration(
237237
experimentIndex: number,
238-
permutationIndex: number,
238+
permutationIndex: number
239239
) {
240240
const exp = findExperiment(experimentIndex);
241241
const refConfig = structuredClone(exp.reference.config);
@@ -248,7 +248,7 @@ export function swapPermutationAndReferenceConfiguration(
248248
"permutations",
249249
permutationIndex,
250250
"config",
251-
refConfig,
251+
refConfig
252252
);
253253
// TODO should names also be swapped?
254254
runExperiment(experimentIndex);

0 commit comments

Comments
 (0)