From f0838eefc4d107f2f9133f61b64b6a65ddb233e6 Mon Sep 17 00:00:00 2001 From: Joshua Kunst Date: Mon, 31 May 2021 16:41:33 -0400 Subject: [PATCH] adding set data and redra proxy methosds @PaulC91 Thanks to @PaulC91 Co-Authored-By: Paul Campbell <25231784+PaulC91@users.noreply.github.com> --- NAMESPACE | 2 ++ R/proxy.R | 56 +++++++++++++++++++++++++++++++ dev/sandbox/proxy-shiny.R | 54 ++++++++++++++++++++++++----- inst/htmlwidgets/highchart.js | 31 +++++++++++++++-- inst/htmlwidgets/highchart2.js | 53 ++++++++++++++++++----------- inst/htmlwidgets/highchartzero.js | 13 ++++--- man/hcpxy_redraw.Rd | 14 ++++++++ man/hcpxy_set_data.Rd | 38 +++++++++++++++++++++ 8 files changed, 226 insertions(+), 35 deletions(-) create mode 100644 man/hcpxy_redraw.Rd create mode 100644 man/hcpxy_set_data.Rd diff --git a/NAMESPACE b/NAMESPACE index 8469592d..988b37f5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -135,8 +135,10 @@ export(hcparcords) export(hcpxy_add_point) export(hcpxy_add_series) export(hcpxy_loading) +export(hcpxy_redraw) export(hcpxy_remove_point) export(hcpxy_remove_series) +export(hcpxy_set_data) export(hcpxy_update) export(hcpxy_update_series) export(hcspark) diff --git a/R/proxy.R b/R/proxy.R index 0b6ef966..1b6aa1fa 100644 --- a/R/proxy.R +++ b/R/proxy.R @@ -257,3 +257,59 @@ hcpxy_remove_point <- function(proxy, id = NULL, i = NULL, redraw = TRUE){ proxy } + +#' Update data for a higchartProxy object +#' +#' @param proxy A `higchartProxy` object. +#' @param type series type (column, bar, line, etc) +#' @param data dataframe of new data to send to chart +#' @param mapping how data should be mapped using `hcaes()` +#' @param redraw boolean Whether to redraw the chart after the series is altered. +#' If doing more operations on the chart, it is a good idea to set redraw to false and call hcpxy_redraw after. +#' @param animation boolean When the updated data is the same length as the existing data, points will be updated by default, +#' and animation visualizes how the points are changed. Set false to disable animation, or a configuration object to set duration or easing. +#' @param updatePoints boolean When this is TRUE, points will be updated instead of replaced whenever possible. +#' This occurs a) when the updated data is the same length as the existing data, b) when points are matched by their id's, or c) when points can be matched by X values. +#' This allows updating with animation and performs better. In this case, the original array is not passed by reference. Set FALSE to prevent. +#' +#' @export +hcpxy_set_data <- function(proxy, type, data, mapping = hcaes(), redraw = FALSE, animation = NULL, updatePoints = TRUE) { + checkProxy(proxy) + + data <- mutate_mapping(data, mapping) + + series <- data_to_series(data, mapping, type = type) + + for(i in 1:length(series)) + proxy$session$sendCustomMessage( + type = 'setData', + message = list( + id = proxy$id, + serie = i-1, + data = series[[i]]$data, + redraw = redraw, + animation = animation, + updatePoints = updatePoints + ) + ) + + proxy + +} + +#' Redraw a higchartProxy object +#' +#' @param proxy A `higchartProxy` object. +#' +#' @export +hcpxy_redraw <- function(proxy) { + checkProxy(proxy) + + proxy$session$sendCustomMessage( + type = 'redraw', + message = list(id = proxy$id) + ) + + proxy + +} diff --git a/dev/sandbox/proxy-shiny.R b/dev/sandbox/proxy-shiny.R index 330934d0..8fd6bf93 100644 --- a/dev/sandbox/proxy-shiny.R +++ b/dev/sandbox/proxy-shiny.R @@ -12,15 +12,15 @@ ui <- fluidPage( column( actionButton("addpnts", "Add Series"), highchartOutput("hc_nd") - ), + ), column( actionButton("mkpreds", "Add Series linkedTo existing one"), highchartOutput("hc_ts") - ), + ), column( actionButton("loading", "Loading"), highchartOutput("hc_ld") - ), + ), column( actionButton("remove", "Remove series"), highchartOutput("hc_rm") @@ -39,6 +39,10 @@ ui <- fluidPage( actionButton("update4", "Update series options"), highchartOutput("hc_opts2") ), + column( + actionButton("set_data", "Update all series data"), + highchartOutput("hc_set_data") + ), column( actionButton("addpoint", "Add point"), actionButton("addpoint_w_shift", "Add point with shift"), @@ -112,7 +116,7 @@ server <- function(input, output, session){ ggplot2::mpg %>% select(displ, cty, cyl), "scatter", hcaes(x = displ, y = cty, group = cyl) - ) + ) }) observeEvent(input$remove_all, { @@ -184,6 +188,42 @@ server <- function(input, output, session){ }) + output$hc_set_data <- renderHighchart({ + input$reset + + df <- data.frame( + month = month.abb, + A = runif(12, 30, 90), + B = runif(12, 30, 90), + C = runif(12, 30, 90), + D = runif(12, 30, 90) + ) %>% tidyr::pivot_longer(A:D, names_to = "name", values_to = "value") + + hchart(df, "column", hcaes(month, value, group = name)) %>% + hc_xAxis(title = list(text = "")) %>% + hc_yAxis(title = list(text = "")) + }) + + observeEvent(input$set_data, { + + df <- data.frame( + month = month.abb, + A = runif(12, 30, 90), + B = runif(12, 30, 90), + C = runif(12, 30, 90), + D = runif(12, 30, 90) + ) %>% tidyr::pivot_longer(A:D, names_to = "name", values_to = "value") + + highchartProxy("hc_set_data") %>% + hcpxy_set_data( + type = "column", + data = df, + mapping = hcaes(month, value, group = name), + redraw = TRUE + ) + + }) + output$hc_addpoint <- renderHighchart({ input$reset @@ -216,7 +256,7 @@ server <- function(input, output, session){ id = "ts", point = list(x = datetime_to_timestamp(Sys.time()), y = rnorm(1)), shift = TRUE - ) + ) }) @@ -226,12 +266,10 @@ server <- function(input, output, session){ hcpxy_remove_point( id = "ts", i = 0 - ) + ) }) - - } if(interactive()) shiny::shinyApp(ui, server, options = list(launch.browser = .rs.invokeShinyPaneViewer)) diff --git a/inst/htmlwidgets/highchart.js b/inst/htmlwidgets/highchart.js index 39beb5d8..69f47f23 100644 --- a/inst/htmlwidgets/highchart.js +++ b/inst/htmlwidgets/highchart.js @@ -1,5 +1,5 @@ HTMLWidgets.widget({ - + name: 'highchart', type: 'output', @@ -140,7 +140,6 @@ HTMLWidgets.widget({ chart.setSize(w, h); } - } }); @@ -221,5 +220,33 @@ if (HTMLWidgets.shinyMode) { }); + Shiny.addCustomMessageHandler('setData', function(msg) { + + var chart = $("#" + msg.id).highcharts(); + + if (typeof chart != 'undefined') { + + chart.series[msg.serie].setData( + data = msg.data, + redraw = msg.redraw, + animation = msg.animation, + updatePoints = msg.updatePoints + ); + + } + + }); + + Shiny.addCustomMessageHandler('redraw', function(msg) { + + var chart = $("#" + msg.id).highcharts(); + + if (typeof chart != 'undefined') { + + chart.redraw(); + + } + + }); } diff --git a/inst/htmlwidgets/highchart2.js b/inst/htmlwidgets/highchart2.js index 825440c6..1f8c9735 100644 --- a/inst/htmlwidgets/highchart2.js +++ b/inst/htmlwidgets/highchart2.js @@ -1,5 +1,5 @@ HTMLWidgets.widget({ - + name: 'highchart2', type: 'output', @@ -16,19 +16,20 @@ HTMLWidgets.widget({ if(x.debug) { window.xclone = JSON.parse(JSON.stringify(x)); + window.elclone = $(el); console.log(el); + console.log("hc_opts", x.hc_opts); console.log("theme", x.theme); console.log("conf_opts", x.conf_opts); } - if(x.fonts !== undefined) { x.fonts = ((typeof(x.fonts) == "string") ? [x.fonts] : x.fonts); x.fonts.forEach(function(s){ + /* http://stackoverflow.com/questions/4724606 */ var urlfont = 'https://fonts.googleapis.com/css?family=' + s; - // http://stackoverflow.com/questions/4724606 if (!$("link[href='" + urlfont + "']").length) { $('').appendTo("head"); } @@ -37,29 +38,38 @@ HTMLWidgets.widget({ } - ResetHighchartsOptions(); - + ResetHighchartsOptions(); if(x.theme !== null) { + if(x.debug) console.log("adding THEME"); + Highcharts.setOptions(x.theme); - if(x.theme.chart.divBackgroundImage !== null){ + } + + if((x.theme && x.theme.chart.divBackgroundImage !== undefined) | + (x.hc_opts.chart && x.hc_opts.chart.divBackgroundImage !== undefined)) { + + if(x.debug) console.log("adding BackgroundImage"); + + var bkgrnd = x.theme.chart.divBackgroundImage || x.hc_opts.chart.divBackgroundImage; + + Highcharts.wrap(Highcharts.Chart.prototype, "getContainer", function (proceed) { + + proceed.call(this); - Highcharts.wrap(Highcharts.Chart.prototype, 'getContainer', function (proceed) { - proceed.call(this); - $("#" + el.id).css('background-image', 'url(' + x.theme.chart.divBackgroundImage + ')'); - - }); + $("#" + el.id).css("background-image", "url(" + bkgrnd + ")"); + $("#" + el.id).css("-webkit-background-size", "cover"); + $("#" + el.id).css("-moz-background-size", "cover"); + $("#" + el.id).css("-o-background-size", "cover"); + $("#" + el.id).css("background-size", "cover"); - } + }); } - - - - $("#" + el.id).highcharts(x.hc_opts); + Highcharts.setOptions(x.conf_opts); }, @@ -67,10 +77,13 @@ HTMLWidgets.widget({ /* http://stackoverflow.com/questions/18445784/ */ var chart = $("#" +el.id).highcharts(); - var height = chart.renderTo.clientHeight; - var width = chart.renderTo.clientWidth; - chart.setSize(width, height); - + + if (chart && chart.options.chart.reflow === true) { // _check for reflow option_ + var w = chart.renderTo.clientWidth; + var h = chart.renderTo.clientHeight; + chart.setSize(w, h); + } + } }); diff --git a/inst/htmlwidgets/highchartzero.js b/inst/htmlwidgets/highchartzero.js index 91839df7..3144b4ce 100644 --- a/inst/htmlwidgets/highchartzero.js +++ b/inst/htmlwidgets/highchartzero.js @@ -8,7 +8,7 @@ HTMLWidgets.widget({ return { // TODO: add instance fields as required - } + }; }, @@ -22,10 +22,13 @@ HTMLWidgets.widget({ /* http://stackoverflow.com/questions/18445784/ */ var chart = $("#" +el.id).highcharts(); - var w = chart.renderTo.clientWidth; - var h = chart.renderTo.clientHeight; - chart.setSize(w, h); - + + if (chart && chart.options.chart.reflow === true) { // _check for reflow option_ + var w = chart.renderTo.clientWidth; + var h = chart.renderTo.clientHeight; + chart.setSize(w, h); + } + } }); diff --git a/man/hcpxy_redraw.Rd b/man/hcpxy_redraw.Rd new file mode 100644 index 00000000..5f6ac75e --- /dev/null +++ b/man/hcpxy_redraw.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/proxy.R +\name{hcpxy_redraw} +\alias{hcpxy_redraw} +\title{Redraw a higchartProxy object} +\usage{ +hcpxy_redraw(proxy) +} +\arguments{ +\item{proxy}{A \code{higchartProxy} object.} +} +\description{ +Redraw a higchartProxy object +} diff --git a/man/hcpxy_set_data.Rd b/man/hcpxy_set_data.Rd new file mode 100644 index 00000000..0d640b6e --- /dev/null +++ b/man/hcpxy_set_data.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/proxy.R +\name{hcpxy_set_data} +\alias{hcpxy_set_data} +\title{Update data for a higchartProxy object} +\usage{ +hcpxy_set_data( + proxy, + type, + data, + mapping = hcaes(), + redraw = FALSE, + animation = NULL, + updatePoints = TRUE +) +} +\arguments{ +\item{proxy}{A \code{higchartProxy} object.} + +\item{type}{series type (column, bar, line, etc)} + +\item{data}{dataframe of new data to send to chart} + +\item{mapping}{how data should be mapped using \code{hcaes()}} + +\item{redraw}{boolean Whether to redraw the chart after the series is altered. +If doing more operations on the chart, it is a good idea to set redraw to false and call hcpxy_redraw after.} + +\item{animation}{boolean When the updated data is the same length as the existing data, points will be updated by default, +and animation visualizes how the points are changed. Set false to disable animation, or a configuration object to set duration or easing.} + +\item{updatePoints}{boolean When this is TRUE, points will be updated instead of replaced whenever possible. +This occurs a) when the updated data is the same length as the existing data, b) when points are matched by their id's, or c) when points can be matched by X values. +This allows updating with animation and performs better. In this case, the original array is not passed by reference. Set FALSE to prevent.} +} +\description{ +Update data for a higchartProxy object +}