Using Apache ECharts with React and TypeScript: Regression Transform
Tags: typescript, react, echarts, webdev
Published: 2022-02-27 14:33:00

In one of our previous articles, [Using Apache ECharts with React and TypeScript: Using Aggregate Transform](https://dev.to/maneetgoyal/using-apache-echarts-with-react-and-typescript-using-aggregate-transform-3d01), we talked about a few data transformation functionalities available in the ECharts ecosystem.

[echarts-stat](https://github.com/ecomfe/echarts-stat) happens to be a very popular ECharts plugin useful in performing a wide range of data transformations like regression, histogram binning, k-means clustering, etc. However, it doesn't seem to be on an active development cycle and is currently suffering from at least 1 TS compatibility [issue](https://github.com/ecomfe/echarts-stat/issues/35).

In essence, this plugin is written in JS and has a [`.d.ts` file](https://github.com/ecomfe/echarts-stat/blob/master/src/ecStat.d.ts) to expose the type information. Unfortunately, the type definition file doesn't export any of the transforms. Consequently, our TSC starts complaining when we try to import any of the transforms.

Turns out that [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) is a neat solution to this missing types problem. Here's what we ended up doing to fix the missing types/modules error:

```ts
// some-name.d.ts

import type { ExternalDataTransform } from "@manufac/echarts-simple-transform";

/**
 * Needed because of: https://github.com/ecomfe/echarts-stat/issues/35
 * Module augmentation: https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
 */
declare module "echarts-stat" {
  let transform: {
    regression: ExternalDataTransform;
    histogram: ExternalDataTransform;
    clustering: ExternalDataTransform;
  };
}
```

You may obtain more information on `@manufac/echarts-simple-transform` in [Using Apache ECharts with React and TypeScript: Using Aggregate Transform](https://dev.to/maneetgoyal/using-apache-echarts-with-react-and-typescript-using-aggregate-transform-3d01).

---

### Okay, now how to use `echarts-stat` with TypeScript and React?

Since the TSC errors are now resolved by the addition of the declaration file (as described above) in our project, we can safely import `"transform"` from `"echarts-stat"`. Here's the full recipe:

```ts
import { transform } from "echarts-stat";
import { ScatterChart, LineChart } from "echarts/charts";
import { TransformComponent } from "echarts/components";
import { init, getInstanceByDom, use, registerTransform } from "echarts/core";
import { useRef, useEffect } from "react";
import type { ScatterSeriesOption, LineSeriesOption } from "echarts/charts";
import type { ECharts, ComposeOption } from "echarts/core";

// Register the required components
use([
  TitleComponent,
  TooltipComponent,
  GridComponent,
  DatasetComponent,
  LegendComponent,
  ToolboxComponent,
  CanvasRenderer,
  ScatterChart,
  LineChart, // In order to plot regression lines
  TransformComponent, // Built-in transform (filter, sort)
]);

registerTransform(transform.regression); // No missing module error due to module augmentation as done above

// As per docs: https://echarts.apache.org/handbook/en/basics/import/#minimal-option-type-in-typescript
export interface ScatterPlotChartProps extends BaseEChartsProps {
  option: ComposeOption<TitleComponentOption | TooltipComponentOption | GridComponentOption | DatasetComponentOption | ScatterSeriesOption | LineSeriesOption>;
}

export function ScatterPlotChart({
  option,
  style,
  settings,
  loading,
  theme,
}: ScatterPlotChartProps): 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 }} />
  );
}
```

---

### And, how does an `option` object look like?

The entire `props` object is shown below which can be passed to the `ScatterPlotChart` component as `<ScatterPlotChart {...props} />`.

```ts
const props = {
  option: {
    dataset: [
      {
        source: [[1, 2], [4, 7], [2, 6], [3, 8], ...]
      },
      {
        transform: {
          type: "ecStat:regression",
          config: { method: "polynomial", order: 3 },
        },
      },
    ],
    grid: {
      containLabel: true,
    },
    title: {
      text: "Scatter-Regression Chart",
      left: "center",
    },
    legend: {
      bottom: 5,
    },
    tooltip: {
      trigger: "item",
    },
    xAxis: {
      name: "Year of Experience",
    },
    yAxis: {
      name: "Salary",
    },
    series: [
      {
        name: "Experience-Salary",
        type: "scatter",
      },
      {
        name: "Cubic Polynomial",
        type: "line",
        datasetIndex: 1,
        symbol: "none",
        // to show regression formule
        endLabel: {
          show: true,
          distance: -200, // distance of endLabel from the grid
          formatter: (params) => {
            const datum = params.data as (string | number)[];
            return datum[2].toString();
          },
        },
      },
    ],
  },
  style: {
    height: "300px",
  },
}
```

---

### Parting Notes

- If the use of `use(...)` function and `ComposeOption<...>` generic type seems weird, you should go through our older article, [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), to learn more about it. TLDR: they are a way to optimize the bundle size.

- This plugin doesn't export regression coefficient value (`R^2`) which seems to be an important missing feature. Nevertheless, the plugin looks pretty comprehensive in that it supports so many kinds of data transformations. For instance, even within just the `regression` transform, it supports linear, logarithmic, exponential, and polynomial (`n`th order) flavors.

- It would have been way nicer if this plugin (`echarts-stat`) was more actively maintained though.

---

Thanks for reading. Feel free to share your views and suggestions. 📚 📖