Nodes/DemonAlone-nodes-ComfyUI/NativeXYZGridRenderer
ComfyUI Node

NativeXYZGridRenderer

The grid image your loop was always supposed to produce

By DemonAlone·Created 9 months ago·Updated about 22 hours ago· 6
NativeXYZGridRenderer
  • images
  • grid_image
◄x_labels—►
◄y_labels—►
◄z_labels—►
◄x_prefixX►
◄y_prefixY►
◄z_prefixZ►
◄font_size30►
◄grid_gap20►
◄max_preview_mp20.0►
◄max_side_limit16384►

A sweep without a labelled grid is a folder of images and a memory problem. That's why XYZ plots were beloved in the A1111 era, and why ComfyUI users spent two years duct-taping loops together to get one: you queue forty generations and want back one picture you can read the settings off.

Now that ComfyUI has native loop nodes, the loop half is solved - StartLoop/EndLoop iterate a list of values for you. What's missing is the last step: assembling the results into a grid labelled with the values themselves. That's this node.

What it does

It takes a ready-made batch of images plus three lists of label text, and returns a labelled grid. One image per Z-plane, stacked as a batch on the output - so a 3×2 grid with four Z values gives you a 4-image batch, each one a page titled with its Z label.

The arithmetic decides whether your labels line up, so it's worth knowing.

images_per_page = len(x_labels) * len(y_labels)
page = images[z_index * images_per_page : ...]
x position = idx % len_x
y position = idx // len_x

Two consequences. X varies fastest, so the image order out of your loop must match a nesting of Z outer, Y middle, X inner. Swap X and Y and you get a transposed grid - perfectly readable, completely mislabelled. And the label counts define the geometry, not the image count. If you have 36 images but only declare four X labels, the renderer will happily lay them out four-wide and give you nonsense rows.

Each page gets a label row across the top, a left column for Y labels, and the Z label centred at the top in a larger font. Long labels wrap to the cell width rather than overflowing.

The single output is grid_image, an IMAGE batch - so it wires into Preview Image or Save Image exactly like a decoded render would. One page per Z value, which means the batch is as deep as your Z axis.

The inputs that matter

images - the batch from the outermost EndLoop. Batches inside the batch get flattened, so it doesn't matter whether your loop hands over single images or groups.

x_labels, y_labels, z_labels - STRING inputs, but note the node sets INPUT_IS_LIST, so they arrive as lists: the accumulated values from the loop, one per iteration. Wire the same text your sweep is driving (the sampler name, the CFG value, whatever the label should say). Feed a single string instead of a list and you get length 1, a one-cell grid, and a mystery.

Optional settings, of which three earn their keep:

  • x_prefix / y_prefix / z_prefix - default X, Y, Z. Set them to empty strings if your labels are already self-describing.
  • font_size (10-60, default 30) and grid_gap (0-100, default 20) - the label size and the space between cells.
  • max_preview_mp (default 20, range 5-21) - the per-page megapixel ceiling. A 5×5 grid of 1024px images is 25 MP before labels, so pages trip this constantly. The node rescales with LANCZOS and prints [XYZ Page 0] Page resized under limit: … to the console.
  • max_side_limit (default 16384) - a hard cap on page width or height, same rescale.

Leave the defaults and read the console: if every page reports a resize, your grid is bigger than you thought and the labels shrink with it.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/DemonAlone/DemonAlone-nodes-ComfyUI
cd DemonAlone-nodes-ComfyUI
pip install -r requirements.txt

Or install DemonAlone-nodes-ComfyUI via ComfyUI Manager and restart. No models, no pip packages beyond the pack's lone piexif requirement, no frontend extension. NativeXYZGridRenderer appears under utils/XYZ.

This is the new-world node. The pack still carries older qq-nodes-derived helpers (MyXYZHelper, MyXYGridAccumulator, MyXYZSuperStacker) for JS-driven plots; don't mix the pipelines, they make different assumptions about who's counting.

Things that go wrong

Labels are tiny and font_size does nothing. The renderer asks PIL for arial.ttf, and on a clean Linux or Docker install that call fails, so it silently falls back to PIL's default bitmap font at a fixed small size. There's no error, because the fallback is inside a try. Put a real TTF where the process can find it under that name - a copy of DejaVu Sans named arial.ttf in the ComfyUI working directory does the job - and restart.

The grid is one cell and everything else vanished. Your label lists are length 1. They need to be lists of the actual swept values, coming off the loop, not a typed-in string.

Columns and rows are swapped. Image order is row-major with X fastest. Re-nest the loops (Z outside, X innermost) or swap the label lists - one or the other, not both.

The output is a 64×64 black square. The node's fallback when no images reached it - a zeros tensor rather than an error. Check the loop connection first.

Nothing shows up because the pack didn't load. Module-level piexif import in image_utilites.py - a skipped pip install -r requirements.txt takes the whole pack down. Restart after installing it.

Categoryutils/XYZ

Inputs (11)

NameTypeDefaultDescription
imagesIMAGE—
x_labelsSTRING—
y_labelsSTRING—
z_labelsSTRING—
x_prefixoptSTRINGX—
y_prefixoptSTRINGY—
z_prefixoptSTRINGZ—
font_sizeoptINT3010–60—
grid_gapoptINT200–100—
max_preview_mpoptFLOAT20.05–21—
max_side_limitoptINT163842048–16384—

Outputs (1)

NameTypeDescription
grid_imageIMAGE—