diff --git a/samples/line-legend.html b/samples/line-legend.html
new file mode 100644
index 000000000..6373cb5f2
--- /dev/null
+++ b/samples/line-legend.html
@@ -0,0 +1,196 @@
+
+
+
+
+ Line Chart
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/core/core.controller.js b/src/core/core.controller.js
index 915ae8512..18593955a 100644
--- a/src/core/core.controller.js
+++ b/src/core/core.controller.js
@@ -54,6 +54,8 @@
this.ensureScalesHaveIDs();
this.buildOrUpdateControllers();
this.buildScales();
+ this.buildLegends();
+ this.updateLayout();
this.resetElements();
this.initToolTip();
this.update();
@@ -161,7 +163,25 @@
this.scales.radialScale = scale;
}
- Chart.scaleService.update(this, this.chart.width, this.chart.height);
+ Chart.scaleService.addScalesToLayout(this);
+ },
+
+ buildLegends: function() {
+ if (!this.options.legend) {
+ return;
+ }
+
+ this.legend = new Chart.Legend({
+ ctx: this.chart.ctx,
+ options: this.options.legend,
+ chart: this,
+ });
+
+ Chart.layoutService.addBox(this, this.legend);
+ },
+
+ updateLayout: function() {
+ Chart.layoutService.update(this, this.chart.width, this.chart.height);
},
buildOrUpdateControllers: function buildOrUpdateControllers(resetNewControllers) {
@@ -199,7 +219,7 @@
},
update: function update(animationDuration, lazy) {
- Chart.scaleService.update(this, this.chart.width, this.chart.height);
+ Chart.layoutService.update(this, this.chart.width, this.chart.height);
// Make sure dataset controllers are updated and new controllers are reset
this.buildOrUpdateControllers(true);
@@ -251,8 +271,8 @@
this.clear();
// Draw all the scales
- helpers.each(this.scales, function(scale) {
- scale.draw(this.chartArea);
+ helpers.each(this.boxes, function(box) {
+ box.draw(this.chartArea);
}, this);
if (this.scale) {
this.scale.draw();
diff --git a/src/core/core.layoutService.js b/src/core/core.layoutService.js
new file mode 100644
index 000000000..66399e335
--- /dev/null
+++ b/src/core/core.layoutService.js
@@ -0,0 +1,347 @@
+(function() {
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+ // The layout service is ver 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 chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins.
+ addBox: function(chartInstance, box) {
+ if (!chartInstance.boxes) {
+ chartInstance.boxes = [];
+ }
+ chartInstance.boxes.push(box);
+ },
+
+ removeBox: function(chartInstance, box) {
+ if (!chartInstance.boxes) {
+ return;
+ }
+ chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1);
+ },
+
+ // The most important function
+ update: function(chartInstance, width, height) {
+
+ if (!chartInstance) {
+ return;
+ }
+
+ var xPadding = width > 30 ? 5 : 2;
+ var yPadding = height > 30 ? 5 : 2;
+
+ var leftBoxes = helpers.where(chartInstance.boxes, function(box) {
+ return box.options.position == "left";
+ });
+ var rightBoxes = helpers.where(chartInstance.boxes, function(box) {
+ return box.options.position == "right";
+ });
+ var topBoxes = helpers.where(chartInstance.boxes, function(box) {
+ return box.options.position == "top";
+ });
+ var bottomBoxes = helpers.where(chartInstance.boxes, function(box) {
+ return box.options.position == "bottom";
+ });
+
+ // Boxes that overlay the chartarea such as the radialLinear scale
+ var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) {
+ return box.options.position == "chartArea";
+ });
+
+ // Essentially we now have any number of boxes on each of the 4 sides.
+ // Our canvas looks like the following.
+ // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
+ // B1 is the bottom axis
+ // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
+ // These locations are single-box locations only, when trying to register a chartArea location that is already taken,
+ // an error will be thrown.
+ //
+ // |----------------------------------------------------|
+ // | T1 (Full Width) |
+ // |----------------------------------------------------|
+ // | | | T2 | |
+ // | |----|-------------------------------------|----|
+ // | | | C1 | | C2 | |
+ // | | |----| |----| |
+ // | | | | |
+ // | L1 | L2 | ChartArea (C0) | R1 |
+ // | | | | |
+ // | | |----| |----| |
+ // | | | C3 | | C4 | |
+ // | |----|-------------------------------------|----|
+ // | | | B1 | |
+ // |----------------------------------------------------|
+ // | B2 (Full Width) |
+ // |----------------------------------------------------|
+ //
+ // What we do to find the best sizing, we do the following
+ // 1. Determine the minimum size of the chart area.
+ // 2. Split the remaining width equally between each vertical axis
+ // 3. Split the remaining height equally between each horizontal axis
+ // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
+ // 5. Adjust the sizes of each axis based on it's minimum reported size.
+ // 6. Refit each axis
+ // 7. Position each axis in the final location
+ // 8. Tell the chart the final location of the chart area
+ // 9. Tell any axes that overlay the chart area the positions of the chart area
+
+ // Step 1
+ var chartWidth = width / 2; // min 50%
+ var chartHeight = height / 2; // min 50%
+
+ chartWidth -= (2 * xPadding);
+ chartHeight -= (2 * yPadding);
+
+ // Step 2
+ var verticalBoxWidth = (width - chartWidth) / (leftBoxes.length + rightBoxes.length);
+
+ // Step 3
+ var horizontalBoxHeight = (height - chartHeight) / (topBoxes.length + bottomBoxes.length);
+
+ // Step 4;
+ var minBoxSizes = [];
+
+ // vertical boxes
+ helpers.each(leftBoxes, verticalBoxMinSizeFunction);
+ helpers.each(rightBoxes, verticalBoxMinSizeFunction);
+
+ function verticalBoxMinSizeFunction(box) {
+ var minSize = box.update(verticalBoxWidth, chartHeight);
+ minBoxSizes.push({
+ horizontal: false,
+ minSize: minSize,
+ box: box,
+ });
+ }
+
+ // horizontal boxes
+ helpers.each(topBoxes, horizontalBoxMinSizeFunction);
+ helpers.each(bottomBoxes, horizontalBoxMinSizeFunction);
+
+ function horizontalBoxMinSizeFunction(box) {
+ var minSize = box.update(chartWidth, horizontalBoxHeight);
+ minBoxSizes.push({
+ horizontal: true,
+ minSize: minSize,
+ box: box,
+ });
+ }
+
+ // Step 5
+ var maxChartHeight = height - (2 * yPadding);
+ var maxChartWidth = width - (2 * xPadding);
+
+ helpers.each(minBoxSizes, function(minimumBoxSize) {
+ if (minimumBoxSize.horizontal) {
+ maxChartHeight -= minimumBoxSize.minSize.height;
+ } else {
+ maxChartWidth -= minimumBoxSize.minSize.width;
+ }
+ });
+
+ // At this point, maxChartHeight and maxChartWidth are the size the chart area could
+ // be if the axes are drawn at their minimum sizes.
+
+ // Step 6
+ var totalLeftWidth = xPadding;
+ var totalRightWidth = xPadding;
+ var totalTopHeight = yPadding;
+ var totalBottomHeight = yPadding;
+
+ helpers.each(leftBoxes, verticalBoxFitFunction);
+ helpers.each(rightBoxes, verticalBoxFitFunction);
+
+ function verticalBoxFitFunction(box) {
+ var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
+ return minBoxSize.box === box;
+ });
+
+ if (minBoxSize) {
+ box.update(minBoxSize.minSize.width, maxChartHeight);
+ }
+ }
+
+ // Figure out how much margin is on the left and right of the horizontal axes
+ helpers.each(leftBoxes, function(box) {
+ totalLeftWidth += box.width;
+ });
+
+ helpers.each(rightBoxes, function(box) {
+ totalRightWidth += box.width;
+ });
+
+ helpers.each(topBoxes, horizontalBoxFitFunction);
+ helpers.each(bottomBoxes, horizontalBoxFitFunction);
+
+ function horizontalBoxFitFunction(box) {
+ var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) {
+ return minBoxSize.box === box;
+ });
+
+ var scaleMargin = {
+ left: totalLeftWidth,
+ right: totalRightWidth,
+ top: 0,
+ bottom: 0,
+ };
+
+ if (minBoxSize) {
+ box.update(maxChartWidth, minBoxSize.minSize.height, scaleMargin);
+ }
+ }
+
+ helpers.each(topBoxes, function(box) {
+ totalTopHeight += box.height;
+ });
+
+ helpers.each(bottomBoxes, function(box) {
+ totalBottomHeight += box.height;
+ });
+
+ // Let the left layout know the final margin
+ helpers.each(leftBoxes, function(box) {
+ var wrapper = helpers.findNextWhere(minBoxSizes, function(wrapper) {
+ return wrapper.box === box;
+ });
+
+ var scaleMargin = {
+ left: 0,
+ right: 0,
+ top: totalTopHeight,
+ bottom: totalBottomHeight
+ };
+
+ if (wrapper) {
+ box.update(wrapper.minSize.width, maxChartHeight, scaleMargin);
+ }
+ });
+
+ helpers.each(rightBoxes, function(box) {
+ var wrapper = helpers.findNextWhere(minBoxSizes, function(wrapper) {
+ return wrapper.box === box;
+ });
+
+ var scaleMargin = {
+ left: 0,
+ right: 0,
+ top: totalTopHeight,
+ bottom: totalBottomHeight
+ };
+
+ if (wrapper) {
+ box.update(wrapper.minSize.width, maxChartHeight, scaleMargin);
+ }
+ });
+
+ // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
+ totalLeftWidth = xPadding;
+ totalRightWidth = xPadding;
+ totalTopHeight = yPadding;
+ totalBottomHeight = yPadding;
+
+ helpers.each(leftBoxes, function(box) {
+ totalLeftWidth += box.width;
+ });
+
+ helpers.each(rightBoxes, function(box) {
+ totalRightWidth += box.width;
+ });
+
+ helpers.each(topBoxes, function(box) {
+ totalTopHeight += box.height;
+ });
+ helpers.each(bottomBoxes, function(box) {
+ totalBottomHeight += box.height;
+ });
+
+ // Figure out if our chart area changed. This would occur if the dataset layout label rotation
+ // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
+ // without calling `fit` again
+ var newMaxChartHeight = height - totalTopHeight - totalBottomHeight;
+ var newMaxChartWidth = width - totalLeftWidth - totalRightWidth;
+
+ if (newMaxChartWidth !== maxChartWidth || newMaxChartHeight !== maxChartHeight) {
+ helpers.each(leftBoxes, function(box) {
+ box.height = newMaxChartHeight;
+ });
+
+ helpers.each(rightBoxes, function(box) {
+ box.height = newMaxChartHeight;
+ });
+
+ helpers.each(topBoxes, function(box) {
+ box.width = newMaxChartWidth;
+ });
+
+ helpers.each(bottomBoxes, function(box) {
+ box.width = newMaxChartWidth;
+ });
+
+ maxChartHeight = newMaxChartHeight;
+ maxChartWidth = newMaxChartWidth;
+ }
+
+ // Step 7 - Position the boxes
+ var left = xPadding;
+ var top = yPadding;
+ var right = 0;
+ var bottom = 0;
+
+ helpers.each(leftBoxes, verticalBoxPlacer);
+ helpers.each(topBoxes, horizontalBoxPlacer);
+
+ // Account for chart width and height
+ left += maxChartWidth;
+ top += maxChartHeight;
+
+ helpers.each(rightBoxes, verticalBoxPlacer);
+ helpers.each(bottomBoxes, horizontalBoxPlacer);
+
+ function verticalBoxPlacer(box) {
+ box.left = left;
+ box.right = left + box.width;
+ box.top = totalTopHeight;
+ box.bottom = totalTopHeight + maxChartHeight;
+
+ // Move to next point
+ left = box.right;
+ }
+
+ function horizontalBoxPlacer(box) {
+ box.left = totalLeftWidth;
+ box.right = totalLeftWidth + maxChartWidth;
+ box.top = top;
+ box.bottom = top + box.height;
+
+ // Move to next point
+ top = box.bottom;
+ }
+
+ // Step 8
+ chartInstance.chartArea = {
+ left: totalLeftWidth,
+ top: totalTopHeight,
+ right: totalLeftWidth + maxChartWidth,
+ bottom: totalTopHeight + maxChartHeight,
+ };
+
+ // Step 9
+ helpers.each(chartAreaBoxes, function(box) {
+ box.left = chartInstance.chartArea.left;
+ box.top = chartInstance.chartArea.top;
+ box.right = chartInstance.chartArea.right;
+ box.bottom = chartInstance.chartArea.bottom;
+
+ box.update(maxChartWidth, maxChartHeight);
+ });
+ }
+ };
+
+
+}).call(this);
diff --git a/src/core/core.legend.js b/src/core/core.legend.js
new file mode 100644
index 000000000..28e3194f5
--- /dev/null
+++ b/src/core/core.legend.js
@@ -0,0 +1,271 @@
+(function() {
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+ Chart.defaults.legend = {
+
+ display: true,
+ position: 'top',
+ onClick: false, // a callback will override the default behavior of toggling the datasets
+
+ title: {
+ position: 'top',
+ fontColor: '#666',
+ fontFamily: 'Helvetica Neue',
+ fontSize: 12,
+ fontStyle: 'bold',
+ padding: 10,
+
+ // actual title
+ text: '',
+
+ // display property
+ display: false,
+ },
+
+ labels: {
+ fontSize: 12,
+ fontStyle: "normal",
+ fontColor: "#666",
+ fontFamily: "Helvetica Neue",
+ padding: 10,
+ reverse: false,
+ display: true,
+ callback: function(value) {
+ return '' + value;
+ },
+ },
+ };
+
+ Chart.Legend = Chart.Element.extend({
+
+ initialize: function(config) {
+ helpers.extend(this, config);
+ this.options = helpers.configMerge(Chart.defaults.legend, config.options);
+ },
+
+ // These methods are ordered by lifecyle. Utilities then follow.
+ // Any function defined here is inherited by all legend types.
+ // Any function can be extended by the legend type
+
+ beforeUpdate: helpers.noop,
+ update: function(maxWidth, maxHeight, margins) {
+
+ // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
+ this.beforeUpdate();
+
+ // Absorb the master measurements
+ this.maxWidth = maxWidth;
+ this.maxHeight = maxHeight;
+ this.margins = margins;
+
+ // Dimensions
+ this.beforeSetDimensions();
+ this.setDimensions();
+ this.afterSetDimensions();
+ // Labels
+ this.beforeBuildLabels();
+ this.buildLabels();
+ this.afterBuildLabels();
+
+ // Fit
+ this.beforeFit();
+ this.fit();
+ this.afterFit();
+ //
+ this.afterUpdate();
+
+ return this.minSize;
+
+ },
+ afterUpdate: helpers.noop,
+
+ //
+
+ beforeSetDimensions: helpers.noop,
+ setDimensions: function() {
+ // Set the unconstrained dimension before label rotation
+ if (this.isHorizontal()) {
+ // Reset position before calculating rotation
+ this.width = this.maxWidth;
+ this.left = 0;
+ this.right = this.width;
+ } else {
+ this.height = this.maxHeight;
+
+ // Reset position before calculating rotation
+ this.top = 0;
+ this.bottom = this.height;
+ }
+
+ // Reset padding
+ this.paddingLeft = 0;
+ this.paddingTop = 0;
+ this.paddingRight = 0;
+ this.paddingBottom = 0;
+ },
+ afterSetDimensions: helpers.noop,
+
+ //
+
+ beforeBuildLabels: helpers.noop,
+ buildLabels: function() {
+ // Convert ticks to strings
+ this.labels = this.chart.data.datasets.map(function(dataset) {
+ return this.options.labels.callback.call(this, dataset.label);
+ }, this);
+ },
+ afterBuildLabels: helpers.noop,
+
+ //
+
+ beforeFit: helpers.noop,
+ fit: function() {
+
+ var ctx = this.ctx;
+ var titleFont = helpers.fontString(this.options.title.fontSize, this.options.title.fontStyle, this.options.title.fontFamily);
+ var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily);
+
+ this.minSize = {
+ width: 0,
+ height: 0,
+ };
+
+ // Legends can use all available space by default as no alignment to the chartArea is necessary
+
+ // Width
+ if (this.isHorizontal()) {
+ this.minSize.width = this.maxWidth; // fill all the width
+ } else {
+ this.minSize.width = this.options.display ? 10 : 0;
+ }
+
+ // height
+ if (this.isHorizontal()) {
+ this.minSize.height = this.options.display ? 10 : 0;
+ } else {
+ this.minSize.height = this.maxHeight; // fill all the height
+ }
+
+ // Increase sizes here
+ if (this.isHorizontal()) {
+
+ // Title
+ if (this.options.title.display) {
+ this.minSize.height += this.options.title.fontSize + (this.options.title.padding * 2);
+ }
+
+ // Labels
+
+ var totalWidth = 0;
+ var totalHeight = this.labels.length ? this.options.labels.fontSize + (this.options.labels.padding) : 0;
+
+ ctx.font = labelFont;
+
+ helpers.each(this.labels, function(label, i) {
+ var width = ctx.measureText(label).width;
+ if (totalWidth + width > this.width) {
+ totalHeight += this.options.labels.fontSize + (this.options.labels.padding);
+ totalWidth = 0;
+ }
+ totalWidth += width + this.options.labels.padding;
+ }, this);
+
+ this.minSize.height += totalHeight;
+
+ } else {
+ // TODO vertical
+ }
+
+ this.width = this.minSize.width;
+ this.height = this.minSize.height;
+
+ },
+ afterFit: helpers.noop,
+
+ // Shared Methods
+ isHorizontal: function() {
+ return this.options.position == "top" || this.options.position == "bottom";
+ },
+
+ // Actualy draw the legend on the canvas
+ draw: function() {
+ if (this.options.display) {
+
+ var ctx = this.ctx;
+ var cursor = {
+ x: this.left,
+ y: this.top,
+ };
+
+ // Horizontal
+ if (this.isHorizontal()) {
+
+ // Title Spacing if on top
+ if (this.options.title.display && this.options.title.position == 'top') {
+ cursor.y += this.options.title.fontSize + (this.options.title.padding * 2);
+ }
+
+ // Labels
+ ctx.textAlign = "left";
+ ctx.textBaseline = 'top';
+ ctx.fillStyle = this.options.labels.fontColor; // render in correct colour
+ ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily);
+
+ helpers.each(this.labels, function(label, i) {
+ var width = ctx.measureText(label).width;
+ if (cursor.x + width > this.width) {
+ cursor.y += this.options.labels.fontSize + (this.options.labels.padding);
+ cursor.x = this.left;
+ }
+ ctx.fillText(label, cursor.x, cursor.y);
+ cursor.x += width + (this.options.labels.padding);
+ }, this);
+
+
+ // Title
+ if (this.options.title.display) {
+
+ ctx.textAlign = "center";
+ ctx.textBaseline = 'middle';
+ ctx.fillStyle = this.options.title.fontColor; // render in correct colour
+ ctx.font = helpers.fontString(this.options.title.fontSize, this.options.title.fontStyle, this.options.title.fontFamily);
+
+ var titleX = this.left + ((this.right - this.left) / 2); // midpoint of the width
+ var titleY = this.options.position == 'bottom' ? this.bottom - (this.options.title.fontSize / 2) - this.options.title.padding : this.top + (this.options.title.fontSize / 2) + this.options.title.padding;
+
+ ctx.fillText(this.options.title.text, titleX, titleY);
+ }
+
+
+ } else {
+
+ // Title
+ if (this.options.title.display) {
+
+ // Draw the legend label
+ titleX = this.options.position == 'left' ? this.left + (this.options.title.fontSize / 2) : this.right - (this.options.title.fontSize / 2);
+ titleY = this.top + ((this.bottom - this.top) / 2);
+ var rotation = this.options.position == 'left' ? -0.5 * Math.PI : 0.5 * Math.PI;
+
+ ctx.save();
+ ctx.translate(titleX, titleY);
+ ctx.rotate(rotation);
+ ctx.textAlign = "center";
+ ctx.fillStyle = this.options.title.fontColor; // render in correct colour
+ ctx.font = helpers.fontString(this.options.title.fontSize, this.options.title.fontStyle, this.options.title.fontFamily);
+ ctx.textBaseline = 'middle';
+ ctx.fillText(this.options.title.text, 0, 0);
+ ctx.restore();
+
+ }
+
+ }
+ }
+ }
+ });
+
+}).call(this);
diff --git a/src/core/core.scaleService.js b/src/core/core.scaleService.js
index a1c1f1c20..2100eae7d 100644
--- a/src/core/core.scaleService.js
+++ b/src/core/core.scaleService.js
@@ -5,9 +5,6 @@
Chart = root.Chart,
helpers = Chart.helpers;
- // The scale service is used to resize charts along with all of their axes. We make this as
- // a service where scales are registered with their respective charts so that changing the
- // scales does not require
Chart.scaleService = {
// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
// use the new chart options to grab the correct scale
@@ -28,308 +25,12 @@
// Return the scale defaults merged with the global settings so that we always use the latest ones
return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {};
},
- // The interesting function
- update: function(chartInstance, width, height) {
- var xPadding = width > 30 ? 5 : 2;
- var yPadding = height > 30 ? 5 : 2;
-
- if (chartInstance) {
- var leftScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "left";
- });
- var rightScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "right";
- });
- var topScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "top";
- });
- var bottomScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "bottom";
- });
-
- // Scales that overlay the chartarea such as the radialLinear scale
- var chartAreaScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "chartArea";
- });
-
- // Essentially we now have any number of scales on each of the 4 sides.
- // Our canvas looks like the following.
- // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
- // B1 is the bottom axis
- // |------------------------------------------------------|
- // | | T1 | |
- // |----|-----|-------------------------------------|-----|
- // | | | | |
- // | L1 | L2 | Chart area | R1 |
- // | | | | |
- // | | | | |
- // |----|-----|-------------------------------------|-----|
- // | | B1 | |
- // | | | |
- // |------------------------------------------------------|
-
- // What we do to find the best sizing, we do the following
- // 1. Determine the minimum size of the chart area.
- // 2. Split the remaining width equally between each vertical axis
- // 3. Split the remaining height equally between each horizontal axis
- // 4. Give each scale the maximum size it can be. The scale will return it's minimum size
- // 5. Adjust the sizes of each axis based on it's minimum reported size.
- // 6. Refit each axis
- // 7. Position each axis in the final location
- // 8. Tell the chart the final location of the chart area
- // 9. Tell any axes that overlay the chart area the positions of the chart area
-
- // Step 1
- var chartWidth = width / 2; // min 50%
- var chartHeight = height / 2; // min 50%
-
- chartWidth -= (2 * xPadding);
- chartHeight -= (2 * yPadding);
-
- // Step 2
- var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length);
-
- // Step 3
- var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length);
-
- // Step 4;
- var minimumScaleSizes = [];
-
- var verticalScaleMinSizeFunction = function(scaleInstance) {
- var minSize = scaleInstance.update(verticalScaleWidth, chartHeight);
- minimumScaleSizes.push({
- horizontal: false,
- minSize: minSize,
- scale: scaleInstance,
- });
- };
-
- var horizontalScaleMinSizeFunction = function(scaleInstance) {
- var minSize = scaleInstance.update(chartWidth, horizontalScaleHeight);
- minimumScaleSizes.push({
- horizontal: true,
- minSize: minSize,
- scale: scaleInstance,
- });
- };
-
- // vertical scales
- helpers.each(leftScales, verticalScaleMinSizeFunction);
- helpers.each(rightScales, verticalScaleMinSizeFunction);
-
- // horizontal scales
- helpers.each(topScales, horizontalScaleMinSizeFunction);
- helpers.each(bottomScales, horizontalScaleMinSizeFunction);
-
- // Step 5
- var maxChartHeight = height - (2 * yPadding);
- var maxChartWidth = width - (2 * xPadding);
-
- helpers.each(minimumScaleSizes, function(wrapper) {
- if (wrapper.horizontal) {
- maxChartHeight -= wrapper.minSize.height;
- } else {
- maxChartWidth -= wrapper.minSize.width;
- }
- });
-
- // At this point, maxChartHeight and maxChartWidth are the size the chart area could
- // be if the axes are drawn at their minimum sizes.
-
- // Step 6
- var verticalScaleFitFunction = function(scaleInstance) {
- var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
- return wrapper.scale === scaleInstance;
- });
-
- if (wrapper) {
- scaleInstance.update(wrapper.minSize.width, maxChartHeight);
- }
- };
-
- var horizontalScaleFitFunction = function(scaleInstance) {
- var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
- return wrapper.scale === scaleInstance;
- });
-
- var scaleMargin = {
- left: totalLeftWidth,
- right: totalRightWidth,
- top: 0,
- bottom: 0,
- };
-
- if (wrapper) {
- scaleInstance.update(maxChartWidth, wrapper.minSize.height, scaleMargin);
- }
- };
-
- var totalLeftWidth = xPadding;
- var totalRightWidth = xPadding;
- var totalTopHeight = yPadding;
- var totalBottomHeight = yPadding;
-
- helpers.each(leftScales, verticalScaleFitFunction);
- helpers.each(rightScales, verticalScaleFitFunction);
-
- // Figure out how much margin is on the left and right of the horizontal axes
- helpers.each(leftScales, function(scaleInstance) {
- totalLeftWidth += scaleInstance.width;
- });
-
- helpers.each(rightScales, function(scaleInstance) {
- totalRightWidth += scaleInstance.width;
- });
-
- helpers.each(topScales, horizontalScaleFitFunction);
- helpers.each(bottomScales, horizontalScaleFitFunction);
-
- helpers.each(topScales, function(scaleInstance) {
- totalTopHeight += scaleInstance.height;
- });
- helpers.each(bottomScales, function(scaleInstance) {
- totalBottomHeight += scaleInstance.height;
- });
-
- // Let the left scale know the final margin
- helpers.each(leftScales, function(scaleInstance) {
- var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
- return wrapper.scale === scaleInstance;
- });
-
- var scaleMargin = {
- left: 0,
- right: 0,
- top: totalTopHeight,
- bottom: totalBottomHeight
- };
-
- if (wrapper) {
- scaleInstance.update(wrapper.minSize.width, maxChartHeight, scaleMargin);
- }
- });
-
- helpers.each(rightScales, function(scaleInstance) {
- var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
- return wrapper.scale === scaleInstance;
- });
-
- var scaleMargin = {
- left: 0,
- right: 0,
- top: totalTopHeight,
- bottom: totalBottomHeight
- };
-
- if (wrapper) {
- scaleInstance.update(wrapper.minSize.width, maxChartHeight, scaleMargin);
- }
- });
-
- // Recalculate because the size of each scale might have changed slightly due to the margins (label rotation for instance)
- totalLeftWidth = xPadding;
- totalRightWidth = xPadding;
- totalTopHeight = yPadding;
- totalBottomHeight = yPadding;
-
- helpers.each(leftScales, function(scaleInstance) {
- totalLeftWidth += scaleInstance.width;
- });
-
- helpers.each(rightScales, function(scaleInstance) {
- totalRightWidth += scaleInstance.width;
- });
-
- helpers.each(topScales, function(scaleInstance) {
- totalTopHeight += scaleInstance.height;
- });
- helpers.each(bottomScales, function(scaleInstance) {
- totalBottomHeight += scaleInstance.height;
- });
-
- // Figure out if our chart area changed. This would occur if the dataset scale label rotation
- // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
- // without calling `fit` again
- var newMaxChartHeight = height - totalTopHeight - totalBottomHeight;
- var newMaxChartWidth = width - totalLeftWidth - totalRightWidth;
-
- if (newMaxChartWidth !== maxChartWidth || newMaxChartHeight !== maxChartHeight) {
- helpers.each(leftScales, function(scale) {
- scale.height = newMaxChartHeight;
- });
-
- helpers.each(rightScales, function(scale) {
- scale.height = newMaxChartHeight;
- });
-
- helpers.each(topScales, function(scale) {
- scale.width = newMaxChartWidth;
- });
-
- helpers.each(bottomScales, function(scale) {
- scale.width = newMaxChartWidth;
- });
-
- maxChartHeight = newMaxChartHeight;
- maxChartWidth = newMaxChartWidth;
- }
-
- // Step 7
- // Position the scales
- var left = xPadding;
- var top = yPadding;
- var right = 0;
- var bottom = 0;
-
- var verticalScalePlacer = function(scaleInstance) {
- scaleInstance.left = left;
- scaleInstance.right = left + scaleInstance.width;
- scaleInstance.top = totalTopHeight;
- scaleInstance.bottom = totalTopHeight + maxChartHeight;
-
- // Move to next point
- left = scaleInstance.right;
- };
-
- var horizontalScalePlacer = function(scaleInstance) {
- scaleInstance.left = totalLeftWidth;
- scaleInstance.right = totalLeftWidth + maxChartWidth;
- scaleInstance.top = top;
- scaleInstance.bottom = top + scaleInstance.height;
-
- // Move to next point
- top = scaleInstance.bottom;
- };
-
- helpers.each(leftScales, verticalScalePlacer);
- helpers.each(topScales, horizontalScalePlacer);
-
- // Account for chart width and height
- left += maxChartWidth;
- top += maxChartHeight;
-
- helpers.each(rightScales, verticalScalePlacer);
- helpers.each(bottomScales, horizontalScalePlacer);
-
- // Step 8
- chartInstance.chartArea = {
- left: totalLeftWidth,
- top: totalTopHeight,
- right: totalLeftWidth + maxChartWidth,
- bottom: totalTopHeight + maxChartHeight,
- };
-
- // Step 9
- helpers.each(chartAreaScales, function(scaleInstance) {
- scaleInstance.left = chartInstance.chartArea.left;
- scaleInstance.top = chartInstance.chartArea.top;
- scaleInstance.right = chartInstance.chartArea.right;
- scaleInstance.bottom = chartInstance.chartArea.bottom;
-
- scaleInstance.update(maxChartWidth, maxChartHeight);
- });
- }
- }
+ addScalesToLayout: function(chartInstance) {
+ // Adds each scale to the chart.boxes array to be sized accordingly
+ helpers.each(chartInstance.scales, function(scale) {
+ Chart.layoutService.addBox(chartInstance, scale);
+ });
+ },
};