Real time charts with D3.js - javascript

I am totally new to D3.js. I am trying to create a simple sparkline with time on the X axis and numbers upto 1000 on the Y axis. I have a web socket server which pushes random numbers unto 1000 to clients. I would like to plot a graph with these values on Y axis and time (in 24 hrs format) on the X axis. I tried a few things, but none of them work properly. My graph turns out vertical and is displayed only sometimes.
Appreciate any help in getting this working.
Below is what I have in my code, which is a tweaked version of an example I found online.
data[] is populated from the web socket call.
var margin = {top: 20, right: 20, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, data[data.length -1]])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 100])
.range([height, 0]);
var line = d3.svg.line()
.x(function(d, i) {
var dt = new Date();
var time = dt.getHours() + ":" + dt.getMinutes() + ":" + dt.getSeconds();
return dt.getSeconds();
})
.y(function(d, i) { console.log("D is " + d+ "Y is " + y(d));return y(d); });
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 + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + y(0) + ")")
.call(d3.svg.axis().scale(x).orient("bottom"));
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0," + x(0)+")")
.call(d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
redraw();
function redraw() {
path
.attr("d", line)
.attr("transform", null)
.transition()
.duration(500)
.ease("linear")
.attr("transform", "translate(" + x(-1) + ",0)")
.each("end", tick);
data.shift();
}
Could someone point me to a beginner's tutorial to D3 which will help me get started with creating different types of graphs?

It may help to have the structure of data you are using.
If you want to go for Angular+D3js there are a few libraries out ther that will help you get the job done quickly.
An simple library is:
http://angularjs-nvd3-directives.github.io/angularjs-nvd3-directives/
Another good set of directives which use D3.js is
http://krispo.github.io/angular-nvd3/#/
The Home page has a lot of examples you can check.
Although it lacks of documentation, every example has a plunker and you can check how it works.

Related

Setting x axis labels position with D3.js

I have a problem setting the x axis for a simple visualization made with D3.js.
I have something like the following image:
Obtained with this code (taken from an example I'm following):
var start_year = 2006, end_year = 2015;
var x = d3.scale.linear().range([0, width]);
var xAxis = d3.svg.axis().scale(x).orient("top");
var formatYears = d3.format("0000");
xAxis.tickFormat(formatYears);
var svg = d3.select("#page_content").append("svg")
.attr("width", "100%")
.attr("height", "100%")
.style("margin-left", 0)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain([start_year, end_year]);
var xScale = d3.scale.linear()
.domain([start_year, end_year])
.range([0, width]);
var g = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 0 + ")")
.call(xAxis);
The problem is that I'm dealing with football seasons, so I would like to have for the x axis domain something like "2006/2007", "2007/2008" and so on.
I tried by creating an array of strings and using it as x domain:
domain_data = ["2006-2007","2007-2008","2008-2009","2009-2010","2010-2011","2011-2012","2012-2013","2013-2014","2014-2015","2015-2016"];
var x = d3.scale.ordinal().rangeRoundBands([0, width]);
var xAxis = d3.svg.axis().scale(x).orient("top");
var svg = d3.select("#page_content").append("svg")
.attr("width", "100%")
.attr("height", "100%")
.style("margin-left", 0)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(domain_data.map(function(d) { return d; }));
var xScale = d3.scale.linear()
.domain([start_year, end_year])
.range([0, width]);
var g = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + 0 + ")")
.call(xAxis);
but, as you can see from the following image, each label is slightly shifted on the right or on the left with respect to the circles.
I'm very new to D3.js so if someone can explain me how to fix this problem, or maybe a better solution to achieve my goal, it'll be very helpful. Thanks in advance
** * EDIT: Added a JSFiddle * **
This is the JSFiddle you asked, hope it helps finding the solution. Thank you

Filter with d3.js

I have a big problem.
I use d3 to visualize my (poll)data. (So Questions and answers) It works fine except one problem.
Sometimes it doesn't visualize the data like they are. For example one time d3 get all right data about d3.json. But the graph is not right. It should load 300 answers in the first bars but it just show 50. I tested it and the json works.
I tried to filter the graph and I got the same problem.
This is my d3 code.
<div>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 430
},
width = 1400 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var xScale = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var yScale = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(10);
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("class", "chart");
var chart = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("getdata.php", function(error, data) {
xScale.domain(data.map(function(d) {
return d.text;
}));
yScale.domain([0, d3.max(data, function(d) {
return d.count;
})]);
chart.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return xScale(d.text);
})
.attr("y", function(d) {
return yScale(d.count);
})
.attr("height", function(d) {
return height - yScale(d.count);
})
.attr("width", xScale.rangeBand())
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Antworten");
});
function type(d) {
d.count = +d.text;
return d;
}
</script>
</div>
I guess the problem starts here. But I don't know why. The problem doesn't appear at every question. Only at a few or when I try to use my filter.
Maybe it happens when two values have a big difference?
Update:
It is like: D3 doesn't want to lose the smallest bar. So other bars must based on that smallest. And then the values are not correct for the bigger bars. Maybe the Y scale depends on the smallest bar.

How to apply the the enter() and data() methods for ds3 in-memory data

I have loaded data from a WebSocket connection into an array variable "data". I can see the 50 elements in the array and they do have the correct map elements.
The following snippet works properly: at the end the "data" elements have all rows transformed:
data.forEach(function (d) {
d[date] = parseDate(d[date]);
d[close] = +d[close];
});
Now, how to apply this data array to the internal d3 "values" so that the subsequent d3 dom manipulations use that data? In the next snippet I have made the attempt based on the examples / blogs I had seen:
var svg = d3.select("body").selectAll("svg")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
/* The following is NOT the correct place/way to do it.. need help here! */
.data(data)
.enter()
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
UPDATE Well I just tried moving those two data(data) and .enter() lines around - now placing them right after the selectAll().
var svg = d3.select("body").selectAll("svg")
.data(data)
.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 + ")")
The results? Well we do have data now ! Maybe too much of a good thing?
EDIT Here is the entire function
function updateD3(data) {
var WIDTH = 1800, HEIGHT = 800;
var margin = {top: 120, right: 20, bottom: 120, left: 100},
width = WIDTH - margin.left - margin.right,
height = HEIGHT - margin.top - margin.bottom;
var parseDate = d3.time.format("%m-%d-%Y %H").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom").ticks(31);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function (d) {
return x(d[date]);
})
.y(function (d) {
return y(d[close]);
});
var myNode = document.body;
while (myNode.firstChild) {
myNode.removeChild(myNode.firstChild);
}
var len = data.length;
console.log("data size=" + len + " date: " + data[0][date] + " close: " + data[0][close]
+ " last value: date: " + data[len-1][date] + " close: " + data[len-1][close]);
var date = "CALL_HOUR";
var close = "DROPPED_CALL";
data.forEach(function (d) {
d[date] = parseDate(d[date]);
d[close] = +d[close];
});
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 + "," + margin.top + ")");
x.domain(d3.extent(data, function (d) {
return d[date];
}));
y.domain(d3.extent(data, function (d) {
return d[close];
}));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-1.1em")
.attr("dy", ".15em")
.attr("transform", function (d) {
return "rotate(-65)"
});
svg.append("text") // text label for the x axis
.attr("x", width / 2)
.attr("y", height + margin.bottom)
.style("text-anchor", "middle")
.style("font-size", "14px")
.text("Call Date");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Dropped Calls");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "20px")
.style("text-decoration", "underline")
.text("No. of Dropped Calls vs Date Line Chart");
}
By biding your data to svg elements, you are creating as many svg elements as you have data items. I suspect that is not what you want. I created this FIDDLE exemplifying what you are doing but also showing how to bind the data to a single g.
Part of fiddle with data under a single g:
var data = [10,20,30];
var svg = d3.select("body")
.append("svg")
.attr("class","oneSvg")
.attr("width", 100)
.attr("height",100)
.append("g")
.attr("transform", "translate(0,0)");
circles = svg.selectAll("circle")
.data(data);
circles
.enter()
.append("circle")
.attr("cx",function (d) {return d;})
.attr("cy",20)
.attr("r",5)
.style("fill","blue");

Bars not appending in the DOM with d3.js barchart

I am attempting to build a very basic bar chart. Three bars. The data is correct. However, the bars do not display, nor are they showing up when I inspect the barchart with dev tools. However, the x-axis and y-axis render correctly, with the correct labels and tick marks.
function buildBarChart(data) {
var margin = {top: 20, right: 20, bottom: 30, left:40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var chart = d3.select(".barchart").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 + ")");
var mapped_values = _.map(data, function(d) {
return d.count;
});
x.domain(d3.keys(data));
y.domain([0, d3.max(mapped_values)]);
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Count");
_.each(data, function(d) {
console.log(d.name + " ++ " + (height - y(d.count)) + " ++ " + d.count);
});
chart.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.name) })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.count); })
.attr("height", function(d) { return height - y(d.count); });
};
The result of the console.log above is:
quotes ++ 90 ++ 16
subscriptions ++ 450 ++ 80
sponsored_licenses ++ 45 ++ 8
which is correct. I have been starting at this for hours and cannot figure out why everything displays (including the line charts on the same page) except for the bars.
Why are my bars not displaying?
The data you assign to a selection has to be an array, not a key:value object. You can use the d3.values(object) utility function to extract just the values as an array.
chart.selectAll(".bar")
.data(d3.values(data))
/* etc. */
However, the key values for the objects are no longer available in that form, and I notice that you're using them for the x-axis. If the keys are the same as the "name" of the data object that shouldn't be a problem. If not, the d3.entries() utility function returns both keys and values, but the values become a nested object, so you'd have to use d.value.name and d.value.count to access the original data.

D3 - No graph appearing

I have a variable called final csv which looks like and is stored in a CSV format. The lines are separated by a \n. I'm using the statement D3.csv.parse. Can anyone tell me why nothing appears on the page at all? :-
String, Float
value, 1
value2, 2
function generateGraph(finalcsv)
{
var finaldata = finalcsv;
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatPercent = d3.format(".0");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent);
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 + "," + margin.top + ")");
console.log(finaldata);
d3.csv.parse(finaldata, function(error, data) {
data.forEach(function(d) {
d.Float = +d.Float;
});
x.domain(data.map(function(d) { return d.String; }));
y.domain([0, d3.max(data, function(d) { return d.Float; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Integer");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.String); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.Float); })
.attr("height", function(d) { return height - y(d.Float); });
});
}
I quickly built an example which seems to work (or at least it shows something): http://jsfiddle.net/frXRW/2/
The first difference with the code you provided is that I didn't use the d3.csv.parse() function , I don't know if your problem comes from there but I suggest you take a look at the documentation if your problem comes from there: https://github.com/mbostock/d3/wiki/CSV
The second thing I changed, and that I suspect is the source of your problem, is that you forgot to put the #body identifier between "", so it is considered as an object. Thus I just changed the following line:
var svg = d3.select(#body).append("svg")
to
var svg = d3.select("#body").append("svg")
If this was your problem, then I would recommend you to use the chrome javascript console as it gave the following error pointing exactly to the line above:
Uncaught SyntaxError: Unexpected identifier

Categories