在Shiny App中创建一个knobInput小部件

时间:2017-11-13 10:05:44

标签: r shiny widget

我很快就尝试根据jQuery knob library创建一个闪亮的旋钮自定义输入小部件。此外,我还根据我的具体情况调整了Rstudio tutorial。 这就是我得到的here

更改输入的唯一方法是在旋钮文本输入区输入其值。但是,我希望在向上或向下滚动以及使用鼠标拖动时更改此输入。如何修改代码以实现此目标?

以下是js代码:

  1. 旋钮input.R

    # This function generates the client-side HTML for a knob input
    knobInput <- function(inputId, label, value = NULL, min = 0, max = 100, 
                  angleArc = 360, angleOffset = 0, stopper = TRUE,
                  rotation = "clockwise", skin = "tron") {
      tagList(
        # This makes web page load the JS file in the HTML head.
        # The call to singleton ensures it's only included once
        # in a page.
        shiny::singleton(
          shiny::tags$head(
            shiny::tags$script(src = "jquery.knob.js"),
            shiny::tags$script(src = "knob-input-binding.js")
          )
        ),
    
        shiny::tags$label(label, `for` = inputId),
        shiny::tags$input(id = inputId, type = "text", value = value, class = "dial",
                  "data-value" = value,
                  "data-min" = min,
                  "data-max" = max,
                  "data-angleArc" = angleArc,
                  "data-angleOffset" = angleOffset,
                  "data-stopper" = stopper,
                  "data-rotation" = rotation,
                  "data-skin" = skin
                  )
      )
    }
    
    # Send an update message to a knob input on the client.
    # This update message can change the value and/or label.
    updateknobInput <- function(session, inputId,
                       label = NULL, value = NULL) {
    
      message <- dropNulls(list(label = label, value = value))
      session$sendInputMessage(inputId, message)
    }
    
    
    # Given a vector or list, drop all the NULL items in it
    dropNulls <- function(x) {
      x[!vapply(x, is.null, FUN.VALUE = logical(1))]
    }
    
  2. 旋钮输入binding.js

    // Knob input binding
    
    var knobInputBinding = new Shiny.InputBinding();
    
    
    // An input binding must implement these methods
    $.extend(knobInputBinding, {
    
      // This returns a jQuery object with the DOM element
      find: function(scope) {
       return $(scope).find('.dial');
      },
    
      // this method will be called on initialisation
      initialize: function(el){
    
         // extract the value from el
         // note here our knobInput does not yet exist
         var value = $(el).data("value");
    
         // initialize our knob based on the extracted state
         el.value = value;
      },
    
      // Given the DOM element for the input, return the value
      getValue: function(el) {
        return el.value;
      },
    
      // Set up the event listeners so that interactions with the
      // input will result in data being sent to server.
      // callback is a function that queues data to be sent to
      // the server.
      subscribe: function(el, callback) {
        $(el).on('keyup.knobInputBinding', function(event) {
          callback(true);
          // When called with true, it will use the rate policy,
          // which in this case is to debounce at 500ms.
        });
    
        $(el).on('change.knobInputBinding', function(event) {
          callback(false);
          // When called with false, it will NOT use the rate policy,
          // so changes will be sent immediately
        });
      },
    
      // Remove the event listeners
      unsubscribe: function(el) {
        $(el).off('.knobInputBinding');
      },
    
      // Receive messages from the server.
      // Messages sent by updateknobInput() are received by this function.
      receiveMessage: function(el, data) {
        if (data.hasOwnProperty('value'))
          this.initialize(el, data.value);
    
        if (data.hasOwnProperty('label'))
          $(el).parent().find('label[for="' + $escape(el.id) + '"]').text(data.label);
    
        $(el).trigger('change');
      },
    
      // This returns a full description of the input's state.
      // Note that some inputs may be too complex for a full description of the
      // state to be feasible.
      getState: function(el) {
        return {
          label: $(el).parent().find('label[for="' + $escape(el.id) + '"]').text(),
      value: el.value
        };
      },
    
      // The input rate limiting policy
      getRatePolicy: function() {
        return {
        // Can be 'debounce' or 'throttle'
          policy: 'debounce',
          delay: 500
        };
      }
    
    });
    
    Shiny.inputBindings.register(knobInputBinding, 'shiny.knobInput');
    
  3. 基本-knob.js

    $(function($) {
      $(".dial").knob();
    });
    
  4. (如果第三个代码段不存在,则无法正确呈现旋钮。)

    编辑:这是单个文件,shinyapp代码

    library(purrr)
    source("knob-input.R")
    
    ui <- fluidPage(
      titlePanel("Custom input example"),
    
      includeScript("www/basic-knob.js"),
    
      fluidRow(
        column(4, wellPanel(
          knobInput("knobval", "", value = 10),
          knobInput("knobval2", "", value = 20),
          actionButton("reset", "Reset Knob")
        )),
        column(8, wellPanel(
          verbatimTextOutput("value")
        ))
      )
    )
    
    server <- function(input, output, session) {
    
      output$value <- renderText({
        c(input$knobval, input$knobval2)
      })
    
      observe({
        # Run whenever reset button is pressed
        input$reset
        knobvec <- c("knobval", "knobval2")
        # Send an update to knobs, resetting their values
        map(knobvec, updateknobInput, session = session, value = 0)
      })
    }
    
    shinyApp(ui = ui, server = server)
    

    我的第二个问题是:我怎样才能改变使用的皮肤?例如,我想使用data-skin="tron",例如in the jQuery knob showcase

    非常感谢提前。

1 个答案:

答案 0 :(得分:2)

旋钮有一种特殊的方式来处理事件,触发更改你可以在你的订阅方法中添加这段代码:

$(el).trigger('configure', {
  'change': function (v) {
     callback(false);
  }
});

编辑:没有真正的“tron”皮肤,这是在初始化时在draw参数中手动实现的,您可以使用以下方式更改basic-knob.js文件:

$(function($) {
    $(".dial").knob({
        draw: function() {
            // "tron" case
            if (this.$.data('skin') == 'tron') {
                this.cursorExt = 0.3;
                var a = this.arc(this.cv) // Arc
                    ,
                    pa // Previous arc
                    , r = 1;
                this.g.lineWidth = this.lineWidth;
                if (this.o.displayPrevious) {
                    pa = this.arc(this.v);
                    this.g.beginPath();
                    this.g.strokeStyle = this.pColor;
                    this.g.arc(this.xy, this.xy, this.radius - this.lineWidth, pa.s, pa.e, pa.d);
                    this.g.stroke();
                }
                this.g.beginPath();
                this.g.strokeStyle = r ? this.o.fgColor : this.fgColor;
                this.g.arc(this.xy, this.xy, this.radius - this.lineWidth, a.s, a.e, a.d);
                this.g.stroke();
                this.g.lineWidth = 2;
                this.g.beginPath();
                this.g.strokeStyle = this.o.fgColor;
                this.g.arc(this.xy, this.xy, this.radius - this.lineWidth + 1 + this.lineWidth * 2 / 3, 0, 2 * Math.PI, false);
                this.g.stroke();
                return false;
            }
        }
    });
});