Selkie.git | t/ | 46-plot-palette.rakutest


use Test;
use lib 'lib';

use Selkie::Plot::Palette;

plan 11;

subtest "default series palette is okabe-ito" => {
	plan 2;
	my @default = Selkie::Plot::Palette.series;
	my @explicit = Selkie::Plot::Palette.series('okabe-ito');
	is @default.elems, 8, "default palette has 8 colors";
	is @default, @explicit, "default name resolves to okabe-ito";
};

subtest "okabe-ito palette has documented RGB values" => {
	plan 8;
	my @p = Selkie::Plot::Palette.series('okabe-ito');
	is @p[0], 0x999999, "gray (substituted from black)";
	is @p[1], 0xE69F00, "orange";
	is @p[2], 0x56B4E9, "sky blue";
	is @p[3], 0x009E73, "bluish green";
	is @p[4], 0xF0E442, "yellow";
	is @p[5], 0x0072B2, "blue";
	is @p[6], 0xD55E00, "vermillion";
	is @p[7], 0xCC79A7, "reddish purple";
};

subtest "tol-bright palette has 7 documented colors" => {
	plan 7;
	my @p = Selkie::Plot::Palette.series('tol-bright');
	is @p[0], 0x4477AA, "blue";
	is @p[1], 0x66CCEE, "cyan";
	is @p[2], 0x228833, "green";
	is @p[3], 0xCCBB44, "yellow";
	is @p[4], 0xEE6677, "red";
	is @p[5], 0xAA3377, "purple";
	is @p[6], 0xBBBBBB, "gray";
};

subtest "tableau-10 palette has 10 documented colors" => {
	plan 1;
	my @p = Selkie::Plot::Palette.series('tableau-10');
	is @p.elems, 10, "ten colors";
};

subtest "unknown series palette dies with helpful message" => {
	plan 2;
	dies-ok { Selkie::Plot::Palette.series('not-a-palette') },
		"unknown name throws";
	throws-like { Selkie::Plot::Palette.series('not-a-palette') },
		Exception,
		message => /'okabe-ito'/,
		"error mentions known palettes";
};

subtest "default ramp is viridis" => {
	plan 2;
	my @default = Selkie::Plot::Palette.ramp;
	my @explicit = Selkie::Plot::Palette.ramp('viridis');
	is @default.elems, 5, "five stops";
	is @default, @explicit, "default name resolves to viridis";
};

subtest "viridis stops match documented values" => {
	plan 5;
	my @stops = Selkie::Plot::Palette.ramp('viridis');
	is @stops[0].key, 0.00,    "stop 0 position";
	is @stops[0].value, 0x440154, "stop 0 color";
	is @stops[2].key, 0.50,    "midpoint position";
	is @stops[2].value, 0x21908C, "midpoint color";
	is @stops[4].value, 0xFDE725, "final color (yellow)";
};

subtest "all five named ramps are present" => {
	plan 5;
	for <viridis magma plasma coolwarm grayscale> -> $name {
		my @stops = Selkie::Plot::Palette.ramp($name);
		is @stops.elems, 5, "$name has 5 stops";
	}
};

subtest "sample at exact stop returns that stop's color" => {
	plan 5;
	is Selkie::Plot::Palette.sample('viridis', 0.0),  0x440154, "0.0 → stop 0";
	is Selkie::Plot::Palette.sample('viridis', 0.25), 0x3B528B, "0.25 → stop 1";
	is Selkie::Plot::Palette.sample('viridis', 0.5),  0x21908C, "0.5 → stop 2";
	is Selkie::Plot::Palette.sample('viridis', 0.75), 0x5DC863, "0.75 → stop 3";
	is Selkie::Plot::Palette.sample('viridis', 1.0),  0xFDE725, "1.0 → stop 4";
};

subtest "sample interpolates between adjacent stops" => {
	plan 3;
	# Between stop 0 (0xFF0000-equivalent... wait, viridis 0=0x440154)
	# At t=0.125, halfway between viridis stop 0 (0x440154) and stop 1 (0x3B528B):
	#   r: (0x44+0x3B)/2 = 0x3F (0x44=68, 0x3B=59, avg=63.5→64=0x40)
	#   g: (0x01+0x52)/2 = 0x29 (1+82=83/2=41.5→42=0x2A)
	#   b: (0x54+0x8B)/2 = 0x6F (84+139=223/2=111.5→112=0x70)
	# Expected: 0x402A70
	my $mid = Selkie::Plot::Palette.sample('viridis', 0.125);
	is $mid, 0x402A70, "halfway between stop 0 and stop 1";

	# Between stop 1 (0x3B528B) and stop 2 (0x21908C) at t=0.375:
	#   r: avg(0x3B, 0x21) = avg(59, 33) = 46 = 0x2E
	#   g: avg(0x52, 0x90) = avg(82, 144) = 113 = 0x71
	#   b: avg(0x8B, 0x8C) = avg(139, 140) = 139.5→140 = 0x8C
	is Selkie::Plot::Palette.sample('viridis', 0.375), 0x2E718C,
		"halfway between stop 1 and stop 2";

	# Grayscale midpoint between 0 and 0.25:
	#   stop 0 = 0x101010, stop 1 = 0x404040
	#   t=0.125 means halfway: avg(0x10, 0x40) = avg(16, 64) = 40 = 0x28
	#   0x282828
	is Selkie::Plot::Palette.sample('grayscale', 0.125), 0x282828,
		"grayscale interpolation is uniform per channel";
};

subtest "sample clamps out-of-range t to nearest stop" => {
	plan 4;
	is Selkie::Plot::Palette.sample('viridis', -0.5),  0x440154, "negative clamps to first stop";
	is Selkie::Plot::Palette.sample('viridis', -1e9),  0x440154, "extreme negative clamps";
	is Selkie::Plot::Palette.sample('viridis',  1.5),  0xFDE725, "above 1 clamps to last stop";
	is Selkie::Plot::Palette.sample('viridis',  1e9),  0xFDE725, "extreme above clamps";
};