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