From e4616538dacccd56ea4e026350e416bd928bedf4 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 28 Aug 2026 09:23:07 +0200 Subject: [PATCH 1/5] Improved how `node_color=` colours numeric node variables --- DESCRIPTION | 2 +- NEWS.md | 6 +++ R/graph_aes.R | 21 +++++++++- R/graph_legends.R | 74 +++++++++++++++++++++++------------- R/graph_nodes.R | 28 +++++++++----- R/graphr.R | 4 ++ R/grapht.R | 15 ++++++++ man/plot_graphr.Rd | 4 ++ man/plot_grapht.Rd | 4 ++ tests/testthat/test-graphr.R | 49 ++++++++++++++++++++++++ tests/testthat/test-grapht.R | 9 +++++ 11 files changed, 178 insertions(+), 38 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 0e18f8e6..1ee66dd6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: autograph Title: Automatic Plotting and Theming of Many Graphs -Version: 1.2.1 +Version: 1.2.2 Description: Visual exploration and presentation of networks should not be difficult. This package includes functions for plotting networks and network-related metrics with sensible and pretty defaults. It includes 'ggplot2'-based plot methods for many popular network package classes. diff --git a/NEWS.md b/NEWS.md index fb357149..cf633fe7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# autograph 1.2.2 + +## Graphing + +- Improved how `node_color=` colours numeric node variables + # autograph 1.2.1 ## Layouts diff --git a/R/graph_aes.R b/R/graph_aes.R index f3afdeb1..0ff0e347 100644 --- a/R/graph_aes.R +++ b/R/graph_aes.R @@ -168,8 +168,21 @@ if (!node_color %in% manynet::net_node_attributes(g)) return(NULL) vals <- manynet::node_attribute(g, node_color) if ("node_mark" %in% class(vals)) - factor(as.character(vals), levels = c("FALSE", "TRUE")) else - as.factor(as.character(vals)) + return(factor(as.character(vals), levels = c("FALSE", "TRUE"))) + if (.is_continuous(vals)) return(as.numeric(vals)) + as.factor(as.character(vals)) +} + +# A measure such as coreness or centrality has an order and a distance between +# its values that a set of categories does not, so it is drawn as a gradient +# rather than given one colour for each value it takes. Marks are logical and +# memberships are characters, so neither of them reaches this. +# Two values are a contrast rather than a gradient, and are left to the +# categorical palette, which draws such a pair in the theme's base and +# highlight colours anyway. +.is_continuous <- function(vals) { + if (!is.numeric(vals) || is.factor(vals)) return(FALSE) + length(unique(stats::na.omit(as.numeric(vals)))) > 2 } # `levels` holds the categories that `graphs()` found across all of its panels. @@ -179,6 +192,9 @@ .infer_ncolor <- function(g, node_color, levels = NULL) { vals <- .ncolor_values(g, node_color) if (is.null(vals)) return(if (!is.null(node_color)) node_color else ag_ink()) + # A gradient has no categories to hold level for level across the panels of a + # `graphs()` plot; its panels are held together by a shared range instead. + if (is.numeric(vals)) return(vals) if (!is.null(levels)) return(factor(as.character(vals), levels = levels)) if (length(unique(vals)) == 1) { .inform_constant_color("node_color", node_color, "node") @@ -352,6 +368,7 @@ .shared_range(gather(function(g) .infer_nsize(g, node_size, layout))), ecolor = .shared_levels(gather(function(g) .ecolor_values(g, edge_color))), ncolor = .shared_levels(gather(function(g) .ncolor_values(g, node_color))), + ncolor_range = .shared_range(gather(function(g) .ncolor_values(g, node_color))), nshape = .shared_levels(gather(function(g) .nshape_values(g, node_shape))), diffusion = .shared_levels(gather(.diffusion_states)), nadopt = .shared_range(gather(.finite_adoption_time))) diff --git a/R/graph_legends.R b/R/graph_legends.R index 8a7f1d06..b50f96ca 100644 --- a/R/graph_legends.R +++ b/R/graph_legends.R @@ -3,32 +3,54 @@ graph_legends <- function(p, g, node_color = NULL, node_shape = NULL, node_size = NULL, edge_color = NULL, edge_size = NULL) { .check_legend_size(g, node_color, node_shape, edge_color) - p + - ggplot2::guides(fill = ggplot2::guide_legend(order = 1, - title = ifelse(is.null(node_color), - "Color", node_color), - override.aes = list(shape = 21)), - color = ggplot2::guide_legend(order = 2), - shape = ggplot2::guide_legend(order = 3, - title = ifelse(is.null(node_shape), - ifelse(manynet::is_twomode(g), "Mode", "Shape"), - node_shape)), - size = ggplot2::guide_legend(order = 4, - title = ifelse(is.null(node_size), - "Size", node_size)), - linetype = ggplot2::guide_legend(order = 5), - # `.infer_ecolor_title()` decides this alongside the colours - # themselves in R/graph_aes.R, so that the two cannot - # disagree about what the colour is showing, as they did - # when this said "Sign" over colours that showed layers. - edge_colour = ggplot2::guide_legend( - order = 6, title = .infer_ecolor_title(g, edge_color)), - edge_size = ggplot2::guide_legend(order = 7, - title = ifelse(is.null(edge_size), - ifelse(manynet::is_weighted(g), "Weight", "Size"), - edge_size)), - alpha = ggplot2::guide_legend(order = 99, - override.aes = list( alpha = 0, size = 0, shape = NA ))) + # A guide set here wins over the one the scale asked for, so a continuous + # fill -- a measure drawn as a gradient, or a time of adoption -- keeps the + # colourbar its scale gave it rather than being broken into keys. Where the + # user named the attribute, the bar is titled with it; otherwise the title + # the scale set is left alone. + fill_guide <- if (.fill_is_continuous(p)) { + if (is.null(node_color)) NULL else + ggplot2::guide_colourbar(order = 1, title = node_color) + } else { + ggplot2::guide_legend(order = 1, + title = ifelse(is.null(node_color), + "Color", node_color), + override.aes = list(shape = 21)) + } + guides <- list(fill = fill_guide, + color = ggplot2::guide_legend(order = 2), + shape = ggplot2::guide_legend(order = 3, + title = ifelse(is.null(node_shape), + ifelse(manynet::is_twomode(g), "Mode", "Shape"), + node_shape)), + size = ggplot2::guide_legend(order = 4, + title = ifelse(is.null(node_size), + "Size", node_size)), + linetype = ggplot2::guide_legend(order = 5), + # `.infer_ecolor_title()` decides this alongside the colours + # themselves in R/graph_aes.R, so that the two cannot + # disagree about what the colour is showing, as they did + # when this said "Sign" over colours that showed layers. + edge_colour = ggplot2::guide_legend( + order = 6, title = .infer_ecolor_title(g, edge_color)), + edge_size = ggplot2::guide_legend(order = 7, + title = ifelse(is.null(edge_size), + ifelse(manynet::is_weighted(g), "Weight", "Size"), + edge_size)), + alpha = ggplot2::guide_legend(order = 99, + override.aes = list( alpha = 0, size = 0, shape = NA ))) + p + do.call(ggplot2::guides, guides[!vapply(guides, is.null, logical(1))]) +} + +# Whether the plot's fill scale maps a continuous variable. Read from the +# scale the plot already carries rather than from the network, so that every +# gradient -- node colour, time of adoption -- is treated the same way. +.fill_is_continuous <- function(p) { + scales <- p[["scales"]][["scales"]] + if (!length(scales)) return(FALSE) + any(vapply(scales, function(s) + "fill" %in% s[["aesthetics"]] && inherits(s, "ScaleContinuous"), + logical(1))) } # A legend is read by matching a key against a mark, and a reader cannot hold diff --git a/R/graph_nodes.R b/R/graph_nodes.R index 473c115d..42ff30e9 100644 --- a/R/graph_nodes.R +++ b/R/graph_nodes.R @@ -23,15 +23,25 @@ graph_nodes <- function(p, g, node_color, node_shape, node_size, # Named values, shared limits and `drop = FALSE` for the same reason as the # edge colours in R/graph_edges.R: a category keeps its colour and its key # in every panel of a `graphs()` plot. - nlevels <- shared[["ncolor"]] - if (is.null(nlevels)) nlevels <- unique(as.character(out[["ncolor"]])) - if (length(nlevels) > 1){ - nvalues <- if (length(nlevels) == 2) - getOption("snet_highlight", default = c("grey","black")) else - ag_qualitative(length(nlevels)) - p <- p + ggplot2::scale_fill_manual( - values = stats::setNames(nvalues, nlevels), limits = nlevels, - drop = FALSE, guide = ggplot2::guide_legend(node_color)) + if (is.numeric(out[["ncolor"]])) { + # A measure is drawn as a gradient from the theme's base colour to its + # highlight, so that the order of its values can be read off the plot. + # The limits are shared across the panels of a `graphs()` plot, so that + # one value keeps one colour throughout. + p <- p + ggplot2::scale_fill_gradientn( + colours = ag_sequential(9), limits = shared[["ncolor_range"]], + guide = ggplot2::guide_colourbar(title = node_color)) + } else { + nlevels <- shared[["ncolor"]] + if (is.null(nlevels)) nlevels <- unique(as.character(out[["ncolor"]])) + if (length(nlevels) > 1){ + nvalues <- if (length(nlevels) == 2) + getOption("snet_highlight", default = c("grey","black")) else + ag_qualitative(length(nlevels)) + p <- p + ggplot2::scale_fill_manual( + values = stats::setNames(nvalues, nlevels), limits = nlevels, + drop = FALSE, guide = ggplot2::guide_legend(node_color)) + } } } # Consider rescaling nodes diff --git a/R/graphr.R b/R/graphr.R index d9e2466b..e4354156 100644 --- a/R/graphr.R +++ b/R/graphr.R @@ -111,6 +111,10 @@ #' @param node_color,node_colour Node variable to be used for coloring the nodes. #' It is easiest if this is added as a node attribute to #' the graph before plotting. +#' A categorical variable gives one colour to each category. +#' A measure, such as a centrality or coreness score, is drawn instead as a +#' gradient from the theme's base colour to its highlight colour, +#' with a colourbar in place of the legend. #' Nodes can also be colored by declaring a color instead. #' @param node_group Node variable to be used for grouping the nodes. #' It is easiest if this is added as a hull over diff --git a/R/grapht.R b/R/grapht.R index 8e294b29..e0845fea 100644 --- a/R/grapht.R +++ b/R/grapht.R @@ -535,6 +535,17 @@ print.grapht <- function(x, ...) { } if (!is.null(node_color) && node_color %in% manynet::net_node_attributes(waves[[1]])) { + # A measure is drawn as a gradient, as it is in `graphr()`; see + # `.is_continuous()` in R/graph_aes.R. Read over all of the waves at once, + # so that one value keeps one colour from frame to frame. + if (.is_continuous(unlist(lapply(waves, function(w) + manynet::node_attribute(w, node_color))))) { + vals <- vapply(waves, function(w) + as.numeric(manynet::node_attribute(w, node_color)), + numeric(igraph::vcount(waves[[1]]))) + return(list(mapped = TRUE, diffusion = FALSE, continuous = TRUE, + values = vals)) + } vals <- vapply(waves, function(w) as.character(manynet::node_attribute(w, node_color)), character(igraph::vcount(waves[[1]]))) @@ -784,6 +795,10 @@ print.grapht <- function(x, ...) { name = NULL, values = c("Infected" = cols[1], "Susceptible" = cols[2], "Exposed" = cols[3], "Recovered" = cols[4])) + } else if (ncolor_mapped && is.numeric(nodes_out$ncolor)) { + p <- p + ggplot2::scale_fill_gradientn(colours = ag_sequential(9), + guide = ggplot2::guide_colourbar( + title = node_color)) } else if (ncolor_mapped) { nlevels <- length(unique(nodes_out$ncolor)) if (nlevels == 2) { diff --git a/man/plot_graphr.Rd b/man/plot_graphr.Rd index ddf59f36..0622c9c5 100644 --- a/man/plot_graphr.Rd +++ b/man/plot_graphr.Rd @@ -108,6 +108,10 @@ so that every level is labelled and not just the densest.} \item{node_color, node_colour}{Node variable to be used for coloring the nodes. It is easiest if this is added as a node attribute to the graph before plotting. +A categorical variable gives one colour to each category. +A measure, such as a centrality or coreness score, is drawn instead as a +gradient from the theme's base colour to its highlight colour, +with a colourbar in place of the legend. Nodes can also be colored by declaring a color instead.} \item{node_shape}{Node variable to be used for shaping the nodes. diff --git a/man/plot_grapht.Rd b/man/plot_grapht.Rd index 3772a617..db16ec4c 100644 --- a/man/plot_grapht.Rd +++ b/man/plot_grapht.Rd @@ -122,6 +122,10 @@ so that every level is labelled and not just the densest.} \item{node_color, node_colour}{Node variable to be used for coloring the nodes. It is easiest if this is added as a node attribute to the graph before plotting. +A categorical variable gives one colour to each category. +A measure, such as a centrality or coreness score, is drawn instead as a +gradient from the theme's base colour to its highlight colour, +with a colourbar in place of the legend. Nodes can also be colored by declaring a color instead.} \item{node_shape}{Node variable to be used for shaping the nodes. diff --git a/tests/testthat/test-graphr.R b/tests/testthat/test-graphr.R index 843a6af5..dd6dc451 100644 --- a/tests/testthat/test-graphr.R +++ b/tests/testthat/test-graphr.R @@ -200,6 +200,55 @@ test_that("node_color with multiple values uses fill scale", { expect_true(any(grepl("fill", scale_names))) }) +# A measure has an order that categories do not ---- + +.fill_scale <- function(p) { + scales <- Filter(function(s) "fill" %in% s[["aesthetics"]], + p[["scales"]][["scales"]]) + if (length(scales)) scales[[1]] else NULL +} + +test_that("a numeric node_color is drawn as a gradient, not as categories", { + skip_on_cran() + net <- manynet::mutate_nodes(ison_adolescents, + core = c(0, 0.1, 0.4, 0.6, 0.7, 1, 0.3, 0.2)) + p <- graphr(net, node_color = "core") + expect_s3_class(.fill_scale(p), "ScaleContinuous") + expect_buildable(p) + # The gradient runs from the theme's base colour to its highlight + expect_equal(.fill_scale(p)[["palette"]](c(0, 1)), + c(ag_sequential(9)[1], ag_sequential(9)[9])) +}) + +test_that("a gradient node_color is given a colourbar titled by the attribute", { + skip_on_cran() + net <- manynet::mutate_nodes(ison_adolescents, + core = c(0, 0.1, 0.4, 0.6, 0.7, 1, 0.3, 0.2)) + guides <- ggplot2::ggplot_build(graphr(net, node_color = "core"))[["plot"]][["guides"]][["guides"]] + bars <- Filter(function(gd) inherits(gd, "GuideColourbar"), guides) + expect_length(bars, 1) + expect_equal(bars[[1]][["params"]][["title"]], "core") +}) + +test_that("a numeric node_color of two values stays categorical", { + skip_on_cran() + net <- manynet::mutate_nodes(ison_adolescents, + grp = c(1, 2, 1, 2, 1, 2, 1, 2)) + expect_s3_class(.fill_scale(graphr(net, node_color = "grp")), "ScaleDiscrete") +}) + +test_that("graphs() shares one gradient range across its panels", { + skip_on_cran() + nets <- list(a = manynet::mutate_nodes(ison_adolescents, + core = seq(0, 0.7, length.out = 8)), + b = manynet::mutate_nodes(ison_adolescents, + core = seq(0.3, 1, length.out = 8))) + shared <- autograph:::.shared_aes(nets, node_color = "core") + expect_equal(shared[["ncolor_range"]], c(0, 1)) + expect_null(shared[["ncolor"]]) + expect_buildable(graphs(nets, node_color = "core")) +}) + test_that("two-mode networks get correct node shapes", { skip_on_cran() p <- graphr(ison_southern_women) diff --git a/tests/testthat/test-grapht.R b/tests/testthat/test-grapht.R index 40fbbf7a..74c0433b 100644 --- a/tests/testthat/test-grapht.R +++ b/tests/testthat/test-grapht.R @@ -339,6 +339,15 @@ test_that(".shorten_segments never produces negative-length segments", { expect_true(all(newlen <= oldlen)) }) +test_that("grapht() draws a numeric node_color as a gradient", { + net <- manynet::mutate_nodes(.wave_fixture()[[1]], + core = c(0, 0.1, 0.4, 0.6, 0.7, 1, 0.3, 0.2)) + p <- grapht(list(t1 = net, t2 = net), node_color = "core") + scales <- Filter(function(s) "fill" %in% s[["aesthetics"]], + p[["scales"]][["scales"]]) + expect_s3_class(scales[[1]], "ScaleContinuous") +}) + # Issue #40: the tutorial pipeline with a custom time attribute ---- test_that("grapht() works on to_waves() output split by a custom attribute (#40)", { From b6c21c72cdde239eec6da9f0d95b5c5e7f289469 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 28 Aug 2026 09:31:21 +0200 Subject: [PATCH 2/5] Fixed the arc strength test depending on the BLAS --- NEWS.md | 4 ++++ tests/testthat/test-graphr.R | 37 ++++++++++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index cf633fe7..ef2f84f6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,10 @@ - Improved how `node_color=` colours numeric node variables +## Tests + +- Fixed the arc strength test depending on the BLAS + # autograph 1.2.1 ## Layouts diff --git a/tests/testthat/test-graphr.R b/tests/testthat/test-graphr.R index dd6dc451..f879ccfc 100644 --- a/tests/testthat/test-graphr.R +++ b/tests/testthat/test-graphr.R @@ -701,13 +701,18 @@ test_that("parallel ties are fanned apart", { test_that("an arc is drawn for every tie the arc stat keeps", { # `geom_edge_arc()` drops every tie whose two ends sit at one point, and its # `strength` is a parameter rather than an aesthetic, so it has to leave the - # same ties out. The "scaling" layout draws two nodes that hold the same - # distances to every other node at one point, which used to leave `strength` - # two ties longer than the ties it was measured against. + # same ties out. Otherwise a full-length vector recycles against the shorter + # edge set and warns. + # + # The "scaling" layout draws two nodes that hold the same distances to every + # other node at one point, which used to leave `strength` two ties longer + # than the ties it was measured against. How many nodes that layout places at + # exactly one point depends on the BLAS `cmdscale()` decomposes the distances + # with, so it is not counted here. See the test below, which builds the + # coincidence rather than laying it out. net <- manynet::ison_networkers p <- suppressMessages(graphr(net, layout = "scaling", labels = FALSE)) drawn <- sum(!autograph:::.tie_is_coincident(net, p)) - expect_lt(drawn, manynet::net_ties(net)) expect_length(autograph:::.infer_arc_strength(net, p), drawn) expect_no_warning(ggplot2::ggplot_build(p)) # A network drawn with every node in its own place keeps every tie. @@ -722,3 +727,27 @@ test_that("an arc is drawn for every tie the arc stat keeps", { expect_true(utils::tail(autograph:::.tie_is_coincident(loops, lp), 1)) expect_no_warning(ggplot2::ggplot_build(lp)) }) + +test_that("a tie between two nodes at one point is left out of the arc strength", { + # The self-loop above is the one coincidence a layout cannot avoid. This + # covers the other one, two separate nodes drawn at one point, without + # relying on a layout to place them there: `graphr()` takes the name of a + # layout rather than a set of coordinates, and which nodes a scaled layout + # collapses depends on the BLAS. The coordinates are moved after the plot is + # drawn, and the two functions that read them are called on that plot. + net <- manynet::as_igraph(manynet::ison_networkers) + p <- suppressMessages(graphr(net, layout = "stress", labels = FALSE)) + expect_equal(sum(autograph:::.tie_is_coincident(net, p)), 0) + # The two ends of the first tie are put at one point. + pair <- igraph::as_edgelist(net, names = FALSE)[1, ] + p[["data"]][pair[2], c("x", "y")] <- p[["data"]][pair[1], c("x", "y")] + # Every tie that joins the pair, in either direction, is now coincident. + el <- igraph::as_edgelist(net, names = FALSE) + expected <- sum(el[, 1] %in% pair & el[, 2] %in% pair) + expect_gt(expected, 0) + coincident <- autograph:::.tie_is_coincident(net, p) + expect_equal(sum(coincident), expected) + # `strength` is as long as the ties the arc stat keeps, and no longer. + expect_length(autograph:::.infer_arc_strength(net, p), + manynet::net_ties(net) - expected) +}) From e22df2171b8226f8d20b1c13b3b1e21feceae889 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 28 Aug 2026 15:06:33 +0200 Subject: [PATCH 3/5] Updated cran-comments --- cran-comments.md | 55 +++++------------------------------------------- 1 file changed, 5 insertions(+), 50 deletions(-) diff --git a/cran-comments.md b/cran-comments.md index d63d9d17..ecc36c01 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,57 +1,12 @@ -## Resubmission - -This is a resubmission of 1.2.0, which failed the incoming checks with a test error -on Windows and on Debian. Both are fixed here. - -* `layout_valence()` started its nodes at random points, and a start that placed two - nodes close together gave a force large enough to spoil the layout. The nodes now - start on a circle, the force is bounded, and the test sets a seed. -* On Windows, the tutorial test failed on a deprecation warning that the tutorial code - does not raise itself: `netrics::tie_by_closeness()` (0.4.1) calls - `manynet::to_ties()`, which manynet deprecates in 2.3.0. netrics 1.0.0, to be - submitted, calls the current function. The test now fails only if the tutorial calls - a deprecated function itself, so it passes with either netrics version. - -The full test suite passes with each of these three pairs: manynet 2.2.3 with -netrics 0.4.0, manynet 2.3.1 with netrics 0.4.1 (the pair that failed on Windows), -and manynet 2.3.0 with netrics 1.0.0. manynet 2.3.1 and netrics 1.0.0 are to be -submitted; this version does not require either of them. - ## Test environments -* local R installation, aarch64-apple-darwin20, R 4.5.1 -* macOS 14.7.6 (on Github), R 4.5.1 -* Microsoft Windows Server 2022 10.0.20348 (on Github), R 4.5.1 -* Ubuntu 24.04.2 (on Github), R 4.5.1 +* local R installation, aarch64-apple-darwin23, R 4.6.1 +* macOS 26.5.2 (on Github), R 4.6.1 +* Microsoft Windows Server 2022 10.0.26100 (on Github), R 4.6.1 +* Ubuntu 24.04.4 (on Github), R 4.6.1 ## R CMD check results 0 errors | 0 warnings | 0 notes -## User filespace - -This version adds an optional `persist` argument to `stocnet_theme()`. When, and only when, a user -passes `persist = TRUE`, the chosen theme is written to `tools::R_user_dir("autograph", "config")`. -Nothing is written on load, on attach, or by any default code path, and the package is fully -functional if the directory is absent or unwritable. No other location is written to. - -## Backward/forward compatibility - -This version works and tests alongside both manynet 2.2.3 and 2.3.0. -manynet 2.3.0 ships several networks in a list-based class, -and spells a layer as the tie attribute "layer" rather than "type" and -a sign as a negative weight rather than as a "sign". -Which tie attribute records the layer is now read from the network, -so a multiplex network is still coloured by layer under either spelling. -Signs are read through `manynet::tie_signs()`, in `graphr()` and in `layout_valence()`, -rather than from a "sign" tie attribute. -Attribute names are read through `manynet::net_node_attributes()`/`net_tie_attributes()`, -which accept a network in either class. -`layout_concentric()`, `layout_multilevel()` and `layout_lineage()` coerce what they are given, -as the other layouts already did. -`grapht()` and `graphs()` fall back to `manynet::to_times()` where `to_waves()` -cannot split a network, which covers a panel recording its waves as "time" -(e.g. `ison_monks`) and a diffusion result. -Each guard tests for the function or attribute rather than for the manynet version, -so a development build is treated by what it offers. - +This fixes the 'Additional issues' relating to a test depending on BLAS \ No newline at end of file From 9e471d64ff243de0f0642960f0ceffca4b5603f9 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 28 Aug 2026 16:20:30 +0200 Subject: [PATCH 4/5] Copilot wants me to name Github GitHub Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- cran-comments.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cran-comments.md b/cran-comments.md index ecc36c01..15a5fb77 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,9 +1,9 @@ ## Test environments * local R installation, aarch64-apple-darwin23, R 4.6.1 -* macOS 26.5.2 (on Github), R 4.6.1 -* Microsoft Windows Server 2022 10.0.26100 (on Github), R 4.6.1 -* Ubuntu 24.04.4 (on Github), R 4.6.1 +* macOS 26.5.2 (on GitHub), R 4.6.1 +* Microsoft Windows Server 2022 10.0.26100 (on GitHub), R 4.6.1 +* Ubuntu 24.04.4 (on GitHub), R 4.6.1 ## R CMD check results From f50f66b6591bca2c2847dab25fc3f79ca90542d9 Mon Sep 17 00:00:00 2001 From: James Hollway Date: Fri, 28 Aug 2026 16:48:35 +0200 Subject: [PATCH 5/5] Shortened the startup message of `.onAttach()` --- NEWS.md | 4 ++++ R/zzz.R | 41 +++++++++++++++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index ef2f84f6..f4be10c7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # autograph 1.2.2 +## Package + +- Shortened the startup message of `.onAttach()` + ## Graphing - Improved how `node_color=` colours numeric node variables diff --git a/R/zzz.R b/R/zzz.R index 518db6ff..f85dae8a 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -31,14 +31,43 @@ if (!interactive()) return() local_version <- utils::packageVersion("autograph") - snet_info("You are using {.auto autograph} version {.version {local_version}}.") - snet_info(c("i" = "Theme set to {.code {getOption('stocnet_theme')}}. Use {.fn stocnet_theme} to change the theme.")) + snet_info("You are using {.auto autograph} version {.version {local_version}},", + "with theme {.code {getOption('stocnet_theme')}}.") # Only after the interactive() guard above: a script or a check run should # never reach into the IDE, whatever is remembered. - if (isTRUE(read_pref("completion")) && .completion_activate()) - snet_info("Completion of argument values is on. Use {.fn stocnet_completion} to switch it off.") -} -# nocov end + completion_on <- isTRUE(read_pref("completion")) && .completion_activate() + + # One short status line. The theme is always there, because there is always a + # theme. The medium and the completion appear only when they are away from + # their defaults, so the common case stays to a few words. What is left out of + # the status line can still be reached through a tip below. + greet_startup_cli <- function() { + medium <- getOption("stocnet_medium") + status <- paste0("Theme {.code ", getOption("stocnet_theme"), "}") + if (!identical(medium, "screen")) + status <- paste0(status, ", medium {.code ", medium, "}") + if (completion_on) status <- paste0(status, ", completion {.code on}") + # snet_info(c("i" = paste0(status, "."))) + tips <- c( + "i" = "Change the theme with {.run [stocnet_theme()](autograph::stocnet_theme())}.", + "i" = "Keep a theme for later sessions with {.code stocnet_theme(persist = TRUE)}.", + "i" = "Set output medium with {.run [stocnet_medium()](autograph::stocnet_medium())}. Currently {.code getOption('stocnet_medium')}", + "i" = "Autocomplete arguments with {.run [stocnet_completion()](autograph::stocnet_completion())}." + # "i" = "Share bugs, issues, or feature requests at {.url https://github.com/stocnet/autograph/issues}.", + # "i" = "Explore changes since the last version with {.run [news(package = 'autograph')](utils::news(package = 'autograph'))}.", + # "i" = "Visit {.url https://stocnet.github.io/autograph/} to learn more.", + # "i" = "Discover new functions at {.url https://stocnet.github.io/autograph/reference/index.html}.", + # "i" = "Discover {.emph stocnet} R packages at {.url https://github.com/stocnet/}." + ) + # Do not offer to switch on what is already on, or to set what the status + # line already reports. + if (completion_on) tips <- tips[-4] + if (!identical(medium, "screen")) tips <- tips[-3] + snet_info(sample(tips, 1)) + } + greet_startup_cli() +} +# nocov end