+
Chart Color Palette
+
+ Observable categorical color palette - designed to work well on both light
+ and dark backgrounds with good contrast and accessibility.
+
+
+ Source:{' '}
+
+ Observable D3 Color Schemes
+
+
+
+
Data Series Colors
+
+ {CHART_COLORS.map(({ name, hex, label }) => (
+
+ ))}
+
+
+
Semantic Chart Colors
+
+ {SEMANTIC_CHART_COLORS.map(({ name, hex, label }) => (
+
+ ))}
+
+
+);
+
+export const BarChartPreview = () => {
+ const data = [
+ { label: 'Jan', values: [65, 45, 30] },
+ { label: 'Feb', values: [80, 55, 40] },
+ { label: 'Mar', values: [70, 60, 50] },
+ { label: 'Apr', values: [90, 70, 45] },
+ { label: 'May', values: [85, 65, 55] },
+ ];
+
+ return (
+
+
Line Chart Preview
+
+
+
+
+ {[
+ { name: 'Requests', colorIndex: 0 },
+ { name: 'Warnings', colorIndex: 1 },
+ { name: 'Latency', colorIndex: 3 },
+ ].map(({ name, colorIndex }) => (
+
+ ))}
+
+
+ );
+};
+
+export const SemanticColorsPreview = () => (
+
+
Semantic Chart Colors
+
+ Use these colors to indicate status in charts (e.g., success rate, error
+ count).
+
+
+ {SEMANTIC_CHART_COLORS.map(({ hex, label }, i) => (
+
+
+ {i === 0 ? '98%' : i === 1 ? '15' : '3'}
+
+
{label}
+
+ {hex}
+
+
+ ))}
+
+
+);
+
+export const AccessibilityCheck = () => (
+
+
Color Accessibility
+
+ The Observable palette is designed to be distinguishable for users with
+ color vision deficiencies. Toggle between dark and light mode to verify
+ contrast.
+
+
+ {CHART_COLORS.map(({ hex, label }, i) => (
+
+ {i + 1}
+ {label}
+
+ ))}
+
+
+);
diff --git a/packages/app/src/theme/semanticColorsGrouped.ts b/packages/app/src/theme/semanticColorsGrouped.ts
index 2439e04156..c7360b6e89 100644
--- a/packages/app/src/theme/semanticColorsGrouped.ts
+++ b/packages/app/src/theme/semanticColorsGrouped.ts
@@ -44,4 +44,21 @@ export const semanticColorsGrouped = {
'color-json-array',
'color-json-punctuation',
],
+ charts: [
+ 'color-chart-1',
+ 'color-chart-2',
+ 'color-chart-3',
+ 'color-chart-4',
+ 'color-chart-5',
+ 'color-chart-6',
+ 'color-chart-7',
+ 'color-chart-8',
+ 'color-chart-9',
+ 'color-chart-10',
+ 'color-chart-success',
+ 'color-chart-warning',
+ 'color-chart-error',
+ 'color-chart-error-highlight',
+ 'color-chart-warning-highlight',
+ ],
};
diff --git a/packages/app/src/utils.ts b/packages/app/src/utils.ts
index 6144c56898..be98a679d2 100644
--- a/packages/app/src/utils.ts
+++ b/packages/app/src/utils.ts
@@ -405,19 +405,38 @@ export const getLogLevelClass = (lvl: string | undefined) => {
: undefined;
};
-// Accessible chart colors
+// Chart color palette - single source of truth
+// Colors from Observable categorical palette, with custom brand green
+// https://observablehq.com/@d3/color-schemes
+export const CHART_PALETTE = {
+ green: '#00c28a', // Brand green (Mantine green.5) - used as primary chart color
+ blue: '#4269d0',
+ orange: '#efb118',
+ red: '#ff725c',
+ cyan: '#6cc5b0',
+ pink: '#ff8ab7',
+ purple: '#a463f2',
+ lightBlue: '#97bbf5',
+ brown: '#9c6b4e',
+ gray: '#9498a0',
+ // Highlighted variants (lighter shades for hover/selection states)
+ redHighlight: '#ffa090',
+ orangeHighlight: '#f5c94d',
+} as const;
+
+// Ordered array for chart series - green first for brand consistency
+// Maps to CSS variables: COLORS[0] -> --color-chart-1, COLORS[1] -> --color-chart-2, etc.
export const COLORS = [
- '#20c997', // Green
- // '#F81358', // Red
- '#8250dc', // Light Purple
- '#cdad7a', // Tan
- '#0d6efd', // Blue
- '#fd7e14', // Orange
- '#0dcaf0', // Turqoise
- '#828c95', // Grey
- '#ff9382', // Coral
- '#39b5ab', // Olive-tealish?
- '#ffa600', // Yellow
+ CHART_PALETTE.green, // 1 - Brand green (primary)
+ CHART_PALETTE.blue, // 2
+ CHART_PALETTE.orange, // 3
+ CHART_PALETTE.red, // 4
+ CHART_PALETTE.cyan, // 5
+ CHART_PALETTE.pink, // 6
+ CHART_PALETTE.purple, // 7
+ CHART_PALETTE.lightBlue, // 8
+ CHART_PALETTE.brown, // 9
+ CHART_PALETTE.gray, // 10
];
export function hashCode(str: string) {
@@ -433,6 +452,15 @@ export function hashCode(str: string) {
return hash;
}
+// Semantic colors for log levels (derived from palette)
+export const CHART_COLOR_SUCCESS = CHART_PALETTE.green;
+export const CHART_COLOR_WARNING = CHART_PALETTE.orange;
+export const CHART_COLOR_ERROR = CHART_PALETTE.red;
+
+// Highlighted variants (derived from palette)
+export const CHART_COLOR_ERROR_HIGHLIGHT = CHART_PALETTE.redHighlight;
+export const CHART_COLOR_WARNING_HIGHLIGHT = CHART_PALETTE.orangeHighlight;
+
// Try to match log levels to colors
export const semanticKeyedColor = (
key: string | number | undefined,
@@ -441,10 +469,10 @@ export const semanticKeyedColor = (
const logLevel = getLogLevelClass(`${key}`);
if (logLevel != null) {
return logLevel === 'error'
- ? '#d63384' // magenta
+ ? CHART_COLOR_ERROR
: logLevel === 'warn'
- ? '#ffc107' // yellow
- : '#20c997'; // green;
+ ? CHART_COLOR_WARNING
+ : CHART_COLOR_SUCCESS;
}
return COLORS[index % COLORS.length];
@@ -453,10 +481,10 @@ export const semanticKeyedColor = (
export const logLevelColor = (key: string | number | undefined) => {
const logLevel = getLogLevelClass(`${key}`);
return logLevel === 'error'
- ? '#F81358' // red
+ ? CHART_COLOR_ERROR
: logLevel === 'warn'
- ? '#ffc107' // yellow
- : '#20c997'; // green;
+ ? CHART_COLOR_WARNING
+ : CHART_COLOR_SUCCESS;
};
// order of colors for sorting. green on bottom, then yellow, then red
@@ -471,10 +499,10 @@ const getLevelColor = (logLevel?: string) => {
return;
}
return logLevel === 'error'
- ? '#d63384' // magenta
+ ? CHART_COLOR_ERROR
: logLevel === 'warn'
- ? '#ffc107' // yellow
- : '#20c997'; // green;
+ ? CHART_COLOR_WARNING
+ : CHART_COLOR_SUCCESS;
};
export const getColorProps = (index: number, level: string): string => {
diff --git a/packages/app/styles/_semantic-colors.scss b/packages/app/styles/_semantic-colors.scss
index 74903b489d..603fff5bab 100644
--- a/packages/app/styles/_semantic-colors.scss
+++ b/packages/app/styles/_semantic-colors.scss
@@ -65,6 +65,29 @@
--color-json-array: var(--mantine-color-green-3);
--color-json-punctuation: var(--mantine-color-dark-4);
+ /* Chart Colors - Brand green first, then Observable palette
+ Note: 1-indexed CSS vars map to 0-indexed COLORS array in utils.ts
+ e.g., --color-chart-1 corresponds to COLORS[0] */
+ --color-chart-1: #00c28a; /* Green - Brand (primary) */
+ --color-chart-2: #4269d0; /* Blue */
+ --color-chart-3: #efb118; /* Orange */
+ --color-chart-4: #ff725c; /* Red */
+ --color-chart-5: #6cc5b0; /* Cyan */
+ --color-chart-6: #ff8ab7; /* Pink */
+ --color-chart-7: #a463f2; /* Purple */
+ --color-chart-8: #97bbf5; /* Light Blue */
+ --color-chart-9: #9c6b4e; /* Brown */
+ --color-chart-10: #9498a0; /* Gray */
+
+ /* Chart Semantic Colors */
+ --color-chart-success: #00c28a; /* Green - Brand */
+ --color-chart-warning: #efb118; /* Orange */
+ --color-chart-error: #ff725c; /* Red */
+
+ /* Chart Semantic Colors - Highlighted (for hover/selection states) */
+ --color-chart-error-highlight: #ffa090;
+ --color-chart-warning-highlight: #f5c94d;
+
/* Mantine Overrides */
--mantine-color-body: var(--color-bg-body) !important;
}
@@ -139,4 +162,25 @@
--color-json-object: var(--mantine-color-green-7);
--color-json-array: var(--mantine-color-green-7);
--color-json-punctuation: var(--mantine-color-dark-5);
+
+ /* Chart Colors - Brand green first, then Observable palette (same for light mode) */
+ --color-chart-1: #00c28a; /* Green - Brand (primary) */
+ --color-chart-2: #4269d0; /* Blue */
+ --color-chart-3: #efb118; /* Orange */
+ --color-chart-4: #ff725c; /* Red */
+ --color-chart-5: #6cc5b0; /* Cyan */
+ --color-chart-6: #ff8ab7; /* Pink */
+ --color-chart-7: #a463f2; /* Purple */
+ --color-chart-8: #97bbf5; /* Light Blue */
+ --color-chart-9: #9c6b4e; /* Brown */
+ --color-chart-10: #9498a0; /* Gray */
+
+ /* Chart Semantic Colors */
+ --color-chart-success: #00c28a; /* Green - Brand */
+ --color-chart-warning: #efb118; /* Orange */
+ --color-chart-error: #ff725c; /* Red */
+
+ /* Chart Semantic Colors - Highlighted (for hover/selection states) */
+ --color-chart-error-highlight: #ffa090;
+ --color-chart-warning-highlight: #f5c94d;
}