Search code examples
rhighchartsr-highcharter

Highchart map in R with mappoint series and marker-clusters module


I want to make a map in R which clusters points when zoomed out. I'm using the highcharter package.

Screenshot of what I'm aiming for, from a demo at https://www.highcharts.com/docs/maps/marker-clusters#introduction:

enter image description here

The module to add this functionality in Highcharts is called marker-clusters, which I can load with hc_add_dependency(). But I can't figure out the syntax to set the values for this added module. I've been trying to work off of an example jsFiddle that uses the marker-clusters module, but I don't know how to convert the JS syntax into R.

This doesn't render the clustering at all. It shows the map points and labels correctly, but zooming in/out doesn't convert into the cluster bubbles with counts in them.

library(highcharter)

hcmap("custom/world-highres2", showInLegend = FALSE) |>
  hc_add_dependency("modules/marker-clusters.js") |> 
  hc_add_series(
    data = data, 
    type = "mappoint",
    name = "Research groups"
  ) |>
  hc_plotOptions(cluster = list(
    enabled = TRUE,
    allowOverlap = TRUE,
    animation = list(duration = 450),
    layoutAlgorithm = list(type = "grid",
                           gridSize = 10),
    zones = c(list(from = 1, to = 4, marker = list(radius = 13))))) |> 
  hc_mapNavigation(enabled = TRUE)

And this is the output I get:

enter image description here

I found some R examples that use hc_add_dependency(), and it looks like whatever additional options come from the added module can be included directly into hc_add_series() as additional arguments. But the map stopped rendering points. So my current attempt tries to put the clustering options in a separate hc_plotOptions() call, but that doesn't work either.

Subset of my location data, for the data dataset:

data <- structure(list(name = c("University of Vermont", "Imperial College London", 
"University of York", "The Open University, UK", "Texas State University", 
"Duke University", "Manchester Metropolitan University", "Northwestern University", 
"University of Tennessee"), address = c("Burlington, VT 05405, USA", 
"Exhibition Rd, South Kensington, London SW7 2BX, UK", "Heslington, York YO10 5DD, UK", 
"Walton Hall, Kents Hill, Milton Keynes MK7 6AA, UK", "601 University Dr, San Marcos, TX 78666, USA", 
"Durham, NC 27708, USA", "All Saints Building, All Saints, Manchester M15 6BH, UK", 
"633 Clark St, Evanston, IL 60208, USA", "Knoxville, TN 37996, USA"
), lat = c(44.4778528, 51.4988222, 53.9461089, 52.0249295, 29.888411, 
36.0014258, 53.4703485, 42.0564594, 35.9544013), lon = c(-73.1964637, 
-0.1748735, -1.0517718, -0.7081895, -97.938351, -78.9382286, 
-2.2392999, -87.675267, -83.9294564)), row.names = c(NA, -9L), spec = structure(list(
    cols = list(name = structure(list(), class = c("collector_character", 
    "collector")), address = structure(list(), class = c("collector_character", 
    "collector")), lat = structure(list(), class = c("collector_double", 
    "collector")), lon = structure(list(), class = c("collector_double", 
    "collector"))), default = structure(list(), class = c("collector_guess", 
    "collector")), delim = ","), class = "col_spec"), problems = <pointer: 0x5637c9f857d0>, class = c("spec_tbl_df", 
"tbl_df", "tbl", "data.frame"))

The settings from the jsFiddle I've been trying to copy:

plotOptions: {
  mappoint: {
    cluster: {
      enabled: true,
      allowOverlap: false,
      animation: {
        duration: 450
      },
      layoutAlgorithm: {
        type: 'grid',
        gridSize: 70
      },
      zones: [{
        from: 1,
        to: 4,
        marker: {
          radius: 13
        }
      }, {
        from: 5,
        to: 9,
        marker: {
          radius: 15
        }
      }, {
        from: 10,
        to: 15,
        marker: {
          radius: 17
        }
      }, {
        from: 16,
        to: 20,
        marker: {
          radius: 19
        }
      }, {
        from: 21,
        to: 100,
        marker: {
          radius: 21
        }
      }]
    }
  }
}

I don't know if there are enough points in this subset for a good demonstration of the zoomed-out clustering effect.

What is the correct syntax?


Solution

  • I gave ChatGPT the Javascript from the jsFiddle in my original post, and asked it to convert it to R code using the highcharter package. I worked through the specifics and made it my own.

    I was missing starting the plotOptions config with a mappoints flag. But there are a few other bits that may be useful to others:

    • dataLabels in hc_add_series() sets the count on the cluster circles.
    • hc_tooltip determines hover behavior, depending on whether the user is hovering over a cluster or a specific point.
    • hc_colorAxis defines the cluster gradient color scheme
    hc <- hcmap("custom/world-highres2")
    hc |> 
      hc_add_dependency("modules/marker-clusters.js") |>
      hc_title(text = 'Research Organizations') |> 
      hc_add_series(
        type = "mappoint",
        enableMouseTracking = TRUE,
        colorKey = "clusterPointsAmount",
        name = "Organizations",
        color = "#5899E2",
        data = data,
        dataLabels = list(
          enabled = TRUE,
          format = "{point.clusterPointsAmount}",
          allowOverlap = TRUE,
          align = "center",
          verticalAlign = "middle",
          color = "white"
        )
      ) |> 
      hc_mapNavigation(enabled = TRUE) |>
      hc_tooltip(formatter = JS(
        "function () {
          if (this.point.clusteredData) {
              return 'Clustered points: ' + this.point.clusterPointsAmount;
          }
          return '<b>' + this.key + '</b><br>Lat: ' + this.point.lat.toFixed(2) + ', Lon: ' + this.point.lon.toFixed(2);
        }")) |>
      hc_colorAxis(
          min = 1,
          max = 120,
          type = 'linear',  # or 'logarithmic'
          minColor = '#B4B8C5',  # Start color (e.g., white)
          maxColor = '#335C81',  # End color (e.g., dark blue)
          stops = list(
            list(0, '#B4B8C5'),
            list(0.5, '#5899E2'),
            list(1, '#335C81')
          )  # Gradient stops (optional; can be used for multi-color gradient)
        ) |> 
      hc_plotOptions(
        mappoint = list(
          cluster = list(
            enabled = TRUE,
            allowOverlap = FALSE,
            animation = list(duration = 450),
            layoutAlgorithm = list(type = 'grid', gridSize = 70),
            zones = list(
              list(from = 1, to = 4, marker = list(radius = 13)),
              list(from = 5, to = 9, marker = list(radius = 15)),
              list(from = 10, to = 15, marker = list(radius = 17)),
              list(from = 16, to = 20, marker = list(radius = 19)),
              list(from = 21, to = 100, marker = list(radius = 21)),
              list(from = 101, to = 150, marker = list(radius = 24))
            )
          )
        )
      )
    

    enter image description here

    (Plot contains more data points than the sample subset included in the question.)