reamkit 1.31.0 → 1.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/esm/core/bidi/index.d.ts +10 -0
- package/dist/esm/core/bidi/index.js +18 -2
- package/dist/esm/core/converter/facade.js +10 -5
- package/dist/esm/core/converter/project.js +1 -0
- package/dist/esm/core/converter/ream.js +17 -6
- package/dist/esm/core/converter/sheet-images.d.ts +17 -0
- package/dist/esm/core/converter/sheet-images.js +33 -0
- package/dist/esm/core/crypto/offcrypto.js +56 -12
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +112 -0
- package/dist/esm/core/drawingml/chart-geometry.d.ts +36 -13
- package/dist/esm/core/drawingml/chart-geometry.js +372 -146
- package/dist/esm/core/drawingml/chart-parser.d.ts +19 -0
- package/dist/esm/core/drawingml/chart-parser.js +126 -18
- package/dist/esm/core/drawingml/colors.d.ts +6 -0
- package/dist/esm/core/drawingml/colors.js +23 -3
- package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
- package/dist/esm/core/drawingml/shape-render.js +4 -2
- package/dist/esm/core/font/measure.d.ts +9 -0
- package/dist/esm/core/font/measure.js +9 -1
- package/dist/esm/core/fonts/families.js +4 -2
- package/dist/esm/core/fonts/scripts.js +4 -2
- package/dist/esm/core/ir/flow.d.ts +6 -0
- package/dist/esm/core/ir/sheet.d.ts +43 -0
- package/dist/esm/core/number-format.d.ts +40 -15
- package/dist/esm/core/number-format.js +295 -83
- package/dist/esm/core/opc/tag-scan.d.ts +15 -0
- package/dist/esm/core/opc/tag-scan.js +41 -0
- package/dist/esm/core/opc/xml-entities.js +39 -2
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +96 -0
- package/dist/esm/core/style-cascade/resolver.js +4 -2
- package/dist/esm/excel/column-bands.d.ts +39 -6
- package/dist/esm/excel/column-bands.js +41 -17
- package/dist/esm/excel/comments-parser.js +19 -0
- package/dist/esm/excel/conditional-format.d.ts +7 -3
- package/dist/esm/excel/conditional-format.js +77 -39
- package/dist/esm/excel/formula/context.d.ts +18 -0
- package/dist/esm/excel/formula/eval.js +51 -0
- package/dist/esm/excel/formula/functions.js +37 -14
- package/dist/esm/excel/formula/index.d.ts +2 -2
- package/dist/esm/excel/formula/lexer.d.ts +1 -1
- package/dist/esm/excel/formula/lexer.js +41 -0
- package/dist/esm/excel/formula/parser.d.ts +25 -0
- package/dist/esm/excel/formula/parser.js +95 -0
- package/dist/esm/excel/print-model.d.ts +38 -1
- package/dist/esm/excel/print-model.js +536 -83
- package/dist/esm/excel/shared-strings-parser.d.ts +9 -0
- package/dist/esm/excel/shared-strings-parser.js +11 -1
- package/dist/esm/excel/sheet-drawing.d.ts +38 -2
- package/dist/esm/excel/sheet-drawing.js +47 -13
- package/dist/esm/excel/sheet-shape-parser.d.ts +6 -2
- package/dist/esm/excel/sheet-shape-parser.js +18 -6
- package/dist/esm/excel/sheet-to-flow.d.ts +17 -0
- package/dist/esm/excel/sheet-to-flow.js +434 -47
- package/dist/esm/excel/styles-parser.d.ts +9 -0
- package/dist/esm/excel/styles-parser.js +80 -26
- package/dist/esm/excel/table-parser.js +5 -0
- package/dist/esm/excel/table-style-presets.d.ts +26 -0
- package/dist/esm/excel/table-style-presets.js +1555 -0
- package/dist/esm/excel/table-style.d.ts +13 -0
- package/dist/esm/excel/table-style.js +75 -0
- package/dist/esm/excel/tint.d.ts +10 -0
- package/dist/esm/excel/tint.js +75 -0
- package/dist/esm/excel/vml-drawing.d.ts +41 -3
- package/dist/esm/excel/vml-drawing.js +144 -11
- package/dist/esm/excel/worksheet-parser.js +89 -15
- package/dist/esm/excel/xls/biff-reader.js +10 -2
- package/dist/esm/excel/xlsx-reader.js +234 -19
- package/dist/esm/excel/xlsx-writer.js +8 -7
- package/dist/esm/html/html-writer.js +202 -20
- package/dist/esm/layout/fit-pages.d.ts +11 -0
- package/dist/esm/layout/fit-pages.js +86 -0
- package/dist/esm/layout/line-paint.d.ts +58 -0
- package/dist/esm/layout/line-paint.js +107 -0
- package/dist/esm/layout/page-doc.d.ts +51 -0
- package/dist/esm/layout/page-doc.js +141 -1
- package/dist/esm/layout/styled-layout.d.ts +7 -0
- package/dist/esm/layout/styled-layout.js +275 -52
- package/dist/esm/layout/turned-section.js +14 -2
- package/dist/esm/markdown/markdown-writer.js +75 -13
- package/dist/esm/pdf/styled-page-emitter.js +49 -71
- package/dist/esm/pdf/styled-page-renderer.js +1 -1
- package/dist/esm/pdf-reader/annot-draw.js +2 -2
- package/dist/esm/pdf-reader/annots.js +3 -1
- package/dist/esm/pdf-reader/font.js +4 -4
- package/dist/esm/pdf-reader/layout.js +1 -1
- package/dist/esm/svg/svg-text.d.ts +58 -0
- package/dist/esm/svg/svg-text.js +322 -0
- package/dist/esm/svg/svg-writer.js +135 -71
- package/dist/esm/word/document-parser.js +1 -1
- package/dist/esm/word/docx-reader.js +13 -8
- package/dist/esm/word/docx-to-pdf.js +3 -3
- package/dist/esm/word/docx-writer.js +11 -2
- package/dist/esm/word/font-table.js +89 -10
- package/dist/esm/word/numbering-parser.js +1 -1
- package/package.json +7 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyNumberFormat } from "../number-format.js";
|
|
1
|
+
import { applyNumberFormat, generalRenderings } from "../number-format.js";
|
|
2
2
|
var AXIS_COLOR = "595959";
|
|
3
3
|
var GRID_COLOR = "D9D9D9";
|
|
4
4
|
var LABEL_COLOR = "595959";
|
|
@@ -23,85 +23,238 @@ var SERIES_COLORS = [
|
|
|
23
23
|
* @returns An RRGGBB hex string.
|
|
24
24
|
*/
|
|
25
25
|
var seriesColor = (s, i, cycle) => s.colorHex ?? (cycle && cycle.length > 0 ? cycle[i % cycle.length] : SERIES_COLORS[i % SERIES_COLORS.length]);
|
|
26
|
-
function niceNum(range, round) {
|
|
27
|
-
const exp = Math.floor(Math.log10(range));
|
|
28
|
-
const f = range / 10 ** exp;
|
|
29
|
-
let nf;
|
|
30
|
-
if (round) nf = f < 1.5 ? 1 : f < 3 ? 2 : f < 7 ? 5 : 10;
|
|
31
|
-
else nf = f <= 1 ? 1 : f <= 2 ? 2 : f <= 5 ? 5 : 10;
|
|
32
|
-
return nf * 10 ** exp;
|
|
33
|
-
}
|
|
34
26
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
27
|
+
* The scale Excel gives a value axis it is left to choose, measured against
|
|
28
|
+
* Excel's own PDF of its column charts (2026-10-01 and -02):
|
|
29
|
+
*
|
|
30
|
+
* - the ends: values all of one sign put the axis's near end at zero —
|
|
31
|
+
* unless their spread is under a sixth of the largest of them, when it
|
|
32
|
+
* stops half a spread short of the nearest (93…97 runs from 91) — and the
|
|
33
|
+
* far end takes 5% of its reach from that near end before it is rounded
|
|
34
|
+
* out to the step. 70…96 reaches 100.8 and runs 0…120 by 20, where 5% of
|
|
35
|
+
* the data's own spread stopped at 97.3 and ran it 0…100 by 10. Both ends
|
|
36
|
+
* of a range across zero take 5% of the spread;
|
|
37
|
+
* - the step: the smallest 1, 2 or 5 × 10ⁿ that leaves at most ten intervals
|
|
38
|
+
* (`maxIntervals`), whatever the chart's height — 3 750 runs 0…4 000 by 500
|
|
39
|
+
* on a 150pt chart as on a 400pt one. Only an axis too short to hold the
|
|
40
|
+
* labels asks for fewer, and the caller says so.
|
|
41
|
+
*
|
|
42
|
+
* Every case of the probes lands where Excel puts it: −1…1.2 on −1.5…1.5 by
|
|
43
|
+
* 0.5, 0.3…4.7 on 0…5 by 0.5, 120…950 on 0…1 000 by 100, 0.012…0.047 on
|
|
44
|
+
* 0…0.05 by 0.005, 1E-150…3E-150 on 0…3.5E-150. Heckbert's nice numbers with
|
|
45
|
+
* a budget of ticks by height stepped the budget chart by 1 000 and started
|
|
46
|
+
* 93…97 at zero.
|
|
39
47
|
*
|
|
40
|
-
* @param dataMin
|
|
41
|
-
* @param dataMax
|
|
42
|
-
* @param
|
|
48
|
+
* @param dataMin The smallest value to cover.
|
|
49
|
+
* @param dataMax The largest value to cover.
|
|
50
|
+
* @param maxIntervals At most this many steps between the ends (default 10).
|
|
43
51
|
* @returns The rounded min/max and tick step.
|
|
44
52
|
*/
|
|
45
|
-
function niceScale(dataMin, dataMax,
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
if (lo === hi) hi = lo + 1;
|
|
49
|
-
const step = niceNum((hi - lo) / Math.max(1, maxTicks - 1), true);
|
|
50
|
-
const headroom = hi > 0 ? hi * 1.05 : hi;
|
|
53
|
+
function niceScale(dataMin, dataMax, maxIntervals = 10) {
|
|
54
|
+
const end = paddedEnds(dataMin, dataMax);
|
|
55
|
+
const step = stepFor(end.lo, end.hi, maxIntervals);
|
|
51
56
|
return {
|
|
52
|
-
min:
|
|
53
|
-
max:
|
|
57
|
+
min: below(end.lo, step),
|
|
58
|
+
max: above(end.hi, step),
|
|
54
59
|
step
|
|
55
60
|
};
|
|
56
61
|
}
|
|
57
62
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
63
|
+
* The ends {@link niceScale} rounds out to its step: the data's, padded as
|
|
64
|
+
* Excel pads them. Never past the largest double, which the padding of values
|
|
65
|
+
* out at ±1.7E+308 would run them to.
|
|
66
|
+
*/
|
|
67
|
+
function paddedEnds(dataMin, dataMax) {
|
|
68
|
+
let lo = Math.min(dataMin, dataMax);
|
|
69
|
+
let hi = Math.max(dataMin, dataMax);
|
|
70
|
+
if (lo === hi) if (hi > 0) lo = 0;
|
|
71
|
+
else if (lo < 0) hi = 0;
|
|
72
|
+
else hi = 1;
|
|
73
|
+
const spread = hi - lo;
|
|
74
|
+
if (lo >= 0) {
|
|
75
|
+
const from = spread < hi / 6 ? lo - spread / 2 : 0;
|
|
76
|
+
return {
|
|
77
|
+
lo: from,
|
|
78
|
+
hi: finite(hi + (hi - from) * .05)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (hi <= 0) {
|
|
82
|
+
const to = spread < -lo / 6 ? hi + spread / 2 : 0;
|
|
83
|
+
return {
|
|
84
|
+
lo: finite(lo - (to - lo) * .05),
|
|
85
|
+
hi: to
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
lo: finite(lo - spread * .05),
|
|
90
|
+
hi: finite(hi + spread * .05)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** `v`, or the largest double of its sign where it ran past it. */
|
|
94
|
+
var finite = (v) => Math.min(Number.MAX_VALUE, Math.max(-Number.MAX_VALUE, v));
|
|
95
|
+
/** `v` rounded down to a multiple of `step`, short of the largest double. */
|
|
96
|
+
var below = (v, step) => finite(Math.floor(v / step + 1e-9) * step);
|
|
97
|
+
/** `v` rounded up to a multiple of `step`, short of the largest double. */
|
|
98
|
+
var above = (v, step) => finite(Math.ceil(v / step - 1e-9) * step);
|
|
99
|
+
/**
|
|
100
|
+
* The smallest 1, 2 or 5 × 10ⁿ that cuts `lo`…`hi` into at most
|
|
101
|
+
* `maxIntervals` steps. The share of the span each interval takes is reckoned
|
|
102
|
+
* so that a span past the largest double has one: −1.7E+308…1.7E+308 spans
|
|
103
|
+
* Infinity, and the search for its step never ended.
|
|
104
|
+
*/
|
|
105
|
+
function stepFor(lo, hi, maxIntervals) {
|
|
106
|
+
const most = Math.max(1, maxIntervals);
|
|
107
|
+
const span = hi - lo;
|
|
108
|
+
const share = Math.abs(Number.isFinite(span) ? span / most : hi / most - lo / most);
|
|
109
|
+
if (!(share > 0 && share < Infinity)) return 1;
|
|
110
|
+
let base = Math.max(Number.MIN_VALUE, 10 ** Math.floor(Math.log10(share)));
|
|
111
|
+
for (;;) {
|
|
112
|
+
for (const m of [
|
|
113
|
+
1,
|
|
114
|
+
2,
|
|
115
|
+
5
|
|
116
|
+
]) if (share <= m * base * 1.000000001) return Math.min(m * base, Number.MAX_VALUE);
|
|
117
|
+
base *= 10;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** How many steps lie between a scale's ends — in halves where the span overflows. */
|
|
121
|
+
function intervalsOf(s) {
|
|
122
|
+
const span = s.max - s.min;
|
|
123
|
+
return Number.isFinite(span) ? span / s.step : (s.max / 2 - s.min / 2) / (s.step / 2);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* How far along `s` the value `v` stands: 0 at its min, 1 at its max —
|
|
127
|
+
* reckoned in halves where a difference overflows, as it does between ends
|
|
128
|
+
* out at ±1.7E+308.
|
|
129
|
+
*/
|
|
130
|
+
function fractionOf(v, s) {
|
|
131
|
+
const span = s.max - s.min;
|
|
132
|
+
const from = v - s.min;
|
|
133
|
+
return Number.isFinite(span) && Number.isFinite(from) ? from / span : (v / 2 - s.min / 2) / (s.max / 2 - s.min / 2);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The most intervals Excel cuts a value axis into. A finer step than that is
|
|
137
|
+
* widened to a 500th of the axis: its own PDF ran 0…26.25 by 0.0525 for steps
|
|
138
|
+
* of 0.05, 0.01 and 0.001 alike, and kept 0.0525 itself (2026-10-02).
|
|
139
|
+
*/
|
|
140
|
+
var MOST_INTERVALS = 500;
|
|
141
|
+
/** The most characters Excel gives a value-axis label under General, its sign among them. */
|
|
142
|
+
var AXIS_LABEL_CHARS = 9;
|
|
143
|
+
/**
|
|
144
|
+
* A value-axis tick as Excel labels it under General. An axis is not a cell:
|
|
145
|
+
* its General gives every label nine characters, the sign among them, on an
|
|
146
|
+
* axis 150pt or 500pt long, upright or lying alike — Excel's own PDF of 44
|
|
147
|
+
* axes (2026-10-01 and -02). Within them it is the cells' rule
|
|
148
|
+
* ({@link generalRenderings}): the number as it is where it fits (0.25 — we
|
|
149
|
+
* wrote 0.3 on an axis stepped by 0.25 — 500000000, −50000000, 0.0000003),
|
|
150
|
+
* else the finest of it rounded or in scientific notation (0.1234568,
|
|
151
|
+
* −0.123457, 123456771, 1E+09, 1.235E+09, −1.23E+09, 1.235E-05, 5E-151).
|
|
152
|
+
* Places counted from the step and written by `toFixed` threw past a hundred
|
|
153
|
+
* of them, which a step of 1E-150 asked for.
|
|
60
154
|
*
|
|
61
|
-
* @param v
|
|
62
|
-
* @param step The tick spacing (from {@link niceScale}).
|
|
155
|
+
* @param v The tick value.
|
|
63
156
|
* @returns The label text.
|
|
64
157
|
*/
|
|
65
|
-
function formatTick(v
|
|
66
|
-
|
|
67
|
-
const decimals = Math.max(0, -Math.floor(Math.log10(step)));
|
|
68
|
-
return v.toFixed(decimals).replace(/\.?0+$/, (m) => m.includes(".") ? "" : m);
|
|
158
|
+
function formatTick(v) {
|
|
159
|
+
return generalRenderings(v, AXIS_LABEL_CHARS, true).next().value ?? String(v);
|
|
69
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* The values a scale labels: its min and every step up to its max. Counted,
|
|
163
|
+
* not summed: the sum drifted, and its tolerance was absolute, so a scale of
|
|
164
|
+
* 1E-150 ran on until it passed 1E-9 and asked for more ticks than an array
|
|
165
|
+
* holds. Never more than Excel's most intervals and one past them — rounding
|
|
166
|
+
* the ends out to a widened step can add that one.
|
|
167
|
+
*/
|
|
70
168
|
var ticks = (s) => {
|
|
169
|
+
const count = Math.min(Math.floor(intervalsOf(s) + 1e-9), MOST_INTERVALS + 1);
|
|
71
170
|
const out = [];
|
|
72
|
-
for (let
|
|
171
|
+
for (let i = 0; i <= count; i++) {
|
|
172
|
+
const sum = s.min + i * s.step;
|
|
173
|
+
const v = Number.isFinite(sum) ? sum : 2 * (s.min / 2 + i * (s.step / 2));
|
|
174
|
+
out.push(Math.abs(v) < s.step * 1e-9 ? 0 : v);
|
|
175
|
+
}
|
|
73
176
|
return out;
|
|
74
177
|
};
|
|
75
178
|
/**
|
|
76
179
|
* The value axis's {@link Scale}: the ends the author fixed where they fixed
|
|
77
|
-
* them (§21.2.2.157 `c:scaling/c:min|c:max`), the data's
|
|
78
|
-
* did not. A fixed end is exact —
|
|
79
|
-
*
|
|
180
|
+
* them (§21.2.2.157 `c:scaling/c:min|c:max`), the data's, padded as
|
|
181
|
+
* {@link niceScale} pads them, where they did not. A fixed end is exact —
|
|
182
|
+
* nice-rounding it would move a number the author chose.
|
|
80
183
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
184
|
+
* The step is the author's (§21.2.2.98 `c:majorUnit`), else the 1-2-5 step
|
|
185
|
+
* of the span between the ends — with both fixed, of the span they fix:
|
|
186
|
+
* −1E+300…1E+300 runs by 2E+299 in Excel, where padding the ends first
|
|
187
|
+
* stepped it by 5E+299. The automatic ends round out to an author's step
|
|
188
|
+
* from where the padding left them, not from where a step of our own would
|
|
189
|
+
* have: 10…25 by 0.1 runs 0…26.3, as in Excel, not 0…30. And no step cuts
|
|
190
|
+
* the axis finer than {@link MOST_INTERVALS}: Excel widens one that would to
|
|
191
|
+
* a 500th of the span and rounds the automatic ends out to the widened step
|
|
192
|
+
* (−10…25 by 0.01 runs −11.781…26.796 by 0.077). A step of 0.00001 asked for
|
|
193
|
+
* three million labels and ran out of stack.
|
|
194
|
+
*
|
|
195
|
+
* @param chart The chart (for its fixed ends and step).
|
|
196
|
+
* @param dataMin The smallest value to cover.
|
|
197
|
+
* @param dataMax The largest value to cover.
|
|
198
|
+
* @param extentPt The axis's length — how many ticks fit is a question
|
|
199
|
+
* about the plot, not about the numbers.
|
|
200
|
+
* @param horizontal Whether the axis lies along the foot.
|
|
86
201
|
* @returns The axis min/max and tick step.
|
|
87
202
|
*/
|
|
88
|
-
function axisScale(chart, dataMin, dataMax,
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
203
|
+
function axisScale(chart, dataMin, dataMax, extentPt, horizontal = false) {
|
|
204
|
+
const fixedMin = chart.valAxisMin;
|
|
205
|
+
const fixedMax = chart.valAxisMax;
|
|
206
|
+
const padded = paddedEnds(fixedMin ?? dataMin, fixedMax ?? dataMax);
|
|
207
|
+
const lo = fixedMin ?? padded.lo;
|
|
208
|
+
const hi = fixedMax ?? padded.hi;
|
|
209
|
+
const rounded = (step) => ({
|
|
210
|
+
min: fixedMin ?? below(lo, step),
|
|
211
|
+
max: fixedMax ?? above(hi, step),
|
|
212
|
+
step
|
|
213
|
+
});
|
|
214
|
+
const scale = rounded(chart.valAxisMajorUnit ?? stepFor(lo, hi, intervalsThatFit(extentPt, horizontal)));
|
|
215
|
+
if (intervalsOf(scale) <= MOST_INTERVALS) return scale;
|
|
216
|
+
const span = hi - lo;
|
|
217
|
+
return rounded(Number.isFinite(span) ? span / MOST_INTERVALS : hi / MOST_INTERVALS - lo / MOST_INTERVALS);
|
|
95
218
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
219
|
+
/**
|
|
220
|
+
* The chart title's lines. A title longer than the chart is wide wraps at its
|
|
221
|
+
* spaces, as Excel's and Calc's do, rather than running out past both sides
|
|
222
|
+
* of the frame: dataValidationTableRange.xlsx's "Ranking of Washington
|
|
223
|
+
* Counties on Days per Patient (ALOS) in 2015" crossed the cells beside its
|
|
224
|
+
* chart. A line holds at most four fifths of the chart's width.
|
|
225
|
+
*
|
|
226
|
+
* @param chart The chart.
|
|
227
|
+
* @param wPt The chart's width.
|
|
228
|
+
* @param measure The text measurer.
|
|
229
|
+
* @returns The lines, top first; none for a chart without a title.
|
|
230
|
+
*/
|
|
231
|
+
function titleLines(chart, wPt, measure) {
|
|
232
|
+
if (!chart.title) return [];
|
|
233
|
+
const room = wPt * .8;
|
|
234
|
+
const lines = [];
|
|
235
|
+
let line = "";
|
|
236
|
+
for (const word of chart.title.split(/\s+/).filter((w) => w.length > 0)) {
|
|
237
|
+
const longer = line ? `${line} ${word}` : word;
|
|
238
|
+
if (line && measure(longer, 13) > room) {
|
|
239
|
+
lines.push(line);
|
|
240
|
+
line = word;
|
|
241
|
+
} else line = longer;
|
|
242
|
+
}
|
|
243
|
+
if (line) lines.push(line);
|
|
244
|
+
return lines;
|
|
245
|
+
}
|
|
246
|
+
/** The band a title of `lines` takes at the top of the chart. */
|
|
247
|
+
var titleHeight = (lines) => lines.length === 0 ? 0 : 13 * (1.6 + 1.2 * (lines.length - 1));
|
|
248
|
+
function pushChartTitle(labels, lines, wPt, hPt) {
|
|
249
|
+
lines.forEach((text, i) => {
|
|
250
|
+
labels.push({
|
|
251
|
+
text,
|
|
252
|
+
x: wPt / 2,
|
|
253
|
+
y: hPt - 4 - 13 * (1 + 1.2 * i),
|
|
254
|
+
sizePt: 13,
|
|
255
|
+
colorHex: TITLE_COLOR,
|
|
256
|
+
align: "center"
|
|
257
|
+
});
|
|
105
258
|
});
|
|
106
259
|
}
|
|
107
260
|
function buildLegendBlock(chart, wPt, hPt, measure) {
|
|
@@ -113,7 +266,7 @@ function buildLegendBlock(chart, wPt, hPt, measure) {
|
|
|
113
266
|
const marker = chart.type === "line" || chart.type === "scatter" ? "line" : "box";
|
|
114
267
|
return layoutLegend(legendEntries, chart.hasLegend, chart.legendPos ?? "b", wPt, hPt, measure, marker);
|
|
115
268
|
}
|
|
116
|
-
function pushGridTicks(gridlines, labels, tickVals, fmt, axis, at, x0, y0, plotW, plotH, grid) {
|
|
269
|
+
function pushGridTicks(gridlines, labels, tickVals, fmt, axis, at, x0, y0, plotW, plotH, grid, atEnd = false) {
|
|
117
270
|
for (const v of tickVals) if (axis === "x") {
|
|
118
271
|
const gx = at(v);
|
|
119
272
|
gridlines.push({
|
|
@@ -124,7 +277,7 @@ function pushGridTicks(gridlines, labels, tickVals, fmt, axis, at, x0, y0, plotW
|
|
|
124
277
|
labels.push({
|
|
125
278
|
text: fmt(v),
|
|
126
279
|
x: gx,
|
|
127
|
-
y: y0 - 9,
|
|
280
|
+
y: atEnd ? y0 + plotH + 9 * .4 : y0 - 9,
|
|
128
281
|
sizePt: 9,
|
|
129
282
|
colorHex: LABEL_COLOR,
|
|
130
283
|
align: "center"
|
|
@@ -138,11 +291,11 @@ function pushGridTicks(gridlines, labels, tickVals, fmt, axis, at, x0, y0, plotW
|
|
|
138
291
|
});
|
|
139
292
|
labels.push({
|
|
140
293
|
text: fmt(v),
|
|
141
|
-
x: x0 - 3,
|
|
294
|
+
x: atEnd ? x0 + plotW + 3 : x0 - 3,
|
|
142
295
|
y: gy - 9 / 3,
|
|
143
296
|
sizePt: 9,
|
|
144
297
|
colorHex: LABEL_COLOR,
|
|
145
|
-
align: "right"
|
|
298
|
+
align: atEnd ? "left" : "right"
|
|
146
299
|
});
|
|
147
300
|
}
|
|
148
301
|
}
|
|
@@ -159,16 +312,24 @@ function axisStroke(style) {
|
|
|
159
312
|
widthPt: style?.widthPt ?? 1
|
|
160
313
|
};
|
|
161
314
|
}
|
|
162
|
-
|
|
315
|
+
/**
|
|
316
|
+
* The two axis lines: the upright one (in the left axis's style) and the one
|
|
317
|
+
* lying along (in the bottom axis's). The category axis lies where it crosses
|
|
318
|
+
* the value axis — `cross.y` up a column chart, `cross.x` along a bar chart —
|
|
319
|
+
* and the other stands at the plot's edge.
|
|
320
|
+
*/
|
|
321
|
+
function pushAxisLines(polylines, x0, y0, plotW, plotH, chart, cross = {}) {
|
|
163
322
|
const val = axisStroke(chart.valAxisLine);
|
|
323
|
+
const x = x0 + (cross.x ?? 0);
|
|
324
|
+
const y = y0 + (cross.y ?? 0);
|
|
164
325
|
if (val) polylines.push({
|
|
165
|
-
points: [[
|
|
326
|
+
points: [[x, y0], [x, y0 + plotH]],
|
|
166
327
|
strokeHex: val.hex,
|
|
167
328
|
widthPt: val.widthPt
|
|
168
329
|
});
|
|
169
330
|
const cat = axisStroke(chart.catAxisLine);
|
|
170
331
|
if (cat) polylines.push({
|
|
171
|
-
points: [[x0,
|
|
332
|
+
points: [[x0, y], [x0 + plotW, y]],
|
|
172
333
|
strokeHex: cat.hex,
|
|
173
334
|
widthPt: cat.widthPt
|
|
174
335
|
});
|
|
@@ -178,30 +339,47 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
178
339
|
const polylines = [];
|
|
179
340
|
const gridlines = [];
|
|
180
341
|
const labels = [];
|
|
181
|
-
const nCats =
|
|
342
|
+
const nCats = catCount(chart);
|
|
182
343
|
const onSecondary = chart.series.filter((s) => s.secondaryAxis);
|
|
183
344
|
const allVals = (onSecondary.length > 0 ? chart.series.filter((s) => !s.secondaryAxis) : chart.series).flatMap((s) => s.values.slice(0, nCats));
|
|
184
|
-
const [dataMin, dataMax] = opts.dataRange ??
|
|
185
|
-
const scale =
|
|
186
|
-
|
|
345
|
+
const [dataMin, dataMax] = opts.dataRange ?? extentOf(allVals) ?? [0, 1];
|
|
346
|
+
const scale = opts.exactRange ? {
|
|
347
|
+
min: dataMin,
|
|
348
|
+
max: dataMax,
|
|
349
|
+
step: niceScale(dataMin, dataMax, intervalsThatFit(horizontal ? wPt : hPt, horizontal)).step
|
|
350
|
+
} : axisScale(chart, dataMin, dataMax, horizontal ? wPt : hPt, horizontal);
|
|
351
|
+
const fmtVal = opts.formatValue ?? formatTick;
|
|
187
352
|
const tickVals = ticks(scale);
|
|
188
|
-
const
|
|
189
|
-
const scale2 =
|
|
353
|
+
const range2 = extentOf(onSecondary.flatMap((s) => s.values.slice(0, nCats)));
|
|
354
|
+
const scale2 = range2 ? niceScale(range2[0], range2[1], intervalsThatFit(hPt, false)) : void 0;
|
|
190
355
|
const tickVals2 = scale2 ? ticks(scale2) : [];
|
|
191
|
-
|
|
192
|
-
|
|
356
|
+
const crosses = chart.catAxisCrosses;
|
|
357
|
+
const crossValue = Math.min(Math.max(crosses === "min" ? scale.min : crosses === "max" ? scale.max : crosses ?? 0, scale.min), scale.max);
|
|
358
|
+
const labelsAt = chart.catTickLabelPos === "none" ? "none" : chart.catTickLabelPos === "low" ? "start" : chart.catTickLabelPos === "high" ? "end" : crossValue <= scale.min ? "start" : crossValue >= scale.max ? "end" : "cross";
|
|
359
|
+
const catText = (c) => chart.categories[c] ?? String(c + 1);
|
|
360
|
+
let widestCat = 0;
|
|
361
|
+
for (let c = 0; c < nCats; c++) widestCat = Math.max(widestCat, measure(catText(c), 9));
|
|
362
|
+
const catBand = 9 * 1.6;
|
|
363
|
+
const groupLevels = horizontal ? [] : chart.categoryGroups ?? [];
|
|
364
|
+
const valAtEnd = chart.catAxisReversed === true ? chart.valAxisCrosses !== "max" : chart.valAxisCrosses === "max";
|
|
365
|
+
const title = titleLines(chart, wPt, measure);
|
|
366
|
+
const top = 4 + titleHeight(title) + (!horizontal && labelsAt === "end" ? catBand : 0) + (horizontal && valAtEnd ? catBand : 0);
|
|
193
367
|
const legend = buildLegendBlock(chart, wPt, hPt, measure);
|
|
194
|
-
const tick2W = scale2 && !horizontal ? Math.max(0, ...tickVals2.map((v) => measure(formatTick(v
|
|
195
|
-
const
|
|
368
|
+
const tick2W = scale2 && !horizontal ? Math.max(0, ...tickVals2.map((v) => measure(formatTick(v), 9))) + 4 + (chart.secondaryValAxisTitle ? 9 * 1.5 : 0) : 0;
|
|
369
|
+
const tickHalf = (v) => horizontal && v !== void 0 ? measure(fmtVal(v), 9) / 2 : 0;
|
|
370
|
+
const lastTickHalf = tickHalf(tickVals[tickVals.length - 1]);
|
|
371
|
+
const catLabelW = Math.min(wPt * .4, widestCat + 6);
|
|
196
372
|
const tickLabelW = Math.max(0, ...tickVals.map((v) => measure(fmtVal(v), 9))) + 4;
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const
|
|
373
|
+
const plotRight = wPt - 4 - legend.rightWidth - tick2W - (horizontal && labelsAt === "end" ? catLabelW : lastTickHalf) - (!horizontal && valAtEnd ? tickLabelW : 0);
|
|
374
|
+
const axisTitleBand = 9 * 1.5;
|
|
375
|
+
const leftTitle = horizontal ? chart.catAxisTitle : chart.valAxisTitle;
|
|
376
|
+
const footTitle = horizontal ? chart.valAxisTitle : chart.catAxisTitle;
|
|
377
|
+
const x0 = 4 + (leftTitle ? axisTitleBand : 0) + (horizontal ? labelsAt === "start" ? catLabelW : tickHalf(tickVals[0]) : valAtEnd ? 0 : tickLabelW);
|
|
378
|
+
const y0 = 4 + legend.bottomHeight + (footTitle ? axisTitleBand : 0) + ((horizontal ? !valAtEnd : labelsAt === "start") ? catBand : 0) + (!horizontal && labelsAt === "start" ? groupLevels.length * catBand : 0);
|
|
201
379
|
const plotW = Math.max(1, plotRight - x0);
|
|
202
380
|
const plotH = Math.max(1, hPt - top - y0);
|
|
203
|
-
const valueOffset = (v) => (v
|
|
204
|
-
const zeroOffset = valueOffset(
|
|
381
|
+
const valueOffset = (v) => fractionOf(v, scale) * (horizontal ? plotW : plotH);
|
|
382
|
+
const zeroOffset = valueOffset(crossValue);
|
|
205
383
|
const plotBackground = chart.plotFillHex ?? chart.plotLine?.colorHex ? {
|
|
206
384
|
x: x0,
|
|
207
385
|
y: y0,
|
|
@@ -214,9 +392,9 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
214
392
|
...chart.plotLine.dash ? { strokeDash: chart.plotLine.dash } : {}
|
|
215
393
|
} : {}
|
|
216
394
|
} : void 0;
|
|
217
|
-
pushChartTitle(labels,
|
|
218
|
-
if (
|
|
219
|
-
text:
|
|
395
|
+
pushChartTitle(labels, title, wPt, hPt);
|
|
396
|
+
if (leftTitle) labels.push({
|
|
397
|
+
text: leftTitle,
|
|
220
398
|
x: 12.1,
|
|
221
399
|
y: y0 + plotH / 2,
|
|
222
400
|
sizePt: 9,
|
|
@@ -224,20 +402,20 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
224
402
|
align: "center",
|
|
225
403
|
rotationDeg: 90
|
|
226
404
|
});
|
|
227
|
-
if (
|
|
228
|
-
text:
|
|
405
|
+
if (footTitle) labels.push({
|
|
406
|
+
text: footTitle,
|
|
229
407
|
x: x0 + plotW / 2,
|
|
230
408
|
y: legend.bottomHeight + 2,
|
|
231
409
|
sizePt: 9,
|
|
232
410
|
colorHex: LABEL_COLOR,
|
|
233
411
|
align: "center"
|
|
234
412
|
});
|
|
235
|
-
if (horizontal) pushGridTicks(gridlines, labels, tickVals, fmtVal, "x", (v) => x0 + valueOffset(v), x0, y0, plotW, plotH, chart.gridLine);
|
|
236
|
-
else pushGridTicks(gridlines, labels, tickVals, fmtVal, "y", (v) => y0 + valueOffset(v), x0, y0, plotW, plotH, chart.gridLine);
|
|
237
|
-
const valueOffset2 = scale2 && !horizontal ? (v) => (v
|
|
413
|
+
if (horizontal) pushGridTicks(gridlines, labels, tickVals, fmtVal, "x", (v) => x0 + valueOffset(v), x0, y0, plotW, plotH, chart.gridLine, valAtEnd);
|
|
414
|
+
else pushGridTicks(gridlines, labels, tickVals, fmtVal, "y", (v) => y0 + valueOffset(v), x0, y0, plotW, plotH, chart.gridLine, valAtEnd);
|
|
415
|
+
const valueOffset2 = scale2 && !horizontal ? (v) => fractionOf(v, scale2) * plotH : void 0;
|
|
238
416
|
if (scale2 && valueOffset2) {
|
|
239
417
|
for (const v of tickVals2) labels.push({
|
|
240
|
-
text: formatTick(v
|
|
418
|
+
text: formatTick(v),
|
|
241
419
|
x: x0 + plotW + 3,
|
|
242
420
|
y: y0 + valueOffset2(v) - 9 / 3,
|
|
243
421
|
sizePt: 9,
|
|
@@ -261,30 +439,60 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
261
439
|
});
|
|
262
440
|
}
|
|
263
441
|
const slot = (horizontal ? plotH : plotW) / nCats;
|
|
264
|
-
const need = horizontal ? 9 * 1.4 :
|
|
442
|
+
const need = horizontal ? 9 * 1.4 : widestCat + 4;
|
|
265
443
|
const step = Math.max(1, Math.ceil(need / Math.max(slot, .01)));
|
|
266
|
-
|
|
267
|
-
|
|
444
|
+
const across = labelsAt === "start" ? 0 : labelsAt === "end" ? horizontal ? plotW : plotH : zeroOffset;
|
|
445
|
+
const labelled = [];
|
|
446
|
+
if (labelsAt !== "none") if (chart.catAxisReversed === true) for (let c = nCats - 1; c >= 0; c -= step) labelled.push(c);
|
|
447
|
+
else for (let c = 0; c < nCats; c += step) labelled.push(c);
|
|
448
|
+
for (const c of labelled) {
|
|
449
|
+
const cat = catText(c);
|
|
268
450
|
if (!cat) continue;
|
|
269
451
|
const center = (horizontal ? y0 : x0) + c * slot + slot / 2;
|
|
270
452
|
if (horizontal) labels.push({
|
|
271
453
|
text: cat,
|
|
272
|
-
x: x0 - 3,
|
|
454
|
+
x: labelsAt === "end" ? x0 + across + 3 : x0 + across - 3,
|
|
273
455
|
y: center - 9 / 3,
|
|
274
456
|
sizePt: 9,
|
|
275
457
|
colorHex: LABEL_COLOR,
|
|
276
|
-
align: "right"
|
|
458
|
+
align: labelsAt === "end" ? "left" : "right"
|
|
277
459
|
});
|
|
278
460
|
else labels.push({
|
|
279
461
|
text: cat,
|
|
280
462
|
x: center,
|
|
281
|
-
y: y0 - 9,
|
|
463
|
+
y: labelsAt === "end" ? y0 + across + 9 * .4 : y0 + across - 9,
|
|
282
464
|
sizePt: 9,
|
|
283
465
|
colorHex: LABEL_COLOR,
|
|
284
466
|
align: "center"
|
|
285
467
|
});
|
|
286
468
|
}
|
|
287
|
-
|
|
469
|
+
if (labelsAt !== "none" && labelsAt !== "end") groupLevels.forEach((groups, level) => {
|
|
470
|
+
const rowY = y0 + across - 9 - (level + 1) * catBand;
|
|
471
|
+
groups.forEach((group, i) => {
|
|
472
|
+
const end = Math.min(nCats, groups[i + 1]?.start ?? nCats);
|
|
473
|
+
if (end <= group.start) return;
|
|
474
|
+
labels.push({
|
|
475
|
+
text: group.label,
|
|
476
|
+
x: x0 + (group.start + end) / 2 * slot,
|
|
477
|
+
y: rowY,
|
|
478
|
+
sizePt: 9,
|
|
479
|
+
colorHex: LABEL_COLOR,
|
|
480
|
+
align: "center"
|
|
481
|
+
});
|
|
482
|
+
if (i > 0) polylines.push({
|
|
483
|
+
points: [[x0 + group.start * slot, y0 + across], [x0 + group.start * slot, rowY - 9 * .4]],
|
|
484
|
+
strokeHex: GRID_COLOR,
|
|
485
|
+
widthPt: .75
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
pushAxisLines(polylines, x0, y0, plotW, plotH, chart, horizontal ? {
|
|
490
|
+
x: zeroOffset,
|
|
491
|
+
...valAtEnd ? { y: plotH } : {}
|
|
492
|
+
} : {
|
|
493
|
+
y: zeroOffset,
|
|
494
|
+
...valAtEnd ? { x: plotW } : {}
|
|
495
|
+
});
|
|
288
496
|
legend.emit(rects, labels);
|
|
289
497
|
return {
|
|
290
498
|
x0,
|
|
@@ -306,8 +514,29 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
306
514
|
}
|
|
307
515
|
var pctLabel = (v) => `${Math.round(v * 100)}%`;
|
|
308
516
|
var fmtDataLabel = (chart, v) => chartValueFormatter(chart)?.(v) ?? (Number.isInteger(v) ? String(v) : String(Math.round(v * 100) / 100));
|
|
517
|
+
/**
|
|
518
|
+
* How many category slots a chart has: its labels, or its longest series —
|
|
519
|
+
* counted in a loop, as spreading a file's series into one call overflows
|
|
520
|
+
* the stack.
|
|
521
|
+
*/
|
|
309
522
|
function catCount(chart) {
|
|
310
|
-
|
|
523
|
+
let n = Math.max(chart.categories.length, 1);
|
|
524
|
+
for (const s of chart.series) n = Math.max(n, s.values.length);
|
|
525
|
+
return n;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* The least and the greatest of `values`, or undefined where none is a
|
|
529
|
+
* number. Read in a loop: `Math.min(...values)` throws on a series of 200 000
|
|
530
|
+
* points, as a call takes no more arguments than the stack holds.
|
|
531
|
+
*/
|
|
532
|
+
function extentOf(values) {
|
|
533
|
+
let lo = Infinity;
|
|
534
|
+
let hi = -Infinity;
|
|
535
|
+
for (const v of values) {
|
|
536
|
+
if (v < lo) lo = v;
|
|
537
|
+
if (v > hi) hi = v;
|
|
538
|
+
}
|
|
539
|
+
return lo <= hi ? [lo, hi] : void 0;
|
|
311
540
|
}
|
|
312
541
|
function stackedTotals(chart, nCats) {
|
|
313
542
|
let max = 0;
|
|
@@ -332,6 +561,7 @@ function groupingFrameOpts(chart, nCats) {
|
|
|
332
561
|
const g = chart.grouping ?? "clustered";
|
|
333
562
|
if (g === "percentStacked") return {
|
|
334
563
|
dataRange: [0, 1],
|
|
564
|
+
exactRange: true,
|
|
335
565
|
formatValue: pctLabel
|
|
336
566
|
};
|
|
337
567
|
const format = chartValueFormatter(chart);
|
|
@@ -655,22 +885,24 @@ function buildScatterScene(chart, wPt, hPt, measure) {
|
|
|
655
885
|
wedges: [],
|
|
656
886
|
labels
|
|
657
887
|
};
|
|
658
|
-
const
|
|
659
|
-
const
|
|
888
|
+
const [xLo, xHi] = extentOf(xs) ?? [0, 1];
|
|
889
|
+
const [yLo, yHi] = extentOf(ys) ?? [0, 1];
|
|
890
|
+
const xScale = niceScale(xLo, xHi, intervalsThatFit(wPt, true));
|
|
891
|
+
const yScale = niceScale(yLo, yHi, intervalsThatFit(hPt, false));
|
|
660
892
|
const xTicks = ticks(xScale);
|
|
661
893
|
const yTicks = ticks(yScale);
|
|
662
|
-
|
|
663
|
-
|
|
894
|
+
const title = titleLines(chart, wPt, measure);
|
|
895
|
+
const top = 4 + titleHeight(title);
|
|
664
896
|
const legend = buildLegendBlock(chart, wPt, hPt, measure);
|
|
665
|
-
const x0 = 4 + (Math.max(0, ...yTicks.map((v) => measure(formatTick(v
|
|
897
|
+
const x0 = 4 + (Math.max(0, ...yTicks.map((v) => measure(formatTick(v), 9))) + 4);
|
|
666
898
|
const y0 = 4 + legend.bottomHeight + 9 * 1.6;
|
|
667
899
|
const plotW = Math.max(1, wPt - 4 - legend.rightWidth - x0);
|
|
668
900
|
const plotH = Math.max(1, hPt - top - y0);
|
|
669
|
-
const xAt = (v) => x0 + (v
|
|
670
|
-
const yAt = (v) => y0 + (v
|
|
671
|
-
pushChartTitle(labels,
|
|
672
|
-
pushGridTicks(gridlines, labels, yTicks,
|
|
673
|
-
pushGridTicks(gridlines, labels, xTicks,
|
|
901
|
+
const xAt = (v) => x0 + fractionOf(v, xScale) * plotW;
|
|
902
|
+
const yAt = (v) => y0 + fractionOf(v, yScale) * plotH;
|
|
903
|
+
pushChartTitle(labels, title, wPt, hPt);
|
|
904
|
+
pushGridTicks(gridlines, labels, yTicks, formatTick, "y", yAt, x0, y0, plotW, plotH, chart.gridLine);
|
|
905
|
+
pushGridTicks(gridlines, labels, xTicks, formatTick, "x", xAt, x0, y0, plotW, plotH, chart.gridLine);
|
|
674
906
|
pushAxisLines(polylines, x0, y0, plotW, plotH, chart);
|
|
675
907
|
const style = chart.scatterStyle ?? "marker";
|
|
676
908
|
const joins = style === "line" || style === "lineMarker" || style.startsWith("smooth");
|
|
@@ -702,27 +934,13 @@ function buildScatterScene(chart, wPt, hPt, measure) {
|
|
|
702
934
|
};
|
|
703
935
|
}
|
|
704
936
|
/**
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
937
|
+
* How many intervals an axis `extentPt` long has room to label: ten, Excel's
|
|
938
|
+
* own most, unless its labels would run into each other first — one label
|
|
939
|
+
* line apiece stacked up a vertical axis, a few digits' width apiece along a
|
|
940
|
+
* horizontal one. Excel crams nine labels up a 150pt column chart rather than
|
|
941
|
+
* step it more coarsely, so the room is all that limits it.
|
|
708
942
|
*/
|
|
709
|
-
|
|
710
|
-
const lo = Math.min(...vals);
|
|
711
|
-
const hi = Math.max(...vals);
|
|
712
|
-
return [lo > 0 && lo < 5 / 6 * hi ? 0 : lo, hi];
|
|
713
|
-
}
|
|
714
|
-
/**
|
|
715
|
-
* How many ticks an axis `extentPt` long carries (mirrors {@link axisScale}).
|
|
716
|
-
*
|
|
717
|
-
* A tall axis takes more of them — a fixed six drew 0/100/200/300 down a plot
|
|
718
|
-
* where both references fit 0/50/…/300 — but the appetite is milder than it
|
|
719
|
-
* looks: asked for one every 32pt, a 427pt chart wanted ten, and Excel labels
|
|
720
|
-
* that chart's 0…5 data 0/1/…/6 where we drew half-steps (chart-texture-bg
|
|
721
|
-
* .pptx). One per ~48pt and never more than eight satisfies both, now that
|
|
722
|
-
* {@link niceScale} measures its step against the range the data spans rather
|
|
723
|
-
* than against Heckbert's rounded-up one.
|
|
724
|
-
*/
|
|
725
|
-
var tickBudget = (extentPt) => Math.min(8, Math.max(4, Math.round(extentPt / 48)));
|
|
943
|
+
var intervalsThatFit = (extentPt, horizontal) => Math.min(10, Math.max(2, Math.floor(extentPt * .75 / (9 * (horizontal ? 3.5 : 1.4)))));
|
|
726
944
|
/** Side (points) of the square stamped for a series that names no symbol. */
|
|
727
945
|
var DEFAULT_MARKER_PT = 4;
|
|
728
946
|
/**
|
|
@@ -772,10 +990,7 @@ function pushMarker(rects, wedges, marker, x, y, color) {
|
|
|
772
990
|
* @returns The positioned scene primitives.
|
|
773
991
|
*/
|
|
774
992
|
function buildLineScene(chart, wPt, hPt, measure) {
|
|
775
|
-
const
|
|
776
|
-
const lo = allVals.length > 0 ? Math.min(...allVals) : 0;
|
|
777
|
-
const hi = allVals.length > 0 ? Math.max(...allVals) : 1;
|
|
778
|
-
const range = lo > 0 && lo > 5 / 6 * hi ? [lo, hi] : [Math.min(0, lo), Math.max(0, hi)];
|
|
993
|
+
const range = extentOf(chart.series.flatMap((s) => s.values)) ?? [0, 1];
|
|
779
994
|
const lineFormat = chartValueFormatter(chart);
|
|
780
995
|
const f = buildFrame(chart, wPt, hPt, measure, false, {
|
|
781
996
|
dataRange: range,
|
|
@@ -846,8 +1061,8 @@ function buildPieScene(chart, wPt, hPt, measure) {
|
|
|
846
1061
|
wedges,
|
|
847
1062
|
labels
|
|
848
1063
|
};
|
|
849
|
-
|
|
850
|
-
|
|
1064
|
+
const title = titleLines(chart, wPt, measure);
|
|
1065
|
+
const top = 4 + titleHeight(title);
|
|
851
1066
|
const legend = layoutLegend(values.map((_, i) => ({
|
|
852
1067
|
name: chart.categories[i] ?? String(i + 1),
|
|
853
1068
|
colorHex: sliceColor(series, i, chart.seriesColorCycle)
|
|
@@ -876,7 +1091,7 @@ function buildPieScene(chart, wPt, hPt, measure) {
|
|
|
876
1091
|
const mid = ang + sweep / 2;
|
|
877
1092
|
const pct = Math.round(v / total * 100);
|
|
878
1093
|
const own = chart.showValues ? fmtDataLabel(chart, v) : `${pct}%`;
|
|
879
|
-
const custom = series.pointLabels
|
|
1094
|
+
const custom = atPoint(series.pointLabels, i)?.text;
|
|
880
1095
|
if (custom !== void 0 || chart.showValues || pct >= 5) labels.push({
|
|
881
1096
|
text: custom ?? own,
|
|
882
1097
|
x: cx + Math.cos(mid) * labelR,
|
|
@@ -895,14 +1110,7 @@ function buildPieScene(chart, wPt, hPt, measure) {
|
|
|
895
1110
|
sweepRad: -2 * Math.PI,
|
|
896
1111
|
fillHex: "FFFFFF"
|
|
897
1112
|
});
|
|
898
|
-
|
|
899
|
-
text: chart.title,
|
|
900
|
-
x: wPt / 2,
|
|
901
|
-
y: hPt - 4 - 13,
|
|
902
|
-
sizePt: 13,
|
|
903
|
-
colorHex: TITLE_COLOR,
|
|
904
|
-
align: "center"
|
|
905
|
-
});
|
|
1113
|
+
pushChartTitle(labels, title, wPt, hPt);
|
|
906
1114
|
legend.emit(rects, labels);
|
|
907
1115
|
return {
|
|
908
1116
|
rects,
|
|
@@ -912,7 +1120,25 @@ function buildPieScene(chart, wPt, hPt, measure) {
|
|
|
912
1120
|
};
|
|
913
1121
|
}
|
|
914
1122
|
function pointColor(series, idx) {
|
|
915
|
-
return series.pointColors
|
|
1123
|
+
return atPoint(series.pointColors, idx)?.colorHex;
|
|
1124
|
+
}
|
|
1125
|
+
/** Each override list's entries by point index, built on first use. */
|
|
1126
|
+
var overridesByPoint = /* @__PURE__ */ new WeakMap();
|
|
1127
|
+
/**
|
|
1128
|
+
* The first of `overrides` for the point at `idx` — looked up, not searched
|
|
1129
|
+
* for: a series that overrides many of its points, searched through for every
|
|
1130
|
+
* one of them, took the square of their number.
|
|
1131
|
+
*/
|
|
1132
|
+
function atPoint(overrides, idx) {
|
|
1133
|
+
if (!overrides) return void 0;
|
|
1134
|
+
let byPoint = overridesByPoint.get(overrides);
|
|
1135
|
+
if (!byPoint) {
|
|
1136
|
+
const index = /* @__PURE__ */ new Map();
|
|
1137
|
+
for (const o of overrides) if (!index.has(o.idx)) index.set(o.idx, o);
|
|
1138
|
+
overridesByPoint.set(overrides, index);
|
|
1139
|
+
byPoint = index;
|
|
1140
|
+
}
|
|
1141
|
+
return byPoint.get(idx);
|
|
916
1142
|
}
|
|
917
1143
|
function layoutLegend(entries, hasLegend, pos, wPt, hPt, measure, marker = "box") {
|
|
918
1144
|
if (!hasLegend || entries.length === 0) return {
|
|
@@ -939,7 +1165,7 @@ function layoutLegend(entries, hasLegend, pos, wPt, hPt, measure, marker = "box"
|
|
|
939
1165
|
};
|
|
940
1166
|
const entryW = (e) => keyW + 3 + measure(e.name, 9) + gap * 2;
|
|
941
1167
|
if (pos === "r" || pos === "l") {
|
|
942
|
-
const colW = Math.max(
|
|
1168
|
+
const colW = entries.reduce((w, e) => Math.max(w, entryW(e)), 0);
|
|
943
1169
|
return {
|
|
944
1170
|
rightWidth: pos === "r" ? colW : 0,
|
|
945
1171
|
bottomHeight: 0,
|
|
@@ -1010,11 +1236,11 @@ function buildChartScene(chart, wPt, hPt, measure) {
|
|
|
1010
1236
|
* in reverse.
|
|
1011
1237
|
*/
|
|
1012
1238
|
function withReversedCategories(chart) {
|
|
1013
|
-
const n =
|
|
1239
|
+
const n = catCount(chart);
|
|
1014
1240
|
const flip = (i) => n - 1 - i;
|
|
1015
1241
|
return {
|
|
1016
1242
|
...chart,
|
|
1017
|
-
categories:
|
|
1243
|
+
categories: Array.from({ length: n }, (_, i) => chart.categories.length > 0 ? chart.categories[flip(i)] ?? "" : String(flip(i) + 1)),
|
|
1018
1244
|
series: chart.series.map((s) => ({
|
|
1019
1245
|
...s,
|
|
1020
1246
|
values: Array.from({ length: n }, (_, i) => s.values[flip(i)] ?? 0),
|