factoriax.analysis.recipe_graph#

Render a recipe DAG as a layered crafting diagram.

Public surface:

  • RecipeSpec: plain-Python recipe, decoupled from the JAX-backed engine so callers can render diagrams from a JSON dump.

  • Layout: geometry constants.

  • render(): top-level entry that consumes recipes and writes a PNG (or SVG, by file extension).

The renderer runs as a chain of helpers. Each one takes the values it needs and returns plain data. Only the _draw_* helpers call matplotlib.

The chain builds the adjacency map, then the positions, then the node colors through factoriax.analysis.categories.item_palette(). It then assigns the ports of each two-input recipe, gives every edge a channel, and routes each edge. A route takes one of three forms: between neighboring tiers, across a lane between rows, or over the skyway above the top row. The boxes and edges are drawn last.

Module Attributes

MAX_PORTS

Ports on the left edge of a box.

class factoriax.analysis.recipe_graph.Layout(box_w: float = 4.0, box_h: float = 0.7, tier_x_step: float = 6.0, row_y_step: float = 1.5, port_offset_frac: float = 0.3, left_inset: float = 0.06, right_inset: float = 0.06, input_name_max_chars: int = 14, hub_threshold: int = 4, hub_alpha: float = 0.55, normal_alpha: float = 1.0, fallback_color: str = '#cccccc', stroke_darken: float = 0.55)#

Geometry constants for the recipe DAG render.

The defaults are tuned against the easy-rocket recipe set. Pass a different instance through the layout argument of render() when a figure needs other proportions.

Distances are matplotlib data units. The aspect ratio is locked to 1, so one unit along x equals one unit along y.

box_w, box_h

The size of one node box.

tier_x_step#

The distance from one tier column to the next. It must be larger than box_w, or the gap between columns closes and the edge channels have nowhere to go.

Type:

float

row_y_step#

The distance from one row to the next.

Type:

float

port_offset_frac#

The offset of the two ports from the middle of a box, as a fraction of box_h. See port_offset.

Type:

float

left_inset, right_inset

The padding inside a box, before the input labels on the left and the result label on the right.

input_name_max_chars#

Where to cut an input label. A longer name is truncated with an ellipsis.

Type:

int

hub_threshold#

An item that feeds this many recipes counts as a hub. Its outgoing edges are drawn with hub_alpha.

Type:

int

hub_alpha, normal_alpha

The opacity of an edge from a hub, and from any other node. Fading the hub edges keeps a busy node such as a plate from hiding the rest of the graph.

fallback_color#

The fill for an item the palette does not know.

Type:

str

stroke_darken#

The factor passed to _darken() to turn a fill into its stroke.

Type:

float

property port_offset: float#

Distance from a box’s centre to its top or bottom port.

factoriax.analysis.recipe_graph.MAX_PORTS: int = 2#

Ports on the left edge of a box. This value limits how many inputs a recipe drawn here can have. It equals MAX_RECIPE_INPUTS in the engine, but is repeated to keep the JAX-backed import out of this module.

class factoriax.analysis.recipe_graph.RecipeSpec(output: str, inputs: tuple[tuple[str, int], ...], ticks: int, output_count: int = 1, name: str = '')#

Plain-Python recipe used by the renderer.

This mirrors factoriax.engine.recipes.Recipe, but names items with strings instead of enum values. A caller can therefore draw a diagram from a JSON dump with no import of the JAX-backed engine module.

output#

The item this recipe makes. It is also the key under which the layout code finds the recipe.

Type:

str

inputs#

((item_name, count), ...). The order is kept, because two inputs at the same height fall back to it when they choose ports. A recipe must have one or two inputs, which _assign_ports() checks.

Type:

tuple[tuple[str, int], …]

ticks#

How long one craft takes, in engine ticks. It is shown in the box subtitle and used nowhere else.

Type:

int

output_count#

How many items one craft makes.

Type:

int

name#

A display name for the box, or "" to use output.

Type:

str

factoriax.analysis.recipe_graph.render(recipes: Sequence[RecipeSpec], out_path: Path | str, *, title: str = '', layout: Layout | None = None) → Path#

Draw the recipe graph and return the path of the new file.

The pipeline runs in stages. Tiers and rows come from factoriax.analysis.graph_layout. Positions come from _positions(), ports from _assign_ports(), and channels from _allocate_edge_channels(). _route_edge() gives each edge its corners. The _draw_* helpers then put those numbers on the axes.

Parameters:
  • recipes – The recipes to draw. Each must have one or two inputs. Every item named as an input gets a box, whether or not it has a recipe of its own.

  • out_path – Where to write the image. The extension sets the format, and matplotlib accepts PNG and SVG among others. Missing parent directories are created.

  • title – A title over the figure, or "" for none.

  • layout – Geometry constants, or None for the defaults.

Returns:

out_path as a Path, after the write.

Return type:

pathlib.Path

Raises:

ValueError – When a recipe has no inputs or more than MAX_PORTS, or when the recipes hold a cycle, or when recipes is empty.

Notes

The function writes a file and closes its own figure. The figure size grows with the tier count and the tallest column. A large recipe set therefore gives a large image, and not a crowded one.