]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Refactor core.layouts (#6304)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Wed, 19 Jun 2019 22:08:07 +0000 (01:08 +0300)
committerEvert Timberg <evert.timberg@gmail.com>
Wed, 19 Jun 2019 22:08:07 +0000 (18:08 -0400)
src/core/core.layouts.js
src/core/core.scale.js
src/scales/scale.category.js
src/scales/scale.linear.js
src/scales/scale.logarithmic.js
src/scales/scale.time.js
test/specs/core.layouts.tests.js
test/specs/scale.linear.tests.js
test/specs/scale.time.tests.js

index 4a0969f62ba4c85be559254a1ecc8ffb89b5690f..c91bf6e701ca98a02a0cde2eac64ed8b56410f23 100644 (file)
 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);
                });
        }
 };
index 7d14be21c2427bb97420ec253d1de9ba5b5e5a1e..298d409aa8ee76e93e868c6e605e83f35a4b006e 100644 (file)
@@ -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;
index bd01587369c45dc4dff5a5b92535bdd55e2bd06e..4cb9422230d130c464794afa6f22c047563ed722 100644 (file)
@@ -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) {
index c9ce8709dedea84dc794455e85a60790d1fb947f..a76f4593c118817345102d78713faa1a0fac32b5 100644 (file)
@@ -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]);
        }
 });
 
index 3e9b2849f0b7adf075ed8f422aaca13df0ed8dde..1229e8d40a32a4b11c2c7539edd42efec50f951e 100644 (file)
@@ -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]);
        },
 
        /**
index 10dd50e4f0227e21275b7b72592128f7e42cd8de..e0e12f52bf2db28c4c164f359a25a6f0951869ee 100644 (file)
@@ -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--;
index bca7f08dca6b30068db0ee984a210ebe0041eef8..21a31c8975cbfd8e62276aad00c11d5e9f32c7e3 100644 (file)
@@ -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() {
index bdf8a9386f9bed84025547ecfe8bcc7e303ef89f..383d7480d44b903d0290af8f559eec17605403f0 100644 (file)
@@ -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);
index 39a920ccd26c110c1eeda69aa0746d54d067f4c4..a380253fa308ea0a4e6de4e996680355dfd9d014 100755 (executable)
@@ -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]]']);
                });
        });