Merge pull request 'fix(dashboard): restore graph visual preferences (#247)' (#250) from fix/dashboard-graph-visual-247 into main
This commit is contained in:
@@ -7,25 +7,23 @@ const FEEDBACK_OFFSET_X = 100;
|
|||||||
const FEEDBACK_RADIUS = 16;
|
const FEEDBACK_RADIUS = 16;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build an SVG path for a feedback (back) edge that routes to the right of the nodes.
|
* Build an SVG path for a feedback (back) edge that routes to the given side of the nodes.
|
||||||
* The path goes: source right → arc → vertical up → arc → target right
|
* The path goes: source → arc → vertical up → arc → target
|
||||||
*/
|
*/
|
||||||
function feedbackPath(sourceX: number, sourceY: number, targetX: number, targetY: number): string {
|
function feedbackPath(sourceX: number, sourceY: number, targetX: number, targetY: number, side: "right" | "left"): string {
|
||||||
const rightX = Math.max(sourceX, targetX) + FEEDBACK_OFFSET_X;
|
const d = side === "right" ? 1 : -1;
|
||||||
|
const offsetX =
|
||||||
|
side === "right"
|
||||||
|
? Math.max(sourceX, targetX) + FEEDBACK_OFFSET_X
|
||||||
|
: Math.min(sourceX, targetX) - FEEDBACK_OFFSET_X;
|
||||||
const r = FEEDBACK_RADIUS;
|
const r = FEEDBACK_RADIUS;
|
||||||
|
|
||||||
// Start from source right side, go right, then up, then left to target right side
|
|
||||||
const segments = [
|
const segments = [
|
||||||
`M ${sourceX} ${sourceY}`,
|
`M ${sourceX} ${sourceY}`,
|
||||||
// Horizontal to the right
|
`L ${offsetX - d * r} ${sourceY}`,
|
||||||
`L ${rightX - r} ${sourceY}`,
|
`Q ${offsetX} ${sourceY} ${offsetX} ${sourceY - r}`,
|
||||||
// Arc turning upward
|
`L ${offsetX} ${targetY + r}`,
|
||||||
`Q ${rightX} ${sourceY} ${rightX} ${sourceY - r}`,
|
`Q ${offsetX} ${targetY} ${offsetX - d * r} ${targetY}`,
|
||||||
// Vertical upward
|
|
||||||
`L ${rightX} ${targetY + r}`,
|
|
||||||
// Arc turning left
|
|
||||||
`Q ${rightX} ${targetY} ${rightX - r} ${targetY}`,
|
|
||||||
// Horizontal left to target
|
|
||||||
`L ${targetX} ${targetY}`,
|
`L ${targetX} ${targetY}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -57,10 +55,13 @@ export function ConditionEdge(props: EdgeProps) {
|
|||||||
let defaultLabelY: number;
|
let defaultLabelY: number;
|
||||||
|
|
||||||
if (isFeedback) {
|
if (isFeedback) {
|
||||||
// Custom feedback path routed to the right
|
const side = edgeData?.feedbackSide ?? "right";
|
||||||
path = feedbackPath(sourceX, sourceY, targetX, targetY);
|
path = feedbackPath(sourceX, sourceY, targetX, targetY, side);
|
||||||
const rightX = Math.max(sourceX, targetX) + FEEDBACK_OFFSET_X;
|
const offsetX =
|
||||||
defaultLabelX = rightX;
|
side === "right"
|
||||||
|
? Math.max(sourceX, targetX) + FEEDBACK_OFFSET_X
|
||||||
|
: Math.min(sourceX, targetX) - FEEDBACK_OFFSET_X;
|
||||||
|
defaultLabelX = offsetX;
|
||||||
defaultLabelY = (sourceY + targetY) / 2;
|
defaultLabelY = (sourceY + targetY) / 2;
|
||||||
} else {
|
} else {
|
||||||
const result = getSmoothStepPath({
|
const result = getSmoothStepPath({
|
||||||
@@ -78,9 +79,8 @@ export function ConditionEdge(props: EdgeProps) {
|
|||||||
defaultLabelY = result[2];
|
defaultLabelY = result[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
const stroke = isFallback ? "var(--color-text-muted)" : "var(--color-accent)";
|
const stroke = "var(--color-accent)";
|
||||||
const strokeDasharray = isFallback ? "5 4" : undefined;
|
const label = isFallback ? "" : (edgeData?.condition ?? "");
|
||||||
const label = edgeData?.condition ?? "";
|
|
||||||
|
|
||||||
// Use pre-computed label position if available, otherwise fall back to default
|
// Use pre-computed label position if available, otherwise fall back to default
|
||||||
const labelX = edgeData?.labelX ?? defaultLabelX;
|
const labelX = edgeData?.labelX ?? defaultLabelX;
|
||||||
@@ -92,7 +92,7 @@ export function ConditionEdge(props: EdgeProps) {
|
|||||||
id={id}
|
id={id}
|
||||||
path={path}
|
path={path}
|
||||||
markerEnd={markerEnd}
|
markerEnd={markerEnd}
|
||||||
style={{ stroke, strokeWidth: 1.5, strokeDasharray }}
|
style={{ stroke, strokeWidth: 1.5 }}
|
||||||
/>
|
/>
|
||||||
{label !== "" && (
|
{label !== "" && (
|
||||||
<EdgeLabelRenderer>
|
<EdgeLabelRenderer>
|
||||||
@@ -102,7 +102,7 @@ export function ConditionEdge(props: EdgeProps) {
|
|||||||
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
|
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
|
||||||
background: "var(--color-surface)",
|
background: "var(--color-surface)",
|
||||||
border: "1px solid var(--color-border)",
|
border: "1px solid var(--color-border)",
|
||||||
color: isFallback ? "var(--color-text-muted)" : "var(--color-text)",
|
color: "var(--color-text)",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function RoleNode(props: NodeProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`px-3 py-2 rounded-md border-2 text-xs font-medium cursor-pointer ${isActive ? "wf-node-pulse" : ""}`}
|
className={`px-3 py-2 rounded-md border-2 text-xs font-medium ${t.state !== "default" ? "cursor-pointer" : ""} ${isActive ? "wf-node-pulse" : ""}`}
|
||||||
style={{
|
style={{
|
||||||
width: 180,
|
width: 180,
|
||||||
height: 60,
|
height: 60,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export type ConditionEdgeData = {
|
|||||||
isFallback: boolean;
|
isFallback: boolean;
|
||||||
isFeedback: boolean;
|
isFeedback: boolean;
|
||||||
isSelfLoop: boolean;
|
isSelfLoop: boolean;
|
||||||
|
feedbackSide: "right" | "left" | null;
|
||||||
labelX: number | null;
|
labelX: number | null;
|
||||||
labelY: number | null;
|
labelY: number | null;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
|
|||||||
@@ -173,6 +173,8 @@ function computeLayout(input: LayoutInput): LayoutResult {
|
|||||||
// Build edges with label positions
|
// Build edges with label positions
|
||||||
// For feedback edges (target rank < source rank), we'll compute label at midpoint
|
// For feedback edges (target rank < source rank), we'll compute label at midpoint
|
||||||
// of the right-side arc. The actual SVG path is drawn by ConditionEdge component.
|
// of the right-side arc. The actual SVG path is drawn by ConditionEdge component.
|
||||||
|
// Track feedback edge count per target node for alternating sides
|
||||||
|
const feedbackCountByTarget = new Map<string, number>();
|
||||||
const edges: Edge[] = input.edges.map((e) => {
|
const edges: Edge[] = input.edges.map((e) => {
|
||||||
const isFallback = e.condition === "FALLBACK";
|
const isFallback = e.condition === "FALLBACK";
|
||||||
const isSelfLoop = e.from === e.to;
|
const isSelfLoop = e.from === e.to;
|
||||||
@@ -185,13 +187,20 @@ function computeLayout(input: LayoutInput): LayoutResult {
|
|||||||
|
|
||||||
let labelX: number | null = null;
|
let labelX: number | null = null;
|
||||||
let labelY: number | null = null;
|
let labelY: number | null = null;
|
||||||
|
let feedbackSide: "right" | "left" | null = null;
|
||||||
|
|
||||||
if (sourcePos !== undefined && targetPos !== undefined) {
|
if (sourcePos !== undefined && targetPos !== undefined) {
|
||||||
if (isFeedback) {
|
if (isFeedback) {
|
||||||
// Label on the right side of the feedback arc
|
// Alternate feedback edges left/right per target node
|
||||||
const rightX = centerX + ROLE_NODE_WIDTH / 2 + FEEDBACK_OFFSET_X;
|
const count = feedbackCountByTarget.get(e.to) ?? 0;
|
||||||
|
feedbackCountByTarget.set(e.to, count + 1);
|
||||||
|
feedbackSide = count % 2 === 0 ? "right" : "left";
|
||||||
|
const offsetX =
|
||||||
|
feedbackSide === "right"
|
||||||
|
? centerX + ROLE_NODE_WIDTH / 2 + FEEDBACK_OFFSET_X
|
||||||
|
: centerX - ROLE_NODE_WIDTH / 2 - FEEDBACK_OFFSET_X;
|
||||||
const midY = (sourcePos.y + sourcePos.h / 2 + targetPos.y + targetPos.h / 2) / 2;
|
const midY = (sourcePos.y + sourcePos.h / 2 + targetPos.y + targetPos.h / 2) / 2;
|
||||||
labelX = rightX;
|
labelX = offsetX;
|
||||||
labelY = midY;
|
labelY = midY;
|
||||||
} else if (!isSelfLoop) {
|
} else if (!isSelfLoop) {
|
||||||
// Forward edge: label between source bottom and target top
|
// Forward edge: label between source bottom and target top
|
||||||
@@ -214,6 +223,7 @@ function computeLayout(input: LayoutInput): LayoutResult {
|
|||||||
isFallback,
|
isFallback,
|
||||||
isFeedback,
|
isFeedback,
|
||||||
isSelfLoop,
|
isSelfLoop,
|
||||||
|
feedbackSide,
|
||||||
labelX,
|
labelX,
|
||||||
labelY,
|
labelY,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user