update chart tooltip designs (#15426)

closes
https://discord.com/channels/1130383047699738754/1430845815634657320

~~TODO: add truncation~~
This commit is contained in:
nitin
2025-10-30 19:07:59 +05:30
committed by GitHub
parent 2461296158
commit 28333815d5
7 changed files with 292 additions and 61 deletions
@@ -3,23 +3,36 @@ import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { IconArrowUpRight } from 'twenty-ui/display';
const StyledTooltipContent = styled.div`
const StyledTooltip = styled.div`
background: ${({ theme }) => theme.background.primary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.md};
box-shadow: ${({ theme }) => theme.boxShadow.strong};
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(3)};
padding: ${({ theme }) => theme.spacing(3)};
gap: 2px;
max-width: min(300px, calc(100vw - 40px));
min-width: 160px;
pointer-events: none;
`;
const StyledTooltipContent = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(3)};
padding: ${({ theme }) => theme.spacing(2)};
`;
const StyledTooltipRow = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.extraLight};
display: flex;
font-size: ${({ theme }) => theme.font.size.xs};
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledTooltipRowContainer = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
`;
@@ -31,22 +44,68 @@ const StyledDot = styled.div<{ $color: string }>`
flex-shrink: 0;
`;
const StyledTooltipValue = styled.span`
margin-left: auto;
white-space: nowrap;
`;
const StyledTooltipLink = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.light};
cursor: default;
display: flex;
justify-content: space-between;
height: ${({ theme }) => theme.spacing(6)};
font-weight: ${({ theme }) => theme.font.weight.regular};
padding-inline: ${({ theme }) => theme.spacing(2)};
line-height: 140%;
`;
const StyledTooltipSeparator = styled.div`
background-color: ${({ theme }) => theme.border.color.light};
min-height: 1px;
width: 100%;
`;
const StyledTooltipHeader = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.sm};
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.medium};
line-height: 140%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const StyledTooltipRowRightContent = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
font-size: ${({ theme }) => theme.font.size.xs};
color: ${({ theme }) => theme.font.color.extraLight};
font-weight: ${({ theme }) => theme.font.weight.regular};
gap: ${({ theme }) => theme.spacing(2)};
min-width: 0;
width: 100%;
`;
const StyledTooltipLabel = styled.span`
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const StyledTooltipValue = styled.span`
flex-shrink: 0;
font-weight: ${({ theme }) => theme.font.weight.medium};
white-space: nowrap;
`;
const StyledHorizontalSectionPadding = styled.div<{
$addTop?: boolean;
$addBottom?: boolean;
}>`
padding-inline: ${({ theme }) => theme.spacing(1)};
margin-top: ${({ $addTop, theme }) => ($addTop ? theme.spacing(1) : 0)};
margin-bottom: ${({ $addBottom, theme }) =>
$addBottom ? theme.spacing(1) : 0};
`;
export type GraphWidgetTooltipItem = {
@@ -58,32 +117,47 @@ export type GraphWidgetTooltipItem = {
type GraphWidgetTooltipProps = {
items: GraphWidgetTooltipItem[];
showClickHint?: boolean;
title?: string;
indexLabel?: string;
};
export const GraphWidgetTooltip = ({
items,
showClickHint = false,
title,
indexLabel,
}: GraphWidgetTooltipProps) => {
const theme = useTheme();
return (
<StyledTooltipContent>
{title && <StyledTooltipHeader>{title}</StyledTooltipHeader>}
{items.map((item, index) => (
<StyledTooltipRow key={index}>
<StyledDot $color={item.dotColor} />
<span>{item.label}</span>
<StyledTooltipValue>{item.formattedValue}</StyledTooltipValue>
</StyledTooltipRow>
))}
<StyledTooltip>
<StyledHorizontalSectionPadding $addTop $addBottom={!showClickHint}>
<StyledTooltipContent>
{indexLabel && (
<StyledTooltipHeader>{indexLabel}</StyledTooltipHeader>
)}
<StyledTooltipRowContainer>
{items.map((item, index) => (
<StyledTooltipRow key={index}>
<StyledDot $color={item.dotColor} />
<StyledTooltipRowRightContent>
<StyledTooltipLabel>{item.label}</StyledTooltipLabel>
<StyledTooltipValue>{item.formattedValue}</StyledTooltipValue>
</StyledTooltipRowRightContent>
</StyledTooltipRow>
))}
</StyledTooltipRowContainer>
</StyledTooltipContent>
</StyledHorizontalSectionPadding>
{showClickHint && (
<StyledTooltipLink>
<span>{t`Click to see data`}</span>
<IconArrowUpRight size={theme.icon.size.sm} />
</StyledTooltipLink>
<>
<StyledTooltipSeparator />
<StyledHorizontalSectionPadding $addBottom>
<StyledTooltipLink>
<span>{t`Click to see data`}</span>
<IconArrowUpRight size={theme.icon.size.sm} />
</StyledTooltipLink>
</StyledHorizontalSectionPadding>
</>
)}
</StyledTooltipContent>
</StyledTooltip>
);
};
@@ -253,6 +253,8 @@ export const Stacked: Story = {
data={args.data}
indexBy={args.indexBy}
keys={args.keys}
groupMode={args.groupMode}
seriesLabels={args.seriesLabels}
showLegend={args.showLegend}
showGrid={args.showGrid}
xAxisLabel={args.xAxisLabel}
@@ -492,6 +494,124 @@ export const Currency: Story = {
),
};
export const GroupedWithAllBarsTooltip: Story = {
args: {
data: [
{
period: 'January',
lastYear: 18000,
thisYear: 19500,
to: '/comparison/january',
},
{
period: 'February',
lastYear: 20000,
thisYear: 20000,
to: '/comparison/february',
},
{
period: 'March',
lastYear: 22000,
thisYear: 24500,
to: '/comparison/march',
},
{
period: 'April',
lastYear: 21000,
thisYear: 23000,
to: '/comparison/april',
},
],
indexBy: 'period',
keys: ['lastYear', 'thisYear'],
seriesLabels: {
lastYear: 'Last year',
thisYear: 'This year',
},
displayType: 'shortNumber',
showLegend: true,
showGrid: true,
xAxisLabel: 'Period',
yAxisLabel: 'Revenue',
groupMode: 'grouped',
id: 'bar-chart-grouped-all-tooltip',
enableGroupTooltip: true,
},
render: (args) => (
<Container>
<GraphWidgetBarChart
data={args.data}
indexBy={args.indexBy}
keys={args.keys}
seriesLabels={args.seriesLabels}
displayType={args.displayType}
showLegend={args.showLegend}
showGrid={args.showGrid}
xAxisLabel={args.xAxisLabel}
yAxisLabel={args.yAxisLabel}
groupMode={args.groupMode}
id={args.id}
enableGroupTooltip={args.enableGroupTooltip}
/>
</Container>
),
};
export const GroupedDefaultTooltip: Story = {
args: {
data: [
{
period: 'January',
lastYear: 18000,
thisYear: 19500,
to: '/comparison/january',
},
{
period: 'February',
lastYear: 20000,
thisYear: 20000,
to: '/comparison/february',
},
{
period: 'March',
lastYear: 22000,
thisYear: 24500,
to: '/comparison/march',
},
],
indexBy: 'period',
keys: ['lastYear', 'thisYear'],
seriesLabels: {
lastYear: 'Last year',
thisYear: 'This year',
},
displayType: 'shortNumber',
showLegend: true,
showGrid: true,
xAxisLabel: 'Period',
yAxisLabel: 'Revenue',
groupMode: 'grouped',
id: 'bar-chart-grouped-default',
},
render: (args) => (
<Container>
<GraphWidgetBarChart
data={args.data}
indexBy={args.indexBy}
keys={args.keys}
seriesLabels={args.seriesLabels}
displayType={args.displayType}
showLegend={args.showLegend}
showGrid={args.showGrid}
xAxisLabel={args.xAxisLabel}
yAxisLabel={args.yAxisLabel}
groupMode={args.groupMode}
id={args.id}
/>
</Container>
),
};
export const Catalog: Story = {
decorators: [CatalogDecorator],
parameters: {
@@ -25,6 +25,7 @@ export const Default: Story = {
},
],
showClickHint: false,
indexLabel: 'March 09, 2024',
},
};
@@ -45,21 +46,38 @@ export const MultipleItems: Story = {
args: {
items: [
{
label: 'Q1',
formattedValue: '$12,345',
label: 'Last year',
formattedValue: '20k',
dotColor: 'blue',
},
{
label: 'Q2',
formattedValue: '$23,456',
dotColor: 'green',
},
{
label: 'Q3',
formattedValue: '$34,567',
dotColor: 'red',
label: 'This year',
formattedValue: '20k',
dotColor: 'purple',
},
],
showClickHint: false,
showClickHint: true,
indexLabel: 'February 2',
},
};
export const SuperLongText: Story = {
args: {
items: [
{
label:
'Total Annual Recurring Revenue (North America Region including Canada)',
formattedValue: '$2,450,000',
dotColor: 'blue',
},
{
label: 'Customer Acquisition Cost (Marketing & Sales Combined)',
formattedValue: '$125,500',
dotColor: 'purple',
},
],
showClickHint: true,
indexLabel:
'Q4 2024 Financial Year End (October - December) - North America Regional Performance Summary',
},
};
@@ -42,6 +42,7 @@ type GraphWidgetBarChartProps = {
seriesLabels?: Record<string, string>;
rangeMin?: number;
rangeMax?: number;
enableGroupTooltip?: boolean;
} & GraphValueFormatOptions;
const StyledContainer = styled.div`
@@ -69,6 +70,7 @@ export const GraphWidgetBarChart = ({
seriesLabels,
rangeMin,
rangeMax,
enableGroupTooltip,
displayType,
decimals,
prefix,
@@ -81,6 +83,9 @@ export const GraphWidgetBarChart = ({
const [chartHeight, setChartHeight] = useState<number>(0);
const containerRef = useRef<HTMLDivElement>(null);
const shouldEnableGroupTooltip =
enableGroupTooltip ?? groupMode === 'stacked';
const formatOptions: GraphValueFormatOptions = {
displayType,
decimals,
@@ -112,6 +117,7 @@ export const GraphWidgetBarChart = ({
data,
indexBy,
formatOptions,
enableGroupTooltip: shouldEnableGroupTooltip,
});
const isLargeChart = data.length * keys.length > LABEL_THRESHOLD;
@@ -140,9 +146,9 @@ export const GraphWidgetBarChart = ({
return (
<GraphWidgetTooltip
items={[tooltipData.tooltipItem]}
items={tooltipData.tooltipItems}
showClickHint={tooltipData.showClickHint}
title={tooltipData.title}
indexLabel={tooltipData.indexLabel}
/>
);
};
@@ -13,6 +13,7 @@ type UseBarChartTooltipProps = {
data: BarChartDataItem[];
indexBy: string;
formatOptions: GraphValueFormatOptions;
enableGroupTooltip?: boolean;
};
export const useBarChartTooltip = ({
@@ -21,26 +22,38 @@ export const useBarChartTooltip = ({
data,
indexBy,
formatOptions,
enableGroupTooltip = true,
}: UseBarChartTooltipProps) => {
const renderTooltip = (datum: ComputedDatum<BarDatum>) => {
const hoveredKey = hoveredBar?.key;
if (!isDefined(hoveredKey)) return null;
const enrichedKey = enrichedKeys.find((item) => item.key === hoveredKey);
if (!enrichedKey) return null;
const dataItem = data.find((d) => d[indexBy] === datum.indexValue);
const seriesValue = Number(datum.data[hoveredKey] || 0);
const tooltipItem = {
label: enrichedKey.label,
formattedValue: formatGraphValue(seriesValue, formatOptions),
dotColor: enrichedKey.colorScheme.solid,
};
let keysToShow: BarChartEnrichedKey[];
if (enableGroupTooltip) {
keysToShow = enrichedKeys;
} else {
const hoveredKey = hoveredBar?.key;
if (!isDefined(hoveredKey)) return null;
const enrichedKey = enrichedKeys.find((item) => item.key === hoveredKey);
if (!isDefined(enrichedKey)) return null;
keysToShow = [enrichedKey];
}
const tooltipItems = keysToShow.map((enrichedKey) => {
const seriesValue = Number(datum.data[enrichedKey.key] ?? 0);
return {
label: enrichedKey.label,
formattedValue: formatGraphValue(seriesValue, formatOptions),
dotColor: enrichedKey.colorScheme.solid,
};
});
return {
tooltipItem,
tooltipItems,
showClickHint: isDefined(dataItem?.to),
title: String(datum.indexValue),
indexLabel: String(datum.indexValue),
};
};
@@ -144,7 +144,7 @@ export const GraphWidgetLineChart = ({
<GraphWidgetTooltip
items={tooltipData.items}
showClickHint={tooltipData.showClickHint}
title={tooltipData.title}
indexLabel={tooltipData.indexLabel}
/>
);
};
@@ -158,7 +158,7 @@ export const GraphWidgetLineChart = ({
<GraphWidgetTooltip
items={tooltipData.items}
showClickHint={tooltipData.showClickHint}
title={tooltipData.title}
indexLabel={tooltipData.indexLabel}
/>
);
};
@@ -31,7 +31,7 @@ export const useLineChartTooltip = ({
return {
items: [],
showClickHint: false,
title: undefined,
indexLabel: undefined,
};
}
@@ -67,7 +67,7 @@ export const useLineChartTooltip = ({
return {
items: tooltipItems,
showClickHint: hasClickablePoint,
title: isDefined(xValue) ? String(xValue) : undefined,
indexLabel: isDefined(xValue) ? String(xValue) : undefined,
};
};
@@ -90,7 +90,7 @@ export const useLineChartTooltip = ({
},
],
showClickHint: isDefined(dataPoint?.to),
title: isDefined(point.data.x) ? String(point.data.x) : undefined,
indexLabel: isDefined(point.data.x) ? String(point.data.x) : undefined,
};
};