Skip to content

Commit

Permalink
Layout service supports a new order setting to configure how boxes ar…
Browse files Browse the repository at this point in the history
…e ordered on left and right edges
  • Loading branch information
etimberg committed Feb 25, 2017
1 parent 2ddd5ff commit 2f0947c
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 39 deletions.
120 changes: 83 additions & 37 deletions src/core/core.layoutService.js
Expand Up @@ -4,30 +4,90 @@ module.exports = function(Chart) {

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;
}
Expand All @@ -53,31 +113,17 @@ module.exports = function(Chart) {
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.
Expand Down Expand Up @@ -138,7 +184,7 @@ module.exports = function(Chart) {
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);
Expand Down Expand Up @@ -201,7 +247,7 @@ module.exports = function(Chart) {

// 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);
}
Expand Down Expand Up @@ -297,13 +343,13 @@ module.exports = function(Chart) {
});

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;
}
});
Expand All @@ -318,8 +364,8 @@ module.exports = function(Chart) {

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;

Expand Down
8 changes: 7 additions & 1 deletion src/core/core.legend.js
Expand Up @@ -495,7 +495,13 @@ module.exports = function(Chart) {
var legend = new Chart.Legend({
ctx: chart.ctx,
options: legendOpts,
chart: chart
chart: chart,

// ILayoutItem parameters for layout service
// pick a large number to ensure we are on the outside after any axes
weight: 1000,
position: legendOpts.position,
fullWidth: legendOpts.fullWidth,
});
chart.legend = legend;
Chart.layoutService.addBox(chart, legend);
Expand Down
3 changes: 3 additions & 0 deletions src/core/core.scaleService.js
Expand Up @@ -33,6 +33,9 @@ module.exports = function(Chart) {
addScalesToLayout: function(chart) {
// Adds each scale to the chart.boxes array to be sized accordingly
helpers.each(chart.scales, function(scale) {
// Set ILayoutItem parameters for backwards compatibility
scale.fullWidth = scale.options.fullWidth;
scale.position = scale.options.position;
Chart.layoutService.addBox(chart, scale);
});
}
Expand Down
7 changes: 6 additions & 1 deletion src/core/core.title.js
Expand Up @@ -185,7 +185,12 @@ module.exports = function(Chart) {
var title = new Chart.Title({
ctx: chart.ctx,
options: titleOpts,
chart: chart
chart: chart,

// ILayoutItem parameters
weight: 2000, // greater than legend to be above
position: titleOpts.position,
fullWidth: titleOpts.fullWidth,
});
chart.titleBlock = title;
Chart.layoutService.addBox(chart, title);
Expand Down

0 comments on commit 2f0947c

Please sign in to comment.