block by timelyportfolio 47cac2df130436f3292afaa38253072d

vsup ggplot2 interactive svg

Full Screen

Claus Wilke has been doing some amazing work within the R community. Recently, he posted a VSUP example map using his package multiscales and the newest ggplot2. I thought a little interactivity would be a nice addition.

# will need newest ggplot2, github multiscales, and dev version of colorspace
# install.packages('ggplot2')
# install.packages("colorspace", repos = "http://R-Forge.R-project.org")
# devtools::install_github("clauswilke/multiscales")

library(htmltools)
library(d3r)
library(svglite)
library(ggplot2)
library(multiscales)

# example from Claus Wilke's multiscales README
colors <- scales::colour_ramp(
  colors = c(red = "#AC202F", purple = "#740280", blue = "#2265A3")
)((0:7)/7)

ggp <- ggplot(US_polling) + 
  geom_sf(aes(fill = zip(Clinton_lead, moe_normalized)), color = "gray30", size = 0.2) + 
  coord_sf(datum = NA) +
  bivariate_scale("fill",
                  pal_vsup(values = colors, max_desat = 0.8, pow_desat = 0.2, max_light = 0.7, pow_light = 1),
                  name = c("Clinton lead", "uncertainty"),
                  limits = list(c(-40, 40), c(0, 1)),
                  breaks = list(c(-40, -20, 0, 20, 40), c(0, 0.25, 0.50, 0.75, 1.)),
                  labels = list(waiver(), scales::percent),
                  guide = "colourfan"
  ) +
  theme_void() +
  theme(
    legend.key.size = grid::unit(0.8, "cm"),
    legend.title.align = 0.5,
    plot.margin = margin(5.5, 20, 5.5, 5.5)
  )

s <- svgstring()
ggp
dev.off()

browsable(
  tagList(
    tags$div(style="width:100%",HTML(s())),
    tags$script(HTML(
"
  var svg = d3.select('svg')

  // add original fill as data on each state path
  svg.selectAll('path').each( function(d) {
    d3.select(this).datum({color: d3.select(this).style('fill')})
  })

  // this is not necessary but makes it cleaner
  //   add g group for each polygon in the legend
  //   the polygons are multiple small portions of the space in the legend
  //   rather than one polygon for each color
  var legendcolors = d3.set()
  svg.selectAll('polygon').each(function(d){legendcolors.add(d3.select(this).style('fill'))})

  legendcolors.values().forEach(function(color) {
    var g = svg.insert('g','svg>polygon').classed('legend-color',true).datum({color: color})
    svg.selectAll('polygon')
      .filter(function(d) {return d3.select(this).style('fill') === color})
      .each(function(d) {
        g.node().appendChild(this)
      })
  })

  svg.selectAll('g.legend-color').on('mouseover', function(d) {
    svg.selectAll('path').filter(function(pathd){return pathd.color !== d.color}).style('fill', 'white')
    svg.selectAll('path').filter(function(pathd){return pathd.color === d.color}).style('fill', d.color)
  })

  svg.selectAll('g.legend-color').on('mouseout', function(d) {
    svg.selectAll('path').style('fill', function(pathd){return pathd.color})
  })

  // create a legend container g
  var legend = svg.insert('g', 'g.legend-color')
    .classed('legend',true)
    .datum({transformx:0,transformy:0})

  // add legend elements to a g container
  svg.selectAll('g.legend-color,g[clip-path]').each(function(d){legend.node().appendChild(this)})

  // make a crude drag function to move the legend when dragged
  //   probably a better way than storing the transforms in datum
  //   but it was my first idea and it works
  function dragged(d) {
    var transformx = d3.select(this).datum().transformx + d3.event.dx
    var transformy = d3.select(this).datum().transformy + d3.event.dy
    d3.select(this).attr('transform', 'translate(' + transformx + ',' + transformy + ')');
    d3.select(this).datum({transformx:transformx, transformy: transformy})
  }
  drag = d3.drag().on('drag',dragged)
  legend.call(drag)
"      
    )),
    d3_dep_v5()
  )
)