tmap icon indicating copy to clipboard operation
tmap copied to clipboard

Standardisation of symbols

Open mtennekes opened this issue 2 years ago • 21 comments

Symbols in view mode are working (see below) thanks to @tomroh , but we need to standardize the symbols between plotting modes:

Currently, tmap uses the pch numbering:

image

Note that black corresponds to the visual variable col and blue to fill. In order to make fill usable, tmap (by default) uses symbols 21:25.

leaflegend which I use for leaflet legends, natively supports these symbols:

Screenshot 2023-03-02 at 16 49 35
  • Only four of them match: rect, circle, triangle, and diamond.
  • The upside-down triangle is missing in leaflegend (@tomroh?)
  • The (filled) plus, cross, star, stadium, and polygon are missing in the pch list. I love the 'stadium' and I will try it out as default in non-symbol discrete color legends (also because it is similar to the rounded rectangle that I used in the default style).
  • I like the symbols to have userfriendly names, like leaflegend: circle is much easer to understand than 21 (you don't wanna know how often I google 'R pch' :-).
Screenshot 2023-03-01 at 20 58 14

What are your thoughts and ideas (@Nowosad @Robinlovelace @tim-salabim)?

mtennekes avatar Mar 02 '23 16:03 mtennekes

I think the core of the symbols are there in leaflegend. There are internals but modify triangle in makeSymbol as an example

'inverseTriangle' = htmltools::tags$polygon(
      id = 'triangle',
      points = sprintf('%s,%s %s,%s %s,%s',
                       strokewidth,
                       height + strokewidth,
                       width + strokewidth,
                       height + strokewidth,
                       width / 2  + strokewidth,
                       strokewidth),
      stroke = color,
      fill = fillColor,
      'stroke-opacity' = opacity,
      'fill-opacity' = fillOpacity,
	  transform = sprintf('translate(%f, %f) rotate(45 0 0 )', height/2, width/2),
      ...
    )

It's an svg so you can rotate it around the center point.

For pch, maybe a function generator to pick by both name an position

pch_symbol <- function(i, width, height, offset = 0) {
	c('rect' = drawRect(width, height, offset = 0) 
		'circle' = drawCircle(width, height, offset = 0))[i](width, height, offset = 0)
}

I'm happy to add this into leaflegend if it helps. I'm not sure on the naming conventions for each symbol.

tomroh avatar Mar 03 '23 03:03 tomroh

I like the symbols to have userfriendly names, like leaflegend: circle is much easer to understand than 21 (you don't wanna know how often I google 'R pch' :-).

Fully agree. Most important thing for users is ease-of-use and compatibility between plot and view modes for the most commonly needed shapes, and all of pch options are not needed IMO. Leaflegend looks amazing!

Robinlovelace avatar Mar 03 '23 08:03 Robinlovelace

For pch, maybe a function generator to pick by both name an position

pch_symbol <- function(i, width, height, offset = 0) {
	c('rect' = drawRect(width, height, offset = 0) 
		'circle' = drawCircle(width, height, offset = 0))[i](width, height, offset = 0)
}

I'm happy to add this into leaflegend if it helps. I'm not sure on the naming conventions for each symbol.

That would be awesome, @tomroh! This is especially great for backwards compatibility: probably each of the pch symbols is used in the wild.

mtennekes avatar Mar 03 '23 12:03 mtennekes

image

My proposal (feel free to improve):

0 open-rect 1 open-circle 2 open-triangle 3 simple-plus 4 simple-cross 5 open-diamond 6 open-down-triangle 7 cross-rect 8 simple-star 9 plus-diamond 10 plus-circle 11 hexagram 12 plus-rect 13 cross-circle 14 triangle-rect 15 solid-rect 16 solid-circle 17 solid-triangle 18 solid-diamond 19 solid-circle 20 solid-dot 21 circle 22 rect 23 diamond 24 triangle 25 down-triangle

The normal (filled) symbols will the defaults in tmap. Probably I can mimic your plus, cross, and star in plot mode with layering two pch 3/4/8 symbols, one with a large lwd in black ("col") and on top a small lwd symbol in blue ("fill").

mtennekes avatar Mar 03 '23 13:03 mtennekes

I still need to tweak some of the symbol aesthetics but the implementation will work like this:

pchNames <- stats::setNames(seq(0L, 25L, 1L),
  c('open-rect', 'open-circle', 'open-triangle', 'simple-plus', 
  'simple-cross', 'open-diamond', 'open-down-triangle', 'cross-rect', 
  'simple-star', 'plus-diamond', 'plus-circle', 'hexagram', 'plus-rect', 
  'cross-circle', 'triangle-rect', 'solid-rect', 'solid-circle-md', 
  'solid-triangle', 'solid-diamond', 'solid-circle-bg', 'solid-circle-sm', 'circle', 
  'rect', 'diamond', 'triangle', 'down-triangle'
  ))
defaultSize <- 20
i <- 1:26
pchSvg <- lapply(names(pchNames)[i], makePch, width = defaultSize, 
  color = 'black', `stroke-width` = 2, fillOpacity = .5)
pchSvgI <- lapply(i-1, makePch, width = defaultSize, 
  color = 'black', `stroke-width` = 2, fillOpacity = .5)
leaflet::leaflet(options = leaflet::leafletOptions(zoomControl = FALSE)) |> 
  addLegendImage(images = pchSvg, labels =names(pchNames), 
    width = defaultSize, height = defaultSize, position = 'topright') |> 
  addLegendImage(images = pchSvgI, labels = i-1, 
    width = defaultSize, height = defaultSize, position = 'topleft')
image

tomroh avatar Mar 04 '23 00:03 tomroh

tomroh/leaflegend@a75fd0f700991fa39f79e3614ea9e87b8680a250

It's in the development version on github now.

tomroh avatar Mar 04 '23 01:03 tomroh

Wow, amazing! Thanks!

I've tried

pchSvg <- lapply(names(pchNames)[i], makePch, width = defaultSize, 
				 color = 'black', `stroke-width` = 2, fillColor = "blue", fillOpacity = .5)
pchSvgI <- lapply(i-1, makePch, width = defaultSize, 
				  color = 'black', `stroke-width` = 2, fillColor = "blue", fillOpacity = .5)
leaflet::leaflet(options = leaflet::leafletOptions(zoomControl = FALSE)) |> 
	addLegendImage(images = pchSvg, labels =names(pchNames), 
				   width = defaultSize, height = defaultSize, position = 'topright') |> 
	addLegendImage(images = pchSvgI, labels = i-1, 
				   width = defaultSize, height = defaultSize, position = 'topleft')
Screenshot 2023-03-06 at 21 58 46

Almost what I expected. The only thing is that 21:25 don't have a black border color anymore. Is that correct, or do I miss something.

mtennekes avatar Mar 06 '23 21:03 mtennekes

What browser? Although I don't think it should matter. Do you have the output of:

cat(URLdecode(pchSvg[[26]]))

I copied that same code you put up there and:

image

tomroh avatar Mar 07 '23 01:03 tomroh

My mistake: I was sleeping :-)) My screenshot only showed 1-18, the last ones didn't fit my laptop screen. I've checked again and it looks great! Thanks again!

I'll leave this issue open until I've implemented this in tmap (as reminder).

mtennekes avatar Mar 07 '23 19:03 mtennekes

Hi @tomroh I'm about to implement the new symbols. However, I get this:

leaflegend::makeSymbolIcons(shape = "open-rect", width = 20, height = 20, color = "#000000", opacity = 1)
#> Error in (function (shape, width, height = width, color, fillColor = color, : Invalid shape argument.

Created on 2023-04-25 with reprex v2.0.2

I have forgotten why, but currently tmap uses makeSymbolIcons for the map and makeSymbols for the legend.

Do you have any clue?

mtennekes avatar Apr 25 '23 08:04 mtennekes

pch symbols are in a different function. Use makePch for 'open-rect' and the like. makeSymbolIcons is used for the map because the svg data uris (images) need to be wrapped in leaflet::icon. I added makePchIcons so that you have the same functionality.

tomroh avatar Apr 26 '23 00:04 tomroh

> leaflegend::makePchIcons
function (shape, color, fillColor = color, opacity, fillOpacity = opacity, 
    strokeWidth = 1, width, height = width, ...) 
{
    symbols <- Map(makeSymbol, shape = shape, width = width, 
        height = height, color = color, fillColor = fillColor, 
        opacity = opacity, fillOpacity = fillOpacity, `stroke-width` = strokeWidth, 
        ...)
    leaflet::icons(iconUrl = unname(symbols), iconAnchorX = width/2, 
        iconAnchorY = height/2)
}
<bytecode: 0x10e0e5548>
<environment: namespace:leaflegend>

Shouldn't it be Map(makePch, ... ) instead of Map(makeSymbol, ...) @tomroh ?

mtennekes avatar Apr 26 '23 06:04 mtennekes

I rushed that... I refactored the code. You can now use makeSymbol and makeSymbolIcons. Usage:

pchNames <- stats::setNames(seq(0L, 25L, 1L),
  c('open-rect', 'open-circle', 'open-triangle', 'simple-plus',
    'simple-cross', 'open-diamond', 'open-down-triangle', 'cross-rect',
    'simple-star', 'plus-diamond', 'plus-circle', 'hexagram', 'plus-rect',
    'cross-circle', 'triangle-rect', 'solid-rect', 'solid-circle-md',
    'solid-triangle', 'solid-diamond', 'solid-circle-bg', 'solid-circle-sm', 'circle',
    'rect', 'diamond', 'triangle', 'down-triangle'
  ))
i <- 1:26
pchSvg <- lapply(names(pchNames)[i], makeSymbol, width = defaultSize,
  color = 'black', `stroke-width` = 2, fillOpacity = .5)
leaflet::leaflet(options = leaflet::leafletOptions(zoomControl = FALSE)) |>
  addSymbols(lng = i, lat = i, shape = availableShapes()[['pch']],
    color = 'black', values = i, `strokeWidth` = 2, fillOpacity = .5) |> 
  addLegendImage(images = pchSvg, labels =names(pchNames),
    width = defaultSize, height = defaultSize, position = 'topright')

Hopefully that makes it easier to work with. There is a name conflict with original symbols and pch names but they return the same svg specs for the symbol.

tomroh avatar Apr 27 '23 17:04 tomroh

leaflegend version 1.1 is on its way to CRAN. In addition to the pch symbols, you can show NA encodings in the legend when NAs are present in the values, and you can also change the labels for addLegendNumeric.

tomroh avatar May 04 '23 15:05 tomroh

Excellent @tomroh thanks! Very handy that you standardised makeSymbol(Icons)

I just noticed one discrepancy with the base implementation of pch symbols: In base R the color of symbols 15 to (and including) 20 come from color rather than fill:

Conceptually, both interpretations are explainable. Using fill for symbols 15-20 is more intuitive for most users I think, but on the other hand, there may be users who are familiar with using color.

What do you think? Also glad to hear more opinions (eg @Nowosad @Robinlovelace @xiaofanliang @tim-salabim )

mtennekes avatar May 08 '23 12:05 mtennekes

I think fill may be slightly more intuitive. There are few people in the world of ggplot2 who use base R as a daily driver for graphics so I think whatever is most intuitive, without worrying about compatibility issues, is a reasonable approach here.

Robinlovelace avatar May 08 '23 14:05 Robinlovelace

Personally, I like fill better as it's more intuitive. Though, I tend to stick to the upstream leaflet terminology where it would be fillColor I guess

tim-salabim avatar May 08 '23 14:05 tim-salabim

I think fillColor is more consistent with the use of leaflegend and more intuitive (granted I'm not a base R plot user in general). You don't have to context switch and treat 6 symbols differently than the others. The only issue I could see is if someone is developing something that can switch between base R static and leaflet interactive graphics. Then it would be a lot smoother to have the color specs consistent. Although, fillColor != fill and col != color anyway, except with partial matching of arguments.

tomroh avatar May 08 '23 16:05 tomroh

Thanks for your input!

tmap4 has a few upstream paths, e.g. leaflet for view mode and grid for plot mode (in the future perhaps also rayshader). So therefore the aim is to have a consistent set of visual variable and values, which is often a trade-off. At the start of tmap4 (already 2 years ago 🙈), we had a short discussion about the variable names: https://github.com/r-tmap/tmap/issues/579

The only issue I could see is if someone is developing something that can switch between base R static and leaflet interactive graphics.

Yes, that is exactly one of the main aims of tmap.

I will settle with the most intuitive, and that is using fill. It could mean that backwards compatibility is not guaranteed, but perhaps I can catch those cases.

mtennekes avatar May 09 '23 08:05 mtennekes

I could make it so you could specify fillColor or the color argument for pch 15-20 if that would help?

tomroh avatar May 09 '23 15:05 tomroh

solid pch symbols now use color if fillColor is missing in >=v1.1.1

tomroh avatar Aug 18 '23 16:08 tomroh

I've added this example to tm_symbols:

# create grid of 25 points in the Athlantic
library(sf)
x = st_as_sf(cbind(expand.grid(x = -51:-47, y = 20:24), id = seq_len(25)), 
    coords = c("x", "y"), crs = 4326)

tm_shape(x, bbox = bb(x, ext = 1.2)) +
tm_symbols(shape = "id",
    size = 2,
    lwd = 2,
    fill = "orange",
    col = "black",
    shape.scale = tm_scale_asis()) +
tm_text("id", ymod = -2)

Plot mode:

image

View mode:

Screenshot 2024-04-25 at 11 17 33

Thx @tomroh for making this possible!

mtennekes avatar Apr 25 '24 09:04 mtennekes