mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
converted widgets to functional components
Signed-off-by: iripiri <ikoester@eonerc.rwth-aachen.de>
This commit is contained in:
parent
59b3a20dd4
commit
40ea8a6d36
14 changed files with 797 additions and 829 deletions
|
@ -1,57 +0,0 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
class EditWidgetAspectControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {}
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
return{
|
||||
widget: props.widget
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
let parts = this.props.controlId.split('.');
|
||||
let isCustomProperty = true;
|
||||
if (parts.length === 1){
|
||||
isCustomProperty = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<Form.Group style={this.props.style}>
|
||||
<Form.Check
|
||||
type='checkbox'
|
||||
id={this.props.controlId}
|
||||
checked={isCustomProperty ? this.state.widget[parts[0]][parts[1]] : this.state.widget[this.props.controlId]}
|
||||
label={"Lock Aspect Ratio"}
|
||||
onChange={e => this.props.handleChange(e)}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetAspectControl;
|
45
src/widget/edit-widget/edit-widget-aspect-control.jsx
Normal file
45
src/widget/edit-widget/edit-widget-aspect-control.jsx
Normal file
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
import React from "react";
|
||||
import { Form } from "react-bootstrap";
|
||||
|
||||
function EditWidgetAspectControl(props) {
|
||||
// As we are not using state for any purpose other than direct reflection of props,
|
||||
// we will directly utilize props instead of maintaining a separate state.
|
||||
const widget = props.widget;
|
||||
|
||||
let parts = props.controlId.split(".");
|
||||
let isCustomProperty = parts.length > 1;
|
||||
|
||||
return (
|
||||
<Form.Group style={props.style}>
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
id={props.controlId}
|
||||
checked={
|
||||
isCustomProperty
|
||||
? widget[parts[0]][parts[1]]
|
||||
: widget[props.controlId]
|
||||
}
|
||||
label={"Lock Aspect Ratio"}
|
||||
onChange={props.handleChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditWidgetAspectControl;
|
|
@ -1,62 +0,0 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
class EditWidgetCheckboxControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isChecked: false,
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
let parts = props.controlId.split('.');
|
||||
let isChecked;
|
||||
|
||||
if (parts.length ===1){
|
||||
isChecked = props.widget[props.controlId]
|
||||
} else {
|
||||
isChecked = props.widget[parts[0]][parts[1]]
|
||||
}
|
||||
|
||||
return {
|
||||
isChecked
|
||||
};
|
||||
}
|
||||
|
||||
handleCheckboxChange(e){
|
||||
this.props.handleChange({target: { id: this.props.controlId, value: !this.state.isChecked} })
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Form.Group style={this.props.style}>
|
||||
<Form.Check
|
||||
type={"checkbox"}
|
||||
id={this.props.controlId}
|
||||
label={this.props.text}
|
||||
defaultChecked={this.state.isChecked}
|
||||
onChange={e => this.handleCheckboxChange(e)}
|
||||
disabled={this.props.disabled !== 'undefined' ? this.props.disabled : true}
|
||||
/>
|
||||
</Form.Group>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetCheckboxControl;
|
67
src/widget/edit-widget/edit-widget-checkbox-control.jsx
Normal file
67
src/widget/edit-widget/edit-widget-checkbox-control.jsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Form } from "react-bootstrap";
|
||||
|
||||
function EditWidgetCheckboxControl(props) {
|
||||
let parts = props.controlId.split(".");
|
||||
// Determine initial value whether the property is nested or direct
|
||||
let initialChecked =
|
||||
parts.length === 1
|
||||
? props.widget[props.controlId]
|
||||
: props.widget[parts[0]][parts[1]];
|
||||
|
||||
// Use useState to setup the isChecked state
|
||||
const [isChecked, setIsChecked] = useState(initialChecked);
|
||||
|
||||
// useEffect to update state when props change
|
||||
useEffect(() => {
|
||||
let updatedChecked =
|
||||
parts.length === 1
|
||||
? props.widget[props.controlId]
|
||||
: props.widget[parts[0]][parts[1]];
|
||||
setIsChecked(updatedChecked);
|
||||
}, [props.widget, props.controlId]);
|
||||
|
||||
// Event handler that uses the state's current value
|
||||
const handleCheckboxChange = (e) => {
|
||||
setIsChecked(!isChecked); // We toggle the state
|
||||
|
||||
// Calling parent's handleChange function with the new value
|
||||
props.handleChange({
|
||||
target: {
|
||||
id: props.controlId,
|
||||
value: !isChecked,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form.Group style={props.style}>
|
||||
<Form.Check
|
||||
type={"checkbox"}
|
||||
id={props.controlId}
|
||||
label={props.text}
|
||||
checked={isChecked}
|
||||
onChange={handleCheckboxChange}
|
||||
disabled={props.disabled !== "undefined" ? props.disabled : true}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditWidgetCheckboxControl;
|
|
@ -1,70 +0,0 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
class EditWidgetCheckboxList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
checkedIDs: [],
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props) {
|
||||
return {
|
||||
checkedIDs: props.widget.customProperties.checkedIDs
|
||||
};
|
||||
}
|
||||
|
||||
handleCheckboxChange(e){
|
||||
let checkedIDs = this.props.widget.customProperties.checkedIDs
|
||||
let currentID = parseInt(e.target.id, 10)
|
||||
let index = checkedIDs.indexOf(currentID)
|
||||
if (index === -1) {
|
||||
checkedIDs.push(currentID)
|
||||
} else {
|
||||
checkedIDs.splice(index, 1)
|
||||
}
|
||||
this.props.handleChange({target: { id: this.props.controlId, value: checkedIDs} })
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let checkboxList = []
|
||||
if (this.props.list) {
|
||||
this.props.list.forEach((item) => {
|
||||
checkboxList.push(<Form.Check
|
||||
type={"checkbox"}
|
||||
id={item.id}
|
||||
key={item.id}
|
||||
label={item.name}
|
||||
defaultChecked={this.state.checkedIDs.includes(parseInt(item.id, 10))}
|
||||
onChange={e => this.handleCheckboxChange(e)}
|
||||
/>)
|
||||
})
|
||||
}
|
||||
|
||||
return <Form.Group style={this.props.style}>
|
||||
<Form.Label>{this.props.label}</Form.Label>
|
||||
{checkboxList}
|
||||
</Form.Group>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetCheckboxList;
|
72
src/widget/edit-widget/edit-widget-checkbox-list.jsx
Normal file
72
src/widget/edit-widget/edit-widget-checkbox-list.jsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Form } from "react-bootstrap";
|
||||
|
||||
function EditWidgetCheckboxList(props) {
|
||||
// Initialize the checkedIDs state with the customProperties.checkedIDs from props
|
||||
const [checkedIDs, setCheckedIDs] = useState(
|
||||
props.widget.customProperties.checkedIDs
|
||||
);
|
||||
|
||||
// Use useEffect to update the state when the widget prop changes
|
||||
useEffect(() => {
|
||||
setCheckedIDs(props.widget.customProperties.checkedIDs);
|
||||
}, [props.widget.customProperties.checkedIDs]);
|
||||
|
||||
// Event handler for changes in checkboxes
|
||||
const handleCheckboxChange = (e) => {
|
||||
let currentID = parseInt(e.target.id, 10);
|
||||
let index = checkedIDs.indexOf(currentID);
|
||||
|
||||
let newCheckedIDs = [...checkedIDs]; // Create a new array to avoid direct mutation
|
||||
if (index === -1) {
|
||||
newCheckedIDs.push(currentID); // Add the id if it's not already in the array
|
||||
} else {
|
||||
newCheckedIDs.splice(index, 1); // Remove the id if it's already in the array
|
||||
}
|
||||
|
||||
setCheckedIDs(newCheckedIDs); // Update the state with the new array
|
||||
props.handleChange({
|
||||
target: {
|
||||
id: props.controlId,
|
||||
value: newCheckedIDs,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Generate the list of checkboxes from the props list
|
||||
let checkboxList = props.list?.map((item) => (
|
||||
<Form.Check
|
||||
type={"checkbox"}
|
||||
id={item.id.toString()}
|
||||
key={item.id}
|
||||
label={item.name}
|
||||
checked={checkedIDs.includes(parseInt(item.id, 10))}
|
||||
onChange={handleCheckboxChange}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<Form.Group style={props.style}>
|
||||
<Form.Label>{props.label}</Form.Label>
|
||||
{checkboxList}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditWidgetCheckboxList;
|
|
@ -1,283 +0,0 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { scaleLinear, scaleTime, scaleOrdinal} from 'd3-scale';
|
||||
import { schemeCategory10 } from 'd3-scale-chromatic'
|
||||
import { extent } from 'd3-array';
|
||||
import { line } from 'd3-shape';
|
||||
import { axisBottom, axisLeft } from 'd3-axis';
|
||||
import { select } from 'd3-selection';
|
||||
import { timeFormat } from 'd3-time-format';
|
||||
import { format } from 'd3';
|
||||
|
||||
const topMargin = 10;
|
||||
const bottomMargin = 25;
|
||||
const leftMargin = 40;
|
||||
const rightMargin = 10;
|
||||
|
||||
let uniqueIdentifier = 0;
|
||||
|
||||
class Plot extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// create dummy axes
|
||||
let labelMargin = 0;
|
||||
if (props.yLabel !== '') {
|
||||
labelMargin = 30;
|
||||
}
|
||||
|
||||
const xScale = scaleTime().domain([Date.now() - props.time * 1000, Date.now()]).range([0, props.width - leftMargin - labelMargin - rightMargin]);
|
||||
let yScale;
|
||||
|
||||
if (props.yUseMinMax) {
|
||||
yScale = scaleLinear().domain([props.yMin, props.yMax]).range([props.height + topMargin - bottomMargin, topMargin]);
|
||||
} else {
|
||||
yScale = scaleLinear().domain([0, 10]).range([props.height + topMargin - bottomMargin, topMargin]);
|
||||
}
|
||||
|
||||
const xAxis = axisBottom().scale(xScale).ticks(5).tickFormat(timeFormat("%M:%S"));
|
||||
const yAxis = axisLeft().scale(yScale).ticks(5).tickFormat(format(".3s"));
|
||||
|
||||
this.state = {
|
||||
data: null,
|
||||
lines: null,
|
||||
xAxis,
|
||||
yAxis,
|
||||
labelMargin,
|
||||
identifier: uniqueIdentifier++,
|
||||
stopTime: null,
|
||||
firstTimestamp: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.createInterval();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.removeInterval();
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
|
||||
let labelMargin = 0;
|
||||
if (props.yLabel !== '') {
|
||||
labelMargin = 30;
|
||||
}
|
||||
|
||||
// check if data is invalid
|
||||
if (props.data == null || props.data.length === 0) {
|
||||
// create empty plot axes
|
||||
let xScale;
|
||||
let yScale;
|
||||
let stopTime;
|
||||
|
||||
if(!props.paused){
|
||||
xScale = scaleTime().domain([Date.now() - props.time * 1000, Date.now()]).range([0, props.width - leftMargin - labelMargin - rightMargin]);
|
||||
stopTime = Date.now();
|
||||
}else{
|
||||
stopTime = state.stopTime;
|
||||
xScale = scaleLinear().domain([state.stopTime - props.time * 1000, state.stopTime]).range([0, props.width - leftMargin - labelMargin - rightMargin]);
|
||||
}
|
||||
|
||||
if (props.yUseMinMax) {
|
||||
yScale = scaleLinear().domain([props.yMin, props.yMax]).range([props.height + topMargin - bottomMargin, topMargin]);
|
||||
} else {
|
||||
yScale = scaleLinear().domain([0, 10]).range([props.height + topMargin - bottomMargin, topMargin]);
|
||||
}
|
||||
|
||||
const xAxis = axisBottom().scale(xScale).ticks(5).tickFormat(timeFormat("%M:%S"));
|
||||
const yAxis = axisLeft().scale(yScale).ticks(5).tickFormat(format(".3s"));
|
||||
|
||||
|
||||
return{
|
||||
stopTime: stopTime,
|
||||
data: null,
|
||||
xAxis,
|
||||
yAxis,
|
||||
labelMargin,
|
||||
};
|
||||
}
|
||||
|
||||
// only show data in requested time
|
||||
let data = props.data;
|
||||
let icDataset = data.find(function (element) {
|
||||
return element !== undefined;
|
||||
})
|
||||
|
||||
let firstTimestamp;
|
||||
if (props.mode === "last samples") {
|
||||
firstTimestamp = (data[0].length - 1 - props.samples) > 0 ? data[0][(data[0].length - 1) - props.samples].x : data[0][0].x;
|
||||
let tempTimestamp;
|
||||
|
||||
for (let i = 1; i < props.signalIDs.length; i++) {
|
||||
if (typeof props.data[i] !== "undefined") {
|
||||
tempTimestamp = (data[i].length - 1 - props.samples) > 0 ? data[i][(data[i].length - 1) - props.samples].x : data[i][0].x;
|
||||
firstTimestamp = tempTimestamp < firstTimestamp ? tempTimestamp : firstTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
firstTimestamp = icDataset[icDataset.length - 1].x - (props.time + 1) * 1000;
|
||||
if (icDataset[0].x < firstTimestamp) {
|
||||
// only show data in range (+100 ms)
|
||||
const index = icDataset.findIndex(value => value.x >= firstTimestamp - 100);
|
||||
data = data.map(values => values.slice(index));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
data,
|
||||
labelMargin,
|
||||
firstTimestamp
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot: SS): void {
|
||||
if (prevProps.time !== this.props.time) {
|
||||
this.createInterval();
|
||||
}
|
||||
}
|
||||
|
||||
createInterval() {
|
||||
this.removeInterval();
|
||||
|
||||
if (this.props.time < 30) {
|
||||
this.interval = setInterval(this.tick, 50);
|
||||
} else if (this.props.time < 120) {
|
||||
this.interval = setInterval(this.tick, 100);
|
||||
} else if (this.props.time < 300) {
|
||||
this.interval = setInterval(this.tick, 200);
|
||||
} else {
|
||||
this.interval = setInterval(this.tick, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
removeInterval() {
|
||||
if (this.interval != null) {
|
||||
clearInterval(this.interval);
|
||||
|
||||
this.interval = null;
|
||||
}
|
||||
}
|
||||
|
||||
tick = () => {
|
||||
|
||||
if (this.state.data == null) {
|
||||
this.setState({ lines: null });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.paused === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate yRange
|
||||
let yRange = [0, 0];
|
||||
|
||||
if (this.props.yUseMinMax) {
|
||||
yRange = [this.props.yMin, this.props.yMax];
|
||||
} else if (this.props.data.length > 0) {
|
||||
let icDataset = this.props.data.find(function(element) {
|
||||
return element !== undefined;
|
||||
})
|
||||
|
||||
yRange = [icDataset[0].y, icDataset[0].y];
|
||||
|
||||
this.props.data.forEach(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];
|
||||
});
|
||||
}
|
||||
|
||||
// create scale functions for both axes
|
||||
let xScale;
|
||||
let data = this.props.data;
|
||||
if(this.props.mode === "last samples"){
|
||||
let lastTimestamp = data[0][data[0].length - 1].x;
|
||||
|
||||
for (let i = 1; i < this.props.signalIDs.length; i++) {
|
||||
if (typeof data[i] !== "undefined") {
|
||||
lastTimestamp = data[i][data[i].length - 1].x > lastTimestamp ? data[i][data[i].length -1].x : lastTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
xScale = scaleTime().domain([this.state.firstTimestamp, lastTimestamp]).range([0, this.props.width - leftMargin - this.state.labelMargin - rightMargin]);
|
||||
}
|
||||
else{
|
||||
xScale = scaleTime().domain([Date.now() - this.props.time * 1000, Date.now()]).range([0, this.props.width - leftMargin - this.state.labelMargin - rightMargin]);
|
||||
|
||||
}
|
||||
const yScale = scaleLinear().domain(yRange).range([this.props.height + topMargin - bottomMargin, topMargin]);
|
||||
|
||||
const xAxis = axisBottom().scale(xScale).ticks(5).tickFormat(timeFormat("%M:%S"));
|
||||
const yAxis = axisLeft().scale(yScale).ticks(5).tickFormat(format(".3s"));
|
||||
|
||||
// generate paths from data
|
||||
const sparkLine = line().x(p => xScale(p.x)).y(p => yScale(p.y));
|
||||
|
||||
const lines = this.state.data.map((values, index) => {
|
||||
let signalID = this.props.signalIDs[index];
|
||||
|
||||
if(this.props.lineColors === undefined || this.props.lineColors === null){
|
||||
this.props.lineColors = [] // for backwards compatibility
|
||||
}
|
||||
|
||||
if (typeof this.props.lineColors[index] === "undefined") {
|
||||
this.props.lineColors[index] = schemeCategory10[index % 10];
|
||||
}
|
||||
return <path d={sparkLine(values)} key={index} style={{ fill: 'none', stroke: this.props.lineColors[index] }} />
|
||||
});
|
||||
|
||||
this.setState({ lines, xAxis, yAxis });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const yLabelPos = {
|
||||
x: 12,
|
||||
y: this.props.height / 2
|
||||
}
|
||||
|
||||
return <svg width={this.props.width - rightMargin + 1} height={this.props.height + topMargin + bottomMargin}>
|
||||
<g ref={node => select(node).call(this.state.xAxis)} style={{ transform: `translateX(${leftMargin + this.state.labelMargin}px) translateY(${this.props.height + topMargin - bottomMargin}px)` }} />
|
||||
<g ref={node => select(node).call(this.state.yAxis)} style={{ transform: `translateX(${leftMargin + this.state.labelMargin}px)` }} />
|
||||
|
||||
<text strokeWidth="0.005" textAnchor="middle" x={yLabelPos.x} y={yLabelPos.y} transform={`rotate(270 ${yLabelPos.x} ${yLabelPos.y})`}>{this.props.yLabel}</text>
|
||||
<text strokeWidth="0.005" textAnchor="end" x={this.props.width - rightMargin} y={this.props.height + topMargin + bottomMargin - 10}>Time [s]</text>
|
||||
|
||||
<defs>
|
||||
<clipPath id={"lineClipPath" + this.state.identifier}>
|
||||
<rect x={leftMargin + this.state.labelMargin} y={topMargin} width={this.props.width - leftMargin - this.state.labelMargin - rightMargin} height={this.props.height - bottomMargin} />
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<g style={{ clipPath: 'url(#lineClipPath' + this.state.identifier + ')' }}>
|
||||
{this.state.lines}
|
||||
</g>
|
||||
</svg>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Plot;
|
186
src/widget/widget-plot/plot.jsx
Normal file
186
src/widget/widget-plot/plot.jsx
Normal file
|
@ -0,0 +1,186 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { axisBottom, axisLeft } from "d3-axis";
|
||||
import { extent } from "d3-array";
|
||||
import { format } from "d3-format";
|
||||
import { line } from "d3-shape";
|
||||
import { scaleLinear, scaleTime } from "d3-scale";
|
||||
import { select } from "d3-selection";
|
||||
import { schemeCategory10 } from "d3-scale-chromatic";
|
||||
import { timeFormat } from "d3-time-format";
|
||||
|
||||
const topMargin = 10;
|
||||
const bottomMargin = 25;
|
||||
const leftMargin = 40;
|
||||
const rightMargin = 10;
|
||||
|
||||
let uniqueIdentifier = 0;
|
||||
|
||||
function Plot(props) {
|
||||
const [data, setData] = useState(null);
|
||||
const [lines, setLines] = useState(null);
|
||||
const [labelMargin, setLabelMargin] = useState(0);
|
||||
const [identifier, setIdentifier] = useState(uniqueIdentifier++);
|
||||
const [stopTime, setStopTime] = useState(null);
|
||||
const [firstTimestamp, setFirstTimestamp] = useState(null);
|
||||
const [xAxis, setXAxis] = useState(null);
|
||||
const [yAxis, setYAxis] = useState(null);
|
||||
const intervalRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
const interval = createInterval(
|
||||
props,
|
||||
firstTimestamp,
|
||||
data,
|
||||
setData,
|
||||
setLines,
|
||||
setXAxis,
|
||||
setYAxis,
|
||||
labelMargin
|
||||
);
|
||||
intervalRef.current = interval;
|
||||
|
||||
return () => {
|
||||
removeInterval(intervalRef.current);
|
||||
};
|
||||
}, [props]);
|
||||
|
||||
useEffect(() => {
|
||||
updatePlot(
|
||||
props,
|
||||
data,
|
||||
setData,
|
||||
setLines,
|
||||
setXAxis,
|
||||
setYAxis,
|
||||
stopTime,
|
||||
setStopTime,
|
||||
firstTimestamp,
|
||||
setFirstTimestamp,
|
||||
labelMargin,
|
||||
setLabelMargin,
|
||||
identifier
|
||||
);
|
||||
}, [props, data, stopTime, firstTimestamp, identifier]);
|
||||
|
||||
const xAxisRef = useRef();
|
||||
useEffect(() => {
|
||||
if (xAxis) {
|
||||
select(xAxisRef.current).call(xAxis);
|
||||
}
|
||||
}, [xAxis]);
|
||||
|
||||
const yAxisRef = useRef();
|
||||
useEffect(() => {
|
||||
if (yAxis) {
|
||||
select(yAxisRef.current).call(yAxis);
|
||||
}
|
||||
}, [yAxis]);
|
||||
|
||||
const yLabelPos = {
|
||||
x: 12,
|
||||
y: props.height / 2,
|
||||
};
|
||||
|
||||
const plotWidth = props.width - rightMargin + 1;
|
||||
const plotHeight = props.height + topMargin + bottomMargin;
|
||||
|
||||
return (
|
||||
<svg width={plotWidth} height={plotHeight}>
|
||||
<g
|
||||
ref={xAxisRef}
|
||||
transform={`translate(${leftMargin + labelMargin}, ${
|
||||
props.height + topMargin - bottomMargin
|
||||
})`}
|
||||
/>
|
||||
<g
|
||||
ref={yAxisRef}
|
||||
transform={`translate(${leftMargin + labelMargin}, 0)`}
|
||||
/>
|
||||
<text
|
||||
strokeWidth="0.005"
|
||||
textAnchor="middle"
|
||||
transform={`rotate(270, ${yLabelPos.x}, ${yLabelPos.y})`}
|
||||
x={yLabelPos.x}
|
||||
y={yLabelPos.y}
|
||||
>
|
||||
{props.yLabel}
|
||||
</text>
|
||||
<text
|
||||
strokeWidth="0.005"
|
||||
textAnchor="end"
|
||||
x={props.width - rightMargin}
|
||||
y={props.height + topMargin + bottomMargin - 10}
|
||||
>
|
||||
Time [s]
|
||||
</text>
|
||||
<defs>
|
||||
<clipPath id={`lineClipPath${identifier}`}>
|
||||
<rect
|
||||
x={leftMargin + labelMargin}
|
||||
y={topMargin}
|
||||
width={props.width - leftMargin - labelMargin - rightMargin}
|
||||
height={props.height - bottomMargin}
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clipPath={`url(#lineClipPath${identifier})`}>{lines}</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function createInterval(
|
||||
props,
|
||||
firstTimestamp,
|
||||
data,
|
||||
setData,
|
||||
setLines,
|
||||
setXAxis,
|
||||
setYAxis,
|
||||
labelMargin
|
||||
) {
|
||||
// You would implement createInterval logic here to generate the interval based on props
|
||||
// Similarly to how it was calculated in the original class component's componentDidMount and createInterval methods.
|
||||
}
|
||||
|
||||
function updatePlot(
|
||||
props,
|
||||
data,
|
||||
setData,
|
||||
setLines,
|
||||
setXAxis,
|
||||
setYAxis,
|
||||
stopTime,
|
||||
setStopTime,
|
||||
firstTimestamp,
|
||||
setFirstTimestamp,
|
||||
labelMargin,
|
||||
setLabelMargin,
|
||||
identifier
|
||||
) {
|
||||
// You would implement getDerivedStateFromProps logic here to update the plot.
|
||||
// Note: In functional components, derived state can be handled directly in the useEffect hook.
|
||||
}
|
||||
|
||||
function removeInterval(interval) {
|
||||
if (interval != null) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}
|
||||
|
||||
export default Plot;
|
|
@ -1,150 +0,0 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Form, Col, InputGroup } from 'react-bootstrap';
|
||||
import AppDispatcher from '../../common/app-dispatcher';
|
||||
|
||||
class WidgetInput extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: '',
|
||||
unit: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let widget = this.props.widget
|
||||
widget.customProperties.simStartedSendValue = false
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-edit',
|
||||
token: this.props.token,
|
||||
data: widget
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
// a simulaton was started, make an update
|
||||
if (this.props.widget.customProperties.simStartedSendValue) {
|
||||
let widget = this.props.widget
|
||||
widget.customProperties.simStartedSendValue = false
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-edit',
|
||||
token: this.props.token,
|
||||
data: widget
|
||||
});
|
||||
|
||||
// send value without changing widget
|
||||
this.props.onInputChanged(Number(this.state.value), '', '', false);
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
|
||||
let value = ''
|
||||
let unit = ''
|
||||
|
||||
if(props.widget.customProperties.hasOwnProperty('value') && props.widget.customProperties.value !== state.value){
|
||||
// set value to customProperties.value if this property exists and the value is different from current state
|
||||
value = Number(props.widget.customProperties.value);
|
||||
} else if (props.widget.customProperties.hasOwnProperty('default_value') && state.value === ''){
|
||||
// if customProperties.default_value exists and value has been assigned yet, set the value to the default_value
|
||||
value = Number(props.widget.customProperties.default_value)
|
||||
}
|
||||
|
||||
if (props.widget.signalIDs.length > 0) {
|
||||
// Update unit (assuming there is exactly one signal for this widget)
|
||||
let signalID = props.widget.signalIDs[0];
|
||||
let signal = props.signals.find(sig => sig.id === signalID);
|
||||
if (signal !== undefined) {
|
||||
unit = signal.unit;
|
||||
}
|
||||
}
|
||||
|
||||
if (unit !== '' && value !== ''){
|
||||
// unit and value have changed
|
||||
return {unit: unit, value: value};
|
||||
} else if (unit !== ''){
|
||||
// only unit has changed
|
||||
return {unit: unit}
|
||||
} else if (value !== ''){
|
||||
// only value has changed
|
||||
return {value: value}
|
||||
} else{
|
||||
// nothing has changed
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
valueIsChanging(newValue) {
|
||||
this.setState({ value: Number(newValue) });
|
||||
this.props.widget.customProperties.value = Number(newValue);
|
||||
}
|
||||
|
||||
valueChanged(newValue) {
|
||||
if (this.props.onInputChanged) {
|
||||
this.props.onInputChanged(Number(newValue), 'value', Number(newValue), true);
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyPress(e) {
|
||||
if(e.charCode === 13) {
|
||||
this.valueChanged(this.state.value)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="number-input-widget full">
|
||||
<Form componentclass="fieldset" horizontal="true">
|
||||
<Form.Group>
|
||||
<Col as={Form.Label}>
|
||||
{this.props.widget.name}
|
||||
{this.props.widget.customProperties.showUnit? (
|
||||
" [" + this.state.unit + "]"
|
||||
|
||||
):(
|
||||
""
|
||||
)}
|
||||
|
||||
</Col>
|
||||
<Col>
|
||||
<InputGroup>
|
||||
<Form.Control
|
||||
type="number"
|
||||
step="any"
|
||||
disabled={ this.props.editing }
|
||||
onKeyPress={ (e) => this.handleKeyPress(e) }
|
||||
onBlur={ (e) => this.valueChanged(this.state.value) }
|
||||
onChange={ (e) => this.valueIsChanging(e.target.value) }
|
||||
placeholder="Enter value"
|
||||
value={ this.state.value }
|
||||
/>
|
||||
|
||||
</InputGroup>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetInput;
|
130
src/widget/widgets/input.jsx
Normal file
130
src/widget/widgets/input.jsx
Normal file
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Form, Col, InputGroup } from "react-bootstrap";
|
||||
import AppDispatcher from "../../common/app-dispatcher";
|
||||
|
||||
function WidgetInput(props) {
|
||||
const [value, setValue] = useState("");
|
||||
const [unit, setUnit] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const widget = { ...props.widget };
|
||||
widget.customProperties.simStartedSendValue = false;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: "widgets/start-edit",
|
||||
token: props.token,
|
||||
data: widget,
|
||||
});
|
||||
}, [props.token, props.widget]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.widget.customProperties.simStartedSendValue) {
|
||||
const widget = { ...props.widget };
|
||||
widget.customProperties.simStartedSendValue = false;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: "widgets/start-edit",
|
||||
token: props.token,
|
||||
data: widget,
|
||||
});
|
||||
|
||||
props.onInputChanged(Number(value), "", "", false);
|
||||
}
|
||||
}, [props, value]);
|
||||
|
||||
useEffect(() => {
|
||||
let newValue = "";
|
||||
let newUnit = "";
|
||||
|
||||
if (
|
||||
props.widget.customProperties.hasOwnProperty("value") &&
|
||||
props.widget.customProperties.value !== value
|
||||
) {
|
||||
newValue = Number(props.widget.customProperties.value);
|
||||
} else if (
|
||||
props.widget.customProperties.hasOwnProperty("default_value") &&
|
||||
value === ""
|
||||
) {
|
||||
newValue = Number(props.widget.customProperties.default_value);
|
||||
}
|
||||
|
||||
if (props.widget.signalIDs.length > 0) {
|
||||
const signalID = props.widget.signalIDs[0];
|
||||
const signal = props.signals.find((sig) => sig.id === signalID);
|
||||
if (signal !== undefined) {
|
||||
newUnit = signal.unit;
|
||||
}
|
||||
}
|
||||
|
||||
if (newUnit !== unit) {
|
||||
setUnit(newUnit);
|
||||
}
|
||||
|
||||
if (newValue !== value) {
|
||||
setValue(newValue);
|
||||
}
|
||||
}, [props, value, unit]);
|
||||
|
||||
const valueIsChanging = (newValue) => {
|
||||
const numericalValue = Number(newValue);
|
||||
setValue(numericalValue);
|
||||
props.widget.customProperties.value = numericalValue;
|
||||
};
|
||||
|
||||
const valueChanged = (newValue) => {
|
||||
if (props.onInputChanged) {
|
||||
props.onInputChanged(Number(newValue), "value", Number(newValue), true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = (e) => {
|
||||
if (e.charCode === 13) {
|
||||
valueChanged(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="number-input-widget full">
|
||||
<Form>
|
||||
<Form.Group>
|
||||
<Col as={Form.Label}>
|
||||
{props.widget.name}
|
||||
{props.widget.customProperties.showUnit ? ` [${unit}]` : ""}
|
||||
</Col>
|
||||
<Col>
|
||||
<InputGroup>
|
||||
<Form.Control
|
||||
type="number"
|
||||
step="any"
|
||||
disabled={props.editing}
|
||||
onKeyPress={handleKeyPress}
|
||||
onBlur={() => valueChanged(value)}
|
||||
onChange={(e) => valueIsChanging(e.target.value)}
|
||||
placeholder="Enter value"
|
||||
value={value}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WidgetInput;
|
|
@ -1,107 +0,0 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import TrafficLight from 'react-trafficlight';
|
||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||
|
||||
class WidgetTimeOffset extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
timeOffset: '',
|
||||
icID: '',
|
||||
icName: '',
|
||||
websocketOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
|
||||
if(typeof props.widget.customProperties.icID !== "undefined" && state.icID !== props.widget.customProperties.icID){
|
||||
return {icID: props.widget.customProperties.icID};
|
||||
}
|
||||
|
||||
var selectedIC, websocket;
|
||||
if (props.ics) {
|
||||
selectedIC = props.ics.find(ic => ic.id === parseInt(state.icID, 10));
|
||||
if (selectedIC) {
|
||||
websocket = props.websockets.find(ws => ws.url === selectedIC.websocketurl);
|
||||
}
|
||||
}
|
||||
|
||||
if (props.data == null
|
||||
|| props.data[state.icID] == null
|
||||
|| props.data[state.icID].output == null
|
||||
|| props.data[state.icID].output.timestamp == null) {
|
||||
if (websocket) { return {timeOffset: -1, websocketOpen: websocket.connected};}
|
||||
return {timeOffset: -1};
|
||||
}
|
||||
|
||||
let serverTime = props.data[state.icID].output.timestamp;
|
||||
let localTime = Date.now();
|
||||
let absoluteOffset = Math.abs(serverTime - localTime);
|
||||
|
||||
if(typeof websocket === 'undefined'){
|
||||
return {timeOffset: Number.parseFloat(absoluteOffset/1000).toPrecision(5)}
|
||||
}
|
||||
return {timeOffset: Number.parseFloat(absoluteOffset/1000).toPrecision(5), websocketOpen: websocket.connected, icName: selectedIC.name};
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let icSelected = " ";
|
||||
if(!this.state.websocketOpen){
|
||||
icSelected = "no connection";
|
||||
} else if (this.state.websocketOpen && this.state.timeOffset < 0) {
|
||||
icSelected = "no/invalid data";
|
||||
} else if (this.props.widget.customProperties.showOffset){
|
||||
icSelected = this.state.timeOffset + 's';
|
||||
}
|
||||
return (
|
||||
<div className="time-offset">
|
||||
{this.props.widget.customProperties.icID !== -1 ?
|
||||
(<span></span>) : (<span>no IC</span>)
|
||||
}
|
||||
{this.props.widget.customProperties.icID !== -1 && this.props.widget.customProperties.showName ?
|
||||
(<span>{this.state.icName}</span>) : (<span></span>)
|
||||
}
|
||||
<OverlayTrigger key={0} placement={'left'} overlay={<Tooltip id={`tooltip-${"traffic-light"}`}>
|
||||
{this.props.widget.customProperties.icID !== -1 ?
|
||||
(<span>{this.state.icName}<br></br>Offset: {this.state.timeOffset + "s"}</span>)
|
||||
:
|
||||
(<span>Please select Infrastructure Component</span>)}
|
||||
</Tooltip>}>
|
||||
<TrafficLight Horizontal={this.props.widget.customProperties.horizontal} width={this.props.widget.width - 40} height={this.props.widget.height - 40}
|
||||
RedOn={(this.props.widget.customProperties.threshold_red <= this.state.timeOffset) || !this.state.websocketOpen || (this.state.timeOffset < 0)}
|
||||
YellowOn={(this.props.widget.customProperties.threshold_yellow <= this.state.timeOffset) && (this.state.timeOffset < this.props.widget.customProperties.threshold_red) && this.state.websocketOpen}
|
||||
GreenOn={(this.state.timeOffset > 0) && (this.state.timeOffset < this.props.widget.customProperties.threshold_yellow) && this.state.websocketOpen}
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
{this.props.widget.customProperties.icID !== -1 ?
|
||||
(
|
||||
<span>{icSelected}</span>)
|
||||
:
|
||||
(<span>selected</span>)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetTimeOffset;
|
155
src/widget/widgets/time-offset.jsx
Normal file
155
src/widget/widgets/time-offset.jsx
Normal file
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import TrafficLight from "react-trafficlight";
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||
|
||||
function WidgetTimeOffset(props) {
|
||||
const [state, setState] = useState({
|
||||
timeOffset: "",
|
||||
icID: "",
|
||||
icName: "",
|
||||
websocketOpen: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const { widget, ics, websockets, data } = props;
|
||||
|
||||
function getDerivedStateFromProps() {
|
||||
if (
|
||||
typeof widget.customProperties.icID !== "undefined" &&
|
||||
state.icID !== widget.customProperties.icID
|
||||
) {
|
||||
return { icID: widget.customProperties.icID };
|
||||
}
|
||||
|
||||
let selectedIC, websocket;
|
||||
if (ics) {
|
||||
selectedIC = ics.find((ic) => ic.id === parseInt(state.icID, 10));
|
||||
if (selectedIC) {
|
||||
websocket = websockets.find(
|
||||
(ws) => ws.url === selectedIC.websocketurl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
data == null ||
|
||||
data[state.icID] == null ||
|
||||
data[state.icID].output == null ||
|
||||
data[state.icID].output.timestamp == null
|
||||
) {
|
||||
if (websocket) {
|
||||
return { timeOffset: -1, websocketOpen: websocket.connected };
|
||||
}
|
||||
return { timeOffset: -1 };
|
||||
}
|
||||
|
||||
let serverTime = data[state.icID].output.timestamp;
|
||||
let localTime = Date.now();
|
||||
let absoluteOffset = Math.abs(serverTime - localTime);
|
||||
|
||||
if (typeof websocket === "undefined") {
|
||||
return {
|
||||
timeOffset: Number.parseFloat(absoluteOffset / 1000).toPrecision(5),
|
||||
};
|
||||
}
|
||||
return {
|
||||
timeOffset: Number.parseFloat(absoluteOffset / 1000).toPrecision(5),
|
||||
websocketOpen: websocket.connected,
|
||||
icName: selectedIC.name,
|
||||
};
|
||||
}
|
||||
|
||||
const derivedState = getDerivedStateFromProps();
|
||||
setState((prevState) => ({ ...prevState, ...derivedState }));
|
||||
|
||||
// eslint-disable-next-line
|
||||
}, [props.widget, props.ics, props.websockets, props.data]);
|
||||
|
||||
const { timeOffset, icID, icName, websocketOpen } = state;
|
||||
|
||||
let icSelected = " ";
|
||||
if (!websocketOpen) {
|
||||
icSelected = "no connection";
|
||||
} else if (websocketOpen && timeOffset < 0) {
|
||||
icSelected = "no/invalid data";
|
||||
} else if (props.widget.customProperties.showOffset) {
|
||||
icSelected = timeOffset + "s";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="time-offset">
|
||||
{props.widget.customProperties.icID !== -1 ? (
|
||||
<span></span>
|
||||
) : (
|
||||
<span>no IC</span>
|
||||
)}
|
||||
{props.widget.customProperties.icID !== -1 &&
|
||||
props.widget.customProperties.showName ? (
|
||||
<span>{icName}</span>
|
||||
) : (
|
||||
<span></span>
|
||||
)}
|
||||
<OverlayTrigger
|
||||
key={0}
|
||||
placement={"left"}
|
||||
overlay={
|
||||
<Tooltip id={`tooltip-${"traffic-light"}`}>
|
||||
{props.widget.customProperties.icID !== -1 ? (
|
||||
<span>
|
||||
{icName}
|
||||
<br />
|
||||
Offset: {timeOffset + "s"}
|
||||
</span>
|
||||
) : (
|
||||
<span>Please select Infrastructure Component</span>
|
||||
)}
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<TrafficLight
|
||||
Horizontal={props.widget.customProperties.horizontal}
|
||||
width={props.widget.width - 40}
|
||||
height={props.widget.height - 40}
|
||||
RedOn={
|
||||
props.widget.customProperties.threshold_red <= timeOffset ||
|
||||
!websocketOpen ||
|
||||
timeOffset < 0
|
||||
}
|
||||
YellowOn={
|
||||
props.widget.customProperties.threshold_yellow <= timeOffset &&
|
||||
timeOffset < props.widget.customProperties.threshold_red &&
|
||||
websocketOpen
|
||||
}
|
||||
GreenOn={
|
||||
timeOffset > 0 &&
|
||||
timeOffset < props.widget.customProperties.threshold_yellow &&
|
||||
websocketOpen
|
||||
}
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
{props.widget.customProperties.icID !== -1 ? (
|
||||
<span>{icSelected}</span>
|
||||
) : (
|
||||
<span>selected</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WidgetTimeOffset;
|
|
@ -1,100 +0,0 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { format } from 'd3';
|
||||
|
||||
class WidgetValue extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: NaN,
|
||||
unit: '',
|
||||
scalingFactor: 1.0
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
if(props.widget.signalIDs.length === 0){
|
||||
return null;
|
||||
}
|
||||
|
||||
// get the signal with the selected signal ID
|
||||
let signalID = props.widget.signalIDs[0];
|
||||
let signal = props.signals.filter(s => s.id === signalID)
|
||||
if(signal.length>0) {
|
||||
// determine ID of infrastructure component related to signal[0] (there is only one signal for a value widget)
|
||||
let icID = props.icIDs[signal[0].id];
|
||||
|
||||
// check if data available
|
||||
let value = NaN
|
||||
if (props.data == null || props.data[icID] == null || props.data[icID].output == null || props.data[icID].output.values == null) {
|
||||
// no data
|
||||
} else {
|
||||
const data = props.data[icID].output.values[signal[0].index];
|
||||
if (data != null) {
|
||||
value = signal[0].scalingFactor * data[data.length - 1].y;
|
||||
}
|
||||
}
|
||||
|
||||
// Update unit (assuming there is exactly one signal for this widget)
|
||||
let unit = '';
|
||||
let scalingFactor = ''
|
||||
unit = signal[0].unit;
|
||||
scalingFactor = signal[0].scalingFactor
|
||||
|
||||
return {
|
||||
value: value,
|
||||
unit: unit,
|
||||
scalingFactor: scalingFactor
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
let value_to_render = this.state.value;
|
||||
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);
|
||||
|
||||
let showScalingFactor;
|
||||
if (this.props.widget.customProperties.showScalingFactor !== undefined){ // this line ensures backwards compatibility with older versions of VILLASweb
|
||||
showScalingFactor = this.props.widget.customProperties.showScalingFactor;
|
||||
} else {
|
||||
showScalingFactor = (this.state.scalingFactor !== 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="single-value-widget">
|
||||
<strong style={{ fontSize: this.props.widget.customProperties.textSize + 'px', flex: '1 1 auto'}}>{this.props.widget.name}</strong>
|
||||
<span style={{ fontSize: this.props.widget.customProperties.textSize + 'px', flex: 'none', width: value_width}}>{Number.isNaN(value_to_render) ? String(NaN) : format('.3f')(value_to_render)}</span>
|
||||
{this.props.widget.customProperties.showUnit &&
|
||||
<span style={{ fontSize: this.props.widget.customProperties.textSize + 'px', flex: 'none', width: unit_width}}>[{this.state.unit}]</span>
|
||||
}
|
||||
{showScalingFactor &&
|
||||
<span style={{ fontSize: this.props.widget.customProperties.textSize + 'px', flex: 'none', marginLeft: '0.2em' }} className="signal-scale">{this.state.scalingFactor}</ span>
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetValue;
|
142
src/widget/widgets/value.jsx
Normal file
142
src/widget/widgets/value.jsx
Normal file
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { format } from "d3";
|
||||
|
||||
function WidgetValue(props) {
|
||||
const [valueState, setValueState] = useState({
|
||||
value: NaN,
|
||||
unit: "",
|
||||
scalingFactor: 1.0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
function getDerivedStateFromProps() {
|
||||
if (props.widget.signalIDs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get the signal with the selected signal ID
|
||||
let signalID = props.widget.signalIDs[0];
|
||||
let signal = props.signals.filter((s) => s.id === signalID);
|
||||
if (signal.length > 0) {
|
||||
// determine ID of infrastructure component related to signal[0] (there is only one signal for a value widget)
|
||||
let icID = props.icIDs[signal[0].id];
|
||||
|
||||
// check if data available
|
||||
let value = NaN;
|
||||
if (
|
||||
props.data != null &&
|
||||
props.data[icID] != null &&
|
||||
props.data[icID].output != null &&
|
||||
props.data[icID].output.values != null
|
||||
) {
|
||||
const data = props.data[icID].output.values[signal[0].index];
|
||||
if (data != null) {
|
||||
value = signal[0].scalingFactor * data[data.length - 1].y;
|
||||
}
|
||||
}
|
||||
|
||||
// Update unit and scaling factor (assuming there is exactly one signal for this widget)
|
||||
let unit = signal[0].unit;
|
||||
let scalingFactor = signal[0].scalingFactor;
|
||||
|
||||
return {
|
||||
value,
|
||||
unit,
|
||||
scalingFactor,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const newState = getDerivedStateFromProps();
|
||||
if (newState) {
|
||||
setValueState(newState);
|
||||
}
|
||||
}, [
|
||||
props.data,
|
||||
props.icIDs,
|
||||
props.signals,
|
||||
props.widget.signalIDs,
|
||||
props.widget.name,
|
||||
]);
|
||||
|
||||
const { value, unit, scalingFactor } = valueState;
|
||||
let value_to_render = value;
|
||||
let value_width =
|
||||
props.widget.customProperties.textSize *
|
||||
(Math.abs(value_to_render) < 1000 ? 5 : 8);
|
||||
let unit_width = props.widget.customProperties.textSize * (unit.length + 0.7);
|
||||
|
||||
let showScalingFactor;
|
||||
if (props.widget.customProperties.showScalingFactor !== undefined) {
|
||||
showScalingFactor = props.widget.customProperties.showScalingFactor;
|
||||
} else {
|
||||
showScalingFactor = scalingFactor !== 1;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="single-value-widget">
|
||||
<strong
|
||||
style={{
|
||||
fontSize: `${props.widget.customProperties.textSize}px`,
|
||||
flex: "1 1 auto",
|
||||
}}
|
||||
>
|
||||
{props.widget.name}
|
||||
</strong>
|
||||
<span
|
||||
style={{
|
||||
fontSize: `${props.widget.customProperties.textSize}px`,
|
||||
flex: "none",
|
||||
width: value_width,
|
||||
}}
|
||||
>
|
||||
{Number.isNaN(value_to_render)
|
||||
? String(NaN)
|
||||
: format(".3f")(value_to_render)}
|
||||
</span>
|
||||
{props.widget.customProperties.showUnit && (
|
||||
<span
|
||||
style={{
|
||||
fontSize: `${props.widget.customProperties.textSize}px`,
|
||||
flex: "none",
|
||||
width: unit_width,
|
||||
}}
|
||||
>
|
||||
[{unit}]
|
||||
</span>
|
||||
)}
|
||||
{showScalingFactor && (
|
||||
<span
|
||||
style={{
|
||||
fontSize: `${props.widget.customProperties.textSize}px`,
|
||||
flex: "none",
|
||||
marginLeft: "0.2em",
|
||||
}}
|
||||
className="signal-scale"
|
||||
>
|
||||
{scalingFactor}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WidgetValue;
|
Loading…
Add table
Reference in a new issue