DEV Community

Cover image for Using Apache ECharts with React and TypeScript: Using Aggregate Transform
Maneet Goyal for Manufac Analytics Private Limited

Posted on • Edited on

Using Apache ECharts with React and TypeScript: Using Aggregate Transform

While dealing with scatter plots, bar charts, etc., it is common to run into requirements wherein you need to perform some data-transformation prior to rendering any visualization. For instance, you may want to sort all the bars in your bar chart in an ascending order, showcase linear regression in your scatter plot, or filter data points belonging to a certain date range. Fortunately, Apache ECharts supports some of these use-cases out-of-the-box.

However, native support for 2 important (IMO) transformations is still missing: regression (in scatter plot) and group-and-aggregate (in bar chart). Since Apache ECharts supports custom data-transforms as well, we were fortunate to find third-party transformation plugins for both of our needs: echarts-simple-transform and echarts-stat. ✨

echarts-simple-transform plugin, however, isn't that well maintained currently and has TypeScript compatibility issues as well. We addressed the shortcomings of this plugin in a new fork and published it as: @manufac/echarts-simple-transform.

This article shall delve into how to use @manufac/echarts-simple-transform with TypeScript & React.


Prerequisites

You may want to look into 2 of our previous articles first, to obtain a better understanding of how to integrate ECharts with TypeScript and React:

  1. Using Apache ECharts with React and TypeScript
  2. Using Apache ECharts with React and TypeScript: Optimizing Bundle Size

Once you have understood how to integrate Apache ECharts with React and TypeScript, and how the bundle optmization workflow/syntax looks like, the rest of the article shall be much easier to grasp.


Some Key Notes

  • You need to register an external data transform first in order to use it. This is the only differentiating thing about the source code. The rest of the source code can be implemented as mentioned in the prerequisite articles.
import { registerTransform, ... } from "echarts/core";
import { aggregate } from "@manufac/echarts-simple-transform";
import type { ExternalDataTransform } from "@manufac/echarts-simple-transform";

registerTransform(aggregate as ExternalDataTransform);
Enter fullscreen mode Exit fullscreen mode
  • The as assertion in as ExternalDataTransform is needed due to a type mismatch error. Since @manufac/echarts-simple-transform exports ExternalDataTransform, the type mismatch error is easily dealt with using as assertion. This isn't possible with echarts-simple-transform since it doesn't export any types and you may have to resort to @ts-ignore there.

  • We understand that using as assertion may not be an ideal fix but efforts were taken to refactor some types/interfaces present inside echarts-simple-transform such that as assertion isn't needed. After trying out various alternates, we speculate that some changes are needed in the echarts project itself to update the registerTransform function typings so that it can easily accommodate external/third-party transforms.

  • echarts-simple-transform has an issue with correctly aggregating (summing up) single element groups. @manufac/echarts-simple-transform overcomes that shortcoming as well. Apart from this bug fix, all of the remaining functionality is kept unchanged.

  • More differences between the two variants are listed in our project README.


How to use the aggregate transform?

Here's the complete recipe:

import { aggregate } from "@manufac/echarts-simple-transform";
import { BarChart } from "echarts/charts";
import { TransformComponent } from "echarts/components";
import { init, getInstanceByDom, use, registerTransform } from "echarts/core";
import { useRef, useEffect } from "react";
import type { ExternalDataTransform } from "@manufac/echarts-simple-transform";
import type { BarSeriesOption } from "echarts/charts";
import type { ECharts, ComposeOption, SetOptionOpts } from "echarts/core";

// Register the required components
use([
  TitleComponent,
  TooltipComponent,
  GridComponent,
  DatasetComponent,
  LegendComponent,
  ToolboxComponent,
  CanvasRenderer,
  BarChart,
  TransformComponent, // Built-in transform (filter, sort)
]);

registerTransform(aggregate as ExternalDataTransform); // `as` assertion is needed

export interface BarChartProps {
  style?: CSSProperties;
  settings?: SetOptionOpts;
  loading?: boolean;
  theme?: "light" | "dark";
  option: ComposeOption<TitleComponentOption | TooltipComponentOption | GridComponentOption | DatasetComponentOption | BarSeriesOption>;
}

export function BarChart({
  option,
  style,
  settings,
  loading,
  theme,
}: BarChartProps): JSX.Element {
  const chartRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // Initialize chart
    let chart: ECharts | undefined;
    if (chartRef.current !== null) {
      chart = init(chartRef.current, theme);
    }

    // Add chart resize listener
    // ResizeObserver is leading to a bit janky UX
    function resizeChart() {
      chart?.resize();
    }
    window.addEventListener("resize", resizeChart);

    // Return cleanup function
    return () => {
      chart?.dispose();
      window.removeEventListener("resize", resizeChart);
    };
  }, [theme]);

  useEffect(() => {
    // Update chart
    if (chartRef.current !== null) {
      const chart = getInstanceByDom(chartRef.current);
      chart?.setOption(option, settings);
    }
  }, [option, settings, theme]);

  useEffect(() => {
    // Update chart
    if (chartRef.current !== null) {
      const chart = getInstanceByDom(chartRef.current);
      // eslint-disable-next-line @typescript-eslint/no-unused-expressions
      loading === true ? chart?.showLoading() : chart?.hideLoading();
    }
  }, [loading, theme]);

  return (
      <div ref={chartRef} style={{ width: "100%", height: "100px", ...style }} />
  );
}
Enter fullscreen mode Exit fullscreen mode

Finally, the option object can be constructed as given in this documentation.


How to implement a custom data transform?

If you have any use cases which are aren't covered by any of the existing plugins, you could consider implementing one on your own. We couldn't locate any official documentation guiding such a task, so we suggest looking into the source code of the existing plugins as the first step.

These source codes can be especially helpful:


If you liked this post, please consider upvoting this issue on GitHub. Hopefully, that way this aggregate transform shall get natively supported by Apache ECharts and we developers won't need to rely on any third-party solution. 🕺

Top comments (0)