@opendata-ai/openchart-vanilla 7.2.3 → 7.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 CHANGED
@@ -467,6 +467,7 @@ function csvEscape(value) {
467
467
  }
468
468
 
469
469
  // src/measure-text.ts
470
+ import { estimateTextWidth } from "@opendata-ai/openchart-core";
470
471
  function createMeasureText() {
471
472
  let canvas = null;
472
473
  let ctx = null;
@@ -476,7 +477,10 @@ function createMeasureText() {
476
477
  ctx = canvas.getContext("2d");
477
478
  }
478
479
  if (!ctx) {
479
- return { width: text.length * fontSize * 0.6, height: fontSize * 1.2 };
480
+ return {
481
+ width: estimateTextWidth(text, fontSize, fontWeight ?? 400),
482
+ height: fontSize * 1.2
483
+ };
480
484
  }
481
485
  const weight = fontWeight ?? 400;
482
486
  ctx.font = `${weight} ${fontSize}px Inter, sans-serif`;
@@ -4993,7 +4997,6 @@ function resolveMarkFill(fill, gradientMap) {
4993
4997
  }
4994
4998
 
4995
4999
  // src/renderers/svg-dom.ts
4996
- import { estimateTextWidth } from "@opendata-ai/openchart-core";
4997
5000
  var SVG_NS3 = "http://www.w3.org/2000/svg";
4998
5001
  var XLINK_NS2 = "http://www.w3.org/1999/xlink";
4999
5002
  function createSVGElement2(tag) {
@@ -5020,23 +5023,6 @@ function applyTextStyle(el, style) {
5020
5023
  el.setAttribute("font-variant", style.fontVariant);
5021
5024
  }
5022
5025
  }
5023
- function computeXAxisExtent(layout) {
5024
- const xAxis = layout.axes.x;
5025
- if (!xAxis) return 0;
5026
- if (xAxis.tickAngle && Math.abs(xAxis.tickAngle) > 10) {
5027
- const fontSize = xAxis.tickLabelStyle.fontSize;
5028
- const fontWeight = xAxis.tickLabelStyle.fontWeight;
5029
- const angleRad = Math.abs(xAxis.tickAngle) * (Math.PI / 180);
5030
- let maxLabelWidth = 40;
5031
- for (const tick of xAxis.ticks) {
5032
- const w = estimateTextWidth(tick.label, fontSize, fontWeight);
5033
- if (w > maxLabelWidth) maxLabelWidth = w;
5034
- }
5035
- const rotatedHeight = Math.min(maxLabelWidth * Math.sin(angleRad) + 6, 120);
5036
- return xAxis.label ? rotatedHeight + 20 : rotatedHeight;
5037
- }
5038
- return xAxis.label ? 48 : 26;
5039
- }
5040
5026
 
5041
5027
  // src/renderers/annotations.ts
5042
5028
  function renderCurvedArrow(parent, from, to, stroke) {
@@ -5198,10 +5184,6 @@ function renderAnnotation(parent, annotation, index2, bgColor) {
5198
5184
  const lineHeight = fontSize * (annotation.label.style.lineHeight ?? 1.3);
5199
5185
  const isMultiLine = lines.length > 1;
5200
5186
  if (isMultiLine) {
5201
- const isDropLine = annotation.label.connector?.style === "drop-line";
5202
- if (!isDropLine) {
5203
- text.setAttribute("text-anchor", "middle");
5204
- }
5205
5187
  for (let i = 0; i < lines.length; i++) {
5206
5188
  const tspan = createSVGElement2("tspan");
5207
5189
  setAttrs2(tspan, { x: annotation.label.x, dy: i === 0 ? 0 : lineHeight });
@@ -5212,18 +5194,33 @@ function renderAnnotation(parent, annotation, index2, bgColor) {
5212
5194
  text.textContent = annotation.label.text;
5213
5195
  }
5214
5196
  if (annotation.label.background) {
5215
- const charWidth = fontSize * 0.55;
5216
- const maxLineWidth = Math.max(...lines.map((l) => l.length)) * charWidth;
5217
- const totalHeight = lines.length * lineHeight;
5218
5197
  const pad = 3;
5219
- const bgX = isMultiLine ? annotation.label.x - maxLineWidth / 2 - pad : annotation.label.x - pad;
5198
+ let bgX;
5199
+ let bgY;
5200
+ let bgW;
5201
+ let bgH;
5202
+ if (annotation.label.bounds) {
5203
+ const b = annotation.label.bounds;
5204
+ bgX = b.x - pad;
5205
+ bgY = b.y - pad;
5206
+ bgW = b.width + pad * 2;
5207
+ bgH = b.height + pad * 2;
5208
+ } else {
5209
+ const charWidth = fontSize * 0.55;
5210
+ const maxLineWidth = Math.max(...lines.map((l) => l.length)) * charWidth;
5211
+ const totalHeight = lines.length * lineHeight;
5212
+ bgX = isMultiLine ? annotation.label.x - maxLineWidth / 2 - pad : annotation.label.x - pad;
5213
+ bgY = annotation.label.y - fontSize + (lineHeight - fontSize) / 2 - pad;
5214
+ bgW = maxLineWidth + pad * 2;
5215
+ bgH = totalHeight + pad * 2;
5216
+ }
5220
5217
  const bgRect = createSVGElement2("rect");
5221
5218
  bgRect.setAttribute("class", "oc-annotation-bg");
5222
5219
  setAttrs2(bgRect, {
5223
5220
  x: bgX,
5224
- y: annotation.label.y - fontSize + (lineHeight - fontSize) / 2 - pad,
5225
- width: maxLineWidth + pad * 2,
5226
- height: totalHeight + pad * 2,
5221
+ y: bgY,
5222
+ width: bgW,
5223
+ height: bgH,
5227
5224
  fill: annotation.label.background,
5228
5225
  rx: 2
5229
5226
  });
@@ -5446,7 +5443,18 @@ function renderAxis(parent, axis, orientation, layout) {
5446
5443
  axisLabel.setAttribute("class", "oc-axis-title");
5447
5444
  applyTextStyle(axisLabel, axis.labelStyle);
5448
5445
  axisLabel.textContent = axis.label;
5449
- if (orientation === "x") {
5446
+ const tp = axis.titlePosition;
5447
+ if (tp) {
5448
+ const attrs = {
5449
+ x: tp.x,
5450
+ y: tp.y,
5451
+ "text-anchor": "middle"
5452
+ };
5453
+ if (tp.angle) {
5454
+ attrs.transform = `rotate(${tp.angle}, ${tp.x}, ${tp.y})`;
5455
+ }
5456
+ setAttrs2(axisLabel, attrs);
5457
+ } else if (orientation === "x") {
5450
5458
  let titleY = area.y + area.height + 35;
5451
5459
  if (axis.tickAngle && Math.abs(axis.tickAngle) > 10) {
5452
5460
  const angleRad = Math.abs(axis.tickAngle) * (Math.PI / 180);
@@ -5523,8 +5531,7 @@ function renderBrand(parent, layout) {
5523
5531
  const rightEdge = width - padding;
5524
5532
  const fill = layout.theme.colors.axis;
5525
5533
  const { chrome } = layout;
5526
- const xAxisExtent = computeXAxisExtent(layout);
5527
- const bottomOffset = layout.area.y + layout.area.height + xAxisExtent;
5534
+ const bottomOffset = chrome.bottomAnchorY ?? layout.area.y + layout.area.height;
5528
5535
  const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
5529
5536
  const { legend } = layout;
5530
5537
  const bottomLegendOffset = legend.position === "bottom" && legend.bounds.height > 0 ? legend.bounds.height + 8 : 0;
@@ -5620,8 +5627,7 @@ function renderChrome2(parent, layout) {
5620
5627
  if (chrome.subtitle) {
5621
5628
  renderChromeElement(g, chrome.subtitle, "oc-subtitle", "subtitle", measureText);
5622
5629
  }
5623
- const xAxisExtent = computeXAxisExtent(layout);
5624
- const bottomOffset = layout.area.y + layout.area.height + xAxisExtent;
5630
+ const bottomOffset = layout.chrome.bottomAnchorY ?? layout.area.y + layout.area.height;
5625
5631
  if (chrome.source) {
5626
5632
  renderChromeElement(
5627
5633
  g,
@@ -5774,11 +5780,16 @@ function renderLegend(parent, legend) {
5774
5780
  g.setAttribute("role", "list");
5775
5781
  g.setAttribute("aria-label", "Chart legend");
5776
5782
  const isHorizontal = legend.position === "top" || legend.position === "bottom";
5783
+ const positions = "entryPositions" in legend ? legend.entryPositions : void 0;
5777
5784
  let offsetX = legend.bounds.x;
5778
5785
  let offsetY = legend.bounds.y;
5779
5786
  for (let i = 0; i < legend.entries.length; i++) {
5780
5787
  const entry = legend.entries[i];
5781
- if (isHorizontal && i > 0) {
5788
+ const pos = positions?.[i];
5789
+ if (pos) {
5790
+ offsetX = pos.x;
5791
+ offsetY = pos.y;
5792
+ } else if (isHorizontal && i > 0) {
5782
5793
  const labelWidth = estimateTextWidth4(
5783
5794
  entry.label,
5784
5795
  legend.labelStyle.fontSize,
@@ -5859,16 +5870,18 @@ function renderLegend(parent, legend) {
5859
5870
  label.textContent = entry.label;
5860
5871
  entryG.appendChild(label);
5861
5872
  g.appendChild(entryG);
5862
- if (isHorizontal) {
5863
- const labelWidth = estimateTextWidth4(
5864
- entry.label,
5865
- legend.labelStyle.fontSize,
5866
- legend.labelStyle.fontWeight
5867
- );
5868
- const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;
5869
- offsetX += entryWidth;
5870
- } else {
5871
- offsetY += legend.swatchSize + legend.entryGap;
5873
+ if (!pos) {
5874
+ if (isHorizontal) {
5875
+ const labelWidth = estimateTextWidth4(
5876
+ entry.label,
5877
+ legend.labelStyle.fontSize,
5878
+ legend.labelStyle.fontWeight
5879
+ );
5880
+ const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;
5881
+ offsetX += entryWidth;
5882
+ } else {
5883
+ offsetY += "rowHeight" in legend && legend.rowHeight ? legend.rowHeight : legend.swatchSize + legend.entryGap;
5884
+ }
5872
5885
  }
5873
5886
  }
5874
5887
  parent.appendChild(g);