Skip to content

Commit

Permalink
feat(tooltip): Provide y position for tooltip position callback
Browse files Browse the repository at this point in the history
Enhance providing y axis position value or function for tooltip.position
callback argument

Close #3434
  • Loading branch information
netil committed Sep 27, 2023
1 parent 19a6e1c commit a793a2e
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 9 deletions.
97 changes: 97 additions & 0 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -4284,6 +4284,103 @@ d3.select(".chart_area")
}
}
},
TooltipPosition: [
{
options: {
data: {
columns: [
["data1", 30, 200, 200, 400, 150, 250]
],
type: "area"
},
padding: {
top: 35
},
axis: {
x: {
padding: {
left: 15,
right: 15,
unit: "px"
}
},
y2: {
show: true
}
},
tooltip: {
position: function(data, width, height, element, pos) {
// when has single dataseries, 'pos.yAxis' is number value
return {
top: pos.yAxis - (height + 10),
left: pos.xAxis - (width / 2)
};
}
}
}
},
{
options: {
data: {
columns: [
["data1", 30, 200, 200, 400, 150, 250],
["data2", 130, 100, 100, 200, 150, 50],
["data3", 230, 200, 200, 300, 250, 250]
],
type: "bar",
groups: [
["data1", "data2", "data3"]
]
},
axis: {
rotated: false,
y2: {
show: true
}
},
tooltip: {
position: function(data, width, height, element, pos) {
const total = data.reduce((a, {value}) => a + value, 0);

// when has multiple dataseries, 'pos.yAxis' is function
return this.config("axis.rotated") ? {
top: pos.xAxis - (width / 2),
left: pos.yAxis(total)
} : {
top: pos.yAxis(total) - height,
left: pos.xAxis - (width / 2)
};
}
}
}
},
{
options: {
data: {
columns: [
["data1", 30, 200, 200, 400, 150, 250]
],
type: "bar"
},
padding: {
right: 80
},
axis: {
rotated: true
},
tooltip: {
position: function(data, width, height, element, pos) {
const total = data.reduce((a, {value}) => a + value, 0);

return {
top: pos.xAxis - (height / 2),
left: pos.yAxis + 10
}
}
}
}
}
],
TooltipTemplate: {
options: {
data: {
Expand Down
30 changes: 25 additions & 5 deletions src/ChartInternal/internals/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,25 +259,45 @@ export default {
* @param {SVGElement} eventTarget Event element
* @private
*/
setTooltipPosition(dataToShow: IDataRow, eventTarget: SVGElement): void {
setTooltipPosition(dataToShow: IDataRow[], eventTarget: SVGElement): void {
const $$ = this;
const {config, scale, state, $el: {eventRect, tooltip}} = $$;
const {bindto} = config.tooltip_contents;
const isRotated = config.axis_rotated;
const datum = tooltip?.datum();

if (!bindto && datum) {
const data = dataToShow ?? JSON.parse(datum.current);
const [x, y] = getPointer(state.event, eventTarget ?? eventRect?.node()); // get mouse event position
const currPos: {x: number, y: number, xAxis?: number} = {x, y};
const currPos: {
x: number, y: number, xAxis?: number, yAxis?: number | (
(value: number, id?: string, axisId?: string) => number
)} = {x, y};

if (state.hasAxis && scale.x && datum && "x" in datum) {
const getYPos = (value = 0, id?: string, axisId = "y"): number => {
const scaleFn = scale[id ? $$.axis?.getId(id) : axisId];

return scaleFn ? scaleFn(value) + (isRotated ? state.margin.left : state.margin.top) : 0;
};

if (scale.x && datum && "x" in datum) {
currPos.xAxis = scale.x(datum.x);
currPos.xAxis = scale.x(datum.x) + (
// add margin only when user specified tooltip.position function
config.tooltip_position ? (isRotated ? state.margin.top : state.margin.left) : 0
);

if (data.length === 1) {
currPos.yAxis = getYPos(data[0].value as number, data[0].id);
} else {
currPos.yAxis = getYPos;
}
}

const {width = 0, height = 0} = datum;

// Get tooltip position
const pos = config.tooltip_position?.bind($$.api)(
dataToShow ?? JSON.parse(datum.current),
data,
width, height, eventRect?.node(), currPos
) ?? $$.getTooltipPosition.bind($$)(width, height, currPos);

Expand Down
26 changes: 25 additions & 1 deletion src/config/Options/common/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export default {
* @see [Demo: Tooltip Grouping](https://naver.github.io/billboard.js/demo/#Tooltip.TooltipGrouping)
* @see [Demo: Tooltip Format](https://naver.github.io/billboard.js/demo/#Tooltip.TooltipFormat)
* @see [Demo: Linked Tooltip](https://naver.github.io/billboard.js/demo/#Tooltip.LinkedTooltips)
* @see [Demo: Tooltip Position](https://naver.github.io/billboard.js/demo/#Tooltip.TooltipPosition)
* @see [Demo: Tooltip Template](https://naver.github.io/billboard.js/demo/#Tooltip.TooltipTemplate)
* @example
* tooltip: {
Expand All @@ -103,8 +104,31 @@ export default {
* // x: Current mouse event x position,
* // y: Current mouse event y position,
* // xAxis: Current x Axis position (the value is given for axis based chart type only)
* // yAxis: Current y Axis position value or function(the value is given for axis based chart type only)
* // }
* return {top: 0, left: 0}
*
* // yAxis will work differently per data lenghts
* // - a) Single data: `yAxis` will return `number` value
* // - b) Multiple data: `yAxis` will return a function with property value
*
* // a) Single data:
* // Get y coordinate
* pos.yAxis; // y axis coordinate value of current data point
*
* // b) Multiple data:
* // Get y coordinate of value 500, where 'data1' scales(y or y2).
* // When 'data.axes' option is used, data can bound to different axes.
* // - when "data.axes={data1: 'y'}", wil return y value from y axis scale.
* // - when "data.axes={data1: 'y2'}", wil return y value from y2 axis scale.
* pos.yAxis(500, "data1"); // will return y coordinate value of data1
*
* pos.yAxis(500); // get y coordinate with value of 500, using y axis scale
* pos.yAxis(500, null, "y2"); // get y coordinate with value of 500, using y2 axis scale
*
* return {
* top: 0,
* left: 0
* }
* },
*
* contents: function(d, defaultTitleFormat, defaultValueFormat, color) {
Expand Down
39 changes: 36 additions & 3 deletions test/internals/tooltip-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,7 @@ describe("TOOLTIP", function() {

describe("do not overlap data point", () => {
it("should show tooltip on proper position", () => {
const tooltip = chart.$.tooltip;
const circles = chart.$.circles;
const {circles, tooltip} = chart.$;
const getCircleRectX = x => circles.filter(`.${$SHAPE.shape}-${x}`)
.node().getBoundingClientRect().x;

Expand Down Expand Up @@ -838,10 +837,40 @@ describe("TOOLTIP", function() {
});

it("set option tooltip.position", () => {
args.data.axes = {
data3: "y2"
};
args.axis = {
y2: {
show: true
}
}

args.tooltip.position = function(data, width, height, element, pos) {
const {scale: {y, y2}, state: {margin}} = this.internal;

expect(pos.x).to.be.equal(99.5);
expect(pos.y).to.be.equal(100.5);
expect(pos.xAxis).to.be.equal(this.internal.scale.x(data[0].x));

expect(pos.xAxis).to.be.equal(
this.internal.scale.x(data[0].x) + margin.left
);

data.forEach(({id, value}) => {
const isY2 = id === "data3";
const scale = isY2 ? y2 : y;

expect(pos.yAxis(value, id)).to.be.equal(
scale(value) + this.internal.state.margin.top
);

if (isY2) {
expect(y2(value) + margin.top).to.be.equal(pos.yAxis(value, null, "y2"));
} else {
expect(y(value) + margin.top).to.be.equal(pos.yAxis(value, null, "y"));
}

})

return {
top: 50, left: 300
Expand All @@ -866,6 +895,10 @@ describe("TOOLTIP", function() {
done();
}, 200);
});

it("", () => {

})
});

describe("tooltip order", () => {
Expand Down
1 change: 1 addition & 0 deletions types/options.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@ export interface TooltipOptions {
x: number;
y: number;
xAxis?: number;
yAxis?: number | ((value: number, id?: string, axisId?: string) => number);
}
) => { top: number; left: number });

Expand Down

0 comments on commit a793a2e

Please sign in to comment.