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:
parent
f298d7d511
commit
e6ccd83739
@ -77,7 +77,7 @@ export const ExecutiveDashboard: VFC = () => {
|
|||||||
<Box sx={(theme) => ({ paddingBottom: theme.spacing(4) })}>
|
<Box sx={(theme) => ({ paddingBottom: theme.spacing(4) })}>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
titleElement={
|
titleElement={
|
||||||
<Typography variant='h1' component='h2'>
|
<Typography variant='h1' component='span'>
|
||||||
Dashboard
|
Dashboard
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
|
@ -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 { useMemo, useState, type VFC } from 'react';
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
LinearScale,
|
LinearScale,
|
||||||
PointElement,
|
PointElement,
|
||||||
LineElement,
|
LineElement,
|
||||||
Title,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
TimeScale,
|
TimeScale,
|
||||||
Chart,
|
Chart,
|
||||||
|
type ChartData,
|
||||||
|
type ScatterDataPoint,
|
||||||
} 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';
|
||||||
@ -18,7 +18,6 @@ import {
|
|||||||
useLocationSettings,
|
useLocationSettings,
|
||||||
type ILocationSettings,
|
type ILocationSettings,
|
||||||
} from 'hooks/useLocationSettings';
|
} from 'hooks/useLocationSettings';
|
||||||
import { ExecutiveSummarySchema } from 'openapi';
|
|
||||||
import { ChartTooltip, TooltipState } from './ChartTooltip/ChartTooltip';
|
import { ChartTooltip, TooltipState } from './ChartTooltip/ChartTooltip';
|
||||||
|
|
||||||
const createOptions = (
|
const createOptions = (
|
||||||
@ -34,7 +33,6 @@ const createOptions = (
|
|||||||
labels: {
|
labels: {
|
||||||
boxWidth: 12,
|
boxWidth: 12,
|
||||||
padding: 30,
|
padding: 30,
|
||||||
// usePointStyle: true,
|
|
||||||
generateLabels: (chart: Chart) => {
|
generateLabels: (chart: Chart) => {
|
||||||
const datasets = chart.data.datasets;
|
const datasets = chart.data.datasets;
|
||||||
const {
|
const {
|
||||||
@ -122,8 +120,8 @@ const createOptions = (
|
|||||||
unit: 'month',
|
unit: 'month',
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
color: theme.palette.divider,
|
color: 'transparent',
|
||||||
borderColor: theme.palette.divider,
|
borderColor: 'transparent',
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
@ -132,47 +130,41 @@ const createOptions = (
|
|||||||
},
|
},
|
||||||
}) as const;
|
}) as const;
|
||||||
|
|
||||||
interface IUsersChartComponentProps {
|
// Vertical line on the hovered chart, filled with gradient. Highlights a section of a chart when you hover over datapoints
|
||||||
userTrends: ExecutiveSummarySchema['userTrends'];
|
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 = (
|
const LineChartComponent: VFC<{
|
||||||
theme: Theme,
|
data: ChartData<'line', (number | ScatterDataPoint | null)[], unknown>;
|
||||||
userTrends: ExecutiveSummarySchema['userTrends'],
|
}> = ({ data }) => {
|
||||||
) => ({
|
|
||||||
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 theme = useTheme();
|
const theme = useTheme();
|
||||||
const { locationSettings } = useLocationSettings();
|
const { locationSettings } = useLocationSettings();
|
||||||
const data = useMemo(
|
|
||||||
() => createData(theme, userTrends),
|
|
||||||
[theme, userTrends],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [tooltip, setTooltip] = useState<null | TooltipState>(null);
|
const [tooltip, setTooltip] = useState<null | TooltipState>(null);
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
@ -182,21 +174,25 @@ const UsersChartComponent: VFC<IUsersChartComponentProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Line options={options} data={data} />
|
<Line
|
||||||
|
options={options}
|
||||||
|
data={data}
|
||||||
|
plugins={[customHighlightPlugin]}
|
||||||
|
/>
|
||||||
<ChartTooltip tooltip={tooltip} />
|
<ChartTooltip tooltip={tooltip} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ChartJS.register(
|
Chart.register(
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
LinearScale,
|
LinearScale,
|
||||||
PointElement,
|
PointElement,
|
||||||
LineElement,
|
LineElement,
|
||||||
TimeScale,
|
TimeScale,
|
||||||
Title,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
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} />;
|
||||||
|
};
|
||||||
|
@ -1,23 +1,27 @@
|
|||||||
import { FC, ReactNode } from 'react';
|
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 { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
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<{
|
export const Widget: FC<{
|
||||||
title: ReactNode;
|
title: ReactNode;
|
||||||
order?: number;
|
order?: number;
|
||||||
span?: number;
|
span?: number;
|
||||||
tooltip?: ReactNode;
|
tooltip?: ReactNode;
|
||||||
}> = ({ title, order, children, span = 1, tooltip }) => (
|
}> = ({ title, order, children, span = 1, tooltip }) => (
|
||||||
<Paper
|
<StyledPaper
|
||||||
elevation={0}
|
elevation={0}
|
||||||
sx={(theme) => ({
|
sx={{
|
||||||
padding: 3,
|
|
||||||
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
|
||||||
order,
|
order,
|
||||||
gridColumn: `span ${span}`,
|
gridColumn: `span ${span}`,
|
||||||
minWidth: 0, // bugfix, see: https://github.com/chartjs/Chart.js/issues/4156#issuecomment-295180128
|
}}
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
variant='h3'
|
variant='h3'
|
||||||
@ -35,5 +39,5 @@ export const Widget: FC<{
|
|||||||
/>
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
{children}
|
{children}
|
||||||
</Paper>
|
</StyledPaper>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user