Skip to content

Commit

Permalink
Versatile clipping (#6642)
Browse files Browse the repository at this point in the history
Versatile clipping algorithm for different chart types
  • Loading branch information
kurkle authored and etimberg committed Nov 10, 2019
1 parent a3392e0 commit 11ef1e5
Show file tree
Hide file tree
Showing 28 changed files with 485 additions and 12 deletions.
3 changes: 3 additions & 0 deletions docs/charts/bar.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ the color of the bars is generally set this way.
| [`borderColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`
| [`borderSkipped`](#borderskipped) | `string` | Yes | Yes | `'bottom'`
| [`borderWidth`](#borderwidth) | <code>number&#124;object</code> | Yes | Yes | `0`
| [`clip`](#general) | <code>number&#124;object</code> | - | - | `undefined`
| [`data`](#data-structure) | `object[]` | - | - | **required**
| [`hoverBackgroundColor`](#interactions) | [`Color`](../general/colors.md) | - | Yes | `undefined`
| [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | - | Yes | `undefined`
Expand All @@ -85,6 +86,7 @@ the color of the bars is generally set this way.

| Name | Description
| ---- | ----
| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`
| `label` | The label for the dataset which appears in the legend and tooltips.
| `order` | The drawing order of dataset. Also affects order for stacking, tooltip, and legend.
| `xAxisID` | The ID of the x axis to plot this dataset on.
Expand All @@ -100,6 +102,7 @@ The style of each bar can be controlled with the following properties:
| `borderColor` | The bar border color.
| [`borderSkipped`](#borderskipped) | The edge to skip when drawing bar.
| [`borderWidth`](#borderwidth) | The bar border width (in pixels).
| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`

All these values, if `undefined`, fallback to the associated [`elements.rectangle.*`](../configuration/elements.md#rectangle-configuration) options.

Expand Down
2 changes: 2 additions & 0 deletions docs/charts/bubble.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ The bubble chart allows a number of properties to be specified for each dataset.
| [`backgroundColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`
| [`borderColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'`
| [`borderWidth`](#styling) | `number` | Yes | Yes | `3`
| [`clip`](#general) | <code>number&#124;object</code> | - | - | `undefined`
| [`data`](#data-structure) | `object[]` | - | - | **required**
| [`hoverBackgroundColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`
| [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`
Expand All @@ -59,6 +60,7 @@ The bubble chart allows a number of properties to be specified for each dataset.

| Name | Description
| ---- | ----
| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`
| `label` | The label for the dataset which appears in the legend and tooltips.
| `order` | The drawing order of dataset.

Expand Down
8 changes: 8 additions & 0 deletions docs/charts/doughnut.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,20 @@ The doughnut/pie chart allows a number of properties to be specified for each da
| [`borderAlign`](#border-alignment) | `string` | Yes | Yes | `'center'`
| [`borderColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'#fff'`
| [`borderWidth`](#styling) | `number` | Yes | Yes | `2`
| [`clip`](#general) | <code>number&#124;object</code> | - | - | `undefined`
| [`data`](#data-structure) | `number[]` | - | - | **required**
| [`hoverBackgroundColor`](#interations) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`
| [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`
| [`hoverBorderWidth`](#interactions) | `number` | Yes | Yes | `undefined`
| [`weight`](#styling) | `number` | - | - | `1`

### General

| Name | Description
| ---- | ----
| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`


### Styling

The style of each arc can be controlled with the following properties:
Expand Down
2 changes: 2 additions & 0 deletions docs/charts/line.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ The line chart allows a number of properties to be specified for each dataset. T
| [`borderDashOffset`](#line-styling) | `number` | Yes | - | `0.0`
| [`borderJoinStyle`](#line-styling) | `string` | Yes | - | `'miter'`
| [`borderWidth`](#line-styling) | `number` | Yes | - | `3`
| [`clip`](#general) | <code>number&#124;object</code> | - | - | `undefined`
| [`cubicInterpolationMode`](#cubicinterpolationmode) | `string` | Yes | - | `'default'`
| [`fill`](#line-styling) | <code>boolean&#124;string</code> | Yes | - | `true`
| [`hoverBackgroundColor`](#line-styling) | [`Color`](../general/colors.md) | Yes | - | `undefined`
Expand Down Expand Up @@ -83,6 +84,7 @@ The line chart allows a number of properties to be specified for each dataset. T

| Name | Description
| ---- | ----
| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`
| `label` | The label for the dataset which appears in the legend and tooltips.
| `order` | The drawing order of dataset. Also affects order for stacking, tooltip, and legend.
| `xAxisID` | The ID of the x axis to plot this dataset on.
Expand Down
7 changes: 7 additions & 0 deletions docs/charts/polar.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,18 @@ The following options can be included in a polar area chart dataset to configure
| [`borderAlign`](#border-alignment) | `string` | Yes | Yes | `'center'`
| [`borderColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'#fff'`
| [`borderWidth`](#styling) | `number` | Yes | Yes | `2`
| [`clip`](#general) | <code>number&#124;object</code> | - | - | `undefined`
| [`data`](#data-structure) | `number[]` | - | - | **required**
| [`hoverBackgroundColor`](#interations) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`
| [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`
| [`hoverBorderWidth`](#interactions) | `number` | Yes | Yes | `undefined`

### General

| Name | Description
| ---- | ----
| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`

### Styling

The style of each arc can be controlled with the following properties:
Expand Down
2 changes: 2 additions & 0 deletions docs/charts/radar.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ The radar chart allows a number of properties to be specified for each dataset.
| [`hoverBorderDashOffset`](#line-styling) | `number` | Yes | - | `undefined`
| [`hoverBorderJoinStyle`](#line-styling) | `string` | Yes | - | `undefined`
| [`hoverBorderWidth`](#line-styling) | `number` | Yes | - | `undefined`
| [`clip`](#general) | <code>number&#124;object</code> | - | - | `undefined`
| [`fill`](#line-styling) | <code>boolean&#124;string</code> | Yes | - | `true`
| [`label`](#general) | `string` | - | - | `''`
| [`order`](#general) | `number` | - | - | `0`
Expand All @@ -102,6 +103,7 @@ The radar chart allows a number of properties to be specified for each dataset.

| Name | Description
| ---- | ----
| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`
| `label` | The label for the dataset which appears in the legend and tooltips.
| `order` | The drawing order of dataset.

Expand Down
15 changes: 15 additions & 0 deletions src/controllers/controller.bubble.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ module.exports = DatasetController.extend({
return parsed;
},

/**
* @private
*/
_getMaxOverflow: function() {
var me = this;
var meta = me._cachedMeta;
var data = meta.data || [];
if (!data.length) {
return false;
}
var firstPoint = data[0].size();
var lastPoint = data[data.length - 1].size();
return Math.max(firstPoint, lastPoint) / 2;
},

/**
* @protected
*/
Expand Down
27 changes: 16 additions & 11 deletions src/controllers/controller.line.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,22 @@ module.exports = DatasetController.extend({
return values;
},

/**
* @private
*/
_getMaxOverflow: function() {
var me = this;
var meta = me._cachedMeta;
var data = meta.data || [];
if (!data.length) {
return false;
}
var border = me._showLine ? meta.dataset._model.borderWidth : 0;
var firstPoint = data[0].size();
var lastPoint = data[data.length - 1].size();
return Math.max(border, firstPoint, lastPoint) / 2;
},

updateBezierControlPoints: function() {
var me = this;
var chart = me.chart;
Expand Down Expand Up @@ -222,21 +238,10 @@ module.exports = DatasetController.extend({
var area = chart.chartArea;
var i = 0;
var ilen = points.length;
var halfBorderWidth;

if (me._showLine) {
halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2;

helpers.canvas.clipArea(chart.ctx, {
left: area.left - halfBorderWidth,
right: area.right + halfBorderWidth,
top: area.top - halfBorderWidth,
bottom: area.bottom + halfBorderWidth
});

meta.dataset.draw();

helpers.canvas.unclipArea(chart.ctx);
}

// Draw the points
Expand Down
13 changes: 13 additions & 0 deletions src/core/core.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,10 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
*/
drawDataset: function(meta, easingValue) {
var me = this;
var ctx = me.ctx;
var clip = meta._clip;
var canvas = me.canvas;
var area = me.chartArea;
var args = {
meta: meta,
index: meta.index,
Expand All @@ -782,8 +786,17 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
return;
}

helpers.canvas.clipArea(ctx, {
left: clip.left === false ? 0 : area.left - clip.left,
right: clip.right === false ? canvas.width : area.right + clip.right,
top: clip.top === false ? 0 : area.top - clip.top,
bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom
});

meta.controller.draw(easingValue);

helpers.canvas.unclipArea(ctx);

plugins.notify(me, 'afterDatasetDraw', [args]);
},

Expand Down
67 changes: 67 additions & 0 deletions src/core/core.datasetController.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,53 @@ function listenArrayEvents(array, listener) {
});
}


function scaleClip(scale, allowedOverflow) {
var tickOpts = scale && scale.options.ticks || {};
var reverse = tickOpts.reverse;
var min = tickOpts.min === undefined ? allowedOverflow : 0;
var max = tickOpts.max === undefined ? allowedOverflow : 0;
return {
start: reverse ? max : min,
end: reverse ? min : max
};
}

function defaultClip(xScale, yScale, allowedOverflow) {
if (allowedOverflow === false) {
return false;
}
var x = scaleClip(xScale, allowedOverflow);
var y = scaleClip(yScale, allowedOverflow);

return {
top: y.end,
right: x.end,
bottom: y.start,
left: x.start
};
}

function toClip(value) {
var t, r, b, l;

if (helpers.isObject(value)) {
t = value.top;
r = value.right;
b = value.bottom;
l = value.left;
} else {
t = r = b = l = value;
}

return {
top: t,
right: r,
bottom: b,
left: l
};
}

/**
* Removes the given array event listener and cleanup extra attached properties (such as
* the _chartjs stub and overridden methods) if array doesn't have any more listeners.
Expand Down Expand Up @@ -556,6 +603,9 @@ helpers.extend(DatasetController.prototype, {
return applyStack(stack, value, meta.index);
},

/**
* @private
*/
_getMinMax: function(scale, canStack) {
var chart = this.chart;
var meta = this._cachedMeta;
Expand Down Expand Up @@ -596,6 +646,9 @@ helpers.extend(DatasetController.prototype, {
};
},

/**
* @private
*/
_getAllParsedValues: function(scale) {
var meta = this._cachedMeta;
var metaData = meta.data;
Expand All @@ -611,6 +664,9 @@ helpers.extend(DatasetController.prototype, {
return values;
},

/**
* @private
*/
_cacheScaleStackStatus: function() {
var me = this;
var indexScale = me._getIndexScale();
Expand All @@ -622,6 +678,9 @@ helpers.extend(DatasetController.prototype, {
}
},

/**
* @private
*/
_scaleCheck: function() {
var me = this;
var indexScale = me._getIndexScale();
Expand All @@ -634,11 +693,19 @@ helpers.extend(DatasetController.prototype, {
cache[valueScale.id] !== valueScale.options.stacked;
},

/**
* @private
*/
_getMaxOverflow: function() {
return false;
},

_update: function(reset) {
var me = this;
me._configure();
me._cachedDataOpts = null;
me.update(reset);
me._cachedMeta._clip = toClip(helpers.valueOrDefault(me._config.clip, defaultClip(me._xScale, me._yScale, me._getMaxOverflow())));
me._cacheScaleStackStatus();
},

Expand Down
7 changes: 7 additions & 0 deletions src/elements/element.point.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ class Point extends Element {
};
}

size() {
var vm = this._view;
var radius = vm.radius || 0;
var borderWidth = vm.borderWidth || 0;
return (radius + borderWidth) * 2;
}

tooltipPosition() {
var vm = this._view;
return {
Expand Down
35 changes: 35 additions & 0 deletions test/fixtures/controller.bubble/clip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module.exports = {
config: {
type: 'line',
data: {
labels: [0, 5, 10, 15, 20, 25, 30, 50, 55, 60],
datasets: [{
data: [6, 11, 10, 10, 3, 22, 7, 24],
type: 'bubble',
label: 'test',
borderColor: '#3e95cd',
fill: false
}]
},
options: {
legend: false,
scales: {
xAxes: [{ticks: {display: false}}],
yAxes: [{
ticks: {
display: false,
min: 8,
max: 25,
beginAtZero: true
}
}]
}
}
},
options: {
canvas: {
height: 256,
width: 256
}
}
};
Binary file added test/fixtures/controller.bubble/clip.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion test/fixtures/controller.bubble/point-style.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
{"x": 9, "y": 2}
],
"backgroundColor": "transparent",
"borderColor": "0000ff",
"borderColor": "#0000ff",
"borderWidth": 0,
"pointStyle": [
"circle",
Expand Down
Loading

0 comments on commit 11ef1e5

Please sign in to comment.