UNPKG

react-native-chart-kit

Version:

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

111 lines (110 loc) 4.18 kB
const isFiniteGeometryPoint = (point) => { return point.defined && Number.isFinite(point.x) && Number.isFinite(point.y); }; const appendUniquePoint = (points, point) => { if (points[points.length - 1] !== point) { points.push(point); } }; export const splitLinePathSegments = (points, connectNulls = false) => { if (connectNulls) { return [points.filter(isFiniteGeometryPoint)]; } const segments = []; let current = []; points.forEach((point) => { if (isFiniteGeometryPoint(point)) { current.push(point); return; } if (current.length > 0) { segments.push(current); current = []; } }); if (current.length > 0) { segments.push(current); } return segments; }; const getSegmentMinimumBudget = (segment) => { return segment.length > 1 ? 2 : segment.length; }; const decimateMinMaxSegment = (segment, maxPoints) => { if (segment.length <= maxPoints) { return segment; } if (maxPoints <= 1) { return segment.slice(0, 1); } const first = segment[0]; const last = segment[segment.length - 1]; const interior = segment.slice(1, -1); const selected = [first]; const interiorBudget = Math.max(0, maxPoints - 2); const bucketCount = Math.max(1, Math.ceil(interiorBudget / 2)); const bucketSize = interior.length / bucketCount; for (let bucketIndex = 0; bucketIndex < bucketCount; bucketIndex++) { const bucketStart = Math.floor(bucketIndex * bucketSize); const bucketEnd = bucketIndex === bucketCount - 1 ? interior.length : Math.floor((bucketIndex + 1) * bucketSize); const bucket = interior.slice(bucketStart, bucketEnd); if (bucket.length === 0) { continue; } let minIndex = 0; let maxIndex = 0; bucket.forEach((point, index) => { if (point.y < bucket[minIndex].y) { minIndex = index; } if (point.y > bucket[maxIndex].y) { maxIndex = index; } }); const extrema = minIndex <= maxIndex ? [bucket[minIndex], bucket[maxIndex]] : [bucket[maxIndex], bucket[minIndex]]; for (const point of extrema) { if (selected.length >= maxPoints - 1) { break; } appendUniquePoint(selected, point); } } appendUniquePoint(selected, last); return selected; }; const allocateSegmentBudgets = (segments, maxPoints) => { const minimumRequired = segments.reduce((sum, segment) => sum + getSegmentMinimumBudget(segment), 0); const effectiveMaxPoints = Math.max(maxPoints, minimumRequired); let remainingBudget = effectiveMaxPoints; let remainingPoints = segments.reduce((sum, segment) => sum + segment.length, 0); return segments.map((segment, index) => { if (index === segments.length - 1) { return Math.min(segment.length, remainingBudget); } const minimumBudget = getSegmentMinimumBudget(segment); const proportionalBudget = Math.round((segment.length / remainingPoints) * remainingBudget); const budget = Math.min(segment.length, Math.max(minimumBudget, proportionalBudget)); remainingBudget -= budget; remainingPoints -= segment.length; return budget; }); }; export const getDecimatedLinePathSegments = ({ connectNulls = false, decimation, points }) => { const segments = splitLinePathSegments(points, connectNulls).filter((segment) => segment.length > 0); if (!decimation || !Number.isFinite(decimation.maxPoints) || decimation.maxPoints <= 0) { return segments; } const totalPointCount = segments.reduce((sum, segment) => sum + segment.length, 0); const maxPoints = Math.floor(decimation.maxPoints); if (totalPointCount <= maxPoints) { return segments; } const budgets = allocateSegmentBudgets(segments, maxPoints); return segments.map((segment, index) => decimateMinMaxSegment(segment, budgets[index] ?? segment.length)); };