Technical Specifications

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-id for 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) point
  • PLOT_WIDTH = width of the plot area in pixels
  • PLOT_HEIGHT = height of the plot area in pixels
  • X_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