devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
455 lines (447 loc) • 17.1 kB
JavaScript
/**
* DevExtreme (viz/series/points/label.js)
* Version: 18.1.3
* Build date: Tue May 15 2018
*
* Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
var _format = require("../../core/format"),
vizUtils = require("../../core/utils"),
each = require("../../../core/utils/iterator").each,
extend = require("../../../core/utils/extend").extend,
_degreesToRadians = vizUtils.degreesToRadians,
_patchFontOptions = vizUtils.patchFontOptions,
_math = Math,
_round = _math.round,
_floor = _math.floor,
_abs = _math.abs,
_getCosAndSin = vizUtils.getCosAndSin,
_rotateBBox = vizUtils.rotateBBox,
CONNECTOR_LENGTH = 12,
LABEL_BACKGROUND_PADDING_X = 8,
LABEL_BACKGROUND_PADDING_Y = 4;
function getClosestCoord(point, coords) {
var closestCoord, closestDistance = 1 / 0;
each(coords, function(_, coord) {
var x = point[0] - coord[0],
y = point[1] - coord[1],
distance = x * x + y * y;
if (distance < closestDistance) {
closestDistance = distance;
closestCoord = coord
}
});
return [_floor(closestCoord[0]), _floor(closestCoord[1])]
}
var barPointStrategy = {
isLabelInside: function(labelPoint, figure) {
var xc = labelPoint.x + labelPoint.width / 2,
yc = labelPoint.y + labelPoint.height / 2;
return figure.x <= xc && xc <= figure.x + figure.width && figure.y <= yc && yc <= figure.y + figure.height
},
prepareLabelPoints: function(bBox, rotatedBBox, isHorizontal, angle) {
var x1 = rotatedBBox.x,
xc = x1 + rotatedBBox.width / 2,
x2 = x1 + rotatedBBox.width - 1,
y1 = rotatedBBox.y,
yc = y1 + rotatedBBox.height / 2,
y2 = y1 + rotatedBBox.height - 1;
return _abs(angle) % 90 === 0 ? [
[x1, y1],
[isHorizontal ? x1 : xc, isHorizontal ? yc : y1],
[x2, y1],
[x1, y2],
[isHorizontal ? x2 : xc, isHorizontal ? yc : y2],
[x2, y2]
] : [
[xc, yc]
]
},
isHorizontal: function(bBox, figure) {
return bBox.x > figure.x + figure.width || bBox.x + bBox.width < figure.x
},
getFigureCenter: function(figure) {
return [_floor(figure.x + figure.width / 2), _floor(figure.y + figure.height / 2)]
},
findFigurePoint: function(figure, labelPoint) {
var figureCenter = barPointStrategy.getFigureCenter(figure),
point = getClosestCoord(labelPoint, [
[figure.x, figureCenter[1]],
[figureCenter[0], figure.y + figure.height],
[figure.x + figure.width, figureCenter[1]],
[figureCenter[0], figure.y]
]);
return point
},
adjustPoints: function(points) {
var lineIsVertical = _abs(points[1] - points[3]) <= 1,
lineIsHorizontal = _abs(points[0] - points[2]) <= 1;
if (lineIsHorizontal) {
points[0] = points[2]
}
if (lineIsVertical) {
points[1] = points[3]
}
return points
}
};
var symbolPointStrategy = {
isLabelInside: function() {
return false
},
prepareLabelPoints: barPointStrategy.prepareLabelPoints,
isHorizontal: function(bBox, figure) {
return bBox.x > figure.x + figure.r || bBox.x + bBox.width < figure.x - figure.r
},
getFigureCenter: function(figure) {
return [figure.x, figure.y]
},
findFigurePoint: function(figure, labelPoint) {
var angle = Math.atan2(figure.y - labelPoint[1], labelPoint[0] - figure.x);
return [_round(figure.x + figure.r * Math.cos(angle)), _round(figure.y - figure.r * Math.sin(angle))]
},
adjustPoints: barPointStrategy.adjustPoints
};
var piePointStrategy = {
isLabelInside: function(_0, _1, isOutside) {
return !isOutside
},
prepareLabelPoints: function(bBox, rotatedBBox, isHorizontal, angle) {
var xl = bBox.x,
xr = xl + bBox.width,
xc = xl + _round(bBox.width / 2),
yt = bBox.y,
yb = yt + bBox.height,
yc = yt + _round(bBox.height / 2),
points = [
[
[xl, yt],
[xr, yt]
],
[
[xr, yt],
[xr, yb]
],
[
[xr, yb],
[xl, yb]
],
[
[xl, yb],
[xl, yt]
]
],
cosSin = _getCosAndSin(angle);
if (0 === angle) {
points = isHorizontal ? [
[xl, yc],
[xr, yc]
] : [
[xc, yt],
[xc, yb]
]
} else {
points = points.map(function(pair) {
return pair.map(function(point) {
return [_round((point[0] - xc) * cosSin.cos + (point[1] - yc) * cosSin.sin + xc), _round(-(point[0] - xc) * cosSin.sin + (point[1] - yc) * cosSin.cos + yc)]
})
}).reduce(function(r, pair) {
var point1x = pair[0][0],
point1y = pair[0][1],
point2x = pair[1][0],
point2y = pair[1][1];
if (isHorizontal) {
if (point1y >= yc && yc >= point2y || point1y <= yc && yc <= point2y) {
r.push([(yc - point1y) * (point2x - point1x) / (point2y - point1y) + point1x, yc])
}
} else {
if (point1x >= xc && xc >= point2x || point1x <= xc && xc <= point2x) {
r.push([xc, (xc - point1x) * (point2y - point1y) / (point2x - point1x) + point1y])
}
}
return r
}, [])
}
return points
},
isHorizontal: function(bBox, figure) {
return bBox.x > figure.x || figure.x > bBox.x + bBox.width
},
getFigureCenter: symbolPointStrategy.getFigureCenter,
findFigurePoint: function(figure, labelPoint, isHorizontal) {
if (!isHorizontal) {
return [figure.x, figure.y]
}
var labelX = labelPoint[0],
x = _round(figure.x + (figure.y - labelPoint[1]) / Math.tan(_degreesToRadians(figure.angle))),
points = [figure.x, figure.y, x, labelPoint[1]];
if (!(figure.x <= x && x <= labelX) && !(labelX <= x && x <= figure.x)) {
if (_abs(figure.x - labelX) < CONNECTOR_LENGTH) {
points = [figure.x, figure.y]
} else {
if (figure.x <= labelX) {
points[2] = figure.x + CONNECTOR_LENGTH
} else {
points[2] = figure.x - CONNECTOR_LENGTH
}
}
}
return points
},
adjustPoints: function(points) {
return points
}
};
function selectStrategy(figure) {
return void 0 !== figure.angle && piePointStrategy || void 0 !== figure.r && symbolPointStrategy || barPointStrategy
}
function disposeItem(obj, field) {
obj[field] && obj[field].dispose();
obj[field] = null
}
function checkBackground(background) {
return background && (background.fill && "none" !== background.fill || background["stroke-width"] > 0 && background.stroke && "none" !== background.stroke)
}
function checkConnector(connector) {
return connector && connector["stroke-width"] > 0 && connector.stroke && "none" !== connector.stroke
}
function formatText(data, options) {
data.valueText = _format(data.value, options);
data.argumentText = _format(data.argument, {
format: options.argumentFormat,
precision: options.argumentPrecision
});
if (void 0 !== data.percent) {
data.percentText = _format(data.percent, {
format: {
type: "percent",
precision: options.format && options.format.percentPrecision || options.percentPrecision
}
})
}
if (void 0 !== data.total) {
data.totalText = _format(data.total, options)
}
if (void 0 !== data.openValue) {
data.openValueText = _format(data.openValue, options)
}
if (void 0 !== data.closeValue) {
data.closeValueText = _format(data.closeValue, options)
}
if (void 0 !== data.lowValue) {
data.lowValueText = _format(data.lowValue, options)
}
if (void 0 !== data.highValue) {
data.highValueText = _format(data.highValue, options)
}
if (void 0 !== data.reductionValue) {
data.reductionValueText = _format(data.reductionValue, options)
}
return options.customizeText ? options.customizeText.call(data, data) : data.valueText
}
function Label(renderSettings) {
this._renderer = renderSettings.renderer;
this._container = renderSettings.labelsGroup;
this._point = renderSettings.point;
this._strategy = renderSettings.strategy
}
Label.prototype = {
constructor: Label,
setColor: function(color) {
this._color = color
},
setOptions: function(options) {
this._options = options
},
setData: function(data) {
this._data = data
},
setDataField: function(fieldName, fieldValue) {
this._data = this._data || {};
this._data[fieldName] = fieldValue
},
getData: function() {
return this._data
},
setFigureToDrawConnector: function(figure) {
this._figure = figure
},
dispose: function() {
var that = this;
disposeItem(that, "_group");
that._data = that._options = that._textContent = that._visible = that._insideGroup = that._text = that._background = that._connector = that._figure = null
},
_setVisibility: function(value, state) {
this._group && this._group.attr({
visibility: value
});
this._visible = state
},
isVisible: function() {
return this._visible
},
hide: function(holdInvisible) {
this._holdVisibility = !!holdInvisible;
this._hide()
},
_hide: function() {
this._setVisibility("hidden", false)
},
show: function(holdVisible) {
var correctPosition = !this._drawn;
if (this._point.hasValue()) {
this._holdVisibility = !!holdVisible;
this._show();
correctPosition && this._point.correctLabelPosition(this)
}
},
_show: function() {
var that = this,
renderer = that._renderer,
container = that._container,
options = that._options || {},
text = that._textContent = formatText(that._data, that._options) || null;
if (text) {
if (!that._group) {
that._group = renderer.g().append(container);
that._insideGroup = renderer.g().append(that._group);
that._text = renderer.text("", 0, 0).append(that._insideGroup)
}
that._text.css(options.attributes ? _patchFontOptions(options.attributes.font) : {});
if (checkBackground(options.background)) {
that._background = that._background || renderer.rect().append(that._insideGroup).toBackground();
that._background.attr(options.background);
that._color && that._background.attr({
fill: that._color
})
} else {
disposeItem(that, "_background")
}
if (checkConnector(options.connector)) {
that._connector = that._connector || renderer.path([], "line").sharp().append(that._group).toBackground();
that._connector.attr(options.connector);
that._color && that._connector.attr({
stroke: that._color
})
} else {
disposeItem(that, "_connector")
}
that._text.attr({
text: text,
align: options.textAlignment
});
that._updateBackground(that._text.getBBox());
that._setVisibility("visible", true);
that._drawn = true
} else {
that._hide()
}
},
_getLabelVisibility: function(isVisible) {
return this._holdVisibility ? this.isVisible() : isVisible
},
draw: function(isVisible) {
if (this._getLabelVisibility(isVisible)) {
this._show();
this._point && this._point.correctLabelPosition(this)
} else {
this._drawn = false;
this._hide()
}
return this
},
_updateBackground: function(bBox) {
var that = this;
if (that._background) {
bBox.x -= LABEL_BACKGROUND_PADDING_X;
bBox.y -= LABEL_BACKGROUND_PADDING_Y;
bBox.width += 2 * LABEL_BACKGROUND_PADDING_X;
bBox.height += 2 * LABEL_BACKGROUND_PADDING_Y;
that._background.attr(bBox)
}
that._bBoxWithoutRotation = extend({}, bBox);
if (that._options.rotationAngle) {
that._insideGroup.rotate(that._options.rotationAngle, bBox.x + bBox.width / 2, bBox.y + bBox.height / 2);
bBox = _rotateBBox(bBox, [bBox.x + bBox.width / 2, bBox.y + bBox.height / 2], -that._options.rotationAngle)
}
that._bBox = bBox
},
_getConnectorPoints: function() {
var labelPoint, isHorizontal, that = this,
figure = that._figure,
options = that._options,
strategy = that._strategy || selectStrategy(figure),
bBox = that._shiftBBox(that._bBoxWithoutRotation),
rotatedBBox = that.getBoundingRect(),
points = [];
if (!strategy.isLabelInside(bBox, figure, "inside" !== options.position)) {
isHorizontal = strategy.isHorizontal(bBox, figure);
points = strategy.prepareLabelPoints(bBox, rotatedBBox, isHorizontal, -options.rotationAngle || 0);
labelPoint = getClosestCoord(strategy.getFigureCenter(figure), points);
points = strategy.findFigurePoint(figure, labelPoint, isHorizontal);
points = points.concat(labelPoint)
}
return strategy.adjustPoints(points)
},
fit: function(maxWidth) {
var padding = this._background ? 2 * LABEL_BACKGROUND_PADDING_X : 0;
this._text && this._text.applyEllipsis(maxWidth - padding);
this._updateBackground(this._text.getBBox())
},
resetEllipsis: function() {
this._text && this._text.restoreText();
this._updateBackground(this._text.getBBox())
},
setTrackerData: function(point) {
this._text.data({
"chart-data-point": point
});
this._background && this._background.data({
"chart-data-point": point
})
},
hideInsideLabel: function(coords) {
return this._point.hideInsideLabel(this, coords)
},
shift: function(x, y) {
var that = this;
if (that._textContent) {
that._insideGroup.attr({
translateX: that._x = _round(x - that._bBox.x),
translateY: that._y = _round(y - that._bBox.y)
});
if (that._connector) {
that._connector.attr({
points: that._getConnectorPoints()
})
}
}
return that
},
getBoundingRect: function() {
return this._shiftBBox(this._bBox)
},
_shiftBBox: function(bBox) {
return this._textContent ? {
x: bBox.x + this._x,
y: bBox.y + this._y,
width: bBox.width,
height: bBox.height
} : {}
},
getLayoutOptions: function() {
var options = this._options;
return {
alignment: options.alignment,
background: checkBackground(options.background),
horizontalOffset: options.horizontalOffset,
verticalOffset: options.verticalOffset,
radialOffset: options.radialOffset,
position: options.position,
connectorOffset: (checkConnector(options.connector) ? CONNECTOR_LENGTH : 0) + (checkBackground(options.background) ? LABEL_BACKGROUND_PADDING_X : 0)
}
}
};
exports.Label = Label;