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

Create a knobInput widget in Shiny App

我根据我的具体情况 jQuery knob library. Besides, I also adapted the Rstudio tutorial 快速尝试在 shiny 中创建一个旋钮自定义输入小部件。 这就是我得到的 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();
    });
    

(如果不存在第三个代码片段,则无法正确呈现旋钮)。

编辑:这是单个文件,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

非常感谢。

knob 有一种特殊的方式来处理事件,要触发更改,您可以在订阅方法中添加以下代码:

$(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;
            }
        }
    });
});