Skip to content

Feature/labels #929

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

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
996b69f
first stab at implementing title/subtitle/caption as annotations
cpsievert Feb 28, 2017
d50aa4c
merge conflicts
cpsievert Apr 6, 2017
1c242b5
merge conflicts
cpsievert Apr 27, 2017
cf11033
merge conflict
cpsievert May 9, 2017
984e6fe
query device size in RStudio instead of opening new device
cpsievert May 10, 2017
da99a14
dplyr hack is no longer necessary
cpsievert May 11, 2017
237b4ac
move closer towards the actual ggplot2 model for element_text() conve…
cpsievert May 11, 2017
432c0f0
remove a browser
cpsievert May 11, 2017
1536e4d
Merge branch 'master' into feature/labels
cpsievert May 11, 2017
63e6049
pass dots to createWidget()
cpsievert May 11, 2017
017e0ca
htmlwidgets padding messes with ggplotly() sizing
cpsievert May 11, 2017
1026861
adjustments for top/bottom axes
cpsievert May 11, 2017
20494fe
Merge branch 'master' into feature/labels
cpsievert May 16, 2017
f9e73bb
merge conflicts
cpsievert May 19, 2017
1940819
merge conflicts
cpsievert Aug 17, 2017
beb048b
label readjustment needs to occur on resize as well
cpsievert Aug 17, 2017
aec9efe
Merge branch 'master' into feature/labels
cpsievert Aug 17, 2017
6b43c8e
drop 'legacy' support of ggplot2
cpsievert Aug 21, 2017
eed0321
obtain ggplot2's gtable (grob table) and train labels (as done in lay…
cpsievert Aug 21, 2017
0d6ab1c
require ggplot2 > 2.2.1
cpsievert Aug 21, 2017
37d1b00
start to leverage new summarise_layout() data structure
cpsievert Aug 21, 2017
22fb1d9
add a test
cpsievert Aug 21, 2017
dcdaf32
continue to leverage new data structures
cpsievert Aug 21, 2017
27715cb
converting to npc then pixels is WRONG
cpsievert Aug 21, 2017
159517f
Merge branch 'master' into feature/labels
cpsievert Aug 21, 2017
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
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ URL: https://plot.ly/r, https://cpsievert.github.io/plotly_book/, https://github
BugReports: https://github.com/ropensci/plotly/issues
Depends:
R (>= 3.2.0),
ggplot2 (>= 2.2.1)
ggplot2 (> 2.2.1)
Imports:
tools,
scales,
Expand Down
534 changes: 0 additions & 534 deletions R/ggplotly-legacy.R

This file was deleted.

407 changes: 252 additions & 155 deletions R/ggplotly.R

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion R/highlight.R
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ highlight <- function(p, on = "plotly_click", off,
p$dependencies <- c(p$dependencies, list(colourPickerLib()))
}


# TODO: expose unhover?
off_options <- paste0(
"plotly_", c("doubleclick", "deselect", "relayout")
Expand Down
37 changes: 18 additions & 19 deletions R/layers2traces.R
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ layers2traces <- function(data, prestats_data, layout, p) {
# 2. geom_smooth() is really geom_path() + geom_ribbon()
datz <- list()
paramz <- list()
layout <- if (is_dev_ggplot2()) layout else list(layout = layout)
for (i in seq_along(data)) {
# This has to be done in a loop, since some layers are really two layers,
# (and we need to replicate the data/params in those cases)
Expand Down Expand Up @@ -387,7 +386,7 @@ to_basic.GeomAbline <- function(data, prestats_data, layout, params, p, ...) {
data$group <- interaction(
data[!grepl("group", names(data)) & !vapply(data, anyNA, logical(1))]
)
lay <- tidyr::gather_(layout$layout, "variable", "x", c("x_min", "x_max"))
lay <- tidyr::gather_(layout$layout, "variable", "x", c("xmin", "xmax"))
data <- merge(lay[c("PANEL", "x")], data, by = "PANEL")
data[["y"]] <- with(data, intercept + slope * x)
prefix_class(data, c("GeomHline", "GeomPath"))
Expand All @@ -399,7 +398,7 @@ to_basic.GeomHline <- function(data, prestats_data, layout, params, p, ...) {
data$group <- do.call(paste,
data[!grepl("group", names(data)) & !vapply(data, anyNA, logical(1))]
)
lay <- tidyr::gather_(layout$layout, "variable", "x", c("x_min", "x_max"))
lay <- tidyr::gather_(layout$layout, "variable", "x", c("xmin", "xmax"))
data <- merge(lay[c("PANEL", "x")], data, by = "PANEL")
data[["y"]] <- data$yintercept
prefix_class(data, c("GeomHline", "GeomPath"))
Expand All @@ -411,7 +410,7 @@ to_basic.GeomVline <- function(data, prestats_data, layout, params, p, ...) {
data$group <- do.call(paste,
data[!grepl("group", names(data)) & !vapply(data, anyNA, logical(1))]
)
lay <- tidyr::gather_(layout$layout, "variable", "y", c("y_min", "y_max"))
lay <- tidyr::gather_(layout$layout, "variable", "y", c("ymin", "ymax"))
data <- merge(lay[c("PANEL", "y")], data, by = "PANEL")
data[["x"]] <- data$xintercept
prefix_class(data, c("GeomVline", "GeomPath"))
Expand All @@ -428,7 +427,7 @@ to_basic.GeomErrorbar <- function(data, prestats_data, layout, params, p, ...) {
# width for ggplot2 means size of the entire bar, on the data scale
# (plotly.js wants half, in pixels)
data <- merge(data, layout$layout, by = "PANEL", sort = FALSE)
data$width <- (data[["xmax"]] - data[["x"]]) /(data[["x_max"]] - data[["x_min"]])
data$width <- (data[["xmax"]] - data[["x"]]) /(data[["xmax"]] - data[["xmin"]])
data$fill <- NULL
prefix_class(data, "GeomErrorbar")
}
Expand All @@ -438,7 +437,7 @@ to_basic.GeomErrorbarh <- function(data, prestats_data, layout, params, p, ...)
# height for ggplot2 means size of the entire bar, on the data scale
# (plotly.js wants half, in pixels)
data <- merge(data, layout$layout, by = "PANEL", sort = FALSE)
data$width <- (data[["ymax"]] - data[["y"]]) / (data[["y_max"]] - data[["y_min"]])
data$width <- (data[["ymax"]] - data[["y"]]) / (data[["ymax"]] - data[["ymin"]])
data$fill <- NULL
prefix_class(data, "GeomErrorbarh")
}
Expand Down Expand Up @@ -476,11 +475,11 @@ to_basic.GeomPointrange <- function(data, prestats_data, layout, params, p, ...)
#' @export
to_basic.GeomDotplot <- function(data, prestats_data, layout, params, p, ...) {
if (identical(params$binaxis, "y")) {
dotdia <- params$dotsize * data$binwidth[1]/(layout$layout$y_max - layout$layout$y_min)
dotdia <- params$dotsize * data$binwidth[1]/(layout$layout$ymax - layout$layout$ymin)
data$size <- as.numeric(grid::convertHeight(grid::unit(dotdia, "npc"), "mm")) / 2
data$x <- (data$countidx - 0.5) * (as.numeric(dotdia) * 6)
} else {
dotdia <- params$dotsize * data$binwidth[1]/(layout$layout$x_max - layout$layout$x_min)
dotdia <- params$dotsize * data$binwidth[1]/(layout$layout$xmax - layout$layout$xmin)
data$size <- as.numeric(grid::convertWidth(grid::unit(dotdia, "npc"), "mm")) / 2
# TODO: why times 6?!?!
data$y <- (data$countidx - 0.5) * (as.numeric(dotdia) * 6)
Expand Down Expand Up @@ -516,9 +515,9 @@ utils::globalVariables(c("xmin", "xmax", "y", "size"))
to_basic.GeomRug <- function(data, prestats_data, layout, params, p, ...) {
# allow the tick length to vary across panels
layout <- layout$layout
layout$tickval_y <- 0.03 * abs(layout$y_max - layout$y_min)
layout$tickval_x <- 0.03 * abs(layout$x_max - layout$x_min)
data <- merge(data, layout[c("PANEL", "x_min", "x_max", "y_min", "y_max", "tickval_y", "tickval_x")])
layout$tickval_y <- 0.03 * abs(layout$ymax - layout$ymin)
layout$tickval_x <- 0.03 * abs(layout$xmax - layout$xmin)
data <- merge(data, layout[c("PANEL", "xmin", "xmax", "ymin", "ymax", "tickval_y", "tickval_x")])

# see GeomRug$draw_panel()
rugs <- list()
Expand All @@ -530,8 +529,8 @@ to_basic.GeomRug <- function(data, prestats_data, layout, params, p, ...) {
data, data.frame(
x = x,
xend = x,
y = y_min,
yend = y_min + tickval_y,
y = ymin,
yend = ymin + tickval_y,
others
)
)
Expand All @@ -541,8 +540,8 @@ to_basic.GeomRug <- function(data, prestats_data, layout, params, p, ...) {
data, data.frame(
x = x,
xend = x,
y = y_max - tickval_y,
yend = y_max,
y = ymax - tickval_y,
yend = ymax,
others
)
)
Expand All @@ -552,8 +551,8 @@ to_basic.GeomRug <- function(data, prestats_data, layout, params, p, ...) {
if (grepl("l", sides)) {
rugs$l <- with(
data, data.frame(
x = x_min,
xend = x_min + tickval_x,
x = xmin,
xend = xmin + tickval_x,
y = y,
yend = y,
others
Expand All @@ -563,8 +562,8 @@ to_basic.GeomRug <- function(data, prestats_data, layout, params, p, ...) {
if (grepl("r", sides)) {
rugs$r <- with(
data, data.frame(
x = x_max - tickval_x,
xend = x_max,
x = xmax - tickval_x,
xend = xmax,
y = y,
yend = y,
others
Expand Down
3 changes: 2 additions & 1 deletion R/plotly.R
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,8 @@ as_widget <- function(x, ...) {
sizingPolicy = htmlwidgets::sizingPolicy(
browser.fill = TRUE,
defaultWidth = '100%',
defaultHeight = 400
defaultHeight = 400,
padding = 0
),
preRenderHook = plotly_build,
dependencies = c(
Expand Down
2 changes: 1 addition & 1 deletion R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ to_milliseconds <- function(x) {
retain <- function(x, f = identity) {
y <- structure(f(x), class = oldClass(x))
attrs <- attributes(x)
# TODO: do we set any other "special" attributes internally
# TODO: do we set any other "special" attributes internally?
# (grepping "structure(" suggests no)
attrs <- attrs[names(attrs) %in% c("defaultAlpha", "apiSrc")]
if (length(attrs)) {
Expand Down
46 changes: 33 additions & 13 deletions inst/htmlwidgets/plotly.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ HTMLWidgets.widget({
},

resize: function(el, width, height, instance) {

var gd = document.getElementById(el.id);

if (instance.autosize) {
var width = instance.width || width;
var height = instance.height || height;
Plotly.relayout(el.id, {width: width, height: height});
Plotly.relayout(gd, {width: width, height: height});
ggplotlyAdjustLabels(gd);
}
},

renderValue: function(el, x, instance) {

if (typeof(window) !== "undefined") {
Expand Down Expand Up @@ -159,8 +163,11 @@ HTMLWidgets.widget({

}

// Trigger plotly.js calls defined via `plotlyProxy()`
plot.then(function() {
plot.then(function(gd) {

ggplotlyAdjustLabels(gd);

// Trigger plotly.js calls defined via `plotlyProxy()`
if (HTMLWidgets.shinyMode) {
Shiny.addCustomMessageHandler("plotly-calls", function(msg) {
var gd = document.getElementById(msg.id);
Expand All @@ -174,6 +181,7 @@ HTMLWidgets.widget({
Plotly[msg.method].apply(null, args);
});
}

});

// Attach attributes (e.g., "key", "z") to plotly event data
Expand Down Expand Up @@ -375,7 +383,8 @@ HTMLWidgets.widget({
selectize.addItems(e.value, true);
selectize.close();
}
}
};

selection.on("change", selectionChange);

// Set a crosstalk variable selection value, triggering an update
Expand Down Expand Up @@ -438,14 +447,7 @@ HTMLWidgets.widget({
}
});
}









}

} // end of renderValue
Expand Down Expand Up @@ -821,3 +823,21 @@ function removeBrush(el) {
outlines[i].remove();
}
}


// for ggplotly labels, scale annotation height/width to match graph size
function ggplotlyAdjustLabels(gd) {
var layout = gd.layout || {};
var anns = layout.annotations || [];
for (var i = 0; i < anns.length; i++) {
var container = {};
if (anns[i].ggplotlyDirection === "horizontal") {
container['annotations[' + i + '].width'] = gd._fullLayout._size.w;
Plotly.relayout(gd, container);
}
if (anns[i].ggplotlyDirection === "vertical") {
container['annotations[' + i + '].height'] = gd._fullLayout._size.h;
Plotly.relayout(gd, container);
}
}
}
58 changes: 49 additions & 9 deletions tests/testthat/test-ggplot-labels.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ test_that("ggtitle is translated correctly", {
geom_point(aes(Petal.Width, Sepal.Width)) +
ggtitle("My amazing plot!")
info <- save_outputs(ggiris, "labels-ggtitle")
# TODO: change me to annotation to support justification
expect_identical(info$layout$title, "My amazing plot!")
})

Expand All @@ -17,15 +18,14 @@ test_that("ylab is translated correctly", {
expect_identical(labs, c("Petal.Width", "sepal width"))
})

# TODO: why is this failing on R-devel???
#test_that("scale_x_continuous(name) is translated correctly", {
# ggiris <- ggplot(iris) +
# geom_point(aes(Petal.Width, Sepal.Width)) +
# scale_x_continuous("petal width")
# info <- save_outputs(ggiris, "labels-scale_x_continuous_name")
# labs <- unlist(lapply(info$layout$annotations, "[[", "text"))
# expect_identical(sort(labs), c("petal width", "Sepal.Width"))
#})
test_that("scale_x_continuous(name) is translated correctly", {
ggiris <- ggplot(iris) +
geom_point(aes(Petal.Width, Sepal.Width)) +
scale_x_continuous("petal width")
info <- save_outputs(ggiris, "labels-scale_x_continuous_name")
labs <- c(info$layout$xaxis$title, info$layout$yaxis$title)
expect_identical(labs, c("petal width", "Sepal.Width"))
})

test_that("angled ticks are translated correctly", {
ggiris <- ggplot(iris) +
Expand All @@ -34,3 +34,43 @@ test_that("angled ticks are translated correctly", {
info <- save_outputs(ggiris, "labels-angles")
expect_identical(info$layout$xaxis$tickangle, -45)
})

test_that("labels are translated correctly", {
ggiris <- ggplot(iris) +
geom_point(aes(Petal.Width, Sepal.Width)) +
ggtitle("My amazing plot!") +
labs(
subtitle = "Some loooooooooooooooooooooooooooooooooooooooooooooong text",
caption = "Some loooooooooooooooooooooooooooong text"
)
info <- save_outputs(ggiris, "labels-ggtitle")
# TODO: change me to annotation to support justification
expect_identical(info$layout$title, "My amazing plot!")
})

# TODO: why is the right plot margin off?
ggplotly(qplot(data = mtcars, vs, mpg))

# TODO:
# (1) how to handle text being clipped to the width?
# (2) test lineheight once we have a ability to set it

qplot(data = mtcars, vs, mpg, color = factor(am)) +
labs(
title = "sadlknewldknewflkcewelkmcdewdlm;dscklmcdslkmcds",
subtitle = "Some looooooooooooo0000000000000000oooooooooooooooooooooooooooong text",
caption = "Some loooooooooooooooooooooooooooong text",
x = "Silly",
y = "Yar har har"
) +
theme(
plot.title = element_text(hjust = 0.5, debug = TRUE),
plot.subtitle = element_text(hjust = 0.5, size = 20, debug = TRUE),
plot.caption = element_text(debug = TRUE),
axis.title.x = element_text(hjust = 0.5, vjust = 0.75, angle = 90, debug = TRUE),
axis.title.y = element_text(hjust = 0.5, vjust = 0.25, angle = 45, debug = TRUE),
# TODO: why doesn't debug get respected here?
legend.title = element_text(size = 7, debug = TRUE),
# TODO:
legend.title.align = 0.5
)
19 changes: 19 additions & 0 deletions tests/testthat/test-ggplot2-api.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
context("ggplot-api")


test_that("summarise_layout() gives the expected summary", {
p <- ggplot(mpg, aes(displ, hwy)) + geom_point() + facet_wrap(drv ~ cyl)
built <- ggplot_build(p)
layout <- summarise_layout(built)

expect_true(nrow(layout) == 9)
expect_equal(lengths(layout$vars), rep(2, 9))
# we access most (if not all) of these variables in the summary
expect_length(
setdiff(
c('panel', 'row', 'col', 'vars', 'xmin', 'xmax', 'ymin', 'ymax', 'xscale', 'yscale'),
names(layout)
), 0
)

})