Switch to recharts.
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit

This commit is contained in:
Tom Butcher 2026-06-21 02:14:22 +01:00
parent afbab60ab9
commit 39111d81c8
4 changed files with 220 additions and 1123 deletions

View File

@ -9,7 +9,6 @@
"private": true, "private": true,
"homepage": "./", "homepage": "./",
"dependencies": { "dependencies": {
"@ant-design/charts": "^2.6.5",
"@ant-design/icons": "^6.1.0", "@ant-design/icons": "^6.1.0",
"@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1",
"@codemirror/lang-cpp": "^6.0.3", "@codemirror/lang-cpp": "^6.0.3",
@ -61,6 +60,7 @@
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-responsive": "^10.0.1", "react-responsive": "^10.0.1",
"react-router-dom": "^7.8.2", "react-router-dom": "^7.8.2",
"recharts": "^3.8.1",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"simplebar-react": "^3.3.2", "simplebar-react": "^3.3.2",
"socket.io-client": "*", "socket.io-client": "*",

1215
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import { useEffect, useState, useContext, useMemo, lazy, Suspense } from 'react' import { useEffect, useState, useContext, useMemo } from 'react'
import { import {
Card, Card,
Segmented, Segmented,
@ -6,13 +6,18 @@ import {
Popover, Popover,
DatePicker, DatePicker,
Button, Button,
Space, Space
Skeleton
} from 'antd' } from 'antd'
import {
const Column = lazy(() => ResponsiveContainer,
import('@ant-design/charts').then((module) => ({ default: module.Column })) BarChart,
) Bar,
CartesianGrid,
XAxis,
YAxis,
Tooltip,
Legend
} from 'recharts'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { getModelByName } from '../../../database/ObjectModels' import { getModelByName } from '../../../database/ObjectModels'
@ -285,39 +290,17 @@ const HistoryDisplay = ({
return colors.default return colors.default
}) })
const config = { const chartRows = Array.from(
data: chartData, chartData.reduce((acc, item) => {
height, const existing = acc.get(item.date) || {
xField: 'dateFormatted', date: item.date,
yField: 'value', dateFormatted: item.dateFormatted
theme: {
type: isDarkMode ? 'dark' : 'light',
fontFamily: '"DM Sans", -apple-system, BlinkMacSystemFont, sans-serif'
},
stack: true,
colorField: 'category',
columnWidthRatio: 1,
scale: {
color: {
range: colorRange
} }
}, existing[item.category] = item.value
smooth: true, acc.set(item.date, existing)
interaction: { return acc
tooltip: { }, new Map()).values()
marker: false ).sort((a, b) => new Date(a.date) - new Date(b.date))
}
},
legend: {
position: 'top'
},
animation: {
appear: {
animation: 'wave-in',
duration: 1000
}
}
}
const customTimeRangeContent = ( const customTimeRangeContent = (
<Space.Compact> <Space.Compact>
@ -391,22 +374,45 @@ const HistoryDisplay = ({
<LoadingPlaceholder message='Loading history data...' /> <LoadingPlaceholder message='Loading history data...' />
</Flex> </Flex>
)} )}
{chartData.length > 0 && ( {chartRows.length > 0 && (
<Suspense <div style={{ width: '100%', height: `${height}px` }}>
fallback={ <ResponsiveContainer width='100%' height='100%'>
<Flex <BarChart data={chartRows}>
justify='center' <CartesianGrid
align='center' strokeDasharray='3 3'
style={{ height: `${height}px` }} stroke={isDarkMode ? '#303030' : '#f0f0f0'}
> />
<Skeleton active paragraph={{ rows: 4 }} /> <XAxis
</Flex> dataKey='dateFormatted'
} tick={{ fill: isDarkMode ? '#d9d9d9' : '#595959', fontSize: 12 }}
> />
<Column {...config} /> <YAxis
</Suspense> tick={{ fill: isDarkMode ? '#d9d9d9' : '#595959', fontSize: 12 }}
/>
<Tooltip
contentStyle={{
backgroundColor: isDarkMode ? '#1f1f1f' : '#ffffff',
border: `1px solid ${isDarkMode ? '#303030' : '#f0f0f0'}`,
borderRadius: 8
}}
/>
<Legend />
{modelStats.map((statDef, index) => {
const category = statDef.label || statDef.name
return (
<Bar
key={category}
dataKey={category}
stackId='history'
fill={colorRange[index] || colors.default}
/>
)
})}
</BarChart>
</ResponsiveContainer>
</div>
)} )}
{loading == false && chartData.length == 0 && ( {loading == false && chartRows.length == 0 && (
<Flex justify='center' align='center' style={{ height: `${height}px` }}> <Flex justify='center' align='center' style={{ height: `${height}px` }}>
<MissingPlaceholder message='No data available.' /> <MissingPlaceholder message='No data available.' />
</Flex> </Flex>
@ -423,7 +429,7 @@ HistoryDisplay.propTypes = {
]), ]),
styles: PropTypes.object, styles: PropTypes.object,
endDate: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), endDate: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]),
height: PropTypes.string height: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
} }
export default HistoryDisplay export default HistoryDisplay

View File

@ -66,16 +66,6 @@ export default defineConfig({
id.includes('node_modules/react-dom') || id.includes('node_modules/react-dom') ||
id.includes('node_modules/react-router-dom') id.includes('node_modules/react-router-dom')
) { ) {
// EXCLUDE charts so they are handled by lazy loading in HistoryDisplay.jsx
if (
id.includes('node_modules/@ant-design/charts') ||
id.includes('node_modules/@ant-design/plots') ||
id.includes('node_modules/@ant-design/graphs') ||
id.includes('node_modules/@ant-design/charts-util') ||
id.includes('node_modules/@antv')
) {
return
}
return 'antd' return 'antd'
} }