                        Changes in version 5.2.1                        

  - The model-diagram test of a long product no longer checks how long
    modelGraph() takes, and is skipped on CRAN. The timing check failed
    at random on loaded CRAN machines (48 s against a 30 s limit).

  - plot() no longer fails on a fit with more than one variance level. A
    model with between-occasion variability reports omega as a list
    holding one matrix per level rather than as a single matrix, and the
    between-subject-variability guard tested nrow(omega) > 0, which is
    NA for a list, so plot() stopped with "missing value where
    TRUE/FALSE needed" before drawing anything. The guard now asks
    whether the fit reports any between-subject eta, which is what the
    BSV plots need and does not depend on the number of variance levels.

  - The between-subject-variability plots now take their etas from the
    subject-level omega block (dropping any fixed at zero) rather than
    from every column ranef() returns, so a mixture fit no longer gets a
    QQ plot of its mixnum subpopulation labels or a spurious correlation
    panel, and an occasion-only fit is correctly treated as having no
    BSV.

  - plot() of a fit, vpcPlot() and plot() of an augPred() object now
    emit one fitResult event on the rxode2 event bus (when rxode2 has
    one) with the plots they made, so loggers such as nlmixr2log can
    store them with the fit. Simulations done inside vpcPlot() emit
    nothing. Without a listener nothing changes.

  - modelDiagram(..., engine = "ggplot2", labels = TRUE) now draws the
    arrow labels on a white background, so the arrow no longer runs
    through the label text.

  - plot() of an augPred() object no longer titles a single-endpoint
    figure with the last endpoint of an earlier multiple-endpoint plot.

  - The figures from plot() of a fit, traceplot() and plot() of an
    augPred() object no longer carry the fit, the full plotting data or
    the other figures in their environments, so saving them (with
    saveRDS() or as a 'targets' target) is far smaller. Before, each
    figure's aes(), facet and layer environments held the frame it was
    built in, and with it the whole fit or every figure built before it
    (the trace plot and the individual plots could each serialize to
    hundreds of MB). Each figure now stores its data once, in $data; the
    plots look the same.

  - The VPC figures from vpcPlot(), vpcPlotTad(), vpcCens() and
    vpcCensTad() no longer carry the VPC simulation, and with method =
    "tidyvpc" no longer carry the fit either. 'vpc' built its figure in
    about a dozen frames that each held the whole simulation, and the
    tidyvpc figure kept the frame of vpcPlot() itself, so saving a VPC
    of a 1500-observation fit with 50 simulations wrote 97 MB ('vpc') or
    up to 34 MB ('tidyvpc'). The 'vpc' figure now takes about 3 MB, and
    neither grows with the number of simulations. The figures look the
    same.

  - The engine = "ggplot2" diagram from modelDiagram() no longer keeps
    its layer data and a second copy of itself in its environments, and
    adds its scales last; together about 1 MB (up to 3.4 MB for large
    models) less for each saved diagram. The diagrams look the same.

  - The bootplot figure that plot() includes for a fit with bootstrap
    results no longer carries the frame of plot(), with the plotting
    data and every other figure: for theo_sd with 3 bootstrap samples it
    went from 152 MB to 6 MB saved. It still carries the fit, which
    nlmixr2extra::bootplot() keeps in its own frame.

                 Changes in version 5.2.0 (2026-09-22)                  

  - Added automatic model diagrams (#10). modelGraph() parses a model's
    differential equations (from a model function, rxode2 model/UI or an
    nlmixr2 fit) into a graph of compartments and flows (mass transfer,
    eliminations, inputs and non-mass-transfer interactions like effect
    compartments or PD stimulation/inhibition), detecting dosing
    compartments from the dosing records. modelDiagram() lays it out
    with dosing and absorption compartments above the central
    compartment, peripheral compartments to the left,
    eliminations/metabolites below and PD models to the right, and draws
    it with the "DiagrammeR" (Graphviz), "ggplot2" or "dot" (DOT source)
    engine. Dosing properties (lag(), f(), rate(), dur()) are shown as
    annotations on their compartment, and delay() terms are understood.
    The diagrams were checked against all 3043 models of 'nlmixr2lib',
    including large QSP/PBPK models. plot() of an rxode2 user interface
    (rxUi) object or compiled rxode2 model draws its diagram, and the
    new "Automatic model diagrams" vignette describes the feature.

  - Fixed vpcPlot() with method = "tidyvpc" when the observed data has
    observation records with a missing DV. Those records were dropped
    from the observed data but kept in the simulation, so a stratified
    (e.g. multiple-endpoint) VPC errored and a single-endpoint VPC
    paired simulated values with the wrong observations. The matching
    simulated records are now dropped too (#74).

  - plot() of a multiple-endpoint fit no longer creates empty "Endpoint:
    " groups for state compartments without observations (like depot or
    central); only observed endpoints are plotted. plot() of an
    augPred() object likewise skips endpoints without data (#44).

  - vpcPlot()/vpcPlotTad() of a multiple-endpoint fit whose data codes
    the endpoints with cmt no longer shows an extra NA panel (or a
    single NA panel with pred_corr = TRUE); the simulated compartment
    labels are now matched to the observed ones by name (#44).

  - The censored VPC (vpcCens(), or vpcPlot() with cens = TRUE and the
    vpc backend) of a multiple-endpoint fit now stratifies by its
    endpoints only, matching the simulated endpoints to the observed
    ones; an endpoint whose observations are all missing no longer
    becomes an NA panel (#44).

  - plot() of a censored multiple-endpoint fit no longer errors in
    geom_cens() ("argument must be coercible to non-negative integer")
    for an endpoint without censored observations (#44).

  - The censored VPC (vpcCens(), vpcCensTad(), or vpcPlot() with cens =
    TRUE and the vpc backend) now honours the data argument; it
    previously always used the fitted data, whatever data was supplied
    (#55). The observed data now comes from the original (or supplied)
    dataset instead of the fit, so censored records are identified from
    the CENS column rather than from the fit's imputed DV (which, for
    censMethod = "cdf", could land above the limit and be counted as
    uncensored). Records without an observation are dropped, as in the
    fit.

  - The VPC observed data now drops records with MDV = 1 even when an
    EVID column is also present (previously only EVID was checked when
    both existed).

  - plot() of an augPred() object now accepts a base-R style log
    argument (log = "y", "x" or "xy") to draw the individual plots on
    log-scaled axes; non-positive values, which cannot be shown on a log
    axis, are dropped (#32).

  - vpcPlot(), vpcPlotTad(), vpcCens() and vpcCensTad() now use a
    supplied nlmixr2est::vpcSim() simulation instead of discarding it
    and re-simulating with the default n = 300. Supplying n alongside a
    simulation warns that it is ignored, and pred_corr = TRUE errors
    unless the simulation was created with pred = TRUE (#57). The
    observed-data prediction correction for a supplied simulation is
    computed from that simulation's own fit rather than from whichever
    vpcSim(pred = TRUE) ran last.

  - Fixed the VPC simulation ignoring the data argument of vpcPlot(),
    vpcPlotTad(), vpcCens() and vpcCensTad(); the simulation always used
    the fit's dataset, so it did not match the observed data built from
    data (and tidyvpc warned that xsim did not match the observed
    x-values). The simulation now uses data when supplied. A supplied
    nlmixr2est::vpcSim() simulation was made from its own dataset, so
    passing data alongside one now warns that it only replaces the
    observed data (#68). Because the simulation now follows data, an
    endpoint without observations in data is no longer simulated either,
    so it drops out of a multiple-endpoint VPC instead of being shown as
    a simulated-only panel. For a model with a non-normal endpoint, data
    now needs a numeric CMT column (the model's compartment numbers),
    since the compartments would otherwise be taken from the fitted data
    by row number, or read from a factor's level order, and would not
    match data.

  - Fixed the prediction-corrected VPC (pred_corr = TRUE) ignoring the
    data argument; the observed data was rebuilt from the fit's dataset
    instead of the supplied data (#62).

  - Fixed stratified censored VPCs (vpcCens()/vpcCensTad(), or vpcPlot()
    with cens = TRUE) failing with "The following specified
    stratification columns were NOT found in observation data"; the
    observed data now comes from the original (or supplied) dataset,
    which keeps the stratification columns that the fit table drops
    (#56, #55).

  - Fixed the confidence-band width (and its legend label) for
    vpcPlot()/ vpcPlotTad() with the tidyvpc backend; ci = c(lower,
    upper) was passed to tidyvpc::vpcstats() as conf.level = ci[2]
    instead of the actual interval width ci[2] - ci[1], so the default
    ci = c(0.05, 0.95) (a 90% CI) was drawn and labeled as a 95% CI.

  - Fixed vpcPlotTad()/vpcCensTad() (and any idv taken from the fit)
    with a user-supplied data that is a subset or reordering of the
    fitted data; tad was merged from the fit by row number, so it
    silently became NA (or was paired with the wrong row). Each supplied
    row is now matched by content to the fitted row it came from, and a
    warning is given for observations that are not in the fitted data
    (#60).

                 Changes in version 5.1.0 (2026-08-05)                  

  - Fixed an "object of type 'closure' is not subsettable" error in
    vpcCensTad() (and vpcCens()/vpcPlot()/vpcPlotTad() with cens = TRUE
    and a non-time idv); the censored VPC now passes the sim/obs column
    mappings to vpc::vpc_cens() explicitly instead of relying on column
    guessing (nlmixr2#390).
  - Fixed the censored VPC confidence band, which was computed as if
    every simulated row were its own replicate. The simulated dv is now
    mapped to the sim column instead of being copied into a new dv
    column, so vpc groups the simulated data by replicate as it does for
    the uncensored VPC.
  - Fixed a quantile() "missing values and NaNs not allowed" error in
    prediction-corrected vpcPlot()/vpcPlotTad() on censored (LLOQ/ULOQ)
    fits with the vpc backend; censored records are now dropped before
    the pred-corrected VPC (which is shown for non-censored data only),
    matching the vpc package's stated behavior (nlmixr2#390).
  - Fixed prediction-corrected vpcPlot()/vpcPlotTad() with method =
    "tidyvpc", cens = TRUE, which previously errored that the observed
    data had no cens column; the censoring column is now retained when
    the observed dataset is rebuilt for pred-correction.
  - plot() on a fit with between-subject variability (BSV) now adds a
    nested "bsv" section (inside each data/compartment group) with QQ
    plots for each BSV parameter, BSV-BSV correlation plots (when more
    than one BSV parameter is present) and, via the new covariate
    argument, BSV-by-covariate plots (box-and-whisker for categorical or
    low-cardinality covariates, scatter plus a linear trend with
    confidence interval for continuous ones). Covariate values are taken
    from each subject's first record, so they are assumed time-invariant
    (#51).
  - Plot collections returned by plot() (for fits and augPred objects)
    are now gglist objects from the ggtibble package instead of the
    internal nlmixr2PlotList class. They still print/plot all figures at
    once and support broadcasting ggplot2 additions (e.g. plot(fit) +
    ggplot2::theme_bw()), and the top-level object remains a named,
    nested collection (plot(fit)[["Endpoint: ..."]]). The collection can
    also be converted to a ggtibble for reporting with
    ggtibble::as_ggtibble().
  - Individual/augPred plots are now paginated with
    ggforce::facet_wrap_paginate(), removing the
    hand-written 16-IDs-per-page chunking. Requires ggtibble
    (>= 1.0.3.9000) and ggforce; the minimum R version is now 4.3.
  - plot() on a fit (and on an augPred object) is now about 3x faster.
    The paginated individual plots were expanded one-per-page with
    ggtibble::as_gglist(), which deep-copies the plot through
    serialize()/unserialize(); because those plots are built inside
    functions that hold the whole fit, each copy walked the entire fit
    object. The pages are now produced by re-adding the paginated facet
    for each page instead.
  - Removed obsolete rxode2::.linCmtSensB() test guards. That internal
    was removed from rxode2 in 2025, so the try()/skip_if_not() checks
    always fell through and never skipped. linCmt() gradients are now
    always available, so the guards are unnecessary and the tests run
    unconditionally.

                 Changes in version 5.0.2 (2026-06-11)                  

  - Fixed an error when plotting models without compartments (#33)

                 Changes in version 5.0.1 (2026-02-28)                  

  - Updated nlmixr2 to optionally use the tidyvpc package

                 Changes in version 5.0.0 (2025-11-29)                  

  - Updated to use nlmixr2 5.0 file format

                 Changes in version 3.0.3 (2025-08-23)                  

  - Updated tests and added Ops method (removing + method) to work with
    ggplot2 version 4 (#39)

                 Changes in version 3.0.1 (2025-02-14)                  

  - Added the ability to add ggplot items to a nlmixr2 plot list with +

                 Changes in version 3.0.0 (2024-09-18)                  

  - Update to support new version of rxode2

                 Changes in version 2.0.9 (2024-05-29)                  

  - Bug fix for vpcPlot() where input data frame for models did not
    stratify all the time when the columns were upper case.

                 Changes in version 2.0.8 (2024-01-31)                  

  - plot() now returns a named list of lists so that users can more
    easily choose which plots to include, if all plots are not desired.
    Or the user could use those names as the basis of figure captions
    (fix #8).

  - Models without eta values (between subject variability) now have
    more consistent plotting to models with eta values (fix #18).

  - The package was updated to align with changes in ggplot2.

  - The package was updated to skip tests with "focei" and linCmt()

                 Changes in version 2.0.7 (2022-10-20)                  

  - Added fixes for upcoming 'ggplot2' 3.4 release

  - Added changes for the generalized log-likelihood supported by
    'nlmixr2est'

  - Added a NEWS.md file to track changes to the package.
