Skip to content

Animation & effects ​

A diagram moves in three ways, and none of them is a document edit:

AnimationAPIWhat moves
Effects (this page)controller.effects.play(effect, on: id)A transient emphasis on one shape or connector: particles, pulses, halos, scaling and fading.
Camera motioncontroller.view.show(target, motion: ...)The viewport travels to a target. See Navigation and motion.
Element motionmove, resize, setElementBounds and layout with motion:Elements travel to the geometry an edit gave them. See Animated geometry and Live data.

Effects play on a mounted editing canvas. Playing, changing or stopping one never edits the document, adds undo entries, changes hit testing, or appears in exports.

dart
// Particles flowing along a connector.
final flow = controller.effects.play(
  const Effect.flow(particle: EffectParticle.dash(), count: 6),
  on: 'order-to-invoice',
);

// Blink a shape three times in the brand color, then stop.
final blink = controller.effects.play(
  const Effect.pulse(
    color: DiagramColor(0xFF4F46E5),
    waveform: Waveform.step,
    period: Duration(milliseconds: 500),
    repeat: 3,
  ),
  on: 'invoice',
);
await blink.done;

flow.stop();
Loading interactive example…Open in new tab ↗
Opening the live canvas…

On this page ​

CategoryWhat you can do
The five kindsChoose flow, pulse, halo, scale or fade.
Play, update and stopControl effects with their handles.
Shared valuesTiming, color, sizes and particles every kind reads.
Blink and pulseOscillate smoothly or switch on and off.
Repeat and holdPlay a number of cycles, or hold a fade.
Branding recipesEffects in your brand colors.
Reduced motionWhat plays when the platform asks for less motion.
Custom effectsRegister your own kinds.

The five kinds ​

KindPlays onBehavior
Effect.flow()ConnectorsParticles or marching dashes travel along the route. The connector's own stroke dims to baseOpacity while it plays.
Effect.pulse()Shapes and connectorsOscillates between rest and emphasis. target picks what changes: EffectTarget.fill (a tint over a shape's fill), .stroke (a bright outline or route), .opacity (the element's opacity, between 1 and dim) or .all. Waveform.step makes it blink.
Effect.halo()ShapesA ring around the outline. HaloMode.ripple grows it spread outward and fades it each cycle; HaloMode.steady glows in place.
Effect.scale()ShapesGrows the shape to amount times its size about its center and back, such as 1.08 to breathe or 1.2 to pop.
Effect.fade()Shapes and connectorsChanges opacity once over period: FadeDirection.fadeOut to dim, or FadeDirection.fadeIn from dim. It holds the result until stopped.

Scale and fade present the element itself: its fill, border, text, ports, connector markers, labels and widget slots scale and fade together. Selection handles stay on the element's real bounds, and clicks land where the element really is, not where it appears while scaled.

Playing an effect on an element it does not suit throws an ArgumentError, such as a flow on a shape or a scale on a connector. Several effects may play on one element; their scales and opacities multiply.

Play, update and stop ​

dart
EffectHandle play(Effect effect, {required String on})
void stopAll({String? on})
List<EffectHandle> get playing
List<EffectHandle> playingOn(String id)
EffectRegistry get registry
MemberContract
playStarts effect on the element with id on. Throws ArgumentError naming the first invalid value, for an unregistered kind, or for an element the kind does not suit. When the element does not exist, the returned handle is already stopped. Throws StateError while no editing canvas is mounted.
stopAllStops every effect, or only those on the element with id on.
playing, playingOnHandles of the playing effects, observable in an Observer.
registryThe kinds the canvas can play, including custom ones.
EffectHandle memberContract
elementIdThe element the effect plays on.
effectCurrent values, or null once stopped.
isPlayingWhether the effect still plays.
update(effect)Replaces the values and keeps the timeline, so a color change does not restart a cycle. Returns false once stopped.
stop()Stops the effect; stopping a fade restores the element's opacity. Safe to call more than once.
doneCompletes when the effect stops: stopped, finished its repeat cycles, lost its element, or its canvas unmounted.

Effects follow their element as it moves, resizes or reroutes, including while element motion animates it. Removing the element stops its effects, and undoing the removal does not restart them.

Shared values ​

Every value has a default; each named constructor offers the values its kind reads. copyWith(...) changes any value (clearEndColor: true removes a gradient, clearRepeat: true repeats forever again).

ValueTypeDefaultContract
periodDuration1600 ms (fade 600 ms)One cycle, or the whole change of a fade. Positive.
delayDurationzeroTime before the effect starts, for staggering. A fade shows its start during the delay; other kinds show nothing.
repeatint?nullCycles before the effect stops itself; null repeats until stopped. At least 1.
phasedouble0Offset into the cycle, from 0 to 1, for staggering.
reverseboolfalseRuns each cycle backward, such as particles flowing from end to start.
waveformWaveform.smoothsmooth, step or easeInOut; see blink and pulse.
colorDiagramColor?nullThe effect color; null uses the connector color or shape stroke.
endColorDiagramColor?nullSecond color of a gradient across the effect.
opacitydouble0.9Peak opacity of what the effect draws.
dimdouble0.6 pulse, 0 fadeThe element's opacity at rest for a pulse, and the faded end of a fade.
baseOpacitydouble0.2The connector's own stroke opacity while a flow or pulse plays along it.
targetEffectTarget.allWhat a pulse changes.
modeHaloMode.rippleHow a halo moves.
directionFadeDirection.fadeOutWhich way a fade goes.
amountdouble1.08The size factor a scale reaches. Above 0, at most 10.
widthdouble2Dash, pulse or halo stroke width.
spreaddouble8How far a halo reaches beyond the outline.
particleEffectParticle.circle()The flow particle, below.
countint5Evenly spaced flow particles.
sizedouble3Particle radius, half a character's font size, or an eighth of a dash.
scaleEffectScale.worldworld sizes follow zoom; screen keeps size, width and spread in logical pixels.

Values are checked when an effect plays or updates, in release builds too: controller.effects.play(const Effect.pulse(repeat: 0), on: id) throws an ArgumentError whose name is repeat. isValid and validate() run the same check.

Particles ​

ConstructorShapeSize
EffectParticle.circle()A filled circle.Radius = size.
EffectParticle.dash()A stroke that follows the route: marching dashes.Length = size × 8; thickness = width.
EffectParticle.arrow()A chevron that points the way it travels.Half-length = size.
EffectParticle.path(points)A filled outline through points, in particle units with x pointing the way it travels.Units × size.
EffectParticle.polygon({sides = 4, rotation})A regular polygon, 3 to 20 sides.Circumradius = size.
EffectParticle.character(character)One grapheme, such as an emoji.Font size = size × 2.

Arrows and path particles turn with the route. count changes spacing, not size; dense particles may overlap.

A pulse moves between rest (its element at dim opacity, nothing drawn) and emphasis (full opacity, tinted fill and bright stroke) once per period. The waveform decides how:

WaveformOver one cycle
Waveform.smoothEases from rest to emphasis at mid-cycle and back, like a sine.
Waveform.stepEmphasis for the first half of the cycle, rest for the second: a blink.
Waveform.easeInOutRises and falls along MotionCurve.easeInOut, holding briefly at each end.
dart
// A calm pulse on a connector's route.
controller.effects.play(
  const Effect.pulse(target: EffectTarget.stroke, width: 4),
  on: 'feed',
);

// An urgent blink of a shape's fill, twice a second.
controller.effects.play(
  const Effect.pulse(
    target: EffectTarget.fill,
    waveform: Waveform.step,
    period: Duration(milliseconds: 500),
    color: DiagramColor(0xFFE11D48),
  ),
  on: 'alarm',
);

The same waveforms shape scale and a steady halo: Effect.scale(waveform: Waveform.step) switches between sizes. A fade uses the waveform as its easing.

Repeat and hold ​

repeat: n plays n cycles, then the effect stops itself, its handle stops playing and done completes:

dart
final attention = controller.effects.play(
  const Effect.scale(
    amount: 1.2,
    period: Duration(milliseconds: 360),
    waveform: Waveform.easeInOut,
    repeat: 1,
  ),
  on: 'new-order',
);
await attention.done; // After one pop.

A fade plays once and holds its end state for as long as it plays, without redrawing. Stop it to restore the element:

dart
// Dim everything except the path under review.
final dims = [
  for (final id in unrelatedIds)
    controller.effects.play(const Effect.fade(dim: 0.25), on: id),
];
// Later:
for (final dim in dims) {
  dim.stop();
}

Stagger several effects with delay or phase:

dart
for (final (index, id) in steps.indexed) {
  controller.effects.play(
    Effect.halo(delay: Duration(milliseconds: 200 * index), repeat: 1),
    on: id,
  );
}

Branding recipes ​

Every drawing kind takes color and endColor, so effects can carry a brand palette:

dart
const brand = DiagramColor(0xFF4F46E5);
const accent = DiagramColor(0xFFF97316);

// A gradient flow from the brand color to the accent.
controller.effects.play(
  const Effect.flow(color: brand, endColor: accent, count: 8),
  on: 'pipeline',
);

// A steady brand halo on the selected step.
controller.effects.play(
  const Effect.halo(mode: HaloMode.steady, color: brand, spread: 10),
  on: 'current-step',
);

// Fixed-size arrows that read at every zoom.
controller.effects.play(
  const Effect.flow(
    particle: EffectParticle.arrow(),
    color: brand,
    size: 6,
    scale: EffectScale.screen,
  ),
  on: 'handoff',
);

Colors adapt to the canvas theme like authored colors do. The connector effects and shape effects examples offer a brand color picker for every kind.

Reduced motion ​

When the platform asks to reduce motion (MediaQuery.disableAnimations), effects do not move. Each holds a static emphasis instead: a pulse or scale at its peak, a rippling halo half spread, flow particles still, and a fade at its end. Effects with a repeat count still stop after their cycles, so done completes as it would otherwise.

Custom effects ​

Register an EffectDefinition through a Flutter extension's effects. The definition is in package:vyuh_diagram_flutter/extension_api.dart; Effect, EffectKind and EffectPlacement are in the main library.

dart
import 'dart:math' as math;

import 'package:vyuh_diagram_flutter/extension_api.dart';
import 'package:vyuh_diagram_flutter/vyuh_diagram_flutter.dart';

const orbit = EffectKind('app.orbit');

final orbitEffects = DiagramFlutterExtension(
  id: 'app.orbit',
  effects: [
    EffectDefinition(
      kind: orbit,
      label: 'Orbit',
      placement: EffectPlacement.around,
      properties: const {EffectProperty.color, EffectProperty.speed},
      paint: (canvas, context) {
        final bounds = context.shape.bounds;
        final radius = math.max(bounds.width, bounds.height) / 2 + 8;
        canvas.drawArc(
          Rect.fromCircle(
            center: Offset(bounds.center.x, bounds.center.y),
            radius: radius,
          ),
          context.progress * math.pi * 2,
          math.pi / 2,
          false,
          context.createPaint()..style = PaintingStyle.stroke,
        );
      },
    ),
  ],
);

final controller = DiagramController(
  configuration: DiagramConfiguration(extensions: [orbitEffects]),
);
DiagramCanvas(controller: controller);
controller.effects.play(const Effect(orbit), on: 'gateway');

See the custom effect example for a complete dashed ring that the inspector edits.

PlacementPlays onPainting
EffectPlacement.alongConnectorsAlong the route; badge labels stay readable above it.
EffectPlacement.insideShapesClipped to the shape's outline.
EffectPlacement.aroundShapesOver the shape and beyond its outline, for halos, rings and badges.
EffectPlacement.anyShapes and connectorsUnclipped; read context.element to tell them apart.

EffectDefinition ​

ArgumentTypeDefault
kindEffectKindRequired, unique.
labelStringRequired inspector label.
placementEffectPlacementRequired.
paintvoid Function(Canvas, EffectContext)?Draws over the canvas each frame, in world coordinates. Paint only; change content through controller verbs.
presentEffectPresentation Function(EffectContext)?Scales and fades the element's own rendering, as scale and fade do. A definition paints, presents, or both.
animatedbooltrue. false for effects that change only when updated, so the canvas does not redraw them each frame.
holdsboolfalse. true plays once over the period and holds the end, like a fade.
propertiesSet<EffectProperty>Empty. Values an inspector offers.
editingEffectEditingInspector ranges, below.

EffectPresentation(scale:, opacity:) scales about the element's center and multiplies its opacity. Connectors ignore scale.

EffectContext ​

MemberTypePurpose
elementResolvedElementThe connector or shape.
connector, shapeResolvedConnector, ResolvedShapeThe element as its kind; the other throws StateError.
effectEffectCurrent values.
progressdoublePosition in the cycle, from 0 to 1, after phase and reverse; for a holding effect, how far its change has come.
leveldoubleEmphasis from 0 (rest) to 1 (peak): progress shaped by the waveform. Held at 1 with reduced motion.
elapsedDurationTime since the effect started, after its delay.
pathFlutter PathCopy of the connector route or the shape outline, in world coordinates.
metricFlutter PathMetricLength and segments of a connector route.
pathGeometryResolvedPathGeometryRoute sampling with tangents, for connector effects.
inverseZoom, sizeToWorlddoubleInverse camera zoom; sizeToWorld is 1 for world scale and the inverse zoom for screen scale.
createPaint([intensity = 1])Flutter PaintThe effect color, gradient and opacity, adapted to the canvas theme; intensity from 0 to 1.
adaptColorAdaptationHow the canvas theme adapts authored colors. Apply it to any other color the effect draws.

Registry and inspector ranges ​

APIContract
EffectRegistry.standardFlow, pulse, halo, scale and fade.
EffectRegistry(definitions)Exactly the supplied effects; include the built-ins with [...EffectRegistry.standard.definitions, ...custom]. Rejects empty kinds or labels, duplicate kinds, and definitions that neither paint nor present, with ArgumentError.
definitions, forConnectors, forShapesAll definitions, or those that play on connectors or on shapes.
contains(kind), registry[kind], require(kind)Lookup; require throws StateError when missing.

The inspector offers the advertised EffectProperty values of the effect on the selected shape or connector: particle, color, gradient, speed, baseOpacity, opacity, size, width, count, direction, scale, waveform, repeat, delay, target, mode, amount, spread, dim and fade. EffectEditing groups their EffectRange(minimum, maximum, step) bounds; ranges do not clamp authored values.

PropertyDefault rangeUnits
speed(0.1, 5, 0.1)Cycles per second
delay(0, 10, 0.1)Seconds
baseOpacity, opacity, dim(0, 100, 5)Percent
size, width(1, 24, 1)Pixels
spread(1, 48, 1)Pixels
amount(0.5, 2, 0.02)Scale factor
count(1, 30, 1)Particles; whole numbers
repeat(1, 20, 1)Cycles; whole numbers

Examples ​

ExampleShows
Connector effectsDots, dashes, arrows and gradient flows; pulse and blink along connectors.
Shape effectsPulse, blink, ripple and steady halos, breathe and pop, fades and dimming, in brand colors.
Element motionAnimated move, resize and layout.
Custom effectA registered effect kind the inspector edits.
Navigation and animationCamera motion.
Live energy flowLive data with flows, a beacon and animated load bars.