Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
758807a
Add concept-map appendix: dependency diagram + descendants table (#872)
d-morrison Jun 3, 2026
9fd4f06
Merge branch 'main' into claude/issue-872-20260603-013639
d-morrison Jun 4, 2026
e29bc1b
fix: use boxed title labels for all nodes in concept map
claude[bot] Jun 4, 2026
d1ea852
feat: add Concept Map to website navbar Appendices dropdown
claude[bot] Jun 4, 2026
dd97e4a
docs: remind to add new chapters to website navbar dropdown
claude[bot] Jun 4, 2026
b2a458a
concept-map: large SVG layout, HTML-only figure, count-only descendan…
claude[bot] Jun 4, 2026
6e410d8
Fix concept map: ggrepel non-overlapping labels, fix crossrefs, simpl…
claude[bot] Jun 4, 2026
2b06822
Concept map: layered layout, basic nodes on top, seed implicit deps
d-morrison Jun 5, 2026
12be85d
Merge main into claude/issue-872-20260603-013639
claude Jun 5, 2026
13c85b2
concept-map: add direct-descendants column; scale label font by desce…
claude[bot] Jun 5, 2026
4ca1124
Merge branch 'main' into claude/issue-872-20260603-013639
d-morrison Jun 5, 2026
5f53a29
fix: strip \index{} from node labels, raise min font size to ~8pt
claude[bot] Jun 5, 2026
52e8329
fix: strip \index{} from titles during extraction; fix proof word-bou…
claude[bot] Jun 5, 2026
de0bd7d
Merge branch 'main' into claude/issue-872-20260603-013639
d-morrison Jun 6, 2026
566a09c
fix: add str_trim() to index stripping, guard nodes empty list
claude[bot] Jun 6, 2026
fa7db3f
fix: stress layout, larger font minimum (4mm≈11pt), trimws() for inde…
claude[bot] Jun 6, 2026
8074583
Merge branch 'main' into claude/issue-872-20260603-013639
d-morrison Jun 9, 2026
e049fb2
use FR layout + axis normalisation to spread concept-map nodes
claude[bot] Jun 9, 2026
6ca9033
Merge branch 'main' into claude/issue-872-20260603-013639
d-morrison Jun 18, 2026
68e1149
fix: replace deprecated subcomponent() with bfs() in callout-graph.R
claude[bot] Jun 18, 2026
489a72b
fix: guard norm_axis against degenerate layout; increase label font size
claude[bot] Jun 18, 2026
cb82496
docs: add step 5 to new-chapter skill (re-run callout-graph.R)
claude[bot] Jun 18, 2026
4808624
Merge remote-tracking branch 'origin/main' into claude/issue-872-2026…
claude Jun 19, 2026
9a75c85
Regenerate callout-graph.rds; drop now-redundant render-time index strip
claude Jun 19, 2026
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
5 changes: 4 additions & 1 deletion .claude/commands/new-chapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ First, parse `$ARGUMENTS`: the first whitespace-delimited token is the **slug**
Steps:

1. Create `chapters/<slug>.qmd` with YAML frontmatter holding just `title:` (set to the title). Do NOT set `date:` — the book sets `date: last-modified` globally, and a per-page `date:` would override it. Do NOT add a top-level `#` heading in the body — Quarto renders the frontmatter `title:` as the page heading.
2. Register the chapter in the `book.chapters:` list in `_quarto-book.yml` at a logical position (read the file first). If it belongs to an existing `part:`, nest it under that part.
2. Register the chapter in the `book.chapters:` list in `_quarto-book.yml` at a logical position (read the file first). If it belongs to an existing `part:`, nest it under that part. **Also** add an entry to the appropriate navbar dropdown (`Chapters` or `Appendices`) in `_quarto-website.yml`, and ensure the file is in the `render:` list. The navbar is NOT auto-generated from `_quarto-book.yml` -- manual addition is required.
3. If the chapter is long, you may split content into includes under `chapters/_subfiles/<slug>/`. Subfiles must NOT start with a heading and must NOT contain a references section.
4. Confirm it renders: `quarto render chapters/<slug>.qmd --to html`.
5. If the chapter contains `def`/`thm`/`lem`/`cor`/`prp` callout divs,
re-run `Rscript data-raw/callout-graph.R` to refresh
`inst/extdata/callout-graph.rds` and keep the concept map current.

Style rules (see `.github/instructions/`):

Expand Down
1 change: 1 addition & 0 deletions _quarto-book.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ book:
- chapters/CONTRIBUTING.qmd
- chapters/exam-formula-sheet.qmd
- chapters/practice-exam-mle-linreg.qmd
- chapters/concept-map.qmd
back-to-top-navigation: true
page-navigation: true

Expand Down
3 changes: 3 additions & 0 deletions _quarto-website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ project:
- chapters/CONTRIBUTING.qmd
- chapters/exam-formula-sheet.qmd
- chapters/package-versions.qmd
- chapters/concept-map.qmd

website:
title: "Regression Models for Epidemiology"
Expand Down Expand Up @@ -125,6 +126,8 @@ website:
href: chapters/exam-formula-sheet.qmd
- text: "Package Versions"
href: chapters/package-versions.qmd
- text: "Concept Map"
href: chapters/concept-map.qmd

bibliography: references.bib

Expand Down
187 changes: 187 additions & 0 deletions chapters/concept-map.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
---
title: "Concept Map: Definitions and Results"
---

{{< include shared-config.qmd >}}

This appendix shows how the definitions and results
(`def`, `thm`, `lem`, `cor`, `prp` callouts)
in the notes depend on one another.

We say result $B$ is a **descendant** of result $A$
if $A$ is referenced inside the statement or proof of $B$
(directly, or transitively through a chain of intermediate results).
The more descendants a result has,
the more of the rest of the notes rests on it ---
so descendant count is a rough measure of how foundational a result is.

The dependency graph is built by `data-raw/callout-graph.R`,
which scans the source `.qmd` files and saves the result to
`inst/extdata/callout-graph.rds`.
This appendix reads that saved file rather than re-scanning the notes on
every render, so **re-run that script whenever divs are added, removed, or
re-titled** to refresh the diagram and table below.

```{r}
#| label: load-concept-graph
#| code-fold: true
#| message: false
#| warning: false

library(igraph)
library(ggraph)
library(ggrepel)

cg <- readRDS(here::here("inst/extdata/callout-graph.rds"))

type_levels <- c("def", "thm", "lem", "cor", "prp")
type_labels <- c(
def = "Definition", thm = "Theorem", lem = "Lemma",
cor = "Corollary", prp = "Proposition"
)
type_palette <- c(
def = "#1b9e77", thm = "#d95f02", lem = "#7570b3",
cor = "#e7298a", prp = "#66a61e"
)
```

There are `r nrow(cg$nodes)` labeled definitions and results in the notes,
connected by `r nrow(cg$edges)` direct dependency links.

## Dependency diagram

::: {.content-visible unless-format="pdf"}
::: {.content-visible unless-format="docx"}

@fig-concept-map shows the results that participate in at least one
dependency link, laid out so that connected results sit near each other.
Color encodes the type of result.
Results with no detected dependency links are omitted from the diagram.
Node labels are larger for results with more total descendants,
so the most foundational results are visually prominent.
Zoom in with Ctrl+scroll (or pinch on mobile) to read individual labels.
Lines from labels to dots indicate where ggrepel moved a label to avoid overlap.

:::{#fig-concept-map}

```{r}
#| label: concept-map-ggraph
#| code-fold: true
#| message: false
#| warning: false
#| fig-width: 50
#| fig-height: 50
#| out-width: "100%"
#| fig-format: "svg"

connected <- cg$nodes$id[cg$nodes$id %in% c(cg$edges$from, cg$edges$to)]
core <- graph_from_data_frame(
cg$edges,
vertices = cg$nodes[cg$nodes$id %in% connected, ],
directed = TRUE
)
V(core)$type <- factor(V(core)$type, levels = type_levels)

set.seed(204)
# Fruchterman-Reingold applies repulsion between all node pairs (not just
# connected ones), spreading nodes more evenly across the canvas. Normalise
# axes afterwards to fill the full canvas and eliminate edge whitespace.
layout <- create_layout(core, layout = "fr", niter = 2000)
norm_axis <- function(x) {
r <- diff(range(x))
if (r == 0) return(x)
(x - min(x)) / r * 2 - 1
}
layout$x <- norm_axis(layout$x)
layout$y <- norm_axis(layout$y)

ggraph(layout) +
geom_edge_link(
arrow = arrow(length = unit(1.8, "mm"), type = "closed"),
end_cap = circle(2, "mm"),
edge_alpha = 0.25, edge_width = 0.3
) +
geom_node_point(color = "grey60", size = 0.8, alpha = 0.7) +
geom_label_repel(
data = as.data.frame(layout),
aes(x = x, y = y, label = title, fill = type, size = n_desc),
inherit.aes = FALSE,
color = "white",
fontface = "bold",
max.overlaps = Inf,
force = 3,
force_pull = 0.5,
box.padding = unit(0.25, "lines"),
label.padding = unit(0.1, "lines"),
label.r = unit(0.05, "lines"),
label.size = 0.1,
alpha = 0.88,
segment.color = "grey60",
segment.alpha = 0.6,
segment.size = 0.3,
min.segment.length = 0,
seed = 204
) +
scale_fill_manual(
values = type_palette, labels = type_labels, name = "Type", drop = FALSE
) +
scale_size_continuous(range = c(8.0, 16.0), guide = "none") +
theme_void() +
theme(legend.position = "bottom")
```

Dependency structure of the definitions and results in the notes.
An arrow points from a result to each result that uses it.
Color indicates result type (see legend).
Label font size is proportional to the number of total descendants.

:::

:::
:::

::: {.content-visible when-format="pdf"}

The dependency diagram is only available in the
[HTML version of the notes](https://d-morrison.github.io/rme/chapters/concept-map.html).

:::

::: {.content-visible when-format="docx"}

The dependency diagram is only available in the
[HTML version of the notes](https://d-morrison.github.io/rme/chapters/concept-map.html).

:::

## Descendants of each result {#sec-descendants-table}

@tbl-descendants lists every result that has at least one descendant,
sorted by the number of total descendants (direct or transitive).

::: {#tbl-descendants}

```{r}
#| label: descendants-table-build
#| code-fold: true
#| message: false
#| warning: false

ranked <- cg$nodes[cg$nodes$n_desc >= 1, ]
ranked <- ranked[order(-ranked$n_desc, ranked$id), ]

descendant_table <- data.frame(
Result = ranked$title,
Type = type_labels[ranked$type],
`Direct descendants` = ranked$n_direct,
`Total descendants` = ranked$n_desc,
check.names = FALSE, row.names = NULL, stringsAsFactors = FALSE
)

knitr::kable(descendant_table, format = "pipe", align = "llrr")
```

All results with at least one descendant,
sorted by number of total descendants (most-foundational first).

:::
Loading
Loading