@opendata-ai/openchart-engine 6.11.0 → 6.13.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/dist/index.d.ts +7 -0
- package/dist/index.js +944 -629
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__test-fixtures__/specs.ts +3 -0
- package/src/__tests__/axes.test.ts +12 -30
- package/src/__tests__/compile-chart.test.ts +4 -4
- package/src/__tests__/dimensions.test.ts +2 -2
- package/src/__tests__/encoding-sugar.test.ts +389 -0
- package/src/annotations/collisions.ts +268 -0
- package/src/annotations/compute.ts +9 -912
- package/src/annotations/constants.ts +32 -0
- package/src/annotations/geometry.ts +167 -0
- package/src/annotations/position.ts +95 -0
- package/src/annotations/resolve-range.ts +98 -0
- package/src/annotations/resolve-refline.ts +148 -0
- package/src/annotations/resolve-text.ts +134 -0
- package/src/charts/__tests__/post-process.test.ts +258 -0
- package/src/charts/bar/__tests__/labels.test.ts +31 -0
- package/src/charts/bar/compute.ts +27 -6
- package/src/charts/bar/labels.ts +7 -1
- package/src/charts/column/__tests__/compute.test.ts +99 -0
- package/src/charts/column/compute.ts +27 -6
- package/src/charts/line/area.ts +19 -2
- package/src/charts/post-process.ts +215 -0
- package/src/compile.ts +113 -169
- package/src/compiler/__tests__/normalize.test.ts +110 -0
- package/src/compiler/normalize.ts +22 -3
- package/src/compiler/types.ts +4 -0
- package/src/graphs/compile-graph.ts +8 -0
- package/src/graphs/types.ts +2 -0
- package/src/layout/axes.ts +10 -13
- package/src/layout/dimensions.ts +6 -3
- package/src/layout/scales.ts +106 -29
- package/src/legend/compute.ts +3 -1
- package/src/sankey/compile-sankey.ts +12 -2
- package/src/sankey/types.ts +1 -0
- package/src/tables/compile-table.ts +5 -0
- package/src/tooltips/__tests__/compute.test.ts +188 -0
- package/src/tooltips/compute.ts +25 -11
- package/src/transforms/__tests__/aggregate.test.ts +159 -0
- package/src/transforms/__tests__/fold.test.ts +79 -0
- package/src/transforms/aggregate.ts +130 -0
- package/src/transforms/fold.ts +49 -0
- package/src/transforms/index.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -11,8 +11,10 @@ import {
|
|
|
11
11
|
resolveTheme as resolveTheme3
|
|
12
12
|
} from "@opendata-ai/openchart-core";
|
|
13
13
|
|
|
14
|
-
// src/annotations/
|
|
15
|
-
import { detectCollision
|
|
14
|
+
// src/annotations/collisions.ts
|
|
15
|
+
import { detectCollision } from "@opendata-ai/openchart-core";
|
|
16
|
+
|
|
17
|
+
// src/annotations/constants.ts
|
|
16
18
|
var DEFAULT_ANNOTATION_FONT_SIZE = 12;
|
|
17
19
|
var DEFAULT_ANNOTATION_FONT_WEIGHT = 400;
|
|
18
20
|
var DEFAULT_LINE_HEIGHT = 1.3;
|
|
@@ -24,76 +26,11 @@ var DARK_TEXT_FILL = "#d1d5db";
|
|
|
24
26
|
var LIGHT_REFLINE_STROKE = "#888888";
|
|
25
27
|
var DARK_REFLINE_STROKE = "#9ca3af";
|
|
26
28
|
var ANCHOR_OFFSET = 8;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
let lower = 0;
|
|
33
|
-
let upper = sorted.length - 1;
|
|
34
|
-
for (let i = 0; i < sorted.length; i++) {
|
|
35
|
-
if (sorted[i].n <= numValue) lower = i;
|
|
36
|
-
if (sorted[i].n >= numValue) {
|
|
37
|
-
upper = i;
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const lowerPos = positionOf(domain[sorted[lower].i]);
|
|
42
|
-
const upperPos = positionOf(domain[sorted[upper].i]);
|
|
43
|
-
if (lower === upper) return lowerPos;
|
|
44
|
-
const t = (numValue - sorted[lower].n) / (sorted[upper].n - sorted[lower].n);
|
|
45
|
-
return lowerPos + t * (upperPos - lowerPos);
|
|
46
|
-
}
|
|
47
|
-
function resolvePosition(value2, scale) {
|
|
48
|
-
if (!scale) return null;
|
|
49
|
-
const s = scale.scale;
|
|
50
|
-
const type = scale.type;
|
|
51
|
-
if (type === "time") {
|
|
52
|
-
const date2 = new Date(String(value2));
|
|
53
|
-
if (Number.isNaN(date2.getTime())) return null;
|
|
54
|
-
return s(date2);
|
|
55
|
-
}
|
|
56
|
-
if (type === "linear" || type === "log") {
|
|
57
|
-
const num = typeof value2 === "number" ? value2 : Number(value2);
|
|
58
|
-
if (!Number.isFinite(num)) return null;
|
|
59
|
-
return s(num);
|
|
60
|
-
}
|
|
61
|
-
if (type === "band") {
|
|
62
|
-
const bandScale = s;
|
|
63
|
-
const strValue2 = String(value2);
|
|
64
|
-
const pos = bandScale(strValue2);
|
|
65
|
-
if (pos !== void 0) return pos + (bandScale.bandwidth?.() ?? 0) / 2;
|
|
66
|
-
const bw = bandScale.bandwidth?.() ?? 0;
|
|
67
|
-
return interpolateInDomain(
|
|
68
|
-
Number(strValue2),
|
|
69
|
-
bandScale.domain(),
|
|
70
|
-
(entry) => (bandScale(entry) ?? 0) + bw / 2
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
const strValue = String(value2);
|
|
74
|
-
const directResult = s(strValue);
|
|
75
|
-
if (directResult !== void 0) return directResult;
|
|
76
|
-
if (type === "point" || type === "ordinal") {
|
|
77
|
-
const domain = s.domain();
|
|
78
|
-
return interpolateInDomain(
|
|
79
|
-
Number(strValue),
|
|
80
|
-
domain,
|
|
81
|
-
(entry) => s(entry) ?? 0
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
function makeAnnotationLabelStyle(fontSize, fontWeight, fill, isDark) {
|
|
87
|
-
const defaultFill = isDark ? DARK_TEXT_FILL : LIGHT_TEXT_FILL;
|
|
88
|
-
return {
|
|
89
|
-
fontFamily: "Inter, system-ui, sans-serif",
|
|
90
|
-
fontSize: fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE,
|
|
91
|
-
fontWeight: fontWeight ?? DEFAULT_ANNOTATION_FONT_WEIGHT,
|
|
92
|
-
fill: fill ?? defaultFill,
|
|
93
|
-
lineHeight: DEFAULT_LINE_HEIGHT,
|
|
94
|
-
textAnchor: "start"
|
|
95
|
-
};
|
|
96
|
-
}
|
|
29
|
+
var NUDGE_PADDING = 6;
|
|
30
|
+
var CLAMP_MARGIN = 4;
|
|
31
|
+
|
|
32
|
+
// src/annotations/geometry.ts
|
|
33
|
+
import { estimateTextWidth } from "@opendata-ai/openchart-core";
|
|
97
34
|
function computeTextBounds(labelX, labelY, text, fontSize, fontWeight) {
|
|
98
35
|
const lines = text.split("\n");
|
|
99
36
|
const isMultiLine = lines.length > 1;
|
|
@@ -149,230 +86,92 @@ function computeConnectorOrigin(labelX, labelY, text, fontSize, fontWeight, targ
|
|
|
149
86
|
}
|
|
150
87
|
return ndx < 0 ? { x: box.x, y: boxCenterY } : { x: box.x + box.width, y: boxCenterY };
|
|
151
88
|
}
|
|
152
|
-
function
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const showConnector = annotation.connector !== false;
|
|
168
|
-
const connectorStyle = annotation.connector === "curve" ? "curve" : "straight";
|
|
169
|
-
const fontSize = annotation.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
|
|
170
|
-
const fontWeight = annotation.fontWeight ?? DEFAULT_ANNOTATION_FONT_WEIGHT;
|
|
171
|
-
const { x: connectorFromX, y: connectorFromY } = computeConnectorOrigin(
|
|
172
|
-
labelX,
|
|
173
|
-
labelY,
|
|
174
|
-
annotation.text,
|
|
89
|
+
function estimateLabelBounds(label) {
|
|
90
|
+
const fontSize = label.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
|
|
91
|
+
const fontWeight = label.style.fontWeight ?? DEFAULT_ANNOTATION_FONT_WEIGHT;
|
|
92
|
+
return computeTextBounds(label.x, label.y, label.text, fontSize, fontWeight);
|
|
93
|
+
}
|
|
94
|
+
function recomputeConnector(label, targetX, targetY) {
|
|
95
|
+
const connector = label.connector;
|
|
96
|
+
if (!connector) return connector;
|
|
97
|
+
const fontSize = label.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
|
|
98
|
+
const fontWeight = label.style.fontWeight ?? DEFAULT_ANNOTATION_FONT_WEIGHT;
|
|
99
|
+
const connStyle = connector.style === "curve" ? "curve" : "straight";
|
|
100
|
+
const newFrom = computeConnectorOrigin(
|
|
101
|
+
label.x,
|
|
102
|
+
label.y,
|
|
103
|
+
label.text,
|
|
175
104
|
fontSize,
|
|
176
105
|
fontWeight,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
106
|
+
targetX,
|
|
107
|
+
targetY,
|
|
108
|
+
connStyle
|
|
180
109
|
);
|
|
181
|
-
|
|
182
|
-
const baseTo = { x: px, y: py };
|
|
183
|
-
const adjustedFrom = {
|
|
184
|
-
x: baseFrom.x + (annotation.connectorOffset?.from?.dx ?? 0),
|
|
185
|
-
y: baseFrom.y + (annotation.connectorOffset?.from?.dy ?? 0)
|
|
186
|
-
};
|
|
187
|
-
const adjustedToRaw = {
|
|
188
|
-
x: baseTo.x + (annotation.connectorOffset?.to?.dx ?? 0),
|
|
189
|
-
y: baseTo.y + (annotation.connectorOffset?.to?.dy ?? 0)
|
|
190
|
-
};
|
|
191
|
-
const GAP = 4;
|
|
192
|
-
const cdx = adjustedToRaw.x - adjustedFrom.x;
|
|
193
|
-
const cdy = adjustedToRaw.y - adjustedFrom.y;
|
|
194
|
-
const dist = Math.sqrt(cdx * cdx + cdy * cdy);
|
|
195
|
-
const adjustedTo = dist > GAP * 2 ? { x: adjustedToRaw.x - cdx / dist * GAP, y: adjustedToRaw.y - cdy / dist * GAP } : adjustedToRaw;
|
|
196
|
-
const label = {
|
|
197
|
-
text: annotation.text,
|
|
198
|
-
x: labelX,
|
|
199
|
-
y: labelY,
|
|
200
|
-
style: labelStyle,
|
|
201
|
-
visible: true,
|
|
202
|
-
connector: showConnector ? {
|
|
203
|
-
from: adjustedFrom,
|
|
204
|
-
to: adjustedTo,
|
|
205
|
-
stroke: annotation.stroke ?? "#999999",
|
|
206
|
-
style: connectorStyle
|
|
207
|
-
} : void 0,
|
|
208
|
-
background: annotation.background
|
|
209
|
-
};
|
|
210
|
-
return {
|
|
211
|
-
type: "text",
|
|
212
|
-
id: annotation.id,
|
|
213
|
-
label,
|
|
214
|
-
stroke: annotation.stroke,
|
|
215
|
-
fill: annotation.fill,
|
|
216
|
-
opacity: annotation.opacity,
|
|
217
|
-
zIndex: annotation.zIndex
|
|
218
|
-
};
|
|
110
|
+
return { ...connector, from: newFrom };
|
|
219
111
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const y2px = resolvePosition(annotation.y2, scales.y);
|
|
235
|
-
if (y1px === null || y2px === null) return null;
|
|
236
|
-
y2 = Math.min(y1px, y2px);
|
|
237
|
-
height = Math.abs(y2px - y1px);
|
|
238
|
-
}
|
|
239
|
-
const rect = { x: x2, y: y2, width, height };
|
|
240
|
-
let label;
|
|
241
|
-
if (annotation.label) {
|
|
242
|
-
const anchor = annotation.labelAnchor ?? "top";
|
|
243
|
-
const centered = anchor === "top" || anchor === "bottom" || anchor === "auto";
|
|
244
|
-
const baseDx = centered ? 0 : anchor === "right" ? -4 : 4;
|
|
245
|
-
const baseDy = 14;
|
|
246
|
-
const labelDelta = applyOffset({ dx: baseDx, dy: baseDy }, annotation.labelOffset);
|
|
247
|
-
const style = makeAnnotationLabelStyle(11, 500, void 0, isDark);
|
|
248
|
-
if (centered) {
|
|
249
|
-
style.textAnchor = "middle";
|
|
250
|
-
} else if (anchor === "right") {
|
|
251
|
-
style.textAnchor = "end";
|
|
112
|
+
|
|
113
|
+
// src/annotations/position.ts
|
|
114
|
+
function interpolateInDomain(numValue, domain, positionOf) {
|
|
115
|
+
if (domain.length < 2) return null;
|
|
116
|
+
const nums = domain.map(Number);
|
|
117
|
+
if (!nums.every(Number.isFinite)) return null;
|
|
118
|
+
const sorted = nums.map((n, i) => ({ n, i })).sort((a, b) => a.n - b.n);
|
|
119
|
+
let lower = 0;
|
|
120
|
+
let upper = sorted.length - 1;
|
|
121
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
122
|
+
if (sorted[i].n <= numValue) lower = i;
|
|
123
|
+
if (sorted[i].n >= numValue) {
|
|
124
|
+
upper = i;
|
|
125
|
+
break;
|
|
252
126
|
}
|
|
253
|
-
const baseX = centered ? x2 + width / 2 : anchor === "right" ? x2 + width : x2;
|
|
254
|
-
label = {
|
|
255
|
-
text: annotation.label,
|
|
256
|
-
x: baseX + labelDelta.dx,
|
|
257
|
-
y: y2 + labelDelta.dy,
|
|
258
|
-
style,
|
|
259
|
-
visible: true
|
|
260
|
-
};
|
|
261
127
|
}
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
label,
|
|
268
|
-
fill: annotation.fill ?? DEFAULT_RANGE_FILL,
|
|
269
|
-
opacity: annotation.opacity ?? defaultOpacity,
|
|
270
|
-
stroke: annotation.stroke,
|
|
271
|
-
zIndex: annotation.zIndex
|
|
272
|
-
};
|
|
128
|
+
const lowerPos = positionOf(domain[sorted[lower].i]);
|
|
129
|
+
const upperPos = positionOf(domain[sorted[upper].i]);
|
|
130
|
+
if (lower === upper) return lowerPos;
|
|
131
|
+
const t = (numValue - sorted[lower].n) / (sorted[upper].n - sorted[lower].n);
|
|
132
|
+
return lowerPos + t * (upperPos - lowerPos);
|
|
273
133
|
}
|
|
274
|
-
function
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
} else if (annotation.x !== void 0) {
|
|
283
|
-
const xPx = resolvePosition(annotation.x, scales.x);
|
|
284
|
-
if (xPx === null) return null;
|
|
285
|
-
start = { x: xPx, y: chartArea.y };
|
|
286
|
-
end = { x: xPx, y: chartArea.y + chartArea.height };
|
|
287
|
-
} else {
|
|
288
|
-
return null;
|
|
134
|
+
function resolvePosition(value2, scale) {
|
|
135
|
+
if (!scale) return null;
|
|
136
|
+
const s = scale.scale;
|
|
137
|
+
const type = scale.type;
|
|
138
|
+
if (type === "time") {
|
|
139
|
+
const date2 = new Date(String(value2));
|
|
140
|
+
if (Number.isNaN(date2.getTime())) return null;
|
|
141
|
+
return s(date2);
|
|
289
142
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
strokeDasharray = "2 2";
|
|
143
|
+
if (type === "linear" || type === "log") {
|
|
144
|
+
const num = typeof value2 === "number" ? value2 : Number(value2);
|
|
145
|
+
if (!Number.isFinite(num)) return null;
|
|
146
|
+
return s(num);
|
|
295
147
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
baseDx = 4;
|
|
308
|
-
baseDy = -4;
|
|
309
|
-
labelX = start.x;
|
|
310
|
-
labelY = start.y;
|
|
311
|
-
textAnchor = "start";
|
|
312
|
-
} else if (anchor === "bottom") {
|
|
313
|
-
baseDx = -4;
|
|
314
|
-
baseDy = 14;
|
|
315
|
-
labelX = end.x;
|
|
316
|
-
labelY = end.y;
|
|
317
|
-
textAnchor = "end";
|
|
318
|
-
} else {
|
|
319
|
-
baseDx = -4;
|
|
320
|
-
baseDy = -4;
|
|
321
|
-
labelX = end.x;
|
|
322
|
-
labelY = end.y;
|
|
323
|
-
textAnchor = "end";
|
|
324
|
-
}
|
|
325
|
-
} else {
|
|
326
|
-
if (anchor === "right") {
|
|
327
|
-
baseDx = -4;
|
|
328
|
-
baseDy = 14;
|
|
329
|
-
labelX = start.x;
|
|
330
|
-
labelY = start.y;
|
|
331
|
-
textAnchor = "end";
|
|
332
|
-
} else if (anchor === "bottom") {
|
|
333
|
-
baseDx = 4;
|
|
334
|
-
baseDy = -4;
|
|
335
|
-
labelX = start.x;
|
|
336
|
-
labelY = end.y;
|
|
337
|
-
textAnchor = "start";
|
|
338
|
-
} else {
|
|
339
|
-
baseDx = 4;
|
|
340
|
-
baseDy = 14;
|
|
341
|
-
labelX = start.x;
|
|
342
|
-
labelY = start.y;
|
|
343
|
-
textAnchor = "start";
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
const labelDelta = applyOffset({ dx: baseDx, dy: baseDy }, annotation.labelOffset);
|
|
347
|
-
const defaultStroke2 = isDark ? DARK_REFLINE_STROKE : LIGHT_REFLINE_STROKE;
|
|
348
|
-
const style = makeAnnotationLabelStyle(11, 400, annotation.stroke ?? defaultStroke2, isDark);
|
|
349
|
-
style.textAnchor = textAnchor;
|
|
350
|
-
label = {
|
|
351
|
-
text: annotation.label,
|
|
352
|
-
x: labelX + labelDelta.dx,
|
|
353
|
-
y: labelY + labelDelta.dy,
|
|
354
|
-
style,
|
|
355
|
-
visible: true
|
|
356
|
-
};
|
|
148
|
+
if (type === "band") {
|
|
149
|
+
const bandScale = s;
|
|
150
|
+
const strValue2 = String(value2);
|
|
151
|
+
const pos = bandScale(strValue2);
|
|
152
|
+
if (pos !== void 0) return pos + (bandScale.bandwidth?.() ?? 0) / 2;
|
|
153
|
+
const bw = bandScale.bandwidth?.() ?? 0;
|
|
154
|
+
return interpolateInDomain(
|
|
155
|
+
Number(strValue2),
|
|
156
|
+
bandScale.domain(),
|
|
157
|
+
(entry) => (bandScale(entry) ?? 0) + bw / 2
|
|
158
|
+
);
|
|
357
159
|
}
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function estimateLabelBounds(label) {
|
|
371
|
-
const fontSize = label.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
|
|
372
|
-
const fontWeight = label.style.fontWeight ?? DEFAULT_ANNOTATION_FONT_WEIGHT;
|
|
373
|
-
return computeTextBounds(label.x, label.y, label.text, fontSize, fontWeight);
|
|
160
|
+
const strValue = String(value2);
|
|
161
|
+
const directResult = s(strValue);
|
|
162
|
+
if (directResult !== void 0) return directResult;
|
|
163
|
+
if (type === "point" || type === "ordinal") {
|
|
164
|
+
const domain = s.domain();
|
|
165
|
+
return interpolateInDomain(
|
|
166
|
+
Number(strValue),
|
|
167
|
+
domain,
|
|
168
|
+
(entry) => s(entry) ?? 0
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
374
172
|
}
|
|
375
|
-
|
|
173
|
+
|
|
174
|
+
// src/annotations/collisions.ts
|
|
376
175
|
function generateNudgeCandidates(selfBounds, obstacles, padding) {
|
|
377
176
|
const candidates = [];
|
|
378
177
|
for (const obs of obstacles) {
|
|
@@ -403,28 +202,11 @@ function nudgeAnnotationFromObstacles(annotation, originalAnnotation, scales, ch
|
|
|
403
202
|
for (const { dx, dy } of candidates) {
|
|
404
203
|
const newLabelX = annotation.label.x + dx;
|
|
405
204
|
const newLabelY = annotation.label.y + dy;
|
|
406
|
-
let newConnector = annotation.label.connector;
|
|
407
|
-
if (newConnector) {
|
|
408
|
-
const annFontSize = annotation.label.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
|
|
409
|
-
const annFontWeight = annotation.label.style.fontWeight ?? DEFAULT_ANNOTATION_FONT_WEIGHT;
|
|
410
|
-
const connStyle = newConnector.style === "curve" ? "curve" : "straight";
|
|
411
|
-
const newFrom = computeConnectorOrigin(
|
|
412
|
-
newLabelX,
|
|
413
|
-
newLabelY,
|
|
414
|
-
annotation.label.text,
|
|
415
|
-
annFontSize,
|
|
416
|
-
annFontWeight,
|
|
417
|
-
px,
|
|
418
|
-
py,
|
|
419
|
-
connStyle
|
|
420
|
-
);
|
|
421
|
-
newConnector = { ...newConnector, from: newFrom };
|
|
422
|
-
}
|
|
423
205
|
const candidateLabel = {
|
|
424
206
|
...annotation.label,
|
|
425
207
|
x: newLabelX,
|
|
426
208
|
y: newLabelY,
|
|
427
|
-
connector:
|
|
209
|
+
connector: recomputeConnector({ ...annotation.label, x: newLabelX, y: newLabelY }, px, py)
|
|
428
210
|
};
|
|
429
211
|
const candidateBounds = estimateLabelBounds(candidateLabel);
|
|
430
212
|
const stillCollides = obstacles.some(
|
|
@@ -477,28 +259,15 @@ function resolveAnnotationCollisions(annotations, originalSpecs, scales, chartAr
|
|
|
477
259
|
const labelCenterY = candidateBounds.y + candidateBounds.height / 2;
|
|
478
260
|
const inBounds = labelCenterX >= chartArea.x && labelCenterX <= chartArea.x + chartArea.width + 10 && labelCenterY >= chartArea.y - fontSize && labelCenterY <= chartArea.y + chartArea.height + fontSize;
|
|
479
261
|
if (inBounds) {
|
|
480
|
-
let newConnector = annotation.label.connector;
|
|
481
|
-
if (newConnector) {
|
|
482
|
-
const annFontSize = annotation.label.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
|
|
483
|
-
const annFontWeight = annotation.label.style.fontWeight ?? DEFAULT_ANNOTATION_FONT_WEIGHT;
|
|
484
|
-
const connStyle = newConnector.style === "curve" ? "curve" : "straight";
|
|
485
|
-
const newFrom = computeConnectorOrigin(
|
|
486
|
-
newLabelX,
|
|
487
|
-
newLabelY,
|
|
488
|
-
annotation.label.text,
|
|
489
|
-
annFontSize,
|
|
490
|
-
annFontWeight,
|
|
491
|
-
px,
|
|
492
|
-
py,
|
|
493
|
-
connStyle
|
|
494
|
-
);
|
|
495
|
-
newConnector = { ...newConnector, from: newFrom };
|
|
496
|
-
}
|
|
497
262
|
annotation.label = {
|
|
498
263
|
...annotation.label,
|
|
499
264
|
x: newLabelX,
|
|
500
265
|
y: newLabelY,
|
|
501
|
-
connector:
|
|
266
|
+
connector: recomputeConnector(
|
|
267
|
+
{ ...annotation.label, x: newLabelX, y: newLabelY },
|
|
268
|
+
px,
|
|
269
|
+
py
|
|
270
|
+
)
|
|
502
271
|
};
|
|
503
272
|
break;
|
|
504
273
|
}
|
|
@@ -509,7 +278,6 @@ function resolveAnnotationCollisions(annotations, originalSpecs, scales, chartAr
|
|
|
509
278
|
placedBounds.push(estimateLabelBounds(annotation.label));
|
|
510
279
|
}
|
|
511
280
|
}
|
|
512
|
-
var CLAMP_MARGIN = 4;
|
|
513
281
|
function clampAnnotationsToBounds(annotations, svgWidth, svgHeight) {
|
|
514
282
|
for (const annotation of annotations) {
|
|
515
283
|
if (annotation.type !== "text" || !annotation.label) continue;
|
|
@@ -528,34 +296,259 @@ function clampAnnotationsToBounds(annotations, svgWidth, svgHeight) {
|
|
|
528
296
|
if (bounds.y + bounds.height + dy > svgHeight - CLAMP_MARGIN) {
|
|
529
297
|
dy = svgHeight - CLAMP_MARGIN - (bounds.y + bounds.height);
|
|
530
298
|
}
|
|
531
|
-
if (dx === 0 && dy === 0) continue;
|
|
532
|
-
const newX = annotation.label.x + dx;
|
|
533
|
-
const newY = annotation.label.y + dy;
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
newX,
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
299
|
+
if (dx === 0 && dy === 0) continue;
|
|
300
|
+
const newX = annotation.label.x + dx;
|
|
301
|
+
const newY = annotation.label.y + dy;
|
|
302
|
+
const connector = annotation.label.connector;
|
|
303
|
+
annotation.label = {
|
|
304
|
+
...annotation.label,
|
|
305
|
+
x: newX,
|
|
306
|
+
y: newY,
|
|
307
|
+
connector: connector ? recomputeConnector(
|
|
308
|
+
{ ...annotation.label, x: newX, y: newY },
|
|
309
|
+
connector.to.x,
|
|
310
|
+
connector.to.y
|
|
311
|
+
) : void 0
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/annotations/resolve-text.ts
|
|
317
|
+
function makeAnnotationLabelStyle(fontSize, fontWeight, fill, isDark) {
|
|
318
|
+
const defaultFill = isDark ? DARK_TEXT_FILL : LIGHT_TEXT_FILL;
|
|
319
|
+
return {
|
|
320
|
+
fontFamily: "Inter, system-ui, sans-serif",
|
|
321
|
+
fontSize: fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE,
|
|
322
|
+
fontWeight: fontWeight ?? DEFAULT_ANNOTATION_FONT_WEIGHT,
|
|
323
|
+
fill: fill ?? defaultFill,
|
|
324
|
+
lineHeight: DEFAULT_LINE_HEIGHT,
|
|
325
|
+
textAnchor: "start"
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function resolveTextAnnotation(annotation, scales, chartArea, isDark) {
|
|
329
|
+
const px = resolvePosition(annotation.x, scales.x);
|
|
330
|
+
const py = resolvePosition(annotation.y, scales.y);
|
|
331
|
+
if (px === null || py === null) return null;
|
|
332
|
+
const defaultTextFill = isDark ? DARK_TEXT_FILL : LIGHT_TEXT_FILL;
|
|
333
|
+
const labelStyle = makeAnnotationLabelStyle(
|
|
334
|
+
annotation.fontSize,
|
|
335
|
+
annotation.fontWeight,
|
|
336
|
+
annotation.fill ?? defaultTextFill,
|
|
337
|
+
isDark
|
|
338
|
+
);
|
|
339
|
+
const anchorDelta = computeAnchorOffset(annotation.anchor, px, py, chartArea);
|
|
340
|
+
const finalDelta = applyOffset(anchorDelta, annotation.offset);
|
|
341
|
+
const labelX = px + finalDelta.dx;
|
|
342
|
+
const labelY = py + finalDelta.dy;
|
|
343
|
+
const showConnector = annotation.connector !== false;
|
|
344
|
+
const connectorStyle = annotation.connector === "curve" ? "curve" : "straight";
|
|
345
|
+
const fontSize = annotation.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
|
|
346
|
+
const fontWeight = annotation.fontWeight ?? DEFAULT_ANNOTATION_FONT_WEIGHT;
|
|
347
|
+
const { x: connectorFromX, y: connectorFromY } = computeConnectorOrigin(
|
|
348
|
+
labelX,
|
|
349
|
+
labelY,
|
|
350
|
+
annotation.text,
|
|
351
|
+
fontSize,
|
|
352
|
+
fontWeight,
|
|
353
|
+
px,
|
|
354
|
+
py,
|
|
355
|
+
connectorStyle
|
|
356
|
+
);
|
|
357
|
+
const baseFrom = { x: connectorFromX, y: connectorFromY };
|
|
358
|
+
const baseTo = { x: px, y: py };
|
|
359
|
+
const adjustedFrom = {
|
|
360
|
+
x: baseFrom.x + (annotation.connectorOffset?.from?.dx ?? 0),
|
|
361
|
+
y: baseFrom.y + (annotation.connectorOffset?.from?.dy ?? 0)
|
|
362
|
+
};
|
|
363
|
+
const adjustedToRaw = {
|
|
364
|
+
x: baseTo.x + (annotation.connectorOffset?.to?.dx ?? 0),
|
|
365
|
+
y: baseTo.y + (annotation.connectorOffset?.to?.dy ?? 0)
|
|
366
|
+
};
|
|
367
|
+
const GAP = 4;
|
|
368
|
+
const cdx = adjustedToRaw.x - adjustedFrom.x;
|
|
369
|
+
const cdy = adjustedToRaw.y - adjustedFrom.y;
|
|
370
|
+
const dist = Math.sqrt(cdx * cdx + cdy * cdy);
|
|
371
|
+
const adjustedTo = dist > GAP * 2 ? { x: adjustedToRaw.x - cdx / dist * GAP, y: adjustedToRaw.y - cdy / dist * GAP } : adjustedToRaw;
|
|
372
|
+
const label = {
|
|
373
|
+
text: annotation.text,
|
|
374
|
+
x: labelX,
|
|
375
|
+
y: labelY,
|
|
376
|
+
style: labelStyle,
|
|
377
|
+
visible: true,
|
|
378
|
+
connector: showConnector ? {
|
|
379
|
+
from: adjustedFrom,
|
|
380
|
+
to: adjustedTo,
|
|
381
|
+
stroke: annotation.stroke ?? "#999999",
|
|
382
|
+
style: connectorStyle
|
|
383
|
+
} : void 0,
|
|
384
|
+
background: annotation.background
|
|
385
|
+
};
|
|
386
|
+
return {
|
|
387
|
+
type: "text",
|
|
388
|
+
id: annotation.id,
|
|
389
|
+
label,
|
|
390
|
+
stroke: annotation.stroke,
|
|
391
|
+
fill: annotation.fill,
|
|
392
|
+
opacity: annotation.opacity,
|
|
393
|
+
zIndex: annotation.zIndex
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// src/annotations/resolve-range.ts
|
|
398
|
+
function resolveRangeAnnotation(annotation, scales, chartArea, isDark) {
|
|
399
|
+
let x2 = chartArea.x;
|
|
400
|
+
let y2 = chartArea.y;
|
|
401
|
+
let width = chartArea.width;
|
|
402
|
+
let height = chartArea.height;
|
|
403
|
+
if (annotation.x1 !== void 0 && annotation.x2 !== void 0) {
|
|
404
|
+
const x1px = resolvePosition(annotation.x1, scales.x);
|
|
405
|
+
const x2px = resolvePosition(annotation.x2, scales.x);
|
|
406
|
+
if (x1px === null || x2px === null) return null;
|
|
407
|
+
x2 = Math.min(x1px, x2px);
|
|
408
|
+
width = Math.abs(x2px - x1px);
|
|
409
|
+
}
|
|
410
|
+
if (annotation.y1 !== void 0 && annotation.y2 !== void 0) {
|
|
411
|
+
const y1px = resolvePosition(annotation.y1, scales.y);
|
|
412
|
+
const y2px = resolvePosition(annotation.y2, scales.y);
|
|
413
|
+
if (y1px === null || y2px === null) return null;
|
|
414
|
+
y2 = Math.min(y1px, y2px);
|
|
415
|
+
height = Math.abs(y2px - y1px);
|
|
416
|
+
}
|
|
417
|
+
const rect = { x: x2, y: y2, width, height };
|
|
418
|
+
let label;
|
|
419
|
+
if (annotation.label) {
|
|
420
|
+
const anchor = annotation.labelAnchor ?? "top";
|
|
421
|
+
const centered = anchor === "top" || anchor === "bottom" || anchor === "auto";
|
|
422
|
+
const baseDx = centered ? 0 : anchor === "right" ? -4 : 4;
|
|
423
|
+
const baseDy = 14;
|
|
424
|
+
const labelDelta = applyOffset({ dx: baseDx, dy: baseDy }, annotation.labelOffset);
|
|
425
|
+
const style = makeAnnotationLabelStyle(11, 500, void 0, isDark);
|
|
426
|
+
if (centered) {
|
|
427
|
+
style.textAnchor = "middle";
|
|
428
|
+
} else if (anchor === "right") {
|
|
429
|
+
style.textAnchor = "end";
|
|
430
|
+
}
|
|
431
|
+
const baseX = centered ? x2 + width / 2 : anchor === "right" ? x2 + width : x2;
|
|
432
|
+
label = {
|
|
433
|
+
text: annotation.label,
|
|
434
|
+
x: baseX + labelDelta.dx,
|
|
435
|
+
y: y2 + labelDelta.dy,
|
|
436
|
+
style,
|
|
437
|
+
visible: true
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const defaultOpacity = isDark ? 0.2 : DEFAULT_RANGE_OPACITY;
|
|
441
|
+
return {
|
|
442
|
+
type: "range",
|
|
443
|
+
id: annotation.id,
|
|
444
|
+
rect,
|
|
445
|
+
label,
|
|
446
|
+
fill: annotation.fill ?? DEFAULT_RANGE_FILL,
|
|
447
|
+
opacity: annotation.opacity ?? defaultOpacity,
|
|
448
|
+
stroke: annotation.stroke,
|
|
449
|
+
zIndex: annotation.zIndex
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/annotations/resolve-refline.ts
|
|
454
|
+
function resolveRefLineAnnotation(annotation, scales, chartArea, isDark) {
|
|
455
|
+
let start;
|
|
456
|
+
let end;
|
|
457
|
+
if (annotation.y !== void 0) {
|
|
458
|
+
const yPx = resolvePosition(annotation.y, scales.y);
|
|
459
|
+
if (yPx === null) return null;
|
|
460
|
+
start = { x: chartArea.x, y: yPx };
|
|
461
|
+
end = { x: chartArea.x + chartArea.width, y: yPx };
|
|
462
|
+
} else if (annotation.x !== void 0) {
|
|
463
|
+
const xPx = resolvePosition(annotation.x, scales.x);
|
|
464
|
+
if (xPx === null) return null;
|
|
465
|
+
start = { x: xPx, y: chartArea.y };
|
|
466
|
+
end = { x: xPx, y: chartArea.y + chartArea.height };
|
|
467
|
+
} else {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
let strokeDasharray;
|
|
471
|
+
if (annotation.style === "dashed" || annotation.style === void 0) {
|
|
472
|
+
strokeDasharray = DEFAULT_REFLINE_DASH;
|
|
473
|
+
} else if (annotation.style === "dotted") {
|
|
474
|
+
strokeDasharray = "2 2";
|
|
475
|
+
}
|
|
476
|
+
let label;
|
|
477
|
+
if (annotation.label) {
|
|
478
|
+
const isHorizontal = annotation.y !== void 0;
|
|
479
|
+
const anchor = annotation.labelAnchor ?? (isHorizontal ? "top" : "left");
|
|
480
|
+
let baseDx;
|
|
481
|
+
let baseDy;
|
|
482
|
+
let labelX;
|
|
483
|
+
let labelY;
|
|
484
|
+
let textAnchor;
|
|
485
|
+
if (isHorizontal) {
|
|
486
|
+
if (anchor === "left") {
|
|
487
|
+
baseDx = 4;
|
|
488
|
+
baseDy = -4;
|
|
489
|
+
labelX = start.x;
|
|
490
|
+
labelY = start.y;
|
|
491
|
+
textAnchor = "start";
|
|
492
|
+
} else if (anchor === "bottom") {
|
|
493
|
+
baseDx = -4;
|
|
494
|
+
baseDy = 14;
|
|
495
|
+
labelX = end.x;
|
|
496
|
+
labelY = end.y;
|
|
497
|
+
textAnchor = "end";
|
|
498
|
+
} else {
|
|
499
|
+
baseDx = -4;
|
|
500
|
+
baseDy = -4;
|
|
501
|
+
labelX = end.x;
|
|
502
|
+
labelY = end.y;
|
|
503
|
+
textAnchor = "end";
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
if (anchor === "right") {
|
|
507
|
+
baseDx = -4;
|
|
508
|
+
baseDy = 14;
|
|
509
|
+
labelX = start.x;
|
|
510
|
+
labelY = start.y;
|
|
511
|
+
textAnchor = "end";
|
|
512
|
+
} else if (anchor === "bottom") {
|
|
513
|
+
baseDx = 4;
|
|
514
|
+
baseDy = -4;
|
|
515
|
+
labelX = start.x;
|
|
516
|
+
labelY = end.y;
|
|
517
|
+
textAnchor = "start";
|
|
518
|
+
} else {
|
|
519
|
+
baseDx = 4;
|
|
520
|
+
baseDy = 14;
|
|
521
|
+
labelX = start.x;
|
|
522
|
+
labelY = start.y;
|
|
523
|
+
textAnchor = "start";
|
|
524
|
+
}
|
|
550
525
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
526
|
+
const labelDelta = applyOffset({ dx: baseDx, dy: baseDy }, annotation.labelOffset);
|
|
527
|
+
const defaultStroke2 = isDark ? DARK_REFLINE_STROKE : LIGHT_REFLINE_STROKE;
|
|
528
|
+
const style = makeAnnotationLabelStyle(11, 400, annotation.stroke ?? defaultStroke2, isDark);
|
|
529
|
+
style.textAnchor = textAnchor;
|
|
530
|
+
label = {
|
|
531
|
+
text: annotation.label,
|
|
532
|
+
x: labelX + labelDelta.dx,
|
|
533
|
+
y: labelY + labelDelta.dy,
|
|
534
|
+
style,
|
|
535
|
+
visible: true
|
|
556
536
|
};
|
|
557
537
|
}
|
|
538
|
+
const defaultStroke = isDark ? DARK_REFLINE_STROKE : LIGHT_REFLINE_STROKE;
|
|
539
|
+
return {
|
|
540
|
+
type: "refline",
|
|
541
|
+
id: annotation.id,
|
|
542
|
+
line: { start, end },
|
|
543
|
+
label,
|
|
544
|
+
stroke: annotation.stroke ?? defaultStroke,
|
|
545
|
+
strokeDasharray,
|
|
546
|
+
strokeWidth: annotation.strokeWidth ?? 1,
|
|
547
|
+
zIndex: annotation.zIndex
|
|
548
|
+
};
|
|
558
549
|
}
|
|
550
|
+
|
|
551
|
+
// src/annotations/compute.ts
|
|
559
552
|
function computeAnnotations(spec, scales, chartArea, strategy, isDark = false, obstacles = [], svgDimensions) {
|
|
560
553
|
if (strategy.annotationPosition === "tooltip-only") {
|
|
561
554
|
return [];
|
|
@@ -788,6 +781,7 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
|
|
|
788
781
|
scales
|
|
789
782
|
);
|
|
790
783
|
}
|
|
784
|
+
const stackMode = xChannel.stack === "normalize" ? "normalize" : xChannel.stack === "center" ? "center" : "zero";
|
|
791
785
|
return computeStackedBars(
|
|
792
786
|
spec.data,
|
|
793
787
|
xChannel.field,
|
|
@@ -797,7 +791,8 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
|
|
|
797
791
|
yScale,
|
|
798
792
|
bandwidth,
|
|
799
793
|
baseline,
|
|
800
|
-
scales
|
|
794
|
+
scales,
|
|
795
|
+
stackMode
|
|
801
796
|
);
|
|
802
797
|
}
|
|
803
798
|
return computeColoredBars(
|
|
@@ -812,27 +807,33 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
|
|
|
812
807
|
scales
|
|
813
808
|
);
|
|
814
809
|
}
|
|
815
|
-
function computeStackedBars(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, _baseline, scales) {
|
|
810
|
+
function computeStackedBars(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, _baseline, scales, stackMode = "zero") {
|
|
816
811
|
const marks = [];
|
|
817
812
|
const categoryGroups = groupByField(data, categoryField);
|
|
818
813
|
for (const [category, rows] of categoryGroups) {
|
|
819
814
|
const bandY = yScale(category);
|
|
820
815
|
if (bandY === void 0) continue;
|
|
821
|
-
let
|
|
816
|
+
let categoryTotal = 0;
|
|
822
817
|
for (const row of rows) {
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
818
|
+
const v = Number(row[valueField] ?? 0);
|
|
819
|
+
if (Number.isFinite(v) && v > 0) categoryTotal += v;
|
|
820
|
+
}
|
|
821
|
+
let cumulativeValue = stackMode === "center" ? -categoryTotal / 2 : 0;
|
|
822
|
+
for (const row of rows) {
|
|
823
|
+
const groupKey2 = String(row[colorField] ?? "");
|
|
824
|
+
const rawValue = Number(row[valueField] ?? 0);
|
|
825
|
+
if (!Number.isFinite(rawValue) || rawValue <= 0) continue;
|
|
826
|
+
const value2 = stackMode === "normalize" && categoryTotal > 0 ? rawValue / categoryTotal : rawValue;
|
|
827
|
+
const color2 = getColor(scales, groupKey2);
|
|
827
828
|
const xLeft = xScale(cumulativeValue);
|
|
828
829
|
const xRight = xScale(cumulativeValue + value2);
|
|
829
830
|
const barWidth = Math.max(Math.abs(xRight - xLeft), MIN_BAR_WIDTH);
|
|
830
831
|
const aria = {
|
|
831
|
-
label: `${category}, ${
|
|
832
|
+
label: `${category}, ${groupKey2}: ${formatBarValue(rawValue)}`
|
|
832
833
|
};
|
|
833
834
|
marks.push({
|
|
834
835
|
type: "rect",
|
|
835
|
-
x: xLeft,
|
|
836
|
+
x: Math.min(xLeft, xRight),
|
|
836
837
|
y: bandY,
|
|
837
838
|
width: barWidth,
|
|
838
839
|
height: bandwidth,
|
|
@@ -866,16 +867,16 @@ function computeGroupedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
866
867
|
const bandY = yScale(category);
|
|
867
868
|
if (bandY === void 0) continue;
|
|
868
869
|
for (const row of rows) {
|
|
869
|
-
const
|
|
870
|
+
const groupKey2 = String(row[colorField] ?? "");
|
|
870
871
|
const value2 = Number(row[valueField] ?? 0);
|
|
871
872
|
if (!Number.isFinite(value2)) continue;
|
|
872
|
-
const groupIndex = groupIndexMap.get(
|
|
873
|
-
const color2 = getColor(scales,
|
|
873
|
+
const groupIndex = groupIndexMap.get(groupKey2) ?? 0;
|
|
874
|
+
const color2 = getColor(scales, groupKey2);
|
|
874
875
|
const xPos = value2 >= 0 ? baseline : xScale(value2);
|
|
875
876
|
const barWidth = Math.max(Math.abs(xScale(value2) - baseline), MIN_BAR_WIDTH);
|
|
876
877
|
const subY = bandY + groupIndex * (subBandHeight + gap);
|
|
877
878
|
const aria = {
|
|
878
|
-
label: `${category}, ${
|
|
879
|
+
label: `${category}, ${groupKey2}: ${formatBarValue(value2)}`
|
|
879
880
|
};
|
|
880
881
|
marks.push({
|
|
881
882
|
type: "rect",
|
|
@@ -901,12 +902,12 @@ function computeColoredBars(data, valueField, categoryField, colorField, xScale,
|
|
|
901
902
|
if (!Number.isFinite(value2)) continue;
|
|
902
903
|
const bandY = yScale(category);
|
|
903
904
|
if (bandY === void 0) continue;
|
|
904
|
-
const
|
|
905
|
-
const color2 = getColor(scales,
|
|
905
|
+
const groupKey2 = String(row[colorField] ?? "");
|
|
906
|
+
const color2 = getColor(scales, groupKey2);
|
|
906
907
|
const xPos = value2 >= 0 ? baseline : xScale(value2);
|
|
907
908
|
const barWidth = Math.max(Math.abs(xScale(value2) - baseline), MIN_BAR_WIDTH);
|
|
908
909
|
const aria = {
|
|
909
|
-
label: `${category}, ${
|
|
910
|
+
label: `${category}, ${groupKey2}: ${formatBarValue(value2)}`
|
|
910
911
|
};
|
|
911
912
|
marks.push({
|
|
912
913
|
type: "rect",
|
|
@@ -979,7 +980,7 @@ var SUFFIX_MULTIPLIERS = {
|
|
|
979
980
|
T: 1e12
|
|
980
981
|
};
|
|
981
982
|
function parseDisplayNumber(raw) {
|
|
982
|
-
const trimmed = raw.trim();
|
|
983
|
+
const trimmed = raw.trim().replace(/\u2212/g, "-");
|
|
983
984
|
if (!trimmed) return NaN;
|
|
984
985
|
const last = trimmed[trimmed.length - 1].toUpperCase();
|
|
985
986
|
const multiplier = SUFFIX_MULTIPLIERS[last];
|
|
@@ -988,6 +989,9 @@ function parseDisplayNumber(raw) {
|
|
|
988
989
|
const n = Number(numPart);
|
|
989
990
|
return Number.isNaN(n) ? NaN : n * multiplier;
|
|
990
991
|
}
|
|
992
|
+
if (last === "%") {
|
|
993
|
+
return Number(trimmed.slice(0, -1).replace(/,/g, ""));
|
|
994
|
+
}
|
|
991
995
|
return Number(trimmed.replace(/,/g, ""));
|
|
992
996
|
}
|
|
993
997
|
var LABEL_FONT_SIZE = 11;
|
|
@@ -1149,6 +1153,7 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
|
|
|
1149
1153
|
scales
|
|
1150
1154
|
);
|
|
1151
1155
|
}
|
|
1156
|
+
const stackMode = yChannel.stack === "normalize" ? "normalize" : yChannel.stack === "center" ? "center" : "zero";
|
|
1152
1157
|
return computeStackedColumns(
|
|
1153
1158
|
spec.data,
|
|
1154
1159
|
xChannel.field,
|
|
@@ -1158,7 +1163,8 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
|
|
|
1158
1163
|
yScale,
|
|
1159
1164
|
bandwidth,
|
|
1160
1165
|
baseline,
|
|
1161
|
-
scales
|
|
1166
|
+
scales,
|
|
1167
|
+
stackMode
|
|
1162
1168
|
);
|
|
1163
1169
|
}
|
|
1164
1170
|
return computeColoredColumns(
|
|
@@ -1236,13 +1242,13 @@ function computeColoredColumns(data, categoryField, valueField, colorField, xSca
|
|
|
1236
1242
|
if (!Number.isFinite(value2)) continue;
|
|
1237
1243
|
const bandX = xScale(category);
|
|
1238
1244
|
if (bandX === void 0) continue;
|
|
1239
|
-
const
|
|
1240
|
-
const color2 = getColor(scales,
|
|
1245
|
+
const groupKey2 = String(row[colorField] ?? "");
|
|
1246
|
+
const color2 = getColor(scales, groupKey2);
|
|
1241
1247
|
const yPos = yScale(value2);
|
|
1242
1248
|
const columnHeight = Math.max(Math.abs(baseline - yPos), MIN_COLUMN_HEIGHT);
|
|
1243
1249
|
const y2 = value2 >= 0 ? yPos : baseline;
|
|
1244
1250
|
const aria = {
|
|
1245
|
-
label: `${category}, ${
|
|
1251
|
+
label: `${category}, ${groupKey2}: ${formatColumnValue(value2)}`
|
|
1246
1252
|
};
|
|
1247
1253
|
marks.push({
|
|
1248
1254
|
type: "rect",
|
|
@@ -1280,17 +1286,17 @@ function computeGroupedColumns(data, categoryField, valueField, colorField, xSca
|
|
|
1280
1286
|
const bandX = xScale(category);
|
|
1281
1287
|
if (bandX === void 0) continue;
|
|
1282
1288
|
for (const row of rows) {
|
|
1283
|
-
const
|
|
1289
|
+
const groupKey2 = String(row[colorField] ?? "");
|
|
1284
1290
|
const value2 = Number(row[valueField] ?? 0);
|
|
1285
1291
|
if (!Number.isFinite(value2)) continue;
|
|
1286
|
-
const groupIndex = groupIndexMap.get(
|
|
1287
|
-
const color2 = getColor(scales,
|
|
1292
|
+
const groupIndex = groupIndexMap.get(groupKey2) ?? 0;
|
|
1293
|
+
const color2 = getColor(scales, groupKey2);
|
|
1288
1294
|
const yPos = yScale(value2);
|
|
1289
1295
|
const columnHeight = Math.max(Math.abs(baseline - yPos), MIN_COLUMN_HEIGHT);
|
|
1290
1296
|
const y2 = value2 >= 0 ? yPos : baseline;
|
|
1291
1297
|
const subX = bandX + groupIndex * (subBandWidth + gap);
|
|
1292
1298
|
const aria = {
|
|
1293
|
-
label: `${category}, ${
|
|
1299
|
+
label: `${category}, ${groupKey2}: ${formatColumnValue(value2)}`
|
|
1294
1300
|
};
|
|
1295
1301
|
marks.push({
|
|
1296
1302
|
type: "rect",
|
|
@@ -1308,28 +1314,34 @@ function computeGroupedColumns(data, categoryField, valueField, colorField, xSca
|
|
|
1308
1314
|
}
|
|
1309
1315
|
return marks;
|
|
1310
1316
|
}
|
|
1311
|
-
function computeStackedColumns(data, categoryField, valueField, colorField, xScale, yScale, bandwidth, _baseline, scales) {
|
|
1317
|
+
function computeStackedColumns(data, categoryField, valueField, colorField, xScale, yScale, bandwidth, _baseline, scales, stackMode = "zero") {
|
|
1312
1318
|
const marks = [];
|
|
1313
1319
|
const categoryGroups = groupByField(data, categoryField);
|
|
1314
1320
|
for (const [category, rows] of categoryGroups) {
|
|
1315
1321
|
const bandX = xScale(category);
|
|
1316
1322
|
if (bandX === void 0) continue;
|
|
1317
|
-
let
|
|
1323
|
+
let categoryTotal = 0;
|
|
1318
1324
|
for (const row of rows) {
|
|
1319
|
-
const
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1325
|
+
const v = Number(row[valueField] ?? 0);
|
|
1326
|
+
if (Number.isFinite(v) && v > 0) categoryTotal += v;
|
|
1327
|
+
}
|
|
1328
|
+
let cumulativeValue = stackMode === "center" ? -categoryTotal / 2 : 0;
|
|
1329
|
+
for (const row of rows) {
|
|
1330
|
+
const groupKey2 = String(row[colorField] ?? "");
|
|
1331
|
+
const rawValue = Number(row[valueField] ?? 0);
|
|
1332
|
+
if (!Number.isFinite(rawValue) || rawValue <= 0) continue;
|
|
1333
|
+
const value2 = stackMode === "normalize" && categoryTotal > 0 ? rawValue / categoryTotal : rawValue;
|
|
1334
|
+
const color2 = getColor(scales, groupKey2);
|
|
1323
1335
|
const yTop = yScale(cumulativeValue + value2);
|
|
1324
1336
|
const yBottom = yScale(cumulativeValue);
|
|
1325
1337
|
const columnHeight = Math.max(Math.abs(yBottom - yTop), MIN_COLUMN_HEIGHT);
|
|
1326
1338
|
const aria = {
|
|
1327
|
-
label: `${category}, ${
|
|
1339
|
+
label: `${category}, ${groupKey2}: ${formatColumnValue(rawValue)}`
|
|
1328
1340
|
};
|
|
1329
1341
|
marks.push({
|
|
1330
1342
|
type: "rect",
|
|
1331
1343
|
x: bandX,
|
|
1332
|
-
y: yTop,
|
|
1344
|
+
y: Math.min(yTop, yBottom),
|
|
1333
1345
|
width: bandwidth,
|
|
1334
1346
|
height: columnHeight,
|
|
1335
1347
|
fill: color2,
|
|
@@ -2521,6 +2533,26 @@ function stack_default() {
|
|
|
2521
2533
|
return stack;
|
|
2522
2534
|
}
|
|
2523
2535
|
|
|
2536
|
+
// ../../node_modules/.bun/d3-shape@3.2.0/node_modules/d3-shape/src/offset/expand.js
|
|
2537
|
+
function expand_default(series, order) {
|
|
2538
|
+
if (!((n = series.length) > 0)) return;
|
|
2539
|
+
for (var i, n, j = 0, m = series[0].length, y2; j < m; ++j) {
|
|
2540
|
+
for (y2 = i = 0; i < n; ++i) y2 += series[i][j][1] || 0;
|
|
2541
|
+
if (y2) for (i = 0; i < n; ++i) series[i][j][1] /= y2;
|
|
2542
|
+
}
|
|
2543
|
+
none_default(series, order);
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
// ../../node_modules/.bun/d3-shape@3.2.0/node_modules/d3-shape/src/offset/silhouette.js
|
|
2547
|
+
function silhouette_default(series, order) {
|
|
2548
|
+
if (!((n = series.length) > 0)) return;
|
|
2549
|
+
for (var j = 0, s0 = series[order[0]], n, m = s0.length; j < m; ++j) {
|
|
2550
|
+
for (var i = 0, y2 = 0; i < n; ++i) y2 += series[i][j][1] || 0;
|
|
2551
|
+
s0[j][1] += s0[j][0] = -y2 / 2;
|
|
2552
|
+
}
|
|
2553
|
+
none_default(series, order);
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2524
2556
|
// src/charts/line/curves.ts
|
|
2525
2557
|
var CURVE_MAP = {
|
|
2526
2558
|
linear: linear_default,
|
|
@@ -2648,7 +2680,9 @@ function computeStackedArea(spec, scales, chartArea) {
|
|
|
2648
2680
|
}
|
|
2649
2681
|
return pivot;
|
|
2650
2682
|
});
|
|
2651
|
-
const
|
|
2683
|
+
const stackProp = yChannel.stack;
|
|
2684
|
+
const offsetFn = stackProp === "normalize" ? expand_default : stackProp === "center" ? silhouette_default : none_default;
|
|
2685
|
+
const stackGenerator = stack_default().keys(keys).order(none_default2).offset(offsetFn);
|
|
2652
2686
|
const stackedData = stackGenerator(pivotData);
|
|
2653
2687
|
const yScale = scales.y.scale;
|
|
2654
2688
|
const marks = [];
|
|
@@ -3102,53 +3136,185 @@ function computePieLabels(marks, _chartArea, density = "auto", _textFill = "#333
|
|
|
3102
3136
|
dominantBaseline: "central"
|
|
3103
3137
|
}
|
|
3104
3138
|
});
|
|
3105
|
-
targetMarkIndices.push(mi);
|
|
3106
|
-
}
|
|
3107
|
-
if (candidates.length === 0) return [];
|
|
3108
|
-
let resolved;
|
|
3109
|
-
if (density === "all") {
|
|
3110
|
-
resolved = candidates.map((c) => ({
|
|
3111
|
-
text: c.text,
|
|
3112
|
-
x: c.anchorX,
|
|
3113
|
-
y: c.anchorY,
|
|
3114
|
-
style: c.style,
|
|
3115
|
-
visible: true
|
|
3116
|
-
}));
|
|
3117
|
-
} else {
|
|
3118
|
-
resolved = resolveCollisions5(candidates);
|
|
3139
|
+
targetMarkIndices.push(mi);
|
|
3140
|
+
}
|
|
3141
|
+
if (candidates.length === 0) return [];
|
|
3142
|
+
let resolved;
|
|
3143
|
+
if (density === "all") {
|
|
3144
|
+
resolved = candidates.map((c) => ({
|
|
3145
|
+
text: c.text,
|
|
3146
|
+
x: c.anchorX,
|
|
3147
|
+
y: c.anchorY,
|
|
3148
|
+
style: c.style,
|
|
3149
|
+
visible: true
|
|
3150
|
+
}));
|
|
3151
|
+
} else {
|
|
3152
|
+
resolved = resolveCollisions5(candidates);
|
|
3153
|
+
}
|
|
3154
|
+
for (let i = 0; i < resolved.length && i < targetMarks.length; i++) {
|
|
3155
|
+
const label = resolved[i];
|
|
3156
|
+
const mark = targetMarks[i];
|
|
3157
|
+
if (label.visible) {
|
|
3158
|
+
label.connector = {
|
|
3159
|
+
from: { x: label.x, y: label.y },
|
|
3160
|
+
to: { x: mark.centroid.x, y: mark.centroid.y },
|
|
3161
|
+
stroke: _textFill,
|
|
3162
|
+
style: "straight"
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
return resolved;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
// src/charts/pie/index.ts
|
|
3170
|
+
var pieRenderer = (spec, scales, chartArea, strategy, theme) => {
|
|
3171
|
+
const marks = computePieMarks(spec, scales, chartArea, strategy, false);
|
|
3172
|
+
const labels = computePieLabels(marks, chartArea, spec.labels.density, theme.colors.text);
|
|
3173
|
+
for (let i = 0; i < marks.length && i < labels.length; i++) {
|
|
3174
|
+
marks[i].label = labels[i];
|
|
3175
|
+
}
|
|
3176
|
+
return marks;
|
|
3177
|
+
};
|
|
3178
|
+
var donutRenderer = (spec, scales, chartArea, strategy, theme) => {
|
|
3179
|
+
const marks = computePieMarks(spec, scales, chartArea, strategy, true);
|
|
3180
|
+
const labels = computePieLabels(marks, chartArea, spec.labels.density, theme.colors.text);
|
|
3181
|
+
for (let i = 0; i < marks.length && i < labels.length; i++) {
|
|
3182
|
+
marks[i].label = labels[i];
|
|
3183
|
+
}
|
|
3184
|
+
return marks;
|
|
3185
|
+
};
|
|
3186
|
+
|
|
3187
|
+
// src/charts/post-process.ts
|
|
3188
|
+
function computeMarkObstacles(marks, scales) {
|
|
3189
|
+
if (scales.y?.type === "band") {
|
|
3190
|
+
return computeBandRowObstacles(marks, scales);
|
|
3191
|
+
}
|
|
3192
|
+
const obstacles = [];
|
|
3193
|
+
for (const mark of marks) {
|
|
3194
|
+
if (mark.type === "rect") {
|
|
3195
|
+
const rm = mark;
|
|
3196
|
+
obstacles.push({ x: rm.x, y: rm.y, width: rm.width, height: rm.height });
|
|
3197
|
+
} else if (mark.type === "point") {
|
|
3198
|
+
const pm = mark;
|
|
3199
|
+
obstacles.push({
|
|
3200
|
+
x: pm.cx - pm.r,
|
|
3201
|
+
y: pm.cy - pm.r,
|
|
3202
|
+
width: pm.r * 2,
|
|
3203
|
+
height: pm.r * 2
|
|
3204
|
+
});
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
return obstacles;
|
|
3208
|
+
}
|
|
3209
|
+
function computeBandRowObstacles(marks, scales) {
|
|
3210
|
+
const rows = /* @__PURE__ */ new Map();
|
|
3211
|
+
for (const mark of marks) {
|
|
3212
|
+
let cy;
|
|
3213
|
+
let left2;
|
|
3214
|
+
let right2;
|
|
3215
|
+
if (mark.type === "point") {
|
|
3216
|
+
const pm = mark;
|
|
3217
|
+
cy = pm.cy;
|
|
3218
|
+
left2 = pm.cx - pm.r;
|
|
3219
|
+
right2 = pm.cx + pm.r;
|
|
3220
|
+
} else if (mark.type === "rect") {
|
|
3221
|
+
const rm = mark;
|
|
3222
|
+
cy = rm.y + rm.height / 2;
|
|
3223
|
+
left2 = rm.x;
|
|
3224
|
+
right2 = rm.x + rm.width;
|
|
3225
|
+
} else {
|
|
3226
|
+
continue;
|
|
3227
|
+
}
|
|
3228
|
+
const key = Math.round(cy);
|
|
3229
|
+
const existing = rows.get(key);
|
|
3230
|
+
if (existing) {
|
|
3231
|
+
existing.minX = Math.min(existing.minX, left2);
|
|
3232
|
+
existing.maxX = Math.max(existing.maxX, right2);
|
|
3233
|
+
} else {
|
|
3234
|
+
rows.set(key, { minX: left2, maxX: right2, bandY: cy });
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
const bandScale = scales.y.scale;
|
|
3238
|
+
const bandwidth = bandScale.bandwidth?.() ?? 0;
|
|
3239
|
+
if (bandwidth === 0) return [];
|
|
3240
|
+
const obstacles = [];
|
|
3241
|
+
for (const { minX, maxX, bandY } of rows.values()) {
|
|
3242
|
+
obstacles.push({
|
|
3243
|
+
x: minX,
|
|
3244
|
+
y: bandY - bandwidth / 2,
|
|
3245
|
+
width: maxX - minX,
|
|
3246
|
+
height: bandwidth
|
|
3247
|
+
});
|
|
3119
3248
|
}
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3249
|
+
return obstacles;
|
|
3250
|
+
}
|
|
3251
|
+
function resolveRendererKey(markType, encoding, markDef) {
|
|
3252
|
+
if (markType === "bar") {
|
|
3253
|
+
const xType = encoding.x?.type;
|
|
3254
|
+
const yType = encoding.y?.type;
|
|
3255
|
+
const isVertical = (xType === "nominal" || xType === "ordinal" || xType === "temporal") && yType === "quantitative";
|
|
3256
|
+
if (isVertical) {
|
|
3257
|
+
return "bar:vertical";
|
|
3258
|
+
}
|
|
3259
|
+
} else if (markType === "arc") {
|
|
3260
|
+
const innerRadius = markDef.innerRadius;
|
|
3261
|
+
if (innerRadius && innerRadius > 0) {
|
|
3262
|
+
return "arc:donut";
|
|
3130
3263
|
}
|
|
3131
3264
|
}
|
|
3132
|
-
return
|
|
3265
|
+
return markType;
|
|
3133
3266
|
}
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3267
|
+
function getMarkPrimaryValue(mark) {
|
|
3268
|
+
switch (mark.type) {
|
|
3269
|
+
case "rect":
|
|
3270
|
+
return mark.height;
|
|
3271
|
+
// bar height is the primary value encoding
|
|
3272
|
+
case "point":
|
|
3273
|
+
return mark.cy;
|
|
3274
|
+
// y position for scatter
|
|
3275
|
+
case "arc":
|
|
3276
|
+
return mark.endAngle - mark.startAngle;
|
|
3277
|
+
// arc angle extent
|
|
3278
|
+
case "line":
|
|
3279
|
+
case "area":
|
|
3280
|
+
return 0;
|
|
3281
|
+
// series marks don't have individual values
|
|
3282
|
+
default:
|
|
3283
|
+
return 0;
|
|
3141
3284
|
}
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3285
|
+
}
|
|
3286
|
+
function assignAnimationIndices(marks, animation) {
|
|
3287
|
+
if (!animation?.enabled) return;
|
|
3288
|
+
if (animation.staggerOrder === "value") {
|
|
3289
|
+
const indexed = marks.map((m, i) => ({ mark: m, idx: i }));
|
|
3290
|
+
indexed.sort((a, b) => {
|
|
3291
|
+
const av = getMarkPrimaryValue(a.mark);
|
|
3292
|
+
const bv = getMarkPrimaryValue(b.mark);
|
|
3293
|
+
return av - bv;
|
|
3294
|
+
});
|
|
3295
|
+
for (let i = 0; i < indexed.length; i++) {
|
|
3296
|
+
const m = indexed[i].mark;
|
|
3297
|
+
if (m.type === "rect" && m.stackGroup) continue;
|
|
3298
|
+
m.animationIndex = i;
|
|
3299
|
+
}
|
|
3149
3300
|
}
|
|
3150
|
-
|
|
3151
|
-
|
|
3301
|
+
const groupIndexMap = /* @__PURE__ */ new Map();
|
|
3302
|
+
const groupStackPos = /* @__PURE__ */ new Map();
|
|
3303
|
+
let nextGroupIndex = 0;
|
|
3304
|
+
for (const mark of marks) {
|
|
3305
|
+
if (mark.type === "rect" && mark.stackGroup) {
|
|
3306
|
+
const rect = mark;
|
|
3307
|
+
const group = rect.stackGroup;
|
|
3308
|
+
if (!groupIndexMap.has(group)) {
|
|
3309
|
+
groupIndexMap.set(group, nextGroupIndex++);
|
|
3310
|
+
}
|
|
3311
|
+
rect.animationIndex = groupIndexMap.get(group);
|
|
3312
|
+
const pos = groupStackPos.get(group) ?? 0;
|
|
3313
|
+
rect.stackPos = pos;
|
|
3314
|
+
groupStackPos.set(group, pos + 1);
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3152
3318
|
|
|
3153
3319
|
// src/charts/registry.ts
|
|
3154
3320
|
var renderers = /* @__PURE__ */ new Map();
|
|
@@ -6135,7 +6301,7 @@ function normalizeChrome(chrome) {
|
|
|
6135
6301
|
};
|
|
6136
6302
|
}
|
|
6137
6303
|
function inferFieldType(data, field) {
|
|
6138
|
-
const sampleSize = Math.min(
|
|
6304
|
+
const sampleSize = Math.min(50, data.length);
|
|
6139
6305
|
let numericCount = 0;
|
|
6140
6306
|
let dateCount = 0;
|
|
6141
6307
|
let totalNonNull = 0;
|
|
@@ -6244,7 +6410,8 @@ function normalizeChartSpec(spec, warnings) {
|
|
|
6244
6410
|
theme: spec.theme ?? {},
|
|
6245
6411
|
darkMode: spec.darkMode ?? "off",
|
|
6246
6412
|
hiddenSeries: spec.hiddenSeries ?? [],
|
|
6247
|
-
seriesStyles: spec.seriesStyles ?? {}
|
|
6413
|
+
seriesStyles: spec.seriesStyles ?? {},
|
|
6414
|
+
watermark: spec.watermark ?? true
|
|
6248
6415
|
};
|
|
6249
6416
|
}
|
|
6250
6417
|
function normalizeTableSpec(spec, _warnings) {
|
|
@@ -6261,7 +6428,8 @@ function normalizeTableSpec(spec, _warnings) {
|
|
|
6261
6428
|
stickyFirstColumn: spec.stickyFirstColumn ?? false,
|
|
6262
6429
|
compact: spec.compact ?? false,
|
|
6263
6430
|
responsive: spec.responsive ?? true,
|
|
6264
|
-
animation: spec.animation
|
|
6431
|
+
animation: spec.animation,
|
|
6432
|
+
watermark: spec.watermark ?? true
|
|
6265
6433
|
};
|
|
6266
6434
|
}
|
|
6267
6435
|
function normalizeSankeySpec(spec, _warnings) {
|
|
@@ -6281,7 +6449,8 @@ function normalizeSankeySpec(spec, _warnings) {
|
|
|
6281
6449
|
darkMode: spec.darkMode ?? "off",
|
|
6282
6450
|
animation: spec.animation,
|
|
6283
6451
|
valueFormat: spec.valueFormat,
|
|
6284
|
-
linkOpacity: spec.linkOpacity
|
|
6452
|
+
linkOpacity: spec.linkOpacity,
|
|
6453
|
+
watermark: spec.watermark ?? true
|
|
6285
6454
|
};
|
|
6286
6455
|
}
|
|
6287
6456
|
function normalizeGraphSpec(spec, _warnings) {
|
|
@@ -6304,7 +6473,8 @@ function normalizeGraphSpec(spec, _warnings) {
|
|
|
6304
6473
|
chrome: normalizeChrome(spec.chrome),
|
|
6305
6474
|
annotations: normalizeAnnotations(spec.annotations),
|
|
6306
6475
|
theme: spec.theme ?? {},
|
|
6307
|
-
darkMode: spec.darkMode ?? "off"
|
|
6476
|
+
darkMode: spec.darkMode ?? "off",
|
|
6477
|
+
watermark: spec.watermark ?? true
|
|
6308
6478
|
};
|
|
6309
6479
|
}
|
|
6310
6480
|
function normalizeSpec(spec, warnings = []) {
|
|
@@ -6331,14 +6501,23 @@ function normalizeSpec(spec, warnings = []) {
|
|
|
6331
6501
|
`Unknown spec shape. Expected mark (chart), layer, type: 'table', type: 'graph', or type: 'sankey'.`
|
|
6332
6502
|
);
|
|
6333
6503
|
}
|
|
6334
|
-
function flattenLayers(spec, parentData, parentEncoding, parentTransforms) {
|
|
6504
|
+
function flattenLayers(spec, parentData, parentEncoding, parentTransforms, parentWatermark) {
|
|
6335
6505
|
const resolvedData = spec.data ?? parentData;
|
|
6336
6506
|
const resolvedEncoding = parentEncoding && spec.encoding ? { ...parentEncoding, ...spec.encoding } : spec.encoding ?? parentEncoding;
|
|
6337
6507
|
const resolvedTransforms = [...parentTransforms ?? [], ...spec.transform ?? []];
|
|
6508
|
+
const resolvedWatermark = spec.watermark ?? parentWatermark;
|
|
6338
6509
|
const leaves = [];
|
|
6339
6510
|
for (const child of spec.layer) {
|
|
6340
6511
|
if (isLayerSpec(child)) {
|
|
6341
|
-
leaves.push(
|
|
6512
|
+
leaves.push(
|
|
6513
|
+
...flattenLayers(
|
|
6514
|
+
child,
|
|
6515
|
+
resolvedData,
|
|
6516
|
+
resolvedEncoding,
|
|
6517
|
+
resolvedTransforms,
|
|
6518
|
+
resolvedWatermark
|
|
6519
|
+
)
|
|
6520
|
+
);
|
|
6342
6521
|
} else {
|
|
6343
6522
|
const mergedData = child.data ?? resolvedData ?? [];
|
|
6344
6523
|
const mergedEncoding = resolvedEncoding ? { ...resolvedEncoding, ...child.encoding } : child.encoding;
|
|
@@ -6347,7 +6526,9 @@ function flattenLayers(spec, parentData, parentEncoding, parentTransforms) {
|
|
|
6347
6526
|
...child,
|
|
6348
6527
|
data: mergedData,
|
|
6349
6528
|
encoding: mergedEncoding,
|
|
6350
|
-
transform: mergedTransforms.length > 0 ? mergedTransforms : void 0
|
|
6529
|
+
transform: mergedTransforms.length > 0 ? mergedTransforms : void 0,
|
|
6530
|
+
// Inherit parent watermark if child doesn't explicitly set one
|
|
6531
|
+
...child.watermark === void 0 && resolvedWatermark !== void 0 ? { watermark: resolvedWatermark } : {}
|
|
6351
6532
|
});
|
|
6352
6533
|
}
|
|
6353
6534
|
}
|
|
@@ -7406,6 +7587,8 @@ function compileGraph(spec, options) {
|
|
|
7406
7587
|
);
|
|
7407
7588
|
}
|
|
7408
7589
|
const graphSpec = normalized;
|
|
7590
|
+
const rawWatermark = spec.watermark;
|
|
7591
|
+
const watermark = rawWatermark !== void 0 ? graphSpec.watermark : options.watermark ?? true;
|
|
7409
7592
|
const mergedThemeConfig = options.theme ? { ...graphSpec.theme, ...options.theme } : graphSpec.theme;
|
|
7410
7593
|
let theme = resolveTheme(mergedThemeConfig);
|
|
7411
7594
|
if (options.darkMode) {
|
|
@@ -7473,7 +7656,10 @@ function compileGraph(spec, options) {
|
|
|
7473
7656
|
},
|
|
7474
7657
|
theme,
|
|
7475
7658
|
options.width,
|
|
7476
|
-
options.measureText
|
|
7659
|
+
options.measureText,
|
|
7660
|
+
"full",
|
|
7661
|
+
void 0,
|
|
7662
|
+
watermark
|
|
7477
7663
|
);
|
|
7478
7664
|
return {
|
|
7479
7665
|
nodes: compiledNodes,
|
|
@@ -7487,7 +7673,8 @@ function compileGraph(spec, options) {
|
|
|
7487
7673
|
width: options.width,
|
|
7488
7674
|
height: options.height
|
|
7489
7675
|
},
|
|
7490
|
-
simulationConfig
|
|
7676
|
+
simulationConfig,
|
|
7677
|
+
watermark
|
|
7491
7678
|
};
|
|
7492
7679
|
}
|
|
7493
7680
|
var DEFAULT_COLLISION_PADDING = 5;
|
|
@@ -7693,7 +7880,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
7693
7880
|
}));
|
|
7694
7881
|
const shouldThin = scales.x.type !== "band" && !axisConfig?.tickCount && !axisConfig?.values;
|
|
7695
7882
|
const ticks2 = shouldThin ? thinTicksUntilFit(allTicks, fontSize, fontWeight, measureText) : allTicks;
|
|
7696
|
-
let tickAngle = axisConfig?.labelAngle
|
|
7883
|
+
let tickAngle = axisConfig?.labelAngle;
|
|
7697
7884
|
if (tickAngle === void 0 && scales.x.type === "band" && ticks2.length > 1) {
|
|
7698
7885
|
const bandwidth = scales.x.scale.bandwidth();
|
|
7699
7886
|
let maxLabelWidth = 0;
|
|
@@ -7705,7 +7892,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
7705
7892
|
tickAngle = -45;
|
|
7706
7893
|
}
|
|
7707
7894
|
}
|
|
7708
|
-
const axisTitle = axisConfig?.title
|
|
7895
|
+
const axisTitle = axisConfig?.title;
|
|
7709
7896
|
result.x = {
|
|
7710
7897
|
ticks: ticks2,
|
|
7711
7898
|
gridlines: axisConfig?.grid ? gridlines : [],
|
|
@@ -7735,14 +7922,14 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
7735
7922
|
} else {
|
|
7736
7923
|
allTicks = continuousTicks(scales.y, yDensity);
|
|
7737
7924
|
}
|
|
7738
|
-
const
|
|
7925
|
+
const shouldThin = scales.y.type !== "band" && !axisConfig?.tickCount && !axisConfig?.values;
|
|
7926
|
+
const ticks2 = shouldThin ? thinTicksUntilFit(allTicks, fontSize, fontWeight, measureText) : allTicks;
|
|
7927
|
+
const gridlines = ticks2.map((t) => ({
|
|
7739
7928
|
position: t.position,
|
|
7740
7929
|
major: true
|
|
7741
7930
|
}));
|
|
7742
|
-
const
|
|
7743
|
-
const
|
|
7744
|
-
const axisTitle = axisConfig?.title ?? axisConfig?.label;
|
|
7745
|
-
const tickAngle = axisConfig?.labelAngle ?? axisConfig?.tickAngle;
|
|
7931
|
+
const axisTitle = axisConfig?.title;
|
|
7932
|
+
const tickAngle = axisConfig?.labelAngle;
|
|
7746
7933
|
result.y = {
|
|
7747
7934
|
ticks: ticks2,
|
|
7748
7935
|
// Y-axis gridlines are shown by default (standard editorial practice)
|
|
@@ -7786,7 +7973,7 @@ function scalePadding(basePadding, width, height) {
|
|
|
7786
7973
|
}
|
|
7787
7974
|
var MIN_CHART_WIDTH = 60;
|
|
7788
7975
|
var MIN_CHART_HEIGHT = 40;
|
|
7789
|
-
function computeDimensions(spec, options, legendLayout, theme, strategy) {
|
|
7976
|
+
function computeDimensions(spec, options, legendLayout, theme, strategy, watermark = true) {
|
|
7790
7977
|
const { width, height } = options;
|
|
7791
7978
|
const padding = scalePadding(theme.spacing.padding, width, height);
|
|
7792
7979
|
const axisMargin = theme.spacing.axisMargin;
|
|
@@ -7797,14 +7984,15 @@ function computeDimensions(spec, options, legendLayout, theme, strategy) {
|
|
|
7797
7984
|
width,
|
|
7798
7985
|
options.measureText,
|
|
7799
7986
|
chromeMode,
|
|
7800
|
-
padding
|
|
7987
|
+
padding,
|
|
7988
|
+
watermark
|
|
7801
7989
|
);
|
|
7802
7990
|
const total = { x: 0, y: 0, width, height };
|
|
7803
7991
|
const isRadial = spec.markType === "arc";
|
|
7804
7992
|
const encoding = spec.encoding;
|
|
7805
7993
|
const xAxis = encoding.x?.axis;
|
|
7806
|
-
const hasXAxisLabel = !!xAxis?.
|
|
7807
|
-
const xTickAngle = xAxis?.
|
|
7994
|
+
const hasXAxisLabel = !!xAxis?.title;
|
|
7995
|
+
const xTickAngle = xAxis?.labelAngle;
|
|
7808
7996
|
let xAxisHeight;
|
|
7809
7997
|
if (isRadial) {
|
|
7810
7998
|
xAxisHeight = 0;
|
|
@@ -7955,7 +8143,8 @@ function computeDimensions(spec, options, legendLayout, theme, strategy) {
|
|
|
7955
8143
|
width,
|
|
7956
8144
|
options.measureText,
|
|
7957
8145
|
fallbackMode,
|
|
7958
|
-
padding
|
|
8146
|
+
padding,
|
|
8147
|
+
watermark
|
|
7959
8148
|
);
|
|
7960
8149
|
const fallbackTopAxisGap = isRadial && fallbackChrome.topHeight === 0 ? 0 : axisMargin;
|
|
7961
8150
|
const newTop = padding + fallbackChrome.topHeight + fallbackTopAxisGap;
|
|
@@ -8028,6 +8217,12 @@ function uniqueStrings(values) {
|
|
|
8028
8217
|
}
|
|
8029
8218
|
return result;
|
|
8030
8219
|
}
|
|
8220
|
+
function applyCategoricalSort(values, sort) {
|
|
8221
|
+
if (sort === null) return values;
|
|
8222
|
+
const sorted = [...values].sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
|
|
8223
|
+
if (sort === "descending") sorted.reverse();
|
|
8224
|
+
return sorted;
|
|
8225
|
+
}
|
|
8031
8226
|
function applyContinuousConfig(scale, channel) {
|
|
8032
8227
|
if (channel.scale?.clamp) {
|
|
8033
8228
|
scale.clamp(true);
|
|
@@ -8189,7 +8384,7 @@ function evenRange(start, end, count) {
|
|
|
8189
8384
|
return Array.from({ length: count }, (_, i) => start + step * i);
|
|
8190
8385
|
}
|
|
8191
8386
|
function buildBandScale(channel, data, rangeStart, rangeEnd) {
|
|
8192
|
-
const values = channel.scale?.domain ? channel.scale.domain : uniqueStrings(fieldValues(data, channel.field));
|
|
8387
|
+
const values = channel.scale?.domain ? channel.scale.domain : applyCategoricalSort(uniqueStrings(fieldValues(data, channel.field)), channel.sort);
|
|
8193
8388
|
const padding = channel.scale?.padding ?? 0.35;
|
|
8194
8389
|
const scale = band().domain(values).range([rangeStart, rangeEnd]).padding(padding);
|
|
8195
8390
|
if (channel.scale?.paddingInner !== void 0) {
|
|
@@ -8205,7 +8400,7 @@ function buildBandScale(channel, data, rangeStart, rangeEnd) {
|
|
|
8205
8400
|
return { scale, type: "band", channel };
|
|
8206
8401
|
}
|
|
8207
8402
|
function buildPointScale(channel, data, rangeStart, rangeEnd) {
|
|
8208
|
-
const values = channel.scale?.domain ? channel.scale.domain : uniqueStrings(fieldValues(data, channel.field));
|
|
8403
|
+
const values = channel.scale?.domain ? channel.scale.domain : applyCategoricalSort(uniqueStrings(fieldValues(data, channel.field)), channel.sort);
|
|
8209
8404
|
const padding = channel.scale?.padding ?? 0.5;
|
|
8210
8405
|
const scale = point4().domain(values).range([rangeStart, rangeEnd]).padding(padding);
|
|
8211
8406
|
if (channel.scale?.reverse) {
|
|
@@ -8215,7 +8410,8 @@ function buildPointScale(channel, data, rangeStart, rangeEnd) {
|
|
|
8215
8410
|
return { scale, type: "point", channel };
|
|
8216
8411
|
}
|
|
8217
8412
|
function buildOrdinalColorScale(channel, data, palette) {
|
|
8218
|
-
const
|
|
8413
|
+
const explicitDomain = channel.scale?.domain;
|
|
8414
|
+
const values = explicitDomain ? explicitDomain.map(String) : applyCategoricalSort(uniqueStrings(fieldValues(data, channel.field)), channel.sort);
|
|
8219
8415
|
const scale = ordinal().domain(values).range(palette);
|
|
8220
8416
|
return { scale, type: "ordinal", channel };
|
|
8221
8417
|
}
|
|
@@ -8293,25 +8489,49 @@ function computeScales(spec, chartArea, data) {
|
|
|
8293
8489
|
}
|
|
8294
8490
|
if (encoding.x) {
|
|
8295
8491
|
let xData = data;
|
|
8492
|
+
let xChannel = encoding.x;
|
|
8296
8493
|
const xStackDisabled = encoding.x.stack === null || encoding.x.stack === false;
|
|
8297
8494
|
if (spec.markType === "bar" && encoding.color && encoding.x.type === "quantitative" && !xStackDisabled) {
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
if (
|
|
8301
|
-
const
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
const
|
|
8305
|
-
|
|
8306
|
-
|
|
8495
|
+
if (encoding.x.stack === "normalize") {
|
|
8496
|
+
xChannel = { ...encoding.x, scale: { ...encoding.x.scale, domain: [0, 1], nice: false } };
|
|
8497
|
+
} else if (encoding.x.stack === "center") {
|
|
8498
|
+
const yField = encoding.y?.field;
|
|
8499
|
+
const xField = encoding.x.field;
|
|
8500
|
+
if (yField) {
|
|
8501
|
+
const sums = /* @__PURE__ */ new Map();
|
|
8502
|
+
for (const row of data) {
|
|
8503
|
+
const cat = String(row[yField] ?? "");
|
|
8504
|
+
const val = Number(row[xField] ?? 0);
|
|
8505
|
+
if (Number.isFinite(val) && val > 0) {
|
|
8506
|
+
sums.set(cat, (sums.get(cat) ?? 0) + val);
|
|
8507
|
+
}
|
|
8508
|
+
}
|
|
8509
|
+
const maxSum = Math.max(...sums.values(), 0);
|
|
8510
|
+
const half = maxSum / 2;
|
|
8511
|
+
xChannel = {
|
|
8512
|
+
...encoding.x,
|
|
8513
|
+
scale: { ...encoding.x.scale, domain: [-half, half], zero: true }
|
|
8514
|
+
};
|
|
8515
|
+
}
|
|
8516
|
+
} else {
|
|
8517
|
+
const yField = encoding.y?.field;
|
|
8518
|
+
const xField = encoding.x.field;
|
|
8519
|
+
if (yField) {
|
|
8520
|
+
const sums = /* @__PURE__ */ new Map();
|
|
8521
|
+
for (const row of data) {
|
|
8522
|
+
const cat = String(row[yField] ?? "");
|
|
8523
|
+
const val = Number(row[xField] ?? 0);
|
|
8524
|
+
if (Number.isFinite(val) && val > 0) {
|
|
8525
|
+
sums.set(cat, (sums.get(cat) ?? 0) + val);
|
|
8526
|
+
}
|
|
8307
8527
|
}
|
|
8528
|
+
const maxSum = Math.max(...sums.values(), 0);
|
|
8529
|
+
xData = [...data, { [xField]: maxSum }];
|
|
8308
8530
|
}
|
|
8309
|
-
const maxSum = Math.max(...sums.values(), 0);
|
|
8310
|
-
xData = [...data, { [xField]: maxSum }];
|
|
8311
8531
|
}
|
|
8312
8532
|
}
|
|
8313
8533
|
result.x = buildPositionalScale(
|
|
8314
|
-
|
|
8534
|
+
xChannel,
|
|
8315
8535
|
xData,
|
|
8316
8536
|
chartArea.x,
|
|
8317
8537
|
chartArea.x + chartArea.width,
|
|
@@ -8321,26 +8541,50 @@ function computeScales(spec, chartArea, data) {
|
|
|
8321
8541
|
}
|
|
8322
8542
|
if (encoding.y) {
|
|
8323
8543
|
let yData = data;
|
|
8544
|
+
let yChannel = encoding.y;
|
|
8324
8545
|
const isVerticalBar = spec.markType === "bar" && (encoding.x?.type === "nominal" || encoding.x?.type === "ordinal") && encoding.y.type === "quantitative";
|
|
8325
8546
|
const yStackDisabled = encoding.y.stack === null || encoding.y.stack === false;
|
|
8326
8547
|
if ((isVerticalBar || spec.markType === "area") && encoding.color && encoding.y.type === "quantitative" && !yStackDisabled) {
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
if (
|
|
8330
|
-
const
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
const
|
|
8334
|
-
|
|
8335
|
-
|
|
8548
|
+
if (encoding.y.stack === "normalize") {
|
|
8549
|
+
yChannel = { ...encoding.y, scale: { ...encoding.y.scale, domain: [0, 1], nice: false } };
|
|
8550
|
+
} else if (encoding.y.stack === "center") {
|
|
8551
|
+
const xField = encoding.x?.field;
|
|
8552
|
+
const yField = encoding.y.field;
|
|
8553
|
+
if (xField) {
|
|
8554
|
+
const sums = /* @__PURE__ */ new Map();
|
|
8555
|
+
for (const row of data) {
|
|
8556
|
+
const cat = String(row[xField] ?? "");
|
|
8557
|
+
const val = Number(row[yField] ?? 0);
|
|
8558
|
+
if (Number.isFinite(val) && val > 0) {
|
|
8559
|
+
sums.set(cat, (sums.get(cat) ?? 0) + val);
|
|
8560
|
+
}
|
|
8561
|
+
}
|
|
8562
|
+
const maxSum = Math.max(...sums.values(), 0);
|
|
8563
|
+
const half = maxSum / 2;
|
|
8564
|
+
yChannel = {
|
|
8565
|
+
...encoding.y,
|
|
8566
|
+
scale: { ...encoding.y.scale, domain: [-half, half], zero: true }
|
|
8567
|
+
};
|
|
8568
|
+
}
|
|
8569
|
+
} else {
|
|
8570
|
+
const xField = encoding.x?.field;
|
|
8571
|
+
const yField = encoding.y.field;
|
|
8572
|
+
if (xField) {
|
|
8573
|
+
const sums = /* @__PURE__ */ new Map();
|
|
8574
|
+
for (const row of data) {
|
|
8575
|
+
const cat = String(row[xField] ?? "");
|
|
8576
|
+
const val = Number(row[yField] ?? 0);
|
|
8577
|
+
if (Number.isFinite(val) && val > 0) {
|
|
8578
|
+
sums.set(cat, (sums.get(cat) ?? 0) + val);
|
|
8579
|
+
}
|
|
8336
8580
|
}
|
|
8581
|
+
const maxSum = Math.max(...sums.values(), 0);
|
|
8582
|
+
yData = [...data, { [yField]: maxSum }];
|
|
8337
8583
|
}
|
|
8338
|
-
const maxSum = Math.max(...sums.values(), 0);
|
|
8339
|
-
yData = [...data, { [yField]: maxSum }];
|
|
8340
8584
|
}
|
|
8341
8585
|
}
|
|
8342
8586
|
result.y = buildPositionalScale(
|
|
8343
|
-
|
|
8587
|
+
yChannel,
|
|
8344
8588
|
yData,
|
|
8345
8589
|
chartArea.y + chartArea.height,
|
|
8346
8590
|
chartArea.y,
|
|
@@ -8441,7 +8685,7 @@ function truncateEntries(entries, maxCount) {
|
|
|
8441
8685
|
});
|
|
8442
8686
|
return truncated;
|
|
8443
8687
|
}
|
|
8444
|
-
function computeLegend(spec, strategy, theme, chartArea) {
|
|
8688
|
+
function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
|
|
8445
8689
|
if (spec.legend?.show === false || strategy.legendMaxHeight === 0) {
|
|
8446
8690
|
return {
|
|
8447
8691
|
position: "top",
|
|
@@ -8518,7 +8762,7 @@ function computeLegend(spec, strategy, theme, chartArea) {
|
|
|
8518
8762
|
entryGap: 4
|
|
8519
8763
|
};
|
|
8520
8764
|
}
|
|
8521
|
-
const availableWidth = chartArea.width - LEGEND_PADDING * 2 - BRAND_RESERVE_WIDTH;
|
|
8765
|
+
const availableWidth = chartArea.width - LEGEND_PADDING * 2 - (watermark ? BRAND_RESERVE_WIDTH : 0);
|
|
8522
8766
|
if (spec.legend?.symbolLimit != null) {
|
|
8523
8767
|
const limit = Math.max(1, spec.legend.symbolLimit);
|
|
8524
8768
|
if (limit < entries.length) {
|
|
@@ -9137,6 +9381,8 @@ function compileSankey(spec, options) {
|
|
|
9137
9381
|
);
|
|
9138
9382
|
}
|
|
9139
9383
|
const sankeySpec = normalized;
|
|
9384
|
+
const rawWatermark = spec.watermark;
|
|
9385
|
+
const watermark = rawWatermark !== void 0 ? sankeySpec.watermark : options.watermark ?? true;
|
|
9140
9386
|
const mergedThemeConfig = options.theme ? { ...sankeySpec.theme, ...options.theme } : sankeySpec.theme;
|
|
9141
9387
|
const lightTheme = resolveTheme2(mergedThemeConfig);
|
|
9142
9388
|
let theme = lightTheme;
|
|
@@ -9157,7 +9403,10 @@ function compileSankey(spec, options) {
|
|
|
9157
9403
|
},
|
|
9158
9404
|
theme,
|
|
9159
9405
|
options.width,
|
|
9160
|
-
options.measureText
|
|
9406
|
+
options.measureText,
|
|
9407
|
+
"full",
|
|
9408
|
+
void 0,
|
|
9409
|
+
watermark
|
|
9161
9410
|
);
|
|
9162
9411
|
const padding = theme.spacing.padding;
|
|
9163
9412
|
const fullArea = {
|
|
@@ -9167,7 +9416,7 @@ function compileSankey(spec, options) {
|
|
|
9167
9416
|
height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
|
|
9168
9417
|
};
|
|
9169
9418
|
if (fullArea.width <= 0 || fullArea.height <= 0) {
|
|
9170
|
-
return emptyLayout(fullArea, chrome, theme, options);
|
|
9419
|
+
return emptyLayout(fullArea, chrome, theme, options, watermark);
|
|
9171
9420
|
}
|
|
9172
9421
|
const sourceField = sankeySpec.encoding.source.field;
|
|
9173
9422
|
const targetField = sankeySpec.encoding.target.field;
|
|
@@ -9203,7 +9452,7 @@ function compileSankey(spec, options) {
|
|
|
9203
9452
|
height: fullArea.height - legend.bounds.height - legendGap
|
|
9204
9453
|
};
|
|
9205
9454
|
if (area.height <= 0) {
|
|
9206
|
-
return emptyLayout(area, chrome, theme, options);
|
|
9455
|
+
return emptyLayout(area, chrome, theme, options, watermark);
|
|
9207
9456
|
}
|
|
9208
9457
|
const labelFontSize = theme.fonts.sizes.small;
|
|
9209
9458
|
const labelFontWeight = theme.fonts.weights.normal;
|
|
@@ -9347,7 +9596,8 @@ function compileSankey(spec, options) {
|
|
|
9347
9596
|
width: options.width,
|
|
9348
9597
|
height: options.height
|
|
9349
9598
|
},
|
|
9350
|
-
animation: resolvedAnimation
|
|
9599
|
+
animation: resolvedAnimation,
|
|
9600
|
+
watermark
|
|
9351
9601
|
};
|
|
9352
9602
|
}
|
|
9353
9603
|
function buildSankeyLegend(nodeColorMap, colorField, data, sourceField, targetField, theme, area) {
|
|
@@ -9454,7 +9704,7 @@ function buildTooltipDescriptors(nodes, links, valueFormat) {
|
|
|
9454
9704
|
}
|
|
9455
9705
|
return descriptors;
|
|
9456
9706
|
}
|
|
9457
|
-
function emptyLayout(area, chrome, theme, options) {
|
|
9707
|
+
function emptyLayout(area, chrome, theme, options, watermark) {
|
|
9458
9708
|
return {
|
|
9459
9709
|
area,
|
|
9460
9710
|
chrome,
|
|
@@ -9486,7 +9736,8 @@ function emptyLayout(area, chrome, theme, options) {
|
|
|
9486
9736
|
dimensions: {
|
|
9487
9737
|
width: options.width,
|
|
9488
9738
|
height: options.height
|
|
9489
|
-
}
|
|
9739
|
+
},
|
|
9740
|
+
watermark
|
|
9490
9741
|
};
|
|
9491
9742
|
}
|
|
9492
9743
|
|
|
@@ -10086,6 +10337,7 @@ function compileTableLayout(spec, options, theme) {
|
|
|
10086
10337
|
});
|
|
10087
10338
|
return { id: rowId, cells, data: row };
|
|
10088
10339
|
});
|
|
10340
|
+
const watermark = spec.watermark;
|
|
10089
10341
|
const chrome = computeChrome4(
|
|
10090
10342
|
{
|
|
10091
10343
|
title: spec.chrome.title,
|
|
@@ -10096,7 +10348,10 @@ function compileTableLayout(spec, options, theme) {
|
|
|
10096
10348
|
},
|
|
10097
10349
|
theme,
|
|
10098
10350
|
options.width,
|
|
10099
|
-
options.measureText
|
|
10351
|
+
options.measureText,
|
|
10352
|
+
"full",
|
|
10353
|
+
void 0,
|
|
10354
|
+
watermark
|
|
10100
10355
|
);
|
|
10101
10356
|
const titleText = spec.chrome.title?.text ?? "";
|
|
10102
10357
|
const caption = titleText ? `Table: ${titleText}` : `Data table with ${data.length} rows`;
|
|
@@ -10118,7 +10373,8 @@ function compileTableLayout(spec, options, theme) {
|
|
|
10118
10373
|
summary: `${resolvedColumns.length} columns, ${totalFiltered} rows`
|
|
10119
10374
|
},
|
|
10120
10375
|
theme,
|
|
10121
|
-
animation: resolveAnimation(spec.animation)
|
|
10376
|
+
animation: resolveAnimation(spec.animation),
|
|
10377
|
+
watermark
|
|
10122
10378
|
};
|
|
10123
10379
|
}
|
|
10124
10380
|
|
|
@@ -10148,10 +10404,16 @@ function formatValue(value2, fieldType, format2) {
|
|
|
10148
10404
|
}
|
|
10149
10405
|
return String(value2);
|
|
10150
10406
|
}
|
|
10407
|
+
function resolveLabel(ch) {
|
|
10408
|
+
return ch.title ?? ch.axis?.title ?? ch.field;
|
|
10409
|
+
}
|
|
10410
|
+
function resolveFormat(ch) {
|
|
10411
|
+
return ch.format ?? ch.axis?.format;
|
|
10412
|
+
}
|
|
10151
10413
|
function buildExplicitTooltipFields(row, channels) {
|
|
10152
10414
|
return channels.map((ch) => ({
|
|
10153
|
-
label: ch
|
|
10154
|
-
value: formatValue(row[ch.field], ch.type, ch
|
|
10415
|
+
label: resolveLabel(ch),
|
|
10416
|
+
value: formatValue(row[ch.field], ch.type, resolveFormat(ch))
|
|
10155
10417
|
}));
|
|
10156
10418
|
}
|
|
10157
10419
|
function buildFields(row, encoding, color2) {
|
|
@@ -10162,21 +10424,25 @@ function buildFields(row, encoding, color2) {
|
|
|
10162
10424
|
const fields = [];
|
|
10163
10425
|
if (encoding.y) {
|
|
10164
10426
|
fields.push({
|
|
10165
|
-
label: encoding.y
|
|
10166
|
-
value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y
|
|
10427
|
+
label: resolveLabel(encoding.y),
|
|
10428
|
+
value: formatValue(row[encoding.y.field], encoding.y.type, resolveFormat(encoding.y)),
|
|
10167
10429
|
color: color2
|
|
10168
10430
|
});
|
|
10169
10431
|
}
|
|
10170
10432
|
if (encoding.x) {
|
|
10171
10433
|
fields.push({
|
|
10172
|
-
label: encoding.x
|
|
10173
|
-
value: formatValue(row[encoding.x.field], encoding.x.type, encoding.x
|
|
10434
|
+
label: resolveLabel(encoding.x),
|
|
10435
|
+
value: formatValue(row[encoding.x.field], encoding.x.type, resolveFormat(encoding.x))
|
|
10174
10436
|
});
|
|
10175
10437
|
}
|
|
10176
10438
|
if (encoding.size && "field" in encoding.size) {
|
|
10177
10439
|
fields.push({
|
|
10178
|
-
label: encoding.size
|
|
10179
|
-
value: formatValue(
|
|
10440
|
+
label: resolveLabel(encoding.size),
|
|
10441
|
+
value: formatValue(
|
|
10442
|
+
row[encoding.size.field],
|
|
10443
|
+
encoding.size.type,
|
|
10444
|
+
resolveFormat(encoding.size)
|
|
10445
|
+
)
|
|
10180
10446
|
});
|
|
10181
10447
|
}
|
|
10182
10448
|
return fields;
|
|
@@ -10229,14 +10495,14 @@ function tooltipsForArc(mark, encoding, markIndex) {
|
|
|
10229
10495
|
if (encoding.y) {
|
|
10230
10496
|
fields.push({
|
|
10231
10497
|
label: categoryName,
|
|
10232
|
-
value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y
|
|
10498
|
+
value: formatValue(row[encoding.y.field], encoding.y.type, resolveFormat(encoding.y)),
|
|
10233
10499
|
color: getRepresentativeColor9(mark.fill)
|
|
10234
10500
|
});
|
|
10235
10501
|
}
|
|
10236
10502
|
} else if (encoding.y) {
|
|
10237
10503
|
fields.push({
|
|
10238
|
-
label: encoding.y
|
|
10239
|
-
value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y
|
|
10504
|
+
label: resolveLabel(encoding.y),
|
|
10505
|
+
value: formatValue(row[encoding.y.field], encoding.y.type, resolveFormat(encoding.y)),
|
|
10240
10506
|
color: getRepresentativeColor9(mark.fill)
|
|
10241
10507
|
});
|
|
10242
10508
|
}
|
|
@@ -10284,6 +10550,93 @@ function computeTooltipDescriptors(spec, marks) {
|
|
|
10284
10550
|
return descriptors;
|
|
10285
10551
|
}
|
|
10286
10552
|
|
|
10553
|
+
// src/transforms/aggregate.ts
|
|
10554
|
+
function computeAggregate(op, values) {
|
|
10555
|
+
if (values.length === 0) return 0;
|
|
10556
|
+
switch (op) {
|
|
10557
|
+
case "count":
|
|
10558
|
+
return values.length;
|
|
10559
|
+
case "sum":
|
|
10560
|
+
return values.reduce((a, b) => a + b, 0);
|
|
10561
|
+
case "mean": {
|
|
10562
|
+
const sum2 = values.reduce((a, b) => a + b, 0);
|
|
10563
|
+
return sum2 / values.length;
|
|
10564
|
+
}
|
|
10565
|
+
case "median": {
|
|
10566
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
10567
|
+
const mid = Math.floor(sorted.length / 2);
|
|
10568
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
10569
|
+
}
|
|
10570
|
+
case "min":
|
|
10571
|
+
return Math.min(...values);
|
|
10572
|
+
case "max":
|
|
10573
|
+
return Math.max(...values);
|
|
10574
|
+
case "variance": {
|
|
10575
|
+
if (values.length < 2) return 0;
|
|
10576
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
10577
|
+
return values.reduce((a, v) => a + (v - mean) ** 2, 0) / values.length;
|
|
10578
|
+
}
|
|
10579
|
+
case "stdev": {
|
|
10580
|
+
if (values.length < 2) return 0;
|
|
10581
|
+
const m = values.reduce((a, b) => a + b, 0) / values.length;
|
|
10582
|
+
return Math.sqrt(values.reduce((a, v) => a + (v - m) ** 2, 0) / values.length);
|
|
10583
|
+
}
|
|
10584
|
+
case "q1": {
|
|
10585
|
+
const s = [...values].sort((a, b) => a - b);
|
|
10586
|
+
const i = (s.length - 1) * 0.25;
|
|
10587
|
+
const lo = Math.floor(i);
|
|
10588
|
+
const frac = i - lo;
|
|
10589
|
+
return s[lo] + frac * ((s[lo + 1] ?? s[lo]) - s[lo]);
|
|
10590
|
+
}
|
|
10591
|
+
case "q3": {
|
|
10592
|
+
const s = [...values].sort((a, b) => a - b);
|
|
10593
|
+
const i = (s.length - 1) * 0.75;
|
|
10594
|
+
const lo = Math.floor(i);
|
|
10595
|
+
const frac = i - lo;
|
|
10596
|
+
return s[lo] + frac * ((s[lo + 1] ?? s[lo]) - s[lo]);
|
|
10597
|
+
}
|
|
10598
|
+
default:
|
|
10599
|
+
return 0;
|
|
10600
|
+
}
|
|
10601
|
+
}
|
|
10602
|
+
function groupKey(row, groupby) {
|
|
10603
|
+
return groupby.map((f) => String(row[f] ?? "")).join("\0");
|
|
10604
|
+
}
|
|
10605
|
+
function runAggregate(data, transform) {
|
|
10606
|
+
const { aggregate, groupby } = transform;
|
|
10607
|
+
const groups = /* @__PURE__ */ new Map();
|
|
10608
|
+
for (const row of data) {
|
|
10609
|
+
const key = groupKey(row, groupby);
|
|
10610
|
+
const existing = groups.get(key);
|
|
10611
|
+
if (existing) {
|
|
10612
|
+
existing.push(row);
|
|
10613
|
+
} else {
|
|
10614
|
+
groups.set(key, [row]);
|
|
10615
|
+
}
|
|
10616
|
+
}
|
|
10617
|
+
const result = [];
|
|
10618
|
+
for (const rows of groups.values()) {
|
|
10619
|
+
const outRow = {};
|
|
10620
|
+
for (const field of groupby) {
|
|
10621
|
+
outRow[field] = rows[0][field];
|
|
10622
|
+
}
|
|
10623
|
+
for (const agg of aggregate) {
|
|
10624
|
+
if (agg.op === "distinct") {
|
|
10625
|
+
outRow[agg.as] = new Set(rows.map((r) => r[agg.field])).size;
|
|
10626
|
+
continue;
|
|
10627
|
+
}
|
|
10628
|
+
const values = rows.map((r) => {
|
|
10629
|
+
if (agg.op === "count") return 1;
|
|
10630
|
+
const v = Number(r[agg.field]);
|
|
10631
|
+
return Number.isFinite(v) ? v : NaN;
|
|
10632
|
+
}).filter((v) => !Number.isNaN(v));
|
|
10633
|
+
outRow[agg.as] = computeAggregate(agg.op, values);
|
|
10634
|
+
}
|
|
10635
|
+
result.push(outRow);
|
|
10636
|
+
}
|
|
10637
|
+
return result;
|
|
10638
|
+
}
|
|
10639
|
+
|
|
10287
10640
|
// src/transforms/bin.ts
|
|
10288
10641
|
function computeStep(extent2, maxbins, nice2) {
|
|
10289
10642
|
const span = extent2[1] - extent2[0];
|
|
@@ -10380,6 +10733,30 @@ function runFilter(data, predicate) {
|
|
|
10380
10733
|
return data.filter((datum) => evaluatePredicate(datum, predicate));
|
|
10381
10734
|
}
|
|
10382
10735
|
|
|
10736
|
+
// src/transforms/fold.ts
|
|
10737
|
+
function runFold(data, transform) {
|
|
10738
|
+
const { fold } = transform;
|
|
10739
|
+
const [keyAs, valueAs] = transform.as ?? ["key", "value"];
|
|
10740
|
+
const foldSet = new Set(fold);
|
|
10741
|
+
const result = [];
|
|
10742
|
+
for (const row of data) {
|
|
10743
|
+
const base = {};
|
|
10744
|
+
for (const [k, v] of Object.entries(row)) {
|
|
10745
|
+
if (!foldSet.has(k)) {
|
|
10746
|
+
base[k] = v;
|
|
10747
|
+
}
|
|
10748
|
+
}
|
|
10749
|
+
for (const field of fold) {
|
|
10750
|
+
result.push({
|
|
10751
|
+
...base,
|
|
10752
|
+
[keyAs]: field,
|
|
10753
|
+
[valueAs]: row[field]
|
|
10754
|
+
});
|
|
10755
|
+
}
|
|
10756
|
+
}
|
|
10757
|
+
return result;
|
|
10758
|
+
}
|
|
10759
|
+
|
|
10383
10760
|
// src/transforms/timeunit.ts
|
|
10384
10761
|
function extractTimeUnit(date2, unit2) {
|
|
10385
10762
|
switch (unit2) {
|
|
@@ -10457,6 +10834,10 @@ function runTransforms(data, transforms) {
|
|
|
10457
10834
|
result = runCalculate(result, transform);
|
|
10458
10835
|
} else if ("timeUnit" in transform) {
|
|
10459
10836
|
result = runTimeUnit(result, transform);
|
|
10837
|
+
} else if ("aggregate" in transform) {
|
|
10838
|
+
result = runAggregate(result, transform);
|
|
10839
|
+
} else if ("fold" in transform) {
|
|
10840
|
+
result = runFold(result, transform);
|
|
10460
10841
|
}
|
|
10461
10842
|
}
|
|
10462
10843
|
return result;
|
|
@@ -10489,71 +10870,55 @@ var builtinRenderers = {
|
|
|
10489
10870
|
for (const [type, renderer] of Object.entries(builtinRenderers)) {
|
|
10490
10871
|
registerChartRenderer(type, renderer);
|
|
10491
10872
|
}
|
|
10492
|
-
function
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
const
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
const
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
}
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
const
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
right2 = rm.x + rm.width;
|
|
10529
|
-
} else {
|
|
10530
|
-
continue;
|
|
10531
|
-
}
|
|
10532
|
-
const key = Math.round(cy);
|
|
10533
|
-
const existing = rows.get(key);
|
|
10534
|
-
if (existing) {
|
|
10535
|
-
existing.minX = Math.min(existing.minX, left2);
|
|
10536
|
-
existing.maxX = Math.max(existing.maxX, right2);
|
|
10537
|
-
} else {
|
|
10538
|
-
rows.set(key, { minX: left2, maxX: right2, bandY: cy });
|
|
10873
|
+
function expandEncodingSugar(spec) {
|
|
10874
|
+
const encoding = spec.encoding;
|
|
10875
|
+
if (!encoding) return spec;
|
|
10876
|
+
const generatedTransforms = [];
|
|
10877
|
+
const updatedEncoding = { ...encoding };
|
|
10878
|
+
let changed = false;
|
|
10879
|
+
for (const channel of Object.keys(encoding)) {
|
|
10880
|
+
const ch = encoding[channel];
|
|
10881
|
+
if (!ch || !ch.field) continue;
|
|
10882
|
+
if (ch.bin != null && ch.bin !== false) {
|
|
10883
|
+
const field = ch.field;
|
|
10884
|
+
const outputField = `bin_${field}`;
|
|
10885
|
+
const binTransform = {
|
|
10886
|
+
bin: ch.bin === true ? true : ch.bin,
|
|
10887
|
+
field,
|
|
10888
|
+
as: outputField
|
|
10889
|
+
};
|
|
10890
|
+
generatedTransforms.push(binTransform);
|
|
10891
|
+
const { bin: _bin, ...rest } = ch;
|
|
10892
|
+
updatedEncoding[channel] = { ...rest, field: outputField };
|
|
10893
|
+
changed = true;
|
|
10894
|
+
}
|
|
10895
|
+
const current = updatedEncoding[channel] ?? ch;
|
|
10896
|
+
if (current.timeUnit) {
|
|
10897
|
+
const field = current.field;
|
|
10898
|
+
const unit2 = current.timeUnit;
|
|
10899
|
+
const outputField = `${unit2}_${field}`;
|
|
10900
|
+
const timeUnitTransform = {
|
|
10901
|
+
timeUnit: unit2,
|
|
10902
|
+
field,
|
|
10903
|
+
as: outputField
|
|
10904
|
+
};
|
|
10905
|
+
generatedTransforms.push(timeUnitTransform);
|
|
10906
|
+
const { timeUnit: _tu, ...rest } = current;
|
|
10907
|
+
updatedEncoding[channel] = { ...rest, field: outputField };
|
|
10908
|
+
changed = true;
|
|
10539
10909
|
}
|
|
10540
10910
|
}
|
|
10541
|
-
|
|
10542
|
-
const
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
y: bandY - bandwidth / 2,
|
|
10549
|
-
width: maxX - minX,
|
|
10550
|
-
height: bandwidth
|
|
10551
|
-
});
|
|
10552
|
-
}
|
|
10553
|
-
return obstacles;
|
|
10911
|
+
if (!changed) return spec;
|
|
10912
|
+
const existingTransforms = spec.transform ?? [];
|
|
10913
|
+
return {
|
|
10914
|
+
...spec,
|
|
10915
|
+
encoding: updatedEncoding,
|
|
10916
|
+
transform: [...generatedTransforms, ...existingTransforms]
|
|
10917
|
+
};
|
|
10554
10918
|
}
|
|
10555
10919
|
function compileChart(spec, options) {
|
|
10556
|
-
const
|
|
10920
|
+
const expandedSpec = spec && typeof spec === "object" && !Array.isArray(spec) ? expandEncodingSugar(spec) : spec;
|
|
10921
|
+
const { spec: normalized } = compile(expandedSpec);
|
|
10557
10922
|
if ("type" in normalized && normalized.type === "table") {
|
|
10558
10923
|
throw new Error("compileChart received a table spec. Use compileTable instead.");
|
|
10559
10924
|
}
|
|
@@ -10564,14 +10929,16 @@ function compileChart(spec, options) {
|
|
|
10564
10929
|
throw new Error("compileChart received a sankey spec. Use compileSankey instead.");
|
|
10565
10930
|
}
|
|
10566
10931
|
let chartSpec = normalized;
|
|
10567
|
-
const
|
|
10932
|
+
const rawWatermark = expandedSpec.watermark;
|
|
10933
|
+
const watermark = rawWatermark !== void 0 ? chartSpec.watermark : options.watermark ?? true;
|
|
10934
|
+
const rawTransforms = expandedSpec.transform;
|
|
10568
10935
|
if (rawTransforms && rawTransforms.length > 0) {
|
|
10569
10936
|
chartSpec = { ...chartSpec, data: runTransforms(chartSpec.data, rawTransforms) };
|
|
10570
10937
|
}
|
|
10571
10938
|
const breakpoint = getBreakpoint(options.width);
|
|
10572
10939
|
const heightClass = getHeightClass(options.height);
|
|
10573
10940
|
const strategy = getLayoutStrategy(breakpoint, heightClass);
|
|
10574
|
-
const rawSpec =
|
|
10941
|
+
const rawSpec = expandedSpec;
|
|
10575
10942
|
const overrides = rawSpec.overrides;
|
|
10576
10943
|
if (overrides?.[breakpoint]) {
|
|
10577
10944
|
const bp = overrides[breakpoint];
|
|
@@ -10622,8 +10989,8 @@ function compileChart(spec, options) {
|
|
|
10622
10989
|
width: options.width,
|
|
10623
10990
|
height: options.height
|
|
10624
10991
|
};
|
|
10625
|
-
const legendLayout = computeLegend(chartSpec, strategy, theme, preliminaryArea);
|
|
10626
|
-
const dims = computeDimensions(chartSpec, options, legendLayout, theme, strategy);
|
|
10992
|
+
const legendLayout = computeLegend(chartSpec, strategy, theme, preliminaryArea, watermark);
|
|
10993
|
+
const dims = computeDimensions(chartSpec, options, legendLayout, theme, strategy, watermark);
|
|
10627
10994
|
const chartArea = dims.chartArea;
|
|
10628
10995
|
const legendArea = { ...chartArea };
|
|
10629
10996
|
if (legendLayout.entries.length > 0) {
|
|
@@ -10641,7 +11008,7 @@ function compileChart(spec, options) {
|
|
|
10641
11008
|
break;
|
|
10642
11009
|
}
|
|
10643
11010
|
}
|
|
10644
|
-
const finalLegend = computeLegend(chartSpec, strategy, theme, legendArea);
|
|
11011
|
+
const finalLegend = computeLegend(chartSpec, strategy, theme, legendArea, watermark);
|
|
10645
11012
|
let renderData = chartSpec.data;
|
|
10646
11013
|
if (chartSpec.hiddenSeries.length > 0 && chartSpec.encoding.color && "field" in chartSpec.encoding.color) {
|
|
10647
11014
|
const colorField = chartSpec.encoding.color.field;
|
|
@@ -10682,20 +11049,11 @@ function compileChart(spec, options) {
|
|
|
10682
11049
|
if (!isRadial) {
|
|
10683
11050
|
computeGridlines(axes, chartArea);
|
|
10684
11051
|
}
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
if (isVertical) {
|
|
10691
|
-
rendererKey = "bar:vertical";
|
|
10692
|
-
}
|
|
10693
|
-
} else if (rendererKey === "arc") {
|
|
10694
|
-
const innerRadius = renderSpec.markDef.innerRadius;
|
|
10695
|
-
if (innerRadius && innerRadius > 0) {
|
|
10696
|
-
rendererKey = "arc:donut";
|
|
10697
|
-
}
|
|
10698
|
-
}
|
|
11052
|
+
const rendererKey = resolveRendererKey(
|
|
11053
|
+
renderSpec.markType,
|
|
11054
|
+
renderSpec.encoding,
|
|
11055
|
+
renderSpec.markDef
|
|
11056
|
+
);
|
|
10699
11057
|
const renderer = getChartRenderer(rendererKey);
|
|
10700
11058
|
const marks = renderer ? renderer(renderSpec, scales, chartArea, strategy, theme) : [];
|
|
10701
11059
|
const obstacles = [];
|
|
@@ -10708,12 +11066,14 @@ function compileChart(spec, options) {
|
|
|
10708
11066
|
obstacles.push(computeLabelBounds(mark.label));
|
|
10709
11067
|
}
|
|
10710
11068
|
}
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
11069
|
+
if (watermark) {
|
|
11070
|
+
const brandPadding = theme.spacing.padding;
|
|
11071
|
+
const brandX = dims.total.width - brandPadding - BRAND_RESERVE_WIDTH2;
|
|
11072
|
+
const xAxisExtent = axes.x?.label ? 48 : axes.x ? 26 : 0;
|
|
11073
|
+
const firstBottomChrome = dims.chrome.source ?? dims.chrome.byline ?? dims.chrome.footer;
|
|
11074
|
+
const brandY = firstBottomChrome ? chartArea.y + chartArea.height + xAxisExtent + firstBottomChrome.y : chartArea.y + chartArea.height + xAxisExtent + theme.spacing.chartToFooter;
|
|
11075
|
+
obstacles.push({ x: brandX, y: brandY, width: BRAND_RESERVE_WIDTH2, height: 30 });
|
|
11076
|
+
}
|
|
10717
11077
|
const annotations = computeAnnotations(
|
|
10718
11078
|
chartSpec,
|
|
10719
11079
|
scales,
|
|
@@ -10741,37 +11101,7 @@ function compileChart(spec, options) {
|
|
|
10741
11101
|
},
|
|
10742
11102
|
chartSpec.data
|
|
10743
11103
|
);
|
|
10744
|
-
|
|
10745
|
-
const indexed = marks.map((m, i) => ({ mark: m, idx: i }));
|
|
10746
|
-
indexed.sort((a, b) => {
|
|
10747
|
-
const av = getMarkPrimaryValue(a.mark);
|
|
10748
|
-
const bv = getMarkPrimaryValue(b.mark);
|
|
10749
|
-
return av - bv;
|
|
10750
|
-
});
|
|
10751
|
-
for (let i = 0; i < indexed.length; i++) {
|
|
10752
|
-
const m = indexed[i].mark;
|
|
10753
|
-
if (m.type === "rect" && m.stackGroup) continue;
|
|
10754
|
-
m.animationIndex = i;
|
|
10755
|
-
}
|
|
10756
|
-
}
|
|
10757
|
-
if (resolvedAnimation?.enabled) {
|
|
10758
|
-
const groupIndexMap = /* @__PURE__ */ new Map();
|
|
10759
|
-
const groupStackPos = /* @__PURE__ */ new Map();
|
|
10760
|
-
let nextGroupIndex = 0;
|
|
10761
|
-
for (const mark of marks) {
|
|
10762
|
-
if (mark.type === "rect" && mark.stackGroup) {
|
|
10763
|
-
const rect = mark;
|
|
10764
|
-
const group = rect.stackGroup;
|
|
10765
|
-
if (!groupIndexMap.has(group)) {
|
|
10766
|
-
groupIndexMap.set(group, nextGroupIndex++);
|
|
10767
|
-
}
|
|
10768
|
-
rect.animationIndex = groupIndexMap.get(group);
|
|
10769
|
-
const pos = groupStackPos.get(group) ?? 0;
|
|
10770
|
-
rect.stackPos = pos;
|
|
10771
|
-
groupStackPos.set(group, pos + 1);
|
|
10772
|
-
}
|
|
10773
|
-
}
|
|
10774
|
-
}
|
|
11104
|
+
assignAnimationIndices(marks, resolvedAnimation);
|
|
10775
11105
|
return {
|
|
10776
11106
|
area: chartArea,
|
|
10777
11107
|
chrome: dims.chrome,
|
|
@@ -10794,28 +11124,10 @@ function compileChart(spec, options) {
|
|
|
10794
11124
|
width: options.width,
|
|
10795
11125
|
height: options.height
|
|
10796
11126
|
},
|
|
10797
|
-
animation: resolvedAnimation
|
|
11127
|
+
animation: resolvedAnimation,
|
|
11128
|
+
watermark
|
|
10798
11129
|
};
|
|
10799
11130
|
}
|
|
10800
|
-
function getMarkPrimaryValue(mark) {
|
|
10801
|
-
switch (mark.type) {
|
|
10802
|
-
case "rect":
|
|
10803
|
-
return mark.height;
|
|
10804
|
-
// bar height is the primary value encoding
|
|
10805
|
-
case "point":
|
|
10806
|
-
return mark.cy;
|
|
10807
|
-
// y position for scatter
|
|
10808
|
-
case "arc":
|
|
10809
|
-
return mark.endAngle - mark.startAngle;
|
|
10810
|
-
// arc angle extent
|
|
10811
|
-
case "line":
|
|
10812
|
-
case "area":
|
|
10813
|
-
return 0;
|
|
10814
|
-
// series marks don't have individual values
|
|
10815
|
-
default:
|
|
10816
|
-
return 0;
|
|
10817
|
-
}
|
|
10818
|
-
}
|
|
10819
11131
|
function compileLayer(spec, options) {
|
|
10820
11132
|
const leaves = flattenLayers(spec);
|
|
10821
11133
|
if (leaves.length === 0) {
|
|
@@ -10864,6 +11176,7 @@ function buildPrimarySpec(leaves, layerSpec) {
|
|
|
10864
11176
|
responsive: layerSpec.responsive ?? leaves[0].responsive,
|
|
10865
11177
|
theme: layerSpec.theme ?? leaves[0].theme,
|
|
10866
11178
|
darkMode: layerSpec.darkMode ?? leaves[0].darkMode,
|
|
11179
|
+
watermark: layerSpec.watermark ?? leaves[0].watermark,
|
|
10867
11180
|
hiddenSeries: layerSpec.hiddenSeries ?? leaves[0].hiddenSeries
|
|
10868
11181
|
};
|
|
10869
11182
|
return primary;
|
|
@@ -10880,7 +11193,9 @@ function compileTable(spec, options) {
|
|
|
10880
11193
|
if (options.darkMode) {
|
|
10881
11194
|
theme = adaptTheme3(theme);
|
|
10882
11195
|
}
|
|
10883
|
-
|
|
11196
|
+
const rawWatermark = spec.watermark;
|
|
11197
|
+
const watermark = rawWatermark !== void 0 ? tableSpec.watermark : options.watermark ?? true;
|
|
11198
|
+
return compileTableLayout({ ...tableSpec, watermark }, options, theme);
|
|
10884
11199
|
}
|
|
10885
11200
|
function compileGraph2(spec, options) {
|
|
10886
11201
|
return compileGraph(spec, options);
|