Skip to content

Commit edf3e1f

Browse files
Show coords in top left of plot (fixes #82) (#121)
* Show coords in top left of plot (fixes #82) * Added formatting to coord infobox * Apply coordinate transformation before showing coords on skewT diagram --------- Co-authored-by: sverhoeven <[email protected]>
1 parent baf57f4 commit edf3e1f

File tree

4 files changed

+71
-30
lines changed

4 files changed

+71
-30
lines changed

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,8 @@ export function TimeSeriesPlot({ analysis }: { analysis: TimeseriesAnalysis }) {
131131
{/* TODO: get label for yVariable from model config */}
132132
<ChartContainer>
133133
<Legend entries={chartData} />
134-
<Chart title="Vertical profile plot">
135-
<AxisBottom
136-
domain={xLim}
137-
label="Time [s]"
138-
tickFormat={formatSeconds}
139-
/>
134+
<Chart title="Vertical profile plot" formatX={formatSeconds}>
135+
<AxisBottom domain={xLim} label="Time [s]" />
140136
<AxisLeft domain={yLim} label={analysis.yVariable} />
141137
<For each={chartData()}>{(d) => Line(d)}</For>
142138
</Chart>

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Code generated by AI and checked/modified for correctness
22

3-
import * as d3 from "d3";
43
import { For, createEffect } from "solid-js";
54
import { useChartContext } from "./ChartContainer";
65

@@ -9,7 +8,6 @@ type AxisProps = {
98
domain?: () => [number, number]; // TODO: is this needed for reactivity?
109
label?: string;
1110
tickValues?: number[];
12-
tickFormat?: (n: number) => string;
1311
};
1412

1513
export const AxisBottom = (props: AxisProps) => {
@@ -19,7 +17,6 @@ export const AxisBottom = (props: AxisProps) => {
1917
props.type && updateChart("scalePropsX", { type: props.type });
2018
});
2119

22-
const format = () => (props.tickFormat ? props.tickFormat : d3.format(".4"));
2320
const ticks = () => props.tickValues || generateTicks(chart.scaleX.domain());
2421
return (
2522
<g transform={`translate(0,${chart.innerHeight - 0.5})`}>
@@ -29,7 +26,7 @@ export const AxisBottom = (props: AxisProps) => {
2926
<g transform={`translate(${chart.scaleX(tick)}, 0)`}>
3027
<line y2="6" stroke="currentColor" />
3128
<text y="9" dy="0.71em" text-anchor="middle">
32-
{format()(tick)}
29+
{chart.formatX(tick)}
3330
</text>
3431
</g>
3532
)}
@@ -49,7 +46,6 @@ export const AxisLeft = (props: AxisProps) => {
4946
});
5047

5148
const ticks = () => props.tickValues || generateTicks(chart.scaleY.domain());
52-
const format = () => (props.tickFormat ? props.tickFormat : d3.format(".4"));
5349
return (
5450
<g transform="translate(-0.5,0)">
5551
<line
@@ -64,7 +60,7 @@ export const AxisLeft = (props: AxisProps) => {
6460
<g transform={`translate(0, ${chart.scaleY(tick)})`}>
6561
<line x2="-6" stroke="currentColor" />
6662
<text x="-9" dy="0.32em" text-anchor="end">
67-
{format()(tick)}
63+
{chart.formatY(tick)}
6864
</text>
6965
</g>
7066
)}

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

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import * as d3 from "d3";
22
import type { JSX } from "solid-js";
3-
import { createContext, createEffect, useContext } from "solid-js";
3+
import {
4+
createContext,
5+
createEffect,
6+
createSignal,
7+
useContext,
8+
} from "solid-js";
49
import { type SetStoreFunction, createStore } from "solid-js/store";
510

6-
type SupportedScaleTypes =
11+
export type SupportedScaleTypes =
712
| d3.ScaleLinear<number, number, never>
813
| d3.ScaleLogarithmic<number, number, never>;
914
const supportedScales = {
@@ -27,6 +32,9 @@ interface Chart {
2732
scalePropsY: ScaleProps;
2833
scaleX: SupportedScaleTypes;
2934
scaleY: SupportedScaleTypes;
35+
formatX: (value: number) => string;
36+
formatY: (value: number) => string;
37+
transformX?: (x: number, y: number, scaleY: SupportedScaleTypes) => number;
3038
}
3139
type SetChart = SetStoreFunction<Chart>;
3240
const ChartContext = createContext<[Chart, SetChart]>();
@@ -55,6 +63,8 @@ export function ChartContainer(props: {
5563
scalePropsY: { type: "linear", domain: [0, 1], range: [innerHeight, 0] },
5664
scaleX: initialScale,
5765
scaleY: initialScale,
66+
formatX: d3.format(".4"),
67+
formatY: d3.format(".4"),
5868
});
5969
createEffect(() => {
6070
// Update scaleXInstance when scaleX props change
@@ -81,30 +91,63 @@ export function ChartContainer(props: {
8191
}
8292

8393
/** Container for chart elements such as axes and lines */
84-
export function Chart(props: { children: JSX.Element; title?: string }) {
94+
export function Chart(props: {
95+
children: JSX.Element;
96+
title?: string;
97+
formatX?: (value: number) => string;
98+
formatY?: (value: number) => string;
99+
transformX?: (x: number, y: number, scaleY: SupportedScaleTypes) => number;
100+
}) {
101+
const [hovering, setHovering] = createSignal(false);
102+
const [coords, setCoords] = createSignal<[number, number]>([0, 0]);
85103
const [chart, updateChart] = useChartContext();
86104
const title = props.title || "Default chart";
87105
const [marginTop, _, __, marginLeft] = chart.margin;
88106

107+
if (props.formatX) {
108+
updateChart("formatX", () => props.formatX);
109+
}
110+
if (props.formatY) {
111+
updateChart("formatY", () => props.formatY);
112+
}
113+
if (props.transformX) {
114+
updateChart("transformX", () => props.transformX);
115+
}
116+
117+
const onMouseMove = (e: MouseEvent) => {
118+
let x = e.offsetX - marginLeft;
119+
const y = e.offsetY - marginTop;
120+
121+
if (chart.transformX) {
122+
x = chart.transformX(x, y, chart.scaleY);
123+
}
124+
125+
setCoords([chart.scaleX.invert(x), chart.scaleY.invert(y)]);
126+
};
127+
128+
const renderXCoord = () =>
129+
hovering() ? `x: ${chart.formatX(coords()[0])}` : "";
130+
const renderYCoord = () =>
131+
hovering() ? `y: ${chart.formatY(coords()[1])}` : "";
132+
89133
return (
90134
<svg
91135
width={chart.width}
92136
height={chart.height}
93137
class="text-slate-500 text-xs tracking-wide"
138+
onmouseover={() => setHovering(true)}
139+
onmousemove={onMouseMove}
140+
onmouseout={() => setHovering(false)}
94141
>
95142
<title>{title}</title>
96143
<g transform={`translate(${marginLeft},${marginTop})`}>
97144
{props.children}
98-
{/* Line along right edge of plot
99-
<line
100-
x1={chart.innerWidth - 0.5}
101-
x2={chart.innerWidth - 0.5}
102-
y1="0"
103-
y2={chart.innerHeight}
104-
stroke="#dfdfdf"
105-
stroke-width="0.75px"
106-
fill="none"
107-
/> */}
145+
<text x="5" y="5">
146+
{renderXCoord()}
147+
</text>
148+
<text x="5" y="20">
149+
{renderYCoord()}
150+
</text>
108151
</g>
109152
</svg>
110153
);

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
import * as d3 from "d3";
33
import { For, createSignal } from "solid-js";
44
import { AxisBottom, AxisLeft } from "./Axes";
5-
import type { ChartData } from "./ChartContainer";
5+
import type { ChartData, SupportedScaleTypes } from "./ChartContainer";
66
import { Chart, ChartContainer, useChartContext } from "./ChartContainer";
77
import { Legend } from "./Legend";
8-
98
interface SoundingRecord {
109
p: number;
1110
T: number;
@@ -17,6 +16,10 @@ const tan = Math.tan(55 * deg2rad);
1716
const basep = 1050;
1817
const topPressure = 100;
1918

19+
function getTempAtCursor(x: number, y: number, scaleY: SupportedScaleTypes) {
20+
return x + 0.5 - (scaleY(basep) - y) / tan;
21+
}
22+
2023
function ClipPath() {
2124
const [chart, updateChart] = useChartContext();
2225

@@ -148,18 +151,21 @@ export function SkewTPlot({
148151
return (
149152
<ChartContainer>
150153
<Legend entries={data} />
151-
<Chart title="Thermodynamic diagram">
154+
<Chart
155+
title="Thermodynamic diagram"
156+
formatX={d3.format(".0d")}
157+
formatY={d3.format(".0d")}
158+
transformX={getTempAtCursor}
159+
>
152160
<AxisBottom
153161
domain={() => [-45, 50]}
154162
tickValues={temperatureLines}
155-
tickFormat={d3.format(".0d")}
156163
label="Temperature [°C]"
157164
/>
158165
<AxisLeft
159166
type="log"
160167
domain={() => [basep, topPressure]}
161168
tickValues={pressureLines}
162-
tickFormat={d3.format(".0d")}
163169
label="Pressure [hPa]"
164170
/>
165171
<ClipPath />

0 commit comments

Comments
 (0)