mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-19 00:15:43 +01:00
Dashboard custom tooltips (#6327)
Initial refactoring for custom tooltips
This commit is contained in:
parent
822851814a
commit
153c60d335
@ -1,5 +1,6 @@
|
|||||||
import { Paper, styled, Typography } from '@mui/material';
|
import { Box, Paper, styled, Typography } from '@mui/material';
|
||||||
import { VFC } from 'react';
|
import { TooltipItem } from 'chart.js';
|
||||||
|
import { FC, VFC } from 'react';
|
||||||
import { objectId } from 'utils/objectId';
|
import { objectId } from 'utils/objectId';
|
||||||
|
|
||||||
export type TooltipState = {
|
export type TooltipState = {
|
||||||
@ -12,6 +13,7 @@ export type TooltipState = {
|
|||||||
color: string;
|
color: string;
|
||||||
value: string;
|
value: string;
|
||||||
}[];
|
}[];
|
||||||
|
dataPoints: TooltipItem<any>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IChartTooltipProps {
|
interface IChartTooltipProps {
|
||||||
@ -38,21 +40,35 @@ const StyledLabelIcon = styled('span')(({ theme }) => ({
|
|||||||
marginRight: theme.spacing(1),
|
marginRight: theme.spacing(1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const ChartTooltipContainer: FC<IChartTooltipProps> = ({
|
||||||
|
tooltip,
|
||||||
|
children,
|
||||||
|
}) => (
|
||||||
|
<Box
|
||||||
|
sx={(theme) => ({
|
||||||
|
top: tooltip?.caretY,
|
||||||
|
left: tooltip?.align === 'left' ? tooltip?.caretX + 20 : 0,
|
||||||
|
right:
|
||||||
|
tooltip?.align === 'right' ? tooltip?.caretX + 20 : undefined,
|
||||||
|
position: 'absolute',
|
||||||
|
display: tooltip ? 'flex' : 'none',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
zIndex: theme.zIndex.tooltip,
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: tooltip?.align === 'left' ? 'flex-start' : 'flex-end',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
export const ChartTooltip: VFC<IChartTooltipProps> = ({ tooltip }) => (
|
export const ChartTooltip: VFC<IChartTooltipProps> = ({ tooltip }) => (
|
||||||
|
<ChartTooltipContainer tooltip={tooltip}>
|
||||||
<Paper
|
<Paper
|
||||||
elevation={3}
|
elevation={3}
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
top: tooltip?.caretY,
|
|
||||||
left:
|
|
||||||
tooltip?.align === 'left'
|
|
||||||
? tooltip?.caretX + 40
|
|
||||||
: (tooltip?.caretX || 0) - 220,
|
|
||||||
position: 'absolute',
|
|
||||||
display: tooltip ? 'block' : 'none',
|
|
||||||
width: 220,
|
width: 220,
|
||||||
padding: theme.spacing(1.5, 2),
|
padding: theme.spacing(1.5, 2),
|
||||||
pointerEvents: 'none',
|
|
||||||
zIndex: theme.zIndex.tooltip,
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
@ -88,4 +104,5 @@ export const ChartTooltip: VFC<IChartTooltipProps> = ({ tooltip }) => (
|
|||||||
))}
|
))}
|
||||||
</StyledList>
|
</StyledList>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
</ChartTooltipContainer>
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
Filler,
|
Filler,
|
||||||
type ChartData,
|
type ChartData,
|
||||||
type ScatterDataPoint,
|
type ScatterDataPoint,
|
||||||
|
TooltipModel,
|
||||||
} 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';
|
||||||
@ -19,10 +20,44 @@ import {
|
|||||||
useLocationSettings,
|
useLocationSettings,
|
||||||
type ILocationSettings,
|
type ILocationSettings,
|
||||||
} from 'hooks/useLocationSettings';
|
} from 'hooks/useLocationSettings';
|
||||||
import { ChartTooltip, TooltipState } from './ChartTooltip/ChartTooltip';
|
import {
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContainer,
|
||||||
|
TooltipState,
|
||||||
|
} from './ChartTooltip/ChartTooltip';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
|
|
||||||
|
const createTooltip =
|
||||||
|
(setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>) =>
|
||||||
|
(context: {
|
||||||
|
chart: Chart;
|
||||||
|
tooltip: TooltipModel<any>;
|
||||||
|
}) => {
|
||||||
|
const tooltip = context.tooltip;
|
||||||
|
if (tooltip.opacity === 0) {
|
||||||
|
setTooltip(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTooltip({
|
||||||
|
caretX:
|
||||||
|
tooltip?.xAlign === 'right'
|
||||||
|
? context.chart.width - tooltip?.caretX
|
||||||
|
: tooltip?.caretX,
|
||||||
|
caretY: tooltip?.caretY,
|
||||||
|
title: tooltip?.title?.join(' ') || '',
|
||||||
|
align: tooltip?.xAlign === 'right' ? 'right' : 'left',
|
||||||
|
body:
|
||||||
|
tooltip?.body?.map((item: any, index: number) => ({
|
||||||
|
title: item?.lines?.join(' '),
|
||||||
|
color: tooltip?.labelColors?.[index]?.borderColor as string,
|
||||||
|
value: '',
|
||||||
|
})) || [],
|
||||||
|
dataPoints: tooltip?.dataPoints || [],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const createOptions = (
|
const createOptions = (
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
locationSettings: ILocationSettings,
|
locationSettings: ILocationSettings,
|
||||||
@ -82,26 +117,7 @@ const createOptions = (
|
|||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
external: (context: any) => {
|
external: createTooltip(setTooltip),
|
||||||
const tooltip = context.tooltip;
|
|
||||||
if (tooltip.opacity === 0) {
|
|
||||||
setTooltip(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
})) || [],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
locale: locationSettings.locale,
|
locale: locationSettings.locale,
|
||||||
@ -211,7 +227,10 @@ const LineChartComponent: VFC<{
|
|||||||
aspectRatio?: number;
|
aspectRatio?: number;
|
||||||
cover?: ReactNode;
|
cover?: ReactNode;
|
||||||
isLocalTooltip?: boolean;
|
isLocalTooltip?: boolean;
|
||||||
}> = ({ data, aspectRatio, cover, isLocalTooltip }) => {
|
TooltipComponent?: ({
|
||||||
|
tooltip,
|
||||||
|
}: { tooltip: TooltipState | null }) => ReturnType<VFC>;
|
||||||
|
}> = ({ data, aspectRatio, cover, isLocalTooltip, TooltipComponent }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { locationSettings } = useLocationSettings();
|
const { locationSettings } = useLocationSettings();
|
||||||
|
|
||||||
@ -239,7 +258,15 @@ const LineChartComponent: VFC<{
|
|||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={!cover}
|
condition={!cover}
|
||||||
show={<ChartTooltip tooltip={tooltip} />}
|
show={
|
||||||
|
TooltipComponent ? (
|
||||||
|
<ChartTooltipContainer tooltip={tooltip}>
|
||||||
|
<TooltipComponent tooltip={tooltip} />
|
||||||
|
</ChartTooltipContainer>
|
||||||
|
) : (
|
||||||
|
<ChartTooltip tooltip={tooltip} />
|
||||||
|
)
|
||||||
|
}
|
||||||
elseShow={
|
elseShow={
|
||||||
<StyledCover>
|
<StyledCover>
|
||||||
<StyledCoverContent>
|
<StyledCoverContent>
|
||||||
|
@ -3,15 +3,84 @@ import 'chartjs-adapter-date-fns';
|
|||||||
import { ExecutiveSummarySchema } from 'openapi';
|
import { ExecutiveSummarySchema } from 'openapi';
|
||||||
import { LineChart } from '../LineChart/LineChart';
|
import { LineChart } from '../LineChart/LineChart';
|
||||||
import { useProjectChartData } from '../useProjectChartData';
|
import { useProjectChartData } from '../useProjectChartData';
|
||||||
|
import { TooltipState } from '../LineChart/ChartTooltip/ChartTooltip';
|
||||||
|
import { Box, Paper, styled } from '@mui/material';
|
||||||
|
import { Badge } from 'component/common/Badge/Badge';
|
||||||
|
|
||||||
interface IFlagsProjectChartProps {
|
interface IFlagsProjectChartProps {
|
||||||
projectFlagTrends: ExecutiveSummarySchema['projectFlagTrends'];
|
projectFlagTrends: ExecutiveSummarySchema['projectFlagTrends'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledTooltipItemContainer = styled(Paper)(({ theme }) => ({
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledItemHeader = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: theme.spacing(2),
|
||||||
|
alignItems: 'center',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const getHealthBadgeColor = (health?: number | null) => {
|
||||||
|
if (health === undefined || health === null) {
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (health >= 75) {
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (health >= 50) {
|
||||||
|
return 'warning';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'error';
|
||||||
|
};
|
||||||
|
|
||||||
|
const TooltipComponent: VFC<{ tooltip: TooltipState | null }> = ({
|
||||||
|
tooltip,
|
||||||
|
}) => {
|
||||||
|
const data = tooltip?.dataPoints.map((point) => {
|
||||||
|
return {
|
||||||
|
title: point.dataset.label,
|
||||||
|
color: point.dataset.borderColor,
|
||||||
|
value: point.raw as number,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={(theme) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: theme.spacing(3),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{data?.map((point, index) => (
|
||||||
|
<StyledTooltipItemContainer elevation={3} key={point.title}>
|
||||||
|
<StyledItemHeader>
|
||||||
|
<div>{point.title}</div>{' '}
|
||||||
|
<Badge color={getHealthBadgeColor(point.value)}>
|
||||||
|
{point.value}%
|
||||||
|
</Badge>
|
||||||
|
</StyledItemHeader>
|
||||||
|
</StyledTooltipItemContainer>
|
||||||
|
)) || null}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const ProjectHealthChart: VFC<IFlagsProjectChartProps> = ({
|
export const ProjectHealthChart: VFC<IFlagsProjectChartProps> = ({
|
||||||
projectFlagTrends,
|
projectFlagTrends,
|
||||||
}) => {
|
}) => {
|
||||||
const data = useProjectChartData(projectFlagTrends, 'health');
|
const data = useProjectChartData(projectFlagTrends, 'health');
|
||||||
|
|
||||||
return <LineChart data={data} isLocalTooltip />;
|
return (
|
||||||
|
<LineChart
|
||||||
|
data={data}
|
||||||
|
isLocalTooltip
|
||||||
|
TooltipComponent={TooltipComponent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user