diff --git a/src/graphs/Renderer.js b/src/graphs/Renderer.js index 4f06585..b25ac5e 100644 --- a/src/graphs/Renderer.js +++ b/src/graphs/Renderer.js @@ -7,9 +7,9 @@ import styles from './tooltipStyles.module.css'; export class Renderer { margin = { top: 30, right: 40, bottom: 70, left: 40 }; width = 1040 - this.margin.left - this.margin.right; - height = 460 - this.margin.top - this.margin.bottom; + height = 380 - this.margin.top - this.margin.bottom; axisLabelFontSize = 14; - focusHeight = 120; + focusHeight = 90; gx; gy; x; diff --git a/src/graphs/control-chart/ControlRenderer.js b/src/graphs/control-chart/ControlRenderer.js index 6bc1ed0..9c59cc9 100644 --- a/src/graphs/control-chart/ControlRenderer.js +++ b/src/graphs/control-chart/ControlRenderer.js @@ -3,7 +3,7 @@ import * as d3 from 'd3'; export class ControlRenderer extends ScatterplotRenderer { color = '#0ea5e9'; - timeScale = 'linear'; + timeScale = 'logarithmic'; connectDots = false; constructor(data, avgMovingRangeFunc, chartName, workTicketsURL) { diff --git a/src/graphs/moving-range/MovingRangeRenderer.js b/src/graphs/moving-range/MovingRangeRenderer.js index 670c852..a7197e1 100644 --- a/src/graphs/moving-range/MovingRangeRenderer.js +++ b/src/graphs/moving-range/MovingRangeRenderer.js @@ -3,7 +3,7 @@ import * as d3 from 'd3'; export class MovingRangeRenderer extends ScatterplotRenderer { color = '#0ea5e9'; - timeScale = 'linear'; + timeScale = 'logarithmic'; constructor(data, avgMovingRangeFunc, workTicketsURL, chartName) { super(data); diff --git a/src/graphs/pbc/PBCRenderer.js b/src/graphs/pbc/PBCRenderer.js new file mode 100644 index 0000000..013d595 --- /dev/null +++ b/src/graphs/pbc/PBCRenderer.js @@ -0,0 +1,171 @@ +import { UIControlsRenderer } from '../UIControlsRenderer.js'; + +export class PBCRenderer extends UIControlsRenderer { + constructor(controlData, movingRangeData, avgMovingRangeFunc, workTicketsURL, chartName = 'pbc') { + super(controlData); + this.controlData = controlData; + this.movingRangeData = movingRangeData; + this.avgMovingRangeFunc = avgMovingRangeFunc; + this.workTicketsURL = workTicketsURL; + this.chartName = chartName; + this.chartType = 'PBC'; + + // Child renderers + this.controlRenderer = null; + this.movingRangeRenderer = null; + } + + /** + * Initialize child renderers with their respective data + */ + initializeRenderers(ControlRenderer, MovingRangeRenderer) { + this.controlRenderer = new ControlRenderer(this.controlData, this.avgMovingRangeFunc, `${this.chartName}-control`, this.workTicketsURL); + + this.movingRangeRenderer = new MovingRangeRenderer( + this.movingRangeData, // Moving range calculated data + this.avgMovingRangeFunc, + this.workTicketsURL, + `${this.chartName}-moving-range` + ); + } + + /** + * Render both charts + */ + renderGraph(containerSelector) { + this.createContainers(containerSelector); + // Render control chart + this.controlRenderer.renderGraph(`${containerSelector} .control-chart`); + // Render moving range chart + this.movingRangeRenderer.renderGraph(`${containerSelector} .moving-range-chart`); + // Sync baseline dates and time ranges + this.syncChartProperties(); + } + + /** + * Create HTML containers for both charts + */ + createContainers(containerSelector) { + const container = document.querySelector(containerSelector); + if (!container) return; + + container.innerHTML = ` +
+
+
+
+ `; + } + + /** + * Setup brush - only from control chart + */ + setupBrush(brushSelector) { + this.brushSelector = brushSelector; + this.controlRenderer.setupBrush(brushSelector); + this.syncBrushEvents(); + this.updateGraph(this.controlRenderer.selectedTimeRange); + } + + /** + * Sync brush events to update both charts + */ + syncBrushEvents() { + // Override control renderer's updateGraph to also update moving range + const originalUpdateGraph = this.controlRenderer.updateGraph.bind(this.controlRenderer); + this.controlRenderer.updateGraph = (domain) => { + // Update control chart + originalUpdateGraph(domain); + + // Update moving range chart with the same domain + this.movingRangeRenderer.updateGraph(domain); + }; + } + + setTimeScale(timeScale) { + this.controlRenderer.timeScale = timeScale; + this.movingRangeRenderer.timeScale = timeScale; + } + + /** + * Sync properties between charts + */ + syncChartProperties() { + if (!this.controlRenderer || !this.movingRangeRenderer) return; + this.movingRangeRenderer.reportingRangeDays = 30; + this.controlRenderer.reportingRangeDays = 30; + this.movingRangeRenderer.timeInterval = 'months'; + this.controlRenderer.timeInterval = 'months'; + this.movingRangeRenderer.timeScale = 'logarithmic'; + this.controlRenderer.timeScale = 'logarithmic'; + } + + /** + * Setup event bus for both charts + */ + setupEventBus(eventBus, mouseChartsEvents, timeRangeChartsEvents) { + this.controlRenderer?.setupEventBus(eventBus, mouseChartsEvents, timeRangeChartsEvents); + this.movingRangeRenderer?.setupEventBus(eventBus, mouseChartsEvents, timeRangeChartsEvents); + } + + /** + * Setup observations for both charts + */ + setupObservationLogging(observations) { + this.controlRenderer?.setupObservationLogging(observations); + this.movingRangeRenderer?.setupObservationLogging(observations); + } + + setTimeScaleListener(timeScaleSelector) { + const selectElement = document.querySelector(timeScaleSelector); + if (!selectElement) { + console.warn(`Time scale selector not found: ${timeScaleSelector}`); + return; + } + // Single event listener that updates both charts + selectElement.addEventListener('change', (event) => { + const newTimeScale = event.target.value; + // Update both renderers + if (this.controlRenderer) { + this.controlRenderer.timeScale = newTimeScale; + this.controlRenderer.computeYScale(); + this.controlRenderer.updateGraph(this.controlRenderer.selectedTimeRange); + this.controlRenderer.renderBrush(); // Only control renderer renders brush + } + + if (this.movingRangeRenderer) { + this.movingRangeRenderer.timeScale = newTimeScale; + this.movingRangeRenderer.computeYScale(); + this.movingRangeRenderer.updateGraph(this.controlRenderer.selectedTimeRange); + // No renderBrush() for moving range + } + }); + } + + /** + * Clear both charts + */ + clearGraph(containerSelector, brushSelector) { + this.controlRenderer?.clearGraph(`${containerSelector} .control-chart`, brushSelector); + this.movingRangeRenderer?.clearGraph(`${containerSelector} .moving-range-chart`, null); + + const container = document.querySelector(containerSelector); + if (container) container.innerHTML = ''; + } + + /** + * Update both charts + */ + updateGraph(domain) { + this.controlRenderer?.updateGraph(domain); + this.movingRangeRenderer?.updateGraph(domain); + } + + /** + * Cleanup + */ + cleanup() { + this.controlRenderer?.cleanupTooltip?.(); + this.movingRangeRenderer?.cleanupTooltip?.(); + } +} diff --git a/src/graphs/tooltipStyles.module.css b/src/graphs/tooltipStyles.module.css index 4d42229..58f19f8 100644 --- a/src/graphs/tooltipStyles.module.css +++ b/src/graphs/tooltipStyles.module.css @@ -5,7 +5,7 @@ height: max-content; padding: 8px; font: 14px sans-serif; - background: #e2ecfd; + background: #b1b1b1; border: 0; border-radius: 8px; z-index: 1; @@ -44,13 +44,13 @@ /* Specific metric colors */ .cycleTime { - color: #d57500; /* orange */ + color: yellow; /* orange */ } .leadTime { - color: #0062a5; /* blue */ + color: indigo; /* blue */ } .wip { - color: #dd0030; /* pinkish-red */ + color: red; /* pinkish-red */ } diff --git a/src/index.js b/src/index.js index 240d89d..73d9f12 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,7 @@ import { ControlRenderer } from './graphs/control-chart/ControlRenderer.js'; import { HistogramRenderer } from './graphs/histogram/HistogramRenderer.js'; import { WorkItemAgeGraph } from './graphs/work-item-age/WorkItemAgeGraph.js'; import { WorkItemAgeRenderer } from './graphs/work-item-age/WorkItemAgeRenderer.js'; +import { PBCRenderer } from './graphs/pbc/PBCRenderer.js'; import { eventBus } from './utils/EventBus.js'; import { processServiceData } from './data-processor.js'; import { ObservationLoggingService } from './graphs/ObservationLoggingService.js'; @@ -26,6 +27,7 @@ export { WorkItemAgeGraph, WorkItemAgeRenderer, ObservationLoggingService, + PBCRenderer, eventBus, processServiceData, };