DEV Community

Cover image for Building Responsive and Performant Graphs in React Native
GeekyAnts Inc for GeekyAnts

Posted on • Originally published at geekyants.com

Building Responsive and Performant Graphs in React Native

Data visualization plays a key role in modern mobile apps — from analytics dashboards to performance summaries. Well-designed graphs make complex data easy to understand at a glance. However, building responsive and high-performance graphs in React Native can be challenging.

In this blog, we will walk through how we approached building responsive and performant graphs in React Native — the challenges we faced and the strategies we adopted.


The Challenges We Faced with Graph Implementations

When working with popular graph libraries in React Native, several practical issues often arise, particularly around responsiveness, alignment, and performance. Here are a few of the most common challenges we encountered:

1. Y-Axis Scrolling Issue

When making the graph horizontally scrollable (for large datasets), the Y-axis scrolled along with the graph content, making it difficult to keep the labels visible while navigating through the data.

2. Axis Alignment Issues

To fix the above issue, we attempted to create two separate graphs — one for the Y-axis and another for the main scrollable X-axis. While this approach initially worked, it introduced alignment inconsistencies between the two graphs, especially when the chart data or layout was updated dynamically.

3. Performance and Crashes on iOS

For large datasets, especially in bar charts or grouped bar charts, the app would occasionally crash on iOS. The crash logs pointed to Metal texture creation failures, meaning the graphics rendering system could not allocate textures large enough for the entire dataset.


What We Require from Our Charting Solution

After evaluating multiple libraries and approaches, it became clear that our ideal solution had to:

  • Handle large datasets gracefully without crashes.
  • Support independent Y-axis rendering for better user readability.
  • Offer responsiveness across screen sizes.
  • Be lightweight and performant, minimizing bundle size and build times.
  • Stay easy to integrate.

The Solution

After exploring several options, we found that combining a lightweight, flexible charting library like react-native-gifted-charts with custom dynamic logic gave us the right balance between performance, maintainability, and control.

Some of the features that stood out about the library were:

  • Built-in support for scrollable charts with fixed axes
  • Smooth animations and transitions
  • Highly customizable props for colors, labels, gradients, and legends
  • Optimized rendering that prevents crashes even with large datasets
  • Lightweight and efficient, which reduced the app's bundle size, improved runtime performance, and significantly shortened the time required to create production builds

1. Installation

To get started, install the necessary dependencies that will enable chart rendering and responsive scaling in your React Native project:

  • react-native-gifted-charts (the charting library)
  • react-native-svg (for rendering graphics)
  • react-native-linear-gradient (or expo-linear-gradient if in an Expo project)
  • react-native-responsive-screen for dynamic sizing across devices (recommended)

Using React Native CLI:

npm install react-native-gifted-charts react-native-svg react-native-linear-gradient react-native-responsive-screen
Enter fullscreen mode Exit fullscreen mode

Using Expo (recommended):

npx expo install react-native-gifted-charts react-native-svg expo-linear-gradient react-native-responsive-screen
Enter fullscreen mode Exit fullscreen mode

Note: Make sure these dependencies are linked (in non-Expo projects) so the native modules work correctly. Autolinking should handle it in recent React Native versions.


2. The Final Optimized Implementation

Let's walk through the final optimized setup step-by-step, starting from the base configuration and moving into the dynamic enhancements we added to make the graphs stable, scalable, and production-ready.

Setting Up the Base Chart

The react-native-gifted-charts library provides a solid and efficient starting point for creating bar graphs. It handles much of the heavy lifting for graph rendering, animations, and performance optimizations internally.

Here's a basic setup to render a simple bar chart:

import { BarChart } from 'react-native-gifted-charts';
import { widthPercentageToDP as wp } from 'react-native-responsive-screen';

const barData = [
  { value: 250, label: 'Jan' },
  { value: 500, label: 'Feb' },
  { value: 745, label: 'Mar' },
  { value: 320, label: 'Apr' },
  { value: 600, label: 'May' },
];

const SimpleBarChart = () => {
  return (
    <BarChart
      data={barData}
      barWidth={wp('5.5%')}
      height={wp('38%')}
      width={wp('90%')}
      spacing={65}
      noOfSections={5}
      maxValue={1000}
      stepValue={200}
      frontColor="#4ABFF4"
    />
  );
};
Enter fullscreen mode Exit fullscreen mode

This base setup alone helps resolve two major pain points:

  • App crashes on iOS for large datasets: react-native-gifted-charts handles large datasets smoothly without triggering the Metal texture allocation errors previously encountered on iOS.
  • Y-axis scrolling along with chart content: The library keeps the Y-axis fixed while allowing horizontal scrolling of large datasets, significantly improving readability and usability.

Key Props and Their Purpose

Before diving into custom enhancements, it's essential to understand a few key props that form the backbone of every chart:

barWidth

Controls the width of each bar. Using wp("5.5%") keeps it proportional to screen width, ensuring the chart looks balanced on both phones and tablets.

height and width

Defines the overall chart dimensions. Using percentage-based values (like height={wp("38%")}) makes the graph scale fluidly across devices.

spacing

Determines the horizontal space between bars. Dynamically adjusted using:

spacing={isTablet ? 70 : 65}
Enter fullscreen mode Exit fullscreen mode

so that the chart maintains even spacing on different screen types.

noOfSections, stepValue, and maxValue

These three props control the scaling of the Y-axis:

  • noOfSections — Number of horizontal grid lines
  • stepValue — Value difference between each section
  • maxValue — The highest Y-axis value (multiply the maximum value by 1.2 to add a buffer, ensuring the tallest bar is never cropped at the top)

These three values must always satisfy:

maxValue = noOfSections * stepValue
Enter fullscreen mode Exit fullscreen mode

formatYLabel

A function used to format the Y-axis values. Used to round decimal values and ensure clean, readable labels:

formatYLabel={(value) => Math.round(Number(value)).toString()}
Enter fullscreen mode Exit fullscreen mode

xAxisLabelTextStyle and yAxisTextStyle

Controls the font size, alignment, and color of the X and Y-axis labels. Adjust font sizes for tablets and phones for better legibility.

frontColor, xAxisColor, and yAxisColor

  • frontColor — Sets the color of the bars
  • xAxisColor & yAxisColor — Defines the axis line colors for a subtle and professional look

dashGap

Defines gaps between dashes in grid lines. Set to 0 to show solid lines behind the graphs.


Making the Charts Truly Dynamic

Now that the base graph is working, let's move on to the custom enhancements that improve accuracy, responsiveness, and flexibility.

Dynamic Y-Axis Width for Large Values

One of the first issues encountered was truncated Y-axis labels when large numerical values were displayed. The Y-axis width is calculated dynamically based on the number of digits in the largest value:

const getYAxisWidth = (maxValue) => {
  const digits = maxValue.toString().length;
  if (digits <= 3) return wp('8%');
  if (digits <= 5) return wp('10%');
  return wp('12%');
};
Enter fullscreen mode Exit fullscreen mode

This approach:

  • Prevents label truncation — The Y-axis automatically adjusts to fit larger values.
  • Maintains proper alignment — Bars and axes remain visually consistent regardless of dataset size.
  • Improves responsiveness — Works seamlessly across devices with different screen sizes.

Properly Calculating Chart Sections and Scaling

For the graph to render correctly, the three properties (noOfSections, stepValue, and maxValue) must always follow this mathematical relationship:

maxValue = noOfSections * stepValue
Enter fullscreen mode Exit fullscreen mode

If this relationship is not maintained — for instance, when only one of the three values is adjusted manually — the chart may render incorrectly or not at all.

Note: The documentation advises reloading the app whenever these values or related props (height, stepHeight, etc.) are modified, as the new configurations are sometimes applied only after a full reload.

Here's how to handle these configurations dynamically:

const calculateChartScaling = (data) => {
  const maxDataValue = Math.max(...data.map((item) => item.value));
  const bufferedMax = maxDataValue * 1.2;

  const noOfSections = 5;
  const stepValue = Math.ceil(bufferedMax / noOfSections / 10) * 10;
  const maxValue = noOfSections * stepValue;

  return { noOfSections, stepValue, maxValue };
};
Enter fullscreen mode Exit fullscreen mode

Structuring the Data for Flexibility

In react-native-gifted-charts, the data prop accepts an array of objects, each representing a single bar or data point:

const chartData = [
  {
    value: 150,
    label: 'KA-51-AF-4155',
    topLabelComponent: () => (
      <Text style={{ fontSize: isTablet ? 10 : 8, color: '#333' }}>150</Text>
    ),
  },
  {
    value: 320,
    label: 'MH-12-BT-9921',
    topLabelComponent: () => (
      <Text style={{ fontSize: isTablet ? 10 : 8, color: '#333' }}>320</Text>
    ),
  },
];
Enter fullscreen mode Exit fullscreen mode

Property breakdown:

  • value — Defines the height of the bar
  • label — Sets the X-axis label
  • topLabelComponent — A custom React component for displaying value labels above each bar

Our Graph in Action

Here's how the charts render across different devices:

Android and iOS comparison bar graph

Tablet data visualization bar graph

The End Result

By combining the base setup from react-native-gifted-charts with these enhancements, we achieved:

  • Smooth, crash-free performance on both iOS and Android, even with large datasets
  • A fixed, properly aligned Y-axis for improved readability
  • Dynamic scaling and sizing that adapts to any screen size or dataset
  • Clean, customizable visuals ready for production use

3. Advanced: Creating Grouped Bar Graphs

In many scenarios, we need to display multiple metrics side-by-side for the same category — such as current vs expected values. The react-native-gifted-charts library makes it straightforward to implement grouped bar charts by structuring the data prop appropriately and customizing the top labels.

Data Structure for Grouped Bars

To implement a grouped bar chart, most of the setup remains the same as a standard bar chart. However, there are a few additional considerations for handling multiple bars per category:

  • spacing — Adds space between bars within a group to clearly separate metrics
  • topLabelComponent — For grouped bars, dynamically render the label above the taller bar in each group
  • frontColor — Used to differentiate bars within the same group

Example snippet for grouped data:

const groupedData = [
  {
    value: 400,
    label: 'Vehicle A',
    frontColor: '#4ABFF4',
    topLabelComponent: () => <TopLabel value={400} compareValue={300} />,
  },
  {
    value: 300,
    frontColor: '#FFA07A',
    topLabelComponent: () => null,
  },
  {
    value: 520,
    label: 'Vehicle B',
    frontColor: '#4ABFF4',
    topLabelComponent: () => <TopLabel value={520} compareValue={480} />,
  },
  {
    value: 480,
    frontColor: '#FFA07A',
    topLabelComponent: () => null,
  },
];
Enter fullscreen mode Exit fullscreen mode

Dynamic Top Labels

For grouped bars, top labels must be carefully positioned to avoid overlapping. Here's a reusable component that automatically adjusts the label width and position based on the taller bar in the group:

const TopLabel = ({ value, compareValue }) => {
  const isLow = value < compareValue;
  const labelText = value.toString();
  const labelWidth = isTablet
    ? Math.max(labelText.length * 8, 40)
    : Math.max(labelText.length * 6, 30);

  return (
    <View style={{ width: labelWidth, alignItems: 'center' }}>
      <Text
        style={{
          fontSize: isTablet ? 10 : 8,
          color: isLow ? '#FF6347' : '#333',
          fontWeight: '600',
        }}
      >
        {labelText}
      </Text>
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

Key points:

  • Dynamic Positioning — The top label is placed above the bar with the larger value in the group, ensuring it doesn't overlap or look misaligned.
  • Dynamic Width Calculation — Width is calculated based on text length and screen type (tablet vs mobile) to prevent clipping.
  • Conditional Styling — Using an isLow flag, different colors indicate low vs high values, improving readability and visual cues.

Our Grouped Bar Graph in Action

Grouped bar graph comparing mileage data on Android and iOS platforms

Grouped bar graph visualization of tablet-based mileage analytics


Final Thoughts

Building responsive and performant graphs in React Native is about more than just aesthetics — it's about ensuring usability, speed, and adaptability across devices. The react-native-gifted-charts library offers a strong foundation, and with a few tailored enhancements like adaptive layouts and precise alignment, we can create production-ready graphs that deliver a seamless experience across Android, iOS, and tablets.


Originally published on GeekyAnts Blog

Top comments (0)