canvas-design.md
# Canvas Design Guide
Reference for `create_visual_design` tool. Follow this process for posters, infographics, artwork, and flow diagrams.
## Design Philosophy Approach
Every visual design follows a 2-step process:
### Step 1: Establish a Design Philosophy
Before writing any code, define the visual intent in 4-6 internal sentences covering:
- **Space & Form**: How will negative space interact with shapes? Dense or sparse?
- **Color & Texture**: What emotional tone? What palette from SKILL.md?
- **Scale & Rhythm**: Are elements uniform or varied? Is there visual rhythm?
- **Composition & Balance**: Symmetric or asymmetric? Where does the eye travel?
### Step 2: Name the Movement
Give the design a 1-2 word movement name that captures its aesthetic:
- Examples: "Brutalist Joy", "Chromatic Silence", "Grid Meditation", "Neon Geometry"
- This name guides every decision — when in doubt, ask "does this serve the movement?"
## Craftsmanship Standards
Build as if a meticulous expert craftsperson is creating a gallery-quality piece:
- Every pixel/point placement is intentional
- Alignments are exact, not approximate
- Colors are chosen with purpose, not randomly
- Spacing is consistent and mathematically grounded
## Text Principles
Text in visual design is a visual element, not content:
- **Minimal**: Use as few words as possible
- **Visual accent**: Text serves composition, not information delivery
- **Typographic hierarchy**: Size and weight create visual rhythm
- **Never dominant**: Text should not compete with visual elements for attention
- **Proportional to canvas**: Text must be sized relative to the overall canvas and surrounding elements. A common failure is text that looks fine in code but renders far too small on the actual canvas. When in doubt, scale up. If you mentally shrink the output to 50%, every text element should still be legible.
## Subtle Reference
When the design has a subject (e.g., "AI poster", "data science infographic"):
- Reflect the theme through metaphor, not literal depiction
- Use abstract forms that evoke the subject
- Let the viewer discover meaning rather than stating it
- Geometric patterns, color relationships, and spatial arrangements carry meaning
## Canvas Production Principles
### Repetition & Pattern
- Systematic repetition creates visual rhythm
- Grids, arrays, and regular intervals provide structure
- Variation within repetition adds interest without chaos
### Perfect Geometry
- Circles are perfect circles, lines are crisp
- Use mathematical relationships for positioning (golden ratio, rule of thirds)
- Consistent border radii, stroke widths, and corner treatments
### Systematic Observation
- Every element relates to at least one other element
- Alignment guides connect disparate parts
- Color echoes across the composition create unity
## Refinement Process
**Two-pass minimum:**
### First Pass: Structure
- Establish the composition grid
- Place primary elements
- Set color palette application
- Define the visual hierarchy
### Second Pass: Polish
- Refine spacing (adjust by 1-2px/pt for perfection)
- Check all alignments
- Verify color contrast and readability
- Ensure nothing bleeds off canvas
- **Priority: refine existing composition over adding new elements**
## Library Save Patterns
### ReportLab (PDF)
```python
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
c = canvas.Canvas(output_filename, pagesize=A4)
width, height = A4
# ... drawing commands ...
c.save()
```
### Pillow (PNG)
```python
from PIL import Image, ImageDraw, ImageFont
img = Image.new('RGB', (3000, 2000), color='#0D1B2A')
draw = ImageDraw.Draw(img)
# ... drawing commands ...
img.save(output_filename, dpi=(300, 300))
```
### SVGWrite → PDF (works)
```python
import svgwrite
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPDF
dwg = svgwrite.Drawing('temp.svg', size=('800px', '600px'))
# ... drawing commands ...
dwg.save()
drawing = svg2rlg('temp.svg')
renderPDF.drawToFile(drawing, output_filename)
```
### SVGWrite → PNG (caution)
`renderPM` (rlPyCairo) is NOT available. For PNG output, prefer Pillow or matplotlib
directly instead of SVG conversion. See library-reference.md for fallback chains.
### matplotlib (non-chart graphics)
```python
import matplotlib.pyplot as plt
import matplotlib.patches as patches
fig, ax = plt.subplots(figsize=(20, 14))
ax.set_xlim(0, 100)
ax.set_ylim(0, 70)
ax.axis('off')
# ... patches, artists, text ...
plt.savefig(output_filename, dpi=300, bbox_inches='tight',
facecolor=fig.get_facecolor(), edgecolor='none')
```
## Canvas Sizes
| Format | Recommended Size | Use Case |
|--------|-----------------|----------|
| Poster (PNG) | 3000x4200px | Print-ready A3 portrait |
| Infographic (PNG) | 2400x4800px | Tall-scroll infographic |
| Landscape (PNG) | 3840x2160px | 4K presentation/wallpaper |
| Document (PDF) | A4 (595x842pt) | Standard document |
| Slide (PNG) | 1920x1080px | Presentation slide |
diagram-design.md
# Diagram Design Guide
Technical diagrams (architecture, flow, system structure) reference.
Supplements canvas-design.md with practical patterns from real iterations.
## Rendering Engine: SVG First
matplotlib is for charts. For box-arrow diagrams, use **svgwrite + cairosvg/wand**.
```python
import svgwrite, os
try:
import cairosvg; HC = True
except: HC = False
try:
from wand.image import Image as WI; HW = True
except: HW = False
dwg = svgwrite.Drawing('temp.svg', size=(f'{W}px', f'{H}px'),
viewBox=f'0 0 {W} {H}')
# ... draw ...
dwg.save()
if HC:
cairosvg.svg2png(url='temp.svg', write_to=output_filename,
output_width=W*2, output_height=H*2) # 2x for Retina
elif HW:
with WI(filename='temp.svg', resolution=300) as img:
img.save(filename=output_filename)
os.remove('temp.svg') # Clean up
```
## Text Vertical Centering
SVG `dominant_baseline` is inconsistent across renderers. Use manual offset:
```python
def tx(x, y, text, fs=14, fw='normal', fl='#333', a='middle'):
"""y = visual center. Add fs*0.35 for baseline correction."""
dwg.add(dwg.text(text, insert=(x, y + fs * 0.35),
font_size=f'{fs}px', font_weight=fw, fill=fl,
text_anchor=a, font_family='Helvetica, Arial, sans-serif'))
def tx_box(bx, by, bw, bh, text, **kwargs):
"""Center text inside a box."""
tx(bx + bw/2, by + bh/2, text, **kwargs)
```
Why 0.35: SVG text y = baseline. Cap height ≈ 70% of font_size. Half of that = 35%.
Adjust 0.3–0.4 for different fonts.
## Canvas Size: Derive from Content
Do NOT pick canvas size first. Calculate from content bounds:
```python
sk_xs = [RX_START + i * SK_GAP for i in range(num_cols)]
RIGHT_EDGE = sk_xs[-1] + BOX_W + PADDING
CANVAS_PAD = 18
W = RIGHT_EDGE + CANVAS_PAD
# Same for height: last element bottom + CANVAS_PAD
```
## Alignment: Shared Coordinate Variables
```python
# Row coordinates — all boxes on same row share same y
ROW_L2_Y = 27
ROW_L3_Y = 140
BOX_H = 42 # Uniform height
# Column coordinates — array-driven
sk_xs = [RX_START + i * SK_GAP for i in range(4)]
for i in range(4):
rr(sk_xs[i], ROW_L2_Y, SK_W, BOX_H, ...)
```
Never hardcode repeated coordinates. One variable per row/column.
## Arrow Patterns
### Straight horizontal
```python
def arrow_h(x1, y, x2, color, label=None, label_offset=-12):
ln(x1, y, x2-4, y, color, sw=2.5)
tri(x2-4, y, color, 10, 'right')
if label:
tx((x1+x2)/2, y + label_offset, label, fs=14, fw='bold', fl=color)
```
### L-shaped (bend)
```python
bend_x = start_x + 22
ln(start_x, start_y, bend_x, start_y, color) # horizontal
ln(bend_x, start_y, bend_x, target_y, color) # vertical
ln(bend_x, target_y, target_x, target_y, color) # horizontal
tri(target_x, target_y, color, 10) # arrowhead
```
### Label placement
- Horizontal arrow: above (y - 12)
- Vertical arrow: right side (x + 12)
- L-shaped: above last horizontal segment
- Match label color to arrow color
### Line styles
- **Solid arrow**: active call/access (Agent → Service)
- **Dashed line**: containment/belongs-to (Parent ⊃ Child)
```python
dwg.add(dwg.line((x1, y1), (x2, y2),
stroke='#AAA', stroke_width=1.5, stroke_dasharray='5,4'))
```
## Systematic Repeated Elements
Separate data from layout:
```python
skills = ['web-search', 'visual-design', 'word-docs', 'code-interp']
skill_resources = [['scripts.py'], ['design.md', 'eval.py'], ['tmpl.js'], ['config.json']]
sk_xs = [RX_START + i * SK_GAP for i in range(len(skills))]
for i, (name, resources) in enumerate(zip(skills, skill_resources)):
sx = sk_xs[i]
rr(sx, ROW_Y, SK_W, BOX_H, bg, bd)
tx_box(sx, ROW_Y, SK_W, BOX_H, name, fs=13)
for j, rname in enumerate(resources):
ry = res_start_y + j * (res_h + res_gap)
rr(sx, ry, SK_W, res_h, rbg, rbd)
```
## Color Tone Matching
When matching a reference style, check these dimensions:
| Dimension | Typical range | Example |
|-----------|--------------|---------|
| Saturation | 20-35% (pastel) | Muted, not vivid |
| Lightness | BG 90%+, Box 80-90%, Text 25-40% | |
| Temperature | Warm (beige/olive) or Cool (blue-gray) | |
| Border contrast | Fill color darkened 15-25% | |
Rules:
- Same role = same color (e.g., all SKILL.md boxes are pink)
- Regions use semi-transparent backgrounds for grouping (opacity 0.6–0.85)
- Text color = darkened version of box fill
## Margin Checklist
After each version, verify:
- Canvas edges: 15-20px padding all sides
- Region internal padding: 12-16px top/bottom, 14-20px left/right
- Box spacing: uniform within rows (use SK_GAP variable)
- Canvas bottom: last element + 15-20px = canvas height
## Canvas Size Guide
| Complexity | Size | Example |
|-----------|------|---------|
| Simple (3-5 boxes) | 600-800 × 200-300 | Single flow |
| Medium (6-15 boxes) | 800-1200 × 300-500 | Architecture overview |
| Complex (15+ boxes) | 1200-1600 × 500-800 | Detailed system structure |
Use these as starting estimates, then derive final size from content (see above).
library-reference.md
# Library Reference
Technical API patterns for visual design tools. Code examples for each library available in Code Interpreter.
## ReportLab
### Canvas Basics
```python
from reportlab.lib.pagesizes import A4, letter
from reportlab.lib.units import inch, cm, mm
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor, Color
c = canvas.Canvas(output_filename, pagesize=A4)
w, h = A4 # 595.27, 841.89 points
```
### Shapes
```python
# Rectangle
c.setFillColor(HexColor('#1E2761'))
c.rect(x, y, width, height, fill=1, stroke=0)
# Rounded rectangle
c.roundRect(x, y, width, height, radius=10, fill=1, stroke=0)
# Circle
c.circle(cx, cy, radius, fill=1, stroke=0)
# Line
c.setStrokeColor(HexColor('#408EC6'))
c.setLineWidth(2)
c.line(x1, y1, x2, y2)
# Bezier curve
c.bezier(x1, y1, cx1, cy1, cx2, cy2, x2, y2)
```
### Text
```python
# Simple text
c.setFont("Helvetica-Bold", 36)
c.setFillColor(HexColor('#FFFFFF'))
c.drawString(x, y, "Title Text")
# Centered text
c.drawCentredString(w/2, y, "Centered Title")
# Right-aligned text
c.drawRightString(w - 50, y, "Right Text")
```
### Font Registration
```python
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
pdfmetrics.registerFont(TTFont('CustomFont', '/path/to/font.ttf'))
c.setFont('CustomFont', 24)
```
### Gradients
```python
from reportlab.lib.colors import linearlyInterpolatedColor
# Manual gradient via thin rectangles
steps = 100
for i in range(steps):
ratio = i / steps
color = linearlyInterpolatedColor(
HexColor('#065A82'), HexColor('#1B9AAA'),
0, 1, ratio
)
c.setFillColor(color)
c.rect(0, h * ratio, w, h / steps + 1, fill=1, stroke=0)
```
### Transparency
```python
c.saveState()
c.setFillAlpha(0.5)
c.setFillColor(HexColor('#408EC6'))
c.circle(200, 400, 80, fill=1, stroke=0)
c.restoreState()
```
### Clipping
```python
p = c.beginPath()
p.circle(200, 400, 100)
c.clipPath(p, stroke=0)
# Everything drawn after this is clipped to the circle
```
---
## Pillow
### Image Creation
```python
from PIL import Image, ImageDraw, ImageFont, ImageFilter
img = Image.new('RGBA', (3000, 2000), (13, 27, 42, 255))
draw = ImageDraw.Draw(img)
```
### Shapes
```python
# Rectangle
draw.rectangle([x1, y1, x2, y2], fill='#1E2761', outline='#408EC6', width=2)
# Rounded rectangle
draw.rounded_rectangle([x1, y1, x2, y2], radius=20, fill='#1E2761')
# Circle / ellipse
draw.ellipse([x-r, y-r, x+r, y+r], fill='#408EC6')
# Line
draw.line([(x1, y1), (x2, y2)], fill='#E8E8E8', width=3)
# Polygon
draw.polygon([(x1, y1), (x2, y2), (x3, y3)], fill='#97BC62')
```
### Text
```python
# Load font (check available fonts first)
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 72)
except:
font = ImageFont.load_default()
draw.text((x, y), "Title", fill='#FFFFFF', font=font)
# Centered text
bbox = draw.textbbox((0, 0), "Title", font=font)
text_w = bbox[2] - bbox[0]
draw.text(((img.width - text_w) / 2, y), "Title", fill='#FFFFFF', font=font)
```
### Alpha Compositing
```python
# Create overlay with transparency
overlay = Image.new('RGBA', img.size, (0, 0, 0, 0))
overlay_draw = ImageDraw.Draw(overlay)
overlay_draw.rectangle([0, 0, 800, 600], fill=(30, 39, 97, 128)) # 50% alpha
img = Image.alpha_composite(img, overlay)
```
### Filters
```python
blurred = img.filter(ImageFilter.GaussianBlur(radius=5))
sharpened = img.filter(ImageFilter.SHARPEN)
```
### Save
```python
# For RGBA images, convert to RGB before saving as PNG
if img.mode == 'RGBA':
bg = Image.new('RGB', img.size, (255, 255, 255))
bg.paste(img, mask=img.split()[3])
bg.save(output_filename, dpi=(300, 300))
else:
img.save(output_filename, dpi=(300, 300))
```
---
## SVGWrite
### Drawing Creation
```python
import svgwrite
dwg = svgwrite.Drawing('temp.svg', size=('800px', '600px'),
viewBox='0 0 800 600')
```
### Basic Shapes
```python
# Rectangle
dwg.add(dwg.rect(insert=(10, 10), size=(200, 100),
fill='#1E2761', stroke='#408EC6', stroke_width=2))
# Circle
dwg.add(dwg.circle(center=(400, 300), r=80, fill='#408EC6'))
# Line
dwg.add(dwg.line(start=(0, 0), end=(800, 600),
stroke='#E8E8E8', stroke_width=2))
# Polygon
dwg.add(dwg.polygon(points=[(100, 100), (200, 50), (300, 100)],
fill='#97BC62'))
```
### Text
```python
dwg.add(dwg.text('Title', insert=(400, 50),
font_size='36px', font_family='Helvetica',
fill='#FFFFFF', text_anchor='middle'))
```
### Patterns & Repetition
```python
# Create a pattern
pattern = dwg.defs.add(dwg.pattern(id='dots', size=(20, 20),
patternUnits='userSpaceOnUse'))
pattern.add(dwg.circle(center=(10, 10), r=3, fill='#408EC6'))
dwg.add(dwg.rect(insert=(0, 0), size=('100%', '100%'),
fill='url(#dots)'))
```
### SVG Output & Conversion
**IMPORTANT**: `renderPM` (rlPyCairo) is NOT available in Code Interpreter.
Do NOT use `renderPM.drawToPIL()` or `renderPM.drawToFile()` for PNG conversion.
**SVG → PDF** (works):
```python
dwg.save()
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPDF
drawing = svg2rlg('temp.svg')
renderPDF.drawToFile(drawing, output_filename)
```
**SVG → PNG** — use one of these fallback chains:
```python
dwg.save()
# Option 1: cairosvg (preferred if available)
try:
import cairosvg
cairosvg.svg2png(url='temp.svg', write_to=output_filename,
output_width=3000) # Scale up for high DPI
print("Converted with cairosvg")
except ImportError:
pass
# Option 2: Wand (ImageMagick binding)
try:
from wand.image import Image as WandImage
with WandImage(filename='temp.svg') as img:
img.format = 'png'
img.save(filename=output_filename)
print("Converted with Wand")
except ImportError:
pass
# Option 3: SVG → PDF → PNG via Pillow (always works)
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPDF
from pdf2image import convert_from_path # or use Pillow + fitz
drawing = svg2rlg('temp.svg')
renderPDF.drawToFile(drawing, 'temp.pdf')
from PIL import Image
# If pdf2image is available:
try:
from pdf2image import convert_from_path
images = convert_from_path('temp.pdf', dpi=300)
images[0].save(output_filename)
print("Converted via SVG→PDF→PNG")
except ImportError:
print("pdf2image not available")
```
**Recommended approach**: If you need PNG output, prefer Pillow or matplotlib directly
instead of the SVG→PNG conversion chain. SVGWrite is best when PDF is the final format.
---
## matplotlib (Advanced Graphics)
### Non-Chart Graphics with Patches
```python
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.collections import PatchCollection
fig, ax = plt.subplots(figsize=(20, 14))
fig.set_facecolor('#0D1B2A')
ax.set_facecolor('#0D1B2A')
ax.set_xlim(0, 100)
ax.set_ylim(0, 70)
ax.axis('off')
# Rectangle
rect = patches.FancyBboxPatch((10, 10), 30, 20,
boxstyle='round,pad=0.5',
facecolor='#1E2761', edgecolor='#408EC6')
ax.add_patch(rect)
# Circle
circle = patches.Circle((60, 40), 10, facecolor='#408EC6', alpha=0.7)
ax.add_patch(circle)
# Arrow
ax.annotate('', xy=(70, 40), xytext=(45, 25),
arrowprops=dict(arrowstyle='->', color='#E8E8E8', lw=2))
# Text
ax.text(50, 65, 'Title', fontsize=28, color='white',
ha='center', va='center', fontweight='bold')
```
### Custom Styles
```python
plt.rcParams.update({
'figure.facecolor': '#0D1B2A',
'axes.facecolor': '#0D1B2A',
'text.color': '#E8E8E8',
'axes.labelcolor': '#E8E8E8',
'xtick.color': '#E8E8E8',
'ytick.color': '#E8E8E8',
})
```
### Save with Background
```python
plt.savefig(output_filename, dpi=300, bbox_inches='tight',
facecolor=fig.get_facecolor(), edgecolor='none',
pad_inches=0.1)
```
---
## fonttools — Font Discovery
### List Available Fonts
```python
import os
import glob
font_dirs = [
'/usr/share/fonts',
'/usr/local/share/fonts',
os.path.expanduser('~/.fonts'),
]
fonts = []
for d in font_dirs:
fonts.extend(glob.glob(os.path.join(d, '**/*.ttf'), recursive=True))
fonts.extend(glob.glob(os.path.join(d, '**/*.otf'), recursive=True))
for f in sorted(fonts):
print(os.path.basename(f))
```
### Inspect Font Properties
```python
from fontTools.ttLib import TTFont
font = TTFont('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf')
name_table = font['name']
for record in name_table.names:
if record.nameID in (1, 2, 4): # Family, Style, Full Name
print(f"{record.nameID}: {record.toUnicode()}")
```
### Common Code Interpreter Fonts
Typically available in Bedrock Code Interpreter:
- DejaVu Sans / DejaVu Serif / DejaVu Sans Mono
- Liberation Sans / Liberation Serif / Liberation Mono
- Noto Sans (may include CJK variants)
Always verify with the font discovery code above before assuming availability.
SKILL.md
---
name: visual-design
description: "Use this skill any time the user needs a visual output as an image
or PDF — charts, diagrams, posters, infographics, abstract artwork, or any
visual design. Trigger for: data visualization requests, poster/flyer creation,
infographic design, abstract or artistic visuals, architecture/flow diagrams,
or any request mentioning 'chart', 'graph', 'poster', 'infographic', 'design',
'visual', or referencing .png/.pdf image output."
---
# Visual Design
## Quick Reference
| Task | Tool | Guide |
|-----------------------------|------------------------|--------------------------------------------|
| Data chart or graph | `generate_chart` | Read SKILL.md Design Ideas |
| Poster / infographic / art | `create_visual_design` | Read [canvas-design.md](canvas-design.md) |
| Architecture / flow diagram | `create_visual_design` | Read [diagram-design.md](diagram-design.md)|
## Available Tools
### generate_chart
Data visualization. Executes matplotlib/plotly code to produce chart PNGs.
- `python_code` (str, required): Chart generation Python code
- `output_filename` (str, required): `.png` filename
### create_visual_design
Visual design creation: posters, infographics, artwork, diagrams.
Uses reportlab, Pillow, svgwrite, or any available library.
- `python_code` (str, required): Design generation Python code
- `output_filename` (str, required): `.png` or `.pdf` filename
## Available Libraries
| Purpose | Libraries | Output | Notes |
|---------|-----------|--------|-------|
| Data charts | matplotlib, plotly, bokeh | PNG | Best for charts |
| PDF design | reportlab, fpdf | PDF | Full control |
| Image design | Pillow + fonttools | PNG | Best for PNG designs |
| Vector graphics | svgwrite → svglib + renderPDF | SVG → PDF | SVG→PNG NOT supported (no renderPM) |
| Image processing | Wand (ImageMagick), opencv-python | PNG | Check availability first |
**IMPORTANT**: For PNG output, use Pillow or matplotlib. Do NOT use svgwrite→renderPM (rlPyCairo is unavailable).
## Design Workflow
### Data Charts (`generate_chart`)
1. Identify data structure and choose appropriate chart type
2. Select color palette (see Design Ideas below)
3. Write code with `plt.savefig(filename, dpi=300, bbox_inches='tight')`
4. Review the generated chart
### Visual Design (`create_visual_design`)
1. Establish design concept/philosophy (internally)
2. Follow the process in [canvas-design.md](canvas-design.md)
3. Select appropriate library and write code
4. Save: reportlab `canvas.save()`, Pillow `image.save()`, matplotlib `plt.savefig()`
5. Review output and refine
## Design Ideas
### Color Palettes
| Theme | Primary | Accent | Background |
|-------|---------|--------|------------|
| Midnight Executive | `1E2761` | `408EC6` | `0D1B2A` |
| Forest & Moss | `2C5F2D` | `97BC62` | `1A1A1A` |
| Coral Energy | `F96167` | `F9E795` | `2F3C7E` |
| Ocean Gradient | `065A82` | `1B9AAA` | `021B29` |
| Charcoal Minimal | `36454F` | `E8E8E8` | `1C1C1E` |
| Cherry Bold | `990011` | `FCF6F5` | `150E11` |
| Sage Calm | `84B59F` | `69A297` | `2D3A2D` |
| Warm Terracotta | `B85042` | `E7E8D1` | `2A1F1C` |
### Typography
Prefer thin/light fonts. Minimize text in designs.
| Element | Size | Style |
|---------|------|-------|
| Main title | 48-72pt | Bold or Thin |
| Subtext | 14-18pt | Light |
| Labels/captions | 8-12pt | Regular, muted |
**Text-to-Canvas Balance (IMPORTANT):**
- Text size must be proportional to the overall canvas and surrounding design elements
- Common mistake: text that is too small relative to the canvas, making it unreadable at normal viewing distance
- Rule of thumb: if you need to zoom in to read it, it's too small
- Titles should command attention — when in doubt, go larger
- Labels/captions should be clearly legible, not decorative afterthoughts
- Test: mentally shrink the output to 50% — all text should still be readable
### Spacing & Composition
- Generous margins (minimum 10% of canvas)
- Consistent spacing between elements
- No overlapping; all elements within canvas bounds
- Visual hierarchy: convey importance via size, color, position
### Avoid
- Elements flush to canvas edges (insufficient margins)
- Overlapping elements
- Too many colors (stick to 3-4)
- Excessive text — visual elements are the focus
- Default matplotlib styles without customization
## Code Requirements
- Code must save a file to disk
- Use the exact `output_filename` provided
- PNG: `dpi=300` or higher recommended
- PDF: A4 or Letter size recommended
- For Korean text: configure appropriate fonts
## QA
**Assume there are problems and look for them.**
1. Review the generated image/PDF
2. Check for overlapping elements, clipped text, insufficient margins
3. Verify sufficient color contrast
4. If issues found, fix the code and regenerate
5. Complete at least one fix-verify cycle before finishing
## UI Guidance (from tools-config)
**Tool Selection:**
- generate_chart: Data charts/graphs (matplotlib, plotly, bokeh) → PNG
- create_visual_design: Posters, infographics, artwork, flow diagrams (reportlab, Pillow, svgwrite) → PNG or PDF
**Code Requirements:**
- Charts: plt.savefig(filename, dpi=300, bbox_inches='tight')
- PDF designs: canvas.save() (reportlab) or equivalent
- Image designs: image.save(filename) (Pillow)
- PNG: dpi=300+ recommended
- PDF: A4 or Letter size recommended