utils.js 9.1 KB
Newer Older
1 2 3
import moment from 'moment';
import _ from 'lodash';

4
/** 轴宽度, 用于计算多轴显示时, 轴线偏移和绘图区域尺寸 */
5 6 7 8
const axisWidth = 40;

/**
 * 图表系列名称格式化
9 10
 *
 * @param {any} data
11
 * @param {boolean} contrast 是否为同期对比
12 13
 * @param {any} contrastOption 同期对比周期配置, day|month
 * @returns
14
 */
15
const nameFormatter = (data, contrast, contrastOption, nameWithSensor) => {
16
  const { equipmentName, sensorName, unit, dataModel, dateFrom, dateTo } = data;
17
  let name = nameWithSensor ? `${equipmentName}-${sensorName}` : equipmentName;
18 19 20 21 22 23 24 25 26
  if (contrast) {
    const time = dateFrom.slice(0, contrastOption === 'day' ? 10 : 7).replace(/-/g, '');
    name = `${name}-${time}`;
  }
  return name;
};

/**
 * 图表系列数据格式化
27 28
 *
 * @param {any} data
29
 * @param {boolean} contrast 是否为同期对比
30
 * @param {any} contrastOption 同期对比周期配置, day|month
31 32 33 34 35
 * @returns 图表系列数据, [[DateTime, value]]
 */
const dataAccessor = (data, contrast, contrastOption) => {
  const { dataModel } = data;
  const formatStr = contrastOption === 'day' ? '2020-01-01 HH:mm:00' : '2020-01-DD HH:mm:00';
36
  return dataModel.map((item) => {
37 38 39 40 41 42 43
    const time = contrast ? moment(item.pt).format(formatStr) : item.pt;
    return [moment(time).valueOf(), item.pv];
  });
};

/**
 * 面积图配置(目前默认曲线图)
44 45 46
 *
 * @param {any} data 数据项
 * @returns Null/areaStyle, 为null显示曲线图, 为areaStyle对象显示为面积图.
47 48 49 50 51 52
 */
const areaStyleFormatter = (data) => {
  const { sensorName } = data;
  return sensorName && sensorName.indexOf('流量') > -1 ? {} : null;
};

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
/**
 * 数据项中指标值最小最大值
 *
 * @param {any} 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];
};
69

70 71 72 73 74 75 76 77 78 79 80
const markLineItem = (name, value, color) => {
  return {
    name: name,
    yAxis: value,
    value: value,
    lineStyle: {
      color: color || '#000',
    },
    label: {
      position: 'insideEndTop',
      color: color || '#000',
81 82 83
      formatter: function () {
        return `${name}:${value}`;
      },
84 85 86 87 88 89 90
    },
  };
};

export const alarmMarkLine = (dataItem, index, dataSource, schemes) => {
  // 只有一个数据曲线时显示markline
  if (!dataItem || !schemes || dataSource.length !== 1) return {};
91
  const { deviceType, stationCode, sensorName, decimalPoint } = dataItem;
92 93 94 95 96 97 98 99 100
  const curSchemes = schemes.filter(
    (item) =>
      item.deviceCode === stationCode &&
      item.sensorName === sensorName &&
      item.valueType === '直接取值',
  );
  const data = [];
  curSchemes.forEach((scheme) => {
    const { hLimit, hhLimit, lLimit, llLimit } = scheme;
101
    lLimit !== null && lLimit !== void 0 && data.push(markLineItem('低限', lLimit, '#fa8c16'));
102
    hLimit !== null && hLimit !== void 0 && data.push(markLineItem('高限', hLimit, '#fa8c16'));
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
    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}`;
      },
    },
120 121 122 123 124 125 126
  });
  return {
    symbol: ['none', 'none'],
    data,
  };
};

127 128 129 130 131 132 133 134 135 136 137 138
export const minMaxMarkPoint = (dataItem, index, dataSource) => {
  // 只有一个数据曲线时显示markline
  if (!dataItem || dataSource.length !== 1) return {};
  const data = [];
  data.push({ type: 'min', name: '最小值', label: { color: '#FFFFFF' } });
  data.push({ type: 'max', name: '最大值', label: { color: '#FFFFFF' } });
  return {
    symbol: 'pin',
    data,
  };
};

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
/**
 * 坐标轴添加网格线配置
 *
 * @param {any} axis
 */
export const decorateAxisGridLine = (axis, showGrid) => {
  if (!axis) return;
  axis.minorTick = {
    lineStyle: {
      color: '#e2e2e2',
    },
    ...(axis.minorTick || {}),
    show: showGrid,
  };
  axis.minorSplitLine = {
    lineStyle: {
      color: '#e2e2e2',
      type: 'dashed',
    },
    ...(axis.minorSplitLine || {}),
    show: showGrid,
  };
  axis.splitLine = {
    ...(axis.splitLine || {}),
    show: showGrid,
  };
  // axis.minorTick ? (axis.minorTick.show = true) : (axis.minorTick = { show: true });
  // axis.minorSplitLine
  //   ? (axis.minorSplitLine.show = true)
  //   : (axis.minorSplitLine = { show: true });
  // axis.splitLine ? (axis.splitLine.show = true) : (axis.splitLine = { show: true });
};

172 173
/**
 * 图表配置项生成
174 175 176 177 178 179 180
 *
 * @param {any} dataSource 数据源
 * @param {any} cusOption 自定义属性
 * @param {any} contrast 是否为同期对比
 * @param {any} contrastOption 同期对比周期配置, day|month
 * @param {any} smooth Ture/false, 曲线/折线
 * @param {any} config 额外配置信息
181 182 183
 */
const optionGenerator = (dataSource, cusOption, contrast, contrastOption, smooth, config) => {
  const needUnit = _.get(config, 'needUnit', false);
184
  const curveCenter = _.get(config, 'curveCenter', false);
185
  const nameWithSensor = _.get(config, 'nameWithSensor', true);
186
  const showGridLine = _.get(config, 'showGridLine', false);
187 188
  const showMarkLine = _.get(config, 'showMarkLine', false);
  const showPoint = _.get(config, 'showPoint', false);
189 190
  const deviceAlarmSchemes = _.get(config, 'deviceAlarmSchemes', []);

191
  // 自定义属性
192
  const restOption = _.pick(cusOption, ['title', 'legend']);
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207

  // 一种指标一个y轴
  const yAxisMap = new Map();
  dataSource.forEach((item, index) => {
    const { sensorName, unit } = item;
    const key = sensorName;

    if (!yAxisMap.has(key)) {
      const i = yAxisMap.size;
      const axis = {
        type: 'value',
        name: needUnit ? unit : null,
        position: i % 2 === 0 ? 'left' : 'right',
        offset: Math.floor(i / 2) * axisWidth,
        axisLabel: {
208
          formatter: (value) => (value > 100000 ? `${value / 1000}k` : value),
209 210 211 212 213
        },
        axisLine: {
          show: true,
        },
        nameTextStyle: {
214
          align: i % 2 === 0 ? 'right' : 'left',
215
        },
216 217 218 219 220 221 222 223 224 225 226
        minorTick: {
          lineStyle: {
            color: '#e2e2e2',
          },
        },
        minorSplitLine: {
          lineStyle: {
            color: '#e2e2e2',
            type: 'dashed',
          },
        },
227
      };
228 229
      yAxisMap.set(key, axis);
    }
230 231 232 233 234 235 236 237

    // 曲线居中
    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);
    }
238 239

    // 网格显示
240 241
    const axis = yAxisMap.get(key);
    decorateAxisGridLine(axis, showGridLine);
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
  });
  const yAxis = yAxisMap.size > 0 ? [...yAxisMap.values()] : { type: 'value' };

  // 根据y轴个数调整边距
  const leftNum = Math.ceil(yAxisMap.size / 2);
  const rightNum = Math.floor(yAxisMap.size / 2);
  const grid = {
    top: needUnit ? 80 : 60,
    left: 10 + leftNum * axisWidth,
    right: rightNum === 0 ? 20 : rightNum * axisWidth,
  };

  // 根据"指标名称"分类yAxis
  const yAxisInterator = (() => {
    const map = new Map();
    let current = -1;
258 259
    const get = (name) => (map.has(name) ? map.get(name) : map.set(name, ++current).get(name));
    return { get };
260 261
  })();

262
  const series = dataSource.map((item, index) => {
263
    const { sensorName, unit } = item;
264
    const name = nameFormatter(item, contrast, contrastOption, nameWithSensor);
265 266 267 268
    const data = dataAccessor(item, contrast, contrastOption);
    const type = 'line';
    const areaStyle = areaStyleFormatter(item);
    const yAxisIndex = yAxisInterator.get(sensorName);
269 270
    const markLine = showMarkLine ? alarmMarkLine(item, index, dataSource, deviceAlarmSchemes) : {};
    const markPoint = showPoint ? minMaxMarkPoint(item, index, dataSource) : {};
271 272 273 274 275 276 277 278 279
    return {
      notMerge: true,
      name,
      type,
      data,
      areaStyle,
      yAxisIndex,
      smooth,
      unit,
280
      markLine,
281
      markPoint,
282
    };
283 284 285
  });

  // 由于series更新后,没有的数据曲线仍然停留在图表区上,导致图表可视区范围有问题
286 287 288 289 290 291 292 293
  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),
  );
294
  const xAxis = { type: 'time', min, max };
295
  decorateAxisGridLine(xAxis, showGridLine);
296

297 298 299 300 301
  const tooltipTimeFormat = !contrast
    ? 'YYYY-MM-DD HH:mm:ss'
    : contrastOption === 'day'
    ? 'HH:mm'
    : 'DD HH:mm';
302 303 304 305 306 307 308
  const tooltip = {
    timeFormat: tooltipTimeFormat,
    // trigger: 'axis',
    // axisPointer: {
    //   type: 'cross'
    // }
  };
309 310 311 312 313 314 315 316

  return {
    yAxis,
    grid,
    xAxis,
    series,
    tooltip,
    ...restOption,
317
  };
318 319 320
};

export default optionGenerator;