diff --git a/src/components/widget-plot-table.js b/src/components/widget-plot-table.js
index 3f67bc6..23fe0c0 100644
--- a/src/components/widget-plot-table.js
+++ b/src/components/widget-plot-table.js
@@ -11,21 +11,16 @@ import React, { Component } from 'react';
import { LineChart } from 'rd3';
import { scaleOrdinal, schemeCategory10 } from 'd3-scale';
import classNames from 'classnames';
-
import { FormGroup, Checkbox } from 'react-bootstrap';
+import Plot from './widget-plot/plot';
+import PlotLegend from './widget-plot/plot-legend';
+
class WidgetPlotTable extends Component {
constructor(props) {
super(props);
-
- this.chartWrapper = null;
this.state = {
- size: { w: 0, h: 0 },
- firstTimestamp: 0,
- latestTimestamp: 0,
- sequence: null,
- values: [],
preselectedSignals: [],
signals: []
};
@@ -35,15 +30,6 @@ class WidgetPlotTable extends Component {
// check data
const simulator = nextProps.widget.simulator;
- // 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 } });
- }
- // this.setState({ size: { w: this.props.widget.width - 100, h: this.props.widget.height - 77 }});
-
// Update internal selected signals state with props (different array objects)
if (this.props.widget.signals !== nextProps.widget.signals) {
this.setState( {signals: nextProps.widget.signals});
@@ -55,28 +41,12 @@ class WidgetPlotTable extends Component {
// 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,
- values: this.state.values.filter( (values) => intersection.includes(values.index))
- });
+ this.setState({ signals: intersection });
this.updatePreselectedSignalsState(nextProps);
return;
}
- // Identify simulation reset
- 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;
- }
-
- this.updatePlotData(nextProps);
}
// Perform the intersection of the lists, alternatively could be done with Sets ensuring unique values
@@ -110,38 +80,6 @@ class WidgetPlotTable extends Component {
this.setState({ preselectedSignals: preselectedSignals });
}
- updatePlotData(nextProps) {
- const simulator = nextProps.widget.simulator;
-
- // 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 = [];
- this.state.signals.forEach((signal_index, i, arr) => (
- // Include signal index, useful to relate them to the signal selection
- values.push(
- {
- index: signal_index,
- values: nextProps.data[simulator].values[signal_index].slice(firstIndex, nextProps.data[simulator].values[signal_index].length - 1)})
- ));
-
- this.setState({ values: values, firstTimestamp: firstTimestamp, latestTimestamp: latestTimestamp, sequence: nextProps.data[simulator].sequence });
- }
-
updateSignalSelection(signal_index, checked) {
// Update the selected signals and propagate to parent component
var new_widget = Object.assign({}, this.props.widget, {
@@ -153,6 +91,10 @@ class WidgetPlotTable extends Component {
render() {
var checkBoxes = [];
+ // 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) => {
@@ -166,9 +108,16 @@ class WidgetPlotTable extends Component {
});
}
- // Make tick count proportional to the plot width using a rough scale ratio
- var tickCount = Math.round(this.state.size.w / 80);
- var colorScale = scaleOrdinal(schemeCategory10);
+ // 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;
+ }, []);
return (
@@ -186,34 +135,10 @@ class WidgetPlotTable extends Component {
-
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] }}
- />
- }
-
+
-
- {
- this.state.preselectedSignals.reduce( (accum, signal, i) => {
- if (this.state.signals.includes(signal.index)) {
- accum.push(
{signal.name}
)
- }
- return accum;
- }, [])
- }
-
+
);
diff --git a/src/components/widget-plot.js b/src/components/widget-plot.js
index 96de856..5c1e785 100644
--- a/src/components/widget-plot.js
+++ b/src/components/widget-plot.js
@@ -8,81 +8,38 @@
**********************************************************************************/
import React, { Component } from 'react';
-import { LineChart } from 'rd3';
+
+import Plot from './widget-plot/plot';
+import PlotLegend from './widget-plot/plot-legend';
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.state.sequence == null) {
+ if (this.props.simulation == 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 (
-
-
{ 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
new file mode 100644
index 0000000..3771a5c
--- /dev/null
+++ b/src/components/widget-plot/plot-legend.js
@@ -0,0 +1,31 @@
+/**
+ * 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
new file mode 100644
index 0000000..40c842c
--- /dev/null
+++ b/src/components/widget-plot/plot.js
@@ -0,0 +1,115 @@
+/**
+ * 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/styles/widgets.css b/src/styles/widgets.css
index c0a354a..45956cb 100644
--- a/src/styles/widgets.css
+++ b/src/styles/widgets.css
@@ -186,8 +186,33 @@ div[class*="-widget"] .btn[disabled], .btn.disabled, div[class*="-widget"] input
.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-table-widget .plot-legend {
+/* 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;
@@ -195,28 +220,19 @@ div[class*="-widget"] .btn[disabled], .btn.disabled, div[class*="-widget"] input
margin-bottom: 5px;
}
-.plot-table-widget .signal-legend {
+.signal-legend {
font-size: 0.8em;
font-weight: 700;
overflow-x: hidden;
}
-.plot-table-widget .legend-color {
+.legend-color {
height: 50%;
display: inline-block;
vertical-align: middle;
}
-
-.plot-table-widget .widget-plot {
- -webkit-flex: 1 1 auto;
- flex: 1 1 auto;
-}
-.plot-table-widget .chart-wrapper {
- height: 100%;
- width: 100%;
-}
-/* End PlotTable Widget */
+/* End Plots */
/*.single-value-widget {
position: absolute;