Skip to content
Merged
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
54 changes: 8 additions & 46 deletions src/lib/components/visualisations/D3BubbleChart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ Uses D3.js circle packing for a balanced, overlap-free layout
<script lang="ts">
import { onMount } from 'svelte';
import type * as d3Type from 'd3';
import { getTheme } from '$lib/stores/themeStore.svelte';
import { innerWidth as windowInnerWidth } from 'svelte/reactivity/window';
import {
getResolvedChartColors,
resolveColors,
getCSSPx,
prefersReducedMotion
} from '$lib/utils/chartColorUtils';

// D3 library loaded dynamically to reduce initial bundle size
let d3: typeof d3Type | null = $state(null);
Expand Down Expand Up @@ -51,53 +56,10 @@ Uses D3.js circle packing for a balanced, overlap-free layout
// Reactive values
const isMobile = $derived((windowInnerWidth.current ?? 1024) < 768);

// Utility functions for CSS variable resolution
// Fallbacks match design system v2.0 values from variables.css
function getCSSVariableValue(variableName: string): string {
if (typeof window === 'undefined') return '#9a4419';
const computedStyle = getComputedStyle(document.documentElement);
const value = computedStyle.getPropertyValue(variableName).trim();
return value || '#9a4419';
}

function getCSSPx(variableName: string, fallbackPx: number): number {
if (typeof window === 'undefined') return fallbackPx;
const computedStyle = getComputedStyle(document.documentElement);
const value = computedStyle.getPropertyValue(variableName).trim();
const match = value.match(/^([\d.]+)px$/);
return match ? Number(match[1]) : fallbackPx;
}

function prefersReducedMotion(): boolean {
if (typeof window === 'undefined') return false;
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}

function resolveColor(color: string): string {
if (color.startsWith('var(')) {
const varName = color.slice(4, -1).trim();
return getCSSVariableValue(varName);
}
if (color.startsWith('rgba(var(')) {
const rgbaMatch = color.match(/rgba\(var\(([^)]+)\),\s*([^)]+)\)/);
if (rgbaMatch) {
const rgbVarName = rgbaMatch[1];
const opacity = rgbaMatch[2];
const rgbValue = getCSSVariableValue(rgbVarName);
return `rgba(${rgbValue}, ${opacity})`;
}
}
return color;
}

// Reactive color resolution
const resolvedColors = $derived({
text: getCSSVariableValue('--color-text'),
border: getCSSVariableValue('--color-border'),
surface: getCSSVariableValue('--color-surface'),
surfaceRgb: getCSSVariableValue('--color-surface-rgb'),
chartColors: colors.map((color) => resolveColor(color)),
currentTheme: getTheme()
...getResolvedChartColors(),
chartColors: resolveColors(colors)
});

// Type for simulation nodes
Expand Down
85 changes: 15 additions & 70 deletions src/lib/components/visualisations/EChartsBarChart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ ECharts Bar Chart - A much simpler alternative to the custom D3 implementation
-->
<script lang="ts">
import type * as echarts from 'echarts';
import { getTheme } from '$lib/stores/themeStore.svelte';
import { innerWidth } from 'svelte/reactivity/window';
import {
getResolvedChartColors,
resolveColor,
getEChartsTooltipStyle,
getEChartsAxisLineStyle,
getEChartsSplitLineStyle
} from '$lib/utils/chartColorUtils';

// Props - keeping the same interface as your D3 component for easy replacement
type DataItem = $$Generic;
Expand Down Expand Up @@ -33,38 +39,10 @@ ECharts Bar Chart - A much simpler alternative to the custom D3 implementation
// Use Svelte's reactive window width
const isMobile = $derived((innerWidth.current ?? 1024) < 768);

// Utility functions for CSS variable resolution
function getCSSVariableValue(variableName: string): string {
if (typeof window === 'undefined') return '';
const computedStyle = getComputedStyle(document.documentElement);
const value = computedStyle.getPropertyValue(variableName).trim();
return value;
}

function resolveColor(color: string): string {
if (color.startsWith('var(')) {
// Extract variable name from var(--variable-name)
const varName = color.slice(4, -1).trim();
const val = getCSSVariableValue(varName);
return val || color; // Return original if var not found
}
return color; // Return as-is if not a CSS variable
}

// Reactive color resolution - updates when theme changes
// Fallbacks match design system v2.0 values from variables.css (warm earth tones)
const resolvedColors = $derived({
primary: getCSSVariableValue('--color-primary') || '#9a4419',
text: getCSSVariableValue('--color-text') || '#2d2820',
textLight: getCSSVariableValue('--color-text-light') || '#7a7267',
border: getCSSVariableValue('--color-border') || '#e8e4df',
surface: getCSSVariableValue('--color-surface') || '#faf9f7',
surfaceRgb: getCSSVariableValue('--color-surface-rgb') || '250, 249, 247',
primaryDark: getCSSVariableValue('--color-primary-dark') || '#7a3516',
barColor: resolveColor(barColor),
fontFamily: getCSSVariableValue('--font-family-sans') || 'system-ui, sans-serif',
// Include theme to make this reactive to theme changes
currentTheme: getTheme()
...getResolvedChartColors(),
barColor: resolveColor(barColor)
});

// Chart data transformation
Expand All @@ -81,18 +59,7 @@ ECharts Bar Chart - A much simpler alternative to the custom D3 implementation
},
tooltip: {
trigger: 'axis',
backgroundColor: `color-mix(in srgb, ${resolvedColors.surface} 90%, transparent)`,
textStyle: {
color: resolvedColors.text,
fontSize: 12,
fontFamily: resolvedColors.fontFamily
},
borderRadius: 8,
borderColor: resolvedColors.border,
borderWidth: 1,
padding: [10, 14],
extraCssText:
'backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); box-shadow: var(--shadow-lg);'
...getEChartsTooltipStyle(resolvedColors)
},
grid: {
left: isMobile ? 32 : 64,
Expand All @@ -118,16 +85,8 @@ ECharts Bar Chart - A much simpler alternative to the custom D3 implementation
fontFamily: resolvedColors.fontFamily,
interval: isMobile ? 'auto' : 0 // Auto interval on mobile to prevent overlapping
},
axisLine: {
lineStyle: {
color: resolvedColors.border
}
},
axisTick: {
lineStyle: {
color: resolvedColors.border
}
}
axisLine: getEChartsAxisLineStyle(resolvedColors),
axisTick: getEChartsAxisLineStyle(resolvedColors)
},
yAxis: {
type: 'value',
Expand All @@ -144,23 +103,9 @@ ECharts Bar Chart - A much simpler alternative to the custom D3 implementation
fontSize: 12,
fontFamily: resolvedColors.fontFamily
},
axisLine: {
lineStyle: {
color: resolvedColors.border
}
},
axisTick: {
lineStyle: {
color: resolvedColors.border
}
},
splitLine: {
lineStyle: {
color: resolvedColors.border,
opacity: 0.3,
type: 'dashed'
}
}
axisLine: getEChartsAxisLineStyle(resolvedColors),
axisTick: getEChartsAxisLineStyle(resolvedColors),
splitLine: getEChartsSplitLineStyle(resolvedColors)
},
series: [
{
Expand Down
59 changes: 8 additions & 51 deletions src/lib/components/visualisations/EChartsDoughnutChart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ ECharts Doughnut/Pie Chart - A doughnut chart for visualizing categorical data
-->
<script lang="ts">
import type * as echarts from 'echarts';
import { getTheme } from '$lib/stores/themeStore.svelte';
import { innerWidth } from 'svelte/reactivity/window';
import {
getResolvedChartColors,
resolveColors,
getEChartsTooltipStyle
} from '$lib/utils/chartColorUtils';

// Props - keeping interface simple for doughnut chart
type DataItem = $$Generic;
Expand Down Expand Up @@ -50,46 +54,10 @@ ECharts Doughnut/Pie Chart - A doughnut chart for visualizing categorical data
// Use Svelte's reactive window width
const isMobile = $derived((innerWidth.current ?? 1024) < 768);

// Utility functions for CSS variable resolution
function getCSSVariableValue(variableName: string): string {
if (typeof window === 'undefined') return '';
const computedStyle = getComputedStyle(document.documentElement);
const value = computedStyle.getPropertyValue(variableName).trim();
return value;
}

function resolveColor(color: string): string {
if (color.startsWith('var(')) {
const varName = color.slice(4, -1).trim();
const val = getCSSVariableValue(varName);
return val || color;
}
if (color.startsWith('rgba(var(')) {
const rgbaMatch = color.match(/rgba\(var\(([^)]+)\),\s*([^)]+)\)/);
if (rgbaMatch) {
const rgbVarName = rgbaMatch[1];
const opacity = rgbaMatch[2];
const rgbValue = getCSSVariableValue(rgbVarName) || '0, 0, 0';
return `rgba(${rgbValue}, ${opacity})`;
}
}
return color;
}

// Reactive color resolution
// Fallbacks match design system v2.0 values from variables.css (warm earth tones)
const resolvedColors = $derived({
primary: getCSSVariableValue('--color-primary') || '#9a4419',
text: getCSSVariableValue('--color-text') || '#2d2820',
textLight: getCSSVariableValue('--color-text-light') || '#7a7267',
border: getCSSVariableValue('--color-border') || '#e8e4df',
surface: getCSSVariableValue('--color-surface') || '#faf9f7',
surfaceRgb: getCSSVariableValue('--color-surface-rgb') || '250, 249, 247',
accent: getCSSVariableValue('--color-accent') || '#c4a35a',
highlight: getCSSVariableValue('--color-highlight') || '#f59e0b',
chartColors: colors.map((color) => resolveColor(color)),
fontFamily: getCSSVariableValue('--font-family-sans') || 'system-ui, sans-serif',
currentTheme: getTheme()
...getResolvedChartColors(),
chartColors: resolveColors(colors)
});
// Chart data transformation
const chartData = $derived(
Expand All @@ -106,18 +74,7 @@ ECharts Doughnut/Pie Chart - A doughnut chart for visualizing categorical data
},
tooltip: {
trigger: 'item',
backgroundColor: `color-mix(in srgb, ${resolvedColors.surface} 90%, transparent)`,
textStyle: {
color: resolvedColors.text,
fontSize: 12,
fontFamily: resolvedColors.fontFamily
},
borderRadius: 8,
borderColor: resolvedColors.border,
borderWidth: 1,
padding: [10, 14],
extraCssText:
'backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); box-shadow: var(--shadow-lg);',
...getEChartsTooltipStyle(resolvedColors),
formatter: '{a} <br/>{b}: {c} ({d}%)',
confine: isMobile,
position: isMobile
Expand Down
83 changes: 15 additions & 68 deletions src/lib/components/visualisations/EChartsHorizontalBarChart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ ECharts Horizontal Bar Chart component
-->
<script lang="ts">
import type * as echarts from 'echarts';
import { getTheme } from '$lib/stores/themeStore.svelte';
import { innerWidth } from 'svelte/reactivity/window';
import {
getResolvedChartColors,
resolveColor,
getEChartsTooltipStyle,
getEChartsAxisLineStyle,
getEChartsSplitLineStyle
} from '$lib/utils/chartColorUtils';

// Props - keeping the same interface as your D3 component for easy replacement
type DataItem = $$Generic;
Expand Down Expand Up @@ -35,36 +41,10 @@ ECharts Horizontal Bar Chart component
// Use Svelte's reactive window width
const isMobile = $derived((innerWidth.current ?? 1024) < 768);

// Utility functions for CSS variable resolution
function getCSSVariableValue(variableName: string): string {
if (typeof window === 'undefined') return '';
const computedStyle = getComputedStyle(document.documentElement);
const value = computedStyle.getPropertyValue(variableName).trim();
return value;
}

function resolveColor(color: string): string {
if (color.startsWith('var(')) {
const varName = color.slice(4, -1).trim();
const val = getCSSVariableValue(varName);
return val || color;
}
return color;
}

// Reactive color resolution
// Fallbacks match design system v2.0 values from variables.css (warm earth tones)
const resolvedColors = $derived({
primary: getCSSVariableValue('--color-primary') || '#9a4419',
text: getCSSVariableValue('--color-text') || '#2d2820',
textLight: getCSSVariableValue('--color-text-light') || '#7a7267',
border: getCSSVariableValue('--color-border') || '#e8e4df',
surface: getCSSVariableValue('--color-surface') || '#faf9f7',
surfaceRgb: getCSSVariableValue('--color-surface-rgb') || '250, 249, 247',
primaryDark: getCSSVariableValue('--color-primary-dark') || '#7a3516',
barColor: resolveColor(barColor),
fontFamily: getCSSVariableValue('--font-family-sans') || 'system-ui, sans-serif',
currentTheme: getTheme()
...getResolvedChartColors(),
barColor: resolveColor(barColor)
});

// Chart data transformation - reverse order so highest values appear at top
Expand All @@ -80,18 +60,7 @@ ECharts Horizontal Bar Chart component
const chartOption = $derived({
tooltip: {
trigger: 'axis',
backgroundColor: `color-mix(in srgb, ${resolvedColors.surface} 90%, transparent)`,
textStyle: {
color: resolvedColors.text,
fontSize: 12,
fontFamily: resolvedColors.fontFamily
},
borderRadius: 8,
borderColor: resolvedColors.border,
borderWidth: 1,
padding: [10, 14],
extraCssText:
'backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); box-shadow: var(--shadow-lg);'
...getEChartsTooltipStyle(resolvedColors)
},
grid: {
left: isMobile ? '120px' : '150px',
Expand Down Expand Up @@ -121,23 +90,9 @@ ECharts Horizontal Bar Chart component
interval: 1,
minInterval: 1,
max: maxValue,
axisLine: {
lineStyle: {
color: resolvedColors.border
}
},
axisTick: {
lineStyle: {
color: resolvedColors.border
}
},
splitLine: {
lineStyle: {
color: resolvedColors.border,
opacity: 0.3,
type: 'dashed'
}
}
axisLine: getEChartsAxisLineStyle(resolvedColors),
axisTick: getEChartsAxisLineStyle(resolvedColors),
splitLine: getEChartsSplitLineStyle(resolvedColors)
},
yAxis: {
type: 'category',
Expand All @@ -157,16 +112,8 @@ ECharts Horizontal Bar Chart component
width: isMobile ? 100 : 130,
overflow: 'truncate'
},
axisLine: {
lineStyle: {
color: resolvedColors.border
}
},
axisTick: {
lineStyle: {
color: resolvedColors.border
}
}
axisLine: getEChartsAxisLineStyle(resolvedColors),
axisTick: getEChartsAxisLineStyle(resolvedColors)
},
series: [
{
Expand Down
Loading