Commit a7f8ce51 authored by 陈龙's avatar 陈龙

feat: 标题被legend覆盖问题的优化;相同数据的轴合并

parent e3b48dbb
......@@ -18,8 +18,6 @@ export const buildDefaultLegend = (option) => {
textStyle: {
padding: [0, 0, 0, 4],
color: '#2d2d2d',
width: 120,
overflow:'truncate'
},
tooltip:{
show:true
......
import React, { memo, useMemo } from 'react';
import React, {memo, useMemo} from 'react';
import _ from 'lodash';
import { BasicChart } from '@wisdom-components/basicchart';
import {BasicChart} from '@wisdom-components/basicchart';
import PandaEmpty from '@wisdom-components/empty';
import optionGenerator from './utils';
const ChartTitle = ({ prefixCls, title, unit }) => {
const cls = `${prefixCls}-grid-item-title`;
return (
<div className={cls}>
<span className={`${cls}_text`}>{title}</span>
{unit && <span className={`${cls}_unit`}>(单位:{unit}</span>}
</div>
);
const ChartTitle = ({prefixCls, title, unit}) => {
const cls = `${prefixCls}-grid-item-title`;
return (
<div className={cls}>
<span className={`${cls}_text`}>{title}</span>
{unit && <span className={`${cls}_unit`}>(单位:{unit}</span>}
</div>
);
};
const GridChart = memo((props) => {
const {
dataSource,
contrast = false,
contrastOption = 'day',
smooth = true,
curveCenter,
} = props;
const { prefixCls } = props;
const {
dataSource,
contrast = false,
contrastOption = 'day',
smooth = true,
curveCenter,
} = props;
const {prefixCls} = props;
const gridData = useMemo(() => {
const grids = dataSource.reduce((pre, item, index) => {
const { sensorName, deviceType } = item;
const key = `${deviceType}_${sensorName}`; // 同设备类型同指标才在同一组
let grid = pre.find((g) => g.key === key);
if (!grid) {
const restProp = _.pick(item, ['equipmentName', 'sensorName', 'stationCode', 'unit']);
grid = {
key: key,
list: [],
...restProp,
};
pre.push(grid);
}
grid.list.push(item);
return pre;
}, []);
return grids;
}, [dataSource]);
const gridData = useMemo(() => {
const grids = dataSource.reduce((pre, item, index) => {
const {sensorName, deviceType} = item;
const key = `${deviceType}_${sensorName}`; // 同设备类型同指标才在同一组
let grid = pre.find((g) => g.key === key);
if (!grid) {
const restProp = _.pick(item, ['equipmentName', 'sensorName', 'stationCode', 'unit']);
grid = {
key: key,
list: [],
...restProp,
};
pre.push(grid);
}
grid.list.push(item);
return pre;
}, []);
return grids;
}, [dataSource]);
const options = useMemo(() => {
let _options = gridData.map((item) => {
const { key, list, equipmentName, sensorName, stationCode, unit } = item;
const cusOption = {
title: {
show: true,
// text: `{prefix|}{t|${sensorName}}${unit ? '{suffix|(单位:' + unit + ')}' : ''}`,
text: ' ',
textStyle: {
width: 200,
overflow: 'truncate',
},
},
legend: {
// orient: 'vertical',
itemGap: 10,
padding: [0, 0, 0, 200],
textStyle: {
width: 120,
overflow: 'truncate',
},
},
};
const option = optionGenerator(list, cusOption, contrast, contrastOption, smooth, {
curveCenter,
nameWithSensor: false,
showGridLine: true,
});
return {
key,
option: option,
};
});
return _options;
}, [gridData, smooth, curveCenter]);
const options = useMemo(() => {
let _options = gridData.map((item) => {
const {key, list, equipmentName, sensorName, stationCode, unit} = item;
let max = 300;
// 5:左侧竖条的宽度;10:标题部分的左侧margin;
// sensorName长度*单个宽度16.7;5:单位部分的左侧margin;
// 91:单位部分的宽度(格式固定,宽度相对固定)
let maxTitleLength = 5 + 10 + sensorName.length * 16.7 + 5 + 91;
let finalLength = maxTitleLength > max ? max : maxTitleLength
const cusOption = {
title: {
show: true,
// text: `{prefix|}{t|${sensorName}}${unit ? '{suffix|(单位:' + unit + ')}' : ''}`,
text: ' ',
textStyle: {
width: finalLength,
overflow: 'truncate',
},
},
legend: {
// orient: 'vertical',
itemGap: 10,
padding: [0, 0, 0, finalLength],
textStyle: {
width: 120,
overflow: 'truncate',
},
},
};
const option = optionGenerator(list, cusOption, contrast, contrastOption, smooth, {
curveCenter,
nameWithSensor: false,
showGridLine: true,
isMultiple: gridData.length > 1
});
// 在多图表里,底部缩放条的高度减小
return {
key,
option: option,
};
});
return _options;
}, [gridData, smooth, curveCenter]);
return (
<div className={`${prefixCls}-grid`}>
{options.map((item, index) => {
const { sensorName, unit } = gridData[index];
const isEmpty =
!item.option.series.length ||
!item.option.series.find((e) => e.data && e.data.length > 0);
return (
<div
key={item.key}
className={`${prefixCls}-grid-item`}
style={{
height: gridData.length === 1 ? '100%' : '',
width: gridData.length === 1 ? '100%' : '',
}}
>
<div className={`${prefixCls}-grid-item-wrap`}>
<ChartTitle prefixCls={prefixCls} title={sensorName} unit={unit} />
{isEmpty ? (
<PandaEmpty />
) : (
<BasicChart
style={{ width: '100%', height: '100%' }}
option={item.option}
notMerge
/>
)}
</div>
</div>
);
})}
</div>
);
return (
<div className={`${prefixCls}-grid`}>
{options.map((item, index) => {
const {sensorName, unit} = gridData[index];
const isEmpty =
!item.option.series.length ||
!item.option.series.find((e) => e.data && e.data.length > 0);
return (
<div
key={item.key}
className={`${prefixCls}-grid-item`}
style={{
height: gridData.length === 1 ? '100%' : '',
width: gridData.length === 1 ? '100%' : '',
}}
>
<div className={`${prefixCls}-grid-item-wrap`}>
<ChartTitle prefixCls={prefixCls} title={sensorName} unit={unit}/>
{isEmpty ? (
<PandaEmpty/>
) : (
<BasicChart
style={{width: '100%', height: '100%'}}
option={item.option}
notMerge
/>
)}
</div>
</div>
);
})}
</div>
);
});
export default GridChart;
......@@ -4,25 +4,25 @@ import { MobileHistoryChart } from "../mobile";
const deviceParams = [
/*10.182*/
/* {
/* {
deviceCode: 'EGBF00000141',
// sensors: '进水压力,出水瞬时流量,出水累计流量',
sensors: '进水压力',
deviceType: '熊猫二供泵房',
pointAddressID: 208,
}, */
},*/
/* {
"deviceCode": "SYJ00000008",
"sensors": "瞬时流量",
"deviceType": "水源井"
},
},*/
{
deviceCode: 'EGJZ00000197',
sensors: '进水压力,出水压力,出水瞬时流量,出水累计流量',
// sensors: '1#变频器运行频率',
deviceType: '二供机组',
// pointAddressID: 208,
}, */
},
/* {
deviceCode: 'EGJZ00000198',
sensors: '进水压力,出水压力,出水瞬时流量,出水累计流量',
......@@ -63,7 +63,7 @@ const deviceParams = [
"sensors": "瞬时流量",
"deviceType": "水厂"
}, */
{
/* {
"deviceCode": "JFJ00000001",
"sensors": "沉淀池投矾量瞬时,",
"deviceType": "加矾间"
......@@ -92,7 +92,7 @@ const deviceParams = [
deviceCode: 'EGJZ00000027',
sensors: '2#变频器运行频率',
deviceType: '二供机组',
},
},*/
/*确山*/
/*泵3状态*/
/* {
......
import moment from 'moment';
import _, { isArray } from 'lodash';
import _, {isArray} from 'lodash';
import maxIcon from './assets/最大实心.svg';
import minIcon from './assets/最小实心.svg';
import minIconDownArrow from './assets/最小实心箭头朝下.svg';
import lineChart from '@wisdom-components/basicchart/es/LineChart';
import { offset } from 'highcharts';
/** 轴宽度, 用于计算多轴显示时, 轴线偏移和绘图区域尺寸 */
const AXIS_WIDTH = 40;
const COLOR = {
NORMAL: '#1685FF',
UPER: '#fa8c16',
UPUPER: '#FF0000',
// LOWER: '#13c2c2',
// LOWLOWER: '#2f54eb',
LOWER: '#fa8c16',
LOWLOWER: '#FF0000',
AVG: '#00B8B1',
NORMAL: '#1685FF',
UPER: '#fa8c16',
UPUPER: '#FF0000',
// LOWER: '#13c2c2',
// LOWLOWER: '#2f54eb',
LOWER: '#fa8c16',
LOWLOWER: '#FF0000',
AVG: '#00B8B1',
};
const isMobile = () => {
const userAgent = navigator.userAgent.toLowerCase();
if (/ipad|iphone|midp|rv:1.2.3.4|ucweb|android|windows ce|windows mobile/.test(userAgent)) {
return true;
}
return false;
const userAgent = navigator.userAgent.toLowerCase();
if (/ipad|iphone|midp|rv:1.2.3.4|ucweb|android|windows ce|windows mobile/.test(userAgent)) {
return true;
}
return false;
};
const returnRem = (num, base = 375) => num * (base / 375);
export const handlePx = (num, unit = '') => {
const _isMobile = isMobile();
const _base = document.body.clientWidth;
let _num = _isMobile ? `${returnRem(num, _base)}` : `${num}`;
return unit ? `${_num}${unit}` : Number(_num);
const _isMobile = isMobile();
const _base = document.body.clientWidth;
let _num = _isMobile ? `${returnRem(num, _base)}` : `${num}`;
return unit ? `${_num}${unit}` : Number(_num);
};
const PC_OPTION = {
markPoint: {
padding: [2, 12],
lineHeight: 22,
backgroundColor:
window.globalConfig && window.globalConfig && window.globalConfig.variableTheme?.primaryColor
? window.globalConfig.variableTheme.primaryColor
: '#0087F7',
borderWidth: 1,
},
fontSize: 16,
fontColor: '#ffffff',
dataZoomHeight: 28,
markPoint: {
padding: [2, 12],
lineHeight: 22,
backgroundColor:
window.globalConfig && window.globalConfig && window.globalConfig.variableTheme?.primaryColor
? window.globalConfig.variableTheme.primaryColor
: '#0087F7',
borderWidth: 1,
},
fontSize: 16,
fontColor: '#ffffff',
dataZoomHeight: 28,
};
const MOBILE_OPTION = {
markPoint: {
padding: [2, 6],
lineHeight: 18,
backgroundColor: 'rgba(255,255,255,0.6)',
borderWidth: 0,
},
fontSize: handlePx(12),
fontColor: '#0087F7',
dataZoomHeight: 20,
markPoint: {
padding: [2, 6],
lineHeight: 18,
backgroundColor: 'rgba(255,255,255,0.6)',
borderWidth: 0,
},
fontSize: handlePx(12),
fontColor: '#0087F7',
dataZoomHeight: 20,
};
const currentOption = isMobile() ? MOBILE_OPTION : PC_OPTION;
/**
......@@ -68,13 +67,13 @@ const currentOption = isMobile() ? MOBILE_OPTION : PC_OPTION;
* @returns
*/
const nameFormatter = (data, contrast, contrastOption, nameWithSensor) => {
const { equipmentName, sensorName, unit, dataModel, dateFrom, dateTo } = data;
let name = nameWithSensor ? `${equipmentName}-${sensorName}` : equipmentName;
if (contrast) {
const time = dateFrom.slice(0, contrastOption === 'day' ? 10 : 7).replace(/-/g, '');
name = `${name}-${time}`;
}
return name;
const {equipmentName, sensorName, unit, dataModel, dateFrom, dateTo} = data;
let name = nameWithSensor ? `${equipmentName}-${sensorName}` : equipmentName;
if (contrast) {
const time = dateFrom.slice(0, contrastOption === 'day' ? 10 : 7).replace(/-/g, '');
name = `${name}-${time}`;
}
return name;
};
/**
......@@ -86,14 +85,14 @@ const nameFormatter = (data, contrast, contrastOption, nameWithSensor) => {
* @returns 图表系列数据, [[DateTime, value]]
*/
const dataAccessor = (data, contrast, contrastOption) => {
const dataModel = data?.dataModel ?? [];
const formatStr = contrastOption === 'day' ? '2020-01-01 HH:mm:ss' : '2020-01-DD HH:mm:ss';
return dataModel
.filter((item) => item.sensorName !== '是否在线')
.map((item) => {
const time = contrast ? moment(item.pt).format(formatStr) : item.pt;
return [moment(time).valueOf(), item.pv];
});
const dataModel = data?.dataModel ?? [];
const formatStr = contrastOption === 'day' ? '2020-01-01 HH:mm:ss' : '2020-01-DD HH:mm:ss';
return dataModel
.filter((item) => item.sensorName !== '是否在线')
.map((item) => {
const time = contrast ? moment(item.pt).format(formatStr) : item.pt;
return [moment(time).valueOf(), item.pv];
});
};
/**
......@@ -103,8 +102,8 @@ const dataAccessor = (data, contrast, contrastOption) => {
* @returns Null/areaStyle, 为null显示曲线图, 为areaStyle对象显示为面积图.
*/
const areaStyleFormatter = (data) => {
const { sensorName } = data;
return sensorName && sensorName.indexOf('流量') > -1 ? {} : null;
const {sensorName} = data;
return sensorName && sensorName.indexOf('流量') > -1 ? {} : null;
};
/**
......@@ -114,100 +113,100 @@ const areaStyleFormatter = (data) => {
* @returns
*/
const minMax = (data) => {
const { dataModel } = data;
let min = Number.MAX_SAFE_INTEGER;
let max = Number.MIN_SAFE_INTEGER;
dataModel.forEach((item) => {
min = Math.min(min, item.pv ?? 0);
max = Math.max(max, item.pv ?? 0);
});
return [min, max];
const {dataModel} = data;
let min = Number.MAX_SAFE_INTEGER;
let max = Number.MIN_SAFE_INTEGER;
dataModel.forEach((item) => {
min = Math.min(min, item.pv ?? 0);
max = Math.max(max, item.pv ?? 0);
});
return [min, max];
};
const markLineItem = (name, value, color) => {
return {
name: name,
yAxis: value,
value: value,
lineStyle: {
color: color || '#000',
},
label: {
position: 'insideEndTop',
color: color || '#000',
formatter: function () {
return `${name}:${value}`;
},
},
};
return {
name: name,
yAxis: value,
value: value,
lineStyle: {
color: color || '#000',
},
label: {
position: 'insideEndTop',
color: color || '#000',
formatter: function () {
return `${name}:${value}`;
},
},
};
};
export const alarmMarkLine = (dataItem, index, dataSource, schemes) => {
// 只有一个数据曲线时显示markline
if (!dataItem || !schemes || dataSource.length !== 1) return {};
const { deviceType, stationCode, sensorName, decimalPoint } = dataItem;
const curSchemes = schemes.filter(
(item) =>
item.deviceCode === stationCode &&
item.sensorName === sensorName &&
item.valueType === '直接取值',
);
const data = [];
curSchemes.forEach((scheme) => {
const { hLimit, hhLimit, lLimit, llLimit } = scheme;
lLimit !== null && lLimit !== void 0 && data.push(markLineItem('低限', lLimit, '#fa8c16'));
hLimit !== null && hLimit !== void 0 && data.push(markLineItem('高限', hLimit, '#fa8c16'));
llLimit !== null && llLimit !== void 0 && data.push(markLineItem('低低限', llLimit, '#FF0000'));
hhLimit !== null && hhLimit !== void 0 && data.push(markLineItem('高高限', hhLimit, '#FF0000'));
});
data.push({
name: '平均线',
type: 'average',
lineStyle: {
color: '#00b8b1',
type: 'solid',
},
label: {
position: 'insideEndTop',
color: '#00b8b1',
formatter: function (param) {
return `平均值:${param.value}`;
},
},
});
return {
symbol: ['none', 'none'],
data,
};
// 只有一个数据曲线时显示markline
if (!dataItem || !schemes || dataSource.length !== 1) return {};
const {deviceType, stationCode, sensorName, decimalPoint} = dataItem;
const curSchemes = schemes.filter(
(item) =>
item.deviceCode === stationCode &&
item.sensorName === sensorName &&
item.valueType === '直接取值',
);
const data = [];
curSchemes.forEach((scheme) => {
const {hLimit, hhLimit, lLimit, llLimit} = scheme;
lLimit !== null && lLimit !== void 0 && data.push(markLineItem('低限', lLimit, '#fa8c16'));
hLimit !== null && hLimit !== void 0 && data.push(markLineItem('高限', hLimit, '#fa8c16'));
llLimit !== null && llLimit !== void 0 && data.push(markLineItem('低低限', llLimit, '#FF0000'));
hhLimit !== null && hhLimit !== void 0 && data.push(markLineItem('高高限', hhLimit, '#FF0000'));
});
data.push({
name: '平均线',
type: 'average',
lineStyle: {
color: '#00b8b1',
type: 'solid',
},
label: {
position: 'insideEndTop',
color: '#00b8b1',
formatter: function (param) {
return `平均值:${param.value}`;
},
},
});
return {
symbol: ['none', 'none'],
data,
};
};
export const minMaxMarkPoint = (dataItem, index, dataSource) => {
const _isMobile = isMobile();
// 只有一个数据曲线时显示markline
if (!dataItem || dataSource.length !== 1) return {};
const data = [
{
type: 'min',
value: null,
name: '最小: ',
symbol: `image://${minIcon}`,
symbolOffset: [0, 18],
},
{
type: 'max',
value: null,
name: '最大: ',
symbol: `image://${maxIcon}`,
symbolOffset: [0, -16],
},
];
return {
label: {
show: false,
},
symbolSize: [49, 31],
data,
};
const _isMobile = isMobile();
// 只有一个数据曲线时显示markline
if (!dataItem || dataSource.length !== 1) return {};
const data = [
{
type: 'min',
value: null,
name: '最小: ',
symbol: `image://${minIcon}`,
symbolOffset: [0, 18],
},
{
type: 'max',
value: null,
name: '最大: ',
symbol: `image://${maxIcon}`,
symbolOffset: [0, -16],
},
];
return {
label: {
show: false,
},
symbolSize: [49, 31],
data,
};
};
/**
......@@ -216,27 +215,27 @@ export const minMaxMarkPoint = (dataItem, index, dataSource) => {
* @param {any} axis
*/
export const decorateAxisGridLine = (axis, showGrid) => {
if (!axis) return;
axis.minorTick = {
lineStyle: {
color: '#e2e2e2',
},
...(axis.minorTick || {}),
show: showGrid,
splitNumber: 2,
};
axis.minorSplitLine = {
lineStyle: {
color: '#e2e2e2',
type: 'dashed',
},
...(axis.minorSplitLine || {}),
show: showGrid,
};
axis.splitLine = {
...(axis.splitLine || {}),
show: showGrid,
};
if (!axis) return;
axis.minorTick = {
lineStyle: {
color: '#e2e2e2',
},
...(axis.minorTick || {}),
show: showGrid,
splitNumber: 2,
};
axis.minorSplitLine = {
lineStyle: {
color: '#e2e2e2',
type: 'dashed',
},
...(axis.minorSplitLine || {}),
show: showGrid,
};
axis.splitLine = {
...(axis.splitLine || {}),
show: showGrid,
};
};
/**
......@@ -245,626 +244,640 @@ export const decorateAxisGridLine = (axis, showGrid) => {
* @param {any} dataItem
*/
export const offlineArea = (dataItem) => {
if (!dataItem) return {};
const { dataModel } = dataItem;
let datas = new Array();
let offlineData = [];
let hasOffline = false;
dataModel.forEach((item, index) => {
if (!item.pv && !hasOffline) {
offlineData = [
{
name: '离线',
xAxis: new Date(item.pt),
label: { show: !datas?.length },
if (!dataItem) return {};
const {dataModel} = dataItem;
let datas = new Array();
let offlineData = [];
let hasOffline = false;
dataModel.forEach((item, index) => {
if (!item.pv && !hasOffline) {
offlineData = [
{
name: '离线',
xAxis: new Date(item.pt),
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,
},
];
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配置
export const buildDefaultLegend = (option) => {
const { title } = option;
let paddingRight = 0;
if (title && title.show) paddingRight = 80; // 给标题留够空间
return {
show: true,
right: 20,
top: 20,
icon: 'rect',
itemWidth: 14,
itemHeight: 8,
itemGap: 20,
padding: [0, 0, 0, paddingRight],
textStyle: {
padding: [0, 0, 0, 4],
color: '#2d2d2d',
},
};
data: datas,
};
};
// tooltip 模板
const headTemplate = (param, opt) => {
if (!param) return '';
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 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>`;
if (!param) return '';
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 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) => {
if (!param || param.seriesName === '自定义') return '';
const { value, encode } = param;
// const val = value[encode.y[0]];
const _unit = unit || '';
const color = '#008CFF';
if (!isArray(value))
return ` <div style="display: flex; align-items: center;">
if (!param || param.seriesName === '自定义') return '';
const {value, encode} = param;
// const val = value[encode.y[0]];
const _unit = unit || '';
const color = '#008CFF';
if (!isArray(value))
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>
? '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};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value?.toFixed(3) ?? '-'
}</span>
}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div>`;
return param.componentSubType !== 'candlestick'
? `<div style="display: flex; align-items: center;">
return param.componentSubType !== 'candlestick'
? `<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>
? '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>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div>`
: `
: `
<div style="display: flex; align-items: center;">
<span>首值</span><span style="display:inline-block;">:</span>
<span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[1] ?? '-'
}</span>
}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div>
<div style="display: flex; align-items: center;">
<span>尾值</span><span style="display:inline-block;">:</span>
<span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[2] ?? '-'
}</span>
}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div>
<div style="display: flex; align-items: center;">
<span>周期最小值</span><span style="display:inline-block;">:</span>
<span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[3] ?? '-'
}</span>
}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div>
<div style="display: flex; align-items: center;">
<span>周期最大值</span><span style="display:inline-block;">:</span>
<span style="color: ${COLOR.AVG};margin: 0 ${handlePx(5, 'px')} 0 auto;">${value[4] ?? '-'
}</span>
}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div>
`;
};
const tooltipAccessor = (unit, contrastOption) => {
return {
formatter: function (params, ticket, callback) {
let tooltipHeader = '';
let tooltipContent = '';
if (isArray(params)) {
tooltipHeader = headTemplate(params[0], contrastOption);
params.forEach((param) => {
tooltipContent += seriesTemplate(param, unit?.[param?.seriesIndex]);
});
} else {
tooltipHeader = headTemplate(params, contrastOption);
tooltipContent += seriesTemplate(params, unit?.[params?.seriesIndex]);
}
return `
return {
formatter: function (params, ticket, callback) {
let tooltipHeader = '';
let tooltipContent = '';
if (isArray(params)) {
tooltipHeader = headTemplate(params[0], contrastOption);
params.forEach((param) => {
tooltipContent += seriesTemplate(param, unit?.[param?.seriesIndex]);
});
} else {
tooltipHeader = headTemplate(params, contrastOption);
tooltipContent += seriesTemplate(params, unit?.[params?.seriesIndex]);
}
return `
<div>
${tooltipHeader}
<div>${tooltipContent}</div>
</div>
`;
},
};
},
};
};
// 处理特殊业务:1. 频率;2. 是否在线(暂时不上)
const handleSpecial1 = (special, dataSource) => {
// 频率部分的业务
const _colorMap = {
变频: '#1685ff',
工频: '#00d0c7',
运行: '#1685ff',
故障: '#ff6b37',
停止: '#666666',
};
let _special1 = special?.special1 ?? {};
let _valDesc = _special1?.valDesc ?? {};
let _data = dataSource.find((item) => item.sensorName === _special1.name);
let _markAreaData = [];
let _pieces =
_data?.dataModel
?.reduce((final, cur, index) => {
let _pt = moment(cur.pt).valueOf();
let _length = final.length;
if (_colorMap[_valDesc[cur.pv]]) {
if (_length === 0) {
final.push({
lte: _pt,
gte: 0,
color: _colorMap[_valDesc[cur.pv]],
value: cur.pv,
text: _valDesc[cur.pv],
});
} else {
if (cur.pv === final[_length - 1].value) {
final[_length - 1].lte = _pt;
} else {
final.push({
lte: _pt,
gte: _pt,
color: _colorMap[_valDesc[cur.pv]],
value: cur.pv,
text: _valDesc[cur.pv],
});
}
}
}
return final;
}, [])
.map((item) => {
_markAreaData.push([
{
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,
// 频率部分的业务
const _colorMap = {
变频: '#1685ff',
工频: '#00d0c7',
运行: '#1685ff',
故障: '#ff6b37',
停止: '#666666',
};
let _special1 = special?.special1 ?? {};
let _valDesc = _special1?.valDesc ?? {};
let _data = dataSource.find((item) => item.sensorName === _special1.name);
let _markAreaData = [];
let _pieces =
_data?.dataModel
?.reduce((final, cur, index) => {
let _pt = moment(cur.pt).valueOf();
let _length = final.length;
if (_colorMap[_valDesc[cur.pv]]) {
if (_length === 0) {
final.push({
lte: _pt,
gte: 0,
color: _colorMap[_valDesc[cur.pv]],
value: cur.pv,
text: _valDesc[cur.pv],
});
} else {
if (cur.pv === final[_length - 1].value) {
final[_length - 1].lte = _pt;
} else {
final.push({
lte: _pt,
gte: _pt,
color: _colorMap[_valDesc[cur.pv]],
value: cur.pv,
text: _valDesc[cur.pv],
});
}
}
}
return final;
}, [])
.map((item) => {
_markAreaData.push([
{
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: {
color: item.color,
opacity: 0.1,
},
},
]);
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;
data: _markAreaData,
};
}
return _final;
};
// 生成x坐标
const returnXAxis = ({
dataSource,
contrast,
contrastOption,
nameWithSensor,
showMarkLine,
deviceAlarmSchemes,
showPoint,
restOption,
smooth,
special,
}) => {
// 根据"指标名称"分类yAxis
const yAxisInterator = (() => {
const map = new Map();
let current = -1;
const get = (name) => (map.has(name) ? map.get(name) : map.set(name, ++current).get(name));
return { get };
})();
let _offlineData = [];
// 生成visualMap、markArea
let { visualMap, markArea } = handleSpecial1(special, dataSource);
let _filterArr = ['是否在线'];
if (special?.special1?.name) {
_filterArr.push(special.special1.name);
}
// 生成series
let series = dataSource
.filter((item) => {
if (item.sensorName === '是否在线') {
_offlineData.push(item);
}
return !_filterArr.includes(item.sensorName);
})
.map((item, index) => {
const { sensorName, unit } = item;
const name = nameFormatter(item, contrast, contrastOption, nameWithSensor);
const data = dataAccessor(item, contrast, contrastOption);
const type = 'line';
const areaStyle = areaStyleFormatter(item);
const yAxisIndex = yAxisInterator.get(sensorName);
const markLine = showMarkLine
? alarmMarkLine(item, index, dataSource, deviceAlarmSchemes)
: {};
const markPoint = showPoint ? minMaxMarkPoint(item, index, dataSource) : {};
// let markArea = null;
// 需求变更:设备离线改用“是否在线”的数据,离线的状态标记的数据用该部分的数据。 2023年4月25日09:36:55
// 暂时注释,离线逻辑需要再确认 2023-09-15
/* let _offlineAreasData = _offlineData
.find((offline) => offline.stationCode === item.stationCode);
let offlineAreas = offlineArea(_offlineAreasData);
if (offlineAreas.data?.length) {
restOption.markArea = null;
markArea = offlineAreas;
}*/
// 需求新增:增加频率业务
return {
notMerge: true,
name,
type,
data,
areaStyle,
yAxisIndex,
smooth,
unit,
markLine,
markPoint,
markArea,
};
});
// 由于series更新后,没有的数据曲线仍然停留在图表区上,导致图表可视区范围有问题
const min = Math.min(
...series.map((item) => item.data?.[0]?.[0]).filter((item) => item !== undefined),
);
const max = Math.max(
...series
.map((item) => item.data?.[item.data.length - 1]?.[0])
.filter((item) => item !== undefined),
);
return {
xAxis: {
type: 'time',
min,
max,
axisLabel: {
formatter: contrast
? contrastOption === 'month'
? '{dd}日'
: '{HH}:{mm}'
: {
year: '{yyyy}',
month: '{MMM}',
day: '{d}日',
hour: '{HH}:{mm}',
minute: '{HH}:{mm}',
second: '{HH}:{mm}:{ss}',
none: '{yyyy}-{MM}-{dd} {hh}:{mm}:{ss}',
},
},
},
series,
visualMap,
};
dataSource,
contrast,
contrastOption,
nameWithSensor,
showMarkLine,
deviceAlarmSchemes,
showPoint,
restOption,
smooth,
special,
yAxis
}) => {
// 根据"指标名称"分类yAxis
const yAxisInterator = (() => {
const map = new Map();
let current = -1;
const get = (name) => (map.has(name) ? map.get(name) : map.set(name, ++current).get(name));
return {get};
})();
let _offlineData = [];
// 生成visualMap、markArea
let {visualMap, markArea} = handleSpecial1(special, dataSource);
let _filterArr = ['是否在线'];
if (special?.special1?.name) {
_filterArr.push(special.special1.name);
}
// 生成series
let series = dataSource
.filter((item) => {
if (item.sensorName === '是否在线') {
_offlineData.push(item);
}
return !_filterArr.includes(item.sensorName);
})
.map((item, index) => {
const {sensorName, unit} = item;
const name = nameFormatter(item, contrast, contrastOption, nameWithSensor);
const data = dataAccessor(item, contrast, contrastOption);
const type = 'line';
const areaStyle = areaStyleFormatter(item);
const _index = yAxis.findIndex(item => item.name === unit);
const yAxisIndex = _index > -1 ? _index : 0;
// console.log('yAxisIndex: ',yAxisInterator);
const markLine = showMarkLine
? alarmMarkLine(item, index, dataSource, deviceAlarmSchemes)
: {};
const markPoint = showPoint ? minMaxMarkPoint(item, index, dataSource) : {};
// let markArea = null;
// 需求变更:设备离线改用“是否在线”的数据,离线的状态标记的数据用该部分的数据。 2023年4月25日09:36:55
// 暂时注释,离线逻辑需要再确认 2023-09-15
/* let _offlineAreasData = _offlineData
.find((offline) => offline.stationCode === item.stationCode);
let offlineAreas = offlineArea(_offlineAreasData);
if (offlineAreas.data?.length) {
restOption.markArea = null;
markArea = offlineAreas;
}*/
// 需求新增:增加频率业务
return {
notMerge: true,
name,
type,
data,
areaStyle,
yAxisIndex,
smooth,
unit,
markLine,
markPoint,
markArea,
};
});
// 由于series更新后,没有的数据曲线仍然停留在图表区上,导致图表可视区范围有问题
const min = Math.min(
...series.map((item) => item.data?.[0]?.[0]).filter((item) => item !== undefined),
);
const max = Math.max(
...series
.map((item) => item.data?.[item.data.length - 1]?.[0])
.filter((item) => item !== undefined),
);
return {
xAxis: {
type: 'time',
min,
max,
axisLabel: {
formatter: contrast
? contrastOption === 'month'
? '{dd}日'
: '{HH}:{mm}'
: {
year: '{yyyy}',
month: '{MMM}',
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 needUnit = _.get(config, 'needUnit', false);
const curveCenter = _.get(config, 'curveCenter', false);
const nameWithSensor = _.get(config, 'nameWithSensor', true);
const showGridLine = _.get(config, 'showGridLine', true);
const showMarkLine = _.get(config, 'showMarkLine', false);
const showPoint = _.get(config, 'showPoint', false);
const deviceAlarmSchemes = _.get(config, 'deviceAlarmSchemes', []);
const chartType = _.get(config, 'chartType', null);
// const justLine = _.get(config, 'justLine', false);
const showBoxOption = _.get(config, 'showBoxOption', false);
// 自定义属性
const restOption = _.pick(cusOption, ['title', 'legend']);
const special = _.get(config, 'special', {});
return {
needUnit,
curveCenter,
nameWithSensor,
showGridLine,
showMarkLine,
showPoint,
deviceAlarmSchemes,
chartType,
showBoxOption,
restOption,
special,
};
const needUnit = _.get(config, 'needUnit', false);
const curveCenter = _.get(config, 'curveCenter', false);
const nameWithSensor = _.get(config, 'nameWithSensor', true);
const showGridLine = _.get(config, 'showGridLine', true);
const showMarkLine = _.get(config, 'showMarkLine', false);
const showPoint = _.get(config, 'showPoint', false);
const deviceAlarmSchemes = _.get(config, 'deviceAlarmSchemes', []);
const chartType = _.get(config, 'chartType', null);
// const justLine = _.get(config, 'justLine', false);
const showBoxOption = _.get(config, 'showBoxOption', false);
// 自定义属性
const restOption = _.pick(cusOption, ['title', 'legend']);
const special = _.get(config, 'special', {});
return {
needUnit,
curveCenter,
nameWithSensor,
showGridLine,
showMarkLine,
showPoint,
deviceAlarmSchemes,
chartType,
showBoxOption,
restOption,
special,
};
};
const handleMaxValue = (value) => {
if (value <= 1) return value.toFixed(2);
if (value >= 100000) return `${(value / 1000).toFixed(2)}k`;
return value.toFixed(2);
if (value <= 1) return value.toFixed(2);
if (value >= 100000) return `${(value / 1000).toFixed(2)}k`;
return value.toFixed(2);
}
const handleYAxis = ({ dataSource, needUnit, curveCenter, showGridLine }) => {
// 一种指标一个y轴
const yAxisMap = new Map();
// 1. 找出最大值; 2. 计算出y轴最大宽度动态计算偏移距离;
let _maxValueArray = [];
let _offsetValue = [];
dataSource.forEach((item, index) => {
let _maxValue = item.dataModel.reduce((final, cur) => {
if (cur.pv > final) final = cur.pv;
return final
}, 0);
_maxValueArray.push(handleMaxValue(_maxValue));
const { sensorName, unit } = item;
const key = sensorName;
if (!yAxisMap.has(key)) {
const i = yAxisMap.size;
let _offset = _maxValueArray[i - 2];
let _baseOffset = _offsetValue[i - 2] ?? 0;
let _finalOffset = (_offset !== undefined ? (_offset === 0 ? 2 : _offset.toString().replaceAll('.','').length) * 10 : 0) + _baseOffset;
_offsetValue.push(_finalOffset);
const axis = {
type: 'value',
name: needUnit ? unit : null,
position: i % 2 === 0 ? 'left' : 'right',
// offset: _offset !== undefined ? (_offset === 0? 2:_offset.toString().length) * 10 : 0,
offset: _finalOffset,
axisLabel: {
formatter: (value) => {
return handleMaxValue(value);
}
},
axisLine: {
show: true,
},
nameTextStyle: {
align: i % 2 === 0 ? 'right' : 'left',
},
minorTick: {
lineStyle: {
color: '#e2e2e2',
},
},
minorSplitLine: {
lineStyle: {
color: '#e2e2e2',
type: 'dashed',
},
},
};
yAxisMap.set(key, axis);
}
const reduceYAxis = (arr, dataSource) => {
let _offsetValue = [];
// 1. 合并相同单位的坐标轴
let _arr = arr.reduce((final, cur) => {
let _key = cur.name === null ? 'null' : cur.name
if (!final[_key]) {
final[_key] = cur
}
return final;
}, {});
console.log('datasource: ', dataSource);
// 2. 合并相同单位的数据,找出最大值
let _maxValueArr = Object.values(dataSource.reduce((final, cur) => {
let _key = cur.name === null ? 'null' : cur.name;
let _maxValue = cur.dataModel.reduce((final, cur) => {
if (cur.pv > final) final = cur.pv;
return final
}, 0);
if (final[_key] === undefined) {
final[_key] = _maxValue
} else {
final[_key] = Math.max([final[_key], _maxValue]);
}
return final
}, {}));
// 3. 合并,生成Y轴配置
return Object.values(_arr).map((item, index) => {
let _key = item.name === null ? 'null' : item.name;
let _offset = _maxValueArr[index - 2];
let _baseOffset = _offsetValue[index - 2] ?? 0;
let _finalOffset = (_offset !== undefined ? (_offset === 0 ? 2 : _offset.toString().replaceAll('.', '').length) * 12 : 0) + _baseOffset;
_offsetValue.push(_finalOffset);
return ({
...item,
offset: _finalOffset,
position: index % 2 === 0 ? 'left' : 'right',
nameTextStyle: {
align: index % 2 === 0 ? 'right' : 'left',
},
})
});
};
const handleYAxis = ({dataSource, needUnit, curveCenter, showGridLine}) => {
// 1. 新需求:合并相同的坐标轴
// 一种指标一个y轴 -------- 需求变更:相同类型的指标放到同一指标轴(单位相同) 2023年9月28日
const yAxisMap = new Map();
// 1. 找出最大值; 2. 计算出y轴最大宽度动态计算偏移距离;
// 曲线居中
if (curveCenter && item.dataModel && item.dataModel.length > 0) {
const [min, max] = minMax(item);
const axis = yAxisMap.get(key);
axis.min = axis.min === void 0 ? min : Math.min(min, axis.min);
axis.max = axis.max === void 0 ? max : Math.max(max, axis.max);
}
dataSource.forEach((item, index) => {
// 网格显示
const axis = yAxisMap.get(key);
decorateAxisGridLine(axis, showGridLine);
});
const yAxis = yAxisMap.size > 0 ? [...yAxisMap.values()] : { type: 'value' };
const {sensorName, unit} = item;
const key = sensorName;
if (!yAxisMap.has(key)) {
/* const i = yAxisMap.size;
let _offset = _maxValueArray[i - 2];
let _baseOffset = _offsetValue[i - 2] ?? 0;
let _finalOffset = (_offset !== undefined ? (_offset === 0 ? 2 : _offset.toString().replaceAll('.','').length) * 10 : 0) + _baseOffset;
_offsetValue.push(_finalOffset);*/
const axis = {
type: 'value',
name: needUnit ? unit : null,
// position: i % 2 === 0 ? 'left' : 'right',
// offset: _offset !== undefined ? (_offset === 0? 2:_offset.toString().length) * 10 : 0,
// offset: _finalOffset,
axisLabel: {
formatter: (value) => {
return handleMaxValue(value);
}
},
axisLine: {
show: true,
},
minorTick: {
lineStyle: {
color: '#e2e2e2',
},
},
minorSplitLine: {
lineStyle: {
color: '#e2e2e2',
type: 'dashed',
},
},
};
yAxisMap.set(key, axis);
}
// 根据y轴个数调整边距
const leftNum = Math.ceil(yAxisMap.size / 2);
const rightNum = Math.floor(yAxisMap.size / 2);
return { leftNum, rightNum, yAxis };
};
const assignOptions = (restOption, xAxis, legendData, chartType, contrast, contrastOption) => {
restOption.dataZoom = [
{
show: true,
bottom: 10,
start: 0,
end: 100,
height: currentOption['dataZoomHeight'],
type: 'inside',
zoomOnMouseWheel: true,
filterMode: chartType === 'lineChart' ? 'none' : 'weakFilter',
},
{
show: true,
bottom: 10,
start: 0,
end: 100,
height: currentOption['dataZoomHeight'],
type: 'slider',
zoomOnMouseWheel: true,
labelFormatter: function (e) {
let _formatterStr = 'YYYY-MM-DD HH:mm:ss';
if (contrast) {
if (contrastOption === 'day') _formatterStr = 'HH:mm';
if (contrastOption === 'month') _formatterStr = 'MM月DD日 HH时';
// 曲线居中
if (curveCenter && item.dataModel && item.dataModel.length > 0) {
const [min, max] = minMax(item);
const axis = yAxisMap.get(key);
axis.min = axis.min === void 0 ? min : Math.min(min, axis.min);
axis.max = axis.max === void 0 ? max : Math.max(max, axis.max);
}
return moment(e).format(_formatterStr);
},
},
];
xAxis.minInterval = 3600 * (1 * 1000);
if (legendData) {
restOption.legend = { ...buildDefaultLegend({}), ...{ data: legendData } };
}
// 网格显示
const axis = yAxisMap.get(key);
decorateAxisGridLine(axis, showGridLine);
});
const yAxis = yAxisMap.size > 0 ? reduceYAxis([...yAxisMap.values()], dataSource) : {type: 'value'};
const leftNum = Math.ceil(yAxisMap.size / 2);
const rightNum = Math.floor(yAxisMap.size / 2);
return {leftNum, rightNum, yAxis};
};
const assignOptions = (restOption, xAxis, legendData, chartType, contrast, contrastOption, config) => {
restOption.dataZoom = [
{
show: true,
bottom: 20,
start: 0,
end: 100,
height: config.isMultiple ? 20 : 28,
type: 'inside',
zoomOnMouseWheel: true,
filterMode: chartType === 'lineChart' ? 'none' : 'weakFilter',
},
{
show: true,
bottom: 20,
start: 0,
end: 100,
height: config.isMultiple ? 20 : 28,
type: 'slider',
zoomOnMouseWheel: true,
labelFormatter: function (e) {
let _formatterStr = 'YYYY-MM-DD HH:mm:ss';
if (contrast) {
if (contrastOption === 'day') _formatterStr = 'HH:mm';
if (contrastOption === 'month') _formatterStr = 'MM月DD日 HH时';
}
return moment(e).format(_formatterStr);
},
},
];
xAxis.minInterval = 3600 * (1 * 1000);
if (legendData) {
restOption.legend = {...restOption.legend, ...{data: legendData}};
}
};
const returnMaxOrMinNumber = (dataSource, type) => {
let _obj = null;
if (type === 'period' && dataSource?.[0]?.dataModel?.length) {
let _length = dataSource?.[0]?.dataModel?.length;
let _first = dataSource?.[0]?.dataModel[0]?.pt;
let _last = dataSource?.[0]?.dataModel[_length - 1]?.pt;
return ['展示时段: ', _first, _last, type];
}
dataSource?.[0]?.dataModel
.filter((item) => item.pv !== null)
.forEach((item) => {
if (!_obj) {
_obj = item;
} else {
if (type === 'min') {
if (item.pv < _obj.pv) {
_obj = item;
}
}
if (type === 'max') {
if (item.pv > _obj.pv) {
_obj = item;
}
}
}
});
let _value = [];
if (_obj?.pt) {
_value = [moment(_obj.pt).valueOf(), _obj.pv];
let _img = type === 'max' ? maxIcon : minIconDownArrow;
_value.push(_img);
_value.push(type);
// 把最大值最小值都push进去,方便计算
}
return _value;
let _obj = null;
if (type === 'period' && dataSource?.[0]?.dataModel?.length) {
let _length = dataSource?.[0]?.dataModel?.length;
let _first = dataSource?.[0]?.dataModel[0]?.pt;
let _last = dataSource?.[0]?.dataModel[_length - 1]?.pt;
return ['展示时段: ', _first, _last, type];
}
dataSource?.[0]?.dataModel
.filter((item) => item.pv !== null)
.forEach((item) => {
if (!_obj) {
_obj = item;
} else {
if (type === 'min') {
if (item.pv < _obj.pv) {
_obj = item;
}
}
if (type === 'max') {
if (item.pv > _obj.pv) {
_obj = item;
}
}
}
});
let _value = [];
if (_obj?.pt) {
_value = [moment(_obj.pt).valueOf(), _obj.pv];
let _img = type === 'max' ? maxIcon : minIconDownArrow;
_value.push(_img);
_value.push(type);
// 把最大值最小值都push进去,方便计算
}
return _value;
};
const handleGrid = (dataSource, needUnit, leftNum, rightNum, chartType) => {
// 如果是单曲线,_grid的top需要一行的高度,用来放置最值最小值
let _base = 60;
let _topForUnit = needUnit ? 20 : 0;
return {
top: _base + _topForUnit,
// left: leftNum === 1 ? 10 : leftNum * AXIS_WIDTH,
left: 20,
right:10,
// right: rightNum === 0 ? 20 : rightNum * AXIS_WIDTH,
bottom: 60,
containLabel: true,
};
// 如果是单曲线,_grid的top需要一行的高度,用来放置最值最小值
let _base = 60;
let _topForUnit = needUnit ? 20 : 0;
return {
top: _base + _topForUnit,
// left: leftNum === 1 ? 10 : leftNum * AXIS_WIDTH,
left: 20,
right: 10,
// right: rightNum === 0 ? 20 : rightNum * AXIS_WIDTH,
bottom: 60,
containLabel: true,
};
};
const renderItem = (params, api) => {
let _base = params.dataIndex;
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 _left = 30;
let _baseWidth = 190 + _numberStringWidth;
let _imgWidth = 45;
let _dateWidth = 128;
let _imageX = _left + _base * _baseWidth;
let _timeX = _left + _base * _baseWidth + _imgWidth;
let _valueX = _left + _base * _baseWidth + _imgWidth + _dateWidth;
let _color = api.value(3) === 'min' ? '#05C2BC' : '#1685FF';
let topValue = 25;
let _trimmer = -2;
return {
type: 'group',
children: [
{
type: 'image',
style: {
image: api.value(2),
textVerticalAlign: 'middle',
y: -6,
},
position: [_imageX, topValue],
},
{
type: 'text',
style: {
text: moment(api.value(0)).format('YYYY-MM-DD HH:mm:ss') + ': ',
textVerticalAlign: 'top',
},
position: [_timeX, topValue + _trimmer],
},
{
type: 'text',
style: {
text: api.value(1),
textVerticalAlign: 'middle',
fill: _color,
font: 'bolder 16px cursive',
lineWidth: 10,
y: 3,
},
position: [_valueX, topValue + _trimmer],
},
],
};
let _base = params.dataIndex;
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 _left = 30;
let _baseWidth = 190 + _numberStringWidth;
let _imgWidth = 45;
let _dateWidth = 128;
let _imageX = _left + _base * _baseWidth;
let _timeX = _left + _base * _baseWidth + _imgWidth;
let _valueX = _left + _base * _baseWidth + _imgWidth + _dateWidth;
let _color = api.value(3) === 'min' ? '#05C2BC' : '#1685FF';
let topValue = 25;
let _trimmer = -2;
return {
type: 'group',
children: [
{
type: 'image',
style: {
image: api.value(2),
textVerticalAlign: 'middle',
y: -6,
},
position: [_imageX, topValue],
},
{
type: 'text',
style: {
text: moment(api.value(0)).format('YYYY-MM-DD HH:mm:ss') + ': ',
textVerticalAlign: 'top',
},
position: [_timeX, topValue + _trimmer],
},
{
type: 'text',
style: {
text: api.value(1),
textVerticalAlign: 'middle',
fill: _color,
font: 'bolder 16px cursive',
lineWidth: 10,
y: 3,
},
position: [_valueX, topValue + _trimmer],
},
],
};
};
const returnCustomSeries = (dataSource) => {
let _maxNumber = returnMaxOrMinNumber(dataSource, 'max');
let _minNumber = returnMaxOrMinNumber(dataSource, 'min');
// let _period = returnMaxOrMinNumber(dataSource, 'period');
// 需要将最大值最小分别传入,后续计算图例位置需要,先min后max
let _max = _maxNumber[1];
let _min = _minNumber[1];
[_maxNumber, _minNumber].forEach((item) => {
item.push(_min);
item.push(_max);
});
return {
name: '自定义',
type: 'custom',
renderItem: renderItem,
data: [_minNumber, _maxNumber],
};
let _maxNumber = returnMaxOrMinNumber(dataSource, 'max');
let _minNumber = returnMaxOrMinNumber(dataSource, 'min');
// let _period = returnMaxOrMinNumber(dataSource, 'period');
// 需要将最大值最小分别传入,后续计算图例位置需要,先min后max
let _max = _maxNumber[1];
let _min = _minNumber[1];
[_maxNumber, _minNumber].forEach((item) => {
item.push(_min);
item.push(_max);
});
return {
name: '自定义',
type: 'custom',
renderItem: renderItem,
data: [_minNumber, _maxNumber],
};
};
/**
* 图表配置项生成
......@@ -878,205 +891,210 @@ const returnCustomSeries = (dataSource) => {
*/
const optionGenerator = (
dataSource,
cusOption,
contrast,
contrastOption,
smooth,
config,
lineDataType = '',
) => {
const {
needUnit,
curveCenter,
nameWithSensor,
showGridLine,
showMarkLine,
showPoint,
deviceAlarmSchemes,
chartType,
showBoxOption,
restOption,
special,
} = handleDefault(config, cusOption);
const { leftNum, rightNum, yAxis } = handleYAxis({
dataSource,
needUnit,
curveCenter,
showGridLine,
});
const grid = handleGrid(dataSource, needUnit, leftNum, rightNum, chartType);
let { xAxis, series, visualMap } = returnXAxis({
dataSource,
cusOption,
contrast,
contrastOption,
nameWithSensor,
showMarkLine,
deviceAlarmSchemes,
showPoint,
smooth,
restOption,
special,
});
decorateAxisGridLine(xAxis, showGridLine);
const tooltipTimeFormat = !contrast
? 'YYYY-MM-DD HH:mm:ss'
: contrastOption === 'day'
? 'HH:mm'
: 'DD HH:mm';
let tooltip = {};
// 增加箱线图的逻辑,单曲线才存在该逻辑
if (chartType && showBoxOption) {
if (chartType === 'boxChart' && lineDataType === '特征曲线') {
const otherData =
dataSource?.[0]?.dataModel.map((item) => {
const { firstPV, lastPV, maxPV, minPV, pt } = item;
return [moment(pt).valueOf(), firstPV, lastPV, minPV, maxPV];
}) || []; //当存在othersData的时候,只是单曲线
xAxis = { type: 'time' };
decorateAxisGridLine(xAxis, showGridLine);
let unit = [];
series = series.map((item) => {
if (item.unit) unit.push(item.unit);
item.areaStyle = null;
return { ...item, showSymbol: false };
});
series.push({
type: 'candlestick',
name: '箱线图',
symbol: 'none',
data: otherData,
itemStyle: {
color: '#FFA200',
color0: '#44CD00',
borderColor: '#FFA200',
borderColor0: '#44CD00',
},
});
tooltip = tooltipAccessor(unit);
}
if (chartType === 'lineChart' || lineDataType === '原始曲线') {
let _maxData = [];
let _minData = [];
let _currentYear = moment().format('YYYY');
const formatStr =
contrastOption === 'day'
? `${_currentYear}-01-01 HH:mm:00`
: `${_currentYear}-01-DD HH:mm:00`; // 用来做同期对比,把日期拉到同一区间
let _maxValues = [];
/**
* 生成泳道图,分两种情况
* 1. 当最大值最小值都是正数时;
* 2. 当最大值小于零时(此时,最小值一定小于零);
*/
dataSource?.[0]?.dataModel.forEach((item) => {
const { firstPV, lastPV, maxPV, minPV, pt } = item;
_maxValues.push(maxPV);
let time = contrast ? moment(pt).format(formatStr) : pt;
_maxData.push([
moment(time).valueOf(),
(maxPV > 0 ? maxPV - minPV : minPV - maxPV).toFixed(2),
]);
_minData.push([moment(time).valueOf(), maxPV > 0 ? minPV : maxPV]);
}); //当存在othersData的时候,只是单曲线
// xAxis = {type: 'category', data: series[0].data.map(item => moment(item[0]).format('YYYY-MM-DD HH:mm:ss'))};
xAxis = { type: 'time' };
decorateAxisGridLine(xAxis, showGridLine);
let _unit = '';
series = series.map((item) => {
_unit = item.unit ?? '';
item.areaStyle = null;
return { ...item, showSymbol: false };
});
[[..._minData], [..._maxData]].forEach((item, index) => {
series.push({
name: index === 0 ? '周期最小值' : '周期最大值',
type: 'line',
data: item,
lineStyle: {
opacity: 0,
},
...(index !== 0
? {
areaStyle: {
color: series?.[0]?.itemStyle?.color ?? '#65a0d1',
opacity: 0.2,
},
}
: {}),
stack: 'confidence-band',
symbol: 'none',
});
});
tooltip = {
trigger: 'axis',
formatter: (e) => {
return `<div>
config,
lineDataType = '',
) => {
const {
needUnit,
curveCenter,
nameWithSensor,
showGridLine,
showMarkLine,
showPoint,
deviceAlarmSchemes,
chartType,
showBoxOption,
restOption,
special,
} = handleDefault(config, cusOption);
const {leftNum, rightNum, yAxis} = handleYAxis({
dataSource,
needUnit,
curveCenter,
showGridLine,
});
console.log(yAxis);
const grid = handleGrid(dataSource, needUnit, leftNum, rightNum, chartType);
let {xAxis, series, visualMap} = returnXAxis({
dataSource,
contrast,
contrastOption,
nameWithSensor,
showMarkLine,
deviceAlarmSchemes,
showPoint,
smooth,
restOption,
special,
yAxis
});
decorateAxisGridLine(xAxis, showGridLine);
const tooltipTimeFormat = !contrast
? 'YYYY-MM-DD HH:mm:ss'
: contrastOption === 'day'
? 'HH:mm'
: 'DD HH:mm';
let tooltip = {};
// 增加箱线图的逻辑,单曲线才存在该逻辑
if (chartType && showBoxOption) {
if (chartType === 'boxChart' && lineDataType === '特征曲线') {
const otherData =
dataSource?.[0]?.dataModel.map((item) => {
const {firstPV, lastPV, maxPV, minPV, pt} = item;
return [moment(pt).valueOf(), firstPV, lastPV, minPV, maxPV];
}) || []; //当存在othersData的时候,只是单曲线
xAxis = {type: 'time'};
decorateAxisGridLine(xAxis, showGridLine);
let unit = [];
series = series.map((item) => {
if (item.unit) unit.push(item.unit);
item.areaStyle = null;
return {...item, showSymbol: false};
});
series.push({
type: 'candlestick',
name: '箱线图',
symbol: 'none',
data: otherData,
itemStyle: {
color: '#FFA200',
color0: '#44CD00',
borderColor: '#FFA200',
borderColor0: '#44CD00',
},
});
tooltip = tooltipAccessor(unit);
}
if (chartType === 'lineChart' || lineDataType === '原始曲线') {
let _maxData = [];
let _minData = [];
let _currentYear = moment().format('YYYY');
const formatStr =
contrastOption === 'day'
? `${_currentYear}-01-01 HH:mm:00`
: `${_currentYear}-01-DD HH:mm:00`; // 用来做同期对比,把日期拉到同一区间
let _maxValues = [];
/**
* 生成泳道图,分两种情况
* 1. 当最大值最小值都是正数时;
* 2. 当最大值小于零时(此时,最小值一定小于零);
*/
dataSource?.[0]?.dataModel.forEach((item) => {
const {firstPV, lastPV, maxPV, minPV, pt} = item;
_maxValues.push(maxPV);
let time = contrast ? moment(pt).format(formatStr) : pt;
_maxData.push([
moment(time).valueOf(),
(maxPV > 0 ? maxPV - minPV : minPV - maxPV).toFixed(2),
]);
_minData.push([moment(time).valueOf(), maxPV > 0 ? minPV : maxPV]);
}); //当存在othersData的时候,只是单曲线
// xAxis = {type: 'category', data: series[0].data.map(item => moment(item[0]).format('YYYY-MM-DD HH:mm:ss'))};
xAxis = {type: 'time'};
decorateAxisGridLine(xAxis, showGridLine);
let _unit = '';
series = series.map((item) => {
_unit = item.unit ?? '';
item.areaStyle = null;
return {...item, showSymbol: false};
});
[[..._minData], [..._maxData]].forEach((item, index) => {
series.push({
name: index === 0 ? '周期最小值' : '周期最大值',
type: 'line',
data: item,
lineStyle: {
opacity: 0,
},
...(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])}
<div>
<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><span style="display:inline-block;">:</span>
? 'width: ' +
handlePx(90, 'px') +
';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>
5,
'px',
)} 0 auto;">${e[0]?.value?.[1] ?? '-'}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div>
<div style="display: ${lineDataType === '特征曲线' ? 'flex' : 'none'
}; align-items: center;">
}; align-items: center;">
<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>
5,
'px',
)} 0 auto;">${e?.[1]?.value?.[1] ?? '-'}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div>
<div style="display: ${lineDataType === '特征曲线' ? 'flex' : 'none'
}; align-items: center;">
}; align-items: center;">
<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>
5,
'px',
)} 0 auto;">${_maxValues[e?.[2]?.dataIndex] ?? '-'}</span>
<span style="font-size: ${handlePx(12, 'px')};">${_unit ?? ''}</span>
</div>
</div>
</div>`;
},
};
}
// 单曲线需要标记最大值、最小值的情况下,需要增加自定义的series,将最大最小值显示在图表上
if (dataSource?.[0]?.dataModel?.length && chartType === 'lineChart') {
let _customSeries = returnCustomSeries(dataSource);
series.push(_customSeries);
},
};
}
// 单曲线需要标记最大值、最小值的情况下,需要增加自定义的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},
);
}
} else {
tooltip = tooltipAccessor(
series.map((item) => item.unit),
{ contrastOption, contrast },
);
}
tooltip.timeFormat = tooltipTimeFormat;
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;
tooltip.timeFormat = tooltipTimeFormat;
let _legendData = series
.filter((item) => !['周期最大值', '周期最小值', '自定义'].includes(item.name)) // legend中,过滤掉辅助业务的legend
.map((item) => item.name);
assignOptions(restOption, xAxis, _legendData, chartType, contrast, contrastOption, config);
let _options = {
yAxis,
grid,
xAxis,
series,
tooltip,
visualMap,
...restOption,
};
console.log('_options: ', _options)
return _options;
};
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