Connecting Real Data

All templates ship with realistic static sample data so you can preview them instantly. Replacing that data with your own source takes only a few lines of code.

## Find the data blocks

Every editable data section is marked with a /* DATA */ comment. Open js/charts.js and search for that marker:

/* DATA ─────────────────────────────────────────── */
const revenueData = {
  months:  ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
  actual:  [42000, 38000, 51000, 62000, 58000, 71000],
  target:  [45000, 45000, 50000, 60000, 60000, 70000],
};
## REST API (fetch)

Replace the static object with an async fetch at page load:

async function loadData() {
  const res  = await fetch('/api/kpis/revenue?period=6m');
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();        // expects same shape as static object
}

document.addEventListener('DOMContentLoaded', async () => {
  const data = await loadData();
  revenueChart.setOption({
    xAxis: { data: data.months },
    series: [
      { data: data.actual },
      { data: data.target },
    ],
  });
});

💡 Use chart.setOption() to update an already-initialised ECharts instance without re-creating it. All animations and resize handlers are preserved.

## Auto-refresh (polling)

For near-real-time dashboards, poll the API on an interval:

const REFRESH_MS = 30_000;   // 30 seconds

async function refresh() {
  const data = await loadData();
  revenueChart.setOption({ series: [{ data: data.actual }] });
  document.getElementById('kpi-revenue').textContent =
    '$' + (data.actual.at(-1) / 1000).toFixed(1) + 'k';
}

refresh();                          // immediate first load
setInterval(refresh, REFRESH_MS);   // then every 30 s
## WebSocket (live streaming)
const ws = new WebSocket('wss://your-api.com/stream/kpis');

ws.addEventListener('message', ({ data }) => {
  const point = JSON.parse(data);    // { timestamp, value }

  // Append new point, drop oldest
  const opt   = revenueChart.getOption();
  const xData = [...opt.xAxis[0].data, point.timestamp].slice(-20);
  const yData = [...opt.series[0].data, point.value].slice(-20);

  revenueChart.setOption({
    xAxis:  [{ data: xData }],
    series: [{ data: yData }],
  });
});
## GraphQL
const QUERY = `
  query RevenueKPIs($period: String!) {
    revenue(period: $period) { months actual target }
  }
`;

const res  = await fetch('/graphql', {
  method:  'POST',
  headers: { 'Content-Type': 'application/json' },
  body:    JSON.stringify({ query: QUERY, variables: { period: '6m' } }),
});
const { data } = await res.json();
## Handling loading and error states
const chartEl = document.getElementById('revenue-chart');

function setLoading(el, loading) {
  if (loading) {
    el.setAttribute('aria-busy', 'true');
    el.style.opacity = '0.4';
  } else {
    el.removeAttribute('aria-busy');
    el.style.opacity = '';
  }
}

try {
  setLoading(chartEl, true);
  const data = await loadData();
  revenueChart.setOption(buildOption(data));
} catch (e) {
  chartEl.innerHTML = '<p class="dt-error">Failed to load data.</p>';
} finally {
  setLoading(chartEl, false);
}
## Expected data shapes

Each template documents its expected data shape in a /* SCHEMA */ comment block at the top of js/charts.js. Match that shape from your API and no other changes are needed.