Skip to content

Commit b559ad4

Browse files
committed
Refine poster: auto-number captions, fix Figure 2 clipping, chart-row layout, vertical centering
- Auto-number Figure/Table captions in column-major reading order - Poster charts rendered as HTML wrapper divs with captions below SVGs - Fix Figure 2 clipping by rebalancing grid (Intro 4 rows, Methods 5 rows) - Remove redundant Background box from Introduction section - Add justify-content: center for empty-title boxes (Acknowledgements) - Add 3 more bullet points to Limitations and future work - Update Acknowledgements links to short format (Data, Code, PDF) - Consistent caption font size (24pt) immune to scale wrappers - Legend field support for charts (legend: right positioning) - Update gallery screenshot
1 parent 14102fa commit b559ad4

6 files changed

Lines changed: 187 additions & 34 deletions

File tree

docs/screenshots/poster-sample.png

26.7 KB
Loading

examples/sample_poster.md

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ IIIIIIIIRRRRRRRRRRRRDDDDDDDD
1111
IIIIIIIIRRRRRRRRRRRRDDDDDDDD
1212
IIIIIIIIRRRRRRRRRRRRDDDDDDDD
1313
IIIIIIIIRRRRRRRRRRRRDDDDDDDD
14-
IIIIIIIIRRRRRRRRRRRRDDDDDDDD
14+
MMMMMMMMRRRRRRRRRRRRDDDDDDDD
1515
MMMMMMMMRRRRRRRRRRRRDDDDDDDD
1616
MMMMMMMMRRRRRRRRRRRRDDDDDDDD
1717
MMMMMMMMRRRRRRRRRRRREEEAAAAA
@@ -28,7 +28,7 @@ MMMMMMMMRRRRRRRRRRRREEEAAAAA
2828

2929
<div class="note-box" data-title="Orient your audience">
3030

31-
Start with the **broad question** your research addresses and narrow to your specific contribution. Explain why this topic matters and what gap your work fills.
31+
Start with the **broad question** your research addresses and narrow to your contribution.
3232

3333
</div>
3434

@@ -46,6 +46,7 @@ Start with the **broad question** your research addresses and narrow to your spe
4646
<span class="label">Your approach</span>
4747
</div>
4848
</div>
49+
<div class="figure-caption">Research paradigm: from observation to hypothesis-driven inquiry</div>
4950

5051
<div class="note-box" data-title="Hypotheses">
5152

@@ -54,16 +55,8 @@ Start with the **broad question** your research addresses and narrow to your spe
5455

5556
</div>
5657

57-
<div class="note-box" data-title="Background">
58-
59-
Prior work motivates this study; our approach advances the field by addressing key limitations in existing models.
60-
61-
</div>
62-
6358
## M: Methods [violet]
6459

65-
<div class="scale-90">
66-
6760
<div class="definition-box" data-title="Experimental design">
6861

6962
**Paradigm**: $N=50$ participants, within-subject design, naturalistic video stimuli.
@@ -102,8 +95,7 @@ Prior work motivates this study; our approach advances the field by addressing k
10295
<span class="label">Visualization</span>
10396
</div>
10497
</div>
105-
106-
</div>
98+
<div class="figure-caption">Analysis toolkit spanning computation, brain imaging, and data visualization</div>
10799

108100
## R: Results [green]
109101

@@ -149,6 +141,8 @@ $$\hat{y} = \beta_0 + \beta_1 x_1 + \beta_2 x_2 + \epsilon, \quad R^2 = 0.73$$
149141
| RT (ms) | 342 | 418 | 450 | < 0.05 |
150142
| F1 Score | 0.85 | 0.68 | 0.60 | < 0.01 |
151143

144+
<div class="table-caption">Performance metrics across experimental groups</div>
145+
152146
<div class="example-box" data-title="Generalization">
153147

154148
Findings **replicate** across datasets and participant groups.
@@ -167,19 +161,18 @@ datasets:
167161
data: 0.71, 0.74, 0.70, 0.75, 0.73
168162
xlabel: Cross-validation fold
169163
ylabel: F1 score
164+
legend: right
170165
caption: Generalization across datasets
171166
```
172167

173168
<div class="example-box" data-title="Robustness">
174169

175-
Cross-validation (k=5) and bootstrap resampling (1000 iterations) confirm stable effect size (Cohen's d = 0.8). Permutation testing corroborates significance (BF > 10).
170+
Cross-validation (k=5) and bootstrap resampling (1000 iterations) confirm stable effect size (Cohen's d = 0.8). Permutation testing corroborates significance (BF > 10). Sensitivity analyses varying regularization strength (λ = 0.01–10) show consistent results across parameter choices.
176171

177172
</div>
178173

179174
## D: Discussion [teal]
180175

181-
<div class="scale-80">
182-
183176
<div class="tip-box" data-title="Key takeaways">
184177

185178
- **Finding 1**: Model outperforms baseline by 15%, supporting H1
@@ -188,14 +181,6 @@ Cross-validation (k=5) and bootstrap resampling (1000 iterations) confirm stable
188181

189182
</div>
190183

191-
<div class="tip-box" data-title="Implications">
192-
193-
- Evidence for predictive coding frameworks in perception
194-
- Challenges models assuming static representations
195-
- Opens avenues for computational psychiatry
196-
197-
</div>
198-
199184
```chart
200185
type: radar
201186
labels: Accuracy, Speed, Scalability, Robustness, Interpretability
@@ -211,14 +196,17 @@ caption: Current capabilities vs. planned improvements
211196

212197
- Sample limited to college-age participants
213198
- Future: longitudinal designs, larger cohorts
214-
215-
</div>
199+
- Explore transfer learning to clinical populations
200+
- Incorporate real-time fMRI neurofeedback paradigms
201+
- Extend to multimodal data (EEG + fMRI fusion)
216202

217203
</div>
218204

219205
## E: References [orange]
220206

221-
<div class="scale-60">
207+
<div class="note-box" data-title="">
208+
209+
<div class="scale-50">
222210

223211
1. Author A *et al.* (2023). *J. Neurosci.*
224212
2. Author C *et al.* (2022). *Nat. Hum. Behav.*
@@ -228,12 +216,22 @@ caption: Current capabilities vs. planned improvements
228216

229217
</div>
230218

219+
</div>
220+
231221
## A: Acknowledgments [spring]
232222

223+
<div class="note-box" data-title="">
224+
233225
<div class="scale-65">
234226

235227
**NSF EPSCoR** #1632738 · **NIH R01** MH112357 · **NSF CAREER** #1849109
236228

237-
🌐 context-lab.com · 💻 github.com/ContextLab
229+
📦 Data: github.com/ContextLab
230+
231+
💻 Code: github.com/ContextLab
232+
233+
🌐 PDF: context-lab.com/publications
234+
235+
</div>
238236

239237
</div>

src/cdl_slides/assets/themes/cdl-poster-theme.css

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,38 @@ table th {
245245
.poster-section > div[class*="scale-"] > .example-box:only-child,
246246
.poster-section > div[class*="scale-"] > .important-box:only-child {
247247
flex: 1;
248-
display: flex;
249-
flex-direction: column;
250-
justify-content: center;
251248
min-height: 0;
252249
overflow: visible;
253250
}
254251

252+
.poster-section .note-box,
253+
.poster-section .warning-box,
254+
.poster-section .tip-box,
255+
.poster-section .definition-box,
256+
.poster-section .example-box,
257+
.poster-section .important-box {
258+
display: flex;
259+
flex-direction: column;
260+
}
261+
262+
.poster-section .note-box > :first-child,
263+
.poster-section .warning-box > :first-child,
264+
.poster-section .tip-box > :first-child,
265+
.poster-section .definition-box > :first-child,
266+
.poster-section .example-box > :first-child,
267+
.poster-section .important-box > :first-child {
268+
margin-top: auto;
269+
}
270+
271+
.poster-section .note-box > :last-child,
272+
.poster-section .warning-box > :last-child,
273+
.poster-section .tip-box > :last-child,
274+
.poster-section .definition-box > :last-child,
275+
.poster-section .example-box > :last-child,
276+
.poster-section .important-box > :last-child {
277+
margin-bottom: auto;
278+
}
279+
255280
.chart-row {
256281
display: flex;
257282
gap: 4mm;
@@ -265,6 +290,10 @@ table th {
265290
min-width: 0;
266291
}
267292

293+
.poster-chart {
294+
width: 100%;
295+
}
296+
268297
/* Ensure SVG charts scale to fit their container */
269298
.poster-section svg {
270299
max-width: 100%;
@@ -317,6 +346,9 @@ table th {
317346
font-size: 1.14em;
318347
background-color: var(--section-box-bg, var(--box-default-bg));
319348
border-left: 3mm solid var(--section-box-border, var(--box-default-border));
349+
overflow: visible;
350+
word-break: normal;
351+
overflow-wrap: break-word;
320352
}
321353

322354
.note-box::before, .warning-box::before, .tip-box::before,
@@ -373,6 +405,17 @@ table th {
373405
[data-title]::before { content: attr(data-title); }
374406
[data-title=""]::before { display: none; }
375407

408+
/* When a box has no visible title, vertically center the content.
409+
The ::before is hidden so we can use justify-content directly. */
410+
.poster-section .note-box[data-title=""],
411+
.poster-section .warning-box[data-title=""],
412+
.poster-section .tip-box[data-title=""],
413+
.poster-section .definition-box[data-title=""],
414+
.poster-section .example-box[data-title=""],
415+
.poster-section .important-box[data-title=""] {
416+
justify-content: center;
417+
}
418+
376419
.poster-color-blue { --section-box-bg: rgba(38, 122, 186, 0.12); --section-box-border: var(--river-blue); --section-box-title: var(--river-navy); }
377420
.poster-color-green { --section-box-bg: rgba(0, 105, 62, 0.10); --section-box-border: var(--dartmouth-green); --section-box-title: var(--dartmouth-green); }
378421
.poster-color-violet, .poster-color-purple { --section-box-bg: rgba(138, 105, 150, 0.12); --section-box-border: var(--violet); --section-box-title: #6a4d7a; }
@@ -477,6 +520,46 @@ p img[alt] {
477520
margin-top: 3mm;
478521
}
479522

523+
.figure-caption {
524+
text-align: center;
525+
font-size: 0.85em;
526+
font-style: italic;
527+
color: var(--granite-gray);
528+
margin: 2mm 0 4mm;
529+
line-height: 1.3;
530+
}
531+
532+
.figure-caption strong {
533+
font-style: normal;
534+
}
535+
536+
.table-caption {
537+
text-align: center;
538+
font-size: 0.85em;
539+
font-style: italic;
540+
color: var(--granite-gray);
541+
margin: 1mm 0 4mm;
542+
line-height: 1.3;
543+
}
544+
545+
.table-caption strong {
546+
font-style: normal;
547+
}
548+
549+
/* Captions and figures must be visually consistent across all poster sections,
550+
regardless of any scale-* wrapper applied to the parent. We use a fixed
551+
size (24pt ≈ 0.85 × 28pt base) so em-based scaling cannot affect them. */
552+
.poster-section .figure-caption,
553+
.poster-section .table-caption {
554+
font-size: 24pt;
555+
}
556+
557+
/* SVG charts inside poster sections should not shrink with scale wrappers */
558+
.poster-section div[class*="scale-"] svg {
559+
max-width: none;
560+
width: 100%;
561+
}
562+
480563
.flow-diagram {
481564
display: flex;
482565
align-items: center;

src/cdl_slides/chart_renderer.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,17 @@ def _add_legend(ax, config: dict, palette: list):
141141
always_legend = chart_type in ("pie", "doughnut", "radar")
142142
show_legend = len(config["datasets"]) > 1 or always_legend
143143
if show_legend:
144-
legend = ax.legend(fontsize=LEGEND_SIZE, frameon=False, labelcolor=CDL_TEXT_COLOR)
144+
legend_pos = config.get("legend", "")
145+
if legend_pos == "right":
146+
legend = ax.legend(
147+
fontsize=LEGEND_SIZE,
148+
frameon=False,
149+
labelcolor=CDL_TEXT_COLOR,
150+
loc="center left",
151+
bbox_to_anchor=(1.02, 0.5),
152+
)
153+
else:
154+
legend = ax.legend(fontsize=LEGEND_SIZE, frameon=False, labelcolor=CDL_TEXT_COLOR)
145155
if legend:
146156
legend.get_frame().set_alpha(0)
147157

@@ -320,7 +330,11 @@ def _data_sum(ds):
320330

321331

322332
def process_poster_chart_blocks(content: str) -> tuple:
323-
"""Process ```chart code blocks and convert them to inline SVG for posters."""
333+
"""Process ```chart code blocks and convert them to inline SVG for posters.
334+
335+
Chart captions are rendered as HTML divs (not embedded in SVG) so the
336+
poster preprocessor can auto-number them consistently with other figures.
337+
"""
324338
chart_pattern = r"```chart\n(.*?)```"
325339
charts_processed = 0
326340

@@ -333,7 +347,14 @@ def replace_chart_block(match):
333347
return match.group(0)
334348

335349
charts_processed += 1
336-
return render_chart_svg(config)
350+
caption = config.get("caption", "")
351+
config["caption"] = ""
352+
svg_html = render_chart_svg(config)
353+
354+
if caption:
355+
svg_html += f'\n<p class="figure-caption">{caption}</p>'
356+
357+
return f'<div class="poster-chart">\n{svg_html}\n</div>'
337358

338359
processed = re.sub(chart_pattern, replace_chart_block, content, flags=re.DOTALL)
339360
return processed, charts_processed

src/cdl_slides/poster_preprocessor.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,50 @@ def extract_poster_sections(content: str) -> dict[str, dict[str, str | None]]:
174174
return sections
175175

176176

177+
def _reading_order_labels(layout: dict[str, Any]) -> list[str]:
178+
"""Return section labels sorted in column-major reading order.
179+
180+
Sorts by (col_start, row_start) so sections are ordered left-to-right
181+
across columns and top-to-bottom within each column — matching the
182+
natural reading order of a multi-column academic poster.
183+
"""
184+
areas = layout["areas"]
185+
return sorted(
186+
layout["labels"],
187+
key=lambda lbl: (areas[lbl]["col_start"], areas[lbl]["row_start"]),
188+
)
189+
190+
191+
def _auto_number_captions(content: str, fig_count: int, tbl_count: int) -> tuple[str, int, int]:
192+
"""Prepend bold 'Figure X.' / 'Table X.' to caption divs in *content*.
193+
194+
Only captions with text are numbered; empty caption divs are skipped.
195+
Returns (updated_content, new_fig_count, new_tbl_count).
196+
"""
197+
198+
def _replace_fig(m: re.Match) -> str:
199+
nonlocal fig_count
200+
tag = m.group(1)
201+
caption_text = m.group(2).strip()
202+
if not caption_text:
203+
return m.group(0)
204+
fig_count += 1
205+
return f'<{tag} class="figure-caption"><strong>Figure {fig_count}.</strong> {caption_text}</{tag}>'
206+
207+
def _replace_tbl(m: re.Match) -> str:
208+
nonlocal tbl_count
209+
tag = m.group(1)
210+
caption_text = m.group(2).strip()
211+
if not caption_text:
212+
return m.group(0)
213+
tbl_count += 1
214+
return f'<{tag} class="table-caption"><strong>Table {tbl_count}.</strong> {caption_text}</{tag}>'
215+
216+
content = re.sub(r'<(div|p) class="figure-caption">(.*?)</(?:div|p)>', _replace_fig, content)
217+
content = re.sub(r'<(div|p) class="table-caption">(.*?)</(?:div|p)>', _replace_tbl, content)
218+
return content, fig_count, tbl_count
219+
220+
177221
def generate_poster_html(
178222
frontmatter: dict[str, Any],
179223
layout: dict[str, Any],
@@ -217,9 +261,12 @@ def generate_poster_html(
217261
}}
218262
</style>"""
219263

264+
ordered_labels = _reading_order_labels(layout)
220265
section_divs = []
221266
charts_total = 0
222-
for label in layout["labels"]:
267+
fig_count = 0
268+
tbl_count = 0
269+
for label in ordered_labels:
223270
if label not in sections:
224271
continue
225272
sec = sections[label]
@@ -230,6 +277,7 @@ def generate_poster_html(
230277
section_content = sec["content"] or ""
231278
section_content, chart_count = process_poster_chart_blocks(section_content)
232279
charts_total += chart_count
280+
section_content, fig_count, tbl_count = _auto_number_captions(section_content, fig_count, tbl_count)
233281
div = f"""<div style="grid-area: {label};" class="{css_class}">
234282
235283
{heading}

0 commit comments

Comments
 (0)