polly-graph 0.1.8 → 0.1.9
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.cjs +158 -116
- package/dist/index.css +14 -6
- package/dist/index.js +158 -116
- package/package.json +2 -3
package/dist/index.cjs
CHANGED
|
@@ -2662,15 +2662,15 @@ function defaultWheelDelta(event) {
|
|
|
2662
2662
|
function defaultTouchable2() {
|
|
2663
2663
|
return navigator.maxTouchPoints || "ontouchstart" in this;
|
|
2664
2664
|
}
|
|
2665
|
-
function defaultConstrain(transform2,
|
|
2666
|
-
var dx0 = transform2.invertX(
|
|
2665
|
+
function defaultConstrain(transform2, extent, translateExtent) {
|
|
2666
|
+
var dx0 = transform2.invertX(extent[0][0]) - translateExtent[0][0], dx1 = transform2.invertX(extent[1][0]) - translateExtent[1][0], dy0 = transform2.invertY(extent[0][1]) - translateExtent[0][1], dy1 = transform2.invertY(extent[1][1]) - translateExtent[1][1];
|
|
2667
2667
|
return transform2.translate(
|
|
2668
2668
|
dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1),
|
|
2669
2669
|
dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1)
|
|
2670
2670
|
);
|
|
2671
2671
|
}
|
|
2672
2672
|
function zoom_default2() {
|
|
2673
|
-
var filter2 = defaultFilter2,
|
|
2673
|
+
var filter2 = defaultFilter2, extent = defaultExtent, constrain = defaultConstrain, wheelDelta = defaultWheelDelta, touchable = defaultTouchable2, scaleExtent = [0, Infinity], translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]], duration = 250, interpolate = zoom_default, listeners = dispatch_default2("start", "zoom", "end"), touchstarting, touchfirst, touchending, touchDelay = 500, wheelDelay = 150, clickDistance2 = 0, tapDistance = 10;
|
|
2674
2674
|
function zoom(selection2) {
|
|
2675
2675
|
selection2.property("__zoom", defaultTransform).on("wheel.zoom", wheeled, { passive: false }).on("mousedown.zoom", mousedowned).on("dblclick.zoom", dblclicked).filter(touchable).on("touchstart.zoom", touchstarted).on("touchmove.zoom", touchmoved).on("touchend.zoom touchcancel.zoom", touchended).style("-webkit-tap-highlight-color", "rgba(0,0,0,0)");
|
|
2676
2676
|
}
|
|
@@ -2693,7 +2693,7 @@ function zoom_default2() {
|
|
|
2693
2693
|
};
|
|
2694
2694
|
zoom.scaleTo = function(selection2, k, p, event) {
|
|
2695
2695
|
zoom.transform(selection2, function() {
|
|
2696
|
-
var e =
|
|
2696
|
+
var e = extent.apply(this, arguments), t0 = this.__zoom, p0 = p == null ? centroid(e) : typeof p === "function" ? p.apply(this, arguments) : p, p1 = t0.invert(p0), k1 = typeof k === "function" ? k.apply(this, arguments) : k;
|
|
2697
2697
|
return constrain(translate(scale(t0, k1), p0, p1), e, translateExtent);
|
|
2698
2698
|
}, p, event);
|
|
2699
2699
|
};
|
|
@@ -2702,12 +2702,12 @@ function zoom_default2() {
|
|
|
2702
2702
|
return constrain(this.__zoom.translate(
|
|
2703
2703
|
typeof x3 === "function" ? x3.apply(this, arguments) : x3,
|
|
2704
2704
|
typeof y3 === "function" ? y3.apply(this, arguments) : y3
|
|
2705
|
-
),
|
|
2705
|
+
), extent.apply(this, arguments), translateExtent);
|
|
2706
2706
|
}, null, event);
|
|
2707
2707
|
};
|
|
2708
2708
|
zoom.translateTo = function(selection2, x3, y3, p, event) {
|
|
2709
2709
|
zoom.transform(selection2, function() {
|
|
2710
|
-
var e =
|
|
2710
|
+
var e = extent.apply(this, arguments), t = this.__zoom, p0 = p == null ? centroid(e) : typeof p === "function" ? p.apply(this, arguments) : p;
|
|
2711
2711
|
return constrain(identity2.translate(p0[0], p0[1]).scale(t.k).translate(
|
|
2712
2712
|
typeof x3 === "function" ? -x3.apply(this, arguments) : -x3,
|
|
2713
2713
|
typeof y3 === "function" ? -y3.apply(this, arguments) : -y3
|
|
@@ -2722,8 +2722,8 @@ function zoom_default2() {
|
|
|
2722
2722
|
var x3 = p0[0] - p1[0] * transform2.k, y3 = p0[1] - p1[1] * transform2.k;
|
|
2723
2723
|
return x3 === transform2.x && y3 === transform2.y ? transform2 : new Transform(transform2.k, x3, y3);
|
|
2724
2724
|
}
|
|
2725
|
-
function centroid(
|
|
2726
|
-
return [(+
|
|
2725
|
+
function centroid(extent2) {
|
|
2726
|
+
return [(+extent2[0][0] + +extent2[1][0]) / 2, (+extent2[0][1] + +extent2[1][1]) / 2];
|
|
2727
2727
|
}
|
|
2728
2728
|
function schedule(transition2, transform2, point, event) {
|
|
2729
2729
|
transition2.on("start.zoom", function() {
|
|
@@ -2731,7 +2731,7 @@ function zoom_default2() {
|
|
|
2731
2731
|
}).on("interrupt.zoom end.zoom", function() {
|
|
2732
2732
|
gesture(this, arguments).event(event).end();
|
|
2733
2733
|
}).tween("zoom", function() {
|
|
2734
|
-
var that = this, args = arguments, g = gesture(that, args).event(event), e =
|
|
2734
|
+
var that = this, args = arguments, g = gesture(that, args).event(event), e = extent.apply(that, args), p = point == null ? centroid(e) : typeof point === "function" ? point.apply(that, args) : point, w = Math.max(e[1][0] - e[0][0], e[1][1] - e[0][1]), a2 = that.__zoom, b = typeof transform2 === "function" ? transform2.apply(that, args) : transform2, i = interpolate(a2.invert(p).concat(w / a2.k), b.invert(p).concat(w / b.k));
|
|
2735
2735
|
return function(t) {
|
|
2736
2736
|
if (t === 1) t = b;
|
|
2737
2737
|
else {
|
|
@@ -2750,7 +2750,7 @@ function zoom_default2() {
|
|
|
2750
2750
|
this.args = args;
|
|
2751
2751
|
this.active = 0;
|
|
2752
2752
|
this.sourceEvent = null;
|
|
2753
|
-
this.extent =
|
|
2753
|
+
this.extent = extent.apply(that, args);
|
|
2754
2754
|
this.taps = 0;
|
|
2755
2755
|
}
|
|
2756
2756
|
Gesture.prototype = {
|
|
@@ -2843,7 +2843,7 @@ function zoom_default2() {
|
|
|
2843
2843
|
}
|
|
2844
2844
|
function dblclicked(event, ...args) {
|
|
2845
2845
|
if (!filter2.apply(this, arguments)) return;
|
|
2846
|
-
var t0 = this.__zoom, p0 = pointer_default(event.changedTouches ? event.changedTouches[0] : event, this), p1 = t0.invert(p0), k1 = t0.k * (event.shiftKey ? 0.5 : 2), t1 = constrain(translate(scale(t0, k1), p0, p1),
|
|
2846
|
+
var t0 = this.__zoom, p0 = pointer_default(event.changedTouches ? event.changedTouches[0] : event, this), p1 = t0.invert(p0), k1 = t0.k * (event.shiftKey ? 0.5 : 2), t1 = constrain(translate(scale(t0, k1), p0, p1), extent.apply(this, args), translateExtent);
|
|
2847
2847
|
noevent_default2(event);
|
|
2848
2848
|
if (duration > 0) select_default2(this).transition().duration(duration).call(schedule, t1, p0, event);
|
|
2849
2849
|
else select_default2(this).call(zoom.transform, t1, p0, event);
|
|
@@ -2922,7 +2922,7 @@ function zoom_default2() {
|
|
|
2922
2922
|
return arguments.length ? (touchable = typeof _ === "function" ? _ : constant_default4(!!_), zoom) : touchable;
|
|
2923
2923
|
};
|
|
2924
2924
|
zoom.extent = function(_) {
|
|
2925
|
-
return arguments.length ? (
|
|
2925
|
+
return arguments.length ? (extent = typeof _ === "function" ? _ : constant_default4([[+_[0][0], +_[0][1]], [+_[1][0], +_[1][1]]]), zoom) : extent;
|
|
2926
2926
|
};
|
|
2927
2927
|
zoom.scaleExtent = function(_) {
|
|
2928
2928
|
return arguments.length ? (scaleExtent[0] = +_[0], scaleExtent[1] = +_[1], zoom) : [scaleExtent[0], scaleExtent[1]];
|
|
@@ -2952,9 +2952,6 @@ function zoom_default2() {
|
|
|
2952
2952
|
return zoom;
|
|
2953
2953
|
}
|
|
2954
2954
|
|
|
2955
|
-
// src/create-graph.ts
|
|
2956
|
-
var import_d3 = require("d3");
|
|
2957
|
-
|
|
2958
2955
|
// src/utils/timer-manager.ts
|
|
2959
2956
|
var TimerManager = class {
|
|
2960
2957
|
activeTimers = /* @__PURE__ */ new Map();
|
|
@@ -3341,7 +3338,6 @@ var GraphManager = class {
|
|
|
3341
3338
|
linkMarkerSnapshots = null;
|
|
3342
3339
|
rootSelection = null;
|
|
3343
3340
|
simulationPaused = false;
|
|
3344
|
-
needsImmediateFitView = false;
|
|
3345
3341
|
/**
|
|
3346
3342
|
* Initialize core managers
|
|
3347
3343
|
*/
|
|
@@ -3397,7 +3393,6 @@ var GraphManager = class {
|
|
|
3397
3393
|
this.linkMarkerSnapshots = null;
|
|
3398
3394
|
this.rootSelection = null;
|
|
3399
3395
|
this.simulationPaused = false;
|
|
3400
|
-
this.needsImmediateFitView = false;
|
|
3401
3396
|
this.cleanupFunctions = [];
|
|
3402
3397
|
}
|
|
3403
3398
|
/**
|
|
@@ -4417,6 +4412,24 @@ function createGraphSimulation(config) {
|
|
|
4417
4412
|
const warmupTicks = enhancedConfig.warmup?.ticks ?? (useAdaptive ? Math.min(100, nodeCount * 2) : 50);
|
|
4418
4413
|
warmupSimulation(simulation, warmupTicks);
|
|
4419
4414
|
}
|
|
4415
|
+
if (config.onReady && config.timerManager) {
|
|
4416
|
+
let readyCallbackFired = false;
|
|
4417
|
+
const handleTick = () => {
|
|
4418
|
+
if (!readyCallbackFired && simulation.alpha() < 0.1) {
|
|
4419
|
+
readyCallbackFired = true;
|
|
4420
|
+
simulation.on("tick.ready", null);
|
|
4421
|
+
config.timerManager.setTimeout("simulation-ready", () => {
|
|
4422
|
+
config.onReady?.();
|
|
4423
|
+
}, 100);
|
|
4424
|
+
}
|
|
4425
|
+
};
|
|
4426
|
+
const cleanup = () => {
|
|
4427
|
+
simulation.on("tick.ready", null);
|
|
4428
|
+
config.timerManager.clearTimer("simulation-ready");
|
|
4429
|
+
};
|
|
4430
|
+
simulation.on("tick.ready", handleTick);
|
|
4431
|
+
simulation.on("end.ready", cleanup);
|
|
4432
|
+
}
|
|
4420
4433
|
return { simulation };
|
|
4421
4434
|
}
|
|
4422
4435
|
function seedNodePositions(nodes, containerWidth, containerHeight) {
|
|
@@ -4803,50 +4816,12 @@ function renderLinks(ctx, links) {
|
|
|
4803
4816
|
|
|
4804
4817
|
// src/renderer/nodes.ts
|
|
4805
4818
|
function renderNodes(ctx, nodes) {
|
|
4806
|
-
return ctx.root.select('[data-layer="nodes"]').selectAll("circle").data(
|
|
4807
|
-
nodes,
|
|
4808
|
-
(d) => d.id
|
|
4809
|
-
).join("circle").attr(
|
|
4810
|
-
"r",
|
|
4811
|
-
(node) => node.style?.radius ?? 8
|
|
4812
|
-
).attr(
|
|
4813
|
-
"fill",
|
|
4814
|
-
(node) => node.style?.fill ?? "#6c5ce7"
|
|
4815
|
-
).attr(
|
|
4816
|
-
"stroke",
|
|
4817
|
-
(node) => node.style?.stroke ?? "#ffffff"
|
|
4818
|
-
).attr(
|
|
4819
|
-
"stroke-width",
|
|
4820
|
-
(node) => node.style?.strokeWidth ?? 1.5
|
|
4821
|
-
).attr(
|
|
4822
|
-
"opacity",
|
|
4823
|
-
(node) => node.style?.opacity ?? 1
|
|
4824
|
-
);
|
|
4819
|
+
return ctx.root.select('[data-layer="nodes"]').selectAll("circle").data(nodes, (d) => d.id).join("circle").attr("r", (node) => node.style?.radius ?? 8).attr("fill", (node) => node.style?.fill ?? "#6c5ce7").attr("stroke", (node) => node.style?.stroke ?? "#ffffff").attr("stroke-width", (node) => node.style?.strokeWidth ?? 1.5).attr("opacity", (node) => node.style?.opacity ?? 1).style("cursor", "pointer");
|
|
4825
4820
|
}
|
|
4826
4821
|
|
|
4827
4822
|
// src/renderer/node-labels.ts
|
|
4828
4823
|
function renderNodeLabels(ctx, nodes) {
|
|
4829
|
-
const selection2 = ctx.root.select('[data-layer="node-labels"]').selectAll("text").data(
|
|
4830
|
-
nodes,
|
|
4831
|
-
(d) => d.id
|
|
4832
|
-
).join("text").attr(
|
|
4833
|
-
"text-anchor",
|
|
4834
|
-
"middle"
|
|
4835
|
-
).attr(
|
|
4836
|
-
"dominant-baseline",
|
|
4837
|
-
"middle"
|
|
4838
|
-
).attr(
|
|
4839
|
-
"font-size",
|
|
4840
|
-
9
|
|
4841
|
-
).attr(
|
|
4842
|
-
"fill",
|
|
4843
|
-
(node) => node.style?.textColor ?? "#ffffff"
|
|
4844
|
-
).attr(
|
|
4845
|
-
"pointer-events",
|
|
4846
|
-
"none"
|
|
4847
|
-
).text(
|
|
4848
|
-
(node) => node.label ?? node.id
|
|
4849
|
-
);
|
|
4824
|
+
const selection2 = ctx.root.select('[data-layer="node-labels"]').selectAll("text").data(nodes, (d) => d.id).join("text").attr("text-anchor", "middle").attr("dominant-baseline", "middle").attr("font-size", 9).attr("fill", (node) => node.style?.textColor ?? "#ffffff").attr("pointer-events", "none").text((node) => node.label ?? node.id);
|
|
4850
4825
|
selection2.each(function(node) {
|
|
4851
4826
|
const textElement = this;
|
|
4852
4827
|
const fullLabel = node.label ?? node.id;
|
|
@@ -4855,10 +4830,7 @@ function renderNodeLabels(ctx, nodes) {
|
|
|
4855
4830
|
let truncatedLabel = fullLabel;
|
|
4856
4831
|
textElement.textContent = truncatedLabel;
|
|
4857
4832
|
while (truncatedLabel.length > 1 && textElement.getComputedTextLength() > maxWidth) {
|
|
4858
|
-
truncatedLabel = truncatedLabel.slice(
|
|
4859
|
-
0,
|
|
4860
|
-
-1
|
|
4861
|
-
);
|
|
4833
|
+
truncatedLabel = truncatedLabel.slice(0, -1);
|
|
4862
4834
|
textElement.textContent = `${truncatedLabel}\u2026`;
|
|
4863
4835
|
}
|
|
4864
4836
|
});
|
|
@@ -4992,10 +4964,12 @@ var RenderPipeline = class {
|
|
|
4992
4964
|
}
|
|
4993
4965
|
if (this.manager.timerManager && this.manager.fitViewCallback) {
|
|
4994
4966
|
this.manager.timerManager.debounce("fit-view-resize", () => {
|
|
4995
|
-
|
|
4996
|
-
this.manager.fitViewCallback
|
|
4997
|
-
|
|
4998
|
-
|
|
4967
|
+
this.manager.timerManager.setTimeout("fit-view-layout", () => {
|
|
4968
|
+
if (this.manager.fitViewCallback) {
|
|
4969
|
+
this.manager.fitViewCallback();
|
|
4970
|
+
}
|
|
4971
|
+
}, 50);
|
|
4972
|
+
}, 200);
|
|
4999
4973
|
}
|
|
5000
4974
|
});
|
|
5001
4975
|
this.manager.addCleanup(cleanupResize);
|
|
@@ -5065,15 +5039,23 @@ var RenderPipeline = class {
|
|
|
5065
5039
|
config: this.manager.config.simulation
|
|
5066
5040
|
};
|
|
5067
5041
|
try {
|
|
5068
|
-
const
|
|
5042
|
+
const simulationConfigWithCallback = {
|
|
5043
|
+
...simulationConfig,
|
|
5044
|
+
onReady: () => {
|
|
5045
|
+
if (this.manager.fitViewCallback) {
|
|
5046
|
+
this.manager.fitViewCallback();
|
|
5047
|
+
}
|
|
5048
|
+
},
|
|
5049
|
+
timerManager: this.manager.timerManager ?? void 0
|
|
5050
|
+
};
|
|
5051
|
+
const simulationResult = createGraphSimulation(simulationConfigWithCallback);
|
|
5069
5052
|
this.manager.simulation = simulationResult.simulation;
|
|
5070
5053
|
const centerX = simulationConfig.width / 2;
|
|
5071
5054
|
const centerY = simulationConfig.height / 2;
|
|
5072
5055
|
this.manager.simulation.force("center", center_default(centerX, centerY));
|
|
5073
|
-
if (
|
|
5056
|
+
if (simulationConfigWithCallback.width > 0 && simulationConfigWithCallback.height > 0) {
|
|
5074
5057
|
this.manager.reheatSimulation(0.3);
|
|
5075
5058
|
this.manager.simulationPaused = false;
|
|
5076
|
-
this.manager.needsImmediateFitView = true;
|
|
5077
5059
|
} else {
|
|
5078
5060
|
this.manager.simulation.stop();
|
|
5079
5061
|
this.manager.simulationPaused = true;
|
|
@@ -6099,6 +6081,27 @@ var SelectionManager = class {
|
|
|
6099
6081
|
}
|
|
6100
6082
|
};
|
|
6101
6083
|
|
|
6084
|
+
// src/utils/resolve-node-style.ts
|
|
6085
|
+
var DEFAULT_NODE_HOVER_STYLE = {
|
|
6086
|
+
stroke: `color-mix(in srgb, ${"#8E42EE" /* PURPLE */}, ${"#000000" /* BLACK */} 20%)`,
|
|
6087
|
+
strokeWidth: 3
|
|
6088
|
+
};
|
|
6089
|
+
function resolveNodeStyle(params) {
|
|
6090
|
+
if (params.isSelected) {
|
|
6091
|
+
return mergeNodeStyle(DEFAULT_NODE_HOVER_STYLE, params.interaction?.selection?.nodeStyle);
|
|
6092
|
+
}
|
|
6093
|
+
if (params.isHovered) {
|
|
6094
|
+
return mergeNodeStyle(DEFAULT_NODE_HOVER_STYLE, params.interaction?.hover?.nodeStyle);
|
|
6095
|
+
}
|
|
6096
|
+
return void 0;
|
|
6097
|
+
}
|
|
6098
|
+
function mergeNodeStyle(base, override) {
|
|
6099
|
+
return {
|
|
6100
|
+
...base,
|
|
6101
|
+
...override
|
|
6102
|
+
};
|
|
6103
|
+
}
|
|
6104
|
+
|
|
6102
6105
|
// src/core/interaction-manager.ts
|
|
6103
6106
|
var InteractionManager = class {
|
|
6104
6107
|
constructor(manager) {
|
|
@@ -6150,7 +6153,13 @@ var InteractionManager = class {
|
|
|
6150
6153
|
tooltipConfig: this.manager.config.interaction.hover.tooltip
|
|
6151
6154
|
});
|
|
6152
6155
|
}
|
|
6153
|
-
|
|
6156
|
+
const defaultNodeHoverStyle = resolveNodeStyle({
|
|
6157
|
+
node: {},
|
|
6158
|
+
// We don't need the actual node for defaults
|
|
6159
|
+
interaction: this.manager.config.interaction,
|
|
6160
|
+
isHovered: true
|
|
6161
|
+
});
|
|
6162
|
+
createNodeHover(selections.nodeSelection, defaultNodeHoverStyle);
|
|
6154
6163
|
createLinkHover(selections.linkSelection, this.manager.config.interaction.hover.linkStyle);
|
|
6155
6164
|
}
|
|
6156
6165
|
}
|
|
@@ -6470,6 +6479,7 @@ async function captureAndDownloadGraph(container, options = {}) {
|
|
|
6470
6479
|
}
|
|
6471
6480
|
svgClone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
6472
6481
|
svgClone.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
6482
|
+
fixTextStyling(svgClone);
|
|
6473
6483
|
const svgString = new XMLSerializer().serializeToString(svgClone);
|
|
6474
6484
|
const svgBlob = new Blob([svgString], { type: "image/svg+xml;charset=utf-8" });
|
|
6475
6485
|
const svgUrl = URL.createObjectURL(svgBlob);
|
|
@@ -6616,6 +6626,64 @@ function createLegendSVGElement(legendEntries, dimensions) {
|
|
|
6616
6626
|
});
|
|
6617
6627
|
return legendGroup;
|
|
6618
6628
|
}
|
|
6629
|
+
function fixTextStyling(svgElement) {
|
|
6630
|
+
const nodeLabelsLayer = svgElement.querySelector('[data-layer="node-labels"]');
|
|
6631
|
+
if (nodeLabelsLayer) {
|
|
6632
|
+
const nodeTexts = nodeLabelsLayer.querySelectorAll("text");
|
|
6633
|
+
nodeTexts.forEach((text) => {
|
|
6634
|
+
const textElement = text;
|
|
6635
|
+
textElement.setAttribute("font-family", 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif');
|
|
6636
|
+
if (!textElement.getAttribute("font-size")) {
|
|
6637
|
+
textElement.setAttribute("font-size", "9");
|
|
6638
|
+
}
|
|
6639
|
+
textElement.setAttribute("font-weight", "500");
|
|
6640
|
+
textElement.setAttribute("text-anchor", "middle");
|
|
6641
|
+
textElement.setAttribute("dominant-baseline", "central");
|
|
6642
|
+
if (!textElement.getAttribute("fill")) {
|
|
6643
|
+
textElement.setAttribute("fill", "#374151");
|
|
6644
|
+
}
|
|
6645
|
+
});
|
|
6646
|
+
}
|
|
6647
|
+
const linkLabels = svgElement.querySelectorAll("g.link-label");
|
|
6648
|
+
linkLabels.forEach((labelGroup) => {
|
|
6649
|
+
const textElement = labelGroup.querySelector("text");
|
|
6650
|
+
if (textElement) {
|
|
6651
|
+
textElement.setAttribute("font-family", 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif');
|
|
6652
|
+
if (!textElement.getAttribute("font-size")) {
|
|
6653
|
+
textElement.setAttribute("font-size", "10");
|
|
6654
|
+
}
|
|
6655
|
+
textElement.setAttribute("font-weight", "400");
|
|
6656
|
+
textElement.setAttribute("text-anchor", "middle");
|
|
6657
|
+
textElement.setAttribute("dominant-baseline", "central");
|
|
6658
|
+
if (!textElement.getAttribute("fill")) {
|
|
6659
|
+
textElement.setAttribute("fill", "#6b7280");
|
|
6660
|
+
}
|
|
6661
|
+
}
|
|
6662
|
+
const rectElement = labelGroup.querySelector("rect");
|
|
6663
|
+
if (rectElement) {
|
|
6664
|
+
if (!rectElement.getAttribute("fill")) {
|
|
6665
|
+
rectElement.setAttribute("fill", "#ffffff");
|
|
6666
|
+
}
|
|
6667
|
+
if (!rectElement.getAttribute("stroke")) {
|
|
6668
|
+
rectElement.setAttribute("stroke", "#e5e7eb");
|
|
6669
|
+
}
|
|
6670
|
+
if (!rectElement.getAttribute("stroke-width")) {
|
|
6671
|
+
rectElement.setAttribute("stroke-width", "1");
|
|
6672
|
+
}
|
|
6673
|
+
rectElement.setAttribute("rx", "4");
|
|
6674
|
+
}
|
|
6675
|
+
});
|
|
6676
|
+
const allTexts = svgElement.querySelectorAll("text");
|
|
6677
|
+
allTexts.forEach((text) => {
|
|
6678
|
+
const textElement = text;
|
|
6679
|
+
if (!textElement.getAttribute("font-family")) {
|
|
6680
|
+
textElement.setAttribute("font-family", 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif');
|
|
6681
|
+
}
|
|
6682
|
+
if (!textElement.getAttribute("font-size")) {
|
|
6683
|
+
textElement.setAttribute("font-size", "10");
|
|
6684
|
+
}
|
|
6685
|
+
});
|
|
6686
|
+
}
|
|
6619
6687
|
function normalizeColor(color2) {
|
|
6620
6688
|
const rgbMatch = color2.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
6621
6689
|
if (rgbMatch) {
|
|
@@ -6948,10 +7016,6 @@ function createGraph(config) {
|
|
|
6948
7016
|
renderPipeline.execute().then((selections) => {
|
|
6949
7017
|
interactionManager.setupInteractions(selections);
|
|
6950
7018
|
setupAdditionalComponents();
|
|
6951
|
-
if (graphManager.needsImmediateFitView) {
|
|
6952
|
-
graphManager.needsImmediateFitView = false;
|
|
6953
|
-
fitViewWithInitialPositions();
|
|
6954
|
-
}
|
|
6955
7019
|
}).catch((error) => {
|
|
6956
7020
|
console.error("[Polly Graph] Render failed:", error);
|
|
6957
7021
|
});
|
|
@@ -7022,56 +7086,34 @@ function createGraph(config) {
|
|
|
7022
7086
|
const svg = graphManager.svgElement;
|
|
7023
7087
|
const nodes = config.nodes;
|
|
7024
7088
|
if (nodes.length === 0) return;
|
|
7025
|
-
const
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
const xExtent = (0, import_d3.extent)(positions, (d) => d.x);
|
|
7030
|
-
const yExtent = (0, import_d3.extent)(positions, (d) => d.y);
|
|
7031
|
-
const padding = 50;
|
|
7032
|
-
const width = graphManager.dimensions.width - padding * 2;
|
|
7033
|
-
const height = graphManager.dimensions.height - padding * 2;
|
|
7034
|
-
const nodeWidth = xExtent[1] - xExtent[0];
|
|
7035
|
-
const nodeHeight = yExtent[1] - yExtent[0];
|
|
7036
|
-
if (nodeWidth === 0 || nodeHeight === 0) {
|
|
7089
|
+
const graphRoot = select_default2(svg).select('[data-layer="viewport"]');
|
|
7090
|
+
const graphRootNode = graphRoot.node();
|
|
7091
|
+
if (!graphRootNode) {
|
|
7092
|
+
console.warn("[Polly Graph] Cannot fit view: graph root not found");
|
|
7037
7093
|
return;
|
|
7038
7094
|
}
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
select_default2(svg).transition().duration(400).call(graphManager.zoomBehavior.transform, transform2);
|
|
7045
|
-
}
|
|
7046
|
-
}
|
|
7047
|
-
function fitViewWithInitialPositions() {
|
|
7048
|
-
if (!graphManager.simulation || !graphManager.svgElement) {
|
|
7049
|
-
console.warn("[Polly Graph] Cannot fit view: simulation or SVG not available");
|
|
7095
|
+
let bounds;
|
|
7096
|
+
try {
|
|
7097
|
+
bounds = graphRootNode.getBBox();
|
|
7098
|
+
} catch {
|
|
7099
|
+
console.warn("[Polly Graph] Cannot get bounds, falling back to default view");
|
|
7050
7100
|
return;
|
|
7051
7101
|
}
|
|
7052
|
-
|
|
7053
|
-
const nodes = config.nodes;
|
|
7054
|
-
if (nodes.length === 0) return;
|
|
7055
|
-
const positions = nodes.map((node) => ({
|
|
7056
|
-
x: node.initialX ?? node.x ?? 0,
|
|
7057
|
-
y: node.initialY ?? node.y ?? 0
|
|
7058
|
-
}));
|
|
7059
|
-
const xExtent = (0, import_d3.extent)(positions, (d) => d.x);
|
|
7060
|
-
const yExtent = (0, import_d3.extent)(positions, (d) => d.y);
|
|
7061
|
-
const padding = 50;
|
|
7062
|
-
const width = graphManager.dimensions.width - padding * 2;
|
|
7063
|
-
const height = graphManager.dimensions.height - padding * 2;
|
|
7064
|
-
const nodeWidth = xExtent[1] - xExtent[0];
|
|
7065
|
-
const nodeHeight = yExtent[1] - yExtent[0];
|
|
7066
|
-
if (nodeWidth === 0 || nodeHeight === 0) {
|
|
7102
|
+
if (bounds.width === 0 || bounds.height === 0) {
|
|
7067
7103
|
return;
|
|
7068
7104
|
}
|
|
7069
|
-
const
|
|
7070
|
-
const
|
|
7071
|
-
const
|
|
7072
|
-
const
|
|
7105
|
+
const svgRect = svg.getBoundingClientRect();
|
|
7106
|
+
const padding = 40;
|
|
7107
|
+
const availableWidth = svgRect.width - padding * 2;
|
|
7108
|
+
const availableHeight = svgRect.height - padding * 2;
|
|
7109
|
+
const scaleX = availableWidth / bounds.width;
|
|
7110
|
+
const scaleY = availableHeight / bounds.height;
|
|
7111
|
+
const scale = Math.min(scaleX, scaleY, 4);
|
|
7112
|
+
const centerX = bounds.x + bounds.width / 2;
|
|
7113
|
+
const centerY = bounds.y + bounds.height / 2;
|
|
7114
|
+
const transform2 = identity2.translate(svgRect.width / 2, svgRect.height / 2).scale(scale).translate(-centerX, -centerY);
|
|
7073
7115
|
if (graphManager.zoomBehavior) {
|
|
7074
|
-
select_default2(svg).transition().duration(
|
|
7116
|
+
select_default2(svg).transition().duration(750).call(graphManager.zoomBehavior.transform, transform2);
|
|
7075
7117
|
}
|
|
7076
7118
|
}
|
|
7077
7119
|
function exportGraph(fileName) {
|
package/dist/index.css
CHANGED
|
@@ -200,14 +200,17 @@
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
/* src/styles/graph-tooltip.css */
|
|
203
|
+
:root {
|
|
204
|
+
--dark-bg: color-mix(in srgb, #808080, #000000 20%);
|
|
205
|
+
}
|
|
203
206
|
.graph-tooltip {
|
|
204
207
|
position: absolute;
|
|
205
208
|
pointer-events: none;
|
|
206
209
|
z-index: 1000;
|
|
207
210
|
width: fit-content;
|
|
208
211
|
max-width: 280px;
|
|
209
|
-
border-radius:
|
|
210
|
-
background:
|
|
212
|
+
border-radius: 0.375rem;
|
|
213
|
+
background: var(--dark-bg);
|
|
211
214
|
color: #f8fafc;
|
|
212
215
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
213
216
|
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22);
|
|
@@ -230,15 +233,15 @@
|
|
|
230
233
|
.graph-tooltip__content {
|
|
231
234
|
position: relative;
|
|
232
235
|
z-index: 2;
|
|
233
|
-
padding:
|
|
236
|
+
padding: 0.375rem 0.5rem;
|
|
234
237
|
word-wrap: break-word;
|
|
235
238
|
overflow-wrap: break-word;
|
|
236
239
|
}
|
|
237
240
|
.graph-tooltip__arrow {
|
|
238
241
|
position: absolute;
|
|
239
|
-
width:
|
|
240
|
-
height:
|
|
241
|
-
background:
|
|
242
|
+
width: 0.625rem;
|
|
243
|
+
height: 0.625rem;
|
|
244
|
+
background: var(--dark-bg);
|
|
242
245
|
transform: rotate(45deg);
|
|
243
246
|
z-index: 1;
|
|
244
247
|
}
|
|
@@ -298,6 +301,10 @@
|
|
|
298
301
|
display: block;
|
|
299
302
|
width: 100%;
|
|
300
303
|
height: 100%;
|
|
304
|
+
cursor: grab;
|
|
305
|
+
}
|
|
306
|
+
.pg-canvas:active {
|
|
307
|
+
cursor: grabbing;
|
|
301
308
|
}
|
|
302
309
|
.pg-overlay {
|
|
303
310
|
position: absolute;
|
|
@@ -310,6 +317,7 @@
|
|
|
310
317
|
transition: r 0.2s ease, stroke-width 0.2s ease;
|
|
311
318
|
}
|
|
312
319
|
.pg-layer-links line {
|
|
320
|
+
cursor: pointer;
|
|
313
321
|
transition: stroke-opacity 0.2s ease;
|
|
314
322
|
}
|
|
315
323
|
|
package/dist/index.js
CHANGED
|
@@ -2630,15 +2630,15 @@ function defaultWheelDelta(event) {
|
|
|
2630
2630
|
function defaultTouchable2() {
|
|
2631
2631
|
return navigator.maxTouchPoints || "ontouchstart" in this;
|
|
2632
2632
|
}
|
|
2633
|
-
function defaultConstrain(transform2,
|
|
2634
|
-
var dx0 = transform2.invertX(
|
|
2633
|
+
function defaultConstrain(transform2, extent, translateExtent) {
|
|
2634
|
+
var dx0 = transform2.invertX(extent[0][0]) - translateExtent[0][0], dx1 = transform2.invertX(extent[1][0]) - translateExtent[1][0], dy0 = transform2.invertY(extent[0][1]) - translateExtent[0][1], dy1 = transform2.invertY(extent[1][1]) - translateExtent[1][1];
|
|
2635
2635
|
return transform2.translate(
|
|
2636
2636
|
dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1),
|
|
2637
2637
|
dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1)
|
|
2638
2638
|
);
|
|
2639
2639
|
}
|
|
2640
2640
|
function zoom_default2() {
|
|
2641
|
-
var filter2 = defaultFilter2,
|
|
2641
|
+
var filter2 = defaultFilter2, extent = defaultExtent, constrain = defaultConstrain, wheelDelta = defaultWheelDelta, touchable = defaultTouchable2, scaleExtent = [0, Infinity], translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]], duration = 250, interpolate = zoom_default, listeners = dispatch_default2("start", "zoom", "end"), touchstarting, touchfirst, touchending, touchDelay = 500, wheelDelay = 150, clickDistance2 = 0, tapDistance = 10;
|
|
2642
2642
|
function zoom(selection2) {
|
|
2643
2643
|
selection2.property("__zoom", defaultTransform).on("wheel.zoom", wheeled, { passive: false }).on("mousedown.zoom", mousedowned).on("dblclick.zoom", dblclicked).filter(touchable).on("touchstart.zoom", touchstarted).on("touchmove.zoom", touchmoved).on("touchend.zoom touchcancel.zoom", touchended).style("-webkit-tap-highlight-color", "rgba(0,0,0,0)");
|
|
2644
2644
|
}
|
|
@@ -2661,7 +2661,7 @@ function zoom_default2() {
|
|
|
2661
2661
|
};
|
|
2662
2662
|
zoom.scaleTo = function(selection2, k, p, event) {
|
|
2663
2663
|
zoom.transform(selection2, function() {
|
|
2664
|
-
var e =
|
|
2664
|
+
var e = extent.apply(this, arguments), t0 = this.__zoom, p0 = p == null ? centroid(e) : typeof p === "function" ? p.apply(this, arguments) : p, p1 = t0.invert(p0), k1 = typeof k === "function" ? k.apply(this, arguments) : k;
|
|
2665
2665
|
return constrain(translate(scale(t0, k1), p0, p1), e, translateExtent);
|
|
2666
2666
|
}, p, event);
|
|
2667
2667
|
};
|
|
@@ -2670,12 +2670,12 @@ function zoom_default2() {
|
|
|
2670
2670
|
return constrain(this.__zoom.translate(
|
|
2671
2671
|
typeof x3 === "function" ? x3.apply(this, arguments) : x3,
|
|
2672
2672
|
typeof y3 === "function" ? y3.apply(this, arguments) : y3
|
|
2673
|
-
),
|
|
2673
|
+
), extent.apply(this, arguments), translateExtent);
|
|
2674
2674
|
}, null, event);
|
|
2675
2675
|
};
|
|
2676
2676
|
zoom.translateTo = function(selection2, x3, y3, p, event) {
|
|
2677
2677
|
zoom.transform(selection2, function() {
|
|
2678
|
-
var e =
|
|
2678
|
+
var e = extent.apply(this, arguments), t = this.__zoom, p0 = p == null ? centroid(e) : typeof p === "function" ? p.apply(this, arguments) : p;
|
|
2679
2679
|
return constrain(identity2.translate(p0[0], p0[1]).scale(t.k).translate(
|
|
2680
2680
|
typeof x3 === "function" ? -x3.apply(this, arguments) : -x3,
|
|
2681
2681
|
typeof y3 === "function" ? -y3.apply(this, arguments) : -y3
|
|
@@ -2690,8 +2690,8 @@ function zoom_default2() {
|
|
|
2690
2690
|
var x3 = p0[0] - p1[0] * transform2.k, y3 = p0[1] - p1[1] * transform2.k;
|
|
2691
2691
|
return x3 === transform2.x && y3 === transform2.y ? transform2 : new Transform(transform2.k, x3, y3);
|
|
2692
2692
|
}
|
|
2693
|
-
function centroid(
|
|
2694
|
-
return [(+
|
|
2693
|
+
function centroid(extent2) {
|
|
2694
|
+
return [(+extent2[0][0] + +extent2[1][0]) / 2, (+extent2[0][1] + +extent2[1][1]) / 2];
|
|
2695
2695
|
}
|
|
2696
2696
|
function schedule(transition2, transform2, point, event) {
|
|
2697
2697
|
transition2.on("start.zoom", function() {
|
|
@@ -2699,7 +2699,7 @@ function zoom_default2() {
|
|
|
2699
2699
|
}).on("interrupt.zoom end.zoom", function() {
|
|
2700
2700
|
gesture(this, arguments).event(event).end();
|
|
2701
2701
|
}).tween("zoom", function() {
|
|
2702
|
-
var that = this, args = arguments, g = gesture(that, args).event(event), e =
|
|
2702
|
+
var that = this, args = arguments, g = gesture(that, args).event(event), e = extent.apply(that, args), p = point == null ? centroid(e) : typeof point === "function" ? point.apply(that, args) : point, w = Math.max(e[1][0] - e[0][0], e[1][1] - e[0][1]), a2 = that.__zoom, b = typeof transform2 === "function" ? transform2.apply(that, args) : transform2, i = interpolate(a2.invert(p).concat(w / a2.k), b.invert(p).concat(w / b.k));
|
|
2703
2703
|
return function(t) {
|
|
2704
2704
|
if (t === 1) t = b;
|
|
2705
2705
|
else {
|
|
@@ -2718,7 +2718,7 @@ function zoom_default2() {
|
|
|
2718
2718
|
this.args = args;
|
|
2719
2719
|
this.active = 0;
|
|
2720
2720
|
this.sourceEvent = null;
|
|
2721
|
-
this.extent =
|
|
2721
|
+
this.extent = extent.apply(that, args);
|
|
2722
2722
|
this.taps = 0;
|
|
2723
2723
|
}
|
|
2724
2724
|
Gesture.prototype = {
|
|
@@ -2811,7 +2811,7 @@ function zoom_default2() {
|
|
|
2811
2811
|
}
|
|
2812
2812
|
function dblclicked(event, ...args) {
|
|
2813
2813
|
if (!filter2.apply(this, arguments)) return;
|
|
2814
|
-
var t0 = this.__zoom, p0 = pointer_default(event.changedTouches ? event.changedTouches[0] : event, this), p1 = t0.invert(p0), k1 = t0.k * (event.shiftKey ? 0.5 : 2), t1 = constrain(translate(scale(t0, k1), p0, p1),
|
|
2814
|
+
var t0 = this.__zoom, p0 = pointer_default(event.changedTouches ? event.changedTouches[0] : event, this), p1 = t0.invert(p0), k1 = t0.k * (event.shiftKey ? 0.5 : 2), t1 = constrain(translate(scale(t0, k1), p0, p1), extent.apply(this, args), translateExtent);
|
|
2815
2815
|
noevent_default2(event);
|
|
2816
2816
|
if (duration > 0) select_default2(this).transition().duration(duration).call(schedule, t1, p0, event);
|
|
2817
2817
|
else select_default2(this).call(zoom.transform, t1, p0, event);
|
|
@@ -2890,7 +2890,7 @@ function zoom_default2() {
|
|
|
2890
2890
|
return arguments.length ? (touchable = typeof _ === "function" ? _ : constant_default4(!!_), zoom) : touchable;
|
|
2891
2891
|
};
|
|
2892
2892
|
zoom.extent = function(_) {
|
|
2893
|
-
return arguments.length ? (
|
|
2893
|
+
return arguments.length ? (extent = typeof _ === "function" ? _ : constant_default4([[+_[0][0], +_[0][1]], [+_[1][0], +_[1][1]]]), zoom) : extent;
|
|
2894
2894
|
};
|
|
2895
2895
|
zoom.scaleExtent = function(_) {
|
|
2896
2896
|
return arguments.length ? (scaleExtent[0] = +_[0], scaleExtent[1] = +_[1], zoom) : [scaleExtent[0], scaleExtent[1]];
|
|
@@ -2920,9 +2920,6 @@ function zoom_default2() {
|
|
|
2920
2920
|
return zoom;
|
|
2921
2921
|
}
|
|
2922
2922
|
|
|
2923
|
-
// src/create-graph.ts
|
|
2924
|
-
import { extent } from "d3";
|
|
2925
|
-
|
|
2926
2923
|
// src/utils/timer-manager.ts
|
|
2927
2924
|
var TimerManager = class {
|
|
2928
2925
|
activeTimers = /* @__PURE__ */ new Map();
|
|
@@ -3309,7 +3306,6 @@ var GraphManager = class {
|
|
|
3309
3306
|
linkMarkerSnapshots = null;
|
|
3310
3307
|
rootSelection = null;
|
|
3311
3308
|
simulationPaused = false;
|
|
3312
|
-
needsImmediateFitView = false;
|
|
3313
3309
|
/**
|
|
3314
3310
|
* Initialize core managers
|
|
3315
3311
|
*/
|
|
@@ -3365,7 +3361,6 @@ var GraphManager = class {
|
|
|
3365
3361
|
this.linkMarkerSnapshots = null;
|
|
3366
3362
|
this.rootSelection = null;
|
|
3367
3363
|
this.simulationPaused = false;
|
|
3368
|
-
this.needsImmediateFitView = false;
|
|
3369
3364
|
this.cleanupFunctions = [];
|
|
3370
3365
|
}
|
|
3371
3366
|
/**
|
|
@@ -4385,6 +4380,24 @@ function createGraphSimulation(config) {
|
|
|
4385
4380
|
const warmupTicks = enhancedConfig.warmup?.ticks ?? (useAdaptive ? Math.min(100, nodeCount * 2) : 50);
|
|
4386
4381
|
warmupSimulation(simulation, warmupTicks);
|
|
4387
4382
|
}
|
|
4383
|
+
if (config.onReady && config.timerManager) {
|
|
4384
|
+
let readyCallbackFired = false;
|
|
4385
|
+
const handleTick = () => {
|
|
4386
|
+
if (!readyCallbackFired && simulation.alpha() < 0.1) {
|
|
4387
|
+
readyCallbackFired = true;
|
|
4388
|
+
simulation.on("tick.ready", null);
|
|
4389
|
+
config.timerManager.setTimeout("simulation-ready", () => {
|
|
4390
|
+
config.onReady?.();
|
|
4391
|
+
}, 100);
|
|
4392
|
+
}
|
|
4393
|
+
};
|
|
4394
|
+
const cleanup = () => {
|
|
4395
|
+
simulation.on("tick.ready", null);
|
|
4396
|
+
config.timerManager.clearTimer("simulation-ready");
|
|
4397
|
+
};
|
|
4398
|
+
simulation.on("tick.ready", handleTick);
|
|
4399
|
+
simulation.on("end.ready", cleanup);
|
|
4400
|
+
}
|
|
4388
4401
|
return { simulation };
|
|
4389
4402
|
}
|
|
4390
4403
|
function seedNodePositions(nodes, containerWidth, containerHeight) {
|
|
@@ -4771,50 +4784,12 @@ function renderLinks(ctx, links) {
|
|
|
4771
4784
|
|
|
4772
4785
|
// src/renderer/nodes.ts
|
|
4773
4786
|
function renderNodes(ctx, nodes) {
|
|
4774
|
-
return ctx.root.select('[data-layer="nodes"]').selectAll("circle").data(
|
|
4775
|
-
nodes,
|
|
4776
|
-
(d) => d.id
|
|
4777
|
-
).join("circle").attr(
|
|
4778
|
-
"r",
|
|
4779
|
-
(node) => node.style?.radius ?? 8
|
|
4780
|
-
).attr(
|
|
4781
|
-
"fill",
|
|
4782
|
-
(node) => node.style?.fill ?? "#6c5ce7"
|
|
4783
|
-
).attr(
|
|
4784
|
-
"stroke",
|
|
4785
|
-
(node) => node.style?.stroke ?? "#ffffff"
|
|
4786
|
-
).attr(
|
|
4787
|
-
"stroke-width",
|
|
4788
|
-
(node) => node.style?.strokeWidth ?? 1.5
|
|
4789
|
-
).attr(
|
|
4790
|
-
"opacity",
|
|
4791
|
-
(node) => node.style?.opacity ?? 1
|
|
4792
|
-
);
|
|
4787
|
+
return ctx.root.select('[data-layer="nodes"]').selectAll("circle").data(nodes, (d) => d.id).join("circle").attr("r", (node) => node.style?.radius ?? 8).attr("fill", (node) => node.style?.fill ?? "#6c5ce7").attr("stroke", (node) => node.style?.stroke ?? "#ffffff").attr("stroke-width", (node) => node.style?.strokeWidth ?? 1.5).attr("opacity", (node) => node.style?.opacity ?? 1).style("cursor", "pointer");
|
|
4793
4788
|
}
|
|
4794
4789
|
|
|
4795
4790
|
// src/renderer/node-labels.ts
|
|
4796
4791
|
function renderNodeLabels(ctx, nodes) {
|
|
4797
|
-
const selection2 = ctx.root.select('[data-layer="node-labels"]').selectAll("text").data(
|
|
4798
|
-
nodes,
|
|
4799
|
-
(d) => d.id
|
|
4800
|
-
).join("text").attr(
|
|
4801
|
-
"text-anchor",
|
|
4802
|
-
"middle"
|
|
4803
|
-
).attr(
|
|
4804
|
-
"dominant-baseline",
|
|
4805
|
-
"middle"
|
|
4806
|
-
).attr(
|
|
4807
|
-
"font-size",
|
|
4808
|
-
9
|
|
4809
|
-
).attr(
|
|
4810
|
-
"fill",
|
|
4811
|
-
(node) => node.style?.textColor ?? "#ffffff"
|
|
4812
|
-
).attr(
|
|
4813
|
-
"pointer-events",
|
|
4814
|
-
"none"
|
|
4815
|
-
).text(
|
|
4816
|
-
(node) => node.label ?? node.id
|
|
4817
|
-
);
|
|
4792
|
+
const selection2 = ctx.root.select('[data-layer="node-labels"]').selectAll("text").data(nodes, (d) => d.id).join("text").attr("text-anchor", "middle").attr("dominant-baseline", "middle").attr("font-size", 9).attr("fill", (node) => node.style?.textColor ?? "#ffffff").attr("pointer-events", "none").text((node) => node.label ?? node.id);
|
|
4818
4793
|
selection2.each(function(node) {
|
|
4819
4794
|
const textElement = this;
|
|
4820
4795
|
const fullLabel = node.label ?? node.id;
|
|
@@ -4823,10 +4798,7 @@ function renderNodeLabels(ctx, nodes) {
|
|
|
4823
4798
|
let truncatedLabel = fullLabel;
|
|
4824
4799
|
textElement.textContent = truncatedLabel;
|
|
4825
4800
|
while (truncatedLabel.length > 1 && textElement.getComputedTextLength() > maxWidth) {
|
|
4826
|
-
truncatedLabel = truncatedLabel.slice(
|
|
4827
|
-
0,
|
|
4828
|
-
-1
|
|
4829
|
-
);
|
|
4801
|
+
truncatedLabel = truncatedLabel.slice(0, -1);
|
|
4830
4802
|
textElement.textContent = `${truncatedLabel}\u2026`;
|
|
4831
4803
|
}
|
|
4832
4804
|
});
|
|
@@ -4960,10 +4932,12 @@ var RenderPipeline = class {
|
|
|
4960
4932
|
}
|
|
4961
4933
|
if (this.manager.timerManager && this.manager.fitViewCallback) {
|
|
4962
4934
|
this.manager.timerManager.debounce("fit-view-resize", () => {
|
|
4963
|
-
|
|
4964
|
-
this.manager.fitViewCallback
|
|
4965
|
-
|
|
4966
|
-
|
|
4935
|
+
this.manager.timerManager.setTimeout("fit-view-layout", () => {
|
|
4936
|
+
if (this.manager.fitViewCallback) {
|
|
4937
|
+
this.manager.fitViewCallback();
|
|
4938
|
+
}
|
|
4939
|
+
}, 50);
|
|
4940
|
+
}, 200);
|
|
4967
4941
|
}
|
|
4968
4942
|
});
|
|
4969
4943
|
this.manager.addCleanup(cleanupResize);
|
|
@@ -5033,15 +5007,23 @@ var RenderPipeline = class {
|
|
|
5033
5007
|
config: this.manager.config.simulation
|
|
5034
5008
|
};
|
|
5035
5009
|
try {
|
|
5036
|
-
const
|
|
5010
|
+
const simulationConfigWithCallback = {
|
|
5011
|
+
...simulationConfig,
|
|
5012
|
+
onReady: () => {
|
|
5013
|
+
if (this.manager.fitViewCallback) {
|
|
5014
|
+
this.manager.fitViewCallback();
|
|
5015
|
+
}
|
|
5016
|
+
},
|
|
5017
|
+
timerManager: this.manager.timerManager ?? void 0
|
|
5018
|
+
};
|
|
5019
|
+
const simulationResult = createGraphSimulation(simulationConfigWithCallback);
|
|
5037
5020
|
this.manager.simulation = simulationResult.simulation;
|
|
5038
5021
|
const centerX = simulationConfig.width / 2;
|
|
5039
5022
|
const centerY = simulationConfig.height / 2;
|
|
5040
5023
|
this.manager.simulation.force("center", center_default(centerX, centerY));
|
|
5041
|
-
if (
|
|
5024
|
+
if (simulationConfigWithCallback.width > 0 && simulationConfigWithCallback.height > 0) {
|
|
5042
5025
|
this.manager.reheatSimulation(0.3);
|
|
5043
5026
|
this.manager.simulationPaused = false;
|
|
5044
|
-
this.manager.needsImmediateFitView = true;
|
|
5045
5027
|
} else {
|
|
5046
5028
|
this.manager.simulation.stop();
|
|
5047
5029
|
this.manager.simulationPaused = true;
|
|
@@ -6067,6 +6049,27 @@ var SelectionManager = class {
|
|
|
6067
6049
|
}
|
|
6068
6050
|
};
|
|
6069
6051
|
|
|
6052
|
+
// src/utils/resolve-node-style.ts
|
|
6053
|
+
var DEFAULT_NODE_HOVER_STYLE = {
|
|
6054
|
+
stroke: `color-mix(in srgb, ${"#8E42EE" /* PURPLE */}, ${"#000000" /* BLACK */} 20%)`,
|
|
6055
|
+
strokeWidth: 3
|
|
6056
|
+
};
|
|
6057
|
+
function resolveNodeStyle(params) {
|
|
6058
|
+
if (params.isSelected) {
|
|
6059
|
+
return mergeNodeStyle(DEFAULT_NODE_HOVER_STYLE, params.interaction?.selection?.nodeStyle);
|
|
6060
|
+
}
|
|
6061
|
+
if (params.isHovered) {
|
|
6062
|
+
return mergeNodeStyle(DEFAULT_NODE_HOVER_STYLE, params.interaction?.hover?.nodeStyle);
|
|
6063
|
+
}
|
|
6064
|
+
return void 0;
|
|
6065
|
+
}
|
|
6066
|
+
function mergeNodeStyle(base, override) {
|
|
6067
|
+
return {
|
|
6068
|
+
...base,
|
|
6069
|
+
...override
|
|
6070
|
+
};
|
|
6071
|
+
}
|
|
6072
|
+
|
|
6070
6073
|
// src/core/interaction-manager.ts
|
|
6071
6074
|
var InteractionManager = class {
|
|
6072
6075
|
constructor(manager) {
|
|
@@ -6118,7 +6121,13 @@ var InteractionManager = class {
|
|
|
6118
6121
|
tooltipConfig: this.manager.config.interaction.hover.tooltip
|
|
6119
6122
|
});
|
|
6120
6123
|
}
|
|
6121
|
-
|
|
6124
|
+
const defaultNodeHoverStyle = resolveNodeStyle({
|
|
6125
|
+
node: {},
|
|
6126
|
+
// We don't need the actual node for defaults
|
|
6127
|
+
interaction: this.manager.config.interaction,
|
|
6128
|
+
isHovered: true
|
|
6129
|
+
});
|
|
6130
|
+
createNodeHover(selections.nodeSelection, defaultNodeHoverStyle);
|
|
6122
6131
|
createLinkHover(selections.linkSelection, this.manager.config.interaction.hover.linkStyle);
|
|
6123
6132
|
}
|
|
6124
6133
|
}
|
|
@@ -6438,6 +6447,7 @@ async function captureAndDownloadGraph(container, options = {}) {
|
|
|
6438
6447
|
}
|
|
6439
6448
|
svgClone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
6440
6449
|
svgClone.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
6450
|
+
fixTextStyling(svgClone);
|
|
6441
6451
|
const svgString = new XMLSerializer().serializeToString(svgClone);
|
|
6442
6452
|
const svgBlob = new Blob([svgString], { type: "image/svg+xml;charset=utf-8" });
|
|
6443
6453
|
const svgUrl = URL.createObjectURL(svgBlob);
|
|
@@ -6584,6 +6594,64 @@ function createLegendSVGElement(legendEntries, dimensions) {
|
|
|
6584
6594
|
});
|
|
6585
6595
|
return legendGroup;
|
|
6586
6596
|
}
|
|
6597
|
+
function fixTextStyling(svgElement) {
|
|
6598
|
+
const nodeLabelsLayer = svgElement.querySelector('[data-layer="node-labels"]');
|
|
6599
|
+
if (nodeLabelsLayer) {
|
|
6600
|
+
const nodeTexts = nodeLabelsLayer.querySelectorAll("text");
|
|
6601
|
+
nodeTexts.forEach((text) => {
|
|
6602
|
+
const textElement = text;
|
|
6603
|
+
textElement.setAttribute("font-family", 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif');
|
|
6604
|
+
if (!textElement.getAttribute("font-size")) {
|
|
6605
|
+
textElement.setAttribute("font-size", "9");
|
|
6606
|
+
}
|
|
6607
|
+
textElement.setAttribute("font-weight", "500");
|
|
6608
|
+
textElement.setAttribute("text-anchor", "middle");
|
|
6609
|
+
textElement.setAttribute("dominant-baseline", "central");
|
|
6610
|
+
if (!textElement.getAttribute("fill")) {
|
|
6611
|
+
textElement.setAttribute("fill", "#374151");
|
|
6612
|
+
}
|
|
6613
|
+
});
|
|
6614
|
+
}
|
|
6615
|
+
const linkLabels = svgElement.querySelectorAll("g.link-label");
|
|
6616
|
+
linkLabels.forEach((labelGroup) => {
|
|
6617
|
+
const textElement = labelGroup.querySelector("text");
|
|
6618
|
+
if (textElement) {
|
|
6619
|
+
textElement.setAttribute("font-family", 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif');
|
|
6620
|
+
if (!textElement.getAttribute("font-size")) {
|
|
6621
|
+
textElement.setAttribute("font-size", "10");
|
|
6622
|
+
}
|
|
6623
|
+
textElement.setAttribute("font-weight", "400");
|
|
6624
|
+
textElement.setAttribute("text-anchor", "middle");
|
|
6625
|
+
textElement.setAttribute("dominant-baseline", "central");
|
|
6626
|
+
if (!textElement.getAttribute("fill")) {
|
|
6627
|
+
textElement.setAttribute("fill", "#6b7280");
|
|
6628
|
+
}
|
|
6629
|
+
}
|
|
6630
|
+
const rectElement = labelGroup.querySelector("rect");
|
|
6631
|
+
if (rectElement) {
|
|
6632
|
+
if (!rectElement.getAttribute("fill")) {
|
|
6633
|
+
rectElement.setAttribute("fill", "#ffffff");
|
|
6634
|
+
}
|
|
6635
|
+
if (!rectElement.getAttribute("stroke")) {
|
|
6636
|
+
rectElement.setAttribute("stroke", "#e5e7eb");
|
|
6637
|
+
}
|
|
6638
|
+
if (!rectElement.getAttribute("stroke-width")) {
|
|
6639
|
+
rectElement.setAttribute("stroke-width", "1");
|
|
6640
|
+
}
|
|
6641
|
+
rectElement.setAttribute("rx", "4");
|
|
6642
|
+
}
|
|
6643
|
+
});
|
|
6644
|
+
const allTexts = svgElement.querySelectorAll("text");
|
|
6645
|
+
allTexts.forEach((text) => {
|
|
6646
|
+
const textElement = text;
|
|
6647
|
+
if (!textElement.getAttribute("font-family")) {
|
|
6648
|
+
textElement.setAttribute("font-family", 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif');
|
|
6649
|
+
}
|
|
6650
|
+
if (!textElement.getAttribute("font-size")) {
|
|
6651
|
+
textElement.setAttribute("font-size", "10");
|
|
6652
|
+
}
|
|
6653
|
+
});
|
|
6654
|
+
}
|
|
6587
6655
|
function normalizeColor(color2) {
|
|
6588
6656
|
const rgbMatch = color2.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
6589
6657
|
if (rgbMatch) {
|
|
@@ -6916,10 +6984,6 @@ function createGraph(config) {
|
|
|
6916
6984
|
renderPipeline.execute().then((selections) => {
|
|
6917
6985
|
interactionManager.setupInteractions(selections);
|
|
6918
6986
|
setupAdditionalComponents();
|
|
6919
|
-
if (graphManager.needsImmediateFitView) {
|
|
6920
|
-
graphManager.needsImmediateFitView = false;
|
|
6921
|
-
fitViewWithInitialPositions();
|
|
6922
|
-
}
|
|
6923
6987
|
}).catch((error) => {
|
|
6924
6988
|
console.error("[Polly Graph] Render failed:", error);
|
|
6925
6989
|
});
|
|
@@ -6990,56 +7054,34 @@ function createGraph(config) {
|
|
|
6990
7054
|
const svg = graphManager.svgElement;
|
|
6991
7055
|
const nodes = config.nodes;
|
|
6992
7056
|
if (nodes.length === 0) return;
|
|
6993
|
-
const
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
const xExtent = extent(positions, (d) => d.x);
|
|
6998
|
-
const yExtent = extent(positions, (d) => d.y);
|
|
6999
|
-
const padding = 50;
|
|
7000
|
-
const width = graphManager.dimensions.width - padding * 2;
|
|
7001
|
-
const height = graphManager.dimensions.height - padding * 2;
|
|
7002
|
-
const nodeWidth = xExtent[1] - xExtent[0];
|
|
7003
|
-
const nodeHeight = yExtent[1] - yExtent[0];
|
|
7004
|
-
if (nodeWidth === 0 || nodeHeight === 0) {
|
|
7057
|
+
const graphRoot = select_default2(svg).select('[data-layer="viewport"]');
|
|
7058
|
+
const graphRootNode = graphRoot.node();
|
|
7059
|
+
if (!graphRootNode) {
|
|
7060
|
+
console.warn("[Polly Graph] Cannot fit view: graph root not found");
|
|
7005
7061
|
return;
|
|
7006
7062
|
}
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
select_default2(svg).transition().duration(400).call(graphManager.zoomBehavior.transform, transform2);
|
|
7013
|
-
}
|
|
7014
|
-
}
|
|
7015
|
-
function fitViewWithInitialPositions() {
|
|
7016
|
-
if (!graphManager.simulation || !graphManager.svgElement) {
|
|
7017
|
-
console.warn("[Polly Graph] Cannot fit view: simulation or SVG not available");
|
|
7063
|
+
let bounds;
|
|
7064
|
+
try {
|
|
7065
|
+
bounds = graphRootNode.getBBox();
|
|
7066
|
+
} catch {
|
|
7067
|
+
console.warn("[Polly Graph] Cannot get bounds, falling back to default view");
|
|
7018
7068
|
return;
|
|
7019
7069
|
}
|
|
7020
|
-
|
|
7021
|
-
const nodes = config.nodes;
|
|
7022
|
-
if (nodes.length === 0) return;
|
|
7023
|
-
const positions = nodes.map((node) => ({
|
|
7024
|
-
x: node.initialX ?? node.x ?? 0,
|
|
7025
|
-
y: node.initialY ?? node.y ?? 0
|
|
7026
|
-
}));
|
|
7027
|
-
const xExtent = extent(positions, (d) => d.x);
|
|
7028
|
-
const yExtent = extent(positions, (d) => d.y);
|
|
7029
|
-
const padding = 50;
|
|
7030
|
-
const width = graphManager.dimensions.width - padding * 2;
|
|
7031
|
-
const height = graphManager.dimensions.height - padding * 2;
|
|
7032
|
-
const nodeWidth = xExtent[1] - xExtent[0];
|
|
7033
|
-
const nodeHeight = yExtent[1] - yExtent[0];
|
|
7034
|
-
if (nodeWidth === 0 || nodeHeight === 0) {
|
|
7070
|
+
if (bounds.width === 0 || bounds.height === 0) {
|
|
7035
7071
|
return;
|
|
7036
7072
|
}
|
|
7037
|
-
const
|
|
7038
|
-
const
|
|
7039
|
-
const
|
|
7040
|
-
const
|
|
7073
|
+
const svgRect = svg.getBoundingClientRect();
|
|
7074
|
+
const padding = 40;
|
|
7075
|
+
const availableWidth = svgRect.width - padding * 2;
|
|
7076
|
+
const availableHeight = svgRect.height - padding * 2;
|
|
7077
|
+
const scaleX = availableWidth / bounds.width;
|
|
7078
|
+
const scaleY = availableHeight / bounds.height;
|
|
7079
|
+
const scale = Math.min(scaleX, scaleY, 4);
|
|
7080
|
+
const centerX = bounds.x + bounds.width / 2;
|
|
7081
|
+
const centerY = bounds.y + bounds.height / 2;
|
|
7082
|
+
const transform2 = identity2.translate(svgRect.width / 2, svgRect.height / 2).scale(scale).translate(-centerX, -centerY);
|
|
7041
7083
|
if (graphManager.zoomBehavior) {
|
|
7042
|
-
select_default2(svg).transition().duration(
|
|
7084
|
+
select_default2(svg).transition().duration(750).call(graphManager.zoomBehavior.transform, transform2);
|
|
7043
7085
|
}
|
|
7044
7086
|
}
|
|
7045
7087
|
function exportGraph(fileName) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polly-graph",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Reusable D3-based graph visualization SDK with configurable nodes, links, labels, interactions, and layout behaviors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Badal",
|
|
@@ -55,8 +55,7 @@
|
|
|
55
55
|
"prepublishOnly": "npm run lint && npm run typecheck && npm run test && npm run build"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"d3": "7.9.0"
|
|
59
|
-
"html2canvas": "^1.4.1"
|
|
58
|
+
"d3": "7.9.0"
|
|
60
59
|
},
|
|
61
60
|
"devDependencies": {
|
|
62
61
|
"@eslint/js": "^9.39.4",
|