mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	refactor: LineChart component (#6072)
Initial version of a reusable trend chart, with a tooltip and vertical highlight
This commit is contained in:
		
							parent
							
								
									f298d7d511
								
							
						
					
					
						commit
						e6ccd83739
					
				| @ -77,7 +77,7 @@ export const ExecutiveDashboard: VFC = () => { | ||||
|             <Box sx={(theme) => ({ paddingBottom: theme.spacing(4) })}> | ||||
|                 <PageHeader | ||||
|                     titleElement={ | ||||
|                         <Typography variant='h1' component='h2'> | ||||
|                         <Typography variant='h1' component='span'> | ||||
|                             Dashboard | ||||
|                         </Typography> | ||||
|                     } | ||||
|  | ||||
| @ -0,0 +1,3 @@ | ||||
| import { lazy } from 'react'; | ||||
| 
 | ||||
| export const LineChart = lazy(() => import('./LineChartComponent')); | ||||
| @ -1,15 +1,15 @@ | ||||
| import { useMemo, useState, type VFC } from 'react'; | ||||
| import { | ||||
|     Chart as ChartJS, | ||||
|     CategoryScale, | ||||
|     LinearScale, | ||||
|     PointElement, | ||||
|     LineElement, | ||||
|     Title, | ||||
|     Tooltip, | ||||
|     Legend, | ||||
|     TimeScale, | ||||
|     Chart, | ||||
|     type ChartData, | ||||
|     type ScatterDataPoint, | ||||
| } from 'chart.js'; | ||||
| import { Line } from 'react-chartjs-2'; | ||||
| import 'chartjs-adapter-date-fns'; | ||||
| @ -18,7 +18,6 @@ import { | ||||
|     useLocationSettings, | ||||
|     type ILocationSettings, | ||||
| } from 'hooks/useLocationSettings'; | ||||
| import { ExecutiveSummarySchema } from 'openapi'; | ||||
| import { ChartTooltip, TooltipState } from './ChartTooltip/ChartTooltip'; | ||||
| 
 | ||||
| const createOptions = ( | ||||
| @ -34,7 +33,6 @@ const createOptions = ( | ||||
|                 labels: { | ||||
|                     boxWidth: 12, | ||||
|                     padding: 30, | ||||
|                     // usePointStyle: true,
 | ||||
|                     generateLabels: (chart: Chart) => { | ||||
|                         const datasets = chart.data.datasets; | ||||
|                         const { | ||||
| @ -122,8 +120,8 @@ const createOptions = ( | ||||
|                     unit: 'month', | ||||
|                 }, | ||||
|                 grid: { | ||||
|                     color: theme.palette.divider, | ||||
|                     borderColor: theme.palette.divider, | ||||
|                     color: 'transparent', | ||||
|                     borderColor: 'transparent', | ||||
|                 }, | ||||
|                 ticks: { | ||||
|                     color: theme.palette.text.secondary, | ||||
| @ -132,47 +130,41 @@ const createOptions = ( | ||||
|         }, | ||||
|     }) as const; | ||||
| 
 | ||||
| interface IUsersChartComponentProps { | ||||
|     userTrends: ExecutiveSummarySchema['userTrends']; | ||||
| } | ||||
| // Vertical line on the hovered chart, filled with gradient. Highlights a section of a chart when you hover over datapoints
 | ||||
| const customHighlightPlugin = { | ||||
|     id: 'customLine', | ||||
|     afterDraw: (chart: Chart) => { | ||||
|         const width = 26; | ||||
|         if (chart.tooltip?.opacity && chart.tooltip.x) { | ||||
|             const x = chart.tooltip.caretX; | ||||
|             const yAxis = chart.scales.y; | ||||
|             const ctx = chart.ctx; | ||||
|             ctx.save(); | ||||
|             const gradient = ctx.createLinearGradient( | ||||
|                 x, | ||||
|                 yAxis.top, | ||||
|                 x, | ||||
|                 yAxis.bottom, | ||||
|             ); | ||||
|             gradient.addColorStop(0, 'rgba(129, 122, 254, 0)'); | ||||
|             gradient.addColorStop(1, 'rgba(129, 122, 254, 0.12)'); | ||||
|             ctx.fillStyle = gradient; | ||||
|             ctx.fillRect( | ||||
|                 x - width / 2, | ||||
|                 yAxis.top, | ||||
|                 width, | ||||
|                 yAxis.bottom - yAxis.top, | ||||
|             ); | ||||
|             ctx.restore(); | ||||
|         } | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| const createData = ( | ||||
|     theme: Theme, | ||||
|     userTrends: ExecutiveSummarySchema['userTrends'], | ||||
| ) => ({ | ||||
|     labels: userTrends.map((item) => item.date), | ||||
|     datasets: [ | ||||
|         { | ||||
|             label: 'Total users', | ||||
|             data: userTrends.map((item) => item.total), | ||||
|             borderColor: theme.palette.primary.light, | ||||
|             backgroundColor: theme.palette.primary.light, | ||||
|             fill: true, | ||||
|         }, | ||||
|         { | ||||
|             label: 'Active users', | ||||
|             data: userTrends.map((item) => item.active), | ||||
|             borderColor: theme.palette.success.border, | ||||
|             backgroundColor: theme.palette.success.border, | ||||
|         }, | ||||
|         { | ||||
|             label: 'Inactive users', | ||||
|             data: userTrends.map((item) => item.inactive), | ||||
|             borderColor: theme.palette.warning.border, | ||||
|             backgroundColor: theme.palette.warning.border, | ||||
|         }, | ||||
|     ], | ||||
| }); | ||||
| 
 | ||||
| const UsersChartComponent: VFC<IUsersChartComponentProps> = ({ | ||||
|     userTrends, | ||||
| }) => { | ||||
| const LineChartComponent: VFC<{ | ||||
|     data: ChartData<'line', (number | ScatterDataPoint | null)[], unknown>; | ||||
| }> = ({ data }) => { | ||||
|     const theme = useTheme(); | ||||
|     const { locationSettings } = useLocationSettings(); | ||||
|     const data = useMemo( | ||||
|         () => createData(theme, userTrends), | ||||
|         [theme, userTrends], | ||||
|     ); | ||||
| 
 | ||||
|     const [tooltip, setTooltip] = useState<null | TooltipState>(null); | ||||
|     const options = useMemo( | ||||
| @ -182,21 +174,25 @@ const UsersChartComponent: VFC<IUsersChartComponentProps> = ({ | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <Line options={options} data={data} /> | ||||
|             <Line | ||||
|                 options={options} | ||||
|                 data={data} | ||||
|                 plugins={[customHighlightPlugin]} | ||||
|             /> | ||||
|             <ChartTooltip tooltip={tooltip} /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| ChartJS.register( | ||||
| Chart.register( | ||||
|     CategoryScale, | ||||
|     LinearScale, | ||||
|     PointElement, | ||||
|     LineElement, | ||||
|     TimeScale, | ||||
|     Title, | ||||
|     Tooltip, | ||||
|     Legend, | ||||
| ); | ||||
| 
 | ||||
| export default UsersChartComponent; | ||||
| // for lazy-loading
 | ||||
| export default LineChartComponent; | ||||
| @ -1,3 +1,42 @@ | ||||
| import { lazy } from 'react'; | ||||
| import { useMemo, type VFC } from 'react'; | ||||
| import 'chartjs-adapter-date-fns'; | ||||
| import { useTheme } from '@mui/material'; | ||||
| import { ExecutiveSummarySchema } from 'openapi'; | ||||
| import { LineChart } from '../LineChart/LineChart'; | ||||
| 
 | ||||
| export const UsersChart = lazy(() => import('./UsersChartComponent')); | ||||
| interface IUsersChartProps { | ||||
|     userTrends: ExecutiveSummarySchema['userTrends']; | ||||
| } | ||||
| 
 | ||||
| export const UsersChart: VFC<IUsersChartProps> = ({ userTrends }) => { | ||||
|     const theme = useTheme(); | ||||
|     const data = useMemo( | ||||
|         () => ({ | ||||
|             labels: userTrends.map((item) => item.date), | ||||
|             datasets: [ | ||||
|                 { | ||||
|                     label: 'Total users', | ||||
|                     data: userTrends.map((item) => item.total), | ||||
|                     borderColor: theme.palette.primary.light, | ||||
|                     backgroundColor: theme.palette.primary.light, | ||||
|                     fill: true, | ||||
|                 }, | ||||
|                 { | ||||
|                     label: 'Active users', | ||||
|                     data: userTrends.map((item) => item.active), | ||||
|                     borderColor: theme.palette.success.border, | ||||
|                     backgroundColor: theme.palette.success.border, | ||||
|                 }, | ||||
|                 { | ||||
|                     label: 'Inactive users', | ||||
|                     data: userTrends.map((item) => item.inactive), | ||||
|                     borderColor: theme.palette.warning.border, | ||||
|                     backgroundColor: theme.palette.warning.border, | ||||
|                 }, | ||||
|             ], | ||||
|         }), | ||||
|         [theme, userTrends], | ||||
|     ); | ||||
| 
 | ||||
|     return <LineChart data={data} />; | ||||
| }; | ||||
|  | ||||
| @ -1,23 +1,27 @@ | ||||
| import { FC, ReactNode } from 'react'; | ||||
| import { Paper, Typography } from '@mui/material'; | ||||
| import { Paper, Typography, styled } from '@mui/material'; | ||||
| import { HelpIcon } from 'component/common/HelpIcon/HelpIcon'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| const StyledPaper = styled(Paper)(({ theme }) => ({ | ||||
|     padding: theme.spacing(3), | ||||
|     borderRadius: `${theme.shape.borderRadiusLarge}px`, | ||||
|     minWidth: 0, // bugfix, see: https://github.com/chartjs/Chart.js/issues/4156#issuecomment-295180128
 | ||||
|     position: 'relative', | ||||
| })); | ||||
| 
 | ||||
| export const Widget: FC<{ | ||||
|     title: ReactNode; | ||||
|     order?: number; | ||||
|     span?: number; | ||||
|     tooltip?: ReactNode; | ||||
| }> = ({ title, order, children, span = 1, tooltip }) => ( | ||||
|     <Paper | ||||
|     <StyledPaper | ||||
|         elevation={0} | ||||
|         sx={(theme) => ({ | ||||
|             padding: 3, | ||||
|             borderRadius: `${theme.shape.borderRadiusLarge}px`, | ||||
|         sx={{ | ||||
|             order, | ||||
|             gridColumn: `span ${span}`, | ||||
|             minWidth: 0, // bugfix, see: https://github.com/chartjs/Chart.js/issues/4156#issuecomment-295180128
 | ||||
|         })} | ||||
|         }} | ||||
|     > | ||||
|         <Typography | ||||
|             variant='h3' | ||||
| @ -35,5 +39,5 @@ export const Widget: FC<{ | ||||
|             /> | ||||
|         </Typography> | ||||
|         {children} | ||||
|     </Paper> | ||||
|     </StyledPaper> | ||||
| ); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user