More UI Tweaks to the visualiser
This commit is contained in:
parent
9e4b687432
commit
47b0528275
@ -409,6 +409,112 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
|
||||
return sharedLaneByTarget;
|
||||
}, [edges, levelByGuid, edgeLaneOffsetByKey, nodePositions]);
|
||||
const mergedRightIncomingByTarget = React.useMemo(() => {
|
||||
const forwardIncomingCount = new Map<string, number>();
|
||||
|
||||
edges.forEach((edge) => {
|
||||
const sourceLevel = levelByGuid.get(edge.sourceGuid) ?? 0;
|
||||
const targetLevel = levelByGuid.get(edge.targetGuid) ?? 0;
|
||||
if (targetLevel > sourceLevel) {
|
||||
forwardIncomingCount.set(
|
||||
edge.targetGuid,
|
||||
(forwardIncomingCount.get(edge.targetGuid) ?? 0) + 1,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const sharedLaneByTarget = new Map<string, number>();
|
||||
|
||||
edges.forEach((edge) => {
|
||||
const source = nodePositions.get(edge.sourceGuid);
|
||||
const target = nodePositions.get(edge.targetGuid);
|
||||
if (!source || !target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceLevel = levelByGuid.get(edge.sourceGuid) ?? 0;
|
||||
const targetLevel = levelByGuid.get(edge.targetGuid) ?? 0;
|
||||
const levelDelta = targetLevel - sourceLevel;
|
||||
if (levelDelta <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetForwardCount = forwardIncomingCount.get(edge.targetGuid) ?? 0;
|
||||
const shouldMergeAdjacent = levelDelta === 1 && targetForwardCount >= 3;
|
||||
if (!(levelDelta > 1 || shouldMergeAdjacent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceShift =
|
||||
(edge.sourceIndex - (edge.sourceCount - 1) / 2) * 2.25;
|
||||
const laneSpread =
|
||||
edgeLaneOffsetByKey.get(`${edge.sourceGuid}->${edge.targetGuid}`) ?? 0;
|
||||
const isLongDownward = levelDelta > 1;
|
||||
const startX = isLongDownward ? source.x + source.width / 2 : source.x;
|
||||
const startY = isLongDownward
|
||||
? source.y + sourceShift * 0.35 + laneSpread * 0.25
|
||||
: source.y + source.height / 2;
|
||||
const endX = target.x + target.width / 2;
|
||||
const endY = isLongDownward
|
||||
? target.y
|
||||
: target.y - target.height / 2 + target.height * 0.5;
|
||||
const verticalSpan = Math.abs(endY - startY);
|
||||
const longRouteExtra =
|
||||
Math.max(0, verticalSpan - 10) * 0.2 + (levelDelta - 1) * 1.2;
|
||||
const laneBaseOffset = isLongDownward ? 6 : 2.5;
|
||||
const candidateLaneX =
|
||||
Math.max(startX, endX) +
|
||||
laneBaseOffset +
|
||||
Math.abs(sourceShift) +
|
||||
(isLongDownward ? longRouteExtra : 0) +
|
||||
laneSpread * 1.2;
|
||||
|
||||
const current = sharedLaneByTarget.get(edge.targetGuid);
|
||||
sharedLaneByTarget.set(
|
||||
edge.targetGuid,
|
||||
current === undefined
|
||||
? candidateLaneX
|
||||
: Math.max(current, candidateLaneX),
|
||||
);
|
||||
});
|
||||
|
||||
return sharedLaneByTarget;
|
||||
}, [edges, levelByGuid, edgeLaneOffsetByKey, nodePositions]);
|
||||
const rightMergeEligibleByTarget = React.useMemo(() => {
|
||||
const forwardIncomingCount = new Map<string, number>();
|
||||
const longIncomingCount = new Map<string, number>();
|
||||
|
||||
edges.forEach((edge) => {
|
||||
const sourceLevel = levelByGuid.get(edge.sourceGuid) ?? 0;
|
||||
const targetLevel = levelByGuid.get(edge.targetGuid) ?? 0;
|
||||
if (targetLevel > sourceLevel) {
|
||||
forwardIncomingCount.set(
|
||||
edge.targetGuid,
|
||||
(forwardIncomingCount.get(edge.targetGuid) ?? 0) + 1,
|
||||
);
|
||||
|
||||
if (targetLevel - sourceLevel > 1) {
|
||||
longIncomingCount.set(
|
||||
edge.targetGuid,
|
||||
(longIncomingCount.get(edge.targetGuid) ?? 0) + 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const eligibility = new Map<
|
||||
string,
|
||||
{ mergeLong: boolean; mergeAdjacent: boolean }
|
||||
>();
|
||||
forwardIncomingCount.forEach((count, targetGuid) => {
|
||||
eligibility.set(targetGuid, {
|
||||
mergeLong: (longIncomingCount.get(targetGuid) ?? 0) >= 2,
|
||||
mergeAdjacent: count >= 3,
|
||||
});
|
||||
});
|
||||
|
||||
return eligibility;
|
||||
}, [edges, levelByGuid]);
|
||||
const connectedByGuid = React.useMemo(() => {
|
||||
const map = new Map<string, Set<string>>();
|
||||
|
||||
@ -597,29 +703,49 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
);
|
||||
const isMergedLeft =
|
||||
levelDelta < 0 && mergedLeftLaneX !== undefined;
|
||||
const mergedRightLaneX = mergedRightIncomingByTarget.get(
|
||||
edge.targetGuid,
|
||||
);
|
||||
const rightMergeEligibility = rightMergeEligibleByTarget.get(
|
||||
edge.targetGuid,
|
||||
);
|
||||
const isMergedRight =
|
||||
mergedRightLaneX !== undefined &&
|
||||
((levelDelta > 1 && rightMergeEligibility?.mergeLong) ||
|
||||
(levelDelta === 1 && rightMergeEligibility?.mergeAdjacent));
|
||||
|
||||
let path = "";
|
||||
let labelX = 0;
|
||||
let labelY = 0;
|
||||
let labelAnchor: "start" | "middle" | "end" = "middle";
|
||||
|
||||
if (levelDelta > 1) {
|
||||
if (levelDelta > 1 || (levelDelta === 1 && isMergedRight)) {
|
||||
// Multi-level downward jumps: route on the right side.
|
||||
const startX = source.x + source.width / 2;
|
||||
const startY = source.y + sourceShift * 0.35 + laneSpread * 0.25;
|
||||
const isLongDownward = levelDelta > 1;
|
||||
const startX = isLongDownward
|
||||
? source.x + source.width / 2
|
||||
: source.x;
|
||||
const startY = isLongDownward
|
||||
? source.y + sourceShift * 0.35 + laneSpread * 0.25
|
||||
: source.y + source.height / 2;
|
||||
const endX = target.x + target.width / 2;
|
||||
const endY = target.y + targetShift * 0.35 + laneSpread * 0.25;
|
||||
const endY = isLongDownward
|
||||
? target.y + targetShift * 0.35 + laneSpread * 0.25
|
||||
: target.y;
|
||||
const verticalSpan = Math.abs(endY - startY);
|
||||
const longRouteExtra =
|
||||
Math.max(0, verticalSpan - 10) * 0.2 + (levelDelta - 1) * 1.2;
|
||||
const laneX =
|
||||
mergedRightLaneX ??
|
||||
Math.max(startX, endX) +
|
||||
6 +
|
||||
Math.abs(sourceShift) +
|
||||
longRouteExtra +
|
||||
laneSpread * 1.2;
|
||||
|
||||
path = `M ${startX} ${startY} L ${laneX} ${startY} L ${laneX} ${endY} L ${endX} ${endY}`;
|
||||
path = isMergedRight
|
||||
? `M ${startX} ${startY} L ${laneX} ${startY} L ${laneX} ${endY}`
|
||||
: `M ${startX} ${startY} L ${laneX} ${startY} L ${laneX} ${endY} L ${endX} ${endY}`;
|
||||
labelX = startX + 0.9;
|
||||
labelY = startY - 1.5 - Math.abs(laneSpread) * 0.12;
|
||||
labelAnchor = "start";
|
||||
@ -716,7 +842,9 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
className={`visualiser-outcome-connector-line${edgeIntensityClass}${edgeIsDim ? " is-dim" : ""}`}
|
||||
d={path}
|
||||
markerEnd={
|
||||
isMergedLeft ? undefined : "url(#visualiser-arrow)"
|
||||
isMergedLeft || isMergedRight
|
||||
? undefined
|
||||
: "url(#visualiser-arrow)"
|
||||
}
|
||||
/>
|
||||
{label && (
|
||||
@ -770,6 +898,43 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
);
|
||||
},
|
||||
)}
|
||||
{Array.from(mergedRightIncomingByTarget.entries()).map(
|
||||
([targetGuid, laneX]) => {
|
||||
const target = nodePositions.get(targetGuid);
|
||||
if (!target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const endX = target.x + target.width / 2;
|
||||
const endY = target.y;
|
||||
const mergedIsActive =
|
||||
hoveredGuid !== null &&
|
||||
(hoveredGuid === targetGuid ||
|
||||
edges.some(
|
||||
(edge) =>
|
||||
edge.targetGuid === targetGuid &&
|
||||
edge.sourceGuid === hoveredGuid &&
|
||||
(levelByGuid.get(edge.targetGuid) ?? 0) >
|
||||
(levelByGuid.get(edge.sourceGuid) ?? 0),
|
||||
));
|
||||
const mergedIsDim = hoveredGuid !== null && !mergedIsActive;
|
||||
const mergedIntensityClass =
|
||||
hoveredGuid === targetGuid
|
||||
? " is-active-incoming"
|
||||
: mergedIsActive
|
||||
? " is-active-outgoing"
|
||||
: "";
|
||||
|
||||
return (
|
||||
<path
|
||||
key={`merged-right-${targetGuid}`}
|
||||
className={`visualiser-outcome-connector-line${mergedIntensityClass}${mergedIsDim ? " is-dim" : ""}`}
|
||||
d={`M ${laneX} ${endY} L ${endX} ${endY}`}
|
||||
markerEnd="url(#visualiser-arrow)"
|
||||
/>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user