Saturday, 28 July 2018

react chart 2 whisker series

project link: https://chuanshuoge1-react-chart3.herokuapp.com/

left random real time data, right whisker chart data, bottom whisker media

change x, y zoom to see detail or big picture

change group to set # of data points in each whisker

whiskerChart.js


import React, { Component } from 'react';
import '../App.css';
import '../../node_modules/react-vis/dist/style.css';

import { withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import TextField from '@material-ui/core/TextField';

import { Media } from 'reactstrap';
import whiskerPic from '../stock.png'

import {
    XYPlot,
    XAxis,
    YAxis,
    VerticalGridLines,
    HorizontalGridLines,
    LineSeries,
    WhiskerSeries
} from 'react-vis';


const styles = theme => ({
    textFieldShort: {
        margin: 0,
        width: 100,
    },
});

class WhiskerChart extends Component {
    constructor(props) {
        super(props);

        this.state = {
            realTimeData: [],
            lastY: 10,
            lastX: 0,
            lineSeriesXNum: 20,
            whiskerSeriesXNum: 10,
            whiskerSeriesYNum: 3,
            whiskerSeriesGroup: 5,
            whiskerGroupPosition: 0,
            whiskerCurrent: { x: 0, y: 10, xVariance: 0, yVariance: 2, color: '#006600' },
            lastWhiskerY: 10,
            lastWhiskerX: 0,
            whiskerGain: [],
            whiskerLoss: [],
            barPlot: [],
        };
    }

    componentDidMount() {
        this.myTimer = setInterval(() => this.handle_timer(), 1000);
    }

    componentWillUnmount() {
        clearInterval(this.myTimer);
    }

    async handle_timer() {
        //handle real time data
        const stateRealTimeData = [...this.state.realTimeData];
        const xValue = this.state.lastX;

        //generate next y = current y +- 0.5, y can't be negative
        let yValue = Math.random().toFixed(2) - 0.5 + this.state.lastY;
        yValue < 0 ? yValue = 0 : null;

        const newData = { x: xValue, y: yValue };

        //keep real time data at 300 max, new data in, old out
        stateRealTimeData.length > 300 ?
            stateRealTimeData.splice(0, 1) : null;

        stateRealTimeData.push(newData);

        //save real time data
        await this.setState({
            realTimeData: stateRealTimeData,
            lastY: yValue,
            lastX: xValue + 1,
        })

        //handle whiskerData
        const whiskerGain = [...this.state.whiskerGain];
        const whiskerLoss = [...this.state.whiskerLoss];

        //whisker data is a group of real time data, whiskerSeriesGroup determines the # of data in the group
        if (this.state.whiskerGroupPosition === this.state.whiskerSeriesGroup) {
            await this.setState({ whiskerGroupPosition: 0 });

            //slice last few data from real time data
            const slicedData = stateRealTimeData.slice(stateRealTimeData.length - this.state.whiskerSeriesGroup, stateRealTimeData.length);

            //find min max of the sliced
            const sliceYArray = slicedData.map((item, index) => { return item.y });
            const sliceYMax = Math.max(...sliceYArray);
            const sliceYMin = Math.min(...sliceYArray);

            //the upper part of the whisker
            const whiskerGainNew = {
                x: slicedData[0].x,
                y: (this.state.lastWhiskerY + sliceYMax) / 2,
                xVariance: 0,
                yVariance: sliceYMax - this.state.lastWhiskerY,
            }

            //the lower part of the whisker
            const whiskerLossNew = {
                x: slicedData[0].x,
                y: (this.state.lastWhiskerY + sliceYMin) / 2,
                xVariance: 0,
                yVariance: this.state.lastWhiskerY - sliceYMin,
            }

            whiskerGain.push(whiskerGainNew);
            whiskerLoss.push(whiskerLossNew);

            //keep whisker data at 50 max, new data in, old out
            whiskerGain.length > 50 ?
                whiskerGain.splice(0, 1) : null;

            whiskerLoss.length > 50 ?
                whiskerLoss.splice(0, 1) : null;

            //save whisker data
            await this.setState({ whiskerGain: whiskerGain, whiskerLoss: whiskerLoss });

            //plot change from last
            const barPlot = [...this.state.barPlot];
            //keep bar plot at 50 max
            barPlot.length > 50 ?
                barPlot.splice(0, 1) : null;

            //current and previous bar point
            const barDataNow = { x: slicedData[0].x, y: newData.y };
            const barDataPre = { x: slicedData[0].x, y: this.state.lastWhiskerY }
            const barColor = barDataPre.y >= barDataNow.y ? '#cc0000' : '#006600';

            //plot bar
            barPlot.push(
                <LineSeries data={[barDataPre, barDataNow]}
                    stroke={barColor} strokeWidth={100 / this.state.whiskerSeriesXNum} />
            )

            //save bar data
            await this.setState({ barPlot: barPlot });

            //record whiskerY, whiskerX
            await this.setState({
                lastWhiskerY: newData.y,
                lastWhiskerX: newData.x,
            });

        }

        //handle real time data in the newest whisker
        const whiskerNewest = {
            x: this.state.lastWhiskerX,
            y: (this.state.lastWhiskerY + newData.y) / 2,
            xVariance: 0,
            yVariance: Math.abs(this.state.lastWhiskerY - newData.y),
            color: this.state.lastWhiskerY > newData.y ? '#cc0000' : '#006600',
        }

        //save the newest whisker and increment # of data in whisker group by 1
        await this.setState(prevState => {
            return {
                whiskerCurrent: whiskerNewest,
                whiskerGroupPosition: prevState.whiskerGroupPosition + 1,
            }
        });
    }

    handleLineSeriesXNumChange(value) {
        this.setState({ lineSeriesXNum: parseInt(value) })
    }

    handleWhiskerSeriesXNumChange(value) {
        this.setState({ whiskerSeriesXNum: parseInt(value) })
    }

    handleWhiskerSeriesYNumChange(value) {
        this.setState({ whiskerSeriesYNum: parseInt(value) });
    }

    handleWhiskerSeriesGroupChange(value) {
        this.setState({ whiskerSeriesGroup: parseInt(value) });
    }

    render() {
        const { classes } = this.props;
        const stateRealTimeData = [...this.state.realTimeData];
        const whiskerGain = [...this.state.whiskerGain];
        const whiskerLoss = [...this.state.whiskerLoss];
        const barPlot = [...this.state.barPlot];

        //lineSeriesXNum defines the max # of real time data on chart
        const realTimeDataTrimed = stateRealTimeData.length < this.state.lineSeriesXNum ?
            stateRealTimeData :
            stateRealTimeData.splice(stateRealTimeData.length - this.state.lineSeriesXNum, this.state.lineSeriesXNum)

        //whiskerSeriesXNum defines the max # of whiskers on chart
        const whiskerGainTrimed = whiskerGain.length < this.state.whiskerSeriesXNum ?
            whiskerGain :
            whiskerGain.splice(whiskerGain.length - this.state.whiskerSeriesXNum, this.state.whiskerSeriesXNum)

        const whiskerLossTrimed = whiskerLoss.length < this.state.whiskerSeriesXNum ?
            whiskerLoss :
            whiskerLoss.splice(whiskerLoss.length - this.state.whiskerSeriesXNum, this.state.whiskerSeriesXNum)

        //whiskerX tick value
        const whiskerXTickValue = whiskerGainTrimed.map((item, index) => { return item.x });

        //whiskerX domain
        const whiskerXDomain = [whiskerXTickValue[0], this.state.lastWhiskerX + this.state.whiskerSeriesGroup];

        //whiskerY domain
        const whiskerYDomainMin = this.state.lastWhiskerY - this.state.whiskerSeriesYNum < 0 ? 0 : this.state.lastWhiskerY - this.state.whiskerSeriesYNum;
        const whiskerYDomain = [whiskerYDomainMin, this.state.lastWhiskerY + this.state.whiskerSeriesYNum];

        //whiskerSeriesXNum defines # of barplots
        const barPlotTrimed = barPlot.length < this.state.whiskerSeriesXNum ?
            barPlot.map((item, index) => item) :
            barPlot
                .splice(barPlot.length - this.state.whiskerSeriesXNum, this.state.whiskerSeriesXNum)
                .map((item, index) => item)

        return (
            <div>
                <Grid container spacing={24}>
                    <Grid item xs={12} sm={6}>
                        <h4 className="my-subTitle">Real Time Stock Price</h4>

                        {/*-------real time chart zoom-------*/}
                        <TextField
                            required
                            label='X Zoom'
                            onChange={(e) => this.handleLineSeriesXNumChange(e.target.value)}
                            value={this.state.lineSeriesXNum}
                            className={classes.textFieldShort}
                        />

                        <input type="range" min="1" max="300"
                            value={this.state.lineSeriesXNum}
                            onChange={(e) => this.handleLineSeriesXNumChange(e.target.value)}
                        />

                        {/*-------real time chart plot-------*/}
                        <XYPlot
                            width={300}
                            height={300}>
                            <VerticalGridLines />
                            <HorizontalGridLines />
                            <XAxis title="time" />
                            <YAxis title="$/share" />
                            <LineSeries data={realTimeDataTrimed} />
                        </XYPlot>
                    </Grid>

                    <Grid item xs={12} sm={6}>
                        <h4 className="my-subTitle">Whisker Stock Chart</h4>

                        {/*-------whisker chart X zoom-------*/}
                        <TextField
                            required
                            label='X Zoom'
                            onChange={(e) => this.handleWhiskerSeriesXNumChange(e.target.value)}
                            value={this.state.whiskerSeriesXNum}
                            className={classes.textFieldShort}
                        />

                        <input type="range" min="1" max="50"
                            value={this.state.whiskerSeriesXNum}
                            onChange={(e) => this.handleWhiskerSeriesXNumChange(e.target.value)}
                        /><br />

                        {/*-------whisker chart Y zoom-------*/}
                        <TextField
                            required
                            label='Y Zoom'
                            onChange={(e) => this.handleWhiskerSeriesYNumChange(e.target.value)}
                            value={this.state.whiskerSeriesYNum}
                            className={classes.textFieldShort}
                        />

                        <input type="range" min="1" max="10"
                            value={this.state.whiskerSeriesYNum}
                            onChange={(e) => this.handleWhiskerSeriesYNumChange(e.target.value)}
                        /><br />

                        {/*-------whisker chart Group-------*/}
                        <TextField
                            required
                            label='Group'
                            onChange={(e) => this.handleWhiskerSeriesGroupChange(e.target.value)}
                            value={this.state.whiskerSeriesGroup}
                            className={classes.textFieldShort}
                        />

                        <input type="range" min="2" max="15"
                            value={this.state.whiskerSeriesGroup}
                            onChange={(e) => this.handleWhiskerSeriesGroupChange(e.target.value)}
                        />

                        {/*-------whisker chart plot-------*/}
                        <XYPlot
                            width={300}
                            height={300}
                            xDomain={whiskerXDomain}
                            yDomain={whiskerYDomain}>

                            <VerticalGridLines />
                            <HorizontalGridLines />
                            <XAxis title="time" tickValues={whiskerXTickValue} tickLabelAngle={-90} />
                            <YAxis title="$/share" />

                            {/*--------whisker data up to current-------*/}
                            <WhiskerSeries data={whiskerGainTrimed} color='#006600' />
                            <WhiskerSeries data={whiskerLossTrimed} color='#cc0000' />

                            {/*--------current whisker data-------*/}
                            <WhiskerSeries data={[this.state.whiskerCurrent]}
                                color={this.state.whiskerCurrent.color} />

                            {/*-------bar data-------*/}
                            {barPlotTrimed}
                        </XYPlot>
                    </Grid>

                    <Grid item xs={12} sm={12}>
                        {/*-------whisker chart tutorial-------*/}
                        <Media>
                            <Media left href="#">
                                <img src={whiskerPic} alt="whisker pic" />
                            </Media>
                            <Media body>
                                <Media heading>Whisker Chart Tutorial</Media>
                                <a href="https://jpgraph.net/download/manuals/chunkhtml/ch15s04.html" target="_blank">tutorial link</a>
                            </Media>
                        </Media>
                    </Grid>
                </Grid>
            </div>
        );
    }
}

export default withStyles(styles)(WhiskerChart);


reference:
http://chuanshuoge2.blogspot.com/2018/07/react-chart-2-whisker-series.html

No comments:

Post a Comment