UNPKG

5.32 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.wilkinsonExtended = exports.ALL_Q = exports.DEFAULT_Q = void 0;
4const util_1 = require("@antv/util");
5const precision_add_1 = require("../utils/precision-add");
6exports.DEFAULT_Q = [1, 5, 2, 2.5, 4, 3];
7exports.ALL_Q = [1, 5, 2, 2.5, 4, 3, 1.5, 7, 6, 8, 9];
8const eps = Number.EPSILON * 100;
9function mod(n, m) {
10 return ((n % m) + m) % m;
11}
12function simplicity(q, Q, j, lmin, lmax, lstep) {
13 const n = util_1.size(Q);
14 const i = util_1.indexOf(Q, q);
15 let v = 0;
16 const m = mod(lmin, lstep);
17 if ((m < eps || lstep - m < eps) && lmin <= 0 && lmax >= 0) {
18 v = 1;
19 }
20 return 1 - i / (n - 1) - j + v;
21}
22function simplicityMax(q, Q, j) {
23 const n = util_1.size(Q);
24 const i = util_1.indexOf(Q, q);
25 const v = 1;
26 return 1 - i / (n - 1) - j + v;
27}
28function density(k, m, dMin, dMax, lMin, lMax) {
29 const r = (k - 1) / (lMax - lMin);
30 const rt = (m - 1) / (Math.max(lMax, dMax) - Math.min(dMin, lMin));
31 return 2 - Math.max(r / rt, rt / r);
32}
33function densityMax(k, m) {
34 if (k >= m) {
35 return 2 - (k - 1) / (m - 1);
36 }
37 return 1;
38}
39function coverage(dMin, dMax, lMin, lMax) {
40 const range = dMax - dMin;
41 return 1 - (0.5 * ((dMax - lMax) ** 2 + (dMin - lMin) ** 2)) / (0.1 * range) ** 2;
42}
43function coverageMax(dMin, dMax, span) {
44 const range = dMax - dMin;
45 if (span > range) {
46 const half = (span - range) / 2;
47 return 1 - half ** 2 / (0.1 * range) ** 2;
48 }
49 return 1;
50}
51function legibility() {
52 return 1;
53}
54/**
55 * An Extension of Wilkinson's Algorithm for Position Tick Labels on Axes
56 * https://www.yuque.com/preview/yuque/0/2019/pdf/185317/1546999150858-45c3b9c2-4e86-4223-bf1a-8a732e8195ed.pdf
57 * @param dMin 最小值
58 * @param dMax 最大值
59 * @param m tick个数
60 * @param onlyLoose 是否允许扩展min、max,不绝对强制,例如[3, 97]
61 * @param Q nice numbers集合
62 * @param w 四个优化组件的权重
63 */
64const wilkinsonExtended = (dMin, dMax, m = 5, onlyLoose = true, Q = exports.DEFAULT_Q, w = [0.25, 0.2, 0.5, 0.05]) => {
65 // nan 也会导致异常
66 if (Number.isNaN(dMin) || Number.isNaN(dMax) || typeof dMin !== 'number' || typeof dMax !== 'number' || !m) {
67 return [];
68 }
69 // js 极大值极小值问题,差值小于 1e-15 会导致计算出错
70 if (dMax - dMin < 1e-15 || m === 1) {
71 return [dMin];
72 }
73 const best = {
74 score: -2,
75 lmin: 0,
76 lmax: 0,
77 lstep: 0,
78 };
79 let j = 1;
80 while (j < Infinity) {
81 // for (const q of Q)
82 for (let i = 0; i < Q.length; i += 1) {
83 const q = Q[i];
84 const sm = simplicityMax(q, Q, j);
85 if (w[0] * sm + w[1] + w[2] + w[3] < best.score) {
86 j = Infinity;
87 break;
88 }
89 let k = 2;
90 while (k < Infinity) {
91 const dm = densityMax(k, m);
92 if (w[0] * sm + w[1] + w[2] * dm + w[3] < best.score) {
93 break;
94 }
95 const delta = (dMax - dMin) / (k + 1) / j / q;
96 let z = Math.ceil(Math.log10(delta));
97 while (z < Infinity) {
98 const step = j * q * 10 ** z;
99 const cm = coverageMax(dMin, dMax, step * (k - 1));
100 if (w[0] * sm + w[1] * cm + w[2] * dm + w[3] < best.score) {
101 break;
102 }
103 const minStart = Math.floor(dMax / step) * j - (k - 1) * j;
104 const maxStart = Math.ceil(dMin / step) * j;
105 if (minStart > maxStart) {
106 z += 1;
107 // eslint-disable-next-line no-continue
108 continue;
109 }
110 for (let start = minStart; start <= maxStart; start += 1) {
111 const lMin = start * (step / j);
112 const lMax = lMin + step * (k - 1);
113 const lStep = step;
114 const s = simplicity(q, Q, j, lMin, lMax, lStep);
115 const c = coverage(dMin, dMax, lMin, lMax);
116 const g = density(k, m, dMin, dMax, lMin, lMax);
117 const l = legibility();
118 const score = w[0] * s + w[1] * c + w[2] * g + w[3] * l;
119 if (score > best.score && (!onlyLoose || (lMin <= dMin && lMax >= dMax))) {
120 best.lmin = lMin;
121 best.lmax = lMax;
122 best.lstep = lStep;
123 best.score = score;
124 }
125 }
126 z += 1;
127 }
128 k += 1;
129 }
130 }
131 j += 1;
132 }
133 let i = 0;
134 const size = (best.lmax - best.lmin) / best.lstep;
135 // 步长为浮点数时处理精度
136 const range = new Array(Math.floor(size));
137 for (let tick = best.lmin; tick <= best.lmax; tick = precision_add_1.precisionAdd(tick, best.lstep)) {
138 range[i] = tick;
139 i += 1;
140 }
141 return range;
142};
143exports.wilkinsonExtended = wilkinsonExtended;
144//# sourceMappingURL=wilkinson-extended.js.map
\No newline at end of file