mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
Feat/dashboard chart tooltip (#6038)
Initial version of new chart tooltip
This commit is contained in:
parent
4a025a4b4b
commit
61c6583e24
@ -9,7 +9,8 @@ import { FlagStats } from './FlagStats/FlagStats';
|
|||||||
|
|
||||||
const StyledGrid = styled(Box)(({ theme }) => ({
|
const StyledGrid = styled(Box)(({ theme }) => ({
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: `repeat(auto-fill, minmax(600px, 1fr))`,
|
gridTemplateColumns: `300px 1fr`,
|
||||||
|
// TODO: responsive grid size
|
||||||
gridAutoRows: '1fr',
|
gridAutoRows: '1fr',
|
||||||
gap: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
}));
|
}));
|
||||||
|
@ -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 {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
@ -9,34 +9,88 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
TimeScale,
|
TimeScale,
|
||||||
|
Chart,
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import { Line } from 'react-chartjs-2';
|
import { Line } from 'react-chartjs-2';
|
||||||
import 'chartjs-adapter-date-fns';
|
import 'chartjs-adapter-date-fns';
|
||||||
import { Paper, Theme, useTheme } from '@mui/material';
|
import { Paper, Theme, Typography, useTheme } from '@mui/material';
|
||||||
import {
|
import {
|
||||||
useLocationSettings,
|
useLocationSettings,
|
||||||
type ILocationSettings,
|
type ILocationSettings,
|
||||||
} from 'hooks/useLocationSettings';
|
} from 'hooks/useLocationSettings';
|
||||||
import { formatDateYMD } from 'utils/formatDate';
|
|
||||||
import { ExecutiveSummarySchema } from 'openapi';
|
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,
|
responsive: true,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
position: 'bottom',
|
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: {
|
tooltip: {
|
||||||
callbacks: {
|
enabled: false,
|
||||||
title: (tooltipItems: any) => {
|
external: (context: any) => {
|
||||||
const item = tooltipItems?.[0];
|
const tooltipModel = context.tooltip;
|
||||||
const date =
|
if (tooltipModel.opacity === 0) {
|
||||||
item?.chart?.data?.labels?.[item.dataIndex];
|
setTooltip(null);
|
||||||
return date
|
return;
|
||||||
? formatDateYMD(date, locationSettings.locale)
|
}
|
||||||
: '';
|
|
||||||
},
|
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,
|
||||||
|
})) || [],
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -45,9 +99,16 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) =>
|
|||||||
intersect: false,
|
intersect: false,
|
||||||
axis: 'x',
|
axis: 'x',
|
||||||
},
|
},
|
||||||
|
elements: {
|
||||||
|
point: {
|
||||||
|
radius: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// cubicInterpolationMode: 'monotone',
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
grid: {
|
grid: {
|
||||||
color: theme.palette.divider,
|
color: theme.palette.divider,
|
||||||
@ -84,23 +145,21 @@ const createData = (
|
|||||||
{
|
{
|
||||||
label: 'Total users',
|
label: 'Total users',
|
||||||
data: userTrends.map((item) => item.total),
|
data: userTrends.map((item) => item.total),
|
||||||
borderColor: theme.palette.primary.main,
|
borderColor: theme.palette.primary.light,
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.light,
|
||||||
fill: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Inactive users',
|
|
||||||
data: userTrends.map((item) => item.inactive),
|
|
||||||
borderColor: theme.palette.error.main,
|
|
||||||
backgroundColor: theme.palette.error.main,
|
|
||||||
fill: true,
|
fill: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Active users',
|
label: 'Active users',
|
||||||
data: userTrends.map((item) => item.active),
|
data: userTrends.map((item) => item.active),
|
||||||
borderColor: theme.palette.success.main,
|
borderColor: theme.palette.success.border,
|
||||||
backgroundColor: theme.palette.success.main,
|
backgroundColor: theme.palette.success.border,
|
||||||
fill: true,
|
},
|
||||||
|
{
|
||||||
|
label: 'Inactive users',
|
||||||
|
data: userTrends.map((item) => item.inactive),
|
||||||
|
borderColor: theme.palette.warning.border,
|
||||||
|
backgroundColor: theme.palette.warning.border,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -115,11 +174,29 @@ const UsersChartComponent: VFC<IUsersChartComponentProps> = ({
|
|||||||
[theme, userTrends],
|
[theme, userTrends],
|
||||||
);
|
);
|
||||||
|
|
||||||
const options = createOptions(theme, locationSettings);
|
const [tooltip, setTooltip] = useState<null | TooltipState>(null);
|
||||||
|
const options = useMemo(
|
||||||
|
() => createOptions(theme, locationSettings, setTooltip),
|
||||||
|
[theme, locationSettings],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
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} />
|
<Line options={options} data={data} />
|
||||||
|
|
||||||
|
<ChartTooltip tooltip={tooltip} />
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user