多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Chart.js 派生轴类型实战:从零实现并注册一个 Log2 对数轴(Derived Axis Type)

Chart.js 派生轴类型实战:从零实现并注册一个 Log2 对数轴(Derived Axis Type) Chart.js 派生轴类型实战从零实现并注册一个 Log2 对数轴Derived Axis Type【免费下载链接】Chart.jsSimple HTML5 Charts using thetag项目地址: https://gitcode.com/gh_mirrors/ch/Chart.jsChart.js 允许开发者继承内置Scale基类自定义全新的坐标轴类型并通过注册机制在图表配置中直接使用。本文以官方示例derived-axis-type为骨架完整剖析一个log2以 2 为底的对数轴从类定义、刻度生成、像素换算到注册使用的全过程并结合仓库源码对照内置LogarithmicScale的实现帮助读者掌握派生轴类型这一扩展机制的完整套路能够照此实现自己的自定义轴。示例概览在折线图上使用 log2 轴本示例位于 docs/samples/advanced/derived-axis-type.md它构造了一个包含 12 个标签的折线图数据集数据值分布在 0~1000 之间横轴使用默认的类目轴x纵轴则换成自定义的log2轴y。配置如下const DATA_COUNT 12; const NUMBER_CFG {count: DATA_COUNT, min: 0, max: 1000}; const labels Utils.months({count: DATA_COUNT}); const data { labels: labels, datasets: [ { label: My First dataset, data: Utils.numbers(NUMBER_CFG), borderColor: Utils.CHART_COLORS.red, backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5), fill: false, } ], }; const config { type: line, data, options: { responsive: true, scales: { x: { display: true, }, y: { display: true, type: log2, } } } };要点说明Utils.months、Utils.numbers、Utils.CHART_COLORS、Utils.transparentize是文档示例专用的辅助函数实现在 docs/scripts/utils.js其中numbers({count, min, max})在[min, max]区间生成count个随机数months({count})循环生成月份名transparentize基于kurkle/color为颜色附加透明度。需要强调的是这些工具函数不会随 Chart.js 库一起发布只是文档构建与示例演示所用复制到自有项目时需自行实现参见 docs/samples/information.md 中的说明。图表类型为linefill: false表示不填充折线下方区域。关键一行是scales.y.type: log2——这正是自定义轴注册后的引用方式type字符串必须与轴类的静态id完全一致。派生轴的核心机制继承 Scale 并注册在深入实现前先明确派生轴类型的三步套路对应官方文档 docs/developers/axes.md继承基类自定义轴应继承Chart.ScaleScale按需覆写接口方法声明静态属性设置id轴类型的唯一标识与defaults默认配置可继承父类注册通过Chart.register(MyScale)注册若新轴不是直接继承Chart.Scale则需显式调用Chart.registry.addScales(MyScale)。注册完成后即可在任意图表配置中通过scales: { y: { type: log2 } }引用该类型。Log2Axis 实现逐行拆解示例的完整实现位于 docs/scripts/log2.js全文如下已整理为可直接复制的形态import {Scale, LinearScale} from chart.js; export default class Log2Axis extends Scale { constructor(cfg) { super(cfg); this._startValue undefined; this._valueRange 0; } parse(raw, index) { const value LinearScale.prototype.parse.apply(this, [raw, index]); return isFinite(value) value 0 ? value : null; } determineDataLimits() { const {min, max} this.getMinMax(true); this.min isFinite(min) ? Math.max(0, min) : null; this.max isFinite(max) ? Math.max(0, max) : null; } buildTicks() { const ticks []; let power Math.floor(Math.log2(this.min || 1)); let maxPower Math.ceil(Math.log2(this.max || 2)); while (power maxPower) { ticks.push({value: Math.pow(2, power)}); power 1; } this.min ticks[0].value; this.max ticks[ticks.length - 1].value; return ticks; } configure() { const start this.min; super.configure(); this._startValue Math.log2(start); this._valueRange Math.log2(this.max) - Math.log2(start); } getPixelForValue(value) { if (value undefined || value 0) { value this.min; } return this.getPixelForDecimal(value this.min ? 0 : (Math.log2(value) - this._startValue) / this._valueRange); } getValueForPixel(pixel) { const decimal this.getDecimalForPixel(pixel); return Math.pow(2, this._startValue decimal * this._valueRange); } } Log2Axis.id log2; Log2Axis.defaults {};parse数据解析与正数约束parse(raw, index) { const value LinearScale.prototype.parse.apply(this, [raw, index]); return isFinite(value) value 0 ? value : null; }对数轴要求值域为正数。parse先委托给LinearScale.prototype.parse完成原始数据 → 数值的通用解析线性轴父类LinearScaleBase实现了数值解析、对象字段提取等逻辑随后做两层过滤isFinite(value)剔除NaN、Infinityvalue 0排除 0 与负数因为log2对非正数无定义。返回null表示该数据点不可解析会在绘制时被跳过。determineDataLimits确定数据范围determineDataLimits() { const {min, max} this.getMinMax(true); this.min isFinite(min) ? Math.max(0, min) : null; this.max isFinite(max) ? Math.max(0, max) : null; }getMinMax(true)是Scale基类提供的工具见 src/core/core.scale.js 中getMinMax(canStack)的实现它会遍历所有关联数据集控制器汇总得到全局最小/最大值布尔参数表示是否考虑堆叠stacked场景。Math.max(0, ...)把下限钳制到非负确保后续Math.log2不会对 0 或负数求值若数据为空则置为null。buildTicks生成 2 的整数幂刻度buildTicks() { const ticks []; let power Math.floor(Math.log2(this.min || 1)); let maxPower Math.ceil(Math.log2(this.max || 2)); while (power maxPower) { ticks.push({value: Math.pow(2, power)}); power 1; } this.min ticks[0].value; this.max ticks[ticks.length - 1].value; return ticks; }这是本轴与线性轴差异最大的地方刻度值不是等距的算术数而是等幂距的 2 的整数次幂如 1、2、4、8、16、32、64、128、256、512、1024。Math.floor/Math.ceil保证首尾刻度向外取整以覆盖数据范围this.min || 1、this.max || 2处理 0 或缺失时的兜底。生成后用第一个和最后一个刻度的值反向覆盖this.min/this.max使轴的可见范围与刻度边界对齐等价于把 bounds 收紧到 ticks。每个 tick 对象携带{value}基类会根据ticks.callback自动生成标签、major 标记等。configure缓存对数域映射参数configure() { const start this.min; super.configure(); this._startValue Math.log2(start); this._valueRange Math.log2(this.max) - Math.log2(start); }configure由基类在布局阶段调用。此处先调用super.configure()完成基类的内部状态准备再缓存两个用于换算的关键参数对数域起点_startValue log2(min)和对数域跨度_valueRange log2(max) - log2(min)。缓存的目的在于像素换算会被高频调用把对数计算收敛到一次配置阶段避免在每次getPixelForValue中重复求log2(max)。getPixelForValue / getValueForPixel像素与数值双向换算getPixelForValue(value) { if (value undefined || value 0) { value this.min; } return this.getPixelForDecimal(value this.min ? 0 : (Math.log2(value) - this._startValue) / this._valueRange); } getValueForPixel(pixel) { const decimal this.getDecimalForPixel(pixel); return Math.pow(2, this._startValue decimal * this._valueRange); }这两个方法是轴的坐标契约getPixelForValue把数据值映射为画布像素用于画点、画线getValueForPixel把像素反解回数据值用于 tooltip、交互命中检测。正方向先在对数域内做线性归一化decimal (log2(value) - _startValue) / _valueRange把值域压到[0, 1]再交给基类的getPixelForDecimal映射为像素反方向先由getDecimalForPixel把像素归一化为[0, 1]小数再执行2^(start decimal * range)还原为原始数值。其中getPixelForDecimal/getDecimalForPixel是Scale基类提供的通用换算工具见 src/core/core.scale.js#L920-L936前者计算this._startPixel decimal * this._length并处理反向轴与像素对齐后者计算(pixel - this._startPixel) / this._length。对数轴的巧妙之处在于值域的对数化让所有线性换算逻辑可以被基类完整复用自定义轴只需负责数值 ↔ 对数小数这一段。id 与 defaults轴的类型身份Log2Axis.id log2; Log2Axis.defaults {};id是注册名也是配置中type: log2的匹配键必须全局唯一defaults用于提供该轴类型的默认选项会与父类默认值合并。这里置为{}即完全继承Scale的默认配置。注册与使用import Log2Axis from ./log2.js; Chart.register(Log2Axis);之后便可在图表配置中直接使用options: { scales: { y: { type: log2 // 与 Log2Axis.id 一致 } } }与内置 LogarithmicScale 的源码对照Log2Axis并非孤立设计——Chart.js 内置的以 10 为底对数轴LogarithmicScale见 src/scales/scale.logarithmic.js与它结构同构可作为理解派生轴设计的官方范本同样覆写了parse、determineDataLimits、buildTicks、configure、getPixelForValue、getValueForPixel这组方法configure中对应使用log10(start)与log10(max) - log10(start)像素换算同样借助getPixelForDecimal/getDecimalForPixellog10直接取自 src/helpers/helpers.math.tsexport const log10 Math.log10但内置版本要复杂得多parse会识别 0 值并设置_zero标记determineDataLimits通过handleTickRangeOptions处理beginAtZero、用户手动设置 min/max、单值数据等边界情形buildTicks借助generateTicks不仅生成 10 的整数幂主刻度还会在相邻幂之间插入 2、3、4……9 倍数的中间刻度significand机制默认ticks.callback为Ticks.formatters.logarithmic且启用 major 刻度。对比可见Log2Axis是刻意简化的教学版——只生成 2 的整数幂刻度、不做 0 值特殊处理从而把派生轴的核心骨架以最小代码量呈现。若你的业务数据以 2 的幂为语义单元如二进制容量、倍率指标log2轴正是内置logarithmic10 底轴之外合适的补充。底层原理Scale 生命周期与接口约定Log2Axis覆写的几个方法并非随意选取它们正是Scale基类对外要求的核心接口。根据 docs/developers/axes.md一个可与 Chart.js 正常协作的自定义轴需要实现方法职责示例中的实现determineDataLimits()依据数据确定this.min/this.max基于getMinMax(true)取正数域buildTicks()生成刻度数组tick 对象含value等字段生成 2 的整数幂刻度getLabelForValue(value)返回刻度的显示文本未覆写走默认回调getPixelForTick(index)由刻度下标求像素未覆写基类经getPixelForValue实现getPixelForValue(value)由数值求像素对数域线性映射getValueForPixel(pixel)由像素反解数值指数还原此外还有可选覆写项generateTickLabels、calculateLabelRotation、fit、draw与基类提供的实用函数isHorizontal()、getTicks()。整体流程可概括为数据解析parse→ 确定范围determineDataLimits→ 生成刻度buildTicks→ 配置换算参数configure→ 布局与像素映射getPixelForValue/getValueForPixel。官方LogarithmicScale的源码路径与上文接口一一对应可作为阅读派生轴完整生命周期的参考。实战注意事项值域必须为正log2只对正数有定义parse已把 0的值过滤为null。若数据中存在 0 或负数这些点将被跳过内置logarithmic轴对此有更完整的边界处理_zero标记与handleTickRangeOptions需要兼顾此类数据时应优先考虑内置轴。刻度数可控buildTicks生成的刻度数量由数据跨度决定每跨越一个 2 的幂多一个刻度数据范围很大时注意标签密度必要时可结合ticks.autoSkip或自定义callback调整。示例代码的构建依赖示例中的Utils.*与chart-editor代码块标记是文档站点构建机制的一部分参见 docs/samples/information.md并非 Chart.js 运行时 API迁移到自有项目时需自行实现数据生成与颜色辅助函数。模块加载方式若使用 ESM 导入如示例所示import {Scale, LinearScale} from chart.js需确保构建工具支持Chart.register(Log2Axis)在图表创建前调用即可。延伸阅读自定义轴完整指南docs/developers/axes.md轴相关的通用选项display、grid、ticks等docs/axes/index.md折线图配置docs/charts/line.md数据与标签结构labelsdocs/general/data-structures.md内置对数轴的完整实现src/scales/scale.logarithmic.js同类派生示例自定义图表类型docs/samples/advanced/derived-chart-type.md【免费下载链接】Chart.jsSimple HTML5 Charts using thetag项目地址: https://gitcode.com/gh_mirrors/ch/Chart.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表