function lineGraph(parent, xAccessor, yAccessor) {
  // Constant size definitions TODO: this could well be improved and calculated...
  const width = 620;
  const height = 420;
  const gutter = 40;
  const pixelsPerTick = 30;

  /**
   * Creates an object that contatins transform functions that:
   *   transforms numeric data into coordinate space, linearly
   *   transforms coordinates into numeric data, linearly
   */
  function numericTransformer(dataMin, dataMax, pxMin, pxMax) {
    var dataDiff = dataMax - dataMin,
      pxDiff = pxMax - pxMin,
      dataRatio = pxDiff / dataDiff,
      coordRatio = dataDiff / pxDiff;

    return {
      // transforms a data point to a coordinate point
      toCoord: function(data) {
        return (data - dataMin) * dataRatio + pxMin;
      },
      // transforms a coord point to a data point
      toData: function(coord) {
        return (coord - pxMin) * coordRatio + dataMin;
      }
    };
  }

  /**
   * Renders an axis.
   *   orientation = 'x' or 'y'
   *   transform = a function for transforming px into data for labeling/creating tick marks
   */
  function axisRenderer(orientation, transform) {
    var axisGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
    var axisPath = document.createElementNS(
      "http://www.w3.org/2000/svg",
      "path"
    );

    axisGroup.setAttribute("class", orientation + "-axis");

    var xMin = gutter;
    var xMax = width - gutter;
    var yMin = height - gutter;
    var yMax = gutter;

    if (orientation === "x") {
      axisPath.setAttribute(
        "d",
        "M " + xMin + " " + yMin + " L " + xMax + " " + yMin
      );

      // generate labels
      for (var i = xMin; i <= xMax; i++) {
        if ((i - xMin) % (pixelsPerTick*3) === 0 && i !== xMin) {
          var text = document.createElementNS(
            "http://www.w3.org/2000/svg",
            "text"
          );
          // primitive formatting
          text.innerHTML = new Date(Math.floor(transform(i))).toLocaleTimeString();
          text.setAttribute("x", i);
          text.setAttribute("y", yMin);
          // offset the text by 1 em
          text.setAttribute("dy", "1em");
          axisGroup.appendChild(text);
        }
      }
    } else {
      axisPath.setAttribute(
        "d",
        "M " + xMin + " " + yMin + " L " + xMin + " " + yMax
      );

      // generate labels
      for (var i = yMax; i <= yMin; i++) {
        if ((i - yMin) % pixelsPerTick === 0 && i !== yMin) {
          var tickGroup = document.createElementNS(
            "http://www.w3.org/2000/svg",
            "g"
          );
          var gridLine = document.createElementNS(
            "http://www.w3.org/2000/svg",
            "path"
          );
          text = document.createElementNS("http://www.w3.org/2000/svg", "text");
          // primitive formatting
          text.innerHTML = Math.floor(transform(i));
          text.setAttribute("x", xMin);
          text.setAttribute("y", i);
          // offset the text labels to align with grid line and keeping it to the left of the y-axis
          text.setAttribute("dx", "-.5em");
          text.setAttribute("dy", ".3em");

          gridLine.setAttribute(
            "d",
            "M " + xMin + " " + i + " L " + xMax + " " + i
          );

          tickGroup.appendChild(gridLine);
          tickGroup.appendChild(text);
          axisGroup.appendChild(tickGroup);
        }
      }
    }

    axisGroup.appendChild(axisPath);
    parent.appendChild(axisGroup);
  }

  /**
   * Renders a line
   */
  function lineRenderer(xAccessor, yAccessor, xTransform, yTransform) {
    var line = document.createElementNS("http://www.w3.org/2000/svg", "path");

    xAccessor.reset();
    yAccessor.reset();
    if (!xAccessor.hasNext() || !yAccessor.hasNext()) {
      return;
    }

    var pathString =
      "M " + xTransform(xAccessor.next()) + " " + yTransform(yAccessor.next());
    while (xAccessor.hasNext() && yAccessor.hasNext()) {
      pathString +=
        " L " +
        xTransform(xAccessor.next()) +
        " " +
        yTransform(yAccessor.next());
    }

    line.setAttribute("class", "series");
    line.setAttribute("d", pathString);

    parent.appendChild(line);
  }

  /**
   * Renders data point circles + text labels
   */
  function pointRenderer(xAccessor, yAccessor, xTransform, yTransform) {
    var pointGroup = document.createElementNS(
      "http://www.w3.org/2000/svg",
      "g"
    );

    pointGroup.setAttribute("class", "data-points");

    xAccessor.reset();
    yAccessor.reset();
    if (!xAccessor.hasNext() || !yAccessor.hasNext()) {
      return;
    }

    while (xAccessor.hasNext() && yAccessor.hasNext()) {
      var xDataValue = xAccessor.next();
      var x = xTransform(xDataValue);
      var yDataValue = yAccessor.next();
      var y = yTransform(yDataValue);

      var circle = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "circle"
      );
      circle.setAttribute("cx", x);
      circle.setAttribute("cy", y);
      circle.setAttribute("r", "4");

      var text = document.createElementNS("http://www.w3.org/2000/svg", "text");
      // primitive formatting
      text.innerHTML = new Date(Math.floor(xDataValue)).toLocaleTimeString() + " / " + Math.floor(yDataValue);
      text.setAttribute("x", x);
      text.setAttribute("y", y);

      text.setAttribute("dx", "1em");
      text.setAttribute("dy", "-.7em");

      pointGroup.appendChild(circle);
      pointGroup.appendChild(text);
    }

    parent.appendChild(pointGroup);
  }

  // perform the rendering
  xTransform = numericTransformer(
    xAccessor.min(),
    xAccessor.max(),
    0 + gutter,
    width - gutter
  );
  // NOTE: for y... have to reverse coordinate space
  yTransform = numericTransformer(
    yAccessor.min(),
    yAccessor.max(),
    height - gutter,
    0 + gutter
  );

  axisRenderer("x", xTransform.toData);
  axisRenderer("y", yTransform.toData);

  lineRenderer(xAccessor, yAccessor, xTransform.toCoord, yTransform.toCoord);
  pointRenderer(xAccessor, yAccessor, xTransform.toCoord, yTransform.toCoord);
}

// Final render function
function renderGraphSvg(dataArray, renderId) {
  var figure = document.getElementById(renderId);
  while (figure.hasChildNodes()) {
    figure.removeChild(figure.lastChild);
  }
  //console.log(dataArray);
  var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  svg.setAttribute("viewBox", "0 0 640 440");
  svg.setAttribute("preserveAspectRatio", "xMidYMid meet");

  lineGraph(
    svg,
    // time accessor
    (function(data, min, max) {
      var i = 0;
      return {
        hasNext: function() {
          return i < data.length;
        },
        next: function() {
          return data[i++].x;
        },
        reset: function() {
          i = 0;
        },
        min: function() {
          return min;
        },
        max: function() {
          return max;
        }
      };
    })(
      dataArray,
      Math.min.apply(
        Math,
        dataArray.map(function(o) {
          return o.x;
        })
      ),
      Math.max.apply(
        Math,
        dataArray.map(function(o) {
          return o.x;
        })
      )
    ),
    // value accessor
    (function(data, min, max) {
      var i = 0;
      return {
        hasNext: function() {
          return i < data.length;
        },
        next: function() {
          return data[i++].y;
        },
        reset: function() {
          i = 0;
        },
        min: function() {
          return min;
        },
        max: function() {
          return max;
        }
      };
    })(
      dataArray,
      Math.min.apply(
        Math,
        dataArray.map(function(o) {
          return o.y;
        })
      ),
      Math.max.apply(
        Math,
        dataArray.map(function(o) {
          return o.y;
        })
      )
    )
  );

  figure.appendChild(svg);
}
