ant-design-vue + Highcharts 实现数据大屏

前言

最近,在工作中开发了一个数据大屏的展示系统,主要是将一些业务的关键指标以数据可视化的方式展示,供运营和数据分析的同学使用。本文梳理了整个系统前端实现的一些步骤点,并基于此实现了一个简单的 Demo,在文末处附上,感兴趣的同学可以先看下效果。

设计选型

项目前期配置

在使用 Vue CLI 创建空项目后,在 src 目录下创建以下文件夹。

javascript 复制代码
src
├─ App.vue
├─ assets
├─ components
├─ config
├─ core
├─ layouts
├─ main.js
├─ mixins
├─ mock
├─ router
├─ store
└─ views

各文件夹的主要作用:

  • components:业务组件
  • config:侧栏菜单配置项,路由组件配置
  • core:按需引入组件配置
  • layouts:通用布局和容器组件
  • mixins:组件抽离出来的公用组件
  • mock:页面调试假数据
  • router:路由引入配置
  • store:vuex 相关文件
  • views:视图组件

关键点

组件按需加载

参考 按需加载,通过 babel-plugin-import 插件实现。在 src/core/component_use.js 文件夹下,配置按需引入的组件,并在 main.js 中组件配置文件。

javascript 复制代码
import Vue from "vue";
import {
  Button
  ...
} from "ant-design-vue";

Vue.use(Button);

组件国际化配置

通过国际化配置,组件的显示文案都会配置成中文。

javascript 复制代码
<template>
  <a-config-provider :locale="zhCN">
    <div id="app">
      <router-view/>
    </div>
  </a-config-provider>
</template>

<script>
import zhCN from "ant-design-vue/lib/locale-provider/zh_CN";

export default {
  data() {
    return {
      zhCN
    };
  }
};
</script>

图表组件

图表组件是基于原生 highcharts 进行封装的,没有使用 highcharts-vue 针对 vue 的扩展包。原因如下: 在 highcharts-vue 中使用 refs 等引用方式调用 highcharts 内置的函数存在限制,一些图表操作需要使用 trick 方法才能实现。官方 README 中也有说明:

封装思路

以堆叠柱状图为例

组件模板

xml 复制代码
<template>
  <div class="stack-column-chart"></div>
</template>

script

kotlin 复制代码
import { chartMixin } from "@/mixins/chart_mixin";
var Highcharts = require("highcharts");

export default {
  name: "StackColumnChart",
  mixins: [chartMixin],
  props: {
    series: {
      type: [Array, Object],
      required: true
    }
  },
  data() {
    return {
      stackColumnChartRef: undefined,
      seriesData: []
    };
  },
  watch: {
    series: {
      immediate: true,
      handler(newVal) {
        setTimeout(() => {
          if (newVal && newVal.length > 0) {
            const seriesData = [...newVal];
            this.seriesData = seriesData;
            this.stackColumnChartRef && this.updateChartData(this.seriesData);
            this.stackColumnChartRef && this.stackColumnChartRef.reflow();
          }
        }, 0);
      }
    }
  },
  methods: {
    generateChartOptions() {
      return Highcharts.chart(this.$el, {
        chart: {},
        title: {},
        xAxis: {},
        yAxis: {},
        legend: {},
        plotOptions: {},
        series: []
      });
    },
    removeChartSeries() {
      // 移除历史 series
      if (this.stackColumnChartRef) {
        while (this.stackColumnChartRef.series.length) {
          this.stackColumnChartRef.series[0].remove();
        }
      }
    },
    initChart() {
      this.stackColumnChartRef = this.generateChartOptions(this.chartType);
      this.initChartRef(this.stackColumnChartRef);
    },
    updateChartData(data) {
      // 移除历史 series
      this.removeChartSeries();

      // 方式一
      // const len = data.length || 0
      // for (let i = 0; i < len; i++) {
      //   this.stackColumnChartRef &&
      //   this.stackColumnChartRef.addSeries({
      //     name: data[i].name,
      //     data: data[i].data
      //   })
      // }

      // // 方式二(默认 addSeries 操作会触发 redraw 重绘,置为 false 则将不会触发重绘)
      const len = data.length || 0;
      for (let i = 0; i < len; i++) {
        this.stackColumnChartRef &&
          this.stackColumnChartRef.addSeries(
            {
              name: data[i].name,
              data: []
            },
            false
          );

        this.stackColumnChartRef &&
          this.stackColumnChartRef.series[i].update(
            {
              data: data[i].data
            },
            false
          );
      }

      // 单独触发一次重绘
      this.stackColumnChartRef.redraw();
    }
  },
  mounted() {
    this.initChart();

    setTimeout(() => {
      this.stackColumnChartRef.reflow();
    }, 0);
  }
};
</script>
  1. 图表初始化:Highchart.chart(this.$el, { 配置对象 }) 会返回一个引用。可以通过该引用来调用图表相关的方法。
  • 图表重绘,自适应:reflowredraw
  • series 数据新增:addSeries
  • series 数据更新:series[i].update
  • series 历史数据清除:series[i].remove

业务方案

全局图表组件自适应

在上一步中,可以通过 redraw/reflow 方法来实现图表自适应。在项目中存在多种类型的图表时,可将 reflow 相应逻辑抽离出来,封装成一个 mixinchart_mixin 如下:

javascript 复制代码
import { mapState } from "vuex";

export const chartMixin = {
  data() {
    return {
      chartRef: undefined
    };
  },
  computed: {
    ...mapState({
      collapseTriggerCount: state => state.app.collapseTriggerCount
    })
  },
  watch: {
    collapseTriggerCount: {
      immediate: true,
      deep: true,
      handler() {
        this.resizeChart(this.chartRef);
      }
    }
  },
  methods: {
    initChartRef(ref) {
      this.chartRef = ref;
    },
    resizeChart(ref) {
      setTimeout(() => {
        ref && ref.reflow();
      }, 100);
    }
  }
};

reflow 方法需要在 setTimeout 调用。

图表渲染卡顿

在堆叠柱状图组件的实现中,发现当数据量相对大时,图表的渲染耗时比较长,并且图表的渲染会阻塞页面的事件相应,导致整个页面非常卡顿,如下:

上述图中,通过 addSeries 默认方式更新图表数据时,平均每个图的绘制需要耗时 3s。当使用在多 Tab 组件中进行图表绘制时。假设有 3 个 Tab。页面会存在着约 10 s 的非正常响应时间,体验非常之差。

优化方法

  • 减少重绘次数,通过设置 addSeries 的第二个参数为 false,更新数据后不触发重绘。在数据更新完毕后,再通过 redraw 方法触发重绘。

其他触发重绘的方法:addPoint

  • 关闭 dataLabels 设置

为了计算 dataLabels 的位置,highcharts 会对每列的 dataLabel 进行定位运算,以确定它的位置。在数据量较大时,耗时也是非常多的。

javascript 复制代码
plotOptions: {
    column: {
        stacking: 'normal',
        dataLabels: {
            enabled: false
        }
    }
  }
  • 按需加载与 loading 状态

主要是给用户呈现一种数据在加载中的效果。

  1. 在多 Tab 组件中,只在切换 Tab 时,才去更新图表数据。
  2. 添加 loading 状态

通过上述的 3 个方式,在图表加载数据时,已经不会有卡顿的现象。单图的绘制时长也控制在了 1 ~ 2s 的范围。

源码

  1. CodeSandbox: highcharts-dashboard
  2. github: highcharts-dashboard
相关推荐
新人11yj46 分钟前
如何给网页增加滚动到顶部的功能
前端·javascript
掘金一周6 分钟前
Figma Dev Mode MCP:大人,时代变了 | 掘金一周7.10
前端·人工智能·mcp
Data_Adventure11 分钟前
推荐几款开源 Canvas 和 WebGL 图形库
前端·webgl·canvas
我爱加班、、25 分钟前
element-plus表单校验失败问题
前端·javascript·vue.js·elementui·ecmascript
香香甜甜的辣椒炒肉30 分钟前
vue快速上手
前端·javascript·vue.js
b1gbrother1 小时前
让你的Claude Code变得更聪明
前端·程序员
国家不保护废物1 小时前
多模态模型数据传输的秘密武器:html5对象Blob深度解析
前端·面试·html
用户2519162427111 小时前
Canvas之概述,画布与画笔
前端·javascript·canvas
南方kenny1 小时前
前端小知识:搞懂 BFC块级格式化上下文,告别面试“拦路虎”!
前端·css·面试
邵洛1 小时前
前端导出excel表格并修改导出表格样式
前端