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 dad039b
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 87 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
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ export const ChartContainer = (props: Props) => {
<div
className={styles.ChartContainer}
style={{
background: chartContainer.backgroundColor,
// background: chartContainer.backgroundColor,
background: 'red',
padding,
borderRadius: chartContainer.borderRadius,
}}
Expand Down Expand Up @@ -126,6 +127,7 @@ export const ChartContainer = (props: Props) => {
<div
className={styles.Chart}
style={{
background: 'blue',
height: value.containerBounds.height,
width: value.containerBounds.width,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ export function Chart({
return maxes;
}, [data, areAllNegative]);

console.log({highestValueForSeries});

const {stackedValues, stackedMin, stackedMax} = useHorizontalStackedValues({
isStacked,
data,
Expand Down Expand Up @@ -299,11 +301,13 @@ export function Chart({
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,
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,40 @@
import type {ScaleLinear} from 'd3-scale';
import type {BoundingRect} from '@shopify/polaris-viz-core';
import {Y_AXIS_CHART_SPACING} 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 =
(chartBounds.height - Y_AXIS_CHART_SPACING) /
data[longestSeriesIndex].data.length;
const isStacked = type === 'stacked';

if (eventType === 'mouse' && event) {
Expand All @@ -32,7 +46,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 +56,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 +81,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 - bandwidth * index,
activeIndex: index,
};
}
Expand Down

0 comments on commit dad039b

Please sign in to comment.