in packages/charts/src/chart_types/xy_chart/state/selectors/get_tooltip_values_highlighted_geoms.ts [77:208]
function getTooltipAndHighlightFromValue(
seriesSpecs: BasicSeriesSpec[],
axesSpecs: AxisSpec[],
settings: SettingsSpec,
projectedPointerPosition: Point,
orientedProjectedPointerPosition: Point,
chartRotation: Rotation,
hasSingleSeries: boolean,
scales: ComputedScales,
matchingGeoms: IndexedGeometry[],
seriesIdentifierDataSeriesMap: Record<string, DataSeries>,
externalPointerEvent: PointerEvent | null,
tooltip: TooltipSpec,
): TooltipAndHighlightedGeoms {
if (!scales.xScale || !scales.yScales) {
return EMPTY_VALUES;
}
let { x, y } = orientedProjectedPointerPosition;
let tooltipType = getTooltipType(tooltip, settings);
if (isValidPointerOverEvent(scales.xScale, externalPointerEvent)) {
tooltipType = getTooltipType(tooltip, settings, true);
if (isNil(externalPointerEvent.x)) {
return EMPTY_VALUES;
}
const scaledX = scales.xScale.pureScale(externalPointerEvent.x);
if (Number.isNaN(scaledX)) {
return EMPTY_VALUES;
}
x = scaledX;
y = 0;
} else if (projectedPointerPosition.x === -1 || projectedPointerPosition.y === -1) {
return EMPTY_VALUES;
}
if (tooltipType === TooltipType.None && !externalPointerEvent) {
return EMPTY_VALUES;
}
if (matchingGeoms.length === 0) {
return EMPTY_VALUES;
}
// build the tooltip value list
let header: PointerValue | null = null;
const highlightedGeometries: IndexedGeometry[] = [];
const xValues = new Set<any>();
const hideNullValues = !tooltip.showNullValues;
const values = matchingGeoms
.slice()
.sort((a, b) => {
// presort matchingGeoms to group by series then y value to prevent flipping
return b.seriesIdentifier.key.localeCompare(a.seriesIdentifier.key) || b.value.y - a.value.y;
})
.reduce<TooltipValue[]>((acc, indexedGeometry) => {
if (hideNullValues && indexedGeometry.value.y === null) {
return acc;
}
const {
seriesIdentifier: { specId },
} = indexedGeometry;
const spec = getSpecsById<BasicSeriesSpec>(seriesSpecs, specId);
// safe guard check
if (!spec) {
return acc;
}
const { xAxis, yAxis } = getAxesSpecForSpecId(axesSpecs, spec.groupId, chartRotation);
// yScales is ensured by the enclosing if
const yScale = scales.yScales.get(getSpecDomainGroupId(spec));
if (!yScale) {
return acc;
}
// check if the pointer is on the geometry (avoid checking if using external pointer event)
let isHighlighted = false;
if (
(!externalPointerEvent || isPointerOutEvent(externalPointerEvent)) &&
isPointOnGeometry(x, y, indexedGeometry, settings.pointBuffer)
) {
isHighlighted = true;
highlightedGeometries.push(indexedGeometry);
}
// format the tooltip values
const formattedTooltip = formatTooltipValue(
indexedGeometry,
spec,
isHighlighted,
hasSingleSeries,
isBandedSpec(spec),
yAxis,
);
// format only one time the x value
if (!header) {
// if we have a tooltipHeaderFormatter, then don't pass in the xAxis as the user will define a formatter
const formatterAxis = tooltip.headerFormatter ? undefined : xAxis;
header = formatTooltipHeader(indexedGeometry, spec, formatterAxis);
}
xValues.add(indexedGeometry.value.x);
return [...acc, formattedTooltip];
}, []);
if (values.length > 1 && xValues.size === values.length) {
// TODO: remove after tooltip redesign
header = null;
}
const baseTooltipSortFn: SeriesCompareFn = (a, b) => {
const aDs = seriesIdentifierDataSeriesMap[a.key];
const bDs = seriesIdentifierDataSeriesMap[b.key];
return defaultXYLegendSeriesSort(aDs, bDs);
};
const tooltipSortFn = tooltip.sort ?? settings.legendSort ?? baseTooltipSortFn;
const sortedTooltipValues = values.sort((a, b) => {
return tooltipSortFn(a.seriesIdentifier, b.seriesIdentifier);
});
return {
tooltip: {
header,
values: sortedTooltipValues,
},
highlightedGeometries,
};
}