Skip to content

Commit

Permalink
fix(tooltip): custom tooltip header context (#1989)
Browse files Browse the repository at this point in the history
- change header from TooltipValue to PointerValue type
- update tooltip header formatter type
- return full set of values for customTooltips

BREAKING CHANGE: The `header` property of `TooltipInfo` type was simplified to `PointerValue` as to include only relevant properties. This change is propagated to all other types using `header` as a `TooltipValue`. The `TooltipInfo.values` used to conditionally pass only highlighted `TooltipValue`s when using a `customTooltip` and now _always_ passes all `values`.
  • Loading branch information
nickofthyme committed Apr 3, 2023
1 parent 95176e1 commit 1e5b861
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 125 deletions.
29 changes: 18 additions & 11 deletions packages/charts/api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2487,7 +2487,7 @@ export const Settings: (props: SFProps<SettingsSpec, keyof (typeof settingsBuild
// Warning: (ae-forgotten-export) The symbol "BuildProps" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export const settingsBuildProps: BuildProps<SettingsSpec, "id" | "chartType" | "specType", "debug" | "rotation" | "ariaLabelHeadingLevel" | "ariaUseDefaultSummary" | "legendPosition" | "flatLegend" | "legendMaxDepth" | "legendSize" | "showLegend" | "showLegendExtra" | "baseTheme" | "rendering" | "animateData" | "externalPointerEvents" | "pointBuffer" | "resizeDebounce" | "pointerUpdateTrigger" | "brushAxis" | "minBrushDelta" | "allowBrushingLastHistogramBin", "tooltip" | "ariaLabel" | "theme" | "xDomain" | "ariaDescription" | "ariaDescribedBy" | "ariaLabelledBy" | "ariaTableCaption" | "legendAction" | "legendColorPicker" | "legendStrategy" | "onLegendItemClick" | "customLegend" | "onLegendItemMinusClick" | "onLegendItemOut" | "onLegendItemOver" | "onLegendItemPlusClick" | "orderOrdinalBinsBy" | "debugState" | "onProjectionClick" | "onElementClick" | "onElementOver" | "onElementOut" | "onBrushEnd" | "onPointerUpdate" | "onRenderChange" | "onProjectionAreaChange" | "onAnnotationClick" | "pointerUpdateDebounce" | "roundHistogramBrushValues" | "noResults" | "legendSort", never>;
export const settingsBuildProps: BuildProps<SettingsSpec, "id" | "chartType" | "specType", "debug" | "rotation" | "ariaLabelHeadingLevel" | "ariaUseDefaultSummary" | "legendPosition" | "flatLegend" | "legendMaxDepth" | "legendSize" | "showLegend" | "showLegendExtra" | "baseTheme" | "rendering" | "animateData" | "externalPointerEvents" | "pointBuffer" | "resizeDebounce" | "pointerUpdateTrigger" | "brushAxis" | "minBrushDelta" | "allowBrushingLastHistogramBin", "tooltip" | "ariaLabel" | "xDomain" | "ariaDescription" | "ariaDescribedBy" | "ariaLabelledBy" | "ariaTableCaption" | "theme" | "legendAction" | "legendColorPicker" | "legendStrategy" | "onLegendItemClick" | "customLegend" | "onLegendItemMinusClick" | "onLegendItemOut" | "onLegendItemOver" | "onLegendItemPlusClick" | "orderOrdinalBinsBy" | "debugState" | "onProjectionClick" | "onElementClick" | "onElementOver" | "onElementOut" | "onBrushEnd" | "onPointerUpdate" | "onRenderChange" | "onProjectionAreaChange" | "onAnnotationClick" | "pointerUpdateDebounce" | "roundHistogramBrushValues" | "noResults" | "legendSort", never>;

// @public (undocumented)
export type SettingsProps = ComponentProps<typeof Settings>;
Expand Down Expand Up @@ -2846,7 +2846,7 @@ export function toEntries<T extends Record<string, string>, S>(array: T[], acces
export type ToggleSelectedTooltipItemCallback = (item: TooltipValue<any, SeriesIdentifier>) => any;

// @public
export const Tooltip: <D extends BaseDatum = any, SI extends SeriesIdentifier = SeriesIdentifier>(props: SFProps<TooltipSpec<D, SI>, "id" | "chartType" | "specType", "body" | "footer" | "header" | "type" | "actions" | "selectionPrompt" | "actionsLoading" | "noActionsLoaded" | "snap" | "showNullValues" | "actionPrompt" | "pinningPrompt" | "maxTooltipItems" | "maxVisibleTooltipItems", "fallbackPlacements" | "placement" | "offset" | "boundary" | "boundaryPadding" | "headerFormatter" | "unit" | "customTooltip" | "stickTo", never>) => null;
export const Tooltip: <D extends BaseDatum = any, SI extends SeriesIdentifier = SeriesIdentifier>(props: SFProps<TooltipSpec<D, SI>, "id" | "chartType" | "specType", "body" | "footer" | "header" | "type" | "snap" | "showNullValues" | "actions" | "actionsLoading" | "noActionsLoaded" | "actionPrompt" | "pinningPrompt" | "selectionPrompt" | "maxTooltipItems" | "maxVisibleTooltipItems", "fallbackPlacements" | "placement" | "offset" | "boundary" | "boundaryPadding" | "headerFormatter" | "unit" | "customTooltip" | "stickTo", never>) => null;

// @public
export type TooltipAction<D extends BaseDatum = Datum, SI extends SeriesIdentifier = SeriesIdentifier> = {
Expand All @@ -2857,7 +2857,7 @@ export type TooltipAction<D extends BaseDatum = Datum, SI extends SeriesIdentifi
};

// @public
export const tooltipBuildProps: BuildProps<TooltipSpec<any, SeriesIdentifier>, "id" | "chartType" | "specType", "body" | "footer" | "header" | "type" | "actions" | "selectionPrompt" | "actionsLoading" | "noActionsLoaded" | "snap" | "showNullValues" | "actionPrompt" | "pinningPrompt" | "maxTooltipItems" | "maxVisibleTooltipItems", "fallbackPlacements" | "placement" | "offset" | "boundary" | "boundaryPadding" | "headerFormatter" | "unit" | "customTooltip" | "stickTo", never>;
export const tooltipBuildProps: BuildProps<TooltipSpec<any, SeriesIdentifier>, "id" | "chartType" | "specType", "body" | "footer" | "header" | "type" | "snap" | "showNullValues" | "actions" | "actionsLoading" | "noActionsLoaded" | "actionPrompt" | "pinningPrompt" | "selectionPrompt" | "maxTooltipItems" | "maxVisibleTooltipItems", "fallbackPlacements" | "placement" | "offset" | "boundary" | "boundaryPadding" | "headerFormatter" | "unit" | "customTooltip" | "stickTo", never>;

// @public
export type TooltipCellStyle = Pick<CSSProperties, 'maxHeight' | 'textAlign' | 'padding' | 'paddingTop' | 'paddingRight' | 'paddingBottom' | 'paddingLeft'>;
Expand Down Expand Up @@ -2905,14 +2905,21 @@ export const TooltipDivider: ({ margin }: TooltipDividerProps) => JSX.Element;
// @public (undocumented)
export const TooltipFooter: ({ children }: TooltipFooterProps) => JSX.Element;

// Warning: (ae-forgotten-export) The symbol "TooltipHeaderProps" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export const TooltipHeader: <D extends BaseDatum = any, SI extends SeriesIdentifier = SeriesIdentifier>(props: TooltipHeaderProps<D, SI>) => JSX.Element | null;
export const TooltipHeader: <D extends BaseDatum = any>(props: TooltipHeaderProps<D>) => JSX.Element | null;

// @public
export type TooltipHeaderFormatter<D extends BaseDatum = Datum> = (data: PointerValue<D>) => JSX.Element | string;

// @public (undocumented)
export type TooltipHeaderProps<D extends BaseDatum = Datum> = PropsOrChildrenWithProps<{
header?: PointerValue<D> | null;
formatter?: TooltipHeaderFormatter<D>;
}>;

// @public
export interface TooltipInfo<D extends BaseDatum = Datum, SI extends SeriesIdentifier = SeriesIdentifier> {
header: TooltipValue<D, SI> | null;
header: PointerValue<D> | null;
values: TooltipValue<D, SI>[];
}

Expand Down Expand Up @@ -2945,18 +2952,18 @@ export interface TooltipSpec<D extends BaseDatum = Datum, SI extends SeriesIdent
}>;
body: 'default' | 'none' | ComponentType<{
items: TooltipValue<D, SI>[];
header: TooltipValue<D, SI> | null;
header: PointerValue<D> | null;
}>;
customTooltip?: CustomTooltip<D, SI>;
footer: 'default' | 'none' | ComponentType<{
items: TooltipValue<D, SI>[];
header: TooltipValue<D, SI> | null;
header: PointerValue<D> | null;
}>;
header: 'default' | 'none' | ComponentType<{
items: TooltipValue<D, SI>[];
header: TooltipValue<D, SI> | null;
header: PointerValue<D> | null;
}>;
headerFormatter?: TooltipValueFormatter<D, SI>;
headerFormatter?: TooltipHeaderFormatter<D>;
maxTooltipItems: number;
maxVisibleTooltipItems: number;
noActionsLoaded: string | ComponentType<{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ import { getChartRotationSelector } from '../../../../state/selectors/get_chart_
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_spec';
import { getTooltipInteractionState } from '../../../../state/selectors/get_tooltip_interaction_state';
import { getTooltipSpecSelector } from '../../../../state/selectors/get_tooltip_spec';
import { PointerValue } from '../../../../state/types';
import { isNil, Rotation } from '../../../../utils/common';
import { isValidPointerOverEvent } from '../../../../utils/events';
import { IndexedGeometry } from '../../../../utils/geometry';
import { Point } from '../../../../utils/point';
import { getTooltipCompareFn } from '../../../../utils/series_sort';
import { isPointOnGeometry } from '../../rendering/utils';
import { formatTooltip } from '../../tooltip/tooltip';
import { formatTooltipHeader, formatTooltipValue } from '../../tooltip/tooltip';
import { defaultXYLegendSeriesSort } from '../../utils/default_series_sort_fn';
import { DataSeries } from '../../utils/series';
import { BasicSeriesSpec, AxisSpec } from '../../utils/specs';
Expand Down Expand Up @@ -124,7 +125,7 @@ function getTooltipAndHighlightFromValue(
}

// build the tooltip value list
let header: TooltipValue | null = null;
let header: PointerValue | null = null;
const highlightedGeometries: IndexedGeometry[] = [];
const xValues = new Set<any>();
const hideNullValues = !tooltip.showNullValues;
Expand Down Expand Up @@ -160,13 +161,13 @@ function getTooltipAndHighlightFromValue(
}

// format the tooltip values
const formattedTooltip = formatTooltip(indexedGeometry, spec, false, isHighlighted, hasSingleSeries, yAxis);
const formattedTooltip = formatTooltipValue(indexedGeometry, spec, isHighlighted, hasSingleSeries, 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 = formatTooltip(indexedGeometry, spec, true, false, hasSingleSeries, formatterAxis);
header = formatTooltipHeader(indexedGeometry, spec, formatterAxis);
}

xValues.add(indexedGeometry.value.x);
Expand All @@ -188,6 +189,7 @@ function getTooltipAndHighlightFromValue(
const sortedTooltipValues = values.sort((a, b) => {
return tooltipSortFn(a.seriesIdentifier, b.seriesIdentifier);
});

return {
tooltip: {
header,
Expand All @@ -206,7 +208,7 @@ export const getHighlightedTooltipTooltipValuesSelector = createCustomCachedSele
const highlightedValues = values.tooltip.values.filter((v) => v.isHighlighted);
const hasTooltipContent = values.tooltip.values.length > tooltip.maxTooltipItems && highlightedValues.length > 0;

if (!pinned && (isFollowTooltipType(tooltipType) || hasTooltipContent)) {
if (!pinned && !tooltip.customTooltip && (isFollowTooltipType(tooltipType) || hasTooltipContent)) {
return {
...values,
tooltip: {
Expand Down
68 changes: 28 additions & 40 deletions packages/charts/src/chart_types/xy_chart/tooltip/tooltip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { formatTooltip } from './tooltip';
import { formatTooltipHeader, formatTooltipValue } from './tooltip';
import { ChartType } from '../..';
import { MockBarGeometry } from '../../../mocks';
import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs';
Expand Down Expand Up @@ -117,7 +117,7 @@ describe('Tooltip formatting', () => {
});

test('format simple tooltip', () => {
const tooltipValue = formatTooltip(indexedGeometry, SPEC_1, false, false, false, YAXIS_SPEC);
const tooltipValue = formatTooltipValue(indexedGeometry, SPEC_1, false, false, YAXIS_SPEC);
expect(tooltipValue).toBeDefined();
expect(tooltipValue.valueAccessor).toBe('y1');
expect(tooltipValue.label).toBe('bar_1');
Expand All @@ -130,41 +130,39 @@ describe('Tooltip formatting', () => {
});
it('should set name as spec name when provided', () => {
const name = 'test - spec';
const tooltipValue = formatTooltip(indexedBandedGeometry, { ...SPEC_1, name }, false, false, false, YAXIS_SPEC);
const tooltipValue = formatTooltipValue(indexedBandedGeometry, { ...SPEC_1, name }, false, false, YAXIS_SPEC);
expect(tooltipValue.label).toBe(name);
});
it('should set name as spec id when name is not provided', () => {
const tooltipValue = formatTooltip(indexedBandedGeometry, SPEC_1, false, false, false, YAXIS_SPEC);
const tooltipValue = formatTooltipValue(indexedBandedGeometry, SPEC_1, false, false, YAXIS_SPEC);
expect(tooltipValue.label).toBe(SPEC_1.id);
});
test('format banded tooltip - upper', () => {
const tooltipValue = formatTooltip(indexedBandedGeometry, bandedSpec, false, false, false, YAXIS_SPEC);
const tooltipValue = formatTooltipValue(indexedBandedGeometry, bandedSpec, false, false, YAXIS_SPEC);
expect(tooltipValue.label).toBe('bar_1 - upper');
});
test('format banded tooltip - y1AccessorFormat', () => {
const tooltipValue = formatTooltip(
const tooltipValue = formatTooltipValue(
indexedBandedGeometry,
{ ...bandedSpec, y1AccessorFormat: ' [max]' },
false,
false,
false,
YAXIS_SPEC,
);
expect(tooltipValue.label).toBe('bar_1 [max]');
});
test('format banded tooltip - y1AccessorFormat as function', () => {
const tooltipValue = formatTooltip(
const tooltipValue = formatTooltipValue(
indexedBandedGeometry,
{ ...bandedSpec, y1AccessorFormat: (label) => `[max] ${label}` },
false,
false,
false,
YAXIS_SPEC,
);
expect(tooltipValue.label).toBe('[max] bar_1');
});
test('format banded tooltip - lower', () => {
const tooltipValue = formatTooltip(
const tooltipValue = formatTooltipValue(
{
...indexedBandedGeometry,
value: {
Expand All @@ -175,13 +173,12 @@ describe('Tooltip formatting', () => {
bandedSpec,
false,
false,
false,
YAXIS_SPEC,
);
expect(tooltipValue.label).toBe('bar_1 - lower');
});
test('format banded tooltip - y0AccessorFormat', () => {
const tooltipValue = formatTooltip(
const tooltipValue = formatTooltipValue(
{
...indexedBandedGeometry,
value: {
Expand All @@ -192,13 +189,12 @@ describe('Tooltip formatting', () => {
{ ...bandedSpec, y0AccessorFormat: ' [min]' },
false,
false,
false,
YAXIS_SPEC,
);
expect(tooltipValue.label).toBe('bar_1 [min]');
});
test('format banded tooltip - y0AccessorFormat as function', () => {
const tooltipValue = formatTooltip(
const tooltipValue = formatTooltipValue(
{
...indexedBandedGeometry,
value: {
Expand All @@ -209,7 +205,6 @@ describe('Tooltip formatting', () => {
{ ...bandedSpec, y0AccessorFormat: (label) => `[min] ${label}` },
false,
false,
false,
YAXIS_SPEC,
);
expect(tooltipValue.label).toBe('[min] bar_1');
Expand All @@ -226,7 +221,7 @@ describe('Tooltip formatting', () => {
seriesKeys: ['y1'],
},
};
const tooltipValue = formatTooltip(geometry, SPEC_1, false, false, false, YAXIS_SPEC);
const tooltipValue = formatTooltipValue(geometry, SPEC_1, false, false, YAXIS_SPEC);
expect(tooltipValue).toBeDefined();
expect(tooltipValue.valueAccessor).toBe('y1');
expect(tooltipValue.label).toBe('bar_1');
Expand All @@ -243,7 +238,7 @@ describe('Tooltip formatting', () => {
accessor: 'y0',
},
};
const tooltipValue = formatTooltip(geometry, SPEC_1, false, false, false, YAXIS_SPEC);
const tooltipValue = formatTooltipValue(geometry, SPEC_1, false, false, YAXIS_SPEC);
expect(tooltipValue).toBeDefined();
expect(tooltipValue.valueAccessor).toBe('y0');
expect(tooltipValue.label).toBe('bar_1');
Expand All @@ -260,17 +255,11 @@ describe('Tooltip formatting', () => {
accessor: 'y0',
},
};
let tooltipValue = formatTooltip(geometry, SPEC_1, true, false, false, YAXIS_SPEC);
expect(tooltipValue).toBeDefined();
expect(tooltipValue.valueAccessor).toBe('y0');
expect(tooltipValue.label).toBe('bar_1');
expect(tooltipValue.isHighlighted).toBe(false);
expect(tooltipValue.color).toBe('blue');
expect(tooltipValue.value).toBe(1);
expect(tooltipValue.formattedValue).toBe('1');
// disable any highlight on x value
tooltipValue = formatTooltip(geometry, SPEC_1, true, true, false, YAXIS_SPEC);
expect(tooltipValue.isHighlighted).toBe(false);
const tooltipHeader = formatTooltipHeader(geometry, SPEC_1, YAXIS_SPEC);
expect(tooltipHeader).toBeDefined();
expect(tooltipHeader.valueAccessor).toBeUndefined();
expect(tooltipHeader.value).toBe(1);
expect(tooltipHeader.formattedValue).toBe('1');
});

it('should format ticks with custom formatter from spec', () => {
Expand All @@ -284,7 +273,7 @@ describe('Tooltip formatting', () => {
...SPEC_1,
tickFormat: tickFormatter,
};
const tooltipValue = formatTooltip(indexedGeometry, spec, false, false, false, axisSpec);
const tooltipValue = formatTooltipValue(indexedGeometry, spec, false, false, axisSpec);
expect(tooltipValue.value).toBe(10);
expect(tooltipValue.formattedValue).toBe('10 spec');
});
Expand All @@ -295,13 +284,13 @@ describe('Tooltip formatting', () => {
...YAXIS_SPEC,
tickFormat: axisTickFormatter,
};
const tooltipValue = formatTooltip(indexedGeometry, SPEC_1, false, false, false, axisSpec);
const tooltipValue = formatTooltipValue(indexedGeometry, SPEC_1, false, false, axisSpec);
expect(tooltipValue.value).toBe(10);
expect(tooltipValue.formattedValue).toBe('10 axis');
});

it('should format ticks with default formatter', () => {
const tooltipValue = formatTooltip(indexedGeometry, SPEC_1, false, false, false, YAXIS_SPEC);
const tooltipValue = formatTooltipValue(indexedGeometry, SPEC_1, false, false, YAXIS_SPEC);
expect(tooltipValue.value).toBe(10);
expect(tooltipValue.formattedValue).toBe('10');
});
Expand All @@ -317,9 +306,9 @@ describe('Tooltip formatting', () => {
...SPEC_1,
tickFormat: tickFormatter,
};
const tooltipValue = formatTooltip(indexedGeometry, spec, true, false, false, axisSpec);
expect(tooltipValue.value).toBe(1);
expect(tooltipValue.formattedValue).toBe('1 axis');
const tooltipHeader = formatTooltipHeader(indexedGeometry, spec, axisSpec);
expect(tooltipHeader.value).toBe(1);
expect(tooltipHeader.formattedValue).toBe('1 axis');
});

it('should format header with default formatter from axis', () => {
Expand All @@ -328,9 +317,9 @@ describe('Tooltip formatting', () => {
...SPEC_1,
tickFormat: tickFormatter,
};
const tooltipValue = formatTooltip(indexedGeometry, spec, true, false, false, YAXIS_SPEC);
expect(tooltipValue.value).toBe(1);
expect(tooltipValue.formattedValue).toBe('1');
const tooltipHeader = formatTooltipHeader(indexedGeometry, spec, YAXIS_SPEC);
expect(tooltipHeader.value).toBe(1);
expect(tooltipHeader.formattedValue).toBe('1');
});

describe('markFormat', () => {
Expand All @@ -347,15 +336,14 @@ describe('Tooltip formatting', () => {
};

it('should format mark value with markFormat', () => {
const tooltipValue = formatTooltip(
const tooltipValue = formatTooltipValue(
markIndexedGeometry,
{
...SPEC_1,
markFormat,
},
false,
false,
false,
YAXIS_SPEC,
);
expect(tooltipValue).toBeDefined();
Expand All @@ -365,7 +353,7 @@ describe('Tooltip formatting', () => {
});

it('should format mark value with defaultTickFormatter', () => {
const tooltipValue = formatTooltip(markIndexedGeometry, SPEC_1, false, false, false, YAXIS_SPEC);
const tooltipValue = formatTooltipValue(markIndexedGeometry, SPEC_1, false, false, YAXIS_SPEC);
expect(tooltipValue).toBeDefined();
expect(tooltipValue.markValue).toBe(10);
expect(tooltipValue.formattedMarkValue).toBe('10');
Expand Down
Loading

0 comments on commit 1e5b861

Please sign in to comment.