Portable Display control that renders the solution’s industrial ontology as an interactive graph.

ReferenceControlsCharts → KnowledgeGraph

Version 10.1.5+


Overview

The Knowledge Graph control (XAML element TKnowledgeGraph) renders the UNS UserType hierarchy and per-Tag relationships as an interactive node-and-edge graph. Operators pan, zoom, and click nodes to inspect the underlying assets; node selections flow back into Tag-bindable properties so the rest of the Display can react to graph interaction.

The control reads its source from the SolutionSettings.KnowledgeGraphSource column — a Mermaid Markdown document regenerated by:

Rendering is performed by a vendored cytoscape.js inside a WebView2 (WPF) or OpenSilver (HTML5) host — no external network dependency at runtime.


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>UNS Visual Report — LocalAI KnowledgeGraph Demo</title>
<script src="C:/Program Files/Tatsoft/FrameworX/fx-10/HTML5/Libraries/cytoscape/dist/cytoscape.min.js"></script>
<style>
  :root {
    --bg: #fafafa;
    --fg: #222222;
    --border: #d0d0d0;
    --panel-bg: #f0f0f0;
    --toolbar-bg: #ececec;
    --muted-fg: #6a6a6a;
    --input-bg: #ffffff;
    --button-bg: #e0e0e0;
    --info-td-fg: #6a6a6a;
    --info-td-border: #e0e0e0;
    --code-bg: #f5f5f5;
    --text-bg-edge: #ffffff;
    --node-class-fill: #4a90d9;
    --node-class-border: #2c5f8f;
    --node-folder-fill: #b8b886;
    --node-folder-border: #807f54;
    --node-tag-fill: #5a9f5a;
    --node-tag-border: #3a6f3a;
    --edge-reference: #e67e22;
    --edge-composition: #9b59b6;
    --edge-inheritance: #888888;
    --edge-containment: #999999;
    --selection: #ff9800;
    --node-label: #ffffff;
    --edge-label: #444444;
  }
  @media (prefers-color-scheme: dark) {
    :root {
      --bg: #1e1e1e;
      --fg: #dddddd;
      --border: #3a3a3a;
      --panel-bg: #252526;
      --toolbar-bg: #2d2d30;
      --muted-fg: #999999;
      --input-bg: #1e1e1e;
      --button-bg: #3a3a3a;
      --info-td-fg: #888888;
      --info-td-border: #333333;
      --code-bg: #1e1e1e;
      --text-bg-edge: #1e1e1e;
      --node-class-fill: #4a90d9;
      --node-class-border: #2c5f8f;
      --node-folder-fill: #7a7a4a;
      --node-folder-border: #555533;
      --node-tag-fill: #5a9f5a;
      --node-tag-border: #3a6f3a;
      --edge-reference: #e67e22;
      --edge-composition: #9b59b6;
      --edge-inheritance: #c0c0c0;
      --edge-containment: #777777;
      --selection: #ff9800;
      --node-label: #ffffff;
      --edge-label: #cccccc;
    }
  }
  :root {
    --bg: #343536;
    --fg: #DADADA;
    --border: #595959;
    --panel-bg: #202123;
    --toolbar-bg: #202123;
    --input-bg: #343536;
    --code-bg: #343536;
    --text-bg-edge: #343536;
    --node-class-fill: #5064B4;
    --node-folder-fill: #484848;
    --node-tag-fill: #008C3C;
    --edge-reference: #FFF546;
    --edge-composition: #D1D1D1;
    --edge-inheritance: #595959;
    --edge-containment: #595959;
    --selection: #FFC107;
  }
  body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; background: var(--bg); color: var(--fg); }
  #header { padding: 12px 16px; background: var(--panel-bg); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 16px; }
  #header h1 { margin: 0; font-size: 16px; font-weight: 500; }
  #header .stats { font-size: 12px; color: var(--muted-fg); }
  #toolbar { padding: 8px 16px; background: var(--toolbar-bg); border-bottom: 1px solid var(--border); display: flex; gap: 12px; align-items: center; font-size: 12px; }
  #toolbar label { cursor: pointer; user-select: none; display: inline-flex; align-items: center; gap: 5px; }
  #toolbar input[type=text] { flex: 0 0 220px; padding: 4px 8px; background: var(--input-bg); color: var(--fg); border: 1px solid var(--border); border-radius: 3px; }
  #toolbar button { background: var(--button-bg); color: var(--fg); border: none; padding: 4px 10px; border-radius: 3px; cursor: pointer; }
  .sw { display: inline-block; vertical-align: middle; flex-shrink: 0; }
  .sw-node { width: 10px; height: 10px; border-radius: 2px; border: 1px solid rgba(0,0,0,0.4); }
  .sw-edge { width: 18px; height: 3px; }
  .sw-class { background: var(--node-class-fill); border-color: var(--node-class-border); }
  .sw-folder { background: var(--node-folder-fill); border-color: var(--node-folder-border); }
  .sw-tag { background: var(--node-tag-fill); border-color: var(--node-tag-border); border-radius: 50%; }
  .sw-reference { background: transparent; height: 0; border-top: 2px dashed var(--edge-reference); }
  .sw-composition { background: var(--edge-composition); }
  .sw-inheritance { background: var(--edge-inheritance); }
  .sw-containment { background: var(--edge-containment); }
  #container { display: flex; height: calc(100vh - 95px); }
  #cy { flex: 1; background: var(--bg); }
  #info { width: 320px; padding: 12px 16px; background: var(--panel-bg); border-left: 1px solid var(--border); overflow-y: auto; font-size: 13px; }
  #info h3 { margin: 0 0 8px 0; font-size: 14px; }
  #info .kind { font-size: 11px; color: var(--muted-fg); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
  #info table { width: 100%; border-collapse: collapse; margin-top: 8px; }
  #info td { padding: 3px 6px; border-bottom: 1px solid var(--info-td-border); font-size: 12px; vertical-align: top; }
  #info td:first-child { color: var(--info-td-fg); width: 90px; }
  #info code { font-family: Consolas, Menlo, monospace; background: var(--code-bg); padding: 1px 4px; border-radius: 2px; }
</style>
</head>
<body>
<div id="header">
  <h1>UNS Visual Report — LocalAI KnowledgeGraph Demo</h1>
  <span class="stats">18 UDTs · 18 tags · 13 folders · 38 relations</span>
</div>
<div id="toolbar">
  <input id="search" type="text" placeholder="Search nodes…" />
  <label><input type="checkbox" class="filter" data-kind="class" checked><span class="sw sw-node sw-class"></span>UDTs</label>
  <label><input type="checkbox" class="filter" data-kind="folder" checked><span class="sw sw-node sw-folder"></span>Folders</label>
  <label><input type="checkbox" class="filter" data-kind="tag" checked><span class="sw sw-node sw-tag"></span>Tags</label>
  <span style="color: var(--muted-fg);">|</span>
  <label><input type="checkbox" class="edgefilter" data-kind="reference" checked><span class="sw sw-edge sw-reference"></span>Reference</label>
  <label><input type="checkbox" class="edgefilter" data-kind="composition" checked><span class="sw sw-edge sw-composition"></span>Composition</label>
  <label><input type="checkbox" class="edgefilter" data-kind="inheritance" checked><span class="sw sw-edge sw-inheritance"></span>Inheritance</label>
  <label><input type="checkbox" class="edgefilter" data-kind="containment" checked><span class="sw sw-edge sw-containment"></span>Containment</label>
  <span style="flex: 1;"></span>
  <button id="downloadLayout" title="Save current node positions as a JSON file">Download Layout</button>
  <button id="loadLayout" title="Load node positions from a JSON file">Load Layout</button>
  <input id="loadLayoutFile" type="file" accept="application/json,.json" style="display:none;" />
  <button onclick="cy.fit()">Fit</button>
</div>
<div id="container">
  <div id="cy"></div>
  <div id="info"><h3>UNS</h3><div class="kind">Click a node for details</div></div>
</div>
<script>const ELEMENTS = [{"data":{"id":"ut_MixerType","label":"MixerType","name":"MixerType","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88eq/Mixer","labels":"High-Shear Mixer; Granulator","members":[{"n":"Temperature","t":"Double"},{"n":"Pressure","t":"Double"},{"n":"StirrerSpeed","t":"Double"},{"n":"JacketTemp","t":"Double"},{"n":"Status","t":"Text"},{"n":"EquipmentClass","t":"Text"},{"n":"NormalTempRange","t":"Text"},{"n":"FeedsInto","t":"Reference_ReactorType"}]}},{"data":{"id":"e1","source":"ut_MixerType","target":"ut_ReactorType","label":"FeedsInto","kind":"reference"}},{"data":{"id":"e2","source":"ut_MixerType","target":"ut_S88_Unit","label":"extends","kind":"inheritance"}},{"data":{"id":"ut_ReactorType","label":"ReactorType","name":"ReactorType","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88eq/Reactor","labels":"Reaction Vessel; Agitated Reactor","members":[{"n":"Temperature","t":"Double"},{"n":"Pressure","t":"Double"},{"n":"Level","t":"Double"},{"n":"StirrerSpeed","t":"Double"},{"n":"Status","t":"Text"},{"n":"EquipmentClass","t":"Text"},{"n":"NormalTempRange","t":"Text"},{"n":"FeedsInto","t":"Reference_CoaterType"},{"n":"ReceivesFrom","t":"Reference_MixerType"}]}},{"data":{"id":"e3","source":"ut_ReactorType","target":"ut_CoaterType","label":"FeedsInto","kind":"reference"}},{"data":{"id":"e4","source":"ut_ReactorType","target":"ut_MixerType","label":"ReceivesFrom","kind":"reference"}},{"data":{"id":"e5","source":"ut_ReactorType","target":"ut_S88_Unit","label":"extends","kind":"inheritance"}},{"data":{"id":"ut_CoaterType","label":"CoaterType","name":"CoaterType","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88eq/Coater","labels":"Coating Drum; Tablet Coater","members":[{"n":"Temperature","t":"Double"},{"n":"SprayRate","t":"Double"},{"n":"DrumSpeed","t":"Double"},{"n":"Status","t":"Text"},{"n":"EquipmentClass","t":"Text"},{"n":"NormalTempRange","t":"Text"},{"n":"FeedsInto","t":"Reference_DryerType"},{"n":"ReceivesFromMain","t":"Reference_ReactorType"},{"n":"ReceivesFromAdditive","t":"Reference_CrystallizerType"}]}},{"data":{"id":"e6","source":"ut_CoaterType","target":"ut_DryerType","label":"FeedsInto","kind":"reference"}},{"data":{"id":"e7","source":"ut_CoaterType","target":"ut_ReactorType","label":"ReceivesFromMain","kind":"reference"}},{"data":{"id":"e8","source":"ut_CoaterType","target":"ut_CrystallizerType","label":"ReceivesFromAdditive","kind":"reference"}},{"data":{"id":"e9","source":"ut_CoaterType","target":"ut_S88_Unit","label":"extends","kind":"inheritance"}},{"data":{"id":"ut_DryerType","label":"DryerType","name":"DryerType","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88eq/Dryer","labels":"Fluid-Bed Dryer","members":[{"n":"Temperature","t":"Double"},{"n":"Humidity","t":"Double"},{"n":"AirFlow","t":"Double"},{"n":"Status","t":"Text"},{"n":"EquipmentClass","t":"Text"},{"n":"NormalTempRange","t":"Text"}]}},{"data":{"id":"e10","source":"ut_DryerType","target":"ut_S88_Unit","label":"extends","kind":"inheritance"}},{"data":{"id":"ut_BlenderType","label":"BlenderType","name":"BlenderType","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88eq/Blender","labels":"Powder Blender","members":[{"n":"Speed","t":"Double"},{"n":"FillLevel","t":"Double"},{"n":"Status","t":"Text"},{"n":"EquipmentClass","t":"Text"},{"n":"NormalTempRange","t":"Text"},{"n":"FeedsInto","t":"Reference_CrystallizerType"}]}},{"data":{"id":"e11","source":"ut_BlenderType","target":"ut_CrystallizerType","label":"FeedsInto","kind":"reference"}},{"data":{"id":"e12","source":"ut_BlenderType","target":"ut_S88_Unit","label":"extends","kind":"inheritance"}},{"data":{"id":"ut_CrystallizerType","label":"CrystallizerType","name":"CrystallizerType","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88eq/Crystallizer","labels":"Crystallization Vessel","members":[{"n":"Temperature","t":"Double"},{"n":"Pressure","t":"Double"},{"n":"Status","t":"Text"},{"n":"EquipmentClass","t":"Text"},{"n":"NormalTempRange","t":"Text"},{"n":"FeedsInto","t":"Reference_CoaterType"},{"n":"ReceivesFrom","t":"Reference_BlenderType"}]}},{"data":{"id":"e13","source":"ut_CrystallizerType","target":"ut_CoaterType","label":"FeedsInto","kind":"reference"}},{"data":{"id":"e14","source":"ut_CrystallizerType","target":"ut_BlenderType","label":"ReceivesFrom","kind":"reference"}},{"data":{"id":"e15","source":"ut_CrystallizerType","target":"ut_S88_Unit","label":"extends","kind":"inheritance"}},{"data":{"id":"ut_Attr_Site","label":"Attr_Site","name":"Attr_Site","kind":"class","description":"","sourceIri":"","labels":"","members":[{"n":"EmergencyContact","t":"Text"},{"n":"OperatingHours","t":"Integer"},{"n":"FDA_RegistrationNumber","t":"Integer"},{"n":"TimeZone","t":"Text"},{"n":"PlantCode","t":"Integer"},{"n":"Address","t":"Text"},{"n":"Longitude","t":"Integer"},{"n":"Latitude","t":"Integer"}]}},{"data":{"id":"ut_Attr_Enterprise","label":"Attr_Enterprise","name":"Attr_Enterprise","kind":"class","description":"","sourceIri":"","labels":"","members":[{"n":"LogoUrl","t":"Text"},{"n":"CEO","t":"Text"},{"n":"Website","t":"Text"},{"n":"NDC_LabelerCode","t":"Text"},{"n":"DUNS_Number","t":"Text"},{"n":"Headquarters","t":"Text"},{"n":"FoundedYear","t":"Integer"},{"n":"CompanyName","t":"Text"}]}},{"data":{"id":"ut_ChatState","label":"ChatState","name":"ChatState","kind":"class","description":"Page-state for the Home AI chat panel — single flat container holding Query (operator input) and Reply (LLM envelope, JSON). Server domain so all connected clients share one conversation.","sourceIri":"","labels":"","members":[{"n":"Query","t":"Text"},{"n":"Reply","t":"Json"}]}},{"data":{"id":"ut_S88_Unit","label":"S88_Unit","name":"S88_Unit","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/Unit","labels":"Vessel; Reactor","members":[{"n":"Name","t":"Text"},{"n":"Description","t":"Text"},{"n":"Temperature_C","t":"Double"},{"n":"Pressure_bar","t":"Double"},{"n":"Speed_rpm","t":"Double"},{"n":"Setpoint","t":"Double"},{"n":"Running","t":"Digital"},{"n":"AnomalyScore","t":"Double"},{"n":"Capacity_L","t":"Double"}]}},{"data":{"id":"ut_S88_ProcessCell","label":"S88_ProcessCell","name":"S88_ProcessCell","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/ProcessCell","labels":"Production Line; Cell","members":[{"n":"Name","t":"Text"},{"n":"ActiveBatch","t":"Text"},{"n":"BatchProgress","t":"Double"}]}},{"data":{"id":"ut_S88_Operation","label":"S88_Operation","name":"S88_Operation","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/Operation","labels":"Procedure Step; Phase","members":[{"n":"OpID","t":"Text"},{"n":"Description","t":"Text"},{"n":"Status","t":"Text"},{"n":"PrecedingOperation","t":"Reference_S88_Operation"},{"n":"FollowingOperation","t":"Reference_S88_Operation"},{"n":"RequiredState","t":"Text"},{"n":"HoldConditions","t":"Text"}]}},{"data":{"id":"e16","source":"ut_S88_Operation","target":"ut_S88_Operation","label":"PrecedingOperation","kind":"reference"}},{"data":{"id":"e17","source":"ut_S88_Operation","target":"ut_S88_Operation","label":"FollowingOperation","kind":"reference"}},{"data":{"id":"ut_S88_EquipmentModule","label":"S88_EquipmentModule","name":"S88_EquipmentModule","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/EquipmentModule","labels":"Module; Subassembly","members":[{"n":"Name","t":"Text"},{"n":"Description","t":"Text"},{"n":"Speed_rpm","t":"Double"},{"n":"Running","t":"Digital"},{"n":"Output","t":"Double"},{"n":"Setpoint","t":"Double"},{"n":"Current_A","t":"Double"}]}},{"data":{"id":"ut_S88_Enterprise","label":"S88_Enterprise","name":"S88_Enterprise","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/Enterprise","labels":"Company; Corporation","members":[{"n":"Name","t":"Text"},{"n":"Description","t":"Text"}]}},{"data":{"id":"ut_S88_Batch","label":"S88_Batch","name":"S88_Batch","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/Batch","labels":"Production Run; Lot","members":[{"n":"BatchID","t":"Text"},{"n":"ProductCode","t":"Text"},{"n":"Status","t":"Text"},{"n":"StartTime","t":"DateTime"}]}},{"data":{"id":"ut_S88_Area","label":"S88_Area","name":"S88_Area","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/Area","labels":"Department","members":[{"n":"Name","t":"Text"},{"n":"Description","t":"Text"}]}},{"data":{"id":"ut_S88_Site","label":"S88_Site","name":"S88_Site","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88/Site","labels":"Plant","members":[{"n":"Name","t":"Text"},{"n":"Location","t":"Text"},{"n":"TotalThroughput","t":"Double"},{"n":"OEE","t":"Double"}]}},{"data":{"id":"ut_Attr_Batch","label":"Attr_Batch","name":"Attr_Batch","kind":"class","description":"","sourceIri":"https://example.tatsoft.com/ontology/s88ext/BatchProfile","labels":"Batch Outcome","members":[{"n":"CompletionTime","t":"DateTime"},{"n":"DurationMinutes","t":"Integer"},{"n":"MaxTemperature_C","t":"Double"},{"n":"AnomaliesFired","t":"Integer"},{"n":"QualityVerdict","t":"Text"},{"n":"BatchDescription","t":"Text"},{"n":"OutcomeNotes","t":"Text"}]}},{"data":{"id":"e18","source":"ut_Attr_Batch","target":"ut_S88_Batch","label":"extends","kind":"inheritance"}},{"data":{"id":"f_PharmaCo","label":"PharmaCo","path":"PharmaCo","kind":"folder"}},{"data":{"id":"f_PharmaCo_Houston","label":"Houston","path":"PharmaCo/Houston","kind":"folder"}},{"data":{"id":"e19","source":"f_PharmaCo","target":"f_PharmaCo_Houston","kind":"containment"}},{"data":{"id":"f_PharmaCo_Houston_Granulation","label":"Granulation","path":"PharmaCo/Houston/Granulation","kind":"folder"}},{"data":{"id":"e20","source":"f_PharmaCo_Houston","target":"f_PharmaCo_Houston_Granulation","kind":"containment"}},{"data":{"id":"f_PharmaCo_Houston_Granulation_MainLine","label":"MainLine","path":"PharmaCo/Houston/Granulation/MainLine","kind":"folder"}},{"data":{"id":"e21","source":"f_PharmaCo_Houston_Granulation","target":"f_PharmaCo_Houston_Granulation_MainLine","kind":"containment"}},{"data":{"id":"f_PharmaCo_Houston_Additives","label":"Additives","path":"PharmaCo/Houston/Additives","kind":"folder"}},{"data":{"id":"e22","source":"f_PharmaCo_Houston","target":"f_PharmaCo_Houston_Additives","kind":"containment"}},{"data":{"id":"f_PharmaCo_Houston_Additives_SideLine","label":"SideLine","path":"PharmaCo/Houston/Additives/SideLine","kind":"folder"}},{"data":{"id":"e23","source":"f_PharmaCo_Houston_Additives","target":"f_PharmaCo_Houston_Additives_SideLine","kind":"containment"}},{"data":{"id":"f_PharmaCo_NewBostonPlant","label":"NewBostonPlant","path":"PharmaCo/NewBostonPlant","kind":"folder"}},{"data":{"id":"e24","source":"f_PharmaCo","target":"f_PharmaCo_NewBostonPlant","kind":"containment"}},{"data":{"id":"f_PharmaCo_NewBostonPlant_GranulationArea","label":"GranulationArea","path":"PharmaCo/NewBostonPlant/GranulationArea","kind":"folder"}},{"data":{"id":"e25","source":"f_PharmaCo_NewBostonPlant","target":"f_PharmaCo_NewBostonPlant_GranulationArea","kind":"containment"}},{"data":{"id":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA","label":"CellA","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA","kind":"folder"}},{"data":{"id":"e26","source":"f_PharmaCo_NewBostonPlant_GranulationArea","target":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA","kind":"containment"}},{"data":{"id":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101","label":"Mixer_M101","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Mixer_M101","kind":"folder"}},{"data":{"id":"e27","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA","target":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101","kind":"containment"}},{"data":{"id":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101","label":"Reactor_R101","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Reactor_R101","kind":"folder"}},{"data":{"id":"e28","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA","target":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101","kind":"containment"}},{"data":{"id":"f_Operations","label":"Operations","path":"Operations","kind":"folder"}},{"data":{"id":"f_Chat","label":"Chat","path":"Chat","kind":"folder"}},{"data":{"id":"t_PharmaCo_Houston_Granulation_MainLine_Mixer_M101","label":"Mixer M-201 : MixerType","path":"PharmaCo/Houston/Granulation/MainLine/Mixer_M101","userType":"MixerType","sourceIri":"https://example.tatsoft.com/ontology/demo/Houston_Mixer_M201","kind":"tag"}},{"data":{"id":"e29","source":"f_PharmaCo_Houston_Granulation_MainLine","target":"t_PharmaCo_Houston_Granulation_MainLine_Mixer_M101","kind":"containment"}},{"data":{"id":"e30","source":"t_PharmaCo_Houston_Granulation_MainLine_Mixer_M101","target":"ut_MixerType","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"e31","source":"t_PharmaCo_Houston_Granulation_MainLine_Mixer_M101","target":"t_PharmaCo_Houston_Granulation_MainLine_Reactor_R101","label":"FeedsInto","kind":"reference"}},{"data":{"id":"t_PharmaCo_Houston_Granulation_MainLine_Reactor_R101","label":"Reactor R-201 : ReactorType","path":"PharmaCo/Houston/Granulation/MainLine/Reactor_R101","userType":"ReactorType","sourceIri":"https://example.tatsoft.com/ontology/demo/Houston_Reactor_R201","kind":"tag"}},{"data":{"id":"e32","source":"f_PharmaCo_Houston_Granulation_MainLine","target":"t_PharmaCo_Houston_Granulation_MainLine_Reactor_R101","kind":"containment"}},{"data":{"id":"e33","source":"t_PharmaCo_Houston_Granulation_MainLine_Reactor_R101","target":"ut_ReactorType","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"e34","source":"t_PharmaCo_Houston_Granulation_MainLine_Reactor_R101","target":"t_PharmaCo_Houston_Granulation_MainLine_Coater_C201","label":"FeedsInto","kind":"reference"}},{"data":{"id":"t_PharmaCo_Houston_Granulation_MainLine_Coater_C201","label":"Coater C-201 : CoaterType","path":"PharmaCo/Houston/Granulation/MainLine/Coater_C201","userType":"CoaterType","sourceIri":"https://example.tatsoft.com/ontology/demo/Coater_C201","kind":"tag"}},{"data":{"id":"e35","source":"f_PharmaCo_Houston_Granulation_MainLine","target":"t_PharmaCo_Houston_Granulation_MainLine_Coater_C201","kind":"containment"}},{"data":{"id":"e36","source":"t_PharmaCo_Houston_Granulation_MainLine_Coater_C201","target":"ut_CoaterType","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"e37","source":"t_PharmaCo_Houston_Granulation_MainLine_Coater_C201","target":"t_PharmaCo_Houston_Granulation_MainLine_Dryer_D301","label":"FeedsInto","kind":"reference"}},{"data":{"id":"t_PharmaCo_Houston_Granulation_MainLine_Dryer_D301","label":"Dryer D-301 : DryerType","path":"PharmaCo/Houston/Granulation/MainLine/Dryer_D301","userType":"DryerType","sourceIri":"https://example.tatsoft.com/ontology/demo/Dryer_D301","kind":"tag"}},{"data":{"id":"e38","source":"f_PharmaCo_Houston_Granulation_MainLine","target":"t_PharmaCo_Houston_Granulation_MainLine_Dryer_D301","kind":"containment"}},{"data":{"id":"e39","source":"t_PharmaCo_Houston_Granulation_MainLine_Dryer_D301","target":"ut_DryerType","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_Houston_Additives_SideLine_Blender_B101","label":"Blender B-101 : BlenderType","path":"PharmaCo/Houston/Additives/SideLine/Blender_B101","userType":"BlenderType","sourceIri":"https://example.tatsoft.com/ontology/demo/Blender_B101","kind":"tag"}},{"data":{"id":"e40","source":"f_PharmaCo_Houston_Additives_SideLine","target":"t_PharmaCo_Houston_Additives_SideLine_Blender_B101","kind":"containment"}},{"data":{"id":"e41","source":"t_PharmaCo_Houston_Additives_SideLine_Blender_B101","target":"ut_BlenderType","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"e42","source":"t_PharmaCo_Houston_Additives_SideLine_Blender_B101","target":"t_PharmaCo_Houston_Additives_SideLine_Crystallizer_X101","label":"FeedsInto","kind":"reference"}},{"data":{"id":"t_PharmaCo_Houston_Additives_SideLine_Crystallizer_X101","label":"Crystallizer X-101 : CrystallizerType","path":"PharmaCo/Houston/Additives/SideLine/Crystallizer_X101","userType":"CrystallizerType","sourceIri":"https://example.tatsoft.com/ontology/demo/Crystallizer_X101","kind":"tag"}},{"data":{"id":"e43","source":"f_PharmaCo_Houston_Additives_SideLine","target":"t_PharmaCo_Houston_Additives_SideLine_Crystallizer_X101","kind":"containment"}},{"data":{"id":"e44","source":"t_PharmaCo_Houston_Additives_SideLine_Crystallizer_X101","target":"ut_CrystallizerType","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"e45","source":"t_PharmaCo_Houston_Additives_SideLine_Crystallizer_X101","target":"t_PharmaCo_Houston_Granulation_MainLine_Coater_C201","label":"FeedsInto","kind":"reference"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_Attr","label":"New Boston Plant : S88_Site","path":"PharmaCo/NewBostonPlant/Attr","userType":"S88_Site","sourceIri":"https://example.tatsoft.com/ontology/demo/NewBoston_Plant","kind":"tag"}},{"data":{"id":"e46","source":"f_PharmaCo_NewBostonPlant","target":"t_PharmaCo_NewBostonPlant_Attr","kind":"containment"}},{"data":{"id":"e47","source":"t_PharmaCo_NewBostonPlant_Attr","target":"ut_S88_Site","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Attr","label":"Granulation Cell A : S88_ProcessCell","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Attr","userType":"S88_ProcessCell","sourceIri":"https://example.tatsoft.com/ontology/demo/Cell_A","kind":"tag"}},{"data":{"id":"e48","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA","target":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Attr","kind":"containment"}},{"data":{"id":"e49","source":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Attr","target":"ut_S88_ProcessCell","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Attr","label":"High-Shear Mixer M-101 : S88_Unit","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Mixer_M101/Attr","userType":"S88_Unit","sourceIri":"https://example.tatsoft.com/ontology/demo/Mixer_M101","kind":"tag"}},{"data":{"id":"e50","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101","target":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Attr","kind":"containment"}},{"data":{"id":"e51","source":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Attr","target":"ut_S88_Unit","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Heater_H101","label":"Mixer M-101 Heater Jacket : S88_EquipmentModule","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Mixer_M101/Heater_H101","userType":"S88_EquipmentModule","sourceIri":"https://example.tatsoft.com/ontology/demo/Heater_H101","kind":"tag"}},{"data":{"id":"e52","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101","target":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Heater_H101","kind":"containment"}},{"data":{"id":"e53","source":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Heater_H101","target":"ut_S88_EquipmentModule","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Stirrer_S101","label":"Mixer M-101 Stirrer : S88_EquipmentModule","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Mixer_M101/Stirrer_S101","userType":"S88_EquipmentModule","sourceIri":"https://example.tatsoft.com/ontology/demo/Stirrer_S101","kind":"tag"}},{"data":{"id":"e54","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101","target":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Stirrer_S101","kind":"containment"}},{"data":{"id":"e55","source":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Mixer_M101_Stirrer_S101","target":"ut_S88_EquipmentModule","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101_Attr","label":"Reaction Vessel R-101 : S88_Unit","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Reactor_R101/Attr","userType":"S88_Unit","sourceIri":"https://example.tatsoft.com/ontology/demo/Reactor_R101","kind":"tag"}},{"data":{"id":"e56","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101","target":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101_Attr","kind":"containment"}},{"data":{"id":"e57","source":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101_Attr","target":"ut_S88_Unit","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101_Stirrer_S102","label":"Reactor R-101 Stirrer : S88_EquipmentModule","path":"PharmaCo/NewBostonPlant/GranulationArea/CellA/Reactor_R101/Stirrer_S102","userType":"S88_EquipmentModule","sourceIri":"https://example.tatsoft.com/ontology/demo/Stirrer_S102","kind":"tag"}},{"data":{"id":"e58","source":"f_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101","target":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101_Stirrer_S102","kind":"containment"}},{"data":{"id":"e59","source":"t_PharmaCo_NewBostonPlant_GranulationArea_CellA_Reactor_R101_Stirrer_S102","target":"ut_S88_EquipmentModule","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_Operations_Op_Charge_001","label":"Charge API Powder : S88_Operation","path":"Operations/Op_Charge_001","userType":"S88_Operation","sourceIri":"https://example.tatsoft.com/ontology/demo/Op_Charge_001","kind":"tag"}},{"data":{"id":"e60","source":"f_Operations","target":"t_Operations_Op_Charge_001","kind":"containment"}},{"data":{"id":"e61","source":"t_Operations_Op_Charge_001","target":"ut_S88_Operation","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_Operations_Op_Mix_001","label":"Wet Granulation Mix : S88_Operation","path":"Operations/Op_Mix_001","userType":"S88_Operation","sourceIri":"https://example.tatsoft.com/ontology/demo/Op_Mix_001","kind":"tag"}},{"data":{"id":"e62","source":"f_Operations","target":"t_Operations_Op_Mix_001","kind":"containment"}},{"data":{"id":"e63","source":"t_Operations_Op_Mix_001","target":"ut_S88_Operation","label":"instanceOf","kind":"instanceOf"}},{"data":{"id":"t_Operations_AnomalyNarrative","label":"AnomalyNarrative : Text","path":"Operations/AnomalyNarrative","userType":"Text","sourceIri":"","kind":"tag"}},{"data":{"id":"e64","source":"f_Operations","target":"t_Operations_AnomalyNarrative","kind":"containment"}},{"data":{"id":"t_Chat_Query","label":"Query : Text","path":"Chat/Query","userType":"Text","sourceIri":"","kind":"tag"}},{"data":{"id":"e65","source":"f_Chat","target":"t_Chat_Query","kind":"containment"}},{"data":{"id":"t_Chat_Reply","label":"Reply : Json","path":"Chat/Reply","userType":"Json","sourceIri":"","kind":"tag"}},{"data":{"id":"e66","source":"f_Chat","target":"t_Chat_Reply","kind":"containment"}}];
const SOLUTION_NAME = "LocalAI KnowledgeGraph Demo";</script>
<script>
// Wait for the cytoscape global before init. The <script src='Libraries/...'>
// above can 404 when the output folder is not co-located with a vendored
// cytoscape (e.g. HTML hand-copied away from the install-tree sibling). There
// is intentionally NO CDN fallback (offline plant-floor safety), so the only
// way the global appears is a successful local load. This poll caps at 60
// retries (3s) and then surfaces a clear console error instead of polling
// forever, naming the local path that needs to be reachable.
(function initWhenReady(retries) {
  if (typeof cytoscape === 'undefined') {
    if (retries > 60) { console.error('cytoscape.js failed to load after 3s. Graph cannot render. Check that Libraries/cytoscape/dist/cytoscape.min.js is present next to this report (vendored with the install tree).'); return; }
    setTimeout(function() { initWhenReady(retries + 1); }, 50);
    return;
  }
// Resolve theme colors from the CSS custom-properties declared in <style>.
// The same vars drive #header / #toolbar / #info chrome, so the cytoscape
// graph stays in lockstep with the HTML chrome under any of three palette
// sources: (1) Light base defaults, (2) prefers-color-scheme dark override,
// (3) explicit Designer-captured palette injected after the media query.
// Resolved ONCE at init — the standalone HTML is a baked artifact, not a
// live-switching surface.
const css = getComputedStyle(document.documentElement);
const v = (name, fallback) => ((css.getPropertyValue(name) || '').trim() || fallback);
const NODE_CLASS_FILL   = v('--node-class-fill',   '#4a90d9');
const NODE_CLASS_BORDER = v('--node-class-border', '#2c5f8f');
const NODE_FOLDER_FILL  = v('--node-folder-fill',  '#7a7a4a');
const NODE_FOLDER_BORDER= v('--node-folder-border','#555533');
const NODE_TAG_FILL     = v('--node-tag-fill',     '#5a9f5a');
const NODE_TAG_BORDER   = v('--node-tag-border',   '#3a6f3a');
const EDGE_REFERENCE    = v('--edge-reference',    '#e67e22');
const EDGE_COMPOSITION  = v('--edge-composition',  '#9b59b6');
const EDGE_INHERITANCE  = v('--edge-inheritance',  '#c0c0c0');
const EDGE_CONTAINMENT  = v('--edge-containment',  '#777777');
const SELECTION         = v('--selection',         '#ff9800');
// Node label color tracks --fg (captured TextForeground) so node text matches the
// theme like the embedded TKnowledgeGraph control: dark on a light page, light on a
// dark page. Was pinned to --node-label (#ffffff) — which left unreadable white text
// on the light-theme soft node fills. (Luis 2026-06-02 light-theme parity.)
const NODE_LABEL        = v('--fg',                 '#dddddd');
const EDGE_LABEL        = v('--edge-label',        '#cccccc');
const TEXT_BG_EDGE      = v('--text-bg-edge',      '#1e1e1e');
const cy = cytoscape({
  container: document.getElementById('cy'),
  elements: ELEMENTS,
  style: [
    { selector: 'node[kind = "class"]', style: {
        'background-color': NODE_CLASS_FILL, 'shape': 'round-rectangle', 'label': 'data(label)',
        'color': NODE_LABEL, 'text-valign': 'center', 'text-halign': 'center',
        'font-size': 12, 'width': 'label', 'height': 28, 'padding': '8px',
        'border-color': NODE_CLASS_BORDER, 'border-width': 1 } },
    { selector: 'node[kind = "folder"]', style: {
        'background-color': NODE_FOLDER_FILL, 'shape': 'round-rectangle', 'label': 'data(label)',
        'color': NODE_LABEL, 'text-valign': 'center', 'text-halign': 'center',
        'font-size': 11, 'width': 'label', 'height': 24, 'padding': '6px',
        'border-color': NODE_FOLDER_BORDER, 'border-width': 1 } },
    { selector: 'node[kind = "tag"]', style: {
        'background-color': NODE_TAG_FILL, 'shape': 'ellipse', 'label': 'data(label)',
        'color': NODE_LABEL, 'text-valign': 'center', 'text-halign': 'center',
        'font-size': 11, 'width': 'label', 'height': 24, 'padding': '6px',
        'border-color': NODE_TAG_BORDER, 'border-width': 1 } },
    { selector: 'edge[kind = "reference"]', style: {
        'line-color': EDGE_REFERENCE, 'line-style': 'dashed', 'width': 1.5,
        'target-arrow-color': EDGE_REFERENCE, 'target-arrow-shape': 'triangle',
        'curve-style': 'bezier', 'label': 'data(label)', 'font-size': 10,
        'color': EDGE_LABEL, 'text-background-color': TEXT_BG_EDGE,
        'text-background-opacity': 0.8, 'text-background-padding': 2 } },
    { selector: 'edge[kind = "composition"]', style: {
        'line-color': EDGE_COMPOSITION, 'width': 2,
        'target-arrow-color': EDGE_COMPOSITION, 'target-arrow-shape': 'diamond',
        'curve-style': 'bezier', 'label': 'data(label)', 'font-size': 10,
        'color': EDGE_LABEL, 'text-background-color': TEXT_BG_EDGE,
        'text-background-opacity': 0.8, 'text-background-padding': 2 } },
    { selector: 'edge[kind = "inheritance"]', style: {
        'line-color': EDGE_INHERITANCE, 'width': 2,
        'target-arrow-color': EDGE_INHERITANCE, 'target-arrow-shape': 'triangle-tee',
        'curve-style': 'bezier' } },
    { selector: 'edge[kind = "containment"]', style: {
        'line-color': EDGE_CONTAINMENT, 'width': 1, 'target-arrow-color': EDGE_CONTAINMENT,
        'target-arrow-shape': 'triangle', 'curve-style': 'bezier' } },
    { selector: 'node:selected', style: { 'border-color': SELECTION, 'border-width': 3 } },
    { selector: '.hidden', style: { 'display': 'none' } },
    { selector: '.dimmed', style: { 'opacity': 0.15 } }
  ],
  layout: { name: 'cose', animate: false, padding: 30, nodeRepulsion: 8000, idealEdgeLength: 80 }
});

// Click ? detail panel
const info = document.getElementById('info');
cy.on('tap', 'node', evt => {
  const d = evt.target.data();
  let html = '<h3>' + esc(d.label || d.name || d.id) + '</h3>';
  html += '<div class="kind">' + (d.kind || '') + '</div><table>';
  const add = (k, v) => { if (v) html += '<tr><td>' + esc(k) + '</td><td>' + esc(v) + '</td></tr>'; };
  add('Name', d.name);
  add('Path', d.path);
  add('UserType', d.userType);
  add('Description', d.description);
  add('Labels', d.labels);
  add('Source IRI', d.sourceIri);
  html += '</table>';
  if (d.members && d.members.length) {
    html += '<h3 style="margin-top:16px;">Members</h3><table>';
    for (const m of d.members)
      html += '<tr><td><code>' + esc(m.n) + '</code></td><td>' + esc(m.t) + '</td></tr>';
    html += '</table>';
  }
  info.innerHTML = html;
});
cy.on('tap', evt => { if (evt.target === cy) info.innerHTML = '<h3>UNS</h3><div class="kind">Click a node for details</div>'; });

// Node-kind filter toggles
document.querySelectorAll('.filter').forEach(cb => cb.addEventListener('change', () => {
  document.querySelectorAll('.filter').forEach(c => {
    const kind = c.getAttribute('data-kind');
    cy.nodes('[kind = "' + kind + '"]').toggleClass('hidden', !c.checked);
  });
}));

// Edge-kind filter toggles
document.querySelectorAll('.edgefilter').forEach(cb => cb.addEventListener('change', () => {
  document.querySelectorAll('.edgefilter').forEach(c => {
    const kind = c.getAttribute('data-kind');
    cy.edges('[kind = "' + kind + '"]').toggleClass('hidden', !c.checked);
  });
}));

// Live search — highlights matches, dims non-matches
document.getElementById('search').addEventListener('input', e => {
  const q = e.target.value.trim().toLowerCase();
  if (!q) { cy.nodes().removeClass('dimmed'); return; }
  cy.nodes().forEach(n => {
    const d = n.data();
    const hay = (d.label + ' ' + (d.name || '') + ' ' + (d.path || '')).toLowerCase();
    n.toggleClass('dimmed', !hay.includes(q));
  });
});

// Download current node positions as JSON. Uses cytoscape's per-node position()
// rather than the full cy.json() blob — keeps the file tiny and forward-compatible.
// Round-trip works against both this position-only payload and a raw cy.json() dump.
document.getElementById('downloadLayout').addEventListener('click', () => {
  const positions = {};
  cy.nodes().forEach(n => {
    const p = n.position();
    positions[n.id()] = { x: p.x, y: p.y };
  });
  const payload = {
    version: 1,
    kind: 'uns-visual-layout',
    solution: SOLUTION_NAME,
    generated: new Date().toISOString(),
    nodeCount: Object.keys(positions).length,
    positions: positions
  };
  const safe = (SOLUTION_NAME || 'uns').replace(/[^A-Za-z0-9_\-]+/g, '_');
  const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = safe + '-layout.json';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  setTimeout(() => URL.revokeObjectURL(url), 100);
});

// Load layout from JSON file — accepts either the position-only payload above
// or a raw cy.json() dump (elements.nodes[].data.id + .position). Missing nodes
// in the graph are skipped silently; nodes in the graph not in the file keep
// their current/auto-layout position.
document.getElementById('loadLayout').addEventListener('click', () => {
  document.getElementById('loadLayoutFile').click();
});
document.getElementById('loadLayoutFile').addEventListener('change', e => {
  const f = e.target.files && e.target.files[0];
  if (!f) return;
  const reader = new FileReader();
  reader.onload = ev => {
    try {
      const data = JSON.parse(ev.target.result);
      let positions = null;
      if (data && data.positions && typeof data.positions === 'object') {
        positions = data.positions;
      } else if (data && data.elements && Array.isArray(data.elements.nodes)) {
        positions = {};
        for (const n of data.elements.nodes) {
          if (n && n.data && n.data.id && n.position) positions[n.data.id] = n.position;
        }
      }
      if (!positions) { alert('Layout file format not recognized.'); return; }
      let applied = 0, missing = 0;
      for (const id of Object.keys(positions)) {
        const node = cy.getElementById(id);
        if (node && node.length > 0) {
          const p = positions[id];
          if (typeof p.x === 'number' && typeof p.y === 'number') {
            node.position({ x: p.x, y: p.y });
            applied++;
          }
        } else {
          missing++;
        }
      }
      console.log('UNS layout loaded: ' + applied + ' node(s) repositioned, ' + missing + ' id(s) not in graph.');
    } catch (err) {
      alert('Failed to load layout: ' + err.message);
    } finally {
      e.target.value = '';
    }
  };
  reader.readAsText(f);
});

function esc(s) { return (s==null?'':String(s)).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
})(0);
</script>
</body>
</html>

Requirements

This component is Portable: it runs both on Windows WPF displays and on Web Pages on any platform. WPF deployments use WebView2 (Microsoft Edge runtime, bundled with FrameworX). HTML5 deployments render through the OpenSilver-hosted cytoscape.js, served from the Display contents.


Configuration

  1. Go to Displays / Draw.

  2. On the Components Panel, select Charts, then Knowledge Graph.

  3. Click or drag-and-drop it onto the Drawing area.

  4. Double-click the object to open the configuration window, or set properties directly in the Properties panel.

Knowledge Graph Settings

Field

Description

Control name

Identifies the control within the Display. Used by binding expressions to address its properties.

Render mode

Selects the rendering pipeline:

  • Interactive (default) — full pan / zoom / click cytoscape.js layout. Recommended for operator workstations.

  • Static — reserved for 10.1.6. Will render a Mermaid→SVG snapshot for non-interactive contexts (reports, thumbnails, low-end clients). In 10.1.5 selecting this mode falls back to Interactive.

Selected node path (output)

Tag-bindable string that receives the full UNS path of the most recently clicked node (e.g. Site1.Area2.Tank305). Empty when no node is selected. Bind to a Tag to react to graph navigation elsewhere in the Display.

Selected node type (output)

Tag-bindable string that receives the UserType name of the most recently clicked node (e.g. Tank, Pump). Empty when no node is selected. Pairs with the path output for type-aware drill-downs.

Localize

When checked (default), node labels render in the active Designer / Runtime culture using the standard FrameworX translation pipeline. Uncheck to show raw ontology IRIs / labels regardless of culture.


Runtime execution

At runtime the control subscribes to the SolutionSettings.KnowledgeGraphSource column. When the column changes — the operator clicks the Asset Tree Knowledge Graph button, an AI session calls generate_uns_visual, or a Script calls TK.GenerateUnsVisual — the control re-renders without a Display reload. Operators can pan and zoom freely; clicking a node updates the two selection-output properties, which downstream bindings can wire to navigation actions, ChildDisplay swaps, or recipe dialogs.

Source content is the W3C-aligned Mermaid Markdown produced by the UNS Visual Report Generator (the same pipeline that emits standalone HTML / SVG reports from the Asset Tree). The control accepts classDiagram nodes, inheritance edges, composition edges, and the FrameworX-specific node-kind decorations the generator emits.


Source regenerators

Source

How it writes KnowledgeGraphSource

Asset Tree button

Operator clicks Knowledge Graph at the top of the Asset Tree. The visual report is generated and the column updated. Existing Knowledge Graph controls on open Displays re-render automatically.

generate_uns_visual MCP tool

An AI assistant invokes the DesignerMCP tool. The tool produces the same Mermaid Markdown the Asset Tree button does, writes it to the column, and returns the source so the AI can inspect or transform it.

TK.GenerateUnsVisual script API

A Script (Task, Class, or Display CodeBehind) calls TK.GenerateUnsVisual() to refresh the source under programmatic triggers (cron, alarm reaction, batch end).

Auto-refresh hook

Edits to UnsUserTypes, UnsTags, or the Asset Tree silently mark the source as stale; the next Asset Tree render or TK.GenerateUnsVisual call regenerates from current state.


Display action wiring

Both output properties are standard FrameworX bindable strings — the same pattern used by other portable controls’ selection outputs. Typical wiring:


HTML5 / OpenSilver parity

The control compiles under both WPF and OpenSilver. On HTML5 the WebView2 host is replaced with the OpenSilver-hosted cytoscape.js loaded from Display contents; the property surface, source contract, and selection outputs are identical. Localize uses the same translation pipeline as TTextBlock in HTML5 mode.


Notes


In this section...