From 0cba63991aa1413de5fffb3fe03893b7caefe385 Mon Sep 17 00:00:00 2001 From: Markus Grigull Date: Thu, 13 Apr 2017 11:12:04 +0200 Subject: [PATCH] Revert "Merge branch 'additional-widgets' into 'develop'" This reverts merge request !5 --- package.json | 4 +- .../dialog/edit-widget-orientation.js | 61 ---- .../dialog/edit-widget-signal-control.js | 54 ---- .../dialog/edit-widget-signal-type-control.js | 64 ---- .../dialog/edit-widget-signals-control.js | 68 ----- .../dialog/edit-widget-simulator-control.js | 44 --- src/components/dialog/edit-widget.js | 22 +- src/components/widget-button.js | 33 -- src/components/widget-gauge.js | 110 ------- src/components/widget-number-input.js | 69 ----- src/components/widget-plot-table.js | 160 +++++----- src/components/widget-plot.js | 93 ++++-- src/components/widget-plot/plot-legend.js | 31 -- src/components/widget-plot/plot.js | 115 ------- src/components/widget-slider.js | 73 ----- src/components/widget-table.js | 4 +- src/containers/visualization.js | 57 +--- src/containers/widget.js | 30 +- src/styles/widgets.css | 287 ++---------------- 19 files changed, 171 insertions(+), 1208 deletions(-) delete mode 100644 src/components/dialog/edit-widget-orientation.js delete mode 100644 src/components/dialog/edit-widget-signal-control.js delete mode 100644 src/components/dialog/edit-widget-signal-type-control.js delete mode 100644 src/components/dialog/edit-widget-signals-control.js delete mode 100644 src/components/dialog/edit-widget-simulator-control.js delete mode 100644 src/components/widget-button.js delete mode 100644 src/components/widget-gauge.js delete mode 100644 src/components/widget-number-input.js delete mode 100644 src/components/widget-plot/plot-legend.js delete mode 100644 src/components/widget-plot/plot.js delete mode 100644 src/components/widget-slider.js diff --git a/package.json b/package.json index 7d9df16..0116f0a 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,7 @@ "react-notification-system": "^0.2.13", "react-rnd": "^4.2.2", "react-router": "^3.0.2", - "superagent": "^3.5.0", - "gaugeJS": "^1.3.2", - "d3-scale": "^1.0.5" + "superagent": "^3.5.0" }, "devDependencies": { "react-scripts": "0.9.3" diff --git a/src/components/dialog/edit-widget-orientation.js b/src/components/dialog/edit-widget-orientation.js deleted file mode 100644 index 76aa874..0000000 --- a/src/components/dialog/edit-widget-orientation.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * File: edit-widget-orientation.js - * Author: Ricardo Hernandez-Montoya - * Date: 10.04.2017 - * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC - * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - **********************************************************************************/ - -import React, { Component } from 'react'; -import { FormGroup, Col, Row, Radio, ControlLabel } from 'react-bootstrap'; - -import WidgetSlider from '../widget-slider'; - -class EditWidgetOrientation extends Component { - constructor(props) { - super(props); - - this.state = { - widget: {} - }; - } - - componentWillReceiveProps(nextProps) { - // Update state's widget with props - this.setState({ widget: nextProps.widget }); - } - - handleOrientationChange(orientation) { - this.props.handleChange({ target: { id: 'orientation', value: orientation } }); - } - - render() { - - // The tag shouldn't be necessary, but it gives height to the row while combining horizontal and vertical forms - return ( - - - - Orientation - - - { - Object.keys(WidgetSlider.OrientationTypes).map( (type) => { - let value = WidgetSlider.OrientationTypes[type].value; - let name = WidgetSlider.OrientationTypes[type].name; - - return ( - this.handleOrientationChange(value)}> - { name } - ) - }) - } - - - - ); - } -} - -export default EditWidgetOrientation; \ No newline at end of file diff --git a/src/components/dialog/edit-widget-signal-control.js b/src/components/dialog/edit-widget-signal-control.js deleted file mode 100644 index fd2cd5e..0000000 --- a/src/components/dialog/edit-widget-signal-control.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * File: edit-widget-signal-control.js - * Author: Ricardo Hernandez-Montoya - * Date: 03.04.2017 - * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC - * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - **********************************************************************************/ - -import React, { Component } from 'react'; -import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap'; - -class EditWidgetSignalControl extends Component { - constructor(props) { - super(props); - - this.state = { - widget: { - simulator: '' - } - }; - } - - componentWillReceiveProps(nextProps) { - // Update state's widget with props - this.setState({ widget: nextProps.widget }); - } - - render() { - // get selected simulation model - var simulationModel = {}; - - if (this.props.simulation) { - this.props.simulation.models.forEach((model) => { - if (model.simulation === this.state.widget.simulation) { - simulationModel = model; - } - }); - } - - return ( - - Signal - this.props.handleChange(e)}> - {simulationModel.mapping.map((signal, index) => ( - - ))} - - - ); - } -} - -export default EditWidgetSignalControl; \ No newline at end of file diff --git a/src/components/dialog/edit-widget-signal-type-control.js b/src/components/dialog/edit-widget-signal-type-control.js deleted file mode 100644 index 8978186..0000000 --- a/src/components/dialog/edit-widget-signal-type-control.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * File: edit-widget-signal-type-control.js - * Author: Ricardo Hernandez-Montoya - * Date: 03.04.2017 - * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC - * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - **********************************************************************************/ - -import React, { Component } from 'react'; -import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap'; - -class EditWidgetSignalTypeControl extends Component { - constructor(props) { - super(props); - - this.state = { - widget: {} - }; - } - - componentWillReceiveProps(nextProps) { - // Update state's widget with props - this.setState({ widget: nextProps.widget }); - } - - render() { - // get selected simulation model - var simulationModel = {}; - - if (this.props.simulation) { - this.props.simulation.models.forEach((model) => { - if (model.simulation === this.state.widget.simulation) { - simulationModel = model; - } - }); - } - - // Obtain unique signal types with the help of dictionary keys - var signalTypes = Object.keys(simulationModel.mapping.reduce( (collection, signal) => { - var lower = signal.type.toLowerCase(); - collection[lower] = ''; - return collection; - }, {})); - - var capitalize = (str) => { return str.charAt(0).toUpperCase() + str.slice(1); } - - var selectedValue = signalTypes.includes(this.state.widget.signalType) ? this.state.widget.signalType : ''; - - return ( - - Signal type - this.props.handleChange(e)}> - - {signalTypes.map((type, index) => ( - - ))} - - - ); - } -} - -export default EditWidgetSignalTypeControl; \ No newline at end of file diff --git a/src/components/dialog/edit-widget-signals-control.js b/src/components/dialog/edit-widget-signals-control.js deleted file mode 100644 index 2842618..0000000 --- a/src/components/dialog/edit-widget-signals-control.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * File: edit-widget-signals-control.js - * Author: Ricardo Hernandez-Montoya - * Date: 03.04.2017 - * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC - * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - **********************************************************************************/ - -import React, { Component } from 'react'; -import { FormGroup, Checkbox, ControlLabel } from 'react-bootstrap'; - -class EditWidgetSignalsControl extends Component { - constructor(props) { - super(props); - - this.state = { - widget: { - simulator: '', - preselectedSignals: [] - } - }; - } - - componentWillReceiveProps(nextProps) { - // Update state's widget with props - this.setState({ widget: nextProps.widget }); - } - - handleSignalChange(checked, index) { - var signals = this.state.widget.preselectedSignals; - var new_signals; - - if (checked) { - // add signal - new_signals = signals.concat(index); - } else { - // remove signal - new_signals = signals.filter( (idx) => idx !== index ); - } - - this.props.handleChange({ target: { id: 'preselectedSignals', value: new_signals } }); - } - - render() { - // get selected simulation model - var simulationModel = {}; - - if (this.props.simulation) { - this.props.simulation.models.forEach((model) => { - if (model.simulation === this.state.widget.simulation) { - simulationModel = model; - } - }); - } - - return ( - - Signals - {simulationModel.mapping.map((signal, index) => ( - this.handleSignalChange(e.target.checked, index)}>{signal.name} - ))} - - ); - } -} - -export default EditWidgetSignalsControl; \ No newline at end of file diff --git a/src/components/dialog/edit-widget-simulator-control.js b/src/components/dialog/edit-widget-simulator-control.js deleted file mode 100644 index baa9679..0000000 --- a/src/components/dialog/edit-widget-simulator-control.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * File: edit-widget-simulator-control.js - * Author: Ricardo Hernandez-Montoya - * Date: 03.04.2017 - * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC - * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - **********************************************************************************/ - -import React, { Component } from 'react'; -import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap'; - -class EditWidgetSimulatorControl extends Component { - constructor(props) { - super(props); - - this.state = { - widget: { - simulator: '' - } - }; - } - - componentWillReceiveProps(nextProps) { - // Update state's widget with props - this.setState({ widget: nextProps.widget }); - } - - render() { - - return ( - - Simulator - this.props.handleChange(e)}> - {this.props.simulation.models.map((model, index) => ( - - ))} - - - ); - } -} - -export default EditWidgetSimulatorControl; \ No newline at end of file diff --git a/src/components/dialog/edit-widget.js b/src/components/dialog/edit-widget.js index a5a2daa..2b7a21e 100644 --- a/src/components/dialog/edit-widget.js +++ b/src/components/dialog/edit-widget.js @@ -16,10 +16,6 @@ import EditValueWidget from './edit-widget-value'; import EditPlotWidget from './edit-widget-plot'; import EditTableWidget from './edit-widget-table'; import EditImageWidget from './edit-widget-image'; -import EditWidgetSimulatorControl from './edit-widget-simulator-control'; -import EditWidgetSignalControl from './edit-widget-signal-control'; -import EditWidgetSignalsControl from './edit-widget-signals-control'; -import EditWidgetOrientation from './edit-widget-orientation'; class EditWidgetDialog extends Component { static propTypes = { @@ -79,8 +75,6 @@ class EditWidgetDialog extends Component { render() { // get widget part var widgetDialog = null; - // Use a list to concatenate the controls according to the widget type - var dialogControls = []; if (this.props.widget) { if (this.props.widget.type === 'Value') { @@ -91,20 +85,6 @@ class EditWidgetDialog extends Component { widgetDialog = this.validateForm(id)} simulation={this.props.simulation} handleChange={(e, index) => this.handleChange(e, index)} />; } else if (this.props.widget.type === 'Image') { widgetDialog = this.validateForm(id)} simulation={this.props.simulation} handleChange={(e, index) => this.handleChange(e, index)} />; - } else if (this.props.widget.type === 'Gauge') { - dialogControls.push( - this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} />, - this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} /> - ) - } else if (this.props.widget.type === 'PlotTable') { - dialogControls.push( - this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} />, - this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} /> - ) - } else if (this.props.widget.type === 'Slider') { - dialogControls.push( - this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} />, - ) } } @@ -116,7 +96,7 @@ class EditWidgetDialog extends Component { this.handleChange(e)} /> - { dialogControls } + {widgetDialog} diff --git a/src/components/widget-button.js b/src/components/widget-button.js deleted file mode 100644 index 72f33fa..0000000 --- a/src/components/widget-button.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * File: widget-button.js - * Author: Ricardo Hernandez-Montoya - * Date: 29.03.2017 - * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC - * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - **********************************************************************************/ - -import React, { Component } from 'react'; - -class WidgetButton extends Component { - - action(e) { - e.target.blur(); // Remove focus - console.log('Button widget action'); - } - - render() { - return ( -
- { this.props.editing ? ( - - ) : ( - - ) - } -
- ); - } -} - -export default WidgetButton; \ No newline at end of file diff --git a/src/components/widget-gauge.js b/src/components/widget-gauge.js deleted file mode 100644 index a61c3e8..0000000 --- a/src/components/widget-gauge.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * File: widget-gauge.js - * Author: Ricardo Hernandez-Montoya - * Date: 31.03.2017 - * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC - * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - **********************************************************************************/ - -import React, { Component } from 'react'; -import { Gauge } from 'gaugeJS'; - -class WidgetGauge extends Component { - constructor(props) { - super(props); - - this.gaugeCanvas = null; - this.gauge = null; - - this.state = { - value: 0 - }; - } - - staticLabels(widget_height) { - var label_font_size = widget_height * 0.055; // font scaling factor - return { - font: label_font_size + 'px "Helvetica Neue"', - labels: [0.0, 0.1, 0.5, 0.9, 1.0], - color: "#000000", - fractionDigits: 1 - } - } - - computeGaugeOptions(widget_height) { - return { - angle: -0.25, - lineWidth: 0.2, - pointer: { - length: 0.6, - strokeWidth: 0.035 - }, - radiusScale: 0.9, - colorStart: '#6EA2B0', - colorStop: '#6EA2B0', - strokeColor: '#E0E0E0', - highDpiSupport: true, - staticLabels: this.staticLabels(widget_height) - }; - } - - componentDidMount() { - const opts = this.computeGaugeOptions(this.props.widget.height); - this.gauge = new Gauge(this.gaugeCanvas).setOptions(opts); - this.gauge.maxValue = 1; - this.gauge.setMinValue(0); - this.gauge.animationSpeed = 30; - this.gauge.set(this.state.value); - } - - componentWillUpdate() { - // Update labels after possible resize - this.gauge.setOptions({ staticLabels: this.staticLabels(this.props.widget.height) }); - } - - componentDidUpdate() { - // update gauge's value - this.gauge.set(this.state.value); - } - - componentWillReceiveProps(nextProps) { - // update value - const simulator = nextProps.widget.simulator; - - if (nextProps.data == null || nextProps.data[simulator] == null || nextProps.data[simulator].values == null) { - this.setState({ value: 0 }); - return; - } - - // check if value has changed - const signal = nextProps.data[simulator].values[nextProps.widget.signal]; - // Take just 3 decimal positions - // Note: Favor this method over Number.toFixed(n) in order to avoid a type conversion, since it returns a String - const new_value = Math.round( signal[signal.length - 1].y * 1e3 ) / 1e3; - if (this.state.value !== new_value) { - this.setState({ value: new_value }); - } - } - - render() { - var componentClass = this.props.editing ? "gauge-widget editing" : "gauge-widget"; - var signalType = null; - - if (this.props.simulation) { - var simulationModel = this.props.simulation.models.filter((model) => model.simulator === this.props.widget.simulator)[0]; - signalType = simulationModel && simulationModel.length > 0? simulationModel.mapping[this.props.widget.signal].type : ''; - } - - return ( -
-
{ this.props.widget.name }
- this.gaugeCanvas = node } /> -
{ signalType }
-
{ this.state.value }
-
- ); - } -} - -export default WidgetGauge; diff --git a/src/components/widget-number-input.js b/src/components/widget-number-input.js deleted file mode 100644 index 13061bb..0000000 --- a/src/components/widget-number-input.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * File: widget-number-input.js - * Author: Ricardo Hernandez-Montoya - * Date: 29.03.2017 - * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC - * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - **********************************************************************************/ - -import React, { Component } from 'react'; -import { Form, FormGroup, Col, ControlLabel, FormControl } from 'react-bootstrap'; - -class WidgetNumberInput extends Component { - - static whichValidationStateIs( condition ) { - switch(condition) { - case 'ok': return null; - case 'error': return 'error'; - default: return 'error'; - } - } - - constructor(props) { - super(props); - - this.state = { - value: '', - validationState: WidgetNumberInput.whichValidationStateIs('ok') - }; - } - - validateInput(e) { - if (e.target.value === '' || e.target.value.endsWith('.')) { - this.setState({ - validationState: WidgetNumberInput.whichValidationStateIs('ok'), - value: e.target.value }); - } else { - var num = Number(e.target.value); - if (Number.isNaN(num)) { - this.setState({ validationState: WidgetNumberInput.whichValidationStateIs('error'), - value: e.target.value }); - } else { - this.setState({ - validationState: WidgetNumberInput.whichValidationStateIs('ok'), - value: num }); - } - } - } - - render() { - return ( -
-
- - - {this.props.widget.name} - - - this.validateInput(e) } placeholder="Enter value" value={ this.state.value } /> - - - -
-
- ); - } -} - -export default WidgetNumberInput; \ No newline at end of file diff --git a/src/components/widget-plot-table.js b/src/components/widget-plot-table.js index cbdee75..5305ff8 100644 --- a/src/components/widget-plot-table.js +++ b/src/components/widget-plot-table.js @@ -8,133 +8,109 @@ **********************************************************************************/ import React, { Component } from 'react'; -import classNames from 'classnames'; -import { FormGroup, Checkbox } from 'react-bootstrap'; +import { LineChart } from 'rd3'; -import Plot from './widget-plot/plot'; -import PlotLegend from './widget-plot/plot-legend'; +import { ButtonGroup, Button } from 'react-bootstrap'; class WidgetPlotTable extends Component { constructor(props) { super(props); this.state = { - preselectedSignals: [], - signals: [] + size: { w: 0, h: 0 }, + signal: 0, + firstTimestamp: 0, + latestTimestamp: 0, + sequence: null, + rows: [], + values: [] }; } componentWillReceiveProps(nextProps) { + // check data + const simulator = nextProps.widget.simulator; - // Update internal selected signals state with props (different array objects) - if (this.props.widget.signals !== nextProps.widget.signals) { - this.setState( {signals: nextProps.widget.signals}); - } + // plot size + this.setState({ size: { w: this.props.widget.width - 100, h: this.props.widget.height - 20 }}); - // Identify if there was a change in the preselected signals - if (nextProps.simulation && (JSON.stringify(nextProps.widget.preselectedSignals) !== JSON.stringify(this.props.widget.preselectedSignals) || this.state.preselectedSignals.length === 0)) { - - // Update the currently selected signals by intersecting with the preselected signals - // Do the same with the plot values - var intersection = this.computeIntersection(nextProps.widget.preselectedSignals, nextProps.widget.signals); - this.setState({ signals: intersection }); - - this.updatePreselectedSignalsState(nextProps); + if (nextProps.simulation == null || nextProps.data == null || nextProps.data[simulator] == null || nextProps.data[simulator].length === 0 || nextProps.data[simulator].values[0].length === 0) { + // clear values + this.setState({ values: [], sequence: null, rows: [] }); return; } - } - - // Perform the intersection of the lists, alternatively could be done with Sets ensuring unique values - computeIntersection(preselectedSignals, selectedSignals) { - return preselectedSignals.filter( s => selectedSignals.includes(s)); - } - - updatePreselectedSignalsState(nextProps) { - const simulator = nextProps.widget.simulator; + // check if new data, otherwise skip + if (this.state.sequence >= nextProps.data[simulator].sequence) { + return; + } // get simulation model const simulationModel = nextProps.simulation.models.find((model) => { return (model.simulator === simulator); }); - // Create checkboxes using the signal indices from simulation model - const preselectedSignals = simulationModel.mapping.reduce( - // Loop through simulation model signals - (accum, model_signal, signal_index) => { - // Append them if they belong to the current selected type - if (nextProps.widget.preselectedSignals.indexOf(signal_index) > -1) { - accum.push( - { - index: signal_index, - name: model_signal.name - } - ) - } - return accum; - }, []); - this.setState({ preselectedSignals: preselectedSignals }); - } + // get rows + var rows = []; - updateSignalSelection(signal_index, checked) { - // Update the selected signals and propagate to parent component - var new_widget = Object.assign({}, this.props.widget, { - signals: checked? this.state.signals.concat(signal_index) : this.state.signals.filter( (idx) => idx !== signal_index ) + simulationModel.mapping.forEach((signal) => { + rows.push({ name: signal.name }) }); - this.props.onWidgetChange(new_widget); - } - render() { - var checkBoxes = []; + // get timestamps + var latestTimestamp = nextProps.data[simulator].values[0][nextProps.data[simulator].values[0].length - 1].x; + var firstTimestamp = latestTimestamp - nextProps.widget.time * 1000; + var firstIndex; - // Data passed to plot - let simulator = this.props.widget.simulator; - let simulatorData = this.props.data[simulator]; - - if (this.state.preselectedSignals && this.state.preselectedSignals.length > 0) { - // Create checkboxes using the signal indices from simulation model - checkBoxes = this.state.preselectedSignals.map( (signal) => { - var checked = this.state.signals.indexOf(signal.index) > -1; - var chkBxClasses = classNames({ - 'btn': true, - 'btn-default': true, - 'active': checked - }); - return this.updateSignalSelection(signal.index, e.target.checked) } > { signal.name } - }); + if (nextProps.data[simulator].values[0][0].x < firstTimestamp) { + // find element index representing firstTimestamp + firstIndex = nextProps.data[simulator].values[0].findIndex((value) => { + return value.x >= firstTimestamp; + }); + } else { + firstIndex = 0; + firstTimestamp = nextProps.data[simulator].values[0][0].x; + latestTimestamp = firstTimestamp + nextProps.widget.time * 1000; } - // Prepare an array with the signals to show in the legend - var legendSignals = this.state.preselectedSignals.reduce( (accum, signal, i) => { - if (this.state.signals.includes(signal.index)) { - accum.push({ - index: signal.index, - name: signal.name - }) - } - return accum; - }, []); + // copy all values for each signal in time region + var values = [{ + values: nextProps.data[simulator].values[this.state.signal].slice(firstIndex, nextProps.data[simulator].values[this.state.signal].length - 1) + }]; + this.setState({ values: values, firstTimestamp: firstTimestamp, latestTimestamp: latestTimestamp, sequence: nextProps.data[simulator].sequence, rows: rows }); + } + + render() { + console.log("Signal: " + this.state.signal); return ( -
+

{this.props.widget.name}

-
-
- { checkBoxes.length > 0 ? ( - - { checkBoxes } - - ) : ( No signal found, select a different signal type. ) +
+ + { this.state.rows.map( (row, index) => ( + + )) } -
- -
- -
+ +
+ +
+ {this.state.sequence && + { if (d != null) { return new Date(d.x); } }} + hoverAnimation={false} + circleRadius={0} + domain={{ x: [this.state.firstTimestamp, this.state.latestTimestamp] }} + /> + }
-
); diff --git a/src/components/widget-plot.js b/src/components/widget-plot.js index df901c9..96de856 100644 --- a/src/components/widget-plot.js +++ b/src/components/widget-plot.js @@ -8,40 +8,81 @@ **********************************************************************************/ import React, { Component } from 'react'; - -import Plot from './widget-plot/plot'; -import PlotLegend from './widget-plot/plot-legend'; +import { LineChart } from 'rd3'; class WidgetPlot extends Component { + constructor(props) { + super(props); + + this.state = { + values: [], + firstTimestamp: 0, + latestTimestamp: 0, + sequence: null + }; + } + + componentWillReceiveProps(nextProps) { + // check data + const simulator = nextProps.widget.simulator; + + if (nextProps.simulation == null || nextProps.data == null || nextProps.data[simulator] == null || nextProps.data[simulator].length === 0 || nextProps.data[simulator].values[0].length === 0) { + // clear values + this.setState({ values: [], sequence: null }); + return; + } + + // check if new data, otherwise skip + if (this.state.sequence >= nextProps.data[simulator].sequence) { + return; + } + + // get timestamps + var latestTimestamp = nextProps.data[simulator].values[0][nextProps.data[simulator].values[0].length - 1].x; + var firstTimestamp = latestTimestamp - nextProps.widget.time * 1000; + var firstIndex; + + if (nextProps.data[simulator].values[0][0].x < firstTimestamp) { + // find element index representing firstTimestamp + firstIndex = nextProps.data[simulator].values[0].findIndex((value) => { + return value.x >= firstTimestamp; + }); + } else { + firstIndex = 0; + firstTimestamp = nextProps.data[simulator].values[0][0].x; + latestTimestamp = firstTimestamp + nextProps.widget.time * 1000; + } + + // copy all values for each signal in time region + var values = []; + + nextProps.widget.signals.forEach((signal) => { + values.push({ + values: nextProps.data[simulator].values[signal].slice(firstIndex, nextProps.data[simulator].values[signal].length - 1) + }); + }); + + this.setState({ values: values, firstTimestamp: firstTimestamp, latestTimestamp: latestTimestamp, sequence: nextProps.data[simulator].sequence }); + } render() { - if (this.props.simulation == null) { + if (this.state.sequence == null) { return (
Empty
); } - let simulator = this.props.widget.simulator; - let simulation = this.props.simulation; - let model = simulation.models.find( (model) => model.simulator === simulator ); - let chosenSignals = this.props.widget.signals; - - let simulatorData = this.props.data[simulator]; - - // Query the signals that will be displayed in the legend - let 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; - }, []); - return ( -
-

{this.props.widget.name}

- -
- -
- +
+ { if (d != null) { return new Date(d.x); } }} + hoverAnimation={false} + circleRadius={0} + domain={{ x: [this.state.firstTimestamp, this.state.latestTimestamp] }} + />
); } diff --git a/src/components/widget-plot/plot-legend.js b/src/components/widget-plot/plot-legend.js deleted file mode 100644 index 3771a5c..0000000 --- a/src/components/widget-plot/plot-legend.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * File: plot-legend.js - * Author: Ricardo Hernandez-Montoya - * Date: 10.04.2017 - * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC - * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - **********************************************************************************/ - -import React, { Component } from 'react'; -import { scaleOrdinal, schemeCategory10 } from 'd3-scale'; - -class PlotLegend extends Component { - // constructor(props) { - // super(props); - // } - - render() { - var colorScale = scaleOrdinal(schemeCategory10); - - return ( -
- { this.props.signals.map( (signal) => -
   {signal.name}
) - } -
- ); - } -} - -export default PlotLegend; \ No newline at end of file diff --git a/src/components/widget-plot/plot.js b/src/components/widget-plot/plot.js deleted file mode 100644 index bb5ff01..0000000 --- a/src/components/widget-plot/plot.js +++ /dev/null @@ -1,115 +0,0 @@ -/** - * File: plot.js - * Author: Ricardo Hernandez-Montoya - * Date: 10.04.2017 - * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC - * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. - * 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; - - this.state = { - size: { w: 0, h: 0 }, - firstTimestamp: 0, - latestTimestamp: 0, - sequence: null, - values: [] - }; - } - - 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 } }); - } - - // Identify simulation reset - if (nextData == null || nextData.length === 0 || nextData.values[0].length === 0) { - // clear values - this.setState({ values: [], sequence: null }); - return; - } - - // check if new data, otherwise skip - if (this.state.sequence >= nextData.sequence) { - return; - } - - this.updatePlotData(nextProps); - - } - - 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 }); - } - - render() { - // Make tick count proportional to the plot width using a rough scale ratio - var tickCount = Math.round(this.state.size.w / 80); - - return ( -
this.chartWrapper = domNode }> - {this.state.sequence && - { if (d != null) { return new Date(d.x); } }} - xAxisTickCount={ tickCount } - hoverAnimation={false} - circleRadius={0} - domain={{ x: [this.state.firstTimestamp, this.state.latestTimestamp] }} - /> - } -
- ); - } - -} - -export default Plot; \ No newline at end of file diff --git a/src/components/widget-slider.js b/src/components/widget-slider.js deleted file mode 100644 index 40f890f..0000000 --- a/src/components/widget-slider.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * File: widget-slider.js - * Author: Ricardo Hernandez-Montoya - * Date: 30.03.2017 - * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC - * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - **********************************************************************************/ - -import React, { Component } from 'react'; -import classNames from 'classnames'; - -class WidgetSlider extends Component { - - static get OrientationTypes() { - return ({ - HORIZONTAL: {value: 0, name: 'Horizontal'}, - VERTICAL: {value: 1, name: 'Vertical'} - }) - } - - constructor(props) { - super(props); - - this.state = { - value: 50 - }; - } - - valueChanged(e) { - this.setState({ value: e.target.value }); - } - - render() { - let fields = { - 'name': this.props.widget.name, - 'control': this.valueChanged(e) } defaultValue={ this.state.value }/>, - 'value': this.state.value - } - - let vertical = this.props.widget.orientation === WidgetSlider.OrientationTypes.VERTICAL.value; - var widgetClasses = classNames({ - 'slider-widget': true, - 'full': true, - 'vertical': vertical, - 'horizontal': !vertical - }); - - return ( - this.props.widget.orientation === WidgetSlider.OrientationTypes.HORIZONTAL.value? ( -
-
- -
-
- { fields.control } - { fields.value } -
-
- ) : ( -
-
- - { fields.value } -
-
{ fields.control }
-
- ) - ); - } -} - -export default WidgetSlider; \ No newline at end of file diff --git a/src/components/widget-table.js b/src/components/widget-table.js index 91e59de..071c084 100644 --- a/src/components/widget-table.js +++ b/src/components/widget-table.js @@ -48,7 +48,7 @@ class WidgetTable extends Component { nextProps.data[simulator].values.forEach((signal, index) => { rows.push({ name: simulationModel.mapping[index].name, - value: signal[signal.length - 1].y.toFixed(3) + value: signal[signal.length - 1].y }) }); @@ -57,7 +57,7 @@ class WidgetTable extends Component { render() { return ( -
+

{this.props.widget.name}

diff --git a/src/containers/visualization.js b/src/containers/visualization.js index 6085b06..9d108fa 100644 --- a/src/containers/visualization.js +++ b/src/containers/visualization.js @@ -23,8 +23,6 @@ import SimulationStore from '../stores/simulation-store'; import FileStore from '../stores/file-store'; import AppDispatcher from '../app-dispatcher'; -import WidgetSlider from '../components/widget-slider'; - class Visualization extends Component { static getStores() { return [ VisualizationStore, ProjectStore, SimulationStore, FileStore ]; @@ -144,8 +142,6 @@ class Visualization extends Component { widget.signal = 0; widget.minWidth = 70; widget.minHeight = 20; - widget.width = 120; - widget.height = 70; } else if (item.name === 'Plot') { widget.simulator = this.state.simulation.models[0].simulator; widget.signals = [ 0 ]; @@ -165,41 +161,16 @@ class Visualization extends Component { widget.minHeight = 20; } else if (item.name === 'PlotTable') { widget.simulator = this.state.simulation.models[0].simulator; - widget.preselectedSignals = []; - widget.signals = []; // initialize selected signals widget.minWidth = 400; - widget.minHeight = 300; + widget.minHeight = 200; widget.width = 500; - widget.height = 500; + widget.height = 400; widget.time = 60 } else if (item.name === 'Image') { widget.minWidth = 100; widget.minHeight = 100; widget.width = 200; widget.height = 200; - } else if (item.name === 'Button') { - widget.minWidth = 100; - widget.minHeight = 50; - widget.width = 100; - widget.height = 100; - } else if (item.name === 'NumberInput') { - widget.minWidth = 200; - widget.minHeight = 50; - widget.width = 200; - widget.height = 50; - } else if (item.name === 'Slider') { - widget.minWidth = 380; - widget.minHeight = 30; - widget.width = 400; - widget.height = 50; - widget.orientation = WidgetSlider.OrientationTypes.HORIZONTAL.value; // Assign default orientation - } else if (item.name === 'Gauge') { - widget.simulator = this.state.simulation.models[0].simulator; - widget.signal = 0; - widget.minWidth = 200; - widget.minHeight = 150; - widget.width = 200; - widget.height = 150; } var new_widgets = this.state.visualization.widgets; @@ -213,12 +184,7 @@ class Visualization extends Component { this.setState({ visualization: visualization }); } - widgetStatusChange(updated_widget, key) { - // Widget changed internally, make changes effective then save them - this.widgetChange(updated_widget, key, this.saveChanges); - } - - widgetChange(updated_widget, key, callback = null) { + widgetChange(updated_widget, key) { var widgets_update = {}; widgets_update[key] = updated_widget; @@ -227,7 +193,7 @@ class Visualization extends Component { var visualization = Object.assign({}, this.state.visualization, { widgets: new_widgets }); - this.setState({ visualization: visualization }, callback); + this.setState({ visualization: visualization }); } editWidget(e, data) { @@ -260,11 +226,6 @@ class Visualization extends Component { this.setState({ visualization: visualization }); } - stopEditing() { - // Provide the callback so it can be called when state change is applied - this.setState({ editing: false }, this.saveChanges ); - } - saveChanges() { // Transform to a list var visualization = Object.assign({}, this.state.visualization, { @@ -275,6 +236,8 @@ class Visualization extends Component { type: 'visualizations/start-edit', data: visualization }); + + this.setState({ editing: false }); } discardChanges() { @@ -351,7 +314,7 @@ class Visualization extends Component { {this.state.editing ? (
-
} this.handleDrop(item, position)} editing={this.state.editing}> {current_widgets != null && Object.keys(current_widgets).map( (widget_key) => ( - this.widgetChange(w, k)} onWidgetStatusChange={(w, k) => this.widgetStatusChange(w, k)} editing={this.state.editing} index={widget_key} grid={this.state.grid} /> + this.widgetChange(w, k)} editing={this.state.editing} index={widget_key} grid={this.state.grid} /> ))} diff --git a/src/containers/widget.js b/src/containers/widget.js index 8f17940..7aa540f 100644 --- a/src/containers/widget.js +++ b/src/containers/widget.js @@ -11,7 +11,6 @@ import React, { Component } from 'react'; import { Container } from 'flux/utils'; import { ContextMenuTrigger } from 'react-contextmenu'; import Rnd from 'react-rnd'; -import classNames from 'classnames'; import AppDispatcher from '../app-dispatcher'; import SimulatorDataStore from '../stores/simulator-data-store'; @@ -23,10 +22,6 @@ import WidgetTable from '../components/widget-table'; import WidgetLabel from '../components/widget-label'; import WidgetPlotTable from '../components/widget-plot-table'; import WidgetImage from '../components/widget-image'; -import WidgetButton from '../components/widget-button'; -import WidgetNumberInput from '../components/widget-number-input'; -import WidgetSlider from '../components/widget-slider'; -import WidgetGauge from '../components/widget-gauge'; import '../styles/widgets.css'; @@ -118,7 +113,6 @@ class Widget extends Component { // get widget element const widget = this.props.data; - var borderedWidget = false; var element = null; // dummy is passed to widgets to keep updating them while in edit mode @@ -126,34 +120,16 @@ class Widget extends Component { element = } else if (widget.type === 'Plot') { element = - borderedWidget = true; } else if (widget.type === 'Table') { element = } else if (widget.type === 'Label') { element = - borderedWidget = true; } else if (widget.type === 'PlotTable') { - element = this.props.onWidgetStatusChange(w, this.props.index) } /> - borderedWidget = true; + element = } else if (widget.type === 'Image') { element = - borderedWidget = true; - } else if (widget.type === 'Button') { - element = - } else if (widget.type === 'NumberInput') { - element = - } else if (widget.type === 'Slider') { - element = - } else if (widget.type === 'Gauge') { - element = } - let widgetClasses = classNames({ - 'widget': !this.props.editing, - 'editing-widget': this.props.editing, - 'border': borderedWidget - }); - if (this.props.editing) { return ( this.borderWasClicked(event) } onResizeStop={(direction, styleSize, clientSize, delta) => this.resizeStop(direction, styleSize, clientSize, delta)} onDragStop={(event, ui) => this.dragStop(event, ui)} @@ -177,7 +153,7 @@ class Widget extends Component { ); } else { return ( -
+
{element}
); diff --git a/src/styles/widgets.css b/src/styles/widgets.css index c8b5678..c6a9c91 100644 --- a/src/styles/widgets.css +++ b/src/styles/widgets.css @@ -8,20 +8,14 @@ **********************************************************************************/ .widget { - background-color: #fff; -} - -.border { - border: 1px solid lightgray; + border: 1px solid lightgray; + padding: 3px 6px; + background-color: #fff; } .editing-widget { - background-color: #fff; -} - -.editing-widget:not(.border):hover { - outline: 1px solid lightgray; - outline-offset: -10px; + border: 1px solid lightgray; + background-color: #fff; } /* Area to trigger the context menu */ @@ -102,15 +96,21 @@ right: 7px; } -/* Reset Bootstrap styles to "disable" while editing */ -div[class*="-widget"] .btn[disabled], .btn.disabled, div[class*="-widget"] input[disabled], .form-control[disabled], .checkbox.disabled label { - cursor: inherit; - pointer-events: none; +.plot-table-widget .content { + display: -webkit-flex; + display: flex; } -.editing-widget .btn-default.disabled { - background-color: #abcfd8; - border-color: #ccc; +.plot-table-widget .widget-table { + min-width: 100px; + display: flex; + flex-direction: column; + justify-content: center; +} + +/* Reset Bootstrap styles to "disable" while editing */ +.plot-table-widget .widget-table .btn[disabled] { + cursor: inherit; } .btn-default[disabled]:hover { @@ -123,253 +123,8 @@ div[class*="-widget"] .btn[disabled], .btn.disabled, div[class*="-widget"] input border-color: #adadad; } /* End reset */ - - /* Match Bootstrap's them to VILLAS */ -.widget .btn-default.active { - background-color: #abcfd8; -} - -.widget .btn-default.active:hover, .widget .btn-default.active:hover { - background-color: #89b3bd; -} - -.widget .btn-default:hover { - background-color: #abcfd8; -} -/* End match */ - -/* PlotTable widget */ -.plot-table-widget, .plot-widget, .value-widget, .image-widget, .label-widget { - width: 100%; - height: 100%; - padding: 3px 6px; -} - -.plot-table-widget { - display: -webkit-flex; - display: flex; - flex-direction: column; -} - -.plot-table-widget .content { - -webkit-flex: 1 0 auto; - flex: 1 0 auto; - display: -webkit-flex; - display: flex; - flex-direction: column; -} - -.table-plot-row { - -webkit-flex: 1 0 auto; - flex: 1 0 auto; - display: -webkit-flex; - display: flex; -} - -.plot-table-widget .widget-table { - -webkit-flex: 1 0 auto; - flex: 1 0 auto; - flex-basis: 90px; - max-width: 50%; - display: flex; - flex-direction: column; - justify-content: center; -} - -.plot-table-widget small { - text-align: center; -} - -.plot-table-widget .checkbox label { - height: 100%; - width: 100%; - padding: 6px 12px; - overflow-x: hidden; -} - -.plot-table-widget .btn { - padding: 0px; -} - -.plot-table-widget input[type="checkbox"] { - display: none; -} .plot-table-widget .widget-plot { - -webkit-flex: 1 1 auto; - flex: 1 1 auto; -} -/* End PlotTable Widget */ - -/* Plot Widget */ -.plot-widget { - display: -webkit-flex; - display: flex; - flex-direction: column; -} - -.plot-widget .widget-plot { - -webkit-flex: 1 1 auto; - flex: 1 1 auto; -} -/* End Plot Widget */ - -/* Plots */ -.chart-wrapper { - height: 100%; - width: 100%; -} - -.plot-legend { - display: -webkit-flex; - display: flex; - flex-wrap: wrap; - justify-content: space-around; - margin-bottom: 5px; -} - -.signal-legend { - font-size: 0.8em; - font-weight: 700; - overflow-x: hidden; -} - -.legend-color { - height: 50%; - display: inline-block; - vertical-align: middle; -} - -/* End Plots */ - -/*.single-value-widget { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 95%; - height: 95%; -}*/ - -.single-value-widget { - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; -} - -.single-value-widget > * { - width: 50%; - float: left; - margin: 5px; - font-size: 1vw; -} - -/* Button widget styling */ -.button-widget button { - border-radius: 25px; - border-style: double; - border-width: 5px; - overflow-x: hidden; -} - -.button-widget button:hover { - border-style: double; -} -/* End button widget styling */ - -.full { - width: 100%; - height: 100%; -} - -/* Number input widget */ -div[class*="-widget"] label { - cursor: inherit; -} -/* End number input widget */ - -/* Slider widget */ -.slider-widget.vertical input[type="range"] { - position: absolute; - top: 40%; - left: 50%; - transform: rotate(270deg); - /*margin-left: 20px;*/ - width: 150px; -} - -input[type=range]::-moz-range-thumb { - background: #ffffff; -} - -input[type=range]::-webkit-slider-thumb { - background: #ffffff; -} - -input[type=range]::-ms-thumb { - background: #ffffff; -} - -.slider-widget.horizontal div { - width: 50%; - display: inline-block; - text-align: center; - vertical-align: top; -} - -.slider-widget.horizontal span { - display: block; - margin: 5px; -} - -.slider-widget.vertical div { - width: 50%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-around; -} - -.slider-widget span { - font-size: 1.5em; - font-weight: 600; -} -/* End slider widget */ - -/* Gauge widget */ -.gauge-widget { - width: 100%; - height: 100%; -} - -.gauge-widget canvas { - width: 100%; - height: 90%; -} - -.gauge-name { - height: 10%; - width: 100%; - text-align: center; - font-weight: bold; -} - -.gauge-unit { - position: absolute; - width: 100%; - font-size: 1.0em; - bottom: 25%; - text-align: center; -} - -.gauge-value { - position: absolute; - width: 100%; - font-weight: bold; - font-size: 1.5em; - bottom: 10%; - text-align: center; -} -/* End gauge widget */ \ No newline at end of file + -webkit-flex: 1 0 auto; + flex: 1 0 auto; +} \ No newline at end of file