Skip to content

Commit 3b8dc69

Browse files
authored
Merge pull request #104 from animint/feature-fill-off
Support for feature `fill_off`
2 parents 12b57c9 + 54c92d9 commit 3b8dc69

File tree

4 files changed

+211
-30
lines changed

4 files changed

+211
-30
lines changed

NEWS.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Changes in 2023.10.6
2+
3+
- User-configurable selection style - fill_off.
4+
15
# Changes in 2023.6.11
26

37
- Remove maptools dependency.
@@ -12,9 +16,6 @@
1216
# Changes in 2022.9.14
1317

1418
- Include Yufan Fei as contributor in DESCRIPTION.
15-
16-
# Changes in 2022.9.14
17-
1819
- Allow configurable legend/axis text size via theme.
1920

2021
# Changes in 2022.8.31
@@ -92,4 +93,4 @@
9293

9394
# Changes in 2017.08.24
9495

95-
- DSL: clickSelects/showSelected are now specified as parameters rather than aesthetics.
96+
- DSL: clickSelects/showSelected are now specified as parameters rather than aesthetics.

R/geom-.r

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ Geom <- gganimintproto("Geom",
307307
g.data <- processed_values$g.data
308308

309309
## Check g.data for color/fill - convert to hexadecimal so JS can parse correctly.
310-
for(color.var in c("colour", "color", "fill", "colour_off", "color_off")){
310+
for(color.var in c("colour", "color", "fill", "colour_off", "color_off", "fill_off")){
311311
if(color.var %in% names(g.data)){
312312
g.data[,color.var] <- toRGB(g.data[,color.var])
313313
}
@@ -330,6 +330,14 @@ Geom <- gganimintproto("Geom",
330330
warning(sprintf("%s has %s which is not used because this geom has no clickSelects; please specify clickSelects or remove %s",
331331
g$classed, paste(off.vec, collapse=", "), paste(off.vec, collapse=", ")))
332332
}
333+
334+
## raise warning for geoms does not support fill
335+
has.fill.off <- any(names(g$params) == "fill_off")
336+
no.fill.geom <- c("path", "line", "segment", "linerange", "hline", "vline")
337+
if (g$geom %in% no.fill.geom && has.fill.off) {
338+
g$params <- g$params[!names(g$params) %in% "fill_off"]
339+
warning(sprintf("%s has fill_off which is not supported.", g$classed))
340+
}
333341
## TODO: coord_transform maybe won't work for
334342
## geom_dotplot|rect|segment and polar/log transformations, which
335343
## could result in something nonlinear. For the time being it is

inst/htmljs/animint.js

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -184,25 +184,35 @@ var animint = function (to_select, json_file) {
184184
// geom. This is a hack and should be removed when we implement
185185
// the selected.color, selected.size, etc aesthetics.
186186
//
187-
// 2022.08.01 update: get rid of the hack of "rect stroke" to
188-
// implement a general function for alpha_off, color_off.
187+
// 2022.08.01 update: get rid of the hack of "rect stroke" to
188+
// implement a general function for alpha_off, color_off.
189189
// In order to have multiple styles functioning together
190-
// so here use array to store the styles.
190+
// so here use array to store the styles.
191191
// Default using alpha/opacity style, execpt rect/tile geom
192192
// rect/tile geom default using stroke style
193+
const checkProperty = (prop) =>
194+
g_info.params.hasOwnProperty(prop) || g_info.aes.hasOwnProperty(prop);
195+
193196
let select_styles = [];
194-
let has_colour_off = g_info.params.hasOwnProperty("colour_off") || g_info.aes.hasOwnProperty("colour_off");
195-
let has_alpha_off = g_info.params.hasOwnProperty("alpha_off") || g_info.aes.hasOwnProperty("alpha_off");
196-
if(has_colour_off || g_info.geom == "rect"){
197-
select_styles.push("stroke");
198-
}
199-
if (has_alpha_off){
200-
select_styles.push("opacity");
201-
}
202-
if (!has_colour_off && !has_alpha_off && !select_styles.length){
203-
select_styles = ["opacity"];
197+
const has_colour_off = checkProperty('colour_off');
198+
const has_alpha_off = checkProperty('alpha_off');
199+
const has_fill_off = checkProperty('fill_off');
200+
201+
if (has_colour_off || g_info.geom === 'rect') {
202+
select_styles.push('stroke');
203+
}
204+
if (has_alpha_off) {
205+
select_styles.push('opacity');
204206
}
207+
if (has_fill_off) {
208+
select_styles.push('fill');
209+
}
210+
if (!select_styles.length) {
211+
select_styles = ['opacity'];
212+
}
213+
205214
g_info.select_style = select_styles;
215+
206216
// Determine if data will be an object or an array.
207217
if(g_info.geom in data_object_geoms){
208218
g_info.data_is_object = true;
@@ -228,8 +238,8 @@ var animint = function (to_select, json_file) {
228238
g_info.common_tsv = common_tsv;
229239
var common_path = getTSVpath(common_tsv);
230240
d3.tsv(common_path, function (error, response) {
231-
var converted = convert_R_types(response, g_info.types);
232-
g_info.data[common_tsv] = nest_by_group.map(converted);
241+
var converted = convert_R_types(response, g_info.types);
242+
g_info.data[common_tsv] = nest_by_group.map(converted);
233243
});
234244
} else {
235245
g_info.common_tsv = null;
@@ -1184,6 +1194,16 @@ var animint = function (to_select, json_file) {
11841194
fill = g_info.params.colour;
11851195
}
11861196

1197+
const get_fill_off = function (d) {
1198+
let off_fill;
1199+
if (aes.hasOwnProperty("fill_off") && d.hasOwnProperty("fill_off")) {
1200+
off_fill = d["fill_off"];
1201+
} else if (g_info.params.hasOwnProperty("fill_off")) {
1202+
off_fill = g_info.params.fill_off;
1203+
}
1204+
return off_fill;
1205+
};
1206+
11871207
// For aes(hjust) the compiler should make an "anchor" column.
11881208
var text_anchor = "middle";
11891209
if(g_info.params.hasOwnProperty("anchor")){
@@ -1219,6 +1239,9 @@ var animint = function (to_select, json_file) {
12191239
if(!g_info.select_style.includes("opacity")){
12201240
e.style("opacity", get_alpha);
12211241
}
1242+
if(!g_info.select_style.includes("fill")){
1243+
e.style("fill", get_fill);
1244+
}
12221245
};
12231246
if(g_info.data_is_object) {
12241247

@@ -1487,7 +1510,6 @@ var animint = function (to_select, json_file) {
14871510
eActions = function (e) {
14881511
e.attr("x", toXY("x", "x"))
14891512
.attr("y", toXY("y", "y"))
1490-
.style("fill", get_colour)
14911513
.attr("font-size", get_size)
14921514
.style("text-anchor", get_text_anchor)
14931515
.attr("transform", get_angle)
@@ -1503,7 +1525,6 @@ var animint = function (to_select, json_file) {
15031525
e.attr("cx", toXY("x", "x"))
15041526
.attr("cy", toXY("y", "y"))
15051527
.attr("r", get_size)
1506-
.style("fill", get_fill)
15071528
.style("stroke-width", get_stroke_width);
15081529
select_style_fun(g_info, e);
15091530
};
@@ -1518,7 +1539,6 @@ var animint = function (to_select, json_file) {
15181539
})
15191540
.attr("y", scales.y.range()[1])
15201541
.attr("height", scales.y.range()[0] - scales.y.range()[1])
1521-
.style("fill", get_fill)
15221542
.style("stroke-dasharray", get_dasharray)
15231543
.style("stroke-width", get_size);
15241544
select_style_fun(g_info, e);
@@ -1534,7 +1554,6 @@ var animint = function (to_select, json_file) {
15341554
})
15351555
.attr("x", scales.x.range()[0])
15361556
.attr("width", scales.x.range()[1] - scales.x.range()[0])
1537-
.style("fill", get_fill)
15381557
.style("stroke-dasharray", get_dasharray)
15391558
.style("stroke-width", get_size);
15401559
select_style_fun(g_info, e);
@@ -1560,7 +1579,6 @@ var animint = function (to_select, json_file) {
15601579
})
15611580
.style("stroke-dasharray", get_dasharray)
15621581
.style("stroke-width", get_size)
1563-
.style("fill", get_fill);
15641582
select_style_fun(g_info, e);
15651583
};
15661584
eAppend = "rect";
@@ -1626,7 +1644,6 @@ var animint = function (to_select, json_file) {
16261644
})
16271645
.style("stroke-dasharray", get_dasharray)
16281646
.style("stroke-width", get_size)
1629-
.style("fill", get_fill);
16301647
select_style_fun(g_info, e);
16311648
e.append("line")
16321649
.attr("x1", function (d) {
@@ -1659,11 +1676,13 @@ var animint = function (to_select, json_file) {
16591676
var selected_funs = function(style_name, select_fun){
16601677
style_on_funs = {
16611678
"opacity": get_alpha,
1662-
"stroke": get_colour
1679+
"stroke": get_colour,
1680+
"fill": get_fill
16631681
};
16641682
style_off_funs = {
16651683
"opacity": get_alpha_off,
1666-
"stroke": get_colour_off
1684+
"stroke": get_colour_off,
1685+
"fill": get_fill_off
16671686
};
16681687
if(select_fun == "mouseout"){
16691688
return function (d) {
@@ -1752,8 +1771,13 @@ var animint = function (to_select, json_file) {
17521771
});
17531772
}
17541773
}else{//has neither clickSelects nor clickSelects.variable.
1755-
elements.style("opacity", get_alpha)
1756-
if(g_info.geom != "text"){
1774+
elements.style("opacity", get_alpha);
1775+
// geom_segment/linerange/hline/vline no `stroke` with no clickSelects
1776+
const excludedGeoms = ["segment", "linerange", "hline", "vline"];
1777+
if (!excludedGeoms.includes(g_info.geom)) {
1778+
elements.style("fill", get_fill);
1779+
}
1780+
if(g_info.geom != "text"){ // geom_text no `stroke` with no clickSelects
17571781
elements.style("stroke", get_colour);
17581782
}
17591783
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
acontext("fill_off")
2+
3+
#
4+
# Test geoms with both fill and colour styles
5+
#
6+
viz.point <- list(
7+
defaultAlphaOff = ggplot() +
8+
geom_point(
9+
data = mtcars,
10+
size = 10,
11+
aes(
12+
x = wt, y = mpg,
13+
colour = disp
14+
),
15+
clickSelects = "gear"
16+
) +
17+
ggtitle("default alpha_off(0.5) style"),
18+
fillOffSpecified = ggplot() +
19+
geom_point(
20+
data = mtcars,
21+
fill_off = "transparent",
22+
size = 10,
23+
aes(
24+
x = wt, y = mpg,
25+
fill = disp
26+
),
27+
clickSelects = "gear"
28+
) +
29+
ggtitle("colour corresponding to `disp` group, fill_off=\"transparent\" "),
30+
fillAndAlphaOff = ggplot() +
31+
geom_point(
32+
data = mtcars,
33+
alpha_off = 0.5,
34+
fill_off = "grey",
35+
size = 10,
36+
aes(
37+
x = wt, y = mpg,
38+
fill = disp,
39+
id = paste0("fillAndAlphaOff_disp", disp, "gear", gear, "wt", wt)
40+
),
41+
clickSelects = "gear"
42+
) +
43+
ggtitle("fill_off + alpha_off")
44+
)
45+
46+
viz_info <- animint2HTML(viz.point)
47+
48+
test_that("fill_off only changes fill when clicked, colour does not change", {
49+
point.xpath <- '//svg[@id="plot_fillAndAlphaOff"]//circle[@id="fillAndAlphaOff_disp275.8gear3wt3.73"]'
50+
circle.list <- getNodeSet(viz_info$html, point.xpath)
51+
before.click.color <- getStyleValue(viz_info$html, point.xpath, "stroke")
52+
before.click.fill <- getStyleValue(viz_info$html, point.xpath, "fill")
53+
54+
clickID("fillAndAlphaOff_disp275.8gear3wt3.73")
55+
html <- getHTML()
56+
after.click.color <- getStyleValue(html, point.xpath, "stroke")
57+
after.click.fill <- getStyleValue(html, point.xpath, "fill")
58+
59+
expect_false(isTRUE(all.equal(before.click.fill, after.click.fill)))
60+
expect_color(after.click.color, before.click.color)
61+
})
62+
63+
test_that("fill and color are not same", {
64+
point.xpath <- '//svg[@id="plot_fillAndAlphaOff"]//circle[@class="geom"]'
65+
circle.list <- getNodeSet(viz_info$html, point.xpath)
66+
circle.color <- getStyleValue(viz_info$html, point.xpath, "stroke")
67+
circle.fill <- getStyleValue(viz_info$html, point.xpath, "fill")
68+
expect_false(isTRUE(all.equal(circle.color, circle.fill)))
69+
})
70+
71+
rect.data <- data.frame(
72+
xmin = c(1, 3, 5),
73+
xmax = c(2, 4, 6),
74+
ymin = c(1, 2, 3),
75+
ymax = c(2, 3, 4),
76+
category = c("A", "B", "C")
77+
)
78+
79+
viz.rect <- list(rectFillOff = ggplot() +
80+
geom_rect(
81+
data = rect.data, aes(
82+
xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
83+
id = paste0("rectFillOff_", category)
84+
),
85+
color = "black", fill = "blue", fill_off = "transparent", clickSelects = "category"
86+
))
87+
88+
viz_info <- animint2HTML(viz.rect)
89+
90+
test_that("with fill_off, fill changes when clicked", {
91+
rect_xpath <- '//svg[@id="plot_rectFillOff"]//rect[@id="rectFillOff_A"]'
92+
93+
rect_list <- getNodeSet(viz_info$html, rect_xpath)
94+
95+
before_click_color <- getStyleValue(viz_info$html, rect_xpath, "stroke")
96+
before_click_fill <- getStyleValue(viz_info$html, rect_xpath, "fill")
97+
98+
clickID("rectFillOff_B")
99+
100+
html <- getHTML()
101+
102+
after_click_color <- getStyleValue(html, rect_xpath, "stroke")
103+
after_click_fill <- getStyleValue(html, rect_xpath, "fill")
104+
expect_false(isTRUE(all.equal(before_click_fill, after_click_fill)))
105+
})
106+
107+
vline.data <- data.frame(
108+
xintercept = c(1, 2, 3),
109+
category = c("A", "B", "C")
110+
)
111+
112+
viz.vline <- list(
113+
v = ggplot() +
114+
geom_vline(
115+
data = vline.data, aes(xintercept = xintercept, key = category,
116+
id = paste0("v_", category)),
117+
fill = "blue", fill_off = "grey", clickSelects = "category"
118+
) +
119+
ggtitle("Click to Select a Vertical Line")
120+
)
121+
122+
test_that("Warning message shows up when using fill_off parameter with geom_vline", {
123+
expect_warning(
124+
animint2HTML(viz.vline),
125+
"geom1_vline_v has fill_off which is not supported."
126+
)
127+
})
128+
129+
test_that("When using fill_off and clickSelects parameter with geom_vline, use default(alpha) selection style", {
130+
viz_info <- animint2HTML(viz.vline)
131+
132+
vline_xpath <- '//g[@class="geom1_vline_v"]//line[@id="v_A"]'
133+
134+
before_click_color <- getStyleValue(viz_info$html, vline_xpath, "stroke")
135+
before_click_opacity <- getStyleValue(viz_info$html, vline_xpath, "opacity")
136+
137+
clickID("v_B")
138+
139+
html <- getHTML()
140+
after_click_color <- getStyleValue(html, vline_xpath, "stroke")
141+
after_click_opacity <- getStyleValue(html, vline_xpath, "opacity")
142+
143+
expect_color(before_click_color, "black")
144+
expect_color(after_click_color, before_click_color)
145+
146+
expect_equal(before_click_opacity, "1")
147+
expect_equal(after_click_opacity, "0.5")
148+
})

0 commit comments

Comments
 (0)