Commit 8a303da5 authored by 陈龙's avatar 陈龙

feat: 升级历史曲线

parent 82a9265e
...@@ -16,7 +16,7 @@ const Demo = () => { ...@@ -16,7 +16,7 @@ const Demo = () => {
}, },
}, },
grid: { grid: {
right: '20%', right: '10%',
}, },
toolbox: { toolbox: {
feature: { feature: {
......
...@@ -18,7 +18,7 @@ export const buildDefaultLegend = (option) => { ...@@ -18,7 +18,7 @@ export const buildDefaultLegend = (option) => {
textStyle: { textStyle: {
padding: [0, 0, 0, 4], padding: [0, 0, 0, 4],
color: '#2d2d2d', color: '#2d2d2d',
width: 80, width: 120,
overflow:'truncate' overflow:'truncate'
}, },
tooltip:{ tooltip:{
......
...@@ -30,7 +30,7 @@ path: / ...@@ -30,7 +30,7 @@ path: /
## 多图表 ## 多图表
<code src="./demos/GridDemo.js"></code> <!-- <code src="./demos/GridDemo.js"></code> -->
## API ## API
......
...@@ -29,7 +29,6 @@ const SingleChart = memo((props) => { ...@@ -29,7 +29,6 @@ const SingleChart = memo((props) => {
showMarkLine: true, showMarkLine: true,
showPoint: true, showPoint: true,
chartType, chartType,
// justLine,
showBoxOption, showBoxOption,
special special
}; };
......
...@@ -4,26 +4,32 @@ import { MobileHistoryChart } from "../mobile"; ...@@ -4,26 +4,32 @@ import { MobileHistoryChart } from "../mobile";
const deviceParams = [ const deviceParams = [
/*10.182*/ /*10.182*/
/* { /* {
deviceCode: 'EGBF00000141', deviceCode: 'EGBF00000141',
// sensors: '进水压力,出水瞬时流量,出水累计流量', // sensors: '进水压力,出水瞬时流量,出水累计流量',
sensors: '进水压力', sensors: '进水压力',
deviceType: '熊猫二供泵房', deviceType: '熊猫二供泵房',
pointAddressID: 208, pointAddressID: 208,
},*/ }, */
/* { /* {
"deviceCode": "SYJ00000008", "deviceCode": "SYJ00000008",
"sensors": "瞬时流量", "sensors": "瞬时流量",
"deviceType": "水源井" "deviceType": "水源井"
},*/ },
{ {
deviceCode: 'EGJZ00000197', deviceCode: 'EGJZ00000197',
sensors: '进水压力,出水压力,出水瞬时流量,出水累计流量', sensors: '进水压力,出水压力,出水瞬时流量,出水累计流量',
// sensors: '1#变频器运行频率', // sensors: '1#变频器运行频率',
deviceType: '二供机组', deviceType: '二供机组',
// pointAddressID: 208, // pointAddressID: 208,
} }, */
/* {
deviceCode: 'EGJZ00000198',
sensors: '进水压力,出水压力,出水瞬时流量,出水累计流量',
// sensors: '1#变频器运行频率',
deviceType: '二供机组',
// pointAddressID: 208,
}, */
/* { /* {
// deviceCode: 'EGBF00000002', // deviceCode: 'EGBF00000002',
// deviceCode: 'EGBF00000018', // deviceCode: 'EGBF00000018',
...@@ -35,12 +41,12 @@ const deviceParams = [ ...@@ -35,12 +41,12 @@ const deviceParams = [
deviceType: '熊猫压力表', deviceType: '熊猫压力表',
pointAddressID: 4, pointAddressID: 4,
},*/ },*/
/* { {
deviceCode: 'EGJZ00000226', deviceCode: 'EGJZ00000226',
sensors: '出水压力', sensors: '出水压力',
deviceType: '二供机组', deviceType: '二供机组',
pointAddressID: 4, pointAddressID: 4,
},*/ },
/* { /* {
"deviceCode": "LLJ00000055", "deviceCode": "LLJ00000055",
"sensors": "正累计流量,瞬时流量", "sensors": "正累计流量,瞬时流量",
...@@ -52,51 +58,51 @@ const deviceParams = [ ...@@ -52,51 +58,51 @@ const deviceParams = [
"deviceType": "二供机组" "deviceType": "二供机组"
}*/ }*/
// 邳州张楼水厂 // 邳州张楼水厂
/* { {
"deviceCode": "SC00000023", "deviceCode": "SC00000023",
"sensors": "瞬时流量", "sensors": "瞬时流量",
"deviceType": "水厂" "deviceType": "水厂"
} */ },
/* { {
"deviceCode": "JFJ00000001", "deviceCode": "JFJ00000001",
"sensors": "沉淀池投矾量瞬时,", "sensors": "沉淀池投矾量瞬时,",
"deviceType": "加矾间" "deviceType": "加矾间"
}*/ },
/* { {
"deviceCode": "QSBF00000001", "deviceCode": "QSBF00000001",
"sensors": "取水浊度", "sensors": "取水浊度",
"deviceType": "取水泵房" "deviceType": "取水泵房"
}*/ },
/* { {
"deviceCode": "SC00000023", "deviceCode": "SC00000023",
"sensors": "出水瞬时流量", "sensors": "出水瞬时流量",
"deviceType": "水厂" "deviceType": "水厂"
}*/ },
// { {
// "deviceCode": "XNCDC00000001", "deviceCode": "XNCDC00000001",
// "sensors": "沉淀池1号进水管流量", "sensors": "沉淀池1号进水管流量",
// "deviceType": "絮凝沉淀池" "deviceType": "絮凝沉淀池"
// } },
/* { {
"deviceCode": "LLJ00000001", "deviceCode": "LLJ00000001",
"sensors": "瞬时流量", "sensors": "瞬时流量",
"deviceType": "流量计" "deviceType": "流量计"
}*/ },
/*邳州*/ /*邳州*/
/* { {
deviceCode: 'EGJZ00000027', deviceCode: 'EGJZ00000027',
sensors: '2#变频器运行频率', sensors: '2#变频器运行频率',
deviceType: '二供机组', deviceType: '二供机组',
}*/ },
/*确山*/ /*确山*/
/*泵3状态*/ /*泵3状态*/
/* { /* {
// EGJZ00000003 // EGJZ00000003
deviceCode: 'EGJZ00000003', deviceCode: 'EGJZ00000003',
// sensors: '3#频率', // sensors: '3#频率',
sensors: '出1累计流量', sensors: '出1累计流量',
deviceType: '二供机组', deviceType: '二供机组',
}*/ } */
]; ];
const Demo = () => { const Demo = () => {
return ( return (
......
import React, {useContext, useEffect, useMemo, useState, useCallback, useRef} from 'react'; import React, { useContext, useEffect, useMemo, useState, useCallback, useRef } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { import {
...@@ -34,10 +34,10 @@ import { ...@@ -34,10 +34,10 @@ import {
import SingleChart from './SingleChart'; import SingleChart from './SingleChart';
import GridChart from './GridChart'; import GridChart from './GridChart';
import './index.less'; import './index.less';
import {globalConfig} from 'antd/lib/config-provider'; import { globalConfig } from 'antd/lib/config-provider';
const {RangePicker} = DatePicker; const { RangePicker } = DatePicker;
const {Option} = Select; const { Option } = Select;
const startFormat = 'YYYY-MM-DD 00:00:00'; const startFormat = 'YYYY-MM-DD 00:00:00';
const endFormat = 'YYYY-MM-DD 23:59:59'; const endFormat = 'YYYY-MM-DD 23:59:59';
...@@ -263,7 +263,7 @@ const OriginMaxDays = 31; // 原始曲线请求数据的最大天数 ...@@ -263,7 +263,7 @@ const OriginMaxDays = 31; // 原始曲线请求数据的最大天数
const CharacteristicMaxDays = null; // 特征曲线或者其他曲线的最大天数 const CharacteristicMaxDays = null; // 特征曲线或者其他曲线的最大天数
const HistoryView = (props) => { const HistoryView = (props) => {
const [completeInit, setCompleteInit] = useState(false); const [completeInit, setCompleteInit] = useState(false);
const {getPrefixCls} = useContext(ConfigProvider.ConfigContext); const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
const prefixCls = getPrefixCls('history-view'); const prefixCls = getPrefixCls('history-view');
const { const {
title, title,
...@@ -311,6 +311,14 @@ const HistoryView = (props) => { ...@@ -311,6 +311,14 @@ const HistoryView = (props) => {
const [percent, setPercent] = useState(0); const [percent, setPercent] = useState(0);
// 频率指标特殊业务 // 频率指标特殊业务
const [special1, setSpecial1] = useState(null); const [special1, setSpecial1] = useState(null);
// 历史数据相关的特征描述
const deviceConfig = useRef({
oneDevice: deviceParams.length === 1,//单设备
oneSensor: [...new Set(deviceParams.reduce((final, cur) => {
let _sensors = cur.sensors.split(',');
return final.concat(_sensors);
}, []))].length === 1, // 单指标
});
// 选择的时间范围值 // 选择的时间范围值
const dateRange = useMemo(() => { const dateRange = useMemo(() => {
if (timeValue === 'customer') { if (timeValue === 'customer') {
...@@ -444,13 +452,13 @@ const HistoryView = (props) => { ...@@ -444,13 +452,13 @@ const HistoryView = (props) => {
onContrastChange(contrastOption); onContrastChange(contrastOption);
setShowBoxOption(false); setShowBoxOption(false);
setChartType('lineChart'); setChartType('lineChart');
onCheckboxChange({target: {value: false}}, 'chartType'); onCheckboxChange({ target: { value: false } }, 'chartType');
onCheckboxChange({target: {value: false}}, 'ignoreOutliers'); onCheckboxChange({ target: { value: false } }, 'ignoreOutliers');
} else { } else {
// 自定义 // 自定义
// 不需要处理 // 不需要处理
setShowBoxOption(true); setShowBoxOption(true);
onCheckboxChange({target: {value: true}}, 'chartType'); onCheckboxChange({ target: { value: true } }, 'chartType');
} }
}; };
const onShortcutsChange = (e) => { const onShortcutsChange = (e) => {
...@@ -460,37 +468,37 @@ const HistoryView = (props) => { ...@@ -460,37 +468,37 @@ const HistoryView = (props) => {
switch (_val) { switch (_val) {
case '近3天': case '近3天':
_arr = [ _arr = [
{key: 1, value: moment()}, { key: 1, value: moment() },
{key: 2, value: moment().subtract(1, 'days')}, { key: 2, value: moment().subtract(1, 'days') },
{key: 3, value: moment().subtract(2, 'days')}, { key: 3, value: moment().subtract(2, 'days') },
] ]
break; break;
case '近7天': case '近7天':
_arr = [ _arr = [
{key: 1, value: moment()}, { key: 1, value: moment() },
{key: 2, value: moment().subtract(1, 'days')}, { key: 2, value: moment().subtract(1, 'days') },
{key: 3, value: moment().subtract(2, 'days')}, { key: 3, value: moment().subtract(2, 'days') },
{key: 4, value: moment().subtract(3, 'days')}, { key: 4, value: moment().subtract(3, 'days') },
{key: 5, value: moment().subtract(4, 'days')}, { key: 5, value: moment().subtract(4, 'days') },
{key: 6, value: moment().subtract(5, 'days')}, { key: 6, value: moment().subtract(5, 'days') },
{key: 7, value: moment().subtract(6, 'days')}, { key: 7, value: moment().subtract(6, 'days') },
] ]
break; break;
case '近3月': case '近3月':
_arr = [ _arr = [
{key: 1, value: moment()}, { key: 1, value: moment() },
{key: 2, value: moment().subtract(1, 'months')}, { key: 2, value: moment().subtract(1, 'months') },
{key: 3, value: moment().subtract(2, 'months')}, { key: 3, value: moment().subtract(2, 'months') },
] ]
break; break;
case '近6月': case '近6月':
_arr = [ _arr = [
{key: 1, value: moment()}, { key: 1, value: moment() },
{key: 2, value: moment().subtract(1, 'months')}, { key: 2, value: moment().subtract(1, 'months') },
{key: 3, value: moment().subtract(2, 'months')}, { key: 3, value: moment().subtract(2, 'months') },
{key: 4, value: moment().subtract(3, 'months')}, { key: 4, value: moment().subtract(3, 'months') },
{key: 5, value: moment().subtract(4, 'months')}, { key: 5, value: moment().subtract(4, 'months') },
{key: 6, value: moment().subtract(5, 'months')}, { key: 6, value: moment().subtract(5, 'months') },
] ]
break; break;
} }
...@@ -553,7 +561,7 @@ const HistoryView = (props) => { ...@@ -553,7 +561,7 @@ const HistoryView = (props) => {
)} )}
{timeValue === 'contrast' && ( // 同期对比 {timeValue === 'contrast' && ( // 同期对比
<> <>
<Select value={contrastOption} style={{width: 60}} onChange={onContrastChange}> <Select value={contrastOption} style={{ width: 60 }} onChange={onContrastChange}>
<Option value="day"></Option> <Option value="day"></Option>
<Option value="month" disabled={lineDataType === '原始曲线'}></Option> <Option value="month" disabled={lineDataType === '原始曲线'}></Option>
</Select> </Select>
...@@ -578,14 +586,14 @@ const HistoryView = (props) => { ...@@ -578,14 +586,14 @@ const HistoryView = (props) => {
picker={contrastOption === 'day' ? undefined : contrastOption} picker={contrastOption === 'day' ? undefined : contrastOption}
value={child.value} value={child.value}
onChange={(date, dateString) => onContrastPickerChange(date, dateString, child)} onChange={(date, dateString) => onContrastPickerChange(date, dateString, child)}
style={{width: 130, border: !shortcutsValue ? '1px solid #1890ff' : ''}} style={{ width: 130, border: !shortcutsValue ? '1px solid #1890ff' : '' }}
/> />
{datePickerArr.length > 2 && ( {datePickerArr.length > 2 && (
<div <div
className={classNames(`${prefixCls}-contrast-delete`)} className={classNames(`${prefixCls}-contrast-delete`)}
onClick={() => handleDeleteDatePicker(index)} onClick={() => handleDeleteDatePicker(index)}
> >
<CloseCircleFilled/> <CloseCircleFilled />
</div> </div>
)} )}
</div> </div>
...@@ -594,7 +602,7 @@ const HistoryView = (props) => { ...@@ -594,7 +602,7 @@ const HistoryView = (props) => {
)} )}
</div> </div>
))} ))}
{datePickerArr.length < 4 && <PlusCircleOutlined onClick={handleAddDatePicker}/>} {datePickerArr.length < 4 && <PlusCircleOutlined onClick={handleAddDatePicker} />}
</> </>
)} )}
</div> </div>
...@@ -664,13 +672,13 @@ const HistoryView = (props) => { ...@@ -664,13 +672,13 @@ const HistoryView = (props) => {
</Checkbox> </Checkbox>
{child.tooltip && ( {child.tooltip && (
<Tooltip title={child.tooltip}> <Tooltip title={child.tooltip}>
<QuestionCircleFilled className={`${prefixCls}-question`}/> <QuestionCircleFilled className={`${prefixCls}-question`} />
</Tooltip> </Tooltip>
)} )}
{ {
child.hasSub && child.checked && false ? child.hasSub && child.checked && false ?
<Select style={{width: 80, marginLeft: 10}} value={algorithmValue} <Select style={{ width: 80, marginLeft: 10 }} value={algorithmValue}
onChange={(e) => setAlgorithmValue(e)}> onChange={(e) => setAlgorithmValue(e)}>
<Option value={1}></Option> <Option value={1}></Option>
<Option value={5}></Option> <Option value={5}></Option>
<Option value={10}></Option> <Option value={10}></Option>
...@@ -685,7 +693,7 @@ const HistoryView = (props) => { ...@@ -685,7 +693,7 @@ const HistoryView = (props) => {
return ( return (
<div <div
className={classNames(`${prefixCls}-cover`)} className={classNames(`${prefixCls}-cover`)}
style={isChart && isSingle ? {width: '100%'} : {}} style={isChart && isSingle ? { width: '100%' } : {}}
> >
{ {
isChart ? <> isChart ? <>
...@@ -700,7 +708,7 @@ const HistoryView = (props) => { ...@@ -700,7 +708,7 @@ const HistoryView = (props) => {
</Radio.Group> </Radio.Group>
{/*<Segmented value={lineDataType} options={['特征曲线', '原始曲线']} onChange={switchLineDataType}/>*/} {/*<Segmented value={lineDataType} options={['特征曲线', '原始曲线']} onChange={switchLineDataType}/>*/}
<Tooltip title={'原始曲线数据量较大,单次查询最多展示1万条数据'}> <Tooltip title={'原始曲线数据量较大,单次查询最多展示1万条数据'}>
<QuestionCircleFilled style={{marginLeft: 6}} className={`${prefixCls}-question`}/> <QuestionCircleFilled style={{ marginLeft: 6 }} className={`${prefixCls}-question`} />
</Tooltip> </Tooltip>
</div> </div>
</> : '' </> : ''
...@@ -709,14 +717,14 @@ const HistoryView = (props) => { ...@@ -709,14 +717,14 @@ const HistoryView = (props) => {
<> <>
{ {
lineDataType !== '原始曲线' ? <> lineDataType !== '原始曲线' ? <>
<div style={{marginLeft: 7}} className={classNames(`${prefixCls}-label`)}>曲线形态</div> <div style={{ marginLeft: 7 }} className={classNames(`${prefixCls}-label`)}>曲线形态</div>
<Radio.Group <Radio.Group
value={chartType} value={chartType}
style={{marginRight: 16}} style={{ marginRight: 16 }}
onChange={(e) => { onChange={(e) => {
let _value = e.target.value; let _value = e.target.value;
setChartType(_value); setChartType(_value);
onCheckboxChange({target: {value: _value !== 'boxChart'}}, 'chartType'); onCheckboxChange({ target: { value: _value !== 'boxChart' } }, 'chartType');
}} }}
> >
<Radio.Button value={'lineChart'}>线形图</Radio.Button> <Radio.Button value={'lineChart'}>线形图</Radio.Button>
...@@ -743,7 +751,7 @@ const HistoryView = (props) => { ...@@ -743,7 +751,7 @@ const HistoryView = (props) => {
{activeTabKey === 'table' && ( {activeTabKey === 'table' && (
<Select <Select
value={dataThinKey} value={dataThinKey}
style={{width: 90}} style={{ width: 90 }}
onChange={onTimeIntervalChange} onChange={onTimeIntervalChange}
disabled={!dataConfig.dataThin} disabled={!dataConfig.dataThin}
> >
...@@ -792,7 +800,7 @@ const HistoryView = (props) => { ...@@ -792,7 +800,7 @@ const HistoryView = (props) => {
// data = data.filter(item => item.sensorName !== '是否在线'); // data = data.filter(item => item.sensorName !== '是否在线');
const ignoreOutliers = checkboxData.find((item) => item.key === 'ignoreOutliers').checked; const ignoreOutliers = checkboxData.find((item) => item.key === 'ignoreOutliers').checked;
const dataIndexAccess = (dataItem, index) => { const dataIndexAccess = (dataItem, index) => {
const {stationCode, sensorName} = dataItem; const { stationCode, sensorName } = dataItem;
return `${stationCode}-${sensorName}-${index}`; return `${stationCode}-${sensorName}-${index}`;
}; };
...@@ -800,17 +808,24 @@ const HistoryView = (props) => { ...@@ -800,17 +808,24 @@ const HistoryView = (props) => {
if (timeValue === 'contrast') { if (timeValue === 'contrast') {
format = contrastOption === 'day' ? '2020-01-01 HH:mm:00' : '2020-01-DD HH:mm:00'; format = contrastOption === 'day' ? '2020-01-01 HH:mm:00' : '2020-01-DD HH:mm:00';
} }
// 判断是否是单设备,单设备则不显示设备名称
// 处理表头数据 // 处理表头数据
const columnsData = data.map((item, index) => { const columnsData = data.map((item, index) => {
const {stationCode, equipmentName, sensorName, unit, dataModel} = item; const { stationCode, equipmentName, sensorName, unit, dataModel } = item;
const dataIndex = dataIndexAccess(item, index); const dataIndex = dataIndexAccess(item, index);
let _title = '';
if (deviceConfig.current.oneDevice) {
_title = `${sensorName}${unit ? `(${unit})` : ''}`;
} else {
_title = `${equipmentName}-${sensorName}${unit ? `(${unit})` : ''}`
}
let col = { let col = {
title: `${equipmentName}-${sensorName}${unit ? `(${unit})` : ''}`, title: _title,
dataIndex: dataIndex, dataIndex: dataIndex,
key: dataIndex, key: dataIndex,
ellipsis: true, ellipsis: true,
align: 'center', align: 'center',
width:200
}; };
// 同期对比 // 同期对比
if (timeValue === 'contrast' && dataModel[0]) { if (timeValue === 'contrast' && dataModel[0]) {
...@@ -825,7 +840,7 @@ const HistoryView = (props) => { ...@@ -825,7 +840,7 @@ const HistoryView = (props) => {
// 格式化时间对齐数据, 生成行数 // 格式化时间对齐数据, 生成行数
const timeData = {}; const timeData = {};
const buildDefaultData = (time) => { const buildDefaultData = (time) => {
const obj = {key: time, time: time}; const obj = { key: time, time: time };
data.forEach((item, index) => { data.forEach((item, index) => {
const dataIndex = dataIndexAccess(item, index); const dataIndex = dataIndexAccess(item, index);
obj[dataIndex] = ''; obj[dataIndex] = '';
...@@ -833,32 +848,32 @@ const HistoryView = (props) => { ...@@ -833,32 +848,32 @@ const HistoryView = (props) => {
return obj; return obj;
}; };
data.forEach((item, index) => { data.forEach((item, index) => {
const {stationCode, sensorName, dataModel} = item; const { stationCode, sensorName, dataModel } = item;
dataModel && dataModel &&
dataModel.forEach((data) => { dataModel.forEach((data) => {
const formatTime = moment(data.pt).format(format); const formatTime = moment(data.pt).format(format);
let time = formatTime; let time = formatTime;
if (timeValue === 'contrast') { if (timeValue === 'contrast') {
time = time.slice(contrastOption === 'day' ? 11 : 8, 16); time = time.slice(contrastOption === 'day' ? 11 : 8, 16);
} }
timeData[formatTime] = timeData[formatTime] || buildDefaultData(time); timeData[formatTime] = timeData[formatTime] || buildDefaultData(time);
}); });
}); });
// 处理表格数据 // 处理表格数据
data.forEach((child, index) => { data.forEach((child, index) => {
const {dataModel} = child; const { dataModel } = child;
const dataIndex = dataIndexAccess(child, index); const dataIndex = dataIndexAccess(child, index);
dataModel && dataModel &&
dataModel.forEach((value, j) => { dataModel.forEach((value, j) => {
const formatTime = moment(value.pt).format(format); const formatTime = moment(value.pt).format(format);
const dataRow = timeData[formatTime]; const dataRow = timeData[formatTime];
if (dataRow) { if (dataRow) {
dataRow[dataIndex] = value.pv === null || value.pv === undefined ? '' : value.pv; dataRow[dataIndex] = value.pv === null || value.pv === undefined ? '' : value.pv;
} }
}); });
}); });
const timeSort = (a, b) => { const timeSort = (a, b) => {
let aa = a, let aa = a,
...@@ -898,40 +913,40 @@ const HistoryView = (props) => { ...@@ -898,40 +913,40 @@ const HistoryView = (props) => {
}; };
const handleDataThinKey = (diffYears, diffDays, diffHours, lineDataType) => { const handleDataThinKey = (diffYears, diffDays, diffHours, lineDataType) => {
if (lineDataType === '原始曲线') { if (lineDataType === '原始曲线') {
return {unit: '', zoom: ''} return { unit: '', zoom: '' }
} }
// edit by zy 根据选择的时长控制抽稀频度 // edit by zy 根据选择的时长控制抽稀频度
if (diffYears > 0) { if (diffYears > 0) {
if (diffYears === 1) return {unit: 'h', zoom: '24'} if (diffYears === 1) return { unit: 'h', zoom: '24' }
return {unit: 'h', zoom: '48'} return { unit: 'h', zoom: '48' }
} else if (diffYears === 0 && diffDays > 0) { } else if (diffYears === 0 && diffDays > 0) {
if (diffDays > 90) return {unit: 'h', zoom: '24'} if (diffDays > 90) return { unit: 'h', zoom: '24' }
if (diffDays > 30) return {unit: 'h', zoom: '6'} if (diffDays > 30) return { unit: 'h', zoom: '6' }
if (diffDays > 15) return {unit: 'h', zoom: '4'} if (diffDays > 15) return { unit: 'h', zoom: '4' }
if (diffDays > 7) return {unit: 'h', zoom: '1'} if (diffDays > 7) return { unit: 'h', zoom: '1' }
if (diffDays > 3) return {unit: 'min', zoom: '30'} if (diffDays > 3) return { unit: 'min', zoom: '30' }
if (diffDays > 1) return {unit: 'min', zoom: '15'} if (diffDays > 1) return { unit: 'min', zoom: '15' }
if (diffDays === 1) return {unit: 'min', zoom: '5'} if (diffDays === 1) return { unit: 'min', zoom: '5' }
} else if (diffYears === 0 && diffDays === 0 && diffHours > 0) { } else if (diffYears === 0 && diffDays === 0 && diffHours > 0) {
if (diffHours > 12) return {unit: 'min', zoom: '5'} if (diffHours > 12) return { unit: 'min', zoom: '5' }
if (diffHours > 4) return {unit: 'min', zoom: '1'} if (diffHours > 4) return { unit: 'min', zoom: '1' }
if (diffHours > 1) return {unit: 's', zoom: '30'} if (diffHours > 1) return { unit: 's', zoom: '30' }
if (diffHours > 0) return {unit: 's', zoom: '5'} if (diffHours > 0) return { unit: 's', zoom: '5' }
return {unit: 's', zoom: '5'} return { unit: 's', zoom: '5' }
} else { } else {
return {unit: '', zoom: ''} return { unit: '', zoom: '' }
} }
}; };
// 处理接口服务参数的变化 // 处理接口服务参数的变化
const onChangeParams = (value = {}) => { const onChangeParams = (value = {}) => {
const {dateRange, isDilute, ignoreOutliers, zoom, unit} = value; const { dateRange, isDilute, ignoreOutliers, zoom, unit } = value;
const requestArr = []; const requestArr = [];
const acrossTables = []; const acrossTables = [];
const zoomArray = []; const zoomArray = [];
deviceParams deviceParams
.map((item) => { .map((item) => {
let _item = {...item}; let _item = { ...item };
// 历史曲线中,是否在线暂时去除,不显示 要显示是否在线解开这里即可 2023-09-15 // 历史曲线中,是否在线暂时去除,不显示 要显示是否在线解开这里即可 2023-09-15
/* _item.sensors = /* _item.sensors =
item.sensors && !item.sensors.includes('是否在线') item.sensors && !item.sensors.includes('是否在线')
...@@ -971,7 +986,7 @@ const HistoryView = (props) => { ...@@ -971,7 +986,7 @@ const HistoryView = (props) => {
let diffHours = moment(item.dateTo).diff(moment(item.dateFrom), 'hours'); let diffHours = moment(item.dateTo).diff(moment(item.dateFrom), 'hours');
let zoomParam = activeTabKey === 'curve' ? handleDataThinKey(diffYears, diffDays, diffHours, lineDataType) : {}; let zoomParam = activeTabKey === 'curve' ? handleDataThinKey(diffYears, diffDays, diffHours, lineDataType) : {};
zoomArray.push(zoomParam); zoomArray.push(zoomParam);
requestArr.push(getHistoryInfo({...param, ...zoomParam})); requestArr.push(getHistoryInfo({ ...param, ...zoomParam }));
}); });
setLoading(true); setLoading(true);
Promise.all(requestArr).then((results) => { Promise.all(requestArr).then((results) => {
...@@ -979,7 +994,7 @@ const HistoryView = (props) => { ...@@ -979,7 +994,7 @@ const HistoryView = (props) => {
if (results.length) { if (results.length) {
let data = []; let data = [];
results.forEach((res, index) => { results.forEach((res, index) => {
const {dateFrom, dateTo} = dateRange?.[index] ?? {}; const { dateFrom, dateTo } = dateRange?.[index] ?? {};
if (res.code === 0 && res.data.length) { if (res.code === 0 && res.data.length) {
res.data.forEach((d) => { res.data.forEach((d) => {
d.dateFrom = dateFrom || ''; d.dateFrom = dateFrom || '';
...@@ -1022,7 +1037,7 @@ const HistoryView = (props) => { ...@@ -1022,7 +1037,7 @@ const HistoryView = (props) => {
useEffect(() => { useEffect(() => {
if (!completeInit) return; if (!completeInit) return;
const {dataThin, ignoreOutliers, zoom, unit} = dataConfig; const { dataThin, ignoreOutliers, zoom, unit } = dataConfig;
beforChangeParams().finally(() => { beforChangeParams().finally(() => {
onChangeParams({ onChangeParams({
isDilute: dataThin, isDilute: dataThin,
...@@ -1067,9 +1082,10 @@ const HistoryView = (props) => { ...@@ -1067,9 +1082,10 @@ const HistoryView = (props) => {
{...tableProps} {...tableProps}
pagination={false} pagination={false}
onChange={handleChange} onChange={handleChange}
scroll={{x:'max-content',y:'calc(100% - 40px)'}}
/> />
) : ( ) : (
<PandaEmpty/> <PandaEmpty />
)} )}
</div> </div>
</> </>
...@@ -1107,11 +1123,11 @@ const HistoryView = (props) => { ...@@ -1107,11 +1123,11 @@ const HistoryView = (props) => {
</div> </div>
{ {
lineDataType === '原始曲线' && false ? lineDataType === '原始曲线' && false ?
<div style={{marginTop: 10}}>展示区间:{returnLongestPeriod(chartDataSource)}</div> : '' <div style={{ marginTop: 10 }}>展示区间:{returnLongestPeriod(chartDataSource)}</div> : ''
} }
<div className={`${prefixCls}-content`}> <div className={`${prefixCls}-content`}>
{!chartDataSource.length ? ( {!chartDataSource.length ? (
<PandaEmpty/> <PandaEmpty />
) : grid === true ? ( ) : grid === true ? (
<GridChart <GridChart
curveCenter={curveCenter} curveCenter={curveCenter}
...@@ -1129,7 +1145,6 @@ const HistoryView = (props) => { ...@@ -1129,7 +1145,6 @@ const HistoryView = (props) => {
showGridLine={chartGrid} showGridLine={chartGrid}
prefixCls={prefixCls} prefixCls={prefixCls}
dataSource={chartDataSource} dataSource={chartDataSource}
// justLine={!!checkboxData.find(item => item.key === 'justLine' && item.checked)}
chartType={isBoxPlots ? chartType : null} chartType={isBoxPlots ? chartType : null}
contrast={timeValue === 'contrast'} contrast={timeValue === 'contrast'}
contrastOption={contrastOption} contrastOption={contrastOption}
...@@ -1152,7 +1167,7 @@ const HistoryView = (props) => { ...@@ -1152,7 +1167,7 @@ const HistoryView = (props) => {
// 非单曲线、单指标不执行 // 非单曲线、单指标不执行
if (deviceParams?.length !== 1 || (deviceParams?.length === 1 && deviceParams?.[0]?.sensors?.split(',')?.length > 1)) return setCompleteInit(true); if (deviceParams?.length !== 1 || (deviceParams?.length === 1 && deviceParams?.[0]?.sensors?.split(',')?.length > 1)) return setCompleteInit(true);
setLoading(true); setLoading(true);
const {deviceCode, deviceType, sensors} = deviceParams[0]; const { deviceCode, deviceType, sensors } = deviceParams[0];
let _id = (await getPointAddress({ let _id = (await getPointAddress({
code: deviceCode code: deviceCode
}))?.data?.[0]?.id; }))?.data?.[0]?.id;
...@@ -1176,7 +1191,7 @@ const HistoryView = (props) => { ...@@ -1176,7 +1191,7 @@ const HistoryView = (props) => {
return final return final
}, {}); }, {});
let _checkboxData = [...checkboxData].map(item => { let _checkboxData = [...checkboxData].map(item => {
let _item = {...item}; let _item = { ...item };
if (_opt[item.label] !== undefined) { if (_opt[item.label] !== undefined) {
_item.checked = _opt[item.label] === 'true' _item.checked = _opt[item.label] === 'true'
} }
...@@ -1190,7 +1205,7 @@ const HistoryView = (props) => { ...@@ -1190,7 +1205,7 @@ const HistoryView = (props) => {
let _statusName = _sensorConfig?.statusName; let _statusName = _sensorConfig?.statusName;
if (_statusName) { if (_statusName) {
let _statusConfig = _res1.data.find(item => item?.name.trim() === _statusName.trim()); let _statusConfig = _res1.data.find(item => item?.name.trim() === _statusName.trim());
let _valDesc = _statusConfig?.valDesc||''; let _valDesc = _statusConfig?.valDesc || '';
setSpecial1({ setSpecial1({
name: _statusName, name: _statusName,
valDesc: _valDesc.split(';').reduce((final, cur) => { valDesc: _valDesc.split(';').reduce((final, cur) => {
...@@ -1230,18 +1245,18 @@ const HistoryView = (props) => { ...@@ -1230,18 +1245,18 @@ const HistoryView = (props) => {
}, [loading]) }, [loading])
return ( return (
<div className={classNames(prefixCls, 'wkt-scroll-light')}> <div className={classNames(prefixCls, 'wkt-scroll-light')}>
<div className={classNames(`${prefixCls}-spin`)} style={{position: "relative"}}> <div className={classNames(`${prefixCls}-spin`)} style={{ position: "relative" }}>
{ {
(loading || percent !== 0) ? <div className={classNames(`${prefixCls}-progressWrapper`)}> (loading || percent !== 0) ? <div className={classNames(`${prefixCls}-progressWrapper`)}>
<div className={classNames(`${prefixCls}-contentWrapper`)}> <div className={classNames(`${prefixCls}-contentWrapper`)}>
{ {
lineDataType === '原始曲线' || lineDataType === '特征曲线' && moment(dateRange?.[0]?.dateTo).diff(moment(dateRange?.[0]?.dateFrom), 'days') >= 30 ? <> lineDataType === '原始曲线' || lineDataType === '特征曲线' && moment(dateRange?.[0]?.dateTo).diff(moment(dateRange?.[0]?.dateFrom), 'days') >= 30 ? <>
<Progress percent={percent} <Progress percent={percent}
steps={20} steps={20}
className={classNames(`${prefixCls}-progress`, `${prefixCls}-blink-2`)} className={classNames(`${prefixCls}-progress`, `${prefixCls}-blink-2`)}
showInfo={false}/> showInfo={false} />
<div className={classNames(`${prefixCls}-tip`)}>加载中...</div> <div className={classNames(`${prefixCls}-tip`)}>加载中...</div>
</> : <Spin spinning={loading || false}/> </> : <Spin spinning={loading || false} />
} }
</div> </div>
...@@ -1261,7 +1276,7 @@ const HistoryView = (props) => { ...@@ -1261,7 +1276,7 @@ const HistoryView = (props) => {
<div className={`${prefixCls}-extra-right`}> <div className={`${prefixCls}-extra-right`}>
{activeTabKey === 'table' && ( {activeTabKey === 'table' && (
<Button type="link" onClick={exportExcelBtn}> <Button type="link" onClick={exportExcelBtn}>
<DownloadOutlined/> <DownloadOutlined />
下载 下载
</Button> </Button>
)} )}
......
import moment from 'moment'; import moment from 'moment';
import _, {isArray} from 'lodash'; import _, { isArray } from 'lodash';
import maxIcon from './assets/最大实心.svg'; import maxIcon from './assets/最大实心.svg';
import minIcon from './assets/最小实心.svg'; import minIcon from './assets/最小实心.svg';
import minIconDownArrow from './assets/最小实心箭头朝下.svg'; import minIconDownArrow from './assets/最小实心箭头朝下.svg';
import lineChart from "@wisdom-components/basicchart/es/LineChart"; import lineChart from '@wisdom-components/basicchart/es/LineChart';
import { offset } from 'highcharts';
/** 轴宽度, 用于计算多轴显示时, 轴线偏移和绘图区域尺寸 */ /** 轴宽度, 用于计算多轴显示时, 轴线偏移和绘图区域尺寸 */
const AXIS_WIDTH = 40; const AXIS_WIDTH = 40;
const COLOR = { const COLOR = {
NORMAL: '#1685FF', NORMAL: '#1685FF',
UPER: '#fa8c16', UPER: '#fa8c16',
UPUPER: '#FF0000', UPUPER: '#FF0000',
// LOWER: '#13c2c2', // LOWER: '#13c2c2',
// LOWLOWER: '#2f54eb', // LOWLOWER: '#2f54eb',
LOWER: '#fa8c16', LOWER: '#fa8c16',
LOWLOWER: '#FF0000', LOWLOWER: '#FF0000',
AVG: '#00B8B1', AVG: '#00B8B1',
}; };
const isMobile = () => { const isMobile = () => {
const userAgent = navigator.userAgent.toLowerCase(); const userAgent = navigator.userAgent.toLowerCase();
if ( if (/ipad|iphone|midp|rv:1.2.3.4|ucweb|android|windows ce|windows mobile/.test(userAgent)) {
/ipad|iphone|midp|rv:1.2.3.4|ucweb|android|windows ce|windows mobile/.test( return true;
userAgent }
) return false;
) {
return true;
}
return false;
}; };
const returnRem = (num, base = 375) => num * (base / 375); const returnRem = (num, base = 375) => num * (base / 375);
export const handlePx = (num, unit = '') => { export const handlePx = (num, unit = '') => {
const _isMobile = isMobile(); const _isMobile = isMobile();
const _base = document.body.clientWidth; const _base = document.body.clientWidth;
let _num = _isMobile ? `${returnRem(num, _base)}` : `${num}`; let _num = _isMobile ? `${returnRem(num, _base)}` : `${num}`;
return unit ? `${_num}${unit}` : Number(_num); return unit ? `${_num}${unit}` : Number(_num);
}; };
const PC_OPTION = { const PC_OPTION = {
markPoint: { markPoint: {
padding: [2, 12], padding: [2, 12],
lineHeight: 22, lineHeight: 22,
backgroundColor: backgroundColor:
window.globalConfig && window.globalConfig && window.globalConfig && window.globalConfig.variableTheme?.primaryColor
window.globalConfig && ? window.globalConfig.variableTheme.primaryColor
window.globalConfig.variableTheme?.primaryColor : '#0087F7',
? window.globalConfig.variableTheme.primaryColor borderWidth: 1,
: '#0087F7', },
borderWidth: 1 fontSize: 16,
}, fontColor: '#ffffff',
fontSize: 16, dataZoomHeight: 28,
fontColor: "#ffffff", };
dataZoomHeight: 28
}
const MOBILE_OPTION = { const MOBILE_OPTION = {
markPoint: { markPoint: {
padding: [2, 6], padding: [2, 6],
lineHeight: 18, lineHeight: 18,
backgroundColor: 'rgba(255,255,255,0.6)', backgroundColor: 'rgba(255,255,255,0.6)',
borderWidth: 0 borderWidth: 0,
}, },
fontSize: handlePx(12), fontSize: handlePx(12),
fontColor: "#0087F7", fontColor: '#0087F7',
dataZoomHeight: 20 dataZoomHeight: 20,
} };
const currentOption = isMobile() ? MOBILE_OPTION : PC_OPTION; const currentOption = isMobile() ? MOBILE_OPTION : PC_OPTION;
/** /**
* 图表系列名称格式化 * 图表系列名称格式化
...@@ -73,13 +68,13 @@ const currentOption = isMobile() ? MOBILE_OPTION : PC_OPTION; ...@@ -73,13 +68,13 @@ const currentOption = isMobile() ? MOBILE_OPTION : PC_OPTION;
* @returns * @returns
*/ */
const nameFormatter = (data, contrast, contrastOption, nameWithSensor) => { const nameFormatter = (data, contrast, contrastOption, nameWithSensor) => {
const {equipmentName, sensorName, unit, dataModel, dateFrom, dateTo} = data; const { equipmentName, sensorName, unit, dataModel, dateFrom, dateTo } = data;
let name = nameWithSensor ? `${equipmentName}-${sensorName}` : equipmentName; let name = nameWithSensor ? `${equipmentName}-${sensorName}` : equipmentName;
if (contrast) { if (contrast) {
const time = dateFrom.slice(0, contrastOption === 'day' ? 10 : 7).replace(/-/g, ''); const time = dateFrom.slice(0, contrastOption === 'day' ? 10 : 7).replace(/-/g, '');
name = `${name}-${time}`; name = `${name}-${time}`;
} }
return name; return name;
}; };
/** /**
...@@ -91,14 +86,14 @@ const nameFormatter = (data, contrast, contrastOption, nameWithSensor) => { ...@@ -91,14 +86,14 @@ const nameFormatter = (data, contrast, contrastOption, nameWithSensor) => {
* @returns 图表系列数据, [[DateTime, value]] * @returns 图表系列数据, [[DateTime, value]]
*/ */
const dataAccessor = (data, contrast, contrastOption) => { const dataAccessor = (data, contrast, contrastOption) => {
const dataModel = data?.dataModel ?? []; const dataModel = data?.dataModel ?? [];
const formatStr = contrastOption === 'day' ? '2020-01-01 HH:mm:ss' : '2020-01-DD HH:mm:ss'; const formatStr = contrastOption === 'day' ? '2020-01-01 HH:mm:ss' : '2020-01-DD HH:mm:ss';
return dataModel return dataModel
.filter((item) => item.sensorName !== '是否在线') .filter((item) => item.sensorName !== '是否在线')
.map((item) => { .map((item) => {
const time = contrast ? moment(item.pt).format(formatStr) : item.pt; const time = contrast ? moment(item.pt).format(formatStr) : item.pt;
return [moment(time).valueOf(), item.pv]; return [moment(time).valueOf(), item.pv];
}); });
}; };
/** /**
...@@ -108,8 +103,8 @@ const dataAccessor = (data, contrast, contrastOption) => { ...@@ -108,8 +103,8 @@ const dataAccessor = (data, contrast, contrastOption) => {
* @returns Null/areaStyle, 为null显示曲线图, 为areaStyle对象显示为面积图. * @returns Null/areaStyle, 为null显示曲线图, 为areaStyle对象显示为面积图.
*/ */
const areaStyleFormatter = (data) => { const areaStyleFormatter = (data) => {
const {sensorName} = data; const { sensorName } = data;
return sensorName && sensorName.indexOf('流量') > -1 ? {} : null; return sensorName && sensorName.indexOf('流量') > -1 ? {} : null;
}; };
/** /**
...@@ -119,100 +114,100 @@ const areaStyleFormatter = (data) => { ...@@ -119,100 +114,100 @@ const areaStyleFormatter = (data) => {
* @returns * @returns
*/ */
const minMax = (data) => { const minMax = (data) => {
const {dataModel} = data; const { dataModel } = data;
let min = Number.MAX_SAFE_INTEGER; let min = Number.MAX_SAFE_INTEGER;
let max = Number.MIN_SAFE_INTEGER; let max = Number.MIN_SAFE_INTEGER;
dataModel.forEach((item) => { dataModel.forEach((item) => {
min = Math.min(min, item.pv ?? 0); min = Math.min(min, item.pv ?? 0);
max = Math.max(max, item.pv ?? 0); max = Math.max(max, item.pv ?? 0);
}); });
return [min, max]; return [min, max];
}; };
const markLineItem = (name, value, color) => { const markLineItem = (name, value, color) => {
return { return {
name: name, name: name,
yAxis: value, yAxis: value,
value: value, value: value,
lineStyle: { lineStyle: {
color: color || '#000', color: color || '#000',
}, },
label: { label: {
position: 'insideEndTop', position: 'insideEndTop',
color: color || '#000', color: color || '#000',
formatter: function () { formatter: function () {
return `${name}:${value}`; return `${name}:${value}`;
}, },
}, },
}; };
}; };
export const alarmMarkLine = (dataItem, index, dataSource, schemes) => { export const alarmMarkLine = (dataItem, index, dataSource, schemes) => {
// 只有一个数据曲线时显示markline // 只有一个数据曲线时显示markline
if (!dataItem || !schemes || dataSource.length !== 1) return {}; if (!dataItem || !schemes || dataSource.length !== 1) return {};
const {deviceType, stationCode, sensorName, decimalPoint} = dataItem; const { deviceType, stationCode, sensorName, decimalPoint } = dataItem;
const curSchemes = schemes.filter( const curSchemes = schemes.filter(
(item) => (item) =>
item.deviceCode === stationCode && item.deviceCode === stationCode &&
item.sensorName === sensorName && item.sensorName === sensorName &&
item.valueType === '直接取值', item.valueType === '直接取值',
); );
const data = []; const data = [];
curSchemes.forEach((scheme) => { curSchemes.forEach((scheme) => {
const {hLimit, hhLimit, lLimit, llLimit} = scheme; const { hLimit, hhLimit, lLimit, llLimit } = scheme;
lLimit !== null && lLimit !== void 0 && data.push(markLineItem('低限', lLimit, '#fa8c16')); lLimit !== null && lLimit !== void 0 && data.push(markLineItem('低限', lLimit, '#fa8c16'));
hLimit !== null && hLimit !== void 0 && data.push(markLineItem('高限', hLimit, '#fa8c16')); hLimit !== null && hLimit !== void 0 && data.push(markLineItem('高限', hLimit, '#fa8c16'));
llLimit !== null && llLimit !== void 0 && data.push(markLineItem('低低限', llLimit, '#FF0000')); llLimit !== null && llLimit !== void 0 && data.push(markLineItem('低低限', llLimit, '#FF0000'));
hhLimit !== null && hhLimit !== void 0 && data.push(markLineItem('高高限', hhLimit, '#FF0000')); hhLimit !== null && hhLimit !== void 0 && data.push(markLineItem('高高限', hhLimit, '#FF0000'));
}); });
data.push({ data.push({
name: '平均线', name: '平均线',
type: 'average', type: 'average',
lineStyle: { lineStyle: {
color: '#00b8b1', color: '#00b8b1',
type: 'solid', type: 'solid',
}, },
label: { label: {
position: 'insideEndTop', position: 'insideEndTop',
color: '#00b8b1', color: '#00b8b1',
formatter: function (param) { formatter: function (param) {
return `平均值:${param.value}`; return `平均值:${param.value}`;
}, },
}, },
}); });
return { return {
symbol: ['none', 'none'], symbol: ['none', 'none'],
data, data,
}; };
}; };
export const minMaxMarkPoint = (dataItem, index, dataSource) => { export const minMaxMarkPoint = (dataItem, index, dataSource) => {
const _isMobile = isMobile(); const _isMobile = isMobile();
// 只有一个数据曲线时显示markline // 只有一个数据曲线时显示markline
if (!dataItem || dataSource.length !== 1) return {}; if (!dataItem || dataSource.length !== 1) return {};
const data = [ const data = [
{ {
type: 'min', type: 'min',
value: null, value: null,
name: '最小: ', name: '最小: ',
symbol: `image://${minIcon}`, symbol: `image://${minIcon}`,
symbolOffset: [0, 18], symbolOffset: [0, 18],
}, },
{ {
type: 'max', type: 'max',
value: null, value: null,
name: '最大: ', name: '最大: ',
symbol: `image://${maxIcon}`, symbol: `image://${maxIcon}`,
symbolOffset: [0, -16], symbolOffset: [0, -16],
} },
]; ];
return { return {
label: { label: {
show: false show: false,
}, },
symbolSize: [49, 31], symbolSize: [49, 31],
data, data,
}; };
}; };
/** /**
...@@ -221,27 +216,27 @@ export const minMaxMarkPoint = (dataItem, index, dataSource) => { ...@@ -221,27 +216,27 @@ export const minMaxMarkPoint = (dataItem, index, dataSource) => {
* @param {any} axis * @param {any} axis
*/ */
export const decorateAxisGridLine = (axis, showGrid) => { export const decorateAxisGridLine = (axis, showGrid) => {
if (!axis) return; if (!axis) return;
axis.minorTick = { axis.minorTick = {
lineStyle: { lineStyle: {
color: '#e2e2e2', color: '#e2e2e2',
}, },
...(axis.minorTick || {}), ...(axis.minorTick || {}),
show: showGrid, show: showGrid,
splitNumber: 2, splitNumber: 2,
}; };
axis.minorSplitLine = { axis.minorSplitLine = {
lineStyle: { lineStyle: {
color: '#e2e2e2', color: '#e2e2e2',
type: 'dashed', type: 'dashed',
}, },
...(axis.minorSplitLine || {}), ...(axis.minorSplitLine || {}),
show: showGrid, show: showGrid,
}; };
axis.splitLine = { axis.splitLine = {
...(axis.splitLine || {}), ...(axis.splitLine || {}),
show: showGrid, show: showGrid,
}; };
}; };
/** /**
...@@ -250,562 +245,625 @@ export const decorateAxisGridLine = (axis, showGrid) => { ...@@ -250,562 +245,625 @@ export const decorateAxisGridLine = (axis, showGrid) => {
* @param {any} dataItem * @param {any} dataItem
*/ */
export const offlineArea = (dataItem) => { export const offlineArea = (dataItem) => {
if (!dataItem) return {}; if (!dataItem) return {};
const {dataModel} = dataItem; const { dataModel } = dataItem;
let datas = new Array(); let datas = new Array();
let offlineData = []; let offlineData = [];
let hasOffline = false; let hasOffline = false;
dataModel.forEach((item, index) => { dataModel.forEach((item, index) => {
if (!item.pv && !hasOffline) { if (!item.pv && !hasOffline) {
offlineData = [ offlineData = [
{ {
name: '离线', name: '离线',
xAxis: new Date(item.pt), xAxis: new Date(item.pt),
label: {show: !datas?.length} label: { show: !datas?.length },
},
];
hasOffline = true;
} else if (item.pv && hasOffline) {
offlineData.push({
xAxis: new Date(item.pt),
});
datas.push(offlineData);
offlineData = [];
hasOffline = false;
}
});
return {
itemStyle: {
color: '#eee',
opacity: 0.6,
}, },
data: datas, ];
}; hasOffline = true;
} else if (item.pv && hasOffline) {
offlineData.push({
xAxis: new Date(item.pt),
});
datas.push(offlineData);
offlineData = [];
hasOffline = false;
}
});
return {
itemStyle: {
color: '#eee',
opacity: 0.6,
},
data: datas,
};
}; };
// 生成默认legend配置 // 生成默认legend配置
export const buildDefaultLegend = (option) => { export const buildDefaultLegend = (option) => {
const {title} = option; const { title } = option;
let paddingRight = 0; let paddingRight = 0;
if (title && title.show) paddingRight = 80; // 给标题留够空间 if (title && title.show) paddingRight = 80; // 给标题留够空间
return { return {
show: true, show: true,
right: 20, right: 20,
top: 20, top: 20,
icon: 'rect', icon: 'rect',
itemWidth: 14, itemWidth: 14,
itemHeight: 8, itemHeight: 8,
itemGap: 20, itemGap: 20,
padding: [0, 0, 0, paddingRight], padding: [0, 0, 0, paddingRight],
textStyle: { textStyle: {
padding: [0, 0, 0, 4], padding: [0, 0, 0, 4],
color: '#2d2d2d', color: '#2d2d2d',
}, },
}; };
}; };
// tooltip 模板 // tooltip 模板
const headTemplate = (param, opt) => { const headTemplate = (param, opt) => {
if (!param) return ''; if (!param) return '';
const {name, axisValueLabel, axisType, axisValue} = param; const { name, axisValueLabel, axisType, axisValue } = param;
const timeFormat = opt && opt.contrast ? (opt.contrastOption === 'day' ? 'HH:mm:ss' : 'DD日HH时') : 'YYYY-MM-DD HH:mm:ss'; const timeFormat =
const text = opt && opt.contrast
axisType === 'xAxis.time' ? moment(axisValue).format(timeFormat) : name || axisValueLabel; ? opt.contrastOption === 'day'
return `<div style="border-bottom: 1px solid #F0F0F0; color: #808080; margin-bottom:${handlePx(5, 'px')}; padding-bottom: ${handlePx(5, 'px')};">${text}</div>`; ? 'HH:mm:ss'
: 'DD日HH时'
: 'YYYY-MM-DD HH:mm:ss';
const text =
axisType === 'xAxis.time' ? moment(axisValue).format(timeFormat) : name || axisValueLabel;
return `<div style="border-bottom: 1px solid #F0F0F0; color: #808080; margin-bottom:${handlePx(
5,
'px',
)}; padding-bottom: ${handlePx(5, 'px')};">${text}</div>`;
}; };
const seriesTemplate = (param, unit) => { const seriesTemplate = (param, unit) => {
if (!param || param.seriesName === '自定义') return ''; if (!param || param.seriesName === '自定义') return '';
const {value, encode} = param; const { value, encode } = param;
// const val = value[encode.y[0]]; // const val = value[encode.y[0]];
const _unit = unit || ''; const _unit = unit || '';
const color = '#008CFF'; const color = '#008CFF';
if (!isArray(value)) if (!isArray(value))
return ` <div style="display: flex; align-items: center;"> return ` <div style="display: flex; align-items: center;">
<span style="${isMobile() ? 'width: ' + handlePx(90, 'px') + ';overflow:hidden;text-overflow:ellipsis;white-space:nowrap' : ''}">${param.seriesName}</span> <span style="${isMobile()
? 'width: ' +
handlePx(90, 'px') +
';overflow:hidden;text-overflow:ellipsis;white-space:nowrap'
: ''
}">${param.seriesName}</span>
<span style="display:inline-block;">:</span> <span style="display:inline-block;">:</span>
<span style="color:${color};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value?.toFixed(3) ?? '-'}</span> <span style="color:${color};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value?.toFixed(3) ?? '-'
}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span> <span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div>`; </div>`;
return param.componentSubType !== 'candlestick' return param.componentSubType !== 'candlestick'
? `<div style="display: flex; align-items: center;"> ? `<div style="display: flex; align-items: center;">
<span style="${isMobile() ? 'width: ' + handlePx(90, 'px') + ';overflow:hidden;text-overflow:ellipsis;white-space:nowrap' : ''}">${param.seriesName}</span><span style="display:inline-block;">:</span> <span style="${isMobile()
<span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[1] ?? '-'}</span> ? 'width: ' +
handlePx(90, 'px') +
';overflow:hidden;text-overflow:ellipsis;white-space:nowrap'
: ''
}">${param.seriesName}</span><span style="display:inline-block;">:</span>
<span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[1] ?? '-'
}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span> <span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div>` </div>`
: ` : `
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<span>首值</span><span style="display:inline-block;">:</span> <span>首值</span><span style="display:inline-block;">:</span>
<span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[1] ?? '-'}</span> <span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[1] ?? '-'
}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span> <span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div> </div>
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<span>尾值</span><span style="display:inline-block;">:</span> <span>尾值</span><span style="display:inline-block;">:</span>
<span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[2] ?? '-'}</span> <span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[2] ?? '-'
}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span> <span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div> </div>
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<span>周期最小值</span><span style="display:inline-block;">:</span> <span>周期最小值</span><span style="display:inline-block;">:</span>
<span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[3] ?? '-'}</span> <span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[3] ?? '-'
}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span> <span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div> </div>
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<span>周期最大值</span><span style="display:inline-block;">:</span> <span>周期最大值</span><span style="display:inline-block;">:</span>
<span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[4] ?? '-'}</span> <span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[4] ?? '-'
}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span> <span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div> </div>
`; `;
}; };
const tooltipAccessor = (unit, contrastOption) => { const tooltipAccessor = (unit, contrastOption) => {
return { return {
formatter: function (params, ticket, callback) { formatter: function (params, ticket, callback) {
let tooltipHeader = ''; let tooltipHeader = '';
let tooltipContent = ''; let tooltipContent = '';
if (isArray(params)) { if (isArray(params)) {
tooltipHeader = headTemplate(params[0], contrastOption); tooltipHeader = headTemplate(params[0], contrastOption);
params.forEach((param) => { params.forEach((param) => {
tooltipContent += seriesTemplate(param, unit?.[param?.seriesIndex]); tooltipContent += seriesTemplate(param, unit?.[param?.seriesIndex]);
}); });
} else { } else {
tooltipHeader = headTemplate(params, contrastOption); tooltipHeader = headTemplate(params, contrastOption);
tooltipContent += seriesTemplate(params, unit?.[params?.seriesIndex]); tooltipContent += seriesTemplate(params, unit?.[params?.seriesIndex]);
} }
return ` return `
<div> <div>
${tooltipHeader} ${tooltipHeader}
<div>${tooltipContent}</div> <div>${tooltipContent}</div>
</div> </div>
`; `;
}, },
}; };
}; };
// 处理特殊业务:1. 频率;2. 是否在线(暂时不上) // 处理特殊业务:1. 频率;2. 是否在线(暂时不上)
const handleSpecial1 = (special, dataSource) => { const handleSpecial1 = (special, dataSource) => {
// 频率部分的业务 // 频率部分的业务
const _colorMap = { const _colorMap = {
变频: '#1685ff', 变频: '#1685ff',
工频: '#00d0c7', 工频: '#00d0c7',
运行: '#1685ff', 运行: '#1685ff',
故障: '#ff6b37', 故障: '#ff6b37',
停止: '#666666' 停止: '#666666',
}; };
let _special1 = special?.special1 ?? {}; let _special1 = special?.special1 ?? {};
let _valDesc = _special1?.valDesc ?? {}; let _valDesc = _special1?.valDesc ?? {};
let _data = dataSource.find(item => item.sensorName === _special1.name); let _data = dataSource.find((item) => item.sensorName === _special1.name);
let _markAreaData = []; let _markAreaData = [];
let _pieces = _data?.dataModel?.reduce((final, cur, index) => { let _pieces =
_data?.dataModel
?.reduce((final, cur, index) => {
let _pt = moment(cur.pt).valueOf(); let _pt = moment(cur.pt).valueOf();
let _length = final.length; let _length = final.length;
if (_colorMap[_valDesc[cur.pv]]) { if (_colorMap[_valDesc[cur.pv]]) {
if (_length === 0) { if (_length === 0) {
final.push({ final.push({
lte: _pt, lte: _pt,
gte: 0, gte: 0,
color: _colorMap[_valDesc[cur.pv]], color: _colorMap[_valDesc[cur.pv]],
value: cur.pv, value: cur.pv,
text: _valDesc[cur.pv] text: _valDesc[cur.pv],
}); });
} else {
if (cur.pv === final[_length - 1].value) {
final[_length - 1].lte = _pt;
} else { } else {
if (cur.pv === final[_length - 1].value) { final.push({
final[_length - 1].lte = _pt; lte: _pt,
} else { gte: _pt,
final.push({ color: _colorMap[_valDesc[cur.pv]],
lte: _pt, value: cur.pv,
gte: _pt, text: _valDesc[cur.pv],
color: _colorMap[_valDesc[cur.pv]], });
value: cur.pv,
text: _valDesc[cur.pv]
});
}
} }
}
} }
return final; return final;
}, []) }, [])
.map(item => { .map((item) => {
_markAreaData.push( _markAreaData.push([
[ {
{ xAxis: item.gte,
xAxis: item.gte,
itemStyle: {
// color: item.color
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: item.color // 0% 处的颜色
}, {
offset: 1, color: item.color// 100% 处的颜色
}],
global: false // 缺省为 false
}
},
name: item.text,
label: {show: true}
},
{
xAxis: item.lte,
itemStyle: {
color: item.color
}
}
]
)
delete item.value;
return item;
})??[];
let _final = {};
if (_markAreaData.length) {
_final.visualMap = {
type: 'piecewise',
show: false,
dimension: 0,
seriesIndex: 0,
pieces: _pieces,
}
}
if (_pieces.length) {
_final.markArea = {
silent: true,
itemStyle: { itemStyle: {
opacity: 0.1 // color: item.color
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: item.color, // 0% 处的颜色
},
{
offset: 1,
color: item.color, // 100% 处的颜色
},
],
global: false, // 缺省为 false
},
}, },
data: _markAreaData name: item.text,
} label: { show: true },
} },
return _final; {
xAxis: item.lte,
itemStyle: {
color: item.color,
},
},
]);
delete item.value;
return item;
}) ?? [];
let _final = {};
if (_markAreaData.length) {
_final.visualMap = {
type: 'piecewise',
show: false,
dimension: 0,
seriesIndex: 0,
pieces: _pieces,
};
}
if (_pieces.length) {
_final.markArea = {
silent: true,
itemStyle: {
opacity: 0.1,
},
data: _markAreaData,
};
}
return _final;
}; };
// 生成x坐标 // 生成x坐标
const returnXAxis = ({ const returnXAxis = ({
dataSource, dataSource,
contrast, contrast,
contrastOption, contrastOption,
nameWithSensor, nameWithSensor,
showMarkLine, showMarkLine,
deviceAlarmSchemes, deviceAlarmSchemes,
showPoint, showPoint,
restOption, restOption,
smooth, smooth,
special special,
}) => { }) => {
// 根据"指标名称"分类yAxis // 根据"指标名称"分类yAxis
const yAxisInterator = (() => { const yAxisInterator = (() => {
const map = new Map(); const map = new Map();
let current = -1; let current = -1;
const get = (name) => (map.has(name) ? map.get(name) : map.set(name, ++current).get(name)); const get = (name) => (map.has(name) ? map.get(name) : map.set(name, ++current).get(name));
return {get}; return { get };
})(); })();
let _offlineData = []; let _offlineData = [];
// 生成visualMap、markArea // 生成visualMap、markArea
let {visualMap, markArea} = handleSpecial1(special, dataSource); let { visualMap, markArea } = handleSpecial1(special, dataSource);
let _filterArr = ['是否在线']; let _filterArr = ['是否在线'];
if (special?.special1?.name) { if (special?.special1?.name) {
_filterArr.push(special.special1.name); _filterArr.push(special.special1.name);
} }
// 生成series // 生成series
let series = dataSource let series = dataSource
.filter((item) => { .filter((item) => {
if (item.sensorName === '是否在线') { if (item.sensorName === '是否在线') {
_offlineData.push(item); _offlineData.push(item);
} }
return !_filterArr.includes(item.sensorName); return !_filterArr.includes(item.sensorName);
}) })
.map((item, index) => { .map((item, index) => {
const {sensorName, unit} = item; const { sensorName, unit } = item;
const name = nameFormatter(item, contrast, contrastOption, nameWithSensor); const name = nameFormatter(item, contrast, contrastOption, nameWithSensor);
const data = dataAccessor(item, contrast, contrastOption); const data = dataAccessor(item, contrast, contrastOption);
const type = 'line'; const type = 'line';
const areaStyle = areaStyleFormatter(item); const areaStyle = areaStyleFormatter(item);
const yAxisIndex = yAxisInterator.get(sensorName); const yAxisIndex = yAxisInterator.get(sensorName);
const markLine = showMarkLine const markLine = showMarkLine
? alarmMarkLine(item, index, dataSource, deviceAlarmSchemes) ? alarmMarkLine(item, index, dataSource, deviceAlarmSchemes)
: {}; : {};
const markPoint = showPoint ? minMaxMarkPoint(item, index, dataSource) : {}; const markPoint = showPoint ? minMaxMarkPoint(item, index, dataSource) : {};
// let markArea = null; // let markArea = null;
// 需求变更:设备离线改用“是否在线”的数据,离线的状态标记的数据用该部分的数据。 2023年4月25日09:36:55 // 需求变更:设备离线改用“是否在线”的数据,离线的状态标记的数据用该部分的数据。 2023年4月25日09:36:55
// 暂时注释,离线逻辑需要再确认 2023-09-15 // 暂时注释,离线逻辑需要再确认 2023-09-15
/* let _offlineAreasData = _offlineData /* let _offlineAreasData = _offlineData
.find((offline) => offline.stationCode === item.stationCode); .find((offline) => offline.stationCode === item.stationCode);
let offlineAreas = offlineArea(_offlineAreasData); let offlineAreas = offlineArea(_offlineAreasData);
if (offlineAreas.data?.length) { if (offlineAreas.data?.length) {
restOption.markArea = null; restOption.markArea = null;
markArea = offlineAreas; markArea = offlineAreas;
}*/ }*/
// 需求新增:增加频率业务 // 需求新增:增加频率业务
return { return {
notMerge: true, notMerge: true,
name, name,
type, type,
data, data,
areaStyle, areaStyle,
yAxisIndex, yAxisIndex,
smooth, smooth,
unit, unit,
markLine, markLine,
markPoint, markPoint,
markArea, markArea,
}; };
}); });
// 由于series更新后,没有的数据曲线仍然停留在图表区上,导致图表可视区范围有问题 // 由于series更新后,没有的数据曲线仍然停留在图表区上,导致图表可视区范围有问题
const min = Math.min( const min = Math.min(
...series.map((item) => item.data?.[0]?.[0]).filter((item) => item !== undefined), ...series.map((item) => item.data?.[0]?.[0]).filter((item) => item !== undefined),
); );
const max = Math.max( const max = Math.max(
...series ...series
.map((item) => item.data?.[item.data.length - 1]?.[0]) .map((item) => item.data?.[item.data.length - 1]?.[0])
.filter((item) => item !== undefined), .filter((item) => item !== undefined),
); );
return { return {
xAxis: { xAxis: {
type: 'time', min, max, axisLabel: { type: 'time',
formatter: contrast ? (contrastOption === 'month' ? '{dd}日' : '{HH}:{mm}') : { min,
year: '{yyyy}', max,
month: '{MMM}', axisLabel: {
day: '{d}日', formatter: contrast
hour: '{HH}:{mm}', ? contrastOption === 'month'
minute: '{HH}:{mm}', ? '{dd}日'
second: '{HH}:{mm}:{ss}', : '{HH}:{mm}'
none: '{yyyy}-{MM}-{dd} {hh}:{mm}:{ss}' : {
} year: '{yyyy}',
} month: '{MMM}',
}, series, visualMap day: '{d}日',
}; hour: '{HH}:{mm}',
} minute: '{HH}:{mm}',
second: '{HH}:{mm}:{ss}',
none: '{yyyy}-{MM}-{dd} {hh}:{mm}:{ss}',
},
},
},
series,
visualMap,
};
};
const handleDefault = (config, cusOption) => { const handleDefault = (config, cusOption) => {
const needUnit = _.get(config, 'needUnit', false); const needUnit = _.get(config, 'needUnit', false);
const curveCenter = _.get(config, 'curveCenter', false); const curveCenter = _.get(config, 'curveCenter', false);
const nameWithSensor = _.get(config, 'nameWithSensor', true); const nameWithSensor = _.get(config, 'nameWithSensor', true);
const showGridLine = _.get(config, 'showGridLine', true); const showGridLine = _.get(config, 'showGridLine', true);
const showMarkLine = _.get(config, 'showMarkLine', false); const showMarkLine = _.get(config, 'showMarkLine', false);
const showPoint = _.get(config, 'showPoint', false); const showPoint = _.get(config, 'showPoint', false);
const deviceAlarmSchemes = _.get(config, 'deviceAlarmSchemes', []); const deviceAlarmSchemes = _.get(config, 'deviceAlarmSchemes', []);
const chartType = _.get(config, 'chartType', null); const chartType = _.get(config, 'chartType', null);
// const justLine = _.get(config, 'justLine', false); // const justLine = _.get(config, 'justLine', false);
const showBoxOption = _.get(config, 'showBoxOption', false); const showBoxOption = _.get(config, 'showBoxOption', false);
// 自定义属性 // 自定义属性
const restOption = _.pick(cusOption, ['title', 'legend']); const restOption = _.pick(cusOption, ['title', 'legend']);
const special = _.get(config, 'special', {}); const special = _.get(config, 'special', {});
return { return {
needUnit, needUnit,
curveCenter, curveCenter,
nameWithSensor, nameWithSensor,
showGridLine, showGridLine,
showMarkLine, showMarkLine,
showPoint, showPoint,
deviceAlarmSchemes, deviceAlarmSchemes,
chartType, chartType,
showBoxOption, showBoxOption,
restOption, restOption,
special special,
} };
};
const handleMaxValue = (value) => {
if (value <= 1) return value;
if (value >= 10000) return `${(value / 1000).toFixed(0)}k`;
return value.toFixed(0);
} }
const handleYAxis = ({dataSource, needUnit, curveCenter, showGridLine}) => { const handleYAxis = ({ dataSource, needUnit, curveCenter, showGridLine }) => {
// 一种指标一个y轴 // 一种指标一个y轴
const yAxisMap = new Map(); const yAxisMap = new Map();
dataSource.forEach((item, index) => { // 1. 找出最大值; 2. 计算出y轴最大宽度动态计算偏移距离;
const {sensorName, unit} = item; let _maxValueArray = [];
const key = sensorName; let _offsetValue = [];
dataSource.forEach((item, index) => {
if (!yAxisMap.has(key)) { let _maxValue = item.dataModel.reduce((final, cur) => {
const i = yAxisMap.size; if (cur.pv > final) final = cur.pv;
const axis = { return final
type: 'value', }, 0);
name: needUnit ? unit : null, _maxValueArray.push(handleMaxValue(_maxValue));
position: i % 2 === 0 ? 'left' : 'right', const { sensorName, unit } = item;
offset: Math.floor(i / 2) * 80, const key = sensorName;
axisLabel: { if (!yAxisMap.has(key)) {
formatter: (value) => (value > 10000 ? `${(value / 1000).toFixed(1)}k` : value.toFixed(1)), const i = yAxisMap.size;
}, let _offset = _maxValueArray[i - 2];
axisLine: { let _baseOffset = _offsetValue[i - 2] ?? 0;
show: true, let _finalOffset = (_offset !== undefined ? (_offset === 0 ? 2 : _offset.toString().replaceAll('.','').length) * 10 : 0) + _baseOffset;
}, _offsetValue.push(_finalOffset);
nameTextStyle: { const axis = {
align: i % 2 === 0 ? 'right' : 'left', type: 'value',
}, name: needUnit ? unit : null,
minorTick: { position: i % 2 === 0 ? 'left' : 'right',
lineStyle: { // offset: _offset !== undefined ? (_offset === 0? 2:_offset.toString().length) * 10 : 0,
color: '#e2e2e2', offset: _finalOffset,
}, axisLabel: {
}, formatter: (value) => {
minorSplitLine: { return handleMaxValue(value);
lineStyle: { }
color: '#e2e2e2', },
type: 'dashed', axisLine: {
}, show: true,
}, },
}; nameTextStyle: {
yAxisMap.set(key, axis); align: i % 2 === 0 ? 'right' : 'left',
} },
minorTick: {
lineStyle: {
color: '#e2e2e2',
},
},
minorSplitLine: {
lineStyle: {
color: '#e2e2e2',
type: 'dashed',
},
},
};
yAxisMap.set(key, axis);
}
// 曲线居中 // 曲线居中
if (curveCenter && item.dataModel && item.dataModel.length > 0) { if (curveCenter && item.dataModel && item.dataModel.length > 0) {
const [min, max] = minMax(item); const [min, max] = minMax(item);
const axis = yAxisMap.get(key); const axis = yAxisMap.get(key);
axis.min = axis.min === void 0 ? min : Math.min(min, axis.min); axis.min = axis.min === void 0 ? min : Math.min(min, axis.min);
axis.max = axis.max === void 0 ? max : Math.max(max, axis.max); axis.max = axis.max === void 0 ? max : Math.max(max, axis.max);
} }
// 网格显示 // 网格显示
const axis = yAxisMap.get(key); const axis = yAxisMap.get(key);
decorateAxisGridLine(axis, showGridLine); decorateAxisGridLine(axis, showGridLine);
}); });
const yAxis = yAxisMap.size > 0 ? [...yAxisMap.values()] : {type: 'value'}; const yAxis = yAxisMap.size > 0 ? [...yAxisMap.values()] : { type: 'value' };
// 根据y轴个数调整边距 // 根据y轴个数调整边距
const leftNum = Math.ceil(yAxisMap.size / 2); const leftNum = Math.ceil(yAxisMap.size / 2);
const rightNum = Math.floor(yAxisMap.size / 2); const rightNum = Math.floor(yAxisMap.size / 2);
return {leftNum, rightNum, yAxis}; return { leftNum, rightNum, yAxis };
} };
const assignOptions = (restOption, xAxis, legendData, chartType, contrast, contrastOption) => { const assignOptions = (restOption, xAxis, legendData, chartType, contrast, contrastOption) => {
restOption.dataZoom = [ restOption.dataZoom = [
{ {
show: true, show: true,
bottom: 10, bottom: 10,
start: 0, start: 0,
end: 100, end: 100,
height: currentOption['dataZoomHeight'], height: currentOption['dataZoomHeight'],
type: 'inside', type: 'inside',
zoomOnMouseWheel: true, zoomOnMouseWheel: true,
filterMode: chartType === 'lineChart' ? 'none' : 'weakFilter' filterMode: chartType === 'lineChart' ? 'none' : 'weakFilter',
}, },
{ {
show: true, show: true,
bottom: 10, bottom: 10,
start: 0, start: 0,
end: 100, end: 100,
height: currentOption['dataZoomHeight'], height: currentOption['dataZoomHeight'],
type: 'slider', type: 'slider',
zoomOnMouseWheel: true, zoomOnMouseWheel: true,
labelFormatter: function (e) { labelFormatter: function (e) {
let _formatterStr = 'YYYY-MM-DD HH:mm:ss'; let _formatterStr = 'YYYY-MM-DD HH:mm:ss';
if (contrast) { if (contrast) {
if (contrastOption === 'day') _formatterStr = 'HH:mm'; if (contrastOption === 'day') _formatterStr = 'HH:mm';
if (contrastOption === 'month') _formatterStr = 'MM月DD日 HH时'; if (contrastOption === 'month') _formatterStr = 'MM月DD日 HH时';
} }
return moment(e).format(_formatterStr) return moment(e).format(_formatterStr);
} },
}, },
]; ];
xAxis.minInterval = 3600 * (1 * 1000); xAxis.minInterval = 3600 * (1 * 1000);
if (legendData) { if (legendData) {
restOption.legend = {...buildDefaultLegend({}), ...{data: legendData}}; restOption.legend = { ...buildDefaultLegend({}), ...{ data: legendData } };
} }
}; };
const returnMaxOrMinNumber = (dataSource, type) => { const returnMaxOrMinNumber = (dataSource, type) => {
let _obj = null; let _obj = null;
if (type === 'period' && dataSource?.[0]?.dataModel?.length) { if (type === 'period' && dataSource?.[0]?.dataModel?.length) {
let _length = dataSource?.[0]?.dataModel?.length; let _length = dataSource?.[0]?.dataModel?.length;
let _first = dataSource?.[0]?.dataModel[0]?.pt; let _first = dataSource?.[0]?.dataModel[0]?.pt;
let _last = dataSource?.[0]?.dataModel[_length - 1]?.pt; let _last = dataSource?.[0]?.dataModel[_length - 1]?.pt;
return ['展示时段: ', _first, _last, type] return ['展示时段: ', _first, _last, type];
} }
dataSource?.[0]?.dataModel.filter(item => item.pv !== null).forEach(item => { dataSource?.[0]?.dataModel
if (!_obj) { .filter((item) => item.pv !== null)
.forEach((item) => {
if (!_obj) {
_obj = item;
} else {
if (type === 'min') {
if (item.pv < _obj.pv) {
_obj = item; _obj = item;
} else { }
if (type === 'min') { }
if (item.pv < _obj.pv) { if (type === 'max') {
_obj = item if (item.pv > _obj.pv) {
} _obj = item;
} }
if (type === 'max') {
if (item.pv > _obj.pv) {
_obj = item
}
}
} }
}
}); });
let _value = []; let _value = [];
if (_obj?.pt) { if (_obj?.pt) {
_value = [moment(_obj.pt).valueOf(), _obj.pv]; _value = [moment(_obj.pt).valueOf(), _obj.pv];
let _img = type === 'max' ? maxIcon : minIconDownArrow; let _img = type === 'max' ? maxIcon : minIconDownArrow;
_value.push(_img); _value.push(_img);
_value.push(type); _value.push(type);
// 把最大值最小值都push进去,方便计算 // 把最大值最小值都push进去,方便计算
} }
return _value; return _value;
}; };
const handleGrid = (dataSource, needUnit, leftNum, rightNum, chartType) => { const handleGrid = (dataSource, needUnit, leftNum, rightNum, chartType) => {
// 如果是单曲线,_grid的top需要一行的高度,用来放置最值最小值 // 如果是单曲线,_grid的top需要一行的高度,用来放置最值最小值
let _base = 60; let _base = 60;
let _topForUnit = needUnit ? 20 : 0; let _topForUnit = needUnit ? 20 : 0;
return { return {
top: _base + _topForUnit, top: _base + _topForUnit,
left: leftNum === 1 ? 10 : leftNum * AXIS_WIDTH, // left: leftNum === 1 ? 10 : leftNum * AXIS_WIDTH,
right: rightNum === 0 ? 20 : rightNum * AXIS_WIDTH, left: 10,
bottom: 60, // right: rightNum === 0 ? 20 : rightNum * AXIS_WIDTH,
containLabel: true bottom: 60,
}; containLabel: true,
};
}; };
const renderItem = (params, api) => { const renderItem = (params, api) => {
let _base = params.dataIndex; let _base = params.dataIndex;
let _baseChartWidth = 10; let _baseChartWidth = 10;
let _numberStringWidth = _base === 1 && api.value(3) === 'max' ? String(api.value(4) || 0).length * _baseChartWidth : String(api.value(1)).length * _baseChartWidth; let _numberStringWidth =
let _left = 30; _base === 1 && api.value(3) === 'max'
let _baseWidth = 190 + _numberStringWidth; ? String(api.value(4) || 0).length * _baseChartWidth
let _imgWidth = 45; : String(api.value(1)).length * _baseChartWidth;
let _dateWidth = 128; let _left = 30;
let _imageX = _left + _base * _baseWidth; let _baseWidth = 190 + _numberStringWidth;
let _timeX = _left + _base * _baseWidth + _imgWidth; let _imgWidth = 45;
let _valueX = _left + _base * _baseWidth + _imgWidth + _dateWidth; let _dateWidth = 128;
let _color = api.value(3) === 'min' ? '#05C2BC' : '#1685FF'; let _imageX = _left + _base * _baseWidth;
let topValue = 25; let _timeX = _left + _base * _baseWidth + _imgWidth;
let _trimmer = -2; let _valueX = _left + _base * _baseWidth + _imgWidth + _dateWidth;
return { let _color = api.value(3) === 'min' ? '#05C2BC' : '#1685FF';
type: 'group', let topValue = 25;
children: [ let _trimmer = -2;
{ return {
type: 'image', type: 'group',
style: { children: [
image: api.value(2), {
textVerticalAlign: 'middle', type: 'image',
y: -6 style: {
}, image: api.value(2),
position: [_imageX, topValue] textVerticalAlign: 'middle',
}, y: -6,
{ },
type: 'text', position: [_imageX, topValue],
style: { },
text: moment(api.value(0)).format('YYYY-MM-DD HH:mm:ss') + ': ', {
textVerticalAlign: 'top' type: 'text',
}, style: {
position: [_timeX, topValue + _trimmer], text: moment(api.value(0)).format('YYYY-MM-DD HH:mm:ss') + ': ',
}, textVerticalAlign: 'top',
{ },
type: 'text', position: [_timeX, topValue + _trimmer],
style: { },
text: api.value(1), {
textVerticalAlign: 'middle', type: 'text',
fill: _color, style: {
font: 'bolder 16px cursive', text: api.value(1),
lineWidth: 10, textVerticalAlign: 'middle',
y: 3 fill: _color,
}, font: 'bolder 16px cursive',
position: [_valueX, topValue + _trimmer] lineWidth: 10,
} y: 3,
] },
} position: [_valueX, topValue + _trimmer],
},
],
};
}; };
const returnCustomSeries = (dataSource) => { const returnCustomSeries = (dataSource) => {
let _maxNumber = returnMaxOrMinNumber(dataSource, 'max'); let _maxNumber = returnMaxOrMinNumber(dataSource, 'max');
let _minNumber = returnMaxOrMinNumber(dataSource, 'min'); let _minNumber = returnMaxOrMinNumber(dataSource, 'min');
// let _period = returnMaxOrMinNumber(dataSource, 'period'); // let _period = returnMaxOrMinNumber(dataSource, 'period');
// 需要将最大值最小分别传入,后续计算图例位置需要,先min后max // 需要将最大值最小分别传入,后续计算图例位置需要,先min后max
let _max = _maxNumber[1]; let _max = _maxNumber[1];
let _min = _minNumber[1]; let _min = _minNumber[1];
[_maxNumber, _minNumber].forEach(item => { [_maxNumber, _minNumber].forEach((item) => {
item.push(_min); item.push(_min);
item.push(_max); item.push(_max);
}) });
return { return {
name: '自定义', name: '自定义',
type: 'custom', type: 'custom',
renderItem: renderItem, renderItem: renderItem,
data: [_minNumber, _maxNumber], data: [_minNumber, _maxNumber],
} };
}; };
/** /**
* 图表配置项生成 * 图表配置项生成
...@@ -818,172 +876,206 @@ const returnCustomSeries = (dataSource) => { ...@@ -818,172 +876,206 @@ const returnCustomSeries = (dataSource) => {
* @param {any} config 额外配置信息 * @param {any} config 额外配置信息
*/ */
const optionGenerator = (dataSource, cusOption, contrast, contrastOption, smooth, config, lineDataType = '') => { const optionGenerator = (
const { dataSource,
needUnit, cusOption,
curveCenter, contrast,
nameWithSensor, contrastOption,
showGridLine, smooth,
showMarkLine, config,
showPoint, lineDataType = '',
deviceAlarmSchemes, ) => {
chartType, const {
showBoxOption, needUnit,
restOption, curveCenter,
special nameWithSensor,
} = handleDefault(config, cusOption); showGridLine,
const {leftNum, rightNum, yAxis} = handleYAxis({dataSource, needUnit, curveCenter, showGridLine}); showMarkLine,
const grid = handleGrid(dataSource, needUnit, leftNum, rightNum, chartType); showPoint,
let {xAxis, series, visualMap} = returnXAxis({ deviceAlarmSchemes,
dataSource, chartType,
contrast, showBoxOption,
contrastOption, restOption,
nameWithSensor, special,
showMarkLine, } = handleDefault(config, cusOption);
deviceAlarmSchemes, const { leftNum, rightNum, yAxis } = handleYAxis({
showPoint, dataSource,
smooth, needUnit,
restOption, curveCenter,
special showGridLine,
}); });
decorateAxisGridLine(xAxis, showGridLine); const grid = handleGrid(dataSource, needUnit, leftNum, rightNum, chartType);
const tooltipTimeFormat = !contrast let { xAxis, series, visualMap } = returnXAxis({
? 'YYYY-MM-DD HH:mm:ss' dataSource,
: contrastOption === 'day' contrast,
? 'HH:mm' contrastOption,
: 'DD HH:mm'; nameWithSensor,
let tooltip = {}; showMarkLine,
// 增加箱线图的逻辑,单曲线才存在该逻辑 deviceAlarmSchemes,
if (chartType && showBoxOption) { showPoint,
if (chartType === 'boxChart' && lineDataType === '特征曲线') { smooth,
const otherData = restOption,
dataSource?.[0]?.dataModel.map((item) => { special,
const {firstPV, lastPV, maxPV, minPV, pt} = item; });
return [moment(pt).valueOf(), firstPV, lastPV, minPV, maxPV]; decorateAxisGridLine(xAxis, showGridLine);
}) || []; //当存在othersData的时候,只是单曲线 const tooltipTimeFormat = !contrast
xAxis = {type: 'time'}; ? 'YYYY-MM-DD HH:mm:ss'
decorateAxisGridLine(xAxis, showGridLine); : contrastOption === 'day'
let unit = []; ? 'HH:mm'
series = series.map((item) => { : 'DD HH:mm';
if (item.unit) unit.push(item.unit); let tooltip = {};
item.areaStyle = null; // 增加箱线图的逻辑,单曲线才存在该逻辑
return {...item, showSymbol: false}; if (chartType && showBoxOption) {
}); if (chartType === 'boxChart' && lineDataType === '特征曲线') {
series.push({ const otherData =
type: 'candlestick', dataSource?.[0]?.dataModel.map((item) => {
name: '箱线图', const { firstPV, lastPV, maxPV, minPV, pt } = item;
symbol: 'none', return [moment(pt).valueOf(), firstPV, lastPV, minPV, maxPV];
data: otherData, }) || []; //当存在othersData的时候,只是单曲线
itemStyle: { xAxis = { type: 'time' };
color: '#FFA200', decorateAxisGridLine(xAxis, showGridLine);
color0: '#44CD00', let unit = [];
borderColor: '#FFA200', series = series.map((item) => {
borderColor0: '#44CD00', if (item.unit) unit.push(item.unit);
}, item.areaStyle = null;
}); return { ...item, showSymbol: false };
tooltip = tooltipAccessor(unit); });
} series.push({
if (chartType === 'lineChart' || lineDataType === '原始曲线') { type: 'candlestick',
let _maxData = []; name: '箱线图',
let _minData = []; symbol: 'none',
let _currentYear = moment().format('YYYY'); data: otherData,
const formatStr = itemStyle: {
contrastOption === 'day' color: '#FFA200',
? `${_currentYear}-01-01 HH:mm:00` color0: '#44CD00',
: `${_currentYear}-01-DD HH:mm:00`; // 用来做同期对比,把日期拉到同一区间 borderColor: '#FFA200',
let _maxValues = []; borderColor0: '#44CD00',
/** },
* 生成泳道图,分两种情况 });
* 1. 当最大值最小值都是正数时; tooltip = tooltipAccessor(unit);
* 2. 当最大值小于零时(此时,最小值一定小于零); }
*/ if (chartType === 'lineChart' || lineDataType === '原始曲线') {
dataSource?.[0]?.dataModel.forEach((item) => { let _maxData = [];
const {firstPV, lastPV, maxPV, minPV, pt} = item; let _minData = [];
_maxValues.push(maxPV); let _currentYear = moment().format('YYYY');
let time = contrast ? moment(pt).format(formatStr) : pt; const formatStr =
_maxData.push([moment(time).valueOf(), (maxPV > 0 ? maxPV - minPV : minPV - maxPV).toFixed(2)]); contrastOption === 'day'
_minData.push([moment(time).valueOf(), maxPV > 0 ? minPV : maxPV]); ? `${_currentYear}-01-01 HH:mm:00`
}); //当存在othersData的时候,只是单曲线 : `${_currentYear}-01-DD HH:mm:00`; // 用来做同期对比,把日期拉到同一区间
// xAxis = {type: 'category', data: series[0].data.map(item => moment(item[0]).format('YYYY-MM-DD HH:mm:ss'))}; let _maxValues = [];
xAxis = {type: 'time'}; /**
decorateAxisGridLine(xAxis, showGridLine); * 生成泳道图,分两种情况
let _unit = ''; * 1. 当最大值最小值都是正数时;
series = series.map((item) => { * 2. 当最大值小于零时(此时,最小值一定小于零);
_unit = item.unit ?? ''; */
item.areaStyle = null; dataSource?.[0]?.dataModel.forEach((item) => {
return {...item, showSymbol: false}; const { firstPV, lastPV, maxPV, minPV, pt } = item;
}); _maxValues.push(maxPV);
[[..._minData], [..._maxData]].forEach((item, index) => { let time = contrast ? moment(pt).format(formatStr) : pt;
series.push({ _maxData.push([
name: index === 0 ? '周期最小值' : '周期最大值', moment(time).valueOf(),
type: 'line', (maxPV > 0 ? maxPV - minPV : minPV - maxPV).toFixed(2),
data: item, ]);
lineStyle: { _minData.push([moment(time).valueOf(), maxPV > 0 ? minPV : maxPV]);
opacity: 0, }); //当存在othersData的时候,只是单曲线
}, // xAxis = {type: 'category', data: series[0].data.map(item => moment(item[0]).format('YYYY-MM-DD HH:mm:ss'))};
...(index !== 0 xAxis = { type: 'time' };
? { decorateAxisGridLine(xAxis, showGridLine);
areaStyle: { let _unit = '';
color: series?.[0]?.itemStyle?.color ?? '#65a0d1', series = series.map((item) => {
opacity: 0.2, _unit = item.unit ?? '';
}, item.areaStyle = null;
} return { ...item, showSymbol: false };
: {}), });
stack: 'confidence-band', [[..._minData], [..._maxData]].forEach((item, index) => {
symbol: 'none', series.push({
}); name: index === 0 ? '周期最小值' : '周期最大值',
}); type: 'line',
tooltip = { data: item,
trigger: 'axis', lineStyle: {
formatter: (e) => { opacity: 0,
return `<div> },
...(index !== 0
? {
areaStyle: {
color: series?.[0]?.itemStyle?.color ?? '#65a0d1',
opacity: 0.2,
},
}
: {}),
stack: 'confidence-band',
symbol: 'none',
});
});
tooltip = {
trigger: 'axis',
formatter: (e) => {
return `<div>
${headTemplate(e[0])} ${headTemplate(e[0])}
<div> <div>
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<span style="${isMobile() ? 'width: ' + handlePx(90, 'px') + ';overflow:hidden;text-overflow:ellipsis;white-space:nowrap' : ''}">${e[0].seriesName <span style="${isMobile()
}</span><span style="display:inline-block;">:</span> ? 'width: ' +
<span style="color: ${COLOR.NORMAL};margin: 0 ${handlePx(5, 'px')} 0 auto;">${e[0]?.value?.[1] ?? '-' handlePx(90, 'px') +
}</span> ';overflow:hidden;text-overflow:ellipsis;white-space:nowrap'
: ''
}">${e[0].seriesName
}</span><span style="display:inline-block;">:</span>
<span style="color: ${COLOR.NORMAL};margin: 0 ${handlePx(
5,
'px',
)} 0 auto;">${e[0]?.value?.[1] ?? '-'}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span> <span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div> </div>
<div style="display: ${lineDataType === '特征曲线' ? 'flex' : 'none'}; align-items: center;"> <div style="display: ${lineDataType === '特征曲线' ? 'flex' : 'none'
}; align-items: center;">
<span>周期最小值</span><span style="display:inline-block;">:</span> <span>周期最小值</span><span style="display:inline-block;">:</span>
<span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${e?.[1]?.value?.[1] ?? '-' <span style="color: ${COLOR.AVG};margin: 0 ${handlePx(
}</span> 5,
'px',
)} 0 auto;">${e?.[1]?.value?.[1] ?? '-'}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span> <span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div> </div>
<div style="display: ${lineDataType === '特征曲线' ? 'flex' : 'none'}; align-items: center;"> <div style="display: ${lineDataType === '特征曲线' ? 'flex' : 'none'
}; align-items: center;">
<span>周期最大值</span><span style="display:inline-block;">:</span> <span>周期最大值</span><span style="display:inline-block;">:</span>
<span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${_maxValues[e?.[2]?.dataIndex] ?? '-' <span style="color: ${COLOR.AVG};margin: 0 ${handlePx(
}</span> 5,
'px',
)} 0 auto;">${_maxValues[e?.[2]?.dataIndex] ?? '-'}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span> <span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div> </div>
</div> </div>
</div>`; </div>`;
}, },
}; };
}
// 单曲线需要标记最大值、最小值的情况下,需要增加自定义的series,将最大最小值显示在图表上
if (dataSource?.[0]?.dataModel?.length && chartType === 'lineChart') {
let _customSeries = returnCustomSeries(dataSource);
series.push(_customSeries)
}
} else {
tooltip = tooltipAccessor(series.map(item => item.unit), {contrastOption, contrast});
} }
tooltip.timeFormat = tooltipTimeFormat; // 单曲线需要标记最大值、最小值的情况下,需要增加自定义的series,将最大最小值显示在图表上
let _legendData = series.filter(item => !['周期最大值', '周期最小值', '自定义'].includes(item.name)).map(item => item.name); if (dataSource?.[0]?.dataModel?.length && chartType === 'lineChart') {
assignOptions(restOption, xAxis, _legendData, chartType, contrast, contrastOption); let _customSeries = returnCustomSeries(dataSource);
let _options = { series.push(_customSeries);
yAxis, }
grid, } else {
xAxis, tooltip = tooltipAccessor(
series, series.map((item) => item.unit),
tooltip, { contrastOption, contrast },
visualMap, );
...restOption, }
}; tooltip.timeFormat = tooltipTimeFormat;
return _options let _legendData = series
.filter((item) => !['周期最大值', '周期最小值', '自定义'].includes(item.name))
.map((item) => item.name);
assignOptions(restOption, xAxis, _legendData, chartType, contrast, contrastOption);
let _options = {
yAxis,
grid,
xAxis,
series,
tooltip,
visualMap,
...restOption,
};
return _options;
}; };
export default optionGenerator; export default optionGenerator;
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