From 3cb625be698120d5664da981b806803aca581552 Mon Sep 17 00:00:00 2001 From: pearmini Date: Fri, 14 Jun 2024 15:47:05 +0800 Subject: [PATCH] feat(tooltip): show tooltip for small interval --- ...-chart-emit-enable-disable-tooltip.spec.ts | 9 +++-- .../api-chart-emit-item-tooltip.spec.ts | 3 +- .../step1.html | 2 +- .../aapl2-candlestick-chart/step0.html | 2 +- .../alphabet-interval-channel/step0.html | 2 +- .../alphabet-interval-custom-dom/step0.html | 4 +- .../alphabet-interval-custom-dom/step1.html | 4 +- .../step0.html | 4 +- .../step1.html | 4 +- .../alphabet-interval-enterable/step0.html | 2 +- .../tooltip/alphabet-interval-full/step0.html | 2 +- .../alphabet-interval-multi-field/step0.html | 2 +- .../alphabet-interval-multi/step0.html | 2 +- .../alphabet-interval-null-items/step0.html | 2 +- .../alphabet-interval-object/step0.html | 2 +- .../alphabet-interval-title/step0.html | 2 +- .../step0.html | 2 +- .../step1.html | 2 +- .../alphabet-interval1d-mounted/step0.html | 2 +- .../tooltip/alphabet-interval1d/step0.html | 2 +- .../tooltip/alphabet-text/step0.html | 2 +- .../snapshots/tooltip/body-point2d/step0.html | 2 +- .../tooltip/cars3-line-xd/step0.html | 2 +- .../tooltip/disasters-point-slider/step0.html | 2 +- .../tooltip/energy-sankey-custom/step0.html | 2 +- .../tooltip/energy-sankey-custom/step1.html | 2 +- .../flare-point-circle-pack-custom/step0.html | 2 +- .../tooltip/flare-treemap-default/step0.html | 2 +- .../languages-interval-stack/step0.html | 2 +- .../tooltip/london-tube-line-geo/step0.html | 2 +- .../tooltip/london-tube-line-geo/step1.html | 2 +- .../tooltip/mock-interval-shared/step1.html | 2 +- .../morley-box-default-extend/step0.html | 2 +- .../snapshots/tooltip/morley-box/step0.html | 2 +- .../step1.html | 2 +- .../step0.html | 2 +- .../step0.html | 2 +- .../state-ages-interval-normalized/step0.html | 2 +- .../state-ages-interval-scrollbar/step0.html | 2 +- .../state-ages-interval-shared/step0.html | 2 +- .../unemployment2-rect-bin-x/step0.html | 2 +- .../tooltip/wind-vector-wind/step0.html | 2 +- __tests__/integration/utils/event.ts | 14 +++++++ ...alphabet-interval-tooltip-render-update.ts | 5 ++- .../plots/tooltip/disasters-point-slider.ts | 4 +- .../profit-interval-legend-filter-ordinal.ts | 3 +- __tests__/plots/tooltip/utils.ts | 17 +++++++-- src/interaction/tooltip.ts | 37 ++++++++++++++----- 48 files changed, 114 insertions(+), 66 deletions(-) diff --git a/__tests__/integration/api-chart-emit-enable-disable-tooltip.spec.ts b/__tests__/integration/api-chart-emit-enable-disable-tooltip.spec.ts index f78abaad03..917aea0580 100644 --- a/__tests__/integration/api-chart-emit-enable-disable-tooltip.spec.ts +++ b/__tests__/integration/api-chart-emit-enable-disable-tooltip.spec.ts @@ -2,7 +2,10 @@ import './utils/useSnapshotMatchers'; import { Chart } from '../../src'; import { createNodeGCanvas } from './utils/createNodeGCanvas'; import { sleep } from './utils/sleep'; -import { dispatchFirstElementEvent, dispatchPlotEvent } from './utils/event'; +import { + dispatchFirstElementPointerMoveEvent, + dispatchPlotEvent, +} from './utils/event'; import { kebabCase } from './utils/kebabCase'; const data = [ @@ -57,7 +60,7 @@ describe('chart.emit', () => { chart.emit('tooltip:disable'); await sleep(20); - dispatchFirstElementEvent(barCanvas, 'pointermove'); + dispatchFirstElementPointerMoveEvent(barCanvas); await expect(barCanvas).toMatchDOMSnapshot(dir, 'step0', { fileFormat: 'html', selector: '.g2-tooltip', @@ -65,7 +68,7 @@ describe('chart.emit', () => { chart.emit('tooltip:enable'); - dispatchFirstElementEvent(barCanvas, 'pointermove'); + dispatchFirstElementPointerMoveEvent(barCanvas); await expect(barCanvas).toMatchDOMSnapshot(dir, 'step1', { fileFormat: 'html', selector: '.g2-tooltip', diff --git a/__tests__/integration/api-chart-emit-item-tooltip.spec.ts b/__tests__/integration/api-chart-emit-item-tooltip.spec.ts index 2ccef4f334..8fcc88d9c5 100644 --- a/__tests__/integration/api-chart-emit-item-tooltip.spec.ts +++ b/__tests__/integration/api-chart-emit-item-tooltip.spec.ts @@ -4,6 +4,7 @@ import { dispatchFirstElementEvent, createPromise, receiveExpectData, + dispatchFirstElementPointerMoveEvent, } from './utils/event'; import './utils/useSnapshotMatchers'; import { createNodeGCanvas } from './utils/createNodeGCanvas'; @@ -37,7 +38,7 @@ describe('chart.emit', () => { // chart.on("tooltip:show", callback) should revive selected data. const [tooltipShowed, resolveShow] = createPromise(); chart.on('tooltip:show', receiveExpectData(resolveShow)); - dispatchFirstElementEvent(canvas, 'pointermove'); + dispatchFirstElementPointerMoveEvent(canvas); await tooltipShowed; // chart.on("tooltip:hide") should be called when hiding tooltip. diff --git a/__tests__/integration/snapshots/api/chart-emit-enable-disable-tooltip/step1.html b/__tests__/integration/snapshots/api/chart-emit-enable-disable-tooltip/step1.html index 5c7540989c..cd6c7cf29a 100644 --- a/__tests__/integration/snapshots/api/chart-emit-enable-disable-tooltip/step1.html +++ b/__tests__/integration/snapshots/api/chart-emit-enable-disable-tooltip/step1.html @@ -1,7 +1,7 @@

A

- (0, 0) + (98, 275)
  • letter: A
  • frequency: 0.08167
  • diff --git a/__tests__/integration/snapshots/tooltip/alphabet-interval-custom-dom/step1.html b/__tests__/integration/snapshots/tooltip/alphabet-interval-custom-dom/step1.html index 49ea6d821c..c40a83430b 100644 --- a/__tests__/integration/snapshots/tooltip/alphabet-interval-custom-dom/step1.html +++ b/__tests__/integration/snapshots/tooltip/alphabet-interval-custom-dom/step1.html @@ -1,12 +1,12 @@

    B

    - (0, 0) + (118, 375)
    • letter: B
    • frequency: 0.01492
    • diff --git a/__tests__/integration/snapshots/tooltip/alphabet-interval-custom-string/step0.html b/__tests__/integration/snapshots/tooltip/alphabet-interval-custom-string/step0.html index 9c5a73d7b7..8e0baa639e 100644 --- a/__tests__/integration/snapshots/tooltip/alphabet-interval-custom-string/step0.html +++ b/__tests__/integration/snapshots/tooltip/alphabet-interval-custom-string/step0.html @@ -1,11 +1,11 @@

      A

      - (0, 0) + (98, 275)
      • letter: A
      • frequency: 0.08167
      • diff --git a/__tests__/integration/snapshots/tooltip/alphabet-interval-custom-string/step1.html b/__tests__/integration/snapshots/tooltip/alphabet-interval-custom-string/step1.html index 25e597d8d0..c1fd542b35 100644 --- a/__tests__/integration/snapshots/tooltip/alphabet-interval-custom-string/step1.html +++ b/__tests__/integration/snapshots/tooltip/alphabet-interval-custom-string/step1.html @@ -1,11 +1,11 @@

        B

        - (0, 0) + (118, 375)
        • letter: B
        • frequency: 0.01492
        • diff --git a/__tests__/integration/snapshots/tooltip/alphabet-interval-enterable/step0.html b/__tests__/integration/snapshots/tooltip/alphabet-interval-enterable/step0.html index 354c77e062..ef6d38a4e9 100644 --- a/__tests__/integration/snapshots/tooltip/alphabet-interval-enterable/step0.html +++ b/__tests__/integration/snapshots/tooltip/alphabet-interval-enterable/step0.html @@ -1,7 +1,7 @@

          B

          diff --git a/__tests__/integration/snapshots/tooltip/alphabet-interval-tooltip-render-update/step1.html b/__tests__/integration/snapshots/tooltip/alphabet-interval-tooltip-render-update/step1.html index 829ff3ffe4..4fe68828ff 100644 --- a/__tests__/integration/snapshots/tooltip/alphabet-interval-tooltip-render-update/step1.html +++ b/__tests__/integration/snapshots/tooltip/alphabet-interval-tooltip-render-update/step1.html @@ -1,7 +1,7 @@

          B

          diff --git a/__tests__/integration/snapshots/tooltip/alphabet-interval1d-mounted/step0.html b/__tests__/integration/snapshots/tooltip/alphabet-interval1d-mounted/step0.html index 61bc4505ec..1b9f47c1e2 100644 --- a/__tests__/integration/snapshots/tooltip/alphabet-interval1d-mounted/step0.html +++ b/__tests__/integration/snapshots/tooltip/alphabet-interval1d-mounted/step0.html @@ -1,7 +1,7 @@

            tooltip

            \ No newline at end of file diff --git a/__tests__/integration/snapshots/tooltip/energy-sankey-custom/step0.html b/__tests__/integration/snapshots/tooltip/energy-sankey-custom/step0.html index 413f6c6847..acd3fd3d01 100644 --- a/__tests__/integration/snapshots/tooltip/energy-sankey-custom/step0.html +++ b/__tests__/integration/snapshots/tooltip/energy-sankey-custom/step0.html @@ -1,7 +1,7 @@
                    { await sleep(100); const elements = document.getElementsByClassName(ELEMENT_CLASS_NAME); const [e0] = elements; - e0.dispatchEvent(new CustomEvent('pointermove')); + dispatchPointermove(e0); }, }, { changeState: async () => { const elements = document.getElementsByClassName(ELEMENT_CLASS_NAME); const [e0] = elements; - e0.dispatchEvent(new CustomEvent('pointermove')); + dispatchPointermove(e0); }, }, ]; diff --git a/__tests__/plots/tooltip/disasters-point-slider.ts b/__tests__/plots/tooltip/disasters-point-slider.ts index 454103052f..c005b6f3df 100644 --- a/__tests__/plots/tooltip/disasters-point-slider.ts +++ b/__tests__/plots/tooltip/disasters-point-slider.ts @@ -1,6 +1,4 @@ -import { CustomEvent } from '@antv/g'; -import { G2Spec, PLOT_CLASS_NAME } from '../../../src'; -import { SLIDER_CLASS_NAME } from '../../../src/interaction/sliderFilter'; +import { G2Spec } from '../../../src'; import { tooltipSteps } from './utils'; export function disastersPointSlider(): G2Spec { diff --git a/__tests__/plots/tooltip/profit-interval-legend-filter-ordinal.ts b/__tests__/plots/tooltip/profit-interval-legend-filter-ordinal.ts index ddc35debdf..15528a9303 100644 --- a/__tests__/plots/tooltip/profit-interval-legend-filter-ordinal.ts +++ b/__tests__/plots/tooltip/profit-interval-legend-filter-ordinal.ts @@ -2,6 +2,7 @@ import { CustomEvent } from '@antv/g'; import { G2Spec, ELEMENT_CLASS_NAME } from '../../../src'; import { LEGEND_ITEMS_CLASS_NAME } from '../../../src/interaction/legendFilter'; import { profit } from '../../data/profit'; +import { dispatchPointermove } from './utils'; export function profitIntervalLegendFilterOrdinal(): G2Spec { return { @@ -37,7 +38,7 @@ profitIntervalLegendFilterOrdinal.steps = ({ canvas }) => { changeState: async () => { const elements = document.getElementsByClassName(ELEMENT_CLASS_NAME); const [e0] = elements; - e0.dispatchEvent(new CustomEvent('pointermove')); + dispatchPointermove(e0); }, }, ]; diff --git a/__tests__/plots/tooltip/utils.ts b/__tests__/plots/tooltip/utils.ts index f95245de50..8a7c505ab4 100644 --- a/__tests__/plots/tooltip/utils.ts +++ b/__tests__/plots/tooltip/utils.ts @@ -19,14 +19,25 @@ export function seriesTooltipSteps(...points) { }; } +export function dispatchPointermove(element) { + const bounds = element.getRenderBounds() ?? element.getBounds(); + const { + min: [x0, y0], + max: [x1, y1], + } = bounds; + const mx = (x0 + x1) / 2; + const my = (y0 + y1) / 2; + element.dispatchEvent( + new CustomEvent('pointermove', { offsetX: mx, offsetY: my }), + ); +} + export function tooltipSteps(...index) { return ({ canvas }) => { const { document } = canvas; const elements = document.getElementsByClassName(ELEMENT_CLASS_NAME); const steps = index.map((i) => ({ - changeState: async () => { - elements[i].dispatchEvent(new CustomEvent('pointermove')); - }, + changeState: async () => dispatchPointermove(elements[i]), })); return steps; }; diff --git a/src/interaction/tooltip.ts b/src/interaction/tooltip.ts index a7f9f172e6..a270d56283 100644 --- a/src/interaction/tooltip.ts +++ b/src/interaction/tooltip.ts @@ -533,13 +533,6 @@ function hasSeries(markState): boolean { ); } -function findElement(node: DisplayObject) { - return maybeRoot(node, (node) => { - if (!node.classList) return false; - return node.classList.includes('element'); - }); -} - /** * Show tooltip for series item. */ @@ -929,6 +922,7 @@ export function tooltip( root: DisplayObject, { elements: elementsof, + coordinate, scale, render, groupName, @@ -956,11 +950,36 @@ export function tooltip( ) { const elements = elementsof(root); const keyGroup = group(elements, groupKey); + const inInterval = (d) => d.markType === 'interval'; + const isBar = elements.every(inInterval) && !isPolar(coordinate); + const xof = (d) => d.__data__.x; + const scaleX = scale.x; + + // Sort for bisector search. + if (isBar) elements.sort((a, b) => xof(a) - xof(b)); + + const findElement = isBar + ? (event) => { + const mouse = mousePosition(root, event); + if (!mouse) return; + const offsetX = scaleX?.getBandWidth ? scaleX.getBandWidth() / 2 : 0; + const [normalizedX] = coordinate.invert(mouse); + const abstractX = normalizedX - offsetX; + const search = bisector(xof).center; + const i = search(elements, abstractX); + return elements[i]; + } + : (event) => { + const { target } = event; + return maybeRoot(target, (node) => { + if (!node.classList) return false; + return node.classList.includes('element'); + }); + }; const pointermove = throttle( (event) => { - const { target } = event; - const element = findElement(target); + const element = findElement(event); if (!element) { hideTooltip({ root, single, emitter, event }); return;