@opendata-ai/openchart-engine 6.24.0 → 6.24.2
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 +46 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/legend.test.ts +11 -0
- package/src/annotations/compute.ts +5 -4
- package/src/annotations/resolve-refline.ts +4 -2
- package/src/annotations/resolve-text.ts +1 -0
- package/src/charts/bar/labels.ts +26 -9
- package/src/charts/line/__tests__/compute.test.ts +28 -0
- package/src/charts/line/area.ts +12 -2
- package/src/compiler/normalize.ts +2 -0
- package/src/layout/dimensions.ts +7 -2
- package/src/legend/compute.ts +31 -17
package/dist/index.js
CHANGED
|
@@ -380,7 +380,8 @@ function resolveTextAnnotation(annotation, scales, chartArea, isDark) {
|
|
|
380
380
|
stroke: annotation.stroke ?? "#999999",
|
|
381
381
|
style: connectorStyle
|
|
382
382
|
} : void 0,
|
|
383
|
-
background: annotation.background
|
|
383
|
+
background: annotation.background,
|
|
384
|
+
halo: annotation.halo
|
|
384
385
|
};
|
|
385
386
|
return {
|
|
386
387
|
type: "text",
|
|
@@ -467,7 +468,9 @@ function resolveRefLineAnnotation(annotation, scales, chartArea, isDark) {
|
|
|
467
468
|
return null;
|
|
468
469
|
}
|
|
469
470
|
let strokeDasharray;
|
|
470
|
-
if (annotation.
|
|
471
|
+
if (annotation.strokeDash && annotation.strokeDash.length > 0) {
|
|
472
|
+
strokeDasharray = annotation.strokeDash.join(" ");
|
|
473
|
+
} else if (annotation.style === "dashed" || annotation.style === void 0) {
|
|
471
474
|
strokeDasharray = DEFAULT_REFLINE_DASH;
|
|
472
475
|
} else if (annotation.style === "dotted") {
|
|
473
476
|
strokeDasharray = "2 2";
|
|
@@ -549,11 +552,12 @@ function resolveRefLineAnnotation(annotation, scales, chartArea, isDark) {
|
|
|
549
552
|
|
|
550
553
|
// src/annotations/compute.ts
|
|
551
554
|
function computeAnnotations(spec, scales, chartArea, strategy, isDark = false, obstacles = [], svgDimensions) {
|
|
552
|
-
|
|
553
|
-
return [];
|
|
554
|
-
}
|
|
555
|
+
const isCompact = strategy.annotationPosition === "tooltip-only";
|
|
555
556
|
const annotations = [];
|
|
556
557
|
for (const annotation of spec.annotations) {
|
|
558
|
+
if (isCompact && annotation.responsive !== false) {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
557
561
|
let resolved = null;
|
|
558
562
|
switch (annotation.type) {
|
|
559
563
|
case "text":
|
|
@@ -979,6 +983,7 @@ function computeSimpleBars(data, valueField, categoryField, xScale, yScale, band
|
|
|
979
983
|
import {
|
|
980
984
|
buildD3Formatter,
|
|
981
985
|
estimateTextWidth as estimateTextWidth2,
|
|
986
|
+
findAccessibleColor,
|
|
982
987
|
getRepresentativeColor,
|
|
983
988
|
resolveCollisions
|
|
984
989
|
} from "@opendata-ai/openchart-core";
|
|
@@ -1047,21 +1052,35 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
|
|
|
1047
1052
|
const textHeight = LABEL_FONT_SIZE * 1.2;
|
|
1048
1053
|
const isStacked = mark.cornerRadius === 0;
|
|
1049
1054
|
const isInside = mark.width >= MIN_WIDTH_FOR_INSIDE_LABEL;
|
|
1055
|
+
const isNegative = Number.isFinite(rawNum) ? rawNum < 0 : false;
|
|
1056
|
+
const bgColor = getRepresentativeColor(mark.fill);
|
|
1050
1057
|
let anchorX;
|
|
1051
1058
|
let fill;
|
|
1052
1059
|
let textAnchor;
|
|
1053
1060
|
if (isStacked && isInside) {
|
|
1054
1061
|
anchorX = mark.x + mark.width / 2;
|
|
1055
|
-
fill = "#ffffff";
|
|
1062
|
+
fill = findAccessibleColor("#ffffff", bgColor, 4.5);
|
|
1056
1063
|
textAnchor = "middle";
|
|
1057
1064
|
} else if (isInside) {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1065
|
+
if (isNegative) {
|
|
1066
|
+
anchorX = mark.x + LABEL_PADDING;
|
|
1067
|
+
fill = findAccessibleColor("#ffffff", bgColor, 4.5);
|
|
1068
|
+
textAnchor = "start";
|
|
1069
|
+
} else {
|
|
1070
|
+
anchorX = mark.x + mark.width - LABEL_PADDING;
|
|
1071
|
+
fill = findAccessibleColor("#ffffff", bgColor, 4.5);
|
|
1072
|
+
textAnchor = "end";
|
|
1073
|
+
}
|
|
1061
1074
|
} else {
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1075
|
+
if (isNegative) {
|
|
1076
|
+
anchorX = mark.x - LABEL_PADDING;
|
|
1077
|
+
fill = getRepresentativeColor(mark.fill);
|
|
1078
|
+
textAnchor = "end";
|
|
1079
|
+
} else {
|
|
1080
|
+
anchorX = mark.x + mark.width + LABEL_PADDING;
|
|
1081
|
+
fill = getRepresentativeColor(mark.fill);
|
|
1082
|
+
textAnchor = "start";
|
|
1083
|
+
}
|
|
1065
1084
|
}
|
|
1066
1085
|
const anchorY = mark.y + mark.height / 2;
|
|
1067
1086
|
const fits = !(isStacked && textWidth > mark.width - 2 * LABEL_PADDING);
|
|
@@ -2662,14 +2681,16 @@ function computeSingleArea(spec, scales, _chartArea) {
|
|
|
2662
2681
|
const color2 = getColor(scales, seriesKey);
|
|
2663
2682
|
const sortedRows = xChannel.type === "nominal" || xChannel.type === "ordinal" ? rows : sortByField(rows, xChannel.field);
|
|
2664
2683
|
const validPoints = [];
|
|
2684
|
+
const y2Channel = encoding.y2;
|
|
2665
2685
|
for (const row of sortedRows) {
|
|
2666
2686
|
const xVal = scaleValue(scales.x.scale, scales.x.type, row[xChannel.field]);
|
|
2667
2687
|
const yVal = scaleValue(scales.y.scale, scales.y.type, row[yChannel.field]);
|
|
2668
2688
|
if (xVal === null || yVal === null) continue;
|
|
2689
|
+
const yBottomVal = y2Channel && row[y2Channel.field] != null ? scaleValue(scales.y.scale, scales.y.type, row[y2Channel.field]) : null;
|
|
2669
2690
|
validPoints.push({
|
|
2670
2691
|
x: xVal,
|
|
2671
2692
|
yTop: yVal,
|
|
2672
|
-
yBottom: baselineY,
|
|
2693
|
+
yBottom: yBottomVal ?? baselineY,
|
|
2673
2694
|
row
|
|
2674
2695
|
});
|
|
2675
2696
|
}
|
|
@@ -2683,6 +2704,7 @@ function computeSingleArea(spec, scales, _chartArea) {
|
|
|
2683
2704
|
const bottomPoints = validPoints.map((p) => ({ x: p.x, y: p.yBottom }));
|
|
2684
2705
|
const ariaLabel = seriesKey === "__default__" ? `Area with ${validPoints.length} data points` : `${seriesKey}: area with ${validPoints.length} data points`;
|
|
2685
2706
|
const aria = { label: ariaLabel };
|
|
2707
|
+
const fillOpacity = y2Channel ? 0.25 : DEFAULT_FILL_OPACITY;
|
|
2686
2708
|
marks.push({
|
|
2687
2709
|
type: "area",
|
|
2688
2710
|
topPoints,
|
|
@@ -2690,7 +2712,7 @@ function computeSingleArea(spec, scales, _chartArea) {
|
|
|
2690
2712
|
path: pathStr,
|
|
2691
2713
|
topPath: topPathStr,
|
|
2692
2714
|
fill: color2,
|
|
2693
|
-
fillOpacity
|
|
2715
|
+
fillOpacity,
|
|
2694
2716
|
stroke: getRepresentativeColor4(color2),
|
|
2695
2717
|
strokeWidth: 2,
|
|
2696
2718
|
seriesKey: seriesKey === "__default__" ? void 0 : seriesKey,
|
|
@@ -6607,8 +6629,10 @@ function normalizeAnnotations(annotations) {
|
|
|
6607
6629
|
fill: ann.fill ?? "#000000"
|
|
6608
6630
|
};
|
|
6609
6631
|
case "refline":
|
|
6632
|
+
case "rule":
|
|
6610
6633
|
return {
|
|
6611
6634
|
...ann,
|
|
6635
|
+
type: "refline",
|
|
6612
6636
|
style: ann.style ?? "dashed",
|
|
6613
6637
|
strokeWidth: ann.strokeWidth ?? 1,
|
|
6614
6638
|
stroke: ann.stroke ?? "#666666",
|
|
@@ -8342,7 +8366,8 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8342
8366
|
left: padding + (isRadial ? padding : axisMargin)
|
|
8343
8367
|
};
|
|
8344
8368
|
const labelDensity = spec.labels.density;
|
|
8345
|
-
|
|
8369
|
+
const labelsHiddenByStrategy = strategy?.labelMode === "none";
|
|
8370
|
+
if ((spec.markType === "line" || spec.markType === "area") && labelDensity !== "none" && !labelsHiddenByStrategy) {
|
|
8346
8371
|
const colorEnc = encoding.color;
|
|
8347
8372
|
const colorField = colorEnc && "field" in colorEnc ? colorEnc.field : void 0;
|
|
8348
8373
|
if (colorField) {
|
|
@@ -8979,6 +9004,7 @@ function extractColorEntries(spec, theme) {
|
|
|
8979
9004
|
...explicitDomain.filter((v) => dataValues.includes(v)),
|
|
8980
9005
|
...dataValues.filter((v) => !explicitDomain.includes(v))
|
|
8981
9006
|
] : dataValues;
|
|
9007
|
+
const excludeSet = new Set(spec.legend?.exclude ?? []);
|
|
8982
9008
|
return uniqueValues.map((value2, i) => {
|
|
8983
9009
|
let colorIndex = i;
|
|
8984
9010
|
if (explicitDomain && explicitRange) {
|
|
@@ -8991,7 +9017,7 @@ function extractColorEntries(spec, theme) {
|
|
|
8991
9017
|
shape,
|
|
8992
9018
|
active: true
|
|
8993
9019
|
};
|
|
8994
|
-
});
|
|
9020
|
+
}).filter((entry) => !excludeSet.has(entry.label));
|
|
8995
9021
|
}
|
|
8996
9022
|
function truncateEntries(entries, maxCount) {
|
|
8997
9023
|
if (maxCount >= entries.length || maxCount <= 0) return entries;
|
|
@@ -9029,7 +9055,10 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
|
|
|
9029
9055
|
const hasLabels = spec.labels.density !== "none";
|
|
9030
9056
|
const labelsWillRender = strategy.labelMode !== "none";
|
|
9031
9057
|
const hasColorEncoding = spec.encoding.color != null;
|
|
9032
|
-
const
|
|
9058
|
+
const userConfiguredLegend = spec.legend != null && Object.keys(spec.legend).some(
|
|
9059
|
+
(k) => k !== "show" || spec.legend[k] !== false
|
|
9060
|
+
);
|
|
9061
|
+
const legendNotForced = !userConfiguredLegend;
|
|
9033
9062
|
if (isLineOrArea && hasLabels && labelsWillRender && hasColorEncoding && legendNotForced) {
|
|
9034
9063
|
const isArea = spec.markType === "area";
|
|
9035
9064
|
const quantChannel = spec.encoding.y?.type === "quantitative" ? spec.encoding.y : spec.encoding.x;
|