☰
flotr2轻量canvas绘图库实战指南:从入门到避坑封装
2026/10/20 16:19:17 网站建设 项目流程

简介:面向Web前端开发者与数据可视化初学者的Flotr2图表库资源包,收录压缩版核心文件flotr2.min.js及配套演示页面,帮助读者在不依赖后端的情况下快速上手绘制折线图、柱状图、饼图、散点图等常见交互图表。压缩包共3个文件,包含一个可直接运行的HTML示例、一个官方Demo地址说明文档和一份核心JS库,整体仅33KB,部署到任意静态页面即可使用,轻量便捷。已有355人学习下载。文件中通过完整示例展示引入库、准备数据、调用Flotr.draw绘图、配置颜色与轴标签等关键步骤,同时附带官方案例入口,便于进一步研究事件处理、响应式重绘和性能优化。适合需要为报表、后台管理或交互应用快速添加图表能力的开发者,省去从零查阅文档的时间,直接对照演示修改数据即可投入使用。

1. flotr2.min.js 到底是什么:一个函数画图,轻量到能塞进老页面

接手一个老页面维护需求时,我只想给一个 640 像素宽的 div 画两条曲线——既不想引入前端框架,也不想装一个几百 KB 的图表全家桶。搜到 flotr2.min.js 这个单文件包后,我却被压缩后的代码挡在门外,文档零散,连第一行从哪写都没头绪。实际上 flotr2 是一个基于 canvas 的轻量绘图库,核心用法就一句:Flotr.draw(容器, 数据, 配置)。它无第三方依赖、单文件,适合老系统改造、嵌入式看板和只有基础图表需求的小页面。

这篇文章按“这是什么 → 最小例子怎么写 → 配置怎么调 → 坑在哪”这条路整理,四个常用图表的代码可以直接拷贝,避坑章节的五个问题都是我实际撞上过的。新手照着第 2 章最小页面就能跑通,熟手可以直接跳到第 5 章对照症状。

2. 先搞清楚 flotr2 的调用模型再动手:三个参数决定你后面少踩多少坑

2.1 为什么选 flotr2:单文件、无依赖的 canvas 绘图方案适用面有多大

flotr2 的历史定位一句话就能说清:它是早期 flotr 的重写版,把原来依赖旧框架的部分全部去掉,变成一个纯 canvas 绘图、无第三方依赖的单文件库。调用方式也是命令式的——你给数据、给配置,它一次性把图画完,不维护虚拟 DOM,也不提供双向绑定。这个特性决定了它的适用边界:凡是“页面加载时画一次、数据变了整体重画”的场景,它都很顺手。

我和大多数选它的人一样,是在老系统改造里遇到的:一个只放监控曲线的内嵌页面,不能引入构造工具链,也不愿意为画两张图拖一个全家桶进来。flotr2.min.js 这种单文件包丢到静态目录就能跑,压缩后的体积在主流图表方案里属于很轻的一档,首屏成本几乎可以忽略。

反过来,如果需求已经发展到需要图表联动、区域缩放、海量点流畅滚动,flotr2 会明显吃力。我目前的判断标准是:点数在几千以内、交互最多到 hover 提示、刷新方式能接受整图重绘,就用它;再往上就该考虑带声明式数据绑定和完整交互模型的重型方案了。先把这个边界划清楚,后面照抄代码才不会在项目中期后悔。

2.2 Flotr.draw 的三个参数:container、data、options 各自约定

调用链非常简单,真正决定成败的是参数格式。第一个参数 container 必须是 DOM 元素,不是选择器字符串,写了 '#chart' 会直接报错;第二个参数 data 是系列数组;第三个参数 options 是配置对象,所有表现都从这里控制。

// 写法 1:单一系列,数组套数组,flotr2 会当成一个默认系列 var data1 = [[1, 30], [2, 50], [3, 45]]; // 写法 2:多系列,每个系列可以带 label 和 color var data2 = [ { label: '入口流量', color: '#3b82f6', data: [[1, 30], [2, 50]] }, { label: '出口流量', color: '#ef4444', data: [[1, 12], [2, 22]] } ]; // 写法 3:从接口数据 map 出来,注意把字符串转成数字 var data3 = rows.map(function (r, i) { return [i, Number(r.value)]; }); Flotr.draw(document.getElementById('chart'), data3, { lines: { show: true } });

逻辑说明:写法 1 适合临时调试,写法 2 才是项目里常用的结构——label 会进图例,color 会覆盖全局配色。写法 3 提醒一个高频问题:接口返回的数字字段常常是字符串,Number() 这一步省掉,后面坐标轴就会出各种怪问题。

参数说明:data 里每个点固定是 [x, y] 两个数字的数组,顺序不能反。options 里 lines、points、bars、pie 是四类开关,至少要打开其中一个,否则画出来一片空白,这是新手最容易疑惑的点。

2.3 最小完整页面:引包、一个 div、一段脚本直接跑

把下面这段存成 html,和 flotr2.min.js 放在同一目录,浏览器打开就能看到一条折线。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>flotr2 最小页面</title> <style> #chart { width: 640px; height: 320px; } </style> </head> <body> <div id="chart"></div> <!-- 顺序不能反:先引库,再写业务脚本 --> <script src="flotr2.min.js"></script> <script> var data = [[1, 10], [2, 25], [3, 18], [4, 30]]; Flotr.draw(document.getElementById('chart'), data, { lines: { show: true }, points: { show: true }, xaxis: { min: 0, max: 5 }, yaxis: { min: 0, max: 40 } }); </script> </body> </html>

逻辑说明:CSS 里的 width 和 height 是必需项,不是装饰。flotr2 第一次 draw 时要读取容器的实际尺寸,拿不到就画白板。脚本顺序也固定:flotr2.min.js 先加载,业务脚本后执行,否则 Flotr 对象还不存在。

参数说明:xaxis 和 yaxis 的 min/max 建议留出一点余量,否则第一个点和最后一个点会贴在坐标轴线上,观感很差。调试期我一般会同时用开发版的 flotr2.js 代替压缩版,报错信息能直接看到是哪一行的问题。

提示:从包管理工具安装 flotr2 后,把发布目录里的 flotr2.min.js 拷进自己的静态资源目录即可,文件路径保持一致就能正常引包。

3. 四种常用图表的最小例子:折线、柱状、饼图和散点一次跑通

3.1 折线图:JSON 数据直接喂给 lines 配置

折线图是最常规的用法。接口返回的监控数据一般是对象数组,组装 data 时按 [序号, 数值] 转换,再把时间标签塞进 xaxis 的 ticks 里由显示层处理,这样 x 轴始终保持数值型。

// 模拟从接口取回来的监控数据 var raw = [ { time: '09:00', cpu: 42 }, { time: '09:01', cpu: 48 }, { time: '09:02', cpu: 51 }, { time: '09:03', cpu: 47 } ]; var data = raw.map(function (r, i) { return [i, Number(r.cpu)]; }); Flotr.draw(document.getElementById('cpuChart'), [{ label: 'CPU 使用率', data: data }], { lines: { show: true, lineWidth: 1.5, fill: true }, shadowSize: 0, grid: { horizontalLines: true, verticalLines: false }, xaxis: { ticks: [[0, '09:00'], [1, '09:01'], [2, '09:02'], [3, '09:03']] }, yaxis: { min: 0, max: 100 } });

逻辑说明:这里把真实时间抽出来做成 ticks 的映射,data 里只用序号,它解决的是“x 轴想要时间标签、又想保持坐标数值化”的矛盾。如果接口返回的是时间戳,同样要先用 Number() 转成数值再放进去。

参数说明:lines.fill 开启面积填充,适合展示趋势;shadowSize 设为 0 是去掉 canvas 阴影,既省性能又避免图形发糊。xaxis.ticks 是二维数组格式,第一项是坐标值,第二项是显示文本,不传 ticks 时 flotr2 会自动生成数字刻度。

3.2 柱状图与饼图:bars 和 pie 的配置差异

柱状图和折线图共用同一套数据格式,差别只在 options 里把 lines 换成 bars。饼图则完全不同,它的点不是坐标,第一个元素只是分组序号。

// 柱状图:两组数据做并列效果,手动在 x 上错开位置 var barData = [ { label: '今年', data: [[0.2, 3200], [1.2, 2400], [2.2, 1800]] }, { label: '去年', data: [[-0.2, 2800], [0.8, 2100], [1.8, 1600]] } ]; Flotr.draw(document.getElementById('barChart'), barData, { bars: { show: true, barWidth: 0.35 }, xaxis: { ticks: [[0, '周一'], [1, '周二'], [2, '周三']] }, yaxis: { min: 0 } }); // 饼图:data 点是 [序号, 数值] var pieData = [ { label: '成功', data: [[1, 78]] }, { label: '失败', data: [[2, 15]] }, { label: '超时', data: [[3, 7]] } ]; Flotr.draw(document.getElementById('pieChart'), pieData, { pie: { show: true, explode: 6, labelFormatter: function (label, value) { return label + ' ' + value + '%'; } } });

逻辑说明:柱状图这里有一个 flotr2 的边界要说清楚——它没有自动分组机制,两个系列用同一个 x 值会重叠在一起。常见做法是在分类坐标上做小幅偏移,今年用 0.2、1.2、2.2,去年用 -0.2、0.8、1.8,这样柱子才能并排显示。偏移量要配合 barWidth 一起调,超过半柱宽两个系列就会错位到分不清。

参数说明:barWidth 的单位是 x 轴上的刻度距离,0.35 表示柱子宽度约占一格的三分之一。饼图里 explode 是扇区离心的像素距离,不要设太大;labelFormatter 收到的 value 是计算好的百分比数值(不含 %),在函数里自己拼显示文案。

3.3 散点图与多系列叠加:同一坐标轴下怎么区分系列

散点图本质是只开 points 不开 lines。多系列时每个系列自带 label 和 color,图例会说明颜色对应关系。

var scatterData = [ { label: '偏差样本', data: [[1, 2], [3, 5], [5, 4], [7, 8]], color: '#ef4444', points: { show: true, radius: 4 } }, { label: '正常样本', data: [[1, 4], [2, 3], [4, 6], [6, 5]], color: '#10b981', points: { show: true, radius: 2 } } ]; Flotr.draw(document.getElementById('scatterChart'), scatterData, { legend: { show: true, position: 'se' }, title: '两类样本分布', shadowSize: 0 });

逻辑说明:系列对象内部的 points 配置会覆盖全局配置,这是 flotr2 的合并规则——你想让某一条折线的点大一点、另一条正常,直接在系列里写 points 就行,不用动全局。

参数说明:legend.position 支持 nw、ne、sw、se 四个方位,se 是右下角,放散点图里最不容易挡住数据。title 是整图标题,居中显示在顶部。想做成“散点加趋势线”的混合图,就在其中一个系列里加上 lines: { show: true },其余系列保持纯散点,视觉上能明显区分。

4. 常用配置项逐项调:坐标轴、图例、提示框和颜色

4.1 坐标轴与网格:范围、刻度、标签与格式化

坐标轴是图表的脸面。min/max 控制显示范围,ticks 控制刻度位置和标签,tickFormatter 对刻度值做二次加工,这三件套基本能覆盖日常需求。

Flotr.draw(container, data, { xaxis: { min: 0, max: 60, ticks: [0, 15, 30, 45, 60], tickFormatter: function (val) { return val + 's'; // 把纯数字显示成带单位的时间 }, tickColor: '#cccccc' }, yaxis: { min: 0, max: 100, autoScale: false // 关闭自动缩放,范围以 min/max 为准 }, grid: { horizontalLines: true, verticalLines: false, backgroundColor: '#fafafa', outlineWidth: 0 } });

参数说明:ticks 可以是数组,数组元素就是刻度位置;想让刻度更自由,用 tickFormatter 就行,它只改显示不改数值。autoScale 是 flotr2 里容易被忽略的一项,不关掉时 min/max 可能被自动范围覆盖,表现就是“我设了 0 到 100,实际只画到 80”。grid 里 horizontalLines 和 verticalLines 分别控制横竖网格线,outlineWidth 设为 0 能去掉最外圈边框,搭配浅色背景时干净很多。

4.2 图例与提示框:legend 与 mouse.track 的配合

默认情况下图例不显示,鼠标跟踪也不开启。这两个功能要显式打开,并且 tooltip 依赖 mouse.track 先开启才能工作。

var fmt = function (obj) { return '时间点 ' + obj.x + ' 对应的值是 ' + obj.y; }; Flotr.draw(container, data, { legend: { show: true, position: 'ne', backgroundColor: 'rgba(255, 255, 255, 0.8)' }, mouse: { track: true, lineWidth: 1, sensibility: 3 }, tooltip: { show: true, trackFormatter: fmt } });

参数说明:trackFormatter 接收一个对象,里面至少带 x 和 y 两个字段,返回 HTML 字符串作为提示框内容。sensibility 是命中判定半径,单位是像素,3 表示鼠标离点 3 像素内就捕获;点又小又密的时候可以调到 5 到 8。legend.backgroundColor 用半透明白色,能避免图例盖住网格线时看不清。

配置作用常用值
legend.show是否显示图例true / false
legend.position图例方位nw / ne / sw / se
mouse.track开启鼠标跟随true / false
mouse.sensibility命中点判定半径3 ~ 8
tooltip.show显示提示框true / false
tooltip.trackFormatter自定义提示内容function(obj) 返回字符串

提示:trackFormatter 返回的内容是直接作为 HTML 插入的,如果塞的是外部数据,记得自己做好转义,别把接口字段原样拼进提示框。

4.3 颜色、标题与观感细节:series 级配置的优先级和两个小参数

颜色有三个层级:系列对象里的 color 优先级最高,其次是全局 colors 数组,最后是库自带的默认色。实际项目里我习惯给每个系列固定 color,不用默认色轮,因为默认色在相邻系列之间区分度不够。

Flotr.draw(container, [ { label: 'A 系列', data: [[0, 1], [1, 2]], color: '#22c55e' }, { label: 'B 系列', data: [[0, 3], [1, 1]] } ], { colors: ['#3b82f6', '#ef4444'], // B 系列会取这个数组的第一个颜色 title: '月度走势', shadowSize: 0, HtmlText: false });

参数说明:colors 数组按系列顺序分配,写了 color 的系列不参与分配。HtmlText 是一个全局开关,默认用 HTML 节点渲染文字;当你在页面上看到坐标轴文字叠影、错位,或者文字出现在不该在的地方,把 HtmlText 改成 false 让所有文字直接画在 canvas 上,这类问题通常一次消失。副作用是文字不再能被浏览器选中复制,对展示类图表无所谓。

5. flotr2 避坑指南:容器尺寸、数据格式和重绘残留的 5 个典型问题

5.1 图表空白:先量容器高度,别急着查数据

现象:容器和 canvas 都在,控制台零报错,画布上却什么都没有。

原因:flotr2 第一次 draw 时读取的是容器的实时尺寸。容器 display:none(弹窗、Tab 切换最常见)或者父级高度塌陷时,高度是 0,canvas 画出来自然是白板。

解决:给容器显式 width/height;弹窗先 display 再画;如果初始化时机实在绕不开,等容器变成可见后再调用一次 draw。

function showDialog() { dialog.style.display = 'block'; // 等该帧布局完成后再画,否则 canvas 高度仍然是 0 requestAnimationFrame(function () { Flotr.draw(dialog, dialogData, dialogOptions); }); }

5.2 折线压成水平线或 x 轴堆成一团:字符串坐标在作怪

现象:接口数据原样塞进 data,折线不是一条水平线,就是所有点堆在 x 轴一侧,y 轴刻度也只剩几个整数。

原因:字段被解析成字符串("42" 而不是 42),或者手写数据时加了引号。flotr2 对非数字值不报错,而是把它当特殊值处理,坐标自然就乱了。

解决:在组装 data 时统一 Number()。别在 draw 里做转换,那会多一次完整遍历。

var data = rows.map(function (r, i) { return [i, Number(r.value)]; // 这步省掉,后面就有的查了 });

5.3 同一个容器重复 draw:提示框和辅助层越叠越多

现象:定时刷新几分钟后,鼠标移到图上出现上一轮的 tooltip 文字,DOM 里提示浮层越来越多。

原因:flotr2 每次 draw 会往容器里追加自己的辅助节点——鼠标跟踪层、提示框容器都在内。对同一容器重复 draw,旧节点不会被自动回收,时间一长既占内存又串内容。

解决:把“清空再画”固化成函数,这是我在所有接入点统一使用的套路。

function redraw(container, data, options) { container.innerHTML = ''; // 清掉旧 canvas 和辅助层 Flotr.draw(container, data, options); }

这一句 innerHTML 会把旧 canvas 和辅助层全部移除,下一次 draw 重新创建,容器里永远只有当前这一份实例。

5.4 图表类型串味:复用的 options 对象把上次的 lines 带到了柱状图上

现象:先画折线,同一容器改成 bars 配置画柱状图,柱子上还压着原来的折线。

原因:全局 options 对象被复用,之前开的 lines.show 一直留在对象里,新配置只加了 bars.show,没关 lines。flotr2 把两类开关都打开时,会叠加绘制。

解决:每次重绘构造新的 options 对象,或者在配置里显式关闭上一类图形,别依赖默认值。

function getBarOptions() { return { bars: { show: true, barWidth: 0.5 }, lines: { show: false } // 显式关闭,防止串味 }; }

5.5 tooltip 追着鼠标亮但位置不对:先查祖先的 CSS transform

现象:mouse.track 正常工作,提示框却出现在偏离鼠标一段距离的左上角,鼠标离点越远偏得越多。

原因:flotr2 的提示框用绝对定位,坐标按 offsetParent 计算。当图表容器或父级用了 CSS transform(弹窗动效里 scale、位移很常见),包含块发生变化,计算出的偏移和视觉位置就对不上。

解决:图表容器设 position: relative;不要在画布或其祖先链上用 transform;弹窗带进出场动画时,等动画结束再初始化图表。这个问题的排查顺序建议是:先看 transform,再看容器的 position 属性,最后才怀疑 flotr2 本身。

提示:多个图表并排时,每个容器维护自己独立的 data 和 options 引用,不要图省事共用同一个数组再往里 push,后面更新数据时最容易出现“改一个全变”的幻觉。

6. 封装成可反复调用的渲染函数:重绘清理与 resize 处理

前面都是单次画图,真正放进系统里要解决两个重复性问题:多次 draw 的残留,以及窗口尺寸变化。我在类似看板场景里的做法是统一一个渲染入口,把清理、尺寸锁定、数据比对都收进去:

function renderChart(container, data, options) { container.innerHTML = ''; // 清理旧实例 var merged = Object.assign({}, options, { width: container.clientWidth, // 锁定绘制时的容器尺寸 height: container.clientHeight }); Flotr.draw(container, data, merged); }

封装之后,resize 只需要做防抖再回调一次 renderChart。flotr2 本身不监听窗口变化,不自己处理的话,浏览器拉窄页面后 canvas 会被 CSS 拉伸,坐标系和视觉尺寸就对不齐,图形会变扁。

轮询刷新场景再加一道“数据没变就不重绘”的判断。最简单的做法是接口响应带一个计数或时间戳,变了才调 renderChart;不要每次把整个二维数组做序列化比较,数据量大时这个比对成本比重绘还高。我最初封装时没注意这一点,结果图表在后台每三秒全量重画一次,页面滚动明显掉帧。

最早我图省事把全局 options 传来传去,换来换去图表串味、辅助层堆积,排查了一下午。后来固定成“重建对象 + 清空容器 + 防抖 resize”这套流程,再没在重复绘制的场景上翻过车。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询