advanced-games-library
Version:
Advanced Gaming Library for React Native - Four Complete Games with iOS Compatibility Fixes
826 lines (696 loc) • 24.1 kB
text/typescript
/**
* Advanced Visualization System - מערכת ויזואליזציה מתקדמת
* מספקת דשבורדים אינטראקטיביים, גרפים בזמן אמת וויזואליזציות מתקדמות
*/
export interface VisualizationConfig {
theme: 'light' | 'dark' | 'auto';
animations: {
enabled: boolean;
duration: number;
easing: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
};
performance: {
maxDataPoints: number;
refreshRate: number; // ms
enableWebGL: boolean;
enableWorkers: boolean;
};
accessibility: {
highContrast: boolean;
reducedMotion: boolean;
screenReaderSupport: boolean;
};
}
export interface ChartData {
id: string;
name: string;
data: DataPoint[];
type: 'line' | 'bar' | 'pie' | 'scatter' | 'heatmap' | 'radar';
color?: string;
metadata?: {
unit?: string;
format?: string;
description?: string;
};
}
export interface DataPoint {
x: number | string | Date;
y: number;
label?: string;
metadata?: any;
}
export interface DashboardWidget {
id: string;
title: string;
type: 'chart' | 'metric' | 'table' | 'heatmap' | 'gauge' | 'text' | 'custom';
position: { x: number; y: number; width: number; height: number };
data: ChartData[] | any;
config: {
refreshInterval?: number;
autoUpdate?: boolean;
interactive?: boolean;
exportable?: boolean;
filters?: FilterConfig[];
};
styling: {
backgroundColor?: string;
borderColor?: string;
textColor?: string;
fontSize?: number;
padding?: number;
};
}
export interface FilterConfig {
id: string;
type: 'date' | 'select' | 'range' | 'search';
label: string;
options?: any[];
defaultValue?: any;
}
export interface Dashboard {
id: string;
name: string;
description?: string;
widgets: DashboardWidget[];
layout: 'grid' | 'free' | 'responsive';
filters: FilterConfig[];
metadata: {
created: number;
updated: number;
author?: string;
version: string;
tags?: string[];
};
}
export interface RealTimeDataSource {
id: string;
name: string;
type: 'websocket' | 'polling' | 'sse' | 'manual';
endpoint?: string;
interval?: number;
transform?: (data: any) => ChartData[];
filters?: (data: any) => boolean;
}
export interface VisualizationExport {
format: 'png' | 'svg' | 'pdf' | 'json' | 'csv';
quality?: 'low' | 'medium' | 'high';
dimensions?: { width: number; height: number };
includeLegend?: boolean;
includeTitle?: boolean;
}
class AdvancedVisualizationSystem {
private config: VisualizationConfig;
private dashboards: Map<string, Dashboard> = new Map();
private dataSources: Map<string, RealTimeDataSource> = new Map();
private activeSubscriptions: Map<string, any> = new Map();
private renderingContext: any = null;
private animationFrameId?: number;
private worker?: Worker;
private performanceMetrics = {
renderTime: 0,
dataPoints: 0,
fps: 0,
memoryUsage: 0
};
constructor(config?: Partial<VisualizationConfig>) {
this.config = {
theme: 'auto',
animations: {
enabled: true,
duration: 300,
easing: 'ease-in-out'
},
performance: {
maxDataPoints: 10000,
refreshRate: 1000,
enableWebGL: true,
enableWorkers: true
},
accessibility: {
highContrast: false,
reducedMotion: false,
screenReaderSupport: true
},
...config
};
this.initializeVisualizationSystem();
console.log('🎨 Advanced Visualization System initialized');
}
/**
* אתחול מערכת הויזואליזציה
*/
private async initializeVisualizationSystem(): Promise<void> {
this.initializeRenderingContext();
if (this.config.performance.enableWorkers) {
this.initializeWorkers();
}
this.setupPerformanceMonitoring();
this.setupThemeDetection();
this.setupAccessibilityFeatures();
}
/**
* אתחול rendering context
*/
private initializeRenderingContext(): void {
if (typeof window !== 'undefined' && this.config.performance.enableWebGL) {
try {
const canvas = document.createElement('canvas');
this.renderingContext = canvas.getContext('webgl2') || canvas.getContext('webgl');
if (this.renderingContext) {
console.log('🎨 WebGL rendering context initialized');
} else {
console.log('🎨 Falling back to 2D rendering context');
this.renderingContext = canvas.getContext('2d');
}
} catch (error) {
console.warn('WebGL initialization failed:', error);
}
}
}
/**
* אתחול Web Workers
*/
private initializeWorkers(): void {
if (typeof Worker !== 'undefined') {
try {
const workerCode = `
self.onmessage = function(e) {
const { type, data } = e.data;
switch (type) {
case 'PROCESS_DATA':
const processed = processChartData(data);
self.postMessage({ type: 'DATA_PROCESSED', data: processed });
break;
case 'CALCULATE_STATISTICS':
const stats = calculateStatistics(data);
self.postMessage({ type: 'STATISTICS_CALCULATED', data: stats });
break;
}
};
function processChartData(data) {
return data.map(point => ({
...point,
processed: true,
timestamp: Date.now()
}));
}
function calculateStatistics(data) {
const values = data.map(d => d.y);
return {
mean: values.reduce((a, b) => a + b, 0) / values.length,
min: Math.min(...values),
max: Math.max(...values),
count: values.length
};
}
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
this.worker = new Worker(URL.createObjectURL(blob));
this.worker.onmessage = (e) => {
this.handleWorkerMessage(e.data);
};
console.log('🛠️ Visualization worker initialized');
} catch (error) {
console.warn('Worker initialization failed:', error);
}
}
}
private handleWorkerMessage(message: any): void {
switch (message.type) {
case 'DATA_PROCESSED':
console.log('🔄 Visualizations updated with processed data');
break;
case 'STATISTICS_CALCULATED':
console.log('📈 Statistics displays updated', message.data);
break;
}
}
private setupPerformanceMonitoring(): void {
let frameCount = 0;
let lastTime = performance.now();
const measurePerformance = () => {
const currentTime = performance.now();
frameCount++;
if (currentTime - lastTime >= 1000) {
this.performanceMetrics.fps = frameCount;
frameCount = 0;
lastTime = currentTime;
if ((performance as any).memory) {
this.performanceMetrics.memoryUsage = (performance as any).memory.usedJSHeapSize;
}
}
this.animationFrameId = requestAnimationFrame(measurePerformance);
};
measurePerformance();
}
private setupThemeDetection(): void {
if (this.config.theme === 'auto' && typeof window !== 'undefined') {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const updateTheme = () => {
const isDark = mediaQuery.matches;
this.applyTheme(isDark ? 'dark' : 'light');
};
mediaQuery.addListener(updateTheme);
updateTheme();
}
}
private applyTheme(theme: 'light' | 'dark'): void {
const themeColors = {
light: {
background: '#ffffff',
surface: '#f5f5f5',
primary: '#2196f3',
text: '#212121',
textSecondary: '#757575'
},
dark: {
background: '#121212',
surface: '#1e1e1e',
primary: '#64b5f6',
text: '#ffffff',
textSecondary: '#b0b0b0'
}
};
this.dashboards.forEach(dashboard => {
dashboard.widgets.forEach(widget => {
widget.styling.backgroundColor = themeColors[theme].surface;
widget.styling.textColor = themeColors[theme].text;
});
});
}
private setupAccessibilityFeatures(): void {
if (typeof window !== 'undefined') {
const reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
if (reducedMotionQuery.matches) {
this.config.accessibility.reducedMotion = true;
this.config.animations.enabled = false;
}
const highContrastQuery = window.matchMedia('(prefers-contrast: high)');
if (highContrastQuery.matches) {
this.config.accessibility.highContrast = true;
}
}
}
/**
* יצירת דשבורד חדש
*/
createDashboard(config: {
name: string;
description?: string;
layout?: Dashboard['layout'];
author?: string;
tags?: string[];
}): string {
const dashboardId = `dashboard_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const dashboard: Dashboard = {
id: dashboardId,
name: config.name,
description: config.description,
widgets: [],
layout: config.layout || 'grid',
filters: [],
metadata: {
created: Date.now(),
updated: Date.now(),
author: config.author,
version: '1.0.0',
tags: config.tags
}
};
this.dashboards.set(dashboardId, dashboard);
console.log(`📊 Dashboard created: ${config.name}`);
return dashboardId;
}
/**
* הוספת widget לדשבורד
*/
addWidget(dashboardId: string, widget: Omit<DashboardWidget, 'id'>): string {
const dashboard = this.dashboards.get(dashboardId);
if (!dashboard) {
throw new Error(`Dashboard not found: ${dashboardId}`);
}
const widgetId = `widget_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const fullWidget: DashboardWidget = {
id: widgetId,
...widget
};
dashboard.widgets.push(fullWidget);
dashboard.metadata.updated = Date.now();
if (fullWidget.config.autoUpdate && fullWidget.config.refreshInterval) {
this.setupWidgetAutoRefresh(dashboardId, widgetId);
}
console.log(`📋 Widget added: ${widget.title}`);
return widgetId;
}
private setupWidgetAutoRefresh(dashboardId: string, widgetId: string): void {
const dashboard = this.dashboards.get(dashboardId);
const widget = dashboard?.widgets.find(w => w.id === widgetId);
if (!widget || !widget.config.refreshInterval) return;
const intervalId = setInterval(() => {
this.refreshWidget(dashboardId, widgetId);
}, widget.config.refreshInterval);
this.activeSubscriptions.set(`${dashboardId}_${widgetId}`, intervalId);
}
private async refreshWidget(dashboardId: string, widgetId: string): Promise<void> {
try {
const dashboard = this.dashboards.get(dashboardId);
const widget = dashboard?.widgets.find(w => w.id === widgetId);
if (!widget) return;
switch (widget.type) {
case 'chart':
await this.refreshChartWidget(widget);
break;
case 'metric':
await this.refreshMetricWidget(widget);
break;
case 'table':
await this.refreshTableWidget(widget);
break;
}
console.log(`🔄 Widget refreshed: ${widget.title}`);
} catch (error) {
console.error('Widget refresh failed:', error);
}
}
private async refreshChartWidget(widget: DashboardWidget): Promise<void> {
const newData = this.generateMockChartData(widget.data[0]?.type || 'line');
widget.data = [newData];
if (this.worker) {
this.worker.postMessage({
type: 'PROCESS_DATA',
data: newData.data
});
}
}
private generateMockChartData(type: ChartData['type']): ChartData {
const dataPoints: DataPoint[] = [];
const pointCount = Math.min(100, this.config.performance.maxDataPoints);
for (let i = 0; i < pointCount; i++) {
dataPoints.push({
x: Date.now() - (pointCount - i) * 60000,
y: Math.random() * 100 + Math.sin(i * 0.1) * 20,
label: `Point ${i}`
});
}
return {
id: `mock_data_${Date.now()}`,
name: 'Mock Data',
data: dataPoints,
type,
color: '#2196f3'
};
}
private async refreshMetricWidget(widget: DashboardWidget): Promise<void> {
widget.data = {
value: Math.floor(Math.random() * 1000),
change: (Math.random() - 0.5) * 20,
trend: Math.random() > 0.5 ? 'up' : 'down',
unit: 'units',
timestamp: Date.now()
};
}
private async refreshTableWidget(widget: DashboardWidget): Promise<void> {
const rows = [];
for (let i = 0; i < 10; i++) {
rows.push({
id: i,
name: `Item ${i}`,
value: Math.floor(Math.random() * 100),
status: Math.random() > 0.5 ? 'active' : 'inactive',
timestamp: Date.now()
});
}
widget.data = {
columns: ['ID', 'Name', 'Value', 'Status', 'Timestamp'],
rows
};
}
/**
* הגדרת מקור נתונים בזמן אמת
*/
addRealTimeDataSource(source: RealTimeDataSource): void {
this.dataSources.set(source.id, source);
switch (source.type) {
case 'polling':
this.setupPollingDataSource(source);
break;
case 'websocket':
this.setupWebSocketDataSource(source);
break;
case 'sse':
this.setupSSEDataSource(source);
break;
}
console.log(`🌍 Real-time data source added: ${source.name}`);
}
private setupPollingDataSource(source: RealTimeDataSource): void {
if (!source.endpoint || !source.interval) return;
const intervalId = setInterval(async () => {
try {
const response = await fetch(source.endpoint!);
const data = await response.json();
if (source.filters && !source.filters(data)) return;
const transformedData = source.transform ? source.transform(data) : data;
this.updateWidgetsWithData(source.id, transformedData);
} catch (error) {
console.error(`Polling data source error (${source.id}):`, error);
}
}, source.interval);
this.activeSubscriptions.set(`datasource_${source.id}`, intervalId);
}
private setupWebSocketDataSource(source: RealTimeDataSource): void {
if (!source.endpoint) return;
try {
const ws = new WebSocket(source.endpoint);
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (source.filters && !source.filters(data)) return;
const transformedData = source.transform ? source.transform(data) : data;
this.updateWidgetsWithData(source.id, transformedData);
} catch (error) {
console.error(`WebSocket data parsing error (${source.id}):`, error);
}
};
this.activeSubscriptions.set(`datasource_${source.id}`, ws);
} catch (error) {
console.error(`WebSocket setup error (${source.id}):`, error);
}
}
private setupSSEDataSource(source: RealTimeDataSource): void {
if (!source.endpoint) return;
try {
const eventSource = new EventSource(source.endpoint);
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (source.filters && !source.filters(data)) return;
const transformedData = source.transform ? source.transform(data) : data;
this.updateWidgetsWithData(source.id, transformedData);
} catch (error) {
console.error(`SSE data parsing error (${source.id}):`, error);
}
};
this.activeSubscriptions.set(`datasource_${source.id}`, eventSource);
} catch (error) {
console.error(`SSE setup error (${source.id}):`, error);
}
}
private updateWidgetsWithData(sourceId: string, data: ChartData[]): void {
this.dashboards.forEach(dashboard => {
dashboard.widgets.forEach(widget => {
if (widget.config.autoUpdate) {
widget.data = data;
console.log(`🔄 Widget re-rendered: ${widget.title}`);
}
});
});
}
/**
* ייצוא דשבורד או widget
*/
async exportVisualization(
dashboardId: string,
widgetId?: string,
exportConfig?: VisualizationExport
): Promise<Blob> {
const config: VisualizationExport = {
format: 'png',
quality: 'high',
dimensions: { width: 1920, height: 1080 },
includeLegend: true,
includeTitle: true,
...exportConfig
};
try {
if (widgetId) {
return await this.exportWidget(dashboardId, widgetId, config);
} else {
return await this.exportDashboard(dashboardId, config);
}
} catch (error) {
console.error('Export failed:', error);
throw new Error('Visualization export failed');
}
}
private async exportWidget(dashboardId: string, widgetId: string, config: VisualizationExport): Promise<Blob> {
const dashboard = this.dashboards.get(dashboardId);
const widget = dashboard?.widgets.find(w => w.id === widgetId);
if (!widget) {
throw new Error('Widget not found');
}
const canvas = document.createElement('canvas');
canvas.width = config.dimensions!.width;
canvas.height = config.dimensions!.height;
const ctx = canvas.getContext('2d')!;
await this.renderWidgetToCanvas(widget, ctx, config);
return new Promise((resolve) => {
canvas.toBlob((blob) => {
resolve(blob!);
}, `image/${config.format}`, config.quality === 'high' ? 1 : config.quality === 'medium' ? 0.8 : 0.6);
});
}
private async renderWidgetToCanvas(widget: DashboardWidget, ctx: CanvasRenderingContext2D, config: VisualizationExport): Promise<void> {
ctx.fillStyle = widget.styling.backgroundColor || '#ffffff';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
if (config.includeTitle) {
ctx.fillStyle = widget.styling.textColor || '#000000';
ctx.font = `${widget.styling.fontSize || 16}px Arial`;
ctx.textAlign = 'center';
ctx.fillText(widget.title, ctx.canvas.width / 2, 30);
}
switch (widget.type) {
case 'chart':
await this.renderChartToCanvas(widget, ctx, config);
break;
case 'metric':
await this.renderMetricToCanvas(widget, ctx, config);
break;
}
}
private async renderChartToCanvas(widget: DashboardWidget, ctx: CanvasRenderingContext2D, config: VisualizationExport): Promise<void> {
const chartData = widget.data[0] as ChartData;
if (!chartData || !chartData.data.length) return;
const padding = 60;
const chartWidth = ctx.canvas.width - 2 * padding;
const chartHeight = ctx.canvas.height - 2 * padding - (config.includeTitle ? 50 : 0);
const startY = config.includeTitle ? 80 : 60;
// Draw chart based on type
if (chartData.type === 'line') {
await this.drawLineChart(ctx, chartData, padding, startY, chartWidth, chartHeight);
}
}
private async drawLineChart(ctx: CanvasRenderingContext2D, chartData: ChartData, x: number, y: number, width: number, height: number): Promise<void> {
if (!chartData.data.length) return;
const minY = Math.min(...chartData.data.map(d => d.y));
const maxY = Math.max(...chartData.data.map(d => d.y));
const range = maxY - minY || 1;
ctx.strokeStyle = chartData.color || '#2196f3';
ctx.lineWidth = 2;
ctx.beginPath();
chartData.data.forEach((point, index) => {
const chartX = x + (index / (chartData.data.length - 1)) * width;
const chartY = y + height - ((point.y - minY) / range) * height;
if (index === 0) {
ctx.moveTo(chartX, chartY);
} else {
ctx.lineTo(chartX, chartY);
}
});
ctx.stroke();
}
private async renderMetricToCanvas(widget: DashboardWidget, ctx: CanvasRenderingContext2D, config: VisualizationExport): Promise<void> {
const metric = widget.data;
if (!metric) return;
const centerX = ctx.canvas.width / 2;
const centerY = ctx.canvas.height / 2;
ctx.fillStyle = widget.styling.textColor || '#000000';
ctx.font = 'bold 48px Arial';
ctx.textAlign = 'center';
ctx.fillText(metric.value.toString(), centerX, centerY);
}
private async exportDashboard(dashboardId: string, config: VisualizationExport): Promise<Blob> {
const dashboard = this.dashboards.get(dashboardId);
if (!dashboard) {
throw new Error('Dashboard not found');
}
const canvas = document.createElement('canvas');
canvas.width = config.dimensions!.width;
canvas.height = config.dimensions!.height;
const ctx = canvas.getContext('2d')!;
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (config.includeTitle) {
ctx.fillStyle = '#000000';
ctx.font = 'bold 32px Arial';
ctx.textAlign = 'center';
ctx.fillText(dashboard.name, canvas.width / 2, 40);
}
return new Promise((resolve) => {
canvas.toBlob((blob) => {
resolve(blob!);
}, `image/${config.format}`, config.quality === 'high' ? 1 : 0.8);
});
}
/**
* Public API Methods
*/
getDashboard(dashboardId: string): Dashboard | null {
return this.dashboards.get(dashboardId) || null;
}
getAllDashboards(): Dashboard[] {
return Array.from(this.dashboards.values());
}
updateWidget(dashboardId: string, widgetId: string, updates: Partial<DashboardWidget>): boolean {
const dashboard = this.dashboards.get(dashboardId);
const widget = dashboard?.widgets.find(w => w.id === widgetId);
if (!widget) return false;
Object.assign(widget, updates);
dashboard!.metadata.updated = Date.now();
console.log(`📋 Widget updated: ${widget.title}`);
return true;
}
removeWidget(dashboardId: string, widgetId: string): boolean {
const dashboard = this.dashboards.get(dashboardId);
if (!dashboard) return false;
const index = dashboard.widgets.findIndex(w => w.id === widgetId);
if (index === -1) return false;
dashboard.widgets.splice(index, 1);
dashboard.metadata.updated = Date.now();
const subscriptionKey = `${dashboardId}_${widgetId}`;
const subscription = this.activeSubscriptions.get(subscriptionKey);
if (subscription) {
clearInterval(subscription);
this.activeSubscriptions.delete(subscriptionKey);
}
console.log(`🗑️ Widget removed: ${widgetId}`);
return true;
}
getPerformanceMetrics(): typeof this.performanceMetrics {
return { ...this.performanceMetrics };
}
updateConfig(updates: Partial<VisualizationConfig>): void {
this.config = { ...this.config, ...updates };
console.log('⚙️ Visualization config updated');
}
dispose(): void {
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId);
}
if (this.worker) {
this.worker.terminate();
}
this.activeSubscriptions.forEach((subscription, key) => {
if (typeof subscription === 'number') {
clearInterval(subscription);
} else if (subscription.close) {
subscription.close();
}
});
this.dashboards.clear();
this.dataSources.clear();
this.activeSubscriptions.clear();
console.log('🎨 Advanced Visualization System disposed');
}
}
export default AdvancedVisualizationSystem;