UNPKG

@antv/g2

Version:

the Grammar of Graphics in Javascript

893 lines 37 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Tooltip = exports.tooltip = exports.seriesTooltip = void 0; const g_1 = require("@antv/g"); const d3_array_1 = require("d3-array"); const util_1 = require("@antv/util"); const component_1 = require("@antv/component"); const scale_1 = require("@antv/scale"); const helper_1 = require("../utils/helper"); const coordinate_1 = require("../utils/coordinate"); const vector_1 = require("../utils/vector"); const scale_2 = require("../utils/scale"); const utils_1 = require("./utils"); const event_1 = require("./event"); function getContainer(group, mount) { if (mount) { return typeof mount === 'string' ? document.querySelector(mount) : mount; } const canvas = group.ownerDocument.defaultView .getContextService() .getDomElement(); return canvas.parentElement; } function getBounding(root) { const bbox = root.getRenderBounds(); const { min: [x1, y1], max: [x2, y2], } = bbox; return { x: x1, y: y1, width: x2 - x1, height: y2 - y1, }; } function getContainerOffset(container1, container2) { const r1 = container1.getBoundingClientRect(); const r2 = container2.getBoundingClientRect(); return { x: r1.x - r2.x, y: r1.y - r2.y, }; } function createTooltip(container, x0, y0, position, enterable, bounding, containerOffset, css = {}, offset = [10, 10]) { const defaults = { '.g2-tooltip': {}, '.g2-tooltip-title': { overflow: 'hidden', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', }, }; const tooltipElement = new component_1.Tooltip({ className: 'tooltip', style: { x: x0, y: y0, container: containerOffset, data: [], bounding, position, enterable, title: '', offset, template: { prefixCls: 'g2-', }, style: (0, util_1.deepMix)(defaults, css), }, }); container.appendChild(tooltipElement.HTMLTooltipElement); return tooltipElement; } function showTooltip({ root, data, x, y, render, event, single, position = 'right-bottom', enterable = false, css, mount, bounding, offset, }) { const container = getContainer(root, mount); const canvasContainer = getContainer(root); // All the views share the same tooltip. const parent = single ? canvasContainer : root; const b = bounding || getBounding(root); const containerOffset = getContainerOffset(canvasContainer, container); const { tooltipElement = createTooltip(container, x, y, position, enterable, b, containerOffset, css, offset), } = parent; const { items, title = '' } = data; tooltipElement.update(Object.assign({ x, y, data: items, title, position, enterable }, (render !== undefined && { content: render(event, { items, title }), }))); parent.tooltipElement = tooltipElement; } function hideTooltip({ root, single, emitter, nativeEvent = true, event = null, }) { if (nativeEvent) { emitter.emit('tooltip:hide', { nativeEvent }); } const container = getContainer(root); const parent = single ? container : root; const { tooltipElement } = parent; if (tooltipElement) { // Must be clientX, clientY. tooltipElement.hide(event === null || event === void 0 ? void 0 : event.clientX, event === null || event === void 0 ? void 0 : event.clientY); } hideRuleY(root); hideRuleX(root); hideMarker(root); } function destroyTooltip({ root, single }) { const container = getContainer(root); const parent = single ? container : root; if (!parent) return; const { tooltipElement } = parent; if (tooltipElement) { tooltipElement.destroy(); parent.tooltipElement = undefined; } hideRuleY(root); hideRuleX(root); hideMarker(root); } function showUndefined(item) { const { value } = item; return Object.assign(Object.assign({}, item), { value: value === undefined ? 'undefined' : value }); } function singleItem(element) { const { __data__: datum } = element; const { title, items = [] } = datum; const newItems = items .filter(helper_1.defined) .map((_a) => { var { color = itemColorOf(element) } = _a, item = __rest(_a, ["color"]); return (Object.assign(Object.assign({}, item), { color })); }) .map(showUndefined); return Object.assign(Object.assign({}, (title && { title })), { items: newItems }); } function groupNameOf(scale, datum) { const { color: scaleColor, series: scaleSeries, facet = false } = scale; const { color, series } = datum; const invertAble = (scale) => { return (scale && scale.invert && !(scale instanceof scale_1.Band) && !(scale instanceof scale_1.Constant)); }; // For non constant color channel. if (invertAble(scaleSeries)) { const cloned = scaleSeries.clone(); return cloned.invert(series); } if (series && scaleSeries instanceof scale_1.Band && scaleSeries.invert(series) !== color && !facet) { return scaleSeries.invert(series); } if (invertAble(scaleColor)) { const name = scaleColor.invert(color); // For threshold scale. if (Array.isArray(name)) return null; return name; } return null; } function itemColorOf(element) { const fill = element.getAttribute('fill'); const stroke = element.getAttribute('stroke'); const { __data__: datum } = element; const { color = fill && fill !== 'transparent' ? fill : stroke } = datum; return color; } function unique(items, key = (d) => d) { const valueName = new Map(items.map((d) => [key(d), d])); return Array.from(valueName.values()); } function groupItems(elements, scale, groupName, data = elements.map((d) => d['__data__']), theme = {}) { const key = (d) => (d instanceof Date ? +d : d); const T = unique(data.map((d) => d.title), key).filter(helper_1.defined); const newItems = data .flatMap((datum, i) => { const element = elements[i]; const { items = [], title } = datum; const definedItems = items.filter(helper_1.defined); // If there is only one item, use groupName as title by default. const useGroupName = groupName !== undefined ? groupName : items.length <= 1 ? true : false; return definedItems.map((_a) => { var { color = itemColorOf(element) || theme.color, name } = _a, item = __rest(_a, ["color", "name"]); const groupName = groupNameOf(scale, datum); const name1 = useGroupName ? groupName || name : name || groupName; return Object.assign(Object.assign({}, item), { color, name: name1 || title }); }); }) .map(showUndefined); return Object.assign(Object.assign({}, (T.length > 0 && { title: T.join(',') })), { items: unique(newItems, (d) => `(${key(d.name)}, ${key(d.value)}, ${key(d.color)})`) }); } function updateRuleX(root, points, mouse, _a) { var { plotWidth, plotHeight, mainWidth, mainHeight, startX, startY, transposed, polar, insetLeft, insetTop } = _a, rest = __rest(_a, ["plotWidth", "plotHeight", "mainWidth", "mainHeight", "startX", "startY", "transposed", "polar", "insetLeft", "insetTop"]); const defaults = Object.assign({ lineWidth: 1, stroke: '#1b1e23', strokeOpacity: 0.5 }, rest); const createCircle = (cx, cy, r) => { const circle = new g_1.Circle({ style: Object.assign({ cx, cy, r }, defaults), }); root.appendChild(circle); return circle; }; const createLine = (x1, x2, y1, y2) => { const line = new g_1.Line({ style: Object.assign({ x1, x2, y1, y2 }, defaults), }); root.appendChild(line); return line; }; const minDistPoint = (mouse, points) => { // only one point do not need compute if (points.length === 1) { return points[0]; } const dists = points.map((p) => (0, vector_1.dist)(p, mouse)); const minDistIndex = (0, d3_array_1.minIndex)(dists, (d) => d); return points[minDistIndex]; }; const target = minDistPoint(mouse, points); const pointsOf = () => { if (transposed) return [ startX + target[0], startX + target[0], startY, startY + plotHeight, ]; return [startX, startX + plotWidth, target[1] + startY, target[1] + startY]; }; const pointsOfPolar = () => { const cx = startX + insetLeft + mainWidth / 2; const cy = startY + insetTop + mainHeight / 2; const cdist = (0, vector_1.dist)([cx, cy], target); return [cx, cy, cdist]; }; if (polar) { const [cx, cy, r] = pointsOfPolar(); const ruleX = root.ruleX || createCircle(cx, cy, r); ruleX.style.cx = cx; ruleX.style.cy = cy; ruleX.style.r = r; root.ruleX = ruleX; } else { const [x1, x2, y1, y2] = pointsOf(); const ruleX = root.ruleX || createLine(x1, x2, y1, y2); ruleX.style.x1 = x1; ruleX.style.x2 = x2; ruleX.style.y1 = y1; ruleX.style.y2 = y2; root.ruleX = ruleX; } } function updateRuleY(root, points, _a) { var { plotWidth, plotHeight, mainWidth, mainHeight, startX, startY, transposed, polar, insetLeft, insetTop } = _a, rest = __rest(_a, ["plotWidth", "plotHeight", "mainWidth", "mainHeight", "startX", "startY", "transposed", "polar", "insetLeft", "insetTop"]); const defaults = Object.assign({ lineWidth: 1, stroke: '#1b1e23', strokeOpacity: 0.5 }, rest); const Y = points.map((p) => p[1]); const X = points.map((p) => p[0]); const y = (0, d3_array_1.mean)(Y); const x = (0, d3_array_1.mean)(X); const pointsOf = () => { if (polar) { const r = Math.min(mainWidth, mainHeight) / 2; const cx = startX + insetLeft + mainWidth / 2; const cy = startY + insetTop + mainHeight / 2; const a = (0, vector_1.angle)((0, vector_1.sub)([x, y], [cx, cy])); const x0 = cx + r * Math.cos(a); const y0 = cy + r * Math.sin(a); return [cx, x0, cy, y0]; } if (transposed) return [startX, startX + plotWidth, y + startY, y + startY]; return [x + startX, x + startX, startY, startY + plotHeight]; }; const [x1, x2, y1, y2] = pointsOf(); const createLine = () => { const line = new g_1.Line({ style: Object.assign({ x1, x2, y1, y2 }, defaults), }); root.appendChild(line); return line; }; // Only update rule with defined series elements. if (X.length > 0) { const ruleY = root.ruleY || createLine(); ruleY.style.x1 = x1; ruleY.style.x2 = x2; ruleY.style.y1 = y1; ruleY.style.y2 = y2; root.ruleY = ruleY; } } function hideRuleY(root) { if (root.ruleY) { root.ruleY.remove(); root.ruleY = undefined; } } function hideRuleX(root) { if (root.ruleX) { root.ruleX.remove(); root.ruleX = undefined; } } function updateMarker(root, { data, style, theme }) { if (root.markers) root.markers.forEach((d) => d.remove()); const { type = '' } = style; const markers = data .filter((d) => { const [{ x, y }] = d; return (0, helper_1.defined)(x) && (0, helper_1.defined)(y); }) .map((d) => { const [{ color, element }, point] = d; const originColor = color || // encode value element.style.fill || element.style.stroke || theme.color; const fill = type === 'hollow' ? 'transparent' : originColor; const stroke = type === 'hollow' ? originColor : '#fff'; const shape = new g_1.Circle({ style: Object.assign({ cx: point[0], cy: point[1], fill, r: 4, stroke, lineWidth: 2 }, style), }); return shape; }); for (const marker of markers) root.appendChild(marker); root.markers = markers; } function hideMarker(root) { if (root.markers) { root.markers.forEach((d) => d.remove()); root.markers = []; } } function interactionKeyof(markState, key) { return Array.from(markState.values()).some( // @ts-ignore (d) => { var _a; return (_a = d.interaction) === null || _a === void 0 ? void 0 : _a[key]; }); } function maybeValue(specified, defaults) { return specified === undefined ? defaults : specified; } function isEmptyTooltipData(data) { const { title, items } = data; if (items.length === 0 && title === undefined) return true; return false; } function hasSeries(markState) { return Array.from(markState.values()).some( // @ts-ignore (d) => { var _a; return ((_a = d.interaction) === null || _a === void 0 ? void 0 : _a.seriesTooltip) && d.tooltip; }); } /** * Show tooltip for series item. */ function seriesTooltip(root, _a) { var { elements: elementsof, sort: sortFunction, filter: filterFunction, scale, coordinate, crosshairs, crosshairsX, crosshairsY, render, groupName, emitter, wait = 50, leading = true, trailing = false, startX = 0, startY = 0, body = true, single = true, position, enterable, mount, bounding, theme, offset, disableNative = false, marker = true, preserve = false, style: _style = {}, css = {} } = _a, rest = __rest(_a, ["elements", "sort", "filter", "scale", "coordinate", "crosshairs", "crosshairsX", "crosshairsY", "render", "groupName", "emitter", "wait", "leading", "trailing", "startX", "startY", "body", "single", "position", "enterable", "mount", "bounding", "theme", "offset", "disableNative", "marker", "preserve", "style", "css"]); const elements = elementsof(root); const transposed = (0, coordinate_1.isTranspose)(coordinate); const polar = (0, coordinate_1.isPolar)(coordinate); const style = (0, util_1.deepMix)(_style, rest); const { innerWidth: plotWidth, innerHeight: plotHeight, width: mainWidth, height: mainHeight, insetLeft, insetTop, } = coordinate.getOptions(); // Split elements into series elements and item elements. const seriesElements = []; const itemElements = []; for (const element of elements) { const { __data__: data } = element; const { seriesX, title, items } = data; if (seriesX) seriesElements.push(element); else if (title || items) itemElements.push(element); } const inInterval = (d) => d.markType === 'interval'; const isBar = itemElements.length && itemElements.every(inInterval) && !(0, coordinate_1.isPolar)(coordinate); const xof = (d) => d.__data__.x; // For band scale x, find the closest series element to focus, // useful for interval + line mark. const isBandScale = !!scale.x.getBandWidth; const closest = isBandScale && itemElements.length > 0; // Sorted elements from top to bottom visually, // or from right to left in transpose coordinate. seriesElements.sort((a, b) => { const index = transposed ? 0 : 1; const minY = (d) => d.getBounds().min[index]; return transposed ? minY(b) - minY(a) : minY(a) - minY(b); }); const extent = (d) => { const index = transposed ? 1 : 0; const { min, max } = d.getLocalBounds(); return (0, d3_array_1.sort)([min[index], max[index]]); }; // Sort itemElements for bisector search. if (isBar) elements.sort((a, b) => xof(a) - xof(b)); else { 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) => { const { __data__: data } = element; const { seriesX } = data; const seriesIndex = seriesX.map((_, i) => i); const sortedIndex = (0, d3_array_1.sort)(seriesIndex, (i) => seriesX[+i]); return [element, [sortedIndex, seriesX]]; })); const { x: scaleX } = scale; // Apply offset for band scale x. const offsetX = (scaleX === null || scaleX === void 0 ? void 0 : scaleX.getBandWidth) ? scaleX.getBandWidth() / 2 : 0; const abstractX = (focus) => { const [normalizedX] = coordinate.invert(focus); return normalizedX - offsetX; }; const indexByFocus = (event, focus, I, X) => { // _x is from emit event, to find the right element. const { _x } = event; const finalX = _x !== undefined ? scaleX.map(_x) : abstractX(focus); const DX = X.filter(helper_1.defined); const [minX, maxX] = (0, d3_array_1.sort)([DX[0], DX[DX.length - 1]]); // If only has one element(minX == maxX), show tooltip when hover whole chart const isOnlyOneElement = minX === maxX; // If closest is true, always find at least one element. // Otherwise, skip element out of plot area. if (!closest && (finalX < minX || finalX > maxX) && !isOnlyOneElement) return null; const search = (0, d3_array_1.bisector)((i) => X[+i]).center; const i = search(I, finalX); return I[i]; }; const elementsByFocus = isBar ? (focus, elements) => { const search = (0, d3_array_1.bisector)(xof).center; const i = search(elements, abstractX(focus)); const find = elements[i]; const groups = (0, d3_array_1.group)(elements, xof); const selected = groups.get(xof(find)); return selected; } : (focus, elements) => { const index = transposed ? 1 : 0; const x = focus[index]; 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 = (0, d3_array_1.bisector)((element) => { const [min, max] = extent(element); return (min + max) / 2; }).center; const i = search(elements, x); return [elements[i]].filter(helper_1.defined); }; const seriesData = (element, index) => { const { __data__: data } = element; return Object.fromEntries(Object.entries(data) .filter(([key]) => key.startsWith('series') && key !== 'series') .map(([key, V]) => { const d = V[index]; return [(0, util_1.lowerFirst)(key.replace('series', '')), d]; })); }; const update = (0, util_1.throttle)((event) => { var _a; const mouse = (0, utils_1.mousePosition)(root, event); if (!mouse) return; const bbox = (0, utils_1.bboxOf)(root); const x = bbox.min[0]; const y = bbox.min[1]; const focus = [mouse[0] - startX, mouse[1] - startY]; if (!focus) return; // Get selected item element. const selectedItems = elementsByFocus(focus, itemElements); // Get selected data item from both series element and item element. const selectedSeriesElements = []; const selectedSeriesData = []; for (const element of seriesElements) { const [sortedIndex, X] = elementSortedX.get(element); const index = indexByFocus(event, focus, sortedIndex, X); if (index !== null) { selectedSeriesElements.push(element); const d = seriesData(element, index); const { x, y } = d; const p = coordinate.map([(x || 0) + offsetX, y || 0]); selectedSeriesData.push([Object.assign(Object.assign({}, d), { element }), p]); } } // Filter selectedSeriesData with different x, // make sure there is only one x closest to focusX. const SX = Array.from(new Set(selectedSeriesData.map((d) => d[0].x))); const closestX = SX[(0, d3_array_1.minIndex)(SX, (x) => Math.abs(x - abstractX(focus)))]; const filteredSeriesData = selectedSeriesData.filter((d) => d[0].x === closestX); const selectedData = [ ...filteredSeriesData.map((d) => d[0]), ...selectedItems.map((d) => d.__data__), ]; // Get the displayed tooltip data. const selectedElements = [...selectedSeriesElements, ...selectedItems]; const tooltipData = groupItems(selectedElements, scale, groupName, selectedData, theme); // Sort items and filter items. if (sortFunction) { tooltipData.items.sort((a, b) => sortFunction(a) - sortFunction(b)); } if (filterFunction) { tooltipData.items = tooltipData.items.filter(filterFunction); } // Hide tooltip with no selected tooltip. if (selectedElements.length === 0 || isEmptyTooltipData(tooltipData)) { hide(event); return; } if (body) { showTooltip({ root, data: tooltipData, x: mouse[0] + x, y: mouse[1] + y, render, event, single, position, enterable, mount, bounding, css, offset, }); } if (crosshairs || crosshairsX || crosshairsY) { const ruleStyle = (0, helper_1.subObject)(style, 'crosshairs'); const ruleStyleX = Object.assign(Object.assign({}, ruleStyle), (0, helper_1.subObject)(style, 'crosshairsX')); const ruleStyleY = Object.assign(Object.assign({}, ruleStyle), (0, helper_1.subObject)(style, 'crosshairsY')); const points = filteredSeriesData.map((d) => d[1]); if (crosshairsX) { updateRuleX(root, points, mouse, Object.assign(Object.assign({}, ruleStyleX), { plotWidth, plotHeight, mainWidth, mainHeight, insetLeft, insetTop, startX, startY, transposed, polar })); } if (crosshairsY) { updateRuleY(root, points, Object.assign(Object.assign({}, ruleStyleY), { plotWidth, plotHeight, mainWidth, mainHeight, insetLeft, insetTop, startX, startY, transposed, polar })); } } if (marker) { const markerStyles = (0, helper_1.subObject)(style, 'marker'); updateMarker(root, { data: filteredSeriesData, style: markerStyles, theme, }); } // X in focus may related multiple points when dataset is large, // so we need to find the first x to show tooltip. const firstX = (_a = filteredSeriesData[0]) === null || _a === void 0 ? void 0 : _a[0].x; const transformedX = firstX !== null && firstX !== void 0 ? firstX : abstractX(focus); emitter.emit('tooltip:show', Object.assign(Object.assign({}, event), { nativeEvent: true, data: { data: { x: (0, scale_2.invert)(scale.x, transformedX, true) }, } })); }, wait, { leading, trailing }); const hide = (event) => { hideTooltip({ root, single, emitter, event }); }; const destroy = () => { destroyTooltip({ root, single }); }; const onTooltipShow = (_a) => { var _b; var { nativeEvent, data, offsetX, offsetY } = _a, rest = __rest(_a, ["nativeEvent", "data", "offsetX", "offsetY"]); if (nativeEvent) return; const x = (_b = data === null || data === void 0 ? void 0 : data.data) === null || _b === void 0 ? void 0 : _b.x; const scaleX = scale.x; const x1 = scaleX.map(x); const [x2, y2] = coordinate.map([x1, 0.5]); const rootBounds = root.getRenderBounds(); const minX = rootBounds.min[0]; const minY = rootBounds.min[1]; update(Object.assign(Object.assign({}, rest), { offsetX: offsetX !== undefined ? offsetX : minX + x2, offsetY: offsetY !== undefined ? offsetY : minY + y2, _x: x })); }; const onTooltipHide = () => { hideTooltip({ root, single, emitter, nativeEvent: false }); }; const onTooltipDisable = () => { removeEventListeners(); destroy(); }; const onTooltipEnable = () => { addEventListeners(); }; const addEventListeners = () => { if (!disableNative) { root.addEventListener('pointerenter', update); root.addEventListener('pointermove', update); // Only emit pointerleave event when the pointer is not in the root area. root.addEventListener('pointerleave', (e) => { if ((0, utils_1.mousePosition)(root, e)) return; hide(e); }); } }; const removeEventListeners = () => { if (!disableNative) { root.removeEventListener('pointerenter', update); root.removeEventListener('pointermove', update); root.removeEventListener('pointerleave', hide); } }; addEventListeners(); emitter.on('tooltip:show', onTooltipShow); emitter.on('tooltip:hide', onTooltipHide); emitter.on('tooltip:disable', onTooltipDisable); emitter.on('tooltip:enable', onTooltipEnable); return () => { removeEventListeners(); emitter.off('tooltip:show', onTooltipShow); emitter.off('tooltip:hide', onTooltipHide); emitter.off('tooltip:disable', onTooltipDisable); emitter.off('tooltip:enable', onTooltipEnable); if (preserve) { hideTooltip({ root, single, emitter, nativeEvent: false }); } else { destroy(); } }; } exports.seriesTooltip = seriesTooltip; /** * Show tooltip for non-series item. */ function tooltip(root, { elements: elementsof, coordinate, scale, render, groupName, sort: sortFunction, filter: filterFunction, emitter, wait = 50, leading = true, trailing = false, groupKey = (d) => d, // group elements by specified key single = true, position, enterable, datum, view, mount, bounding, theme, offset, shared = false, body = true, disableNative = false, preserve = false, css = {}, }) { var _a, _b; const elements = elementsof(root); const keyGroup = (0, d3_array_1.group)(elements, groupKey); const inInterval = (d) => d.markType === 'interval'; const isBar = elements.every(inInterval) && !(0, coordinate_1.isPolar)(coordinate); const scaleX = scale.x; const scaleSeries = scale.series; const bandWidth = (_b = (_a = scaleX === null || scaleX === void 0 ? void 0 : scaleX.getBandWidth) === null || _a === void 0 ? void 0 : _a.call(scaleX)) !== null && _b !== void 0 ? _b : 0; const xof = scaleSeries ? (d) => d.__data__.x + d.__data__.series * bandWidth : (d) => d.__data__.x + bandWidth / 2; // Sort for bisector search. if (isBar) elements.sort((a, b) => xof(a) - xof(b)); const findElementByTarget = (event) => { const { target } = event; return (0, utils_1.maybeRoot)(target, (node) => { if (!node.classList) return false; return node.classList.includes('element'); }); }; const findElement = isBar ? (event) => { const mouse = (0, utils_1.mousePosition)(root, event); if (!mouse) return; const [normalizedX] = coordinate.invert(mouse); const abstractX = normalizedX; const search = (0, d3_array_1.bisector)(xof).center; const i = search(elements, abstractX); const target = elements[i]; if (!shared) { // For grouped bar chart without shared options. const isGrouped = elements.find((d) => d !== target && xof(d) === xof(target)); if (isGrouped) return findElementByTarget(event); } return target; } : findElementByTarget; const pointermove = (0, util_1.throttle)((event) => { const element = findElement(event); if (!element) { hideTooltip({ root, single, emitter, event }); return; } const k = groupKey(element); const group = keyGroup.get(k); if (!group) { return; } const data = group.length === 1 && !shared ? singleItem(group[0]) : groupItems(group, scale, groupName, undefined, theme); // Sort items and sort. if (sortFunction) { data.items.sort((a, b) => sortFunction(a) - sortFunction(b)); } if (filterFunction) { data.items = data.items.filter(filterFunction); } if (isEmptyTooltipData(data)) { hideTooltip({ root, single, emitter, event }); return; } const { offsetX, offsetY } = event; if (body) { showTooltip({ root, data, x: offsetX, y: offsetY, render, event, single, position, enterable, mount, bounding, css, offset, }); } emitter.emit('tooltip:show', Object.assign(Object.assign({}, event), { nativeEvent: true, data: { data: (0, event_1.dataOf)(element, view), } })); }, wait, { leading, trailing }); const pointerleave = (event) => { hideTooltip({ root, single, emitter, event }); }; const addEventListeners = () => { if (!disableNative) { root.addEventListener('pointermove', pointermove); // Only emit pointerleave event when the pointer is not in the root area. // !!!DO NOT USE pointerout event, it will emit when the pointer is in the child area. root.addEventListener('pointerleave', pointerleave); } }; const removeEventListeners = () => { if (!disableNative) { root.removeEventListener('pointermove', pointermove); root.removeEventListener('pointerleave', pointerleave); } }; const onTooltipShow = ({ nativeEvent, offsetX, offsetY, data: raw }) => { if (nativeEvent) return; const { data } = raw; const element = (0, utils_1.selectElementByData)(elements, data, datum); if (!element) return; const bbox = element.getBBox(); const { x, y, width, height } = bbox; const rootBBox = root.getBBox(); pointermove({ target: element, offsetX: offsetX !== undefined ? offsetX + rootBBox.x : x + width / 2, offsetY: offsetY !== undefined ? offsetY + rootBBox.y : y + height / 2, }); }; const onTooltipHide = ({ nativeEvent } = {}) => { if (nativeEvent) return; hideTooltip({ root, single, emitter, nativeEvent: false }); }; const onTooltipDisable = () => { removeEventListeners(); destroyTooltip({ root, single }); }; const onTooltipEnable = () => { addEventListeners(); }; emitter.on('tooltip:show', onTooltipShow); emitter.on('tooltip:hide', onTooltipHide); emitter.on('tooltip:enable', onTooltipEnable); emitter.on('tooltip:disable', onTooltipDisable); addEventListeners(); return () => { removeEventListeners(); emitter.off('tooltip:show', onTooltipShow); emitter.off('tooltip:hide', onTooltipHide); if (preserve) { hideTooltip({ root, single, emitter, nativeEvent: false }); } else { destroyTooltip({ root, single }); } }; } exports.tooltip = tooltip; function Tooltip(options) { const { shared, crosshairs, crosshairsX, crosshairsY, series, name, item = () => ({}), facet = false } = options, rest = __rest(options, ["shared", "crosshairs", "crosshairsX", "crosshairsY", "series", "name", "item", "facet"]); return (target, viewInstances, emitter) => { const { container, view } = target; const { scale, markState, coordinate, theme } = view; // Get default value from mark states. const defaultSeries = interactionKeyof(markState, 'seriesTooltip'); const defaultShowCrosshairs = interactionKeyof(markState, 'crosshairs'); const plotArea = (0, utils_1.selectPlotArea)(container); const isSeries = maybeValue(series, defaultSeries); const crosshairsSetting = maybeValue(crosshairs, defaultShowCrosshairs); // For non-facet and series tooltip. if (isSeries && hasSeries(markState) && !facet) { return seriesTooltip(plotArea, Object.assign(Object.assign({}, rest), { theme, elements: utils_1.selectG2Elements, scale, coordinate, crosshairs: crosshairsSetting, // the crosshairsX settings level: crosshairsX > crosshairs > false // it means crosshairsX default is false crosshairsX: maybeValue(maybeValue(crosshairsX, crosshairs), false), // crosshairsY default depend on the crossharisSettings crosshairsY: maybeValue(crosshairsY, crosshairsSetting), item, emitter })); } // For facet and series tooltip. if (isSeries && facet) { // Get sub view instances for this view. const facetInstances = viewInstances.filter((d) => d !== target && d.options.parentKey === target.options.key); const elements = (0, utils_1.selectFacetG2Elements)(target, viewInstances); // Use the scale of the first view. const scale = facetInstances[0].view.scale; const bbox = plotArea.getBounds(); const startX = bbox.min[0]; const startY = bbox.min[1]; Object.assign(scale, { facet: true }); // @todo Nested structure rather than flat structure for facet? // Add listener to the root area. // @ts-ignore return seriesTooltip(plotArea.parentNode.parentNode, Object.assign(Object.assign({}, rest), { theme, elements: () => elements, scale, coordinate, crosshairs: maybeValue(crosshairs, defaultShowCrosshairs), // the crosshairsX settings level: crosshairsX > crosshairs > false // it means crosshairsX default is false crosshairsX: maybeValue(maybeValue(crosshairsX, crosshairs), false), crosshairsY: maybeValue(crosshairsY, crosshairsSetting), item, startX, startY, emitter })); } return tooltip(plotArea, Object.assign(Object.assign({}, rest), { datum: (0, utils_1.createDatumof)(view), elements: utils_1.selectG2Elements, scale, coordinate, groupKey: shared ? (0, utils_1.createXKey)(view) : undefined, item, emitter, view, theme, shared })); }; } exports.Tooltip = Tooltip; Tooltip.props = { reapplyWhenUpdate: true, }; //# sourceMappingURL=tooltip.js.map