@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 +59 -46
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/known-bugs-render.test.ts +183 -0
- package/src/measure-text.ts +11 -1
- package/src/renderers/annotations.ts +24 -16
- package/src/renderers/axes.ts +12 -9
- package/src/renderers/brand.ts +2 -3
- package/src/renderers/chrome.ts +2 -5
- package/src/renderers/legend.ts +21 -13
- package/src/renderers/svg-dom.ts +1 -27
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 {
|
|
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
|
-
|
|
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:
|
|
5225
|
-
width:
|
|
5226
|
-
height:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
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);
|