ready set go
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
# Target Trial Emulation Learning Project
|
||||
|
||||
This project is for learning how to code and understand target trial emulation in R.
|
||||
|
||||
The main teaching example is an ICU septic shock study:
|
||||
|
||||
> Among ICU patients with suspected septic shock at ICU admission, what is the effect of initiating vasopressors early versus not initiating vasopressors early on 28-day mortality?
|
||||
|
||||
## How To Work With The User
|
||||
|
||||
- Teach by showing small code chunks that the user can type in manually.
|
||||
- Explain each meaningful line of code before moving on.
|
||||
- Teach the base R mechanics when a concept is new, then prefer readable tidyverse-style code for routine analysis.
|
||||
- Make the smallest correct change when editing project files.
|
||||
- Keep scripts and notebooks numbered so the learning sequence is obvious.
|
||||
- Avoid over-abstraction early; build reusable primitives only after the concept is clear.
|
||||
- When adding reusable code, explain what future project need it supports.
|
||||
- When using external dependencies, prefer wrapping them behind project functions so the user learns stable primitives.
|
||||
|
||||
## Code Style
|
||||
|
||||
- Use the native pipe `|>`, not `%>%`
|
||||
- snake_case for all names
|
||||
- Prefer `vapply` over `sapply`; explicit return types
|
||||
- Use `cli::cli_*` for messages, not `message()`/`cat()`
|
||||
- Prefer `dplyr` verbs for data manipulation when external dependencies are allowed.
|
||||
- Prefer `skimr` for quick data summaries.
|
||||
- Prefer `gt` and `gtsummary` for clear analytic tables in notebooks and reports.
|
||||
- Style with `styler::style_pkg()` before commits
|
||||
|
||||
## Don'ts
|
||||
|
||||
- Don't modify `renv.lock` by hand
|
||||
- Don't `setwd()` — rely on the project root (`here::here()`)
|
||||
- Don't introduce new dependencies beyond the approved stack without asking
|
||||
|
||||
## Stack
|
||||
|
||||
- R 4.4 managed by rig
|
||||
- renv for dependency management; lockfile is source of truth
|
||||
- targets for pipeline orchestration
|
||||
- tidyverse, especially `dplyr`, for routine data manipulation
|
||||
- data.table for performance-oriented data manipulation when needed
|
||||
- skimr for quick data summaries
|
||||
- gt for presentation tables
|
||||
- gtsummary for descriptive and model summary tables
|
||||
- Quarto for reports
|
||||
|
||||
## Dependency Preference
|
||||
|
||||
- The first scripts may remain base R to teach the underlying mechanics.
|
||||
- Going forward, use `dplyr`, `tidyverse`, `skimr`, `gt`, and `gtsummary` where they make the code clearer.
|
||||
- Keep base R explanations available when they help the user understand what the package code is doing.
|
||||
- Do not add packages outside the approved stack without asking first.
|
||||
|
||||
## Learning Roadmap
|
||||
|
||||
- [x] Choose ICU teaching scenario: early vasopressor strategy in septic shock.
|
||||
- [x] Simulate a simple ICU observational cohort.
|
||||
- [x] Define the target trial protocol explicitly.
|
||||
- [x] Estimate a naive observational association.
|
||||
- [ ] Show why naive comparison can be biased.
|
||||
- [ ] Align time zero and eligibility criteria.
|
||||
- [ ] Introduce treatment assignment windows.
|
||||
- [ ] Add censoring logic.
|
||||
- [ ] Add inverse probability weighting from first principles.
|
||||
- [x] Refactor repeated logic into reusable project functions.
|
||||
- [ ] Re-implement selected steps with external dependencies.
|
||||
- [ ] Build wrapper functions around external dependency workflows.
|
||||
- [ ] Add a targets pipeline.
|
||||
- [x] Add initial Quarto report for reproducible analysis.
|
||||
|
||||
## Initial Target Trial Protocol
|
||||
|
||||
Clinical question:
|
||||
|
||||
Among ICU patients with suspected septic shock at ICU admission, what is the effect of starting vasopressors within 2 hours compared with not starting vasopressors within 2 hours on 28-day mortality?
|
||||
|
||||
Eligibility criteria:
|
||||
|
||||
- ICU admission.
|
||||
- Suspected sepsis.
|
||||
- Hypotension at baseline.
|
||||
- Elevated lactate at baseline.
|
||||
|
||||
Time zero:
|
||||
|
||||
- ICU admission.
|
||||
|
||||
Treatment strategies:
|
||||
|
||||
- Early vasopressor strategy: start vasopressors within 2 hours of ICU admission.
|
||||
- No early vasopressor strategy: do not start vasopressors within 2 hours of ICU admission.
|
||||
|
||||
Outcome:
|
||||
|
||||
- Death within 28 days.
|
||||
|
||||
Baseline confounders in the first simulated dataset:
|
||||
|
||||
- Age.
|
||||
- Sex.
|
||||
- SOFA score.
|
||||
- Lactate.
|
||||
- Mean arterial pressure.
|
||||
|
||||
Initial causal contrast:
|
||||
|
||||
- Risk difference in 28-day mortality.
|
||||
- Risk ratio for 28-day mortality.
|
||||
|
||||
## File Sequence
|
||||
|
||||
- `scripts/01_simulate_icu_data_base_r.R`: generate synthetic ICU cohort data from the reusable simulation primitive.
|
||||
- `scripts/02_naive_analysis_base_r.R`: compute an initial naive comparison with readable tidyverse-style code.
|
||||
- `R/simulate_icu_cohort.R`: first reusable simulation primitive.
|
||||
- `notebooks/01_target_trial_basics.qmd`: conceptual walkthrough of the target trial protocol with `skimr`, `gt`, and `gtsummary` examples.
|
||||
@@ -0,0 +1,71 @@
|
||||
suppressPackageStartupMessages(library(dplyr))
|
||||
|
||||
simulate_icu_cohort <- function(n_patients = 1000, seed = 20260531) {
|
||||
set.seed(seed)
|
||||
|
||||
tibble(
|
||||
patient_id = seq_len(n_patients),
|
||||
|
||||
age = round(rnorm(n_patients, mean = 65, sd = 14)) |>
|
||||
pmin(95) |>
|
||||
pmax(18),
|
||||
|
||||
sex = sample(c("female", "male"), size = n_patients, replace = TRUE, prob = c(0.45, 0.55)),
|
||||
|
||||
sofa_score = rpois(n_patients, lambda = 7) |>
|
||||
pmin(20),
|
||||
|
||||
lactate = round(rlnorm(n_patients, meanlog = log(3), sdlog = 0.5), 1) |>
|
||||
pmin(15),
|
||||
|
||||
map = round(rnorm(n_patients, mean = 62, sd = 10)) |>
|
||||
pmin(95) |>
|
||||
pmax(35),
|
||||
|
||||
suspected_sepsis = rbinom(n_patients, size = 1, prob = 0.90)
|
||||
) |>
|
||||
mutate(
|
||||
hypotension_at_baseline = as.integer(map < 65),
|
||||
elevated_lactate_at_baseline = as.integer(lactate >= 2),
|
||||
eligible = as.integer(
|
||||
suspected_sepsis == 1 &
|
||||
hypotension_at_baseline == 1 &
|
||||
elevated_lactate_at_baseline == 1
|
||||
),
|
||||
severity_score = 0.04 * (age - 65) +
|
||||
0.18 * (sofa_score - 7) +
|
||||
0.25 * (lactate - 3) -
|
||||
0.04 * (map - 62),
|
||||
prob_early_vasopressor = plogis(-0.2 + severity_score),
|
||||
early_vasopressor = rbinom(n(), size = 1, prob = prob_early_vasopressor),
|
||||
time_to_vasopressor_hours = ifelse(
|
||||
early_vasopressor == 1,
|
||||
runif(n(), min = 0, max = 2),
|
||||
runif(n(), min = 2, max = 24)
|
||||
) |>
|
||||
round(2),
|
||||
mortality_linear_predictor = -1.4 +
|
||||
0.03 * (age - 65) +
|
||||
0.20 * (sofa_score - 7) +
|
||||
0.28 * (lactate - 3) -
|
||||
0.05 * (map - 62) -
|
||||
0.25 * early_vasopressor,
|
||||
prob_death_28d = plogis(mortality_linear_predictor),
|
||||
death_28d = rbinom(n(), size = 1, prob = prob_death_28d)
|
||||
) |>
|
||||
select(
|
||||
patient_id,
|
||||
age,
|
||||
sex,
|
||||
sofa_score,
|
||||
lactate,
|
||||
map,
|
||||
suspected_sepsis,
|
||||
hypotension_at_baseline,
|
||||
elevated_lactate_at_baseline,
|
||||
eligible,
|
||||
early_vasopressor,
|
||||
time_to_vasopressor_hours,
|
||||
death_28d
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,211 @@
|
||||
---
|
||||
title: "Target Trial Basics: Early Vasopressors in Septic Shock"
|
||||
format: html
|
||||
execute:
|
||||
echo: true
|
||||
warning: false
|
||||
message: false
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
This notebook introduces the target trial we want to emulate.
|
||||
|
||||
The clinical question is:
|
||||
|
||||
> Among ICU patients with suspected septic shock at ICU admission, what is the effect of starting vasopressors within 2 hours compared with not starting vasopressors within 2 hours on 28-day mortality?
|
||||
|
||||
## Setup
|
||||
|
||||
```{r}
|
||||
library(dplyr)
|
||||
library(gt)
|
||||
library(gtsummary)
|
||||
library(readr)
|
||||
library(skimr)
|
||||
library(tibble)
|
||||
```
|
||||
|
||||
These packages make the routine analysis code easier to read.
|
||||
|
||||
`dplyr` handles data manipulation, `readr` reads rectangular files, `skimr` gives quick data summaries, `gt` builds display tables, and `gtsummary` builds analytic summary tables.
|
||||
|
||||
## Why A Target Trial?
|
||||
|
||||
Observational ICU data are not randomized.
|
||||
|
||||
Sicker patients are often treated earlier, so a simple comparison between patients who received early vasopressors and patients who did not can be biased.
|
||||
|
||||
The target trial framework asks us to describe the randomized trial we wish we had run, then emulate it as closely as possible using observational data.
|
||||
|
||||
## Target Trial Protocol
|
||||
|
||||
```{r}
|
||||
target_trial_protocol <- tribble(
|
||||
~component, ~definition,
|
||||
"Eligibility criteria", "ICU admission, suspected sepsis, hypotension, elevated lactate",
|
||||
"Time zero", "ICU admission",
|
||||
"Treatment strategy 1", "Start vasopressors within 2 hours",
|
||||
"Treatment strategy 2", "Do not start vasopressors within 2 hours",
|
||||
"Outcome", "Death within 28 days",
|
||||
"Causal contrast", "Risk difference and risk ratio"
|
||||
)
|
||||
|
||||
target_trial_protocol |>
|
||||
gt() |>
|
||||
tab_header(title = "Target Trial Protocol") |>
|
||||
cols_label(
|
||||
component = "Component",
|
||||
definition = "Definition"
|
||||
)
|
||||
```
|
||||
|
||||
`tribble()` creates a small tibble by typing the rows directly.
|
||||
|
||||
`gt()` turns that tibble into a clearer presentation table.
|
||||
|
||||
## Load The Simulated Cohort
|
||||
|
||||
This first version uses a CSV generated by `scripts/01_simulate_icu_data_base_r.R`.
|
||||
|
||||
```{r}
|
||||
icu_data <- read_csv("../data/icu_septic_shock_base_r.csv", show_col_types = FALSE)
|
||||
```
|
||||
|
||||
`read_csv()` reads a rectangular CSV file into R.
|
||||
|
||||
The object `icu_data` is a tibble, where each row is one ICU patient.
|
||||
|
||||
## Inspect The Data
|
||||
|
||||
```{r}
|
||||
icu_data |>
|
||||
slice_head(n = 6)
|
||||
```
|
||||
|
||||
`slice_head()` prints the first few rows so we can inspect the structure before analyzing anything.
|
||||
|
||||
```{r}
|
||||
skim(icu_data)
|
||||
```
|
||||
|
||||
`skim()` gives a quick summary of variable types, missingness, and distributions.
|
||||
|
||||
## Apply Eligibility Criteria
|
||||
|
||||
```{r}
|
||||
eligible_data <- icu_data |>
|
||||
filter(eligible == 1)
|
||||
```
|
||||
|
||||
`filter()` keeps rows that satisfy a condition.
|
||||
|
||||
Here, we keep only patients who satisfy the simulated eligibility criteria.
|
||||
|
||||
## Count Treatment Groups
|
||||
|
||||
```{r}
|
||||
eligible_data |>
|
||||
count(early_vasopressor)
|
||||
```
|
||||
|
||||
`count()` counts how many eligible patients were observed under each treatment group.
|
||||
|
||||
In this first simplified dataset:
|
||||
|
||||
- `1` means vasopressors started within 2 hours.
|
||||
- `0` means vasopressors were not started within 2 hours.
|
||||
|
||||
## Estimate Naive Mortality Risks
|
||||
|
||||
```{r}
|
||||
naive_risks <- eligible_data |>
|
||||
group_by(early_vasopressor) |>
|
||||
summarize(
|
||||
n_patients = n(),
|
||||
risk_death_28d = mean(death_28d),
|
||||
.groups = "drop"
|
||||
)
|
||||
|
||||
naive_risks |>
|
||||
gt() |>
|
||||
tab_header(title = "Naive 28-Day Mortality Risk") |>
|
||||
cols_label(
|
||||
early_vasopressor = "Early vasopressor",
|
||||
n_patients = "Patients",
|
||||
risk_death_28d = "28-day mortality risk"
|
||||
) |>
|
||||
fmt_number(columns = risk_death_28d, decimals = 3)
|
||||
```
|
||||
|
||||
Because `death_28d` is coded as 0 or 1, its mean is the proportion who died.
|
||||
|
||||
This is a naive comparison because it does not yet adjust for the fact that treatment decisions depend on patient severity.
|
||||
|
||||
## Estimate Naive Contrasts
|
||||
|
||||
```{r}
|
||||
risk_early <- naive_risks |>
|
||||
filter(early_vasopressor == 1) |>
|
||||
pull(risk_death_28d)
|
||||
|
||||
risk_not_early <- naive_risks |>
|
||||
filter(early_vasopressor == 0) |>
|
||||
pull(risk_death_28d)
|
||||
|
||||
naive_contrasts <- tibble(
|
||||
measure = c("Risk difference", "Risk ratio"),
|
||||
value = c(
|
||||
risk_early - risk_not_early,
|
||||
risk_early / risk_not_early
|
||||
)
|
||||
)
|
||||
|
||||
naive_contrasts |>
|
||||
gt() |>
|
||||
tab_header(title = "Naive Treatment Contrast") |>
|
||||
cols_label(
|
||||
measure = "Measure",
|
||||
value = "Value"
|
||||
) |>
|
||||
fmt_number(columns = value, decimals = 3)
|
||||
```
|
||||
|
||||
The risk difference is an absolute difference in 28-day mortality risk.
|
||||
|
||||
The risk ratio is a relative comparison of 28-day mortality risk.
|
||||
|
||||
## Check Confounding By Severity
|
||||
|
||||
```{r}
|
||||
eligible_data |>
|
||||
mutate(
|
||||
early_vasopressor = factor(
|
||||
early_vasopressor,
|
||||
levels = c(0, 1),
|
||||
labels = c("No early vasopressor", "Early vasopressor")
|
||||
)
|
||||
) |>
|
||||
select(early_vasopressor, age, sex, sofa_score, lactate, map, death_28d) |>
|
||||
tbl_summary(
|
||||
by = early_vasopressor,
|
||||
statistic = list(
|
||||
all_continuous() ~ "{mean} ({sd})",
|
||||
all_categorical() ~ "{n} ({p}%)"
|
||||
),
|
||||
missing = "no"
|
||||
) |>
|
||||
add_overall()
|
||||
```
|
||||
|
||||
This compares baseline severity between the two treatment groups.
|
||||
|
||||
If early vasopressor patients have higher SOFA scores, higher lactate, or lower MAP, then the naive comparison mixes the treatment effect with baseline severity differences.
|
||||
|
||||
That problem is one reason we need target trial emulation methods rather than a simple treated-versus-untreated comparison.
|
||||
|
||||
## Next Step
|
||||
|
||||
The next lesson should walk through `R/simulate_icu_cohort.R` line by line.
|
||||
|
||||
After that, we can use `scripts/02_naive_analysis_base_r.R` to compute the first naive association and then discuss why it is not yet a causal estimate.
|
||||
+12
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+236
@@ -0,0 +1,236 @@
|
||||
/* quarto syntax highlight colors */
|
||||
:root {
|
||||
--quarto-hl-ot-color: #003B4F;
|
||||
--quarto-hl-at-color: #657422;
|
||||
--quarto-hl-ss-color: #20794D;
|
||||
--quarto-hl-an-color: #5E5E5E;
|
||||
--quarto-hl-fu-color: #4758AB;
|
||||
--quarto-hl-st-color: #20794D;
|
||||
--quarto-hl-cf-color: #003B4F;
|
||||
--quarto-hl-op-color: #5E5E5E;
|
||||
--quarto-hl-er-color: #AD0000;
|
||||
--quarto-hl-bn-color: #AD0000;
|
||||
--quarto-hl-al-color: #AD0000;
|
||||
--quarto-hl-va-color: #111111;
|
||||
--quarto-hl-bu-color: inherit;
|
||||
--quarto-hl-ex-color: inherit;
|
||||
--quarto-hl-pp-color: #AD0000;
|
||||
--quarto-hl-in-color: #5E5E5E;
|
||||
--quarto-hl-vs-color: #20794D;
|
||||
--quarto-hl-wa-color: #5E5E5E;
|
||||
--quarto-hl-do-color: #5E5E5E;
|
||||
--quarto-hl-im-color: #00769E;
|
||||
--quarto-hl-ch-color: #20794D;
|
||||
--quarto-hl-dt-color: #AD0000;
|
||||
--quarto-hl-fl-color: #AD0000;
|
||||
--quarto-hl-co-color: #5E5E5E;
|
||||
--quarto-hl-cv-color: #5E5E5E;
|
||||
--quarto-hl-cn-color: #8f5902;
|
||||
--quarto-hl-sc-color: #5E5E5E;
|
||||
--quarto-hl-dv-color: #AD0000;
|
||||
--quarto-hl-kw-color: #003B4F;
|
||||
}
|
||||
|
||||
/* other quarto variables */
|
||||
:root {
|
||||
--quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
/* syntax highlight based on Pandoc's rules */
|
||||
pre > code.sourceCode > span {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
code.sourceCode > span {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
div.sourceCode,
|
||||
div.sourceCode pre.sourceCode {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
/* Normal */
|
||||
code span {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
code span.al {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Annotation */
|
||||
code span.an {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Attribute */
|
||||
code span.at {
|
||||
color: #657422;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* BaseN */
|
||||
code span.bn {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* BuiltIn */
|
||||
code span.bu {
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* ControlFlow */
|
||||
code span.cf {
|
||||
color: #003B4F;
|
||||
font-weight: bold;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Char */
|
||||
code span.ch {
|
||||
color: #20794D;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Constant */
|
||||
code span.cn {
|
||||
color: #8f5902;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Comment */
|
||||
code span.co {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* CommentVar */
|
||||
code span.cv {
|
||||
color: #5E5E5E;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Documentation */
|
||||
code span.do {
|
||||
color: #5E5E5E;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* DataType */
|
||||
code span.dt {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* DecVal */
|
||||
code span.dv {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Error */
|
||||
code span.er {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Extension */
|
||||
code span.ex {
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Float */
|
||||
code span.fl {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Function */
|
||||
code span.fu {
|
||||
color: #4758AB;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Import */
|
||||
code span.im {
|
||||
color: #00769E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Information */
|
||||
code span.in {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Keyword */
|
||||
code span.kw {
|
||||
color: #003B4F;
|
||||
font-weight: bold;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Operator */
|
||||
code span.op {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Other */
|
||||
code span.ot {
|
||||
color: #003B4F;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Preprocessor */
|
||||
code span.pp {
|
||||
color: #AD0000;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* SpecialChar */
|
||||
code span.sc {
|
||||
color: #5E5E5E;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* SpecialString */
|
||||
code span.ss {
|
||||
color: #20794D;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* String */
|
||||
code span.st {
|
||||
color: #20794D;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Variable */
|
||||
code span.va {
|
||||
color: #111111;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* VerbatimString */
|
||||
code span.vs {
|
||||
color: #20794D;
|
||||
font-style: inherit;
|
||||
}
|
||||
|
||||
/* Warning */
|
||||
code span.wa {
|
||||
color: #5E5E5E;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.prevent-inlining {
|
||||
content: "</";
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=789ad8cd35626ba772517505b42a116e.css.map */
|
||||
@@ -0,0 +1,845 @@
|
||||
import * as tabsets from "./tabsets/tabsets.js";
|
||||
|
||||
const sectionChanged = new CustomEvent("quarto-sectionChanged", {
|
||||
detail: {},
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
composed: false,
|
||||
});
|
||||
|
||||
const layoutMarginEls = () => {
|
||||
// Find any conflicting margin elements and add margins to the
|
||||
// top to prevent overlap
|
||||
const marginChildren = window.document.querySelectorAll(
|
||||
".column-margin.column-container > *, .margin-caption, .aside"
|
||||
);
|
||||
|
||||
let lastBottom = 0;
|
||||
for (const marginChild of marginChildren) {
|
||||
if (marginChild.offsetParent !== null) {
|
||||
// clear the top margin so we recompute it
|
||||
marginChild.style.marginTop = null;
|
||||
const top = marginChild.getBoundingClientRect().top + window.scrollY;
|
||||
if (top < lastBottom) {
|
||||
const marginChildStyle = window.getComputedStyle(marginChild);
|
||||
const marginBottom = parseFloat(marginChildStyle["marginBottom"]);
|
||||
const margin = lastBottom - top + marginBottom;
|
||||
marginChild.style.marginTop = `${margin}px`;
|
||||
}
|
||||
const styles = window.getComputedStyle(marginChild);
|
||||
const marginTop = parseFloat(styles["marginTop"]);
|
||||
lastBottom = top + marginChild.getBoundingClientRect().height + marginTop;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.document.addEventListener("DOMContentLoaded", function (_event) {
|
||||
// Recompute the position of margin elements anytime the body size changes
|
||||
if (window.ResizeObserver) {
|
||||
const resizeObserver = new window.ResizeObserver(
|
||||
throttle(() => {
|
||||
layoutMarginEls();
|
||||
if (
|
||||
window.document.body.getBoundingClientRect().width < 990 &&
|
||||
isReaderMode()
|
||||
) {
|
||||
quartoToggleReader();
|
||||
}
|
||||
}, 50)
|
||||
);
|
||||
resizeObserver.observe(window.document.body);
|
||||
}
|
||||
|
||||
const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]');
|
||||
const sidebarEl = window.document.getElementById("quarto-sidebar");
|
||||
const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left");
|
||||
const marginSidebarEl = window.document.getElementById(
|
||||
"quarto-margin-sidebar"
|
||||
);
|
||||
// function to determine whether the element has a previous sibling that is active
|
||||
const prevSiblingIsActiveLink = (el) => {
|
||||
const sibling = el.previousElementSibling;
|
||||
if (sibling && sibling.tagName === "A") {
|
||||
return sibling.classList.contains("active");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// dispatch for htmlwidgets
|
||||
// they use slideenter event to trigger resize
|
||||
function fireSlideEnter() {
|
||||
const event = window.document.createEvent("Event");
|
||||
event.initEvent("slideenter", true, true);
|
||||
window.document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]');
|
||||
tabs.forEach((tab) => {
|
||||
tab.addEventListener("shown.bs.tab", fireSlideEnter);
|
||||
});
|
||||
|
||||
// dispatch for shiny
|
||||
// they use BS shown and hidden events to trigger rendering
|
||||
function distpatchShinyEvents(previous, current) {
|
||||
if (window.jQuery) {
|
||||
if (previous) {
|
||||
window.jQuery(previous).trigger("hidden");
|
||||
}
|
||||
if (current) {
|
||||
window.jQuery(current).trigger("shown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tabby.js listener: Trigger event for htmlwidget and shiny
|
||||
document.addEventListener(
|
||||
"tabby",
|
||||
function (event) {
|
||||
fireSlideEnter();
|
||||
distpatchShinyEvents(event.detail.previousTab, event.detail.tab);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
// Track scrolling and mark TOC links as active
|
||||
// get table of contents and sidebar (bail if we don't have at least one)
|
||||
const tocLinks = tocEl
|
||||
? [...tocEl.querySelectorAll("a[data-scroll-target]")]
|
||||
: [];
|
||||
const makeActive = (link) => tocLinks[link].classList.add("active");
|
||||
const removeActive = (link) => tocLinks[link].classList.remove("active");
|
||||
const removeAllActive = () =>
|
||||
[...Array(tocLinks.length).keys()].forEach((link) => removeActive(link));
|
||||
|
||||
// activate the anchor for a section associated with this TOC entry
|
||||
tocLinks.forEach((link) => {
|
||||
link.addEventListener("click", () => {
|
||||
if (link.href.indexOf("#") !== -1) {
|
||||
const anchor = link.href.split("#")[1];
|
||||
const heading = window.document.querySelector(
|
||||
`[data-anchor-id="${anchor}"]`
|
||||
);
|
||||
if (heading) {
|
||||
// Add the class
|
||||
heading.classList.add("reveal-anchorjs-link");
|
||||
|
||||
// function to show the anchor
|
||||
const handleMouseout = () => {
|
||||
heading.classList.remove("reveal-anchorjs-link");
|
||||
heading.removeEventListener("mouseout", handleMouseout);
|
||||
};
|
||||
|
||||
// add a function to clear the anchor when the user mouses out of it
|
||||
heading.addEventListener("mouseout", handleMouseout);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const sections = tocLinks.map((link) => {
|
||||
const target = link.getAttribute("data-scroll-target");
|
||||
if (target.startsWith("#")) {
|
||||
return window.document.getElementById(decodeURI(`${target.slice(1)}`));
|
||||
} else {
|
||||
return window.document.querySelector(decodeURI(`${target}`));
|
||||
}
|
||||
});
|
||||
|
||||
const sectionMargin = 200;
|
||||
let currentActive = 0;
|
||||
// track whether we've initialized state the first time
|
||||
let init = false;
|
||||
|
||||
const updateActiveLink = () => {
|
||||
// The index from bottom to top (e.g. reversed list)
|
||||
let sectionIndex = -1;
|
||||
if (
|
||||
window.innerHeight + window.pageYOffset >=
|
||||
window.document.body.offsetHeight
|
||||
) {
|
||||
// This is the no-scroll case where last section should be the active one
|
||||
sectionIndex = 0;
|
||||
} else {
|
||||
// This finds the last section visible on screen that should be made active
|
||||
sectionIndex = [...sections].reverse().findIndex((section) => {
|
||||
if (section) {
|
||||
return window.pageYOffset >= section.offsetTop - sectionMargin;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (sectionIndex > -1) {
|
||||
const current = sections.length - sectionIndex - 1;
|
||||
if (current !== currentActive) {
|
||||
removeAllActive();
|
||||
currentActive = current;
|
||||
makeActive(current);
|
||||
if (init) {
|
||||
window.dispatchEvent(sectionChanged);
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const inHiddenRegion = (top, bottom, hiddenRegions) => {
|
||||
for (const region of hiddenRegions) {
|
||||
if (top <= region.bottom && bottom >= region.top) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const categorySelector = "header.quarto-title-block .quarto-category";
|
||||
const activateCategories = (href) => {
|
||||
// Find any categories
|
||||
// Surround them with a link pointing back to:
|
||||
// #category=Authoring
|
||||
try {
|
||||
const categoryEls = window.document.querySelectorAll(categorySelector);
|
||||
for (const categoryEl of categoryEls) {
|
||||
const categoryText = categoryEl.textContent;
|
||||
if (categoryText) {
|
||||
const link = `${href}#category=${encodeURIComponent(categoryText)}`;
|
||||
const linkEl = window.document.createElement("a");
|
||||
linkEl.setAttribute("href", link);
|
||||
for (const child of categoryEl.childNodes) {
|
||||
linkEl.append(child);
|
||||
}
|
||||
categoryEl.appendChild(linkEl);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
};
|
||||
function hasTitleCategories() {
|
||||
return window.document.querySelector(categorySelector) !== null;
|
||||
}
|
||||
|
||||
function offsetRelativeUrl(url) {
|
||||
const offset = getMeta("quarto:offset");
|
||||
return offset ? offset + url : url;
|
||||
}
|
||||
|
||||
function offsetAbsoluteUrl(url) {
|
||||
const offset = getMeta("quarto:offset");
|
||||
const baseUrl = new URL(offset, window.location);
|
||||
|
||||
const projRelativeUrl = url.replace(baseUrl, "");
|
||||
if (projRelativeUrl.startsWith("/")) {
|
||||
return projRelativeUrl;
|
||||
} else {
|
||||
return "/" + projRelativeUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// read a meta tag value
|
||||
function getMeta(metaName) {
|
||||
const metas = window.document.getElementsByTagName("meta");
|
||||
for (let i = 0; i < metas.length; i++) {
|
||||
if (metas[i].getAttribute("name") === metaName) {
|
||||
return metas[i].getAttribute("content");
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
async function findAndActivateCategories() {
|
||||
// Categories search with listing only use path without query
|
||||
const currentPagePath = offsetAbsoluteUrl(
|
||||
window.location.origin + window.location.pathname
|
||||
);
|
||||
const response = await fetch(offsetRelativeUrl("listings.json"));
|
||||
if (response.status == 200) {
|
||||
return response.json().then(function (listingPaths) {
|
||||
const listingHrefs = [];
|
||||
for (const listingPath of listingPaths) {
|
||||
const pathWithoutLeadingSlash = listingPath.listing.substring(1);
|
||||
for (const item of listingPath.items) {
|
||||
const encodedItem = encodeURI(item);
|
||||
if (
|
||||
encodedItem === currentPagePath ||
|
||||
encodedItem === currentPagePath + "index.html"
|
||||
) {
|
||||
// Resolve this path against the offset to be sure
|
||||
// we already are using the correct path to the listing
|
||||
// (this adjusts the listing urls to be rooted against
|
||||
// whatever root the page is actually running against)
|
||||
const relative = offsetRelativeUrl(pathWithoutLeadingSlash);
|
||||
const baseUrl = window.location;
|
||||
const resolvedPath = new URL(relative, baseUrl);
|
||||
listingHrefs.push(resolvedPath.pathname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look up the tree for a nearby linting and use that if we find one
|
||||
const nearestListing = findNearestParentListing(
|
||||
offsetAbsoluteUrl(window.location.pathname),
|
||||
listingHrefs
|
||||
);
|
||||
if (nearestListing) {
|
||||
activateCategories(nearestListing);
|
||||
} else {
|
||||
// See if the referrer is a listing page for this item
|
||||
const referredRelativePath = offsetAbsoluteUrl(document.referrer);
|
||||
const referrerListing = listingHrefs.find((listingHref) => {
|
||||
const isListingReferrer =
|
||||
listingHref === referredRelativePath ||
|
||||
listingHref === referredRelativePath + "index.html";
|
||||
return isListingReferrer;
|
||||
});
|
||||
|
||||
if (referrerListing) {
|
||||
// Try to use the referrer if possible
|
||||
activateCategories(referrerListing);
|
||||
} else if (listingHrefs.length > 0) {
|
||||
// Otherwise, just fall back to the first listing
|
||||
activateCategories(listingHrefs[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (hasTitleCategories()) {
|
||||
findAndActivateCategories();
|
||||
}
|
||||
|
||||
const findNearestParentListing = (href, listingHrefs) => {
|
||||
if (!href || !listingHrefs) {
|
||||
return undefined;
|
||||
}
|
||||
// Look up the tree for a nearby linting and use that if we find one
|
||||
const relativeParts = href.substring(1).split("/");
|
||||
while (relativeParts.length > 0) {
|
||||
const path = relativeParts.join("/");
|
||||
for (const listingHref of listingHrefs) {
|
||||
if (listingHref.startsWith(path)) {
|
||||
return listingHref;
|
||||
}
|
||||
}
|
||||
relativeParts.pop();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const manageSidebarVisiblity = (el, placeholderDescriptor) => {
|
||||
let isVisible = true;
|
||||
let elRect;
|
||||
|
||||
return (hiddenRegions) => {
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the last element of the TOC
|
||||
const lastChildEl = el.lastElementChild;
|
||||
|
||||
if (lastChildEl) {
|
||||
// Converts the sidebar to a menu
|
||||
const convertToMenu = () => {
|
||||
for (const child of el.children) {
|
||||
child.style.opacity = 0;
|
||||
child.style.overflow = "hidden";
|
||||
child.style.pointerEvents = "none";
|
||||
}
|
||||
|
||||
nexttick(() => {
|
||||
const toggleContainer = window.document.createElement("div");
|
||||
toggleContainer.style.width = "100%";
|
||||
toggleContainer.classList.add("zindex-over-content");
|
||||
toggleContainer.classList.add("quarto-sidebar-toggle");
|
||||
toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom
|
||||
toggleContainer.id = placeholderDescriptor.id;
|
||||
toggleContainer.style.position = "fixed";
|
||||
|
||||
const toggleIcon = window.document.createElement("i");
|
||||
toggleIcon.classList.add("quarto-sidebar-toggle-icon");
|
||||
toggleIcon.classList.add("bi");
|
||||
toggleIcon.classList.add("bi-caret-down-fill");
|
||||
|
||||
const toggleTitle = window.document.createElement("div");
|
||||
const titleEl = window.document.body.querySelector(
|
||||
placeholderDescriptor.titleSelector
|
||||
);
|
||||
if (titleEl) {
|
||||
toggleTitle.append(
|
||||
titleEl.textContent || titleEl.innerText,
|
||||
toggleIcon
|
||||
);
|
||||
}
|
||||
toggleTitle.classList.add("zindex-over-content");
|
||||
toggleTitle.classList.add("quarto-sidebar-toggle-title");
|
||||
toggleContainer.append(toggleTitle);
|
||||
|
||||
const toggleContents = window.document.createElement("div");
|
||||
toggleContents.classList = el.classList;
|
||||
toggleContents.classList.add("zindex-over-content");
|
||||
toggleContents.classList.add("quarto-sidebar-toggle-contents");
|
||||
for (const child of el.children) {
|
||||
if (child.id === "toc-title") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const clone = child.cloneNode(true);
|
||||
clone.style.opacity = 1;
|
||||
clone.style.pointerEvents = null;
|
||||
clone.style.display = null;
|
||||
toggleContents.append(clone);
|
||||
}
|
||||
toggleContents.style.height = "0px";
|
||||
const positionToggle = () => {
|
||||
// position the element (top left of parent, same width as parent)
|
||||
if (!elRect) {
|
||||
elRect = el.getBoundingClientRect();
|
||||
}
|
||||
toggleContainer.style.left = `${elRect.left}px`;
|
||||
toggleContainer.style.top = `${elRect.top}px`;
|
||||
toggleContainer.style.width = `${elRect.width}px`;
|
||||
};
|
||||
positionToggle();
|
||||
|
||||
toggleContainer.append(toggleContents);
|
||||
el.parentElement.prepend(toggleContainer);
|
||||
|
||||
// Process clicks
|
||||
let tocShowing = false;
|
||||
// Allow the caller to control whether this is dismissed
|
||||
// when it is clicked (e.g. sidebar navigation supports
|
||||
// opening and closing the nav tree, so don't dismiss on click)
|
||||
const clickEl = placeholderDescriptor.dismissOnClick
|
||||
? toggleContainer
|
||||
: toggleTitle;
|
||||
|
||||
const closeToggle = () => {
|
||||
if (tocShowing) {
|
||||
toggleContainer.classList.remove("expanded");
|
||||
toggleContents.style.height = "0px";
|
||||
tocShowing = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Get rid of any expanded toggle if the user scrolls
|
||||
window.document.addEventListener(
|
||||
"scroll",
|
||||
throttle(() => {
|
||||
closeToggle();
|
||||
}, 50)
|
||||
);
|
||||
|
||||
// Handle positioning of the toggle
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
throttle(() => {
|
||||
elRect = undefined;
|
||||
positionToggle();
|
||||
}, 50)
|
||||
);
|
||||
|
||||
window.addEventListener("quarto-hrChanged", () => {
|
||||
elRect = undefined;
|
||||
});
|
||||
|
||||
// Process the click
|
||||
clickEl.onclick = () => {
|
||||
if (!tocShowing) {
|
||||
toggleContainer.classList.add("expanded");
|
||||
toggleContents.style.height = null;
|
||||
tocShowing = true;
|
||||
} else {
|
||||
closeToggle();
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Converts a sidebar from a menu back to a sidebar
|
||||
const convertToSidebar = () => {
|
||||
for (const child of el.children) {
|
||||
child.style.opacity = 1;
|
||||
child.style.overflow = null;
|
||||
child.style.pointerEvents = null;
|
||||
}
|
||||
|
||||
const placeholderEl = window.document.getElementById(
|
||||
placeholderDescriptor.id
|
||||
);
|
||||
if (placeholderEl) {
|
||||
placeholderEl.remove();
|
||||
}
|
||||
|
||||
el.classList.remove("rollup");
|
||||
};
|
||||
|
||||
if (isReaderMode()) {
|
||||
convertToMenu();
|
||||
isVisible = false;
|
||||
} else {
|
||||
// Find the top and bottom o the element that is being managed
|
||||
const elTop = el.offsetTop;
|
||||
const elBottom =
|
||||
elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight;
|
||||
|
||||
if (!isVisible) {
|
||||
// If the element is current not visible reveal if there are
|
||||
// no conflicts with overlay regions
|
||||
if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) {
|
||||
convertToSidebar();
|
||||
isVisible = true;
|
||||
}
|
||||
} else {
|
||||
// If the element is visible, hide it if it conflicts with overlay regions
|
||||
// and insert a placeholder toggle (or if we're in reader mode)
|
||||
if (inHiddenRegion(elTop, elBottom, hiddenRegions)) {
|
||||
convertToMenu();
|
||||
isVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]');
|
||||
for (const tabEl of tabEls) {
|
||||
const id = tabEl.getAttribute("data-bs-target");
|
||||
if (id) {
|
||||
const columnEl = document.querySelector(
|
||||
`${id} .column-margin, .tabset-margin-content`
|
||||
);
|
||||
if (columnEl)
|
||||
tabEl.addEventListener("shown.bs.tab", function (event) {
|
||||
const el = event.srcElement;
|
||||
if (el) {
|
||||
const visibleCls = `${el.id}-margin-content`;
|
||||
// walk up until we find a parent tabset
|
||||
let panelTabsetEl = el.parentElement;
|
||||
while (panelTabsetEl) {
|
||||
if (panelTabsetEl.classList.contains("panel-tabset")) {
|
||||
break;
|
||||
}
|
||||
panelTabsetEl = panelTabsetEl.parentElement;
|
||||
}
|
||||
|
||||
if (panelTabsetEl) {
|
||||
const prevSib = panelTabsetEl.previousElementSibling;
|
||||
if (
|
||||
prevSib &&
|
||||
prevSib.classList.contains("tabset-margin-container")
|
||||
) {
|
||||
const childNodes = prevSib.querySelectorAll(
|
||||
".tabset-margin-content"
|
||||
);
|
||||
for (const childEl of childNodes) {
|
||||
if (childEl.classList.contains(visibleCls)) {
|
||||
childEl.classList.remove("collapse");
|
||||
} else {
|
||||
childEl.classList.add("collapse");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layoutMarginEls();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Manage the visibility of the toc and the sidebar
|
||||
const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, {
|
||||
id: "quarto-toc-toggle",
|
||||
titleSelector: "#toc-title",
|
||||
dismissOnClick: true,
|
||||
});
|
||||
const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, {
|
||||
id: "quarto-sidebarnav-toggle",
|
||||
titleSelector: ".title",
|
||||
dismissOnClick: false,
|
||||
});
|
||||
let tocLeftScrollVisibility;
|
||||
if (leftTocEl) {
|
||||
tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, {
|
||||
id: "quarto-lefttoc-toggle",
|
||||
titleSelector: "#toc-title",
|
||||
dismissOnClick: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Find the first element that uses formatting in special columns
|
||||
const conflictingEls = window.document.body.querySelectorAll(
|
||||
'[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]'
|
||||
);
|
||||
|
||||
// Filter all the possibly conflicting elements into ones
|
||||
// the do conflict on the left or ride side
|
||||
const arrConflictingEls = Array.from(conflictingEls);
|
||||
const leftSideConflictEls = arrConflictingEls.filter((el) => {
|
||||
if (el.tagName === "ASIDE") {
|
||||
return false;
|
||||
}
|
||||
return Array.from(el.classList).find((className) => {
|
||||
return (
|
||||
className !== "column-body" &&
|
||||
className.startsWith("column-") &&
|
||||
!className.endsWith("right") &&
|
||||
!className.endsWith("container") &&
|
||||
className !== "column-margin"
|
||||
);
|
||||
});
|
||||
});
|
||||
const rightSideConflictEls = arrConflictingEls.filter((el) => {
|
||||
if (el.tagName === "ASIDE") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hasMarginCaption = Array.from(el.classList).find((className) => {
|
||||
return className == "margin-caption";
|
||||
});
|
||||
if (hasMarginCaption) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Array.from(el.classList).find((className) => {
|
||||
return (
|
||||
className !== "column-body" &&
|
||||
!className.endsWith("container") &&
|
||||
className.startsWith("column-") &&
|
||||
!className.endsWith("left")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const kOverlapPaddingSize = 10;
|
||||
function toRegions(els) {
|
||||
return els.map((el) => {
|
||||
const boundRect = el.getBoundingClientRect();
|
||||
const top =
|
||||
boundRect.top +
|
||||
document.documentElement.scrollTop -
|
||||
kOverlapPaddingSize;
|
||||
return {
|
||||
top,
|
||||
bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let hasObserved = false;
|
||||
const visibleItemObserver = (els) => {
|
||||
let visibleElements = [...els];
|
||||
const intersectionObserver = new IntersectionObserver(
|
||||
(entries, _observer) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
if (visibleElements.indexOf(entry.target) === -1) {
|
||||
visibleElements.push(entry.target);
|
||||
}
|
||||
} else {
|
||||
visibleElements = visibleElements.filter((visibleEntry) => {
|
||||
return visibleEntry !== entry;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasObserved) {
|
||||
hideOverlappedSidebars();
|
||||
}
|
||||
hasObserved = true;
|
||||
},
|
||||
{}
|
||||
);
|
||||
els.forEach((el) => {
|
||||
intersectionObserver.observe(el);
|
||||
});
|
||||
|
||||
return {
|
||||
getVisibleEntries: () => {
|
||||
return visibleElements;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const rightElementObserver = visibleItemObserver(rightSideConflictEls);
|
||||
const leftElementObserver = visibleItemObserver(leftSideConflictEls);
|
||||
|
||||
const hideOverlappedSidebars = () => {
|
||||
marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries()));
|
||||
sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries()));
|
||||
if (tocLeftScrollVisibility) {
|
||||
tocLeftScrollVisibility(
|
||||
toRegions(leftElementObserver.getVisibleEntries())
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
window.quartoToggleReader = () => {
|
||||
// Applies a slow class (or removes it)
|
||||
// to update the transition speed
|
||||
const slowTransition = (slow) => {
|
||||
const manageTransition = (id, slow) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
if (slow) {
|
||||
el.classList.add("slow");
|
||||
} else {
|
||||
el.classList.remove("slow");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
manageTransition("TOC", slow);
|
||||
manageTransition("quarto-sidebar", slow);
|
||||
};
|
||||
const readerMode = !isReaderMode();
|
||||
setReaderModeValue(readerMode);
|
||||
|
||||
// If we're entering reader mode, slow the transition
|
||||
if (readerMode) {
|
||||
slowTransition(readerMode);
|
||||
}
|
||||
highlightReaderToggle(readerMode);
|
||||
hideOverlappedSidebars();
|
||||
|
||||
// If we're exiting reader mode, restore the non-slow transition
|
||||
if (!readerMode) {
|
||||
slowTransition(!readerMode);
|
||||
}
|
||||
};
|
||||
|
||||
const highlightReaderToggle = (readerMode) => {
|
||||
const els = document.querySelectorAll(".quarto-reader-toggle");
|
||||
if (els) {
|
||||
els.forEach((el) => {
|
||||
if (readerMode) {
|
||||
el.classList.add("reader");
|
||||
} else {
|
||||
el.classList.remove("reader");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const setReaderModeValue = (val) => {
|
||||
if (window.location.protocol !== "file:") {
|
||||
window.localStorage.setItem("quarto-reader-mode", val);
|
||||
} else {
|
||||
localReaderMode = val;
|
||||
}
|
||||
};
|
||||
|
||||
const isReaderMode = () => {
|
||||
if (window.location.protocol !== "file:") {
|
||||
return window.localStorage.getItem("quarto-reader-mode") === "true";
|
||||
} else {
|
||||
return localReaderMode;
|
||||
}
|
||||
};
|
||||
let localReaderMode = null;
|
||||
|
||||
const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded");
|
||||
const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1;
|
||||
|
||||
// Walk the TOC and collapse/expand nodes
|
||||
// Nodes are expanded if:
|
||||
// - they are top level
|
||||
// - they have children that are 'active' links
|
||||
// - they are directly below an link that is 'active'
|
||||
const walk = (el, depth) => {
|
||||
// Tick depth when we enter a UL
|
||||
if (el.tagName === "UL") {
|
||||
depth = depth + 1;
|
||||
}
|
||||
|
||||
// It this is active link
|
||||
let isActiveNode = false;
|
||||
if (el.tagName === "A" && el.classList.contains("active")) {
|
||||
isActiveNode = true;
|
||||
}
|
||||
|
||||
// See if there is an active child to this element
|
||||
let hasActiveChild = false;
|
||||
for (const child of el.children) {
|
||||
hasActiveChild = walk(child, depth) || hasActiveChild;
|
||||
}
|
||||
|
||||
// Process the collapse state if this is an UL
|
||||
if (el.tagName === "UL") {
|
||||
if (tocOpenDepth === -1 && depth > 1) {
|
||||
// toc-expand: false
|
||||
el.classList.add("collapse");
|
||||
} else if (
|
||||
depth <= tocOpenDepth ||
|
||||
hasActiveChild ||
|
||||
prevSiblingIsActiveLink(el)
|
||||
) {
|
||||
el.classList.remove("collapse");
|
||||
} else {
|
||||
el.classList.add("collapse");
|
||||
}
|
||||
|
||||
// untick depth when we leave a UL
|
||||
depth = depth - 1;
|
||||
}
|
||||
return hasActiveChild || isActiveNode;
|
||||
};
|
||||
|
||||
// walk the TOC and expand / collapse any items that should be shown
|
||||
if (tocEl) {
|
||||
updateActiveLink();
|
||||
walk(tocEl, 0);
|
||||
}
|
||||
|
||||
// Throttle the scroll event and walk peridiocally
|
||||
window.document.addEventListener(
|
||||
"scroll",
|
||||
throttle(() => {
|
||||
if (tocEl) {
|
||||
updateActiveLink();
|
||||
walk(tocEl, 0);
|
||||
}
|
||||
if (!isReaderMode()) {
|
||||
hideOverlappedSidebars();
|
||||
}
|
||||
}, 5)
|
||||
);
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
throttle(() => {
|
||||
if (tocEl) {
|
||||
updateActiveLink();
|
||||
walk(tocEl, 0);
|
||||
}
|
||||
if (!isReaderMode()) {
|
||||
hideOverlappedSidebars();
|
||||
}
|
||||
}, 10)
|
||||
);
|
||||
hideOverlappedSidebars();
|
||||
highlightReaderToggle(isReaderMode());
|
||||
});
|
||||
|
||||
tabsets.init();
|
||||
|
||||
function throttle(func, wait) {
|
||||
let waiting = false;
|
||||
return function () {
|
||||
if (!waiting) {
|
||||
func.apply(this, arguments);
|
||||
waiting = true;
|
||||
setTimeout(function () {
|
||||
waiting = false;
|
||||
}, wait);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function nexttick(func) {
|
||||
return setTimeout(func, 0);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// grouped tabsets
|
||||
|
||||
export function init() {
|
||||
window.addEventListener("pageshow", (_event) => {
|
||||
function getTabSettings() {
|
||||
const data = localStorage.getItem("quarto-persistent-tabsets-data");
|
||||
if (!data) {
|
||||
localStorage.setItem("quarto-persistent-tabsets-data", "{}");
|
||||
return {};
|
||||
}
|
||||
if (data) {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
}
|
||||
|
||||
function setTabSettings(data) {
|
||||
localStorage.setItem(
|
||||
"quarto-persistent-tabsets-data",
|
||||
JSON.stringify(data)
|
||||
);
|
||||
}
|
||||
|
||||
function setTabState(groupName, groupValue) {
|
||||
const data = getTabSettings();
|
||||
data[groupName] = groupValue;
|
||||
setTabSettings(data);
|
||||
}
|
||||
|
||||
function toggleTab(tab, active) {
|
||||
const tabPanelId = tab.getAttribute("aria-controls");
|
||||
const tabPanel = document.getElementById(tabPanelId);
|
||||
if (active) {
|
||||
tab.classList.add("active");
|
||||
tabPanel.classList.add("active");
|
||||
} else {
|
||||
tab.classList.remove("active");
|
||||
tabPanel.classList.remove("active");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAll(selectedGroup, selectorsToSync) {
|
||||
for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) {
|
||||
const active = selectedGroup === thisGroup;
|
||||
for (const tab of tabs) {
|
||||
toggleTab(tab, active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findSelectorsToSyncByLanguage() {
|
||||
const result = {};
|
||||
const tabs = Array.from(
|
||||
document.querySelectorAll(`div[data-group] a[id^='tabset-']`)
|
||||
);
|
||||
for (const item of tabs) {
|
||||
const div = item.parentElement.parentElement.parentElement;
|
||||
const group = div.getAttribute("data-group");
|
||||
if (!result[group]) {
|
||||
result[group] = {};
|
||||
}
|
||||
const selectorsToSync = result[group];
|
||||
const value = item.innerHTML;
|
||||
if (!selectorsToSync[value]) {
|
||||
selectorsToSync[value] = [];
|
||||
}
|
||||
selectorsToSync[value].push(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function setupSelectorSync() {
|
||||
const selectorsToSync = findSelectorsToSyncByLanguage();
|
||||
Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => {
|
||||
Object.entries(tabSetsByValue).forEach(([value, items]) => {
|
||||
items.forEach((item) => {
|
||||
item.addEventListener("click", (_event) => {
|
||||
setTabState(group, value);
|
||||
toggleAll(value, selectorsToSync[group]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return selectorsToSync;
|
||||
}
|
||||
|
||||
const selectorsToSync = setupSelectorSync();
|
||||
for (const [group, selectedName] of Object.entries(getTabSettings())) {
|
||||
const selectors = selectorsToSync[group];
|
||||
// it's possible that stale state gives us empty selections, so we explicitly check here.
|
||||
if (selectors) {
|
||||
toggleAll(selectedName, selectors);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,513 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<style>body{background-color:white;}</style>
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="aphfsmpepd" style="padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;">
|
||||
<style>#aphfsmpepd table {
|
||||
font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
#aphfsmpepd thead, #aphfsmpepd tbody, #aphfsmpepd tfoot, #aphfsmpepd tr, #aphfsmpepd td, #aphfsmpepd th {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
#aphfsmpepd p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_table {
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
line-height: normal;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
color: #333333;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
background-color: #FFFFFF;
|
||||
width: auto;
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #A8A8A8;
|
||||
border-right-style: none;
|
||||
border-right-width: 2px;
|
||||
border-right-color: #D3D3D3;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #A8A8A8;
|
||||
border-left-style: none;
|
||||
border-left-width: 2px;
|
||||
border-left-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_caption {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_title {
|
||||
color: #333333;
|
||||
font-size: 125%;
|
||||
font-weight: initial;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-bottom-color: #FFFFFF;
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_subtitle {
|
||||
color: #333333;
|
||||
font-size: 85%;
|
||||
font-weight: initial;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 5px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-top-color: #FFFFFF;
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_heading {
|
||||
background-color: #FFFFFF;
|
||||
text-align: center;
|
||||
border-bottom-color: #FFFFFF;
|
||||
border-left-style: none;
|
||||
border-left-width: 1px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 1px;
|
||||
border-right-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_bottom_border {
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_col_headings {
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #D3D3D3;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
border-left-style: none;
|
||||
border-left-width: 1px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 1px;
|
||||
border-right-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_col_heading {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 100%;
|
||||
font-weight: normal;
|
||||
text-transform: inherit;
|
||||
border-left-style: none;
|
||||
border-left-width: 1px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 1px;
|
||||
border-right-color: #D3D3D3;
|
||||
vertical-align: bottom;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 6px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_column_spanner_outer {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 100%;
|
||||
font-weight: normal;
|
||||
text-transform: inherit;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_column_spanner_outer:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_column_spanner_outer:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_column_spanner {
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
vertical-align: bottom;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
overflow-x: hidden;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_spanner_row {
|
||||
border-bottom-style: hidden;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_group_heading {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 100%;
|
||||
font-weight: initial;
|
||||
text-transform: inherit;
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #D3D3D3;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
border-left-style: none;
|
||||
border-left-width: 1px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 1px;
|
||||
border-right-color: #D3D3D3;
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_empty_group_heading {
|
||||
padding: 0.5px;
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 100%;
|
||||
font-weight: initial;
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #D3D3D3;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_from_md > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_from_md > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_row {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
margin: 10px;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
border-top-color: #D3D3D3;
|
||||
border-left-style: none;
|
||||
border-left-width: 1px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 1px;
|
||||
border-right-color: #D3D3D3;
|
||||
vertical-align: middle;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_stub {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 100%;
|
||||
font-weight: initial;
|
||||
text-transform: inherit;
|
||||
border-right-style: solid;
|
||||
border-right-width: 2px;
|
||||
border-right-color: #D3D3D3;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_stub_row_group {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 100%;
|
||||
font-weight: initial;
|
||||
text-transform: inherit;
|
||||
border-right-style: solid;
|
||||
border-right-width: 2px;
|
||||
border-right-color: #D3D3D3;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_row_group_first td {
|
||||
border-top-width: 2px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_row_group_first th {
|
||||
border-top-width: 2px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_summary_row {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
text-transform: inherit;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_first_summary_row {
|
||||
border-top-style: solid;
|
||||
border-top-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_first_summary_row.thick {
|
||||
border-top-width: 2px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_last_summary_row {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_grand_summary_row {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
text-transform: inherit;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_first_grand_summary_row {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-top-style: double;
|
||||
border-top-width: 6px;
|
||||
border-top-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_last_grand_summary_row_top {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-bottom-style: double;
|
||||
border-bottom-width: 6px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_striped {
|
||||
background-color: rgba(128, 128, 128, 0.05);
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_table_body {
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #D3D3D3;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_footnotes {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
border-bottom-style: none;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
border-left-style: none;
|
||||
border-left-width: 2px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 2px;
|
||||
border-right-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_footnote {
|
||||
margin: 0px;
|
||||
font-size: 90%;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_sourcenotes {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
border-bottom-style: none;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
border-left-style: none;
|
||||
border-left-width: 2px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 2px;
|
||||
border-right-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_sourcenote {
|
||||
font-size: 90%;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_right {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_font_normal {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_font_bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_font_italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_super {
|
||||
font-size: 65%;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_footnote_marks {
|
||||
font-size: 75%;
|
||||
vertical-align: 0.4em;
|
||||
position: initial;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_asterisk {
|
||||
font-size: 100%;
|
||||
vertical-align: 0;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_indent_1 {
|
||||
text-indent: 5px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_indent_2 {
|
||||
text-indent: 10px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_indent_3 {
|
||||
text-indent: 15px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_indent_4 {
|
||||
text-indent: 20px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .gt_indent_5 {
|
||||
text-indent: 25px;
|
||||
}
|
||||
|
||||
#aphfsmpepd .katex-display {
|
||||
display: inline-flex !important;
|
||||
margin-bottom: 0.75em !important;
|
||||
}
|
||||
|
||||
#aphfsmpepd div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after {
|
||||
height: 0px !important;
|
||||
}
|
||||
</style>
|
||||
<table class="gt_table" data-quarto-disable-processing="false" data-quarto-bootstrap="false">
|
||||
<thead>
|
||||
<tr class="gt_col_headings">
|
||||
<th class="gt_col_heading gt_columns_bottom_border gt_left" rowspan="1" colspan="1" scope="col" id="label"><span class='gt_from_md'><strong>Characteristic</strong></span></th>
|
||||
<th class="gt_col_heading gt_columns_bottom_border gt_center" rowspan="1" colspan="1" scope="col" id="stat_0"><span class='gt_from_md'><strong>Overall</strong><br />
|
||||
N = 416</span><span class="gt_footnote_marks" style="white-space:nowrap;font-style:italic;font-weight:normal;line-height:0;"><sup>1</sup></span></th>
|
||||
<th class="gt_col_heading gt_columns_bottom_border gt_center" rowspan="1" colspan="1" scope="col" id="stat_1"><span class='gt_from_md'><strong>No early vasopressor</strong><br />
|
||||
N = 181</span><span class="gt_footnote_marks" style="white-space:nowrap;font-style:italic;font-weight:normal;line-height:0;"><sup>1</sup></span></th>
|
||||
<th class="gt_col_heading gt_columns_bottom_border gt_center" rowspan="1" colspan="1" scope="col" id="stat_2"><span class='gt_from_md'><strong>Early vasopressor</strong><br />
|
||||
N = 235</span><span class="gt_footnote_marks" style="white-space:nowrap;font-style:italic;font-weight:normal;line-height:0;"><sup>1</sup></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="gt_table_body">
|
||||
<tr><td headers="label" class="gt_row gt_left">age</td>
|
||||
<td headers="stat_0" class="gt_row gt_center">66 (14)</td>
|
||||
<td headers="stat_1" class="gt_row gt_center">63 (14)</td>
|
||||
<td headers="stat_2" class="gt_row gt_center">67 (14)</td></tr>
|
||||
<tr><td headers="label" class="gt_row gt_left">sex</td>
|
||||
<td headers="stat_0" class="gt_row gt_center"><br /></td>
|
||||
<td headers="stat_1" class="gt_row gt_center"><br /></td>
|
||||
<td headers="stat_2" class="gt_row gt_center"><br /></td></tr>
|
||||
<tr><td headers="label" class="gt_row gt_left"> female</td>
|
||||
<td headers="stat_0" class="gt_row gt_center">201 (48%)</td>
|
||||
<td headers="stat_1" class="gt_row gt_center">91 (50%)</td>
|
||||
<td headers="stat_2" class="gt_row gt_center">110 (47%)</td></tr>
|
||||
<tr><td headers="label" class="gt_row gt_left"> male</td>
|
||||
<td headers="stat_0" class="gt_row gt_center">215 (52%)</td>
|
||||
<td headers="stat_1" class="gt_row gt_center">90 (50%)</td>
|
||||
<td headers="stat_2" class="gt_row gt_center">125 (53%)</td></tr>
|
||||
<tr><td headers="label" class="gt_row gt_left">sofa_score</td>
|
||||
<td headers="stat_0" class="gt_row gt_center">7.00 (2.60)</td>
|
||||
<td headers="stat_1" class="gt_row gt_center">6.29 (2.50)</td>
|
||||
<td headers="stat_2" class="gt_row gt_center">7.55 (2.55)</td></tr>
|
||||
<tr><td headers="label" class="gt_row gt_left">lactate</td>
|
||||
<td headers="stat_0" class="gt_row gt_center">3.98 (1.86)</td>
|
||||
<td headers="stat_1" class="gt_row gt_center">3.68 (1.60)</td>
|
||||
<td headers="stat_2" class="gt_row gt_center">4.22 (2.01)</td></tr>
|
||||
<tr><td headers="label" class="gt_row gt_left">map</td>
|
||||
<td headers="stat_0" class="gt_row gt_center">55.4 (6.3)</td>
|
||||
<td headers="stat_1" class="gt_row gt_center">56.4 (6.0)</td>
|
||||
<td headers="stat_2" class="gt_row gt_center">54.6 (6.4)</td></tr>
|
||||
<tr><td headers="label" class="gt_row gt_left">death_28d</td>
|
||||
<td headers="stat_0" class="gt_row gt_center">131 (31%)</td>
|
||||
<td headers="stat_1" class="gt_row gt_center">47 (26%)</td>
|
||||
<td headers="stat_2" class="gt_row gt_center">84 (36%)</td></tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="gt_footnotes">
|
||||
<td class="gt_footnote" colspan="4"><span class="gt_footnote_marks" style="white-space:nowrap;font-style:italic;font-weight:normal;line-height:0;"><sup>1</sup></span> <span class='gt_from_md'>Mean (SD); n (%)</span></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,490 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<style>body{background-color:white;}</style>
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="nplmwnpuqt" style="padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;">
|
||||
<style>#nplmwnpuqt table {
|
||||
font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
#nplmwnpuqt thead, #nplmwnpuqt tbody, #nplmwnpuqt tfoot, #nplmwnpuqt tr, #nplmwnpuqt td, #nplmwnpuqt th {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
#nplmwnpuqt p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_table {
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
line-height: normal;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
color: #333333;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
background-color: #FFFFFF;
|
||||
width: auto;
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #A8A8A8;
|
||||
border-right-style: none;
|
||||
border-right-width: 2px;
|
||||
border-right-color: #D3D3D3;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #A8A8A8;
|
||||
border-left-style: none;
|
||||
border-left-width: 2px;
|
||||
border-left-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_caption {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_title {
|
||||
color: #333333;
|
||||
font-size: 125%;
|
||||
font-weight: initial;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-bottom-color: #FFFFFF;
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_subtitle {
|
||||
color: #333333;
|
||||
font-size: 85%;
|
||||
font-weight: initial;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 5px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-top-color: #FFFFFF;
|
||||
border-top-width: 0;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_heading {
|
||||
background-color: #FFFFFF;
|
||||
text-align: center;
|
||||
border-bottom-color: #FFFFFF;
|
||||
border-left-style: none;
|
||||
border-left-width: 1px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 1px;
|
||||
border-right-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_bottom_border {
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_col_headings {
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #D3D3D3;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
border-left-style: none;
|
||||
border-left-width: 1px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 1px;
|
||||
border-right-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_col_heading {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 100%;
|
||||
font-weight: normal;
|
||||
text-transform: inherit;
|
||||
border-left-style: none;
|
||||
border-left-width: 1px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 1px;
|
||||
border-right-color: #D3D3D3;
|
||||
vertical-align: bottom;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 6px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_column_spanner_outer {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 100%;
|
||||
font-weight: normal;
|
||||
text-transform: inherit;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_column_spanner_outer:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_column_spanner_outer:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_column_spanner {
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
vertical-align: bottom;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
overflow-x: hidden;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_spanner_row {
|
||||
border-bottom-style: hidden;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_group_heading {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 100%;
|
||||
font-weight: initial;
|
||||
text-transform: inherit;
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #D3D3D3;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
border-left-style: none;
|
||||
border-left-width: 1px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 1px;
|
||||
border-right-color: #D3D3D3;
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_empty_group_heading {
|
||||
padding: 0.5px;
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 100%;
|
||||
font-weight: initial;
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #D3D3D3;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_from_md > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_from_md > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_row {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
margin: 10px;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
border-top-color: #D3D3D3;
|
||||
border-left-style: none;
|
||||
border-left-width: 1px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 1px;
|
||||
border-right-color: #D3D3D3;
|
||||
vertical-align: middle;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_stub {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 100%;
|
||||
font-weight: initial;
|
||||
text-transform: inherit;
|
||||
border-right-style: solid;
|
||||
border-right-width: 2px;
|
||||
border-right-color: #D3D3D3;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_stub_row_group {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
font-size: 100%;
|
||||
font-weight: initial;
|
||||
text-transform: inherit;
|
||||
border-right-style: solid;
|
||||
border-right-width: 2px;
|
||||
border-right-color: #D3D3D3;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_row_group_first td {
|
||||
border-top-width: 2px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_row_group_first th {
|
||||
border-top-width: 2px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_summary_row {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
text-transform: inherit;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_first_summary_row {
|
||||
border-top-style: solid;
|
||||
border-top-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_first_summary_row.thick {
|
||||
border-top-width: 2px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_last_summary_row {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_grand_summary_row {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
text-transform: inherit;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_first_grand_summary_row {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-top-style: double;
|
||||
border-top-width: 6px;
|
||||
border-top-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_last_grand_summary_row_top {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-bottom-style: double;
|
||||
border-bottom-width: 6px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_striped {
|
||||
background-color: rgba(128, 128, 128, 0.05);
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_table_body {
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
border-top-color: #D3D3D3;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_footnotes {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
border-bottom-style: none;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
border-left-style: none;
|
||||
border-left-width: 2px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 2px;
|
||||
border-right-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_footnote {
|
||||
margin: 0px;
|
||||
font-size: 90%;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_sourcenotes {
|
||||
color: #333333;
|
||||
background-color: #FFFFFF;
|
||||
border-bottom-style: none;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: #D3D3D3;
|
||||
border-left-style: none;
|
||||
border-left-width: 2px;
|
||||
border-left-color: #D3D3D3;
|
||||
border-right-style: none;
|
||||
border-right-width: 2px;
|
||||
border-right-color: #D3D3D3;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_sourcenote {
|
||||
font-size: 90%;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_right {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_font_normal {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_font_bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_font_italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_super {
|
||||
font-size: 65%;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_footnote_marks {
|
||||
font-size: 75%;
|
||||
vertical-align: 0.4em;
|
||||
position: initial;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_asterisk {
|
||||
font-size: 100%;
|
||||
vertical-align: 0;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_indent_1 {
|
||||
text-indent: 5px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_indent_2 {
|
||||
text-indent: 10px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_indent_3 {
|
||||
text-indent: 15px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_indent_4 {
|
||||
text-indent: 20px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .gt_indent_5 {
|
||||
text-indent: 25px;
|
||||
}
|
||||
|
||||
#nplmwnpuqt .katex-display {
|
||||
display: inline-flex !important;
|
||||
margin-bottom: 0.75em !important;
|
||||
}
|
||||
|
||||
#nplmwnpuqt div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after {
|
||||
height: 0px !important;
|
||||
}
|
||||
</style>
|
||||
<table class="gt_table" data-quarto-disable-processing="false" data-quarto-bootstrap="false">
|
||||
<thead>
|
||||
<tr class="gt_heading">
|
||||
<td colspan="2" class="gt_heading gt_title gt_font_normal gt_bottom_border" style>Naive 28-Day Mortality Comparison</td>
|
||||
</tr>
|
||||
|
||||
<tr class="gt_col_headings">
|
||||
<th class="gt_col_heading gt_columns_bottom_border gt_left" rowspan="1" colspan="1" scope="col" id="measure">Measure</th>
|
||||
<th class="gt_col_heading gt_columns_bottom_border gt_right" rowspan="1" colspan="1" scope="col" id="value">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="gt_table_body">
|
||||
<tr><td headers="measure" class="gt_row gt_left">n_eligible</td>
|
||||
<td headers="value" class="gt_row gt_right">416.000</td></tr>
|
||||
<tr><td headers="measure" class="gt_row gt_left">n_early</td>
|
||||
<td headers="value" class="gt_row gt_right">235.000</td></tr>
|
||||
<tr><td headers="measure" class="gt_row gt_left">n_not_early</td>
|
||||
<td headers="value" class="gt_row gt_right">181.000</td></tr>
|
||||
<tr><td headers="measure" class="gt_row gt_left">risk_early</td>
|
||||
<td headers="value" class="gt_row gt_right">0.357</td></tr>
|
||||
<tr><td headers="measure" class="gt_row gt_left">risk_not_early</td>
|
||||
<td headers="value" class="gt_row gt_right">0.260</td></tr>
|
||||
<tr><td headers="measure" class="gt_row gt_left">risk_difference</td>
|
||||
<td headers="value" class="gt_row gt_right">0.098</td></tr>
|
||||
<tr><td headers="measure" class="gt_row gt_left">risk_ratio</td>
|
||||
<td headers="value" class="gt_row gt_right">1.377</td></tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
# Simulate an ICU cohort for learning target trial emulation.
|
||||
# The simulation logic lives in R/simulate_icu_cohort.R.
|
||||
|
||||
suppressPackageStartupMessages(library(readr))
|
||||
suppressPackageStartupMessages(library(skimr))
|
||||
|
||||
source("R/simulate_icu_cohort.R")
|
||||
|
||||
icu_data <- simulate_icu_cohort(n_patients = 1000, seed = 20260531)
|
||||
|
||||
write_csv(
|
||||
icu_data,
|
||||
file = "data/icu_septic_shock_base_r.csv"
|
||||
)
|
||||
|
||||
cli::cli_alert_success("Wrote simulated cohort to {.file data/icu_septic_shock_base_r.csv}.")
|
||||
|
||||
skim(icu_data)
|
||||
@@ -0,0 +1,91 @@
|
||||
suppressPackageStartupMessages(library(dplyr))
|
||||
suppressPackageStartupMessages(library(gt))
|
||||
suppressPackageStartupMessages(library(gtsummary))
|
||||
suppressPackageStartupMessages(library(readr))
|
||||
suppressPackageStartupMessages(library(tidyr))
|
||||
|
||||
cli::cli_h1("Naive analysis of the simulated ICU cohort")
|
||||
|
||||
icu_data <- read_csv("data/icu_septic_shock_base_r.csv", show_col_types = FALSE)
|
||||
|
||||
if (!dir.exists("outputs")) {
|
||||
dir.create("outputs")
|
||||
}
|
||||
|
||||
eligible_data <- icu_data |>
|
||||
filter(eligible == 1)
|
||||
|
||||
analysis_summary <- eligible_data |>
|
||||
summarize(
|
||||
n_eligible = n(),
|
||||
n_early = sum(early_vasopressor == 1),
|
||||
n_not_early = sum(early_vasopressor == 0),
|
||||
risk_early = mean(death_28d[early_vasopressor == 1]),
|
||||
risk_not_early = mean(death_28d[early_vasopressor == 0]),
|
||||
risk_difference = risk_early - risk_not_early,
|
||||
risk_ratio = risk_early / risk_not_early
|
||||
)
|
||||
|
||||
analysis_results <- analysis_summary |>
|
||||
pivot_longer(
|
||||
cols = everything(),
|
||||
names_to = "measure",
|
||||
values_to = "value"
|
||||
)
|
||||
|
||||
analysis_table <- analysis_results |>
|
||||
gt() |>
|
||||
tab_header(title = "Naive 28-Day Mortality Comparison") |>
|
||||
cols_label(
|
||||
measure = "Measure",
|
||||
value = "Value"
|
||||
) |>
|
||||
fmt_number(columns = value, decimals = 3)
|
||||
|
||||
baseline_table <- eligible_data |>
|
||||
mutate(
|
||||
early_vasopressor = factor(
|
||||
early_vasopressor,
|
||||
levels = c(0, 1),
|
||||
labels = c("No early vasopressor", "Early vasopressor")
|
||||
)
|
||||
) |>
|
||||
select(early_vasopressor, age, sex, sofa_score, lactate, map, death_28d) |>
|
||||
tbl_summary(
|
||||
by = early_vasopressor,
|
||||
statistic = list(
|
||||
all_continuous() ~ "{mean} ({sd})",
|
||||
all_categorical() ~ "{n} ({p}%)"
|
||||
),
|
||||
missing = "no"
|
||||
) |>
|
||||
add_overall()
|
||||
|
||||
baseline_console <- eligible_data |>
|
||||
group_by(early_vasopressor) |>
|
||||
summarize(
|
||||
mean_age = mean(age),
|
||||
mean_sofa_score = mean(sofa_score),
|
||||
mean_lactate = mean(lactate),
|
||||
mean_map = mean(map),
|
||||
.groups = "drop"
|
||||
)
|
||||
|
||||
cli::cli_h2("Core causal contrast")
|
||||
print(analysis_results)
|
||||
|
||||
gtsave(
|
||||
data = analysis_table,
|
||||
filename = "outputs/naive_mortality_comparison.html"
|
||||
)
|
||||
|
||||
cli::cli_alert_success("Wrote {.file outputs/naive_mortality_comparison.html}.")
|
||||
|
||||
cli::cli_h2("Baseline comparison by observed treatment")
|
||||
print(baseline_console)
|
||||
|
||||
baseline_table |>
|
||||
as_gt() |>
|
||||
gtsave(filename = "outputs/baseline_by_treatment.html")
|
||||
|
||||
cli::cli_alert_success("Wrote {.file outputs/baseline_by_treatment.html}.")
|
||||
Reference in New Issue
Block a user