@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
JavaScript
"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