294 lines
7.2 KiB
JavaScript
294 lines
7.2 KiB
JavaScript
import React, { Component } from "react";
|
|
import _ from "lodash";
|
|
import { connect } from "react-redux";
|
|
import { FlexibleWidthXYPlot, LineSeries, VerticalGridLines, HorizontalGridLines, XAxis, YAxis, DiscreteColorLegend } from "react-vis";
|
|
// import "../../../node_modules/react-vis/dist/style.css";
|
|
|
|
import { color } from "d3-color";
|
|
import { interpolateRgb } from "d3-interpolate";
|
|
import LiquidFillGauge from "react-liquid-gauge";
|
|
import { RadialGauge } from "react-canvas-gauges";
|
|
|
|
/** Class for Main Page
|
|
*
|
|
* @extends React.Component
|
|
*/
|
|
class Main extends Component {
|
|
|
|
/**
|
|
* Map an array of objects to a graph-usable array of objects
|
|
*
|
|
* @param {Array} values - list of objects with timestame and value properties
|
|
*
|
|
* @returns {Array} list of objects with x and y properties
|
|
*/
|
|
mapTimestampAndValuePropToXY(values){
|
|
return _.map(values, (val) => {
|
|
return {x: val.timestamp, y: val.value};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Renders a Liquid Gauge.
|
|
*
|
|
* @param {object} tag - the tag structure
|
|
* @param {string} units - units for the tag
|
|
* @param {number} maxValue - maximum value to be displayed
|
|
* @param {number} label - label for the value (typically the tag name)
|
|
* @returns {ReactElement} liquidGauge
|
|
*/
|
|
renderLiquidGauge(tag, units, maxValue, label){
|
|
if (!tag){
|
|
return <span></span>;
|
|
}
|
|
const tagValue = tag.value;
|
|
const endColor = "#1F4788";
|
|
const startColor = "#dc143c";
|
|
|
|
const radius = 100;
|
|
const interpolate = interpolateRgb(startColor, endColor);
|
|
const fillColor = interpolate(tagValue / maxValue);
|
|
const gradientStops = [
|
|
{
|
|
key: "0%",
|
|
stopColor: color(fillColor).darker(0.5).toString(),
|
|
stopOpacity: 1,
|
|
offset: "0%"
|
|
},
|
|
{
|
|
key: "50%",
|
|
stopColor: fillColor,
|
|
stopOpacity: 0.75,
|
|
offset: "50%"
|
|
},
|
|
{
|
|
key: "100%",
|
|
stopColor: color(fillColor).brighter(0.5).toString(),
|
|
stopOpacity: 0.5,
|
|
offset: "100%"
|
|
}
|
|
];
|
|
|
|
return (
|
|
<div className="col" style={{textAlign: "center"}}>
|
|
<h3>{label}</h3>
|
|
<LiquidFillGauge
|
|
style={{ margin: "0 auto" }}
|
|
width={radius * 2}
|
|
height={radius * 2}
|
|
value={tagValue / maxValue * 100}
|
|
percent={units}
|
|
textSize={1}
|
|
textOffsetX={0}
|
|
textOffsetY={0}
|
|
textRenderer={(props) => {
|
|
const value = Math.round(tagValue);
|
|
const radius = Math.min(props.height / 2, props.width / 2);
|
|
const textPixels = (props.textSize * radius / 2);
|
|
const valueStyle = {
|
|
fontSize: textPixels
|
|
};
|
|
const percentStyle = {
|
|
fontSize: textPixels * 0.6
|
|
};
|
|
|
|
return (
|
|
<tspan>
|
|
<tspan className="value" style={valueStyle}>{value}</tspan>
|
|
<tspan style={percentStyle}>{props.percent}</tspan>
|
|
</tspan>
|
|
);
|
|
}}
|
|
riseAnimation
|
|
waveAnimation
|
|
waveFrequency={3}
|
|
waveAmplitude={2}
|
|
gradient
|
|
gradientStops={gradientStops}
|
|
circleStyle={{
|
|
fill: fillColor
|
|
}}
|
|
waveStyle={{
|
|
fill: fillColor
|
|
}}
|
|
textStyle={{
|
|
fill: color("#444").toString(),
|
|
fontFamily: "Arial"
|
|
}}
|
|
waveTextStyle={{
|
|
fill: color("#fff").toString(),
|
|
fontFamily: "Arial"
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Renders a radial gauge.
|
|
*
|
|
* @param {Object} tag - the tag structure
|
|
* @param {string} units - units for the tag
|
|
* @param {number} maxValue - maximum value to be displayed
|
|
* @param {number} label - label for the value (typically the tag name)
|
|
*/
|
|
renderRadialGauge(tag, units, maxValue, label){
|
|
if (!tag){
|
|
return <span></span>;
|
|
}
|
|
|
|
const ticks = [ 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 ];
|
|
|
|
return (
|
|
<div className="col" style={{textAlign: "center"}}>
|
|
<h3>{label}</h3>
|
|
<RadialGauge
|
|
height={200}
|
|
width={200}
|
|
units={units}
|
|
title={label}
|
|
value={tag.value}
|
|
minValue={0}
|
|
maxValue={maxValue}
|
|
majorTicks={ticks.map((t) => t * maxValue)}
|
|
minorTicks={2}
|
|
></RadialGauge>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* render
|
|
*
|
|
* @returns {ReactElement} markup
|
|
*/
|
|
render(){
|
|
if (!this.props.tagHistory){
|
|
return(
|
|
<div className="container">
|
|
<h1>Loading...</h1>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (this.props.plc.error){
|
|
return(
|
|
<div className="container plc-error">
|
|
<h1>PLC Error</h1>
|
|
<h3>{this.props.plc.error}</h3>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (Object.keys(this.props.tags).length === 0){
|
|
return(
|
|
<div className="container">
|
|
<h1>Waiting for data...</h1>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
|
|
return (
|
|
<div className="container">
|
|
<h3>Process Values</h3>
|
|
<div className="row" style={{marginBottom: "20px"}}>
|
|
{this.renderLiquidGauge(this.props.tags.val_FluidLevel, "ft.", 500, "Level")}
|
|
{this.renderRadialGauge(this.props.tags.val_Flowmeter_BarrelsPerDay, "BPD", 5000, "Flow Rate")}
|
|
{this.renderRadialGauge(this.props.tags.val_TubingPressure, "PSI", 400, "Tubing Pressure")}
|
|
</div>
|
|
|
|
<FlexibleWidthXYPlot
|
|
height={300}
|
|
xType="time"
|
|
>
|
|
<VerticalGridLines />
|
|
<HorizontalGridLines />
|
|
<XAxis />
|
|
<YAxis />
|
|
<LineSeries
|
|
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_IntakePressure)}
|
|
/>
|
|
<LineSeries
|
|
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_Flowmeter)}
|
|
/>
|
|
<LineSeries
|
|
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_FluidLevel)}
|
|
/>
|
|
<LineSeries
|
|
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_IntakeTemperature)}
|
|
/>
|
|
<LineSeries
|
|
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.val_TubingPressure)}
|
|
/>
|
|
</FlexibleWidthXYPlot>
|
|
<DiscreteColorLegend
|
|
items={[
|
|
{title: "Intake Pressure"},
|
|
{title: "Flowmeter"},
|
|
{title: "Fluid Level"},
|
|
{title: "Intake Temp"},
|
|
{title: "Tubing Pressure"}
|
|
]}
|
|
orientation="horizontal"
|
|
/>
|
|
<hr />
|
|
|
|
<h3>VFD Data</h3>
|
|
<div className="row" style={{marginBottom: "20px"}}>
|
|
{this.renderRadialGauge(this.props.tags.VFD_OutCurrent, "A.", 100, "Current")}
|
|
{this.renderRadialGauge(this.props.tags.VFD_SpeedFdbk, "Hz", 60, "Frequency")}
|
|
</div>
|
|
<FlexibleWidthXYPlot
|
|
height={300}
|
|
xType="time"
|
|
>
|
|
<VerticalGridLines />
|
|
<HorizontalGridLines />
|
|
<XAxis />
|
|
<YAxis />
|
|
<LineSeries
|
|
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.VFD_OutCurrent)}
|
|
/>
|
|
<LineSeries
|
|
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.VFD_SpeedFdbk)}
|
|
/>
|
|
<LineSeries
|
|
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.VFD_OutPower)}
|
|
/>
|
|
<LineSeries
|
|
data={this.mapTimestampAndValuePropToXY(this.props.tagHistory.VFD_Temp)}
|
|
/>
|
|
</FlexibleWidthXYPlot>
|
|
<DiscreteColorLegend
|
|
items={[
|
|
{title: "VFD Current"},
|
|
{title: "VFD Speed Feedback"},
|
|
{title: "VFD Output Power"},
|
|
{title: "VFD Temp"}
|
|
]}
|
|
orientation="horizontal"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Map Redux state to React props
|
|
*
|
|
* @param {Object} state
|
|
*
|
|
* @returns {Object} mapped state
|
|
*/
|
|
function mapStateToProps(state){
|
|
return {
|
|
tags: state.tags,
|
|
tagHistory: state.tagHistory,
|
|
plc: state.plc
|
|
};
|
|
}
|
|
|
|
export default connect(mapStateToProps)(Main); |