Skip to content

Commit

Permalink
Make PBC charts able to have the dots connected through lines
Browse files Browse the repository at this point in the history
- add connected dots option in ControlRenderer
- remove event listeners after the graph is cleared
  • Loading branch information
ClaudiaGivan committed Aug 19, 2024
1 parent eaea4e2 commit d35ac4e
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 23 deletions.
6 changes: 3 additions & 3 deletions examples/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,17 @@ function renderScatterplotAndHistogramGraphs(data, reportingRangeDays, controlsE
//Moving range chart
const movingRangeGraph = new MovingRangeGraph(filteredLeadTimeDataSet);
const movingRangeGraphDataSet = movingRangeGraph.computeDataSet();
const movingRangeRenderer = new MovingRangeRenderer(movingRangeGraphDataSet);
const avgMovingRange = movingRangeGraph.getAvgMovingRange()
const movingRangeRenderer = new MovingRangeRenderer(movingRangeGraphDataSet, avgMovingRange);
movingRangeRenderer.renderGraph(movingRangeGraphElementSelector);
movingRangeRenderer.reportingRangeDays = reportingRangeDays;
movingRangeRenderer.setupEventBus(eventBus)
document.querySelector(movingRangeBrushElementSelector) && movingRangeRenderer.setupBrush(movingRangeBrushElementSelector);

movingRangeRenderer.setupXAxisControl()


//Control chart
const controlRenderer = new ControlRenderer(filteredLeadTimeDataSet, movingRangeRenderer.getAvgMovingRange());
const controlRenderer = new ControlRenderer(filteredLeadTimeDataSet, avgMovingRange);
controlRenderer.renderGraph(controlGraphElementSelector);
controlRenderer.reportingRangeDays = reportingRangeDays;
controlRenderer.setupEventBus(eventBus)
Expand Down
1 change: 1 addition & 0 deletions src/graphs/UIControlsRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export default class UIControlsRenderer extends Renderer {
} else {
this.timeInterval = this.determineTheAppropriateAxisLabels();
}

this.eventBus?.emitEvents(`change-time-interval-${chart}`, this.timeInterval);
}

Expand Down
13 changes: 9 additions & 4 deletions src/graphs/cfd/CFDRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class CFDRenderer extends UIControlsRenderer {
this.eventBus?.addEventListener('scatterplot-mouseleave', () => this.hideTooltipAndMovingLine());
this.eventBus?.addEventListener('change-time-interval-scatterplot', (timeInterval) => {
this.timeInterval = timeInterval;
this.drawXAxis(this.gx, this.x.copy().domain(this.selectedTimeRange), this.height, true);
this.drawXAxis(this.gx, this.x?.copy().domain(this.selectedTimeRange), this.height, true);
});
}

Expand Down Expand Up @@ -128,9 +128,12 @@ class CFDRenderer extends UIControlsRenderer {
* @param {string} cfdBrushElementSelector - Selector of the DOM element to clear the brush.
*/
clearGraph(graphElementSelector, cfdBrushElementSelector) {
this.eventBus.removeAllListeners('change-time-range-scatterplot');
this.eventBus.removeAllListeners('scatterplot-mousemove');
this.eventBus.removeAllListeners('scatterplot-mouseleave');
this.eventBus.removeAllListeners('change-time-interval-scatterplot');
this.#drawBrushSvg(cfdBrushElementSelector);
this.#drawSvg(graphElementSelector);
this.#drawAxes();
}

/**
Expand Down Expand Up @@ -368,7 +371,7 @@ class CFDRenderer extends UIControlsRenderer {
* @param {Object} observations - Observations data for the renderer.
*/
setupObservationLogging(observations) {
if (observations) {
if (observations.length>0) {
this.displayObservationMarkers(observations);
this.enableMetrics();
}
Expand All @@ -390,12 +393,13 @@ class CFDRenderer extends UIControlsRenderer {
* @private
*/
#createObservationMarkers(observations) {
console.log(observations)
const triangleHeight = 16;
const triangleBase = 11;
const trianglePath = `M${-triangleBase / 2},0 L${triangleBase / 2},0 L0,-${triangleHeight} Z`;
this.chartArea
.selectAll('observations')
.data(observations.data.filter((d) => d.chart_type === 'CFD'))
.data(observations?.data?.filter((d) => d.chart_type === 'CFD'))
.join('path')
.attr('class', 'observation-marker')
.attr('d', trianglePath)
Expand Down Expand Up @@ -719,6 +723,7 @@ class CFDRenderer extends UIControlsRenderer {
}

if (averageCycleTime) {
console.log("avg cylcle", averageCycleTime)
this.#drawHorizontalMetricLine(
cycleTimeDateBefore,
currentDate,
Expand Down
38 changes: 37 additions & 1 deletion src/graphs/control-chart/ControlRenderer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import ScatterplotRenderer from '../scatterplot/ScatterplotRenderer.js';
import * as d3 from 'd3';

class ControlRenderer extends ScatterplotRenderer {
color = '#0ea5e9';
timeScale = 'linear';
connectDots = false;

constructor(data, avgMovingRange) {
super(data);
Expand All @@ -21,10 +23,17 @@ class ControlRenderer extends ScatterplotRenderer {
renderGraph(graphElementSelector) {
this.drawSvg(graphElementSelector);
this.drawAxes();
this.drawArea();
this.avgLeadTime = this.getAvgLeadTime();
this.topLimit = Math.ceil(this.avgLeadTime + this.avgMovingRange * 2.66);

this.bottomLimit = Math.ceil(this.avgLeadTime - this.avgMovingRange * 2.66);
const maxY = this.y.domain()[1] > this.topLimit ? this.y.domain()[1] : this.topLimit + 2;
let minY = this.y.domain()[0];
if (this.bottomLimit > 0) {
minY = this.y.domain()[0] < this.bottomLimit ? this.y.domain()[0] : this.bottomLimit - 2;
}
this.y.domain([minY, maxY]);
this.drawArea();
this.drawHorizontalLine(this.y, this.topLimit, 'purple', 'top');
this.drawHorizontalLine(this.y, this.avgLeadTime, 'orange', 'center');
this.bottomLimit > 0 && this.drawHorizontalLine(this.y, this.bottomLimit, 'purple', 'bottom');
Expand All @@ -45,14 +54,41 @@ class ControlRenderer extends ScatterplotRenderer {
.style('cursor', 'pointer')
.attr('fill', this.color)
.on('click', (event, d) => this.handleMouseClickEvent(event, d));
this.connectDots && this.generateLines(chartArea, data, x, y);
}

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

generateLines(chartArea, data, x, y) {
// Define the line generator
const line = d3
.line()
.x((d) => x(d.deliveredDate))
.y((d) => y(d.leadTime));
chartArea
.selectAll('dot-line')
.data([data])
.enter()
.append('path')
.attr('class', 'dot-line')
.attr('id', (d) => `line-${d.ticketId}`)
.attr('d', line)
.attr('stroke', 'black')
.attr('stroke-width', 2)
.attr('fill', 'none');
}

updateGraph(domain) {
this.updateChartArea(domain);
if (this.connectDots) {
const line = d3
.line()
.x((d) => this.currentXScale(d.deliveredDate))
.y((d) => this.currentYScale(d.leadTime));
this.chartArea.selectAll('.dot-line').attr('d', line);
}
this.drawHorizontalLine(this.currentYScale, this.topLimit, 'purple', 'top');
this.drawHorizontalLine(this.currentYScale, this.avgLeadTime, 'orange', 'center');
this.bottomLimit > 0 && this.drawHorizontalLine(this.currentYScale, this.bottomLimit, 'purple', 'bottom');
Expand Down
14 changes: 11 additions & 3 deletions src/graphs/moving-range/MovingRangeGraph.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as d3 from 'd3';

class MovingRangeGraph {
dataSet = [];
constructor(data) {
this.data = data;
}
Expand All @@ -16,20 +18,26 @@ class MovingRangeGraph {
// Sort the groupedArray by date to ensure correct ordering for difference calculation
groupedArray.sort((a, b) => new Date(a.date) - new Date(b.date));
// Step 3: Calculate absolute differences
const avgLeadTimes = [];
this.dataSet = [];
for (let i = 1; i < groupedArray.length; i++) {
const prev = groupedArray[i - 1];
const current = groupedArray[i];
const difference = Math.abs(current.value - prev.value);

avgLeadTimes.push({
this.dataSet .push({
fromDate: new Date(prev.date),
deliveredDate: new Date(current.date),
leadTime: difference,
});
}
return this.dataSet;
}

return avgLeadTimes;
getAvgMovingRange() {
if (!this.dataSet) {
throw new Error("Data set not computed. Call computeDataSet() first.");
}
return Math.ceil(this.dataSet.reduce((acc, curr) => acc + curr.leadTime, 0) / this.dataSet.length);
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/graphs/moving-range/MovingRangeRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ class MovingRangeRenderer extends ScatterplotRenderer {
color = '#0ea5e9';
timeScale = 'linear';

constructor(data) {
constructor(data, avgMovingRange) {
super(data);
this.avgMovingRange = avgMovingRange;
this.chartName = 'moving-range';
this.chartType = 'MOVING_RANGE';
this.dotClass = 'moving-range-dot';
Expand All @@ -20,8 +21,10 @@ class MovingRangeRenderer extends ScatterplotRenderer {
renderGraph(graphElementSelector) {
this.drawSvg(graphElementSelector);
this.drawAxes();
this.topLimit = this.avgMovingRange;
const maxY = this.y.domain()[1] > this.topLimit ? this.y.domain()[1] : this.topLimit + 2;
this.y.domain([this.y.domain()[0], maxY]);
this.drawArea();
this.topLimit = this.getAvgMovingRange();
this.drawHorizontalLine(this.y, this.topLimit, 'orange', 'mid');
}

Expand Down Expand Up @@ -62,11 +65,8 @@ class MovingRangeRenderer extends ScatterplotRenderer {
.x((d) => this.currentXScale(d.deliveredDate))
.y((d) => this.currentYScale(d.leadTime));
this.chartArea.selectAll('.dot-line').attr('d', line);
this.drawHorizontalLine(this.currentYScale, this.getAvgMovingRange(), 'orange', 'mid');
this.drawHorizontalLine(this.currentYScale, this.avgMovingRange, 'orange', 'mid');
}

getAvgMovingRange() {
return Math.ceil(this.data.reduce((acc, curr) => acc + curr.leadTime, 0) / this.data.length);
}
}
export default MovingRangeRenderer;
17 changes: 12 additions & 5 deletions src/graphs/scatterplot/ScatterplotRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class ScatterplotRenderer extends UIControlsRenderer {
this.eventBus?.addEventListener('change-time-range-cfd', this.updateBrushSelection.bind(this));
this.eventBus?.addEventListener('change-time-interval-cfd', (timeInterval) => {
this.timeInterval = timeInterval;
this.drawXAxis(this.gx, this.x.copy().domain(this.selectedTimeRange), this.height, true);
this.drawXAxis(this.gx, this.x?.copy().domain(this.selectedTimeRange), this.height, true);
});
}

Expand Down Expand Up @@ -110,9 +110,10 @@ class ScatterplotRenderer extends UIControlsRenderer {
* @param {string} brushElementSelector - The selector of the brush element to clear.
*/
clearGraph(graphElementSelector, brushElementSelector) {
this.eventBus.removeAllListeners('change-time-interval-cfd');
this.eventBus.removeAllListeners('change-time-range-cfd');
this.drawBrushSvg(brushElementSelector);
this.drawSvg(graphElementSelector);
this.drawAxes();
}

/**
Expand Down Expand Up @@ -241,7 +242,13 @@ class ScatterplotRenderer extends UIControlsRenderer {
}

computeXScale() {
const xDomain = d3.extent(this.data, (d) => d.deliveredDate);
const bufferDays = 2;
const xExtent = d3.extent(this.data, (d) => d.deliveredDate);
const minDate = new Date(xExtent[0]);
const maxDate = new Date(xExtent[1]);
minDate.setDate(minDate.getDate() - bufferDays);
maxDate.setDate(maxDate.getDate() + bufferDays);
const xDomain = [minDate, maxDate];
this.x = this.computeTimeScale(xDomain, [0, this.width]);
}

Expand Down Expand Up @@ -311,7 +318,7 @@ class ScatterplotRenderer extends UIControlsRenderer {
* @param {Object} observations - Observations data for the renderer.
*/
setupObservationLogging(observations) {
if (observations) {
if (observations.length>0) {
this.displayObservationMarkers(observations);
this.enableMetrics();
}
Expand Down Expand Up @@ -346,7 +353,7 @@ class ScatterplotRenderer extends UIControlsRenderer {
.selectAll('ring')
.data(
this.data.filter((d) =>
this.observations.data.some((o) => o.work_item.toString() === d.ticketId.toString() && o.chart_type === this.chartType)
this.observations?.data?.some((o) => o.work_item.toString() === d.ticketId.toString() && o.chart_type === this.chartType)
)
)
.enter()
Expand Down
1 change: 1 addition & 0 deletions src/graphs/scatterplot/SimpleScatterplotRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class SimpleScatterplotRenderer extends ScatterplotRenderer {
this.timeScale = event.target.value;
this.computeYScale();
this.updateGraph(this.selectedTimeRange);
this.renderBrush()
});
}
}
Expand Down
15 changes: 14 additions & 1 deletion src/utils/EventBus.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,20 @@ class EventBus {

emitEvents(eventName, params) {
if (!this.eventTopics[eventName] || this.eventTopics[eventName].length < 1) return;
this.eventTopics[eventName].forEach((listener) => listener(params ? params : {}));
this.eventTopics[eventName].forEach((listener) => {
try {
listener(params ? params : {});
} catch (error) {
console.error("Error in listener for event", eventName, ":", error);
}
});
}

removeAllListeners(eventName) {
if (this.eventTopics[eventName]) {
this.eventTopics[eventName] = [];
}
}

}
export const eventBus = new EventBus();

0 comments on commit d35ac4e

Please sign in to comment.