Finance & Investing

Free Stock Chart Widget

A lightweight line/area chart for one symbol, drawn on plain canvas.

A dependency-free stock chart that renders a hand-rolled canvas line or area chart for a single symbol, complete with grid, axes, a last-price label, and a hover crosshair. It is built for bloggers, fund pages, and finance sites that want to show a price series without pulling in a heavy charting library. You supply the data yourself (inline JSON or your own JSON endpoint) and install with a single script tag.

Live demo

Embed this widget

Paste either snippet into your page — a Custom HTML block on WordPress, Shopify, or Squarespace works too. Both snippets update live as you customize, so what you copy is exactly what you configured.

Hosted (recommended)html
<script src="https://hayneslab.dev/widgets/stock-chart.js" data-hl-config='{"symbol":"HLAB","points":[{"t":"2026-07-03T16:00:00","c":44.2},{"t":"2026-07-04T16:00:00","c":43.85},{"t":"2026-07-05T16:00:00","c":44.6},{"t":"2026-07-06T16:00:00","c":45.1},{"t":"2026-07-07T16:00:00","c":44.75},{"t":"2026-07-08T16:00:00","c":46.05},{"t":"2026-07-09T16:00:00","c":46.4},{"t":"2026-07-10T16:00:00","c":45.9},{"t":"2026-07-11T16:00:00","c":47.25},{"t":"2026-07-12T16:00:00","c":47.8},{"t":"2026-07-13T16:00:00","c":47.1},{"t":"2026-07-14T16:00:00","c":48.35},{"t":"2026-07-15T16:00:00","c":49.05},{"t":"2026-07-16T16:00:00","c":48.6},{"t":"2026-07-17T16:00:00","c":49.9},{"t":"2026-07-18T16:00:00","c":50.45},{"t":"2026-07-19T16:00:00","c":49.75},{"t":"2026-07-20T16:00:00","c":51.2},{"t":"2026-07-21T16:00:00","c":51.85},{"t":"2026-07-22T16:00:00","c":51.3},{"t":"2026-07-23T16:00:00","c":52.6},{"t":"2026-07-24T16:00:00","c":52.15},{"t":"2026-07-25T16:00:00","c":53.4},{"t":"2026-07-26T16:00:00","c":54.1},{"t":"2026-07-27T16:00:00","c":53.55},{"t":"2026-07-28T16:00:00","c":54.85},{"t":"2026-07-29T16:00:00","c":55.3},{"t":"2026-07-30T16:00:00","c":54.7},{"t":"2026-07-31T16:00:00","c":56.15},{"t":"2026-08-01T16:00:00","c":56.8}]}' async></script>
<!-- Free Stock Chart widget by haynes lab — https://hayneslab.dev/tools/widgets/stock-chart -->
Inline (self-contained)html
<!-- Free Stock Chart widget by haynes lab — https://hayneslab.dev/tools/widgets/stock-chart -->
<script data-hl-config='{"symbol":"HLAB","points":[{"t":"2026-07-03T16:00:00","c":44.2},{"t":"2026-07-04T16:00:00","c":43.85},{"t":"2026-07-05T16:00:00","c":44.6},{"t":"2026-07-06T16:00:00","c":45.1},{"t":"2026-07-07T16:00:00","c":44.75},{"t":"2026-07-08T16:00:00","c":46.05},{"t":"2026-07-09T16:00:00","c":46.4},{"t":"2026-07-10T16:00:00","c":45.9},{"t":"2026-07-11T16:00:00","c":47.25},{"t":"2026-07-12T16:00:00","c":47.8},{"t":"2026-07-13T16:00:00","c":47.1},{"t":"2026-07-14T16:00:00","c":48.35},{"t":"2026-07-15T16:00:00","c":49.05},{"t":"2026-07-16T16:00:00","c":48.6},{"t":"2026-07-17T16:00:00","c":49.9},{"t":"2026-07-18T16:00:00","c":50.45},{"t":"2026-07-19T16:00:00","c":49.75},{"t":"2026-07-20T16:00:00","c":51.2},{"t":"2026-07-21T16:00:00","c":51.85},{"t":"2026-07-22T16:00:00","c":51.3},{"t":"2026-07-23T16:00:00","c":52.6},{"t":"2026-07-24T16:00:00","c":52.15},{"t":"2026-07-25T16:00:00","c":53.4},{"t":"2026-07-26T16:00:00","c":54.1},{"t":"2026-07-27T16:00:00","c":53.55},{"t":"2026-07-28T16:00:00","c":54.85},{"t":"2026-07-29T16:00:00","c":55.3},{"t":"2026-07-30T16:00:00","c":54.7},{"t":"2026-07-31T16:00:00","c":56.15},{"t":"2026-08-01T16:00:00","c":56.8}]}'>
/*!
 * haynes lab — Stock Chart widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/stock-chart
 *
 * DATA: this widget does NOT fetch live market data. You supply the series:
 *   data-hl-config='{"symbol":"ACME","points":[{"t":"2026-07-01T16:00:00","c":44.2}, ...]}'
 * or data-hl-endpoint="https://yoursite.com/prices.json" returning the same JSON.
 * "t" may be an ISO date/datetime string or epoch milliseconds; "c" is the close price.
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-stock-chart';
  var STYLE_ID = 'hlw-stock-chart-styles';

  // Built-in sample (fictional ticker, 30 daily closes) used only when the
  // page supplies neither data-hl-config nor data-hl-endpoint.
  var SAMPLE = {
    symbol: 'HLAB',
    points: [
      { t: '2026-07-03T16:00:00', c: 44.20 }, { t: '2026-07-04T16:00:00', c: 43.85 }, { t: '2026-07-05T16:00:00', c: 44.60 }, { t: '2026-07-06T16:00:00', c: 45.10 },
      { t: '2026-07-07T16:00:00', c: 44.75 }, { t: '2026-07-08T16:00:00', c: 46.05 }, { t: '2026-07-09T16:00:00', c: 46.40 }, { t: '2026-07-10T16:00:00', c: 45.90 },
      { t: '2026-07-11T16:00:00', c: 47.25 }, { t: '2026-07-12T16:00:00', c: 47.80 }, { t: '2026-07-13T16:00:00', c: 47.10 }, { t: '2026-07-14T16:00:00', c: 48.35 },
      { t: '2026-07-15T16:00:00', c: 49.05 }, { t: '2026-07-16T16:00:00', c: 48.60 }, { t: '2026-07-17T16:00:00', c: 49.90 }, { t: '2026-07-18T16:00:00', c: 50.45 },
      { t: '2026-07-19T16:00:00', c: 49.75 }, { t: '2026-07-20T16:00:00', c: 51.20 }, { t: '2026-07-21T16:00:00', c: 51.85 }, { t: '2026-07-22T16:00:00', c: 51.30 },
      { t: '2026-07-23T16:00:00', c: 52.60 }, { t: '2026-07-24T16:00:00', c: 52.15 }, { t: '2026-07-25T16:00:00', c: 53.40 }, { t: '2026-07-26T16:00:00', c: 54.10 },
      { t: '2026-07-27T16:00:00', c: 53.55 }, { t: '2026-07-28T16:00:00', c: 54.85 }, { t: '2026-07-29T16:00:00', c: 55.30 }, { t: '2026-07-30T16:00:00', c: 54.70 },
      { t: '2026-07-31T16:00:00', c: 56.15 }, { t: '2026-08-01T16:00:00', c: 56.80 }
    ]
  };

  function findScript() {
    return document.currentScript ||
      document.querySelector('script[src*="widgets/stock-chart.js"]');
  }

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: relative; max-width: 640px; margin: 0 auto; padding: 18px 20px 28px;' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;' +
      ' font-size: 14px; line-height: 1.4; color: #1f2937; background: #ffffff;' +
      ' border: 1px solid #e5e7eb; border-radius: 12px; }' +
      '.' + PREFIX + '__header { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }' +
      '.' + PREFIX + '__symbol { font-weight: 700; font-size: 16px; color: #111827; letter-spacing: 0.02em; }' +
      '.' + PREFIX + '__price { font-weight: 600; font-size: 16px; color: #111827; }' +
      '.' + PREFIX + '__change { font-weight: 600; font-size: 13px; }' +
      '.' + PREFIX + '__change--up { color: #16a34a; }' +
      '.' + PREFIX + '__change--down { color: #dc2626; }' +
      '.' + PREFIX + '__body { position: relative; }' +
      '.' + PREFIX + '__canvas { display: block; width: 100%; cursor: crosshair; }' +
      '.' + PREFIX + '__tip { position: absolute; display: none; z-index: 2; pointer-events: none;' +
      ' transform: translate(-50%, -100%); white-space: nowrap; padding: 4px 8px;' +
      ' font-size: 12px; color: #f9fafb; background: #111827; border-radius: 6px; }' +
      '.' + PREFIX + '__credit { position: absolute; right: 10px; bottom: 6px; font-size: 11px;' +
      ' display: inline-flex; align-items: center; gap: 4px; color: #9ca3af; text-decoration: none; }' +
      '.' + PREFIX + '__credit svg { display: block; }' +
      '.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }';
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }

  function el(tag, className, text) {
    var node = document.createElement(tag);
    if (className) node.className = className;
    if (text != null) node.textContent = text;
    return node;
  }

  function logoSvg() {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 32 32');
    svg.setAttribute('width', '13');
    svg.setAttribute('height', '13');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    function circle(cx, cy, r, attrs) {
      var c = document.createElementNS(svgNS, 'circle');
      c.setAttribute('cx', cx); c.setAttribute('cy', cy); c.setAttribute('r', r);
      for (var key in attrs) c.setAttribute(key, attrs[key]);
      svg.appendChild(c);
    }
    circle(16, 16, 16, { fill: '#0f0a0a' });
    circle(16, 16, 12, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.3 });
    circle(16, 16, 9, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.5 });
    circle(16, 16, 6, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1.2, opacity: 0.9 });
    circle(16, 16, 3, { fill: '#06b6d4' });
    circle(24.5, 7.5, 2, { fill: '#f97316' });
    circle(7, 16, 1.8, { fill: '#f97316' });
    circle(20, 20, 1.5, { fill: '#f97316' });
    return svg;
  }

  function parseTime(t) {
    var d = (typeof t === 'number') ? new Date(t) : new Date(String(t));
    return isNaN(d.getTime()) ? null : d;
  }

  function fmtPrice(v) {
    return Number(v).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  }

  function fmtAxis(v) {
    var abs = Math.abs(v);
    var opts = abs >= 1000 ? { maximumFractionDigits: 0 }
      : abs >= 100 ? { maximumFractionDigits: 1 }
      : { minimumFractionDigits: 2, maximumFractionDigits: 2 };
    return Number(v).toLocaleString('en-US', opts);
  }

  function normalize(raw, source) {
    if (!raw || typeof raw !== 'object' || !Array.isArray(raw.points)) {
      console.warn('[haynes lab stock-chart] ' + source + ' must be JSON like {"symbol":"ACME","points":[{"t","c"},...]}.');
      return null;
    }
    var points = [];
    raw.points.forEach(function (p) {
      if (p && isFinite(Number(p.c))) points.push({ t: p.t, c: Number(p.c) });
    });
    if (points.length < 2) {
      console.warn('[haynes lab stock-chart] ' + source + ' needs at least 2 valid points ({t, c}).');
      return null;
    }
    return { symbol: raw.symbol ? String(raw.symbol) : 'STOCK', points: points };
  }

  function readConfig(script) {
    var raw = script.getAttribute('data-hl-config');
    if (!raw) return null;
    try {
      return normalize(JSON.parse(raw), 'data-hl-config');
    } catch (e) {
      console.warn('[haynes lab stock-chart] data-hl-config is not valid JSON.', e);
      return null;
    }
  }

  function sampleData() {
    console.warn('[haynes lab stock-chart] No data-hl-config or data-hl-endpoint supplied — showing built-in sample data (fictional ticker "HLAB").');
    return SAMPLE;
  }

  function load(script, done) {
    var endpoint = script.getAttribute('data-hl-endpoint');
    var local = readConfig(script);
    if (endpoint && window.fetch) {
      fetch(endpoint).then(function (res) {
        if (!res.ok) throw new Error('HTTP ' + res.status);
        return res.json();
      }).then(function (data) {
        done(normalize(data, 'data-hl-endpoint response') || local || sampleData());
      }).catch(function (e) {
        console.warn('[haynes lab stock-chart] Failed to load data-hl-endpoint "' + endpoint + '".', e);
        done(local || sampleData());
      });
      return;
    }
    done(local || sampleData());
  }

  function roundRectPath(c, x, y, w, h, r) {
    c.beginPath();
    c.moveTo(x + r, y);
    c.arcTo(x + w, y, x + w, y + h, r);
    c.arcTo(x + w, y + h, x, y + h, r);
    c.arcTo(x, y + h, x, y, r);
    c.arcTo(x, y, x + w, y, r);
    c.closePath();
  }

  function build(script, data) {
    var lineColor = script.getAttribute('data-hl-line-color') || '#f97316';
    var fill = script.getAttribute('data-hl-fill') !== 'false';
    var height = parseInt(script.getAttribute('data-hl-height'), 10);
    if (isNaN(height) || height < 120) height = 260;

    injectStyles();

    var points = data.points, n = points.length;
    var first = points[0].c, last = points[n - 1].c;
    var diff = last - first;
    var pct = first !== 0 ? (diff / first) * 100 : 0;
    var up = diff >= 0;

    var t0 = parseTime(points[0].t), t1 = parseTime(points[n - 1].t);
    var intraday = !!(t0 && t1) && (t1.getTime() - t0.getTime()) <= 2 * 24 * 3600 * 1000;

    function fmtAxisTime(t) {
      var d = parseTime(t);
      if (!d) return String(t);
      return intraday
        ? d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })
        : d.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    }
    function fmtTipTime(t) {
      var d = parseTime(t);
      if (!d) return String(t);
      var date = d.toLocaleDateString('en-US', intraday ? { month: 'short', day: 'numeric' } : { month: 'short', day: 'numeric', year: 'numeric' });
      return intraday ? date + ' ' + d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }) : date;
    }

    var root = el('div', PREFIX);
    root.setAttribute('role', 'region');
    root.setAttribute('aria-label', 'Stock chart');

    var header = el('div', PREFIX + '__header');
    header.appendChild(el('span', PREFIX + '__symbol', data.symbol));
    header.appendChild(el('span', PREFIX + '__price', fmtPrice(last)));
    var sign = up ? '+' : '−';
    header.appendChild(el('span', PREFIX + '__change ' + PREFIX + (up ? '__change--up' : '__change--down'),
      (up ? '▲ ' : '▼ ') + sign + fmtPrice(Math.abs(diff)) + ' (' + sign + Math.abs(pct).toFixed(2) + '%)'));
    root.appendChild(header);

    var body = el('div', PREFIX + '__body');
    var canvas = el('canvas', PREFIX + '__canvas');
    canvas.setAttribute('role', 'img');
    canvas.setAttribute('aria-label', 'Line chart of ' + data.symbol + ' prices, ' + n +
      ' points from ' + fmtTipTime(points[0].t) + ' to ' + fmtTipTime(points[n - 1].t) + '. Last price ' + fmtPrice(last) + '.');
    var tip = el('div', PREFIX + '__tip');
    tip.setAttribute('aria-hidden', 'true');
    body.appendChild(canvas);
    body.appendChild(tip);
    root.appendChild(body);

    var credit = el('a', PREFIX + '__credit');
    credit.appendChild(logoSvg());
    credit.appendChild(document.createTextNode('Powered by haynes lab'));
    credit.href = 'https://hayneslab.dev/tools/widgets/stock-chart';
    credit.target = '_blank';
    credit.rel = 'noopener';
    root.appendChild(credit);

    if (script.parentNode && script.parentNode.nodeName !== 'HEAD') {
      script.insertAdjacentElement('afterend', root);
    } else {
      document.body.appendChild(root);
    }

    var ctx = canvas.getContext('2d');
    var layout = null;
    var hoverIndex = -1;

    function draw() {
      var w = body.clientWidth || 600;
      var dpr = window.devicePixelRatio || 1;
      canvas.width = Math.round(w * dpr);
      canvas.height = Math.round(height * dpr);
      canvas.style.width = w + 'px';
      canvas.style.height = height + 'px';
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      ctx.clearRect(0, 0, w, height);
      ctx.font = '11px system-ui, -apple-system, "Segoe UI", Roboto, sans-serif';

      var min = Infinity, max = -Infinity, i;
      for (i = 0; i < n; i++) {
        if (points[i].c < min) min = points[i].c;
        if (points[i].c > max) max = points[i].c;
      }
      if (min === max) { min -= 1; max += 1; }
      var pad = (max - min) * 0.06;
      min -= pad; max += pad;

      var padT = 12, padR = 10, padB = 24, padL = 8;
      for (i = 0; i <= 4; i++) {
        var tw = ctx.measureText(fmtAxis(min + (max - min) * i / 4)).width + 14;
        if (tw > padL) padL = tw;
      }
      var plotW = w - padL - padR, plotH = height - padT - padB;
      if (plotW < 40 || plotH < 40) return;

      function X(idx) { return padL + plotW * idx / (n - 1); }
      function Y(v) { return padT + plotH * (1 - (v - min) / (max - min)); }
      layout = { X: X, Y: Y, padL: padL, plotW: plotW };

      // horizontal grid + y-axis labels
      ctx.lineWidth = 1;
      ctx.strokeStyle = '#eef0f3';
      ctx.fillStyle = '#9ca3af';
      ctx.textAlign = 'left';
      ctx.textBaseline = 'middle';
      for (i = 0; i <= 4; i++) {
        var v = min + (max - min) * i / 4, gy = Y(v);
        ctx.beginPath(); ctx.moveTo(padL, gy); ctx.lineTo(padL + plotW, gy); ctx.stroke();
        ctx.fillText(fmtAxis(v), 4, gy);
      }

      // x-axis labels (deduped, edge-aligned)
      var ticks = Math.min(5, n), seen = {};
      ctx.textBaseline = 'top';
      for (i = 0; i < ticks; i++) {
        var idx = Math.round(i * (n - 1) / (ticks - 1 || 1));
        var label = fmtAxisTime(points[idx].t);
        if (seen[label]) continue;
        seen[label] = true;
        ctx.textAlign = i === 0 ? 'left' : i === ticks - 1 ? 'right' : 'center';
        ctx.fillText(label, X(idx), padT + plotH + 7);
      }

      // optional area fill
      if (fill) {
        ctx.beginPath();
        ctx.moveTo(X(0), Y(points[0].c));
        for (i = 1; i < n; i++) ctx.lineTo(X(i), Y(points[i].c));
        ctx.lineTo(X(n - 1), padT + plotH); ctx.lineTo(X(0), padT + plotH);
        ctx.closePath();
        ctx.save(); ctx.globalAlpha = 0.12; ctx.fillStyle = lineColor; ctx.fill(); ctx.restore();
      }

      // price line
      ctx.beginPath();
      ctx.moveTo(X(0), Y(points[0].c));
      for (i = 1; i < n; i++) ctx.lineTo(X(i), Y(points[i].c));
      ctx.strokeStyle = lineColor; ctx.lineWidth = 2;
      ctx.lineJoin = 'round'; ctx.lineCap = 'round';
      ctx.stroke();

      // last-price dashed line + chip
      var ly = Y(last);
      ctx.save();
      ctx.setLineDash([4, 4]); ctx.lineWidth = 1; ctx.strokeStyle = lineColor;
      ctx.beginPath(); ctx.moveTo(padL, ly); ctx.lineTo(padL + plotW, ly); ctx.stroke();
      ctx.restore();
      var ltext = fmtPrice(last);
      var cw = ctx.measureText(ltext).width + 12, ch = 18;
      var cx = padL + plotW - cw;
      var cy = Math.max(1, Math.min(height - 1 - ch, ly - ch / 2));
      ctx.fillStyle = lineColor;
      roundRectPath(ctx, cx, cy, cw, ch, 4);
      ctx.fill();
      ctx.fillStyle = '#ffffff';
      ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
      ctx.fillText(ltext, cx + cw / 2, cy + ch / 2 + 0.5);

      // hover crosshair
      if (hoverIndex >= 0) {
        var hx = X(hoverIndex), hy = Y(points[hoverIndex].c);
        ctx.save();
        ctx.setLineDash([3, 3]); ctx.lineWidth = 1; ctx.strokeStyle = '#9ca3af';
        ctx.beginPath(); ctx.moveTo(hx, padT); ctx.lineTo(hx, padT + plotH); ctx.stroke();
        ctx.restore();
        ctx.beginPath(); ctx.arc(hx, hy, 4, 0, Math.PI * 2);
        ctx.fillStyle = lineColor; ctx.fill();
        ctx.lineWidth = 2; ctx.strokeStyle = '#ffffff'; ctx.stroke();
      }
    }

    canvas.addEventListener('mousemove', function (e) {
      if (!layout) return;
      var rect = canvas.getBoundingClientRect();
      var idx = Math.round((e.clientX - rect.left - layout.padL) / layout.plotW * (n - 1));
      idx = Math.max(0, Math.min(n - 1, idx));
      if (idx !== hoverIndex) {
        hoverIndex = idx;
        draw();
      }
      var p = points[idx], tx = layout.X(idx);
      tip.textContent = fmtTipTime(p.t) + ' · ' + fmtPrice(p.c);
      tip.style.display = 'block';
      tip.style.left = tx + 'px';
      tip.style.top = (layout.Y(p.c) - 10) + 'px';
      var half = tip.offsetWidth / 2;
      if (tx - half < 0) tip.style.left = half + 'px';
      else if (tx + half > rect.width) tip.style.left = (rect.width - half) + 'px';
    });
    canvas.addEventListener('mouseleave', function () {
      hoverIndex = -1;
      tip.style.display = 'none';
      draw();
    });

    var raf = 0;
    window.addEventListener('resize', function () {
      if (raf) return;
      raf = window.requestAnimationFrame(function () {
        raf = 0;
        draw();
      });
    });

    draw();
  }

  function init() {
    var script = findScript();
    if (!script) {
      console.warn('[haynes lab stock-chart] Could not locate its own script tag. Nothing rendered.');
      return;
    }
    load(script, function (data) {
      build(script, data);
    });
  }

  if (document.body) {
    init();
  } else {
    document.addEventListener('DOMContentLoaded', init);
  }
})();

</script>

Configuration

Every option is an attribute on the script tag — the same attributes the customizer above exposes. All attributes are optional unless marked required — defaults apply when omitted.

AttributeDefaultDescription
data-hl-config(none)JSON like {"symbol":"ACME","points":[{"t":"2026-07-01T16:00:00","c":44.2},...]}. You own the data — t is an ISO date/datetime or epoch ms, c is the price. Omit to show built-in sample data.
data-hl-endpoint(none)Optional URL the widget fetches for the same {"symbol","points"} JSON shape. Takes precedence over data-hl-config; on failure it falls back to data-hl-config, then the sample data.
data-hl-line-color"#f97316"Any CSS color for the price line, area fill, and last-price label.
data-hl-fill"true"Set to "false" to draw a plain line without the shaded area under it.
data-hl-height"260"Canvas height in pixels. The width always matches the container.

Frequently asked questions

How do I install the stock chart?

Copy the embed snippet and paste it where you want the chart to appear on your page. The script renders the chart right after the tag. Put your price series in data-hl-config (or point data-hl-endpoint at your own JSON) — the widget never fetches market data on its own, so you control exactly what is shown.

Where does the price data come from?

You supply it. Pass a JSON object like {"symbol":"ACME","points":[{"t":"2026-07-01T16:00:00","c":44.2}]} in data-hl-config, or set data-hl-endpoint to a URL that returns the same JSON (for example, a small file your backend updates daily). t can be an ISO date/datetime or epoch milliseconds; c is the price. With neither attribute, the widget renders built-in sample data so you can preview it.

How do I customize the look?

Use the data-hl-* attributes on the script tag: data-hl-line-color changes the line and area color, data-hl-fill="false" draws a plain line without the shaded area, and data-hl-height sets the canvas height in pixels. The width always adapts to its container.

Does it work on WordPress, Shopify, or Squarespace?

Yes. The widget is a single dependency-free vanilla JS file, so it runs anywhere you can paste a script tag — WordPress (custom HTML block), Shopify (theme.liquid or a custom section), Squarespace (code block), Webflow, or a plain HTML site.

More free widgets

Need a custom widget?

We build custom widgets, integrations, and whole products. Tell us what your site needs and we'll scope it.

Get in Touch