feat: add color blind colors to reviews graph

Signed-off-by: David Brenn <davidbrenn@t-online.de>
This commit is contained in:
David Brenn 2025-08-12 15:30:29 +02:00
parent 8d6d8dc6c8
commit c530a024e2
3 changed files with 63 additions and 14 deletions

View file

@ -22,7 +22,7 @@ import {
sum, sum,
} from "d3"; } from "d3";
import type { GraphBounds } from "./graph-helpers"; import { colorBlindColors, type GraphBounds } from "./graph-helpers";
type Count = [string, number, boolean, string]; type Count = [string, number, boolean, string];
export interface GraphData { export interface GraphData {
@ -36,13 +36,13 @@ let barColours;
if((window as any).colorBlindMode) if((window as any).colorBlindMode)
{ {
barColours = [ barColours = [
"#88CCEE", /* new */ colorBlindColors.new, /* new */
"#44AA99", /* learn */ colorBlindColors.learn, /* learn */
"#117733", /* relearn */ colorBlindColors.relearn, /* relearn */
"#CC6677", /* young */ colorBlindColors.young, /* young */
"#882255", /* mature */ colorBlindColors.mature, /* mature */
"#DDCC77", /* suspended */ colorBlindColors.suspended, /* suspended */
"#332288", /* buried */ colorBlindColors.buried, /* buried */
]; ];
} }
else else

View file

@ -105,3 +105,17 @@ export function numericMap<T>(obj: { [k: string]: T }): Map<number, T> {
export function getNumericMapBinValue(d: Bin<Map<number, number>, number>): number { export function getNumericMapBinValue(d: Bin<Map<number, number>, number>): number {
return sum(d, (d) => d[1]); return sum(d, (d) => d[1]);
} }
/**
* Colorblind-friendly colors from https://davidmathlogic.com/colorblind/
*/
export const colorBlindColors = {
new: "#88CCEE",
learn: "#44AA99",
relearn: "#117733",
young: "#CC6677",
mature: "#882255",
suspended: "#DDCC77",
buried: "#332288",
filtered: "#AA4499"
};

View file

@ -22,16 +22,20 @@ import {
interpolateGreens, interpolateGreens,
interpolatePurples, interpolatePurples,
interpolateReds, interpolateReds,
interpolateRgb,
max, max,
min, min,
pointer, pointer,
scaleLinear, scaleLinear,
scaleOrdinal,
scaleSequential, scaleSequential,
select, select,
sum, sum,
color,
hsl
} from "d3"; } from "d3";
import type { GraphBounds, TableDatum } from "./graph-helpers"; import { colorBlindColors, type GraphBounds, type TableDatum } from "./graph-helpers";
import { GraphRange, numericMap, setDataAvailable } from "./graph-helpers"; import { GraphRange, numericMap, setDataAvailable } from "./graph-helpers";
import { hideTooltip, showTooltip } from "./tooltip-utils.svelte"; import { hideTooltip, showTooltip } from "./tooltip-utils.svelte";
@ -188,21 +192,52 @@ export function renderReviews(
x.domain() as any, x.domain() as any,
); );
const colorBlindMode = (window as any).colorBlindMode;
function makeColorBlindGradient(baseHex: string, satAdjust = 0.02, lightAdjust = 0.02) {
const base = color(baseHex);
if (!base) throw new Error(`Invalid color: ${baseHex}`);
const lighter = hsl(base);
lighter.s = Math.min(1, lighter.s + satAdjust);
lighter.l = Math.min(1, lighter.l + lightAdjust);
const darker = hsl(base);
darker.s = Math.max(0, darker.s - satAdjust);
darker.l = Math.max(0, darker.l - lightAdjust);
return scaleSequential(interpolateRgb(darker.toString(), lighter.toString()));
}
const colorBlindScales = {
mature: makeColorBlindGradient(colorBlindColors.mature),
learn: makeColorBlindGradient(colorBlindColors.learn),
relearn: makeColorBlindGradient(colorBlindColors.relearn),
young: makeColorBlindGradient(colorBlindColors.young),
suspended: makeColorBlindGradient(colorBlindColors.suspended),
buried: makeColorBlindGradient(colorBlindColors.buried),
filtered: makeColorBlindGradient(colorBlindColors.filtered)
};
Object.values(colorBlindScales).forEach(scale => scale.domain(x.domain() as any));
function binColor(idx: BinIndex): ScaleSequential<string> { function binColor(idx: BinIndex): ScaleSequential<string> {
switch (idx) { switch (idx) {
case BinIndex.Mature: case BinIndex.Mature:
return darkerGreens; return colorBlindMode ? colorBlindScales.mature : darkerGreens;
case BinIndex.Young: case BinIndex.Young:
return lighterGreens; return colorBlindMode ? colorBlindScales.young : lighterGreens;
case BinIndex.Learn: case BinIndex.Learn:
return blues; return colorBlindMode ? colorBlindScales.learn : blues;
case BinIndex.Relearn: case BinIndex.Relearn:
return reds; return colorBlindMode ? colorBlindScales.relearn : reds;
case BinIndex.Filtered: case BinIndex.Filtered:
return purples; return colorBlindMode ? colorBlindScales.filtered : purples;
} }
} }
function valueLabel(n: number): string { function valueLabel(n: number): string {
if (showTime) { if (showTime) {
return timeSpan(n / 1000, false, true, TimespanUnit.Hours); return timeSpan(n / 1000, false, true, TimespanUnit.Hours);