Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Third-party notices

EarthPrints is licensed under the Apache License 2.0 (see `LICENSE`). The colour maps used by the fingerprint plot come from the sources below, under their own licences.

## Scientific colour maps (Fabio Crameri)

Used for vik, berlin, broc, lisbon, cork, tofino, bam, vanimo, roma and managua, in `src/lib/map/colormapTables.ts`. The tables are the official 256-entry tables from version 8.0.0, downsampled to 33 stops. The dark-centred maps (berlin, lisbon, tofino, vanimo, managua) are modified: their centre is shifted onto the app's dark panel colour, and the shift fades out linearly toward both ends.

- Source: Crameri, F. (2023). *Scientific colour maps* (8.0.0). Zenodo. https://doi.org/10.5281/zenodo.8035877
- Website: https://www.fabiocrameri.ch/colourmaps/
- Licence: MIT

Further reading: Crameri, F., Shephard, G. E. and Heron, P. J. (2020). The misuse of colour in science communication. *Nature Communications*, 11, 5444. https://doi.org/10.1038/s41467-020-19160-7

```
MIT License

Copyright (c) Fabio Crameri

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

## ColorBrewer (Cynthia A. Brewer)

Used for RdBu, in `src/lib/map/colormapTables.ts`, reversed so blue is negative.

- Source: Brewer, C. A. ColorBrewer 2.0. https://colorbrewer2.org
- Repository: https://github.com/axismaps/colorbrewer
- Licence: Apache License 2.0 (the same licence as EarthPrints, full text in `LICENSE`)

## CET perceptually uniform colour maps (Peter Kovesi)

Used for Flux, in `src/lib/map/fingerprintScale.ts`: `linear_kbc_5_95_c73` for uptake and reversed `linear_kryw_5_100_c67` for release, taken from the colorcet package.

- Source: Kovesi, P. (2015). Good colour maps: how to design them. arXiv:1509.03700. https://arxiv.org/abs/1509.03700
- Website: https://colorcet.com
- Package: colorcet (HoloViz), https://github.com/holoviz/colorcet
- Licence: Creative Commons Attribution 4.0 International (CC BY 4.0), https://creativecommons.org/licenses/by/4.0/
12 changes: 3 additions & 9 deletions src/components/panels/ColormapSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
fingerprintRampGradient,
type ColormapId,
type Palette,
PALETTES,
} from "@/lib/map/fingerprintScale";
import {
Select,
Expand All @@ -16,13 +17,6 @@ import {
} from "@/components/ui/select";
import { useTheme } from "@/providers/ThemeProvider";

const PALETTES: Palette[] = ["science", "flux"];

const PALETTE_LABELS: Record<Palette, string> = {
science: "Science",
flux: "Flux",
};

const SWATCHES = Object.fromEntries(
(Object.keys(COLORMAPS) as ColormapId[]).map((id) => [
id,
Expand Down Expand Up @@ -53,7 +47,7 @@ export function ColormapSelect({ value, onChange }: ColormapSelectProps) {
<SelectTrigger aria-label="Palette">
<SelectValue>
<Swatch id={selected} />
{PALETTE_LABELS[value]}
{COLORMAPS[selected].label}
</SelectValue>
</SelectTrigger>
<SelectContent>
Expand All @@ -66,7 +60,7 @@ export function ColormapSelect({ value, onChange }: ColormapSelectProps) {
title={COLORMAPS[id].description}
>
<Swatch id={id} />
{PALETTE_LABELS[palette]}
{COLORMAPS[id].label}
</SelectItem>
);
})}
Expand Down
8 changes: 6 additions & 2 deletions src/hooks/useSeriesDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import {
type TimeBasis,
} from "@/lib/zarr/localTime";
import { hasFiniteValues } from "@/lib/zarr/series";
import { colormapFor, type Palette } from "@/lib/map/fingerprintScale";
import {
colormapFor,
DEFAULT_PALETTE,
type Palette,
} from "@/lib/map/fingerprintScale";
import { loadPalette, savePalette } from "@/lib/settings/colormap";
import { useTheme } from "@/providers/ThemeProvider";
import type { CellSeries } from "@/hooks/useCellSeries";
Expand All @@ -29,7 +33,7 @@ export function useSeriesDisplay(
const [plotView, setPlotView] = useState<PlotView>("line");
const [transposed, setTransposed] = useState(false);
const [palette, setPaletteState] = useState<Palette>(
() => loadPalette() ?? "science",
() => loadPalette() ?? DEFAULT_PALETTE,
);
const colormapId = colormapFor(palette, isLight);

Expand Down
192 changes: 182 additions & 10 deletions src/lib/map/colormapTables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
* Lookup tables for the diverging colour maps offered by the fingerprint plot.
*
* These are Fabio Crameri's perceptually uniform, colourblind-safe scientific
* colour maps, downsampled from the official 256-entry tables to 33 stops
* (indistinguishable once interpolated, small enough to read in a diff). Cite
* as: Crameri, F. (2023), Scientific colour maps, Zenodo,
* doi:10.5281/zenodo.1243862 (MIT). RdBu is the familiar ColorBrewer ramp, kept
* as a non-uniform reference point.
* colour maps, downsampled from the official 256-entry tables (version 8.0.0)
* to 33 stops by taking entry round(i * 255 / 32) (indistinguishable once
* interpolated, small enough to read in a diff). RdBu is the familiar
* ColorBrewer ramp, kept as a non-uniform reference point. Sources and licences
* are in THIRD_PARTY_NOTICES.md.
*
* Every table here is diverging with a neutral centre, so stop 16 of 33 is the
* zero colour. `vik`, `broc`, `cork` and `roma` are light-centred; `berlin` and
* `vanimo` are dark-centred, so a near-zero cell recedes into a dark panel
* instead of glowing. The Flux palette is not here: it is two sequential CET
* zero colour. `vik`, `broc`, `cork`, `bam` and `roma` are light-centred; `berlin`,
* `lisbon`, `tofino`, `vanimo` and `managua` are their dark-centred
* counterparts, so a near-zero cell recedes into a dark panel instead of
* glowing (see `onDarkPanel`). The Flux palette is not here: it is two sequential CET
* ramps glued at zero rather than a single diverging table, and lives in
* `fingerprintScale.ts` with the rest of the CET data.
*/
Expand Down Expand Up @@ -234,6 +235,150 @@ const VANIMO: readonly Rgb[] = [
[190, 253, 165],
];

const LISBON: readonly Rgb[] = [
[230, 229, 255],
[208, 214, 242],
[187, 198, 229],
[165, 183, 217],
[144, 167, 204],
[124, 152, 192],
[104, 137, 179],
[84, 121, 166],
[65, 106, 151],
[48, 90, 134],
[36, 76, 116],
[27, 63, 97],
[22, 51, 79],
[18, 40, 62],
[17, 31, 46],
[18, 25, 33],
[23, 25, 25],
[30, 29, 23],
[40, 38, 26],
[53, 50, 32],
[67, 63, 40],
[81, 76, 49],
[96, 90, 57],
[111, 105, 67],
[127, 120, 76],
[143, 135, 88],
[160, 152, 101],
[177, 169, 117],
[193, 186, 135],
[209, 203, 154],
[224, 220, 175],
[240, 238, 195],
[255, 255, 217],
];

const TOFINO: readonly Rgb[] = [
[222, 217, 255],
[200, 202, 246],
[179, 187, 236],
[158, 172, 227],
[136, 157, 217],
[115, 141, 206],
[95, 125, 193],
[76, 109, 175],
[62, 94, 154],
[52, 80, 134],
[44, 68, 113],
[36, 56, 93],
[29, 45, 74],
[23, 35, 56],
[18, 26, 40],
[15, 21, 27],
[13, 22, 19],
[15, 27, 18],
[18, 35, 20],
[22, 47, 25],
[28, 59, 31],
[34, 72, 37],
[40, 86, 44],
[47, 100, 52],
[55, 115, 60],
[65, 131, 69],
[80, 147, 80],
[100, 164, 92],
[124, 179, 105],
[148, 192, 118],
[172, 205, 131],
[195, 217, 143],
[219, 230, 155],
];

const BAM: readonly Rgb[] = [
[101, 2, 75],
[119, 23, 93],
[136, 39, 110],
[151, 53, 126],
[165, 67, 140],
[178, 80, 153],
[189, 94, 165],
[200, 110, 176],
[209, 127, 188],
[217, 145, 198],
[224, 163, 208],
[231, 181, 218],
[236, 198, 226],
[241, 213, 233],
[245, 226, 239],
[246, 235, 241],
[246, 241, 240],
[243, 243, 237],
[238, 242, 228],
[229, 239, 213],
[216, 232, 194],
[200, 222, 171],
[181, 210, 146],
[161, 196, 122],
[141, 181, 101],
[122, 167, 84],
[105, 153, 70],
[90, 141, 58],
[76, 129, 47],
[62, 117, 37],
[48, 104, 25],
[32, 90, 13],
[13, 76, 0],
];

const MANAGUA: readonly Rgb[] = [
[255, 207, 103],
[245, 191, 98],
[235, 176, 93],
[225, 161, 88],
[216, 147, 83],
[206, 134, 79],
[197, 121, 74],
[187, 109, 70],
[177, 98, 67],
[166, 87, 63],
[154, 76, 61],
[142, 66, 58],
[129, 57, 57],
[116, 49, 57],
[105, 43, 60],
[95, 41, 65],
[87, 41, 73],
[82, 44, 83],
[78, 50, 96],
[76, 59, 111],
[76, 69, 127],
[77, 81, 142],
[80, 93, 156],
[83, 105, 168],
[87, 117, 179],
[92, 129, 189],
[96, 142, 199],
[102, 156, 208],
[107, 170, 218],
[112, 184, 227],
[118, 199, 236],
[123, 215, 245],
[129, 231, 255],
];

/** ColorBrewer RdBu, reversed so blue is negative and red positive like the rest. */
const RDBU: readonly Rgb[] = [
[5, 48, 97],
Expand All @@ -249,14 +394,41 @@ const RDBU: readonly Rgb[] = [
[103, 0, 31],
];

/** The dark theme's panel colour, `--editor-bg-primary` in `globals.css`. */
export const DARK_PANEL: Rgb = [33, 33, 32];

/**
* The dark-centred maps bottom out darker than the dark panel, so near-zero
* cells read as a black band instead of receding. Shift the centre stop onto
* the panel colour and fade the shift linearly to nothing at both ends; the
* shift changes more slowly than the maps brighten, so lightness still rises
* steadily away from zero.
*/
function onDarkPanel(lut: readonly Rgb[]): readonly Rgb[] {
const mid = (lut.length - 1) / 2;
const centre = lut[mid];
return lut.map(([r, g, b], i) => {
const w = 1 - Math.abs(i - mid) / mid;
return [
Math.round(r + (DARK_PANEL[0] - centre[0]) * w),
Math.round(g + (DARK_PANEL[1] - centre[1]) * w),
Math.round(b + (DARK_PANEL[2] - centre[2]) * w),
];
});
}

/** Every table that `fingerprintColorScale` can sample, keyed by colormap id. */
export const DIVERGING_TABLES = {
vik: VIK,
berlin: BERLIN,
berlin: onDarkPanel(BERLIN),
broc: BROC,
cork: CORK,
roma: ROMA,
vanimo: VANIMO,
vanimo: onDarkPanel(VANIMO),
lisbon: onDarkPanel(LISBON),
tofino: onDarkPanel(TOFINO),
bam: BAM,
managua: onDarkPanel(MANAGUA),
rdbu: RDBU,
} as const;

Expand Down
Loading
Loading