reamkit 1.31.0 → 1.32.0
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/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 +21 -8
- package/dist/esm/core/drawingml/chart-geometry.js +198 -108
- package/dist/esm/core/drawingml/chart-parser.js +73 -2
- 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.js +16 -11
- 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/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 +522 -80
- 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 +143 -10
- 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 +195 -14
- 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 +274 -51
- package/dist/esm/layout/turned-section.js +14 -2
- package/dist/esm/markdown/markdown-writer.js +17 -6
- 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/font.js +3 -3
- 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/docx-writer.js +11 -2
- package/package.json +5 -1
|
@@ -23,37 +23,63 @@ 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 eight column charts (2026-10-01):
|
|
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, 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;
|
|
35
|
+
* - the step: the smallest 1, 2 or 5 × 10ⁿ that leaves at most ten intervals
|
|
36
|
+
* (`maxIntervals`), whatever the chart's height — 3 750 runs 0…4 000 by 500
|
|
37
|
+
* on a 150pt chart as on a 400pt one. Only an axis too short to hold the
|
|
38
|
+
* labels asks for fewer, and the caller says so.
|
|
39
39
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* Every case of the probe lands where Excel puts it: −1…1.2 on −1.5…1.5 by
|
|
41
|
+
* 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
|
+
*
|
|
45
|
+
* @param dataMin The smallest value to cover.
|
|
46
|
+
* @param dataMax The largest value to cover.
|
|
47
|
+
* @param maxIntervals At most this many steps between the ends (default 10).
|
|
43
48
|
* @returns The rounded min/max and tick step.
|
|
44
49
|
*/
|
|
45
|
-
function niceScale(dataMin, dataMax,
|
|
46
|
-
|
|
50
|
+
function niceScale(dataMin, dataMax, maxIntervals = 10) {
|
|
51
|
+
let lo = Math.min(dataMin, dataMax);
|
|
47
52
|
let hi = Math.max(dataMin, dataMax);
|
|
48
|
-
if (lo === hi) hi
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
if (lo === hi) if (hi > 0) lo = 0;
|
|
54
|
+
else if (lo < 0) hi = 0;
|
|
55
|
+
else hi = 1;
|
|
56
|
+
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);
|
|
51
64
|
return {
|
|
52
|
-
min: Math.floor(lo / step) * step,
|
|
53
|
-
max: Math.ceil(
|
|
65
|
+
min: Math.floor(end.lo / step + 1e-9) * step,
|
|
66
|
+
max: Math.ceil(end.hi / step - 1e-9) * step,
|
|
54
67
|
step
|
|
55
68
|
};
|
|
56
69
|
}
|
|
70
|
+
/** The smallest 1, 2 or 5 × 10ⁿ that cuts `span` into at most `maxIntervals` steps. */
|
|
71
|
+
function stepFor(span, maxIntervals) {
|
|
72
|
+
const most = Math.max(1, maxIntervals);
|
|
73
|
+
let base = 10 ** Math.floor(Math.log10(span / most));
|
|
74
|
+
for (;;) {
|
|
75
|
+
for (const m of [
|
|
76
|
+
1,
|
|
77
|
+
2,
|
|
78
|
+
5
|
|
79
|
+
]) if (span / (m * base) <= most + 1e-9) return m * base;
|
|
80
|
+
base *= 10;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
57
83
|
/**
|
|
58
84
|
* Format an axis tick value, choosing decimal places from the tick `step` so
|
|
59
85
|
* `0.25`-spaced ticks read `0.25` while integer steps drop the fraction.
|
|
@@ -65,7 +91,8 @@ function niceScale(dataMin, dataMax, maxTicks = 6) {
|
|
|
65
91
|
function formatTick(v, step) {
|
|
66
92
|
if (Number.isInteger(step) && Number.isInteger(v)) return String(v);
|
|
67
93
|
const decimals = Math.max(0, -Math.floor(Math.log10(step)));
|
|
68
|
-
|
|
94
|
+
const fixed = v.toFixed(decimals);
|
|
95
|
+
return fixed.includes(".") ? fixed.replace(/0+$/, "").replace(/\.$/, "") : fixed;
|
|
69
96
|
}
|
|
70
97
|
var ticks = (s) => {
|
|
71
98
|
const out = [];
|
|
@@ -85,23 +112,54 @@ var ticks = (s) => {
|
|
|
85
112
|
* the plot, not about the numbers.
|
|
86
113
|
* @returns The axis min/max and tick step.
|
|
87
114
|
*/
|
|
88
|
-
function axisScale(chart, dataMin, dataMax,
|
|
89
|
-
const rounded = niceScale(chart.valAxisMin ?? dataMin, chart.valAxisMax ?? dataMax,
|
|
115
|
+
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;
|
|
90
118
|
return {
|
|
91
|
-
min: chart.valAxisMin ?? rounded.min,
|
|
92
|
-
max: chart.valAxisMax ?? rounded.max,
|
|
93
|
-
step
|
|
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),
|
|
121
|
+
step
|
|
94
122
|
};
|
|
95
123
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
124
|
+
/**
|
|
125
|
+
* The chart title's lines. A title longer than the chart is wide wraps at its
|
|
126
|
+
* spaces, as Excel's and Calc's do, rather than running out past both sides
|
|
127
|
+
* of the frame: dataValidationTableRange.xlsx's "Ranking of Washington
|
|
128
|
+
* Counties on Days per Patient (ALOS) in 2015" crossed the cells beside its
|
|
129
|
+
* chart. A line holds at most four fifths of the chart's width.
|
|
130
|
+
*
|
|
131
|
+
* @param chart The chart.
|
|
132
|
+
* @param wPt The chart's width.
|
|
133
|
+
* @param measure The text measurer.
|
|
134
|
+
* @returns The lines, top first; none for a chart without a title.
|
|
135
|
+
*/
|
|
136
|
+
function titleLines(chart, wPt, measure) {
|
|
137
|
+
if (!chart.title) return [];
|
|
138
|
+
const room = wPt * .8;
|
|
139
|
+
const lines = [];
|
|
140
|
+
let line = "";
|
|
141
|
+
for (const word of chart.title.split(/\s+/).filter((w) => w.length > 0)) {
|
|
142
|
+
const longer = line ? `${line} ${word}` : word;
|
|
143
|
+
if (line && measure(longer, 13) > room) {
|
|
144
|
+
lines.push(line);
|
|
145
|
+
line = word;
|
|
146
|
+
} else line = longer;
|
|
147
|
+
}
|
|
148
|
+
if (line) lines.push(line);
|
|
149
|
+
return lines;
|
|
150
|
+
}
|
|
151
|
+
/** The band a title of `lines` takes at the top of the chart. */
|
|
152
|
+
var titleHeight = (lines) => lines.length === 0 ? 0 : 13 * (1.6 + 1.2 * (lines.length - 1));
|
|
153
|
+
function pushChartTitle(labels, lines, wPt, hPt) {
|
|
154
|
+
lines.forEach((text, i) => {
|
|
155
|
+
labels.push({
|
|
156
|
+
text,
|
|
157
|
+
x: wPt / 2,
|
|
158
|
+
y: hPt - 4 - 13 * (1 + 1.2 * i),
|
|
159
|
+
sizePt: 13,
|
|
160
|
+
colorHex: TITLE_COLOR,
|
|
161
|
+
align: "center"
|
|
162
|
+
});
|
|
105
163
|
});
|
|
106
164
|
}
|
|
107
165
|
function buildLegendBlock(chart, wPt, hPt, measure) {
|
|
@@ -113,7 +171,7 @@ function buildLegendBlock(chart, wPt, hPt, measure) {
|
|
|
113
171
|
const marker = chart.type === "line" || chart.type === "scatter" ? "line" : "box";
|
|
114
172
|
return layoutLegend(legendEntries, chart.hasLegend, chart.legendPos ?? "b", wPt, hPt, measure, marker);
|
|
115
173
|
}
|
|
116
|
-
function pushGridTicks(gridlines, labels, tickVals, fmt, axis, at, x0, y0, plotW, plotH, grid) {
|
|
174
|
+
function pushGridTicks(gridlines, labels, tickVals, fmt, axis, at, x0, y0, plotW, plotH, grid, atEnd = false) {
|
|
117
175
|
for (const v of tickVals) if (axis === "x") {
|
|
118
176
|
const gx = at(v);
|
|
119
177
|
gridlines.push({
|
|
@@ -124,7 +182,7 @@ function pushGridTicks(gridlines, labels, tickVals, fmt, axis, at, x0, y0, plotW
|
|
|
124
182
|
labels.push({
|
|
125
183
|
text: fmt(v),
|
|
126
184
|
x: gx,
|
|
127
|
-
y: y0 - 9,
|
|
185
|
+
y: atEnd ? y0 + plotH + 9 * .4 : y0 - 9,
|
|
128
186
|
sizePt: 9,
|
|
129
187
|
colorHex: LABEL_COLOR,
|
|
130
188
|
align: "center"
|
|
@@ -138,11 +196,11 @@ function pushGridTicks(gridlines, labels, tickVals, fmt, axis, at, x0, y0, plotW
|
|
|
138
196
|
});
|
|
139
197
|
labels.push({
|
|
140
198
|
text: fmt(v),
|
|
141
|
-
x: x0 - 3,
|
|
199
|
+
x: atEnd ? x0 + plotW + 3 : x0 - 3,
|
|
142
200
|
y: gy - 9 / 3,
|
|
143
201
|
sizePt: 9,
|
|
144
202
|
colorHex: LABEL_COLOR,
|
|
145
|
-
align: "right"
|
|
203
|
+
align: atEnd ? "left" : "right"
|
|
146
204
|
});
|
|
147
205
|
}
|
|
148
206
|
}
|
|
@@ -159,16 +217,24 @@ function axisStroke(style) {
|
|
|
159
217
|
widthPt: style?.widthPt ?? 1
|
|
160
218
|
};
|
|
161
219
|
}
|
|
162
|
-
|
|
220
|
+
/**
|
|
221
|
+
* The two axis lines: the upright one (in the left axis's style) and the one
|
|
222
|
+
* lying along (in the bottom axis's). The category axis lies where it crosses
|
|
223
|
+
* the value axis — `cross.y` up a column chart, `cross.x` along a bar chart —
|
|
224
|
+
* and the other stands at the plot's edge.
|
|
225
|
+
*/
|
|
226
|
+
function pushAxisLines(polylines, x0, y0, plotW, plotH, chart, cross = {}) {
|
|
163
227
|
const val = axisStroke(chart.valAxisLine);
|
|
228
|
+
const x = x0 + (cross.x ?? 0);
|
|
229
|
+
const y = y0 + (cross.y ?? 0);
|
|
164
230
|
if (val) polylines.push({
|
|
165
|
-
points: [[
|
|
231
|
+
points: [[x, y0], [x, y0 + plotH]],
|
|
166
232
|
strokeHex: val.hex,
|
|
167
233
|
widthPt: val.widthPt
|
|
168
234
|
});
|
|
169
235
|
const cat = axisStroke(chart.catAxisLine);
|
|
170
236
|
if (cat) polylines.push({
|
|
171
|
-
points: [[x0,
|
|
237
|
+
points: [[x0, y], [x0 + plotW, y]],
|
|
172
238
|
strokeHex: cat.hex,
|
|
173
239
|
widthPt: cat.widthPt
|
|
174
240
|
});
|
|
@@ -181,27 +247,43 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
181
247
|
const nCats = Math.max(chart.categories.length, ...chart.series.map((s) => s.values.length), 1);
|
|
182
248
|
const onSecondary = chart.series.filter((s) => s.secondaryAxis);
|
|
183
249
|
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 ?? [Math.min(
|
|
185
|
-
const scale =
|
|
250
|
+
const [dataMin, dataMax] = opts.dataRange ?? (allVals.length > 0 ? [Math.min(...allVals), Math.max(...allVals)] : [0, 1]);
|
|
251
|
+
const scale = opts.exactRange ? {
|
|
252
|
+
min: dataMin,
|
|
253
|
+
max: dataMax,
|
|
254
|
+
step: niceScale(dataMin, dataMax, intervalsThatFit(horizontal ? wPt : hPt, horizontal)).step
|
|
255
|
+
} : axisScale(chart, dataMin, dataMax, horizontal ? wPt : hPt, horizontal);
|
|
186
256
|
const fmtVal = opts.formatValue ?? ((v) => formatTick(v, scale.step));
|
|
187
257
|
const tickVals = ticks(scale);
|
|
188
258
|
const vals2 = onSecondary.flatMap((s) => s.values.slice(0, nCats));
|
|
189
|
-
const scale2 = vals2.length > 0 ? niceScale(Math.min(
|
|
259
|
+
const scale2 = vals2.length > 0 ? niceScale(Math.min(...vals2), Math.max(...vals2), intervalsThatFit(hPt, false)) : void 0;
|
|
190
260
|
const tickVals2 = scale2 ? ticks(scale2) : [];
|
|
191
|
-
|
|
192
|
-
|
|
261
|
+
const crosses = chart.catAxisCrosses;
|
|
262
|
+
const crossValue = Math.min(Math.max(crosses === "min" ? scale.min : crosses === "max" ? scale.max : crosses ?? 0, scale.min), scale.max);
|
|
263
|
+
const labelsAt = chart.catTickLabelPos === "none" ? "none" : chart.catTickLabelPos === "low" ? "start" : chart.catTickLabelPos === "high" ? "end" : crossValue <= scale.min ? "start" : crossValue >= scale.max ? "end" : "cross";
|
|
264
|
+
const catText = (c) => chart.categories[c] ?? String(c + 1);
|
|
265
|
+
const widestCat = Math.max(0, ...Array.from({ length: nCats }, (_, c) => measure(catText(c), 9)));
|
|
266
|
+
const catBand = 9 * 1.6;
|
|
267
|
+
const groupLevels = horizontal ? [] : chart.categoryGroups ?? [];
|
|
268
|
+
const valAtEnd = chart.catAxisReversed === true ? chart.valAxisCrosses !== "max" : chart.valAxisCrosses === "max";
|
|
269
|
+
const title = titleLines(chart, wPt, measure);
|
|
270
|
+
const top = 4 + titleHeight(title) + (!horizontal && labelsAt === "end" ? catBand : 0) + (horizontal && valAtEnd ? catBand : 0);
|
|
193
271
|
const legend = buildLegendBlock(chart, wPt, hPt, measure);
|
|
194
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;
|
|
195
|
-
const
|
|
273
|
+
const tickHalf = (v) => horizontal && v !== void 0 ? measure(fmtVal(v), 9) / 2 : 0;
|
|
274
|
+
const lastTickHalf = tickHalf(tickVals[tickVals.length - 1]);
|
|
275
|
+
const catLabelW = Math.min(wPt * .4, widestCat + 6);
|
|
196
276
|
const tickLabelW = Math.max(0, ...tickVals.map((v) => measure(fmtVal(v), 9))) + 4;
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const
|
|
277
|
+
const plotRight = wPt - 4 - legend.rightWidth - tick2W - (horizontal && labelsAt === "end" ? catLabelW : lastTickHalf) - (!horizontal && valAtEnd ? tickLabelW : 0);
|
|
278
|
+
const axisTitleBand = 9 * 1.5;
|
|
279
|
+
const leftTitle = horizontal ? chart.catAxisTitle : chart.valAxisTitle;
|
|
280
|
+
const footTitle = horizontal ? chart.valAxisTitle : chart.catAxisTitle;
|
|
281
|
+
const x0 = 4 + (leftTitle ? axisTitleBand : 0) + (horizontal ? labelsAt === "start" ? catLabelW : tickHalf(tickVals[0]) : valAtEnd ? 0 : tickLabelW);
|
|
282
|
+
const y0 = 4 + legend.bottomHeight + (footTitle ? axisTitleBand : 0) + ((horizontal ? !valAtEnd : labelsAt === "start") ? catBand : 0) + (!horizontal && labelsAt === "start" ? groupLevels.length * catBand : 0);
|
|
201
283
|
const plotW = Math.max(1, plotRight - x0);
|
|
202
284
|
const plotH = Math.max(1, hPt - top - y0);
|
|
203
285
|
const valueOffset = (v) => (v - scale.min) / (scale.max - scale.min) * (horizontal ? plotW : plotH);
|
|
204
|
-
const zeroOffset = valueOffset(
|
|
286
|
+
const zeroOffset = valueOffset(crossValue);
|
|
205
287
|
const plotBackground = chart.plotFillHex ?? chart.plotLine?.colorHex ? {
|
|
206
288
|
x: x0,
|
|
207
289
|
y: y0,
|
|
@@ -214,9 +296,9 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
214
296
|
...chart.plotLine.dash ? { strokeDash: chart.plotLine.dash } : {}
|
|
215
297
|
} : {}
|
|
216
298
|
} : void 0;
|
|
217
|
-
pushChartTitle(labels,
|
|
218
|
-
if (
|
|
219
|
-
text:
|
|
299
|
+
pushChartTitle(labels, title, wPt, hPt);
|
|
300
|
+
if (leftTitle) labels.push({
|
|
301
|
+
text: leftTitle,
|
|
220
302
|
x: 12.1,
|
|
221
303
|
y: y0 + plotH / 2,
|
|
222
304
|
sizePt: 9,
|
|
@@ -224,16 +306,16 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
224
306
|
align: "center",
|
|
225
307
|
rotationDeg: 90
|
|
226
308
|
});
|
|
227
|
-
if (
|
|
228
|
-
text:
|
|
309
|
+
if (footTitle) labels.push({
|
|
310
|
+
text: footTitle,
|
|
229
311
|
x: x0 + plotW / 2,
|
|
230
312
|
y: legend.bottomHeight + 2,
|
|
231
313
|
sizePt: 9,
|
|
232
314
|
colorHex: LABEL_COLOR,
|
|
233
315
|
align: "center"
|
|
234
316
|
});
|
|
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);
|
|
317
|
+
if (horizontal) pushGridTicks(gridlines, labels, tickVals, fmtVal, "x", (v) => x0 + valueOffset(v), x0, y0, plotW, plotH, chart.gridLine, valAtEnd);
|
|
318
|
+
else pushGridTicks(gridlines, labels, tickVals, fmtVal, "y", (v) => y0 + valueOffset(v), x0, y0, plotW, plotH, chart.gridLine, valAtEnd);
|
|
237
319
|
const valueOffset2 = scale2 && !horizontal ? (v) => (v - scale2.min) / (scale2.max - scale2.min) * plotH : void 0;
|
|
238
320
|
if (scale2 && valueOffset2) {
|
|
239
321
|
for (const v of tickVals2) labels.push({
|
|
@@ -261,30 +343,60 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
261
343
|
});
|
|
262
344
|
}
|
|
263
345
|
const slot = (horizontal ? plotH : plotW) / nCats;
|
|
264
|
-
const need = horizontal ? 9 * 1.4 :
|
|
346
|
+
const need = horizontal ? 9 * 1.4 : widestCat + 4;
|
|
265
347
|
const step = Math.max(1, Math.ceil(need / Math.max(slot, .01)));
|
|
266
|
-
|
|
267
|
-
|
|
348
|
+
const across = labelsAt === "start" ? 0 : labelsAt === "end" ? horizontal ? plotW : plotH : zeroOffset;
|
|
349
|
+
const labelled = [];
|
|
350
|
+
if (labelsAt !== "none") if (chart.catAxisReversed === true) for (let c = nCats - 1; c >= 0; c -= step) labelled.push(c);
|
|
351
|
+
else for (let c = 0; c < nCats; c += step) labelled.push(c);
|
|
352
|
+
for (const c of labelled) {
|
|
353
|
+
const cat = catText(c);
|
|
268
354
|
if (!cat) continue;
|
|
269
355
|
const center = (horizontal ? y0 : x0) + c * slot + slot / 2;
|
|
270
356
|
if (horizontal) labels.push({
|
|
271
357
|
text: cat,
|
|
272
|
-
x: x0 - 3,
|
|
358
|
+
x: labelsAt === "end" ? x0 + across + 3 : x0 + across - 3,
|
|
273
359
|
y: center - 9 / 3,
|
|
274
360
|
sizePt: 9,
|
|
275
361
|
colorHex: LABEL_COLOR,
|
|
276
|
-
align: "right"
|
|
362
|
+
align: labelsAt === "end" ? "left" : "right"
|
|
277
363
|
});
|
|
278
364
|
else labels.push({
|
|
279
365
|
text: cat,
|
|
280
366
|
x: center,
|
|
281
|
-
y: y0 - 9,
|
|
367
|
+
y: labelsAt === "end" ? y0 + across + 9 * .4 : y0 + across - 9,
|
|
282
368
|
sizePt: 9,
|
|
283
369
|
colorHex: LABEL_COLOR,
|
|
284
370
|
align: "center"
|
|
285
371
|
});
|
|
286
372
|
}
|
|
287
|
-
|
|
373
|
+
if (labelsAt !== "none" && labelsAt !== "end") groupLevels.forEach((groups, level) => {
|
|
374
|
+
const rowY = y0 + across - 9 - (level + 1) * catBand;
|
|
375
|
+
groups.forEach((group, i) => {
|
|
376
|
+
const end = Math.min(nCats, groups[i + 1]?.start ?? nCats);
|
|
377
|
+
if (end <= group.start) return;
|
|
378
|
+
labels.push({
|
|
379
|
+
text: group.label,
|
|
380
|
+
x: x0 + (group.start + end) / 2 * slot,
|
|
381
|
+
y: rowY,
|
|
382
|
+
sizePt: 9,
|
|
383
|
+
colorHex: LABEL_COLOR,
|
|
384
|
+
align: "center"
|
|
385
|
+
});
|
|
386
|
+
if (i > 0) polylines.push({
|
|
387
|
+
points: [[x0 + group.start * slot, y0 + across], [x0 + group.start * slot, rowY - 9 * .4]],
|
|
388
|
+
strokeHex: GRID_COLOR,
|
|
389
|
+
widthPt: .75
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
pushAxisLines(polylines, x0, y0, plotW, plotH, chart, horizontal ? {
|
|
394
|
+
x: zeroOffset,
|
|
395
|
+
...valAtEnd ? { y: plotH } : {}
|
|
396
|
+
} : {
|
|
397
|
+
y: zeroOffset,
|
|
398
|
+
...valAtEnd ? { x: plotW } : {}
|
|
399
|
+
});
|
|
288
400
|
legend.emit(rects, labels);
|
|
289
401
|
return {
|
|
290
402
|
x0,
|
|
@@ -332,6 +444,7 @@ function groupingFrameOpts(chart, nCats) {
|
|
|
332
444
|
const g = chart.grouping ?? "clustered";
|
|
333
445
|
if (g === "percentStacked") return {
|
|
334
446
|
dataRange: [0, 1],
|
|
447
|
+
exactRange: true,
|
|
335
448
|
formatValue: pctLabel
|
|
336
449
|
};
|
|
337
450
|
const format = chartValueFormatter(chart);
|
|
@@ -655,12 +768,12 @@ function buildScatterScene(chart, wPt, hPt, measure) {
|
|
|
655
768
|
wedges: [],
|
|
656
769
|
labels
|
|
657
770
|
};
|
|
658
|
-
const xScale = niceScale(...
|
|
659
|
-
const yScale = niceScale(...
|
|
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));
|
|
660
773
|
const xTicks = ticks(xScale);
|
|
661
774
|
const yTicks = ticks(yScale);
|
|
662
|
-
|
|
663
|
-
|
|
775
|
+
const title = titleLines(chart, wPt, measure);
|
|
776
|
+
const top = 4 + titleHeight(title);
|
|
664
777
|
const legend = buildLegendBlock(chart, wPt, hPt, measure);
|
|
665
778
|
const x0 = 4 + (Math.max(0, ...yTicks.map((v) => measure(formatTick(v, yScale.step), 9))) + 4);
|
|
666
779
|
const y0 = 4 + legend.bottomHeight + 9 * 1.6;
|
|
@@ -668,7 +781,7 @@ function buildScatterScene(chart, wPt, hPt, measure) {
|
|
|
668
781
|
const plotH = Math.max(1, hPt - top - y0);
|
|
669
782
|
const xAt = (v) => x0 + (v - xScale.min) / (xScale.max - xScale.min) * plotW;
|
|
670
783
|
const yAt = (v) => y0 + (v - yScale.min) / (yScale.max - yScale.min) * plotH;
|
|
671
|
-
pushChartTitle(labels,
|
|
784
|
+
pushChartTitle(labels, title, wPt, hPt);
|
|
672
785
|
pushGridTicks(gridlines, labels, yTicks, (v) => formatTick(v, yScale.step), "y", yAt, x0, y0, plotW, plotH, chart.gridLine);
|
|
673
786
|
pushGridTicks(gridlines, labels, xTicks, (v) => formatTick(v, xScale.step), "x", xAt, x0, y0, plotW, plotH, chart.gridLine);
|
|
674
787
|
pushAxisLines(polylines, x0, y0, plotW, plotH, chart);
|
|
@@ -702,27 +815,13 @@ function buildScatterScene(chart, wPt, hPt, measure) {
|
|
|
702
815
|
};
|
|
703
816
|
}
|
|
704
817
|
/**
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
818
|
+
* How many intervals an axis `extentPt` long has room to label: ten, Excel's
|
|
819
|
+
* own most, unless its labels would run into each other first — one label
|
|
820
|
+
* line apiece stacked up a vertical axis, a few digits' width apiece along a
|
|
821
|
+
* horizontal one. Excel crams nine labels up a 150pt column chart rather than
|
|
822
|
+
* step it more coarsely, so the room is all that limits it.
|
|
708
823
|
*/
|
|
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)));
|
|
824
|
+
var intervalsThatFit = (extentPt, horizontal) => Math.min(10, Math.max(2, Math.floor(extentPt * .75 / (9 * (horizontal ? 3.5 : 1.4)))));
|
|
726
825
|
/** Side (points) of the square stamped for a series that names no symbol. */
|
|
727
826
|
var DEFAULT_MARKER_PT = 4;
|
|
728
827
|
/**
|
|
@@ -773,9 +872,7 @@ function pushMarker(rects, wedges, marker, x, y, color) {
|
|
|
773
872
|
*/
|
|
774
873
|
function buildLineScene(chart, wPt, hPt, measure) {
|
|
775
874
|
const allVals = chart.series.flatMap((s) => s.values);
|
|
776
|
-
const
|
|
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)];
|
|
875
|
+
const range = [allVals.length > 0 ? Math.min(...allVals) : 0, allVals.length > 0 ? Math.max(...allVals) : 1];
|
|
779
876
|
const lineFormat = chartValueFormatter(chart);
|
|
780
877
|
const f = buildFrame(chart, wPt, hPt, measure, false, {
|
|
781
878
|
dataRange: range,
|
|
@@ -846,8 +943,8 @@ function buildPieScene(chart, wPt, hPt, measure) {
|
|
|
846
943
|
wedges,
|
|
847
944
|
labels
|
|
848
945
|
};
|
|
849
|
-
|
|
850
|
-
|
|
946
|
+
const title = titleLines(chart, wPt, measure);
|
|
947
|
+
const top = 4 + titleHeight(title);
|
|
851
948
|
const legend = layoutLegend(values.map((_, i) => ({
|
|
852
949
|
name: chart.categories[i] ?? String(i + 1),
|
|
853
950
|
colorHex: sliceColor(series, i, chart.seriesColorCycle)
|
|
@@ -895,14 +992,7 @@ function buildPieScene(chart, wPt, hPt, measure) {
|
|
|
895
992
|
sweepRad: -2 * Math.PI,
|
|
896
993
|
fillHex: "FFFFFF"
|
|
897
994
|
});
|
|
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
|
-
});
|
|
995
|
+
pushChartTitle(labels, title, wPt, hPt);
|
|
906
996
|
legend.emit(rects, labels);
|
|
907
997
|
return {
|
|
908
998
|
rects,
|
|
@@ -1014,7 +1104,7 @@ function withReversedCategories(chart) {
|
|
|
1014
1104
|
const flip = (i) => n - 1 - i;
|
|
1015
1105
|
return {
|
|
1016
1106
|
...chart,
|
|
1017
|
-
categories:
|
|
1107
|
+
categories: Array.from({ length: n }, (_, i) => chart.categories.length > 0 ? chart.categories[flip(i)] ?? "" : String(flip(i) + 1)),
|
|
1018
1108
|
series: chart.series.map((s) => ({
|
|
1019
1109
|
...s,
|
|
1020
1110
|
values: Array.from({ length: n }, (_, i) => s.values[flip(i)] ?? 0),
|
|
@@ -71,10 +71,13 @@ function parseChart(chartXml, resolveColor, resolveImage) {
|
|
|
71
71
|
const secondaryValAxisTitle = secondaryTitleNode ? collectAT(secondaryTitleNode) || "Axis Title" : void 0;
|
|
72
72
|
let categories = [];
|
|
73
73
|
let categoriesRef;
|
|
74
|
+
let categoryGroups;
|
|
74
75
|
for (const s of serNodes) {
|
|
75
76
|
const cat = poChildren(s).find((c) => poIs(c, "c:cat"));
|
|
76
77
|
if (cat) {
|
|
77
|
-
|
|
78
|
+
const levels = multiLevelCategories(cat);
|
|
79
|
+
categories = levels ? levels.categories : denseStrings(cat);
|
|
80
|
+
categoryGroups = levels?.groups;
|
|
78
81
|
categoriesRef ??= refFormula(cat);
|
|
79
82
|
break;
|
|
80
83
|
}
|
|
@@ -99,7 +102,12 @@ function parseChart(chartXml, resolveColor, resolveImage) {
|
|
|
99
102
|
const gridLine = lineStyleOf(leftAxNode ? poChildren(leftAxNode).find((c) => poIs(c, "c:majorGridlines")) : void 0, resolveColor);
|
|
100
103
|
const valAxisMin = axisScaling(plotArea, "c:min");
|
|
101
104
|
const valAxisMax = axisScaling(plotArea, "c:max");
|
|
105
|
+
const valAxisMajorUnit = majorUnitOf(plotArea);
|
|
102
106
|
const catAxisReversed = axisOrientation(catAxNode) === "maxMin";
|
|
107
|
+
const catAxisCrosses = axisCrossing(catAxNode);
|
|
108
|
+
const valAxisCrosses = axisCrossing(valAxNode);
|
|
109
|
+
const tickLblPos = catAxNode ? poVal(poChildren(catAxNode).find((c) => poIs(c, "c:tickLblPos"))) : void 0;
|
|
110
|
+
const catTickLabelPos = tickLblPos === "low" || tickLblPos === "high" || tickLblPos === "none" ? tickLblPos : void 0;
|
|
103
111
|
const chartSpace = tree.find((c) => poIs(c, "c:chartSpace"));
|
|
104
112
|
const spaceSpPr = poChildren(chartSpace).find((c) => poIs(c, "c:spPr"));
|
|
105
113
|
const frameLine = spaceSpPr ? poChildren(spaceSpPr).find((c) => poIs(c, "a:ln")) : void 0;
|
|
@@ -123,6 +131,7 @@ function parseChart(chartXml, resolveColor, resolveImage) {
|
|
|
123
131
|
...Number.isFinite(gapPercent) && gapPercent >= 0 ? { gapPercent } : {},
|
|
124
132
|
categories,
|
|
125
133
|
...categoriesRef ? { categoriesRef } : {},
|
|
134
|
+
...categoryGroups && categoryGroups.length > 0 ? { categoryGroups } : {},
|
|
126
135
|
series,
|
|
127
136
|
hasLegend: legend !== void 0,
|
|
128
137
|
...isLegendPos(legendPos) ? { legendPos } : {},
|
|
@@ -132,6 +141,9 @@ function parseChart(chartXml, resolveColor, resolveImage) {
|
|
|
132
141
|
...showValues ? { showValues: true } : {},
|
|
133
142
|
...catAxisTitle ? { catAxisTitle } : {},
|
|
134
143
|
...catAxisReversed ? { catAxisReversed } : {},
|
|
144
|
+
...catAxisCrosses !== void 0 ? { catAxisCrosses } : {},
|
|
145
|
+
...valAxisCrosses !== void 0 ? { valAxisCrosses } : {},
|
|
146
|
+
...catTickLabelPos ? { catTickLabelPos } : {},
|
|
135
147
|
...valAxisTitle ? { valAxisTitle } : {},
|
|
136
148
|
...secondaryValAxisTitle ? { secondaryValAxisTitle } : {},
|
|
137
149
|
...scatterStyle ? { scatterStyle } : {},
|
|
@@ -142,6 +154,7 @@ function parseChart(chartXml, resolveColor, resolveImage) {
|
|
|
142
154
|
...gridLine ? { gridLine } : {},
|
|
143
155
|
...valAxisMin !== void 0 ? { valAxisMin } : {},
|
|
144
156
|
...valAxisMax !== void 0 ? { valAxisMax } : {},
|
|
157
|
+
...valAxisMajorUnit !== void 0 ? { valAxisMajorUnit } : {},
|
|
145
158
|
...frameFillHex ? { frameFillHex } : {},
|
|
146
159
|
...frameFillImage ? { frameFillImage } : {},
|
|
147
160
|
...frameLineHex ? { frameLineHex } : {},
|
|
@@ -298,9 +311,16 @@ function fillColorOf(spPr, resolveColor) {
|
|
|
298
311
|
const lnFill = ln ? poChildren(ln).find((c) => poIs(c, "a:solidFill")) : void 0;
|
|
299
312
|
return lnFill ? colorFromSolidFill(lnFill, resolveColor) : void 0;
|
|
300
313
|
}
|
|
314
|
+
/** The colour elements a chart's fill names a colour by (§20.1.2.3). */
|
|
315
|
+
var CHART_COLOR_NODES = [
|
|
316
|
+
"a:srgbClr",
|
|
317
|
+
"a:schemeClr",
|
|
318
|
+
"a:sysClr",
|
|
319
|
+
"a:prstClr"
|
|
320
|
+
];
|
|
301
321
|
function colorFromSolidFill(solid, resolveColor) {
|
|
302
322
|
for (const c of poChildren(solid)) {
|
|
303
|
-
if (!
|
|
323
|
+
if (!CHART_COLOR_NODES.some((name) => poIs(c, name))) continue;
|
|
304
324
|
if (!poAttr(c, "val")) continue;
|
|
305
325
|
return resolveColorNode(c, resolveColor);
|
|
306
326
|
}
|
|
@@ -374,10 +394,30 @@ function axisTitle(plotArea, axTag) {
|
|
|
374
394
|
* does not carry — those keep the plain numeric render.
|
|
375
395
|
*/
|
|
376
396
|
/** §21.2.2.134 `c:scaling/c:orientation` — `minMax` (the default) or `maxMin`. */
|
|
397
|
+
/**
|
|
398
|
+
* §21.2.2.33/§21.2.2.34 — where an axis crosses the one it is drawn against:
|
|
399
|
+
* `crossesAt` a value, or `crosses` at that axis's minimum or maximum.
|
|
400
|
+
* undefined for `autoZero`, the default.
|
|
401
|
+
*/
|
|
402
|
+
function axisCrossing(ax) {
|
|
403
|
+
if (!ax) return void 0;
|
|
404
|
+
const at = poChildren(ax).find((c) => poIs(c, "c:crossesAt"));
|
|
405
|
+
const value = at ? Number(poAttr(at, "val")) : NaN;
|
|
406
|
+
if (Number.isFinite(value)) return value;
|
|
407
|
+
const crosses = poVal(poChildren(ax).find((c) => poIs(c, "c:crosses")));
|
|
408
|
+
return crosses === "min" || crosses === "max" ? crosses : void 0;
|
|
409
|
+
}
|
|
377
410
|
function axisOrientation(ax) {
|
|
378
411
|
const scaling = ax ? poChildren(ax).find((c) => poIs(c, "c:scaling")) : void 0;
|
|
379
412
|
return scaling ? poVal(poChildren(scaling).find((c) => poIs(c, "c:orientation"))) : void 0;
|
|
380
413
|
}
|
|
414
|
+
/** §21.2.2.98 `c:valAx/c:majorUnit` — a positive step, or undefined for "auto". */
|
|
415
|
+
function majorUnitOf(plotArea) {
|
|
416
|
+
const ax = poChildren(plotArea).find((c) => poIs(c, "c:valAx"));
|
|
417
|
+
const node = ax ? poChildren(ax).find((c) => poIs(c, "c:majorUnit")) : void 0;
|
|
418
|
+
const v = node ? Number(poAttr(node, "val")) : NaN;
|
|
419
|
+
return Number.isFinite(v) && v > 0 ? v : void 0;
|
|
420
|
+
}
|
|
381
421
|
function axisScaling(plotArea, tag) {
|
|
382
422
|
const ax = poChildren(plotArea).find((c) => poIs(c, "c:valAx"));
|
|
383
423
|
const scaling = ax ? poChildren(ax).find((c) => poIs(c, "c:scaling")) : void 0;
|
|
@@ -489,6 +529,37 @@ function denseStrings(container) {
|
|
|
489
529
|
for (const p of pts) arr[p.idx] = p.v;
|
|
490
530
|
return arr;
|
|
491
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* §21.2.2.115 `c:multiLvlStrCache` — categories labelled on several levels:
|
|
534
|
+
* the first `c:lvl` labels each category, every later one groups them, a
|
|
535
|
+
* group's label standing at the category it starts at. Read as one flat cache
|
|
536
|
+
* it read as none, and an xlsx chart fell back to the cells its reference
|
|
537
|
+
* names, both columns of them in turn: WithChartSheet.xlsx's six bars stood in
|
|
538
|
+
* the first six of sixteen slots under a jumble of years and measure names.
|
|
539
|
+
*
|
|
540
|
+
* @param cat The `c:cat` element.
|
|
541
|
+
* @returns The innermost labels and the outer levels' groups, or undefined
|
|
542
|
+
* for a category axis of one level.
|
|
543
|
+
*/
|
|
544
|
+
function multiLevelCategories(cat) {
|
|
545
|
+
const cache = poFindDescendant(cat, "c:multiLvlStrCache");
|
|
546
|
+
if (!cache) return void 0;
|
|
547
|
+
const count = poIntAttr(poChildren(cache).find((c) => poIs(c, "c:ptCount")) ?? cache, "val") ?? 0;
|
|
548
|
+
const [inner, ...outer] = poChildren(cache).filter((c) => poIs(c, "c:lvl")).map((lvl) => poChildren(lvl).filter((pt) => poIs(pt, "c:pt")).map((pt) => {
|
|
549
|
+
const v = poChildren(pt).find((c) => poIs(c, "c:v"));
|
|
550
|
+
return {
|
|
551
|
+
start: poIntAttr(pt, "idx") ?? 0,
|
|
552
|
+
label: v ? poText(v) : ""
|
|
553
|
+
};
|
|
554
|
+
}));
|
|
555
|
+
if (!inner) return void 0;
|
|
556
|
+
const categories = new Array(Math.max(count, ...inner.map((p) => p.start + 1))).fill("");
|
|
557
|
+
for (const p of inner) categories[p.start] = p.label;
|
|
558
|
+
return {
|
|
559
|
+
categories,
|
|
560
|
+
groups: outer.map((level) => [...level].sort((a, b) => a.start - b.start))
|
|
561
|
+
};
|
|
562
|
+
}
|
|
492
563
|
function isLegendPos(v) {
|
|
493
564
|
return v === "r" || v === "l" || v === "t" || v === "b";
|
|
494
565
|
}
|
|
@@ -40,6 +40,12 @@ export declare function applyColorMods(hex: string, mods: ReadonlyArray<ColorMod
|
|
|
40
40
|
* scheme slots when a document carries no custom theme part.
|
|
41
41
|
*/
|
|
42
42
|
export declare const DEFAULT_THEME_PALETTE: ReadonlyMap<string, string>;
|
|
43
|
+
/**
|
|
44
|
+
* The Office 2023 theme's palette — what Excel colours a workbook that carries
|
|
45
|
+
* no theme part with. Its own PDF of theme slots 0…9 in such a workbook
|
|
46
|
+
* (2026-10-01) paints exactly these: accent1 is 156082, not 2013's 4472C4.
|
|
47
|
+
*/
|
|
48
|
+
export declare const OFFICE_2023_THEME_PALETTE: ReadonlyMap<string, string>;
|
|
43
49
|
/**
|
|
44
50
|
* Which theme slot each `schemeClr` name stands for. Only the text/background
|
|
45
51
|
* aliases are ever remapped; anything absent resolves under its own name.
|