From 47b0c7525f5edd6763814da2109c689353736a97 Mon Sep 17 00:00:00 2001 From: hustcc Date: Mon, 15 May 2023 17:54:49 +0800 Subject: [PATCH 1/3] feat: tooltip render to mount dom --- .../step0.html | 1 + .../aapl-line-area-basic-sample-mount.ts | 51 ++++++++++ __tests__/plots/tooltip/index.ts | 1 + src/interaction/tooltip.ts | 98 ++++++++++++++----- src/spec/interaction.ts | 5 +- 5 files changed, 130 insertions(+), 26 deletions(-) create mode 100644 __tests__/integration/snapshots/tooltip/aapl-line-area-basic-sample-mount/step0.html create mode 100644 __tests__/plots/tooltip/aapl-line-area-basic-sample-mount.ts diff --git a/__tests__/integration/snapshots/tooltip/aapl-line-area-basic-sample-mount/step0.html b/__tests__/integration/snapshots/tooltip/aapl-line-area-basic-sample-mount/step0.html new file mode 100644 index 0000000000..ec747fa47d --- /dev/null +++ b/__tests__/integration/snapshots/tooltip/aapl-line-area-basic-sample-mount/step0.html @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/__tests__/plots/tooltip/aapl-line-area-basic-sample-mount.ts b/__tests__/plots/tooltip/aapl-line-area-basic-sample-mount.ts new file mode 100644 index 0000000000..e9e85cc8c4 --- /dev/null +++ b/__tests__/plots/tooltip/aapl-line-area-basic-sample-mount.ts @@ -0,0 +1,51 @@ +import { G2Spec } from '../../../src'; +import { seriesTooltipSteps } from './utils'; + +export function aaplLineAreaBasicSampleMount(): G2Spec { + return { + width: 600, + height: 400, + type: 'view', + data: { + type: 'fetch', + value: 'data/aapl.csv', + }, + children: [ + { + type: 'area', + encode: { + x: 'date', + y: 'close', + }, + transform: [ + { + type: 'sample', + thresholds: 100, + strategy: 'lttb', + }, + ], + style: { + fillOpacity: 0.5, + }, + tooltip: { + title: (d) => new Date(d.date).toUTCString(), + }, + interaction: { + tooltip: { + mount: 'body', + bounding: { + x: 100, + y: 100, + width: 500, + height: 300, + }, + }, + }, + }, + ], + }; +} + +aaplLineAreaBasicSampleMount.maxError = 100; + +aaplLineAreaBasicSampleMount.steps = seriesTooltipSteps([500, 200]); diff --git a/__tests__/plots/tooltip/index.ts b/__tests__/plots/tooltip/index.ts index cd18085e5d..ac04fb29d1 100644 --- a/__tests__/plots/tooltip/index.ts +++ b/__tests__/plots/tooltip/index.ts @@ -56,6 +56,7 @@ export { scoreByItemAreaRadar } from './score-by-item-area-radar'; export { profitIntervalLegendFilterOrdinal } from './profit-interval-legend-filter-ordinal'; export { aaplLineSliderFilter } from './appl-line-slider-filter'; export { aaplLineAreaBasicSample } from './aapl-line-area-basic-sample'; +export { aaplLineAreaBasicSampleMount } from './aapl-line-area-basic-sample-mount'; export { aaplAreaMissingDataTranspose } from './aapl-area-missing-data-transpose'; export { alphabetIntervalBrushTooltip } from './alphabet-interval-brush-tooltip'; export { mockLineFalsy } from './mock-line-falsy'; diff --git a/src/interaction/tooltip.ts b/src/interaction/tooltip.ts index fb0e57c27c..7cabab8d90 100644 --- a/src/interaction/tooltip.ts +++ b/src/interaction/tooltip.ts @@ -7,6 +7,7 @@ import { defined, subObject } from '../utils/helper'; import { isTranspose, isPolar } from '../utils/coordinate'; import { angle, sub } from '../utils/vector'; import { invert } from '../utils/scale'; +import { BBox } from '../runtime'; import { selectG2Elements, createXKey, @@ -17,30 +18,59 @@ import { } from './utils'; import { dataOf } from './event'; -function getContainer(group: IElement) { +function getContainer( + group: IElement, + mount: string | HTMLElement, +): HTMLElement { + if (mount) + return typeof mount === 'string' ? document.querySelector(mount) : mount; // @ts-ignore return group.getRootNode().defaultView.getConfig().container; } -function createTooltip(root, x0, y0, position, enterable) { +function getBounding(root): BBox { const bbox = root.getBounds(); const { - min: [x, y], - max: [x1, y1], + min: [x1, y1], + max: [x2, y2], } = bbox; + return { + x: x1, + y: y1, + width: x2 - x1, + height: y2 - y1, + }; +} + +function getContainerOffset( + container1: HTMLElement, + container2: HTMLElement, +): { x: number; y: number } { + const r1 = container1.getBoundingClientRect(); + const r2 = container2.getBoundingClientRect(); + return { + x: r1.x - r2.x, + y: r1.y - r2.y, + }; +} + +function createTooltip( + container: HTMLElement, + x0, + y0, + position, + enterable, + bounding, + containerOffset, +) { const tooltipElement = new TooltipComponent({ className: 'tooltip', style: { x: x0, y: y0, - container: { x: 0, y: 0 }, + container: containerOffset, data: [], - bounding: { - x, - y, - width: x1 - x, - height: y1 - y, - }, + bounding, position, enterable, title: '', @@ -55,8 +85,6 @@ function createTooltip(root, x0, y0, position, enterable) { }, }, }); - // @ts-ignore - const container = getContainer(root); container.appendChild(tooltipElement.HTMLTooltipElement); return tooltipElement; } @@ -71,11 +99,25 @@ function showTooltip({ single, position = 'right-bottom', enterable = false, + mount, + bounding, }) { // All the views share the same tooltip. - const container = single ? getContainer(root) : root; - const { tooltipElement = createTooltip(root, x, y, position, enterable) } = - container; + const canvasContainer = root.getRootNode().defaultView.getConfig().container; + const container = single ? getContainer(root, mount) : root; + const b = bounding || getBounding(root); + const containerOffset = getContainerOffset(canvasContainer, container); + const { + tooltipElement = createTooltip( + container, + x, + y, + position, + enterable, + b, + containerOffset, + ), + } = container; const { items, title = '' } = data; tooltipElement.update({ x, @@ -91,8 +133,8 @@ function showTooltip({ container.tooltipElement = tooltipElement; } -function hideTooltip({ root, single, emitter, nativeEvent = true }) { - const container = single ? getContainer(root) : root; +function hideTooltip({ root, single, emitter, nativeEvent = true, mount }) { + const container = single ? getContainer(root, mount) : root; const { tooltipElement } = container; if (tooltipElement) { tooltipElement.hide(); @@ -318,6 +360,8 @@ export function seriesTooltip( single = true, position, enterable, + mount, + bounding, style: _style = {}, ...rest }: Record, @@ -477,6 +521,8 @@ export function seriesTooltip( single, position, enterable, + mount, + bounding, }); } @@ -504,7 +550,7 @@ export function seriesTooltip( ) as (...args: any[]) => void; const hide = () => { - hideTooltip({ root, single, emitter }); + hideTooltip({ root, single, emitter, mount }); if (crosshairs) hideRuleY(root); }; @@ -521,7 +567,7 @@ export function seriesTooltip( }; const onTooltipHide = () => { - hideTooltip({ root, single, emitter, nativeEvent: false }); + hideTooltip({ root, single, emitter, nativeEvent: false, mount }); }; emitter.on('tooltip:show', onTooltipShow); @@ -564,6 +610,8 @@ export function tooltip( enterable, datum, view, + mount, + bounding, }: Record, ) { const elements = elementsof(root); @@ -574,7 +622,7 @@ export function tooltip( (event) => { const { target: element } = event; if (!elementSet.has(element)) { - hideTooltip({ root, single, emitter }); + hideTooltip({ root, single, emitter, mount }); return; } const k = groupKey(element); @@ -593,7 +641,7 @@ export function tooltip( } if (isEmptyTooltipData(data)) { - hideTooltip({ root, single, emitter }); + hideTooltip({ root, single, emitter, mount }); return; } @@ -608,6 +656,8 @@ export function tooltip( single, position, enterable, + mount, + bounding, }); emitter.emit('tooltip:show', { @@ -625,7 +675,7 @@ export function tooltip( const pointerout = (event) => { const { target: element } = event; if (!elementSet.has(element)) return; - hideTooltip({ root, single, emitter }); + hideTooltip({ root, single, emitter, mount }); }; const onTooltipShow = ({ nativeEvent, data }) => { @@ -647,7 +697,7 @@ export function tooltip( const onTooltipHide = ({ nativeEvent }: any = {}) => { if (nativeEvent) return; - hideTooltip({ root, single, emitter, nativeEvent: false }); + hideTooltip({ root, single, emitter, nativeEvent: false, mount }); }; emitter.on('tooltip:show', onTooltipShow); diff --git a/src/spec/interaction.ts b/src/spec/interaction.ts index 78b4aae817..b42a85db2e 100644 --- a/src/spec/interaction.ts +++ b/src/spec/interaction.ts @@ -1,8 +1,7 @@ import type { TooltipStyleProps } from '@antv/gui'; -import { InteractionComponent } from '../runtime'; +import { BBox, InteractionComponent } from '../runtime'; import { FisheyeCoordinate } from './coordinateTransform'; import { TooltipItemValue } from './component'; -import { AtheisticChanelTypes } from './mark'; export type Interaction = | ElementHighlightInteraction @@ -176,6 +175,8 @@ export type TooltipInteraction = { crosshairs?: boolean; groupName?: boolean; position?: TooltipStyleProps['position']; + bounding?: BBox; + mount?: string | HTMLElement; // enterable?: boolean; sort?: (d: TooltipItemValue) => any; filter?: (d: TooltipItemValue) => any; From e0bf1250d7b2b2375892928450ee6916e5643e9b Mon Sep 17 00:00:00 2001 From: hustcc Date: Mon, 15 May 2023 20:20:06 +0800 Subject: [PATCH 2/3] fix: append canvas into jsdom --- .../step0.html | 47 ++++++++++++++++++- .../step1.html | 46 ++++++++++++++++++ __tests__/integration/spec-tooltip.spec.ts | 3 ++ .../integration/utils/createDOMGCanvas.ts | 1 + __tests__/integration/utils/renderSpec.ts | 1 - .../integration/utils/toMatchDOMSnapshot.ts | 2 +- .../aapl-line-area-basic-sample-mount.ts | 2 +- 7 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 __tests__/integration/snapshots/tooltip/aapl-line-area-basic-sample-mount/step1.html diff --git a/__tests__/integration/snapshots/tooltip/aapl-line-area-basic-sample-mount/step0.html b/__tests__/integration/snapshots/tooltip/aapl-line-area-basic-sample-mount/step0.html index ec747fa47d..47e433703a 100644 --- a/__tests__/integration/snapshots/tooltip/aapl-line-area-basic-sample-mount/step0.html +++ b/__tests__/integration/snapshots/tooltip/aapl-line-area-basic-sample-mount/step0.html @@ -1 +1,46 @@ -null \ No newline at end of file +
+
+ Sun, 13 May 2007 00:00:00 GMT +
+
    +
  • + + + + close + + + + 109.36 + +
  • +
+
; diff --git a/__tests__/integration/snapshots/tooltip/aapl-line-area-basic-sample-mount/step1.html b/__tests__/integration/snapshots/tooltip/aapl-line-area-basic-sample-mount/step1.html new file mode 100644 index 0000000000..c334c6681e --- /dev/null +++ b/__tests__/integration/snapshots/tooltip/aapl-line-area-basic-sample-mount/step1.html @@ -0,0 +1,46 @@ +
+
+ Mon, 13 Feb 2012 00:00:00 GMT +
+
    +
  • + + + + close + + + + 509.46 + +
  • +
+
; diff --git a/__tests__/integration/spec-tooltip.spec.ts b/__tests__/integration/spec-tooltip.spec.ts index 22f1e556b7..be6b7ede9a 100644 --- a/__tests__/integration/spec-tooltip.spec.ts +++ b/__tests__/integration/spec-tooltip.spec.ts @@ -55,4 +55,7 @@ describe('Tooltips', () => { } }); } + afterEach(() => { + document.body.innerHTML = ''; + }); }); diff --git a/__tests__/integration/utils/createDOMGCanvas.ts b/__tests__/integration/utils/createDOMGCanvas.ts index b5da773497..386b6f8273 100644 --- a/__tests__/integration/utils/createDOMGCanvas.ts +++ b/__tests__/integration/utils/createDOMGCanvas.ts @@ -11,6 +11,7 @@ export function createDOMGCanvas(width, height) { const domInteractionPlugin = renderer.getPlugin('dom-interaction'); renderer.unregisterPlugin(domInteractionPlugin); const container = document.createElement('div'); + document.body.append(container); return new Canvas({ container, width, diff --git a/__tests__/integration/utils/renderSpec.ts b/__tests__/integration/utils/renderSpec.ts index bdf0e84e58..827a3584b3 100644 --- a/__tests__/integration/utils/renderSpec.ts +++ b/__tests__/integration/utils/renderSpec.ts @@ -2,7 +2,6 @@ import { Canvas } from '@antv/g'; import { G2Context, G2Spec, render } from '../../../src'; import { renderToMountedElement } from '../../utils/renderToMountedElement'; import { createNodeGCanvas } from './createNodeGCanvas'; -import { sleep } from './sleep'; export async function renderSpec( generateOptions, diff --git a/__tests__/integration/utils/toMatchDOMSnapshot.ts b/__tests__/integration/utils/toMatchDOMSnapshot.ts index 92294d3fb4..d325e1c4c1 100644 --- a/__tests__/integration/utils/toMatchDOMSnapshot.ts +++ b/__tests__/integration/utils/toMatchDOMSnapshot.ts @@ -21,7 +21,7 @@ export async function toMatchDOMSnapshot( const actualPath = path.join(dir, `${name}-actual.${fileFormat}`); const expectedPath = path.join(dir, `${name}.${fileFormat}`); const container = gCanvas.getConfig().container as HTMLElement; - const dom = selector ? container.querySelector(selector) : container; + const dom = selector ? document.body.querySelector(selector) : container; let actual; try { diff --git a/__tests__/plots/tooltip/aapl-line-area-basic-sample-mount.ts b/__tests__/plots/tooltip/aapl-line-area-basic-sample-mount.ts index e9e85cc8c4..4ae29b8563 100644 --- a/__tests__/plots/tooltip/aapl-line-area-basic-sample-mount.ts +++ b/__tests__/plots/tooltip/aapl-line-area-basic-sample-mount.ts @@ -48,4 +48,4 @@ export function aaplLineAreaBasicSampleMount(): G2Spec { aaplLineAreaBasicSampleMount.maxError = 100; -aaplLineAreaBasicSampleMount.steps = seriesTooltipSteps([500, 200]); +aaplLineAreaBasicSampleMount.steps = seriesTooltipSteps([50, 200], [550, 200]); From feea06cc61e5e5ae53a2920326158b2deca8ff8a Mon Sep 17 00:00:00 2001 From: hustcc Date: Mon, 15 May 2023 20:24:28 +0800 Subject: [PATCH 3/3] docs: update interaction.tooltip documents --- site/docs/spec/interaction/tooltip.zh.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/site/docs/spec/interaction/tooltip.zh.md b/site/docs/spec/interaction/tooltip.zh.md index 4ef031df1f..bc0e3f4d0b 100644 --- a/site/docs/spec/interaction/tooltip.zh.md +++ b/site/docs/spec/interaction/tooltip.zh.md @@ -44,6 +44,8 @@ chart.render(); | body | 是否展示 tooltip | `boolean` | true | | groupName | 是否使用 groupName | `boolean` | true | | position | tooltip 位置 | `TooltipPosition` | - | +| mount | tooltip 渲染的 dom 节点 | `string` \| `HTMLElement` | 图表容器 | +| bounding | tooltip 渲染的限制区域,超出会自动调整位置 | `BBox` | 图表区域大小 | | crosshairs | 是否暂时指示线 | `boolean` | - | | `crosshairs${StyleAttrs}` | 指示线的样式 | `number \| string` | - | | render | 自定义 tooltip 渲染函数 | `(event, options) => HTMLElement \| string` | - | @@ -52,7 +54,6 @@ chart.render(); ```ts type TooltipPosition = - | 'auto' | 'top' | 'bottom' | 'left' @@ -61,6 +62,8 @@ type TooltipPosition = | 'top-right' | 'bottom-left' | 'bottom-right'; + +type BBox = { x: number, y: number, width: number, height: number }; ``` ## 案例