From aa61c7809f30e4dfccfbaad55847f8ea662b19de Mon Sep 17 00:00:00 2001 From: ClaudiaGivan Date: Thu, 23 Nov 2023 19:39:03 +0200 Subject: [PATCH] [TRON-17796] Refactor library - refactor namings and methods - v1 --- README.md | 12 +- examples/example.js | 4 +- src/graphs/UIControlsRenderer.js | 91 +++++----- src/graphs/cfd/CFDRenderer.js | 58 +++--- src/graphs/histogram/HistogramRenderer.js | 2 +- src/graphs/scatterplot/ScatterplotRenderer.js | 167 ++++++++++-------- 6 files changed, 188 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index 22655e4..25faf99 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,12 @@ import { CFDGraph, CFDRenderer } from 'graphs-renderer'; ```javascript let data = [...] - let cfdSelector = "#cfd"; - let cfdGraph = new CFDGraph(data) - let cfdDataSet = cfdGraph.computeDataSet(); - let cfdRenderer = new CFDRenderer(cfdDataSet) - cfdRenderer.drawGraph(cfdSelector) - cfdRenderer.useBrush("#cfd-brush") +let cfdSelector = "#cfd"; +let cfdGraph = new CFDGraph(data) +let cfdDataSet = cfdGraph.computeDataSet(); +let cfdRenderer = new CFDRenderer(cfdDataSet) +cfdRenderer.renderGraph(cfdSelector) +cfdRenderer.setupBrush("#cfd-brush") ``` To see usage examples of the library and the data format for the graph refer to [Examples](#examples) diff --git a/examples/example.js b/examples/example.js index 5f29901..914f45f 100644 --- a/examples/example.js +++ b/examples/example.js @@ -43,7 +43,7 @@ async function renderGraphs(data, serviceId) { cfdRenderer.useEventBus(eventBus); if (document.querySelector(cfdGraphElementSelector)) { if (cfdGraphDataSet.length > 0) { - cfdRenderer.drawGraph(cfdGraphElementSelector); + cfdRenderer.renderGraph(cfdGraphElementSelector); document.querySelector(cfdBrushElementSelector) && cfdRenderer.useBrush(cfdBrushElementSelector); document.querySelector(controlsElementSelector) && cfdRenderer.useControls("#reporting-range-input", "#range-increments-select"); document.querySelector(loadConfigInputSelector) && cfdRenderer.useConfigLoading(loadConfigInputSelector, resetConfigInputSelector); @@ -68,7 +68,7 @@ async function renderGraphs(data, serviceId) { const histogramRenderer = new HistogramRenderer(leadTimeDataSet, eventBus); if (document.querySelector(scatterplotGraphElementSelector)) { if (leadTimeDataSet.length > 0) { - scatterplotRenderer.drawGraph(scatterplotGraphElementSelector); + scatterplotRenderer.renderGraph(scatterplotGraphElementSelector); document.querySelector(histogramGraphElementSelector) && histogramRenderer.drawGraph(histogramGraphElementSelector); document.querySelector(scatterplotBrushElementSelector) && scatterplotRenderer.useBrush(scatterplotBrushElementSelector); document.querySelector(controlsElementSelector) && diff --git a/src/graphs/UIControlsRenderer.js b/src/graphs/UIControlsRenderer.js index 8fbb6c8..f8304a5 100644 --- a/src/graphs/UIControlsRenderer.js +++ b/src/graphs/UIControlsRenderer.js @@ -2,62 +2,69 @@ import { readJsonFile } from '../utils/utils.js'; import Renderer from './Renderer.js'; export default class UIControlsRenderer extends Renderer { - currentSelectionDomain; - defaultSelectionDomain; + selectedTimeRange; + defaultTimeRange; #defaultReportingRangeDays = 90; - #defaultRangeIncrementUnits = 'weeks'; + #defaultTimeInterval = 'weeks'; reportingRangeDays = this.#defaultReportingRangeDays; - rangeIncrementUnits = this.#defaultRangeIncrementUnits; - gBrush; + timeInterval = this.#defaultTimeInterval; + brushGroup; brush; - isUserBrushEvent = true; + isManualBrushUpdate = true; constructor(data) { super(data); this.reportingRangeDays = localStorage.getItem('reportingRangeDays') || this.reportingRangeDays; - this.rangeIncrementUnits = localStorage.getItem('rangeIncrementUnits') || this.rangeIncrementUnits; + this.timeInterval = localStorage.getItem('rangeIncrementUnits') || this.timeInterval; } - useBrush(brushElementSelector) { + setupBrush(brushElementSelector) { this.brushSelector = brushElementSelector; - this.defaultSelectionDomain = this.getReportingDomain(this.reportingRangeDays); - this.currentSelectionDomain ||= Array.from(this.defaultSelectionDomain); - this.drawBrush(); + this.defaultTimeRange = this.computeReportingRange(this.reportingRangeDays); + this.selectedTimeRange ||= Array.from(this.defaultTimeRange); + this.renderBrush(); } - updateBrush(newSelectionDomain) { - if (newSelectionDomain) { - this.isUserBrushEvent = false; - this.currentSelectionDomain = newSelectionDomain; - this.gBrush?.call(this.brush)?.call( + updateBrushSelection(newTimeRange) { + if (newTimeRange) { + this.isManualBrushUpdate = false; + this.selectedTimeRange = newTimeRange; + this.brushGroup?.call(this.brush)?.call( this.brush.move, - newSelectionDomain?.map((d) => this.x(d)) + newTimeRange?.map((d) => this.x(d)) ); } } - useControls(reportingRangeDaysSelector, rangeIncrementUnits) { - this.reportingRangeDaysElement = document.querySelector(reportingRangeDaysSelector); - this.reportingRangeDaysElement.value = this.reportingRangeDays; - this.currentSelectionDomain ||= this.getReportingDomain(this.reportingRangeDays); - this.reportingRangeDaysElement.addEventListener('keypress', (event) => { + setupChartControls(reportingRangeDaysSelector, timeIntervalSelector) { + this.setupReportingRangeDays(reportingRangeDaysSelector); + this.setupTimeInterval(timeIntervalSelector); + this.brushSelector ? this.renderBrush() : this.updateChart(this.selectedTimeRange); + } + + setupReportingRangeDays(reportingRangeDaysSelector) { + this.reportingRangeDaysInput = document.querySelector(reportingRangeDaysSelector); + this.reportingRangeDaysInput.value = this.reportingRangeDays; + this.selectedTimeRange ||= this.computeReportingRange(this.reportingRangeDays); + this.reportingRangeDaysInput.addEventListener('keypress', (event) => { if (event.key === 'Enter') { this.reportingRangeDays = event.target.value; - this.currentSelectionDomain = this.getReportingDomain(this.reportingRangeDays); - this.brushSelector ? this.drawBrush() : this.updateChart(this.currentSelectionDomain); + this.selectedTimeRange = this.computeReportingRange(this.reportingRangeDays); + this.brushSelector ? this.renderBrush() : this.updateChart(this.selectedTimeRange); } }); + } - this.rangeIncrementUnitsElement = document.querySelector(rangeIncrementUnits); - this.rangeIncrementUnitsElement.value = this.rangeIncrementUnits; + setupTimeInterval(timeIntervalSelector) { + this.rangeIncrementUnitsElement = document.querySelector(timeIntervalSelector); + this.rangeIncrementUnitsElement.value = this.timeInterval; this.rangeIncrementUnitsElement.addEventListener('change', (event) => { - this.rangeIncrementUnits = event.target.value; - this.drawXAxis(this.gx, this.x.copy().domain(this.currentSelectionDomain), this.rangeIncrementUnits); + this.timeInterval = event.target.value; + this.drawXAxis(this.gx, this.x.copy().domain(this.selectedTimeRange), this.timeInterval); }); - this.brushSelector ? this.drawBrush() : this.updateChart(this.currentSelectionDomain); } - useConfigLoading(loadConfigInputSelector, resetConfigInputSelector) { + setupConfigLoader(loadConfigInputSelector, resetConfigInputSelector) { this.loadConfigButton = document.querySelector(loadConfigInputSelector); const fileChosenElement = document.querySelector('#config-file-chosen'); this.loadConfigButton.addEventListener('change', async (event) => { @@ -66,11 +73,11 @@ export default class UIControlsRenderer extends Renderer { const jsonConfig = await readJsonFile(file); fileChosenElement.textContent = file.name; this.reportingRangeDays = jsonConfig.reportingRangeDays || this.reportingRangeDays; - this.rangeIncrementUnits = jsonConfig.rangeIncrementUnits || this.rangeIncrementUnits; + this.timeInterval = jsonConfig.timeInterval || this.timeInterval; localStorage.setItem('reportingRangeDays', this.reportingRangeDays); - localStorage.setItem('rangeIncrementUnits', this.rangeIncrementUnits); - this.currentSelectionDomain = this.getReportingDomain(this.reportingRangeDays); - this.brushSelector ? this.drawBrush() : this.updateChart(this.currentSelectionDomain); + localStorage.setItem('rangeIncrementUnits', this.timeInterval); + this.selectedTimeRange = this.computeReportingRange(this.reportingRangeDays); + this.brushSelector ? this.renderBrush() : this.updateChart(this.selectedTimeRange); } catch (err) { console.error(err); fileChosenElement.textContent = err; @@ -81,28 +88,28 @@ export default class UIControlsRenderer extends Renderer { localStorage.removeItem('reportingRangeDays'); localStorage.removeItem('rangeIncrementUnits'); this.reportingRangeDays = this.#defaultReportingRangeDays; - this.rangeIncrementUnits = this.#defaultRangeIncrementUnits; - this.currentSelectionDomain = this.getReportingDomain(this.reportingRangeDays); - this.brushSelector ? this.drawBrush() : this.updateChart(this.currentSelectionDomain); + this.timeInterval = this.#defaultTimeInterval; + this.selectedTimeRange = this.computeReportingRange(this.reportingRangeDays); + this.brushSelector ? this.renderBrush() : this.updateChart(this.selectedTimeRange); }); } setReportingRangeDays(reportingRangeDays) { this.reportingRangeDays = reportingRangeDays; - if (this.reportingRangeDaysElement) { - this.reportingRangeDaysElement.value = Math.floor(this.reportingRangeDays); + if (this.reportingRangeDaysInput) { + this.reportingRangeDaysInput.value = Math.floor(this.reportingRangeDays); } } - setRangeIncrementUnits(rangeIncrementUnits) { - this.rangeIncrementUnits = rangeIncrementUnits; + setTimeInterval(rangeIncrementUnits) { + this.timeInterval = rangeIncrementUnits; if (this.rangeIncrementUnitsElement) { const option = Array.from(this.rangeIncrementUnitsElement.options).find((o) => o.value === rangeIncrementUnits); option.selected = true; } } - drawBrush() { + renderBrush() { throw new Error('Method not implemented!'); } } diff --git a/src/graphs/cfd/CFDRenderer.js b/src/graphs/cfd/CFDRenderer.js index 04ddf96..28f9157 100644 --- a/src/graphs/cfd/CFDRenderer.js +++ b/src/graphs/cfd/CFDRenderer.js @@ -46,20 +46,28 @@ class CFDRenderer extends UIControlsRenderer { console.table(data); } - useEventBus(eventBus) { + setupEventBus(eventBus) { this.eventBus = eventBus; - this.eventBus?.addEventListener('change-time-range-scatterplot', this.updateBrush.bind(this)); + this.eventBus?.addEventListener('change-time-range-scatterplot', this.updateBrushSelection.bind(this)); } - useObservationLogging(observations) { + setupObservationLogging(observations) { if (observations) { - this.cfdTooltip = d3.select('body').append('div').attr('class', styles.tooltip).attr('id', 'c-tooltip').style('opacity', 0); - this.cfdLine = this.chartArea.append('line').attr('id', 'cfd-line').attr('stroke', 'black').style('display', 'none'); - this.chartArea.on('mouseleave', () => this.hideTooltip()); + this.#createTooltipAndMovingLine(); + this.#setupMouseLeaveHandler(); this.markObservations(observations); } } + #setupMouseLeaveHandler() { + this.chartArea.on('mouseleave', () => this.hideTooltip()); + } + + #createTooltipAndMovingLine() { + this.cfdTooltip = d3.select('body').append('div').attr('class', styles.tooltip).attr('id', 'c-tooltip').style('opacity', 0); + this.cfdLine = this.chartArea.append('line').attr('id', 'cfd-line').attr('stroke', 'black').style('display', 'none'); + } + markObservations(observations) { if (observations) { this.observations = observations; @@ -110,13 +118,13 @@ class CFDRenderer extends UIControlsRenderer { event.observationBody && this.cfdTooltip.append('p').text('Observation: ' + event.observationBody); } - getReportingDomain(noOfDays) { + computeReportingRange(noOfDays) { const finalDate = this.data[this.data.length - 1].date; let endDate = new Date(finalDate); let startDate = addDaysToDate(finalDate, -Number(noOfDays)); - if (this.currentSelectionDomain) { - endDate = new Date(this.currentSelectionDomain[1]); - startDate = new Date(this.currentSelectionDomain[0]); + if (this.selectedTimeRange) { + endDate = new Date(this.selectedTimeRange[1]); + startDate = new Date(this.selectedTimeRange[0]); const diffDays = Number(noOfDays) - calculateDaysBetweenDates(startDate, endDate); if (diffDays < 0) { startDate = addDaysToDate(startDate, -Number(diffDays)); @@ -135,16 +143,16 @@ class CFDRenderer extends UIControlsRenderer { return [startDate, endDate]; } - drawGraph(graphElementSelector) { + renderGraph(graphElementSelector) { this.#drawSvg(graphElementSelector); this.svg.append('g').attr('transform', `translate(${this.margin.left}, ${this.margin.top})`); this.#drawAxis(); this.#drawArea(); } - drawBrush() { + renderBrush() { const svgBrush = this.#drawBrushSvg(this.brushSelector); - const defaultSelectionRange = this.defaultSelectionDomain.map((d) => this.x(d)); + const defaultSelectionRange = this.defaultTimeRange.map((d) => this.x(d)); this.brush = d3 .brushX() .extent([ @@ -152,26 +160,26 @@ class CFDRenderer extends UIControlsRenderer { [this.width, this.focusHeight - this.margin.top + 1], ]) .on('brush', ({ selection }) => { - this.currentSelectionDomain = selection.map(this.x.invert, this.x); - this.updateChart(this.currentSelectionDomain); - if (this.isUserBrushEvent && this.eventBus) { - this.eventBus?.emitEvents('change-time-range-cfd', this.currentSelectionDomain); + this.selectedTimeRange = selection.map(this.x.invert, this.x); + this.updateChart(this.selectedTimeRange); + if (this.isManualBrushUpdate && this.eventBus) { + this.eventBus?.emitEvents('change-time-range-cfd', this.selectedTimeRange); } - this.isUserBrushEvent = true; + this.isManualBrushUpdate = true; }) .on('end', ({ selection }) => { if (!selection) { - this.gBrush.call(this.brush.move, defaultSelectionRange); + this.brushGroup.call(this.brush.move, defaultSelectionRange); } }); const brushArea = this.#computeArea(this.x, this.y.copy().range([this.focusHeight - this.margin.top, 4])); this.#drawStackedAreaChart(svgBrush, this.#stackedData, brushArea); this.drawXAxis(svgBrush.append('g'), this.x, '', this.focusHeight - this.margin.top); - this.gBrush = svgBrush.append('g'); - this.gBrush.call(this.brush).call( + this.brushGroup = svgBrush.append('g'); + this.brushGroup.call(this.brush).call( this.brush.move, - this.currentSelectionDomain.map((d) => this.x(d)) + this.selectedTimeRange.map((d) => this.x(d)) ); } @@ -198,7 +206,7 @@ class CFDRenderer extends UIControlsRenderer { this.gx = this.svg.append('g'); this.gy = this.svg.append('g'); - this.drawXAxis(this.gx, this.x, this.rangeIncrementUnits); + this.drawXAxis(this.gx, this.x, this.timeInterval); this.drawYAxis(this.gy, this.y); } @@ -300,7 +308,7 @@ class CFDRenderer extends UIControlsRenderer { this.setReportingRangeDays(calculateDaysBetweenDates(domain[0], domain[1])); this.currentXScale = this.x.copy().domain(domain); this.currentYScale = this.y.copy().domain([0, maxY]).nice(); - this.drawXAxis(this.gx, this.currentXScale, this.rangeIncrementUnits, this.height); + this.drawXAxis(this.gx, this.currentXScale, this.timeInterval, this.height); this.drawYAxis(this.gy, this.currentYScale); this.chartArea @@ -313,7 +321,7 @@ class CFDRenderer extends UIControlsRenderer { drawXAxis(g, x, rangeIncrementUnits, height = this.height) { let axis; - rangeIncrementUnits && this.setRangeIncrementUnits(rangeIncrementUnits); + rangeIncrementUnits && this.setTimeInterval(rangeIncrementUnits); switch (rangeIncrementUnits) { case 'days': axis = d3 diff --git a/src/graphs/histogram/HistogramRenderer.js b/src/graphs/histogram/HistogramRenderer.js index 30e62bc..8593eba 100644 --- a/src/graphs/histogram/HistogramRenderer.js +++ b/src/graphs/histogram/HistogramRenderer.js @@ -56,7 +56,7 @@ class HistogramRenderer extends Renderer { this.x = this.computeLinearScale(xDomain, [0, this.width]).nice(); } - drawGraph(graphElementSelector) { + renderGraph(graphElementSelector) { this.#drawSvg(graphElementSelector); this.#drawAxis(); this.#drawArea(); diff --git a/src/graphs/scatterplot/ScatterplotRenderer.js b/src/graphs/scatterplot/ScatterplotRenderer.js index deed66f..d0fc12c 100644 --- a/src/graphs/scatterplot/ScatterplotRenderer.js +++ b/src/graphs/scatterplot/ScatterplotRenderer.js @@ -36,53 +36,71 @@ class ScatterplotRenderer extends UIControlsRenderer { console.table(data); } - useEventBus(eventBus) { + setupEventBus(eventBus) { this.eventBus = eventBus; - this.eventBus?.addEventListener('change-time-range-cfd', this.updateBrush.bind(this)); + this.eventBus?.addEventListener('change-time-range-cfd', this.updateBrushSelection.bind(this)); } - useObservationLogging(observations, workTicketsURL) { + setupObservationLogging(observations, workTicketsURL) { if (observations) { - this.scatterplotTooltip = d3.select('body').append('div').attr('class', styles.tooltip).attr('id', 's-tooltip').style('opacity', 0); - d3.select(this.svg.node().parentNode).on('mouseleave', (event) => { - if (event.relatedTarget !== this.scatterplotTooltip.node()) { - this.hideTooltip(); - } - }); + this.#createTooltip(); + this.#setupMouseLeaveHandler(); this.workTicketsURL = workTicketsURL; - this.markObservations(observations); + this.#displayObservationMarkers(observations); } } - markObservations(observations) { + #setupMouseLeaveHandler() { + d3.select(this.svg.node().parentNode).on('mouseleave', (event) => { + if (event.relatedTarget !== this.tooltip.node()) { + this.hideTooltip(); + } + }); + } + + #createTooltip() { + this.tooltip = d3.select('body').append('div').attr('class', styles.tooltip).attr('id', 's-tooltip').style('opacity', 0); + } + + #displayObservationMarkers(observations) { if (observations) { this.observations = observations; - this.chartArea.selectAll('.ring').remove(); - this.chartArea - .selectAll('ring') - .data(this.data.filter((d) => this.observations.data.rows.some((o) => o.work_item === d.ticketId))) - .enter() - .append('circle') - .attr('class', 'ring') - .attr('cx', (d) => this.currentXScale(d.delivered)) - .attr('cy', (d) => this.currentYScale(d.noOfDays)) - .attr('r', 8) - .attr('fill', 'none') - .attr('stroke', 'black') - .attr('stroke-width', '2px'); + this.#removeObservationMarkers(); + this.#createObservationMarkers(); } } + #removeObservationMarkers() { + this.chartArea.selectAll('.ring').remove(); + } + + #createObservationMarkers() { + this.chartArea + .selectAll('ring') + .data(this.data.filter((d) => this.observations.data.rows.some((o) => o.work_item === d.ticketId))) + .enter() + .append('circle') + .attr('class', 'ring') + .attr('cx', (d) => this.currentXScale(d.delivered)) + .attr('cy', (d) => this.currentYScale(d.noOfDays)) + .attr('r', 8) + .attr('fill', 'none') + .attr('stroke', 'black') + .attr('stroke-width', '2px'); + } + hideTooltip() { - this.scatterplotTooltip.transition().duration(100).style('opacity', 0).style('pointer-events', 'none'); + this.tooltip.transition().duration(100).style('opacity', 0).style('pointer-events', 'none'); } showTooltip(event) { - this.scatterplotTooltip.selectAll('*').remove(); - this.scatterplotTooltip.transition().duration(100).style('opacity', 0.9).style('pointer-events', 'auto'); - this.scatterplotTooltip - .style('left', event.tooltipLeft + 'px') - .style('top', event.tooltipTop + 'px') + this.#clearTooltipContent(); + this.#positionTooltip(event.tooltipLeft, event.tooltipTop); + this.#populateTooltip(event); + } + + #populateTooltip(event) { + this.tooltip .style('pointer-events', 'auto') .style('opacity', 0.9) .append('a') @@ -91,42 +109,26 @@ class ScatterplotRenderer extends UIControlsRenderer { .attr('href', `#`) .text(event.ticketId) .attr('target', '_blank'); - event.observationBody && this.scatterplotTooltip.append('p').text('Observation: ' + event.observationBody); + event.observationBody && this.tooltip.append('p').text('Observation: ' + event.observationBody); } - getReportingDomain(noOfDays) { - const finalDate = this.data[this.data.length - 1].delivered; - let endDate = new Date(finalDate); - let startDate = addDaysToDate(finalDate, -Number(noOfDays)); - if (this.currentSelectionDomain) { - endDate = new Date(this.currentSelectionDomain[1]); - startDate = new Date(this.currentSelectionDomain[0]); - const diffDays = Number(noOfDays) - calculateDaysBetweenDates(startDate, endDate); - if (diffDays < 0) { - startDate = addDaysToDate(startDate, -Number(diffDays)); - } else { - endDate = addDaysToDate(endDate, Number(diffDays)); - if (endDate > finalDate) { - const diffEndDays = calculateDaysBetweenDates(finalDate, endDate); - endDate = finalDate; - startDate = addDaysToDate(startDate, -Number(diffEndDays)); - } - } - } - if (startDate < this.data[0].delivered) { - startDate = this.data[0].delivered; - } - return [startDate, endDate]; + #positionTooltip(left, top) { + this.tooltip.transition().duration(100).style('opacity', 0.9).style('pointer-events', 'auto'); + this.tooltip.style('left', left + 'px').style('top', top + 'px'); } - drawGraph(graphElementSelector) { + #clearTooltipContent() { + this.tooltip.selectAll('*').remove(); + } + + renderGraph(graphElementSelector) { this.#drawSvg(graphElementSelector); this.#drawAxis(); this.#drawArea(); } - drawBrush() { - const defaultSelectionRange = this.defaultSelectionDomain.map((d) => this.x(d)); + renderBrush() { + const defaultSelectionRange = this.defaultTimeRange.map((d) => this.x(d)); const svgBrush = this.createSvg(this.brushSelector, this.focusHeight); this.brush = d3 .brushX() @@ -135,26 +137,26 @@ class ScatterplotRenderer extends UIControlsRenderer { [this.width, this.focusHeight - this.margin.top + 1], ]) .on('brush', ({ selection }) => { - this.currentSelectionDomain = selection.map(this.x.invert, this.x); - this.updateChart(this.currentSelectionDomain); - if (this.isUserBrushEvent && this.eventBus) { - this.eventBus?.emitEvents('change-time-range-scatterplot', this.currentSelectionDomain); + this.selectedTimeRange = selection.map(this.x.invert, this.x); + this.updateChart(this.selectedTimeRange); + if (this.isManualBrushUpdate && this.eventBus) { + this.eventBus?.emitEvents('change-time-range-scatterplot', this.selectedTimeRange); } - this.isUserBrushEvent = true; + this.isManualBrushUpdate = true; }) .on('end', ({ selection }) => { if (!selection) { - this.gBrush.call(this.brush.move, defaultSelectionRange); + this.brushGroup.call(this.brush.move, defaultSelectionRange); } }); const brushArea = this.addClipPath(svgBrush, 'scatterplot-brush-clip', this.width, this.focusHeight - this.margin.top + 1); this.#drawScatterPlot(brushArea, this.data, this.x, this.y.copy().range([this.focusHeight - this.margin.top - 2, 2])); this.drawXAxis(svgBrush.append('g'), this.x, '', this.focusHeight - this.margin.top); - this.gBrush = brushArea; - this.gBrush.call(this.brush).call( + this.brushGroup = brushArea; + this.brushGroup.call(this.brush).call( this.brush.move, - this.currentSelectionDomain.map((d) => this.x(d)) + this.selectedTimeRange.map((d) => this.x(d)) ); } @@ -172,6 +174,31 @@ class ScatterplotRenderer extends UIControlsRenderer { return this.createSvg(brushSelector, this.focusHeight); } + computeReportingRange(noOfDays) { + const finalDate = this.data[this.data.length - 1].delivered; + let endDate = new Date(finalDate); + let startDate = addDaysToDate(finalDate, -Number(noOfDays)); + if (this.selectedTimeRange) { + endDate = new Date(this.selectedTimeRange[1]); + startDate = new Date(this.selectedTimeRange[0]); + const diffDays = Number(noOfDays) - calculateDaysBetweenDates(startDate, endDate); + if (diffDays < 0) { + startDate = addDaysToDate(startDate, -Number(diffDays)); + } else { + endDate = addDaysToDate(endDate, Number(diffDays)); + if (endDate > finalDate) { + const diffEndDays = calculateDaysBetweenDates(finalDate, endDate); + endDate = finalDate; + startDate = addDaysToDate(startDate, -Number(diffEndDays)); + } + } + } + if (startDate < this.data[0].delivered) { + startDate = this.data[0].delivered; + } + return [startDate, endDate]; + } + #drawAxis() { const xDomain = d3.extent(this.data, (d) => d.delivered); this.x = this.computeTimeScale(xDomain, [0, this.width]); @@ -180,7 +207,7 @@ class ScatterplotRenderer extends UIControlsRenderer { this.gx = this.svg.append('g'); this.gy = this.svg.append('g'); - this.drawXAxis(this.gx, this.x, this.rangeIncrementUnits); + this.drawXAxis(this.gx, this.x, this.timeInterval); this.drawYAxis(this.gy, this.y); } @@ -197,7 +224,7 @@ class ScatterplotRenderer extends UIControlsRenderer { this.currentXScale = this.x.copy().domain(domain); this.currentYScale = this.y.copy().domain([0, maxY]).nice(); const focusData = this.data.filter((d) => d.delivered <= domain[1] && d.delivered >= domain[0]); - this.drawXAxis(this.gx, this.currentXScale, this.rangeIncrementUnits, this.height); + this.drawXAxis(this.gx, this.currentXScale, this.timeInterval, this.height); this.drawYAxis(this.gy, this.currentYScale); this.chartArea @@ -206,12 +233,12 @@ class ScatterplotRenderer extends UIControlsRenderer { .attr('cy', (d) => this.currentYScale(d.noOfDays)) .attr('fill', this.#color); this.#drawPercentileLines(this.svg, focusData, this.currentYScale); - this.markObservations(this.observations); + this.#displayObservationMarkers(this.observations); } drawXAxis(g, x, rangeIncrementUnits, height = this.height) { let axis; - rangeIncrementUnits && this.setRangeIncrementUnits(rangeIncrementUnits); + rangeIncrementUnits && this.setTimeInterval(rangeIncrementUnits); switch (rangeIncrementUnits) { case 'days': axis = d3