Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
178 views
in Technique[技术] by (71.8m points)

javascript - Draggable line chart in R/Shiny

I have built an R/Shiny app which uses linear regression to predict some metrics.

In order to make this app more interactive, I need to add a line chart, where I can drag the points of the line chart, capture the new points and predict the values based on the new points.

Basically, I'm looking for something like this in RShiny. Any help on how to achieve this?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You could do it with R/Shiny + d3.js: A preview, reproducible example, code and a walkthrough can be found below.

Edit: 12/2018 - See the comment of MrGrumble:

"With d3 v5, I had to rename the events from dragstart and dragend to start and end, and change the line var drag = d3.behavior.drag() to var drag d3.drag()."

Reproducible example:

The easiest way is to clone this repository (https://github.com/Timag/DraggableRegressionPoints).

Preview:

Sry for poor gif quality: enter image description here

Explanation:

The code is based on d3.js+shiny+R. It includes a custom shiny function which i named renderDragableChart(). You can set color and radius of the circles. The implementation can be found in DragableFunctions.R.

Interaction of R->d3.js->R:

The location of the data points is initially set in R. See server.R:

df <- data.frame(x = seq(20,150, length.out = 10) + rnorm(10)*8,
                 y = seq(20,150, length.out = 10) + rnorm(10)*8)
df$y[1] = df$y[1] + 80

The graphic is rendered via d3.js. Additions like lines etc. have to be added there. The main gimmicks should be that the points are draggable and the changes should be send to R. The first is realised with .on('dragstart', function(d, i) {} and .on('dragend', function(d, i) {} , the latter with Shiny.onInputChange("JsData", coord);.

The code:

ui.R

includes a custom shiny function DragableChartOutput() which is defined in DragableFunctions.R.

library(shiny)
shinyUI( bootstrapPage( 
  fluidRow(
    column(width = 3,
           DragableChartOutput("mychart")
    ),
    column(width = 9,
           verbatimTextOutput("regression")
    )
  )
))

server.R

also basic shiny except for a custom function renderDragableChart().

library(shiny)
options(digits=2)
df <- data.frame(x = seq(20,150, length.out = 10) + rnorm(10)*8,
                 y = seq(20,150, length.out = 10) + rnorm(10)*8)
df$y[1] = df$y[1] + 80
#plot(df)
shinyServer( function(input, output, session) {

  output$mychart <- renderDragableChart({
    df
  }, r = 3, color = "purple")
  
  output$regression <- renderPrint({
    if(!is.null(input$JsData)){
      mat <- matrix(as.integer(input$JsData), ncol = 2, byrow = TRUE)
      summary(lm(mat[, 2] ~  mat[, 1]))
    }else{
      summary(lm(df$y ~  df$x))
    }
  })
})

The functions are defined in DragableFunctions.R. Note, it could also be implemented with library(htmlwidgets). I decided to implement it the long way as it isn′t much harder and you gain more understanding of the interface.

library(shiny)

dataSelect <- reactiveValues(type = "all")

# To be called from ui.R
DragableChartOutput <- function(inputId, width="500px", height="500px") {
  style <- sprintf("width: %s; height: %s;",
    validateCssUnit(width), validateCssUnit(height))
  tagList(
    tags$script(src = "d3.v3.min.js"),
    includeScript("ChartRendering.js"),
    div(id=inputId, class="Dragable", style = style,
      tag("svg", list())
    )
  )
}

# To be called from server.R
renderDragableChart <- function(expr, env = parent.frame(), quoted = FALSE, color = "orange", r = 10) {
  installExprFunction(expr, "data", env, quoted)
  function(){
    data <- lapply(1:dim(data())[1], function(idx) list(x = data()$x[idx], y = data()$y[idx], r = r))
    list(data = data, col = color)
  } 
}

Now we are only left with generating the d3.js code. This is done in ChartRendering.js. Basically the circles have to be created and "draggable functions" have to be added. As soon as a drag movement is finished we want the updated data to be send to R. This is realised in .on('dragend',.) with Shiny.onInputChange("JsData", coord);});. This data can be accessed in server.R with input$JsData.

var col = "orange";
var coord = [];
var binding = new Shiny.OutputBinding();

binding.find = function(scope) {
  return $(scope).find(".Dragable");
};

binding.renderValue = function(el, data) {
  var $el = $(el);
  var boxWidth = 600;  
  var boxHeight = 400;
  dataArray = data.data
  col = data.col
    var box = d3.select(el) 
            .append('svg')
            .attr('class', 'box')
            .attr('width', boxWidth)
            .attr('height', boxHeight);     
        var drag = d3.behavior.drag()  
        .on('dragstart', function(d, i) { 
                box.select("circle:nth-child(" + (i + 1) + ")")
                .style('fill', 'red'); 
            })
            .on('drag', function(d, i) { 
              box.select("circle:nth-child(" + (i + 1) + ")")
                .attr('cx', d3.event.x)
                .attr('cy', d3.event.y);
            })
      .on('dragend', function(d, i) { 
                circle.style('fill', col);
                coord = []
                d3.range(1, (dataArray.length + 1)).forEach(function(entry) {
                  sel = box.select("circle:nth-child(" + (entry) + ")")
                  coord = d3.merge([coord, [sel.attr("cx"), sel.attr("cy")]])                 
                })
                console.log(coord)
        Shiny.onInputChange("JsData", coord);
            });
            
        var circle = box.selectAll('.draggableCircle')  
                .data(dataArray)
                .enter()
                .append('svg:circle')
                .attr('class', 'draggableCircle')
                .attr('cx', function(d) { return d.x; })
                .attr('cy', function(d) { return d.y; })
                .attr('r', function(d) { return d.r; })
                .call(drag)
                .style('fill', col);
};

// Regsiter new Shiny binding
Shiny.outputBindings.register(binding, "shiny.Dragable");

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...