Skip to content

Common tasks

Start with vyuh_diagram_kit in Flutter, or vyuh_diagram_engine in a Dart service. Both expose the same content commands. Keep one session for the editing lifetime; widgets observe it rather than owning a second document.

TaskStart here
Mount a drawingDrawingSession and DrawingCanvas(session: session)
Create content with registry defaultscreateShape, createText
Insert an already-authored recordaddElement
Connect existing objectsconnect
Add a labeladdConnectorLabel(text: ...)
Rename textsession.text.setPlainText
Preserve rich formatting while editingsession.text.replace
Save committed contentsession.document.encode()
Observe edits for autosaveaddDocumentChangeListener
Open saved contentDrawingSession.fromJson with the original custom registry
Undo or redoundo().requireAllowed(), redo().requireAllowed()
Customize shapes and label grammarShapeRegistry
Customize canvas appearance and UIDrawingConfiguration

Create, connect and rename

dart
final source = session.createShape(
  type: Shapes.rectangle,
  worldCenter: const DiagramPoint(100, 100),
  text: 'Draft',
);
final target = session.createText(
  worldCenter: const DiagramPoint(400, 100),
  text: 'Review',
);
final connector = session.connect(source, target);
session.addConnectorLabel(connector, text: 'Ready\nfor review');
session.text.setPlainText(elementId: source, text: 'Proposal').requireAllowed();

setPlainText preserves story identity and replaces formatting with ordinary paragraphs. Newline sequences become paragraph boundaries, including a final empty line. Text that looks like a list remains plain text. Rich-text replacement is available when you need to retain marks or list structure.

For a connector with several labels, pass the desired label's story.id as storyId. An ambiguous connector is denied rather than silently editing its first label. The same canonical admission checks enforce single-line regions, locks and host policies. Authored content that violates grammar is rejected; it is not silently truncated.

For a story with several text slots, also pass slotId. The edit preserves role and slot bindings and leaves all other slots unchanged. Omitting the slot for an ambiguous story is denied.

Handle command outcomes

Use this convention consistently in your host:

  • Commands returning an identity throw DiagramChangeDenied on a policy denial.
  • Commands returning DiagramPolicyDecision let you inspect the result. Call requireAllowed() to use the same exception path as identity-returning commands.
  • Void document edits report denied publication by throwing. Selection and navigation are presentation operations with their own preconditions.
  • Invalid authored data, unknown identities and invalid lifecycle calls can throw ArgumentError, StateError or format errors. Do not catch those as ordinary policy denials.
dart
try {
  session.text.setPlainText(elementId: source, text: 'Approved').requireAllowed();
} on DiagramChangeDenied catch (error) {
  onDenied(error.decision); // Your host's feedback callback.
  // Use error.decision.code to choose host messaging or localization.
  // error.decision.reason is optional human-readable detail.
}

DiagramDenialCode provides stable categories. Host policies created with DiagramPolicyDecision.deny(...) have the policy code. Use DiagramPolicyDecision.denied(code, reason: ...) to supply a category explicitly. Never parse the reason string. A successful individual command is one undoable operation; several statements in a try block are not an atomic batch.

One error-handling boundary

Creation returns the new ID; admitted editing commands return a decision. For an application that handles denials with exceptions, call requireAllowed() on decisions and catch DiagramChangeDenied once at the initiating UI boundary. Keep IDs as IDs: no result wrapper is needed to create and connect two shapes. Named actions use this same decision contract, including typed denial codes.

For several changes that must undo together, register a named action and stage edits through its context. Do not call independent session commands inside that callback. Use context.updateValues for declared data, context.resize for dimensions, and context.updateShape for other shape fields. For a single ordinary text edit, use session.text.setPlainText with a slot ID; manual text-story replacement is an advanced escape hatch.

Choose the correct owner

ConcernOwnerLifetime
Shapes, ports, text regions, label block grammarShapeRegistry and definitionsEstablish when creating a session; reuse for saved documents.
Creation/connection rules and document validationSession policiesCanonical admission for API and interactive edits.
Before-change approvalCore beforeChange; Flutter DrawingConfiguration.hooks.beforeChangeCanonical admission, including history.
Grid, navigation, UI extensions and widget buildersFlutter DrawingConfigurationPresentation configuration for the session.
Font metricsText-layout engineChoose before creating a session that will be mounted.
AutosaveDocument-change listenerSubscribe once, remove before host disposal.
Pointer preview and camera feedbackGeneral/presentation observationDo not treat transient previews as committed autosave data.

Platform capabilities

CapabilityPortable sessionFlutter session, unmountedFlutter session, mounted
Create/edit/history/JSONYesYesYes
Text metricsExplicit backend; default fixed metricsFlutter shapingFlutter shaping
SVGPrepared image/font resourcesFlutter output adapterFlutter output adapter
PNGRequires raster output adapterSupported by Flutter outputSupported by Flutter output
Navigate or read viewportNo mounted viewportRequires attachmentSupported
Pointer, keyboard and IMENoNoSupported

Native shaping is available through the separate Pango library; native resources and fonts need their own setup and disposal. Fixed metrics are useful for deterministic headless work, but do not promise font-accurate output.

DrawingSession.fromSession(coreSession) borrows the existing canonical owner. Configure Flutter text layout on that source before mounting, and dispose the Flutter adapter before its source. It does not create a collaboration participant.

Progress to custom definitions

Follow shapes, then connectors, text, embedded widgets and ownership. Add one concern at a time: outline, ports, text regions, declared values, widget slots, then custom tools. Keep content in declared values and stories; a widget builder is a projection and dispatches through its bounded commands. An enabled snapshot is presentation feedback, not permission to bypass live admission.

A small starting surface

For a new Flutter host, start with:

import 'package:vyuh_diagram_kit/drawing.dart';

This entry point exposes the everyday canvas, session, style, shape-definition and error contracts. It re-exports the same objects, not another implementation. The existing vyuh_diagram_kit.dart import remains supported and provides the full extension surface for custom geometry, tools, effects, text grammars and export adapters. Add it when the task calls for those capabilities.

Draw lines without coordinate bookkeeping

dart
final line = session.createLine(
  start: const DiagramPoint(40, 60),
  end: const DiagramPoint(240, 160),
);
session.createPolyline(
  points: const [
    DiagramPoint(300, 60),
    DiagramPoint(460, 60),
    DiagramPoint(380, 180),
  ],
  closed: true,
);
final corner = session.paths.addPoint(line, after: 'node-0');
session.paths.setHandleMode(line, corner, DiagramPathHandleMode.smooth)
    .requireAllowed();

Points are in world coordinates. The session computes the frame, normalizes nodes, applies parent placement and records one undo step. createPath remains available for authored Bézier nodes, tangent offsets and explicit frames. Creation returns an ID or throws on denial; edits return a decision you can inspect or promote to an exception with requireAllowed().

Declare a custom shape and its inspector

A rectangle with editable text needs only ShapeDefinition(type: 'task'). Standard defaults supply its outline, padded text region, size and capabilities. Declare only the differences:

dart
final task = ShapeDefinition(
  type: 'task',
  displayName: 'Task',
  valueFields: {
    'progress': const NumberValueField(defaultValue: 0, minimum: 0, maximum: 100),
    'status': TextValueField(defaultValue: 'Draft', allowedValues: ['Draft', 'Ready']),
  },
  inspector: ShapeInspectorDefinition([
    ShapePropertyEditor.customValues,
    ShapePropertyEditor.fill,
    ShapePropertyEditor.stroke,
  ]),
);
final taskSession = DrawingSession(shapeRegistry: ShapeRegistry.withStandard([task]));
taskSession.createShape(
  type: 'task',
  worldCenter: const DiagramPoint(100, 100),
  text: 'Review proposal',
);

Omit inspector to derive editors from capabilities, or provide a list to choose their order. Unsupported and duplicate entries reject. Visibility does not change edit permissions. Path and gradient controls use the same declarations.