UNPKG

@moontra/moonui-pro

Version:

Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components

358 lines (305 loc) 10.7 kB
import { ChartDataPoint, ChartSeries } from '../components/advanced-chart' interface ChartTheme { colors: string[] backgroundColor: string textColor: string gridColor: string tooltipBackground: string tooltipBorder: string } export const CHART_THEMES: Record<string, ChartTheme> = { default: { colors: ['#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6', '#06b6d4', '#f97316', '#84cc16', '#ec4899', '#6366f1'], backgroundColor: '#ffffff', textColor: '#374151', gridColor: '#e5e7eb', tooltipBackground: '#1f2937', tooltipBorder: '#374151', }, dark: { colors: ['#60a5fa', '#f87171', '#34d399', '#fbbf24', '#a78bfa', '#22d3ee', '#fb923c', '#a3e635', '#f472b6', '#818cf8'], backgroundColor: '#1f2937', textColor: '#f9fafb', gridColor: '#374151', tooltipBackground: '#374151', tooltipBorder: '#4b5563', }, minimal: { colors: ['#000000', '#666666', '#999999', '#cccccc'], backgroundColor: '#ffffff', textColor: '#000000', gridColor: '#f3f4f6', tooltipBackground: '#000000', tooltipBorder: '#000000', }, } export function generateChartData( count: number, series: string[], options?: { startDate?: Date interval?: 'hour' | 'day' | 'week' | 'month' trend?: 'up' | 'down' | 'random' baseValue?: number variance?: number } ): ChartDataPoint[] { const { startDate = new Date(), interval = 'day', trend = 'random', baseValue = 100, variance = 20, } = options || {} const data: ChartDataPoint[] = [] const current = new Date(startDate) for (let i = 0; i < count; i++) { const point: ChartDataPoint = { name: formatDateForInterval(current, interval), timestamp: current.getTime(), } series.forEach((seriesName, index) => { let value = baseValue if (trend === 'up') { value += (i * 5) + (Math.random() - 0.5) * variance } else if (trend === 'down') { value -= (i * 5) + (Math.random() - 0.5) * variance } else { value += (Math.random() - 0.5) * variance * 2 } // Add some series-specific variation value += index * 10 + (Math.random() - 0.5) * 10 point[seriesName] = Math.max(0, Math.round(value)) }) data.push(point) // Increment date based on interval switch (interval) { case 'hour': current.setHours(current.getHours() + 1) break case 'day': current.setDate(current.getDate() + 1) break case 'week': current.setDate(current.getDate() + 7) break case 'month': current.setMonth(current.getMonth() + 1) break } } return data } function formatDateForInterval(date: Date, interval: 'hour' | 'day' | 'week' | 'month'): string { switch (interval) { case 'hour': return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }) case 'day': return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) case 'week': return `Week ${getWeekNumber(date)}` case 'month': return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }) default: return date.toLocaleDateString() } } function getWeekNumber(date: Date): number { const firstDayOfYear = new Date(date.getFullYear(), 0, 1) const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000 return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7) } export function calculateMovingAverage(data: ChartDataPoint[], key: string, window: number): ChartDataPoint[] { if (window <= 0 || window > data.length) return data return data.map((point, index) => { if (index < window - 1) return point const windowData = data.slice(index - window + 1, index + 1) const sum = windowData.reduce((acc, item) => acc + (Number(item[key]) || 0), 0) const average = sum / window return { ...point, [`${key}_ma${window}`]: Math.round(average * 100) / 100, } }) } export function detectOutliers(data: ChartDataPoint[], key: string, threshold: number = 2): ChartDataPoint[] { const values = data.map(point => Number(point[key]) || 0) const mean = values.reduce((sum, val) => sum + val, 0) / values.length const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length const stdDev = Math.sqrt(variance) return data.map(point => { const value = Number(point[key]) || 0 const zScore = Math.abs((value - mean) / stdDev) return { ...point, [`${key}_outlier`]: zScore > threshold, [`${key}_zscore`]: Math.round(zScore * 100) / 100, } }) } export function interpolateData(data: ChartDataPoint[], key: string, method: 'linear' | 'polynomial' = 'linear'): ChartDataPoint[] { const result = [...data] for (let i = 0; i < result.length; i++) { const point = result[i] if (point[key] == null || point[key] === '') { // Find nearest non-null values let prevIndex = i - 1 let nextIndex = i + 1 while (prevIndex >= 0 && (result[prevIndex][key] == null || result[prevIndex][key] === '')) { prevIndex-- } while (nextIndex < result.length && (result[nextIndex][key] == null || result[nextIndex][key] === '')) { nextIndex++ } if (prevIndex >= 0 && nextIndex < result.length) { const prevValue = Number(result[prevIndex][key]) const nextValue = Number(result[nextIndex][key]) if (method === 'linear') { const ratio = (i - prevIndex) / (nextIndex - prevIndex) point[key] = prevValue + (nextValue - prevValue) * ratio } } else if (prevIndex >= 0) { point[key] = result[prevIndex][key] } else if (nextIndex < result.length) { point[key] = result[nextIndex][key] } } } return result } export function aggregateDataByPeriod( data: ChartDataPoint[], period: 'hour' | 'day' | 'week' | 'month', aggregations: Record<string, 'sum' | 'avg' | 'min' | 'max' | 'count'> ): ChartDataPoint[] { const groups: Record<string, ChartDataPoint[]> = {} data.forEach(point => { const timestamp = point.timestamp ? new Date(point.timestamp as number) : new Date() const key = formatDateForInterval(timestamp, period) if (!groups[key]) { groups[key] = [] } groups[key].push(point) }) return Object.entries(groups).map(([key, groupData]) => { const result: ChartDataPoint = { name: key } Object.entries(aggregations).forEach(([field, operation]) => { const values = groupData.map(point => Number(point[field]) || 0) switch (operation) { case 'sum': result[field] = values.reduce((sum, val) => sum + val, 0) break case 'avg': result[field] = values.reduce((sum, val) => sum + val, 0) / values.length break case 'min': result[field] = Math.min(...values) break case 'max': result[field] = Math.max(...values) break case 'count': result[field] = values.length break } }) return result }) } export function exportChartAsImage( chartElement: HTMLElement, filename: string = 'chart', format: 'png' | 'jpeg' | 'svg' = 'png' ): void { // This would typically use html2canvas or similar library // For now, we'll provide a basic implementation console.log(`Exporting chart as ${format} with filename: ${filename}`) // Implementation would go here // This is a placeholder for the actual export functionality } export function exportChartData( data: ChartDataPoint[], filename: string = 'chart-data', format: 'json' | 'csv' = 'json' ): void { let content: string let mimeType: string if (format === 'json') { content = JSON.stringify(data, null, 2) mimeType = 'application/json' } else { // CSV export const headers = Object.keys(data[0] || {}) const csvRows = [ headers.join(','), ...data.map(row => headers.map(header => { const value = row[header] return typeof value === 'string' && value.includes(',') ? `"${value}"` : value }).join(',') ) ] content = csvRows.join('\n') mimeType = 'text/csv' } const blob = new Blob([content], { type: mimeType }) const url = window.URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = `${filename}.${format}` document.body.appendChild(link) link.click() document.body.removeChild(link) window.URL.revokeObjectURL(url) } // Gradient color generator export function generateGradientColors(baseColor: string, count: number = 5): string[] { const colors: string[] = [] const rgb = hexToRgb(baseColor) if (!rgb) return [baseColor] for (let i = 0; i < count; i++) { const factor = i / (count - 1) const r = Math.round(rgb.r + (255 - rgb.r) * factor * 0.5) const g = Math.round(rgb.g + (255 - rgb.g) * factor * 0.5) const b = Math.round(rgb.b + (255 - rgb.b) * factor * 0.5) colors.push(`rgb(${r}, ${g}, ${b})`) } return colors } function hexToRgb(hex: string): { r: number; g: number; b: number } | null { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null } // Performance optimization helpers export function throttleChartData(data: ChartDataPoint[], maxPoints: number = 1000): ChartDataPoint[] { if (data.length <= maxPoints) return data const step = Math.ceil(data.length / maxPoints) return data.filter((_, index) => index % step === 0) } // Format large numbers export function formatChartValue(value: number): string { if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B` if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M` if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K` return value.toString() } // Calculate chart statistics export function calculateChartStats(data: ChartDataPoint[], keys: string[]): Record<string, { min: number; max: number; avg: number; total: number }> { const stats: Record<string, { min: number; max: number; avg: number; total: number }> = {} keys.forEach(key => { const values = data.map(point => Number(point[key]) || 0) stats[key] = { min: Math.min(...values), max: Math.max(...values), avg: values.reduce((sum, val) => sum + val, 0) / values.length, total: values.reduce((sum, val) => sum + val, 0) } }) return stats }