Add dashboard country charts (Stages 1–3)
- Install Recharts v3 as the project charting library - Add chartTheme utility with Fluent UI v9 token resolution helper and a 5-colour categorical palette (resolves CSS vars at runtime) - Add TopCountriesPieChart: top-4 + Other slice, Tooltip, Legend - Add TopCountriesBarChart: horizontal top-20 bar chart - Add useDashboardCountryData hook (wraps /api/dashboard/bans/by-country) - Integrate both charts into DashboardPage in a responsive chartsRow (side-by-side on wide screens, stacked on narrow) - All tsc --noEmit and eslint checks pass with zero warnings
This commit is contained in:
72
frontend/src/utils/chartTheme.ts
Normal file
72
frontend/src/utils/chartTheme.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Chart theme utility — maps Fluent UI v9 design tokens to Recharts-compatible
|
||||
* CSS colour strings.
|
||||
*
|
||||
* Recharts renders SVG elements and sets colour values as SVG attributes, not
|
||||
* CSS properties. SVG attributes do not support CSS custom-property
|
||||
* references (`var(…)`), so token values must be resolved to their actual
|
||||
* colour strings at render time via `getComputedStyle`.
|
||||
*
|
||||
* Call `resolveFluentToken` inside a component (not at module level) so that
|
||||
* the resolved value reflects the theme that is active when the component
|
||||
* renders.
|
||||
*/
|
||||
|
||||
import { tokens } from "@fluentui/react-components";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Runtime resolver
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Resolves a Fluent UI v9 token string (e.g. `tokens.colorNeutralForeground2`)
|
||||
* to the literal CSS colour value defined in the active theme.
|
||||
*
|
||||
* @param tokenValue - A Fluent v9 token string such as
|
||||
* `"var(--colorNeutralForeground2)"`.
|
||||
* @returns The resolved colour string (e.g. `"#605e5c"`), or the original
|
||||
* token value if resolution fails.
|
||||
*/
|
||||
export function resolveFluentToken(tokenValue: string): string {
|
||||
const match = /var\((--[^,)]+)/.exec(tokenValue);
|
||||
if (match == null || match[1] == null) return tokenValue;
|
||||
const resolved = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue(match[1])
|
||||
.trim();
|
||||
return resolved !== "" ? resolved : tokenValue;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Categorical palette
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Five distinct categorical colours for pie/bar slices and line series,
|
||||
* expressed as Fluent UI v9 CSS custom-property references.
|
||||
*
|
||||
* Resolve at render time with `resolveFluentToken` before passing to
|
||||
* Recharts components.
|
||||
*/
|
||||
export const CHART_PALETTE: readonly string[] = [
|
||||
tokens.colorPaletteBlueBorderActive,
|
||||
tokens.colorPaletteRedBorderActive,
|
||||
tokens.colorPaletteGreenBorderActive,
|
||||
tokens.colorPaletteGoldBorderActive,
|
||||
tokens.colorPalettePurpleBorderActive,
|
||||
] as const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Structural colours
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Fluent token for axis labels and tick text — resolves per active theme. */
|
||||
export const CHART_AXIS_TEXT_TOKEN: string = tokens.colorNeutralForeground2;
|
||||
|
||||
/** Fluent token for CartesianGrid lines — resolves per active theme. */
|
||||
export const CHART_GRID_LINE_TOKEN: string = tokens.colorNeutralStroke2;
|
||||
|
||||
/** Fluent token for tooltip background — resolves per active theme. */
|
||||
export const CHART_TOOLTIP_BG_TOKEN: string = tokens.colorNeutralBackground1;
|
||||
|
||||
/** Fluent token for tooltip text — resolves per active theme. */
|
||||
export const CHART_TOOLTIP_TEXT_TOKEN: string = tokens.colorNeutralForeground1;
|
||||
Reference in New Issue
Block a user