Fading Coder

An Old Coder’s Final Dance

Home > Tech > Content

Fix ECharts "Cannot get DOM width or height" in hidden or zero‑sized containers

Tech 1

When a chart is initialized while its container has no measurable size (for example, in side a hidden tab with display:none or a div without explicit width/height), ECharts logs "Can’t get dom width or height!" and the chart remains blank. This commonly happens when rendering multiple charts across tabs where only the first (visible) chart renders and the others stay empty.

Below are practical ways to avoid this by ensuring the container has a size before initialization, deferring initialization until the chart is visible, or resizing after it becomes visible.

Quick fix: size the container and listen for resize

  • Give the chart container explicit width and height before callling echarts.init
  • Update the container and chart on window resize

Vanilla JS version:

function ensureContainerSize(el) {
  el.style.width = window.innerWidth + 'px';
  el.style.height = Math.floor(window.innerHeight * 0.8) + 'px';
}

function initChartWithSizing(el) {
  ensureContainerSize(el);
  const chart = echarts.init(el);

  window.addEventListener('resize', () => {
    ensureContainerSize(el);
    chart.resize();
  });

  return chart;
}

jQuery-style resize hook (if you already use jQuery):

function sizeContainer($el) {
  $el.css({
    width: window.innerWidth + 'px',
    height: (window.innerHeight * 0.8) + 'px'
  });
}

function initChartWithSizingJQ($el) {
  sizeContainer($el);
  const chart = echarts.init($el[0]);

  $(window).on('resize', () => {
    sizeContainer($el);
    chart.resize();
  });

  return chart;
}

Full example: build and render a chart safely

The following example transforms input data, sizes the container, initializes the chart, and handles resize. Variable names and control flow are refactored for clarity.

// Example data shape assumption: data[0] is an object keyed by 'YYYY-MM'
// with fields like xfsr, xfbk, shouldPay, pay.
function renderMainChart(dataArray, containerId) {
  const host = document.getElementById(containerId);

  // Ensure the container is measurable before init
  function setHostSize() {
    host.style.width = window.innerWidth + 'px';
    host.style.height = Math.floor(window.innerHeight * 0.8) + 'px';
  }
  setHostSize();

  // Prepare series data
  const src = (dataArray && dataArray[0]) ? dataArray[0] : {};
  const labels = Object.keys(src).sort((a, b) => {
    const [ay, am] = a.split('-').map(Number);
    const [by, bm] = b.split('-').map(Number);
    return ay === by ? am - bm : ay - by;
  });

  const seriesBuckets = {
    perfTotal: [],         // Total performance = xfbk + xfsr
    receivables: [],       // shouldPay
    registrationIncome: [],// xfsr
    totalCost: [],         // pay
    grossProfit: [],       // (xfbk + xfsr) - pay
    growthCurve: []        // using total performance for line
  };

  for (let i = 0; i < labels.length; i++) {
    const k = labels[i];
    const item = src[k];
    if (item) {
      const feeBack = Math.round(item.xfbk || 0);
      const feeIncome = Math.round(item.xfsr || 0);
      const payable = Math.round(item.pay || 0);
      const shouldPay = Math.round(item.shouldPay || 0);
      const total = feeBack + feeIncome;

      seriesBuckets.registrationIncome.push(feeIncome);
      seriesBuckets.perfTotal.push(total);
      seriesBuckets.receivables.push(shouldPay);
      seriesBuckets.totalCost.push(payable);
      seriesBuckets.grossProfit.push(total - payable);
      seriesBuckets.growthCurve.push(total);
    } else {
      seriesBuckets.registrationIncome.push(0);
      seriesBuckets.perfTotal.push(0);
      seriesBuckets.receivables.push(0);
      seriesBuckets.totalCost.push(0);
      seriesBuckets.grossProfit.push(0);
      seriesBuckets.growthCurve.push(0);
    }
  }

  // Initialize chart
  const chart = echarts.init(host);

  // Auto-resize
  window.addEventListener('resize', () => {
    setHostSize();
    chart.resize();
  });

  const option = {
    tooltip: { show: true, trigger: 'axis' },
    toolbox: {
      feature: {
        dataView: { show: true, readOnly: true, title: 'Data view' },
        magicType: { show: true, type: ['line', 'bar'] },
        saveAsImage: { show: true }
      }
    },
    title: { text: '' },
    legend: {
      data: [
        'Total performance',
        'Total receivables',
        'Registration income',
        'Total cost',
        'Gross profit',
        'Performance growth ratio'
      ]
    },
    xAxis: [
      {
        type: 'category',
        name: 'Month',
        axisLabel: { show: true },
        data: labels
      }
    ],
    yAxis: {
      type: 'value',
      name: 'Amount of money',
      min: 0,
      axisLabel: { formatter: '${value}' }
    },
    series: [
      { name: 'Total performance', type: 'bar', data: seriesBuckets.perfTotal },
      { name: 'Total receivables', type: 'bar', data: seriesBuckets.receivables },
      { name: 'Registration income', type: 'bar', data: seriesBuckets.registrationIncome },
      { name: 'Total cost', type: 'bar', data: seriesBuckets.totalCost },
      { name: 'Gross profit', type: 'bar', data: seriesBuckets.grossProfit },
      { name: 'Performance growth ratio', type: 'line', data: seriesBuckets.perfTotal }
    ],
    color: ['#f68484', '#75b9e6', '#87b87f', '#ae91e1', '#f6ac61', '#c4ccd3']
  };

  chart.setOption(option);
  return chart;
}

Tabbed layouts: initialize when visible or resize on show

Charts inside hiddden tabs typically have zero width/height. Either defer initialization until the tab is shown or call chart.resize() when the tab becomes visible.

Bootstrap example (shown.bs.tab):

const charts = new Map(); // tabId -> echarts instance

$('a[data-bs-toggle="tab"]').on('shown.bs.tab', function (e) {
  const targetSelector = $(e.target).attr('href'); // e.g. '#tab-2'
  const panel = document.querySelector(targetSelector);
  const el = panel.querySelector('.chart-host');

  if (!charts.has(targetSelector)) {
    // initialize only once when the panel is visible
    const chart = initChartWithSizing(el);
    chart.setOption(/* your option here */);
    charts.set(targetSelector, chart);
  } else {
    charts.get(targetSelector).resize();
  }
});

Generic approach with IntersectionObserver (no framework dependancy):

function lazyInitEChart(chartEl, option) {
  let instance = null;
  const obs = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        if (!instance) {
          instance = initChartWithSizing(chartEl);
          instance.setOption(option);
        } else {
          instance.resize();
        }
      }
    });
  }, { threshold: 0.1 });

  obs.observe(chartEl);
}

Alternative: supply width/height to echarts.init (ECharts 5+)

If you know the intended size even while hidden, you can pass it to echarts.init so it doesn’t rely on DOM measurements:

// ECharts 5+ accepts width/height in the third argument
const chart = echarts.init(containerEl, null, { width: 800, height: 480 });
chart.setOption(option);

// Later, when layout changes, call resize()
chart.resize();

CSS sizing that works reliably

  • Ensure the container and its ancestors have determinable sizes. Percentage heights require the parent chain to have explicit heights.
  • If you want a chart to fill a region:
.chart-wrapper {
  position: relative;
  width: 100%;
  height: 60vh; /* or a fixed px height */
}

.chart-host {
  width: 100%;
  height: 100%;
}
<div class="chart-wrapper">
  <div id="main" class="chart-host"></div>
</div>

Common pitfalls that cause the warning

  • The chart is initialized inside an element with display:none
  • The container lacks explicit size (e.g., height: auto with no content)
  • Parent containers collapse to 0 height due to CSS
  • The chart is mounted before the DOM is ready or before layout finishes; use requestAnimationFrame or a tab shown event to defer

Using these patterns, you can ensure ECharts always measures a non-zero container and avoid "Can’t get dom width or height!" while rendering in multi-tab pages.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.