Chart.js v2 水平条形图圆角实现技术求助
搞定Chart.js v2水平条形图的圆角问题
我明白你现在的困扰——把垂直条形图的圆角逻辑改成水平的,卡在二次贝塞尔曲线这儿了对吧?别担心,我一步一步给你讲清楚怎么改。
首先,先搞懂原代码里的drawRoundedTopRectangle是干嘛的:它给垂直条形的顶部两个角做圆角,底部是直角。但水平条形图我们需要的是右侧(条形末端)的两个角做圆角,所以得完全重写这个辅助函数的路径逻辑。
先搞懂二次贝塞尔曲线
Chart.js里的quadraticCurveTo(cpX, cpY, endX, endY)其实很简单:
- 第一个参数
(cpX, cpY)是控制点,它决定了曲线的弯曲方向和弧度大小 - 第二个参数
(endX, endY)是曲线的终点 - 曲线会从当前画布的位置(上一个路径点)平滑过渡到终点,控制点就是“拉着”曲线变弯的那个点
修改辅助函数适配水平条形图
我们把原函数重命名为drawRoundedEndRectangle(更贴合水平场景),然后重新设计路径:
Chart.helpers.drawRoundedEndRectangle = function(ctx, x, y, width, height, radius) { ctx.beginPath(); // 从条形左下角开始 ctx.moveTo(x, y + height); // 画到左上角 ctx.lineTo(x, y); // 画到右上角的圆角起点(留出半径长度的位置) ctx.lineTo(x + width - radius, y); // 绘制右上角圆角:控制点在右上角顶点,终点到右侧中间位置 ctx.quadraticCurveTo(x + width, y, x + width, y + radius); // 绘制右下角圆角:控制点在右下角顶点,终点到右侧中间位置 ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); // 回到左下角,闭合路径 ctx.lineTo(x, y + height); ctx.closePath(); };
调整条形元素的绘制逻辑
接下来还要修改条形元素的draw方法,主要是两个地方:
- 针对水平条形,计算圆角半径时要用条形的高度(而不是宽度),因为圆角是在垂直方向弯曲的
- 把调用的辅助函数换成我们新写的
drawRoundedEndRectangle
修改后的完整代码如下:
Chart.helpers.drawRoundedEndRectangle = function(ctx, x, y, width, height, radius) { ctx.beginPath(); ctx.moveTo(x, y + height); ctx.lineTo(x, y); ctx.lineTo(x + width - radius, y); ctx.quadraticCurveTo(x + width, y, x + width, y + radius); ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); ctx.lineTo(x, y + height); ctx.closePath(); }; Chart.elements.RoundedEndRectangle = Chart.elements.Rectangle.extend({ draw: function() { const ctx = this._chart.ctx; const vm = this._view; let left, right, top, bottom, signX, signY, borderSkipped; let borderWidth = vm.borderWidth; if (!vm.horizontal) { // 保留原垂直条形的逻辑(如果需要的话) left = vm.x - vm.width / 2; right = vm.x + vm.width / 2; top = vm.y; bottom = vm.base; signX = 1; signY = bottom > top ? 1 : -1; borderSkipped = vm.borderSkipped || 'bottom'; } else { // 水平条形的坐标逻辑 left = vm.base; right = vm.x; top = vm.y - vm.height / 2; bottom = vm.y + vm.height / 2; signX = right > left ? 1 : -1; signY = 1; borderSkipped = vm.borderSkipped || 'left'; } // 边框宽度调整逻辑(保留原逻辑即可) if (borderWidth) { const barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom)); borderWidth = borderWidth > barSize ? barSize : borderWidth; const halfStroke = borderWidth / 2; const borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0); const borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0); const borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0); const borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0); if (borderLeft !== borderRight) { top = borderTop; bottom = borderBottom; } if (borderTop !== borderBottom) { left = borderLeft; right = borderRight; } } // 计算水平条形的圆角半径:用高度的一半乘以roundness const barHeight = Math.abs(top - bottom); const roundness = 1.0; // 你可以调整这个值控制圆角弧度(0是直角,1是最大圆角) const radius = barHeight * roundness * 0.5; // 调整条形位置,留出圆角空间 const prevRight = right; right = prevRight - radius; const barRadius = prevRight - right; ctx.beginPath(); ctx.fillStyle = vm.backgroundColor; ctx.strokeStyle = vm.borderColor; ctx.lineWidth = borderWidth; // 调用新的圆角绘制函数 Chart.helpers.drawRoundedEndRectangle(ctx, left, top, prevRight - left, barHeight, barRadius); ctx.fill(); if (borderWidth) { ctx.stroke(); } // 恢复原始坐标,保证 tooltip 和刻度正常工作 right = prevRight; }, }); Chart.defaults.roundedBar = Chart.helpers.clone(Chart.defaults.bar); Chart.controllers.roundedBar = Chart.controllers.bar.extend({ dataElementType: Chart.elements.RoundedEndRectangle });
关键修改点总结
- 辅助函数路径完全适配水平条形的右侧圆角,用二次贝塞尔曲线画出两个圆角
- 水平条形的圆角半径计算改为基于条形高度,这样圆角大小会和条形高度匹配
- 调整了条形的右侧位置,留出绘制圆角的空间,避免圆角被截断
这样修改后,你的水平条形图就能完美显示右侧圆角啦!
内容的提问来源于stack exchange,提问作者Siddharth Venkatesh
相关产品推荐
相关产品推荐

