JSFiddle - React, Tailwind, and code Playground

by Felis Catus

HTML

<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1000 500">
  <defs>
    <linearGradient id="graph_fill" x1="0" x2="0" y1="0" y2="1">
      <stop class="stop1" offset="0%" stop-color="#00b000" stop-opacity="0.5" />
      <stop class="stop2" offset="50%" stop-color="#00b000" stop-opacity="0.1" />
      <stop class="stop2" offset="100%" stop-color="#00b000" stop-opacity="0.05" />
    </linearGradient>
  </defs>
  <path id="axes" d="M0,0 v500h1000 M50,0v470h950" stroke="#000000" fill="none" />
  <path d="M30,47v-100q3,0 10,0t10,10t10,-50t10,-10t10,-50t10,0V500z" stroke="#b00000" fill="red" fill-opacity="0.0" opacity="0" />
  <path id="testpath" d="M0,500v-100c5,0 5,0 10,0s 5,10 10,10s 5,-50 10,-50s5,-10 10,-10s5,-50 10,-50s5,0 10,0V500" stroke="#00b000" fill="url(#graph_fill)" />
  <path id="testpath2" d="" stroke="#00b0b0" fill="none" />
  <g id="yaxis">
  </g>
</svg>

CSS

#yaxis {
  font-family: sans-serif;
  font-size: 10px;
}

JavaScript

function yscale(max) {
  var ret = {};
  ret.y = max;
  ret.zeroes = Math.floor(Math.log10(max));
  ret.top = Math.ceil(max.toString().slice(0, -Math.floor(ret.zeroes - 1)) / 10);
  ret.axis_top = ret.top * Math.pow(10, ret.zeroes);
  ret.thousands = Math.floor(ret.zeroes / 3);
  ret.letter = ['', 'K', 'M', 'G', 'T'][ret.thousands];
  ret.factor = Math.pow(10, ret.thousands * 3);
  return ret;
}

function compose_graph(datapoints, opt) {
  var offset;
  return datapoints.reduce(function(path, pt, idx, arr) {
    if (idx == 0)
      offset = pt[1];
    var x = opt.start.x + (pt[1] - offset) * opt.factor.x;
    var y = opt.start.y + (pt[0]||0) * opt.factor.y;
    path += (idx == 0 ? 'C' + x + ',' + y + ' ' : 'S') + (x - 10) + ',' + y + ' ' + x + ',' + y;
    if (idx == arr.length - 1)
      path += 'V' + opt.start.y;
    return path;
  }, 'M' + opt.start.x + ',' + opt.start.y);
}
var svgns = 'http://www.w3.org/2000/svg';

function fill_axes(datapoints, opt) {
  var max = datapoints.reduce(function(max, next) {
    return Math.max(max, next[0]||0);
  }, 1);
  var ysc = yscale(max);
  console.log('yscale', ysc);
  var step = ysc.top <= 2 ? 0.1 : (ysc.top <= 5 ? 0.5 : 1);
  var gr = document.querySelector('#yaxis');
  gr.innerHTML = '';
  for (var y = 0; y <= ysc.top; y += step) {
    var tx = document.createElementNS(svgns, 'text');
    tx.textContent = (y * Math.pow(10, ysc.zeroes - ysc.thousands * 3)).toFixed(1) + ' ' + ysc.letter + ' -';
    tx.setAttribute('x', opt.start.x);
    tx.setAttribute('y', opt.start.y + opt.factor.y * y * Math.pow(10, ysc.zeroes));
    tx.setAttribute('text-anchor', 'end');
    tx.setAttribute('alignment-baseline', 'middle');
    gr.appendChild(tx);
  }
  var xsc = xscale(datapoints[0][1], datapoints.slice(-1)[0][1]);
  console.log('xscale', xsc);
  var om, od;
  for (var x = xsc.start; x <= xsc.to; x += xsc.step) {
    var dt = new Date(x * 1000),
      label1 = null, label2 = null;
    if (xsc.step < 30 * 86400 && (om !=...