|
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; |
|
} |
Hi stumbled upon your code and was wondering if you know an answer to this question? http://stackoverflow.com/questions/17768226/position-of-an-element-based-on-the-current-d3-event-scale
Thanks!