UNPKG

react-native-chart-kit

Version:

Beautiful React Native charts for dashboards, reports, and data-rich mobile apps.

217 lines (216 loc) 8.85 kB
const minVisiblePoints = 2; const millisecondsInOneDay = 24 * 60 * 60 * 1000; const presetDayCounts = { "1D": 1, "1W": 7, "1M": 30, "3M": 90, "6M": 180, "1Y": 365 }; const clamp = (value, min, max) => { if (max < min) { return min; } return Math.min(Math.max(value, min), max); }; const normalizePositiveInteger = (value, fallback, min = 1) => { if (typeof value !== "number" || !Number.isFinite(value)) { return fallback; } return Math.max(min, Math.floor(value)); }; export const resolveChartViewport = ({ itemCount, scrollable = false, viewportWidth, visiblePoints }) => { const safeViewportWidth = Number.isFinite(viewportWidth) && viewportWidth > 0 ? viewportWidth : 0; const safeItemCount = normalizePositiveInteger(itemCount, 0, 0); const fallbackVisiblePoints = Math.max(safeItemCount, minVisiblePoints); const normalizedVisiblePoints = clamp(normalizePositiveInteger(visiblePoints, fallbackVisiblePoints), minVisiblePoints, Math.max(safeItemCount, minVisiblePoints)); const shouldScroll = scrollable && safeViewportWidth > 0 && safeItemCount > normalizedVisiblePoints; const pointSpacing = normalizedVisiblePoints > 1 ? safeViewportWidth / (normalizedVisiblePoints - 1) : safeViewportWidth; const contentWidth = shouldScroll ? safeViewportWidth + (safeItemCount - normalizedVisiblePoints) * pointSpacing : safeViewportWidth; return { contentWidth, itemCount: safeItemCount, maxOffset: Math.max(0, contentWidth - safeViewportWidth), pointSpacing, scrollable: shouldScroll, viewportWidth: safeViewportWidth, visiblePoints: normalizedVisiblePoints }; }; export const resolveChartViewportInitialOffset = ({ initialIndex = "start", viewport }) => { if (!viewport.scrollable || initialIndex === "start") { return 0; } if (initialIndex === "end") { return viewport.maxOffset; } if (!Number.isFinite(initialIndex)) { return 0; } const index = clamp(Math.floor(initialIndex), 0, viewport.itemCount - 1); return clamp(index * viewport.pointSpacing, 0, viewport.maxOffset); }; export const resolveChartViewportWindow = ({ endIndex, initialIndex = "start", itemCount, startIndex, visiblePoints }) => { const safeItemCount = normalizePositiveInteger(itemCount, 0, 0); if (safeItemCount === 0) { return { endIndex: 0, isWindowed: false, itemCount: 0, startIndex: 0, visibleCount: 0 }; } if (startIndex !== undefined || endIndex !== undefined) { const normalizedStartIndex = clamp(normalizePositiveInteger(startIndex, 0, 0), 0, safeItemCount - 1); const normalizedEndIndex = clamp(normalizePositiveInteger(endIndex, safeItemCount, 0), normalizedStartIndex + 1, safeItemCount); return { endIndex: normalizedEndIndex, isWindowed: normalizedStartIndex > 0 || normalizedEndIndex < safeItemCount, itemCount: safeItemCount, startIndex: normalizedStartIndex, visibleCount: normalizedEndIndex - normalizedStartIndex }; } const normalizedVisiblePoints = clamp(normalizePositiveInteger(visiblePoints, safeItemCount), minVisiblePoints, safeItemCount); if (normalizedVisiblePoints >= safeItemCount) { return { endIndex: safeItemCount, isWindowed: false, itemCount: safeItemCount, startIndex: 0, visibleCount: safeItemCount }; } const startFromInitialIndex = initialIndex === "end" ? safeItemCount - normalizedVisiblePoints : typeof initialIndex === "number" && Number.isFinite(initialIndex) ? Math.floor(initialIndex) : 0; const normalizedStartIndex = clamp(startFromInitialIndex, 0, safeItemCount - normalizedVisiblePoints); return { endIndex: normalizedStartIndex + normalizedVisiblePoints, isWindowed: true, itemCount: safeItemCount, startIndex: normalizedStartIndex, visibleCount: normalizedVisiblePoints }; }; export const sliceChartViewportData = (data, window) => data.slice(window.startIndex, window.endIndex); export const resolveChartViewportIndexFromPosition = ({ itemCount, locationX, plotWidth, plotX }) => { const safeItemCount = normalizePositiveInteger(itemCount, 0, 0); if (safeItemCount === 0) { return 0; } const safePlotWidth = Number.isFinite(plotWidth) && plotWidth > 0 ? plotWidth : 1; const normalizedPosition = clamp((locationX - plotX) / safePlotWidth, 0, 1); return clamp(Math.round(normalizedPosition * (safeItemCount - 1)), 0, safeItemCount - 1); }; export const resolveChartViewportWindowFromPosition = ({ itemCount, locationX, plotWidth, plotX, visibleCount }) => { const safeItemCount = normalizePositiveInteger(itemCount, 0, 0); if (safeItemCount === 0) { return resolveChartViewportWindow({ itemCount: safeItemCount }); } const safeVisibleCount = clamp(normalizePositiveInteger(visibleCount, safeItemCount), minVisiblePoints, safeItemCount); if (safeVisibleCount >= safeItemCount) { return resolveChartViewportWindow({ itemCount: safeItemCount }); } const centerIndex = resolveChartViewportIndexFromPosition({ itemCount: safeItemCount, locationX, plotWidth, plotX }); const startIndex = clamp(centerIndex - Math.floor(safeVisibleCount / 2), 0, safeItemCount - safeVisibleCount); return resolveChartViewportWindow({ itemCount: safeItemCount, startIndex, endIndex: startIndex + safeVisibleCount }); }; export const resolveChartViewportWindowFromHandlePosition = ({ currentWindow, handle, itemCount, locationX, minVisibleCount, plotWidth, plotX }) => { const safeItemCount = normalizePositiveInteger(itemCount, 0, 0); if (safeItemCount === 0) { return resolveChartViewportWindow({ itemCount: safeItemCount }); } const normalizedCurrentWindow = resolveChartViewportWindow({ itemCount: safeItemCount, startIndex: currentWindow.startIndex, endIndex: currentWindow.endIndex }); const safeMinVisibleCount = clamp(normalizePositiveInteger(minVisibleCount, minVisiblePoints), minVisiblePoints, safeItemCount); const targetIndex = resolveChartViewportIndexFromPosition({ itemCount: safeItemCount, locationX, plotWidth, plotX }); if (handle === "start") { const startIndex = clamp(targetIndex, 0, normalizedCurrentWindow.endIndex - safeMinVisibleCount); return resolveChartViewportWindow({ itemCount: safeItemCount, startIndex, endIndex: normalizedCurrentWindow.endIndex }); } const endIndex = clamp(targetIndex + 1, normalizedCurrentWindow.startIndex + safeMinVisibleCount, safeItemCount); return resolveChartViewportWindow({ itemCount: safeItemCount, startIndex: normalizedCurrentWindow.startIndex, endIndex }); }; const isFiniteDate = (value) => value instanceof Date && Number.isFinite(value.valueOf()); const getPresetPointFallback = (preset, itemCount) => { if (preset === "ALL") { return itemCount; } if (preset === "YTD") { return Math.min(itemCount, 252); } return Math.min(itemCount, presetDayCounts[preset] ?? itemCount); }; const getStartDateForPreset = (preset, lastDate) => { if (preset === "ALL") { return undefined; } if (preset === "YTD") { return new Date(lastDate.getFullYear(), 0, 1); } const days = presetDayCounts[preset]; if (days === undefined) { return undefined; } return new Date(lastDate.valueOf() - days * millisecondsInOneDay); }; export const resolveChartViewportPresetWindow = ({ preset, xValues }) => { const itemCount = xValues.length; if (preset === "ALL") { return resolveChartViewportWindow({ itemCount }); } const lastValue = xValues[itemCount - 1]; if (lastValue && isFiniteDate(lastValue)) { const startDate = getStartDateForPreset(preset, lastValue); if (startDate) { const startIndex = xValues.findIndex((value) => isFiniteDate(value) && value.valueOf() >= startDate.valueOf()); return resolveChartViewportWindow({ itemCount, startIndex: startIndex >= 0 ? startIndex : itemCount - 1, endIndex: itemCount }); } } return resolveChartViewportWindow({ itemCount, visiblePoints: getPresetPointFallback(preset, itemCount), initialIndex: "end" }); };