我有一个散点图,其中的点密集地聚集。放大其中的一些后,双击空白区域将重置轴,这正是我想要的。但是在很多情况下,它还会选择一个新点-这不是我想要的。毕竟,我双击了空白区域。我无意选择新的观点。
问题似乎是双重的。首先,each double click is also registered as a single click。其次,单击将记录在轴重置后的坐标上,而不是双击时实际看到的坐标。即使放大后单击空白区域,轴重置后的坐标也会映射到一个绘制的点。如何解决此问题?
这里是一个最小的例子。 Plotly图是从R生成的,但这似乎并不重要:
library(plotly)
x <- c(rnorm(3000, 0, 3), rnorm(1000, 0, 0.2))
y <- c(rnorm(3000, 0, 3), rnorm(1000, 0, 0.2))
groups <- rep(c("a", "b", "c", "d"), 1000)
myData <- highlight_key( data.frame(x, y, groups), ~groups )
myPlot <- plot_ly(
x = ~x, y = ~y,
color = ~groups,
data = myData)
highlight(myPlot, color = "red")
[This animated GIF显示了代码创建的图形,它也说明了问题。
问题出在noted before。但是我似乎无法通过双击时抢先使用默认的“单击选择”功能或使用任何其他策略来解决。我尝试过的几件事:
触发plotly_doubleclick
事件后,更改存储的JSON数据,以使x> Highlight> on为空。然后用Plotly.newPlot()
重画。
触发plotly_doubleclick
事件后,使用remove.listener()
禁用plotly_click
事件。但是到触发plotly_doubleclick
时,该策略似乎已经为时已晚:单击(plotly_click
)事件已经触发。
更改布局>从“先跟踪”到“先布局”
当检测到双击时,将plotly_click
事件处理程序推向return false
。 (我使用this method使plotly_click
事件处理程序检测到激活它的点击是否是双击的一部分。)此策略可能适用于点击图例,但似乎不适用于点击情节本身。
这些都不起作用。但是我认为必须有一个解决方案-在那里吗?
有解决方案。它需要(a)覆盖默认的点击行为,以及(b)补充默认的双击行为。在这两种情况下,我们都需要编写自定义事件处理程序。
[人们似乎倾向于通过引入一些延迟来确保单击既不是双击也不是第一次,也不是最后一次,来区分单击和双击。这是合理的,但是在像这样的应用程序中使用时,延迟很明显:单击绘制的点后,在突出显示该点之前会有明显的延迟。出现延迟的原因是单击(plotly_click
)事件处理程序正在等待确保触发它的单击不是双击的一部分。
幸运的是,我们不需要在此应用程序中引入该延迟。关键是要意识到完全没有必要区分单击和双击。我们只需要确保触发plotly_click
的点击不是双击中的第二次点击即可。我不确定为什么我们只需要检查这种情况。但这足够了,我们可以检查这种情况而不会在突出显示过程中引入任何明显的延迟。
这里是完成任务的代码。在R中:
library(plotly)
x <- c(rnorm(3000, 0, 3), rnorm(1000, 0, 0.2))
y <- c(rnorm(3000, 0, 3), rnorm(1000, 0, 0.2))
groups <- rep(c("a", "b", "c", "d"), 1000)
myData <- data.frame(x, y, groups)
myPlot <- plot_ly(
x = ~x, y = ~y,
color = ~groups,
data = myData)
myPlot$elementId <- "myPlot"
myPlot <- highlight(myPlot, on = NULL, off = "plotly_doubleclick")
onRender(myPlot, readLines("onRender.js"))
其中“ onRender.js”为
function singleClickHandler (data, el, COLORS_TRACE, OPACITY_START, OPACITY_DIM) {
let t0 = Date.now();
// If the triggering click wasn't the second click in a double click...
if ((t0 - doubleClickTime) > interval) {
highlightTrace(data, el, COLORS_TRACE, OPACITY_START, OPACITY_DIM);
}
}
function highlightTrace (data, el, OPACITY_START, OPACITY_DIM) {
// We want clicking on a point to "highlight" that point and all other
// points in the trace -- by dimming the points in all -other- traces.
const numTraces = el.data.length; // total # of traces in plot
const traceNum = data.points[0].curveNumber; // number of clicked trace
// Initialize array with one element for each trace
let traceOpacity = new Array(numTraces).fill().map( () => OPACITY_DIM );
// Set only the clicked-on trace to have normal (relatively high) opacity
traceOpacity[traceNum] = OPACITY_START;
// Restyle
Plotly.restyle("myPlot", { "marker.opacity": traceOpacity } );
}
function onRender (el) {
// Get opacity of first mark in first trace when figure is first displayed
const OPACITY_START = el._fullData[0].marker.opacity;
const OPACITY_DIM = 0.2;
// Set timing
interval = 1000; // two clicks within 1 second (1000 ms) is a double click
doubleClickTime = 0;
// Wrap the singleClickHandler() event handler in onSingleClick(). We do
// this so we can pass both event info ("data") and other objects to
// singleClickHandler().
var onSingleClick = (data) => singleClickHandler(data, el, OPACITY_START, OPACITY_DIM);
el.on('plotly_click', onSingleClick);
el.on('plotly_doubleclick', function (d) {
doubleClickTime = Date.now();
Plotly.restyle("myPlot", { "marker.opacity": OPACITY_START } );
});
}
onRender