/** * @fileOverview The tooltip handler * @author sima.zhang */ var Util = require('../../util'); var _require = require('../../global'), defaultColor = _require.defaultColor; var FIELD_ORIGIN = '_origin'; function getScaleName(scale) { return scale.alias || scale.field; } var TooltipMixin = { _getIntervalSize: function _getIntervalSize(obj) { var size = null; var type = this.get('type'); var coord = this.get('coord'); if (coord.isRect && (type === 'interval' || type === 'schema')) { size = this.getSize(obj[FIELD_ORIGIN]); // 如果字段发生了映射,宽度计算就会报错 var dim = coord.isTransposed ? 'y' : 'x'; if (Util.isArray(obj[dim])) { var width = Math.abs(obj[dim][1] - obj[dim][0]); size = size < width ? null : size; // 直方图计算错误 } } return size; }, _snapEqual: function _snapEqual(v1, v2, scale) { var equals; v1 = scale.translate(v1); v2 = scale.translate(v2); if (scale.isCategory) { equals = v1 === v2; } else { equals = Util.snapEqual(v1, v2); } return equals; }, _getScaleValueByPoint: function _getScaleValueByPoint(point) { var result = 0; var coord = this.get('coord'); var xScale = this.getXScale(); var invertPoint = coord.invert(point); var xValue = invertPoint.x; if (this.isInCircle() && xValue > (1 + xScale.rangeMax()) / 2) { xValue = xScale.rangeMin(); // 极坐标下,scale 的 range 被做过特殊处理 see view.js#L88 } result = xScale.invert(xValue); if (xScale.isCategory) { result = xScale.translate(result); // 防止分类类型 } return result; }, _getOriginByPoint: function _getOriginByPoint(point) { var xScale = this.getXScale(); var yScale = this.getYScale(); var xField = xScale.field; var yField = yScale.field; var coord = this.get('coord'); var invertPoint = coord.invert(point); var xValue = xScale.invert(invertPoint.x); var yValue = yScale.invert(invertPoint.y); var result = {}; result[xField] = xValue; result[yField] = yValue; return result; }, _getScale: function _getScale(field) { var self = this; var scales = self.get('scales'); var rst = null; Util.each(scales, function (scale) { if (scale.field === field) { rst = scale; return false; } }); return rst; }, // 获取值对应的度量 _getTipValueScale: function _getTipValueScale() { var attrs = this.getAttrsForLegend(); var scale; Util.each(attrs, function (attr) { var tmpScale = attr.getScale(attr.type); if (tmpScale.isLinear) { // 如果指定字段是非position的,同时是连续的 scale = tmpScale; return false; } }); var xScale = this.getXScale(); var yScale = this.getYScale(); if (!scale && yScale && yScale.field === '..y') { return xScale; } return scale || yScale || xScale; }, _getTipTitleScale: function _getTipTitleScale(titleField) { var self = this; if (titleField) { return self._getScale(titleField); } var position = self.getAttr('position'); var fields = position.getFields(); var tmpField; Util.each(fields, function (field) { if (!field.includes('..')) { tmpField = field; return false; } }); return self._getScale(tmpField); }, _filterValue: function _filterValue(arr, point) { var coord = this.get('coord'); var yScale = this.getYScale(); var yField = yScale.field; var invertPoint = coord.invert(point); var yValue = invertPoint.y; yValue = yScale.invert(yValue); var rst = arr[arr.length - 1]; Util.each(arr, function (obj) { var origin = obj[FIELD_ORIGIN]; if (origin[yField][0] <= yValue && origin[yField][1] >= yValue) { rst = obj; return false; } }); return rst; }, getXDistance: function getXDistance() { var self = this; var distance = self.get('xDistance'); if (!distance) { var xScale = self.getXScale(); if (xScale.isCategory) { distance = 1; } else { var values = xScale.values; // values 是无序的 var min = xScale.translate(values[0]); var max = min; Util.each(values, function (value) { // 时间类型需要 translate value = xScale.translate(value); if (value < min) { min = value; } if (value > max) { max = value; } }); var length = values.length; // 应该是除以 length - 1 distance = (max - min) / (length - 1); } self.set('xDistance', distance); } return distance; }, findPoint: function findPoint(point, dataArray) { var self = this; var type = self.get('type'); var xScale = self.getXScale(); var yScale = self.getYScale(); var xField = xScale.field; var yField = yScale.field; var rst = null; if (Util.indexOf(['heatmap', 'point'], type) > -1) { var coord = self.get('coord'); var invertPoint = coord.invert(point); var xValue = xScale.invert(invertPoint.x); var yValue = yScale.invert(invertPoint.y); var min = Infinity; Util.each(dataArray, function (obj) { var distance = Math.pow(obj[FIELD_ORIGIN][xField] - xValue, 2) + Math.pow(obj[FIELD_ORIGIN][yField] - yValue, 2); if (distance < min) { min = distance; rst = obj; } }); return rst; } var first = dataArray[0]; var last = dataArray[dataArray.length - 1]; if (!first) { return rst; } var value = self._getScaleValueByPoint(point); // 根据该点获得对应度量后数据的值 var firstXValue = first[FIELD_ORIGIN][xField]; var firstYValue = first[FIELD_ORIGIN][yField]; var lastXValue = last[FIELD_ORIGIN][xField]; var isYRange = yScale.isLinear && Util.isArray(firstYValue); // 考虑 x 维度相同,y 是数组区间的情况 // 如果x的值是数组 if (Util.isArray(firstXValue)) { Util.each(dataArray, function (record) { var origin = record[FIELD_ORIGIN]; if (xScale.translate(origin[xField][0]) <= value && xScale.translate(origin[xField][1]) >= value) { if (isYRange) { if (!Util.isArray(rst)) { rst = []; } rst.push(record); } else { rst = record; return false; } } }); if (Util.isArray(rst)) { rst = this._filterValue(rst, point); } } else { var next; if (!xScale.isLinear && xScale.type !== 'timeCat') { Util.each(dataArray, function (record, index) { var origin = record[FIELD_ORIGIN]; if (self._snapEqual(origin[xField], value, xScale)) { if (isYRange) { if (!Util.isArray(rst)) { rst = []; } rst.push(record); } else { rst = record; return false; } } else if (xScale.translate(origin[xField]) <= value) { last = record; next = dataArray[index + 1]; } }); if (Util.isArray(rst)) { rst = this._filterValue(rst, point); } } else { if ((value > xScale.translate(lastXValue) || value < xScale.translate(firstXValue)) && (value > xScale.max || value < xScale.min)) { return null; } var firstIdx = 0; var lastIdx = dataArray.length - 1; var middleIdx; while (firstIdx <= lastIdx) { middleIdx = Math.floor((firstIdx + lastIdx) / 2); var item = dataArray[middleIdx][FIELD_ORIGIN][xField]; if (self._snapEqual(item, value, xScale)) { return dataArray[middleIdx]; } if (xScale.translate(item) <= xScale.translate(value)) { firstIdx = middleIdx + 1; last = dataArray[middleIdx]; next = dataArray[middleIdx + 1]; } else { if (lastIdx === 0) { last = dataArray[0]; } lastIdx = middleIdx - 1; } } } if (last && next) { // 计算最逼近的 if (Math.abs(xScale.translate(last[FIELD_ORIGIN][xField]) - value) > Math.abs(xScale.translate(next[FIELD_ORIGIN][xField]) - value)) { last = next; } } } var distance = self.getXDistance(); // 每个分类间的平均间距 if (!rst && Math.abs(xScale.translate(last[FIELD_ORIGIN][xField]) - value) <= distance / 2) { rst = last; } return rst; }, /** * @protected * 获取tooltip的标题 * @param {Object} origin 点的原始信息 * @param {String} titleField 标题的字段 * @return {String} 提示信息的标题 */ getTipTitle: function getTipTitle(origin, titleField) { var tipTitle = ''; var titleScale = this._getTipTitleScale(titleField); if (titleScale) { var value = origin[titleScale.field]; tipTitle = titleScale.getText(value); } else if (this.get('type') === 'heatmap') { // 热力图在不存在 title 的时候特殊处理 var xScale = this.getXScale(); var yScale = this.getYScale(); var xValue = xScale.getText(origin[xScale.field]); var yValue = yScale.getText(origin[yScale.field]); tipTitle = '( ' + xValue + ', ' + yValue + ' )'; } return tipTitle; }, getTipValue: function getTipValue(origin, valueScale) { var value; var field = valueScale.field; var key = origin.key; value = origin[field]; if (Util.isArray(value)) { var tmp = []; Util.each(value, function (sub) { tmp.push(valueScale.getText(sub)); }); value = tmp.join('-'); } else { value = valueScale.getText(value, key); } return value; }, /** * @protected * 获取tooltip的名称 * @param {Object} origin 点的原始信息 * @return {String} 提示信息的名称 */ getTipName: function getTipName(origin) { var name; var nameScale; var groupScales = this._getGroupScales(); if (groupScales.length) { // 如果存在分组类型,取第一个分组类型 Util.each(groupScales, function (scale) { nameScale = scale; return false; }); } if (nameScale) { var field = nameScale.field; name = nameScale.getText(origin[field]); } else { var valueScale = this._getTipValueScale(); name = getScaleName(valueScale); } return name; }, /** * 获取点对应tooltip的信息 * @protected * @param {Object} point 原始的数据记录 * @param {String} titleField tooltipTitle 配置信息 * @return {Array} 一条或者多条记录 */ getTipItems: function getTipItems(point, titleField) { var self = this; var origin = point[FIELD_ORIGIN]; var tipTitle = self.getTipTitle(origin, titleField); var tooltipCfg = self.get('tooltipCfg'); var items = []; var name; var value; function addItem(itemName, itemValue, cfg) { if (!Util.isNil(itemValue) && itemValue !== '') { // 值为null的时候,忽视 var item = { title: tipTitle, point: point, name: itemName || tipTitle, value: itemValue, color: point.color || defaultColor, marker: true }; item.size = self._getIntervalSize(point); items.push(Util.mix({}, item, cfg)); } } if (tooltipCfg) { var fields = tooltipCfg.fields; var cfg = tooltipCfg.cfg; var callbackParams = []; Util.each(fields, function (field) { callbackParams.push(origin[field]); }); if (cfg) { // 存在回调函数 if (Util.isFunction(cfg)) { cfg = cfg.apply(null, callbackParams); } var itemCfg = Util.mix({}, { point: point, title: tipTitle, color: point.color || defaultColor, marker: true // 默认展示 marker }, cfg); itemCfg.size = self._getIntervalSize(point); items.push(itemCfg); } else { Util.each(fields, function (field) { if (!Util.isNil(origin[field])) { // 字段数据为null ,undefined时不显示 var scale = self._getScale(field); name = getScaleName(scale); value = scale.getText(origin[field]); addItem(name, value); } }); } } else { var valueScale = self._getTipValueScale(); if (!Util.isNil(origin[valueScale.field])) { // 字段数据为null ,undefined时不显示 value = self.getTipValue(origin, valueScale); name = self.getTipName(origin); addItem(name, value); } } return items; }, isShareTooltip: function isShareTooltip() { var shareTooltip = this.get('shareTooltip'); var type = this.get('type'); var view = this.get('view'); var options; if (view.get('parent')) { options = view.get('parent').get('options'); } else { options = view.get('options'); } if (type === 'interval') { var coord = this.get('coord'); var coordType = coord.type; if (coordType === 'theta' || coordType === 'polar' && coord.isTransposed) { shareTooltip = false; } } else if (!this.getYScale() || Util.inArray(['contour', 'point', 'polygon', 'edge'], type)) { shareTooltip = false; } if (options.tooltip && Util.isBoolean(options.tooltip.shared)) { // 以用户设置的为准 shareTooltip = options.tooltip.shared; } return shareTooltip; } }; module.exports = TooltipMixin;