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 +}