r_alanb
r_alanb

Reputation: 913

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

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.

Upvotes: 5

Views: 4723

Answers (3)

zanef
zanef

Reputation: 1

After a bit of research I think that this would be simplest. At least that's the way it seems for me.

You would need to download the "barseries.js" file available at http://dygraphs.com/tests/plotters.html

Then the code would look like so

library(dygraphs)

dyBarSeries <- function(dygraph, name, ...) {
  file <- "plotters/barseries.js" #you need to link to the downloaded file
  plotter_ <- paste0(readLines(file, skipNul = T), collapse = "\n")

  dots <- list(...)
  do.call('dySeries', c(list(dygraph = dygraph, name = name, plotter = 
plotter_), dots))

}

lungDeaths <- cbind(ldeaths, mdeaths)



dygraph(lungDeaths) %>% 
  dyBarSeries("ldeaths") %>%
  dySeries("mdeaths")

Yielding this result

enter image description here

Upvotes: 0

Buggy
Buggy

Reputation: 2100

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)

Upvotes: 4

KoenV
KoenV

Reputation: 4283

I am not sure this is exactly what you want. What I propose, comes close to the combination of a bar plot and a line plot, without the need to create a separate function.

You can set the type of plot per series, with dySeries. You can choose between lineplot (default), stepPlot, and stemPlot. In addition you may set to see the points with drawPoints and pointSize, you may also opt to fill the graph or not with fillGraph. For other options type ?dySeries

The code looks as follows:

library(dygraphs)

lungDeaths <- cbind(ldeaths, mdeaths)

dygraph(lungDeaths, main = "Main Title") %>%
  dySeries("ldeaths", drawPoints = FALSE) %>%
  dySeries("mdeaths", stepPlot = TRUE, fillGraph = TRUE)

Yielding this plot:

enter image description here

Please, let me know whether this is what you want.

Upvotes: 0

Related Questions