2026-07-26 21:28:14 +02:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
2026-07-29 09:03:59 +02:00
< title > PR Data Meta Model · T-Box< / title >
2026-07-26 21:28:14 +02:00
< style >
: root {
2026-07-29 09:03:59 +02:00
--navy : #0A1F44 ; --gold : #B8935A ; --ink : #1A1A1A ; --bg : #F7F6F2 ;
--panel : #fff ; --rule : #E4E2DA ; --muted : #6B6B6B ; --soft : #F1EFE8 ;
--mono : ui-monospace , "SF Mono" , SFMono-Regular , Menlo , Consolas , monospace ;
2026-07-26 21:28:14 +02:00
--sans : - apple-system , BlinkMacSystemFont , "Segoe UI" , Helvetica , Arial , sans-serif ;
2026-07-29 09:03:59 +02:00
--serif : Georgia , "Times New Roman" , serif ;
2026-07-26 21:28:14 +02:00
}
* { box-sizing : border-box ; -webkit- tap-highlight-color : transparent }
2026-07-29 09:03:59 +02:00
html , body { margin : 0 ; height : 100 % }
2026-07-26 21:28:14 +02:00
body { font-family : var ( - - sans ) ; background : var ( - - bg ) ; color : var ( - - ink ) ;
display : flex ; flex-direction : column ; overflow : hidden }
: focus-visible { outline : 2 px solid var ( - - gold ) ; outline-offset : 2 px }
2026-07-29 09:03:59 +02:00
header { background : var ( - - navy ) ; color : #fff ; padding : 11 px 20 px ; display : flex ;
align-items : center ; gap : 18 px ; flex-shrink : 0 }
header h1 { margin : 0 ; font-family : var ( - - mono ) ; font-size : 12.5 px ; font-weight : 600 ;
letter-spacing : .17 em ; text-transform : uppercase }
header . sub { font-size : 11 px ; color : rgba ( 255 , 255 , 255 , .6 ) ; margin-top : 3 px }
. tabs { margin-left : auto ; display : flex ; gap : 2 px ; background : rgba ( 255 , 255 , 255 , .08 ) ;
padding : 3 px ; border-radius : 4 px }
. tabs button { padding : 6 px 14 px ; border : none ; background : transparent ; color : rgba ( 255 , 255 , 255 , .7 ) ;
font-family : var ( - - mono ) ; font-size : 10 px ; letter-spacing : .09 em ; text-transform : uppercase ;
cursor : pointer ; border-radius : 3 px ; font-weight : 600 }
. tabs button . on { background : #fff ; color : var ( - - navy ) }
main { flex : 1 ; overflow-y : auto ; padding : 22 px 20 px 60 px }
. wrap { max-width : 1220 px ; margin : 0 auto }
. lede { font-family : var ( - - serif ) ; font-size : 21 px ; line-height : 1.45 ; color : var ( - - navy ) ;
margin : 0 0 6 px ; max-width : 860 px }
. lede b { color : var ( - - gold ) }
. sublede { font-size : 13 px ; color : var ( - - muted ) ; margin : 0 0 22 px ; max-width : 860 px ; line-height : 1.55 }
. kpis { display : flex ; gap : 26 px ; flex-wrap : wrap ; padding : 16 px 0 20 px ;
border-top : 1 px solid var ( - - rule ) ; border-bottom : 1 px solid var ( - - rule ) ; margin-bottom : 24 px }
. kpi b { display : block ; font-family : var ( - - serif ) ; font-size : 29 px ; color : var ( - - navy ) ; line-height : 1 }
. kpi . warn b { color : var ( - - gold ) }
. kpi span { font-size : 9.5 px ; text-transform : uppercase ; letter-spacing : .1 em ; color : var ( - - muted ) ;
display : block ; margin-top : 5 px }
h2 . sec { font-family : var ( - - mono ) ; font-size : 10 px ; letter-spacing : .15 em ; text-transform : uppercase ;
color : var ( - - muted ) ; margin : 30 px 0 12 px ; font-weight : 600 }
. grid { display : grid ; grid-template-columns : repeat ( auto - fill , minmax ( 280 px , 1 fr ) ) ; gap : 14 px }
. card { background : var ( - - panel ) ; border : 1 px solid var ( - - rule ) ; border-radius : 4 px ;
padding : 0 ; overflow : hidden ; cursor : pointer ; transition : box-shadow .15 s }
. card : hover { box-shadow : 0 3 px 14 px rgba ( 10 , 31 , 68 , .10 ) }
. card . top { height : 4 px }
. card . body { padding : 15 px 17 px 16 px }
. card . role { font-family : var ( - - mono ) ; font-size : 8.5 px ; letter-spacing : .13 em ;
2026-07-26 21:28:14 +02:00
text-transform : uppercase ; font-weight : 600 }
2026-07-29 09:03:59 +02:00
. card h3 { margin : 5 px 0 2 px ; font-family : var ( - - serif ) ; font-size : 19 px ; color : var ( - - navy ) }
. card . who { font-size : 11.5 px ; color : var ( - - muted ) ; margin-bottom : 12 px }
. card . nums { display : flex ; gap : 15 px ; flex-wrap : wrap ; padding-top : 11 px ; border-top : 1 px solid var ( - - rule ) }
. card . nums div { font-size : 10.5 px ; color : var ( - - muted ) }
. card . nums b { display : block ; font-family : var ( - - mono ) ; font-size : 15 px ; color : var ( - - ink ) }
. flag { display : inline-block ; margin-top : 11 px ; padding : 4 px 9 px ; border-radius : 3 px ;
background : #FBF3E4 ; border : 1 px solid var ( - - gold ) ; color : #7A5B2A ;
font-family : var ( - - mono ) ; font-size : 9.5 px ; letter-spacing : .05 em }
table { width : 100 % ; border-collapse : collapse ; background : var ( - - panel ) ; font-size : 12.5 px }
th { text-align : left ; padding : 9 px 11 px ; font-family : var ( - - mono ) ; font-size : 9 px ;
letter-spacing : .1 em ; text-transform : uppercase ; color : var ( - - muted ) ;
border-bottom : 1.5 px solid var ( - - navy ) ; white-space : nowrap ; background : var ( - - panel ) ;
position : sticky ; top : 0 ; cursor : pointer }
td { padding : 8 px 11 px ; border-bottom : 1 px solid var ( - - rule ) ; vertical-align : top }
tr . arb td { background : #FDFAF3 }
tr : hover td { background : var ( - - soft ) }
tr { cursor : pointer }
. dot { display : inline-block ; width : 8 px ; height : 8 px ; border-radius : 2 px ; margin-right : 7 px ;
vertical-align : baseline }
. mono { font-family : var ( - - mono ) ; font-size : 10.5 px ; color : var ( - - muted ) }
. pill { display : inline-block ; padding : 2 px 7 px ; border-radius : 2 px ; font-family : var ( - - mono ) ;
font-size : 8.5 px ; letter-spacing : .06 em ; text-transform : uppercase ; border : 1 px solid }
. bar { display : flex ; height : 26 px ; border-radius : 3 px ; overflow : hidden ; margin : 6 px 0 4 px }
. bar div { position : relative }
. legend { display : flex ; gap : 16 px ; flex-wrap : wrap ; font-size : 11 px ; color : var ( - - muted ) ; margin-bottom : 20 px }
. legend i { display : inline-block ; width : 10 px ; height : 10 px ; border-radius : 2 px ; margin-right : 6 px }
. filters { display : flex ; gap : 8 px ; flex-wrap : wrap ; margin-bottom : 14 px ; align-items : center }
. filters input { padding : 7 px 11 px ; border : 1 px solid var ( - - rule ) ; border-radius : 3 px ;
font-family : var ( - - mono ) ; font-size : 12 px ; min-width : 180 px ; background : #fff }
. filters button { padding : 6 px 12 px ; border : 1 px solid var ( - - rule ) ; background : #fff ;
border-radius : 3 px ; font-family : var ( - - mono ) ; font-size : 10 px ; cursor : pointer ;
text-transform : uppercase ; letter-spacing : .07 em ; color : var ( - - muted ) }
. filters button . on { background : var ( - - navy ) ; color : #fff ; border-color : var ( - - navy ) }
# insp { position : fixed ; top : 0 ; right : 0 ; width : 390 px ; max-width : 100 % ; height : 100 % ;
2026-07-26 21:28:14 +02:00
background : var ( - - panel ) ; border-left : 1 px solid var ( - - rule ) ;
2026-07-29 09:03:59 +02:00
box-shadow : -8 px 0 30 px rgba ( 10 , 31 , 68 , .13 ) ; overflow-y : auto ; padding : 22 px 22 px 60 px ;
transform : translateX ( 100 % ) ; transition : transform .22 s ; z-index : 50 }
2026-07-26 21:28:14 +02:00
# insp . open { transform : translateX ( 0 ) }
2026-07-29 09:03:59 +02:00
# insp . x { position : absolute ; top : 13 px ; right : 15 px ; border : none ; background : none ;
font-size : 23 px ; cursor : pointer ; color : var ( - - muted ) ; line-height : 1 }
. inh { font-style : italic ; color : #8A6A28 ; font-size : 9 px ; text-transform : uppercase ;
letter-spacing : .06 em ; font-family : var ( - - mono ) }
. backlink { background : none ; border : none ; padding : 0 0 10 px ; font-family : var ( - - mono ) ;
font-size : 10 px ; color : var ( - - navy ) ; cursor : pointer ; letter-spacing : .05 em }
. kind { font-family : var ( - - mono ) ; font-size : 9 px ; letter-spacing : .14 em ;
2026-07-26 21:28:14 +02:00
text-transform : uppercase ; font-weight : 600 }
2026-07-29 09:03:59 +02:00
. gctl . gsec { font-family : var ( - - mono ) ; font-size : 8.5 px ; letter-spacing : .12 em ;
text-transform : uppercase ; color : var ( - - muted ) ; margin : 9 px 0 4 px ; font-weight : 600 }
. gctl select { width : 100 % ; padding : 4 px ; font-family : var ( - - mono ) ; font-size : 10 px ;
border : 1 px solid var ( - - rule ) ; border-radius : 3 px ; margin-top : 5 px }
. lanelabel { font-family : var ( - - mono ) ; font-size : 10 px ; letter-spacing : .1 em ; font-weight : 600 }
. elabel { font-family : var ( - - mono ) ; font-size : 7.5 px ; fill : #5A6270 ; text-anchor : middle ;
pointer-events : none }
. elbg { fill : #F4F2EC ; pointer-events : none }
. elabel . dim , . elbg . dim { opacity : .06 }
# insp h3 { margin : 7 px 0 2 px ; font-family : var ( - - serif ) ; font-size : 21 px ; color : var ( - - navy ) }
# insp . iri { font-family : var ( - - mono ) ; font-size : 10.5 px ; color : var ( - - muted ) ; margin-bottom : 15 px }
# insp h4 { font-family : var ( - - mono ) ; font-size : 9 px ; letter-spacing : .14 em ; text-transform : uppercase ;
color : var ( - - muted ) ; margin : 19 px 0 7 px ; border-top : 1 px solid var ( - - rule ) ; padding-top : 11 px }
# insp p { font-size : 12.5 px ; line-height : 1.6 ; margin : 0 0 8 px }
. row { display : flex ; justify-content : space-between ; gap : 12 px ; padding : 5 px 0 ;
border-bottom : 1 px dotted var ( - - rule ) ; font-size : 12 px }
. row span : last-child { color : var ( - - muted ) ; text-align : right }
2026-07-26 21:28:14 +02:00
. jump { background : none ; border : none ; padding : 0 ; font : inherit ; color : var ( - - navy ) ;
2026-07-29 09:03:59 +02:00
cursor : pointer ; text-decoration : underline ; text-underline-offset : 2 px ; font-size : 12 px }
. note { background : #FBF3E4 ; border-left : 3 px solid var ( - - gold ) ; padding : 10 px 12 px ;
font-size : 11.5 px ; line-height : 1.55 ; color : #6B5426 }
/* ---- graph ---- */
. gwrap { position : relative ; height : calc ( 100 vh - 250 px ) ; min-height : 420 px ;
border : 1 px solid var ( - - rule ) ; border-radius : 4 px ; overflow : hidden ; background :
linear-gradient ( 90 deg , rgba ( 10 , 31 , 68 , .03 ) 1 px , transparent 1 px ) 0 0 / 26 px 26 px ,
linear-gradient ( 180 deg , #FCFBF7 , #F0EEE5 ) ; touch-action : none }
. gwrap svg { display : block ; width : 100 % ; height : 100 % ; cursor : grab }
. gwrap svg . drag { cursor : grabbing }
. bandrect { opacity : .5 }
. bandlabel { font-family : var ( - - mono ) ; font-size : 9.5 px ; letter-spacing : .14 em ;
text-transform : uppercase ; font-weight : 600 }
. bandtag { font-size : 9 px ; fill : var ( - - muted ) ; font-style : italic }
. gnode { cursor : pointer }
. gnode rect { stroke-width : 1.4 }
. gnode text { font-size : 9 px ; text-anchor : middle ; pointer-events : none ; font-weight : 500 }
. gnode . dim { opacity : .11 }
. gnode . sel rect { stroke-width : 3 }
. gedge { fill : none ; stroke : #A6ABB5 ; stroke-width : 1 ; marker-end : url ( #ga ) }
. gedge . cross { stroke : #B8935A ; stroke-width : 1.8 ; marker-end : url ( #gg ) }
. gedge . dim { opacity : .05 }
. gedge . hot { stroke : var ( - - navy ) ; stroke-width : 2.4 ; marker-end : url ( #gn ) }
. gctl { position : absolute ; left : 12 px ; top : 12 px ; background : rgba ( 255 , 255 , 255 , .94 ) ;
border : 1 px solid var ( - - rule ) ; border-radius : 4 px ; padding : 9 px 11 px ; font-size : 11 px ;
max-width : 230 px }
. gctl . seg { display : flex ; border : 1 px solid var ( - - rule ) ; border-radius : 3 px ; overflow : hidden ;
margin-bottom : 8 px }
. gctl . seg button { flex : 1 ; padding : 5 px ; border : none ; background : #fff ; cursor : pointer ;
font-family : var ( - - mono ) ; font-size : 9 px ; text-transform : uppercase ; letter-spacing : .06 em ;
color : var ( - - muted ) }
. gctl . seg button + button { border-left : 1 px solid var ( - - rule ) }
. gctl . seg button . on { background : var ( - - navy ) ; color : #fff }
. gctl label { display : flex ; align-items : center ; gap : 7 px ; padding : 3 px 0 ; cursor : pointer }
. gctl label i { display : inline-block ; width : 8 px ; height : 8 px ; border-radius : 2 px ; flex-shrink : 0 }
. gctl input { accent-color : var ( - - navy ) }
. gzoom { position : absolute ; right : 12 px ; bottom : 12 px ; display : flex ; gap : 3 px ;
background : #fff ; border : 1 px solid var ( - - rule ) ; border-radius : 4 px ; padding : 3 px }
. gzoom button { width : 30 px ; height : 30 px ; border : none ; background : #fff ; cursor : pointer ;
font-size : 15 px ; color : var ( - - navy ) ; border-radius : 3 px }
. ghint { position : absolute ; left : 12 px ; bottom : 12 px ; font-family : var ( - - mono ) ; font-size : 9 px ;
color : var ( - - muted ) ; background : rgba ( 255 , 255 , 255 , .85 ) ; padding : 4 px 8 px ; border-radius : 3 px }
footer { background : var ( - - navy ) ; color : rgba ( 255 , 255 , 255 , .5 ) ; padding : 7 px 20 px ;
font-family : var ( - - mono ) ; font-size : 9 px ; letter-spacing : .06 em ; flex-shrink : 0 ;
display : flex ; justify-content : space-between }
@ media ( max-width : 760px ) {
. tabs { width : 100 % ; margin : 8 px 0 0 } header { flex-wrap : wrap }
. kpis { gap : 18 px } . kpi b { font-size : 23 px }
table { font-size : 11.5 px } td , th { padding : 7 px 8 px }
. hide-s { display : none }
2026-07-26 21:28:14 +02:00
}
@ media ( prefers-reduced-motion : reduce ) { * { transition : none !important } }
< / style >
< / head >
< body >
< header >
< div >
< h1 > PR Data Meta Model · T-Box< / h1 >
2026-07-29 09:03:59 +02:00
< div class = "sub" > Six layers, crossed with what governance asserts and what a harvester observes< / div >
2026-07-26 21:28:14 +02:00
< / div >
2026-07-29 09:03:59 +02:00
< div class = "tabs" role = "tablist" >
< button id = "t-scope" class = "on" > Overview< / button >
< button id = "t-model" > Layers< / button >
< button id = "t-graph" > Graph< / button >
< button id = "t-props" > Relations< / button >
< button id = "t-dep" > Deprecated< / button >
2026-07-26 21:28:14 +02:00
< / div >
< / header >
2026-07-29 09:03:59 +02:00
< main > < div class = "wrap" id = "view" > < / div > < / main >
< div id = "insp" role = "dialog" aria-label = "Details" >
< button class = "x" id = "insp-x" aria-label = "Close" > × < / button >
< div id = "insp-body" > < / div >
< / div >
2026-07-26 21:28:14 +02:00
< footer >
< span > Build artefact of pr_metamodel.ttl — regenerate, never edit< / span >
2026-07-29 09:03:59 +02:00
< span id = "f-count" > < / span >
2026-07-26 21:28:14 +02:00
< / footer >
< script >
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
var M = { "nodes" : [ { "id" : "pr:MetaModelObject" , "kind" : "Class" , "label" : "Meta Model Object" , "shortLabel" : "Meta model object" , "layer" : "roots" , "prov" : "structural" , "depth" : 0 , "parent" : "" , "comment" : "Anything the meta model describes. Actors are deliberately NOT Meta Model Objects." , "harvest" : "" , "authoring" : "" , "acronym" : "" , "deprecated" : false , "replacedBy" : "" , "abstract" : false , "members" : [ ] } , { "id" : "pr:DefinedObject" , "kind" : "Class" , "label" : "Defined Object" , "shortLabel" : "Defined" , "layer" : "roots" , "prov" : "defined" , "depth" : 1 , "parent" : "pr:MetaModelObject" , "comment" : "Somebody decided it. Written by governance, edited through a pull request on the domain TTL file, and subject to the full identity and lifecycle shape: identifier, name, owner, owning Data Domain, status, version." , "harvest" : "" , "authoring" : "ASSERTED" , "acronym" : "" , "deprecated" : false , "replacedBy" : "" , "abstract" : true , "members" : [ ] } , { "id" : "pr:CuratedObject" , "kind" : "Class" , "label" : "Curated Object (deprecated)" , "shortLabel" : "" , "layer" : "roots" , "prov" : "structural" , "depth" : 0 , "parent" : "" , "comment" : "v1.4 term. Renamed because 'curated' is governance jargon; 'defined' says the same thing to a business reader. No instance ever named this class directly." , "harvest" : "" , "authoring" : "" , "acronym" : "" , "deprecated" : true , "replacedBy" : "pr:DefinedObject" , "abstract" : false , "members" : [ ] } , { "id" : "pr:CapturedObject" , "kind" : "Class" , "label" : "Captured Object" , "shortLabel" : "Captured" , "layer" : "roots" , "prov" : "captured" , "depth" : 1 , "parent" : "pr:MetaModelObject" , "comment" : "Read from a running system and rebuilt on every run. Never hand-edited. Carries sourceIdentifier, fullyQualifiedName and harvestedOn instead of declared ownership: attribution is INFERRED through the Data Element it is mapped to, never asserted on the node." , "harvest" : "" , "authoring" : "HARVESTED" , "acronym" : "" , "deprecated" : false , "replacedBy" : "" , "abstract" : true , "members" : [ ] } , { "id" : "pr:ObservedObject" , "kind" : "Class" , "label" : "Observed Object (deprecated)" , "shortLabel" : "" , "layer" : "roots" , "prov" : "structural" , "depth" : 0 , "parent" : "" , "comment" : "v1.4 term. Renamed because 'observed' is passive and technical; 'captured' says where it came from. No instance ever named this class directly." , "harvest" : "" , "authoring" : "" , "acronym" : "" , "deprecated" : true , "replacedBy" : "pr:CapturedObject" , "abstract" : false , "members" : [ ] } , { "id" : "pr:OwnershipLayerObject" , "kind" : "Class" , "label" : "Ownership & Categorization Layer Object" , "shortLabel" : "" , "layer" : "ownership" , "prov" : "defined" , "depth" : 2 , "parent" : "pr:DefinedObject" , "comment" : "" , "harvest" : "" , "authoring" : "" , "acronym" : "" , "deprecated" : false , "replacedBy" : "" , "abstract" : false , "members" : [ ] } , { "id" : "pr:BusinessLayerObject" , "kind" : "Class" , "label" : "Business Layer Object" , "shortLabel" : "" , "layer" : "business" , "prov" : "defined" , "depth" : 2 , "parent" : "pr:DefinedObject" , "comment" : "" , "harvest" : "" , "authoring" : "" , "acronym" : "" , "deprecated" : false , "replacedBy" : "" , "abstract" : false , "members" : [ ] } , { "id" : "pr:LogicalLayerObject" , "kind" : "Class" , "label" : "Logical Layer Object" , "shortLabel" : "" , "layer" : "logical" , "prov" : "defined" , "depth" : 2 , "parent" : "pr:DefinedObject" , "comment" : "How the data is structured." , "harvest" : "" , "authoring" : "" , "acronym" : "" , "deprecated" : false , "replacedBy" : "" , "abstract" : false , "members" : [ ] } , { "id" : "pr:DeliveryLayerObject" , "kind" : "Class" , "label" : "Delivery Layer Object" , "shortLabel" : "" , "layer" : "delivery" , "prov" : "defined" , "depth" : 2 , "parent" : "pr:DefinedObject" , "comment" : "What we promise, to whom, until when. A different question from how the data is structured, hence a layer of its own: version, owner, changelog, support channel, consumers to warn." , "harvest" : "" , "authoring" : "" , "acronym" : "" , "deprecated" : false , "replacedBy" : "" , "abstract" : false , "members" : [ ] } , { "id" : "pr:PhysicalLayerObject" , "kind" : "Class" , "label" : "Physical Layer Object" , "shortLabel" : "" , "layer" : "physical" , "prov" : "structural" , "depth" : 1 , "parent" : "pr:MetaModelObject" , "comment" : "\"\"The one layer whose members do NOT share a provenance, which is why this class -- unlike the other five layer roots -- says nothing about it.\n\nA System is DEFINED: somebody decides that MDM Product is a system worth trackin
2026-07-29 09:03:59 +02:00
var byId = {};
M.nodes.forEach(function(n){ byId[n.id] = n; });
M.relations.concat(M.attributes, M.bridges).forEach(function(p){ byId[p.id] = p; });
var view = " scope ", filt = {q:" ", kind:" "};
function esc(s){ return (s||" ").replace(/&/g," & amp ; ").replace(/</g," & lt ; "); }
function clean(s){ return (s||" ").replace(" ( deprecated ) "," "); }
document.getElementById(" f - count ").textContent =
M.nodes.filter(function(n){return !n.deprecated;}).length + " classes \u00b7 "
+ M.relations.filter(function(r){return !r.deprecated;}).length + " relations ";
/* ============================================================ GRAPH */
var G = {mode:" strata ", provs:[], sel:null, hot:null, labels:true,
layers:{},
view:{x:0,y:0,s:1}, nodes:[], edges:[]};
var NW=126, NH=32, LANEPAD=26, ROWGAP=15, BANDGAP=34, GUT=142;
function provColour(p){
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
return p === " captured " ? " # 8 A6A28 " : p === " defined " ? " # 1E4 B9B " : " # 6 B6B6B ";
2026-07-29 09:03:59 +02:00
}
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
/* Scoped relations: these carry no rdfs:domain or rdfs:range on purpose,
because each serves several classes with different targets and the scope
belongs in the SHACL shapes. Both the graph and the cards read this list,
so an edge drawn in one is never missing from the other. */
var SCOPED_PAIRS = [
[" pr : belongsTo ", " pr : SubDomain ", " pr : DataDomain ", " belongs to "],
[" pr : belongsTo ", " pr : BusinessObject ", " pr : SubDomain ", " belongs to "],
[" pr : represents ", " pr : DataObject ", " pr : BusinessObject ", " represents "],
[" pr : represents ", " pr : DataElement ", " pr : BusinessConcept ", " represents "],
[" pr : monitoredBy ", " pr : BusinessObject ", " pr : DataSteward ", " monitored by "],
[" pr : owningDomain ", " pr : DefinedObject ", " pr : DataDomain ", " owning domain "],
[" pr : ownedBy ", " pr : DefinedObject ", " pr : Actor ", " owned by "]
];
var ORDER = [" pr : MetaModelObject ", " pr : DefinedObject ", " pr : CapturedObject ",
" pr : OwnershipLayerObject ", " pr : DataDomain ", " pr : SubDomain ", " pr : BusinessObject ",
" pr : BusinessLayerObject ", " pr : BusinessConcept ", " pr : Metric ", " pr : KPI ",
" pr : LogicalLayerObject ", " pr : DataObject ", " pr : DataElement ",
" pr : DeliveryLayerObject ", " pr : DataProduct ", " pr : DataContract ", " pr : DataInterface ",
" pr : PhysicalLayerObject ", " pr : System ", " pr : Database ", " pr : Schema ",
" pr : DataStructure ", " pr : BaseTable ", " pr : View ", " pr : ExternalTable ", " pr : Field ",
" pr : KeyConstraint ", " pr : PrimaryKey ", " pr : ForeignKeyConstraint ", " pr : Transformation ",
" pr : ConsumptionLayerObject ", " pr : BIWorkspace ", " pr : BIDataSource ", " pr : BIField ",
" pr : BIReport "];
var LANES = [[" defined "," Defined \u00b7 someone decided it "," # 1E4 B9B "],
[" captured "," Captured \u00b7 read from a running system "," # 8 A6A28 "],
2026-07-29 09:03:59 +02:00
[" structural "," Structural "," # 6 B6B6B "]];
function live(a){ return a.filter(function(x){ return !x.deprecated; }); }
function layerTitle(id){
var l = M.layers.filter(function(x){ return x.id === id; })[0];
return l ? l.title : id;
}
function buildGraph(){
var keep = {};
live(M.nodes).forEach(function(n){
if(!G.layers[n.layer]) return;
if(G.provs.length && G.provs.indexOf(n.prov) === -1) return;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
/* row = rank inside the layer, so the hierarchy reads DOWNWARD:
layer root, then Domain, then Sub-domain, then Business object.
Depth alone put siblings side by side, which read left to right. */
var rank = ORDER.indexOf(n.id);
2026-07-29 09:03:59 +02:00
keep[n.id] = {n:n, x:0, y:0,
layer: M.layers.map(function(l){return l.id;}).indexOf(n.layer),
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
row: rank > -1 ? rank : 90 + Math.min(n.depth, 5)};
2026-07-29 09:03:59 +02:00
});
var E = [];
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
/* Properties deliberately left without rdfs:domain/range serve several
classes with different targets, and their scope lives in the SHACL
shapes instead. The graph reads domain and range, so those edges were
simply absent -- Business Object to Sub-domain among them. The pairs are
declared here, mirroring the shapes, rather than added to the T-Box where
they would over-constrain the property. */
SCOPED_PAIRS.forEach(function(sc){
if(byId[sc[0]] && !byId[sc[0]].deprecated && keep[sc[1]] && keep[sc[2]]
&& sc[1] !== sc[2])
E.push({s:sc[1], t:sc[2], l:sc[3], cross:false});
});
var _unused = [
[" pr : belongsTo ", " pr : SubDomain ", " pr : DataDomain ", " belongs to "],
[" pr : belongsTo ", " pr : BusinessObject ", " pr : SubDomain ", " belongs to "],
[" pr : represents ", " pr : DataObject ", " pr : BusinessObject ", " represents "],
[" pr : represents ", " pr : DataElement ", " pr : BusinessConcept ", " represents "],
[" pr : monitoredBy ", " pr : BusinessObject ", " pr : DataSteward ", " monitored by "],
[" pr : owningDomain ", " pr : DefinedObject ", " pr : DataDomain ", " owning domain "],
[" pr : ownedBy ", " pr : DefinedObject ", " pr : Actor ", " owned by "]
];
2026-07-29 09:03:59 +02:00
live(M.relations).forEach(function(r){
if(keep[r.domain] && keep[r.range] && r.domain !== r.range)
E.push({s:r.domain, t:r.range, l:r.label, cross:r.seam});
2026-07-26 21:28:14 +02:00
});
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
2026-07-29 09:03:59 +02:00
live(M.nodes).forEach(function(n){
if(n.parent && keep[n.id] && keep[n.parent])
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
E.push({s:n.id, t:n.parent, l:" type of ", cross:false});
2026-07-29 09:03:59 +02:00
});
G.nodes = Object.keys(keep).map(function(k){ return keep[k]; });
G.edges = E;
return keep;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
/* Strata = a matrix. Columns are domains, rows are meta model layers.
Everything owned by one domain sits in one vertical lane, so a cross-domain
dependency is literally a line leaving its column. */
function layoutGraph(idx){
if(G.mode === " network "){ layoutForce(idx); return; }
var lanes = LANES.map(function(L){ return L[0]; })
.filter(function(c){ return G.nodes.some(function(n){ return n.n.prov === c; }); });
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
/* One class per row. A lane is one column wide, so a layer reads DOWNWARDS:
the layer class on top, then Domain, then Sub-domain, then Business
Object. That is how a hierarchy is read; laying them left to right made
the reader scan across for something that descends. */
2026-07-29 09:03:59 +02:00
var laneW = {}, x = GUT;
G._lanes = [];
lanes.forEach(function(code){
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
var w = NW + LANEPAD*2;
laneW[code] = {x:x, w:w};
2026-07-29 09:03:59 +02:00
var L = LANES.filter(function(z){ return z[0] === code; })[0];
G._lanes.push({code:code, x:x, w:w, label:L[1], colour:L[2]});
x += w;
2026-07-26 21:28:14 +02:00
});
2026-07-29 09:03:59 +02:00
G._width = x;
var y = 0;
M.layers.forEach(function(b, li){
b._y = y;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
var any = G.nodes.filter(function(n){ return n.layer === li; });
if(!any.length){ b._h = 0; return; }
var cursor = y + 36, rows = 0;
lanes.forEach(function(code){
var g = any.filter(function(n){ return n.n.prov === code; });
if(!g.length) return;
g.sort(function(p, q){
return ((ORDER.indexOf(p.n.id)+1)||99) - ((ORDER.indexOf(q.n.id)+1)||99)
|| p.n.label.localeCompare(q.n.label); });
g.forEach(function(n, i){
n.x = laneW[code].x + laneW[code].w/2;
n.y = cursor + i*(NH+ROWGAP) + NH/2;
2026-07-29 09:03:59 +02:00
});
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
rows = Math.max(rows, g.length);
2026-07-26 21:28:14 +02:00
});
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
b._h = rows*(NH+ROWGAP) + 48;
y += b._h + BANDGAP;
2026-07-29 09:03:59 +02:00
});
G._height = y;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
function layoutForce(idx){
var links = G.edges.filter(function(e){ return idx[e.s] && idx[e.t]; });
G.nodes.forEach(function(n, i){
var a = i*2.399963;
n.x = 600 + Math.sqrt(i)*46*Math.cos(a);
n.y = 400 + Math.sqrt(i)*46*Math.sin(a);
});
var iters = G.nodes.length > 90 ? 200 : 380;
for(var it=0; it<iters; it++){
var k = 1 - it/iters;
for(var i=0; i<G.nodes.length; i++){
var a = G.nodes[i];
for(var j=i+1; j<G.nodes.length; j++){
var b = G.nodes[j], dx = b.x-a.x, dy = (b.y-a.y)*1.7;
var d2 = dx*dx+dy*dy || 1, f = Math.min(30000/d2, 8), d = Math.sqrt(d2);
a.x -= dx/d*f; a.y -= dy/d*f*.6; b.x += dx/d*f; b.y += dy/d*f*.6;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
a.x += (600-a.x)*.003*k; a.y += (400-a.y)*.004*k;
2026-07-26 21:28:14 +02:00
}
links.forEach(function(e){
2026-07-29 09:03:59 +02:00
var a = idx[e.s], b = idx[e.t], dx = b.x-a.x, dy = b.y-a.y;
var d = Math.sqrt(dx*dx+dy*dy) || 1, f = (d-190)*.02*k;
a.x += dx/d*f; a.y += dy/d*f; b.x -= dx/d*f; b.y -= dy/d*f;
2026-07-26 21:28:14 +02:00
});
}
2026-07-29 09:03:59 +02:00
for(var pass=0; pass<40; pass++){
var moved = 0;
for(var i=0; i<G.nodes.length; i++){
for(var j=i+1; j<G.nodes.length; j++){
var a = G.nodes[i], b = G.nodes[j];
var ox = (NW+10)-Math.abs(b.x-a.x), oy = (NH+8)-Math.abs(b.y-a.y);
if(ox>0 && oy>0){
moved++;
if(ox<oy){ var s=(b.x>=a.x?1:-1)*ox/2; a.x-=s; b.x+=s; }
else { var t=(b.y>=a.y?1:-1)*oy/2; a.y-=t; b.y+=t; }
}
}
}
if(!moved) break;
2026-07-26 21:28:14 +02:00
}
}
2026-07-29 09:03:59 +02:00
function renderGraph(){
var idx = buildGraph();
G.nodes.forEach(function(n){ idx[n.n.id] = n; });
layoutGraph(idx);
var bL=document.getElementById(" g - b "), eL=document.getElementById(" g - e "),
lL=document.getElementById(" g - l "), nL=document.getElementById(" g - n ");
bL.innerHTML=eL.innerHTML=lL.innerHTML=nL.innerHTML=" ";
var NS=" http : //www.w3.org/2000/svg";
function el ( t , a ) { var e = document . createElementNS ( NS , t ) ;
for ( var k in a ) e . setAttribute ( k , a [ k ] ) ; return e ; }
if ( G . mode === "strata" && G . nodes . length ) {
/* domain lanes: alternating tint, so a column reads as one domain */
( G . _lanes || [ ] ) . forEach ( function ( l , i ) {
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
var isSubject = l . code === "defined" ;
2026-07-29 09:03:59 +02:00
bL . appendChild ( el ( "rect" , { x : l . x , y : - 6 , width : l . w , height : G . _height + 10 ,
fill : l . colour , opacity : isSubject ? . 05 : . 028 } ) ) ;
if ( i ) bL . appendChild ( el ( "line" , { x1 : l . x , y1 : - 6 , x2 : l . x , y2 : G . _height + 4 ,
stroke : "#D8D4C8" , "stroke-width" : 1 } ) ) ;
var t = el ( "text" , { x : l . x + l . w / 2 , y : - 16 , "text-anchor" : "middle" ,
class : "lanelabel" , fill : l . colour } ) ;
t . textContent = l . label ;
bL . appendChild ( t ) ;
} ) ;
M . layers . forEach ( function ( b ) {
if ( ! b . _h ) return ;
bL . appendChild ( el ( "line" , { x1 : 0 , y1 : b . _y , x2 : ( G . _width || 900 ) + 10 , y2 : b . _y ,
stroke : "#CFCABA" , "stroke-width" : 1 } ) ) ;
var t = el ( "text" , { x : GUT - 16 , y : b . _y + 18 , class : "bandlabel" , "text-anchor" : "end" ,
fill : "#0A1F44" } ) ; t . textContent = b . title ; bL . appendChild ( t ) ;
var g = el ( "text" , { x : GUT - 16 , y : b . _y + 31 , class : "bandtag" , "text-anchor" : "end" } ) ;
2026-07-26 21:28:14 +02:00
g . textContent = b . tag ; bL . appendChild ( g ) ;
} ) ;
}
2026-07-29 09:03:59 +02:00
/* clip both ends to the node rectangle: an arrow head drawn to the centre
disappears under the box, which is what hid every marker until now */
function clip ( from , to ) {
var dx = to . x - from . x , dy = to . y - from . y ;
if ( ! dx && ! dy ) return { x : to . x , y : to . y } ;
var sx = ( NW / 2 + 5 ) / Math . abs ( dx || 1e-6 ) , sy = ( NH / 2 + 5 ) / Math . abs ( dy || 1e-6 ) ;
var t = Math . min ( sx , sy , 1 ) ;
return { x : to . x - dx * t , y : to . y - dy * t } ;
}
G . edges . forEach ( function ( e ) {
var a = idx [ e . s ] , b = idx [ e . t ] ; if ( ! a || ! b ) return ;
var dx = b . x - a . x , dy = b . y - a . y ;
var cx = a . x + dx / 2 + ( - dy ) * . 09 , cy = a . y + dy / 2 + dx * . 035 ;
var p0 = clip ( b , a ) , p1 = clip ( a , b ) ;
var p = el ( "path" , { d : "M" + p0 . x + "," + p0 . y + " Q" + cx + "," + cy + " " + p1 . x + "," + p1 . y ,
class : "gedge" + ( e . cross ? " cross" : "" ) } ) ;
p . dataset . a = e . s ; p . dataset . b = e . t ; eL . appendChild ( p ) ;
if ( G . labels ) {
var mx = ( a . x + 2 * cx + b . x ) / 4 , my = ( a . y + 2 * cy + b . y ) / 4 ;
var w = e . l . length * 4.3 + 7 ;
var bg = el ( "rect" , { x : mx - w / 2 , y : my - 6 , width : w , height : 11 , rx : 2 , class : "elbg" } ) ;
bg . dataset . a = e . s ; bg . dataset . b = e . t ; lL . appendChild ( bg ) ;
var tx = el ( "text" , { x : mx , y : my + 2.4 , class : "elabel" } ) ;
tx . dataset . a = e . s ; tx . dataset . b = e . t ; tx . textContent = e . l ; lL . appendChild ( tx ) ;
2026-07-26 21:28:14 +02:00
}
} ) ;
2026-07-29 09:03:59 +02:00
G . nodes . forEach ( function ( n ) {
var c = provColour ( n . n . prov ) ;
var g = el ( "g" , { class : "gnode" , tabindex : "0" , role : "button" ,
transform : "translate(" + ( n . x - NW / 2 ) + "," + ( n . y - NH / 2 ) + ")" } ) ;
g . dataset . id = n . n . id ;
/* dashed outline = abstract: a class that exists to give its
specialisations a shape, never instantiated */
g . appendChild ( el ( "rect" , { width : NW , height : NH , rx : 3 , fill : "#FFFFFF" ,
stroke : c , "stroke-dasharray" : n . n . abstract ? "5 3" : "none" } ) ) ;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
var lines = wrapText ( n . n . shortLabel || n . n . label , 21 ) ;
2026-07-29 09:03:59 +02:00
lines . forEach ( function ( t , i ) {
var e = el ( "text" , { x : NW / 2 , y : NH / 2 + 3.5 + ( i - ( lines . length - 1 ) / 2 ) * 10 , fill : "#1A1A1A" } ) ;
e . textContent = t ; g . appendChild ( e ) ;
2026-07-26 21:28:14 +02:00
} ) ;
nL . appendChild ( g ) ;
} ) ;
applyHot ( ) ;
2026-07-29 09:03:59 +02:00
fitGraph ( ) ;
var cd = G . edges . filter ( function ( e ) { return e . cross ; } ) . length ;
document . getElementById ( "g-count" ) . textContent =
G . nodes . length + " classes \u00b7 " + G . edges . length + " edges \u00b7 " + cd + " seam" ;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
function wrapText ( s , max ) {
var w = s . split ( " " ) , out = [ ] , cur = "" ;
2026-07-26 21:28:14 +02:00
w . forEach ( function ( x ) {
2026-07-29 09:03:59 +02:00
if ( ( cur + " " + x ) . trim ( ) . length > max ) { if ( cur ) out . push ( cur ) ; cur = x ; }
else cur = ( cur + " " + x ) . trim ( ) ; } ) ;
if ( cur ) out . push ( cur ) ;
return out . slice ( 0 , 2 ) ;
2026-07-26 21:28:14 +02:00
}
function applyHot ( ) {
2026-07-29 09:03:59 +02:00
var id = G . sel || G . hot ;
var ns = document . querySelectorAll ( ".gnode" ) , es = document . querySelectorAll ( ".gedge" ) ,
ls = document . querySelectorAll ( ".elabel,.elbg" ) ;
2026-07-26 21:28:14 +02:00
if ( ! id ) {
2026-07-29 09:03:59 +02:00
ns . forEach ( function ( n ) { n . classList . remove ( "dim" , "sel" ) ; } ) ;
es . forEach ( function ( e ) { e . classList . remove ( "dim" , "hot" ) ; } ) ;
ls . forEach ( function ( l ) { l . classList . remove ( "dim" ) ; } ) ;
2026-07-26 21:28:14 +02:00
return ;
}
2026-07-29 09:03:59 +02:00
var keep = { } ; keep [ id ] = 1 ;
G . edges . forEach ( function ( e ) {
if ( e . s === id ) keep [ e . t ] = 1 ; if ( e . t === id ) keep [ e . s ] = 1 ; } ) ;
es . forEach ( function ( p ) {
var on = p . dataset . a === id || p . dataset . b === id ;
p . classList . toggle ( "hot" , on ) ; p . classList . toggle ( "dim" , ! on ) ; } ) ;
ls . forEach ( function ( l ) {
l . classList . toggle ( "dim" , ! ( l . dataset . a === id || l . dataset . b === id ) ) ; } ) ;
ns . forEach ( function ( n ) {
n . classList . toggle ( "dim" , ! keep [ n . dataset . id ] ) ;
n . classList . toggle ( "sel" , n . dataset . id === G . sel ) ; } ) ;
}
function clearSel ( ) { G . sel = null ; G . hot = null ; applyHot ( ) ; }
function applyView ( ) {
document . getElementById ( "g-vp" ) . setAttribute ( "transform" ,
"translate(" + G . view . x + "," + G . view . y + ") scale(" + G . view . s + ")" ) ;
}
function fitGraph ( ) {
if ( ! G . nodes . length ) return ;
var svg = document . getElementById ( "g-svg" ) , r = svg . getBoundingClientRect ( ) ;
var xs = G . nodes . map ( function ( n ) { return n . x ; } ) , ys = G . nodes . map ( function ( n ) { return n . y ; } ) ;
var pad = G . mode === "strata" ? GUT + 20 : NW ;
var mnx = Math . min . apply ( null , xs ) - pad , mxx = Math . max . apply ( null , xs ) + NW ;
var mny = Math . min . apply ( null , ys ) - ( G . mode === "strata" ? 66 : 60 ) , mxy = Math . max . apply ( null , ys ) + 50 ;
G . view . s = Math . min ( r . width / ( mxx - mnx ) , r . height / ( mxy - mny ) , 1.15 ) ;
G . view . x = ( r . width - ( mxx - mnx ) * G . view . s ) / 2 - mnx * G . view . s ;
G . view . y = ( r . height - ( mxy - mny ) * G . view . s ) / 2 - mny * G . view . s ;
applyView ( ) ;
}
function zoomG ( f ) {
var svg = document . getElementById ( "g-svg" ) , r = svg . getBoundingClientRect ( ) ;
var cx = r . width / 2 , cy = r . height / 2 ;
var ns = Math . max ( . 1 , Math . min ( G . view . s * f , 3 ) ) ;
G . view . x = cx - ( cx - G . view . x ) * ( ns / G . view . s ) ;
G . view . y = cy - ( cy - G . view . y ) * ( ns / G . view . s ) ;
G . view . s = ns ; applyView ( ) ;
}
/* ------------------------------------------------------------ OVERVIEW */
function kpi ( v , l , w ) { return '<div class="kpi' + ( w ? ' warn' : '' ) + '"><b>' + v + '</b><span>' + l + '</span></div>' ; }
function renderScope ( ) {
var cls = live ( M . nodes ) , rel = live ( M . relations ) , att = live ( M . attributes ) ;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
var cur = cls . filter ( function ( n ) { return n . prov === "defined" ; } ) ;
var obs = cls . filter ( function ( n ) { return n . prov === "captured" ; } ) ;
2026-07-29 09:03:59 +02:00
var str = cls . filter ( function ( n ) { return n . prov === "structural" ; } ) ;
var seam = rel . filter ( function ( r ) { return r . seam ; } ) ;
var dep = M . nodes . filter ( function ( n ) { return n . deprecated ; } )
. concat ( M . relations . concat ( M . attributes ) . filter ( function ( r ) { return r . deprecated ; } ) ) ;
var h = [ ] ;
h . push ( '<p class="lede">The layer says what an object <b>is</b>. The provenance says <b>how it got here</b>.</p>' ) ;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
h . push ( '<p class="sublede">Defined objects are written by governance and reviewed through a pull request. '
+ 'Captured objects are read from Snowflake, Tableau and Looker, rebuilt on every run and never edited '
2026-07-29 09:03:59 +02:00
+ 'by hand. Without that split the ownership rules would demand an owner, a status and a version on every '
+ 'one of some fifty thousand warehouse columns.</p>' ) ;
h . push ( '<div class="kpis">' + kpi ( cls . length , "Classes" ) + kpi ( rel . length , "Relations" )
+ kpi ( att . length , "Attributes" ) + kpi ( M . bridges . length , "Bridge properties" )
+ kpi ( seam . length , "Seam edges" , true ) + kpi ( dep . length , "Deprecated" , true ) + '</div>' ) ;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
h . push ( '<h2 class="sec">Defined against captured</h2><div class="bar">'
2026-07-29 09:03:59 +02:00
+ '<div style="width:' + ( cur . length / cls . length * 100 ) + '%;background:#1E4B9B"></div>'
+ '<div style="width:' + ( obs . length / cls . length * 100 ) + '%;background:#8A6A28"></div>'
+ '<div style="width:' + ( str . length / cls . length * 100 ) + '%;background:#B9B4A7"></div></div>' ) ;
h . push ( '<div class="legend">'
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
+ '<span><i style="background:#1E4B9B"></i>Defined \u2014 ' + cur . length + '</span>'
+ '<span><i style="background:#8A6A28"></i>Captured \u2014 ' + obs . length + '</span>'
2026-07-29 09:03:59 +02:00
+ '<span><i style="background:#B9B4A7"></i>Structural \u2014 ' + str . length + '</span></div>' ) ;
h . push ( '<div class="note" style="max-width:880px;margin-bottom:26px"><b>Only ' + seam . length
+ ' relations cross between the two.</b> ' + seam . map ( function ( r ) { return esc ( r . label ) ; } ) . join ( " and " )
+ ' \u2014 both proposed by the reconciliation job and confirmed by a steward. Everywhere else the '
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
+ 'graph is either pure governance or pure machine, which is what keeps the mapping effort bounded.</div>' ) ;
2026-07-29 09:03:59 +02:00
h . push ( '<h2 class="sec">The layers</h2><div class="grid">' ) ;
M . layers . forEach ( function ( l ) {
var g = cls . filter ( function ( n ) { return n . layer === l . id ; } ) ;
if ( ! g . length ) return ;
var c = provColour ( g [ 0 ] . prov ) ;
h . push ( '<div class="card" data-layer="' + l . id + '"><div class="top" style="background:' + c + '"></div>'
+ '<div class="body"><div class="role" style="color:' + c + '">' + esc ( l . tag ) + '</div>'
+ '<h3>' + esc ( l . title ) + '</h3><p>' + g . slice ( 0 , 5 ) . map ( function ( n ) { return esc ( n . label ) ; } ) . join ( " \u00b7 " )
+ ( g . length > 5 ? " \u00b7 \u2026" : "" ) + '</p><div class="nums"><div><b>' + g . length + '</b>classes</div>'
+ '<div><b>' + rel . filter ( function ( r ) { return byId [ r . domain ] && byId [ r . domain ] . layer === l . id ; } ) . length
+ '</b>relations from</div></div></div></div>' ) ;
2026-07-26 21:28:14 +02:00
} ) ;
2026-07-29 09:03:59 +02:00
h . push ( '</div>' ) ;
return h . join ( "" ) ;
}
/* ------------------------------------------------------------ LAYERS */
function pill ( t , c ) { c = c || "#8A6A28" ;
return '<span class="pill" style="color:' + c + ';border-color:' + c + ';background:#FBF3E4">' + t + '</span>' ; }
function renderModel ( ) {
var h = [ '<p class="lede">Every class, in the layer it belongs to.</p>'
+ '<p class="sublede">Abstract classes are never instantiated \u2014 they exist to give their '
+ 'specialisations a common parent and a common shape. Superseded terms are kept rather than deleted '
+ 'so assertions written against them keep classifying; they live on the Deprecated tab.</p>' ] ;
M . layers . forEach ( function ( l ) {
var g = live ( M . nodes ) . filter ( function ( n ) { return n . layer === l . id ; } ) ;
if ( ! g . length ) return ;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
g . sort ( function ( a , b ) {
return a . depth - b . depth
|| ( ( ORDER . indexOf ( a . id ) + 1 ) || 99 ) - ( ( ORDER . indexOf ( b . id ) + 1 ) || 99 )
|| a . label . localeCompare ( b . label ) ; } ) ;
2026-07-29 09:03:59 +02:00
h . push ( '<h2 class="sec" style="color:' + provColour ( g [ 0 ] . prov ) + '">' + esc ( l . title ) + ' \u00b7 ' + esc ( l . tag ) + '</h2>' ) ;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
h . push ( '<table><thead><tr><th>Class</th><th>Provenance</th><th class="hide-s">Type of</th>'
2026-07-29 09:03:59 +02:00
+ '<th class="hide-s">What it is for</th></tr></thead><tbody>' ) ;
g . forEach ( function ( n ) {
h . push ( '<tr data-id="' + n . id + '"><td><span class="dot" style="background:' + provColour ( n . prov )
+ '"></span><b>' + esc ( n . label ) + '</b>' + ( n . abstract ? ' ' + pill ( "abstract" ) : '' ) + '</td>'
+ '<td class="mono">' + n . prov + '</td><td class="hide-s mono">'
+ ( byId [ n . parent ] ? esc ( clean ( byId [ n . parent ] . label ) ) : "\u2014" ) + '</td>'
+ '<td class="hide-s">' + esc ( ( n . comment || "" ) . split ( ". " ) [ 0 ] ) + '</td></tr>' ) ;
} ) ;
h . push ( '</tbody></table>' ) ;
2026-07-26 21:28:14 +02:00
} ) ;
2026-07-29 09:03:59 +02:00
return h . join ( "" ) ;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
/* ------------------------------------------------------------ RELATIONS */
function renderProps ( ) {
var h = [ '<p class="lede">' + live ( M . relations ) . length + ' relations and ' + live ( M . attributes ) . length + ' attributes.</p>'
+ '<p class="sublede">A property with no declared domain or range is deliberate: it serves several classes '
+ 'with different targets, so the scope lives in the SHACL shapes instead, stated per class.</p>' ] ;
h . push ( '<div class="filters"><input id="q" type="search" placeholder="filter\u2026">'
+ '<button data-k="" class="on">All</button><button data-k="Relation">Relations</button>'
+ '<button data-k="Attribute">Attributes</button><button data-k="Bridge">Bridges</button>'
+ '</div><div id="tbl"></div>' ) ;
return h . join ( "" ) ;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
function propRows ( ) {
var all = M . relations . concat ( M . attributes , M . bridges ) . filter ( function ( p ) {
if ( p . deprecated ) return false ;
if ( filt . kind && p . kind !== filt . kind ) return false ;
if ( filt . q && ( p . label + " " + p . id + " " + ( p . comment || "" ) ) . toLowerCase ( ) . indexOf ( filt . q ) === - 1 ) return false ;
return true ;
} ) ;
all . sort ( function ( a , b ) { return a . kind . localeCompare ( b . kind ) || a . label . localeCompare ( b . label ) ; } ) ;
var h = [ '<table><thead><tr><th>Property</th><th class="hide-s">From</th><th class="hide-s">To</th>'
+ '<th>Traits</th></tr></thead><tbody>' ] ;
all . forEach ( function ( p ) {
var t = [ ] ;
if ( p . functional ) t . push ( "exactly one" ) ;
if ( p . transitive ) t . push ( "transitive" ) ;
if ( p . seam ) t . push ( "seam" ) ;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
if ( p . authoring ) t . push ( p . authoring . toLowerCase ( ) . replace ( /_/g , " " ) ) ;
2026-07-29 09:03:59 +02:00
h . push ( '<tr data-id="' + p . id + '"><td><b>' + esc ( p . label ) + '</b><div class="mono">' + esc ( p . id ) + '</div></td>'
+ '<td class="hide-s">' + ( byId [ p . domain ] ? esc ( clean ( byId [ p . domain ] . label ) ) : '<span class="mono">any</span>' ) + '</td>'
+ '<td class="hide-s">' + ( byId [ p . range ] ? esc ( clean ( byId [ p . range ] . label ) )
: '<span class="mono">' + esc ( p . range || "\u2014" ) + '</span>' ) + '</td>'
+ '<td class="mono">' + ( t . join ( " \u00b7 " ) || "\u2014" ) + '</td></tr>' ) ;
} ) ;
h . push ( '</tbody></table><p class="sublede" style="margin-top:12px">' + all . length + ' shown.</p>' ) ;
return h . join ( "" ) ;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
/* ------------------------------------------------------------ DEPRECATED */
function renderDep ( ) {
var dep = M . nodes . filter ( function ( n ) { return n . deprecated ; } )
. concat ( M . relations . concat ( M . attributes ) . filter ( function ( r ) { return r . deprecated ; } ) ) ;
var h = [ '<p class="lede">' + dep . length + ' terms kept, none of them to be used.</p>'
+ '<p class="sublede">Rule LC-002: a superseded term is deprecated rather than deleted, so assertions '
+ 'written against it keep classifying while a migration runs. The obligation is symmetric \u2014 a '
+ 'deprecation that never ends stops meaning anything, which is why Table was removed outright in v1.2 '
+ 'once nothing used it.</p>' ] ;
h . push ( '<table><thead><tr><th>Term</th><th>Kind</th><th>Replaced by</th>'
+ '<th class="hide-s">Why</th></tr></thead><tbody>' ) ;
dep . forEach ( function ( d ) {
h . push ( '<tr class="dep" data-id="' + d . id + '"><td><b>' + esc ( clean ( d . label ) ) + '</b>'
+ '<div class="mono">' + esc ( d . id ) + '</div></td><td class="mono">'
+ ( d . kind === "Class" ? "class" : d . kind . toLowerCase ( ) ) + '</td><td>'
+ ( byId [ d . replacedBy ] ? esc ( clean ( byId [ d . replacedBy ] . label ) ) : '<span class="mono">\u2014</span>' )
+ '</td><td class="hide-s">' + esc ( ( d . comment || "" ) . split ( ". " ) [ 0 ] ) + '</td></tr>' ) ;
} ) ;
h . push ( '</tbody></table>' ) ;
return h . join ( "" ) ;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
/* ------------------------------------------------------------ SHELL */
function renderGraphShell ( ) {
var h = [ '<p class="lede">The meta model as a graph.</p>'
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
+ '<p class="sublede">Columns are provenance: <b style="color:#1E4B9B">defined</b> on the left, '
+ '<b style="color:#8A6A28">captured</b> on the right. Rows are layers. A <b>navy edge</b> crosses '
2026-07-29 09:03:59 +02:00
+ 'between the two \u2014 that is the seam, the only place a steward confirms anything. Dashed outlines '
+ 'are abstract classes, never instantiated.</p>' ] ;
h . push ( '<div class="gwrap"><svg id="g-svg" xmlns="http://www.w3.org/2000/svg"><defs>'
+ '<marker id="ga" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" orient="auto">'
+ '<path d="M0,0 L10,5 L0,10 z" fill="#A6ABB5"/></marker>'
+ '<marker id="gg" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" orient="auto">'
+ '<path d="M0,0 L10,5 L0,10 z" fill="#0A1F44"/></marker>'
+ '<marker id="gn" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" orient="auto">'
+ '<path d="M0,0 L10,5 L0,10 z" fill="#B8935A"/></marker></defs>'
+ '<g id="g-vp"><g id="g-b"></g><g id="g-e"></g><g id="g-l"></g><g id="g-n"></g></g></svg>' ) ;
h . push ( '<div class="gctl"><div class="seg"><button id="g-strata" class="on">Strata</button>'
+ '<button id="g-network">Network</button></div><div class="gsec">Layers</div>'
+ M . layers . map ( function ( l ) {
var n = live ( M . nodes ) . filter ( function ( x ) { return x . layer === l . id ; } ) . length ;
if ( ! n ) return "" ;
return '<label><input type="checkbox" data-ly="' + l . id + '"' + ( G . layers [ l . id ] ? " checked" : "" )
+ '> ' + esc ( l . title ) + ' <span class="mono">' + n + '</span></label>' ; } ) . join ( "" )
+ '<div class="gsec">Provenance</div>'
+ LANES . map ( function ( L ) {
var n = live ( M . nodes ) . filter ( function ( x ) { return x . prov === L [ 0 ] ; } ) . length ;
if ( ! n ) return "" ;
return '<label><input type="checkbox" data-pv="' + L [ 0 ] + '"'
+ ( ! G . provs . length || G . provs . indexOf ( L [ 0 ] ) > - 1 ? " checked" : "" ) + '> <i style="background:' + L [ 2 ]
+ '"></i>' + L [ 0 ] + ' <span class="mono">' + n + '</span></label>' ; } ) . join ( "" )
+ '<div class="gsec">Options</div>'
+ '<label><input type="checkbox" id="g-lab" checked> Relation names</label>'
+ '<div class="mono" id="g-count" style="margin-top:8px"></div></div>' ) ;
h . push ( '<div class="gzoom"><button id="g-in">+</button><button id="g-out">\u2212</button>'
+ '<button id="g-fit" style="font-size:12px">\u21ba</button></div>' ) ;
h . push ( '<div class="ghint">drag to pan \u00b7 scroll to zoom \u00b7 click empty space to clear</div></div>' ) ;
return h . join ( "" ) ;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
function wireGraph ( ) {
renderGraph ( ) ;
var svg = document . getElementById ( "g-svg" ) ;
function mode ( m ) {
G . mode = m ;
document . getElementById ( "g-strata" ) . classList . toggle ( "on" , m === "strata" ) ;
document . getElementById ( "g-network" ) . classList . toggle ( "on" , m === "network" ) ;
renderGraph ( ) ;
}
document . getElementById ( "g-strata" ) . onclick = function ( ) { mode ( "strata" ) ; } ;
document . getElementById ( "g-network" ) . onclick = function ( ) { mode ( "network" ) ; } ;
document . querySelectorAll ( "[data-ly]" ) . forEach ( function ( cb ) {
cb . onchange = function ( ) { G . layers [ cb . dataset . ly ] = cb . checked ; renderGraph ( ) ; } ;
} ) ;
document . getElementById ( "g-lab" ) . onchange = function ( ) {
G . labels = this . checked ; renderGraph ( ) ; } ;
document . querySelectorAll ( "[data-pv]" ) . forEach ( function ( cb ) {
cb . onchange = function ( ) {
var on = [ ] ;
document . querySelectorAll ( "[data-pv]" ) . forEach ( function ( x ) {
if ( x . checked ) on . push ( x . dataset . pv ) ; } ) ;
G . provs = ( on . length === LANES . length ) ? [ ] : on ;
renderGraph ( ) ;
} ;
} ) ;
document . getElementById ( "g-in" ) . onclick = function ( ) { zoomG ( 1.25 ) ; } ;
document . getElementById ( "g-out" ) . onclick = function ( ) { zoomG ( . 8 ) ; } ;
document . getElementById ( "g-fit" ) . onclick = fitGraph ;
var drag = false , moved = false , ds , vs , pinch = null ;
svg . addEventListener ( "mousedown" , function ( e ) {
drag = true ; moved = false ; svg . classList . add ( "drag" ) ;
ds = { x : e . clientX , y : e . clientY } ; vs = { x : G . view . x , y : G . view . y } ; } ) ;
window . addEventListener ( "mousemove" , function ( e ) {
if ( ! drag ) return ;
if ( Math . abs ( e . clientX - ds . x ) + Math . abs ( e . clientY - ds . y ) > 4 ) moved = true ;
G . view . x = vs . x + ( e . clientX - ds . x ) ; G . view . y = vs . y + ( e . clientY - ds . y ) ; applyView ( ) ; } ) ;
window . addEventListener ( "mouseup" , function ( ) {
drag = false ; svg . classList . remove ( "drag" ) ; } ) ;
/* click on empty canvas clears the selection -- a highlight that cannot be
dismissed traps the reader in one node */
svg . addEventListener ( "click" , function ( e ) {
if ( ! moved && ! e . target . closest ( ".gnode" ) ) { clearSel ( ) ; insp . classList . remove ( "open" ) ; }
} ) ;
svg . addEventListener ( "wheel" , function ( e ) { e . preventDefault ( ) ;
var f = e . deltaY < 0 ? 1.1 : . 9 , r = svg . getBoundingClientRect ( ) ;
var mx = e . clientX - r . left , my = e . clientY - r . top ;
var ns = Math . max ( . 1 , Math . min ( G . view . s * f , 3 ) ) ;
G . view . x = mx - ( mx - G . view . x ) * ( ns / G . view . s ) ; G . view . y = my - ( my - G . view . y ) * ( ns / G . view . s ) ;
G . view . s = ns ; applyView ( ) ; } , { passive : false } ) ;
svg . addEventListener ( "touchstart" , function ( e ) {
if ( e . touches . length === 1 ) { drag = true ; moved = false ;
ds = { x : e . touches [ 0 ] . clientX , y : e . touches [ 0 ] . clientY } ; vs = { x : G . view . x , y : G . view . y } ; }
else if ( e . touches . length === 2 ) { drag = false ;
var dx = e . touches [ 0 ] . clientX - e . touches [ 1 ] . clientX ,
dy = e . touches [ 0 ] . clientY - e . touches [ 1 ] . clientY ;
pinch = { d : Math . sqrt ( dx * dx + dy * dy ) , s : G . view . s } ; } } , { passive : true } ) ;
svg . addEventListener ( "touchmove" , function ( e ) {
if ( e . touches . length === 1 && drag ) { moved = true ;
G . view . x = vs . x + ( e . touches [ 0 ] . clientX - ds . x ) ;
G . view . y = vs . y + ( e . touches [ 0 ] . clientY - ds . y ) ; applyView ( ) ; e . preventDefault ( ) ; }
else if ( e . touches . length === 2 && pinch ) {
var dx = e . touches [ 0 ] . clientX - e . touches [ 1 ] . clientX ,
dy = e . touches [ 0 ] . clientY - e . touches [ 1 ] . clientY ;
var nd = Math . sqrt ( dx * dx + dy * dy ) , ns = Math . max ( . 1 , Math . min ( pinch . s * ( nd / pinch . d ) , 3 ) ) ;
var r = svg . getBoundingClientRect ( ) ;
G . view . x = r . width / 2 - ( r . width / 2 - G . view . x ) * ( ns / G . view . s ) ;
G . view . y = r . height / 2 - ( r . height / 2 - G . view . y ) * ( ns / G . view . s ) ;
G . view . s = ns ; applyView ( ) ; e . preventDefault ( ) ; } } , { passive : false } ) ;
svg . addEventListener ( "touchend" , function ( ) { drag = false ; pinch = null ; } ) ;
var nL = document . getElementById ( "g-n" ) ;
nL . addEventListener ( "click" , function ( e ) {
var g = e . target . closest ( ".gnode" ) ; if ( ! g ) return ;
e . stopPropagation ( ) ;
if ( G . sel === g . dataset . id ) { clearSel ( ) ; insp . classList . remove ( "open" ) ; return ; }
G . sel = g . dataset . id ; G . hot = null ; applyHot ( ) ; openCard ( g . dataset . id , true ) ;
} ) ;
nL . addEventListener ( "keydown" , function ( e ) {
var g = e . target . closest ( ".gnode" ) ;
if ( g && ( e . key === "Enter" || e . key === " " ) ) { e . preventDefault ( ) ;
G . sel = g . dataset . id ; applyHot ( ) ; openCard ( g . dataset . id , true ) ; } } ) ;
nL . addEventListener ( "mouseover" , function ( e ) {
var g = e . target . closest ( ".gnode" ) ; if ( g && ! G . sel ) { G . hot = g . dataset . id ; applyHot ( ) ; } } ) ;
nL . addEventListener ( "mouseout" , function ( e ) {
if ( ! G . sel && ( ! e . relatedTarget || ! e . relatedTarget . closest
|| ! e . relatedTarget . closest ( ".gnode" ) ) ) { G . hot = null ; applyHot ( ) ; } } ) ;
window . addEventListener ( "resize" , fitGraph ) ;
}
/* ------------------------------------------------------------ INSPECTOR */
var insp = document . getElementById ( "insp" ) , ibody = document . getElementById ( "insp-body" ) ;
var HIST = [ ] ;
var CHILDREN = { } , FROM = { } , TO = { } , ATTRS = { } ;
M . nodes . forEach ( function ( n ) {
if ( n . parent ) ( CHILDREN [ n . parent ] = CHILDREN [ n . parent ] || [ ] ) . push ( n . id ) ; } ) ;
M . relations . forEach ( function ( r ) {
if ( r . deprecated ) return ;
if ( r . domain ) ( FROM [ r . domain ] = FROM [ r . domain ] || [ ] ) . push ( r . id ) ;
if ( r . range ) ( TO [ r . range ] = TO [ r . range ] || [ ] ) . push ( r . id ) ; } ) ;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
/* The same scoped pairs the graph uses. Without them a Business Object shows
no incoming relation at all, because belongsTo and represents carry no
rdfs:domain -- their scope lives in the shapes. The card was silent about
edges the graph was drawing. */
SCOPED _PAIRS . forEach ( function ( sc ) {
if ( ! byId [ sc [ 0 ] ] || byId [ sc [ 0 ] ] . deprecated ) return ;
( FROM [ sc [ 1 ] ] = FROM [ sc [ 1 ] ] || [ ] ) . push ( sc [ 0 ] ) ;
( TO [ sc [ 2 ] ] = TO [ sc [ 2 ] ] || [ ] ) . push ( sc [ 0 ] ) ;
} ) ;
/* the scoped properties again: without this a Sub-domain card showed no
incoming Business Object, and a Business Object card had no incoming list
at all, because neither relation declares a domain or a range */
[ [ "pr:belongsTo" , "pr:SubDomain" , "pr:DataDomain" ] ,
[ "pr:belongsTo" , "pr:BusinessObject" , "pr:SubDomain" ] ,
[ "pr:represents" , "pr:DataObject" , "pr:BusinessObject" ] ,
[ "pr:represents" , "pr:DataElement" , "pr:BusinessConcept" ] ,
[ "pr:monitoredBy" , "pr:BusinessObject" , "pr:DataSteward" ] ,
[ "pr:owningDomain" , "pr:DefinedObject" , "pr:DataDomain" ] ,
[ "pr:ownedBy" , "pr:DefinedObject" , "pr:Actor" ] ] . forEach ( function ( sc ) {
if ( ! byId [ sc [ 0 ] ] || byId [ sc [ 0 ] ] . deprecated ) return ;
if ( ( FROM [ sc [ 1 ] ] || [ ] ) . indexOf ( sc [ 0 ] ) === - 1 ) ( FROM [ sc [ 1 ] ] = FROM [ sc [ 1 ] ] || [ ] ) . push ( sc [ 0 ] ) ;
if ( ( TO [ sc [ 2 ] ] || [ ] ) . indexOf ( sc [ 0 ] ) === - 1 ) ( TO [ sc [ 2 ] ] = TO [ sc [ 2 ] ] || [ ] ) . push ( sc [ 0 ] ) ;
} ) ;
2026-07-29 09:03:59 +02:00
M . attributes . forEach ( function ( a ) {
if ( a . deprecated || ! a . domain ) return ;
( ATTRS [ a . domain ] = ATTRS [ a . domain ] || [ ] ) . push ( a . id ) ; } ) ;
function openCard ( id , reset ) {
if ( reset ) HIST = [ ] ;
var cur = HIST [ HIST . length - 1 ] ;
if ( cur && cur !== id ) HIST . push ( id ) ; else if ( ! cur ) HIST . push ( id ) ;
paint ( id ) ;
}
function back ( ) {
HIST . pop ( ) ;
var prev = HIST [ HIST . length - 1 ] ;
if ( prev ) { paint ( prev ) ; if ( G . sel ) { G . sel = prev ; applyHot ( ) ; } }
else insp . classList . remove ( "open" ) ;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
function open ( id ) { openCard ( id , true ) ; }
function paint ( id ) {
var n = byId [ id ] ; if ( ! n ) return ;
var h = [ ] ;
if ( HIST . length > 1 )
h . push ( '<button class="backlink" id="i-back">\u2190 back to '
+ esc ( clean ( byId [ HIST [ HIST . length - 2 ] ] . label ) ) + '</button>' ) ;
var kindLabel = n . kind === "Class" ? "Class" : n . kind === "Relation" ? "Relation"
: n . kind === "Attribute" ? "Attribute" : "Bridge property" ;
h . push ( '<div class="kind" style="color:' + provColour ( n . prov || "structural" ) + '">'
+ kindLabel + ( n . layer ? ' \u00b7 ' + esc ( layerTitle ( n . layer ) ) : '' ) + '</div>' ) ;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
h . push ( '<h3>' + esc ( n . shortLabel || clean ( n . label ) ) + '</h3>' ) ;
if ( n . shortLabel && n . shortLabel !== clean ( n . label ) )
h . push ( '<div class="row" style="border:0;padding:0 0 6px"><span class="mono">'
+ esc ( clean ( n . label ) ) + '</span><span></span></div>' ) ;
2026-07-29 09:03:59 +02:00
h . push ( '<div class="iri">' + esc ( n . id ) + '</div>' ) ;
if ( n . deprecated ) h . push ( '<div class="note"><b>Deprecated.</b> Kept so assertions written '
+ 'against it keep classifying (LC-002)'
+ ( byId [ n . replacedBy ] ? ', replaced by <b>' + esc ( clean ( byId [ n . replacedBy ] . label ) ) + '</b>' : '' )
+ '. Not to be used in new work.</div>' ) ;
/* 1. attributes, always first and always in the same place */
var rows = [ ] ;
function at ( l , v ) { if ( v ) rows . push ( [ l , v ] ) ; }
if ( n . kind === "Class" ) {
at ( "Provenance" , n . prov ) ;
at ( "Instantiated" , n . abstract ? "never \u2014 abstract" : "yes" ) ;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
at ( "Authoring mode" , ( n . authoring || "" ) . toLowerCase ( ) . replace ( /_/g , " " ) ) ;
2026-07-29 09:03:59 +02:00
at ( "Acronym" , n . acronym ) ;
} else {
at ( "Cardinality" , n . functional ? "exactly one" : "many" ) ;
at ( "Transitive" , n . transitive ? "yes" : "" ) ;
at ( "Crosses the seam" , n . seam ? "yes" : "" ) ;
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
at ( "Authoring mode" , ( n . authoring || "" ) . toLowerCase ( ) . replace ( /_/g , " " ) ) ;
2026-07-29 09:03:59 +02:00
if ( ! byId [ n . range ] ) at ( "Range" , n . range ) ;
if ( ! n . domain ) at ( "Domain" , "any \u2014 scoped by SHACL per class" ) ;
}
if ( rows . length ) h . push ( '<h4>Attributes</h4>' + rows . map ( function ( r ) {
return '<div class="row"><span>' + r [ 0 ] + '</span><span class="mono">' + esc ( r [ 1 ] ) + '</span></div>' ;
} ) . join ( "" ) ) ;
/* 2. what it is for */
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
h . push ( '<h4>What it is for</h4><p>' + ( n . comment
? esc ( n . comment ) . replace ( /\n/g , "<br>" )
: '<em style="color:var(--muted)">No definition yet \u2014 this term needs one '
+ 'before it can be published.</em>' ) + '</p>' ) ;
2026-07-29 09:03:59 +02:00
if ( n . harvest ) h . push ( '<h4>Harvested from</h4><div class="harv">' + esc ( n . harvest ) + '</div>' ) ;
/* 3. related terms, from the coarsest down */
function link ( label , ids ) {
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
/* deprecated terms live on their own tab; showing them in a link list
invites someone to use one */
ids = ( ids || [ ] ) . filter ( function ( i ) {
return byId [ i ] && ( ! byId [ i ] . deprecated || label === "Replaced by" ) ; } ) ;
2026-07-29 09:03:59 +02:00
if ( ! ids . length ) return ;
h . push ( '<h4>' + label + '</h4>' ) ;
ids . slice ( 0 , 16 ) . forEach ( function ( i ) {
h . push ( '<div class="row"><button class="jump" data-j="' + i + '">' + esc ( clean ( byId [ i ] . label ) )
+ '</button><span class="mono">' + ( byId [ i ] . deprecated ? "deprecated"
: byId [ i ] . kind === "Class" ? ( byId [ i ] . prov || "" ) : byId [ i ] . kind . toLowerCase ( ) ) + '</span></div>' ) ;
} ) ;
if ( ids . length > 16 ) h . push ( '<div class="row"><span class="mono">\u2026 and '
+ ( ids . length - 16 ) + ' more</span><span></span></div>' ) ;
}
tbox: v1.5 - PhysicalRelation devient DataStructure et point d'extension declare pour le non-tabulaire, hasSource devient sourcedFrom vers un System instancie, storedIn sert deux granularites et deprecie materializedAs, references devient qualifiedBy, primaryLocation sous-propriete de storedIn, hasShortLabel sur 34 classes. CuratedObject devient DefinedObject et ObservedObject devient CapturedObject jusque dans les IRI, curationMode devient authoringMode. Correction de fond : PhysicalLayerObject ne porte plus la provenance, chaque classe de la couche la declare - un System est defined, tout ce qui est en dessous est captured. instances: 5 systemes crees, 151 sourcedFrom, 15 BO nettoyes de hasSource, labels courts, versions alignees
2026-07-30 15:13:03 +02:00
link ( "Type of" , [ n . parent , n . subPropertyOf ] ) ;
link ( "Types" , CHILDREN [ n . id ] ) ;
2026-07-29 09:03:59 +02:00
link ( "Replaced by" , [ n . replacedBy ] ) ;
link ( "From" , [ n . domain ] ) ;
link ( "To" , [ n . range ] ) ;
link ( "Inverse of" , [ n . inverseOf ] ) ;
link ( "Relations from here" , ( FROM [ n . id ] || [ ] ) ) ;
link ( "Relations to here" , ( TO [ n . id ] || [ ] ) ) ;
link ( "Attributes carried" , ATTRS [ n . id ] ) ;
if ( n . members && n . members . length )
h . push ( '<h4>Allowed values</h4>' + n . members . map ( function ( m ) {
return '<div class="row"><span>' + esc ( m . label ) + '</span><span class="mono">'
+ esc ( m . id ) + '</span></div>' ; } ) . join ( "" ) ) ;
ibody . innerHTML = h . join ( "" ) ;
insp . classList . add ( "open" ) ;
var bk = document . getElementById ( "i-back" ) ; if ( bk ) bk . onclick = back ;
ibody . querySelectorAll ( ".jump" ) . forEach ( function ( b ) {
b . onclick = function ( ) { HIST . push ( b . dataset . j ) ; paint ( b . dataset . j ) ;
if ( G . sel ) { G . sel = b . dataset . j ; applyHot ( ) ; } } ; } ) ;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
document . getElementById ( "insp-x" ) . onclick = function ( ) {
insp . classList . remove ( "open" ) ; clearSel ( ) ; } ;
document . addEventListener ( "keydown" , function ( e ) {
if ( e . key === "Escape" ) { insp . classList . remove ( "open" ) ; clearSel ( ) ; } } ) ;
/* ------------------------------------------------------------ WIRING */
var host = document . getElementById ( "view" ) ;
function render ( ) {
host . innerHTML = view === "scope" ? renderScope ( )
: view === "model" ? renderModel ( )
: view === "graph" ? renderGraphShell ( )
: view === "props" ? renderProps ( ) : renderDep ( ) ;
if ( view === "graph" ) wireGraph ( ) ;
if ( view === "props" ) {
document . getElementById ( "tbl" ) . innerHTML = propRows ( ) ;
document . getElementById ( "q" ) . oninput = function ( ) {
filt . q = this . value . trim ( ) . toLowerCase ( ) ;
document . getElementById ( "tbl" ) . innerHTML = propRows ( ) ; bindRows ( ) ; } ;
host . querySelectorAll ( "[data-k]" ) . forEach ( function ( b ) {
b . onclick = function ( ) {
filt . kind = b . dataset . k ;
host . querySelectorAll ( "[data-k]" ) . forEach ( function ( x ) {
x . classList . toggle ( "on" , x === b ) ; } ) ;
document . getElementById ( "tbl" ) . innerHTML = propRows ( ) ; bindRows ( ) ; } ; } ) ;
}
bindRows ( ) ;
host . querySelectorAll ( ".card" ) . forEach ( function ( c ) {
c . onclick = function ( ) {
M . layers . forEach ( function ( l ) { G . layers [ l . id ] = ( l . id === c . dataset . layer ) ; } ) ;
setView ( "graph" ) ; } ; } ) ;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
function bindRows ( ) {
host . querySelectorAll ( "tr[data-id]" ) . forEach ( function ( tr ) {
tr . onclick = function ( ) { open ( tr . dataset . id ) ; } ; } ) ;
2026-07-26 21:28:14 +02:00
}
2026-07-29 09:03:59 +02:00
function setView ( v ) {
view = v ;
[ "scope" , "model" , "graph" , "props" , "dep" ] . forEach ( function ( k ) {
document . getElementById ( "t-" + k ) . classList . toggle ( "on" , k === v ) ; } ) ;
render ( ) ;
document . querySelector ( "main" ) . scrollTop = 0 ;
}
[ "scope" , "model" , "graph" , "props" , "dep" ] . forEach ( function ( k ) {
document . getElementById ( "t-" + k ) . onclick = function ( ) {
if ( k === "graph" ) M . layers . forEach ( function ( l ) { G . layers [ l . id ] = ( l . id !== "vocab" ) ; } ) ;
setView ( k ) ; } ; } ) ;
render ( ) ;
2026-07-26 21:28:14 +02:00
< / script >
< / body >
< / html >