React
React components
The same chart as declarative components, with hooks for the crosshair and for live updates.
React components
@ortex-charts/reactLoading react components…
The same chart as declarative components, with hooks for the crosshair and for live updates.
react-chart.ts
import { useEffect, useMemo, useState } from "react";
import {
FinancialChart,
Pane,
Series,
useChart,
useLiveSource,
type IndicatorSpec,
type LiveItem,
type LiveSource,
} from "@ortex-charts/react";
import type { Bar } from "@ortex-charts/math";
/** Indicators are described, not called. Identity is the JSON of each entry. */
const INDICATORS: IndicatorSpec[] = [{ id: "ema", inputs: { length: 21 } }, { id: "rsi", inputs: { length: 14 } }];
/** A live source is anything with a `subscribe` method. This one replays real bars. */
function replaySource(bars: Bar[], everyMs: number): LiveSource<LiveItem> {
return {
subscribe(listener) {
let index = 0;
const timer = window.setInterval(() => listener(bars[index++ % bars.length]), everyMs);
return () => window.clearInterval(timer);
},
};
}
/**
* A child with no output of its own: `useChart()` hands it the chart its
* parent created, and it tidies up after itself like any other effect.
*/
function PriorClose({ price }: { price: number }): null {
const chart = useChart();
useEffect(() => {
const main = chart?.mainSeries();
if (!chart || !main) return;
const id = chart.addPriceLine(main.id, { price, color: "#3AAEA9", lineStyle: "dashed", title: "Prior close" });
return () => chart.removePriceLine(main.id, id);
}, [chart, price]);
return null;
}
function StockChart({
history,
arriving,
shortInterest,
theme,
onStatus,
}: {
history: Bar[];
arriving: Bar[];
shortInterest: Point[];
theme: ExampleTheme;
onStatus: (text: string) => void;
}) {
const [streamed, setStreamed] = useState(0);
// Object props are diffed by identity, so give them a stable one.
const options = useMemo(
() => ({ theme, timeZone: TIME_ZONE, resolution: "1D", watermark: { text: SYMBOL, visible: true, fontSize: 36 } }),
[theme],
);
const siOptions = useMemo(
() => ({ title: "Short interest, % of free float", color: "#A78BFA", priceFormat: { type: "percent" as const, precision: 2 } }),
[],
);
const liveOptions = useMemo(() => ({ onUpdate: () => setStreamed((n) => n + 1) }), []);
// One source for the lifetime of its dependencies, closed on unmount. A real
// page returns `websocketSource({ url, parse })` from this factory.
const source = useLiveSource(() => replaySource(arriving, 900), [arriving]);
return (
<FinancialChart
data={history}
options={options}
indicators={INDICATORS}
live={source}
liveOptions={liveOptions}
style={{ height: "100%" }}
onCrosshairMove={(event) =>
onStatus(
event.time === null
? `${streamed} bars streamed in through useLiveSource.`
: `${new Date(event.time).toISOString().slice(0, 10)} — ${streamed} bars streamed in through useLiveSource.`,
)
}
>
<Pane heightRatio={0.24}>
<Series type="area" data={shortInterest} options={siOptions} />
</Pane>
<PriorClose price={history[history.length - 1].close} />
</FinancialChart>
);
}