From: Javier Sagredo Date: Sun, 3 May 2026 23:54:00 +0000 (+0200) Subject: Mark top-level classes (no subclasses) and render them at the top X-Git-Url: https://git.sagredo.dev/?a=commitdiff_plain;h=efb8813c1248a1c8eef833d95dfe9a424e856d52;p=classgraph.git Mark top-level classes (no subclasses) and render them at the top --- diff --git a/data/viewer.css b/data/viewer.css index 005ce9b..62212bb 100644 --- a/data/viewer.css +++ b/data/viewer.css @@ -136,6 +136,7 @@ body { display: flex; } 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; } diff --git a/data/viewer.html b/data/viewer.html index df2ead4..09aad9d 100644 --- a/data/viewer.html +++ b/data/viewer.html @@ -29,6 +29,7 @@

Nodes

ClsClass defined in this program
+
ClsTop-level class (no other class extends it; rendered at the top)
FamType family (open / closed / associated)
InstClass instance
f a=btype instance F … = … row
diff --git a/data/viewer.js b/data/viewer.js index 0eb256f..ad5868b 100644 --- a/data/viewer.js +++ b/data/viewer.js @@ -93,7 +93,7 @@ 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); @@ -418,7 +418,7 @@ 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()); } @@ -1418,15 +1418,29 @@ '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"]', diff --git a/src/Classgraph/Render.hs b/src/Classgraph/Render.hs index 4d5901c..35c83a7 100644 --- a/src/Classgraph/Render.hs +++ b/src/Classgraph/Render.hs @@ -89,19 +89,35 @@ buildGraph pd = CyGraph , 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)