Broke out the magic numbers
This commit is contained in:
parent
e2f5d2dc89
commit
fcd0de35ea
@ -26,6 +26,33 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
Map<string, { x: number; y: number; width: number; height: number }>
|
||||
>(new Map());
|
||||
|
||||
// Vertical spacing between stacked verdict labels in merged outcome connectors
|
||||
const VERDICT_LABEL_SPACING = 3;
|
||||
|
||||
// SVG coordinate constants
|
||||
const SVG_CENTER_POSITION = 50;
|
||||
const SVG_COORDINATE_MAX = 100;
|
||||
|
||||
// Column layout constants
|
||||
const COLUMN_MIN_WIDTH = 220;
|
||||
|
||||
// Outcome connector curve constants
|
||||
const CURVE_BASE_OFFSET = 20;
|
||||
const CURVE_OFFSET_MULTIPLIER = 0.3;
|
||||
const CURVE_CONTROL_OFFSET_FACTOR = 0.3;
|
||||
const CURVE_OFFSET_STEP = 15;
|
||||
|
||||
// Bezier curve position constants
|
||||
const BEZIER_LABEL_POSITION = 0.5; // Midpoint for label placement
|
||||
const BEZIER_ARROW_POSITION = 0.3; // Position along curve for directional arrow
|
||||
const BEZIER_ARROW_TANGENT_DELTA = 0.01; // Small offset for tangent calculation
|
||||
|
||||
// Arrow dimensions
|
||||
const ARROW_HALF_WIDTH = 1.5;
|
||||
const DIRECTIONAL_ARROW_SIZE = 3;
|
||||
const END_ARROW_OFFSET = 3;
|
||||
const END_ARROW_HALF_WIDTH = 1;
|
||||
|
||||
// Calculate column range directly from taskColumnMap in render
|
||||
const allColumns = Array.from(taskColumnMap.current.values());
|
||||
const columnRange = {
|
||||
@ -206,7 +233,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
(nodeRect.left - levelRect.left + nodeRect.width / 2) /
|
||||
levelRect.width;
|
||||
// Convert to SVG coordinates (0-100)
|
||||
nodePositions.push(relativeCenter * 100);
|
||||
nodePositions.push(relativeCenter * SVG_COORDINATE_MAX);
|
||||
});
|
||||
|
||||
if (nodePositions.length > 0) {
|
||||
@ -270,9 +297,9 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
|
||||
const getColumnPercent = (column: number): number => {
|
||||
const numColumns = columnRange.max - columnRange.min + 1;
|
||||
if (numColumns <= 1) return 50;
|
||||
if (numColumns <= 1) return SVG_CENTER_POSITION;
|
||||
const gridCol = column - columnRange.min;
|
||||
return ((gridCol + 0.5) / numColumns) * 100;
|
||||
return ((gridCol + 0.5) / numColumns) * SVG_COORDINATE_MAX;
|
||||
};
|
||||
|
||||
// Calculate connector positions based on grid columns
|
||||
@ -280,7 +307,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
levelTasks: CreateWorkflowTemplateVersion["tasks"][],
|
||||
): number[] => {
|
||||
const numColumns = columnRange.max - columnRange.min + 1;
|
||||
if (numColumns <= 1) return [50];
|
||||
if (numColumns <= 1) return [SVG_CENTER_POSITION];
|
||||
|
||||
return levelTasks.map((task) => {
|
||||
const predecessors = (task.config.predecessors as string[]) ?? [];
|
||||
@ -297,13 +324,13 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
predColumns.reduce((sum, col) => sum + col, 0) / predColumns.length;
|
||||
const gridCol = avgPredColumn - columnRange.min;
|
||||
// Map averaged column to percentage for centered merges
|
||||
return ((gridCol + 0.5) / numColumns) * 100;
|
||||
return ((gridCol + 0.5) / numColumns) * SVG_COORDINATE_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
const gridCol = taskColumn - columnRange.min;
|
||||
// Map grid column to percentage (e.g., 2 columns: col 0 = 25%, col 1 = 75%)
|
||||
return ((gridCol + 0.5) / numColumns) * 100;
|
||||
return ((gridCol + 0.5) / numColumns) * SVG_COORDINATE_MAX;
|
||||
});
|
||||
};
|
||||
|
||||
@ -312,7 +339,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
excludeTasks?: Set<string>,
|
||||
): number[] => {
|
||||
const numColumns = columnRange.max - columnRange.min + 1;
|
||||
if (numColumns <= 1) return [50];
|
||||
if (numColumns <= 1) return [SVG_CENTER_POSITION];
|
||||
|
||||
const predColumns = new Set<number>();
|
||||
|
||||
@ -402,7 +429,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
/>
|
||||
<polygon
|
||||
className="visualiser-connector-branch-arrow"
|
||||
points={`${x - 1.5},28 ${x + 1.5},28 ${x},34`}
|
||||
points={`${x - ARROW_HALF_WIDTH},28 ${x + ARROW_HALF_WIDTH},28 ${x},34`}
|
||||
/>
|
||||
</g>
|
||||
))}
|
||||
@ -412,8 +439,8 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
|
||||
if (count <= 1) {
|
||||
// For a single child, use predecessor position if available
|
||||
let startX = 50;
|
||||
let endX = 50;
|
||||
let startX = SVG_CENTER_POSITION;
|
||||
let endX = SVG_CENTER_POSITION;
|
||||
|
||||
// If single child with predecessor positions, connect from the appropriate predecessor
|
||||
if (positions && positions.length === 1) {
|
||||
@ -475,7 +502,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
/>
|
||||
<polygon
|
||||
className="visualiser-connector-branch-arrow"
|
||||
points={`${endX - 1.5},34 ${endX + 1.5},34 ${endX},40`}
|
||||
points={`${endX - ARROW_HALF_WIDTH},34 ${endX + ARROW_HALF_WIDTH},34 ${endX},40`}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@ -487,7 +514,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
? positions
|
||||
: Array.from(
|
||||
{ length: count },
|
||||
(_, index) => ((index + 1) / (count + 1)) * 100,
|
||||
(_, index) => ((index + 1) / (count + 1)) * SVG_COORDINATE_MAX,
|
||||
);
|
||||
|
||||
// Guard against empty or invalid positions
|
||||
@ -516,7 +543,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
const topX =
|
||||
predecessorPositions && predecessorPositions.length > 0
|
||||
? predecessorPositions[0]
|
||||
: 50;
|
||||
: SVG_CENTER_POSITION;
|
||||
|
||||
return (
|
||||
<svg
|
||||
@ -550,7 +577,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
/>
|
||||
<polygon
|
||||
className="visualiser-connector-branch-arrow"
|
||||
points={`${x - 1.5},28 ${x + 1.5},28 ${x},34`}
|
||||
points={`${x - ARROW_HALF_WIDTH},28 ${x + ARROW_HALF_WIDTH},28 ${x},34`}
|
||||
/>
|
||||
</g>
|
||||
))}
|
||||
@ -562,7 +589,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
const getOutcomeConnections = (): Array<{
|
||||
sourceGuid: string;
|
||||
targetGuid: string;
|
||||
verdict: string;
|
||||
verdicts: string[];
|
||||
sourceX: number;
|
||||
targetX: number;
|
||||
sourceY: number;
|
||||
@ -576,7 +603,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
const connections: Array<{
|
||||
sourceGuid: string;
|
||||
targetGuid: string;
|
||||
verdict: string;
|
||||
verdicts: string[];
|
||||
sourceX: number;
|
||||
targetX: number;
|
||||
sourceY: number;
|
||||
@ -588,6 +615,21 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
curveOffset: number;
|
||||
}> = [];
|
||||
|
||||
// First, collect all outcome connections temporarily
|
||||
const tempConnections: Array<{
|
||||
sourceGuid: string;
|
||||
targetGuid: string;
|
||||
verdict: string;
|
||||
sourceX: number;
|
||||
targetX: number;
|
||||
sourceY: number;
|
||||
targetY: number;
|
||||
sourceWidth: number;
|
||||
sourceHeight: number;
|
||||
targetWidth: number;
|
||||
targetHeight: number;
|
||||
}> = [];
|
||||
|
||||
tasks.forEach((task) => {
|
||||
const outcomes =
|
||||
(task.config.outcomeActions as Array<{
|
||||
@ -601,7 +643,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
const targetPos = taskDOMPositions.get(outcome.task);
|
||||
|
||||
if (sourcePos && targetPos) {
|
||||
connections.push({
|
||||
tempConnections.push({
|
||||
sourceGuid: task.config.guid as string,
|
||||
targetGuid: outcome.task,
|
||||
verdict: outcome.verdict,
|
||||
@ -613,13 +655,40 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
sourceHeight: sourcePos.height,
|
||||
targetWidth: targetPos.width,
|
||||
targetHeight: targetPos.height,
|
||||
curveOffset: 0, // Will be calculated below
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Group by source+target pair to combine connections with same start and end
|
||||
const connectionGroups = new Map<string, typeof tempConnections>();
|
||||
tempConnections.forEach((conn) => {
|
||||
const key = `${conn.sourceGuid}→${conn.targetGuid}`;
|
||||
const group = connectionGroups.get(key) ?? [];
|
||||
group.push(conn);
|
||||
connectionGroups.set(key, group);
|
||||
});
|
||||
|
||||
// Merge connections with same source and target
|
||||
connectionGroups.forEach((group) => {
|
||||
const first = group[0];
|
||||
connections.push({
|
||||
sourceGuid: first.sourceGuid,
|
||||
targetGuid: first.targetGuid,
|
||||
verdicts: group.map((c) => c.verdict),
|
||||
sourceX: first.sourceX,
|
||||
sourceY: first.sourceY,
|
||||
targetX: first.targetX,
|
||||
targetY: first.targetY,
|
||||
sourceWidth: first.sourceWidth,
|
||||
sourceHeight: first.sourceHeight,
|
||||
targetWidth: first.targetWidth,
|
||||
targetHeight: first.targetHeight,
|
||||
curveOffset: 0, // Will be calculated below
|
||||
});
|
||||
});
|
||||
|
||||
// Detect overlapping connections to the same target and offset them
|
||||
const targetGroups = new Map<string, typeof connections>();
|
||||
connections.forEach((conn) => {
|
||||
@ -635,7 +704,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
group.forEach((conn, idx) => {
|
||||
// Spread offsets: -10, 0, 10 for 3 connections, etc.
|
||||
const totalOffsets = group.length;
|
||||
const offsetStep = 15;
|
||||
const offsetStep = CURVE_OFFSET_STEP;
|
||||
const baseOffset = -((totalOffsets - 1) * offsetStep) / 2;
|
||||
conn.curveOffset = baseOffset + idx * offsetStep;
|
||||
});
|
||||
@ -668,8 +737,8 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
columnRange.max > columnRange.min
|
||||
? `repeat(${
|
||||
columnRange.max - columnRange.min + 1
|
||||
}, minmax(220px, 1fr))`
|
||||
: "220px",
|
||||
}, minmax(${COLUMN_MIN_WIDTH}px, 1fr))`
|
||||
: `${COLUMN_MIN_WIDTH}px`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
@ -858,15 +927,18 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
? conn.targetX + conn.targetWidth / 2 // right edge for rightward curves
|
||||
: conn.targetX - conn.targetWidth / 2; // left edge for leftward curves
|
||||
|
||||
const baseControlOffsetX = 20 + Math.abs(conn.curveOffset) * 0.3;
|
||||
const baseControlOffsetX =
|
||||
CURVE_BASE_OFFSET +
|
||||
Math.abs(conn.curveOffset) * CURVE_OFFSET_MULTIPLIER;
|
||||
const controlOffsetY =
|
||||
(Math.abs(conn.targetY - conn.sourceY) / 2) * 0.3;
|
||||
(Math.abs(conn.targetY - conn.sourceY) / 2) *
|
||||
CURVE_CONTROL_OFFSET_FACTOR;
|
||||
|
||||
// Apply horizontal offset to spread overlapping curves
|
||||
const offsetX = conn.curveOffset;
|
||||
|
||||
// Calculate text position at the curve's midpoint (t=0.5 on Bezier curve)
|
||||
const t = 0.5;
|
||||
const t = BEZIER_LABEL_POSITION;
|
||||
const p0 = { x: sourceEdgeX, y: conn.sourceY };
|
||||
const p1 = {
|
||||
x: sourceEdgeX + baseControlOffsetX * curveDirection + offsetX,
|
||||
@ -891,7 +963,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
Math.pow(t, 3) * p3.y;
|
||||
|
||||
// Calculate arrow position at t=0.3 for directional indicator
|
||||
const tArrow = 0.3;
|
||||
const tArrow = BEZIER_ARROW_POSITION;
|
||||
const arrowX =
|
||||
Math.pow(1 - tArrow, 3) * p0.x +
|
||||
3 * Math.pow(1 - tArrow, 2) * tArrow * p1.x +
|
||||
@ -904,7 +976,8 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
Math.pow(tArrow, 3) * p3.y;
|
||||
|
||||
// Calculate tangent for arrow rotation
|
||||
const tArrowDelta = 0.31;
|
||||
const tArrowDelta =
|
||||
BEZIER_ARROW_POSITION + BEZIER_ARROW_TANGENT_DELTA;
|
||||
const arrowX2 =
|
||||
Math.pow(1 - tArrowDelta, 3) * p0.x +
|
||||
3 * Math.pow(1 - tArrowDelta, 2) * tArrowDelta * p1.x +
|
||||
@ -934,23 +1007,32 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
{/* Directional arrow along curve */}
|
||||
<polygon
|
||||
className="visualiser-outcome-connector-arrow"
|
||||
points={`-3,-1.5 -3,1.5 0,0`}
|
||||
points={`-${DIRECTIONAL_ARROW_SIZE},-${ARROW_HALF_WIDTH} -${DIRECTIONAL_ARROW_SIZE},${ARROW_HALF_WIDTH} 0,0`}
|
||||
transform={`translate(${arrowX},${arrowY}) rotate(${angle})`}
|
||||
/>
|
||||
{/* End arrow */}
|
||||
<polygon
|
||||
className="visualiser-outcome-connector-arrow"
|
||||
points={`${targetEdgeX - 1},${conn.targetY - 3} ${targetEdgeX + 1},${conn.targetY - 3} ${targetEdgeX},${conn.targetY}`}
|
||||
points={`${targetEdgeX - END_ARROW_HALF_WIDTH},${conn.targetY - END_ARROW_OFFSET} ${targetEdgeX + END_ARROW_HALF_WIDTH},${conn.targetY - END_ARROW_OFFSET} ${targetEdgeX},${conn.targetY}`}
|
||||
/>
|
||||
<text
|
||||
x={textX}
|
||||
y={textY}
|
||||
className="visualiser-outcome-connector-label"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
{conn.verdict}
|
||||
</text>
|
||||
{/* Render verdict labels - multiple if merged */}
|
||||
{conn.verdicts.map((verdict, vIdx) => {
|
||||
const labelOffsetY =
|
||||
(vIdx - (conn.verdicts.length - 1) / 2) *
|
||||
VERDICT_LABEL_SPACING;
|
||||
return (
|
||||
<text
|
||||
key={`verdict-${vIdx}`}
|
||||
x={textX}
|
||||
y={textY + labelOffsetY}
|
||||
className="visualiser-outcome-connector-label"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
{verdict}
|
||||
</text>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user