AI Model Workflow Graph
This example renders a fixed-position AI workflow as custom parameter cards and connects individual input and output rows with synthesized fake lines. It also combines saved coordinates, a shared type-color system, minimap navigation, and click-based dependency highlighting to make a dense read-only workflow easier to inspect.
AI Model Workflow Graph
What This Example Builds
This example builds a fixed-position workflow board that looks like an AI generation pipeline rather than a standard node diagram. Each node is rendered as a dark rounded card with a title, left and right parameter columns, and stacked option rows. Some option rows are rendered as special blocks, including a multiline textarea and an image preview.
The visible connections do not attach to the node box as a whole. They attach to individual input and output rows, so the graph reads like a parameter dependency board. Users can pan and zoom the canvas, use the minimap to navigate the wide layout, and click a node to highlight only the dependencies that belong to that step.
How the Data Is Organized
The source data starts in MyGraph-data.ts as myJsonData, with a rootId, a nodes array, and empty lines and fakeLines arrays. Each node carries a data object with three sections: input, output, and options. That structure is what the custom node renderer turns into the card layout.
The dependency model is stored separately in modelRelations. Each relation names an upstream model id and output parameter together with a downstream model id and input parameter. Before setJsonData() runs, the example converts those records into relation-graph fakeLines, copies connected output types onto matching input records, and assigns saved x and y coordinates from a predefined position table with an index-based fallback.
In a real application, the same structure could represent ML pipeline steps, ETL tasks, workflow jobs, or any process where a node exposes named inputs, named outputs, and configuration fields.
How relation-graph Is Used
The example uses relation-graph as a viewer, not an editor. RGProvider supplies graph context, and RGHooks.useGraphInstance() retrieves the instance used to clear previous state, load JSON data, move the viewport to the center, zoom to fit, and inspect existing fake lines during click handling.
The graph is configured with a fixed layout, RGJunctionPoint.border, a translucent default node color, and no default node border. That leaves the visual node surface to the custom card component rendered through RGSlotOnNode. Inside that card, every input and output row is wrapped in RGConnectTarget, which gives relation-graph a concrete DOM endpoint for line attachment. RGSlotOnView adds RGMiniView, so the example keeps the built-in overview navigation without adding external UI.
The styling layer is important to the result. my-relation-graph.scss turns the canvas into a dark workspace, removes the default node shell appearance, and replaces the checked-state glow with a custom outline around the slotted content. MyAIModelNode.scss styles the card layout and makes the generated I_ and O_ endpoint elements relatively positioned so the DOM-targeted lines can resolve correctly.
Key Interactions
- On mount, the example prepares fake lines, restores saved coordinates, loads the graph, and fits the workflow into the viewport.
- Clicking a node highlights only the fake lines that reference that node, which turns the graph into a dependency tracer instead of a static picture.
- The minimap helps users navigate the unusually wide fixed canvas without losing context.
- Because each parameter row is a separate endpoint, users can inspect port-to-port dependencies directly in the canvas instead of inferring them from node-level edges.
Key Code Fragments
This fragment shows that the graph is intentionally configured as a fixed-layout viewer with the default node shell minimized.
const graphOptions: RGOptions = {
debug: false,
defaultJunctionPoint: RGJunctionPoint.border,
defaultNodeColor: 'rgba(100, 100, 100, 0.5)',
defaultNodeBorderWidth: 0,
layout: {
layoutName: 'fixed'
},
};
This fragment shows how a separate workflow relation list is turned into fake lines and how the code normalizes connected input types before rendering.
myJsonData.fakeLines = modelRelations.map((relation, index) => {
const outputModel = getModel(relation.output_model);
const inputModel = getModel(relation.input_model);
if (!outputModel || !inputModel) return null;
const outputParam = getModelOutputParam(outputModel, relation.output_param_name);
const inputParam = getModelInputParam(inputModel, relation.input_param_name);
if (inputParam && outputParam) {
inputParam.type = outputParam.type; // fix data bug
}
This fragment shows that the synthesized lines target specific parameter rows inside node content instead of whole-node anchors.
return {
id: `line-${index}`,
from: `O_${relation.output_model}-${relation.output_param_name}`,
fromJunctionPoint: RGJunctionPoint.right,
to: `I_${relation.input_model}-${relation.input_param_name}`,
toJunctionPoint: RGJunctionPoint.left,
text: '',
lineShape: RGLineShape.StandardCurve,
lineWidth: 3,
color: outputParam ? dataTypeColorMap[outputParam.type] : '#ccc',
isFakeLine: true
};
This fragment proves that each input row in the custom node card is exposed as an RGConnectTarget.
{node.data?.input?.map((input: any) => (
<RGConnectTarget key={input.name} targetId={`I_${node.id}-${input.name}`} junctionPoint={RGJunctionPoint.left}>
<span className="c-dot" style={{ backgroundColor: dataTypeColorMap[input.type] || '#ccc' }}/>
{input.name}
</RGConnectTarget>
))}
This fragment shows how the example replays saved coordinates and frames the prepared workflow on first load.
myJsonData.nodes.forEach((model, index) => {
const position = nodesPosition.find(n => n.id === model.id);
if (position) {
model.x = position.x;
model.y = position.y;
} else {
model.x = index * 50;
model.y = index * 50;
}
});
graphInstance.clearGraph();
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
This fragment shows the click-based dependency inspection behavior.
const onNodeClick = (node: RGNode, $event: RGUserEvent) => {
console.log('onNodeClick:', node.text);
const elLines = graphInstance.getFakeLines();
elLines.forEach((line: any) => {
const keywords = `_${node.id}-`;
const refed = line.from.includes(keywords) || line.to.includes(keywords);
line.animation = refed ? 2 : undefined;
});
};
What Makes This Example Distinct
The comparison data marks this example as much more than a generic node-style sample. Its strongest differentiator is the combination of custom card nodes, per-row RGConnectTarget endpoints, synthesized fake lines from a separate modelRelations list, and click-based highlighting of only the connected dependencies. That combination is flagged as rare in the prepared comparison data.
Compared with node-content-lines, this example derives the visible dependency lines from workflow metadata at runtime and turns parameter types into a shared color language across both ports and wires. Compared with table-relationship, it uses the same DOM-endpoint technique for an AI workflow board instead of a schema viewer, and it adds type propagation plus dependency highlighting. Compared with node, it uses the node slot and minimap for one focused workflow-inspection pattern rather than a broader node-styling playground.
The result is a stronger starting point for fixed, read-only process maps where users need to inspect exact upstream and downstream parameter flow.
Where Else This Pattern Applies
- Read-only AI or automation workflow snapshots that need to preserve a curated layout instead of using automatic layout.
- Data lineage or ETL screens where fields, not just tables or jobs, need explicit source-to-target wiring.
- Approval and rules-engine views where each step exposes named inputs, outputs, and configuration parameters.
- Manufacturing, media-processing, or orchestration boards that need click-driven dependency focus without full graph editing.