white-space: nowrap;
}
.swatch-class { background: #3b82f6; color: #fff; border-color: #1d4ed8; }
+.swatch-toplevel { border-color: #f59e0b; border-width: 2px; }
.swatch-family { background: #fbbf24; color: #3f2d05; border-color: #b45309; }
.swatch-instance { background: #ecfdf5; color: #065f46; border-color: #10b981; }
.swatch-faminst { background: #f5f3ff; color: #5b21b6; border-color: #a78bfa; font-style: italic; }
<div class="help-content">
<h4>Nodes</h4>
<div class="legend-row"><span class="swatch"><span class="swatch-node swatch-class">Cls</span></span><span>Class defined in this program</span></div>
+ <div class="legend-row"><span class="swatch"><span class="swatch-node swatch-class swatch-toplevel">Cls</span></span><span><strong>Top-level</strong> class (no other class extends it; rendered at the top)</span></div>
<div class="legend-row"><span class="swatch"><span class="swatch-node swatch-family">Fam</span></span><span>Type family (open / closed / associated)</span></div>
<div class="legend-row"><span class="swatch"><span class="swatch-node swatch-instance">Inst</span></span><span>Class instance</span></div>
<div class="legend-row"><span class="swatch"><span class="swatch-node swatch-faminst">f a=b</span></span><span><code>type instance F … = …</code> row</span></div>
state.classId = null;
state.familyId = null;
loadGraph(buildClassesView());
- cy.layout({ name: 'dagre', rankDir: 'BT', nodeSep: 50, rankSep: 90 }).run();
+ cy.layout({ name: 'dagre', rankDir: 'TB', nodeSep: 50, rankSep: 90 }).run();
setTopbar('classgraph', '');
setHint('classes');
setBackVisible(false);
function relayoutClassesView() {
if (state.view !== 'classes') return;
loadGraph(buildClassesView());
- cy.layout({ name: 'dagre', rankDir: 'BT', nodeSep: 50, rankSep: 90 }).run();
+ cy.layout({ name: 'dagre', rankDir: 'TB', nodeSep: 50, rankSep: 90 }).run();
setCounts(focusCountsLine());
}
'font-size': 12,
'font-weight': 600,
width: 'label',
- height: 28,
+ height: 'label',
padding: '8px',
shape: 'round-rectangle',
'border-width': 1,
'border-color': '#1d4ed8',
+ 'text-wrap': 'wrap',
+ 'text-justification': 'center',
+ 'line-height': 1.2,
'transition-property': 'opacity, border-width, border-color',
'transition-duration': '120ms',
},
},
+ // Top-level class — no other class has it as a (direct) superclass.
+ // Visually marked with a gold border so they pop against ordinary
+ // classes; their label also carries an extra "★ top" line baked in
+ // by Render.hs.
+ {
+ selector: 'node[kind = "class"][?isTopLevel]',
+ style: {
+ 'border-color': '#f59e0b',
+ 'border-width': 2.5,
+ },
+ },
// Type family node (classes view)
{
selector: 'node[kind = "family"]',
, cyMeta = Aeson.toJSON pd
}
where
+ -- "Top-level" = no other class has this class as a (direct) superclass.
+ -- Computed once over the merged ProgramData; used to mark such classes
+ -- visually so the user can find the entry-point classes at a glance.
+ referencedAsSuper :: Set.Set Text
+ referencedAsSuper = Set.fromList
+ [ renderQualName (seSuperclass se)
+ | c <- pdClasses pd
+ , se <- ciSuperclasses c
+ ]
+ isTopLevel c = not (Set.member (renderQualName (ciName c)) referencedAsSuper)
+
classNodes =
[ CyNode $ Aeson.object
[ "id" Aeson..= renderQualName (ciName c)
- , "label" Aeson..= qnName (ciName c)
+ , "label" Aeson..= classNodeLabel c
, "kind" Aeson..= ("class" :: Text)
, "module" Aeson..= qnModule (ciName c)
, "package" Aeson..= qnPackage (ciName c)
, "tyvars" Aeson..= ciTyVars c
, "methods" Aeson..= ciMethods c
+ , "isTopLevel" Aeson..= isTopLevel c
]
| c <- pdClasses pd
]
+ classNodeLabel c
+ | isTopLevel c = qnName (ciName c) <> "\n\9733 top" -- ★
+ | otherwise = qnName (ciName c)
+
familyNodes =
[ CyNode $ Aeson.object
[ "id" Aeson..= renderQualName (tfName f)