Creating Node Images in Nodlin
Overview
Every node in Nodlin displays a visual representation on the graph. This guide covers the various ways to create these visuals using Python scripting.
For complete API details, see the Starlark Extensions Reference.
Image Types
Nodlin supports two main image formats:
- SVG - Scalable Vector Graphics (recommended for icons and illustrations)
- PNG - Base64-encoded raster images (for photos or complex graphics)
Setting Node Image Properties
The value object has special properties for controlling node display:
# Required for images
value.image = svgString # SVG or base64 PNG data
value.sizeX = 300 # Width in pixels
value.sizeY = 200 # Height in pixels
# Optional display properties
value.label = "My Node" # Short label
value.summary = "Node description for tooltip"
value.help = "# Help\nDetailed help in markdown"Raw Strings for SVG
When working with SVG that contains quotes, use raw strings (prefix with r):
mySVG = r'<svg id="Layer_1" height="512" viewBox="0 0 512 512" width="512"
xmlns="http://www.w3.org/2000/svg">
<path d="M239.231 17.345..." fill="#ffe178"/>
</svg>'
value.image = mySVGThe r prefix prevents Python from interpreting escape sequences in the SVG markup.
SVG Images
Direct SVG
The simplest approach is to create or edit SVG directly:
iconSVG = r'<svg width="200" height="120" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="120" fill="#4a90e2" rx="10"/>
<text x="100" y="70" text-anchor="middle" fill="white"
font-family="Arial" font-size="24" font-weight="bold">
My Node
</text>
</svg>'
value.image = iconSVG
value.sizeX = 200
value.sizeY = 120Parameterized SVG
Use string formatting to create dynamic SVG based on node data:
svgTemplate = r'<svg width="300" height="150" xmlns="http://www.w3.org/2000/svg">
<rect width="300" height="150" fill="{backgroundColor}" rx="8"/>
<text x="150" y="80" text-anchor="middle" fill="white"
font-family="Arial" font-size="20" font-weight="bold">
{label}
</text>
<text x="150" y="110" text-anchor="middle" fill="white"
font-family="Arial" font-size="14">
Status: {status}
</text>
</svg>'
# Determine background color based on status
statusColors = {
"Active": "#4caf50",
"Pending": "#ff9800",
"Complete": "#2196f3"
}
params = {
'backgroundColor': statusColors.get(value.status, "#9e9e9e"),
'label': value.name,
'status': value.status
}
value.image = svgTemplate.format(**params)
value.sizeX = 300
value.sizeY = 150Example from TOGAF Architecture Project:
# Template with placeholders
iconSVG = r'''<svg width="400" height="260" viewBox="0 0 400 260"
xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#667eea"/>
<stop offset="100%" style="stop-color:#764ba2"/>
</linearGradient>
</defs>
<rect width="400" height="260" fill="url(#bgGrad)" rx="12"/>
<!-- Dashboard grid with counts -->
<rect x="20" y="20" width="85" height="80" fill="white" rx="6"/>
<text x="62.5" y="60" text-anchor="middle" font-size="32"
font-weight="bold" fill="#667eea">{$STAKEHOLDER_COUNT}</text>
<text x="62.5" y="85" text-anchor="middle" font-size="11"
fill="#666">Stakeholders</text>
<!-- Title at bottom -->
<text x="200" y="220" text-anchor="middle" font-size="18"
font-weight="bold" fill="white">{$TITLE}</text>
</svg>'''
# Format with actual values
params = {
'$STAKEHOLDER_COUNT': str(stakeholderCount),
'$TITLE': sanitize_svg_text(value.name)
}
value.image = iconSVG.format(**params)SVG Text Escaping
Always escape XML special characters in SVG text:
def sanitize_svg_text(s):
"""Escape XML special characters for use in SVG"""
if type(s) != "string":
return s
return (
s.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace('"', """)
.replace("'", "'")
)
# Use in SVG
title = sanitize_svg_text(value.title)
svgContent = svgTemplate.format(title=title)SVG Text Truncation
Helper to truncate text to fit within SVG:
def short(s, n):
"""Truncate to max 2 lines, whole words, n = single-line width"""
if not s:
return ("", "")
s = str(s)
# Fits on one line
if len(s) <= n:
return (s, "")
words = s.split(" ")
line1 = ""
line2 = ""
used = 0
for w in words:
target = line1 if not line2 else line2
sep = "" if target == "" else " "
cand = target + sep + w
# Try line1
if not line2 and len(cand) <= n:
line1 = cand
used += len(w) + (1 if sep else 0)
continue
# Try line2
if line2 == "":
cand2 = w
else:
cand2 = line2 + " " + w
if len(cand2) <= n and used + len(w) + 1 <= 2 * n:
line2 = cand2
used += len(w) + 1
continue
# Overflow - add ellipsis
if line2 == "":
if n > 1:
line2 = w[:n-1] + "…"
else:
if len(line2) > 1:
line2 = line2[:n-1] + "…"
break
return (line1, line2)
# Use in SVG
titleLine1, titleLine2 = short(value.name, 30)
svgContent = template.format(line1=titleLine1, line2=titleLine2)Recommended SVG Tools
Boxy SVG is recommended for creating and editing SVG images.
Workflow:
- Design your icon in Boxy SVG
- Name elements with IDs for easy location in SVG text
- Design at full size to determine limits, then resize
- Copy SVG text from editor
- Replace dynamic elements with
{$paramName}placeholders - Use
.format()in Python to insert values
# SVG from Boxy SVG with placeholders
mySVG = r'''<svg viewBox="0 0 {$width} {$height}">
<circle cx="{$cx}" cy="{$cy}" r="30" fill="{$color}"/>
<text x="{$cx}" y="{$cy}" text-anchor="middle">{$label}</text>
</svg>'''
params = {
'$width': 200,
'$height': 200,
'$cx': 100,
'$cy': 100,
'$color': '#4a90e2',
'$label': value.name
}
value.image = mySVG.format(**params)PNG Images
PNG images must be base64-encoded with the proper URI prefix:
# PNG constant provides proper prefix
pngData = "iVBORw0KGgoAAAANSUhEUgAA..." # Your base64 data
value.image = webform.PNG + pngData
value.sizeX = 300
value.sizeY = 200The webform.PNG constant provides: data:image/png;base64,
Textbox Images
Create text-based images with icons, perfect for status displays and summaries.
Basic Textbox
# Color and font settings
backColor = color.Name("yellow")
borderColor = color.Name("blue")
myBold = textbox.fontStyle.Bold
textboxOpts = textbox.Options(
fontFamily="Roboto_Mono",
fontColor="black",
fontStyle=myBold,
textHAlign=textbox.textAlign.Left,
backgroundColor=backColor,
borderColor=borderColor,
borderHeight=0.10
)
# Create textbox image
value.image = textbox.Create(
"My descriptive text that will wrap automatically",
300.0, # Width
200.0, # Height
textboxOpts
)
value.sizeX = 300
value.sizeY = 200
value.label = "My Textbox Node"Textbox with MDI Icon
Add Material Design Icons to textboxes:
# Load an icon
myIcon = image.mdiIcon(group="action", name="paid")
# Color and font settings
backColor = color.Name("yellow")
borderColor = color.Name("blue")
myBold = textbox.fontStyle.Bold
textboxOpts = textbox.Options(
fontFamily="Roboto_Mono",
fontColor="black",
fontStyle=myBold,
textHAlign=textbox.textAlign.Left,
backgroundColor=backColor,
borderColor=borderColor,
borderHeight=0.10,
icon=myIcon,
iconSize=0.3 # Icon takes 30% of left side
)
value.image = textbox.Create(
"Some descriptive text with an icon",
300.0,
200.0,
textboxOpts
)Textbox with MDI Symbol
Symbols support color customization:
# Load symbol with color
myIcon = image.mdiSymbol(name="change_circle", color=color.Name("blue"))
backColor = color.Name("white")
borderColor = color.Name("blue")
myBold = textbox.fontStyle.Bold
textboxOpts = textbox.Options(
fontFamily="Roboto_Mono",
fontColor="black",
fontStyle=myBold,
textHAlign=textbox.textAlign.Left,
backgroundColor=backColor,
borderColor=borderColor,
borderHeight=0.10,
icon=myIcon,
iconSize=0.2
)
value.image = textbox.Create(
"Text with colored MDI symbol",
300.0,
200.0,
textboxOpts
)Textbox with Custom SVG Icon
Use your own SVG as an icon:
# Your custom SVG
mySVG = r'<svg id="Layer_1" height="512" viewBox="0 0 512 512"
width="512" xmlns="http://www.w3.org/2000/svg">
<path d="M239.231 17.345..." fill="#ffe178"/>
</svg>'
# Convert SVG to icon with fill color
fillColor = color.Name("purple")
myIcon = image.fromSVG(mySVG, fillColor)
# Textbox options
backColor = color.Name("lightslategrey")
borderColor = color.Name("blue")
myBold = textbox.fontStyle.Bold
textboxOpts = textbox.Options(
fontFamily="Roboto_Mono",
fontColor=borderColor,
fontStyle=myBold,
textHAlign=textbox.textAlign.Left,
backgroundColor=backColor,
borderColor=borderColor,
borderHeight=0.10,
icon=myIcon,
iconSize=0.4
)
value.image = textbox.Create(
"My description with custom SVG icon",
300.0,
200.0,
textboxOpts
)Number Display with Currency
Display formatted numbers with currency symbols:
# Create SVG icon
mySVG = r'<svg>...</svg>'
fillColor = color.Name("red")
myIcon = image.fromSVG(mySVG, fillColor)
# Textbox styling
backColor = color.Name("white")
borderColor = color.Name("blue")
myBold = textbox.fontStyle.Bold
textboxOpts = textbox.Options(
fontFamily="Roboto_Mono",
fontColor=borderColor,
fontStyle=myBold,
textHAlign=textbox.textAlign.Left,
backgroundColor=backColor,
borderColor=borderColor,
borderHeight=0.10,
icon=myIcon,
iconSize=0.2
)
# Create number display with currency
value.image = textbox.Number(
25300.12, # The number
300, # Width
200, # Height
numberOptions=textbox.NumberOptions(
currency="GBP", # Currency code
scale=2 # Decimal places
),
textboxOptions=textboxOpts
)Available Colors
Access named colors:
# List all available colors
print("Available colors:", color.colors)
# Use named colors
redColor = color.Name("red")
blueColor = color.Name("blue")
greenColor = color.Name("mediumspringgreen")Text Alignment Options
# Horizontal alignment
textbox.textAlign.Left
textbox.textAlign.Center
textbox.textAlign.Right
# Font styles
textbox.fontStyle.Regular
textbox.fontStyle.Bold
textbox.fontStyle.ItalicGridder - Programmatic SVG
Draw custom SVG programmatically using gridder module for grid-based layouts.
Basic Setup
# Configure image and grid
imageConfig = gridder.ImageConfig(width=600, height=400)
gridConfig = gridder.GridConfig(
rows=6,
columns=8,
lineStrokeWidth=2.1,
borderStrokeWidth=10.1
)
# Create gridder instance
myImage = gridder.New(imageConfig, gridConfig)Drawing Shapes
Circles
# Define circle style
r = color.RGBA(R=245, G=69, B=8, A=1)
circle = gridder.CircleConfig(
radius=40.0,
stroke=False,
color=r,
strokeWidth=4.3
)
# Draw circle at grid position (row, col)
myImage.DrawCircle(2, 2, circle)
# Draw line of circles
lineCircle = gridder.CircleConfig(radius=30.0, stroke=False, color=r)
for x in range(8):
myImage.DrawCircle(3, x, lineCircle)Rectangles
# Rectangle with rotation
myRectColor = color.RGBA(R=1, G=255, B=80, A=1)
myRectangle = gridder.RectangleConfig(
width=40.1,
height=120.1,
color=myRectColor,
rotate=50.0 # Degrees
)
# Draw multiple rectangles
for recCol in range(3):
myImage.DrawRectangle(4, 2+recCol, myRectangle)Lines
lineColor = color.RGBA(R=80, G=200, B=80, A=1)
myLine = gridder.LineConfig(
length=160.2,
rotate=12.1,
color=lineColor,
strokeWidth=30.0
)
myImage.DrawLine(2, 1, myLine)Paths
# Draw path from one grid cell to another
myPath = gridder.PathConfig(
strokeWidth=2.1,
color=color.Black,
dashes=1.5 # Dashed line
)
myImage.DrawPath(1, 1, 5, 7, myPath) # From (1,1) to (5,7)Drawing Text
# Font configuration
stringColor = color.HEX("#8A2BE2")
myFont = font.face(size=60.1, name="italic")
# String style
myStringConfig = gridder.StringConfig(color=stringColor)
# Draw text at grid position
myImage.DrawString(1, 4, "Nodlin", myFont, myStringConfig)
# Another line with different font
myFont2 = font.face(size=50.1, name="mono")
myImage.DrawString(2, 4, "$23,121,443", myFont2,
gridder.StringConfig(color=color.Name("red")))Cell Operations
# Paint a cell background
myImage.PaintCell(1, 1, color.Name("mediumspringgreen"))Complete Gridder Example
# Setup
imageConfig = gridder.ImageConfig(width=600, height=400)
gridConfig = gridder.GridConfig(rows=6, columns=8,
lineStrokeWidth=2.1, borderStrokeWidth=10.1)
myImage = gridder.New(imageConfig, gridConfig)
# Draw circles
r = color.RGBA(R=245, G=69, B=8, A=1)
circle = gridder.CircleConfig(radius=40.0, stroke=False, color=r)
myImage.DrawCircle(2, 2, circle)
# Draw text
stringColor = color.HEX("#8A2BE2")
myFont = font.face(size=60.1, name="italic")
myImage.DrawString(1, 4, "Nodlin", myFont,
gridder.StringConfig(color=stringColor))
# Draw path
myPath = gridder.PathConfig(strokeWidth=2.1, color=color.Black, dashes=1.5)
myImage.DrawPath(1, 1, 5, 7, myPath)
# Draw rectangles
myRectColor = color.RGBA(R=1, G=255, B=80, A=1)
myRectangle = gridder.RectangleConfig(width=40.1, height=120.1,
color=myRectColor, rotate=50.0)
for recCol in range(3):
myImage.DrawRectangle(4, 2+recCol, myRectangle)
# Draw line
lineColor = color.RGBA(R=80, G=200, B=80, A=1)
myLine = gridder.LineConfig(length=160.2, rotate=12.1,
color=lineColor, strokeWidth=30.0)
myImage.DrawLine(2, 1, myLine)
# Paint cell
myImage.PaintCell(1, 1, color.Name("mediumspringgreen"))
# Get the final SVG
value.image = myImage.GetImage()Color Specifications
# RGB
color1 = color.RGBA(R=245, G=69, B=8, A=1) # A=1 for opaque
# Hex
color2 = color.HEX("#8A2BE2")
# Named colors
color3 = color.Name("blue")
color4 = color.Name("mediumspringgreen")
# List all available named colors
print("colors=", color.colors)Charts with Quickchart
Create sophisticated charts using the Quickchart API.
Basic Chart
# Define chart structure
chart = {
"type": "bar",
"data": {
"labels": ["Q1", "Q2", "Q3", "Q4"],
"datasets": [{
"label": "Revenue",
"data": [12, 19, 3, 17],
"backgroundColor": "rgba(54, 162, 235, 0.5)"
}]
},
"options": {
"title": {
"display": True,
"text": "Quarterly Revenue"
}
}
}
# Create chart spec
chartSpec = {
"backgroundColor": "transparent",
"width": 400,
"height": 400,
"chart": chart
}
# Generate image
value.image = image.chart(chartSpec)
value.sizeX = 400
value.sizeY = 400Radar Chart Example
From the TOGAF objectives example:
# Set size
value.sizeX = 400
value.sizeY = 400
# Prepare datasets (from related nodes)
datasets = []
# ... populate datasets from node relationships ...
# Define radar chart
chart = {
"type": "radar",
"data": {
"labels": value.subObjectives,
"datasets": datasets
},
"options": {
"title": {
"display": True,
"position": "top",
"fontSize": 22,
"fontFamily": "sans-serif",
"fontColor": "#f2ecec",
"fontStyle": "bold",
"padding": 10,
"lineHeight": 1.2,
"text": value.objectiveTitle
},
"scale": {
"id": "radial",
"display": True,
"stacked": False,
"distribution": "linear",
"ticks": {
"display": False,
"min": 0,
"max": 100
},
"pointLabels": {
"display": True,
"fontColor": "#f2ecec",
"fontSize": 10,
"fontStyle": "bold"
},
"gridLines": {
"display": False
},
"angleLines": {
"display": True,
"color": "rgba(222, 139, 17, 0.7)",
"borderDash": [0, 0],
"lineWidth": 1
}
},
"plugins": {
"datalabels": {
"display": True,
"backgroundColor": "#eee",
"borderRadius": 6,
"borderWidth": 1,
"padding": 2,
"color": "#666666",
"font": {
"family": "sans-serif",
"size": 10,
"style": "normal"
}
}
}
}
}
# Create chart
chartSpec = {
"backgroundColor": "transparent",
"width": value.sizeX,
"height": value.sizeY,
"chart": chart
}
value.image = image.chart(chartSpec)Chart Types
Quickchart supports many chart types:
bar- Bar chartsline- Line chartspie- Pie chartsdoughnut- Doughnut chartsradar- Radar/spider chartspolarArea- Polar area charts- Many more…
See Quickchart documentation for complete options.
Chart Best Practices
- Keep it simple: Don’t overcrowd charts with too much data
- Use appropriate types: Match chart type to data (trends→line, comparisons→bar)
- Color wisely: Use color to highlight important data
- Label clearly: Always include titles and axis labels
- Consider size: Charts should be readable at the node size
Complete Examples
Status Badge Node
# Determine colors based on status
statusColors = {
"Active": "#4caf50",
"Warning": "#ff9800",
"Error": "#f44336",
"Inactive": "#9e9e9e"
}
bgColor = statusColors.get(value.status, "#9e9e9e")
# Create simple status badge
svgTemplate = r'''<svg width="180" height="80"
xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="80" fill="{bgColor}" rx="10"/>
<text x="90" y="35" text-anchor="middle" fill="white"
font-family="Arial" font-size="16" font-weight="bold">
{label}
</text>
<text x="90" y="55" text-anchor="middle" fill="white"
font-family="Arial" font-size="12">
{status}
</text>
</svg>'''
value.image = svgTemplate.format(
bgColor=bgColor,
label=sanitize_svg_text(value.name),
status=sanitize_svg_text(value.status)
)
value.sizeX = 180
value.sizeY = 80Progress Bar Node
# Calculate progress percentage
progress = (value.completed / value.total * 100) if value.total > 0 else 0
progressWidth = int(progress * 2.6) # 260px max width
svgTemplate = r'''<svg width="300" height="120"
xmlns="http://www.w3.org/2000/svg">
<!-- Background -->
<rect width="300" height="120" fill="#f5f5f5" rx="8"/>
<!-- Title -->
<text x="150" y="30" text-anchor="middle" font-family="Arial"
font-size="16" font-weight="bold" fill="#333">
{title}
</text>
<!-- Progress bar background -->
<rect x="20" y="50" width="260" height="30" fill="#e0e0e0" rx="15"/>
<!-- Progress bar fill -->
<rect x="20" y="50" width="{progressWidth}" height="30"
fill="#4caf50" rx="15"/>
<!-- Progress text -->
<text x="150" y="95" text-anchor="middle" font-family="Arial"
font-size="14" fill="#666">
{completed} / {total} ({progress:.0f}%)
</text>
</svg>'''
value.image = svgTemplate.format(
title=sanitize_svg_text(value.name),
progressWidth=progressWidth,
completed=value.completed,
total=value.total,
progress=progress
)
value.sizeX = 300
value.sizeY = 120Dashboard Card from TOGAF
# Count related items
stakeholderCount = len(node.related.stakeholder.all()) if 'stakeholder' in node.related else 0
requirementCount = len(node.related.requirement.all()) if 'requirement' in node.related else 0
# Create dashboard SVG
iconSVG = r'''<svg width="400" height="260" viewBox="0 0 400 260"
xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#667eea"/>
<stop offset="100%" style="stop-color:#764ba2"/>
</linearGradient>
</defs>
<!-- Background -->
<rect width="400" height="260" fill="url(#bgGrad)" rx="12"/>
<!-- Stakeholders card -->
<rect x="20" y="20" width="85" height="80" fill="white" rx="6"/>
<text x="62.5" y="60" text-anchor="middle" font-size="32"
font-weight="bold" fill="#667eea">{stakeholderCount}</text>
<text x="62.5" y="85" text-anchor="middle" font-size="11"
fill="#666">Stakeholders</text>
<!-- Requirements card -->
<rect x="115" y="20" width="85" height="80" fill="white" rx="6"/>
<text x="157.5" y="60" text-anchor="middle" font-size="32"
font-weight="bold" fill="#1976d2">{requirementCount}</text>
<text x="157.5" y="85" text-anchor="middle" font-size="11"
fill="#666">Requirements</text>
<!-- Title -->
<text x="200" y="220" text-anchor="middle" font-size="18"
font-weight="bold" fill="white">{title}</text>
</svg>'''
value.image = iconSVG.format(
stakeholderCount=stakeholderCount,
requirementCount=requirementCount,
title=sanitize_svg_text(value.name)
)
value.sizeX = 400
value.sizeY = 260Best Practices
1. Choose the Right Approach
- Simple icons: Use direct SVG or textbox
- Dynamic data display: Use parameterized SVG
- Complex layouts: Use gridder
- Data visualization: Use charts
- Text with icons: Use textbox with MDI icons
2. Keep Images Readable
# Good: Reasonable size for readability
value.sizeX = 300
value.sizeY = 200
# Too small: Text will be hard to read
value.sizeX = 50
value.sizeY = 303. Use Consistent Sizing
# Define size constants at top of script
DEFAULT_WIDTH = 300
DEFAULT_HEIGHT = 200
# Use throughout
value.sizeX = DEFAULT_WIDTH
value.sizeY = DEFAULT_HEIGHT4. Color for Meaning
Use colors to convey status or importance:
statusColors = {
"success": "#4caf50",
"warning": "#ff9800",
"error": "#f44336",
"info": "#2196f3"
}5. Escape Text Properly
Always sanitize user input in SVG:
def sanitize_svg_text(s):
if type(s) != "string":
return s
return (s.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace('"', """)
.replace("'", "'"))6. Test at Different Scales
Node images are displayed at different zoom levels. Ensure your images remain legible when zoomed out.
Troubleshooting
Image Not Displaying
Check that all properties are set:
# All three required
value.image = mySVG
value.sizeX = 300
value.sizeY = 200SVG Syntax Errors
Use raw strings and validate SVG:
# Good: Raw string prevents escape issues
svg = r'<svg>...</svg>'
# Bad: Regular string may interpret escapes
svg = '<svg>...</svg>' # May cause issuesText Overlapping
Adjust font size and spacing:
# If text overlaps, reduce font size
<text font-size="14">...</text> # Instead of 24
# Or increase image height
value.sizeY = 250 # Give more vertical spaceCharts Not Loading
Ensure chart spec is valid JSON structure:
# Chart data must be proper Python dict
chart = {
"type": "bar", # Not 'type': "bar" (wrong quotes)
"data": {...} # Must be nested dicts
}Reference Links
- Starlark Extensions API - Complete API reference
- Python Scripting Guide - Overall scripting guide
- Forms Guide - Creating interactive forms
- Quickchart Documentation - Chart options
- Boxy SVG - SVG editor
- Material Design Icons - Icon library
Next Steps
Now that you understand creating node visuals:
- Review the Python Scripting Guide for overall script structure
- Learn about Creating Forms for user interaction
- See complete script examples showing images in context