devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
502 lines (498 loc) • 18.7 kB
JavaScript
/**
* DevExtreme (viz/core/tooltip.js)
* Version: 18.2.18
* Build date: Tue Oct 18 2022
*
* Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
var domAdapter = require("../../core/dom_adapter"),
windowUtils = require("../../core/utils/window"),
inflector = require("../../core/utils/inflector"),
window = windowUtils.getWindow(),
$ = require("../../core/renderer"),
rendererModule = require("./renderers/renderer"),
typeUtils = require("../../core/utils/type"),
extend = require("../../core/utils/extend").extend,
HALF_ARROW_WIDTH = 10,
vizUtils = require("./utils"),
_format = require("../../format_helper").format,
mathCeil = Math.ceil,
mathMax = Math.max,
mathMin = Math.min;
function hideElement($element) {
$element.css({
left: "-9999px"
}).detach()
}
function getSpecialFormatOptions(options, specialFormat) {
var result = options;
switch (specialFormat) {
case "argument":
result = {
format: options.argumentFormat
};
break;
case "percent":
result = {
format: {
type: "percent",
precision: options.format && options.format.percentPrecision
}
}
}
return result
}
function Tooltip(params) {
var renderer, root, that = this;
that._eventTrigger = params.eventTrigger;
that._widgetRoot = params.widgetRoot;
that._wrapper = $("<div>").css({
position: "absolute",
overflow: "visible",
height: "1px",
pointerEvents: "none"
}).addClass(params.cssClass);
that._renderer = renderer = new rendererModule.Renderer({
pathModified: params.pathModified,
container: that._wrapper[0]
});
root = renderer.root;
root.attr({
"pointer-events": "none"
});
that._cloud = renderer.path([], "area").sharp().append(root);
that._shadow = renderer.shadowFilter();
that._textGroup = renderer.g().attr({
align: "center"
}).append(root);
that._text = renderer.text(void 0, 0, 0).append(that._textGroup);
that._textGroupHtml = $("<div>").css({
position: "absolute",
width: 0,
padding: 0,
margin: 0,
border: "0px solid transparent"
}).appendTo(that._wrapper);
that._textHtml = $("<div>").css({
position: "relative",
display: "inline-block",
padding: 0,
margin: 0,
border: "0px solid transparent"
}).appendTo(that._textGroupHtml)
}
Tooltip.prototype = {
constructor: Tooltip,
dispose: function() {
this._wrapper.remove();
this._renderer.dispose();
this._options = this._widgetRoot = null
},
_getContainer: function() {
var options = this._options,
container = $(this._widgetRoot).closest(options.container);
if (0 === container.length) {
container = $(options.container)
}
return (container.length ? container : $("body")).get(0)
},
setOptions: function(options) {
options = options || {};
var that = this,
cloudSettings = that._cloudSettings = {
opacity: options.opacity,
filter: that._shadow.id,
"stroke-width": null,
stroke: null
},
borderOptions = options.border || {};
that._shadowSettings = extend({
x: "-50%",
y: "-50%",
width: "200%",
height: "200%"
}, options.shadow);
that._options = options;
if (borderOptions.visible) {
extend(cloudSettings, {
"stroke-width": borderOptions.width,
stroke: borderOptions.color,
"stroke-opacity": borderOptions.opacity,
dashStyle: borderOptions.dashStyle
})
}
that._textFontStyles = vizUtils.patchFontOptions(options.font);
that._textFontStyles.color = options.font.color;
that._wrapper.css({
zIndex: options.zIndex
});
that._customizeTooltip = typeUtils.isFunction(options.customizeTooltip) ? options.customizeTooltip : null;
return that
},
setRendererOptions: function(options) {
this._renderer.setOptions(options);
this._textGroupHtml.css({
direction: options.rtl ? "rtl" : "ltr"
});
return this
},
render: function() {
var that = this;
hideElement(that._wrapper);
that._cloud.attr(that._cloudSettings);
that._shadow.attr(that._shadowSettings);
var normalizedCSS = {};
for (var name in that._textFontStyles) {
normalizedCSS[inflector.camelize(name)] = that._textFontStyles[name]
}
that._textGroupHtml.css(normalizedCSS);
that._textGroup.css(that._textFontStyles);
that._text.css(that._textFontStyles);
that._eventData = null;
return that
},
update: function(options) {
return this.setOptions(options).render()
},
_prepare: function(formatObject, state) {
var options = this._options,
customize = {};
if (this._customizeTooltip) {
customize = this._customizeTooltip.call(formatObject, formatObject);
customize = typeUtils.isPlainObject(customize) ? customize : {};
if ("text" in customize) {
state.text = typeUtils.isDefined(customize.text) ? String(customize.text) : ""
}
if ("html" in customize) {
state.html = typeUtils.isDefined(customize.html) ? String(customize.html) : ""
}
}
if (!("text" in state) && !("html" in state)) {
state.text = formatObject.valueText || ""
}
state.color = customize.color || options.color;
state.borderColor = customize.borderColor || (options.border || {}).color;
state.textColor = customize.fontColor || (options.font || {}).color;
return !!state.text || !!state.html
},
show: function(formatObject, params, eventData) {
var bBox, contentSize, that = this,
state = {},
options = that._options,
paddingLeftRight = options.paddingLeftRight,
paddingTopBottom = options.paddingTopBottom,
textGroupHtml = that._textGroupHtml,
textHtml = that._textHtml,
ss = that._shadowSettings,
xOff = ss.offsetX,
yOff = ss.offsetY,
blur = 2 * ss.blur + 1,
getComputedStyle = window.getComputedStyle;
if (!that._prepare(formatObject, state)) {
return false
}
that._state = state;
state.tc = {};
that._wrapper.appendTo(that._getContainer());
that._cloud.attr({
fill: state.color,
stroke: state.borderColor
});
if (state.html) {
that._text.attr({
text: ""
});
textGroupHtml.css({
color: state.textColor,
width: that._getCanvas().width
});
textHtml.html(state.html);
if (getComputedStyle) {
bBox = getComputedStyle(textHtml.get(0));
bBox = {
x: 0,
y: 0,
width: mathCeil(parseFloat(bBox.width)),
height: mathCeil(parseFloat(bBox.height))
}
} else {
bBox = textHtml.get(0).getBoundingClientRect();
bBox = {
x: 0,
y: 0,
width: mathCeil(bBox.width ? bBox.width : bBox.right - bBox.left),
height: mathCeil(bBox.height ? bBox.height : bBox.bottom - bBox.top)
}
}
textGroupHtml.width(bBox.width);
textGroupHtml.height(bBox.height)
} else {
textHtml.html("");
that._text.css({
fill: state.textColor
}).attr({
text: state.text
});
bBox = that._textGroup.css({
fill: state.textColor
}).getBBox()
}
contentSize = state.contentSize = {
x: bBox.x - paddingLeftRight,
y: bBox.y - paddingTopBottom,
width: bBox.width + 2 * paddingLeftRight,
height: bBox.height + 2 * paddingTopBottom,
lm: blur - xOff > 0 ? blur - xOff : 0,
rm: blur + xOff > 0 ? blur + xOff : 0,
tm: blur - yOff > 0 ? blur - yOff : 0,
bm: blur + yOff > 0 ? blur + yOff : 0
};
contentSize.fullWidth = contentSize.width + contentSize.lm + contentSize.rm;
contentSize.fullHeight = contentSize.height + contentSize.tm + contentSize.bm + options.arrowLength;
that.move(params.x, params.y, params.offset);
that._eventData && that._eventTrigger("tooltipHidden", that._eventData);
that._eventData = eventData;
that._eventTrigger("tooltipShown", that._eventData);
return true
},
hide: function() {
var that = this;
hideElement(that._wrapper);
that._eventData && that._eventTrigger("tooltipHidden", that._eventData);
that._eventData = null
},
move: function(x, y, offset) {
offset = offset || 0;
var that = this,
canvas = that._getCanvas(),
state = that._state,
coords = state.tc,
contentSize = state.contentSize;
if (that._calculatePosition(x, y, offset, canvas)) {
that._cloud.attr({
points: coords.cloudPoints
}).move(contentSize.lm, contentSize.tm);
if (state.html) {
that._textGroupHtml.css({
left: -contentSize.x + contentSize.lm,
top: -contentSize.y + contentSize.tm + coords.correction
})
} else {
that._textGroup.move(-contentSize.x + contentSize.lm, -contentSize.y + contentSize.tm + coords.correction)
}
that._renderer.resize("out" === coords.hp ? canvas.fullWidth + contentSize.lm : contentSize.fullWidth, "out" === coords.vp ? canvas.fullHeight : contentSize.fullHeight)
}
offset = that._wrapper.css({
left: 0,
top: 0
}).offset();
that._wrapper.css({
left: coords.x - offset.left,
top: coords.y - offset.top,
width: "out" === coords.hp ? canvas.fullWidth + contentSize.lm : contentSize.fullWidth
})
},
formatValue: function(value, _specialFormat) {
var options = _specialFormat ? getSpecialFormatOptions(this._options, _specialFormat) : this._options;
return _format(value, options.format)
},
getLocation: function() {
return vizUtils.normalizeEnum(this._options.location)
},
isEnabled: function() {
return !!this._options.enabled
},
isShared: function() {
return !!this._options.shared
},
_calculatePosition: function(x, y, offset, canvas) {
var cloudPoints, y1, y3, that = this,
options = that._options,
arrowLength = options.arrowLength,
state = that._state,
coords = state.tc,
contentSize = state.contentSize,
contentWidth = contentSize.width,
halfContentWidth = contentWidth / 2,
contentHeight = contentSize.height,
cTop = y - canvas.top,
cBottom = canvas.top + canvas.height - y,
cLeft = x - canvas.left,
cRight = canvas.width + canvas.left - x,
tTop = contentHeight + arrowLength + offset + contentSize.tm,
tBottom = contentHeight + arrowLength + offset + contentSize.bm,
tLeft = contentWidth + contentSize.lm,
tRight = contentWidth + contentSize.rm,
tHalfLeft = halfContentWidth + contentSize.lm,
tHalfRight = halfContentWidth + contentSize.rm,
correction = 0,
arrowPoints = [6, 0],
x1 = halfContentWidth + HALF_ARROW_WIDTH,
x2 = halfContentWidth,
x3 = halfContentWidth - HALF_ARROW_WIDTH,
y2 = contentHeight + arrowLength,
hp = "center",
vp = "bottom";
y1 = y3 = contentHeight;
if (tTop > cTop && tBottom > cBottom) {
vp = "out"
} else {
if (tTop > cTop) {
vp = "top"
}
}
if (tLeft > cLeft && tRight > cRight) {
hp = "out"
} else {
if (tHalfLeft > cLeft && tRight < cRight) {
hp = "left"
} else {
if (tHalfRight > cRight && tLeft < cLeft) {
hp = "right"
}
}
}
if ("out" === hp) {
x = canvas.left
} else {
if ("left" === hp) {
x1 = HALF_ARROW_WIDTH;
x2 = x3 = 0
} else {
if ("right" === hp) {
x1 = x2 = contentWidth;
x3 = contentWidth - HALF_ARROW_WIDTH;
x -= contentWidth
} else {
if ("center" === hp) {
x -= halfContentWidth
}
}
}
}
if ("out" === vp) {
y = canvas.top
} else {
if ("top" === vp) {
"out" !== hp && (correction = arrowLength);
arrowPoints[0] = 2;
y1 = y3 = arrowLength;
y2 = x1;
x1 = x3;
x3 = y2;
y2 = 0;
y += offset
} else {
y -= contentHeight + arrowLength + offset
}
}
coords.x = x - contentSize.lm;
coords.y = y - contentSize.tm;
coords.correction = correction;
if (hp === coords.hp && vp === coords.vp) {
return false
}
coords.hp = hp;
coords.vp = vp;
cloudPoints = [0, 0 + correction, contentWidth, 0 + correction, contentWidth, contentHeight + correction, 0, contentHeight + correction];
if ("out" !== hp && "out" !== vp) {
arrowPoints.splice(2, 0, x1, y1, x2, y2, x3, y3);
cloudPoints.splice.apply(cloudPoints, arrowPoints)
}
coords.cloudPoints = cloudPoints;
return true
},
_getCanvas: function() {
var container = this._getContainer(),
containerBox = container.getBoundingClientRect(),
html = domAdapter.getDocumentElement(),
body = domAdapter.getBody(),
left = window.pageXOffset || html.scrollLeft || 0,
top = window.pageYOffset || html.scrollTop || 0;
var box = {
left: left,
top: top,
width: html.clientWidth || 0,
height: html.clientHeight || 0,
fullWidth: mathMax(body.scrollWidth, html.scrollWidth, body.offsetWidth, html.offsetWidth, body.clientWidth, html.clientWidth) - left,
fullHeight: mathMax(body.scrollHeight, html.scrollHeight, body.offsetHeight, html.offsetHeight, body.clientHeight, html.clientHeight) - top
};
if (container !== body) {
left = mathMax(box.left, box.left + containerBox.left);
top = mathMax(box.top, box.top + containerBox.top);
box.width = mathMin(box.width + box.left - left, containerBox.width + (containerBox.left > 0 ? 0 : containerBox.left));
box.height = mathMin(box.height + box.top - top, containerBox.height + (containerBox.top > 0 ? 0 : containerBox.top));
box.fullWidth = box.width;
box.fullHeight = box.height;
box.left = left;
box.top = top
}
return box
}
};
exports.Tooltip = Tooltip;
exports.plugin = {
name: "tooltip",
init: function() {
this._initTooltip()
},
dispose: function() {
this._disposeTooltip()
},
members: {
_initTooltip: function() {
this._tooltip = new exports.Tooltip({
cssClass: this._rootClassPrefix + "-tooltip",
eventTrigger: this._eventTrigger,
pathModified: this.option("pathModified"),
widgetRoot: this.element()
})
},
_disposeTooltip: function() {
this._tooltip.dispose();
this._tooltip = null
},
_hideTooltip: function() {
this._tooltip.hide()
},
_onRender: function() {
if (!this._$element.is(":visible")) {
this._hideTooltip()
}
},
_setTooltipRendererOptions: function() {
this._tooltip.setRendererOptions(this._getRendererOptions())
},
_setTooltipOptions: function() {
this._tooltip.update(this._getOption("tooltip"))
}
},
customize: function(constructor) {
var proto = constructor.prototype;
proto._eventsMap.onTooltipShown = {
name: "tooltipShown"
};
proto._eventsMap.onTooltipHidden = {
name: "tooltipHidden"
};
constructor.addChange({
code: "TOOLTIP_RENDERER",
handler: function() {
this._setTooltipRendererOptions()
},
isThemeDependent: true,
isOptionChange: true
});
constructor.addChange({
code: "TOOLTIP",
handler: function() {
this._setTooltipOptions()
},
isThemeDependent: true,
isOptionChange: true,
option: "tooltip"
})
}
};