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" >
< title > PR Data Meta Model · T-Box v1.0< / title >
< style >
: root {
--navy : #0A1F44 ; --gold : #B8935A ; --ink : #1A1A1A ;
--bg : #F7F6F2 ; --panel : #FFFFFF ; --rule : #E4E2DA ; --muted : #6B6B6B ;
--c-roots : #0A1F44 ; --c-ownership : #B8935A ; --c-business : #8B3A3A ;
--c-logical : #7A4E8B ; --c-physical : #1E4B9B ; --c-consumption : #1E7A5F ;
--c-context : #6B6B6B ;
--mono : ui-monospace , "SF Mono" , SFMono-Regular , Menlo , Consolas , "Liberation Mono" , monospace ;
--sans : - apple-system , BlinkMacSystemFont , "Segoe UI" , Helvetica , Arial , sans-serif ;
}
* { box-sizing : border-box ; -webkit- tap-highlight-color : transparent }
html , body { height : 100 % ; margin : 0 }
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 }
/* ---------- chrome ---------- */
header { background : var ( - - navy ) ; color : #fff ; padding : 10 px 18 px ; display : flex ;
align-items : center ; gap : 16 px ; flex-shrink : 0 ; box-shadow : 0 2 px 6 px rgba ( 0 , 0 , 0 , .15 ) }
header h1 { margin : 0 ; font-family : var ( - - mono ) ; font-size : 13 px ; font-weight : 600 ;
letter-spacing : .16 em ; text-transform : uppercase }
header . sub { font-size : 11 px ; color : rgba ( 255 , 255 , 255 , .62 ) ; margin-top : 3 px }
. stats { margin-left : auto ; display : flex ; gap : 20 px }
. stat { text-align : right ; line-height : 1.15 }
. stat b { display : block ; font-family : var ( - - mono ) ; font-size : 16 px ; color : var ( - - gold ) }
. stat span { font-size : 9 px ; text-transform : uppercase ; letter-spacing : .09 em ;
color : rgba ( 255 , 255 , 255 , .55 ) }
# mtoggle { display : none ; background : transparent ; color : #fff ;
border : 1 px solid rgba ( 255 , 255 , 255 , .4 ) ; padding : 5 px 10 px ; border-radius : 3 px ;
font-size : 11 px ; font-weight : 600 ; cursor : pointer ; font-family : inherit }
main { flex : 1 ; display : flex ; min-height : 0 ; position : relative }
/* ---------- sidebar ---------- */
aside { width : 255 px ; background : var ( - - panel ) ; border-right : 1 px solid var ( - - rule ) ;
overflow-y : auto ; padding : 16 px ; flex-shrink : 0 }
aside h2 { font-family : var ( - - mono ) ; font-size : 9.5 px ; text-transform : uppercase ;
letter-spacing : .14 em ; color : var ( - - muted ) ; margin : 22 px 0 9 px ; font-weight : 600 }
aside h2 : first-child { margin-top : 0 }
# q { width : 100 % ; padding : 8 px 10 px ; border : 1 px solid var ( - - rule ) ; border-radius : 3 px ;
font-family : var ( - - mono ) ; font-size : 12 px ; background : var ( - - bg ) }
# q : focus { border-color : var ( - - navy ) ; outline : none ; background : #fff }
. chip { display : flex ; align-items : center ; gap : 9 px ; padding : 5 px 0 ; font-size : 12.5 px ;
cursor : pointer ; user-select : none ; border : none ; background : none ; width : 100 % ;
text-align : left ; font-family : inherit ; color : inherit }
. chip . swatch { width : 11 px ; height : 11 px ; flex-shrink : 0 ; border-radius : 2 px }
. chip . n { margin-left : auto ; font-family : var ( - - mono ) ; font-size : 10 px ; color : var ( - - muted ) }
. chip . off { opacity : .34 }
. seg { display : flex ; border : 1 px solid var ( - - rule ) ; border-radius : 3 px ; overflow : hidden }
. seg button { flex : 1 ; padding : 7 px 4 px ; background : #fff ; border : none ; cursor : pointer ;
font-family : var ( - - mono ) ; font-size : 9.5 px ; letter-spacing : .07 em ;
text-transform : uppercase ; color : var ( - - muted ) }
. seg button + button { border-left : 1 px solid var ( - - rule ) }
. seg button . on { background : var ( - - navy ) ; color : #fff }
. opt { display : flex ; align-items : center ; gap : 8 px ; padding : 4 px 0 ; font-size : 12 px ; cursor : pointer }
. opt input { accent-color : var ( - - navy ) ; width : 15 px ; height : 15 px ; cursor : pointer }
. note { font-size : 11 px ; line-height : 1.55 ; color : var ( - - muted ) }
. note code { font-family : var ( - - mono ) ; font-size : 10.5 px ; background : var ( - - bg ) ; padding : 1 px 4 px }
/* ---------- canvas ---------- */
# wrap { flex : 1 ; min-width : 0 ; position : relative ; overflow : hidden ; touch-action : none ;
background :
linear-gradient ( 90 deg , rgba ( 10 , 31 , 68 , .035 ) 1 px , transparent 1 px ) 0 0 / 28 px 28 px ,
linear-gradient ( 180 deg , #FCFBF7 , #EFEDE3 ) }
svg { display : block ; width : 100 % ; height : 100 % ; cursor : grab }
svg . drag { cursor : grabbing }
. bandrect { opacity : .5 }
. bandline { stroke : var ( - - rule ) ; stroke-width : 1 }
. bandlabel { font-family : var ( - - mono ) ; font-size : 10 px ; letter-spacing : .15 em ;
text-transform : uppercase ; font-weight : 600 }
. bandtag { font-family : var ( - - sans ) ; font-size : 9.5 px ; fill : var ( - - muted ) ; font-style : italic }
. node { cursor : pointer }
. node rect { stroke-width : 1.5 }
. node text { font-size : 10 px ; pointer-events : none ; text-anchor : middle ; font-weight : 500 }
. node . dim { opacity : .13 }
. node . sel rect { stroke-width : 3 }
. edge { fill : none ; stroke : #9AA0AB ; stroke-width : 1.2 ; marker-end : url ( #ar ) }
. edge . bridge { stroke : var ( - - navy ) ; stroke-width : 2 ; marker-end : url ( #arb ) }
. edge . uni { stroke : #C3BFB4 ; stroke-dasharray : 3 3 ; marker-end : url ( #aru ) }
. edge . dim { opacity : .07 }
. edge . hot { stroke : var ( - - gold ) ; stroke-width : 2.6 ; marker-end : url ( #arg ) }
. elabel { font-family : var ( - - mono ) ; font-size : 8 px ; fill : #5A6270 ; text-anchor : middle ;
pointer-events : none }
. elabel . dim { opacity : .06 }
. ebg { fill : #F4F2EC }
# zoom { position : absolute ; left : 14 px ; bottom : 14 px ; display : flex ; flex-direction : column ;
gap : 3 px ; background : #fff ; border : 1 px solid var ( - - rule ) ; border-radius : 4 px ; padding : 3 px }
# zoom button { width : 32 px ; height : 32 px ; background : #fff ; border : none ; border-radius : 3 px ;
font-size : 16 px ; cursor : pointer ; color : var ( - - navy ) ; font-weight : 600 ; font-family : inherit }
# zoom button : hover { background : var ( - - bg ) }
# hint { position : absolute ; right : 14 px ; bottom : 14 px ; font-family : var ( - - mono ) ;
font-size : 9.5 px ; color : var ( - - muted ) ; background : rgba ( 255 , 255 , 255 , .82 ) ;
padding : 5 px 9 px ; border-radius : 3 px ; letter-spacing : .04 em }
/* ---------- inspector ---------- */
# insp { position : absolute ; top : 0 ; right : 0 ; width : 352 px ; max-width : 100 % ; height : 100 % ;
background : var ( - - panel ) ; border-left : 1 px solid var ( - - rule ) ;
box-shadow : -6 px 0 24 px rgba ( 10 , 31 , 68 , .10 ) ; overflow-y : auto ; padding : 20 px 20 px 40 px ;
transform : translateX ( 100 % ) ; transition : transform .22 s ease }
# insp . open { transform : translateX ( 0 ) }
# insp . close { position : absolute ; top : 12 px ; right : 14 px ; border : none ; background : none ;
font-size : 22 px ; cursor : pointer ; color : var ( - - muted ) ; line-height : 1 ; padding : 2 px 6 px }
# insp . kicker { font-family : var ( - - mono ) ; font-size : 9 px ; letter-spacing : .16 em ;
text-transform : uppercase ; font-weight : 600 }
# insp h3 { margin : 6 px 0 3 px ; font-size : 19 px ; letter-spacing : -.01 em }
# insp . iri { font-family : var ( - - mono ) ; font-size : 10.5 px ; color : var ( - - muted ) ;
word-break : break-all ; margin-bottom : 14 px }
# insp h4 { font-family : var ( - - mono ) ; font-size : 9 px ; text-transform : uppercase ;
letter-spacing : .14 em ; color : var ( - - muted ) ; margin : 20 px 0 7 px ; font-weight : 600 ;
border-top : 1 px solid var ( - - rule ) ; padding-top : 10 px }
# insp p { font-size : 12.5 px ; line-height : 1.62 ; margin : 0 0 8 px }
. pill { display : inline-block ; padding : 3 px 8 px ; border-radius : 2 px ; font-family : var ( - - mono ) ;
font-size : 9 px ; font-weight : 600 ; letter-spacing : .08 em ; text-transform : uppercase ;
margin : 0 5 px 5 px 0 ; border : 1 px solid }
. rel { display : grid ; grid-template-columns : 1 fr auto ; gap : 4 px 10 px ; align-items : baseline ;
padding : 5 px 0 ; border-bottom : 1 px dotted var ( - - rule ) ; font-size : 12 px }
. rel . p { font-family : var ( - - mono ) ; font-size : 10.5 px ; color : var ( - - navy ) }
. rel . o { color : var ( - - muted ) ; font-size : 11.5 px ; text-align : right }
. rel . flag { font-family : var ( - - mono ) ; font-size : 8.5 px ; color : var ( - - gold ) ;
letter-spacing : .06 em ; text-transform : uppercase }
. jump { background : none ; border : none ; padding : 0 ; font : inherit ; color : var ( - - navy ) ;
cursor : pointer ; text-decoration : underline ; text-underline-offset : 2 px }
. harvest { background : var ( - - bg ) ; border-left : 2 px solid var ( - - gold ) ; padding : 9 px 11 px ;
font-family : var ( - - mono ) ; font-size : 10.5 px ; line-height : 1.55 ; color : #4A5160 }
footer { background : var ( - - navy ) ; color : rgba ( 255 , 255 , 255 , .55 ) ; padding : 6 px 18 px ;
font-family : var ( - - mono ) ; font-size : 9.5 px ; letter-spacing : .06 em ; display : flex ;
justify-content : space-between ; flex-shrink : 0 }
@ media ( max-width : 860px ) {
aside { position : absolute ; z-index : 30 ; height : 100 % ; transform : translateX ( -100 % ) ;
transition : transform .22 s ; box-shadow : 4 px 0 18 px rgba ( 0 , 0 , 0 , .16 ) }
aside . open { transform : translateX ( 0 ) }
# mtoggle { display : inline-block }
. stats { display : none }
# insp { width : 100 % }
# hint { display : none }
}
@ media ( prefers-reduced-motion : reduce ) { * { transition : none !important } }
< / style >
< / head >
< body >
< header >
< button id = "mtoggle" aria-label = "Toggle filters" > ☰ < / button >
< div >
< h1 > PR Data Meta Model · T-Box< / h1 >
< div class = "sub" > Five layers, crossed with a provenance axis: what governance asserts, what harvesters observe< / div >
< / div >
< div class = "stats" >
< div class = "stat" > < b id = "s-cls" > 0< / b > < span > Classes< / span > < / div >
< div class = "stat" > < b id = "s-obj" > 0< / b > < span > Relations< / span > < / div >
< div class = "stat" > < b id = "s-dat" > 0< / b > < span > Attributes< / span > < / div >
< div class = "stat" > < b id = "s-brg" > 0< / b > < span > Seam edges< / span > < / div >
< / div >
< / header >
< main >
< aside id = "side" >
< h2 > Find a term< / h2 >
< input id = "q" type = "search" placeholder = "metric, field, lineage…" autocomplete = "off" >
< h2 > Layout< / h2 >
< div class = "seg" role = "group" >
< button id = "m-strata" class = "on" > Strata< / button >
< button id = "m-tax" > Taxonomy< / button >
< button id = "m-free" > Network< / button >
< / div >
< h2 > Layers< / h2 >
< div id = "bands" > < / div >
< h2 > Show< / h2 >
< label class = "opt" > < input type = "checkbox" id = "o-uni" > Universal relations< / label >
< label class = "opt" > < input type = "checkbox" id = "o-dep" > Deprecated terms< / label >
< label class = "opt" > < input type = "checkbox" id = "o-lab" checked > Relation names< / label >
< h2 > Reading it< / h2 >
< p class = "note" >
Solid fill is < b > curated< / b > : governance writes it, a pull request reviews it.
Hatched outline is < b > observed< / b > : a harvester writes it nightly and no one
edits it by hand.< br > < br >
The two navy edges are the seam — < code > materializedAs< / code > and
< code > physicalizedIn< / code > . They are the only place the two worlds meet,
and the only mapping a steward has to confirm.
< / p >
< / aside >
< div id = "wrap" >
< svg id = "svg" xmlns = "http://www.w3.org/2000/svg" >
< defs >
< marker id = "ar" viewBox = "0 0 10 10" refX = "9" refY = "5" markerWidth = "6" markerHeight = "6" orient = "auto" > < path d = "M0,0 L10,5 L0,10 z" fill = "#9AA0AB" / > < / marker >
< marker id = "arb" viewBox = "0 0 10 10" refX = "9" refY = "5" markerWidth = "6" markerHeight = "6" orient = "auto" > < path d = "M0,0 L10,5 L0,10 z" fill = "#0A1F44" / > < / marker >
< marker id = "aru" viewBox = "0 0 10 10" refX = "9" refY = "5" markerWidth = "6" markerHeight = "6" orient = "auto" > < path d = "M0,0 L10,5 L0,10 z" fill = "#C3BFB4" / > < / marker >
< marker id = "arg" viewBox = "0 0 10 10" refX = "9" refY = "5" markerWidth = "6" markerHeight = "6" orient = "auto" > < path d = "M0,0 L10,5 L0,10 z" fill = "#B8935A" / > < / marker >
< pattern id = "hatch" width = "5" height = "5" patternTransform = "rotate(45)" patternUnits = "userSpaceOnUse" >
< line x1 = "0" y1 = "0" x2 = "0" y2 = "5" stroke = "#0A1F44" stroke-width = "1.1" opacity = ".16" / >
< / pattern >
< / defs >
< g id = "vp" > < g id = "bL" > < / g > < g id = "eL" > < / g > < g id = "tL" > < / g > < g id = "nL" > < / g > < / g >
< / svg >
< div id = "zoom" >
< button id = "z-in" aria-label = "Zoom in" > +< / button >
< button id = "z-out" aria-label = "Zoom out" > − < / button >
< button id = "z-fit" aria-label = "Fit to view" style = "font-size:13px" > ↻ < / button >
< / div >
< div id = "hint" > drag to pan · scroll to zoom · click a class< / div >
< / div >
< div id = "insp" role = "dialog" aria-label = "Term details" >
< button class = "close" id = "insp-x" aria-label = "Close" > × < / button >
< div id = "insp-body" > < / div >
< / div >
< / main >
< footer >
< span > Build artefact of pr_metamodel.ttl — regenerate, never edit< / span >
< span id = "f-ver" > T-Box v1.0< / span >
< / footer >
< script >
2026-07-27 18:17:19 +02:00
var M = { "bands" : [ { "id" : "roots" , "title" : "Model roots" , "tag" : "How a term got here" , "color" : "#0A1F44" } , { "id" : "ownership" , "title" : "Ownership & Categorization" , "tag" : "Who is accountable" , "color" : "#B8935A" } , { "id" : "business" , "title" : "Business" , "tag" : "What the business means" , "color" : "#8B3A3A" } , { "id" : "logical" , "title" : "Logical" , "tag" : "How the data is structured" , "color" : "#7A4E8B" } , { "id" : "delivery" , "title" : "Delivery" , "tag" : "What we promise, to whom" , "color" : "#B8935A" } , { "id" : "physical" , "title" : "Physical" , "tag" : "What actually runs" , "color" : "#1E4B9B" } , { "id" : "consumption" , "title" : "Consumption" , "tag" : "What people open in the morning" , "color" : "#1E7A5F" } , { "id" : "context" , "title" : "Actors & vocabularies" , "tag" : "Outside the object stack" , "color" : "#6B6B6B" } ] , "nodes" : [ { "id" : "pr:DataDomainOwner" , "label" : "Data Domain Owner" , "band" : "context" , "prov" : "neutral" , "parent" : "pr:Actor" , "comment" : "Accountable for a Data Domain. Exactly one per domain." , "harvest" : "" , "curation" : "" , "deprecated" : false , "replacedBy" : "" , "members" : [ ] } , { "id" : "pr:BaseTable" , "label" : "Base Table" , "band" : "physical" , "prov" : "observed" , "parent" : "pr:PhysicalRelation" , "comment" : "" , "harvest" : "" , "curation" : "" , "deprecated" : false , "replacedBy" : "" , "members" : [ ] } , { "id" : "pr:CuratedObject" , "label" : "Curated Object" , "band" : "roots" , "prov" : "curated" , "parent" : "pr:MetaModelObject" , "comment" : "Asserted by governance, edited through pull request on the domain TTL file, subject to the full ownership and lifecycle shape (identifier, name, owner, owning Data Domain, status, version)." , "harvest" : "" , "curation" : "ASSERTED" , "deprecated" : false , "replacedBy" : "" , "members" : [ ] } , { "id" : "pr:Table" , "label" : "Table (deprecated)" , "band" : "physical" , "prov" : "observed" , "parent" : "pr:PhysicalRelation" , "comment" : "v0.9 term. Kept so that existing assertions still classify. Use BaseTable." , "harvest" : "" , "curation" : "" , "deprecated" : true , "replacedBy" : "pr:BaseTable" , "members" : [ ] } , { "id" : "pr:DataInterface" , "label" : "Data Interface" , "band" : "delivery" , "prov" : "curated" , "parent" : "pr:DeliveryLayerObject" , "comment" : "The channel through which a consumer actually gets the data: a Snowflake secure share or listing, a consumption schema with its grants, a file extract to a panel partner, an API or reverse-ETL feed. Not a Data Object (that is structure) and not a BI Data Source (that is one consumer among others). Verifiable: a grant, a share or an endpoint either exists or it does not. The output port in data-mesh terms." , "harvest" : "" , "curation" : "" , "deprecated" : false , "replacedBy" : "" , "members" : [ ] } , { "id" : "pr:ForeignKeyConstraint" , "label" : "Foreign Key Constraint" , "band" : "physical" , "prov" : "observed" , "parent" : "pr:KeyConstraint" , "comment" : "" , "harvest" : "" , "curation" : "" , "deprecated" : false , "replacedBy" : "" , "members" : [ ] } , { "id" : "pr:DataContract" , "label" : "Data Contract" , "band" : "delivery" , "prov" : "curated" , "parent" : "pr:DeliveryLayerObject" , "comment" : "The service commitment attached to a Data Product: schema, freshness, quality thresholds, support, breaking-change notice period. Candidate alignment: ODCS (Bitol)." , "harvest" : "" , "curation" : "" , "deprecated" : false , "replacedBy" : "" , "members" : [ ] } , { "id" : "pr:Transformation" , "label" : "Transformation" , "band" : "physical" , "prov" : "observed" , "parent" : "pr:PhysicalLayerObject" , "comment" : "The code that turns inputs into outputs: a dbt model, a Snowflake task, an ADF pipeline. Subclass of prov:Activity so that lineage is expressed in a standard vocabulary rather than a private one." , "harvest" : "dbt manifest.json, Snowflake TASKS, Azure Data Factory pipelines" , "curation" : "" , "deprecated" : false , "replacedBy" : "" , "members" : [ ] } , { "id" : "pr:PhysicalLayerObject" , "label" : "Physical Layer Object" , "band" : "physical" , "prov" : "observed" , "parent" : "pr:ObservedObject" , "comment" : "" , "harvest" : "" , "curation" : "" , "deprecated" : false , "replacedBy" : "" , "members" : [ ] } , { "id" : "pr:SubDomainOwner" , "label" : "Sub-Domain Owner" , "band" : "context" , "prov" : "neutral" , "parent" : "pr:Actor" , "comment" : "Accountable for a Sub-Domain. Exactly one per sub-domain." , "harvest" : "" , "curation" : "" , "deprecated" : false , "replacedBy" : "" , "members" : [ ] } , { "id" : "pr:Metric" , "label" : "Metric" , "band" : "business" , "prov"
2026-07-26 21:28:14 +02:00
/* ---------------- state ---------------- */
var NW = 168 , NH = 46 , BANDGAP = 52 , GUT = 206 ;
var byId = { } , off = { } , mode = 'strata' , showUni = false , showDep = false , showLab = true ;
var sel = null , hot = null , view = { x : 0 , y : 0 , s : 1 } ;
var svg = document . getElementById ( 'svg' ) , vp = document . getElementById ( 'vp' ) ;
var bL = document . getElementById ( 'bL' ) , eL = document . getElementById ( 'eL' ) ;
var tL = document . getElementById ( 'tL' ) , nL = document . getElementById ( 'nL' ) ;
var SEAM = { 'pr:materializedAs' : 1 , 'pr:physicalizedIn' : 1 } ;
M . nodes . forEach ( function ( n ) { byId [ n . id ] = n ; } ) ;
document . getElementById ( 's-cls' ) . textContent = M . nodes . length ;
document . getElementById ( 's-obj' ) . textContent = M . edges . length + M . universal . length ;
document . getElementById ( 's-dat' ) . textContent = M . datatypes . length ;
document . getElementById ( 's-brg' ) . textContent = M . edges . filter ( function ( e ) { return SEAM [ e . id ] ; } ) . length ;
function el ( t , a ) { var e = document . createElementNS ( 'http://www.w3.org/2000/svg' , t ) ;
if ( a ) for ( var k in a ) e . setAttribute ( k , a [ k ] ) ; return e ; }
function bandOf ( id ) { return M . bands . filter ( function ( b ) { return b . id === id ; } ) [ 0 ] ; }
function visible ( n ) { if ( off [ n . band ] ) return false ; if ( n . deprecated && ! showDep ) return false ; return true ; }
/* ---------------- layout ---------------- */
function layout ( ) {
var live = M . nodes . filter ( visible ) ;
if ( mode === 'tax' ) { layoutTax ( live ) ; return live ; }
var y = 0 ;
M . bands . forEach ( function ( b ) {
var group = live . filter ( function ( n ) { return n . band === b . id ; } ) ;
if ( ! group . length ) { b . _h = 0 ; b . _y = y ; return ; }
var perRow = Math . max ( 3 , Math . ceil ( Math . sqrt ( group . length * 2.1 ) ) ) ;
var rows = Math . ceil ( group . length / perRow ) ;
b . _y = y ; b . _h = rows * ( NH + 20 ) + 48 ; b . _rows = rows ; b . _per = perRow ;
group . forEach ( function ( n , i ) {
n . row = Math . floor ( i / perRow ) ;
n . x = GUT + ( i % perRow ) * ( NW + 26 ) + NW / 2 ;
n . y = y + 34 + n . row * ( NH + 20 ) + NH / 2 ;
} ) ;
y += b . _h + BANDGAP ;
} ) ;
if ( mode === 'strata' ) relax ( live ) ;
else force ( live ) ;
return live ;
}
/* barycentre relaxation on x only: connected classes drift together,
strata stay intact, which is the whole point of the drawing */
function relax ( live ) {
var idx = { } ; live . forEach ( function ( n ) { idx [ n . id ] = n ; } ) ;
var adj = { } ; live . forEach ( function ( n ) { adj [ n . id ] = [ ] ; } ) ;
M . edges . forEach ( function ( e ) {
if ( idx [ e . s ] && idx [ e . t ] && ( showDep || ! e . deprecated ) ) {
adj [ e . s ] . push ( e . t ) ; adj [ e . t ] . push ( e . s ) ; }
} ) ;
for ( var it = 0 ; it < 70 ; it ++ ) {
live . forEach ( function ( n ) {
var ns = adj [ n . id ] ; if ( ! ns . length ) return ;
var sum = 0 , c = 0 ;
ns . forEach ( function ( m ) { if ( idx [ m ] ) { sum += idx [ m ] . x ; c ++ ; } } ) ;
if ( c ) n . x += ( sum / c - n . x ) * 0.22 ;
} ) ;
/* de-overlap inside each row */
var rows = { } ;
live . forEach ( function ( n ) { var k = n . band + '|' + n . row ; ( rows [ k ] = rows [ k ] || [ ] ) . push ( n ) ; } ) ;
for ( var k in rows ) {
var r = rows [ k ] . sort ( function ( a , b ) { return a . x - b . x ; } ) ;
for ( var i = 1 ; i < r . length ; i ++ ) {
var need = ( NW + 22 ) - ( r [ i ] . x - r [ i - 1 ] . x ) ;
if ( need > 0 ) { r [ i ] . x += need * 0.55 ; r [ i - 1 ] . x -= need * 0.45 ; }
}
r . forEach ( function ( n ) { if ( n . x < GUT + NW / 2 ) n . x = GUT + NW / 2 ; } ) ;
}
}
/* final strict pass: guarantee no overlap, preserve the order relaxation found */
var rows2 = { } ;
live . forEach ( function ( n ) { var k = n . band + '|' + n . row ; ( rows2 [ k ] = rows2 [ k ] || [ ] ) . push ( n ) ; } ) ;
for ( var k2 in rows2 ) {
var r2 = rows2 [ k2 ] . sort ( function ( a , b ) { return a . x - b . x ; } ) ;
var cursor = Math . max ( GUT + NW / 2 , r2 [ 0 ] . x ) ;
r2 . forEach ( function ( n ) { n . x = Math . max ( n . x , cursor ) ; cursor = n . x + NW + 22 ; } ) ;
}
}
/* plain force-directed network: no strata, for people who want the usual view */
function force ( live ) {
var idx = { } ; live . forEach ( function ( n ) { idx [ n . id ] = n ; } ) ;
var links = M . edges . filter ( function ( e ) {
return idx [ e . s ] && idx [ e . t ] && e . s !== e . t && ( showDep || ! e . deprecated ) ; } ) ;
var cx = live . reduce ( function ( a , n ) { return a + n . x ; } , 0 ) / live . length ;
var cy = live . reduce ( function ( a , n ) { return a + n . y ; } , 0 ) / live . length ;
for ( var it = 0 ; it < 420 ; it ++ ) {
var k = 1 - it / 420 ;
for ( var i = 0 ; i < live . length ; i ++ ) {
var a = live [ i ] ;
for ( var j = i + 1 ; j < live . length ; j ++ ) {
var b = live [ j ] , dx = b . x - a . x , dy = ( b . y - a . y ) * 1.9 ;
var d2 = dx * dx + dy * dy || 1 , f = 42000 / d2 ;
if ( f > 9 ) f = 9 ;
var d = Math . sqrt ( d2 ) ;
a . x -= dx / d * f ; a . y -= dy / d * f * 0.5 ; b . x += dx / d * f ; b . y += dy / d * f * 0.5 ;
}
a . x += ( cx - a . x ) * 0.004 * k ; a . y += ( cy - a . y ) * 0.006 * k ;
}
links . forEach ( function ( e ) {
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 - 235 ) * 0.018 * k ;
a . x += dx / d * f ; a . y += dy / d * f ; b . x -= dx / d * f ; b . y -= dy / d * f ;
} ) ;
}
}
function layoutTax ( live ) {
var kids = { } , roots = [ ] ;
live . forEach ( function ( n ) { if ( n . parent && byId [ n . parent ] && visible ( byId [ n . parent ] ) )
( kids [ n . parent ] = kids [ n . parent ] || [ ] ) . push ( n ) ; else roots . push ( n ) ; } ) ;
var cursor = 0 ;
function place ( n , depth ) {
var ch = kids [ n . id ] || [ ] ;
n . x = depth * ( NW + 58 ) + NW / 2 + 30 ;
if ( ! ch . length ) { n . y = cursor * ( NH + 13 ) + NH / 2 + 20 ; cursor ++ ; }
else { ch . forEach ( function ( c ) { place ( c , depth + 1 ) ; } ) ;
n . y = ( ch [ 0 ] . y + ch [ ch . length - 1 ] . y ) / 2 ; }
}
roots . forEach ( function ( r ) { place ( r , 0 ) ; cursor += 0.7 ; } ) ;
M . bands . forEach ( function ( b ) { b . _h = 0 ; } ) ;
}
/* ---------------- render ---------------- */
function render ( ) {
var live = layout ( ) ;
var idx = { } ; live . forEach ( function ( n ) { idx [ n . id ] = n ; } ) ;
bL . innerHTML = '' ; eL . innerHTML = '' ; tL . innerHTML = '' ; nL . innerHTML = '' ;
if ( mode === 'strata' ) {
var maxx = Math . max . apply ( null , live . map ( function ( n ) { return n . x ; } ) . concat ( [ 600 ] ) ) + NW ;
M . bands . forEach ( function ( b ) {
if ( ! b . _h ) return ;
bL . appendChild ( el ( 'rect' , { x : 0 , y : b . _y , width : maxx + 60 , height : b . _h ,
fill : b . color , opacity : . 045 , class : 'bandrect' } ) ) ;
bL . appendChild ( el ( 'line' , { x1 : 0 , y1 : b . _y , x2 : maxx + 60 , y2 : b . _y , class : 'bandline' } ) ) ;
bL . appendChild ( el ( 'rect' , { x : GUT - 5 , y : b . _y + 10 , width : 3 , height : b . _h - 22 ,
fill : b . color , opacity : . 85 } ) ) ;
var t = el ( 'text' , { x : GUT - 20 , y : b . _y + 26 , class : 'bandlabel' , fill : b . color ,
'text-anchor' : 'end' } ) ;
t . textContent = b . title ; bL . appendChild ( t ) ;
var g = el ( 'text' , { x : GUT - 20 , y : b . _y + 41 , class : 'bandtag' , 'text-anchor' : 'end' } ) ;
g . textContent = b . tag ; bL . appendChild ( g ) ;
} ) ;
}
var list = M . edges . slice ( ) ;
if ( showUni ) list = list . concat ( M . universal ) ;
list . forEach ( function ( e ) {
if ( ! idx [ e . s ] || ! idx [ e . t ] ) return ;
if ( e . deprecated && ! showDep ) return ;
var a = idx [ e . s ] , b = idx [ e . t ] , uni = M . universal . indexOf ( e ) > - 1 ;
var cls = 'edge' + ( uni ? ' uni' : SEAM [ e . id ] ? ' bridge' : '' ) ;
var p , mx , my ;
if ( a === b ) {
p = 'M' + ( a . x - 24 ) + ',' + ( a . y - NH / 2 ) + ' C' + ( a . x - 70 ) + ',' + ( a . y - 70 ) + ' ' + ( a . x + 70 ) + ',' + ( a . y - 70 ) + ' ' + ( a . x + 24 ) + ',' + ( a . y - NH / 2 ) ;
mx = a . x ; my = a . y - 52 ;
} else {
var dx = b . x - a . x , dy = b . y - a . y , k = Math . abs ( dy ) > 4 ? 0.38 : 0 ;
var cx = a . x + dx / 2 + ( - dy ) * k * 0.14 , cy = a . y + dy / 2 + dx * k * 0.05 ;
p = 'M' + a . x + ',' + a . y + ' Q' + cx + ',' + cy + ' ' + b . x + ',' + b . y ;
mx = ( a . x + 2 * cx + b . x ) / 4 ; my = ( a . y + 2 * cy + b . y ) / 4 ;
}
var path = el ( 'path' , { d : p , class : cls } ) ; path . dataset . e = e . id ;
path . dataset . a = e . s ; path . dataset . b = e . t ; eL . appendChild ( path ) ;
if ( showLab && ! uni ) {
var w = e . label . length * 4.6 + 8 ;
var bg = el ( 'rect' , { x : mx - w / 2 , y : my - 6 , width : w , height : 11 , rx : 2 , class : 'ebg' } ) ;
bg . dataset . el = e . id ; tL . appendChild ( bg ) ;
var tx = el ( 'text' , { x : mx , y : my + 2.5 , class : 'elabel' } ) ;
tx . dataset . el = e . id ; tx . textContent = e . label ; tL . appendChild ( tx ) ;
}
} ) ;
live . forEach ( function ( n ) {
var col = bandOf ( n . band ) . color ;
var g = el ( 'g' , { class : 'node' , 'tabindex' : '0' , 'role' : 'button' ,
'aria-label' : n . label , transform : 'translate(' + ( n . x - NW / 2 ) + ',' + ( n . y - NH / 2 ) + ')' } ) ;
g . dataset . id = n . id ;
g . appendChild ( el ( 'rect' , { width : NW , height : NH , rx : 3 ,
fill : n . prov === 'observed' ? '#fff' : col ,
'fill-opacity' : n . prov === 'observed' ? 1 : . 13 ,
stroke : col , 'stroke-dasharray' : n . deprecated ? '4 3' : 'none' } ) ) ;
if ( n . prov === 'observed' )
g . appendChild ( el ( 'rect' , { width : NW , height : NH , rx : 3 , fill : 'url(#hatch)' , stroke : 'none' } ) ) ;
var lines = wrap ( n . label , 22 ) ;
lines . forEach ( function ( s , i ) {
var t = el ( 'text' , { x : NW / 2 , y : NH / 2 + 4 + ( i - ( lines . length - 1 ) / 2 ) * 11.8 ,
fill : n . deprecated ? '#9A9A9A' : '#1A1A1A' ,
'text-decoration' : n . deprecated ? 'line-through' : 'none' } ) ;
t . textContent = s ; g . appendChild ( t ) ;
} ) ;
nL . appendChild ( g ) ;
} ) ;
applyHot ( ) ;
}
function wrap ( s , max ) {
var w = s . split ( ' ' ) , out = [ ] , cur = '' ;
w . forEach ( function ( x ) {
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 , 3 ) ;
}
/* ---------------- focus / highlight ---------------- */
function neighbours ( id ) {
var s = { } ; s [ id ] = 1 ;
M . edges . concat ( M . universal ) . forEach ( function ( e ) {
if ( e . s === id ) s [ e . t ] = 1 ; if ( e . t === id ) s [ e . s ] = 1 ; } ) ;
return s ;
}
function applyHot ( ) {
var id = hot || sel ;
var nodes = nL . querySelectorAll ( '.node' ) ;
var paths = eL . querySelectorAll ( '.edge' ) ;
var labs = tL . querySelectorAll ( '[data-el]' ) ;
if ( ! id ) {
nodes . forEach ( function ( n ) { n . classList . remove ( 'dim' , 'sel' ) ; } ) ;
paths . forEach ( function ( p ) { p . classList . remove ( 'dim' , 'hot' ) ; } ) ;
labs . forEach ( function ( l ) { l . classList . remove ( 'dim' ) ; } ) ;
return ;
}
var keep = neighbours ( id ) , live = { } ;
paths . forEach ( function ( p ) {
var on = ( p . dataset . a === id || p . dataset . b === id ) ;
p . classList . toggle ( 'hot' , on ) ; p . classList . toggle ( 'dim' , ! on ) ;
if ( on ) live [ p . dataset . e ] = 1 ;
} ) ;
labs . forEach ( function ( l ) { l . classList . toggle ( 'dim' , ! live [ l . dataset . el ] ) ; } ) ;
nodes . forEach ( function ( n ) {
n . classList . toggle ( 'dim' , ! keep [ n . dataset . id ] ) ;
n . classList . toggle ( 'sel' , n . dataset . id === sel ) ;
} ) ;
}
/* ---------------- inspector ---------------- */
var insp = document . getElementById ( 'insp' ) , ibody = document . getElementById ( 'insp-body' ) ;
function esc ( s ) { return ( s || '' ) . replace ( /&/g , '&' ) . replace ( /</g , '<' ) ; }
function open ( id ) {
var n = byId [ id ] ; if ( ! n ) return ;
sel = id ; var b = bandOf ( n . band ) , h = [ ] ;
h . push ( '<div class="kicker" style="color:' + b . color + '">' + esc ( b . title ) + '</div>' ) ;
h . push ( '<h3>' + esc ( n . label ) + '</h3>' ) ;
h . push ( '<div class="iri">' + esc ( n . id ) + '</div>' ) ;
h . push ( '<span class="pill" style="color:' + b . color + ';border-color:' + b . color + '">' +
( n . prov === 'observed' ? 'Observed · harvested' : n . prov === 'curated' ? 'Curated · asserted' : 'Structural' ) + '</span>' ) ;
if ( n . curation ) h . push ( '<span class="pill" style="color:#B8935A;border-color:#B8935A">' + esc ( n . curation ) + '</span>' ) ;
if ( n . deprecated ) h . push ( '<span class="pill" style="color:#8B3A3A;border-color:#8B3A3A">Deprecated</span>' ) ;
if ( n . comment ) h . push ( '<h4>What it is for</h4><p>' + esc ( n . comment ) + '</p>' ) ;
if ( n . replacedBy ) h . push ( '<p><b>Replaced by</b> ' + link ( n . replacedBy ) + '</p>' ) ;
if ( n . harvest ) h . push ( '<h4>Harvested from</h4><div class="harvest">' + esc ( n . harvest ) + '</div>' ) ;
if ( n . parent ) h . push ( '<h4>Specialises</h4><div class="rel"><span class="p">rdfs:subClassOf</span><span class="o">' + link ( n . parent ) + '</span></div>' ) ;
var kids = M . nodes . filter ( function ( m ) { return m . parent === id ; } ) ;
if ( kids . length ) h . push ( '<h4>Specialised by</h4>' + kids . map ( function ( k ) {
return '<div class="rel"><span class="p">' + esc ( k . label ) + '</span><span class="o">' +
( k . deprecated ? 'deprecated' : bandOf ( k . band ) . title ) + '</span></div>' ; } ) . join ( '' ) ) ;
if ( n . members && n . members . length ) h . push ( '<h4>Allowed values</h4>' + n . members . map ( function ( m ) {
return '<div class="rel"><span class="p">' + esc ( m . label ) + '</span><span class="o">' + esc ( m . id ) + '</span></div>' ; } ) . join ( '' ) ) ;
var out = M . edges . concat ( M . universal ) . filter ( function ( e ) { return e . s === id && ( showDep || ! e . deprecated ) ; } ) ;
var inc = M . edges . concat ( M . universal ) . filter ( function ( e ) { return e . t === id && ( showDep || ! e . deprecated ) ; } ) ;
if ( out . length ) h . push ( '<h4>Points to</h4>' + out . map ( rel ) . join ( '' ) ) ;
if ( inc . length ) h . push ( '<h4>Pointed to by</h4>' + inc . map ( function ( e ) {
return '<div class="rel"><span class="p">' + esc ( e . label ) + '</span><span class="o">' + link ( e . s ) + '</span></div>' ; } ) . join ( '' ) ) ;
ibody . innerHTML = h . join ( '' ) ;
insp . classList . add ( 'open' ) ;
bindJumps ( ) ; applyHot ( ) ;
}
function rel ( e ) {
var flags = [ ] ; if ( e . functional ) flags . push ( 'exactly one' ) ;
if ( e . transitive ) flags . push ( 'transitive' ) ;
if ( SEAM [ e . id ] ) flags . push ( 'seam' ) ;
return '<div class="rel"><span class="p">' + esc ( e . label ) +
( flags . length ? ' <span class="flag">' + flags . join ( ' · ' ) + '</span>' : '' ) +
'</span><span class="o">' + link ( e . t ) + '</span>' +
( e . comment ? '<p style="grid-column:1/-1;margin:4px 0 0;font-size:11.5px;color:#6B6B6B">' + esc ( e . comment ) + '</p>' : '' ) +
'</div>' ;
}
function link ( id ) {
var n = byId [ id ] ;
return n ? '<button class="jump" data-j="' + id + '">' + esc ( n . label ) + '</button>' : esc ( id ) ;
}
function bindJumps ( ) {
ibody . querySelectorAll ( '.jump' ) . forEach ( function ( b ) {
b . onclick = function ( ) { open ( b . dataset . j ) ; focusOn ( b . dataset . j ) ; } ; } ) ;
}
document . getElementById ( 'insp-x' ) . onclick = function ( ) {
insp . classList . remove ( 'open' ) ; sel = null ; applyHot ( ) ; } ;
document . addEventListener ( 'keydown' , function ( e ) {
if ( e . key === 'Escape' ) { insp . classList . remove ( 'open' ) ; sel = null ; applyHot ( ) ; } } ) ;
/* ---------------- view ---------------- */
function apply ( ) { vp . setAttribute ( 'transform' , 'translate(' + view . x + ',' + view . y + ') scale(' + view . s + ')' ) ; }
function fit ( ) {
var live = M . nodes . filter ( visible ) ; if ( ! live . length ) return ;
var xs = live . map ( function ( n ) { return n . x ; } ) , ys = live . map ( function ( n ) { return n . y ; } ) ;
var pad = ( mode === 'strata' ) ? GUT + 30 : NW ;
var mnx = Math . min . apply ( null , xs ) - pad , mxx = Math . max . apply ( null , xs ) + NW ;
var mny = Math . min . apply ( null , ys ) - 90 , mxy = Math . max . apply ( null , ys ) + 70 ;
var r = svg . getBoundingClientRect ( ) ;
view . s = Math . min ( r . width / ( mxx - mnx ) , r . height / ( mxy - mny ) , 1.25 ) ;
view . x = ( r . width - ( mxx - mnx ) * view . s ) / 2 - mnx * view . s ;
view . y = ( r . height - ( mxy - mny ) * view . s ) / 2 - mny * view . s ;
apply ( ) ;
}
function focusOn ( id ) {
var n = byId [ id ] ; if ( ! n ) return ;
var r = svg . getBoundingClientRect ( ) ;
view . x = r . width / 2 - n . x * view . s - 120 ; view . y = r . height / 2 - n . y * view . s ; apply ( ) ;
}
document . getElementById ( 'z-in' ) . onclick = function ( ) { zoom ( 1.22 ) ; } ;
document . getElementById ( 'z-out' ) . onclick = function ( ) { zoom ( . 82 ) ; } ;
document . getElementById ( 'z-fit' ) . onclick = fit ;
function zoom ( f ) {
var r = svg . getBoundingClientRect ( ) , cx = r . width / 2 , cy = r . height / 2 ;
var ns = Math . max ( . 18 , Math . min ( view . s * f , 2.6 ) ) ;
view . x = cx - ( cx - view . x ) * ( ns / view . s ) ; view . y = cy - ( cy - view . y ) * ( ns / view . s ) ;
view . s = ns ; apply ( ) ;
}
var drag = false , ds , vs , pinch = null ;
svg . addEventListener ( 'mousedown' , function ( e ) { drag = true ; svg . classList . add ( 'drag' ) ;
ds = { x : e . clientX , y : e . clientY } ; vs = { x : view . x , y : view . y } ; } ) ;
window . addEventListener ( 'mousemove' , function ( e ) { if ( ! drag ) return ;
view . x = vs . x + ( e . clientX - ds . x ) ; view . y = vs . y + ( e . clientY - ds . y ) ; apply ( ) ; } ) ;
window . addEventListener ( 'mouseup' , function ( ) { drag = false ; svg . classList . remove ( 'drag' ) ; } ) ;
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 ( . 18 , Math . min ( view . s * f , 2.6 ) ) ;
view . x = mx - ( mx - view . x ) * ( ns / view . s ) ; view . y = my - ( my - view . y ) * ( ns / view . s ) ;
view . s = ns ; apply ( ) ; } , { passive : false } ) ;
svg . addEventListener ( 'touchstart' , function ( e ) {
if ( e . touches . length === 1 ) { drag = true ; ds = { x : e . touches [ 0 ] . clientX , y : e . touches [ 0 ] . clientY } ;
vs = { x : view . x , y : 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 : view . s } ; } } , { passive : true } ) ;
svg . addEventListener ( 'touchmove' , function ( e ) {
if ( e . touches . length === 1 && drag ) { view . x = vs . x + ( e . touches [ 0 ] . clientX - ds . x ) ;
view . y = vs . y + ( e . touches [ 0 ] . clientY - ds . y ) ; apply ( ) ; 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 ( . 18 , Math . min ( pinch . s * ( nd / pinch . d ) , 2.6 ) ) ;
var r = svg . getBoundingClientRect ( ) , cx = r . width / 2 , cy = r . height / 2 ;
view . x = cx - ( cx - view . x ) * ( ns / view . s ) ; view . y = cy - ( cy - view . y ) * ( ns / view . s ) ;
view . s = ns ; apply ( ) ; e . preventDefault ( ) ; } } , { passive : false } ) ;
svg . addEventListener ( 'touchend' , function ( ) { drag = false ; pinch = null ; } ) ;
nL . addEventListener ( 'click' , function ( e ) {
var g = e . target . closest ( '.node' ) ; if ( g ) open ( g . dataset . id ) ; } ) ;
nL . addEventListener ( 'keydown' , function ( e ) {
var g = e . target . closest ( '.node' ) ;
if ( g && ( e . key === 'Enter' || e . key === ' ' ) ) { e . preventDefault ( ) ; open ( g . dataset . id ) ; } } ) ;
nL . addEventListener ( 'mouseover' , function ( e ) {
var g = e . target . closest ( '.node' ) ; if ( g && ! sel ) { hot = g . dataset . id ; applyHot ( ) ; } } ) ;
nL . addEventListener ( 'mouseout' , function ( e ) {
if ( ! e . relatedTarget || ! e . relatedTarget . closest || ! e . relatedTarget . closest ( '.node' ) ) {
if ( ! sel ) { hot = null ; applyHot ( ) ; } } } ) ;
/* ---------------- controls ---------------- */
var bandsBox = document . getElementById ( 'bands' ) ;
M . bands . forEach ( function ( b ) {
var n = M . nodes . filter ( function ( x ) { return x . band === b . id ; } ) . length ;
if ( ! n ) return ;
var btn = document . createElement ( 'button' ) ;
btn . className = 'chip' ; btn . setAttribute ( 'aria-pressed' , 'true' ) ;
btn . innerHTML = '<span class="swatch" style="background:' + b . color + '"></span>' +
b . title + '<span class="n">' + n + '</span>' ;
btn . onclick = function ( ) {
off [ b . id ] = ! off [ b . id ] ;
btn . classList . toggle ( 'off' , ! ! off [ b . id ] ) ;
btn . setAttribute ( 'aria-pressed' , String ( ! off [ b . id ] ) ) ;
render ( ) ; fit ( ) ; } ;
bandsBox . appendChild ( btn ) ;
} ) ;
function setMode ( m ) {
mode = m ;
[ 'strata' , 'tax' , 'free' ] . forEach ( function ( k ) {
document . getElementById ( 'm-' + k ) . classList . toggle ( 'on' , k === m ) ; } ) ;
render ( ) ; fit ( ) ;
}
document . getElementById ( 'm-strata' ) . onclick = function ( ) { setMode ( 'strata' ) ; } ;
document . getElementById ( 'm-tax' ) . onclick = function ( ) { setMode ( 'tax' ) ; } ;
document . getElementById ( 'm-free' ) . onclick = function ( ) { setMode ( 'free' ) ; } ;
document . getElementById ( 'o-uni' ) . onchange = function ( ) { showUni = this . checked ; render ( ) ; } ;
document . getElementById ( 'o-dep' ) . onchange = function ( ) { showDep = this . checked ; render ( ) ; fit ( ) ; } ;
document . getElementById ( 'o-lab' ) . onchange = function ( ) { showLab = this . checked ; render ( ) ; } ;
document . getElementById ( 'mtoggle' ) . onclick = function ( ) {
document . getElementById ( 'side' ) . classList . toggle ( 'open' ) ; } ;
document . getElementById ( 'q' ) . addEventListener ( 'input' , function ( ) {
var v = this . value . trim ( ) . toLowerCase ( ) ;
if ( ! v ) { hot = null ; applyHot ( ) ; return ; }
var hit = M . nodes . filter ( visible ) . filter ( function ( n ) {
return ( n . label + ' ' + n . id + ' ' + ( n . comment || '' ) ) . toLowerCase ( ) . indexOf ( v ) > - 1 ; } ) ;
nL . querySelectorAll ( '.node' ) . forEach ( function ( g ) {
var on = hit . some ( function ( n ) { return n . id === g . dataset . id ; } ) ;
g . classList . toggle ( 'dim' , ! on ) ; } ) ;
if ( hit . length === 1 ) { open ( hit [ 0 ] . id ) ; focusOn ( hit [ 0 ] . id ) ; }
} ) ;
render ( ) ; fit ( ) ;
window . addEventListener ( 'resize' , fit ) ;
< / script >
< / body >
< / html >