简介这是一份面向Web前端开发者和初学者的Flotr2图表库上手资源压缩包内共3个文件其中flotr2.min.js是核心压缩库配合示例HTML页面与官方Demo地址文本可直接运行查看折线图、柱状图、饼图等常见图表的绘制效果。资源包仅33KB轻量易部署适合用于快速掌握图表配置、数据格式和基础API调用。目前已有355人学习下载说明该示例对入门Flotr2具备实用参考价值。通过阅读示例代码并对照官方Demo可了解如何引入库、准备数据、设置画布与选项以及实现基本交互与重绘操作方便后续在数据分析、报表展示等网页项目中落地使用。1. flotr2一个不需要 jQuery 的 Canvas 绘图库用在哪儿你心里要有数我在帮朋友排查一个用 flotr2.min.js 做出的数据看板时发现多数人的困惑不在绘图本身而在于“把它当成 ECharts 在用”。flotr2 是一个体积很小、无框架依赖的 Canvas 绘图库特征是单文件引入、数据驱动绘制、交互钩子开放。它没有默认皮肤和主题但正因为如此做内网运维面板、设备曲线这类需要轻量自绘的页面时加载快、可控性强。适合谁适合那些不想为了几个折线图再引一个重型图表框架的开发者也适合把图表做进现有页面且不想被框架绑定的项目。下面我先从它的数据模型讲起落到真实接入步骤和实践中踩过的几个坑最后一章给一个可以直接扩展的联动技巧。2. flotr2 的核心机制数据对象结构、渲染管线与交互钩子在敲代码之前值得先花十分钟理解 flotr2 的数据组织方式。它所有类型的图都建立在坐标系加数据集之上饼图不过是把角度计算封装进同一个体系。这套设计学习成本不高但选错层级配置会很折磨人。2.1 series、data 与 options 三层结构的关系Flotr.draw 是唯一核心入口签名固定Flotr.draw(container, seriesArray, options);container 是页面里已存在的 DOM 元素seriesArray 是图表数据options 控制坐标轴、网格、鼠标交互和标题等。最关键的是 seriesArray它一定是个数组数组里每一项代表一个系列一条线、一组柱子或者一个扇区。最常见的折线系列结构var mySeries { label: CPU 使用率, data: [[1559290000, 62], [1559293600, 67], [1559297200, 58]], color: #3182bd, lines: { show: true, lineWidth: 1 }, points: { show: false } };label 用于图例和 tooltipdata 是二维数组每一项第一列是 x 值、第二列是 y 值时间序列图通常让 x 存 Unix 时间戳color 是显示颜色lines 和 points 是同一系列下的绘制开关它们挂在 series 上而不是 options 上。这样设计的好处是能在同一张图里混排不同形态比如一组柱状图叠加一条折线用来对比计划值和真实值。options 是第二层核心按功能分成独立的子对象var myOptions { xaxis: { mode: time, min: 1559290000, max: 1559297200 }, yaxis: { min: 0, max: 100, title: 百分比 }, grid: { verticalLines: true, horizontalLines: true, color: #e0e0e0 }, mouse: { track: true } };很多第一次用的人会把 lines 塞进 options真正执行时毫无反应。绘制控制放 series坐标轴、网格、图例、title 放 options这是我排查出错时判断“配置找错层”的首要依据。下面这张表我经常拿来提醒自己配置层级典型参数作用范围series 层label、data、color、lines、points、bars、pie单个系列options 层xaxis、yaxis、grid、mouse、legend、title整个图表分清楚这两层后配置出错率至少降低一半。我早期调试过一个混排图为了给每条线设不同点形在 options 里找半天最后才知道点形属于 series 层这算是 flotr2 里最具代表性的认知坑。2.2 Canvas 渲染管线为什么 resize 会丢图、为什么重绘比动画频繁flotr2 的渲染方式很朴素每次调用 Flotr.draw 都会清空容器内的 canvas然后遍历 seriesArray 逐条完成坐标换算、路径绘制、描边和填充。它没有双向绑定状态也不做增量更新。这意味着任何一次重绘都是全量重绘数据量在几百个点时毫无压力但你每 100ms 就塞几千个点进来再 draw页面必然卡。我自己的做法是给刷新加节流500ms 到 1s 一次。典型轮询刷新写法var container document.getElementById(realTimeChart); var options { xaxis: { mode: time }, yaxis: { min: 0 } }; function render(series) { Flotr.draw(container, series, options); } setInterval(function () { fetch(/api/latest) .then(function (res) { return res.json(); }) .then(function (dataArray) { render([{ data: dataArray, lines: { show: true } }]); }); }, 1000);这里 render 每次传入新的 series 数组flotr2 内部没有增删补丁所以看起来像整个图被换了一张。这个模型简单可靠坏处是动画你必须自己实现。flotr2 没有 Flotr.animate 这样的现成 API需要补间效果时我习惯在 requestAnimationFrame 循环里逐步生成中间点再重绘。另一个高频问题是容器尺寸变化。canvas 有两个尺寸CSS 设定的展示尺寸以及内部绘图像素的 width/height 属性。两者不一致时图表会模糊、拉伸或者被裁剪。常见做法是窗口尺寸变化后显式调用function onResize() { Flotr.resize(container, container.offsetWidth, container.offsetHeight); } window.addEventListener(resize, onResize);注意 Flotr.resize 会保留当前图形配置但如果你在 resize 之前重新渲染过容器 DOM必须保证 container 引用的还是同一个节点。我在嵌入 iframe 的页面里遇到过 resize 后图表消失的问题具体原因和绕过方式第五章避坑部分展开。2.3 交互钩子追踪、格式化与事件回调flotr2 的交互能力集中在 mouse 配置块。最常用的是悬停追踪 tracker默认在鼠标附近显示小浮层展示当前点的 x、y 值。觉得默认文案不够用就自己实现 trackFormattervar opts { mouse: { track: true, lineColor: #f00, sens: 4, trackDecimals: 2, trackFormatter: function (obj) { return obj.series.label : obj.x , obj.y; } } };trackFormatter 接收的 obj 对象里至少有 x、y、series 和 index 字段。返回值就是浮层显示内容这是 flotr2 里最灵活的一个钩子大量自定义 tooltip 都是从这里改造出来的。需要强调的是 trackFormatter 只负责显示浮层存不存在由 track 开关控制。回调类钩子包括 clickCallback、mouseoutCallback作用范围是整个图表。例如想在某条折线附近点击后打开详情页可以在 clickCallback 里通过 series 和 index 找到具体点是哪个值。我一般在这里做两件事阻止默认行为把当前悬停点的索引存到一个全局变量后续业务逻辑统一从这个变量取数据。这套钩子把 lib 从一个只能画固定图形的黑匣子变成了可扩展的绘图工具。3. 把 flotr2.min.js 跑起来最小页面、首个折线图与目录组织下载回来的 flotr2.min.js 通常不到 100KB引入方式非常直接。这一章从目录结构开始带你拿到一个能双击打开的完整示例。3.1 文件放哪、怎么引入、本地静态服务器注意点我习惯把第三方库单独放一个 vendor 目录业务脚本与库文件分离方便后续升级替换。目录结构长这样demo/ ├── index.html ├── app.js └── vendor/ └── flotr2.min.jsindex.html 是页面入口app.js 写绘图代码vendor 目录放库文件。下面是一个最小 HTML 模板!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleflotr2 最小折线图/title style html, body { margin: 0; padding: 0; height: 100%; } #chart { width: 100%; height: 320px; } /style /head body div idchart/div script srcvendor/flotr2.min.js/script script srcapp.js/script /body /html这里有几个容易出问题的地方。script 顺序不能反先 vendor 再业务。div 容器必须有显式高度否则 canvas 绘制高度为 0图出不来。width 可以不写canvas 会继承父级宽度但 height 不设就是空白一块。如果你是本地测试直接用 file:// 协议打开多数浏览器允许加载本地 js但为了和线上行为一致我更推荐起一个静态服务在 demo 目录下执行 python -m http.server 8000然后访问 localhost:8000。3.2 第一个折线图代码逐行拆解app.js 的内容(function () { var container document.getElementById(chart); if (!container) { return; } var series []; for (var i 0; i 24; i) { series.push([i, 20 Math.sin(i / 4) * 5 Math.random() * 3]); } Flotr.draw(container, [{ label: 室温变化, data: series, lines: { show: true, lineWidth: 1.5 }, points: { show: true, radius: 1.5 } }], { xaxis: { min: 0, max: 23, title: 小时 }, yaxis: { min: 10, max: 30, title: ℃ }, grid: { color: #ccc, backgroundColor: #fff }, mouse: { track: true, trackFormatter: function (o) { return o.series.label o.y.toFixed(1) ℃; }} }); })();第一段用 IIFE 包裹避免全局变量污染。循环生成 24 个点x 表示小时y 是叠加了正弦和随机数的室温曲线既能验证坐标轴也能验证数据映射。lines 和 points 都打开曲线和数据点同时可见。xaxis.min/max 强制坐标轴范围不然 flotr2 自动缩放会让最后一段贴着右边框。mouse.track 开启悬停浮层trackFormatter 里用 toFixed(1) 控制小数位。这个页面打开后鼠标在曲线上移动会看到一个小框跟着走显示当前小时和温度。这是 flotr2 最典型的完成状态折线、坐标轴、浮层交互全部就位。常用参数速查参数类型作用xaxis.min / maxnumber强制 x 轴显示范围lines.showboolean是否绘制折线lines.lineWidthnumber线宽单位像素points.showboolean是否绘制数据节点points.radiusnumber节点圆点半径mouse.trackboolean是否追踪鼠标最近数据点grid.colorstring网格线颜色3.3 多系列图表与图例颜色、图例位置、x 轴对齐真实业务很少只画一条线至少 CPU、内存、磁盘三件套。多系列时 seriesArray 里放多个对象var cpuData [], memData []; for (var i 0; i 60; i) { cpuData.push([i, 30 Math.random() * 40]); memData.push([i, 50 Math.random() * 20]); } Flotr.draw(container, [ { label: CPU, data: cpuData, lines: { show: true }, color: #d62728 }, { label: 内存, data: memData, lines: { show: true }, color: #1f77b4 } ], { legend: { position: ne, backgroundColor: #fff }, grid: { verticalLines: true, horizontalLines: true } });颜色最好显式指定flotr2 默认调色板是固定顺序的两个图表都画 4 条线时第二条颜色会和另一个图的第二条重复混合展示容易误判。legend position 写 ne 代表右上角数据点多时图例会遮挡线条可以改成 se 或用 backgroundOpacity 做半透明。多系列还有一个经常被忽略的问题两个系列的 x 值可能没有完全对齐。flotr2 会按 series 自身的 data 顺序绘制如果 x 点错位曲线交叉处看起来像拉链。我的习惯是在传入前统一 x 网格把两个系列的数据插值到同一组 x 上。flotr2 不提供插值函数这一步需要自己做追求简单的场景线性插值就够用。4. 常见图表配置拆解柱状图、饼图与实时刷新折线只是入场实际项目里柱状图、饼图、流式刷新才是大头。这一章给三种高频需求各准备一段可直接抄的配置。4.1 柱状图barWidth、ticks 与堆叠柱的要点柱状图和折线图的核心差异在 bars 配置块Flotr.draw(container, [ { label: 订单数, data: [[0, 23], [1, 31], [2, 28]], bars: { show: true, barWidth: 0.6 } }, { label: 退款数, data: [[0, 5], [1, 8], [2, 6]], bars: { show: true, barWidth: 0.6 } } ], { xaxis: { ticks: [[0, 周一], [1, 周二], [2, 周三]], min: -0.5, max: 2.5 }, yaxis: { min: 0 }, legend: { position: ne } });barWidth 的单位是 x 轴坐标的绝对宽度0.6 表示每根柱子占 0.6 个坐标间距。xaxis.min 和 max 设成 -0.5 与 2.5是为了让第一根和最后一根柱子不被坐标轴切半。ticks 数组的作用是把数字 0、1、2 显示成周一、周二、周三这样的类别标签。堆叠柱状图需要打开 stackedbars: { show: true, barWidth: 0.5, stacked: true }堆叠顺序由 series 数组顺序决定排在前面的在最下方。如果数据里有负值堆叠表现可能和预期不同负数柱子会向下延伸。常见做法是把负值单独成一个系列放在最底部避免和其他正数混叠。4.2 饼图单点数据、explode 间距与百分比 tooltip饼图的 series 数据比较特别每个系列只保留一个数据点x 值不参与计算传 0 即可Flotr.draw(container, [ { label: 正常, data: [[0, 63]] }, { label: 告警, data: [[0, 22]] }, { label: 严重, data: [[0, 15]] } ], { pie: { show: true, explode: 4 } });flotr2 内部会把每个 series.data[0][1] 累加得到总数再按比例分配扇区角度。explode 让扇区之间裂开几个像素视觉上更容易区分。饼图默认不展示百分比需要自己在 tooltip 里计算mouse: { track: true, trackFormatter: function (obj) { var total 63 22 15; return obj.series.label : (obj.y / total * 100).toFixed(1) %; } }实际项目里 total 不应该写死我一般会在构建 series 时顺便累加一个 total 变量trackFormatter 里直接引用。另外饼图模式下 xaxis、yaxis 这些坐标轴配置会被静默忽略别花时间调 axis 参数。4.3 实时数据刷新滑动窗口、固定时间跨度与重绘节流监控场景的滚动曲线是 flotr2 的常见应用。核心思路是维护一个数组新数据进、旧数据出每次重绘时把 x 轴终点固定为当前时间var MAX_POINTS 60; var chartData []; function pushValue(val) { var now Date.now(); chartData.push([now, val]); if (chartData.length MAX_POINTS) { chartData.shift(); } redraw(now); } function redraw(now) { Flotr.draw(container, [{ data: chartData, lines: { show: true }, points: { show: false } }], { xaxis: { mode: time, min: now - (MAX_POINTS - 1) * 1000, max: now }, yaxis: { min: 0 } }); } setInterval(function () { pushValue(Math.random() * 100); }, 1000);pushValue 追加新点超过 60 个就把最早的丢掉。redraw 每次重新设置 xaxis.min 和 maxmin 等于当前时间减 59 秒这样曲线看起来从右侧不断增长、整体向左滑动。每次重绘是全量操作但 60 个点以内非常轻量1 秒一次刷新低端设备也扛得住。如果后台推送频率是 200ms 一条前端不必跟着 200ms 重绘一次。我的做法是数据先攒到一个缓冲数组攒够 5 条再触发一次 draw这就是采集高频率、绘图低频次的节流习惯。5. 避坑指南flotr2 开发中五个高频踩坑点与排查顺序这一章写的是我实际调试 flotr2 时记录下来的血泪经验每一条都按现象、原因、解决的顺序展开。5.1 样式相关的坑容器高度为零、resize 后模糊裁剪现象一Flotr.draw 执行后不报错但页面空白控制台安静如常。经常出现在把图表塞进 flex 布局或绝对定位容器的时候。原因很简单div 没有显式高度默认高度为 0canvas 绘制高度继承 0画面自然出不来。解决方式#chart { height: 320px; } .flex-chart-wrap { margin: 0; padding: 0; }给容器一个固定高度或者确保父级链条上每个高度都是显式的。排查顺序是F12 看容器高度、再试固定高度、如果固定高度正常问题就出在样式继承。现象二浏览器窗口缩放后图表内容被拉伸、部分被裁剪鼠标追踪位置也偏移。原因是 canvas 的 CSS 尺寸变了但内部绘图像素 width/height 没同步。解决方式window.addEventListener(resize, function () { Flotr.resize(container, container.offsetWidth, container.offsetHeight); });如果图表容器在异步切换中变成 display: none再变回 blockcanvas 尺寸会被重置为 0此时需要触发一次 draw 或 resize。我的兜底方案是在页面切换后延迟 200ms 强制 resize 一次用 setTimeout 包住专门对付 iframe 和选项卡这类场景。5.2 数据相关的坑乱序、NaN 与 null 断线现象折线变成一团蜘蛛网X 轴方向交错回折。原因接口返回的记录没有按时间排序flotr2 不负责排序按数据顺序直接连线。解决data.sort(function (a, b) { return a[0] - b[0]; });排序后仍混乱就要检查数据里有没有 null 或 NaN。null 会让线段直接断开NaN 则更隐蔽折线绘制时整段线可能消失。我的习惯是进入图表前先过滤一遍data data.filter(function (p) { return p[1] ! null !isNaN(p[1]); });这个过滤建议放在排序之后、draw 之前。多系列场景下过滤后的数组长度可能不一致但 flotr2 按 series 各自绘制不要求等长。5.3 模式与交互的坑饼图坐标轴失效、浮层偏移现象一饼图里设置 xaxis 和 yaxis 完全没反应还不报错。原因饼图走的是内部角度计算坐标系统线性坐标分支直接跳过。解决饼图不配置坐标轴只看 pie、mouse、legend、title 这些图表级参数。想要环形饼图可以在绘制完成后用 canvas 2D API 在中心盖一个白色圆这是社区里最常见的变通方案。现象二鼠标悬停时浮层坐标明显偏移高亮点和实际数据位置对不上。原因canvas 的绘制坐标系和可视化坐标系错位。常见诱因是容器有 padding 或者被 transform 缩放。解决#chart { padding: 0; box-sizing: border-box; }如果父容器用了 transform: scale()浮层定位也会跟着缩放计算坐标时要把缩放比例考虑进去。这种错位问题很多时候像玄学但绝大多数情况逃不出 padding、border、transform 这三件事按顺序排查基本能定位。6. 进阶技巧多图联动与自定义悬浮框多图联动的需求很常见主图表展示总流量副图表跟着主图的悬停位置展示对应时刻的错误流量。flotr2 的 trackFormatter 就是天然的联动触发点。var mainContainer document.getElementById(mainChart); var subContainer document.getElementById(subChart); var mainData []; var subData []; for (var i 0; i 30; i) { mainData.push([i, 80 Math.random() * 20]); subData.push([i, 3 Math.random() * 5]); } function syncToSub(activePoint) { var nearestSub null; subData.forEach(function (p) { if (Math.abs(p[0] - activePoint.x) 1) { nearestSub p; } }); if (nearestSub) { Flotr.draw(subContainer, [{ data: [activePoint, nearestSub], points: { show: true, radius: 3 } }], { yaxis: { min: 0 } }); } } Flotr.draw(mainContainer, [{ label: 总流量, data: mainData, lines: { show: true } }], { mouse: { track: true, trackFormatter: function (obj) { syncToSub(obj); return 时间点 obj.x 的值 obj.y; } } });当鼠标在主图上移动时trackFormatter 反复触发 syncToSub在副图里找到同一 x 坐标附近的点用两个离散点重绘出联动关系。副图数据量小重绘开销可以忽略。如果数据量大建议在 syncToSub 里加节流200ms 内最多触发一次避免拖动鼠标时频繁重绘。另一个惯用手法是彻底隐藏 flotr2 默认浮层换成自己定位的 div。把 trackFormatter 返回空字符串浮层内容区域就是空白然后在 mousemove 事件里自己计算坐标把自定义浮层放到指定位置。这个方案能完全控制浮层样式代价是坐标换算需要自己处理容器有 padding 或缩放时复杂度上升。我自己的一个习惯是每个 flotr2 接入项目都先做一次三浏览器速测在 Chromium、Firefox、Safari 里分别加载一个只有 30 个点的默认折线 demo确认 canvas 行为一致后再往里面堆业务代码。这个习惯帮我躲过过几次上线前才发现 canvas 兼容问题的情况尤其是 resize 相关的渲染差异。多花十分钟做这个小验证后面能省下大把调试时间希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?