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:
parent
e355c3bd4e
commit
b67003a8fe
132
app/js/services/tooltip.js
Normal file
132
app/js/services/tooltip.js
Normal 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);
|
79
test/unit/services/tooltip_spec.js
Normal file
79
test/unit/services/tooltip_spec.js
Normal 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;');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user