Live graphing of data with d3 using real data - node.js

I have seen lots of great demos for live graphing of data using D3.
http://bl.ocks.org/simenbrekken/6634070 is one I like. However, all of the examples I have seen use random generated values. I want to graph live data, and display the most recent values as an updating numeric display. I use a python script which writes data from sensor readings to csv files. The csv is 3 values on each line: unixtime,sensor1_value,sensor2_value. Every 5 seconds there is a new line of data added to a ring buffer file which has 720 lines of data. When the web page is displayed I want to read the 720 lines in the buffer file then update the graph with each new value which is written onto the end of the file. I could also create a file with just the new line of csv every 5 seconds so that the update was performed by always reading a file with just 1 line of csv rather than manipulating the entire buffer.
Does anyone know of an example, or the right code to achieve this?
The code for the above cited example is:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.graph .axis {
stroke-width: 1;
}
.graph .axis .tick line {
stroke: black;
}
.graph .axis .tick text {
fill: black;
font-size: 0.7em;
}
.graph .axis .domain {
fill: none;
stroke: black;
}
.graph .group {
fill: none;
stroke: black;
stroke-width: 1.5;
}
</style>
</head>
<body>
<div class="graph"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var limit = 60 * 1,
duration = 750,
now = new Date(Date.now() - duration)
var width = 500,
height = 200
var groups = {
current: {
value: 0,
color: 'orange',
data: d3.range(limit).map(function() {
return 0
})
},
target: {
value: 0,
color: 'green',
data: d3.range(limit).map(function() {
return 0
})
},
output: {
value: 0,
color: 'grey',
data: d3.range(limit).map(function() {
return 0
})
}
}
var x = d3.time.scale()
.domain([now - (limit - 2), now - duration])
.range([0, width])
var y = d3.scale.linear()
.domain([0, 100])
.range([height, 0])
var line = d3.svg.line()
.interpolate('basis')
.x(function(d, i) {
return x(now - (limit - 1 - i) * duration)
})
.y(function(d) {
return y(d)
})
var svg = d3.select('.graph').append('svg')
.attr('class', 'chart')
.attr('width', width)
.attr('height', height + 50)
var axis = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(x.axis = d3.svg.axis().scale(x).orient('bottom'))
var paths = svg.append('g')
for (var name in groups) {
var group = groups[name]
group.path = paths.append('path')
.data([group.data])
.attr('class', name + ' group')
.style('stroke', group.color)
}
function tick() {
now = new Date()
// Add new values
for (var name in groups) {
var group = groups[name]
//group.data.push(group.value) // Real values arrive at irregular intervals
group.data.push(20 + Math.random() * 100)
group.path.attr('d', line)
}
// Shift domain
x.domain([now - (limit - 2) * duration, now - duration])
// Slide x-axis left
axis.transition()
.duration(duration)
.ease('linear')
.call(x.axis)
// Slide paths left
paths.attr('transform', null)
.transition()
.duration(duration)
.ease('linear')
.attr('transform', 'translate(' + x(now - (limit - 1) * duration) + ')')
.each('end', tick)
// Remove oldest data point from each group
for (var name in groups) {
var group = groups[name]
group.data.shift()
}
}
tick()
</script>
</body>
</html>
Whereas the code I use for creating a static graph of one of the values (o2) from my csv versus a line:
<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 900 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
// Set the ranges
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(10);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(10);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.o2); });
// Adds the svg canvas
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 + ")");
// Get the data
d3.csv("./data/buffer.txt", function(error, data) {
data.forEach(function(d) {
d.time = +d.time;
d.o2 = +d.o2;
console.log(d.time);
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain([0, d3.max(data, function(d) { return d.o2; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
</script>
</body>

Related

D3 change map colors on select from dropdown

I am new to d3js and I need some help for my project. I want to my map to change color on dropdown change. I have added two maps in my code, default would be data/world-map.json, and I want to change to data/2000.json when 2000 is selected from dropdown menu. Any help would be appreciated.
Here is my code:
<!DOCTYPE html>
<meta charset="utf-8">
<title>Project</title>
<style>
.country {
stroke: white;
stroke-width: 0.5px;
}
.country:hover{
stroke: #fff;
stroke-width: 1.5px;
}
.text{
font-size:10px;
text-transform:capitalize;
}
#container {
margin:10px 10%;
border:2px solid #000;
border-radius: 5px;
height:100%;
overflow:hidden;
background: #F0F8FF;
}
.hidden {
display: none;
}
div.tooltip {
color: #222;
background: #fff;
padding: .5em;
text-shadow: #f5f5f5 0 1px 0;
border-radius: 5px;
box-shadow: 0px 0px 2px 0px #a6a6a6;
opacity: 0.9;
position: absolute;
}
.graticule {
fill: none;
stroke: #bbb;
stroke-width: .5px;
stroke-opacity: .5;
}
.equator {
stroke: #ccc;
stroke-width: 1px;
}
</style>
</head>
<body>
<h1 align="center">FIFA PLAYER OF THE YEAR 2000 - 2010</h1>
<select id="select_button" name="select_button">
<option value="0" selected="selected">Select year</option>
<option value="1">2000.</option>
<option value="2">2001.</option>
<option value="3">2002.</option>
<option value="4">2003.</option>
<option value="5">2004.</option>
<option value="6">2005.</option>
<option value="7">2006.</option>
<option value="8">2007.</option>
<option value="9">2008.</option>
<option value="10">2009.</option>
<option value="11">2010.</option>
</select>
<div id="container"></div>
<script src="js/d3.min.js"></script>
<script src="js/topojson.v1.min.js"></script>
<script>
d3.select(window).on("resize", throttle);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 9])
.on("zoom", move);
var width = document.getElementById('container').offsetWidth;
var height = width / 2;
var topo,projection,path,svg,g;
var currentKey = '0';
d3.select('#select_button').on('change', function(a) {
currentKey = d3.select(this).property('value');
draw(topo);
});
var graticule = d3.geo.graticule();
var tooltip = d3.select("#container").append("div").attr("class", "tooltip hidden");
setup(width,height);
function setup(width,height){
projection = d3.geo.mercator()
.translate([(width/2), (height/2)])
.scale( width / 2 / Math.PI);
path = d3.geo.path().projection(projection);
svg = d3.select("#container").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom)
.on("click", click)
.append("g");
g = svg.append("g");
}
d3.json("data/world-map.json", function(error, world) {
var countries = topojson.feature(world, world.objects.countries).features;
topo = countries;
draw(topo);
});
d3.json("data/2000.json", function(error, world) {
var countries2 = topojson.feature(world, world.objects.countries).features;
topo = countries2;
draw(topo);
});
d3.selectAll("path").remove();
function draw(topo) {
/* svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
g.append("path")
.datum({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]})
.attr("class", "equator")
.attr("d", path);
*/
var country = g.selectAll(".country").data(topo);
country.enter().insert("path")
.attr("class", "country")
.attr("d", path)
.attr("id", function(d,i) { return d.id; })
.attr("title", function(d,i) { return d.properties.name; })
.style("fill", function(d, i) { return d.properties.color; });
//offsets for tooltips
var offsetL = document.getElementById('container').offsetLeft+20;
var offsetT = document.getElementById('container').offsetTop+10;
//tooltips
country
.on("mousemove", function(d,i) {
var mouse = d3.mouse(svg.node()).map( function(d) { return parseInt(d); } );
tooltip.classed("hidden", false)
.attr("style", "left:"+(mouse[0]+offsetL)+"px;top:"+(mouse[1]+offsetT)+"px")
.html(d.properties.name);
})
.on("mouseout", function(d,i) {
tooltip.classed("hidden", true);
});
/*
//EXAMPLE: adding some capitals from external CSV file
d3.csv("data/country-capitals.csv", function(err, capitals) {
capitals.forEach(function(i){
addpoint(i.CapitalLongitude, i.CapitalLatitude, i.CapitalName );
});
}); */
}
function redraw() {
width = document.getElementById('container').offsetWidth;
height = width / 2;
d3.select('svg').remove();
setup(width,height);
draw(topo);
}
function move() {
var t = d3.event.translate;
var s = d3.event.scale;
zscale = s;
var h = height/4;
t[0] = Math.min(
(width/height) * (s - 1),
Math.max( width * (1 - s), t[0] )
);
t[1] = Math.min(
h * (s - 1) + h * s,
Math.max(height * (1 - s) - h * s, t[1])
);
zoom.translate(t);
g.attr("transform", "translate(" + t + ")scale(" + s + ")");
//adjust the country hover stroke width based on zoom level
d3.selectAll(".country").style("stroke-width", 0.5 / s);
}
var throttleTimer;
function throttle() {
window.clearTimeout(throttleTimer);
throttleTimer = window.setTimeout(function() {
redraw();
}, 200);
}
//geo translation on mouse click in map
function click() {
var latlon = projection.invert(d3.mouse(this));
console.log(latlon);
}
/*
//function to add points and text to the map (used in plotting capitals)
function addpoint(lat,lon,text) {
var gpoint = g.append("g").attr("class", "gpoint");
var x = projection([lat,lon])[0];
var y = projection([lat,lon])[1];
gpoint.append("svg:circle")
.attr("cx", x)
.attr("cy", y)
.attr("class","point")
.attr("r", 1.5);
//conditional in case a point has no associated text
if(text.length>0){
gpoint.append("text")
.attr("x", x+2)
.attr("y", y+2)
.attr("class","text")
.text(text);
}
}*/
</script>
</body>
</html>

Creating clickable legends with D3

I'm trying to create a multi-line chart with D3, but I'm stuck on toggling the visibility of lines off. So far, there is only one line, whilst I try to figure it all out (still a bit of a beginner with this), but I can't get the legend to appear, so I can't test if it'll actually get rid of the line too. Here is the JavaScript code:
var BlackBird = [{
"population": "100",
"year": "1970"
}, {
"population": "100.8",
"year": "1971"
}, {
"population": "103.5",
"year": "1972"
}, {
"population": "95.6",
"year": "1973"
}, {
"population": "101.7",
"year": "1974"
}, {
"population": "102",
"year": "1975"
}
];
var vis = d3.select("#visualisation"),
WIDTH = 1110,
HEIGHT = 580,
MARGINS = {
top: 30,
right: 20,
bottom: 20,
left: 50
},
xScale = d3.scale.linear()
.range([MARGINS.left, WIDTH - MARGINS.right])
.domain([1970,2008]),
yScale = d3.scale.linear()
.range([HEIGHT - MARGINS.top, MARGINS.bottom])
.domain([0,300]),
xAxis = d3.svg.axis()
.scale(xScale)
.ticks(25)
.tickFormat(d3.format('0f')),
yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(12);
vis.append("svg:g")
.attr("class", "axis")
.attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")")
.call(xAxis);
vis.append("svg:g")
.attr("class", "axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis);
var lineGen = d3.svg.line()
.x(function(d) {
return xScale(d.year);
})
.y(function(d) {
return yScale(d.population);
})
.interpolate("basis");
vis.append('path')
.attr('d', lineGen(BlackBird))
.attr('stroke-width', 5)
.attr('fill', 'none')
.attr('opacity', '0.2')
.attr("id", "aline");
vis.append("text")
.attr("x", WIDTH + MARGINS.left +10)
.attr("y", 10)
.attr("class", "legend")
.style("fill", "steelblue")
.on("click", function(){
var active = aline.active ? false : true,
newOpacity = active ? 0 : 1;
d3.select("#aline").attr("opacity", newOpacity);
aline.active = active;
})
.text("Blue Line");
HTML:
<!DOCTYPE html> <html lang= "en"> <head> <meta charset="UTF-8"> <title>D3 Birds</title> <link rel="stylesheet" href="D3Bird2.css"> </head> <body> <svg id="visualisation" width="1140" height="600"></svg> <div id ="BlaBird"> <img src="Blackbird.png" alt="A Blackbird" class= "Birdie"> </div> <script src="d3.min.js" charset="utf-8"> </script> <script src="script2.js" charset="utf-8"></script> </body> </html>
CSS:
.axis path {
fill: none;
stroke: #777;
shape-rendering: crispEdges;
}
.axis text {
font-family: Lato;
font-size: 13px;
}
#aline {
stroke: #000;
opacity: 0.5;
transition: 0.5s;
}
#aline: hover {
opacity: 1;
transition: 0.5s;
}
.Birdie {
transition: 0.5s;
opacity: 0.5;
}
#BlaBird {
position: absolute;
left: 1150px;
top: 30px;
}
.legend {
font-size: 16px;
font-weight: bold;
text-anchor: start;
}
First, you didn't include your HTML markup, but I'm assuming #visualisation is an SVG element. Make sure you've assigned it height and width attributes.
Second, you don't assign a stroke color to your line. I'm assuming you do this in CSS somewhere.
Third, after fixing your variable names, this line:
.attr("x", width + margin.left +10)
is the problem. This pushes the text element outside of it's SVG.
Here's an example where I've fixed some of this up.
EDITS
New problem after looking at your CSS. You are setting the attribute opacity in code, the CSS sets the style opacity. When both are set the browser will use the style. So change your click handler to:
.on("click", function() {
var active = aline.active ? false : true,
newOpacity = active ? 0 : 1;
d3.select("#aline").style("opacity", newOpacity); //<-- set style, not attr
aline.active = active;
})
Example updated.

Replacing a d3.js path transition with a new one?

I've seen this question and many other examples, but it isn't helping. I'm trying the last example on this page and after 5 seconds, I want the curved path that is being drawn, to completely disappear and 5 more seconds later, I want a new path to be created.
I've tried the below code, but although the entire svg element itself is removed, when I use appendGraph() to created the svg and the path again, the same old path re-appears. How can I ensure that the old path is completely removed and that the tick function also does not get called when the graph is removed?
The fiddle is here: http://jsfiddle.net/nav9/5uygqj9v/
And the code is:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<style>
svg {
font: 10px sans-serif;
}
.noselect {
/* these are to disable text selection */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
opacity: 0.5;
shape-rendering: crispEdges;
}
rect.zoom {
stroke: steelblue;
fill-opacity: 0.3;
}
#placeholder {margin: 10px 5px 15px 70px;}
</style>
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
</head>
<body>
<div id="placeholder" ></div>
<script>
//---------globals
var timer = null, interval = 500, value = 0;
var value1 = 0;
var n = 143, duration = interval, now = new Date(Date.now() - duration), count = 0, data = d3.range(n).map(function() { return 0; });
var margin = {top: 20, right: 40, bottom: 50, left: 60}, width = 580 - margin.right, height = 420 - margin.top - margin.bottom;
var x = d3.time.scale().domain([now - (n - 2) * duration, now - duration]).range([0, width]);
var y = d3.scale.linear().domain([-1, 1]).range([height, 0]);
var line = d3.svg.line().interpolate("basis")
.x(function(d, i) { return x(now - (n - 1 - i) * duration); })
.y(function(d, i) { return y(d); });
var svg, path, yaxis, axis;
//--------program starts
appendGraph();
tick();
value1 = 0;
setTimeout(function() {removeGraph();}, 5000);
setTimeout(function() {addGraphAgain();}, 10000);
//-------------------------------functions -------------------------------
function appendGraph()
{
svg = d3.select("body").select("#placeholder").append("p").append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "mainSVG")
.style("margin-left", -margin.left + "px")
.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);
axis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
yaxis = svg.append("g")
.attr("class", "y axis")
.call(y.axis = d3.svg.axis().scale(y).orient("left"));
path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.data([data])
.attr("id", "line1")
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", "1.5px")
.style("visibility","visible");
}//appendGraph
//TODO: These tick functions could be simplified to handle more lines on the graph
function tick()
{
// push the accumulated count onto the back, and reset the count
value1 = Math.random() * 100;
if (value1 >= 0) {data.push(value1);} else {data.push(0);}//ensure that no NaN or undefined values corrupt the range
// update the domains
now = new Date();
x.domain([now - (n - 2) * duration, now - duration]);
count = 0;
// redraw the lines
svg.select("#line1").attr("d", line).attr("transform", null);
// slide the line left
path.transition().duration(duration).ease("linear").attr("transform", "translate(" + x(now - (n - 1) * duration) + ")").each("end", tick);
y.domain([0, 100]);
y = d3.scale.linear().domain([0, 100]).range([height, 0]);
yaxis.call(y.axis = d3.svg.axis().scale(y).orient("left"));
// pop the old data point off the front
data.shift();
console.log("tick being called");
}
function removeGraph()
{
path.transition().duration(0).each(function() { this.__transition__.active = 0; });//at least this is stopping tick from being called
svg.selectAll("*").remove();
//-------tried these too
// d3.select("#mainSVG").remove("svg");
// d3.select("#line1").remove("path");
// path.remove();
//d3.selectAll("path").attr("d", "Z");
console.log("REMOVED");
}//removeGraph
function addGraphAgain()
{
appendGraph();
tick();
value1 = 0;
console.log("ADDED AGAIN");
}//addGraphAgain
</script>
</body>
</html>
Not an exact answer to this question, but since the reason I asked this was because I wanted to have phases where I wanted to send null inputs to the graph and there seemed no other way to do it other than to remove the line and replace it with a new line.
The trick to handle null or NaN data or missing data in d3.js or to simply not display data for a while is to use defined.
A working example of it here and in the line transition, it's like this:
I supply a random number at if (counter%5==0) ran = null;data.push(ran); and .defined(function(d) { return d != null; }) takes care of the null, by not drawing a line there.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.line {
fill: none;
stroke: #000;
stroke-width: 1.5px;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var n = 40,
random = d3.random.normal(0, .2),
data = d3.range(n).map(random);
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, n - 1])
.range([0, width]);
var y = d3.scale.linear()
.domain([-1, 1])
.range([height, 0]);
var line = d3.svg.line()
.defined(function(d) { return d != null; })
.x(function(d, i) { return x(i); })
.y(function(d, i) { 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")
.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);
var counter = 0;
tick();
function tick()
{
// push a new data point onto the back
var ran = random();
counter++;
if (counter%5==0) ran = null;
data.push(ran);
// redraw the line, and slide it to the left
path
.attr("d", line)
.attr("transform", null)
.transition()
.duration(500)
.ease("linear")
.attr("transform", "translate(" + x(-1) + ",0)")
.each("end", tick);
// pop the old data point off the front
data.shift();
}
</script>

can't get globe to rotate in d3

I'm trying to get a d3 globe to rotate to a particular country when you click that country in a list. To start out, I'm trying to get the following example working (I got it from http://bl.ocks.org/KoGor/5994804), but it throws an error TypeError: world is undefined from line 100. Can anyone help, please?:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Earth globe</title>
<script src="./d3/d3.v3.min.js"></script>
<script src="./d3/topojson.v1.min.js"></script>
<script src="./d3/queue.v1.min.js"></script>
</head>
<style type="text/css">
.water {
fill: #00248F;
}
.land {
fill: #A98B6F;
stroke: #FFF;
stroke-width: 0.7px;
}
.land:hover {
fill:#33CC33;
stroke-width: 1px;
}
.focused {
fill: #33CC33;
}
select {
position: absolute;
top: 20px;
left: 580px;
border: solid #ccc 1px;
padding: 3px;
box-shadow: inset 1px 1px 2px #ddd8dc;
}
.countryTooltip {
position: absolute;
display: none;
pointer-events: none;
background: #fff;
padding: 5px;
text-align: left;
border: solid #ccc 1px;
color: #666;
font-size: 14px;
font-family: sans-serif;
}
</style>
<body>
<script>
var width = 600,
height = 500,
sens = 0.25,
focused;
//Setting projection
var projection = d3.geo.orthographic()
.scale(245)
.rotate([0, 0])
.translate([width / 2, height / 2])
.clipAngle(90);
var path = d3.geo.path()
.projection(projection);
//SVG container
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
//Adding water
svg.append("path")
.datum({type: "Sphere"})
.attr("class", "water")
.attr("d", path);
var countryTooltip = d3.select("body").append("div").attr("class", "countryTooltip"),
countryList = d3.select("body").append("select").attr("name", "countries");
queue()
.defer(d3.json, "http://bl.ocks.org/KoGor/raw/5685937/world-110m.json")
.defer(d3.tsv, "http://bl.ocks.org/KoGor/raw/5685937/world-110m-country-names.tsv")
.await(ready);
//Main function
function ready(error, world, countryData) {
var countryById = {},
countries = topojson.feature(world, world.objects.countries).features;
//Adding countries to select
countryData.forEach(function(d) {
countryById[d.id] = d.name;
option = countryList.append("option");
option.text(d.name);
option.property("value", d.id);
});
//Drawing countries on the globe
var world = svg.selectAll("path.land")
.data(countries)
.enter().append("path")
.attr("class", "land")
.attr("d", path)
//Drag event
.call(d3.behavior.drag()
.origin(function() { var r = projection.rotate(); return {x: r[0] / sens, y: -r[1] / sens}; })
.on("drag", function() {
var rotate = projection.rotate();
projection.rotate([d3.event.x * sens, -d3.event.y * sens, rotate[2]]);
svg.selectAll("path.land").attr("d", path);
svg.selectAll(".focused").classed("focused", focused = false);
}))
//Mouse events
.on("mouseover", function(d) {
countryTooltip.text(countryById[d.id])
.style("left", (d3.event.pageX + 7) + "px")
.style("top", (d3.event.pageY - 15) + "px")
.style("display", "block")
.style("opacity", 1);
})
.on("mouseout", function(d) {
countryTooltip.style("opacity", 0)
.style("display", "none");
})
.on("mousemove", function(d) {
countryTooltip.style("left", (d3.event.pageX + 7) + "px")
.style("top", (d3.event.pageY - 15) + "px");
});
//Country focus on option select
d3.select("select").on("change", function() {
var rotate = projection.rotate(),
focusedCountry = country(countries, this),
p = d3.geo.centroid(focusedCountry);
svg.selectAll(".focused").classed("focused", focused = false);
//Globe rotating
(function transition() {
d3.transition()
.duration(2500)
.tween("rotate", function() {
var r = d3.interpolate(projection.rotate(), [-p[0], -p[1]]);
return function(t) {
projection.rotate(r(t));
svg.selectAll("path").attr("d", path)
.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
};
})
})();
});
function country(cnt, sel) {
for(var i = 0, l = cnt.length; i < l; i++) {
if(cnt[i].id == sel.value) {return cnt[i];}
}
};
};
</script>
</body>
</html>

d3 Gives a 406 Error when trying to run d3js example on IIS

I have setup a basic IIS server and am trying to demonstrate a d3js example. First I create an html page with the example code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
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()
.rangeRound([height, 0]);
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
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 + ")");
d3.csv("data.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "State"; }));
data.forEach(function(d) {
var y0 = 0;
d.ages = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.ages[d.ages.length - 1].y1;
});
data.sort(function(a, b) { return b.total - a.total; });
x.domain(data.map(function(d) { return d.State; }));
y.domain([0, d3.max(data, function(d) { return d.total; })]);
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("Population");
var state = svg.selectAll(".state")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; });
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
var legend = svg.selectAll(".legend")
.data(color.domain().slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
</script>
and then i create the data.csv file:
State,Under 5 Years,5 to 13 Years,14 to 17 Years,18 to 24 Years,25 to 44 Years,45 to 64 Years,65 Years and Over
AL,310504,552339,259034,450818,1231572,1215966,641667
AK,52083,85640,42153,74257,198724,183159,50277
AZ,515910,828669,362642,601943,1804762,1523681,862573
AR,202070,343207,157204,264160,754420,727124,407205
CA,2704659,4499890,2159981,3853788,10604510,8819342,4114496
CO,358280,587154,261701,466194,1464939,1290094,511094
CT,211637,403658,196918,325110,916955,968967,478007
DE,59319,99496,47414,84464,230183,230528,121688
DC,36352,50439,25225,75569,193557,140043,70648
FL,1140516,1938695,925060,1607297,4782119,4746856,3187797
GA,740521,1250460,557860,919876,2846985,2389018,981024
HI,87207,134025,64011,124834,356237,331817,190067
ID,121746,201192,89702,147606,406247,375173,182150
IL,894368,1558919,725973,1311479,3596343,3239173,1575308
IN,443089,780199,361393,605863,1724528,1647881,813839
IA,201321,345409,165883,306398,750505,788485,444554
KS,202529,342134,155822,293114,728166,713663,366706
KY,284601,493536,229927,381394,1179637,1134283,565867
LA,310716,542341,254916,471275,1162463,1128771,540314
ME,71459,133656,69752,112682,331809,397911,199187
MD,371787,651923,316873,543470,1556225,1513754,679565
MA,383568,701752,341713,665879,1782449,1751508,871098
MI,625526,1179503,585169,974480,2628322,2706100,1304322
MN,358471,606802,289371,507289,1416063,1391878,650519
MS,220813,371502,174405,305964,764203,730133,371598
MO,399450,690476,331543,560463,1569626,1554812,805235
MT,61114,106088,53156,95232,236297,278241,137312
NE,132092,215265,99638,186657,457177,451756,240847
NV,199175,325650,142976,212379,769913,653357,296717
NH,75297,144235,73826,119114,345109,388250,169978
NJ,557421,1011656,478505,769321,2379649,2335168,1150941
NM,148323,241326,112801,203097,517154,501604,260051
NY,1208495,2141490,1058031,1999120,5355235,5120254,2607672
NC,652823,1097890,492964,883397,2575603,2380685,1139052
ND,41896,67358,33794,82629,154913,166615,94276
OH,743750,1340492,646135,1081734,3019147,3083815,1570837
OK,266547,438926,200562,369916,957085,918688,490637
OR,243483,424167,199925,338162,1044056,1036269,503998
PA,737462,1345341,679201,1203944,3157759,3414001,1910571
RI,60934,111408,56198,114502,277779,282321,147646
SC,303024,517803,245400,438147,1193112,1186019,596295
SD,58566,94438,45305,82869,196738,210178,116100
TN,416334,725948,336312,550612,1719433,1646623,819626
TX,2027307,3277946,1420518,2454721,7017731,5656528,2472223
UT,268916,413034,167685,329585,772024,538978,246202
VT,32635,62538,33757,61679,155419,188593,86649
VA,522672,887525,413004,768475,2203286,2033550,940577
WA,433119,750274,357782,610378,1850983,1762811,783877
WV,105435,189649,91074,157989,470749,514505,285067
WI,362277,640286,311849,553914,1487457,1522038,750146
WY,38253,60890,29314,53980,137338,147279,65614
When I access the page, nothing displays...A quick look through fiddler shows the html content downloads fine (you can also see it in show source). You can also look at the data.csv file directly by accessing it from the url
http://localhost/data.csv
The problem shown in fiddler is a 406 error when d3js attempts to load the CSV file. Any ideas?
Thanks
What does your HTTP Request's Accept header contain? Apache is probably configured in such a way so as to return a 406 because the value in the Accept header does not include whatever MIME type your CSV is returning.
See
http://blogs.msdn.com/b/ieinternals/archive/2011/03/27/http-406-not-acceptable-php-ie9-standards-mode-accepts-only-text_2f00_css-for-stylesheets.aspx for a similar problem sometimes seen in browsers.
Change the build action for the csv file to "Resource" in the file's properties using your Visual Studio.

Resources