Эффективный способ изменить выбранные точки в onRender() htmlWidgets
Я работаю над интерактивной графикой, которая отображает черные точки для 2 количественных переменных (A и B). Пользователь может выбрать порог. После этого строится только точка, которая выше этой пороговой величины, и серый прямоугольник, обозначающий область, в которой порог не прошел, также отображается. Кажется, все это работает нормально.
Однако сейчас я пытаюсь добиться того, чтобы пользователь мог выбирать определенные моменты. При этом выбранные точки должны изменить цвет с черного на красный. Когда пользователь выбирает новое подмножество точек, ранее выбранные точки возвращаются из красного в черный. Другими словами, единственные точки, которые кажутся красными на графике, должны быть последними выбранными.
К сожалению, эта часть, похоже, не работает. Проблемы прямо сейчас:
1) Выбранные точки остаются красными постоянно. 2) Если уже выделены красные точки, иногда невыбранные черные точки становятся красными. Вы можете увидеть пример этой ошибки (в картинках) внизу поста.
Я полагаю, что это происходит, потому что я просто рисую совершенно новый след красных точек, которые соответствуют выбранным значениям x и y (это ниже в коде, который комментируется как "// Добавить выбранные пользователем точки красного цвета"). Следовательно, красные точки становятся их собственной сущностью.
У меня вопрос: как эффективный способ решить эту проблему (чтобы выбранные точки становились красными, пока не выбрано другое подмножество?) Я использую термин эффективный, потому что я работаю с большими наборами данных (точки 100-1000 с), и так Я ищу способы сохранить скорость вычислений. Может быть, более эффективно просто изменить выбранные черные точки на красные, а не полностью перерисовывать наложенные красные точки? Тем не менее, я не уверен, как добиться этого с помощью синтаксиса трассировки Plotly.
Любая помощь будет принята с благодарностью.
Ниже приведена упрощенная версия моего скрипта.
library(plotly)
library(GGally)
library(htmlwidgets)
ui <- shinyUI(fluidPage(
sliderInput("thresh", "Threshold:", min = 0, max = 3, value=1, step=1),
plotlyOutput("myPlot"),
textOutput("selectedValues")
))
server <- shinyServer(function(input, output) {
thresh <- reactive(input$thresh)
set.seed(1)
dat <- data.frame(Row = paste0("Row",sample(c(1:20),20)), A=4*rnorm(20), B=4*rnorm(20))
dat$Row <- as.character(dat$Row)
minVal = min(dat[,-1])
maxVal = max(dat[,-1])
gg <- ggplot(data = dat, aes(x=A, y=B)) + coord_cartesian(xlim = c(minVal, maxVal), ylim = c(minVal, maxVal))
ggY <- ggplotly(gg)
output$myPlot <- renderPlotly(ggY %>% onRender("
function(el, x, data) {
var Points = [];
var Traces = [];
var selRows = [];
data.dat.forEach(function(row){
if(Math.abs(row['B']) > data.thresh) selRows.push(row);});
var xArr = [];
var yArr = [];
var keepIndex = [];
for (a=0; a<selRows.length; a++){
xArr.push(selRows[a]['A'])
yArr.push(selRows[a]['B'])
keepIndex.push(selRows[a]['Row'])
}
Points.push(keepIndex);
// Add points above the threshold in black
var tracePoints = {
x: xArr,
y: yArr,
hoverinfo: 'none',
mode: 'markers',
marker: {
color: 'black',
size: 4
}
};
// Add upper horizontal line of gray box
var hiLine = {
x: [-15,15],
y: [data.thresh,data.thresh],
mode: 'lines',
line: {
color: 'gray',
width: 1
},
opacity: 0.25,
hoverinfo: 'none'
};
// Add lower horizontal line of gray box
var lowLine = {
x: [-15,15],
y: [-1*data.thresh,-1*data.thresh],
mode: 'lines',
fill: 'tonexty',
line: {
color: 'gray',
width: 1
},
opacity: 0.25,
hoverinfo: 'none'
};
Traces.push(tracePoints);
Traces.push(hiLine);
Traces.push(lowLine);
Plotly.addTraces(el.id, Traces);
var idRows = []
for (a=0; a<data.dat.length; a++){
idRows.push(data.dat[a]['Row'])
}
el.on('plotly_selected', function(e) {
numSel = e.points.length
cN = e.points[0].curveNumber;
var pointNumbers = [];
var selData = [];
for (a=0; a<numSel; a++){
pointNumbers.push(e.points[a].pointNumber)
selData.push(data.dat[idRows.indexOf(Points[0][pointNumbers[a]])])
}
Shiny.onInputChange('selData', selData);
var Traces = [];
var xArr = [];
var yArr = [];
for (a=0; a<selData.length; a++){
xArr.push(selData[a]['A'])
yArr.push(selData[a]['B'])
}
// Add user-selected points in red
var traceRed = {
x: xArr,
y: yArr,
mode: 'markers',
marker: {
color: 'red',
size: 4
},
hoverinfo: 'none'
};
Traces.push(traceRed);
Plotly.addTraces(el.id, Traces);
})
}", data = list(dat=dat, thresh=thresh())))
selData <- reactive(input$selData)
output$selectedValues <- renderPrint({selData()})
})
shinyApp(ui, server)
Ниже приведен пример ошибки:
Скажем, я открываю приложение со всеми его значениями по умолчанию и просто Plotly-выбираю четыре верхние правильные точки. Результат выглядит хорошо, потому что только эти четыре точки окрашены в красный цвет, как показано ниже:
Теперь, скажем, я выбрал эти четыре верхних пункта. Результат больше не выглядит хорошим, потому что, хотя эти четыре точки окрашены в красный цвет, две дополнительные точки также были ошибочно окрашены в красный цвет:
1 ответ
Самый простой способ справиться с этим - просто отслеживать, что вы выбрали, и удалять предыдущую трассу, содержащую выбранные точки, если была предыдущая выборка. Для этого были сделаны следующие изменения:
- добавил счетчик
nseltrace
следить за тем, как часто мы выбираем вещи - добавил
Plotly.deleteTraces
оператор для удаления последней трассы (которая может быть указана с индексом -1. - это действие удаления охраняется
if (nseltrace>0)
заявление. - переформатировал точки выбора, чтобы поместить их во фрейм данных (они объединяются как символьный вектор)
- использовал
verbatumTextOutput
распечатать их, поскольку это более чистый формат.
Обновить:
- добавлен охранник, чтобы он выбирал только точки, принадлежащие
curveNumber
1, т.е. исходные данные, а не выбранные следы, которые следуют. Это приводило к тому, что уже выбранные точки вводились в недавно выбранный список дважды.
От того, является ли это наиболее эффективным способом, очевидно, зависит. Вы наносите на карту выбранные точки в предыдущих невыбранных представлениях, наихудший случай будет в два раза, но рисование точек будет очень быстрым, и типичное замедление для небольшого процента точек, вероятно, даже не измеримо. Я бы не стал оптимизировать это, если бы это не оказалось проблемой позже, а затем я бы использовал последние сюжетно, так как в настоящее время в базовой базе кода происходит довольно много оптимизации (и устранения ошибок).
Вот код:
library(plotly)
library(GGally)
library(htmlwidgets)
library(shiny)
ui <- shinyUI(fluidPage(
sliderInput("thresh", "Threshold:", min = 0, max = 3, value=1, step=1),
plotlyOutput("myPlot"),
verbatimTextOutput("selectedValues")
))
server <- shinyServer(function(input, output) {
thresh <- reactive(input$thresh)
set.seed(1)
dat <- data.frame(Row = paste0("Row",sample(c(1:20),20)), A=4*rnorm(20), B=4*rnorm(20))
dat$Row <- as.character(dat$Row)
minVal = min(dat[,-1])
maxVal = max(dat[,-1])
gg <- ggplot(data = dat, aes(x=A, y=B)) + coord_cartesian(xlim = c(minVal, maxVal), ylim = c(minVal, maxVal))
ggY <- ggplotly(gg)
output$myPlot <- renderPlotly(ggY %>% onRender("
function(el, x, data) {
var Points = [];
var Traces = [];
var selRows = [];
data.dat.forEach(function(row){
if(Math.abs(row['B']) > data.thresh) selRows.push(row);});
var xArr = [];
var yArr = [];
var keepIndex = [];
for (a=0; a<selRows.length; a++){
xArr.push(selRows[a]['A'])
yArr.push(selRows[a]['B'])
keepIndex.push(selRows[a]['Row'])
}
Points.push(keepIndex);
// Add points above the threshold in black
var tracePoints = {
x: xArr,
y: yArr,
hoverinfo: 'none',
mode: 'markers',
marker: {
color: 'black',
size: 4
}
};
// Add upper horizontal line of gray box
var hiLine = {
x: [-15,15],
y: [data.thresh,data.thresh],
mode: 'lines',
line: {
color: 'gray',
width: 1
},
opacity: 0.25,
hoverinfo: 'none'
};
// Add lower horizontal line of gray box
var lowLine = {
x: [-15,15],
y: [-1*data.thresh,-1*data.thresh],
mode: 'lines',
fill: 'tonexty',
line: {
color: 'gray',
width: 1
},
opacity: 0.25,
hoverinfo: 'none'
};
Traces.push(tracePoints);
Traces.push(hiLine);
Traces.push(lowLine);
Plotly.addTraces(el.id, Traces);
var idRows = []
for (a=0; a<data.dat.length; a++){
idRows.push(data.dat[a]['Row'])
}
var nseltrace = 0;
el.on('plotly_selected', function(e) {
console.log(e.points)
numSel = e.points.length
var pointNumbers = [];
var selData = [];
for (a=0; a<numSel; a++){
if (e.points[a].curveNumber==1){ // restrict to points in the original curve
pointNumbers.push(e.points[a].pointNumber)
selData.push(data.dat[idRows.indexOf(Points[0][pointNumbers[a]])])
}
}
Shiny.onInputChange('selData', selData);
var Traces = [];
var xArr = [];
var yArr = [];
for (a=0; a<selData.length; a++){
xArr.push(selData[a]['A'])
yArr.push(selData[a]['B'])
}
// Add user-selected points in red
var traceRed = {
x: xArr,
y: yArr,
mode: 'markers',
marker: {
color: 'red',
size: 4
},
hoverinfo: 'none'
};
if (nseltrace>0){
Plotly.deleteTraces(el.id,-1)
}
Traces.push(traceRed);
nseltrace = nseltrace+1
Plotly.addTraces(el.id, Traces);
})
}", data = list(dat=dat, thresh=thresh())))
selData <- reactive({
req(input$selData)
rawc <- input$selData
df <- data.frame(t(matrix(rawc,nrow=3)))
names(df) <- names(rawc)[1:3]
df
})
output$selectedValues <- renderPrint({selData()})
})
shinyApp(ui, server)
И вот снимок экрана: