UNPKG

react-native-chart-kit

Version:

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

158 lines (157 loc) 6.21 kB
import { buildPieArcs, normalizeLegacyPieData } from "../../../core/index"; import { resolveCartesianChartThemeConfig } from "../../theme/presets"; import { buildPieChartArcLabels, getPieChartArcLabelHorizontalReserve, getPieChartArcLabelsVisible, resolvePieChartArcLabelsConfig } from "./arcLabels"; import { getPieChartActiveSliceGutter, resolvePieChartActiveSliceConfig, shouldReservePieChartActiveSliceGutter } from "./activeSlice"; import { getPieChartSliceSeparatorGutter, resolvePieChartSliceSeparatorConfig } from "./sliceSeparator"; const defaultLabelKey = "name"; const defaultColorKey = "color"; const defaultLegendHeight = 54; const defaultFormatValue = (value) => String(value); const defaultFormatPercentage = (percentage) => `${Math.round(percentage * 100)}%`; const clamp = (value, min, max) => { if (max < min) { return min; } return Math.min(Math.max(value, min), max); }; const getStringValue = (value, fallback) => typeof value === "string" && value.length > 0 ? value : fallback; const getLegendVisible = (legend) => { if (legend === false) { return false; } if (typeof legend === "object") { return legend.visible !== false; } return true; }; const getLegendReservedHeight = (legend) => { if (!getLegendVisible(legend)) { return 0; } const config = typeof legend === "object" ? legend : {}; return typeof config.reservedHeight === "number" && Number.isFinite(config.reservedHeight) ? Math.max(0, config.reservedHeight) : defaultLegendHeight; }; const normalizePieInput = ({ colorKey, colors, data, labelKey, valueKey }) => data.map((item, index) => { const label = getStringValue(item[labelKey ?? defaultLabelKey], `Slice ${index + 1}`); const color = getStringValue(item[colorKey ?? defaultColorKey], colors?.[index] ?? ""); const normalized = { ...item, name: label, [valueKey]: item[valueKey] }; if (color) { normalized.color = color; } return normalized; }); export const buildPieChartModel = ({ chartKitTheme, props, selectedIndex }) => { const { arcLabels, colors, colorKey, data, formatPercentage = defaultFormatPercentage, formatValue = defaultFormatValue, height, innerRadius, innerRadiusRatio, labelKey, legend, preset, theme, valueKey, width } = props; const resolvedTheme = resolveCartesianChartThemeConfig({ mode: typeof theme === "string" && theme !== "system" ? theme : chartKitTheme.mode, preset: preset ?? chartKitTheme.preset, presets: chartKitTheme.presets, theme: typeof theme === "object" ? theme : chartKitTheme.theme }); const legendVisible = getLegendVisible(legend); const legendReservedHeight = getLegendReservedHeight(legend); const sliceSeparator = resolvePieChartSliceSeparatorConfig({ backgroundColor: resolvedTheme.background, sliceSeparator: props.sliceSeparator }); const arcLabelConfig = resolvePieChartArcLabelsConfig(arcLabels); const arcLabelsVisible = getPieChartArcLabelsVisible(arcLabels); const chartHeight = Math.max(120, height - legendReservedHeight); const centerX = width / 2; const centerY = chartHeight / 2; const arcLabelHorizontalReserve = arcLabelsVisible ? getPieChartArcLabelHorizontalReserve(arcLabelConfig) : 0; const arcLabelVerticalReserve = arcLabelsVisible ? arcLabelConfig.reservedWidth : 0; const sliceSeparatorGutter = getPieChartSliceSeparatorGutter(sliceSeparator); const availableRadius = Math.max(0, Math.min(Math.max(0, width - arcLabelHorizontalReserve * 2), Math.max(0, chartHeight - arcLabelVerticalReserve * 0.48)) / 2 - 6 - sliceSeparatorGutter); const activeSlice = resolvePieChartActiveSliceConfig({ activeSlice: props.activeSlice, backgroundColor: resolvedTheme.background }); const activeSliceGutter = shouldReservePieChartActiveSliceGutter({ props, selectedIndex }) ? getPieChartActiveSliceGutter({ activeSlice, radius: availableRadius }) : 0; const radius = Math.max(0, availableRadius - activeSliceGutter); const resolvedInnerRadius = typeof innerRadius === "number" && Number.isFinite(innerRadius) ? innerRadius : radius * clamp(innerRadiusRatio ?? 0, 0, 0.92); const normalized = normalizeLegacyPieData(normalizePieInput({ colorKey, colors, data, labelKey, valueKey }), { accessor: valueKey }); const arcs = buildPieArcs({ slices: normalized.slices, centerX, centerY, radius, innerRadius: resolvedInnerRadius }).map((arc, index) => { const { color: _color, raw: _raw, ...rest } = arc; const color = arc.color ?? resolvedTheme.series[index % resolvedTheme.series.length] ?? resolvedTheme.text; const raw = data[arc.index]; return { ...rest, color, ...(raw !== undefined ? { raw } : {}) }; }); const total = arcs.reduce((sum, arc) => (arc.value !== null ? sum + arc.value : sum), 0); const legendItems = arcs .filter((arc) => arc.defined) .map((arc) => ({ index: arc.index, key: `${arc.index}-${arc.label}`, label: arc.label, valueLabel: formatValue(arc.value ?? 0), percentageLabel: formatPercentage(arc.percentage), color: arc.color ?? resolvedTheme.series[0] ?? resolvedTheme.text, arc })); const resolvedArcLabels = buildPieChartArcLabels({ arcs, centerX, centerY, chartHeight, chartWidth: width, config: arcLabelConfig, legendItems, radius, selectedIndex, theme: resolvedTheme }); return { arcLabels: resolvedArcLabels, arcLabelsVisible, arcs, centerX, centerY, chartHeight, innerRadius: resolvedInnerRadius, legendItems, legendVisible, radius, resolvedTheme, sliceSeparator, total }; };