Skip to content

Commit

Permalink
Merge pull request #1630 from ag-grid/AG-11591/add_series_specific_se…
Browse files Browse the repository at this point in the history
…ries_tooltip_range_option

AG-11591 Add series-specific series[].tooltip.range option
  • Loading branch information
alantreadway committed Jun 3, 2024
2 parents d6efd4f + 960b1fa commit 9a8ed54
Show file tree
Hide file tree
Showing 45 changed files with 213 additions and 60 deletions.
73 changes: 55 additions & 18 deletions packages/ag-charts-community/src/chart/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import { ChartOverlays } from './overlay/chartOverlays';
import { getLoadingSpinner } from './overlay/loadingSpinner';
import { type PickFocusOutputs, type Series, SeriesGroupingChangedEvent, SeriesNodePickMode } from './series/series';
import { SeriesLayerManager } from './series/seriesLayerManager';
import type { SeriesProperties } from './series/seriesProperties';
import type { SeriesGrouping } from './series/seriesStateManager';
import type { ISeries, SeriesNodeDatum } from './series/seriesTypes';
import {
Expand Down Expand Up @@ -1047,18 +1048,22 @@ export abstract class Chart extends Observable {
protected animationRect?: BBox;

// x/y are local canvas coordinates in CSS pixels, not actual pixels
private pickSeriesNode(point: Point, exactMatchOnly: boolean, maxDistance?: number): PickedNode | undefined {
private pickNode(
point: Point,
collection: {
series: Series<SeriesNodeDatum, SeriesProperties<object[]>>;
pickModes?: SeriesNodePickMode[];
maxDistance?: number;
}[]
): PickedNode | undefined {
const start = performance.now();

// Disable 'nearest match' options if looking for exact matches only
const pickModes = exactMatchOnly ? [SeriesNodePickMode.EXACT_SHAPE_MATCH] : undefined;

// Iterate through series in reverse, as later declared series appears on top of earlier
// declared series.
const reverseSeries = [...this.series].reverse();
const reverseSeries = [...collection].reverse();

let result: { series: Series<any, any>; datum: SeriesNodeDatum; distance: number } | undefined;
for (const series of reverseSeries) {
for (const { series, pickModes, maxDistance } of reverseSeries) {
if (!series.visible || !series.rootGroup.visible) {
continue;
}
Expand All @@ -1081,6 +1086,35 @@ export abstract class Chart extends Observable {
return result;
}

private pickSeriesNode(point: Point, exactMatchOnly: boolean, maxDistance?: number): PickedNode | undefined {
// Disable 'nearest match' options if looking for exact matches only
const pickModes = exactMatchOnly ? [SeriesNodePickMode.EXACT_SHAPE_MATCH] : undefined;
return this.pickNode(
point,
this.series.map((series) => {
return { series, pickModes, maxDistance };
})
);
}

private pickTooltip(point: Point): PickedNode | undefined {
return this.pickNode(
point,
this.series.map((series) => {
const tooltipRange = series.properties.tooltip.range;
let pickModes: SeriesNodePickMode[] | undefined;
if (tooltipRange === 'exact') {
pickModes = [SeriesNodePickMode.EXACT_SHAPE_MATCH];
} else {
pickModes = undefined;
}

const maxDistance = typeof tooltipRange === 'number' ? tooltipRange : undefined;
return { series, pickModes, maxDistance };
})
);
}

private lastPick?: SeriesNodeDatum;

protected onMouseMove(event: PointerInteractionEvent<'hover'>): void {
Expand Down Expand Up @@ -1286,15 +1320,9 @@ export abstract class Chart extends Observable {
event: TooltipPointerEvent<'hover'>,
disablePointer: (highlightOnly?: boolean) => void
) {
const { lastPick, tooltip } = this;
const { range } = tooltip;
const { lastPick } = this;
const { offsetX, offsetY, targetElement } = event;

let pixelRange;
if (isFiniteNumber(range)) {
pixelRange = range;
}

if (
targetElement &&
this.tooltip.interactive &&
Expand All @@ -1304,7 +1332,7 @@ export abstract class Chart extends Observable {
return;
}

const pick = this.pickSeriesNode({ x: offsetX, y: offsetY }, range === 'exact', pixelRange);
const pick = this.pickTooltip({ x: offsetX, y: offsetY });
if (!pick) {
this.ctx.tooltipManager.removeTooltip(this.id);
if (this.highlight.range === 'tooltip') {
Expand All @@ -1324,11 +1352,8 @@ export abstract class Chart extends Observable {
}
}

const isPixelRange = pixelRange != null;
const tooltipEnabled = this.tooltip.enabled && pick.series.tooltipEnabled;
const exactlyMatched = range === 'exact' && pick.distance === 0;
const rangeMatched = range === 'nearest' || isPixelRange || exactlyMatched;
const shouldUpdateTooltip = tooltipEnabled && rangeMatched && (!isNewDatum || html !== undefined);
const shouldUpdateTooltip = tooltipEnabled && (!isNewDatum || html !== undefined);

const meta = TooltipManager.makeTooltipMeta(event, pick.datum);

Expand Down Expand Up @@ -1923,6 +1948,7 @@ export abstract class Chart extends Observable {
}

target.properties.set(seriesOptions);
this.applySeriesTooltipDefaults(target);

if ('data' in options) {
target.setOptionsData(data);
Expand All @@ -1941,6 +1967,17 @@ export abstract class Chart extends Observable {
}
}

// The `chart.series[].tooltip.range` option is a bit different for legacy reason. This use to be
// global option (`chart.tooltip.range`) that could overriden the theme. But now, the tooltip range
// option is series-specific.
//
// To preserve backward compatiblity, the `chart.tooltip.range` theme default has been changed from
// 'nearest' to undefined.
private applySeriesTooltipDefaults(target: Series<SeriesNodeDatum, SeriesProperties<never>>) {
target.properties.tooltip.range ??= this.tooltip.range;
target.properties.tooltip.range ??= target.defaultTooltipRange;
}

private createAxis(options: AgBaseAxisOptions[], skip: string[]): ChartAxis[] {
const newAxes: ChartAxis[] = [];
const moduleContext = this.getModuleContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export class AreaSeries extends CartesianSeries<
pathsPerSeries: 2,
pathsZIndexSubOrderOffset: [0, 1000],
hasMarkers: true,
defaultTooltipRange: 'nearest',
markerSelectionGarbageCollection: false,
pickModes: [SeriesNodePickMode.NEAREST_BY_MAIN_AXIS_FIRST, SeriesNodePickMode.EXACT_SHAPE_MATCH],
animationResetFns: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export class BarSeries extends AbstractBarSeries<Rect, BarSeriesProperties, BarN
directionKeys: DEFAULT_CARTESIAN_DIRECTION_KEYS,
directionNames: DEFAULT_CARTESIAN_DIRECTION_NAMES,
pickModes: [SeriesNodePickMode.NEAREST_NODE, SeriesNodePickMode.EXACT_SHAPE_MATCH],
defaultTooltipRange: 'exact',
pathsPerSeries: 0,
hasHighlightedLabels: true,
datumSelectionGarbageCollection: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class BubbleSeries extends CartesianSeries<Group, BubbleSeriesProperties,
],
pathsPerSeries: 0,
hasMarkers: true,
defaultTooltipRange: 'nearest',
markerSelectionGarbageCollection: false,
animationResetFns: {
label: resetLabelFn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export class HistogramSeries extends CartesianSeries<Rect, HistogramSeriesProper
directionKeys: DEFAULT_CARTESIAN_DIRECTION_KEYS,
directionNames: DEFAULT_CARTESIAN_DIRECTION_NAMES,
pickModes: [SeriesNodePickMode.NEAREST_NODE, SeriesNodePickMode.EXACT_SHAPE_MATCH],
defaultTooltipRange: 'exact',
datumSelectionGarbageCollection: false,
animationResetFns: {
datum: resetBarSelectionsFn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class LineSeries extends CartesianSeries<Group, LineSeriesProperties, Lin
directionKeys: DEFAULT_CARTESIAN_DIRECTION_KEYS,
directionNames: DEFAULT_CARTESIAN_DIRECTION_NAMES,
hasMarkers: true,
defaultTooltipRange: 'nearest',
pickModes: [
SeriesNodePickMode.NEAREST_BY_MAIN_CATEGORY_AXIS_FIRST,
SeriesNodePickMode.NEAREST_NODE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class ScatterSeries extends CartesianSeries<Group, ScatterSeriesPropertie
],
pathsPerSeries: 0,
hasMarkers: true,
defaultTooltipRange: 'nearest',
markerSelectionGarbageCollection: false,
animationResetFns: {
marker: resetMarkerFn,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export abstract class HierarchySeries<
super({
moduleCtx,
pickModes: [SeriesNodePickMode.NEAREST_NODE, SeriesNodePickMode.EXACT_SHAPE_MATCH],
defaultTooltipRange: 'exact',
contentGroupVirtual: false,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export class DonutSeries extends PolarSeries<DonutNodeDatum, DonutSeriesProperti
super({
moduleCtx,
pickModes: [SeriesNodePickMode.NEAREST_NODE, SeriesNodePickMode.EXACT_SHAPE_MATCH],
defaultTooltipRange: 'exact',
useLabelLayer: true,
animationResetFns: { item: resetPieSelectionsFn, label: resetLabelFn },
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export class PieSeries extends PolarSeries<PieNodeDatum, PieSeriesProperties, Se
super({
moduleCtx,
pickModes: [SeriesNodePickMode.NEAREST_NODE, SeriesNodePickMode.EXACT_SHAPE_MATCH],
defaultTooltipRange: 'exact',
useLabelLayer: true,
animationResetFns: { item: resetPieSelectionsFn, label: resetLabelFn },
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ModuleContext } from '../../../module/moduleContext';
import type { AnimationValue } from '../../../motion/animation';
import { resetMotion } from '../../../motion/resetMotion';
import type { InteractionRange } from '../../../options/chart/types';
import type { BBox } from '../../../scene/bbox';
import { Group } from '../../../scene/group';
import type { Node } from '../../../scene/node';
Expand Down Expand Up @@ -86,13 +87,15 @@ export abstract class PolarSeries<
useLabelLayer = false,
pickModes = [SeriesNodePickMode.NEAREST_NODE, SeriesNodePickMode.EXACT_SHAPE_MATCH],
canHaveAxes = false,
defaultTooltipRange,
animationResetFns,
...opts
}: {
moduleCtx: ModuleContext;
useLabelLayer?: boolean;
pickModes?: SeriesNodePickMode[];
canHaveAxes?: boolean;
defaultTooltipRange: InteractionRange;
animationResetFns?: {
item?: (node: TNode, datum: TDatum) => AnimationValue & Partial<TNode>;
label?: (node: Text, datum: TDatum) => AnimationValue & Partial<Text>;
Expand All @@ -112,6 +115,7 @@ export abstract class PolarSeries<
[ChartAxisDirection.Y]: ['radiusName'],
},
canHaveAxes,
defaultTooltipRange,
});

this.showFocusBox = false;
Expand Down
8 changes: 7 additions & 1 deletion packages/ag-charts-community/src/chart/series/series.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ModuleContext, SeriesContext } from '../../module/moduleContext';
import { ModuleMap } from '../../module/moduleMap';
import type { SeriesOptionInstance, SeriesOptionModule, SeriesType } from '../../module/optionsModuleTypes';
import type { AgChartLabelFormatterParams, AgChartLabelOptions } from '../../options/agChartOptions';
import type { AgChartLabelFormatterParams, AgChartLabelOptions, InteractionRange } from '../../options/agChartOptions';
import type {
AgSeriesMarkerFormatterParams,
AgSeriesMarkerStyle,
Expand Down Expand Up @@ -265,6 +265,7 @@ export type SeriesConstructorOpts<TProps extends SeriesProperties<any>> = {
directionKeys?: SeriesDirectionKeysMapping<TProps>;
directionNames?: SeriesDirectionKeysMapping<TProps>;
canHaveAxes?: boolean;
defaultTooltipRange: InteractionRange;
};

export abstract class Series<
Expand Down Expand Up @@ -300,6 +301,9 @@ export abstract class Series<

readonly canHaveAxes: boolean;

// This property is used to keep backward compatibility with the old global `tooltip.range` option.
readonly defaultTooltipRange: InteractionRange;

get type(): SeriesType {
return (this.constructor as any).type ?? '';
}
Expand Down Expand Up @@ -415,12 +419,14 @@ export abstract class Series<
directionNames = {},
contentGroupVirtual = true,
canHaveAxes = false,
defaultTooltipRange,
} = seriesOpts;

this.ctx = moduleCtx;
this.directionKeys = directionKeys;
this.directionNames = directionNames;
this.canHaveAxes = canHaveAxes;
this.defaultTooltipRange = defaultTooltipRange;

this.contentGroup = this.rootGroup.appendChild(
new Group({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { InteractionRange } from '../../options/agChartOptions';
import type { AgSeriesTooltipRendererParams, AgTooltipRendererResult } from '../../options/chart/tooltipOptions';
import { BaseProperties } from '../../util/properties';
import type { RequireOptional } from '../../util/types';
import { BOOLEAN, FUNCTION, OBJECT, Validate } from '../../util/validation';
import { BOOLEAN, FUNCTION, INTERACTION_RANGE, OBJECT, Validate } from '../../util/validation';
import { TooltipPosition, toTooltipHtml } from '../tooltip/tooltip';

type TooltipRenderer<P> = (params: P) => string | AgTooltipRendererResult;
Expand All @@ -27,6 +28,9 @@ export class SeriesTooltip<P extends AgSeriesTooltipRendererParams> extends Base
@Validate(OBJECT)
readonly position = new TooltipPosition();

@Validate(INTERACTION_RANGE, { optional: true })
range?: InteractionRange = undefined;

toTooltipHtml(defaults: AgTooltipRendererResult, params: RequireOptional<P>) {
if (this.renderer) {
return toTooltipHtml(this.renderer(params as P), defaults);
Expand Down
3 changes: 1 addition & 2 deletions packages/ag-charts-community/src/chart/themes/chartTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type {
AgChartThemeOverrides,
AgChartThemePalette,
AgCommonThemeableChartOptions,
InteractionRange,
} from '../../options/agChartOptions';
import { deepClone, jsonWalk } from '../../util/json';
import { mergeDefaults } from '../../util/object';
Expand Down Expand Up @@ -195,7 +194,7 @@ export class ChartTheme {
tooltip: {
enabled: true,
darkTheme: IS_DARK_THEME,
range: 'nearest' as InteractionRange,
range: undefined,
delay: 0,
},
overlays: {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9a8ed54

Please sign in to comment.