Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  1. Divide the canvas into zones. Rectangular regions for each process section (Intake, Treatment, Distribution) or each info panel (Equipment Detail, Live Metrics, Identity).
  2. Lay zone background Rectangles first. These are the visual scaffolding — operators read the display by scanning zones, not individual shapes.
  3. Place elements WITHIN zone coordinates. Everything in a zone has its Left/Top relative to the zone's origin.
  4. Connect zones with flow indicators. Arrows, pipe lines, direction markers.

Canvas

...

sizes

CanvasWidth × HeightWhen
Standard HD1366 × 728Default, works everywhere
Wide1600 × 900Modern control-room monitors
Full HD1920 × 1080Dedicated operator displays
4K scaled3840 × 2160Rare — prefer 1920×1080 with StretchFill

Zone math (any canvas size, N zones)

text margin = 20 gap = 15 titleBar = 60 // top strip for display title + status bottomPanel =

Canvas inherits from ISA-101 process graphics, not from SaaS design. Three rules do most of the work:

  • Color is reserved for state communication. Pipes, equipment outlines, and nominal state all use a neutral (desaturated) brush from the theme. Saturated color — warning amber, alarm red, running green — is reserved for conditions that need attention. Equipment that's running normally should look neutral, not vibrantly green.
  • Numbers are sized for scanning, not for visual impact. Live values on a Canvas are typically 14–22 FontSize. Numbers above 22 on a live value are almost always wrong — that's a Dashboard CenterValue pattern, and it doesn't belong on equipment.
  • No gradients, drop shadows, glass effects, or rounded "cards" around equipment. Zone edges are 1px outlines on a theme-defined background; equipment lives inside zones by geometry, not inside elevated surfaces.

Use theme brushes, not hex. Call list_elements('ThemeColors') for the brushes available in the active theme. Background is always theme:PageBackground; zone fills are theme:PanelBackground; text uses the theme's foreground brushes.

Canvas sizes

CanvasWidth × HeightWhen
Standard HD1366 × 728Default, works everywhere
Wide1600 × 900Modern control-room monitors
Full HD1920 × 1080Dedicated operator displays
4K scaled3840 × 2160Rare — prefer 1920×1080 with StretchFill

Zone math (any canvas size, N zones)

text margin = 20 gap = 15 titleBar = 60 // top strip for display title + status bottomPanel = 140 // for trend/alarm/summary zoneHeight = Height - titleBar - bottomPanel - margin zoneWidth = (Width - 2*margin - (N-1)*gap) / N zone[i].Left = margin + i * (zoneWidth + gap) zone[i].Top = titleBar zone[i].Width = zoneWidth zone[i].Height = zoneHeight

...

json { "Type": "ShapeGroup", "Left": 300, "Top": 200, "Width": 180, "Height": 220, "Stretch": "None", "Children": [ { "Type": "Ellipse", "Left": 40, "Top": 0, "Width": 100, "Height": 20 }, { "Type": "Rectangle", "Left": 40, "Top": 10, "Width": 100, "Height": 180 }, { "Type": "Ellipse", "Left": 40, "Top": 180, "Width": 100, "Height": 20 } ], "FillTheme": "ElementBlue", "Dynamics": [ { "Type": "FillColorDynamic", "LinkedValue": "@Tag.Reactor/Alarm", "ChangeColorItems": [ { "Type": "ChangeColorItem", "ChangeLimit": 0, "LimitColor": "#FF1E3A8A" }, { "Type": "ChangeColorItem", "ChangeLimit": 01, "LimitColor": "#FF1E3A8A#FFEF4444" } ] } , { "Type": "ChangeColorItem", "ChangeLimit": 1, "LimitColor": "#FFEF4444" } ] } ] }

SvgGroup — when you'd rather author in SVG

Any inline SVG string becomes a ShapeGroup with native WPF shapes:

json { "Type": "SvgGroup", "Left": 224, "Top": 240, "Width": 180, "Height": 220, "SvgContent": "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 220'><ellipse cx='90' cy='14' rx='60' ry='10' fill='#95a5a6'/><rect x='30' y='14' width='120' height='180' fill='#3498db'/><ellipse cx='90' cy='194' rx='60' ry='10' fill='#95a5a6'/></svg>", "Name": "ReactorVessel" }

Supported SVG elements: rect, circle, ellipse, line, path, polyline, polygon, g.

Trade-off: SvgContent hex colors are opaque to the theme system. Fine for process-meaning colors (heat red, water blue), wrong for UI chrome. If you need theme-reactive composed equipment, use ShapeGroup directly.

Group — for interactive panels

Use Group when children need INDEPENDENT dynamics:

json { "Type": "Group", "Left": 100, "Top": 100, "Width": 400, "Height": 280, "Children": [ { "Type": "Rectangle", "Left": 0, "Top": 0, "Width": 400, "Height": 280, "FillTheme": "PanelBackground" }, { "Type": "TextBlock", "Left": 16, "Top": 16, "Width": 360, "Height": 24, "LinkedValue": "Reactor Control" }, { "Type": "Button", "Left": 16, "Top": 220, "Width": 120, "Height": 40, "LabelLink": "Start", "Dynamics": [{ "Type": "ActionDynamic", "MouseLeftButtonDown": { "Type": "DynamicActionInfo", "ActionType": "SetValue", "ObjectLink": "@Tag.Reactor/Running", "ObjectValueLink": 1 }}] } ] }

Section 5 — The equipment cookbook

Recipe 1 — Vessel with jacket

The archetype: a cylindrical vessel with a heating jacket that changes color when the heater is running.

] }

SvgGroup — when you'd rather author in SVG

Any inline SVG string becomes a ShapeGroup with native WPF shapes. Supported SVG elements: rect, circle, ellipse, line, path, polyline, polygon, g. Trade-off: SvgContent hex colors are opaque to the theme system. Fine for process-meaning colors, wrong for UI chrome. If you need theme-reactive composed equipment, use ShapeGroup directly.

Group — for interactive panels

Use Group when children need INDEPENDENT dynamics (a panel with its own chart, buttons, and labels that moves as a unit but where each child has its own behavior).

Section 4.5 — Choosing a symbol source

Before composing equipment from primitives, look for an existing symbol. The search order matters — using the wrong source creates inconsistency across a solution's displays.

  1. Solution symbols first. When working in an existing solution, list_elements('Solution') shows what the plant has already standardized on. If a Solution/Tanks/Tank_Vertical exists, use it — other displays in this solution already do. Mixing Solution symbols with Library symbols or Wizards for the same equipment type produces a display that visually disagrees with the rest of the plant's screens.
  2. HPG Library next. list_elements('Library/HPG') for High Performance Graphics — flat, theme-aware symbols that respond to the standard state convention (0=Off/Stopped/Closed, 1=On/Running/Open, 2=Disabled/Out-of-Service) via the HPOffFill/HPOnFill/HPDisableFill theme brushes. This is the right default for operator control screens.
  3. HMI Library for detailed/realistic symbols. list_elements('Library/HMI') — traditional detailed symbols with shading and depth. Right for training material, technical diagrams, and mechanical documentation. Wrong for live operator screens (too much visual noise competes with state communication).
  4. Wizard symbols for fast-path canonical equipment. Five pre-wired Wizards cover the most common cases: Wizard/BLOWER, Wizard/MOTOR, Wizard/PUMP, Wizard/TANK, Wizard/VALVE. See Recipe 2.
  5. Compose from primitives (ShapeGroup) only when none of the above fits. This is the slowest path and the one most likely to drift across displays.

Rule: when writing to an existing solution, run list_elements('Solution') before reaching for a Library symbol or a Wizard. Match what's already there.

Section 5 — The equipment cookbook

Recipe 1 — Vessel with jacket

The archetype: a cylindrical vessel with a heating jacket that changes color when the heater is running.

json { "Type": "Ellipse", "Left": 300, "Top": 180, "Width": 96, "Height": 24, "FillTheme": "ElementGray" }, { "Type": "Rectangle", "Left": 328, "Top": 150, "Width": 40, "Height": 36, "FillTheme": "ElementGray" }, { "Type": "Cylinder", "Left": 276, "Top": 200, "Width": 144, "Height": 300, "FillTheme": "ElementBluejson { "Type": "Ellipse", "Left": 300, "Top": 180, "Width": 96, "Height": 24, "FillTheme": "ElementGray" }, { "Type": "Rectangle", "Left": 328256, "Top": 150280, "Width": 4022, "Height": 36180, "FillTheme": "OffFill", "Dynamics": [ { "Type": "ElementGray" }, "FillColorDynamic", "LinkedValue": "@Tag.Reactor/Heater/Running", "ChangeColorItems": [ { "Type": "CylinderChangeColorItem", "LeftChangeLimit": 2760, "TopLimitColor": 200"#FF7F1D1D" }, { "WidthType": 144"ChangeColorItem", "HeightChangeLimit": 3001, "FillThemeLimitColor": "ElementBlue#FFEF4444" } ] } ] }, { "Type": "Rectangle", "Left": 256418, "Top": 280, "Width": 22, "Height": 180, "FillTheme": "OffFill", "Dynamics": [ { "Type": "FillColorDynamic", "LinkedValue": "@Tag.Reactor/Heater/Running", "ChangeColorItems": [ /* same FillColorDynamic */ ] }

Elements top to bottom: drive cap (Ellipse), motor housing (Rectangle), vessel body (Cylinder with ElementBlue), left jacket rail (Rectangle with heater-gated FillColorDynamic), right jacket rail (same).

For an impeller shaft: add a thin vertical Rectangle, apply RotateDynamic gated by Running:

json { "Type": "ChangeColorItemRectangle", "ChangeLimitLeft": 0344, "LimitColorTop": "#FF7F1D1D" }230, { "TypeWidth": "ChangeColorItem"8, "ChangeLimitHeight": 1240, "LimitColorFillTheme": "#FFEF4444DefaultStroke" } ] } ] }, { "TypeDynamics": "Rectangle", "Left[ { "Type": 418, "TopRotateDynamic": 280, "WidthLinkedValue": 22, "Height30": 180, "FillThemeIsRpm": "OffFill"true, "DynamicsOnOffLink": [ /* same FillColorDynamic */ ] }

Elements top to bottom: drive cap (Ellipse), motor housing (Rectangle), vessel body (Cylinder with ElementBlue), left jacket rail (Rectangle with heater-gated FillColorDynamic), right jacket rail (same).

For an impeller shaft: add a thin vertical Rectangle, apply RotateDynamic gated by Running:

json { "Type": "Rectangle", "Left": 344, "Top": 230, "Width": 8, "Height": 240, "FillTheme": "DefaultStroke", "Dynamics": [ { "Type": "RotateDynamic", "LinkedValue": "30", "IsRpm": true, "OnOffLink": "@Tag.Reactor/Agitator/Running" } ] }

LinkedValue: "30" and IsRpm: true means "rotate at 30 rpm." OnOffLink gates the rotation — the shaft only spins when Running=1.

Recipe 2 — Centrifugal pump (Wizard symbol with live dynamics)

"@Tag.Reactor/Agitator/Running" } ] }

LinkedValue: "30" and IsRpm: true means "rotate at 30 rpm." OnOffLink gates the rotation — the shaft only spins when Running=1.

Recipe 2 — Wizard symbol (the fast path for canonical equipment)

Five Wizard symbols — Wizard/BLOWER, Wizard/MOTOR, Wizard/PUMP, Wizard/TANK, Wizard/VALVE — are pre-wired with common state dynamics. When one of these five fits the equipment, it's almost always the fastest path.

The AI's job when dropping a Wizard:

  1. Place it (Left, Top, Width, Height)
  2. Wire its SymbolLabels (State, RPM, whatever the symbol exposes) to tags
  3. Stop there

Visual customization — orientation, style variant, pipe-connection direction, foot details — is handled by the user in Designer via the Wizard configuration button (double-click the symbol in the editor). Do not attempt to reproduce these variants via raw geometry or extra elements; the Wizard holds them.

<ac:structured-macro ac:name="code"> <ac:parameter ac:name="language">json</ac:parameter> ac:plain-text-bodyjson { "Type": "Symbol", "SymbolName": "Wizard/PUMP", "Left": 500, "Top": 300, "Width": 80, "Height": 80, "SymbolLabels": [ { "Type": "SymbolLabel", "Key": "State", "LabelName": "State", "LabelValue": "@Tag.Pump1/Running", "FieldType": "Expression" }, { "Type": "SymbolLabel", "Key": "RPM", "LabelName": "RPM", "LabelValue": "@Tag.Pump1/Speed", "FieldType": ": "Expression" } ] }Expression" } ] }</ac:plain-text-body> </ac:structured-macro>

The symbol itself knows how to change its fill based on State — no extra dynamics FillColorDynamic needed. To add a click-to-open-detail action, add put the dynamic ActionDynamic directly on the Symbol:

json "Dynamics": [ { "Type": "ActionDynamic", "MouseLeftButtonDown": { "Type": "DynamicActionInfo", "ActionType": "OpenDisplay", "ObjectLink": "PumpDetail" } } ]

For equipment outside the five Wizard types, follow the symbol source order in §4.5 — Solution first, then HPG Library, then HMI Library, then primitives.

Recipe 3 — Pipe segment with flow direction

...

json { /* Recipe 1 elements for vessel body with jacket */ }, <p>{ &quot;Type&quot;: &quot;Path&quot;, &quot;Left&quot;: 305, &quot;Top&quot;: 220, &quot;Width&quot;: 130, &quot;Height&quot;: 240, &quot;Data&quot;: &quot;M0,0 Q65,20 130,0 M0,40 Q65,60 130,40 M0,80 Q65,100 130,80 M0,120 Q65,140 130,120 M0,160 Q65,180 130,160 M0,200 Q65,220 130,200&quot;, &quot;StrokeTheme&quot;: &quot;StateRed&quot;, &quot;StrokeThickness&quot;: 2, &quot;Fill&quot;: &quot;#00000000&quot;, &quot;FillTheme&quot;: &quot;&quot;, &quot;Dynamics&quot;: [ { &quot;Type&quot;: &quot;VisibilityDynamic&quot;, &quot;LinkedValue&quot;: &quot;@Tag.Reactor/Heater/Running&quot; } ] }]]&gt;&lt;/ac:plain-text-body&gt; &lt;/ac:structured-macro&gt;</p> <p>The coils are only visible when the heater is active — a subtle but unmistakable visual cue for operators.</p> <h3>Recipe 5 — Reference displays to study</h3> <p>For a worked canvas at scale, read displays from the <code>Industrial_Ontology_Enhanced_Demo</code> solution (solution_id <code>J8P7LY</code>). The display <code>ProcessAreaOverview</code> is a good starting anchor — it demonstrates zone decomposition, inline readouts attached to equipment, pipe/gridline composition, and the use of Library symbols alongside primitives. Read with <code>get_objects('DisplaysList', names=['ProcessAreaOverview'], detail='full')</code> to see how the pieces compose at scale.</p> <h2>Section 6 — Dynamics reference</h2> <p>Call <code>list_dynamics()</code> for the full list. Here are the The 14 types grouped by what they do:</p> <h3>Action category</h3> <table> <tbody> <tr><th>Dynamic</th><th>What it does</th><th>Use for</th></tr> <tr><td><code>ActionDynamic</code></td><td>Run an action on mouse event</td><td>Navigation, tag writes, scripts, toggles</td></tr> <tr><td><code>CodeBehindDynamic</code></td><td>Run code on display lifecycle events</td><td>Init, cleanup, periodic updates</td></tr> <tr><td><code>HyperlinkDynamic</code></td><td>Open external URL on click</td><td>Documentation links, external dashboards</td></tr> </tbody> </table> <h3>Color category</h3> <table> <tbody> <tr><th>Dynamic</th><th>What it does</th><th>Use for</th></tr> <tr><td><code>FillColorDynamic</code></td><td>Change element Fill based on thresholds</td><td>Status indicators, process meaning</td></tr> <tr><td><code>LineColorDynamic</code></td><td>Change element Stroke based on thresholds</td><td>Pipe color-by-flow, boundary-alarm borders</td></tr> <tr><td><code>TextColorDynamic</code></td><td>Change text Foreground based on thresholds</td><td>Alert text, highlighted values</td></tr> </tbody> </table> <h3>Animation category (Canvas-only)</h3> <table> <tbody> <tr><th>Dynamic</th><th>What it does</th><th>Use for</th></tr> <tr><td><code>RotateDynamic</code></td><td>Rotate element by angle or rpm</td><td>Pointers, impeller shafts, motors, compass needles</td></tr> <tr><td><code>ScaleDynamic</code></td><td>Scale element by tag value</td><td>Growth/shrink animations, level indicators</td></tr> <tr><td><code>MoveDragDynamic</code></td><td>Move element by tag value OR let user drag</td><td>Sliding valves, operator drag-to-set</td></tr> <tr><td><code>SkewDynamic</code></td><td>Skew element</td><td>Perspective effects, rare</td></tr> </tbody> </table> <h3>Data category (Canvas-only)</h3> <table> <tbody> <tr><th>Dynamic</th><th>What it does</th><th>Use for</th></tr> <tr><td><code>BargraphDynamic</code></td><td>Fill a Rectangle proportionally based on value</td><td>Tank level fill, progress fills, power meter strips</td></tr> </tbody> </table> <h3>Visibility, Security, Feedback</h3> <table> <tbody> <tr><th>Dynamic</th><th>What it does</th><th>Use for</th></tr> <tr><td><code>VisibilityDynamic</code></td><td>Show/hide based on expression</td><td>Conditional panels, state-dependent overlays, coils-only-when-heating</td></tr> <tr><td><code>SecurityDynamic</code></td><td>Hide/disable based on user permission</td><td>Admin-only controls, role-based HMI</td></tr> <tr><td><code>ShineDynamic</code></td><td>Mouse-over highlight / glow effect</td><td>Hover feedback on clickable shapes</td></tr> </tbody> </table> <h3>Copy-paste patterns</h3> <p><strong>status_indicator</strong> (shape that changes color on running/stopped):</p> <ac:structured-macro ac:name="code"> <ac:parameter ac:name="language">json</ac:parameter> <ac:plain-text-body><![CDATA[{ "Type": "Ellipse", "Left": 100, "Top": 100, "Width": 24, "Height": 24, "Dynamics": [ { "Type": "FillColorDynamic", "LinkedValue": "@Tag.Equipment/Running", "ChangeColorItems": [ { "Type": "ChangeColorItem", "ChangeLimit": 0, "LimitColor": "#FF808080" }, { "Type": "ChangeColorItem", "ChangeLimit": 1, "LimitColor": "#FF34D399" } ] } ] }

...

  • ? Background set explicitly to theme:PageBackground (not the #FFFAFAFA default)
  • ? Zones calculated to fill the full canvas width and height
  • ? Every zone has a background Rectangle with FillTheme: "PanelBackground" — FIRST in Elements
  • ? Symbol source lookup order followed: Solution → Library/HPG → Library/HMI → Wizard → primitives (§4.5)
  • ? Symbols ≥ 60×60 (Wizard), ≥ 80×80 recommended on process overview displays
  • ? Library symbols scaled proportionally — maintain aspect ratio
  • ? No equipment symbols overlap unless intentionally layered
  • ? Zone titles FontSize ≥ 14, value text FontSize ≥ 12
  • ? Value and Unit in the SAME TextBlock with composite LinkedValue, OR adjacent TextBlocks (inline readout pattern)
  • ? Live values attached to equipment use inline TextBlocks, not CenterValue
  • ? No hero numbers on live values (FontSize ≤ 22, except one wall-display header KPI)
  • ? Nominal-state elements use desaturated theme brushes; saturated color reserved for warning/alarm
  • ? No element extends beyond the display's Width/Height
  • ? Page navigation is in the Header, not on content pages
  • ? All dynamics inside Dynamics array (never as direct properties)
  • ? ChangeColorItems is a flat array (no ColorChangeList wrapper)
  • ? All shape / symbol types verified via list_elements() before use
  • ? get_state after write shows errorList empty

...

text Rectangle // backgrounds, bars, pipes (when rectangular) Ellipse // caps, LEDs, pump bodies Polygon // arrows, funnels, custom closed shapes Gridline // pipes (always prefer over Polyline) Path // curves, coils, complex shapes Cylinder // vessels, tanks (first-class shortcut) TextBlock // all text (labels, values, composite) Symbol // WizardSolution / Library / Solution symbols Wizard ShapeGroup // composed equipment with unified dynamics RadialGauge // temperature, pressure, any circular gauge

...