import React, { useContext, useEffect, useRef, useState } from 'react'; 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 [timeType, setTimeType] = useState('近7天'); // 时间 const [outlier, setOutlier] = useState(3); // 过滤异常 const [curve, setCurve] = useState('特征曲线'); // 曲线类型 const [sensitive, setSensitive] = useState(10); // 敏感度 const [sensorData, setSensorData] = useState([]); // 所有数据 const [chartData, setChartData] = useState([]); // 图表数据 const [timeCycle, setTimeCycle] = useState(60); const [options, setOptions] = useState({}); const chartRef = useRef(null); // 确定 const onOk = () => { props.onOk && props.onOk(123); }; // 取消 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: deviceCode, sensors: sensors, deviceType: deviceType }], isBoxPlots: true, }; const results = await getHistoryInfo(params); setSpinning(false); const historyData = results?.data?.[0] || {}; setSensorData(() => { return historyData; }); }; // 图表数据处理 const chartDataHandle = (data) => { const times = moment().subtract(1, 'day').format('YYYY-MM-DD'); const chart = data.map((item) => { return { ...item, time: moment(item.pt).format(times + ' HH:mm:ss'), }; }); return chart; }; // 聚集方法 const clusteredMothod = () => {}; // 渲染图表 const renderChart = (_chartData) => { const chartData = _chartData.map((item) => { return [new Date(item.time).getTime(), item.pv]; }); const clustered = skmeans(chartData, 24); const { centroids = [] } = clustered; const _centroids = centroids.sort((a, b) => { return a[0] - b[0]; }); console.log(_centroids); 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: [ { type: 'scatter', name: sensors, sampling: 'average', large: true, symbolSize: 5, data: _chartData.map((item) => { return [new Date(item.time).getTime(), item.pv]; }), }, { type: 'line', name: sensors, sampling: 'average', large: true, data: _centroids.map((item) => { return [Math.floor(item[0]), item[1]]; }), }, ], }; setOptions(option); }; useEffect(() => { open && getSensorsData(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); useEffect(() => { const { dataModel = [] } = sensorData; if (!dataModel.length) return setChartData([]); const count = Math.floor((24 * 60) / timeCycle); const times = moment().subtract(1, 'day').format('YYYY-MM-DD'); const _dataModel = dataModel.map((item) => { return { ...item, time: moment(item.pt).format(times + ' HH:mm:ss'), }; }); const _chartData = []; const _clustered = []; for (let i = 0; i < count; i++) { const data = _dataModel.filter((item) => { const time = new Date(item.time).getTime(); const min = new Date(moment(times + ' 00:00:00').add(i * timeCycle, 'minute')).getTime(); const max = new Date( moment(times + ' 00:00:00').add((i + 1) * timeCycle, 'minute'), ).getTime(); return time && time >= min && max >= time; }); const clusteredArr = []; const dataArr = []; const pvArr = data.map((item) => { return item.pv; }); const stdVal = pvArr.length ? std(pvArr) : 0; const medianVal = pvArr.length ? average(pvArr) : 0; const range = { min: medianVal - outlier * stdVal, max: medianVal + outlier * stdVal, }; data.forEach((item) => { if (item.pv >= range.min && item.pv <= range.max) dataArr.push(item); clusteredArr.push([new Date(item.time).getTime(), item.pv]); }); const clustered = clusteredArr.length ? skmeans(clusteredArr, 1) : {}; const { centroids = [] } = clustered; _chartData.push(...dataArr); _clustered.push(...centroids); } renderChart(_chartData, _clustered); // eslint-disable-next-line react-hooks/exhaustive-deps }, [sensorData, timeType, outlier]); useEffect(() => { setOpen(props.open); }, [props.open]); return ( <> <Modal title={`${sensors || ''}近7天历史数据`} centered okText={'确定'} width={width || '1200px'} cancelText={'取消'} open={open} onOk={onOk} 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={timeArr} optionType={'button'} value={timeType} onChange={(e) => { setTimeType(e.target.value); }} /> </span> <span className={classNames(`${prefixCls}-header-item`)}> 异常过滤: <Radio.Group options={outlierArr} optionType={'button'} value={outlier} onChange={(e) => { setOutlier(e.target.value); }} /> </span> <span className={classNames(`${prefixCls}-header-item`)}> 浮动范围: <Slider min={0} max={100} style={{ width: '100px' }} onChange={(value) => { setSensitive(value); }} value={typeof sensitive === 'number' ? sensitive : 0} /> <InputNumber min={1} max={100} style={{ margin: '0 16px', width: '100px', }} addonAfter="%" value={sensitive} onChange={(value) => { setSensitive(value); }} /> </span> </div> <div className={classNames(`${prefixCls}-header-list`)}> <span className={classNames(`${prefixCls}-header-item`)}> 取值限制: <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> <div className={classNames(`${prefixCls}-content`)}> <BasicChart ref={chartRef} option={options} notMerge style={{ width: '100%', height: '100%' }} /> </div> {spinning && <div className={classNames(`${prefixCls}-load`)}><LoadBox spinning={spinning}/></div>} </div> </Modal> </> ); }; export default LimitCurve;