{"id":13712473,"url":"https://github.com/jschoeley/tricolore","last_synced_at":"2025-10-22T06:05:46.494Z","repository":{"id":37850914,"uuid":"111605917","full_name":"jschoeley/tricolore","owner":"jschoeley","description":"A flexible color scale for ternary compositions","archived":false,"fork":false,"pushed_at":"2024-05-14T13:27:21.000Z","size":34153,"stargazers_count":153,"open_issues_count":2,"forks_count":21,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-11T06:06:34.745Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jschoeley.png","metadata":{"files":{"readme":"README.Rmd","changelog":"NEWS.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-21T21:59:39.000Z","updated_at":"2025-03-31T22:31:07.000Z","dependencies_parsed_at":"2024-05-14T14:53:39.055Z","dependency_job_id":null,"html_url":"https://github.com/jschoeley/tricolore","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jschoeley%2Ftricolore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jschoeley%2Ftricolore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jschoeley%2Ftricolore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jschoeley%2Ftricolore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jschoeley","download_url":"https://codeload.github.com/jschoeley/tricolore/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252779018,"owners_count":21802866,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T23:01:18.907Z","updated_at":"2025-10-22T06:05:41.465Z","avatar_url":"https://github.com/jschoeley.png","language":"R","funding_links":[],"categories":["Themes and aesthetics","R","ggplot"],"sub_categories":["Palettes 🎨"],"readme":"---\ntitle: \"tricolore. A flexible color scale for ternary compositions\"\nauthor: \"Jonas Schöley \u0026 Ilya Kashnitsky\"\noutput: github_document\n---\n\n```{r echo=FALSE}\nknitr::opts_chunk$set(warning=FALSE,\n                      message=FALSE,\n                      fig.width = 12,\n                      fig.height = 12)\n```\n\n[![CRAN_Version](https://www.r-pkg.org/badges/version/tricolore)](https://cran.r-project.org/package=tricolore)\n[![GitHub Actions R-CMD-check](https://github.com/jschoeley/tricolore/actions/workflows/R-CMD-check.yaml/badge.svg)\n[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n\nWhat is *tricolore*?\n--------------------\n\n`tricolore` is an R library providing a flexible color scale for the visualization of three-part (ternary) compositions. Its main functionality is to color-code any ternary composition as a mixture of three primary colors and to draw a suitable color-key. `tricolore` flexibly adapts to different visualization challenges via\n\n- *discrete* and *continuous* color support,\n- support for unbalanced compositional data via *centering*,\n- support for data with very narrow range via *scaling*,\n- *hue*, *chroma* and *lightness* options.\n\n![](README_files/teaser.png)\n\nGetting Started\n---------------\n\n```{r eval=FALSE}\ninstall.packages('tricolore')\nlibrary(tricolore); DemoTricolore()\n```\n\nThe `Tricolore()` function expects a dataframe of three-part compositions, color-codes the compositions and returns a list with elements `rgb` and `key`. The first list element is a vector of rgb codes for the color-coded compositions, the latter element gives a plot of the color key.\n\nHere's a minimal example using simulated data.\n\n```{r message=FALSE, fig.cap='A ternary color key with the color-coded compositional data visible as points.'}\nlibrary(tricolore)\n\n# simulate 243 ternary compositions\nP \u003c- as.data.frame(prop.table(matrix(runif(3^6), ncol = 3), 1))\n# color-code each composition and return a corresponding color key\ncolors_and_legend \u003c- Tricolore(P, 'V1', 'V2', 'V3')\n# the color-coded compositions\nhead(colors_and_legend$rgb)\ncolors_and_legend$key\n```\n\nYou can familiarize yourself with the various options of `tricolore` by running `DemoTricolore()`.\n\nTernary choropleth maps\n-----------------------\n\nHere I demonstrate how to create a choropleth map of the regional distribution of education attainment in Europe 2016 using `ggplot2`.\n\nThe data set `euro_example` contains the administrative boundaries for the European NUTS-2 regions in the column `geometry`. This data can be used to plot a choropleth map of Europe using the `sf` package. Each region is represented by a single row. The name of a region is given by the variable `name` while the respective [NUTS-2](https://en.wikipedia.org/wiki/Nomenclature_of_Territorial_Units_for_Statistics) geocode is given by the variable `id`. For each region some compositional statistics are available: Variables starting with `ed` refer to the relative share of population ages 25 to 64 by educational attainment in 2016 and variables starting with `lf` refer to the relative share of workers by labor-force sector in the European NUTS-2 regions 2016.\n\n**1. Using the `Tricolore()` function, color-code each educational composition in the `euro_example` data set and add the resulting vector of hex-srgb colors as a new variable to the dataframe. Store the color key separately.**\n\n```{r}\n# color-code the data set and generate a color-key\ntric_educ \u003c- Tricolore(euro_example,\n                       p1 = 'ed_0to2', p2 = 'ed_3to4', p3 = 'ed_5to8')\n```\n\n`tric` contains both a vector of color-coded compositions (`tric$rgb`) and the corresponding color key (`tric$key`). We add the vector of colors to the map-data.\n\n```{r}\n# add the vector of colors to the `euro_example` data\neuro_example$educ_rgb \u003c- tric_educ$rgb\n```\n\n**2. Using `ggplot2` and the joined color-coded education data and geodata, plot a ternary choropleth map of education attainment in the European regions. Add the color key to the map.**\n\nThe secret ingredient is `scale_fill_identity()` to make sure that each region is colored according to the value in the `educ_rgb` variable of `euro_example`.\n\n```{r}\nlibrary(ggplot2)\n\nplot_educ \u003c-\n  # using data sf data `euro_example`...\n  ggplot(euro_example) +\n  # ...draw a choropleth map\n  geom_sf(aes(fill = educ_rgb, geometry = geometry), size = 0.1) +\n  # ...and color each region according to the color-code\n  # in the variable `educ_rgb`\n  scale_fill_identity()\n\nplot_educ\n```\n\nUsing `annotation_custom()` and `ggplotGrob` we can add the color key produced by `Tricolore()` to the map. Internally, the color key is produced with the [`ggtern`](http://www.ggtern.com/) package. In order for it to render correctly we need to load `ggtern` *after* loading `ggplot2`. Don't worry, the `ggplot2` functions still work.\n\n```{r}\nlibrary(ggtern)\nplot_educ +\n  annotation_custom(\n    ggplotGrob(tric_educ$key),\n    xmin = 55e5, xmax = 75e5, ymin = 8e5, ymax = 80e5\n  )\n```\n\nBecause the color key behaves just like a `ggplot2` plot we can change it to our liking.\n\n```{r}\nplot_educ \u003c-\n  plot_educ +\n  annotation_custom(\n    ggplotGrob(\n      tric_educ$key +\n        labs(L = '0-2', T = '3-4', R = '5-8')),\n    xmin = 55e5, xmax = 75e5, ymin = 8e5, ymax = 80e5\n  )\nplot_educ\n```\n\nSome final touches...\n\n```{r}\nplot_educ +\n  theme_void() +\n  coord_sf(datum = NA) +\n  labs(title = 'European inequalities in educational attainment',\n       subtitle = 'Regional distribution of ISCED education levels for people aged 25-64 in 2016.')\n```\n\nContinuous vs. discrete colors\n------------------------------\n\nBy default `tricolore` uses a discrete colors scale with 16 colors. This can be changed via the `breaks` parameter. A value of `Inf` gives a continuous color scale...\n\n```{r}\n# color-code the data set and generate a color-key\ntric_educ_disc \u003c- Tricolore(euro_example,\n                            p1 = 'ed_0to2', p2 = 'ed_3to4', p3 = 'ed_5to8',\n                            breaks = Inf)\neuro_example$educ_rgb_disc \u003c- tric_educ_disc$rgb\n\nggplot(euro_example) +\n  geom_sf(aes(fill = educ_rgb_disc, geometry = geometry), size = 0.1) +\n  scale_fill_identity() +\n  annotation_custom(\n    ggplotGrob(\n      tric_educ_disc$key +\n        labs(L = '0-2', T = '3-4', R = '5-8')),\n    xmin = 55e5, xmax = 75e5, ymin = 8e5, ymax = 80e5\n  ) +\n  theme_void() +\n  coord_sf(datum = NA) +\n  labs(title = 'European inequalities in educational attainment',\n       subtitle = 'Regional distribution of ISCED education levels for people aged 25-64 in 2016.')\n```\n\n...and a `breaks = 2` gives a discrete color scale with $2^2=4$ colors, highlighting the regions with an absolute majority of any part of the composition.\n\n```{r}\n# color-code the data set and generate a color-key\ntric_educ_disc \u003c- Tricolore(euro_example,\n                            p1 = 'ed_0to2', p2 = 'ed_3to4', p3 = 'ed_5to8',\n                            breaks = 2)\neuro_example$educ_rgb_disc \u003c- tric_educ_disc$rgb\n\nggplot(euro_example) +\n  geom_sf(aes(fill = educ_rgb_disc, geometry = geometry), size = 0.1) +\n  scale_fill_identity() +\n  annotation_custom(\n    ggplotGrob(\n      tric_educ_disc$key +\n        labs(L = '0-2', T = '3-4', R = '5-8')),\n    xmin = 55e5, xmax = 75e5, ymin = 8e5, ymax = 80e5\n  ) +\n  theme_void() +\n  coord_sf(datum = NA) +\n  labs(title = 'European inequalities in educational attainment',\n       subtitle = 'Regional distribution of ISCED education levels for people aged 25-64 in 2016.')\n```\n\nTernary centering\n-----------------\n\nWhile the ternary balance scheme allows for dense yet clear visualizations of *well spread out* ternary compositions the technique is less informative when used with highly *unbalanced data*. The map below shows the regional labor force composition in Europe as of 2016 in nearly monochromatic colors, the different shades of blue signifying a working population which is concentrated in the tertiary (services) sector. Regions in Turkey and Eastern Europe show a somewhat higher concentration of workers in the primary (production) sector but overall the data shows little variation with regards to the *visual reference point*, i.e. the greypoint marking perfectly balanced proportions.\n\n```{r}\ntric_lf_non_centered \u003c- Tricolore(euro_example, breaks = Inf,\n                                  'lf_pri', 'lf_sec', 'lf_ter')\n\neuro_example$rgb_lf_non_centered \u003c- tric_lf_non_centered$rgb\n\nggplot(euro_example) +\n  geom_sf(aes(fill = rgb_lf_non_centered, geometry = geometry), size = 0.1) +\n  scale_fill_identity() +\n  annotation_custom(\n    ggplotGrob(tric_lf_non_centered$key +\n                 labs(L = '% Primary', T = '% Secondary', R = '% Tertiary')),\n    xmin = 55e5, xmax = 75e5, ymin = 8e5, ymax = 80e5\n  ) +\n  theme_void() +\n  coord_sf(datum = NA) +\n  labs(title = 'European inequalities in labor force composition',\n       subtitle = 'Regional distribution of labor force across the three sectors in 2016.')\n\n```\n\nA remedy for analyzing data which shows little variation in relation to some reference point is to *change the point of reference*. The map below yet again shows the European regional labor force composition in 2016 but the color scale has been altered so that its greypoint -- the visual point of reference -- is positioned at the European annual average. Consequently the colors now show direction and magnitude of the deviation from the European average labor force composition. Pink, Green and Blue hues show a higher than average share of workers in the primary, secondary and tertiary sector respectively. The saturation of the colors show the magnitude of that deviation with perfect grey marking a region that has a labor force composition equal to the European average, i.e. the reference point.\n\nCentering the color scale over the labor-force composition of the average European NUTS-2 region shows various patterns of deviations from the average. Metropolitan regions (Hamburg, Stockholm, Paris, Madrid) have a higher than average share of tertiary workers. Large parts of France are quite grey, indicating a labor-force composition close to the average, while Eastern Europe, the south of Spain and Italy have a higher than average share of workers active in the primary sector.\n\n```{r}\ntric_lf_centered \u003c-\n  Tricolore(euro_example,\n            'lf_pri', 'lf_sec', 'lf_ter',\n            center = NA, crop = TRUE)\n\neuro_example$rgb_lf_centered \u003c- tric_lf_centered$rgb\n\nggplot(euro_example) +\n  geom_sf(aes(fill = rgb_lf_centered, geometry = geometry), size = 0.1) +\n  scale_fill_identity() +\n  annotation_custom(\n    ggplotGrob(\n      tric_lf_centered$key +\n        labs(L = '% Primary', T = '% Secondary', R = '% Tertiary')),\n    xmin = 55e5, xmax = 75e5, ymin = 8e5, ymax = 80e5\n  ) +\n  theme_void() +\n  coord_sf(datum = NA) +\n  labs(title = 'European inequalities in labor force composition',\n       subtitle = 'Regional distribution of labor force across the three sectors in 2016.')\n```\n\nContributing\n------------\n\nThis software is an academic project. We welcome any issues and pull requests.\n\nPlease report any bugs you find by submitting an issue on github.com/jschoeley/tricolore/issues.\n\nIf you wish to contribute, please submit a pull request following the guidelines stated in [CONTRIBUTING.md](https://github.com/jschoeley/tricolore/blob/devel/CONTRIBUTING.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjschoeley%2Ftricolore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjschoeley%2Ftricolore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjschoeley%2Ftricolore/lists"}