var helpers = Chart.helpers;
+ function filterByPosition(array, position) {
+ return helpers.where(array, function(v) {
+ return v.position === position;
+ });
+ }
+
+ function sortByWeight(array, reverse) {
+ array.forEach(function(v, i) {
+ v._tmpIndex_ = i;
+ return v;
+ });
+ array.sort(function(a, b) {
+ var v0 = reverse ? b : a;
+ var v1 = reverse ? a : b;
+ return v0.weight === v1.weight ?
+ v0._tmpIndex_ - v1._tmpIndex_ :
+ v0.weight - v1.weight;
+ });
+ array.forEach(function(v) {
+ delete v._tmpIndex_;
+ });
+ }
+
+ /**
+ * @interface ILayoutItem
+ * @prop {String} position - The position of the item in the chart layout. Possible values are
+ * 'left', 'top', 'right', 'bottom', and 'chartArea'
+ * @prop {Number} weight - The weight used to sort the item. Higher weights are further away from the chart area
+ * @prop {Boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
+ * @prop {Function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
+ * @prop {Function} update - Takes two parameters: width and height. Returns size of item
+ * @prop {Function} getPadding - Returns an object with padding on the edges
+ * @prop {Number} width - Width of item. Must be valid after update()
+ * @prop {Number} height - Height of item. Must be valid after update()
+ * @prop {Number} left - Left edge of the item. Set by layout system and cannot be used in update
+ * @prop {Number} top - Top edge of the item. Set by layout system and cannot be used in update
+ * @prop {Number} right - Right edge of the item. Set by layout system and cannot be used in update
+ * @prop {Number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
+ */
+
// The layout service is very self explanatory. It's responsible for the layout within a chart.
// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
// It is this service's responsibility of carrying out that layout.
Chart.layoutService = {
defaults: {},
- // Register a box to a chart. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins.
- addBox: function(chart, box) {
+ /**
+ * Register a box to a chart.
+ * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
+ * @param {Chart} chart - the chart to use
+ * @param {ILayoutItem} layoutItem - the item to add to be layed out
+ */
+ addBox: function(chart, layoutItem) {
if (!chart.boxes) {
chart.boxes = [];
}
- chart.boxes.push(box);
+
+ // Ensure that all layout items have a weight
+ if (!layoutItem.weight) {
+ layoutItem.weight = 0;
+ }
+ chart.boxes.push(layoutItem);
},
- removeBox: function(chart, box) {
+ /**
+ * Remove a layoutItem from a chart
+ * @param {Chart} chart - the chart to remove the box from
+ * @param {Object} layoutItem - the item to remove from the layout
+ */
+ removeBox: function(chart, layoutItem) {
if (!chart.boxes) {
return;
}
- chart.boxes.splice(chart.boxes.indexOf(box), 1);
+ chart.boxes.splice(chart.boxes.indexOf(layoutItem), 1);
},
- // The most important function
+ /**
+ * Fits boxes of the given chart into the given size by having each box measure itself
+ * then running a fitting algorithm
+ * @param {Chart} chart - the chart
+ * @param {Number} width - the width to fit into
+ * @param {Number} height - the height to fit into
+ */
update: function(chart, width, height) {
-
if (!chart) {
return;
}
bottomPadding = padding.bottom || 0;
}
- var leftBoxes = helpers.where(chart.boxes, function(box) {
- return box.options.position === 'left';
- });
- var rightBoxes = helpers.where(chart.boxes, function(box) {
- return box.options.position === 'right';
- });
- var topBoxes = helpers.where(chart.boxes, function(box) {
- return box.options.position === 'top';
- });
- var bottomBoxes = helpers.where(chart.boxes, function(box) {
- return box.options.position === 'bottom';
- });
-
- // Boxes that overlay the chartarea such as the radialLinear scale
- var chartAreaBoxes = helpers.where(chart.boxes, function(box) {
- return box.options.position === 'chartArea';
- });
+ var leftBoxes = filterByPosition(chart.boxes, 'left');
+ var rightBoxes = filterByPosition(chart.boxes, 'right');
+ var topBoxes = filterByPosition(chart.boxes, 'top');
+ var bottomBoxes = filterByPosition(chart.boxes, 'bottom');
+ var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea');
- // Ensure that full width boxes are at the very top / bottom
- topBoxes.sort(function(a, b) {
- return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0);
- });
- bottomBoxes.sort(function(a, b) {
- return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0);
- });
+ // Sort boxes by weight. A higher weight is further away from the chart area
+ sortByWeight(leftBoxes, true);
+ sortByWeight(rightBoxes, false);
+ sortByWeight(topBoxes, true);
+ sortByWeight(bottomBoxes, false);
// Essentially we now have any number of boxes on each of the 4 sides.
// Our canvas looks like the following.
var isHorizontal = box.isHorizontal();
if (isHorizontal) {
- minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
+ minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight);
maxChartAreaHeight -= minSize.height;
} else {
minSize = box.update(verticalBoxWidth, chartAreaHeight);
// Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
// on the margin. Sometimes they need to increase in size slightly
- box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
+ box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
} else {
box.update(minBoxSize.minSize.width, maxChartAreaHeight);
}
});
helpers.each(topBoxes, function(box) {
- if (!box.options.fullWidth) {
+ if (!box.fullWidth) {
box.width = newMaxChartAreaWidth;
}
});
helpers.each(bottomBoxes, function(box) {
- if (!box.options.fullWidth) {
+ if (!box.fullWidth) {
box.width = newMaxChartAreaWidth;
}
});
function placeBox(box) {
if (box.isHorizontal()) {
- box.left = box.options.fullWidth ? leftPadding : totalLeftBoxesWidth;
- box.right = box.options.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth;
+ box.left = box.fullWidth ? leftPadding : totalLeftBoxesWidth;
+ box.right = box.fullWidth ? width - rightPadding : totalLeftBoxesWidth + maxChartAreaWidth;
box.top = top;
box.bottom = top + box.height;