Browse Source

Clearer handling of tooltip ordering with data_order option (#1813)

If data is grouped:
  - the tooltip will keep the same ordering as the stacked values

If data is not grouped:
  - the tooltip will use the data_order option to sort the values

 Also adds an optional 'tooltip_order' option. If set, it will override the data_order option.
dev
Anthony Pessy 8 years ago committed by Ændrew Rininsland
parent
commit
461bcddd5d
  1. 340
      spec/tooltip-spec.js
  2. 3
      src/config.js
  3. 6
      src/data.js
  4. 92
      src/tooltip.js
  5. 6
      src/util.js

340
spec/tooltip-spec.js

@ -2,16 +2,20 @@ describe('c3 chart tooltip', function () {
'use strict';
var chart;
var tooltipConfiguration;
var tooltipConfiguration = {};
var dataOrder = 'desc';
var dataGroups;
var args = function () {
return {
data: {
columns: [
['data1', 30, 200, 100, 400, 150, 250],
['data2', 50, 20, 10, 40, 15, 25],
['data3', 150, 120, 110, 140, 115, 125]
['data1', 30, 200, 100, 400, 150, 250], // 1130
['data2', 50, 20, 10, 40, 15, 25], // 160
['data3', 150, 120, 110, 140, 115, 125] // 760
],
order: dataOrder,
groups: dataGroups
},
tooltip: tooltipConfiguration
};
@ -19,6 +23,8 @@ describe('c3 chart tooltip', function () {
beforeEach(function (done) {
chart = window.initChart(chart, args(), done);
dataOrder = 'desc';
dataGroups = undefined;
});
describe('tooltip position', function () {
@ -99,24 +105,320 @@ describe('c3 chart tooltip', function () {
});
});
describe('tooltip getTooltipContent', function () {
beforeAll(function () {
describe('tooltip with data_order as desc with grouped data', function() {
beforeAll(function() {
dataOrder = 'desc';
dataGroups = [ [ 'data1', 'data2', 'data3' ]];
});
it('should display each data in descending order', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data1'); // 1130
expect(classes[2]).toBe('c3-tooltip-name--data3'); // 760
expect(classes[3]).toBe('c3-tooltip-name--data2'); // 160
});
});
describe('tooltip with data_order as asc with grouped data', function() {
beforeAll(function() {
dataOrder = 'asc';
dataGroups = [ [ 'data1', 'data2', 'data3' ]];
});
it('should display each data in ascending order', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data2'); // 160
expect(classes[2]).toBe('c3-tooltip-name--data3'); // 760
expect(classes[3]).toBe('c3-tooltip-name--data1'); // 1130
});
});
describe('tooltip with data_order as NULL with grouped data', function() {
beforeAll(function() {
dataOrder = null;
dataGroups = [ [ 'data1', 'data2', 'data3' ]];
});
it('should display each data in given order', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data1');
expect(classes[2]).toBe('c3-tooltip-name--data2');
expect(classes[3]).toBe('c3-tooltip-name--data3');
});
});
describe('tooltip with data_order as Function with grouped data', function() {
beforeAll(function() {
var order = [ 'data2', 'data1', 'data3' ];
dataOrder = function(data1, data2) {
return order.indexOf(data1.id) - order.indexOf(data2.id);
};
dataGroups = [ [ 'data1', 'data2', 'data3' ]];
});
it('should display each data in order given by function', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data2');
expect(classes[2]).toBe('c3-tooltip-name--data1');
expect(classes[3]).toBe('c3-tooltip-name--data3');
});
});
describe('tooltip with data_order as Array with grouped data', function() {
beforeAll(function() {
dataOrder = [ 'data2', 'data1', 'data3' ];
dataGroups = [ [ 'data1', 'data2', 'data3' ]];
});
it('should display each data in order given by array', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data2');
expect(classes[2]).toBe('c3-tooltip-name--data1');
expect(classes[3]).toBe('c3-tooltip-name--data3');
});
});
describe('tooltip with data_order as desc with un-grouped data', function() {
beforeAll(function() {
dataOrder = 'desc';
});
it('should display each tooltip value descending order', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data3'); // 110
expect(classes[2]).toBe('c3-tooltip-name--data1'); // 100
expect(classes[3]).toBe('c3-tooltip-name--data2'); // 10
});
});
describe('tooltip with data_order as asc with un-grouped data', function() {
beforeAll(function() {
dataOrder = 'asc';
});
it('should display each tooltip value in ascending order', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data2'); // 10
expect(classes[2]).toBe('c3-tooltip-name--data1'); // 100
expect(classes[3]).toBe('c3-tooltip-name--data3'); // 110
});
});
describe('tooltip with data_order as NULL with un-grouped data', function() {
beforeAll(function() {
dataOrder = null;
});
it('should display each tooltip value in given data order', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data1');
expect(classes[2]).toBe('c3-tooltip-name--data2');
expect(classes[3]).toBe('c3-tooltip-name--data3');
});
});
describe('tooltip with data_order as Function with un-grouped data', function() {
beforeAll(function() {
var order = [ 'data2', 'data1', 'data3' ];
dataOrder = function(data1, data2) {
return order.indexOf(data1.id) - order.indexOf(data2.id);
};
});
it('should display each tooltip value in data order given by function', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data2');
expect(classes[2]).toBe('c3-tooltip-name--data1');
expect(classes[3]).toBe('c3-tooltip-name--data3');
});
});
describe('tooltip with data_order as Array with un-grouped data', function() {
beforeAll(function() {
dataOrder = [ 'data2', 'data1', 'data3' ];
});
it('should display each tooltip value in data order given by array', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data2');
expect(classes[2]).toBe('c3-tooltip-name--data1');
expect(classes[3]).toBe('c3-tooltip-name--data3');
});
});
describe('tooltip with tooltip_order as desc', function() {
beforeAll(function() {
tooltipConfiguration = {
data_order: 'desc'
};
order: 'desc'
};
// this should be ignored
dataOrder = 'asc';
dataGroups = [ [ 'data1', 'data2', 'data3' ]];
});
it('should display each tooltip value descending order', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data3'); // 110
expect(classes[2]).toBe('c3-tooltip-name--data1'); // 100
expect(classes[3]).toBe('c3-tooltip-name--data2'); // 10
});
});
describe('tooltip with tooltip_order as asc', function() {
beforeAll(function() {
tooltipConfiguration = {
order: 'asc'
};
it('should sort values desc', function () {
var eventRect = d3.select('.c3-event-rect-2').node();
window.setMouseEvent(chart, 'mousemove', 100, 100, eventRect);
// this should be ignored
dataOrder = 'desc';
dataGroups = [ [ 'data1', 'data2', 'data3' ]];
});
it('should display each tooltip value in ascending order', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
var tooltipTable = d3.select('.c3-tooltip')[0];
var expected = ["", "c3-tooltip-name--data3",
"c3-tooltip-name--data1", "c3-tooltip-name--data2"];
var i;
for (i = 0; i < tooltipTable[0].rows.length; i++) {
expect(tooltipTable[0].rows[i].className).toBe(expected[i]);
}
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data2'); // 10
expect(classes[2]).toBe('c3-tooltip-name--data1'); // 100
expect(classes[3]).toBe('c3-tooltip-name--data3'); // 110
});
});
describe('tooltip with tooltip_order as NULL', function() {
beforeAll(function() {
tooltipConfiguration = {
order: null
};
});
it('should display each tooltip value in given order', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data1');
expect(classes[2]).toBe('c3-tooltip-name--data2');
expect(classes[3]).toBe('c3-tooltip-name--data3');
});
});
describe('tooltip with tooltip_order as Function', function() {
beforeAll(function() {
var order = [ 'data2', 'data1', 'data3' ];
tooltipConfiguration = {
order: function(data1, data2) {
return order.indexOf(data1.id) - order.indexOf(data2.id);
}
};
});
it('should display each tooltip value in data order given by function', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data2');
expect(classes[2]).toBe('c3-tooltip-name--data1');
expect(classes[3]).toBe('c3-tooltip-name--data3');
});
});
describe('tooltip with tooltip_order as Array', function() {
beforeAll(function() {
tooltipConfiguration = {
order: [ 'data2', 'data1', 'data3' ]
};
});
it('should display each tooltip value in data order given by array', function() {
window.setMouseEvent(chart, 'mousemove', 100, 100, d3.select('.c3-event-rect-2').node());
var classes = d3.selectAll('.c3-tooltip tr')[0].map(function(node) {
return node.className;
});
expect(classes[0]).toBe(''); // header
expect(classes[1]).toBe('c3-tooltip-name--data2');
expect(classes[2]).toBe('c3-tooltip-name--data1');
expect(classes[3]).toBe('c3-tooltip-name--data3');
});
});
});
});

3
src/config.js

@ -121,7 +121,7 @@ c3_chart_internal_fn.getDefaultConfig = function () {
axis_y_label: {},
axis_y_tick_format: undefined,
axis_y_tick_outer: true,
axis_y_tick_values: null,
axis_y_tick_values: null,
axis_y_tick_rotate: 0,
axis_y_tick_count: undefined,
axis_y_tick_time_value: undefined,
@ -203,6 +203,7 @@ c3_chart_internal_fn.getDefaultConfig = function () {
regions: [],
// tooltip - show when mouseover on each data
tooltip_show: true,
tooltip_order: undefined,
tooltip_grouped: true,
tooltip_format_title: undefined,
tooltip_format_name: undefined,

6
src/data.js

@ -233,7 +233,11 @@ c3_chart_internal_fn.orderTargets = function (targets) {
});
} else if (isFunction(config.data_order)) {
targets.sort(config.data_order);
} // TODO: accept name array for order
} else if (isArray(config.data_order)) {
targets.sort(function (t1, t2) {
return config.data_order.indexOf(t1.id) - config.data_order.indexOf(t2.id);
});
}
return targets;
};
c3_chart_internal_fn.filterByX = function (targets, x) {

92
src/tooltip.js

@ -24,31 +24,85 @@ c3_chart_internal_fn.initTooltip = function () {
.style("display", "block");
}
};
c3_chart_internal_fn.getTooltipSortFunction = function() {
var $$ = this, config = $$.config;
if (config.data_groups.length === 0 || config.tooltip_order !== undefined) {
// if data are not grouped or if an order is specified
// for the tooltip values we sort them by their values
var order = config.tooltip_order;
if (order === undefined) {
order = config.data_order;
}
var valueOf = function(obj) {
return obj ? obj.value : null;
};
// if data are not grouped, we sort them by their value
if (isString(order) && order.toLowerCase() === 'asc') {
return function(a, b) {
return valueOf(a) - valueOf(b);
};
} else if (isString(order) && order.toLowerCase() === 'desc') {
return function (a, b) {
return valueOf(b) - valueOf(a);
};
} else if (isFunction(order)) {
// if the function is from data_order we need
// to wrap the returned function in order to format
// the sorted value to the expected format
var sortFunction = order;
if (config.tooltip_order === undefined) {
sortFunction = function (a, b) {
return order(a ? {
id: a.id,
values: [ a ]
} : null, b ? {
id: b.id,
values: [ b ]
} : null)
};
}
return sortFunction;
} else if (isArray(order)) {
return function(a, b) {
return order.indexOf(a.id) - order.indexOf(b.id);
}
}
} else {
// if data are grouped, we follow the order of grouped targets
var ids = $$.orderTargets($$.data.targets).map(function(i) {
return i.id;
});
// if it was either asc or desc we need to invert the order
// returned by orderTargets
if ($$.isOrderAsc() || $$.isOrderDesc()) {
ids = ids.reverse();
}
return function(a, b) {
return ids.indexOf(a.id) - ids.indexOf(b.id);
}
}
};
c3_chart_internal_fn.getTooltipContent = function (d, defaultTitleFormat, defaultValueFormat, color) {
var $$ = this, config = $$.config,
titleFormat = config.tooltip_format_title || defaultTitleFormat,
nameFormat = config.tooltip_format_name || function (name) { return name; },
valueFormat = config.tooltip_format_value || defaultValueFormat,
text, i, title, value, name, bgcolor,
orderAsc = $$.isOrderAsc();
text, i, title, value, name, bgcolor;
if (config.data_groups.length === 0) {
d.sort(function(a, b){
var v1 = a ? a.value : null, v2 = b ? b.value : null;
return orderAsc ? v1 - v2 : v2 - v1;
});
} else {
var ids = $$.orderTargets($$.data.targets).map(function (i) {
return i.id;
});
d.sort(function(a, b) {
var v1 = a ? a.value : null, v2 = b ? b.value : null;
if (v1 > 0 && v2 > 0) {
v1 = a ? ids.indexOf(a.id) : null;
v2 = b ? ids.indexOf(b.id) : null;
}
return orderAsc ? v1 - v2 : v2 - v1;
});
var tooltipSortFunction = this.getTooltipSortFunction();
if (tooltipSortFunction) {
d.sort(tooltipSortFunction);
}
for (i = 0; i < d.length; i++) {

6
src/util.js

@ -7,6 +7,12 @@ var isValue = c3_chart_internal_fn.isValue = function (v) {
isString = c3_chart_internal_fn.isString = function (o) {
return typeof o === 'string';
},
isArray = c3_chart_internal_fn.isArray = function (o) {
if (Array.isArray) {
return Array.isArray(o);
}
return Object.prototype.toString.call(o) === '[object Array]';
},
isUndefined = c3_chart_internal_fn.isUndefined = function (v) {
return typeof v === 'undefined';
},

Loading…
Cancel
Save