513 lines
14 KiB
Java
513 lines
14 KiB
Java
/**
|
||
* @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; |