Skip to content

Commit

Permalink
refactor: LineChart component (#6072)
Browse files Browse the repository at this point in the history
Initial version of a reusable trend chart, with a tooltip and vertical highlight
  • Loading branch information
Tymek committed Jan 31, 2024
1 parent f298d7d commit e6ccd83
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 59 deletions.
Expand Up @@ -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>
}
Expand Down
@@ -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';
Expand All @@ -18,7 +18,6 @@ import {
useLocationSettings,
type ILocationSettings,
} from 'hooks/useLocationSettings';
import { ExecutiveSummarySchema } from 'openapi';
import { ChartTooltip, TooltipState } from './ChartTooltip/ChartTooltip';

const createOptions = (
Expand All @@ -34,7 +33,6 @@ const createOptions = (
labels: {
boxWidth: 12,
padding: 30,
// usePointStyle: true,
generateLabels: (chart: Chart) => {
const datasets = chart.data.datasets;
const {
Expand Down Expand Up @@ -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,
Expand All @@ -132,47 +130,41 @@ const createOptions = (
},
}) as const;

interface IUsersChartComponentProps {
userTrends: ExecutiveSummarySchema['userTrends'];
}

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,
},
],
});
// 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 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(
Expand All @@ -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} />;
};
20 changes: 12 additions & 8 deletions frontend/src/component/executiveDashboard/Widget/Widget.tsx
@@ -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'
Expand All @@ -35,5 +39,5 @@ export const Widget: FC<{
/>
</Typography>
{children}
</Paper>
</StyledPaper>
);

0 comments on commit e6ccd83

Please sign in to comment.