@naniteninja/profile-comparison-lib 1.0.24 → 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,6 +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));
|
|
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
|
+
}
|
|
2684
2708
|
// --- Scan matrix for max similarity to normalize spring distances ---
|
|
2685
2709
|
let matrixMax = 0;
|
|
2686
2710
|
if (this.matrixData?.rows) {
|
|
@@ -2758,7 +2782,7 @@ class ProfileComparisonLibComponent {
|
|
|
2758
2782
|
return nodes.filter(n => n.group === 'p1' || n.group === 'p2');
|
|
2759
2783
|
};
|
|
2760
2784
|
const ySimilarityFloor = Math.max(0.18, springThreshold - 0.06);
|
|
2761
|
-
for (let pass = 0; pass <
|
|
2785
|
+
for (let pass = 0; pass < 6; pass++) {
|
|
2762
2786
|
const snapshotY = new Map();
|
|
2763
2787
|
nodes.forEach(node => {
|
|
2764
2788
|
snapshotY.set(node.id, node.targetY ?? (minCenterY + verticalSpan * 0.5));
|
|
@@ -2782,57 +2806,27 @@ class ProfileComparisonLibComponent {
|
|
|
2782
2806
|
return;
|
|
2783
2807
|
const semanticY = weightedY / totalWeight;
|
|
2784
2808
|
const currentY = snapshotY.get(node.id) ?? semanticY;
|
|
2785
|
-
const blend = node.group === 'shared' ? 0.
|
|
2809
|
+
const blend = node.group === 'shared' ? 0.84 : 0.78;
|
|
2786
2810
|
node.targetY = Math.max(minCenterY, Math.min(maxY, currentY + (semanticY - currentY) * blend));
|
|
2787
2811
|
});
|
|
2788
2812
|
}
|
|
2789
|
-
const groupCounts = {
|
|
2790
|
-
p1: p1Items.length,
|
|
2791
|
-
shared: shared.length,
|
|
2792
|
-
p2: p2Items.length,
|
|
2793
|
-
};
|
|
2794
|
-
const fullVerticalSpan = Math.max(rowGap, maxY - minCenterY);
|
|
2795
2813
|
const buildNodeBounds = (node) => {
|
|
2796
|
-
const halfW = getHalfWidth(node.label);
|
|
2797
|
-
const globalMinX = getNodeMinX(node.label);
|
|
2798
|
-
const globalMaxX = getNodeMaxX(node.label);
|
|
2799
|
-
const leftLimit = zoneShared - halfW - centerGuard;
|
|
2800
|
-
const rightLimit = zoneShared + halfW + centerGuard;
|
|
2801
2814
|
let minX = globalMinX;
|
|
2802
2815
|
let maxX = globalMaxX;
|
|
2803
2816
|
if (node.group === 'p1') {
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
maxX = minX;
|
|
2817
|
+
minX = p1BoundsMinX;
|
|
2818
|
+
maxX = p1BoundsMaxX;
|
|
2807
2819
|
}
|
|
2808
2820
|
else if (node.group === 'p2') {
|
|
2809
|
-
minX =
|
|
2810
|
-
|
|
2811
|
-
minX = maxX;
|
|
2812
|
-
}
|
|
2813
|
-
else {
|
|
2814
|
-
// Shared chips should align directly under the centered Shared title.
|
|
2815
|
-
const centerX = Math.max(globalMinX, Math.min(globalMaxX, zoneShared));
|
|
2816
|
-
minX = centerX;
|
|
2817
|
-
maxX = centerX;
|
|
2818
|
-
}
|
|
2819
|
-
const baseY = node.targetY ?? (minCenterY + fullVerticalSpan * 0.5);
|
|
2820
|
-
const groupCount = groupCounts[node.group];
|
|
2821
|
-
let ySlack = rowGap * 2.2;
|
|
2822
|
-
if (node.group === 'shared') {
|
|
2823
|
-
ySlack = Math.max(rowGap * 4.2, fullVerticalSpan * 0.58);
|
|
2821
|
+
minX = p2BoundsMinX;
|
|
2822
|
+
maxX = p2BoundsMaxX;
|
|
2824
2823
|
}
|
|
2825
2824
|
else {
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
}
|
|
2829
|
-
let minY = Math.max(minCenterY, baseY - ySlack);
|
|
2830
|
-
let maxYForNode = Math.min(maxY, baseY + ySlack);
|
|
2831
|
-
if (minY > maxYForNode) {
|
|
2832
|
-
minY = minCenterY;
|
|
2833
|
-
maxYForNode = maxY;
|
|
2825
|
+
minX = sharedBoundsMinX;
|
|
2826
|
+
maxX = sharedBoundsMaxX;
|
|
2834
2827
|
}
|
|
2835
|
-
|
|
2828
|
+
// Y should be column-wide so any chip can travel vertically within its lane.
|
|
2829
|
+
return { minX, maxX, minY: minCenterY, maxY };
|
|
2836
2830
|
};
|
|
2837
2831
|
const nodeBounds = new Map();
|
|
2838
2832
|
nodes.forEach(node => nodeBounds.set(node.id, buildNodeBounds(node)));
|
|
@@ -2974,20 +2968,21 @@ class ProfileComparisonLibComponent {
|
|
|
2974
2968
|
return;
|
|
2975
2969
|
const targetY = weightedY / totalWeight;
|
|
2976
2970
|
const dy = targetY - (node.y ?? targetY);
|
|
2971
|
+
const sparseNeighbors = neighbors.length <= 2;
|
|
2977
2972
|
const alignStrength = node.group === 'shared'
|
|
2978
|
-
? (
|
|
2979
|
-
: 0.
|
|
2973
|
+
? (sparseNeighbors ? 0.95 : 0.8)
|
|
2974
|
+
: (sparseNeighbors ? 0.82 : 0.68);
|
|
2980
2975
|
node.vy = (node.vy ?? 0) + dy * alignStrength * alpha;
|
|
2981
2976
|
});
|
|
2982
2977
|
};
|
|
2983
2978
|
this.ngZone.runOutsideAngular(() => {
|
|
2984
2979
|
const sim = forceSimulation(nodes)
|
|
2985
|
-
.alphaDecay(0.
|
|
2980
|
+
.alphaDecay(0.014)
|
|
2986
2981
|
.stop()
|
|
2987
2982
|
.force('link', forceLink(links)
|
|
2988
2983
|
.id(d => d.id)
|
|
2989
2984
|
.distance(d => d.distance)
|
|
2990
|
-
.strength(d => d.kind === 'cross' ? d.strength *
|
|
2985
|
+
.strength(d => d.kind === 'cross' ? d.strength * 1.45 : d.strength * 0.86))
|
|
2991
2986
|
.force('collide', forceCollide((d) => {
|
|
2992
2987
|
const baseRadius = getHalfWidth(d.label) * 0.55 + ProfileComparisonLibComponent.CHIP_COLLISION_BORDER_PADDING;
|
|
2993
2988
|
return Math.min(58, Math.max(ProfileComparisonLibComponent.CHIP_RADIUS + 8, baseRadius));
|
|
@@ -2996,7 +2991,7 @@ class ProfileComparisonLibComponent {
|
|
|
2996
2991
|
.iterations(2))
|
|
2997
2992
|
.force('xBound', forceX(d => d.targetX ?? zoneShared)
|
|
2998
2993
|
.strength(d => d.group === 'shared' ? 1.15 : 0.85))
|
|
2999
|
-
.force('yTargetSeed', forceY(d => d.targetY ?? minCenterY).strength(0.
|
|
2994
|
+
.force('yTargetSeed', forceY(d => d.targetY ?? minCenterY).strength(0.1))
|
|
3000
2995
|
.force('crossAlign', crossAlignForce);
|
|
3001
2996
|
for (let i = 0; i < 420 && sim.alpha() > 0.0035; i++) {
|
|
3002
2997
|
sim.tick();
|
|
@@ -3004,21 +2999,6 @@ class ProfileComparisonLibComponent {
|
|
|
3004
2999
|
clampNodeToBounds(n);
|
|
3005
3000
|
});
|
|
3006
3001
|
}
|
|
3007
|
-
const enforceGroupStack = (group) => {
|
|
3008
|
-
const groupNodes = nodes
|
|
3009
|
-
.filter(n => n.group === group)
|
|
3010
|
-
.sort((a, b) => (a.y ?? minCenterY) - (b.y ?? minCenterY));
|
|
3011
|
-
for (let i = 1; i < groupNodes.length; i++) {
|
|
3012
|
-
const prevY = groupNodes[i - 1].y ?? minCenterY;
|
|
3013
|
-
const minAllowed = prevY + rowGap * 0.92;
|
|
3014
|
-
if ((groupNodes[i].y ?? minCenterY) < minAllowed) {
|
|
3015
|
-
groupNodes[i].y = minAllowed;
|
|
3016
|
-
}
|
|
3017
|
-
}
|
|
3018
|
-
};
|
|
3019
|
-
enforceGroupStack('p1');
|
|
3020
|
-
enforceGroupStack('shared');
|
|
3021
|
-
enforceGroupStack('p2');
|
|
3022
3002
|
nodes.forEach(n => {
|
|
3023
3003
|
clampNodeToBounds(n);
|
|
3024
3004
|
n.originalFx = n.x;
|
|
@@ -3675,12 +3655,8 @@ class ProfileComparisonLibComponent {
|
|
|
3675
3655
|
const maxY = bounds?.maxY ?? fallbackMaxY;
|
|
3676
3656
|
const clampedX = Math.max(minX, Math.min(maxX, relX));
|
|
3677
3657
|
const clampedY = Math.max(minY, Math.min(maxY, relY));
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
const xElasticity = this.draggedNode.group === 'shared' ? 0.62 : 0.72;
|
|
3681
|
-
const yElasticity = this.draggedNode.group === 'shared' ? 0.58 : 0.68;
|
|
3682
|
-
this.draggedNode.fx = anchorX + (clampedX - anchorX) * xElasticity;
|
|
3683
|
-
this.draggedNode.fy = anchorY + (clampedY - anchorY) * yElasticity;
|
|
3658
|
+
this.draggedNode.fx = clampedX;
|
|
3659
|
+
this.draggedNode.fy = clampedY;
|
|
3684
3660
|
// Keep active force simulation running
|
|
3685
3661
|
this.simulation.alpha(0.3).restart();
|
|
3686
3662
|
}
|