From 027806717a62d7ca55131e1bd2f96ece66675f18 Mon Sep 17 00:00:00 2001
From: Jae Sung Park <alberto.park@gmail.com>
Date: Thu, 25 Nov 2021 17:23:08 +0900
Subject: [PATCH] fix(tooltip): fix candlestick tooltip display with xs option

- Make handling candlestick type shape in .findClosest()
- Move .isWithinBar() from bar.ts -> shape.ts

Fix #2434
---
 src/ChartInternal/data/data.ts   | 15 +++++++++------
 src/ChartInternal/shape/bar.ts   | 23 +---------------------
 src/ChartInternal/shape/shape.ts | 25 ++++++++++++++++++++++--
 test/internals/tooltip-spec.ts   | 33 ++++++++++++++++++++++++++++++++
 4 files changed, 66 insertions(+), 30 deletions(-)

diff --git a/src/ChartInternal/data/data.ts b/src/ChartInternal/data/data.ts
index 8534f6b6f..dc4aa4e89 100644
--- a/src/ChartInternal/data/data.ts
+++ b/src/ChartInternal/data/data.ts
@@ -743,20 +743,23 @@ export default {
 		let minDist = config.point_sensitivity;
 		let closest;
 
-		// find mouseovering bar
+		// find mouseovering bar/candlestick
+		// https://github.com/naver/billboard.js/issues/2434
 		data
-			.filter(v => $$.isBarType(v.id))
+			.filter(v => $$.isBarType(v.id) || $$.isCandlestickType(v.id))
 			.forEach(v => {
-				const shape = main.select(`.${CLASS.bars}${$$.getTargetSelectorSuffix(v.id)} .${CLASS.bar}-${v.index}`).node();
+				const selector = $$.isBarType(v.id) ?
+					`.${CLASS.chartBar}.${CLASS.target}${$$.getTargetSelectorSuffix(v.id)} .${CLASS.bar}-${v.index}` :
+					`.${CLASS.chartCandlestick}.${CLASS.target}${$$.getTargetSelectorSuffix(v.id)} .${CLASS.candlestick}-${v.index} path`;
 
-				if (!closest && $$.isWithinBar(shape)) {
+				if (!closest && $$.isWithinBar(main.select(selector).node())) {
 					closest = v;
 				}
 			});
 
-		// find closest point from non-bar
+		// find closest point from non-bar/candlestick
 		data
-			.filter(v => !$$.isBarType(v.id))
+			.filter(v => !$$.isBarType(v.id) && !$$.isCandlestickType(v.id))
 			.forEach(v => {
 				const d = $$.dist(v, pos);
 
diff --git a/src/ChartInternal/shape/bar.ts b/src/ChartInternal/shape/bar.ts
index 6f04a1f6a..8c1274fd2 100644
--- a/src/ChartInternal/shape/bar.ts
+++ b/src/ChartInternal/shape/bar.ts
@@ -3,7 +3,7 @@
  * billboard.js project is licensed under the MIT license
  */
 import CLASS from "../../config/classes";
-import {getPointer, getRandom, getRectSegList, isNumber} from "../../module/util";
+import {getRandom, isNumber} from "../../module/util";
 
 export default {
 	initBar(): void {
@@ -191,26 +191,5 @@ export default {
 				[startPosX, offset]
 			];
 		};
-	},
-
-	isWithinBar(that): boolean {
-		const mouse = getPointer(this.state.event, that);
-		const list = getRectSegList(that);
-		const [seg0, seg1] = list;
-		const x = Math.min(seg0.x, seg1.x);
-		const y = Math.min(seg0.y, seg1.y);
-		const offset = this.config.bar_sensitivity;
-		const {width, height} = that.getBBox();
-		const sx = x - offset;
-		const ex = x + width + offset;
-		const sy = y + height + offset;
-		const ey = y - offset;
-
-		const isWithin = sx < mouse[0] &&
-			mouse[0] < ex &&
-			ey < mouse[1] &&
-			mouse[1] < sy;
-
-		return isWithin;
 	}
 };
diff --git a/src/ChartInternal/shape/shape.ts b/src/ChartInternal/shape/shape.ts
index 5c7c2c90b..6c0aaa574 100644
--- a/src/ChartInternal/shape/shape.ts
+++ b/src/ChartInternal/shape/shape.ts
@@ -23,9 +23,9 @@ import {
 	curveStep as d3CurveStep
 } from "d3-shape";
 import {select as d3Select} from "d3-selection";
-import {d3Selection} from "types/types";
+import {d3Selection} from "../../../types/types";
 import CLASS from "../../config/classes";
-import {capitalize, getUnique, isObjectType, isNumber, isValue, isUndefined, notEmpty} from "../../module/util";
+import {capitalize, getPointer, getRectSegList, getUnique, isObjectType, isNumber, isValue, isUndefined, notEmpty} from "../../module/util";
 
 export default {
 	/**
@@ -424,5 +424,26 @@ export default {
 				$$.isStepType(d) ?
 					config.line_step_type : "linear"
 			);
+	},
+
+	isWithinBar(that): boolean {
+		const mouse = getPointer(this.state.event, that);
+		const list = getRectSegList(that);
+		const [seg0, seg1] = list;
+		const x = Math.min(seg0.x, seg1.x);
+		const y = Math.min(seg0.y, seg1.y);
+		const offset = this.config.bar_sensitivity;
+		const {width, height} = that.getBBox();
+		const sx = x - offset;
+		const ex = x + width + offset;
+		const sy = y + height + offset;
+		const ey = y - offset;
+
+		const isWithin = sx < mouse[0] &&
+			mouse[0] < ex &&
+			ey < mouse[1] &&
+			mouse[1] < sy;
+
+		return isWithin;
 	}
 };
diff --git a/test/internals/tooltip-spec.ts b/test/internals/tooltip-spec.ts
index 405d64057..c0aff6fe2 100644
--- a/test/internals/tooltip-spec.ts
+++ b/test/internals/tooltip-spec.ts
@@ -1409,4 +1409,37 @@ describe("TOOLTIP", function() {
 			chart.$.chart.style("margin-left", null);
 		});
 	});
+
+	describe("tooltip: candlestick type with xs option", () => {
+		before(() => {
+			args = {
+				data: {
+					xs: {
+						data1: "x"
+					},
+					columns: [
+						["x", "2021-02-20"],
+						["data1", { open: 1300, high: 1369, low: 1200, close: 1339, volume: 100 }]
+					],
+					type: "candlestick",
+					labels: true,
+				},
+				axis: {
+					x: {
+						type: "timeseries",
+						tick: {
+						format: "%Y-%m-%d",
+						}
+					}
+				}
+			};
+		});
+
+		it("should display tooltip", () => {
+			util.hoverChart(chart, "mousemove", {clientX: 180, clientY: 130});
+
+			expect(chart.$.tooltip.select(".value").html())
+				.to.be.equal(`<b>Open:</b> 1300 <b>High:</b> 1369 <b>Low:</b> 1200 <b>Close:</b> 1339 <b>Volume:</b> 100`);
+		});
+	});
 });