Skip to content

Commit

Permalink
Feat/dashboard chart tooltip (#6038)
Browse files Browse the repository at this point in the history
Initial version of new chart tooltip
  • Loading branch information
Tymek committed Jan 26, 2024
1 parent 4a025a4 commit 61c6583
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 28 deletions.
Expand Up @@ -9,7 +9,8 @@ import { FlagStats } from './FlagStats/FlagStats';

const StyledGrid = styled(Box)(({ theme }) => ({
display: 'grid',
gridTemplateColumns: `repeat(auto-fill, minmax(600px, 1fr))`,
gridTemplateColumns: `300px 1fr`,
// TODO: responsive grid size
gridAutoRows: '1fr',
gap: theme.spacing(2),
}));
Expand Down
@@ -0,0 +1,89 @@
import { Paper, styled, Typography } from '@mui/material';
import { VFC } from 'react';

export type TooltipState = {
caretX: number;
caretY: number;
title: string;
align: 'left' | 'right';
body: {
title: string;
color: string;
value: string;
}[];
};

interface IChartTooltipProps {
tooltip: TooltipState | null;
}

const StyledList = styled('ul')(({ theme }) => ({
listStyle: 'none',
margin: 0,
padding: 0,
}));

const StyledItem = styled('li')(({ theme }) => ({
marginBottom: theme.spacing(0.5),
display: 'flex',
alignItems: 'center',
}));

const StyledLabelIcon = styled('span')(({ theme }) => ({
display: 'inline-block',
width: 8,
height: 8,
borderRadius: '50%',
marginRight: theme.spacing(1),
}));

export const ChartTooltip: VFC<IChartTooltipProps> = ({ tooltip }) => (
<Paper
elevation={3}
sx={(theme) => ({
top: tooltip?.caretY,
left:
tooltip?.align === 'left'
? tooltip?.caretX + 40
: (tooltip?.caretX || 0) - 220,
position: 'absolute',
display: tooltip ? 'block' : 'none',
width: 220,
padding: theme.spacing(1.5, 2),
pointerEvents: 'none',
})}
>
{
<Typography
variant='body2'
sx={(theme) => ({
marginBottom: theme.spacing(1),
color: theme.palette.text.secondary,
})}
>
{tooltip?.title}
</Typography>
}
<StyledList>
{tooltip?.body.map((item) => (
<StyledItem key={item.title}>
<StyledLabelIcon
sx={{
backgroundColor: item.color,
}}
>
{' '}
</StyledLabelIcon>
<Typography
variant='body2'
sx={{
display: 'inline-block',
}}
>
{item.title}
</Typography>
</StyledItem>
))}
</StyledList>
</Paper>
);
@@ -1,4 +1,4 @@
import { useMemo, type VFC } from 'react';
import { useMemo, useState, type VFC } from 'react';
import {
Chart as ChartJS,
CategoryScale,
Expand All @@ -9,34 +9,88 @@ import {
Tooltip,
Legend,
TimeScale,
Chart,
} from 'chart.js';
import { Line } from 'react-chartjs-2';
import 'chartjs-adapter-date-fns';
import { Paper, Theme, useTheme } from '@mui/material';
import { Paper, Theme, Typography, useTheme } from '@mui/material';
import {
useLocationSettings,
type ILocationSettings,
} from 'hooks/useLocationSettings';
import { formatDateYMD } from 'utils/formatDate';
import { ExecutiveSummarySchema } from 'openapi';
import { ChartTooltip, TooltipState } from './ChartTooltip/ChartTooltip';

const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
const createOptions = (
theme: Theme,
locationSettings: ILocationSettings,
setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>,
) =>
({
responsive: true,
plugins: {
legend: {
position: 'bottom',
labels: {
boxWidth: 12,
padding: 30,
// usePointStyle: true,
generateLabels: (chart: Chart) => {
const datasets = chart.data.datasets;
const {
labels: {
usePointStyle,
pointStyle,
textAlign,
color,
},
} = chart?.legend?.options || {
labels: {},
};
return (chart as any)
._getSortedDatasetMetas()
.map((meta: any) => {
const style = meta.controller.getStyle(
usePointStyle ? 0 : undefined,
);
return {
text: datasets[meta.index].label,
fillStyle: style.backgroundColor,
fontColor: color,
hidden: !meta.visible,
lineWidth: 0,
borderRadius: 6,
strokeStyle: style.borderColor,
pointStyle: pointStyle || style.pointStyle,
textAlign: textAlign || style.textAlign,
datasetIndex: meta.index,
};
});
},
},
},
tooltip: {
callbacks: {
title: (tooltipItems: any) => {
const item = tooltipItems?.[0];
const date =
item?.chart?.data?.labels?.[item.dataIndex];
return date
? formatDateYMD(date, locationSettings.locale)
: '';
},
enabled: false,
external: (context: any) => {
const tooltipModel = context.tooltip;
if (tooltipModel.opacity === 0) {
setTooltip(null);
return;
}

const tooltip = context.tooltip;
setTooltip({
caretX: tooltip?.caretX,
caretY: tooltip?.caretY,
title: tooltip?.title?.join(' ') || '',
align: tooltip?.xAlign || 'left',
body:
tooltip?.body?.map((item: any, index: number) => ({
title: item?.lines?.join(' '),
color: tooltip?.labelColors?.[index]
?.borderColor,
})) || [],
});
},
},
},
Expand All @@ -45,9 +99,16 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
intersect: false,
axis: 'x',
},
elements: {
point: {
radius: 0,
},
},
// cubicInterpolationMode: 'monotone',
color: theme.palette.text.secondary,
scales: {
y: {
beginAtZero: true,
type: 'linear',
grid: {
color: theme.palette.divider,
Expand Down Expand Up @@ -84,23 +145,21 @@ const createData = (
{
label: 'Total users',
data: userTrends.map((item) => item.total),
borderColor: theme.palette.primary.main,
backgroundColor: theme.palette.primary.main,
fill: true,
},
{
label: 'Inactive users',
data: userTrends.map((item) => item.inactive),
borderColor: theme.palette.error.main,
backgroundColor: theme.palette.error.main,
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.main,
backgroundColor: theme.palette.success.main,
fill: true,
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,
},
],
});
Expand All @@ -115,11 +174,29 @@ const UsersChartComponent: VFC<IUsersChartComponentProps> = ({
[theme, userTrends],
);

const options = createOptions(theme, locationSettings);
const [tooltip, setTooltip] = useState<null | TooltipState>(null);
const options = useMemo(
() => createOptions(theme, locationSettings, setTooltip),
[theme, locationSettings],
);

return (
<Paper sx={(theme) => ({ padding: theme.spacing(4) })}>
<Paper
elevation={0}
sx={(theme) => ({
padding: theme.spacing(3),
position: 'relative',
})}
>
<Typography
variant='h3'
sx={(theme) => ({ marginBottom: theme.spacing(3) })}
>
Users
</Typography>
<Line options={options} data={data} />

<ChartTooltip tooltip={tooltip} />
</Paper>
);
};
Expand Down

0 comments on commit 61c6583

Please sign in to comment.