Skip to content

Commit b9f43f2

Browse files
authored
Convert guides to ggproto (#4879)
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).
1 parent f6e87ac commit b9f43f2

File tree

99 files changed

+3843
-3174
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+3843
-3174
lines changed

DESCRIPTION

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,20 +173,21 @@ Collate:
173173
'grob-dotstack.R'
174174
'grob-null.R'
175175
'grouping.R'
176+
'guide-.R'
177+
'guide-axis.R'
178+
'guide-legend.R'
176179
'guide-bins.R'
177180
'guide-colorbar.R'
178181
'guide-colorsteps.R'
179-
'guide-legend.R'
182+
'layer.R'
183+
'guide-none.R'
180184
'guides-.R'
181-
'guides-axis.R'
182185
'guides-grid.R'
183-
'guides-none.R'
184186
'hexbin.R'
185187
'import-standalone-obj-type.R'
186188
'import-standalone-types-check.R'
187189
'labeller.R'
188190
'labels.R'
189-
'layer.R'
190191
'layer-sf.R'
191192
'layout.R'
192193
'limits.R'

NAMESPACE

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ S3method(ggplot_add,"NULL")
5555
S3method(ggplot_add,"function")
5656
S3method(ggplot_add,Coord)
5757
S3method(ggplot_add,Facet)
58+
S3method(ggplot_add,Guides)
5859
S3method(ggplot_add,Layer)
5960
S3method(ggplot_add,Scale)
6061
S3method(ggplot_add,by)
6162
S3method(ggplot_add,data.frame)
6263
S3method(ggplot_add,default)
63-
S3method(ggplot_add,guides)
6464
S3method(ggplot_add,labels)
6565
S3method(ggplot_add,list)
6666
S3method(ggplot_add,theme)
@@ -75,30 +75,6 @@ S3method(grobWidth,absoluteGrob)
7575
S3method(grobWidth,zeroGrob)
7676
S3method(grobX,absoluteGrob)
7777
S3method(grobY,absoluteGrob)
78-
S3method(guide_gengrob,axis)
79-
S3method(guide_gengrob,bins)
80-
S3method(guide_gengrob,colorbar)
81-
S3method(guide_gengrob,guide_none)
82-
S3method(guide_gengrob,legend)
83-
S3method(guide_geom,axis)
84-
S3method(guide_geom,bins)
85-
S3method(guide_geom,colorbar)
86-
S3method(guide_geom,guide_none)
87-
S3method(guide_geom,legend)
88-
S3method(guide_merge,axis)
89-
S3method(guide_merge,bins)
90-
S3method(guide_merge,colorbar)
91-
S3method(guide_merge,guide_none)
92-
S3method(guide_merge,legend)
93-
S3method(guide_train,axis)
94-
S3method(guide_train,bins)
95-
S3method(guide_train,colorbar)
96-
S3method(guide_train,colorsteps)
97-
S3method(guide_train,guide_none)
98-
S3method(guide_train,legend)
99-
S3method(guide_transform,axis)
100-
S3method(guide_transform,default)
101-
S3method(guide_transform,guide_none)
10278
S3method(heightDetails,titleGrob)
10379
S3method(heightDetails,zeroGrob)
10480
S3method(interleave,default)
@@ -228,6 +204,13 @@ export(GeomText)
228204
export(GeomTile)
229205
export(GeomViolin)
230206
export(GeomVline)
207+
export(Guide)
208+
export(GuideAxis)
209+
export(GuideBins)
210+
export(GuideColourbar)
211+
export(GuideColoursteps)
212+
export(GuideLegend)
213+
export(GuideNone)
231214
export(Layout)
232215
export(Position)
233216
export(PositionDodge)
@@ -433,13 +416,8 @@ export(guide_colorbar)
433416
export(guide_colorsteps)
434417
export(guide_colourbar)
435418
export(guide_coloursteps)
436-
export(guide_gengrob)
437-
export(guide_geom)
438419
export(guide_legend)
439-
export(guide_merge)
440420
export(guide_none)
441-
export(guide_train)
442-
export(guide_transform)
443421
export(guides)
444422
export(has_flipped_aes)
445423
export(is.Coord)
@@ -472,6 +450,7 @@ export(mean_sdl)
472450
export(mean_se)
473451
export(median_hilow)
474452
export(merge_element)
453+
export(new_guide)
475454
export(panel_cols)
476455
export(panel_rows)
477456
export(position_dodge)

NEWS.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
# ggplot2 (development version)
22

3+
* The guide system, as the last remaining chunk of ggplot2, has been rewritten
4+
in ggproto. The axes and legends now inherit from a <Guide> class, which makes
5+
them extensible in the same manner as geoms, stats, facets and coords
6+
(#3329, @teunbrand). In addition, the following changes were made:
7+
* Styling theme parts of the guide now inherit from the plot's theme
8+
(#2728).
9+
* Styling non-theme parts of the guides accept <element> objects, so that
10+
the following is possible: `guide_colourbar(frame = element_rect(...))`.
11+
* Primary axis titles are now placed at the primary guide, so that
12+
`guides(x = guide_axis(position = "top"))` will display the title at the
13+
top by default (#4650).
14+
* Unknown secondary axis guide positions are now inferred as the opposite
15+
of the primary axis guide when the latter has a known `position` (#4650).
16+
* `guide_colourbar()`, `guide_coloursteps()` and `guide_bins()` gain a
17+
`ticks.length` argument.
18+
* In `guide_bins()`, the title no longer arbitrarily becomes offset from
19+
the guide when it has long labels.
20+
* The `order` argument of guides now strictly needs to be a length-1
21+
integer (#4958).
22+
* More informative error for mismatched
23+
`direction`/`theme(legend.direction = ...)` arguments (#4364, #4930).
24+
* `guide_coloursteps()` and `guide_bins()` sort breaks (#5152).
25+
326
* `geom_label()` now uses the `angle` aesthetic (@teunbrand, #2785)
427
* 'lines' units in `geom_label()`, often used in the `label.padding` argument,
528
are now are relative to the text size. This causes a visual change, but fixes

R/coord-.R

Lines changed: 63 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -60,31 +60,7 @@ Coord <- ggproto("Coord",
6060
aspect = function(ranges) NULL,
6161

6262
labels = function(self, labels, panel_params) {
63-
# If panel params contains guides information, use it.
64-
# Otherwise use the labels as is, for backward-compatibility
65-
if (is.null(panel_params$guides)) {
66-
return(labels)
67-
}
68-
69-
positions_x <- c("top", "bottom")
70-
positions_y <- c("left", "right")
71-
72-
list(
73-
x = lapply(c(1, 2), function(i) {
74-
panel_guide_label(
75-
panel_params$guides,
76-
position = positions_x[[i]],
77-
default_label = labels$x[[i]]
78-
)
79-
}),
80-
y = lapply(c(1, 2), function(i) {
81-
panel_guide_label(
82-
panel_params$guides,
83-
position = positions_y[[i]],
84-
default_label = labels$y[[i]]
85-
)
86-
})
87-
)
63+
labels
8864
},
8965

9066
render_fg = function(panel_params, theme) element_render(theme, "panel.border"),
@@ -120,58 +96,75 @@ Coord <- ggproto("Coord",
12096
setup_panel_guides = function(self, panel_params, guides, params = list()) {
12197
aesthetics <- c("x", "y", "x.sec", "y.sec")
12298
names(aesthetics) <- aesthetics
99+
is_sec <- grepl("sec$", aesthetics)
100+
101+
# Do guide setup
102+
guides <- guides$setup(
103+
panel_params, aesthetics,
104+
default = params$guide_default %||% guide_axis(),
105+
missing = params$guide_missing %||% guide_none()
106+
)
107+
guide_params <- guides$get_params(aesthetics)
108+
109+
# Resolve positions
110+
scale_position <- lapply(panel_params[aesthetics], `[[`, "position")
111+
guide_position <- lapply(guide_params, `[[`, "position")
112+
guide_position[!is_sec] <- Map(
113+
function(guide, scale) guide %|W|% scale,
114+
guide = guide_position[!is_sec],
115+
scale = scale_position[!is_sec]
116+
)
117+
opposite <- c(
118+
"top" = "bottom", "bottom" = "top",
119+
"left" = "right", "right" = "left"
120+
)
121+
guide_position[is_sec] <- Map(
122+
function(sec, prim) sec %|W|% unname(opposite[prim]),
123+
sec = guide_position[is_sec],
124+
prim = guide_position[!is_sec]
125+
)
126+
guide_params <- Map(
127+
function(params, pos) {
128+
params[["position"]] <- pos
129+
params
130+
},
131+
params = guide_params,
132+
pos = guide_position
133+
)
123134

124-
# If the panel_params doesn't contain the scale, do not use a guide for that aesthetic
125-
idx <- vapply(aesthetics, function(aesthetic) {
126-
scale <- panel_params[[aesthetic]]
127-
!is.null(scale) && inherits(scale, "ViewScale")
128-
}, logical(1L))
129-
aesthetics <- aesthetics[idx]
130-
131-
# resolve the specified guide from the scale and/or guides
132-
guides <- lapply(aesthetics, function(aesthetic) {
133-
resolve_guide(
134-
aesthetic,
135-
panel_params[[aesthetic]],
136-
guides,
137-
default = guide_axis(),
138-
null = guide_none()
139-
)
140-
})
141-
142-
# resolve the guide definition as a "guide" S3
143-
guides <- lapply(guides, validate_guide)
144-
145-
# if there is a "position" specification in the scale, pass this on to the guide
146-
# ideally, this should be specified in the guide
147-
guides <- lapply(aesthetics, function(aesthetic) {
148-
guide <- guides[[aesthetic]]
149-
scale <- panel_params[[aesthetic]]
150-
# position could be NULL here for an empty scale
151-
guide$position <- guide$position %|W|% scale$position
152-
guide
153-
})
135+
# Update positions
136+
guides$update_params(guide_params)
154137

155138
panel_params$guides <- guides
156139
panel_params
157140
},
158141

159-
train_panel_guides = function(self, panel_params, layers, default_mapping, params = list()) {
142+
train_panel_guides = function(self, panel_params, layers, params = list()) {
143+
160144
aesthetics <- c("x", "y", "x.sec", "y.sec")
161145

162146
# If the panel_params doesn't contain the scale, there's no guide for the aesthetic
163-
aesthetics <- intersect(aesthetics, names(panel_params$guides))
164-
147+
aesthetics <- intersect(aesthetics, names(panel_params$guides$aesthetics))
165148
names(aesthetics) <- aesthetics
166149

167-
panel_params$guides <- lapply(aesthetics, function(aesthetic) {
168-
axis <- substr(aesthetic, 1, 1)
169-
guide <- panel_params$guides[[aesthetic]]
170-
guide <- guide_train(guide, panel_params[[aesthetic]])
171-
guide <- guide_transform(guide, self, panel_params)
172-
guide <- guide_geom(guide, layers, default_mapping)
173-
guide
174-
})
150+
guides <- panel_params$guides$get_guide(aesthetics)
151+
empty <- vapply(guides, inherits, logical(1), "GuideNone")
152+
guide_params <- panel_params$guides$get_params(aesthetics)
153+
aesthetics <- aesthetics[!empty]
154+
155+
guide_params[!empty] <- Map(
156+
function(guide, guide_param, scale) {
157+
guide_param <- guide$train(guide_param, scale)
158+
guide_param <- guide$transform(guide_param, self, panel_params)
159+
guide_param <- guide$get_layer_key(guide_param, layers)
160+
guide_param
161+
},
162+
guide = guides[!empty],
163+
guide_param = guide_params[!empty],
164+
scale = panel_params[aesthetics]
165+
)
166+
167+
panel_params$guides$update_params(guide_params)
175168

176169
panel_params
177170
},
@@ -187,7 +180,10 @@ Coord <- ggproto("Coord",
187180
is_free = function() FALSE,
188181

189182
setup_params = function(data) {
190-
list()
183+
list(
184+
guide_default = guide_axis(),
185+
guide_missing = guide_none()
186+
)
191187
},
192188

193189
setup_data = function(data, params = list()) {

R/coord-cartesian-.R

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -144,24 +144,38 @@ view_scales_from_scale <- function(scale, coord_limits = NULL, expand = TRUE) {
144144
view_scales
145145
}
146146

147-
panel_guide_label <- function(guides, position, default_label) {
148-
guide <- guide_for_position(guides, position) %||% guide_none(title = waiver())
149-
guide$title %|W|% default_label
150-
}
151-
152147
panel_guides_grob <- function(guides, position, theme) {
153-
guide <- guide_for_position(guides, position) %||% guide_none()
154-
guide_gengrob(guide, theme)
148+
pair <- guide_for_position(guides, position) %||%
149+
list(guide = guide_none(), params = NULL)
150+
pair$guide$draw(theme, pair$params)
155151
}
156152

157153
guide_for_position <- function(guides, position) {
154+
params <- guides$params
158155
has_position <- vapply(
159-
guides,
160-
function(guide) identical(guide$position, position),
161-
logical(1)
156+
params, function(p) identical(p$position, position), logical(1)
162157
)
158+
if (!any(has_position)) {
159+
return(NULL)
160+
}
161+
162+
# Subset guides and parameters
163+
guides <- guides$get_guide(has_position)
164+
params <- params[has_position]
165+
# Pair up guides with parameters
166+
pairs <- Map(list, guide = guides, params = params)
163167

164-
guides <- guides[has_position]
165-
guides_order <- vapply(guides, function(guide) as.numeric(guide$order)[1], numeric(1))
166-
Reduce(guide_merge, guides[order(guides_order)])
168+
# Early exit, nothing to merge
169+
if (length(pairs) == 1) {
170+
return(pairs[[1]])
171+
}
172+
173+
# TODO: There must be a smarter way to merge these
174+
order <- order(vapply(params, function(p) as.numeric(p$order), numeric(1)))
175+
Reduce(
176+
function(old, new) {
177+
old$guide$merge(old$params, new$guide, new$params)
178+
},
179+
pairs[order]
180+
)
167181
}

R/coord-polar.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,14 @@ CoordPolar <- ggproto("CoordPolar", Coord,
154154
details
155155
},
156156

157+
setup_panel_guides = function(self, panel_params, guides, params = list()) {
158+
panel_params
159+
},
160+
161+
train_panel_guides = function(self, panel_params, layers, default_mapping, params = list()) {
162+
panel_params
163+
},
164+
157165
transform = function(self, data, panel_params) {
158166
data <- rename_data(self, data)
159167

0 commit comments

Comments
 (0)