diff --git a/package.json b/package.json index 98e3fa3..e71b043 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,11 @@ "dependencies": { "bootstrap": "^3.3.7", "classnames": "^2.2.5", - "d3-scale": "^1.0.5", + "d3-array": "^1.2.0", + "d3-axis": "^1.0.8", + "d3-scale": "^1.0.6", + "d3-selection": "^1.1.0", + "d3-shape": "^1.2.0", "es6-promise": "^4.0.5", "flux": "^3.1.2", "gaugeJS": "^1.3.2", diff --git a/src/components/widget-plot.js b/src/components/widget-plot.js index c02aa97..56b5b2a 100644 --- a/src/components/widget-plot.js +++ b/src/components/widget-plot.js @@ -19,13 +19,12 @@ * along with VILLASweb. If not, see . ******************************************************************************/ -import React, { Component } from 'react'; +import React from 'react'; import Plot from './widget-plot/plot'; -import PlotLegend from './widget-plot/plot-legend'; - -class WidgetPlot extends Component { +//import PlotLegend from './widget-plot/plot-legend'; +class WidgetPlot extends React.Component { render() { const simulator = this.props.widget.simulator; const simulation = this.props.simulation; @@ -38,15 +37,15 @@ class WidgetPlot extends Component { const model = simulation.models.find( model => model.simulator.node === simulator.node && model.simulator.simulator === simulator.simulator ); const chosenSignals = this.props.widget.signals; - simulatorData = this.props.data[simulator.node][simulator.simulator]; + simulatorData = this.props.data[simulator.node][simulator.simulator].values[0]; // Query the signals that will be displayed in the legend legendSignals = model.mapping.reduce( (accum, model_signal, signal_index) => { - if (chosenSignals.includes(signal_index)) { - accum.push({ index: signal_index, name: model_signal.name }); - } - return accum; - }, []); + if (chosenSignals.includes(signal_index)) { + accum.push({ index: signal_index, name: model_signal.name }); + } + return accum; + }, []); } return ( @@ -54,9 +53,12 @@ class WidgetPlot extends Component {

{this.props.widget.name}

- +
- ); } diff --git a/src/components/widget-plot/plot.js b/src/components/widget-plot/plot.js index 7b0c3aa..ee29df4 100644 --- a/src/components/widget-plot/plot.js +++ b/src/components/widget-plot/plot.js @@ -7,135 +7,45 @@ * Unauthorized copying of this file, via any medium is strictly prohibited. **********************************************************************************/ -import React, { Component } from 'react'; -import { LineChart } from 'rd3'; -import { scaleOrdinal, schemeCategory10 } from 'd3-scale'; - -class Plot extends Component { - constructor(props) { - super(props); - - this.chartWrapper = null; - - // Initialize plot size and data - this.state = Object.assign( - { size: { w: 0, h: 0 } }, - this.getPlotInitData(true) - ); - } - - // Get an object with 'invisible' init data for the last minute. - // Include start/end timestamps if required. - getPlotInitData(withRangeTimestamps = false) { - - const initSecondTime = Date.now(); - const initFirstTime = initSecondTime - 1000 * 60; // Decrease 1 min - const values = [{ values: [{x: initFirstTime, y: 0}], strokeWidth: 0 }]; - - let output = withRangeTimestamps? - { sequence: 0, values: values, firstTimestamp: initFirstTime, latestTimestamp: initSecondTime, } : - { sequence: 0, values: values }; - - return output; - } - - componentWillReceiveProps(nextProps) { - let nextData = nextProps.simulatorData; - - // handle plot size - const w = this.chartWrapper.offsetWidth - 20; - const h = this.chartWrapper.offsetHeight - 20; - const currentSize = this.state.size; - if (w !== currentSize.w || h !== currentSize.h) { - this.setState({size: { w, h } }); - } - - // If signals were cleared, clear the plot (triggers a new state) - if (this.signalsWereJustCleared(nextProps)) { this.clearPlot(); return; } - - // If no signals have been selected, just leave - if (nextProps.signals == null || nextProps.signals.length === 0) { return; } - - // Identify simulation reset - if (nextData == null || nextData.length === 0 || nextData.values[0].length === 0) { this.clearPlot(); return; } - - // check if new data, otherwise skip - if (this.state.sequence >= nextData.sequence) { return; } - - this.updatePlotData(nextProps); - - } - - signalsWereJustCleared(nextProps) { - - return this.props.signals && - nextProps.signals && - this.props.signals.length > 0 && - nextProps.signals.length === 0; - } - - clearPlot() { - this.setState( this.getPlotInitData(false) ); - } - - updatePlotData(nextProps) { - let nextData = nextProps.simulatorData; - - // get timestamps - var latestTimestamp = nextData.values[0][nextData.values[0].length - 1].x; - var firstTimestamp = latestTimestamp - nextProps.time * 1000; - var firstIndex; - - if (nextData.values[0][0].x < firstTimestamp) { - // find element index representing firstTimestamp - firstIndex = nextData.values[0].findIndex((value) => { - return value.x >= firstTimestamp; - }); - } else { - firstIndex = 0; - firstTimestamp = nextData.values[0][0].x; - latestTimestamp = firstTimestamp + nextProps.time * 1000; - } - - // copy all values for each signal in time region - var values = []; - nextProps.signals.forEach((signal_index, i, arr) => ( - // Include signal index, useful to relate them to the signal selection - values.push( - { - index: signal_index, - values: nextData.values[signal_index].slice(firstIndex, nextData.values[signal_index].length - 1)}) - )); - - this.setState({ values: values, firstTimestamp: firstTimestamp, latestTimestamp: latestTimestamp, sequence: nextData.sequence }); - } +import React from 'react'; +import { scaleLinear, scaleTime } from 'd3-scale'; +import { extent, max } from 'd3-array'; +import { line } from 'd3-shape'; +import { axisBottom, axisLeft } from 'd3-axis'; +import { select } from 'd3-selection'; +class Plot extends React.Component { render() { - // Make tick count proportional to the plot width using a rough scale ratio - var tickCount = Math.round(this.state.size.w / 80); + const leftMargin = 30; + const bottomMargin = 20; + const values = 100; - return ( -
this.chartWrapper = domNode }> - {this.state.sequence != null && - { if (d != null) { return new Date(d.x); } }} - xAxisTickCount={ tickCount } - yAxisLabel={ this.props.yAxisLabel } - hoverAnimation={false} - circleRadius={0} - domain={{ x: [this.state.firstTimestamp, this.state.latestTimestamp] }} - /> - } -
+ let data = this.props.data; + + if (data.length > values) { + data = data.slice(data.length - values); + } + + const xScale = scaleTime().domain(extent(data, p => new Date(p.x))).range([leftMargin, this.props.width]); + const yScale = scaleLinear().domain(extent(data, p => p.y)).range([this.props.height, bottomMargin]); + + const xAxis = axisBottom().scale(xScale).ticks(5); + const yAxis = axisLeft().scale(yScale).ticks(5); + + const sparkLine = line().x(p => xScale(p.x)).y(p => yScale(p.y)); + const linePath = sparkLine(data); + + return( + + select(node).call(xAxis)} style={{ transform: `translateY(${this.props.height}px)` }} /> + select(node).call(yAxis)} style={{ transform: `translateX(${leftMargin}px)`}} /> + + + + + ); } - } export default Plot;