This is an automated email from the ASF dual-hosted git repository.

sadpandajoe pushed a commit to branch ultraset-nif-44532-2
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 99183d3d1c9627720f40eed0958a19bb867fb2a7
Author: Joe Li <[email protected]>
AuthorDate: Thu Sep 24 07:55:21 2026 +0000

    fix(calendar): position month/week cells by index within their own block
    
    getMonthWeekNumber positioned a week cell relative to the cell date's
    own calendar month instead of the month block it is rendered in. Since
    a month block also renders the tail (or head) of an adjacent month's
    week, this made most months draw two week cells at the same x position
    (one hiding the other), and made a month whose 1st falls on a Monday
    resolve its own first week to x = -1, outside its block.
    
    Position each cell by its index within the block's own generated cell
    list instead. defaultColumnNumber for month domains also under-counted
    by one relative to the number of cells computeWeekSubDomainSize
    actually generates for the block, which narrowed the block itself and
    is fixed alongside the positioning for the same reason.
    
    Fixes #44532
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
---
 .../src/vendor/cal-heatmap.ts                      | 50 +++++++++-------------
 .../plugin-chart-calendar/test/cal-heatmap.test.ts | 40 +++++++++++++++++
 2 files changed, 61 insertions(+), 29 deletions(-)

diff --git 
a/superset-frontend/plugins/plugin-chart-calendar/src/vendor/cal-heatmap.ts 
b/superset-frontend/plugins/plugin-chart-calendar/src/vendor/cal-heatmap.ts
index 8b366be1838..fe9253e5d2f 100644
--- a/superset-frontend/plugins/plugin-chart-calendar/src/vendor/cal-heatmap.ts
+++ b/superset-frontend/plugins/plugin-chart-calendar/src/vendor/cal-heatmap.ts
@@ -584,10 +584,15 @@ var CalHeatMap = function () {
           case 'year':
             return self._domainType.week.maxItemNumber;
           case 'month':
+            // +1: this must match the number of cells computeWeekSubDomainSize
+            // generates for the block (an inclusive week-number range), or the
+            // block is rendered one column narrower than its own content.
             return self.options.domainDynamicDimension
               ? self.getWeekNumber(
                   new Date(d.getFullYear(), d.getMonth() + 1, 0),
-                ) - self.getWeekNumber(d)
+                ) -
+                  self.getWeekNumber(d) +
+                  1
               : 5;
         }
       },
@@ -599,16 +604,19 @@ var CalHeatMap = function () {
         return self.getSubDomainColumnNumber(d);
       },
       position: {
-        x: function (d) {
+        x: function (d, i) {
           switch (self.options.domain) {
             case 'year':
               return Math.floor(
                 self.getWeekNumber(d) / self._domainType.week.row(d),
               );
             case 'month':
-              return Math.floor(
-                self.getMonthWeekNumber(d) / self._domainType.week.row(d),
-              );
+              // Position by index within the block's own cell list rather
+              // than by a week-number difference against the cell's own
+              // month: a month block also renders the tail (or head) of an
+              // adjacent month's week, and that borrowed cell's "own month"
+              // is not the block it is being drawn into.
+              return Math.floor(i / self._domainType.week.row(d));
           }
         },
         y: function (d) {
@@ -765,10 +773,11 @@ var CalHeatMap = function () {
       .map(function (d) {
         self._domains.set(
           d,
-          self.getSubDomain(d).map(function (d) {
+          self.getSubDomain(d).map(function (d, i) {
             return {
               t: self._domainType[self.options.subDomain].extractUnit(d),
               v: null,
+              i: i,
             };
           }),
         );
@@ -1062,7 +1071,7 @@ var CalHeatMap = function () {
       .attr('width', options.cellSize)
       .attr('height', options.cellSize)
       .attr('x', function (d) {
-        return self.positionSubDomainX(d.t);
+        return self.positionSubDomainX(d);
       })
       .attr('y', function (d) {
         return self.positionSubDomainY(d.t);
@@ -1230,7 +1239,7 @@ var CalHeatMap = function () {
           return 'subdomain-text' + self.getHighlightClassName(d.t);
         })
         .attr('x', function (d) {
-          return self.positionSubDomainX(d.t) + options.cellSize / 2;
+          return self.positionSubDomainX(d) + options.cellSize / 2;
         })
         .attr('y', function (d) {
           return self.positionSubDomainY(d.t) + options.cellSize / 2;
@@ -2105,10 +2114,11 @@ CalHeatMap.prototype = {
     var total = newDomains.length;
     var domains = this.getDomainKeys();
 
-    function buildSubDomain(d) {
+    function buildSubDomain(d, i) {
       return {
         t: parent._domainType[parent.options.subDomain].extractUnit(d),
         v: null,
+        i: i,
       };
     }
 
@@ -2214,7 +2224,8 @@ CalHeatMap.prototype = {
     'use strict';
 
     var index = this._domainType[this.options.subDomain].position.x(
-      new Date(d),
+      new Date(d.t),
+      d.i,
     );
     return index * this.options.cellSize + index * this.options.cellPadding;
   },
@@ -2449,25 +2460,6 @@ CalHeatMap.prototype = {
     return f(d);
   },
 
-  /**
-   * Return the week number, relative to its month
-   *
-   * @param  int|Date d Date or timestamp in milliseconds
-   * @return int Week number, relative to the month [0-5]
-   */
-  getMonthWeekNumber: function (d) {
-    'use strict';
-
-    if (typeof d === 'number') {
-      d = new Date(d);
-    }
-
-    var monthFirstWeekNumber = this.getWeekNumber(
-      new Date(d.getFullYear(), d.getMonth()),
-    );
-    return this.getWeekNumber(d) - monthFirstWeekNumber - 1;
-  },
-
   /**
    * Return the number of weeks in the dates' year
    *
diff --git 
a/superset-frontend/plugins/plugin-chart-calendar/test/cal-heatmap.test.ts 
b/superset-frontend/plugins/plugin-chart-calendar/test/cal-heatmap.test.ts
index 2247592a50e..37b95948e48 100644
--- a/superset-frontend/plugins/plugin-chart-calendar/test/cal-heatmap.test.ts
+++ b/superset-frontend/plugins/plugin-chart-calendar/test/cal-heatmap.test.ts
@@ -27,10 +27,15 @@ interface CalHeatMapInstance {
     dateFormatter: DateFormatter | null;
     timeFormatter: (t: number) => string;
     valueFormatter: (v: number) => string;
+    domain: string;
+    subDomain: string;
+    weekStartOnMonday: boolean;
   };
   formatDate(date: Date, format: string | FunctionalDateFormat): string;
   tip: { html(): (d: { t: number; v: number }) => string };
   legendTip: { html(): (d: number) => string };
+  positionSubDomainX(d: { t: number; i: number }): number;
+  getSubDomain(date: Date): Date[];
 }
 
 const CalHeatMap = CalHeatMapImport as unknown as new () => CalHeatMapInstance;
@@ -89,3 +94,38 @@ test('legend tooltip HTML escapes creator-controlled 
formatter output', () => {
   expect(html).not.toContain('<img');
   expect(html).toContain('&lt;img');
 });
+
+function positionsForMonthBlock(
+  calendar: CalHeatMapInstance,
+  monthStart: Date,
+): number[] {
+  // Mirrors how _init()/loadNewDomains() build a block's subdomain
+  // list: each cell's render position is derived from its index within
+  // that specific block's own cell list, not from the cell's own date.
+  return calendar
+    .getSubDomain(monthStart)
+    .map((date, i) => calendar.positionSubDomainX({ t: date.getTime(), i }));
+}
+
+test('Month/Week domain gives every cell in a block a distinct, non-negative 
position', () => {
+  // Regression test: a month block also renders the tail (or head) of
+  // an adjacent month's week. Positioning must be relative to the
+  // block being drawn, not to the cell date's own calendar month, or a
+  // borrowed cell collides with one of the block's own cells and hides
+  // its value.
+  const calendar = new CalHeatMap();
+  calendar.options.domain = 'month';
+  calendar.options.subDomain = 'week';
+  calendar.options.weekStartOnMonday = true;
+
+  // May 2026 borrows 2026-04-27 as its first cell; May's own last week
+  // (2026-05-25) must not resolve to the same on-screen column.
+  const mayPositions = positionsForMonthBlock(calendar, new Date(2026, 4, 1));
+  expect(new Set(mayPositions).size).toBe(mayPositions.length);
+
+  // June 2026's 1st falls on a Monday (the week-start day), so the
+  // block has no borrowed cell. Its own first week must stay within
+  // the block instead of resolving to a negative position.
+  const junePositions = positionsForMonthBlock(calendar, new Date(2026, 5, 1));
+  expect(Math.min(...junePositions)).toBeGreaterThanOrEqual(0);
+});

Reply via email to