diff --git a/README.md b/README.md index 41ba215..76c1608 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,39 @@ # VILLASweb -## Description - -This is VILLASweb, the website displaying and processing simulation data in the web browser. The term __frontend__ refers to this project, the actual website. - -The frontend connects to __two__ backends: _VILLASweb-backend-go_ and _VILLASnode_. - -VILLASnode provides actual simulation data via websockets. VILLASweb-backend-go provides any other data like user accounts, infrastructure components and configurations, dashboards etc. - +This is VILLASweb, the website to configure real-time co-simulations and display simulation real-time data in the web browser. +The term **frontend** refers to this project, the actual website. +The frontend connects to **two** backends: [VILLASweb-backend-go](https://git.rwth-aachen.de/acs/public/villas/web-backend-go) and [VILLASnode](https://git.rwth-aachen.de/acs/public/villas/node). +VILLASnode provides actual simulation data via websockets. VILLASweb-backend-go provides any other data such as user accounts, infrastructure components and configurations, dashboards etc. For more information on the backends see their repositories. ## Frameworks - The frontend is build upon [ReactJS](https://facebook.github.io/react/) and [Flux](https://facebook.github.io/flux/). - React is responsible for rendering the UI and Flux for handling the data and communication with the backends. For more information also have a look at REACT.md - -Additional libraries are used, for a complete list see package.json. +Additional libraries are used, for a complete list see the file `package.json`. ## Data model - ![Datamodel](src/img/datamodel.png) ## Quick start - -We recommend Docker to get started quickly: - ```bash -$ git clone --recursive git@git.rwth-aachen.de:VILLASframework/VILLASweb.git -$ cd VILLASweb +$ git clone --recursive https://git.rwth-aachen.de/acs/public/villas/web.git +$ cd web $ npm install $ npm start ``` - We recommend to start the VILLASweb-backend-go before the frontend. If you want to use test data (including some test users), you can start the backend with the parameter `-mode=test`. Please check the repository of the VILLASweb-backend-go to find information on the test user login names and passwords. The testing mode is NOT intended for production deployments. +## Documentation + +More details on the setup and usage of VILLASweb is available here: +- [Requirements](doc/Requirements.md) +- [Structure and datamodel](doc/Structure.md) +- [Development setup](doc/development.md) +- [Production setup](doc/Production.md) + ## Copyright 2020, Institute for Automation of Complex Power Systems, EONERC diff --git a/doc/Production.md b/doc/Production.md new file mode 100644 index 0000000..e5fd20c --- /dev/null +++ b/doc/Production.md @@ -0,0 +1,82 @@ +# Production Setup {#web-production} + +## Setting up VILLASweb for production + +For development setup instructions see @ref web-development. +The production setup is based on docker. +Clone the [frontend](https://git.rwth-aachen.de/acs/public/villas/web) and [backend](https://git.rwth-aachen.de/acs/public/villas/web-backend-go) repositories on your computer and build the Docker images for both: + +### Frontend + - `cd VILLASweb` + - `docker build -t villasweb-frontend .` + +### Backend + - `cd ..\VILLASweb-backend-go` + - `docker build -t villasweb-backend .` + +### WIP Docker compose and/or Kubernetes +Run the production docker-compose file: + - `docker-compose -f docker-compose-production.yml up -d` + + +## Configure VILLASnode to get data into VILLASweb + +### Install VILLASnode + +See: @ref node-installation + +### Create a VILLASnode demo data source + +1. Create a new empty configuration file with the following contents and save it as `webdemo.conf`: + +> WIP this example configuration requires revision! +``` +nodes = { + sine = { + type = "signal" + + signal = "mixed" + values = 5 + rate = 25 + frequency = 5 + } + + web = { + type = "websocket" + + destinations = [ + "TODO" + ] + } + } + + paths = ( + { + in = "sine" + out = "web" + } + ) +``` + +The node `sine` is a software signal generator for 5 signals. +The node `web` is the websocket interface to stream the data generated by the `sine` node to the browser. + +> Note: If you do not want to use your local system as the destination for the websocket node, +>change the option `destinations` of the `web` node to the destination of your production environment, for example `https://my.production.environment/ws/webdemo`. + +### Start the VILLASnode gateway + +Run the following command on your system: + +```bash +villas node webdemo.conf +``` +> Note: Change the path to the configuration file accordingly. The `villas` command will only work if VILLASnode is installed on your system. + +### Visualize real-time data in VILLASweb Dashboards +1. Use the VILLASweb frontend to create a new infrastructure component for the VILLASnode gateway from above (Admin user required). +2. Set the `host` parameter of the component to the target you used as the `web.destinations` parameter in the configuration from above. +3. Create a new scenario in VILLASweb and within that scenario create a new component configuration that uses the infrastructure component you created under 2. +4. WIP: Use the signal auto-configure function to retrieve the signal configuration of the VILLASnode automatically. +5. Create a new dashboard with widgets of your choice and link these widgets to the signals received from the infrastructure component. +6. Enjoy what you see. diff --git a/doc/Requirements.md b/doc/Requirements.md index c53871b..2ec0efe 100644 --- a/doc/Requirements.md +++ b/doc/Requirements.md @@ -1,16 +1,12 @@ # Requirements {#web-requirements} -## Services - - NodeJS: Runs VILLASweb frontend - - Go: Runs VILLASweb backend - - PostgreSQL database (min version 11): Backend database +## Services and tools required for development + - [NodeJS with npm](https://nodejs.org/en/): Runs VILLASweb frontend + - [Go](https://golang.org/): Runs VILLASweb backend + - [PostgreSQL database](https://www.postgresql.org/) (min version 11): Backend database - [swag](https://github.com/swaggo/swag): For automated API documentation creation - - NGinX: Webserver and reverse proxy for backends (only for production) - - Docker: Container management system + - [Docker](https://www.docker.com/): Container management system -## Installed on your local computer - - NodeJS with npm - - Go (at least version 1.11) - - [swag](https://github.com/swaggo/swag) - - Docker +## Additional requirements for productive use + - [NGinX](https://www.nginx.com/): Webserver and reverse proxy for backends diff --git a/doc/development.md b/doc/development.md index 259fb19..5b29595 100644 --- a/doc/development.md +++ b/doc/development.md @@ -8,11 +8,11 @@ In order to get started with VILLASweb, you might also want to check our our [de ### Description -The website itself based on the React JavaScript framework. +The website itself based on the [React JavaScript framework](https://reactjs.org/) and the [Flux library](https://facebook.github.io/flux/). ### Required - - NodeJS with npm + - [NodeJS with npm](https://nodejs.org/en/) ### Setup @@ -25,18 +25,19 @@ The website itself based on the React JavaScript framework. - `npm start` This runs the development server for the website on your local computer at port 3000. -The backend must be running to make the website work. +The backend must be running to make the website work. +Type `http://localhost:3000/` in the address field of your browser to open the website. ## Backend ### Description -The backend of VILLASweb uses the programming language Go and a PostgreSQL data base. +The backend of VILLASweb uses the programming language Go and a PostgreSQL database. ### Required - - Go (min version 1.11) - - Running PostgreSQL data base (min version 11) + - [Go](https://golang.org/) (min version 1.11) + - [PostgreSQL database](https://www.postgresql.org/) (min version 11) - [swag](https://github.com/swaggo/swag) ### Setup and Running diff --git a/src/common/table-column.js b/src/common/table-column.js index 748035a..53b99e5 100644 --- a/src/common/table-column.js +++ b/src/common/table-column.js @@ -29,6 +29,7 @@ class TableColumn extends Component { linkKey: '', dataIndex: false, inlineEditable: false, + inputType: 'text', clickable: false, labelKey: null, checkbox: false, diff --git a/src/common/table.js b/src/common/table.js index b4edea3..d68191a 100644 --- a/src/common/table.js +++ b/src/common/table.js @@ -205,7 +205,7 @@ class CustomTable extends Component { return ( {(this.state.editCell[0] === cellIndex && this.state.editCell[1] === rowIndex ) ? ( - children[cellIndex].props.onInlineChange(event, rowIndex, cellIndex)} ref={ref => { this.activeInput = ref; }} /> + children[cellIndex].props.onInlineChange(event, rowIndex, cellIndex)} ref={ref => { this.activeInput = ref; }} /> ) : ( {cell.map((element, elementIndex) => ( diff --git a/src/dashboard/dashboard.js b/src/dashboard/dashboard.js index 874eb10..05f69e1 100644 --- a/src/dashboard/dashboard.js +++ b/src/dashboard/dashboard.js @@ -73,6 +73,10 @@ class Dashboard extends Component { return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar; }, 0); + if(dashboard.height === 0 || maxHeight + 80 > dashboard.height) + { + dashboard.height = maxHeight + 80; + } // filter component configurations to the ones that belong to this scenario let configs = [] @@ -108,7 +112,7 @@ class Dashboard extends Component { return ICused; }); } - + return { dashboard, widgets, @@ -130,7 +134,6 @@ class Dashboard extends Component { widgetOrigIDs: prevState.widgetOrigIDs || [], maxWidgetHeight: maxHeight || null, - dropZoneHeight: maxHeight +80 || null, }; } @@ -188,25 +191,6 @@ class Dashboard extends Component { } } - /* - * Adapt the area's height with the position of the new widget. - * Return true if the height increased, otherwise false. - */ - increaseHeightWithWidget(widget) { - let increased = false; - let thisWidgetHeight = widget.y + widget.height; - - if (thisWidgetHeight > this.state.maxWidgetHeight) { - increased = true; - - this.setState({ - maxWidgetHeight: thisWidgetHeight, - dropZoneHeight: thisWidgetHeight + 40 - }); - } - - return increased; - } transformToWidgetsList(widgets) { return Object.keys(widgets).map( (key) => widgets[key]); @@ -238,24 +222,6 @@ class Dashboard extends Component { } - /* - * Set the initial height state based on the existing widgets - */ - computeHeightWithWidgets(widgets) { - // Compute max height from widgets - let maxHeight = Object.keys(widgets).reduce( (maxHeightSoFar, widgetKey) => { - let thisWidget = widgets[widgetKey]; - let thisWidgetHeight = thisWidget.y + thisWidget.height; - - return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar; - }, 0); - - this.setState({ - maxWidgetHeight: maxHeight, - dropZoneHeight: maxHeight + 80 - }); - } - editWidget(widget, index){ this.setState({ editModal: true, modalData: widget, modalIndex: index }); @@ -377,6 +343,12 @@ class Dashboard extends Component { token: this.state.sessionToken, param: '?dashboardID=' + this.state.dashboard.id }); + + AppDispatcher.dispatch({ + type: 'dashboards/start-load', + data: this.props.match.params.dashboard, + token: this.state.sessionToken + }); this.setState({ editing: false, widgetChangeData: []}); }; @@ -389,6 +361,30 @@ class Dashboard extends Component { this.forceUpdate(); }; + setDashboardSize(value) { + const maxHeight = Object.values(this.state.widgets).reduce((currentHeight, widget) => { + const absolutHeight = widget.y + widget.height; + + return absolutHeight > currentHeight ? absolutHeight : currentHeight; + }, 0); + let dashboard = this.state.dashboard; + if(value === -1){ + + let tempHeight = this.state.dashboard.height - 50; + + if(tempHeight > (maxHeight + 80)){ + dashboard.height = tempHeight; + this.setState({dashboard}); + } + } + else{ + dashboard.height = this.state.dashboard.height +50; + this.setState( {dashboard}); + } + + this.forceUpdate(); + } + pauseData(){ this.setState({ paused: true }); }; @@ -402,6 +398,7 @@ class Dashboard extends Component { const grid = this.state.dashboard.grid; const boxClasses = classNames('section', 'box', { 'fullscreen-padding': this.props.isFullscreen }); let draggable = this.state.editing; + let dropZoneHeight = this.state.dashboard.height; return
@@ -424,10 +421,10 @@ class Dashboard extends Component {
e.preventDefault() }> {this.state.editing && - + } {!draggable?( - + {this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => ( ) : ( - + {this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => ( s.id === signalID); + + //dispatch changes to signal + AppDispatcher.dispatch({ + type: 'signals/start-edit', + data: sig, + token: this.props.sessionToken, + }); + } + this.props.onCloseEdit(this.state.dir) } handleMappingChange = (event, row, column) => { - let sig = {} + + let signals = this.state.signals; + let modifiedSignals = this.state.modifiedSignalIDs; + if (column === 1) { // Name change - if (event.target.value !== '') { - sig = this.state.signals[row]; - sig.name = event.target.value; - } + signals[row].name = event.target.value; + if (modifiedSignals.find(id => id === signals[row].id) === undefined){ + modifiedSignals.push(signals[row].id); + } } else if (column === 2) { // unit change - if (event.target.value !== '') { - sig = this.state.signals[row]; - sig.unit = event.target.value; - } + signals[row].unit = event.target.value; + if (modifiedSignals.find(id => id === signals[row].id) === undefined){ + modifiedSignals.push(signals[row].id); + } + } else if (column === 3) { // scaling factor change + signals[row].scalingFactor = parseFloat(event.target.value); + if (modifiedSignals.find(id => id === signals[row].id) === undefined){ + modifiedSignals.push(signals[row].id); + } } else if (column === 0) { //index change - sig = this.state.signals[row]; - sig.index = parseInt(event.target.value, 10); + signals[row].index =parseInt(event.target.value, 10); + if (modifiedSignals.find(id => id === signals[row].id) === undefined){ + modifiedSignals.push(signals[row].id); + } } - if (sig !== {}){ - //dispatch changes to signal - AppDispatcher.dispatch({ - type: 'signals/start-edit', - data: sig, - token: this.props.sessionToken, - }); - } + this.setState({ + signals: signals, + modifiedSignalIDs: modifiedSignals + }) }; @@ -108,7 +128,8 @@ class EditSignalMapping extends React.Component { direction: this.state.dir, name: "PlaceholderName", unit: "PlaceholderUnit", - index: 999 + index: 999, + scalingFactor: 1.0 }; AppDispatcher.dispatch({ @@ -139,7 +160,7 @@ class EditSignalMapping extends React.Component { this.onClose(c)} onReset={() => this.resetState()} @@ -149,9 +170,10 @@ class EditSignalMapping extends React.Component { {this.props.direction} Mapping Click Index, Name or Unit cell to edit - this.handleMappingChange(e, row, column)} /> - this.handleMappingChange(e, row, column)} /> - this.handleMappingChange(e, row, column)} /> + this.handleMappingChange(e, row, column)} /> + this.handleMappingChange(e, row, column)} /> + this.handleMappingChange(e, row, column)} /> + this.handleMappingChange(e, row, column)} /> this.handleDelete(index)} />
diff --git a/src/styles/app.css b/src/styles/app.css index 97c6548..797512a 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -387,7 +387,7 @@ body { .section-buttons-group-right { height: auto !important; - + padding: 5px; float: right; } diff --git a/src/styles/widgets.css b/src/styles/widgets.css index 192acdd..4a8c976 100644 --- a/src/styles/widgets.css +++ b/src/styles/widgets.css @@ -373,9 +373,9 @@ div[class*="-widget"] label { /* End table widget*/ /* Begin box widget */ -.box-widget .border { +.box-widget { width: 100%; height: 100%; - border: 2px solid; + border: 2px solid lightgray; } /* End box widget */ diff --git a/src/widget/widget-area.js b/src/widget/widget-area.js index 0686066..888c311 100644 --- a/src/widget/widget-area.js +++ b/src/widget/widget-area.js @@ -44,13 +44,8 @@ class WidgetArea extends React.Component { } render() { - const maxHeight = Object.values(this.props.widgets).reduce((currentHeight, widget) => { - const absolutHeight = widget.y + widget.height; - return absolutHeight > currentHeight ? absolutHeight : currentHeight; - }, 0); - - return + return {this.props.children} diff --git a/src/widget/widget-factory.js b/src/widget/widget-factory.js index 1360988..8b08117 100644 --- a/src/widget/widget-factory.js +++ b/src/widget/widget-factory.js @@ -168,6 +168,7 @@ class WidgetFactory { widget.width = 100; widget.height = 100; widget.customProperties.border_color = 0; + widget.customProperties.background_color = 9; widget.customProperties.background_color_opacity = 0.5; widget.z = 0; break; diff --git a/src/widget/widget-plot/plot-legend.js b/src/widget/widget-plot/plot-legend.js index f3f4cd6..5ac618c 100644 --- a/src/widget/widget-plot/plot-legend.js +++ b/src/widget/widget-plot/plot-legend.js @@ -27,7 +27,7 @@ class PlotLegend extends React.Component {
    {this.props.signals.map(signal =>
  • - {signal.name} + {signal.name + "(x" + signal.scalingFactor + ")"} {signal.unit}
  • )} diff --git a/src/widget/widget-toolbox.js b/src/widget/widget-toolbox.js index 60be4af..18912f1 100644 --- a/src/widget/widget-toolbox.js +++ b/src/widget/widget-toolbox.js @@ -18,6 +18,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import Slider from 'rc-slider'; +import { Button, OverlayTrigger, Tooltip } from 'react-bootstrap'; +import Icon from "../common/icon"; import ToolboxItem from './toolbox-item'; @@ -57,6 +59,21 @@ class WidgetToolbox extends React.Component {
    Grid: { this.props.grid > 1 ? this.props.grid : 'Disabled' } + +
    +
+
+
+ Increase dashboard height } > + + + Decrease dashboard height } > + +
; @@ -66,7 +83,8 @@ class WidgetToolbox extends React.Component { WidgetToolbox.propTypes = { widgets: PropTypes.array, grid: PropTypes.number, - onGridChange: PropTypes.func + onGridChange: PropTypes.func, + onDashboardSizeChange: PropTypes.func }; export default WidgetToolbox; diff --git a/src/widget/widget.js b/src/widget/widget.js index 11fe5c2..e2faeb1 100644 --- a/src/widget/widget.js +++ b/src/widget/widget.js @@ -88,13 +88,13 @@ class Widget extends React.Component { }; } - inputDataChanged(widget, data, controlID) { + inputDataChanged(widget, data, controlID, controlValue) { // controlID is the path to the widget customProperty that is changed (for example 'value') // modify the widget customProperty if (controlID !== '') { let updatedWidget = JSON.parse(JSON.stringify(widget)); - updatedWidget.customProperties[controlID] = data; + updatedWidget.customProperties[controlID] = controlValue; AppDispatcher.dispatch({ type: 'widgets/start-edit', token: this.state.sessionToken, @@ -118,7 +118,7 @@ class Widget extends React.Component { type: 'icData/inputChanged', ic: icID, signal: signal[0].index, - data + data: signal[0].scalingFactor * data }); } @@ -179,14 +179,14 @@ class Widget extends React.Component { return this.inputDataChanged(widget, value, controlID)} + onInputChanged={(value, controlID, controlValue) => this.inputDataChanged(widget, value, controlID, controlValue)} signals={this.state.signals} /> } else if (widget.type === 'NumberInput') { return this.inputDataChanged(widget, value, controlID)} + onInputChanged={(value, controlID, controlValue) => this.inputDataChanged(widget, value, controlID, controlValue)} signals={this.state.signals} /> } else if (widget.type === 'Slider') { @@ -194,7 +194,7 @@ class Widget extends React.Component { widget={widget} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } - onInputChanged={(value, controlID) => this.inputDataChanged(widget, value, controlID)} + onInputChanged={(value, controlID, controlValue) => this.inputDataChanged(widget, value, controlID, controlValue)} signals={this.state.signals} /> } else if (widget.type === 'Gauge') { diff --git a/src/widget/widgets/box.js b/src/widget/widgets/box.js index ff470f3..88e7c67 100644 --- a/src/widget/widgets/box.js +++ b/src/widget/widgets/box.js @@ -31,10 +31,8 @@ class WidgetBox extends Component { } return ( -
-
+
{ } -
); } diff --git a/src/widget/widgets/button.js b/src/widget/widgets/button.js index e3a11a0..e95e740 100644 --- a/src/widget/widgets/button.js +++ b/src/widget/widgets/button.js @@ -28,11 +28,16 @@ class WidgetButton extends Component { } } + static getDerivedStateFromProps(props, state){ + return { + pressed: props.widget.customProperties.pressed + } + } + onPress(e) { if (e.button === 0 && !this.props.widget.customProperties.toggle) { - this.setState({ pressed: true }); - this.valueChanged(this.props.widget.customProperties.on_value); + this.valueChanged(this.props.widget.customProperties.on_value, true); } } @@ -43,15 +48,14 @@ class WidgetButton extends Component { if (this.props.widget.customProperties.toggle) { nextState = !this.state.pressed; } - this.props.widget.customProperties.pressed = nextState; - this.setState({pressed: nextState}); - this.valueChanged(nextState ? this.props.widget.customProperties.on_value : this.props.widget.customProperties.off_value); + this.valueChanged(nextState ? this.props.widget.customProperties.on_value : this.props.widget.customProperties.off_value, nextState); } } - valueChanged(newValue) { - if (this.props.onInputChanged) - this.props.onInputChanged(newValue, 'pressed'); + valueChanged(newValue, pressed) { + if (this.props.onInputChanged) { + this.props.onInputChanged(newValue, 'pressed', pressed); + } } render() { diff --git a/src/widget/widgets/gauge.js b/src/widget/widgets/gauge.js index 00ffa93..3aa3e3b 100644 --- a/src/widget/widgets/gauge.js +++ b/src/widget/widgets/gauge.js @@ -49,7 +49,7 @@ class WidgetGauge extends Component { } componentDidUpdate(prevProps: Readonly

, prevState: Readonly, snapshot: SS): void { - + // update gauge's value if(prevState.value !== this.state.value){ this.gauge.set(this.state.value) @@ -116,7 +116,7 @@ class WidgetGauge extends Component { // Take just 3 decimal positions // Note: Favor this method over Number.toFixed(n) in order to avoid a type conversion, since it returns a String if (data != null) { - const value = Math.round(data[data.length - 1].y * 1e3) / 1e3; + const value = signal[0].scalingFactor * Math.round(data[data.length - 1].y * 1e3) / 1e3; let minValue = null; let maxValue = null; @@ -149,7 +149,7 @@ class WidgetGauge extends Component { maxValue = props.widget.customProperties.valueMax; updateMaxValue = true; updateLabels = true; - + } if (updateLabels === false && state.gauge) { @@ -169,7 +169,7 @@ class WidgetGauge extends Component { if(props.widget.customProperties.valueUseMinMax !== state.useMinMax){ returnState["useMinMax"] = props.widget.customProperties.valueUseMinMax; } - + // prepare returned state if(updateValue === true){ returnState["value"] = value; @@ -201,7 +201,7 @@ class WidgetGauge extends Component { for (let i = 0; i < labelCount; i++) { labels.push(minValue + labelStep * i); } - + // calculate zones let zones = this.props.widget.customProperties.colorZones ? this.props.widget.customProperties.zones : null; if (zones != null) { diff --git a/src/widget/widgets/input.js b/src/widget/widgets/input.js index 7fe7c38..0b87617 100644 --- a/src/widget/widgets/input.js +++ b/src/widget/widgets/input.js @@ -71,7 +71,7 @@ class WidgetInput extends Component { valueChanged(newValue) { if (this.props.onInputChanged) { - this.props.onInputChanged(Number(newValue), 'value'); + this.props.onInputChanged(Number(newValue), 'value', Number(newValue)); } } diff --git a/src/widget/widgets/lamp.js b/src/widget/widgets/lamp.js index 939caed..c0ca9a0 100644 --- a/src/widget/widgets/lamp.js +++ b/src/widget/widgets/lamp.js @@ -49,8 +49,8 @@ class WidgetLamp extends Component { // check if value has changed const data = props.data[icID].output.values[signal[0].index-1]; - if (data != null && Number(state.value) !== data[data.length - 1].y) { - return { value: data[data.length - 1].y }; + if (data != null && Number(state.value) !== signal[0].scalingFactor * data[data.length - 1].y) { + return { value: signal[0].scalingFactor * data[data.length - 1].y }; } return null; diff --git a/src/widget/widgets/plot.js b/src/widget/widgets/plot.js index 20245e2..e87e7d1 100644 --- a/src/widget/widgets/plot.js +++ b/src/widget/widgets/plot.js @@ -49,13 +49,31 @@ class WidgetPlot extends React.Component { if (sig.direction === "out") { if (props.data[icID] != null && props.data[icID].output != null && props.data[icID].output.values != null) { if (props.data[icID].output.values[sig.index-1] !== undefined) { - data.push(props.data[icID].output.values[sig.index-1]); + let values = props.data[icID].output.values[sig.index-1]; + if(sig.scalingFactor !== 1) { + let scaledValues = JSON.parse(JSON.stringify(values)); + for (let i=0; i< scaledValues.length; i++){ + scaledValues[i].y = scaledValues[i].y * sig.scalingFactor; + } + data.push(scaledValues); + } else { + data.push(values); + } } } } else if (sig.direction === "in") { if (props.data[icID] != null && props.data[icID].input != null && props.data[icID].input.values != null) { if (props.data[icID].input.values[sig.index-1] !== undefined) { - data.push(props.data[icID].input.values[sig.index-1]); + let values = props.data[icID].output.values[sig.index-1]; + if(sig.scalingFactor !== 1) { + let scaledValues = JSON.parse(JSON.stringify(values)); + for (let i=0; i< scaledValues.length; i++){ + scaledValues[i].y = scaledValues[i].y * sig.scalingFactor; + } + data.push(scaledValues); + } else { + data.push(values); + } } } } @@ -67,6 +85,11 @@ class WidgetPlot extends React.Component { } + + scaleData(data, scaleFactor){ + // data is an array of value pairs x,y + } + render() { return

diff --git a/src/widget/widgets/slider.js b/src/widget/widgets/slider.js index 89f4da7..10bb055 100644 --- a/src/widget/widgets/slider.js +++ b/src/widget/widgets/slider.js @@ -105,7 +105,7 @@ class WidgetSlider extends Component { valueChanged(newValue) { if (this.props.onInputChanged) { - this.props.onInputChanged(newValue, 'value'); + this.props.onInputChanged(newValue, 'value', newValue); } } diff --git a/src/widget/widgets/table.js b/src/widget/widgets/table.js index 089eec0..3ecc59a 100644 --- a/src/widget/widgets/table.js +++ b/src/widget/widgets/table.js @@ -41,15 +41,20 @@ class WidgetTable extends Component { // determine ID of infrastructure component related to signal (via config) let icID = props.icIDs[sig.id] + let signalName = sig.name; + if(sig.scalingFactor !== 1.0){ + signalName = signalName + "(x" + String(sig.scalingFactor) + ")"; + } + // distinguish between input and output signals if (sig.direction === "out") { if (props.data[icID] != null && props.data[icID].output != null && props.data[icID].output.values != null) { if (props.data[icID].output.values[sig.index-1] !== undefined) { let data = props.data[icID].output.values[sig.index-1]; rows.push({ - name: sig.name, + name: signalName, unit: sig.unit, - value: data[data.length - 1].y + value: data[data.length - 1].y * sig.scalingFactor }); } @@ -59,9 +64,9 @@ class WidgetTable extends Component { if (props.data[icID].input.values[sig.index-1] !== undefined) { let data = props.data[icID].input.values[sig.index-1]; rows.push({ - name: sig.name, + name: signalName, unit: sig.unit, - value: data[data.length - 1].y + value: data[data.length - 1].y * sig.scalingFactor }); } } @@ -86,7 +91,7 @@ class WidgetTable extends Component { var columns = [ , - + ]; if (this.props.widget.customProperties.showUnit) diff --git a/src/widget/widgets/value.js b/src/widget/widgets/value.js index 0468b3e..8c4c5a9 100644 --- a/src/widget/widgets/value.js +++ b/src/widget/widgets/value.js @@ -47,7 +47,7 @@ class WidgetValue extends Component { // check if value has changed const data = props.data[icID].output.values[signal[0].index - 1]; if (data != null && Number(state.value) !== data[data.length - 1].y) { - value = data[data.length - 1].y; + value = signal[0].scalingFactor * data[data.length - 1].y; } } @@ -66,12 +66,12 @@ class WidgetValue extends Component { render() { let value_to_render = Number(this.state.value); - let value_width = this.props.widget.customProperties.textSize*(value_to_render < 1000 ? (2):(3)); + let value_width = this.props.widget.customProperties.textSize*(Math.abs(value_to_render) < 1000 ? (5):(8)); let unit_width = this.props.widget.customProperties.textSize*(this.state.unit.length + 0.7); return (
{this.props.widget.name} - {Number.isNaN(value_to_render) ? NaN : format('.3s')(value_to_render)} + {Number.isNaN(value_to_render) ? NaN : format('.3f')(value_to_render)} {this.props.widget.customProperties.showUnit && [{this.state.unit}] }