Add TooltipService to generate custom nvd3 tooltips

This adds a new TooltipService with a method for generating custom
tooltip functions for nvd3 charts. These functions can include
arbitary information about the current datapoint rather than the
plain numeric value that nvd3 displays by default.

Change-Id: Ib769b76d6aaad0bb080116c55b5b8a8e0c30aa92
This commit is contained in:
Tim Buckley 2016-03-07 19:26:56 -07:00
parent e355c3bd4e
commit b67003a8fe
2 changed files with 211 additions and 0 deletions

132
app/js/services/tooltip.js Normal file
View File

@ -0,0 +1,132 @@
'use strict';
var angular = require('angular');
var servicesModule = require('./_index.js');
function TooltipService() {
var service = {};
var render = function(dest, value, d) {
if (angular.isFunction(value)) {
value = value(d);
}
if (angular.isElement(value)) {
dest.append(value);
} else {
dest.text(value);
}
return dest;
};
var fill = function(dest, element, count) {
for (var i = 0; i < count; i++) {
dest.append(element.clone());
}
};
service.generator = function(content, options) {
options = options || {};
return function(d) {
// partially render content first so we can determine the column count
var columns = 0;
var columnOffset = 0;
var partialContent = [];
var table = angular.element('<table>');
table.addClass('osh-tooltip');
if (options.addonClass) {
table.addClass(options.addonClass);
}
angular.forEach(content, function(row) {
// row can be a function to output colum values (or more functions)
if (angular.isFunction(row)) {
row = row(d);
}
var values = [];
angular.forEach(row, function(col, i) {
values.push(col);
if (i + 1 > columns) {
columns = i + 1;
}
});
partialContent.push(values);
});
// build the header, if any
if (options.title || options.header) {
var thead = angular.element('<thead>');
if (options.colors) {
columnOffset++;
}
if (options.title) {
var tr = angular.element('<tr>');
var th = render(
angular.element('<th>').attr('colspan', columns + columnOffset),
options.title, d);
tr.append(th);
thead.append(tr);
}
if (options.header) {
var tr = angular.element('<tr>');
fill(tr, angular.element('<th>'), columnOffset);
angular.forEach(options.header, function(title) {
tr.append(render(angular.element('<th>'), title, d));
});
thead.append(tr);
}
table.append(thead);
}
// build the body
var tbody = angular.element('<tbody>');
angular.forEach(partialContent, function(row, rowIndex) {
var tr = angular.element('<tr>');
if (options.colors && options.colors[rowIndex]) {
var td = angular.element('<td>');
td.addClass('legend-color-guide');
var div = angular.element('<div>');
div.css('background-color', options.colors[rowIndex]);
td.append(div);
tr.append(td);
fill(tr, angular.element('<td>'), columnOffset - 1);
} else {
fill(tr, angular.element('<td>'), columnOffset);
}
angular.forEach(row, function(col, i) {
var td = render(angular.element('<td>'), col, d);
if (row.length < columns && i == row.length - 1) {
// auto-set colspan for last entry in row
td.attr('colspan', columns - i);
}
tr.append(td);
});
tbody.append(tr);
});
table.append(tbody);
return table[0].outerHTML;
};
};
return service;
}
servicesModule.service('tooltipService', TooltipService);

View File

@ -0,0 +1,79 @@
describe('TooltipService', function() {
var $compile;
var tooltipService;
var sampleData;
beforeEach(function() {
module('app');
module('app.services');
inject(function($injector) {
$compile = $injector.get('$compile');
tooltipService = $injector.get('tooltipService');
});
sampleData = {
index: 0,
value: 0,
color: 'green',
data: { custom: 123 }
};
});
it('should generate a simple tooltip', function() {
var generator = tooltipService.generator([['Value']]);
var element = angular.element(generator(sampleData))[0];
expect(element.classList).toContain('osh-tooltip');
expect(element.querySelectorAll('tr').length).toEqual(1);
expect(element.querySelectorAll('td').length).toEqual(1);
expect(element.querySelectorAll('th').length).toEqual(0);
});
it('should generate a tooltip with a header', function() {
var generator = tooltipService.generator([
['Value']
], { title: 'test', header: ['column'] });
var element = angular.element(generator(sampleData))[0];
expect(element.querySelectorAll('tr').length).toEqual(3);
expect(element.querySelectorAll('thead tr').length).toEqual(2);
expect(element.querySelectorAll('thead tr')[0].innerText).toEqual('test');
expect(element.querySelectorAll('thead tr')[1].innerText).toEqual('column');
expect(element.querySelector('tbody tr td').innerText).toEqual('Value');
});
it('should generate a tooltip with custom columns', function() {
var generator = tooltipService.generator([
['Value', function(d) { return d.value; }],
['Custom', function(d) { return d.data.custom; }]
]);
var element = angular.element(generator(sampleData))[0];
expect(element.querySelectorAll('tr').length).toEqual(2);
expect(element.querySelectorAll('td').length).toEqual(4);
var rows = element.querySelectorAll('tbody tr');
expect(rows[0].children[0].innerText).toEqual('Value');
expect(rows[0].children[1].innerText).toEqual('0');
expect(rows[1].children[0].innerText).toEqual('Custom');
expect(rows[1].children[1].innerText).toEqual('123');
});
it('should generate a tooltip with colors', function() {
var generator = tooltipService.generator([
['Value', function(d) { return d.value; }],
['Custom', function(d) { return d.data.custom; }]
], { colors: ['red', 'blue'] });
var element = angular.element(generator(sampleData))[0];
expect(element.querySelectorAll('tr').length).toEqual(2);
expect(element.querySelectorAll('td').length).toEqual(6);
var guides = element.querySelectorAll('td:first-child.legend-color-guide');
expect(guides.length).toEqual(2);
expect(guides[0].children[0].getAttribute('style')).toEqual('background-color: red;');
expect(guides[1].children[0].getAttribute('style')).toEqual('background-color: blue;');
});
});