@opendata-ai/openchart-engine 7.1.4 → 7.2.1

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.d.ts CHANGED
@@ -298,7 +298,7 @@ interface NormalizedChartSpec {
298
298
  metrics?: _opendata_ai_openchart_core.Metric[];
299
299
  annotations: Annotation[];
300
300
  /** Normalized label configuration with defaults applied. density, format, and prefix are always set; offsets and color stay optional. */
301
- labels: Required<Pick<LabelConfig, 'density' | 'format' | 'prefix'>> & Pick<LabelConfig, 'offsets' | 'color'>;
301
+ labels: Required<Pick<LabelConfig, 'density' | 'format' | 'prefix'>> & Pick<LabelConfig, 'offsets' | 'color' | 'fontSize' | 'suffix'>;
302
302
  /** Legend configuration (position override). */
303
303
  legend?: LegendConfig;
304
304
  /** Right-side endpoint labels column config (multi-series line/area only). */
package/dist/index.js CHANGED
@@ -1778,7 +1778,8 @@ var LABEL_FONT_SIZE = 11;
1778
1778
  var LABEL_FONT_WEIGHT = 600;
1779
1779
  var LABEL_PADDING = 6;
1780
1780
  var MIN_WIDTH_FOR_INSIDE_LABEL = 40;
1781
- function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField, labelColor, darkMode = false) {
1781
+ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField, labelColor, darkMode = false, fontSize, labelSuffix) {
1782
+ const FONT_SIZE = fontSize ?? LABEL_FONT_SIZE;
1782
1783
  const targetMarks = filterByDensity(marks, density);
1783
1784
  const candidates = [];
1784
1785
  const fitsInSegment = [];
@@ -1804,8 +1805,9 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
1804
1805
  }
1805
1806
  }
1806
1807
  if (labelPrefix) valuePart = labelPrefix + valuePart;
1807
- const textWidth = estimateTextWidth4(valuePart, LABEL_FONT_SIZE, LABEL_FONT_WEIGHT);
1808
- const textHeight = LABEL_FONT_SIZE * 1.2;
1808
+ if (labelSuffix) valuePart = valuePart + labelSuffix;
1809
+ const textWidth = estimateTextWidth4(valuePart, FONT_SIZE, LABEL_FONT_WEIGHT);
1810
+ const textHeight = FONT_SIZE * 1.2;
1809
1811
  const isStacked2 = mark.stackGroup !== void 0;
1810
1812
  const isInside = mark.width >= MIN_WIDTH_FOR_INSIDE_LABEL;
1811
1813
  const isNegative = Number.isFinite(rawNum) ? rawNum < 0 : false;
@@ -1850,7 +1852,7 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
1850
1852
  priority: "data",
1851
1853
  style: {
1852
1854
  fontFamily: "system-ui, -apple-system, sans-serif",
1853
- fontSize: LABEL_FONT_SIZE,
1855
+ fontSize: FONT_SIZE,
1854
1856
  fontWeight: LABEL_FONT_WEIGHT,
1855
1857
  fill,
1856
1858
  lineHeight: 1.2,
@@ -1909,7 +1911,9 @@ var barRenderer = (spec, scales, chartArea, strategy, theme) => {
1909
1911
  spec.labels.prefix,
1910
1912
  valueField,
1911
1913
  spec.labels.color,
1912
- theme.isDark
1914
+ theme.isDark,
1915
+ spec.labels.fontSize,
1916
+ spec.labels.suffix
1913
1917
  );
1914
1918
  for (let i = 0; i < marks.length && i < labels.length; i++) {
1915
1919
  marks[i].label = labels[i];
@@ -2214,7 +2218,8 @@ import {
2214
2218
  var LABEL_FONT_SIZE2 = 10;
2215
2219
  var LABEL_FONT_WEIGHT2 = 600;
2216
2220
  var LABEL_OFFSET_Y = 8;
2217
- function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField, labelColor) {
2221
+ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField, labelColor, fontSize, labelSuffix) {
2222
+ const FONT_SIZE = fontSize ?? LABEL_FONT_SIZE2;
2218
2223
  const targetMarks = filterByDensity(marks, density);
2219
2224
  const formatter = buildD3Formatter2(labelFormat);
2220
2225
  const candidates = [];
@@ -2238,11 +2243,12 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, l
2238
2243
  valuePart = rawValue;
2239
2244
  }
2240
2245
  }
2241
- if (labelPrefix) valuePart = labelPrefix + valuePart;
2242
2246
  const numericValue = parseFloat(valuePart);
2243
2247
  const isNegative = Number.isFinite(numericValue) && numericValue < 0;
2244
- const textWidth = estimateTextWidth5(valuePart, LABEL_FONT_SIZE2, LABEL_FONT_WEIGHT2);
2245
- const textHeight = LABEL_FONT_SIZE2 * 1.2;
2248
+ if (labelPrefix) valuePart = labelPrefix + valuePart;
2249
+ if (labelSuffix) valuePart = valuePart + labelSuffix;
2250
+ const textWidth = estimateTextWidth5(valuePart, FONT_SIZE, LABEL_FONT_WEIGHT2);
2251
+ const textHeight = FONT_SIZE * 1.2;
2246
2252
  const anchorX = mark.x + mark.width / 2;
2247
2253
  const anchorY = isNegative ? mark.y + mark.height + LABEL_OFFSET_Y : mark.y - LABEL_OFFSET_Y - textHeight;
2248
2254
  candidates.push({
@@ -2254,7 +2260,7 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, l
2254
2260
  priority: "data",
2255
2261
  style: {
2256
2262
  fontFamily: "system-ui, -apple-system, sans-serif",
2257
- fontSize: LABEL_FONT_SIZE2,
2263
+ fontSize: FONT_SIZE,
2258
2264
  fontWeight: LABEL_FONT_WEIGHT2,
2259
2265
  fill: labelColor ?? getRepresentativeColor2(mark.fill),
2260
2266
  lineHeight: 1.2,
@@ -2287,7 +2293,9 @@ var columnRenderer = (spec, scales, chartArea, strategy, _theme) => {
2287
2293
  spec.labels.format,
2288
2294
  spec.labels.prefix,
2289
2295
  valueField,
2290
- spec.labels.color
2296
+ spec.labels.color,
2297
+ spec.labels.fontSize,
2298
+ spec.labels.suffix
2291
2299
  );
2292
2300
  for (let i = 0; i < marks.length && i < labels.length; i++) {
2293
2301
  marks[i].label = labels[i];
@@ -3924,6 +3932,7 @@ function computeLineLabels(marks, strategy, density = "auto", labelOffsets, spec
3924
3932
  }
3925
3933
 
3926
3934
  // src/charts/line/index.ts
3935
+ var AREA_POINT_RADIUS = 3;
3927
3936
  var lineRenderer = (spec, scales, chartArea, strategy, _theme) => {
3928
3937
  const marks = computeLineMarks(spec, scales, chartArea, strategy);
3929
3938
  const lineMarks = marks.filter((m) => m.type === "line");
@@ -3949,7 +3958,8 @@ var areaRenderer = (spec, scales, chartArea, strategy, theme) => {
3949
3958
  const encoding = spec.encoding;
3950
3959
  const hasColor = !!(encoding.color && "field" in encoding.color);
3951
3960
  const lines = hasColor ? linesFromAreas(areas) : computeLineMarks(spec, scales, chartArea, strategy);
3952
- return [...areas, ...lines];
3961
+ const points = hasColor && spec.markDef.point ? pointsFromAreas(areas, spec.markDef.point) : [];
3962
+ return [...areas, ...lines, ...points];
3953
3963
  };
3954
3964
  function linesFromAreas(areas) {
3955
3965
  return areas.map((a) => ({
@@ -3964,6 +3974,34 @@ function linesFromAreas(areas) {
3964
3974
  aria: { label: `${a.seriesKey ?? "Series"}: line with ${a.topPoints.length} data points` }
3965
3975
  }));
3966
3976
  }
3977
+ function pointsFromAreas(areas, pointMode) {
3978
+ const isTransparent = pointMode === "transparent";
3979
+ const isEndpoints = pointMode === "endpoints";
3980
+ const points = [];
3981
+ for (const a of areas) {
3982
+ const stroke = getRepresentativeColor6(a.fill);
3983
+ const lastIdx = a.topPoints.length - 1;
3984
+ for (let i = 0; i < a.topPoints.length; i++) {
3985
+ const pt = a.topPoints[i];
3986
+ const isEndpoint = i === 0 || i === lastIdx;
3987
+ const visible = !isTransparent && (!isEndpoints || isEndpoint);
3988
+ const hollow = isEndpoints && visible;
3989
+ points.push({
3990
+ type: "point",
3991
+ cx: pt.x,
3992
+ cy: pt.y,
3993
+ r: visible ? AREA_POINT_RADIUS : 0,
3994
+ fill: hollow ? "transparent" : stroke,
3995
+ stroke: hollow ? stroke : visible ? "#ffffff" : "transparent",
3996
+ strokeWidth: visible ? 1.5 : 0,
3997
+ fillOpacity: isTransparent ? 0 : 1,
3998
+ data: a.data[i] ?? {},
3999
+ aria: { decorative: true }
4000
+ });
4001
+ }
4002
+ }
4003
+ return points;
4004
+ }
3967
4005
 
3968
4006
  // src/charts/pie/compute.ts
3969
4007
  import { isConditionalDef, isGradientDef as isGradientDef4 } from "@opendata-ai/openchart-core";
@@ -7287,8 +7325,10 @@ function normalizeLabels(labels) {
7287
7325
  density: labels.density ?? "auto",
7288
7326
  format: labels.format ?? "",
7289
7327
  prefix: labels.prefix ?? "",
7328
+ suffix: labels.suffix,
7290
7329
  offsets: labels.offsets,
7291
- color: labels.color
7330
+ color: labels.color,
7331
+ fontSize: labels.fontSize
7292
7332
  };
7293
7333
  }
7294
7334
  function normalizeChartSpec(spec, warnings) {
@@ -9758,22 +9798,23 @@ var TEMPORAL_SCALE_TYPES = /* @__PURE__ */ new Set(["time", "utc"]);
9758
9798
  function formatTickLabel(value2, resolvedScale) {
9759
9799
  const axisConfig = resolvedScale.channel.axis || void 0;
9760
9800
  const formatStr = axisConfig?.format;
9801
+ const suffix = axisConfig?.labelSuffix ?? "";
9761
9802
  if (TEMPORAL_SCALE_TYPES.has(resolvedScale.type)) {
9762
9803
  const temporalFmt = buildTemporalFormatter(formatStr);
9763
- if (temporalFmt) return temporalFmt(value2);
9804
+ if (temporalFmt) return temporalFmt(value2) + suffix;
9764
9805
  const useUtc = resolvedScale.type === "utc";
9765
- return formatDate(value2, void 0, void 0, useUtc);
9806
+ return formatDate(value2, void 0, void 0, useUtc) + suffix;
9766
9807
  }
9767
9808
  if (NUMERIC_SCALE_TYPES.has(resolvedScale.type)) {
9768
9809
  const num = value2;
9769
9810
  if (formatStr) {
9770
9811
  const fmt = buildD3Formatter5(formatStr);
9771
- if (fmt) return fmt(num);
9812
+ if (fmt) return fmt(num) + suffix;
9772
9813
  }
9773
- if (Math.abs(num) >= 1e3) return abbreviateNumber2(num);
9774
- return formatNumber3(num);
9814
+ if (Math.abs(num) >= 1e3) return abbreviateNumber2(num) + suffix;
9815
+ return formatNumber3(num) + suffix;
9775
9816
  }
9776
- return String(value2);
9817
+ return String(value2) + suffix;
9777
9818
  }
9778
9819
  function continuousTicks(resolvedScale, density, targetCount) {
9779
9820
  const scale = resolvedScale.scale;
@@ -10426,7 +10467,8 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
10426
10467
  const labelHeight = Math.min(rotatedHeight, 120);
10427
10468
  xAxisHeight = hasXAxisLabel ? labelHeight + 20 : labelHeight;
10428
10469
  } else {
10429
- xAxisHeight = hasXAxisLabel ? 48 : 26;
10470
+ const base = theme.spacing.xAxisHeight;
10471
+ xAxisHeight = hasXAxisLabel ? base + 22 : base;
10430
10472
  }
10431
10473
  const yAxisCfgPre = encoding.y?.axis ?? void 0;
10432
10474
  const yTickPositionExplicitPre = yAxisCfgPre?.tickPosition;
@@ -10966,7 +11008,7 @@ function buildBandScale(channel, data, rangeStart, rangeEnd) {
10966
11008
  }
10967
11009
  function buildPointScale(channel, data, rangeStart, rangeEnd) {
10968
11010
  const values = channel.scale?.domain ? channel.scale.domain : applyCategoricalSort(uniqueStrings(fieldValues(data, channel.field)), channel.sort);
10969
- const padding = channel.scale?.padding ?? 0.5;
11011
+ const padding = channel.scale?.padding ?? channel.scale?.paddingOuter ?? 0.5;
10970
11012
  const scale = point4().domain(values).range([rangeStart, rangeEnd]).padding(padding);
10971
11013
  if (channel.scale?.reverse) {
10972
11014
  const [r0, r1] = scale.range();