How to make plotting possible on x-axis lines only? (d3.js) - javascript

I've just started with trying out the d3 library.
I am trying to create an interactive line chart where people can plot their own points. You can find it over here: http://jsfiddle.net/6FjJ2/
My question is: how can I make sure that plotting can only be done on the x-axis' lines? If you check out my example, you will see it kind of works, but with a lot of cheating. Check out the ok variable... What would be the correct way of achieving this? I have no idea how I can achieve this with a ... so I'm getting a lot of seperate 's.
var data = [2, 3, 4, 3, 4, 5],
w = 1000,
h = 300,
monthsData = [],
months = 18;
for(i = 0; i < months; i++) {
monthsData.push(i);
}
var max = d3.max(monthsData),
x = d3.scale.linear().domain([0, monthsData.length]).range([0, w]),
y = d3.scale.linear().domain([0, max]).range([h, 0]),
pointpos = [];
lvl = [0, 10],
lvly = d3.scale.linear().domain([0, d3.max(lvl)]).range([h, 0]);
svg = d3.select(".chart")
.attr("width", w)
.attr("height", h);
svg.selectAll('path.line')
// Return "data" array which will form the path coordinates
.data([data])
// Add path
.enter().append("svg:path")
.attr("d", d3.svg.line()
.x(function(d, i) { return x(i); })
.y(y));
// Y-axis ticks
ticks = svg.selectAll(".ticky")
// Change number of ticks for more gridlines!
.data(lvly.ticks(10))
.enter().append("svg:g")
.attr("transform", function(d) { return "translate(0, " + (lvly(d)) + ")"; })
.attr("class", "ticky");
ticks.append("svg:line")
.attr("y1", 0)
.attr("y2", 0)
.attr("x1", 0)
.attr("x2", w);
ticks.append("svg:text")
.text( function(d) { return d; })
.attr("text-anchor","end")
.attr("dy", 2)
.attr("dx", -4);
// X-axis ticks
ticks = svg.selectAll(".tickx")
.data(x.ticks(monthsData.length))
.enter().append("svg:g")
.attr("transform", function(d, i) { return "translate(" + (x(i)) + ", 0)"; })
.attr("class", "tickx");
ticks.append("svg:line")
.attr("y1", h)
.attr("y2", 0)
.attr("x1", 0)
.attr("x2", 0);
ticks.append("svg:text")
.text( function(d, i) { return i; })
.attr("y", h)
.attr("dy", 15)
.attr("dx", -2);
// var d = $(".tickx:first line").css({"stroke-width" : "2", opacity : "1"});
var line;
var ok = -55;
svg.on("mousedown", mouseDown)
.on("mouseup", mouseUp);
function mouseDown() {
var m = d3.mouse(this);
line = svg.append("line")
.data(monthsData)
/* .attr("x1", m[0]) */
.attr("x1", function(d, i) { pointpos.push(m[0]); ok += 55; return ok;})
.attr("y1", m[1])
.attr("x2", function(d, i) { return ok + 56; })
/* .attr("x2", function(d, i) {return 300; }) */
.attr("y2", m[1]);
svg.on("mousemove", mouseMove);
var m = d3.mouse(this);
var point = svg.append("circle")
.attr("cx", function(d, i) { return ok; })
.attr("cy", function(d, i) { return m[1]; })
.attr("r", 8);
lvl.push(100);
}
function mouseMove() {
var m = d3.mouse(this);
line.attr("y2", m[1]);
/* .attr("y1", m[0]); */
}
function mouseUp() {
// Change null to mousemove for a graph kinda draw mode
svg.on("mousemove", mouseMove);
}
Excuse my bad code!
Thanks in advance.

It looks like you need:
histogram layout for binning your points.
ordinal scales for restricting their x-axis positions according to the bin
As a sidenote, you can use d3.svg.axis to draw the axis for you.

Related

d3.js water bar chart

I am trying to fix this watery bar chart so that it can handle dynamic data sets -- http://jsfiddle.net/NYEaX/1855/
Having an issue with the positioning of the labels and adjusting the width/height of the lines.
var lineHeights = 100;
//__ labels
var labels = labelsholder.selectAll("text")
.data(data);
labels.enter()
.append("text")
.attr("class", "barlabels")
.attr("x", 200)
.attr("y", function(d, i) {
return lineHeights - (20 * i);
})
.text(function(d) {
return d.label;
})
var lines = lineholder.selectAll("line")
.data(data);
//horizontal
lines.enter()
.append("line") // attach a line
.style("stroke-dasharray", ("3, 3"))
.style("stroke", "black") // colour the line
.attr("x1", function(d, i) {
return barWidth - 100/(i+1);
}) //x pos of the 1st end of the line
.attr("y1", function(d, i) {
return lineHeights - (20 * i);
}) //y pos of the 1st end of the line
.attr("x2", function(d, i) {
return barWidth;
}) //x pos of the 2nd end of the line
.attr("y2", function(d, i) {
return lineHeights - (20 * i);
}); //y pos of the 2nd end of the line
//verticals
lines.enter()
.append("line") // attach a line
.style("stroke-dasharray", ("3, 3"))
.style("stroke", "black") // colour the line
.attr("x1", function(d, i) {
return 30 * i;
}) //x pos of the 1st end of the line
.attr("y1", function(d, i) {
return lineHeights - (20 * i);
}) //y pos of the 1st end of the line
.attr("x2", function(d, i) {
return 30 * i;
}) //x pos of the 2nd end of the line
.attr("y2", function(d, i) {
return -15;
}); //y pos of the 2nd end of the line
Here's the working JSFiddle: http://jsfiddle.net/NYEaX/1860/
Where you were appending the horizontal lines, you were using barWidth - 100/(i+1); to determine the x-axis. This would have worked if barWidth was actually the width of each bar, (but it had been set to 150 instead?)
.attr("x1", function(d, i) {
return (i * 30);
Each bar has a width of 20 and a margin of 5 on each side. So to calculate the offset, just multipy the bar number, i by with total width of the bar, 30.

D3 Multi-Series Line Chart with ZOOM

Apologies for the newbie question here. I'm trying to reproduce this example https://bl.ocks.org/mbostock/3884955 with some data on online influencers (essentially drawing many lines) but with an scroll zoom in order to isolate parts of the data. Sounds simple enough, but I can't seem to get it to function!
< svg width = "960"
height = "500" > < /svg>
<script src="/ / d3js.org / d3.v4.min.js "></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 80, bottom: 30, left: 100},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform ", "translate(" + margin.left +
", " + margin.top + ")");
var zoom = d3.zoom()
.scaleExtent([1, 32])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
var xAxis = d3.axisBottom(x),
yAxis = d3.axisLeft(y);
var parseTime = d3.timeParse(" % d / % m / % Y ");
var x = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
z = d3.scaleOrdinal(d3.schemeCategory10);
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.engagement); });
d3.csv("stack2.csv", type, function(error, data) {
if (error) throw error;
var influencers = data.columns.slice(1).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, engagement: d[id]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([d3.min(influencers, function(c) {
return d3.min(c.values, function(d) {
return d.engagement; }); }),
d3.max(influencers, function(c) {
return d3.max(c.values, function(d) {
return d.engagement; }); })
]);
z.domain(influencers.map(function(c) { return c.id; }));
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0, " + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 5)
.attr("dy", "0.71em")
.attr("fill", "#000")
.text("Engagement");
var influ = g.selectAll(".influ")
.data(influencers)
.enter().append("g")
.attr("class", "influ");
influ.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", "lightgrey")
.on("mouseover", function(d, i) {
d3.select(this).transition()
.style("stroke", function(d) {
return z(d.id);
})
.style("stroke-width", 3)
console.log(d.id);
})
.on("mouseout", function(d) {
d3.select(this).transition()
.style("stroke", "lightgrey")
.style("stroke-width", 1)
})
influ.append("text")
.datum(function(d) {
return {
id: d.id,
value: d.values[d.values.length - 1]
};
})
.attr("transform", function(d) {
return "translate(" + x(d.value.date) + "," + y(d.value.engagement) + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.style("opacity", 0.7)
.style("font", "10px sans-serif")
.text(function(d) {
return d.id;
});
svg.call(zoom)
});
function type(d, _, columns) {
d.date = parseTime(d.date);
for (var i = 1, n = columns.length, c; i < n; ++i) d[c = columns[i]] = +d[c];
return d;
}
function zoomed() {
var t = d3.event.transform,
xt = t.rescaleX(x);
g.selectAll("path.line").attr("d", function(d) {
return xt(d.date);
});
g.select(".axis--x").call(xAxis.scale(xt));
}
< /script>
I think the problem is isolated to this part:
function zoomed() {
var t = d3.event.transform,
xt = t.rescaleX(x);
g.selectAll("path.line").attr("d", function(d) {
return xt(d.date);
});
g.select(".axis--x").call(xAxis.scale(xt));
}
The Axis zoom is fine, but i don't think I'm calling the data for the line graphs correctly. I think g.selectAll definitely selects the lines, as they disappear on scroll... so I am assuming that .attr("d", function(d) {return xt(d.date); is wrong. Anyone have any tips?
I use something along these lines in an interactive line chart D3 plot that I wrote a couple of days back. I've commented extensively, so it should be pretty self explanatory. This works really well and has been bug free.
function zoomed() {
lastEventTransform = d3.event.transform;
// Rescale axes using current zoom transform
gX.call(xAxis.scale(lastEventTransform.rescaleX(x)));
gY.call(yAxis.scale(lastEventTransform.rescaleY(y)));
// Create new scales that incorporate current zoom transform on original scale
var xt = lastEventTransform.rescaleX(x),
yt = lastEventTransform.rescaleY(y);
// Apply new scale to create new definition of d3.line method for path drawing of line plots
var line = d3.line()
.x(function(d) { return xt(d.x); })
.y(function(d) { return yt(d.y); });
// Update all line-plot elements with new line method that incorporates transform
innerSvg.selectAll(".line")
.attr("d", function(d) { return line(d.values); });
// Update any scatter points if you are also plotting them
innerSvg.selectAll(".dot")
.attr("cx", function(d) {return xt(d.x); })
.attr("cy", function(d) {return yt(d.y); });
}
Note: gX and gY are just the d3 g group elements that had the axes originally called on them during setup of the plot.
This Zoom function tricks the domain to change and then redraws the lines on the new domain. Its the best i can manage for now... but hardly elegant!
function zoomed() {
var t = d3.event.transform;
// var t = d3.event.scaleBy;
var xt = t.rescaleY(y);
domain = yAxis.scale().domain();
g.select(".axis--y").call(yAxis.scale(xt));
g.selectAll("path.line").attr("d", function(d) {
if ( d3.max(d.values.map(function(dd) { return dd.engagement } )) > domain[1] ) {
return null
}
else {
return line(d.values)
}
});
if (domain[1] > 50000000) {
max = 50000000
} else if ( domain[1] < 500 ) {
max = 500
} else {
max = domain[1]
}
y.domain([0, max]);
}
It zooms alright but it is prone to de-coupling the axis from what the lines are saying. If anyone has something better let me know!

Alternating or preventing overlapping paths in D3

I am creating an arc diagram where I'd like to, hopefully, find a way to prevent the overlap of arcs. There's an example of the working bl.ock here.
The darker lines in this case are overlapping lines where multiple nodes share the same edge. I'd like to prevent that, perhaps by doing two passes: the first would alternate the arc to go above the nodes rather than below, giving a sort of helix appearance; the second would draw a slightly larger arc if an arc already exists above/below to help differentiate the links.
var width = 1000,
height = 500,
margin = 20,
pad = margin / 2,
radius = 6,
yfixed = pad + radius;
var color = d3.scale.category10();
// Main
//-----------------------------------------------------
function arcDiagram(graph) {
var radius = d3.scale.sqrt()
.domain([0, 20])
.range([0, 15]);
var svg = d3.select("#chart").append("svg")
.attr("id", "arc")
.attr("width", width)
.attr("height", height);
// create plot within svg
var plot = svg.append("g")
.attr("id", "plot")
.attr("transform", "translate(" + pad + ", " + pad + ")");
// fix graph links to map to objects
graph.links.forEach(function(d,i) {
d.source = isNaN(d.source) ? d.source : graph.nodes[d.source];
d.target = isNaN(d.target) ? d.target : graph.nodes[d.target];
});
linearLayout(graph.nodes);
drawLinks(graph.links);
drawNodes(graph.nodes);
}
// layout nodes linearly
function linearLayout(nodes) {
nodes.sort(function(a,b) {
return a.uniq - b.uniq;
})
var xscale = d3.scale.linear()
.domain([0, nodes.length - 1])
.range([radius, width - margin - radius]);
nodes.forEach(function(d, i) {
d.x = xscale(i);
d.y = yfixed;
});
}
function drawNodes(nodes) {
var gnodes = d3.select("#plot").selectAll("g.node")
.data(nodes)
.enter().append('g');
var nodes = gnodes.append("circle")
.attr("class", "node")
.attr("id", function(d, i) { return d.name; })
.attr("cx", function(d, i) { return d.x; })
.attr("cy", function(d, i) { return d.y; })
.attr("r", 5)
.style("stroke", function(d, i) { return color(d.gender); });
nodes.append("text")
.attr("dx", function(d) { return 20; })
.attr("cy", ".35em")
.text(function(d) { return d.name; })
}
function drawLinks(links) {
var radians = d3.scale.linear()
.range([Math.PI / 2, 3 * Math.PI / 2]);
var arc = d3.svg.line.radial()
.interpolate("basis")
.tension(0)
.angle(function(d) { return radians(d); });
d3.select("#plot").selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("transform", function(d,i) {
var xshift = d.source.x + (d.target.x - d.source.x) / 2;
var yshift = yfixed;
return "translate(" + xshift + ", " + yshift + ")";
})
.attr("d", function(d,i) {
var xdist = Math.abs(d.source.x - d.target.x);
arc.radius(xdist / 2);
var points = d3.range(0, Math.ceil(xdist / 3));
radians.domain([0, points.length - 1]);
return arc(points);
});
}
Any pointers on how I might start approaching the problem?
Here is a bl.ock for reference. It shows your original paths in gray, and the proposed paths in red.
First store the counts for how many times a given path occurs:
graph.links.forEach(function(d,i) {
var pathCount = 0;
for (var j = 0; j < i; j++) {
var otherPath = graph.links[j];
if (otherPath.source === d.source && otherPath.target === d.target) {
pathCount++;
}
}
d.pathCount = pathCount;
});
Then once you have that data, I would use an ellipse instead of a radial line since it appears the radial line can only draw a curve for a circle:
d3.select("#plot").selectAll(".ellipse-link")
.data(links)
.enter().append("ellipse")
.attr("fill", "transparent")
.attr("stroke", "gray")
.attr("stroke-width", 1)
.attr("cx", function(d) {
return (d.target.x - d.source.x) / 2 + radius;
})
.attr("cy", pad)
.attr("rx", function(d) {
return Math.abs(d.target.x - d.source.x) / 2;
})
.attr("ry", function(d) {
return 150 + d.pathCount * 20;
})
.attr("transform", function(d,i) {
var xshift = d.source.x - radius;
var yshift = yfixed;
return "translate(" + xshift + ", " + yshift + ")";
});
Note that changing the value for ry above will change the heights of different curves.
Finally you'll have to use a clippath to restrict the area of each ellipse that's actually shown, so that they only display below the nodes. (This is not done in the bl.ock)

D3.js how do I arrange nodes of a force layout to be on a circle

I have developed a force layout to represent relationships between social groups. Now I would like to get the nodes to be distributed in a circle with links joining them. What is the best way to do this?
The complete version of the code (without data) is here http://jsfiddle.net/PatriciaW/zZSJT/
(Why do I have to include code here too? Here is the main portion)
d3.json("/relationships?nocache=" + (new Date()).getTime(),function(error,members){
var links=members.organizations.map(function(members) {
return members.member;
});
var nodes = {};
links.forEach(function(link) {
link.source = nodes[link.xsource] || (nodes[link.xsource] = {source: link.xsource, name: link.xsource, category: link.categorysource, path: link.pathsource, desc: link.descsource, title: link.titlesource});
link.target = nodes[link.xtarget] || (nodes[link.xtarget] = {target: link.xtarget, name: link.xtarget, category: link.categorytarget, path: link.pathtarget, desc: link.desctarget, title: link.titletarget});
});
force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.charge(-120)
.linkDistance(function() {return (Math.random() * 200) + 100;})
.linkStrength(0.5)
.on("tick", tick)
.start();
var link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("class", "link");
var node_drag = d3.behavior.drag()
.on("dragstart", dragstart)
.on("drag", dragmove)
.on("dragend", dragend);
var loading = svg.append("text")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Simulating. One moment pleaseā€¦");
function dragstart(d, i) {
force.stop() // stops the force auto positioning before you start dragging
}
function dragmove(d, i) {
d.px += d3.event.dx;
d.py += d3.event.dy;
d.x += d3.event.dx;
d.y += d3.event.dy;
tick(); // this is the key to make it work together with updating both px,py,x,y on d !
}
function dragend(d, i) {
d.fixed = true; // of course set the node to fixed so the force doesn't include the node in its auto positioning stuff
tick();
force.resume();
};
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", clickAlert)
.call(node_drag);
node.append("circle")
.attr("r", 8)
.style("fill", function(d) {
return categoryColour [d.category];
})
// add an image marker
node.append("image")
.attr("x",-8)
.attr("y",-8)
.attr("width", 16)
.attr("height", 16)
.attr("xlink:href", function(d) {
return categoryImage [d.category]
})
.on("click", clickAlert)
.style("cursor", "pointer")
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
// Use a timeout to allow the rest of the page to load first.
setTimeout(function() {
// Run the layout a fixed number of times.
// The ideal number of times scales with graph complexity.
force.start();
for (var i = n * n; i > 0; --i) force.tick();
force.stop();
svg.selectAll("line")
.data(links)
.enter().append("line")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 4.5);
loading.remove();
}, 10);
function tick() {
link
.attr("x1", function(d) {
return d.source.x + xadj; })
.attr("y1", function(d) {
return d.source.y + yadj; })
.attr("x2", function(d) {
return d.target.x +xadj; })
.attr("y2", function(d) {
return d.target.y +yadj; });
node
.attr("transform", function(d) {
return "translate(" + (d.x + xadj) + "," + (d.y + yadj) + ")";
});
};
function mouseover() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 16);
d3.select(this).select("text")
.attr("font-size","34px")
.style("font-weight", "bold");
};
function mouseout() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 8);
d3.select(this).select("text")
.attr("font-size","12px")
.style("font-weight", "normal");
};
}) // end json
Here's someone else's solution:
This network graph uses the D3 force layout to draw nodes and links, but instead of using d3.force() to find the best node positions, we draw an invisible arc and evenly places nodes along the circumference.
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
<style>
line.node-link, path.node-link {
fill: none;
stroke: black
}
circle.node {
fill: white;
stroke: black
}
circle.node+text {
text-anchor: middle;
}
text {
font-family: sans-serif;
pointer-events: none;
}
</style>
</head>
<body>
<script type="text/javascript">
// number of random nodes (gets crowded at >25 unless you change node diameter)
var num = 20;
// returns random int between 0 and num
function getRandomInt() {return Math.floor(Math.random() * (num));}
// nodes returns a [list] of {id: 1, fixed:true}
var nodes = d3.range(num).map(function(d) { return {id: d}; });
// links returns a [list] of {source: 0, target: 1} (values refer to indicies of nodes)
var links = d3.range(num).map(function(d) { return {source: getRandomInt(), target: getRandomInt()}; });
var width = 500,
height = 500;
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height]);
// evenly spaces nodes along arc
var circleCoord = function(node, index, num_nodes){
var circumference = circle.node().getTotalLength();
var pointAtLength = function(l){return circle.node().getPointAtLength(l)};
var sectionLength = (circumference)/num_nodes;
var position = sectionLength*index+sectionLength/2;
return pointAtLength(circumference-position)
}
// fades out lines that aren't connected to node d
var is_connected = function(d, opacity) {
lines.transition().style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// invisible circle for placing nodes
// it's actually two arcs so we can use the getPointAtLength() and getTotalLength() methods
var dim = width-80
var circle = svg.append("path")
.attr("d", "M 40, "+(dim/2+40)+" a "+dim/2+","+dim/2+" 0 1,0 "+dim+",0 a "+dim/2+","+dim/2+" 0 1,0 "+dim*-1+",0")
.style("fill", "#f5f5f5");
force.start();
// set coordinates for container nodes
nodes.forEach(function(n, i) {
var coord = circleCoord(n, i, nodes.length)
n.x = coord.x
n.y = coord.y
});
// use this one for straight line links...
// var lines = svg.selectAll("line.node-link")
// .data(links).enter().append("line")
// .attr("class", "node-link")
// .attr("x1", function(d) { return d.source.x; })
// .attr("y1", function(d) { return d.source.y; })
// .attr("x2", function(d) { return d.target.x; })
// .attr("y2", function(d) { return d.target.y; });
// ...or use this one for curved line links
var lines = svg.selectAll("path.node-link")
.data(links).enter().append("path")
.attr("class", "node-link")
.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
var gnodes = svg.selectAll('g.gnode')
.data(nodes).enter().append('g')
.attr("transform", function(d) {
return "translate("+d.x+","+d.y+")"
})
.classed('gnode', true);
var node = gnodes.append("circle")
.attr("r", 25)
.attr("class", "node")
.on("mouseenter", function(d) {
is_connected(d, 0.1)
node.transition().duration(100).attr("r", 25)
d3.select(this).transition().duration(100).attr("r", 30)
})
.on("mouseleave", function(d) {
node.transition().duration(100).attr("r", 25);
is_connected(d, 1);
});
var labels = gnodes.append("text")
.attr("dy", 4)
.text(function(d){return d.id})
</script>
</body>
</html>

dealing with dates on d3.js axis

How do I make my line x-axis based on date in d3.js?
I am attempting to teach myself how to use d3.js. I've been looking at the examples that come with it and have been attempting to recreate the line graph using json delivered data. I'm able to feed the data into the line graph, but the x-axis is supposed to be a date instead of a number. The date format that I'm using is MM/DD/YY, but the graph plots everything at 0. My json data is coming across fine, but I'm having trouble figuring out how to plot the x coordinates. This was taken straight from the line.js that comes in the d3.js examples folder when downloaded. The date portion doesn't do the trick. I'm hoping someone can point me to an example or be able to explain how I can make it work.
d3.json('jsonChartData.action',
function (data) {
console.log(data);
var w = 450,
h = 275,
p = 30,
x = d3.scale.linear().domain([0, 100]).range([0, w]),
y = d3.scale.linear().domain([0, 100]).range([h, 0]);
var vis = d3.select("body")
.data([data])
.append("svg:svg")
.attr("width", w + p * 2)
.attr("height", h + p * 2)
.append("svg:g")
.attr("transform", "translate(" + p + "," + p + ")");
var rules = vis.selectAll("g.rule")
.data(x.ticks(5))
.enter().append("svg:g")
.attr("class", "rule");
rules.append("svg:line")
.attr("x1", x)
.attr("x2", x)
.attr("y1", 0)
.attr("y2", h - 1);
rules.append("svg:line")
.attr("class", function(d) { return d ? null : "axis"; })
.attr("y1", y)
.attr("y2", y)
.attr("x1", 0)
.attr("x2", w + 1);
rules.append("svg:text")
.attr("x", x)
.attr("y", h + 3)
.attr("dy", ".71em")
.attr("text-anchor", "middle")
.text(x.tickFormat(10));
rules.append("svg:text")
.attr("y", y)
.attr("x", -3)
.attr("dy", ".35em")
.attr("text-anchor", "end")
.text(y.tickFormat(10));
vis.append("svg:path")
.attr("class", "line")
.attr("d", d3.svg.line()
.x(function(d) { return x(d3.time.days(new Date(d.jsonDate))); })
.y(function(d) { return y(d.jsonHitCount); }));
vis.selectAll("circle.line")
.data(data)
.enter().append("svg:circle")
.attr("class", "line")
.attr("cx", function(d) { return x(d3.time.days(new Date(d.jsonDate))); })
.attr("cy", function(d) { return y(d.jsonHitCount); })
.attr("r", 3.5);
});
JSON as printed out by my action:
[{"jsonDate":"09\/22\/11","jsonHitCount":2,"seriesKey":"Website Usage"},`{"jsonDate":"09\/26\/11","jsonHitCount":9,"seriesKey":"Website Usage"},{"jsonDate":"09\/27\/11","jsonHitCount":9,"seriesKey":"Website Usage"},{"jsonDate":"09\/29\/11","jsonHitCount":26,"seriesKey":"Website Usage"},{"jsonDate":"09\/30\/11","jsonHitCount":2,"seriesKey":"Website Usage"},{"jsonDate":"10\/03\/11","jsonHitCount":3,"seriesKey":"Website Usage"},{"jsonDate":"10\/06\/11","jsonHitCount":2,"seriesKey":"Website Usage"},{"jsonDate":"10\/11\/11","jsonHitCount":2,"seriesKey":"Website Usage"},{"jsonDate":"10\/12\/11","jsonHitCount":2,"seriesKey":"Website Usage"},{"jsonDate":"10\/13\/11","jsonHitCount":1,"seriesKey":"Website Usage"},{"jsonDate":"10\/14\/11","jsonHitCount":5,"seriesKey":"Website Usage"},{"jsonDate":"10\/17\/11","jsonHitCount":2,"seriesKey":"Website Usage"},{"jsonDate":"10\/18\/11","jsonHitCount":6,"seriesKey":"Website Usage"},{"jsonDate":"10\/19\/11","jsonHitCount":8,"seriesKey":"Website Usage"},{"jsonDate":"10\/20\/11","jsonHitCount":2,"seriesKey":"Website Usage"},{"jsonDate":"10\/21\/11","jsonHitCount":4,"seriesKey":"Website Usage"},{"jsonDate":"10\/24\/11","jsonHitCount":1,"seriesKey":"Website Usage"},{"jsonDate":"10\/25\/11","jsonHitCount":1,"seriesKey":"Website Usage"},{"jsonDate":"10\/27\/11","jsonHitCount":3,"seriesKey":"Website Usage"},{"jsonDate":"11\/01\/11","jsonHitCount":2,"seriesKey":"Website Usage"},{"jsonDate":"11\/02\/11","jsonHitCount":1,"seriesKey":"Website Usage"},{"jsonDate":"11\/03\/11","jsonHitCount":2,"seriesKey":"Website Usage"},{"jsonDate":"11\/04\/11","jsonHitCount":37,"seriesKey":"Website Usage"},{"jsonDate":"11\/08\/11","jsonHitCount":1,"seriesKey":"Website Usage"},{"jsonDate":"11\/10\/11","jsonHitCount":39,"seriesKey":"Website Usage"},{"jsonDate":"11\/11\/11","jsonHitCount":1,"seriesKey":"Website Usage"},{"jsonDate":"11\/14\/11","jsonHitCount":15,"seriesKey":"Website Usage"},{"jsonDate":"11\/15\/11","jsonHitCount":2,"seriesKey":"Website Usage"},{"jsonDate":"11\/16\/11","jsonHitCount":5,"seriesKey":"Website Usage"},{"jsonDate":"11\/17\/11","jsonHitCount":4,"seriesKey":"Website Usage"},{"jsonDate":"11\/21\/11","jsonHitCount":2,"seriesKey":"Website Usage"},{"jsonDate":"11\/22\/11","jsonHitCount":3,"seriesKey":"Website Usage"},{"jsonDate":"11\/23\/11","jsonHitCount":11,"seriesKey":"Website Usage"},{"jsonDate":"11\/24\/11","jsonHitCount":2,"seriesKey":"Website Usage"},{"jsonDate":"11\/25\/11","jsonHitCount":1,"seriesKey":"Website Usage"},{"jsonDate":"11\/28\/11","jsonHitCount":10,"seriesKey":"Website Usage"},{"jsonDate":"11\/29\/11","jsonHitCount":3,"seriesKey":"Website Usage"}]`
You're trying to use d3.scale.linear() for dates, and that won't work. You need to use d3.time.scale() instead (docs):
// helper function
function getDate(d) {
return new Date(d.jsonDate);
}
// get max and min dates - this assumes data is sorted
var minDate = getDate(data[0]),
maxDate = getDate(data[data.length-1]);
var x = d3.time.scale().domain([minDate, maxDate]).range([0, w]);
Then you don't need to deal with the time interval functions, you can just pass x a date:
.attr("d", d3.svg.line()
.x(function(d) { return x(getDate(d)) })
.y(function(d) { return y(d.jsonHitCount) })
);
Working fiddle here: http://jsfiddle.net/nrabinowitz/JTrnC/

Categories

Resources