ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

d3-scale 中的 Point 比例尺:为散点图实现离散维度定位(scalePoint 完全指南)

d3-scale 中的 Point 比例尺:为散点图实现离散维度定位(scalePoint 完全指南) d3-scale 中的 Point 比例尺为散点图实现离散维度定位scalePoint 完全指南【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3Point scale点比例尺是 d3 中最容易被忽视、却在散点图与分类维度定位中不可或缺的比例尺类型它把离散的 domain 值如a、b、c或任意字符串、数字一一映射到连续 range 中的“点”上且带宽bandwidth恒为零。本篇基于 d3 官方文档 Point scales 逐项讲解scalePoint的构造与全部 API——domain、range、rangeRound、round、padding、align、step、bandwidth、copy——并结合本仓库的 d3-scale 总览、Band scales 与 InternMap 说明 等文档以及仓库的 入口文件、测试 和文档渲染组件 PlotRender.js帮你建立从 API 用法到底层机制的完整认知能直接用于编写可运行的分类散点图代码。一、Point scale 是什么band scale 的“零带宽特例”官方文档对 Point scale 的定义一句话就说清了它的定位Point scales are a variant of band scales with the bandwidth fixed to zero. Point scales are typically used for scatterplots with an ordinal or categorical dimension.即Point scale 是 band scale 的变体带宽被固定为零典型应用场景是带有序或分类维度的散点图。在 d3-scale 总览页 中所有比例尺按用途分为几类其中与 Point scale 最相关的是两条Band scales - for categorical or ordinal data as a position encoding面向分类/序数数据的定位编码Point scales - for categorical or ordinal data as a position encoding同样面向分类/序数数据的定位编码两者的区别可以这样理解对比项Band scalePoint scale带宽bandwidth()返回每个 band 的宽度恒返回 0调用x(value)的返回值对应 band 的起点对应的点位置典型图表柱状图每个分类占一段宽度散点图每个分类占一个精确坐标padding 接口paddingInner/paddingOuter/padding三件套只有padding等价于 band 的paddingOuter底层实现同一份源码同一份源码band.js 中构造bandwidth 置零由于文档中 Point scale 的每个方法都指向 band scale 的同一实现源文件可以推断scalePoint在内部就是复用了 band scale 的布局算法只是把带宽固定为 0使每个分类占据一个“无宽度”的位置。理解这一点对理解后文的step、padding、align行为至关重要——point scale 的空间布局语义与 band scale 完全同构只是“band 的宽度”退化成了 0。二、构造比例尺scalePoint(domain,range)d3.scalePoint(*domain*, *range*)创建一个 point scale。可选参数domaindomain 值数组不指定时默认为空 domain[]。range两元素数字数组不指定时默认为单位区间[0, 1]。构造时没有 padding、没有取整rounding、居中对齐align 为 0.5这些都需要通过后续方法显式设置。// 最常用写法同时给定 domain 与 range const x d3.scalePoint([a, b, c], [0, 960]); // 也可以只给 range注意单参数会被解释为 range 而非 domain const y d3.scalePoint().range([0, 960]);调用构造后的 scale 函数完成映射const x d3.scalePoint([a, b, c], [0, 960]); x(a); // 0 x(b); // 480 x(c); // 960 x(d); // undefined三个要点首尾点会落在 range 的端点上。a映射到0、c映射到960中间点均匀分布。这与 band scale 的“band 起点”不同——point scale 没有内边距概念所以默认就顶满整个 range。domain 外的值返回undefined。这是 point scale以及 band、ordinal 等离散 scale的统一行为实际写代码时建议对x(d)这类缺失值做防御处理避免把undefined传给 SVG 的cx/cy属性。与 Band scales 文档 中scaleBand的一个微妙差别scaleBand在只给一个参数时把该参数解释为range而scalePoint的文档明确写着单参形式是scalePoint(*domain*, *range*)示例代码中也用d3.scalePoint([0, 960]).domain([...])这种“先给 range、再链式设 domain”的方式写代码时请严格遵循文档给出的调用形态。文档中的动态示例原文档 point.md 在domain、round、padding、align各节都内嵌了可交互的 Plot 示例如输入框改 domain、滑块调 padding/align其渲染由文档站点组件 PlotRender.js 驱动该组件把Plot.plot(options)的结果先经服务端observablehq/plot在 Node 环境用 mock DOM 渲染再转换为 Vue 的虚拟 DOM 做客户端水合图表宽度默认为 688px。因此以下代码摘录保留了文档示例的核心配置Plot.frame绘制边框、Plot.tickX在对应位置绘制刻度你可以把它作为理解“点落在哪”的可视化参照// 对应 point.md 中 padding 一节的交互式 Plot 配置 { grid: true, marginTop: 0.5, x: { padding: 0.1, round: false, domain: [a, b, c, d, e, f] }, marks: [ Plot.frame({strokeOpacity: 0.3}), Plot.tickX([a, b, c, d, e, f], {x: Plot.identity, stroke: currentColor}) ] }Plot.tickX中x: Plot.identity表示让 Plot 自动按 point scale 布局规则计算每个标签的横坐标等价于你手动调用x(value)得到每个点的位置。三、*point*(*value*)把 domain 值映射为坐标给定 domain 内的一个value返回由输出 range 推导出的对应点位置一个数字给定不在 domain 中的值则返回undefined见第二节示例。从文档描述“Domain values are stored internally in an InternMap from primitive value to index”可知其内部机制每个 domain 值被**强制转换为原始值primitive**后存入一个 InternMapintern 结构由 d3-array 提供查找时先取索引 index再用索引参与布局计算得出点位置因此domain 值必须可强制转换为原始值且原始值必须能唯一标识对应的点——例如两个仅引用地址不同但值相同的对象字面量{a: 1}会被视为同一 domain 值。四、domain/range/rangeRoundpoint.domain(domain)指定时把 domain 设为给定的值数组并返回 scale不指定时返回当前 domain。const x d3.scalePoint([0, 960]).domain([a, b, c]);domain的第一个元素映射到第一个点第二个元素映射到第二个点依此类推。文档示例point.md中的动态代码按当前输入值生成形如const x d3.scalePoint([0, 960]).domain([a, b, c, d, e, f]);point.range(range)指定时把 range 设为给定的两元素数字数组并返回 scale元素若非数字会被强制转换为数字不指定时返回当前 range默认[0, 1]。const x d3.scalePoint().range([0, 960]);point.rangeRound(range)在设置 range 的同时顺带开启取整等价于point.range(range).round(true)const x d3.scalePoint().rangeRound([0, 960]);取整有时可以避免抗锯齿伪影antialiasing artifacts文档同时提示也可以考虑使用 SVG 的shape-renderingcrispEdges样式达到类似目的。五、round整数坐标与“剩余空间”陷阱point.round(round)指定时按round布尔值开启或关闭取整并返回 scale不指定时返回是否已开启取整默认false。const x d3.scalePoint([a, b, c], [0, 960]).round(true); x.round(); // true开启取整后每个点的位置都会是整数。文档特别强调了一个容易踩坑的行为Note that if the width of the domain is not a multiple of the cardinality of the range, there may be leftover unused space, even without padding!即如果 range 宽度不是 domain 元素个数的整数倍取整后会存在剩余未使用的空间——哪怕 padding 为 0。此时用align决定这些剩余空间如何分布。文档中对应的交互式示例可开关 Round 复选框配置为 10 个点、padding 0.2// 对应 point.md 中 round 一节的 Plot 配置 { grid: true, marginTop: 0.5, x: { padding: 0.2, round: true /* 由复选框控制 */ }, marks: [ Plot.frame({strokeOpacity: 0.3}), Plot.tickX(abcdefghij, {x: Plot.identity, stroke: currentColor}) ] }六、padding只控外边距且以 step 为单位point.padding(padding)指定时把**外边距outer padding**设为给定数值典型取值范围[0, 1]不指定时返回当前外边距默认0。const x d3.scalePoint([a, b, c], [0, 960]).padding(0.5); x.padding(); // 0.5文档原文说明了两点关键语义外边距以 step 的倍数计The outer padding specifies the amount of blank space, in terms of multiples of the step, to reserve before the first point and after the last point.它等价于 band scale 的paddingOuter见 band 文档对应小节point scale 没有paddingInner的概念——因为点没有宽度点与点之间的间隔由 step 决定唯一可调的就是首点之前、末点之后的空白。文档中配套的交互式示例0~1 的滑块调 padding10 个点// 对应 point.md 中 padding 一节的 Plot 配置 { grid: true, marginTop: 0.5, x: { padding: 0.5, round: false }, marks: [ Plot.frame({strokeOpacity: 0.3}), Plot.tickX(abcdefghij, {x: Plot.identity, stroke: currentColor}) ] }直观理解padding 从 0 增大到 1 时整体点阵从“顶满 range”逐渐收缩首尾各多出接近一个 step 的空白。七、align剩余空间的分配策略point.align(align)指定时把对齐值设为给定数字必须位于[0, 1]区间不指定时返回当前对齐值默认0.5。const x d3.scalePoint([a, b, c], [0, 960]).align(0.25); x.align(); // 0.25语义align 指定 range 中剩余未使用空间如何分配0.5默认剩余空间均分到首点之前与末点之后点阵在 range 中居中0或1把点阵整体推到某一侧——文档给出的典型动机是“position them adjacent to an axis”即让点紧贴坐标轴边缘避免点被画布边缘裁切。对应交互式示例滑块 0~1padding 固定 0.2// 对应 point.md 中 align 一节的 Plot 配置 { grid: true, marginTop: 0.5, x: { padding: 0.2, align: 0.5, round: false }, marks: [ Plot.frame({strokeOpacity: 0.3}), Plot.tickX(abcdefghij, {x: Plot.identity, stroke: currentColor}) ] }实战提示散点图的点通常带半径cx x(category)若首尾点落在 range 端点上半截点会被 SVG 边界裁掉。常规解法是二选一padding(0.5)留出半个 step 的外边距或align(0)/align(1)配合图表留白把点阵内收。八、step()、bandwidth()与copy()point.step()返回相邻两个点之间的距离数字。文档示例配置10 个点、padding 0.2、range 为[20, 688-20]中用自定义 symbol 标注了 step 的几何含义——从第一个点画一条带箭头的尺寸线到x.step处。它常用于计算点的大小上限、碰撞半径或辅助网格间距例如const r x.step() / 4;让相邻点之间保持一定间距。point.bandwidth()恒返回 0。这正是 Point scale 与 Band scale 的本质区别第一节已述也是把scaleBand换到散点图时最常见的适配点凡是需要用x.bandwidth()决定元素宽度的代码在 point scale 下都要改为固定尺寸或以x.step()推导。point.copy()返回该 scale 的一个精确副本。对本 scale 的修改不会影响副本反之亦然const x1 d3.scalePoint([a, b, c], [0, 960]).padding(0.5); const x2 x1.copy(); // x2 继承 x1 的全部当前配置这在需要“同一套 domain/range 但不同 padding”的多个坐标系如主图与迷你图时非常有用避免手动重放整条配置链。九、完整速查表API行为指定参数时行为不指定参数时默认值scalePoint(domain, range)创建 point scale—domain[]range[0, 1]无 padding、无取整、align 0.5point(value)返回 value 对应的点位置domain 外返回undefined——point.domain(domain)设置 domain 并返回 scale返回当前 domain[]point.range(range)设置 range两元素数字数组非数字会被强制转换并返回 scale返回当前 range[0, 1]point.rangeRound(range)设 range 并开启取整等价range(range).round(true)——point.round(round)开关取整并返回 scale返回是否开启取整falsepoint.padding(padding)设置外边距以 step 倍数计通常[0, 1]并返回 scale返回当前外边距0point.align(align)设置对齐值必须[0, 1]并返回 scale返回当前对齐值0.5point.step()—返回相邻点距离—point.bandwidth()—恒返回0—point.copy()—返回 scale 的精确副本—十、一个可直接运行的散点图示例把上述 API 组合起来下面是把分类维度映射到 SVG 横轴的典型写法与文档示例一致的最小可用形态import * as d3 from d3; // 1. 定义分类 domain 与绘图区 range const x d3.scalePoint() .domain([a, b, c, d, e, f]) .range([40, 960 - 40]) // 左右各留 40px 边距 .padding(0.5); // 首尾各留半个 step防止点被裁切 const y d3.scaleLinear().domain([0, 1]).range([480, 40]); // 2. 逐点定位 const data [ {group: a, value: 0.21}, {group: c, value: 0.77}, {group: f, value: 0.42} ]; // 3. 渲染selection API参见 d3-selection 文档 const svg d3.create(svg).attr(width, 960).attr(height, 520); svg.append(g) .selectAll(circle) .data(data) .join(circle) .attr(cx, (d) x(d.group)) // point scale一个精确坐标 .attr(cy, (d) y(d.value)) .attr(r, Math.min(10, x.step() / 6)) // 半径由 step 推导避免重叠 .attr(fill, steelblue);要点回顾x(d.group)返回的是点坐标本身所以cx直接可用r基于x.step()推导保证 domain 变多时点不会互相覆盖若d.group不在 domain 中x(...)返回undefined应提前过滤数据。十一、在 d3 仓库中的位置与验证方式导出关系本仓库是 d3 的聚合入口src/index.js 中export * from d3-scale;一行把 d3-scale 模块的全部导出包括scalePoint转发到d3命名空间package.json 显示其依赖d3-scale: ^4.0.2。导出完整性由测试保证test/d3-test.js 遍历package.json中每个依赖模块的全部导出名断言它们都存在于d3命名空间下d3 exports everything from d3-scale一类断言即scalePoint必然可用。运行yarn test等价于 mocha 跑test/**/*-test.js加 eslint可以验证。文档站构建本仓库docs/目录即 d3 官方文档站的 VitePress 源码point.md 中的交互图由 PlotRender.js 与 deferRender.js 驱动IntersectionObserver 延迟渲染图表默认宽 688pxyarn docs:dev可本地起站查看这些交互示例。相关文档Band scales同构实现与 step/bandwidth 几何语义、Ordinal scales分类数据的其他定位方式、InternMapdomain 值的内部存储结构、d3-axis用比例尺渲染坐标轴。小结Point scale 是“band scale 带宽恒为 0”的定位比例尺专为带分类维度的散点图设计bandwidth()恒为 0点位置由step均匀划分。核心配置只有四个旋钮padding等价于 band 的 paddingOuter以 step 倍数计、align剩余空间分配0.5 居中、round/rangeRound整数坐标注意剩余空间陷阱、domain/range映射两端。domain 值经 InternMap 以原始值建索引domain 外的查询返回undefined代码中需防御。与 band scale 的代码互换时把所有x.bandwidth()用法替换为固定尺寸或基于x.step()的推导即可从柱状图语境迁移到散点图语境。【免费下载链接】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),仅供参考
返回列表