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

Improve plot widgets

Add y-axis scale options
Add time option to table-plot
This commit is contained in:
Markus Grigull 2017-09-18 09:26:21 +02:00
parent 7825d119de
commit bb943cce9c
7 changed files with 107 additions and 68 deletions

View file

@ -60,7 +60,8 @@ export default function createControls(widgetType = null, widget = null, session
<EditWidgetTimeControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={2} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => plotBoundOnChange(e)} />,
<EditWidgetSignalsControl key={3} controlId={'signals'} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextControl key={4} controlId={'ylabel'} label={'Y-Axis name'} placeholder={'Enter a name for the y-axis'} widget={widget} handleChange={(e) => handleChange(e)} />
<EditWidgetTextControl key={4} controlId={'ylabel'} label={'Y-Axis name'} placeholder={'Enter a name for the y-axis'} widget={widget} handleChange={(e) => handleChange(e)} />,
<EditWidgetMinMaxControl key={5} widget={widget} controlId="y" handleChange={e => handleChange(e)} />
);
break;
case 'Table':
@ -94,7 +95,9 @@ export default function createControls(widgetType = null, widget = null, session
dialogControls.push(
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => plotTableBoundOnChange(e)} />,
<EditWidgetSignalsControl key={2} controlId={'preselectedSignals'} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetTextControl key={3} controlId={'ylabel'} label={'Y-Axis'} placeholder={'Enter a name for the Y-axis'} widget={widget} handleChange={(e) => handleChange(e)} />
<EditWidgetTextControl key={3} controlId={'ylabel'} label={'Y-Axis'} placeholder={'Enter a name for the Y-axis'} widget={widget} handleChange={(e) => handleChange(e)} />,
<EditWidgetTimeControl key={4} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetMinMaxControl key={5} widget={widget} controlId="y" handleChange={e => handleChange(e)} />
);
break;
case 'Slider':

View file

@ -47,6 +47,9 @@ class WidgetFactory {
widget.minHeight = 200;
widget.width = 400;
widget.height = 200;
widget.yMin = 0;
widget.yMax = 10;
widget.yUseMinMax = false;
break;
case 'Table':
widget.simulator = defaultSimulator;
@ -68,11 +71,14 @@ class WidgetFactory {
widget.preselectedSignals = [];
widget.signals = []; // initialize selected signals
widget.ylabel = '';
widget.minWidth = 400;
widget.minHeight = 300;
widget.width = 500;
widget.height = 500;
widget.minWidth = 200;
widget.minHeight = 100;
widget.width = 600;
widget.height = 300;
widget.time = 60;
widget.yMin = 0;
widget.yMax = 10;
widget.yUseMinMax = false;
break;
case 'Image':
widget.minWidth = 20;

View file

@ -127,8 +127,6 @@ class WidgetGauge extends Component {
zones = zones.map(zone => {
return Object.assign({}, zone, { min: (zone.min * step) + +minValue, max: zone.max * step + +minValue, strokeStyle: '#' + zone.strokeStyle });
});
console.log(zones);
}
this.gauge.setOptions({

View file

@ -37,7 +37,6 @@ class WidgetPlotTable extends Component {
}
componentWillReceiveProps(nextProps) {
// Update internal selected signals state with props (different array objects)
if (this.props.widget.signals !== nextProps.widget.signals) {
this.setState( {signals: nextProps.widget.signals});
@ -158,6 +157,9 @@ class WidgetPlotTable extends Component {
time={this.props.widget.time}
width={this.props.widget.width - 100}
height={this.props.widget.height - 55}
yMin={this.props.widget.yMin}
yMax={this.props.widget.yMax}
yUseMinMax={this.props.widget.yUseMinMax}
/>
</div>
</div>

View file

@ -25,45 +25,59 @@ import Plot from './widget-plot/plot';
import PlotLegend from './widget-plot/plot-legend';
class WidgetPlot extends React.Component {
render() {
const simulator = this.props.widget.simulator;
const simulation = this.props.simulation;
let legendSignals = [];
let simulatorData = [];
constructor(props) {
super(props);
this.state = {
data: [],
legend: []
};
}
componentWillReceiveProps(nextProps) {
const simulator = nextProps.widget.simulator;
const simulation = nextProps.simulation;
// Proceed if a simulation with models and a simulator are available
if (simulator && this.props.data[simulator.node] != null && this.props.data[simulator.node][simulator.simulator] != null && simulation && simulation.models.length > 0) {
if (simulator && nextProps.data[simulator.node] != null && nextProps.data[simulator.node][simulator.simulator] != null && simulation && simulation.models.length > 0) {
const model = simulation.models.find(model => model.simulator.node === simulator.node && model.simulator.simulator === simulator.simulator);
const chosenSignals = this.props.widget.signals;
const chosenSignals = nextProps.widget.signals;
simulatorData = this.props.data[simulator.node][simulator.simulator].values.filter((values, index) => (
this.props.widget.signals.findIndex(value => value === index) !== -1
const data = nextProps.data[simulator.node][simulator.simulator].values.filter((values, index) => (
nextProps.widget.signals.findIndex(value => value === index) !== -1
));
// Query the signals that will be displayed in the legend
legendSignals = model.mapping.reduce( (accum, model_signal, signal_index) => {
const legend = model.mapping.reduce( (accum, model_signal, signal_index) => {
if (chosenSignals.includes(signal_index)) {
accum.push({ index: signal_index, name: model_signal.name });
}
return accum;
}, []);
this.setState({ data, legend });
} else {
this.setState({ data: [], legend: [] });
}
}
return (
<div className="plot-widget" ref="wrapper">
<div className="widget-plot">
<Plot
data={simulatorData}
height={this.props.widget.height - 55}
width={this.props.widget.width - 20}
time={this.props.widget.time}
/>
</div>
<PlotLegend signals={legendSignals} />
render() {
return <div className="plot-widget" ref="wrapper">
<div className="widget-plot">
<Plot
data={this.state.data}
height={this.props.widget.height - 55}
width={this.props.widget.width - 20}
time={this.props.widget.time}
yMin={this.props.widget.yMin}
yMax={this.props.widget.yMax}
yUseMinMax={this.props.widget.yUseMinMax}
/>
</div>
);
<PlotLegend signals={this.state.legend} />
</div>;
}
}

View file

@ -7,25 +7,21 @@
* Unauthorized copying of this file, via any medium is strictly prohibited.
**********************************************************************************/
import React, { Component } from 'react';
import React from 'react';
import { scaleOrdinal, schemeCategory10 } from 'd3-scale';
class PlotLegend extends Component {
// constructor(props) {
// super(props);
// }
class PlotLegend extends React.Component {
render() {
var colorScale = scaleOrdinal(schemeCategory10);
const colorScale = scaleOrdinal(schemeCategory10);
return (
<div className="plot-legend">
{ this.props.signals.map( (signal) =>
<div key={signal.index} className="signal-legend"><span className="legend-color" style={{ background: colorScale(signal.index) }}>&nbsp;&nbsp;</span> {signal.name} </div>)
}
</div>
);
return <div className="plot-legend">
{this.props.signals.map(signal =>
<div key={signal.index} className="signal-legend">
<span className="legend-color" style={{ background: colorScale(signal.index) }}>&nbsp;&nbsp;</span>{signal.name}
</div>
)}
</div>;
}
}
export default PlotLegend;
export default PlotLegend;

View file

@ -22,22 +22,38 @@ class Plot extends React.Component {
constructor(props) {
super(props);
// create dummy axes
const xScale = scaleTime().domain([Date.now(), Date.now() + 5 * 1000]).range([leftMargin, props.width]);
const yScale = scaleLinear().domain([0, 10]).range([props.height, bottomMargin]);
const xAxis = axisBottom().scale(xScale).ticks(5).tickFormat(date => timeFormat("%M:%S")(date));
const yAxis = axisLeft().scale(yScale).ticks(5);
this.state = {
data: null
data: null,
xAxis,
yAxis
};
}
componentWillReceiveProps(nextProps) {
// check if data is valid
if (nextProps.data == null || nextProps.data.length === 0 || nextProps.data[0].length === 0) {
this.setState({ data: null });
// create empty plot axes
const xScale = scaleTime().domain([Date.now(), Date.now() + 5 * 1000]).range([leftMargin, nextProps.width]);
const yScale = scaleLinear().domain([0, 10]).range([nextProps.height, bottomMargin]);
const xAxis = axisBottom().scale(xScale).ticks(5).tickFormat(date => timeFormat("%M:%S")(date));
const yAxis = axisLeft().scale(yScale).ticks(5);
this.setState({ data: null, xAxis, yAxis });
return;
}
// only show data in requested time
let data = nextProps.data;
const firstTimestamp = data[0][data[0].length - 1].x - this.props.time * 1000;
const firstTimestamp = data[0][data[0].length - 1].x - nextProps.time * 1000;
if (data[0][0].x < firstTimestamp) {
// only show data in range (+100 ms)
const index = data[0].findIndex(value => value.x >= firstTimestamp - 100);
@ -50,20 +66,26 @@ class Plot extends React.Component {
xRange[0] = xRange[1] - nextProps.time * 1000;
}
let yRange = [0, 0];
let yRange;
data.map(values => {
const range = extent(values, p => p.y);
if (range[0] < yRange[0]) yRange[0] = range[0];
if (range[1] > yRange[1]) yRange[1] = range[1];
if (nextProps.yUseMinMax) {
yRange = [nextProps.yMin, nextProps.yMax];
} else {
yRange = [0, 0];
return values;
});
data.map(values => {
const range = extent(values, p => p.y);
if (range[0] < yRange[0]) yRange[0] = range[0];
if (range[1] > yRange[1]) yRange[1] = range[1];
return values;
});
}
// create scale functions for both axes
const xScale = scaleTime().domain(xRange).range([leftMargin, nextProps.width]);
const yScale = scaleLinear().domain(yRange).range([nextProps.height, bottomMargin]);
const xAxis = axisBottom().scale(xScale).ticks(5).tickFormat(date => timeFormat("%M:%S")(date));
const yAxis = axisLeft().scale(yScale).ticks(5);
@ -77,18 +99,16 @@ class Plot extends React.Component {
}
render() {
if (this.state.data == null) return false;
console.log(this.state);
return(
<svg width={this.props.width + leftMargin} height={this.props.height + bottomMargin}>
<g ref={node => select(node).call(this.state.xAxis)} style={{ transform: `translateY(${this.props.height}px)` }} />
<g ref={node => select(node).call(this.state.yAxis)} style={{ transform: `translateX(${leftMargin}px)`}} />
return <svg width={this.props.width + leftMargin} height={this.props.height + bottomMargin}>
<g ref={node => select(node).call(this.state.xAxis)} style={{ transform: `translateY(${this.props.height}px)` }} />
<g ref={node => select(node).call(this.state.yAxis)} style={{ transform: `translateX(${leftMargin}px)`}} />
<g>
{this.state.data}
</g>
</svg>
);
<g>
{this.state.data}
</g>
</svg>;
}
}