fix: show all the category for cardinality distribution graph (#24098)

* fix: show all the category for cardinality distribution graph

* feat: enhance CardinalityDistributionChart with category selection and custom Y-axis ticks

* fix: update cursor fill color in visualisation charts for better visibility

---------

Co-authored-by: Harsh Vador <58542468+harsh-vador@users.noreply.github.com>
This commit is contained in:
Shailesh Parmar 2025-11-06 19:53:37 +05:30 committed by GitHub
parent 4da984cd56
commit dd8b6481b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 99 additions and 17 deletions

View File

@ -13,13 +13,13 @@
import { Box, Card, Divider, Typography, useTheme } from '@mui/material'; import { Box, Card, Divider, Typography, useTheme } from '@mui/material';
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import { useMemo } from 'react'; import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
Bar, Bar,
BarChart, BarChart,
CartesianGrid, CartesianGrid,
Legend, Cell,
ResponsiveContainer, ResponsiveContainer,
Tooltip, Tooltip,
TooltipProps, TooltipProps,
@ -52,6 +52,7 @@ const CardinalityDistributionChart = ({
}: CardinalityDistributionChartProps) => { }: CardinalityDistributionChartProps) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const firstDayAllUnique = const firstDayAllUnique =
data.firstDayData?.cardinalityDistribution?.allValuesUnique ?? false; data.firstDayData?.cardinalityDistribution?.allValuesUnique ?? false;
@ -170,6 +171,52 @@ const CardinalityDistributionChart = ({
'message.all-values-unique-no-distribution-available' 'message.all-values-unique-no-distribution-available'
); );
const handleCategoryClick = (categoryName: string) => {
setSelectedCategory((prev) =>
prev === categoryName ? null : categoryName
);
};
const CustomYAxisTick = (props: {
x?: number;
y?: number;
payload?: { value: string };
}) => {
const { x, y, payload } = props;
if (!payload) {
return null;
}
const categoryName = payload.value;
const isSelected = selectedCategory === categoryName;
const isHighlighted = selectedCategory && selectedCategory !== categoryName;
return (
<g transform={`translate(${x},${y})`}>
<text
cursor="pointer"
dy={4}
fill={
isSelected
? theme.palette.primary.main
: isHighlighted
? theme.palette.grey[400]
: theme.palette.grey[700]
}
fontSize={12}
fontWeight={isSelected ? 600 : 400}
opacity={isHighlighted ? 0.5 : 1}
textAnchor="end"
x={-8}
onClick={() => handleCategoryClick(categoryName)}>
{categoryName.length > 15
? `${categoryName.slice(0, 15)}...`
: categoryName}
</text>
</g>
);
};
return ( return (
<Box <Box
data-testid="chart-container" data-testid="chart-container"
@ -203,6 +250,8 @@ const CardinalityDistributionChart = ({
'MMM dd, yyyy' 'MMM dd, yyyy'
); );
const containerHeight = Math.max(350, graphData.length * 30);
return ( return (
<Box <Box
key={key} key={key}
@ -236,16 +285,21 @@ const CardinalityDistributionChart = ({
})}: ${cardinalityData.categories?.length || 0}`} })}: ${cardinalityData.categories?.length || 0}`}
</DataPill> </DataPill>
</Box> </Box>
<Box sx={{ flex: 1, minHeight: 350 }}> <Box
sx={{
flex: 1,
minHeight: 350,
overflowX: 'hidden',
}}>
<ResponsiveContainer <ResponsiveContainer
debounce={200} debounce={200}
height={containerHeight}
id={`${key}-cardinality`} id={`${key}-cardinality`}
minHeight={300}> width="100%">
<BarChart <BarChart
className="w-full" className="w-full"
data={graphData} data={graphData}
layout="vertical" layout="vertical">
margin={{ left: 16 }}>
<CartesianGrid <CartesianGrid
horizontal={renderHorizontalGridLine} horizontal={renderHorizontalGridLine}
stroke={GRAPH_BACKGROUND_COLOR} stroke={GRAPH_BACKGROUND_COLOR}
@ -267,29 +321,49 @@ const CardinalityDistributionChart = ({
axisLine={false} axisLine={false}
dataKey="name" dataKey="name"
padding={{ top: 16, bottom: 16 }} padding={{ top: 16, bottom: 16 }}
tick={{ fontSize: 12 }} tick={<CustomYAxisTick />}
tickFormatter={(value: string) =>
value?.length > 15
? `${value.slice(0, 15)}...`
: value
}
tickLine={false} tickLine={false}
type="category" type="category"
width={120} width={120}
/> />
<Legend />
<Tooltip <Tooltip
content={renderTooltip} content={renderTooltip}
cursor={{ cursor={{
fill: theme.palette.grey[100],
stroke: theme.palette.grey[200], stroke: theme.palette.grey[200],
strokeDasharray: '3 3', strokeDasharray: '3 3',
}} }}
/> />
<Bar <Bar
barSize={22}
dataKey="percentage" dataKey="percentage"
fill={CHART_BLUE_1} radius={[0, 8, 8, 0]}>
radius={[0, 8, 8, 0]} {graphData.map((entry) => {
/> const isSelected =
selectedCategory === entry.name;
const isHighlighted =
selectedCategory &&
selectedCategory !== entry.name;
return (
<Cell
cursor="pointer"
fill={
isSelected
? theme.palette.primary.main
: isHighlighted
? theme.palette.grey[300]
: CHART_BLUE_1
}
key={`cell-${entry.name}`}
opacity={isHighlighted ? 0.3 : 1}
onClick={() =>
handleCategoryClick(entry.name)
}
/>
);
})}
</Bar>
</BarChart> </BarChart>
</ResponsiveContainer> </ResponsiveContainer>
</Box> </Box>

View File

@ -117,6 +117,7 @@ const CustomBarChart = ({
/> />
} }
cursor={{ cursor={{
fill: theme.palette.grey[100],
stroke: theme.palette.grey[200], stroke: theme.palette.grey[200],
strokeDasharray: '3 3', strokeDasharray: '3 3',
}} }}

View File

@ -180,6 +180,7 @@ const DataDistributionHistogram = ({
/> />
} }
cursor={{ cursor={{
fill: theme.palette.grey[100],
stroke: theme.palette.grey[200], stroke: theme.palette.grey[200],
strokeDasharray: '3 3', strokeDasharray: '3 3',
}} }}

View File

@ -15,11 +15,17 @@ import React from 'react';
window.React = React; window.React = React;
jest.mock('recharts', () => ({ jest.mock('recharts', () => ({
Bar: jest.fn().mockImplementation(() => <div>Bar</div>), Bar: jest.fn().mockImplementation(({ children }) => (
<div>
<p>Bar</p>
{children}
</div>
)),
Line: jest.fn().mockImplementation(() => <div>Line</div>), Line: jest.fn().mockImplementation(() => <div>Line</div>),
Brush: jest.fn().mockImplementation(() => <div>Brush</div>), Brush: jest.fn().mockImplementation(() => <div>Brush</div>),
Area: jest.fn().mockImplementation(() => <div>Area</div>), Area: jest.fn().mockImplementation(() => <div>Area</div>),
Scatter: jest.fn().mockImplementation(() => <div>Scatter</div>), Scatter: jest.fn().mockImplementation(() => <div>Scatter</div>),
Cell: jest.fn().mockImplementation(() => <div>Cell</div>),
CartesianGrid: jest.fn().mockImplementation(() => <div>CartesianGrid</div>), CartesianGrid: jest.fn().mockImplementation(() => <div>CartesianGrid</div>),
Legend: jest.fn().mockImplementation(() => <div>Legend</div>), Legend: jest.fn().mockImplementation(() => <div>Legend</div>),
Tooltip: jest.fn().mockImplementation(() => <div>Tooltip</div>), Tooltip: jest.fn().mockImplementation(() => <div>Tooltip</div>),