
d3 径向区域生成器 areaRadial 详解角度/半径坐标系、完整 API 与 d3-shape 源码剖析【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3本文以 d3 官方文档docs/d3-shape/radial-area.md为主体系统讲解 d3-shape 中的径向区域生成器areaRadial()它把笛卡尔 区域生成器 的 x/y 访问器替换为 angle角度与 radius半径访问器用于极坐标区域图如昼夜温度带、24 小时径向图。全文覆盖全部 API 方法与默认行为并结合 d3-shape v3.2.0 的源码areaRadial.js、curve/radial.js、point.js解释角度约定0 位于 12 点方向的坐标变换实现最后给出 SVG / Canvas 两套可直接运行的完整示例。1. 核心概念把 x/y 换成 angle/radius径向区域生成器与 area 生成器 的工作机制完全一致——由一条“上边界线”topline和一条“基线”baseline围成一个闭合多边形——区别只在于坐标系笛卡尔区域用x/y0/y1描述每个点的位置径向区域则改用角度弧度制 半径到原点的距离笛卡尔区域 area径向区域 areaRadial含义.x(x).angle(angle)一维定位x 坐标 → 角度弧度.y(y).radius(radius)一维定位y 坐标 → 半径到原点的距离.x0(x).startAngle(angle)起点定位.x1(x).endAngle(angle)终点定位.y0(y).innerRadius(radius)内边界.y1(y).outerRadius(radius)外边界文档原文的关键约束有两条后面源码部分会解释它们的实现来源角度单位为弧度0 位于 -y12 点方向即正上方顺时针增加径向区域相对原点 (0,0) 定位想移动图形位置必须用 transformSVG或上下文的变换Canvas改变原点而不是给每个点加偏移。径向线与笛卡尔线的对应关系见 radial lines 文档本文第 3.6 节的lineStartAngle等方法正是基于该生成器派生的。2. 适用前提版本与依赖当前仓库是 d3 主包v7.9.0其中d3-shape依赖声明为^3.2.0本仓库 src/index.js 通过export * from d3-shape将areaRadial等 API 重导出因此在浏览器端加载 d3 v7 后d3.areaRadial()即可用areaRadial自 d3 v4 起随 shape 模块重构引入v3 及更早版本没有该 API参见 CHANGES.mdv7 中其 API 面与本文一致若单独安装模块包npm install d3-shape后从d3-shape直接导入areaRadial亦可。3. 完整 API 参考以下逐条对应 radial-area 文档示例代码保持文档原样注释为补充说明。3.1 areaRadial()构造一个使用默认设置的径向区域生成器。const area d3.areaRadial();3.2areaRadial(data)对输入数组data生成区域。返回 SVG 的path数据字符串若设置了 context则改为向该上下文发出一系列 path 方法调用并返回void。svg.append(path).attr(d, area(data));文档提醒与 area 文档 一致取决于所用 curve输入data可能需要先按角度排序再传入——曲线插值假设点序是单调的。3.3areaRadial.angle(angle)等价于area.x区别是访问器返回弧度制角度0 位于 12 点方向。不设置时angle与startAngle同时生效见 第 4 节源码分析 中a.angle a.x的别名实现。const area d3.areaRadial().angle((d) a(d.Date));3.4areaRadial.startAngle(angle) /areaRadial.endAngle(angle)分别等价于area.x0 与area.x1访问器返回弧度制角度0 位于 12 点方向。文档原注通常使用angle而不是分别设置 start/end angle只有当两个边界需要不同角度类似笛卡尔区域的“竖直/带状”区域时才用到它们。3.5areaRadial.radius(radius)等价于area.y访问器返回半径到原点的距离。const area d3.areaRadial().radius((d) r(d.temperature));3.6areaRadial.innerRadius(radius)等价于area.y0访问器返回半径。文档示例const area d3.areaRadial().radius((d) r(d.low));注意文档此处示例使用的是.radius(...)而非.innerRadius(...)。从源码看两者等价a.radius a.y、a.innerRadius a.y0而area().y内部就是设置y0但语义上“只设内边界”时应使用.innerRadius()——radius只把y1置空回退到y0而.innerRadius()不会动y1。3.7areaRadial.outerRadius(radius)等价于area.y1访问器返回半径。文档示例const area d3.areaRadial().radius((d) r(d.high));同样的注意文档示例写的是.radius(...)。若要生成典型的“高低温带状”区域内边界 外边界两条不同的半径应显式同时调用.innerRadius()与.outerRadius()见 第 5 节实践示例。3.8areaRadial.defined(defined)等价于area.defined返回false的输入会打断区域生成多个不连续的区域段。const area d3.areaRadial().defined((d) !isNaN(d.temperature));3.9areaRadial.curve(curve)等价于area.curve可选值与 curves 文档 一致。文档原注不建议对径向区域使用 curveMonotoneX 或 curveMonotoneY因为它们假设数据在 x 或 y 方向单调这对径向数据通常不成立。const area d3.areaRadial().curve(d3.curveBasisClosed);注意无论传入什么曲线源码都会把它包装成径向曲线见 4.3因此不需要也不应该自己手动做极坐标换算。3.10areaRadial.context(context)等价于area.context把输出目标从 path 字符串切换为 Canvas 2D 上下文。const context canvas.getContext(2d); const area d3.areaRadial().context(context);3.11 派生线生成器lineStartAngle / lineEndAngle / lineInnerRadius / lineOuterRadius这四个方法都返回一个新的 径向线生成器继承该区域的当前 defined 访问器、curve 与 context差异在于角度与半径访问器的取值来源方法线的角度访问器线的半径访问器lineInnerRadius()startAngle 访问器innerRadius 访问器lineStartAngle()startAngle 访问器innerRadius 访问器lineEndAngle()endAngle 访问器innerRadius 访问器lineOuterRadius()startAngle 访问器outerRadius 访问器其中lineInnerRadius()是lineStartAngle()的别名文档明确标注。这些方法用于在同一张极坐标图上额外描出区域的内/外轮廓线例如昼夜温度带的上下边缘svg.append(path).attr(d, area.lineInnerRadius()(data)).attr(fill, none).attr(stroke, #c44); svg.append(path).attr(d, area.lineOuterRadius()(data)).attr(fill, none).attr(stroke, #c44);4. 源码剖析angle/radius 是如何变成 SVG 坐标的以下分析基于当前仓库package.json所依赖的 d3-shape v3.2.0 源码本仓库为 d3 文档与聚合包d3-shape 的实现随依赖发布文档中每条 API 的 Source 均指向 d3-shape 仓库的src/areaRadial.js。4.1 areaRadial() area() 的重命名封装areaRadial.js全文只做了两件事以径向默认曲线创建笛卡尔 area然后把 x/y 系方法整体改名为 angle/radius 系方法并删除旧名export default function() { var a area().curve(curveRadialLinear), c a.curve, x0 a.lineX0, x1 a.lineX1, y0 a.lineY0, y1 a.lineY1; a.angle a.x, delete a.x; a.startAngle a.x0, delete a.x0; a.endAngle a.x1, delete a.x1; a.radius a.y, delete a.y; a.innerRadius a.y0, delete a.y0; a.outerRadius a.y1, delete a.y1; a.lineStartAngle function() { return lineRadial(x0()); }, delete a.lineX0; a.lineEndAngle function() { return lineRadial(x1()); }, delete a.lineX1; a.lineInnerRadius function() { return lineRadial(y0()); }, delete a.lineY0; a.lineOuterRadius function() { return lineRadial(y1()); }, delete a.lineY1; a.curve function(_) { return arguments.length ? c(curveRadial(_)) : c()._curve; }; return a; }由此可以得到几个文档未展开、但对排错有用的实现事实a.angle a.x——.angle()与笛卡尔的.x()是同一个函数设置x0并把x1置null。因此“设置angle而非 start/end angle”的文档建议在实现层面就是“x1 null时复用x0”径向区域默认曲线不是曲线名curveLinear而是curveRadialLinear即径向包装后的 linear 曲线——这是areaRadial与area唯一的行为差异入口a.curve被重写为c(curveRadial(_))设置时把传入曲线包一层径向适配后再交给内部 area读取时返回c()._curve解包后的原始曲线所以areaRadial().curve()取回的是你最初传进去的曲线对象而不是包装器lineStartAngle等方法在创建时被“冻结”为对创建时刻x0/x1/y0/y1的闭包引用x0 a.lineX0而访问器函数本身是稳定的后续.angle()/.innerRadius()的重新赋值会作用在同一函数上——这正是 3.11 节“派生线继承区域访问器”的来源。4.2 角度约定0 在 12 点方向、顺时针增加角度到坐标的换算由默认的径向定位函数pointRadial.js完成全部实现就一行export default function(x, y) { return [(y y) * Math.cos(x - Math.PI / 2), y * Math.sin(x)]; }这里x是角度、y是半径。关键在x - Math.PI / 2标准数学角从 3 点方向逆时针度量减去 π/2 后零角度被转到正上方12 点方向再叠加 SVG/Canvas 的 y 轴向下约定角度沿屏幕顺时针增加。对应到极坐标图就是角度方向变换后的坐标 (r 半径)012 点(0, −r)π/23 点(r, 0)π6 点(0, r)3π/29 点(−r, 0)这一约定与 d3-axis 的弧轴、Canvas 的arc方法一致因此在极坐标图上叠加刻度、标签时可以直接复用同一套角度值。4.3 curveRadial把任意曲线变成径向曲线curve/radial.js实现了一个对任意曲线的适配层Radial它把 area/line 的曲线协议areaStart/areaEnd/lineStart/lineEnd/point全部透传给内部曲线仅在point处追加坐标变换Radial.prototype { // areaStart / areaEnd / lineStart / lineEnd 均透传给 this._curve point: function(a, r) { this._curve.point(r * Math.sin(a), r * -Math.cos(a)); } };注意这里的变换x r·sin(a)、y −r·cos(a)与 4.2 节结论完全一致a0 得 (0, −r)。这意味着曲线的平滑插值发生在笛卡尔空间径向区域先被展开为普通的平面曲线用角度、半径换算出的 x/y再交给线性/B 样条/Catmull-Rom 等曲线插值——这也解释了为什么curveBasisClosed这类曲线特别适合“首尾闭合”的整圈径向图24 小时数据首尾相接时首尾切线自然衔接。4.4 默认访问器一览含一个易踩的坑从area.js与point.js的源码可以确认各访问器的默认值径向名称与笛卡尔名称一一对应访问器默认值默认行为输入为二元数组[a, r]时angle / startAnglex0(d) d[0]角度取d[0]endAnglex1null复用 anglestartAngleradius / innerRadiusy0() 0半径恒为 0圆心outerRadiusy1(d) d[1]外半径取d[1]defined() true所有点均有效最后一行是径向区域与笛卡尔区域的一个微妙差异outerRadius的默认值是“读d[1]”而innerRadius的默认是常数 0。据此我们用 d3-shape 3.2.0 实际运行了三组输入数据为[角度, 半径]二元数组// 输入均为 [[0, 9], [π/2, 9], [π, 9]] 之类的点列 d3.areaRadial().radius(() 50)([[0,9],[Math.PI/2,9],[Math.PI,9]]) // → M0,-50L50,0L0,50L0,50L50,0L0,-50Z d3.areaRadial()([[0,100],[Math.PI/2,100],[Math.PI,100]]) // → M0,-100L100,0L0,100L0,0L0,0L0,0Z第一组输出值得细看.radius(f)只设置了y0y1仍为null复用 y0因此内外边界完全重合输出的 path 是先画一遍上边界、再原路折返的退化闭合形L0,50L0,50L50,0L0,-50中出现了折返段。第二组则展示了“完全裸奔”的默认组合内边界取常数 0全部点聚到圆心0,0外边界取d[1]。由此可以确认文档 3.6/3.7 两节示例的实际行为.radius(f)在实现上等于“设 innerRadius 并清空 outerRadius 的独立访问器”。如果你的数据是对象数组如{date, low, high}务必显式同时调用.innerRadius()与.outerRadius()——只调用.radius()会让外边界回退到默认访问器去读d[1]得到NaN的无效路径。5. 实践示例SVG 与 Canvas5.1 SVG一张完整的 24 小时昼夜温度径向图svg width300 height300 viewBox0 0 300 300/svgconst svg d3.select(svg); // 24 小时的低/高温数据 const data d3.range(24).map((hour) ({ hour, low: 12 4 * Math.sin((hour - 6) / 24 * 2 * Math.PI), high: 22 5 * Math.sin((hour - 4) / 24 * 2 * Math.PI) })); // 角度比例尺小时 → [0, 2π)0 落在 12 点方向 const a d3.scaleLinear().domain([0, 24]).range([0, 2 * Math.PI]); // 半径比例尺温度 → 像素半径 const r d3.scaleLinear().domain([0, 30]).range([0, 110]); const area d3.areaRadial() .angle((d) a(d.hour)) .innerRadius((d) r(d.low)) // 内边界 .outerRadius((d) r(d.high)) // 外边界 .curve(d3.curveCatmullRomClosed.alpha(0.5)); // 首尾闭合的光滑曲线 // 径向区域相对原点定位用 transform 把原点移到画布中心 svg.append(g) .attr(transform, translate(150,150)) .append(path) .attr(d, area(data)) .attr(fill, #69b3a7);要点回顾角度用scaleLinear把时间域映射到[0, 2π)天然满足“0 在 12 点方向、顺时针增加”的约定用transform平移原点而不是修改访问器输出——这是文档强调的定位方式curveCatmullRomClosedcurveBasisClosed同理保证 24 点数据首尾平滑闭合避免在 0 点12 点方向出现折角。5.2 Canvas同一生成器直出路径const context canvas.getContext(2d); context.translate(150, 150); // 同样先移动原点 const area d3.areaRadial() .context(context) .angle((d) a(d.hour)) .innerRadius((d) r(d.low)) .outerRadius((d) r(d.high)); context.beginPath(); area(data); // 已设置 context不再返回字符串而是直接调用上下文的 path 方法 context.fill();6. 小结areaRadial()是area()的径向坐标版本x/x0/x1/y/y0/y1依次对应angle/startAngle/endAngle/radius/innerRadius/outerRadiusdefined/curve/context行为不变角度以弧度表示0 位于 12 点方向、顺时针增加该约定由pointRadial.js中x - Math.PI/2的旋转实现并被curve/radial.js的point(a, r)透传应用到曲线插值上图形相对原点定位位置调整一律交给 transform设置只写一个半径访问器时注意radius只动y0显式区分innerRadius/outerRadius才能得到真正的带状区域需要单独描边界时用lineInnerRadius()lineStartAngle()、lineEndAngle()、lineOuterRadius()派生径向线生成器。【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考