Skip to content
Merged

Dev #22

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified poster/pipeline_priors.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 37 additions & 21 deletions poster/pipeline_priors.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import argparse
import sys
import textwrap
from pathlib import Path

import numpy as np
Expand All @@ -19,7 +20,7 @@

# Vertical budget in inches for everything that is not a drawn panel.
H_TOP = 0.10 # air above the pipeline
H_TITLE = 0.62 # prior-panel titles (also the air below the funnel mouth)
H_TITLE = 1.05 # prior-panel titles (also the air below the funnel mouth)
H_CAPTION = 0.68 # prior-panel captions
H_LEGEND = 0.60 # shared key at the very bottom

Expand All @@ -30,13 +31,12 @@
# tiles keep their proportions (and the rounded corners stay circular) at any width.
PIPE_RHO = (2.8 * 0.96) / (11.0 * 0.98)

# The funnel carries no caption by default — the shape alone says "these plug in here".
DEFAULT_LABEL = ""

# Prior-panel captions are set larger here than in the standalone figure: on the poster this
# banner is read from further away. The caption band grows with the type size and the panels
# give back exactly what it takes, so the figure's size never changes (see make_figure).
CAPTION_SIZE = 14.5
CAPTION_SIZE = 17.0


def draw_funnel(fig, ax_pipe, geom, x0, x1, y_mouth, *, label, label_size):
Expand All @@ -54,7 +54,6 @@ def to_fig(x, y):

cx = geom["cxs"][geom["hi"]]
tile_bottom = geom["cy"] - geom["TH"] / 2
# Overlap the tile slightly so the neck disappears behind its fill (no hairline seam).
nl_x, y_neck = to_fig(cx - geom["TW"] * 0.30, tile_bottom + 1.5)
nr_x, _ = to_fig(cx + geom["TW"] * 0.30, tile_bottom + 1.5)
_, y_tile = to_fig(cx, tile_bottom)
Expand All @@ -64,8 +63,6 @@ def to_fig(x, y):
right = [(x1, y_mouth), (x1, y_mouth + 0.55 * dy), (nr_x, y_neck - 0.45 * dy), (nr_x, y_neck)]
curve = [MPath.MOVETO, MPath.CURVE4, MPath.CURVE4, MPath.CURVE4]

# Two open strokes only — no mouth line, no fill: the funnel reads as a flow onto the
# panels instead of as a filled box drawn around them.
for side in (left, right):
fig.add_artist(
PathPatch(
Expand All @@ -79,8 +76,6 @@ def to_fig(x, y):
)
)

# Arrowhead in the neck: makes the direction of flow explicit (priors -> sampler).
# Sized in inches so it stays a tidy glyph whatever the figure's aspect ratio is.
neck_cx = (nl_x + nr_x) / 2
half, height = 0.24 / fig_w, 0.34 / fig_h
apex = y_tile - 0.09 / fig_h
Expand Down Expand Up @@ -115,13 +110,14 @@ def make_figure(
seed,
out_dir,
dpi=400,
panel_w=5.4,
panel_w=6.8,
panel_h=4.5,
pipe_frac=0.76,
pipe_scale=1.45,
funnel_h=1.30,
label=DEFAULT_LABEL,
caption_size=CAPTION_SIZE,
title_size=19.0,
):
import matplotlib

Expand All @@ -132,18 +128,28 @@ def make_figure(

rng = np.random.default_rng(seed)
panels = pg.build_panels(n, num_blocks, rng)
# Wrap long titles at the first "(" so they fit in their panel width.
# Re-wrap captions: the hard-coded \n breaks were tuned for the smaller standalone
# caption size; re-flow to ~38 chars per line for the larger size used here.
panels = [
(
key,
title.replace(" (", "\n("),
spec,
textwrap.fill(cap.replace("\n", " "), width=38),
)
for key, title, spec, cap in panels
]
Comment on lines +131 to +142

# ---- geometry: lay the figure out in inches, then convert to figure fractions ----
# The prior panels keep their standalone size and span the full width; the pipeline is
# a narrower centred band above them, closer to its own standalone proportions.
W = len(panels) * panel_w
x_margin = 0.16 / W # equal air on both sides
pipe_w_in = W * pipe_frac
h_pipe = (PIPE_YLIM[1] - PIPE_YLIM[0]) / 100.0 * pipe_w_in * PIPE_RHO
H = H_TOP + h_pipe + funnel_h + H_TITLE + panel_h + H_CAPTION + H_LEGEND

# Bigger captions need a deeper band; take it out of the panels so H (and the pixel size)
# is the same whatever --caption-size is.
# Bigger captions need a deeper band; take it out of the panels so H (and the pixel
# size) is the same whatever --caption-size is.
cap_band = H_CAPTION * caption_size / pg.CAPTION_SIZE
panel_h = panel_h - (cap_band - H_CAPTION)

Expand All @@ -167,14 +173,14 @@ def make_figure(
drawn = []
for ax, (key, title, spec, caption) in zip(axes, panels, strict=True):
pos = pg.compute_pos(spec, seed)
pg.draw_panel(ax, spec, pos, pg.ACCENT[key], title, caption, caption_size=caption_size)
pg.draw_panel(ax, spec, pos, pg.ACCENT[key], title, caption, caption_size=caption_size, title_size=title_size)
drawn.append((ax, key, title, spec, caption, pos))

fig.legend(
handles=pg.legend_handles(),
loc="lower center",
ncol=3,
fontsize=13,
fontsize=16,
frameon=False,
bbox_to_anchor=(0.5, 0.006),
)
Expand All @@ -189,7 +195,17 @@ def make_figure(
eff[ti] *= 1.9 # the target's outer ring is drawn larger than its fill
new_pos = pg.resolve_overlaps(ax, pos, spec["nodes"], eff, lock_y=(spec["layout"] == "age"))
ax.clear()
pg.draw_panel(ax, spec, new_pos, pg.ACCENT[key], title, caption, fixed_lims=lims, caption_size=caption_size)
pg.draw_panel(
ax,
spec,
new_pos,
pg.ACCENT[key],
title,
caption,
fixed_lims=lims,
caption_size=caption_size,
title_size=title_size,
)

draw_funnel(fig, ax_pipe, geom, x_margin, 1 - x_margin, y_mouth, label=label, label_size=15.0)

Expand All @@ -204,16 +220,15 @@ def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--nodes", type=int, default=15, help="nodes per illustrative graph")
ap.add_argument("--blocks", type=int, default=3, help="SBM communities")
ap.add_argument("--seed", type=int, default=3, help="sampling + layout seed (bump for a nicer draw)")
ap.add_argument("--panel-width", type=float, default=5.4, help="width of one prior panel, inches")
ap.add_argument("--seed", type=int, default=3, help="sampling + layout seed")
ap.add_argument("--panel-width", type=float, default=6.8, help="width of one prior panel, inches")
ap.add_argument("--panel-height", type=float, default=4.5, help="height of one prior panel, inches")
ap.add_argument("--pipe-width", type=float, default=0.76, help="pipeline width as a fraction of the figure")
ap.add_argument("--pipe-scale", type=float, default=1.45, help="type/marker scale for the pipeline row")
ap.add_argument("--funnel-height", type=float, default=1.30, help="height of the connector band, inches")
ap.add_argument("--label", default=DEFAULT_LABEL, help="text inside the funnel ('' to omit)")
ap.add_argument(
"--caption-size", type=float, default=CAPTION_SIZE, help="pt size of the prior-panel captions"
)
ap.add_argument("--caption-size", type=float, default=CAPTION_SIZE, help="pt size of the prior-panel captions")
ap.add_argument("--title-size", type=float, default=19.0, help="pt size of the prior-panel titles")
ap.add_argument("--dpi", type=int, default=400, help="PNG resolution")
ap.add_argument("--out-dir", type=Path, default=Path(__file__).resolve().parent)
args = ap.parse_args()
Expand All @@ -230,6 +245,7 @@ def main():
pipe_scale=args.pipe_scale,
funnel_h=args.funnel_height,
caption_size=args.caption_size,
title_size=args.title_size,
label=args.label,
):
print(f"wrote {p}")
Expand Down