@naniteninja/profile-comparison-lib 1.0.23 → 1.0.25
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.
|
@@ -2660,8 +2660,6 @@ class ProfileComparisonLibComponent {
|
|
|
2660
2660
|
const horizontalPadding = this.chipPaddingXPx * 2;
|
|
2661
2661
|
const getHalfWidth = (label) => (label.length * approxCharWidth + horizontalPadding) / 2;
|
|
2662
2662
|
const EDGE_PADDING = 20;
|
|
2663
|
-
const getNodeMinX = (label) => getHalfWidth(label) + EDGE_PADDING;
|
|
2664
|
-
const getNodeMaxX = (label) => containerWidth - getHalfWidth(label) - EDGE_PADDING;
|
|
2665
2663
|
const maxHalfP1 = p1Items.length ? Math.max(...p1Items.map(getHalfWidth)) : chipHalfH;
|
|
2666
2664
|
const maxHalfShared = shared.length ? Math.max(...shared.map(getHalfWidth)) : chipHalfH;
|
|
2667
2665
|
const maxHalfP2 = p2Items.length ? Math.max(...p2Items.map(getHalfWidth)) : chipHalfH;
|
|
@@ -2681,7 +2679,32 @@ class ProfileComparisonLibComponent {
|
|
|
2681
2679
|
const minCenterY = startY + chipHalfH;
|
|
2682
2680
|
const verticalSpan = Math.max(0, maxY - minCenterY);
|
|
2683
2681
|
const centerGuard = Math.max(12, Math.min(26, (zoneP2 - zoneP1) * 0.12));
|
|
2684
|
-
const
|
|
2682
|
+
const maxHalfAny = Math.max(maxHalfP1, maxHalfShared, maxHalfP2, chipHalfH);
|
|
2683
|
+
const globalMinX = maxHalfAny + EDGE_PADDING;
|
|
2684
|
+
const globalMaxX = containerWidth - maxHalfAny - EDGE_PADDING;
|
|
2685
|
+
const dividerLeft = (zoneP1 + zoneShared) / 2;
|
|
2686
|
+
const dividerRight = (zoneShared + zoneP2) / 2;
|
|
2687
|
+
const laneGap = Math.max(4, Math.min(16, centerGuard * 0.45));
|
|
2688
|
+
let p1BoundsMinX = globalMinX;
|
|
2689
|
+
let p1BoundsMaxX = Math.min(globalMaxX, dividerLeft - laneGap);
|
|
2690
|
+
let sharedBoundsMinX = Math.max(globalMinX, dividerLeft + laneGap);
|
|
2691
|
+
let sharedBoundsMaxX = Math.min(globalMaxX, dividerRight - laneGap);
|
|
2692
|
+
let p2BoundsMinX = Math.max(globalMinX, dividerRight + laneGap);
|
|
2693
|
+
let p2BoundsMaxX = globalMaxX;
|
|
2694
|
+
if (p1BoundsMaxX < p1BoundsMinX)
|
|
2695
|
+
p1BoundsMaxX = p1BoundsMinX;
|
|
2696
|
+
if (p2BoundsMinX > p2BoundsMaxX)
|
|
2697
|
+
p2BoundsMinX = p2BoundsMaxX;
|
|
2698
|
+
if (sharedBoundsMaxX < sharedBoundsMinX) {
|
|
2699
|
+
const centerX = Math.max(globalMinX, Math.min(globalMaxX, zoneShared));
|
|
2700
|
+
const fallbackHalfWidth = Math.max(6, Math.min(20, Math.max(0, p2BoundsMinX - p1BoundsMaxX) * 0.25));
|
|
2701
|
+
sharedBoundsMinX = Math.max(globalMinX, centerX - fallbackHalfWidth);
|
|
2702
|
+
sharedBoundsMaxX = Math.min(globalMaxX, centerX + fallbackHalfWidth);
|
|
2703
|
+
if (sharedBoundsMaxX < sharedBoundsMinX) {
|
|
2704
|
+
sharedBoundsMinX = centerX;
|
|
2705
|
+
sharedBoundsMaxX = centerX;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2685
2708
|
// --- Scan matrix for max similarity to normalize spring distances ---
|
|
2686
2709
|
let matrixMax = 0;
|
|
2687
2710
|
if (this.matrixData?.rows) {
|
|
@@ -2759,7 +2782,7 @@ class ProfileComparisonLibComponent {
|
|
|
2759
2782
|
return nodes.filter(n => n.group === 'p1' || n.group === 'p2');
|
|
2760
2783
|
};
|
|
2761
2784
|
const ySimilarityFloor = Math.max(0.18, springThreshold - 0.06);
|
|
2762
|
-
for (let pass = 0; pass <
|
|
2785
|
+
for (let pass = 0; pass < 6; pass++) {
|
|
2763
2786
|
const snapshotY = new Map();
|
|
2764
2787
|
nodes.forEach(node => {
|
|
2765
2788
|
snapshotY.set(node.id, node.targetY ?? (minCenterY + verticalSpan * 0.5));
|
|
@@ -2783,60 +2806,27 @@ class ProfileComparisonLibComponent {
|
|
|
2783
2806
|
return;
|
|
2784
2807
|
const semanticY = weightedY / totalWeight;
|
|
2785
2808
|
const currentY = snapshotY.get(node.id) ?? semanticY;
|
|
2786
|
-
const blend = node.group === 'shared' ? 0.
|
|
2809
|
+
const blend = node.group === 'shared' ? 0.84 : 0.78;
|
|
2787
2810
|
node.targetY = Math.max(minCenterY, Math.min(maxY, currentY + (semanticY - currentY) * blend));
|
|
2788
2811
|
});
|
|
2789
2812
|
}
|
|
2790
|
-
const groupCounts = {
|
|
2791
|
-
p1: p1Items.length,
|
|
2792
|
-
shared: shared.length,
|
|
2793
|
-
p2: p2Items.length,
|
|
2794
|
-
};
|
|
2795
|
-
const fullVerticalSpan = Math.max(rowGap, maxY - minCenterY);
|
|
2796
2813
|
const buildNodeBounds = (node) => {
|
|
2797
|
-
const halfW = getHalfWidth(node.label);
|
|
2798
|
-
const globalMinX = getNodeMinX(node.label);
|
|
2799
|
-
const globalMaxX = getNodeMaxX(node.label);
|
|
2800
|
-
const leftLimit = zoneShared - halfW - centerGuard;
|
|
2801
|
-
const rightLimit = zoneShared + halfW + centerGuard;
|
|
2802
2814
|
let minX = globalMinX;
|
|
2803
2815
|
let maxX = globalMaxX;
|
|
2804
2816
|
if (node.group === 'p1') {
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
maxX = minX;
|
|
2817
|
+
minX = p1BoundsMinX;
|
|
2818
|
+
maxX = p1BoundsMaxX;
|
|
2808
2819
|
}
|
|
2809
2820
|
else if (node.group === 'p2') {
|
|
2810
|
-
minX =
|
|
2811
|
-
|
|
2812
|
-
minX = maxX;
|
|
2813
|
-
}
|
|
2814
|
-
else {
|
|
2815
|
-
minX = Math.max(globalMinX, zoneShared - sharedBand);
|
|
2816
|
-
maxX = Math.min(globalMaxX, zoneShared + sharedBand);
|
|
2817
|
-
if (minX > maxX) {
|
|
2818
|
-
const centerX = Math.max(globalMinX, Math.min(globalMaxX, zoneShared));
|
|
2819
|
-
minX = centerX;
|
|
2820
|
-
maxX = centerX;
|
|
2821
|
-
}
|
|
2822
|
-
}
|
|
2823
|
-
const baseY = node.targetY ?? (minCenterY + fullVerticalSpan * 0.5);
|
|
2824
|
-
const groupCount = groupCounts[node.group];
|
|
2825
|
-
let ySlack = rowGap * 2.2;
|
|
2826
|
-
if (node.group === 'shared') {
|
|
2827
|
-
ySlack = Math.max(rowGap * 4.2, fullVerticalSpan * 0.58);
|
|
2821
|
+
minX = p2BoundsMinX;
|
|
2822
|
+
maxX = p2BoundsMaxX;
|
|
2828
2823
|
}
|
|
2829
2824
|
else {
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
}
|
|
2833
|
-
let minY = Math.max(minCenterY, baseY - ySlack);
|
|
2834
|
-
let maxYForNode = Math.min(maxY, baseY + ySlack);
|
|
2835
|
-
if (minY > maxYForNode) {
|
|
2836
|
-
minY = minCenterY;
|
|
2837
|
-
maxYForNode = maxY;
|
|
2825
|
+
minX = sharedBoundsMinX;
|
|
2826
|
+
maxX = sharedBoundsMaxX;
|
|
2838
2827
|
}
|
|
2839
|
-
|
|
2828
|
+
// Y should be column-wide so any chip can travel vertically within its lane.
|
|
2829
|
+
return { minX, maxX, minY: minCenterY, maxY };
|
|
2840
2830
|
};
|
|
2841
2831
|
const nodeBounds = new Map();
|
|
2842
2832
|
nodes.forEach(node => nodeBounds.set(node.id, buildNodeBounds(node)));
|
|
@@ -2978,20 +2968,21 @@ class ProfileComparisonLibComponent {
|
|
|
2978
2968
|
return;
|
|
2979
2969
|
const targetY = weightedY / totalWeight;
|
|
2980
2970
|
const dy = targetY - (node.y ?? targetY);
|
|
2971
|
+
const sparseNeighbors = neighbors.length <= 2;
|
|
2981
2972
|
const alignStrength = node.group === 'shared'
|
|
2982
|
-
? (
|
|
2983
|
-
: 0.
|
|
2973
|
+
? (sparseNeighbors ? 0.95 : 0.8)
|
|
2974
|
+
: (sparseNeighbors ? 0.82 : 0.68);
|
|
2984
2975
|
node.vy = (node.vy ?? 0) + dy * alignStrength * alpha;
|
|
2985
2976
|
});
|
|
2986
2977
|
};
|
|
2987
2978
|
this.ngZone.runOutsideAngular(() => {
|
|
2988
2979
|
const sim = forceSimulation(nodes)
|
|
2989
|
-
.alphaDecay(0.
|
|
2980
|
+
.alphaDecay(0.014)
|
|
2990
2981
|
.stop()
|
|
2991
2982
|
.force('link', forceLink(links)
|
|
2992
2983
|
.id(d => d.id)
|
|
2993
2984
|
.distance(d => d.distance)
|
|
2994
|
-
.strength(d => d.kind === 'cross' ? d.strength *
|
|
2985
|
+
.strength(d => d.kind === 'cross' ? d.strength * 1.45 : d.strength * 0.86))
|
|
2995
2986
|
.force('collide', forceCollide((d) => {
|
|
2996
2987
|
const baseRadius = getHalfWidth(d.label) * 0.55 + ProfileComparisonLibComponent.CHIP_COLLISION_BORDER_PADDING;
|
|
2997
2988
|
return Math.min(58, Math.max(ProfileComparisonLibComponent.CHIP_RADIUS + 8, baseRadius));
|
|
@@ -3000,7 +2991,7 @@ class ProfileComparisonLibComponent {
|
|
|
3000
2991
|
.iterations(2))
|
|
3001
2992
|
.force('xBound', forceX(d => d.targetX ?? zoneShared)
|
|
3002
2993
|
.strength(d => d.group === 'shared' ? 1.15 : 0.85))
|
|
3003
|
-
.force('yTargetSeed', forceY(d => d.targetY ?? minCenterY).strength(0.
|
|
2994
|
+
.force('yTargetSeed', forceY(d => d.targetY ?? minCenterY).strength(0.1))
|
|
3004
2995
|
.force('crossAlign', crossAlignForce);
|
|
3005
2996
|
for (let i = 0; i < 420 && sim.alpha() > 0.0035; i++) {
|
|
3006
2997
|
sim.tick();
|
|
@@ -3008,21 +2999,6 @@ class ProfileComparisonLibComponent {
|
|
|
3008
2999
|
clampNodeToBounds(n);
|
|
3009
3000
|
});
|
|
3010
3001
|
}
|
|
3011
|
-
const enforceGroupStack = (group) => {
|
|
3012
|
-
const groupNodes = nodes
|
|
3013
|
-
.filter(n => n.group === group)
|
|
3014
|
-
.sort((a, b) => (a.y ?? minCenterY) - (b.y ?? minCenterY));
|
|
3015
|
-
for (let i = 1; i < groupNodes.length; i++) {
|
|
3016
|
-
const prevY = groupNodes[i - 1].y ?? minCenterY;
|
|
3017
|
-
const minAllowed = prevY + rowGap * 0.92;
|
|
3018
|
-
if ((groupNodes[i].y ?? minCenterY) < minAllowed) {
|
|
3019
|
-
groupNodes[i].y = minAllowed;
|
|
3020
|
-
}
|
|
3021
|
-
}
|
|
3022
|
-
};
|
|
3023
|
-
enforceGroupStack('p1');
|
|
3024
|
-
enforceGroupStack('shared');
|
|
3025
|
-
enforceGroupStack('p2');
|
|
3026
3002
|
nodes.forEach(n => {
|
|
3027
3003
|
clampNodeToBounds(n);
|
|
3028
3004
|
n.originalFx = n.x;
|
|
@@ -3679,12 +3655,8 @@ class ProfileComparisonLibComponent {
|
|
|
3679
3655
|
const maxY = bounds?.maxY ?? fallbackMaxY;
|
|
3680
3656
|
const clampedX = Math.max(minX, Math.min(maxX, relX));
|
|
3681
3657
|
const clampedY = Math.max(minY, Math.min(maxY, relY));
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
const xElasticity = this.draggedNode.group === 'shared' ? 0.62 : 0.72;
|
|
3685
|
-
const yElasticity = this.draggedNode.group === 'shared' ? 0.58 : 0.68;
|
|
3686
|
-
this.draggedNode.fx = anchorX + (clampedX - anchorX) * xElasticity;
|
|
3687
|
-
this.draggedNode.fy = anchorY + (clampedY - anchorY) * yElasticity;
|
|
3658
|
+
this.draggedNode.fx = clampedX;
|
|
3659
|
+
this.draggedNode.fy = clampedY;
|
|
3688
3660
|
// Keep active force simulation running
|
|
3689
3661
|
this.simulation.alpha(0.3).restart();
|
|
3690
3662
|
}
|