
Obsidian Canvas Creator
- 1.7k installs
- 3.3k repo stars
- Updated June 26, 2026
- axtonliu/axton-obsidian-visual-skills
obsidian-canvas-creator converts text into Obsidian Canvas files with MindMap or freeform layouts.
About
The obsidian-canvas-creator skill transforms text content into structured Obsidian Canvas JSON files supporting MindMap and freeform layouts. Workflow analyzes input for topics, hierarchy, key points, and existing structure (headings, lists), then maps nodes and edges into canvas coordinates. Use when users want canvases, mind maps, visual diagrams from articles or notes, or spatial organization in Obsidian format. Agents preserve hierarchical relationships in MindMap mode or allow flexible positioning in freeform mode. Output is importable .canvas files compatible with Obsidian Canvas plugin. Converts text to Obsidian Canvas JSON with MindMap or freeform layouts Analyzes headings, lists, and hierarchy before node placement Supports mind maps, visual diagrams, and spatial note organization Output importable as Obsidian .canvas files Triggered by canvas, mind map, or Obsidian Canvas visualization requests obsidian-canvas-creator converts text into Obsidian Canvas files with MindMap or freeform layouts Structured .canvas file with nodes, links, and chosen layout mode User wants Obsidian Canvas, mind map from text, or spatial note layout Obsidian users visualizing notes, outlines, or.
- Converts text to Obsidian Canvas JSON with MindMap or freeform layouts.
- Analyzes headings, lists, and hierarchy before node placement.
- Supports mind maps, visual diagrams, and spatial note organization.
- Output importable as Obsidian .canvas files.
- Triggered by canvas, mind map, or Obsidian Canvas visualization requests.
Obsidian Canvas Creator by the numbers
- 1,703 all-time installs (skills.sh)
- +33 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #317 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
obsidian-canvas-creator capabilities & compatibility
- Capabilities
- content analysis · mindmap layout · freeform layout · canvas json export · hierarchy preservation
- Works with
- obsidian
- Use cases
- documentation · planning
npx skills add https://github.com/axtonliu/axton-obsidian-visual-skills --skill obsidian-canvas-creatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.7k |
|---|---|
| repo stars | ★ 3.3k |
| Security audit | 2 / 3 scanners passed |
| Last updated | June 26, 2026 |
| Repository | axtonliu/axton-obsidian-visual-skills ↗ |
How do I turn notes or an article into an Obsidian Canvas mind map?
Create Obsidian Canvas files from text with MindMap or freeform layouts for spatial note organization.
Who is it for?
Obsidian users visualizing notes, outlines, or articles as canvases.
Skip if: Non-Obsidian diagram tools or live collaborative whiteboards.
When should I use this skill?
User wants Obsidian Canvas, mind map from text, or spatial note layout.
What you get
Structured .canvas file with nodes, links, and chosen layout mode.
- Obsidian .canvas JSON file
- Grouped concept nodes
- Positioned text blocks
Files
Obsidian Canvas Creator
Transform text content into structured Obsidian Canvas files with support for MindMap and freeform layouts.
When to Use This Skill
- User requests to create a canvas, mind map, or visual diagram from text
- User wants to organize information spatially
- User mentions "Obsidian Canvas" or similar visualization tools
- Converting structured content (articles, notes, outlines) into visual format
Core Workflow
1. Analyze Content
Read and understand the input content:
- Identify main topics and hierarchical relationships
- Extract key points, facts, and supporting details
- Note any existing structure (headings, lists, sections)
2. Determine Layout Type
Ask user to choose or infer from context:
MindMap Layout:
- Radial structure from center
- Parent-child relationships
- Clear hierarchy
- Good for: brainstorming, topic exploration, hierarchical content
Freeform Layout:
- Custom positioning
- Flexible relationships
- Multiple connection types
- Good for: complex networks, non-hierarchical content, custom arrangements
3. Plan Structure
For MindMap:
- Identify central concept (root node)
- Map primary branches (main topics)
- Organize secondary branches (subtopics)
- Position leaf nodes (details)
For Freeform:
- Group related concepts
- Identify connection patterns
- Plan spatial zones
- Consider visual flow
4. Generate Canvas
Create JSON following the Canvas specification:
Node Creation:
- Assign unique 8-12 character hex IDs
- Set appropriate dimensions based on content length
- Apply consistent color schemes
- Ensure no coordinate overlaps
Edge Creation:
- Connect parent-child relationships
- Use appropriate arrow styles
- Add labels for complex relationships
- Choose line styles (straight for hierarchy, curved for cross-references)
Grouping (Optional):
- Create visual containers for related nodes
- Use subtle background colors
- Add descriptive labels
5. Apply Layout Algorithm
MindMap Layout Calculations:
Refer to references/layout-algorithms.md for detailed algorithms. Key principles:
- Center root at (0, 0)
- Distribute primary nodes radially
- Space secondary nodes based on sibling count
- Maintain minimum spacing: 320px horizontal, 200px vertical
Freeform Layout Principles:
- Start with logical groupings
- Position groups with clear separation
- Connect across groups with curved edges
- Balance visual weight across canvas
6. Validate and Output
Before outputting:
Validation Checklist:
- All nodes have unique IDs
- No coordinate overlaps (check distance > node dimensions + spacing)
- All edges reference valid node IDs
- Groups (if any) have labels
- Colors use consistent format (hex or preset numbers)
- JSON is properly escaped (Chinese quotes: 『』 for double, 「」 for single)
Output Format:
- Complete, valid JSON Canvas file
- No additional explanation text
- Directly importable into Obsidian
Node Sizing Guidelines
Text Length-Based Sizing:
- Short text (<30 chars): 220 × 100 px
- Medium text (30-60 chars): 260 × 120 px
- Long text (60-100 chars): 320 × 140 px
- Very long text (>100 chars): 320 × 180 px
Color Schemes
Preset Colors (Recommended):
"1"- Red (warnings, important)"2"- Orange (action items)"3"- Yellow (questions, notes)"4"- Green (positive, completed)"5"- Cyan (information, details)"6"- Purple (concepts, abstract)
Custom Hex Colors: Use for brand consistency or specific themes. Always use uppercase format: "#4A90E2"
Critical Rules
1. Quote Handling:
- Chinese double quotes → 『』
- Chinese single quotes → 「」
- English double quotes →
\"
2. ID Generation:
- 8-12 character random hex strings
- Must be unique across all nodes and edges
3. Z-Index Order:
- Output groups first (bottom layer)
- Then subgroups
- Finally text/link nodes (top layer)
4. Spacing Requirements:
- Minimum horizontal: 320px between node centers
- Minimum vertical: 200px between node centers
- Account for node dimensions when calculating
5. JSON Structure:
- Top level contains only
nodesandedgesarrays - No extra wrapping objects
- No comments in output
6. No Emoji:
- Do not use any Emoji symbols in node text
- Use color coding or text labels for visual distinction instead
Examples
Simple MindMap Request
User: "Create a mind map about solar system planets"
Process: 1. Identify center: "Solar System" 2. Primary branches: Inner Planets, Outer Planets, Dwarf Planets 3. Secondary nodes: Individual planets with key facts 4. Apply radial layout 5. Generate JSON with proper spacing
Freeform Content Request
User: "Turn this article into a canvas" + [article text]
Process: 1. Extract article structure (intro, body sections, conclusion) 2. Identify key concepts and relationships 3. Group related sections spatially 4. Connect with labeled edges 5. Apply freeform layout with clear zones
Reference Documents
- Canvas Specification:
references/canvas-spec.md- Complete JSON Canvas format specification - Layout Algorithms:
references/layout-algorithms.md- Detailed positioning algorithms for both layout types
Load these references when:
- Need specification details for edge cases
- Implementing complex layout calculations
- Troubleshooting validation errors
Tips for Quality Canvases
1. Keep text concise: Each node should be scannable (<2 lines preferred) 2. Use hierarchy: Group by importance and relationship 3. Balance the canvas: Distribute nodes to avoid clustering 4. Strategic colors: Use colors to encode meaning, not just decoration 5. Meaningful connections: Only add edges that clarify relationships 6. Test in Obsidian: Verify the output opens correctly
Common Pitfalls to Avoid
- Overlapping nodes (always check distances)
- Inconsistent quote escaping (breaks JSON parsing)
- Missing group labels (causes sidebar navigation issues)
- Too much text in nodes (use file nodes for long content)
- Duplicate IDs (each must be unique)
- Unconnected nodes (unless intentional islands)
{
"nodes": [
{
"id": "group01",
"type": "group",
"x": -50,
"y": -50,
"width": 600,
"height": 400,
"label": "Group 1 - Core Concepts",
"color": "4"
},
{
"id": "group02",
"type": "group",
"x": 650,
"y": -50,
"width": 600,
"height": 400,
"label": "Group 2 - Applications",
"color": "5"
},
{
"id": "node01",
"type": "text",
"x": 0,
"y": 0,
"width": 240,
"height": 100,
"text": "Concept A",
"color": "4"
},
{
"id": "node02",
"type": "text",
"x": 290,
"y": 0,
"width": 240,
"height": 100,
"text": "Concept B",
"color": "4"
},
{
"id": "node03",
"type": "text",
"x": 0,
"y": 150,
"width": 240,
"height": 100,
"text": "Concept C",
"color": "4"
},
{
"id": "node04",
"type": "text",
"x": 290,
"y": 150,
"width": 240,
"height": 100,
"text": "Concept D",
"color": "4"
},
{
"id": "node05",
"type": "text",
"x": 700,
"y": 0,
"width": 240,
"height": 100,
"text": "Application 1",
"color": "5"
},
{
"id": "node06",
"type": "text",
"x": 990,
"y": 0,
"width": 240,
"height": 100,
"text": "Application 2",
"color": "5"
},
{
"id": "node07",
"type": "text",
"x": 700,
"y": 150,
"width": 240,
"height": 100,
"text": "Application 3",
"color": "5"
}
],
"edges": [
{
"id": "e1",
"fromNode": "node01",
"fromSide": "bottom",
"toNode": "node03",
"toSide": "top",
"toEnd": "arrow"
},
{
"id": "e2",
"fromNode": "node02",
"fromSide": "bottom",
"toNode": "node04",
"toSide": "top",
"toEnd": "arrow"
},
{
"id": "e3",
"fromNode": "node01",
"fromSide": "right",
"toNode": "node05",
"toSide": "left",
"toEnd": "arrow",
"label": "leads to",
"color": "3"
},
{
"id": "e4",
"fromNode": "node02",
"fromSide": "right",
"toNode": "node06",
"toSide": "left",
"toEnd": "arrow",
"label": "enables",
"color": "3"
}
]
}
{
"nodes": [
{
"id": "root001",
"type": "text",
"x": -150,
"y": -60,
"width": 300,
"height": 120,
"text": "# Central Topic\n\nMain concept goes here",
"color": "4"
},
{
"id": "branch01",
"type": "text",
"x": 250,
"y": -200,
"width": 220,
"height": 100,
"text": "Branch 1\n\nFirst main idea",
"color": "5"
},
{
"id": "branch02",
"type": "text",
"x": 250,
"y": -50,
"width": 220,
"height": 100,
"text": "Branch 2\n\nSecond main idea",
"color": "5"
},
{
"id": "branch03",
"type": "text",
"x": 250,
"y": 100,
"width": 220,
"height": 100,
"text": "Branch 3\n\nThird main idea",
"color": "5"
},
{
"id": "detail01",
"type": "text",
"x": 550,
"y": -200,
"width": 200,
"height": 80,
"text": "Detail A",
"color": "6"
},
{
"id": "detail02",
"type": "text",
"x": 550,
"y": -100,
"width": 200,
"height": 80,
"text": "Detail B",
"color": "6"
}
],
"edges": [
{
"id": "e1",
"fromNode": "root001",
"fromSide": "right",
"toNode": "branch01",
"toSide": "left",
"toEnd": "arrow"
},
{
"id": "e2",
"fromNode": "root001",
"fromSide": "right",
"toNode": "branch02",
"toSide": "left",
"toEnd": "arrow"
},
{
"id": "e3",
"fromNode": "root001",
"fromSide": "right",
"toNode": "branch03",
"toSide": "left",
"toEnd": "arrow"
},
{
"id": "e4",
"fromNode": "branch01",
"fromSide": "right",
"toNode": "detail01",
"toSide": "left",
"toEnd": "arrow"
},
{
"id": "e5",
"fromNode": "branch01",
"fromSide": "right",
"toNode": "detail02",
"toSide": "left",
"toEnd": "arrow"
}
]
}
JSON Canvas Specification for Obsidian
Version 1.0 — 2024-03-11
Overview
JSON Canvas is a format for representing infinite canvas documents. This specification defines the structure for creating canvas files compatible with Obsidian.
Top Level Structure
The root JSON object contains two optional arrays:
{
"nodes": [...],
"edges": [...]
}nodes(optional, array): All canvas objects (text, files, links, groups)edges(optional, array): All connections between nodes
Node Types
Common Attributes
All nodes share these required attributes:
id(required, string): Unique identifier for the nodetype(required, string): Node type (text,file,link,group)x(required, integer): X position in pixelsy(required, integer): Y position in pixelswidth(required, integer): Width in pixelsheight(required, integer): Height in pixelscolor(optional, string/number): Color (hex"#FF0000"or preset"1")
Text Nodes
Store plain text with Markdown formatting.
Required Attributes:
text(string): Content in Markdown syntax
Example:
{
"id": "abc123",
"type": "text",
"x": 0,
"y": 0,
"width": 250,
"height": 100,
"text": "# Main Topic\n\nKey point here",
"color": "4"
}File Nodes
Reference other files or attachments (images, PDFs, etc.).
Required Attributes:
file(string): Path to file in the vault
Optional Attributes:
subpath(string): Link to specific heading/block (starts with#)
Example:
{
"id": "def456",
"type": "file",
"x": 300,
"y": 0,
"width": 400,
"height": 300,
"file": "Images/diagram.png"
}With Subpath:
{
"id": "ghi789",
"type": "file",
"x": 0,
"y": 200,
"width": 250,
"height": 100,
"file": "Notes/Meeting Notes.md",
"subpath": "#Action Items"
}Link Nodes
Reference external URLs.
Required Attributes:
url(string): Full URL including protocol
Example:
{
"id": "jkl012",
"type": "link",
"x": 0,
"y": -200,
"width": 250,
"height": 100,
"url": "https://obsidian.md",
"color": "5"
}Group Nodes
Visual containers for organizing related nodes.
Optional Attributes:
label(string): Text label for the group (recommended)background(string): Path to background imagebackgroundStyle(string): Image rendering stylecover: Fill entire noderatio: Maintain aspect ratiorepeat: Tile as pattern
Example:
{
"id": "group1",
"type": "group",
"x": -50,
"y": -50,
"width": 600,
"height": 400,
"label": "Main Concepts",
"color": "4"
}With Background:
{
"id": "group2",
"type": "group",
"x": 700,
"y": 0,
"width": 500,
"height": 600,
"label": "Reference Materials",
"background": "Images/texture.png",
"backgroundStyle": "repeat"
}Z-Index and Layering
Nodes are displayed in array order:
- First node: Bottom layer (rendered below others)
- Last node: Top layer (rendered above others)
Best Practice Order: 1. Group nodes (backgrounds) 2. Sub-groups 3. Regular nodes (text, file, link)
This ensures groups appear behind content.
Edges (Connections)
Edges connect nodes with lines.
Required Attributes:
id(required, string): Unique identifierfromNode(required, string): Starting node IDtoNode(required, string): Ending node ID
Optional Attributes:
fromSide(string): Starting edge side- Values:
top,right,bottom,left fromEnd(string): Start endpoint shape- Values:
none(default),arrow toSide(string): Ending edge side- Values:
top,right,bottom,left toEnd(string): End endpoint shape- Values:
arrow(default),none color(string/number): Edge colorlabel(string): Text label on edge
Example - Simple Connection:
{
"id": "edge1",
"fromNode": "abc123",
"toNode": "def456"
}Example - Fully Specified:
{
"id": "edge2",
"fromNode": "def456",
"fromSide": "bottom",
"fromEnd": "none",
"toNode": "ghi789",
"toSide": "top",
"toEnd": "arrow",
"color": "3",
"label": "leads to"
}Color System
Preset Colors
Use string numbers "1" through "6":
"1"- Red"2"- Orange"3"- Yellow"4"- Green"5"- Cyan"6"- Purple
Note: Exact colors adapt to Obsidian's theme. These provide semantic meaning across light/dark modes.
Custom Hex Colors
Use hex format: "#RRGGBB"
Examples:
"#4A90E2"(blue)"#50E3C2"(teal)"#F5A623"(orange)
Best Practice: Use consistent format within a canvas (all hex OR all presets).
Complete Example
{
"nodes": [
{
"id": "group001",
"type": "group",
"x": -50,
"y": -50,
"width": 700,
"height": 500,
"label": "Core Concepts",
"color": "4"
},
{
"id": "center01",
"type": "text",
"x": 0,
"y": 0,
"width": 300,
"height": 120,
"text": "# Central Topic\n\nMain idea here",
"color": "4"
},
{
"id": "branch01",
"type": "text",
"x": 400,
"y": -100,
"width": 220,
"height": 100,
"text": "Subtopic A",
"color": "5"
},
{
"id": "branch02",
"type": "text",
"x": 400,
"y": 100,
"width": 220,
"height": 100,
"text": "Subtopic B",
"color": "5"
},
{
"id": "detail01",
"type": "text",
"x": 700,
"y": -100,
"width": 200,
"height": 80,
"text": "Detail 1",
"color": "6"
}
],
"edges": [
{
"id": "e1",
"fromNode": "center01",
"fromSide": "right",
"toNode": "branch01",
"toSide": "left",
"toEnd": "arrow"
},
{
"id": "e2",
"fromNode": "center01",
"fromSide": "right",
"toNode": "branch02",
"toSide": "left",
"toEnd": "arrow"
},
{
"id": "e3",
"fromNode": "branch01",
"fromSide": "right",
"toNode": "detail01",
"toSide": "left",
"toEnd": "arrow",
"color": "3"
}
]
}Validation Requirements
When creating canvas files, ensure:
1. Unique IDs: All id values must be unique across nodes and edges 2. Valid References: All edge fromNode and toNode must reference existing node IDs 3. Required Fields: All required attributes are present for each type 4. Valid Coordinates: All position/dimension values are integers 5. Color Format: Colors use either hex ("#RRGGBB") or preset strings ("1" to "6") 6. Quote Escaping: Special characters properly escaped in JSON strings
Common Issues and Solutions
Issue: Canvas won't open in Obsidian
Solutions:
- Validate JSON syntax (use JSON validator)
- Check all IDs are unique
- Verify all edge references exist
- Ensure required fields present
Issue: Nodes appear overlapped
Solutions:
- Increase spacing between coordinates
- Account for node dimensions in positioning
- Use minimum spacing: 320px horizontal, 200px vertical
Issue: Groups don't show properly
Solutions:
- Ensure groups appear before content nodes in array
- Add explicit
labelto all groups - Check group dimensions encompass child nodes
Issue: Colors don't match expectations
Solutions:
- Use consistent color format (all hex OR all presets)
- Remember presets adapt to theme
- Test in both light and dark mode if using custom colors
Issue: Text appears truncated
Solutions:
- Increase node dimensions
- Break long text into multiple nodes
- Use file nodes for lengthy content
Character Encoding for Chinese Content
When canvas contains Chinese text, apply these transformations:
- Chinese double quotes
"→『』 - Chinese single quotes
'→「」 - English double quotes must be escaped:
\"
Example:
{
"text": "『核心概念』包含:「子概念A」和「子概念B」"
}This prevents JSON parsing errors with mixed-language content.
Performance Considerations
- Large Canvases: Keep node count reasonable (<500 for smooth performance)
- Image Files: Use compressed images for backgrounds
- Text Length: Keep node text concise; use file nodes for long content
- Edge Complexity: Minimize crossing edges for clarity
Future Extensions
This specification may be extended with:
- Additional node types
- More edge styling options
- Animation properties
- Interactive behaviors
Always check Obsidian documentation for latest Canvas features.
Layout Algorithms for Obsidian Canvas
Detailed algorithms for positioning nodes in MindMap and Freeform layouts.
Layout Principles
Universal Spacing Constants
HORIZONTAL_SPACING = 320 // Minimum horizontal space between node centers
VERTICAL_SPACING = 200 // Minimum vertical space between node centers
NODE_PADDING = 20 // Internal padding within nodesCollision Detection
Before finalizing any node position, verify:
def check_collision(node1, node2):
"""Returns True if nodes overlap or are too close"""
center1_x = node1.x + node1.width / 2
center1_y = node1.y + node1.height / 2
center2_x = node2.x + node2.width / 2
center2_y = node2.y + node2.height / 2
dx = abs(center1_x - center2_x)
dy = abs(center1_y - center2_y)
min_dx = (node1.width + node2.width) / 2 + HORIZONTAL_SPACING
min_dy = (node1.height + node2.height) / 2 + VERTICAL_SPACING
return dx < min_dx or dy < min_dyMindMap Layout Algorithm
1. Radial Tree Layout
Place root at center, arrange children radially.
Step 1: Position Root Node
root = {
"x": 0 - (root_width / 2), # Center horizontally
"y": 0 - (root_height / 2), # Center vertically
"width": root_width,
"height": root_height
}Step 2: Calculate Primary Branch Positions
Distribute first-level children around root:
def position_primary_branches(root, children, radius=400):
"""Position first-level children in a circle around root"""
n = len(children)
angle_step = 2 * pi / n
positions = []
for i, child in enumerate(children):
angle = i * angle_step
# Calculate position on circle
x = root.center_x + radius * cos(angle) - child.width / 2
y = root.center_y + radius * sin(angle) - child.height / 2
positions.append({"x": x, "y": y})
return positionsRadius Selection:
- Small canvases (≤10 children): 400px
- Medium canvases (11-20 children): 500px
- Large canvases (>20 children): 600px
Step 3: Position Secondary Branches
For each primary branch, arrange its children:
Horizontal Layout (preferred for most cases):
def position_secondary_horizontal(parent, children, distance=350):
"""Arrange children horizontally to the right of parent"""
n = len(children)
total_height = sum(child.height for child in children)
total_spacing = (n - 1) * VERTICAL_SPACING
# Start position (top of vertical arrangement)
start_y = parent.center_y - (total_height + total_spacing) / 2
positions = []
current_y = start_y
for child in children:
x = parent.x + parent.width + distance
y = current_y
positions.append({"x": x, "y": y})
current_y += child.height + VERTICAL_SPACING
return positionsVertical Layout (for left/right primary branches):
def position_secondary_vertical(parent, children, distance=250):
"""Arrange children vertically below parent"""
n = len(children)
total_width = sum(child.width for child in children)
total_spacing = (n - 1) * HORIZONTAL_SPACING
# Start position (left of horizontal arrangement)
start_x = parent.center_x - (total_width + total_spacing) / 2
positions = []
current_x = start_x
for child in children:
x = current_x
y = parent.y + parent.height + distance
positions.append({"x": x, "y": y})
current_x += child.width + HORIZONTAL_SPACING
return positionsStep 4: Balance and Adjust
After initial placement, check for collisions and adjust:
def balance_layout(nodes):
"""Adjust nodes to prevent overlaps"""
max_iterations = 10
for iteration in range(max_iterations):
collisions = find_all_collisions(nodes)
if not collisions:
break
for node1, node2 in collisions:
# Move node2 away from node1
dx = node2.center_x - node1.center_x
dy = node2.center_y - node1.center_y
distance = sqrt(dx*dx + dy*dy)
# Calculate required distance
min_dist = calculate_min_distance(node1, node2)
if distance > 0:
# Move proportionally
move_x = (dx / distance) * (min_dist - distance) / 2
move_y = (dy / distance) * (min_dist - distance) / 2
node2.x += move_x
node2.y += move_y2. Tree Layout (Hierarchical Top-Down)
Alternative for deep hierarchies.
Positioning Formula
def position_tree_layout(root, tree):
"""Top-down tree layout"""
# Level 0 (root)
root.x = 0 - root.width / 2
root.y = 0 - root.height / 2
# Process each level
for level in range(1, max_depth):
nodes_at_level = get_nodes_at_level(tree, level)
# Calculate horizontal spacing
total_width = sum(node.width for node in nodes_at_level)
total_spacing = (len(nodes_at_level) - 1) * HORIZONTAL_SPACING
start_x = -(total_width + total_spacing) / 2
y = level * (150 + VERTICAL_SPACING) # 150px level height
current_x = start_x
for node in nodes_at_level:
node.x = current_x
node.y = y
current_x += node.width + HORIZONTAL_SPACINGFreeform Layout Algorithm
1. Content-Based Grouping
First, identify natural groupings in content:
def identify_groups(nodes, content_structure):
"""Group nodes by semantic relationships"""
groups = []
# Analyze content structure
for section in content_structure:
group_nodes = [node for node in nodes if node.section == section]
if len(group_nodes) > 1:
groups.append({
"label": section.title,
"nodes": group_nodes
})
return groups2. Grid-Based Zone Layout
Divide canvas into zones for different groups:
def layout_zones(groups, canvas_width=2000, canvas_height=1500):
"""Arrange groups in grid zones"""
n_groups = len(groups)
# Calculate grid dimensions
cols = ceil(sqrt(n_groups))
rows = ceil(n_groups / cols)
zone_width = canvas_width / cols
zone_height = canvas_height / rows
# Assign zones
zones = []
for i, group in enumerate(groups):
col = i % cols
row = i // cols
zone = {
"x": col * zone_width - canvas_width / 2,
"y": row * zone_height - canvas_height / 2,
"width": zone_width * 0.9, # Leave 10% margin
"height": zone_height * 0.9,
"group": group
}
zones.append(zone)
return zones3. Within-Zone Node Positioning
Position nodes within each zone:
Option A: Organic Flow
def position_organic(zone, nodes):
"""Organic, flowing arrangement within zone"""
positions = []
# Start at zone top-left with margin
current_x = zone.x + 50
current_y = zone.y + 50
row_height = 0
for node in nodes:
# Check if node fits in current row
if current_x + node.width > zone.x + zone.width - 50:
# Move to next row
current_x = zone.x + 50
current_y += row_height + VERTICAL_SPACING
row_height = 0
positions.append({
"x": current_x,
"y": current_y
})
current_x += node.width + HORIZONTAL_SPACING
row_height = max(row_height, node.height)
return positionsOption B: Structured Grid
def position_grid(zone, nodes):
"""Grid arrangement within zone"""
n = len(nodes)
cols = ceil(sqrt(n))
rows = ceil(n / cols)
cell_width = (zone.width - 100) / cols # 50px margin each side
cell_height = (zone.height - 100) / rows
positions = []
for i, node in enumerate(nodes):
col = i % cols
row = i // cols
# Center node in cell
x = zone.x + 50 + col * cell_width + (cell_width - node.width) / 2
y = zone.y + 50 + row * cell_height + (cell_height - node.height) / 2
positions.append({"x": x, "y": y})
return positions4. Cross-Zone Connections
Calculate optimal edge paths between zones:
def calculate_edge_path(from_node, to_node):
"""Determine edge connection points"""
# Calculate centers
from_center = (from_node.x + from_node.width/2,
from_node.y + from_node.height/2)
to_center = (to_node.x + to_node.width/2,
to_node.y + to_node.height/2)
# Determine best sides to connect
dx = to_center[0] - from_center[0]
dy = to_center[1] - from_center[1]
# Choose sides based on direction
if abs(dx) > abs(dy):
# Horizontal connection
from_side = "right" if dx > 0 else "left"
to_side = "left" if dx > 0 else "right"
else:
# Vertical connection
from_side = "bottom" if dy > 0 else "top"
to_side = "top" if dy > 0 else "bottom"
return {
"fromSide": from_side,
"toSide": to_side
}Advanced Techniques
Force-Directed Layout
For complex networks with many cross-connections:
def force_directed_layout(nodes, edges, iterations=100):
"""Spring-based layout algorithm"""
# Constants
SPRING_LENGTH = 200
SPRING_CONSTANT = 0.1
REPULSION_CONSTANT = 5000
for iteration in range(iterations):
# Calculate repulsive forces (all pairs)
for node1 in nodes:
force_x, force_y = 0, 0
for node2 in nodes:
if node1 == node2:
continue
dx = node1.x - node2.x
dy = node1.y - node2.y
distance = sqrt(dx*dx + dy*dy)
if distance > 0:
# Repulsive force
force = REPULSION_CONSTANT / (distance * distance)
force_x += (dx / distance) * force
force_y += (dy / distance) * force
node1.force_x = force_x
node1.force_y = force_y
# Calculate attractive forces (connected nodes)
for edge in edges:
node1 = get_node(edge.fromNode)
node2 = get_node(edge.toNode)
dx = node2.x - node1.x
dy = node2.y - node1.y
distance = sqrt(dx*dx + dy*dy)
# Spring force
force = SPRING_CONSTANT * (distance - SPRING_LENGTH)
node1.force_x += (dx / distance) * force
node1.force_y += (dy / distance) * force
node2.force_x -= (dx / distance) * force
node2.force_y -= (dy / distance) * force
# Apply forces
for node in nodes:
node.x += node.force_x
node.y += node.force_yHierarchical Clustering
Group related nodes automatically:
def hierarchical_cluster(nodes, similarity_threshold=0.7):
"""Cluster nodes by content similarity"""
clusters = []
# Calculate similarity matrix
similarity = calculate_similarity_matrix(nodes)
# Agglomerative clustering
current_clusters = [[node] for node in nodes]
while len(current_clusters) > 1:
# Find most similar clusters
max_sim = 0
merge_i, merge_j = 0, 1
for i in range(len(current_clusters)):
for j in range(i + 1, len(current_clusters)):
sim = cluster_similarity(current_clusters[i],
current_clusters[j],
similarity)
if sim > max_sim:
max_sim = sim
merge_i, merge_j = i, j
if max_sim < similarity_threshold:
break
# Merge clusters
current_clusters[merge_i].extend(current_clusters[merge_j])
current_clusters.pop(merge_j)
return current_clustersLayout Optimization
Minimize Edge Crossings
def minimize_crossings(nodes, edges):
"""Reduce edge crossing through node repositioning"""
crossings = count_crossings(edges)
# Try swapping adjacent nodes
improved = True
while improved:
improved = False
for i in range(len(nodes) - 1):
# Swap nodes i and i+1
swap_positions(nodes[i], nodes[i+1])
new_crossings = count_crossings(edges)
if new_crossings < crossings:
crossings = new_crossings
improved = True
else:
# Swap back
swap_positions(nodes[i], nodes[i+1])Visual Balance
def calculate_visual_weight(canvas):
"""Calculate center of mass for visual balance"""
total_weight = 0
weighted_x = 0
weighted_y = 0
for node in canvas.nodes:
# Weight is proportional to area
weight = node.width * node.height
total_weight += weight
weighted_x += node.center_x * weight
weighted_y += node.center_y * weight
center_x = weighted_x / total_weight
center_y = weighted_y / total_weight
# Shift entire canvas to center at (0, 0)
offset_x = -center_x
offset_y = -center_y
for node in canvas.nodes:
node.x += offset_x
node.y += offset_yPerformance Optimization
Spatial Indexing
For large canvases, use spatial indexing to speed up collision detection:
class SpatialGrid:
"""Grid-based spatial index for fast collision detection"""
def __init__(self, cell_size=500):
self.cell_size = cell_size
self.grid = {}
def add_node(self, node):
"""Add node to grid"""
cells = self.get_cells(node)
for cell in cells:
if cell not in self.grid:
self.grid[cell] = []
self.grid[cell].append(node)
def get_cells(self, node):
"""Get grid cells node occupies"""
min_x = int(node.x / self.cell_size)
max_x = int((node.x + node.width) / self.cell_size)
min_y = int(node.y / self.cell_size)
max_y = int((node.y + node.height) / self.cell_size)
cells = []
for x in range(min_x, max_x + 1):
for y in range(min_y, max_y + 1):
cells.append((x, y))
return cells
def get_nearby_nodes(self, node):
"""Get nodes in nearby cells"""
cells = self.get_cells(node)
nearby = set()
for cell in cells:
if cell in self.grid:
nearby.update(self.grid[cell])
return nearbyCommon Layout Patterns
Timeline Layout
For chronological content:
def layout_timeline(events, direction="horizontal"):
"""Create timeline layout"""
if direction == "horizontal":
for i, event in enumerate(events):
event.x = i * (event.width + HORIZONTAL_SPACING)
event.y = 0
else: # vertical
for i, event in enumerate(events):
event.x = 0
event.y = i * (event.height + VERTICAL_SPACING)Circular Layout
For cyclical processes:
def layout_circular(nodes, radius=500):
"""Arrange nodes in a circle"""
n = len(nodes)
angle_step = 2 * pi / n
for i, node in enumerate(nodes):
angle = i * angle_step
node.x = radius * cos(angle) - node.width / 2
node.y = radius * sin(angle) - node.height / 2Matrix Layout
For comparing multiple dimensions:
def layout_matrix(nodes, rows, cols):
"""Arrange nodes in a matrix"""
cell_width = 400
cell_height = 250
for i, node in enumerate(nodes):
row = i // cols
col = i % cols
node.x = col * cell_width
node.y = row * cell_heightQuality Checks
Before finalizing layout, verify:
1. No Overlaps: All nodes have minimum spacing 2. Balanced: Visual center near (0, 0) 3. Accessible: All nodes reachable via edges 4. Readable: Text sizes appropriate for zoom level 5. Efficient: Edge paths reasonably direct
Use these algorithms as foundations, adapting to specific content and user preferences.
Related skills
FAQ
What layout modes exist?
MindMap for hierarchy and freeform for flexible spatial placement.
What input works best?
Structured content with headings, lists, or clear topic hierarchy.
What file format is produced?
Obsidian-compatible .canvas JSON files.
Is Obsidian Canvas Creator safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.