From a91dba3e8a713993003d598032158359c98b5d1c Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Mon, 26 Oct 2015 13:57:59 -0600 Subject: [PATCH] 50% Canvas Legend Support --- samples/line-legend.html | 196 +++++++++++++++++++ src/core/core.controller.js | 28 ++- src/core/core.layoutService.js | 347 +++++++++++++++++++++++++++++++++ src/core/core.legend.js | 271 +++++++++++++++++++++++++ src/core/core.scaleService.js | 311 +---------------------------- 5 files changed, 844 insertions(+), 309 deletions(-) create mode 100644 samples/line-legend.html create mode 100644 src/core/core.layoutService.js create mode 100644 src/core/core.legend.js 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); + }); + }, };