#Importar librerias
library(plotly)
## Warning: package 'plotly' was built under R version 4.2.3
## Loading required package: ggplot2
##
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
##
## last_plot
## The following object is masked from 'package:stats':
##
## filter
## The following object is masked from 'package:graphics':
##
## layout
library(ggplot2)
library(maps)
## Warning: package 'maps' was built under R version 4.2.3
library(ggmap)
## Warning: package 'ggmap' was built under R version 4.2.3
## ℹ Google's Terms of Service: ]8;;https://mapsplatform.google.com<https://mapsplatform.google.com>]8;;
## ℹ Please cite ggmap if you use it! Use `citation("ggmap")` for details.
##
## Attaching package: 'ggmap'
## The following object is masked from 'package:plotly':
##
## wind
library(sp)
## Warning: package 'sp' was built under R version 4.2.3
library(stats)
library(graphics)
library(rnaturalearth)
## Warning: package 'rnaturalearth' was built under R version 4.2.3
library(plotly)
Sys.setenv('MAPBOX_TOKEN' = 'pk.eyJ1IjoibGloa2lyIiwiYSI6ImNsZnIxNmM2YTAzeTkzc3BvMjRtYWk3cDUifQ.cmuXPv09JIs6GInX5K1uCQ')
Existen dos enfoques principales para hacer mapas en plotly.js: integrados o personalizados. Los mapas integrados utilizan Mapbox o tecnología d3.js para representar una capa base de mapa, y son convenientes para crear mapas rápidos sin necesidad de representaciones gráficas de objetos geoespaciales. Por otro lado, el enfoque personalizado ofrece un mayor control y es útil para crear mapas más sofisticados, utilizando herramientas de geocomputación como sf, sp o ggmap. La sección 4.2 de plotly.js cubre la creación de mapas sofisticados utilizando el paquete sf de R.
Con datos de latitud y longitud sencillos y se desea crear un mapa rápido, se pueden utilizar las opciones de mapeo integradas de plotly.js, como plot_mapbox() y plot_geo(). Estas funciones se pueden utilizar como un reemplazo directo de plot_ly(), y permiten crear un mapa base dinámico detrás de los datos. Además, todas las capas basadas en dispersión pueden ser utilizadas con plot_ly().
plot_mapbox(maps::canada.cities) %>%
add_markers(
x = ~long,
y = ~lat,
size = ~pop,
color = ~country.etc,
colors = "Accent",
text = ~paste(name, pop),
hoverinfo = "text"
)
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning: `line.width` does not currently support multiple values.
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
## Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Accent is 8
## Returning the palette you asked for with that many colors
FIGURA 4.1: Un gráfico de burbujas potenciado por mapbox que muestra la población de varias ciudades en Canadá. Para el interactivo, consulte https://plotly-r.com/interactives/mapbox-bubble.html
El estilo del mapa base de Mapbox se controla a través del layout.mapbox.styleatributo. El paquete plotly viene con soporte para 7 estilos diferentes, pero también puede proporcionar una URL personalizada a un estilo de cuadro de mapa personalizado . Para obtener todos los nombres de estilo de mapa base preempaquetados, puede obtenerlos del plotly.js oficial schema():
styles <- schema()$layout$layoutAttributes$mapbox$style$values
styles
## [1] "basic" "streets" "outdoors"
## [4] "light" "dark" "satellite"
## [7] "satellite-streets" "carto-darkmatter" "carto-positron"
## [10] "open-street-map" "stamen-terrain" "stamen-toner"
## [13] "stamen-watercolor" "white-bg"
#> [1] "basic" "streets"
#> [3] "outdoors" "light"
#> [5] "dark" "satellite"
#> [7] "satellite-streets" "open-street-map"
#> [9] "white-bg" "carto-positron"
#> [11] "carto-darkmatter" "stamen-terrain"
#> [13] "stamen-toner" "stamen-watercolor"
Cualquiera de estos valores se puede utilizar para un estilo de cuadro de mapa. La figura 4.2 muestra el mapa base de imágenes satelitales de la tierra.
layout(
plot_mapbox(),
mapbox = list(style = "satellite")
)
## No scattermapbox mode specifed:
## Setting the mode to markers
## Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
FIGURA 4.2: Ampliación de imágenes satelitales de la Tierra usando plot_mapbox(). Para el interactivo, consulte https://plotly-r.com/interactives/satellite.html
La figura 4.3 muestra cómo crear un menú desplegable en plotly.js para controlar el estilo del mapa base a través del atributo layout.updatemenusatributo. Este menú proporciona una lista de botones que invocan métodos de plotly.js con diferentes argumentos. En este caso, cada botón utiliza el método de retransmisión para modificar el atributo layout.mapbox.style y cambiar el estilo del mapa base.
style_buttons <- lapply(styles, function(s) {
list(
label = s,
method = "relayout",
args = list("mapbox.style", s)
)
})
layout(
plot_mapbox(),
mapbox = list(style = "dark"),
updatemenus = list(
list(y = 0.8, buttons = style_buttons)
)
)
## No scattermapbox mode specifed:
## Setting the mode to markers
## Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
FIGURA 4.3: Proporcionar un menú desplegable para controlar el estilo de la capa base del cuadro de mapa. Para el interactivo, consulte https://plotly-r.com/interactives/mapbox-style-dropdown.html
plot_geo() es otra solución de mapeo integrado en plotly que es compatible con diferentes proyecciones de mapas, pero tiene un estilo de mapa base limitado y puede ser más complicado de usar en comparación con plot_mapbox(). La figura 4.4 muestra cómo utilizar plot_geo() junto con add_markers() y add_segments() para visualizar rutas de vuelo dentro de los Estados Unidos. Mientras que plot_mapbox() está limitado a la proyección Mercator, plot_geo() tiene diferentes proyecciones disponibles, incluyendo la proyección ortográfica que simula el globo en 3D.
library(plotly)
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
# airport locations
air <- read.csv(
'https://plotly-r.com/data-raw/airport_locations.csv'
)
# flights between airports
flights <- read.csv(
'https://plotly-r.com/data-raw/flight_paths.csv'
)
flights$id <- seq_len(nrow(flights))
# map projection
geo <- list(
projection = list(
type = 'orthographic',
rotation = list(lon = -100, lat = 40, roll = 0)
),
showland = TRUE,
landcolor = toRGB("gray95"),
countrycolor = toRGB("gray80")
)
plot_geo(color = I("red")) %>%
add_markers(
data = air, x = ~long, y = ~lat, text = ~airport,
size = ~cnt, hoverinfo = "text", alpha = 0.5
) %>%
add_segments(
data = group_by(flights, id),
x = ~start_lon, xend = ~end_lon,
y = ~start_lat, yend = ~end_lat,
alpha = 0.3, size = I(1), hoverinfo = "none"
) %>%
layout(geo = geo, showlegend = FALSE)
## Warning: `line.width` does not currently support multiple values.
FIGURA 4.4: Uso de la proyección ortográfica integrada para visualizar patrones de vuelo en un globo ‘3D’. Para el interactivo, consulte https://plotly-r.com/interactives/geo-flights.html
Una ventaja de plot_geo() es que proyecta automáticamente las geometrías en el sistema de coordenadas adecuado por la proyección del mapa. En la Figura 4.5 se muestra que un segmento de línea simple aparece recto cuando se utiliza plot_mapbox(), pero curvo cuando se utiliza plot_geo(). Si bien es posible lograr el mismo efecto usando plot_ly() o plot_mapbox(), los datos deben estructurarse en una estructura de datos sf antes de ser renderizados.
map1 <- plot_mapbox() %>%
add_segments(x = -100, xend = -50, y = 50, yend = 75) %>%
layout(
mapbox = list(
zoom = 0,
center = list(lat = 65, lon = -75)
)
)
map2 <- plot_geo() %>%
add_segments(x = -100, xend = -50, y = 50, yend = 75) %>%
layout(geo = list(projection = list(type = "mercator")))
library(htmltools)
browsable(tagList(map1, map2))
FIGURA 4.5: Una comparación de las soluciones de mapeo integradas de plotlyplot_mapbox() : (arriba) y plot_geo()(abajo). El plot_geo()enfoque transformará segmentos de línea para reflejar correctamente su proyección en un sistema de coordenadas no cartesiano.
Además de las trazas de dispersión, las soluciones de mapeo integradas en plotly, como plot_mapbox() y plot_geo(), tienen un tipo de traza de coropletas optimizada. Choroplethmapbox es más poderoso ya que puede especificar completamente la colección de características usando GeoJSON, pero la traza de coropletas puede ser más fácil de usar si se ajusta a su caso de uso.
La Figura 4.6 utiliza el tipo de traza de coropletas en plotly, para mostrar la densidad de población de los Estados Unidos a través de los datos estatales del paquete de conjuntos de datos. Al proporcionar el atributo ‘z’, los objetos de plotly_geo() intentarán crear una coropleta, pero también se debe proporcionar un ‘ubicaciones’ y ‘modo de ubicación’. Se debe tener en cuenta que locationmode está actualmente limitado a países y estados de EE. UU., por lo que si se necesita una unidad geográfica diferente, se debe utilizar el tipo de traza choroplethmapbox o un enfoque de mapeo personalizado.
density <- state.x77[, "Population"] / state.x77[, "Area"]
g <- list(
scope = 'usa',
projection = list(type = 'albers usa'),
lakecolor = toRGB('white')
)
plot_geo() %>%
add_trace(
z = ~density, text = state.name, span = I(0),
locations = state.abb, locationmode = 'USA-states'
) %>%
layout(geo = g)
FIGURA 4.6: Un mapa de la densidad de población de EE. UU. utilizando los state.x77datos del paquete de conjuntos de datos . Choroplethmapbox es una opción más flexible que choropleth porque proporciona su propia definición GeoJSON de la coropleta a través del atributo geojson. Actualmente, este atributo debe ser una URL que apunte a un archivo geojson, y la ubicación debe apuntar a un atributo de identificación de nivel superior de cada entidad dentro del archivo geojson. La Figura 4.7 muestra un ejemplo de cómo podríamos visualizar la misma información de densidad de población que la Figura 4.6, pero esta vez usando choroplethmapbox.
plot_ly() %>%
add_trace(
type = "choroplethmapbox",
# See how this GeoJSON URL was generated at
# https://plotly-r.com/data-raw/us-states.R
geojson = paste(c(
"https://gist.githubusercontent.com/cpsievert/",
"7cdcb444fb2670bd2767d349379ae886/raw/",
"cf5631bfd2e385891bb0a9788a179d7f023bf6c8/",
"us-states.json"
), collapse = ""),
locations = row.names(state.x77),
z = state.x77[, "Population"] / state.x77[, "Area"],
span = I(0)
) %>%
layout(
mapbox = list(
style = "light",
zoom = 4,
center = list(lon = -98.58, lat = 39.82)
)
) %>%
config(
mapboxAccessToken = Sys.getenv("MAPBOX_TOKEN"),
# Workaround to make sure image download uses full container
# size https://github.com/plotly/plotly.js/pull/3746
toImageButtonOptions = list(
format = "svg",
width = NULL,
height = NULL
)
)
FIGURA 4.7: Otro mapa de la densidad de población de EE. UU., esta vez usando choroplethmapbox con un archivo GeoJSON personalizado.
Estas figuras (4.6 y 4.7) no son la forma ideal de visualizar la población estatal desde un punto de vista de percepción gráfica. El uso del color en coropletas para codificar una variable numérica puede ser engañoso, ya que el ojo percibe naturalmente el área que cubre un color en particular como proporcional a su efecto general, lo cual no puede estar relacionado con los datos codificados por el color. Un ejemplo clásico de este efecto engañoso se encuentra en los mapas electorales de EE. UU., donde la proporción de colores rojo y azul no es representativa del voto popular general.
La sección 4.2.2 de la fuente cubre el uso de cartogramas en plotly, una técnica que permite reducir el efecto engañoso de las coropletas y agregar otra dimensión para codificar datos a través del tamaño de las características geoespaciales. Para esto, se utiliza la librería sf y la función cartogram, que permite una proyección de tamaño uniforme de las áreas geográficas proporcionales a los datos codificados.
##4.2 Mapas personalizados ## 4.2.1 Características simples (sf)
El paquete sf en R es una herramienta moderna para trabajar con datos geoespaciales que se basa en principios de datos ordenados. La idea detrás de sf es almacenar las geometrías geoespaciales en una columna de lista de un marco de datos, lo que permite que cada fila represente la unidad real de observación/interés. El paquete no proporciona datos geoespaciales en sí, sino que proporciona el marco y las utilidades para almacenar y calcular estructuras de datos geoespaciales. Esto lo hace adecuado para flujos de trabajo ordenados más grandes.
Existen varios paquetes en R que permiten acceder y trabajar con datos geoespaciales, incluyendo estructuras de datos de características simples. Dos ejemplos notables son rnaturalearth y USAboundaries. rnaturalearth proporciona una API para acceder a datos de mapas de todo el mundo, mientras que USAboundaries es una buena opción para obtener datos de mapas de los Estados Unidos en cualquier momento histórico.
library(rnaturalearth)
world <- ne_countries(returnclass = "sf")
class(world)
## [1] "sf" "data.frame"
#> [1] "sf" "data.frame"
plot_ly(world, color = I("gray90"), stroke = I("black"), span = I(1))
## No trace type specified:
## Based on info supplied, a 'scatter' trace seems appropriate.
## Read more about this trace type -> https://plotly.com/r/reference/#scatter
FIGURA 4.8: Representación de todos los países del mundo usando plot_ly()y la ne_countries()función del paquete rnaturalearth .
El paquete sf almacena las geometrías geoespaciales en una columna de lista dentro de un marco de datos. Plot_ly() sabe cómo representar los países porque las características geoespaciales están codificadas en esta columna de lista especial llamada ‘geometría’, y los metadatos sobre la estructura geoespacial se conservan como atributos especiales de los datos. Además, el paquete sf proporciona métodos especiales de dplyr para esta clase especial de marco de datos, lo que permite tratar las manipulaciones de datos como si fuera una estructura de datos “ordenada”. La columna de geometría siempre se retiene, lo que significa que al seleccionar una columna específica, también se obtendrá la geometría.
library(sf)
## Warning: package 'sf' was built under R version 4.2.3
## Linking to GEOS 3.9.3, GDAL 3.5.2, PROJ 8.2.1; sf_use_s2() is TRUE
world %>%
select(name) %>%
print(n = 4)
## Simple feature collection with 177 features and 1 field
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: -180 ymin: -90 xmax: 180 ymax: 83.64513
## Geodetic CRS: +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
## First 4 features:
## name geometry
## 0 Fiji MULTIPOLYGON (((180 -16.067...
## 1 Tanzania MULTIPOLYGON (((33.90371 -0...
## 2 W. Sahara MULTIPOLYGON (((-8.66559 27...
## 3 Canada MULTIPOLYGON (((-122.84 49,...
FIGURA 4.9: Diagrama de un marco de datos de características simples. La columna de geometría rastrea las características espaciales adjuntas a cada fila en el marco de datos.
Existen 4 formas diferentes de renderizar objetos sf con plotly: plot_ly(), plot_mapbox(), plot_geo() ya través de ggplot2geom_sf(). Estas funciones representan varios polígonos utilizando un solo trazo de forma predeterminada, lo cual es rápido pero limita la flexibilidad de variar el color de varios polígonos. Para representar varios polígonos con diferentes colores, es necesario hacerlo mediante un identificador único (como name) y usar varias trazas. El uso de varias trazas también agrega automáticamente la capacidad de filtrar a través de entradas de leyenda.
canada <- ne_states(country = "Canada", returnclass = "sf")
plot_ly(canada, split = ~name, color = ~provnum_ne)
## No trace type specified:
## Based on info supplied, a 'scatter' trace seems appropriate.
## Read more about this trace type -> https://plotly.com/r/reference/#scatter
FIGURA 4.10: Uso de splity color para crear un mapa de coropletas de las provincias de Canadá.
Al crear mapas, puede ser necesario dividir varios polígonos en varios trazos para mostrar un relleno flotante diferente para cada polígono. Esto se puede lograr proporcionando un texto único dentro de cada polígono y especificando hoveron=‘fills’. Esto vincula el comportamiento de la información sobre herramientas con el relleno del trazo en lugar de mostrarlo en cada punto a lo largo del polígono.
plot_ly(
canada,
split = ~name,
color = I("gray90"),
text = ~paste(name, "is \n province number", provnum_ne),
hoveron = "fills",
hoverinfo = "text",
showlegend = FALSE
)
## No trace type specified:
## Based on info supplied, a 'scatter' trace seems appropriate.
## Read more about this trace type -> https://plotly.com/r/reference/#scatter
FIGURA 4.11: Uso de split, texty hoveron=’fills’para mostrar información sobre herramientas específica para cada provincia canadiense.
El enfoque de mapeo personalizado con plot_ly() y geom_sf() es más flexible que los enfoques de mapeo integrados como plot_mapbox() y plot_geo() ya que permite cualquier proyección de mapeo bien definida. Es posible buscar proyecciones de mapas en sitios web como http://spatialreference.org/ y extraer comandos para proyectar objetos geoespaciales en esa proyección. Para realizar la proyección, se puede proporcionar el comando PROJ4 relevante a la función st_transform() en sf.
# filter the world sf object down to canada
canada <- filter(world, name == "Canada")
# coerce cities lat/long data to an official sf object
cities <- st_as_sf(
maps::canada.cities,
coords = c("long", "lat"),
crs = 4326
)
# A PROJ4 projection designed for Canada
# http://spatialreference.org/ref/sr-org/7/
# http://spatialreference.org/ref/sr-org/7/proj4/
moll_proj <- "+proj=moll +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84
+units=m +no_defs"
# perform the projections
canada <- st_transform(canada, moll_proj)
cities <- st_transform(cities, moll_proj)
# plot with geom_sf()
p <- ggplot() +
geom_sf(data = canada) +
geom_sf(data = cities, aes(size = pop), color = "red", alpha = 0.3)
ggplotly(p)
FIGURA 4.12: La población de varias ciudades canadienses representada en un mapa base personalizado utilizando una proyección de Mollweide.
En algunos casos los objetos geoespaciales pueden tener una resolución demasiado alta para una visualización específica, lo que puede ralentizar el código R y afectar la capacidad de respuesta de la visualización. Por lo tanto, se puede utilizar la función st_simplify()de sf para simplificar las características antes de trazarlas y mejorar la velocidad del código y la capacidad de respuesta de la visualización. La resolución más alta permite ampliar mejor las regiones geoespaciales más complejas, pero los archivos HTML son más grandes y la capacidad de respuesta es más lenta.
sum(rapply(world$geometry, nrow))
## [1] 10654
#> [1] 10586
world_large <- ne_countries(scale = "large", returnclass = "sf")
sum(rapply(world_large$geometry, nrow))
## [1] 548471
#> [1] 548121
Al igual que en la sección 3.2, es importante tener en cuenta las ventajas y desventajas de representar gráficos utilizando uno o varios trazos, y saber cómo aprovechar cualquiera de los enfoques en Plotly. Por defecto, Plotly intenta representar todas las características simples en un solo trazo, lo cual es eficaz pero no proporciona mucha interactividad. Por otro lado, utilizar varios trazos permite una mayor flexibilidad para personalizar la apariencia de cada característica y agregar interactividad a cada una de ellas.
plot_mapbox(
world_large,
color = NA,
stroke = I("black"),
span = I(0.5)
)
## Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels