Skip to content

React

React components

The same chart as declarative components, with hooks for the crosshair and for live updates.

React components@ortex-charts/react
Loading 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>
    );
}