Gice the loss graph more diverse colors
This commit is contained in:
@@ -98,30 +98,19 @@ function emaWithNulls(ys: (number | null)[], alpha: number): (number | null)[] {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hashToIndex(str: string, mod: number) {
|
// Hue order is deliberate: adjacent slots are maximally separated for
|
||||||
let h = 2166136261;
|
// colorblind viewers, validated against the gray-950 chart surface.
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
h ^= str.charCodeAt(i);
|
|
||||||
h = Math.imul(h, 16777619);
|
|
||||||
}
|
|
||||||
return Math.abs(h) % mod;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PALETTE = [
|
const PALETTE = [
|
||||||
'rgba(96,165,250,1)', // blue-400
|
'rgba(57,135,229,1)', // blue
|
||||||
'rgba(52,211,153,1)', // emerald-400
|
'rgba(0,131,0,1)', // green
|
||||||
'rgba(167,139,250,1)', // purple-400
|
'rgba(213,81,129,1)', // magenta
|
||||||
'rgba(251,191,36,1)', // amber-400
|
'rgba(201,133,0,1)', // yellow
|
||||||
'rgba(244,114,182,1)', // pink-400
|
'rgba(25,158,112,1)', // aqua
|
||||||
'rgba(248,113,113,1)', // red-400
|
'rgba(217,89,38,1)', // orange
|
||||||
'rgba(34,211,238,1)', // cyan-400
|
'rgba(144,133,233,1)', // violet
|
||||||
'rgba(129,140,248,1)', // indigo-400
|
'rgba(230,103,103,1)', // red
|
||||||
];
|
];
|
||||||
|
|
||||||
function strokeForKey(key: string) {
|
|
||||||
return PALETTE[hashToIndex(key, PALETTE.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Persisted, per-URL graph settings. Sliders + display toggles + which loss
|
// Persisted, per-URL graph settings. Sliders + display toggles + which loss
|
||||||
// series are visible. Zoom / highlighted window is intentionally NOT persisted.
|
// series are visible. Zoom / highlighted window is intentionally NOT persisted.
|
||||||
interface PersistedSettings {
|
interface PersistedSettings {
|
||||||
@@ -243,6 +232,18 @@ export default function JobLossGraph({ job }: Props) {
|
|||||||
|
|
||||||
const activeKeys = useMemo(() => lossKeys.filter(k => enabled[k] !== false), [lossKeys, enabled]);
|
const activeKeys = useMemo(() => lossKeys.filter(k => enabled[k] !== false), [lossKeys, enabled]);
|
||||||
|
|
||||||
|
// Assign palette slots by position in the (sorted) lossKeys list rather than
|
||||||
|
// by hashing the key name — hashing let different keys collide onto the same
|
||||||
|
// color. Keyed off lossKeys (not activeKeys) so toggling a series off never
|
||||||
|
// repaints the others.
|
||||||
|
const colorByKey = useMemo(() => {
|
||||||
|
const m: Record<string, string> = {};
|
||||||
|
lossKeys.forEach((k, i) => {
|
||||||
|
m[k] = PALETTE[i % PALETTE.length];
|
||||||
|
});
|
||||||
|
return m;
|
||||||
|
}, [lossKeys]);
|
||||||
|
|
||||||
// Build uPlot-aligned data + series configs.
|
// Build uPlot-aligned data + series configs.
|
||||||
const built = useMemo(() => {
|
const built = useMemo(() => {
|
||||||
const stride = Math.max(1, plotStride | 0);
|
const stride = Math.max(1, plotStride | 0);
|
||||||
@@ -305,7 +306,7 @@ export default function JobLossGraph({ job }: Props) {
|
|||||||
const smooth = emaWithNulls(raw, alpha);
|
const smooth = emaWithNulls(raw, alpha);
|
||||||
const fullSmooth = emaWithNulls(raw, fullAlpha);
|
const fullSmooth = emaWithNulls(raw, fullAlpha);
|
||||||
|
|
||||||
const color = strokeForKey(key);
|
const color = colorByKey[key] ?? PALETTE[0];
|
||||||
const colorDull = dulledColor(color);
|
const colorDull = dulledColor(color);
|
||||||
|
|
||||||
const colArrays: (number | null)[][] = [];
|
const colArrays: (number | null)[][] = [];
|
||||||
@@ -395,7 +396,7 @@ export default function JobLossGraph({ job }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { data: data as uPlot.AlignedData, seriesConfigs, scales, axes, yClip, sparseFlags };
|
return { data: data as uPlot.AlignedData, seriesConfigs, scales, axes, yClip, sparseFlags };
|
||||||
}, [series, activeKeys, smoothing, plotStride, windowSize, useLogScale, showTrend, clipOutliers]);
|
}, [series, activeKeys, colorByKey, smoothing, plotStride, windowSize, useLogScale, showTrend, clipOutliers]);
|
||||||
|
|
||||||
// Layout wrapper we measure for sizing — uPlot collapses its own mount node
|
// Layout wrapper we measure for sizing — uPlot collapses its own mount node
|
||||||
// to width:min-content, so we can't read sizes off it.
|
// to width:min-content, so we can't read sizes off it.
|
||||||
@@ -614,7 +615,7 @@ export default function JobLossGraph({ job }: Props) {
|
|||||||
aria-pressed={enabled[k] !== false}
|
aria-pressed={enabled[k] !== false}
|
||||||
title={k}
|
title={k}
|
||||||
>
|
>
|
||||||
<span className="inline-block h-2 w-2 rounded-full mr-2" style={{ background: strokeForKey(k) }} />
|
<span className="inline-block h-2 w-2 rounded-full mr-2" style={{ background: colorByKey[k] }} />
|
||||||
{k}
|
{k}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user