Skip to content

Commit a6cf5cc

Browse files
authored
Merge pull request #37 from pfizer-opensource/pbc-with-lines
Make PBC charts able to have the dots connected through lines
2 parents 7b8f9fa + 5fedc61 commit a6cf5cc

File tree

8 files changed

+88
-21
lines changed

8 files changed

+88
-21
lines changed

src/graphs/UIControlsRenderer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ export default class UIControlsRenderer extends Renderer {
171171
} else {
172172
this.timeInterval = this.determineTheAppropriateAxisLabels();
173173
}
174+
174175
this.eventBus?.emitEvents(`change-time-interval-${chart}`, this.timeInterval);
175176
}
176177

src/graphs/cfd/CFDRenderer.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class CFDRenderer extends UIControlsRenderer {
6666
this.eventBus?.addEventListener('scatterplot-mouseleave', () => this.hideTooltipAndMovingLine());
6767
this.eventBus?.addEventListener('change-time-interval-scatterplot', (timeInterval) => {
6868
this.timeInterval = timeInterval;
69-
this.drawXAxis(this.gx, this.x.copy().domain(this.selectedTimeRange), this.height, true);
69+
this.drawXAxis(this.gx, this.x?.copy().domain(this.selectedTimeRange), this.height, true);
7070
});
7171
}
7272

@@ -128,9 +128,12 @@ class CFDRenderer extends UIControlsRenderer {
128128
* @param {string} cfdBrushElementSelector - Selector of the DOM element to clear the brush.
129129
*/
130130
clearGraph(graphElementSelector, cfdBrushElementSelector) {
131+
this.eventBus.removeAllListeners('change-time-range-scatterplot');
132+
this.eventBus.removeAllListeners('scatterplot-mousemove');
133+
this.eventBus.removeAllListeners('scatterplot-mouseleave');
134+
this.eventBus.removeAllListeners('change-time-interval-scatterplot');
131135
this.#drawBrushSvg(cfdBrushElementSelector);
132136
this.#drawSvg(graphElementSelector);
133-
this.#drawAxes();
134137
}
135138

136139
/**
@@ -368,7 +371,7 @@ class CFDRenderer extends UIControlsRenderer {
368371
* @param {Object} observations - Observations data for the renderer.
369372
*/
370373
setupObservationLogging(observations) {
371-
if (observations) {
374+
if (observations.length > 0) {
372375
this.displayObservationMarkers(observations);
373376
this.enableMetrics();
374377
}
@@ -395,7 +398,7 @@ class CFDRenderer extends UIControlsRenderer {
395398
const trianglePath = `M${-triangleBase / 2},0 L${triangleBase / 2},0 L0,-${triangleHeight} Z`;
396399
this.chartArea
397400
.selectAll('observations')
398-
.data(observations.data.filter((d) => d.chart_type === 'CFD'))
401+
.data(observations?.data?.filter((d) => d.chart_type === 'CFD'))
399402
.join('path')
400403
.attr('class', 'observation-marker')
401404
.attr('d', trianglePath)

src/graphs/control-chart/ControlRenderer.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import ScatterplotRenderer from '../scatterplot/ScatterplotRenderer.js';
2+
import * as d3 from 'd3';
23

34
class ControlRenderer extends ScatterplotRenderer {
45
color = '#0ea5e9';
56
timeScale = 'linear';
7+
connectDots = false;
68

79
constructor(data, avgMovingRange) {
810
super(data);
@@ -21,10 +23,17 @@ class ControlRenderer extends ScatterplotRenderer {
2123
renderGraph(graphElementSelector) {
2224
this.drawSvg(graphElementSelector);
2325
this.drawAxes();
24-
this.drawArea();
2526
this.avgLeadTime = this.getAvgLeadTime();
2627
this.topLimit = Math.ceil(this.avgLeadTime + this.avgMovingRange * 2.66);
28+
2729
this.bottomLimit = Math.ceil(this.avgLeadTime - this.avgMovingRange * 2.66);
30+
const maxY = this.y.domain()[1] > this.topLimit ? this.y.domain()[1] : this.topLimit + 2;
31+
let minY = this.y.domain()[0];
32+
if (this.bottomLimit > 0) {
33+
minY = this.y.domain()[0] < this.bottomLimit ? this.y.domain()[0] : this.bottomLimit - 2;
34+
}
35+
this.y.domain([minY, maxY]);
36+
this.drawArea();
2837
this.drawHorizontalLine(this.y, this.topLimit, 'purple', 'top');
2938
this.drawHorizontalLine(this.y, this.avgLeadTime, 'orange', 'center');
3039
this.bottomLimit > 0 && this.drawHorizontalLine(this.y, this.bottomLimit, 'purple', 'bottom');
@@ -45,14 +54,41 @@ class ControlRenderer extends ScatterplotRenderer {
4554
.style('cursor', 'pointer')
4655
.attr('fill', this.color)
4756
.on('click', (event, d) => this.handleMouseClickEvent(event, d));
57+
this.connectDots && this.generateLines(chartArea, data, x, y);
4858
}
4959

5060
getAvgLeadTime() {
5161
return Math.ceil(this.data.reduce((acc, curr) => acc + curr.leadTime, 0) / this.data.length);
5262
}
5363

64+
generateLines(chartArea, data, x, y) {
65+
// Define the line generator
66+
const line = d3
67+
.line()
68+
.x((d) => x(d.deliveredDate))
69+
.y((d) => y(d.leadTime));
70+
chartArea
71+
.selectAll('dot-line')
72+
.data([data])
73+
.enter()
74+
.append('path')
75+
.attr('class', 'dot-line')
76+
.attr('id', (d) => `line-${d.ticketId}`)
77+
.attr('d', line)
78+
.attr('stroke', 'black')
79+
.attr('stroke-width', 2)
80+
.attr('fill', 'none');
81+
}
82+
5483
updateGraph(domain) {
5584
this.updateChartArea(domain);
85+
if (this.connectDots) {
86+
const line = d3
87+
.line()
88+
.x((d) => this.currentXScale(d.deliveredDate))
89+
.y((d) => this.currentYScale(d.leadTime));
90+
this.chartArea.selectAll('.dot-line').attr('d', line);
91+
}
5692
this.drawHorizontalLine(this.currentYScale, this.topLimit, 'purple', 'top');
5793
this.drawHorizontalLine(this.currentYScale, this.avgLeadTime, 'orange', 'center');
5894
this.bottomLimit > 0 && this.drawHorizontalLine(this.currentYScale, this.bottomLimit, 'purple', 'bottom');

src/graphs/moving-range/MovingRangeGraph.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as d3 from 'd3';
2+
23
class MovingRangeGraph {
4+
dataSet = [];
35
constructor(data) {
46
this.data = data;
57
}
@@ -16,20 +18,26 @@ class MovingRangeGraph {
1618
// Sort the groupedArray by date to ensure correct ordering for difference calculation
1719
groupedArray.sort((a, b) => new Date(a.date) - new Date(b.date));
1820
// Step 3: Calculate absolute differences
19-
const avgLeadTimes = [];
21+
this.dataSet = [];
2022
for (let i = 1; i < groupedArray.length; i++) {
2123
const prev = groupedArray[i - 1];
2224
const current = groupedArray[i];
2325
const difference = Math.abs(current.value - prev.value);
2426

25-
avgLeadTimes.push({
27+
this.dataSet.push({
2628
fromDate: new Date(prev.date),
2729
deliveredDate: new Date(current.date),
2830
leadTime: difference,
2931
});
3032
}
33+
return this.dataSet;
34+
}
3135

32-
return avgLeadTimes;
36+
getAvgMovingRange() {
37+
if (!this.dataSet) {
38+
throw new Error('Data set not computed. Call computeDataSet() first.');
39+
}
40+
return Math.ceil(this.dataSet.reduce((acc, curr) => acc + curr.leadTime, 0) / this.dataSet.length);
3341
}
3442
}
3543

src/graphs/moving-range/MovingRangeRenderer.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ class MovingRangeRenderer extends ScatterplotRenderer {
55
color = '#0ea5e9';
66
timeScale = 'linear';
77

8-
constructor(data) {
8+
constructor(data, avgMovingRange) {
99
super(data);
10+
this.avgMovingRange = avgMovingRange;
1011
this.chartName = 'moving-range';
1112
this.chartType = 'MOVING_RANGE';
1213
this.dotClass = 'moving-range-dot';
@@ -20,8 +21,10 @@ class MovingRangeRenderer extends ScatterplotRenderer {
2021
renderGraph(graphElementSelector) {
2122
this.drawSvg(graphElementSelector);
2223
this.drawAxes();
24+
this.topLimit = this.avgMovingRange;
25+
const maxY = this.y.domain()[1] > this.topLimit ? this.y.domain()[1] : this.topLimit + 2;
26+
this.y.domain([this.y.domain()[0], maxY]);
2327
this.drawArea();
24-
this.topLimit = this.getAvgMovingRange();
2528
this.drawHorizontalLine(this.y, this.topLimit, 'orange', 'mid');
2629
}
2730

@@ -62,11 +65,7 @@ class MovingRangeRenderer extends ScatterplotRenderer {
6265
.x((d) => this.currentXScale(d.deliveredDate))
6366
.y((d) => this.currentYScale(d.leadTime));
6467
this.chartArea.selectAll('.dot-line').attr('d', line);
65-
this.drawHorizontalLine(this.currentYScale, this.getAvgMovingRange(), 'orange', 'mid');
66-
}
67-
68-
getAvgMovingRange() {
69-
return Math.ceil(this.data.reduce((acc, curr) => acc + curr.leadTime, 0) / this.data.length);
68+
this.drawHorizontalLine(this.currentYScale, this.avgMovingRange, 'orange', 'mid');
7069
}
7170
}
7271
export default MovingRangeRenderer;

src/graphs/scatterplot/ScatterplotRenderer.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class ScatterplotRenderer extends UIControlsRenderer {
5151
this.eventBus?.addEventListener('change-time-range-cfd', this.updateBrushSelection.bind(this));
5252
this.eventBus?.addEventListener('change-time-interval-cfd', (timeInterval) => {
5353
this.timeInterval = timeInterval;
54-
this.drawXAxis(this.gx, this.x.copy().domain(this.selectedTimeRange), this.height, true);
54+
this.drawXAxis(this.gx, this.x?.copy().domain(this.selectedTimeRange), this.height, true);
5555
});
5656
}
5757

@@ -110,9 +110,10 @@ class ScatterplotRenderer extends UIControlsRenderer {
110110
* @param {string} brushElementSelector - The selector of the brush element to clear.
111111
*/
112112
clearGraph(graphElementSelector, brushElementSelector) {
113+
this.eventBus?.removeAllListeners('change-time-interval-cfd');
114+
this.eventBus?.removeAllListeners('change-time-range-cfd');
113115
this.drawBrushSvg(brushElementSelector);
114116
this.drawSvg(graphElementSelector);
115-
this.drawAxes();
116117
}
117118

118119
/**
@@ -241,7 +242,13 @@ class ScatterplotRenderer extends UIControlsRenderer {
241242
}
242243

243244
computeXScale() {
244-
const xDomain = d3.extent(this.data, (d) => d.deliveredDate);
245+
const bufferDays = 2;
246+
const xExtent = d3.extent(this.data, (d) => d.deliveredDate);
247+
const minDate = new Date(xExtent[0]);
248+
const maxDate = new Date(xExtent[1]);
249+
minDate.setDate(minDate.getDate() - bufferDays);
250+
maxDate.setDate(maxDate.getDate() + bufferDays);
251+
const xDomain = [minDate, maxDate];
245252
this.x = this.computeTimeScale(xDomain, [0, this.width]);
246253
}
247254

@@ -311,7 +318,7 @@ class ScatterplotRenderer extends UIControlsRenderer {
311318
* @param {Object} observations - Observations data for the renderer.
312319
*/
313320
setupObservationLogging(observations) {
314-
if (observations) {
321+
if (observations.length > 0) {
315322
this.displayObservationMarkers(observations);
316323
this.enableMetrics();
317324
}
@@ -346,7 +353,7 @@ class ScatterplotRenderer extends UIControlsRenderer {
346353
.selectAll('ring')
347354
.data(
348355
this.data.filter((d) =>
349-
this.observations.data.some((o) => o.work_item.toString() === d.ticketId.toString() && o.chart_type === this.chartType)
356+
this.observations?.data?.some((o) => o.work_item.toString() === d.ticketId.toString() && o.chart_type === this.chartType)
350357
)
351358
)
352359
.enter()

src/graphs/scatterplot/SimpleScatterplotRenderer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class SimpleScatterplotRenderer extends ScatterplotRenderer {
8787
this.timeScale = event.target.value;
8888
this.computeYScale();
8989
this.updateGraph(this.selectedTimeRange);
90+
this.renderBrush();
9091
});
9192
}
9293
}

src/utils/EventBus.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,19 @@ class EventBus {
1212

1313
emitEvents(eventName, params) {
1414
if (!this.eventTopics[eventName] || this.eventTopics[eventName].length < 1) return;
15-
this.eventTopics[eventName].forEach((listener) => listener(params ? params : {}));
15+
this.eventTopics[eventName].forEach((listener) => {
16+
try {
17+
listener(params ? params : {});
18+
} catch (error) {
19+
console.error('Error in listener for event', eventName, ':', error);
20+
}
21+
});
22+
}
23+
24+
removeAllListeners(eventName) {
25+
if (this.eventTopics[eventName]) {
26+
this.eventTopics[eventName] = [];
27+
}
1628
}
1729
}
1830
export const eventBus = new EventBus();

0 commit comments

Comments
 (0)