]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Make `offsetGridLines` consistent and add new `offset` scale option (#4545)
authorAkihiko Kusanagi <nagi@nagi-p.com>
Mon, 14 Aug 2017 08:09:33 +0000 (16:09 +0800)
committerSimon Brunel <simonbrunel@users.noreply.github.com>
Mon, 14 Aug 2017 08:09:33 +0000 (10:09 +0200)
Add a new `offset` option to scales to add extra space at edges and remove the `includeOffset` argument from `getPixelForValue()` and `getPixelForTick()`. The bar controller now automatically calculates the bar width to avoid overlaps. When `offsetGridLines` is true, grid lines move to the left by one half of the tick interval, and labels don't move.

18 files changed:
docs/axes/cartesian/README.md
docs/axes/styling.md
docs/charts/bar.md
src/controllers/controller.bar.js
src/controllers/controller.bubble.js
src/controllers/controller.line.js
src/core/core.controller.js
src/core/core.scale.js
src/scales/scale.category.js
src/scales/scale.time.js
test/specs/controller.bar.tests.js
test/specs/controller.line.tests.js
test/specs/core.helpers.tests.js
test/specs/scale.category.tests.js
test/specs/scale.linear.tests.js
test/specs/scale.logarithmic.tests.js
test/specs/scale.radialLinear.tests.js
test/specs/scale.time.tests.js

index 7d27c625b487b9da83757d8c19dd65f9ed64a885..64ce32073c20f3fb1a3268e5994e7986003dfbb8 100644 (file)
@@ -15,6 +15,7 @@ All of the included cartesian axes support a number of common options.
 | -----| ---- | --------| -----------
 | `type` | `String` | | Type of scale being employed. Custom scales can be created and registered with a string key. This allows changing the type of an axis for a chart.
 | `position` | `String` | | Position of the axis in the chart. Possible values are: `'top'`, `'left'`, `'bottom'`, `'right'`
+| `offset` | `Boolean` | `false` | If true, extra space is added to the both edges and the axis is scaled to fit into the chart area. This is set to `true` in the bar chart by default.
 | `id` | `String` | | The ID is used to link datasets and scale axes together. [more...](#axis-id)
 | `gridLines` | `Object` | | Grid line configuration. [more...](../styling.md#grid-line-configuration)
 | `scaleLabel` | `Object` | | Scale title configuration. [more...](../labelling.md#scale-title-configuration)
@@ -101,4 +102,4 @@ var myChart = new Chart(ctx, {
         }
     }
 });
-```
\ No newline at end of file
+```
index 255b779d6e34d0338d43107842fc319c1ccd4ed6..79cb4e9d66f6cf309a7214cd4e70106fd90da556 100644 (file)
@@ -21,7 +21,7 @@ The grid line configuration is nested under the scale configuration in the `grid
 | `zeroLineColor` | Color | `'rgba(0, 0, 0, 0.25)'` | Stroke color of the grid line for the first index (index 0).
 | `zeroLineBorderDash` | `Number[]` | `[]` | Length and spacing of dashes of the grid line for the first index (index 0). See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash)
 | `zeroLineBorderDashOffset` | `Number` | `0` | Offset for line dashes of the grid line for the first index (index 0). See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset)
-| `offsetGridLines` | `Boolean` | `false` | If true, labels are shifted to be between grid lines. This is used in the bar chart and should not generally be used.
+| `offsetGridLines` | `Boolean` | `false` | If true, grid lines will be shifted to be between labels. This is set to `true` in the bar chart by default.
 
 ## Tick Configuration
 The tick configuration is nested under the scale configuration in the `ticks` key. It defines options for the tick marks that are generated by the axis.
index 8dbc1b486025a2f644b4f0d693f8f4c0e34b4040..974cf8a710427e72ab1275e115eba091b9336559 100644 (file)
@@ -93,16 +93,16 @@ The bar chart defines the following configuration options. These options are mer
 
 | Name | Type | Default | Description
 | ---- | ---- | ------- | -----------
-| `barPercentage` | `Number` | `0.9` | Percent (0-1) of the available width each bar should be within the category percentage. 1.0 will take the whole category width and put the bars right next to each other. [more...](#barpercentage-vs-categorypercentage)
-| `categoryPercentage` | `Number` | `0.8` | Percent (0-1) of the available width (the space between the gridlines for small datasets) for each data-point to use for the bars. [more...](#barpercentage-vs-categorypercentage)
-| `barThickness` | `Number` | | Manually set width of each bar in pixels. If not set, the bars are sized automatically using `barPercentage` and `categoryPercentage`;
-| `maxBarThickness` | `Number` | | Set this to ensure that the automatically sized bars are not sized thicker than this. Only works if barThickness is not set (automatic sizing is enabled).
-| `gridLines.offsetGridLines` | `Boolean` | `true` | If true, the bars for a particular data point fall between the grid lines. If false, the grid line will go right down the middle of the bars. [more...](#offsetgridlines)
+| `barPercentage` | `Number` | `0.9` | Percent (0-1) of the available width each bar should be within the category width. 1.0 will take the whole category width and put the bars right next to each other. [more...](#barpercentage-vs-categorypercentage)
+| `categoryPercentage` | `Number` | `0.8` | Percent (0-1) of the available width each category should be within the sample width. [more...](#barpercentage-vs-categorypercentage)
+| `barThickness` | `Number` | | Manually set width of each bar in pixels. If not set, the base sample widths are calculated automatically so that they take the full available widths without overlap. Then, the bars are sized using `barPercentage` and `categoryPercentage`.
+| `maxBarThickness` | `Number` | | Set this to ensure that bars are not sized thicker than this.
+| `gridLines.offsetGridLines` | `Boolean` | `true` | If true, the bars for a particular data point fall between the grid lines. The grid line will move to the left by one half of the tick interval. If false, the grid line will go right down the middle of the bars. [more...](#offsetgridlines)
 
 ### offsetGridLines
-If true, the bars for a particular data point fall between the grid lines. If false, the grid line will go right down the middle of the bars. It is unlikely that this will ever need to be changed in practice. It exists more as a way to reuse the axis code by configuring the existing axis slightly differently.
+If true, the bars for a particular data point fall between the grid lines. The grid line will move to the left by one half of the tick interval, which is the space between the grid lines. If false, the grid line will go right down the middle of the bars. This is set to true for a bar chart while false for other charts by default.
 
-This setting applies to the axis configuration for a bar chart. If axes are added to the chart, this setting will need to be set for each new axis.
+This setting applies to the axis configuration. If axes are added to the chart, this setting will need to be set for each new axis.
 
 ```javascript
 options = {
index 9c2eb2a1afde1c70cf669c7b6064ceb8a67fa840..baea1d94975ecd417ee00c9aa3a674ed5a52dfe8 100644 (file)
@@ -17,6 +17,9 @@ defaults._set('bar', {
                        categoryPercentage: 0.8,
                        barPercentage: 0.9,
 
+                       // offset settings
+                       offset: true,
+
                        // grid line settings
                        gridLines: {
                                offsetGridLines: true
@@ -49,6 +52,9 @@ defaults._set('horizontalBar', {
                        categoryPercentage: 0.8,
                        barPercentage: 0.9,
 
+                       // offset settings
+                       offset: true,
+
                        // grid line settings
                        gridLines: {
                                offsetGridLines: true
@@ -234,26 +240,23 @@ module.exports = function(Chart) {
                getRuler: function() {
                        var me = this;
                        var scale = me.getIndexScale();
-                       var options = scale.options;
                        var stackCount = me.getStackCount();
-                       var fullSize = scale.isHorizontal() ? scale.width : scale.height;
-                       var tickSize = fullSize / scale.getTicks().length;
-                       var categorySize = tickSize * options.categoryPercentage;
-                       var fullBarSize = categorySize / stackCount;
-                       var barSize = fullBarSize * options.barPercentage;
-
-                       barSize = Math.min(
-                               helpers.valueOrDefault(options.barThickness, barSize),
-                               helpers.valueOrDefault(options.maxBarThickness, Infinity));
+                       var datasetIndex = me.index;
+                       var pixels = [];
+                       var isHorizontal = scale.isHorizontal();
+                       var start = isHorizontal ? scale.left : scale.top;
+                       var end = start + (isHorizontal ? scale.width : scale.height);
+                       var i, ilen;
+
+                       for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
+                               pixels.push(scale.getPixelForValue(null, i, datasetIndex));
+                       }
 
                        return {
+                               pixels: pixels,
+                               start: start,
+                               end: end,
                                stackCount: stackCount,
-                               tickSize: tickSize,
-                               categorySize: categorySize,
-                               categorySpacing: tickSize - categorySize,
-                               fullBarSize: fullBarSize,
-                               barSize: barSize,
-                               barSpacing: fullBarSize - barSize,
                                scale: scale
                        };
                },
@@ -308,16 +311,45 @@ module.exports = function(Chart) {
                 */
                calculateBarIndexPixels: function(datasetIndex, index, ruler) {
                        var me = this;
-                       var scale = ruler.scale;
-                       var isCombo = me.chart.isCombo;
+                       var options = ruler.scale.options;
                        var stackIndex = me.getStackIndex(datasetIndex);
-                       var base = scale.getPixelForValue(null, index, datasetIndex, isCombo);
-                       var size = ruler.barSize;
+                       var pixels = ruler.pixels;
+                       var base = pixels[index];
+                       var length = pixels.length;
+                       var start = ruler.start;
+                       var end = ruler.end;
+                       var leftSampleSize, rightSampleSize, leftCategorySize, rightCategorySize, fullBarSize, size;
+
+                       if (length === 1) {
+                               leftSampleSize = base > start ? base - start : end - base;
+                               rightSampleSize = base < end ? end - base : base - start;
+                       } else {
+                               if (index > 0) {
+                                       leftSampleSize = (base - pixels[index - 1]) / 2;
+                                       if (index === length - 1) {
+                                               rightSampleSize = leftSampleSize;
+                                       }
+                               }
+                               if (index < length - 1) {
+                                       rightSampleSize = (pixels[index + 1] - base) / 2;
+                                       if (index === 0) {
+                                               leftSampleSize = rightSampleSize;
+                                       }
+                               }
+                       }
+
+                       leftCategorySize = leftSampleSize * options.categoryPercentage;
+                       rightCategorySize = rightSampleSize * options.categoryPercentage;
+                       fullBarSize = (leftCategorySize + rightCategorySize) / ruler.stackCount;
+                       size = fullBarSize * options.barPercentage;
+
+                       size = Math.min(
+                               helpers.valueOrDefault(options.barThickness, size),
+                               helpers.valueOrDefault(options.maxBarThickness, Infinity));
 
-                       base -= isCombo ? ruler.tickSize / 2 : 0;
-                       base += ruler.fullBarSize * stackIndex;
-                       base += ruler.categorySpacing / 2;
-                       base += ruler.barSpacing / 2;
+                       base -= leftCategorySize;
+                       base += fullBarSize * stackIndex;
+                       base += (fullBarSize - size) / 2;
 
                        return {
                                size: size,
index 58e3793e96ec79c613c3028e7da9d50025c65c8c..be229da9def7709f1a49682dc7d2eb69f9bb8575 100644 (file)
@@ -76,7 +76,7 @@ module.exports = function(Chart) {
 
                                // Desired view properties
                                _model: {
-                                       x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex, me.chart.isCombo),
+                                       x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex),
                                        y: reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex),
                                        // Appearance
                                        radius: reset ? 0 : custom.radius ? custom.radius : me.getRadius(data),
index 8fe352ed40a331910a44c1f78e9b182a153402fe..9d9206d5cb211f385e8ed4b9e1325ae59a955abc 100644 (file)
@@ -159,8 +159,6 @@ module.exports = function(Chart) {
                        var xScale = me.getScaleForId(meta.xAxisID);
                        var pointOptions = me.chart.options.elements.point;
                        var x, y;
-                       var labels = me.chart.data.labels || [];
-                       var includeOffset = (labels.length === 1 || dataset.data.length === 1) || me.chart.isCombo;
 
                        // Compatibility: If the properties are defined with only the old name, use those values
                        if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) {
@@ -170,7 +168,7 @@ module.exports = function(Chart) {
                                dataset.pointHitRadius = dataset.hitRadius;
                        }
 
-                       x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex, includeOffset);
+                       x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
                        y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
 
                        // Utility
index 28dd976509514e27945db192db83e15324ff8693..b4739c6af09803c49e8c47e92ccae128f709211c 100644 (file)
@@ -312,15 +312,6 @@ module.exports = function(Chart) {
                                }
                        }, me);
 
-                       if (types.length > 1) {
-                               for (var i = 1; i < types.length; i++) {
-                                       if (types[i] !== types[i - 1]) {
-                                               me.isCombo = true;
-                                               break;
-                                       }
-                               }
-                       }
-
                        return newControllers;
                },
 
index 7c418575ec2b49fc5f19ea68ad514091774545be..dd99d0bdd70cd5c88789fac1593079f9ea211e4a 100644 (file)
@@ -8,6 +8,7 @@ var Ticks = require('./core.ticks');
 defaults._set('scale', {
        display: true,
        position: 'left',
+       offset: false,
 
        // grid line settings
        gridLines: {
@@ -68,6 +69,19 @@ function labelsFromTicks(ticks) {
        return labels;
 }
 
+function getLineValue(scale, index, offsetGridLines) {
+       var lineValue = scale.getPixelForTick(index);
+
+       if (offsetGridLines) {
+               if (index === 0) {
+                       lineValue -= (scale.getPixelForTick(1) - lineValue) / 2;
+               } else {
+                       lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2;
+               }
+       }
+       return lineValue;
+}
+
 module.exports = function(Chart) {
 
        function computeTextSize(context, tick, font) {
@@ -521,14 +535,15 @@ module.exports = function(Chart) {
                getValueForPixel: helpers.noop,
 
                // Used for tick location, should
-               getPixelForTick: function(index, includeOffset) {
+               getPixelForTick: function(index) {
                        var me = this;
+                       var offset = me.options.offset;
                        if (me.isHorizontal()) {
                                var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
-                               var tickWidth = innerWidth / Math.max((me._ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
+                               var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
                                var pixel = (tickWidth * index) + me.paddingLeft;
 
-                               if (includeOffset) {
+                               if (offset) {
                                        pixel += tickWidth / 2;
                                }
 
@@ -541,7 +556,7 @@ module.exports = function(Chart) {
                },
 
                // Utility for getting the pixel location of a percentage of scale
-               getPixelForDecimal: function(decimal /* , includeOffset*/) {
+               getPixelForDecimal: function(decimal) {
                        var me = this;
                        if (me.isHorizontal()) {
                                var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
@@ -659,7 +674,7 @@ module.exports = function(Chart) {
                        helpers.each(ticks, function(tick, index) {
                                var label = tick.label;
                                var lineWidth, lineColor, borderDash, borderDashOffset;
-                               if (index === (typeof me.zeroLineIndex !== 'undefined' ? me.zeroLineIndex : 0)) {
+                               if (index === (typeof me.zeroLineIndex !== 'undefined' ? me.zeroLineIndex : 0) && (options.offset === gridLines.offsetGridLines)) {
                                        // Draw the first index specially
                                        lineWidth = gridLines.zeroLineWidth;
                                        lineColor = gridLines.zeroLineColor;
@@ -693,8 +708,13 @@ module.exports = function(Chart) {
                                                labelY = me.bottom - labelYOffset;
                                        }
 
-                                       var xLineValue = me.getPixelForTick(index) + helpers.aliasPixel(lineWidth); // xvalues for grid lines
-                                       labelX = me.getPixelForTick(index, gridLines.offsetGridLines) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
+                                       var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
+                                       if (xLineValue < me.left) {
+                                               lineColor = 'rgba(0,0,0,0)';
+                                       }
+                                       xLineValue += helpers.aliasPixel(lineWidth);
+
+                                       labelX = me.getPixelForTick(index) + optionTicks.labelOffset; // x values for optionTicks (need to consider offsetLabel option)
 
                                        tx1 = tx2 = x1 = x2 = xLineValue;
                                        ty1 = yTickStart;
@@ -715,9 +735,13 @@ module.exports = function(Chart) {
 
                                        labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset;
 
-                                       var yLineValue = me.getPixelForTick(index); // xvalues for grid lines
+                                       var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1);
+                                       if (yLineValue < me.top) {
+                                               lineColor = 'rgba(0,0,0,0)';
+                                       }
                                        yLineValue += helpers.aliasPixel(lineWidth);
-                                       labelY = me.getPixelForTick(index, gridLines.offsetGridLines) + optionTicks.labelOffset;
+
+                                       labelY = me.getPixelForTick(index) + optionTicks.labelOffset;
 
                                        tx1 = xTickStart;
                                        tx2 = xTickEnd;
index d5c21e788e452734f7adfc1a0aa4d5bd50e82c14..5910ce88a5154cf455d0ea1b1ede8cd8908e869e 100644 (file)
@@ -60,10 +60,11 @@ module.exports = function(Chart) {
                },
 
                // Used to get data value locations.  Value can either be an index or a numerical value
-               getPixelForValue: function(value, index, datasetIndex, includeOffset) {
+               getPixelForValue: function(value, index) {
                        var me = this;
+                       var offset = me.options.offset;
                        // 1 is added because we need the length but we have the indexes
-                       var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
+                       var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1);
 
                        // If value is a data object, then index is the index in the data array,
                        // not the index of the scale. We need to change that.
@@ -82,7 +83,7 @@ module.exports = function(Chart) {
                                var valueWidth = me.width / offsetAmt;
                                var widthOffset = (valueWidth * (index - me.minIndex));
 
-                               if (me.options.gridLines.offsetGridLines && includeOffset || me.maxIndex === me.minIndex && includeOffset) {
+                               if (offset) {
                                        widthOffset += (valueWidth / 2);
                                }
 
@@ -91,25 +92,26 @@ module.exports = function(Chart) {
                        var valueHeight = me.height / offsetAmt;
                        var heightOffset = (valueHeight * (index - me.minIndex));
 
-                       if (me.options.gridLines.offsetGridLines && includeOffset) {
+                       if (offset) {
                                heightOffset += (valueHeight / 2);
                        }
 
                        return me.top + Math.round(heightOffset);
                },
-               getPixelForTick: function(index, includeOffset) {
-                       return this.getPixelForValue(this.ticks[index], index + this.minIndex, null, includeOffset);
+               getPixelForTick: function(index) {
+                       return this.getPixelForValue(this.ticks[index], index + this.minIndex, null);
                },
                getValueForPixel: function(pixel) {
                        var me = this;
+                       var offset = me.options.offset;
                        var value;
-                       var offsetAmt = Math.max((me.ticks.length - ((me.options.gridLines.offsetGridLines) ? 0 : 1)), 1);
+                       var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
                        var horz = me.isHorizontal();
                        var valueDimension = (horz ? me.width : me.height) / offsetAmt;
 
                        pixel -= horz ? me.left : me.top;
 
-                       if (me.options.gridLines.offsetGridLines) {
+                       if (offset) {
                                pixel -= (valueDimension / 2);
                        }
 
@@ -119,7 +121,7 @@ module.exports = function(Chart) {
                                value = Math.round(pixel / valueDimension);
                        }
 
-                       return value;
+                       return value + me.minIndex;
                },
                getBasePixel: function() {
                        return this.bottom;
index 035991a765bef79cf4ab7326606ff2341d85a7e1..dd014e1069f605a5a10481a6b5b10cbfbb6b492d 100644 (file)
@@ -330,6 +330,37 @@ function generate(min, max, minor, major, capacity, options) {
        return ticks;
 }
 
+/**
+ * Returns the right and left offsets from edges in the form of {left, right}.
+ * Offsets are added when the `offset` option is true.
+ */
+function computeOffsets(table, ticks, min, max, options) {
+       var left = 0;
+       var right = 0;
+       var upper, lower;
+
+       if (options.offset && ticks.length) {
+               if (!options.time.min) {
+                       upper = ticks.length > 1 ? ticks[1] : max;
+                       lower = ticks[0];
+                       left = (
+                               interpolate(table, 'time', upper, 'pos') -
+                               interpolate(table, 'time', lower, 'pos')
+                       ) / 2;
+               }
+               if (!options.time.max) {
+                       upper = ticks[ticks.length - 1];
+                       lower = ticks.length > 1 ? ticks[ticks.length - 2] : min;
+                       right = (
+                               interpolate(table, 'time', upper, 'pos') -
+                               interpolate(table, 'time', lower, 'pos')
+                       ) / 2;
+               }
+       }
+
+       return {left: left, right: right};
+}
+
 function ticksFromTimestamps(values, majorUnit) {
        var ticks = [];
        var i, ilen, value, major;
@@ -443,9 +474,9 @@ module.exports = function(Chart) {
                determineDataLimits: function() {
                        var me = this;
                        var chart = me.chart;
-                       var options = me.options;
-                       var min = parse(options.time.min, me) || MAX_INTEGER;
-                       var max = parse(options.time.max, me) || MIN_INTEGER;
+                       var timeOpts = me.options.time;
+                       var min = parse(timeOpts.min, me) || MAX_INTEGER;
+                       var max = parse(timeOpts.max, me) || MIN_INTEGER;
                        var timestamps = [];
                        var datasets = [];
                        var labels = [];
@@ -562,6 +593,7 @@ module.exports = function(Chart) {
                        me._minorFormat = formats[unit];
                        me._majorFormat = formats[majorUnit];
                        me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);
+                       me._offsets = computeOffsets(me._table, ticks, min, max, options);
 
                        return ticksFromTimestamps(ticks, majorUnit);
                },
@@ -622,7 +654,7 @@ module.exports = function(Chart) {
                        var start = me._horizontal ? me.left : me.top;
                        var pos = interpolate(me._table, 'time', time, 'pos');
 
-                       return start + size * pos;
+                       return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right);
                },
 
                getPixelForValue: function(value, index, datasetIndex) {
@@ -653,7 +685,7 @@ module.exports = function(Chart) {
                        var me = this;
                        var size = me._horizontal ? me.width : me.height;
                        var start = me._horizontal ? me.left : me.top;
-                       var pos = size ? (pixel - start) / size : 0;
+                       var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right;
                        var time = interpolate(me._table, 'pos', pos, 'time');
 
                        return moment(time);
index 0c69ddc302f2213416208c948c0290d4928b3766..09abf5288ae78177168cff1e3dcbc012c6724a27 100644 (file)
@@ -1108,7 +1108,7 @@ describe('Bar controller tests', function() {
                        {x: 224, y: 32}
                ].forEach(function(values, i) {
                        expect(meta.data[i]._model.base).toBeCloseToPixel(484);
-                       expect(meta.data[i]._model.width).toBeCloseToPixel(40);
+                       expect(meta.data[i]._model.width).toBeCloseToPixel(42);
                        expect(meta.data[i]._model.x).toBeCloseToPixel(values.x);
                        expect(meta.data[i]._model.y).toBeCloseToPixel(values.y);
                });
@@ -1150,7 +1150,7 @@ describe('Bar controller tests', function() {
                        {b: 258, x: 224, y: 32}
                ].forEach(function(values, i) {
                        expect(meta.data[i]._model.base).toBeCloseToPixel(values.b);
-                       expect(meta.data[i]._model.width).toBeCloseToPixel(40);
+                       expect(meta.data[i]._model.width).toBeCloseToPixel(42);
                        expect(meta.data[i]._model.x).toBeCloseToPixel(values.x);
                        expect(meta.data[i]._model.y).toBeCloseToPixel(values.y);
                });
@@ -1519,4 +1519,152 @@ describe('Bar controller tests', function() {
                        };
                });
        });
+
+       describe('Bar thickness with a category scale', function() {
+               [undefined, 20].forEach(function(barThickness) {
+                       describe('When barThickness is ' + barThickness, function() {
+                               beforeEach(function() {
+                                       this.chart = window.acquireChart({
+                                               type: 'bar',
+                                               data: {
+                                                       datasets: [{
+                                                               data: [1, 2]
+                                                       }, {
+                                                               data: [1, 2]
+                                                       }],
+                                                       labels: ['label1', 'label2', 'label3']
+                                               },
+                                               options: {
+                                                       scales: {
+                                                               xAxes: [{
+                                                                       id: 'x',
+                                                                       type: 'category',
+                                                                       barThickness: barThickness
+                                                               }],
+                                                               yAxes: [{
+                                                                       type: 'linear'
+                                                               }]
+                                                       }
+                                               }
+                                       });
+                               });
+
+                               it('should correctly set bar width', function() {
+                                       var chart = this.chart;
+                                       var expected, i, ilen, meta;
+
+                                       if (barThickness) {
+                                               expected = barThickness;
+                                       } else {
+                                               var scale = chart.scales.x;
+                                               var options = chart.options.scales.xAxes[0];
+                                               var categoryPercentage = options.categoryPercentage;
+                                               var barPercentage = options.barPercentage;
+                                               var tickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0);
+
+                                               expected = tickInterval * categoryPercentage / 2 * barPercentage;
+                                       }
+
+                                       for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
+                                               meta = chart.getDatasetMeta(i);
+                                               expect(meta.data[0]._model.width).toBeCloseToPixel(expected);
+                                               expect(meta.data[1]._model.width).toBeCloseToPixel(expected);
+                                       }
+                               });
+
+                               it('should correctly set bar width if maxBarThickness is specified', function() {
+                                       var chart = this.chart;
+                                       var options = chart.options.scales.xAxes[0];
+                                       var i, ilen, meta;
+
+                                       options.maxBarThickness = 10;
+                                       chart.update();
+
+                                       for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
+                                               meta = chart.getDatasetMeta(i);
+                                               expect(meta.data[0]._model.width).toBeCloseToPixel(10);
+                                               expect(meta.data[1]._model.width).toBeCloseToPixel(10);
+                                       }
+                               });
+                       });
+               });
+       });
+
+       describe('Bar thickness with a time scale', function() {
+               ['auto', 'data', 'labels'].forEach(function(source) {
+                       ['series', 'linear'].forEach(function(distribution) {
+                               describe('When ticks.source is "' + source + '", distribution is "' + distribution + '"', function() {
+                                       beforeEach(function() {
+                                               this.chart = window.acquireChart({
+                                                       type: 'bar',
+                                                       data: {
+                                                               datasets: [{
+                                                                       data: [1, 2, 3]
+                                                               }, {
+                                                                       data: [1, 2, 3]
+                                                               }],
+                                                               labels: ['2017', '2018', '2020']
+                                                       },
+                                                       options: {
+                                                               scales: {
+                                                                       xAxes: [{
+                                                                               id: 'x',
+                                                                               type: 'time',
+                                                                               time: {
+                                                                                       unit: 'year',
+                                                                                       parser: 'YYYY'
+                                                                               },
+                                                                               ticks: {
+                                                                                       source: source
+                                                                               },
+                                                                               offset: true,
+                                                                               distribution: distribution
+                                                                       }],
+                                                                       yAxes: [{
+                                                                               type: 'linear'
+                                                                       }]
+                                                               }
+                                                       }
+                                               });
+                                       });
+
+                                       it('should correctly set bar width', function() {
+                                               var chart = this.chart;
+                                               var scale = chart.scales.x;
+                                               var options = chart.options.scales.xAxes[0];
+                                               var categoryPercentage = options.categoryPercentage;
+                                               var barPercentage = options.barPercentage;
+                                               var firstInterval = scale.getPixelForValue('2018') - scale.getPixelForValue('2017');
+                                               var firstExpected = firstInterval * categoryPercentage / 2 * barPercentage;
+                                               var lastInterval = scale.getPixelForValue('2020') - scale.getPixelForValue('2018');
+                                               var lastExpected = lastInterval * categoryPercentage / 2 * barPercentage;
+                                               var i, ilen, meta;
+
+                                               for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
+                                                       meta = chart.getDatasetMeta(i);
+                                                       expect(meta.data[0]._model.width).toBeCloseToPixel(firstExpected);
+                                                       expect(meta.data[1]._model.width).toBeCloseToPixel((firstExpected + lastExpected) / 2);
+                                                       expect(meta.data[2]._model.width).toBeCloseToPixel(lastExpected);
+                                               }
+                                       });
+
+                                       it('should correctly set bar width if maxBarThickness is specified', function() {
+                                               var chart = this.chart;
+                                               var options = chart.options.scales.xAxes[0];
+                                               var i, ilen, meta;
+
+                                               options.maxBarThickness = 10;
+                                               chart.update();
+
+                                               for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
+                                                       meta = chart.getDatasetMeta(i);
+                                                       expect(meta.data[0]._model.width).toBeCloseToPixel(10);
+                                                       expect(meta.data[1]._model.width).toBeCloseToPixel(10);
+                                                       expect(meta.data[2]._model.width).toBeCloseToPixel(10);
+                                               }
+                                       });
+                               });
+                       });
+               });
+       });
 });
index 4c9471db9358ca4dd40a5b5ce8717603a3a5f6df..e1231dc692c4819935a8fe6c033c27241567bbbf 100644 (file)
@@ -250,7 +250,7 @@ describe('Line controller tests', function() {
                var meta = chart.getDatasetMeta(0);
                // 1 point
                var point = meta.data[0];
-               expect(point._model.x).toBeCloseToPixel(262);
+               expect(point._model.x).toBeCloseToPixel(27);
 
                // 2 points
                chart.data.labels = ['One', 'Two'];
index e6f6264395a09da9dbec9c9bea1c5919bbdb8e0d..1b5edb89edf945522e2679b51c76b618df552b5e 100644 (file)
@@ -127,6 +127,7 @@ describe('Core helper tests', function() {
                                                borderDashOffset: 0.0
                                        },
                                        position: 'right',
+                                       offset: false,
                                        scaleLabel: {
                                                display: false,
                                                labelString: '',
@@ -168,6 +169,7 @@ describe('Core helper tests', function() {
                                                borderDashOffset: 0.0
                                        },
                                        position: 'left',
+                                       offset: false,
                                        scaleLabel: {
                                                display: false,
                                                labelString: '',
index e7774bf443fbf25bf72860189a05134e3e57e5bd..8d8d32cd424d94bee33f645acd018a774eacbdec 100644 (file)
@@ -29,6 +29,7 @@ describe('Category scale tests', function() {
                                borderDashOffset: 0.0
                        },
                        position: 'bottom',
+                       offset: false,
                        scaleLabel: {
                                display: false,
                                labelString: '',
@@ -214,25 +215,20 @@ describe('Category scale tests', function() {
                });
 
                var xScale = chart.scales.xScale0;
-               expect(xScale.getPixelForValue(0, 0, 0, false)).toBeCloseToPixel(23);
-               expect(xScale.getPixelForValue(0, 0, 0, true)).toBeCloseToPixel(23);
-               expect(xScale.getValueForPixel(33)).toBe(0);
+               expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(23);
+               expect(xScale.getValueForPixel(23)).toBe(0);
 
-               expect(xScale.getPixelForValue(0, 4, 0, false)).toBeCloseToPixel(487);
-               expect(xScale.getPixelForValue(0, 4, 0, true)).toBeCloseToPixel(487);
+               expect(xScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(487);
                expect(xScale.getValueForPixel(487)).toBe(4);
 
-               xScale.options.gridLines.offsetGridLines = true;
+               xScale.options.offset = true;
+               chart.update();
 
-               expect(xScale.getPixelForValue(0, 0, 0, false)).toBeCloseToPixel(23);
-               expect(xScale.getPixelForValue(0, 0, 0, true)).toBeCloseToPixel(69);
-               expect(xScale.getValueForPixel(33)).toBe(0);
-               expect(xScale.getValueForPixel(78)).toBe(0);
+               expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(69);
+               expect(xScale.getValueForPixel(69)).toBe(0);
 
-               expect(xScale.getPixelForValue(0, 4, 0, false)).toBeCloseToPixel(395);
-               expect(xScale.getPixelForValue(0, 4, 0, true)).toBeCloseToPixel(441);
+               expect(xScale.getPixelForValue(0, 4, 0)).toBeCloseToPixel(441);
                expect(xScale.getValueForPixel(397)).toBe(4);
-               expect(xScale.getValueForPixel(441)).toBe(4);
        });
 
        it ('Should get the correct pixel for a value when there are repeated labels', function() {
@@ -262,8 +258,8 @@ describe('Category scale tests', function() {
                });
 
                var xScale = chart.scales.xScale0;
-               expect(xScale.getPixelForValue('tick_1', 0, 0, false)).toBeCloseToPixel(23);
-               expect(xScale.getPixelForValue('tick_1', 1, 0, false)).toBeCloseToPixel(139);
+               expect(xScale.getPixelForValue('tick_1', 0, 0)).toBeCloseToPixel(23);
+               expect(xScale.getPixelForValue('tick_1', 1, 0)).toBeCloseToPixel(139);
        });
 
        it ('Should get the correct pixel for a value when horizontal and zoomed', function() {
@@ -297,19 +293,14 @@ describe('Category scale tests', function() {
                });
 
                var xScale = chart.scales.xScale0;
-               expect(xScale.getPixelForValue(0, 1, 0, false)).toBeCloseToPixel(23);
-               expect(xScale.getPixelForValue(0, 1, 0, true)).toBeCloseToPixel(23);
+               expect(xScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(23);
+               expect(xScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(496);
 
-               expect(xScale.getPixelForValue(0, 3, 0, false)).toBeCloseToPixel(496);
-               expect(xScale.getPixelForValue(0, 3, 0, true)).toBeCloseToPixel(496);
+               xScale.options.offset = true;
+               chart.update();
 
-               xScale.options.gridLines.offsetGridLines = true;
-
-               expect(xScale.getPixelForValue(0, 1, 0, false)).toBeCloseToPixel(23);
-               expect(xScale.getPixelForValue(0, 1, 0, true)).toBeCloseToPixel(102);
-
-               expect(xScale.getPixelForValue(0, 3, 0, false)).toBeCloseToPixel(338);
-               expect(xScale.getPixelForValue(0, 3, 0, true)).toBeCloseToPixel(419);
+               expect(xScale.getPixelForValue(0, 1, 0)).toBeCloseToPixel(102);
+               expect(xScale.getPixelForValue(0, 3, 0)).toBeCloseToPixel(417);
        });
 
        it ('should get the correct pixel for a value when vertical', function() {
@@ -341,24 +332,19 @@ describe('Category scale tests', function() {
                });
 
                var yScale = chart.scales.yScale0;
-               expect(yScale.getPixelForValue(0, 0, 0, false)).toBe(32);
-               expect(yScale.getPixelForValue(0, 0, 0, true)).toBe(32);
+               expect(yScale.getPixelForValue(0, 0, 0)).toBe(32);
                expect(yScale.getValueForPixel(32)).toBe(0);
 
-               expect(yScale.getPixelForValue(0, 4, 0, false)).toBe(484);
-               expect(yScale.getPixelForValue(0, 4, 0, true)).toBe(484);
+               expect(yScale.getPixelForValue(0, 4, 0)).toBe(484);
                expect(yScale.getValueForPixel(484)).toBe(4);
 
-               yScale.options.gridLines.offsetGridLines = true;
+               yScale.options.offset = true;
+               chart.update();
 
-               expect(yScale.getPixelForValue(0, 0, 0, false)).toBe(32);
-               expect(yScale.getPixelForValue(0, 0, 0, true)).toBe(77);
-               expect(yScale.getValueForPixel(32)).toBe(0);
+               expect(yScale.getPixelForValue(0, 0, 0)).toBe(77);
                expect(yScale.getValueForPixel(77)).toBe(0);
 
-               expect(yScale.getPixelForValue(0, 4, 0, false)).toBe(394);
-               expect(yScale.getPixelForValue(0, 4, 0, true)).toBe(439);
-               expect(yScale.getValueForPixel(394)).toBe(4);
+               expect(yScale.getPixelForValue(0, 4, 0)).toBe(439);
                expect(yScale.getValueForPixel(439)).toBe(4);
        });
 
@@ -396,18 +382,13 @@ describe('Category scale tests', function() {
 
                var yScale = chart.scales.yScale0;
 
-               expect(yScale.getPixelForValue(0, 1, 0, false)).toBe(32);
-               expect(yScale.getPixelForValue(0, 1, 0, true)).toBe(32);
-
-               expect(yScale.getPixelForValue(0, 3, 0, false)).toBe(484);
-               expect(yScale.getPixelForValue(0, 3, 0, true)).toBe(484);
-
-               yScale.options.gridLines.offsetGridLines = true;
+               expect(yScale.getPixelForValue(0, 1, 0)).toBe(32);
+               expect(yScale.getPixelForValue(0, 3, 0)).toBe(484);
 
-               expect(yScale.getPixelForValue(0, 1, 0, false)).toBe(32);
-               expect(yScale.getPixelForValue(0, 1, 0, true)).toBe(107);
+               yScale.options.offset = true;
+               chart.update();
 
-               expect(yScale.getPixelForValue(0, 3, 0, false)).toBe(333);
-               expect(yScale.getPixelForValue(0, 3, 0, true)).toBe(409);
+               expect(yScale.getPixelForValue(0, 1, 0)).toBe(107);
+               expect(yScale.getPixelForValue(0, 3, 0)).toBe(409);
        });
 });
index de653268d73174daa370d7f8725fa7092a446369..ed6f9c1f96ed8ecbb6203e5120d11a3aeb0d5a92 100644 (file)
@@ -27,6 +27,7 @@ describe('Linear Scale', function() {
                                borderDashOffset: 0.0
                        },
                        position: 'left',
+                       offset: false,
                        scaleLabel: {
                                display: false,
                                labelString: '',
index 863f747729393893bb15ca024d8baa42bc4a3d59..98d0fbf09c4cb0948f9ee17890fe3b40475ace67 100644 (file)
@@ -26,6 +26,7 @@ describe('Logarithmic Scale tests', function() {
                                borderDashOffset: 0.0
                        },
                        position: 'left',
+                       offset: false,
                        scaleLabel: {
                                display: false,
                                labelString: '',
index 6164249d652ddadea7ca83edbc896c21d8d4de8b..70c75f02ad7249ff540f18842d055dfeb5e38bbf 100644 (file)
@@ -39,6 +39,7 @@ describe('Test the radial linear scale', function() {
                                callback: defaultConfig.pointLabels.callback, // make this nicer, then check explicitly below
                        },
                        position: 'chartArea',
+                       offset: false,
                        scaleLabel: {
                                display: false,
                                labelString: '',
index 267c2b99a2bbfc926f050bb1003e193a52cea727..3c757243d56ea5fc855df5e42c6e91d1d953ee12 100755 (executable)
@@ -72,6 +72,7 @@ describe('Time scale tests', function() {
                                borderDashOffset: 0.0
                        },
                        position: 'bottom',
+                       offset: false,
                        scaleLabel: {
                                display: false,
                                labelString: '',
@@ -1087,4 +1088,85 @@ describe('Time scale tests', function() {
                        });
                });
        });
+
+       ['auto', 'data', 'labels'].forEach(function(source) {
+               ['series', 'linear'].forEach(function(distribution) {
+                       describe('when ticks.source is "' + source + '" and distribution is "' + distribution + '"', function() {
+                               beforeEach(function() {
+                                       this.chart = window.acquireChart({
+                                               type: 'line',
+                                               data: {
+                                                       labels: ['2017', '2019', '2020', '2025', '2042'],
+                                                       datasets: [{data: [0, 1, 2, 3, 4, 5]}]
+                                               },
+                                               options: {
+                                                       scales: {
+                                                               xAxes: [{
+                                                                       id: 'x',
+                                                                       type: 'time',
+                                                                       time: {
+                                                                               parser: 'YYYY'
+                                                                       },
+                                                                       ticks: {
+                                                                               source: source
+                                                                       },
+                                                                       distribution: distribution
+                                                               }]
+                                                       }
+                                               }
+                                       });
+                               });
+
+                               it ('should not add offset from the edges', function() {
+                                       var scale = this.chart.scales.x;
+
+                                       expect(scale.getPixelForValue('2017')).toBeCloseToPixel(scale.left);
+                                       expect(scale.getPixelForValue('2042')).toBeCloseToPixel(scale.left + scale.width);
+                               });
+
+                               it ('should add offset from the edges if offset is true', function() {
+                                       var chart = this.chart;
+                                       var scale = chart.scales.x;
+                                       var options = chart.options.scales.xAxes[0];
+
+                                       options.offset = true;
+                                       chart.update();
+
+                                       var numTicks = scale.ticks.length;
+                                       var firstTickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0);
+                                       var lastTickInterval = scale.getPixelForTick(numTicks - 1) - scale.getPixelForTick(numTicks - 2);
+
+                                       expect(scale.getPixelForValue('2017')).toBeCloseToPixel(scale.left + firstTickInterval / 2);
+                                       expect(scale.getPixelForValue('2042')).toBeCloseToPixel(scale.left + scale.width - lastTickInterval / 2);
+                               });
+
+                               it ('should not add offset if min and max extend the labels range', function() {
+                                       var chart = this.chart;
+                                       var scale = chart.scales.x;
+                                       var options = chart.options.scales.xAxes[0];
+
+                                       options.time.min = '2012';
+                                       options.time.max = '2051';
+                                       chart.update();
+
+                                       expect(scale.getPixelForValue('2012')).toBeCloseToPixel(scale.left);
+                                       expect(scale.getPixelForValue('2051')).toBeCloseToPixel(scale.left + scale.width);
+                               });
+
+                               it ('should not add offset if min and max extend the labels range and offset is true', function() {
+                                       var chart = this.chart;
+                                       var scale = chart.scales.x;
+                                       var options = chart.options.scales.xAxes[0];
+
+                                       options.time.min = '2012';
+                                       options.time.max = '2051';
+                                       options.offset = true;
+                                       chart.update();
+
+                                       expect(scale.getPixelForValue('2012')).toBeCloseToPixel(scale.left);
+                                       expect(scale.getPixelForValue('2051')).toBeCloseToPixel(scale.left + scale.width);
+                               });
+                       });
+               });
+       });
 });