Added documentation for HierarchicalCanvasGraph.py
This commit is contained in:
706
docs/HierarchicalCanvasGraph.md
Normal file
706
docs/HierarchicalCanvasGraph.md
Normal file
@@ -0,0 +1,706 @@
|
||||
# HierarchicalCanvasGraph
|
||||
|
||||
## Introduction
|
||||
|
||||
The HierarchicalCanvasGraph component provides a canvas-based hierarchical graph visualization with interactive features. It displays nodes and edges in a tree layout using the Reingold-Tilford algorithm, with built-in support for expand/collapse, zoom/pan, and filtering.
|
||||
|
||||
**Key features:**
|
||||
|
||||
- Canvas-based rendering for smooth performance with large graphs
|
||||
- Expand/collapse nodes with children
|
||||
- Zoom and pan with mouse wheel and drag
|
||||
- Search/filter nodes by text, type, or kind
|
||||
- Click to select nodes
|
||||
- Stable zoom maintained on container resize
|
||||
- Persistent state (collapsed nodes, view transform, filters)
|
||||
- Event handlers for node interactions
|
||||
|
||||
**Common use cases:**
|
||||
|
||||
- Visualizing class hierarchies and inheritance trees
|
||||
- Displaying dependency graphs
|
||||
- Exploring file/folder structures
|
||||
- Showing organizational charts
|
||||
- Debugging object instance relationships
|
||||
|
||||
## Quick Start
|
||||
|
||||
Here's a minimal example showing a simple hierarchy:
|
||||
|
||||
```python
|
||||
from fasthtml.common import *
|
||||
from myfasthtml.controls.HierarchicalCanvasGraph import (
|
||||
HierarchicalCanvasGraph,
|
||||
HierarchicalCanvasGraphConf
|
||||
)
|
||||
from myfasthtml.core.instances import RootInstance
|
||||
from myfasthtml.myfastapp import create_app
|
||||
|
||||
app, rt = create_app()
|
||||
|
||||
@rt("/")
|
||||
def index(session):
|
||||
root = RootInstance(session)
|
||||
|
||||
# Define nodes and edges
|
||||
nodes = [
|
||||
{"id": "root", "label": "Root", "description": "Base class", "type": "Class", "kind": "base"},
|
||||
{"id": "child1", "label": "Child 1", "description": "First derived class", "type": "Class", "kind": "derived"},
|
||||
{"id": "child2", "label": "Child 2", "description": "Second derived class", "type": "Class", "kind": "derived"},
|
||||
]
|
||||
|
||||
edges = [
|
||||
{"from": "root", "to": "child1"},
|
||||
{"from": "root", "to": "child2"},
|
||||
]
|
||||
|
||||
# Create graph
|
||||
conf = HierarchicalCanvasGraphConf(nodes=nodes, edges=edges)
|
||||
graph = HierarchicalCanvasGraph(root, conf)
|
||||
|
||||
return Titled("Graph Example", graph)
|
||||
|
||||
serve()
|
||||
```
|
||||
|
||||
This creates a complete hierarchical graph with:
|
||||
|
||||
- Three nodes in a simple parent-child relationship
|
||||
- Automatic tree layout with expand/collapse controls
|
||||
- Built-in zoom/pan navigation
|
||||
- Search filter bar
|
||||
|
||||
**Note:** The graph automatically saves collapsed state and zoom/pan position across sessions.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Visual Structure
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Filter instances... [x] │ ← Query filter bar
|
||||
├─────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ Root │ [±] │ ← Node label (bold)
|
||||
│ │ Description │ │ ← Description (gray, smaller)
|
||||
│ └──────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────┴─────┐ │
|
||||
│ │ │ │
|
||||
│ ┌─────────┐ ┌─────────┐ │ ← Child nodes
|
||||
│ │Child 1 │ │Child 2 │ │
|
||||
│ │Details │ │Details │ │
|
||||
│ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
│ • Dot grid background │ ← Canvas area
|
||||
│ • Mouse wheel to zoom │
|
||||
│ • Drag to pan │
|
||||
│ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Creating the Graph
|
||||
|
||||
**Step 1: Define nodes**
|
||||
|
||||
Each node is a dictionary with:
|
||||
|
||||
```python
|
||||
nodes = [
|
||||
{
|
||||
"id": "unique_id", # Required: unique identifier
|
||||
"label": "Display Name", # Required: shown in the node (main line)
|
||||
"description": "Details...", # Optional: shown below label (smaller, gray)
|
||||
"type": "ClassName", # Optional: shown as badge
|
||||
"kind": "category", # Optional: affects border color
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Note:** Nodes with `description` are taller (54px vs 36px) to accommodate the second line of text.
|
||||
|
||||
**Step 2: Define edges**
|
||||
|
||||
Each edge connects two nodes:
|
||||
|
||||
```python
|
||||
edges = [
|
||||
{"from": "parent_id", "to": "child_id"}
|
||||
]
|
||||
```
|
||||
|
||||
**Step 3: Create configuration**
|
||||
|
||||
```python
|
||||
from myfasthtml.controls.HierarchicalCanvasGraph import HierarchicalCanvasGraphConf
|
||||
|
||||
conf = HierarchicalCanvasGraphConf(
|
||||
nodes=nodes,
|
||||
edges=edges,
|
||||
events_handlers=None # Optional: dict of event handlers
|
||||
)
|
||||
```
|
||||
|
||||
**Step 4: Instantiate component**
|
||||
|
||||
```python
|
||||
graph = HierarchicalCanvasGraph(parent, conf, _id="custom-id")
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
| Parameter | Type | Description | Default |
|
||||
|-------------------|-----------------------|--------------------------------------------------|---------|
|
||||
| `nodes` | `list[dict]` | List of node dictionaries | - |
|
||||
| `edges` | `list[dict]` | List of edge dictionaries | - |
|
||||
| `events_handlers` | `Optional[dict]` | Event name to Command object mapping | `None` |
|
||||
|
||||
### Node Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|---------------|-------|----------|------------------------------------------------|
|
||||
| `id` | `str` | Yes | Unique node identifier |
|
||||
| `label` | `str` | Yes | Display text shown in the node (main line) |
|
||||
| `description` | `str` | No | Secondary text shown below label (smaller, gray) |
|
||||
| `type` | `str` | No | Type badge shown on node (clickable filter) |
|
||||
| `kind` | `str` | No | Category determining border color/style |
|
||||
|
||||
### Edge Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|-------|----------|--------------------------------|
|
||||
| `from` | `str` | Yes | Source node ID |
|
||||
| `to` | `str` | Yes | Target node ID |
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Filtering
|
||||
|
||||
The component provides three types of filtering:
|
||||
|
||||
**1. Text search** (via filter bar)
|
||||
|
||||
Searches in node `id`, `label`, `type`, and `kind` fields:
|
||||
|
||||
```python
|
||||
# User types in filter bar
|
||||
# Automatically filters matching nodes
|
||||
# Non-matching nodes are dimmed
|
||||
```
|
||||
|
||||
**2. Type filter** (click node badge)
|
||||
|
||||
Click a node's type badge to filter by that type:
|
||||
|
||||
```python
|
||||
# Clicking "Class" badge shows only nodes with type="Class"
|
||||
# Clicking again clears the filter
|
||||
```
|
||||
|
||||
**3. Kind filter** (click node border)
|
||||
|
||||
Click a node's border to filter by kind:
|
||||
|
||||
```python
|
||||
# Clicking border of kind="base" shows only base nodes
|
||||
# Clicking again clears the filter
|
||||
```
|
||||
|
||||
**Filter behavior:**
|
||||
|
||||
- Only one filter type active at a time
|
||||
- Filters dim non-matching nodes (opacity reduced)
|
||||
- Matching nodes and their ancestors remain fully visible
|
||||
- Clear filter by clicking same badge/border or clearing search text
|
||||
|
||||
### Events
|
||||
|
||||
The component supports two event types via `events_handlers`:
|
||||
|
||||
**select_node**: Fired when clicking a node (not the toggle button)
|
||||
|
||||
```python
|
||||
from myfasthtml.core.commands import Command
|
||||
|
||||
def on_select(node_id=None):
|
||||
print(f"Selected node: {node_id}")
|
||||
return f"Selected: {node_id}"
|
||||
|
||||
conf = HierarchicalCanvasGraphConf(
|
||||
nodes=nodes,
|
||||
edges=edges,
|
||||
events_handlers={
|
||||
"select_node": Command(
|
||||
"SelectNode",
|
||||
"Handle node selection",
|
||||
on_select
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**toggle_node**: Fired when clicking expand/collapse button
|
||||
|
||||
```python
|
||||
def on_toggle(node_id=None):
|
||||
print(f"Toggled node: {node_id}")
|
||||
return "Toggled"
|
||||
|
||||
conf = HierarchicalCanvasGraphConf(
|
||||
nodes=nodes,
|
||||
edges=edges,
|
||||
events_handlers={
|
||||
"toggle_node": Command(
|
||||
"ToggleNode",
|
||||
"Handle node toggle",
|
||||
on_toggle
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### State Persistence
|
||||
|
||||
The graph automatically persists:
|
||||
|
||||
**Collapsed nodes:**
|
||||
```python
|
||||
# Get current collapsed state
|
||||
collapsed = graph.get_state().collapsed # List of node IDs
|
||||
|
||||
# Programmatically set collapsed nodes
|
||||
graph.set_collapsed({"node1", "node2"})
|
||||
|
||||
# Toggle a specific node
|
||||
graph.toggle_node("node1")
|
||||
```
|
||||
|
||||
**View transform (zoom/pan):**
|
||||
```python
|
||||
# Get current transform
|
||||
transform = graph.get_state().transform
|
||||
# {"x": 100, "y": 50, "scale": 1.5}
|
||||
```
|
||||
|
||||
**Layout mode:**
|
||||
```python
|
||||
# Get current layout mode
|
||||
mode = graph.get_state().layout_mode # "horizontal" or "vertical"
|
||||
```
|
||||
|
||||
**Current selection:**
|
||||
```python
|
||||
# Get selected node ID
|
||||
selected = graph.get_selected_id() # Returns node ID or None
|
||||
```
|
||||
|
||||
### Layout Modes
|
||||
|
||||
The graph supports two layout orientations:
|
||||
|
||||
- **horizontal**: Nodes flow left-to-right (default)
|
||||
- **vertical**: Nodes flow top-to-bottom
|
||||
|
||||
Users can toggle between modes using the UI controls. The mode is persisted in state.
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Simple Class Hierarchy
|
||||
|
||||
Basic inheritance tree visualization:
|
||||
|
||||
```python
|
||||
from fasthtml.common import *
|
||||
from myfasthtml.controls.HierarchicalCanvasGraph import (
|
||||
HierarchicalCanvasGraph,
|
||||
HierarchicalCanvasGraphConf
|
||||
)
|
||||
from myfasthtml.core.instances import RootInstance
|
||||
from myfasthtml.myfastapp import create_app
|
||||
|
||||
app, rt = create_app()
|
||||
|
||||
@rt("/")
|
||||
def index(session):
|
||||
root = RootInstance(session)
|
||||
|
||||
nodes = [
|
||||
{"id": "object", "label": "Object", "description": "Base class for all objects", "type": "BaseClass", "kind": "builtin"},
|
||||
{"id": "animal", "label": "Animal", "description": "Abstract animal class", "type": "Class", "kind": "abstract"},
|
||||
{"id": "dog", "label": "Dog", "description": "Canine implementation", "type": "Class", "kind": "concrete"},
|
||||
{"id": "cat", "label": "Cat", "description": "Feline implementation", "type": "Class", "kind": "concrete"},
|
||||
]
|
||||
|
||||
edges = [
|
||||
{"from": "object", "to": "animal"},
|
||||
{"from": "animal", "to": "dog"},
|
||||
{"from": "animal", "to": "cat"},
|
||||
]
|
||||
|
||||
conf = HierarchicalCanvasGraphConf(nodes=nodes, edges=edges)
|
||||
graph = HierarchicalCanvasGraph(root, conf)
|
||||
|
||||
return Titled("Class Hierarchy", graph)
|
||||
|
||||
serve()
|
||||
```
|
||||
|
||||
### Example 2: Graph with Event Handlers
|
||||
|
||||
Handling node selection and expansion:
|
||||
|
||||
```python
|
||||
from fasthtml.common import *
|
||||
from myfasthtml.controls.HierarchicalCanvasGraph import (
|
||||
HierarchicalCanvasGraph,
|
||||
HierarchicalCanvasGraphConf
|
||||
)
|
||||
from myfasthtml.core.instances import RootInstance
|
||||
from myfasthtml.core.commands import Command
|
||||
from myfasthtml.myfastapp import create_app
|
||||
|
||||
app, rt = create_app()
|
||||
|
||||
@rt("/")
|
||||
def index(session):
|
||||
root = RootInstance(session)
|
||||
|
||||
# Event handlers
|
||||
def on_select(node_id=None):
|
||||
return Div(
|
||||
f"Selected: {node_id}",
|
||||
id="selection-info",
|
||||
cls="alert alert-info"
|
||||
)
|
||||
|
||||
def on_toggle(node_id=None):
|
||||
return Div(
|
||||
f"Toggled: {node_id}",
|
||||
id="toggle-info",
|
||||
cls="alert alert-success"
|
||||
)
|
||||
|
||||
# Create commands
|
||||
select_cmd = Command("SelectNode", "Handle selection", on_select)
|
||||
toggle_cmd = Command("ToggleNode", "Handle toggle", on_toggle)
|
||||
|
||||
nodes = [
|
||||
{"id": "root", "label": "Root Module", "description": "Main package entry point", "type": "Module", "kind": "package"},
|
||||
{"id": "sub1", "label": "Submodule A", "description": "Feature A implementation", "type": "Module", "kind": "module"},
|
||||
{"id": "sub2", "label": "Submodule B", "description": "Feature B implementation", "type": "Module", "kind": "module"},
|
||||
{"id": "class1", "label": "ClassA", "description": "Handler for feature A", "type": "Class", "kind": "class"},
|
||||
{"id": "class2", "label": "ClassB", "description": "Handler for feature B", "type": "Class", "kind": "class"},
|
||||
]
|
||||
|
||||
edges = [
|
||||
{"from": "root", "to": "sub1"},
|
||||
{"from": "root", "to": "sub2"},
|
||||
{"from": "sub1", "to": "class1"},
|
||||
{"from": "sub2", "to": "class2"},
|
||||
]
|
||||
|
||||
conf = HierarchicalCanvasGraphConf(
|
||||
nodes=nodes,
|
||||
edges=edges,
|
||||
events_handlers={
|
||||
"select_node": select_cmd,
|
||||
"toggle_node": toggle_cmd
|
||||
}
|
||||
)
|
||||
|
||||
graph = HierarchicalCanvasGraph(root, conf)
|
||||
|
||||
return Titled("Interactive Graph",
|
||||
graph,
|
||||
Div(id="selection-info"),
|
||||
Div(id="toggle-info")
|
||||
)
|
||||
|
||||
serve()
|
||||
```
|
||||
|
||||
### Example 3: Filtered Graph with Type/Kind Badges
|
||||
|
||||
Graph with multiple node types for filtering:
|
||||
|
||||
```python
|
||||
from fasthtml.common import *
|
||||
from myfasthtml.controls.HierarchicalCanvasGraph import (
|
||||
HierarchicalCanvasGraph,
|
||||
HierarchicalCanvasGraphConf
|
||||
)
|
||||
from myfasthtml.core.instances import RootInstance
|
||||
from myfasthtml.myfastapp import create_app
|
||||
|
||||
app, rt = create_app()
|
||||
|
||||
@rt("/")
|
||||
def index(session):
|
||||
root = RootInstance(session)
|
||||
|
||||
nodes = [
|
||||
# Controllers
|
||||
{"id": "ctrl1", "label": "UserController", "description": "Handles user requests", "type": "Controller", "kind": "web"},
|
||||
{"id": "ctrl2", "label": "AdminController", "description": "Admin panel endpoints", "type": "Controller", "kind": "web"},
|
||||
|
||||
# Services
|
||||
{"id": "svc1", "label": "AuthService", "description": "Authentication logic", "type": "Service", "kind": "business"},
|
||||
{"id": "svc2", "label": "EmailService", "description": "Email notifications", "type": "Service", "kind": "infrastructure"},
|
||||
|
||||
# Repositories
|
||||
{"id": "repo1", "label": "UserRepo", "description": "User data access", "type": "Repository", "kind": "data"},
|
||||
{"id": "repo2", "label": "LogRepo", "description": "Logging data access", "type": "Repository", "kind": "data"},
|
||||
]
|
||||
|
||||
edges = [
|
||||
{"from": "ctrl1", "to": "svc1"},
|
||||
{"from": "ctrl2", "to": "svc1"},
|
||||
{"from": "svc1", "to": "repo1"},
|
||||
{"from": "svc2", "to": "repo2"},
|
||||
]
|
||||
|
||||
conf = HierarchicalCanvasGraphConf(nodes=nodes, edges=edges)
|
||||
graph = HierarchicalCanvasGraph(root, conf)
|
||||
|
||||
return Titled("Dependency Graph",
|
||||
Div(
|
||||
P("Click on type badges (Controller, Service, Repository) to filter by type"),
|
||||
P("Click on node borders to filter by kind (web, business, infrastructure, data)"),
|
||||
P("Use search bar to filter by text"),
|
||||
cls="mb-4"
|
||||
),
|
||||
graph
|
||||
)
|
||||
|
||||
serve()
|
||||
```
|
||||
|
||||
### Example 4: Programmatic State Control
|
||||
|
||||
Controlling collapsed state and getting selection:
|
||||
|
||||
```python
|
||||
from fasthtml.common import *
|
||||
from myfasthtml.controls.HierarchicalCanvasGraph import (
|
||||
HierarchicalCanvasGraph,
|
||||
HierarchicalCanvasGraphConf
|
||||
)
|
||||
from myfasthtml.controls.helpers import mk
|
||||
from myfasthtml.core.instances import RootInstance
|
||||
from myfasthtml.core.commands import Command
|
||||
from myfasthtml.myfastapp import create_app
|
||||
|
||||
app, rt = create_app()
|
||||
|
||||
@rt("/")
|
||||
def index(session):
|
||||
root = RootInstance(session)
|
||||
|
||||
nodes = [
|
||||
{"id": "root", "label": "Project", "description": "Root directory", "type": "Folder", "kind": "root"},
|
||||
{"id": "src", "label": "src/", "description": "Source code", "type": "Folder", "kind": "folder"},
|
||||
{"id": "tests", "label": "tests/", "description": "Test suite", "type": "Folder", "kind": "folder"},
|
||||
{"id": "main", "label": "main.py", "description": "Application entry point", "type": "File", "kind": "python"},
|
||||
{"id": "utils", "label": "utils.py", "description": "Utility functions", "type": "File", "kind": "python"},
|
||||
{"id": "test1", "label": "test_main.py", "description": "Main module tests", "type": "File", "kind": "test"},
|
||||
]
|
||||
|
||||
edges = [
|
||||
{"from": "root", "to": "src"},
|
||||
{"from": "root", "to": "tests"},
|
||||
{"from": "src", "to": "main"},
|
||||
{"from": "src", "to": "utils"},
|
||||
{"from": "tests", "to": "test1"},
|
||||
]
|
||||
|
||||
conf = HierarchicalCanvasGraphConf(nodes=nodes, edges=edges)
|
||||
graph = HierarchicalCanvasGraph(root, conf, _id="file-tree")
|
||||
|
||||
# Commands to control graph
|
||||
def collapse_all():
|
||||
graph.set_collapsed({"root", "src", "tests"})
|
||||
return graph
|
||||
|
||||
def expand_all():
|
||||
graph.set_collapsed(set())
|
||||
return graph
|
||||
|
||||
def show_selection():
|
||||
selected = graph.get_selected_id()
|
||||
if selected:
|
||||
return Div(f"Selected: {selected}", id="info", cls="alert alert-info")
|
||||
return Div("No selection", id="info", cls="alert alert-warning")
|
||||
|
||||
collapse_cmd = Command("CollapseAll", "Collapse all nodes", collapse_all)
|
||||
expand_cmd = Command("ExpandAll", "Expand all nodes", expand_all)
|
||||
selection_cmd = Command("ShowSelection", "Show selection", show_selection)
|
||||
|
||||
return Titled("File Tree",
|
||||
Div(
|
||||
mk.button("Collapse All", command=collapse_cmd.htmx(target="#file-tree")),
|
||||
mk.button("Expand All", command=expand_cmd.htmx(target="#file-tree")),
|
||||
mk.button("Show Selection", command=selection_cmd.htmx(target="#info")),
|
||||
cls="flex gap-2 mb-4"
|
||||
),
|
||||
Div(id="info"),
|
||||
graph
|
||||
)
|
||||
|
||||
serve()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Developer Reference
|
||||
|
||||
This section contains technical details for developers working on the HierarchicalCanvasGraph component itself.
|
||||
|
||||
### State
|
||||
|
||||
The component uses `HierarchicalCanvasGraphState` (inherits from `DbObject`) for persistence.
|
||||
|
||||
| Name | Type | Description | Default | Persisted |
|
||||
|-----------------|-------------------|--------------------------------------------------|--------------|-----------|
|
||||
| `collapsed` | `list[str]` | List of collapsed node IDs | `[]` | Yes |
|
||||
| `transform` | `dict` | Zoom/pan transform: `{x, y, scale}` | See below | Yes |
|
||||
| `layout_mode` | `str` | Layout orientation: "horizontal" or "vertical" | `"horizontal"` | Yes |
|
||||
| `filter_text` | `Optional[str]` | Text search filter | `None` | Yes |
|
||||
| `filter_type` | `Optional[str]` | Type filter (from badge click) | `None` | Yes |
|
||||
| `filter_kind` | `Optional[str]` | Kind filter (from border click) | `None` | Yes |
|
||||
| `ns_selected_id` | `Optional[str]` | Currently selected node ID (ephemeral) | `None` | No |
|
||||
|
||||
**Default transform:**
|
||||
```python
|
||||
{"x": 0, "y": 0, "scale": 1}
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
Internal commands (managed by `Commands` class inheriting from `BaseCommands`):
|
||||
|
||||
| Name | Description | Internal |
|
||||
|----------------------|--------------------------------------------------|----------|
|
||||
| `update_view_state()` | Update view transform and layout mode | Yes |
|
||||
| `apply_filter()` | Apply current filter and update graph display | Yes |
|
||||
|
||||
### Public Methods
|
||||
|
||||
| Method | Description | Returns |
|
||||
|--------------------------------|------------------------------------------|----------------------------|
|
||||
| `get_state()` | Get the persistent state object | `HierarchicalCanvasGraphState` |
|
||||
| `get_selected_id()` | Get currently selected node ID | `Optional[str]` |
|
||||
| `set_collapsed(node_ids: set)` | Set collapsed state of nodes | `None` |
|
||||
| `toggle_node(node_id: str)` | Toggle collapsed state of a node | `self` |
|
||||
| `render()` | Render the complete component | `Div` |
|
||||
|
||||
### Constructor Parameters
|
||||
|
||||
| Parameter | Type | Description | Default |
|
||||
|------------|-----------------------------------|------------------------------------|---------|
|
||||
| `parent` | Instance | Parent instance (required) | - |
|
||||
| `conf` | `HierarchicalCanvasGraphConf` | Configuration object | - |
|
||||
| `_id` | `Optional[str]` | Custom ID (auto-generated if None) | `None` |
|
||||
|
||||
### High Level Hierarchical Structure
|
||||
|
||||
```
|
||||
Div(id="{id}", cls="mf-hierarchical-canvas-graph")
|
||||
├── Query(id="{id}-query")
|
||||
│ └── [Filter input and controls]
|
||||
├── Div(id="{id}_container", cls="mf-hcg-container")
|
||||
│ └── [Canvas element - created by JS]
|
||||
└── Script
|
||||
└── initHierarchicalCanvasGraph('{id}_container', options)
|
||||
```
|
||||
|
||||
### Element IDs
|
||||
|
||||
| Name | Description |
|
||||
|----------------------|------------------------------------------|
|
||||
| `{id}` | Root container div |
|
||||
| `{id}-query` | Query filter component |
|
||||
| `{id}_container` | Canvas container (sized by JS) |
|
||||
|
||||
**Note:** `{id}` is the instance ID (auto-generated or custom `_id`).
|
||||
|
||||
### CSS Classes
|
||||
|
||||
| Class | Element |
|
||||
|------------------------------------|-----------------------------------|
|
||||
| `mf-hierarchical-canvas-graph` | Root container |
|
||||
| `mf-hcg-container` | Canvas container div |
|
||||
|
||||
### Internal Methods
|
||||
|
||||
| Method | Description | Returns |
|
||||
|-------------------------------------|--------------------------------------------------|-------------------|
|
||||
| `_handle_update_view_state()` | Update view state from client | `str` (empty) |
|
||||
| `_handle_apply_filter()` | Apply filter and re-render graph | `self` |
|
||||
| `_calculate_filtered_nodes()` | Calculate which nodes match current filter | `Optional[list[str]]` |
|
||||
| `_prepare_options()` | Prepare JavaScript options object | `dict` |
|
||||
|
||||
### JavaScript Interface
|
||||
|
||||
The component calls `initHierarchicalCanvasGraph(containerId, options)` where `options` is:
|
||||
|
||||
```javascript
|
||||
{
|
||||
nodes: [...], // Array of node objects {id, label, description?, type?, kind?}
|
||||
edges: [...], // Array of edge objects {from, to}
|
||||
collapsed: [...], // Array of collapsed node IDs
|
||||
transform: {x, y, scale}, // Current view transform
|
||||
layout_mode: "horizontal", // Layout orientation
|
||||
filtered_nodes: [...], // Array of visible node IDs (null if no filter)
|
||||
events: {
|
||||
_internal_update_state: {...}, // HTMX options for state updates
|
||||
_internal_filter_by_type: {...}, // HTMX options for type filter
|
||||
_internal_filter_by_kind: {...}, // HTMX options for kind filter
|
||||
select_node: {...}, // User event handler (optional)
|
||||
toggle_node: {...} // User event handler (optional)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Node object structure:**
|
||||
- `id` (required): Unique identifier
|
||||
- `label` (required): Main display text
|
||||
- `description` (optional): Secondary text shown below label in smaller gray font
|
||||
- `type` (optional): Type badge shown on node
|
||||
- `kind` (optional): Category affecting border color (root|single|unique|multiple)
|
||||
|
||||
### Event Flow
|
||||
|
||||
**Node selection:**
|
||||
1. User clicks node (not toggle button)
|
||||
2. JS calls HTMX with `node_id` parameter
|
||||
3. `select_node` handler executes (if defined)
|
||||
4. Result updates target element
|
||||
|
||||
**Node toggle:**
|
||||
1. User clicks expand/collapse button
|
||||
2. JS updates local collapsed state
|
||||
3. JS calls `_internal_update_state` HTMX endpoint
|
||||
4. State persisted in `HierarchicalCanvasGraphState.collapsed`
|
||||
5. `toggle_node` handler executes (if defined)
|
||||
|
||||
**Filter changes:**
|
||||
1. User types in search / clicks badge / clicks border
|
||||
2. Query component or JS triggers `apply_filter()` command
|
||||
3. `_handle_apply_filter()` updates state filters
|
||||
4. Component re-renders with filtered nodes
|
||||
5. JS dims non-matching nodes
|
||||
|
||||
### Dependencies
|
||||
|
||||
**Python:**
|
||||
- `fasthtml.components.Div`
|
||||
- `fasthtml.xtend.Script`
|
||||
- `myfasthtml.controls.Query` - Filter search bar
|
||||
- `myfasthtml.core.instances.MultipleInstance` - Instance management
|
||||
- `myfasthtml.core.dbmanager.DbObject` - State persistence
|
||||
|
||||
**JavaScript:**
|
||||
- `initHierarchicalCanvasGraph()` - Must be loaded via MyFastHtml assets
|
||||
- Canvas API - For rendering
|
||||
- HTMX - For server communication
|
||||
Reference in New Issue
Block a user