Skip to content

Convert guides to ggproto #4879

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 131 commits into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
131 commits
Select commit Hold shift + click to select a range
59850ac
Merge pull request #1 from tidyverse/master
teunbrand Jan 13, 2021
86787de
Merge branch 'tidyverse:master' into master
teunbrand Sep 15, 2021
1d1a2b0
Merge branch 'tidyverse:main' into master
teunbrand Mar 24, 2022
28eff54
Merge branch 'tidyverse:main' into master
teunbrand May 11, 2022
98a5c33
Allow Guide ggproto as valid guide
teunbrand Jun 18, 2022
30d7382
The parent Guide ggproto
teunbrand Jun 18, 2022
2d871e8
Add guide constructor
teunbrand Jun 18, 2022
afbf086
Small tweaks
teunbrand Jun 18, 2022
3a286f3
Convert guide_none to ggproto
teunbrand Jun 18, 2022
870221c
Accommodate Guide in cartesian coords
teunbrand Jun 18, 2022
12fc416
Bifurcate guides_train (for now)
teunbrand Jun 18, 2022
23d2f76
Convert guide_axis to ggproto
teunbrand Jun 18, 2022
ad3aa6c
Resolve merge conflicts
teunbrand Jun 19, 2022
0e67b73
Merge branch 'tidyverse-main2' into ggproto_guides
teunbrand Jun 19, 2022
26eebaf
merge upstream
teunbrand Aug 7, 2022
952b153
Merge branch 'tidyverse-main' into ggproto_guides
teunbrand Aug 7, 2022
7c12fda
revert scale$map() logic
teunbrand Aug 8, 2022
570aba0
Adress some minor comments
teunbrand Aug 8, 2022
9c27d3e
Better warning when guide function not found
teunbrand Aug 11, 2022
058f0cf
move guide position specification from setup to training
teunbrand Aug 11, 2022
fb32308
Stop if guide has no valid function
teunbrand Aug 13, 2022
9b95e22
Make a GuidesList class
teunbrand Aug 13, 2022
a0f6a2d
Coord uses GuidesList
teunbrand Aug 13, 2022
ed1f392
Invalid guide throws warning instead of error
teunbrand Aug 13, 2022
aabc112
Update axis merge method
teunbrand Aug 13, 2022
1ca3f06
Trim guide methods, return params
teunbrand Aug 13, 2022
4f7492d
CoordPolar doesn't use guides
teunbrand Aug 13, 2022
9ab5ffb
Consider guide titles earlier
teunbrand Aug 13, 2022
bbb4f00
expand tests on label hierarchy
teunbrand Aug 13, 2022
d0fbdc2
roxygenate
teunbrand Aug 13, 2022
bfa93a4
Fix #4958
teunbrand Sep 3, 2022
c652cd3
Default guide$transform() throws error
teunbrand Sep 4, 2022
855cca3
guide$train() gets ellipses to pass to guide$extract_params()
teunbrand Sep 4, 2022
aaba298
Decoration 'name' is an axis thing
teunbrand Sep 4, 2022
7e6752c
guide$assemble_drawing() knows about elements
teunbrand Sep 4, 2022
43b24a1
re-doc `guide_axis()`'s order argument
teunbrand Sep 4, 2022
b933ff4
Use .trbl helper vector
teunbrand Sep 4, 2022
71d7ee0
Fix few mistakes
teunbrand Sep 4, 2022
3bbaf26
rename GuidesList class to Guides
teunbrand Sep 4, 2022
1c6d103
Move guide training to from guides_train() to Guides$train()
teunbrand Sep 4, 2022
cabc462
Move guide merging from guides_merge() to Guides$merge()
teunbrand Sep 4, 2022
b9d2a62
Move guides_geom() to Guides$process_layers()
teunbrand Sep 4, 2022
c72ffa6
Move guides_gengrob() to Guides$draw()
teunbrand Sep 4, 2022
ee5a5a4
Move guides_build() to Guides$assemble()
teunbrand Sep 4, 2022
f77a3c4
Update test to use Guides
teunbrand Sep 4, 2022
1b63262
Clean up the guides_*() function family
teunbrand Sep 4, 2022
271b285
Convert guide_legend() to ggproto
teunbrand Sep 4, 2022
8b1ba23
Re-oxygenate
teunbrand Sep 4, 2022
7a5d734
Forgot about `reverse` argument
teunbrand Sep 4, 2022
6617558
Move label justification to GuideLegend$build_labels
teunbrand Sep 7, 2022
82cbc15
Accommodate NULL labels
teunbrand Sep 9, 2022
6ef1267
`Guide$build_ticks()` can take key as vector.
teunbrand Sep 9, 2022
00ca023
Properly pass ticks to `Guide$build_decor()`
teunbrand Sep 9, 2022
21dbd8a
GuideLegend children can opt-out of label re-justification
teunbrand Sep 9, 2022
0e0962a
Simplify key measuring logic
teunbrand Sep 13, 2022
94d7c07
Add sensible defaults for some parameters
teunbrand Sep 13, 2022
2cfc27c
Convert guide_colourbar
teunbrand Sep 13, 2022
f299963
More use theme elements more consistently
teunbrand Sep 13, 2022
184774b
Document alternate use for `frame` and `ticks` in GuideColourbar
teunbrand Sep 13, 2022
e9c24ec
Convert `guide_bins()` to ggproto
teunbrand Sep 13, 2022
e0112c4
Convert `guide_coloursteps()` to ggproto
teunbrand Sep 13, 2022
f88db51
Update guide tests
teunbrand Sep 13, 2022
96e811b
Remove branches for old guides from Guides methods
teunbrand Sep 13, 2022
ff7695e
Go back to `matched_aes()` instead of manual calculation
teunbrand Sep 13, 2022
0fdd7a0
Remove old S3 generics
teunbrand Sep 13, 2022
f9dcb5d
Re-oxygenate
teunbrand Sep 13, 2022
f946c57
Rename guides-{axis/none}.r to guide-{axis/none}.r for consistency
teunbrand Sep 13, 2022
b23146a
Commit man pages
teunbrand Sep 13, 2022
9dcbed8
Merge branch 'tidyverse:main' into ggproto_guides
teunbrand Sep 13, 2022
1a53626
Avoid spurious order mixing
teunbrand Sep 13, 2022
7178e30
Avoid some spurious axis rearrangements
teunbrand Sep 13, 2022
62b300b
size --> linewidth
teunbrand Sep 13, 2022
0f88e75
Don't clip `guide_bins()`
teunbrand Sep 13, 2022
981103b
Convert hash items quos to exprs
teunbrand Sep 13, 2022
de7330d
Approve visual change in horizontal guide_bins
teunbrand Sep 13, 2022
65a7195
Approve invisible visible changes
teunbrand Sep 13, 2022
d238d57
Don't map continuous scales
teunbrand Nov 13, 2022
772093f
Pre-populate GuideNone where possible
teunbrand Nov 13, 2022
2cce9df
Speedup axis guide training
teunbrand Nov 13, 2022
258a913
Merge branch 'main' into ggproto_guides
teunbrand Nov 13, 2022
0deae41
Merge branch 'ggproto_guides' of https://github.com/teunbrand/ggplot2…
teunbrand Nov 13, 2022
575dd94
Don't glue theme elements
teunbrand Nov 13, 2022
b7adfbe
Add NEWS bullet
teunbrand Nov 14, 2022
2a1d41f
Merge branch 'main' into ggproto_guides
teunbrand Dec 11, 2022
9fd9f31
Update test
teunbrand Dec 11, 2022
5792ea9
Prevent bug in guide axis
teunbrand Dec 31, 2022
e500551
Pluralise error message
teunbrand Jan 2, 2023
29af9b9
Legend assembly conditional on grob being present
teunbrand Jan 2, 2023
5157221
`guides()` returns <Guides>
teunbrand Jan 2, 2023
8272173
guides() rejects unnamed guides
teunbrand Jan 2, 2023
a1c8dab
Fix `calc_element()` not recognising 'character' as special keyword
teunbrand Jan 2, 2023
49559f9
GuideLegend can skip computing missing stuff
teunbrand Jan 2, 2023
ac3a59f
Give <Guides> nicer print method
teunbrand Jan 2, 2023
c010599
Merge branch 'main' into ggproto_guides
teunbrand Jan 3, 2023
80da767
Fix #5152
teunbrand Jan 18, 2023
6239017
Merge branch 'ggproto_guides' of https://github.com/teunbrand/ggplot2…
teunbrand Jan 18, 2023
9024e12
Reduce `guides()` logic depth
teunbrand Feb 13, 2023
bb9cf55
Make Guides$build method
teunbrand Feb 13, 2023
d3371ba
Guarantee `guides` is `Guides`-class
teunbrand Feb 13, 2023
7a77dd4
Integrate Guides$build
teunbrand Feb 13, 2023
daa8a6f
Remove superfluous check
teunbrand Feb 13, 2023
2a11d51
Restructure constructor -> class -> helpers
teunbrand Feb 13, 2023
26d037c
Omit NEWS bullet for now
teunbrand Feb 13, 2023
af8e1f5
Merge pull request #10 from teunbrand/main
teunbrand Feb 13, 2023
16937c0
Fix failing test
teunbrand Feb 14, 2023
99f1724
`Guide$build_decor()` recieves all grobs
teunbrand Feb 14, 2023
76d08f6
Merge branch 'ggproto_guides' of https://github.com/teunbrand/ggplot2…
teunbrand Feb 14, 2023
4bb5dd8
Revert "Fix `calc_element()` not recognising 'character' as special k…
teunbrand Feb 27, 2023
b7ba4d6
Resolve conflict
teunbrand Feb 27, 2023
166af6f
Merge pull request #13 from teunbrand/temp_main
teunbrand Feb 27, 2023
04cda8d
Resolve merge conflicts
teunbrand Mar 26, 2023
676a4db
Don't `Map()` setters
teunbrand Mar 26, 2023
63337ba
Better variable names
teunbrand Mar 26, 2023
f8eee08
Rename `$geom()` method to `$get_layer_key()`
teunbrand Mar 26, 2023
f0f223c
Use dual type checks
teunbrand Mar 26, 2023
8eecb1c
Initialise <Guides>
teunbrand Mar 26, 2023
5640d69
Fix last code review issues
teunbrand Mar 26, 2023
57687a1
Rename guide-.r to guide-.R
teunbrand Mar 26, 2023
18f275f
Rename guide-axis.r to guide-axis.R
teunbrand Mar 26, 2023
4db47c8
Rename guide-none.r to guide-none.R
teunbrand Mar 26, 2023
6065f96
Add statelessness test
teunbrand Apr 11, 2023
f1e48d1
Swap test hashing for serialisation
teunbrand Apr 12, 2023
365b3f5
Purge `default_mapping` arguments, see #4475
teunbrand Apr 16, 2023
a230443
Test `Guides` instead of plain list
teunbrand Apr 16, 2023
5bd9e59
validator also validates output of function name input
teunbrand Apr 16, 2023
54b3a6a
remove unnecessary `scale_index` field
teunbrand Apr 16, 2023
a1bbe23
Deal with `guide_none()` once
teunbrand Apr 16, 2023
91059d7
Reorganise and clarify
teunbrand Apr 16, 2023
da5e75d
Merge branch 'main' into ggproto_guides
teunbrand Apr 22, 2023
57b82f7
Fix plot state bug
teunbrand Apr 23, 2023
700d717
Add news bullet
teunbrand Apr 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -173,20 +173,21 @@ Collate:
'grob-dotstack.R'
'grob-null.R'
'grouping.R'
'guide-.R'
'guide-axis.R'
'guide-legend.R'
'guide-bins.R'
'guide-colorbar.R'
'guide-colorsteps.R'
'guide-legend.R'
'layer.R'
'guide-none.R'
'guides-.R'
'guides-axis.R'
'guides-grid.R'
'guides-none.R'
'hexbin.R'
'import-standalone-obj-type.R'
'import-standalone-types-check.R'
'labeller.R'
'labels.R'
'layer.R'
'layer-sf.R'
'layout.R'
'limits.R'
Expand Down
39 changes: 9 additions & 30 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ S3method(ggplot_add,"NULL")
S3method(ggplot_add,"function")
S3method(ggplot_add,Coord)
S3method(ggplot_add,Facet)
S3method(ggplot_add,Guides)
S3method(ggplot_add,Layer)
S3method(ggplot_add,Scale)
S3method(ggplot_add,by)
S3method(ggplot_add,data.frame)
S3method(ggplot_add,default)
S3method(ggplot_add,guides)
S3method(ggplot_add,labels)
S3method(ggplot_add,list)
S3method(ggplot_add,theme)
Expand All @@ -75,30 +75,6 @@ S3method(grobWidth,absoluteGrob)
S3method(grobWidth,zeroGrob)
S3method(grobX,absoluteGrob)
S3method(grobY,absoluteGrob)
S3method(guide_gengrob,axis)
S3method(guide_gengrob,bins)
S3method(guide_gengrob,colorbar)
S3method(guide_gengrob,guide_none)
S3method(guide_gengrob,legend)
S3method(guide_geom,axis)
S3method(guide_geom,bins)
S3method(guide_geom,colorbar)
S3method(guide_geom,guide_none)
S3method(guide_geom,legend)
S3method(guide_merge,axis)
S3method(guide_merge,bins)
S3method(guide_merge,colorbar)
S3method(guide_merge,guide_none)
S3method(guide_merge,legend)
S3method(guide_train,axis)
S3method(guide_train,bins)
S3method(guide_train,colorbar)
S3method(guide_train,colorsteps)
S3method(guide_train,guide_none)
S3method(guide_train,legend)
S3method(guide_transform,axis)
S3method(guide_transform,default)
S3method(guide_transform,guide_none)
S3method(heightDetails,titleGrob)
S3method(heightDetails,zeroGrob)
S3method(interleave,default)
Expand Down Expand Up @@ -228,6 +204,13 @@ export(GeomText)
export(GeomTile)
export(GeomViolin)
export(GeomVline)
export(Guide)
export(GuideAxis)
export(GuideBins)
export(GuideColourbar)
export(GuideColoursteps)
export(GuideLegend)
export(GuideNone)
export(Layout)
export(Position)
export(PositionDodge)
Expand Down Expand Up @@ -433,13 +416,8 @@ export(guide_colorbar)
export(guide_colorsteps)
export(guide_colourbar)
export(guide_coloursteps)
export(guide_gengrob)
export(guide_geom)
export(guide_legend)
export(guide_merge)
export(guide_none)
export(guide_train)
export(guide_transform)
export(guides)
export(has_flipped_aes)
export(is.Coord)
Expand Down Expand Up @@ -472,6 +450,7 @@ export(mean_sdl)
export(mean_se)
export(median_hilow)
export(merge_element)
export(new_guide)
export(panel_cols)
export(panel_rows)
export(position_dodge)
Expand Down
23 changes: 23 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# ggplot2 (development version)

* The guide system, as the last remaining chunk of ggplot2, has been rewritten
in ggproto. The axes and legends now inherit from a <Guide> class, which makes
them extensible in the same manner as geoms, stats, facets and coords
(#3329, @teunbrand). In addition, the following changes were made:
* Styling theme parts of the guide now inherit from the plot's theme
(#2728).
* Styling non-theme parts of the guides accept <element> objects, so that
the following is possible: `guide_colourbar(frame = element_rect(...))`.
* Primary axis titles are now placed at the primary guide, so that
`guides(x = guide_axis(position = "top"))` will display the title at the
top by default (#4650).
* Unknown secondary axis guide positions are now inferred as the opposite
of the primary axis guide when the latter has a known `position` (#4650).
* `guide_colourbar()`, `guide_coloursteps()` and `guide_bins()` gain a
`ticks.length` argument.
* In `guide_bins()`, the title no longer arbitrarily becomes offset from
the guide when it has long labels.
* The `order` argument of guides now strictly needs to be a length-1
integer (#4958).
* More informative error for mismatched
`direction`/`theme(legend.direction = ...)` arguments (#4364, #4930).
* `guide_coloursteps()` and `guide_bins()` sort breaks (#5152).

* `geom_label()` now uses the `angle` aesthetic (@teunbrand, #2785)
* 'lines' units in `geom_label()`, often used in the `label.padding` argument,
are now are relative to the text size. This causes a visual change, but fixes
Expand Down
130 changes: 63 additions & 67 deletions R/coord-.R
Original file line number Diff line number Diff line change
Expand Up @@ -60,31 +60,7 @@ Coord <- ggproto("Coord",
aspect = function(ranges) NULL,

labels = function(self, labels, panel_params) {
# If panel params contains guides information, use it.
# Otherwise use the labels as is, for backward-compatibility
if (is.null(panel_params$guides)) {
return(labels)
}

positions_x <- c("top", "bottom")
positions_y <- c("left", "right")

list(
x = lapply(c(1, 2), function(i) {
panel_guide_label(
panel_params$guides,
position = positions_x[[i]],
default_label = labels$x[[i]]
)
}),
y = lapply(c(1, 2), function(i) {
panel_guide_label(
panel_params$guides,
position = positions_y[[i]],
default_label = labels$y[[i]]
)
})
)
labels
},

render_fg = function(panel_params, theme) element_render(theme, "panel.border"),
Expand Down Expand Up @@ -120,58 +96,75 @@ Coord <- ggproto("Coord",
setup_panel_guides = function(self, panel_params, guides, params = list()) {
aesthetics <- c("x", "y", "x.sec", "y.sec")
names(aesthetics) <- aesthetics
is_sec <- grepl("sec$", aesthetics)

# Do guide setup
guides <- guides$setup(
panel_params, aesthetics,
default = params$guide_default %||% guide_axis(),
missing = params$guide_missing %||% guide_none()
)
guide_params <- guides$get_params(aesthetics)

# Resolve positions
scale_position <- lapply(panel_params[aesthetics], `[[`, "position")
guide_position <- lapply(guide_params, `[[`, "position")
guide_position[!is_sec] <- Map(
function(guide, scale) guide %|W|% scale,
guide = guide_position[!is_sec],
scale = scale_position[!is_sec]
)
opposite <- c(
"top" = "bottom", "bottom" = "top",
"left" = "right", "right" = "left"
)
guide_position[is_sec] <- Map(
function(sec, prim) sec %|W|% unname(opposite[prim]),
sec = guide_position[is_sec],
prim = guide_position[!is_sec]
)
guide_params <- Map(
function(params, pos) {
params[["position"]] <- pos
params
},
params = guide_params,
pos = guide_position
)

# If the panel_params doesn't contain the scale, do not use a guide for that aesthetic
idx <- vapply(aesthetics, function(aesthetic) {
scale <- panel_params[[aesthetic]]
!is.null(scale) && inherits(scale, "ViewScale")
}, logical(1L))
aesthetics <- aesthetics[idx]

# resolve the specified guide from the scale and/or guides
guides <- lapply(aesthetics, function(aesthetic) {
resolve_guide(
aesthetic,
panel_params[[aesthetic]],
guides,
default = guide_axis(),
null = guide_none()
)
})

# resolve the guide definition as a "guide" S3
guides <- lapply(guides, validate_guide)

# if there is a "position" specification in the scale, pass this on to the guide
# ideally, this should be specified in the guide
guides <- lapply(aesthetics, function(aesthetic) {
guide <- guides[[aesthetic]]
scale <- panel_params[[aesthetic]]
# position could be NULL here for an empty scale
guide$position <- guide$position %|W|% scale$position
guide
})
# Update positions
guides$update_params(guide_params)

panel_params$guides <- guides
panel_params
},

train_panel_guides = function(self, panel_params, layers, default_mapping, params = list()) {
train_panel_guides = function(self, panel_params, layers, params = list()) {

aesthetics <- c("x", "y", "x.sec", "y.sec")

# If the panel_params doesn't contain the scale, there's no guide for the aesthetic
aesthetics <- intersect(aesthetics, names(panel_params$guides))

aesthetics <- intersect(aesthetics, names(panel_params$guides$aesthetics))
names(aesthetics) <- aesthetics

panel_params$guides <- lapply(aesthetics, function(aesthetic) {
axis <- substr(aesthetic, 1, 1)
guide <- panel_params$guides[[aesthetic]]
guide <- guide_train(guide, panel_params[[aesthetic]])
guide <- guide_transform(guide, self, panel_params)
guide <- guide_geom(guide, layers, default_mapping)
guide
})
guides <- panel_params$guides$get_guide(aesthetics)
empty <- vapply(guides, inherits, logical(1), "GuideNone")
guide_params <- panel_params$guides$get_params(aesthetics)
aesthetics <- aesthetics[!empty]

guide_params[!empty] <- Map(
function(guide, guide_param, scale) {
guide_param <- guide$train(guide_param, scale)
guide_param <- guide$transform(guide_param, self, panel_params)
guide_param <- guide$get_layer_key(guide_param, layers)
guide_param
},
guide = guides[!empty],
guide_param = guide_params[!empty],
scale = panel_params[aesthetics]
)

panel_params$guides$update_params(guide_params)

panel_params
},
Expand All @@ -187,7 +180,10 @@ Coord <- ggproto("Coord",
is_free = function() FALSE,

setup_params = function(data) {
list()
list(
guide_default = guide_axis(),
guide_missing = guide_none()
)
},

setup_data = function(data, params = list()) {
Expand Down
40 changes: 27 additions & 13 deletions R/coord-cartesian-.R
Original file line number Diff line number Diff line change
Expand Up @@ -144,24 +144,38 @@ view_scales_from_scale <- function(scale, coord_limits = NULL, expand = TRUE) {
view_scales
}

panel_guide_label <- function(guides, position, default_label) {
guide <- guide_for_position(guides, position) %||% guide_none(title = waiver())
guide$title %|W|% default_label
}

panel_guides_grob <- function(guides, position, theme) {
guide <- guide_for_position(guides, position) %||% guide_none()
guide_gengrob(guide, theme)
pair <- guide_for_position(guides, position) %||%
list(guide = guide_none(), params = NULL)
pair$guide$draw(theme, pair$params)
}

guide_for_position <- function(guides, position) {
params <- guides$params
has_position <- vapply(
guides,
function(guide) identical(guide$position, position),
logical(1)
params, function(p) identical(p$position, position), logical(1)
)
if (!any(has_position)) {
return(NULL)
}

# Subset guides and parameters
guides <- guides$get_guide(has_position)
params <- params[has_position]
# Pair up guides with parameters
pairs <- Map(list, guide = guides, params = params)

guides <- guides[has_position]
guides_order <- vapply(guides, function(guide) as.numeric(guide$order)[1], numeric(1))
Reduce(guide_merge, guides[order(guides_order)])
# Early exit, nothing to merge
if (length(pairs) == 1) {
return(pairs[[1]])
}

# TODO: There must be a smarter way to merge these
order <- order(vapply(params, function(p) as.numeric(p$order), numeric(1)))
Reduce(
function(old, new) {
old$guide$merge(old$params, new$guide, new$params)
},
pairs[order]
)
}
8 changes: 8 additions & 0 deletions R/coord-polar.R
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ CoordPolar <- ggproto("CoordPolar", Coord,
details
},

setup_panel_guides = function(self, panel_params, guides, params = list()) {
panel_params
},

train_panel_guides = function(self, panel_params, layers, default_mapping, params = list()) {
panel_params
},

transform = function(self, data, panel_params) {
data <- rename_data(self, data)

Expand Down
Loading