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