vue2简单封装highcharts

vue2简单封装highcharts

  • 前言
  • 一、vue文件(high-chart.vue)
  • 二、highcharts配置(default-options.js)
  • 三、包裹highcharts的div宽度变化时,需要重绘,因此需要监听el
    • 1、resize-event.js
    • 2、debounce.js
  • 四、依赖库版本(package.json)


前言

把highcharts封装成vue组件,任何项目用起来都很方便


一、vue文件(high-chart.vue)

<template>
  <div class="highcharts-container" />
</template>

<script>
import * as _ from 'lodash';
import HighCharts from 'highcharts/highstock';
import HighchartsMore from 'highcharts/highcharts-more';
import HighchartsDrilldown from 'highcharts/modules/drilldown';
import Highcharts3D from 'highcharts/highcharts-3d';
import Exporting from 'highcharts/modules/exporting.js';
import HighchartsNoData from 'highcharts-no-data-to-display';
import defaultOptions from '_c/chart/default-options';
import Annotations from 'highcharts/modules/annotations.js';
import Oldie from 'highcharts/modules/oldie.js';
import { addResizeListener, removeResizeListener } from '@/utils/resize-event.js';
import debounce from '@/utils/debounce.js';

HighchartsMore(HighCharts);
HighchartsDrilldown(HighCharts);
Highcharts3D(HighCharts);
Exporting(HighCharts);
Annotations(HighCharts);
Oldie(HighCharts);
HighchartsNoData(HighCharts);

HighCharts.setOptions({
  global: {
    timezoneOffset: -8 * 60,
  },
  lang: {
    noData: '数据未上报',
    viewFullscreen: '全屏查看',
    printChart: '打印图表',
    downloadJPEG: '下载JPEG 图片',
    downloadPDF: '下载PDF文档',
    downloadPNG: '下载PNG 图片',
    downloadSVG: '下载SVG 矢量图',
    exportButtonTitle: '导出图片',
    loading: '加载中...',
    resetZoom: '重置视图',
  },
});

export default {
  name: 'high-chart',
  props: {
    options: {
      type: Object,
      default: () => {},
    },
    series: {
      type: Array,
      default: () => [],
    },
    loading: {
      type: Boolean,
      default: () => false,
    },
  },
  data() {
    return {     
      chart: null,
    };
  },
  computed: {
    finalOptions() {
      return _.merge(_.cloneDeep(defaultOptions), this.options);
    },
  },
  watch: {
    options: {
      handler() {
        this.chart && this.chart.update(this.finalOptions);        
      },
      deep: true,
    },
    loading(v) {
      if (v && this.chart) {
        this.chart.showLoading();
      } else if (!v && this.chart) {
        this.chart.hideLoading();
      }
    },
    series: {
      handler(newVal) {
        if (newVal?.length) {
          this.updateSeries();
        } else {
          this.initChart();
        }
      },
    },
  },
  mounted() {
    this.initChart();
    addResizeListener(this.$el, this.onResize());
  },
  beforeDestroy() {
    this.chart && this.chart.destroy();
    this.chart = null;
  },
  destroyed() {
    removeResizeListener(this.$el, this.onResize());
  },
  methods: {
    onResize() {
      return debounce(() => {
        this.chart && this.chart.reflow();
      }, 200);
    },
    initChart() {
      this.chart = new HighCharts.chart(this.$el, this.finalOptions);
      window.chart = this.chart;
      if (this.loading) {
        this.chart.showLoading();
      }
      this.updateSeries();
    },
    updateSeries() {
      if (!this.chart) {
        return;
      }
      this.series.forEach((series) => {
        const exist = this.chart.get(series.id);
        if (exist) {
          exist.setData(series.data, false);
          exist.setVisible(true, false);
          exist.update(
            {
              diffDay: series.diffDay,
              visible: series.visible,
              showInLegend: series.showInLegend,
              type: series.type,
            },
            false,
          );
        } else {
          this.chart.addSeries(series, false);
        }
      });
      this.chart.series.forEach((series) => {
        if (!this.series.find((s) => s.name === series.name)) {
          series.setVisible(false, false);
          series.update({ showInLegend: false, type: series.type }, false);
        }
      });
      this.chart.redraw();
    },
  },
};
</script>

<style lang="less" scoped>
@import '@/style/index';

.highcharts-container {
  display: block;

  & /deep/ .highcharts-container {
    margin: 0 auto;
  }
}
</style>

二、highcharts配置(default-options.js)

export default {
  chart: {
    spacingBottom: 5,
    spacingLeft: 0,
    height: 300,
  },
  colors: [
    '#0000ff',
    '#ff9a38',
    '#3ecc36',
    '#ff5620',
    '#7953ff',
    '#1ee6e6',
    '#1be591',
    '#db30ff',
    '#ff3383',
    '#33c4ff',
    '#ffc500',
  ],
  title: {
    style: {
      fontSize: '14px',
      textOverflow: 'ellipsis',
      wordBreak: 'break-all',
      wordWrap: 'break-word',
      // visibility: 'hidden',
    },
    text: '',
  },
  xAxis: {
    gridLineWidth: 1,
    tickPixelInterval: 40, // 刻度间距
    type: 'datetime',
    dateTimeLabelFormats: {
      millisecond: '%H:%M:%S.%L',
      second: '%H:%M:%S',
      minute: '%H:%M',
      hour: '%H',
      day: '%m-%d',
      week: '%m-%d',
      month: '%Y-%m',
      year: '%Y',
    },
    labels: {
      rotation: -45,
    },
    plotBands: [
      // {
      //   color: '#FFD4C5',
      //   from: 1635726000000,
      //   to: 1635736800000,
      //   label: {
      //     text: '异常',
      //   },
      // },
    ],
  },
  yAxis: [
    {
      gridLineWidth: 1,
      tickAmount: 7, // 规定坐标轴上的刻度总数
      max: null,
      endOnTick: true, // 是否强制将坐标轴结束于刻度线
      ceiling: null, // 自动计算坐标轴极值的上限
      // softMax: 1, // 坐标轴的柔和最大值
      title: {
        text: '',
        style: {
          fontWeight: 'bold',
          fontSize: '12px',
        },
        align: 'high',
        rotation: 0,
        offset: 0,
        y: -20,
        x: 6,
      },
      labels: {
        align: 'right',
        padding: 0,
      },
      height: '90%',
    },
    {
      gridLineWidth: 0,
      // tickAmount: 7, // 规定坐标轴上的刻度总数
      max: 100,
      min: -100,
      endOnTick: true, // 是否强制将坐标轴结束于刻度线
      ceiling: null, // 自动计算坐标轴极值的上限
      // softMax: 1, // 坐标轴的柔和最大值
      title: {
        text: '',
        style: {
          fontWeight: 'bold',
          fontSize: '12px',
        },
        align: 'high',
        rotation: 0,
        offset: 0,
        y: -20,
        x: 6,
      },
      labels: {
        align: 'left',
        padding: 0,
      },
      opposite: true,
      top: '90%',
      height: '10%',
    },
  ],
  tooltip: {
    enabled: true,
    shared: true,
    crosshairs: true,
    dateTimeLabelFormats: {
      millisecond: '%H:%M',
      second: '%H:%M',
      minute: '%H:%M',
      hour: '%H:%M',
      day: '%m-%d',
      week: '%m-%d',
      month: '%Y-%m',
    },
    useHTML: true,
  },
  credits: {
    enabled: false,
  },
  legend: {
    layout: 'horizontal',
    align: 'center',
    verticalAlign: 'bottom',
    enabled: true,
    margin: 0,
    padding: 0,
    itemWidth: 120,
    symbolWidth: 10,
    itemStyle: {
      fontSize: '11px',
    },
  },
  exporting: {
    enabled: false,
    buttons: {
      contextButton: {
        menuItems: [],
      },
    },
  },
  plotOptions: {
    line: {
      lineWidth: 1,
      states: {
        hover: {
          lineWidth: 2,
        },
      },
      marker: {
        enabled: false,
        symbol: 'diamond',
      },
    },
    area: {
      marker: {
        enabled: false,
      },
    },
    column: {
      minPointLength: 10,
      borderWidth: 0,
      pointWidth: 10,
      // pointPadding: 50,
      // groupPadding: 50,
      dataLabels: {
        enabled: true,
      },
    },

    series: {
      states: {
        inactive: {
          enabled: false,
        },
      },
    },
  },
  annotations: [],
  series: [],
  responsive: {
    // 通过设定不同的响应规则来实现对图表在不同尺寸下的响应
    rules: [
      {
        condition: {
          maxWidth: 500,
        },
        chartOptions: {
          legend: {
            layout: 'horizontal',
            align: 'center',
            verticalAlign: 'bottom',
          },
        },
      },
    ],
  },
};

三、包裹highcharts的div宽度变化时,需要重绘,因此需要监听el

1、resize-event.js

import ResizeObserver from 'resize-observer-polyfill';
/* istanbul ignore next */
// eslint-disable-next-line func-names
const resizeHandler = function (entries) {
  // eslint-disable-next-line no-restricted-syntax
  for (const entry of entries) {
    const listeners = entry.target.__resizeListeners__ || [];
    if (listeners.length) {
      listeners.forEach((fn) => {
        fn();
      });
    }
  }
};

/* istanbul ignore next */
// eslint-disable-next-line func-names
export const addResizeListener = function (element, fn) {
  if (!element.__resizeListeners__) {
    // eslint-disable-next-line no-param-reassign
    element.__resizeListeners__ = [];
    // eslint-disable-next-line no-param-reassign
    element.__ro__ = new ResizeObserver(resizeHandler);
    element.__ro__.observe(element);
  }
  element.__resizeListeners__.push(fn);
};

/* istanbul ignore next */
// eslint-disable-next-line func-names
export const removeResizeListener = function (element, fn) {
  if (!element || !element.__resizeListeners__) return;
  element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
  if (!element.__resizeListeners__.length) {
    element.__ro__.disconnect();
  }
};

// eslint-disable-next-line func-names
export const clearResizeListener = function (element) {
  if (!element || !element.__resizeListeners__) return;
  // eslint-disable-next-line no-param-reassign
  element.__resizeListeners__ = [];
  element.__ro__.disconnect();
};

2、debounce.js

// Some functions take a variable number of arguments, or a few expected
// arguments at the beginning and then a variable number of values to operate
// on. This helper accumulates all remaining arguments past the function’s
// argument length (or an explicit `startIndex`), into an array that becomes
// the last argument. Similar to ES6’s "rest parameter".
// eslint-disable-next-line func-names
const restArguments = function (func, startIndex) {
  // eslint-disable-next-line no-param-reassign
  startIndex = startIndex == null ? func.length - 1 : +startIndex;
  // eslint-disable-next-line func-names
  return function () {
    const length = Math.max(arguments.length - startIndex, 0);
    const rest = Array(length);
    let index = 0;
    for (; index < length; index++) {
      // eslint-disable-next-line prefer-rest-params
      rest[index] = arguments[index + startIndex];
    }
    // eslint-disable-next-line default-case
    switch (startIndex) {
      case 0:
        return func.call(this, rest);
      // eslint-disable-next-line prefer-rest-params
      case 1:
        return func.call(this, arguments[0], rest);
      // eslint-disable-next-line prefer-rest-params
      case 2:
        return func.call(this, arguments[0], arguments[1], rest);
    }
    const args = Array(startIndex + 1);
    for (index = 0; index < startIndex; index++) {
      // eslint-disable-next-line prefer-rest-params
      args[index] = arguments[index];
    }
    args[startIndex] = rest;
    return func.apply(this, args);
  };
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
const delay = restArguments((func, wait, args) => {
  return setTimeout(() => {
    // eslint-disable-next-line prefer-spread
    return func.apply(null, args);
  }, wait);
});
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
// eslint-disable-next-line func-names
const debounce = function (func, wait, immediate) {
  let timeout;
  let result;

  // eslint-disable-next-line func-names
  const later = function (context, args) {
    timeout = null;
    if (args) result = func.apply(context, args);
  };

  // eslint-disable-next-line func-names
  const debounced = restArguments(function (args) {
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      const callNow = !timeout;
      timeout = setTimeout(later, wait);
      if (callNow) result = func.apply(this, args);
    } else {
      timeout = delay(later, wait, this, args);
    }

    return result;
  });

  // eslint-disable-next-line func-names
  debounced.cancel = function () {
    clearTimeout(timeout);
    timeout = null;
  };

  return debounced;
};

export default debounce;

四、依赖库版本(package.json)

{
  "name": "tdesign-web-vue2-template",
  "version": "0.0.1",
  "scripts": {
    "dev:mock": "vite --open --mode mock",
    "dev": "vite --open --mode development",
    "dev:linux": "vite --mode developmenet",
    "build:test": "vite build --mode test",
    "build": "vite build --mode release",
    "serve": "vite preview",
    "lint": "eslint --ext .vue,.js,.jsx,.ts,.tsx ./ --max-warnings 0",
    "lint:fix": "eslint --ext .vue,.js,jsx,.ts,.tsx ./ --max-warnings 0 --fix",
    "stylelint": "stylelint src/**/*.{html,vue,sass,less}",
    "stylelint:fix": "stylelint --cache --fix src/**/*.{html,vue,vss,sass,less}",
    "test": "jest --coverage"
  },
  "dependencies": {
    "@types/lodash": "^4.14.176",
    "@vitejs/plugin-legacy": "^1.5.3",
    "dayjs": "^1.10.6",
    "highcharts": "^9.3.3",
    "highcharts-no-data-to-display": "^0.1.7",
    "insert-css": "^2.0.0",
    "lint-staged": "^10.5.4",
    "lodash": "^4.17.21",
    "nprogress": "^0.2.0",
    "qrcode.vue": "^1.7.0",
    "resize-observer-polyfill": "^1.5.1",
    "tdesign-icons-vue": "^0.0.8",
    "tdesign-vue": "^0.41.3",
    "typescript": "^4.2.4",
    "vite-plugin-vue2-svg": "^0.1.8",
    "vue": "^2.6.11",
    "vuedraggable": "^2.24.3",
    "vuex": "^3.6.2"
  },
  "devDependencies": {
    "@babel/core": "^7.16.0",
    "@babel/preset-env": "^7.16.4",
    "@babel/preset-typescript": "^7.16.0",
    "@commitlint/cli": "^12.0.1",
    "@commitlint/config-conventional": "^12.0.1",
    "@rollup/plugin-dynamic-import-vars": "^1.1.1",
    "@types/jest": "^27.0.3",
    "@typescript-eslint/eslint-plugin": "^4.19.0",
    "@typescript-eslint/parser": "^4.19.0",
    "@vue/test-utils": "^1.3.0",
    "axios": "^0.21.1",
    "babel-jest": "^27.4.2",
    "commitizen": "^4.2.3",
    "eslint": "^7.22.0",
    "eslint-config-airbnb-base": "^14.2.1",
    "eslint-config-prettier": "^8.3.0",
    "eslint-import-resolver-alias": "^1.1.2",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-vue": "^7.8.0",
    "http-proxy-agent": "^4.0.1",
    "husky": "^4.2.5",
    "jest": "^27.4.3",
    "less": "^4.1.0",
    "less-loader": "^7.2.1",
    "less-vars-to-js": "^1.3.0",
    "mockjs": "^1.1.0",
    "prettier": "^2.3.2",
    "rollup-plugin-visualizer": "^5.5.4",
    "stylelint": "^13.13.1",
    "stylelint-config-airbnb": "0.0.0",
    "stylelint-order": "^4.1.0",
    "stylelint-scss": "^3.20.1",
    "ts-jest": "^27.1.0",
    "vite": "2.5.10",
    "vite-plugin-environment": "^1.1.0",
    "vite-plugin-mock": "^2.3.0",
    "vite-plugin-theme": "^0.8.1",
    "vite-plugin-vue2": "^1.2.2",
    "vue-clipboard2": "^0.3.1",
    "vue-jest": "^3.0.7",
    "vue-router": "^3.5.1",
    "vuex-router-sync": "^5.0.0"
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  },
  "bit": {
    "env": {},
    "componentsDefaultDirectory": "components/{name}",
    "packageManager": "npm"
  }
}

你可能感兴趣的:(highcharts,vue.js,前端,highcharts)