
简介这是一份面向Web前端开发者与数据可视化初学者的JS绘图组件学习资源围绕柱状图、饼图等常见图表的实现展开帮助读者在数据分析、报告展示与交互场景中快速把复杂数据转化为直观图形。压缩包共51个文件约286KB以27个html示例页面、12张jpg效果图、9个xml配置、1个pdf说明文档、1个txt使用指南和1个js核心脚本为主html与js便于直接运行调试pdf与txt补充配置选项和API调用方法xml与jpg则辅助理解主题样式与渲染效果。资源涵盖柱状图、饼图、折线图等多类示例并附带基础、自定义配色等多种主题方便对照学习数据绑定、样式定制、交互高亮与多轴支持等关键特性。目前已有172人学习下载适合希望快速上手JS图表组件、了解二次开发思路的开发者参考借鉴。1. 从一份原生 JS 绘图组件说起不依赖框架的图表方案前阵子接手一个后台数据看板的小活页面本身是服务端渲染的老架构引不进打包工具更别提往里面塞 ECharts 或者 Chart.js 这种体量不小的库。需求倒不复杂几张柱状图、一个饼图数据从接口拿能自适应容器宽度就行。翻了一圈现成的方案要么依赖 canvas 且体积感人要么得配构建流程最后找到一份纯原生 JS 写的绘图组件柱状图、饼图这些常见图形都实现了直接 script 标签引入就能跑。这份资源适合谁做轻量后台、嵌入式页面、老项目改造或者单纯想搞清楚图表底层是怎么画出来的开发者。它不追求大而全胜在零依赖、可读、能改拿来当学习样本或者直接嵌进小项目都合适。2. 拆开看结构柱状图与饼图的绘制逻辑2.1 组件目录与核心文件职责拿到一份绘图组件我习惯先看目录结构这决定了后面改起来顺不顺手。这类原生 JS 图表组件通常长这样一个主入口文件负责对外暴露 API一个核心渲染文件处理坐标计算和元素生成再按图形类型拆成独立模块。柱状图和饼图虽然视觉差异大但底层都绕不开三件事——把数据映射成像素坐标、生成对应的 DOM 或 SVG 节点、绑定交互事件。以常见的实现方式来说柱状图的核心是计算每根柱子的 x 位置和高度。假设容器宽度 W数据有 n 条柱子间距和宽度需要按比例分配高度则用(value / maxValue) * chartHeight换算。饼图更依赖三角函数每个扇区的起始角度和结束角度由数据占比累加得到再用Math.cos和Math.sin算出弧线端点坐标。这些计算逻辑如果封装得干净改配色、改间距、加动画都只是改参数的事。提示先通读核心渲染文件把坐标计算和节点生成两部分分开理解后面调样式时不容易改乱。2.2 数据到图形的映射坐标计算与比例尺图表组件最容易翻车的地方就是比例尺。数据里可能有 0可能有负数也可能最大值和最小值差距极大。一个健壮的实现不会直接把原始值当高度用而是先做归一化。常见做法是找出数据集的最大值用它作为基准再留一点顶部内边距避免最高的柱子顶到容器边缘。下面这段是柱状图高度计算的核心逻辑我按常见实现整理了一下参数名做了通用化处理// 计算柱状图每根柱子的几何参数 function calcBarLayout(data, options) { const { width, height, padding, barGap } options; // 绘图区域去掉内边距 const chartW width - padding.left - padding.right; const chartH height - padding.top - padding.bottom; // 取最大值作为比例基准避免除零 const maxVal Math.max(...data.map(d d.value), 1); // 每根柱子分到的槽位宽度 const slotW chartW / data.length; // 柱子实际宽度 槽位宽度 - 间隙 const barW slotW - barGap; return data.map((item, i) { // 高度按比例换算负值单独处理 const barH (item.value / maxVal) * chartH; return { x: padding.left i * slotW barGap / 2, y: padding.top chartH - barH, w: barW, h: barH, raw: item }; }); }这段代码里几个参数值得留意。maxVal用了Math.max(..., 1)是为了防止数据全为 0 时出现除以零这是很多手写图表会忽略的边界。barGap控制柱子之间的呼吸感设太大柱子会细得像牙签设太小又糊成一片一般取槽位宽度的 20% 到 30% 比较舒服。padding是容器内边距给坐标轴文字留位置如果发现柱子贴着边或者标签被裁掉先调这里。饼图那边则是另一套算法核心是把百分比转成弧度// 饼图扇区角度计算 function calcPieSectors(data, options) { const { cx, cy, radius } options; const total data.reduce((sum, d) sum d.value, 0) || 1; let startAngle -Math.PI / 2; // 从正上方开始画 return data.map(item { const ratio item.value / total; const angle ratio * Math.PI * 2; const endAngle startAngle angle; // 计算弧线起止点坐标 const x1 cx radius * Math.cos(startAngle); const y1 cy radius * Math.sin(startAngle); const x2 cx radius * Math.cos(endAngle); const y2 cy radius * Math.sin(endAngle); const largeArc angle Math.PI ? 1 : 0; // 大于半圆标记 const path M ${cx} ${cy} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z; startAngle endAngle; return { path, ratio, raw: item }; }); }startAngle初始设为-Math.PI / 2是为了让第一个扇区从十二点钟方向开始符合大多数人的阅读习惯。largeArc这个标记很关键当某个扇区占比超过一半时SVG 的弧线命令需要它来决定走大弧还是小弧漏掉这个参数会导致大扇区画反。total同样做了兜底防止数据为空时出现 NaN。2.3 初始化与配置项把图挂到页面上组件用起来一般就两步准备一个容器元素传入数据和配置。下面是一个典型的调用示例// 初始化柱状图 const barChart new SimpleChart({ el: #barContainer, // 容器选择器或 DOM 节点 type: bar, // 图形类型bar / pie data: [ { label: 一月, value: 120 }, { label: 二月, value: 200 }, { label: 三月, value: 150 } ], width: auto, // auto 表示跟随容器宽度 height: 300, colors: [#4e79a7, #f28e2b, #e15759], // 自定义配色 showValue: true, // 是否在图形上显示数值 animation: true // 是否开启入场动画 }); barChart.render();el支持选择器字符串和 DOM 节点两种形式后者在动态创建元素的场景下更稳。width设为auto时组件会读取容器的clientWidth这样配合 CSS 的百分比布局就能实现响应式。colors数组会按顺序循环取用数据条数超过颜色数时会从头复用。showValue打开后柱状图会在柱子顶部、饼图会在扇区外侧标注数值如果标签重叠严重可以关掉它改用 tooltip。渲染流程通常是先清空容器再根据类型分发到对应的绘制函数最后统一绑定事件。这里有个细节如果容器里已经有内容直接 append 会导致重复渲染所以每次 render 前做一次innerHTML 是必要的。动画方面柱状图一般用 CSS transition 改高度饼图用 SVG 的 stroke-dasharray 或者直接改 path实现难度不同效果也有差异。3. 动手改一个柱状图从配色到交互的完整流程3.1 环境准备与最小可运行页面这份组件是原生 JS不需要 npm 安装也不需要打包。新建一个 HTML 文件把组件的 JS 文件用 script 标签引进来就行。我一般会先搭一个最小可运行页面确认基础功能正常再往上加东西。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title图表测试页/title style /* 容器给个固定高度宽度用百分比 */ .chart-box { width: 100%; max-width: 800px; height: 320px; margin: 20px auto; border: 1px solid #eee; } /style /head body div idbarContainer classchart-box/div script src./simple-chart.js/script script // 等 DOM 就绪再初始化 document.addEventListener(DOMContentLoaded, function () { var chart new SimpleChart({ el: #barContainer, type: bar, data: [ { label: A, value: 80 }, { label: B, value: 160 }, { label: C, value: 110 }, { label: D, value: 200 } ], height: 320, showValue: true }); chart.render(); }); /script /body /html这里把初始化放在DOMContentLoaded里是因为组件需要读取容器的实际宽度如果脚本在 head 里执行容器还没渲染出来clientWidth会是 0图就画不出来。容器高度必须显式指定图表组件一般不会自动撑开高度给个固定值或者用 flex 布局分配都可以。3.2 配置项逐个拆解宽度、颜色、动画配置项是这类组件的灵魂改对了事半功倍改错了就是玄学问题。我把常用参数整理成一张表方便对照着调参数类型默认值说明elstring / DOM无容器选择器或节点必填typestringbar图形类型支持 bar、piedataarray[]数据数组每项含 label 和 valuewidthnumber / autoauto画布宽度auto 跟随容器heightnumber300画布高度单位像素colorsarray内置色板颜色数组循环取用showValuebooleanfalse是否显示数值标签animationbooleantrue是否开启入场动画barGapnumber8柱状图柱子间隙单位像素radiusnumber自动饼图半径不传则按容器算width设为auto时有个坑如果容器宽度是 0比如父元素 display:none组件算出来的画布宽度也是 0图会消失。遇到这种情况要么等容器可见后再 render要么手动传一个固定宽度。animation在数据频繁更新的场景下建议关掉否则每次重绘都播一遍动画视觉上很闹腾。3.3 加一个点击交互事件绑定与数据回传光看图不够实际项目里经常需要点击柱子查看明细。这类组件一般会暴露一个on方法或者配置项里的回调。我按常见做法给它加一个点击事件var chart new SimpleChart({ el: #barContainer, type: bar, data: [ { label: A, value: 80 }, { label: B, value: 160 }, { label: C, value: 110 } ], height: 320, onItemClick: function (item, index) { // item 是原始数据对象index 是索引 console.log(点击了第 index 项, item.label, item.value); // 实际场景里可以弹窗、跳转或者请求详情接口 } }); chart.render();如果组件本身没有提供onItemClick这类配置那就得自己绑。渲染完成后柱子节点上一般会带>// 事件委托方式兼容动态渲染 document.getElementById(barContainer).addEventListener(click, function (e) { var target e.target; // 向上找带>// 窗口缩放时重绘带防抖 var resizeTimer null; window.addEventListener(resize, function () { clearTimeout(resizeTimer); resizeTimer setTimeout(function () { chart.render(); // 重新读取容器宽度并绘制 }, 200); });4.4 数据更新后旧图没清掉出现重影现象调用 render 更新数据新图叠在旧图上或者标签重复。原因渲染前没有清空容器。解决在 render 方法开头加this.el.innerHTML 或者用while (el.firstChild) el.removeChild(el.firstChild)逐个移除。前者简单粗暴后者在需要保留某些子节点时更可控。如果组件用了 canvas则要调clearRect而不是清 DOM。4.5 动画过程中点击事件失效现象入场动画播放期间点击柱子没反应动画结束后才正常。原因动画期间节点的位置或尺寸在变化事件绑定的时机不对或者动画用了pointer-events: none。解决把事件绑定放在动画结束的回调里或者用事件委托挂在容器上容器本身不受动画影响。如果动画是 CSS transition可以监听transitionend再启用交互。5. 进阶技巧让这份组件适配更多场景5.1 用 SVG 还是 Canvas选型与替换思路这份组件如果底层用的是 SVG那它在交互和样式上更友好每个柱子、扇区都是独立节点改颜色、加 hover 效果直接写 CSS 就行。缺点是数据量大时节点数暴涨几百根柱子就会卡。Canvas 则相反绘制快、节点少但交互要靠坐标反查样式也得用代码画。我一般这么选数据条数在 100 以内、需要精细交互的用 SVG数据量大、以展示为主的换 Canvas 实现。替换时核心的坐标计算逻辑可以复用只改节点生成那一段。5.2 加一个简单的 tooltip鼠标悬停显示数值tooltip 是图表的高频需求实现思路不复杂监听容器的mousemove根据鼠标位置找到对应的数据项在合适的位置显示一个绝对定位的 div。下面是一个简化版实现// 简易 tooltip var tooltip document.createElement(div); tooltip.style.cssText position:absolute;padding:4px 8px;background:rgba(0,0,0,.75);color:#fff;font-size:12px;border-radius:4px;pointer-events:none;display:none;z-index:10;; document.body.appendChild(tooltip); document.getElementById(barContainer).addEventListener(mousemove, function (e) { var target e.target; while (target target ! this) { if (target.dataset target.dataset.index ! undefined) { var idx Number(target.dataset.index); var item chart.options.data[idx]; tooltip.textContent item.label item.value; tooltip.style.display block; // 跟随鼠标偏移一点避免遮挡 tooltip.style.left (e.pageX 12) px; tooltip.style.top (e.pageY - 28) px; return; } target target.parentNode; } tooltip.style.display none; // 没命中就隐藏 });pointer-events: none是关键不加的话 tooltip 会挡住鼠标导致 mousemove 反复触发和隐藏出现闪烁。位置用pageX/pageY而不是clientX/clientY是为了兼容页面滚动的情况。如果图表在滚动容器里还要减去容器的偏移量。5.3 数据量大了怎么办分页与抽稀柱状图数据超过 50 条柱子会细得看不清标签也会挤成一团。这时候有两个方向一是分页一次只展示一部分加左右翻页按钮二是抽稀比如每隔几条取一条或者把相邻数据合并成一组。分页实现简单适合数据本身有明确分段的情况抽稀会损失细节适合趋势展示。我一般优先分页把每页控制在 20 到 30 条配合横向滚动或者翻页控件体验比硬塞强得多。5.4 一个我常做的验证习惯改完图表配置后我习惯做三件事再交付第一把数据全设为 0看会不会报错或者画出异常图形第二把数据设成一条看单柱或整圆是否正常第三把容器宽度缩到很窄看标签会不会重叠、柱子会不会消失。这三个边界场景能覆盖大部分低级问题。从那以后我每次接入新的图表组件都强制走一遍这套边界测试省得上线后被用户截图打脸。希望帮到你。本文还有配套的精品资源点击获取