nextgenSOFTWARE
SOFTWARE NEXT GENERATION

NgDiagram — Interactive diagram

Live component previewTry the controls · demonstration data
Open preview ↗

Manual · Getting started · Code examples · Models

When to use it

Display and edit a graph of nodes and directed or undirected connections.

Appearance and live example

Try the Blazor example · Flutter example code

The live Blazor preview is embedded above. Use Reset to restore its initial state. Flutter implementation code is provided below; run the Flutter gallery from the installed developer workspace. Dialogs and drawers start closed; use their Open button. Tooltips appear on hover or keyboard focus.

Behavior and practical rules

Selection and movement

Click a node or connection to select it. The other selection type is first cleared through its callback with null. Store both selected IDs. Unknown IDs are not highlighted; remove stale selection when deleting data. Dragging previews a new position, and only pointer release proposes coordinates through ValueChanged/onChanged. Accept by returning the proposed graph; rejection retains the original coordinates and removes the drag preview. Selection is a separate proposal and may still change when movement is rejected. Escape cancels drag. Tab focuses nodes; Enter/Space selects; arrows move 10 units, Shift+arrow moves 1. Movement stays inside the canvas. Flutter exposes keyboard-selectable Connections; Blazor SVG connections are focusable.

Labels and empty state

NodeLabel/nodeLabel and EdgeLabel/edgeLabel format plain-text display and accessible names without changing raw IDs, coordinates or JSON. Full labels are used in accessible names and tooltips/list entries; canvas labels may be shortened to fit. EmptyText/emptyText replaces the empty-graph message. The default values retain existing labels. These are not arbitrary templates.

Zoom and viewport

Zoom changes by 25 percentage points, within 25–200%. Fit width fits the entire canvas width; 100% resets zoom. The viewport is 420 logical/CSS pixels tall, with internal scrolling. Zoom does not alter model coordinates or JSON. Fit diagram fits node bounds in both axes with 24 px padding and centers by scrolling; it preserves selection and works in ReadOnly. Empty graphs disable it. Minimum zoom can still require scrolling for large graphs; canvas boundaries constrain centering and long connection labels may extend beyond bounds. Fitting is explicit, not automatic layout.

NgDiagramModel.FitView/fitView(viewportWidth,viewportHeight) returns NgDiagramView (Zoom/ScrollX/ScrollY or lowerCamelCase). Sizes must be finite and positive. An empty graph returns 100% and zero scroll.

Model and persistence

Value is immutable. Store the new graph from callbacks; an independent replacement cancels active drag. ReadOnly or a missing ValueChanged/onChanged disables movement but retains selection, zoom and scrolling. SelectionReadOnly/selectionReadOnly independently disables node and edge selection; movement can remain enabled. Missing selection callbacks disable their respective selection action without clearing the other selection. A successful selection first requests clearing the opposite ID; both IDs remain parent-owned. Any parent update cancels active drag and invalidates captured actions, including replacement graphs reusing node IDs. Zoom also cancels drag. Blazor rechecks graph identity and permissions after awaiting selection callbacks, before proposing a move. Host-side asynchronous responses still need ordering against newer data. The application owns create/edit/delete actions and their permissions. The gallery shows a properties panel, connections, 20-change history and import; NgDiagram itself has no undo history.

UpsertNode/UpsertEdge add or replace by ID. RemoveNode also removes incident edges; RemoveEdge keeps nodes. Move(id,x,y) clamps coordinates to the canvas and returns a new model. For example: graph = graph.Move("start",80,120), or graph = graph.move('start',80,120). ToJson/toJson and FromJson/fromJson use the shared version-1 Nextgen schema, not third-party JSON. Catch invalid imports and retain the previous graph. No automatic file storage or upload.

Geometry and limits

Shapes: rectangle, ellipse, diamond (PascalCase enum names in C#). Nodes store ID, text, top-left X/Y and width/height (default 160×80). Labels are visually shortened but available in tooltips/accessibility/properties. Connections run straight between shape boundaries along the centers; directed connections have an end arrow. On-canvas connection labels shorten to 24 characters.

Canvas dimensions: 200–5000 (default 1000×600); up to 100 nodes and 200 edges. Nodes: 60–400 × 40–240, fully inside the canvas. IDs: 1–64 ASCII letters/digits/_/-, unique separately for nodes and edges. Node text is nonempty and at most 100 UTF-16 units; edge text 0–100. Coordinates are finite. Edges require two different existing nodes. Cycles and parallel edges are allowed; workflow rules belong to the application. Overlaps are not resolved automatically; coincident centers yield a zero-length line.

No automatic layout, obstacle routing, connector drawing, pointer resizing, groups, multiple selection, image/templates, export, minimap, pinch zoom or collaboration. Blazor loads diagram.js automatically from static web assets and requires interactive rendering; if drag fails, check the module and ValueChanged handling. Native/mobile and screen-reader audits remain separate.

Blazor example

Razor / Blazor
<div class="row">
<NgButton OnClick="()=>readOnly=!readOnly">Toggle move lock</NgButton>
<NgButton OnClick="()=>selectionReadOnly=!selectionReadOnly">Toggle selection lock</NgButton>
<NgButton OnClick="()=>accept=!accept">Toggle acceptance</NgButton>
<NgButton OnClick="()=>graph=Sample(120,140)">Replace graph</NgButton>
<NgButton OnClick="()=>graph=graph.Nodes.Count==0?Sample():new()">Toggle empty graph</NgButton>
</div>
<p>Movement: @(readOnly?"locked":"enabled") · Selection: @(selectionReadOnly?"locked":"enabled") · Acceptance: @(accept?"enabled":"rejected")</p>
<p>Start position: @(graph.Nodes.Count==0?"none":$"{graph.Nodes[0].X}, {graph.Nodes[0].Y}") · Move proposals: @moves</p>
<NgDiagram Label="Process" Value="graph" ValueChanged="Change" ReadOnly="readOnly" SelectionReadOnly="selectionReadOnly"
    NodeLabel="@(n=>$"Task {n.Text}")" EdgeLabel="@(e=>$"Flow {e.Text}")" EmptyText="Nothing to connect"
    @bind-SelectedNodeId="selectedNode" @bind-SelectedEdgeId="selectedEdge"/>
<p>Node: @(selectedNode??"none") · Connection: @(selectedEdge??"none")</p>
@code {
    private string? selectedNode, selectedEdge;
    private bool readOnly,selectionReadOnly;
    private bool accept=true;
    private int moves;
    private NgDiagramModel graph=Sample();
    private static NgDiagramModel Sample(double x=40,double y=100)=>new([
        new("start","Start",x,y,Shape:NgDiagramShape.Ellipse),
        new("work","Processing",360,100)
    ],[new("next","start","work","Continue")]);
    private void Change(NgDiagramModel next){moves++;if(accept)graph=next;}
}

Flutter example

Dart / Flutter
NgDiagramModel sample([double x=40,double y=100]) => NgDiagramModel(nodes: [
  NgDiagramNode('start', 'Start', x, y, shape: NgDiagramShape.ellipse),
  const NgDiagramNode('work', 'Processing', 360, 100),
], edges: const [NgDiagramEdge('next', 'start', 'work', text: 'Continue')]);
late NgDiagramModel graph=sample();
String? selectedNode, selectedEdge;
bool readOnly=false, selectionReadOnly=false, accept=true;
int moves=0;
Widget build(BuildContext context) => Column(crossAxisAlignment:CrossAxisAlignment.stretch, children:[
  Wrap(spacing:8,runSpacing:8,children:[
    NgButton(onPressed:()=>setState(()=>readOnly=!readOnly),child:const Text('Toggle move lock')),
    NgButton(onPressed:()=>setState(()=>selectionReadOnly=!selectionReadOnly),child:const Text('Toggle selection lock')),
    NgButton(onPressed:()=>setState(()=>accept=!accept),child:const Text('Toggle acceptance')),
    NgButton(onPressed:()=>setState(()=>graph=sample(120,140)),child:const Text('Replace graph')),
    NgButton(onPressed:()=>setState(()=>graph=graph.nodes.isEmpty?sample():NgDiagramModel()),child:const Text('Toggle empty graph')),
  ]),
  Text('Movement: ${readOnly?"locked":"enabled"} · Selection: ${selectionReadOnly?"locked":"enabled"} · Acceptance: ${accept?"enabled":"rejected"}'),
  Text('Start position: ${graph.nodes.isEmpty?"none":"${graph.nodes.first.x.round()}, ${graph.nodes.first.y.round()}"} · Move proposals: $moves'),
  NgDiagram(label: 'Process', value: graph, readOnly:readOnly, selectionReadOnly:selectionReadOnly,
    nodeLabel:(n)=>'Task ${n.text}',edgeLabel:(e)=>'Flow ${e.text}',emptyText:'Nothing to connect',
    onChanged: (v) => setState(() {moves++;if(accept)graph=v;}),
    selectedNodeId: selectedNode, selectedEdgeId: selectedEdge,
    onSelectedNodeIdChanged: (v) => setState(() => selectedNode = v),
    onSelectedEdgeIdChanged: (v) => setState(() => selectedEdge = v)),
  Text('Node: ${selectedNode??"none"} · Connection: ${selectedEdge??"none"}'),
]);

Blazor parameters and events

Names are case-sensitive. ? permits null. EditorRequired parameters are marked required; other values use the initializer or CLR default.

Parameter Type Default / requirement Purpose
Label string "Diagram" Visible label or accessible name.
Value NgDiagramModel required Current value; see state ownership and model rules above.
ValueChanged EventCallback<NgDiagramModel> not set New value for the parent to store and pass back.
SelectedNodeId string? not set See the behavior rules and model contract above.
SelectedNodeIdChanged EventCallback<string?> not set See the behavior rules and model contract above.
SelectedEdgeId string? not set See the behavior rules and model contract above.
SelectedEdgeIdChanged EventCallback<string?> not set See the behavior rules and model contract above.
ReadOnly bool false Prevent changes while retaining supported viewing actions.
SelectionReadOnly bool false See the behavior rules and model contract above.
NodeLabel Func<NgDiagramNode,string>? not set See the behavior rules and model contract above.
EdgeLabel Func<NgDiagramEdge,string>? not set See the behavior rules and model contract above.
EmptyText string "No nodes yet." See the behavior rules and model contract above.

Flutter parameters and events

required marks a mandatory argument even if its type is nullable. key is the standard Flutter Key. The exact constructor below specifies defaults.

Argument Type Purpose
label String Visible label or accessible name.
value NgDiagramModel Current value; see state ownership and model rules above.
onChanged ValueChanged<NgDiagramModel>? Store the new value using setState or your state manager.
selectedNodeId String? See the behavior rules and model contract above.
selectedEdgeId String? See the behavior rules and model contract above.
onSelectedNodeIdChanged ValueChanged<String?>? See the behavior rules and model contract above.
onSelectedEdgeIdChanged ValueChanged<String?>? See the behavior rules and model contract above.
readOnly bool Prevent changes while retaining supported viewing actions.
selectionReadOnly bool See the behavior rules and model contract above.
nodeLabel String Function(NgDiagramNode)? See the behavior rules and model contract above.
edgeLabel String Function(NgDiagramEdge)? See the behavior rules and model contract above.
emptyText String See the behavior rules and model contract above.
Dart / Flutter
const NgDiagram({
    super.key,
    this.label = 'Diagram',
    required this.value,
    this.onChanged,
    this.selectedNodeId,
    this.selectedEdgeId,
    this.onSelectedNodeIdChanged,
    this.onSelectedEdgeIdChanged,
    this.readOnly = false,
    this.selectionReadOnly = false,
    this.nodeLabel,
    this.edgeLabel,
    this.emptyText = 'No nodes yet.',
  });

Integration and troubleshooting

If the UI does not update, confirm that the parent stores callback values and passes them back. Blazor event handlers need interactive rendering; Flutter requires setState or a state manager. Submit form data only after successful validation.

Check accessible labels, empty/invalid states, disabled actions and narrow layouts. Keyboard support is not full screen-reader certification. Current visual verification targets Web in visible Chrome on Linux/iMac; native mobile platforms are a separate phase.

Source and complete examples

API declarations are generated from current source. Curated examples and behavior live in scripts/manual_content.py; regenerate with python3 scripts/generate_manual.py.