Browse Source

coord_proj

tags/v0.1.1
hrbrmstr 9 years ago
parent
commit
a05cf82509
  1. 4
      DESCRIPTION
  2. 25
      NAMESPACE
  3. 209
      R/coord_proj.r
  4. 6
      R/ggalt-package.r
  5. 47
      R/grob_absolute.r
  6. 115
      R/guide_axis.r
  7. 67
      R/utils.r
  8. 21
      README.Rmd
  9. 29
      README.md
  10. BIN
      README_figs/README-unnamed-chunk-4-15.png
  11. BIN
      README_figs/README-unnamed-chunk-4-16.png
  12. BIN
      README_figs/README-unnamed-chunk-4-17.png
  13. 17
      man/absoluteGrob.Rd
  14. 39
      man/coord_proj.Rd

4
DESCRIPTION

@ -1,6 +1,6 @@
Package: ggalt
Title: Alternate/Extra 'Geoms', 'Stats' and 'Coords' for 'ggplot2'
Version: 0.0.2.9005
Version: 0.0.3.9000
Authors@R: c(person("Bob", "Rudis", email = "bob@rudis.net", role = c("aut", "cre")))
Description: A package containing additional/alternate 'geoms', 'coords' and 'stats'
for use with the revamped (late 2015) version of ggplot2.
@ -9,4 +9,4 @@ License: AGPL + file LICENSE
LazyData: true
Suggests: testthat
Encoding: UTF-8
Imports: graphics, grDevices, dplyr, KernSmooth
Imports: graphics, grDevices, dplyr, KernSmooth, proj4, scales, grid, gtable

25
NAMESPACE

@ -1,11 +1,18 @@
# Generated by roxygen2 (4.1.1): do not edit by hand
S3method(grid.draw,absoluteGrob)
S3method(grobHeight,absoluteGrob)
S3method(grobWidth,absoluteGrob)
S3method(grobX,absoluteGrob)
S3method(grobY,absoluteGrob)
export(CoordProj)
export(GeomBkde)
export(GeomBkde2d)
export(GeomXspline)
export(StatBkde)
export(StatBkde2d)
export(StatXspline)
export(coord_proj)
export(geom_bkde)
export(geom_bkde2d)
export(geom_xspline)
@ -17,3 +24,21 @@ import(dplyr)
import(ggplot2)
import(grDevices)
import(graphics)
import(proj4)
importFrom(grid,gList)
importFrom(grid,gTree)
importFrom(grid,grid.draw)
importFrom(grid,grobHeight)
importFrom(grid,grobName)
importFrom(grid,grobTree)
importFrom(grid,grobWidth)
importFrom(grid,grobX)
importFrom(grid,grobY)
importFrom(grid,unit.c)
importFrom(grid,viewport)
importFrom(gtable,gtable_col)
importFrom(gtable,gtable_height)
importFrom(gtable,gtable_row)
importFrom(gtable,gtable_width)
importFrom(scales,expand_range)
importFrom(scales,rescale)

209
R/coord_proj.r

@ -0,0 +1,209 @@
#' Like \code{coord_map} only better :-)
#'
#' The representation of a portion of the earth, which is approximately
#' spherical, onto a flat 2D plane requires a projection. This is what
#' \code{coord_proj} does, using the \link[proj4]{project()} function from
#' the \code{proj4} package.
#'
#' @param proj projection definition
#' @param inverse if \code{TRUE} inverse projection is performed (from a
#' cartographic projection into lat/long), otherwise projects from
#' lat/long into a cartographic projection.
#' @param degrees if \code{TRUE} then the lat/long data is assumed to be in
#' degrees, otherwise in radians
#' @param ellps.default default ellipsoid that will be added if no datum or
#' ellipsoid parameter is specified in proj. Older versions of PROJ.4
#' didn't require a datum (and used sphere by default), but 4.5.0 and
#' higher always require a datum or an ellipsoid. Set to \code{NA} if no
#' datum should be added to proj (e.g. if you specify an ellipsoid
#' directly).
#' @param xlim manually specific x limits (in degrees of longitude)
#' @param ylim manually specific y limits (in degrees of latitude)
#' @export
coord_proj <- function(proj="+proj=robin +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
inverse = FALSE, degrees = TRUE,
ellps.default="sphere", xlim = NULL, ylim = NULL) {
ggproto(NULL, CoordProj,
proj = proj,
inverse = inverse,
ellps.default = ellps.default,
degrees = degrees,
limits = list(x = xlim, y = ylim),
params= list()
)
}
#' @export
CoordProj <- ggproto("CoordProj", Coord,
transform = function(self, data, scale_details) {
trans <- project4(self, data$x, data$y)
out <- cunion(trans[c("x", "y")], data)
out$x <- rescale(out$x, 0:1, scale_details$x.proj)
out$y <- rescale(out$y, 0:1, scale_details$y.proj)
out
},
distance = function(x, y, scale_details) {
max_dist <- dist_central_angle(scale_details$x.range, scale_details$y.range)
dist_central_angle(x, y) / max_dist
},
aspect = function(ranges) {
diff(ranges$y.proj) / diff(ranges$x.proj)
},
train = function(self, scale_details) {
# range in scale
ranges <- list()
for (n in c("x", "y")) {
scale <- scale_details[[n]]
limits <- self$limits[[n]]
if (is.null(limits)) {
range <- scale$dimension(expand_default(scale))
} else {
range <- range(scale_transform(scale, limits))
}
ranges[[n]] <- range
}
orientation <- self$orientation %||% c(90, 0, mean(ranges$x))
# Increase chances of creating valid boundary region
grid <- expand.grid(
x = seq(ranges$x[1], ranges$x[2], length.out = 50),
y = seq(ranges$y[1], ranges$y[2], length.out = 50)
)
ret <- list(x = list(), y = list())
# range in map
proj <- project4(self, grid$x, grid$y)$range
ret$x$proj <- proj[1:2]
ret$y$proj <- proj[3:4]
for (n in c("x", "y")) {
out <- scale_details[[n]]$break_info(ranges[[n]])
ret[[n]]$range <- out$range
ret[[n]]$major <- out$major_source
ret[[n]]$minor <- out$minor_source
ret[[n]]$labels <- out$labels
}
details <- list(
orientation = orientation,
x.range = ret$x$range, y.range = ret$y$range,
x.proj = ret$x$proj, y.proj = ret$y$proj,
x.major = ret$x$major, x.minor = ret$x$minor, x.labels = ret$x$labels,
y.major = ret$y$major, y.minor = ret$y$minor, y.labels = ret$y$labels
)
details
},
render_bg = function(self, scale_details, theme) {
xrange <- expand_range(scale_details$x.range, 0.2)
yrange <- expand_range(scale_details$y.range, 0.2)
# Limit ranges so that lines don't wrap around globe
xmid <- mean(xrange)
ymid <- mean(yrange)
xrange[xrange < xmid - 180] <- xmid - 180
xrange[xrange > xmid + 180] <- xmid + 180
yrange[yrange < ymid - 90] <- ymid - 90
yrange[yrange > ymid + 90] <- ymid + 90
xgrid <- with(scale_details, expand.grid(
y = c(seq(yrange[1], yrange[2], length.out = 50), NA),
x = x.major
))
ygrid <- with(scale_details, expand.grid(
x = c(seq(xrange[1], xrange[2], length.out = 50), NA),
y = y.major
))
xlines <- self$transform(xgrid, scale_details)
ylines <- self$transform(ygrid, scale_details)
if (nrow(xlines) > 0) {
grob.xlines <- element_render(
theme, "panel.grid.major.x",
xlines$x, xlines$y, default.units = "native"
)
} else {
grob.xlines <- zeroGrob()
}
if (nrow(ylines) > 0) {
grob.ylines <- element_render(
theme, "panel.grid.major.y",
ylines$x, ylines$y, default.units = "native"
)
} else {
grob.ylines <- zeroGrob()
}
ggname("grill", grobTree(
element_render(theme, "panel.background"),
grob.xlines, grob.ylines
))
},
render_axis_h = function(self, scale_details, theme) {
if (is.null(scale_details$x.major)) return(zeroGrob())
x_intercept <- with(scale_details, data.frame(
x = x.major,
y = y.range[1]
))
pos <- self$transform(x_intercept, scale_details)
guide_axis(pos$x, scale_details$x.labels, "bottom", theme)
},
render_axis_v = function(self, scale_details, theme) {
if (is.null(scale_details$y.major)) return(zeroGrob())
x_intercept <- with(scale_details, data.frame(
x = x.range[1],
y = y.major
))
pos <- self$transform(x_intercept, scale_details)
guide_axis(pos$y, scale_details$y.labels, "left", theme)
}
)
project4 <- function(coord, x, y) {
df <- data.frame(x=x, y=y)
# map extremes cause issues with projections both with proj4 &
# spTransform. this compensates for them.
df$x <- ifelse(df$x <= -180, -179.999999999, df$x)
df$x <- ifelse(df$x >= 180, 179.999999999, df$x)
df$y <- ifelse(df$y <= -90, -89.999999999, df$y)
df$y <- ifelse(df$y >= 90, 89.999999999, df$y)
suppressWarnings({
res <- proj4::project(list(x=df$x, y=df$y),
proj = coord$proj,
inverse = coord$inverse,
degrees = coord$degrees,
ellps.default = coord$ellps.default)
res$range <- c(range(res$x, na.rm=TRUE), range(res$y, na.rm=TRUE))
res$error <- 0
res
})
}

6
R/ggalt-package.r

@ -6,5 +6,9 @@
#' @name ggalt
#' @docType package
#' @author Bob Rudis (@@hrbrmstr)
#' @import ggplot2 graphics grDevices dplyr KernSmooth
#' @import ggplot2 graphics grDevices dplyr KernSmooth proj4
#' @importFrom scales rescale expand_range
#' @importFrom grid grobName grobTree unit.c grobHeight grobWidth viewport
#' grid.draw grobX grobY gTree gList
#' @importFrom gtable gtable_col gtable_height gtable_width gtable_row gtable_col
NULL

47
R/grob_absolute.r

@ -0,0 +1,47 @@
#' Absolute grob
#'
#' This grob has fixed dimensions and position.
#'
#' It's still experimental
#'
#' @keywords internal
absoluteGrob <- function(grob, width = NULL, height = NULL,
xmin = NULL, ymin = NULL, vp = NULL) {
gTree(
children = grob,
width = width, height = height,
xmin = xmin, ymin = ymin,
vp = vp, cl = "absoluteGrob"
)
}
#' @export
#' @method grobHeight absoluteGrob
grobHeight.absoluteGrob <- function(x) {
x$height %||% grobHeight(x$children)
}
#' @export
#' @method grobWidth absoluteGrob
grobWidth.absoluteGrob <- function(x) {
x$width %||% grobWidth(x$children)
}
#' @export
#' @method grobX absoluteGrob
grobX.absoluteGrob <- function(x, theta) {
if (!is.null(x$xmin) && theta == "west") return(x$xmin)
grobX(x$children, theta)
}
#' @export
#' @method grobY absoluteGrob
grobY.absoluteGrob <- function(x, theta) {
if (!is.null(x$ymin) && theta == "south") return(x$ymin)
grobY(x$children, theta)
}
#' @export
#' @method grid.draw absoluteGrob
grid.draw.absoluteGrob <- function(x, recording = TRUE) {
NextMethod()
}

115
R/guide_axis.r

@ -0,0 +1,115 @@
# Grob for axes
#
# @param position of ticks
# @param labels at ticks
# @param position of axis (top, bottom, left or right)
# @param range of data values
guide_axis <- function(at, labels, position = "right", theme) {
if (length(at) == 0)
return(zeroGrob())
at <- unit(at, "native")
position <- match.arg(position, c("top", "bottom", "right", "left"))
zero <- unit(0, "npc")
one <- unit(1, "npc")
label_render <- switch(position,
top = , bottom = "axis.text.x",
left = , right = "axis.text.y"
)
label_x <- switch(position,
top = ,
bottom = at,
right = theme$axis.ticks.length,
left = one - theme$axis.ticks.length
)
label_y <- switch(position,
top = theme$axis.ticks.length,
bottom = one - theme$axis.ticks.length,
right = ,
left = at
)
if (is.list(labels)) {
if (any(sapply(labels, is.language))) {
labels <- do.call(expression, labels)
} else {
labels <- unlist(labels)
}
}
labels <- switch(position,
top = ,
bottom = element_render(theme, label_render, labels, x = label_x, expand_y = TRUE),
right = ,
left = element_render(theme, label_render, labels, y = label_y, expand_x = TRUE))
line <- switch(position,
top = element_render(theme, "axis.line.x", c(0, 1), c(0, 0), id.lengths = 2),
bottom = element_render(theme, "axis.line.x", c(0, 1), c(1, 1), id.lengths = 2),
right = element_render(theme, "axis.line.y", c(0, 0), c(0, 1), id.lengths = 2),
left = element_render(theme, "axis.line.y", c(1, 1), c(0, 1), id.lengths = 2)
)
nticks <- length(at)
ticks <- switch(position,
top = element_render(theme, "axis.ticks.x",
x = rep(at, each = 2),
y = rep(unit.c(zero, theme$axis.ticks.length), nticks),
id.lengths = rep(2, nticks)),
bottom = element_render(theme, "axis.ticks.x",
x = rep(at, each = 2),
y = rep(unit.c(one - theme$axis.ticks.length, one), nticks),
id.lengths = rep(2, nticks)),
right = element_render(theme, "axis.ticks.y",
x = rep(unit.c(zero, theme$axis.ticks.length), nticks),
y = rep(at, each = 2),
id.lengths = rep(2, nticks)),
left = element_render(theme, "axis.ticks.y",
x = rep(unit.c(one - theme$axis.ticks.length, one), nticks),
y = rep(at, each = 2),
id.lengths = rep(2, nticks))
)
# Create the gtable for the ticks + labels
gt <- switch(position,
top = gtable_col("axis",
grobs = list(labels, ticks),
width = one,
heights = unit.c(grobHeight(labels), theme$axis.ticks.length)
),
bottom = gtable_col("axis",
grobs = list(ticks, labels),
width = one,
heights = unit.c(theme$axis.ticks.length, grobHeight(labels))
),
right = gtable_row("axis",
grobs = list(ticks, labels),
widths = unit.c(theme$axis.ticks.length, grobWidth(labels)),
height = one
),
left = gtable_row("axis",
grobs = list(labels, ticks),
widths = unit.c(grobWidth(labels), theme$axis.ticks.length),
height = one
)
)
# Viewport for justifying the axis grob
justvp <- switch(position,
top = viewport(y = 0, just = "bottom", height = gtable_height(gt)),
bottom = viewport(y = 1, just = "top", height = gtable_height(gt)),
right = viewport(x = 0, just = "left", width = gtable_width(gt)),
left = viewport(x = 1, just = "right", width = gtable_width(gt))
)
absoluteGrob(
gList(line, gt),
width = gtable_width(gt),
height = gtable_height(gt),
vp = justvp
)
}

67
R/utils.r

@ -0,0 +1,67 @@
"%||%" <- function(a, b) {
if (!is.null(a)) a else b
}
"%|W|%" <- function(a, b) {
if (!is.waive(a)) a else b
}
is.waive <- function(x) inherits(x, "waiver")
# Compute central angle between two points.
# Multiple by radius of sphere to get great circle distance
# @arguments longitude
# @arguments latitude
dist_central_angle <- function(lon, lat) {
# Convert to radians
lat <- lat * pi / 180
lon <- lon * pi / 180
hav <- function(x) sin(x / 2) ^ 2
ahav <- function(x) 2 * asin(x)
n <- length(lat)
ahav(sqrt(hav(diff(lat)) + cos(lat[-n]) * cos(lat[-1]) * hav(diff(lon))))
}
expand_default <- function(scale, discrete = c(0, 0.6), continuous = c(0.05, 0)) {
scale$expand %|W|% if (scale$is_discrete()) discrete else continuous
}
# Col union
# Form the union of columns in a and b. If there are columns of the same name in both a and b, take the column from a.
#
# @param data frame a
# @param data frame b
# @keyword internal
cunion <- function(a, b) {
if (length(a) == 0) return(b)
if (length(b) == 0) return(a)
cbind(a, b[setdiff(names(b), names(a))])
}
# Given a theme object and element name, return a grob for the element
element_render <- function(theme, element, ..., name = NULL) {
# Get the element from the theme, calculating inheritance
el <- calc_element(element, theme)
if (is.null(el)) {
message("Theme element ", element, " missing")
return(zeroGrob())
}
ggname(paste(element, name, sep = "."), element_grob(el, ...))
}
# Name ggplot grid object
# Convenience function to name grid objects
#
# @keyword internal
ggname <- function(prefix, grob) {
grob$name <- grobName(grob, prefix)
grob
}

21
README.Rmd

@ -15,12 +15,11 @@ knitr::opts_chunk$set(
)
```
`ggalt` : Extra Geoms, Stats and Coords for `ggplot2`
`ggalt` : Alternate/Extra 'Geoms', 'Stats' and 'Coords' for 'ggplot2'
A package containing additional geoms, coords and stats for the revamped (late 2015) version
of ggplot2.
A package containing additional/alternate 'geoms', 'coords' and 'stats' for use with the revamped (late 2015) version of ggplot2.
The first three forays into this brave, new `ggplot2` world are _splines_! and being able to use the (much better) `KernSmooth::bkde` and `KernSmooth::bkde2D` for density plots.
The first three forays into this brave, new `ggplot2` world are _splines_! and being able to use the (much better) `KernSmooth::bkde` and `KernSmooth::bkde2D` for density plots and an initial port of the (still needing work) `coord_proj`.
*NOTE*
@ -28,6 +27,7 @@ Until the new `ggplot2` version is on CRAN, you'll need to install it from githu
The following functions are implemented:
- `coord_proj` : Like `coord_map` only better `:-)`
- `geom_xspline` : Connect control points/observations with an X-spline
- `stat_xspline` : Connect control points/observations with an X-spline
- `geom_bkde` : Display a smooth density estimate (uses `KernSmooth::bkde`)
@ -37,6 +37,7 @@ The following functions are implemented:
### News
- Version 0.0.3.9000 released - `coord_proj`! (requires my github copy of ggplot2 for now)
- Version 0.0.2.9005 released - cleanup before blog post
- Version 0.0.2.9002 released - working 2D density plots
- Version 0.0.2.9000 released
@ -142,6 +143,18 @@ ggplot(geyser_dat, aes(x, y)) +
ggplot(geyser_dat, aes(x, y)) +
geom_point() +
stat_bkde2d(bandwidth=c(0.7, 7))
### coord_proj LIVES! (still needs work)
world <- map_data("world")
world <- world[world$region != "Antarctica",]
gg <- ggplot()
gg <- gg + geom_map(data=world, map=world,
aes(x=long, y=lat, map_id=region))
gg <- gg + coord_proj("+proj=wintri")
gg
```
### Test Results

29
README.md

@ -1,9 +1,9 @@
<!-- README.md is generated from README.Rmd. Please edit that file -->
`ggalt` : Extra Geoms, Stats and Coords for `ggplot2`
`ggalt` : Alternate/Extra 'Geoms', 'Stats' and 'Coords' for 'ggplot2'
A package containing additional geoms, coords and stats for the revamped (late 2015) version of ggplot2.
A package containing additional/alternate 'geoms', 'coords' and 'stats' for use with the revamped (late 2015) version of ggplot2.
The first three forays into this brave, new `ggplot2` world are *splines*! and being able to use the (much better) `KernSmooth::bkde` and `KernSmooth::bkde2D` for density plots.
The first three forays into this brave, new `ggplot2` world are *splines*! and being able to use the (much better) `KernSmooth::bkde` and `KernSmooth::bkde2D` for density plots and an initial port of the (still needing work) `coord_proj`.
*NOTE*
@ -11,6 +11,7 @@ Until the new `ggplot2` version is on CRAN, you'll need to install it from githu
The following functions are implemented:
- `coord_proj` : Like `coord_map` only better `:-)`
- `geom_xspline` : Connect control points/observations with an X-spline
- `stat_xspline` : Connect control points/observations with an X-spline
- `geom_bkde` : Display a smooth density estimate (uses `KernSmooth::bkde`)
@ -20,6 +21,8 @@ The following functions are implemented:
### News
- Version 0.0.3.9000 released - `coord_proj`! (requires my github copy of ggplot2 for now)
- Version 0.0.2.9005 released - cleanup before blog post
- Version 0.0.2.9002 released - working 2D density plots
- Version 0.0.2.9000 released
@ -39,7 +42,7 @@ library(ggalt)
# current verison
packageVersion("ggalt")
#> [1] '0.0.2.9004'
#> [1] '0.0.3.9000'
set.seed(1492)
dat <- data.frame(x=c(1:10, 1:10, 1:10),
@ -207,6 +210,22 @@ ggplot(geyser_dat, aes(x, y)) +
<img src="README_figs/README-unnamed-chunk-4-16.png" title="" alt="" width="672" />
``` r
### coord_proj LIVES! (still needs work)
world <- map_data("world")
world <- world[world$region != "Antarctica",]
gg <- ggplot()
gg <- gg + geom_map(data=world, map=world,
aes(x=long, y=lat, map_id=region))
gg <- gg + coord_proj("+proj=wintri")
gg
```
<img src="README_figs/README-unnamed-chunk-4-17.png" title="" alt="" width="672" />
### Test Results
``` r
@ -214,7 +233,7 @@ library(ggalt)
library(testthat)
date()
#> [1] "Fri Sep 11 16:19:52 2015"
#> [1] "Fri Sep 11 18:14:16 2015"
test_dir("tests/")
#> testthat results ========================================================================================================

BIN
README_figs/README-unnamed-chunk-4-15.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 272 KiB

BIN
README_figs/README-unnamed-chunk-4-16.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 272 KiB

BIN
README_figs/README-unnamed-chunk-4-17.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

17
man/absoluteGrob.Rd

@ -0,0 +1,17 @@
% Generated by roxygen2 (4.1.1): do not edit by hand
% Please edit documentation in R/grob_absolute.r
\name{absoluteGrob}
\alias{absoluteGrob}
\title{Absolute grob}
\usage{
absoluteGrob(grob, width = NULL, height = NULL, xmin = NULL,
ymin = NULL, vp = NULL)
}
\description{
This grob has fixed dimensions and position.
}
\details{
It's still experimental
}
\keyword{internal}

39
man/coord_proj.Rd

@ -0,0 +1,39 @@
% Generated by roxygen2 (4.1.1): do not edit by hand
% Please edit documentation in R/coord_proj.r
\name{coord_proj}
\alias{coord_proj}
\title{Like \code{coord_map} only better :-)}
\usage{
coord_proj(proj = "+proj=robin +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
inverse = FALSE, degrees = TRUE, ellps.default = "sphere",
xlim = NULL, ylim = NULL)
}
\arguments{
\item{proj}{projection definition}
\item{inverse}{if \code{TRUE} inverse projection is performed (from a
cartographic projection into lat/long), otherwise projects from
lat/long into a cartographic projection.}
\item{degrees}{if \code{TRUE} then the lat/long data is assumed to be in
degrees, otherwise in radians}
\item{ellps.default}{default ellipsoid that will be added if no datum or
ellipsoid parameter is specified in proj. Older versions of PROJ.4
didn't require a datum (and used sphere by default), but 4.5.0 and
higher always require a datum or an ellipsoid. Set to \code{NA} if no
datum should be added to proj (e.g. if you specify an ellipsoid
directly).}
\item{xlim}{manually specific x limits (in degrees of longitude)}
\item{ylim}{manually specific y limits (in degrees of latitude)}
}
\description{
The representation of a portion of the earth, which is approximately
spherical, onto a flat 2D plane requires a projection. This is what
\code{coord_proj} does, using the \link[proj4]{project()} function from
the \code{proj4} package.
}
Loading…
Cancel
Save