Skip to content

Commit 8aff4f2

Browse files
committed
feat: adds support for pane primitives in root chart pane
1 parent 8f40d24 commit 8aff4f2

10 files changed

Lines changed: 515 additions & 366 deletions

File tree

README.md

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
[![npm version](https://img.shields.io/npm/v/@dschz/solid-lightweight-charts?style=flat-square)](https://www.npmjs.com/package/@dschz/solid-lightweight-charts)
1010
[![Bundle Size](https://img.shields.io/bundlephobia/minzip/@dschz/solid-uplot)](https://bundlephobia.com/package/@dschz/solid-lightweight-charts)
1111
[![CI](https://github.com/dsnchz/solid-lightweight-charts/actions/workflows/ci.yaml/badge.svg)](https://github.com/dsnchz/solid-lightweight-charts/actions/workflows/ci.yaml)
12+
[![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?logo=discord&logoColor=white)](https://discord.gg/jV4MghpHUA)
1213

1314
> A fully typed SolidJS wrapper around TradingView's [Lightweight Charts](https://github.com/tradingview/lightweight-charts), providing declarative, reactive charting with support for time series, price, and yield curve data.
1415
@@ -155,35 +156,6 @@ import { TimeChart } from "@dschz/solid-lightweight-charts";
155156
</TimeChart>;
156157
```
157158

158-
### Enhanced Markers with Reactive Props
159-
160-
```tsx
161-
import { TimeChart } from "@dschz/solid-lightweight-charts";
162-
163-
<TimeChart>
164-
<TimeChart.Series
165-
type="Line"
166-
data={priceData}
167-
markers={(data) => [
168-
{
169-
time: data[5].time,
170-
position: "aboveBar",
171-
color: "#2196F3",
172-
shape: "arrowUp",
173-
text: "Buy Signal",
174-
},
175-
{
176-
time: data[15].time,
177-
position: "belowBar",
178-
color: "#F44336",
179-
shape: "arrowDown",
180-
text: "Sell Signal",
181-
},
182-
]}
183-
/>
184-
</TimeChart>;
185-
```
186-
187159
### Custom Series
188160

189161
```tsx
@@ -477,6 +449,7 @@ bun start
477449

478450
- [TradingView Lightweight Charts Docs](https://tradingview.github.io/lightweight-charts/)
479451
- [Lightweight Charts GitHub](https://github.com/tradingview/lightweight-charts)
452+
- [Discord](https://discord.gg/jV4MghpHUA)
480453

481454
> Full documentation and advanced guides coming soon.
482455

src/PriceChart.tsx

Lines changed: 79 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import {
22
createOptionsChart,
33
createSeriesMarkers,
4-
type IPaneApi,
54
type ISeriesApi,
65
type SeriesMarker,
76
} from "lightweight-charts";
87
import {
9-
type Accessor,
108
createContext,
119
createEffect,
1210
createMemo,
@@ -22,10 +20,10 @@ import {
2220
} from "solid-js";
2321

2422
import { SERIES_DEFINITION_MAP } from "./constants";
25-
import { PriceChartContext, usePriceChart } from "./contexts/chart";
2623
import type {
2724
BuiltInSeriesType,
2825
ChartCommonProps,
26+
ChartContextType,
2927
ChartWithPaneState,
3028
CustomSeriesProps,
3129
IOptionsChartApi,
@@ -35,6 +33,19 @@ import type {
3533
SeriesPrimitive,
3634
SeriesProps,
3735
} from "./types";
36+
import { attachPanePrimitives, detachPanePrimitives } from "./utils";
37+
38+
const PriceChartContext = createContext<ChartContextType<IOptionsChartApi, number>>();
39+
40+
export const usePriceChart = () => {
41+
const ctx = useContext(PriceChartContext);
42+
43+
if (!ctx) {
44+
throw new Error("[solid-lightweight-charts] No parent PriceChart component found!");
45+
}
46+
47+
return ctx;
48+
};
3849

3950
type OptionsChartOptions = NonNullable<Parameters<typeof createOptionsChart>[1]>;
4051

@@ -48,7 +59,7 @@ type OptionsChartOptions = NonNullable<Parameters<typeof createOptionsChart>[1]>
4859
* @property onCreateChart - Callback invoked with the chart instance after creation
4960
* @property onResize - Callback triggered on manual resize (only if `autoSize: false`)
5061
*/
51-
type OptionsChartProps = ChartCommonProps<IOptionsChartApi> & OptionsChartOptions;
62+
type OptionsChartProps = ChartCommonProps<IOptionsChartApi, number> & OptionsChartOptions;
5263

5364
/**
5465
* A SolidJS wrapper component for rendering horizontally price-scaled charts using
@@ -76,27 +87,31 @@ type OptionsChartProps = ChartCommonProps<IOptionsChartApi> & OptionsChartOption
7687
export const PriceChart = (props: ParentProps<OptionsChartProps>): JSX.Element => {
7788
let chartContainer!: HTMLDivElement;
7889

79-
const [containerProps, options] = splitProps(props, [
90+
const _props = mergeProps(
91+
{
92+
autoSize: true,
93+
width: 0,
94+
height: 0,
95+
forceRepaintOnResize: false,
96+
primitives: [] as PanePrimitive<number>[],
97+
},
98+
props,
99+
);
100+
101+
const [containerProps, options] = splitProps(_props, [
80102
"id",
81103
"class",
82104
"ref",
83105
"style",
106+
"primitives",
107+
"onPrimitivesAttached",
108+
"onPrimitivesDetached",
84109
"onCreateChart",
85110
"onResize",
86111
"children",
87112
]);
88113

89-
const _options = mergeProps(
90-
{
91-
autoSize: true,
92-
width: 0,
93-
height: 0,
94-
forceRepaintOnResize: false,
95-
},
96-
options,
97-
);
98-
99-
const [resizeProps, chartOptions] = splitProps(_options, [
114+
const [resizeProps, chartOptions] = splitProps(options, [
100115
"width",
101116
"height",
102117
"forceRepaintOnResize",
@@ -105,7 +120,7 @@ export const PriceChart = (props: ParentProps<OptionsChartProps>): JSX.Element =
105120
const [chart, setChart] = createSignal<IOptionsChartApi>();
106121

107122
onMount(() => {
108-
props.ref?.(chartContainer);
123+
_props.ref?.(chartContainer);
109124
const chart = createOptionsChart(
110125
chartContainer,
111126
chartOptions,
@@ -133,6 +148,15 @@ export const PriceChart = (props: ParentProps<OptionsChartProps>): JSX.Element =
133148
});
134149
});
135150

151+
const primitives = () => _props.primitives;
152+
153+
const onChartPrimitivesAttached = (primitives: PanePrimitive<number>[]) => {
154+
containerProps.onPrimitivesAttached?.(primitives);
155+
};
156+
const onChartPrimitivesDetached = (primitives: PanePrimitive<number>[]) => {
157+
containerProps.onPrimitivesDetached?.(primitives);
158+
};
159+
136160
return (
137161
<>
138162
<div
@@ -143,7 +167,9 @@ export const PriceChart = (props: ParentProps<OptionsChartProps>): JSX.Element =
143167
/>
144168
<Show when={chart()}>
145169
{(chart) => (
146-
<PriceChartContext.Provider value={chart}>
170+
<PriceChartContext.Provider
171+
value={{ chart, primitives, onChartPrimitivesAttached, onChartPrimitivesDetached }}
172+
>
147173
{containerProps.children}
148174
</PriceChartContext.Provider>
149175
)}
@@ -155,12 +181,12 @@ export const PriceChart = (props: ParentProps<OptionsChartProps>): JSX.Element =
155181
const PaneContext = createContext<PaneContextType<number>>({
156182
paneIdx: () => 0,
157183
panePrimitives: () => [],
158-
attachPanePrimitives: () => {},
159-
detachPanePrimitives: () => {},
184+
onPanePrimitivesAttached: () => {},
185+
onPanePrimitivesDetached: () => {},
160186
});
161187

162188
const Pane = (props: PaneProps<number>) => {
163-
const chart = usePriceChart() as unknown as Accessor<ChartWithPaneState<IOptionsChartApi>>;
189+
const { chart } = usePriceChart();
164190

165191
const _props = mergeProps(
166192
{
@@ -169,37 +195,22 @@ const Pane = (props: PaneProps<number>) => {
169195
props,
170196
);
171197

172-
const paneIdx = createMemo(() => _props.index ?? chart().__getNextPaneIndex());
198+
const paneIdx = createMemo(
199+
() => _props.index ?? (chart() as ChartWithPaneState<IOptionsChartApi>).__getNextPaneIndex(),
200+
);
173201
const panePrimitives = () => _props.primitives;
174202

175-
const attachPanePrimitives = (primitives: PanePrimitive<number>[], pane?: IPaneApi<number>) => {
176-
if (!pane) return;
177-
178-
// Since pane primitives are reactive, we need to detach them first to avoid unnecessary re-attachments
179-
for (const primitive of primitives) {
180-
pane.detachPrimitive(primitive);
181-
}
182-
183-
for (const primitive of primitives) {
184-
pane.attachPrimitive(primitive);
185-
}
186-
203+
const onPanePrimitivesAttached = (primitives: PanePrimitive<number>[]) => {
187204
_props.onAttachPrimitives?.(primitives);
188205
};
189206

190-
const detachPanePrimitives = (primitives: PanePrimitive<number>[], pane?: IPaneApi<number>) => {
191-
if (!pane) return;
192-
193-
for (const primitive of primitives) {
194-
pane.detachPrimitive(primitive);
195-
}
196-
207+
const onPanePrimitivesDetached = (primitives: PanePrimitive<number>[]) => {
197208
_props.onDetachPrimitives?.(primitives);
198209
};
199210

200211
return (
201212
<PaneContext.Provider
202-
value={{ paneIdx, panePrimitives, attachPanePrimitives, detachPanePrimitives }}
213+
value={{ paneIdx, panePrimitives, onPanePrimitivesAttached, onPanePrimitivesDetached }}
203214
>
204215
{props.children}
205216
</PaneContext.Provider>
@@ -226,8 +237,13 @@ const Pane = (props: PaneProps<number>) => {
226237
PriceChart.Pane = Pane;
227238

228239
const Series = <T extends BuiltInSeriesType>(props: SeriesProps<T, number>) => {
229-
const chart = usePriceChart();
230-
const { paneIdx, panePrimitives, attachPanePrimitives, detachPanePrimitives } =
240+
const {
241+
chart,
242+
primitives: chartPrimitives,
243+
onChartPrimitivesAttached,
244+
onChartPrimitivesDetached,
245+
} = usePriceChart();
246+
const { paneIdx, panePrimitives, onPanePrimitivesAttached, onPanePrimitivesDetached } =
231247
useContext(PaneContext);
232248

233249
const _props = mergeProps(
@@ -270,12 +286,17 @@ const Series = <T extends BuiltInSeriesType>(props: SeriesProps<T, number>) => {
270286
});
271287

272288
createEffect(() => {
273-
const currentPanePrimitives = panePrimitives();
289+
// If paneIdx is 0, use the primitives from the chart context, otherwise use the primitives from the pane context
290+
const currentPanePrimitives = paneIdx() === 0 ? chartPrimitives() : panePrimitives();
291+
const attachCallback = paneIdx() === 0 ? onChartPrimitivesAttached : onPanePrimitivesAttached;
292+
const detachCallback = paneIdx() === 0 ? onChartPrimitivesDetached : onPanePrimitivesDetached;
274293

275294
attachPanePrimitives(currentPanePrimitives, seriesPane);
295+
attachCallback(currentPanePrimitives);
276296

277297
onCleanup(() => {
278298
detachPanePrimitives(currentPanePrimitives, seriesPane);
299+
detachCallback(currentPanePrimitives);
279300
});
280301
});
281302

@@ -328,8 +349,13 @@ const Series = <T extends BuiltInSeriesType>(props: SeriesProps<T, number>) => {
328349
PriceChart.Series = Series;
329350

330351
const CustomSeries = (props: CustomSeriesProps<number>) => {
331-
const chart = usePriceChart();
332-
const { paneIdx, panePrimitives, attachPanePrimitives, detachPanePrimitives } =
352+
const {
353+
chart,
354+
primitives: chartPrimitives,
355+
onChartPrimitivesAttached,
356+
onChartPrimitivesDetached,
357+
} = usePriceChart();
358+
const { paneIdx, panePrimitives, onPanePrimitivesAttached, onPanePrimitivesDetached } =
333359
useContext(PaneContext);
334360

335361
const _props = mergeProps(
@@ -362,12 +388,17 @@ const CustomSeries = (props: CustomSeriesProps<number>) => {
362388
});
363389

364390
createEffect(() => {
365-
const currentPanePrimitives = panePrimitives();
391+
// If paneIdx is 0, use the primitives from the chart context, otherwise use the primitives from the pane context
392+
const currentPanePrimitives = paneIdx() === 0 ? chartPrimitives() : panePrimitives();
393+
const attachCallback = paneIdx() === 0 ? onChartPrimitivesAttached : onPanePrimitivesAttached;
394+
const detachCallback = paneIdx() === 0 ? onChartPrimitivesDetached : onPanePrimitivesDetached;
366395

367396
attachPanePrimitives(currentPanePrimitives, seriesPane);
397+
attachCallback(currentPanePrimitives);
368398

369399
onCleanup(() => {
370400
detachPanePrimitives(currentPanePrimitives, seriesPane);
401+
detachCallback(currentPanePrimitives);
371402
});
372403
});
373404

0 commit comments

Comments
 (0)