UNPKG

@antv/scale

Version:

Toolkit for mapping abstract data into visual representation.

238 lines 8.97 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Band = void 0; const utils_1 = require("../utils"); const ordinal_1 = require("./ordinal"); function normalize(array) { const min = Math.min(...array); return array.map((d) => d / min); } function splice(array, n) { const sn = array.length; const diff = n - sn; return diff > 0 ? [...array, ...new Array(diff).fill(1)] : diff < 0 ? array.slice(0, n) : array; } function pretty(n) { return Math.round(n * 1e12) / 1e12; } /** * 基于 band 基础配置获取存在 flex band 的状态 */ function computeFlexBandState(options) { // 如果 flex 比 domain 少,那么就补全 // 如果 flex 比 domain 多,就截取 const { domain, range, paddingOuter, paddingInner, flex: F, round, align } = options; const n = domain.length; const flex = splice(F, n); // 根据下面的等式可以计算出所有 step 的总和 // stepSum = step1 + step2 ... + stepN; // stepAverage = stepSum / n; // PO = stepAverage * paddingOuter; // PI = stepAverage * paddingInner; // width = PO * 2 + stepSum - PI; const [start, end] = range; const width = end - start; const ratio = (2 / n) * paddingOuter + 1 - (1 / n) * paddingInner; const stepSum = width / ratio; // stepSum = (b1 + PI) + (b2 + PI) ... + (bN + PI) // = bandSum + PI * n; const PI = (stepSum * paddingInner) / n; const bandWidthSum = stepSum - n * PI; // 计算出最小的 bandWidth const normalizedFlex = normalize(flex); const flexSum = normalizedFlex.reduce((sum, value) => sum + value); const minBandWidth = bandWidthSum / flexSum; // 计算每个 bandWidth 和 step,并且用定义域内的值索引 const valueBandWidth = new utils_1.InternMap(domain.map((d, i) => { const bandWidth = normalizedFlex[i] * minBandWidth; return [d, round ? Math.floor(bandWidth) : bandWidth]; })); const valueStep = new utils_1.InternMap(domain.map((d, i) => { const bandWidth = normalizedFlex[i] * minBandWidth; const step = bandWidth + PI; return [d, round ? Math.floor(step) : step]; })); // 计算起始位置的偏移量 // 因为 step 可能被 round 了,重新计算所有的 step 的总和 const finalStepSum = Array.from(valueStep.values()).reduce((sum, value) => sum + value); const outerPaddingSum = width - (finalStepSum - (finalStepSum / n) * paddingInner); const offset = outerPaddingSum * align; // 计算 adjustedRange,也就是 domain 中每个值映射之后的值 const bandStart = start + offset; let prev = round ? Math.round(bandStart) : bandStart; const adjustedRange = new Array(n); for (let i = 0; i < n; i += 1) { // 简单处理精度问题 adjustedRange[i] = pretty(prev); const value = domain[i]; prev += valueStep.get(value); } return { valueBandWidth, valueStep, adjustedRange, }; } /** * 基于 band 基础配置获取 band 的状态 */ function computeBandState(options) { var _a; const { domain } = options; const n = domain.length; if (n === 0) { return { valueBandWidth: undefined, valueStep: undefined, adjustedRange: [], }; } const hasFlex = !!((_a = options.flex) === null || _a === void 0 ? void 0 : _a.length); if (hasFlex) { return computeFlexBandState(options); } const { range, paddingOuter, paddingInner, round, align } = options; let step; let bandWidth; let rangeStart = range[0]; const rangeEnd = range[1]; // range 的计算方式如下: // = stop - start // = (n * step(n 个 step) ) // + (2 * step * paddingOuter(两边的 padding)) // - (1 * step * paddingInner(多出的一个 inner)) const deltaRange = rangeEnd - rangeStart; const outerTotal = paddingOuter * 2; const innerTotal = n - paddingInner; step = deltaRange / Math.max(1, outerTotal + innerTotal); // 优化成整数 if (round) { step = Math.floor(step); } // 基于 align 实现偏移 rangeStart += (deltaRange - step * (n - paddingInner)) * align; // 一个 step 的组成如下: // step = bandWidth + step * paddingInner, // 则 bandWidth = step - step * (paddingInner) bandWidth = step * (1 - paddingInner); if (round) { rangeStart = Math.round(rangeStart); bandWidth = Math.round(bandWidth); } // 转化后的 range const adjustedRange = new Array(n).fill(0).map((_, i) => rangeStart + i * step); return { valueStep: step, valueBandWidth: bandWidth, adjustedRange, }; } /** * Band 比例尺 * * 一种特殊的 ordinal scale,区别在于值域的范围是连续的。 * 使用的场景例如柱状图,可以用来定位各个柱子水平方向距离原点开始绘制的距离、各柱子之间的间距 * * 由于部分选项较为抽象,见下图描述: * * BN = bandWidthN * SN = stepN * domain = [A, B] * * 约束关系如下 * width = PO + B1 + PI + B2 + PI ... + BN + PO; * PO = (S1 + S2 + ... SN) / N * paddingOuter * PI = (S1 + S2 + ... SN) / N * paddingInner * BN / BN-1 = flex[n] / flex[n-1] * * |<------------------------------------------- range ------------------------------------------->| * | | | | | | | * |<-----PO---->|<------B1--------->|<-----PI---->|<-------B2-------->|<----PI----->|<-----PO---->| * | | ***************** | | ***************** | | | * | | ******* A ******* | | ******* B ******* | | | * | | ***************** | | ***************** | | | * | |<--------------S1--------------->| <--------------S2-------------->| | * |-----------------------------------------------------------------------------------------------| * */ class Band extends ordinal_1.Ordinal { // 覆盖默认配置 getDefaultOptions() { return { domain: [], range: [0, 1], align: 0.5, round: false, paddingInner: 0, paddingOuter: 0, padding: 0, unknown: ordinal_1.defaultUnknown, flex: [], }; } // 显示指定 options 的类型为 OrdinalOptions,从而推断出 O 的类型 constructor(options) { super(options); } clone() { return new Band(this.options); } getStep(x) { if (this.valueStep === undefined) return 1; // 没有 flex 的情况时, valueStep 是 number 类型 if (typeof this.valueStep === 'number') { return this.valueStep; } // 对于 flex 都为 1 的情况,x 不是必须要传入的 // 这种情况所有的条的 step 都相等,所以返回第一个就好 if (x === undefined) return Array.from(this.valueStep.values())[0]; return this.valueStep.get(x); } getBandWidth(x) { if (this.valueBandWidth === undefined) return 1; // 没有 flex, valueBandWidth 是 number 类型 if (typeof this.valueBandWidth === 'number') { return this.valueBandWidth; } // 对于 flex 都为 1 的情况,x 不是必须要传入的 // 这种情况所有的条的 bandWidth 都相等,所以返回第一个 if (x === undefined) return Array.from(this.valueBandWidth.values())[0]; return this.valueBandWidth.get(x); } getRange() { return this.adjustedRange; } getPaddingInner() { const { padding, paddingInner } = this.options; return padding > 0 ? padding : paddingInner; } getPaddingOuter() { const { padding, paddingOuter } = this.options; return padding > 0 ? padding : paddingOuter; } rescale() { super.rescale(); // 当用户配置了opt.padding 且非 0 时,我们覆盖已经设置的 paddingInner paddingOuter // 我们约定 padding 的优先级较 paddingInner 和 paddingOuter 高 const { align, domain, range, round, flex } = this.options; const { adjustedRange, valueBandWidth, valueStep } = computeBandState({ align, range, round, flex, paddingInner: this.getPaddingInner(), paddingOuter: this.getPaddingOuter(), domain, }); // 更新必要的属性 this.valueStep = valueStep; this.valueBandWidth = valueBandWidth; this.adjustedRange = adjustedRange; } } exports.Band = Band; //# sourceMappingURL=band.js.map