Using Apache ECharts with React and TypeScript: Regression Transform
Maneet Goyal
Posted on February 27, 2022
In one of our previous articles, Using Apache ECharts with React and TypeScript: Using Aggregate Transform, we talked about a few data transformation functionalities available in the ECharts ecosystem.
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.
In essence, this plugin is written in JS and has a .d.ts
file 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 is a neat solution to this missing types problem. Here's what we ended up doing to fix the missing types/modules error:
// 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.
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:
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} />
.
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 andComposeOption<...>
generic type seems weird, you should go through our older article, Using Apache ECharts with React and TypeScript: Optimizing Bundle Size, 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 theregression
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. 📚 📖
Posted on February 27, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
February 27, 2022