mirror of https://github.com/ghostfolio/ghostfolio
				
				
			
				 11 changed files with 1254 additions and 21 deletions
			
			
		@ -0,0 +1,333 @@ | 
				
			|||||
 | 
					<!doctype html> | 
				
			||||
 | 
					<html lang="en"> | 
				
			||||
 | 
					  <head> | 
				
			||||
 | 
					    <meta charset="UTF-8" /> | 
				
			||||
 | 
					    <meta content="width=device-width, initial-scale=1.0" name="viewport" /> | 
				
			||||
 | 
					    <title>Ghostfolio Chart Improvements Demo</title> | 
				
			||||
 | 
					    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | 
				
			||||
 | 
					    <script src="https://cdn.jsdelivr.net/npm/date-fns@2.29.3/index.min.js"></script> | 
				
			||||
 | 
					    <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script> | 
				
			||||
 | 
					    <style> | 
				
			||||
 | 
					      body { | 
				
			||||
 | 
					        font-family: 'Inter', sans-serif; | 
				
			||||
 | 
					        margin: 0; | 
				
			||||
 | 
					        padding: 20px; | 
				
			||||
 | 
					        background-color: #f5f5f5; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      .container { | 
				
			||||
 | 
					        max-width: 1200px; | 
				
			||||
 | 
					        margin: 0 auto; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      .chart-container { | 
				
			||||
 | 
					        background: white; | 
				
			||||
 | 
					        border-radius: 8px; | 
				
			||||
 | 
					        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | 
				
			||||
 | 
					        padding: 20px; | 
				
			||||
 | 
					        margin-bottom: 30px; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      h1, | 
				
			||||
 | 
					      h2 { | 
				
			||||
 | 
					        color: #1976d2; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      .description { | 
				
			||||
 | 
					        background: #e3f2fd; | 
				
			||||
 | 
					        border-left: 4px solid #1976d2; | 
				
			||||
 | 
					        padding: 15px; | 
				
			||||
 | 
					        margin: 20px 0; | 
				
			||||
 | 
					        border-radius: 0 4px 4px 0; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      .comparison { | 
				
			||||
 | 
					        display: flex; | 
				
			||||
 | 
					        gap: 20px; | 
				
			||||
 | 
					        margin: 30px 0; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      .comparison-panel { | 
				
			||||
 | 
					        flex: 1; | 
				
			||||
 | 
					        padding: 20px; | 
				
			||||
 | 
					        border-radius: 8px; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      .before { | 
				
			||||
 | 
					        background: #ffebee; | 
				
			||||
 | 
					        border: 1px solid #ffcdd2; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      .after { | 
				
			||||
 | 
					        background: #e8f5e9; | 
				
			||||
 | 
					        border: 1px solid #c8e6c9; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      .chart-wrapper { | 
				
			||||
 | 
					        height: 400px; | 
				
			||||
 | 
					        position: relative; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      canvas { | 
				
			||||
 | 
					        width: 100% !important; | 
				
			||||
 | 
					        height: 100% !important; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					    </style> | 
				
			||||
 | 
					  </head> | 
				
			||||
 | 
					  <body> | 
				
			||||
 | 
					    <div class="container"> | 
				
			||||
 | 
					      <h1>Ghostfolio Chart Improvements Demo</h1> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      <div class="description"> | 
				
			||||
 | 
					        <h2>Visual Improvements for Portfolio Analysis Charts</h2> | 
				
			||||
 | 
					        <p> | 
				
			||||
 | 
					          This demo shows the improvements made to chart visual comparability in | 
				
			||||
 | 
					          the Portfolio Analysis section. The key enhancements include: | 
				
			||||
 | 
					        </p> | 
				
			||||
 | 
					        <ul> | 
				
			||||
 | 
					          <li> | 
				
			||||
 | 
					            <strong>Date Range Filtering:</strong> Charts now properly scale to | 
				
			||||
 | 
					            show only data within the selected time period | 
				
			||||
 | 
					          </li> | 
				
			||||
 | 
					          <li> | 
				
			||||
 | 
					            <strong>Enhanced Visual Design:</strong> Improved color schemes, | 
				
			||||
 | 
					            better spacing, and clearer data representation | 
				
			||||
 | 
					          </li> | 
				
			||||
 | 
					          <li> | 
				
			||||
 | 
					            <strong>Better Readability:</strong> Cleaner axes, grid lines, and | 
				
			||||
 | 
					            tooltips for easier analysis | 
				
			||||
 | 
					          </li> | 
				
			||||
 | 
					        </ul> | 
				
			||||
 | 
					      </div> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      <h2>Portfolio Performance Comparison</h2> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      <div class="comparison"> | 
				
			||||
 | 
					        <div class="comparison-panel before"> | 
				
			||||
 | 
					          <h3>Before: Truncated/Unscaled Charts</h3> | 
				
			||||
 | 
					          <p> | 
				
			||||
 | 
					            Charts were not properly scaled when applying time period filters, | 
				
			||||
 | 
					            making data difficult to read. | 
				
			||||
 | 
					          </p> | 
				
			||||
 | 
					          <div class="chart-wrapper"> | 
				
			||||
 | 
					            <canvas id="chartBefore"></canvas> | 
				
			||||
 | 
					          </div> | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        <div class="comparison-panel after"> | 
				
			||||
 | 
					          <h3>After: Properly Scaled Charts</h3> | 
				
			||||
 | 
					          <p> | 
				
			||||
 | 
					            Charts now automatically adjust to show only the data within the | 
				
			||||
 | 
					            selected time period with proper scaling. | 
				
			||||
 | 
					          </p> | 
				
			||||
 | 
					          <div class="chart-wrapper"> | 
				
			||||
 | 
					            <canvas id="chartAfter"></canvas> | 
				
			||||
 | 
					          </div> | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					      </div> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      <h2>Investment Timeline Comparison</h2> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      <div class="chart-container"> | 
				
			||||
 | 
					        <h3>Enhanced Investment Timeline Chart</h3> | 
				
			||||
 | 
					        <p> | 
				
			||||
 | 
					          Improved visualization with better color coding and clearer data | 
				
			||||
 | 
					          representation. | 
				
			||||
 | 
					        </p> | 
				
			||||
 | 
					        <div class="chart-wrapper"> | 
				
			||||
 | 
					          <canvas id="investmentChart"></canvas> | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					      </div> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      <h2>Dividend Timeline Comparison</h2> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      <div class="chart-container"> | 
				
			||||
 | 
					        <h3>Enhanced Dividend Timeline Chart</h3> | 
				
			||||
 | 
					        <p> | 
				
			||||
 | 
					          Better visual distinction for dividend data with improved readability. | 
				
			||||
 | 
					        </p> | 
				
			||||
 | 
					        <div class="chart-wrapper"> | 
				
			||||
 | 
					          <canvas id="dividendChart"></canvas> | 
				
			||||
 | 
					        </div> | 
				
			||||
 | 
					      </div> | 
				
			||||
 | 
					    </div> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    <script> | 
				
			||||
 | 
					      // Generate mock data | 
				
			||||
 | 
					      function generateMockData(days = 365, startDate = new Date()) { | 
				
			||||
 | 
					        const data = []; | 
				
			||||
 | 
					        let value = 10000; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        for (let i = days; i >= 0; i--) { | 
				
			||||
 | 
					          const date = new Date(startDate); | 
				
			||||
 | 
					          date.setDate(date.getDate() - i); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					          // Add some realistic fluctuations | 
				
			||||
 | 
					          const change = (Math.random() - 0.5) * 200; | 
				
			||||
 | 
					          value += change; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					          data.push({ | 
				
			||||
 | 
					            date: date.toISOString().split('T')[0], | 
				
			||||
 | 
					            value: value | 
				
			||||
 | 
					          }); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        return data; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      // Generate investment data | 
				
			||||
 | 
					      function generateInvestmentData(months = 24) { | 
				
			||||
 | 
					        const data = []; | 
				
			||||
 | 
					        const today = new Date(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        for (let i = months; i >= 0; i--) { | 
				
			||||
 | 
					          const date = new Date(today); | 
				
			||||
 | 
					          date.setMonth(date.getMonth() - i); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					          const investment = Math.random() * 2000 + 500; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					          data.push({ | 
				
			||||
 | 
					            date: date.toISOString().split('T')[0], | 
				
			||||
 | 
					            investment: investment | 
				
			||||
 | 
					          }); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        return data; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      // Generate dividend data | 
				
			||||
 | 
					      function generateDividendData(months = 24) { | 
				
			||||
 | 
					        const data = []; | 
				
			||||
 | 
					        const today = new Date(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        for (let i = months; i >= 0; i--) { | 
				
			||||
 | 
					          const date = new Date(today); | 
				
			||||
 | 
					          date.setMonth(date.getMonth() - i); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					          const dividend = Math.random() * 500 + 100; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					          data.push({ | 
				
			||||
 | 
					            date: date.toISOString().split('T')[0], | 
				
			||||
 | 
					            investment: dividend | 
				
			||||
 | 
					          }); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        return data; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      // Create a chart with the given configuration | 
				
			||||
 | 
					      function createChart(canvasId, data, title, isInvestment = false) { | 
				
			||||
 | 
					        const ctx = document.getElementById(canvasId).getContext('2d'); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        // Filter data for last 6 months for "after" chart | 
				
			||||
 | 
					        const sixMonthsAgo = new Date(); | 
				
			||||
 | 
					        sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        let filteredData = data; | 
				
			||||
 | 
					        if (canvasId === 'chartAfter') { | 
				
			||||
 | 
					          filteredData = data.filter( | 
				
			||||
 | 
					            (item) => new Date(item.date) >= sixMonthsAgo | 
				
			||||
 | 
					          ); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        const chartData = { | 
				
			||||
 | 
					          labels: filteredData.map((item) => item.date), | 
				
			||||
 | 
					          datasets: [ | 
				
			||||
 | 
					            { | 
				
			||||
 | 
					              label: isInvestment ? 'Investment' : 'Portfolio Value', | 
				
			||||
 | 
					              data: filteredData.map((item) => | 
				
			||||
 | 
					                isInvestment ? item.investment : item.value | 
				
			||||
 | 
					              ), | 
				
			||||
 | 
					              borderColor: isInvestment ? '#388e3c' : '#1976d2', | 
				
			||||
 | 
					              backgroundColor: isInvestment | 
				
			||||
 | 
					                ? 'rgba(56, 142, 60, 0.1)' | 
				
			||||
 | 
					                : 'rgba(25, 118, 210, 0.1)', | 
				
			||||
 | 
					              borderWidth: 2, | 
				
			||||
 | 
					              pointRadius: 0, | 
				
			||||
 | 
					              fill: true, | 
				
			||||
 | 
					              tension: 0 | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					          ] | 
				
			||||
 | 
					        }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        if (!isInvestment) { | 
				
			||||
 | 
					          // Add benchmark data | 
				
			||||
 | 
					          chartData.datasets.push({ | 
				
			||||
 | 
					            label: 'Benchmark (S&P 500)', | 
				
			||||
 | 
					            data: filteredData.map( | 
				
			||||
 | 
					              (item) => | 
				
			||||
 | 
					                (isInvestment ? item.investment : item.value) * | 
				
			||||
 | 
					                (0.9 + Math.random() * 0.2) | 
				
			||||
 | 
					            ), | 
				
			||||
 | 
					            borderColor: '#f57c00', | 
				
			||||
 | 
					            backgroundColor: 'transparent', | 
				
			||||
 | 
					            borderWidth: 2, | 
				
			||||
 | 
					            pointRadius: 0, | 
				
			||||
 | 
					            fill: false, | 
				
			||||
 | 
					            tension: 0 | 
				
			||||
 | 
					          }); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        return new Chart(ctx, { | 
				
			||||
 | 
					          type: 'line', | 
				
			||||
 | 
					          data: chartData, | 
				
			||||
 | 
					          options: { | 
				
			||||
 | 
					            responsive: true, | 
				
			||||
 | 
					            maintainAspectRatio: false, | 
				
			||||
 | 
					            plugins: { | 
				
			||||
 | 
					              title: { | 
				
			||||
 | 
					                display: true, | 
				
			||||
 | 
					                text: title | 
				
			||||
 | 
					              }, | 
				
			||||
 | 
					              legend: { | 
				
			||||
 | 
					                position: 'top' | 
				
			||||
 | 
					              }, | 
				
			||||
 | 
					              tooltip: { | 
				
			||||
 | 
					                mode: 'index', | 
				
			||||
 | 
					                intersect: false | 
				
			||||
 | 
					              } | 
				
			||||
 | 
					            }, | 
				
			||||
 | 
					            scales: { | 
				
			||||
 | 
					              x: { | 
				
			||||
 | 
					                type: 'time', | 
				
			||||
 | 
					                time: { | 
				
			||||
 | 
					                  unit: 'month' | 
				
			||||
 | 
					                }, | 
				
			||||
 | 
					                grid: { | 
				
			||||
 | 
					                  display: false | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					              }, | 
				
			||||
 | 
					              y: { | 
				
			||||
 | 
					                beginAtZero: false, | 
				
			||||
 | 
					                grid: { | 
				
			||||
 | 
					                  color: 'rgba(0, 0, 0, 0.05)' | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					              } | 
				
			||||
 | 
					            }, | 
				
			||||
 | 
					            interaction: { | 
				
			||||
 | 
					              intersect: false, | 
				
			||||
 | 
					              mode: 'index' | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					          } | 
				
			||||
 | 
					        }); | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      // Initialize charts when the page loads | 
				
			||||
 | 
					      document.addEventListener('DOMContentLoaded', function () { | 
				
			||||
 | 
					        // Generate mock data | 
				
			||||
 | 
					        const performanceData = generateMockData(365); | 
				
			||||
 | 
					        const investmentData = generateInvestmentData(24); | 
				
			||||
 | 
					        const dividendData = generateDividendData(24); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        // Create charts | 
				
			||||
 | 
					        createChart( | 
				
			||||
 | 
					          'chartBefore', | 
				
			||||
 | 
					          performanceData, | 
				
			||||
 | 
					          'Portfolio Performance (Full Year)' | 
				
			||||
 | 
					        ); | 
				
			||||
 | 
					        createChart( | 
				
			||||
 | 
					          'chartAfter', | 
				
			||||
 | 
					          performanceData, | 
				
			||||
 | 
					          'Portfolio Performance (Last 6 Months)' | 
				
			||||
 | 
					        ); | 
				
			||||
 | 
					        createChart( | 
				
			||||
 | 
					          'investmentChart', | 
				
			||||
 | 
					          investmentData, | 
				
			||||
 | 
					          'Investment Timeline', | 
				
			||||
 | 
					          true | 
				
			||||
 | 
					        ); | 
				
			||||
 | 
					        createChart('dividendChart', dividendData, 'Dividend Timeline', true); | 
				
			||||
 | 
					      }); | 
				
			||||
 | 
					    </script> | 
				
			||||
 | 
					  </body> | 
				
			||||
 | 
					</html> | 
				
			||||
@ -0,0 +1,273 @@ | 
				
			|||||
 | 
					import { | 
				
			||||
 | 
					  getChartColorPalette, | 
				
			||||
 | 
					  getPerformanceColor, | 
				
			||||
 | 
					  getAllocationColor | 
				
			||||
 | 
					} from './chart-theme'; | 
				
			||||
 | 
					import { ColorScheme } from './types'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Standardized Chart Configuration for Ghostfolio | 
				
			||||
 | 
					 * | 
				
			||||
 | 
					 * Provides consistent styling, layout, and behavior across all chart types | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export interface ChartConfigOptions { | 
				
			||||
 | 
					  colorScheme?: ColorScheme; | 
				
			||||
 | 
					  currency?: string; | 
				
			||||
 | 
					  locale?: string; | 
				
			||||
 | 
					  unit?: string; | 
				
			||||
 | 
					  showLegend?: boolean; | 
				
			||||
 | 
					  showGrid?: boolean; | 
				
			||||
 | 
					  aspectRatio?: number; | 
				
			||||
 | 
					  responsive?: boolean; | 
				
			||||
 | 
					  maintainAspectRatio?: boolean; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export interface ChartLayoutConfig { | 
				
			||||
 | 
					  padding?: number; | 
				
			||||
 | 
					  spacing?: number; | 
				
			||||
 | 
					  borderRadius?: number; | 
				
			||||
 | 
					  borderWidth?: number; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Get standardized chart options with consistent styling | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					export function getStandardChartOptions( | 
				
			||||
 | 
					  options: ChartConfigOptions = {}, | 
				
			||||
 | 
					  layoutConfig: ChartLayoutConfig = {} | 
				
			||||
 | 
					): any { | 
				
			||||
 | 
					  const { | 
				
			||||
 | 
					    colorScheme = 'LIGHT', | 
				
			||||
 | 
					    showLegend = false, | 
				
			||||
 | 
					    showGrid = true, | 
				
			||||
 | 
					    aspectRatio = 16 / 9, | 
				
			||||
 | 
					    responsive = true, | 
				
			||||
 | 
					    maintainAspectRatio = true | 
				
			||||
 | 
					  } = options; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  const { | 
				
			||||
 | 
					    padding = 0, | 
				
			||||
 | 
					    spacing = 2, | 
				
			||||
 | 
					    borderRadius = 4, | 
				
			||||
 | 
					    borderWidth = 1 | 
				
			||||
 | 
					  } = layoutConfig; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  const palette = getChartColorPalette(colorScheme); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  return { | 
				
			||||
 | 
					    responsive, | 
				
			||||
 | 
					    maintainAspectRatio, | 
				
			||||
 | 
					    aspectRatio, | 
				
			||||
 | 
					    layout: { | 
				
			||||
 | 
					      padding: padding | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    plugins: { | 
				
			||||
 | 
					      legend: { | 
				
			||||
 | 
					        display: showLegend, | 
				
			||||
 | 
					        position: 'bottom', | 
				
			||||
 | 
					        align: 'start', | 
				
			||||
 | 
					        labels: { | 
				
			||||
 | 
					          usePointStyle: true, | 
				
			||||
 | 
					          padding: spacing * 4, | 
				
			||||
 | 
					          font: { | 
				
			||||
 | 
					            size: 12, | 
				
			||||
 | 
					            family: 'Inter, sans-serif' | 
				
			||||
 | 
					          }, | 
				
			||||
 | 
					          color: palette.text.secondary | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					      }, | 
				
			||||
 | 
					      tooltip: { | 
				
			||||
 | 
					        enabled: true, | 
				
			||||
 | 
					        backgroundColor: palette.background, | 
				
			||||
 | 
					        titleColor: palette.text.primary, | 
				
			||||
 | 
					        bodyColor: palette.text.primary, | 
				
			||||
 | 
					        borderColor: palette.border, | 
				
			||||
 | 
					        borderWidth: 1, | 
				
			||||
 | 
					        cornerRadius: borderRadius, | 
				
			||||
 | 
					        displayColors: true, | 
				
			||||
 | 
					        usePointStyle: true, | 
				
			||||
 | 
					        padding: spacing * 3, | 
				
			||||
 | 
					        font: { | 
				
			||||
 | 
					          size: 12, | 
				
			||||
 | 
					          family: 'Inter, sans-serif' | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    scales: { | 
				
			||||
 | 
					      x: { | 
				
			||||
 | 
					        display: true, | 
				
			||||
 | 
					        grid: { | 
				
			||||
 | 
					          display: showGrid, | 
				
			||||
 | 
					          color: palette.grid, | 
				
			||||
 | 
					          tickLength: spacing * 2 | 
				
			||||
 | 
					        }, | 
				
			||||
 | 
					        border: { | 
				
			||||
 | 
					          display: true, | 
				
			||||
 | 
					          color: palette.border, | 
				
			||||
 | 
					          width: borderWidth | 
				
			||||
 | 
					        }, | 
				
			||||
 | 
					        ticks: { | 
				
			||||
 | 
					          display: true, | 
				
			||||
 | 
					          color: palette.text.secondary, | 
				
			||||
 | 
					          padding: spacing * 2, | 
				
			||||
 | 
					          font: { | 
				
			||||
 | 
					            size: 11, | 
				
			||||
 | 
					            family: 'Inter, sans-serif' | 
				
			||||
 | 
					          } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					      }, | 
				
			||||
 | 
					      y: { | 
				
			||||
 | 
					        display: true, | 
				
			||||
 | 
					        position: 'right', | 
				
			||||
 | 
					        grid: { | 
				
			||||
 | 
					          display: showGrid, | 
				
			||||
 | 
					          color: palette.grid, | 
				
			||||
 | 
					          tickLength: spacing * 2 | 
				
			||||
 | 
					        }, | 
				
			||||
 | 
					        ticks: { | 
				
			||||
 | 
					          display: true, | 
				
			||||
 | 
					          color: palette.text.secondary, | 
				
			||||
 | 
					          padding: spacing * 2, | 
				
			||||
 | 
					          mirror: true, | 
				
			||||
 | 
					          z: 1, | 
				
			||||
 | 
					          font: { | 
				
			||||
 | 
					            size: 11, | 
				
			||||
 | 
					            family: 'Inter, sans-serif' | 
				
			||||
 | 
					          } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    elements: { | 
				
			||||
 | 
					      point: { | 
				
			||||
 | 
					        radius: 0, | 
				
			||||
 | 
					        hoverRadius: spacing * 2, | 
				
			||||
 | 
					        borderWidth: 0 | 
				
			||||
 | 
					      }, | 
				
			||||
 | 
					      line: { | 
				
			||||
 | 
					        borderWidth: borderWidth * 2, | 
				
			||||
 | 
					        tension: 0, | 
				
			||||
 | 
					        fill: false | 
				
			||||
 | 
					      }, | 
				
			||||
 | 
					      bar: { | 
				
			||||
 | 
					        borderWidth: 0, | 
				
			||||
 | 
					        borderRadius: borderRadius | 
				
			||||
 | 
					      }, | 
				
			||||
 | 
					      arc: { | 
				
			||||
 | 
					        borderWidth: 0, | 
				
			||||
 | 
					        borderRadius: borderRadius | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    animation: false, | 
				
			||||
 | 
					    interaction: { | 
				
			||||
 | 
					      intersect: false, | 
				
			||||
 | 
					      mode: 'index' | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					  }; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Get standardized colors for chart datasets | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					export function getStandardDatasetColors( | 
				
			||||
 | 
					  index: number, | 
				
			||||
 | 
					  type: 'line' | 'bar' | 'doughnut' | 'treemap', | 
				
			||||
 | 
					  colorScheme: ColorScheme = 'LIGHT', | 
				
			||||
 | 
					  value?: number | 
				
			||||
 | 
					): { | 
				
			||||
 | 
					  backgroundColor: string | string[]; | 
				
			||||
 | 
					  borderColor: string | string[]; | 
				
			||||
 | 
					  hoverBackgroundColor?: string | string[]; | 
				
			||||
 | 
					  hoverBorderColor?: string | string[]; | 
				
			||||
 | 
					} { | 
				
			||||
 | 
					  const palette = getChartColorPalette(colorScheme); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  switch (type) { | 
				
			||||
 | 
					    case 'line': | 
				
			||||
 | 
					      return { | 
				
			||||
 | 
					        backgroundColor: 'transparent', | 
				
			||||
 | 
					        borderColor: index === 0 ? palette.primary : palette.secondary, | 
				
			||||
 | 
					        hoverBackgroundColor: palette.hover | 
				
			||||
 | 
					      }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    case 'bar': | 
				
			||||
 | 
					      return { | 
				
			||||
 | 
					        backgroundColor: getAllocationColor(index, colorScheme), | 
				
			||||
 | 
					        borderColor: 'transparent', | 
				
			||||
 | 
					        hoverBackgroundColor: palette.hover | 
				
			||||
 | 
					      }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    case 'doughnut': | 
				
			||||
 | 
					      return { | 
				
			||||
 | 
					        backgroundColor: getAllocationColor(index, colorScheme), | 
				
			||||
 | 
					        borderColor: 'transparent', | 
				
			||||
 | 
					        hoverBackgroundColor: palette.hover | 
				
			||||
 | 
					      }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    case 'treemap': | 
				
			||||
 | 
					      if (value !== undefined) { | 
				
			||||
 | 
					        return { | 
				
			||||
 | 
					          backgroundColor: getPerformanceColor(value, colorScheme), | 
				
			||||
 | 
					          borderColor: 'transparent' | 
				
			||||
 | 
					        }; | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					      return { | 
				
			||||
 | 
					        backgroundColor: getAllocationColor(index, colorScheme), | 
				
			||||
 | 
					        borderColor: 'transparent' | 
				
			||||
 | 
					      }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    default: | 
				
			||||
 | 
					      return { | 
				
			||||
 | 
					        backgroundColor: palette.primary, | 
				
			||||
 | 
					        borderColor: palette.primary | 
				
			||||
 | 
					      }; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Get responsive breakpoints for charts | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					export function getChartBreakpoints() { | 
				
			||||
 | 
					  return { | 
				
			||||
 | 
					    mobile: 480, | 
				
			||||
 | 
					    tablet: 768, | 
				
			||||
 | 
					    desktop: 1024, | 
				
			||||
 | 
					    large: 1440 | 
				
			||||
 | 
					  }; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Get responsive chart configuration | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					export function getResponsiveChartConfig( | 
				
			||||
 | 
					  colorScheme: ColorScheme = 'LIGHT', | 
				
			||||
 | 
					  deviceType: 'mobile' | 'tablet' | 'desktop' = 'desktop' | 
				
			||||
 | 
					): Partial<ChartConfigOptions> { | 
				
			||||
 | 
					  const baseConfig: ChartConfigOptions = { | 
				
			||||
 | 
					    colorScheme, | 
				
			||||
 | 
					    showLegend: deviceType !== 'mobile', | 
				
			||||
 | 
					    aspectRatio: deviceType === 'mobile' ? 1 : 16 / 9, | 
				
			||||
 | 
					    maintainAspectRatio: deviceType !== 'mobile' | 
				
			||||
 | 
					  }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  switch (deviceType) { | 
				
			||||
 | 
					    case 'mobile': | 
				
			||||
 | 
					      return { | 
				
			||||
 | 
					        ...baseConfig, | 
				
			||||
 | 
					        aspectRatio: 1, | 
				
			||||
 | 
					        maintainAspectRatio: false, | 
				
			||||
 | 
					        showLegend: false, | 
				
			||||
 | 
					        showGrid: false | 
				
			||||
 | 
					      }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    case 'tablet': | 
				
			||||
 | 
					      return { | 
				
			||||
 | 
					        ...baseConfig, | 
				
			||||
 | 
					        aspectRatio: 4 / 3, | 
				
			||||
 | 
					        showLegend: true | 
				
			||||
 | 
					      }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    default: | 
				
			||||
 | 
					      return baseConfig; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,287 @@ | 
				
			|||||
 | 
					import { ColorScheme } from './types'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Chart Color Theme System for Ghostfolio | 
				
			||||
 | 
					 * | 
				
			||||
 | 
					 * Provides consistent color schemes across all chart types with support for: | 
				
			||||
 | 
					 * - Light and dark themes | 
				
			||||
 | 
					 * - Semantic color meanings (positive, negative, neutral) | 
				
			||||
 | 
					 * - Performance-based color coding | 
				
			||||
 | 
					 * - Accessibility-compliant contrast ratios | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export interface ChartColorPalette { | 
				
			||||
 | 
					  // Primary chart colors
 | 
				
			||||
 | 
					  primary: string; | 
				
			||||
 | 
					  secondary: string; | 
				
			||||
 | 
					  accent: string; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // Semantic colors
 | 
				
			||||
 | 
					  positive: string; | 
				
			||||
 | 
					  negative: string; | 
				
			||||
 | 
					  neutral: string; | 
				
			||||
 | 
					  warning: string; | 
				
			||||
 | 
					  info: string; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // Performance-based colors (green to red spectrum)
 | 
				
			||||
 | 
					  performance: { | 
				
			||||
 | 
					    excellent: string; | 
				
			||||
 | 
					    good: string; | 
				
			||||
 | 
					    average: string; | 
				
			||||
 | 
					    poor: string; | 
				
			||||
 | 
					    terrible: string; | 
				
			||||
 | 
					  }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // Allocation colors (diverse palette for categories)
 | 
				
			||||
 | 
					  allocation: string[]; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // Background and text colors
 | 
				
			||||
 | 
					  background: string; | 
				
			||||
 | 
					  surface: string; | 
				
			||||
 | 
					  text: { | 
				
			||||
 | 
					    primary: string; | 
				
			||||
 | 
					    secondary: string; | 
				
			||||
 | 
					    disabled: string; | 
				
			||||
 | 
					  }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // Interactive states
 | 
				
			||||
 | 
					  hover: string; | 
				
			||||
 | 
					  active: string; | 
				
			||||
 | 
					  focus: string; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // Grid and border colors
 | 
				
			||||
 | 
					  grid: string; | 
				
			||||
 | 
					  border: string; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export interface ChartTheme { | 
				
			||||
 | 
					  light: ChartColorPalette; | 
				
			||||
 | 
					  dark: ChartColorPalette; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Generate color palette based on color scheme | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					export function getChartColorPalette( | 
				
			||||
 | 
					  colorScheme: ColorScheme = 'LIGHT' | 
				
			||||
 | 
					): ChartColorPalette { | 
				
			||||
 | 
					  const isDark = colorScheme === 'DARK'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  if (isDark) { | 
				
			||||
 | 
					    return { | 
				
			||||
 | 
					      // Primary chart colors
 | 
				
			||||
 | 
					      primary: '#64b5f6', // Light blue
 | 
				
			||||
 | 
					      secondary: '#81c784', // Light green
 | 
				
			||||
 | 
					      accent: '#ffb74d', // Orange
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      // Semantic colors
 | 
				
			||||
 | 
					      positive: '#4caf50', // Green
 | 
				
			||||
 | 
					      negative: '#f44336', // Red
 | 
				
			||||
 | 
					      neutral: '#9e9e9e', // Grey
 | 
				
			||||
 | 
					      warning: '#ff9800', // Orange
 | 
				
			||||
 | 
					      info: '#2196f3', // Blue
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      // Performance-based colors
 | 
				
			||||
 | 
					      performance: { | 
				
			||||
 | 
					        excellent: '#2e7d32', // Dark green
 | 
				
			||||
 | 
					        good: '#4caf50', // Green
 | 
				
			||||
 | 
					        average: '#8bc34a', // Light green
 | 
				
			||||
 | 
					        poor: '#ff5722', // Deep orange
 | 
				
			||||
 | 
					        terrible: '#d32f2f' // Dark red
 | 
				
			||||
 | 
					      }, | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      // Allocation colors (diverse palette)
 | 
				
			||||
 | 
					      allocation: [ | 
				
			||||
 | 
					        '#2196f3', // Blue
 | 
				
			||||
 | 
					        '#4caf50', // Green
 | 
				
			||||
 | 
					        '#ff9800', // Orange
 | 
				
			||||
 | 
					        '#9c27b0', // Purple
 | 
				
			||||
 | 
					        '#f44336', // Red
 | 
				
			||||
 | 
					        '#00bcd4', // Cyan
 | 
				
			||||
 | 
					        '#ffc107', // Amber
 | 
				
			||||
 | 
					        '#795548', // Brown
 | 
				
			||||
 | 
					        '#607d8b', // Blue grey
 | 
				
			||||
 | 
					        '#e91e63', // Pink
 | 
				
			||||
 | 
					        '#3f51b5', // Indigo
 | 
				
			||||
 | 
					        '#009688', // Teal
 | 
				
			||||
 | 
					        '#8bc34a', // Light green
 | 
				
			||||
 | 
					        '#ff5722', // Deep orange
 | 
				
			||||
 | 
					        '#9c27b0' // Purple
 | 
				
			||||
 | 
					      ], | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      // Background and text colors
 | 
				
			||||
 | 
					      background: '#121212', | 
				
			||||
 | 
					      surface: '#1e1e1e', | 
				
			||||
 | 
					      text: { | 
				
			||||
 | 
					        primary: '#ffffff', | 
				
			||||
 | 
					        secondary: '#b3b3b3', | 
				
			||||
 | 
					        disabled: '#666666' | 
				
			||||
 | 
					      }, | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      // Interactive states
 | 
				
			||||
 | 
					      hover: 'rgba(255, 255, 255, 0.08)', | 
				
			||||
 | 
					      active: 'rgba(255, 255, 255, 0.16)', | 
				
			||||
 | 
					      focus: 'rgba(100, 181, 246, 0.24)', | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      // Grid and border colors
 | 
				
			||||
 | 
					      grid: 'rgba(255, 255, 255, 0.08)', | 
				
			||||
 | 
					      border: 'rgba(255, 255, 255, 0.12)' | 
				
			||||
 | 
					    }; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // Light theme
 | 
				
			||||
 | 
					  return { | 
				
			||||
 | 
					    // Primary chart colors
 | 
				
			||||
 | 
					    primary: '#1976d2', // Blue
 | 
				
			||||
 | 
					    secondary: '#388e3c', // Green
 | 
				
			||||
 | 
					    accent: '#f57c00', // Orange
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    // Semantic colors
 | 
				
			||||
 | 
					    positive: '#2e7d32', // Green
 | 
				
			||||
 | 
					    negative: '#d32f2f', // Red
 | 
				
			||||
 | 
					    neutral: '#757575', // Grey
 | 
				
			||||
 | 
					    warning: '#f57c00', // Orange
 | 
				
			||||
 | 
					    info: '#1976d2', // Blue
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    // Performance-based colors
 | 
				
			||||
 | 
					    performance: { | 
				
			||||
 | 
					      excellent: '#1b5e20', // Dark green
 | 
				
			||||
 | 
					      good: '#2e7d32', // Green
 | 
				
			||||
 | 
					      average: '#4caf50', // Light green
 | 
				
			||||
 | 
					      poor: '#f57c00', // Orange
 | 
				
			||||
 | 
					      terrible: '#d32f2f' // Dark red
 | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    // Allocation colors (diverse palette)
 | 
				
			||||
 | 
					    allocation: [ | 
				
			||||
 | 
					      '#1976d2', // Blue
 | 
				
			||||
 | 
					      '#388e3c', // Green
 | 
				
			||||
 | 
					      '#f57c00', // Orange
 | 
				
			||||
 | 
					      '#7b1fa2', // Purple
 | 
				
			||||
 | 
					      '#d32f2f', // Red
 | 
				
			||||
 | 
					      '#00796b', // Teal
 | 
				
			||||
 | 
					      '#fbc02d', // Yellow
 | 
				
			||||
 | 
					      '#5d4037', // Brown
 | 
				
			||||
 | 
					      '#455a64', // Blue grey
 | 
				
			||||
 | 
					      '#c2185b', // Pink
 | 
				
			||||
 | 
					      '#3f51b5', // Indigo
 | 
				
			||||
 | 
					      '#009688', // Teal
 | 
				
			||||
 | 
					      '#689f38', // Light green
 | 
				
			||||
 | 
					      '#e65100', // Deep orange
 | 
				
			||||
 | 
					      '#7b1fa2' // Purple
 | 
				
			||||
 | 
					    ], | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    // Background and text colors
 | 
				
			||||
 | 
					    background: '#ffffff', | 
				
			||||
 | 
					    surface: '#f5f5f5', | 
				
			||||
 | 
					    text: { | 
				
			||||
 | 
					      primary: '#212121', | 
				
			||||
 | 
					      secondary: '#757575', | 
				
			||||
 | 
					      disabled: '#bdbdbd' | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    // Interactive states
 | 
				
			||||
 | 
					    hover: 'rgba(0, 0, 0, 0.04)', | 
				
			||||
 | 
					    active: 'rgba(0, 0, 0, 0.08)', | 
				
			||||
 | 
					    focus: 'rgba(25, 118, 210, 0.12)', | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    // Grid and border colors
 | 
				
			||||
 | 
					    grid: 'rgba(0, 0, 0, 0.08)', | 
				
			||||
 | 
					    border: 'rgba(0, 0, 0, 0.12)' | 
				
			||||
 | 
					  }; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Get performance color based on percentage value | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					export function getPerformanceColor( | 
				
			||||
 | 
					  percentage: number, | 
				
			||||
 | 
					  colorScheme: ColorScheme = 'LIGHT' | 
				
			||||
 | 
					): string { | 
				
			||||
 | 
					  const palette = getChartColorPalette(colorScheme); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  if (percentage >= 15) return palette.performance.excellent; | 
				
			||||
 | 
					  if (percentage >= 5) return palette.performance.good; | 
				
			||||
 | 
					  if (percentage >= -5) return palette.performance.average; | 
				
			||||
 | 
					  if (percentage >= -15) return palette.performance.poor; | 
				
			||||
 | 
					  return palette.performance.terrible; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Get allocation color for index-based coloring | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					export function getAllocationColor( | 
				
			||||
 | 
					  index: number, | 
				
			||||
 | 
					  colorScheme: ColorScheme = 'LIGHT' | 
				
			||||
 | 
					): string { | 
				
			||||
 | 
					  const palette = getChartColorPalette(colorScheme); | 
				
			||||
 | 
					  return palette.allocation[index % palette.allocation.length]; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Get semantic color based on context | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					export function getSemanticColor( | 
				
			||||
 | 
					  type: 'positive' | 'negative' | 'neutral' | 'warning' | 'info', | 
				
			||||
 | 
					  colorScheme: ColorScheme = 'LIGHT' | 
				
			||||
 | 
					): string { | 
				
			||||
 | 
					  const palette = getChartColorPalette(colorScheme); | 
				
			||||
 | 
					  return palette[type]; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Calculate relative luminance of a color (0-1 scale) | 
				
			||||
 | 
					 * Based on WCAG guidelines for color contrast | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					function calculateLuminance(color: string): number { | 
				
			||||
 | 
					  // Convert hex to RGB
 | 
				
			||||
 | 
					  const hex = color.replace('#', ''); | 
				
			||||
 | 
					  const r = parseInt(hex.substr(0, 2), 16) / 255; | 
				
			||||
 | 
					  const g = parseInt(hex.substr(2, 2), 16) / 255; | 
				
			||||
 | 
					  const b = parseInt(hex.substr(4, 2), 16) / 255; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // Apply gamma correction
 | 
				
			||||
 | 
					  const gammaCorrect = (value: number): number => { | 
				
			||||
 | 
					    return value <= 0.03928 | 
				
			||||
 | 
					      ? value / 12.92 | 
				
			||||
 | 
					      : Math.pow((value + 0.055) / 1.055, 2.4); | 
				
			||||
 | 
					  }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  const rCorrected = gammaCorrect(r); | 
				
			||||
 | 
					  const gCorrected = gammaCorrect(g); | 
				
			||||
 | 
					  const bCorrected = gammaCorrect(b); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // Calculate relative luminance
 | 
				
			||||
 | 
					  return 0.2126 * rCorrected + 0.7152 * gCorrected + 0.0722 * bCorrected; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					export function getContrastTextColor( | 
				
			||||
 | 
					  backgroundColor: string, | 
				
			||||
 | 
					  colorScheme: ColorScheme = 'LIGHT' | 
				
			||||
 | 
					): string { | 
				
			||||
 | 
					  const palette = getChartColorPalette(colorScheme); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // Calculate relative luminance of the background color
 | 
				
			||||
 | 
					  const luminance = calculateLuminance(backgroundColor); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  // Use dark text for light backgrounds, light text for dark backgrounds
 | 
				
			||||
 | 
					  return luminance > 0.5 ? palette.text.primary : '#ffffff'; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Generate gradient colors for area charts | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					export function generateGradientColors( | 
				
			||||
 | 
					  baseColor: string, | 
				
			||||
 | 
					  colorScheme: ColorScheme = 'LIGHT', | 
				
			||||
 | 
					  opacity: number = 0.1 | 
				
			||||
 | 
					): { start: string; end: string } { | 
				
			||||
 | 
					  // Using palette for potential future enhancements
 | 
				
			||||
 | 
					  getChartColorPalette(colorScheme); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  return { | 
				
			||||
 | 
					    start: `${baseColor}${Math.round(opacity * 255) | 
				
			||||
 | 
					      .toString(16) | 
				
			||||
 | 
					      .padStart(2, '0')}`,
 | 
				
			||||
 | 
					    end: baseColor | 
				
			||||
 | 
					  }; | 
				
			||||
 | 
					} | 
				
			||||
					Loading…
					
					
				
		Reference in new issue