@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 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.style === "dashed" || annotation.style === void 0) {
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
- if (strategy.annotationPosition === "tooltip-only") {
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
- anchorX = mark.x + mark.width - LABEL_PADDING;
1059
- fill = "#ffffff";
1060
- textAnchor = "end";
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
- anchorX = mark.x + mark.width + LABEL_PADDING;
1063
- fill = getRepresentativeColor(mark.fill);
1064
- textAnchor = "start";
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: DEFAULT_FILL_OPACITY,
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
- if ((spec.markType === "line" || spec.markType === "area") && labelDensity !== "none") {
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 legendNotForced = spec.legend?.show !== true;
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;