Creating Node Images in Nodlin

Feb 3, 2026 · 13 min read

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 = mySVG

The 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 = 120

Parameterized 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 = 150

Example 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("&", "&amp;")
         .replace("<", "&lt;")
         .replace(">", "&gt;")
         .replace('"', "&quot;")
         .replace("'", "&apos;")
    )

# 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)

Boxy SVG is recommended for creating and editing SVG images.

Workflow:

  1. Design your icon in Boxy SVG
  2. Name elements with IDs for easy location in SVG text
  3. Design at full size to determine limits, then resize
  4. Copy SVG text from editor
  5. Replace dynamic elements with {$paramName} placeholders
  6. 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 = 200

The 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.Italic

Gridder - 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 = 400

Radar 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 charts
  • line - Line charts
  • pie - Pie charts
  • doughnut - Doughnut charts
  • radar - Radar/spider charts
  • polarArea - Polar area charts
  • Many more…

See Quickchart documentation for complete options.

Chart Best Practices

  1. Keep it simple: Don’t overcrowd charts with too much data
  2. Use appropriate types: Match chart type to data (trends→line, comparisons→bar)
  3. Color wisely: Use color to highlight important data
  4. Label clearly: Always include titles and axis labels
  5. 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 = 80

Progress 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 = 120

Dashboard 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 = 260

Best 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 = 30

3. 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_HEIGHT

4. 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("&", "&amp;")
             .replace("<", "&lt;")
             .replace(">", "&gt;")
             .replace('"', "&quot;")
             .replace("'", "&apos;"))

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 = 200

SVG 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 issues

Text 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 space

Charts 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
}

Next Steps

Now that you understand creating node visuals:

  1. Review the Python Scripting Guide for overall script structure
  2. Learn about Creating Forms for user interaction
  3. See complete script examples showing images in context