Skip to content

Commit

Permalink
facets to ggproto (#1633)
Browse files Browse the repository at this point in the history
* Rewrite facet and panel functionality in ggproto. facet is now Facet, while panel is Layout.

* Define an extension interface to facetting. Show examples in the vignette

* Move current facet functions to new implementation

* Make it possible to place axes at either side

* Add support for secondary axes that are linear transformations of the primary scale
  • Loading branch information
thomasp85 committed Sep 17, 2016
1 parent 6530a2b commit 06037a9
Show file tree
Hide file tree
Showing 64 changed files with 2,498 additions and 1,429 deletions.
Binary file added .DS_Store
Binary file not shown.
11 changes: 5 additions & 6 deletions DESCRIPTION
Expand Up @@ -24,7 +24,8 @@ Imports:
reshape2,
scales (>= 0.3.0),
stats,
tibble
tibble,
lazyeval
Suggests:
covr,
ggplot2movies,
Expand Down Expand Up @@ -69,6 +70,7 @@ Collate:
'annotation-raster.r'
'annotation.r'
'autoplot.r'
'axis-secondary.R'
'bench.r'
'bin.R'
'coord-.r'
Expand All @@ -83,11 +85,7 @@ Collate:
'data.R'
'facet-.r'
'facet-grid-.r'
'facet-labels.r'
'facet-layout.r'
'facet-locate.r'
'facet-null.r'
'facet-viewports.r'
'facet-wrap.r'
'fortify-lm.r'
'fortify-map.r'
Expand Down Expand Up @@ -143,11 +141,12 @@ Collate:
'guides-axis.r'
'guides-grid.r'
'hexbin.R'
'labeller.r'
'labels.r'
'layer.r'
'layout.R'
'limits.r'
'margins.R'
'panel.r'
'plot-build.r'
'plot-construction.r'
'plot-last.r'
Expand Down
38 changes: 17 additions & 21 deletions NAMESPACE
Expand Up @@ -13,26 +13,7 @@ S3method(element_grob,element_blank)
S3method(element_grob,element_line)
S3method(element_grob,element_rect)
S3method(element_grob,element_text)
S3method(facet_axes,grid)
S3method(facet_axes,wrap)
S3method(facet_map_layout,grid)
S3method(facet_map_layout,null)
S3method(facet_map_layout,wrap)
S3method(facet_panels,grid)
S3method(facet_panels,wrap)
S3method(facet_render,grid)
S3method(facet_render,null)
S3method(facet_render,wrap)
S3method(facet_strips,grid)
S3method(facet_strips,wrap)
S3method(facet_train_layout,grid)
S3method(facet_train_layout,null)
S3method(facet_train_layout,wrap)
S3method(facet_vars,grid)
S3method(facet_vars,null)
S3method(facet_vars,wrap)
S3method(finite.cases,data.frame)
S3method(format,facet)
S3method(format,ggproto)
S3method(format,ggproto_method)
S3method(fortify,"NULL")
Expand Down Expand Up @@ -89,7 +70,6 @@ S3method(predictdf,glm)
S3method(predictdf,locfit)
S3method(predictdf,loess)
S3method(print,element)
S3method(print,facet)
S3method(print,ggplot)
S3method(print,ggplot2_bins)
S3method(print,ggproto)
Expand Down Expand Up @@ -123,6 +103,10 @@ export(CoordMap)
export(CoordPolar)
export(CoordQuickmap)
export(CoordTrans)
export(Facet)
export(FacetGrid)
export(FacetNull)
export(FacetWrap)
export(Geom)
export(GeomAbline)
export(GeomAnnotationMap)
Expand Down Expand Up @@ -224,6 +208,7 @@ export(autoplot)
export(benchplot)
export(borders)
export(calc_element)
export(combine_vars)
export(continuous_scale)
export(coord_cartesian)
export(coord_equal)
Expand All @@ -237,6 +222,7 @@ export(coord_trans)
export(cut_interval)
export(cut_number)
export(cut_width)
export(derive)
export(discrete_scale)
export(draw_key_abline)
export(draw_key_blank)
Expand All @@ -253,16 +239,17 @@ export(draw_key_smooth)
export(draw_key_text)
export(draw_key_vline)
export(draw_key_vpath)
export(dup_axis)
export(element_blank)
export(element_grob)
export(element_line)
export(element_rect)
export(element_text)
export(expand_limits)
export(facet)
export(facet_grid)
export(facet_null)
export(facet_wrap)
export(find_panel)
export(fortify)
export(geom_abline)
export(geom_area)
Expand Down Expand Up @@ -342,11 +329,15 @@ export(layer_scales)
export(lims)
export(map_data)
export(margin)
export(max_height)
export(max_width)
export(mean_cl_boot)
export(mean_cl_normal)
export(mean_sdl)
export(mean_se)
export(median_hilow)
export(panel_cols)
export(panel_rows)
export(position_dodge)
export(position_fill)
export(position_identity)
Expand All @@ -358,6 +349,8 @@ export(qplot)
export(quickplot)
export(rel)
export(remove_missing)
export(render_axes)
export(render_strips)
export(resolution)
export(scale_alpha)
export(scale_alpha_continuous)
Expand Down Expand Up @@ -434,6 +427,7 @@ export(scale_y_discrete)
export(scale_y_log10)
export(scale_y_reverse)
export(scale_y_sqrt)
export(sec_axis)
export(should_stop)
export(stat_bin)
export(stat_bin2d)
Expand Down Expand Up @@ -482,6 +476,7 @@ export(update_geom_defaults)
export(update_labels)
export(update_stat_defaults)
export(waiver)
export(wrap_dims)
export(xlab)
export(xlim)
export(ylab)
Expand All @@ -490,6 +485,7 @@ export(zeroGrob)
import(grid)
import(gtable)
import(scales)
importFrom(lazyeval,f_eval)
importFrom(plyr,as.quoted)
importFrom(plyr,defaults)
importFrom(stats,setNames)
Expand Down
58 changes: 49 additions & 9 deletions NEWS.md
@@ -1,4 +1,37 @@
# ggplot2 2.1.0.9000
* The facet system, as well as the internal panel class, has been rewritten in
ggproto. Facets are now extendable in the same manner as geoms, stats etc. and
the manner in which this is done is described in the extension vignette. On
top of that the rewrite has added the following:

* `facet_grid` and `facet_wrap` now allow the use of expressions in their
facetting formulas (fixes #1596). Thanks to @DanRuderman.

* When `facet_wrap` results in an uneven number of panels, axes will now be
drawn underneath the hanging panels (fixes #1607)

* strips can now be freely positioned in `facet_wrap` using the
`strip.position` argument (deprecates `switch`).

* The relative order of panel, strip, and axis can now be controlled with
the theme setting `strip.placement` that takes either `inside` (between
panel and axis) or `outside` (after axis).

* The theme option `panel.margin` has been deprecated in favour of
`panel.spacing` to clearer communicate intend.

* The position of x and y axes can now be changed using the `position` argument
in `scale_x_*`and `scale_y_*` which can take `top` and `bottom`, and `left`and
`right` respectively.

* The styling of top and right axes text and labels can be modified directly
using the `.top` and `.right` modifiers to `axis.text.*` and `axis.title.*`

* `scale_x_continuous` and `scale_y_continuous` can now display a secondary axis
that is a linear transformation of the primary axis (e.g. degrees Celcius to
degrees Fahrenheit). The secondary axis will be positioned opposite of the
primary axis and can be controlled using the `sec.axis` argument to the scale
constructor.

* The documentation for theme elements has been improved (#1743).

Expand Down Expand Up @@ -106,15 +139,22 @@
lines (#1740)

* Multiple changes to legend theming:
- `legend.justification` now works outside of plotting area as well
- `panel.margin` and `legend.margin` has been renamed to `panel.spacing` and
`legend.spacing` respectively to better communicate intend
- `legend.margin` now controls margin around individual legends
- Added `legend.box.margin` to control the margin around the total legend area
- Added `legend.box.background` to control the background of the total legend
area
- Added `legend.box.spacing` to control the distance between the plot area and
the legend area

* `legend.justification` now works outside of plotting area as well

* `panel.margin` and `legend.margin` has been renamed to `panel.spacing` and
`legend.spacing` respectively to better communicate intend

* `legend.margin` now controls margin around individual legends

* Added `legend.box.margin` to control the margin around the total legend
area

* Added `legend.box.background` to control the background of the total
legend area

* Added `legend.box.spacing` to control the distance between the plot area
and the legend area

# ggplot2 2.1.0

Expand Down
152 changes: 152 additions & 0 deletions R/axis-secondary.R
@@ -0,0 +1,152 @@
#' Secondary axes
#'
#' Create a secondary axis as a transformation of the primary axis, positioned
#' opposite of the primary axis.
#'
#' @param trans A transformation formula
#'
#' @param name The name of the secondary axis
#'
#' @param breaks One of:
#' \itemize{
#' \item{\code{NULL} for no breaks}
#' \item{\code{waiver()} for the default breaks computed by the transformation object}
#' \item{A numeric vector of positions}
#' \item{A function that takes the limits as input and returns breaks as output}
#' }
#'
#' @param labels One of:
#' \itemize{
#' \item{\code{NULL} for no labels}
#' \item{\code{waiver()} for the default labels computed by the transformation object}
#' \item{A character vector giving labels (must be same length as \code{breaks})}
#' \item{A function that takes the breaks as input and returns labels as output}
#' }
#'
#' @details
#' \code{sec_axis} is used to create the specifications for a secondary axis.
#' Except for the \code{trans} argument any of the arguments can be set to
#' \code{derive()} which would result in the secondary axis inheriting the
#' settings from the primary axis.
#'
#' \code{dup_axis} is provide as a shorthand for creating a secondary axis that
#' is a duplication of the primary axis, effectively mirroring the primary axis.
#'
#' @examples
#' p <- ggplot(mtcars, aes(cyl, mpg)) +
#' geom_point()
#'
#' # Create a simple secondary axis
#' p + scale_y_continuous(sec.axis = sec_axis(~.+10))
#'
#' # Inherit the name from the primary axis
#' p + scale_y_continuous("Miles/gallon", sec.axis = sec_axis(~.+10, name = derive()))
#'
#' # Duplicate the primary axis
#' p + scale_y_continuous(sec.axis = dup_axis())
#'
#' # You can pass in a formula as a shorthand
#' p + scale_y_continuous(sec.axis = ~.^2)
#'
#' @export
sec_axis <- function(trans = NULL, name = waiver(), breaks = waiver(), labels = waiver()) {
if (!is.formula(trans)) stop("transformation for secondary axes must be a formula", call. = FALSE)
ggproto(NULL, AxisSecondary,
trans = trans,
name = name,
breaks = breaks,
labels = labels
)
}
#' @rdname sec_axis
#'
#' @export
dup_axis <- function(trans = ~., name = derive(), breaks = derive(), labels = derive()) {
sec_axis(trans, name, breaks, labels)
}
is.sec_axis <- function(x) {
inherits(x, "AxisSecondary")
}
#' @rdname sec_axis
#'
#' @export
derive <- function() {
structure(list(), class = "derived")
}
is.derived <- function(x) {
inherits(x, "derived")
}
#' @importFrom lazyeval f_eval
AxisSecondary <- ggproto("AxisSecondary", NULL,
trans = NULL,
axis = NULL,
name = waiver(),
breaks = waiver(),
labels = waiver(),

# This determines the quality of the remapping from the secondary axis and
# back to the primary axis i.e. the exactness of the placement of the
# breakpoints of the secondary axis.
detail = 1000,

empty = function(self) {
is.null(self$trans)
},

# Inherit settings from the primary axis/scale
init = function(self, scale) {
if (self$empty()) return()
if (!is.formula(self$trans)) stop("transformation for secondary axes must be a formula", call. = FALSE)
if (is.derived(self$name)) self$name <- scale$name
if (is.derived(self$breaks)) self$breaks <- scale$breaks
if (is.derived(self$labels)) self$labels <- scale$labels
},

transform_range = function(self, range) {
range <- structure(data.frame(range), names = '.')
f_eval(self$trans, range)
},


break_info = function(self, range, scale) {
if (self$empty()) return()

# Get original range before transformation
inv_range <- scale$trans$inverse(range)

# Create mapping between primary and secondary range
old_range <- seq(inv_range[1], inv_range[2], length.out = self$detail)
full_range <- self$transform_range(old_range)

# Test for monotony
if (length(unique(sign(diff(full_range)))) != 1) stop("transformation for secondary axes must be monotonous")

# Get break info for the secondary axis
new_range <- full_range[c(1, self$detail)]
temp_scale <- self$create_scale(new_range)
range_info <- temp_scale$break_info()

# Map the break values back to their correct position on the primary scale
old_val <- lapply(range_info$major_source, function(x) which.min(abs(full_range - x)))
old_val <- old_range[unlist(old_val)]
old_val_trans <- scale$trans$transform(old_val)
range_info$major[] <- round(rescale(scale$map(old_val_trans, range(old_val_trans)), from = range), digits = 3)

names(range_info) <- paste0("sec.", names(range_info))
range_info
},

# Temporary scale for the purpose of calling break_info()
create_scale = function(self, range) {
scale <- ggproto(NULL, ScaleContinuousPosition,
name = self$name,
breaks = self$breaks,
labels = self$labels,
limits = range,
expand = c(0, 0),
trans = identity_trans()
)
scale$train(range)
scale
}
)

0 comments on commit 06037a9

Please sign in to comment.