registerKeyboardHandler = function(callback) {
  d3.select(window).on("keydown", callback);
};

function simpleGraph(elem, options) {
  var cx = 600, cy = 300;

  if (arguments.length) {
    elem = d3.select(elem);
    cx = elem.property("clientWidth");
    cy = elem.property("clientHeight");
  }

  var vis, plot, title, xlabel, ylabel, points, xtic, ytic,
      options = options || {
        "xmax": 60, "xmin": 0,
        "ymax": 40, "ymin": 0, 
        "title": "Simple Graph1",
        "xlabel": "X Axis",
        "ylabel": "Y Axis"
      },
      padding = {
       "top":    options.title  ? 40 : 20,
       "right":                 30,
       "bottom": options.xlabel ? 60 : 10,
       "left":   options.ylabel ? 70 : 45
      },

      size = {
        "width":  cx - padding.left - padding.right,
        "height": cy - padding.top  - padding.bottom
      },

      xValue = function(d) { return d[0]; },
      yValue = function(d) { return d[1]; },

      xScale = d3.scale.linear()
        .domain([options.xmin, options.xmax])
        .range([0, size.width]),

      downx = Math.NaN,

      yScale = d3.scale.linear()
        .domain([options.ymax, options.ymin]).nice()
        .range([0, size.height]).nice(),

      downy = Math.NaN,

      dragged = null,
      selected = null,

      line = d3.svg.line()
          .x(function(d, i) { return xScale(points[i].x); })
          .y(function(d, i) { return yScale(points[i].y); });

      options.xrange = options.xmax - options.xmin;
      options.yrange = options.ymax - options.ymin;

  function graph(selection) {
    if (!selection) { selection = elem; };
    selection.each(function() {

      if (this.clientWidth && this.clientHeight) {
        cx = this.clientWidth;
        cy = this.clientHeight;
        size.width  = cx - padding.left - padding.right;
        size.height = cy - padding.top  - padding.bottom;
      }

      fakeDataPoints();
      updateXScale();
      updateYScale();

      vis = d3.select(this).append("svg")
          .attr("width",  cx)
          .attr("height", cy)
          .append("g")
            .attr("transform", "translate(" + padding.left + "," + padding.top + ")");

      plot = vis.append("rect")
          .attr("width", size.width)
          .attr("height", size.height)
          .style("fill", "#EEEEEE")
          .attr("pointer-events", "all")
          .on("mousedown.drag", plot_drag)
          .on("touchstart.drag", plot_drag)
          .call(d3.behavior.zoom().x(xScale).y(yScale).on("zoom", redraw));

      vis.append("svg")
          .attr("top", 0)
          .attr("left", 0)
          .attr("width", size.width)
          .attr("height", size.height)
          .attr("viewBox", "0 0 "+size.width+" "+size.height)
          .attr("class", "line")
          .append("path")
              .attr("class", "line")
              .attr("d", line(points));

      // add Chart Title
      if (options.title) {
        title = vis.append("text")
            .attr("class", "title")
            .text(options.title)
            .attr("x", size.width/2)
            .attr("dy","-0.8em")
            .style("text-anchor","middle");
      }

      // Add the x-axis label
      if (options.xlabel) {
        xlabel = vis.append("text")
            .attr("class", "axis")
            .text(options.xlabel)
            .attr("x", size.width/2)
            .attr("y", size.height)
            .attr("dy","2.4em")
            .style("text-anchor","middle");
      }

      // add y-axis label
      if (options.ylabel) {
        ylabel = vis.append("g").append("text")
            .attr("class", "axis")
            .text(options.ylabel)
            .style("text-anchor","middle")
            .attr("transform","translate(" + -40 + " " + size.height/2+") rotate(-90)");
      }

      d3.select(this)
          .on("mousemove.drag", mousemove)
          .on("touchmove.drag", mousemove)
          .on("mouseup.drag",   mouseup)
          .on("touchend.drag",  mouseup);

      redraw();
    });

    function fakeDataPoints() {
      var yrange2 = options.yrange / 2,
          yrange4 = yrange2 / 2;

      options.datacount = size.width/30;
      options.xtic = options.xrange / options.datacount;
      options.ytic = options.yrange / options.datacount;

      points = d3.range(options.datacount).map(function(i) {
        return { x: i * options.xtic + options.xmin, y: options.ymin + yrange4 + Math.random() * yrange2 };
      })
    }

    function keydown() {
      if (!selected) return;
      switch (d3.event.keyCode) {
        case 8:   // backspace
        case 46:  // delete
          var i = points.indexOf(selected);
          points.splice(i, 1);
          selected = points.length ? points[i > 0 ? i - 1 : 0] : null;
          update();
          break;
      }
    }

    // update the layout
    function updateLayout() {
      padding = {
       "top":    options.title  ? 40 : 20,
       "right":                 30,
       "bottom": options.xlabel ? 60 : 10,
       "left":   options.ylabel ? 70 : 45
      };

      size.width  = cx - padding.left - padding.right;
      size.height = cy - padding.top  - padding.bottom;

      plot.attr("width", size.width)
          .attr("height", size.height);
    }

    // Update the x-scale.
    function updateXScale() {
      xScale.domain([options.xmin, options.xmax])
            .range([0, size.width]);
    }

    // Update the y-scale.
    function updateYScale() {
      yScale.domain([options.ymin, options.ymax])
            .range([size.height, 0]);
    }

    function redraw() {
      var tx = function(d) {
        return "translate(" + xScale(d) + ",0)";
      },
      ty = function(d) {
        return "translate(0," + yScale(d) + ")";
      },
      stroke = function(d) {
        return d ? "#ccc" : "#666";
      },

      fx = xScale.tickFormat(options.datacount),
      fy = xScale.tickFormat(options.datacount);

      // Regenerate x-ticks…
      var gx = vis.selectAll("g.x")
          .data(xScale.ticks(10), String)
          .attr("transform", tx);

      gx.select("text")
          .text(fx);

      var gxe = gx.enter().insert("g", "a")
          .attr("class", "x")
          .attr("transform", tx);

      gxe.append("line")
          .attr("stroke", stroke)
          .attr("y1", 0)
          .attr("y2", size.height);

      gxe.append("text")
          .attr("class", "axis")
          .attr("y", size.height)
          .attr("dy", "1em")
          .attr("text-anchor", "middle")
          .text(fx)
          .style("cursor", "ew-resize")
          .on("mouseover", function(d) { d3.select(this).style("font-weight", "bold");})
          .on("mouseout",  function(d) { d3.select(this).style("font-weight", "normal");})
          .on("mousedown.drag",  xaxis_drag)
          .on("touchstart.drag", xaxis_drag);

      gx.exit().remove();

      // Regenerate y-ticks…
      var gy = vis.selectAll("g.y")
          .data(yScale.ticks(10), String)
          .attr("transform", ty);

      gy.select("text")
          .text(fy);

      var gye = gy.enter().insert("g", "a")
          .attr("class", "y")
          .attr("transform", ty)
          .attr("background-fill", "#FFEEB6");

      gye.append("line")
          .attr("stroke", stroke)
          .attr("x1", 0)
          .attr("x2", size.width);

      gye.append("text")
          .attr("class", "axis")
          .attr("x", -3)
          .attr("dy", ".35em")
          .attr("text-anchor", "end")
          .text(fy)
          .style("cursor", "ns-resize")
          .on("mouseover", function(d) { d3.select(this).style("font-weight", "bold");})
          .on("mouseout",  function(d) { d3.select(this).style("font-weight", "normal");})
          .on("mousedown.drag",  yaxis_drag)
          .on("touchstart.drag", yaxis_drag);

      gy.exit().remove();
      plot.call(d3.behavior.zoom().x(xScale).y(yScale).on("zoom", redraw));
      update();
    }

    function update() {
      var lines = vis.select("path").attr("d", line(points));

      var circle = vis.select("svg").selectAll("circle")
          .data(points, function(d) { return d; });

      circle.enter().append("circle")
          .attr("class", function(d) { return d === selected ? "selected" : null; })
          .attr("cx",    function(d) { return xScale(d.x); })
          .attr("cy",    function(d) { return yScale(d.y); })
          .attr("r", 10.0)
          .style("cursor", "ns-resize")
          .on("mousedown.drag",  datapoint_drag)
          .on("touchstart.drag", datapoint_drag);

      circle
          .attr("class", function(d) { return d === selected ? "selected" : null; })
          .attr("cx",    function(d) { return xScale(d.x); })
          .attr("cy",    function(d) { return yScale(d.y); });

      circle.exit().remove();

      if (d3.event && d3.event.keyCode) {
        d3.event.preventDefault();
        d3.event.stopPropagation();
      }
    }

    function plot_drag() {
      registerKeyboardHandler(keydown);
      d3.select('body').style("cursor", "move");
      if (d3.event.altKey) {
        var p = d3.svg.mouse(vis.node());
        var newpoint = {};
        newpoint.x = xScale.invert(Math.max(0, Math.min(size.width,  p[0])));
        newpoint.y = yScale.invert(Math.max(0, Math.min(size.height, p[1])));
        points.push(newpoint);
        points.sort(function(a, b) {
          if (a.x < b.x) { return -1; }
          if (a.x > b.x) { return  1; }
          return 0;
        });
        selected = newpoint;
        update();
        d3.event.preventDefault();
        d3.event.stopPropagation();
      }
    }

    function xaxis_drag(d) {
      document.onselectstart = function() { return false; };
      var p = d3.svg.mouse(vis[0][0]);
      downx = xScale.invert(p[0]);
    }

    function yaxis_drag(d) {
      document.onselectstart = function() { return false; };
      var p = d3.svg.mouse(vis[0][0]);
      downy = yScale.invert(p[1]);
    }

    function datapoint_drag(d) {
      registerKeyboardHandler(keydown);
      document.onselectstart = function() { return false; };
      selected = dragged = d;
      update();
    }

    function mousemove() {
      var p = d3.svg.mouse(vis[0][0]),
          changex, changey, new_domain,
          t = d3.event.changedTouches;

      if (dragged) {
        dragged.y = yScale.invert(Math.max(0, Math.min(size.height, p[1])));
        update();
      }

      if (!isNaN(downx)) {
        d3.select('body').style("cursor", "ew-resize");
        var rupx = xScale.invert(p[0]),
            xaxis1 = xScale.domain()[0],
            xaxis2 = xScale.domain()[1],
            xextent = xaxis2 - xaxis1;
        if (rupx !== 0) {
          changex = downx / rupx;
          new_domain = [xaxis1, xaxis1 + (xextent * changex)];
          xScale.domain(new_domain);
          redraw();
        }
        d3.event.preventDefault();
        d3.event.stopPropagation();
      }

      if (!isNaN(downy)) {
        d3.select('body').style("cursor", "ns-resize");
        var rupy = yScale.invert(p[1]),
            yaxis1 = yScale.domain()[1],
            yaxis2 = yScale.domain()[0],
            yextent = yaxis2 - yaxis1;
        if (rupy !== 0) {
          changey = downy / rupy;
          new_domain = [yaxis2, yaxis2 - yextent * changey];
          yScale.domain(new_domain);
          redraw();
        }
        d3.event.preventDefault();
        d3.event.stopPropagation();
      }
    }

    function mouseup() {
      document.onselectstart = function() { return true; };
      d3.select('body').style("cursor", "auto");
      d3.select('body').style("cursor", "auto");
      if (!isNaN(downx)) {
        redraw();
        downx = Math.NaN;
        d3.event.preventDefault();
        d3.event.stopPropagation();
      }
      if (!isNaN(downy)) {
        redraw();
        downy = Math.NaN;
        d3.event.preventDefault();
        d3.event.stopPropagation();
      }
      if (dragged) {
        dragged = null;
      }
    }

    // make these private variables and functions available
    graph.elem = elem;
    graph.redraw = redraw;
    graph.updateXScale = updateXScale;
    graph.updateYScale = updateYScale;

  }

  // update the title
  function updateTitle() {
    if (options.title && title) {
      title.text(options.title);
    }
  }

  // update the x-axis label
  function updateXlabel() {
    if (options.xlabel && xlabel) {
      xlabel.text(options.xlabel);
    }
  }

  // update the y-axis label
  function updateYlabel() {
    if (options.ylabel && ylabel) {
      ylabel.text(options.ylabel);
    } else {
      ylabel.style("display", "none");
    }
  }

  // The x-accessor for the path generator; xScale ∘ xValue.
  function X(d) {
    return xScale(d[0]);
  }

  // The x-accessor for the path generator; yScale ∘ yValue.
  function Y(d) {
    return yScale(d[1]);
  }

  function gRedraw() {
    redraw();
  }

  graph.options = function(_) {
    if (!arguments.length) return options;
    // options = _;
    return graph;
  };

  graph.margin = function(_) {
    if (!arguments.length) return margin;
    margin = _;
    return graph;
  };

  graph.xmin = function(_) {
    if (!arguments.length) return options.xmin;
    options.xmin = _;
    options.xrange = options.xmax - options.xmin;
    if (graph.updateXScale) {
      graph.updateXScale();
      graph.redraw();
    }
    return graph;
  };

  graph.xmax = function(_) {
    if (!arguments.length) return options.xmax;
    options.xmax = _;
    options.xrange = options.xmax - options.xmin;
    if (graph.updateXScale) {
      graph.updateXScale();
      graph.redraw();
    }
    return graph;
  };

  graph.ymin = function(_) {
    if (!arguments.length) return options.ymin;
    options.ymin = _;
    options.yrange = options.ymax - options.ymin;
    if (graph.updateYScale) {
      graph.updateYScale();
      graph.redraw();
    }
    return graph;
  };

  graph.ymax = function(_) {
    if (!arguments.length) return options.ymax;
    options.ymax = _;
    options.yrange = options.ymax - options.ymin;
    if (graph.updateYScale) {
      graph.updateYScale();
      graph.redraw();
    }
    return graph;
  };

  graph.xLabel = function(_) {
    if (!arguments.length) return options.xlabel;
    options.xlabel = _;
    updateXlabel();
    return graph;
  };

  graph.yLabel = function(_) {
    if (!arguments.length) return options.ylabel;
    options.ylabel = _;
    updateYlabel();
    return graph;
  };

  graph.title = function(_) {
    if (!arguments.length) return options.title;
    options.title = _;
    updateTitle();
    return graph;
  };

  graph.width = function(_) {
    if (!arguments.length) return size.width;
    size.width = _;
    return graph;
  };

  graph.height = function(_) {
    if (!arguments.length) return size.height;
    size.height = _;
    return graph;
  };

  graph.x = function(_) {
    if (!arguments.length) return xValue;
    xValue = _;
    return graph;
  };

  graph.y = function(_) {
    if (!arguments.length) return yValue;
    yValue = _;
    return graph;
  };

  graph.elem = function(_) {
    if (!arguments.length) return elem;
    elem = d3.select(_);
    elem.call(graph);
    return graph;
  };

  if (elem) { elem.call(graph); }

  return graph;
}