@opendata-ai/openchart-engine 2.10.0 → 2.12.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 +182 -48
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/axes.test.ts +179 -2
- package/src/annotations/__tests__/compute.test.ts +173 -4
- package/src/annotations/compute.ts +158 -41
- package/src/charts/column/__tests__/labels.test.ts +104 -0
- package/src/charts/dot/__tests__/labels.test.ts +98 -0
- package/src/charts/pie/__tests__/labels.test.ts +132 -0
- package/src/compile.ts +58 -10
- package/src/layout/axes.ts +114 -15
- package/src/legend/compute.ts +5 -4
- package/src/tooltips/compute.ts +5 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/compile.ts
|
|
2
2
|
import {
|
|
3
3
|
adaptTheme as adaptTheme2,
|
|
4
|
+
BRAND_RESERVE_WIDTH as BRAND_RESERVE_WIDTH2,
|
|
5
|
+
computeLabelBounds,
|
|
4
6
|
generateAltText,
|
|
5
7
|
generateDataTable,
|
|
6
8
|
getBreakpoint,
|
|
@@ -10,7 +12,7 @@ import {
|
|
|
10
12
|
} from "@opendata-ai/openchart-core";
|
|
11
13
|
|
|
12
14
|
// src/annotations/compute.ts
|
|
13
|
-
import { estimateTextWidth } from "@opendata-ai/openchart-core";
|
|
15
|
+
import { detectCollision, estimateTextWidth } from "@opendata-ai/openchart-core";
|
|
14
16
|
var DEFAULT_ANNOTATION_FONT_SIZE = 12;
|
|
15
17
|
var DEFAULT_ANNOTATION_FONT_WEIGHT = 400;
|
|
16
18
|
var DEFAULT_LINE_HEIGHT = 1.3;
|
|
@@ -325,41 +327,34 @@ function estimateLabelBounds(label) {
|
|
|
325
327
|
const fontWeight = label.style.fontWeight ?? DEFAULT_ANNOTATION_FONT_WEIGHT;
|
|
326
328
|
return computeTextBounds(label.x, label.y, label.text, fontSize, fontWeight);
|
|
327
329
|
}
|
|
328
|
-
function rectsOverlap(a, b) {
|
|
329
|
-
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
|
|
330
|
-
}
|
|
331
330
|
var NUDGE_PADDING = 6;
|
|
331
|
+
function generateNudgeCandidates(selfBounds, obstacles, padding) {
|
|
332
|
+
const candidates = [];
|
|
333
|
+
for (const obs of obstacles) {
|
|
334
|
+
const belowDy = obs.y + obs.height + padding - selfBounds.y;
|
|
335
|
+
candidates.push({ dx: 0, dy: belowDy, distance: Math.abs(belowDy) });
|
|
336
|
+
const aboveDy = obs.y - padding - (selfBounds.y + selfBounds.height);
|
|
337
|
+
candidates.push({ dx: 0, dy: aboveDy, distance: Math.abs(aboveDy) });
|
|
338
|
+
const leftDx = obs.x - padding - (selfBounds.x + selfBounds.width);
|
|
339
|
+
candidates.push({ dx: leftDx, dy: 0, distance: Math.abs(leftDx) });
|
|
340
|
+
const rightDx = obs.x + obs.width + padding - selfBounds.x;
|
|
341
|
+
candidates.push({ dx: rightDx, dy: 0, distance: Math.abs(rightDx) });
|
|
342
|
+
}
|
|
343
|
+
candidates.sort((a, b) => a.distance - b.distance);
|
|
344
|
+
return candidates;
|
|
345
|
+
}
|
|
332
346
|
function nudgeAnnotationFromObstacles(annotation, originalAnnotation, scales, chartArea, obstacles) {
|
|
333
347
|
if (annotation.type !== "text" || !annotation.label) return false;
|
|
334
348
|
const labelBounds = estimateLabelBounds(annotation.label);
|
|
335
349
|
const collidingObs = obstacles.filter(
|
|
336
|
-
(obs) => obs.width > 0 && obs.height > 0 &&
|
|
350
|
+
(obs) => obs.width > 0 && obs.height > 0 && detectCollision(labelBounds, obs)
|
|
337
351
|
);
|
|
338
352
|
if (collidingObs.length === 0) return false;
|
|
339
353
|
const px = resolvePosition(originalAnnotation.x, scales.x);
|
|
340
354
|
const py = resolvePosition(originalAnnotation.y, scales.y);
|
|
341
355
|
if (px === null || py === null) return false;
|
|
342
|
-
const candidates =
|
|
356
|
+
const candidates = generateNudgeCandidates(labelBounds, collidingObs, NUDGE_PADDING);
|
|
343
357
|
const fontSize = labelBounds.height / Math.max(1, annotation.label.text.split("\n").length);
|
|
344
|
-
for (const obs of collidingObs) {
|
|
345
|
-
const currentLabelTop = labelBounds.y;
|
|
346
|
-
const targetLabelTop = obs.y + obs.height + NUDGE_PADDING;
|
|
347
|
-
const belowDy = targetLabelTop - currentLabelTop;
|
|
348
|
-
candidates.push({ dx: 0, dy: belowDy, distance: Math.abs(belowDy) });
|
|
349
|
-
const currentLabelBottom = labelBounds.y + labelBounds.height;
|
|
350
|
-
const targetLabelBottom = obs.y - NUDGE_PADDING;
|
|
351
|
-
const aboveDy = targetLabelBottom - currentLabelBottom;
|
|
352
|
-
candidates.push({ dx: 0, dy: aboveDy, distance: Math.abs(aboveDy) });
|
|
353
|
-
const currentLabelRight = labelBounds.x + labelBounds.width;
|
|
354
|
-
const targetLabelRight = obs.x - NUDGE_PADDING;
|
|
355
|
-
const leftDx = targetLabelRight - currentLabelRight;
|
|
356
|
-
candidates.push({ dx: leftDx, dy: 0, distance: Math.abs(leftDx) });
|
|
357
|
-
const currentLabelLeft = labelBounds.x;
|
|
358
|
-
const targetLabelLeft = obs.x + obs.width + NUDGE_PADDING;
|
|
359
|
-
const rightDx = targetLabelLeft - currentLabelLeft;
|
|
360
|
-
candidates.push({ dx: rightDx, dy: 0, distance: Math.abs(rightDx) });
|
|
361
|
-
}
|
|
362
|
-
candidates.sort((a, b) => a.distance - b.distance);
|
|
363
358
|
for (const { dx, dy } of candidates) {
|
|
364
359
|
const newLabelX = annotation.label.x + dx;
|
|
365
360
|
const newLabelY = annotation.label.y + dy;
|
|
@@ -388,12 +383,12 @@ function nudgeAnnotationFromObstacles(annotation, originalAnnotation, scales, ch
|
|
|
388
383
|
};
|
|
389
384
|
const candidateBounds = estimateLabelBounds(candidateLabel);
|
|
390
385
|
const stillCollides = obstacles.some(
|
|
391
|
-
(obs) => obs.width > 0 && obs.height > 0 &&
|
|
386
|
+
(obs) => obs.width > 0 && obs.height > 0 && detectCollision(candidateBounds, obs)
|
|
392
387
|
);
|
|
393
388
|
if (stillCollides) continue;
|
|
394
389
|
const labelCenterX = candidateBounds.x + candidateBounds.width / 2;
|
|
395
390
|
const labelCenterY = candidateBounds.y + candidateBounds.height / 2;
|
|
396
|
-
const inBounds = labelCenterX >= chartArea.x && labelCenterX <= chartArea.x + chartArea.width + 100 && labelCenterY >= chartArea.y - fontSize && labelCenterY <= chartArea.y + chartArea.height + fontSize;
|
|
391
|
+
const inBounds = labelCenterX >= chartArea.x && labelCenterX <= chartArea.x + chartArea.width + 100 && labelCenterY >= chartArea.y - fontSize && labelCenterY <= chartArea.y + chartArea.height + fontSize * 3;
|
|
397
392
|
if (inBounds) {
|
|
398
393
|
if (candidateLabel.connector && dx === 0 && dy !== 0) {
|
|
399
394
|
candidateLabel.connector = {
|
|
@@ -407,6 +402,74 @@ function nudgeAnnotationFromObstacles(annotation, originalAnnotation, scales, ch
|
|
|
407
402
|
}
|
|
408
403
|
return false;
|
|
409
404
|
}
|
|
405
|
+
function resolveAnnotationCollisions(annotations, originalSpecs, scales, chartArea) {
|
|
406
|
+
const placedBounds = [];
|
|
407
|
+
for (let i = 0; i < annotations.length; i++) {
|
|
408
|
+
const annotation = annotations[i];
|
|
409
|
+
if (annotation.type !== "text" || !annotation.label) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const bounds = estimateLabelBounds(annotation.label);
|
|
413
|
+
const collidingBounds = placedBounds.filter(
|
|
414
|
+
(pb) => pb.width > 0 && pb.height > 0 && detectCollision(bounds, pb)
|
|
415
|
+
);
|
|
416
|
+
if (collidingBounds.length > 0) {
|
|
417
|
+
const originalSpec = originalSpecs[i];
|
|
418
|
+
if (originalSpec?.type === "text") {
|
|
419
|
+
const px = resolvePosition(originalSpec.x, scales.x);
|
|
420
|
+
const py = resolvePosition(originalSpec.y, scales.y);
|
|
421
|
+
if (px !== null && py !== null) {
|
|
422
|
+
const candidates = generateNudgeCandidates(bounds, collidingBounds, NUDGE_PADDING);
|
|
423
|
+
const fontSize = bounds.height / Math.max(1, annotation.label.text.split("\n").length);
|
|
424
|
+
for (const { dx, dy } of candidates) {
|
|
425
|
+
const newLabelX = annotation.label.x + dx;
|
|
426
|
+
const newLabelY = annotation.label.y + dy;
|
|
427
|
+
const candidateLabel = {
|
|
428
|
+
...annotation.label,
|
|
429
|
+
x: newLabelX,
|
|
430
|
+
y: newLabelY
|
|
431
|
+
};
|
|
432
|
+
const candidateBounds = estimateLabelBounds(candidateLabel);
|
|
433
|
+
const stillCollides = placedBounds.some(
|
|
434
|
+
(pb) => pb.width > 0 && pb.height > 0 && detectCollision(candidateBounds, pb)
|
|
435
|
+
);
|
|
436
|
+
if (stillCollides) continue;
|
|
437
|
+
const labelCenterX = candidateBounds.x + candidateBounds.width / 2;
|
|
438
|
+
const labelCenterY = candidateBounds.y + candidateBounds.height / 2;
|
|
439
|
+
const inBounds = labelCenterX >= chartArea.x && labelCenterX <= chartArea.x + chartArea.width + 100 && labelCenterY >= chartArea.y - fontSize && labelCenterY <= chartArea.y + chartArea.height + fontSize;
|
|
440
|
+
if (inBounds) {
|
|
441
|
+
let newConnector = annotation.label.connector;
|
|
442
|
+
if (newConnector) {
|
|
443
|
+
const annFontSize = annotation.label.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
|
|
444
|
+
const annFontWeight = annotation.label.style.fontWeight ?? DEFAULT_ANNOTATION_FONT_WEIGHT;
|
|
445
|
+
const connStyle = newConnector.style === "curve" ? "curve" : "straight";
|
|
446
|
+
const newFrom = computeConnectorOrigin(
|
|
447
|
+
newLabelX,
|
|
448
|
+
newLabelY,
|
|
449
|
+
annotation.label.text,
|
|
450
|
+
annFontSize,
|
|
451
|
+
annFontWeight,
|
|
452
|
+
px,
|
|
453
|
+
py,
|
|
454
|
+
connStyle
|
|
455
|
+
);
|
|
456
|
+
newConnector = { ...newConnector, from: newFrom };
|
|
457
|
+
}
|
|
458
|
+
annotation.label = {
|
|
459
|
+
...annotation.label,
|
|
460
|
+
x: newLabelX,
|
|
461
|
+
y: newLabelY,
|
|
462
|
+
connector: newConnector
|
|
463
|
+
};
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
placedBounds.push(estimateLabelBounds(annotation.label));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
410
473
|
function computeAnnotations(spec, scales, chartArea, strategy, isDark = false, obstacles = []) {
|
|
411
474
|
if (strategy.annotationPosition === "tooltip-only") {
|
|
412
475
|
return [];
|
|
@@ -432,6 +495,7 @@ function computeAnnotations(spec, scales, chartArea, strategy, isDark = false, o
|
|
|
432
495
|
annotations.push(resolved);
|
|
433
496
|
}
|
|
434
497
|
}
|
|
498
|
+
resolveAnnotationCollisions(annotations, spec.annotations, scales, chartArea);
|
|
435
499
|
annotations.sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));
|
|
436
500
|
return annotations;
|
|
437
501
|
}
|
|
@@ -5993,6 +6057,8 @@ var HEIGHT_MINIMAL_THRESHOLD = 120;
|
|
|
5993
6057
|
var HEIGHT_REDUCED_THRESHOLD = 200;
|
|
5994
6058
|
var WIDTH_MINIMAL_THRESHOLD = 150;
|
|
5995
6059
|
var WIDTH_REDUCED_THRESHOLD = 300;
|
|
6060
|
+
var MIN_TICK_GAP_FACTOR = 1;
|
|
6061
|
+
var MIN_TICK_COUNT = 2;
|
|
5996
6062
|
var DENSITY_ORDER = ["full", "reduced", "minimal"];
|
|
5997
6063
|
function effectiveDensity(baseDensity, axisLength, minimalThreshold, reducedThreshold) {
|
|
5998
6064
|
let density = baseDensity;
|
|
@@ -6005,17 +6071,49 @@ function effectiveDensity(baseDensity, axisLength, minimalThreshold, reducedThre
|
|
|
6005
6071
|
}
|
|
6006
6072
|
return density;
|
|
6007
6073
|
}
|
|
6008
|
-
function
|
|
6074
|
+
function measureLabel(text, fontSize, fontWeight, measureText) {
|
|
6075
|
+
return measureText ? measureText(text, fontSize, fontWeight).width : estimateTextWidth7(text, fontSize, fontWeight);
|
|
6076
|
+
}
|
|
6077
|
+
function ticksOverlap(ticks2, fontSize, fontWeight, measureText) {
|
|
6078
|
+
if (ticks2.length < 2) return false;
|
|
6079
|
+
const minGap = fontSize * MIN_TICK_GAP_FACTOR;
|
|
6080
|
+
for (let i = 0; i < ticks2.length - 1; i++) {
|
|
6081
|
+
const aWidth = measureLabel(ticks2[i].label, fontSize, fontWeight, measureText);
|
|
6082
|
+
const bWidth = measureLabel(ticks2[i + 1].label, fontSize, fontWeight, measureText);
|
|
6083
|
+
const aRight = ticks2[i].position + aWidth / 2;
|
|
6084
|
+
const bLeft = ticks2[i + 1].position - bWidth / 2;
|
|
6085
|
+
if (aRight + minGap > bLeft) return true;
|
|
6086
|
+
}
|
|
6087
|
+
return false;
|
|
6088
|
+
}
|
|
6089
|
+
function thinTicksUntilFit(ticks2, fontSize, fontWeight, measureText) {
|
|
6090
|
+
if (!ticksOverlap(ticks2, fontSize, fontWeight, measureText)) return ticks2;
|
|
6091
|
+
let current = ticks2;
|
|
6092
|
+
while (current.length > MIN_TICK_COUNT) {
|
|
6093
|
+
const thinned = [current[0]];
|
|
6094
|
+
for (let i = 2; i < current.length - 1; i += 2) {
|
|
6095
|
+
thinned.push(current[i]);
|
|
6096
|
+
}
|
|
6097
|
+
if (current.length > 1) thinned.push(current[current.length - 1]);
|
|
6098
|
+
current = thinned;
|
|
6099
|
+
if (!ticksOverlap(current, fontSize, fontWeight, measureText)) break;
|
|
6100
|
+
}
|
|
6101
|
+
return current;
|
|
6102
|
+
}
|
|
6103
|
+
function continuousTicks(resolvedScale, density, fontSize, fontWeight, measureText) {
|
|
6009
6104
|
const scale = resolvedScale.scale;
|
|
6010
|
-
const
|
|
6011
|
-
const
|
|
6012
|
-
|
|
6105
|
+
const explicitCount = resolvedScale.channel.axis?.tickCount;
|
|
6106
|
+
const count = explicitCount ?? TICK_COUNTS[density];
|
|
6107
|
+
const rawTicks = scale.ticks(count);
|
|
6108
|
+
const ticks2 = rawTicks.map((value) => ({
|
|
6013
6109
|
value,
|
|
6014
6110
|
position: scale(value),
|
|
6015
6111
|
label: formatTickLabel(value, resolvedScale)
|
|
6016
6112
|
}));
|
|
6113
|
+
if (explicitCount) return ticks2;
|
|
6114
|
+
return thinTicksUntilFit(ticks2, fontSize, fontWeight, measureText);
|
|
6017
6115
|
}
|
|
6018
|
-
function categoricalTicks(resolvedScale, density) {
|
|
6116
|
+
function categoricalTicks(resolvedScale, density, fontSize, fontWeight, measureText) {
|
|
6019
6117
|
const scale = resolvedScale.scale;
|
|
6020
6118
|
const domain = scale.domain();
|
|
6021
6119
|
const explicitTickCount = resolvedScale.channel.axis?.tickCount;
|
|
@@ -6025,7 +6123,7 @@ function categoricalTicks(resolvedScale, density) {
|
|
|
6025
6123
|
const step = Math.ceil(domain.length / maxTicks);
|
|
6026
6124
|
selectedValues = domain.filter((_, i) => i % step === 0);
|
|
6027
6125
|
}
|
|
6028
|
-
|
|
6126
|
+
const ticks2 = selectedValues.map((value) => {
|
|
6029
6127
|
const bandScale = resolvedScale.type === "band" ? scale : null;
|
|
6030
6128
|
const pos = bandScale ? (bandScale(value) ?? 0) + bandScale.bandwidth() / 2 : scale(value) ?? 0;
|
|
6031
6129
|
return {
|
|
@@ -6034,6 +6132,10 @@ function categoricalTicks(resolvedScale, density) {
|
|
|
6034
6132
|
label: value
|
|
6035
6133
|
};
|
|
6036
6134
|
});
|
|
6135
|
+
if (resolvedScale.type !== "band" && !explicitTickCount) {
|
|
6136
|
+
return thinTicksUntilFit(ticks2, fontSize, fontWeight, measureText);
|
|
6137
|
+
}
|
|
6138
|
+
return ticks2;
|
|
6037
6139
|
}
|
|
6038
6140
|
function formatTickLabel(value, resolvedScale) {
|
|
6039
6141
|
const formatStr = resolvedScale.channel.axis?.format;
|
|
@@ -6052,7 +6154,7 @@ function formatTickLabel(value, resolvedScale) {
|
|
|
6052
6154
|
}
|
|
6053
6155
|
return String(value);
|
|
6054
6156
|
}
|
|
6055
|
-
function computeAxes(scales, chartArea, strategy, theme) {
|
|
6157
|
+
function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
6056
6158
|
const result = {};
|
|
6057
6159
|
const baseDensity = strategy.axisLabelDensity;
|
|
6058
6160
|
const yDensity = effectiveDensity(
|
|
@@ -6082,8 +6184,10 @@ function computeAxes(scales, chartArea, strategy, theme) {
|
|
|
6082
6184
|
fill: theme.colors.text,
|
|
6083
6185
|
lineHeight: 1.3
|
|
6084
6186
|
};
|
|
6187
|
+
const { fontSize } = tickLabelStyle;
|
|
6188
|
+
const { fontWeight } = tickLabelStyle;
|
|
6085
6189
|
if (scales.x) {
|
|
6086
|
-
const ticks2 = scales.x.type === "band" || scales.x.type === "point" || scales.x.type === "ordinal" ? categoricalTicks(scales.x, xDensity) : continuousTicks(scales.x, xDensity);
|
|
6190
|
+
const ticks2 = scales.x.type === "band" || scales.x.type === "point" || scales.x.type === "ordinal" ? categoricalTicks(scales.x, xDensity, fontSize, fontWeight, measureText) : continuousTicks(scales.x, xDensity, fontSize, fontWeight, measureText);
|
|
6087
6191
|
const gridlines = ticks2.map((t) => ({
|
|
6088
6192
|
position: t.position,
|
|
6089
6193
|
major: true
|
|
@@ -6093,11 +6197,7 @@ function computeAxes(scales, chartArea, strategy, theme) {
|
|
|
6093
6197
|
const bandwidth = scales.x.scale.bandwidth();
|
|
6094
6198
|
let maxLabelWidth = 0;
|
|
6095
6199
|
for (const t of ticks2) {
|
|
6096
|
-
const w =
|
|
6097
|
-
t.label,
|
|
6098
|
-
theme.fonts.sizes.axisTick,
|
|
6099
|
-
theme.fonts.weights.normal
|
|
6100
|
-
);
|
|
6200
|
+
const w = measureLabel(t.label, fontSize, fontWeight, measureText);
|
|
6101
6201
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
6102
6202
|
}
|
|
6103
6203
|
if (maxLabelWidth > bandwidth * 0.85) {
|
|
@@ -6116,7 +6216,7 @@ function computeAxes(scales, chartArea, strategy, theme) {
|
|
|
6116
6216
|
};
|
|
6117
6217
|
}
|
|
6118
6218
|
if (scales.y) {
|
|
6119
|
-
const ticks2 = scales.y.type === "band" || scales.y.type === "point" || scales.y.type === "ordinal" ? categoricalTicks(scales.y, yDensity) : continuousTicks(scales.y, yDensity);
|
|
6219
|
+
const ticks2 = scales.y.type === "band" || scales.y.type === "point" || scales.y.type === "ordinal" ? categoricalTicks(scales.y, yDensity, fontSize, fontWeight, measureText) : continuousTicks(scales.y, yDensity, fontSize, fontWeight, measureText);
|
|
6120
6220
|
const gridlines = ticks2.map((t) => ({
|
|
6121
6221
|
position: t.position,
|
|
6122
6222
|
major: true
|
|
@@ -6543,7 +6643,7 @@ function computeScales(spec, chartArea, data) {
|
|
|
6543
6643
|
}
|
|
6544
6644
|
|
|
6545
6645
|
// src/legend/compute.ts
|
|
6546
|
-
import { estimateTextWidth as estimateTextWidth9 } from "@opendata-ai/openchart-core";
|
|
6646
|
+
import { BRAND_RESERVE_WIDTH, estimateTextWidth as estimateTextWidth9 } from "@opendata-ai/openchart-core";
|
|
6547
6647
|
var SWATCH_SIZE2 = 12;
|
|
6548
6648
|
var SWATCH_GAP2 = 6;
|
|
6549
6649
|
var ENTRY_GAP2 = 16;
|
|
@@ -6685,7 +6785,7 @@ function computeLegend(spec, strategy, theme, chartArea) {
|
|
|
6685
6785
|
entryGap: 4
|
|
6686
6786
|
};
|
|
6687
6787
|
}
|
|
6688
|
-
const availableWidth = chartArea.width - LEGEND_PADDING * 2;
|
|
6788
|
+
const availableWidth = chartArea.width - LEGEND_PADDING * 2 - BRAND_RESERVE_WIDTH;
|
|
6689
6789
|
const maxFit = entriesThatFit(entries, availableWidth, TOP_LEGEND_MAX_ROWS, labelStyle);
|
|
6690
6790
|
if (maxFit < entries.length) {
|
|
6691
6791
|
entries = truncateEntries(entries, maxFit);
|
|
@@ -6716,7 +6816,7 @@ function computeLegend(spec, strategy, theme, chartArea) {
|
|
|
6716
6816
|
bounds: {
|
|
6717
6817
|
x: chartArea.x + offsetDx,
|
|
6718
6818
|
y: (resolvedPosition === "bottom" ? chartArea.y + chartArea.height - legendHeight : chartArea.y) + offsetDy,
|
|
6719
|
-
width: Math.min(totalWidth,
|
|
6819
|
+
width: Math.min(totalWidth, availableWidth),
|
|
6720
6820
|
height: legendHeight
|
|
6721
6821
|
},
|
|
6722
6822
|
labelStyle,
|
|
@@ -7401,6 +7501,9 @@ function buildFields(row, encoding, color2) {
|
|
|
7401
7501
|
return fields;
|
|
7402
7502
|
}
|
|
7403
7503
|
function getTooltipTitle(row, encoding) {
|
|
7504
|
+
if (encoding.detail) {
|
|
7505
|
+
return String(row[encoding.detail.field] ?? "");
|
|
7506
|
+
}
|
|
7404
7507
|
if (encoding.x?.type === "temporal") {
|
|
7405
7508
|
return formatValue(row[encoding.x.field], "temporal");
|
|
7406
7509
|
}
|
|
@@ -7497,8 +7600,28 @@ var builtinRenderers = {
|
|
|
7497
7600
|
for (const [type, renderer] of Object.entries(builtinRenderers)) {
|
|
7498
7601
|
registerChartRenderer(type, renderer);
|
|
7499
7602
|
}
|
|
7500
|
-
function
|
|
7501
|
-
if (
|
|
7603
|
+
function computeMarkObstacles(marks, scales) {
|
|
7604
|
+
if (scales.y?.type === "band") {
|
|
7605
|
+
return computeBandRowObstacles(marks, scales);
|
|
7606
|
+
}
|
|
7607
|
+
const obstacles = [];
|
|
7608
|
+
for (const mark of marks) {
|
|
7609
|
+
if (mark.type === "rect") {
|
|
7610
|
+
const rm = mark;
|
|
7611
|
+
obstacles.push({ x: rm.x, y: rm.y, width: rm.width, height: rm.height });
|
|
7612
|
+
} else if (mark.type === "point") {
|
|
7613
|
+
const pm = mark;
|
|
7614
|
+
obstacles.push({
|
|
7615
|
+
x: pm.cx - pm.r,
|
|
7616
|
+
y: pm.cy - pm.r,
|
|
7617
|
+
width: pm.r * 2,
|
|
7618
|
+
height: pm.r * 2
|
|
7619
|
+
});
|
|
7620
|
+
}
|
|
7621
|
+
}
|
|
7622
|
+
return obstacles;
|
|
7623
|
+
}
|
|
7624
|
+
function computeBandRowObstacles(marks, scales) {
|
|
7502
7625
|
const rows = /* @__PURE__ */ new Map();
|
|
7503
7626
|
for (const mark of marks) {
|
|
7504
7627
|
let cy;
|
|
@@ -7657,7 +7780,7 @@ function compileChart(spec, options) {
|
|
|
7657
7780
|
}
|
|
7658
7781
|
scales.defaultColor = theme.colors.categorical[0];
|
|
7659
7782
|
const isRadial = chartSpec.type === "pie" || chartSpec.type === "donut";
|
|
7660
|
-
const axes = isRadial ? { x: void 0, y: void 0 } : computeAxes(scales, chartArea, strategy, theme);
|
|
7783
|
+
const axes = isRadial ? { x: void 0, y: void 0 } : computeAxes(scales, chartArea, strategy, theme, options.measureText);
|
|
7661
7784
|
if (!isRadial) {
|
|
7662
7785
|
computeGridlines(axes, chartArea);
|
|
7663
7786
|
}
|
|
@@ -7667,7 +7790,18 @@ function compileChart(spec, options) {
|
|
|
7667
7790
|
if (finalLegend.bounds.width > 0) {
|
|
7668
7791
|
obstacles.push(finalLegend.bounds);
|
|
7669
7792
|
}
|
|
7670
|
-
obstacles.push(...
|
|
7793
|
+
obstacles.push(...computeMarkObstacles(marks, scales));
|
|
7794
|
+
for (const mark of marks) {
|
|
7795
|
+
if (mark.type !== "area" && mark.label?.visible) {
|
|
7796
|
+
obstacles.push(computeLabelBounds(mark.label));
|
|
7797
|
+
}
|
|
7798
|
+
}
|
|
7799
|
+
const brandPadding = theme.spacing.padding;
|
|
7800
|
+
const brandX = dims.total.width - brandPadding - BRAND_RESERVE_WIDTH2;
|
|
7801
|
+
const xAxisExtent = axes.x?.label ? 48 : axes.x ? 26 : 0;
|
|
7802
|
+
const firstBottomChrome = dims.chrome.source ?? dims.chrome.byline ?? dims.chrome.footer;
|
|
7803
|
+
const brandY = firstBottomChrome ? chartArea.y + chartArea.height + xAxisExtent + firstBottomChrome.y : chartArea.y + chartArea.height + xAxisExtent + theme.spacing.chartToFooter;
|
|
7804
|
+
obstacles.push({ x: brandX, y: brandY, width: BRAND_RESERVE_WIDTH2, height: 30 });
|
|
7671
7805
|
const annotations = computeAnnotations(
|
|
7672
7806
|
chartSpec,
|
|
7673
7807
|
scales,
|