mcpField
V
visualize-semantic-model

visualize-semantic-model

github.com/revosaiData & databases0123 installsUpdated 5d ago
Repository

About visualize-semantic-model

visualize-semantic-model is a skill that turns Cube.dev YAML definitions into a dark-themed directed graph PNG. It parses joins and cardinalities, identifies a fact spine, and uses a bundled renderer for consistent diagrams.

Key features

  1. 01Parse Cube.dev YAML definitions
  2. 02Detect the fact spine and joins
  3. 03Label edges with join keys and cardinality
  4. 04Render a dark-themed graph as PNG
  5. 05Display the generated image inline

Use cases

  1. 01Inspect relationships across Cube models
  2. 02Review fact and dimension connections
  3. 03Generate a diagram from cube YAML files

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/revosai/skills --skill visualize-semantic-model

After installing, type/visualize-semantic-modelin Claude Code to confirm it loaded.

Bundled filesscripts/render_graph.py

Details

From the project README

Visualize Semantic Model

Render a dark-themed directed graph of a Cube.dev model by parsing cube YAML files, detecting the fact spine, then invoking the bundled renderer with a JSON spec.

The renderer (scripts/render_graph.py) is pure layout + drawing — it takes a graph spec on stdin and writes the PNG. Keeping it bundled means every run produces visually consistent output and you don't reinvent matplotlib code each time.


Step 1: Resolve the cubes folder

If the user passed a folder argument, use it. Otherwise default to cubes/. If the folder doesn't exist, ask the user where the cube definitions live before proceeding.

find <folder> -name "*.yml" -not -name "model-graph.*" | sort

Step 2: Parse the cube graph

For each .yml file extract:

  • spec.name — the cube identifier referenced by ${CUBE} and joins (metadata.name is the local slug, normally identical — fall back to it if spec.name is absent)
  • spec.meta.icon — icon key if present (catalog key, url:…, or data:…); null if absent
  • spec.joins — map of target_cube → { relationship, sql }
  • A short join-key label parsed from each join's sql

Join-key label rules. Strip ${CUBE}. and ${<target>}. prefixes. For a single equality, use the LHS column. For composite keys (multiple AND), join the LHS column names with +.

sql: "${CUBE}.user_id = ${users}.user_id"
# → "user_id"

sql: "${CUBE}.traffic_source = ${rev}.traffic_source AND ${CUBE}.order_date = ${rev}.order_date"
# → "traffic_source + order_date"

Cardinality (always render edges from the one side to the many side):

Declared on this cubeThis cube's sideOther cube's side
many_to_one∞1
one_to_many1∞
one_to_one11

Fact-spine detection. The cube with the most many_to_one joins (it holds the FKs) is the spine. Tie-break by preferring names containing enriched, fact, or items. If no cube has any many_to_one joins (e.g. the model is all one_to_many from a hub), fall back to the cube with the most outgoing joins of any kind. If still ambiguous, ask the user which cube to treat as the spine.

Edge cases — stop and tell the user instead of rendering:

  • The folder contains fewer than 2 cubes → at least two cubes are needed.
  • No joins found anywhere → there are no relationships to draw.

Step 3: Build the graph spec

Build a JSON object the renderer understands:

{
  "title": "Semantic Model — <project_name>",
  "fact_spine": {
    "name": "<fact_cube>",
    "icon": "<catalog_key_or_null>",
    "pk": "<pk_col>",
    "fks": ["fk_a", "fk_b", "fk_c", "fk_d"]
  },
  "dimensions": [
    {
      "name": "<dim_cube>",
      "icon": "<catalog_key_or_null>",
      "pk": "<pk_col>",
      "extras": ["metric1", "metric2"]
    }
  ],
  "edges": [
    {
      "from": "<dim_cube>",
      "to": "<fact_cube>",
      "label": "<join_key>",
      "from_card": "1",
      "to_card": "∞"
    }
  ]
}

from_card / to_card are the labels rendered at each end of the arrow — "1" or "∞". For one_to_one joins both ends are "1".

The icon field is captured in the spec for future renderer support — the current render_graph.py does not draw icons yet. Include it so the data is available when the renderer is upgraded.

The arrow always points from dimension to fact, regardless of how the relationship was declared on the YAML.


Step 4: Render

python3 -c "import matplotlib" 2>/dev/null || python3 -m pip install matplotlib --quiet

python3 .claude/skills/visualize-semantic-model/scripts/render_graph.py \
  --output <folder>/model-graph.png \
  <<'EOF'
{ ... the JSON spec from Step 3 ... }
EOF

If <folder>/model-graph.png already exists and the user did not explicitly ask to regenerate, ask before overwriting.


Step 5: Show the result

Use the Read tool (not a Python Read() call — Read is a Claude Code tool) on the PNG path so the image renders inline in chat:

Read <folder>/model-graph.png


Final response template

Generated: <folder>/model-graph.png

Cubes visualized: <n>
Fact spine:       <cube_name>
Dimensions:       <dim1>, <dim2>, ...
Edges:            <dim1> → <fact>  (<join_key>)  [1:∞]
                  <dim2> → <fact>  (<join_key>)  [1:∞]

Rules

  • Always parse YAML — never hardcode cube names or relationships.
  • Edge direction is always dimension → fact (arrow tail at dimension, head at fact).
  • The 1 and ∞ markers are positioned by the renderer based on from_card/to_card; set them correctly per the cardinality table above.
  • Do not overwrite an existing model-graph.png without confirming.
  • After saving, always show the image inline using the Read tool.

Comments

Markdown supported