Skip to content

Commit db80285

Browse files
committed
force user to specify inputs/events to register as part of plot object
1 parent 8377bec commit db80285

File tree

2 files changed

+61
-219
lines changed

2 files changed

+61
-219
lines changed

R/layout.R

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ rangeslider <- function(p, start = NULL, end = NULL, ...) {
101101
#' (see [here](https://github.com/ropensci/plotly/blob/master/inst/examples/rmd/MathJax/index.Rmd)
102102
#' for an **rmarkdown** example and
103103
#' [here](https://github.com/ropensci/plotly/blob/master/inst/examples/rmd/MathJax/index.Rmd) for a **shiny** example).
104+
#' @param shinyInputs plotly.js events to register as shiny input values
105+
#' @param shinyEvents plotly.js events to register as shiny input values with event priority
104106
#' @author Carson Sievert
105107
#' @export
106108
#' @examples
@@ -131,7 +133,9 @@ rangeslider <- function(p, start = NULL, end = NULL, ...) {
131133
#' config(p, locale = "zh-CN")
132134
#'
133135

134-
config <- function(p, ..., collaborate = TRUE, cloud = FALSE, locale = NULL, mathjax = NULL) {
136+
config <- function(p, ..., collaborate = TRUE, cloud = FALSE, locale = NULL, mathjax = NULL,
137+
shinyInputs = c("plotly_hover", "plotly_click", "plotly_selected", "plotly_relayout"),
138+
shinyEvents = c("plotly_doubleclick", "plotly_deselect", "plotly_afterplot")) {
135139

136140
if (!is.null(locale)) {
137141
p$dependencies <- c(
@@ -170,6 +174,9 @@ config <- function(p, ..., collaborate = TRUE, cloud = FALSE, locale = NULL, mat
170174
}
171175

172176
p$x$config$cloud <- cloud
177+
# TODO: validate event names
178+
p$x$config$shinyInputs <- shinyInputs
179+
p$x$config$shinyEvents <- shinyEvents
173180

174181
p
175182
}

inst/htmlwidgets/plotly.js

Lines changed: 53 additions & 218 deletions
Original file line numberDiff line numberDiff line change
@@ -298,181 +298,69 @@ HTMLWidgets.widget({
298298

299299
// send user input event data to shiny
300300
if (HTMLWidgets.shinyMode) {
301-
var priority = x.config.priority ? {priority: x.config.priority} : undefined;
302301

303-
// https://plot.ly/javascript/zoom-events/
304-
graphDiv.on('plotly_relayout', function(d) {
305-
Shiny.setInputValue(
306-
".clientValue-plotly_relayout-" + x.source,
307-
JSON.stringify(d),
308-
priority
309-
);
310-
});
311-
graphDiv.on('plotly_restyle', function(d) {
312-
Shiny.setInputValue(
313-
".clientValue-plotly_restyle-" + x.source,
314-
JSON.stringify(d),
315-
priority
316-
);
317-
});
318-
graphDiv.on('plotly_hover', function(d) {
319-
Shiny.setInputValue(
320-
".clientValue-plotly_hover-" + x.source,
321-
JSON.stringify(eventDataWithKey(d)),
322-
priority
323-
);
324-
});
325-
graphDiv.on('plotly_click', function(d) {
326-
Shiny.setInputValue(
327-
".clientValue-plotly_click-" + x.source,
328-
JSON.stringify(eventDataWithKey(d)),
329-
priority
330-
);
302+
// Some events clear other input values
303+
// TODO: always register these?
304+
var eventClearMap = {
305+
plotly_deselect: ["plotly_selected", "plotly_selecting", "plotly_brushed", "plotly_brushing", "plotly_click"],
306+
plotly_unhover: ["plotly_hover"],
307+
plotly_doubleclick: ["plotly_click"]
308+
};
309+
310+
Object.keys(eventClearMap).map(function(evt) {
311+
graphDiv.on(evt, function() {
312+
var inputsToClear = eventClearMap[evt];
313+
inputsToClear.map(function(input) {
314+
Shiny.setInputValue(".clientValue-" + input + "-" + x.source + "-" + priority, null);
315+
});
316+
});
331317
});
332-
graphDiv.on('plotly_selected', function(d) {
318+
319+
var eventDataFunctionMap = {
320+
plotly_click: eventDataWithKey,
321+
plotly_hover: eventDataWithKey,
322+
plotly_unhover: eventDataWithKey,
333323
// If 'plotly_selected' has already been fired, and you click
334324
// on the plot afterwards, this event fires `undefined`?!?
335325
// That might be considered a plotly.js bug, but it doesn't make
336326
// sense for this input change to occur if `d` is falsy because,
337327
// even in the empty selection case, `d` is truthy (an object),
338328
// and the 'plotly_deselect' event will reset this input
339-
if (d) {
340-
Shiny.setInputValue(
341-
".clientValue-plotly_selected-" + x.source,
342-
JSON.stringify(eventDataWithKey(d)),
343-
priority
344-
);
345-
var limits = d.range ? d.range : d.lassoPoints;
346-
Shiny.setInputValue(
347-
".clientValue-plotly_brush-" + x.source,
348-
JSON.stringify(limits),
349-
priority
350-
);
351-
}
352-
});
353-
graphDiv.on('plotly_selecting', function(d) {
354-
if (d) {
355-
Shiny.setInputValue(
356-
".clientValue-plotly_selecting-" + x.source,
357-
JSON.stringify(eventDataWithKey(d)),
358-
priority
359-
);
360-
var limits = d.range ? d.range : d.lassoPoints;
361-
Shiny.setInputValue(
362-
".clientValue-plotly_brushing-" + x.source,
363-
JSON.stringify(limits),
364-
priority
365-
);
366-
}
367-
});
368-
graphDiv.on('plotly_unhover', function(eventData) {
369-
Shiny.setInputValue(
370-
".clientValue-plotly_hover-" + x.source,
371-
null,
372-
priority
373-
);
374-
Shiny.setInputValue(
375-
".clientValue-plotly_unhover-" + x.source,
376-
JSON.stringify(el.id),
377-
{priority: "event"}
378-
);
379-
});
380-
graphDiv.on('plotly_doubleclick', function(eventData) {
381-
Shiny.setInputValue(
382-
".clientValue-plotly_click-" + x.source,
383-
null,
384-
priority
385-
);
386-
Shiny.setInputValue(
387-
".clientValue-plotly_doubleclick-" + x.source,
388-
JSON.stringify(el.id),
389-
{priority: "event"}
390-
);
391-
});
392-
393-
// 'plotly_deselect' is code for doubleclick when in select mode
394-
graphDiv.on('plotly_deselect', function(eventData) {
395-
Shiny.setInputValue(
396-
".clientValue-plotly_selected-" + x.source,
397-
null,
398-
priority
399-
);
400-
Shiny.setInputValue(
401-
".clientValue-plotly_selecting-" + x.source,
402-
null,
403-
priority
404-
);
405-
Shiny.setInputValue(
406-
".clientValue-plotly_brush-" + x.source,
407-
null,
408-
priority
409-
);
410-
Shiny.setInputValue(
411-
".clientValue-plotly_brushing-" + x.source,
412-
null,
413-
priority
414-
);
415-
Shiny.setInputValue(
416-
".clientValue-plotly_click-" + x.source,
417-
null,
418-
priority
419-
);
420-
Shiny.setInputValue(
421-
".clientValue-plotly_deselect-" + x.source,
422-
JSON.stringify(el.id),
423-
{priority: "event"}
424-
);
425-
});
426-
427-
graphDiv.on('plotly_clickannotation', function(d) {
428-
Shiny.setInputValue(
429-
".clientValue-plotly_clickannotation-" + x.source,
430-
JSON.stringify(d.fullAnnotation),
431-
priority
432-
);
433-
});
434-
435-
// This is a 'true' event -- always give it priority
436-
graphDiv.on('plotly_afterplot', function() {
437-
Shiny.setInputValue(
438-
".clientValue-plotly_afterplot-" + x.source,
439-
JSON.stringify(el.id),
440-
{priority: "event"}
441-
);
442-
});
443-
444-
var legendEventData = function(d) {
445-
// if legendgroup is not relevant just return the trace
446-
var trace = d.data[d.curveNumber];
447-
if (!trace.legendgroup) return trace;
448-
449-
// if legendgroup was specified, return all traces that match the group
450-
var legendgrps = d.data.map(function(trace){ return trace.legendgroup; });
451-
var traces = [];
452-
for (i = 0; i < legendgrps.length; i++) {
453-
if (legendgrps[i] == trace.legendgroup) {
454-
traces.push(d.data[i]);
455-
}
456-
}
457-
458-
return traces;
329+
plotly_selected: function(d) { if (d) { return eventDataWithKey(d); } },
330+
plotly_selecting: function(d) { if (d) { return eventDataWithKey(d); } },
331+
plotly_brushed: function(d) {
332+
if (d) { return d.range ? d.range : d.lassoPoints; }
333+
},
334+
plotly_brushing: function(d) {
335+
if (d) { return d.range ? d.range : d.lassoPoints; }
336+
},
337+
plotly_legendclick: legendEventData,
338+
plotly_legenddoubleclick: legendEventData,
339+
plotly_clickannotation: function(d) { return d.fullAnnotation }
459340
};
460341

461-
graphDiv.on('plotly_legendclick', function(d) {
462-
Shiny.setInputValue(
463-
".clientValue-plotly_legendclick-" + x.source,
464-
JSON.stringify(legendEventData(d)),
465-
priority
466-
);
342+
var registerShinyValue = function(event, asShinyEvent) {
343+
var eventDataPreProcessor = eventDataFunctionMap[event] || function(d) { return d ? d : el.id };
344+
// some events are unique to the R package
345+
var plotlyJSevent = (event == "plotly_brushed") ? "plotly_selected" : (event == "plotly_brushing") ? "plotly_selecting" : event;
346+
// register the event
347+
graphDiv.on(plotlyJSevent, function(d) {
348+
Shiny.setInputValue(
349+
".clientValue-" + event + "-" + x.source,
350+
JSON.stringify(eventDataPreProcessor(d)),
351+
asShinyEvent ? {priority: "event"} : undefined
352+
);
353+
});
354+
}
355+
356+
var shinyInputs = x.config.shinyInputs || [];
357+
shinyInputs.map(function(input) {
358+
return registerShinyValue(input, false);
467359
});
468-
graphDiv.on('plotly_legenddoubleclick', function(d) {
469-
Shiny.setInputValue(
470-
".clientValue-plotly_legenddoubleclick-" + x.source,
471-
JSON.stringify(legendEventData(d)),
472-
priority
473-
);
360+
var shinyEvents = x.config.shinyEvents || [];
361+
shinyEvents.map(function(event) {
362+
return registerShinyValue(event, true);
474363
});
475-
476364
}
477365

478366
// Given an array of {curveNumber: x, pointNumber: y} objects,
@@ -1053,6 +941,8 @@ function debounce(func, wait, immediate) {
1053941

1054942

1055943
if (HTMLWidgets.shinyMode) {
944+
window.plotlyInputEvents
945+
1056946
// This Shiny.addCustomMessageHandler() callback is fired once per flush
1057947
// (i.e. whenever an input value changes)
1058948
Shiny.addCustomMessageHandler("plotlyEventData", function(message) {
@@ -1069,61 +959,6 @@ if (HTMLWidgets.shinyMode) {
1069959
plotlyInputEvents.push(msgID);
1070960
crosstalk.var("plotlyInputEvents").set(plotlyInputEvents)
1071961

1072-
var eventDataFunctionMap = {
1073-
plotly_click: eventDataWithKey,
1074-
plotly_hover: eventDataWithKey,
1075-
plotly_unhover: eventDataWithKey,
1076-
// If 'plotly_selected' has already been fired, and you click
1077-
// on the plot afterwards, this event fires `undefined`?!?
1078-
// That might be considered a plotly.js bug, but it doesn't make
1079-
// sense for this input change to occur if `d` is falsy because,
1080-
// even in the empty selection case, `d` is truthy (an object),
1081-
// and the 'plotly_deselect' event will reset this input
1082-
plotly_selected: function(d) { if (d) { return eventDataWithKey(d); } },
1083-
plotly_selecting: function(d) { if (d) { return eventDataWithKey(d); } },
1084-
plotly_brushed: function(d) {
1085-
if (d) { return d.range ? d.range : d.lassoPoints; }
1086-
},
1087-
plotly_brushing: function(d) {
1088-
if (d) { return d.range ? d.range : d.lassoPoints; }
1089-
},
1090-
plotly_legendclick: legendEventData,
1091-
plotly_legenddoubleclick: legendEventData,
1092-
plotly_clickannotation: function(d) { return d.fullAnnotation }
1093-
};
1094-
var eventDataPreProcessor = eventDataFunctionMap[evt] || function(d) { return d ? d : el.id };
1095-
1096-
// some events are unique to the R package
1097-
var plotlyJSevent = (evt == "plotly_brushed") ? "plotly_selected" : (evt == "plotly_brushing") ? "plotly_selecting" : evt;
1098-
// Some events clear other input values
1099-
var eventClearMap = {
1100-
plotly_deselect: ["plotly_selected", "plotly_selecting", "plotly_brushed", "plotly_brushing", "plotly_click"],
1101-
plotly_unhover: ["plotly_hover"],
1102-
plotly_doubleclick: ["plotly_click"]
1103-
}
1104-
1105-
// register events for all DOM elements connected to this source id
1106-
var idMap = crosstalk.var("plotlySourceDomMap").get() || {};
1107-
var gids = idMap[src];
1108-
gids.map(function(id) {
1109-
var gid = document.getElementById(id);
1110-
// register the event
1111-
gid.on(plotlyJSevent, function(d) {
1112-
Shiny.setInputValue(
1113-
".clientValue-" + evt + "-" + src + "-" + priority,
1114-
JSON.stringify(eventDataPreProcessor(d)),
1115-
priority == "event" ? {priority: "event"} : undefined
1116-
);
1117-
});
1118-
Object.keys(eventClearMap).map(function(evt) {
1119-
gid.on(evt, function() {
1120-
var inputsToClear = eventClearMap[evt];
1121-
inputsToClear.map(function(input) {
1122-
Shiny.setInputValue(".clientValue-" + input + "-" + src + "-" + priority, null);
1123-
});
1124-
});
1125-
});
1126-
});
1127962
});
1128963
}
1129964

0 commit comments

Comments
 (0)