-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Trying to wrap charts in chartcontainer with context provider, but something's not working yet * Don't use prop destructuring; seems to fix issue with context * Distinguish between ChartContainer and Chart so legend can reuse context; lineplot now works perfectly with context, very nice * Also use contextmanager in skewT plot, very clean. +Add hover to lineplot + make legend width work * Move chartdata interface to chartcontainer * formatting
- Loading branch information
Showing
6 changed files
with
297 additions
and
262 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import * as d3 from "d3"; | ||
import type { JSX } from "solid-js"; | ||
import { createContext, useContext } from "solid-js"; | ||
import { type SetStoreFunction, createStore } from "solid-js/store"; | ||
|
||
interface Chart { | ||
width: number; | ||
height: number; | ||
margin: [number, number, number, number]; | ||
innerWidth: number; | ||
innerHeight: number; | ||
scaleX: d3.ScaleLinear<number, number> | d3.ScaleLogarithmic<number, number>; | ||
scaleY: d3.ScaleLinear<number, number> | d3.ScaleLogarithmic<number, number>; | ||
} | ||
type SetChart = SetStoreFunction<Chart>; | ||
const ChartContext = createContext<[Chart, SetChart]>(); | ||
|
||
/** Container and context manager for chart + legend */ | ||
export function ChartContainer(props: { | ||
children: JSX.Element; | ||
width?: number; | ||
height?: number; | ||
margin?: [number, number, number, number]; | ||
}) { | ||
const width = props.width || 500; | ||
const height = props.height || 500; | ||
const margin = props.margin || [20, 20, 35, 55]; | ||
const [marginTop, marginRight, marginBottom, marginLeft] = margin; | ||
const innerHeight = height - marginTop - marginBottom; | ||
const innerWidth = width - marginRight - marginLeft; | ||
const [chart, updateChart] = createStore<Chart>({ | ||
width, | ||
height, | ||
margin, | ||
innerHeight, | ||
innerWidth, | ||
scaleX: d3.scaleLinear().range([0, innerWidth]), | ||
scaleY: d3.scaleLinear().range([innerHeight, 0]), | ||
}); | ||
return ( | ||
<ChartContext.Provider value={[chart, updateChart]}> | ||
<figure>{props.children}</figure> | ||
</ChartContext.Provider> | ||
); | ||
} | ||
|
||
/** Container for chart elements such as axes and lines */ | ||
export function Chart(props: { children: JSX.Element; title?: string }) { | ||
const [chart, updateChart] = useChartContext(); | ||
const title = props.title || "Default chart"; | ||
const [marginTop, _, __, marginLeft] = chart.margin; | ||
|
||
return ( | ||
<svg | ||
width={chart.width} | ||
height={chart.height} | ||
class="text-slate-500 text-xs tracking-wide" | ||
> | ||
<title>{title}</title> | ||
<g transform={`translate(${marginLeft},${marginTop})`}> | ||
{props.children} | ||
{/* Line along right edge of plot | ||
<line | ||
x1={chart.innerWidth - 0.5} | ||
x2={chart.innerWidth - 0.5} | ||
y1="0" | ||
y2={chart.innerHeight} | ||
stroke="#dfdfdf" | ||
stroke-width="0.75px" | ||
fill="none" | ||
/> */} | ||
</g> | ||
</svg> | ||
); | ||
} | ||
|
||
export function useChartContext() { | ||
const context = useContext(ChartContext); | ||
if (!context) { | ||
throw new Error( | ||
"useChartContext must be used within a ChartProvider; typically by wrapping your components in a ChartContainer.", | ||
); | ||
} | ||
return context; | ||
} | ||
export interface ChartData<T> { | ||
label: string; | ||
color: string; | ||
linestyle: string; | ||
data: T[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,78 +1,55 @@ | ||
import * as d3 from "d3"; | ||
import { For } from "solid-js"; | ||
import { For, createSignal } from "solid-js"; | ||
import { AxisBottom, AxisLeft, getNiceAxisLimits } from "./Axes"; | ||
import type { ChartData } from "./Base"; | ||
import type { ChartData } from "./ChartContainer"; | ||
import { Chart, ChartContainer, useChartContext } from "./ChartContainer"; | ||
import { Legend } from "./Legend"; | ||
|
||
export interface Point { | ||
x: number; | ||
y: number; | ||
} | ||
|
||
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> | ||
); | ||
} | ||
|
||
export default function LinePlot({ | ||
data, | ||
xlabel, | ||
ylabel, | ||
}: { data: () => ChartData<Point>[]; xlabel?: string; ylabel?: string }) { | ||
// TODO: Make responsive | ||
// const margin = [30, 40, 20, 45]; // reference from skew-T | ||
const [marginTop, marginRight, marginBottom, marginLeft] = [20, 20, 35, 55]; | ||
const width = 500; | ||
const height = 500; | ||
const w = 500 - marginRight - marginLeft; | ||
const h = 500 - marginTop - marginBottom; | ||
|
||
const xLim = () => | ||
getNiceAxisLimits(data().flatMap((d) => d.data.flatMap((d) => d.x))); | ||
const yLim = () => | ||
getNiceAxisLimits(data().flatMap((d) => d.data.flatMap((d) => d.y))); | ||
const scaleX = () => d3.scaleLinear(xLim(), [0, w]); | ||
const scaleY = () => d3.scaleLinear(yLim(), [h, 0]); | ||
|
||
const l = d3.line<Point>( | ||
(d) => scaleX()(d.x), | ||
(d) => scaleY()(d.y), | ||
); | ||
|
||
return ( | ||
<figure> | ||
<Legend entries={data} width={`w-[${width}px]`} /> | ||
{/* Plot */} | ||
<svg | ||
width={width} | ||
height={height} | ||
class="text-slate-500 text-xs tracking-wide" | ||
> | ||
<g transform={`translate(${marginLeft},${marginTop})`}> | ||
<title>Vertical profile plot</title> | ||
{/* Axes */} | ||
<AxisBottom | ||
scale={scaleX()} | ||
transform={`translate(0,${h - 0.5})`} | ||
label={xlabel} | ||
/> | ||
<AxisLeft | ||
scale={scaleY()} | ||
transform="translate(-0.5,0)" | ||
label={ylabel} | ||
/> | ||
|
||
{/* Line */} | ||
<For each={data()}> | ||
{(d) => ( | ||
<path | ||
fill="none" | ||
stroke={d.color} | ||
stroke-dasharray={d.linestyle} | ||
stroke-width="3" | ||
d={l(d.data) || ""} | ||
> | ||
<title>{d.label}</title> | ||
</path> | ||
)} | ||
</For> | ||
</g> | ||
</svg> | ||
</figure> | ||
<ChartContainer> | ||
<Legend entries={data} /> | ||
<Chart title="Vertical profile plot"> | ||
<AxisBottom domain={xLim} label={xlabel} /> | ||
<AxisLeft domain={yLim} label={ylabel} /> | ||
<For each={data()}>{(d) => Line(d)}</For> | ||
</Chart> | ||
</ChartContainer> | ||
); | ||
} |
Oops, something went wrong.