// Args are: (tooltipItem, data)
getBody: function(tooltipItems, data) {
- var lines = [];
+ var bodyItems = [];
+
+ helpers.each(tooltipItems, function(tooltipItem) {
+ var bodyItem = {
+ before: [],
+ lines: [],
+ after: []
+ };
+ helpers.pushAllIfDefined(this._options.callbacks.beforeLabel.call(this, tooltipItem, data), bodyItem.before);
+ helpers.pushAllIfDefined(this._options.callbacks.label.call(this, tooltipItem, data), bodyItem.lines);
+ helpers.pushAllIfDefined(this._options.callbacks.afterLabel.call(this, tooltipItem, data), bodyItem.after);
- helpers.each(tooltipItems, function(bodyItem) {
- helpers.pushAllIfDefined(this._options.callbacks.beforeLabel.call(this, bodyItem, data), lines);
- helpers.pushAllIfDefined(this._options.callbacks.label.call(this, bodyItem, data), lines);
- helpers.pushAllIfDefined(this._options.callbacks.afterLabel.call(this, bodyItem, data), lines);
+ bodyItems.push(bodyItem);
}, this);
- return lines;
+ return bodyItems;
},
// Args are: (tooltipItem, data)
height: vm.yPadding * 2, // Tooltip Padding
width: 0
};
- var combinedBodyLength = vm.body.length + vm.beforeBody.length + vm.afterBody.length;
+
+
+ var combinedBodyLength = vm.body.reduce(function(count, bodyItem) {
+ return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
+ }, 0);
+ // Count in before and after body sections
+ combinedBodyLength += vm.beforeBody.length + vm.afterBody.length;
size.height += vm.title.length * vm.titleFontSize; // Title Lines
size.height += (vm.title.length - 1) * vm.titleSpacing; // Title Line Spacing
helpers.each(vm.beforeBody.concat(vm.afterBody), function(line) {
size.width = Math.max(size.width, ctx.measureText(line).width);
});
- helpers.each(vm.body, function(line) {
- size.width = Math.max(size.width, ctx.measureText(line).width + (this._options.mode !== 'single' ? (vm.bodyFontSize + 2) : 0));
- }, this);
+
+ var _this = this;
+ var maxBodyWidth = function(line) {
+ size.width = Math.max(size.width, ctx.measureText(line).width + (_this._options.mode !== 'single' ? (vm.bodyFontSize + 2) : 0));
+ };
+ helpers.each(vm.body, function(bodyItem) {
+ helpers.each(bodyItem.before, maxBodyWidth);
+ helpers.each(bodyItem.lines, maxBodyWidth);
+ helpers.each(bodyItem.after, maxBodyWidth);
+ });
ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
helpers.each(vm.footer, function(line) {
pt.y += vm.bodyFontSize + vm.bodySpacing;
});
- helpers.each(vm.body, function(body, i) {
- // Draw Legend-like boxes if needed
- if (this._options.mode !== 'single') {
- // Fill a white rect so that colours merge nicely if the opacity is < 1
- ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString();
- ctx.fillRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize);
+ helpers.each(vm.body, function(bodyItem, i) {
+ var _this = this;
+ var fillLine = function(line) {
+ // Body Line
+ ctx.fillText(line, pt.x + (_this._options.mode !== 'single' ? (vm.bodyFontSize + 2) : 0), pt.y);
+ pt.y += vm.bodyFontSize + vm.bodySpacing;
+ };
+
+ helpers.each(bodyItem.before, fillLine);
- // Border
- ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString();
- ctx.strokeRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize);
+ helpers.each(bodyItem.lines, function(line) {
+ // Draw Legend-like boxes if needed
+ if (this._options.mode !== 'single') {
+ // Fill a white rect so that colours merge nicely if the opacity is < 1
+ ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString();
+ ctx.fillRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize);
- // Inner square
- ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString();
- ctx.fillRect(pt.x + 1, pt.y + 1, vm.bodyFontSize - 2, vm.bodyFontSize - 2);
+ // Border
+ ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString();
+ ctx.strokeRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize);
- ctx.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).rgbaString(); // Return fill style for text
- }
+ // Inner square
+ ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString();
+ ctx.fillRect(pt.x + 1, pt.y + 1, vm.bodyFontSize - 2, vm.bodyFontSize - 2);
- // Body Line
- ctx.fillText(body, pt.x + (this._options.mode !== 'single' ? (vm.bodyFontSize + 2) : 0), pt.y);
+ ctx.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).rgbaString(); // Return fill style for text
+ }
- pt.y += vm.bodyFontSize + vm.bodySpacing;
+ fillLine(line);
+ }, this);
+
+ helpers.each(bodyItem.after, fillLine);
}, this);
// After Body
--- /dev/null
+// Test the rectangle element
+describe('tooltip tests', function() {
+
+ beforeEach(function() {
+ window.addDefaultMatchers(jasmine);
+ });
+
+ afterEach(function() {
+ window.releaseAllCharts();
+ });
+
+ it('Should display in label mode', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 20, 30],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ },
+ options: {
+ tooltips: {
+ mode: 'label'
+ }
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta = chartInstance.getDatasetMeta(0);
+ var point = meta.data[1];
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+
+ var evt = new MouseEvent('mousemove', {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + point._model.x,
+ clientY: rect.top + point._model.y
+ });
+
+ // Manully trigger rather than having an async test
+ node.dispatchEvent(evt);
+
+ // Check and see if tooltip was displayed
+ var tooltip = chartInstance.tooltip;
+ var globalDefaults = Chart.defaults.global;
+
+ expect(tooltip._view).toEqual(jasmine.objectContaining({
+ // Positioning
+ xPadding: 6,
+ yPadding: 6,
+ xAlign: 'left',
+ yAlign: 'center',
+
+ // Body
+ bodyColor: '#fff',
+ _bodyFontFamily: globalDefaults.defaultFontFamily,
+ _bodyFontStyle: globalDefaults.defaultFontStyle,
+ _bodyAlign: 'left',
+ bodyFontSize: globalDefaults.defaultFontSize,
+ bodySpacing: 2,
+
+ // Title
+ titleColor: '#fff',
+ _titleFontFamily: globalDefaults.defaultFontFamily,
+ _titleFontStyle: 'bold',
+ titleFontSize: globalDefaults.defaultFontSize,
+ _titleAlign: 'left',
+ titleSpacing: 2,
+ titleMarginBottom: 6,
+
+ // Footer
+ footerColor: '#fff',
+ _footerFontFamily: globalDefaults.defaultFontFamily,
+ _footerFontStyle: 'bold',
+ footerFontSize: globalDefaults.defaultFontSize,
+ _footerAlign: 'left',
+ footerSpacing: 2,
+ footerMarginTop: 6,
+
+ // Appearance
+ caretSize: 5,
+ cornerRadius: 6,
+ backgroundColor: 'rgba(0,0,0,0.8)',
+ opacity: 1,
+ legendColorBackground: '#fff',
+
+ // Text
+ title: ['Point 2'],
+ beforeBody: [],
+ body: [{
+ before: [],
+ lines: ['Dataset 1: 20'],
+ after: []
+ }, {
+ before: [],
+ lines: ['Dataset 2: 40'],
+ after: []
+ }],
+ afterBody: [],
+ footer: [],
+ x: 269,
+ y: 155,
+ caretPadding: 2,
+ labelColors: [{
+ borderColor: 'rgb(255, 0, 0)',
+ backgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ borderColor: 'rgb(0, 0, 255)',
+ backgroundColor: 'rgb(0, 255, 255)'
+ }]
+ }));
+ });
+
+ it('Should display in single mode', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 20, 30],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ },
+ options: {
+ tooltips: {
+ mode: 'single'
+ }
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta = chartInstance.getDatasetMeta(0);
+ var point = meta.data[1];
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+
+ var evt = new MouseEvent('mousemove', {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + point._model.x,
+ clientY: rect.top + point._model.y
+ });
+
+ // Manully trigger rather than having an async test
+ node.dispatchEvent(evt);
+
+ // Check and see if tooltip was displayed
+ var tooltip = chartInstance.tooltip;
+ var globalDefaults = Chart.defaults.global;
+
+ expect(tooltip._view).toEqual(jasmine.objectContaining({
+ // Positioning
+ xPadding: 6,
+ yPadding: 6,
+ xAlign: 'left',
+ yAlign: 'center',
+
+ // Body
+ bodyColor: '#fff',
+ _bodyFontFamily: globalDefaults.defaultFontFamily,
+ _bodyFontStyle: globalDefaults.defaultFontStyle,
+ _bodyAlign: 'left',
+ bodyFontSize: globalDefaults.defaultFontSize,
+ bodySpacing: 2,
+
+ // Title
+ titleColor: '#fff',
+ _titleFontFamily: globalDefaults.defaultFontFamily,
+ _titleFontStyle: 'bold',
+ titleFontSize: globalDefaults.defaultFontSize,
+ _titleAlign: 'left',
+ titleSpacing: 2,
+ titleMarginBottom: 6,
+
+ // Footer
+ footerColor: '#fff',
+ _footerFontFamily: globalDefaults.defaultFontFamily,
+ _footerFontStyle: 'bold',
+ footerFontSize: globalDefaults.defaultFontSize,
+ _footerAlign: 'left',
+ footerSpacing: 2,
+ footerMarginTop: 6,
+
+ // Appearance
+ caretSize: 5,
+ cornerRadius: 6,
+ backgroundColor: 'rgba(0,0,0,0.8)',
+ opacity: 1,
+ legendColorBackground: '#fff',
+
+ // Text
+ title: ['Point 2'],
+ beforeBody: [],
+ body: [{
+ before: [],
+ lines: ['Dataset 1: 20'],
+ after: []
+ }],
+ afterBody: [],
+ footer: [],
+ x: 269,
+ y: 312,
+ caretPadding: 2,
+ labelColors: []
+ }));
+ });
+
+ it('Should display information from user callbacks', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 20, 30],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ },
+ options: {
+ tooltips: {
+ mode: 'label',
+ callbacks: {
+ beforeTitle: function() {
+ return 'beforeTitle';
+ },
+ title: function() {
+ return 'title';
+ },
+ afterTitle: function() {
+ return 'afterTitle'
+ },
+ beforeBody: function() {
+ return 'beforeBody';
+ },
+ beforeLabel: function() {
+ return 'beforeLabel';
+ },
+ label: function() {
+ return 'label';
+ },
+ afterLabel: function() {
+ return 'afterLabel';
+ },
+ afterBody: function() {
+ return 'afterBody';
+ },
+ beforeFooter: function() {
+ return 'beforeFooter';
+ },
+ footer: function() {
+ return 'footer';
+ },
+ afterFooter: function() {
+ return 'afterFooter'
+ }
+ }
+ }
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta = chartInstance.getDatasetMeta(0);
+ var point = meta.data[1];
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+
+ var evt = new MouseEvent('mousemove', {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + point._model.x,
+ clientY: rect.top + point._model.y
+ });
+
+ // Manully trigger rather than having an async test
+ node.dispatchEvent(evt);
+
+ // Check and see if tooltip was displayed
+ var tooltip = chartInstance.tooltip;
+ var globalDefaults = Chart.defaults.global;
+
+ expect(tooltip._view).toEqual(jasmine.objectContaining({
+ // Positioning
+ xPadding: 6,
+ yPadding: 6,
+ xAlign: 'center',
+ yAlign: 'top',
+
+ // Body
+ bodyColor: '#fff',
+ _bodyFontFamily: globalDefaults.defaultFontFamily,
+ _bodyFontStyle: globalDefaults.defaultFontStyle,
+ _bodyAlign: 'left',
+ bodyFontSize: globalDefaults.defaultFontSize,
+ bodySpacing: 2,
+
+ // Title
+ titleColor: '#fff',
+ _titleFontFamily: globalDefaults.defaultFontFamily,
+ _titleFontStyle: 'bold',
+ titleFontSize: globalDefaults.defaultFontSize,
+ _titleAlign: 'left',
+ titleSpacing: 2,
+ titleMarginBottom: 6,
+
+ // Footer
+ footerColor: '#fff',
+ _footerFontFamily: globalDefaults.defaultFontFamily,
+ _footerFontStyle: 'bold',
+ footerFontSize: globalDefaults.defaultFontSize,
+ _footerAlign: 'left',
+ footerSpacing: 2,
+ footerMarginTop: 6,
+
+ // Appearance
+ caretSize: 5,
+ cornerRadius: 6,
+ backgroundColor: 'rgba(0,0,0,0.8)',
+ opacity: 1,
+ legendColorBackground: '#fff',
+
+ // Text
+ title: ['beforeTitle', 'title', 'afterTitle'],
+ beforeBody: ['beforeBody'],
+ body: [{
+ before: ['beforeLabel'],
+ lines: ['label'],
+ after: ['afterLabel']
+ }, {
+ before: ['beforeLabel'],
+ lines: ['label'],
+ after: ['afterLabel']
+ }],
+ afterBody: ['afterBody'],
+ footer: ['beforeFooter', 'footer', 'afterFooter'],
+ x: 216,
+ y: 190,
+ caretPadding: 2,
+ labelColors: [{
+ borderColor: 'rgb(255, 0, 0)',
+ backgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ borderColor: 'rgb(0, 0, 255)',
+ backgroundColor: 'rgb(0, 255, 255)'
+ }]
+ }));
+ });
+});