| [`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
| `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.
| `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.
| `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.
| -----| ---- | --------| -----------
| `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.
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,
'hoverBorderWidth',
'hoverRadius',
'hitRadius',
- 'pointStyle'
+ 'pointStyle',
+ 'rotation'
];
for (i = 0, ilen = keys.length; i < ilen; ++i) {
dataset.radius,
options.radius
], context, index);
-
return values;
}
});
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();
// 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),
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)
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;
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);
}
}
});
}
},
- 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();
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;
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();
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) {
point._view = {
radius: 2,
pointStyle: 'circle',
+ rotation: 25,
hitRadius: 3,
borderColor: 'rgba(1, 2, 3, 1)',
borderWidth: 6,
}, {
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: [],
}, {
name: 'stroke',
args: []
+ }, {
+ name: 'restore',
+ args: []
}]);
mockContext.resetCalls();
}, {
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: [],
}, {
name: 'stroke',
args: []
+ }, {
+ name: 'restore',
+ args: []
}]);
mockContext.resetCalls();
}, {
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');
expect(drawRoundedRectangleSpy).toHaveBeenCalledWith(
mockContext,
- 10 - offset,
- 15 - offset,
+ 0 - offset,
+ 0 - offset,
Math.SQRT2 * 2,
Math.SQRT2 * 2,
2 * 0.425
}, {
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: []
}, {
name: 'stroke',
args: []
+ }, {
+ name: 'restore',
+ args: []
}]);
mockContext.resetCalls();
}, {
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();
}, {
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();
}, {
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();
}, {
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();
}, {
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: []
}]);
});
}, {
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: [],
}, {
name: 'stroke',
args: []
+ }, {
+ name: 'restore',
+ args: []
}]);
});