From 52083bfccf69fa5c6d25fa25982c5dc20fe1a353 Mon Sep 17 00:00:00 2001 From: Jae Sung Park Date: Thu, 24 Aug 2023 19:17:17 +0900 Subject: [PATCH] fix(tooltip): Fix tootip display on tooltip.init Fix tooltip initial call to call .tooltip.show() api to follow internal flow process Fix #3369 --- src/ChartInternal/internals/text.ts | 27 +--- src/ChartInternal/internals/tooltip.ts | 48 +++---- src/config/Options/common/tooltip.ts | 13 +- src/module/util.ts | 6 +- test/internals/text-spec.ts | 165 ++++++++++++++++--------- test/internals/tooltip-spec.ts | 85 +++++++++++-- types/options.d.ts | 6 +- 7 files changed, 213 insertions(+), 137 deletions(-) diff --git a/src/ChartInternal/internals/text.ts b/src/ChartInternal/internals/text.ts index e47366c15..49cf2d6af 100644 --- a/src/ChartInternal/internals/text.ts +++ b/src/ChartInternal/internals/text.ts @@ -291,9 +291,6 @@ export default { const anchorString = getRotateAnchor(angle); const rotateString = angle ? `rotate(${angle})` : ""; - - // $$.meetsLabelThreshold(ratio, - $$.$el.text .style("fill", $$.getStylePropValue($$.updateTextColor)) .attr("filter", $$.updateTextBacgroundColor.bind($$)) @@ -452,7 +449,7 @@ export default { */ getXForText(points, d: IDataRow, textElement): number { const $$ = this; - const {config, state} = $$; + const {config} = $$; const isRotated = config.axis_rotated; const isTreemapType = $$.isTreemapType(d); let xPos = points[0][0]; @@ -484,17 +481,6 @@ export default { } } - // show labels regardless of the domain if value is null - if (d.value === null) { - if (xPos > state.width) { - const {width} = getBoundingRect(textElement); - - xPos = state.width - width; - } else if (xPos < 0) { - xPos = 4; - } - } - if (isRotated || isTreemapType) { xPos += $$.getCenteredTextPos(d, points, textElement, "x"); } @@ -572,17 +558,6 @@ export default { } } - // show labels regardless of the domain if value is null - if (d.value === null && !isRotated) { - const boxHeight = rect.height; - - if (yPos < boxHeight) { - yPos = boxHeight; - } else if (yPos > state.height) { - yPos = state.height - 4; - } - } - if (!isRotated || isTreemapType) { yPos += $$.getCenteredTextPos(d, points, textElement, "y"); } diff --git a/src/ChartInternal/internals/tooltip.ts b/src/ChartInternal/internals/tooltip.ts index f93a99488..a4c19cc6e 100644 --- a/src/ChartInternal/internals/tooltip.ts +++ b/src/ChartInternal/internals/tooltip.ts @@ -6,7 +6,7 @@ import {select as d3Select} from "d3-selection"; import {document} from "../../module/browser"; import {$ARC, $TOOLTIP} from "../../config/classes"; import type {IArcData, IDataRow} from "../data/IData"; -import {getPointer, isFunction, isObject, isString, isValue, callFn, sanitize, tplProcess, isUndefined, parseDate} from "../../module/util"; +import {getPointer, isEmpty, isFunction, isObject, isString, isValue, callFn, sanitize, tplProcess, isUndefined, parseDate} from "../../module/util"; export default { /** @@ -31,50 +31,36 @@ export default { $$.bindTooltipResizePos(); }, + /** + * Show tooltip at initialization. + * Is called only when tooltip.init.show=true option is set + * @private + */ initShowTooltip(): void { const $$ = this; const {config, $el, state: {hasAxis, hasRadar}} = $$; // Show tooltip if needed if (config.tooltip_init_show) { - const isArc = !(hasAxis && hasRadar); + const isArc = !(hasAxis || hasRadar); if ($$.axis?.isTimeSeries() && isString(config.tooltip_init_x)) { - const targets = $$.data.targets[0]; - let i; - let val; - config.tooltip_init_x = parseDate.call($$, config.tooltip_init_x); - - for (i = 0; (val = targets.values[i]); i++) { - if ((val.x - config.tooltip_init_x) === 0) { - break; - } - } - - config.tooltip_init_x = i; } - let data = $$.data.targets.map(d => { - const x = isArc ? 0 : config.tooltip_init_x; - - return $$.addName(d.values[x]); + $$.api.tooltip.show({ + data: { + [isArc ? "index" : "x"]: config.tooltip_init_x + } }); - if (isArc) { - data = [data[config.tooltip_init_x]]; - } + const position = config.tooltip_init_position; - $el.tooltip.html($$.getTooltipHTML( - data, - $$.axis?.getXAxisTickFormat(), - $$.getDefaultValueFormat(), - $$.color - )); + if (!config.tooltip_contents.bindto && !isEmpty(position)) { + const {top = 0, left = 50} = position; - if (!config.tooltip_contents.bindto) { - $el.tooltip.style("top", config.tooltip_init_position.top) - .style("left", config.tooltip_init_position.left) + $el.tooltip.style("top", isString(top) ? top : `${top}px`) + .style("left", isString(left) ? left : `${left}px`) .style("display", null); } } @@ -368,7 +354,7 @@ export default { * @param {SVGElement} eventTarget Event element * @private */ - showTooltip(selectedData: IDataRow[], eventTarget?: SVGElement): void { + showTooltip(selectedData: IDataRow[], eventTarget: SVGElement): void { const $$ = this; const {config, $el: {tooltip}} = $$; const dataToShow = selectedData.filter(d => d && isValue($$.getBaseValue(d))); diff --git a/src/config/Options/common/tooltip.ts b/src/config/Options/common/tooltip.ts index ac6e1b1fb..69926089d 100644 --- a/src/config/Options/common/tooltip.ts +++ b/src/config/Options/common/tooltip.ts @@ -63,7 +63,7 @@ export default { * - The value array length should match with the data length * @property {boolean} [tooltip.init.show=false] Show tooltip at the initialization. * @property {number} [tooltip.init.x=0] Set x Axis index(or index for Arc(donut, gauge, pie) types) to be shown at the initialization. - * @property {object} [tooltip.init.position={top: "0px",left: "50px"}] Set the position of tooltip at the initialization. + * @property {object} [tooltip.init.position] Set the position of tooltip at the initialization. * @property {Function} [tooltip.onshow] Set a callback that will be invoked before the tooltip is shown. * @property {Function} [tooltip.onhide] Set a callback that will be invoked before the tooltip is hidden. * @property {Function} [tooltip.onshown] Set a callback that will be invoked after the tooltip is shown @@ -155,10 +155,10 @@ export default { * // show at the initialization * init: { * show: true, - * x: 2, // x Axis index(or index for Arc(donut, gauge, pie) types) + * x: 2, // x Axis index (or index for Arc(donut, gauge, pie) types) * position: { - * top: "150px", - * left: "250px" + * top: "150px", // specify as number or as string with 'px' unit string + * left: 250 // specify as number or as string with 'px' unit string * } * }, * @@ -212,10 +212,7 @@ export default { > {}, tooltip_init_show: false, tooltip_init_x: 0, - tooltip_init_position: { - top: "0px", - left: "50px" - }, + tooltip_init_position: undefined, tooltip_linked: false, tooltip_linked_name: "", tooltip_onshow: () => {}, diff --git a/src/module/util.ts b/src/module/util.ts index 76fc55817..95a8f93c8 100644 --- a/src/module/util.ts +++ b/src/module/util.ts @@ -266,7 +266,11 @@ function getPathBox( */ function getPointer(event, element?: Element): number[] { const touches = event && (event.touches || (event.sourceEvent && event.sourceEvent.touches))?.[0]; - const pointer = d3Pointer(touches || event, element); + let pointer = [0, 0]; + + try { + pointer = d3Pointer(touches || event, element); + } catch (e) {} return pointer.map(v => (isNaN(v) ? 0 : v)); } diff --git a/test/internals/text-spec.ts b/test/internals/text-spec.ts index 41475a753..b22875864 100644 --- a/test/internals/text-spec.ts +++ b/test/internals/text-spec.ts @@ -180,90 +180,137 @@ describe("TEXT", () => { }); describe("rotate", () => { - before(() => { - args = { - data: { - columns: [ - ["data1", 90, 100, -100] - ], - type: "bar", - labels: { - rotate: 90 + describe("normal axis", () => { + before(() => { + args = { + data: { + columns: [ + ["data1", 90, 100, -100] + ], + type: "bar", + labels: { + rotate: 90 + } + }, + axis: { + rotated: false } - }, - axis: { - rotated: false } - } - }); + }); - it("rotate attribute should be applied", () => { - chart.$.text.texts.each(function(d) { - const transform = this.getAttribute("transform"); - const anchor = this.getAttribute("text-anchor"); + it("rotate attribute should be applied", () => { + chart.$.text.texts.each(function(d) { + const transform = this.getAttribute("transform"); + const anchor = this.getAttribute("text-anchor"); - expect(transform.indexOf(`rotate(${args.data.labels.rotate})`) > -1).to.be.true; - expect(anchor).to.be.equal("end"); + expect(transform.indexOf(`rotate(${args.data.labels.rotate})`) > -1).to.be.true; + expect(anchor).to.be.equal("end"); - if (d.value < 0) { - const y = +this.getAttribute("transform").match(/\s(\d+\.\d+)/)[1]; + if (d.value < 0) { + const y = +this.getAttribute("transform").match(/\s(\d+\.\d+)/)[1]; - expect(y).to.be.closeTo(405, 1); - } + expect(y).to.be.closeTo(405, 1); + } + }); }); - }); - it("set options: data.labels.rotate=180", () => { - args.data.labels.rotate = 180; - }); + it("set options: data.labels.rotate=180", () => { + args.data.labels.rotate = 180; + }); - it("text-anchor should be middle for rotate(180deg)", () => { - chart.$.text.texts.each(function() { - const anchor = this.getAttribute("text-anchor"); + it("text-anchor should be middle for rotate(180deg)", () => { + chart.$.text.texts.each(function() { + const anchor = this.getAttribute("text-anchor"); - expect(anchor).to.be.equal("middle"); + expect(anchor).to.be.equal("middle"); + }); }); - }); - it("set options: data.labels.rotate=270", () => { - args.data.labels.rotate = 270; - }); + it("set options: data.labels.rotate=270", () => { + args.data.labels.rotate = 270; + }); - it("text-anchor should be middle for rotate(270deg)", () => { - chart.$.text.texts.each(function(d) { - const anchor = this.getAttribute("text-anchor"); + it("text-anchor should be middle for rotate(270deg)", () => { + chart.$.text.texts.each(function(d) { + const anchor = this.getAttribute("text-anchor"); - expect(anchor).to.be.equal("start"); + expect(anchor).to.be.equal("start"); - if (d.value < 0) { - const y = +this.getAttribute("transform").match(/\s(\d+\.\d+)/)[1]; + if (d.value < 0) { + const y = +this.getAttribute("transform").match(/\s(\d+\.\d+)/)[1]; - expect(y).to.be.closeTo(405, 1); - } + expect(y).to.be.closeTo(405, 1); + } + }); }); - }); - it("set options: axis.rotated=true", () => { - args.axis.rotated = true; - args.data.labels.rotate = 90; - }); + it("set options: axis.rotated=true", () => { + args.axis.rotated = true; + args.data.labels.rotate = 90; + }); - it("check for rotated axis", () => { - const expectedY = [80, 220, 362]; + it("check for rotated axis", () => { + const expectedY = [80, 220, 362]; - chart.$.text.texts.each(function(d, i) { - const transform = +this.getAttribute("transform").match(/\s(\d+\.\d+)/)[1]; - const anchor = this.getAttribute("text-anchor"); + chart.$.text.texts.each(function(d, i) { + const transform = +this.getAttribute("transform").match(/\s(\d+\.\d+)/)[1]; + const anchor = this.getAttribute("text-anchor"); - expect(transform).to.be.closeTo(expectedY[i], 1); - expect(anchor).to.be.equal("end"); + expect(transform).to.be.closeTo(expectedY[i], 1); + expect(anchor).to.be.equal("end"); - if (d.value < 0) { - const x = +this.getAttribute("transform").match(/\((\d+\.\d+)/)[1]; + if (d.value < 0) { + const x = +this.getAttribute("transform").match(/\((\d+\.\d+)/)[1]; + + expect(x).to.be.closeTo(57, 1); + } + }); + }); + }); - expect(x).to.be.closeTo(57, 1); + describe("rotated axis", () => { + before(() => { + args = { + data: { + columns: [ + ["data1", 90, 100, -100] + ], + type: "bar", + labels: { + rotate: 90 + } + }, + axis: { + rotated: true + } } }); + + it("check when rotate=90", () => { + chart.$.text.texts.each(function() { + expect(this.getAttribute("text-anchor")).to.be.equal("end"); + }); + }); + + it("set options: data.labels.rotate=200", () => { + args.data.labels.rotate = 200; + }); + + it("check when rotate=200", () => { + chart.$.text.texts.each(function() { + expect(this.getAttribute("text-anchor")).to.be.equal("start"); + }); + }); + + it("set options: data.labels.rotate=400", () => { + args.data.labels.rotate = 400; + }); + + it("check when rotate=400", () => { + chart.$.text.texts.each(function() { + expect(this.getAttribute("text-anchor")).to.be.equal("middle"); + }); + }); }); }); diff --git a/test/internals/tooltip-spec.ts b/test/internals/tooltip-spec.ts index e4e0ad15f..2897c2bcf 100644 --- a/test/internals/tooltip-spec.ts +++ b/test/internals/tooltip-spec.ts @@ -1153,18 +1153,13 @@ describe("TOOLTIP", function() { ["data3", 150, 120, 110, 140, 115, 125] ] }, - interaction: { - inputType: { - touch: true - } - }, tooltip: { init: { show: true, x: 1, position: { - left: "100px", - top: "30px" + left: 100, + top: 30 } } } @@ -1177,11 +1172,83 @@ describe("TOOLTIP", function() { left: tooltip.style("left"), top: tooltip.style("top") }; + const dataLen = chart.data().length; + const name = tooltip.selectAll(".name"); + const value = tooltip.selectAll(".value"); expect(tooltip.style("display")).to.be.equal("block"); - expect(pos.left).to.be.equal(args.tooltip.init.position.left); - expect(pos.top).to.be.equal(args.tooltip.init.position.top); + expect(pos.left).to.be.equal(args.tooltip.init.position.left + "px"); + expect(pos.top).to.be.equal(args.tooltip.init.position.top + "px"); + + expect(name.size()).to.be.equal(dataLen); + expect(value.size()).to.be.equal(dataLen); + expect(+tooltip.select("th").text()).to.be.equal(args.tooltip.init.x); + }); + + it("set options: data.type='pie'", () => { + args.data.type = "pie"; + }); + + it("check if tooltip shows correct data values for pie", () => { + const tooltip = chart.$.tooltip; + const name = tooltip.selectAll(".name"); + const value = tooltip.selectAll(".value"); + + expect(name.size()).to.be.equal(1); + expect(value.size()).to.be.equal(1); + + expect(name.text()).to.be.equal("data3"); + expect(value.text()).to.be.equal("37.1%"); + }); + + it("set options: timeseries x axis", () => { + args = { + data: { + x: "x", + columns: [ + ["x", "2023-08-24", "2023-08-25", "2023-08-26", "2023-08-27", "2023-08-28", "2023-08-29"], + ["data1", 30, 200, 100, 400, 150, 250], + ["data2", 50, 20, 10, 40, 15, 25], + ["data3", 150, 120, 110, 140, 115, 125] + ] + }, + axis: { + x: { + type: "timeseries", + tick: { + format: "%Y-%m-%d" + } + } + }, + tooltip: { + init: { + show: true, + x: "2023-08-28" + } + } + }; + }); + + it("check if tooltip shows correct data values", () => { + const tooltip = chart.$.tooltip; + const dataLen = chart.data().length; + const name = tooltip.selectAll(".name"); + const value = tooltip.selectAll(".value"); + + const valueAtIndex = chart.data().map(v => { + return v.values[4].value + }); + + // is has correct data values? + value.each(function(d, i) { + expect(+this.textContent).to.be.equal(valueAtIndex[i]); + }); + + expect(tooltip.node().getBoundingClientRect().x).to.be.closeTo(405, 5); + expect(name.size()).to.be.equal(dataLen); + expect(value.size()).to.be.equal(dataLen); + expect(tooltip.select("th").text()).to.be.equal(args.tooltip.init.x); }); }); diff --git a/types/options.d.ts b/types/options.d.ts index 8371385c2..3a8f38fcf 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -646,14 +646,14 @@ export interface TooltipOptions { /** * Set x Axis index to be shown at the initialization. */ - x?: number; + x?: number | string; /** * Set the position of tooltip at the initialization. */ position?: { - top?: string; - left?: string; + top?: number | string; + left?: number | string; } };