Skip to content

Commit 185097f

Browse files
authored
Merge pull request #10677 from marmelab/chore-chart
[Demo] Use Echarts instead of rechart
2 parents b5fdfba + b50fefb commit 185097f

7 files changed

Lines changed: 260 additions & 407 deletions

File tree

docs/Demos.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ The admin of a fictional poster shop, allowing to manage sales, products, custom
249249
The source shows how to implement the following features:
250250

251251
- [Custom theme with white AppBar](https://github.com/marmelab/react-admin/blob/master/examples/demo/src/layout/themes.ts)
252-
- [d3.js chart with Recharts](https://github.com/marmelab/react-admin/blob/master/examples/demo/src/dashboard/OrderChart.tsx)
252+
- [Line chart with Echart](https://github.com/marmelab/react-admin/blob/master/examples/demo/src/dashboard/OrderChart.tsx)
253253
- [List widget embedded in another page](https://github.com/marmelab/react-admin/blob/master/examples/demo/src/dashboard/NewCustomers.tsx)
254254
- [Tabbed Datagrid](https://github.com/marmelab/react-admin/blob/master/examples/demo/src/orders/OrderList.tsx)
255255
- [Row expand with a custom show view](https://github.com/marmelab/react-admin/blob/master/examples/demo/src/invoices/InvoiceList.tsx)

docs/WithListContext.md

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -145,31 +145,73 @@ Whenever you use a react-admin component to fetch a list of records, react-admin
145145

146146
## Building a Chart
147147

148-
A common use case is to build a chart based on the list data. For instance, the following component fetches a list of fruit prices (using `<ListBase>`), and draws a line chart with the data using [ReCharts](https://recharts.org/en-US):
148+
A common use case is to build a chart based on the list data. For instance, the following component fetches a list of fruit prices (using `<ListBase>`), and draws a line chart with the data using [Echarts](https://echarts.apache.org/en/index.html):
149149

150150
![Chart based on ListContext](./img/WithListContext-chart.png)
151151

152+
{% raw %}
152153
```jsx
153154
import { ListBase, WithListContext } from 'react-admin';
154-
import { LineChart, Line, XAxis, YAxis, CartesianGrid } from 'recharts';
155+
import * as echarts from 'echarts';
155156

156157
const FruitChart = () => (
157158
<ListBase resource="fruits" disableSyncWithLocation perPage={100}>
158-
<WithListContext
159-
render={({ data }) => (
160-
<LineChart width={700} height={300} data={data}>
161-
<XAxis dataKey="date" />
162-
<YAxis />
163-
<CartesianGrid stroke="#eee" strokeDasharray="5 5" />
164-
<Line dataKey="apples" type="monotone" stroke="#8884d8" />
165-
<Line dataKey="blueberries" type="monotone" stroke="#82ca9d" />
166-
<Line dataKey="carrots" type="monotone" stroke="#ffc658" />
167-
</LineChart>
168-
)}
159+
<WithListContext<Fruit>
160+
render={({ data }) => <LineChart data={data} />}
169161
/>
170162
</ListBase>
171163
);
164+
165+
const LineChart = ({ data }) => {
166+
const chartRef = React.useRef(null);
167+
React.useEffect(() => {
168+
if (!data) return;
169+
const chartInstance = echarts.init(chartRef.current);
170+
171+
const option = {
172+
tooltip: {
173+
trigger: 'axis',
174+
},
175+
legend: {
176+
data: ['Apples', 'Blueberries', 'Carrots'],
177+
},
178+
xAxis: {
179+
type: 'category',
180+
data: data.map(fruit => fruit.date),
181+
},
182+
yAxis: {
183+
type: 'value',
184+
},
185+
series: [
186+
{
187+
name: 'Apples',
188+
type: 'line',
189+
data: data.map(fruit => fruit.apples),
190+
},
191+
{
192+
name: 'Blueberries',
193+
type: 'line',
194+
data: data.map(fruit => fruit.blueberries),
195+
},
196+
{
197+
name: 'Carrots',
198+
type: 'line',
199+
data: data.map(fruit => fruit.carrots),
200+
},
201+
],
202+
};
203+
204+
chartInstance.setOption(option);
205+
206+
return () => {
207+
chartInstance.dispose();
208+
};
209+
}, [data]);
210+
211+
return <div ref={chartRef} style={{ height: 300, width: 700 }} />;
212+
};
172213
```
214+
{% endraw %}
173215

174216
## Building a Refresh Button
175217

examples/demo/package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
"@apollo/client": "^3.12.4",
88
"@mui/icons-material": "^6.0.0",
99
"@mui/material": "^6.0.0",
10-
"@types/recharts": "^1.8.10",
1110
"@vitejs/plugin-react": "^2.2.0",
1211
"clsx": "^2.1.1",
1312
"data-generator-retail": "^5.0.0",
1413
"date-fns": "^3.6.0",
14+
"echarts": "^5.6.0",
1515
"fakerest": "^4.1.1",
1616
"fetch-mock": "^9.11.0",
1717
"graphql": "^15.6.0",
@@ -30,8 +30,7 @@
3030
"react-admin": "^5.0.0",
3131
"react-dom": "^19.0.0",
3232
"react-router": "^7.1.1",
33-
"react-router-dom": "^7.1.1",
34-
"recharts": "^2.15.0"
33+
"react-router-dom": "^7.1.1"
3534
},
3635
"scripts": {
3736
"dev": "vite",

examples/demo/src/dashboard/OrderChart.tsx

Lines changed: 118 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
import * as React from 'react';
22
import { Card, CardHeader, CardContent } from '@mui/material';
3-
import {
4-
ResponsiveContainer,
5-
AreaChart,
6-
Area,
7-
XAxis,
8-
YAxis,
9-
CartesianGrid,
10-
Tooltip,
11-
} from 'recharts';
3+
import * as echarts from 'echarts';
124
import { useTranslate } from 'react-admin';
135
import { format, subDays, addDays } from 'date-fns';
146

@@ -47,70 +39,128 @@ const getRevenuePerDay = (orders: Order[]): TotalByDay[] => {
4739
const OrderChart = (props: { orders?: Order[] }) => {
4840
const { orders } = props;
4941
const translate = useTranslate();
50-
if (!orders) return null;
42+
const chartRef = React.useRef<HTMLDivElement>(null);
43+
const chartInstance = React.useRef<echarts.ECharts | null>(null);
44+
45+
React.useEffect(() => {
46+
if (!orders) return;
47+
// Initialize chart
48+
if (chartRef.current) {
49+
if (!chartInstance.current) {
50+
chartInstance.current = echarts.init(chartRef.current);
51+
}
52+
53+
const revenueData = getRevenuePerDay(orders);
54+
55+
// Configure the chart
56+
const option = {
57+
xAxis: {
58+
type: 'time',
59+
min: addDays(aMonthAgo, 1).getTime(),
60+
max: new Date().getTime(),
61+
axisLabel: {
62+
formatter: (value: number) => dateFormatter(value),
63+
},
64+
},
65+
yAxis: {
66+
type: 'value',
67+
axisLabel: {
68+
formatter: (value: number) => `$${value}`,
69+
},
70+
splitLine: {
71+
show: true,
72+
lineStyle: {
73+
type: [3, 4],
74+
color: '#aaa',
75+
},
76+
},
77+
},
78+
tooltip: {
79+
trigger: 'axis',
80+
formatter: (params: any) => {
81+
const param = params[0];
82+
return `${dateFormatter(param.value[0])}: ${new Intl.NumberFormat(
83+
undefined,
84+
{
85+
style: 'currency',
86+
currency: 'USD',
87+
}
88+
).format(param.value[1])}`;
89+
},
90+
axisPointer: {
91+
type: 'line',
92+
lineStyle: {
93+
type: 'dashed',
94+
dashArray: [3, 3],
95+
},
96+
},
97+
},
98+
grid: {
99+
left: '0%',
100+
right: '1%',
101+
bottom: '0%',
102+
top: '2%',
103+
containLabel: true,
104+
},
105+
series: [
106+
{
107+
name: 'Revenue',
108+
type: 'line',
109+
smooth: true,
110+
smoothMonotone: 'x',
111+
symbol: 'none',
112+
sampling: 'average',
113+
areaStyle: {
114+
color: new echarts.graphic.LinearGradient(
115+
0,
116+
0,
117+
0,
118+
1,
119+
[
120+
{
121+
offset: 0.05,
122+
color: 'rgba(136, 132, 216, 0.8)',
123+
},
124+
{
125+
offset: 0.95,
126+
color: 'rgba(136, 132, 216, 0)',
127+
},
128+
]
129+
),
130+
},
131+
lineStyle: {
132+
color: '#8884d8',
133+
width: 2,
134+
},
135+
data: revenueData.map(item => [item.date, item.total]),
136+
},
137+
],
138+
};
139+
140+
// Apply the config
141+
chartInstance.current.setOption(option);
142+
}
143+
144+
// Handle resize
145+
const handleResize = () => {
146+
chartInstance.current?.resize();
147+
};
148+
149+
window.addEventListener('resize', handleResize);
150+
151+
// Cleanup
152+
return () => {
153+
window.removeEventListener('resize', handleResize);
154+
chartInstance.current?.dispose();
155+
chartInstance.current = null;
156+
};
157+
}, [orders]);
51158

52159
return (
53160
<Card>
54161
<CardHeader title={translate('pos.dashboard.month_history')} />
55162
<CardContent>
56-
<div style={{ width: '100%', height: 300 }}>
57-
<ResponsiveContainer>
58-
<AreaChart data={getRevenuePerDay(orders)}>
59-
<defs>
60-
<linearGradient
61-
id="colorUv"
62-
x1="0"
63-
y1="0"
64-
x2="0"
65-
y2="1"
66-
>
67-
<stop
68-
offset="5%"
69-
stopColor="#8884d8"
70-
stopOpacity={0.8}
71-
/>
72-
<stop
73-
offset="95%"
74-
stopColor="#8884d8"
75-
stopOpacity={0}
76-
/>
77-
</linearGradient>
78-
</defs>
79-
<XAxis
80-
dataKey="date"
81-
name="Date"
82-
type="number"
83-
scale="time"
84-
domain={[
85-
addDays(aMonthAgo, 1).getTime(),
86-
new Date().getTime(),
87-
]}
88-
tickFormatter={dateFormatter}
89-
/>
90-
<YAxis dataKey="total" name="Revenue" unit="$" />
91-
<CartesianGrid strokeDasharray="3 3" />
92-
<Tooltip
93-
cursor={{ strokeDasharray: '3 3' }}
94-
formatter={(value: any) =>
95-
new Intl.NumberFormat(undefined, {
96-
style: 'currency',
97-
currency: 'USD',
98-
}).format(value)
99-
}
100-
labelFormatter={(label: any) =>
101-
dateFormatter(label)
102-
}
103-
/>
104-
<Area
105-
type="monotone"
106-
dataKey="total"
107-
stroke="#8884d8"
108-
strokeWidth={2}
109-
fill="url(#colorUv)"
110-
/>
111-
</AreaChart>
112-
</ResponsiveContainer>
113-
</div>
163+
<div ref={chartRef} style={{ width: '100%', height: 300 }} />
114164
</CardContent>
115165
</Card>
116166
);

packages/ra-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@types/node-polyglot": "^0.4.31",
3636
"@types/react": "^18.3.3",
3737
"cross-env": "^5.2.0",
38+
"echarts": "^5.6.0",
3839
"expect": "^27.4.6",
3940
"ignore-styles": "~5.0.1",
4041
"jscodeshift": "^0.15.2",
@@ -43,7 +44,6 @@
4344
"react-hook-form": "^7.53.0",
4445
"react-router": "^6.28.1",
4546
"react-router-dom": "^6.28.1",
46-
"recharts": "^2.1.15",
4747
"rimraf": "^3.0.2",
4848
"typescript": "^5.1.3",
4949
"yup": "^0.32.11",

0 commit comments

Comments
 (0)