Using Apache ECharts with React and TypeScript
Tags: react, typescript, visualization, echarts
Published: 2021-07-03 05:09:00

## What is [Apache ECharts](https://echarts.apache.org/en/index.html)?

It's a cool data-visualization library like [Highcharts](https://www.highcharts.com/), [Chart.js](https://www.chartjs.org/), [amCharts](https://www.amcharts.com/), [Vega-Lite](https://vega.github.io/vega-lite/), and numerous others. A [lot of companies/products](https://github.com/apache/echarts/issues/11135) including AWS are using it in production.

It supports numerous charts out-of-the-box. Here's a wide range of [examples](https://echarts.apache.org/examples/en/index.html) to help you out. We also found their [`echarts-liquidfill`](https://github.com/ecomfe/echarts-liquidfill) extension quite useful.

Different teams have varying criteria behind adopting a data visualization library. If you happen to use **Apache ECharts**, this feed may help you integrate it with your React + TypeScript codebase.

## How to integrate with React and TypeScript?

You can implement a React functional component and reuse it in different parts of the app to avoid declaring `useEffect` hook and subscribing/unsubscribing to the `"resize"` event multiple times.

```tsx
// React-ECharts.tsx

import React, { useRef, useEffect } from "react";
import { init, getInstanceByDom } from "echarts";
import type { CSSProperties } from "react";
import type { EChartsOption, ECharts, SetOptionOpts } from "echarts";

export interface ReactEChartsProps {
  option: EChartsOption;
  style?: CSSProperties;
  settings?: SetOptionOpts;
  loading?: boolean;
  theme?: "light" | "dark";
}

export function ReactECharts({
  option,
  style,
  settings,
  loading,
  theme,
}: ReactEChartsProps): 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]); // Whenever theme changes we need to add option and setting due to it being deleted in cleanup function

  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 }} />;
}

```

## What about [`echarts-for-react`](https://github.com/hustcc/echarts-for-react)?

It does a similar job as the React component implemented above. But we were having trouble in making sure that the chart resizes when the window width changes. Also, at the time of writing this article, it seemed that the library may not be that actively maintained.

You can definitely try out `echarts-for-react` as it seems to expose more functionalities for the end user than the component implemented above. 

But creating our own component eliminated the need to add an extra dependency and gave us more control into how our component should map the input `props` to ECharts API. 

Knowing how the integration with React and TypeScript works under-the-hood, we ourselves can extend the component as and when needed instead of relying on an external library. 

Clearly, there are trade-offs involved so choose whatever is more reasonable for your use cases.

## How to integrate [`echarts-liquidfill`](https://github.com/ecomfe/echarts-liquidfill) extension?

The approach is quite similar to the component implemented above.

First, we need to specify the type definition for `liquidfill` charts. We are using the following:

```ts
// utils.ts

import type { DefaultLabelFormatterCallbackParams, EChartsOption } from "echarts";

/**
 * interface for LiquidFillGauge series config
 */
interface LiquidFillGaugeSeries {
  name?: string;
  type: "liquidFill";
  data: (
    | number
    | {
        name?: string;
        value: number;
        direction?: "left" | "right";
        itemStyle?: {
          color?: string;
          opacity?: number;
        };
        emphasis?: {
          itemStyle?: {
            opacity?: number;
          };
        };
      }
  )[];
  silent?: boolean;

  color?: string[];
  center?: string[];
  radius?: string;
  amplitude?: number;
  waveLength?: string | number;
  phase?: number | "auto";
  period?: number | "auto" | ((value: number, index: number) => number);
  direction?: "right" | "left";
  shape?: "circle" | "rect" | "roundRect" | "triangle" | "diamond" | "pin" | "arrow" | string;

  waveAnimation?: boolean;
  animationEasing?: string;
  animationEasingUpdate?: string;
  animationDuration?: number;
  animationDurationUpdate?: number;

  outline?: {
    show?: boolean;
    borderDistance?: number;
    itemStyle?: {
      color?: string;
      borderColor?: string;
      borderWidth?: number;
      shadowBlur?: number;
      shadowColor?: string;
    };
  };

  backgroundStyle?: {
    color?: string;
    borderWidth?: string;
    borderColor?: string;
    itemStyle?: {
      shadowBlur?: number;
      shadowColor?: string;
      opacity?: number;
    };
  };

  itemStyle?: {
    opacity?: number;
    shadowBlur?: number;
    shadowColor?: string;
  };

  label?: {
    show?: true;
    color?: string;
    insideColor?: string;
    fontSize?: number;
    fontWeight?: string;
    formatter?: string | ((params: DefaultLabelFormatterCallbackParams) => string);

    align?: "left" | "center" | "right";
    baseline?: "top" | "middle" | "bottom";
    position?: "inside" | "left" | "right" | "top" | "bottom" | string[];
  };

  emphasis?: {
    itemStyle?: {
      opacity?: number;
    };
  };
}

export interface LiquidFillGaugeOption extends Omit<EChartsOption, "series"> {
  series: LiquidFillGaugeSeries;
}
```

Then, update the `ReactEChartsProps`:

```ts
export interface ReactEChartsProps {
  option: EChartsOption | LiquidFillGaugeOption;
  style?: CSSProperties;
  settings?: SetOptionOpts;
  loading?: boolean;
  theme?: "light" | "dark";
}
```

Finally, reuse the `ReactECharts` component to create `LiquidFillGauge` component:

```tsx
// LiquidFillGauge.tsx

import React from "react";
import "echarts-liquidfill";
import type { CSSProperties } from "react";
import { ReactECharts } from "../React-ECharts";
import type { LiquidFillGaugeOption } from "../utils";

export interface LiquidFillGaugeProps {
  option: LiquidFillGaugeOption;
  style?: CSSProperties;
}

export function LiquidFillGauge({ option, style }: LiquidFillGaugeProps): JSX.Element {
  return (
    <ReactECharts
      option={option}
      style={style}
    />
  );
}
```

## How do you call this component in an app?

Create an `option` object, say:

```ts
const option: ReactEChartsProps["option"] = {
    dataset: {
      source: [
        ["Commodity", "Owned", "Financed"],
        ["Commodity 1", 4, 1],
        ["Commodity 2", 2, 4],
        ["Commodity 3", 3, 6],
        ["Commodity 4", 5, 3],
      ],
    },
    tooltip: {
      trigger: "axis",
      axisPointer: {
        type: "shadow",
      },
    },
    legend: {
      data: ["Owned", "Financed"],
    },
    grid: {
      left: "10%",
      right: "0%",
      top: "20%",
      bottom: "20%",
    },
    xAxis: {
      type: "value",
    },
    yAxis: {
      type: "category",
    },
    series: [
      {
        type: "bar",
        stack: "total",
        label: {
          show: true,
        },
      },
      {
        type: "bar",
        stack: "total",
        label: {
          show: true,
        },
      },
    ],
  }
```

Now, simply use it as a `prop` as you would in any other component:

```tsx
<div>
  <ReactECharts option={option} />
</div>
```

---

Do consider Apache Echarts if you are looking for a data visualization library for your projects.

---

Feel free to take a look at [Using Apache ECharts with React and TypeScript: Optimizing Bundle Size](https://dev.to/maneetgoyal/using-apache-echarts-with-react-and-typescript-optimizing-bundle-size-29l8) in case you're trying to reduce your bundle size as well.