mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-30 00:00:13 +01:00
extracted plot and legend to separate components, reused them in plot widget
This commit is contained in:
parent
a6cbecd811
commit
58f49c13a1
5 changed files with 219 additions and 175 deletions
|
@ -11,21 +11,16 @@ import React, { Component } from 'react';
|
||||||
import { LineChart } from 'rd3';
|
import { LineChart } from 'rd3';
|
||||||
import { scaleOrdinal, schemeCategory10 } from 'd3-scale';
|
import { scaleOrdinal, schemeCategory10 } from 'd3-scale';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { FormGroup, Checkbox } from 'react-bootstrap';
|
import { FormGroup, Checkbox } from 'react-bootstrap';
|
||||||
|
|
||||||
|
import Plot from './widget-plot/plot';
|
||||||
|
import PlotLegend from './widget-plot/plot-legend';
|
||||||
|
|
||||||
class WidgetPlotTable extends Component {
|
class WidgetPlotTable extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.chartWrapper = null;
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
size: { w: 0, h: 0 },
|
|
||||||
firstTimestamp: 0,
|
|
||||||
latestTimestamp: 0,
|
|
||||||
sequence: null,
|
|
||||||
values: [],
|
|
||||||
preselectedSignals: [],
|
preselectedSignals: [],
|
||||||
signals: []
|
signals: []
|
||||||
};
|
};
|
||||||
|
@ -35,15 +30,6 @@ class WidgetPlotTable extends Component {
|
||||||
// check data
|
// check data
|
||||||
const simulator = nextProps.widget.simulator;
|
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)
|
// Update internal selected signals state with props (different array objects)
|
||||||
if (this.props.widget.signals !== nextProps.widget.signals) {
|
if (this.props.widget.signals !== nextProps.widget.signals) {
|
||||||
this.setState( {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
|
// Update the currently selected signals by intersecting with the preselected signals
|
||||||
// Do the same with the plot values
|
// Do the same with the plot values
|
||||||
var intersection = this.computeIntersection(nextProps.widget.preselectedSignals, nextProps.widget.signals);
|
var intersection = this.computeIntersection(nextProps.widget.preselectedSignals, nextProps.widget.signals);
|
||||||
this.setState({
|
this.setState({ signals: intersection });
|
||||||
signals: intersection,
|
|
||||||
values: this.state.values.filter( (values) => intersection.includes(values.index))
|
|
||||||
});
|
|
||||||
|
|
||||||
this.updatePreselectedSignalsState(nextProps);
|
this.updatePreselectedSignalsState(nextProps);
|
||||||
return;
|
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
|
// 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 });
|
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) {
|
updateSignalSelection(signal_index, checked) {
|
||||||
// Update the selected signals and propagate to parent component
|
// Update the selected signals and propagate to parent component
|
||||||
var new_widget = Object.assign({}, this.props.widget, {
|
var new_widget = Object.assign({}, this.props.widget, {
|
||||||
|
@ -153,6 +91,10 @@ class WidgetPlotTable extends Component {
|
||||||
render() {
|
render() {
|
||||||
var checkBoxes = [];
|
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) {
|
if (this.state.preselectedSignals && this.state.preselectedSignals.length > 0) {
|
||||||
// Create checkboxes using the signal indices from simulation model
|
// Create checkboxes using the signal indices from simulation model
|
||||||
checkBoxes = this.state.preselectedSignals.map( (signal) => {
|
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
|
// Prepare an array with the signals to show in the legend
|
||||||
var tickCount = Math.round(this.state.size.w / 80);
|
var legendSignals = this.state.preselectedSignals.reduce( (accum, signal, i) => {
|
||||||
var colorScale = scaleOrdinal(schemeCategory10);
|
if (this.state.signals.includes(signal.index)) {
|
||||||
|
accum.push({
|
||||||
|
index: signal.index,
|
||||||
|
name: signal.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return accum;
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="plot-table-widget" ref="wrapper">
|
<div className="plot-table-widget" ref="wrapper">
|
||||||
|
@ -186,34 +135,10 @@ class WidgetPlotTable extends Component {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="widget-plot">
|
<div className="widget-plot">
|
||||||
<div className="chart-wrapper" ref={ (domNode) => this.chartWrapper = domNode }>
|
<Plot signals={ this.state.signals } time={ this.props.widget.time } simulatorData={ simulatorData } />
|
||||||
{this.state.sequence &&
|
|
||||||
<LineChart
|
|
||||||
width={ this.state.size.w || 100 }
|
|
||||||
height={ this.state.size.h || 100 }
|
|
||||||
data={this.state.values }
|
|
||||||
colors={ scaleOrdinal(schemeCategory10) }
|
|
||||||
gridHorizontal={true}
|
|
||||||
xAccessor={(d) => { if (d != null) { return new Date(d.x); } }}
|
|
||||||
xAxisTickCount={ tickCount }
|
|
||||||
hoverAnimation={false}
|
|
||||||
circleRadius={0}
|
|
||||||
domain={{ x: [this.state.firstTimestamp, this.state.latestTimestamp] }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<PlotLegend signals={legendSignals} />
|
||||||
<div className="plot-legend">
|
|
||||||
{
|
|
||||||
this.state.preselectedSignals.reduce( (accum, signal, i) => {
|
|
||||||
if (this.state.signals.includes(signal.index)) {
|
|
||||||
accum.push(<div key={signal.index} className="signal-legend"><span className="legend-color" style={{ background: colorScale(signal.index) }}> </span> {signal.name} </div>)
|
|
||||||
}
|
|
||||||
return accum;
|
|
||||||
}, [])
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,81 +8,38 @@
|
||||||
**********************************************************************************/
|
**********************************************************************************/
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
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 {
|
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() {
|
render() {
|
||||||
if (this.state.sequence == null) {
|
if (this.props.simulation == null) {
|
||||||
return (<div>Empty</div>);
|
return (<div>Empty</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<div style={{ width: '100%', height: '100%' }} ref="wrapper">
|
<div className="plot-widget" ref="wrapper">
|
||||||
<LineChart
|
<div className="widget-plot">
|
||||||
width={this.props.widget.width}
|
<Plot signals={ this.props.widget.signals } time={ this.props.widget.time } simulatorData={ simulatorData } />
|
||||||
height={this.props.widget.height - 20}
|
</div>
|
||||||
data={this.state.values}
|
<PlotLegend signals={legendSignals} />
|
||||||
title={this.props.widget.name}
|
|
||||||
gridHorizontal={true}
|
|
||||||
xAccessor={(d) => { if (d != null) { return new Date(d.x); } }}
|
|
||||||
hoverAnimation={false}
|
|
||||||
circleRadius={0}
|
|
||||||
domain={{ x: [this.state.firstTimestamp, this.state.latestTimestamp] }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
31
src/components/widget-plot/plot-legend.js
Normal file
31
src/components/widget-plot/plot-legend.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* File: plot-legend.js
|
||||||
|
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||||
|
* 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 (
|
||||||
|
<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) }}> </span> {signal.name} </div>)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PlotLegend;
|
115
src/components/widget-plot/plot.js
Normal file
115
src/components/widget-plot/plot.js
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/**
|
||||||
|
* File: plot.js
|
||||||
|
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||||
|
* 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 (
|
||||||
|
<div className="chart-wrapper" ref={ (domNode) => this.chartWrapper = domNode }>
|
||||||
|
{this.state.sequence &&
|
||||||
|
<LineChart
|
||||||
|
width={ this.state.size.w || 100 }
|
||||||
|
height={ this.state.size.h || 100 }
|
||||||
|
margins= {{top: 10, right: 0, bottom: 20, left: 45 }}
|
||||||
|
data={this.state.values }
|
||||||
|
colors={ scaleOrdinal(schemeCategory10) }
|
||||||
|
gridHorizontal={true}
|
||||||
|
xAccessor={(d) => { if (d != null) { return new Date(d.x); } }}
|
||||||
|
xAxisTickCount={ tickCount }
|
||||||
|
hoverAnimation={false}
|
||||||
|
circleRadius={0}
|
||||||
|
domain={{ x: [this.state.firstTimestamp, this.state.latestTimestamp] }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Plot;
|
|
@ -187,7 +187,32 @@ div[class*="-widget"] .btn[disabled], .btn.disabled, div[class*="-widget"] input
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plot-table-widget .plot-legend {
|
.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: -webkit-flex;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -195,28 +220,19 @@ div[class*="-widget"] .btn[disabled], .btn.disabled, div[class*="-widget"] input
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plot-table-widget .signal-legend {
|
.signal-legend {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plot-table-widget .legend-color {
|
.legend-color {
|
||||||
height: 50%;
|
height: 50%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plot-table-widget .widget-plot {
|
/* End Plots */
|
||||||
-webkit-flex: 1 1 auto;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plot-table-widget .chart-wrapper {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
/* End PlotTable Widget */
|
|
||||||
|
|
||||||
/*.single-value-widget {
|
/*.single-value-widget {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
Loading…
Add table
Reference in a new issue