1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00

refactor: LineChart component (#6072)

Initial version of a reusable trend chart, with a tooltip and vertical highlight
This commit is contained in:
Tymoteusz Czech 2024-01-31 10:07:29 +01:00 committed by GitHub
parent f298d7d511
commit e6ccd83739
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 101 additions and 59 deletions

View File

@ -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>
}

View File

@ -0,0 +1,3 @@
import { lazy } from 'react';
export const LineChart = lazy(() => import('./LineChartComponent'));

View File

@ -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;

View File

@ -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} />;
};

View File

@ -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>
);