630 lines
18 KiB
Java
630 lines
18 KiB
Java
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; |