Skip to content

Commit

Permalink
fix(replays): Memory Chart Tooltip Overflow (#38746)
Browse files Browse the repository at this point in the history
Configures the Tooltip class to make use of the `appendToBody` field to tooltips can render outside of their containing element. 

Callsites need to set these values: `appendToBody: true, renderMode: 'html', chartId: 'some-id-attr'`

Closes #37238
  • Loading branch information
danecando authored Sep 15, 2022
1 parent 05b76b9 commit 81573cd
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 33 deletions.
71 changes: 44 additions & 27 deletions static/app/components/charts/baseChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'echarts/lib/component/toolbox';
import 'zrender/lib/svg/svg';

import {forwardRef, useMemo} from 'react';
import {useTheme} from '@emotion/react';
import {css, Global, useTheme} from '@emotion/react';
import styled from '@emotion/styled';
import type {
AxisPointerComponentOption,
Expand Down Expand Up @@ -465,6 +465,8 @@ function BaseChartUnwrapped({
: undefined;
const bucketSize = seriesData ? seriesData[1][0] - seriesData[0][0] : undefined;

const isTooltipPortalled = tooltip?.appendToBody;

const tooltipOrNone =
tooltip !== null
? Tooltip({
Expand All @@ -474,6 +476,9 @@ function BaseChartUnwrapped({
utc,
bucketSize,
...tooltip,
className: isTooltipPortalled
? `${tooltip?.className ?? ''} chart-tooltip-portal`
: tooltip?.className,
})
: undefined;

Expand Down Expand Up @@ -538,6 +543,7 @@ function BaseChartUnwrapped({

return (
<ChartContainer autoHeightResize={autoHeightResize} data-test-id={dataTestId}>
{isTooltipPortalled && <Global styles={getPortalledTooltipStyles({theme})} />}
<ReactEchartsCore
ref={forwardedRef}
echarts={echarts}
Expand All @@ -559,32 +565,29 @@ function BaseChartUnwrapped({
);
}

// Contains styling for chart elements as we can't easily style those
// elements directly
const ChartContainer = styled('div')<{autoHeightResize: boolean}>`
${p => p.autoHeightResize && 'height: 100%;'}
// Tooltip styles shared for regular and portalled tooltips
const getTooltipStyles = (p: {theme: Theme}) => css`
/* Tooltip styling */
.tooltip-series,
.tooltip-date {
color: ${p => p.theme.subText};
font-family: ${p => p.theme.text.family};
color: ${p.theme.subText};
font-family: ${p.theme.text.family};
font-variant-numeric: tabular-nums;
padding: ${space(1)} ${space(2)};
border-radius: ${p => p.theme.borderRadius} ${p => p.theme.borderRadius} 0 0;
border-radius: ${p.theme.borderRadius} ${p.theme.borderRadius} 0 0;
}
.tooltip-series {
border-bottom: none;
}
.tooltip-series-solo {
border-radius: ${p => p.theme.borderRadius};
border-radius: ${p.theme.borderRadius};
}
.tooltip-label {
margin-right: ${space(1)};
}
.tooltip-label strong {
font-weight: normal;
color: ${p => p.theme.textColor};
color: ${p.theme.textColor};
}
.tooltip-label-indent {
margin-left: 18px;
Expand All @@ -595,11 +598,11 @@ const ChartContainer = styled('div')<{autoHeightResize: boolean}>`
align-items: baseline;
}
.tooltip-date {
border-top: solid 1px ${p => p.theme.innerBorder};
border-top: solid 1px ${p.theme.innerBorder};
text-align: center;
position: relative;
width: auto;
border-radius: ${p => p.theme.borderRadiusBottom};
border-radius: ${p.theme.borderRadiusBottom};
}
.tooltip-arrow {
top: 100%;
Expand All @@ -608,12 +611,12 @@ const ChartContainer = styled('div')<{autoHeightResize: boolean}>`
pointer-events: none;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 8px solid ${p => p.theme.backgroundElevated};
border-top: 8px solid ${p.theme.backgroundElevated};
margin-left: -8px;
&:before {
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 8px solid ${p => p.theme.translucentBorder};
border-top: 8px solid ${p.theme.translucentBorder};
content: '';
display: block;
position: absolute;
Expand All @@ -623,26 +626,18 @@ const ChartContainer = styled('div')<{autoHeightResize: boolean}>`
}
}
.echarts-for-react div:first-of-type {
width: 100% !important;
}
.echarts-for-react text {
font-variant-numeric: tabular-nums !important;
}
/* Tooltip description styling */
.tooltip-description {
color: ${p => p.theme.white};
border-radius: ${p => p.theme.borderRadius};
color: ${p.theme.white};
border-radius: ${p.theme.borderRadius};
background: #000;
opacity: 0.9;
padding: 5px 10px;
position: relative;
font-weight: bold;
font-size: ${p => p.theme.fontSizeSmall};
font-size: ${p.theme.fontSizeSmall};
line-height: 1.4;
font-family: ${p => p.theme.text.family};
font-family: ${p.theme.text.family};
max-width: 230px;
min-width: 230px;
white-space: normal;
Expand All @@ -662,6 +657,28 @@ const ChartContainer = styled('div')<{autoHeightResize: boolean}>`
}
`;

// Contains styling for chart elements as we can't easily style those
// elements directly
const ChartContainer = styled('div')<{autoHeightResize: boolean}>`
${p => p.autoHeightResize && 'height: 100%;'}
.echarts-for-react div:first-of-type {
width: 100% !important;
}
.echarts-for-react text {
font-variant-numeric: tabular-nums !important;
}
${p => getTooltipStyles(p)}
`;

const getPortalledTooltipStyles = (p: {theme: Theme}) => css`
.chart-tooltip-portal {
${getTooltipStyles(p)};
}
`;

const BaseChart = forwardRef<ReactEchartsRef, Props>((props, ref) => (
<BaseChartUnwrapped forwardedRef={ref} {...props} />
));
Expand Down
24 changes: 19 additions & 5 deletions static/app/components/charts/components/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,13 @@ function getFormatter({

type Props = ChartProps['tooltip'] &
Pick<ChartProps, NeededChartProps> &
Pick<FormatterOptions, 'addSecondsToTimeFormat'>;
Pick<FormatterOptions, 'addSecondsToTimeFormat'> & {
/**
* An ID for the chart when using renderToBody to portal the tooltip.
* A reference to the chart is needed to calculate the tooltip position.
*/
chartId?: string;
};

export default function Tooltip({
filter,
Expand All @@ -268,6 +274,7 @@ export default function Tooltip({
markerFormatter,
hideDelay,
subLabels,
chartId,
...props
}: Props = {}): TooltipComponentOption {
const theme = useTheme();
Expand Down Expand Up @@ -315,12 +322,19 @@ export default function Tooltip({
// Center the tooltip slightly above the cursor.
const [tipWidth, tipHeight] = size.contentSize;

let parentNode: Element = document.body;
if (dom.parentNode instanceof Element) {
parentNode = dom.parentNode;
}

const chartElement: Element =
props.appendToBody && chartId
? document.getElementById(chartId) ?? parentNode
: parentNode;

// Get the left offset of the tip container (the chart)
// so that we can estimate overflows
const chartLeft =
dom.parentNode instanceof Element
? dom.parentNode.getBoundingClientRect().left
: 0;
const chartLeft = chartElement.getBoundingClientRect().left ?? 0;

// Determine the new left edge.
let leftPos = Number(pos[0]) - tipWidth / 2;
Expand Down
5 changes: 4 additions & 1 deletion static/app/views/replays/detail/memoryChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ function MemoryChart({
right: space(1),
}),
tooltip: Tooltip({
appendToBody: true,
trigger: 'axis',
renderMode: 'html',
chartId: 'replay-memory-chart',
formatter: values => {
const seriesTooltips = values.map(
value => `
Expand Down Expand Up @@ -201,7 +204,7 @@ function MemoryChart({
];

return (
<MemoryChartWrapper>
<MemoryChartWrapper id="replay-memory-chart">
<AreaChart forwardedRef={forwardedRef} series={series} {...chartOptions} />
</MemoryChartWrapper>
);
Expand Down

0 comments on commit 81573cd

Please sign in to comment.