devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
345 lines (331 loc) • 14 kB
JavaScript
/**
* DevExtreme (viz/chart_components/layout_manager.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/
*/
;
var extend = require("../../core/utils/extend").extend,
layoutElementModule = require("../core/layout_element"),
_isNumber = require("../../core/utils/type").isNumeric,
_min = Math.min,
_max = Math.max,
_floor = Math.floor,
_sqrt = Math.sqrt,
consts = require("../components/consts"),
RADIAL_LABEL_INDENT = consts.radialLabelIndent;
function getNearestCoord(firstCoord, secondCoord, pointCenterCoord) {
var nearestCoord;
if (pointCenterCoord < firstCoord) {
nearestCoord = firstCoord
} else {
if (secondCoord < pointCenterCoord) {
nearestCoord = secondCoord
} else {
nearestCoord = pointCenterCoord
}
}
return nearestCoord
}
function getLabelLayout(point) {
if (point._label.isVisible() && "inside" !== point._label.getLayoutOptions().position) {
return point._label.getBoundingRect()
}
}
function getPieRadius(series, paneCenterX, paneCenterY, accessibleRadius, minR) {
series.some(function(singleSeries) {
return singleSeries.getVisiblePoints().reduce(function(radiusIsFound, point) {
var labelBBox = getLabelLayout(point);
if (labelBBox) {
var xCoords = getNearestCoord(labelBBox.x, labelBBox.x + labelBBox.width, paneCenterX),
yCoords = getNearestCoord(labelBBox.y, labelBBox.y + labelBBox.height, paneCenterY);
accessibleRadius = _min(_max(getLengthFromCenter(xCoords, yCoords, paneCenterX, paneCenterY) - RADIAL_LABEL_INDENT, minR), accessibleRadius);
radiusIsFound = true
}
return radiusIsFound
}, false)
});
return accessibleRadius
}
function getSizeLabels(series) {
return series.reduce(function(res, singleSeries) {
var maxWidth = singleSeries.getVisiblePoints().reduce(function(width, point) {
var labelBBox = getLabelLayout(point);
if (labelBBox && labelBBox.width > width) {
width = labelBBox.width
}
return width
}, 0);
if (maxWidth) {
res.outerLabelsCount++;
if (res.outerLabelsCount > 1) {
maxWidth += consts.pieLabelSpacing
}
}
res.sizes.push(maxWidth);
res.common += maxWidth;
return res
}, {
sizes: [],
common: 0,
outerLabelsCount: 0
})
}
function correctLabelRadius(sizes, radius, series, canvas, averageWidthLabels) {
var curRadius, i, centerX = (canvas.width - canvas.left - canvas.right) / 2 + canvas.left,
runningWidth = 0;
for (i = 0; i < series.length; i++) {
if (0 === sizes[i]) {
curRadius && (curRadius += sizes[i - 1]);
continue
}
curRadius = _floor(curRadius ? curRadius + sizes[i - 1] : radius);
series[i].correctLabelRadius(curRadius);
runningWidth += averageWidthLabels || sizes[i];
sizes[i] = averageWidthLabels || sizes[i];
series[i].setVisibleArea({
left: centerX - radius - runningWidth,
right: canvas.width - (centerX + radius + runningWidth),
top: canvas.top,
bottom: canvas.bottom,
width: canvas.width,
height: canvas.height
})
}
}
function getLengthFromCenter(x, y, paneCenterX, paneCenterY) {
return _sqrt((x - paneCenterX) * (x - paneCenterX) + (y - paneCenterY) * (y - paneCenterY))
}
function getInnerRadius(series) {
var innerRadius;
if ("pie" === series.type) {
innerRadius = 0
} else {
innerRadius = _isNumber(series.innerRadius) ? Number(series.innerRadius) : .5;
innerRadius = innerRadius < .2 ? .2 : innerRadius;
innerRadius = innerRadius > .8 ? .8 : innerRadius
}
return innerRadius
}
var inverseAlign = {
left: "right",
right: "left",
top: "bottom",
bottom: "top",
center: "center"
};
function downSize(canvas, layoutOptions) {
canvas[layoutOptions.cutLayoutSide] += "horizontal" === layoutOptions.cutSide ? layoutOptions.width : layoutOptions.height
}
function getOffset(layoutOptions, offsets) {
var side = layoutOptions.cutLayoutSide,
offset = {
horizontal: 0,
vertical: 0
};
switch (side) {
case "top":
case "left":
offset[layoutOptions.cutSide] = -offsets[side];
break;
case "bottom":
case "right":
offset[layoutOptions.cutSide] = offsets[side]
}
return offset
}
function LayoutManager() {}
function toLayoutElementCoords(canvas) {
return new layoutElementModule.WrapperLayoutElement(null, {
x: canvas.left,
y: canvas.top,
width: canvas.width - canvas.left - canvas.right,
height: canvas.height - canvas.top - canvas.bottom
})
}
function correctAvailableRadius(availableRadius, canvas, series, minR, paneCenterX, paneCenterY) {
var averageWidthLabels, sizeLabels = getSizeLabels(series),
fullRadiusWithLabels = paneCenterX - canvas.left - (sizeLabels.outerLabelsCount > 0 ? sizeLabels.common + RADIAL_LABEL_INDENT : 0);
if (fullRadiusWithLabels < minR) {
availableRadius = minR;
averageWidthLabels = (paneCenterX - availableRadius - RADIAL_LABEL_INDENT - canvas.left) / sizeLabels.outerLabelsCount
} else {
availableRadius = _min(getPieRadius(series, paneCenterX, paneCenterY, availableRadius, minR), fullRadiusWithLabels)
}
correctLabelRadius(sizeLabels.sizes, availableRadius + RADIAL_LABEL_INDENT, series, canvas, averageWidthLabels);
return availableRadius
}
LayoutManager.prototype = {
constructor: LayoutManager,
setOptions: function(options) {
this._options = options
},
applyPieChartSeriesLayout: function(canvas, series, hideLayoutLabels) {
var availableRadius, minR, paneSpaceHeight = canvas.height - canvas.top - canvas.bottom,
paneSpaceWidth = canvas.width - canvas.left - canvas.right,
paneCenterX = paneSpaceWidth / 2 + canvas.left,
paneCenterY = paneSpaceHeight / 2 + canvas.top,
piePercentage = this._options.piePercentage;
if (_isNumber(piePercentage)) {
availableRadius = minR = piePercentage * _min(canvas.height, canvas.width) / 2
} else {
availableRadius = _min(paneSpaceWidth, paneSpaceHeight) / 2;
minR = this._options.minPiePercentage * availableRadius
}
if (!hideLayoutLabels) {
availableRadius = correctAvailableRadius(availableRadius, canvas, series, minR, paneCenterX, paneCenterY)
}
return {
centerX: _floor(paneCenterX),
centerY: _floor(paneCenterY),
radiusInner: _floor(availableRadius * getInnerRadius(series[0])),
radiusOuter: _floor(availableRadius)
}
},
applyEqualPieChartLayout: function(series, layout) {
var radius = layout.radius;
return {
centerX: _floor(layout.x),
centerY: _floor(layout.y),
radiusInner: _floor(radius * getInnerRadius(series[0])),
radiusOuter: _floor(radius)
}
},
needMoreSpaceForPanesCanvas: function(panes, rotated) {
var options = this._options,
width = options.width,
height = options.height,
piePercentage = options.piePercentage,
percentageIsValid = _isNumber(piePercentage),
needHorizontalSpace = 0,
needVerticalSpace = 0;
panes.forEach(function(pane) {
var paneCanvas = pane.canvas,
minSize = percentageIsValid ? _min(paneCanvas.width, paneCanvas.height) * piePercentage : void 0,
needPaneHorizontalSpace = (percentageIsValid ? minSize : width) - (paneCanvas.width - paneCanvas.left - paneCanvas.right),
needPaneVerticalSpace = (percentageIsValid ? minSize : height) - (paneCanvas.height - paneCanvas.top - paneCanvas.bottom);
if (rotated) {
needHorizontalSpace += needPaneHorizontalSpace > 0 ? needPaneHorizontalSpace : 0;
needVerticalSpace = _max(needPaneVerticalSpace > 0 ? needPaneVerticalSpace : 0, needVerticalSpace)
} else {
needHorizontalSpace = _max(needPaneHorizontalSpace > 0 ? needPaneHorizontalSpace : 0, needHorizontalSpace);
needVerticalSpace += needPaneVerticalSpace > 0 ? needPaneVerticalSpace : 0
}
});
return needHorizontalSpace > 0 || needVerticalSpace > 0 ? {
width: needHorizontalSpace,
height: needVerticalSpace
} : false
},
layoutElements: function(elements, canvas, funcAxisDrawer, panes, rotated) {
this._elements = elements;
this._probeDrawing(canvas);
this._drawElements(canvas);
funcAxisDrawer();
this._processAdaptiveLayout(panes, rotated, canvas, funcAxisDrawer);
this._positionElements(canvas)
},
_processAdaptiveLayout: function(panes, rotated, canvas, funcAxisDrawer) {
var that = this,
size = that.needMoreSpaceForPanesCanvas(panes, rotated),
items = this._elements;
if (!size) {
return
}
function processCanvases(item, layoutOptions, side) {
if (!item.getLayoutOptions()[side]) {
canvas[layoutOptions.cutLayoutSide] -= layoutOptions[side];
size[side] = size[side] - layoutOptions[side]
}
}
items.slice().reverse().forEach(function(item) {
var sizeObject, cutSide, layoutOptions = item.getLayoutOptions(),
needRedraw = false;
if (!layoutOptions) {
return
}
sizeObject = extend({}, layoutOptions);
needRedraw = "vertical" === layoutOptions.cutSide && size.width < 0 || "horizontal" === layoutOptions.cutSide && size.height < 0 || "vertical" === layoutOptions.cutSide && size.height > 0 || "horizontal" === layoutOptions.cutSide && size.width > 0;
cutSide = "horizontal" === layoutOptions.cutSide ? "width" : "height";
if (needRedraw) {
var width = sizeObject.width - size.width;
var height = sizeObject.height - size.height;
if ("height" === cutSide && size.width < 0) {
width = canvas.width - canvas.left - canvas.right
}
if ("width" === cutSide && size.height < 0) {
height = canvas.height - canvas.top - canvas.bottom
}
item.draw(width, height)
}
processCanvases(item, layoutOptions, cutSide)
});
funcAxisDrawer(size)
},
_probeDrawing: function(canvas) {
var that = this;
this._elements.forEach(function(item) {
var sizeObject, layoutOptions = item.getLayoutOptions();
if (!layoutOptions) {
return
}
sizeObject = {
width: canvas.width - canvas.left - canvas.right,
height: canvas.height - canvas.top - canvas.bottom
};
if ("vertical" === layoutOptions.cutSide) {
sizeObject.height -= that._options.height
} else {
sizeObject.width -= that._options.width
}
item.probeDraw(sizeObject.width, sizeObject.height);
downSize(canvas, item.getLayoutOptions())
})
},
_drawElements: function(canvas) {
this._elements.slice().reverse().forEach(function(item) {
var sizeObject, cutSide, length, layoutOptions = item.getLayoutOptions();
if (!layoutOptions) {
return
}
sizeObject = {
width: canvas.width - canvas.left - canvas.right,
height: canvas.height - canvas.top - canvas.bottom
};
cutSide = layoutOptions.cutSide;
length = "horizontal" === cutSide ? "width" : "height";
sizeObject[length] = layoutOptions[length];
item.draw(sizeObject.width, sizeObject.height)
})
},
_positionElements: function(canvas) {
var offsets = {
left: 0,
right: 0,
top: 0,
bottom: 0
};
this._elements.slice().reverse().forEach(function(item) {
var position, cutSide, my, layoutOptions = item.getLayoutOptions();
if (!layoutOptions) {
return
}
position = layoutOptions.position;
cutSide = layoutOptions.cutSide;
my = {
horizontal: position.horizontal,
vertical: position.vertical
};
my[cutSide] = inverseAlign[my[cutSide]];
item.position({ of: toLayoutElementCoords(canvas),
my: my,
at: position,
offset: getOffset(layoutOptions, offsets)
});
offsets[layoutOptions.cutLayoutSide] += layoutOptions["horizontal" === layoutOptions.cutSide ? "width" : "height"]
})
}
};
exports.LayoutManager = LayoutManager;