Let us explore SVG in the browser. The default svg is 300x150 pixels, and it is scaled with the page.
html
<svg style="border: 1px solid gray"/>
Grid Pattern
A grid pattern helps viewers understand the coordinate system - its origin, extent, and scale. The grid pattern shows:
- Origin: Red dot at (0,0) centered in the grid with labeled axes
- Extent: Configurable via
extentvariable (default ±200) - Scale: Configurable
minorStep(default 10) andmajorStep(default 50) with auto-generated labels - Y-axis direction: Set
yUp = truefor math-style coordinates (positive y goes up),falsefor SVG default (positive y goes down) - Layering: Content in the SVG body appears in
#contentLayeron top of#gridLayer, with#labelLayeralways on top
html
<svg id="gridSvg" style="border: 1px solid gray">
<circle cx="50" cy="50" r="20" fill="blue"/>
<rect x="-50" y="0" width="50" height="50" fill="none" stroke="red" stroke-width="2"/>
<line x1="0" y1="0" x2="-100" y2="100" stroke="green" stroke-width="2"/>
<text x="0" y="160"
font-family="Georgia, serif"
font-size="16"
font-weight="bold"
font-style="italic"
fill="#141a78"
text-anchor="middle"
dominant-baseline="middle">
Centered Text
</text>
<g transform="translate(-150, 100)">
<!-- Triangle (polygon) -->
<polygon points="0,-15 13,7.5 -13,7.5" fill="none" stroke="#e74c3c" stroke-width="2"/>
</g>
<g transform="translate(-50, 100)">
<!-- Square (polygon) -->
<polygon points="-12,-12 12,-12 12,12 -12,12" fill="none" stroke="#e67e22" stroke-width="2"/>
</g>
<g transform="translate(50, 100)">
<!-- Pentagon (polygon) -->
<polygon points="0,-15 14.3,-4.6 8.8,12.1 -8.8,12.1 -14.3,-4.6" fill="none" stroke="#27ae60" stroke-width="2"/>
</g>
<g transform="translate(150, 100)">
<!-- Hexagon (polygon) -->
<polygon points="15,0 7.5,13 -7.5,13 -15,0 -7.5,-13 7.5,-13" fill="none" stroke="#3498db" stroke-width="2"/>
</g>
<g transform="translate(-150, 140)">
<!-- Triangle (path) -->
<path d="M 0,-15 L 13,7.5 L -13,7.5 Z" fill="none" stroke="#e74c3c" stroke-width="2"/>
</g>
<g transform="translate(-50, 140)">
<!-- Square (path) -->
<path d="M -12,-12 L 12,-12 L 12,12 L -12,12 Z" fill="none" stroke="#e67e22" stroke-width="2"/>
</g>
<g transform="translate(50, 140)">
<!-- Pentagon (path) -->
<path d="M 0,-15 L 14.3,-4.6 L 8.8,12.1 L -8.8,12.1 L -14.3,-4.6 Z" fill="none" stroke="#27ae60" stroke-width="2"/>
</g>
<g transform="translate(150, 140)">
<!-- Hexagon (path) -->
<path d="M 15,0 L 7.5,13 L -7.5,13 L -15,0 L -7.5,-13 L 7.5,-13 Z" fill="none" stroke="#3498db" stroke-width="2"/>
</g>
<!-- Bezier curves in lower-left quadrant (negative x, positive y in SVG coords = negative y when yUp) -->
<g transform="translate(-150, -50)">
<!-- Quadratic Bezier: Q (one control point) -->
<path d="M -40,0 Q 0,-40 40,0" fill="none" stroke="#9b59b6" stroke-width="2"/>
<!-- Show control point -->
<circle cx="0" cy="-40" r="3" fill="#9b59b6" opacity="0.5"/>
<line x1="-40" y1="0" x2="0" y2="-40" stroke="#9b59b6" stroke-width="0.5" stroke-dasharray="3"/>
<line x1="0" y1="-40" x2="40" y2="0" stroke="#9b59b6" stroke-width="0.5" stroke-dasharray="3"/>
<text x="0" y="15" font-size="8" text-anchor="middle">Q (quadratic)</text>
</g>
<g transform="translate(-50, -50)">
<!-- Cubic Bezier: C (two control points) -->
<path d="M -40,0 C -20,-50 20,50 40,0" fill="none" stroke="#e74c3c" stroke-width="2"/>
<!-- Show control points -->
<circle cx="-20" cy="-50" r="3" fill="#e74c3c" opacity="0.5"/>
<circle cx="20" cy="50" r="3" fill="#e74c3c" opacity="0.5"/>
<line x1="-40" y1="0" x2="-20" y2="-50" stroke="#e74c3c" stroke-width="0.5" stroke-dasharray="3"/>
<line x1="20" y1="50" x2="40" y2="0" stroke="#e74c3c" stroke-width="0.5" stroke-dasharray="3"/>
<text x="0" y="15" font-size="8" text-anchor="middle">C (cubic)</text>
</g>
<g transform="translate(-150, -120)">
<!-- Smooth Quadratic: T (reflects previous Q control point) -->
<path d="M -40,0 Q -20,-30 0,0 T 40,0" fill="none" stroke="#3498db" stroke-width="2"/>
<text x="0" y="15" font-size="8" text-anchor="middle">Q...T (smooth quad)</text>
</g>
<g transform="translate(-50, -120)">
<!-- Smooth Cubic: S (reflects previous C control point) -->
<path d="M -40,0 C -30,-30 -10,-30 0,0 S 30,30 40,0" fill="none" stroke="#27ae60" stroke-width="2"/>
<text x="0" y="15" font-size="8" text-anchor="middle">C...S (smooth cubic)</text>
</g>
<!-- Stroke end caps in lower-right quadrant -->
<g transform="translate(50, -50)">
<line x1="-30" y1="0" x2="30" y2="0" stroke="#333" stroke-width="10" stroke-linecap="butt"/>
<text x="0" y="20" font-size="8" text-anchor="middle">butt (default)</text>
</g>
<g transform="translate(150, -50)">
<line x1="-30" y1="0" x2="30" y2="0" stroke="#333" stroke-width="10" stroke-linecap="round"/>
<text x="0" y="20" font-size="8" text-anchor="middle">round</text>
</g>
<g transform="translate(100, -100)">
<line x1="-30" y1="0" x2="30" y2="0" stroke="#333" stroke-width="10" stroke-linecap="square"/>
<text x="0" y="20" font-size="8" text-anchor="middle">square</text>
</g>
<!-- Stroke line joins in lower-right quadrant -->
<g transform="translate(50, -150)">
<path d="M -20,10 L 0,-10 L 20,10" fill="none" stroke="#333" stroke-width="8" stroke-linejoin="miter"/>
<text x="0" y="25" font-size="8" text-anchor="middle">miter (default)</text>
</g>
<g transform="translate(150, -150)">
<path d="M -20,10 L 0,-10 L 20,10" fill="none" stroke="#333" stroke-width="8" stroke-linejoin="round"/>
<text x="0" y="25" font-size="8" text-anchor="middle">round</text>
</g>
<g transform="translate(100, -190)">
<path d="M -20,10 L 0,-10 L 20,10" fill="none" stroke="#333" stroke-width="8" stroke-linejoin="bevel"/>
<text x="0" y="25" font-size="8" text-anchor="middle">bevel</text>
</g>
</svg>
js
import {elt} from '/lib/svg.js';
const svg = elt('gridSvg');
svg.setAttribute('width', '100%');
// Grid configuration
const extent = 200; // grid spans -extent to +extent on both axes
const majorStep = 50; // major gridlines every N units
const minorStep = 10; // minor gridlines every N units
const yUp = true; // true: positive y goes up (math style), false: positive y goes down (SVG default)
// Compute viewBox with padding for labels
const padding = 20;
const viewMin = -extent - padding;
const viewSize = (extent + padding) * 2;
svg.setAttribute('viewBox', `${viewMin} ${viewMin} ${viewSize} ${viewSize}`);
// preserveAspectRatio: <align> [<meetOrSlice>]
// align: none | xMinYMin | xMidYMin | xMaxYMin | xMinYMid | xMidYMid | xMaxYMid | xMinYMax | xMidYMax | xMaxYMax
// meetOrSlice: meet (fit inside, letterbox) | slice (fill, crop overflow)
// https://www.w3.org/TR/SVG2/coords.html#PreserveAspectRatioAttribute
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
// Generate scale labels at each major step
const xLabels = [];
const yLabels = [];
for (let v = -extent; v <= extent; v += majorStep) {
if (v === 0) continue; // skip origin
xLabels.push(`<text x="${v}" y="${15}" text-anchor="middle">${v}</text>`);
// For y labels: if yUp, negate the display value (SVG y=-100 means math y=+100)
const yDisplay = yUp ? -v : v;
yLabels.push(`<text x="${-12}" y="${v + 4}" text-anchor="end">${yDisplay}</text>`);
}
// Save existing content nodes before modifying
const existingNodes = [...svg.childNodes];
// Insert grid and labels
svg.innerHTML = `
<defs>
<pattern id="minorGrid" width="${minorStep}" height="${minorStep}" patternUnits="userSpaceOnUse">
<path d="M ${minorStep} 0 L 0 0 0 ${minorStep}" fill="none" stroke="#e0e0e0" stroke-width="0.5"/>
</pattern>
<pattern id="majorGrid" width="${majorStep}" height="${majorStep}" patternUnits="userSpaceOnUse">
<rect width="${majorStep}" height="${majorStep}" fill="url(#minorGrid)"/>
<path d="M ${majorStep} 0 L 0 0 0 ${majorStep}" fill="none" stroke="#a0a0a0" stroke-width="1"/>
</pattern>
</defs>
<!-- Grid layer (behind content) -->
<g id="gridLayer">
<rect x="${-extent}" y="${-extent}" width="${extent * 2}" height="${extent * 2}" fill="url(#majorGrid)"/>
<rect x="${-extent}" y="${-extent}" width="${extent * 2}" height="${extent * 2}" fill="none" stroke="#333" stroke-width="1"/>
<line x1="${-extent}" y1="0" x2="${extent}" y2="0" stroke="#e74c3c" stroke-width="1" opacity="0.6"/>
<line x1="0" y1="${-extent}" x2="0" y2="${extent}" stroke="#27ae60" stroke-width="1" opacity="0.6"/>
<circle cx="0" cy="0" r="4" fill="#e74c3c" opacity="0.8"/>
</g>
<!-- Content layer (on top of grid, with optional y-flip) -->
<g id="contentLayer" ${yUp ? 'transform="scale(1,-1)"' : ''}></g>
<!-- Labels layer (always on top, never flipped) -->
<g id="labelLayer" font-size="8" fill="#666">
<text x="8" y="${yUp ? 12 : -8}" font-size="10" fill="#333">(0,0)</text>
${xLabels.join('\n ')}
${yLabels.join('\n ')}
</g>
`;
// Move existing content into contentLayer
const contentLayer = svg.querySelector('#contentLayer');
existingNodes.forEach(node => contentLayer.appendChild(node));
// If yUp, flip text elements back so they're readable
if (yUp) {
contentLayer.querySelectorAll('text').forEach(text => {
const x = text.getAttribute('x') || 0;
const y = text.getAttribute('y') || 0;
const existing = text.getAttribute('transform') || '';
text.setAttribute('transform', `${existing} translate(${x},${y}) scale(1,-1) translate(${-x},${-y})`.trim());
});
}
© 2026 simpatico