react-native-chart-kit
Version:
Beautiful React Native charts for dashboards, reports, and data-rich mobile apps.
105 lines (104 loc) • 3.66 kB
JavaScript
const fullCircle = Math.PI * 2;
const defaultStartAngle = -Math.PI / 2;
const clamp = (value, min, max) => {
if (max < min) {
return min;
}
return Math.min(Math.max(value, min), max);
};
const formatNumber = (value) => Number.isInteger(value) ? String(value) : Number(value.toFixed(4)).toString();
const polarToCartesian = ({ angle, centerX, centerY, radius }) => ({
x: centerX + radius * Math.cos(angle),
y: centerY + radius * Math.sin(angle)
});
const moveTo = (point) => `M ${formatNumber(point.x)} ${formatNumber(point.y)}`;
const arcTo = ({ end, largeArc, radius, sweep }) => `A ${formatNumber(radius)} ${formatNumber(radius)} 0 ${largeArc} ${sweep} ${formatNumber(end.x)} ${formatNumber(end.y)}`;
const buildArcStrokePath = ({ centerX, centerY, endAngle, radius, startAngle }) => {
const span = Math.max(0, endAngle - startAngle);
if (radius <= 0 || span <= 0) {
return "";
}
const isFullCircle = span >= fullCircle - 0.0001;
const start = polarToCartesian({
angle: startAngle,
centerX,
centerY,
radius
});
const mid = polarToCartesian({
angle: startAngle + Math.PI,
centerX,
centerY,
radius
});
const end = polarToCartesian({
angle: isFullCircle ? startAngle + fullCircle : endAngle,
centerX,
centerY,
radius
});
if (isFullCircle) {
return [
moveTo(start),
arcTo({ end: mid, largeArc: 1, radius, sweep: 1 }),
arcTo({ end: start, largeArc: 1, radius, sweep: 1 })
].join(" ");
}
return [
moveTo(start),
arcTo({
end,
largeArc: span > Math.PI ? 1 : 0,
radius,
sweep: 1
})
].join(" ");
};
export const buildProgressRings = ({ rings, centerX, centerY, maxRadius, strokeWidth, ringGap = 8, startAngle = defaultStartAngle }) => {
const safeMaxRadius = Number.isFinite(maxRadius) && maxRadius > 0 ? maxRadius : 0;
const safeStrokeWidth = Number.isFinite(strokeWidth) && strokeWidth > 0 ? strokeWidth : 0;
const safeRingGap = Number.isFinite(ringGap) && ringGap > 0 ? ringGap : 0;
const safeStartAngle = Number.isFinite(startAngle)
? startAngle
: defaultStartAngle;
return rings.map((ring, index) => {
const radius = Math.max(0, safeMaxRadius - index * (safeStrokeWidth + safeRingGap));
const value = ring.defined ? ring.value : null;
const clampedValue = value !== null && Number.isFinite(value) ? clamp(value, 0, 1) : 0;
const endAngle = safeStartAngle + fullCircle * clampedValue;
const backgroundPath = buildArcStrokePath({
centerX,
centerY,
endAngle: safeStartAngle + fullCircle,
radius,
startAngle: safeStartAngle
});
const path = buildArcStrokePath({
centerX,
centerY,
endAngle,
radius,
startAngle: safeStartAngle
});
const defined = ring.defined && safeStrokeWidth > 0 && radius > 0 && path.length > 0;
const model = {
index: ring.index,
value,
clampedValue,
defined,
radius,
strokeWidth: safeStrokeWidth,
startAngle: safeStartAngle,
endAngle,
backgroundPath,
path
};
if (ring.label !== undefined) {
model.label = ring.label;
}
if (ring.color !== undefined) {
model.color = ring.color;
}
return model;
});
};