SVG Graph Workflow
What this file covers: Complete workflow for creating SVG coordinate graphs, pixel formulas, pre-calculated tables, and validation rules.
Only read this file if Visual Type = "SVG visual" (coordinate graphs).
For non-graph diagrams (tape diagrams, hangers, etc.), see analysis/diagram-patterns.md.
Workflow Overview
SVG graphs are the ONLY component that requires the clone-and-modify workflow. All other patterns use TSX component composition.
Step 1: READ graph patterns ← Copy the complete SVG structure
Step 2: READ this file ← Get formulas and tables
Step 3: CALCULATE pixel positions ← For your specific scale (640x360 viewport)
Step 4: MODIFY the copied SVG ← Replace values
Step 5: ADD annotations ← Add labels and annotations
Step 6: WRAP in data-element-id groups ← For fixture testing
Step 7: VERIFY grid alignment ← Run the checklist
DO NOT create graphs from scratch. Always copy and modify from existing graph patterns.
Required Reading
Before using the formulas below, READ these pattern files:
READ: graph pattern files ← SOURCE OF TRUTH for SVG structure
READ: annotation patterns ← SOURCE OF TRUTH for annotation patterns
This markdown file contains only formulas and tables. The patterns you copy and modify are in the files above.
SVG Graph Checklist (VERIFY BEFORE WRITING)
Structure:
- Started from graph patterns (NOT from scratch)
- SVG uses
viewBox="0 0 640 360"(or appropriate sub-viewport) - Graph elements wrapped in
<g data-element-id="...">for fixture testing
Coordinate System:
- X_MAX and Y_MAX set correctly for your data
- Grid lines align with axis labels (same pixel values)
- Single "0" at origin (not two separate zeros)
- Scale labels go to last tick before arrow
Axes & Lines:
- Axes have arrowheads (marker-end)
- Data lines extend to plot edges with arrows
- All lines use correct colors from styling.md
Progressive Reveal:
- Each step's additions wrapped in
<Animated>with correct segment - Each annotation group has
data-element-idfor fixture testing - Progressive elements use
show()predicate for visibility
Grid Alignment Rules (CRITICAL)
The #1 problem with SVG graphs is misaligned grids.
Rule 1: Use Consistent Spacing Formula
All coordinate calculations MUST use the same linear interpolation formula:
pixelX = ORIGIN_X + (dataX / X_MAX) * PLOT_WIDTH
pixelY = ORIGIN_Y - (dataY / Y_MAX) * PLOT_HEIGHT
Where:
ORIGIN_X,ORIGIN_Y= pixel coordinates of the origin (0,0) pointPLOT_WIDTH= width of the plot area in pixelsPLOT_HEIGHT= height of the plot area in pixelsX_MAX,Y_MAX= maximum data values on each axis
Rule 2: Grid Lines Must Match Labels
If you place a label at x=60 for value "0", x=210 for value "5", and x=360 for value "10":
- Grid lines MUST be at x=60, 210, 360 (NOT different values)
- The spacing is (360-60)/(10-0) = 30 pixels per unit
Rule 3: Define Constants First
Before writing any SVG, define these values for the 640x360 viewport:
ORIGIN_X = 60 // Left edge of plot (after Y-axis labels)
ORIGIN_Y = 310 // Bottom edge of plot (above X-axis labels)
PLOT_WIDTH = 530 // Width from origin to right edge
PLOT_HEIGHT = 270 // Height from origin to top edge
X_MAX = 10 // Maximum X value (varies per graph)
Y_MAX = 100 // Maximum Y value (varies per graph)
Note: These constants are scaled for the 640x360 viewport. The previous 960x540 system used different values (ORIGIN_X=40, ORIGIN_Y=170, PLOT_WIDTH=220, PLOT_HEIGHT=150 in a 280x200 viewBox). All calculations below use the new viewport dimensions.
Axis Requirements
Every coordinate plane MUST have all 5 elements:
1. Tick Marks at Each Label Position
<!-- X-axis ticks (5px below axis, from y=310 to y=315) -->
<g stroke="#1e293b" stroke-width="1.5">
<line x1="60" y1="310" x2="60" y2="315" />
<line x1="193" y1="310" x2="193" y2="315" />
<line x1="325" y1="310" x2="325" y2="315" />
<!-- ... one tick per label position -->
</g>
<!-- Y-axis ticks (5px left of axis, from x=55 to x=60) -->
<g stroke="#1e293b" stroke-width="1.5">
<line x1="55" y1="310" x2="60" y2="310" />
<line x1="55" y1="242.5" x2="60" y2="242.5" />
<!-- ... one tick per label position -->
</g>
2. Arrowheads on Both Axes
<defs>
<marker
id="axis-arrow"
markerWidth="10"
markerHeight="7"
refX="9"
refY="3.5"
orient="auto"
>
<polygon points="0 0, 10 3.5, 0 7" fill="#1e293b" />
</marker>
</defs>
<!-- X-axis with arrow (extends 10px past last label) -->
<line
x1="60"
y1="310"
x2="600"
y2="310"
stroke="#1e293b"
stroke-width="2"
marker-end="url(#axis-arrow)"
/>
<!-- Y-axis with arrow (extends 10px past last label) -->
<line
x1="60"
y1="320"
x2="60"
y2="25"
stroke="#1e293b"
stroke-width="2"
marker-end="url(#axis-arrow)"
/>
3. Single "0" at Origin (NOT two separate zeros)
<!-- ONE zero label at origin, positioned to serve both axes -->
<text
x="50"
y="325"
fill="#64748b"
font-family="Arial"
font-size="12"
text-anchor="end"
>0</text
>
WRONG:
<!-- DON'T do this - two separate zeros -->
<text x="60" y="330">0</text>
<!-- X-axis zero -->
<text x="50" y="314">0</text>
<!-- Y-axis zero - WRONG! -->
4. Complete Scale Labels (to the arrows)
Labels must go all the way to the last tick mark before the arrow:
- X-axis: 0, 10, 20, 30, 40, 50 (if X_MAX=50)
- Y-axis: 0, 10, 20, 30, 40, 50 (if Y_MAX=50)
Scale must be consistent - use increments of 5, 10, 20, 25, 50, or 100.
5. Axis Labels (Optional)
If including axis labels like "x" and "y":
<text
x="610"
y="315"
fill="#64748b"
font-family="Arial"
font-size="13"
font-style="italic"
>x</text
>
<text
x="65"
y="20"
fill="#64748b"
font-family="Arial"
font-size="13"
font-style="italic"
>y</text
>
Line Extension Rules
Lines must extend to the edges of the plot area with arrows showing they continue beyond.
How to Calculate Line Endpoints
For a line y = mx + b within plot area (0, 0) to (X_MAX, Y_MAX):
Step 1: Calculate where line intersects plot boundaries
Left edge (x=0): y = b
Right edge (x=X_MAX): y = m * X_MAX + b
Top edge (y=Y_MAX): x = (Y_MAX - b) / m
Bottom edge (y=0): x = -b / m
Step 2: Determine entry point (where line enters plot area)
- If 0 <= b <= Y_MAX: entry is (0, b) on left edge
- If b < 0: entry is (-b/m, 0) on bottom edge
- If b > Y_MAX: entry is ((Y_MAX-b)/m, Y_MAX) on top edge
Step 3: Determine exit point (where line exits plot area)
- Calculate y at x=X_MAX:
y_exit = m * X_MAX + b - If 0 <= y_exit <= Y_MAX: exit is (X_MAX, y_exit) on right edge
- If y_exit > Y_MAX: exit is ((Y_MAX-b)/m, Y_MAX) on top edge
- If y_exit < 0: exit is (-b/m, 0) on bottom edge
Step 4: Draw line with arrow at exit point
- Use
marker-end="url(#line-arrow)"to show line continues
Line Arrow Marker (separate from axis arrows)
<defs>
<marker
id="line-arrow"
markerWidth="6"
markerHeight="4"
refX="5"
refY="2"
orient="auto"
>
<polygon points="0 0, 6 2, 0 4" fill="currentColor" />
</marker>
</defs>
Examples
Example 1: y = 10x (steep, hits top before right edge)
- X_MAX=8, Y_MAX=80
- Entry: (0, 0) - starts at origin
- At x=8: y=80 - exactly at corner
- Exit: (8, 80) - right-top corner
Example 2: y = 5x + 20 (moderate slope, y-intercept at 20)
- X_MAX=8, Y_MAX=80
- Entry: (0, 20) - left edge at y=20
- At x=8: y=60 - still within Y_MAX
- Exit: (8, 60) - right edge at y=60
Example 3: y = 20x (very steep, exits through top)
- X_MAX=8, Y_MAX=80
- Entry: (0, 0) - origin
- At x=4: y=80 - hits top
- Exit: (4, 80) - top edge at x=4
Pixel Conversion for Line Endpoints
After calculating data coordinates, convert to pixels using the 640x360 viewport constants:
pixelX = ORIGIN_X + (dataX / X_MAX) * PLOT_WIDTH
pixelY = ORIGIN_Y - (dataY / Y_MAX) * PLOT_HEIGHT
With the standard constants (ORIGIN_X=60, ORIGIN_Y=310, PLOT_WIDTH=530, PLOT_HEIGHT=270):
pixelX = 60 + (dataX / X_MAX) * 530
pixelY = 310 - (dataY / Y_MAX) * 270
Quick Reference: Pixel Calculations
Standard Plot Area (viewBox 640x360)
| Constant | Value | Purpose |
|---|---|---|
| ORIGIN_X | 60 | X pixel of origin |
| ORIGIN_Y | 310 | Y pixel of origin |
| PLOT_WIDTH | 530 | Pixels from x=0 to x=max |
| PLOT_HEIGHT | 270 | Pixels from y=0 to y=max |
| LABEL_Y_OFFSET | 330 | Y pixel for X-axis labels |
| LABEL_X_OFFSET | 50 | X pixel for Y-axis labels |
Conversion Formulas
// Data to Pixel (640x360 viewport)
function dataToPixelX(dataX, xMax) {
return 60 + (dataX / xMax) * 530;
}
function dataToPixelY(dataY, yMax) {
return 310 - (dataY / yMax) * 270;
}
// Example: Point (6, 45) with X_MAX=10, Y_MAX=100
// pixelX = 60 + (6/10)*530 = 60 + 318 = 378
// pixelY = 310 - (45/100)*270 = 310 - 121.5 = 188.5
Common X-Axis Scales
Use these pre-calculated values for common scales (ORIGIN_X=60, PLOT_WIDTH=530):
X: 0 to 4 (spacing = 132.5px per unit)
| Data | Pixel |
|---|---|
| 0 | 60 |
| 1 | 192.5 |
| 2 | 325 |
| 3 | 457.5 |
| 4 | 590 |
X: 0 to 5 (spacing = 106px per unit)
| Data | Pixel |
|---|---|
| 0 | 60 |
| 1 | 166 |
| 2 | 272 |
| 3 | 378 |
| 4 | 484 |
| 5 | 590 |
X: 0 to 8 (spacing = 66.25px per unit)
| Data | Pixel |
|---|---|
| 0 | 60 |
| 2 | 192.5 |
| 4 | 325 |
| 6 | 457.5 |
| 8 | 590 |
X: 0 to 10 (spacing = 53px per unit)
| Data | Pixel |
|---|---|
| 0 | 60 |
| 2 | 166 |
| 4 | 272 |
| 5 | 325 |
| 6 | 378 |
| 8 | 484 |
| 10 | 590 |
X: 0 to 12 (spacing = 44.17px per unit)
| Data | Pixel |
|---|---|
| 0 | 60 |
| 3 | 192.5 |
| 6 | 325 |
| 9 | 457.5 |
| 12 | 590 |
X: 0 to 20 (spacing = 26.5px per unit)
| Data | Pixel |
|---|---|
| 0 | 60 |
| 5 | 192.5 |
| 10 | 325 |
| 15 | 457.5 |
| 20 | 590 |
Common Y-Axis Scales
Use these pre-calculated values (ORIGIN_Y=310, PLOT_HEIGHT=270):
Y: 0 to 100 (spacing = 2.7px per unit)
| Data | Pixel |
|---|---|
| 0 | 310 |
| 25 | 242.5 |
| 50 | 175 |
| 75 | 107.5 |
| 100 | 40 |
Y: 0 to 80 (spacing = 3.375px per unit)
| Data | Pixel |
|---|---|
| 0 | 310 |
| 20 | 242.5 |
| 40 | 175 |
| 60 | 107.5 |
| 80 | 40 |
Y: 0 to 200 (spacing = 1.35px per unit)
| Data | Pixel |
|---|---|
| 0 | 310 |
| 50 | 242.5 |
| 100 | 175 |
| 150 | 107.5 |
| 200 | 40 |
Y: 0 to 400 (spacing = 0.675px per unit)
| Data | Pixel |
|---|---|
| 0 | 310 |
| 100 | 242.5 |
| 200 | 175 |
| 300 | 107.5 |
| 400 | 40 |
Scale Selection Reference
Target: 10 or fewer ticks per axis
X-AXIS (ORIGIN_X=60, PLOT_WIDTH=530):
| X_MAX | Increment | Ticks | Pixel positions |
|---|---|---|---|
| 4 | 1 | 5 | 0->60, 1->192.5, 2->325, 3->457.5, 4->590 |
| 5 | 1 | 6 | 0->60, 1->166, 2->272, 3->378, 4->484, 5->590 |
| 6 | 1 | 7 | 0->60, 1->148, 2->237, 3->325, 4->413, 5->502, 6->590 |
| 8 | 2 | 5 | 0->60, 2->192.5, 4->325, 6->457.5, 8->590 |
| 10 | 2 | 6 | 0->60, 2->166, 4->272, 6->378, 8->484, 10->590 |
Y-AXIS (ORIGIN_Y=310, PLOT_HEIGHT=270):
| Y_MAX | Increment | Ticks | Notes |
|---|---|---|---|
| 9 | 1 | 10 | Max for counting by 1s |
| 18 | 2 | 10 | Max for counting by 2s |
| 36 | 4 | 10 | Count by 4s |
| 45 | 5 | 10 | Count by 5s |
| 72 | 8 | 10 | Count by 8s |
| 90 | 10 | 10 | Count by 10s |
RULE: Grid lines at EVERY tick position. Never skip values!
Preventing Element Overlap (CRITICAL)
The #2 problem with SVG graphs is overlapping elements.
Recommended Element Sizes
| Element | Recommended Size | Max Size |
|---|---|---|
| Data point circles | r="5" to r="7" | r="8" |
| Point labels | font-size="11" to "12" | font-size="13" |
| Arrow stroke width | stroke-width="2" | stroke-width="3" |
| Arrow markers | markerWidth="6" markerHeight="4" | markerWidth="8" markerHeight="5" |
| Annotation text | font-size="11" | font-size="13" |
Note: Element sizes are slightly larger than the old 280x200 viewBox system because the 640x360 viewport provides more space.
Label Positioning Strategy
Point labels - Position AWAY from other elements:
- If point is in upper area: place label ABOVE (y - 12px)
- If point is in lower area: place label BELOW (y + 18px)
- If two points are close horizontally: stagger labels (one above, one below)
- Never place labels directly on the axes
Annotation labels (rise/run, change in y/x):
- Position to the LEFT of vertical arrows (x - 30px)
- Position BELOW horizontal arrows (y + 18px)
- Use font-size="11" for annotations
Minimum Spacing Guidelines
| Between | Minimum Distance |
|---|---|
| Point label and point center | 12px |
| Point label and axis | 18px |
| Two point labels | 24px |
| Arrow end and target point | 6px gap |
| Annotation text and arrow line | 4px |
Overlap Scenarios to Check
Before finalizing, verify NO overlaps between:
- Point labels and data points
- Point labels and axis labels
- Point labels and grid lines (especially at intersections)
- Arrow markers and data points
- Arrow markers and axes
- Annotation text and arrows
- Two point labels (when points are close together)
Common Mistakes to Avoid
WRONG: Hardcoded unrelated grid positions
<!-- BAD: Grid lines don't match labels -->
<line x1="200" y1="40" x2="200" y2="310" />
<!-- Grid at x=200 -->
<text x="190" y="330">2</text>
<!-- Label at x=190 - MISMATCH! -->
CORRECT: Grid and labels use same positions
<!-- GOOD: Grid lines match labels -->
<line x1="166" y1="40" x2="166" y2="310" />
<!-- Grid at x=166 -->
<text x="166" y="330">2</text>
<!-- Label at x=166 - ALIGNED! -->
WRONG: Inconsistent spacing
<!-- BAD: Spacing not uniform -->
<text x="60">0</text>
<!-- 0 at 60 -->
<text x="200">2</text>
<!-- 2 at 200 (140px from 0) -->
<text x="310">4</text>
<!-- 4 at 310 (110px from 2) - WRONG! -->
CORRECT: Uniform spacing
<!-- GOOD: Each tick is 106px apart (X_MAX=5) -->
<text x="60">0</text>
<!-- 0 at 60 -->
<text x="166">1</text>
<!-- 1 at 166 (106px from 0) -->
<text x="272">2</text>
<!-- 2 at 272 (106px from 1) -->
Progressive Reveal for Graph Elements
Each step's additions to the graph should be wrapped for progressive reveal:
{/* Base graph - always visible from "problem-setup" onward */}
<g data-element-id="graph-base">
{/* Axes, grid, initial elements */}
</g>
{/* Step 1 additions - visible from "step-1" onward */}
<Animated segment="step-1" isVisible={s => show(s)} entrance="fade">
<g data-element-id="step-1-graph-addition">
{/* New line, point, or annotation added at step 1 */}
</g>
</Animated>
{/* Step 2 additions - visible from "step-2" onward */}
<Animated segment="step-2" isVisible={s => show(s)} entrance="fade">
<g data-element-id="step-2-graph-addition">
{/* New annotation or highlight added at step 2 */}
</g>
</Animated>
Printable Worksheet SVG
For printable slides, use smaller dimensions and monochrome colors. See printable pattern files for complete examples.
Key differences from projection SVG:
- Smaller viewBox (300x225 vs 640x360)
- Black on white colors only
- No animations or progressive reveal
Colors Reference
| Use | Color | Hex |
|---|---|---|
| Line 1 | Blue | #60a5fa |
| Line 2 | Green | #22c55e |
| Line 3 | Red | #ef4444 |
| Axis/Grid | Slate | #1e293b |
| Labels | Gray | #64748b |
| Light grid | Slate | #e2e8f0 |