JavaScript is required

PCB Component Wiring Editor

This example shows how to build a PCB-style component wiring workspace with fixed-position nodes, a drag-in component palette, and custom chip or part renderers that expose many pin-level connection targets. It also combines multi-selection, selected-wire path editing, runtime canvas settings, and image export on the same relation-graph surface.

Building a PCB-Style Component Wiring Workspace with Pin-Level Targets

What This Example Builds

This example builds a lightweight PCB-style wiring workspace rather than a generic relationship viewer. The screen shows a fixed board canvas, a floating description and settings window, a left-side component palette, custom-rendered chips and passive parts, and ruler overlays that make the surface feel like a drafting tool.

Users can drag components from the palette into the canvas, select one or many nodes, delete the currently selected node, draw wires between exposed pins or pads, and edit the path of a selected wire. The main point of interest is the combination of dense custom connection targets and editor behavior on one compact relation-graph surface.

How the Data Is Organized

The seeded graph data comes from my-data-api.ts as a plain RGJsonData object with nodes, lines, and fakeLines. The nodes are flat records with id, text, type, x, and y, so the fixed layout can place each component directly from saved coordinates instead of computing a layout at runtime.

The most important preprocessing choice is structural rather than algorithmic. Palette templates only define a type and text, and startCreatingNodePlot(...) turns a palette drag into a positioned node by generating a new id and assigning x and y on drop. Seed connections are stored in fakeLines and reference specific connector ids such as N-pvrcW-pin-17 or N-zjCzE-capacitor-output, which means the connection model is pin-to-pin or pin-to-pad rather than node-to-node.

In a production system, the same structure could represent board-level wiring sketches, connector harness diagrams, control cabinet layouts, embedded system prototyping surfaces, or any technical editor where components have multiple named attachment points.

How relation-graph Is Used

The example wraps the page in RGProvider, then retrieves the live graph instance through RGHooks.useGraphInstance(). The graph runs in layoutName: 'fixed', so relation-graph renders components from explicit coordinates loaded through setJsonData(...), and the first viewport adjustment is a simple zoomToFit() after initialization.

The graph options are tuned for an editor shell. defaultNodeBorderWidth is disabled so the custom PCB renderers own the visible shape, defaultLineShape is set to RGLineShape.StandardCurve, defaultJunctionPoint is set to RGJunctionPoint.lr, defaultLineWidth is widened to 3, the built-in toolbar is moved to the right, wheel behavior starts in zoom mode, and drag behavior starts in move mode.

The node slot is the core rendering extension. RGSlotOnNode switches on node.type and mounts custom React components for resistors, capacitors, diodes, transistors, inductors, and several chip packages. Those renderers expose many connection anchors through RGConnectTarget, sometimes on SVG elements and sometimes on HTML pin strips, which lets relation-graph treat arbitrary points on the custom body as valid wire endpoints.

The view slot carries the editing UI that should stay in screen space instead of moving with the graph content. RGSlotOnView mounts the left component rail, RGEditingReferenceLine, RGEditingNodeController, RGEditingLineController, the single-node delete toolbar, and the custom MyCanvasCaliper ruler overlay. The rulers are not built into relation-graph; they are calculated by reading the graph viewport through getViewBoundingClientRect() and converting view coordinates to canvas coordinates with getCanvasXyByViewXy(...).

The runtime APIs drive most of the editor behavior. The example uses startCreatingNodePlot(...) for palette drops, generateNewNodeId() and addNodes(...) for creation, getNodeById(...) to hand the new node into edit mode, removeNode(...) for deletion, toggleEditingNode(...) and setEditingNodes(...) for selection state, setEditingLine(...) for selected-wire editing, getNodesInSelectionView(...) for box selection, clearChecked() for canvas reset, and generateNewUUID() plus addFakeLines(...) to persist newly drawn wires. The shared floating panel also uses setOptions(...) to switch wheel and drag behavior at runtime, and prepareForImageGeneration() plus restoreAfterImageGeneration() to export the canvas as an image.

The stylesheet customizes the graph shell instead of restyling individual nodes ad hoc. It gives the canvas a gray board background, colors the built-in toolbar purple, removes default node chrome, and adds a strong checked-line treatment where the selected wire gets a pink foreground stroke and a wide translucent background stroke.

Key Interactions

  • Dragging a component preview from the left rail onto the canvas creates a new positioned node and immediately hands that node to the editing state.
  • Clicking a node without modifiers selects only that node; clicking with Shift, Ctrl, or Meta toggles it into or out of the current multi-selection.
  • Switching canvas drag mode to selection in the floating settings panel enables drag-box selection, and onCanvasSelectionEnd turns the box result into the current editing-node set.
  • Clicking a wire makes that wire the active editing line, and RGEditingLineController exposes path editing handles for it.
  • Drawing a new wire between exposed targets persists the accepted result as a fake line with generated id and endpoint target-type metadata.
  • Clicking empty canvas space clears edited nodes, clears the active line, and removes checked highlights.
  • Selecting exactly one node shows a delete button above it through RGEditingNodeController and MyNodeToolbar.
  • Opening the floating settings panel lets the user switch wheel mode, switch drag mode, and download an exported image of the current canvas.

Key Code Fragments

This fragment shows that the workspace is intentionally fixed-position and configured as an editor rather than an automatic layout demo.

const graphOptions: RGOptions = {
    debug: false,
    layout: {
        layoutName: 'fixed'
    },
    defaultNodeBorderWidth: 0,
    defaultLineShape: RGLineShape.StandardCurve,
    defaultJunctionPoint: RGJunctionPoint.lr,
    defaultLineWidth: 3,
    toolBarPositionH: 'right',
    wheelEventAction: 'zoom',
    dragEventAction: 'move',
};

This fragment shows how a palette gesture becomes a real graph node with coordinates and immediate edit focus.

const onToolItemMouseDown = (e, nodeTemplate: JsonNodeLike) => {
    graphInstance.startCreatingNodePlot(e.nativeEvent, {
        templateNode: {
            ...nodeTemplate
        },
        onCreateNode: (x, y, nodeTemplate) => {
            const newNode = {
                ...nodeTemplate,
                id: `N-${graphInstance.generateNewNodeId()}`,
                x: x - 20,
                y: y - 20
            };
            graphInstance.addNodes([newNode]);
            graphInstance.setEditingNodes([graphInstance.getNodeById(newNode.id)]);
        }
    });
};

This fragment shows that the connection targets live on the custom component renderer itself, not on a generic node boundary.

<RGConnectTarget forSvg={true} targetId={`${id}-resistor-input`}>
  <circle cx="2" cy="30" r="4" fill="#ef4444" />
</RGConnectTarget>
<RGConnectTarget forSvg={true} targetId={`${id}-resistor-output`}>
  <circle cx="198" cy="30" r="4" fill="#ef4444" />
</RGConnectTarget>

This fragment shows the same pattern on a chip package, where each named pin becomes its own connection anchor.

{leftPins.map((pin) => (
  <div key={pin.label} className="flex items-center justify-end">
    <span className="text-xs font-mono mr-2 text-gray-500">{pin.name}</span>
    <RGConnectTarget targetId={`${id}-pin-${pin.name}`} junctionPoint={RGJunctionPoint.left}>
      <div className="w-8 h-4 bg-gray-400 rounded-sm shadow-inner" />
    </RGConnectTarget>
  </div>
))}

This fragment shows how newly drawn wires are accepted and persisted as fake lines with endpoint target metadata.

const onLineBeCreated = (lineInfo: {
    lineJson: JsonLine,
    fromNode: RGLineTarget | RGNode,
    toNode: RGLineTarget | RGNode
}) => {
    if (lineInfo.toNode && lineInfo.toNode.id) {
        const newLine = {
            ...lineInfo.lineJson,
            fromType: lineInfo.fromNode.targetType,
            toType: lineInfo.toNode.targetType,
            id: graphInstance.generateNewUUID(16)
        };
        graphInstance.addFakeLines([newLine]);
    }
};

This fragment shows the graph-attached editing stack: custom node rendering, a screen-space view slot, snapping guides, node deletion controls, wire-path editing, and the ruler overlay.

<RGSlotOnNode>
    {({ node }: RGNodeSlotProps) => {
        const type = node.type;
        return <PCBItem id={node.id} type={type} />
    }}
</RGSlotOnNode>
<RGSlotOnView>
    <RGEditingReferenceLine adsorption={true} showText={true} />
    <RGEditingNodeController>
        <MyNodeToolbar onRemoveNode={onRemoveNode} />
    </RGEditingNodeController>
    <RGEditingLineController textEditable={false} pathEditable={true} onMoveLineVertexEnd={onMoveLineVertexEnd} />
    <MyCanvasCaliper />
</RGSlotOnView>

This fragment shows that the ruler overlay is computed from relation-graph viewport APIs instead of being a static decoration.

const graphViewBox = graphInstance.getViewBoundingClientRect();
const visibleAreaStart = graphInstance.getCanvasXyByViewXy({
    x: 0,
    y: 0
});
const visibleAreaEnd = graphInstance.getCanvasXyByViewXy({
    x: graphViewBox.width,
    y: graphViewBox.height
});

What Makes This Example Distinct

The comparison data places this example closest to compact editor demos such as editor-button-on-line, create-line-from-node, line-vertex-on-node, change-line-path, and gee-node-alignment-guides, but its emphasis is different from each of them. Compared with editor-button-on-line, the reusable lesson here is not generic node-and-line authoring or inline edge actions. It is dense pin-level wiring on domain-specific component bodies, with new wires stored as fakeLines.

Compared with create-line-from-node and line-vertex-on-node, this example does not teach connection launch from a selected-node toolbar or a target-endpoint picker on ordinary nodes. It teaches a different pattern: embed many predefined targets directly into custom PCB renderers, let users place those components on a fixed surface, and accept new wires against those exposed targets.

Compared with change-line-path and gee-node-alignment-guides, path editing and reference lines are supporting features rather than the main topic. They matter, but the stronger combination is palette-driven component placement, fixed coordinates, arbitrary pin-to-pin connection targets, fake-line persistence, and drafting-style rulers on one technical canvas.

That combination is what makes this example unusually useful as a starting point. It is not a full PCB CAD tool, but it is more domain-specific than a generic graph editor and more behaviorally complete than a single-purpose micro-demo.

Where Else This Pattern Applies

This pattern transfers well to wiring and connector editors where each component needs many named attachment points, such as control cabinet schematics, sensor harness planning, test fixture layouts, or embedded prototype boards. The same approach also fits educational electronics tools where users should wire recognizable parts instead of abstract nodes.

A second extension is any technical workspace that needs external part insertion plus fixed-position editing, even outside electronics. Teams could adapt it to equipment panel design, rack-face patching tools, robotics I/O mapping, or lab instrument hookup planners by swapping the component renderers and keeping the same target, selection, and fake-line persistence model.