New to Kendo UI for jQueryStart a free 30-day trial

Sticky x-Axis Header in Kendo UI for jQuery Heatmap

Updated on Jun 8, 2026

Environment

Product Kendo UI for jQuery Heatmap
Version 2026.1.415

Description

In a Kendo UI for jQuery Heatmap, long datasets may require vertical scrolling. However, scrolling often hides the x-axis labels, making it challenging to track data. This article explains how to keep the x-axis labels always visible at the top, even during scrolls. Additionally, it addresses aligning the header across different browsers and creating a grouped header with two levels, such as years and months.

This knowledge base article also answers the following questions:

  • How do I create a sticky x-axis header in Kendo UI for jQuery Heatmap?
  • How can I create a grouped header with years and months in Kendo UI for jQuery Heatmap?
  • How can I align the Heatmap header across browsers?

Solution

To create a sticky x-axis header for the Heatmap, follow these steps:

  1. Set up a scrollable wrapper: Place the Heatmap inside a scrollable <div> with overflow-y: auto and a fixed height. Add a separate header <div> above the scrollable area to display the x-axis labels.

  2. Populate the header dynamically: Extract the categories from the Heatmap's data and populate the header <div> dynamically. This ensures the header adapts to variable data.

    javascript
    const headerEl = document.getElementById('heatmap-header');
    months.forEach((m) => {
        const div = document.createElement('div');
        div.className = 'header-label';
        div.textContent = m;
        headerEl.appendChild(div);
    });
  3. Hide duplicate x-axis labels: Set xAxis.labels.visible: false in the Heatmap configuration to hide the default x-axis labels.

    javascript
    xAxis: {
        labels: {
            visible: false
        }
    }
  4. Synchronize the header with the Heatmap: To align the header and the Heatmap, calculate the left spacer width based on the Heatmap's plot area bounding box. Recalculate this width whenever the Heatmap renders or resizes.

    javascript
    function syncHeader(chart) {
        const plotBox = chart._plotArea.box;
        const headerSpacer = document.querySelector('#header-spacer');
        headerSpacer.style.width = `${plotBox.left}px`;
    }
    
    $("#heatmap").kendoChart({
        render: function(e) {
            syncHeader(e.sender);
        }
    });
  5. Handle responsiveness: Use width: 100%; max-width: 1200px for the wrapper to allow it to shrink on narrow viewports. Attach a $(window).on('resize') handler to call chart.resize() and keep the header aligned.

  6. Create a grouped header: For a two-level grouped header, such as years and months, dynamically create two sets of <div> elements—one for each level—and align them above the Heatmap.

    javascript
    years.forEach((year) => {
        const yearDiv = document.createElement('div');
        yearDiv.className = 'year-label';
        yearDiv.textContent = year;
        yearHeaderEl.appendChild(yearDiv);
    });
    
    months.forEach((month) => {
        const monthDiv = document.createElement('div');
        monthDiv.className = 'month-label';
        monthDiv.textContent = month;
        monthHeaderEl.appendChild(monthDiv);
    });
  7. Ensure cross-browser compatibility: To maintain consistent header alignment in different browsers, test and adjust CSS styles (e.g., box-sizing, padding, margin). Use the modified example linked below for browser-specific adjustments.

Below is a runnable example:

 <style>
      body {
        font-family: Arial, sans-serif;
        padding: 20px;
      }
      #heatmap-wrapper {
        width: 100%;
        max-width: 1200px;
      }
      #heatmap-header {
        display: flex;
        background: #fff;
        border-bottom: 1px solid #ddd;
        font: bold 12px Arial;
        color: #333;
      }
      #heatmap-header .header-label {
        text-align: center;
        flex: 1;
        padding: 6px 0;
      }
      #heatmap-header .header-y-spacer {
        flex: 0 0 200px;
      }
      #heatmap-scroll {
        width: 100%;
        height: 500px;
        overflow-y: auto;
      }
      #heatmap {
        width: 100%;
      }
    </style>

    <div id="heatmap-wrapper">
      <div id="heatmap-header">
        <div class="header-y-spacer">Business Partner</div>
      </div>
      <div id="heatmap-scroll">
        <div id="heatmap"></div>
      </div>
    </div>

    <script>
      const partners = [
        "MEGGITT SAFETY SYSTEMS INC",
        "METTIS AEROSPACE LTD",
        "MINALEX CORP",
        "HONEYWELL INTL INC",
        "ROLLS ROYCE PLC",
        "SAFRAN SA",
        "BAE SYSTEMS PLC",
        "GENERAL ELECTRIC CO",
        "PRATT & WHITNEY",
        "NORTHROP GRUMMAN CORP",
        "RAYTHEON TECHNOLOGIES",
        "LOCKHEED MARTIN CORP",
        "BOEING COMPANY",
        "AIRBUS SE",
        "LEONARDO SPA",
        "THALES GROUP",
        "L3HARRIS TECHNOLOGIES",
        "TEXTRON INC",
        "SPIRIT AEROSYSTEMS",
        "TRIUMPH GROUP INC",
      ];

      const months = [
        "Apr 2025",
        "May 2025",
        "Jun 2025",
        "Jul 2025",
        "Aug 2025",
        "Sep 2025",
        "Oct 2025",
        "Nov 2025",
        "Dec 2025",
        "Jan 2026",
        "Feb 2026",
        "Mar 2026",
        "Apr 2026",
      ];

      const values = {
        "MEGGITT SAFETY SYSTEMS INC": [2, 4, 9, 5, 4, 3, 4, 8, 7, 8, 10, 10, 9],
        "METTIS AEROSPACE LTD": [7, 8, 5, 6, 7, 6, 6, 6, 6, 5, 10, 13, 10],
        "MINALEX CORP": [
          null,
          null,
          2,
          1,
          null,
          null,
          null,
          null,
          null,
          null,
          null,
          null,
          null,
        ],
        "HONEYWELL INTL INC": [5, 3, 7, 8, 6, 9, 4, 5, 11, 7, 8, 6, 4],
        "ROLLS ROYCE PLC": [10, 9, 8, 7, 6, 5, 4, 3, 2, 6, 9, 12, 11],
        "SAFRAN SA": [3, 5, 4, 6, 8, 7, 9, 10, 8, 6, 4, 3, 5],
        "BAE SYSTEMS PLC": [6, 7, 8, 9, 10, 11, 12, 10, 8, 7, 6, 5, 4],
        "GENERAL ELECTRIC CO": [4, 6, 5, 3, 7, 8, 6, 5, 9, 11, 13, 10, 8],
        "PRATT & WHITNEY": [8, 7, 6, 5, 4, 3, 5, 7, 9, 10, 11, 9, 7],
        "NORTHROP GRUMMAN CORP": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
        "RAYTHEON TECHNOLOGIES": [9, 8, 7, 6, 5, 4, 3, 2, 4, 6, 8, 10, 12],
        "LOCKHEED MARTIN CORP": [5, 5, 6, 7, 8, 9, 10, 11, 10, 9, 7, 5, 3],
        "BOEING COMPANY": [11, 10, 9, 8, 7, 6, 5, 4, 3, 5, 7, 9, 11],
        "AIRBUS SE": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 11, 10, 9],
        "LEONARDO SPA": [7, 6, 5, 4, 3, 2, 4, 6, 8, 10, 12, 11, 9],
        "THALES GROUP": [2, 3, 6, 8, 10, 12, 11, 9, 7, 5, 3, 2, 4],
        "L3HARRIS TECHNOLOGIES": [6, 8, 10, 12, 11, 9, 7, 5, 3, 4, 6, 8, 10],
        "TEXTRON INC": [4, 3, 2, 5, 7, 9, 11, 13, 10, 8, 6, 4, 2],
        "SPIRIT AEROSYSTEMS": [9, 7, 5, 3, 1, 3, 5, 7, 9, 11, 10, 8, 6],
        "TRIUMPH GROUP INC": [
          null,
          null,
          3,
          5,
          7,
          9,
          8,
          6,
          4,
          2,
          null,
          null,
          null,
        ],
      };

      // Build heatmap data as [xCategory, yCategory, value] arrays
      const heatmapData = [];
      partners.forEach((partner) => {
        months.forEach((month, i) => {
          const val = values[partner][i];
          if (val != null) {
            heatmapData.push([month, partner, val]);
          }
        });
      });

      // Populate header labels
      const headerEl = document.getElementById("heatmap-header");
      months.forEach((m) => {
        const div = document.createElement("div");
        div.className = "header-label";
        div.textContent = m;
        headerEl.appendChild(div);
      });

      function syncHeader() {
        const chart = $("#heatmap").data("kendoChart");
        if (chart && chart._plotArea) {
          const plotBox = chart._plotArea.backgroundBox();
          document.querySelector(".header-y-spacer").style.flex =
            `0 0 ${plotBox.x1}px`;
        }
      }

      function wrapText(e) {
        var maxWidth = window.innerWidth < 768 ? 80 : 150;
        var text = e.value;
        var draw = kendo.drawing;
        var geom = kendo.geometry;
        var rect = e.rect;

        var words = text.split(" ");
        var lines = [];
        var currentLine = words[0];

        // Measure using a temporary text element
        for (var i = 1; i < words.length; i++) {
          var testLine = currentLine + " " + words[i];
          var testEl = new draw.Text(testLine, [0, 0], { font: "12px Arial" });
          var testWidth = testEl.bbox().width();
          if (testWidth > maxWidth) {
            lines.push(currentLine);
            currentLine = words[i];
          } else {
            currentLine = testLine;
          }
        }
        lines.push(currentLine);

        var group = new draw.Group();
        var lineHeight = 14;
        var totalHeight = lines.length * lineHeight;
        var startY = rect.origin.y + (rect.size.height - totalHeight) / 2;

        for (var j = 0; j < lines.length; j++) {
          var textEl = new draw.Text(lines[j], [0, 0], { font: "12px Arial" });
          var textWidth = textEl.bbox().width();
          var x = rect.origin.x + rect.size.width - textWidth - 5;
          var y = startY + j * lineHeight;
          var line = new draw.Text(lines[j], [x, y], { font: "12px Arial" });
          group.append(line);
        }

        return group;
      }

      // Scrollable heatmap chart — x-axis labels hidden (shown in sticky header)
      $("#heatmap").kendoChart({
        chartArea: {
          height: 1000,
        },
        series: [
          {
            type: "heatmap",
            data: heatmapData,
            color: "#4ec94e",
          },
        ],
        xAxis: {
          categories: months,
          labels: { visible: false },
          line: { visible: false },
          majorTicks: { visible: false },
        },
        yAxis: {
          labels: {
            visual: wrapText,
          },
        },
        legend: { visible: false },
        render: syncHeader,
      });

      $(window).on("resize", function () {
        $("#heatmap").data("kendoChart").resize();
      });
    </script>

See Also