🔥 用 Vue3 + HTML Canvas 实现动态数据可视化图表生成器,解决我的日常工作痛点!

313 阅读4分钟

最近在工作中,我遇到了一个需求:需要为销售团队开发一个动态数据可视化工具,能够将每月的销售数据快速生成图表,并支持导出为图片,方便他们在报告和分享中使用。作为一个前端开发者,我决定用 Vue3 和 HTML Canvas 来实现这个功能。今天,我就来分享一下我的开发过程和解决的一些实际问题。


1. 需求背景

在我们的业务中,销售团队每月都会收集大量的销售数据,包括每个产品的销售额、增长率等。以前,他们通常使用 Excel 来制作图表,但这种方式效率低下,且无法动态更新。我的任务是开发一个工具,能够:

  1. 动态生成图表:支持柱状图和折线图。
  2. 实时更新:用户输入数据后,图表能够立即更新。
  3. 导出图片:生成的图表可以保存为图片,方便分享。
  4. 支持多图表类型:根据需求切换不同的图表类型。
  5. 自定义样式:允许用户调整图表的颜色、字体等样式。

2. 技术选型

为了实现这个需求,我选择了以下技术:

  • Vue3:用于构建用户界面,利用其响应式特性实现数据的动态绑定。
  • HTML Canvas:用于绘制图表,性能优异且无需依赖第三方库。
  • Vite:作为构建工具,提升开发效率。

3. 实现过程

3.1 初始化项目

我使用 Vite 快速初始化了一个 Vue3 项目:

npm create vite@latest sales-chart-tool --template vue
cd sales-chart-tool
npm install
npm run dev

3.2 创建 Canvas 画布

在 Vue 组件中,我创建了一个 Canvas 画布,并设置了画布尺寸:

<template>
  <div class="chart-container">
    <canvas ref="chartCanvas" width="800" height="400"></canvas>
    <div class="controls">
      <input v-model="chartData" placeholder="输入数据,例如:10,20,30,40" />
      <select v-model="chartType">
        <option value="bar">柱状图</option>
        <option value="line">折线图</option>
      </select>
      <button @click="drawChart">生成图表</button>
      <button @click="saveChart">保存图表</button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const chartCanvas = ref(null);
const chartData = ref('10,20,30,40'); // 默认数据
const chartType = ref('bar'); // 默认图表类型

onMounted(() => {
  drawChart();
});
</script>

<style>
.chart-container {
  text-align: center;
}
canvas {
  border: 1px solid #ccc;
}
.controls {
  margin-top: 20px;
}
input, select {
  padding: 8px;
  margin-right: 10px;
}
button {
  padding: 8px 16px;
  cursor: pointer;
}
</style>

3.3 绘制柱状图

在 drawChart 方法中,我实现了柱状图的绘制逻辑。这里的关键是动态计算每个柱子的高度和位置:

const drawChart = () => {
  const canvas = chartCanvas.value;
  const ctx = canvas.getContext('2d');
  const data = chartData.value.split(',').map(Number); // 将输入数据转换为数组

  // 清空画布
  ctx.clearRect(0, 0, 800, 400);

  // 设置背景色
  ctx.fillStyle = '#f9f9f9';
  ctx.fillRect(0, 0, 800, 400);

  // 绘制坐标轴
  ctx.strokeStyle = '#333';
  ctx.beginPath();
  ctx.moveTo(50, 350); // X 轴
  ctx.lineTo(750, 350);
  ctx.moveTo(50, 350); // Y 轴
  ctx.lineTo(50, 50);
  ctx.stroke();

  // 绘制柱状图
  const barWidth = 40;
  const gap = 20;
  const maxValue = Math.max(...data);
  data.forEach((value, index) => {
    const x = 80 + (barWidth + gap) * index;
    const y = 350 - (value / maxValue) * 300; // 根据数据比例计算高度
    ctx.fillStyle = '#007bff';
    ctx.fillRect(x, y, barWidth, 350 - y);
  });

  // 绘制数据标签
  ctx.fillStyle = '#333';
  ctx.font = '14px Arial';
  data.forEach((value, index) => {
    const x = 80 + (barWidth + gap) * index + barWidth / 2;
    ctx.fillText(value, x, 370);
  });
};

3.4 绘制折线图

为了满足销售团队的需求,我还增加了折线图的绘制逻辑:

const drawChart = () => {
  const canvas = chartCanvas.value;
  const ctx = canvas.getContext('2d');
  const data = chartData.value.split(',').map(Number);

  // 清空画布
  ctx.clearRect(0, 0, 800, 400);

  // 设置背景色
  ctx.fillStyle = '#f9f9f9';
  ctx.fillRect(0, 0, 800, 400);

  // 绘制坐标轴
  ctx.strokeStyle = '#333';
  ctx.beginPath();
  ctx.moveTo(50, 350); // X 轴
  ctx.lineTo(750, 350);
  ctx.moveTo(50, 350); // Y 轴
  ctx.lineTo(50, 50);
  ctx.stroke();

  // 绘制折线图
  ctx.strokeStyle = '#ff6347';
  ctx.beginPath();
  const maxValue = Math.max(...data);
  data.forEach((value, index) => {
    const x = 80 + (100 * index);
    const y = 350 - (value / maxValue) * 300;
    if (index === 0) {
      ctx.moveTo(x, y);
    } else {
      ctx.lineTo(x, y);
    }
  });
  ctx.stroke();

  // 绘制数据点
  ctx.fillStyle = '#ff6347';
  data.forEach((value, index) => {
    const x = 80 + (100 * index);
    const y = 350 - (value / maxValue) * 300;
    ctx.beginPath();
    ctx.arc(x, y, 5, 0, Math.PI * 2);
    ctx.fill();
  });

  // 绘制数据标签
  ctx.fillStyle = '#333';
  ctx.font = '14px Arial';
  data.forEach((value, index) => {
    const x = 80 + (100 * index);
    ctx.fillText(value, x, 370);
  });
};

3.5 保存图表

为了方便销售团队使用,我增加了导出图表的功能:

const saveChart = () => {
  const canvas = chartCanvas.value;
  const link = document.createElement('a');
  link.href = canvas.toDataURL('image/png');
  link.download = 'chart.png';
  link.click();
};

4. 实际应用效果

这个工具上线后,销售团队的反馈非常好。他们可以快速输入数据,生成图表,并导出为图片用于报告和分享。以下是几个实际应用场景:

  • 月度销售报告:输入每月的销售额数据,生成柱状图。
  • 用户增长趋势:输入每日或每月的用户增长数据,生成折线图。

希望能给大家带来灵感,