Skip to content

Commit c0d71b1

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

File tree

9 files changed

+264
-25
lines changed

9 files changed

+264
-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: 59 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,65 @@ 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.axisBottom(x)
188+
.ticks(d3.timeDay)
189+
.tickFormat((d) => d.getDay() === 1 ? d3.timeFormat('%b %d')(d) : '');
186190
break;
187-
case 'months':
188-
axis = d3.axisBottom(x).ticks(d3.timeMonth);
191+
}
192+
case 'months': {
193+
// Tick every week, label only for the first week of each month
194+
const weeks = d3.timeWeek.range(x.domain()[0], x.domain()[1]);
195+
axis = d3.axisBottom(x)
196+
.ticks(d3.timeWeek)
197+
.tickFormat((d, i) => {
198+
const monthFormat = d3.timeFormat('%b');
199+
const yearFormat = d3.timeFormat('%Y');
200+
// Label only if this week is the first week of the month
201+
if (d.getDate() <= 7) {
202+
// Show year if first tick or year changes from previous tick
203+
if (i === 0 || (i > 0 && d.getFullYear() !== weeks[i - 1].getFullYear())) {
204+
return `${monthFormat(d)} ${yearFormat(d)}`;
205+
}
206+
return monthFormat(d);
207+
}
208+
return '';
209+
});
189210
break;
211+
}
212+
case 'quarters': {
213+
// Custom tick format: show year at first quarter of each year, otherwise just month
214+
const months = d3.timeMonth.range(x.domain()[0], x.domain()[1]);
215+
axis = d3.axisBottom(x)
216+
.ticks(d3.timeMonth)
217+
.tickFormat((d, i) => {
218+
const month = d.getMonth();
219+
const monthFormat = d3.timeFormat('%b');
220+
const yearFormat = d3.timeFormat('%Y');
221+
// Only label the first month of each quarter
222+
if (month % 3 === 0) {
223+
// Show year if first tick or year changes from previous quarter tick
224+
const prevQuarterIndex = (() => {
225+
for (let j = i - 1; j >= 0; j--) {
226+
if (months[j].getMonth() % 3 === 0) return j;
227+
}
228+
return -1;
229+
})();
230+
if (i === 0 || (prevQuarterIndex >= 0 && d.getFullYear() !== months[prevQuarterIndex].getFullYear())) {
231+
return `${monthFormat(d)} ${yearFormat(d)}`;
232+
}
233+
return monthFormat(d);
234+
}
235+
return '';
236+
});
237+
break;
238+
}
190239
default:
191240
return d3.axisBottom(x);
192241
}
@@ -224,10 +273,13 @@ export class UIControlsRenderer extends Renderer {
224273
if (this.reportingRangeDays <= 31) {
225274
return 'days';
226275
}
227-
if (this.reportingRangeDays > 31 && this.reportingRangeDays <= 124) {
276+
if (this.reportingRangeDays > 31 && this.reportingRangeDays <= 170) {
228277
return 'weeks';
229278
}
230-
return 'months';
279+
if (this.reportingRangeDays > 170 && this.reportingRangeDays <= 770) {
280+
return 'months';
281+
}
282+
return 'quarters';
231283
}
232284

233285
/**

src/graphs/cfd/CFDRenderer.js

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,25 @@ 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);
98+
9699
}
97100

98101
/**
99102
* Renders a brush component with the time range selection.
100103
*/
101104
renderBrush() {
102105
const svgBrush = this.#drawBrushSvg(this.brushSelector);
103-
const defaultSelectionRange = this.defaultTimeRange.map((d) => this.x(d));
106+
console.log('selectedTimeRange', this.selectedTimeRange);
107+
console.log('defaultTimeRange', this.defaultTimeRange);
108+
const defaultSelectionRange = this.selectedTimeRange.map((d) => this.x(d));
104109
this.brush = d3
105110
.brushX()
106111
.extent([
@@ -109,6 +114,7 @@ export class CFDRenderer extends UIControlsRenderer {
109114
])
110115
.on('brush', ({ selection }) => {
111116
this.selectedTimeRange = selection.map(this.x?.invert, this.x);
117+
console.log('inside-selectedTimeRange---------', this.selectedTimeRange);
112118
this.updateGraph(this.selectedTimeRange);
113119
if (this.isManualBrushUpdate && this.eventBus) {
114120
this.eventBus?.emitEvents(`change-time-range-${this.chartName}`, this.selectedTimeRange);
@@ -133,17 +139,51 @@ export class CFDRenderer extends UIControlsRenderer {
133139
}
134140

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

149189
/**
@@ -372,7 +412,7 @@ export class CFDRenderer extends UIControlsRenderer {
372412
g.selectAll('text').attr('y', 30).style('fill', 'black');
373413
g.attr('clip-path', `url(#${clipId})`);
374414
} else {
375-
axis = this.createXAxis(x, 'months');
415+
axis = this.createXAxis(x, 'quarters');
376416
g.call(axis).attr('transform', `translate(0, ${height})`);
377417
}
378418
}

src/graphs/control-chart/ControlRenderer.js

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

src/graphs/histogram/HistogramRenderer.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,26 @@ 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 =>
72+
this.eventBus.removeAllListeners(event)
73+
);
74+
}
75+
d3.select(graphElementSelector).selectAll('*').remove();
6076
this.#drawSvg(graphElementSelector);
6177
this.#drawAxes();
6278
}

src/graphs/moving-range/MovingRangeRenderer.js

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

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