function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /** * @fileOverview The controller of tooltip * @author sima.zhang */ var Util = require('../../util'); var Shape = require('../../geom/shape/shape'); var _require = require('@antv/component/lib'), Tooltip = _require.Tooltip; var MatrixUtil = Util.MatrixUtil; var Vector2 = MatrixUtil.vec2; var TYPE_SHOW_MARKERS = ['line', 'area', 'path', 'areaStack']; // 默认展示 tooltip marker 的几何图形 var TYPE_SHOW_CROSSHAIRS = ['line', 'area', 'point']; // 默认展示十字瞄准线的几何图形 // TODO FIXME this is HARD CODING var IGNORE_TOOLTIP_ITEM_PROPERTIES = ['marker', 'showMarker']; function _indexOfArray(items, item) { var rst = -1; Util.each(items, function (sub, index) { var isEqual = true; for (var key in item) { if (item.hasOwnProperty(key) && !IGNORE_TOOLTIP_ITEM_PROPERTIES.includes(key)) { if (!Util.isObject(item[key]) && item[key] !== sub[key]) { isEqual = false; break; } } } if (isEqual) { rst = index; return false; } }); return rst; } // 判断是否有样式 function _hasClass(dom, className) { if (!dom) { return false; } var cls = ''; if (!dom.className) return false; if (!Util.isNil(dom.className.baseVal)) { cls = dom.className.baseVal; } else { cls = dom.className; } return cls.includes(className); } function _isParent(dom, cls) { var parent = dom.parentNode; var rst = false; while (parent && parent !== document.body) { if (_hasClass(parent, cls)) { rst = true; break; } parent = parent.parentNode; } return rst; } // 去除重复的值, 去除不同图形相同数据,只展示一份即可 function _uniqItems(items) { var tmp = []; Util.each(items, function (item) { var index = _indexOfArray(tmp, item); if (index === -1) { tmp.push(item); } else { tmp[index] = item; } }); return tmp; } var TooltipController = /*#__PURE__*/function () { function TooltipController(cfg) { Util.assign(this, cfg); this.timeStamp = 0; // tooltip 锁定不能移动 this.locked = false; } var _proto = TooltipController.prototype; _proto._normalizeEvent = function _normalizeEvent(event) { var chart = this.chart; var canvas = this._getCanvas(); var point = canvas.getPointByClient(event.clientX, event.clientY); var pixelRatio = canvas.get('pixelRatio'); point.x = point.x / pixelRatio; point.y = point.y / pixelRatio; var views = chart.getViewsByPoint(point); point.views = views; return point; }; _proto._getCanvas = function _getCanvas() { return this.chart.get('canvas'); }; _proto._getTriggerEvent = function _getTriggerEvent() { var options = this.options; var triggerOn = options.triggerOn; var eventName; if (!triggerOn || triggerOn === 'mousemove') { eventName = 'plotmove'; } else if (triggerOn === 'click') { eventName = 'plotclick'; } else if (triggerOn === 'none') { eventName = null; } return eventName; }; _proto._getDefaultTooltipCfg = function _getDefaultTooltipCfg() { var self = this; var chart = self.chart; var viewTheme = self.viewTheme; var options = self.options; var defaultCfg = Util.mix({}, viewTheme.tooltip); var geoms = chart.getAllGeoms().filter(function (geom) { return geom.get('visible'); }); var shapes = []; Util.each(geoms, function (geom) { var type = geom.get('type'); var adjusts = geom.get('adjusts'); var isSymmetric = false; if (adjusts) { Util.each(adjusts, function (adjust) { if (adjust.type === 'symmetric' || adjust.type === 'Symmetric') { isSymmetric = true; return false; } }); } if (Util.indexOf(shapes, type) === -1 && !isSymmetric) { shapes.push(type); } }); var isTransposed = geoms.length && geoms[0].get('coord') ? geoms[0].get('coord').isTransposed : false; var crosshairsCfg; if (geoms.length && geoms[0].get('coord') && geoms[0].get('coord').type === 'cartesian') { if (shapes[0] === 'interval' && options.shared !== false) { // 直角坐标系下 interval 的 crosshair 为矩形背景框 var crosshairs = Util.mix({}, viewTheme.tooltipCrosshairsRect); crosshairs.isTransposed = isTransposed; crosshairsCfg = { zIndex: 0, // 矩形背景框不可覆盖 geom crosshairs: crosshairs }; } else if (Util.indexOf(TYPE_SHOW_CROSSHAIRS, shapes[0]) > -1) { var _crosshairs = Util.mix({}, viewTheme.tooltipCrosshairsLine); _crosshairs.isTransposed = isTransposed; crosshairsCfg = { crosshairs: _crosshairs }; } } return Util.mix(defaultCfg, crosshairsCfg, {}); }; _proto._bindEvent = function _bindEvent() { var chart = this.chart; var triggerEvent = this._getTriggerEvent(); if (triggerEvent) { chart.on(triggerEvent, Util.wrapBehavior(this, 'onMouseMove')); chart.on('plotleave', Util.wrapBehavior(this, 'onMouseOut')); } }; _proto._offEvent = function _offEvent() { var chart = this.chart; var triggerEvent = this._getTriggerEvent(); if (triggerEvent) { chart.off(triggerEvent, Util.getWrapBehavior(this, 'onMouseMove')); chart.off('plotleave', Util.getWrapBehavior(this, 'onMouseOut')); } }; _proto._setTooltip = function _setTooltip(point, items, markersItems, target) { var self = this; var tooltip = self.tooltip; var prePoint = self.prePoint; if (!prePoint || prePoint.x !== point.x || prePoint.y !== point.y) { items = _uniqItems(items); self.prePoint = point; var chart = self.chart; var viewTheme = self.viewTheme; var x = Util.isArray(point.x) ? point.x[point.x.length - 1] : point.x; var y = Util.isArray(point.y) ? point.y[point.y.length - 1] : point.y; if (!tooltip.get('visible')) { chart.emit('tooltip:show', { x: x, y: y, tooltip: tooltip }); } var first = items[0]; var title = first.title || first.name; if (tooltip.isContentChange(title, items)) { chart.emit('tooltip:change', { tooltip: tooltip, x: x, y: y, items: items }); // bugfix: when set the title in the tooltip:change event does not take effect. title = items[0].title || items[0].name; tooltip.setContent(title, items); if (!Util.isEmpty(markersItems)) { if (self.options.hideMarkers === true) { // 不展示 tooltip marker tooltip.set('markerItems', markersItems); // 用于 tooltip 辅助线的定位 } else { tooltip.setMarkers(markersItems, viewTheme.tooltipMarker); } } else { tooltip.clearMarkers(); // clearMarkers 只会将 markerItems 从 markerGroup 中移除 // 所以我们还要将 markerItems 从 tooltip 中移除 // 这么做是为了防止上一次设置 marker 时的 markerItems 影响此次 tooltip 辅助线的定位 tooltip.set('markerItems', []); } } var canvas = this._getCanvas(); if (target === canvas && tooltip.get('type') === 'mini') { // filter mini tooltip tooltip.hide(); } else { tooltip.setPosition(x, y, target); tooltip.show(); } } }; _proto.hideTooltip = function hideTooltip() { var tooltip = this.tooltip; var chart = this.chart; var canvas = this._getCanvas(); this.prePoint = null; tooltip.hide(); chart.emit('tooltip:hide', { tooltip: tooltip }); canvas.draw(); }; _proto.onMouseMove = function onMouseMove(ev) { // 锁定时不移动 tooltip if (Util.isEmpty(ev.views) || this.locked) { return; } var lastTimeStamp = this.timeStamp; var timeStamp = +new Date(); var point = { x: ev.x, y: ev.y }; if (timeStamp - lastTimeStamp > 16 && !this.chart.get('stopTooltip')) { this.showTooltip(point, ev.views, ev.shape); this.timeStamp = timeStamp; } }; _proto.onMouseOut = function onMouseOut(ev) { var tooltip = this.tooltip; // 锁定 tooltip 时不隐藏 if (!tooltip.get('visible') || !tooltip.get('follow') || this.locked) { return; } // 除非离开 plot 时鼠标依然在图形上,这段逻辑没有意义 // if (ev && ev.target !== canvas) { // return; // } if (ev && ev.toElement && (_hasClass(ev.toElement, 'g2-tooltip') || _isParent(ev.toElement, 'g2-tooltip'))) { return; } this.hideTooltip(); }; _proto.renderTooltip = function renderTooltip() { var self = this; if (self.tooltip) { // tooltip 对象已经创建 return; } var chart = self.chart; var viewTheme = self.viewTheme; var canvas = self._getCanvas(); var defaultCfg = self._getDefaultTooltipCfg(); var options = self.options; options = Util.deepMix({ plotRange: chart.get('plotRange'), capture: false, canvas: canvas, frontPlot: chart.get('frontPlot'), viewTheme: viewTheme.tooltip, backPlot: chart.get('backPlot') }, defaultCfg, options); if (options.crosshairs && options.crosshairs.type === 'rect') { options.zIndex = 0; // toolip 背景框不可遮盖住 geom,防止用户配置了 crosshairs } options.visible = false; // @2018-09-13 by blue.lb 如果设置shared为false不需要指定position // if (options.shared === false && Util.isNil(options.position)) { // options.position = 'top'; // } var tooltip; if (options.type === 'mini') { options.crosshairs = false; // this.options.shared = false; options.position = 'top'; tooltip = new Tooltip.Mini(options); } else if (options.useHtml) { tooltip = new Tooltip.Html(options); } else { tooltip = new Tooltip.Canvas(options); } self.tooltip = tooltip; var triggerEvent = self._getTriggerEvent(); var tooltipContainer = tooltip.get('container'); if (!tooltip.get('enterable') && triggerEvent === 'plotmove') { // 鼠标不允许进入 tooltip 容器 if (tooltipContainer) { tooltipContainer.onmousemove = function (e) { // 避免 tooltip 频繁闪烁 var eventObj = self._normalizeEvent(e); chart.emit(triggerEvent, eventObj); }; } } // 优化:鼠标移入 tooltipContainer 然后再移出时,需要隐藏 tooltip if (tooltipContainer) { tooltipContainer.onmouseleave = function () { if (!self.locked) { self.hideTooltip(); } }; } self._bindEvent(); }; _proto._formatMarkerOfItem = function _formatMarkerOfItem(coord, geom, item) { var self = this; var options = self.options; var point = item.point; if (point && point.x && point.y) { // hotfix: make sure there is no null value var x = Util.isArray(point.x) ? point.x[point.x.length - 1] : point.x; var y = Util.isArray(point.y) ? point.y[point.y.length - 1] : point.y; point = coord.applyMatrix(x, y, 1); item.x = point[0]; item.y = point[1]; item.showMarker = true; // bugfix // 由于tooltip是DOM而不是Canvas,设置渐变色时,marker无法正常显示 // 如果,设置的颜色是渐变色并且设置了tooltip使用html方式渲染,则取渐变色的起始颜色作为marker的颜色,暂时解决这个问题 if (item.color.substring(0, 2) === 'l(' && (!options.hasOwnProperty('useHtml') || options.useHtml)) { item.color = item.color.split(' ')[1].substring(2); } var itemMarker = self._getItemMarker(geom, item); item.marker = itemMarker; if (Util.indexOf(TYPE_SHOW_MARKERS, geom.get('type')) !== -1) { return item; } } return null; }; _proto.lockTooltip = function lockTooltip() { this.locked = true; }; _proto.unlockTooltip = function unlockTooltip() { this.locked = false; }; _proto.showTooltip = function showTooltip(point, views, target) { var _this = this; var self = this; if (Util.isEmpty(views) || !point) { return; } if (!this.tooltip) { this.renderTooltip(); // 如果一开始 tooltip 关闭,用户重新调用的时候需要先生成 tooltip } var options = self.options; var markersItems = []; var items = []; Util.each(views, function (view) { if (!view.get('tooltipEnable')) { // 如果不显示tooltip,则跳过 return true; } var geoms = view.get('geoms'); var coord = view.get('coord'); Util.each(geoms, function (geom) { var type = geom.get('type'); if (geom.get('visible') && geom.get('tooltipCfg') !== false) { var dataArray = geom.get('dataArray'); if (geom.isShareTooltip() || options.shared === false && Util.inArray(['area', 'line', 'path', 'polygon'], type)) { // 打补丁解决 bug: https://github.com/antvis/g2/issues/1248 // 当 interval 对应的 color 和 x 字段相同的时候,并且包含 dodge,items 取值逻辑不一样 // 这种情况下,每一个 x 字段分成一组 var xScale = geom.getXScale(); var colorAttr = geom.getAttr('color'); var colorField = colorAttr ? colorAttr.field : undefined; if (type === 'interval' && xScale.field === colorField && geom.hasAdjust('dodge')) { // 找不到不为空的 var points = Util.find(dataArray, function (obj) { return !!geom.findPoint(point, obj); }); // 转为 tooltip items Util.each(points, function (tmpPoint) { var subItems = geom.getTipItems(tmpPoint, options.title); Util.each(subItems, function (v) { var markerItem = self._formatMarkerOfItem(coord, geom, v); if (markerItem) { markersItems.push(markerItem); } }); items = items.concat(subItems); }); } else { Util.each(dataArray, function (obj) { var tmpPoint = geom.findPoint(point, obj); if (tmpPoint) { var subItems = geom.getTipItems(tmpPoint, options.title); Util.each(subItems, function (v) { var markerItem = self._formatMarkerOfItem(coord, geom, v); if (markerItem) { markersItems.push(markerItem); } }); items = items.concat(subItems); } }); } } else { var geomContainer = geom.get('shapeContainer'); var canvas = geomContainer.get('canvas'); var pixelRatio = canvas.get('pixelRatio'); var shape = geomContainer.getShape(point.x * pixelRatio, point.y * pixelRatio); if (shape && shape.get('visible') && shape.get('origin')) { items = geom.getTipItems(shape.get('origin'), options.title); } Util.each(items, function (v) { var markerItem = _this._formatMarkerOfItem(coord, geom, v); if (markerItem) { markersItems.push(markerItem); } }); } } }); Util.each(items, function (item) { var point = item.point; var x = Util.isArray(point.x) ? point.x[point.x.length - 1] : point.x; var y = Util.isArray(point.y) ? point.y[point.y.length - 1] : point.y; point = coord.applyMatrix(x, y, 1); item.x = point[0]; item.y = point[1]; }); }); if (items.length) { var first = items[0]; // bugfix: multiple tooltip items with different titles if (!items.every(function (item) { return item.title === first.title; })) { var nearestItem = first; var nearestDistance = Infinity; items.forEach(function (item) { var distance = Vector2.distance([point.x, point.y], [item.x, item.y]); if (distance < nearestDistance) { nearestDistance = distance; nearestItem = item; } }); items = items.filter(function (item) { return item.title === nearestItem.title; }); markersItems = markersItems.filter(function (item) { return item.title === nearestItem.title; }); } if (options.shared === false && items.length > 1) { var snapItem = items[0]; var min = Math.abs(point.y - snapItem.y); Util.each(items, function (aItem) { if (Math.abs(point.y - aItem.y) <= min) { snapItem = aItem; min = Math.abs(point.y - aItem.y); } }); if (snapItem && snapItem.x && snapItem.y) { markersItems = [snapItem]; } items = [snapItem]; } // 3.0 采用当前鼠标位置作为 tooltip 的参考点 // if (!Util.isEmpty(markersItems)) { // point = markersItems[0]; // } self._setTooltip(point, items, markersItems, target); } else { self.hideTooltip(); } }; _proto.clear = function clear() { var tooltip = this.tooltip; tooltip && tooltip.destroy(); this.tooltip = null; this.prePoint = null; this._offEvent(); }; _proto._getItemMarker = function _getItemMarker(geom, item) { var options = this.options; var markerOption = options.marker || this.viewTheme.tooltip.marker; if (Util.isFunction(markerOption)) { var shapeType = geom.get('shapeType') || 'point'; var shape = geom.getDefaultValue('shape') || 'circle'; var shapeObject = Shape.getShapeFactory(shapeType); var cfg = { color: item.color }; var marker = shapeObject.getMarkerCfg(shape, cfg); return markerOption(marker, item); } return _extends({ fill: item.color }, markerOption); }; return TooltipController; }(); module.exports = TooltipController;