diff --git a/docs/00-Getting-Started.md b/docs/00-Getting-Started.md index edf077a2b..2baa14ad2 100644 --- a/docs/00-Getting-Started.md +++ b/docs/00-Getting-Started.md @@ -209,6 +209,7 @@ line | - | - | - *line*.fill | Boolean | true | point | - | - | - *point*.radius | Number | 3 | Default point radius +*point*.pointStyle | String | 'circle' | Default point style *point*.backgroundColor | Color | `Chart.defaults.global.defaultColor` | Default point fill color *point*.borderWidth | Number | 1 | Default point stroke width *point*.borderColor | Color | `Chart.defaults.global.defaultColor` | Default point stroke color diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index a89590991..4d2df3bb6 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -186,6 +186,7 @@ // Appearance tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension), radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius), + pointStyle: point.custom && point.custom.pointStyle ? point.custom.pointStyle : helpers.getValueAtIndexOrDefault(this.getDataset().pointStyle, index, this.chart.options.elements.point.pointStyle), backgroundColor: this.getPointBackgroundColor(point, index), borderColor: this.getPointBorderColor(point, index), borderWidth: this.getPointBorderWidth(point, index), diff --git a/src/elements/element.point.js b/src/elements/element.point.js index 94565e3ca..314ee4ce6 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -19,6 +19,7 @@ Chart.defaults.global.elements.point = { radius: 3, + pointStyle: 'circle', backgroundColor: Chart.defaults.global.defaultColor, borderWidth: 1, borderColor: Chart.defaults.global.defaultColor, @@ -69,17 +70,88 @@ if (vm.radius > 0 || vm.borderWidth > 0) { - ctx.beginPath(); - - ctx.arc(vm.x, vm.y, vm.radius || Chart.defaults.global.elements.point.radius, 0, Math.PI * 2); - ctx.closePath(); - ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth; ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; - ctx.fill(); + var radius = vm.radius || Chart.defaults.global.elements.point.radius; + + switch (vm.pointStyle) { + case 'circle': + default: + ctx.beginPath(); + ctx.arc(vm.x, vm.y, radius, 0, Math.PI * 2); + ctx.closePath(); + ctx.fill(); + break; + case 'triangle': + ctx.beginPath(); + var edgeLength = 3 * radius / Math.sqrt(3); + var height = edgeLength * Math.sqrt(3) / 2; + ctx.moveTo(vm.x - edgeLength / 2, vm.y + height / 3); + ctx.lineTo(vm.x + edgeLength / 2, vm.y + height / 3); + ctx.lineTo(vm.x, vm.y - 2 * height / 3); + ctx.closePath(); + ctx.fill(); + break; + case 'rect': + ctx.fillRect(vm.x - radius, vm.y - radius, 2 * radius, 2 * radius); + ctx.strokeRect(vm.x - radius, vm.y - radius, 2 * radius, 2 * radius); + break; + case 'rectRot': + ctx.translate(vm.x, vm.y); + ctx.rotate(Math.PI / 4); + ctx.fillRect(-radius, -radius, 2 * radius, 2 * radius); + ctx.strokeRect(-radius, -radius, 2 * radius, 2 * radius); + ctx.setTransform(1, 0, 0, 1, 0, 0); + break; + case 'cross': + ctx.beginPath(); + ctx.moveTo(vm.x, vm.y + radius); + ctx.lineTo(vm.x, vm.y - radius); + ctx.moveTo(vm.x - radius, vm.y); + ctx.lineTo(vm.x + radius, vm.y); + ctx.closePath(); + break; + case 'crossRot': + ctx.beginPath(); + var xOffset = Math.cos(Math.PI / 4) * radius; + var yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(vm.x - xOffset, vm.y - yOffset); + ctx.lineTo(vm.x + xOffset, vm.y + yOffset); + ctx.moveTo(vm.x - xOffset, vm.y + yOffset); + ctx.lineTo(vm.x + xOffset, vm.y - yOffset); + ctx.closePath(); + break; + case 'star': + ctx.beginPath(); + ctx.moveTo(vm.x, vm.y + radius); + ctx.lineTo(vm.x, vm.y - radius); + ctx.moveTo(vm.x - radius, vm.y); + ctx.lineTo(vm.x + radius, vm.y); + var xOffset = Math.cos(Math.PI / 4) * radius; + var yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(vm.x - xOffset, vm.y - yOffset); + ctx.lineTo(vm.x + xOffset, vm.y + yOffset); + ctx.moveTo(vm.x - xOffset, vm.y + yOffset); + ctx.lineTo(vm.x + xOffset, vm.y - yOffset); + ctx.closePath(); + break; + case 'line': + ctx.beginPath(); + ctx.moveTo(vm.x - radius, vm.y); + ctx.lineTo(vm.x + radius, vm.y); + ctx.closePath(); + break; + case 'dash': + ctx.beginPath(); + ctx.moveTo(vm.x, vm.y); + ctx.lineTo(vm.x + radius, vm.y); + ctx.closePath(); + break; + } + ctx.stroke(); } } diff --git a/test/controller.line.tests.js b/test/controller.line.tests.js index 136c93c17..3cf921c14 100644 --- a/test/controller.line.tests.js +++ b/test/controller.line.tests.js @@ -238,6 +238,7 @@ describe('Line controller tests', function() { hoverRadius: 4, hoverBorderWidth: 1, radius: 3, + pointStyle: 'circle' } }, scales: { @@ -281,6 +282,7 @@ describe('Line controller tests', function() { borderColor: Chart.defaults.global.defaultColor, hitRadius: 1, radius: 3, + pointStyle: 'circle', skip: false, tension: 0.1, @@ -301,6 +303,7 @@ describe('Line controller tests', function() { borderColor: Chart.defaults.global.defaultColor, hitRadius: 1, radius: 3, + pointStyle: 'circle', skip: false, tension: 0.1, @@ -321,6 +324,7 @@ describe('Line controller tests', function() { borderColor: Chart.defaults.global.defaultColor, hitRadius: 1, radius: 3, + pointStyle: 'circle', skip: false, tension: 0.1, @@ -341,6 +345,7 @@ describe('Line controller tests', function() { borderColor: Chart.defaults.global.defaultColor, hitRadius: 1, radius: 3, + pointStyle: 'circle', skip: false, tension: 0.1, @@ -397,6 +402,7 @@ describe('Line controller tests', function() { borderColor: 'rgb(56, 57, 58)', hitRadius: 3.3, radius: 22, + pointStyle: 'circle', skip: false, tension: 0, @@ -417,6 +423,7 @@ describe('Line controller tests', function() { borderColor: 'rgb(56, 57, 58)', hitRadius: 3.3, radius: 22, + pointStyle: 'circle', skip: false, tension: 0, @@ -437,6 +444,7 @@ describe('Line controller tests', function() { borderColor: 'rgb(56, 57, 58)', hitRadius: 3.3, radius: 22, + pointStyle: 'circle', skip: false, tension: 0, @@ -457,6 +465,7 @@ describe('Line controller tests', function() { borderColor: 'rgb(56, 57, 58)', hitRadius: 3.3, radius: 22, + pointStyle: 'circle', skip: false, tension: 0, @@ -519,6 +528,7 @@ describe('Line controller tests', function() { borderColor: 'rgb(4, 6, 8)', hitRadius: 5, radius: 2.2, + pointStyle: 'circle', skip: true, tension: 0.15, diff --git a/test/element.point.tests.js b/test/element.point.tests.js index 8a4052ca7..1a7bef64c 100644 --- a/test/element.point.tests.js +++ b/test/element.point.tests.js @@ -77,6 +77,7 @@ describe('Point element tests', function() { // Attach a view object as if we were the controller point._view = { radius: 2, + pointStyle: 'circle', hitRadius: 3, borderColor: 'rgba(1, 2, 3, 1)', borderWidth: 6, @@ -89,15 +90,6 @@ describe('Point element tests', function() { point.draw(); expect(mockContext.getCalls()).toEqual([{ - name: 'beginPath', - args: [] - }, { - name: 'arc', - args: [10, 15, 2, 0, 2 * Math.PI] - }, { - name: 'closePath', - args: [], - }, { name: 'setStrokeStyle', args: ['rgba(1, 2, 3, 1)'] }, { @@ -106,6 +98,15 @@ describe('Point element tests', function() { }, { name: 'setFillStyle', args: ['rgba(0, 255, 0)'] + }, { + name: 'beginPath', + args: [] + }, { + name: 'arc', + args: [10, 15, 2, 0, 2 * Math.PI] + }, { + name: 'closePath', + args: [], }, { name: 'fill', args: [], @@ -113,6 +114,280 @@ describe('Point element tests', function() { name: 'stroke', args: [] }]); + + mockContext.resetCalls(); + point._view.pointStyle = 'triangle'; + point.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'setStrokeStyle', + args: ['rgba(1, 2, 3, 1)'] + }, { + name: 'setLineWidth', + args: [6] + }, { + name: 'setFillStyle', + args: ['rgba(0, 255, 0)'] + }, { + name: 'beginPath', + args: [] + }, { + name: 'moveTo', + args: [10 - 3 * 2 / Math.sqrt(3) / 2, 15 + 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], + }, { + name: 'lineTo', + args: [10, 15 - 2 * 3 * 2 / Math.sqrt(3) * Math.sqrt(3) / 2 / 3], + }, { + name: 'closePath', + args: [], + }, { + name: 'fill', + args: [], + }, { + name: 'stroke', + args: [] + }]); + + mockContext.resetCalls(); + point._view.pointStyle = 'rect'; + point.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'setStrokeStyle', + args: ['rgba(1, 2, 3, 1)'] + }, { + name: 'setLineWidth', + args: [6] + }, { + name: 'setFillStyle', + args: ['rgba(0, 255, 0)'] + }, { + name: 'fillRect', + args: [8, 13, 4, 4] + }, { + name: 'strokeRect', + args: [8, 13, 4, 4] + }, { + name: 'stroke', + args: [] + }]); + + mockContext.resetCalls(); + point._view.pointStyle = 'rectRot'; + point.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'setStrokeStyle', + args: ['rgba(1, 2, 3, 1)'] + }, { + name: 'setLineWidth', + args: [6] + }, { + name: 'setFillStyle', + args: ['rgba(0, 255, 0)'] + }, { + name: 'translate', + args: [10, 15] + }, { + name: 'rotate', + args: [Math.PI / 4] + }, { + name: 'fillRect', + args: [-2, -2, 4, 4], + }, { + name: 'strokeRect', + args: [-2, -2, 4, 4], + }, { + name: 'setTransform', + args: [1, 0, 0, 1, 0, 0], + }, { + name: 'stroke', + args: [] + }]); + + mockContext.resetCalls(); + point._view.pointStyle = 'cross'; + point.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'setStrokeStyle', + args: ['rgba(1, 2, 3, 1)'] + }, { + name: 'setLineWidth', + args: [6] + }, { + name: 'setFillStyle', + args: ['rgba(0, 255, 0)'] + }, { + name: 'beginPath', + args: [] + }, { + name: 'moveTo', + args: [10, 17] + }, { + name: 'lineTo', + args: [10, 13], + }, { + name: 'moveTo', + args: [8, 15], + }, { + name: 'lineTo', + args: [12, 15], + },{ + name: 'closePath', + args: [], + }, { + name: 'stroke', + args: [] + }]); + + mockContext.resetCalls(); + point._view.pointStyle = 'crossRot'; + point.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'setStrokeStyle', + args: ['rgba(1, 2, 3, 1)'] + }, { + name: 'setLineWidth', + args: [6] + }, { + name: 'setFillStyle', + args: ['rgba(0, 255, 0)'] + }, { + name: 'beginPath', + args: [] + }, { + name: 'moveTo', + args: [10 - Math.cos(Math.PI / 4) * 2, 15 - Math.sin(Math.PI / 4) * 2] + }, { + name: 'lineTo', + args: [10 + Math.cos(Math.PI / 4) * 2, 15 + Math.sin(Math.PI / 4) * 2], + }, { + name: 'moveTo', + args: [10 - Math.cos(Math.PI / 4) * 2, 15 + Math.sin(Math.PI / 4) * 2], + }, { + name: 'lineTo', + args: [10 + Math.cos(Math.PI / 4) * 2, 15 - Math.sin(Math.PI / 4) * 2], + }, { + name: 'closePath', + args: [], + }, { + name: 'stroke', + args: [] + }]); + + mockContext.resetCalls(); + point._view.pointStyle = 'star'; + point.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'setStrokeStyle', + args: ['rgba(1, 2, 3, 1)'] + }, { + name: 'setLineWidth', + args: [6] + }, { + name: 'setFillStyle', + args: ['rgba(0, 255, 0)'] + }, { + name: 'beginPath', + args: [] + }, { + name: 'moveTo', + args: [10, 17] + }, { + name: 'lineTo', + args: [10, 13], + }, { + name: 'moveTo', + args: [8, 15], + }, { + name: 'lineTo', + args: [12, 15], + },{ + name: 'moveTo', + args: [10 - Math.cos(Math.PI / 4) * 2, 15 - Math.sin(Math.PI / 4) * 2] + }, { + name: 'lineTo', + args: [10 + Math.cos(Math.PI / 4) * 2, 15 + Math.sin(Math.PI / 4) * 2], + }, { + name: 'moveTo', + args: [10 - Math.cos(Math.PI / 4) * 2, 15 + Math.sin(Math.PI / 4) * 2], + }, { + name: 'lineTo', + args: [10 + Math.cos(Math.PI / 4) * 2, 15 - Math.sin(Math.PI / 4) * 2], + }, { + name: 'closePath', + args: [], + }, { + name: 'stroke', + args: [] + }]); + + mockContext.resetCalls(); + point._view.pointStyle = 'line'; + point.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'setStrokeStyle', + args: ['rgba(1, 2, 3, 1)'] + }, { + name: 'setLineWidth', + args: [6] + }, { + name: 'setFillStyle', + args: ['rgba(0, 255, 0)'] + }, { + name: 'beginPath', + args: [] + }, { + name: 'moveTo', + args: [8, 15] + }, { + name: 'lineTo', + args: [12, 15], + }, { + name: 'closePath', + args: [], + }, { + name: 'stroke', + args: [] + }]); + + mockContext.resetCalls(); + point._view.pointStyle = 'dash'; + point.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'setStrokeStyle', + args: ['rgba(1, 2, 3, 1)'] + }, { + name: 'setLineWidth', + args: [6] + }, { + name: 'setFillStyle', + args: ['rgba(0, 255, 0)'] + }, { + name: 'beginPath', + args: [] + }, { + name: 'moveTo', + args: [10, 15] + }, { + name: 'lineTo', + args: [12, 15], + }, { + name: 'closePath', + args: [], + }, { + name: 'stroke', + args: [] + }]); + }); it ('should draw correctly with default settings if necessary', function() { @@ -137,15 +412,6 @@ describe('Point element tests', function() { point.draw(); expect(mockContext.getCalls()).toEqual([{ - name: 'beginPath', - args: [] - }, { - name: 'arc', - args: [10, 15, 2, 0, 2 * Math.PI] - }, { - name: 'closePath', - args: [], - }, { name: 'setStrokeStyle', args: ['rgba(0,0,0,0.1)'] }, { @@ -154,6 +420,15 @@ describe('Point element tests', function() { }, { name: 'setFillStyle', args: ['rgba(0,0,0,0.1)'] + }, { + name: 'beginPath', + args: [] + }, { + name: 'arc', + args: [10, 15, 2, 0, 2 * Math.PI] + }, { + name: 'closePath', + args: [], }, { name: 'fill', args: [], diff --git a/test/mockContext.js b/test/mockContext.js index 6559bc7b4..38755362c 100644 --- a/test/mockContext.js +++ b/test/mockContext.js @@ -82,7 +82,9 @@ save: function() {}, setLineDash: function() {}, stroke: function() {}, - translate: function() {}, + strokeRect: function(x, y, w, h) {}, + setTransform: function(a, b, c, d, e, f) {}, + translate: function(x, y) {}, }; // attach methods to the class itself