如何在Chart.js雷达图中按角度旋转箭头并计算端点?
问题描述
承接之前的提问,需求是让雷达图上的箭头像时钟一样按角度旋转。目前能在圆形底部端点生成箭头,但无法根据圆半径的角度计算正确的箭头端点。尝试过基于XY轴使用sin、cos函数(代码如下),也试过rotate方法,均未成功;使用雷达图的getPointPositionForValue时,箭头会出现在画布随机位置。
尝试的代码片段:
ctx.beginPath(); ctx.arrow( xScale.getPixelForValue(0), yScale.getPixelForValue(0.0), xScale.getPixelForValue(8 * xline90), yScale.getPixelForValue(8 * yline90), [0, 0.75, -15, 0.75, -15, 10] ); ctx.fill(); var xline90 = Math.sin((Math.PI * 2 * 120) / 360); var yline90 = Math.cos((Math.PI * 2 * 120) / 360);
完整示例代码
JavaScript
var mobilewidth = 420; var backgroundcolor = "transparent"; var titleshow = true; var titletext = [ "Example" ]; var titleshowmob = true; var titlecolor = "rgba(75,75,75,1)"; var titlesize = 15; var titlesizemob = 9; var titleweight = "600"; var titlefont = "sans-serif"; var anglelinecolor = "rgba(120,120,120,1)"; var anglelinewidth = 2; var resultcustomer = 4; var resultoperational = 9; var resultproduct = 10; var resultbackgroundcolor = "rgba(247,70,74,0.2)"; var resultlinecolor = "rgba(247,70,74,1)"; var resultlinewidth = 2; var resultpoint = 2; var resultpointwidth = 3; var pointlabelcolor = "rgba(75,75,75,1)"; var pointlabelsize = 15; var pointlabelsizemob = 9; var pointlabelweight = "600"; var pointlabelfont = "sans-serif"; var pointlabelalign = "center"; var pointlabelpadding = 10; var ringbackgroundinner = "rgba(195,195,195,1)"; var ringbackgroundmiddle = "rgba(225,225,225,1)"; var ringbackgroundouter = "rgba(254,254,254,1)"; var ringbordercolor = "rgba(120,120,120,1)"; var ringborderdash = [6, 4]; var ringborderwidth = 1.5; var ctx = document.getElementById("myChart"); var data = { labels: [ ["test"], ["test2"], ["test3"] ], datasets: [ { label: "nvt", backgroundColor: "rgba(38,120,255,0.2)", borderColor: "rgba(38,120,255, 1)", pointRadius: 0, data: [, 0] }, { label: "Score", data: [resultcustomer, resultproduct, resultoperational], borderWidth: resultlinewidth, pointRadius: resultpoint, pointBorderWidth: resultpointwidth, backgroundColor: resultbackgroundcolor, borderColor: resultlinecolor } ] }; var options = { plugins: { legend: { display: false }, tooltip: { enabled: false }, title: { display: function () { if (window.innerWidth < mobilewidth) { return titleshowmob; } else { return titleshow; } }, color: titlecolor, font: { size: function () { if (window.innerWidth < mobilewidth) { return titlesizemob; } else { return titlesize; } }, weight: titleweight, family: titlefont }, text: titletext } }, scales: { r: { angleLines: { color: anglelinecolor, borderWidth: anglelinewidth }, pointLabels: { color: pointlabelcolor, padding: pointlabelpadding, font: { size: function () { if (window.innerWidth < mobilewidth) { return pointlabelsizemob; } else { return pointlabelsize; } }, weight: pointlabelweight, family: pointlabelfont } }, min: 1, max: 10, ticks: { display: false }, grid: { display: false, circular: true } } } }; var plugin1 = { beforeDraw(chart) { var c = chart.ctx; c.save(); c.globalCompositeOperation = "destination-over"; c.fillStyle = backgroundcolor; c.fillRect(0, 0, chart.width, chart.height); c.restore(); var rScale = chart.scales.r; var xline0 = Math.sin(340); var yline0 = Math.cos(340); chart.data.datasets[0].data.forEach(function (item, index) { var circle3 = rScale.getPointPositionForValue(0.5 + index, 10); var base = rScale.getPointPositionForValue(0.5 + index, 1); c.beginPath(); c.lineWidth = ringborderwidth; c.fillStyle = ringbackgroundouter; c.arc(base.x, base.y, circle3.y - base.y, 0, 2 * Math.PI); c.setLineDash(ringborderdash); c.strokeStyle = ringbordercolor; c.stroke(); c.fill(); c.save(); c.beginPath(); c.fillStyle = "black"; c.arrow(base.x, base.y, circle3.x, circle3.y, [ 0, 5, -15, 5, -15, 10 ]); c.fill(); c.restore(); }); } }; var myChart = new Chart(ctx, { type: "radar", data: data, options: options, plugins: [plugin1] });
HTML
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://frogcat.github.io/canvas-arrow/canvas-arrow.js"></script> <html> <canvas id="myChart" /> </html>
解决方案
你的需求可以实现,核心是利用雷达图的角度计算箭头的旋转角度,同时结合Canvas的坐标变换来定位箭头。以下是关键修改点:
1. 正确获取雷达图标签角度
Chart.js的雷达轴(rScale)提供了getAngleForIndex方法,可直接获取对应标签的弧度角度,无需手动计算sin/cos,避免坐标系转换错误。注意雷达图的角度是从Y轴正方向顺时针计算,需要转换为Canvas的逆时针旋转坐标系。
2. 用Canvas变换实现箭头旋转
通过translate将画布原点移到雷达图中心,再rotate到目标角度,此时绘制水平箭头就会自动指向对应角度的方向,最后恢复画布状态即可。
修改后的插件代码
替换原plugin1代码:
var plugin1 = { beforeDraw(chart) { var c = chart.ctx; c.save(); c.globalCompositeOperation = "destination-over"; c.fillStyle = backgroundcolor; c.fillRect(0, 0, chart.width, chart.height); c.restore(); var rScale = chart.scales.r; // 雷达图中心坐标 const centerX = rScale.xCenter; const centerY = rScale.yCenter; // 箭头长度(根据雷达图直径调整比例) const arrowLength = rScale.drawingArea * 0.45; // 遍历每个标签角度绘制箭头 chart.data.labels.forEach((_, index) => { // 获取当前标签的弧度角度 let angle = rScale.getAngleForIndex(index); // 转换为Canvas逆时针旋转坐标系 angle = -angle + Math.PI / 2; c.save(); // 平移画布到雷达中心 c.translate(centerX, centerY); // 旋转画布到目标角度 c.rotate(angle); // 绘制箭头:从中心指向外侧 c.beginPath(); c.fillStyle = "black"; c.arrow(0, 0, arrowLength, 0, [0, 5, -15, 5, -15, 10]); c.fill(); // 恢复画布初始状态 c.restore(); }); } };
关键说明
getAngleForIndex(index):直接获取对应标签的角度,避免手动计算带来的坐标系偏差。- Canvas变换:先平移再旋转,让箭头绘制逻辑简化为水平方向,自动对齐目标角度。
- 箭头长度控制:通过
rScale.drawingArea(雷达图直径)的比例值调整,确保箭头在雷达图内部显示。
修改后,箭头会像时钟指针一样,精准指向每个雷达图标签对应的角度位置。
内容的提问来源于stack exchange,提问作者Patlion
相关产品推荐
相关产品推荐

