前端小白也能画出炫酷图表:D3.js和Chart.js实战指南(附避坑技巧)

前端小白也能画出炫酷图表:D3.js和Chart.js实战指南(附避坑技巧)

说实话啊,我刚入行那会儿最怕的就是产品经理突然拍我肩膀:“哎那个谁,下周汇报材料里加几个数据图表呗,要炫酷一点的。”

我当时心里就一万头草泥马奔腾而过——炫酷?我连 CSS 都写不利索你让我炫酷?但嘴上还得笑嘻嘻:“好的好的没问题。”

回去就打开 Excel 截图,调个颜色,往 PPT 里一贴。结果第二天被老板打回来:“这什么玩意儿?上世纪风格?我们要科技感!”

行吧,被逼上梁山,这才开始正儿八经研究前端绘图库。今天就把我这几年踩过的坑、流过的泪,还有偷学到的骚操作,一股脑儿倒给你们。


为啥前端总被喊去"做个好看点的图"

这事儿吧,得从公司的权力结构说起。

数据分析师会 SQL 会 Python,但让他们写个网页?那是要了他们老命。UI 设计师能画出天仙一样的效果图,但你要他们绑定真实数据?立马眼神飘忽说"这个得找开发"。

最后这锅就扣在前端头上了。

更惨的是,老板们对"好看"的定义飘忽不定。今天说要"科技感",明天要"商务风",后天突然来一句"能不能像苹果发布会那种?"——大哥,苹果为了一个转场动画能优化三个月,我就一礼拜啊!

所以咱们得手里有粮心里不慌。掌握几个趁手的绘图库,下次再被点名的时候,至少能从容地打开 VS Code,而不是偷偷摸摸去搜"Excel 图表怎么导出高清图"。


别再用 Excel 截图糊弄人了

我知道,我知道,Excel 截图多省事啊。选中数据,插入图表,调个颜色,截图,完事儿。

但你想过没有,这玩意儿是静态的!数据更新了怎么办?重新截图?老板说要换个颜色呢?再截图?客户问能不能交互一下,点点看详细数据?你让人家对着图片干瞪眼?

而且说实话,Excel 默认那个配色… 蓝不蓝紫不紫的,放在 PPT 里还行,往网页上一贴,立马暴露土味审美。

前端绘图库就不一样了。数据是活的,样式是代码控制的,交互是自带的。最重要的是——显得专业

下次汇报的时候,你打开浏览器,现场输入几个参数,图表实时变化,旁边的产品经理眼睛都直了。这逼装得,值回票价。


D3.js:灵活到能画宇宙,但上手像在造火箭

第一次打开 D3.js 官网的时候,我整个人都懵了。

这啥啊?满屏的 selectAll().data().enter().append(),跟天书似的。我当时就想,我不就想画个柱状图吗,至于吗?

但用熟了之后,真香。

D3 的牛逼之处在于,它不是在"封装图表",而是在"给你画笔"。你想画什么都能画——柱状图、折线图、力导向图、地图、甚至那种很炫的数据新闻可视化,纽约时报那种级别的,D3 都能搞。

问题是,自由度高意味着你要操心的事儿也多。坐标轴自己画,刻度自己算,动画自己写,连图例都得一行行代码拼出来。

我给你们看段代码感受一下,这是用 D3 画个最简单的柱状图:

// 先准备数据,就几个数字
const data = [30, 10, 50, 20, 80];

// 设置画布大小,别问为啥 margin 这么写,D3 社区惯例
const margin = { top: 20, right: 30, bottom: 40, left: 40 };
const width = 600 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;

// 创建 SVG 容器,这一步就是往 body 里塞个 svg 标签
const svg = d3.select("body")
  .append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")  // 加个 g 标签,相当于分组,后面所有元素都放这里面
  .attr("transform", `translate(${margin.left},${margin.top})`);

// 定义 x 轴比例尺,band 就是带状比例尺,适合离散数据
const x = d3.scaleBand()
  .domain(data.map((d, i) => i))  // 域名是索引 0,1,2,3,4
  .range([0, width])              // 范围是 0 到画布宽
  .padding(0.2);                  // 柱子之间留 20% 空隙,不然挤在一起丑死

// 定义 y 轴比例尺,linear 是线性比例尺,适合连续数据
const y = d3.scaleLinear()
  .domain([0, 100])  // 假设数据最大 100,实际项目里应该用 d3.max 算
  .range([height, 0]);  // 注意这里反过来,因为 SVG 坐标系 y 轴向下

// 画 x 轴,tickSizeOuter(0) 去掉两端的刻度线,看着干净点
svg.append("g")
  .attr("transform", `translate(0,${height})`)  // 移到最下面
  .call(d3.axisBottom(x))
  .selectAll("text")
  .style("text-anchor", "middle");

// 画 y 轴
svg.append("g")
  .call(d3.axisLeft(y));

// 终于开始画柱子了!这里用 enter 模式,是 D3 的数据绑定精髓
svg.selectAll("rect")
  .data(data)
  .enter()
  .append("rect")
  .attr("x", (d, i) => x(i))           // x 坐标用比例尺算
  .attr("y", d => y(d))                // y 坐标,注意 y(d) 得到的是顶部的位置
  .attr("width", x.bandwidth())        // 柱子宽度,band 比例尺自带的方法
  .attr("height", d => height - y(d))  // 高度要算一下,因为 SVG 是从上往下画的
  .attr("fill", "#69b3a2");            // 来个原谅绿,护眼

// 加个鼠标悬停效果,不然太素了
svg.selectAll("rect")
  .on("mouseover", function() {
    d3.select(this).attr("fill", "#404080");  // 变个紫色
  })
  .on("mouseout", function() {
    d3.select(this).attr("fill", "#69b3a2");  // 变回去
  });

看到没?就这么几柱子,写了这么多代码。而且这还没加动画、没加 tooltip、没响应式。要是用 Chart.js,三行就搞定了。

但 D3 的好处是,每一行你都能控制。想改柱子的圆角?加个 rx 属性。想加渐变?定义个 linearGradient。想把柱子换成三角形?改 append("rect")append("path") 自己画路径。

这就是 D3 的哲学:给你绝对的自由,但你也得绝对负责


Chart.js:开箱即用小甜心

如果说 D3 是手动挡跑车,Chart.js 就是自动挡家用车。你不用懂发动机原理,踩油门就走。

我第一次用 Chart.js 的时候,感动得差点哭出来。原来画个图可以这么简单?

// 就这几行,真的就这几行
const ctx = document.getElementById('myChart').getContext('2d');

new Chart(ctx, {
  type: 'bar',  // 柱状图,想换折线图就改成 'line'
  data: {
    labels: ['一月', '二月', '三月', '四月', '五月'],  // x 轴标签
    datasets: [{
      label: '销售额',
      data: [30, 10, 50, 20, 80],  // 数据
      backgroundColor: [  // 每个柱子不同颜色,花里胡哨的
        'rgba(255, 99, 132, 0.6)',
        'rgba(54, 162, 235, 0.6)',
        'rgba(255, 206, 86, 0.6)',
        'rgba(75, 192, 192, 0.6)',
        'rgba(153, 102, 255, 0.6)'
      ],
      borderColor: [  // 边框也来一套
        'rgba(255, 99, 132, 1)',
        'rgba(54, 162, 235, 1)',
        'rgba(255, 206, 86, 1)',
        'rgba(75, 192, 192, 1)',
        'rgba(153, 102, 255, 1)'
      ],
      borderWidth: 2
    }]
  },
  options: {
    responsive: true,  // 响应式,自动适应容器大小,感动!
    scales: {
      y: {
        beginAtZero: true  // y 轴从 0 开始,不然数据看着会骗人
      }
    },
    plugins: {
      legend: {
        display: true,
        position: 'top'
      },
      tooltip: {
        enabled: true,  // 鼠标悬停显示数值,自带的!
        mode: 'index',
        intersect: false
      }
    }
  }
});

完事儿了。真的完事儿了。动画是自带的,tooltip 是自带的,响应式是自带的,图例也是自带的。

我当时就想,之前用 D3 写那几百行代码是图啥?就为了显得我代码量大吗?

但用久了也发现,Chart.js 的"开箱即用"是有代价的。你想做点稍微复杂的东西,比如把柱子改成圆角的,或者加个自定义的交互,就开始有点别扭了。它给你封装好了 90% 的场景,但剩下 10% 的个性化需求,你得去翻文档找配置项,有时候还得写插件。


ECharts、ApexCharts 这些"别人家的孩子"

虽然标题说是 D3 和 Chart.js 的实战,但咱也得客观一点,市面上还有其他选择。

ECharts 是百度出品的,在国内用得特别多。它的优势是配置项极其丰富,基本上你能想到的图表类型它都有,而且文档是中文的,对英语不好的同学友好。但问题是,它有点"重",体积大,而且那个配置项的嵌套层级… 我反正经常配着配着就把自己绕进去了。

ApexCharts 是后起之秀,颜值很高,动画效果很丝滑,而且支持 Vue、React 的封装。如果你项目里用的是现代框架,而且追求视觉效果,可以看看这个。

Highcharts 是老牌子了,商业项目用得很多,但它是商业软件,免费版有水印,公司用的话得买授权,贵。

所以你看,选型这事儿真得看场景。没有最好的,只有最合适的。


D3.js 到底强在哪:数据驱动一切

前面说了 D3 难学,但为啥还有那么多人死磕?因为它那个"数据驱动"的理念,一旦理解了,真的爽。

传统的 DOM 操作是命令式的:先拿到元素,然后改它的属性。但 D3 是声明式的:你告诉我数据长什么样,我帮你把 DOM 变成那样。

这里面的核心就是三个方法:enter()update()exit()

// 假设我们有一组数据,一开始是 5 个数字
let data = [30, 10, 50, 20, 80];

// 绑定数据,画柱子,这段前面写过
function render(data) {
  const bars = svg.selectAll("rect")
    .data(data, (d, i) => i);  // 第二个参数是 key 函数,用来识别数据身份

  // enter:数据比元素多,需要新增元素
  bars.enter()
    .append("rect")
    .attr("x", (d, i) => x(i))
    .attr("y", height)  // 从底部开始
    .attr("width", x.bandwidth())
    .attr("height", 0)  // 高度为 0
    .attr("fill", "#69b3a2")
    .merge(bars)  // 和现有的元素合并,一起更新
    .transition()  // 加个过渡动画,舒服
    .duration(750)
    .attr("y", d => y(d))
    .attr("height", d => height - y(d));

  // exit:数据比元素少,需要删除元素
  bars.exit()
    .transition()
    .duration(750)
    .attr("y", height)
    .attr("height", 0)
    .remove();  // 动画结束后删掉 DOM
}

// 第一次渲染
render(data);

// 过两秒数据变了,变成 3 个数字
setTimeout(() => {
  data = [40, 60, 30];
  render(data);  // 再次调用,D3 会自动处理 enter/update/exit
}, 2000);

看到没?数据变了,DOM 自动跟着变,不用你手动去增删改。这就是数据驱动的魅力。

而且 D3 不只是能操作 SVG,它还能算数据。各种比例尺、插值器、布局算法,都是宝藏。比如你想画个力导向图,节点之间的引力和斥力,D3 都给你算好了:

// 力导向图模拟,这玩意儿自己写能写秃头
const simulation = d3.forceSimulation(nodes)
  .force("link", d3.forceLink(links).id(d => d.id).distance(100))  // 连线有距离
  .force("charge", d3.forceManyBody().strength(-300))  // 节点间有斥力,负值越大斥力越强
  .force("center", d3.forceCenter(width / 2, height / 2))  // 整体向中心靠拢
  .on("tick", () => {
    // 每一帧更新位置
    link
      .attr("x1", d => d.source.x)
      .attr("y1", d => d.source.y)
      .attr("x2", d => d.target.x)
      .attr("y2", d => d.target.y);
    
    node
      .attr("cx", d => d.x)
      .attr("cy", d => d.y);
  });

// 加上拖拽,让用户能手动调整
node.call(d3.drag()
  .on("start", (event, d) => {
    if (!event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
  })
  .on("drag", (event, d) => {
    d.fx = event.x;
    d.fy = event.y;
  })
  .on("end", (event, d) => {
    if (!event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
  }));

这种复杂的物理模拟,用原生 Canvas 写能写几百行,D3 几行就搞定了。这就是专业库的价值。


SVG 和 Canvas 怎么选

D3 既能操作 SVG 也能操作 Canvas,但新手经常纠结用哪个。

SVG 是矢量图,每个图形都是一个 DOM 元素。好处是可以用 CSS 控制样式,可以绑定事件,放大不会模糊。坏处是元素太多的时候(比如几千个圆点),浏览器会卡。因为 DOM 操作本来就慢,几千个节点,浏览器渲染树都建半天。

Canvas 是位图,就一个 <canvas> 标签,所有图形都是像素点。好处是快,画几万个点都不眨眼。坏处是事件处理麻烦,你得自己算鼠标在哪个图形上,而且放大就模糊。

所以简单判断:数据点少于 1000 用 SVG,大于 5000 用 Canvas,中间的自己测一下

Chart.js 默认用 Canvas,所以性能比 D3 的 SVG 模式好。但 D3 也能用 Canvas,就是代码写起来麻烦点,得用 context.fillRect() 这种命令式 API。


Chart.js 为什么适合新手:配置项少到感人

我前面展示了 Chart.js 的基础用法,但真要用到生产环境,还得知道一些实用配置。

比如响应式,Chart.js 是默认开启的,但有时候容器大小变化了,图表没跟着变,为啥?因为你没给容器设相对高度:

<!-- 这样写,图表高度固定 400px,不会自适应 -->
<div style="height: 400px;">
  <canvas id="myChart"></canvas>
</div>

<!-- 这样写,容器高度随父元素,图表才能真正响应式 -->
<div style="position: relative; height: 40vh; width: 80vw;">
  <canvas id="myChart"></canvas>
</div>

还有那个烦人的 aspect ratio(宽高比)。Chart.js 默认保持 2:1 的宽高比,有时候你想让图表填满容器,它偏要留白。这时候得关掉:

options: {
  responsive: true,
  maintainAspectRatio: false  // 关掉固定比例,图表会填满容器
}

插件生态方面,Chart.js 不如 D3 丰富,但常用的都有。比如导出图片:

// 先引入插件
import ChartDataLabels from 'chartjs-plugin-datalabels';
import { Chart } from 'chart.js';

// 注册插件
Chart.register(ChartDataLabels);

// 配置里启用
options: {
  plugins: {
    datalabels: {
      anchor: 'end',
      align: 'top',
      formatter: (value) => value + '元',  // 在柱子上方显示数值
      font: {
        weight: 'bold'
      }
    }
  }
}

想要导出成 PNG?不用插件,原生支持:

// 拿到 canvas 元素,直接转 base64
const link = document.createElement('a');
link.download = '我的图表.png';
link.href = myChart.toBase64Image();
link.click();

就三行代码,香不香?


性能与维护性大比拼

说点实际的,公司项目里选技术栈,不能只看好不好用,还得考虑后期维护

D3 的自由度是双刃剑。你写的时候爽,各种骚操作,链式调用写得飞起。但过三个月回来看,或者换个同事接手,就傻眼了。

// 这种代码,写的时候觉得自己是艺术家
svg.selectAll(".node")
  .data(nodes)
  .enter().append("circle")
  .attr("class", "node")
  .attr("r", d => d.size ? d.size * 2 : 5)
  .attr("cx", d => d.x || width/2)
  .attr("cy", d => d.y || height/2)
  .style("fill", d => colorScale(d.group))
  .style("opacity", 0)
  .transition().duration(500).style("opacity", 1)
  .selection()
  .on("click", handleClick)
  .on("mouseover", function(event, d) {
    d3.select(this).transition().duration(200).attr("r", d => (d.size ? d.size * 2 : 5) + 5);
    showTooltip(event, d);
  })
  .on("mouseout", function(event, d) {
    d3.select(this).transition().duration(200).attr("r", d => d.size ? d.size * 2 : 5);
    hideTooltip();
  });

这还只是一段,实际项目里可能几百行这种代码,没注释的话,debug 的时候想死。

Chart.js 就好多了,配置对象结构清晰,改起来心里有底。但问题是,一旦需求超纲,比如要在图表里加个自定义的标记点,或者实现个复杂的交互逻辑,你就得写插件或者 hack,那时候代码反而更乱。

大数据量场景下,Chart.js 的 Canvas 渲染优势明显。我测过,同样 5000 个数据点,D3 用 SVG 模式,页面卡成 PPT;Chart.js 流畅得很。但 D3 切到 Canvas 模式,性能也不差,就是代码量翻倍。

所以团队协作的时候,如果组里 D3 高手多,可以用 D3;如果大家都是业务开发,图个稳定快速,Chart.js 更保险。


真实项目里怎么选:别为了炫技用 D3

我给大家列个决策树,下次被问到的时候直接甩给老板:

要画关系图、网络图、力导向图、地图? → D3.js,没别的选择,Chart.js 干不了这个。

要画常规的折线、柱状、饼图、雷达图? → Chart.js,三行代码搞定,别折腾。

数据量特别大,超过 1 万条? → 考虑 Canvas 方案,D3 的 Canvas 模式或者专门的库比如 Observable Plot。

需要高度定制化,每个像素都要控制? → D3,但评估一下维护成本。

内部管理后台,快速上线? → Chart.js 或者直接用现成的组件库比如 Ant Design Charts。

对外展示页,要惊艳效果? → D3 或者 ECharts,花点时间打磨动画。

老板说要"科技感"但说不清具体啥样? → 先拿 Chart.js 出个 demo,说"这是第一版",然后慢慢磨 D3 的高级版本,争取时间。

记住一句话:能用配置解决的,别写代码;能用简单库解决的,别上复杂库。你的 KPI 是交付功能,不是展示你 D3 玩得有多溜。


遇到图表不渲染怎么办:那些年我们踩过的坑

坑一:容器没宽高,图表是空白

这是新手最常踩的坑,而且控制台还不报错,你就看着页面上一个空白区域,怀疑人生。

// 错误示范
<div id="chart-container"></div>  <!-- 没设宽高,默认 0x0 -->

<script>
  const ctx = document.getElementById('myChart').getContext('2d');
  new Chart(ctx, { ... });  // 图表创建了,但看不见,因为容器是 0 高度
</script>

// 正确姿势
<div id="chart-container" style="height: 400px; width: 100%;"></div>

D3 也一样,SVG append 进去了,但宽高是 0,啥都看不见。

坑二:数据格式不对

Chart.js 对数据格式很宽容,但 D3 很严格。特别是时间序列数据:

// 错误:直接传字符串,D3 不知道这是日期
const data = [
  { date: "2023-01-01", value: 100 },
  { date: "2023-02-01", value: 200 }
];

// 正确:用 d3.timeParse 转成 Date 对象
const parseDate = d3.timeParse("%Y-%m-%d");
data.forEach(d => {
  d.date = parseDate(d.date);
});

// 然后比例尺要用 d3.scaleTime()
const x = d3.scaleTime()
  .domain(d3.extent(data, d => d.date))
  .range([0, width]);

还有数字格式,从后端拿到的数据可能是字符串 “100”,你要是没转成数字 100,D3 的 d3.max() 算出来可能不对,或者动画的时候出问题。

坑三:异步数据忘记 update

现在的项目数据都是异步拉的,你图表初始化的时候数据还没回来,等数据回来了,图表不会自动更新。

// 错误示范
const myChart = new Chart(ctx, {
  data: { labels: [], datasets: [{ data: [] }] }  // 先空着
});

fetch('/api/data')
  .then(res => res.json())
  .then(data => {
    // 直接改 data,Chart.js 不会自动刷新
    myChart.data.labels = data.labels;
    myChart.data.datasets[0].data = data.values;
    // 忘了调用 update()!
  });

// 正确姿势
fetch('/api/data')
  .then(res => res.json())
  .then(data => {
    myChart.data.labels = data.labels;
    myChart.data.datasets[0].data = data.values;
    myChart.update();  // 必须调用这个!
  });

// D3 的话,要重新调用 render 函数

坑四:IE 兼容性

都 2024 年了,还有公司要支持 IE11,你说气不气?

Chart.js 3.x 版本已经不支持 IE 了,2.x 还支持。D3 6.x 以上用了 ES2015 语法,IE 直接报错。如果你不幸要支持 IE,要么降版本,要么上 polyfill。

我的建议是:能劝退 IE 用户就劝退,实在劝不退的,给他们看静态图片吧,别折腾交互了。


让图表更"聪明"的开发技巧

用 useMemo 缓存 D3 生成的 SVG

如果你在 React 里用 D3,千万别每次渲染都重新创建 SVG,性能爆炸。

import React, { useRef, useEffect, useMemo } from 'react';
import * as d3 from 'd3';

function D3Chart({ data }) {
  const svgRef = useRef();

  // 用 useMemo 缓存比例尺,数据没变就不重新计算
  const scales = useMemo(() => {
    const x = d3.scaleBand()
      .domain(data.map((d, i) => i))
      .range([0, 500])
      .padding(0.2);
    
    const y = d3.scaleLinear()
      .domain([0, d3.max(data)])
      .range([300, 0]);
    
    return { x, y };
  }, [data]);

  useEffect(() => {
    // 这里只更新柱子,不重新创建 SVG
    const svg = d3.select(svgRef.current);
    
    svg.selectAll("rect")
      .data(data)
      .join(
        enter => enter.append("rect")
          .attr("x", (d, i) => scales.x(i))
          .attr("y", scales.y(0))
          .attr("width", scales.x.bandwidth())
          .attr("height", 0)
          .attr("fill", "steelblue")
          .call(enter => enter.transition().duration(750)
            .attr("y", d => scales.y(d))
            .attr("height", d => 300 - scales.y(d))
          ),
        update => update.call(update => update.transition().duration(750)
          .attr("x", (d, i) => scales.x(i))
          .attr("y", d => scales.y(d))
          .attr("width", scales.x.bandwidth())
          .attr("height", d => 300 - scales.y(d))
        ),
        exit => exit.call(exit => exit.transition().duration(750)
          .attr("y", 300)
          .attr("height", 0)
          .remove()
        )
      );
  }, [data, scales]);

  return <svg ref={svgRef} width={500} height={300}></svg>;
}

useMemo 帮你省掉重复计算,join 方法(D3 v6+)帮你优雅处理 enter/update/exit。

Chart.js 动态切换主题色

现在不都流行暗黑模式嘛,图表也得跟着变。

// 定义两套配色
const themes = {
  light: {
    background: '#ffffff',
    text: '#666666',
    grid: '#e0e0e0',
    colors: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF']
  },
  dark: {
    background: '#1a1a1a',
    text: '#cccccc',
    grid: '#333333',
    colors: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF']
  }
};

function updateTheme(themeName) {
  const theme = themes[themeName];
  const ctx = document.getElementById('myChart').getContext('2d');
  
  // 修改全局默认字体颜色
  Chart.defaults.color = theme.text;
  Chart.defaults.borderColor = theme.grid;
  
  // 修改具体图表配置
  myChart.data.datasets.forEach((dataset, i) => {
    dataset.backgroundColor = theme.colors[i];
    dataset.borderColor = theme.colors[i];
  });
  
  myChart.update();
}

// 监听系统主题变化
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
  updateTheme(e.matches ? 'dark' : 'light');
});

鼠标悬停防抖

图表里经常要监听鼠标移动显示 tooltip,但 mousemove 事件触发太频繁了,不做防抖性能直接崩。

// 简陋版防抖
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// 用在 D3 的 mousemove 上
svg.on("mousemove", debounce((event) => {
  const [x, y] = d3.pointer(event);
  // 算鼠标在哪个数据点上,显示 tooltip
  showTooltip(x, y);
}, 50));  // 50ms 延迟,肉眼基本无感知,但事件触发次数少很多

封装成可复用组件

别每次画图都 copy 代码,封装一下:

// 一个通用的 Chart.js 封装
class BaseChart {
  constructor(canvasId, type, options = {}) {
    this.ctx = document.getElementById(canvasId).getContext('2d');
    this.chart = new Chart(this.ctx, {
      type: type,
      data: {
        labels: [],
        datasets: []
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        ...options
      }
    });
  }

  setData(labels, data, datasetOptions = {}) {
    this.chart.data.labels = labels;
    this.chart.data.datasets = [{
      data: data,
      ...datasetOptions
    }];
    this.chart.update();
  }

  destroy() {
    this.chart.destroy();
  }
}

// 使用
const myBarChart = new BaseChart('chart1', 'bar', {
  scales: { y: { beginAtZero: true } }
});

myBarChart.setData(
  ['A', 'B', 'C'],
  [10, 20, 30],
  { backgroundColor: 'rgba(75, 192, 192, 0.6)' }
);

这样业务代码里只需要关心数据,不用重复写那些配置。


别卷了,你的图表真的需要动效吗

最后聊点哲学问题。

我刚学 D3 那会儿,特别迷恋动画。页面加载的时候柱子从下往上长,鼠标悬停的时候柱子放大旋转,数据更新的时候旧柱子飞出去新柱子飞进来… 觉得自己酷毙了。

直到有次在客户的老电脑上演示,i5 处理器配核显,8G 内存,我的"炫酷图表"卡成了 PPT。客户老板面无表情地看着屏幕,我汗流浃背地解释"可能是网络问题"。

从那以后我悟了:动画是调味品,不是主菜

Chart.js 默认的动画就挺克制,淡入淡出,不抢戏。D3 里你要是自己写,建议遵循几个原则:

入场动画可以有,让用户知道图表从哪里来的,有个心理预期。

更新动画要适度,数据变了的时候平滑过渡,但别超过 500ms,用户没耐心等。

交互动画要轻,鼠标悬停的时候变个色、加个阴影就够了,别搞旋转跳跃。

大数据量的时候关掉动画,几千个元素一起动,浏览器直接罢工。

而且说实话,很多场景下静态图更专业。比如打印成 PDF、截图发邮件、嵌入 PPT,动画根本没用,反而增加了出 bug 的概率。

记住,图表的目的是讲清楚数据故事,不是展示你的技术有多牛。老板要的是"一眼看懂趋势",不是"哇这柱子飞出来的轨迹好丝滑"。

克制,是一种更高级的技术自信。


写在最后

写这篇文章的时候,我想起了自己第一次用 D3 画出折线图的那个晚上。凌晨两点,终于把那条线画出来了,截图发到朋友圈,虽然根本没人点赞,但自己看着那条线扭来扭去,心里特别满足。

这几年前端可视化领域变化挺快的,新库层出不穷,但 D3 和 Chart.js 依然是两座绕不过去的大山。一个代表了极致的灵活,一个代表了极致的便捷。

希望这篇碎碎念能帮你少走点弯路。下次再被喊去"做个好看点的图"的时候,你能从容地打开编辑器,而不是偷偷摸摸搜 Excel 教程。

毕竟,咱们是前端工程师,不是 PPT 搬运工,对吧?

代码写得开心,图表画得漂亮,咱们下回见。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值