UNPKG

8.97 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Band = void 0;
4const utils_1 = require("../utils");
5const ordinal_1 = require("./ordinal");
6function normalize(array) {
7 const min = Math.min(...array);
8 return array.map((d) => d / min);
9}
10function splice(array, n) {
11 const sn = array.length;
12 const diff = n - sn;
13 return diff > 0 ? [...array, ...new Array(diff).fill(1)] : diff < 0 ? array.slice(0, n) : array;
14}
15function pretty(n) {
16 return Math.round(n * 1e12) / 1e12;
17}
18/**
19 * 基于 band 基础配置获取存在 flex band 的状态
20 */
21function computeFlexBandState(options) {
22 // 如果 flex 比 domain 少,那么就补全
23 // 如果 flex 比 domain 多,就截取
24 const { domain, range, paddingOuter, paddingInner, flex: F, round, align } = options;
25 const n = domain.length;
26 const flex = splice(F, n);
27 // 根据下面的等式可以计算出所有 step 的总和
28 // stepSum = step1 + step2 ... + stepN;
29 // stepAverage = stepSum / n;
30 // PO = stepAverage * paddingOuter;
31 // PI = stepAverage * paddingInner;
32 // width = PO * 2 + stepSum - PI;
33 const [start, end] = range;
34 const width = end - start;
35 const ratio = (2 / n) * paddingOuter + 1 - (1 / n) * paddingInner;
36 const stepSum = width / ratio;
37 // stepSum = (b1 + PI) + (b2 + PI) ... + (bN + PI)
38 // = bandSum + PI * n;
39 const PI = (stepSum * paddingInner) / n;
40 const bandWidthSum = stepSum - n * PI;
41 // 计算出最小的 bandWidth
42 const normalizedFlex = normalize(flex);
43 const flexSum = normalizedFlex.reduce((sum, value) => sum + value);
44 const minBandWidth = bandWidthSum / flexSum;
45 // 计算每个 bandWidth 和 step,并且用定义域内的值索引
46 const valueBandWidth = new utils_1.InternMap(domain.map((d, i) => {
47 const bandWidth = normalizedFlex[i] * minBandWidth;
48 return [d, round ? Math.floor(bandWidth) : bandWidth];
49 }));
50 const valueStep = new utils_1.InternMap(domain.map((d, i) => {
51 const bandWidth = normalizedFlex[i] * minBandWidth;
52 const step = bandWidth + PI;
53 return [d, round ? Math.floor(step) : step];
54 }));
55 // 计算起始位置的偏移量
56 // 因为 step 可能被 round 了,重新计算所有的 step 的总和
57 const finalStepSum = Array.from(valueStep.values()).reduce((sum, value) => sum + value);
58 const outerPaddingSum = width - (finalStepSum - (finalStepSum / n) * paddingInner);
59 const offset = outerPaddingSum * align;
60 // 计算 adjustedRange,也就是 domain 中每个值映射之后的值
61 const bandStart = start + offset;
62 let prev = round ? Math.round(bandStart) : bandStart;
63 const adjustedRange = new Array(n);
64 for (let i = 0; i < n; i += 1) {
65 // 简单处理精度问题
66 adjustedRange[i] = pretty(prev);
67 const value = domain[i];
68 prev += valueStep.get(value);
69 }
70 return {
71 valueBandWidth,
72 valueStep,
73 adjustedRange,
74 };
75}
76/**
77 * 基于 band 基础配置获取 band 的状态
78 */
79function computeBandState(options) {
80 var _a;
81 const { domain } = options;
82 const n = domain.length;
83 if (n === 0) {
84 return {
85 valueBandWidth: undefined,
86 valueStep: undefined,
87 adjustedRange: [],
88 };
89 }
90 const hasFlex = !!((_a = options.flex) === null || _a === void 0 ? void 0 : _a.length);
91 if (hasFlex) {
92 return computeFlexBandState(options);
93 }
94 const { range, paddingOuter, paddingInner, round, align } = options;
95 let step;
96 let bandWidth;
97 let rangeStart = range[0];
98 const rangeEnd = range[1];
99 // range 的计算方式如下:
100 // = stop - start
101 // = (n * step(n 个 step) )
102 // + (2 * step * paddingOuter(两边的 padding))
103 // - (1 * step * paddingInner(多出的一个 inner))
104 const deltaRange = rangeEnd - rangeStart;
105 const outerTotal = paddingOuter * 2;
106 const innerTotal = n - paddingInner;
107 step = deltaRange / Math.max(1, outerTotal + innerTotal);
108 // 优化成整数
109 if (round) {
110 step = Math.floor(step);
111 }
112 // 基于 align 实现偏移
113 rangeStart += (deltaRange - step * (n - paddingInner)) * align;
114 // 一个 step 的组成如下:
115 // step = bandWidth + step * paddingInner,
116 // 则 bandWidth = step - step * (paddingInner)
117 bandWidth = step * (1 - paddingInner);
118 if (round) {
119 rangeStart = Math.round(rangeStart);
120 bandWidth = Math.round(bandWidth);
121 }
122 // 转化后的 range
123 const adjustedRange = new Array(n).fill(0).map((_, i) => rangeStart + i * step);
124 return {
125 valueStep: step,
126 valueBandWidth: bandWidth,
127 adjustedRange,
128 };
129}
130/**
131 * Band 比例尺
132 *
133 * 一种特殊的 ordinal scale,区别在于值域的范围是连续的。
134 * 使用的场景例如柱状图,可以用来定位各个柱子水平方向距离原点开始绘制的距离、各柱子之间的间距
135 *
136 * 由于部分选项较为抽象,见下图描述:
137 *
138 * BN = bandWidthN
139 * SN = stepN
140 * domain = [A, B]
141 *
142 * 约束关系如下
143 * width = PO + B1 + PI + B2 + PI ... + BN + PO;
144 * PO = (S1 + S2 + ... SN) / N * paddingOuter
145 * PI = (S1 + S2 + ... SN) / N * paddingInner
146 * BN / BN-1 = flex[n] / flex[n-1]
147 *
148 * |<------------------------------------------- range ------------------------------------------->|
149 * | | | | | | |
150 * |<-----PO---->|<------B1--------->|<-----PI---->|<-------B2-------->|<----PI----->|<-----PO---->|
151 * | | ***************** | | ***************** | | |
152 * | | ******* A ******* | | ******* B ******* | | |
153 * | | ***************** | | ***************** | | |
154 * | |<--------------S1--------------->| <--------------S2-------------->| |
155 * |-----------------------------------------------------------------------------------------------|
156 *
157 */
158class Band extends ordinal_1.Ordinal {
159 // 覆盖默认配置
160 getDefaultOptions() {
161 return {
162 domain: [],
163 range: [0, 1],
164 align: 0.5,
165 round: false,
166 paddingInner: 0,
167 paddingOuter: 0,
168 padding: 0,
169 unknown: ordinal_1.defaultUnknown,
170 flex: [],
171 };
172 }
173 // 显示指定 options 的类型为 OrdinalOptions,从而推断出 O 的类型
174 constructor(options) {
175 super(options);
176 }
177 clone() {
178 return new Band(this.options);
179 }
180 getStep(x) {
181 if (this.valueStep === undefined)
182 return 1;
183 // 没有 flex 的情况时, valueStep 是 number 类型
184 if (typeof this.valueStep === 'number') {
185 return this.valueStep;
186 }
187 // 对于 flex 都为 1 的情况,x 不是必须要传入的
188 // 这种情况所有的条的 step 都相等,所以返回第一个就好
189 if (x === undefined)
190 return Array.from(this.valueStep.values())[0];
191 return this.valueStep.get(x);
192 }
193 getBandWidth(x) {
194 if (this.valueBandWidth === undefined)
195 return 1;
196 // 没有 flex, valueBandWidth 是 number 类型
197 if (typeof this.valueBandWidth === 'number') {
198 return this.valueBandWidth;
199 }
200 // 对于 flex 都为 1 的情况,x 不是必须要传入的
201 // 这种情况所有的条的 bandWidth 都相等,所以返回第一个
202 if (x === undefined)
203 return Array.from(this.valueBandWidth.values())[0];
204 return this.valueBandWidth.get(x);
205 }
206 getRange() {
207 return this.adjustedRange;
208 }
209 getPaddingInner() {
210 const { padding, paddingInner } = this.options;
211 return padding > 0 ? padding : paddingInner;
212 }
213 getPaddingOuter() {
214 const { padding, paddingOuter } = this.options;
215 return padding > 0 ? padding : paddingOuter;
216 }
217 rescale() {
218 super.rescale();
219 // 当用户配置了opt.padding 且非 0 时,我们覆盖已经设置的 paddingInner paddingOuter
220 // 我们约定 padding 的优先级较 paddingInner 和 paddingOuter 高
221 const { align, domain, range, round, flex } = this.options;
222 const { adjustedRange, valueBandWidth, valueStep } = computeBandState({
223 align,
224 range,
225 round,
226 flex,
227 paddingInner: this.getPaddingInner(),
228 paddingOuter: this.getPaddingOuter(),
229 domain,
230 });
231 // 更新必要的属性
232 this.valueStep = valueStep;
233 this.valueBandWidth = valueBandWidth;
234 this.adjustedRange = adjustedRange;
235 }
236}
237exports.Band = Band;
238//# sourceMappingURL=band.js.map
\No newline at end of file