Skip to content

Commit 6a18f9e

Browse files
authored
Merge pull request #79 from pfizer-opensource/tooltip-fixes
Tooltip fixes
2 parents 78a7ec9 + abc1b4b commit 6a18f9e

File tree

6 files changed

+127
-138
lines changed

6 files changed

+127
-138
lines changed

examples/example.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ if (!data || data.length === 0) {
2626
renderGraphs(data, serviceId);
2727
}
2828

29+
// eslint-disable-next-line no-unused-vars
2930
function renderCfdGraph(data, controlsElementSelector, loadConfigInputSelector, resetConfigInputSelector) {
3031
//The cfd area chart and brush window elements css selectors
3132
const cfdGraphElementSelector = "#cfd-area-div";

src/graphs/Renderer.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as d3 from 'd3';
2+
import styles from './tooltipStyles.module.css';
23

34
/**
45
* Represents a generic graphs renderer
@@ -18,6 +19,8 @@ export class Renderer {
1819

1920
constructor(data) {
2021
this.data = data;
22+
this.tooltip = null;
23+
this.tooltipTimeout = null;
2124
}
2225

2326
/**
@@ -143,4 +146,95 @@ export class Renderer {
143146
updateGraph(domain) {
144147
throw new Error('Method not implemented. It must be implemented in subclasses!');
145148
}
149+
150+
/**
151+
* Shows the tooltip with provided event data.
152+
* @param {Object} event - The event data for the tooltip.
153+
*/
154+
showTooltip(event) {
155+
this.clearTooltipTimeout();
156+
!this.tooltip && this.createTooltip();
157+
this.clearTooltipContent();
158+
this.positionTooltip(event.tooltipLeft, event.tooltipTop);
159+
this.populateTooltip(event);
160+
this.tooltipTimeout = setTimeout(() => {
161+
this.hideTooltip();
162+
}, 10000);
163+
164+
this.tooltip.on('mouseleave', () => this.setupMouseLeaveHandler());
165+
}
166+
167+
/**
168+
* Populates the tooltip's content with event data: ticket id and observation body
169+
* @param {Object} event - The event data for the tooltip.
170+
*/
171+
// eslint-disable-next-line no-unused-vars
172+
populateTooltip(event) {
173+
throw new Error('populateTooltip() must be implemented by child class');
174+
}
175+
176+
/**
177+
* Hides the tooltip.
178+
*/
179+
hideTooltip() {
180+
this.clearTooltipTimeout(); // Clear the timeout when manually hiding
181+
this.tooltip?.transition().duration(100).style('opacity', 0).style('pointer-events', 'none');
182+
}
183+
184+
clearTooltipTimeout() {
185+
if (this.tooltipTimeout) {
186+
clearTimeout(this.tooltipTimeout);
187+
this.tooltipTimeout = null;
188+
}
189+
}
190+
191+
cleanupTooltip() {
192+
this.clearTooltipTimeout();
193+
this.hideTooltip();
194+
if (this.tooltip) {
195+
this.tooltip.remove();
196+
this.tooltip = null;
197+
}
198+
}
199+
200+
/**
201+
* Creates a tooltip for the chart used for the observation logging.
202+
*/
203+
createTooltip() {
204+
this.tooltip = d3.select('body').append('div').attr('class', styles.chartTooltip).attr('id', 's-tooltip').style('opacity', 0);
205+
}
206+
207+
/**
208+
* Positions the tooltip on the page.
209+
* @param {number} left - The left position for the tooltip.
210+
* @param {number} top - The top position for the tooltip.
211+
*/
212+
positionTooltip(left, top) {
213+
this.tooltip.transition().duration(100).style('opacity', 0.9).style('pointer-events', 'auto');
214+
this.tooltip.style('left', left + 'px').style('top', top + 'px');
215+
}
216+
217+
/**
218+
* Clears the content of the tooltip.
219+
*/
220+
clearTooltipContent() {
221+
this.tooltip.selectAll('*').remove();
222+
}
223+
224+
setupMouseLeaveHandler(retries = 10) {
225+
const svgNode = this.svg?.node();
226+
if (!svgNode || !svgNode.parentNode) {
227+
if (retries > 0) {
228+
setTimeout(() => this.setupMouseLeaveHandler(retries - 1), 100);
229+
} else {
230+
console.error('SVG parentNode is not available after retries.');
231+
}
232+
return;
233+
}
234+
d3.select(svgNode.parentNode).on('mouseleave', (event) => {
235+
if (event.relatedTarget !== this.tooltip?.node()) {
236+
this.hideTooltip();
237+
}
238+
});
239+
}
146240
}

src/graphs/cfd/CFDRenderer.js

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class CFDRenderer extends UIControlsRenderer {
7575
if (this.eventBus && Array.isArray(mouseChartsEvents)) {
7676
mouseChartsEvents.forEach((chart) => {
7777
this.eventBus?.addEventListener(`${chart}-mousemove`, (event) => this.#handleMouseEvent(event, `${chart}-mousemove`));
78-
this.eventBus?.addEventListener(`${chart}-mouseleave`, () => this.hideTooltipAndMovingLine());
78+
this.eventBus?.addEventListener(`${chart}-mouseleave`, () => this.hideTooltip());
7979
});
8080
}
8181
}
@@ -432,15 +432,14 @@ export class CFDRenderer extends UIControlsRenderer {
432432
/**
433433
* Shows the tooltip and the moving line at a specific position
434434
* @param {Object} event - The event object containing details: coordinates for the tooltip and line.
435-
* @private
436435
*/
437-
#showTooltipAndMovingLine(event) {
438-
!this.tooltip && this.#createTooltipAndMovingLine(event.lineX, event.lineY);
436+
showTooltip(event) {
437+
!this.tooltip && this.createTooltip(event.lineX, event.lineY);
439438
let { tooltipWidth, tooltipTop } = this.computeTooltipWidthAndTop(event);
440439

441-
this.#clearTooltipAndMovingLine(event.lineX, event.lineY);
442-
this.#positionTooltip(event.tooltipLeft, tooltipTop, tooltipWidth);
443-
this.#populateTooltip(event);
440+
this.clearTooltipContent(event.lineX, event.lineY);
441+
this.positionTooltip(event.tooltipLeft, tooltipTop, tooltipWidth);
442+
this.populateTooltip(event);
444443
}
445444

446445
computeTooltipWidthAndTop(event) {
@@ -482,19 +481,30 @@ export class CFDRenderer extends UIControlsRenderer {
482481
/**
483482
* Hides the tooltip and the moving line on the chart.
484483
*/
485-
hideTooltipAndMovingLine() {
484+
hideTooltip() {
486485
if (this.tooltip) {
487486
this.tooltip.transition().duration(100).style('opacity', 0).style('pointer-events', 'none');
488487
this.cfdLine.transition().duration(100).style('display', 'none');
489488
this.#removeMetricsLines();
490489
}
491490
}
492491

492+
cleanupTooltip() {
493+
this.hideTooltip();
494+
if (this.tooltip) {
495+
this.tooltip.remove();
496+
this.tooltip = null;
497+
}
498+
if (this.cfdLine) {
499+
this.cfdLine.remove();
500+
this.cfdLine = null;
501+
}
502+
}
503+
493504
/**
494505
* Creates a tooltip and a moving line for the chart used for the metrics and observation logging.
495-
* @private
496506
*/
497-
#createTooltipAndMovingLine(x, y) {
507+
createTooltip(x, y) {
498508
this.tooltip = d3.select('body').append('div').attr('class', styles.chartTooltip).attr('id', 'c-tooltip').style('opacity', 0);
499509
this.cfdLine = this.chartArea
500510
?.append('line')
@@ -509,22 +519,28 @@ export class CFDRenderer extends UIControlsRenderer {
509519

510520
/**
511521
* Positions the tooltip on the page.
512-
* @private
513522
* @param {number} left - The left position for the tooltip.
514523
* @param {number} top - The top position for the tooltip.
515524
* @param {number} width - The width for the tooltip.
516525
*/
517-
#positionTooltip(left, top, width) {
526+
positionTooltip(left, top, width) {
518527
this.tooltip?.transition().duration(100).style('opacity', 0.9).style('pointer-events', 'auto');
519528
this.tooltip?.style('left', left - width + 'px').style('top', top + 'px');
520529
}
521530

531+
/**
532+
* Clears the content of the tooltip and the moving line.
533+
*/
534+
clearTooltipContent(x, y) {
535+
this.cfdLine?.attr('stroke', 'black').attr('y1', 0).attr('y2', y).attr('x1', x).attr('x2', x).style('display', null);
536+
this.tooltip?.selectAll('*').remove();
537+
}
538+
522539
/**
523540
* Populates the tooltip's content with event data: data, metrics and observation body
524-
* @private
525541
* @param {Object} event - The event data for the tooltip.
526542
*/
527-
#populateTooltip(event) {
543+
populateTooltip(event) {
528544
this.tooltip?.append('p').text(formatDateToLocalString(event.date)).attr('class', styles.tooltipDate);
529545

530546
const gridContainer = this.tooltip?.append('div').attr('class', styles.tooltipGrid);
@@ -566,15 +582,6 @@ export class CFDRenderer extends UIControlsRenderer {
566582
}
567583
}
568584

569-
/**
570-
* Clears the content of the tooltip and the moving line.
571-
* @private
572-
*/
573-
#clearTooltipAndMovingLine(x, y) {
574-
this.cfdLine?.attr('stroke', 'black').attr('y1', 0).attr('y2', y).attr('x1', x).attr('x2', x).style('display', null);
575-
this.tooltip?.selectAll('*').remove();
576-
}
577-
578585
//endregion
579586

580587
//region Metrics
@@ -632,7 +639,7 @@ export class CFDRenderer extends UIControlsRenderer {
632639
observationBody: observation?.body,
633640
observationId: observation?.id,
634641
};
635-
this.#showTooltipAndMovingLine(data);
642+
this.showTooltip(data);
636643
eventName.includes('click') && this.eventBus?.emitEvents(eventName, data);
637644
}
638645
}
@@ -642,7 +649,7 @@ export class CFDRenderer extends UIControlsRenderer {
642649
* @private
643650
*/
644651
#setupMouseLeaveHandler() {
645-
this.chartArea?.on('mouseleave', () => this.hideTooltipAndMovingLine());
652+
this.chartArea?.on('mouseleave', () => this.hideTooltip());
646653
}
647654

648655
/**

src/graphs/control-chart/ControlRenderer.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ export class ControlRenderer extends ScatterplotRenderer {
5151
}
5252

5353
populateTooltip(event) {
54-
console.log('populateTooltip', event);
5554
this.tooltip
5655
.style('pointer-events', 'auto')
5756
.style('opacity', 0.9)

src/graphs/scatterplot/ScatterplotRenderer.js

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { calculateDaysBetweenDates } from '../../utils/utils.js';
22
import { UIControlsRenderer } from '../UIControlsRenderer.js';
3-
import styles from '../tooltipStyles.module.css';
43

54
import * as d3 from 'd3';
65

@@ -487,32 +486,6 @@ export class ScatterplotRenderer extends UIControlsRenderer {
487486

488487
//region Tooltip
489488

490-
/**
491-
* Shows the tooltip with provided event data.
492-
* @param {Object} event - The event data for the tooltip.
493-
*/
494-
showTooltip(event) {
495-
!this.tooltip && this.#createTooltip();
496-
this.#clearTooltipContent();
497-
this.#positionTooltip(event.tooltipLeft, event.tooltipTop);
498-
this.populateTooltip(event);
499-
}
500-
501-
/**
502-
* Hides the tooltip.
503-
*/
504-
hideTooltip() {
505-
this.tooltip?.transition().duration(100).style('opacity', 0).style('pointer-events', 'none');
506-
}
507-
508-
/**
509-
* Creates a tooltip for the chart used for the observation logging.
510-
* @private
511-
*/
512-
#createTooltip() {
513-
this.tooltip = d3.select('body').append('div').attr('class', styles.chartTooltip).attr('id', 's-tooltip').style('opacity', 0);
514-
}
515-
516489
/**
517490
* Populates the tooltip's content with event data: ticket id and observation body
518491
* @private
@@ -532,26 +505,6 @@ export class ScatterplotRenderer extends UIControlsRenderer {
532505
});
533506
event.observationBody && this.tooltip.append('p').text('Observation: ' + event.observationBody);
534507
}
535-
536-
/**
537-
* Positions the tooltip on the page.
538-
* @private
539-
* @param {number} left - The left position for the tooltip.
540-
* @param {number} top - The top position for the tooltip.
541-
*/
542-
#positionTooltip(left, top) {
543-
this.tooltip.transition().duration(100).style('opacity', 0.9).style('pointer-events', 'auto');
544-
this.tooltip.style('left', left + 'px').style('top', top + 'px');
545-
}
546-
547-
/**
548-
* Clears the content of the tooltip.
549-
* @private
550-
*/
551-
#clearTooltipContent() {
552-
this.tooltip.selectAll('*').remove();
553-
}
554-
555508
//endregion
556509

557510
//region Metrics
@@ -615,7 +568,6 @@ export class ScatterplotRenderer extends UIControlsRenderer {
615568
}
616569
d3.select(svgNode.parentNode).on('mouseleave', (event) => {
617570
if (event.relatedTarget !== this.tooltip?.node()) {
618-
console.log('setup mouse leave to hide tooltip');
619571
this.hideTooltip();
620572
}
621573
});

0 commit comments

Comments
 (0)