Skip to content

Tooltip fixes #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ if (!data || data.length === 0) {
renderGraphs(data, serviceId);
}

// eslint-disable-next-line no-unused-vars
function renderCfdGraph(data, controlsElementSelector, loadConfigInputSelector, resetConfigInputSelector) {
//The cfd area chart and brush window elements css selectors
const cfdGraphElementSelector = "#cfd-area-div";
Expand Down
94 changes: 94 additions & 0 deletions src/graphs/Renderer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as d3 from 'd3';
import styles from './tooltipStyles.module.css';

/**
* Represents a generic graphs renderer
Expand All @@ -18,6 +19,8 @@ export class Renderer {

constructor(data) {
this.data = data;
this.tooltip = null;
this.tooltipTimeout = null;
}

/**
Expand Down Expand Up @@ -143,4 +146,95 @@ export class Renderer {
updateGraph(domain) {
throw new Error('Method not implemented. It must be implemented in subclasses!');
}

/**
* Shows the tooltip with provided event data.
* @param {Object} event - The event data for the tooltip.
*/
showTooltip(event) {
this.clearTooltipTimeout();
!this.tooltip && this.createTooltip();
this.clearTooltipContent();
this.positionTooltip(event.tooltipLeft, event.tooltipTop);
this.populateTooltip(event);
this.tooltipTimeout = setTimeout(() => {
this.hideTooltip();
}, 10000);

this.tooltip.on('mouseleave', () => this.setupMouseLeaveHandler());
}

/**
* Populates the tooltip's content with event data: ticket id and observation body
* @param {Object} event - The event data for the tooltip.
*/
// eslint-disable-next-line no-unused-vars
populateTooltip(event) {
throw new Error('populateTooltip() must be implemented by child class');
}

/**
* Hides the tooltip.
*/
hideTooltip() {
this.clearTooltipTimeout(); // Clear the timeout when manually hiding
this.tooltip?.transition().duration(100).style('opacity', 0).style('pointer-events', 'none');
}

clearTooltipTimeout() {
if (this.tooltipTimeout) {
clearTimeout(this.tooltipTimeout);
this.tooltipTimeout = null;
}
}

cleanupTooltip() {
this.clearTooltipTimeout();
this.hideTooltip();
if (this.tooltip) {
this.tooltip.remove();
this.tooltip = null;
}
}

/**
* Creates a tooltip for the chart used for the observation logging.
*/
createTooltip() {
this.tooltip = d3.select('body').append('div').attr('class', styles.chartTooltip).attr('id', 's-tooltip').style('opacity', 0);
}

/**
* Positions the tooltip on the page.
* @param {number} left - The left position for the tooltip.
* @param {number} top - The top position for the tooltip.
*/
positionTooltip(left, top) {
this.tooltip.transition().duration(100).style('opacity', 0.9).style('pointer-events', 'auto');
this.tooltip.style('left', left + 'px').style('top', top + 'px');
}

/**
* Clears the content of the tooltip.
*/
clearTooltipContent() {
this.tooltip.selectAll('*').remove();
}

setupMouseLeaveHandler(retries = 10) {
const svgNode = this.svg?.node();
if (!svgNode || !svgNode.parentNode) {
if (retries > 0) {
setTimeout(() => this.setupMouseLeaveHandler(retries - 1), 100);
} else {
console.error('SVG parentNode is not available after retries.');
}
return;
}
d3.select(svgNode.parentNode).on('mouseleave', (event) => {
if (event.relatedTarget !== this.tooltip?.node()) {
this.hideTooltip();
}
});
}
}
57 changes: 32 additions & 25 deletions src/graphs/cfd/CFDRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class CFDRenderer extends UIControlsRenderer {
if (this.eventBus && Array.isArray(mouseChartsEvents)) {
mouseChartsEvents.forEach((chart) => {
this.eventBus?.addEventListener(`${chart}-mousemove`, (event) => this.#handleMouseEvent(event, `${chart}-mousemove`));
this.eventBus?.addEventListener(`${chart}-mouseleave`, () => this.hideTooltipAndMovingLine());
this.eventBus?.addEventListener(`${chart}-mouseleave`, () => this.hideTooltip());
});
}
}
Expand Down Expand Up @@ -432,15 +432,14 @@ export class CFDRenderer extends UIControlsRenderer {
/**
* Shows the tooltip and the moving line at a specific position
* @param {Object} event - The event object containing details: coordinates for the tooltip and line.
* @private
*/
#showTooltipAndMovingLine(event) {
!this.tooltip && this.#createTooltipAndMovingLine(event.lineX, event.lineY);
showTooltip(event) {
!this.tooltip && this.createTooltip(event.lineX, event.lineY);
let { tooltipWidth, tooltipTop } = this.computeTooltipWidthAndTop(event);

this.#clearTooltipAndMovingLine(event.lineX, event.lineY);
this.#positionTooltip(event.tooltipLeft, tooltipTop, tooltipWidth);
this.#populateTooltip(event);
this.clearTooltipContent(event.lineX, event.lineY);
this.positionTooltip(event.tooltipLeft, tooltipTop, tooltipWidth);
this.populateTooltip(event);
}

computeTooltipWidthAndTop(event) {
Expand Down Expand Up @@ -482,19 +481,30 @@ export class CFDRenderer extends UIControlsRenderer {
/**
* Hides the tooltip and the moving line on the chart.
*/
hideTooltipAndMovingLine() {
hideTooltip() {
if (this.tooltip) {
this.tooltip.transition().duration(100).style('opacity', 0).style('pointer-events', 'none');
this.cfdLine.transition().duration(100).style('display', 'none');
this.#removeMetricsLines();
}
}

cleanupTooltip() {
this.hideTooltip();
if (this.tooltip) {
this.tooltip.remove();
this.tooltip = null;
}
if (this.cfdLine) {
this.cfdLine.remove();
this.cfdLine = null;
}
}

/**
* Creates a tooltip and a moving line for the chart used for the metrics and observation logging.
* @private
*/
#createTooltipAndMovingLine(x, y) {
createTooltip(x, y) {
this.tooltip = d3.select('body').append('div').attr('class', styles.chartTooltip).attr('id', 'c-tooltip').style('opacity', 0);
this.cfdLine = this.chartArea
?.append('line')
Expand All @@ -509,22 +519,28 @@ export class CFDRenderer extends UIControlsRenderer {

/**
* Positions the tooltip on the page.
* @private
* @param {number} left - The left position for the tooltip.
* @param {number} top - The top position for the tooltip.
* @param {number} width - The width for the tooltip.
*/
#positionTooltip(left, top, width) {
positionTooltip(left, top, width) {
this.tooltip?.transition().duration(100).style('opacity', 0.9).style('pointer-events', 'auto');
this.tooltip?.style('left', left - width + 'px').style('top', top + 'px');
}

/**
* Clears the content of the tooltip and the moving line.
*/
clearTooltipContent(x, y) {
this.cfdLine?.attr('stroke', 'black').attr('y1', 0).attr('y2', y).attr('x1', x).attr('x2', x).style('display', null);
this.tooltip?.selectAll('*').remove();
}

/**
* Populates the tooltip's content with event data: data, metrics and observation body
* @private
* @param {Object} event - The event data for the tooltip.
*/
#populateTooltip(event) {
populateTooltip(event) {
this.tooltip?.append('p').text(formatDateToLocalString(event.date)).attr('class', styles.tooltipDate);

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

/**
* Clears the content of the tooltip and the moving line.
* @private
*/
#clearTooltipAndMovingLine(x, y) {
this.cfdLine?.attr('stroke', 'black').attr('y1', 0).attr('y2', y).attr('x1', x).attr('x2', x).style('display', null);
this.tooltip?.selectAll('*').remove();
}

//endregion

//region Metrics
Expand Down Expand Up @@ -632,7 +639,7 @@ export class CFDRenderer extends UIControlsRenderer {
observationBody: observation?.body,
observationId: observation?.id,
};
this.#showTooltipAndMovingLine(data);
this.showTooltip(data);
eventName.includes('click') && this.eventBus?.emitEvents(eventName, data);
}
}
Expand All @@ -642,7 +649,7 @@ export class CFDRenderer extends UIControlsRenderer {
* @private
*/
#setupMouseLeaveHandler() {
this.chartArea?.on('mouseleave', () => this.hideTooltipAndMovingLine());
this.chartArea?.on('mouseleave', () => this.hideTooltip());
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/graphs/control-chart/ControlRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export class ControlRenderer extends ScatterplotRenderer {
}

populateTooltip(event) {
console.log('populateTooltip', event);
this.tooltip
.style('pointer-events', 'auto')
.style('opacity', 0.9)
Expand Down
48 changes: 0 additions & 48 deletions src/graphs/scatterplot/ScatterplotRenderer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { calculateDaysBetweenDates } from '../../utils/utils.js';
import { UIControlsRenderer } from '../UIControlsRenderer.js';
import styles from '../tooltipStyles.module.css';

import * as d3 from 'd3';

Expand Down Expand Up @@ -487,32 +486,6 @@ export class ScatterplotRenderer extends UIControlsRenderer {

//region Tooltip

/**
* Shows the tooltip with provided event data.
* @param {Object} event - The event data for the tooltip.
*/
showTooltip(event) {
!this.tooltip && this.#createTooltip();
this.#clearTooltipContent();
this.#positionTooltip(event.tooltipLeft, event.tooltipTop);
this.populateTooltip(event);
}

/**
* Hides the tooltip.
*/
hideTooltip() {
this.tooltip?.transition().duration(100).style('opacity', 0).style('pointer-events', 'none');
}

/**
* Creates a tooltip for the chart used for the observation logging.
* @private
*/
#createTooltip() {
this.tooltip = d3.select('body').append('div').attr('class', styles.chartTooltip).attr('id', 's-tooltip').style('opacity', 0);
}

/**
* Populates the tooltip's content with event data: ticket id and observation body
* @private
Expand All @@ -532,26 +505,6 @@ export class ScatterplotRenderer extends UIControlsRenderer {
});
event.observationBody && this.tooltip.append('p').text('Observation: ' + event.observationBody);
}

/**
* Positions the tooltip on the page.
* @private
* @param {number} left - The left position for the tooltip.
* @param {number} top - The top position for the tooltip.
*/
#positionTooltip(left, top) {
this.tooltip.transition().duration(100).style('opacity', 0.9).style('pointer-events', 'auto');
this.tooltip.style('left', left + 'px').style('top', top + 'px');
}

/**
* Clears the content of the tooltip.
* @private
*/
#clearTooltipContent() {
this.tooltip.selectAll('*').remove();
}

//endregion

//region Metrics
Expand Down Expand Up @@ -615,7 +568,6 @@ export class ScatterplotRenderer extends UIControlsRenderer {
}
d3.select(svgNode.parentNode).on('mouseleave', (event) => {
if (event.relatedTarget !== this.tooltip?.node()) {
console.log('setup mouse leave to hide tooltip');
this.hideTooltip();
}
});
Expand Down
Loading