@opendata-ai/openchart-engine 6.12.0 → 6.15.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.js +1022 -648
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- 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 +390 -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/index.ts +3 -0
- package/src/charts/bar/labels.ts +38 -14
- package/src/charts/column/__tests__/compute.test.ts +99 -0
- package/src/charts/column/compute.ts +27 -6
- package/src/charts/column/index.ts +3 -0
- package/src/charts/column/labels.ts +35 -13
- package/src/charts/dot/index.ts +10 -1
- package/src/charts/dot/labels.ts +37 -6
- package/src/charts/line/area.ts +31 -6
- package/src/charts/line/compute.ts +7 -2
- package/src/charts/line/index.ts +33 -2
- package/src/charts/post-process.ts +215 -0
- package/src/compile.ts +91 -158
- package/src/compiler/normalize.ts +2 -2
- package/src/layout/axes.ts +12 -15
- package/src/layout/dimensions.ts +3 -3
- package/src/layout/scales.ts +116 -36
- package/src/legend/compute.ts +2 -4
- package/src/tooltips/__tests__/compute.test.ts +188 -0
- package/src/tooltips/compute.ts +54 -12
- 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",
|
|
@@ -967,11 +968,17 @@ function computeSimpleBars(data, valueField, categoryField, xScale, yScale, band
|
|
|
967
968
|
|
|
968
969
|
// src/charts/bar/labels.ts
|
|
969
970
|
import {
|
|
971
|
+
abbreviateNumber as abbreviateNumber2,
|
|
970
972
|
buildD3Formatter,
|
|
971
973
|
estimateTextWidth as estimateTextWidth2,
|
|
974
|
+
formatNumber as formatNumber2,
|
|
972
975
|
getRepresentativeColor,
|
|
973
976
|
resolveCollisions
|
|
974
977
|
} from "@opendata-ai/openchart-core";
|
|
978
|
+
function formatBarValue2(value2) {
|
|
979
|
+
if (Math.abs(value2) >= 1e3) return abbreviateNumber2(value2);
|
|
980
|
+
return formatNumber2(value2);
|
|
981
|
+
}
|
|
975
982
|
var SUFFIX_MULTIPLIERS = {
|
|
976
983
|
K: 1e3,
|
|
977
984
|
M: 1e6,
|
|
@@ -979,7 +986,7 @@ var SUFFIX_MULTIPLIERS = {
|
|
|
979
986
|
T: 1e12
|
|
980
987
|
};
|
|
981
988
|
function parseDisplayNumber(raw) {
|
|
982
|
-
const trimmed = raw.trim();
|
|
989
|
+
const trimmed = raw.trim().replace(/\u2212/g, "-");
|
|
983
990
|
if (!trimmed) return NaN;
|
|
984
991
|
const last = trimmed[trimmed.length - 1].toUpperCase();
|
|
985
992
|
const multiplier = SUFFIX_MULTIPLIERS[last];
|
|
@@ -988,27 +995,39 @@ function parseDisplayNumber(raw) {
|
|
|
988
995
|
const n = Number(numPart);
|
|
989
996
|
return Number.isNaN(n) ? NaN : n * multiplier;
|
|
990
997
|
}
|
|
998
|
+
if (last === "%") {
|
|
999
|
+
return Number(trimmed.slice(0, -1).replace(/,/g, ""));
|
|
1000
|
+
}
|
|
991
1001
|
return Number(trimmed.replace(/,/g, ""));
|
|
992
1002
|
}
|
|
993
1003
|
var LABEL_FONT_SIZE = 11;
|
|
994
1004
|
var LABEL_FONT_WEIGHT = 600;
|
|
995
1005
|
var LABEL_PADDING = 6;
|
|
996
1006
|
var MIN_WIDTH_FOR_INSIDE_LABEL = 40;
|
|
997
|
-
function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix) {
|
|
1007
|
+
function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField) {
|
|
998
1008
|
if (density === "none") return [];
|
|
999
1009
|
const targetMarks = density === "endpoints" && marks.length > 1 ? [marks[0], marks[marks.length - 1]] : marks;
|
|
1000
1010
|
const candidates = [];
|
|
1001
1011
|
const fitsInSegment = [];
|
|
1002
1012
|
const formatter = buildD3Formatter(labelFormat);
|
|
1003
1013
|
for (const mark of targetMarks) {
|
|
1004
|
-
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1014
|
+
let valuePart;
|
|
1015
|
+
const rawNum = valueField != null ? Number(mark.data[valueField]) : NaN;
|
|
1016
|
+
if (formatter && Number.isFinite(rawNum)) {
|
|
1017
|
+
valuePart = formatter(rawNum);
|
|
1018
|
+
} else if (Number.isFinite(rawNum)) {
|
|
1019
|
+
valuePart = formatBarValue2(rawNum);
|
|
1020
|
+
} else {
|
|
1021
|
+
const ariaLabel = mark.aria.label;
|
|
1022
|
+
const lastColon = ariaLabel.lastIndexOf(":");
|
|
1023
|
+
const rawValue = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
|
|
1024
|
+
if (!rawValue) continue;
|
|
1025
|
+
if (formatter) {
|
|
1026
|
+
const num = parseDisplayNumber(rawValue);
|
|
1027
|
+
valuePart = !Number.isNaN(num) ? formatter(num) : rawValue;
|
|
1028
|
+
} else {
|
|
1029
|
+
valuePart = rawValue;
|
|
1030
|
+
}
|
|
1012
1031
|
}
|
|
1013
1032
|
if (labelPrefix) valuePart = labelPrefix + valuePart;
|
|
1014
1033
|
const textWidth = estimateTextWidth2(valuePart, LABEL_FONT_SIZE, LABEL_FONT_WEIGHT);
|
|
@@ -1093,12 +1112,14 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
|
|
|
1093
1112
|
// src/charts/bar/index.ts
|
|
1094
1113
|
var barRenderer = (spec, scales, chartArea, strategy, _theme) => {
|
|
1095
1114
|
const marks = computeBarMarks(spec, scales, chartArea, strategy);
|
|
1115
|
+
const valueField = spec.encoding?.x && "field" in spec.encoding.x ? spec.encoding.x.field : void 0;
|
|
1096
1116
|
const labels = computeBarLabels(
|
|
1097
1117
|
marks,
|
|
1098
1118
|
chartArea,
|
|
1099
1119
|
spec.labels.density,
|
|
1100
1120
|
spec.labels.format,
|
|
1101
|
-
spec.labels.prefix
|
|
1121
|
+
spec.labels.prefix,
|
|
1122
|
+
valueField
|
|
1102
1123
|
);
|
|
1103
1124
|
for (let i = 0; i < marks.length && i < labels.length; i++) {
|
|
1104
1125
|
marks[i].label = labels[i];
|
|
@@ -1107,11 +1128,11 @@ var barRenderer = (spec, scales, chartArea, strategy, _theme) => {
|
|
|
1107
1128
|
};
|
|
1108
1129
|
|
|
1109
1130
|
// src/charts/column/compute.ts
|
|
1110
|
-
import { abbreviateNumber as
|
|
1131
|
+
import { abbreviateNumber as abbreviateNumber3, formatNumber as formatNumber3, isGradientDef as isGradientDef2 } from "@opendata-ai/openchart-core";
|
|
1111
1132
|
var MIN_COLUMN_HEIGHT = 1;
|
|
1112
1133
|
function formatColumnValue(value2) {
|
|
1113
|
-
if (Math.abs(value2) >= 1e3) return
|
|
1114
|
-
return
|
|
1134
|
+
if (Math.abs(value2) >= 1e3) return abbreviateNumber3(value2);
|
|
1135
|
+
return formatNumber3(value2);
|
|
1115
1136
|
}
|
|
1116
1137
|
function computeColumnMarks(spec, scales, _chartArea, _strategy) {
|
|
1117
1138
|
const encoding = spec.encoding;
|
|
@@ -1149,6 +1170,7 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
|
|
|
1149
1170
|
scales
|
|
1150
1171
|
);
|
|
1151
1172
|
}
|
|
1173
|
+
const stackMode = yChannel.stack === "normalize" ? "normalize" : yChannel.stack === "center" ? "center" : "zero";
|
|
1152
1174
|
return computeStackedColumns(
|
|
1153
1175
|
spec.data,
|
|
1154
1176
|
xChannel.field,
|
|
@@ -1158,7 +1180,8 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
|
|
|
1158
1180
|
yScale,
|
|
1159
1181
|
bandwidth,
|
|
1160
1182
|
baseline,
|
|
1161
|
-
scales
|
|
1183
|
+
scales,
|
|
1184
|
+
stackMode
|
|
1162
1185
|
);
|
|
1163
1186
|
}
|
|
1164
1187
|
return computeColoredColumns(
|
|
@@ -1236,13 +1259,13 @@ function computeColoredColumns(data, categoryField, valueField, colorField, xSca
|
|
|
1236
1259
|
if (!Number.isFinite(value2)) continue;
|
|
1237
1260
|
const bandX = xScale(category);
|
|
1238
1261
|
if (bandX === void 0) continue;
|
|
1239
|
-
const
|
|
1240
|
-
const color2 = getColor(scales,
|
|
1262
|
+
const groupKey2 = String(row[colorField] ?? "");
|
|
1263
|
+
const color2 = getColor(scales, groupKey2);
|
|
1241
1264
|
const yPos = yScale(value2);
|
|
1242
1265
|
const columnHeight = Math.max(Math.abs(baseline - yPos), MIN_COLUMN_HEIGHT);
|
|
1243
1266
|
const y2 = value2 >= 0 ? yPos : baseline;
|
|
1244
1267
|
const aria = {
|
|
1245
|
-
label: `${category}, ${
|
|
1268
|
+
label: `${category}, ${groupKey2}: ${formatColumnValue(value2)}`
|
|
1246
1269
|
};
|
|
1247
1270
|
marks.push({
|
|
1248
1271
|
type: "rect",
|
|
@@ -1280,17 +1303,17 @@ function computeGroupedColumns(data, categoryField, valueField, colorField, xSca
|
|
|
1280
1303
|
const bandX = xScale(category);
|
|
1281
1304
|
if (bandX === void 0) continue;
|
|
1282
1305
|
for (const row of rows) {
|
|
1283
|
-
const
|
|
1306
|
+
const groupKey2 = String(row[colorField] ?? "");
|
|
1284
1307
|
const value2 = Number(row[valueField] ?? 0);
|
|
1285
1308
|
if (!Number.isFinite(value2)) continue;
|
|
1286
|
-
const groupIndex = groupIndexMap.get(
|
|
1287
|
-
const color2 = getColor(scales,
|
|
1309
|
+
const groupIndex = groupIndexMap.get(groupKey2) ?? 0;
|
|
1310
|
+
const color2 = getColor(scales, groupKey2);
|
|
1288
1311
|
const yPos = yScale(value2);
|
|
1289
1312
|
const columnHeight = Math.max(Math.abs(baseline - yPos), MIN_COLUMN_HEIGHT);
|
|
1290
1313
|
const y2 = value2 >= 0 ? yPos : baseline;
|
|
1291
1314
|
const subX = bandX + groupIndex * (subBandWidth + gap);
|
|
1292
1315
|
const aria = {
|
|
1293
|
-
label: `${category}, ${
|
|
1316
|
+
label: `${category}, ${groupKey2}: ${formatColumnValue(value2)}`
|
|
1294
1317
|
};
|
|
1295
1318
|
marks.push({
|
|
1296
1319
|
type: "rect",
|
|
@@ -1308,28 +1331,34 @@ function computeGroupedColumns(data, categoryField, valueField, colorField, xSca
|
|
|
1308
1331
|
}
|
|
1309
1332
|
return marks;
|
|
1310
1333
|
}
|
|
1311
|
-
function computeStackedColumns(data, categoryField, valueField, colorField, xScale, yScale, bandwidth, _baseline, scales) {
|
|
1334
|
+
function computeStackedColumns(data, categoryField, valueField, colorField, xScale, yScale, bandwidth, _baseline, scales, stackMode = "zero") {
|
|
1312
1335
|
const marks = [];
|
|
1313
1336
|
const categoryGroups = groupByField(data, categoryField);
|
|
1314
1337
|
for (const [category, rows] of categoryGroups) {
|
|
1315
1338
|
const bandX = xScale(category);
|
|
1316
1339
|
if (bandX === void 0) continue;
|
|
1317
|
-
let
|
|
1340
|
+
let categoryTotal = 0;
|
|
1318
1341
|
for (const row of rows) {
|
|
1319
|
-
const
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1342
|
+
const v = Number(row[valueField] ?? 0);
|
|
1343
|
+
if (Number.isFinite(v) && v > 0) categoryTotal += v;
|
|
1344
|
+
}
|
|
1345
|
+
let cumulativeValue = stackMode === "center" ? -categoryTotal / 2 : 0;
|
|
1346
|
+
for (const row of rows) {
|
|
1347
|
+
const groupKey2 = String(row[colorField] ?? "");
|
|
1348
|
+
const rawValue = Number(row[valueField] ?? 0);
|
|
1349
|
+
if (!Number.isFinite(rawValue) || rawValue <= 0) continue;
|
|
1350
|
+
const value2 = stackMode === "normalize" && categoryTotal > 0 ? rawValue / categoryTotal : rawValue;
|
|
1351
|
+
const color2 = getColor(scales, groupKey2);
|
|
1323
1352
|
const yTop = yScale(cumulativeValue + value2);
|
|
1324
1353
|
const yBottom = yScale(cumulativeValue);
|
|
1325
1354
|
const columnHeight = Math.max(Math.abs(yBottom - yTop), MIN_COLUMN_HEIGHT);
|
|
1326
1355
|
const aria = {
|
|
1327
|
-
label: `${category}, ${
|
|
1356
|
+
label: `${category}, ${groupKey2}: ${formatColumnValue(rawValue)}`
|
|
1328
1357
|
};
|
|
1329
1358
|
marks.push({
|
|
1330
1359
|
type: "rect",
|
|
1331
1360
|
x: bandX,
|
|
1332
|
-
y: yTop,
|
|
1361
|
+
y: Math.min(yTop, yBottom),
|
|
1333
1362
|
width: bandwidth,
|
|
1334
1363
|
height: columnHeight,
|
|
1335
1364
|
fill: color2,
|
|
@@ -1347,28 +1376,43 @@ function computeStackedColumns(data, categoryField, valueField, colorField, xSca
|
|
|
1347
1376
|
|
|
1348
1377
|
// src/charts/column/labels.ts
|
|
1349
1378
|
import {
|
|
1379
|
+
abbreviateNumber as abbreviateNumber4,
|
|
1350
1380
|
buildD3Formatter as buildD3Formatter2,
|
|
1351
1381
|
estimateTextWidth as estimateTextWidth3,
|
|
1382
|
+
formatNumber as formatNumber4,
|
|
1352
1383
|
getRepresentativeColor as getRepresentativeColor2,
|
|
1353
1384
|
resolveCollisions as resolveCollisions2
|
|
1354
1385
|
} from "@opendata-ai/openchart-core";
|
|
1386
|
+
function formatColumnValue2(value2) {
|
|
1387
|
+
if (Math.abs(value2) >= 1e3) return abbreviateNumber4(value2);
|
|
1388
|
+
return formatNumber4(value2);
|
|
1389
|
+
}
|
|
1355
1390
|
var LABEL_FONT_SIZE2 = 10;
|
|
1356
1391
|
var LABEL_FONT_WEIGHT2 = 600;
|
|
1357
1392
|
var LABEL_OFFSET_Y = 6;
|
|
1358
|
-
function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix) {
|
|
1393
|
+
function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField) {
|
|
1359
1394
|
if (density === "none") return [];
|
|
1360
1395
|
const targetMarks = density === "endpoints" && marks.length > 1 ? [marks[0], marks[marks.length - 1]] : marks;
|
|
1361
1396
|
const formatter = buildD3Formatter2(labelFormat);
|
|
1362
1397
|
const candidates = [];
|
|
1363
1398
|
for (const mark of targetMarks) {
|
|
1364
|
-
|
|
1365
|
-
const
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1399
|
+
let valuePart;
|
|
1400
|
+
const rawNum = valueField != null ? Number(mark.data[valueField]) : NaN;
|
|
1401
|
+
if (formatter && Number.isFinite(rawNum)) {
|
|
1402
|
+
valuePart = formatter(rawNum);
|
|
1403
|
+
} else if (Number.isFinite(rawNum)) {
|
|
1404
|
+
valuePart = formatColumnValue2(rawNum);
|
|
1405
|
+
} else {
|
|
1406
|
+
const ariaLabel = mark.aria.label;
|
|
1407
|
+
const lastColon = ariaLabel.lastIndexOf(":");
|
|
1408
|
+
const rawValue = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
|
|
1409
|
+
if (!rawValue) continue;
|
|
1410
|
+
if (formatter) {
|
|
1411
|
+
const num = Number(rawValue.replace(/[^0-9.-]/g, ""));
|
|
1412
|
+
valuePart = !Number.isNaN(num) ? formatter(num) : rawValue;
|
|
1413
|
+
} else {
|
|
1414
|
+
valuePart = rawValue;
|
|
1415
|
+
}
|
|
1372
1416
|
}
|
|
1373
1417
|
if (labelPrefix) valuePart = labelPrefix + valuePart;
|
|
1374
1418
|
const numericValue = parseFloat(valuePart);
|
|
@@ -1411,12 +1455,14 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, l
|
|
|
1411
1455
|
// src/charts/column/index.ts
|
|
1412
1456
|
var columnRenderer = (spec, scales, chartArea, strategy, _theme) => {
|
|
1413
1457
|
const marks = computeColumnMarks(spec, scales, chartArea, strategy);
|
|
1458
|
+
const valueField = spec.encoding?.y && "field" in spec.encoding.y ? spec.encoding.y.field : void 0;
|
|
1414
1459
|
const labels = computeColumnLabels(
|
|
1415
1460
|
marks,
|
|
1416
1461
|
chartArea,
|
|
1417
1462
|
spec.labels.density,
|
|
1418
1463
|
spec.labels.format,
|
|
1419
|
-
spec.labels.prefix
|
|
1464
|
+
spec.labels.prefix,
|
|
1465
|
+
valueField
|
|
1420
1466
|
);
|
|
1421
1467
|
for (let i = 0; i < marks.length && i < labels.length; i++) {
|
|
1422
1468
|
marks[i].label = labels[i];
|
|
@@ -1574,22 +1620,42 @@ function computeLollipopMarks(data, valueField, categoryField, xScale, yScale, b
|
|
|
1574
1620
|
|
|
1575
1621
|
// src/charts/dot/labels.ts
|
|
1576
1622
|
import {
|
|
1623
|
+
abbreviateNumber as abbreviateNumber5,
|
|
1624
|
+
buildD3Formatter as buildD3Formatter3,
|
|
1577
1625
|
estimateTextWidth as estimateTextWidth4,
|
|
1626
|
+
formatNumber as formatNumber5,
|
|
1578
1627
|
getRepresentativeColor as getRepresentativeColor3,
|
|
1579
1628
|
resolveCollisions as resolveCollisions3
|
|
1580
1629
|
} from "@opendata-ai/openchart-core";
|
|
1630
|
+
function formatDotValue(value2) {
|
|
1631
|
+
if (Math.abs(value2) >= 1e3) return abbreviateNumber5(value2);
|
|
1632
|
+
return formatNumber5(value2);
|
|
1633
|
+
}
|
|
1581
1634
|
var LABEL_FONT_SIZE3 = 11;
|
|
1582
1635
|
var LABEL_FONT_WEIGHT3 = 600;
|
|
1583
1636
|
var LABEL_OFFSET_X = 10;
|
|
1584
|
-
function computeDotLabels(marks, _chartArea, density = "auto", labelPrefix) {
|
|
1637
|
+
function computeDotLabels(marks, _chartArea, density = "auto", labelPrefix, labelFormat, valueField) {
|
|
1585
1638
|
if (density === "none") return [];
|
|
1586
1639
|
const targetMarks = density === "endpoints" && marks.length > 1 ? [marks[0], marks[marks.length - 1]] : marks;
|
|
1640
|
+
const formatter = buildD3Formatter3(labelFormat);
|
|
1587
1641
|
const candidates = [];
|
|
1588
1642
|
for (const mark of targetMarks) {
|
|
1589
|
-
|
|
1590
|
-
const
|
|
1591
|
-
|
|
1592
|
-
|
|
1643
|
+
let valuePart;
|
|
1644
|
+
const rawNum = valueField != null ? Number(mark.data[valueField]) : NaN;
|
|
1645
|
+
if (formatter && Number.isFinite(rawNum)) {
|
|
1646
|
+
valuePart = formatter(rawNum);
|
|
1647
|
+
} else if (Number.isFinite(rawNum)) {
|
|
1648
|
+
valuePart = formatDotValue(rawNum);
|
|
1649
|
+
} else {
|
|
1650
|
+
const ariaLabel = mark.aria.label;
|
|
1651
|
+
const lastColon = ariaLabel.lastIndexOf(":");
|
|
1652
|
+
valuePart = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
|
|
1653
|
+
if (!valuePart) continue;
|
|
1654
|
+
if (formatter) {
|
|
1655
|
+
const num = Number(valuePart.replace(/[^0-9.-]/g, ""));
|
|
1656
|
+
if (!Number.isNaN(num)) valuePart = formatter(num);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1593
1659
|
if (labelPrefix) valuePart = labelPrefix + valuePart;
|
|
1594
1660
|
const textWidth = estimateTextWidth4(valuePart, LABEL_FONT_SIZE3, LABEL_FONT_WEIGHT3);
|
|
1595
1661
|
const textHeight = LABEL_FONT_SIZE3 * 1.2;
|
|
@@ -1628,7 +1694,15 @@ function computeDotLabels(marks, _chartArea, density = "auto", labelPrefix) {
|
|
|
1628
1694
|
var dotRenderer = (spec, scales, chartArea, strategy, _theme) => {
|
|
1629
1695
|
const marks = computeDotMarks(spec, scales, chartArea, strategy);
|
|
1630
1696
|
const pointMarks = marks.filter((m) => m.type === "point");
|
|
1631
|
-
const
|
|
1697
|
+
const valueField = spec.encoding?.x && "field" in spec.encoding.x ? spec.encoding.x.field : void 0;
|
|
1698
|
+
const labels = computeDotLabels(
|
|
1699
|
+
pointMarks,
|
|
1700
|
+
chartArea,
|
|
1701
|
+
spec.labels.density,
|
|
1702
|
+
spec.labels.prefix,
|
|
1703
|
+
spec.labels.format,
|
|
1704
|
+
valueField
|
|
1705
|
+
);
|
|
1632
1706
|
let labelIdx = 0;
|
|
1633
1707
|
for (const mark of marks) {
|
|
1634
1708
|
if (mark.type === "point" && labelIdx < labels.length) {
|
|
@@ -1639,6 +1713,9 @@ var dotRenderer = (spec, scales, chartArea, strategy, _theme) => {
|
|
|
1639
1713
|
return marks;
|
|
1640
1714
|
};
|
|
1641
1715
|
|
|
1716
|
+
// src/charts/line/index.ts
|
|
1717
|
+
import { getRepresentativeColor as getRepresentativeColor6 } from "@opendata-ai/openchart-core";
|
|
1718
|
+
|
|
1642
1719
|
// src/charts/line/area.ts
|
|
1643
1720
|
import { getRepresentativeColor as getRepresentativeColor4 } from "@opendata-ai/openchart-core";
|
|
1644
1721
|
|
|
@@ -2521,6 +2598,26 @@ function stack_default() {
|
|
|
2521
2598
|
return stack;
|
|
2522
2599
|
}
|
|
2523
2600
|
|
|
2601
|
+
// ../../node_modules/.bun/d3-shape@3.2.0/node_modules/d3-shape/src/offset/expand.js
|
|
2602
|
+
function expand_default(series, order) {
|
|
2603
|
+
if (!((n = series.length) > 0)) return;
|
|
2604
|
+
for (var i, n, j = 0, m = series[0].length, y2; j < m; ++j) {
|
|
2605
|
+
for (y2 = i = 0; i < n; ++i) y2 += series[i][j][1] || 0;
|
|
2606
|
+
if (y2) for (i = 0; i < n; ++i) series[i][j][1] /= y2;
|
|
2607
|
+
}
|
|
2608
|
+
none_default(series, order);
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
// ../../node_modules/.bun/d3-shape@3.2.0/node_modules/d3-shape/src/offset/silhouette.js
|
|
2612
|
+
function silhouette_default(series, order) {
|
|
2613
|
+
if (!((n = series.length) > 0)) return;
|
|
2614
|
+
for (var j = 0, s0 = series[order[0]], n, m = s0.length; j < m; ++j) {
|
|
2615
|
+
for (var i = 0, y2 = 0; i < n; ++i) y2 += series[i][j][1] || 0;
|
|
2616
|
+
s0[j][1] += s0[j][0] = -y2 / 2;
|
|
2617
|
+
}
|
|
2618
|
+
none_default(series, order);
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2524
2621
|
// src/charts/line/curves.ts
|
|
2525
2622
|
var CURVE_MAP = {
|
|
2526
2623
|
linear: linear_default,
|
|
@@ -2565,7 +2662,7 @@ function computeSingleArea(spec, scales, _chartArea) {
|
|
|
2565
2662
|
const marks = [];
|
|
2566
2663
|
for (const [seriesKey, rows] of groups) {
|
|
2567
2664
|
const color2 = getColor(scales, seriesKey);
|
|
2568
|
-
const sortedRows = sortByField(rows, xChannel.field);
|
|
2665
|
+
const sortedRows = xChannel.type === "nominal" || xChannel.type === "ordinal" ? rows : sortByField(rows, xChannel.field);
|
|
2569
2666
|
const validPoints = [];
|
|
2570
2667
|
for (const row of sortedRows) {
|
|
2571
2668
|
const xVal = scaleValue(scales.x.scale, scales.x.type, row[xChannel.field]);
|
|
@@ -2614,7 +2711,7 @@ function computeStackedArea(spec, scales, chartArea) {
|
|
|
2614
2711
|
if (!xChannel || !yChannel || !scales.x || !scales.y || !colorField) {
|
|
2615
2712
|
return computeSingleArea(spec, scales, chartArea);
|
|
2616
2713
|
}
|
|
2617
|
-
const sortedData = sortByField(spec.data, xChannel.field);
|
|
2714
|
+
const sortedData = xChannel.type === "nominal" || xChannel.type === "ordinal" ? spec.data : sortByField(spec.data, xChannel.field);
|
|
2618
2715
|
const seriesKeys = /* @__PURE__ */ new Set();
|
|
2619
2716
|
const xValueSet = /* @__PURE__ */ new Set();
|
|
2620
2717
|
const rowsByXSeries = /* @__PURE__ */ new Map();
|
|
@@ -2648,7 +2745,9 @@ function computeStackedArea(spec, scales, chartArea) {
|
|
|
2648
2745
|
}
|
|
2649
2746
|
return pivot;
|
|
2650
2747
|
});
|
|
2651
|
-
const
|
|
2748
|
+
const stackProp = yChannel.stack;
|
|
2749
|
+
const offsetFn = stackProp === "normalize" ? expand_default : stackProp === "center" ? silhouette_default : none_default;
|
|
2750
|
+
const stackGenerator = stack_default().keys(keys).order(none_default2).offset(offsetFn);
|
|
2652
2751
|
const stackedData = stackGenerator(pivotData);
|
|
2653
2752
|
const yScale = scales.y.scale;
|
|
2654
2753
|
const marks = [];
|
|
@@ -2729,7 +2828,7 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
|
|
|
2729
2828
|
for (const [seriesKey, rows] of groups) {
|
|
2730
2829
|
const color2 = isSequentialColor ? getSequentialColor(scales, _getMidValue(rows, sequentialColorField)) : getColor(scales, seriesKey);
|
|
2731
2830
|
const strokeColor = getRepresentativeColor5(color2);
|
|
2732
|
-
const sortedRows = sortByField(rows, xChannel.field);
|
|
2831
|
+
const sortedRows = xChannel.type === "nominal" || xChannel.type === "ordinal" ? rows : sortByField(rows, xChannel.field);
|
|
2733
2832
|
const pointsWithData = [];
|
|
2734
2833
|
const segments = [];
|
|
2735
2834
|
let currentSegment = [];
|
|
@@ -2917,9 +3016,24 @@ var lineRenderer = (spec, scales, chartArea, strategy, _theme) => {
|
|
|
2917
3016
|
};
|
|
2918
3017
|
var areaRenderer = (spec, scales, chartArea, strategy, _theme) => {
|
|
2919
3018
|
const areas = computeAreaMarks(spec, scales, chartArea);
|
|
2920
|
-
const
|
|
3019
|
+
const encoding = spec.encoding;
|
|
3020
|
+
const hasColor = !!(encoding.color && "field" in encoding.color);
|
|
3021
|
+
const lines = hasColor ? linesFromAreas(areas) : computeLineMarks(spec, scales, chartArea, strategy);
|
|
2921
3022
|
return [...areas, ...lines];
|
|
2922
3023
|
};
|
|
3024
|
+
function linesFromAreas(areas) {
|
|
3025
|
+
return areas.map((a) => ({
|
|
3026
|
+
type: "line",
|
|
3027
|
+
points: a.topPoints,
|
|
3028
|
+
path: a.topPath,
|
|
3029
|
+
stroke: getRepresentativeColor6(a.fill),
|
|
3030
|
+
strokeWidth: a.strokeWidth ?? 1,
|
|
3031
|
+
seriesKey: a.seriesKey,
|
|
3032
|
+
data: a.data,
|
|
3033
|
+
dataPoints: a.dataPoints,
|
|
3034
|
+
aria: { label: `${a.seriesKey ?? "Series"}: line with ${a.topPoints.length} data points` }
|
|
3035
|
+
}));
|
|
3036
|
+
}
|
|
2923
3037
|
|
|
2924
3038
|
// src/charts/pie/compute.ts
|
|
2925
3039
|
import { isConditionalDef, isGradientDef as isGradientDef3 } from "@opendata-ai/openchart-core";
|
|
@@ -3129,26 +3243,158 @@ function computePieLabels(marks, _chartArea, density = "auto", _textFill = "#333
|
|
|
3129
3243
|
};
|
|
3130
3244
|
}
|
|
3131
3245
|
}
|
|
3132
|
-
return resolved;
|
|
3246
|
+
return resolved;
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
// src/charts/pie/index.ts
|
|
3250
|
+
var pieRenderer = (spec, scales, chartArea, strategy, theme) => {
|
|
3251
|
+
const marks = computePieMarks(spec, scales, chartArea, strategy, false);
|
|
3252
|
+
const labels = computePieLabels(marks, chartArea, spec.labels.density, theme.colors.text);
|
|
3253
|
+
for (let i = 0; i < marks.length && i < labels.length; i++) {
|
|
3254
|
+
marks[i].label = labels[i];
|
|
3255
|
+
}
|
|
3256
|
+
return marks;
|
|
3257
|
+
};
|
|
3258
|
+
var donutRenderer = (spec, scales, chartArea, strategy, theme) => {
|
|
3259
|
+
const marks = computePieMarks(spec, scales, chartArea, strategy, true);
|
|
3260
|
+
const labels = computePieLabels(marks, chartArea, spec.labels.density, theme.colors.text);
|
|
3261
|
+
for (let i = 0; i < marks.length && i < labels.length; i++) {
|
|
3262
|
+
marks[i].label = labels[i];
|
|
3263
|
+
}
|
|
3264
|
+
return marks;
|
|
3265
|
+
};
|
|
3266
|
+
|
|
3267
|
+
// src/charts/post-process.ts
|
|
3268
|
+
function computeMarkObstacles(marks, scales) {
|
|
3269
|
+
if (scales.y?.type === "band") {
|
|
3270
|
+
return computeBandRowObstacles(marks, scales);
|
|
3271
|
+
}
|
|
3272
|
+
const obstacles = [];
|
|
3273
|
+
for (const mark of marks) {
|
|
3274
|
+
if (mark.type === "rect") {
|
|
3275
|
+
const rm = mark;
|
|
3276
|
+
obstacles.push({ x: rm.x, y: rm.y, width: rm.width, height: rm.height });
|
|
3277
|
+
} else if (mark.type === "point") {
|
|
3278
|
+
const pm = mark;
|
|
3279
|
+
obstacles.push({
|
|
3280
|
+
x: pm.cx - pm.r,
|
|
3281
|
+
y: pm.cy - pm.r,
|
|
3282
|
+
width: pm.r * 2,
|
|
3283
|
+
height: pm.r * 2
|
|
3284
|
+
});
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
return obstacles;
|
|
3288
|
+
}
|
|
3289
|
+
function computeBandRowObstacles(marks, scales) {
|
|
3290
|
+
const rows = /* @__PURE__ */ new Map();
|
|
3291
|
+
for (const mark of marks) {
|
|
3292
|
+
let cy;
|
|
3293
|
+
let left2;
|
|
3294
|
+
let right2;
|
|
3295
|
+
if (mark.type === "point") {
|
|
3296
|
+
const pm = mark;
|
|
3297
|
+
cy = pm.cy;
|
|
3298
|
+
left2 = pm.cx - pm.r;
|
|
3299
|
+
right2 = pm.cx + pm.r;
|
|
3300
|
+
} else if (mark.type === "rect") {
|
|
3301
|
+
const rm = mark;
|
|
3302
|
+
cy = rm.y + rm.height / 2;
|
|
3303
|
+
left2 = rm.x;
|
|
3304
|
+
right2 = rm.x + rm.width;
|
|
3305
|
+
} else {
|
|
3306
|
+
continue;
|
|
3307
|
+
}
|
|
3308
|
+
const key = Math.round(cy);
|
|
3309
|
+
const existing = rows.get(key);
|
|
3310
|
+
if (existing) {
|
|
3311
|
+
existing.minX = Math.min(existing.minX, left2);
|
|
3312
|
+
existing.maxX = Math.max(existing.maxX, right2);
|
|
3313
|
+
} else {
|
|
3314
|
+
rows.set(key, { minX: left2, maxX: right2, bandY: cy });
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
const bandScale = scales.y.scale;
|
|
3318
|
+
const bandwidth = bandScale.bandwidth?.() ?? 0;
|
|
3319
|
+
if (bandwidth === 0) return [];
|
|
3320
|
+
const obstacles = [];
|
|
3321
|
+
for (const { minX, maxX, bandY } of rows.values()) {
|
|
3322
|
+
obstacles.push({
|
|
3323
|
+
x: minX,
|
|
3324
|
+
y: bandY - bandwidth / 2,
|
|
3325
|
+
width: maxX - minX,
|
|
3326
|
+
height: bandwidth
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
return obstacles;
|
|
3330
|
+
}
|
|
3331
|
+
function resolveRendererKey(markType, encoding, markDef) {
|
|
3332
|
+
if (markType === "bar") {
|
|
3333
|
+
const xType = encoding.x?.type;
|
|
3334
|
+
const yType = encoding.y?.type;
|
|
3335
|
+
const isVertical = (xType === "nominal" || xType === "ordinal" || xType === "temporal") && yType === "quantitative";
|
|
3336
|
+
if (isVertical) {
|
|
3337
|
+
return "bar:vertical";
|
|
3338
|
+
}
|
|
3339
|
+
} else if (markType === "arc") {
|
|
3340
|
+
const innerRadius = markDef.innerRadius;
|
|
3341
|
+
if (innerRadius && innerRadius > 0) {
|
|
3342
|
+
return "arc:donut";
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
return markType;
|
|
3133
3346
|
}
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3347
|
+
function getMarkPrimaryValue(mark) {
|
|
3348
|
+
switch (mark.type) {
|
|
3349
|
+
case "rect":
|
|
3350
|
+
return mark.height;
|
|
3351
|
+
// bar height is the primary value encoding
|
|
3352
|
+
case "point":
|
|
3353
|
+
return mark.cy;
|
|
3354
|
+
// y position for scatter
|
|
3355
|
+
case "arc":
|
|
3356
|
+
return mark.endAngle - mark.startAngle;
|
|
3357
|
+
// arc angle extent
|
|
3358
|
+
case "line":
|
|
3359
|
+
case "area":
|
|
3360
|
+
return 0;
|
|
3361
|
+
// series marks don't have individual values
|
|
3362
|
+
default:
|
|
3363
|
+
return 0;
|
|
3141
3364
|
}
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3365
|
+
}
|
|
3366
|
+
function assignAnimationIndices(marks, animation) {
|
|
3367
|
+
if (!animation?.enabled) return;
|
|
3368
|
+
if (animation.staggerOrder === "value") {
|
|
3369
|
+
const indexed = marks.map((m, i) => ({ mark: m, idx: i }));
|
|
3370
|
+
indexed.sort((a, b) => {
|
|
3371
|
+
const av = getMarkPrimaryValue(a.mark);
|
|
3372
|
+
const bv = getMarkPrimaryValue(b.mark);
|
|
3373
|
+
return av - bv;
|
|
3374
|
+
});
|
|
3375
|
+
for (let i = 0; i < indexed.length; i++) {
|
|
3376
|
+
const m = indexed[i].mark;
|
|
3377
|
+
if (m.type === "rect" && m.stackGroup) continue;
|
|
3378
|
+
m.animationIndex = i;
|
|
3379
|
+
}
|
|
3149
3380
|
}
|
|
3150
|
-
|
|
3151
|
-
|
|
3381
|
+
const groupIndexMap = /* @__PURE__ */ new Map();
|
|
3382
|
+
const groupStackPos = /* @__PURE__ */ new Map();
|
|
3383
|
+
let nextGroupIndex = 0;
|
|
3384
|
+
for (const mark of marks) {
|
|
3385
|
+
if (mark.type === "rect" && mark.stackGroup) {
|
|
3386
|
+
const rect = mark;
|
|
3387
|
+
const group = rect.stackGroup;
|
|
3388
|
+
if (!groupIndexMap.has(group)) {
|
|
3389
|
+
groupIndexMap.set(group, nextGroupIndex++);
|
|
3390
|
+
}
|
|
3391
|
+
rect.animationIndex = groupIndexMap.get(group);
|
|
3392
|
+
const pos = groupStackPos.get(group) ?? 0;
|
|
3393
|
+
rect.stackPos = pos;
|
|
3394
|
+
groupStackPos.set(group, pos + 1);
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3152
3398
|
|
|
3153
3399
|
// src/charts/registry.ts
|
|
3154
3400
|
var renderers = /* @__PURE__ */ new Map();
|
|
@@ -3163,7 +3409,7 @@ function clearRenderers() {
|
|
|
3163
3409
|
}
|
|
3164
3410
|
|
|
3165
3411
|
// src/charts/rule/index.ts
|
|
3166
|
-
import { getRepresentativeColor as
|
|
3412
|
+
import { getRepresentativeColor as getRepresentativeColor7 } from "@opendata-ai/openchart-core";
|
|
3167
3413
|
function computeRuleMarks(spec, scales, chartArea) {
|
|
3168
3414
|
const encoding = spec.encoding;
|
|
3169
3415
|
const xChannel = encoding.x;
|
|
@@ -3206,7 +3452,7 @@ function computeRuleMarks(spec, scales, chartArea) {
|
|
|
3206
3452
|
const y2Val = scaleValue(scales.y.scale, scales.y.type, row[y2Channel.field]);
|
|
3207
3453
|
if (y2Val != null) y2 = y2Val;
|
|
3208
3454
|
}
|
|
3209
|
-
const color2 =
|
|
3455
|
+
const color2 = getRepresentativeColor7(
|
|
3210
3456
|
colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__")
|
|
3211
3457
|
);
|
|
3212
3458
|
const strokeDashEncoding = encoding.strokeDash && "field" in encoding.strokeDash ? encoding.strokeDash : void 0;
|
|
@@ -6015,7 +6261,7 @@ var scatterRenderer = (spec, scales, chartArea, strategy, _theme) => {
|
|
|
6015
6261
|
};
|
|
6016
6262
|
|
|
6017
6263
|
// src/charts/text/index.ts
|
|
6018
|
-
import { getRepresentativeColor as
|
|
6264
|
+
import { getRepresentativeColor as getRepresentativeColor8 } from "@opendata-ai/openchart-core";
|
|
6019
6265
|
function computeTextMarks(spec, scales) {
|
|
6020
6266
|
const encoding = spec.encoding;
|
|
6021
6267
|
const xChannel = encoding.x;
|
|
@@ -6041,7 +6287,7 @@ function computeTextMarks(spec, scales) {
|
|
|
6041
6287
|
}
|
|
6042
6288
|
const text = String(row[textChannel.field] ?? "");
|
|
6043
6289
|
if (!text) continue;
|
|
6044
|
-
const color2 =
|
|
6290
|
+
const color2 = getRepresentativeColor8(
|
|
6045
6291
|
colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__")
|
|
6046
6292
|
);
|
|
6047
6293
|
const fontSize = sizeEncoding ? Math.max(8, Math.min(48, Number(row[sizeEncoding.field]) || 12)) : 12;
|
|
@@ -6068,7 +6314,7 @@ var textRenderer = (spec, scales, _chartArea, _strategy, _theme) => {
|
|
|
6068
6314
|
};
|
|
6069
6315
|
|
|
6070
6316
|
// src/charts/tick/index.ts
|
|
6071
|
-
import { getRepresentativeColor as
|
|
6317
|
+
import { getRepresentativeColor as getRepresentativeColor9 } from "@opendata-ai/openchart-core";
|
|
6072
6318
|
var DEFAULT_TICK_LENGTH = 18;
|
|
6073
6319
|
function computeTickMarks(spec, scales, _chartArea) {
|
|
6074
6320
|
const encoding = spec.encoding;
|
|
@@ -6084,7 +6330,7 @@ function computeTickMarks(spec, scales, _chartArea) {
|
|
|
6084
6330
|
const xVal = scaleValue(scales.x.scale, scales.x.type, row[xChannel.field]);
|
|
6085
6331
|
const yVal = scaleValue(scales.y.scale, scales.y.type, row[yChannel.field]);
|
|
6086
6332
|
if (xVal == null || yVal == null) continue;
|
|
6087
|
-
const color2 =
|
|
6333
|
+
const color2 = getRepresentativeColor9(
|
|
6088
6334
|
colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__")
|
|
6089
6335
|
);
|
|
6090
6336
|
const aria = {
|
|
@@ -6135,7 +6381,7 @@ function normalizeChrome(chrome) {
|
|
|
6135
6381
|
};
|
|
6136
6382
|
}
|
|
6137
6383
|
function inferFieldType(data, field) {
|
|
6138
|
-
const sampleSize = Math.min(
|
|
6384
|
+
const sampleSize = Math.min(50, data.length);
|
|
6139
6385
|
let numericCount = 0;
|
|
6140
6386
|
let dateCount = 0;
|
|
6141
6387
|
let totalNonNull = 0;
|
|
@@ -7515,16 +7761,16 @@ var DEFAULT_COLLISION_PADDING = 5;
|
|
|
7515
7761
|
|
|
7516
7762
|
// src/layout/axes.ts
|
|
7517
7763
|
import {
|
|
7518
|
-
abbreviateNumber as
|
|
7519
|
-
buildD3Formatter as
|
|
7764
|
+
abbreviateNumber as abbreviateNumber6,
|
|
7765
|
+
buildD3Formatter as buildD3Formatter4,
|
|
7520
7766
|
buildTemporalFormatter,
|
|
7521
7767
|
estimateTextWidth as estimateTextWidth7,
|
|
7522
7768
|
formatDate,
|
|
7523
|
-
formatNumber as
|
|
7769
|
+
formatNumber as formatNumber6
|
|
7524
7770
|
} from "@opendata-ai/openchart-core";
|
|
7525
7771
|
var TICK_COUNTS = {
|
|
7526
|
-
full:
|
|
7527
|
-
reduced:
|
|
7772
|
+
full: 10,
|
|
7773
|
+
reduced: 7,
|
|
7528
7774
|
minimal: 3
|
|
7529
7775
|
};
|
|
7530
7776
|
var HEIGHT_MINIMAL_THRESHOLD = 120;
|
|
@@ -7637,11 +7883,11 @@ function formatTickLabel(value2, resolvedScale) {
|
|
|
7637
7883
|
if (NUMERIC_SCALE_TYPES.has(resolvedScale.type)) {
|
|
7638
7884
|
const num = value2;
|
|
7639
7885
|
if (formatStr) {
|
|
7640
|
-
const fmt =
|
|
7886
|
+
const fmt = buildD3Formatter4(formatStr);
|
|
7641
7887
|
if (fmt) return fmt(num);
|
|
7642
7888
|
}
|
|
7643
|
-
if (Math.abs(num) >= 1e3) return
|
|
7644
|
-
return
|
|
7889
|
+
if (Math.abs(num) >= 1e3) return abbreviateNumber6(num);
|
|
7890
|
+
return formatNumber6(num);
|
|
7645
7891
|
}
|
|
7646
7892
|
return String(value2);
|
|
7647
7893
|
}
|
|
@@ -7714,7 +7960,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
7714
7960
|
}));
|
|
7715
7961
|
const shouldThin = scales.x.type !== "band" && !axisConfig?.tickCount && !axisConfig?.values;
|
|
7716
7962
|
const ticks2 = shouldThin ? thinTicksUntilFit(allTicks, fontSize, fontWeight, measureText) : allTicks;
|
|
7717
|
-
let tickAngle = axisConfig?.labelAngle
|
|
7963
|
+
let tickAngle = axisConfig?.labelAngle;
|
|
7718
7964
|
if (tickAngle === void 0 && scales.x.type === "band" && ticks2.length > 1) {
|
|
7719
7965
|
const bandwidth = scales.x.scale.bandwidth();
|
|
7720
7966
|
let maxLabelWidth = 0;
|
|
@@ -7726,7 +7972,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
7726
7972
|
tickAngle = -45;
|
|
7727
7973
|
}
|
|
7728
7974
|
}
|
|
7729
|
-
const axisTitle = axisConfig?.title
|
|
7975
|
+
const axisTitle = axisConfig?.title;
|
|
7730
7976
|
result.x = {
|
|
7731
7977
|
ticks: ticks2,
|
|
7732
7978
|
gridlines: axisConfig?.grid ? gridlines : [],
|
|
@@ -7756,14 +8002,14 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
7756
8002
|
} else {
|
|
7757
8003
|
allTicks = continuousTicks(scales.y, yDensity);
|
|
7758
8004
|
}
|
|
7759
|
-
const
|
|
8005
|
+
const shouldThin = scales.y.type !== "band" && !axisConfig?.tickCount && !axisConfig?.values;
|
|
8006
|
+
const ticks2 = shouldThin ? thinTicksUntilFit(allTicks, fontSize, fontWeight, measureText) : allTicks;
|
|
8007
|
+
const gridlines = ticks2.map((t) => ({
|
|
7760
8008
|
position: t.position,
|
|
7761
8009
|
major: true
|
|
7762
8010
|
}));
|
|
7763
|
-
const
|
|
7764
|
-
const
|
|
7765
|
-
const axisTitle = axisConfig?.title ?? axisConfig?.label;
|
|
7766
|
-
const tickAngle = axisConfig?.labelAngle ?? axisConfig?.tickAngle;
|
|
8011
|
+
const axisTitle = axisConfig?.title;
|
|
8012
|
+
const tickAngle = axisConfig?.labelAngle;
|
|
7767
8013
|
result.y = {
|
|
7768
8014
|
ticks: ticks2,
|
|
7769
8015
|
// Y-axis gridlines are shown by default (standard editorial practice)
|
|
@@ -7825,8 +8071,8 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
7825
8071
|
const isRadial = spec.markType === "arc";
|
|
7826
8072
|
const encoding = spec.encoding;
|
|
7827
8073
|
const xAxis = encoding.x?.axis;
|
|
7828
|
-
const hasXAxisLabel = !!xAxis?.
|
|
7829
|
-
const xTickAngle = xAxis?.
|
|
8074
|
+
const hasXAxisLabel = !!xAxis?.title;
|
|
8075
|
+
const xTickAngle = xAxis?.labelAngle;
|
|
7830
8076
|
let xAxisHeight;
|
|
7831
8077
|
if (isRadial) {
|
|
7832
8078
|
xAxisHeight = 0;
|
|
@@ -8051,6 +8297,12 @@ function uniqueStrings(values) {
|
|
|
8051
8297
|
}
|
|
8052
8298
|
return result;
|
|
8053
8299
|
}
|
|
8300
|
+
function applyCategoricalSort(values, sort) {
|
|
8301
|
+
if (!sort) return values;
|
|
8302
|
+
const sorted = [...values].sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
|
|
8303
|
+
if (sort === "descending") sorted.reverse();
|
|
8304
|
+
return sorted;
|
|
8305
|
+
}
|
|
8054
8306
|
function applyContinuousConfig(scale, channel) {
|
|
8055
8307
|
if (channel.scale?.clamp) {
|
|
8056
8308
|
scale.clamp(true);
|
|
@@ -8064,7 +8316,7 @@ function buildTimeScale(channel, data, rangeStart, rangeEnd) {
|
|
|
8064
8316
|
const values = parseDates(fieldValues(data, channel.field));
|
|
8065
8317
|
const domain = channel.scale?.domain ? [new Date(channel.scale.domain[0]), new Date(channel.scale.domain[1])] : extent(values);
|
|
8066
8318
|
const scale = time().domain(domain).range([rangeStart, rangeEnd]);
|
|
8067
|
-
if (channel.scale?.nice
|
|
8319
|
+
if (!channel.scale?.domain && channel.scale?.nice === true) {
|
|
8068
8320
|
scale.nice();
|
|
8069
8321
|
}
|
|
8070
8322
|
applyContinuousConfig(scale, channel);
|
|
@@ -8074,7 +8326,7 @@ function buildUtcScale(channel, data, rangeStart, rangeEnd) {
|
|
|
8074
8326
|
const values = parseDates(fieldValues(data, channel.field));
|
|
8075
8327
|
const domain = channel.scale?.domain ? [new Date(channel.scale.domain[0]), new Date(channel.scale.domain[1])] : extent(values);
|
|
8076
8328
|
const scale = utcTime().domain(domain).range([rangeStart, rangeEnd]);
|
|
8077
|
-
if (channel.scale?.nice
|
|
8329
|
+
if (!channel.scale?.domain && channel.scale?.nice === true) {
|
|
8078
8330
|
scale.nice();
|
|
8079
8331
|
}
|
|
8080
8332
|
applyContinuousConfig(scale, channel);
|
|
@@ -8097,7 +8349,7 @@ function buildLinearScale(channel, data, rangeStart, rangeEnd) {
|
|
|
8097
8349
|
}
|
|
8098
8350
|
}
|
|
8099
8351
|
const scale = linear2().domain([domainMin, domainMax]).range([rangeStart, rangeEnd]);
|
|
8100
|
-
if (channel.scale?.nice !== false) {
|
|
8352
|
+
if (!channel.scale?.domain && channel.scale?.nice !== false) {
|
|
8101
8353
|
scale.nice();
|
|
8102
8354
|
}
|
|
8103
8355
|
applyContinuousConfig(scale, channel);
|
|
@@ -8111,7 +8363,7 @@ function buildLogScale(channel, data, rangeStart, rangeEnd) {
|
|
|
8111
8363
|
if (channel.scale?.base !== void 0) {
|
|
8112
8364
|
scale.base(channel.scale.base);
|
|
8113
8365
|
}
|
|
8114
|
-
if (channel.scale?.nice !== false) {
|
|
8366
|
+
if (!channel.scale?.domain && channel.scale?.nice !== false) {
|
|
8115
8367
|
scale.nice();
|
|
8116
8368
|
}
|
|
8117
8369
|
applyContinuousConfig(scale, channel);
|
|
@@ -8135,7 +8387,7 @@ function buildPowScale(channel, data, rangeStart, rangeEnd) {
|
|
|
8135
8387
|
if (channel.scale?.exponent !== void 0) {
|
|
8136
8388
|
scale.exponent(channel.scale.exponent);
|
|
8137
8389
|
}
|
|
8138
|
-
if (channel.scale?.nice !== false) {
|
|
8390
|
+
if (!channel.scale?.domain && channel.scale?.nice !== false) {
|
|
8139
8391
|
scale.nice();
|
|
8140
8392
|
}
|
|
8141
8393
|
applyContinuousConfig(scale, channel);
|
|
@@ -8156,7 +8408,7 @@ function buildSqrtScale(channel, data, rangeStart, rangeEnd) {
|
|
|
8156
8408
|
}
|
|
8157
8409
|
}
|
|
8158
8410
|
const scale = sqrt2().domain([domainMin, domainMax]).range([rangeStart, rangeEnd]);
|
|
8159
|
-
if (channel.scale?.nice !== false) {
|
|
8411
|
+
if (!channel.scale?.domain && channel.scale?.nice !== false) {
|
|
8160
8412
|
scale.nice();
|
|
8161
8413
|
}
|
|
8162
8414
|
applyContinuousConfig(scale, channel);
|
|
@@ -8180,7 +8432,7 @@ function buildSymlogScale(channel, data, rangeStart, rangeEnd) {
|
|
|
8180
8432
|
if (channel.scale?.constant !== void 0) {
|
|
8181
8433
|
scale.constant(channel.scale.constant);
|
|
8182
8434
|
}
|
|
8183
|
-
if (channel.scale?.nice !== false) {
|
|
8435
|
+
if (!channel.scale?.domain && channel.scale?.nice !== false) {
|
|
8184
8436
|
scale.nice();
|
|
8185
8437
|
}
|
|
8186
8438
|
applyContinuousConfig(scale, channel);
|
|
@@ -8212,7 +8464,7 @@ function evenRange(start, end, count) {
|
|
|
8212
8464
|
return Array.from({ length: count }, (_, i) => start + step * i);
|
|
8213
8465
|
}
|
|
8214
8466
|
function buildBandScale(channel, data, rangeStart, rangeEnd) {
|
|
8215
|
-
const values = channel.scale?.domain ? channel.scale.domain : uniqueStrings(fieldValues(data, channel.field));
|
|
8467
|
+
const values = channel.scale?.domain ? channel.scale.domain : applyCategoricalSort(uniqueStrings(fieldValues(data, channel.field)), channel.sort);
|
|
8216
8468
|
const padding = channel.scale?.padding ?? 0.35;
|
|
8217
8469
|
const scale = band().domain(values).range([rangeStart, rangeEnd]).padding(padding);
|
|
8218
8470
|
if (channel.scale?.paddingInner !== void 0) {
|
|
@@ -8228,7 +8480,7 @@ function buildBandScale(channel, data, rangeStart, rangeEnd) {
|
|
|
8228
8480
|
return { scale, type: "band", channel };
|
|
8229
8481
|
}
|
|
8230
8482
|
function buildPointScale(channel, data, rangeStart, rangeEnd) {
|
|
8231
|
-
const values = channel.scale?.domain ? channel.scale.domain : uniqueStrings(fieldValues(data, channel.field));
|
|
8483
|
+
const values = channel.scale?.domain ? channel.scale.domain : applyCategoricalSort(uniqueStrings(fieldValues(data, channel.field)), channel.sort);
|
|
8232
8484
|
const padding = channel.scale?.padding ?? 0.5;
|
|
8233
8485
|
const scale = point4().domain(values).range([rangeStart, rangeEnd]).padding(padding);
|
|
8234
8486
|
if (channel.scale?.reverse) {
|
|
@@ -8238,7 +8490,8 @@ function buildPointScale(channel, data, rangeStart, rangeEnd) {
|
|
|
8238
8490
|
return { scale, type: "point", channel };
|
|
8239
8491
|
}
|
|
8240
8492
|
function buildOrdinalColorScale(channel, data, palette) {
|
|
8241
|
-
const
|
|
8493
|
+
const explicitDomain = channel.scale?.domain;
|
|
8494
|
+
const values = explicitDomain ? explicitDomain.map(String) : applyCategoricalSort(uniqueStrings(fieldValues(data, channel.field)), channel.sort);
|
|
8242
8495
|
const scale = ordinal().domain(values).range(palette);
|
|
8243
8496
|
return { scale, type: "ordinal", channel };
|
|
8244
8497
|
}
|
|
@@ -8316,25 +8569,49 @@ function computeScales(spec, chartArea, data) {
|
|
|
8316
8569
|
}
|
|
8317
8570
|
if (encoding.x) {
|
|
8318
8571
|
let xData = data;
|
|
8572
|
+
let xChannel = encoding.x;
|
|
8319
8573
|
const xStackDisabled = encoding.x.stack === null || encoding.x.stack === false;
|
|
8320
8574
|
if (spec.markType === "bar" && encoding.color && encoding.x.type === "quantitative" && !xStackDisabled) {
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
if (
|
|
8324
|
-
const
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
const
|
|
8328
|
-
|
|
8329
|
-
|
|
8575
|
+
if (encoding.x.stack === "normalize") {
|
|
8576
|
+
xChannel = { ...encoding.x, scale: { ...encoding.x.scale, domain: [0, 1], nice: false } };
|
|
8577
|
+
} else if (encoding.x.stack === "center") {
|
|
8578
|
+
const yField = encoding.y?.field;
|
|
8579
|
+
const xField = encoding.x.field;
|
|
8580
|
+
if (yField) {
|
|
8581
|
+
const sums = /* @__PURE__ */ new Map();
|
|
8582
|
+
for (const row of data) {
|
|
8583
|
+
const cat = String(row[yField] ?? "");
|
|
8584
|
+
const val = Number(row[xField] ?? 0);
|
|
8585
|
+
if (Number.isFinite(val) && val > 0) {
|
|
8586
|
+
sums.set(cat, (sums.get(cat) ?? 0) + val);
|
|
8587
|
+
}
|
|
8588
|
+
}
|
|
8589
|
+
const maxSum = Math.max(...sums.values(), 0);
|
|
8590
|
+
const half = maxSum / 2;
|
|
8591
|
+
xChannel = {
|
|
8592
|
+
...encoding.x,
|
|
8593
|
+
scale: { ...encoding.x.scale, domain: [-half, half], zero: true }
|
|
8594
|
+
};
|
|
8595
|
+
}
|
|
8596
|
+
} else {
|
|
8597
|
+
const yField = encoding.y?.field;
|
|
8598
|
+
const xField = encoding.x.field;
|
|
8599
|
+
if (yField) {
|
|
8600
|
+
const sums = /* @__PURE__ */ new Map();
|
|
8601
|
+
for (const row of data) {
|
|
8602
|
+
const cat = String(row[yField] ?? "");
|
|
8603
|
+
const val = Number(row[xField] ?? 0);
|
|
8604
|
+
if (Number.isFinite(val) && val > 0) {
|
|
8605
|
+
sums.set(cat, (sums.get(cat) ?? 0) + val);
|
|
8606
|
+
}
|
|
8330
8607
|
}
|
|
8608
|
+
const maxSum = Math.max(...sums.values(), 0);
|
|
8609
|
+
xData = [...data, { [xField]: maxSum }];
|
|
8331
8610
|
}
|
|
8332
|
-
const maxSum = Math.max(...sums.values(), 0);
|
|
8333
|
-
xData = [...data, { [xField]: maxSum }];
|
|
8334
8611
|
}
|
|
8335
8612
|
}
|
|
8336
8613
|
result.x = buildPositionalScale(
|
|
8337
|
-
|
|
8614
|
+
xChannel,
|
|
8338
8615
|
xData,
|
|
8339
8616
|
chartArea.x,
|
|
8340
8617
|
chartArea.x + chartArea.width,
|
|
@@ -8344,26 +8621,50 @@ function computeScales(spec, chartArea, data) {
|
|
|
8344
8621
|
}
|
|
8345
8622
|
if (encoding.y) {
|
|
8346
8623
|
let yData = data;
|
|
8624
|
+
let yChannel = encoding.y;
|
|
8347
8625
|
const isVerticalBar = spec.markType === "bar" && (encoding.x?.type === "nominal" || encoding.x?.type === "ordinal") && encoding.y.type === "quantitative";
|
|
8348
8626
|
const yStackDisabled = encoding.y.stack === null || encoding.y.stack === false;
|
|
8349
8627
|
if ((isVerticalBar || spec.markType === "area") && encoding.color && encoding.y.type === "quantitative" && !yStackDisabled) {
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
if (
|
|
8353
|
-
const
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
const
|
|
8357
|
-
|
|
8358
|
-
|
|
8628
|
+
if (encoding.y.stack === "normalize") {
|
|
8629
|
+
yChannel = { ...encoding.y, scale: { ...encoding.y.scale, domain: [0, 1], nice: false } };
|
|
8630
|
+
} else if (encoding.y.stack === "center") {
|
|
8631
|
+
const xField = encoding.x?.field;
|
|
8632
|
+
const yField = encoding.y.field;
|
|
8633
|
+
if (xField) {
|
|
8634
|
+
const sums = /* @__PURE__ */ new Map();
|
|
8635
|
+
for (const row of data) {
|
|
8636
|
+
const cat = String(row[xField] ?? "");
|
|
8637
|
+
const val = Number(row[yField] ?? 0);
|
|
8638
|
+
if (Number.isFinite(val) && val > 0) {
|
|
8639
|
+
sums.set(cat, (sums.get(cat) ?? 0) + val);
|
|
8640
|
+
}
|
|
8641
|
+
}
|
|
8642
|
+
const maxSum = Math.max(...sums.values(), 0);
|
|
8643
|
+
const half = maxSum / 2;
|
|
8644
|
+
yChannel = {
|
|
8645
|
+
...encoding.y,
|
|
8646
|
+
scale: { ...encoding.y.scale, domain: [-half, half], zero: true }
|
|
8647
|
+
};
|
|
8648
|
+
}
|
|
8649
|
+
} else {
|
|
8650
|
+
const xField = encoding.x?.field;
|
|
8651
|
+
const yField = encoding.y.field;
|
|
8652
|
+
if (xField) {
|
|
8653
|
+
const sums = /* @__PURE__ */ new Map();
|
|
8654
|
+
for (const row of data) {
|
|
8655
|
+
const cat = String(row[xField] ?? "");
|
|
8656
|
+
const val = Number(row[yField] ?? 0);
|
|
8657
|
+
if (Number.isFinite(val) && val > 0) {
|
|
8658
|
+
sums.set(cat, (sums.get(cat) ?? 0) + val);
|
|
8659
|
+
}
|
|
8359
8660
|
}
|
|
8661
|
+
const maxSum = Math.max(...sums.values(), 0);
|
|
8662
|
+
yData = [...data, { [yField]: maxSum }];
|
|
8360
8663
|
}
|
|
8361
|
-
const maxSum = Math.max(...sums.values(), 0);
|
|
8362
|
-
yData = [...data, { [yField]: maxSum }];
|
|
8363
8664
|
}
|
|
8364
8665
|
}
|
|
8365
8666
|
result.y = buildPositionalScale(
|
|
8366
|
-
|
|
8667
|
+
yChannel,
|
|
8367
8668
|
yData,
|
|
8368
8669
|
chartArea.y + chartArea.height,
|
|
8369
8670
|
chartArea.y,
|
|
@@ -8517,7 +8818,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
|
|
|
8517
8818
|
1,
|
|
8518
8819
|
Math.floor((maxLegendHeight - LEGEND_PADDING * 2) / (entryHeight + 4))
|
|
8519
8820
|
);
|
|
8520
|
-
const maxEntries = spec.legend?.symbolLimit != null ? Math.
|
|
8821
|
+
const maxEntries = spec.legend?.symbolLimit != null ? Math.max(1, spec.legend.symbolLimit) : maxFromSpace;
|
|
8521
8822
|
if (entries.length > maxEntries) {
|
|
8522
8823
|
entries = truncateEntries(entries, maxEntries);
|
|
8523
8824
|
}
|
|
@@ -8592,10 +8893,10 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
|
|
|
8592
8893
|
// src/sankey/compile-sankey.ts
|
|
8593
8894
|
import {
|
|
8594
8895
|
adaptTheme as adaptTheme2,
|
|
8595
|
-
buildD3Formatter as
|
|
8896
|
+
buildD3Formatter as buildD3Formatter5,
|
|
8596
8897
|
computeChrome as computeChrome3,
|
|
8597
8898
|
estimateTextWidth as estimateTextWidth10,
|
|
8598
|
-
formatNumber as
|
|
8899
|
+
formatNumber as formatNumber7,
|
|
8599
8900
|
resolveTheme as resolveTheme2
|
|
8600
8901
|
} from "@opendata-ai/openchart-core";
|
|
8601
8902
|
|
|
@@ -9449,10 +9750,10 @@ function buildSankeyLegend(nodeColorMap, colorField, data, sourceField, targetFi
|
|
|
9449
9750
|
}
|
|
9450
9751
|
function formatFlowValue(value2, valueFormat) {
|
|
9451
9752
|
if (valueFormat) {
|
|
9452
|
-
const fmt =
|
|
9753
|
+
const fmt = buildD3Formatter5(valueFormat);
|
|
9453
9754
|
if (fmt) return fmt(value2);
|
|
9454
9755
|
}
|
|
9455
|
-
return
|
|
9756
|
+
return formatNumber7(value2);
|
|
9456
9757
|
}
|
|
9457
9758
|
function buildTooltipDescriptors(nodes, links, valueFormat) {
|
|
9458
9759
|
const descriptors = /* @__PURE__ */ new Map();
|
|
@@ -9629,7 +9930,7 @@ function computeCategoryColors(data, column, theme, darkMode) {
|
|
|
9629
9930
|
}
|
|
9630
9931
|
|
|
9631
9932
|
// src/tables/format-cells.ts
|
|
9632
|
-
import { buildD3Formatter as
|
|
9933
|
+
import { buildD3Formatter as buildD3Formatter6, formatDate as formatDate2, formatNumber as formatNumber8 } from "@opendata-ai/openchart-core";
|
|
9633
9934
|
function isNumericValue(value2) {
|
|
9634
9935
|
if (typeof value2 === "number") return Number.isFinite(value2);
|
|
9635
9936
|
return false;
|
|
@@ -9648,7 +9949,7 @@ function formatCell(value2, column) {
|
|
|
9648
9949
|
};
|
|
9649
9950
|
}
|
|
9650
9951
|
if (column.format && isNumericValue(value2)) {
|
|
9651
|
-
const formatter =
|
|
9952
|
+
const formatter = buildD3Formatter6(column.format);
|
|
9652
9953
|
if (formatter) {
|
|
9653
9954
|
return {
|
|
9654
9955
|
value: value2,
|
|
@@ -9660,7 +9961,7 @@ function formatCell(value2, column) {
|
|
|
9660
9961
|
if (isNumericValue(value2)) {
|
|
9661
9962
|
return {
|
|
9662
9963
|
value: value2,
|
|
9663
|
-
formattedValue:
|
|
9964
|
+
formattedValue: formatNumber8(value2),
|
|
9664
9965
|
style
|
|
9665
9966
|
};
|
|
9666
9967
|
}
|
|
@@ -9680,13 +9981,13 @@ function formatCell(value2, column) {
|
|
|
9680
9981
|
function formatValueForSearch(value2, column) {
|
|
9681
9982
|
if (value2 == null) return "";
|
|
9682
9983
|
if (column.format && isNumericValue(value2)) {
|
|
9683
|
-
const formatter =
|
|
9984
|
+
const formatter = buildD3Formatter6(column.format);
|
|
9684
9985
|
if (formatter) {
|
|
9685
9986
|
return formatter(value2);
|
|
9686
9987
|
}
|
|
9687
9988
|
}
|
|
9688
9989
|
if (isNumericValue(value2)) {
|
|
9689
|
-
return
|
|
9990
|
+
return formatNumber8(value2);
|
|
9690
9991
|
}
|
|
9691
9992
|
return String(value2);
|
|
9692
9993
|
}
|
|
@@ -10161,8 +10462,8 @@ function compileTableLayout(spec, options, theme) {
|
|
|
10161
10462
|
import {
|
|
10162
10463
|
buildTemporalFormatter as buildTemporalFormatter2,
|
|
10163
10464
|
formatDate as formatDate3,
|
|
10164
|
-
formatNumber as
|
|
10165
|
-
getRepresentativeColor as
|
|
10465
|
+
formatNumber as formatNumber9,
|
|
10466
|
+
getRepresentativeColor as getRepresentativeColor10
|
|
10166
10467
|
} from "@opendata-ai/openchart-core";
|
|
10167
10468
|
function formatValue(value2, fieldType, format2) {
|
|
10168
10469
|
if (value2 == null) return "";
|
|
@@ -10176,17 +10477,23 @@ function formatValue(value2, fieldType, format2) {
|
|
|
10176
10477
|
try {
|
|
10177
10478
|
return format(format2)(value2);
|
|
10178
10479
|
} catch {
|
|
10179
|
-
return
|
|
10480
|
+
return formatNumber9(value2);
|
|
10180
10481
|
}
|
|
10181
10482
|
}
|
|
10182
|
-
return
|
|
10483
|
+
return formatNumber9(value2);
|
|
10183
10484
|
}
|
|
10184
10485
|
return String(value2);
|
|
10185
10486
|
}
|
|
10487
|
+
function resolveLabel(ch) {
|
|
10488
|
+
return ch.title ?? ch.axis?.title ?? ch.field;
|
|
10489
|
+
}
|
|
10490
|
+
function resolveFormat(ch) {
|
|
10491
|
+
return ch.format ?? ch.axis?.format;
|
|
10492
|
+
}
|
|
10186
10493
|
function buildExplicitTooltipFields(row, channels) {
|
|
10187
10494
|
return channels.map((ch) => ({
|
|
10188
|
-
label: ch
|
|
10189
|
-
value: formatValue(row[ch.field], ch.type, ch
|
|
10495
|
+
label: resolveLabel(ch),
|
|
10496
|
+
value: formatValue(row[ch.field], ch.type, resolveFormat(ch))
|
|
10190
10497
|
}));
|
|
10191
10498
|
}
|
|
10192
10499
|
function buildFields(row, encoding, color2) {
|
|
@@ -10195,23 +10502,38 @@ function buildFields(row, encoding, color2) {
|
|
|
10195
10502
|
return buildExplicitTooltipFields(row, channels);
|
|
10196
10503
|
}
|
|
10197
10504
|
const fields = [];
|
|
10198
|
-
if (encoding.
|
|
10505
|
+
if (encoding.color && "field" in encoding.color) {
|
|
10199
10506
|
fields.push({
|
|
10200
|
-
label: encoding.
|
|
10201
|
-
value: formatValue(
|
|
10507
|
+
label: resolveLabel(encoding.color),
|
|
10508
|
+
value: formatValue(
|
|
10509
|
+
row[encoding.color.field],
|
|
10510
|
+
encoding.color.type,
|
|
10511
|
+
resolveFormat(encoding.color)
|
|
10512
|
+
),
|
|
10202
10513
|
color: color2
|
|
10203
10514
|
});
|
|
10204
10515
|
}
|
|
10516
|
+
if (encoding.y) {
|
|
10517
|
+
fields.push({
|
|
10518
|
+
label: resolveLabel(encoding.y),
|
|
10519
|
+
value: formatValue(row[encoding.y.field], encoding.y.type, resolveFormat(encoding.y)),
|
|
10520
|
+
color: encoding.color ? void 0 : color2
|
|
10521
|
+
});
|
|
10522
|
+
}
|
|
10205
10523
|
if (encoding.x) {
|
|
10206
10524
|
fields.push({
|
|
10207
|
-
label: encoding.x
|
|
10208
|
-
value: formatValue(row[encoding.x.field], encoding.x.type, encoding.x
|
|
10525
|
+
label: resolveLabel(encoding.x),
|
|
10526
|
+
value: formatValue(row[encoding.x.field], encoding.x.type, resolveFormat(encoding.x))
|
|
10209
10527
|
});
|
|
10210
10528
|
}
|
|
10211
10529
|
if (encoding.size && "field" in encoding.size) {
|
|
10212
10530
|
fields.push({
|
|
10213
|
-
label: encoding.size
|
|
10214
|
-
value: formatValue(
|
|
10531
|
+
label: resolveLabel(encoding.size),
|
|
10532
|
+
value: formatValue(
|
|
10533
|
+
row[encoding.size.field],
|
|
10534
|
+
encoding.size.type,
|
|
10535
|
+
resolveFormat(encoding.size)
|
|
10536
|
+
)
|
|
10215
10537
|
});
|
|
10216
10538
|
}
|
|
10217
10539
|
return fields;
|
|
@@ -10229,6 +10551,16 @@ function getTooltipTitle(row, encoding) {
|
|
|
10229
10551
|
if (encoding.y?.type === "nominal" || encoding.y?.type === "ordinal") {
|
|
10230
10552
|
return String(row[encoding.y.field] ?? "");
|
|
10231
10553
|
}
|
|
10554
|
+
if (encoding.x?.type === "quantitative" && encoding.y?.type === "quantitative") {
|
|
10555
|
+
const encodedFields = new Set(
|
|
10556
|
+
[encoding.x, encoding.y, encoding.color, encoding.size, encoding.detail].filter((ch) => !!ch && "field" in ch).map((ch) => ch.field)
|
|
10557
|
+
);
|
|
10558
|
+
for (const [key, value2] of Object.entries(row)) {
|
|
10559
|
+
if (!encodedFields.has(key) && typeof value2 === "string") {
|
|
10560
|
+
return value2;
|
|
10561
|
+
}
|
|
10562
|
+
}
|
|
10563
|
+
}
|
|
10232
10564
|
if (encoding.color && "field" in encoding.color) {
|
|
10233
10565
|
return String(row[encoding.color.field] ?? "");
|
|
10234
10566
|
}
|
|
@@ -10247,12 +10579,12 @@ function tooltipsForLine(mark, encoding, _markIndex) {
|
|
|
10247
10579
|
}
|
|
10248
10580
|
function tooltipsForPoint(mark, encoding, markIndex) {
|
|
10249
10581
|
const title = getTooltipTitle(mark.data, encoding);
|
|
10250
|
-
const fields = buildFields(mark.data, encoding,
|
|
10582
|
+
const fields = buildFields(mark.data, encoding, getRepresentativeColor10(mark.fill));
|
|
10251
10583
|
return [[`point-${markIndex}`, { title, fields }]];
|
|
10252
10584
|
}
|
|
10253
10585
|
function tooltipsForRect(mark, encoding, markIndex) {
|
|
10254
10586
|
const title = getTooltipTitle(mark.data, encoding);
|
|
10255
|
-
const fields = buildFields(mark.data, encoding,
|
|
10587
|
+
const fields = buildFields(mark.data, encoding, getRepresentativeColor10(mark.fill));
|
|
10256
10588
|
return [[`rect-${markIndex}`, { title, fields }]];
|
|
10257
10589
|
}
|
|
10258
10590
|
function tooltipsForArc(mark, encoding, markIndex) {
|
|
@@ -10264,15 +10596,15 @@ function tooltipsForArc(mark, encoding, markIndex) {
|
|
|
10264
10596
|
if (encoding.y) {
|
|
10265
10597
|
fields.push({
|
|
10266
10598
|
label: categoryName,
|
|
10267
|
-
value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y
|
|
10268
|
-
color:
|
|
10599
|
+
value: formatValue(row[encoding.y.field], encoding.y.type, resolveFormat(encoding.y)),
|
|
10600
|
+
color: getRepresentativeColor10(mark.fill)
|
|
10269
10601
|
});
|
|
10270
10602
|
}
|
|
10271
10603
|
} else if (encoding.y) {
|
|
10272
10604
|
fields.push({
|
|
10273
|
-
label: encoding.y
|
|
10274
|
-
value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y
|
|
10275
|
-
color:
|
|
10605
|
+
label: resolveLabel(encoding.y),
|
|
10606
|
+
value: formatValue(row[encoding.y.field], encoding.y.type, resolveFormat(encoding.y)),
|
|
10607
|
+
color: getRepresentativeColor10(mark.fill)
|
|
10276
10608
|
});
|
|
10277
10609
|
}
|
|
10278
10610
|
const title = colorEnc ? String(row[colorEnc.field] ?? "") : void 0;
|
|
@@ -10283,7 +10615,7 @@ function tooltipsForArea(mark, encoding, _markIndex) {
|
|
|
10283
10615
|
for (const dp of mark.dataPoints) {
|
|
10284
10616
|
dp.tooltip = {
|
|
10285
10617
|
title: getTooltipTitle(dp.datum, encoding),
|
|
10286
|
-
fields: buildFields(dp.datum, encoding,
|
|
10618
|
+
fields: buildFields(dp.datum, encoding, getRepresentativeColor10(mark.fill))
|
|
10287
10619
|
};
|
|
10288
10620
|
}
|
|
10289
10621
|
}
|
|
@@ -10319,6 +10651,93 @@ function computeTooltipDescriptors(spec, marks) {
|
|
|
10319
10651
|
return descriptors;
|
|
10320
10652
|
}
|
|
10321
10653
|
|
|
10654
|
+
// src/transforms/aggregate.ts
|
|
10655
|
+
function computeAggregate(op, values) {
|
|
10656
|
+
if (values.length === 0) return 0;
|
|
10657
|
+
switch (op) {
|
|
10658
|
+
case "count":
|
|
10659
|
+
return values.length;
|
|
10660
|
+
case "sum":
|
|
10661
|
+
return values.reduce((a, b) => a + b, 0);
|
|
10662
|
+
case "mean": {
|
|
10663
|
+
const sum2 = values.reduce((a, b) => a + b, 0);
|
|
10664
|
+
return sum2 / values.length;
|
|
10665
|
+
}
|
|
10666
|
+
case "median": {
|
|
10667
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
10668
|
+
const mid = Math.floor(sorted.length / 2);
|
|
10669
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
10670
|
+
}
|
|
10671
|
+
case "min":
|
|
10672
|
+
return Math.min(...values);
|
|
10673
|
+
case "max":
|
|
10674
|
+
return Math.max(...values);
|
|
10675
|
+
case "variance": {
|
|
10676
|
+
if (values.length < 2) return 0;
|
|
10677
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
10678
|
+
return values.reduce((a, v) => a + (v - mean) ** 2, 0) / values.length;
|
|
10679
|
+
}
|
|
10680
|
+
case "stdev": {
|
|
10681
|
+
if (values.length < 2) return 0;
|
|
10682
|
+
const m = values.reduce((a, b) => a + b, 0) / values.length;
|
|
10683
|
+
return Math.sqrt(values.reduce((a, v) => a + (v - m) ** 2, 0) / values.length);
|
|
10684
|
+
}
|
|
10685
|
+
case "q1": {
|
|
10686
|
+
const s = [...values].sort((a, b) => a - b);
|
|
10687
|
+
const i = (s.length - 1) * 0.25;
|
|
10688
|
+
const lo = Math.floor(i);
|
|
10689
|
+
const frac = i - lo;
|
|
10690
|
+
return s[lo] + frac * ((s[lo + 1] ?? s[lo]) - s[lo]);
|
|
10691
|
+
}
|
|
10692
|
+
case "q3": {
|
|
10693
|
+
const s = [...values].sort((a, b) => a - b);
|
|
10694
|
+
const i = (s.length - 1) * 0.75;
|
|
10695
|
+
const lo = Math.floor(i);
|
|
10696
|
+
const frac = i - lo;
|
|
10697
|
+
return s[lo] + frac * ((s[lo + 1] ?? s[lo]) - s[lo]);
|
|
10698
|
+
}
|
|
10699
|
+
default:
|
|
10700
|
+
return 0;
|
|
10701
|
+
}
|
|
10702
|
+
}
|
|
10703
|
+
function groupKey(row, groupby) {
|
|
10704
|
+
return groupby.map((f) => String(row[f] ?? "")).join("\0");
|
|
10705
|
+
}
|
|
10706
|
+
function runAggregate(data, transform) {
|
|
10707
|
+
const { aggregate, groupby } = transform;
|
|
10708
|
+
const groups = /* @__PURE__ */ new Map();
|
|
10709
|
+
for (const row of data) {
|
|
10710
|
+
const key = groupKey(row, groupby);
|
|
10711
|
+
const existing = groups.get(key);
|
|
10712
|
+
if (existing) {
|
|
10713
|
+
existing.push(row);
|
|
10714
|
+
} else {
|
|
10715
|
+
groups.set(key, [row]);
|
|
10716
|
+
}
|
|
10717
|
+
}
|
|
10718
|
+
const result = [];
|
|
10719
|
+
for (const rows of groups.values()) {
|
|
10720
|
+
const outRow = {};
|
|
10721
|
+
for (const field of groupby) {
|
|
10722
|
+
outRow[field] = rows[0][field];
|
|
10723
|
+
}
|
|
10724
|
+
for (const agg of aggregate) {
|
|
10725
|
+
if (agg.op === "distinct") {
|
|
10726
|
+
outRow[agg.as] = new Set(rows.map((r) => r[agg.field])).size;
|
|
10727
|
+
continue;
|
|
10728
|
+
}
|
|
10729
|
+
const values = rows.map((r) => {
|
|
10730
|
+
if (agg.op === "count") return 1;
|
|
10731
|
+
const v = Number(r[agg.field]);
|
|
10732
|
+
return Number.isFinite(v) ? v : NaN;
|
|
10733
|
+
}).filter((v) => !Number.isNaN(v));
|
|
10734
|
+
outRow[agg.as] = computeAggregate(agg.op, values);
|
|
10735
|
+
}
|
|
10736
|
+
result.push(outRow);
|
|
10737
|
+
}
|
|
10738
|
+
return result;
|
|
10739
|
+
}
|
|
10740
|
+
|
|
10322
10741
|
// src/transforms/bin.ts
|
|
10323
10742
|
function computeStep(extent2, maxbins, nice2) {
|
|
10324
10743
|
const span = extent2[1] - extent2[0];
|
|
@@ -10415,6 +10834,30 @@ function runFilter(data, predicate) {
|
|
|
10415
10834
|
return data.filter((datum) => evaluatePredicate(datum, predicate));
|
|
10416
10835
|
}
|
|
10417
10836
|
|
|
10837
|
+
// src/transforms/fold.ts
|
|
10838
|
+
function runFold(data, transform) {
|
|
10839
|
+
const { fold } = transform;
|
|
10840
|
+
const [keyAs, valueAs] = transform.as ?? ["key", "value"];
|
|
10841
|
+
const foldSet = new Set(fold);
|
|
10842
|
+
const result = [];
|
|
10843
|
+
for (const row of data) {
|
|
10844
|
+
const base = {};
|
|
10845
|
+
for (const [k, v] of Object.entries(row)) {
|
|
10846
|
+
if (!foldSet.has(k)) {
|
|
10847
|
+
base[k] = v;
|
|
10848
|
+
}
|
|
10849
|
+
}
|
|
10850
|
+
for (const field of fold) {
|
|
10851
|
+
result.push({
|
|
10852
|
+
...base,
|
|
10853
|
+
[keyAs]: field,
|
|
10854
|
+
[valueAs]: row[field]
|
|
10855
|
+
});
|
|
10856
|
+
}
|
|
10857
|
+
}
|
|
10858
|
+
return result;
|
|
10859
|
+
}
|
|
10860
|
+
|
|
10418
10861
|
// src/transforms/timeunit.ts
|
|
10419
10862
|
function extractTimeUnit(date2, unit2) {
|
|
10420
10863
|
switch (unit2) {
|
|
@@ -10492,6 +10935,10 @@ function runTransforms(data, transforms) {
|
|
|
10492
10935
|
result = runCalculate(result, transform);
|
|
10493
10936
|
} else if ("timeUnit" in transform) {
|
|
10494
10937
|
result = runTimeUnit(result, transform);
|
|
10938
|
+
} else if ("aggregate" in transform) {
|
|
10939
|
+
result = runAggregate(result, transform);
|
|
10940
|
+
} else if ("fold" in transform) {
|
|
10941
|
+
result = runFold(result, transform);
|
|
10495
10942
|
}
|
|
10496
10943
|
}
|
|
10497
10944
|
return result;
|
|
@@ -10524,71 +10971,55 @@ var builtinRenderers = {
|
|
|
10524
10971
|
for (const [type, renderer] of Object.entries(builtinRenderers)) {
|
|
10525
10972
|
registerChartRenderer(type, renderer);
|
|
10526
10973
|
}
|
|
10527
|
-
function
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
const
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
const
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
}
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
const
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
right2 = rm.x + rm.width;
|
|
10564
|
-
} else {
|
|
10565
|
-
continue;
|
|
10566
|
-
}
|
|
10567
|
-
const key = Math.round(cy);
|
|
10568
|
-
const existing = rows.get(key);
|
|
10569
|
-
if (existing) {
|
|
10570
|
-
existing.minX = Math.min(existing.minX, left2);
|
|
10571
|
-
existing.maxX = Math.max(existing.maxX, right2);
|
|
10572
|
-
} else {
|
|
10573
|
-
rows.set(key, { minX: left2, maxX: right2, bandY: cy });
|
|
10974
|
+
function expandEncodingSugar(spec) {
|
|
10975
|
+
const encoding = spec.encoding;
|
|
10976
|
+
if (!encoding) return spec;
|
|
10977
|
+
const generatedTransforms = [];
|
|
10978
|
+
const updatedEncoding = { ...encoding };
|
|
10979
|
+
let changed = false;
|
|
10980
|
+
for (const channel of Object.keys(encoding)) {
|
|
10981
|
+
const ch = encoding[channel];
|
|
10982
|
+
if (!ch || !ch.field) continue;
|
|
10983
|
+
if (ch.bin != null && ch.bin !== false) {
|
|
10984
|
+
const field = ch.field;
|
|
10985
|
+
const outputField = `bin_${field}`;
|
|
10986
|
+
const binTransform = {
|
|
10987
|
+
bin: ch.bin === true ? true : ch.bin,
|
|
10988
|
+
field,
|
|
10989
|
+
as: outputField
|
|
10990
|
+
};
|
|
10991
|
+
generatedTransforms.push(binTransform);
|
|
10992
|
+
const { bin: _bin, ...rest } = ch;
|
|
10993
|
+
updatedEncoding[channel] = { ...rest, field: outputField };
|
|
10994
|
+
changed = true;
|
|
10995
|
+
}
|
|
10996
|
+
const current = updatedEncoding[channel] ?? ch;
|
|
10997
|
+
if (current.timeUnit) {
|
|
10998
|
+
const field = current.field;
|
|
10999
|
+
const unit2 = current.timeUnit;
|
|
11000
|
+
const outputField = `${unit2}_${field}`;
|
|
11001
|
+
const timeUnitTransform = {
|
|
11002
|
+
timeUnit: unit2,
|
|
11003
|
+
field,
|
|
11004
|
+
as: outputField
|
|
11005
|
+
};
|
|
11006
|
+
generatedTransforms.push(timeUnitTransform);
|
|
11007
|
+
const { timeUnit: _tu, ...rest } = current;
|
|
11008
|
+
updatedEncoding[channel] = { ...rest, field: outputField };
|
|
11009
|
+
changed = true;
|
|
10574
11010
|
}
|
|
10575
11011
|
}
|
|
10576
|
-
|
|
10577
|
-
const
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
y: bandY - bandwidth / 2,
|
|
10584
|
-
width: maxX - minX,
|
|
10585
|
-
height: bandwidth
|
|
10586
|
-
});
|
|
10587
|
-
}
|
|
10588
|
-
return obstacles;
|
|
11012
|
+
if (!changed) return spec;
|
|
11013
|
+
const existingTransforms = spec.transform ?? [];
|
|
11014
|
+
return {
|
|
11015
|
+
...spec,
|
|
11016
|
+
encoding: updatedEncoding,
|
|
11017
|
+
transform: [...generatedTransforms, ...existingTransforms]
|
|
11018
|
+
};
|
|
10589
11019
|
}
|
|
10590
11020
|
function compileChart(spec, options) {
|
|
10591
|
-
const
|
|
11021
|
+
const expandedSpec = spec && typeof spec === "object" && !Array.isArray(spec) ? expandEncodingSugar(spec) : spec;
|
|
11022
|
+
const { spec: normalized } = compile(expandedSpec);
|
|
10592
11023
|
if ("type" in normalized && normalized.type === "table") {
|
|
10593
11024
|
throw new Error("compileChart received a table spec. Use compileTable instead.");
|
|
10594
11025
|
}
|
|
@@ -10599,16 +11030,16 @@ function compileChart(spec, options) {
|
|
|
10599
11030
|
throw new Error("compileChart received a sankey spec. Use compileSankey instead.");
|
|
10600
11031
|
}
|
|
10601
11032
|
let chartSpec = normalized;
|
|
10602
|
-
const rawWatermark =
|
|
11033
|
+
const rawWatermark = expandedSpec.watermark;
|
|
10603
11034
|
const watermark = rawWatermark !== void 0 ? chartSpec.watermark : options.watermark ?? true;
|
|
10604
|
-
const rawTransforms =
|
|
11035
|
+
const rawTransforms = expandedSpec.transform;
|
|
10605
11036
|
if (rawTransforms && rawTransforms.length > 0) {
|
|
10606
11037
|
chartSpec = { ...chartSpec, data: runTransforms(chartSpec.data, rawTransforms) };
|
|
10607
11038
|
}
|
|
10608
11039
|
const breakpoint = getBreakpoint(options.width);
|
|
10609
11040
|
const heightClass = getHeightClass(options.height);
|
|
10610
11041
|
const strategy = getLayoutStrategy(breakpoint, heightClass);
|
|
10611
|
-
const rawSpec =
|
|
11042
|
+
const rawSpec = expandedSpec;
|
|
10612
11043
|
const overrides = rawSpec.overrides;
|
|
10613
11044
|
if (overrides?.[breakpoint]) {
|
|
10614
11045
|
const bp = overrides[breakpoint];
|
|
@@ -10719,20 +11150,11 @@ function compileChart(spec, options) {
|
|
|
10719
11150
|
if (!isRadial) {
|
|
10720
11151
|
computeGridlines(axes, chartArea);
|
|
10721
11152
|
}
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
if (isVertical) {
|
|
10728
|
-
rendererKey = "bar:vertical";
|
|
10729
|
-
}
|
|
10730
|
-
} else if (rendererKey === "arc") {
|
|
10731
|
-
const innerRadius = renderSpec.markDef.innerRadius;
|
|
10732
|
-
if (innerRadius && innerRadius > 0) {
|
|
10733
|
-
rendererKey = "arc:donut";
|
|
10734
|
-
}
|
|
10735
|
-
}
|
|
11153
|
+
const rendererKey = resolveRendererKey(
|
|
11154
|
+
renderSpec.markType,
|
|
11155
|
+
renderSpec.encoding,
|
|
11156
|
+
renderSpec.markDef
|
|
11157
|
+
);
|
|
10736
11158
|
const renderer = getChartRenderer(rendererKey);
|
|
10737
11159
|
const marks = renderer ? renderer(renderSpec, scales, chartArea, strategy, theme) : [];
|
|
10738
11160
|
const obstacles = [];
|
|
@@ -10780,37 +11202,7 @@ function compileChart(spec, options) {
|
|
|
10780
11202
|
},
|
|
10781
11203
|
chartSpec.data
|
|
10782
11204
|
);
|
|
10783
|
-
|
|
10784
|
-
const indexed = marks.map((m, i) => ({ mark: m, idx: i }));
|
|
10785
|
-
indexed.sort((a, b) => {
|
|
10786
|
-
const av = getMarkPrimaryValue(a.mark);
|
|
10787
|
-
const bv = getMarkPrimaryValue(b.mark);
|
|
10788
|
-
return av - bv;
|
|
10789
|
-
});
|
|
10790
|
-
for (let i = 0; i < indexed.length; i++) {
|
|
10791
|
-
const m = indexed[i].mark;
|
|
10792
|
-
if (m.type === "rect" && m.stackGroup) continue;
|
|
10793
|
-
m.animationIndex = i;
|
|
10794
|
-
}
|
|
10795
|
-
}
|
|
10796
|
-
if (resolvedAnimation?.enabled) {
|
|
10797
|
-
const groupIndexMap = /* @__PURE__ */ new Map();
|
|
10798
|
-
const groupStackPos = /* @__PURE__ */ new Map();
|
|
10799
|
-
let nextGroupIndex = 0;
|
|
10800
|
-
for (const mark of marks) {
|
|
10801
|
-
if (mark.type === "rect" && mark.stackGroup) {
|
|
10802
|
-
const rect = mark;
|
|
10803
|
-
const group = rect.stackGroup;
|
|
10804
|
-
if (!groupIndexMap.has(group)) {
|
|
10805
|
-
groupIndexMap.set(group, nextGroupIndex++);
|
|
10806
|
-
}
|
|
10807
|
-
rect.animationIndex = groupIndexMap.get(group);
|
|
10808
|
-
const pos = groupStackPos.get(group) ?? 0;
|
|
10809
|
-
rect.stackPos = pos;
|
|
10810
|
-
groupStackPos.set(group, pos + 1);
|
|
10811
|
-
}
|
|
10812
|
-
}
|
|
10813
|
-
}
|
|
11205
|
+
assignAnimationIndices(marks, resolvedAnimation);
|
|
10814
11206
|
return {
|
|
10815
11207
|
area: chartArea,
|
|
10816
11208
|
chrome: dims.chrome,
|
|
@@ -10834,28 +11226,10 @@ function compileChart(spec, options) {
|
|
|
10834
11226
|
height: options.height
|
|
10835
11227
|
},
|
|
10836
11228
|
animation: resolvedAnimation,
|
|
10837
|
-
watermark
|
|
11229
|
+
watermark,
|
|
11230
|
+
measureText: options.measureText
|
|
10838
11231
|
};
|
|
10839
11232
|
}
|
|
10840
|
-
function getMarkPrimaryValue(mark) {
|
|
10841
|
-
switch (mark.type) {
|
|
10842
|
-
case "rect":
|
|
10843
|
-
return mark.height;
|
|
10844
|
-
// bar height is the primary value encoding
|
|
10845
|
-
case "point":
|
|
10846
|
-
return mark.cy;
|
|
10847
|
-
// y position for scatter
|
|
10848
|
-
case "arc":
|
|
10849
|
-
return mark.endAngle - mark.startAngle;
|
|
10850
|
-
// arc angle extent
|
|
10851
|
-
case "line":
|
|
10852
|
-
case "area":
|
|
10853
|
-
return 0;
|
|
10854
|
-
// series marks don't have individual values
|
|
10855
|
-
default:
|
|
10856
|
-
return 0;
|
|
10857
|
-
}
|
|
10858
|
-
}
|
|
10859
11233
|
function compileLayer(spec, options) {
|
|
10860
11234
|
const leaves = flattenLayers(spec);
|
|
10861
11235
|
if (leaves.length === 0) {
|