4

I am trying to create a map containing some negative values and mostly positive values. The code I have (given below) generates the correct map, however the legend is not centered at 0 (ie. the white color is not at 0, while more negative values are deeper red and more positive values are blue). How can I change the color palette to center around 0 regardless of the values in the dataset?

Code:

library(tidyverse)
library(sf)
library(mapview)

palfunc <- function (n, alpha = 1, begin = 0, end = 1, direction = 1) 
{
colors <- RColorBrewer::brewer.pal(11, "RdBu")
if (direction < 0) colors <- rev(colors)
colorRampPalette(colors, alpha = alpha)(n)
}


foo <- franconia %>% mutate(foo = rnorm(n()) + 2)

max_val = max(abs(foo$foo), na.rm = T)
n_val = max( length(unique(keep(foo$foo, ~.x > 0))),
         length(unique(keep(foo$foo, ~.x < 0))))
at = lattice::do.breaks(endpoints = c(-max_val, max_val), nint = 2 * n_val + 1)
p <- mapView(foo, zcol = 'foo', layer.name = "Example", col.regions = palfunc, at = at)
M--
  • 25,431
  • 8
  • 61
  • 93

1 Answers1

1

TL;DR

It seems that r-mapview cannot handle more than 10 breaks in its at variable. Look below:

library(tidyverse)
library(sf)
library(mapview)

palfunc <- function (n, alpha = 1, begin = 0, end = 1, direction = 1) 
{
  colors <- RColorBrewer::brewer.pal(11, "RdBu")
  if (direction < 0) colors <- rev(colors)
  colorRampPalette(colors, alpha = alpha)(n)
}

set.seed(92)
foo <- franconia %>% mutate(foo = rnorm(n()) + 2)

max_val = max(abs(foo$foo), na.rm = T)
n_val = max( length(unique(keep(foo$foo, ~.x > 0))),
             length(unique(keep(foo$foo, ~.x < 0))))
  • 10 Breaks:
at_10 = lattice::do.breaks(endpoints = c(-max_val, max_val), nint = 10)
mapView(foo, zcol = 'foo', layer.name = "Example", col.regions = palfunc, at = at_10)

  • 11 Breaks:
at_11 = lattice::do.breaks(endpoints = c(-max_val, max_val), nint = 11)
mapView(foo, zcol = 'foo', layer.name = "Example", col.regions = palfunc, at = at_11)

  • When limits of data are actually mirrored around 0:

However, if we actually had the negative value (min(values) ≈ max(values)), then the legend by default would be centered around 0:

library(tidyverse)
library(sf)
library(mapview)

palfunc <- function (n, alpha = 1, begin = 0, end = 1, direction = 1) 
{
  colors <- RColorBrewer::brewer.pal(11, "RdBu")
  if (direction < 0) colors <- rev(colors)
  colorRampPalette(colors, alpha = alpha)(n)
}

set.seed(92)
foo <- franconia %>% mutate(foo = c((rnorm((n()-1)) + 2), -4))

max_val = max(abs(foo$foo), na.rm = T)
n_val = max( length(unique(keep(foo$foo, ~.x > 0))),
             length(unique(keep(foo$foo, ~.x < 0))))

mapView(foo, zcol = 'foo', layer.name = "Example", col.regions = palfunc)

Solution:

So, my hacky way to address your problem is adding two rows to your foo datast with -max_val and +max_val which are basically multipolygons with no area and length (a.k.a. point) so our dataset has mirrored values around zero and therefore mapview would generate "balanced" legend, however user would not see those points on the map as their area is zero. Look below for, again, hacky implementation. (p.s. If you want to you can add those dummy points/multipolygons somewhere out of the boundary of your data and then set the default zoom to your actual data, which is unnecessary because as I said, those dummy points are not visible).

library(tidyverse)
library(sf)
library(mapview)

palfunc <- function (n, alpha = 1, begin = 0, end = 1, direction = 1) 
{
  colors <- RColorBrewer::brewer.pal(11, "RdBu")
  if (direction < 0) colors <- rev(colors)
  colorRampPalette(colors, alpha = alpha)(n)
}

set.seed(92)
foo <- franconia %>% mutate(foo = (rnorm((n())) + 2))

max_val = max(abs(foo$foo), na.rm = T)
n_val = max( length(unique(keep(foo$foo, ~.x > 0))),
             length(unique(keep(foo$foo, ~.x < 0))))

#creating a dummy polygon which all of its boundaries point are the same
a_dummy_m <- matrix(c(10.92582, 49.92508, 10.92582, 49.92508,
                    10.92582, 49.92508, 10.92582, 49.92508,
                    10.92582, 49.92508),ncol=2, byrow=TRUE)

a_dummy_p <- st_multipolygon(list(list(a_dummy_m), list(a_dummy_m), list(a_dummy_m)))

#mimicking foo structure to make a point with negative value of absolute maximum
dummy_neg <- structure(list(NUTS_ID = "N/A", SHAPE_AREA = st_area(a_dummy_p),
              SHAPE_LEN = st_length(a_dummy_p), CNTR_CODE = structure(1L, 
                                                .Label = "N/A", class = "factor"), 
              NAME_ASCI = structure(1L, .Label = c("N/A"), class = "factor"), 
              geometry = structure(list(a_dummy_p), class = c("sfc_MULTIPOLYGON", "sfc"),
              precision = 0, bbox = st_bbox(a_dummy_p), 
              crs = structure(list(epsg = 4326L, 
                     proj4string = "+proj=longlat +datum=WGS84 +no_defs"), 
                     class = "crs"), n_empty = 0L), 
                     district = "N/A", foo = -max_val), sf_column = "geometry", 
              agr = structure(c(NUTS_ID = NA_integer_, SHAPE_AREA = NA_integer_, 
                     SHAPE_LEN = NA_integer_, CNTR_CODE = NA_integer_, 
                     NAME_ASCI = NA_integer_, district = NA_integer_, 
                     foo = NA_integer_), 
             .Label = c("constant", "aggregate", "identity"), class = "factor"),
              row.names = 1L, class = c("sf", "data.frame"))


#and now making the data with positive value 
dummy_pos <- dummy_neg %>% mutate(foo=max_val)

#row binding those with `foo` dataset to make a new dataset which is "balanced"
foo2 <- rbind(dummy_neg, dummy_pos,foo)

mapView(foo2, zcol = 'foo', layer.name = "Example", col.regions = palfunc)

Created on 2019-06-25 by the reprex package (v0.3.0)

Community
  • 1
  • 1
M--
  • 25,431
  • 8
  • 61
  • 93