From 0b466ef2f9b10c144b153573a1c069d569d6b9cf Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Tue, 17 Jul 2018 08:13:36 +0900 Subject: [PATCH 01/25] Add geom_sf_label() and geom_sf_text() --- DESCRIPTION | 1 + NAMESPACE | 4 ++ R/sf.R | 115 +++++++++++++++++++++++++++++++++++++ R/stat-sf-coordinates.R | 74 ++++++++++++++++++++++++ man/ggsf.Rd | 43 ++++++++++++++ man/stat_sf_coordinates.Rd | 82 ++++++++++++++++++++++++++ 6 files changed, 319 insertions(+) create mode 100644 R/stat-sf-coordinates.R create mode 100644 man/stat_sf_coordinates.Rd diff --git a/DESCRIPTION b/DESCRIPTION index bcbfff5607..96299c8458 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -212,6 +212,7 @@ Collate: 'stat-qq-line.R' 'stat-qq.r' 'stat-quantile.r' + 'stat-sf-coordinates.R' 'stat-smooth-methods.r' 'stat-smooth.r' 'stat-sum.r' diff --git a/NAMESPACE b/NAMESPACE index f09568dd1c..cd90994627 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -217,6 +217,7 @@ export(StatQq) export(StatQqLine) export(StatQuantile) export(StatSf) +export(StatSfCoordinates) export(StatSmooth) export(StatSum) export(StatSummary) @@ -335,6 +336,8 @@ export(geom_ribbon) export(geom_rug) export(geom_segment) export(geom_sf) +export(geom_sf_label) +export(geom_sf_text) export(geom_smooth) export(geom_spoke) export(geom_step) @@ -526,6 +529,7 @@ export(stat_qq) export(stat_qq_line) export(stat_quantile) export(stat_sf) +export(stat_sf_coordinates) export(stat_smooth) export(stat_spoke) export(stat_sum) diff --git a/R/sf.R b/R/sf.R index 0aaca34ecd..12781f79f0 100644 --- a/R/sf.R +++ b/R/sf.R @@ -6,6 +6,7 @@ #' an unusual geom because it will draw different geometric objects depending #' on what simple features are present in the data: you can get points, lines, #' or polygons. +#' For texts and labels, you can use `geom_sf_text` and `geom_sf_text`. #' #' @section Geometry aesthetic: #' `geom_sf` uses a unique aesthetic: `geometry`, giving an @@ -70,6 +71,11 @@ #' "+proj=laea +y_0=0 +lon_0=155 +lat_0=-90 +ellps=WGS84 +no_defs" #' ) #' ggplot() + geom_sf(data = world2) +#' +#' # To add labels, use geom_sf_label(). +#' ggplot(nc_3867[1:3, ]) + +#' geom_sf(aes(fill = AREA)) + +#' geom_sf_label(aes(label = NAME)) #' } #' @name ggsf NULL @@ -249,6 +255,115 @@ geom_sf <- function(mapping = aes(), data = NULL, stat = "sf", ) } +#' @export +#' @rdname ggsf +#' @inheritParams geom_label +#' @inheritParams stat_sf_coordinates +#' @seealso [stat_sf_coordinates()] +geom_sf_label <- function(mapping = aes(), data = NULL, + stat = "sf_coordinates", position = "identity", + ..., + parse = FALSE, + nudge_x = 0, + nudge_y = 0, + label.padding = unit(0.25, "lines"), + label.r = unit(0.15, "lines"), + label.size = 0.25, + na.rm = FALSE, + show.legend = NA, + inherit.aes = TRUE, + fun.geometry = sf::st_point_on_surface) { + + # Automatically determin name of geometry column + if (!is.null(data) && is_sf(data)) { + geometry_col <- attr(data, "sf_column") + } else { + geometry_col <- "geometry" + } + if (is.null(mapping$geometry)) { + mapping$geometry <- as.name(geometry_col) + } + + if (!missing(nudge_x) || !missing(nudge_y)) { + if (!missing(position)) { + stop("Specify either `position` or `nudge_x`/`nudge_y`", call. = FALSE) + } + + position <- position_nudge(nudge_x, nudge_y) + } + + layer( + data = data, + mapping = mapping, + stat = stat, + geom = GeomLabel, + position = position, + show.legend = show.legend, + inherit.aes = inherit.aes, + params = list( + parse = parse, + label.padding = label.padding, + label.r = label.r, + label.size = label.size, + na.rm = na.rm, + fun.geometry = fun.geometry, + ... + ) + ) +} + +#' @export +#' @rdname ggsf +#' @inheritParams geom_text +#' @inheritParams stat_sf_coordinates +geom_sf_text <- function(mapping = aes(), data = NULL, + stat = "sf_coordinates", position = "identity", + ..., + parse = FALSE, + nudge_x = 0, + nudge_y = 0, + check_overlap = FALSE, + na.rm = FALSE, + show.legend = NA, + inherit.aes = TRUE, + fun.geometry = sf::st_point_on_surface) { + # Automatically determin name of geometry column + if (!is.null(data) && is_sf(data)) { + geometry_col <- attr(data, "sf_column") + } else { + geometry_col <- "geometry" + } + if (is.null(mapping$geometry)) { + mapping$geometry <- as.name(geometry_col) + } + + if (!missing(nudge_x) || !missing(nudge_y)) { + if (!missing(position)) { + stop("Specify either `position` or `nudge_x`/`nudge_y`", call. = FALSE) + } + + position <- position_nudge(nudge_x, nudge_y) + } + + layer( + data = data, + mapping = mapping, + stat = stat, + geom = GeomText, + position = position, + show.legend = show.legend, + inherit.aes = inherit.aes, + params = list( + parse = parse, + check_overlap = check_overlap, + na.rm = na.rm, + fun.geometry = sf::st_point_on_surface, + ... + ) + ) +} + + #' @export scale_type.sfc <- function(x) "identity" diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R new file mode 100644 index 0000000000..dcdffdf5fc --- /dev/null +++ b/R/stat-sf-coordinates.R @@ -0,0 +1,74 @@ +#' Extract coordinates from 'sf' objects +#' +#' @rdname stat_sf_coordinates +#' @section Computed variables: +#' \describe{ +#' \item{X}{X dimension of the simple feature} +#' \item{Y}{Y dimension of the simple feature} +#' \item{Z}{Z dimension of the simple feature} +#' \item{M}{M dimension of the simple feature} +#' } +#' +#' @examples +#' if (requireNamespace("sf", quietly = TRUE)) { +#' storms <- sf::st_read(system.file("shape/storms_xyz.shp", package = "sf"), quiet = TRUE) +#' +#' ggplot(storms) + +#' stat_sf_coordinates() +#' +#' ggplot(storms) + +#' stat_sf_coordinates(aes(colour = stat(Z)) +#' } +#' +#' @export +#' @inheritParams stat_identity +#' @param fun.geometry +#' A function that takes a `sfc` object and returns a +#' `sfc_POINT` with the same length as the input (e.g. [sf::st_point_on_surface()]). +stat_sf_coordinates <- function(mapping = aes(), data = NULL, geom = "point", + position = "identity", na.rm = FALSE, show.legend = NA, + inherit.aes = TRUE, fun.geometry = sf::st_point_on_surface, + ...) { + # Automatically determin name of geometry column + if (!is.null(data) && is_sf(data)) { + geometry_col <- attr(data, "sf_column") + } else { + geometry_col <- "geometry" + } + if (is.null(mapping$geometry)) { + mapping$geometry <- as.name(geometry_col) + } + + layer( + stat = StatSfCoordinates, + data = data, + mapping = mapping, + geom = geom, + position = position, + show.legend = show.legend, + inherit.aes = inherit.aes, + params = list( + na.rm = na.rm, + fun.geometry = fun.geometry, + ... + ) + ) +} + +#' @rdname stat_sf_coordinates +#' @usage NULL +#' @format NULL +#' @export +StatSfCoordinates <- ggproto( + "StatSfCoordinates", Stat, + compute_group = function(data, scales, fun.geometry) { + points_sfc <- fun.geometry(data$geometry) + coordinates <- sf::st_coordinates(points_sfc) + data <- cbind(data, coordinates) + + data + }, + + default_aes = aes(x = stat(X), y = stat(Y)), + required_aes = c("geometry") +) diff --git a/man/ggsf.Rd b/man/ggsf.Rd index c9222848f7..2e7d027f0e 100644 --- a/man/ggsf.Rd +++ b/man/ggsf.Rd @@ -7,6 +7,8 @@ \alias{stat_sf} \alias{GeomSf} \alias{geom_sf} +\alias{geom_sf_label} +\alias{geom_sf_text} \alias{CoordSf} \alias{coord_sf} \title{Visualise sf objects} @@ -19,6 +21,17 @@ geom_sf(mapping = aes(), data = NULL, stat = "sf", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, ...) +geom_sf_label(mapping = aes(), data = NULL, stat = "sf_coordinates", + position = "identity", ..., parse = FALSE, nudge_x = 0, nudge_y = 0, + label.padding = unit(0.25, "lines"), label.r = unit(0.15, "lines"), + label.size = 0.25, na.rm = FALSE, show.legend = NA, + inherit.aes = TRUE, fun.geometry = sf::st_point_on_surface) + +geom_sf_text(mapping = aes(), data = NULL, stat = "sf_coordinates", + position = "identity", ..., parse = FALSE, nudge_x = 0, nudge_y = 0, + check_overlap = FALSE, na.rm = FALSE, show.legend = NA, + inherit.aes = TRUE, fun.geometry = sf::st_point_on_surface) + coord_sf(xlim = NULL, ylim = NULL, expand = TRUE, crs = NULL, datum = sf::st_crs(4326), ndiscr = 100, default = FALSE) } @@ -70,6 +83,27 @@ to the paired geom/stat.} \item{stat}{The statistical transformation to use on the data for this layer, as a string.} +\item{parse}{If \code{TRUE}, the labels will be parsed into expressions and +displayed as described in \code{?plotmath}.} + +\item{nudge_x}{Horizontal and vertical adjustment to nudge labels by. +Useful for offsetting text from points, particularly on discrete scales.} + +\item{nudge_y}{Horizontal and vertical adjustment to nudge labels by. +Useful for offsetting text from points, particularly on discrete scales.} + +\item{label.padding}{Amount of padding around label. Defaults to 0.25 lines.} + +\item{label.r}{Radius of rounded corners. Defaults to 0.15 lines.} + +\item{label.size}{Size of label border, in mm.} + +\item{fun.geometry}{A function that takes a \code{sfc} object and returns a +\code{sfc_POINT} with the same length as the input (e.g. \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}}).} + +\item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the +same layer will not be plotted.} + \item{xlim}{Limits for the x and y axes.} \item{ylim}{Limits for the x and y axes.} @@ -98,6 +132,7 @@ uses \code{stat_sf} and adds \code{coord_sf} for you. \code{geom_sf} is an unusual geom because it will draw different geometric objects depending on what simple features are present in the data: you can get points, lines, or polygons. +For texts and labels, you can use \code{geom_sf_text} and \code{geom_sf_text}. } \section{Geometry aesthetic}{ @@ -161,6 +196,14 @@ world2 <- sf::st_transform( "+proj=laea +y_0=0 +lon_0=155 +lat_0=-90 +ellps=WGS84 +no_defs" ) ggplot() + geom_sf(data = world2) + +# To add labels, use geom_sf_label(). +ggplot(nc_3867[1:3, ]) + + geom_sf(aes(fill = AREA)) + + geom_sf_label(aes(label = NAME)) +} } +\seealso{ +\code{\link[=stat_sf_coordinates]{stat_sf_coordinates()}} } \keyword{datasets} diff --git a/man/stat_sf_coordinates.Rd b/man/stat_sf_coordinates.Rd new file mode 100644 index 0000000000..ccce2a9038 --- /dev/null +++ b/man/stat_sf_coordinates.Rd @@ -0,0 +1,82 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/stat-sf-coordinates.R +\docType{data} +\name{stat_sf_coordinates} +\alias{stat_sf_coordinates} +\alias{StatSfCoordinates} +\title{Extract coordinates from 'sf' objects} +\usage{ +stat_sf_coordinates(mapping = aes(), data = NULL, geom = "point", + position = "identity", na.rm = FALSE, show.legend = NA, + inherit.aes = TRUE, fun.geometry = sf::st_point_on_surface, ...) +} +\arguments{ +\item{mapping}{Set of aesthetic mappings created by \code{\link[=aes]{aes()}} or +\code{\link[=aes_]{aes_()}}. If specified and \code{inherit.aes = TRUE} (the +default), it is combined with the default mapping at the top level of the +plot. You must supply \code{mapping} if there is no plot mapping.} + +\item{data}{The data to be displayed in this layer. There are three +options: + +If \code{NULL}, the default, the data is inherited from the plot +data as specified in the call to \code{\link[=ggplot]{ggplot()}}. + +A \code{data.frame}, or other object, will override the plot +data. All objects will be fortified to produce a data frame. See +\code{\link[=fortify]{fortify()}} for which variables will be created. + +A \code{function} will be called with a single argument, +the plot data. The return value must be a \code{data.frame}, and +will be used as the layer data.} + +\item{geom}{The geometric object to use display the data} + +\item{position}{Position adjustment, either as a string, or the result of +a call to a position adjustment function.} + +\item{show.legend}{logical. Should this layer be included in the legends? +\code{NA}, the default, includes if any aesthetics are mapped. +\code{FALSE} never includes, and \code{TRUE} always includes. +It can also be a named logical vector to finely select the aesthetics to +display.} + +\item{inherit.aes}{If \code{FALSE}, overrides the default aesthetics, +rather than combining with them. This is most useful for helper functions +that define both data and aesthetics and shouldn't inherit behaviour from +the default plot specification, e.g. \code{\link[=borders]{borders()}}.} + +\item{fun.geometry}{A function that takes a \code{sfc} object and returns a +\code{sfc_POINT} with the same length as the input (e.g. \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}}).} + +\item{...}{Other arguments passed on to \code{\link[=layer]{layer()}}. These are +often aesthetics, used to set an aesthetic to a fixed value, like +\code{colour = "red"} or \code{size = 3}. They may also be parameters +to the paired geom/stat.} +} +\description{ +Extract coordinates from 'sf' objects +} +\section{Computed variables}{ + +\describe{ +\item{X}{X dimension of the simple feature} +\item{Y}{Y dimension of the simple feature} +\item{Z}{Z dimension of the simple feature} +\item{M}{M dimension of the simple feature} +} +} + +\examples{ +if (requireNamespace("sf", quietly = TRUE)) { +storms <- sf::st_read(system.file("shape/storms_xyz.shp", package = "sf"), quiet = TRUE) + +ggplot(storms) + + stat_sf_coordinates() + +ggplot(storms) + + stat_sf_coordinates(aes(colour = stat(Z)) +} + +} +\keyword{datasets} From 7b7be7b30cea4b74e39958022bf5b7157aae44b9 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Tue, 17 Jul 2018 19:55:40 +0900 Subject: [PATCH 02/25] Add a missing parenthesis --- R/stat-sf-coordinates.R | 2 +- man/stat_sf_coordinates.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R index dcdffdf5fc..f3657bf091 100644 --- a/R/stat-sf-coordinates.R +++ b/R/stat-sf-coordinates.R @@ -17,7 +17,7 @@ #' stat_sf_coordinates() #' #' ggplot(storms) + -#' stat_sf_coordinates(aes(colour = stat(Z)) +#' stat_sf_coordinates(aes(colour = stat(Z))) #' } #' #' @export diff --git a/man/stat_sf_coordinates.Rd b/man/stat_sf_coordinates.Rd index ccce2a9038..8dbec7c42f 100644 --- a/man/stat_sf_coordinates.Rd +++ b/man/stat_sf_coordinates.Rd @@ -75,7 +75,7 @@ ggplot(storms) + stat_sf_coordinates() ggplot(storms) + - stat_sf_coordinates(aes(colour = stat(Z)) + stat_sf_coordinates(aes(colour = stat(Z))) } } From c9689318f091d23bf7d67805bcc7713364319174 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 11 Aug 2018 11:48:29 +0900 Subject: [PATCH 03/25] Add tests for stat_sf_coordinates() --- tests/testthat/test-stat-sf-coordinates.R | 44 +++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/testthat/test-stat-sf-coordinates.R diff --git a/tests/testthat/test-stat-sf-coordinates.R b/tests/testthat/test-stat-sf-coordinates.R new file mode 100644 index 0000000000..87d7efbe31 --- /dev/null +++ b/tests/testthat/test-stat-sf-coordinates.R @@ -0,0 +1,44 @@ +context("stat_sf_coordinates") + +comp_sf_coord <- function(df, ...) { + plot <- ggplot(df) + stat_sf_coordinates(...) + layer_data(plot) +} + +test_that("stat_sf_coordinates() retrieves coordinates from sf objects", { + skip_if_not_installed("sf") + + # point + df_point <- sf::st_sf(geometry = sf::st_sfc(sf::st_point(c(0, 0)))) + expect_identical(comp_sf_coord(df_point)[, c("x", "y")], data.frame(x = 0, y = 0)) + + # line + c_line <- rbind(c(-1, -1), c(1, 1)) + df_line <- sf::st_sf(geometry = sf::st_sfc(sf::st_linestring(c_line))) + expect_identical(comp_sf_coord(df_point)[, c("x", "y")], data.frame(x = 0, y = 0)) + + # polygon + c_polygon <- list(rbind(c(-1, -1), c(-1, 1), c(1, 1), c(1, -1), c(-1, -1))) + df_polygon <- sf::st_sf(geometry = sf::st_sfc(sf::st_polygon(c_polygon))) + expect_identical(comp_sf_coord(df_point)[, c("x", "y")], data.frame(x = 0, y = 0)) +}) + +test_that("stat_sf_coordinates() handles Z and M coordinates", { + skip_if_not_installed("sf") + + # XYZ + df_xyz <- sf::st_sf(geometry = sf::st_sfc(sf::st_point(c(1, 2, 3)))) + expect_identical( + comp_sf_coord(df_xyz, aes(x = stat(Y), y = stat(Z)))[, c("x", "y")], + data.frame(x = 2, y = 3) + ) + + # XYM + df_xym <- sf::st_sf(geometry = sf::st_sfc(sf::st_point(c(1, 2, 3), dim = "XYM"))) + expect_identical( + # Note that M st_centroid() and st_point_on_surface() cannot handle M dimension + # since GEOS does not support it. So we have to use some other function like identity(). + comp_sf_coord(df_xym, aes(x = stat(Y), y = stat(M)), fun.geometry = identity)[, c("x", "y")], + data.frame(x = 2, y = 3) + ) +}) From fd1b0698317d2f283ef3dbaaf8e74d5f384ff52a Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 11 Aug 2018 11:52:19 +0900 Subject: [PATCH 04/25] Fix a typo --- tests/testthat/test-stat-sf-coordinates.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-stat-sf-coordinates.R b/tests/testthat/test-stat-sf-coordinates.R index 87d7efbe31..d837cddfb4 100644 --- a/tests/testthat/test-stat-sf-coordinates.R +++ b/tests/testthat/test-stat-sf-coordinates.R @@ -36,8 +36,8 @@ test_that("stat_sf_coordinates() handles Z and M coordinates", { # XYM df_xym <- sf::st_sf(geometry = sf::st_sfc(sf::st_point(c(1, 2, 3), dim = "XYM"))) expect_identical( - # Note that M st_centroid() and st_point_on_surface() cannot handle M dimension - # since GEOS does not support it. So we have to use some other function like identity(). + # Note that st_centroid() and st_point_on_surface() cannot handle M dimension since + # GEOS does not support it. So we have to use some other function like identity(). comp_sf_coord(df_xym, aes(x = stat(Y), y = stat(M)), fun.geometry = identity)[, c("x", "y")], data.frame(x = 2, y = 3) ) From fc7ce6942800810655b8723df063cd6fa9811c09 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 11 Aug 2018 12:30:56 +0900 Subject: [PATCH 05/25] Fix a typo in examples --- R/sf.R | 2 +- man/ggsf.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/sf.R b/R/sf.R index 12781f79f0..3d94f38a39 100644 --- a/R/sf.R +++ b/R/sf.R @@ -73,7 +73,7 @@ #' ggplot() + geom_sf(data = world2) #' #' # To add labels, use geom_sf_label(). -#' ggplot(nc_3867[1:3, ]) + +#' ggplot(nc_3857[1:3, ]) + #' geom_sf(aes(fill = AREA)) + #' geom_sf_label(aes(label = NAME)) #' } diff --git a/man/ggsf.Rd b/man/ggsf.Rd index 2e7d027f0e..042cdcacc5 100644 --- a/man/ggsf.Rd +++ b/man/ggsf.Rd @@ -198,7 +198,7 @@ world2 <- sf::st_transform( ggplot() + geom_sf(data = world2) # To add labels, use geom_sf_label(). -ggplot(nc_3867[1:3, ]) + +ggplot(nc_3857[1:3, ]) + geom_sf(aes(fill = AREA)) + geom_sf_label(aes(label = NAME)) } From 3e9121d03cd5da8a44fb20d57cc21f44207edf17 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 11 Aug 2018 12:33:49 +0900 Subject: [PATCH 06/25] Add visual tests for geom_sf_label() and geom_sf_text() --- tests/testthat/test-geom-sf.R | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/testthat/test-geom-sf.R b/tests/testthat/test-geom-sf.R index df4c5a7289..d0d3215ed2 100644 --- a/tests/testthat/test-geom-sf.R +++ b/tests/testthat/test-geom-sf.R @@ -19,3 +19,21 @@ test_that("geom_sf draws correctly", { ggplot() + geom_sf(data = pts) ) }) + +test_that("geom_sf_text() and geom_sf_label() draws correctly", { + skip_if_not_installed("sf") + if (packageVersion("sf") < "0.5.3") skip("Need sf 0.5.3") + + f <- system.file("gpkg/nc.gpkg", package="sf") + nc <- sf::read_sf(f) + # In order to avoid warning, trnasform to a projected coordinate system + nc_3857 <- sf::st_transform(nc, "+init=epsg:3857") + + expect_doppelganger("Texts for North Carolina", + ggplot() + geom_sf_text(data = nc_3857[1:3, ], aes(label = NAME)) + ) + + expect_doppelganger("Labels for North Carolina", + ggplot() + geom_sf_label(data = nc_3857[1:3, ], aes(label = NAME)) + ) +}) From 9764a95151fbd7b71adac73810782f1037715b2d Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 11 Aug 2018 13:35:29 +0900 Subject: [PATCH 07/25] Match args for stat_sf_coordinates() and StatSfCoordinates$compute_group() --- R/stat-sf-coordinates.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R index f3657bf091..552a5d23f3 100644 --- a/R/stat-sf-coordinates.R +++ b/R/stat-sf-coordinates.R @@ -61,7 +61,7 @@ stat_sf_coordinates <- function(mapping = aes(), data = NULL, geom = "point", #' @export StatSfCoordinates <- ggproto( "StatSfCoordinates", Stat, - compute_group = function(data, scales, fun.geometry) { + compute_group = function(data, scales, fun.geometry = sf::st_point_on_surface) { points_sfc <- fun.geometry(data$geometry) coordinates <- sf::st_coordinates(points_sfc) data <- cbind(data, coordinates) From 9d540ebe61fae8c797e9b35c158db6aa16f6c5c0 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 11 Aug 2018 13:40:42 +0900 Subject: [PATCH 08/25] Document stat_sf_coordinates()'s na.rm --- R/stat-sf-coordinates.R | 1 + man/stat_sf_coordinates.Rd | 3 +++ 2 files changed, 4 insertions(+) diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R index 552a5d23f3..998f9ecc87 100644 --- a/R/stat-sf-coordinates.R +++ b/R/stat-sf-coordinates.R @@ -22,6 +22,7 @@ #' #' @export #' @inheritParams stat_identity +#' @inheritParams geom_point #' @param fun.geometry #' A function that takes a `sfc` object and returns a #' `sfc_POINT` with the same length as the input (e.g. [sf::st_point_on_surface()]). diff --git a/man/stat_sf_coordinates.Rd b/man/stat_sf_coordinates.Rd index 8dbec7c42f..5cef057b85 100644 --- a/man/stat_sf_coordinates.Rd +++ b/man/stat_sf_coordinates.Rd @@ -35,6 +35,9 @@ will be used as the layer data.} \item{position}{Position adjustment, either as a string, or the result of a call to a position adjustment function.} +\item{na.rm}{If \code{FALSE}, the default, missing values are removed with +a warning. If \code{TRUE}, missing values are silently removed.} + \item{show.legend}{logical. Should this layer be included in the legends? \code{NA}, the default, includes if any aesthetics are mapped. \code{FALSE} never includes, and \code{TRUE} always includes. From a813230a069bab8765af3d9ad8d84a768dd3c3e1 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 11 Aug 2018 14:07:05 +0900 Subject: [PATCH 09/25] Add documents about stat_sf_coordinats() --- R/stat-sf-coordinates.R | 43 +++++++++++++++++++++++++++++++------- man/ggsf.Rd | 7 +++++-- man/stat_sf_coordinates.Rd | 37 +++++++++++++++++++++++++++----- 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R index 998f9ecc87..c2d4b23ce0 100644 --- a/R/stat-sf-coordinates.R +++ b/R/stat-sf-coordinates.R @@ -1,13 +1,38 @@ #' Extract coordinates from 'sf' objects -#' +#' +#' `stat_sf_coordinates()` extracts the coordinates from 'sf' objects and +#' summarises them to one pair of coordinates (X and Y, and possibly Z and/or M) +#' per geometry. This is convenient when you draw an sf object as geoms like +#' texts and labels (so [geom_sf_text()] and [geom_sf_label()] relies on this). +#' #' @rdname stat_sf_coordinates +#' @details +#' coordinates of an `sf` object can be retrieved by [sf::st_coordinates()]. +#' But, we cannot simply use `sf::st_coordinates()` because, whereas texts and +#' labels require exactly one coordinate per geometry, it returns multiple ones +#' for a polygon or a line. Thus, these two steps are needed: +#' +#' 1. Choose one point per geometry by some function like `sf::st_centroid()` +#' and `sf::st_point_on_surface()`. +#' 2. Retrieve coordinates from the points by `sf::st_coordinates()`. +#' +#' For the first step, you can use an arbitrary function via `fun.geometry`. +#' By default, [sf::st_point_on_surface()] is used; This seems more appropriate +#' than [sf::st_centroid()] since lables and texts usually are intended to be +#' put within the polygon or the line. +#' #' @section Computed variables: #' \describe{ #' \item{X}{X dimension of the simple feature} #' \item{Y}{Y dimension of the simple feature} -#' \item{Z}{Z dimension of the simple feature} -#' \item{M}{M dimension of the simple feature} +#' \item{Z}{Z dimension of the simple feature (if available)} +#' \item{M}{M dimension of the simple feature (if available)} #' } +#' +#' Note that, while Z and M dimensions are theoretically available, you may +#' face errors because sf functions don't always support Z and M. In such cases, +#' you can drop these dimensions either beforehand or in a custom `fun.geometry` +#' by [sf::st_zm()]. #' #' @examples #' if (requireNamespace("sf", quietly = TRUE)) { @@ -24,11 +49,15 @@ #' @inheritParams stat_identity #' @inheritParams geom_point #' @param fun.geometry -#' A function that takes a `sfc` object and returns a -#' `sfc_POINT` with the same length as the input (e.g. [sf::st_point_on_surface()]). +#' A function that takes a `sfc` object and returns a `sfc_POINT` with the +#' same length as the input (e.g. [sf::st_point_on_surface()]). Note that the +#' function may warn about the incorrectness of the result if the data is not +#' projected, but you can ignore this except when you are very careful about +#' the exact locations. stat_sf_coordinates <- function(mapping = aes(), data = NULL, geom = "point", - position = "identity", na.rm = FALSE, show.legend = NA, - inherit.aes = TRUE, fun.geometry = sf::st_point_on_surface, + position = "identity", na.rm = FALSE, + show.legend = NA, inherit.aes = TRUE, + fun.geometry = sf::st_point_on_surface, ...) { # Automatically determin name of geometry column if (!is.null(data) && is_sf(data)) { diff --git a/man/ggsf.Rd b/man/ggsf.Rd index 042cdcacc5..4f5dd1b3f6 100644 --- a/man/ggsf.Rd +++ b/man/ggsf.Rd @@ -98,8 +98,11 @@ Useful for offsetting text from points, particularly on discrete scales.} \item{label.size}{Size of label border, in mm.} -\item{fun.geometry}{A function that takes a \code{sfc} object and returns a -\code{sfc_POINT} with the same length as the input (e.g. \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}}).} +\item{fun.geometry}{A function that takes a \code{sfc} object and returns a \code{sfc_POINT} with the +same length as the input (e.g. \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}}). Note that the +function may warn about the incorrectness of the result if the data is not +projected, but you can ignore this except when you are very careful about +the exact locations.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} diff --git a/man/stat_sf_coordinates.Rd b/man/stat_sf_coordinates.Rd index 5cef057b85..bc56d8b02e 100644 --- a/man/stat_sf_coordinates.Rd +++ b/man/stat_sf_coordinates.Rd @@ -49,8 +49,11 @@ rather than combining with them. This is most useful for helper functions that define both data and aesthetics and shouldn't inherit behaviour from the default plot specification, e.g. \code{\link[=borders]{borders()}}.} -\item{fun.geometry}{A function that takes a \code{sfc} object and returns a -\code{sfc_POINT} with the same length as the input (e.g. \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}}).} +\item{fun.geometry}{A function that takes a \code{sfc} object and returns a \code{sfc_POINT} with the +same length as the input (e.g. \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}}). Note that the +function may warn about the incorrectness of the result if the data is not +projected, but you can ignore this except when you are very careful about +the exact locations.} \item{...}{Other arguments passed on to \code{\link[=layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like @@ -58,16 +61,40 @@ often aesthetics, used to set an aesthetic to a fixed value, like to the paired geom/stat.} } \description{ -Extract coordinates from 'sf' objects +\code{stat_sf_coordinates()} extracts the coordinates from 'sf' objects and +summarises them to one pair of coordinates (X and Y, and possibly Z and/or M) +per geometry. This is convenient when you draw an sf object as geoms like +texts and labels (so \code{\link[=geom_sf_text]{geom_sf_text()}} and \code{\link[=geom_sf_label]{geom_sf_label()}} relies on this). +} +\details{ +coordinates of an \code{sf} object can be retrieved by \code{\link[sf:st_coordinates]{sf::st_coordinates()}}. +But, we cannot simply use \code{sf::st_coordinates()} because, whereas texts and +labels require exactly one coordinate per geometry, it returns multiple ones +for a polygon or a line. Thus, these two steps are needed: +\enumerate{ +\item Choose one point per geometry by some function like \code{sf::st_centroid()} +and \code{sf::st_point_on_surface()}. +\item Retrieve coordinates from the points by \code{sf::st_coordinates()}. +} + +For the first step, you can use an arbitrary function via \code{fun.geometry}. +By default, \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}} is used; This seems more appropriate +than \code{\link[sf:st_centroid]{sf::st_centroid()}} since lables and texts usually are intended to be +put within the polygon or the line. } \section{Computed variables}{ \describe{ \item{X}{X dimension of the simple feature} \item{Y}{Y dimension of the simple feature} -\item{Z}{Z dimension of the simple feature} -\item{M}{M dimension of the simple feature} +\item{Z}{Z dimension of the simple feature (if available)} +\item{M}{M dimension of the simple feature (if available)} } + +Note that, while Z and M dimensions are theoretically available, you may +face errors because sf functions don't always support Z and M. In such cases, +you can drop these dimensions either beforehand or in a custom \code{fun.geometry} +by \code{\link[sf:st_zm]{sf::st_zm()}}. } \examples{ From f22ee4748c4e49aa395aca433541f28b8d371603 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 11 Aug 2018 15:32:52 +0900 Subject: [PATCH 10/25] Set the default of fun.geometry to NULL When sf package is not installed, test-function-args fails. --- R/stat-sf-coordinates.R | 14 ++++++++------ man/ggsf.Rd | 24 +++++++++++++----------- man/stat_sf_coordinates.Rd | 10 +++++----- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R index c2d4b23ce0..8a75502922 100644 --- a/R/stat-sf-coordinates.R +++ b/R/stat-sf-coordinates.R @@ -50,14 +50,14 @@ #' @inheritParams geom_point #' @param fun.geometry #' A function that takes a `sfc` object and returns a `sfc_POINT` with the -#' same length as the input (e.g. [sf::st_point_on_surface()]). Note that the -#' function may warn about the incorrectness of the result if the data is not -#' projected, but you can ignore this except when you are very careful about -#' the exact locations. +#' same length as the input. If `NULL`, [sf::st_point_on_surface()]) will be +#' used. Note that the function may warn about the incorrectness of the result +#' if the data is not projected, but you can ignore this except when you +#' really care about the exact locations. stat_sf_coordinates <- function(mapping = aes(), data = NULL, geom = "point", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, - fun.geometry = sf::st_point_on_surface, + fun.geometry = NULL, ...) { # Automatically determin name of geometry column if (!is.null(data) && is_sf(data)) { @@ -91,7 +91,9 @@ stat_sf_coordinates <- function(mapping = aes(), data = NULL, geom = "point", #' @export StatSfCoordinates <- ggproto( "StatSfCoordinates", Stat, - compute_group = function(data, scales, fun.geometry = sf::st_point_on_surface) { + compute_group = function(data, scales, fun.geometry = NULL) { + if (is.null(fun.geometry)) fun.geometry <- sf::st_point_on_surface + points_sfc <- fun.geometry(data$geometry) coordinates <- sf::st_coordinates(points_sfc) data <- cbind(data, coordinates) diff --git a/man/ggsf.Rd b/man/ggsf.Rd index 4f5dd1b3f6..62f9ffb8ba 100644 --- a/man/ggsf.Rd +++ b/man/ggsf.Rd @@ -22,15 +22,17 @@ geom_sf(mapping = aes(), data = NULL, stat = "sf", inherit.aes = TRUE, ...) geom_sf_label(mapping = aes(), data = NULL, stat = "sf_coordinates", - position = "identity", ..., parse = FALSE, nudge_x = 0, nudge_y = 0, - label.padding = unit(0.25, "lines"), label.r = unit(0.15, "lines"), - label.size = 0.25, na.rm = FALSE, show.legend = NA, - inherit.aes = TRUE, fun.geometry = sf::st_point_on_surface) + position = "identity", ..., parse = FALSE, nudge_x = 0, + nudge_y = 0, label.padding = unit(0.25, "lines"), + label.r = unit(0.15, "lines"), label.size = 0.25, na.rm = FALSE, + show.legend = NA, inherit.aes = TRUE, + fun.geometry = sf::st_point_on_surface) geom_sf_text(mapping = aes(), data = NULL, stat = "sf_coordinates", - position = "identity", ..., parse = FALSE, nudge_x = 0, nudge_y = 0, - check_overlap = FALSE, na.rm = FALSE, show.legend = NA, - inherit.aes = TRUE, fun.geometry = sf::st_point_on_surface) + position = "identity", ..., parse = FALSE, nudge_x = 0, + nudge_y = 0, check_overlap = FALSE, na.rm = FALSE, + show.legend = NA, inherit.aes = TRUE, + fun.geometry = sf::st_point_on_surface) coord_sf(xlim = NULL, ylim = NULL, expand = TRUE, crs = NULL, datum = sf::st_crs(4326), ndiscr = 100, default = FALSE) @@ -99,10 +101,10 @@ Useful for offsetting text from points, particularly on discrete scales.} \item{label.size}{Size of label border, in mm.} \item{fun.geometry}{A function that takes a \code{sfc} object and returns a \code{sfc_POINT} with the -same length as the input (e.g. \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}}). Note that the -function may warn about the incorrectness of the result if the data is not -projected, but you can ignore this except when you are very careful about -the exact locations.} +same length as the input. If \code{NULL}, \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}}) will be +used. Note that the function may warn about the incorrectness of the result +if the data is not projected, but you can ignore this except when you +really care about the exact locations.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} diff --git a/man/stat_sf_coordinates.Rd b/man/stat_sf_coordinates.Rd index bc56d8b02e..64f8c73e8d 100644 --- a/man/stat_sf_coordinates.Rd +++ b/man/stat_sf_coordinates.Rd @@ -8,7 +8,7 @@ \usage{ stat_sf_coordinates(mapping = aes(), data = NULL, geom = "point", position = "identity", na.rm = FALSE, show.legend = NA, - inherit.aes = TRUE, fun.geometry = sf::st_point_on_surface, ...) + inherit.aes = TRUE, fun.geometry = NULL, ...) } \arguments{ \item{mapping}{Set of aesthetic mappings created by \code{\link[=aes]{aes()}} or @@ -50,10 +50,10 @@ that define both data and aesthetics and shouldn't inherit behaviour from the default plot specification, e.g. \code{\link[=borders]{borders()}}.} \item{fun.geometry}{A function that takes a \code{sfc} object and returns a \code{sfc_POINT} with the -same length as the input (e.g. \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}}). Note that the -function may warn about the incorrectness of the result if the data is not -projected, but you can ignore this except when you are very careful about -the exact locations.} +same length as the input. If \code{NULL}, \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}}) will be +used. Note that the function may warn about the incorrectness of the result +if the data is not projected, but you can ignore this except when you +really care about the exact locations.} \item{...}{Other arguments passed on to \code{\link[=layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like From a2406224389a610a3866b3cfe4269bdd483b0a50 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 11 Aug 2018 15:56:13 +0900 Subject: [PATCH 11/25] Set more fun.geometry to NULL --- R/sf.R | 4 ++-- man/ggsf.Rd | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/R/sf.R b/R/sf.R index 3d94f38a39..d2238a1da2 100644 --- a/R/sf.R +++ b/R/sf.R @@ -272,7 +272,7 @@ geom_sf_label <- function(mapping = aes(), data = NULL, na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, - fun.geometry = sf::st_point_on_surface) { + fun.geometry = NULL) { # Automatically determin name of geometry column if (!is.null(data) && is_sf(data)) { @@ -326,7 +326,7 @@ geom_sf_text <- function(mapping = aes(), data = NULL, na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, - fun.geometry = sf::st_point_on_surface) { + fun.geometry = NULL) { # Automatically determin name of geometry column if (!is.null(data) && is_sf(data)) { geometry_col <- attr(data, "sf_column") diff --git a/man/ggsf.Rd b/man/ggsf.Rd index 62f9ffb8ba..10f102d8b2 100644 --- a/man/ggsf.Rd +++ b/man/ggsf.Rd @@ -25,14 +25,12 @@ geom_sf_label(mapping = aes(), data = NULL, stat = "sf_coordinates", position = "identity", ..., parse = FALSE, nudge_x = 0, nudge_y = 0, label.padding = unit(0.25, "lines"), label.r = unit(0.15, "lines"), label.size = 0.25, na.rm = FALSE, - show.legend = NA, inherit.aes = TRUE, - fun.geometry = sf::st_point_on_surface) + show.legend = NA, inherit.aes = TRUE, fun.geometry = NULL) geom_sf_text(mapping = aes(), data = NULL, stat = "sf_coordinates", position = "identity", ..., parse = FALSE, nudge_x = 0, nudge_y = 0, check_overlap = FALSE, na.rm = FALSE, - show.legend = NA, inherit.aes = TRUE, - fun.geometry = sf::st_point_on_surface) + show.legend = NA, inherit.aes = TRUE, fun.geometry = NULL) coord_sf(xlim = NULL, ylim = NULL, expand = TRUE, crs = NULL, datum = sf::st_crs(4326), ndiscr = 100, default = FALSE) From 6a280a9f729d1cc0d3635b3217f416491060c9e2 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 11 Aug 2018 15:56:33 +0900 Subject: [PATCH 12/25] Fix mistakenly passed sf::point_on_surface --- R/sf.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/sf.R b/R/sf.R index d2238a1da2..c4d0af6625 100644 --- a/R/sf.R +++ b/R/sf.R @@ -357,7 +357,7 @@ geom_sf_text <- function(mapping = aes(), data = NULL, parse = parse, check_overlap = check_overlap, na.rm = na.rm, - fun.geometry = sf::st_point_on_surface, + fun.geometry = fun.geometry, ... ) ) From bfe7f3a04a28a2dd0a728f2349156811705e0aa1 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sat, 11 Aug 2018 16:34:50 +0900 Subject: [PATCH 13/25] Stop cross-referencing sf functions --- R/stat-sf-coordinates.R | 10 +++++----- man/ggsf.Rd | 2 +- man/stat_sf_coordinates.Rd | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R index 8a75502922..4729593005 100644 --- a/R/stat-sf-coordinates.R +++ b/R/stat-sf-coordinates.R @@ -7,7 +7,7 @@ #' #' @rdname stat_sf_coordinates #' @details -#' coordinates of an `sf` object can be retrieved by [sf::st_coordinates()]. +#' coordinates of an 'sf' object can be retrieved by `sf::st_coordinates()`. #' But, we cannot simply use `sf::st_coordinates()` because, whereas texts and #' labels require exactly one coordinate per geometry, it returns multiple ones #' for a polygon or a line. Thus, these two steps are needed: @@ -17,8 +17,8 @@ #' 2. Retrieve coordinates from the points by `sf::st_coordinates()`. #' #' For the first step, you can use an arbitrary function via `fun.geometry`. -#' By default, [sf::st_point_on_surface()] is used; This seems more appropriate -#' than [sf::st_centroid()] since lables and texts usually are intended to be +#' By default, `sf::st_point_on_surface()` is used; This seems more appropriate +#' than `sf::st_centroid()` since lables and texts usually are intended to be #' put within the polygon or the line. #' #' @section Computed variables: @@ -32,7 +32,7 @@ #' Note that, while Z and M dimensions are theoretically available, you may #' face errors because sf functions don't always support Z and M. In such cases, #' you can drop these dimensions either beforehand or in a custom `fun.geometry` -#' by [sf::st_zm()]. +#' by `sf::st_zm()`. #' #' @examples #' if (requireNamespace("sf", quietly = TRUE)) { @@ -50,7 +50,7 @@ #' @inheritParams geom_point #' @param fun.geometry #' A function that takes a `sfc` object and returns a `sfc_POINT` with the -#' same length as the input. If `NULL`, [sf::st_point_on_surface()]) will be +#' same length as the input. If `NULL`, `sf::st_point_on_surface()` will be #' used. Note that the function may warn about the incorrectness of the result #' if the data is not projected, but you can ignore this except when you #' really care about the exact locations. diff --git a/man/ggsf.Rd b/man/ggsf.Rd index 10f102d8b2..cfaacd9047 100644 --- a/man/ggsf.Rd +++ b/man/ggsf.Rd @@ -99,7 +99,7 @@ Useful for offsetting text from points, particularly on discrete scales.} \item{label.size}{Size of label border, in mm.} \item{fun.geometry}{A function that takes a \code{sfc} object and returns a \code{sfc_POINT} with the -same length as the input. If \code{NULL}, \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}}) will be +same length as the input. If \code{NULL}, \code{sf::st_point_on_surface()} will be used. Note that the function may warn about the incorrectness of the result if the data is not projected, but you can ignore this except when you really care about the exact locations.} diff --git a/man/stat_sf_coordinates.Rd b/man/stat_sf_coordinates.Rd index 64f8c73e8d..627d48bb47 100644 --- a/man/stat_sf_coordinates.Rd +++ b/man/stat_sf_coordinates.Rd @@ -50,7 +50,7 @@ that define both data and aesthetics and shouldn't inherit behaviour from the default plot specification, e.g. \code{\link[=borders]{borders()}}.} \item{fun.geometry}{A function that takes a \code{sfc} object and returns a \code{sfc_POINT} with the -same length as the input. If \code{NULL}, \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}}) will be +same length as the input. If \code{NULL}, \code{sf::st_point_on_surface()} will be used. Note that the function may warn about the incorrectness of the result if the data is not projected, but you can ignore this except when you really care about the exact locations.} @@ -67,7 +67,7 @@ per geometry. This is convenient when you draw an sf object as geoms like texts and labels (so \code{\link[=geom_sf_text]{geom_sf_text()}} and \code{\link[=geom_sf_label]{geom_sf_label()}} relies on this). } \details{ -coordinates of an \code{sf} object can be retrieved by \code{\link[sf:st_coordinates]{sf::st_coordinates()}}. +coordinates of an 'sf' object can be retrieved by \code{sf::st_coordinates()}. But, we cannot simply use \code{sf::st_coordinates()} because, whereas texts and labels require exactly one coordinate per geometry, it returns multiple ones for a polygon or a line. Thus, these two steps are needed: @@ -78,8 +78,8 @@ and \code{sf::st_point_on_surface()}. } For the first step, you can use an arbitrary function via \code{fun.geometry}. -By default, \code{\link[sf:st_point_on_surface]{sf::st_point_on_surface()}} is used; This seems more appropriate -than \code{\link[sf:st_centroid]{sf::st_centroid()}} since lables and texts usually are intended to be +By default, \code{sf::st_point_on_surface()} is used; This seems more appropriate +than \code{sf::st_centroid()} since lables and texts usually are intended to be put within the polygon or the line. } \section{Computed variables}{ @@ -94,7 +94,7 @@ put within the polygon or the line. Note that, while Z and M dimensions are theoretically available, you may face errors because sf functions don't always support Z and M. In such cases, you can drop these dimensions either beforehand or in a custom \code{fun.geometry} -by \code{\link[sf:st_zm]{sf::st_zm()}}. +by \code{sf::st_zm()}. } \examples{ From 342c0c29da3921dbafaed5b29e3e56e71978a58d Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Sun, 19 Aug 2018 19:07:22 +0900 Subject: [PATCH 14/25] Ignore Z and M dimension --- R/stat-sf-coordinates.R | 55 ++++++++++++---------- man/ggsf.Rd | 8 ++-- man/stat_sf_coordinates.Rd | 50 ++++++++++---------- tests/testthat/Rplot001.png | Bin 0 -> 8877 bytes tests/testthat/test-stat-sf-coordinates.R | 27 +++++------ 5 files changed, 71 insertions(+), 69 deletions(-) create mode 100644 tests/testthat/Rplot001.png diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R index 4729593005..3c2e6593ce 100644 --- a/R/stat-sf-coordinates.R +++ b/R/stat-sf-coordinates.R @@ -1,13 +1,13 @@ #' Extract coordinates from 'sf' objects #' #' `stat_sf_coordinates()` extracts the coordinates from 'sf' objects and -#' summarises them to one pair of coordinates (X and Y, and possibly Z and/or M) -#' per geometry. This is convenient when you draw an sf object as geoms like -#' texts and labels (so [geom_sf_text()] and [geom_sf_label()] relies on this). +#' summarises them to one pair of coordinates (X and Y) per geometry. This is +#' convenient when you draw an sf object as geoms like texts and labels (so +#' [geom_sf_text()] and [geom_sf_label()] relies on this). #' #' @rdname stat_sf_coordinates #' @details -#' coordinates of an 'sf' object can be retrieved by `sf::st_coordinates()`. +#' coordinates of an `sf` object can be retrieved by `sf::st_coordinates()`. #' But, we cannot simply use `sf::st_coordinates()` because, whereas texts and #' labels require exactly one coordinate per geometry, it returns multiple ones #' for a polygon or a line. Thus, these two steps are needed: @@ -17,32 +17,34 @@ #' 2. Retrieve coordinates from the points by `sf::st_coordinates()`. #' #' For the first step, you can use an arbitrary function via `fun.geometry`. -#' By default, `sf::st_point_on_surface()` is used; This seems more appropriate -#' than `sf::st_centroid()` since lables and texts usually are intended to be -#' put within the polygon or the line. +#' By default, `function(x) sf::st_point_on_surface(sf::st_zm(x))` is used; +#' `sf::st_point_on_surface()` seems more appropriate than `sf::st_centroid()` +#' since lables and texts usually are intended to be put within the polygon or +#' the line. `sf::st_zm()` is needed to drop Z and M dimension beforehand, +#' otherwise `sf::st_point_on_surface()` may fail when the geometries have M +#' dimension. #' #' @section Computed variables: #' \describe{ -#' \item{X}{X dimension of the simple feature} -#' \item{Y}{Y dimension of the simple feature} -#' \item{Z}{Z dimension of the simple feature (if available)} -#' \item{M}{M dimension of the simple feature (if available)} +#' \item{x}{X dimension of the simple feature} +#' \item{y}{Y dimension of the simple feature} #' } #' -#' Note that, while Z and M dimensions are theoretically available, you may -#' face errors because sf functions don't always support Z and M. In such cases, -#' you can drop these dimensions either beforehand or in a custom `fun.geometry` -#' by `sf::st_zm()`. -#' #' @examples #' if (requireNamespace("sf", quietly = TRUE)) { -#' storms <- sf::st_read(system.file("shape/storms_xyz.shp", package = "sf"), quiet = TRUE) +#' nc <- sf::st_read(system.file("shape/nc.shp", package="sf")) #' -#' ggplot(storms) + +#' ggplot(nc) + #' stat_sf_coordinates() #' -#' ggplot(storms) + -#' stat_sf_coordinates(aes(colour = stat(Z))) +#' ggplot(nc) + +#' geom_errorbarh( +#' aes(xmin = stat(x) - 0.2, +#' xmax = stat(x) + 0.2, +#' y = stat(y), +#' height = 0.04), +#' stat = "sf_coordinates" +#' ) #' } #' #' @export @@ -50,10 +52,10 @@ #' @inheritParams geom_point #' @param fun.geometry #' A function that takes a `sfc` object and returns a `sfc_POINT` with the -#' same length as the input. If `NULL`, `sf::st_point_on_surface()` will be -#' used. Note that the function may warn about the incorrectness of the result -#' if the data is not projected, but you can ignore this except when you -#' really care about the exact locations. +#' same length as the input. If `NULL`, `function(x) sf::st_point_on_surface(sf::st_zm(x))` +#' will be used. Note that the function may warn about the incorrectness of +#' the result if the data is not projected, but you can ignore this except +#' when you really care about the exact locations. stat_sf_coordinates <- function(mapping = aes(), data = NULL, geom = "point", position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, @@ -96,11 +98,12 @@ StatSfCoordinates <- ggproto( points_sfc <- fun.geometry(data$geometry) coordinates <- sf::st_coordinates(points_sfc) - data <- cbind(data, coordinates) + data$x <- coordinates[, "X"] + data$y <- coordinates[, "Y"] data }, - default_aes = aes(x = stat(X), y = stat(Y)), + default_aes = aes(x = stat(x), y = stat(y)), required_aes = c("geometry") ) diff --git a/man/ggsf.Rd b/man/ggsf.Rd index cfaacd9047..e7ea018d67 100644 --- a/man/ggsf.Rd +++ b/man/ggsf.Rd @@ -99,10 +99,10 @@ Useful for offsetting text from points, particularly on discrete scales.} \item{label.size}{Size of label border, in mm.} \item{fun.geometry}{A function that takes a \code{sfc} object and returns a \code{sfc_POINT} with the -same length as the input. If \code{NULL}, \code{sf::st_point_on_surface()} will be -used. Note that the function may warn about the incorrectness of the result -if the data is not projected, but you can ignore this except when you -really care about the exact locations.} +same length as the input. If \code{NULL}, \code{function(x) sf::st_point_on_surface(sf::st_zm(x))} +will be used. Note that the function may warn about the incorrectness of +the result if the data is not projected, but you can ignore this except +when you really care about the exact locations.} \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the same layer will not be plotted.} diff --git a/man/stat_sf_coordinates.Rd b/man/stat_sf_coordinates.Rd index 627d48bb47..21bce0fad3 100644 --- a/man/stat_sf_coordinates.Rd +++ b/man/stat_sf_coordinates.Rd @@ -50,10 +50,10 @@ that define both data and aesthetics and shouldn't inherit behaviour from the default plot specification, e.g. \code{\link[=borders]{borders()}}.} \item{fun.geometry}{A function that takes a \code{sfc} object and returns a \code{sfc_POINT} with the -same length as the input. If \code{NULL}, \code{sf::st_point_on_surface()} will be -used. Note that the function may warn about the incorrectness of the result -if the data is not projected, but you can ignore this except when you -really care about the exact locations.} +same length as the input. If \code{NULL}, \code{function(x) sf::st_point_on_surface(sf::st_zm(x))} +will be used. Note that the function may warn about the incorrectness of +the result if the data is not projected, but you can ignore this except +when you really care about the exact locations.} \item{...}{Other arguments passed on to \code{\link[=layer]{layer()}}. These are often aesthetics, used to set an aesthetic to a fixed value, like @@ -62,12 +62,12 @@ to the paired geom/stat.} } \description{ \code{stat_sf_coordinates()} extracts the coordinates from 'sf' objects and -summarises them to one pair of coordinates (X and Y, and possibly Z and/or M) -per geometry. This is convenient when you draw an sf object as geoms like -texts and labels (so \code{\link[=geom_sf_text]{geom_sf_text()}} and \code{\link[=geom_sf_label]{geom_sf_label()}} relies on this). +summarises them to one pair of coordinates (X and Y) per geometry. This is +convenient when you draw an sf object as geoms like texts and labels (so +\code{\link[=geom_sf_text]{geom_sf_text()}} and \code{\link[=geom_sf_label]{geom_sf_label()}} relies on this). } \details{ -coordinates of an 'sf' object can be retrieved by \code{sf::st_coordinates()}. +coordinates of an \code{sf} object can be retrieved by \code{sf::st_coordinates()}. But, we cannot simply use \code{sf::st_coordinates()} because, whereas texts and labels require exactly one coordinate per geometry, it returns multiple ones for a polygon or a line. Thus, these two steps are needed: @@ -78,34 +78,36 @@ and \code{sf::st_point_on_surface()}. } For the first step, you can use an arbitrary function via \code{fun.geometry}. -By default, \code{sf::st_point_on_surface()} is used; This seems more appropriate -than \code{sf::st_centroid()} since lables and texts usually are intended to be -put within the polygon or the line. +By default, \code{function(x) sf::st_point_on_surface(sf::st_zm(x))} is used; +\code{sf::st_point_on_surface()} seems more appropriate than \code{sf::st_centroid()} +since lables and texts usually are intended to be put within the polygon or +the line. \code{sf::st_zm()} is needed to drop Z and M dimension beforehand, +otherwise \code{sf::st_point_on_surface()} may fail when the geometries have M +dimension. } \section{Computed variables}{ \describe{ -\item{X}{X dimension of the simple feature} -\item{Y}{Y dimension of the simple feature} -\item{Z}{Z dimension of the simple feature (if available)} -\item{M}{M dimension of the simple feature (if available)} +\item{x}{X dimension of the simple feature} +\item{y}{Y dimension of the simple feature} } - -Note that, while Z and M dimensions are theoretically available, you may -face errors because sf functions don't always support Z and M. In such cases, -you can drop these dimensions either beforehand or in a custom \code{fun.geometry} -by \code{sf::st_zm()}. } \examples{ if (requireNamespace("sf", quietly = TRUE)) { -storms <- sf::st_read(system.file("shape/storms_xyz.shp", package = "sf"), quiet = TRUE) +nc <- sf::st_read(system.file("shape/nc.shp", package="sf")) -ggplot(storms) + +ggplot(nc) + stat_sf_coordinates() -ggplot(storms) + - stat_sf_coordinates(aes(colour = stat(Z))) +ggplot(nc) + + geom_errorbarh( + aes(xmin = stat(x) - 0.2, + xmax = stat(x) + 0.2, + y = stat(y), + height = 0.04), + stat = "sf_coordinates" + ) } } diff --git a/tests/testthat/Rplot001.png b/tests/testthat/Rplot001.png new file mode 100644 index 0000000000000000000000000000000000000000..84b6189d6cbfd0a546ba9fe488b73f0e7dd44558 GIT binary patch literal 8877 zcmeI&F$+Oa6bJBg-=m% Date: Sun, 19 Aug 2018 19:23:01 +0900 Subject: [PATCH 15/25] Fix the example of stat_sf_coordinates() --- R/stat-sf-coordinates.R | 6 ++++-- man/stat_sf_coordinates.Rd | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R index 3c2e6593ce..ed61274d76 100644 --- a/R/stat-sf-coordinates.R +++ b/R/stat-sf-coordinates.R @@ -39,10 +39,12 @@ #' #' ggplot(nc) + #' geom_errorbarh( -#' aes(xmin = stat(x) - 0.2, -#' xmax = stat(x) + 0.2, +#' aes(geometry = geometry, +#' xmin = stat(x) - 0.1, +#' xmax = stat(x) + 0.1, #' y = stat(y), #' height = 0.04), +#' colour = "white", #' stat = "sf_coordinates" #' ) #' } diff --git a/man/stat_sf_coordinates.Rd b/man/stat_sf_coordinates.Rd index 21bce0fad3..8660d4e386 100644 --- a/man/stat_sf_coordinates.Rd +++ b/man/stat_sf_coordinates.Rd @@ -102,10 +102,12 @@ ggplot(nc) + ggplot(nc) + geom_errorbarh( - aes(xmin = stat(x) - 0.2, - xmax = stat(x) + 0.2, + aes(geometry = geometry, + xmin = stat(x) - 0.1, + xmax = stat(x) + 0.1, y = stat(y), height = 0.04), + colour = "white", stat = "sf_coordinates" ) } From 67824e1bcfa06cd3cf08a683e47dc0d8b146a9df Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Mon, 20 Aug 2018 06:51:59 +0900 Subject: [PATCH 16/25] Remove Rplot001.png --- tests/testthat/Rplot001.png | Bin 8877 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/testthat/Rplot001.png diff --git a/tests/testthat/Rplot001.png b/tests/testthat/Rplot001.png deleted file mode 100644 index 84b6189d6cbfd0a546ba9fe488b73f0e7dd44558..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8877 zcmeI&F$+Oa6bJBg-=m% Date: Mon, 20 Aug 2018 06:57:52 +0900 Subject: [PATCH 17/25] Fix doc of stat_sf_coordinates() --- R/stat-sf-coordinates.R | 4 ++-- man/stat_sf_coordinates.Rd | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R index ed61274d76..de6cafb99d 100644 --- a/R/stat-sf-coordinates.R +++ b/R/stat-sf-coordinates.R @@ -1,7 +1,7 @@ #' Extract coordinates from 'sf' objects #' #' `stat_sf_coordinates()` extracts the coordinates from 'sf' objects and -#' summarises them to one pair of coordinates (X and Y) per geometry. This is +#' summarises them to one pair of coordinates (x and y) per geometry. This is #' convenient when you draw an sf object as geoms like texts and labels (so #' [geom_sf_text()] and [geom_sf_label()] relies on this). #' @@ -13,7 +13,7 @@ #' for a polygon or a line. Thus, these two steps are needed: #' #' 1. Choose one point per geometry by some function like `sf::st_centroid()` -#' and `sf::st_point_on_surface()`. +#' or `sf::st_point_on_surface()`. #' 2. Retrieve coordinates from the points by `sf::st_coordinates()`. #' #' For the first step, you can use an arbitrary function via `fun.geometry`. diff --git a/man/stat_sf_coordinates.Rd b/man/stat_sf_coordinates.Rd index 8660d4e386..890f69df68 100644 --- a/man/stat_sf_coordinates.Rd +++ b/man/stat_sf_coordinates.Rd @@ -62,7 +62,7 @@ to the paired geom/stat.} } \description{ \code{stat_sf_coordinates()} extracts the coordinates from 'sf' objects and -summarises them to one pair of coordinates (X and Y) per geometry. This is +summarises them to one pair of coordinates (x and y) per geometry. This is convenient when you draw an sf object as geoms like texts and labels (so \code{\link[=geom_sf_text]{geom_sf_text()}} and \code{\link[=geom_sf_label]{geom_sf_label()}} relies on this). } @@ -73,7 +73,7 @@ labels require exactly one coordinate per geometry, it returns multiple ones for a polygon or a line. Thus, these two steps are needed: \enumerate{ \item Choose one point per geometry by some function like \code{sf::st_centroid()} -and \code{sf::st_point_on_surface()}. +or \code{sf::st_point_on_surface()}. \item Retrieve coordinates from the points by \code{sf::st_coordinates()}. } From 721c83ffde9cba75ab7502878f808cfab31636e4 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Mon, 20 Aug 2018 06:59:29 +0900 Subject: [PATCH 18/25] Fix an example of stat_sf_coordinates() --- R/stat-sf-coordinates.R | 1 - man/stat_sf_coordinates.Rd | 1 - 2 files changed, 2 deletions(-) diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R index de6cafb99d..5a558481fa 100644 --- a/R/stat-sf-coordinates.R +++ b/R/stat-sf-coordinates.R @@ -44,7 +44,6 @@ #' xmax = stat(x) + 0.1, #' y = stat(y), #' height = 0.04), -#' colour = "white", #' stat = "sf_coordinates" #' ) #' } diff --git a/man/stat_sf_coordinates.Rd b/man/stat_sf_coordinates.Rd index 890f69df68..9ad7a1ade8 100644 --- a/man/stat_sf_coordinates.Rd +++ b/man/stat_sf_coordinates.Rd @@ -107,7 +107,6 @@ ggplot(nc) + xmax = stat(x) + 0.1, y = stat(y), height = 0.04), - colour = "white", stat = "sf_coordinates" ) } From 1da63647782417b543ce0becc6932505838def51 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Mon, 20 Aug 2018 21:22:23 +0900 Subject: [PATCH 19/25] Fix default fun.geometry and tests --- R/stat-sf-coordinates.R | 4 +++- tests/testthat/test-stat-sf-coordinates.R | 12 +++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R index 5a558481fa..8102785fb4 100644 --- a/R/stat-sf-coordinates.R +++ b/R/stat-sf-coordinates.R @@ -95,7 +95,9 @@ stat_sf_coordinates <- function(mapping = aes(), data = NULL, geom = "point", StatSfCoordinates <- ggproto( "StatSfCoordinates", Stat, compute_group = function(data, scales, fun.geometry = NULL) { - if (is.null(fun.geometry)) fun.geometry <- sf::st_point_on_surface + if (is.null(fun.geometry)) { + fun.geometry <- function(x) sf::st_point_on_surface(sf::st_zm(x)) + } points_sfc <- fun.geometry(data$geometry) coordinates <- sf::st_coordinates(points_sfc) diff --git a/tests/testthat/test-stat-sf-coordinates.R b/tests/testthat/test-stat-sf-coordinates.R index e28616311a..6c56c213b7 100644 --- a/tests/testthat/test-stat-sf-coordinates.R +++ b/tests/testthat/test-stat-sf-coordinates.R @@ -15,7 +15,12 @@ test_that("stat_sf_coordinates() retrieves coordinates from sf objects", { # line c_line <- rbind(c(-1, -1), c(1, 1)) df_line <- sf::st_sf(geometry = sf::st_sfc(sf::st_linestring(c_line))) - expect_identical(comp_sf_coord(df_point)[, c("x", "y")], data.frame(x = 0, y = 0)) + expect_identical( + # Note that st_point_on_surface() does not return the centroid for + # `df_line`, which may be a bit confusing. So, use st_centroid() here. + comp_sf_coord(df_line, fun.geometry = sf::st_centroid)[, c("x", "y")], + data.frame(x = 0, y = 0) + ) # polygon c_polygon <- list(rbind(c(-1, -1), c(-1, 1), c(1, 1), c(1, -1), c(-1, -1))) @@ -34,8 +39,9 @@ test_that("stat_sf_coordinates() ignores Z and M coordinates", { skip_if_not_installed("sf") # XYM - df_xym <- sf::st_sf(geometry = sf::st_sfc(sf::st_point(c(1, 2), dim = "XYM"))) + c_polygon <- list(rbind(c(-1, -1, 0), c(-1, 1, 0), c(1, 1, 0), c(1, -1, 0), c(-1, -1, 0))) + df_xym <- sf::st_sf(geometry = sf::st_sfc(sf::st_polygon(c_polygon, dim = "XYM"))) # Note that st_centroid() and st_point_on_surface() cannot handle M dimension since # GEOS does not support it. The default fun.geometry should drop M. - expect_identical(comp_sf_coord(df_xym)[, c("x", "y")], data.frame(x = 1, y = 2)) + expect_identical(comp_sf_coord(df_xym)[, c("x", "y")], data.frame(x = 0, y = 0)) }) From f50d88334653be34d486c66339aa6b3c3e43649c Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Mon, 20 Aug 2018 21:24:33 +0900 Subject: [PATCH 20/25] Fix a typo in doc --- R/sf.R | 2 +- man/ggsf.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/sf.R b/R/sf.R index c4d0af6625..3d6e5e29f4 100644 --- a/R/sf.R +++ b/R/sf.R @@ -6,7 +6,7 @@ #' an unusual geom because it will draw different geometric objects depending #' on what simple features are present in the data: you can get points, lines, #' or polygons. -#' For texts and labels, you can use `geom_sf_text` and `geom_sf_text`. +#' For texts and labels, you can use `geom_sf_text` and `geom_sf_label`. #' #' @section Geometry aesthetic: #' `geom_sf` uses a unique aesthetic: `geometry`, giving an diff --git a/man/ggsf.Rd b/man/ggsf.Rd index e7ea018d67..f95c199280 100644 --- a/man/ggsf.Rd +++ b/man/ggsf.Rd @@ -135,7 +135,7 @@ uses \code{stat_sf} and adds \code{coord_sf} for you. \code{geom_sf} is an unusual geom because it will draw different geometric objects depending on what simple features are present in the data: you can get points, lines, or polygons. -For texts and labels, you can use \code{geom_sf_text} and \code{geom_sf_text}. +For texts and labels, you can use \code{geom_sf_text} and \code{geom_sf_label}. } \section{Geometry aesthetic}{ From 0a6fe1af987b61e3a9b5ad7bc1699bff9784c25c Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Mon, 20 Aug 2018 21:44:48 +0900 Subject: [PATCH 21/25] Add reference images for vdiffr tests --- .../geom-sf/labels-for-north-carolina.svg | 59 +++++++++++++++++++ .../figs/geom-sf/texts-for-north-carolina.svg | 56 ++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 tests/figs/geom-sf/labels-for-north-carolina.svg create mode 100644 tests/figs/geom-sf/texts-for-north-carolina.svg diff --git a/tests/figs/geom-sf/labels-for-north-carolina.svg b/tests/figs/geom-sf/labels-for-north-carolina.svg new file mode 100644 index 0000000000..c1643d8b1a --- /dev/null +++ b/tests/figs/geom-sf/labels-for-north-carolina.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + +Ashe + +Alleghany + +Surry + + + + + + +4354000 +4356000 +4358000 +4360000 +4362000 +4364000 +4366000 + + + + + + + + + + + +-9075000 +-9050000 +-9025000 +-9000000 +x +y +Labels for North Carolina + diff --git a/tests/figs/geom-sf/texts-for-north-carolina.svg b/tests/figs/geom-sf/texts-for-north-carolina.svg new file mode 100644 index 0000000000..633254ec86 --- /dev/null +++ b/tests/figs/geom-sf/texts-for-north-carolina.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + +Ashe +Alleghany +Surry + + + + + + +4354000 +4356000 +4358000 +4360000 +4362000 +4364000 +4366000 + + + + + + + + + + + +-9075000 +-9050000 +-9025000 +-9000000 +x +y +Texts for North Carolina + From 2a677049a1e30b1d99148190c98b555261463d27 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Mon, 20 Aug 2018 22:34:12 +0900 Subject: [PATCH 22/25] Disable stat-sf-coordinates test --- tests/testthat/test-geom-sf.R | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/testthat/test-geom-sf.R b/tests/testthat/test-geom-sf.R index d0d3215ed2..a8959e4118 100644 --- a/tests/testthat/test-geom-sf.R +++ b/tests/testthat/test-geom-sf.R @@ -29,6 +29,15 @@ test_that("geom_sf_text() and geom_sf_label() draws correctly", { # In order to avoid warning, trnasform to a projected coordinate system nc_3857 <- sf::st_transform(nc, "+init=epsg:3857") + # Perform minimal tests as long as vdiffr tests are disabled + plot <- ggplot() + geom_sf_text(data = nc_3857[1:3, ], aes(label = NAME)) + expect_error(regexp = NA, ggplot_build(plot)) + + plot <- ggplot() + geom_sf_label(data = nc_3857[1:3, ], aes(label = NAME)) + expect_error(regexp = NA, ggplot_build(plot)) + + skip("sf tests are currently unstable") + expect_doppelganger("Texts for North Carolina", ggplot() + geom_sf_text(data = nc_3857[1:3, ], aes(label = NAME)) ) From cab2b4f9af845700362e66a2bcc3e979a7b8161e Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Thu, 23 Aug 2018 08:55:16 +0900 Subject: [PATCH 23/25] Fix "texts" to "text", and move seealso --- R/sf.R | 4 ++-- R/stat-sf-coordinates.R | 6 +++--- man/ggsf.Rd | 2 +- man/stat_sf_coordinates.Rd | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/R/sf.R b/R/sf.R index 3d6e5e29f4..ef35f0230e 100644 --- a/R/sf.R +++ b/R/sf.R @@ -6,7 +6,7 @@ #' an unusual geom because it will draw different geometric objects depending #' on what simple features are present in the data: you can get points, lines, #' or polygons. -#' For texts and labels, you can use `geom_sf_text` and `geom_sf_label`. +#' For text and labels, you can use `geom_sf_text()` and `geom_sf_label()`. #' #' @section Geometry aesthetic: #' `geom_sf` uses a unique aesthetic: `geometry`, giving an @@ -33,6 +33,7 @@ #' #' You can also set this to one of "polygon", "line", and "point" to #' override the default legend. +#' @seealso [stat_sf_coordinates()] #' @examples #' if (requireNamespace("sf", quietly = TRUE)) { #' nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) @@ -259,7 +260,6 @@ geom_sf <- function(mapping = aes(), data = NULL, stat = "sf", #' @rdname ggsf #' @inheritParams geom_label #' @inheritParams stat_sf_coordinates -#' @seealso [stat_sf_coordinates()] geom_sf_label <- function(mapping = aes(), data = NULL, stat = "sf_coordinates", position = "identity", ..., diff --git a/R/stat-sf-coordinates.R b/R/stat-sf-coordinates.R index 8102785fb4..4893829d6f 100644 --- a/R/stat-sf-coordinates.R +++ b/R/stat-sf-coordinates.R @@ -2,13 +2,13 @@ #' #' `stat_sf_coordinates()` extracts the coordinates from 'sf' objects and #' summarises them to one pair of coordinates (x and y) per geometry. This is -#' convenient when you draw an sf object as geoms like texts and labels (so +#' convenient when you draw an sf object as geoms like text and labels (so #' [geom_sf_text()] and [geom_sf_label()] relies on this). #' #' @rdname stat_sf_coordinates #' @details #' coordinates of an `sf` object can be retrieved by `sf::st_coordinates()`. -#' But, we cannot simply use `sf::st_coordinates()` because, whereas texts and +#' But, we cannot simply use `sf::st_coordinates()` because, whereas text and #' labels require exactly one coordinate per geometry, it returns multiple ones #' for a polygon or a line. Thus, these two steps are needed: #' @@ -19,7 +19,7 @@ #' For the first step, you can use an arbitrary function via `fun.geometry`. #' By default, `function(x) sf::st_point_on_surface(sf::st_zm(x))` is used; #' `sf::st_point_on_surface()` seems more appropriate than `sf::st_centroid()` -#' since lables and texts usually are intended to be put within the polygon or +#' since lables and text usually are intended to be put within the polygon or #' the line. `sf::st_zm()` is needed to drop Z and M dimension beforehand, #' otherwise `sf::st_point_on_surface()` may fail when the geometries have M #' dimension. diff --git a/man/ggsf.Rd b/man/ggsf.Rd index f95c199280..36a556c14b 100644 --- a/man/ggsf.Rd +++ b/man/ggsf.Rd @@ -135,7 +135,7 @@ uses \code{stat_sf} and adds \code{coord_sf} for you. \code{geom_sf} is an unusual geom because it will draw different geometric objects depending on what simple features are present in the data: you can get points, lines, or polygons. -For texts and labels, you can use \code{geom_sf_text} and \code{geom_sf_label}. +For text and labels, you can use \code{geom_sf_text()} and \code{geom_sf_label()}. } \section{Geometry aesthetic}{ diff --git a/man/stat_sf_coordinates.Rd b/man/stat_sf_coordinates.Rd index 9ad7a1ade8..e658ffefb0 100644 --- a/man/stat_sf_coordinates.Rd +++ b/man/stat_sf_coordinates.Rd @@ -63,12 +63,12 @@ to the paired geom/stat.} \description{ \code{stat_sf_coordinates()} extracts the coordinates from 'sf' objects and summarises them to one pair of coordinates (x and y) per geometry. This is -convenient when you draw an sf object as geoms like texts and labels (so +convenient when you draw an sf object as geoms like text and labels (so \code{\link[=geom_sf_text]{geom_sf_text()}} and \code{\link[=geom_sf_label]{geom_sf_label()}} relies on this). } \details{ coordinates of an \code{sf} object can be retrieved by \code{sf::st_coordinates()}. -But, we cannot simply use \code{sf::st_coordinates()} because, whereas texts and +But, we cannot simply use \code{sf::st_coordinates()} because, whereas text and labels require exactly one coordinate per geometry, it returns multiple ones for a polygon or a line. Thus, these two steps are needed: \enumerate{ @@ -80,7 +80,7 @@ or \code{sf::st_point_on_surface()}. For the first step, you can use an arbitrary function via \code{fun.geometry}. By default, \code{function(x) sf::st_point_on_surface(sf::st_zm(x))} is used; \code{sf::st_point_on_surface()} seems more appropriate than \code{sf::st_centroid()} -since lables and texts usually are intended to be put within the polygon or +since lables and text usually are intended to be put within the polygon or the line. \code{sf::st_zm()} is needed to drop Z and M dimension beforehand, otherwise \code{sf::st_point_on_surface()} may fail when the geometries have M dimension. From e60df8e26809090ba8d28a0f8fa54a6d4bf0ab6e Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Thu, 23 Aug 2018 09:03:53 +0900 Subject: [PATCH 24/25] Add () to functions in ggsf.Rd for consitency --- R/sf.R | 10 +++++----- man/ggsf.Rd | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/R/sf.R b/R/sf.R index ef35f0230e..ff3f2100ac 100644 --- a/R/sf.R +++ b/R/sf.R @@ -1,19 +1,19 @@ #' Visualise sf objects #' #' This set of geom, stat, and coord are used to visualise simple feature (sf) -#' objects. For simple plots, you will only need `geom_sf` as it -#' uses `stat_sf` and adds `coord_sf` for you. `geom_sf` is +#' objects. For simple plots, you will only need `geom_sf()` as it +#' uses `stat_sf()` and adds `coord_sf()` for you. `geom_sf()` is #' an unusual geom because it will draw different geometric objects depending #' on what simple features are present in the data: you can get points, lines, #' or polygons. #' For text and labels, you can use `geom_sf_text()` and `geom_sf_label()`. #' #' @section Geometry aesthetic: -#' `geom_sf` uses a unique aesthetic: `geometry`, giving an +#' `geom_sf()` uses a unique aesthetic: `geometry`, giving an #' column of class `sfc` containing simple features data. There #' are three ways to supply the `geometry` aesthetic: #' -#' - Do nothing: by default `geom_sf` assumes it is stored in +#' - Do nothing: by default `geom_sf()` assumes it is stored in #' the `geometry` column. #' - Explicitly pass an `sf` object to the `data` argument. #' This will use the primary geometry column, no matter what it's called. @@ -24,7 +24,7 @@ #' #' @section CRS: #' `coord_sf()` ensures that all layers use a common CRS. You can -#' either specify it using the `CRS` param, or `coord_sf` will +#' either specify it using the `CRS` param, or `coord_sf()` will #' take it from the first layer that defines a CRS. #' #' @param show.legend logical. Should this layer be included in the legends? diff --git a/man/ggsf.Rd b/man/ggsf.Rd index 36a556c14b..9bb16eb742 100644 --- a/man/ggsf.Rd +++ b/man/ggsf.Rd @@ -130,8 +130,8 @@ is suppressed.} } \description{ This set of geom, stat, and coord are used to visualise simple feature (sf) -objects. For simple plots, you will only need \code{geom_sf} as it -uses \code{stat_sf} and adds \code{coord_sf} for you. \code{geom_sf} is +objects. For simple plots, you will only need \code{geom_sf()} as it +uses \code{stat_sf()} and adds \code{coord_sf()} for you. \code{geom_sf()} is an unusual geom because it will draw different geometric objects depending on what simple features are present in the data: you can get points, lines, or polygons. @@ -139,11 +139,11 @@ For text and labels, you can use \code{geom_sf_text()} and \code{geom_sf_label() } \section{Geometry aesthetic}{ -\code{geom_sf} uses a unique aesthetic: \code{geometry}, giving an +\code{geom_sf()} uses a unique aesthetic: \code{geometry}, giving an column of class \code{sfc} containing simple features data. There are three ways to supply the \code{geometry} aesthetic: \itemize{ -\item Do nothing: by default \code{geom_sf} assumes it is stored in +\item Do nothing: by default \code{geom_sf()} assumes it is stored in the \code{geometry} column. \item Explicitly pass an \code{sf} object to the \code{data} argument. This will use the primary geometry column, no matter what it's called. @@ -157,7 +157,7 @@ the plot. \section{CRS}{ \code{coord_sf()} ensures that all layers use a common CRS. You can -either specify it using the \code{CRS} param, or \code{coord_sf} will +either specify it using the \code{CRS} param, or \code{coord_sf()} will take it from the first layer that defines a CRS. } From 5cd4d64f7f3cf2c58c23706f376526eff792f8cd Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Thu, 23 Aug 2018 10:44:21 +0900 Subject: [PATCH 25/25] Add a news bullet --- NEWS.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS.md b/NEWS.md index 6b825cd86c..f9cb9db45a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -35,6 +35,11 @@ is now always internally converted to "colour", even when part of a longer aesthetic name (e.g., `point_color`) (@clauswilke, #2649). +* New `geom_sf_label()` and `geom_sf_text()` draw labels and text on sf objects. + Under the hood, new `stat_sf_coordinates()` calculates the x and y from the + coordinates of the geometries. You can customize the calculation method via + `fun.geometry` argument (@yutannihilation, #2761). + # ggplot2 3.0.0 ## Breaking changes