Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions component/src/charts/__tests__/circle-packing-chart.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,20 @@ describe("CirclePackingChart", () => {
expect(text?.style?.fill).toBe("#000000");
});

it("fills depth circles from the citrine palette (no stock ECharts colors)", () => {
render(<CirclePackingChart data={hierarchicalData} />);
const { renderItem } = mockSetOption.mock.calls[0][0].series[0];
// depth 1, no per-node color -> falls back to the citrine depth palette.
const vals = [120, 120, 40, 1, 50, "", "Frontend"];
const api = { value: (d: number) => vals[d], style: () => ({}) };
const group = renderItem(undefined, api) as {
children: { type: string; shape?: object; style?: { fill?: string } }[];
};
const circle = group.children.find((c) => c.type === "circle");
expect(circle?.style?.fill).toContain("hsl");
expect(circle?.style?.fill).not.toBe("#5470c6");
});

it("shows loading state", () => {
render(<CirclePackingChart data={hierarchicalData} loading />);
expect(screen.getByTestId("base-chart")).toBeInTheDocument();
Expand Down
8 changes: 8 additions & 0 deletions component/src/charts/__tests__/gauge-chart.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ describe("GaugeChart", () => {
expect(optionsCall.series[0].type).toBe("gauge");
});

it("defaults the progress arc to the brand citrine accent, not stock blue", () => {
render(<GaugeChart data={sampleData} />);
const color = mockSetOption.mock.calls[0][0].series[0].progress.itemStyle
.color as string;
expect(color).not.toBe("#5470c6");
expect(color.toLowerCase()).toContain("hsl(38"); // citrine amber
});

it("passes min and max to the series", () => {
render(<GaugeChart data={sampleData} min={10} max={200} />);
const optionsCall = mockSetOption.mock.calls[0][0];
Expand Down
28 changes: 11 additions & 17 deletions component/src/charts/circle-packing-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { TitleComponent, TooltipComponent } from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
import type { EChartsOption } from "echarts";
import { pack, hierarchy, type HierarchyCircularNode } from "d3-hierarchy";
import { BaseChart } from "./base-chart";
import { BaseChart, useDarkMode } from "./base-chart";
import type { BaseChartProps } from "./types";
import { useContainerSize } from "@/hooks/useContainerSize";
import {
buildEmptyDataOption,
resolveItemColor,
contrastTextColor,
} from "./chart-utils";
import { CITRINE_LIGHT, CITRINE_DARK } from "./theme";
import type { StylingRule } from "./styling-rule";

echarts.use([CustomChart, TitleComponent, TooltipComponent, CanvasRenderer]);
Expand Down Expand Up @@ -49,18 +50,8 @@ interface PackedNode {
color?: string;
}

/** Default color palette by depth level. */
const DEPTH_COLORS = [
"rgba(100, 140, 200, 0.15)", // root background
"#5470c6",
"#91cc75",
"#fac858",
"#ee6666",
"#73c0de",
"#3ba272",
"#fc8452",
"#9a60b4",
];
/** Faint neutral fill for the (label-less) root circle. */
const ROOT_FILL = "rgba(140, 140, 140, 0.08)";

function CirclePackingChart({
data,
Expand All @@ -72,8 +63,11 @@ function CirclePackingChart({
}: CirclePackingChartProps) {
const { width, height, containerRef } = useContainerSize();
const measured = width > 0;
const dark = useDarkMode();

const options = useMemo((): EChartsOption | undefined => {
// Brand citrine palette, indexed by depth (was a stock ECharts palette).
const depthColors = dark ? CITRINE_DARK : CITRINE_LIGHT;
if (!measured) return undefined;
if (!data.length) return buildEmptyDataOption();

Expand Down Expand Up @@ -134,10 +128,9 @@ function CirclePackingChart({
const nodeColor = api.value(5);
const name = String(api.value(6));

// depth 1 → first citrine color, depth 2 → second, … (cycling).
const fillColor =
nodeColor ||
DEPTH_COLORS[depth] ||
DEPTH_COLORS[DEPTH_COLORS.length - 1];
nodeColor || depthColors[(Math.max(1, depth) - 1) % depthColors.length];

const group: { type: string; children: unknown[] } = {
type: "group",
Expand All @@ -146,7 +139,7 @@ function CirclePackingChart({
type: "circle",
shape: { cx, cy, r },
style: {
fill: depth === 0 ? "rgba(100, 140, 200, 0.08)" : fillColor,
fill: depth === 0 ? ROOT_FILL : fillColor,
stroke: depth === 0 ? "none" : "rgba(255, 255, 255, 0.6)",
lineWidth: 1,
opacity: depth === 0 ? 1 : 0.85,
Expand Down Expand Up @@ -229,6 +222,7 @@ function CirclePackingChart({
padding,
stylingRules,
paramValues,
dark,
]);

return (
Expand Down
8 changes: 7 additions & 1 deletion component/src/charts/gauge-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {
buildEmptyDataOption,
resolveItemColor,
parseGaugeThresholdZones,
isDark,
} from "./chart-utils";
import { CITRINE_LIGHT, CITRINE_DARK } from "./theme";
import type { StylingRule } from "./styling-rule";

echarts.use([EGaugeChart, TitleComponent, TooltipComponent, CanvasRenderer]);
Expand Down Expand Up @@ -103,7 +105,11 @@ function GaugeChart({
hasCustomZones && normalizedValue !== undefined
? thresholdZones.find(([stop]) => normalizedValue <= stop)?.[1]
: undefined;
const progressColor = resolvedColor ?? thresholdColor ?? "#5470c6";
// Default to the brand citrine accent (chart-1), not the stock ECharts blue.
const progressColor =
resolvedColor ??
thresholdColor ??
(isDark() ? CITRINE_DARK[0] : CITRINE_LIGHT[0]);

// Track color — light gray that works in both themes
const trackColor = hasCustomZones
Expand Down
37 changes: 10 additions & 27 deletions component/src/charts/graph-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,19 @@ import {
} from "@/components/ui/popover";
import { Label } from "@/components/ui/label";
import { useDarkMode } from "./base-chart";
import { CITRINE_LIGHT, CITRINE_DARK } from "./theme";

export type GraphLayout = "force" | "circular" | "hierarchical";

/** Light-mode palette — moderate saturation, good contrast on white. */
const LABEL_COLOR_PALETTE_LIGHT = [
"#4E79A7",
"#F28E2B",
"#E15759",
"#76B7B2",
"#59A14F",
"#EDC948",
"#B07AA1",
"#FF9DA7",
"#9C755F",
"#BAB0AC",
];

/** Dark-mode palette — same hues, boosted lightness for contrast on dark backgrounds. */
const LABEL_COLOR_PALETTE_DARK = [
"#6B9BD2",
"#F5A854",
"#E8787A",
"#8FD0CA",
"#74C068",
"#F0D86A",
"#C99ABF",
"#FFB5BD",
"#B89278",
"#CFC5BF",
];
/**
* Node-label palettes — the brand citrine ("Citrine") palette, citrine-led and
* colorblind-safe, shared with every other chart. Was a generic Tableau-10
* palette (off-brand "stock chart" look); aligning it to CITRINE makes the
* graph read as part of the same vibrant system. Light/dark variants track
* the theme.
*/
const LABEL_COLOR_PALETTE_LIGHT = CITRINE_LIGHT;
const LABEL_COLOR_PALETTE_DARK = CITRINE_DARK;

/**
* Builds a map of Neo4j label → palette color.
Expand Down