简体   繁体   中英

Trying to make master-detail charts in ReactJS

I'm trying to replicate this example: https://www.highcharts.com/demo/dynamic-master-detail but with React (+Hooks).
My trouble is with the detailChart because I already got the master to work, I know that the detailChart will be generated by callback (I'm still not very good at the callbacks concept).

In the Highcharts documentation says I can make a callback function in chart.events.load, which is what I decided to do in the masterChart.
However I'm getting an error in the createDetails function (the callback function) saying

undefined is not an object (evaluating 'masterChart.series[0]')

which is weird because I do the same when creating the masterChart and it worked, so the problem can't be my RData object I think.

This is my code by now:

import React, {useEffect,useState} from 'react';
import Highcharts from 'highcharts';
import HighchartsReact from 'highcharts-react-official';
import RData from '../data/RData'

const RChart = () =>  {
    const[masterChart,setMasterChart] = useState([]);
    const[detailChart,setDetailChart] = useState([]);

    //updating masterChart
    useEffect(()=>{
        setMasterChart({
            chart: {
                reflow: false,
                borderWidth: 0,
                backgroundColor: "#ffffff",
                marginLeft: 50,
                marginRight: 20,
                zoomType: 'x',
                events: {

                    // listen to the selection event on the master chart to update the
                    // extremes of the detail chart
                    selection: function (event) {
                        var extremesObject = event.xAxis[0],
                            min = extremesObject.min,
                            max = extremesObject.max,
                            detailData = [],
                            xAxis = this.xAxis[0];

                        // reverse engineer the last part of the data
                        RData.each(this.series[0].RData, function () {
                            if (this.x > min && this.x < max) {
                                detailData.push([this.x, this.y]);
                            }
                        });

                        // move the plot bands to reflect the new detail span
                        xAxis.removePlotBand('mask-before');
                        xAxis.addPlotBand({
                            id: 'mask-before',
                            from: RData[0][0],
                            to: min,
                            color: 'rgba(0, 0, 0, 0.2)'
                        });

                        xAxis.removePlotBand('mask-after');
                        xAxis.addPlotBand({
                            id: 'mask-after',
                            from: max,
                            to: RData[RData.length - 1][0],
                            color: 'rgba(255, 255, 0, 0.2)'
                        });


                        detailChart.series[0].setData(detailData);

                        return false;
                    },
                    load:createDetail(masterChart) //callback!!
                }
            },
            title: {
                text: null
            },
            accessibility: {
                enabled: false
            },
            xAxis: {
                type: 'datetime',
                showLastTickLabel: true,
                maxZoom: 14 * 24 * 3600000, // fourteen days
                plotBands: [{
                    id: 'mask-before',
                    from: RData[0][0],
                    to: RData[RData.length - 1][0],
                    color: 'rgba(0, 0, 0, 0.2)'
                }],
                title: {
                    text: null
                }
            },
            yAxis: {
                gridLineWidth: 0,
                labels: {
                    enabled: false
                },
                title: {
                    text: null
                },
                min: 0.6,
                showFirstLabel: false
            },
            tooltip: {
                formatter: function () {
                    return false;
                }
            },
            legend: {
                enabled: false
            },
            credits: {
                enabled: false
            },
            plotOptions: {
                series: {
                    fillColor: {
                        linearGradient: [0, 0, 0, 255],
                        stops: [
                            [0, Highcharts.getOptions().colors[0]],
                            [1, 'rgba(0,255,255,0)']
                        ]
                    },
                    lineWidth: 1,
                    marker: {
                        enabled: false
                    },
                    shadow: false,
                    states: {
                        hover: {
                            lineWidth: 1
                        }
                    },
                    enableMouseTracking: false
                }
            },

            series: [{
                type: 'area',
                name: 'USD to EUR',
                pointInterval: 24 * 3600 * 1000,
                pointStart: RData[0][0],
                data: RData
            }],

            exporting: {
                enabled: false
            }
        })
    },[]);

    //callback function to create detail chart
    const createDetail = (masterChart) => {
        // prepare the detail chart
        var detailData = [],
        detailStart = RData[0][0];

        //Error here
        RData.each(masterChart.series[0].RData, function () {
            if (this.x >= detailStart) {
                detailData.push(this.y);
            }
        });

        setDetailChart({
            chart: {
                marginBottom: 120,
                reflow: false,
                marginLeft: 50,
                marginRight: 20,
                style: {
                    position: 'absolute'
                }
            },
            credits: {
                enabled: false
            },
            title: {
                text: 'Historical USD to EUR Exchange Rate',
                align: 'left'
            },
            subtitle: {
                text: 'Select an area by dragging across the lower chart',
                align: 'left'
            },
            xAxis: {
                type: 'datetime'
            },
            yAxis: {
                title: {
                    text: null
                },
                maxZoom: 0.1
            },
            tooltip: {
                formatter: function () {
                    var point = this.points[0];
                    return '<b>' + point.series.name + '</b><br/>' + Highcharts.dateFormat('%A %B %e %Y', this.x) + ':<br/>' +
                        '1 USD = ' + Highcharts.numberFormat(point.y, 2) + ' EUR';
                },
                shared: true
            },
            legend: {
                enabled: false
            },
            plotOptions: {
                series: {
                    marker: {
                        enabled: false,
                        states: {
                            hover: {
                                enabled: true,
                                radius: 3
                            }
                        }
                    }
                }
            },
            series: [{
                name: 'USD to EUR',
                pointStart: detailStart,
                pointInterval: 24 * 3600 * 1000,
                data: detailData
            }],

            exporting: {
                enabled: false
            }
        });
    }


    return(
        <React.Fragment>
            <HighchartsReact highcharts={Highcharts} options={masterChart} />
            <HighchartsReact highcharts={Highcharts} options={detailChart} />
        </React.Fragment>
    );
} 

export default RChart;

I'm open to any criticism and better ways to this.

(Edited) Try adding setMasterChart and setDetailChart in the empty array as the second argument of your effect hook. Just an idea.

I think that a better approach is to set Hooks options into one functional Component and pass them as a props into the functions which will render each chart.

Demo: https://stackblitz.com/edit/react-lwkgkv?file=index.js

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM