diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 2ec25da2d..0ef0d1eb8 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -255,6 +255,7 @@ Ranjit Odedra Eltaurus jariji Francisco Esteva +Junia Mannervik ******************** diff --git a/ts/routes/graphs/reviews.ts b/ts/routes/graphs/reviews.ts index ed0e07f8c..64cbdb63b 100644 --- a/ts/routes/graphs/reviews.ts +++ b/ts/routes/graphs/reviews.ts @@ -109,19 +109,32 @@ export function renderReviews( break; } const desiredBars = Math.min(70, Math.abs(xMin!)); + const unboundRange = range == GraphRange.AllTime; + const originalXMin = xMin!; - const x = scaleLinear().domain([xMin!, xMax]); - if (range === GraphRange.AllTime) { - x.nice(desiredBars); + // Create initial scale to determine tick spacing + let x = scaleLinear().domain([xMin!, xMax]); + let thresholds = x.ticks(desiredBars); + // For unbound ranges, extend xMin backward so that the oldest bin has the same width as others + if (unboundRange && thresholds.length >= 2) { + const spacing = thresholds[1] - thresholds[0]; + const partial = thresholds[0] - xMin!; + if (spacing > 0 && partial > 0 && partial < spacing) { + xMin = thresholds[0] - spacing; + x = scaleLinear().domain([xMin, xMax]); + thresholds = x.ticks(desiredBars); + } + } + // For Year & All Time, shift thresholds forward by one day to make first bin 0-4 instead of 0-5 + if (range === GraphRange.Year || range === GraphRange.AllTime) { + thresholds = [...new Set(thresholds.map(t => Math.min(t + 1, 1)))].sort((a, b) => a - b); } const sourceMap = showTime ? sourceData.reviewTime : sourceData.reviewCount; const bins = bin() - .value((m) => { - return m[0]; - }) + .value((m) => m[0]) .domain(x.domain() as any) - .thresholds(x.ticks(desiredBars))(sourceMap.entries() as any); + .thresholds(thresholds)(sourceMap.entries() as any); // empty graph? const totalDays = sum(bins, (bin) => bin.length); @@ -212,7 +225,13 @@ export function renderReviews( } function tooltipText(d: BinType, cumulative: number): string { - const day = dayLabel(d.x0!, d.x1!); + // Convert bin boundaries [x0, x1) for dayLabel + // If bin ends at 0, treat it as crossing zero so day 0 is included + // For the first (oldest) bin, use the original xMin to ensure labels match the intended range + const isFirstBin = bins.length > 0 && d.x0 === bins[0].x0; + const startDay = isFirstBin ? originalXMin : Math.floor(d.x0!); + const endDay = d.x1! === 0 ? 1 : d.x1!; + const day = dayLabel(startDay, endDay); const totals = totalsForBin(d); const dayTotal = valueLabel(sum(totals)); let buf = ``; @@ -327,7 +346,8 @@ export function renderReviews( }) .on("mouseout", hideTooltip); - const periodDays = -xMin + 1; + // The xMin might be extended for bin alignment, so use the original xMin + const periodDays = -originalXMin + 1; const studiedDays = sum(bins, (bin) => bin.length); const studiedPercent = (studiedDays / periodDays) * 100; const total = yCumMax;
${day}${dayTotal}