如何绘制mqtt数据的实时折线图而无需刷新图表

时间:2018-02-08 07:54:52

标签: r shiny

我尝试从蚊子测试服务器获取流数据,以创建实时折线图。我检查了一些实时图表的例子,但我似乎无法达到同样的目标。图表是实时更新的,但它总是刷新。

这是我从一个例子编辑的脚本:

library(shiny)
library(magrittr)
library(mqtt)
library(jsonlite)
ui <- shinyServer(fluidPage(
plotOutput("plot")
))
server <- shinyServer(function(input, output, session){
myData <- data.frame()
# Function to get new observations
get_new_data <- function(){
d <- character()
mqtt::topic_subscribe(host = "test.mosquitto.org", port = 1883L, client_id       = "dcR", topic = "IoTDemoData", 
                      message_callback = 
                        function(id, topic, payload, qos, retain) {
                            if (topic == "IoTDemoData") {

                              d <<- readBin(payload, "character")
                              # print(received_payload)
                              # received_payload <- fromJSON(received_payload)
                              # print(d)                                  
                              return("quit")
                            }
                          }
                        )

d <- fromJSON(d)
d <- as.data.frame(d)
return(d)
# data <- rnorm(5) %>% rbind %>% data.frame
# return(data)
}

# Initialize my_data
myData <- get_new_data()

# Function to update my_data
update_data <- function(){
myData <<- rbind(get_new_data(), myData)
}

# Plot the 30 most recent values
output$plot <- renderPlot({
invalidateLater(1000, session)
update_data()
print(myData)
plot(temperature ~ 1, data=myData[1:30,], ylim=c(-20, -10), las=1, type="l")
})
})

shinyApp(ui=ui,server=server)

我一直在努力创建实时图表。如果有人可以指出问题为什么线图总是刷新和解决方案,将非常感谢!

以下是基于Florian回答的修订工作脚本:

library(shiny)
library(mqtt)
library(jsonlite)
library(ggplot2)


ui <- shinyServer(fluidPage(
plotOutput("mqttData")
))

server <- shinyServer(function(input, output, session){
myData <- reactiveVal()
get_new_data <- function(){
d <- character()
mqtt::topic_subscribe(host = "localhost", port = 1883L, client_id = "dcR",       topic = "IoTDemoData", 
message_callback = 
function(id, topic, payload, qos, retain) {
if (topic == "IoTDemoData") {
d <<- readBin(payload, "character")
return("quit")
}
}
)
d <- fromJSON(d)
d <- as.data.frame(d)
return(d)
}

observe({
invalidateLater(1000, session)
isolate({    
# fetch the new data
new_data <- get_new_data()
# If myData is empty, we initialize it with just the new data.
if(is.null(myData()))
myData(new_data)
else # row bind the new data to the existing data, and set that as the new    value.
myData(rbind(myData(),new_data))
})
})

output$mqttData <- renderPlot({
ggplot(mapping = aes(x = c(1:nrow(myData())), y = myData()$temperature)) +
geom_line() +
labs(x = "Second", y = "Celsius")
})
})

shinyApp(ui=ui,server=server) 

然而,在添加第二个图后,闪烁开始了。当我评论出其中一个图时,情节很好,无需刷新。 库(闪亮)     库(MQTT)     库(jsonlite)     库(GGPLOT2)

ui <- shinyServer(fluidPage(
  plotOutput("mqttData"),
  plotOutput("mqttData_RH")
))

server <- shinyServer(function(input, output, session){
  myData <- reactiveVal()
  get_new_data <- function(){
    d <- character()
    mqtt::topic_subscribe(host = "test.mosquitto.org", port = 1883L, client_id = "dcR", topic = "IoTDemoData", 
    # mqtt::topic_subscribe(host = "localhost", port = 1883L, client_id = "dcR", topic = "IoTDemoData", 
                      message_callback = 
                        function(id, topic, payload, qos, retain) {
                            if (topic == "IoTDemoData") {
                              d <<- readBin(payload, "character")
                              return("quit")
                            }
                          }
                        )
    d <- fromJSON(d)
    d <- as.data.frame(d)
    d$RH <- as.numeric(as.character( d$RH))

    return(d)
  }

  observe({
    invalidateLater(10000, session)
    isolate({    
      # fetch the new data
      new_data <- get_new_data()
      # If myData is empty, we initialize it with just the new data.
      if(is.null(myData()))
    myData(new_data)
      else # row bind the new data to the existing data, and set that as the new value.
    myData(rbind(myData(),new_data))
    })
  })

  output$mqttData <- renderPlot({
    ggplot(mapping = aes(x = c(1:nrow(myData())), y = myData()$temperature)) +
      geom_line() +
      labs(x = "Second", y = "Celsius")
  })
  output$mqttData_RH <- renderPlot({
    ggplot(mapping = aes(x = c(1:nrow(myData())), y = myData()$RH)) +
      geom_line() +
      labs(x = "Second", y = "RH %")
  })
})

shinyApp(ui=ui,server=server)

我发现一个解决方案在一个renderPlot对象中绘制图表。闪烁减少了。

output$mqttData <- renderPlot({
    myData() %>% 
      gather('Var', 'Val', c(temperature, RH)) %>% 
      ggplot(aes(timestamp,Val, group = 1))+geom_line()+facet_grid(Var ~ ., scales="free_y")
  })

但是,我想知道是否有办法单独绘制图表而不会闪烁/刷新。

我发现一个github示例使用管道%&gt;%(https://github.com/mokjpn/R_IoT)将数据放入ggplot2并修改它以绘制分离的图表。

library(shiny)
library(ggplot2)
library(tidyr)

# Dashboard-like layout
ui <- shinyServer(fluidPage(
  fluidRow(
    column(
      6,
      plotOutput("streaming_data_1")
    ),
    column(
      6,
      plotOutput("streaming_data_2")
    )
  ),
  fluidRow(
    column(
      6,
      plotOutput("streaming_data_3")
    ),
    column(
      6,
      plotOutput("streaming_data_4")
    )
  )
))

server <- shinyServer(function(input, output, session){
  myData <- reactiveVal()
  # show the first and last timestamp in the streaming charts
  realtime_graph_x_labels <- reactiveValues(first = "",last ="")

  get_new_data <- function(){
    epochTimeStamp <- as.character(as.integer(Sys.time()))
    sensor_1 <- -runif(1,min = 10, max = 30)
    sensor_2 <- runif(1,min = 0,max = 100)
    sensor_3 <- runif(1,min = 0,max = 100000)
    sensor_4 <- runif(1,min = 0,max = 10)
    newData <- data.frame(ts = epochTimeStamp, val_1 = sensor_1, val_2 = sensor_2, val_3 = sensor_3, val_4 = sensor_4)
    return(newData)
  }

  observe({
    invalidateLater(1000, session)
    isolate({    
      # fetch the new data
      new_data <- get_new_data()
      # If myData is empty, we initialize it with just the new data.
      if(is.null(myData()))
      {
    myData(new_data)
    realtime_graph_x_labels$first <- as.character(head(myData()$ts,1))
      }
      else # row bind the new data to the existing data, and set that as the new value.
    myData(rbind(myData(),new_data))

      realtime_graph_x_labels$last <- as.character(tail(myData()$ts,1))
    })
  })

  # When displaying two charts, there is no flickering / refreshing, which is desired
  output$streaming_data_1 <- renderPlot({
    myData() %>% 
      ggplot(aes(ts,val_1, group = 1))+geom_line() +
      scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
      labs(title ="Sensor 1") +
      theme(plot.margin = unit(c(1,4,1,1),"lines"))
  })
  output$streaming_data_2<- renderPlot({
    myData() %>% 
      ggplot(aes(ts,val_2, group = 1))+geom_line() +
      scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
      labs(title ="Sensor 2") +
      theme(plot.margin = unit(c(1,4,1,1),"lines"))
  })
  # When adding the 3rd chart, every charts start to flicker / refresh when ploting new value
  output$streaming_data_3<- renderPlot({
    myData() %>%
      ggplot(aes(ts,val_3, group = 1))+geom_line() +
      scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
      labs(title ="Sensor 3") +
      theme(plot.margin = unit(c(1,4,1,1),"lines"))
  })
  output$streaming_data_4<- renderPlot({
    myData() %>%
      ggplot(aes(ts,val_4, group = 1))+geom_line() +
      scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
      labs(title ="Sensor 4") +
      theme(plot.margin = unit(c(1,4,1,1),"lines"))
  })

})

shinyApp(ui=ui,server=server)

当只有两个图表并且在添加第三个图片时开始闪烁/刷新时,该解决方案可以正常工作。

1 个答案:

答案 0 :(得分:1)

一个可能的原因可能是1000ms太短而数据无法完成处理。例如,尝试invalidateLater(10000, session),看看会发生什么。

我无法使用我的R版本安装mqtt,因此我无法重现您的行为。但是,我查看了您的代码,我认为您可以采用不同的方法来改进代码:使用<<-将数据写入全局环境通常不是一个好主意。可能更适合的是reactiveVal,您可以在其中存储数据,以及哪些其他函数具有依赖性。因此,在下面的示例中,我创建了reactiveVal和相应的observer,每1000毫秒更新一次reactiveVal

下面是一个工作示例,为了便于说明,我用简单的单行代替了函数的内容。

希望这有帮助!

set.seed(1)

library(shiny)

ui <- fluidPage(
  plotOutput("plotx")
)

server <- function(input, output, session){

  # A reactiveVal that holds our data
  myData <- reactiveVal()

  # Our function to get new data
  get_new_data <- function(){
    data.frame(a=sample(seq(20),1),b=sample(seq(20),1))
  }

  # Observer that updates the data every 1000ms.
  observe({
    # invalidate every 1000ms
    invalidateLater(1000, session)
    isolate({    
      # fetch the new data
      new_data <- get_new_data()

      # If myData is empty, we initialize it with just the new data.
      if(is.null(myData()))
        myData(new_data)
      else # row bind the new data to the existing data, and set that as the new value.
        myData(rbind(myData(),new_data))
    })
  })

  # Plot a histrogram
  output$plotx <- renderPlot({
    hist(myData()$a)
  })
}

shinyApp(ui=ui,server=server)

基于新的可重复示例进行编辑。好像只需要一些时间来创建所有的情节。你可以添加

tags$style(type="text/css", ".recalculating {opacity: 1.0;}")

到你的应用程序,以防止它们闪烁。工作示例:

library(shiny)
library(ggplot2)
library(tidyr)

# Dashboard-like layout
ui <- shinyServer(fluidPage(
  tags$style(type="text/css", ".recalculating {opacity: 1.0;}"),
  fluidRow(
    column(
      6,
      plotOutput("streaming_data_1")
    ),
    column(
      6,
      plotOutput("streaming_data_2")
    )
  ),
  fluidRow(
    column(
      6,
      plotOutput("streaming_data_3")
    ),
    column(
      6,
      plotOutput("streaming_data_4")
    )
  )
))

server <- shinyServer(function(input, output, session){
  myData <- reactiveVal()
  # show the first and last timestamp in the streaming charts
  realtime_graph_x_labels <- reactiveValues(first = "",last ="")

  get_new_data <- function(){
    epochTimeStamp <- as.character(as.integer(Sys.time()))
    sensor_1 <- -runif(1,min = 10, max = 30)
    sensor_2 <- runif(1,min = 0,max = 100)
    sensor_3 <- runif(1,min = 0,max = 100000)
    sensor_4 <- runif(1,min = 0,max = 10)
    newData <- data.frame(ts = epochTimeStamp, val_1 = sensor_1, val_2 = sensor_2, val_3 = sensor_3, val_4 = sensor_4)
    return(newData)
  }

  observe({
    invalidateLater(1000, session)
    isolate({    
      # fetch the new data
      new_data <- get_new_data()
      # If myData is empty, we initialize it with just the new data.
      if(is.null(myData()))
      {
        myData(new_data)
        realtime_graph_x_labels$first <- as.character(head(myData()$ts,1))
      }
      else # row bind the new data to the existing data, and set that as the new value.
        myData(rbind(myData(),new_data))

      realtime_graph_x_labels$last <- as.character(tail(myData()$ts,1))
    })
  })

  # When displaying two charts, there is no flickering / refreshing, which is desired
  output$streaming_data_1 <- renderPlot({
    myData() %>% 
      ggplot(aes(ts,val_1, group = 1))+geom_line() +
      scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
      labs(title ="Sensor 1") +
      theme(plot.margin = unit(c(1,4,1,1),"lines"))
  })
  output$streaming_data_2<- renderPlot({
    myData() %>% 
      ggplot(aes(ts,val_2, group = 1))+geom_line() +
      scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
      labs(title ="Sensor 2") +
      theme(plot.margin = unit(c(1,4,1,1),"lines"))
  })
  # When adding the 3rd chart, every charts start to flicker / refresh when ploting new value
  output$streaming_data_3<- renderPlot({
    myData() %>%
      ggplot(aes(ts,val_3, group = 1))+geom_line() +
      scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
      labs(title ="Sensor 3") +
      theme(plot.margin = unit(c(1,4,1,1),"lines"))
  })
  output$streaming_data_4<- renderPlot({
    myData() %>%
      ggplot(aes(ts,val_4, group = 1))+geom_line() +
      scale_x_discrete(breaks = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last), labels = c(realtime_graph_x_labels$first, realtime_graph_x_labels$last)) +
      labs(title ="Sensor 4") +
      theme(plot.margin = unit(c(1,4,1,1),"lines"))
  })

})

shinyApp(ui=ui,server=server)