Skip to content
CodArchthe living map of the architecture
ESEN

2D map design

Status: implemented in M0; since M1 it consumes snapshots generated by codarch analyze. Prototype: design/map-mockup.html. Render: codarch render / codarch analyze.

Screenshots: risk · structure · changes · migration

Implemented: 2D server-side treemap (viewBox 1400×900), Structure / Risk / Metric lenses, search, inspector, file and folder selection, and outgoing/incoming edges of the selected node. The HTML is a single file, makes no network requests and is generated in ~35 ms for 2,003 files.

The render does not analyse code: it consumes a cc.json or the snapshot.cc.json produced by codarch analyze. The prototype’s Changes and Migration lenses are not rendered yet: they need diff and history. It does not invent PR findings either; the top strip only shows real hotspots, cycles and counts from the snapshot.


A screenshot of the map, without interaction, has to answer:

  1. Where is the mass of the code?
  2. What is hot?
  3. What has changed?
  4. What depends on what?
  5. What breaks the architecture?

If the legend needs explaining, the design has failed.

  • Area = RLOC. Not LOC: comments add no risk.
  • Colour = one metric, the active lens’s.
  • Nesting = folders. Each group has its header with name and total.

A force-directed graph would be more “architecture”, but it is a worse product: it is not deterministic, it rearranges on every run, it stops being readable past ~200 nodes and it cannot be compared between two commits. The treemap is stable, comparable and fits in one screenshot.

The cost, plainly: the 3D city encodes three metrics at once (area, height, colour). 2D encodes two. We compensate with lenses, delta badges and inspection. It is a deliberate trade: we lose visual density and gain precision, comparability and speed.

LensEncodingQuestionStatus
Structurecategorical colour by folderhow is it organised?M0
Risksequential, beige → redwhat hurts?M0
Metricsequential according to the chosen metricwhere does it concentrate?M0
Changesdiverging, blue → neutral → redwhat is moving?M2
Migrationcategorical ts / bridge / rusthow much is left?M4

Never two metrics competing for colour. The lens switch is the product’s grammar: the same geometry, different questions.

Drawing all dependencies produces a tangle. Here:

  • Outgoing: solid line.
  • Incoming: dashed line, pointing at the selected node.
  • Layer violation: red, thicker.
  • Limit: 8 + 8 edges. The rest, in the inspector.

Selecting store.ts shows the cycle with http.service.ts in red without the rest of the map disappearing. That is what a global graph cannot achieve.

Self-loops are excluded from the drawn edges; the inCycle / cycleSize metrics still mark the file.

1 layer violated · 2 new cycles · 3 hotspots moved is a fixed strip, not a hidden panel. It is the product’s wedge (PR/CI) showing in the interface. Each chip leads to the evidence.

In M0/M1 the strip only shows real data: hotspots, cyclic edges, files and dependencies. There are no PR findings until M2.

  • Sequential for magnitude (risk).
  • Diverging for delta (growth/decline).
  • Categorical only for structure and migration, with a muted palette.
  • Never a rainbow. Colour is data, not decoration.
  • Gamma on risk: raw^1.7. Without it, almost everything came out orange and the map stopped informing. A map where everything is hot says nothing.

Composite risk: 0.5·complexity + 0.32·churn + 0.18·size, normalised by p95. If a signal is missing (for example, git is unavailable or the analysis runs with --no-git), the weights are renormalised so the maximum is still 100/100 and not an artificial 68/100.

Two lines when the block allows it: base name and extension (store / .ts). One line if not. No label below the threshold: hover resolves it. Text density is a treemap’s biggest aesthetic risk.

Metrics, incoming and outgoing dependencies, and the violation if it exists. No decorative charts. The inspector answers “what about this file?”, not “do you want to see a donut?”.

  • Area: rloc by default; if missing, each file gets area 1 so it does not disappear. It is a legibility decision, not an invented metric: the inspector still shows the real value.
  • Hierarchy: folder chains with a single child collapse into one header (app/codeCharta/features, not three empty headers).
  • Tiny folders: when the inner space cannot draw files with one pixel, the folder stays as an aggregated block. In the real sample, 1,894 of 2,003 cells are drawn; the rest are still available in the data, the search and the inspector. The web app’s zoom will solve it.
  • Geometry: integer coordinates in the viewBox, shared borders and shape-rendering: crispEdges so the SVG does not produce subpixel cracks.
  • Labels: two lines (name + extension) only if they fit; below the threshold no text is drawn and the information stays in the hover. The real sample produces ~100 labels, not 2,000.
  • Edges: at most 8 outgoing + 8 incoming; incoming ones are dashed. The rest are listed in the inspector.
  • Payload: unique directories, compact rows per file, cells with rects and integer coordinates. The real sample’s HTML stays under 500 KB.

The same samples/codecharta_visualization.cc.json.gz was compared with CodeCharta Visualization 2.9.0 (area=rloc, height=complexity, color=complexity) and with CodArch (area by rloc, colour by complexity).

SignalCodArchCodeCharta
Files2,0032,003
rloc103,577103,577
Edges3,7273,727
Structureapp 102.4k · features 51.2kapp 97% of files · features visible
Main hotspotdependencyMap.component.spec.tsdependencyMap.component.spec.ts

Conclusions:

  • The totals match. The area difference is only the minimum of 1 we give to the 79 files without rloc; the inspector still shows the real value.
  • The hotspots match. Both maps highlight dependencyMap.component.spec.ts, threeSceneService.ts, metricColorRangeDiagram.component.ts and fileValidator.ts. Our risk selector picks the same file CodeCharta labels (rloc 1,053, complexity 9, risk 100/100).
  • The hierarchy matches. app concentrates 102.4k rloc and 1,935 files; features ~51.2k.
  • Deliberate differences: 2D versus 3D, a neutral ramp in the Metric lens versus CodeCharta’s green-red, and composite risk by default. No data or render defects were found.
  • CodeCharta’s explorer percentage is the share of files, not of area.

No CodeCharta code or design was copied; the comparison was made on the same cc.json.

  • 3D. A later lens, never the default view.
  • Force-directed as the main view. Unreadable and non-deterministic.
  • Layout animations. The map must be comparable between runs.
  • An icon per language in each block. Noise.
  • Dark mode in v1. Yes, later.

The treemap is for exploring. It is no good for the PR comment: what must be shown is the violation, not the city.

Proposal for M2: a lanes view where each layer is a horizontal band, modules are placed inside and edges that go up are drawn in red. It is deterministic, reads in a 600 px screenshot and turns a finding into an actionable image.

Treemap to explore. Lanes to decide.

Open capture criteria live in the roadmap backlog (folders without the legend, dominant red, findings without zoom, selection without colour, black and white).

  1. Composite risk or raw complexity by default? The composite (complexity + churn + size) finds real hotspots, but it is a formula that must be explained and trusted. Raw complexity is immediate and honest, but ignores change.
  2. Treemap or lanes as the initial view? The treemap tells the project better; lanes tell the problem better.
  3. Incoming edges by default? They add context (who uses you) and noise. In the prototype they are on; perhaps they should be opt-in.