Skip to content

Commit

Permalink
Use a portal for horizontal tooltips
Browse files Browse the repository at this point in the history
  • Loading branch information
envex committed Jan 31, 2025
1 parent a648037 commit 2f46506
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export const ExternalTooltipWithFrame: Story<BarChartProps> =
TemplateWithFrame.bind({});

ExternalTooltip.args = {
direction: 'horizontal',
data: [
{
name: 'Apr 1 – Apr 14, 2020',
Expand Down
23 changes: 12 additions & 11 deletions packages/polaris-viz/src/components/HorizontalBarChart/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,14 @@ export function Chart({
longestLabel,
});

const {barHeight, chartHeight, groupBarsAreaHeight, groupHeight} =
useHorizontalBarSizes({
chartDimensions: {width: drawableWidth, height: drawableHeight},
isSimple: xAxisOptions.hide,
isStacked,
seriesLength: longestSeriesCount,
singleBarCount: data.length,
xAxisHeight,
});
const {barHeight, chartHeight, groupHeight} = useHorizontalBarSizes({
chartDimensions: {width: drawableWidth, height: drawableHeight},
isSimple: xAxisOptions.hide,
isStacked,
seriesLength: longestSeriesCount,
singleBarCount: data.length,
xAxisHeight,
});

const annotationsDrawableHeight =
chartYPosition + chartHeight + ANNOTATIONS_LABELS_OFFSET;
Expand Down Expand Up @@ -293,17 +292,19 @@ export function Chart({

{highestValueForSeries.length !== 0 && (
<TooltipWrapper
bandwidth={groupBarsAreaHeight}
bandwidth={groupHeight}
chartBounds={chartBounds}
chartType={InternalChartType.HorizontalBar}
data={data}
focusElementDataType={DataType.BarGroup}
getMarkup={getTooltipMarkup}
margin={ChartMargin}
margin={{...ChartMargin, Top: chartYPosition}}
parentElement={svgRef}
longestSeriesIndex={longestSeriesIndex}
highestValueForSeries={highestValueForSeries}
xScale={xScale}
type={type}
usePortal
/>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ interface BaseProps {
xScale: ScaleLinear<number, number> | ScaleBand<string>;
bandwidth?: number;
onIndexChange?: (index: number | null) => void;
highestValueForSeries?: number[];
id?: string;
type?: ChartType;
yScale?: ScaleLinear<number, number>;
Expand All @@ -56,6 +57,7 @@ function TooltipWrapperRaw(props: BaseProps) {
type,
xScale,
yScale,
highestValueForSeries,
} = props;
const {scrollContainer, isTouchDevice, containerBounds} = useChartContext();
const [position, setPosition] = useState<TooltipPosition>({
Expand Down Expand Up @@ -111,13 +113,19 @@ function TooltipWrapperRaw(props: BaseProps) {
case InternalChartType.HorizontalBar:
return getHorizontalBarChartTooltipPosition({
chartBounds,
containerBounds,
data,
event,
eventType,
index,
longestSeriesIndex,
type,
xScale: xScale as ScaleLinear<number, number>,
highestValueForSeries: highestValueForSeries ?? [],
bandwidth,
scrollY: scrollContainer
? scrollContainer.scrollTop
: window.scrollY,
});
case InternalChartType.Bar:
default:
Expand All @@ -136,6 +144,8 @@ function TooltipWrapperRaw(props: BaseProps) {
}
},
[
highestValueForSeries,
bandwidth,
chartBounds,
containerBounds,
chartType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {BoundingRect, Dimensions} from '@shopify/polaris-viz-core';
import {HORIZONTAL_GROUP_LABEL_HEIGHT} from '@shopify/polaris-viz-core';
import type {Dimensions} from '@shopify/polaris-viz-core';
import {clamp, HORIZONTAL_GROUP_LABEL_HEIGHT} from '@shopify/polaris-viz-core';

import {TOOLTIP_MARGIN} from '../constants';
import {SCROLLBAR_WIDTH, TOOLTIP_MARGIN} from '../constants';
import type {AlteredPositionProps, AlteredPositionReturn} from '../types';

export function getAlteredHorizontalBarPosition(
Expand All @@ -20,100 +20,59 @@ function getNegativeOffset(props: AlteredPositionProps): AlteredPositionReturn {
const yOffset = (bandwidth - tooltipDimensions.height) / 2;

const y = currentY - tooltipDimensions.height;

if (flippedX - tooltipDimensions.width < 0) {
return {x: flippedX, y: y < 0 ? 0 : y};
return clampPosition({
x: flippedX,
y: y < 0 ? 0 : y,
tooltipDimensions,
});
}

return {
return clampPosition({
x: flippedX - tooltipDimensions.width - TOOLTIP_MARGIN,
y: currentY + HORIZONTAL_GROUP_LABEL_HEIGHT + yOffset,
};
tooltipDimensions,
});
}

function getPositiveOffset(props: AlteredPositionProps): AlteredPositionReturn {
const {bandwidth, currentX, currentY, tooltipDimensions, chartBounds} = props;
const {currentX, currentY} = props;

const isOutside = isOutsideBounds({
return clampPosition({
x: currentX,
y: currentY,
tooltipDimensions,
chartBounds,
tooltipDimensions: props.tooltipDimensions,
});

if (isOutside.top && isOutside.right) {
return {
x: chartBounds.width - tooltipDimensions.width,
y: 0,
};
}

if (isOutside.top && !isOutside.right) {
return {
x: currentX + TOOLTIP_MARGIN,
y: 0,
};
}

if (!isOutside.right && !isOutside.bottom) {
const yOffset = (bandwidth - tooltipDimensions.height) / 2;
return {
x: currentX + TOOLTIP_MARGIN,
y: currentY + HORIZONTAL_GROUP_LABEL_HEIGHT + yOffset,
};
}

if (isOutside.right) {
const x = currentX - tooltipDimensions.width;
const y =
currentY -
tooltipDimensions.height +
HORIZONTAL_GROUP_LABEL_HEIGHT -
TOOLTIP_MARGIN;

if (y < 0) {
return {
x,
y: bandwidth + HORIZONTAL_GROUP_LABEL_HEIGHT + TOOLTIP_MARGIN,
};
}

return {
x,
y,
};
}

if (isOutside.bottom) {
return {
x: currentX + TOOLTIP_MARGIN,
y:
chartBounds.height -
tooltipDimensions.height -
HORIZONTAL_GROUP_LABEL_HEIGHT,
};
}

return {x: currentX, y: currentY};
}

function isOutsideBounds({
function clampPosition({
x,
y,
tooltipDimensions,
chartBounds,
}: {
x: number;
y: number;
tooltipDimensions: Dimensions;
chartBounds: BoundingRect;
}) {
const right = x + TOOLTIP_MARGIN + tooltipDimensions.width;
const bottom = y + tooltipDimensions.height;

return {
left: x <= 0,
right: right > chartBounds.width,
bottom: bottom > chartBounds.height,
top: y <= 0,
x: clamp({
amount: x,
min: TOOLTIP_MARGIN,
max:
window.innerWidth -
tooltipDimensions.width -
TOOLTIP_MARGIN -
SCROLLBAR_WIDTH,
}),
y: clamp({
amount: y,
min: window.scrollY + TOOLTIP_MARGIN,
max:
window.scrollY +
window.innerHeight -
tooltipDimensions.height -
TOOLTIP_MARGIN,
}),
};
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
import type {ScaleLinear} from 'd3-scale';
import type {BoundingRect} from '@shopify/polaris-viz-core';

import {getStackedValuesFromDataSeries} from '../../../utilities/getStackedValuesFromDataSeries';
import type {TooltipPosition, TooltipPositionParams} from '../types';
import {TOOLTIP_POSITION_DEFAULT_RETURN} from '../constants';

import {eventPointNative} from './eventPoint';

const SPACING = 10;

interface Props extends Omit<TooltipPositionParams, 'xScale'> {
bandwidth: number;
containerBounds: BoundingRect;
highestValueForSeries: number[];
scrollY: number;
xScale: ScaleLinear<number, number>;
}

export function getHorizontalBarChartTooltipPosition({
chartBounds,
containerBounds,
data,
event,
eventType,
index,
longestSeriesIndex,
type,
xScale,
scrollY,
highestValueForSeries,
bandwidth,
}: Props): TooltipPosition {
const groupHeight = chartBounds.height / data[longestSeriesIndex].data.length;
const groupHeight = bandwidth;

const isStacked = type === 'stacked';

if (eventType === 'mouse' && event) {
Expand All @@ -32,7 +43,7 @@ export function getHorizontalBarChartTooltipPosition({

const {svgY} = point;

const currentPoint = svgY - 0;
const currentPoint = svgY - scrollY;
const currentIndex = Math.floor(currentPoint / groupHeight);

if (
Expand All @@ -42,14 +53,17 @@ export function getHorizontalBarChartTooltipPosition({
return TOOLTIP_POSITION_DEFAULT_RETURN;
}

return formatPositionForTooltip(currentIndex);
return formatPositionForTooltip(currentIndex, containerBounds);
} else if (index != null) {
return formatPositionForTooltip(index);
return formatPositionForTooltip(index, containerBounds);
}

return TOOLTIP_POSITION_DEFAULT_RETURN;

function formatPositionForTooltip(index: number): TooltipPosition {
function formatPositionForTooltip(
index: number,
containerBounds: BoundingRect,
): TooltipPosition {
if (isStacked) {
const {formattedStackedValues} = getStackedValuesFromDataSeries(data);

Expand All @@ -64,18 +78,18 @@ export function getHorizontalBarChartTooltipPosition({
}, xScale(0));

return {
x: chartBounds.x + x,
y: chartBounds.y + groupHeight * index,
x: containerBounds.x + x,
y: containerBounds.y + groupHeight * index,
activeIndex: index,
};
}

const highestValue = data[longestSeriesIndex].data[index].value ?? 0;
const x = chartBounds.x + (xScale(highestValue ?? 0) ?? 0);
const highestValue = highestValueForSeries[index] ?? 0;
const x = containerBounds.x + (xScale(highestValue ?? 0) ?? 0) + SPACING;

return {
x: highestValue < 0 ? -x : x,
y: groupHeight * index,
y: containerBounds.y + groupHeight * index,
activeIndex: index,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function HorizontalGroup({
className={style.Group}
>
<rect
fill="transparent"
fill="green"
height={groupHeight}
width={containerWidth}
y={-(groupHeight - rowHeight) / 2}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('useHorizontalBarSizes()', () => {
expect(data).toStrictEqual({
barHeight: 44.333333333333336,
chartHeight: 380,
groupBarsAreaHeight: 88.66666666666667,

groupHeight: 126.66666666666667,
});
});
Expand All @@ -51,7 +51,6 @@ describe('useHorizontalBarSizes()', () => {
expect(data).toStrictEqual({
barHeight: 28.88888888888889,
chartHeight: 380,
groupBarsAreaHeight: 86.66666666666667,
groupHeight: 126.66666666666667,
});
});
Expand All @@ -69,7 +68,6 @@ describe('useHorizontalBarSizes()', () => {
const data = parseData(result);

expect(data.chartHeight).toStrictEqual(416);
expect(data.groupBarsAreaHeight).toStrictEqual(100.66666666666666);
expect(data.groupHeight).toStrictEqual(138.66666666666666);
});
});
Expand Down
1 change: 0 additions & 1 deletion packages/polaris-viz/src/hooks/useHorizontalBarSizes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export function useHorizontalBarSizes({
return {
barHeight,
chartHeight,
groupBarsAreaHeight,
groupHeight,
};
}, [
Expand Down

0 comments on commit 2f46506

Please sign in to comment.