UNPKG

@dt-workspace/react-native-heatmap

Version:

A modern, highly customizable React Native heatmap component library inspired by GitHub's contribution calendar

304 lines (272 loc) 7.86 kB
"use strict"; /** * Virtualization utilities for React Native Heatmap * Provides efficient rendering for large datasets */ /** * Virtualization configuration */ /** * Default virtualization configuration */ export const DEFAULT_VIRTUALIZATION_CONFIG = { enabled: false, bufferSize: 50, threshold: 500, cellSize: 12, cellSpacing: 2 }; /** * Viewport information for virtualization */ /** * Visible cell range */ /** * Calculate visible cell range based on viewport */ export function calculateVisibleRange(cells, viewport, config) { if (!config.enabled || cells.length < config.threshold) { return { startIndex: 0, endIndex: cells.length - 1, visibleCells: cells }; } const cellWidth = config.cellSize + config.cellSpacing; const cellHeight = config.cellSize + config.cellSpacing; // Calculate visible bounds with buffer const visibleLeft = Math.max(0, viewport.offsetX - config.bufferSize * cellWidth); const visibleRight = viewport.offsetX + viewport.width + config.bufferSize * cellWidth; const visibleTop = Math.max(0, viewport.offsetY - config.bufferSize * cellHeight); const visibleBottom = viewport.offsetY + viewport.height + config.bufferSize * cellHeight; // Filter cells within visible bounds const visibleCells = cells.filter(cell => { const cellLeft = cell.x * cellWidth; const cellRight = cellLeft + cellWidth; const cellTop = cell.y * cellHeight; const cellBottom = cellTop + cellHeight; return cellRight >= visibleLeft && cellLeft <= visibleRight && cellBottom >= visibleTop && cellTop <= visibleBottom; }); // Find start and end indices const startIndex = cells.findIndex(cell => visibleCells.includes(cell)); const endIndex = startIndex + visibleCells.length - 1; return { startIndex: Math.max(0, startIndex), endIndex: Math.min(cells.length - 1, endIndex), visibleCells }; } /** * Calculate optimal virtualization settings based on dataset size */ export function calculateOptimalVirtualization(dataLength, cellSize, cellSpacing, _containerSize) { const baseConfig = { ...DEFAULT_VIRTUALIZATION_CONFIG }; // Enable virtualization for large datasets if (dataLength > 1000) { baseConfig.enabled = true; baseConfig.threshold = 500; baseConfig.bufferSize = 100; } else if (dataLength > 500) { baseConfig.enabled = true; baseConfig.threshold = 300; baseConfig.bufferSize = 50; } else { baseConfig.enabled = false; } baseConfig.cellSize = cellSize; baseConfig.cellSpacing = cellSpacing; return baseConfig; } /** * Lazy loading utility for progressive data loading */ export class LazyLoader { loadedChunks = new Set(); constructor(initialData, chunkSize = 100, loader) { this.data = initialData; this.chunkSize = chunkSize; this.loader = loader || this.defaultLoader; } defaultLoader = async (_startIndex, _endIndex) => { // Default implementation returns empty array return []; }; /** * Get data for a specific range, loading if necessary */ async getRange(startIndex, endIndex) { const result = []; for (let i = startIndex; i <= endIndex; i++) { const chunkIndex = Math.floor(i / this.chunkSize); if (!this.loadedChunks.has(chunkIndex)) { await this.loadChunk(chunkIndex); } const item = this.data[i]; if (item !== null && item !== undefined) { result.push(item); } } return result; } /** * Load a specific chunk of data */ async loadChunk(chunkIndex) { if (this.loadedChunks.has(chunkIndex)) { return; } const startIndex = chunkIndex * this.chunkSize; const endIndex = Math.min(startIndex + this.chunkSize - 1, this.data.length - 1); try { const chunkData = await this.loader(startIndex, endIndex); // Insert chunk data into the main array for (let i = 0; i < chunkData.length; i++) { const dataIndex = startIndex + i; const chunkItem = chunkData[i]; if (dataIndex < this.data.length && chunkItem !== undefined) { this.data[dataIndex] = chunkItem; } } this.loadedChunks.add(chunkIndex); } catch (error) { console.warn(`Failed to load chunk ${chunkIndex}:`, error); } } /** * Preload chunks around a specific range */ async preloadAround(centerIndex, radius = 2) { const centerChunk = Math.floor(centerIndex / this.chunkSize); const startChunk = Math.max(0, centerChunk - radius); const endChunk = Math.min(Math.floor(this.data.length / this.chunkSize), centerChunk + radius); const loadPromises = []; for (let chunkIndex = startChunk; chunkIndex <= endChunk; chunkIndex++) { loadPromises.push(this.loadChunk(chunkIndex)); } await Promise.all(loadPromises); } /** * Get total data length */ get length() { return this.data.length; } /** * Check if a chunk is loaded */ isChunkLoaded(chunkIndex) { return this.loadedChunks.has(chunkIndex); } /** * Clear loaded chunks to free memory */ clearCache() { this.loadedChunks.clear(); } } /** * Memory optimization utilities */ export class MemoryOptimizer { memoryUsage = new Map(); maxMemoryUsage = 50 * 1024 * 1024; // 50MB default static getInstance() { if (!MemoryOptimizer.instance) { MemoryOptimizer.instance = new MemoryOptimizer(); } return MemoryOptimizer.instance; } /** * Track memory usage for a component */ trackMemoryUsage(componentId, bytes) { this.memoryUsage.set(componentId, bytes); if (this.getTotalMemoryUsage() > this.maxMemoryUsage) { this.triggerGarbageCollection(); } } /** * Get total memory usage */ getTotalMemoryUsage() { return Array.from(this.memoryUsage.values()).reduce((sum, usage) => sum + usage, 0); } /** * Trigger garbage collection for least recently used items */ triggerGarbageCollection() { // Simple LRU implementation - remove oldest entries const entries = Array.from(this.memoryUsage.entries()); const toRemove = Math.ceil(entries.length * 0.3); // Remove 30% of entries for (let i = 0; i < toRemove; i++) { const entry = entries[i]; if (entry) { this.memoryUsage.delete(entry[0]); } } } /** * Clear memory tracking for a component */ clearMemoryTracking(componentId) { this.memoryUsage.delete(componentId); } /** * Set maximum memory usage threshold */ setMaxMemoryUsage(bytes) { this.maxMemoryUsage = bytes; } } /** * Performance monitoring utilities */ export class PerformanceMonitor { static measurements = new Map(); /** * Start performance measurement */ static startMeasurement(key) { const startTime = performance.now(); const measurements = this.measurements.get(key) || []; measurements.push(startTime); this.measurements.set(key, measurements); } /** * End performance measurement and return duration */ static endMeasurement(key) { const measurements = this.measurements.get(key) || []; if (measurements.length === 0) { return 0; } const startTime = measurements.pop(); const duration = performance.now() - startTime; return duration; } /** * Get average measurement for a key */ static getAverageMeasurement(key) { const measurements = this.measurements.get(key) || []; if (measurements.length === 0) { return 0; } const sum = measurements.reduce((acc, val) => acc + val, 0); return sum / measurements.length; } /** * Clear measurements */ static clearMeasurements(key) { if (key) { this.measurements.delete(key); } else { this.measurements.clear(); } } } //# sourceMappingURL=virtualization.js.map