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.
| Task | Start here |
|---|---|
| Mount a drawing | DrawingSession and DrawingCanvas(session: session) |
| Create content with registry defaults | createShape, createText |
| Insert an already-authored record | addElement |
| Connect existing objects | connect |
| Add a label | addConnectorLabel(text: ...) |
| Rename text | session.text.setPlainText |
| Preserve rich formatting while editing | session.text.replace |
| Save committed content | session.document.encode() |
| Observe edits for autosave | addDocumentChangeListener |
| Open saved content | DrawingSession.fromJson with the original custom registry |
| Undo or redo | undo().requireAllowed(), redo().requireAllowed() |
| Customize shapes and label grammar | ShapeRegistry |
| Customize canvas appearance and UI | DrawingConfiguration |
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
DiagramChangeDeniedon a policy denial. - Commands returning
DiagramPolicyDecisionlet you inspect the result. CallrequireAllowed()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,StateErroror 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
| Concern | Owner | Lifetime |
|---|---|---|
| Shapes, ports, text regions, label block grammar | ShapeRegistry and definitions | Establish when creating a session; reuse for saved documents. |
| Creation/connection rules and document validation | Session policies | Canonical admission for API and interactive edits. |
| Before-change approval | Core beforeChange; Flutter DrawingConfiguration.hooks.beforeChange | Canonical admission, including history. |
| Grid, navigation, UI extensions and widget builders | Flutter DrawingConfiguration | Presentation configuration for the session. |
| Font metrics | Text-layout engine | Choose before creating a session that will be mounted. |
| Autosave | Document-change listener | Subscribe once, remove before host disposal. |
| Pointer preview and camera feedback | General/presentation observation | Do not treat transient previews as committed autosave data. |
Platform capabilities
| Capability | Portable session | Flutter session, unmounted | Flutter session, mounted |
|---|---|---|---|
| Create/edit/history/JSON | Yes | Yes | Yes |
| Text metrics | Explicit backend; default fixed metrics | Flutter shaping | Flutter shaping |
| SVG | Prepared image/font resources | Flutter output adapter | Flutter output adapter |
| PNG | Requires raster output adapter | Supported by Flutter output | Supported by Flutter output |
| Navigate or read viewport | No mounted viewport | Requires attachment | Supported |
| Pointer, keyboard and IME | No | No | Supported |
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.