Skip to content

Commit c6f96f4

Browse files
committed
better handling of colorscale inputs, fixes #1432
1 parent d06a053 commit c6f96f4

File tree

3 files changed

+108
-16
lines changed

3 files changed

+108
-16
lines changed

R/plotly_build.R

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,9 @@ plotly_build.plotly <- function(p, registerFrames = TRUE) {
373373

374374
p <- verify_guides(p)
375375

376+
# verify colorscale attributes are in a sensible data structure
377+
p <- verify_colorscale(p)
378+
376379
# verify plot attributes are legal according to the plotly.js spec
377380
p <- verify_attr_names(p)
378381
# box up 'data_array' attributes where appropriate
@@ -804,11 +807,6 @@ map_color <- function(traces, stroke = FALSE, title = "", colorway, na.color = "
804807
colorObj[c("cmin", "cmax")] <- NULL
805808
colorObj[["showscale"]] <- default(TRUE)
806809
traces[[i]] <- modify_list(colorObj, traces[[i]])
807-
traces[[i]]$colorscale <- as_df(traces[[i]]$colorscale)
808-
# sigh, contour colorscale doesn't support alpha
809-
if (grepl("contour", traces[[i]][["type"]])) {
810-
traces[[i]]$colorscale[, 2] <- strip_alpha(traces[[i]]$colorscale[, 2])
811-
}
812810
traces[[i]] <- structure(traces[[i]], class = c("plotly_colorbar", "zcolor"))
813811
next
814812
}
@@ -852,8 +850,6 @@ map_color <- function(traces, stroke = FALSE, title = "", colorway, na.color = "
852850
traces[[i]] <- modify_list(list(fillcolor = col), traces[[i]])
853851
}
854852

855-
# make sure the colorscale is going to convert to JSON nicely
856-
traces[[i]]$marker$colorscale <- as_df(traces[[i]]$marker$colorscale)
857853
}
858854
}
859855

R/utils.R

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,6 @@ colorway <- function(p = NULL) {
130130
# TODO: make this more unique?
131131
crosstalk_key <- function() ".crossTalkKey"
132132

133-
# modifyList turns elements that are data.frames into lists
134-
# which changes the behavior of toJSON
135-
as_df <- function(x) {
136-
if (is.null(x) || is.matrix(x)) return(x)
137-
if (is.list(x) && !is.data.frame(x)) {
138-
setNames(as.data.frame(x), NULL)
139-
}
140-
}
141-
142133
# arrange data if the vars exist, don't throw error if they don't
143134
arrange_safe <- function(data, vars) {
144135
vars <- vars[vars %in% names(data)]
@@ -658,6 +649,33 @@ verify_mode <- function(p) {
658649
p
659650
}
660651

652+
653+
verify_colorscale <- function(p) {
654+
p$x$data <- lapply(p$x$data, function(trace) {
655+
trace$colorscale <- colorscale_json(trace$colorscale)
656+
trace$marker$colorscale <- colorscale_json(trace$marker$colorscale)
657+
trace
658+
})
659+
p
660+
}
661+
662+
# Coerce `x` into a data structure that can map to a colorscale attribute.
663+
# Note that colorscales can either be the name of a scale (e.g., 'Rainbow') or
664+
# a 2D array (e.g., [[0, 'rgb(0,0,255)', [1, 'rgb(255,0,0)']])
665+
colorscale_json <- function(x) {
666+
if (!length(x)) return(x)
667+
if (is.character(x)) return(x)
668+
if (is.matrix(x)) {
669+
if (ncol(x) != 2) stop("A colorscale matrix requires two columns")
670+
x <- as.data.frame(x)
671+
x[, 1] <- as.numeric(x[, 1])
672+
}
673+
if (is.list(x) && length(x) == 2) {
674+
x <- setNames(as.data.frame(x), NULL)
675+
}
676+
x
677+
}
678+
661679
# if an object (e.g. trace.marker) contains a non-default attribute, it has been user-specified
662680
user_specified <- function(obj = NULL) {
663681
if (!length(obj)) return(FALSE)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
context("colorscales")
2+
3+
4+
test_that("Can specify marker.colorscale", {
5+
p <- plot_ly(
6+
x = c(-9, -6, -5, -3, -1),
7+
y = c(0, 1, 4, 5, 7),
8+
marker = list(
9+
color = 1:5,
10+
colorscale='Rainbow',
11+
showscale = TRUE
12+
)
13+
)
14+
l <- expect_doppelganger_built(p, "marker.colorscale")
15+
})
16+
17+
test_that("Can specify contour colorscale", {
18+
p <- plot_ly(
19+
x = c(-9, -6, -5, -3, -1),
20+
y = c(0, 1, 4, 5, 7),
21+
z = matrix(c(10, 10.625, 12.5, 15.625, 20, 5.625, 6.25, 8.125, 11.25, 15.625, 2.5, 3.125, 5, 8.125, 12.5, 0.625, 1.25, 3.125,
22+
6.25, 10.625, 0, 0.625, 2.5, 5.625, 10), nrow = 5, ncol = 5),
23+
type = "contour",
24+
colorscale = 'Rainbow'
25+
)
26+
l <- expect_doppelganger_built(p, "contour-colorscale")
27+
})
28+
29+
test_that("Can provide a color interpolation function", {
30+
p <- plot_ly(dat, x = 1:10, y = 1:10, color = 1:10, colors = scales::colour_ramp(c("red", "green")))
31+
l <- expect_doppelganger_built(p, "colorRamp")
32+
})
33+
34+
test_that("Can specify contour colorscale", {
35+
36+
plot_colorscale <- function(colorscale) {
37+
plot_ly(
38+
x = 1:10,
39+
y = 1:10,
40+
marker = list(
41+
color = 1:10,
42+
colorscale = colorScale,
43+
showscale = TRUE
44+
)
45+
)
46+
}
47+
48+
colorScale <- list(
49+
val = seq(0, 1, by = 0.1),
50+
col = pal(seq(0, 1, by = 0.1))
51+
)
52+
test_list <- plot_colorscale(colorScale)
53+
test_df <- plot_colorscale(as.data.frame(colorScale))
54+
test_matrix <- plot_colorscale(as.matrix(as.data.frame(colorScale)))
55+
56+
})
57+
58+
59+
60+
test_that("marker.colorscale overrides color", {
61+
plot_ly(
62+
x = c(-9, -6, -5, -3, -1),
63+
y = c(0, 1, 4, 5, 7),
64+
color = 1:5,
65+
marker = list(
66+
#color = 6:10,
67+
colorscale='Rainbow',
68+
showscale = TRUE
69+
)
70+
)
71+
l <- expect_doppelganger_built(p, "marker.colorscale")
72+
})
73+
74+
75+
test_that("contour colorscale supports alpha", {
76+
p <- plot_ly(z = volcano, type = "contour", stroke = I("black"), alpha = 0.1)
77+
l <- expect_doppelganger_built(p, "contour-alpha")
78+
})

0 commit comments

Comments
 (0)