Core & Formats
Raw .md

SVG Matrices & Layouts

How nittygrid maps animations onto custom-shaped displays drawn as SVG, how to import a background image in Matrix Setup, and how SVGs are kept safe offline.

Real displays are rarely a neat rectangle. A car light bar curves, a wearable follows the body, a sign has gaps. nittygrid handles this by splitting the problem in two:

  • Where animations draw: a plain grid of cells, like a tiny screen.
  • How your display looks: an SVG file (a vector drawing) where each shape is one physical LED.

Each frame, nittygrid reads the color of a grid cell and paints it onto the matching SVG shape. Effects never need to know your display's shape.


Add a background image

In Matrix setup you can import a photo or render of your product (PNG, JPG, WebP) behind your LEDs. This works for both regular grids and SVG layouts.

  • Align by dragging. Move and resize the LED overlay on the photo until it matches the real LEDs. Grids can be stretched freely (hold Shift on a corner to keep proportions). SVG layouts always keep their aspect ratio.
  • Image scale vs. Preview zoom. Image scale changes how big the photo is in the saved result. Preview zoom (or trackpad pinch) only magnifies your view while aligning.
  • It is saved with your work. The image and alignment are stored in matrix presets and in .ngproj files. An .mp4 export can include it too (Include preview image).

How to prepare an SVG

SVG can contain almost anything. For LED layouts, keep it simple.

  1. Draw one shape per LED

    Shapes inside <defs>, <clipPath>, <mask>, or <symbol> are ignored. Standalone <use> copies are rejected, so expand them into real shapes first.

  2. Optionally label rows and columns

    Add id="px-ROW-COL" (or data-row and data-col) with numbers that start at 0 in the top-left:

    xml
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 40">
      <rect id="px-0-0" data-led-id="px-0-0" data-row="0" data-col="0" x="4"  y="4" width="28" height="12" rx="2" />
      <rect id="px-0-1" data-led-id="px-0-1" data-row="0" data-col="1" x="44" y="4" width="28" height="12" rx="2" />
      <rect id="px-0-2" data-led-id="px-0-2" data-row="0" data-col="2" x="84" y="4" width="28" height="12" rx="2" />
    </svg>
  3. Or upload it without labels

    nittygrid sorts shapes by their center points: ascending y, then ascending x. It assigns the resulting list to consecutive grid cells. This is a spatial fallback, not a wiring order; add row and column labels when you need a specific mapping.

  4. Split cells (optional)

    If one cell holds two separately wired LEDs, such as an upper and a lower triangle, mark them data-part="0" and data-part="1". They stay two separate LEDs in exports.


Safety and cleanup

SVG files are XML and could in theory contain scripts or links to the internet. nittygrid cleans every SVG you upload, restore, or import from a project file.

  • No code runs. Scripts and animations are removed. The layout is shown inside an isolated shadow root and never parsed as HTML, so its styles cannot change the app.
  • Fully offline. External images, fonts, and stylesheets are removed, so the layout looks the same years from now.
  • Common editor exports just work. Standard DOCTYPE lines, comments, and missing xmlns are fixed, and wrapper tags like Illustrator's <switch> are unwrapped.

Technical reference: the exact rules

Generated from the app's own SVG import code, so it always matches what nittygrid does. This is for technicians and AI agents.

Rule CategorySpecification
Eligible LED geometry (SHAPE_TAGS)<path>, <circle>, <rect>, <ellipse>, <polygon>, <polyline>, <line>
Preserved geometry attributesd, points, x, y, width, height, cx, cy, r, rx, ry, x1, y1, x2, y2, transform, fill-rule, clip-rule, pathLength
Excluded definition contexts & <use> policyShapes inside <defs>, <clipPath>, <mask>, <marker>, <pattern>, or <symbol> are presentation definitions, never LEDs. Standalone <use> instances are rejected—expand instances into independent shapes before importing.
Canonical LED metadata & group inheritancedata-led-id (stable portable LED identity), id="px-ROW-COL", data-row="ROW", data-col="COL", optional data-part="PART" (0-based top-left origin). Logical addresses on ancestor <g> or <svg> elements are inherited during normalization; unusable or duplicate metadata attributes are stripped cleanly on import instead of rejecting the file.
Untagged sampling order & identityWithout sampling addresses, LEDs are sorted by center cy, then cx and sampled from consecutive grid cells. Addressed layouts sort by sampling y, then x. When a row/col/part address lands on a cell another LED already uses, explicit data-x/data-y addresses keep their cell and the other LED moves to a spare row below the grid. data-parts-per-cell counts only as a positive whole number. IDs remain paired with their source shapes; SVG element order is not a hardware wiring order.
Accepted import aliases & editor cleanupid="led-rNN-cNNN" (automatically flipped from bottom-left to top-left origin on import). Standard DOCTYPEs, XML comments, and CDATA markers are stripped; missing SVG namespaces are added; unsupported containers like Illustrator <switch> are unwrapped.
Shadow DOM isolation & removed active elementsLayouts render inside an isolated shadow root imported from the XML parse (never re-parsed as HTML, and SVG <style> rules cannot restyle the app). Strips <script>, <foreignObject>, <iframe>, <embed>, <object>, <audio>, <video>, SMIL animation tags, <handler>, and <listener>.
Offline link, raster & buffer budgetsExternal href / xlink:href / CSS url(...) and @import rules are stripped. Only local #... fragments and header-verified data:image/(png|jpeg|gif|webp);base64 rasters (≤ 4 MiB, ≤ 4 MP, ≤ 4096 px/side) are allowed. Sampling addresses are capped at a 2048 × 2048 render buffer (≤ 8 MiB UTF-8 SVG for portable export).

What each LED stores

Each LED in an SVG layout keeps four separate pieces of information:

  1. Identity (data-led-id): its permanent, unique name across saves and .ngrender.json / .ngrender.zip exports.
  2. Logical address (row, col, part): optional 0-based grid position, starting top-left.
  3. Sample position (x, y): the integer slot in the W × H grid that the renderer reads its color from.
  4. Shape (tag, attrs, cx, cy): the actual outline and center point inside the SVG viewBox.

LED identities are assigned before sorting and remain paired with their original shapes. For addressed layouts, the pixel list is sorted by sampling y, then x; for unaddressed layouts, it is sorted by center cy, then cx. Exported frame colors follow the explicit LED list in the animation JSON. Consumers should use that list, not the SVG element order, to interpret the frames.