@opendata-ai/openchart-vanilla 2.3.5 → 2.5.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
@@ -2116,6 +2116,23 @@ import { compileChart } from "@opendata-ai/openchart-engine";
2116
2116
  // src/svg-renderer.ts
2117
2117
  import { estimateTextWidth } from "@opendata-ai/openchart-core";
2118
2118
  var SVG_NS = "http://www.w3.org/2000/svg";
2119
+ function computeXAxisExtent(layout) {
2120
+ const xAxis = layout.axes.x;
2121
+ if (!xAxis) return 0;
2122
+ if (xAxis.tickAngle && Math.abs(xAxis.tickAngle) > 10) {
2123
+ const fontSize = xAxis.tickLabelStyle.fontSize;
2124
+ const fontWeight = xAxis.tickLabelStyle.fontWeight;
2125
+ const angleRad = Math.abs(xAxis.tickAngle) * (Math.PI / 180);
2126
+ let maxLabelWidth = 40;
2127
+ for (const tick of xAxis.ticks) {
2128
+ const w = estimateTextWidth(tick.label, fontSize, fontWeight);
2129
+ if (w > maxLabelWidth) maxLabelWidth = w;
2130
+ }
2131
+ const rotatedHeight = Math.min(maxLabelWidth * Math.sin(angleRad) + 6, 120);
2132
+ return xAxis.label ? rotatedHeight + 20 : rotatedHeight;
2133
+ }
2134
+ return xAxis.label ? 48 : 26;
2135
+ }
2119
2136
  function createSVGElement(tag) {
2120
2137
  return document.createElementNS(SVG_NS, tag);
2121
2138
  }
@@ -2160,7 +2177,7 @@ function renderChrome(parent, layout) {
2160
2177
  if (chrome.subtitle) {
2161
2178
  renderChromeElement(g, chrome.subtitle, "viz-subtitle", "subtitle");
2162
2179
  }
2163
- const xAxisExtent = layout.axes.x ? layout.axes.x.label ? 48 : 26 : 0;
2180
+ const xAxisExtent = computeXAxisExtent(layout);
2164
2181
  const bottomOffset = layout.area.y + layout.area.height + xAxisExtent;
2165
2182
  if (chrome.source) {
2166
2183
  renderChromeElement(
@@ -2209,11 +2226,23 @@ function renderAxis(parent, axis, orientation, layout) {
2209
2226
  if (orientation === "x") {
2210
2227
  const label = createSVGElement("text");
2211
2228
  label.setAttribute("class", "viz-axis-tick");
2212
- setAttrs(label, {
2213
- x: tick.position,
2214
- y: area.y + area.height + 14,
2215
- "text-anchor": "middle"
2216
- });
2229
+ if (axis.tickAngle && Math.abs(axis.tickAngle) > 10) {
2230
+ const labelX = tick.position;
2231
+ const labelY = area.y + area.height + 6;
2232
+ setAttrs(label, {
2233
+ x: labelX,
2234
+ y: labelY,
2235
+ "text-anchor": axis.tickAngle < 0 ? "end" : "start",
2236
+ "dominant-baseline": "central",
2237
+ transform: `rotate(${axis.tickAngle}, ${labelX}, ${labelY})`
2238
+ });
2239
+ } else {
2240
+ setAttrs(label, {
2241
+ x: tick.position,
2242
+ y: area.y + area.height + 14,
2243
+ "text-anchor": "middle"
2244
+ });
2245
+ }
2217
2246
  applyTextStyle(label, axis.tickLabelStyle);
2218
2247
  label.textContent = tick.label;
2219
2248
  g.appendChild(label);
@@ -2263,9 +2292,24 @@ function renderAxis(parent, axis, orientation, layout) {
2263
2292
  applyTextStyle(axisLabel, axis.labelStyle);
2264
2293
  axisLabel.textContent = axis.label;
2265
2294
  if (orientation === "x") {
2295
+ let titleY = area.y + area.height + 35;
2296
+ if (axis.tickAngle && Math.abs(axis.tickAngle) > 10) {
2297
+ const angleRad = Math.abs(axis.tickAngle) * (Math.PI / 180);
2298
+ let maxLabelWidth = 40;
2299
+ for (const tick of axis.ticks) {
2300
+ const w = estimateTextWidth(
2301
+ tick.label,
2302
+ axis.tickLabelStyle.fontSize,
2303
+ axis.tickLabelStyle.fontWeight
2304
+ );
2305
+ if (w > maxLabelWidth) maxLabelWidth = w;
2306
+ }
2307
+ const rotatedHeight = Math.min(maxLabelWidth * Math.sin(angleRad) + 6, 120);
2308
+ titleY = area.y + area.height + rotatedHeight + 14;
2309
+ }
2266
2310
  setAttrs(axisLabel, {
2267
2311
  x: area.x + area.width / 2,
2268
- y: area.y + area.height + 35,
2312
+ y: titleY,
2269
2313
  "text-anchor": "middle"
2270
2314
  });
2271
2315
  } else {
@@ -2308,6 +2352,9 @@ function renderLineMark(mark, index) {
2308
2352
  if (mark.strokeDasharray) {
2309
2353
  path.setAttribute("stroke-dasharray", mark.strokeDasharray);
2310
2354
  }
2355
+ if (mark.opacity != null) {
2356
+ path.setAttribute("opacity", String(mark.opacity));
2357
+ }
2311
2358
  g.appendChild(path);
2312
2359
  }
2313
2360
  if (mark.label?.visible) {
@@ -2740,52 +2787,21 @@ function renderLegend(parent, legend) {
2740
2787
  }
2741
2788
  parent.appendChild(g);
2742
2789
  }
2743
- var BRAND_FONT_SIZE = 20;
2790
+ var BRAND_FONT_SIZE = 11;
2744
2791
  var BRAND_MIN_WIDTH2 = 120;
2745
2792
  var BRAND_URL = "https://tryopendata.ai";
2746
2793
  var XLINK_NS = "http://www.w3.org/1999/xlink";
2747
- function brandPosition(layout) {
2794
+ function renderBrand(parent, layout) {
2795
+ if (layout.dimensions.width < BRAND_MIN_WIDTH2) return;
2748
2796
  const { width } = layout.dimensions;
2749
2797
  const padding = layout.theme.spacing.padding;
2750
2798
  const rightEdge = width - padding;
2799
+ const fill = layout.theme.colors.axis;
2751
2800
  const { chrome } = layout;
2752
- const xAxisExtent = layout.axes.x ? layout.axes.x.label ? 48 : 26 : 0;
2801
+ const xAxisExtent = computeXAxisExtent(layout);
2753
2802
  const bottomOffset = layout.area.y + layout.area.height + xAxisExtent;
2754
2803
  const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
2755
2804
  const chromeY = firstBottom ? bottomOffset + firstBottom.y : bottomOffset + layout.theme.spacing.chartToFooter;
2756
- const y = chromeY + BRAND_FONT_SIZE;
2757
- const dataWidth = estimateTextWidth("Data", BRAND_FONT_SIZE, 600);
2758
- const dataX = rightEdge - dataWidth;
2759
- const openX = dataX;
2760
- return { openX, dataX, y, fill: layout.theme.colors.axis };
2761
- }
2762
- function renderBrandOpen(parent, layout) {
2763
- if (layout.dimensions.width < BRAND_MIN_WIDTH2) return;
2764
- const { openX, y, fill } = brandPosition(layout);
2765
- const a = createSVGElement("a");
2766
- a.setAttribute("href", BRAND_URL);
2767
- a.setAttributeNS(XLINK_NS, "xlink:href", BRAND_URL);
2768
- a.setAttribute("target", "_blank");
2769
- a.setAttribute("rel", "noopener");
2770
- a.setAttribute("class", "viz-axis-ref");
2771
- const text = createSVGElement("text");
2772
- setAttrs(text, {
2773
- x: openX,
2774
- y,
2775
- "font-family": layout.theme.fonts.family,
2776
- "font-size": BRAND_FONT_SIZE,
2777
- "font-weight": 500,
2778
- "text-anchor": "end",
2779
- "fill-opacity": 0.55
2780
- });
2781
- text.style.setProperty("fill", fill);
2782
- text.textContent = "Open";
2783
- a.appendChild(text);
2784
- parent.appendChild(a);
2785
- }
2786
- function renderBrandData(parent, layout) {
2787
- if (layout.dimensions.width < BRAND_MIN_WIDTH2) return;
2788
- const { dataX, y, fill } = brandPosition(layout);
2789
2805
  const a = createSVGElement("a");
2790
2806
  a.setAttribute("href", BRAND_URL);
2791
2807
  a.setAttributeNS(XLINK_NS, "xlink:href", BRAND_URL);
@@ -2794,16 +2810,23 @@ function renderBrandData(parent, layout) {
2794
2810
  a.setAttribute("class", "viz-chrome-ref");
2795
2811
  const text = createSVGElement("text");
2796
2812
  setAttrs(text, {
2797
- x: dataX,
2798
- y,
2813
+ x: rightEdge,
2814
+ y: chromeY,
2799
2815
  "font-family": layout.theme.fonts.family,
2800
2816
  "font-size": BRAND_FONT_SIZE,
2801
- "font-weight": 600,
2802
- "text-anchor": "start",
2817
+ "text-anchor": "end",
2818
+ "dominant-baseline": "hanging",
2803
2819
  "fill-opacity": 0.55
2804
2820
  });
2805
2821
  text.style.setProperty("fill", fill);
2806
- text.textContent = "Data";
2822
+ const openSpan = createSVGElement("tspan");
2823
+ openSpan.setAttribute("font-weight", "500");
2824
+ openSpan.textContent = "Open";
2825
+ text.appendChild(openSpan);
2826
+ const dataSpan = createSVGElement("tspan");
2827
+ dataSpan.setAttribute("font-weight", "600");
2828
+ dataSpan.textContent = "Data";
2829
+ text.appendChild(dataSpan);
2807
2830
  a.appendChild(text);
2808
2831
  parent.appendChild(a);
2809
2832
  }
@@ -2847,9 +2870,8 @@ function renderChartSVG(layout, container) {
2847
2870
  svg.appendChild(clippedGroup);
2848
2871
  renderAnnotations(svg, layout);
2849
2872
  renderLegend(svg, layout.legend);
2850
- renderBrandOpen(svg, layout);
2851
2873
  renderChrome(svg, layout);
2852
- renderBrandData(svg, layout);
2874
+ renderBrand(svg, layout);
2853
2875
  container.appendChild(svg);
2854
2876
  return svg;
2855
2877
  }