react-native-chart-kit's render layer carries the highest structural debt — 5 functions to address first

Four of the five highest-risk functions in react-native-chart-kit are long render methods sitting untouched for over 30 days, accumulating structural debt that will cost the next contributor dearly.

Stephen Collins ·
Generated by hotspots · free & open source
Activity Risk8.72Low
Hottest Functionrender

Antipatterns Detected

long_function4

Run this on your own codebase

Hotspots runs locally in under a minute — no account, no data leaves your machine.

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is a long function and why does it matter in react-native-chart-kit?

A long function is one that handles multiple distinct responsibilities without decomposing them into smaller, named sub-functions — it grows by accumulation rather than design. The concrete problems are threefold: it's hard to test in isolation because any test must exercise the entire function to reach a specific branch; it's hard to read because a reviewer must hold the full context in working memory to understand any single part; and it's hard to change safely because an edit to one concern (say, legend positioning) sits adjacent to unrelated code (say, value formatting), increasing the chance of accidental interference. In react-native-chart-kit, all four flagged long functions in the `debt` quadrant are render methods that mix layout arithmetic, data transformation, and conditional JSX assembly in the same body — the `render` in `LineChart.tsx` alone destructures roughly 20 props before making its first conditional decision.

How do I reduce cyclomatic complexity in TypeScript?

The most effective technique is extract-method: identify each independent conditional branch and move it into a named function with a clear return type, leaving the original method as an orchestrator of named steps. A cyclomatic complexity above 10 is a signal to start extracting; above 15 — like `render` in `LineChart.tsx` at CC 17 — it warrants immediate decomposition before the next feature is added. A concrete first step for `LineChart.tsx` would be to extract the segment-count calculation (`Math.min(...datas) === Math.max(...datas) ? 1 : 4`, overridden by `segments`) into a `getSegmentCount(datas, segments)` pure function — that alone removes one branch from render and produces a testable unit. In TypeScript specifically, watch for implicit complexity added by type narrowing inside conditional branches: a branch that narrows a union type is an additional execution path that CC doesn't count but a reader must reason about.

Is react-native-chart-kit actively maintained?

Based on the data from commit `bc0b8af`, none of the five highest-risk functions were touched in the last 30 days — zero touches in that window across all of them. Four of those functions (`render` in `PieChart.tsx`, `render` in `ProgressChart.tsx`, `getClassNameForIndex` in `ContributionGraph.tsx`, and `render` in `StackedBarChart.tsx`) sit in the `debt` quadrant, last changed 32 days ago. The `fire`-quadrant `render` in `LineChart.tsx` was last modified 30 days ago and has accumulated 13 total commits over its history, with 38% of those tagged as bug fixes, which suggests periods of active development in the past. The fair characterisation is that the library appears to be in a low-activity phase at this snapshot — structural debt has accumulated in the `debt`-quadrant functions, but that's not the same as abandoned; the commit history shows the codebase has been actively worked before and could resume that pace.

How do I reproduce this analysis?

The analysis was run against commit `bc0b8af` of `indiespirit/react-native-chart-kit` using the Hotspots CLI, available at github.com/hotspots-dev/hotspots. After checking out the repository and running `git checkout bc0b8af`, execute `hotspots analyze . --mode snapshot --explain-patterns --force` from the repository root to reproduce the results. The same command works on any local git repository without additional configuration.

What does activity_risk mean?

Activity_risk combines structural complexity — derived from cyclomatic complexity, maximum nesting depth, and fan-out — with how frequently a function has been touched by recent commits. The intuition is that a structurally complex function no one has touched in two years poses a lower near-term regression risk than a moderately complex function being edited every week, because the dormant one isn't currently being changed and therefore can't currently introduce bugs. In react-native-chart-kit, the top-ranked function scores 8.72 despite a cyclomatic complexity of only 5, because its structural weight — length, nesting depth of 3, and fan-out of 5 together — is high relative to its activity. The `fire`-quadrant `render` in `LineChart.tsx` scores 6.66 despite a cyclomatic complexity of 17, reflecting that its recent dormancy (last changed 30 days ago, zero touches in 30 days) tempers what would otherwise be a much higher score. The goal of the metric is to surface where refactoring effort reduces the probability of bugs being introduced right now, not just where the code is abstractly complicated.

The dominant risk pattern in indiespirit/react-native-chart-kit is structural debt concentrated in render methods, not active churn. The top-ranked function — render in src/PieChart.tsx — hasn’t been touched in 32 days and carries an activity_risk of 8.72, which means the structural weight it carries will land hard on whoever next has to change it. Across 52 analysed functions, four of the five high-band entries are long render methods sitting in the debt quadrant with zero touches in the last 30 days; only render in LineChart.tsx sits in the fire quadrant, last modified 30 days ago. I’d start with PieChart.tsx and LineChart.tsx given the combination of flag-heavy prop destructuring, inline layout arithmetic, and, in LineChart’s case, a cyclomatic complexity of 17 inside a function that has seen 13 total commits with 38% tagged as bug fixes.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
rendersrc/PieChart.tsx8.7535
rendersrc/ProgressChart.tsx8.7719
getClassNameForIndexsrc/contribution-graph/ContributionGraph.tsx6.9632
rendersrc/line-chart/LineChart.tsx6.71715
rendersrc/StackedBarChart.tsx6.3921

Quadrant overview

Triage Band Distribution
Fire1Debt4Watch3OK44

52 functions analyzed

44 of the 52 analysed functions are in the ok quadrant — low complexity, inactive. The concern is the cluster of 4 debt-quadrant functions and 1 fire-quadrant function, all in the high band. The debt quadrant means high structural complexity with no recent activity: these are not emergencies today, but they carry blast-radius risk. Whoever touches them next inherits whatever assumptions were baked in during the last edit, with no recent test or review context to catch regressions.

Detected Antipatterns
Long Function×4Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.

Every flagged function carries the long_function pattern. That single pattern, repeated across four chart components, tells me the library grew its rendering logic by accretion — conditions, layout calculations, and JSX assembly were added incrementally into the same method rather than extracted. The right response is extract-method refactoring, breaking each render into named sub-functions that can be read, tested, and changed in isolation.


Top 5 analysis

render — PieChart.tsx

render
src/PieChart.tsx
8.72
high
CC 5
ND 3
FO 5
touches/30d 0

This is the highest-ranked function in the repository with an activity_risk of 8.72, and it sits in the debt quadrant — last changed 32 days ago with zero touches in the past 30 days. Looking at the source excerpt, the method does several distinct things in one pass: it calls a Pie layout engine, computes a total by reducing over the data array, then maps over chart.curves to produce JSX slice elements that include conditional legend rectangles and text labels — with inline positional arithmetic baked directly into each attribute. The nesting depth of 3 comes from the absolute / total === 0 / avoidFalseZero branch chain inside the map callback, which produces the display value string. Each branch is a distinct execution path, and the current cyclomatic complexity of 5 understates the real cognitive load because TypeScript’s type narrowing on value — which starts as string but is assigned 0 + "%" in one branch — adds an implicit path that the compiler must reconcile.

The long_function flag is the key signal here. The percentage-string logic, the legend rect/text pair, and the slice path rendering are three separable concerns that could each be their own named function. I’d extract the value-formatting branch first — it’s the most likely source of an edge-case bug (avoidFalseZero && percentage === 0 sits inside a total === 0 guard, and the two interact in ways that aren’t obvious at a glance). File-level signals show 4 total commits and no bug-linked commits or reverts, so there’s no historical defect signal here — this is maintainability debt, not a known bug surface.


render — ProgressChart.tsx

render
src/ProgressChart.tsx
8.68
high
CC 7
ND 1
FO 9
touches/30d 0

Also in the debt quadrant, untouched for 32 days, with an activity_risk of 8.68. It stands out from the other render methods because its fan-out of 9 is the highest in the top five — the method calls into Pie, produces two separate data-mapped arrays (pies and pieBackgrounds), defines inline helpers (withLabel, withColor), and then constructs a conditional legend block, all within the same function body. That breadth of callees means a change to any one dependency — the Pie layout interface, the chartConfig.color signature, or the legend layout logic — has a reasonable chance of requiring edits here.

The cyclomatic complexity of 7 is moderate, but in TypeScript a method that branches on Array.isArray(data) in two separate map callbacks, then accesses (data as any).labels and (data as any).colors via type casts, carries more implicit paths than the CC alone suggests. Those as any casts are a type-safety gap — if the data shape changes, TypeScript won’t catch it at this call site. The file-level bug-fix fraction of 16.7% (1 of 6 commits) is a mild historical signal worth noting. I’d start the refactoring by extracting pies and pieBackgrounds construction into a dedicated method and pulling the inline withLabel/withColor helpers out of render scope.


getClassNameForIndex — ContributionGraph.tsx

getClassNameForIndex
src/contribution-graph/ContributionGraph.tsx
6.94
high
CC 6
ND 3
FO 2
touches/30d 0

This function sits in the debt quadrant — last changed 32 days ago with zero touches in the past 30 days — and carries an activity_risk of 6.94. Its name suggests it maps a cell index to a colour class for the contribution heatmap — and the source excerpt confirms it: the function walks three levels of nesting (valueCache[index] exists → value exists → count exists) before calling mapValue to compute an opacity, then passes that opacity into chartConfig.color. The nesting depth of 3 is the defining structural issue. A reader must track three guard conditions simultaneously before reaching the mapValue call, and each guard failing drops through to a default chartConfig.color(0.15) return.

The edge case in mapValue is worth flagging: when maxValue === minValue, the function substitutes 0 for minValue to avoid a division-by-zero — a workaround that is correct but opaque without a comment. The file-level bug-fix fraction of 50% (2 of 4 commits) is the strongest historical defect-adjacent signal across all five functions. That doesn’t prove this function caused those fixes, but it warrants extra scrutiny. The three sibling functions visible in the excerpt — getTitleForIndex, getTooltipDataAttrsForIndex, getTooltipDataAttrsForValue — follow a nearly identical guard structure, suggesting the valueCache lookup pattern could be extracted into a shared helper that all of them delegate to, reducing the nesting and centralising the fallback logic.


render — LineChart.tsx

render
src/line-chart/LineChart.tsx
6.66
high
CC 17
ND 1
FO 5
touches/30d 0

This is the only fire-quadrant function in the repository — high structural complexity with recent activity — and it’s the one I’d watch most carefully going forward. It was last touched 30 days ago, and at a cyclomatic complexity of 17 it is by far the most branch-heavy function in the top five. Any change landing on this function now lands on actively contested, complex ground.

Cyclomatic Complexity 17
threshold: 10

The source excerpt shows why: the method destructures roughly 20 props in one block, then makes several conditional decisions — whether to include scrollable dots, whether to render a legend offset, how to compute segment count (defaulting to 1 or 4 based on min/max equality, then overriding with a segments prop). Each of those conditions is an independent execution path and a required test case. The fan-out of 5 includes renderLegend, renderDefs, getDatas, and getGradientUrl, so changes to any of those sub-renderers have a reasonable chance of requiring coordinated edits here.

The file-level signals are the strongest in the data: 13 total commits, a bug-fix fraction of 38.5%, and a PR review comment density of 0.125. None of that proves the current code is defective, but the pattern of repeated fixes on a file with CC 17 is a meaningful signal that this function is harder to change correctly than it looks. The long_function flag reinforces the recommendation: extract the scrollable-dot setup, the legend-offset calculation, and the segment-count logic into named methods before the next feature lands here.


render — StackedBarChart.tsx

render
src/StackedBarChart.tsx
6.31
high
CC 9
ND 2
FO 1
touches/30d 0

In the debt quadrant, untouched for 32 days, with an activity_risk of 6.31. The fan-out of 1 is notably low compared to the other long render methods — this function delegates almost nothing, which means it is doing more inline than its peers. The source excerpt confirms it: the method computes bar dimensions, iterates over data.data with a manual for loop to find the max stacked value, applies a percentile branch to set the scale border, and then assembles the full SVG tree including horizontal lines, horizontal labels, and a conditional legend block, all without extracting sub-renderers.

The cyclomatic complexity of 9 comes from the percentile branch, the showLegend / stackedBar conditionals, and the withHorizontalLabels / withVerticalLabels guards inside the JSX. With a bug-fix fraction of 33% across 9 total commits, there’s a moderate historical defect signal at the file level. The most actionable first step is to extract the max-value computation loop into a getMaxValue(data) helper and the legend determination into a shouldShowLegend(props) predicate — both are pure calculations that can be tested independently from the render output.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
long_function4

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

Reproduce This Analysis

git clone https://github.com/indiespirit/react-native-chart-kit
cd react-native-chart-kit
git checkout bc0b8afaa243d937cd2f8c980f0e0b6795ed1f74
hotspots analyze . --mode snapshot --explain-patterns --force

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →

Related Analyses