rdygraphsr-highcharter

dygraphs/highcharter highlighting on both plots - interactivity


I am trying to replicate the following plot in dyGraph or highcharter.

df %>% 
  ggplot(aes(x = mts2, y = price)) +
  geom_point() +
  geom_jitter() +
  facet_wrap(~ type, scales = "free_y", ncol = 1) +
  stat_smooth(method = "lm", formula = y ~ x + I(x^2), size = 1, color = "red")

enter image description here

However, I would like it interactive so when the user hovers over one of the points a line is drawn to connect the rental and the purchases. So when I hover over the regression line it will highlight the regression in the other plot.

I am trying to recreate the same plot but in dygraphs or highcharter where I can highlight the same points in both regressions. (i.e. when we hoverover 80 mts2 and the points around 400,000 price it should highlight the points in the top graph around 1,500

Code:

library(dygraphs)
df %>% 
  filter(type == "comprar") %>% 
  select(-c(type, habs)) %>%
  dygraph(main = "myTitle") %>% 
  dyOptions(drawPoints = TRUE) %>% 
  dySeries(drawPoints = TRUE, color = "#0099F9")
  
  library(highcharter)
  df %>% 
    highchart() %>% 
    hc_title(text = "Scatter chart with size and color") %>% 
    hc_add_series(df, "scatter", hcaes(x = price, y = mts2, size  = mts2, color = mts2))

Data:

df = structure(list(price = c(1600, 1200, 249000, 288000, 775000, 
350000, 715000, 330000, 375000, 925, 1250, 300000, 425000, 489000, 
1200, 550000, 1895, 310000, 289000, 450000, 1250, 288000, 1000, 
600, 1100, 350000, 1200, 339000, 405000, 427000, 299000, 218000, 
159900, 360000, 365000, 725, 405000, 300000, 715000, 1300, 1400, 
1500, 415000, 1500, 663, 350000, 365000, 230000, 515000, 259000, 
310000, 405000, 288000, 350000, 288000, 1300, 350000, 1350, 715000, 
350000, 715000, 185000, 2200, 288000, 353800, 290000, 229000, 
365000, 1900, 1300, 590000, 180000, 1050, 1900, 1100, 1950, 288000, 
1995, 112000, 369000, 593000, 550000, 365000, 715000, 1800, 713000, 
1100, 260000, 375000, 715000, 338000, 288000, 1900, 288000, 2800, 
2450, 1990, 260000, 415000, 745000), habs = c(1, 1, 1, 4, 3, 
4, NA, 4, 2, 2, 2, 2, 4, 3, 3, 4, 2, 2, 3, 4, 1, 4, 1, 1, 2, 
5, 3, 4, 3, 4, 2, 2, NA, 4, 3, 1, 3, 3, 3, 3, 3, 2, 4, 2, 1, 
3, 3, 3, 2, 1, 2, 3, 4, 4, 4, 3, 4, 3, NA, 3, 3, 1, 3, 4, 1, 
4, 3, 3, 1, 2, 3, 2, 1, 1, 2, 2, 4, 2, 1, 3, 2, 4, 3, 3, 2, 3, 
3, NA, 2, 3, 3, 4, 1, 4, 4, 4, 1, NA, 4, 3), mts2 = c(70, 65, 
55, 76, 121, 87, 109, 85, 81, 46, 65, 55, 100, 102, 65, 122, 
66, 51, 85, 99, 50, 75, 55, 10, 75, 87, 71, 75, 83, 118, 85, 
57, 45, 112, 63, 40, 83, 75, 109, 91, 74, 58, 100, 75, 42, 82, 
90, 65, 104, 52, 55, 83, 79, 87, 76, 77, 87, 88, 109, 83, 109, 
46, 145, 76, 40, 66, 63, 90, 45, 65, 115, 44, 46, 45, 73, 90, 
79, 110, 42, 81, 73, 115, 94, 109, 70, 104, 75, 58, 80, 109, 
92, 79, 45, 76, 122, 160, 47, 58, 100, 104), type = c("alquiler", 
"alquiler", "comprar", "comprar", "comprar", "comprar", "comprar", 
"comprar", "comprar", "alquiler", "alquiler", "comprar", "comprar", 
"comprar", "alquiler", "comprar", "alquiler", "comprar", "comprar", 
"comprar", "alquiler", "comprar", "alquiler", "alquiler", "alquiler", 
"comprar", "alquiler", "comprar", "comprar", "comprar", "comprar", 
"comprar", "comprar", "comprar", "comprar", "alquiler", "comprar", 
"comprar", "comprar", "alquiler", "alquiler", "alquiler", "comprar", 
"alquiler", "alquiler", "comprar", "comprar", "comprar", "comprar", 
"comprar", "comprar", "comprar", "comprar", "comprar", "comprar", 
"alquiler", "comprar", "alquiler", "comprar", "comprar", "comprar", 
"comprar", "alquiler", "comprar", "comprar", "comprar", "comprar", 
"comprar", "alquiler", "alquiler", "comprar", "comprar", "alquiler", 
"alquiler", "alquiler", "alquiler", "comprar", "alquiler", "comprar", 
"comprar", "comprar", "comprar", "comprar", "comprar", "alquiler", 
"comprar", "alquiler", "comprar", "comprar", "comprar", "comprar", 
"comprar", "alquiler", "comprar", "alquiler", "alquiler", "alquiler", 
"comprar", "comprar", "comprar")), row.names = c(NA, -100L), class = c("tbl_df", 
"tbl", "data.frame"))

Solution

  • I'm going to a wild stab at what I think you're looking for here. There's a lot you can do with these libraries. I don't know what you wanted to see in the tooltips over the regressions, either.

    For this solution, I opted to go with highcharter.

    First, I used imap to create the two plots. I've added many comments so you can see the purpose of the calls. If anything is unclear, let me know.

    library(highcharter)
    library(tidyverse)
    library(htmltools)
    
    imap(unique(df$type), function(j, k) {                   # index & type in df
      plt <- df %>% filter(type == j) %>%                    # filter for the type
        hchart("point", regression = T,                      # add poly reg line
               regressionSettings = list(color = "red", type = "polynomial",
                                         hideInLegend = T),  # no legend
               hcaes(x = mts2, y = price)) %>% 
        hc_add_dependency("plugins/highcharts-regression.js") %>%  # tie-in reg dep
        hc_xAxis(min = 5, max = 160, crosshair = T) %>%      # all w/ same x rng
        hc_tooltip(useHTML = T, crosshair = T)
      assign(paste0("plt", k), plt, envir = .GlobalEnv)      # add plot to env
    })
    

    Now, to render both plots together with synched tooltips, I've used browsable. If you're using RStudio or another IDE with a viewer pane, you'll need to send the results to the browser to see the plots combined. In Rstudio, you can just click on the icon to send it there. The icon looks like this... (center-ish in the viewer pane).

    enter image description here

    The remaining code is almost entirely Javascript/JQuery. Again, I've added a lot of comments here to explain what's happening.

    I wanted to point out one particular line:

    point = chart.series[1].searchPoint(event, true); /* get closest point; reg line only */
    

    In this declaration of the point object, the [1] after series is what's telling this entire chunk of code to only align the regressions between the plots. For example, if you changed this to [0] it would ignore the regression lines and focus on the points in both plots.

    browsable(tagList(
      tags$script(HTML("
      setTimeout(function() {                                   /* using id from div */
        $('#hc_container').bind('mousemove touchmove touchstart', function(e) {
          var chart, point, i, event;
          for (i = 0; i < Highcharts.charts.length; i++) {      /* loop through both charts */
              chart = Highcharts.charts[i];                     /* identify the chart */
              event = chart.pointer.normalize(e.originalEvent); /* find chart coordinates */
              point = chart.series[1].searchPoint(event, true); /* get closest point; reg line only */
              if (point) {                                      /* if point found, tip it */
                  point.highlight(e);
              }
          }
        });
      }, 500);
      Highcharts.Point.prototype.highlight = function(event) { /* executes tooltip from trigger */
        event = this.series.chart.pointer.normalize(event);    /* capture that event occurred */
        this.onMouseOver();                                    /* show marker */
        this.series.chart.tooltip.refresh(this);               /* show tooltip */
        this.series.chart.xAxis[0].drawCrosshair(event, this); /* show crosshair */
      };
      Highcharts.Pointer.prototype.reset = function() {        /* vigilant tooltip */
        return null;
      };
    ")),
    div(id = "hc_container",     # this id is used in the JQuery/Javascript above
        div(plt1, style = 'height:50%; width: 100%;'), # first plot
        div(plt2, style = 'height:50%; width: 100%;'), # second plot
        style = "height:100%; width:100%;")))          # container styles
    

    enter image description here

    For customizing things like the tooltip in the regression line (or any other settings for the regression line), use this site. For some reason, when I go to this site randomly the readme doesn't load. If I refresh my browser it shows up immediately. I'm not sure what's up with that, but in case it's blank for you, that's all you've got to do.

    If you have any questions, let me know.