diff --git a/docs/charts/bubble.md b/docs/charts/bubble.md index 4cb2ee6994f..e9f8a7216b1 100644 --- a/docs/charts/bubble.md +++ b/docs/charts/bubble.md @@ -51,6 +51,7 @@ The bubble chart allows a number of properties to be specified for each dataset. | [`hitRadius`](#interactions) | `Number` | Yes | Yes | `1` | [`label`](#labeling) | `String` | - | - | `undefined` | [`pointStyle`](#styling) | `String` | Yes | Yes | `circle` +| [`rotation`](#styling) | `Number` | Yes | Yes | `0` | [`radius`](#styling) | `Number` | Yes | Yes | `3` ### Labeling @@ -67,6 +68,7 @@ The style of each bubble can be controlled with the following properties: | `borderColor` | bubble border color | `borderWidth` | bubble border width (in pixels) | `pointStyle` | bubble [shape style](../configuration/elements#point-styles) +| `rotation` | bubble rotation (in degrees) | `radius` | bubble radius (in pixels) All these values, if `undefined`, fallback to the associated [`elements.point.*`](../configuration/elements.md#point-configuration) options. diff --git a/docs/charts/line.md b/docs/charts/line.md index 90471e462dd..db0245b1e0c 100644 --- a/docs/charts/line.md +++ b/docs/charts/line.md @@ -63,6 +63,7 @@ All point* properties can be specified as an array. If these are set to an array | `pointBorderWidth` | `Number/Number[]` | The width of the point border in pixels. | `pointRadius` | `Number/Number[]` | The radius of the point shape. If set to 0, the point is not rendered. | `pointStyle` | `String/String[]/Image/Image[]` | Style of the point. [more...](../configuration/elements#point-styles) +| `pointRotation` | `Number/Number[]` | The rotation of the point in degrees. | `pointHitRadius` | `Number/Number[]` | The pixel size of the non-displayed point that reacts to mouse events. | `pointHoverBackgroundColor` | `Color/Color[]` | Point background color when hovered. | `pointHoverBorderColor` | `Color/Color[]` | Point border color when hovered. diff --git a/docs/charts/radar.md b/docs/charts/radar.md index b8a41c8384c..947e15a3102 100644 --- a/docs/charts/radar.md +++ b/docs/charts/radar.md @@ -82,6 +82,7 @@ All point* properties can be specified as an array. If these are set to an array | `pointBorderColor` | `Color/Color[]` | The border color for points. | `pointBorderWidth` | `Number/Number[]` | The width of the point border in pixels. | `pointRadius` | `Number/Number[]` | The radius of the point shape. If set to 0, the point is not rendered. +| `pointRotation` | `Number/Number[]` | The rotation of the point in degrees. | `pointStyle` | `String/String[]/Image/Image[]` | Style of the point. [more...](#pointstyle) | `pointHitRadius` | `Number/Number[]` | The pixel size of the non-displayed point that reacts to mouse events. | `pointHoverBackgroundColor` | `Color/Color[]` | Point background color when hovered. diff --git a/docs/configuration/elements.md b/docs/configuration/elements.md index 5375a7e9f1f..148b5f39a54 100644 --- a/docs/configuration/elements.md +++ b/docs/configuration/elements.md @@ -19,6 +19,7 @@ Global point options: `Chart.defaults.global.elements.point` | -----| ---- | --------| ----------- | `radius` | `Number` | `3` | Point radius. | [`pointStyle`](#point-styles) | `String` | `circle` | Point style. +| `rotation` | `Number` | `0` | Point rotation (in degrees). | `backgroundColor` | `Color` | `'rgba(0,0,0,0.1)'` | Point fill color. | `borderWidth` | `Number` | `1` | Point stroke width. | `borderColor` | `Color` | `'rgba(0,0,0,0.1)'` | Point stroke color. diff --git a/src/controllers/controller.bubble.js b/src/controllers/controller.bubble.js index b808b366b1f..f14e512300f 100644 --- a/src/controllers/controller.bubble.js +++ b/src/controllers/controller.bubble.js @@ -87,6 +87,7 @@ module.exports = function(Chart) { borderWidth: options.borderWidth, hitRadius: options.hitRadius, pointStyle: options.pointStyle, + rotation: options.rotation, radius: reset ? 0 : options.radius, skip: custom.skip || isNaN(x) || isNaN(y), x: x, @@ -146,7 +147,8 @@ module.exports = function(Chart) { 'hoverBorderWidth', 'hoverRadius', 'hitRadius', - 'pointStyle' + 'pointStyle', + 'rotation' ]; for (i = 0, ilen = keys.length; i < ilen; ++i) { @@ -165,7 +167,6 @@ module.exports = function(Chart) { dataset.radius, options.radius ], context, index); - return values; } }); diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index 2d1856e01e1..4a18bdadb3c 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -148,6 +148,19 @@ module.exports = function(Chart) { return borderWidth; }, + getPointRotation: function(point, index) { + var pointRotation = this.chart.options.elements.point.rotation; + var dataset = this.getDataset(); + var custom = point.custom || {}; + + if (!isNaN(custom.rotation)) { + pointRotation = custom.rotation; + } else if (!isNaN(dataset.pointRotation) || helpers.isArray(dataset.pointRotation)) { + pointRotation = helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointRotation); + } + return pointRotation; + }, + updateElement: function(point, index, reset) { var me = this; var meta = me.getMeta(); @@ -185,6 +198,7 @@ module.exports = function(Chart) { // Appearance radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), + rotation: me.getPointRotation(point, index), backgroundColor: me.getPointBackgroundColor(point, index), borderColor: me.getPointBorderColor(point, index), borderWidth: me.getPointBorderWidth(point, index), diff --git a/src/controllers/controller.radar.js b/src/controllers/controller.radar.js index 10d71cdacb5..89717157a37 100644 --- a/src/controllers/controller.radar.js +++ b/src/controllers/controller.radar.js @@ -106,6 +106,7 @@ module.exports = function(Chart) { borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), + rotation: custom.rotation ? custom.rotation : helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointElementOptions.rotation), // Tooltip hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius) diff --git a/src/elements/element.point.js b/src/elements/element.point.js index fa7c42ec641..2bcdc88f0f8 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -68,6 +68,7 @@ module.exports = Element.extend({ var model = this._model; var ctx = this._chart.ctx; var pointStyle = vm.pointStyle; + var rotation = vm.rotation; var radius = vm.radius; var x = vm.x; var y = vm.y; @@ -82,7 +83,7 @@ module.exports = Element.extend({ ctx.strokeStyle = vm.borderColor || defaultColor; ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth); ctx.fillStyle = vm.backgroundColor || defaultColor; - helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y); + helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); } } }); diff --git a/src/helpers/helpers.canvas.js b/src/helpers/helpers.canvas.js index 4bfae9c48fd..26f7d37212f 100644 --- a/src/helpers/helpers.canvas.js +++ b/src/helpers/helpers.canvas.js @@ -46,8 +46,9 @@ var exports = module.exports = { } }, - drawPoint: function(ctx, style, radius, x, y) { + drawPoint: function(ctx, style, radius, x, y, rotation) { var type, edgeLength, xOffset, yOffset, height, size; + rotation = rotation || 0; if (style && typeof style === 'object') { type = style.toString(); @@ -61,11 +62,15 @@ var exports = module.exports = { return; } + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rotation * Math.PI / 180); + switch (style) { // Default includes circle default: ctx.beginPath(); - ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.arc(0, 0, radius, 0, Math.PI * 2); ctx.closePath(); ctx.fill(); break; @@ -73,22 +78,22 @@ var exports = module.exports = { ctx.beginPath(); edgeLength = 3 * radius / Math.sqrt(3); height = edgeLength * Math.sqrt(3) / 2; - ctx.moveTo(x - edgeLength / 2, y + height / 3); - ctx.lineTo(x + edgeLength / 2, y + height / 3); - ctx.lineTo(x, y - 2 * height / 3); + ctx.moveTo(-edgeLength / 2, height / 3); + ctx.lineTo(edgeLength / 2, height / 3); + ctx.lineTo(0, -2 * height / 3); ctx.closePath(); ctx.fill(); break; case 'rect': size = 1 / Math.SQRT2 * radius; ctx.beginPath(); - ctx.fillRect(x - size, y - size, 2 * size, 2 * size); - ctx.strokeRect(x - size, y - size, 2 * size, 2 * size); + ctx.fillRect(-size, -size, 2 * size, 2 * size); + ctx.strokeRect(-size, -size, 2 * size, 2 * size); break; case 'rectRounded': var offset = radius / Math.SQRT2; - var leftX = x - offset; - var topY = y - offset; + var leftX = -offset; + var topY = -offset; var sideSize = Math.SQRT2 * radius; ctx.beginPath(); @@ -104,60 +109,61 @@ var exports = module.exports = { case 'rectRot': size = 1 / Math.SQRT2 * radius; ctx.beginPath(); - ctx.moveTo(x - size, y); - ctx.lineTo(x, y + size); - ctx.lineTo(x + size, y); - ctx.lineTo(x, y - size); + ctx.moveTo(-size, 0); + ctx.lineTo(0, size); + ctx.lineTo(size, 0); + ctx.lineTo(0, -size); ctx.closePath(); ctx.fill(); break; case 'cross': ctx.beginPath(); - ctx.moveTo(x, y + radius); - ctx.lineTo(x, y - radius); - ctx.moveTo(x - radius, y); - ctx.lineTo(x + radius, y); + ctx.moveTo(0, radius); + ctx.lineTo(0, -radius); + ctx.moveTo(-radius, 0); + ctx.lineTo(radius, 0); ctx.closePath(); break; case 'crossRot': ctx.beginPath(); xOffset = Math.cos(Math.PI / 4) * radius; yOffset = Math.sin(Math.PI / 4) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.moveTo(x - xOffset, y + yOffset); - ctx.lineTo(x + xOffset, y - yOffset); + ctx.moveTo(-xOffset, -yOffset); + ctx.lineTo(xOffset, yOffset); + ctx.moveTo(-xOffset, yOffset); + ctx.lineTo(xOffset, -yOffset); ctx.closePath(); break; case 'star': ctx.beginPath(); - ctx.moveTo(x, y + radius); - ctx.lineTo(x, y - radius); - ctx.moveTo(x - radius, y); - ctx.lineTo(x + radius, y); + ctx.moveTo(0, radius); + ctx.lineTo(0, -radius); + ctx.moveTo(-radius, 0); + ctx.lineTo(radius, 0); xOffset = Math.cos(Math.PI / 4) * radius; yOffset = Math.sin(Math.PI / 4) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.moveTo(x - xOffset, y + yOffset); - ctx.lineTo(x + xOffset, y - yOffset); + ctx.moveTo(-xOffset, -yOffset); + ctx.lineTo(xOffset, yOffset); + ctx.moveTo(-xOffset, yOffset); + ctx.lineTo(xOffset, -yOffset); ctx.closePath(); break; case 'line': ctx.beginPath(); - ctx.moveTo(x - radius, y); - ctx.lineTo(x + radius, y); + ctx.moveTo(-radius, 0); + ctx.lineTo(radius, 0); ctx.closePath(); break; case 'dash': ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x + radius, y); + ctx.moveTo(0, 0); + ctx.lineTo(radius, 0); ctx.closePath(); break; } ctx.stroke(); + ctx.restore(); }, clipArea: function(ctx, area) { diff --git a/test/specs/element.point.tests.js b/test/specs/element.point.tests.js index 0321112fb12..b2f803416b5 100644 --- a/test/specs/element.point.tests.js +++ b/test/specs/element.point.tests.js @@ -108,6 +108,7 @@ describe('Point element tests', function() { point._view = { radius: 2, pointStyle: 'circle', + rotation: 25, hitRadius: 3, borderColor: 'rgba(1, 2, 3, 1)', borderWidth: 6, @@ -128,12 +129,21 @@ describe('Point element tests', function() { }, { name: 'setFillStyle', args: ['rgba(0, 255, 0)'] + }, { + name: 'save', + args: [] + }, { + name: 'translate', + args: [10, 15] + }, { + name: 'rotate', + args: [25 * Math.PI / 180] }, { name: 'beginPath', args: [] }, { name: 'arc', - args: [10, 15, 2, 0, 2 * Math.PI] + args: [0, 0, 2, 0, 2 * Math.PI] }, { name: 'closePath', args: [], @@ -143,6 +153,9 @@ describe('Point element tests', function() { }, { name: 'stroke', args: [] + }, { + name: 'restore', + args: [] }]); mockContext.resetCalls(); @@ -158,18 +171,27 @@ describe('Point element tests', function() { }, { name: 'setFillStyle', args: ['rgba(0, 255, 0)'] + }, { + name: 'save', + args: [] + }, { + name: 'translate', + args: [10, 15] + }, { + name: 'rotate', + args: [25 * Math.PI / 180] }, { name: 'beginPath', args: [] }, { name: 'moveTo', - args: [10 - 3 * 2 / Math.sqrt(3) / 2, 15 + 3 * 2 / Math.sqrt(3) * Math.sqrt(3) / 2 / 3] + args: [0 - 3 * 2 / Math.sqrt(3) / 2, 0 + 3 * 2 / Math.sqrt(3) * Math.sqrt(3) / 2 / 3] }, { name: 'lineTo', - args: [10 + 3 * 2 / Math.sqrt(3) / 2, 15 + 3 * 2 / Math.sqrt(3) * Math.sqrt(3) / 2 / 3], + args: [0 + 3 * 2 / Math.sqrt(3) / 2, 0 + 3 * 2 / Math.sqrt(3) * Math.sqrt(3) / 2 / 3], }, { name: 'lineTo', - args: [10, 15 - 2 * 3 * 2 / Math.sqrt(3) * Math.sqrt(3) / 2 / 3], + args: [0, 0 - 2 * 3 * 2 / Math.sqrt(3) * Math.sqrt(3) / 2 / 3], }, { name: 'closePath', args: [], @@ -179,6 +201,9 @@ describe('Point element tests', function() { }, { name: 'stroke', args: [] + }, { + name: 'restore', + args: [] }]); mockContext.resetCalls(); @@ -194,18 +219,30 @@ describe('Point element tests', function() { }, { name: 'setFillStyle', args: ['rgba(0, 255, 0)'] + }, { + name: 'save', + args: [] + }, { + name: 'translate', + args: [10, 15] + }, { + name: 'rotate', + args: [25 * Math.PI / 180] }, { name: 'beginPath', args: [] }, { name: 'fillRect', - args: [10 - 1 / Math.SQRT2 * 2, 15 - 1 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2] + args: [0 - 1 / Math.SQRT2 * 2, 0 - 1 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2] }, { name: 'strokeRect', - args: [10 - 1 / Math.SQRT2 * 2, 15 - 1 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2] + args: [0 - 1 / Math.SQRT2 * 2, 0 - 1 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2, 2 / Math.SQRT2 * 2] }, { name: 'stroke', args: [] + }, { + name: 'restore', + args: [] }]); var drawRoundedRectangleSpy = jasmine.createSpy('drawRoundedRectangle'); @@ -218,8 +255,8 @@ describe('Point element tests', function() { expect(drawRoundedRectangleSpy).toHaveBeenCalledWith( mockContext, - 10 - offset, - 15 - offset, + 0 - offset, + 0 - offset, Math.SQRT2 * 2, Math.SQRT2 * 2, 2 * 0.425 @@ -245,21 +282,30 @@ describe('Point element tests', function() { }, { name: 'setFillStyle', args: ['rgba(0, 255, 0)'] + }, { + name: 'save', + args: [] + }, { + name: 'translate', + args: [10, 15] + }, { + name: 'rotate', + args: [25 * Math.PI / 180] }, { name: 'beginPath', args: [] }, { name: 'moveTo', - args: [10 - 1 / Math.SQRT2 * 2, 15] + args: [0 - 1 / Math.SQRT2 * 2, 0] }, { name: 'lineTo', - args: [10, 15 + 1 / Math.SQRT2 * 2] + args: [0, 0 + 1 / Math.SQRT2 * 2] }, { name: 'lineTo', - args: [10 + 1 / Math.SQRT2 * 2, 15], + args: [0 + 1 / Math.SQRT2 * 2, 0], }, { name: 'lineTo', - args: [10, 15 - 1 / Math.SQRT2 * 2], + args: [0, 0 - 1 / Math.SQRT2 * 2], }, { name: 'closePath', args: [] @@ -269,6 +315,9 @@ describe('Point element tests', function() { }, { name: 'stroke', args: [] + }, { + name: 'restore', + args: [] }]); mockContext.resetCalls(); @@ -284,27 +333,39 @@ describe('Point element tests', function() { }, { name: 'setFillStyle', args: ['rgba(0, 255, 0)'] + }, { + name: 'save', + args: [] + }, { + name: 'translate', + args: [10, 15] + }, { + name: 'rotate', + args: [25 * Math.PI / 180] }, { name: 'beginPath', args: [] }, { name: 'moveTo', - args: [10, 17] + args: [0, 2] }, { name: 'lineTo', - args: [10, 13], + args: [0, -2], }, { name: 'moveTo', - args: [8, 15], + args: [-2, 0], }, { name: 'lineTo', - args: [12, 15], + args: [2, 0], }, { name: 'closePath', args: [], }, { name: 'stroke', args: [] + }, { + name: 'restore', + args: [] }]); mockContext.resetCalls(); @@ -320,27 +381,39 @@ describe('Point element tests', function() { }, { name: 'setFillStyle', args: ['rgba(0, 255, 0)'] + }, { + name: 'save', + args: [] + }, { + name: 'translate', + args: [10, 15] + }, { + name: 'rotate', + args: [25 * Math.PI / 180] }, { name: 'beginPath', args: [] }, { name: 'moveTo', - args: [10 - Math.cos(Math.PI / 4) * 2, 15 - Math.sin(Math.PI / 4) * 2] + args: [0 - Math.cos(Math.PI / 4) * 2, 0 - Math.sin(Math.PI / 4) * 2] }, { name: 'lineTo', - args: [10 + Math.cos(Math.PI / 4) * 2, 15 + Math.sin(Math.PI / 4) * 2], + args: [0 + Math.cos(Math.PI / 4) * 2, 0 + Math.sin(Math.PI / 4) * 2], }, { name: 'moveTo', - args: [10 - Math.cos(Math.PI / 4) * 2, 15 + Math.sin(Math.PI / 4) * 2], + args: [0 - Math.cos(Math.PI / 4) * 2, 0 + Math.sin(Math.PI / 4) * 2], }, { name: 'lineTo', - args: [10 + Math.cos(Math.PI / 4) * 2, 15 - Math.sin(Math.PI / 4) * 2], + args: [0 + Math.cos(Math.PI / 4) * 2, 0 - Math.sin(Math.PI / 4) * 2], }, { name: 'closePath', args: [], }, { name: 'stroke', args: [] + }, { + name: 'restore', + args: [] }]); mockContext.resetCalls(); @@ -356,39 +429,51 @@ describe('Point element tests', function() { }, { name: 'setFillStyle', args: ['rgba(0, 255, 0)'] + }, { + name: 'save', + args: [] + }, { + name: 'translate', + args: [10, 15] + }, { + name: 'rotate', + args: [25 * Math.PI / 180] }, { name: 'beginPath', args: [] }, { name: 'moveTo', - args: [10, 17] + args: [0, 2] }, { name: 'lineTo', - args: [10, 13], + args: [0, -2], }, { name: 'moveTo', - args: [8, 15], + args: [-2, 0], }, { name: 'lineTo', - args: [12, 15], + args: [2, 0], }, { name: 'moveTo', - args: [10 - Math.cos(Math.PI / 4) * 2, 15 - Math.sin(Math.PI / 4) * 2] + args: [0 - Math.cos(Math.PI / 4) * 2, 0 - Math.sin(Math.PI / 4) * 2] }, { name: 'lineTo', - args: [10 + Math.cos(Math.PI / 4) * 2, 15 + Math.sin(Math.PI / 4) * 2], + args: [0 + Math.cos(Math.PI / 4) * 2, 0 + Math.sin(Math.PI / 4) * 2], }, { name: 'moveTo', - args: [10 - Math.cos(Math.PI / 4) * 2, 15 + Math.sin(Math.PI / 4) * 2], + args: [0 - Math.cos(Math.PI / 4) * 2, 0 + Math.sin(Math.PI / 4) * 2], }, { name: 'lineTo', - args: [10 + Math.cos(Math.PI / 4) * 2, 15 - Math.sin(Math.PI / 4) * 2], + args: [0 + Math.cos(Math.PI / 4) * 2, 0 - Math.sin(Math.PI / 4) * 2], }, { name: 'closePath', args: [], }, { name: 'stroke', args: [] + }, { + name: 'restore', + args: [] }]); mockContext.resetCalls(); @@ -404,21 +489,33 @@ describe('Point element tests', function() { }, { name: 'setFillStyle', args: ['rgba(0, 255, 0)'] + }, { + name: 'save', + args: [] + }, { + name: 'translate', + args: [10, 15] + }, { + name: 'rotate', + args: [25 * Math.PI / 180] }, { name: 'beginPath', args: [] }, { name: 'moveTo', - args: [8, 15] + args: [-2, 0] }, { name: 'lineTo', - args: [12, 15], + args: [2, 0], }, { name: 'closePath', args: [], }, { name: 'stroke', args: [] + }, { + name: 'restore', + args: [] }]); mockContext.resetCalls(); @@ -434,21 +531,33 @@ describe('Point element tests', function() { }, { name: 'setFillStyle', args: ['rgba(0, 255, 0)'] + }, { + name: 'save', + args: [] + }, { + name: 'translate', + args: [10, 15] + }, { + name: 'rotate', + args: [25 * Math.PI / 180] }, { name: 'beginPath', args: [] }, { name: 'moveTo', - args: [10, 15] + args: [0, 0] }, { name: 'lineTo', - args: [12, 15], + args: [2, 0], }, { name: 'closePath', args: [], }, { name: 'stroke', args: [] + }, { + name: 'restore', + args: [] }]); }); @@ -483,12 +592,21 @@ describe('Point element tests', function() { }, { name: 'setFillStyle', args: ['rgba(0,0,0,0.1)'] + }, { + name: 'save', + args: [] + }, { + name: 'translate', + args: [10, 15] + }, { + name: 'rotate', + args: [0] }, { name: 'beginPath', args: [] }, { name: 'arc', - args: [10, 15, 2, 0, 2 * Math.PI] + args: [0, 0, 2, 0, 2 * Math.PI] }, { name: 'closePath', args: [], @@ -498,6 +616,9 @@ describe('Point element tests', function() { }, { name: 'stroke', args: [] + }, { + name: 'restore', + args: [] }]); });