@opendata-ai/openchart-engine 2.5.0 → 2.7.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 CHANGED
@@ -202,14 +202,23 @@ function resolveRangeAnnotation(annotation, scales, chartArea, isDark) {
202
202
  const rect = { x, y, width, height };
203
203
  let label;
204
204
  if (annotation.label) {
205
- const baseDx = 4;
205
+ const anchor = annotation.labelAnchor ?? "left";
206
+ const centered = anchor === "top" || anchor === "bottom" || anchor === "auto";
207
+ const baseDx = centered ? 0 : anchor === "right" ? -4 : 4;
206
208
  const baseDy = 14;
207
209
  const labelDelta = applyOffset({ dx: baseDx, dy: baseDy }, annotation.labelOffset);
210
+ const style = makeAnnotationLabelStyle(11, 500, void 0, isDark);
211
+ if (centered) {
212
+ style.textAnchor = "middle";
213
+ } else if (anchor === "right") {
214
+ style.textAnchor = "end";
215
+ }
216
+ const baseX = centered ? x + width / 2 : anchor === "right" ? x + width : x;
208
217
  label = {
209
218
  text: annotation.label,
210
- x: x + labelDelta.dx,
219
+ x: baseX + labelDelta.dx,
211
220
  y: y + labelDelta.dy,
212
- style: makeAnnotationLabelStyle(11, 500, void 0, isDark),
221
+ style,
213
222
  visible: true
214
223
  };
215
224
  }
@@ -249,8 +258,9 @@ function resolveRefLineAnnotation(annotation, scales, chartArea, isDark) {
249
258
  let label;
250
259
  if (annotation.label) {
251
260
  const isHorizontal = annotation.y !== void 0;
261
+ const anchor = annotation.labelAnchor ?? "top";
252
262
  const baseDx = isHorizontal ? -4 : 4;
253
- const baseDy = -4;
263
+ const baseDy = anchor === "bottom" ? 14 : -4;
254
264
  const labelDelta = applyOffset({ dx: baseDx, dy: baseDy }, annotation.labelOffset);
255
265
  const defaultStroke2 = isDark ? DARK_REFLINE_STROKE : LIGHT_REFLINE_STROKE;
256
266
  const style = makeAnnotationLabelStyle(11, 400, annotation.stroke ?? defaultStroke2, isDark);
@@ -579,20 +589,30 @@ function computeSimpleBars(data, valueField, categoryField, xScale, yScale, band
579
589
  }
580
590
 
581
591
  // src/charts/bar/labels.ts
582
- import { estimateTextWidth as estimateTextWidth2, resolveCollisions } from "@opendata-ai/openchart-core";
592
+ import {
593
+ buildD3Formatter,
594
+ estimateTextWidth as estimateTextWidth2,
595
+ resolveCollisions
596
+ } from "@opendata-ai/openchart-core";
583
597
  var LABEL_FONT_SIZE = 11;
584
598
  var LABEL_FONT_WEIGHT = 600;
585
599
  var LABEL_PADDING = 6;
586
600
  var MIN_WIDTH_FOR_INSIDE_LABEL = 40;
587
- function computeBarLabels(marks, _chartArea, density = "auto") {
601
+ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat) {
588
602
  if (density === "none") return [];
589
603
  const targetMarks = density === "endpoints" && marks.length > 1 ? [marks[0], marks[marks.length - 1]] : marks;
590
604
  const candidates = [];
605
+ const formatter = buildD3Formatter(labelFormat);
591
606
  for (const mark of targetMarks) {
592
607
  const ariaLabel = mark.aria.label;
593
608
  const lastColon = ariaLabel.lastIndexOf(":");
594
- const valuePart = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
595
- if (!valuePart) continue;
609
+ const rawValue = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
610
+ if (!rawValue) continue;
611
+ let valuePart = rawValue;
612
+ if (formatter) {
613
+ const num = Number(rawValue.replace(/[^0-9.-]/g, ""));
614
+ if (!Number.isNaN(num)) valuePart = formatter(num);
615
+ }
596
616
  const textWidth = estimateTextWidth2(valuePart, LABEL_FONT_SIZE, LABEL_FONT_WEIGHT);
597
617
  const textHeight = LABEL_FONT_SIZE * 1.2;
598
618
  const isStacked = mark.cornerRadius === 0;
@@ -648,7 +668,7 @@ function computeBarLabels(marks, _chartArea, density = "auto") {
648
668
  // src/charts/bar/index.ts
649
669
  var barRenderer = (spec, scales, chartArea, strategy, _theme) => {
650
670
  const marks = computeBarMarks(spec, scales, chartArea, strategy);
651
- const labels = computeBarLabels(marks, chartArea, spec.labels.density);
671
+ const labels = computeBarLabels(marks, chartArea, spec.labels.density, spec.labels.format);
652
672
  for (let i = 0; i < marks.length && i < labels.length; i++) {
653
673
  marks[i].label = labels[i];
654
674
  }
@@ -813,24 +833,34 @@ function computeStackedColumns(data, categoryField, valueField, colorField, xSca
813
833
  }
814
834
 
815
835
  // src/charts/column/labels.ts
816
- import { estimateTextWidth as estimateTextWidth3, resolveCollisions as resolveCollisions2 } from "@opendata-ai/openchart-core";
836
+ import {
837
+ buildD3Formatter as buildD3Formatter2,
838
+ estimateTextWidth as estimateTextWidth3,
839
+ resolveCollisions as resolveCollisions2
840
+ } from "@opendata-ai/openchart-core";
817
841
  var LABEL_FONT_SIZE2 = 10;
818
842
  var LABEL_FONT_WEIGHT2 = 600;
819
843
  var LABEL_OFFSET_Y = 6;
820
- function computeColumnLabels(marks, _chartArea, density = "auto") {
844
+ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat) {
821
845
  if (density === "none") return [];
822
846
  const targetMarks = density === "endpoints" && marks.length > 1 ? [marks[0], marks[marks.length - 1]] : marks;
847
+ const formatter = buildD3Formatter2(labelFormat);
823
848
  const candidates = [];
824
849
  for (const mark of targetMarks) {
825
850
  const ariaLabel = mark.aria.label;
826
851
  const lastColon = ariaLabel.lastIndexOf(":");
827
- const valuePart = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
828
- if (!valuePart) continue;
852
+ const rawValue = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
853
+ if (!rawValue) continue;
854
+ let valuePart = rawValue;
855
+ if (formatter) {
856
+ const num = Number(rawValue.replace(/[^0-9.-]/g, ""));
857
+ if (!Number.isNaN(num)) valuePart = formatter(num);
858
+ }
829
859
  const numericValue = parseFloat(valuePart);
830
860
  const isNegative = Number.isFinite(numericValue) && numericValue < 0;
831
861
  const textWidth = estimateTextWidth3(valuePart, LABEL_FONT_SIZE2, LABEL_FONT_WEIGHT2);
832
862
  const textHeight = LABEL_FONT_SIZE2 * 1.2;
833
- const anchorX = mark.x + mark.width / 2 - textWidth / 2;
863
+ const anchorX = mark.x + mark.width / 2;
834
864
  const anchorY = isNegative ? mark.y + mark.height + LABEL_OFFSET_Y : mark.y - LABEL_OFFSET_Y - textHeight;
835
865
  candidates.push({
836
866
  text: valuePart,
@@ -866,7 +896,7 @@ function computeColumnLabels(marks, _chartArea, density = "auto") {
866
896
  // src/charts/column/index.ts
867
897
  var columnRenderer = (spec, scales, chartArea, strategy, _theme) => {
868
898
  const marks = computeColumnMarks(spec, scales, chartArea, strategy);
869
- const labels = computeColumnLabels(marks, chartArea, spec.labels.density);
899
+ const labels = computeColumnLabels(marks, chartArea, spec.labels.density, spec.labels.format);
870
900
  for (let i = 0; i < marks.length && i < labels.length; i++) {
871
901
  marks[i].label = labels[i];
872
902
  }
@@ -2804,7 +2834,12 @@ function compileGraph(spec, options) {
2804
2834
  var DEFAULT_COLLISION_PADDING = 5;
2805
2835
 
2806
2836
  // src/layout/axes.ts
2807
- import { abbreviateNumber as abbreviateNumber3, formatDate, formatNumber as formatNumber3 } from "@opendata-ai/openchart-core";
2837
+ import {
2838
+ abbreviateNumber as abbreviateNumber3,
2839
+ buildD3Formatter as buildD3Formatter3,
2840
+ formatDate,
2841
+ formatNumber as formatNumber3
2842
+ } from "@opendata-ai/openchart-core";
2808
2843
  var TICK_COUNTS = {
2809
2844
  full: 8,
2810
2845
  reduced: 5,
@@ -2864,7 +2899,10 @@ function formatTickLabel(value, resolvedScale) {
2864
2899
  }
2865
2900
  if (resolvedScale.type === "linear" || resolvedScale.type === "log") {
2866
2901
  const num = value;
2867
- if (formatStr) return formatNumber3(num);
2902
+ if (formatStr) {
2903
+ const fmt = buildD3Formatter3(formatStr);
2904
+ if (fmt) return fmt(num);
2905
+ }
2868
2906
  if (Math.abs(num) >= 1e3) return abbreviateNumber3(num);
2869
2907
  return formatNumber3(num);
2870
2908
  }
@@ -2986,7 +3024,8 @@ function computeDimensions(spec, options, legendLayout, theme) {
2986
3024
  bottom: padding + chrome.bottomHeight + xAxisHeight,
2987
3025
  left: padding + (isRadial ? padding : axisMargin)
2988
3026
  };
2989
- if (spec.type === "line" || spec.type === "area") {
3027
+ const labelDensity = spec.labels.density;
3028
+ if ((spec.type === "line" || spec.type === "area") && labelDensity !== "none") {
2990
3029
  const colorField = encoding.color?.field;
2991
3030
  if (colorField) {
2992
3031
  let maxLabelWidth = 0;