Skip to content

Commit 203ae7d

Browse files
committed
Data update for renderers
1 parent 91a0ad4 commit 203ae7d

File tree

9 files changed

+252
-25
lines changed

9 files changed

+252
-25
lines changed

src/graphs/Renderer.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,34 @@ export class Renderer {
147147
throw new Error('Method not implemented. It must be implemented in subclasses!');
148148
}
149149

150+
/**
151+
* Updates the renderer's data and re-renders the graph, preserving persistent props.
152+
* Subclasses should override renderGraph for custom rendering.
153+
* @param {Array} newData - The new data to render.
154+
*/
155+
updateData(newData) {
156+
this.data = newData;
157+
if (this.graphElementSelector) {
158+
this.clearGraph(this.graphElementSelector);
159+
this.renderGraph(this.graphElementSelector);
160+
}
161+
}
162+
163+
/**
164+
* Clears the graph from the specified DOM element, error-proof for event bus.
165+
* Subclasses can override for custom logic.
166+
* @param {string} graphElementSelector - Selector of the DOM element to clear.
167+
*/
168+
clearGraph(graphElementSelector) {
169+
if (this.eventBus && typeof this.eventBus.removeAllListeners === 'function') {
170+
this.eventBus.removeAllListeners('change-time-range-scatterplot');
171+
this.eventBus.removeAllListeners('scatterplot-mousemove');
172+
this.eventBus.removeAllListeners('scatterplot-mouseleave');
173+
this.eventBus.removeAllListeners('change-time-interval-scatterplot');
174+
}
175+
d3.select(graphElementSelector).selectAll('*').remove();
176+
}
177+
150178
/**
151179
* Shows the tooltip with provided event data.
152180
* @param {Object} event - The event data for the tooltip.

src/graphs/UIControlsRenderer.js

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export class UIControlsRenderer extends Renderer {
3636
setupBrush(brushElementSelector) {
3737
this.brushSelector = brushElementSelector;
3838
this.defaultTimeRange = this.computeReportingRange(this.reportingRangeDays);
39+
console.log('setupBrush-selectedTimeRange', this.selectedTimeRange);
3940
this.selectedTimeRange ||= Array.from(this.defaultTimeRange);
4041
this.renderBrush();
4142
}
@@ -176,17 +177,68 @@ export class UIControlsRenderer extends Renderer {
176177
case 'days':
177178
axis = d3
178179
.axisBottom(x)
179-
.ticks(d3.timeDay.every(1)) // label every 2 days
180+
.ticks(d3.timeDay.every(1))
180181
.tickFormat((d, i) => {
181182
return i % 2 === 0 ? d3.timeFormat('%b %d')(d) : '';
182183
});
183184
break;
184-
case 'weeks':
185-
axis = d3.axisBottom(x).ticks(d3.timeWeek);
185+
case 'weeks': {
186+
// Show a tick for every day, but label only the first day of each week (Monday)
187+
axis = d3
188+
.axisBottom(x)
189+
.ticks(d3.timeDay)
190+
.tickFormat((d) => (d.getDay() === 1 ? d3.timeFormat('%b %d')(d) : ''));
186191
break;
187-
case 'months':
188-
axis = d3.axisBottom(x).ticks(d3.timeMonth);
192+
}
193+
case 'months': {
194+
// Tick every week, label only for the first week of each month
195+
const weeks = d3.timeWeek.range(x.domain()[0], x.domain()[1]);
196+
axis = d3
197+
.axisBottom(x)
198+
.ticks(d3.timeWeek)
199+
.tickFormat((d, i) => {
200+
const monthFormat = d3.timeFormat('%b');
201+
const yearFormat = d3.timeFormat('%Y');
202+
// Label only if this week is the first week of the month
203+
if (d.getDate() <= 7) {
204+
// Show year if first tick or year changes from previous tick
205+
if (i === 0 || (i > 0 && d.getFullYear() !== weeks[i - 1].getFullYear())) {
206+
return `${monthFormat(d)} ${yearFormat(d)}`;
207+
}
208+
return monthFormat(d);
209+
}
210+
return '';
211+
});
189212
break;
213+
}
214+
case 'quarters': {
215+
// Custom tick format: show year at first quarter of each year, otherwise just month
216+
const months = d3.timeMonth.range(x.domain()[0], x.domain()[1]);
217+
axis = d3
218+
.axisBottom(x)
219+
.ticks(d3.timeMonth)
220+
.tickFormat((d, i) => {
221+
const month = d.getMonth();
222+
const monthFormat = d3.timeFormat('%b');
223+
const yearFormat = d3.timeFormat('%Y');
224+
// Only label the first month of each quarter
225+
if (month % 3 === 0) {
226+
// Show year if first tick or year changes from previous quarter tick
227+
const prevQuarterIndex = (() => {
228+
for (let j = i - 1; j >= 0; j--) {
229+
if (months[j].getMonth() % 3 === 0) return j;
230+
}
231+
return -1;
232+
})();
233+
if (i === 0 || (prevQuarterIndex >= 0 && d.getFullYear() !== months[prevQuarterIndex].getFullYear())) {
234+
return `${monthFormat(d)} ${yearFormat(d)}`;
235+
}
236+
return monthFormat(d);
237+
}
238+
return '';
239+
});
240+
break;
241+
}
190242
default:
191243
return d3.axisBottom(x);
192244
}
@@ -224,10 +276,13 @@ export class UIControlsRenderer extends Renderer {
224276
if (this.reportingRangeDays <= 31) {
225277
return 'days';
226278
}
227-
if (this.reportingRangeDays > 31 && this.reportingRangeDays <= 124) {
279+
if (this.reportingRangeDays > 31 && this.reportingRangeDays <= 170) {
228280
return 'weeks';
229281
}
230-
return 'months';
282+
if (this.reportingRangeDays > 170 && this.reportingRangeDays <= 770) {
283+
return 'months';
284+
}
285+
return 'quarters';
231286
}
232287

233288
/**

src/graphs/cfd/CFDRenderer.js

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,24 @@ export class CFDRenderer extends UIControlsRenderer {
8787
* @param {string} graphElementSelector - Selector of the DOM element to render the graph.
8888
*/
8989
renderGraph(graphElementSelector) {
90+
console.log('setupBrush-b-selectedTimeRange', this.selectedTimeRange);
9091
this.graphElementSelector = graphElementSelector;
9192
this.#drawSvg(graphElementSelector);
9293
this.svg.append('g').attr('transform', `translate(${this.margin.left}, ${this.margin.top})`);
9394
this.#stackedData = this.#computeStackData();
9495
this.#drawAxes();
9596
this.#drawArea();
97+
console.log('setupBrush-a-selectedTimeRange', this.selectedTimeRange);
9698
}
9799

98100
/**
99101
* Renders a brush component with the time range selection.
100102
*/
101103
renderBrush() {
102104
const svgBrush = this.#drawBrushSvg(this.brushSelector);
103-
const defaultSelectionRange = this.defaultTimeRange.map((d) => this.x(d));
105+
console.log('selectedTimeRange', this.selectedTimeRange);
106+
console.log('defaultTimeRange', this.defaultTimeRange);
107+
const defaultSelectionRange = this.selectedTimeRange.map((d) => this.x(d));
104108
this.brush = d3
105109
.brushX()
106110
.extent([
@@ -109,6 +113,7 @@ export class CFDRenderer extends UIControlsRenderer {
109113
])
110114
.on('brush', ({ selection }) => {
111115
this.selectedTimeRange = selection.map(this.x?.invert, this.x);
116+
console.log('inside-selectedTimeRange---------', this.selectedTimeRange);
112117
this.updateGraph(this.selectedTimeRange);
113118
if (this.isManualBrushUpdate && this.eventBus) {
114119
this.eventBus?.emitEvents(`change-time-range-${this.chartName}`, this.selectedTimeRange);
@@ -133,17 +138,51 @@ export class CFDRenderer extends UIControlsRenderer {
133138
}
134139

135140
/**
136-
* Clears the CFD graph and brush from the specified DOM elements.
137-
* @param {string} graphElementSelector - Selector of the DOM element to clear the graph.
138-
* @param {string} cfdBrushElementSelector - Selector of the DOM element to clear the brush.
141+
* Updates the renderer's data and re-renders the graph, preserving persistent props.
142+
* @param {Array} newData - The new data to render.
143+
*/
144+
updateData(newData) {
145+
this.data = newData;
146+
// After updating this.data
147+
const dataDates = this.data.map((d) => new Date(d.date));
148+
const minDate = d3.min(dataDates);
149+
const maxDate = d3.max(dataDates);
150+
151+
if (this.selectedTimeRange) {
152+
let [selectedStart, selectedEnd] = this.selectedTimeRange;
153+
selectedStart = selectedStart < minDate ? minDate : selectedStart;
154+
selectedEnd = selectedEnd > maxDate ? maxDate : selectedEnd;
155+
this.selectedTimeRange = [selectedStart, selectedEnd];
156+
} else {
157+
this.selectedTimeRange = [minDate, maxDate];
158+
}
159+
// Preserve persistent props like selectedTimeRange, reportingRangeDays, etc.
160+
// Re-render the graph with the new data
161+
if (this.graphElementSelector) {
162+
this.clearGraph(this.graphElementSelector, this.brushSelector);
163+
this.renderGraph(this.graphElementSelector);
164+
this.renderBrush(this.brushSelector);
165+
}
166+
}
167+
168+
/**
169+
* Clears the graph and brush SVGs, removes listeners, and handles missing event bus gracefully.
139170
*/
140171
clearGraph(graphElementSelector, cfdBrushElementSelector) {
141-
this.eventBus.removeAllListeners('change-time-range-scatterplot');
142-
this.eventBus.removeAllListeners('scatterplot-mousemove');
143-
this.eventBus.removeAllListeners('scatterplot-mouseleave');
144-
this.eventBus.removeAllListeners('change-time-interval-scatterplot');
172+
console.log('clearGraph-selectedTimeRange', this.selectedTimeRange);
173+
if (this.eventBus && typeof this.eventBus.removeAllListeners === 'function') {
174+
this.eventBus.removeAllListeners('change-time-range-scatterplot');
175+
this.eventBus.removeAllListeners('scatterplot-mousemove');
176+
this.eventBus.removeAllListeners('scatterplot-mouseleave');
177+
this.eventBus.removeAllListeners('change-time-interval-scatterplot');
178+
}
179+
// Remove all children from the SVG elements
180+
d3.select(graphElementSelector).selectAll('*').remove();
181+
d3.select(cfdBrushElementSelector).selectAll('*').remove();
182+
145183
this.#drawBrushSvg(cfdBrushElementSelector);
146184
this.#drawSvg(graphElementSelector);
185+
console.log('clearGraph-after-selectedTimeRange', this.selectedTimeRange);
147186
}
148187

149188
/**
@@ -372,7 +411,7 @@ export class CFDRenderer extends UIControlsRenderer {
372411
g.selectAll('text').attr('y', 30).style('fill', 'black');
373412
g.attr('clip-path', `url(#${clipId})`);
374413
} else {
375-
axis = this.createXAxis(x, 'months');
414+
axis = this.createXAxis(x, 'quarters');
376415
g.call(axis).attr('transform', `translate(0, ${height})`);
377416
}
378417
}

src/graphs/control-chart/ControlRenderer.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,4 +249,25 @@ export class ControlRenderer extends ScatterplotRenderer {
249249
this.visibleLimits = {};
250250
this.activeProcessSignal = null;
251251
}
252+
253+
getEventNamesToRemove() {
254+
return ['change-time-range-scatterplot', 'scatterplot-mousemove', 'scatterplot-mouseleave', 'change-time-interval-scatterplot'];
255+
}
256+
257+
clearGraph(graphElementSelector) {
258+
if (this.eventBus && typeof this.eventBus.removeAllListeners === 'function') {
259+
this.getEventNamesToRemove().forEach((event) => this.eventBus.removeAllListeners(event));
260+
}
261+
d3.select(graphElementSelector).selectAll('*').remove();
262+
}
263+
264+
/**
265+
* Updates the renderer's data and re-renders the graph, preserving persistent props.
266+
* @param {Array} newData - The new data to render.
267+
*/
268+
updateData(newData) {
269+
this.data = newData;
270+
this.clearGraph(this.graphElementSelector);
271+
this.renderGraph(this.graphElementSelector);
272+
}
252273
}

src/graphs/histogram/HistogramRenderer.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,24 @@ export class HistogramRenderer extends Renderer {
5353
}
5454

5555
/**
56-
* Clears the histogram graph from the specified DOM element.
56+
* Updates the renderer's data and re-renders the graph, preserving persistent props.
57+
* @param {Array} newData - The new data to render.
58+
*/
59+
updateData(newData) {
60+
this.data = newData;
61+
this.clearGraph(this.graphElementSelector);
62+
this.renderGraph(this.graphElementSelector);
63+
}
64+
65+
/**
66+
* Clears the histogram graph from the specified DOM element, error-proof for event bus.
5767
* @param {string} graphElementSelector - Selector of the DOM element to clear.
5868
*/
5969
clearGraph(graphElementSelector) {
70+
if (this.eventBus && typeof this.eventBus.removeAllListeners === 'function') {
71+
this.getEventNamesToRemove().forEach((event) => this.eventBus.removeAllListeners(event));
72+
}
73+
d3.select(graphElementSelector).selectAll('*').remove();
6074
this.#drawSvg(graphElementSelector);
6175
this.#drawAxes();
6276
}

src/graphs/moving-range/MovingRangeRenderer.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,30 @@ export class MovingRangeRenderer extends ScatterplotRenderer {
231231
this.drawSignals();
232232
}
233233

234+
getEventNamesToRemove() {
235+
return ['change-time-range-scatterplot', 'scatterplot-mousemove', 'scatterplot-mouseleave', 'change-time-interval-scatterplot'];
236+
}
237+
238+
/**
239+
* Updates the renderer's data and re-renders the graph, preserving persistent props.
240+
* @param {Array} newData - The new data to render.
241+
*/
242+
updateData(newData) {
243+
this.data = newData;
244+
this.clearGraph(this.graphElementSelector);
245+
this.renderGraph(this.graphElementSelector);
246+
}
247+
248+
/**
249+
* Clears the graph, error-proof for event bus.
250+
*/
251+
clearGraph(graphElementSelector) {
252+
if (this.eventBus && typeof this.eventBus.removeAllListeners === 'function') {
253+
this.getEventNamesToRemove().forEach((event) => this.eventBus.removeAllListeners(event));
254+
}
255+
d3.select(graphElementSelector).selectAll('*').remove();
256+
}
257+
234258
cleanup() {
235259
this.limitData = {};
236260
this.visibleLimits = {};

src/graphs/pbc/PBCRenderer.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,33 @@ export class PBCRenderer extends UIControlsRenderer {
135135
}
136136

137137
/**
138-
* Clear both charts
138+
* Updates the renderer's data and re-renders the graph, preserving persistent props.
139+
* @param {Array} newControlData - The new control chart data.
140+
* @param {Array} newMovingRangeData - The new moving range chart data.
139141
*/
140-
clearGraph(containerSelector, brushSelector) {
141-
this.controlRenderer?.clearGraph(`${containerSelector} .control-chart`, brushSelector);
142-
this.movingRangeRenderer?.clearGraph(`${containerSelector} .moving-range-chart`, null);
142+
updateData(newControlData, newMovingRangeData) {
143+
this.controlData = newControlData;
144+
this.movingRangeData = newMovingRangeData;
145+
if (this.controlRenderer && this.movingRangeRenderer) {
146+
this.controlRenderer.updateData(newControlData);
147+
this.movingRangeRenderer.updateData(newMovingRangeData);
148+
}
149+
}
150+
151+
getEventNamesToRemove() {
152+
return [];
153+
}
143154

155+
/**
156+
* Clears both charts, error-proof for event bus.
157+
*/
158+
clearGraph(containerSelector) {
159+
if (this.controlRenderer) {
160+
this.controlRenderer.clearGraph(`${containerSelector} .control-chart`);
161+
}
162+
if (this.movingRangeRenderer) {
163+
this.movingRangeRenderer.clearGraph(`${containerSelector} .moving-range-chart`);
164+
}
144165
const container = document.querySelector(containerSelector);
145166
if (container) container.innerHTML = '';
146167
}

src/graphs/scatterplot/ScatterplotRenderer.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,13 @@ export class ScatterplotRenderer extends UIControlsRenderer {
120120
* @param {string} brushElementSelector - The selector of the brush element to clear.
121121
*/
122122
clearGraph(graphElementSelector, brushElementSelector) {
123-
this.eventBus?.removeAllListeners('change-time-interval-cfd');
124-
this.eventBus?.removeAllListeners('change-time-range-cfd');
125-
this.drawBrushSvg(brushElementSelector);
126-
this.drawSvg(graphElementSelector);
123+
if (this.eventBus && typeof this.eventBus.removeAllListeners === 'function') {
124+
this.getEventNamesToRemove().forEach((event) => this.eventBus.removeAllListeners(event));
125+
}
126+
d3.select(graphElementSelector).selectAll('*').remove();
127+
if (brushElementSelector) {
128+
d3.select(brushElementSelector).selectAll('*').remove();
129+
}
127130
}
128131

129132
/**

0 commit comments

Comments
 (0)