| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- "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 gui_1 = require("@antv/gui");
- 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;
- // @ts-ignore
- return group.getRootNode().defaultView.getConfig().container;
- }
- function getBounding(root) {
- const bbox = root.getBounds();
- 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) {
- const tooltipElement = new gui_1.Tooltip({
- className: 'tooltip',
- style: {
- x: x0,
- y: y0,
- container: containerOffset,
- data: [],
- bounding,
- position,
- enterable,
- title: '',
- offset: [10, 10],
- template: {
- prefixCls: 'g2-',
- },
- style: {
- '.g2-tooltip': {},
- '.g2-tooltip-title': {
- overflow: 'hidden',
- 'white-space': 'nowrap',
- 'text-overflow': 'ellipsis',
- },
- },
- },
- });
- container.appendChild(tooltipElement.HTMLTooltipElement);
- return tooltipElement;
- }
- function showTooltip({ root, data, x, y, render, event, single, position = 'right-bottom', enterable = false, mount, bounding, }) {
- // All the views share the same tooltip.
- 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(Object.assign({ x,
- y, data: items, title,
- position,
- enterable }, (render !== undefined && {
- content: render(event, { items, title }),
- })));
- container.tooltipElement = tooltipElement;
- }
- function hideTooltip({ root, single, emitter, nativeEvent = true, mount }) {
- const container = single ? getContainer(root, mount) : root;
- const { tooltipElement } = container;
- if (tooltipElement) {
- tooltipElement.hide();
- if (nativeEvent) {
- emitter.emit('tooltip:hide', { nativeEvent });
- }
- }
- }
- function destroyTooltip(root) {
- const { tooltipElement } = root;
- if (tooltipElement) {
- tooltipElement.destroy();
- root.tooltipElement = undefined;
- }
- }
- 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 } = scale;
- const { color, series } = datum;
- const invertAble = (scale) => {
- return (scale &&
- scale.invert &&
- !(scale instanceof scale_1.Identity) &&
- !(scale instanceof scale_1.Constant));
- };
- // For non constant color channel.
- if (invertAble(scaleSeries))
- return scaleSeries.invert(series);
- if (series && series !== color)
- return 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__'])) {
- 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), name } = _a, item = __rest(_a, ["color", "name"]);
- const name1 = useGroupName
- ? groupNameOf(scale, datum) || name
- : name || groupNameOf(scale, datum);
- 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 updateRuleY(root, points, _a) {
- var { height, width, startX, startY, transposed, polar } = _a, rest = __rest(_a, ["height", "width", "startX", "startY", "transposed", "polar"]);
- 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 cx = startX + width / 2;
- const cy = startY + height / 2;
- const r = Math.min(width, height) / 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 + width, y + startY, y + startY];
- }
- return [x + startX, x + startX, startY, startY + height];
- };
- 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;
- };
- 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 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, render, groupName, emitter, wait = 50, leading = true, trailing = false, startX = 0, startY = 0, body = true, single = true, position, enterable, mount, bounding, style: _style = {} } = _a, rest = __rest(_a, ["elements", "sort", "filter", "scale", "coordinate", "crosshairs", "render", "groupName", "emitter", "wait", "leading", "trailing", "startX", "startY", "body", "single", "position", "enterable", "mount", "bounding", "style"]);
- 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: width, innerHeight: height } = 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 } = data;
- if (seriesX)
- seriesElements.push(element);
- else
- itemElements.push(element);
- }
- // 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);
- });
- // 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 ruleStyle = (0, helper_1.subObject)(style, 'crosshairs');
- 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 = (focus, I, X) => {
- const finalX = abstractX(focus);
- const [minX, maxX] = (0, d3_array_1.sort)([X[0], X[X.length - 1]]);
- // Skip x out of range.
- if (finalX < minX || finalX > maxX)
- return null;
- const search = (0, d3_array_1.bisector)((i) => X[+i]).center;
- const i = search(I, finalX);
- return I[i];
- };
- const elementsByFocus = (focus, elements) => {
- const index = transposed ? 1 : 0;
- const x = focus[index];
- const extent = (d) => {
- const { min, max } = d.getLocalBounds();
- return (0, d3_array_1.sort)([min[index], max[index]]);
- };
- return elements.filter((element) => {
- const [min, max] = extent(element);
- return x >= min && x <= max;
- });
- };
- 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) => {
- const mouse = (0, utils_1.mousePosition)(root, event);
- if (!mouse)
- return;
- const bbox = root.getRenderBounds();
- 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(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([d, 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);
- // 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();
- return;
- }
- if (body) {
- showTooltip({
- root,
- data: tooltipData,
- x: mouse[0] + x,
- y: mouse[1] + y,
- render,
- event,
- single,
- position,
- enterable,
- mount,
- bounding,
- });
- }
- if (crosshairs) {
- const points = filteredSeriesData.map((d) => d[1]);
- updateRuleY(root, points, Object.assign(Object.assign({}, ruleStyle), { width,
- height,
- startX,
- startY,
- transposed,
- polar }));
- }
- emitter.emit('tooltip:show', Object.assign(Object.assign({}, event), { nativeEvent: true, data: { data: { x: (0, scale_2.invert)(scale.x, abstractX(focus), true) } } }));
- }, wait, { leading, trailing });
- const hide = () => {
- hideTooltip({ root, single, emitter, mount });
- if (crosshairs)
- hideRuleY(root);
- };
- const onTooltipShow = ({ nativeEvent, data }) => {
- if (nativeEvent)
- return;
- const { x } = data.data;
- const { x: scaleX } = scale;
- const x1 = scaleX.map(x);
- const [x2, y2] = coordinate.map([x1, 0.5]);
- const { min: [minX, minY], } = root.getRenderBounds();
- update({ offsetX: x2 + minX, offsetY: y2 + minY });
- };
- const onTooltipHide = () => {
- hideTooltip({ root, single, emitter, nativeEvent: false, mount });
- };
- emitter.on('tooltip:show', onTooltipShow);
- emitter.on('tooltip:hide', onTooltipHide);
- root.addEventListener('pointerenter', update);
- root.addEventListener('pointermove', update);
- root.addEventListener('pointerleave', hide);
- return () => {
- root.removeEventListener('pointerenter', update);
- root.removeEventListener('pointermove', update);
- root.removeEventListener('pointerleave', hide);
- emitter.off('tooltip:show', onTooltipShow);
- emitter.off('tooltip:hide', onTooltipHide);
- destroyTooltip(root);
- if (crosshairs)
- hideRuleY(root);
- };
- }
- exports.seriesTooltip = seriesTooltip;
- /**
- * Show tooltip for non-series item.
- */
- function tooltip(root, { elements: elementsof, 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, }) {
- const elements = elementsof(root);
- const elementSet = new Set(elements);
- const keyGroup = (0, d3_array_1.group)(elements, groupKey);
- const pointerover = (0, util_1.throttle)((event) => {
- const { target: element } = event;
- if (!elementSet.has(element)) {
- hideTooltip({ root, single, emitter, mount });
- return;
- }
- const k = groupKey(element);
- const group = keyGroup.get(k);
- const data = group.length === 1
- ? singleItem(group[0])
- : groupItems(group, scale, groupName);
- // 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, mount });
- return;
- }
- const { offsetX, offsetY } = event;
- showTooltip({
- root,
- data,
- x: offsetX,
- y: offsetY,
- render,
- event,
- single,
- position,
- enterable,
- mount,
- bounding,
- });
- emitter.emit('tooltip:show', Object.assign(Object.assign({}, event), { nativeEvent: true, data: {
- data: (0, event_1.dataOf)(element, view),
- } }));
- }, wait, { leading, trailing });
- const pointerout = (event) => {
- const { target: element } = event;
- if (!elementSet.has(element))
- return;
- hideTooltip({ root, single, emitter, mount });
- };
- const onTooltipShow = ({ nativeEvent, data }) => {
- if (nativeEvent)
- return;
- const element = (0, utils_1.selectElementByData)(elements, data.data, datum);
- if (!element)
- return;
- const bbox = element.getBBox();
- const { x, y, width, height } = bbox;
- pointerover({
- target: element,
- offsetX: x + width / 2,
- offsetY: y + height / 2,
- });
- };
- const onTooltipHide = ({ nativeEvent } = {}) => {
- if (nativeEvent)
- return;
- hideTooltip({ root, single, emitter, nativeEvent: false, mount });
- };
- emitter.on('tooltip:show', onTooltipShow);
- emitter.on('tooltip:hide', onTooltipHide);
- root.addEventListener('pointerover', pointerover);
- root.addEventListener('pointermove', pointerover);
- root.addEventListener('pointerout', pointerout);
- return () => {
- root.removeEventListener('pointerover', pointerover);
- root.removeEventListener('pointermove', pointerover);
- root.removeEventListener('pointerout', pointerout);
- emitter.off('tooltip:show', onTooltipShow);
- emitter.off('tooltip:hide', onTooltipHide);
- destroyTooltip(root);
- };
- }
- exports.tooltip = tooltip;
- function Tooltip(options) {
- const { shared, crosshairs, series, name, item = () => ({}), facet = false } = options, rest = __rest(options, ["shared", "crosshairs", "series", "name", "item", "facet"]);
- return (target, viewInstances, emitter) => {
- const { container, view } = target;
- const { scale, markState, coordinate } = 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);
- // For non-facet and series tooltip.
- if (isSeries && hasSeries(markState) && !facet) {
- return seriesTooltip(plotArea, Object.assign(Object.assign({}, rest), { elements: utils_1.selectG2Elements, scale,
- coordinate, crosshairs: maybeValue(crosshairs, defaultShowCrosshairs), 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];
- // @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), { elements: () => elements, scale,
- coordinate, crosshairs: maybeValue(crosshairs, defaultShowCrosshairs), 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 }));
- };
- }
- exports.Tooltip = Tooltip;
- //# sourceMappingURL=tooltip.js.map
|