reamkit 1.32.0 → 1.33.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 +5 -4
- package/dist/esm/core/bidi/algorithm.js +2 -2
- package/dist/esm/core/bidi/char-types.js +1 -1
- package/dist/esm/core/bidi/index.js +3 -3
- package/dist/esm/core/bytes.js +1 -1
- package/dist/esm/core/converter/facade.js +3 -2
- package/dist/esm/core/converter/ream.js +12 -5
- package/dist/esm/core/crypto/asn1.js +2 -2
- package/dist/esm/core/crypto/cms.js +5 -3
- package/dist/esm/core/crypto/index.js +2 -1
- package/dist/esm/core/crypto/offcrypto.js +64 -19
- package/dist/esm/core/crypto/primitives.js +16 -16
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +174 -2
- package/dist/esm/core/drawingml/chart-geometry.d.ts +45 -17
- package/dist/esm/core/drawingml/chart-geometry.js +1090 -257
- package/dist/esm/core/drawingml/chart-parser.d.ts +24 -1
- package/dist/esm/core/drawingml/chart-parser.js +334 -41
- package/dist/esm/core/drawingml/chart-text.d.ts +41 -0
- package/dist/esm/core/drawingml/chart-text.js +93 -0
- package/dist/esm/core/drawingml/colors.js +5 -5
- package/dist/esm/core/drawingml/diagram/layout-engine.js +1 -4
- package/dist/esm/core/drawingml/diagram/run.js +2 -1
- package/dist/esm/core/drawingml/preset-geometry.js +4 -6
- package/dist/esm/core/drawingml/shape-render.js +1 -1
- package/dist/esm/core/drawingml/text-warp.js +4 -16
- package/dist/esm/core/drawingml/theme-parser.js +14 -8
- package/dist/esm/core/font/arabic-joining.js +2 -2
- package/dist/esm/core/font/binary-reader.js +2 -0
- package/dist/esm/core/font/font-registry.js +1 -0
- package/dist/esm/core/font/ligatures.js +1 -1
- package/dist/esm/core/font/opentype-layout.js +11 -5
- package/dist/esm/core/font/ttf-build.js +6 -10
- package/dist/esm/core/font/ttf-parser.js +2 -1
- package/dist/esm/core/font/ttf-subset.js +8 -8
- package/dist/esm/core/fonts/families.js +1 -1
- package/dist/esm/core/fonts/remote-fonts.js +4 -4
- package/dist/esm/core/hyphenation/index.js +1 -1
- package/dist/esm/core/images.js +8 -8
- package/dist/esm/core/ir/loss.js +1 -0
- package/dist/esm/core/line-breaker/cjk.js +1 -1
- package/dist/esm/core/line-breaker/index.js +4 -3
- package/dist/esm/core/links.js +1 -1
- package/dist/esm/core/lzw.js +1 -1
- package/dist/esm/core/metafile/blit.js +1 -1
- package/dist/esm/core/metafile/emf.js +5 -7
- package/dist/esm/core/metafile/picture.js +1 -1
- package/dist/esm/core/metafile/symbol-fonts.js +7 -7
- package/dist/esm/core/metafile/wmf.js +5 -7
- package/dist/esm/core/number-format.d.ts +40 -15
- package/dist/esm/core/number-format.js +284 -79
- package/dist/esm/core/numbering/index.js +3 -2
- package/dist/esm/core/ole/cfb.js +4 -4
- package/dist/esm/core/ole/escher-blip.js +1 -1
- package/dist/esm/core/opc/index.js +5 -4
- package/dist/esm/core/opc/package.js +3 -1
- 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 +40 -3
- package/dist/esm/core/outline.js +1 -1
- package/dist/esm/core/png-encode.js +3 -3
- package/dist/esm/core/po-helpers.js +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +6 -0
- package/dist/esm/core/style-cascade/index.js +4 -3
- package/dist/esm/core/tiff.js +3 -1
- package/dist/esm/excel/activex-parser.js +1 -1
- package/dist/esm/excel/conditional-format.js +7 -7
- package/dist/esm/excel/formula/functions.js +45 -18
- package/dist/esm/excel/formula/index.js +3 -3
- package/dist/esm/excel/formula/lexer.js +2 -2
- package/dist/esm/excel/formula/parser.js +4 -3
- package/dist/esm/excel/formula/value.js +1 -1
- package/dist/esm/excel/index.js +7 -6
- package/dist/esm/excel/print-model.js +18 -7
- package/dist/esm/excel/printer-settings.js +6 -7
- package/dist/esm/excel/rich-value.js +1 -1
- package/dist/esm/excel/sheet-shape-parser.js +4 -2
- package/dist/esm/excel/sheet-to-flow.js +13 -11
- package/dist/esm/excel/styles-parser.js +5 -3
- package/dist/esm/excel/tint.js +5 -5
- package/dist/esm/excel/vml-drawing.js +3 -3
- package/dist/esm/excel/worksheet-parser.js +5 -5
- package/dist/esm/excel/xls/biff-chart.js +1 -3
- package/dist/esm/excel/xls/biff-reader.js +10 -11
- package/dist/esm/excel/xls/xls-reader.js +1 -1
- package/dist/esm/excel/xlsx-reader.js +56 -9
- package/dist/esm/excel/xlsx-to-pdf.js +1 -1
- package/dist/esm/excel/xlsx-writer.js +2 -2
- package/dist/esm/html/html-writer.js +18 -10
- package/dist/esm/layout/line-paint.js +1 -1
- package/dist/esm/layout/math-layout.js +5 -8
- package/dist/esm/layout/styled-layout.js +104 -32
- package/dist/esm/layout/turned-section.js +2 -2
- package/dist/esm/markdown/markdown-writer.js +59 -8
- package/dist/esm/pdf/encryption.js +9 -9
- package/dist/esm/pdf/icc-profile.js +2 -2
- package/dist/esm/pdf/index.js +3 -2
- package/dist/esm/pdf/objects.js +7 -0
- package/dist/esm/pdf/serialize.js +1 -1
- package/dist/esm/pdf/struct-tree.js +3 -1
- package/dist/esm/pdf/styled-page-emitter.js +23 -15
- package/dist/esm/pdf/styled-page-renderer.js +9 -5
- package/dist/esm/pdf-reader/annot-draw.js +5 -5
- package/dist/esm/pdf-reader/annots.js +3 -1
- package/dist/esm/pdf-reader/ccitt.js +2 -1
- package/dist/esm/pdf-reader/cff-outline.js +1 -4
- package/dist/esm/pdf-reader/content.js +13 -15
- package/dist/esm/pdf-reader/decrypt.js +5 -4
- package/dist/esm/pdf-reader/document.js +6 -2
- package/dist/esm/pdf-reader/encodings.js +1 -1
- package/dist/esm/pdf-reader/flow-build.js +4 -6
- package/dist/esm/pdf-reader/font.js +4 -4
- package/dist/esm/pdf-reader/function.js +8 -7
- package/dist/esm/pdf-reader/icc.js +1 -1
- package/dist/esm/pdf-reader/images.js +1 -1
- package/dist/esm/pdf-reader/jbig2.js +36 -38
- package/dist/esm/pdf-reader/jpeg.js +14 -13
- package/dist/esm/pdf-reader/layout.js +13 -10
- package/dist/esm/pdf-reader/lexer.js +2 -1
- package/dist/esm/pdf-reader/math-rows.js +2 -2
- package/dist/esm/pdf-reader/optional-content.js +1 -1
- package/dist/esm/pdf-reader/page-numbers.js +1 -1
- package/dist/esm/pdf-reader/pattern-tint.js +1 -2
- package/dist/esm/pdf-reader/reader.js +2 -2
- package/dist/esm/pdf-reader/struct-tree.js +1 -1
- package/dist/esm/pdf-reader/tagged.js +3 -2
- package/dist/esm/pdf-reader/text.js +2 -2
- package/dist/esm/pdf-reader/type1-outline.js +1 -3
- package/dist/esm/pptx/embedded-fonts.js +3 -5
- package/dist/esm/pptx/placeholder-cascade.js +1 -1
- package/dist/esm/pptx/ppt/ppt-reader.js +5 -4
- package/dist/esm/pptx/ppt/ppt-text.js +9 -10
- package/dist/esm/pptx/pptx-reader.js +6 -5
- package/dist/esm/pptx/preset-table-styles.js +1 -1
- package/dist/esm/pptx/slide-parser.js +6 -4
- package/dist/esm/pptx/sp-helpers.js +1 -1
- package/dist/esm/pptx/table-style.js +2 -1
- package/dist/esm/svg/svg-writer.js +1 -1
- package/dist/esm/word/doc/doc-reader.js +2 -2
- package/dist/esm/word/doc/doc-text.js +20 -22
- package/dist/esm/word/document-parser.js +27 -22
- package/dist/esm/word/docx-reader.js +15 -10
- package/dist/esm/word/docx-to-pdf.js +6 -6
- package/dist/esm/word/docx-writer.js +10 -6
- package/dist/esm/word/drawing-parser.js +18 -18
- package/dist/esm/word/font-table.js +90 -11
- package/dist/esm/word/index.js +6 -5
- package/dist/esm/word/numbering-parser.js +5 -4
- package/dist/esm/word/omml-parser.js +1 -1
- package/dist/esm/word/paragraph-properties.js +11 -10
- package/dist/esm/word/run-properties.js +4 -4
- package/dist/esm/word/settings-parser.js +2 -1
- package/dist/esm/word/shading.js +1 -1
- package/dist/esm/word/styles-parser.js +5 -4
- package/dist/esm/word/table-parser.js +4 -4
- package/dist/esm/word/theme-fonts.js +1 -1
- package/package.json +10 -4
|
@@ -1,8 +1,26 @@
|
|
|
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";
|
|
5
5
|
var TITLE_COLOR = "404040";
|
|
6
|
+
/**
|
|
7
|
+
* The face one role of the chart's text is set in: the chart's own
|
|
8
|
+
* ({@link Chart.text}) where the reader resolved it, and otherwise the sizes
|
|
9
|
+
* and greys this module has always drawn in, in the document's face.
|
|
10
|
+
*/
|
|
11
|
+
function faceOf(chart, role) {
|
|
12
|
+
const own = chart.text?.[role];
|
|
13
|
+
const title = role === "title";
|
|
14
|
+
return {
|
|
15
|
+
sizePt: own?.sizePt ?? (title ? 13 : 9),
|
|
16
|
+
colorHex: own?.colorHex ?? (title ? TITLE_COLOR : LABEL_COLOR),
|
|
17
|
+
...own?.family ? { family: own.family } : {},
|
|
18
|
+
...own?.bold ? { bold: true } : {},
|
|
19
|
+
...own?.italic ? { italic: true } : {}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** The width of `text` set in `face`. */
|
|
23
|
+
var widthIn = (measure, text, face) => measure(text, face.sizePt, face);
|
|
6
24
|
/** The Office accent cycle (RRGGBB) for series without an explicit colour. */
|
|
7
25
|
var SERIES_COLORS = [
|
|
8
26
|
"4472C4",
|
|
@@ -25,22 +43,25 @@ var SERIES_COLORS = [
|
|
|
25
43
|
var seriesColor = (s, i, cycle) => s.colorHex ?? (cycle && cycle.length > 0 ? cycle[i % cycle.length] : SERIES_COLORS[i % SERIES_COLORS.length]);
|
|
26
44
|
/**
|
|
27
45
|
* The scale Excel gives a value axis it is left to choose, measured against
|
|
28
|
-
* Excel's own PDF of
|
|
46
|
+
* Excel's own PDF of its column charts (2026-10-01 and -02):
|
|
29
47
|
*
|
|
30
48
|
* - the ends: values all of one sign put the axis's near end at zero —
|
|
31
49
|
* unless their spread is under a sixth of the largest of them, when it
|
|
32
50
|
* stops half a spread short of the nearest (93…97 runs from 91) — and the
|
|
33
|
-
* far end
|
|
34
|
-
*
|
|
51
|
+
* far end takes 5% of its reach from that near end before it is rounded
|
|
52
|
+
* out to the step. 70…96 reaches 100.8 and runs 0…120 by 20, where 5% of
|
|
53
|
+
* the data's own spread stopped at 97.3 and ran it 0…100 by 10. Both ends
|
|
54
|
+
* of a range across zero take 5% of the spread;
|
|
35
55
|
* - the step: the smallest 1, 2 or 5 × 10ⁿ that leaves at most ten intervals
|
|
36
56
|
* (`maxIntervals`), whatever the chart's height — 3 750 runs 0…4 000 by 500
|
|
37
57
|
* on a 150pt chart as on a 400pt one. Only an axis too short to hold the
|
|
38
58
|
* labels asks for fewer, and the caller says so.
|
|
39
59
|
*
|
|
40
|
-
* Every case of the
|
|
60
|
+
* Every case of the probes lands where Excel puts it: −1…1.2 on −1.5…1.5 by
|
|
41
61
|
* 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
|
|
62
|
+
* 0…0.05 by 0.005, 1E-150…3E-150 on 0…3.5E-150. Heckbert's nice numbers with
|
|
63
|
+
* a budget of ticks by height stepped the budget chart by 1 000 and started
|
|
64
|
+
* 93…97 at zero.
|
|
44
65
|
*
|
|
45
66
|
* @param dataMin The smallest value to cover.
|
|
46
67
|
* @param dataMax The largest value to cover.
|
|
@@ -48,78 +69,214 @@ var seriesColor = (s, i, cycle) => s.colorHex ?? (cycle && cycle.length > 0 ? cy
|
|
|
48
69
|
* @returns The rounded min/max and tick step.
|
|
49
70
|
*/
|
|
50
71
|
function niceScale(dataMin, dataMax, maxIntervals = 10) {
|
|
72
|
+
const end = paddedEnds(dataMin, dataMax);
|
|
73
|
+
const step = stepFor(end.lo, end.hi, maxIntervals);
|
|
74
|
+
return {
|
|
75
|
+
min: below(end.lo, step),
|
|
76
|
+
max: above(end.hi, step),
|
|
77
|
+
step
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The ends {@link niceScale} rounds out to its step: the data's, padded as
|
|
82
|
+
* Excel pads them — by `pad` of their reach, 5%, which a 3-D chart's axis
|
|
83
|
+
* does without: Excel's PDF runs 3-D columns of 70…96 on 0…100 by 10 and of
|
|
84
|
+
* −5…15 on −6…16 by 2, where flat ones run 0…120 by 20 and −10…20 by 5
|
|
85
|
+
* (2026-10-02). Never past the largest double, which the padding of values
|
|
86
|
+
* out at ±1.7E+308 would run them to.
|
|
87
|
+
*/
|
|
88
|
+
function paddedEnds(dataMin, dataMax, pad = .05) {
|
|
51
89
|
let lo = Math.min(dataMin, dataMax);
|
|
52
90
|
let hi = Math.max(dataMin, dataMax);
|
|
53
|
-
if (lo === hi)
|
|
54
|
-
|
|
55
|
-
|
|
91
|
+
if (lo === hi) {
|
|
92
|
+
if (hi > 0) lo = 0;
|
|
93
|
+
else if (lo < 0) hi = 0;
|
|
94
|
+
else hi = 1;
|
|
95
|
+
}
|
|
56
96
|
const spread = hi - lo;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
97
|
+
if (lo >= 0) {
|
|
98
|
+
const from = spread < hi / 6 ? lo - spread / 2 : 0;
|
|
99
|
+
return {
|
|
100
|
+
lo: from,
|
|
101
|
+
hi: finite(hi + (hi - from) * pad)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (hi <= 0) {
|
|
105
|
+
const to = spread < -lo / 6 ? hi + spread / 2 : 0;
|
|
106
|
+
return {
|
|
107
|
+
lo: finite(lo - (to - lo) * pad),
|
|
108
|
+
hi: to
|
|
109
|
+
};
|
|
110
|
+
}
|
|
64
111
|
return {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
step
|
|
112
|
+
lo: finite(lo - spread * pad),
|
|
113
|
+
hi: finite(hi + spread * pad)
|
|
68
114
|
};
|
|
69
115
|
}
|
|
70
|
-
/**
|
|
71
|
-
|
|
116
|
+
/** `v`, or the largest double of its sign where it ran past it. */
|
|
117
|
+
var finite = (v) => Math.min(Number.MAX_VALUE, Math.max(-Number.MAX_VALUE, v));
|
|
118
|
+
/** `v` rounded down to a multiple of `step`, short of the largest double. */
|
|
119
|
+
var below = (v, step) => finite(Math.floor(v / step + 1e-9) * step);
|
|
120
|
+
/** `v` rounded up to a multiple of `step`, short of the largest double. */
|
|
121
|
+
var above = (v, step) => finite(Math.ceil(v / step - 1e-9) * step);
|
|
122
|
+
/**
|
|
123
|
+
* The smallest 1, 2 or 5 × 10ⁿ that cuts `lo`…`hi` into at most
|
|
124
|
+
* `maxIntervals` steps. The share of the span each interval takes is reckoned
|
|
125
|
+
* so that a span past the largest double has one: −1.7E+308…1.7E+308 spans
|
|
126
|
+
* Infinity, and the search for its step never ended.
|
|
127
|
+
*/
|
|
128
|
+
function stepFor(lo, hi, maxIntervals) {
|
|
72
129
|
const most = Math.max(1, maxIntervals);
|
|
73
|
-
|
|
130
|
+
const span = hi - lo;
|
|
131
|
+
const share = Math.abs(Number.isFinite(span) ? span / most : hi / most - lo / most);
|
|
132
|
+
if (!(share > 0 && share < Infinity)) return 1;
|
|
133
|
+
let base = Math.max(Number.MIN_VALUE, 10 ** Math.floor(Math.log10(share)));
|
|
74
134
|
for (;;) {
|
|
75
135
|
for (const m of [
|
|
76
136
|
1,
|
|
77
137
|
2,
|
|
78
138
|
5
|
|
79
|
-
]) if (
|
|
139
|
+
]) if (share <= m * base * (1 + 1e-9)) return Math.min(m * base, Number.MAX_VALUE);
|
|
80
140
|
base *= 10;
|
|
81
141
|
}
|
|
82
142
|
}
|
|
143
|
+
/** How many steps lie between a scale's ends — in halves where the span overflows. */
|
|
144
|
+
function intervalsOf(s) {
|
|
145
|
+
const span = s.max - s.min;
|
|
146
|
+
return Number.isFinite(span) ? span / s.step : (s.max / 2 - s.min / 2) / (s.step / 2);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* How far along `s` the value `v` stands: 0 at its min, 1 at its max —
|
|
150
|
+
* reckoned in halves where a difference overflows, as it does between ends
|
|
151
|
+
* out at ±1.7E+308.
|
|
152
|
+
*/
|
|
153
|
+
function fractionOf(v, s) {
|
|
154
|
+
const span = s.max - s.min;
|
|
155
|
+
const from = v - s.min;
|
|
156
|
+
return Number.isFinite(span) && Number.isFinite(from) ? from / span : (v / 2 - s.min / 2) / (s.max / 2 - s.min / 2);
|
|
157
|
+
}
|
|
83
158
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
159
|
+
* The most intervals Excel cuts a value axis into. A finer step than that is
|
|
160
|
+
* widened to a 500th of the axis: its own PDF ran 0…26.25 by 0.0525 for steps
|
|
161
|
+
* of 0.05, 0.01 and 0.001 alike, and kept 0.0525 itself (2026-10-02).
|
|
162
|
+
*/
|
|
163
|
+
var MOST_INTERVALS = 500;
|
|
164
|
+
/** The most characters Excel gives a value-axis label under General, its sign among them. */
|
|
165
|
+
var AXIS_LABEL_CHARS = 9;
|
|
166
|
+
/**
|
|
167
|
+
* A value-axis tick as Excel labels it under General. An axis is not a cell:
|
|
168
|
+
* its General gives every label nine characters, the sign among them, on an
|
|
169
|
+
* axis 150pt or 500pt long, upright or lying alike — Excel's own PDF of 44
|
|
170
|
+
* axes (2026-10-01 and -02). Within them it is the cells' rule
|
|
171
|
+
* ({@link generalRenderings}): the number as it is where it fits (0.25 — we
|
|
172
|
+
* wrote 0.3 on an axis stepped by 0.25 — 500000000, −50000000, 0.0000003),
|
|
173
|
+
* else the finest of it rounded or in scientific notation (0.1234568,
|
|
174
|
+
* −0.123457, 123456771, 1E+09, 1.235E+09, −1.23E+09, 1.235E-05, 5E-151).
|
|
175
|
+
* Places counted from the step and written by `toFixed` threw past a hundred
|
|
176
|
+
* of them, which a step of 1E-150 asked for.
|
|
86
177
|
*
|
|
87
|
-
* @param v
|
|
88
|
-
* @param step The tick spacing (from {@link niceScale}).
|
|
178
|
+
* @param v The tick value.
|
|
89
179
|
* @returns The label text.
|
|
90
180
|
*/
|
|
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;
|
|
181
|
+
function formatTick(v) {
|
|
182
|
+
return generalRenderings(v, AXIS_LABEL_CHARS, true).next().value ?? String(v);
|
|
96
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* The values a scale labels: its min and every step up to its max. Counted,
|
|
186
|
+
* not summed: the sum drifted, and its tolerance was absolute, so a scale of
|
|
187
|
+
* 1E-150 ran on until it passed 1E-9 and asked for more ticks than an array
|
|
188
|
+
* holds. Never more than Excel's most intervals and one past them — rounding
|
|
189
|
+
* the ends out to a widened step can add that one.
|
|
190
|
+
*/
|
|
97
191
|
var ticks = (s) => {
|
|
192
|
+
const count = Math.min(Math.floor(intervalsOf(s) + 1e-9), 501);
|
|
98
193
|
const out = [];
|
|
99
|
-
for (let
|
|
194
|
+
for (let i = 0; i <= count; i++) {
|
|
195
|
+
const sum = s.min + i * s.step;
|
|
196
|
+
const v = Number.isFinite(sum) ? sum : 2 * (s.min / 2 + i * (s.step / 2));
|
|
197
|
+
out.push(Math.abs(v) < s.step * 1e-9 ? 0 : v);
|
|
198
|
+
}
|
|
100
199
|
return out;
|
|
101
200
|
};
|
|
102
201
|
/**
|
|
103
202
|
* 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
|
-
*
|
|
203
|
+
* them (§21.2.2.157 `c:scaling/c:min|c:max`), the data's, padded as
|
|
204
|
+
* {@link niceScale} pads them, where they did not. A fixed end is exact —
|
|
205
|
+
* nice-rounding it would move a number the author chose.
|
|
107
206
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
207
|
+
* The step is the author's (§21.2.2.103 `c:majorUnit`), else the 1-2-5 step
|
|
208
|
+
* of the span between the ends — with both fixed, of the span they fix:
|
|
209
|
+
* −1E+300…1E+300 runs by 2E+299 in Excel, where padding the ends first
|
|
210
|
+
* stepped it by 5E+299. The automatic ends round out to an author's step
|
|
211
|
+
* from where the padding left them, not from where a step of our own would
|
|
212
|
+
* have: 10…25 by 0.1 runs 0…26.3, as in Excel, not 0…30. And no step cuts
|
|
213
|
+
* the axis finer than {@link MOST_INTERVALS}: Excel widens one that would to
|
|
214
|
+
* a 500th of the span and rounds the automatic ends out to the widened step
|
|
215
|
+
* (−10…25 by 0.01 runs −11.781…26.796 by 0.077). A step of 0.00001 asked for
|
|
216
|
+
* three million labels and ran out of stack.
|
|
217
|
+
*
|
|
218
|
+
* @param chart The chart (for its fixed ends and step).
|
|
219
|
+
* @param dataMin The smallest value to cover.
|
|
220
|
+
* @param dataMax The largest value to cover.
|
|
221
|
+
* @param extentPt The axis's length — how many ticks fit is a question
|
|
222
|
+
* about the plot, not about the numbers.
|
|
223
|
+
* @param horizontal Whether the axis lies along the foot.
|
|
224
|
+
* @param pad How far past the data the automatic ends reach, as a
|
|
225
|
+
* share of its reach ({@link paddedEnds}).
|
|
113
226
|
* @returns The axis min/max and tick step.
|
|
114
227
|
*/
|
|
115
|
-
function axisScale(chart, dataMin, dataMax, extentPt, horizontal = false) {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
228
|
+
function axisScale(chart, dataMin, dataMax, extentPt, horizontal = false, pad = .05) {
|
|
229
|
+
const fixedMin = chart.valAxisMin;
|
|
230
|
+
const fixedMax = chart.valAxisMax;
|
|
231
|
+
const padded = paddedEnds(fixedMin ?? dataMin, fixedMax ?? dataMax, pad);
|
|
232
|
+
const lo = fixedMin ?? padded.lo;
|
|
233
|
+
const hi = fixedMax ?? padded.hi;
|
|
234
|
+
const rounded = (step) => ({
|
|
235
|
+
min: fixedMin ?? below(lo, step),
|
|
236
|
+
max: fixedMax ?? above(hi, step),
|
|
237
|
+
step
|
|
238
|
+
});
|
|
239
|
+
const scale = rounded(chart.valAxisMajorUnit ?? stepFor(lo, hi, intervalsThatFit(extentPt, horizontal)));
|
|
240
|
+
if (intervalsOf(scale) <= MOST_INTERVALS) return scale;
|
|
241
|
+
const span = hi - lo;
|
|
242
|
+
return rounded(Number.isFinite(span) ? span / MOST_INTERVALS : hi / MOST_INTERVALS - lo / MOST_INTERVALS);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* §21.2.2.104 — the plot's own rectangle where its author sized it (an
|
|
246
|
+
* `inner` manual layout), in the scene's y-up frame: the axes' labels and
|
|
247
|
+
* titles stand outside it, where the chart sets them.
|
|
248
|
+
*/
|
|
249
|
+
function innerPlot(chart, wPt, hPt) {
|
|
250
|
+
const box = chart.plotBox;
|
|
251
|
+
if (!box?.inner) return void 0;
|
|
118
252
|
return {
|
|
119
|
-
|
|
120
|
-
|
|
253
|
+
x0: box.x * wPt,
|
|
254
|
+
y0: hPt - (box.y + box.h) * hPt,
|
|
255
|
+
plotW: Math.max(1, box.w * wPt),
|
|
256
|
+
plotH: Math.max(1, box.h * hPt)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* A scatter axis's {@link Scale}: {@link niceScale}'s where its author fixed
|
|
261
|
+
* nothing, and where they did, their ends exactly and their step, the ends
|
|
262
|
+
* they left to the data rounded out to it — never cut into more than
|
|
263
|
+
* {@link MOST_INTERVALS}, as a frame chart's value axis is not.
|
|
264
|
+
*/
|
|
265
|
+
function scatterScale(lo, hi, maxIntervals, fix) {
|
|
266
|
+
if (fix.min === void 0 && fix.max === void 0 && fix.unit === void 0) return niceScale(lo, hi, maxIntervals);
|
|
267
|
+
const auto = niceScale(fix.min ?? lo, fix.max ?? hi, maxIntervals);
|
|
268
|
+
const step = fix.unit ?? (fix.min !== void 0 && fix.max !== void 0 ? stepFor(fix.min, fix.max, maxIntervals) : auto.step);
|
|
269
|
+
const scale = {
|
|
270
|
+
min: fix.min ?? below(auto.min, step),
|
|
271
|
+
max: fix.max ?? above(auto.max, step),
|
|
121
272
|
step
|
|
122
273
|
};
|
|
274
|
+
if (intervalsOf(scale) <= MOST_INTERVALS) return scale;
|
|
275
|
+
const span = scale.max - scale.min;
|
|
276
|
+
return {
|
|
277
|
+
...scale,
|
|
278
|
+
step: Number.isFinite(span) ? span / MOST_INTERVALS : scale.max / MOST_INTERVALS - scale.min / MOST_INTERVALS
|
|
279
|
+
};
|
|
123
280
|
}
|
|
124
281
|
/**
|
|
125
282
|
* The chart title's lines. A title longer than the chart is wide wraps at its
|
|
@@ -135,12 +292,13 @@ function axisScale(chart, dataMin, dataMax, extentPt, horizontal = false) {
|
|
|
135
292
|
*/
|
|
136
293
|
function titleLines(chart, wPt, measure) {
|
|
137
294
|
if (!chart.title) return [];
|
|
295
|
+
const face = faceOf(chart, "title");
|
|
138
296
|
const room = wPt * .8;
|
|
139
297
|
const lines = [];
|
|
140
298
|
let line = "";
|
|
141
299
|
for (const word of chart.title.split(/\s+/).filter((w) => w.length > 0)) {
|
|
142
300
|
const longer = line ? `${line} ${word}` : word;
|
|
143
|
-
if (line && measure
|
|
301
|
+
if (line && widthIn(measure, longer, face) > room) {
|
|
144
302
|
lines.push(line);
|
|
145
303
|
line = word;
|
|
146
304
|
} else line = longer;
|
|
@@ -149,15 +307,15 @@ function titleLines(chart, wPt, measure) {
|
|
|
149
307
|
return lines;
|
|
150
308
|
}
|
|
151
309
|
/** The band a title of `lines` takes at the top of the chart. */
|
|
152
|
-
var titleHeight = (lines) => lines.length === 0 ? 0 :
|
|
153
|
-
function pushChartTitle(labels, lines, wPt, hPt) {
|
|
310
|
+
var titleHeight = (lines, chart) => lines.length === 0 ? 0 : faceOf(chart, "title").sizePt * (1.6 + 1.2 * (lines.length - 1));
|
|
311
|
+
function pushChartTitle(labels, lines, wPt, hPt, chart) {
|
|
312
|
+
const face = faceOf(chart, "title");
|
|
154
313
|
lines.forEach((text, i) => {
|
|
155
314
|
labels.push({
|
|
156
315
|
text,
|
|
157
316
|
x: wPt / 2,
|
|
158
|
-
y: hPt - 4 -
|
|
159
|
-
|
|
160
|
-
colorHex: TITLE_COLOR,
|
|
317
|
+
y: hPt - 4 - face.sizePt * (1 + 1.2 * i),
|
|
318
|
+
...face,
|
|
161
319
|
align: "center"
|
|
162
320
|
});
|
|
163
321
|
});
|
|
@@ -169,37 +327,37 @@ function buildLegendBlock(chart, wPt, hPt, measure) {
|
|
|
169
327
|
...s.line?.none ? { marker: "box" } : isLineLike(s) ? { marker: "line" } : {}
|
|
170
328
|
}));
|
|
171
329
|
const marker = chart.type === "line" || chart.type === "scatter" ? "line" : "box";
|
|
172
|
-
return layoutLegend(legendEntries, chart.hasLegend, chart.legendPos ?? "b", wPt, hPt, measure, marker);
|
|
330
|
+
return layoutLegend(legendEntries, chart.hasLegend, chart.legendPos ?? "b", wPt, hPt, measure, faceOf(chart, "legend"), marker);
|
|
173
331
|
}
|
|
174
|
-
function pushGridTicks(gridlines, labels, tickVals, fmt, axis, at, x0, y0, plotW, plotH, grid, atEnd = false) {
|
|
332
|
+
function pushGridTicks(gridlines, labels, tickVals, fmt, axis, at, x0, y0, plotW, plotH, grid, face, atEnd = false, labelled = true) {
|
|
333
|
+
const rule = grid?.none ? void 0 : {
|
|
334
|
+
strokeHex: grid?.colorHex ?? GRID_COLOR,
|
|
335
|
+
widthPt: grid?.widthPt ?? .75
|
|
336
|
+
};
|
|
175
337
|
for (const v of tickVals) if (axis === "x") {
|
|
176
338
|
const gx = at(v);
|
|
177
|
-
gridlines.push({
|
|
339
|
+
if (rule) gridlines.push({
|
|
178
340
|
points: [[gx, y0], [gx, y0 + plotH]],
|
|
179
|
-
|
|
180
|
-
widthPt: .75
|
|
341
|
+
...rule
|
|
181
342
|
});
|
|
182
|
-
labels.push({
|
|
343
|
+
if (labelled) labels.push({
|
|
183
344
|
text: fmt(v),
|
|
184
345
|
x: gx,
|
|
185
|
-
y: atEnd ? y0 + plotH +
|
|
186
|
-
|
|
187
|
-
colorHex: LABEL_COLOR,
|
|
346
|
+
y: atEnd ? y0 + plotH + face.sizePt * .4 : y0 - face.sizePt,
|
|
347
|
+
...face,
|
|
188
348
|
align: "center"
|
|
189
349
|
});
|
|
190
350
|
} else {
|
|
191
351
|
const gy = at(v);
|
|
192
|
-
gridlines.push({
|
|
352
|
+
if (rule) gridlines.push({
|
|
193
353
|
points: [[x0, gy], [x0 + plotW, gy]],
|
|
194
|
-
|
|
195
|
-
widthPt: .75
|
|
354
|
+
...rule
|
|
196
355
|
});
|
|
197
|
-
labels.push({
|
|
356
|
+
if (labelled) labels.push({
|
|
198
357
|
text: fmt(v),
|
|
199
358
|
x: atEnd ? x0 + plotW + 3 : x0 - 3,
|
|
200
|
-
y: gy -
|
|
201
|
-
|
|
202
|
-
colorHex: LABEL_COLOR,
|
|
359
|
+
y: gy - face.sizePt / 3,
|
|
360
|
+
...face,
|
|
203
361
|
align: atEnd ? "left" : "right"
|
|
204
362
|
});
|
|
205
363
|
}
|
|
@@ -223,12 +381,16 @@ function axisStroke(style) {
|
|
|
223
381
|
* the value axis — `cross.y` up a column chart, `cross.x` along a bar chart —
|
|
224
382
|
* and the other stands at the plot's edge.
|
|
225
383
|
*/
|
|
226
|
-
function pushAxisLines(polylines, x0, y0, plotW, plotH, chart, cross = {}) {
|
|
384
|
+
function pushAxisLines(polylines, x0, y0, plotW, plotH, chart, cross = {}, shift = {}) {
|
|
385
|
+
const up = shift.upright ?? {
|
|
386
|
+
dx: 0,
|
|
387
|
+
dy: 0
|
|
388
|
+
};
|
|
227
389
|
const val = axisStroke(chart.valAxisLine);
|
|
228
|
-
const x = x0 + (cross.x ?? 0);
|
|
390
|
+
const x = x0 + (cross.x ?? 0) + up.dx;
|
|
229
391
|
const y = y0 + (cross.y ?? 0);
|
|
230
392
|
if (val) polylines.push({
|
|
231
|
-
points: [[x, y0], [x, y0 + plotH]],
|
|
393
|
+
points: [[x, y0 + up.dy], [x, y0 + plotH + up.dy]],
|
|
232
394
|
strokeHex: val.hex,
|
|
233
395
|
widthPt: val.widthPt
|
|
234
396
|
});
|
|
@@ -239,56 +401,197 @@ function pushAxisLines(polylines, x0, y0, plotW, plotH, chart, cross = {}) {
|
|
|
239
401
|
widthPt: cat.widthPt
|
|
240
402
|
});
|
|
241
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Where a 3-D box's back stands from its front. Each point of depth moves
|
|
406
|
+
* `sin rotY` across and `sin rotX` up — Excel's PDF of its own 3-D columns,
|
|
407
|
+
* at four views and three depths (2026-10-02) — and the box is as deep as a
|
|
408
|
+
* share of one category slot, the slot a share of the front plane the depth
|
|
409
|
+
* leaves: solved for, so box and front fill the plot between them. Never so
|
|
410
|
+
* deep that the front plane is left with no height (or width) across.
|
|
411
|
+
*
|
|
412
|
+
* @returns The offset from the front plane's corner to the back wall's.
|
|
413
|
+
*/
|
|
414
|
+
function boxDepth(d, boxW, boxH, nCats, horizontal) {
|
|
415
|
+
const k = d.perSlot / Math.max(1, nCats);
|
|
416
|
+
const along = horizontal ? boxH / (1 + k * Math.abs(d.sinX)) : boxW / (1 + k * Math.abs(d.sinY));
|
|
417
|
+
const across = Math.abs(horizontal ? d.sinY : d.sinX);
|
|
418
|
+
const room = (horizontal ? boxW : boxH) * .9;
|
|
419
|
+
const t = across > 0 ? Math.min(k * along, room / across) : k * along;
|
|
420
|
+
return {
|
|
421
|
+
dx: t * d.sinY,
|
|
422
|
+
dy: t * d.sinX
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/** The light grey Excel rules a 3-D chart's floor in where it states none. */
|
|
426
|
+
var FLOOR_RULE = {
|
|
427
|
+
colorHex: GRID_COLOR,
|
|
428
|
+
widthPt: .75
|
|
429
|
+
};
|
|
430
|
+
/**
|
|
431
|
+
* §21.2.2.69/§21.2.2.11/§21.2.2.191 — a 3-D box's floor, back wall and side
|
|
432
|
+
* wall, as the chart fills and rules them; the floor ruled in light grey
|
|
433
|
+
* unless the chart says otherwise, the walls drawn only as stated. Which wall
|
|
434
|
+
* is which is as Excel's PDF draws them (2026-10-02): columns stand on the
|
|
435
|
+
* bottom, which a view from below does not show, and their side wall is the
|
|
436
|
+
* one the view looks into — the left, turned right; bars stand on the side
|
|
437
|
+
* the view looks into — the left, turned right — and their side wall is the
|
|
438
|
+
* bottom, whatever the view.
|
|
439
|
+
*/
|
|
440
|
+
function pushBoxWalls(walls, chart, f, d, horizontal) {
|
|
441
|
+
const back = sideOf(f, d, "back");
|
|
442
|
+
const turnedRight = d.dx >= 0;
|
|
443
|
+
const floor = horizontal ? sideOf(f, d, turnedRight ? "left" : "right") : d.dy >= 0 ? sideOf(f, d, "bottom") : void 0;
|
|
444
|
+
const side = horizontal ? sideOf(f, d, "bottom") : sideOf(f, d, turnedRight ? "left" : "right");
|
|
445
|
+
const surface = (points, fillHex, rule) => {
|
|
446
|
+
const stroke = rule && !rule.none && rule.colorHex ? rule : void 0;
|
|
447
|
+
if (!points || !fillHex && !stroke) return;
|
|
448
|
+
walls.push({
|
|
449
|
+
points,
|
|
450
|
+
...fillHex ? { fillHex } : {},
|
|
451
|
+
...stroke ? {
|
|
452
|
+
strokeHex: stroke.colorHex,
|
|
453
|
+
widthPt: stroke.widthPt ?? .75
|
|
454
|
+
} : {}
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
surface(back, chart.backWall?.fillHex, chart.backWall?.line);
|
|
458
|
+
surface(side, chart.sideWall?.fillHex, chart.sideWall?.line);
|
|
459
|
+
surface(floor, chart.floor?.fillHex, chart.floor ? chart.floor.line : FLOOR_RULE);
|
|
460
|
+
}
|
|
461
|
+
/** One face of a 3-D box: its back wall, or the wall along one side of its front. */
|
|
462
|
+
function sideOf(f, d, which) {
|
|
463
|
+
const { x0, y0, plotW, plotH } = f;
|
|
464
|
+
if (which === "back") return [
|
|
465
|
+
[x0 + d.dx, y0 + d.dy],
|
|
466
|
+
[x0 + plotW + d.dx, y0 + d.dy],
|
|
467
|
+
[x0 + plotW + d.dx, y0 + plotH + d.dy],
|
|
468
|
+
[x0 + d.dx, y0 + plotH + d.dy]
|
|
469
|
+
];
|
|
470
|
+
if (which === "bottom") return [
|
|
471
|
+
[x0, y0],
|
|
472
|
+
[x0 + plotW, y0],
|
|
473
|
+
[x0 + plotW + d.dx, y0 + d.dy],
|
|
474
|
+
[x0 + d.dx, y0 + d.dy]
|
|
475
|
+
];
|
|
476
|
+
const x = which === "left" ? x0 : x0 + plotW;
|
|
477
|
+
return [
|
|
478
|
+
[x, y0],
|
|
479
|
+
[x + d.dx, y0 + d.dy],
|
|
480
|
+
[x + d.dx, y0 + plotH + d.dy],
|
|
481
|
+
[x, y0 + plotH]
|
|
482
|
+
];
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* A 3-D box's major gridlines: each value's line across the side wall from
|
|
486
|
+
* the front plane and on along the back wall, as Excel rules them — a bar
|
|
487
|
+
* chart's across its bottom whatever the view.
|
|
488
|
+
*
|
|
489
|
+
* @param offsets Each tick's distance along the value axis from its start.
|
|
490
|
+
*/
|
|
491
|
+
function pushBoxGridlines(gridlines, grid, offsets, f, d, horizontal) {
|
|
492
|
+
if (grid?.none) return;
|
|
493
|
+
const rule = {
|
|
494
|
+
strokeHex: grid?.colorHex ?? GRID_COLOR,
|
|
495
|
+
widthPt: grid?.widthPt ?? .75
|
|
496
|
+
};
|
|
497
|
+
const { x0, y0, plotW, plotH } = f;
|
|
498
|
+
for (const off of offsets) if (horizontal) {
|
|
499
|
+
const gx = x0 + off;
|
|
500
|
+
gridlines.push({
|
|
501
|
+
points: [
|
|
502
|
+
[gx, y0],
|
|
503
|
+
[gx + d.dx, y0 + d.dy],
|
|
504
|
+
[gx + d.dx, y0 + plotH + d.dy]
|
|
505
|
+
],
|
|
506
|
+
...rule
|
|
507
|
+
});
|
|
508
|
+
} else {
|
|
509
|
+
const gy = y0 + off;
|
|
510
|
+
gridlines.push({
|
|
511
|
+
points: d.dx >= 0 ? [
|
|
512
|
+
[x0, gy],
|
|
513
|
+
[x0 + d.dx, gy + d.dy],
|
|
514
|
+
[x0 + plotW + d.dx, gy + d.dy]
|
|
515
|
+
] : [
|
|
516
|
+
[x0 + d.dx, gy + d.dy],
|
|
517
|
+
[x0 + plotW + d.dx, gy + d.dy],
|
|
518
|
+
[x0 + plotW, gy]
|
|
519
|
+
],
|
|
520
|
+
...rule
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
242
524
|
function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
243
525
|
const rects = [];
|
|
244
526
|
const polylines = [];
|
|
245
527
|
const gridlines = [];
|
|
246
528
|
const labels = [];
|
|
247
|
-
const nCats =
|
|
529
|
+
const nCats = catCount(chart);
|
|
248
530
|
const onSecondary = chart.series.filter((s) => s.secondaryAxis);
|
|
249
531
|
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
|
|
532
|
+
const [dataMin, dataMax] = opts.dataRange ?? extentOf(allVals) ?? [0, 1];
|
|
251
533
|
const scale = opts.exactRange ? {
|
|
252
534
|
min: dataMin,
|
|
253
535
|
max: dataMax,
|
|
254
536
|
step: niceScale(dataMin, dataMax, intervalsThatFit(horizontal ? wPt : hPt, horizontal)).step
|
|
255
|
-
} : axisScale(chart, dataMin, dataMax, horizontal ? wPt : hPt, horizontal);
|
|
256
|
-
const fmtVal = opts.formatValue ??
|
|
537
|
+
} : axisScale(chart, dataMin, dataMax, horizontal ? wPt : hPt, horizontal, opts.unpadded ? 0 : .05);
|
|
538
|
+
const fmtVal = opts.formatValue ?? formatTick;
|
|
257
539
|
const tickVals = ticks(scale);
|
|
258
|
-
const
|
|
259
|
-
const scale2 =
|
|
540
|
+
const range2 = extentOf(onSecondary.flatMap((s) => s.values.slice(0, nCats)));
|
|
541
|
+
const scale2 = range2 ? niceScale(range2[0], range2[1], intervalsThatFit(hPt, false)) : void 0;
|
|
260
542
|
const tickVals2 = scale2 ? ticks(scale2) : [];
|
|
261
543
|
const crosses = chart.catAxisCrosses;
|
|
262
544
|
const crossValue = Math.min(Math.max(crosses === "min" ? scale.min : crosses === "max" ? scale.max : crosses ?? 0, scale.min), scale.max);
|
|
263
|
-
const
|
|
545
|
+
const fromBelow = (opts.depth?.sinX ?? 0) < 0;
|
|
546
|
+
const labelsAt = chart.catAxisDeleted === true || chart.catTickLabelPos === "none" || fromBelow && !horizontal ? "none" : chart.catTickLabelPos === "low" ? "start" : chart.catTickLabelPos === "high" ? "end" : crossValue <= scale.min ? "start" : crossValue >= scale.max ? "end" : "cross";
|
|
264
547
|
const catText = (c) => chart.categories[c] ?? String(c + 1);
|
|
265
|
-
const
|
|
266
|
-
const
|
|
548
|
+
const catFace = faceOf(chart, "catAxis");
|
|
549
|
+
const valFace = faceOf(chart, "valAxis");
|
|
550
|
+
const val2Face = faceOf(chart, "secondaryValAxis");
|
|
551
|
+
let widestCat = 0;
|
|
552
|
+
for (let c = 0; c < nCats; c++) widestCat = Math.max(widestCat, widthIn(measure, catText(c), catFace));
|
|
553
|
+
const catBand = catFace.sizePt * 1.6;
|
|
267
554
|
const groupLevels = horizontal ? [] : chart.categoryGroups ?? [];
|
|
268
555
|
const valAtEnd = chart.catAxisReversed === true ? chart.valAxisCrosses !== "max" : chart.valAxisCrosses === "max";
|
|
556
|
+
const valLabelled = chart.valAxisDeleted !== true && !(fromBelow && horizontal);
|
|
269
557
|
const title = titleLines(chart, wPt, measure);
|
|
270
|
-
const top = 4 + titleHeight(title) + (!horizontal && labelsAt === "end" ? catBand : 0) + (horizontal && valAtEnd ? catBand : 0);
|
|
558
|
+
const top = 4 + titleHeight(title, chart) + (!horizontal && labelsAt === "end" ? catBand : 0) + (horizontal && valAtEnd && valLabelled ? catBand : 0);
|
|
271
559
|
const legend = buildLegendBlock(chart, wPt, hPt, measure);
|
|
272
|
-
const tick2W = scale2 && !horizontal ? Math.max(0, ...tickVals2.map((v) => measure
|
|
273
|
-
const tickHalf = (v) => horizontal && v !== void 0 ? measure
|
|
560
|
+
const tick2W = scale2 && !horizontal ? Math.max(0, ...tickVals2.map((v) => widthIn(measure, formatTick(v), val2Face))) + 4 + (chart.secondaryValAxisTitle ? faceOf(chart, "secondaryValAxisTitle").sizePt * 1.5 : 0) : 0;
|
|
561
|
+
const tickHalf = (v) => horizontal && valLabelled && v !== void 0 ? widthIn(measure, fmtVal(v), valFace) / 2 : 0;
|
|
274
562
|
const lastTickHalf = tickHalf(tickVals[tickVals.length - 1]);
|
|
275
563
|
const catLabelW = Math.min(wPt * .4, widestCat + 6);
|
|
276
|
-
const tickLabelW = Math.max(0, ...tickVals.map((v) => measure
|
|
564
|
+
const tickLabelW = valLabelled ? Math.max(0, ...tickVals.map((v) => widthIn(measure, fmtVal(v), valFace))) + 4 : 0;
|
|
277
565
|
const plotRight = wPt - 4 - legend.rightWidth - tick2W - (horizontal && labelsAt === "end" ? catLabelW : lastTickHalf) - (!horizontal && valAtEnd ? tickLabelW : 0);
|
|
278
|
-
const axisTitleBand = 9 * 1.5;
|
|
279
566
|
const leftTitle = horizontal ? chart.catAxisTitle : chart.valAxisTitle;
|
|
280
567
|
const footTitle = horizontal ? chart.valAxisTitle : chart.catAxisTitle;
|
|
281
|
-
const
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
const
|
|
285
|
-
const
|
|
568
|
+
const leftTitleFace = faceOf(chart, horizontal ? "catAxisTitle" : "valAxisTitle");
|
|
569
|
+
const footTitleFace = faceOf(chart, horizontal ? "valAxisTitle" : "catAxisTitle");
|
|
570
|
+
const own = innerPlot(chart, wPt, hPt);
|
|
571
|
+
const boxX0 = own?.x0 ?? 4 + (leftTitle ? leftTitleFace.sizePt * 1.5 : 0) + (horizontal ? labelsAt === "start" ? catLabelW : tickHalf(tickVals[0]) : valAtEnd ? 0 : tickLabelW);
|
|
572
|
+
const boxY0 = own?.y0 ?? 4 + legend.bottomHeight + (footTitle ? footTitleFace.sizePt * 1.5 : 0) + ((horizontal ? !valAtEnd && valLabelled : labelsAt === "start") ? catBand : 0) + (!horizontal && labelsAt === "start" ? groupLevels.length * catBand : 0);
|
|
573
|
+
const boxW = own?.plotW ?? Math.max(1, plotRight - boxX0);
|
|
574
|
+
const boxH = own?.plotH ?? Math.max(1, hPt - top - boxY0);
|
|
575
|
+
const depth = opts.depth ? boxDepth(opts.depth, boxW, boxH, nCats, horizontal) : void 0;
|
|
576
|
+
const x0 = boxX0 + Math.max(0, -(depth?.dx ?? 0));
|
|
577
|
+
const y0 = boxY0 + Math.max(0, -(depth?.dy ?? 0));
|
|
578
|
+
const plotW = Math.max(1, boxW - Math.abs(depth?.dx ?? 0));
|
|
579
|
+
const plotH = Math.max(1, boxH - Math.abs(depth?.dy ?? 0));
|
|
580
|
+
const upright = depth && depth.dx < 0 ? depth : {
|
|
581
|
+
dx: 0,
|
|
582
|
+
dy: 0
|
|
583
|
+
};
|
|
584
|
+
const edge = horizontal ? {
|
|
585
|
+
dx: 0,
|
|
586
|
+
dy: 0
|
|
587
|
+
} : upright;
|
|
588
|
+
const valueOffset = (v) => fractionOf(v, scale) * (horizontal ? plotW : plotH);
|
|
286
589
|
const zeroOffset = valueOffset(crossValue);
|
|
287
590
|
const plotBackground = chart.plotFillHex ?? chart.plotLine?.colorHex ? {
|
|
288
|
-
x:
|
|
289
|
-
y:
|
|
290
|
-
w:
|
|
291
|
-
h:
|
|
591
|
+
x: boxX0,
|
|
592
|
+
y: boxY0,
|
|
593
|
+
w: boxW,
|
|
594
|
+
h: boxH,
|
|
292
595
|
...chart.plotFillHex ? { fillHex: chart.plotFillHex } : {},
|
|
293
596
|
...chart.plotLine?.colorHex ? {
|
|
294
597
|
strokeHex: chart.plotLine.colorHex,
|
|
@@ -296,13 +599,12 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
296
599
|
...chart.plotLine.dash ? { strokeDash: chart.plotLine.dash } : {}
|
|
297
600
|
} : {}
|
|
298
601
|
} : void 0;
|
|
299
|
-
pushChartTitle(labels, title, wPt, hPt);
|
|
602
|
+
pushChartTitle(labels, title, wPt, hPt, chart);
|
|
300
603
|
if (leftTitle) labels.push({
|
|
301
604
|
text: leftTitle,
|
|
302
|
-
x:
|
|
605
|
+
x: 4 + leftTitleFace.sizePt * .9,
|
|
303
606
|
y: y0 + plotH / 2,
|
|
304
|
-
|
|
305
|
-
colorHex: LABEL_COLOR,
|
|
607
|
+
...leftTitleFace,
|
|
306
608
|
align: "center",
|
|
307
609
|
rotationDeg: 90
|
|
308
610
|
});
|
|
@@ -310,20 +612,30 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
310
612
|
text: footTitle,
|
|
311
613
|
x: x0 + plotW / 2,
|
|
312
614
|
y: legend.bottomHeight + 2,
|
|
313
|
-
|
|
314
|
-
colorHex: LABEL_COLOR,
|
|
615
|
+
...footTitleFace,
|
|
315
616
|
align: "center"
|
|
316
617
|
});
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
618
|
+
const flatGrid = depth ? { none: true } : chart.gridLine;
|
|
619
|
+
if (horizontal) pushGridTicks(gridlines, labels, tickVals, fmtVal, "x", (v) => x0 + edge.dx + valueOffset(v), x0 + edge.dx, y0 + edge.dy, plotW, plotH, flatGrid, valFace, valAtEnd, valLabelled);
|
|
620
|
+
else pushGridTicks(gridlines, labels, tickVals, fmtVal, "y", (v) => y0 + edge.dy + valueOffset(v), x0 + edge.dx, y0 + edge.dy, plotW, plotH, flatGrid, valFace, valAtEnd, valLabelled);
|
|
621
|
+
const walls = [];
|
|
622
|
+
if (depth) {
|
|
623
|
+
const front = {
|
|
624
|
+
x0,
|
|
625
|
+
y0,
|
|
626
|
+
plotW,
|
|
627
|
+
plotH
|
|
628
|
+
};
|
|
629
|
+
pushBoxWalls(walls, chart, front, depth, horizontal);
|
|
630
|
+
pushBoxGridlines(gridlines, chart.gridLine, tickVals.map((v) => valueOffset(v)), front, depth, horizontal);
|
|
631
|
+
}
|
|
632
|
+
const valueOffset2 = scale2 && !horizontal ? (v) => fractionOf(v, scale2) * plotH : void 0;
|
|
320
633
|
if (scale2 && valueOffset2) {
|
|
321
634
|
for (const v of tickVals2) labels.push({
|
|
322
|
-
text: formatTick(v
|
|
635
|
+
text: formatTick(v),
|
|
323
636
|
x: x0 + plotW + 3,
|
|
324
|
-
y: y0 + valueOffset2(v) -
|
|
325
|
-
|
|
326
|
-
colorHex: LABEL_COLOR,
|
|
637
|
+
y: y0 + valueOffset2(v) - val2Face.sizePt / 3,
|
|
638
|
+
...val2Face,
|
|
327
639
|
align: "left"
|
|
328
640
|
});
|
|
329
641
|
const sec = axisStroke(chart.secondaryValAxisLine);
|
|
@@ -336,42 +648,41 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
336
648
|
text: chart.secondaryValAxisTitle,
|
|
337
649
|
x: wPt - 4,
|
|
338
650
|
y: y0 + plotH / 2,
|
|
339
|
-
|
|
340
|
-
colorHex: LABEL_COLOR,
|
|
651
|
+
...faceOf(chart, "secondaryValAxisTitle"),
|
|
341
652
|
align: "center",
|
|
342
653
|
rotationDeg: 90
|
|
343
654
|
});
|
|
344
655
|
}
|
|
345
656
|
const slot = (horizontal ? plotH : plotW) / nCats;
|
|
346
|
-
const need = horizontal ?
|
|
657
|
+
const need = horizontal ? catFace.sizePt * 1.4 : widestCat + 4;
|
|
347
658
|
const step = Math.max(1, Math.ceil(need / Math.max(slot, .01)));
|
|
348
659
|
const across = labelsAt === "start" ? 0 : labelsAt === "end" ? horizontal ? plotW : plotH : zeroOffset;
|
|
349
660
|
const labelled = [];
|
|
350
|
-
if (labelsAt !== "none")
|
|
351
|
-
|
|
661
|
+
if (labelsAt !== "none") {
|
|
662
|
+
if (chart.catAxisReversed === true) for (let c = nCats - 1; c >= 0; c -= step) labelled.push(c);
|
|
663
|
+
else for (let c = 0; c < nCats; c += step) labelled.push(c);
|
|
664
|
+
}
|
|
352
665
|
for (const c of labelled) {
|
|
353
666
|
const cat = catText(c);
|
|
354
667
|
if (!cat) continue;
|
|
355
668
|
const center = (horizontal ? y0 : x0) + c * slot + slot / 2;
|
|
356
669
|
if (horizontal) labels.push({
|
|
357
670
|
text: cat,
|
|
358
|
-
x: labelsAt === "end" ? x0 + across + 3 : x0 + across - 3,
|
|
359
|
-
y: center -
|
|
360
|
-
|
|
361
|
-
colorHex: LABEL_COLOR,
|
|
671
|
+
x: (labelsAt === "end" ? x0 + across + 3 : x0 + across - 3) + upright.dx,
|
|
672
|
+
y: center - catFace.sizePt / 3 + upright.dy,
|
|
673
|
+
...catFace,
|
|
362
674
|
align: labelsAt === "end" ? "left" : "right"
|
|
363
675
|
});
|
|
364
676
|
else labels.push({
|
|
365
677
|
text: cat,
|
|
366
678
|
x: center,
|
|
367
|
-
y: labelsAt === "end" ? y0 + across +
|
|
368
|
-
|
|
369
|
-
colorHex: LABEL_COLOR,
|
|
679
|
+
y: labelsAt === "end" ? y0 + across + catFace.sizePt * .4 : y0 + across - catFace.sizePt,
|
|
680
|
+
...catFace,
|
|
370
681
|
align: "center"
|
|
371
682
|
});
|
|
372
683
|
}
|
|
373
684
|
if (labelsAt !== "none" && labelsAt !== "end") groupLevels.forEach((groups, level) => {
|
|
374
|
-
const rowY = y0 + across -
|
|
685
|
+
const rowY = y0 + across - catFace.sizePt - (level + 1) * catBand;
|
|
375
686
|
groups.forEach((group, i) => {
|
|
376
687
|
const end = Math.min(nCats, groups[i + 1]?.start ?? nCats);
|
|
377
688
|
if (end <= group.start) return;
|
|
@@ -379,24 +690,26 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
379
690
|
text: group.label,
|
|
380
691
|
x: x0 + (group.start + end) / 2 * slot,
|
|
381
692
|
y: rowY,
|
|
382
|
-
|
|
383
|
-
colorHex: LABEL_COLOR,
|
|
693
|
+
...catFace,
|
|
384
694
|
align: "center"
|
|
385
695
|
});
|
|
386
696
|
if (i > 0) polylines.push({
|
|
387
|
-
points: [[x0 + group.start * slot, y0 + across], [x0 + group.start * slot, rowY -
|
|
697
|
+
points: [[x0 + group.start * slot, y0 + across], [x0 + group.start * slot, rowY - catFace.sizePt * .4]],
|
|
388
698
|
strokeHex: GRID_COLOR,
|
|
389
699
|
widthPt: .75
|
|
390
700
|
});
|
|
391
701
|
});
|
|
392
702
|
});
|
|
393
|
-
pushAxisLines(polylines, x0, y0, plotW, plotH,
|
|
703
|
+
pushAxisLines(polylines, x0, y0, plotW, plotH, fromBelow ? {
|
|
704
|
+
...chart,
|
|
705
|
+
catAxisLine: { none: true }
|
|
706
|
+
} : chart, horizontal ? {
|
|
394
707
|
x: zeroOffset,
|
|
395
708
|
...valAtEnd ? { y: plotH } : {}
|
|
396
709
|
} : {
|
|
397
710
|
y: zeroOffset,
|
|
398
711
|
...valAtEnd ? { x: plotW } : {}
|
|
399
|
-
});
|
|
712
|
+
}, { upright });
|
|
400
713
|
legend.emit(rects, labels);
|
|
401
714
|
return {
|
|
402
715
|
x0,
|
|
@@ -413,13 +726,38 @@ function buildFrame(chart, wPt, hPt, measure, horizontal, opts = {}) {
|
|
|
413
726
|
polylines,
|
|
414
727
|
gridlines,
|
|
415
728
|
...plotBackground ? { plotBackground } : {},
|
|
416
|
-
labels
|
|
729
|
+
labels,
|
|
730
|
+
...depth ? {
|
|
731
|
+
depth,
|
|
732
|
+
walls
|
|
733
|
+
} : {}
|
|
417
734
|
};
|
|
418
735
|
}
|
|
419
736
|
var pctLabel = (v) => `${Math.round(v * 100)}%`;
|
|
420
737
|
var fmtDataLabel = (chart, v) => chartValueFormatter(chart)?.(v) ?? (Number.isInteger(v) ? String(v) : String(Math.round(v * 100) / 100));
|
|
738
|
+
/**
|
|
739
|
+
* How many category slots a chart has: its labels, or its longest series —
|
|
740
|
+
* counted in a loop, as spreading a file's series into one call overflows
|
|
741
|
+
* the stack.
|
|
742
|
+
*/
|
|
421
743
|
function catCount(chart) {
|
|
422
|
-
|
|
744
|
+
let n = Math.max(chart.categories.length, 1);
|
|
745
|
+
for (const s of chart.series) n = Math.max(n, s.values.length);
|
|
746
|
+
return n;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* The least and the greatest of `values`, or undefined where none is a
|
|
750
|
+
* number. Read in a loop: `Math.min(...values)` throws on a series of 200 000
|
|
751
|
+
* points, as a call takes no more arguments than the stack holds.
|
|
752
|
+
*/
|
|
753
|
+
function extentOf(values) {
|
|
754
|
+
let lo = Infinity;
|
|
755
|
+
let hi = -Infinity;
|
|
756
|
+
for (const v of values) {
|
|
757
|
+
if (v < lo) lo = v;
|
|
758
|
+
if (v > hi) hi = v;
|
|
759
|
+
}
|
|
760
|
+
return lo <= hi ? [lo, hi] : void 0;
|
|
423
761
|
}
|
|
424
762
|
function stackedTotals(chart, nCats) {
|
|
425
763
|
let max = 0;
|
|
@@ -459,15 +797,72 @@ function groupingFrameOpts(chart, nCats) {
|
|
|
459
797
|
}
|
|
460
798
|
var CHART_FORMAT_ID = 1e6;
|
|
461
799
|
var formatterCache = /* @__PURE__ */ new WeakMap();
|
|
800
|
+
/**
|
|
801
|
+
* A tick formatter for a number format code — made per scene and not kept: a
|
|
802
|
+
* cache by code would grow with every code every document ever named.
|
|
803
|
+
*/
|
|
804
|
+
function formatterOf(code) {
|
|
805
|
+
if (code === void 0) return void 0;
|
|
806
|
+
const formats = /* @__PURE__ */ new Map([[CHART_FORMAT_ID, code]]);
|
|
807
|
+
return (v) => applyNumberFormat(String(v), CHART_FORMAT_ID, formats);
|
|
808
|
+
}
|
|
462
809
|
function chartValueFormatter(chart) {
|
|
463
810
|
const hit = formatterCache.get(chart);
|
|
464
811
|
if (hit !== void 0) return hit ?? void 0;
|
|
465
812
|
const code = chart.numberFormat;
|
|
466
|
-
const made = code === void 0 ? null : ((formats) => (v) => applyNumberFormat(String(v), CHART_FORMAT_ID, formats))(new Map([[CHART_FORMAT_ID, code]]));
|
|
813
|
+
const made = code === void 0 ? null : ((formats) => (v) => applyNumberFormat(String(v), CHART_FORMAT_ID, formats))(/* @__PURE__ */ new Map([[CHART_FORMAT_ID, code]]));
|
|
467
814
|
formatterCache.set(chart, made);
|
|
468
815
|
return made ?? void 0;
|
|
469
816
|
}
|
|
470
817
|
/**
|
|
818
|
+
* Whole percentages that add up to a hundred, as Excel labels a pie's slices:
|
|
819
|
+
* each share's floor, and the points still missing given to the slices with
|
|
820
|
+
* the largest remainders — 10, 20 and 15 of 45 read 22%, 45% and 33% in
|
|
821
|
+
* Excel's PDF, where rounding each one reads 22, 44 and 33.
|
|
822
|
+
*
|
|
823
|
+
* @param values The slices' values, a slice that is not positive drawing none.
|
|
824
|
+
* @param total Their sum.
|
|
825
|
+
* @returns Each slice's whole percentage.
|
|
826
|
+
*/
|
|
827
|
+
function wholePercents(values, total) {
|
|
828
|
+
const exact = values.map((v) => v > 0 && total > 0 ? v / total * 100 : 0);
|
|
829
|
+
const out = exact.map((e) => Math.floor(e));
|
|
830
|
+
let missing = 100 - out.reduce((a, b) => a + b, 0);
|
|
831
|
+
const byRemainder = exact.map((e, i) => ({
|
|
832
|
+
i,
|
|
833
|
+
rest: e - (out[i] ?? 0)
|
|
834
|
+
})).filter(({ i }) => (values[i] ?? 0) > 0).sort((a, b) => b.rest - a.rest || a.i - b.i);
|
|
835
|
+
for (const { i } of byRemainder) {
|
|
836
|
+
if (missing <= 0) break;
|
|
837
|
+
out[i] = (out[i] ?? 0) + 1;
|
|
838
|
+
missing--;
|
|
839
|
+
}
|
|
840
|
+
return out;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* §21.2.2.49 — the text a slice's data label shows: the parts the chart's
|
|
844
|
+
* switches turn on, in Excel's order — series name, category name, value,
|
|
845
|
+
* percentage — joined by its separator, ", " where it states none. The
|
|
846
|
+
* labels' own number format is the value's where the value is shown, and the
|
|
847
|
+
* percentage's where only that is; a percentage is otherwise whole.
|
|
848
|
+
* LIBRE_OFFICE-100610-0.pptx's five pies show the 4, 6, 3 and 6 they ask for,
|
|
849
|
+
* 45540_classic_Footer.xlsx its functions' names over their shares, and a pie
|
|
850
|
+
* whose labels show nothing — or that has none — shows nothing, as Excel's.
|
|
851
|
+
*
|
|
852
|
+
* @returns The label, or undefined when the chart labels nothing.
|
|
853
|
+
*/
|
|
854
|
+
function sliceLabel(chart, series, i, v, total, percent) {
|
|
855
|
+
const dl = chart.dataLabels;
|
|
856
|
+
if (!dl) return void 0;
|
|
857
|
+
const own = formatterOf(dl.numberFormat);
|
|
858
|
+
const parts = [];
|
|
859
|
+
if (dl.showSerName) parts.push(series.name ?? legendSeriesName(series, 0));
|
|
860
|
+
if (dl.showCatName) parts.push(chart.categories[i] ?? String(i + 1));
|
|
861
|
+
if (dl.showVal) parts.push(own ? own(v) : fmtDataLabel(chart, v));
|
|
862
|
+
if (dl.showPercent) parts.push(own && !dl.showVal ? own(v / total) : `${String(percent)}%`);
|
|
863
|
+
return parts.length > 0 ? parts.join(dl.separator ?? ", ") : void 0;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
471
866
|
* Lay out a bar/column {@link Chart} into a {@link ChartScene}. Honours
|
|
472
867
|
* `chart.grouping` (clustered / stacked / percentStacked) and `chart.barDir`
|
|
473
868
|
* (column vs horizontal bar), over the shared cartesian frame.
|
|
@@ -501,13 +896,39 @@ function buildBarScene(chart, wPt, hPt, measure) {
|
|
|
501
896
|
const stacked = g === "stacked" || g === "percentStacked";
|
|
502
897
|
const percent = g === "percentStacked";
|
|
503
898
|
const horizontal = chart.barDir === "bar";
|
|
504
|
-
const
|
|
899
|
+
const nCats = catCount(chart);
|
|
505
900
|
const barIdx = [];
|
|
506
901
|
const lineIdx = [];
|
|
507
902
|
chart.series.forEach((s, i) => (isLineLike(s) ? lineIdx : barIdx).push(i));
|
|
903
|
+
const nSer = Math.max(1, barIdx.length);
|
|
904
|
+
const gap = (chart.gapPercent ?? 150) / 100;
|
|
905
|
+
const overlap = (chart.overlapPercent ?? 0) / 100;
|
|
906
|
+
const barShare = 1 / (stacked ? 1 + gap : nSer - (nSer - 1) * overlap + gap);
|
|
907
|
+
const view = chart.bar3D;
|
|
908
|
+
const sinX = view ? Math.sin(view.rotX * Math.PI / 180) : 0;
|
|
909
|
+
const sinY = view ? Math.sin(view.rotY * Math.PI / 180) : 0;
|
|
910
|
+
const seen = view !== void 0 && (Math.abs(sinX) > 1e-9 || Math.abs(sinY) > 1e-9);
|
|
911
|
+
const f = buildFrame(chart, wPt, hPt, measure, horizontal, {
|
|
912
|
+
...groupingFrameOpts(chart, nCats),
|
|
913
|
+
...view ? { unpadded: true } : {},
|
|
914
|
+
...view && seen ? { depth: {
|
|
915
|
+
perSlot: barShare * (view.depthPercent / 100) * (1 + view.gapDepth / 100),
|
|
916
|
+
sinX,
|
|
917
|
+
sinY
|
|
918
|
+
} } : {}
|
|
919
|
+
});
|
|
920
|
+
const barW = f.slot * barShare;
|
|
921
|
+
const boxes = [];
|
|
922
|
+
const put = (rect, lift = [0, 0]) => {
|
|
923
|
+
if (f.depth) boxes.push({
|
|
924
|
+
rect,
|
|
925
|
+
lift
|
|
926
|
+
});
|
|
927
|
+
else f.rects.push(rect);
|
|
928
|
+
};
|
|
929
|
+
const values = [];
|
|
508
930
|
if (stacked) {
|
|
509
|
-
const groupPad = f.slot
|
|
510
|
-
const barW = f.slot - 2 * groupPad;
|
|
931
|
+
const groupPad = (f.slot - barW) / 2;
|
|
511
932
|
for (let c = 0; c < f.nCats; c++) {
|
|
512
933
|
const along = (horizontal ? f.y0 : f.x0) + c * f.slot + groupPad;
|
|
513
934
|
const denom = percent ? barIdx.reduce((a, i) => a + Math.abs(chart.series[i].values[c] ?? 0), 0) || 1 : 1;
|
|
@@ -524,7 +945,7 @@ function buildBarScene(chart, wPt, hPt, measure) {
|
|
|
524
945
|
const span = Math.abs(o1 - o0);
|
|
525
946
|
const color = pointColor(series, c) ?? seriesColor(series, s, chart.seriesColorCycle);
|
|
526
947
|
const outline = barOutline(series);
|
|
527
|
-
if (horizontal)
|
|
948
|
+
if (horizontal) put({
|
|
528
949
|
x: f.x0 + lo,
|
|
529
950
|
y: along,
|
|
530
951
|
w: span,
|
|
@@ -532,7 +953,7 @@ function buildBarScene(chart, wPt, hPt, measure) {
|
|
|
532
953
|
fillHex: color,
|
|
533
954
|
...outline
|
|
534
955
|
});
|
|
535
|
-
else
|
|
956
|
+
else put({
|
|
536
957
|
x: along,
|
|
537
958
|
y: f.y0 + lo,
|
|
538
959
|
w: barW,
|
|
@@ -540,90 +961,170 @@ function buildBarScene(chart, wPt, hPt, measure) {
|
|
|
540
961
|
fillHex: color,
|
|
541
962
|
...outline
|
|
542
963
|
});
|
|
543
|
-
if (chart.showValues && span >
|
|
964
|
+
if (chart.showValues && span > faceOf(chart, "dataLabels").sizePt) {
|
|
544
965
|
const raw = series.values[c] ?? 0;
|
|
545
|
-
|
|
546
|
-
|
|
966
|
+
const face = faceOf(chart, "dataLabels");
|
|
967
|
+
values.push({
|
|
968
|
+
label: horizontal ? centeredLabel(fmtDataLabel(chart, raw), f.x0 + lo + span / 2, f.depth ? along + barW / 2 - face.sizePt * .35 : along + barW * .3, face) : centeredLabel(fmtDataLabel(chart, raw), along + barW / 2, f.y0 + lo + span / 2 - 3, face),
|
|
969
|
+
lift: [0, 0]
|
|
970
|
+
});
|
|
547
971
|
}
|
|
548
972
|
if (v >= 0) cumPos = top;
|
|
549
973
|
else cumNeg = top;
|
|
550
974
|
}
|
|
551
975
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const groupPad = (f.slot - barW * nSer) / 2;
|
|
567
|
-
for (let c = 0; c < f.nCats; c++) {
|
|
568
|
-
const slotStart = (horizontal ? f.y0 : f.x0) + c * f.slot + groupPad;
|
|
569
|
-
for (let b = 0; b < barIdx.length; b++) {
|
|
570
|
-
const s = barIdx[b];
|
|
571
|
-
const series = chart.series[s];
|
|
572
|
-
const len = f.valueOffset(series.values[c] ?? 0) - f.zeroOffset;
|
|
573
|
-
const color = pointColor(series, c) ?? seriesColor(series, s, chart.seriesColorCycle);
|
|
574
|
-
const along = slotStart + b * barW;
|
|
575
|
-
const outline = barOutline(series);
|
|
576
|
-
if (horizontal) {
|
|
577
|
-
const bx = f.x0 + f.zeroOffset + Math.min(0, len);
|
|
578
|
-
f.rects.push({
|
|
579
|
-
x: bx,
|
|
976
|
+
} else {
|
|
977
|
+
const pitch = barW * (1 - overlap);
|
|
978
|
+
const groupPad = (f.slot - barW - (nSer - 1) * pitch) / 2;
|
|
979
|
+
for (let c = 0; c < f.nCats; c++) {
|
|
980
|
+
const slotStart = (horizontal ? f.y0 : f.x0) + c * f.slot + groupPad;
|
|
981
|
+
for (let b = 0; b < barIdx.length; b++) {
|
|
982
|
+
const s = barIdx[b];
|
|
983
|
+
const series = chart.series[s];
|
|
984
|
+
const len = f.valueOffset(series.values[c] ?? 0) - f.zeroOffset;
|
|
985
|
+
const color = pointColor(series, c) ?? seriesColor(series, s, chart.seriesColorCycle);
|
|
986
|
+
const along = slotStart + b * pitch;
|
|
987
|
+
const outline = barOutline(series);
|
|
988
|
+
if (horizontal) put({
|
|
989
|
+
x: f.x0 + f.zeroOffset + Math.min(0, len),
|
|
580
990
|
y: along,
|
|
581
991
|
w: Math.abs(len),
|
|
582
992
|
h: barW,
|
|
583
993
|
fillHex: color,
|
|
584
994
|
...outline
|
|
585
995
|
});
|
|
586
|
-
|
|
587
|
-
const by = f.y0 + f.zeroOffset + Math.min(0, len);
|
|
588
|
-
f.rects.push({
|
|
996
|
+
else put({
|
|
589
997
|
x: along,
|
|
590
|
-
y:
|
|
998
|
+
y: f.y0 + f.zeroOffset + Math.min(0, len),
|
|
591
999
|
w: barW,
|
|
592
1000
|
h: Math.abs(len),
|
|
593
1001
|
fillHex: color,
|
|
594
1002
|
...outline
|
|
595
1003
|
});
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1004
|
+
if (chart.showValues) {
|
|
1005
|
+
const txt = fmtDataLabel(chart, series.values[c] ?? 0);
|
|
1006
|
+
const face = faceOf(chart, "dataLabels");
|
|
1007
|
+
if (horizontal) {
|
|
1008
|
+
const end = f.x0 + f.zeroOffset + len;
|
|
1009
|
+
values.push({
|
|
1010
|
+
label: {
|
|
1011
|
+
text: txt,
|
|
1012
|
+
x: end + (len >= 0 ? 3 : -3),
|
|
1013
|
+
y: f.depth ? along + barW / 2 - face.sizePt * .35 : along + barW * .25,
|
|
1014
|
+
...face,
|
|
1015
|
+
align: len >= 0 ? "left" : "right"
|
|
1016
|
+
},
|
|
1017
|
+
lift: [len >= 0 ? .5 : 0, 0]
|
|
1018
|
+
});
|
|
1019
|
+
} else {
|
|
1020
|
+
const end = f.y0 + f.zeroOffset + len;
|
|
1021
|
+
const above = f.depth ? end - face.sizePt * .35 : end + 2;
|
|
1022
|
+
values.push({
|
|
1023
|
+
label: centeredLabel(txt, along + barW * .45, len >= 0 ? above : end - face.sizePt, face),
|
|
1024
|
+
lift: [0, len >= 0 ? 1 : 0]
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
612
1027
|
}
|
|
613
1028
|
}
|
|
614
1029
|
}
|
|
615
1030
|
}
|
|
1031
|
+
const polygons = [];
|
|
1032
|
+
let shift = {
|
|
1033
|
+
dx: 0,
|
|
1034
|
+
dy: 0
|
|
1035
|
+
};
|
|
1036
|
+
let own = {
|
|
1037
|
+
dx: 0,
|
|
1038
|
+
dy: 0
|
|
1039
|
+
};
|
|
1040
|
+
if (f.depth && view) {
|
|
1041
|
+
const share = 1 / (1 + view.gapDepth / 100);
|
|
1042
|
+
own = {
|
|
1043
|
+
dx: f.depth.dx * share,
|
|
1044
|
+
dy: f.depth.dy * share
|
|
1045
|
+
};
|
|
1046
|
+
const back = view.gapDepth / 200;
|
|
1047
|
+
shift = {
|
|
1048
|
+
dx: own.dx * back,
|
|
1049
|
+
dy: own.dy * back
|
|
1050
|
+
};
|
|
1051
|
+
const across = (r) => horizontal ? r.y + r.h / 2 : r.x + r.w / 2;
|
|
1052
|
+
const up = (r) => horizontal ? r.x + r.w / 2 : r.y + r.h / 2;
|
|
1053
|
+
const first = Math.sign(horizontal ? sinX : sinY) || 1;
|
|
1054
|
+
const then = Math.sign(horizontal ? sinY : sinX) || 1;
|
|
1055
|
+
boxes.sort((a, b) => first * (across(a.rect) - across(b.rect)) || then * (up(a.rect) - up(b.rect)));
|
|
1056
|
+
for (const { rect } of boxes) {
|
|
1057
|
+
const front = {
|
|
1058
|
+
...rect,
|
|
1059
|
+
x: rect.x + shift.dx,
|
|
1060
|
+
y: rect.y + shift.dy
|
|
1061
|
+
};
|
|
1062
|
+
pushBarFaces(polygons, front, own);
|
|
1063
|
+
f.rects.push(front);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
for (const { label, lift } of values) f.labels.push(f.depth ? {
|
|
1067
|
+
...label,
|
|
1068
|
+
x: label.x + shift.dx + lift[0] * own.dx,
|
|
1069
|
+
y: label.y + shift.dy + lift[1] * own.dy
|
|
1070
|
+
} : label);
|
|
616
1071
|
pushComboLines(chart, f, lineIdx);
|
|
617
1072
|
return {
|
|
618
1073
|
rects: f.rects,
|
|
619
1074
|
polylines: f.polylines,
|
|
620
1075
|
gridlines: f.gridlines,
|
|
621
1076
|
...f.plotBackground ? { plotBackground: f.plotBackground } : {},
|
|
1077
|
+
...f.walls && f.walls.length > 0 ? { walls: f.walls } : {},
|
|
1078
|
+
...polygons.length > 0 ? { polygons } : {},
|
|
622
1079
|
wedges: [],
|
|
623
1080
|
labels: f.labels
|
|
624
1081
|
};
|
|
625
1082
|
}
|
|
626
1083
|
/**
|
|
1084
|
+
* The faces of one 3-D bar a viewer sees beside its front: its top when the
|
|
1085
|
+
* view looks down on it (its bottom when up at it), its right side when the
|
|
1086
|
+
* view is turned to the right (its left when to the left) — each shaded as
|
|
1087
|
+
* Excel's own PDF shades them: a top 0.76 of the bar's colour, a right side
|
|
1088
|
+
* 0.635, a bottom or a left side 0.4 (2026-10-02).
|
|
1089
|
+
*
|
|
1090
|
+
* @param r The bar's front.
|
|
1091
|
+
* @param d Its depth, as an offset from the front.
|
|
1092
|
+
*/
|
|
1093
|
+
function pushBarFaces(polygons, r, d) {
|
|
1094
|
+
const fill = r.fillHex ?? "000000";
|
|
1095
|
+
const stroke = r.strokeHex ? {
|
|
1096
|
+
strokeHex: r.strokeHex,
|
|
1097
|
+
widthPt: r.strokeWidthPt ?? .75
|
|
1098
|
+
} : {};
|
|
1099
|
+
const { x, y, w, h } = r;
|
|
1100
|
+
if (d.dy !== 0) {
|
|
1101
|
+
const at = d.dy > 0 ? y + h : y;
|
|
1102
|
+
polygons.push({
|
|
1103
|
+
points: [
|
|
1104
|
+
[x, at],
|
|
1105
|
+
[x + w, at],
|
|
1106
|
+
[x + w + d.dx, at + d.dy],
|
|
1107
|
+
[x + d.dx, at + d.dy]
|
|
1108
|
+
],
|
|
1109
|
+
fillHex: shadeHex(fill, d.dy > 0 ? .76 : .4),
|
|
1110
|
+
...stroke
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
if (d.dx !== 0) {
|
|
1114
|
+
const at = d.dx > 0 ? x + w : x;
|
|
1115
|
+
polygons.push({
|
|
1116
|
+
points: [
|
|
1117
|
+
[at, y],
|
|
1118
|
+
[at + d.dx, y + d.dy],
|
|
1119
|
+
[at + d.dx, y + h + d.dy],
|
|
1120
|
+
[at, y + h]
|
|
1121
|
+
],
|
|
1122
|
+
fillHex: shadeHex(fill, d.dx > 0 ? .635 : .4),
|
|
1123
|
+
...stroke
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
627
1128
|
* The name a series shows in the legend: its own `c:tx`, or Excel's positional
|
|
628
1129
|
* `SeriesN` when it has none. Exported because the SUBSET has to know it too —
|
|
629
1130
|
* a name invented at draw time is a name no glyph collector ever saw, and
|
|
@@ -669,13 +1170,12 @@ function pushComboLines(chart, f, lineIdx) {
|
|
|
669
1170
|
}
|
|
670
1171
|
}
|
|
671
1172
|
}
|
|
672
|
-
function centeredLabel(text, x, y) {
|
|
1173
|
+
function centeredLabel(text, x, y, face) {
|
|
673
1174
|
return {
|
|
674
1175
|
text,
|
|
675
1176
|
x,
|
|
676
1177
|
y,
|
|
677
|
-
|
|
678
|
-
colorHex: LABEL_COLOR,
|
|
1178
|
+
...face,
|
|
679
1179
|
align: "center"
|
|
680
1180
|
};
|
|
681
1181
|
}
|
|
@@ -768,22 +1268,55 @@ function buildScatterScene(chart, wPt, hPt, measure) {
|
|
|
768
1268
|
wedges: [],
|
|
769
1269
|
labels
|
|
770
1270
|
};
|
|
771
|
-
const
|
|
772
|
-
const
|
|
1271
|
+
const [xLo, xHi] = extentOf(xs) ?? [0, 1];
|
|
1272
|
+
const [yLo, yHi] = extentOf(ys) ?? [0, 1];
|
|
1273
|
+
const xScale = scatterScale(xLo, xHi, intervalsThatFit(wPt, true), {
|
|
1274
|
+
min: chart.xAxisMin,
|
|
1275
|
+
max: chart.xAxisMax,
|
|
1276
|
+
unit: chart.xAxisMajorUnit
|
|
1277
|
+
});
|
|
1278
|
+
const yScale = scatterScale(yLo, yHi, intervalsThatFit(hPt, false), {
|
|
1279
|
+
min: chart.valAxisMin,
|
|
1280
|
+
max: chart.valAxisMax,
|
|
1281
|
+
unit: chart.valAxisMajorUnit
|
|
1282
|
+
});
|
|
773
1283
|
const xTicks = ticks(xScale);
|
|
774
1284
|
const yTicks = ticks(yScale);
|
|
1285
|
+
const fmtX = formatterOf(chart.xNumberFormat) ?? formatTick;
|
|
1286
|
+
const fmtY = formatterOf(chart.numberFormat) ?? formatTick;
|
|
775
1287
|
const title = titleLines(chart, wPt, measure);
|
|
776
|
-
const top = 4 + titleHeight(title);
|
|
1288
|
+
const top = 4 + titleHeight(title, chart);
|
|
777
1289
|
const legend = buildLegendBlock(chart, wPt, hPt, measure);
|
|
778
|
-
const
|
|
779
|
-
const
|
|
780
|
-
const
|
|
781
|
-
const
|
|
782
|
-
const
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
1290
|
+
const xFace = faceOf(chart, "catAxis");
|
|
1291
|
+
const yFace = faceOf(chart, "valAxis");
|
|
1292
|
+
const yTitleFace = faceOf(chart, "valAxisTitle");
|
|
1293
|
+
const xTitleFace = faceOf(chart, "catAxisTitle");
|
|
1294
|
+
const tickLabelW = chart.valAxisDeleted === true ? 0 : Math.max(0, ...yTicks.map((v) => widthIn(measure, fmtY(v), yFace))) + 4;
|
|
1295
|
+
const own = innerPlot(chart, wPt, hPt);
|
|
1296
|
+
const x0 = own?.x0 ?? 4 + (chart.valAxisTitle ? yTitleFace.sizePt * 1.5 : 0) + tickLabelW;
|
|
1297
|
+
const y0 = own?.y0 ?? 4 + legend.bottomHeight + (chart.catAxisTitle ? xTitleFace.sizePt * 1.5 : 0) + (chart.catAxisDeleted === true ? 0 : xFace.sizePt * 1.6);
|
|
1298
|
+
const plotW = own?.plotW ?? Math.max(1, wPt - 4 - legend.rightWidth - x0);
|
|
1299
|
+
const plotH = own?.plotH ?? Math.max(1, hPt - top - y0);
|
|
1300
|
+
const xAt = (v) => x0 + fractionOf(v, xScale) * plotW;
|
|
1301
|
+
const yAt = (v) => y0 + fractionOf(v, yScale) * plotH;
|
|
1302
|
+
pushChartTitle(labels, title, wPt, hPt, chart);
|
|
1303
|
+
if (chart.valAxisTitle) labels.push({
|
|
1304
|
+
text: chart.valAxisTitle,
|
|
1305
|
+
x: 4 + yTitleFace.sizePt * .9,
|
|
1306
|
+
y: y0 + plotH / 2,
|
|
1307
|
+
...yTitleFace,
|
|
1308
|
+
align: "center",
|
|
1309
|
+
rotationDeg: 90
|
|
1310
|
+
});
|
|
1311
|
+
if (chart.catAxisTitle) labels.push({
|
|
1312
|
+
text: chart.catAxisTitle,
|
|
1313
|
+
x: x0 + plotW / 2,
|
|
1314
|
+
y: legend.bottomHeight + 2,
|
|
1315
|
+
...xTitleFace,
|
|
1316
|
+
align: "center"
|
|
1317
|
+
});
|
|
1318
|
+
pushGridTicks(gridlines, labels, yTicks, fmtY, "y", yAt, x0, y0, plotW, plotH, chart.gridLine, yFace, false, chart.valAxisDeleted !== true);
|
|
1319
|
+
pushGridTicks(gridlines, labels, xTicks, fmtX, "x", xAt, x0, y0, plotW, plotH, chart.xGridLine, xFace, false, chart.catAxisDeleted !== true);
|
|
787
1320
|
pushAxisLines(polylines, x0, y0, plotW, plotH, chart);
|
|
788
1321
|
const style = chart.scatterStyle ?? "marker";
|
|
789
1322
|
const joins = style === "line" || style === "lineMarker" || style.startsWith("smooth");
|
|
@@ -871,8 +1404,7 @@ function pushMarker(rects, wedges, marker, x, y, color) {
|
|
|
871
1404
|
* @returns The positioned scene primitives.
|
|
872
1405
|
*/
|
|
873
1406
|
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];
|
|
1407
|
+
const range = extentOf(chart.series.flatMap((s) => s.values)) ?? [0, 1];
|
|
876
1408
|
const lineFormat = chartValueFormatter(chart);
|
|
877
1409
|
const f = buildFrame(chart, wPt, hPt, measure, false, {
|
|
878
1410
|
dataRange: range,
|
|
@@ -888,7 +1420,7 @@ function buildLineScene(chart, wPt, hPt, measure) {
|
|
|
888
1420
|
const y = f.y0 + f.valueOffset(series.values[c] ?? 0);
|
|
889
1421
|
pts.push([x, y]);
|
|
890
1422
|
if (chart.lineMarkers) pushMarker(f.rects, wedges, series.marker, x, y, color);
|
|
891
|
-
if (chart.showValues) f.labels.push(centeredLabel(fmtDataLabel(chart, series.values[c] ?? 0), x, y + 3));
|
|
1423
|
+
if (chart.showValues) f.labels.push(centeredLabel(fmtDataLabel(chart, series.values[c] ?? 0), x, y + 3, faceOf(chart, "dataLabels")));
|
|
892
1424
|
}
|
|
893
1425
|
if (pts.length >= 2) f.polylines.push({
|
|
894
1426
|
points: pts,
|
|
@@ -944,73 +1476,372 @@ function buildPieScene(chart, wPt, hPt, measure) {
|
|
|
944
1476
|
labels
|
|
945
1477
|
};
|
|
946
1478
|
const title = titleLines(chart, wPt, measure);
|
|
947
|
-
const top = 4 + titleHeight(title);
|
|
1479
|
+
const top = 4 + titleHeight(title, chart);
|
|
948
1480
|
const legend = layoutLegend(values.map((_, i) => ({
|
|
949
1481
|
name: chart.categories[i] ?? String(i + 1),
|
|
950
1482
|
colorHex: sliceColor(series, i, chart.seriesColorCycle)
|
|
951
|
-
})), chart.hasLegend, chart.legendPos ?? "r", wPt, hPt, measure);
|
|
952
|
-
const
|
|
953
|
-
const
|
|
954
|
-
const
|
|
955
|
-
const cx = 4 + availW / 2;
|
|
956
|
-
const cy = 4 + legend.bottomHeight + availH / 2;
|
|
1483
|
+
})), chart.hasLegend, chart.legendPos ?? "r", wPt, hPt, measure, faceOf(chart, "legend"));
|
|
1484
|
+
const plotBox = chart.plotBox;
|
|
1485
|
+
const availW = plotBox ? plotBox.w * wPt : Math.max(1, wPt - 8 - legend.rightWidth);
|
|
1486
|
+
const availH = plotBox ? plotBox.h * hPt : Math.max(1, hPt - top - 4 - legend.bottomHeight);
|
|
1487
|
+
const cx = plotBox ? (plotBox.x + plotBox.w / 2) * wPt : 4 + availW / 2;
|
|
1488
|
+
const cy = plotBox ? hPt - (plotBox.y + plotBox.h / 2) * hPt : 4 + legend.bottomHeight + availH / 2;
|
|
1489
|
+
const disc = chart.pie3D;
|
|
1490
|
+
const elevation = disc ? Math.min(90, Math.max(0, disc.rotX)) * Math.PI / 180 : Math.PI / 2;
|
|
1491
|
+
const tilt = Math.max(Math.sin(elevation), .02);
|
|
1492
|
+
const thickF = disc ? .25 * Math.cos(elevation) ** 2 : 0;
|
|
1493
|
+
const explosionOfSlice = (i) => (atPoint(series.pointExplosions, i)?.percent ?? series.explosion ?? 0) / 100;
|
|
1494
|
+
let maxOut = 0;
|
|
1495
|
+
for (let i = 0; i < values.length; i++) if ((values[i] ?? 0) > 0) maxOut = Math.max(maxOut, explosionOfSlice(i));
|
|
1496
|
+
const fullR = Math.max(1, Math.min(availW / 2 / (1 + maxOut), availH / (2 * tilt * (1 + maxOut) + thickF)) * (plotBox ? 1 : .95));
|
|
1497
|
+
const turn = (disc ? disc.rotY : chart.firstSliceAngle ?? 0) * Math.PI / 180;
|
|
1498
|
+
const sweeps = values.map((v) => v > 0 ? -(v / total) * 2 * Math.PI : 0);
|
|
1499
|
+
const starts = [];
|
|
1500
|
+
let ang = Math.PI / 2 - turn;
|
|
1501
|
+
for (const sweep of sweeps) {
|
|
1502
|
+
starts.push(ang);
|
|
1503
|
+
ang += sweep;
|
|
1504
|
+
}
|
|
1505
|
+
const midOf = (i) => (starts[i] ?? 0) + (sweeps[i] ?? 0) / 2;
|
|
1506
|
+
const boxes = pieLabelBoxes(chart, series, values, total, sweeps, starts, fullR, wPt, measure);
|
|
1507
|
+
let room = fullR;
|
|
1508
|
+
for (const b of plotBox ? [] : boxes) {
|
|
1509
|
+
if (b.where !== "out" || b.placement) continue;
|
|
1510
|
+
const c = Math.abs(Math.cos(b.mid));
|
|
1511
|
+
const sn = Math.abs(Math.sin(b.mid)) * tilt;
|
|
1512
|
+
if (c > .05) room = Math.min(room, (availW / 2 - PIE_LABEL_GAP - b.width) / c);
|
|
1513
|
+
if (sn > .05) room = Math.min(room, (availH / 2 - PIE_LABEL_GAP - b.height) / sn);
|
|
1514
|
+
}
|
|
1515
|
+
const discOf = (rr) => {
|
|
1516
|
+
const thickness = thickF * rr;
|
|
1517
|
+
const cyTop = cy + thickness / 2;
|
|
1518
|
+
const centre = (i) => {
|
|
1519
|
+
const out = explosionOfSlice(i) * rr;
|
|
1520
|
+
const mid = midOf(i);
|
|
1521
|
+
return [cx + Math.cos(mid) * out, cyTop + Math.sin(mid) * out * tilt];
|
|
1522
|
+
};
|
|
1523
|
+
const point = (i, t, along) => {
|
|
1524
|
+
const [x0, y0] = centre(i);
|
|
1525
|
+
return [x0 + Math.cos(t) * along, y0 + Math.sin(t) * along * tilt];
|
|
1526
|
+
};
|
|
1527
|
+
return {
|
|
1528
|
+
thickness,
|
|
1529
|
+
cyTop,
|
|
1530
|
+
centre,
|
|
1531
|
+
point
|
|
1532
|
+
};
|
|
1533
|
+
};
|
|
1534
|
+
const positionOf = (box, pieR) => {
|
|
1535
|
+
const rr = box.placement ? fullR : pieR;
|
|
1536
|
+
const d = discOf(rr);
|
|
1537
|
+
const i = box.index;
|
|
1538
|
+
const cos = Math.cos(box.mid);
|
|
1539
|
+
const sin = Math.sin(box.mid);
|
|
1540
|
+
let bx;
|
|
1541
|
+
let by;
|
|
1542
|
+
if (box.where === "out") {
|
|
1543
|
+
const [px, py0] = d.point(i, box.mid, rr + PIE_LABEL_GAP / Math.max(tilt, .2));
|
|
1544
|
+
const py = sin < 0 ? py0 - d.thickness : py0;
|
|
1545
|
+
bx = px + (cos >= 0 ? box.width / 2 : -box.width / 2);
|
|
1546
|
+
by = py + sin * box.height / 2;
|
|
1547
|
+
} else {
|
|
1548
|
+
const depth = chart.doughnut ? (rr * .5 + rr) / 2 : rr * (box.where === "ctr" ? .5 : box.where === "end" ? .75 : .6);
|
|
1549
|
+
[bx, by] = d.point(i, box.mid, depth);
|
|
1550
|
+
}
|
|
1551
|
+
const moved = box.placement;
|
|
1552
|
+
if (moved?.edge) {
|
|
1553
|
+
if (moved.x !== void 0) bx = moved.x * wPt + box.width / 2;
|
|
1554
|
+
if (moved.y !== void 0) by = hPt - moved.y * hPt - box.height / 2;
|
|
1555
|
+
} else if (moved) {
|
|
1556
|
+
bx += (moved.x ?? 0) * wPt;
|
|
1557
|
+
by -= (moved.y ?? 0) * hPt;
|
|
1558
|
+
}
|
|
1559
|
+
return {
|
|
1560
|
+
bx: Math.min(Math.max(bx, box.width / 2 + 2), wPt - box.width / 2 - 2),
|
|
1561
|
+
by: Math.min(Math.max(by, box.height / 2 + 2), hPt - box.height / 2 - 2)
|
|
1562
|
+
};
|
|
1563
|
+
};
|
|
1564
|
+
const across = (x, y, cyTop) => Math.hypot(x - cx, (y - cyTop) / tilt);
|
|
1565
|
+
let r = Math.max(fullR * .4, room);
|
|
1566
|
+
for (const box of plotBox ? [] : boxes) {
|
|
1567
|
+
if (box.where !== "out" || !box.placement) continue;
|
|
1568
|
+
const { bx, by } = positionOf(box, r);
|
|
1569
|
+
const cyTop = discOf(fullR).cyTop;
|
|
1570
|
+
if (across(bx, by, cyTop) < fullR * .9) continue;
|
|
1571
|
+
const nx = Math.min(Math.max(cx, bx - box.width / 2), bx + box.width / 2);
|
|
1572
|
+
const ny = Math.min(Math.max(cyTop, by - box.height / 2), by + box.height / 2);
|
|
1573
|
+
r = Math.max(fullR * .4, Math.min(r, across(nx, ny, cyTop) - PIE_LABEL_GAP / 2));
|
|
1574
|
+
}
|
|
1575
|
+
const d = discOf(r);
|
|
957
1576
|
const holeR = chart.doughnut ? r * .5 : 0;
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1577
|
+
const polygons = [];
|
|
1578
|
+
if (disc) {
|
|
1579
|
+
const sides = [];
|
|
1580
|
+
for (let i = 0; i < values.length; i++) {
|
|
1581
|
+
if ((values[i] ?? 0) <= 0) continue;
|
|
1582
|
+
const fill = shadeHex(sliceColor(series, i, chart.seriesColorCycle), .7);
|
|
1583
|
+
const a0 = starts[i];
|
|
1584
|
+
const a1 = a0 + sweeps[i];
|
|
1585
|
+
for (const [from, to] of frontArcs(a1, a0)) {
|
|
1586
|
+
const topArc = arcPoints(from, to, (t) => d.point(i, t, r));
|
|
1587
|
+
const bottom = topArc.map(([x, y]) => [x, y - d.thickness]).reverse();
|
|
1588
|
+
sides.push({
|
|
1589
|
+
points: [...topArc, ...bottom],
|
|
1590
|
+
fillHex: fill,
|
|
1591
|
+
strokeHex: fill,
|
|
1592
|
+
widthPt: .5,
|
|
1593
|
+
depth: Math.min(...topArc.map(([, y]) => y))
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
if (explosionOfSlice(i) > 0) {
|
|
1597
|
+
const [x0, y0] = d.centre(i);
|
|
1598
|
+
for (const t of [a0, a1]) {
|
|
1599
|
+
const [ex, ey] = d.point(i, t, r);
|
|
1600
|
+
sides.push({
|
|
1601
|
+
points: [
|
|
1602
|
+
[x0, y0],
|
|
1603
|
+
[ex, ey],
|
|
1604
|
+
[ex, ey - d.thickness],
|
|
1605
|
+
[x0, y0 - d.thickness]
|
|
1606
|
+
],
|
|
1607
|
+
fillHex: fill,
|
|
1608
|
+
strokeHex: fill,
|
|
1609
|
+
widthPt: .5,
|
|
1610
|
+
depth: Math.min(y0, ey)
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
sides.sort((a, b) => b.depth - a.depth);
|
|
1616
|
+
for (const { depth: _depth, ...side } of sides) polygons.push(side);
|
|
1617
|
+
for (let i = 0; i < values.length; i++) {
|
|
1618
|
+
if ((values[i] ?? 0) <= 0) continue;
|
|
1619
|
+
const a0 = starts[i];
|
|
1620
|
+
polygons.push({
|
|
1621
|
+
points: [d.centre(i), ...arcPoints(a0, a0 + sweeps[i], (t) => d.point(i, t, r))],
|
|
1622
|
+
fillHex: sliceColor(series, i, chart.seriesColorCycle),
|
|
1623
|
+
strokeHex: "FFFFFF",
|
|
1624
|
+
widthPt: .75
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
} else {
|
|
1628
|
+
for (let i = 0; i < values.length; i++) {
|
|
1629
|
+
if ((values[i] ?? 0) <= 0) continue;
|
|
1630
|
+
const [x0, y0] = d.centre(i);
|
|
1631
|
+
wedges.push({
|
|
1632
|
+
cx: x0,
|
|
1633
|
+
cy: y0,
|
|
1634
|
+
r,
|
|
1635
|
+
startRad: starts[i],
|
|
1636
|
+
sweepRad: sweeps[i],
|
|
1637
|
+
fillHex: sliceColor(series, i, chart.seriesColorCycle),
|
|
1638
|
+
strokeHex: "FFFFFF"
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
if (holeR > 0) wedges.push({
|
|
965
1642
|
cx,
|
|
966
1643
|
cy,
|
|
967
|
-
r,
|
|
968
|
-
startRad:
|
|
969
|
-
sweepRad:
|
|
970
|
-
fillHex:
|
|
971
|
-
strokeHex: "FFFFFF"
|
|
1644
|
+
r: holeR,
|
|
1645
|
+
startRad: 0,
|
|
1646
|
+
sweepRad: -2 * Math.PI,
|
|
1647
|
+
fillHex: "FFFFFF"
|
|
972
1648
|
});
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1649
|
+
}
|
|
1650
|
+
const polylines = [];
|
|
1651
|
+
const face = faceOf(chart, "dataLabels");
|
|
1652
|
+
const lineH = face.sizePt * 1.2;
|
|
1653
|
+
const placed = boxes.map((box) => ({
|
|
1654
|
+
box,
|
|
1655
|
+
...positionOf(box, r)
|
|
1656
|
+
}));
|
|
1657
|
+
const outside = placed.filter((p) => p.box.where === "out" || across(p.bx, p.by, d.cyTop) >= r).sort((a, b) => b.by - a.by);
|
|
1658
|
+
const overlaps = (a, b) => Math.abs(a.bx - b.bx) < (a.box.width + b.box.width) / 2 && Math.abs(a.by - b.by) < (a.box.height + b.box.height) / 2 + 2;
|
|
1659
|
+
const fixed = outside.filter((p) => p.box.placement);
|
|
1660
|
+
const chartSet = outside.filter((p) => !p.box.placement);
|
|
1661
|
+
if (fixed.length + chartSet.length <= MOST_SPREAD_LABELS) {
|
|
1662
|
+
chartSet.forEach((here, k) => {
|
|
1663
|
+
const blocks = (other) => overlaps(other, here);
|
|
1664
|
+
for (let pass = 0; pass <= fixed.length + k; pass++) {
|
|
1665
|
+
const hit = fixed.find(blocks) ?? chartSet.slice(0, k).find(blocks);
|
|
1666
|
+
if (!hit) break;
|
|
1667
|
+
here.by = hit.by - (hit.box.height + here.box.height) / 2 - 2;
|
|
1668
|
+
}
|
|
984
1669
|
});
|
|
985
|
-
|
|
1670
|
+
const lowest = chartSet.reduce((low, p) => low === void 0 || p.by < low.by ? p : low, void 0);
|
|
1671
|
+
const under = lowest ? 2 - (lowest.by - lowest.box.height / 2) : 0;
|
|
1672
|
+
if (under > 0) for (const p of chartSet) p.by += under;
|
|
986
1673
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1674
|
+
for (const { box, bx, by } of placed) {
|
|
1675
|
+
const moved = box.placement;
|
|
1676
|
+
if ((box.where === "out" || moved !== void 0) && across(bx, by, d.cyTop) >= r && chart.dataLabels?.showLeaderLines) {
|
|
1677
|
+
const [ex, ey] = d.point(box.index, box.mid, r);
|
|
1678
|
+
const tx = Math.min(Math.max(ex, bx - box.width / 2), bx + box.width / 2);
|
|
1679
|
+
const ty = Math.min(Math.max(ey, by - box.height / 2), by + box.height / 2);
|
|
1680
|
+
if (Math.hypot(tx - ex, ty - ey) > 3) polylines.push({
|
|
1681
|
+
points: [[ex, ey], [tx, ty]],
|
|
1682
|
+
strokeHex: LABEL_COLOR,
|
|
1683
|
+
widthPt: .75
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
const colorHex = box.where === "out" || moved !== void 0 || box.custom || chart.doughnut || chart.text?.dataLabels?.colorHex ? face.colorHex : "FFFFFF";
|
|
1687
|
+
box.lines.forEach((line, k) => {
|
|
1688
|
+
labels.push({
|
|
1689
|
+
text: line,
|
|
1690
|
+
x: bx,
|
|
1691
|
+
y: by - face.sizePt / 3 + ((box.lines.length - 1) / 2 - k) * lineH,
|
|
1692
|
+
...face,
|
|
1693
|
+
colorHex,
|
|
1694
|
+
align: "center"
|
|
1695
|
+
});
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
pushChartTitle(labels, title, wPt, hPt, chart);
|
|
996
1699
|
legend.emit(rects, labels);
|
|
997
1700
|
return {
|
|
998
1701
|
rects,
|
|
999
|
-
polylines
|
|
1702
|
+
polylines,
|
|
1000
1703
|
wedges,
|
|
1001
|
-
labels
|
|
1704
|
+
labels,
|
|
1705
|
+
...polygons.length > 0 ? { polygons } : {}
|
|
1002
1706
|
};
|
|
1003
1707
|
}
|
|
1708
|
+
/**
|
|
1709
|
+
* The parts of an arc from `from` to `to` (radians, `from` < `to`) that lie
|
|
1710
|
+
* on the disc's front half — below its centre in the y-up frame, where sin
|
|
1711
|
+
* is negative — each as a [from, to] pair.
|
|
1712
|
+
*/
|
|
1713
|
+
function frontArcs(from, to) {
|
|
1714
|
+
const out = [];
|
|
1715
|
+
const TAU = 2 * Math.PI;
|
|
1716
|
+
const k0 = Math.floor((from - Math.PI) / TAU) - 1;
|
|
1717
|
+
const k1 = Math.ceil((to - Math.PI) / TAU) + 1;
|
|
1718
|
+
for (let k = k0; k <= k1; k++) {
|
|
1719
|
+
const lo = Math.max(from, Math.PI + k * TAU);
|
|
1720
|
+
const hi = Math.min(to, TAU + k * TAU);
|
|
1721
|
+
if (hi - lo > 1e-6) out.push([lo, hi]);
|
|
1722
|
+
}
|
|
1723
|
+
return out;
|
|
1724
|
+
}
|
|
1725
|
+
/** Points along an arc from `a0` by its sweep to `a1`, one every 3.75°. */
|
|
1726
|
+
function arcPoints(a0, a1, at) {
|
|
1727
|
+
const n = Math.max(2, Math.ceil(Math.abs(a1 - a0) / (Math.PI / 48)));
|
|
1728
|
+
const out = [];
|
|
1729
|
+
for (let k = 0; k <= n; k++) out.push(at(a0 + (a1 - a0) * k / n));
|
|
1730
|
+
return out;
|
|
1731
|
+
}
|
|
1732
|
+
/** `hex` with each channel scaled by `f` — a darker shade of it below 1. */
|
|
1733
|
+
function shadeHex(hex, f) {
|
|
1734
|
+
const n = parseInt(hex, 16);
|
|
1735
|
+
const ch = (shift) => Math.max(0, Math.min(255, Math.round((n >> shift & 255) * f))).toString(16).padStart(2, "0");
|
|
1736
|
+
return `${ch(16)}${ch(8)}${ch(0)}`.toUpperCase();
|
|
1737
|
+
}
|
|
1738
|
+
/** The room kept between a pie and a label set outside it. */
|
|
1739
|
+
var PIE_LABEL_GAP = 6;
|
|
1740
|
+
/** The most labels round a pie that are spread apart (see buildPieScene). */
|
|
1741
|
+
var MOST_SPREAD_LABELS = 64;
|
|
1742
|
+
/**
|
|
1743
|
+
* §21.2.2.48 — each slice's label, measured and placed: inside where the
|
|
1744
|
+
* position asks for it, outside for `outEnd`, and for `bestFit`, Excel's
|
|
1745
|
+
* default, inside where it fits across the slice and outside where it does
|
|
1746
|
+
* not — wrapped at its spaces to under a third of the chart's width, as
|
|
1747
|
+
* 45544.xlsx's function names stand beside their slices.
|
|
1748
|
+
*/
|
|
1749
|
+
function pieLabelBoxes(chart, series, values, total, sweeps, starts, fullR, wPt, measure) {
|
|
1750
|
+
const face = faceOf(chart, "dataLabels");
|
|
1751
|
+
const lineH = face.sizePt * 1.2;
|
|
1752
|
+
const percents = wholePercents(values, total);
|
|
1753
|
+
const widest = (lines) => lines.reduce((m, l) => Math.max(m, widthIn(measure, l, face)), 0);
|
|
1754
|
+
const out = [];
|
|
1755
|
+
for (let i = 0; i < values.length; i++) {
|
|
1756
|
+
const v = values[i] ?? 0;
|
|
1757
|
+
if (v <= 0) continue;
|
|
1758
|
+
const custom = atPoint(series.pointLabels, i)?.text;
|
|
1759
|
+
const text = custom ?? sliceLabel(chart, series, i, v, total, percents[i] ?? 0);
|
|
1760
|
+
if (text === void 0 || text.length === 0) continue;
|
|
1761
|
+
const placement = atPoint(series.pointLabelPlacements, i);
|
|
1762
|
+
const position = placement?.position ?? chart.dataLabels?.position ?? "bestFit";
|
|
1763
|
+
const sweep = Math.abs(sweeps[i] ?? 0);
|
|
1764
|
+
const mid = (starts[i] ?? 0) - sweep / 2;
|
|
1765
|
+
let lines = text.split("\n");
|
|
1766
|
+
let width = widest(lines);
|
|
1767
|
+
const across = sweep >= Math.PI ? fullR * 1.6 : 2 * fullR * .6 * Math.sin(Math.min(sweep, Math.PI) / 2);
|
|
1768
|
+
let fits = width + 4 <= across && lines.length * lineH <= fullR * .7;
|
|
1769
|
+
const dragged = placement !== void 0 && !placement.edge && (placement.x !== void 0 || placement.y !== void 0);
|
|
1770
|
+
if (!fits && position === "bestFit" && !dragged) {
|
|
1771
|
+
const wrapped = lines.flatMap((line) => wrapAtSpaces(line, Math.min(across * .9, wPt * .3), (t) => widthIn(measure, t, face)));
|
|
1772
|
+
const wrappedWidth = widest(wrapped);
|
|
1773
|
+
if (wrappedWidth + 4 <= across && wrapped.length * lineH <= fullR * .7) {
|
|
1774
|
+
lines = wrapped;
|
|
1775
|
+
width = wrappedWidth;
|
|
1776
|
+
fits = true;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
const where = chart.doughnut ? "in" : position === "outEnd" || position === "bestFit" && !fits ? "out" : position === "ctr" || position === "inBase" ? "ctr" : position === "inEnd" ? "end" : "in";
|
|
1780
|
+
if (where === "out") {
|
|
1781
|
+
const room = Math.max(40, wPt * .3);
|
|
1782
|
+
lines = lines.flatMap((line) => wrapAtSpaces(line, room, (t) => widthIn(measure, t, face)));
|
|
1783
|
+
width = widest(lines);
|
|
1784
|
+
}
|
|
1785
|
+
out.push({
|
|
1786
|
+
index: i,
|
|
1787
|
+
mid,
|
|
1788
|
+
lines,
|
|
1789
|
+
width,
|
|
1790
|
+
height: lines.length * lineH,
|
|
1791
|
+
where,
|
|
1792
|
+
...placement && (placement.x !== void 0 || placement.y !== void 0) ? { placement: {
|
|
1793
|
+
...placement.x !== void 0 ? { x: placement.x } : {},
|
|
1794
|
+
...placement.y !== void 0 ? { y: placement.y } : {},
|
|
1795
|
+
...placement.edge ? { edge: true } : {}
|
|
1796
|
+
} } : {},
|
|
1797
|
+
custom: custom !== void 0
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
return out;
|
|
1801
|
+
}
|
|
1802
|
+
/** A line broken at its spaces into lines no wider than `room` where a break allows. */
|
|
1803
|
+
function wrapAtSpaces(line, room, width) {
|
|
1804
|
+
const words = line.split(" ");
|
|
1805
|
+
const out = [];
|
|
1806
|
+
let cur = "";
|
|
1807
|
+
for (const word of words) {
|
|
1808
|
+
const longer = cur ? `${cur} ${word}` : word;
|
|
1809
|
+
if (cur && width(longer) > room) {
|
|
1810
|
+
out.push(cur);
|
|
1811
|
+
cur = word;
|
|
1812
|
+
} else cur = longer;
|
|
1813
|
+
}
|
|
1814
|
+
out.push(cur);
|
|
1815
|
+
return out;
|
|
1816
|
+
}
|
|
1004
1817
|
function pointColor(series, idx) {
|
|
1005
|
-
return series.pointColors
|
|
1818
|
+
return atPoint(series.pointColors, idx)?.colorHex;
|
|
1006
1819
|
}
|
|
1007
|
-
|
|
1820
|
+
/** Each override list's entries by point index, built on first use. */
|
|
1821
|
+
var overridesByPoint = /* @__PURE__ */ new WeakMap();
|
|
1822
|
+
/**
|
|
1823
|
+
* The first of `overrides` for the point at `idx` — looked up, not searched
|
|
1824
|
+
* for: a series that overrides many of its points, searched through for every
|
|
1825
|
+
* one of them, took the square of their number.
|
|
1826
|
+
*/
|
|
1827
|
+
function atPoint(overrides, idx) {
|
|
1828
|
+
if (!overrides) return void 0;
|
|
1829
|
+
let byPoint = overridesByPoint.get(overrides);
|
|
1830
|
+
if (!byPoint) {
|
|
1831
|
+
const index = /* @__PURE__ */ new Map();
|
|
1832
|
+
for (const o of overrides) if (!index.has(o.idx)) index.set(o.idx, o);
|
|
1833
|
+
overridesByPoint.set(overrides, index);
|
|
1834
|
+
byPoint = index;
|
|
1835
|
+
}
|
|
1836
|
+
return byPoint.get(idx);
|
|
1837
|
+
}
|
|
1838
|
+
function layoutLegend(entries, hasLegend, pos, wPt, hPt, measure, face, marker = "box") {
|
|
1008
1839
|
if (!hasLegend || entries.length === 0) return {
|
|
1009
1840
|
rightWidth: 0,
|
|
1010
1841
|
bottomHeight: 0,
|
|
1011
1842
|
emit: () => {}
|
|
1012
1843
|
};
|
|
1013
|
-
const sw =
|
|
1844
|
+
const sw = face.sizePt;
|
|
1014
1845
|
const gap = 4;
|
|
1015
1846
|
const keyW = sw * 2.2;
|
|
1016
1847
|
const keyH = sw * .55;
|
|
@@ -1027,9 +1858,9 @@ function layoutLegend(entries, hasLegend, pos, wPt, hPt, measure, marker = "box"
|
|
|
1027
1858
|
h: keyH,
|
|
1028
1859
|
fillHex: e.colorHex
|
|
1029
1860
|
};
|
|
1030
|
-
const entryW = (e) => keyW + 3 + measure
|
|
1861
|
+
const entryW = (e) => keyW + 3 + widthIn(measure, e.name, face) + 8;
|
|
1031
1862
|
if (pos === "r" || pos === "l") {
|
|
1032
|
-
const colW = Math.max(
|
|
1863
|
+
const colW = entries.reduce((w, e) => Math.max(w, entryW(e)), 0);
|
|
1033
1864
|
return {
|
|
1034
1865
|
rightWidth: pos === "r" ? colW : 0,
|
|
1035
1866
|
bottomHeight: 0,
|
|
@@ -1042,8 +1873,7 @@ function layoutLegend(entries, hasLegend, pos, wPt, hPt, measure, marker = "box"
|
|
|
1042
1873
|
text: e.name,
|
|
1043
1874
|
x: lx + keyW + 3,
|
|
1044
1875
|
y: ly + 1,
|
|
1045
|
-
|
|
1046
|
-
colorHex: LABEL_COLOR,
|
|
1876
|
+
...face,
|
|
1047
1877
|
align: "left"
|
|
1048
1878
|
});
|
|
1049
1879
|
ly -= sw + 4;
|
|
@@ -1063,9 +1893,8 @@ function layoutLegend(entries, hasLegend, pos, wPt, hPt, measure, marker = "box"
|
|
|
1063
1893
|
labels.push({
|
|
1064
1894
|
text: e.name,
|
|
1065
1895
|
x: lx + keyW + 3,
|
|
1066
|
-
y:
|
|
1067
|
-
|
|
1068
|
-
colorHex: LABEL_COLOR,
|
|
1896
|
+
y: 3,
|
|
1897
|
+
...face,
|
|
1069
1898
|
align: "left"
|
|
1070
1899
|
});
|
|
1071
1900
|
lx += entryW(e);
|
|
@@ -1100,7 +1929,7 @@ function buildChartScene(chart, wPt, hPt, measure) {
|
|
|
1100
1929
|
* in reverse.
|
|
1101
1930
|
*/
|
|
1102
1931
|
function withReversedCategories(chart) {
|
|
1103
|
-
const n =
|
|
1932
|
+
const n = catCount(chart);
|
|
1104
1933
|
const flip = (i) => n - 1 - i;
|
|
1105
1934
|
return {
|
|
1106
1935
|
...chart,
|
|
@@ -1115,6 +1944,10 @@ function withReversedCategories(chart) {
|
|
|
1115
1944
|
...s.pointLabels ? { pointLabels: s.pointLabels.map((p) => ({
|
|
1116
1945
|
...p,
|
|
1117
1946
|
idx: flip(p.idx)
|
|
1947
|
+
})) } : {},
|
|
1948
|
+
...s.pointLabelPlacements ? { pointLabelPlacements: s.pointLabelPlacements.map((p) => ({
|
|
1949
|
+
...p,
|
|
1950
|
+
idx: flip(p.idx)
|
|
1118
1951
|
})) } : {}
|
|
1119
1952
|
}))
|
|
1120
1953
|
};
|
|
@@ -1158,4 +1991,4 @@ function withFrame(scene, chart, wPt, hPt) {
|
|
|
1158
1991
|
};
|
|
1159
1992
|
}
|
|
1160
1993
|
//#endregion
|
|
1161
|
-
export { buildChartScene, legendSeriesName };
|
|
1994
|
+
export { SERIES_COLORS, buildAreaScene, buildBarScene, buildChartScene, buildLineScene, buildPieScene, buildScatterScene, formatTick, legendSeriesName, niceScale, seriesColor };
|