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.
@@ -159,29 +159,73 @@ function agileKeyData(attrs) {
159
159
  };
160
160
  }
161
161
  function passwordKeyEncryptor(xml) {
162
- const re = /<[A-Za-z0-9]*:?encryptedKey\b([^>]*)\/?>/gu;
163
- let m = re.exec(xml);
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 m = new RegExp(`<[A-Za-z0-9]*:?${tag}\\b([^>]*)/?>`, "u").exec(xml);
173
- return m ? parseAttrs(m[1] ?? "") : {};
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
- const re = /([A-Za-z0-9_:]+)\s*=\s*"([^"]*)"/gu;
178
- let m = re.exec(raw);
179
- while (m) {
180
- out[m[1].replace(/^.*:/u, "")] = m[2];
181
- m = re.exec(raw);
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 eight column charts (2026-10-01):
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, like both ends of a range across zero, takes 5% of the spread
128
- * beyond the data before it is rounded out to the step;
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 probe lands where Excel puts it: −1…1.2 on −1.5…1.5 by
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 a budget of ticks by height
137
- * stepped the budget chart by 1 000 and started 93…97 at zero.
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
- * Format an axis tick value, choosing decimal places from the tick `step` so
147
- * `0.25`-spaced ticks read `0.25` while integer steps drop the fraction.
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 The tick value.
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, step: number): string;
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 eight column charts (2026-10-01):
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, like both ends of a range across zero, takes 5% of the spread
34
- * beyond the data before it is rounded out to the step;
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 probe lands where Excel puts it: −1…1.2 on −1.5…1.5 by
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 a budget of ticks by height
43
- * stepped the budget chart by 1 000 and started 93…97 at zero.
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
- const end = {
58
- lo: lo - spread * .05,
59
- hi: hi + spread * .05
60
- };
61
- if (lo >= 0) end.lo = spread < hi / 6 ? lo - spread / 2 : 0;
62
- else if (hi <= 0) end.hi = spread < -lo / 6 ? hi + spread / 2 : 0;
63
- const step = stepFor(end.hi - end.lo, maxIntervals);
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
- min: Math.floor(end.lo / step + 1e-9) * step,
66
- max: Math.ceil(end.hi / step - 1e-9) * step,
67
- step
89
+ lo: finite(lo - spread * .05),
90
+ hi: finite(hi + spread * .05)
68
91
  };
69
92
  }
70
- /** The smallest 1, 2 or 5 × 10ⁿ that cuts `span` into at most `maxIntervals` steps. */
71
- function stepFor(span, maxIntervals) {
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
- let base = 10 ** Math.floor(Math.log10(span / most));
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 (span / (m * base) <= most + 1e-9) return m * base;
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
- * Format an axis tick value, choosing decimal places from the tick `step` so
85
- * `0.25`-spaced ticks read `0.25` while integer steps drop the fraction.
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 The tick value.
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, step) {
92
- if (Number.isInteger(step) && Number.isInteger(v)) return String(v);
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 v = s.min; v <= s.max + 1e-9; v += s.step) out.push(Math.abs(v) < 1e-9 ? 0 : v);
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 own range where they
105
- * did not. A fixed end is exact — nice-rounding it would move a number the
106
- * author chose — so only the tick step comes from the rounding pass.
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
- * @param chart The chart (for its fixed ends).
109
- * @param dataMin The smallest value to cover.
110
- * @param dataMax The largest value to cover.
111
- * @param hPt The chart's height — how many ticks fit is a question about
112
- * the plot, not about the numbers.
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 rounded = niceScale(chart.valAxisMin ?? dataMin, chart.valAxisMax ?? dataMax, intervalsThatFit(extentPt, horizontal));
117
- const step = chart.valAxisMajorUnit ?? rounded.step;
118
- return {
119
- min: chart.valAxisMin ?? (step === rounded.step ? rounded.min : Math.floor(rounded.min / step) * step),
120
- max: chart.valAxisMax ?? (step === rounded.step ? rounded.max : Math.ceil(rounded.max / step) * step),
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 = Math.max(chart.categories.length, ...chart.series.map((s) => s.values.length), 1);
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.length > 0 ? [Math.min(...allVals), Math.max(...allVals)] : [0, 1]);
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 ?? ((v) => formatTick(v, scale.step));
351
+ const fmtVal = opts.formatValue ?? formatTick;
257
352
  const tickVals = ticks(scale);
258
- const vals2 = onSecondary.flatMap((s) => s.values.slice(0, nCats));
259
- const scale2 = vals2.length > 0 ? niceScale(Math.min(...vals2), Math.max(...vals2), intervalsThatFit(hPt, false)) : void 0;
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
- const widestCat = Math.max(0, ...Array.from({ length: nCats }, (_, c) => measure(catText(c), 9)));
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, scale2.step), 9))) + 4 + (chart.secondaryValAxisTitle ? 9 * 1.5 : 0) : 0;
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 - scale.min) / (scale.max - scale.min) * (horizontal ? plotW : plotH);
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 - scale2.min) / (scale2.max - scale2.min) * plotH : void 0;
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, scale2.step),
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
- return Math.max(chart.categories.length, ...chart.series.map((s) => s.values.length), 1);
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 xScale = niceScale(Math.min(...xs), Math.max(...xs), intervalsThatFit(wPt, true));
772
- const yScale = niceScale(Math.min(...ys), Math.max(...ys), intervalsThatFit(hPt, false));
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, yScale.step), 9))) + 4);
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 - xScale.min) / (xScale.max - xScale.min) * plotW;
783
- const yAt = (v) => y0 + (v - yScale.min) / (yScale.max - yScale.min) * plotH;
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, (v) => formatTick(v, yScale.step), "y", yAt, x0, y0, plotW, plotH, chart.gridLine);
786
- pushGridTicks(gridlines, labels, xTicks, (v) => formatTick(v, xScale.step), "x", xAt, x0, y0, plotW, plotH, chart.gridLine);
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 allVals = chart.series.flatMap((s) => s.values);
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?.find((l) => l.idx === i)?.text;
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?.find((p) => p.idx === idx)?.colorHex;
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(...entries.map(entryW));
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 = Math.max(chart.categories.length, ...chart.series.map((s) => s.values.length), 1);
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