react-native-chart-kit
Version:
Beautiful React Native charts for dashboards, reports, and data-rich mobile apps.
217 lines (216 loc) • 8.85 kB
JavaScript
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"
});
};