Skip to content

Commit

Permalink
feat(tooltip): support closest
Browse files Browse the repository at this point in the history
  • Loading branch information
pearmini committed Sep 19, 2023
1 parent 8a10429 commit d879968
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<div
xmlns="http://www.w3.org/1999/xhtml"
class="g2-tooltip"
style="pointer-events: none; position: absolute; visibility: visible; z-index: 8; transition: visibility 0.2s cubic-bezier(0.23, 1, 0.32, 1), left 0.4s cubic-bezier(0.23, 1, 0.32, 1), top 0.4s cubic-bezier(0.23, 1, 0.32, 1); background-color: rgba(255, 255, 255, 0.96); box-shadow: 0 6px 12px 0 rgba(0, 0, 0, 0.12); border-radius: 4px; color: rgba(0, 0, 0, 0.65); font-size: 12px; line-height: 20px; padding: 12px; min-width: 120px; max-width: 360px; font-family: Roboto-Regular; left: 560px; top: 310px;"
>
<div
class="g2-tooltip-title"
style="color: rgba(0, 0, 0, 0.45); overflow: hidden; white-space: nowrap; text-overflow: ellipsis;"
>
10:40
</div>
<ul
class="g2-tooltip-list"
style="margin: 0px; list-style-type: none; padding: 0px;"
>
<li
class="g2-tooltip-list-item"
data-index="0"
style="list-style-type: none; display: flex; line-height: 2em; align-items: center; justify-content: space-between; white-space: nowrap;"
>
<span
class="g2-tooltip-list-item-name"
style="display: flex; align-items: center; max-width: 216px;"
>
<span
class="g2-tooltip-list-item-marker"
style="background: rgb(253, 174, 107); width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 4px;"
/>
<span
class="g2-tooltip-list-item-name-label"
title="people"
style="flex: 1; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;"
>
people
</span>
</span>
<span
class="g2-tooltip-list-item-value"
title="2"
style="display: inline-block; float: right; flex: 1; text-align: right; min-width: 28px; margin-left: 30px; color: rgba(0, 0, 0, 0.85); overflow: hidden; white-space: nowrap; text-overflow: ellipsis;"
>
2
</span>
</li>
<li
class="g2-tooltip-list-item"
data-index="1"
style="list-style-type: none; display: flex; line-height: 2em; align-items: center; justify-content: space-between; white-space: nowrap;"
>
<span
class="g2-tooltip-list-item-name"
style="display: flex; align-items: center; max-width: 216px;"
>
<span
class="g2-tooltip-list-item-marker"
style="background: rgb(23, 131, 255); width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 4px;"
/>
<span
class="g2-tooltip-list-item-name-label"
title="waiting"
style="flex: 1; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;"
>
waiting
</span>
</span>
<span
class="g2-tooltip-list-item-value"
title="1"
style="display: inline-block; float: right; flex: 1; text-align: right; min-width: 28px; margin-left: 30px; color: rgba(0, 0, 0, 0.85); overflow: hidden; white-space: nowrap; text-overflow: ellipsis;"
>
1
</span>
</li>
</ul>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<div
xmlns="http://www.w3.org/1999/xhtml"
class="g2-tooltip"
style="pointer-events: none; position: absolute; visibility: visible; z-index: 8; transition: visibility 0.2s cubic-bezier(0.23, 1, 0.32, 1), left 0.4s cubic-bezier(0.23, 1, 0.32, 1), top 0.4s cubic-bezier(0.23, 1, 0.32, 1); background-color: rgba(255, 255, 255, 0.96); box-shadow: 0 6px 12px 0 rgba(0, 0, 0, 0.12); border-radius: 4px; color: rgba(0, 0, 0, 0.65); font-size: 12px; line-height: 20px; padding: 12px; min-width: 120px; max-width: 360px; font-family: Roboto-Regular; left: 155px; top: 310px;"
>
<div
class="g2-tooltip-title"
style="color: rgba(0, 0, 0, 0.45); overflow: hidden; white-space: nowrap; text-overflow: ellipsis;"
>
10:15
</div>
<ul
class="g2-tooltip-list"
style="margin: 0px; list-style-type: none; padding: 0px;"
>
<li
class="g2-tooltip-list-item"
data-index="0"
style="list-style-type: none; display: flex; line-height: 2em; align-items: center; justify-content: space-between; white-space: nowrap;"
>
<span
class="g2-tooltip-list-item-name"
style="display: flex; align-items: center; max-width: 216px;"
>
<span
class="g2-tooltip-list-item-marker"
style="background: rgb(253, 174, 107); width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 4px;"
/>
<span
class="g2-tooltip-list-item-name-label"
title="people"
style="flex: 1; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;"
>
people
</span>
</span>
<span
class="g2-tooltip-list-item-value"
title="3"
style="display: inline-block; float: right; flex: 1; text-align: right; min-width: 28px; margin-left: 30px; color: rgba(0, 0, 0, 0.85); overflow: hidden; white-space: nowrap; text-overflow: ellipsis;"
>
3
</span>
</li>
<li
class="g2-tooltip-list-item"
data-index="1"
style="list-style-type: none; display: flex; line-height: 2em; align-items: center; justify-content: space-between; white-space: nowrap;"
>
<span
class="g2-tooltip-list-item-name"
style="display: flex; align-items: center; max-width: 216px;"
>
<span
class="g2-tooltip-list-item-marker"
style="background: rgb(23, 131, 255); width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 4px;"
/>
<span
class="g2-tooltip-list-item-name-label"
title="waiting"
style="flex: 1; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;"
>
waiting
</span>
</span>
<span
class="g2-tooltip-list-item-value"
title="6"
style="display: inline-block; float: right; flex: 1; text-align: right; min-width: 28px; margin-left: 30px; color: rgba(0, 0, 0, 0.85); overflow: hidden; white-space: nowrap; text-overflow: ellipsis;"
>
6
</span>
</li>
</ul>
</div>
1 change: 1 addition & 0 deletions __tests__/plots/tooltip/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ export { pointsPointRegressionQuad } from './points-point-regression-quad';
export { alphabetIntervalTooltipRenderUpdate } from './alphabet-interval-tooltip-render-update';
export { mockIntervalShared } from './mock-interval-shared';
export { stateAgesIntervalCustomStyle } from './stateages-interval-custom-style';
export { mockTooltipClosest } from './mock-tooltip-closest';
46 changes: 46 additions & 0 deletions __tests__/plots/tooltip/mock-tooltip-closest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { seriesTooltipSteps } from './utils';

export function mockTooltipClosest() {
return {
type: 'view',
data: [
{ time: '10:10', call: 4, waiting: 2, people: 2 },
{ time: '10:15', call: 2, waiting: 6, people: 3 },
{ time: '10:20', call: 13, waiting: 2, people: 5 },
{ time: '10:25', call: 9, waiting: 9, people: 1 },
{ time: '10:30', call: 5, waiting: 2, people: 3 },
{ time: '10:35', call: 8, waiting: 2, people: 1 },
{ time: '10:40', call: 13, waiting: 1, people: 2 },
],
children: [
{
type: 'interval',
encode: { x: 'time', y: 'waiting' },
axis: { y: { title: 'Waiting', titleFill: '#5B8FF9' } },
},
{
type: 'line',
encode: { x: 'time', y: 'people', shape: 'smooth' },
scale: { y: { independent: true } },
style: { stroke: '#fdae6b', lineWidth: 2 },
axis: {
y: {
position: 'right',
grid: null,
title: 'People',
titleFill: '#fdae6b',
},
},
},
],
interaction: {
tooltip: {
closest: true,
},
},
};
}

mockTooltipClosest.steps = seriesTooltipSteps([570, 300], [145, 300]);

mockTooltipClosest.only = true;
43 changes: 22 additions & 21 deletions site/docs/spec/interaction/tooltip.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,28 @@ chart.render();

## 选项

| 属性 | 描述 | 类型 | 默认值 |
| ------------------------- | ----------------------------------------------------------------- | ------------------------------------------- | --------------------- |
| wait | 提示信息更新的时间间隔,单位为毫秒 | `number` | 50 |
| leading | 是否在时间间隔开始的时候更新提示信息 | `boolean` | true |
| trailing | 是否在时间间隔结束的时候更新提示信息 | `boolean` | false |
| shared | 相同 x 的元素是否共享 tooltip | `boolean` | false |
| series | 是否是系列元素的 tooltip | `boolean` | - |
| body | 是否展示 tooltip | `boolean` | true |
| marker | 是否展示 marker | `boolean` | true |
| groupName | 是否使用 groupName | `boolean` | true |
| position | tooltip 位置 | `TooltipPosition` | - |
| mount | tooltip 渲染的 dom 节点 | `string` \| `HTMLElement` | 图表容器 |
| bounding | tooltip 渲染的限制区域,超出会自动调整位置 | `BBox` | 图表区域大小 |
| crosshairs | 是否暂时指示线 | `boolean` | - |
| `crosshairs${StyleAttrs}` | 指示线的样式 | `number \| string` | - |
| `marker${StyleAttrs}` | marker 的样式 | `number \| string` | - |
| render | 自定义 tooltip 渲染函数 | `(event, options) => HTMLElement \| string` | - |
| sort | item 排序器 | `(d: TooltipItemValue) => any` | - |
| filter | item 筛选器 | `(d: TooltipItemValue) => any` | - |
| disableNative | 是否响应原生事件(pointerover 和 pointerout) | true | `boolean` |
| css | 设置容器的 [css](/examples/component/tooltip/#tooltip-style) 样式 | - | `Record<string, any>` |
| 属性 | 描述 | 类型 | 默认值 |
| ------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------- | --------------------- |
| wait | 提示信息更新的时间间隔,单位为毫秒 | `number` | 50 |
| leading | 是否在时间间隔开始的时候更新提示信息 | `boolean` | true |
| trailing | 是否在时间间隔结束的时候更新提示信息 | `boolean` | false |
| shared | 相同 x 的元素是否共享 tooltip | `boolean` | false |
| series | 是否是系列元素的 tooltip | `boolean` | - |
| body | 是否展示 tooltip | `boolean` | true |
| marker | 是否展示 marker | `boolean` | true |
| groupName | 是否使用 groupName | `boolean` | true |
| position | tooltip 位置 | `TooltipPosition` | - |
| mount | tooltip 渲染的 dom 节点 | `string` \| `HTMLElement` | 图表容器 |
| bounding | tooltip 渲染的限制区域,超出会自动调整位置 | `BBox` | 图表区域大小 |
| crosshairs | 是否暂时指示线 | `boolean` | - |
| `crosshairs${StyleAttrs}` | 指示线的样式 | `number \| string` | - |
| `marker${StyleAttrs}` | marker 的样式 | `number \| string` | - |
| render | 自定义 tooltip 渲染函数 | `(event, options) => HTMLElement \| string` | - |
| sort | item 排序器 | `(d: TooltipItemValue) => any` | - |
| filter | item 筛选器 | `(d: TooltipItemValue) => any` | - |
| disableNative | 是否响应原生事件(pointerover 和 pointerout) | true | `boolean` |
| css | 设置容器的 [css](/examples/component/tooltip/#tooltip-style) 样式 | - | `Record<string, any>` |
| closest | 在设置 series 为 true 的时候,是否拾取离鼠标最近元素的 tooltip item,一般在双轴图中使用 | false | `boolean` |

```ts
type TooltipPosition =
Expand Down
2 changes: 2 additions & 0 deletions site/examples/general/dual/demo/line-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ chart
titleFill: '#fdae6b',
});

chart.interaction('tooltip', { closest: true });

chart.render();
2 changes: 2 additions & 0 deletions site/examples/general/dual/demo/multi-line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,6 @@ chart
titleFill: '#91CC75',
});

chart.interaction('tooltip', { closest: true });

chart.render();
2 changes: 2 additions & 0 deletions site/examples/general/dual/demo/pareto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,6 @@ chart
.axis('y', null)
.tooltip(null);

chart.interaction('tooltip', { closest: true });

chart.render();
36 changes: 29 additions & 7 deletions src/interaction/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ export function seriesTooltip(
startY = 0,
body = true,
single = true,
closest = false,
position,
enterable,
mount,
Expand Down Expand Up @@ -470,6 +471,20 @@ export function seriesTooltip(
return transposed ? minY(b) - minY(a) : minY(a) - minY(b);
});

const extent = (d) => {
const index = transposed ? 1 : 0;
const { min, max } = d.getLocalBounds();
return sort([min[index], max[index]]);
};
// Sort itemElements by x or y.
itemElements.sort((a, b) => {
const [minA, maxA] = extent(a);
const [minB, maxB] = extent(b);
const midA = (minA + maxA) / 2;
const midB = (minB + maxB) / 2;
return transposed ? midB - midA : midA - midB;
});

// Get sortedIndex and X for each series elements
const elementSortedX = new Map(
seriesElements.map((element) => {
Expand All @@ -494,8 +509,9 @@ export function seriesTooltip(
const indexByFocus = (focus, I, X) => {
const finalX = abstractX(focus);
const [minX, maxX] = sort([X[0], X[X.length - 1]]);
// Skip x out of range.
if (finalX < minX || finalX > maxX) return null;
// If closest is true, always find at least one element.
// Otherwise, skip element out of plot area.
if (!closest && (finalX < minX || finalX > maxX)) return null;
const search = bisector((i) => X[+i]).center;
const i = search(I, finalX);
return I[i];
Expand All @@ -504,14 +520,20 @@ export function seriesTooltip(
const elementsByFocus = (focus, elements) => {
const index = transposed ? 1 : 0;
const x = focus[index];
const extent = (d) => {
const { min, max } = d.getLocalBounds();
return sort([min[index], max[index]]);
};
return elements.filter((element) => {
const filtered = elements.filter((element) => {
const [min, max] = extent(element);
return x >= min && x <= max;
});
// If closet is true, always find at least one element.
if (!closest || filtered.length > 0) return filtered;

// Search the closet element to the focus.
const search = bisector((element) => {
const [min, max] = extent(element);
return (min + max) / 2;
}).center;
const i = search(elements, x);
return [elements[i]].filter(defined);
};

const seriesData = (element, index) => {
Expand Down

0 comments on commit d879968

Please sign in to comment.