Skip to content

Commit

Permalink
feat(tooltip): Implement tooltip.order
Browse files Browse the repository at this point in the history
- Add new tooltip.order option
- Detach data.order to be used on tooltip ordering

Fix #127
Close #131
  • Loading branch information
netil authored Sep 6, 2017
1 parent 0e8e0e1 commit db7d0f8
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 55 deletions.
115 changes: 97 additions & 18 deletions spec/tooltip-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,34 @@ describe("TOOLTIP", function() {
["data1", 30, 200, 100, 400, 150, 250],
["data2", 50, 20, 10, 40, 15, 25],
["data3", 150, 120, 110, 140, 115, 125]
],
]
},
tooltip: {}
};

// check for the tooltip's ordering
const checkTooltip = (chart, expected) => {
const eventRect = chart.internal.main
.select(`.${CLASS.eventRect}-2`)
.node();

util.fireEvent(eventRect, "mousemove", {
clientX: 100,
clientY: 100
}, chart);

const tooltips = d3.select(chart.element)
.selectAll(`.${CLASS.tooltip} tr`)
.nodes();

if (expected) {
for (let i = 1, el; (el = tooltips[i]); i++) {
expect(el.className).to.be.equal(expected[i - 1]);
}
}
};


beforeEach(() => {
chart = util.generate(args);
});
Expand Down Expand Up @@ -109,31 +132,87 @@ describe("TOOLTIP", function() {
});
});

describe("tooltip getTooltipContent", () => {
before(() => {
args.tooltip.data_order = "desc";
describe("tooltip order", () => {
it("should sort values in data display order", () => {
checkTooltip(chart, [
"bb-tooltip-name-data1",
"bb-tooltip-name-data2",
"bb-tooltip-name-data3"
]);
});

it("should sort values desc", () => {
const eventRect = chart.internal.main.select(`.${CLASS.eventRect}-2`).node();
it("set options tooltip.order=asc", () => {
args.tooltip.order = "asc";
});

util.fireEvent(eventRect, "mousemove", {
clientX: 100,
clientY: 100
}, chart);
it("should sort values ascending order", () => {
checkTooltip(chart, [
"bb-tooltip-name-data2",
"bb-tooltip-name-data1",
"bb-tooltip-name-data3"
]);
});

it("set options tooltip.order=desc", () => {
args.tooltip.order = "desc";
});

const tooltips = d3.select(chart.element).selectAll(`.${CLASS.tooltip} tr`).nodes();
const len = tooltips.length;
const expected = [
"",
it("set options tooltip.order=desc", () => {
checkTooltip(chart, [
"bb-tooltip-name-data3",
"bb-tooltip-name-data1",
"bb-tooltip-name-data2"
];
]);
});

for (let i = 0; i < len; i++) {
expect(tooltips[i].className).to.be.equal(expected[i]);
}
// check for stacking bar
it("set options data.groups", () => {
args.data.type = "bar";
args.data.groups = [["data1", "data2", "data3"]];
args.tooltip.order = args.data.order = "desc";
});

it("stacked bar: should sort values in descending order", () => {
checkTooltip(chart, [
"bb-tooltip-name-data3",
"bb-tooltip-name-data1",
"bb-tooltip-name-data2"
]);
});

it("set options tooltip.order=asc", () => {
args.tooltip.order = args.data.order = "asc";
});

it("stacked bar: should sort values in ascending order", () => {
checkTooltip(chart, [
"bb-tooltip-name-data2",
"bb-tooltip-name-data1",
"bb-tooltip-name-data3"
]);
});

it("set options tooltip.order=null", () => {
args.tooltip.order = args.data.order = null;
});

it("stacked bar: should be ordered in data input order", () => {
checkTooltip(chart, [
"bb-tooltip-name-data3",
"bb-tooltip-name-data2",
"bb-tooltip-name-data1"
]);
});

it("set options tooltip.order=function", () => {
args.tooltip.order = sinon.spy(function(a, b) {
return a.value - b.value;
});
});

it("data.order function should be called", () => {
checkTooltip(chart);
expect(args.tooltip.order.called).to.be.true;
});
});
});
56 changes: 47 additions & 9 deletions src/config/Options.js
Original file line number Diff line number Diff line change
Expand Up @@ -520,19 +520,40 @@ export default class Options {
data_labels_position: {},

/**
* This option changes the order of stacking the data and pieces of pie/donut. If `null` specified, it will be the order the data loaded. If function specified, it will be used to sort the data and it will recieve the data as argument.<br><br>
* This option changes the order of stacking data and pieces of pie/donut.
* - If `null` specified, it will be the order the data loaded.
* - If function specified, it will be used as [Array.sort compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Parameters)<br><br>
*
* **Available Values:**
* - desc
* - asc
* - function(data1, data2) { ... }
* - null
* - `desc`: In descending order
* - `asc`: In ascending order
* - `null`: It keeps the data load order
* - `function(data1, data2) { ... }`: Array.sort compareFunction
* @name data:order
* @memberof Options
* @type {String|Function}
* @type {String|Function|null}
* @default desc
* @example
* data: {
* // in descending order (default)
* order: "desc"
*
* // in ascending order
* order: "asc"
*
* // keeps data input order
* order: null
*
* // specifying sort function
* order: function(a, b) {
* // param data passed format
* {
* id: "data1", id_org: "data1", values: [
* {x: 5, value: 250, id: "data1", index: 5, name: "data1"},
* ...
* ]
* }
* }
* }
*/
data_order: "desc",
Expand Down Expand Up @@ -2268,10 +2289,16 @@ export default class Options {
* @property {Function} [tooltip.format.value] Set format for the value of each data in tooltip.<br>
* Specified function receives name, ratio, id and index of the data point to show. ratio will be undefined if the chart is not donut/pie/gauge.
* If undefined returned, the row of that value will be skipped.
* @property {function} [tooltip.position] Set custom position for the tooltip.<br>
* @property {Function} [tooltip.position] Set custom position for the tooltip.<br>
* This option can be used to modify the tooltip position by returning object that has top and left.
* @property {function} [tooltip.contents] Set custom HTML for the tooltip.<br>
* @property {Function} [tooltip.contents] Set custom HTML for the tooltip.<br>
* Specified function receives data, defaultTitleFormat, defaultValueFormat and color of the data point to show. If tooltip.grouped is true, data includes multiple data points.
* @property {String|Function|null} [tooltip.order=null] Set tooltip data display order.<br><br>
* **Available Values:**
* - `desc`: In descending data value order
* - `asc`: In ascending data value order
* - `null`: It keeps the data display order
* - `function(data1, data2) { ... }`: [Array.sort compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Parameters)
* @example
* tooltip: {
* show: true,
Expand All @@ -2286,7 +2313,17 @@ export default class Options {
* },
* contents: function(d, defaultTitleFormat, defaultValueFormat, color) {
* return ... // formatted html as you want
* }
* },
*
* // sort tooltip data value display in ascending order
* order: "asc",
*
* // specifying sort function
* order: function(a, b) {
* // param data passed format
* {x: 5, value: 250, id: "data1", index: 5, name: "data1"}
* ...
* }
* }
*/
tooltip_show: true,
Expand All @@ -2307,6 +2344,7 @@ export default class Options {
},
tooltip_onshow: () => {},
tooltip_onhide: () => {},
tooltip_order: null,

/**
* Set title options
Expand Down
73 changes: 45 additions & 28 deletions src/internals/tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import {mouse as d3Mouse} from "d3";
import ChartInternal from "./ChartInternal";
import CLASS from "../config/classes";
import {extend, isValue, sanitise, isString} from "./util";
import {extend, isValue, sanitise, isString, isFunction} from "./util";

extend(ChartInternal.prototype, {
/**
Expand Down Expand Up @@ -46,6 +46,7 @@ extend(ChartInternal.prototype, {
.style("display", "block");
}
},

/**
* Returns the tooltip content(HTML string)
* @private
Expand All @@ -61,64 +62,78 @@ extend(ChartInternal.prototype, {
const titleFormat = config.tooltip_format_title || defaultTitleFormat;
const nameFormat = config.tooltip_format_name || (name => name);
const valueFormat = config.tooltip_format_value || defaultValueFormat;
const orderAsc = $$.isOrderAsc();
const order = config.tooltip_order;
let text;
let i;
let title;
let value;
let name;
let bgcolor;

if (config.data_groups.length === 0) {
d.sort((a, b) => {
const v1 = a ? a.value : null;
const v2 = b ? b.value : null;

return orderAsc ? v1 - v2 : v2 - v1;
});
} else {
const ids = $$.orderTargets($$.data.targets).map(i2 => i2.id);
if (order === null && config.data_groups.length) {
// for stacked data, order should aligned with the visually displayed data
const ids = $$.orderTargets($$.data.targets)
.map(i2 => i2.id)
.reverse();

d.sort((a, b) => {
let v1 = a ? a.value : null;
let v2 = b ? b.value : null;

if (v1 > 0 && v2 > 0) {
v1 = a ? ids.indexOf(a.id) : null;
v2 = b ? ids.indexOf(b.id) : null;
v1 = a.id ? ids.indexOf(a.id) : null;
v2 = b.id ? ids.indexOf(b.id) : null;
}
return orderAsc ? v1 - v2 : v2 - v1;

return v1 - v2;
});
} else if (/^(asc|desc)$/.test(order)) {
const isAscending = order === "asc";

d.sort((a, b) => {
const v1 = a ? a.value : null;
const v2 = b ? b.value : null;

return isAscending ? v1 - v2 : v2 - v1;
});
} else if (isFunction(order)) {
d.sort(order);
}

for (i = 0; i < d.length; i++) {
if (!(d[i] && (d[i].value || d[i].value === 0))) {
for (let i = 0, row, len = d.length; i < len; i++) {
if (!(
(row = d[i]) &&
(row.value || row.value === 0)
)) {
continue;
}

if (!text) {
title = sanitise(titleFormat ? titleFormat(d[i].x) : d[i].x);
title = sanitise(titleFormat ? titleFormat(row.x) : row.x);
text = (title || title === 0 ? `<tr><th colspan="2">${title}</th></tr>` : "");
text = `<table class="${$$.CLASS.tooltip}">${text}`;
}

value = sanitise(valueFormat(d[i].value, d[i].ratio, d[i].id, d[i].index, d));
value = sanitise(valueFormat(row.value, row.ratio, row.id, row.index, d));

if (value !== undefined) {
// Skip elements when their name is set to null
if (d[i].name === null) { continue; }
name = sanitise(nameFormat(d[i].name, d[i].ratio, d[i].id, d[i].index));
bgcolor = $$.levelColor ? $$.levelColor(d[i].value) : color(d[i].id);

text += `<tr class="${$$.CLASS.tooltipName}${$$.getTargetSelectorSuffix(d[i].id)}">` +
`<td class="name"><span style="background-color:${bgcolor}"></span>${name}</td>` +
`<td class="value">${value}</td>` +
`</tr>`;
if (row.name === null) {
continue;
}

name = sanitise(nameFormat(row.name, row.ratio, row.id, row.index));
bgcolor = $$.levelColor ? $$.levelColor(row.value) : color(row.id);

text += `<tr class="${$$.CLASS.tooltipName}${$$.getTargetSelectorSuffix(row.id)}">
<td class="name"><span style="background-color:${bgcolor}"></span>${name}</td>
<td class="value">${value}</td>
</tr>`;
}
}

return `${text}</table>`;
},

/**
* Returns the position of the tooltip
* @private
Expand Down Expand Up @@ -179,6 +194,7 @@ extend(ChartInternal.prototype, {
left: tooltipLeft
};
},

/**
* Show the tooltip
* @private
Expand Down Expand Up @@ -216,11 +232,12 @@ extend(ChartInternal.prototype, {
.style("top", `${position.top}px`)
.style("left", `${position.left}px`);
},

/**
* Hide the tooltip
* @private
*/
hideTooltip() {
this.tooltip.style("display", "none");
},
}
});

0 comments on commit db7d0f8

Please sign in to comment.