Skip to content

Commit

Permalink
fix(tooltip): Auto pos adjustion for tooltip
Browse files Browse the repository at this point in the history
- Remove 'tooltip.position.unit' option
- Implement auto tooltip x positioning
- Improve on resize function to be invoked with intervals

Fix #1243
Fix #1239
  • Loading branch information
netil authored Feb 25, 2020
1 parent 3f9bfa5 commit c54f731
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 76 deletions.
2 changes: 1 addition & 1 deletion spec/internals/bb-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ describe("Interface & initialization", () => {
container.style.width = width + "px";

// run the resize handler
chart.internal.api.internal.charts.forEach(c => {
chart.internal.charts.forEach(c => {
c.internal.resizeFunction();
});

Expand Down
47 changes: 33 additions & 14 deletions spec/internals/tooltip-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,30 +241,49 @@ describe("TOOLTIP", function() {
});

it("set option tooltip.position", () => {
args.tooltip.position = () => ({top: "10%", left: 20});
args.tooltip.position = () => ({
top: 50, left: 600
});

args.tooltip.doNotHide = true;
});

it("check tooltip's position unit", () => {
const pos = args.tooltip.position();
it("tooltip repositioning: when the pos is greater than the current width", done => {
util.hoverChart(chart);

["top", "left"].forEach(v => {
expect(chart.$.tooltip.style(v)).to.be.equal(
pos[v] + (v === "left" ? "px" : "")
);
});
const {tooltip} = chart.$;
const left = parseInt(tooltip.style("left"));

// do resize
chart.internal.resizeFunction();

setTimeout(() => {
expect(parseInt(tooltip.style("left"))).to.be.below(left);
done();
}, 200);
});

it("set option tooltip.position={unit: '%'}", () => {
args.tooltip.position = {unit: "%"};
it("set option tooltip.position", () => {
args.tooltip.position = () => ({
top: 50, left: 300
});
});

it("check tooltip's position unit as percentage", () => {
it("tooltip repositioning: when the chart width is increasing", done => {
chart.resize({width:450});
util.hoverChart(chart);

["top", "left"].forEach(v => {
expect(/^\d+(\.\d+)?%$/.test(chart.$.tooltip.style(v))).to.be.true;
});
const {tooltip} = chart.$;
const left = parseInt(tooltip.style("left"));

// do resize
chart.resize({width:640});
chart.internal.resizeFunction();

setTimeout(() => {
expect(parseInt(tooltip.style("left"))).to.be.above(left);
done();
}, 200);
});
});

Expand Down
2 changes: 1 addition & 1 deletion src/api/api.chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ extend(Chart.prototype, {

// clear timers && pending transition
$$.svg.select("*").interrupt();
isDefined($$.resizeTimeout) && window.clearTimeout($$.resizeTimeout);
$$.generateResize.timeout && window.clearTimeout($$.generateResize.timeout);

window.removeEventListener("resize", $$.resizeFunction);
$$.selectChart.classed("bb", false).html("");
Expand Down
9 changes: 0 additions & 9 deletions src/config/Options.js
Original file line number Diff line number Diff line change
Expand Up @@ -3725,8 +3725,6 @@ export default class Options {
* If undefined returned, the row of that value will be skipped.
* @property {Function} [tooltip.position] Set custom position function for the tooltip.<br>
* This option can be used to modify the tooltip position by returning object that has top and left.
* @property {String} [tooltip.position.unit="px"] Set tooltip's position unit.
* - **NOTE:** This option can't be used along with `tooltip.position` custom function. If want to specify unit in custom function, return value with desired unit.
* @property {Function|Object} [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|HTMLElement} [tooltip.contents.bindto=undefined] Set CSS selector or element reference to bind tooltip.
Expand Down Expand Up @@ -3777,13 +3775,6 @@ export default class Options {
* // return with unit or without. If the value is number, is treated as 'px'.
* return {top: "10%", left: 20} // top:10%; left: 20px;
* },
*
* position: {
* // set tooltip's position unit as '%', rather than 'px'.
* // ex) If want to keep the position on mobile device rotation, set as '%'.
* unit: "%"
* },
*
* contents: function(d, defaultTitleFormat, defaultValueFormat, color) {
* return ... // formatted html as you want
* },
Expand Down
49 changes: 26 additions & 23 deletions src/internals/ChartInternal.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ export default class ChartInternal {

$$.updateSvgSize();

// Bind resize event
$$.bindResize();

// Define regions
const main = $$.svg.append("g").attr("transform", $$.getTranslate("main"));

Expand Down Expand Up @@ -361,9 +364,6 @@ export default class ChartInternal {
callFn(config.data_onmax, $$, minMax.max);
}

// Bind resize event
$$.bindResize();

// export element of the chart
$$.api.element = $$.selectChart.node();

Expand Down Expand Up @@ -1142,40 +1142,43 @@ export default class ChartInternal {
bindResize() {
const $$ = this;
const config = $$.config;
const resizeFunction = $$.generateResize();
const list = [];

$$.resizeFunction = $$.generateResize();
$$.resizeFunction.add(() => callFn(config.onresize, $$, $$.api));
list.push(() => callFn(config.onresize, $$, $$.api));

if (config.resize_auto) {
$$.resizeFunction.add(() => {
if ($$.resizeTimeout) {
window.clearTimeout($$.resizeTimeout);
$$.resizeTimeout = null;
}

$$.resizeTimeout = window.setTimeout(() => {
$$.api.flush(false, true);
}, 200);
});
list.push(() => $$.api.flush(false, true));
}

$$.resizeFunction.add(() => callFn(config.onresized, $$, $$.api));
list.push(() => callFn(config.onresized, $$, $$.api));

// add resize functions
list.forEach(v => resizeFunction.add(v));

// attach resize event
window.addEventListener("resize", $$.resizeFunction);
window.addEventListener("resize", $$.resizeFunction = resizeFunction);
}

generateResize() {
const resizeFunctions = [];
const fn = [];

function callResizeFn() {
// Delay all resize functions call, to prevent unintended excessive call from resize event
if (callResizeFn.timeout) {
window.clearTimeout(callResizeFn.timeout);
callResizeFn.timeout = null;
}

function callResizeFunctions() {
resizeFunctions.forEach(f => f());
callResizeFn.timeout = window.setTimeout(() => {
fn.forEach(f => f());
}, 200);
}

callResizeFunctions.add = f => resizeFunctions.push(f);
callResizeFunctions.remove = f => resizeFunctions.splice(resizeFunctions.indexOf(f), 1);
callResizeFn.add = f => fn.push(f);
callResizeFn.remove = f => fn.splice(fn.indexOf(f), 1);

return callResizeFunctions;
return callResizeFn;
}

endall(transition, callback) {
Expand Down
61 changes: 37 additions & 24 deletions src/internals/tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ extend(ChartInternal.prototype, {
.style("display", "block");
}
}

$$.bindTooltipResizePos();
},

/**
Expand Down Expand Up @@ -292,9 +294,9 @@ extend(ChartInternal.prototype, {
return;
}

const datum = $$.tooltip.datum();
const dataStr = JSON.stringify(selectedData);
let datum = $$.tooltip.datum();
let {width = 0, height = 0} = datum || {};
const dataStr = JSON.stringify(selectedData);

if (!datum || datum.current !== dataStr) {
const index = selectedData.concat().sort()[0].index;
Expand All @@ -311,7 +313,7 @@ extend(ChartInternal.prototype, {
))
.style("display", null)
.style("visibility", null) // for IE9
.datum({
.datum(datum = {
index,
current: dataStr,
width: width = $$.tooltip.property("offsetWidth"),
Expand All @@ -323,38 +325,49 @@ extend(ChartInternal.prototype, {
}

if (!bindto) {
let fn = config.tooltip_position;
let unit;

if (!isFunction(fn)) {
unit = fn && fn.unit;
fn = $$.tooltipPosition;
}
const fnPos = config.tooltip_position || $$.tooltipPosition;

// Get tooltip dimensions
const pos = fn.call(this, dataToShow, width, height, element);
const pos = fnPos.call(this, dataToShow, width, height, element);

["top", "left"].forEach(v => {
let value = pos[v];
const value = pos[v];

// when value is number
if (/^\d+(\.\d+)?$/.test(value)) {
if (unit === "%") {
const size = $$[v === "top" ? "currentHeight" : "currentWidth"];
$$.tooltip.style(v, `${value}px`);

value = value / size * 100;
} else {
unit = "px";
}

value += unit;
// Remember left pos in percentage to be used on resize call
if (v === "left" && !datum.xPosInPercent) {
datum.xPosInPercent = value / $$.currentWidth * 100;
}

$$.tooltip.style(v, value);
});
}
},

/**
* Adjust tooltip position on resize event
* @private
*/
bindTooltipResizePos() {
const $$ = this;
const {resizeFunction, tooltip} = $$;

resizeFunction.add(() => {
if (tooltip.style("display") === "block") {
const {currentWidth} = $$;
const {width, xPosInPercent} = tooltip.datum();
let value = currentWidth / 100 * xPosInPercent;
const diff = currentWidth - (value + width);

// if tooltip size overs current viewport size
if (diff < 0) {
value += diff;
}

tooltip.style("left", `${value}px`);
}
});
},

/**
* Hide the tooltip
* @param {Boolean} force Force to hide
Expand Down
5 changes: 1 addition & 4 deletions types/options.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -863,16 +863,13 @@ export interface TooltipOptions {
/**
* Set custom position function for the tooltip.
* This option can be used to modify the tooltip position by returning object that has top and left.
*
* Or set tooltip's position unit.
* This option can't be used along with `tooltip.position` custom function. If want to specify unit in custom function, return value with desired unit.
*/
position?: ((
data: any,
width: number,
height: number,
element: any
) => { top: number; left: number }) | { unit: string; };
) => { top: number; left: number });

/**
* Set custom HTML for the tooltip.
Expand Down

0 comments on commit c54f731

Please sign in to comment.