@opendata-ai/openchart-engine 6.2.1 → 6.3.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 +200 -20
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/legend.test.ts +102 -0
- package/src/annotations/__tests__/compute.test.ts +107 -0
- package/src/annotations/compute.ts +84 -2
- package/src/charts/bar/__tests__/labels.test.ts +112 -0
- package/src/charts/bar/labels.ts +77 -4
- package/src/charts/line/labels.ts +6 -2
- package/src/charts/scatter/__tests__/compute.test.ts +121 -0
- package/src/charts/scatter/compute.ts +63 -12
- package/src/compile.ts +2 -0
- package/src/compiler/__tests__/validate.test.ts +34 -0
- package/src/compiler/validate.ts +34 -0
- package/src/layout/dimensions.ts +1 -0
- package/src/layout/scales.ts +4 -1
- package/src/legend/compute.ts +22 -2
- package/src/tooltips/__tests__/compute.test.ts +61 -0
- package/src/tooltips/compute.ts +14 -0
package/dist/index.js
CHANGED
|
@@ -388,7 +388,7 @@ function nudgeAnnotationFromObstacles(annotation, originalAnnotation, scales, ch
|
|
|
388
388
|
if (stillCollides) continue;
|
|
389
389
|
const labelCenterX = candidateBounds.x + candidateBounds.width / 2;
|
|
390
390
|
const labelCenterY = candidateBounds.y + candidateBounds.height / 2;
|
|
391
|
-
const inBounds = labelCenterX >= chartArea.x && labelCenterX <= chartArea.x + chartArea.width +
|
|
391
|
+
const inBounds = labelCenterX >= chartArea.x && labelCenterX <= chartArea.x + chartArea.width + 10 && labelCenterY >= chartArea.y - fontSize && labelCenterY <= chartArea.y + chartArea.height + fontSize * 3;
|
|
392
392
|
if (inBounds) {
|
|
393
393
|
if (candidateLabel.connector && dx === 0 && dy !== 0) {
|
|
394
394
|
candidateLabel.connector = {
|
|
@@ -436,7 +436,7 @@ function resolveAnnotationCollisions(annotations, originalSpecs, scales, chartAr
|
|
|
436
436
|
if (stillCollides) continue;
|
|
437
437
|
const labelCenterX = candidateBounds.x + candidateBounds.width / 2;
|
|
438
438
|
const labelCenterY = candidateBounds.y + candidateBounds.height / 2;
|
|
439
|
-
const inBounds = labelCenterX >= chartArea.x && labelCenterX <= chartArea.x + chartArea.width +
|
|
439
|
+
const inBounds = labelCenterX >= chartArea.x && labelCenterX <= chartArea.x + chartArea.width + 10 && labelCenterY >= chartArea.y - fontSize && labelCenterY <= chartArea.y + chartArea.height + fontSize;
|
|
440
440
|
if (inBounds) {
|
|
441
441
|
let newConnector = annotation.label.connector;
|
|
442
442
|
if (newConnector) {
|
|
@@ -470,7 +470,54 @@ function resolveAnnotationCollisions(annotations, originalSpecs, scales, chartAr
|
|
|
470
470
|
placedBounds.push(estimateLabelBounds(annotation.label));
|
|
471
471
|
}
|
|
472
472
|
}
|
|
473
|
-
|
|
473
|
+
var CLAMP_MARGIN = 4;
|
|
474
|
+
function clampAnnotationsToBounds(annotations, svgWidth, svgHeight) {
|
|
475
|
+
for (const annotation of annotations) {
|
|
476
|
+
if (annotation.type !== "text" || !annotation.label) continue;
|
|
477
|
+
const bounds = estimateLabelBounds(annotation.label);
|
|
478
|
+
let dx = 0;
|
|
479
|
+
let dy = 0;
|
|
480
|
+
if (bounds.x + bounds.width > svgWidth - CLAMP_MARGIN) {
|
|
481
|
+
dx = svgWidth - CLAMP_MARGIN - (bounds.x + bounds.width);
|
|
482
|
+
}
|
|
483
|
+
if (bounds.x + dx < CLAMP_MARGIN) {
|
|
484
|
+
dx = CLAMP_MARGIN - bounds.x;
|
|
485
|
+
}
|
|
486
|
+
if (bounds.y < CLAMP_MARGIN) {
|
|
487
|
+
dy = CLAMP_MARGIN - bounds.y;
|
|
488
|
+
}
|
|
489
|
+
if (bounds.y + bounds.height + dy > svgHeight - CLAMP_MARGIN) {
|
|
490
|
+
dy = svgHeight - CLAMP_MARGIN - (bounds.y + bounds.height);
|
|
491
|
+
}
|
|
492
|
+
if (dx === 0 && dy === 0) continue;
|
|
493
|
+
const newX = annotation.label.x + dx;
|
|
494
|
+
const newY = annotation.label.y + dy;
|
|
495
|
+
let newConnector = annotation.label.connector;
|
|
496
|
+
if (newConnector) {
|
|
497
|
+
const fontSize = annotation.label.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
|
|
498
|
+
const fontWeight = annotation.label.style.fontWeight ?? DEFAULT_ANNOTATION_FONT_WEIGHT;
|
|
499
|
+
const connStyle = newConnector.style === "curve" ? "curve" : "straight";
|
|
500
|
+
const newFrom = computeConnectorOrigin(
|
|
501
|
+
newX,
|
|
502
|
+
newY,
|
|
503
|
+
annotation.label.text,
|
|
504
|
+
fontSize,
|
|
505
|
+
fontWeight,
|
|
506
|
+
newConnector.to.x,
|
|
507
|
+
newConnector.to.y,
|
|
508
|
+
connStyle
|
|
509
|
+
);
|
|
510
|
+
newConnector = { ...newConnector, from: newFrom };
|
|
511
|
+
}
|
|
512
|
+
annotation.label = {
|
|
513
|
+
...annotation.label,
|
|
514
|
+
x: newX,
|
|
515
|
+
y: newY,
|
|
516
|
+
connector: newConnector
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function computeAnnotations(spec, scales, chartArea, strategy, isDark = false, obstacles = [], svgDimensions) {
|
|
474
521
|
if (strategy.annotationPosition === "tooltip-only") {
|
|
475
522
|
return [];
|
|
476
523
|
}
|
|
@@ -496,6 +543,9 @@ function computeAnnotations(spec, scales, chartArea, strategy, isDark = false, o
|
|
|
496
543
|
}
|
|
497
544
|
}
|
|
498
545
|
resolveAnnotationCollisions(annotations, spec.annotations, scales, chartArea);
|
|
546
|
+
if (svgDimensions) {
|
|
547
|
+
clampAnnotationsToBounds(annotations, svgDimensions.width, svgDimensions.height);
|
|
548
|
+
}
|
|
499
549
|
annotations.sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));
|
|
500
550
|
return annotations;
|
|
501
551
|
}
|
|
@@ -772,6 +822,24 @@ import {
|
|
|
772
822
|
estimateTextWidth as estimateTextWidth2,
|
|
773
823
|
resolveCollisions
|
|
774
824
|
} from "@opendata-ai/openchart-core";
|
|
825
|
+
var SUFFIX_MULTIPLIERS = {
|
|
826
|
+
K: 1e3,
|
|
827
|
+
M: 1e6,
|
|
828
|
+
B: 1e9,
|
|
829
|
+
T: 1e12
|
|
830
|
+
};
|
|
831
|
+
function parseDisplayNumber(raw) {
|
|
832
|
+
const trimmed = raw.trim();
|
|
833
|
+
if (!trimmed) return NaN;
|
|
834
|
+
const last = trimmed[trimmed.length - 1].toUpperCase();
|
|
835
|
+
const multiplier = SUFFIX_MULTIPLIERS[last];
|
|
836
|
+
if (multiplier) {
|
|
837
|
+
const numPart = trimmed.slice(0, -1).replace(/,/g, "");
|
|
838
|
+
const n = Number(numPart);
|
|
839
|
+
return Number.isNaN(n) ? NaN : n * multiplier;
|
|
840
|
+
}
|
|
841
|
+
return Number(trimmed.replace(/,/g, ""));
|
|
842
|
+
}
|
|
775
843
|
var LABEL_FONT_SIZE = 11;
|
|
776
844
|
var LABEL_FONT_WEIGHT = 600;
|
|
777
845
|
var LABEL_PADDING = 6;
|
|
@@ -780,6 +848,7 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat) {
|
|
|
780
848
|
if (density === "none") return [];
|
|
781
849
|
const targetMarks = density === "endpoints" && marks.length > 1 ? [marks[0], marks[marks.length - 1]] : marks;
|
|
782
850
|
const candidates = [];
|
|
851
|
+
const fitsInSegment = [];
|
|
783
852
|
const formatter = buildD3Formatter(labelFormat);
|
|
784
853
|
for (const mark of targetMarks) {
|
|
785
854
|
const ariaLabel = mark.aria.label;
|
|
@@ -788,7 +857,7 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat) {
|
|
|
788
857
|
if (!rawValue) continue;
|
|
789
858
|
let valuePart = rawValue;
|
|
790
859
|
if (formatter) {
|
|
791
|
-
const num =
|
|
860
|
+
const num = parseDisplayNumber(rawValue);
|
|
792
861
|
if (!Number.isNaN(num)) valuePart = formatter(num);
|
|
793
862
|
}
|
|
794
863
|
const textWidth = estimateTextWidth2(valuePart, LABEL_FONT_SIZE, LABEL_FONT_WEIGHT);
|
|
@@ -812,6 +881,8 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat) {
|
|
|
812
881
|
textAnchor = "start";
|
|
813
882
|
}
|
|
814
883
|
const anchorY = mark.y + mark.height / 2;
|
|
884
|
+
const fits = !(isStacked && textWidth > mark.width - 2 * LABEL_PADDING);
|
|
885
|
+
fitsInSegment.push(fits);
|
|
815
886
|
candidates.push({
|
|
816
887
|
text: valuePart,
|
|
817
888
|
anchorX,
|
|
@@ -832,15 +903,40 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat) {
|
|
|
832
903
|
}
|
|
833
904
|
if (candidates.length === 0) return [];
|
|
834
905
|
if (density === "all") {
|
|
835
|
-
return candidates.map((c) => ({
|
|
906
|
+
return candidates.map((c, i) => ({
|
|
836
907
|
text: c.text,
|
|
837
908
|
x: c.anchorX,
|
|
838
909
|
y: c.anchorY,
|
|
839
910
|
style: c.style,
|
|
840
|
-
visible:
|
|
911
|
+
visible: fitsInSegment[i] !== false
|
|
841
912
|
}));
|
|
842
913
|
}
|
|
843
|
-
|
|
914
|
+
const fittingCandidates = [];
|
|
915
|
+
const unfittingIndices = /* @__PURE__ */ new Set();
|
|
916
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
917
|
+
if (fitsInSegment[i] === false) {
|
|
918
|
+
unfittingIndices.add(i);
|
|
919
|
+
} else {
|
|
920
|
+
fittingCandidates.push(candidates[i]);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
const resolved = resolveCollisions(fittingCandidates);
|
|
924
|
+
const results = [];
|
|
925
|
+
let resolvedIdx = 0;
|
|
926
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
927
|
+
if (unfittingIndices.has(i)) {
|
|
928
|
+
results.push({
|
|
929
|
+
text: candidates[i].text,
|
|
930
|
+
x: candidates[i].anchorX,
|
|
931
|
+
y: candidates[i].anchorY,
|
|
932
|
+
style: candidates[i].style,
|
|
933
|
+
visible: false
|
|
934
|
+
});
|
|
935
|
+
} else {
|
|
936
|
+
results.push(resolved[resolvedIdx++]);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return results;
|
|
844
940
|
}
|
|
845
941
|
|
|
846
942
|
// src/charts/bar/index.ts
|
|
@@ -2487,7 +2583,11 @@ function _getMidValue(rows, field) {
|
|
|
2487
2583
|
}
|
|
2488
2584
|
|
|
2489
2585
|
// src/charts/line/labels.ts
|
|
2490
|
-
import {
|
|
2586
|
+
import {
|
|
2587
|
+
EXTENDED_OFFSET_STRATEGIES,
|
|
2588
|
+
estimateTextWidth as estimateTextWidth5,
|
|
2589
|
+
resolveCollisions as resolveCollisions4
|
|
2590
|
+
} from "@opendata-ai/openchart-core";
|
|
2491
2591
|
var LABEL_FONT_SIZE4 = 11;
|
|
2492
2592
|
var LABEL_FONT_WEIGHT4 = 600;
|
|
2493
2593
|
var LABEL_OFFSET_X2 = 6;
|
|
@@ -2541,7 +2641,7 @@ function computeLineLabels(marks, strategy, density = "auto", labelOffsets) {
|
|
|
2541
2641
|
}
|
|
2542
2642
|
return result;
|
|
2543
2643
|
}
|
|
2544
|
-
const resolved = resolveCollisions4(candidates);
|
|
2644
|
+
const resolved = resolveCollisions4(candidates, EXTENDED_OFFSET_STRATEGIES);
|
|
2545
2645
|
for (let i = 0; i < resolved.length; i++) {
|
|
2546
2646
|
const seriesKey = seriesOrder[i];
|
|
2547
2647
|
const label = resolved[i];
|
|
@@ -5494,6 +5594,30 @@ function sequential() {
|
|
|
5494
5594
|
var DEFAULT_POINT_RADIUS2 = 5;
|
|
5495
5595
|
var MIN_BUBBLE_RADIUS = 3;
|
|
5496
5596
|
var MAX_BUBBLE_RADIUS = 30;
|
|
5597
|
+
function resolvePosition2(value, channelType, scale) {
|
|
5598
|
+
switch (channelType) {
|
|
5599
|
+
case "nominal":
|
|
5600
|
+
case "ordinal": {
|
|
5601
|
+
const s = String(value);
|
|
5602
|
+
if ("bandwidth" in scale && typeof scale.bandwidth === "function") {
|
|
5603
|
+
const bw = scale.bandwidth();
|
|
5604
|
+
const pos = scale(s);
|
|
5605
|
+
if (pos === void 0) return void 0;
|
|
5606
|
+
return bw > 0 ? pos + bw / 2 : pos;
|
|
5607
|
+
}
|
|
5608
|
+
return scale(s);
|
|
5609
|
+
}
|
|
5610
|
+
case "temporal": {
|
|
5611
|
+
const px = scale(new Date(value));
|
|
5612
|
+
return Number.isNaN(px) ? void 0 : px;
|
|
5613
|
+
}
|
|
5614
|
+
default: {
|
|
5615
|
+
const num = Number(value);
|
|
5616
|
+
if (!Number.isFinite(num)) return void 0;
|
|
5617
|
+
return scale(num);
|
|
5618
|
+
}
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5497
5621
|
function computeScatterMarks(spec, scales, _chartArea, _strategy) {
|
|
5498
5622
|
const encoding = spec.encoding;
|
|
5499
5623
|
const xChannel = encoding.x;
|
|
@@ -5503,6 +5627,8 @@ function computeScatterMarks(spec, scales, _chartArea, _strategy) {
|
|
|
5503
5627
|
}
|
|
5504
5628
|
const xScale = scales.x.scale;
|
|
5505
5629
|
const yScale = scales.y.scale;
|
|
5630
|
+
const xType = xChannel.type;
|
|
5631
|
+
const yType = yChannel.type;
|
|
5506
5632
|
const colorEnc = encoding.color && "field" in encoding.color ? encoding.color : void 0;
|
|
5507
5633
|
const isSequentialColor = colorEnc?.type === "quantitative";
|
|
5508
5634
|
const colorField = colorEnc?.field;
|
|
@@ -5516,11 +5642,11 @@ function computeScatterMarks(spec, scales, _chartArea, _strategy) {
|
|
|
5516
5642
|
}
|
|
5517
5643
|
const marks = [];
|
|
5518
5644
|
for (const row of spec.data) {
|
|
5519
|
-
const
|
|
5520
|
-
const
|
|
5521
|
-
|
|
5522
|
-
const
|
|
5523
|
-
|
|
5645
|
+
const rawX = row[xChannel.field];
|
|
5646
|
+
const rawY = row[yChannel.field];
|
|
5647
|
+
const cx = resolvePosition2(rawX, xType, xScale);
|
|
5648
|
+
const cy = resolvePosition2(rawY, yType, yScale);
|
|
5649
|
+
if (cx === void 0 || cy === void 0) continue;
|
|
5524
5650
|
const category = colorField && !isSequentialColor ? String(row[colorField] ?? "") : void 0;
|
|
5525
5651
|
let color2;
|
|
5526
5652
|
if (isSequentialColor && colorField) {
|
|
@@ -5536,7 +5662,7 @@ function computeScatterMarks(spec, scales, _chartArea, _strategy) {
|
|
|
5536
5662
|
radius = sizeScale(sizeVal);
|
|
5537
5663
|
}
|
|
5538
5664
|
}
|
|
5539
|
-
const labelParts = [`${xChannel.field}=${
|
|
5665
|
+
const labelParts = [`${xChannel.field}=${rawX}`, `${yChannel.field}=${rawY}`];
|
|
5540
5666
|
if (category) labelParts.push(`${colorField}=${category}`);
|
|
5541
5667
|
if (sizeField && row[sizeField] != null) {
|
|
5542
5668
|
labelParts.push(`${sizeField}=${row[sizeField]}`);
|
|
@@ -6026,6 +6152,38 @@ function validateChartSpec(spec, errors) {
|
|
|
6026
6152
|
}
|
|
6027
6153
|
for (const [channel, channelSpec] of Object.entries(encoding)) {
|
|
6028
6154
|
if (!channelSpec || typeof channelSpec !== "object") continue;
|
|
6155
|
+
if (channel === "tooltip" && Array.isArray(channelSpec)) {
|
|
6156
|
+
for (let i = 0; i < channelSpec.length; i++) {
|
|
6157
|
+
const elem = channelSpec[i];
|
|
6158
|
+
if (!elem || typeof elem !== "object") continue;
|
|
6159
|
+
if (!elem.field || typeof elem.field !== "string") {
|
|
6160
|
+
errors.push({
|
|
6161
|
+
message: `Spec error: encoding.tooltip[${i}] must have a "field" string`,
|
|
6162
|
+
path: `encoding.tooltip[${i}].field`,
|
|
6163
|
+
code: "MISSING_FIELD",
|
|
6164
|
+
suggestion: `Add a field name from your data columns: ${availableColumns}`
|
|
6165
|
+
});
|
|
6166
|
+
continue;
|
|
6167
|
+
}
|
|
6168
|
+
if (!dataColumns.has(elem.field) && !transformFields.has(elem.field)) {
|
|
6169
|
+
errors.push({
|
|
6170
|
+
message: `Spec error: encoding.tooltip[${i}].field "${elem.field}" does not exist in data. Available columns: ${availableColumns}`,
|
|
6171
|
+
path: `encoding.tooltip[${i}].field`,
|
|
6172
|
+
code: "DATA_FIELD_MISSING",
|
|
6173
|
+
suggestion: `Use one of the available data columns: ${availableColumns}`
|
|
6174
|
+
});
|
|
6175
|
+
}
|
|
6176
|
+
if (elem.type && !VALID_FIELD_TYPES.has(elem.type)) {
|
|
6177
|
+
errors.push({
|
|
6178
|
+
message: `Spec error: encoding.tooltip[${i}].type "${elem.type}" is not valid. Must be one of: ${[...VALID_FIELD_TYPES].join(", ")}`,
|
|
6179
|
+
path: `encoding.tooltip[${i}].type`,
|
|
6180
|
+
code: "INVALID_VALUE",
|
|
6181
|
+
suggestion: `Use one of: ${[...VALID_FIELD_TYPES].join(", ")}`
|
|
6182
|
+
});
|
|
6183
|
+
}
|
|
6184
|
+
}
|
|
6185
|
+
continue;
|
|
6186
|
+
}
|
|
6029
6187
|
const channelObj = channelSpec;
|
|
6030
6188
|
const channelRule = rules[channel];
|
|
6031
6189
|
if ("condition" in channelObj) continue;
|
|
@@ -7276,7 +7434,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy) {
|
|
|
7276
7434
|
}
|
|
7277
7435
|
}
|
|
7278
7436
|
if (encoding.y && !isRadial) {
|
|
7279
|
-
if (spec.markType === "bar" || spec.markType === "circle" || encoding.y.type === "nominal" || encoding.y.type === "ordinal") {
|
|
7437
|
+
if (spec.markType === "bar" || spec.markType === "circle" || spec.markType === "lollipop" || encoding.y.type === "nominal" || encoding.y.type === "ordinal") {
|
|
7280
7438
|
const yField = encoding.y.field;
|
|
7281
7439
|
let maxLabelWidth = 0;
|
|
7282
7440
|
for (const row of spec.data) {
|
|
@@ -7658,7 +7816,7 @@ function buildPositionalScale(channel, data, rangeStart, rangeEnd, chartType, ax
|
|
|
7658
7816
|
return buildLinearScale(channel, data, rangeStart, rangeEnd);
|
|
7659
7817
|
case "nominal":
|
|
7660
7818
|
case "ordinal":
|
|
7661
|
-
if (chartType === "bar" || chartType === "circle" && axis === "y") {
|
|
7819
|
+
if (chartType === "bar" || (chartType === "circle" || chartType === "lollipop") && axis === "y") {
|
|
7662
7820
|
return buildBandScale(channel, data, rangeStart, rangeEnd);
|
|
7663
7821
|
}
|
|
7664
7822
|
return buildPointScale(channel, data, rangeStart, rangeEnd);
|
|
@@ -7779,6 +7937,7 @@ function swatchShapeForType(markType) {
|
|
|
7779
7937
|
return "line";
|
|
7780
7938
|
case "point":
|
|
7781
7939
|
case "circle":
|
|
7940
|
+
case "lollipop":
|
|
7782
7941
|
return "circle";
|
|
7783
7942
|
default:
|
|
7784
7943
|
return "square";
|
|
@@ -7881,10 +8040,11 @@ function computeLegend(spec, strategy, theme, chartArea) {
|
|
|
7881
8040
|
const entryHeight = Math.max(SWATCH_SIZE2, labelStyle.fontSize * labelStyle.lineHeight);
|
|
7882
8041
|
const maxHeightRatio = strategy.legendMaxHeight > 0 ? strategy.legendMaxHeight : RIGHT_LEGEND_MAX_HEIGHT_RATIO;
|
|
7883
8042
|
const maxLegendHeight = chartArea.height * maxHeightRatio;
|
|
7884
|
-
const
|
|
8043
|
+
const maxFromSpace = Math.max(
|
|
7885
8044
|
1,
|
|
7886
8045
|
Math.floor((maxLegendHeight - LEGEND_PADDING * 2) / (entryHeight + 4))
|
|
7887
8046
|
);
|
|
8047
|
+
const maxEntries = spec.legend?.symbolLimit != null ? Math.min(Math.max(1, spec.legend.symbolLimit), maxFromSpace) : maxFromSpace;
|
|
7888
8048
|
if (entries.length > maxEntries) {
|
|
7889
8049
|
entries = truncateEntries(entries, maxEntries);
|
|
7890
8050
|
}
|
|
@@ -7909,7 +8069,14 @@ function computeLegend(spec, strategy, theme, chartArea) {
|
|
|
7909
8069
|
};
|
|
7910
8070
|
}
|
|
7911
8071
|
const availableWidth = chartArea.width - LEGEND_PADDING * 2 - BRAND_RESERVE_WIDTH;
|
|
7912
|
-
|
|
8072
|
+
if (spec.legend?.symbolLimit != null) {
|
|
8073
|
+
const limit = Math.max(1, spec.legend.symbolLimit);
|
|
8074
|
+
if (limit < entries.length) {
|
|
8075
|
+
entries = truncateEntries(entries, limit);
|
|
8076
|
+
}
|
|
8077
|
+
}
|
|
8078
|
+
const maxRows = spec.legend?.columns != null ? Math.ceil(entries.length / spec.legend.columns) : TOP_LEGEND_MAX_ROWS;
|
|
8079
|
+
const maxFit = entriesThatFit(entries, availableWidth, maxRows, labelStyle);
|
|
7913
8080
|
if (maxFit < entries.length) {
|
|
7914
8081
|
entries = truncateEntries(entries, maxFit);
|
|
7915
8082
|
}
|
|
@@ -8602,7 +8769,17 @@ function formatValue(value, fieldType, format2) {
|
|
|
8602
8769
|
}
|
|
8603
8770
|
return String(value);
|
|
8604
8771
|
}
|
|
8772
|
+
function buildExplicitTooltipFields(row, channels) {
|
|
8773
|
+
return channels.map((ch) => ({
|
|
8774
|
+
label: ch.axis?.label ?? ch.field,
|
|
8775
|
+
value: formatValue(row[ch.field], ch.type, ch.axis?.format)
|
|
8776
|
+
}));
|
|
8777
|
+
}
|
|
8605
8778
|
function buildFields(row, encoding, color2) {
|
|
8779
|
+
if (encoding.tooltip) {
|
|
8780
|
+
const channels = Array.isArray(encoding.tooltip) ? encoding.tooltip : [encoding.tooltip];
|
|
8781
|
+
return buildExplicitTooltipFields(row, channels);
|
|
8782
|
+
}
|
|
8606
8783
|
const fields = [];
|
|
8607
8784
|
if (encoding.y) {
|
|
8608
8785
|
fields.push({
|
|
@@ -8922,6 +9099,8 @@ var builtinRenderers = {
|
|
|
8922
9099
|
// old 'donut'
|
|
8923
9100
|
circle: dotRenderer,
|
|
8924
9101
|
// old 'dot'
|
|
9102
|
+
lollipop: dotRenderer,
|
|
9103
|
+
// semantic alias for dot/circle
|
|
8925
9104
|
text: textRenderer,
|
|
8926
9105
|
rule: ruleRenderer,
|
|
8927
9106
|
tick: tickRenderer,
|
|
@@ -9157,7 +9336,8 @@ function compileChart(spec, options) {
|
|
|
9157
9336
|
chartArea,
|
|
9158
9337
|
strategy,
|
|
9159
9338
|
theme.isDark,
|
|
9160
|
-
obstacles
|
|
9339
|
+
obstacles,
|
|
9340
|
+
{ width: dims.total.width, height: dims.total.height }
|
|
9161
9341
|
);
|
|
9162
9342
|
const tooltipDescriptors = computeTooltipDescriptors(chartSpec, marks);
|
|
9163
9343
|
const altText = generateAltText(
|