Skip to content

Commit f50e233

Browse files
authored
Merge pull request #53 from pfizer-opensource/cfd-tooltip-fixes
Fix the CFD tooltip
2 parents 1168e75 + 30801f5 commit f50e233

File tree

4 files changed

+68
-34
lines changed

4 files changed

+68
-34
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,14 @@
3636
"peerDependencies": {
3737
"css-loader": "^6.8.1",
3838
"style-loader": "^3.3.3"
39-
4039
},
4140
"devDependencies": {
41+
"css-loader": "^6.8.1",
4242
"eslint": "^8.44.0",
4343
"eslint-config-prettier": "^8.8.0",
4444
"eslint-plugin-prettier": "^4.2.1",
4545
"jest": "^29.7.0",
4646
"prettier": "^3.0.0",
47-
"css-loader": "^6.8.1",
4847
"style-loader": "^3.3.3"
4948
}
5049
}

src/graphs/cfd/CFDRenderer.js

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class CFDRenderer extends UIControlsRenderer {
108108
[this.width, this.focusHeight - this.margin.top + 1],
109109
])
110110
.on('brush', ({ selection }) => {
111-
this.selectedTimeRange = selection.map(this.x.invert, this.x);
111+
this.selectedTimeRange = selection.map(this.x?.invert, this.x);
112112
this.updateGraph(this.selectedTimeRange);
113113
if (this.isManualBrushUpdate && this.eventBus) {
114114
this.eventBus?.emitEvents(`change-time-range-${this.chartName}`, this.selectedTimeRange);
@@ -160,7 +160,7 @@ class CFDRenderer extends UIControlsRenderer {
160160
this.drawYAxis(this.gy, this.currentYScale);
161161

162162
this.chartArea
163-
.selectAll('path')
163+
?.selectAll('path')
164164
.attr('class', (d) => 'area ' + d.key)
165165
.style('fill', (d) => this.#statesColors(d.key))
166166
.attr('d', this.#createAreaGenerator(this.currentXScale, this.currentYScale));
@@ -193,7 +193,7 @@ class CFDRenderer extends UIControlsRenderer {
193193
#drawArea() {
194194
this.chartArea = this.addClipPath(this.svg, `${this.chartName}-clip`);
195195
this.chartArea
196-
.append('rect')
196+
?.append('rect')
197197
.attr('width', '100%')
198198
.attr('height', '100%')
199199
.attr('id', `${this.chartName}-area`)
@@ -412,7 +412,7 @@ class CFDRenderer extends UIControlsRenderer {
412412
const triangleBase = 11;
413413
const trianglePath = `M${-triangleBase / 2},0 L${triangleBase / 2},0 L0,-${triangleHeight} Z`;
414414
this.chartArea
415-
.selectAll('observations')
415+
?.selectAll('observations')
416416
.data(observations?.data?.filter((d) => d.chart_type === this.chartType))
417417
.join('path')
418418
.attr('class', 'observation-marker')
@@ -436,14 +436,49 @@ class CFDRenderer extends UIControlsRenderer {
436436
*/
437437
#showTooltipAndMovingLine(event) {
438438
!this.tooltip && this.#createTooltipAndMovingLine(event.lineX, event.lineY);
439-
const tooltipWidth = this.tooltip.node().getBoundingClientRect().width;
440-
const cfdGraphRect = d3.select(this.graphElementSelector).node().getBoundingClientRect();
441-
const tooltipTop = window.scrollY + cfdGraphRect.top - 50;
439+
let { tooltipWidth, tooltipTop } = this.computeTooltipWidthAndTop(event);
440+
442441
this.#clearTooltipAndMovingLine(event.lineX, event.lineY);
443442
this.#positionTooltip(event.tooltipLeft, tooltipTop, tooltipWidth);
444443
this.#populateTooltip(event);
445444
}
446445

446+
computeTooltipWidthAndTop(event) {
447+
const tooltipRect = this.tooltip.node().getBoundingClientRect();
448+
const tooltipWidth = tooltipRect.width;
449+
const tooltipHeight = tooltipRect.height;
450+
const graphRect = d3.select(this.graphElementSelector).node().getBoundingClientRect();
451+
const padding = 10;
452+
let tooltipLeft = event.tooltipLeft;
453+
let tooltipTop = event.lineY - tooltipHeight - padding;
454+
455+
// Get viewport dimensions
456+
const viewportWidth = window.innerWidth;
457+
const viewportHeight = window.innerHeight;
458+
459+
// Adjust tooltipLeft to prevent overflow on the right
460+
if (tooltipLeft + tooltipWidth + padding > viewportWidth) {
461+
tooltipLeft = viewportWidth - tooltipWidth - padding;
462+
}
463+
464+
// Adjust tooltipLeft to prevent overflow on the left
465+
if (tooltipLeft < padding) {
466+
tooltipLeft = padding;
467+
}
468+
469+
// Adjust tooltipTop to prevent overflow on the top
470+
if (tooltipTop < graphRect.top) {
471+
// Position the tooltip below the event point if there's not enough space above
472+
tooltipTop = event.lineY + padding;
473+
474+
// Ensure the tooltip doesn't overflow the bottom of the viewport
475+
if (tooltipTop + tooltipHeight + padding > viewportHeight) {
476+
tooltipTop = viewportHeight - tooltipHeight - padding;
477+
}
478+
}
479+
return { tooltipWidth, tooltipTop };
480+
}
481+
447482
/**
448483
* Hides the tooltip and the moving line on the chart.
449484
*/
@@ -460,9 +495,9 @@ class CFDRenderer extends UIControlsRenderer {
460495
* @private
461496
*/
462497
#createTooltipAndMovingLine(x, y) {
463-
this.tooltip = d3.select('body').append('div').attr('class', styles.tooltip).attr('id', 'c-tooltip').style('opacity', 0);
498+
this.tooltip = d3.select('body').append('div').attr('class', styles.chartTooltip).attr('id', 'c-tooltip').style('opacity', 0);
464499
this.cfdLine = this.chartArea
465-
.append('line')
500+
?.append('line')
466501
.attr('id', `${this.chartName}-line`)
467502
.attr('stroke', 'black')
468503
.attr('y1', 0)
@@ -481,7 +516,7 @@ class CFDRenderer extends UIControlsRenderer {
481516
*/
482517
#positionTooltip(left, top, width) {
483518
this.tooltip?.transition().duration(100).style('opacity', 0.9).style('pointer-events', 'auto');
484-
this.tooltip?.style('left', left - width + 'px').style('top', top + 30 + 'px');
519+
this.tooltip?.style('left', left - width + 'px').style('top', top + 'px');
485520
}
486521

487522
/**
@@ -492,7 +527,7 @@ class CFDRenderer extends UIControlsRenderer {
492527
#populateTooltip(event) {
493528
this.tooltip?.append('p').text(formatDateToLocalString(event.date)).attr('class', 'text-center');
494529
const gridContainer = this.tooltip?.append('div').attr('class', 'grid grid-cols-2');
495-
if (event.metrics.averageCycleTime > 0) {
530+
if (event.metrics?.averageCycleTime > 0) {
496531
gridContainer
497532
.append('span')
498533
.text('Cycle time:')
@@ -501,12 +536,12 @@ class CFDRenderer extends UIControlsRenderer {
501536
.style('color', this.#cycleTimeColor);
502537
gridContainer
503538
.append('span')
504-
.text(`${event.metrics.averageCycleTime} days`)
539+
.text(`${event.metrics?.averageCycleTime} days`)
505540
.attr('class', 'pl-1')
506541
.style('text-align', 'start')
507542
.style('color', this.#cycleTimeColor);
508543
}
509-
if (event.metrics.averageLeadTime > 0) {
544+
if (event.metrics?.averageLeadTime > 0) {
510545
gridContainer
511546
.append('span')
512547
.text('Lead time:')
@@ -566,8 +601,8 @@ class CFDRenderer extends UIControlsRenderer {
566601
return; // Exit the function if metrics are already enabled
567602
}
568603
this.#areMetricsEnabled = true;
569-
this.chartArea.on('mousemove', (event) => this.#handleMouseEvent(event, `${this.chartName}-mousemove`));
570-
this.chartArea.on('click', (event) => this.#handleMouseEvent(event, `${this.chartName}-click`));
604+
this.chartArea?.on('mousemove', (event) => this.#handleMouseEvent(event, `${this.chartName}-mousemove`));
605+
this.chartArea?.on('click', (event) => this.#handleMouseEvent(event, `${this.chartName}-click`));
571606
this.#setupMouseLeaveHandler();
572607
}
573608

@@ -590,8 +625,8 @@ class CFDRenderer extends UIControlsRenderer {
590625
return;
591626
}
592627

593-
const date = this.currentXScale.invert(xPosition);
594-
const cumulativeCountOfWorkItems = this.currentYScale.invert(yPosition);
628+
const date = this.currentXScale?.invert(xPosition);
629+
const cumulativeCountOfWorkItems = this.currentYScale?.invert(yPosition);
595630
const excludeCycleTime = eventName.includes('mousemove') && !eventName.includes(this.chartName);
596631

597632
const metrics = this.computeMetrics(date, Math.floor(cumulativeCountOfWorkItems), excludeCycleTime);
@@ -619,7 +654,7 @@ class CFDRenderer extends UIControlsRenderer {
619654
* @private
620655
*/
621656
#setupMouseLeaveHandler() {
622-
this.chartArea.on('mouseleave', () => this.hideTooltipAndMovingLine());
657+
this.chartArea?.on('mouseleave', () => this.hideTooltipAndMovingLine());
623658
}
624659

625660
/**
@@ -729,8 +764,8 @@ class CFDRenderer extends UIControlsRenderer {
729764
* @private
730765
*/
731766
#drawMetricLines({
732-
averageCycleTime,
733-
averageLeadTime,
767+
averageCycleTime = '',
768+
averageLeadTime = '',
734769
leadTimeDateBefore,
735770
cycleTimeDateBefore,
736771
currentDate,
@@ -769,12 +804,12 @@ class CFDRenderer extends UIControlsRenderer {
769804
* @private
770805
*/
771806
#drawHorizontalMetricLine(dateBefore, dateAfter, noOfItems, cssClass, color, width) {
772-
this.chartArea.selectAll(`.${cssClass}`).remove();
807+
this.chartArea?.selectAll(`.${cssClass}`).remove();
773808
const x1 = this.currentXScale(dateBefore);
774809
const x2 = this.currentXScale(dateAfter);
775810
const y = this.currentYScale(noOfItems);
776811
this.chartArea
777-
.append('line')
812+
?.append('line')
778813
.attr('x1', x1)
779814
.attr('y1', y)
780815
.attr('x2', x2)
@@ -795,12 +830,12 @@ class CFDRenderer extends UIControlsRenderer {
795830
* @private
796831
*/
797832
#drawVerticalMetricLine(date, noOfItemsBefore, noOfItemsAfter, cssClass, color) {
798-
this.chartArea.selectAll(`.${cssClass}`).remove();
833+
this.chartArea?.selectAll(`.${cssClass}`).remove();
799834
const y1 = this.currentYScale(noOfItemsBefore);
800835
const y2 = this.currentYScale(noOfItemsAfter);
801836
const x = this.currentXScale(date);
802837
this.chartArea
803-
.append('line')
838+
?.append('line')
804839
.attr('x1', x)
805840
.attr('y1', y1)
806841
.attr('x2', x)
@@ -815,9 +850,9 @@ class CFDRenderer extends UIControlsRenderer {
815850
* @private
816851
*/
817852
#removeMetricsLines() {
818-
this.chartArea.selectAll('.wip-line').remove();
819-
this.chartArea.selectAll('.cycle-time-line').remove();
820-
this.chartArea.selectAll('.lead-time-line').remove();
853+
this.chartArea?.selectAll('.wip-line').remove();
854+
this.chartArea?.selectAll('.cycle-time-line').remove();
855+
this.chartArea?.selectAll('.lead-time-line').remove();
821856
}
822857

823858
/**

src/graphs/scatterplot/ScatterplotRenderer.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ class ScatterplotRenderer extends UIControlsRenderer {
434434
* @param {Object} observations - Observations data for the renderer.
435435
*/
436436
setupObservationLogging(observations) {
437-
if (observations.data.length > 0) {
437+
if (observations?.data?.length > 0) {
438438
this.displayObservationMarkers(observations);
439439
this.enableMetrics();
440440
}
@@ -457,7 +457,7 @@ class ScatterplotRenderer extends UIControlsRenderer {
457457
* @private
458458
*/
459459
#removeObservationMarkers() {
460-
this.chartArea.selectAll('.ring').remove();
460+
this.chartArea?.selectAll('.ring')?.remove();
461461
}
462462

463463
/**
@@ -510,7 +510,7 @@ class ScatterplotRenderer extends UIControlsRenderer {
510510
* @private
511511
*/
512512
#createTooltip() {
513-
this.tooltip = d3.select('body').append('div').attr('class', styles.tooltip).attr('id', 's-tooltip').style('opacity', 0);
513+
this.tooltip = d3.select('body').append('div').attr('class', styles.chartTooltip).attr('id', 's-tooltip').style('opacity', 0);
514514
}
515515

516516
/**

src/graphs/tooltipStyles.module.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.tooltip {
1+
.chartTooltip {
22
position: absolute;
33
text-align: center;
44
width: max-content;
@@ -11,7 +11,7 @@
1111
z-index: 1;
1212
}
1313

14-
.tooltip p {
14+
.chartTooltip p {
1515
white-space: nowrap;
1616
overflow: hidden;
1717
text-overflow: ellipsis;

0 commit comments

Comments
 (0)