Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you create a bar and line plot with R dygraphs?

Tags:

I would like to create a bar and line chart using dygraphs, which seems like it should be possible based on the "Bar & Line Chart" dygraphs example here, and the dyBarChart() custom plotter provided in the dygraphs package.

Using the custom wrapper, I can create a barplot, so I think that code is working:

library(dygraphs)

dyBarChart <- function(dygraph) {
  dyPlotter(
    dygraph = dygraph,
    name = "BarChart",
    path = system.file("examples/plotters/barchart.js",package = "dygraphs")
  )
}

lungDeaths <- cbind(ldeaths, mdeaths)
dygraph(lungDeaths) %>% 
  dyBarChart()

enter image description here

I assumed that I could then use dySeries() to customize the series I wanted to show up with a line/bar, but neither of the following work. They do not error out, but nothing is created. I'm also not sure if the "linePlotter" is the correct plotter name, but either way, I need a little help.

# doesn't work
dygraph(lungDeaths) %>% 
  dyBarChart() %>% 
  dySeries("ldeaths", plotter = "linePlotter")

# also doesn't work:
dygraph(lungDeaths) %>%
  dySeries("ldeaths", plotter = "dyBarChart") %>%
  dySeries("mdeaths", color = "blue")

Thanks.

like image 237
r_alanb Avatar asked Jun 16 '17 21:06

r_alanb


1 Answers

Sometimes you get lucky… I‘ve worked on the same thing a couple of weeks ago and I‘ve found that the documentation is not quite clear on how to do it. But you were pretty close yourself.

How to do it – step by step:

  1. You have to set the plotter for each dyseries
  2. The plotter argument in the dyseries command does not take functions names. But it needs to be a javascript function as plain text
  3. Stacking the bars is easier. Multibars need a way to pass an argument to the javascript function, which you cannot do directly in the package. So I had to do a workaround (At least I found no better way to do it in R).

BTW, setting the dyPlotter command did not work because it sets the plotter globally for all dySeries in the plot. At least that‘s what I figure it does.

So without further ado, here‘s my code. I have added some more test data just to show all the functions.

Test data:

library(xts)
library(dygraphs)
test<-xts(matrix(rnorm(100*4), ncol=4, nrow=100), order.by=seq.POSIXt(as.POSIXct("2017-01-01 00:00", tz="UTC"),by=3600, length.out = 100))
colnames(test)<-c("Series_A","Series_B", "Series_C", "Series_D")

Functions:

dy_position<-function(data_final, plot_title, y2_names=NULL, y1_label, y2_label, y1_step=F, y2_step=F, stacked=T){

  data_final<-reorder_xts(data_final, y2_names) #reorder necessary so that all y2 are at the right end of the xts. Needed for the multibar plot

  dyg <- dygraphs::dygraph(data_final, main=plot_title)
  dyg <- dygraphs::dyAxis(dyg, "x", rangePad=20)
  dyg <- dygraphs::dyAxis(dyg, "y", label = y1_label,
                      axisLabelWidth = 90)
  y1_names<-colnames(data_final)[!(colnames(data_final) %in%y2_names)]

  if (length(y1_names)==1){
    stacked<-T #in this case only stacking works
  }

  if (stacked){
    dyg <- dygraphs::dyOptions(dyg,stepPlot=y1_step,stackedGraph = T)
    for(i in seq_along(y1_names)) {
     dyg <- dygraphs::dySeries(dyg, y1_names[i], axis = "y", strokeWidth = 1.5, stepPlot = y1_step, plotter="  function barChartPlotter(e) {
                            var ctx = e.drawingContext;
                            var points = e.points;
                            var y_bottom = e.dygraph.toDomYCoord(0);

                            ctx.fillStyle = e.color;

                            // Find the minimum separation between x-values.
                            // This determines the bar width.
                            var min_sep = Infinity;
                            for (var i = 1; i < points.length; i++) {
                            var sep = points[i].canvasx - points[i - 1].canvasx;
                            if (sep < min_sep) min_sep = sep;
                            }
                            var bar_width = Math.floor(2.0 / 3 * min_sep);

                            // Do the actual plotting.
                            for (var i = 0; i < points.length; i++) {
                            var p = points[i];
                            var center_x = p.canvasx;

                            ctx.fillRect(center_x - bar_width / 2, p.canvasy,
                            bar_width, y_bottom - p.canvasy);

                            ctx.strokeRect(center_x - bar_width / 2, p.canvasy,
                            bar_width, y_bottom - p.canvasy);
                            }
}")
    }
  } else {
    dyg <- dygraphs::dyOptions(dyg,stepPlot=y1_step)
    for(i in seq_along(y1_names)) {

      #plotter in function
      dyg <- dygraphs::dySeries(dyg, y1_names[i], axis = "y", strokeWidth   = 1.5, stepPlot = y1_step, plotter =multibar_combi_plotter(length(y2_names)))
    }
  }

  # put stuff on y2 axis
  dyg <- dygraphs::dyAxis(dyg, "y2", label = y2_label, independentTicks = T)
  for(i in seq_along(y2_names)) {
    dyg <- dygraphs::dySeries(dyg, y2_names[i], axis = "y2", strokeWidth = 1.5, stepPlot = y2_step)
  }

  return(dyg)
}

#we need to take into account all values and then leave out the ones we do not like
multibar_combi_plotter<-function(num_values){
  #plotter function
  plotter_text<-"function multiColumnBarPlotter(e) {
  // We need to handle all the series simultaneously.
  if (e.seriesIndex !== 0) return;

  var g = e.dygraph;
  var ctx = e.drawingContext;
  var sets = e.allSeriesPoints;
  var y_bottom = e.dygraph.toDomYCoord(0);

  // Find the minimum separation between x-values.
  // This determines the bar width.
  var min_sep = Infinity;
   for (var j = 0; j < sets.length-%s; j++) {
    var points = sets[j];
    for (var i = 1; i < points.length; i++) {
     var sep = points[i].canvasx - points[i - 1].canvasx;
     if (sep < min_sep) min_sep = sep;
  }
  }
  var bar_width = Math.floor(2.0 / 3 * min_sep);

  var fillColors = [];
  var strokeColors = g.getColors();
  for (var i = 0; i < strokeColors.length; i++) {
  fillColors.push(strokeColors[i]);
  }

  for (var j = 0; j < sets.length-%s; j++) {
  ctx.fillStyle = fillColors[j];
  ctx.strokeStyle = strokeColors[j];
  for (var i = 0; i < sets[j].length; i++) {
    var p = sets[j][i];
    var center_x = p.canvasx;
    var x_left = center_x - (bar_width / 2) * (1 - j/(sets.length-%s-1));

   ctx.fillRect(x_left, p.canvasy,
   bar_width/sets.length, y_bottom - p.canvasy);

  ctx.strokeRect(x_left, p.canvasy,
  bar_width/sets.length, y_bottom - p.canvasy);
 }
 }
   }"

  custom_plotter <- sprintf(plotter_text, num_values, num_values, num_values)
  return(custom_plotter)
  }


reorder_xts<-function(xts_series,line_names){
  bar_names<-colnames(xts_series)[!(colnames(xts_series)%in%line_names)]
  xts_series<-xts_series[,c(bar_names,line_names)]
  return(xts_series)
}

Some Explanation:

dy_position does all the plotting. It uses individual plotters per series axis.

reorder_xts is needed to make sure that all lines plots are at the right end of the xts. This is needed for the multibar plot. Because the java script is looping over all series (sets) to determine the width of the bars and we need to make sure we are not looping over the series which are line plots. Otherwise we have additional bars.

multibar_combi_plotter does exactly that. It takes a numeric parameter lines_names and modifies the javascript string so that it loops over all plots except for the line_names (i.e. last series in the right part of the xts). Notice several little %s in the string for the sprintfcommand! Afterwards it returns the plotter as character for the dySeries argument.

All the javascript code is taken directly from the examples in the dygraphs folder.

Here are some examples...

Examples:

dy_position(test,plot_title = "Test1", y2_names =    c("Series_C","Series_D"),y1_label = "Axis1", y2_label = "Axis2", stacked=F)
dy_position(test,plot_title = "Test1", y2_names = c("Series_C","Series_D"),y1_label = "Axis1", y2_label = "Axis2", stacked=T)
dy_position(test,plot_title = "Test1", y2_names = c("Series_B","Series_C","Series_D"),y1_label = "Axis1", y2_label = "Axis2", stacked=T)
dy_position(test,plot_title = "Test1", y2_names = c("Series_D"),y1_label = "Axis1", y2_label = "Axis2", stacked=F)
dy_position(test,plot_title = "Test1", y2_names = c("Series_D"),y1_label = "Axis1", y2_label = "Axis2", stacked=T)
dy_position(test,plot_title = "Test1", y2_names = NULL ,y1_label = "Axis1", y2_label = "Axis2", stacked=F)
dy_position(test,plot_title = "Test1", y2_names = NULL ,y1_label = "Axis1", y2_label = "Axis2", stacked=T)
like image 124
Buggy Avatar answered Sep 29 '22 23:09

Buggy