← Charts

Line Chart Draw

A line chart draws itself from left to right, the area under it fades in, and each point pops just as the line reaches it.

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-line-chart-draw="…values…" to an element with a height.

Options

Add any of these attributes to the same element.

data-labelsComma-separated labels shown under the chart.
data-colorLine colour. Default #a855f7.
data-durationTime to draw the line. Default 1800 ms.
data-loopRedraw every few seconds.
1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Line Chart Draw: a line chart draws itself from left to right, the area
    // under it fades in, and each point pops as the line reaches it.
    // Usage: <div data-line-chart-draw="12,18,15,26,22,34,40" style="height:220px"></div>
    // Optional: data-labels="Mon,Tue,…", data-color (default #a855f7),
    // data-duration (ms), data-loop (redraw every few seconds).
    var DEFAULTS = { color: '#a855f7', duration: 1800, hold: 2600 };
    var uid = 0;

    var css =
      '.lcd{position:relative;display:flex;flex-direction:column}' +
      '.lcd-plot{position:relative;flex:1;min-height:0}' +
      '.lcd-plot svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}' +
      '.lcd-dot{position:absolute;width:10px;height:10px;margin:-5px 0 0 -5px;border-radius:50%;background:#fff;border:2.5px solid var(--lcd-color);box-sizing:border-box}' +
      '.lcd-labels{display:flex;justify-content:space-between;font-size:12px;opacity:.6;margin-top:8px}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    function build(el) {
      var d = el.dataset;
      var values = el.getAttribute('data-line-chart-draw').split(',').map(Number);
      var labels = d.labels ? d.labels.split(',') : [];
      var color = d.color || DEFAULTS.color;
      var id = 'lcd' + uid++;
      var max = Math.max.apply(null, values), min = Math.min(0, Math.min.apply(null, values));
      var n = values.length;
      var pts = values.map(function (v, i) {
        return [n > 1 ? (i / (n - 1)) * 100 : 50, 100 - ((v - min) / (max - min || 1)) * 92 - 4];
      });
      var line = pts.map(function (p, i) { return (i ? 'L' : 'M') + p[0] + ' ' + p[1]; }).join(' ');
      var area = line + ' L100 100 L0 100 Z';

      el.classList.add('lcd');
      el.style.setProperty('--lcd-color', color);
      el.setAttribute('role', 'img');
      el.setAttribute('aria-label', 'Line chart: ' + values.map(function (v, i) { return (labels[i] ? labels[i] + ' ' : '') + v; }).join(', '));
      el.innerHTML =
        '<div class="lcd-plot" aria-hidden="true">' +
        '<svg viewBox="0 0 100 100" preserveAspectRatio="none">' +
        '<defs><linearGradient id="' + id + '" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="' + color + '" stop-opacity=".28"/><stop offset="1" stop-color="' + color + '" stop-opacity="0"/></linearGradient></defs>' +
        [25, 50, 75].map(function (y) { return '<line x1="0" x2="100" y1="' + y + '" y2="' + y + '" stroke="currentColor" stroke-opacity=".1" vector-effect="non-scaling-stroke"/>'; }).join('') +
        '<path class="lcd-area" d="' + area + '" fill="url(#' + id + ')"/>' +
        '<path class="lcd-line" d="' + line + '" fill="none" stroke="' + color + '" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" vector-effect="non-scaling-stroke"/>' +
        '</svg></div>' +
        (labels.length ? '<div class="lcd-labels" aria-hidden="true">' + labels.map(function (l) { return '<span>' + l.replace(/</g, '&lt;') + '</span>'; }).join('') + '</div>' : '');
      var plot = el.querySelector('.lcd-plot');
      var dots = pts.map(function (p) {
        var dot = document.createElement('span');
        dot.className = 'lcd-dot';
        dot.style.left = p[0] + '%';
        dot.style.top = p[1] + '%';
        plot.appendChild(dot);
        return dot;
      });
      return { el: el, line: el.querySelector('.lcd-line'), area: el.querySelector('.lcd-area'), dots: dots };
    }

    function hide(c) {
      anime.utils.set(anime.svg.createDrawable(c.line), { draw: '0 0' });
      anime.utils.set(c.area, { opacity: 0 });
      anime.utils.set(c.dots, { scale: 0 });
    }

    function draw(c) {
      var d = c.el.dataset;
      var duration = +d.duration || DEFAULTS.duration;
      var n = c.dots.length;
      anime.animate(anime.svg.createDrawable(c.line), { draw: ['0 0', '0 1'], duration: duration, ease: 'inOut(2)' });
      anime.animate(c.area, { opacity: [0, 1], duration: duration * 0.8, delay: duration * 0.3, ease: 'out(2)' });
      anime.animate(c.dots, {
        scale: [0, 1],
        duration: 500,
        // Pop each point roughly when the line reaches it.
        delay: function (_, i) { return n > 1 ? (i / (n - 1)) * duration * 0.85 : 0; },
        ease: 'outBack(2.5)'
      });
      if (d.loop !== undefined && d.loop !== 'false') {
        setTimeout(function () {
          anime.animate(c.dots, { scale: 0, duration: 250, delay: anime.stagger(30) });
          anime.animate(c.area, { opacity: 0, duration: 300 });
          anime.animate(anime.svg.createDrawable(c.line), {
            draw: '1 1', duration: 600, ease: 'in(2)',
            onComplete: function () { hide(c); draw(c); }
          });
        }, duration + DEFAULTS.hold);
      }
    }

    function init() {
      var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
      var io = new IntersectionObserver(function (entries) {
        entries.forEach(function (e) {
          if (!e.isIntersecting) return;
          io.unobserve(e.target);
          draw(e.target._lcd);
        });
      }, { threshold: 0.3 });
      document.querySelectorAll('[data-line-chart-draw]').forEach(function (el) {
        if (el._lcd) return;
        var c = (el._lcd = build(el));
        if (reduced) return;
        hide(c);
        io.observe(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<div data-line-chart-draw="12,18,15,26,22,34,40"
     data-labels="Mon,Tue,Wed,Thu,Fri,Sat,Sun"
     style="height: 220px"></div>