@opendata-ai/openchart-vanilla 6.7.1 → 6.9.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 +38 -8
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/svg-renderer.test.ts +80 -0
- package/src/export.ts +24 -4
- package/src/sankey-mount.ts +25 -2
- package/src/sankey-renderer.ts +9 -3
- package/src/svg-renderer.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -12,6 +12,10 @@ function getSVGDimensions(svg) {
|
|
|
12
12
|
}
|
|
13
13
|
return { width: 600, height: 400 };
|
|
14
14
|
}
|
|
15
|
+
function ensureSVGDimensions(svgString, width, height) {
|
|
16
|
+
if (/^<svg[^>]*\swidth\s*=/.test(svgString)) return svgString;
|
|
17
|
+
return svgString.replace(/^(<svg)/, `$1 width="${width}" height="${height}"`);
|
|
18
|
+
}
|
|
15
19
|
function collectUsedFonts(svgElement) {
|
|
16
20
|
const fonts = /* @__PURE__ */ new Map();
|
|
17
21
|
const textElements = svgElement.querySelectorAll("text");
|
|
@@ -135,8 +139,8 @@ async function exportPNG(svgElement, options) {
|
|
|
135
139
|
if (shouldEmbed) {
|
|
136
140
|
await embedFonts(svgElement);
|
|
137
141
|
}
|
|
138
|
-
const svgString = exportSVG(svgElement);
|
|
139
142
|
const { width, height } = getSVGDimensions(svgElement);
|
|
143
|
+
const svgString = ensureSVGDimensions(exportSVG(svgElement), width, height);
|
|
140
144
|
const canvas = document.createElement("canvas");
|
|
141
145
|
canvas.width = width * dpi;
|
|
142
146
|
canvas.height = height * dpi;
|
|
@@ -150,7 +154,7 @@ async function exportPNG(svgElement, options) {
|
|
|
150
154
|
const url = URL.createObjectURL(blob);
|
|
151
155
|
return new Promise((resolve, reject) => {
|
|
152
156
|
img.onload = () => {
|
|
153
|
-
ctx.drawImage(img, 0, 0);
|
|
157
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
154
158
|
URL.revokeObjectURL(url);
|
|
155
159
|
canvas.toBlob((result) => {
|
|
156
160
|
if (result) {
|
|
@@ -174,8 +178,8 @@ async function exportJPG(svgElement, options) {
|
|
|
174
178
|
if (shouldEmbed) {
|
|
175
179
|
await embedFonts(svgElement);
|
|
176
180
|
}
|
|
177
|
-
const svgString = exportSVG(svgElement);
|
|
178
181
|
const { width, height } = getSVGDimensions(svgElement);
|
|
182
|
+
const svgString = ensureSVGDimensions(exportSVG(svgElement), width, height);
|
|
179
183
|
const canvas = document.createElement("canvas");
|
|
180
184
|
canvas.width = width * dpi;
|
|
181
185
|
canvas.height = height * dpi;
|
|
@@ -191,7 +195,7 @@ async function exportJPG(svgElement, options) {
|
|
|
191
195
|
const url = URL.createObjectURL(blob);
|
|
192
196
|
return new Promise((resolve, reject) => {
|
|
193
197
|
img.onload = () => {
|
|
194
|
-
ctx.drawImage(img, 0, 0);
|
|
198
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
195
199
|
URL.revokeObjectURL(url);
|
|
196
200
|
canvas.toBlob(
|
|
197
201
|
(result) => {
|
|
@@ -3356,6 +3360,12 @@ function applyTextStyle(el, style) {
|
|
|
3356
3360
|
}
|
|
3357
3361
|
function wrapText(text, fontSize, fontWeight, maxWidth) {
|
|
3358
3362
|
if (maxWidth <= 0) return [text];
|
|
3363
|
+
const segments = text.split("\n");
|
|
3364
|
+
if (segments.length > 1) {
|
|
3365
|
+
return segments.flatMap(
|
|
3366
|
+
(segment) => segment.length === 0 ? [""] : wrapText(segment, fontSize, fontWeight, maxWidth)
|
|
3367
|
+
);
|
|
3368
|
+
}
|
|
3359
3369
|
const AVG_CHAR_WIDTH = 0.55;
|
|
3360
3370
|
const WEIGHT_FACTORS = {
|
|
3361
3371
|
100: 0.9,
|
|
@@ -6654,11 +6664,14 @@ function buildNodePositionMap(nodes) {
|
|
|
6654
6664
|
}
|
|
6655
6665
|
return map;
|
|
6656
6666
|
}
|
|
6667
|
+
function sanitizeId(s) {
|
|
6668
|
+
return s.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
6669
|
+
}
|
|
6657
6670
|
function renderGradientDefs(defs, links, nodePositions) {
|
|
6658
6671
|
for (let i = 0; i < links.length; i++) {
|
|
6659
6672
|
const link = links[i];
|
|
6660
6673
|
if (link.sourceColor === link.targetColor) continue;
|
|
6661
|
-
const gradId = `oc-sg-${link.sourceId}-${link.targetId}-${i}`;
|
|
6674
|
+
const gradId = `oc-sg-${sanitizeId(link.sourceId)}-${sanitizeId(link.targetId)}-${i}`;
|
|
6662
6675
|
const gradient = createSVGElement2("linearGradient");
|
|
6663
6676
|
gradient.setAttribute("id", gradId);
|
|
6664
6677
|
gradient.setAttribute("gradientUnits", "userSpaceOnUse");
|
|
@@ -6684,7 +6697,7 @@ function renderLinks(parent, links, _nodePositions, animation) {
|
|
|
6684
6697
|
const link = links[i];
|
|
6685
6698
|
const linkG = createSVGElement2("g");
|
|
6686
6699
|
linkG.setAttribute("class", "oc-sankey-link");
|
|
6687
|
-
linkG.setAttribute("data-mark-id", `link-${link.sourceId}-${link.targetId}`);
|
|
6700
|
+
linkG.setAttribute("data-mark-id", `link-${link.sourceId}-${link.targetId}-${i}`);
|
|
6688
6701
|
linkG.setAttribute("data-source", link.sourceId);
|
|
6689
6702
|
linkG.setAttribute("data-target", link.targetId);
|
|
6690
6703
|
if (link.aria?.label) {
|
|
@@ -6696,7 +6709,7 @@ function renderLinks(parent, links, _nodePositions, animation) {
|
|
|
6696
6709
|
path.setAttribute("stroke", "none");
|
|
6697
6710
|
path.setAttribute("fill-opacity", String(link.fillOpacity));
|
|
6698
6711
|
if (link.sourceColor !== link.targetColor) {
|
|
6699
|
-
const gradId = `oc-sg-${link.sourceId}-${link.targetId}-${i}`;
|
|
6712
|
+
const gradId = `oc-sg-${sanitizeId(link.sourceId)}-${sanitizeId(link.targetId)}-${i}`;
|
|
6700
6713
|
path.setAttribute("fill", `url(#${gradId})`);
|
|
6701
6714
|
} else {
|
|
6702
6715
|
path.setAttribute("fill", link.sourceColor);
|
|
@@ -6811,6 +6824,7 @@ function resolveDarkMode3(mode) {
|
|
|
6811
6824
|
}
|
|
6812
6825
|
var HIGHLIGHT_OPACITY = 0.7;
|
|
6813
6826
|
var DIM_OPACITY = 0.15;
|
|
6827
|
+
var NODE_DIM_OPACITY = 0.2;
|
|
6814
6828
|
function createSankey(container, spec, options) {
|
|
6815
6829
|
let currentSpec = spec;
|
|
6816
6830
|
let currentLayout;
|
|
@@ -6945,6 +6959,7 @@ function createSankey(container, spec, options) {
|
|
|
6945
6959
|
return link?.data ?? {};
|
|
6946
6960
|
}
|
|
6947
6961
|
function highlightConnectedLinks(svg, nodeId, _layout) {
|
|
6962
|
+
const connectedNodeIds = /* @__PURE__ */ new Set([nodeId]);
|
|
6948
6963
|
const linkElements = svg.querySelectorAll(".oc-sankey-link");
|
|
6949
6964
|
for (const el of linkElements) {
|
|
6950
6965
|
const source = el.getAttribute("data-source");
|
|
@@ -6953,6 +6968,17 @@ function createSankey(container, spec, options) {
|
|
|
6953
6968
|
if (!path) continue;
|
|
6954
6969
|
const isConnected = source === nodeId || target === nodeId;
|
|
6955
6970
|
path.setAttribute("fill-opacity", String(isConnected ? HIGHLIGHT_OPACITY : DIM_OPACITY));
|
|
6971
|
+
if (isConnected) {
|
|
6972
|
+
if (source) connectedNodeIds.add(source);
|
|
6973
|
+
if (target) connectedNodeIds.add(target);
|
|
6974
|
+
}
|
|
6975
|
+
}
|
|
6976
|
+
const nodeElements = svg.querySelectorAll(".oc-sankey-node");
|
|
6977
|
+
for (const el of nodeElements) {
|
|
6978
|
+
const nid = el.getAttribute("data-node-id");
|
|
6979
|
+
if (!nid) continue;
|
|
6980
|
+
const isConnected = connectedNodeIds.has(nid);
|
|
6981
|
+
el.style.opacity = isConnected ? "1" : String(NODE_DIM_OPACITY);
|
|
6956
6982
|
}
|
|
6957
6983
|
}
|
|
6958
6984
|
function resetLinkOpacity(svg, layout) {
|
|
@@ -6963,7 +6989,11 @@ function createSankey(container, spec, options) {
|
|
|
6963
6989
|
const source = el.getAttribute("data-source");
|
|
6964
6990
|
const target = el.getAttribute("data-target");
|
|
6965
6991
|
const link = layout.links.find((l) => l.sourceId === source && l.targetId === target);
|
|
6966
|
-
path.setAttribute("fill-opacity", String(link?.fillOpacity ?? 0.
|
|
6992
|
+
path.setAttribute("fill-opacity", String(link?.fillOpacity ?? 0.5));
|
|
6993
|
+
}
|
|
6994
|
+
const nodeElements = svg.querySelectorAll(".oc-sankey-node");
|
|
6995
|
+
for (const el of nodeElements) {
|
|
6996
|
+
el.style.opacity = "1";
|
|
6967
6997
|
}
|
|
6968
6998
|
}
|
|
6969
6999
|
function render() {
|