1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-09 00:00:01 +01:00

widget plot table signal preselection

This commit is contained in:
Ricardo Hernandez-Montoya 2017-04-07 13:47:18 +02:00
parent 6b426ce194
commit 5a31763272
5 changed files with 169 additions and 55 deletions

View file

@ -0,0 +1,68 @@
/**
* File: edit-widget-signals-control.js
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
* 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 (
<FormGroup>
<ControlLabel>Signals</ControlLabel>
{simulationModel.mapping.map((signal, index) => (
<Checkbox key={index} checked={this.state.widget.preselectedSignals.indexOf(index) !== -1} onChange={(e) => this.handleSignalChange(e.target.checked, index)}>{signal.name}</Checkbox>
))}
</FormGroup>
);
}
}
export default EditWidgetSignalsControl;

View file

@ -18,7 +18,7 @@ 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 EditWidgetSignalTypeControl from './edit-widget-signal-type-control';
import EditWidgetSignalsControl from './edit-widget-signals-control';
class EditWidgetDialog extends Component {
static propTypes = {
@ -92,13 +92,13 @@ class EditWidgetDialog extends Component {
widgetDialog = <EditImageWidget widget={this.state.temporal} files={this.props.files} validate={(id) => this.validateForm(id)} simulation={this.props.simulation} handleChange={(e, index) => this.handleChange(e, index)} />;
} else if (this.props.widget.type === 'Gauge') {
dialogControls.push(
<EditWidgetSimulatorControl widget={this.state.temporal} validate={(id) => this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} />,
<EditWidgetSignalControl widget={this.state.temporal} validate={(id) => this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} />
<EditWidgetSimulatorControl key={1} widget={this.state.temporal} validate={(id) => this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} />,
<EditWidgetSignalControl key={2} widget={this.state.temporal} validate={(id) => this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} />
)
} else if (this.props.widget.type === 'PlotTable') {
dialogControls.push(
<EditWidgetSimulatorControl widget={this.state.temporal} validate={(id) => this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} />,
<EditWidgetSignalTypeControl widget={this.state.temporal} validate={(id) => this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} />
<EditWidgetSimulatorControl key={1} widget={this.state.temporal} validate={(id) => this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} />,
<EditWidgetSignalsControl key={2} widget={this.state.temporal} validate={(id) => this.validateForm(id)} simulation={this.props.simulation} handleChange={(e) => this.handleChange(e)} />
)
}
}

View file

@ -20,13 +20,12 @@ class WidgetPlotTable extends Component {
this.state = {
size: { w: 0, h: 0 },
signals: [],
firstTimestamp: 0,
latestTimestamp: 0,
sequence: null,
signalsOfCurrType: [],
values: [],
active_sim_model: {}
preselectedSignals: [],
signals: []
};
}
@ -37,13 +36,30 @@ class WidgetPlotTable extends Component {
// plot size
this.setState({ size: { w: this.props.widget.width - 100, h: this.props.widget.height - 20 }});
if (nextProps.widget.signalType !== this.props.widget.signalType) {
this.setState({ signals: []});
// Update internal selected signals state with props (different array objects)
if (this.props.widget.signals !== nextProps.widget.signals) {
this.setState( {signals: nextProps.widget.signals});
}
// 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,
values: this.state.values.filter( (values) => intersection.includes(values.index))
});
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, signalsOfCurrType: [] });
this.setState({ values: [], sequence: null });
return;
}
@ -52,25 +68,42 @@ class WidgetPlotTable extends Component {
return;
}
this.updatePlotData(nextProps);
}
// 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;
// get simulation model
const simulationModel = nextProps.simulation.models.find((model, model_index) => {
var found = false;
if (model.simulator === simulator) {
this.setState({ active_sim_model: model_index });
found = true;
}
return found;
const simulationModel = nextProps.simulation.models.find((model) => {
return (model.simulator === simulator);
});
// get signals belonging to the currently selected type
var filteredSignals = {};
simulationModel.mapping
.filter( (signal) =>
signal.type.toLowerCase() === nextProps.widget.signalType)
.forEach((signal) => {
// Store signals to be shown in a dictionary
filteredSignals[signal.name.toLowerCase()] = '';
});
// 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 });
}
updatePlotData(nextProps) {
const simulator = nextProps.widget.simulator;
// get timestamps
var latestTimestamp = nextProps.data[simulator].values[0][nextProps.data[simulator].values[0].length - 1].x;
@ -91,41 +124,38 @@ class WidgetPlotTable extends Component {
// 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(
{ values: nextProps.data[simulator].values[signal_index].slice(firstIndex, nextProps.data[simulator].values[signal_index].length - 1)})
{
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, signalsOfCurrType: filteredSignals });
this.setState({ values: values, firstTimestamp: firstTimestamp, latestTimestamp: latestTimestamp, sequence: nextProps.data[simulator].sequence });
}
updateSignalSelection(signal_index, checked) {
// If the signal is selected, add it to array, remove it otherwise
this.setState({
// 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 )
});
this.props.onWidgetChange(new_widget);
}
render() {
var checkBoxes = [];
if (this.state.signalsOfCurrType && Object.keys(this.state.signalsOfCurrType).length > 0) {
if (this.state.preselectedSignals && this.state.preselectedSignals.length > 0) {
// Create checkboxes using the signal indices from simulation model
checkBoxes = this.props.simulation.models[this.state.active_sim_model].mapping.reduce(
// Loop through simulation model signals
(accum, model_signal, signal_index) => {
// Append them if they belong to the current selected type
if (this.state.signalsOfCurrType.hasOwnProperty(model_signal.name.toLowerCase())) {
// Tag as active if it is currently selected
checkBoxes = this.state.preselectedSignals.map( (signal) => {
var checked = this.state.signals.indexOf(signal.index) > -1;
var chkBxClasses = classNames({
'btn': true,
'btn-default': true,
'active': this.state.signals.indexOf(signal_index) > -1
'active': checked
});
accum.push(
<Checkbox key={signal_index} className={chkBxClasses} disabled={ this.props.editing } onChange={(e) => this.updateSignalSelection(signal_index, e.target.checked) } > { model_signal.name } </Checkbox>
)
}
return accum;
}, []);
return <Checkbox key={signal.index} className={chkBxClasses} checked={checked} disabled={ this.props.editing } onChange={(e) => this.updateSignalSelection(signal.index, e.target.checked) } > { signal.name } </Checkbox>
});
}
// Make tick count proportional to the plot width using a rough scale ratio
@ -150,7 +180,7 @@ class WidgetPlotTable extends Component {
<LineChart
width={ this.state.size.w || 100 }
height={ this.state.size.h || 100 }
data={this.state.values}
data={this.state.values }
colors={ scaleOrdinal(schemeCategory10) }
gridHorizontal={true}
xAccessor={(d) => { if (d != null) { return new Date(d.x); } }}
@ -160,6 +190,13 @@ class WidgetPlotTable extends Component {
domain={{ x: [this.state.firstTimestamp, this.state.latestTimestamp] }}
/>
}
<div>
{
this.state.signals.map((signal) =>
({signal} + ',')
)
}
</div>
</div>
</div>
</div>

View file

@ -163,7 +163,8 @@ class Visualization extends Component {
widget.minHeight = 20;
} else if (item.name === 'PlotTable') {
widget.simulator = this.state.simulation.models[0].simulator;
widget.signalType = this.state.simulation.models[0].mapping[0].type.toLowerCase();
widget.preselectedSignals = [];
widget.signals = []; // initialize selected signals
widget.minWidth = 400;
widget.minHeight = 200;
widget.width = 500;
@ -209,7 +210,12 @@ class Visualization extends Component {
this.setState({ visualization: visualization });
}
widgetChange(updated_widget, key) {
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) {
var widgets_update = {};
widgets_update[key] = updated_widget;
@ -218,7 +224,7 @@ class Visualization extends Component {
var visualization = Object.assign({}, this.state.visualization, {
widgets: new_widgets
});
this.setState({ visualization: visualization });
this.setState({ visualization: visualization }, callback);
}
editWidget(e, data) {
@ -251,6 +257,11 @@ 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, {
@ -261,8 +272,6 @@ class Visualization extends Component {
type: 'visualizations/start-edit',
data: visualization
});
this.setState({ editing: false });
}
discardChanges() {
@ -339,7 +348,7 @@ class Visualization extends Component {
</div>
{this.state.editing ? (
<div className='section-buttons-group'>
<Button bsStyle="link" onClick={() => this.saveChanges()}>
<Button bsStyle="link" onClick={() => this.stopEditing()}>
<span className="glyphicon glyphicon-floppy-disk"></span> Save
</Button>
<Button bsStyle="link" onClick={() => this.discardChanges()}>
@ -374,7 +383,7 @@ class Visualization extends Component {
<Dropzone height={height} onDrop={(item, position) => this.handleDrop(item, position)} editing={this.state.editing}>
{current_widgets != null &&
Object.keys(current_widgets).map( (widget_key) => (
<Widget key={widget_key} data={current_widgets[widget_key]} simulation={this.state.simulation} onWidgetChange={(w, k) => this.widgetChange(w, k)} editing={this.state.editing} index={widget_key} grid={this.state.grid} />
<Widget key={widget_key} data={current_widgets[widget_key]} simulation={this.state.simulation} onWidgetChange={(w, k) => this.widgetChange(w, k)} onWidgetStatusChange={(w, k) => this.widgetStatusChange(w, k)} editing={this.state.editing} index={widget_key} grid={this.state.grid} />
))}
</Dropzone>

View file

@ -129,7 +129,7 @@ class Widget extends Component {
} else if (widget.type === 'Label') {
element = <WidgetLabel widget={widget} />
} else if (widget.type === 'PlotTable') {
element = <WidgetPlotTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulation={this.props.simulation} editing={this.props.editing} />
element = <WidgetPlotTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulation={this.props.simulation} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } />
} else if (widget.type === 'Image') {
element = <WidgetImage widget={widget} files={this.state.files} />
} else if (widget.type === 'Button') {