Commit cf66b908 authored by 李纪文's avatar 李纪文

feat: 组态分组增加消息通信

parent 82c96828
......@@ -4,6 +4,15 @@ import { Button } from 'antd';
const Demos = () => {
const [open, setOpen] = useState(false);
const [sensors, setSensors] = useState('出水瞬时流量');
const onOk = () => {
setOpen(false);
};
const onCancel = () => {
setOpen(false);
};
return (
<>
......@@ -14,7 +23,21 @@ const Demos = () => {
>
打开限制曲线
</Button>
<LimitCurve open={open} />
<Button
onClick={() => {
setSensors('噪声');
}}
>
改变sensors
</Button>
<LimitCurve
open={open}
onOk={onOk}
onCancel={onCancel}
deviceCode={'EGBF00000120'}
deviceType={'二供泵房'}
sensors={sensors}
/>
</>
);
};
......
import React, { useContext, useEffect, useRef, useState } from 'react';
import { ConfigProvider, Modal, Radio, Slider, InputNumber } from 'antd';
import { ConfigProvider, Modal, Radio, Slider, InputNumber, Input, Button } from 'antd';
import classNames from 'classnames';
import moment from 'moment';
import { BasicChart } from '@wisdom-components/basicchart';
import { getHistoryInfo } from '../apis';
import { std } from 'mathjs';
import skmeans from 'skmeans';
import LoadBox from '@wisdom-components/loadbox';
import { outlierArr, timeArr, chartArr, average } from './utils';
import './index.less';
const LimitCurve = (props) => {
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls('limit-curve');
const { width, deviceCode, sensors, deviceType, getContainer, title } = props;
const [spinning,setSpinning] = useState(true);
const [open, setOpen] = useState(false);
const [cluster, setCluster] = useState('K-means'); // 聚集算法
const [timeType, setTimeType] = useState('近7天'); // 时间
const [outlier, setOutlier] = useState(3); // 过滤异常
const [curve, setCurve] = useState('特征曲线'); // 曲线类型
const [sensitive, setSensitive] = useState(10); // 敏感度
......@@ -32,22 +35,23 @@ const LimitCurve = (props) => {
// 取消
const onCancel = () => {
setOpen(false);
props.onCancel && props.onCancel();
};
// 获取历史数据
const getSensorsData = async () => {
setSpinning(true);
const params = {
isDilute: true,
zoom: '',
unit: '',
dateFrom: moment().subtract(8, 'day').format('YYYY-MM-DD 00:00:00'),
dateTo: moment().subtract(1, 'day').format('YYYY-MM-DD 23:59:59'),
acrossTables: [
{ deviceCode: 'EGBF00000120', sensors: '出水瞬时流量', deviceType: '二供泵房' },
],
acrossTables: [{ deviceCode: deviceCode, sensors: sensors, deviceType: deviceType }],
isBoxPlots: true,
};
const results = await getHistoryInfo(params);
setSpinning(false);
const historyData = results?.data?.[0] || {};
setSensorData(() => {
......@@ -80,6 +84,7 @@ const LimitCurve = (props) => {
const _centroids = centroids.sort((a, b) => {
return a[0] - b[0];
});
console.log(_centroids);
const option = {
xAxis: {
type: 'time',
......@@ -108,17 +113,23 @@ const LimitCurve = (props) => {
},
series: [
{
type: 'scatter',
name: sensors,
sampling: 'average',
large: true,
symbolSize: 5,
data: _chartData.map((item) => {
return [new Date(item.time).getTime(), item.pv];
}),
type: 'scatter',
},
{
data: centroids.map((item) => {
type: 'line',
name: sensors,
sampling: 'average',
large: true,
data: _centroids.map((item) => {
return [Math.floor(item[0]), item[1]];
}),
type: 'line',
},
],
};
......@@ -126,8 +137,9 @@ const LimitCurve = (props) => {
};
useEffect(() => {
getSensorsData();
}, []);
open && getSensorsData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
useEffect(() => {
const { dataModel = [] } = sensorData;
......@@ -155,7 +167,6 @@ const LimitCurve = (props) => {
const dataArr = [];
const pvArr = data.map((item) => {
return item.pv;
// clusteredArr.push([new Date(item.time).getTime(),item.pv])
});
const stdVal = pvArr.length ? std(pvArr) : 0;
const medianVal = pvArr.length ? average(pvArr) : 0;
......@@ -173,68 +184,12 @@ const LimitCurve = (props) => {
_clustered.push(...centroids);
}
renderChart(_chartData, _clustered);
}, [sensorData, cluster, outlier]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sensorData, timeType, outlier]);
useEffect(() => {
console.log(chartData);
const _chartData = chartDataHandle(chartData);
const clusteredData = _chartData.map((item) => {
return [new Date(item.time).getTime(), item.pv];
});
const clustered = clusteredData.length ? skmeans(clusteredData, 24) : {};
console.log(clustered);
const { centroids = [] } = clustered;
const _centroids = centroids.sort((a, b) => {
return a[0] - b[0];
});
const option = {
xAxis: {
type: 'time',
// axisTick: {
// alignWithLabel: true,
// },
// boundaryGap: false,
// splitLine: {
// show: true,
// lineStyle: {
// type: 'dashed',
// },
// },
},
yAxis: {
type: 'value',
name: 'm',
position: 'left',
alignTicks: true,
axisLine: {
show: true,
},
axisLabel: {
formatter: '{value}',
},
},
series: [
{
symbolSize: 5,
data: _chartData.map((item) => {
return [new Date(item.time).getTime(), item.pv];
}),
type: 'scatter',
},
{
data: _centroids.map((item) => {
return [new Date(Math.floor(item[0])).getTime(), item[1]];
}),
type: 'line',
},
],
};
setOptions(option);
}, [chartData]);
useEffect(()=>{
setOpen(props.open);
},[props.open])
}, [props.open]);
return (
<>
......@@ -249,18 +204,30 @@ const LimitCurve = (props) => {
onCancel={onCancel}
wrapClassName={classNames(`${prefixCls}`)}
getContainer={getContainer || document.body}
destroyOnClose={true}
>
<div className={classNames(`${prefixCls}-box`)}>
<div className={classNames(`${prefixCls}-header`)}>
<div className={classNames(`${prefixCls}-header-list`)}>
<span className={classNames(`${prefixCls}-header-item`)}>
聚类算法:
曲线选择:
<Radio.Group
options={chartArr}
optionType={'button'}
value={curve}
onChange={(e) => {
setCurve(e.target.value);
}}
/>
</span>
<span className={classNames(`${prefixCls}-header-item`)}>
取值方式:
<Radio.Group
options={clusterArr}
options={timeArr}
optionType={'button'}
value={cluster}
value={timeType}
onChange={(e) => {
setCluster(e.target.value);
setTimeType(e.target.value);
}}
/>
</span>
......@@ -276,7 +243,7 @@ const LimitCurve = (props) => {
/>
</span>
<span className={classNames(`${prefixCls}-header-item`)}>
敏感度
浮动范围
<Slider
min={0}
max={100}
......@@ -303,15 +270,46 @@ const LimitCurve = (props) => {
</div>
<div className={classNames(`${prefixCls}-header-list`)}>
<span className={classNames(`${prefixCls}-header-item`)}>
曲线选择:
<Radio.Group
options={chartArr}
optionType={'button'}
value={curve}
onChange={(e) => {
setCurve(e.target.value);
}}
/>
取值限制:
<div className={classNames(`${prefixCls}-header-value`)}>
<Input
style={{
width: '150px',
}}
addonBefore="低低限"
disabled
/>
</div>
<div className={classNames(`${prefixCls}-header-value`)}>
<Input
style={{
width: '150px',
}}
addonBefore="低限"
disabled
/>
</div>
<div className={classNames(`${prefixCls}-header-value`)}>
<Input
style={{
width: '150px',
}}
addonBefore="高限"
disabled
/>
</div>
<div className={classNames(`${prefixCls}-header-value`)}>
<Input
style={{
width: '150px',
}}
addonBefore="高高限"
disabled
/>
</div>
</span>
<span className={classNames(`${prefixCls}-header-item`)}>
<Button type="primary">确定</Button>
</span>
</div>
</div>
......@@ -323,48 +321,11 @@ const LimitCurve = (props) => {
style={{ width: '100%', height: '100%' }}
/>
</div>
{spinning && <div className={classNames(`${prefixCls}-load`)}><LoadBox spinning={spinning}/></div>}
</div>
</Modal>
</>
);
};
const outlierArr = [
{
label: '低',
value: 3,
},
{
label: '中',
value: 2,
},
{
label: '高',
value: 1,
},
];
const clusterArr = [
{
label: 'K-means',
value: 'K-means',
},
];
const chartArr = [
{
label: '特征曲线',
value: '特征曲线',
},
{
label: '原始曲线',
value: '原始曲线',
},
];
// 平均值方法
const average = (arr) => {
return arr.reduce((acc, cur) => acc + cur, 0) / arr.length;
};
export default LimitCurve;
......@@ -12,6 +12,7 @@
height: 100%;
display: flex;
flex-direction: column;
position: relative;
}
&-header {
......@@ -33,10 +34,31 @@
align-items: center;
justify-content: center;
}
&-value {
margin-right: 10px;
.@{ant-prefix}-input[disabled] {
background-color: #ffffff;
}
}
}
&-content {
flex: 1;
overflow: hidden;
}
&-load {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.9);
z-index: 100;
}
}
\ No newline at end of file
export const variance = (numbers) => {
let mean = 0;
let sum = 0;
for (const i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
mean = sum / numbers.length;
sum = 0;
for (const i = 0; i < numbers.length; i++) {
sum += Math.pow(numbers[i] - mean, 2);
}
return sum / numbers.length;
};
export const outlierArr = [
{
label: '低',
value: 3,
},
{
label: '中',
value: 2,
},
{
label: '高',
value: 1,
},
];
export const timeArr = [
{
label: '近7天',
value: '近7天',
},
{
label: '7工作日',
value: '7工作日',
},
{
label: '7节假日',
value: '7节假日',
},
];
export const chartArr = [
{
label: '特征曲线',
value: '特征曲线',
},
{
label: '原始曲线',
value: '原始曲线',
},
];
// 平均值方法
export const average = (arr) => {
return arr.reduce((acc, cur) => acc + cur, 0) / arr.length;
};
\ No newline at end of file
......@@ -35,6 +35,7 @@ group:
| config | 全局 globalConfig,没有时需要传递 | object | {} |
| isZoom | 是否可缩放(手持上建议设置 true) | boolean | false |
| flowShow | 是否水流效果(无数据表现) | boolean | true |
| messaged | 传递信息给组态 | object | {} |
| customBack | 自定义交互的回调,返回点击的模型 | function(node){} | - |
### 工艺回放
......
......@@ -61,7 +61,7 @@ const ConfigurationView = (props) => {
const [description, setDescription] = useState(''); // 画板无数据描述
twoID = `TDG${Date.now().toString(36)}`;
const ConfigurationRef = useRef();
const customBack = props.customBack ? props.customBack : () => {};
const {
......@@ -313,6 +313,12 @@ const ConfigurationView = (props) => {
myDiagram.model.setDataProperty(node, 'visible', shRule ? shRule.visible : true);
}
break;
case 'groupCase': // 分组模型
shRule = ruleOperation(node, realVal);
if (node.shType === '显隐展示') {
myDiagram.model.setDataProperty(node, 'visible', shRule ? shRule.visible : true);
}
break;
default:
break;
}
......@@ -1140,12 +1146,13 @@ const ConfigurationView = (props) => {
goJS(
go.Group,
'Auto',
{ ungroupable: true, zOrder: 1 },
{ ungroupable: true, zOrder: 1, visible: true },
{
// 设置其可选择
selectable: false,
layerName: 'Background',
},
new go.Binding('visible', 'visible').makeTwoWay(),
goJS(
go.Shape,
'RoundedRectangle', // surrounds everything
......
......@@ -80,6 +80,7 @@ const ConfigurationView = (props) => {
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls('ec-configuration-view');
const componentPrefix = getPrefixCls('');
const [isDiagram, setIsDiagram] = useState(null);
const [isModalVisible, setIsModalVisible] = useState(false);
const [isAuModalVisible, setIsAuModalVisible] = useState(false); // 登录模态框
const [isHIModalVisible, setIsHIModalVisible] = useState(false); // 历史曲线模态框
......@@ -382,6 +383,12 @@ const ConfigurationView = (props) => {
myDiagram.model.setDataProperty(node, 'visible', shRule ? shRule.visible : true);
}
break;
case 'groupCase': // 分组模型
shRule = ruleOperation(node, realVal);
if (node.shType === '显隐展示') {
myDiagram.model.setDataProperty(node, 'visible', shRule ? shRule.visible : true);
}
break;
default:
break;
}
......@@ -883,6 +890,7 @@ const ConfigurationView = (props) => {
if (myDiagram) {
myDiagram.div = null;
myDiagram = null;
setIsDiagram(null);
}
};
}, [props.name, numerals]);
......@@ -912,6 +920,65 @@ const ConfigurationView = (props) => {
};
}, []);
useEffect(() => {
try {
const { messaged = {} } = props;
if (isDiagram && Object.keys(messaged).length > 0) {
const json = JSON.parse(isDiagram.model.toJson());
const jsonCopy = JSON.parse(JSON.stringify(json));
jsonCopy.nodeDataArray.forEach((item) => {
if (item.category === 'groupCase' && !item.shName) {
const node = isDiagram.model.findNodeDataForKey(item.key);
messagedMethod(node, messaged);
}
});
}
} catch (err) {
// console.log(err);
}
}, [props.messaged, isDiagram]);
// 信息通信
const messagedMethod = (node, messaged) => {
let shRule = [];
try {
switch (node.category) {
case 'groupCase': // 分组模型
shRule = ruleMessaged(node, messaged);
if (node.shType === '显隐展示') {
isDiagram.model.setDataProperty(
node,
'visible',
shRule ? shRule.visible : node.dtVisible,
);
}
break;
default:
break;
}
} catch (e) {
// console.log(e);
}
};
// 信息通信语句解析
const ruleMessaged = (node, messaged) => {
const patt = /[><=]/gi;
const shRule = JSON.parse(node.shRule).find((rule) => {
if (rule.val.toString().match(patt)) {
const ruleStr = 'if(' + rule.val + '){ return true } else { return false }';
try {
return new Function('x', 'X', ruleStr)(messaged, messaged);
} catch (err) {
return false;
}
} else {
return false;
}
});
return shRule;
};
/** ************************************获取画板JSON******************************* */
const getDiagramJson = async (list, siteInfo) => {
const response = await getSketchPadContent({
......@@ -1648,12 +1715,13 @@ const ConfigurationView = (props) => {
goJS(
go.Group,
'Auto',
{ ungroupable: true, zOrder: 1 },
{ ungroupable: true, zOrder: 1, visible: true },
{
// 设置其可选择
selectable: false,
layerName: 'Background',
},
new go.Binding('visible', 'visible').makeTwoWay(),
goJS(
go.Shape,
'RoundedRectangle', // surrounds everything
......@@ -3096,6 +3164,9 @@ const ConfigurationView = (props) => {
if (item.category === 'bgCase') {
myDiagram.defaultScale = item.scaling || item.scaling === 0 ? item.scaling * 1 : 1;
}
if (item.category === 'groupCase') {
item.dtVisible = item?.visible || false;
}
// 兼容V1之前版本(部分展示可支持)
if (chartInfo.version === 'V1') return false;
......@@ -3106,6 +3177,7 @@ const ConfigurationView = (props) => {
});
myDiagram.model = go.Model.fromJson(json);
getDataModel();
setIsDiagram(myDiagram);
};
return (
......@@ -3271,6 +3343,7 @@ ConfigurationView.defaultProps = {
config: {},
isZoom: false,
flowShow: true,
messaged: {},
customBack: () => {},
};
......@@ -3281,6 +3354,7 @@ ConfigurationView.propTypes = {
config: PropTypes.object,
isZoom: PropTypes.bool,
flowShow: PropTypes.bool,
messaged: PropTypes.object,
customBack: PropTypes.func,
};
......
......@@ -3,10 +3,11 @@ import { Button } from 'antd';
import PandaConfiguration from '../index';
// import PandaConfigurationView from '../../es/index';
const Demo = () => {
const [name, setName] = useState('宿场加压站2');
const [devices, setDevices] = useState(
'JYJZ00000001,JYBZ00000001',
);
const [name, setName] = useState('cs0320');
const [devices, setDevices] = useState('EQZT00000008');
const [messaged, setMessaged] = useState({
age: '运行监控1',
});
return (
<>
<div style={{ width: '100%', height: '600px', background: '#242835' }}>
......@@ -21,16 +22,20 @@ const Demo = () => {
// devices={'CPAA00000001, CPAD00000001, LJSC00000002'.split(',')}
config={globalConfig}
deviceName={['工艺流程1', '工艺流程2', '工艺流程3']}
messaged={messaged}
// isZoom={true}
// flowShow={false}
/>
</div>
<Button
onClick={() => {
setName('崇左丽江水厂原水泵房');
setDevices(
'EQZT00000007,CPBA00000001,CPAA00000001,EQZT00000008,CPDA00000001,CPAD00000001,LJSC00000002,EQZT00000005,EQZT00000004,EQZT00000002,EQZT00000003',
);
// setName('崇左丽江水厂原水泵房');
// setDevices(
// 'EQZT00000007,CPBA00000001,CPAA00000001,EQZT00000008,CPDA00000001,CPAD00000001,LJSC00000002,EQZT00000005,EQZT00000004,EQZT00000002,EQZT00000003',
// );
setMessaged({
age: '运行监控',
});
}}
style={{ margin: '10px' }}
>
......@@ -44,18 +49,18 @@ export default Demo;
const globalConfig = {
token: 'a1372ef0ce7b4e4884d31cfd99fe92f6',
mqtt_iotIP: '36.140.140.170:8200',
mqtt_path: '/ws/',
mqtt_IsSSL: false,
mqtt_site_code: 'site_ds5418ua',
mqtt_iotIP: 'emqttd10.panda-water.cn:443',
mqtt_path: '/mqtt',
mqtt_IsSSL: true,
mqtt_site_code: 'site_dc8302ni',
mqtt_mess: {
MessageLevel: '1.0',
TcpIP: '36.140.140.170',
TcpPort: 8200,
site_code: 'site_ds5418ua',
TcpIP: 'emqttd10.panda-water.cn',
TcpPort: 443,
site_code: 'site_dc8302ni',
},
userInfo: {
LocalSite: 'site_ds5418ua',
LocalSite: 'site_dc8302ni',
site: '',
},
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment