Skip to content
D
Documentation

Configure ports

how-to
3 min readUpdated

Declare connection points on each node to control their glyphs, labels, shared layout, and data-type rules; the same node specs work across Grafloria's framework bindings.

When to use declared ports

Use declared ports when a node needs named inputs and outputs, more than the four default connection points, or visible distinctions between connection types. The graph data belongs to the diagram model; the framework binding converts your node specs into live models, and the instance connects the rendered canvas to engine behavior.

Each node can declare a ports array on its NodeSpec. Give each port an id, then add a shape, a label, a group, or a dataType as needed. Nodes without ports keep their four deterministic default ports.

Declare and mount the ports

The samples below all render the same small data-flow diagram: a Source node has number and string outputs, and a Convert node has matching inputs. Both nodes arrange their ports in named side groups. The port glyphs have different shapes and labels; the initial number-to-number link is present in every sample. A registered type palette adds type colours and can declare compatibility beyond exact type-name matches.

The JavaScript and framework mounting patterns are covered in Build runnable workflows; this page adds port declarations and a registered type palette to the node data.

ts
import {
  render,
  portTypeRegistry,
  type EdgeSpec,
  type NodeSpec,
} from '@grafloria/element';

portTypeRegistry.registerAll([
  { name: 'number', color: '#2563eb', compatibleWith: ['number'] },
  { name: 'string', color: '#9333ea', compatibleWith: ['string'] },
]);

const nodes: NodeSpec[] = [
  {
    id: 'source',
    position: { x: 80, y: 130 },
    size: { width: 160, height: 120 },
    label: 'Source',
    metadata: {
      portGroups: {
        outputs: {
          id: 'outputs',
          side: 'right',
          visibility: 'always',
          layout: { strategy: 'sideLinear', args: { padding: 18 } },
        },
      },
    },
    ports: [
      {
        id: 'number-out',
        group: 'outputs',
        type: 'output',
        dataType: 'number',
        shape: { shape: 'circle', size: 14 },
        label: { text: 'number', layout: 'outside' },
      },
      {
        id: 'string-out',
        group: 'outputs',
        type: 'output',
        dataType: 'string',
        shape: { shape: 'square', size: 14 },
        label: { text: 'string', layout: 'outside' },
      },
    ],
  },
  {
    id: 'convert',
    position: { x: 430, y: 130 },
    size: { width: 160, height: 120 },
    label: 'Convert',
    metadata: {
      portGroups: {
        inputs: {
          id: 'inputs',
          side: 'left',
          visibility: 'always',
          layout: { strategy: 'sideLinear', args: { padding: 18 } },
        },
      },
    },
    ports: [
      {
        id: 'number-in',
        group: 'inputs',
        type: 'input',
        dataType: 'number',
        shape: { shape: 'diamond', size: 14 },
        label: { text: 'number', layout: 'outside' },
      },
      {
        id: 'string-in',
        group: 'inputs',
        type: 'input',
        dataType: 'string',
        shape: { shape: 'triangle', size: 14 },
        label: { text: 'string', layout: 'outside' },
      },
    ],
  },
];

const edges: EdgeSpec[] = [
  { id: 'number-link', source: 'source', sourceHandle: 'number-out', target: 'convert', targetHandle: 'number-in' },
];

const host = document.createElement('div');
host.style.width = '100%';
host.style.height = '420px';
document.body.append(host);

requestAnimationFrame(() => render({ nodes, edges }, host));

When the canvas mounts, you see two labelled nodes, four always-visible ports, and the initial number-to-number connection. The source and target groups inherit their side and sideLinear layout; each port supplies its own glyph, label, and type. In every sample, the palette colors number ports blue and string ports purple. Drag number-out to string-in to see the mismatch refused; both registrations allow only the same named type.

Port fields that shape the result

OptionTypeDefaultWhat it does
portsPortSpec[]Omitted: four deterministic default portsDeclares the node's own connection points.
shapeShape objectCircleChooses circle, square, diamond, triangle, or an SVG path; size sets the glyph box in pixels.
labelLabel objectNo label; when present, layout defaults to outsideAdds text near the port. Choose inside, outside, orthogonal, or radial placement.
groupstringNo groupInherits shared settings from the same id in the node's metadata.portGroups; a port's own settings override group values.
metadata.portGroupsNode metadata objectNo groupsDefines shared port settings such as side, visibility, shape, and layout. A sideLinear layout distributes group members along that side.
dataTypestringUntypedAssociates the port with a registered type for glyph colour and connection compatibility.

The default visibility mode shows ports on hover. The examples set each group's visibility to always so the ports and their labels appear immediately. shape: 'path' accepts caller-provided SVG path data; use it when the built-in glyphs do not distinguish your port.

See the port behaviors

The port shapes demo compares circle, square, diamond, triangle, and custom-path glyphs as distinct SVG primitives.

The port labels demo shows labels placed inside, outside, and orthogonal to their glyphs.

The port groups and layouts demo compares a side column, a line segment, and an ellipse spread.

The typed ports demo registers number and string types, then shows a matching connection and a mismatched target.

Pitfalls

  • If the node spec omits ports, it keeps the four deterministic defaults; add a ports array to replace that set with your declared ports.
  • Put shared configuration under the node's metadata.portGroups and match each port's group value to that entry's id. A port-level value takes precedence over the inherited group value.
  • Register named data types to assign colours or declare compatibility beyond exact matches. Compatibility is directional when compatibleWith is used; an untyped endpoint remains unconstrained.
  • A dataType alone does not pick a colour. Register a color for the type, then the renderer uses it for that type's port glyph.

Was this page helpful?