Copied RSS Feed

Chart of the week

S&P 500 Returns After Rate Cuts: Visualized Using a Flutter Heatmap

TL;DR: Explore how to visualize S&P 500 returns after interest rate cuts using a Risk Heat Map with Flutter 100% Stacked Bar Chart. This blog demonstrates how to effectively deploy color coding, custom renderers, and axis configurations to craft a dynamic and interactive heat map, making historical market performance intelligible at a glance.

Welcome to our Chart of the Week blog series!

Charts offer a powerful means to represent complex datasets visually, aiding in the understanding and interpretation of information. In industries like business, education, and research, charts play a crucial role in transforming raw data into visually compelling graphics, uncovering trends that may otherwise go unnoticed.

In this blog, we’ll walk you through creating a risk heat map using the Syncfusion Flutter 100% Stacked Bar Chart to showcase S&P 500 returns after interest rate cuts over time.

100% Stacked Bar Chart

The StackedBar100Series displays data as horizontal bars, where each bar represents the percentage distribution of categories within a whole.

Here, we use this chart to create a risk heat map that highlights the variations in S&P 500 returns after interest rate cuts over different time intervals—3 months, 6 months, and 1 year. By applying color coding and percentages, the chart effectively reveals the stock market’s relative performance over decades.

Color mapping

Color plays an essential role in creating a risk heatmap effect. By applying conditional color coding, we can visually differentiate various risk levels, such as low, medium, and high risk, making it easier to identify and assess patterns or critical points at a glance.

In this visualization, we use the pointColorMapper property to assign specific colors to data points based on their values or predefined conditions. This color mapping technique adds a layer of visual meaning, allowing for quick interpretation of risk levels. Using color this way makes the information more intuitive, enhancing clarity and making it easier to understand.

Axes configuration

Axes play an essential role in enhancing a chart’s readability. By configuring the axes and their labels, we ensure that the data is presented in an organized and comprehensible manner.

In this visualization, we configure the y-axis with multi-level labels to represent the time frames: 3 months, 6 months, and 1 year after the rate cut. These multi-level labels help organize the data into meaningful intervals, making it easier for viewers to interpret the data in terms of time. By adjusting the y-axis this way, the chart clearly shows how S&P 500 returns evolved over different periods following the rate cut.

100% Stacked Bar series renderer

The onCreateRenderer callback allows you to assign a custom series renderer to render unique shapes for each segment by creating a custom renderer class, such as _HeatmapSeriesRenderer, which extends StackedBar100SeriesRenderer.

In this custom renderer, the key methods include populateDataSource, which efficiently converts and updates raw data into a structured format for dynamic and interactive chart rendering. The custom series allows you to create unique visual effects for your chart by extending the default chart series behavior.

Here, we’ll create risk heat maps to visualize the S&P 500 returns after a rate cut. By using a custom series renderer, we can ensure equal height for each stacked bar to create the heat map effect. This involves adjusting the fixed position and dimensions.

Refer to the following image.

Before proceeding, refer to our previous blog on how to create an interactive heat map using Flutter Charts

Let’s get started!

Step 1: Gathering data on S&P 500 performance

Collect data on the S&P 500 performance following interest rate cuts over the last five decades from S&P 500-rate-cuts. Organize this information into a list that includes the year of the first-rate cut and the returns for 3 months, 6 months, and 1 year after the rate cut.

Step 2: Initializing the data for the chart

Now, create a _HeatMapData model that contains details of the 3 months after the first rate cut, 6 months after the first rate cut, one year after the first rate cut, and the year when the data was recorded. 

Refer to the following code example.

class _HeatMapData {
  _HeatMapData(this.year, this.threeMonths, this.sixMonths, this.oneYear);

  final DateTime year;
  final num threeMonths;
  final num sixMonths;
  final num oneYear;
}

Next, create a list to hold the S&P 500 returns after interest rate cuts data and the year the data was recorded, which will be the source of the Flutter Chart.

Refer to the following code example.

 List <_HeatMapData>? _heatMapData;

 @override
 void initState() {
    _heatMapData = <_HeatMapData>[
      _HeatMapData(DateTime(1973), -10.2, -6.2, -36.0),
      _HeatMapData(DateTime(1974), -14.7, -15.3, 7.5),
      _HeatMapData(DateTime(1980), 15.0, 28.9, 30.3),
      _HeatMapData(DateTime(1981), -11.0, -7.9, -17.8),
      _HeatMapData(DateTime(1982), -4.8, 17.4, 36.5),
      _HeatMapData(DateTime(1987), 0.1, 1.7, 7.5),
      _HeatMapData(DateTime(1989), 7.4, 7.5, 11.9),
      _HeatMapData(DateTime(1995), 10, 5.1, 13.4),
      _HeatMapData(DateTime(1998), 17.2, 26.5, 27.3),
      _HeatMapData(DateTime(2001), -16.3, -12.4, -14.9),
      _HeatMapData(DateTime(2007), -4.4, -11.8, -27.2),
      _HeatMapData(DateTime(2019), 3.8, 13.3, 14.5),
    ];
    super.initState();
 }

Step 3: Build the Flutter 100% Stacked Bar Chart

To create a risk heat map, we’ll use three StackedBar100Series to visualize S&P 500 returns following a rate cut. The DateTimeCategoryAxis on the X-axis displays non-linear date intervals, while the NumericAxis on the Y-axis shows descriptive time intervals (3 months, 6 months, and 1 year) after rate cuts. This axis configuration will be explained later.

Refer to the following code example.

SfCartesianChart _buildHeatmapChart() {
  return SfCartesianChart(
    primaryXAxis: DateTimeCategoryAxis(),
    primaryYAxis: NumericAxis(),
    series: _buildHeatmapSeries(),
  );
}

List<CartesianSeries<_HeatMapData, DateTime>> _buildHeatmapSeries() {
  return <CartesianSeries<_HeatMapData, DateTime>>[
    for (int i = 0; i < 3; i++)
      StackedBar100Series<_HeatMapData, DateTime>(
        dataSource: _heatMapData!,
        xValueMapper: (_HeatMapData data, int index) => data.year,
        yValueMapper: (_HeatMapData data, int index) => _yValue(data, i),
        name: _seriesName(i),
      ),
  ];
}

num _yValue(_HeatMapData data, int seriesIndex) {
  switch (seriesIndex) {
    case 0:
      return data.threeMonths;
    case 1:
      return data.sixMonths;
    case 2:
      return data.oneYear;
    default:
      return 0;
  }
}

String _seriesName(int seriesIndex) {
  switch (seriesIndex) {
    case 0:
      return '3 Months After First Rate Cut';
    case 1:
      return '6 Months After First Rate Cut';
    case 2:
      return '1 Year After First Rate Cut';
    default:
      return '';
  }
}

Refer to the following image.

Creating the Flutter 100% Stacked Bar Chart

Step 4: Creating a custom heat map series renderer

The onCreateRenderer callback enables the creation of a custom heat map series renderer, allowing you to render unique shapes for the segments. To achieve this, we’ll define a custom renderer class, such as _HeatmapSeriesRenderer, that extends the StackedBar100SeriesRenderer class.

Refer to the following code example.

List<CartesianSeries<_HeatMapData, DateTime>> _buildHeatmapSeries() {
   return <CartesianSeries<_HeatMapData, DateTime>>[
     for (int i = 0; i < 3; i++)
     StackedBar100Series<_HeatMapData, DateTime>(
       dataSource: _heatMapData!,
       xValueMapper: (_HeatMapData data, int index) => data.year,
       yValueMapper: (_HeatMapData data, int index) => _yValue(data, i),
       animationDuration: 0,
       name: _seriesName(i),
       onCreateRenderer: (ChartSeries<_HeatMapData, DateTime> series) {
         return _HeatmapSeriesRenderer();
       },
     ),
  ];
}

Calculate the default range

The heat map visualization needs to maintain a consistent value range to ensure proper color representation.

  • We override the populateDataSource method to set a fixed range for the y-axis values.
  • The yMin is set to 0, and the yMax is set to 100, meaning the heat map will always represent values within this range, even if negative values are present in the dataset. This step helps keep the chart balanced, making it easier to compare different data points.
class _HeatmapSeriesRenderer
  extends StackedBar100SeriesRenderer<_HeatMapData, DateTime> {
  _HeatmapSeriesRenderer();

  @override
  void populateDataSource(
   [List<ChartValueMapper<_HeatMapData, num>>? yPaths,
    List<List<num>>? chaoticYLists,
    List<List<num>>? yLists,
    List<ChartValueMapper<_HeatMapData, Object>>? fPaths,
    List<List<Object?>>? chaoticFLists,
    List<List<Object?>>? fLists]) {
   super.populateDataSource(
    yPaths, 
chaoticYLists,
yLists,
fPaths,
chaoticFLists,
fLists
); // Always keep positive 0 to 100 range even set negative value. yMin = 0; yMax = 100; // Calculate heatmap segment top and bottom values. _computeHeatMapValues(); }

Calculate the heat map values

We calculate the top and bottom values for each segment using the _computeHeatMapValues() method, ensuring they are evenly distributed, which makes the chart visually balanced.

The height of each segment is determined using the formula yValue = 100 / seriesLength, where seriesLength is the number of series dependent on the y-axis. For each series, we skip those that are not visible or have no data, then assign the bottom value based on its position and set the top value as stackValue + yValue.

Finally, we loop through the data points and apply these values, ensuring all segments maintain a consistent height across the heatmap.

Refer to the following code example.

void _computeHeatMapValues() {
    if (xAxis == null || yAxis == null) {
      return;
    }

    if (yAxis!.dependents.isEmpty) {
      return;
    }

    // Get the number of series dependent on the yAxis.
    final int seriesLength = yAxis!.dependents.length;
    // Calculate the proportional height for each series
    // (as a percentage of the total height).
    final num yValue = 100 / seriesLength;
    // Loop through each dependent series to calculate top and bottom values 
    // for the heatmap.
    for (int i = 0; i < seriesLength; i++) {
      // Check if the current series is a '_HeatmapSeriesRenderer'.
      if (yAxis!.dependents[i] is _HeatmapSeriesRenderer) {
        final _HeatmapSeriesRenderer current =
            yAxis!.dependents[i] as _HeatmapSeriesRenderer;

        // Skip processing if the series is not visible or has no data.
        if (!current.controller.isVisible || current.dataCount == 0) 
        {
          continue;
        }

        // Calculate the bottom (stack) value for the current series.
        num stackValue = 0;
        stackValue = yValue * i;

        current.topValues.clear();
        current.bottomValues.clear();

        // Loop through the data points in the current series.
        final int length = current.dataCount;
        for (int j = 0; j < length; j++) {
          // Add the bottom value (stackValue) for the current data point.
          current.bottomValues.add(stackValue.toDouble());
          // Add the top value (stackValue + yValue) for the current data   point.
          current.topValues.add((stackValue + yValue).toDouble());
        }
      }
    }
  }
}

Refer to the following image.

Creating the heat map using the Flutter 100% Stacked Bar Chart

Step 5: Customizing the heat map appearance

Color coding is the essence of a heatmap, allowing users to quickly identify risk levels by glancing at varying color intensities. In this step, we’ll customize the heatmap’s appearance and implement dynamic color mapping to visually represent risk values effectively.

Key customizations for heatmap

The _buildColor function serves as the backbone of the heatmap, mapping risk values to specific color intensities. Here’s how it works:

  • High risk: Negative values are mapped to shades of red, representing varying risk severity levels.
  • Moderate risk: Values around zero are mapped to shades of orange, indicating caution.
  • Low risk: Positive values are mapped to shades of green, denoting stability or low-risk levels.

Refer to the following code example.

Color _buildColor(num value) {
 switch (value) {
   case var val when val >= 20.0:
    return Colors.green.shade900; 
   case var val when val >= 15.0:
    return Colors.green.shade700; 
   case var val when val >= 10.0:
    return Colors.green.shade500; 
   case var val when val >= 5.0:
    return Colors.green.shade300;
   case var val when val >= 2.5:
    return Colors.orange.shade200;
   case var val when val > 0.0:
    return Colors.orange.shade400;
   case var val when val >= -2.5:
    return Colors.orange.shade600; 
   case var val when val >= -5.0:
    return Colors.orange.shade800;
   case var val when val >= -10.0:
    return Colors.red.shade200; 
   case var val when val >= -15.0:
    return Colors.red.shade400; 
   case var val when val >= -20.0:
    return Colors.red.shade600; 
   default:
    return Colors.red.shade800; 
 }
}

Applying colors to the series

Adjusting colors, borders, and labels makes the heat map clear and easy to understand. The pointColorMapper property returns the _buildColor function in the StackedBar100Series, which automatically assigns colors to each segment based on its data value. This helps display different intensity levels in the chart.

The width property controls the size of each segment, keeping the layout organized. The borderColor makes the segment edges clear.

Refer to the following code example.

StackedBar100Series<_HeatMapData, DateTime>(
 . . . . . . 
 pointColorMapper: (_HeatMapData data, int index) =>_buildColor(_yValue(data, i)),
 width: 1,
 name: _seriesName(i),
 animationDuration: 0,
 borderColor: Colors.white,
 borderRadius: const BorderRadius.all(Radius.circular(10)),
 . . . . . 
),

Refer to the following image.

Customizing the Flutter heat map chart

Step 6: Customizing other elements in the Flutter heat map chart

To further enhance the appearance and interactivity of the Flutter Heat Map Chart, you can configure the following customizations, as previously explained in the blog Create an Interactive Heat Map Using Flutter Charts.

  1. Axis customization: We’ll customize the axis for a heat map chart using a numeric axis. You can refer to the previous blog to understand the axis customization and modify it accordingly using the DateTimeCategoryAxis for visualizing the S&P 500 returns.
  2. Add data labels: Data labels help display the values directly on the chart. The implementation steps remain the same as discussed in the previous blog.
  3. Add tooltips: Tooltips provide additional information when hovering over the heat map cells. You can follow the same approach explained in the previous blog to enable tooltips. 

    Refer to the following image.

    Visualizing the S&P 500 returns after rate cuts data using the Flutter heat map chart

GitHub reference

For more details, refer to the visualizing the S&P 500 returns after rate cuts using heat maps in Flutter Charts demo on GitHub.

Unlock the power of Syncfusion’s highly customizable Flutter widgets.

Conclusion

Thank you for reading! In this blog, we’ve explored how to visualize the S&P 500 returns after interest rate cuts by creating a Heat Map using the Syncfusion Flutter 100% Stacked Bar Chart.

The existing customers can download the new version of Essential Studio on the License and Downloads page.  If you are not a Syncfusion customer, try our 30-day free trial to check out our incredible features. 

You can also contact us through our support forumssupport portal, or feedback portal. We are always happy to assist you!

Meet the Author

Kompelli Sravan Kumar Kompelli Lakshman

Kompelli Sravan Kumar is a proficient software engineer at Syncfusion, specializing in high-performance cross-platform component development. With a passion for cutting-edge technologies, he crafts innovative and scalable solutions that elevate user experiences. His deep expertise in modern cross-platform development empowers him to build efficient, adaptable applications that cater to diverse needs.