diff --git a/src/core/core.layouts.js b/src/core/core.layouts.js index 4a0969f62ba..c91bf6e701c 100644 --- a/src/core/core.layouts.js +++ b/src/core/core.layouts.js @@ -3,55 +3,193 @@ var defaults = require('./core.defaults'); var helpers = require('../helpers/index'); +var extend = helpers.extend; + function filterByPosition(array, position) { return helpers.where(array, function(v) { - return v.position === position; + return v.pos === position; }); } function sortByWeight(array, reverse) { - array.forEach(function(v, i) { - v._tmpIndex_ = i; - return v; - }); - array.sort(function(a, b) { + return 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.index - v1.index : v0.weight - v1.weight; }); - array.forEach(function(v) { - delete v._tmpIndex_; - }); } -function findMaxPadding(boxes) { - var top = 0; - var left = 0; - var bottom = 0; - var right = 0; - helpers.each(boxes, function(box) { - if (box.getPadding) { - var boxPadding = box.getPadding(); - top = Math.max(top, boxPadding.top); - left = Math.max(left, boxPadding.left); - bottom = Math.max(bottom, boxPadding.bottom); - right = Math.max(right, boxPadding.right); - } - }); +function wrapBoxes(boxes) { + var layoutBoxes = []; + var i, ilen, box; + + for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { + box = boxes[i]; + layoutBoxes.push({ + index: i, + box: box, + pos: box.position, + horizontal: box.isHorizontal(), + weight: box.weight + }); + } + return layoutBoxes; +} + +function setLayoutDims(layouts, params) { + var i, ilen, layout; + for (i = 0, ilen = layouts.length; i < ilen; ++i) { + layout = layouts[i]; + // store width used instead of chartArea.w in fitBoxes + layout.width = layout.horizontal + ? layout.box.fullWidth && params.availableWidth + : params.vBoxMaxWidth; + // store height used instead of chartArea.h in fitBoxes + layout.height = layout.horizontal && params.hBoxMaxHeight; + } +} + +function buildLayoutBoxes(boxes) { + var layoutBoxes = wrapBoxes(boxes); + var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); + var right = sortByWeight(filterByPosition(layoutBoxes, 'right')); + var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); + var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); + return { - top: top, - left: left, - bottom: bottom, - right: right + leftAndTop: left.concat(top), + rightAndBottom: right.concat(bottom), + chartArea: filterByPosition(layoutBoxes, 'chartArea'), + vertical: left.concat(right), + horizontal: top.concat(bottom) }; } -function addSizeByPosition(boxes, size) { - helpers.each(boxes, function(box) { - size[box.position] += box.isHorizontal() ? box.height : box.width; - }); +function getCombinedMax(maxPadding, chartArea, a, b) { + return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); +} + +function updateDims(chartArea, params, layout) { + var box = layout.box; + var maxPadding = chartArea.maxPadding; + var newWidth, newHeight; + + if (layout.size) { + // this layout was already counted for, lets first reduce old size + chartArea[layout.pos] -= layout.size; + } + layout.size = layout.horizontal ? box.height : box.width; + chartArea[layout.pos] += layout.size; + + if (box.getPadding) { + var boxPadding = box.getPadding(); + maxPadding.top = Math.max(maxPadding.top, boxPadding.top); + maxPadding.left = Math.max(maxPadding.left, boxPadding.left); + maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); + maxPadding.right = Math.max(maxPadding.right, boxPadding.right); + } + + newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'); + newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'); + + if (newWidth !== chartArea.w || newHeight !== chartArea.h) { + chartArea.w = newWidth; + chartArea.h = newHeight; + + // return true if chart area changed in layout's direction + return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h; + } +} + +function handleMaxPadding(chartArea) { + var maxPadding = chartArea.maxPadding; + + function updatePos(pos) { + var change = Math.max(maxPadding[pos] - chartArea[pos], 0); + chartArea[pos] += change; + return change; + } + chartArea.y += updatePos('top'); + chartArea.x += updatePos('left'); + updatePos('right'); + updatePos('bottom'); +} + +function getMargins(horizontal, chartArea) { + var maxPadding = chartArea.maxPadding; + + function marginForPositions(positions) { + var margin = {left: 0, top: 0, right: 0, bottom: 0}; + positions.forEach(function(pos) { + margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); + }); + return margin; + } + + return horizontal + ? marginForPositions(['left', 'right']) + : marginForPositions(['top', 'bottom']); +} + +function fitBoxes(boxes, chartArea, params) { + var refitBoxes = []; + var i, ilen, layout, box, refit, changed; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + + box.update( + layout.width || chartArea.w, + layout.height || chartArea.h, + getMargins(layout.horizontal, chartArea) + ); + if (updateDims(chartArea, params, layout)) { + changed = true; + if (refitBoxes.length) { + // Dimensions changed and there were non full width boxes before this + // -> we have to refit those + refit = true; + } + } + if (!box.fullWidth) { // fullWidth boxes don't need to be re-fitted in any case + refitBoxes.push(layout); + } + } + + return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed; +} + +function placeBoxes(boxes, chartArea, params) { + var userPadding = params.padding; + var x = chartArea.x; + var y = chartArea.y; + var i, ilen, layout, box; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + if (layout.horizontal) { + box.left = box.fullWidth ? userPadding.left : chartArea.left; + box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w; + box.top = y; + box.bottom = y + box.height; + box.width = box.right - box.left; + y = box.bottom; + } else { + box.left = x; + box.right = x + box.width; + box.top = chartArea.top; + box.bottom = chartArea.top + chartArea.h; + box.height = box.bottom - box.top; + x = box.right; + } + } + + chartArea.x = x; + chartArea.y = y; } defaults._set('global', { @@ -161,26 +299,12 @@ module.exports = { var layoutOptions = chart.options.layout || {}; var padding = helpers.options.toPadding(layoutOptions.padding); - var leftPadding = padding.left; - var rightPadding = padding.right; - var topPadding = padding.top; - var bottomPadding = padding.bottom; - - 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'); - - // 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); - - var verticalBoxes = leftBoxes.concat(rightBoxes); - var horizontalBoxes = topBoxes.concat(bottomBoxes); - var outerBoxes = verticalBoxes.concat(horizontalBoxes); + + var availableWidth = width - padding.width; + var availableHeight = height - padding.height; + var boxes = buildLayoutBoxes(chart.boxes); + var verticalBoxes = boxes.vertical; + var horizontalBoxes = boxes.horizontal; // Essentially we now have any number of boxes on each of the 4 sides. // Our canvas looks like the following. @@ -208,201 +332,57 @@ module.exports = { // | 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 - leftPadding - rightPadding; - var chartHeight = height - topPadding - bottomPadding; - var chartAreaWidth = chartWidth / 2; // min 50% - - // Step 2 - var verticalBoxWidth = (width - chartAreaWidth) / verticalBoxes.length; - - // Step 3 - // TODO re-limit horizontal axis height (this limit has affected only padding calculation since PR 1837) - // var horizontalBoxHeight = (height - chartAreaHeight) / horizontalBoxes.length; - - // Step 4 - var maxChartAreaWidth = chartWidth; - var maxChartAreaHeight = chartHeight; - var outerBoxSizes = {top: topPadding, left: leftPadding, bottom: bottomPadding, right: rightPadding}; - var minBoxSizes = []; - var maxPadding; - - function getMinimumBoxSize(box) { - var minSize; - var isHorizontal = box.isHorizontal(); - - if (isHorizontal) { - minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2); - maxChartAreaHeight -= minSize.height; - } else { - minSize = box.update(verticalBoxWidth, maxChartAreaHeight); - maxChartAreaWidth -= minSize.width; - } - - minBoxSizes.push({ - horizontal: isHorizontal, - width: minSize.width, - box: box, - }); - } - - helpers.each(outerBoxes, getMinimumBoxSize); - - // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478) - maxPadding = findMaxPadding(outerBoxes); - - // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could - // be if the axes are drawn at their minimum sizes. - // Steps 5 & 6 - - // Function to fit a box - function fitBox(box) { - var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBox) { - return minBox.box === box; - }); - - if (minBoxSize) { - if (minBoxSize.horizontal) { - var scaleMargin = { - left: Math.max(outerBoxSizes.left, maxPadding.left), - right: Math.max(outerBoxSizes.right, maxPadding.right), - top: 0, - bottom: 0 - }; - - // 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.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); - } else { - box.update(minBoxSize.width, maxChartAreaHeight); - } - } - } - - // Update, and calculate the left and right margins for the horizontal boxes - helpers.each(verticalBoxes, fitBox); - addSizeByPosition(verticalBoxes, outerBoxSizes); - - // Set the Left and Right margins for the horizontal boxes - helpers.each(horizontalBoxes, fitBox); - addSizeByPosition(horizontalBoxes, outerBoxSizes); - - function finalFitVerticalBox(box) { - var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minSize) { - return minSize.box === box; - }); - - var scaleMargin = { - left: 0, - right: 0, - top: outerBoxSizes.top, - bottom: outerBoxSizes.bottom - }; - - if (minBoxSize) { - box.update(minBoxSize.width, maxChartAreaHeight, scaleMargin); - } - } - - // Let the left layout know the final margin - helpers.each(verticalBoxes, finalFitVerticalBox); - - // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) - outerBoxSizes = {top: topPadding, left: leftPadding, bottom: bottomPadding, right: rightPadding}; - addSizeByPosition(outerBoxes, outerBoxSizes); - - // We may be adding some padding to account for rotated x axis labels - var leftPaddingAddition = Math.max(maxPadding.left - outerBoxSizes.left, 0); - outerBoxSizes.left += leftPaddingAddition; - outerBoxSizes.right += Math.max(maxPadding.right - outerBoxSizes.right, 0); - - var topPaddingAddition = Math.max(maxPadding.top - outerBoxSizes.top, 0); - outerBoxSizes.top += topPaddingAddition; - outerBoxSizes.bottom += Math.max(maxPadding.bottom - outerBoxSizes.bottom, 0); - - // 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 newMaxChartAreaHeight = height - outerBoxSizes.top - outerBoxSizes.bottom; - var newMaxChartAreaWidth = width - outerBoxSizes.left - outerBoxSizes.right; - - if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { - helpers.each(verticalBoxes, function(box) { - box.height = newMaxChartAreaHeight; - }); - - helpers.each(horizontalBoxes, function(box) { - if (!box.fullWidth) { - box.width = newMaxChartAreaWidth; - } - }); - maxChartAreaHeight = newMaxChartAreaHeight; - maxChartAreaWidth = newMaxChartAreaWidth; + var params = Object.freeze({ + outerWidth: width, + outerHeight: height, + padding: padding, + availableWidth: availableWidth, + vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length, + hBoxMaxHeight: availableHeight / 2 + }); + var chartArea = extend({ + maxPadding: extend({}, padding), + w: availableWidth, + h: availableHeight, + x: padding.left, + y: padding.top + }, padding); + + setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); + + // First fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + + // Then fit horizontal boxes + if (fitBoxes(horizontalBoxes, chartArea, params)) { + // if the area changed, re-fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); } - // Step 7 - Position the boxes - var left = leftPadding + leftPaddingAddition; - var top = topPadding + topPaddingAddition; - - function placeBox(box) { - if (box.isHorizontal()) { - box.left = box.fullWidth ? leftPadding : outerBoxSizes.left; - box.right = box.fullWidth ? width - rightPadding : outerBoxSizes.left + maxChartAreaWidth; - box.top = top; - box.bottom = top + box.height; - - // Move to next point - top = box.bottom; + handleMaxPadding(chartArea, params); - } else { + // Finally place the boxes to correct coordinates + placeBoxes(boxes.leftAndTop, chartArea, params); - box.left = left; - box.right = left + box.width; - box.top = outerBoxSizes.top; - box.bottom = outerBoxSizes.top + maxChartAreaHeight; + // Move to opposite side of chart + chartArea.x += chartArea.w; + chartArea.y += chartArea.h; - // Move to next point - left = box.right; - } - } + placeBoxes(boxes.rightAndBottom, chartArea, params); - helpers.each(leftBoxes.concat(topBoxes), placeBox); - - // Account for chart width and height - left += maxChartAreaWidth; - top += maxChartAreaHeight; - - helpers.each(rightBoxes, placeBox); - helpers.each(bottomBoxes, placeBox); - - // Step 8 chart.chartArea = { - left: outerBoxSizes.left, - top: outerBoxSizes.top, - right: outerBoxSizes.left + maxChartAreaWidth, - bottom: outerBoxSizes.top + maxChartAreaHeight + left: chartArea.left, + top: chartArea.top, + right: chartArea.left + chartArea.w, + bottom: chartArea.top + chartArea.h }; - // Step 9 - helpers.each(chartAreaBoxes, function(box) { - box.left = chart.chartArea.left; - box.top = chart.chartArea.top; - box.right = chart.chartArea.right; - box.bottom = chart.chartArea.bottom; - - box.update(maxChartAreaWidth, maxChartAreaHeight); + // Finally update boxes in chartArea (radial scale for example) + helpers.each(boxes.chartArea, function(layout) { + var box = layout.box; + extend(box, chart.chartArea); + box.update(chartArea.w, chartArea.h); }); } }; diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 7d14be21c24..298d409aa8e 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -476,8 +476,7 @@ var Scale = Element.extend({ // Width if (isHorizontal) { - // subtract the margins to line up with the chartArea if we are a full width scale - minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth; + minSize.width = me.maxWidth; } else if (display) { minSize.width = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); } @@ -565,10 +564,10 @@ var Scale = Element.extend({ handleMargins: function() { var me = this; if (me.margins) { - me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0); - me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0); - me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0); - me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0); + me.margins.left = Math.max(me.paddingLeft, me.margins.left); + me.margins.top = Math.max(me.paddingTop, me.margins.top); + me.margins.right = Math.max(me.paddingRight, me.margins.right); + me.margins.bottom = Math.max(me.paddingBottom, me.margins.bottom); } }, @@ -679,21 +678,21 @@ var Scale = Element.extend({ getPixelForTick: function(index) { var me = this; var offset = me.options.offset; + var numTicks = me._ticks.length; + if (index < 0 || index > numTicks - 1) { + return null; + } if (me.isHorizontal()) { - var innerWidth = me.width - (me.paddingLeft + me.paddingRight); - var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1); - var pixel = (tickWidth * index) + me.paddingLeft; + var tickWidth = me.width / Math.max((numTicks - (offset ? 0 : 1)), 1); + var pixel = (tickWidth * index); if (offset) { pixel += tickWidth / 2; } - var finalVal = me.left + pixel; - finalVal += me.isFullWidth() ? me.margins.left : 0; - return finalVal; + return me.left + pixel; } - var innerHeight = me.height - (me.paddingTop + me.paddingBottom); - return me.top + (index * (innerHeight / (me._ticks.length - 1))); + return me.top + (index * (me.height / (numTicks - 1))); }, /** @@ -702,15 +701,9 @@ var Scale = Element.extend({ */ getPixelForDecimal: function(decimal) { var me = this; - if (me.isHorizontal()) { - var innerWidth = me.width - (me.paddingLeft + me.paddingRight); - var valueOffset = (innerWidth * decimal) + me.paddingLeft; - - var finalVal = me.left + valueOffset; - finalVal += me.isFullWidth() ? me.margins.left : 0; - return finalVal; - } - return me.top + (decimal * me.height); + return me.isHorizontal() + ? me.left + decimal * me.width + : me.top + decimal * me.height; }, /** @@ -749,9 +742,7 @@ var Scale = Element.extend({ var ticksLength = me._tickSize() * (tickCount - 1); // Axis length - var axisLength = isHorizontal - ? me.width - (me.paddingLeft + me.paddingRight) - : me.height - (me.paddingTop + me.PaddingBottom); + var axisLength = isHorizontal ? me.width : me.height; var result = []; var i, tick; diff --git a/src/scales/scale.category.js b/src/scales/scale.category.js index bd01587369c..4cb9422230d 100644 --- a/src/scales/scale.category.js +++ b/src/scales/scale.category.js @@ -101,7 +101,11 @@ module.exports = Scale.extend({ }, getPixelForTick: function(index) { - return this.getPixelForValue(this.ticks[index], index + this.minIndex); + var ticks = this.ticks; + if (index < 0 || index > ticks.length - 1) { + return null; + } + return this.getPixelForValue(ticks[index], index + this.minIndex); }, getValueForPixel: function(pixel) { diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index c9ce8709ded..a76f4593c11 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -184,7 +184,11 @@ module.exports = LinearScaleBase.extend({ }, getPixelForTick: function(index) { - return this.getPixelForValue(this.ticksAsNumbers[index]); + var ticks = this.ticksAsNumbers; + if (index < 0 || index > ticks.length - 1) { + return null; + } + return this.getPixelForValue(ticks[index]); } }); diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index 3e9b2849f0b..1229e8d40a3 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -247,7 +247,11 @@ module.exports = Scale.extend({ }, getPixelForTick: function(index) { - return this.getPixelForValue(this.tickValues[index]); + var ticks = this.tickValues; + if (index < 0 || index > ticks.length - 1) { + return null; + } + return this.getPixelForValue(ticks[index]); }, /** diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 10dd50e4f02..e0e12f52bf2 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -774,7 +774,7 @@ module.exports = Scale.extend({ var me = this; var ticksOpts = me.options.ticks; var tickLabelWidth = me.ctx.measureText(label).width; - var angle = helpers.toRadians(ticksOpts.maxRotation); + var angle = helpers.toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation); var cosRotation = Math.cos(angle); var sinRotation = Math.sin(angle); var tickFontSize = valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize); @@ -800,19 +800,12 @@ module.exports = Scale.extend({ var me = this; var timeOpts = me.options.time; var displayFormats = timeOpts.displayFormats; - var margins = me.margins; // pick the longest format (milliseconds) for guestimation var format = displayFormats[timeOpts.unit] || displayFormats.millisecond; var exampleLabel = me.tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format); var size = me._getLabelSize(exampleLabel); - - // Using margins instead of padding because padding is not calculated - // at this point (buildTicks). Margins are provided from previous calculation - // in layout steps 5/6 - var capacity = Math.floor(me.isHorizontal() - ? (me.width - margins.left - margins.right) / size.w - : (me.height - margins.top - margins.bottom) / size.h); + var capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h); if (me.options.offset) { capacity--; diff --git a/test/specs/core.layouts.tests.js b/test/specs/core.layouts.tests.js index bca7f08dca6..21a31c8975c 100644 --- a/test/specs/core.layouts.tests.js +++ b/test/specs/core.layouts.tests.js @@ -9,10 +9,7 @@ describe('Chart.layouts', function() { expect(Chart.layouts.update).toBeDefined(); }); - // Disable tests which need to be rewritten based on changes introduced by - // the following changes: https://github.com/chartjs/Chart.js/pull/2346 - // using xit marks the test as pending: https://jasmine.github.io/2.0/introduction.html#section-Pending_Specs - xit('should fit a simple chart with 2 scales', function() { + it('should fit a simple chart with 2 scales', function() { var chart = window.acquireChart({ type: 'bar', data: { @@ -40,27 +37,27 @@ describe('Chart.layouts', function() { } }); - expect(chart.chartArea.bottom).toBeCloseToPixel(112); - expect(chart.chartArea.left).toBeCloseToPixel(41); - expect(chart.chartArea.right).toBeCloseToPixel(250); + expect(chart.chartArea.bottom).toBeCloseToPixel(120); + expect(chart.chartArea.left).toBeCloseToPixel(34); + expect(chart.chartArea.right).toBeCloseToPixel(247); expect(chart.chartArea.top).toBeCloseToPixel(32); // Is xScale at the right spot expect(chart.scales.xScale.bottom).toBeCloseToPixel(150); - expect(chart.scales.xScale.left).toBeCloseToPixel(41); - expect(chart.scales.xScale.right).toBeCloseToPixel(250); - expect(chart.scales.xScale.top).toBeCloseToPixel(112); - expect(chart.scales.xScale.labelRotation).toBeCloseTo(25); + expect(chart.scales.xScale.left).toBeCloseToPixel(34); + expect(chart.scales.xScale.right).toBeCloseToPixel(247); + expect(chart.scales.xScale.top).toBeCloseToPixel(120); + expect(chart.scales.xScale.labelRotation).toBeCloseTo(0); // Is yScale at the right spot - expect(chart.scales.yScale.bottom).toBeCloseToPixel(112); + expect(chart.scales.yScale.bottom).toBeCloseToPixel(120); expect(chart.scales.yScale.left).toBeCloseToPixel(0); - expect(chart.scales.yScale.right).toBeCloseToPixel(41); + expect(chart.scales.yScale.right).toBeCloseToPixel(34); expect(chart.scales.yScale.top).toBeCloseToPixel(32); expect(chart.scales.yScale.labelRotation).toBeCloseTo(0); }); - xit('should fit scales that are in the top and right positions', function() { + it('should fit scales that are in the top and right positions', function() { var chart = window.acquireChart({ type: 'bar', data: { @@ -90,26 +87,94 @@ describe('Chart.layouts', function() { } }); - expect(chart.chartArea.bottom).toBeCloseToPixel(150); - expect(chart.chartArea.left).toBeCloseToPixel(0); - expect(chart.chartArea.right).toBeCloseToPixel(209); - expect(chart.chartArea.top).toBeCloseToPixel(71); + expect(chart.chartArea.bottom).toBeCloseToPixel(142); + expect(chart.chartArea.left).toBeCloseToPixel(3); + expect(chart.chartArea.right).toBeCloseToPixel(216); + expect(chart.chartArea.top).toBeCloseToPixel(62); // Is xScale at the right spot - expect(chart.scales.xScale.bottom).toBeCloseToPixel(71); - expect(chart.scales.xScale.left).toBeCloseToPixel(0); - expect(chart.scales.xScale.right).toBeCloseToPixel(209); + expect(chart.scales.xScale.bottom).toBeCloseToPixel(62); + expect(chart.scales.xScale.left).toBeCloseToPixel(3); + expect(chart.scales.xScale.right).toBeCloseToPixel(216); expect(chart.scales.xScale.top).toBeCloseToPixel(32); - expect(chart.scales.xScale.labelRotation).toBeCloseTo(25); + expect(chart.scales.xScale.labelRotation).toBeCloseTo(0); // Is yScale at the right spot - expect(chart.scales.yScale.bottom).toBeCloseToPixel(150); - expect(chart.scales.yScale.left).toBeCloseToPixel(209); + expect(chart.scales.yScale.bottom).toBeCloseToPixel(142); + expect(chart.scales.yScale.left).toBeCloseToPixel(216); expect(chart.scales.yScale.right).toBeCloseToPixel(250); - expect(chart.scales.yScale.top).toBeCloseToPixel(71); + expect(chart.scales.yScale.top).toBeCloseToPixel(62); expect(chart.scales.yScale.labelRotation).toBeCloseTo(0); }); + it('should fit scales with long labels correctly', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [ + {data: [10, 5, 0, 25, 78, -10]} + ], + labels: ['tick1 is very long one', 'tick2', 'tick3', 'tick4', 'tick5', 'tick6 is very long one'] + }, + options: { + legend: { + display: false + }, + scales: { + xAxes: [{ + id: 'xScale', + type: 'category', + ticks: { + maxRotation: 0, + autoSkip: false + } + }], + yAxes: [{ + id: 'yScale', + type: 'linear', + position: 'right' + }] + } + } + }, { + canvas: { + height: 150, + width: 512 + } + }); + + expect(chart.chartArea.bottom).toBeCloseToPixel(120); + expect(chart.chartArea.left).toBeCloseToPixel(60); + expect(chart.chartArea.right).toBeCloseToPixel(452); + expect(chart.chartArea.top).toBeCloseToPixel(7); + + // Is xScale at the right spot + expect(chart.scales.xScale.bottom).toBeCloseToPixel(150); + expect(chart.scales.xScale.left).toBeCloseToPixel(60); + expect(chart.scales.xScale.right).toBeCloseToPixel(452); + expect(chart.scales.xScale.top).toBeCloseToPixel(120); + expect(chart.scales.xScale.labelRotation).toBeCloseTo(0); + + expect(chart.scales.xScale.height).toBeCloseToPixel(30); + expect(chart.scales.xScale.paddingLeft).toBeCloseToPixel(60); + expect(chart.scales.xScale.paddingTop).toBeCloseToPixel(0); + expect(chart.scales.xScale.paddingRight).toBeCloseToPixel(60); + expect(chart.scales.xScale.paddingBottom).toBeCloseToPixel(0); + + // Is yScale at the right spot + expect(chart.scales.yScale.bottom).toBeCloseToPixel(120); + expect(chart.scales.yScale.left).toBeCloseToPixel(452); + expect(chart.scales.yScale.right).toBeCloseToPixel(486); + expect(chart.scales.yScale.top).toBeCloseToPixel(7); + expect(chart.scales.yScale.labelRotation).toBeCloseTo(0); + + expect(chart.scales.yScale.width).toBeCloseToPixel(34); + expect(chart.scales.yScale.paddingLeft).toBeCloseToPixel(0); + expect(chart.scales.yScale.paddingTop).toBeCloseToPixel(7); + expect(chart.scales.yScale.paddingRight).toBeCloseToPixel(0); + expect(chart.scales.yScale.paddingBottom).toBeCloseToPixel(7); + }); + it('should fit scales that overlap the chart area', function() { var chart = window.acquireChart({ type: 'radar', @@ -136,7 +201,7 @@ describe('Chart.layouts', function() { expect(chart.scale.height).toBeCloseToPixel(480); }); - xit('should fit multiple axes in the same position', function() { + it('should fit multiple axes in the same position', function() { var chart = window.acquireChart({ type: 'bar', data: { @@ -171,33 +236,33 @@ describe('Chart.layouts', function() { } }); - expect(chart.chartArea.bottom).toBeCloseToPixel(102); - expect(chart.chartArea.left).toBeCloseToPixel(86); - expect(chart.chartArea.right).toBeCloseToPixel(250); + expect(chart.chartArea.bottom).toBeCloseToPixel(118); + expect(chart.chartArea.left).toBeCloseToPixel(73); + expect(chart.chartArea.right).toBeCloseToPixel(247); expect(chart.chartArea.top).toBeCloseToPixel(32); // Is xScale at the right spot expect(chart.scales.xScale.bottom).toBeCloseToPixel(150); - expect(chart.scales.xScale.left).toBeCloseToPixel(86); - expect(chart.scales.xScale.right).toBeCloseToPixel(250); - expect(chart.scales.xScale.top).toBeCloseToPixel(103); - expect(chart.scales.xScale.labelRotation).toBeCloseTo(50); + expect(chart.scales.xScale.left).toBeCloseToPixel(73); + expect(chart.scales.xScale.right).toBeCloseToPixel(247); + expect(chart.scales.xScale.top).toBeCloseToPixel(118); + expect(chart.scales.xScale.labelRotation).toBeCloseTo(40, -1); // Are yScales at the right spot - expect(chart.scales.yScale1.bottom).toBeCloseToPixel(102); - expect(chart.scales.yScale1.left).toBeCloseToPixel(0); - expect(chart.scales.yScale1.right).toBeCloseToPixel(41); + expect(chart.scales.yScale1.bottom).toBeCloseToPixel(118); + expect(chart.scales.yScale1.left).toBeCloseToPixel(41); + expect(chart.scales.yScale1.right).toBeCloseToPixel(73); expect(chart.scales.yScale1.top).toBeCloseToPixel(32); expect(chart.scales.yScale1.labelRotation).toBeCloseTo(0); - expect(chart.scales.yScale2.bottom).toBeCloseToPixel(102); - expect(chart.scales.yScale2.left).toBeCloseToPixel(41); - expect(chart.scales.yScale2.right).toBeCloseToPixel(86); + expect(chart.scales.yScale2.bottom).toBeCloseToPixel(118); + expect(chart.scales.yScale2.left).toBeCloseToPixel(0); + expect(chart.scales.yScale2.right).toBeCloseToPixel(41); expect(chart.scales.yScale2.top).toBeCloseToPixel(32); expect(chart.scales.yScale2.labelRotation).toBeCloseTo(0); }); - xit ('should fix a full width box correctly', function() { + it ('should fit a full width box correctly', function() { var chart = window.acquireChart({ type: 'bar', data: { @@ -230,17 +295,17 @@ describe('Chart.layouts', function() { }); expect(chart.chartArea.bottom).toBeCloseToPixel(484); - expect(chart.chartArea.left).toBeCloseToPixel(45); - expect(chart.chartArea.right).toBeCloseToPixel(512); - expect(chart.chartArea.top).toBeCloseToPixel(60); + expect(chart.chartArea.left).toBeCloseToPixel(40); + expect(chart.chartArea.right).toBeCloseToPixel(496); + expect(chart.chartArea.top).toBeCloseToPixel(62); // Are xScales at the right spot expect(chart.scales.xScale1.bottom).toBeCloseToPixel(512); - expect(chart.scales.xScale1.left).toBeCloseToPixel(45); - expect(chart.scales.xScale1.right).toBeCloseToPixel(512); + expect(chart.scales.xScale1.left).toBeCloseToPixel(40); + expect(chart.scales.xScale1.right).toBeCloseToPixel(496); expect(chart.scales.xScale1.top).toBeCloseToPixel(484); - expect(chart.scales.xScale2.bottom).toBeCloseToPixel(60); + expect(chart.scales.xScale2.bottom).toBeCloseToPixel(62); expect(chart.scales.xScale2.left).toBeCloseToPixel(0); expect(chart.scales.xScale2.right).toBeCloseToPixel(512); expect(chart.scales.xScale2.top).toBeCloseToPixel(32); @@ -248,8 +313,8 @@ describe('Chart.layouts', function() { // Is yScale at the right spot expect(chart.scales.yScale.bottom).toBeCloseToPixel(484); expect(chart.scales.yScale.left).toBeCloseToPixel(0); - expect(chart.scales.yScale.right).toBeCloseToPixel(45); - expect(chart.scales.yScale.top).toBeCloseToPixel(60); + expect(chart.scales.yScale.right).toBeCloseToPixel(40); + expect(chart.scales.yScale.top).toBeCloseToPixel(62); }); describe('padding settings', function() { diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index bdf8a9386f9..383d7480d44 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -907,16 +907,16 @@ describe('Linear Scale', function() { }); var xScale = chart.scales.xScale0; + var yScale = chart.scales.yScale0; expect(xScale.paddingTop).toBeCloseToPixel(0); expect(xScale.paddingBottom).toBeCloseToPixel(0); - expect(xScale.paddingLeft).toBeCloseToPixel(0); - expect(xScale.paddingRight).toBeCloseToPixel(0); + expect(xScale.paddingLeft).toBeCloseToPixel(12); + expect(xScale.paddingRight).toBeCloseToPixel(13.5); expect(xScale.width).toBeCloseToPixel(468 - 6); // minus lineSpace expect(xScale.height).toBeCloseToPixel(30); - var yScale = chart.scales.yScale0; - expect(yScale.paddingTop).toBeCloseToPixel(0); - expect(yScale.paddingBottom).toBeCloseToPixel(0); + expect(yScale.paddingTop).toBeCloseToPixel(7); + expect(yScale.paddingBottom).toBeCloseToPixel(7); expect(yScale.paddingLeft).toBeCloseToPixel(0); expect(yScale.paddingRight).toBeCloseToPixel(0); expect(yScale.width).toBeCloseToPixel(30 + 6); // plus lineSpace @@ -929,13 +929,13 @@ describe('Linear Scale', function() { expect(xScale.paddingTop).toBeCloseToPixel(0); expect(xScale.paddingBottom).toBeCloseToPixel(0); - expect(xScale.paddingLeft).toBeCloseToPixel(0); - expect(xScale.paddingRight).toBeCloseToPixel(0); + expect(xScale.paddingLeft).toBeCloseToPixel(12); + expect(xScale.paddingRight).toBeCloseToPixel(13.5); expect(xScale.width).toBeCloseToPixel(440); expect(xScale.height).toBeCloseToPixel(53); - expect(yScale.paddingTop).toBeCloseToPixel(0); - expect(yScale.paddingBottom).toBeCloseToPixel(0); + expect(yScale.paddingTop).toBeCloseToPixel(7); + expect(yScale.paddingBottom).toBeCloseToPixel(7); expect(yScale.paddingLeft).toBeCloseToPixel(0); expect(yScale.paddingRight).toBeCloseToPixel(0); expect(yScale.width).toBeCloseToPixel(58); diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 39a920ccd26..a380253fa30 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -686,6 +686,11 @@ describe('Time scale tests', function() { xAxes: [{ id: 'xScale0', type: 'time', + time: { + displayFormats: { + second: 'h:mm:ss' + } + }, ticks: { callback: function(value) { return '<' + value + '>'; @@ -703,8 +708,8 @@ describe('Time scale tests', function() { expect(scale._ticks.map(function(tick) { return tick.major; - })).toEqual([true, false, false, false, true]); - expect(scale.ticks).toEqual(['<8:00:00 pm>', '<8:00:15 pm>', '<8:00:30 pm>', '<8:00:45 pm>', '<8:01:00 pm>']); + })).toEqual([true, false, false, false, false, false, true]); + expect(scale.ticks).toEqual(['<8:00:00>', '<8:00:10>', '<8:00:20>', '<8:00:30>', '<8:00:40>', '<8:00:50>', '<8:01:00>']); }); it('should update ticks.callback correctly', function() { @@ -715,7 +720,7 @@ describe('Time scale tests', function() { return '{' + value + '}'; }; chart.update(); - expect(scale.ticks).toEqual(['{8:00:00 pm}', '{8:00:15 pm}', '{8:00:30 pm}', '{8:00:45 pm}', '{8:01:00 pm}']); + expect(scale.ticks).toEqual(['{8:00:00}', '{8:00:10}', '{8:00:20}', '{8:00:30}', '{8:00:40}', '{8:00:50}', '{8:01:00}']); }); }); @@ -763,8 +768,8 @@ describe('Time scale tests', function() { expect(scale._ticks.map(function(tick) { return tick.major; - })).toEqual([true, false, false, false, true]); - expect(scale.ticks).toEqual(['[[8:00 pm]]', '(8:00:15 pm)', '(8:00:30 pm)', '(8:00:45 pm)', '[[8:01 pm]]']); + })).toEqual([true, false, false, false, false, false, true]); + expect(scale.ticks).toEqual(['[[8:00 pm]]', '(8:00:10 pm)', '(8:00:20 pm)', '(8:00:30 pm)', '(8:00:40 pm)', '(8:00:50 pm)', '[[8:01 pm]]']); }); it('should only use ticks.minor callback if ticks.major.enabled is false', function() { @@ -773,7 +778,7 @@ describe('Time scale tests', function() { chart.options.scales.xAxes[0].ticks.major.enabled = false; chart.update(); - expect(scale.ticks).toEqual(['(8:00:00 pm)', '(8:00:15 pm)', '(8:00:30 pm)', '(8:00:45 pm)', '(8:01:00 pm)']); + expect(scale.ticks).toEqual(['(8:00:00 pm)', '(8:00:10 pm)', '(8:00:20 pm)', '(8:00:30 pm)', '(8:00:40 pm)', '(8:00:50 pm)', '(8:01:00 pm)']); }); it('should use ticks.callback if ticks.major.callback is omitted', function() { @@ -782,7 +787,7 @@ describe('Time scale tests', function() { chart.options.scales.xAxes[0].ticks.major.callback = undefined; chart.update(); - expect(scale.ticks).toEqual(['<8:00 pm>', '(8:00:15 pm)', '(8:00:30 pm)', '(8:00:45 pm)', '<8:01 pm>']); + expect(scale.ticks).toEqual(['<8:00 pm>', '(8:00:10 pm)', '(8:00:20 pm)', '(8:00:30 pm)', '(8:00:40 pm)', '(8:00:50 pm)', '<8:01 pm>']); }); it('should use ticks.callback if ticks.minor.callback is omitted', function() { @@ -791,7 +796,7 @@ describe('Time scale tests', function() { chart.options.scales.xAxes[0].ticks.minor.callback = undefined; chart.update(); - expect(scale.ticks).toEqual(['[[8:00 pm]]', '<8:00:15 pm>', '<8:00:30 pm>', '<8:00:45 pm>', '[[8:01 pm]]']); + expect(scale.ticks).toEqual(['[[8:00 pm]]', '<8:00:10 pm>', '<8:00:20 pm>', '<8:00:30 pm>', '<8:00:40 pm>', '<8:00:50 pm>', '[[8:01 pm]]']); }); });