Public Access
feat(stats): status donut, meters and fixed chart colors
Pie and donut charts use one fixed color-blind safe palette in every skin and accent. The overview and the profile status tile show a status donut with the total in the middle. Habits and achievements get meter bars, top games get share bars, the pile of shame bar uses the palette.
This commit is contained in:
1 parent
dabb0c941c
commit
8aa3181c37
20 files changed
+468
-110
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import {apiFetch} from './api.js';
|
||||
import {GAME_STATUSES} from './limits.js';
|
||||
import {dayNumber} from './stats.js';
|
||||
|
||||
// The chart forms of each stat group, the first is the default. Auth checks the same list.
|
||||
@@ -36,6 +37,27 @@ export const topSlices = (items, valueOf, keep = 5) => {
|
||||
return [...slices.slice(0, keep), {name: null, value: rest, item: null, other: true}];
|
||||
};
|
||||
|
||||
export const SLICE_COLORS = ['var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', 'var(--chart-5)', 'var(--chart-6)'];
|
||||
|
||||
/**
|
||||
* The fill of each slice. The colors come in a fixed order and repeat after the last one; only slices with games
|
||||
* take one. A slice without games gets null, the "other" slice gray.
|
||||
*
|
||||
* @returns `(string|null)[]`, one per slice
|
||||
*/
|
||||
export const sliceColors = (slices) => {
|
||||
let next = 0;
|
||||
return slices.map((slice) => {
|
||||
if (slice.value <= 0) return null;
|
||||
if (slice.other) return 'var(--muted-foreground)';
|
||||
return SLICE_COLORS[next++ % SLICE_COLORS.length];
|
||||
});
|
||||
};
|
||||
|
||||
/** One slice per game status in the fixed status order; a status without games stays in, with value 0. */
|
||||
export const statusSlices = (byStatus) =>
|
||||
GAME_STATUSES.map((s) => ({name: s, value: byStatus?.[s] ?? 0, item: null, other: false}));
|
||||
|
||||
const round = (n) => Math.round(n * 1000) / 1000;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {describe, expect, it} from 'vitest';
|
||||
import {chartOf, linePoints, slicePaths, topSlices, trendBars, yearSeries} from './statCharts.js';
|
||||
import {chartOf, linePoints, sliceColors, slicePaths, statusSlices, topSlices, trendBars, yearSeries} from './statCharts.js';
|
||||
import {GAME_STATUSES} from './limits.js';
|
||||
|
||||
describe('chartOf', () => {
|
||||
it('gives the stored form of the group', () => {
|
||||
@@ -92,3 +93,38 @@ describe('trendBars', () => {
|
||||
expect(trendBars([], 100, 40)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sliceColors', () => {
|
||||
const slice = (value, other = false) => ({name: other ? null : 'x', value, item: null, other});
|
||||
|
||||
it('gives the colors in a fixed order', () => {
|
||||
expect(sliceColors([slice(3), slice(2), slice(1)])).toEqual(['var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)']);
|
||||
});
|
||||
|
||||
it('gives a slice without games no color and no slot', () => {
|
||||
expect(sliceColors([slice(3), slice(0), slice(1)])).toEqual(['var(--chart-1)', null, 'var(--chart-2)']);
|
||||
});
|
||||
|
||||
it('keeps the other slice gray and out of the slots', () => {
|
||||
expect(sliceColors([slice(3), slice(2, true), slice(1)])).toEqual(['var(--chart-1)', 'var(--muted-foreground)', 'var(--chart-2)']);
|
||||
});
|
||||
|
||||
it('starts again after the sixth color', () => {
|
||||
const colors = sliceColors(Array.from({length: 7}, () => slice(1)));
|
||||
expect(colors[6]).toBe('var(--chart-1)');
|
||||
expect(colors[5]).toBe('var(--chart-6)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('statusSlices', () => {
|
||||
it('has one slice per status in the fixed order', () => {
|
||||
const slices = statusSlices({PLAYING: 2, BACKLOG: 5});
|
||||
expect(slices.map((s) => s.name)).toEqual(GAME_STATUSES);
|
||||
expect(slices.map((s) => s.value)).toEqual(GAME_STATUSES.map((s) => ({BACKLOG: 5, PLAYING: 2})[s] ?? 0));
|
||||
expect(slices.every((s) => !s.other && s.item === null)).toBe(true);
|
||||
});
|
||||
|
||||
it('gives five empty slices without data', () => {
|
||||
expect(statusSlices(null).map((s) => s.value)).toEqual([0, 0, 0, 0, 0]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user