@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
text/typescript
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
}