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/
Related
I am new to javascript and have been stuck at a problem for the better part of 2 weeks. I am trying to make a bar graph that updates in real time using data from Firebase. The structure of my database is:
title:
-------child1
-------child2
-------child3
-------child4
The data to firebase is provided from a python script that is working perfectly and is updating every child of title every 10 seconds.
I made a bar graph that is updating automatically via random number generation.
//Return array of 10 random numbers
var randArray = function() {
for(var i = 0, array = new Array(); i<10; i++) {
array.push(Math.floor(Math.random()*10 + 1))
}
return array
}
var initRandArray = randArray();
var newArray;
var w = 500;
var h = 200;
var barPadding = 1;
var mAx = d3.max(initRandArray)
var yScale = d3.scale.linear()
.domain([0, mAx])
.range([0, h])
var svg = d3.select("section")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.selectAll("rect")
.data(initRandArray)
.enter()
.append("rect")
.attr("x", function(d,i) {return i*(w/initRandArray.length)})
.attr("y", function(d) {return h - yScale(d)})
.attr("width", w / initRandArray.length - barPadding)
.attr("height", function(d){return yScale(d)})
.attr("fill", function(d) {
return "rgb(136, 196, " + (d * 100) + ")";
});
svg.selectAll("text")
.data(initRandArray)
.enter()
.append("text")
.text(function(d){return d})
.attr("x", function(d, i){return (i*(w/initRandArray.length) + 20)})
.attr("y", function(d) {return h - yScale(d) + 15})
.attr("font-family", "sans-serif")
.attr("fill", "white")
setInterval(function() {
newArray = randArray();
var rects = svg.selectAll("rect")
rects.data(newArray)
.enter()
.append("rect")
rects.transition()
.ease("cubic-in-out")
.duration(2000)
.attr("x", function(d,i) {return i*(w/newArray.length)})
.attr("y", function(d) {return h - yScale(d)})
.attr("width", w / newArray.length - barPadding)
.attr("height", function(d){return yScale(d)})
.attr("fill", function(d) {
return "rgb(136, 196, " + (d * 100) + ")";
});
var labels = svg.selectAll("text")
labels.data(newArray)
.enter()
.append("text")
labels.transition()
.ease("cubic-in-out")
.duration(2000)
.text(function(d){return d})
.attr("x", function(d, i){return (i*(w/newArray.length) + 20)})
.attr("y", function(d) {return h - yScale(d) + 15})
.attr("font-family", "sans-serif")
.attr("fill", "white")
}, 3000)
Live bar chart on random number
I need to update the chart using the data from firebase. I already know how to connect firebase to js using the snapshot and have already tried it to no avail.
Also, need some help with the styling of the graph.
Please if anybody knows how I can finish this(its time sensitive).
Here's the code link in jsfiddle: Live bar chart d3
Thanks
Current situation: I already have a small multiple visualization for my data. What it represents is the stress intensity over time for six different days. It plots the graphs correctly. Now I wanted to add dots on the existing graph if the person smoked at that time. I am reading a csv file which consists of date, time, stress level and whether the person smoked or not (so 1 if they did and -1 if they didn't). I am using d3 v4.
This is what I am currently getting but the red dots are obviously in the wrong spot because they are showing up places I don't even have data.
What I wanted was for the red dots to be on the graph and represent the times the user smoked.
Code:
<script>
var margin = {top: 8, right: 10, bottom: 2, left: 10},
width = 1160 - margin.left - margin.right,
height = 100 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%H:%M:%S");
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var area = d3.area()
.x(function (d) {
return x(d.time);
})
.y0(height)
.y1(function (d) {
return y(d.stress);
});
var line = d3.line()
.x(function (d) {
return x(d.time);
})
.y(function (d) {
return y(d.stress);
});
d3.csv("6000smokedData3.csv", type, function (error, data) {
// Nest data by date.
var dates = d3.nest()
.key(function (d) {
return d.date;
})
.entries(data);
// Compute the maximum stress per date, needed for the y-domain.
dates.forEach(function (s) {
s.maxPrice = d3.max(s.values, function (d) {
return d.stress;
});
});
// Compute the minimum and maximum time across dates.
// We assume values are sorted by time.
x.domain([
d3.min(dates, function (s) {
return s.values[0].time;
}),
d3.max(dates, function (s) {
return s.values[s.values.length - 1].time;
})
]);
// Add an SVG element for each date, with the desired dimensions and margin.
var svg = d3.select("body").selectAll("svg")
.data(dates)
.enter().append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Add the scatterplot
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 4)
.style("fill", function (d) {
return "red";
})
.attr("cx", function (d) {
if (d.smoked == 1) {
return x(d.time);
}
})
.attr("cy", function (d) {
if (d.smoked == 1) {
return y(d.stress);
}
});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
// Add the area path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "area")
.attr("d", function (d) {
y.domain([0, d.maxPrice]);
return area(d.values);
});
// Add the line path elements. Note: the y-domain is set per element.
svg.append("path")
.attr("class", "line")
.attr("d", function (d) {
y.domain([0, d.maxPrice]);
return line(d.values);
});
// Add a small label for the date name.
svg.append("text")
.attr("x", width - 6)
.attr("y", height - 6)
.style("text-anchor", "end")
.text(function (d) {
return d.key;
});
});
function type(d) {
d.stress = +d.stress;
d.time = parseDate(d.time);
d.smoked = +d.smoked;
return d;
}
</script>
Few lines of csv file:
date,time,stress,smoked
2014-08-04,11:24:28,0.026191,-1
2014-08-04,11:24:29,0.026183,-1
2014-08-04,11:24:30,0.031845,-1
2014-08-04,11:24:31,0.01235,-1
Thank you
You're drawing the dots before you set the y scale for each element. I usually like to make small multiples inside of an each loop to avoid tricky things like. It looks like the y axis is also off - they should be different on each plot.
I'm trying to create a basic d3 pie chart with a legend. I'm following the examples in two different tutorials and somehow code from one example isn't playing well with the other. What I'm trying to do is set an ordinal scale's domain so I can use that to create a legend.
On the following line, I set the domain. If I step through the code, I can see that immediately after I get ["HEURISTIC", "ADWARE", "COMPANY_BLACK_LIST", "PUP", "SUSPECTED_MALWARE", "KNOWN_MALWARE"]. This is exactly what I want.
color.domain(labels)
However, if I keep stepping through, once I reach the following line, the domain changes to ["HEURISTIC", "ADWARE", "COMPANY_BLACK_LIST", "PUP", "SUSPECTED_MALWARE", "KNOWN_MALWARE", 0, 1, 2, 3, 4, 5]
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } )
.attr("d", arc);
QUESTION: What is causing those six extra items to be inserted into the domain?
Code (http://jsfiddle.net/tonicboy/2urZY/5/):
var w = 150,
h = 100,
r = 50,
color = d3.scale.category20c(),
dataset = [{"name":"HEURISTIC","value":65},{"name":"ADWARE","value":75},{"name":"COMPANY_BLACK_LIST","value":9},{"name":"PUP","value":34},{"name":"SUSPECTED_MALWARE","value":14},{"name":"KNOWN_MALWARE","value":156}],
labels = _.pluck(dataset, "name");
color.domain(labels);
var chart = d3.select("#pie_chart")
.append("svg:svg")
.data([dataset])
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", "0 0 " + w + " " + h)
.attr("preserveAspectRatio", "xMinYMin meet");
var vis = chart.append("g")
.attr("transform", "translate(" + (w - r) + "," + r + ")");
var arc = d3.svg.arc()
.outerRadius(r);
var pie = d3.layout.pie()
.value(function(d) { return d.value; });
var arcs = vis.selectAll("g.slice")
.data(pie)
.enter()
.append("svg:g")
.attr("class", "slice");
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } )
.attr("d", arc);
var legend = chart.append("g")
.attr("class", "pie-legend")
.selectAll("g")
.data(color.domain())
.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 7 + ")"; });
legend.append("rect")
.attr("width", 5)
.attr("height",5)
.style("fill", color);
legend.append("text")
.attr("x", 8)
.attr("y", 9)
.text(function(d) { return d; });
Here is what the chart looks like so far:
You're setting your ordinal scale domain with strings, but then calling it with index numbers. If you ask an ordinal scale for a value that isn't currently in its domain, it will add it to the domain and assign it the next value in the range (or recycle the range values if it runs out).
Original code:
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } )
.attr("d", arc);
Should be
arcs.append("svg:path")
.attr("fill", function(d, i) {return color( d.data.name); } )
.attr("d", arc);
The d value is the object created by the pie chart function; it stores the original data object as d.data. The name from that data is one of the values used in the color scale domain.
Updated fiddle: http://jsfiddle.net/2urZY/6/
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.
I have implemented the following graph with the edges rendered with d3.svg.diagonal(). However, when I try substituting the diagonal with d3.svg.line(), it doesn't appear to pull the target and source data. What am I missing? Is there something I don't understand about d3.svg.line?
The following is the code I am referring to, followed by the full code:
var line = d3.svg.line()
.x(function(d) { return d.lx; })
.y(function(d) { return d.ly; });
...
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",d3.svg.diagonal())
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
The entire code:
var margin = {top: 20, right: 20, bottom: 20, left: 20},
width =1500,
height = 1500,
diameter = Math.min(width, height),
radius = diameter / 2;
var balloon = d3.layout.balloon()
.size([width, height])
.value(function(d) { return d.size; })
.gap(50)
var line = d3.svg.line()
.x(function(d) { return d.lx; })
.y(function(d) { return d.ly; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + (margin.left + radius) + "," + (margin.top + radius) + ")")
root = "flare.json";
root.y0 = height / 2;
root.x0 = width / 2;
d3.json("flare.json", function(root) {
var nodes = balloon.nodes(root),
links = balloon.links(nodes);
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",d3.svg.diagonal())
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
var node = svg.selectAll("g.node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node");
node.append("circle")
.attr("r", function(d) { return d.r; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
node.append("text")
.attr("dx", function(d) { return d.x })
.attr("dy", function(d) { return d.y })
.attr("font-size", "5px")
.attr("fill", "white")
.style("text-anchor", function(d) { return d.children ? "middle" : "middle"; })
.text(function(d) { return d.name; })
});
A comparison of how the d attribute of the svg disappears when using "line."
Question is quite dated, but since I don't see an answer and someone might face the same problem, here it is.
The reason why simple replacement of diagonal with line is not working is because d3.svg.line and d3.svg.diagonal return different results:
d3.svg.diagonal returns function that accepts datum and its index and transforms it to path using projection. In other words diagonal.projection determines how the function will get points' coordinates from supplied datum.
d3.svg.line returns function that accepts an array of points of the line and transforms it to path. Methods line.x and line.y determine how coordinates of the point retreived from the single element of supplied array
D3 SVG-Shapes reference
SVG Paths and D3.js
So you can not use result of the d3.svg.line directly in d3 selections (at least when you want to draw multiple lines).
You need to wrap it in another function like this:
var line = d3.svg.line()
.x( function(point) { return point.lx; })
.y( function(point) { return point.ly; });
function lineData(d){
// i'm assuming here that supplied datum
// is a link between 'source' and 'target'
var points = [
{lx: d.source.x, ly: d.source.y},
{lx: d.target.x, ly: d.target.y}
];
return line(points);
}
// usage:
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",lineData)
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
Here's working version of jsFiddle mobeets posted: jsFiddle
I had the same problem...There's a jsFiddle here.
Note that changing line to diagonal will make it work.
Perhaps encapsulating the diagonal function and editing its parameters could work for you:
var diagonal = d3.svg.diagonal();
var new_diagonal = function (obj, a, b) {
//Here you may change the reference a bit.
var nobj = {
source : {
x: obj.source.x,
y: obj.source.y
},
target : {
x: obj.target.x,
y: obj.target.y
}
}
return diagonal.apply(this, [nobj, a, b]);
}
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",new_diagonal)
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
Just set the d attribute of link to line:
.attr("d", line)