reamkit 1.32.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/dist/esm/core/crypto/offcrypto.js +56 -12
- package/dist/esm/core/drawingml/chart-geometry.d.ts +21 -11
- package/dist/esm/core/drawingml/chart-geometry.js +205 -69
- package/dist/esm/core/drawingml/chart-parser.d.ts +19 -0
- package/dist/esm/core/drawingml/chart-parser.js +60 -23
- package/dist/esm/core/number-format.d.ts +40 -15
- package/dist/esm/core/number-format.js +280 -73
- 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/excel/formula/functions.js +37 -14
- package/dist/esm/excel/print-model.js +14 -3
- package/dist/esm/excel/vml-drawing.js +1 -1
- package/dist/esm/excel/xlsx-reader.js +39 -5
- package/dist/esm/layout/styled-layout.js +1 -1
- package/dist/esm/markdown/markdown-writer.js +58 -7
- 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 +1 -1
- package/dist/esm/pdf-reader/layout.js +1 -1
- 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/font-table.js +89 -10
- package/dist/esm/word/numbering-parser.js +1 -1
- package/package.json +3 -3
|
@@ -159,29 +159,73 @@ function agileKeyData(attrs) {
|
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
161
|
function passwordKeyEncryptor(xml) {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
while (m) {
|
|
165
|
-
const attrs = parseAttrs(m[1] ?? "");
|
|
162
|
+
for (const raw of elementsNamed(xml, "encryptedKey")) {
|
|
163
|
+
const attrs = parseAttrs(raw);
|
|
166
164
|
if (attrs["spinCount"] !== void 0) return attrs;
|
|
167
|
-
m = re.exec(xml);
|
|
168
165
|
}
|
|
169
166
|
throw new Error("No password key encryptor in the encryption descriptor");
|
|
170
167
|
}
|
|
171
168
|
function attrsOf(xml, tag) {
|
|
172
|
-
const
|
|
173
|
-
return
|
|
169
|
+
const raw = elementsNamed(xml, tag)[0];
|
|
170
|
+
return raw === void 0 ? {} : parseAttrs(raw);
|
|
171
|
+
}
|
|
172
|
+
function elementsNamed(xml, tag) {
|
|
173
|
+
const out = [];
|
|
174
|
+
let open = xml.indexOf("<");
|
|
175
|
+
while (open >= 0) {
|
|
176
|
+
const end = nameEnd(xml, open + 1, tag);
|
|
177
|
+
if (end < 0) {
|
|
178
|
+
open = xml.indexOf("<", open + 1);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const close = xml.indexOf(">", end);
|
|
182
|
+
if (close < 0) break;
|
|
183
|
+
out.push(xml.slice(end, close));
|
|
184
|
+
open = xml.indexOf("<", close + 1);
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
function nameEnd(xml, from, tag) {
|
|
189
|
+
let end = from;
|
|
190
|
+
while (isAlnum(xml.charCodeAt(end))) end++;
|
|
191
|
+
const prefixed = end + 1 + tag.length;
|
|
192
|
+
if (xml[end] === ":" && xml.startsWith(tag, end + 1) && !isWordChar(xml.charCodeAt(prefixed))) return prefixed;
|
|
193
|
+
if (end - from >= tag.length && xml.startsWith(tag, end - tag.length)) return isWordChar(xml.charCodeAt(end)) ? -1 : end;
|
|
194
|
+
return -1;
|
|
174
195
|
}
|
|
175
196
|
function parseAttrs(raw) {
|
|
176
197
|
const out = {};
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
198
|
+
let at = 0;
|
|
199
|
+
while (at < raw.length) {
|
|
200
|
+
if (!isNameChar(raw.charCodeAt(at))) {
|
|
201
|
+
at++;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
let end = at;
|
|
205
|
+
while (isNameChar(raw.charCodeAt(end))) end++;
|
|
206
|
+
let k = skipSpace(raw, end);
|
|
207
|
+
if (raw[k] === "=") {
|
|
208
|
+
k = skipSpace(raw, k + 1);
|
|
209
|
+
const close = raw[k] === "\"" ? raw.indexOf("\"", k + 1) : -1;
|
|
210
|
+
if (close >= 0) {
|
|
211
|
+
const name = raw.slice(at, end);
|
|
212
|
+
out[name.slice(name.lastIndexOf(":") + 1)] = raw.slice(k + 1, close);
|
|
213
|
+
at = close + 1;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
at = end;
|
|
182
218
|
}
|
|
183
219
|
return out;
|
|
184
220
|
}
|
|
221
|
+
var isAlnum = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122;
|
|
222
|
+
var isWordChar = (c) => isAlnum(c) || c === 95;
|
|
223
|
+
var isNameChar = (c) => isWordChar(c) || c === 58;
|
|
224
|
+
function skipSpace(s, from) {
|
|
225
|
+
let at = from;
|
|
226
|
+
while (at < s.length && /\s/u.test(s[at])) at++;
|
|
227
|
+
return at;
|
|
228
|
+
}
|
|
185
229
|
function utf16le(s) {
|
|
186
230
|
const out = new Uint8Array(s.length * 2);
|
|
187
231
|
const v = new DataView(out.buffer);
|
|
@@ -119,22 +119,25 @@ export interface Scale {
|
|
|
119
119
|
}
|
|
120
120
|
/**
|
|
121
121
|
* The scale Excel gives a value axis it is left to choose, measured against
|
|
122
|
-
* Excel's own PDF of
|
|
122
|
+
* Excel's own PDF of its column charts (2026-10-01 and -02):
|
|
123
123
|
*
|
|
124
124
|
* - the ends: values all of one sign put the axis's near end at zero —
|
|
125
125
|
* unless their spread is under a sixth of the largest of them, when it
|
|
126
126
|
* stops half a spread short of the nearest (93…97 runs from 91) — and the
|
|
127
|
-
* far end
|
|
128
|
-
*
|
|
127
|
+
* far end takes 5% of its reach from that near end before it is rounded
|
|
128
|
+
* out to the step. 70…96 reaches 100.8 and runs 0…120 by 20, where 5% of
|
|
129
|
+
* the data's own spread stopped at 97.3 and ran it 0…100 by 10. Both ends
|
|
130
|
+
* of a range across zero take 5% of the spread;
|
|
129
131
|
* - the step: the smallest 1, 2 or 5 × 10ⁿ that leaves at most ten intervals
|
|
130
132
|
* (`maxIntervals`), whatever the chart's height — 3 750 runs 0…4 000 by 500
|
|
131
133
|
* on a 150pt chart as on a 400pt one. Only an axis too short to hold the
|
|
132
134
|
* labels asks for fewer, and the caller says so.
|
|
133
135
|
*
|
|
134
|
-
* Every case of the
|
|
136
|
+
* Every case of the probes lands where Excel puts it: −1…1.2 on −1.5…1.5 by
|
|
135
137
|
* 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
|
|
136
|
-
* 0…0.05 by 0.005. Heckbert's nice numbers with
|
|
137
|
-
* stepped the budget chart by 1 000 and started
|
|
138
|
+
* 0…0.05 by 0.005, 1E-150…3E-150 on 0…3.5E-150. Heckbert's nice numbers with
|
|
139
|
+
* a budget of ticks by height stepped the budget chart by 1 000 and started
|
|
140
|
+
* 93…97 at zero.
|
|
138
141
|
*
|
|
139
142
|
* @param dataMin The smallest value to cover.
|
|
140
143
|
* @param dataMax The largest value to cover.
|
|
@@ -143,14 +146,21 @@ export interface Scale {
|
|
|
143
146
|
*/
|
|
144
147
|
export declare function niceScale(dataMin: number, dataMax: number, maxIntervals?: number): Scale;
|
|
145
148
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
149
|
+
* A value-axis tick as Excel labels it under General. An axis is not a cell:
|
|
150
|
+
* its General gives every label nine characters, the sign among them, on an
|
|
151
|
+
* axis 150pt or 500pt long, upright or lying alike — Excel's own PDF of 44
|
|
152
|
+
* axes (2026-10-01 and -02). Within them it is the cells' rule
|
|
153
|
+
* ({@link generalRenderings}): the number as it is where it fits (0.25 — we
|
|
154
|
+
* wrote 0.3 on an axis stepped by 0.25 — 500000000, −50000000, 0.0000003),
|
|
155
|
+
* else the finest of it rounded or in scientific notation (0.1234568,
|
|
156
|
+
* −0.123457, 123456771, 1E+09, 1.235E+09, −1.23E+09, 1.235E-05, 5E-151).
|
|
157
|
+
* Places counted from the step and written by `toFixed` threw past a hundred
|
|
158
|
+
* of them, which a step of 1E-150 asked for.
|
|
148
159
|
*
|
|
149
|
-
* @param v
|
|
150
|
-
* @param step The tick spacing (from {@link niceScale}).
|
|
160
|
+
* @param v The tick value.
|
|
151
161
|
* @returns The label text.
|
|
152
162
|
*/
|
|
153
|
-
export declare function formatTick(v: number
|
|
163
|
+
export declare function formatTick(v: number): string;
|
|
154
164
|
export declare function buildBarScene(chart: Chart, wPt: number, hPt: number, measure: MeasureText): ChartScene;
|
|
155
165
|
/**
|
|
156
166
|
* The name a series shows in the legend: its own `c:tx`, or Excel's positional
|
|
@@ -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";
|
|
@@ -25,22 +25,25 @@ var SERIES_COLORS = [
|
|
|
25
25
|
var seriesColor = (s, i, cycle) => s.colorHex ?? (cycle && cycle.length > 0 ? cycle[i % cycle.length] : SERIES_COLORS[i % SERIES_COLORS.length]);
|
|
26
26
|
/**
|
|
27
27
|
* The scale Excel gives a value axis it is left to choose, measured against
|
|
28
|
-
* Excel's own PDF of
|
|
28
|
+
* Excel's own PDF of its column charts (2026-10-01 and -02):
|
|
29
29
|
*
|
|
30
30
|
* - the ends: values all of one sign put the axis's near end at zero —
|
|
31
31
|
* unless their spread is under a sixth of the largest of them, when it
|
|
32
32
|
* stops half a spread short of the nearest (93…97 runs from 91) — and the
|
|
33
|
-
* far end
|
|
34
|
-
*
|
|
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;
|
|
35
37
|
* - the step: the smallest 1, 2 or 5 × 10ⁿ that leaves at most ten intervals
|
|
36
38
|
* (`maxIntervals`), whatever the chart's height — 3 750 runs 0…4 000 by 500
|
|
37
39
|
* on a 150pt chart as on a 400pt one. Only an axis too short to hold the
|
|
38
40
|
* labels asks for fewer, and the caller says so.
|
|
39
41
|
*
|
|
40
|
-
* Every case of the
|
|
42
|
+
* Every case of the probes lands where Excel puts it: −1…1.2 on −1.5…1.5 by
|
|
41
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
|
|
42
|
-
* 0…0.05 by 0.005. Heckbert's nice numbers with
|
|
43
|
-
* stepped the budget chart by 1 000 and started
|
|
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.
|
|
44
47
|
*
|
|
45
48
|
* @param dataMin The smallest value to cover.
|
|
46
49
|
* @param dataMax The largest value to cover.
|
|
@@ -48,78 +51,170 @@ var seriesColor = (s, i, cycle) => s.colorHex ?? (cycle && cycle.length > 0 ? cy
|
|
|
48
51
|
* @returns The rounded min/max and tick step.
|
|
49
52
|
*/
|
|
50
53
|
function niceScale(dataMin, dataMax, maxIntervals = 10) {
|
|
54
|
+
const end = paddedEnds(dataMin, dataMax);
|
|
55
|
+
const step = stepFor(end.lo, end.hi, maxIntervals);
|
|
56
|
+
return {
|
|
57
|
+
min: below(end.lo, step),
|
|
58
|
+
max: above(end.hi, step),
|
|
59
|
+
step
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
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) {
|
|
51
68
|
let lo = Math.min(dataMin, dataMax);
|
|
52
69
|
let hi = Math.max(dataMin, dataMax);
|
|
53
70
|
if (lo === hi) if (hi > 0) lo = 0;
|
|
54
71
|
else if (lo < 0) hi = 0;
|
|
55
72
|
else hi = 1;
|
|
56
73
|
const spread = hi - lo;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
+
}
|
|
64
88
|
return {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
step
|
|
89
|
+
lo: finite(lo - spread * .05),
|
|
90
|
+
hi: finite(hi + spread * .05)
|
|
68
91
|
};
|
|
69
92
|
}
|
|
70
|
-
/**
|
|
71
|
-
|
|
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) {
|
|
72
106
|
const most = Math.max(1, maxIntervals);
|
|
73
|
-
|
|
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)));
|
|
74
111
|
for (;;) {
|
|
75
112
|
for (const m of [
|
|
76
113
|
1,
|
|
77
114
|
2,
|
|
78
115
|
5
|
|
79
|
-
]) if (
|
|
116
|
+
]) if (share <= m * base * 1.000000001) return Math.min(m * base, Number.MAX_VALUE);
|
|
80
117
|
base *= 10;
|
|
81
118
|
}
|
|
82
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
|
+
}
|
|
83
135
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
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.
|
|
86
154
|
*
|
|
87
|
-
* @param v
|
|
88
|
-
* @param step The tick spacing (from {@link niceScale}).
|
|
155
|
+
* @param v The tick value.
|
|
89
156
|
* @returns The label text.
|
|
90
157
|
*/
|
|
91
|
-
function formatTick(v
|
|
92
|
-
|
|
93
|
-
const decimals = Math.max(0, -Math.floor(Math.log10(step)));
|
|
94
|
-
const fixed = v.toFixed(decimals);
|
|
95
|
-
return fixed.includes(".") ? fixed.replace(/0+$/, "").replace(/\.$/, "") : fixed;
|
|
158
|
+
function formatTick(v) {
|
|
159
|
+
return generalRenderings(v, AXIS_LABEL_CHARS, true).next().value ?? String(v);
|
|
96
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
|
+
*/
|
|
97
168
|
var ticks = (s) => {
|
|
169
|
+
const count = Math.min(Math.floor(intervalsOf(s) + 1e-9), MOST_INTERVALS + 1);
|
|
98
170
|
const out = [];
|
|
99
|
-
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
|
+
}
|
|
100
176
|
return out;
|
|
101
177
|
};
|
|
102
178
|
/**
|
|
103
179
|
* The value axis's {@link Scale}: the ends the author fixed where they fixed
|
|
104
|
-
* them (§21.2.2.157 `c:scaling/c:min|c:max`), the data's
|
|
105
|
-
* did not. A fixed end is exact —
|
|
106
|
-
*
|
|
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.
|
|
107
183
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
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.
|
|
113
201
|
* @returns The axis min/max and tick step.
|
|
114
202
|
*/
|
|
115
203
|
function axisScale(chart, dataMin, dataMax, extentPt, horizontal = false) {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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),
|
|
121
212
|
step
|
|
122
|
-
};
|
|
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);
|
|
123
218
|
}
|
|
124
219
|
/**
|
|
125
220
|
* The chart title's lines. A title longer than the chart is wide wraps at its
|
|
@@ -244,32 +339,33 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
244
339
|
const polylines = [];
|
|
245
340
|
const gridlines = [];
|
|
246
341
|
const labels = [];
|
|
247
|
-
const nCats =
|
|
342
|
+
const nCats = catCount(chart);
|
|
248
343
|
const onSecondary = chart.series.filter((s) => s.secondaryAxis);
|
|
249
344
|
const allVals = (onSecondary.length > 0 ? chart.series.filter((s) => !s.secondaryAxis) : chart.series).flatMap((s) => s.values.slice(0, nCats));
|
|
250
|
-
const [dataMin, dataMax] = opts.dataRange ?? (allVals
|
|
345
|
+
const [dataMin, dataMax] = opts.dataRange ?? extentOf(allVals) ?? [0, 1];
|
|
251
346
|
const scale = opts.exactRange ? {
|
|
252
347
|
min: dataMin,
|
|
253
348
|
max: dataMax,
|
|
254
349
|
step: niceScale(dataMin, dataMax, intervalsThatFit(horizontal ? wPt : hPt, horizontal)).step
|
|
255
350
|
} : axisScale(chart, dataMin, dataMax, horizontal ? wPt : hPt, horizontal);
|
|
256
|
-
const fmtVal = opts.formatValue ??
|
|
351
|
+
const fmtVal = opts.formatValue ?? formatTick;
|
|
257
352
|
const tickVals = ticks(scale);
|
|
258
|
-
const
|
|
259
|
-
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;
|
|
260
355
|
const tickVals2 = scale2 ? ticks(scale2) : [];
|
|
261
356
|
const crosses = chart.catAxisCrosses;
|
|
262
357
|
const crossValue = Math.min(Math.max(crosses === "min" ? scale.min : crosses === "max" ? scale.max : crosses ?? 0, scale.min), scale.max);
|
|
263
358
|
const labelsAt = chart.catTickLabelPos === "none" ? "none" : chart.catTickLabelPos === "low" ? "start" : chart.catTickLabelPos === "high" ? "end" : crossValue <= scale.min ? "start" : crossValue >= scale.max ? "end" : "cross";
|
|
264
359
|
const catText = (c) => chart.categories[c] ?? String(c + 1);
|
|
265
|
-
|
|
360
|
+
let widestCat = 0;
|
|
361
|
+
for (let c = 0; c < nCats; c++) widestCat = Math.max(widestCat, measure(catText(c), 9));
|
|
266
362
|
const catBand = 9 * 1.6;
|
|
267
363
|
const groupLevels = horizontal ? [] : chart.categoryGroups ?? [];
|
|
268
364
|
const valAtEnd = chart.catAxisReversed === true ? chart.valAxisCrosses !== "max" : chart.valAxisCrosses === "max";
|
|
269
365
|
const title = titleLines(chart, wPt, measure);
|
|
270
366
|
const top = 4 + titleHeight(title) + (!horizontal && labelsAt === "end" ? catBand : 0) + (horizontal && valAtEnd ? catBand : 0);
|
|
271
367
|
const legend = buildLegendBlock(chart, wPt, hPt, measure);
|
|
272
|
-
const tick2W = scale2 && !horizontal ? Math.max(0, ...tickVals2.map((v) => measure(formatTick(v
|
|
368
|
+
const tick2W = scale2 && !horizontal ? Math.max(0, ...tickVals2.map((v) => measure(formatTick(v), 9))) + 4 + (chart.secondaryValAxisTitle ? 9 * 1.5 : 0) : 0;
|
|
273
369
|
const tickHalf = (v) => horizontal && v !== void 0 ? measure(fmtVal(v), 9) / 2 : 0;
|
|
274
370
|
const lastTickHalf = tickHalf(tickVals[tickVals.length - 1]);
|
|
275
371
|
const catLabelW = Math.min(wPt * .4, widestCat + 6);
|
|
@@ -282,7 +378,7 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
282
378
|
const y0 = 4 + legend.bottomHeight + (footTitle ? axisTitleBand : 0) + ((horizontal ? !valAtEnd : labelsAt === "start") ? catBand : 0) + (!horizontal && labelsAt === "start" ? groupLevels.length * catBand : 0);
|
|
283
379
|
const plotW = Math.max(1, plotRight - x0);
|
|
284
380
|
const plotH = Math.max(1, hPt - top - y0);
|
|
285
|
-
const valueOffset = (v) => (v
|
|
381
|
+
const valueOffset = (v) => fractionOf(v, scale) * (horizontal ? plotW : plotH);
|
|
286
382
|
const zeroOffset = valueOffset(crossValue);
|
|
287
383
|
const plotBackground = chart.plotFillHex ?? chart.plotLine?.colorHex ? {
|
|
288
384
|
x: x0,
|
|
@@ -316,10 +412,10 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
316
412
|
});
|
|
317
413
|
if (horizontal) pushGridTicks(gridlines, labels, tickVals, fmtVal, "x", (v) => x0 + valueOffset(v), x0, y0, plotW, plotH, chart.gridLine, valAtEnd);
|
|
318
414
|
else pushGridTicks(gridlines, labels, tickVals, fmtVal, "y", (v) => y0 + valueOffset(v), x0, y0, plotW, plotH, chart.gridLine, valAtEnd);
|
|
319
|
-
const valueOffset2 = scale2 && !horizontal ? (v) => (v
|
|
415
|
+
const valueOffset2 = scale2 && !horizontal ? (v) => fractionOf(v, scale2) * plotH : void 0;
|
|
320
416
|
if (scale2 && valueOffset2) {
|
|
321
417
|
for (const v of tickVals2) labels.push({
|
|
322
|
-
text: formatTick(v
|
|
418
|
+
text: formatTick(v),
|
|
323
419
|
x: x0 + plotW + 3,
|
|
324
420
|
y: y0 + valueOffset2(v) - 9 / 3,
|
|
325
421
|
sizePt: 9,
|
|
@@ -418,8 +514,29 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
418
514
|
}
|
|
419
515
|
var pctLabel = (v) => `${Math.round(v * 100)}%`;
|
|
420
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
|
+
*/
|
|
421
522
|
function catCount(chart) {
|
|
422
|
-
|
|
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;
|
|
423
540
|
}
|
|
424
541
|
function stackedTotals(chart, nCats) {
|
|
425
542
|
let max = 0;
|
|
@@ -768,22 +885,24 @@ function buildScatterScene(chart, wPt, hPt, measure) {
|
|
|
768
885
|
wedges: [],
|
|
769
886
|
labels
|
|
770
887
|
};
|
|
771
|
-
const
|
|
772
|
-
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));
|
|
773
892
|
const xTicks = ticks(xScale);
|
|
774
893
|
const yTicks = ticks(yScale);
|
|
775
894
|
const title = titleLines(chart, wPt, measure);
|
|
776
895
|
const top = 4 + titleHeight(title);
|
|
777
896
|
const legend = buildLegendBlock(chart, wPt, hPt, measure);
|
|
778
|
-
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);
|
|
779
898
|
const y0 = 4 + legend.bottomHeight + 9 * 1.6;
|
|
780
899
|
const plotW = Math.max(1, wPt - 4 - legend.rightWidth - x0);
|
|
781
900
|
const plotH = Math.max(1, hPt - top - y0);
|
|
782
|
-
const xAt = (v) => x0 + (v
|
|
783
|
-
const yAt = (v) => y0 + (v
|
|
901
|
+
const xAt = (v) => x0 + fractionOf(v, xScale) * plotW;
|
|
902
|
+
const yAt = (v) => y0 + fractionOf(v, yScale) * plotH;
|
|
784
903
|
pushChartTitle(labels, title, wPt, hPt);
|
|
785
|
-
pushGridTicks(gridlines, labels, yTicks,
|
|
786
|
-
pushGridTicks(gridlines, labels, xTicks,
|
|
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);
|
|
787
906
|
pushAxisLines(polylines, x0, y0, plotW, plotH, chart);
|
|
788
907
|
const style = chart.scatterStyle ?? "marker";
|
|
789
908
|
const joins = style === "line" || style === "lineMarker" || style.startsWith("smooth");
|
|
@@ -871,8 +990,7 @@ function pushMarker(rects, wedges, marker, x, y, color) {
|
|
|
871
990
|
* @returns The positioned scene primitives.
|
|
872
991
|
*/
|
|
873
992
|
function buildLineScene(chart, wPt, hPt, measure) {
|
|
874
|
-
const
|
|
875
|
-
const range = [allVals.length > 0 ? Math.min(...allVals) : 0, allVals.length > 0 ? Math.max(...allVals) : 1];
|
|
993
|
+
const range = extentOf(chart.series.flatMap((s) => s.values)) ?? [0, 1];
|
|
876
994
|
const lineFormat = chartValueFormatter(chart);
|
|
877
995
|
const f = buildFrame(chart, wPt, hPt, measure, false, {
|
|
878
996
|
dataRange: range,
|
|
@@ -973,7 +1091,7 @@ function buildPieScene(chart, wPt, hPt, measure) {
|
|
|
973
1091
|
const mid = ang + sweep / 2;
|
|
974
1092
|
const pct = Math.round(v / total * 100);
|
|
975
1093
|
const own = chart.showValues ? fmtDataLabel(chart, v) : `${pct}%`;
|
|
976
|
-
const custom = series.pointLabels
|
|
1094
|
+
const custom = atPoint(series.pointLabels, i)?.text;
|
|
977
1095
|
if (custom !== void 0 || chart.showValues || pct >= 5) labels.push({
|
|
978
1096
|
text: custom ?? own,
|
|
979
1097
|
x: cx + Math.cos(mid) * labelR,
|
|
@@ -1002,7 +1120,25 @@ function buildPieScene(chart, wPt, hPt, measure) {
|
|
|
1002
1120
|
};
|
|
1003
1121
|
}
|
|
1004
1122
|
function pointColor(series, idx) {
|
|
1005
|
-
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);
|
|
1006
1142
|
}
|
|
1007
1143
|
function layoutLegend(entries, hasLegend, pos, wPt, hPt, measure, marker = "box") {
|
|
1008
1144
|
if (!hasLegend || entries.length === 0) return {
|
|
@@ -1029,7 +1165,7 @@ function layoutLegend(entries, hasLegend, pos, wPt, hPt, measure, marker = "box"
|
|
|
1029
1165
|
};
|
|
1030
1166
|
const entryW = (e) => keyW + 3 + measure(e.name, 9) + gap * 2;
|
|
1031
1167
|
if (pos === "r" || pos === "l") {
|
|
1032
|
-
const colW = Math.max(
|
|
1168
|
+
const colW = entries.reduce((w, e) => Math.max(w, entryW(e)), 0);
|
|
1033
1169
|
return {
|
|
1034
1170
|
rightWidth: pos === "r" ? colW : 0,
|
|
1035
1171
|
bottomHeight: 0,
|
|
@@ -1100,7 +1236,7 @@ function buildChartScene(chart, wPt, hPt, measure) {
|
|
|
1100
1236
|
* in reverse.
|
|
1101
1237
|
*/
|
|
1102
1238
|
function withReversedCategories(chart) {
|
|
1103
|
-
const n =
|
|
1239
|
+
const n = catCount(chart);
|
|
1104
1240
|
const flip = (i) => n - 1 - i;
|
|
1105
1241
|
return {
|
|
1106
1242
|
...chart,
|
|
@@ -2,6 +2,25 @@ import { Chart } from '../document-model/index.js';
|
|
|
2
2
|
import { ResourceId } from '../ir/index.js';
|
|
3
3
|
import { OpcPackage } from '../opc/index.js';
|
|
4
4
|
import { ColorResolver } from './colors.js';
|
|
5
|
+
/**
|
|
6
|
+
* The most points a chart keeps, all its series together: as many as a sheet
|
|
7
|
+
* has rows, the longest range a series can name. A chart part states its own
|
|
8
|
+
* point counts — a cache's `c:ptCount`, each point's `idx`, the range of a
|
|
9
|
+
* `c:f` — and taken at their word, a count of two billion in a part a few
|
|
10
|
+
* hundred bytes long asked for a dense array that ran the process out of
|
|
11
|
+
* memory, and one past 2³² − 1 threw.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MOST_CHART_POINTS = 1048576;
|
|
14
|
+
/**
|
|
15
|
+
* How many points each of a chart's `seriesCount` series keeps: an even share
|
|
16
|
+
* of {@link MOST_CHART_POINTS}, so that the bound holds for the chart and not
|
|
17
|
+
* series by series — two thousand series, each declaring a sheet's worth of
|
|
18
|
+
* points, are two billion. A series' points past its share are not read.
|
|
19
|
+
*
|
|
20
|
+
* @param seriesCount How many series the chart has.
|
|
21
|
+
* @returns The points each keeps, at least one.
|
|
22
|
+
*/
|
|
23
|
+
export declare function pointsPerSeries(seriesCount: number): number;
|
|
5
24
|
/**
|
|
6
25
|
* Parse a DrawingML chart part (chart1.xml) into a {@link Chart}, reading the
|
|
7
26
|
* CACHED data (`c:numCache` / `c:strCache`) rather than the embedded spreadsheet
|