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:
parent
6b426ce194
commit
5a31763272
5 changed files with 169 additions and 55 deletions
68
src/components/dialog/edit-widget-signals-control.js
Normal file
68
src/components/dialog/edit-widget-signals-control.js
Normal 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;
|
|
@ -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)} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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') {
|
||||
|
|
Loading…
Add table
Reference in a new issue