Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solved! Zoom / Pan a streaming chart with code example #1771

Open
frbju opened this issue Nov 16, 2022 · 1 comment
Open

Solved! Zoom / Pan a streaming chart with code example #1771

frbju opened this issue Nov 16, 2022 · 1 comment
Milestone

Comments

@frbju
Copy link

frbju commented Nov 16, 2022

ezgif com-gif-maker

Solved! zoom & pan on a streaming "updating" chart.

This way i can control each axis and make overlay charts that share one or both axis. In the example above, my overlay indicator only use the yAxis and have its own xAxis.

yAxis drag = scale,
xAxis drag = scale left,
plotarea drag = pan.
Plotarea scroll = uniform zoom.

Posting code here if anyone needs it. Might give some ideas on how to implement it.

App

return (
	<>
		<ChartCanvas id={0} {...data}>
			<StockChart />
                        <Books /> <--- Overlay indicator
		</ChartCanvas>
	</>
);

Chart canvas component:

const ChartCanvas = (props) => {
    const ref = useRef(null);
    const [xExtent, setxExtent] = useState([0, 0])
    const [yExtent, setyExtent] = useState([0, 0])
    const [kxy, setkxy] = useState({ k: 1, x: 0, y: 0, })
    const [x, setx] = useState(0)
    const [y, sety] = useState(0)

    useEffect(() => {
        if (props.candlesticks.length > 0) {
            let h = ref.current.clientHeight;
            let w = ref.current.clientWidth;

            const min = props.candlesticks[props.candlesticks.length - 1].date;
            const max = props.candlesticks[0].date;
            const high = Math.max(...props.candlesticks.map(c => c.high * 1.005));
            const low = Math.min(...props.candlesticks.map(c => c.low * 0.995));
            
            const xScale = (max - min) * (kxy.k - 1);
            const xRange = (max - min) * (x / w);
            const xOffset = (max - min) * ((kxy.x * -1) / w);
            const X = [min - (xRange - xOffset) + xScale, max + xOffset - xScale];
            setxExtent(X)

            const yScale = (high - low) * (kxy.k - 1);
            const yRange = (high - low) * (y / h);
            const yOffset = (high - low) * ((kxy.y) / h);
            const Y = [low - (yRange - yOffset) + yScale, high + (yRange + yOffset) - yScale];
            setyExtent(Y)
        }
    }, [props, kxy, x, y])

    const options = {
        xExtent: xExtent,
        yExtent: yExtent,
        setyExtent: (e) => setyExtent(e),
        setxExtent: (e) => setxExtent(e),
        setkxy: (e) => setkxy(e),
        setx: (e) => setx(e),
        sety: (e) => sety(e),
        ...props
    }

    return (
        <div ref={ref} className="chart-canvas">
            {props.candlesticks.length > 0 && (
                <>
                    {props.children.map((child, i) => {
                        if (isValidElement(child)) {
                            return cloneElement(child, { key: `child-${props.id}-${i}`, ...options });
                        }
                    })}
                </>
            )}
        </div>
    )
}
export default ChartCanvas

StockChart component:

const StockChart = (props) => {
    const ref = useRef(null);
    const data = props.candlesticks
    const xScale = d3.scaleTime();
    const yScale = d3.scaleLinear();
    const gridlines = fc.annotationSvgGridline();
    const candlesticks = fc
        .autoBandwidth(fc.seriesSvgCandlestick())
        .widthFraction(0.5)
        .crossValue((d) => d.date - (30 * 60 * 1000))
        .openValue((d) => d.open)
        .highValue((d) => d.high)
        .lowValue((d) => d.low)
        .closeValue((d) => d.close);

    const lastPrice = fc
        .seriesSvgLine()
        .crossValue(d => d.date)
        .mainValue(data[0].close)
        .decorate(sel => sel.classed("last_price", true));

    const series = fc.seriesSvgMulti()
        .series([gridlines, candlesticks, lastPrice]);

    const zoom = (zoomType) =>
        d3.zoom()
            .on('zoom', event => {
                let t = event.transform

                if (zoomType === 'xy') {
                    props.setkxy(t)
                }
                if (zoomType === 'x') {
                    props.setx(t.x)
                }
                if (zoomType === 'y') {
                     props.sety(t.y)
                }
            })

    const chart = fc.chartCartesian({ xScale: xScale, yScale: yScale })
    chart.xDomain(props.xExtent)
    chart.yDomain(props.yExtent)
    chart.svgPlotArea(series)
        .decorate((sel) => {
            sel.enter().selectAll(".plot-area").call(zoom('xy'));
            sel.enter().selectAll(".x-axis").call(zoom('x'));
            sel.enter().selectAll(".y-axis").call(zoom('y'));
        });
    function render() {
        d3.select(ref.current)
            .datum(data)
            .call(chart);
    }
    render()

    return (
        <div ref={ref} className="chart chart-stockchart" />
    )
}
export default StockChart
@frbju frbju changed the title Zoom / Pan a streaming chart Solved! Zoom / Pan a streaming chart with code example Nov 19, 2022
@ColinEberhardt
Copy link
Member

Glad you’ve solved this and thanks for the update 👍

@cfisher-scottlogic cfisher-scottlogic added this to the Triage issues milestone Apr 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants