So I am following along on this tutorial for D3JS, I have used javascript some in the past but I really wanted to broaden my horizons and learn how to make these really awesome data driven websites. Plus I am currently working in a place that will in the future require the DDD model and I want to be up to speed when that comes about. However while following this tutorial I get this crazy error
Error: value of <"rect"> = "NaN", Error:Value of <"text"> = "Nan"
in my inspect console and nothing renders on the page (obviously because it isn't properly getting the data that it needs to display anything), I have tried removing the type function and using the parseInt on the return values in text and rect, as well as leaving the type function and just parsing that value and returning it but nothing seems to be working. I am using a tsv to get the data from and have done everything that the tutorial said to do. Just for giggles I thought hmmm, I have an error, let me try their code. So I copy pasted their code into my code block (not just the pieces that were throwing the error but the entire thing!!!), and got the same error that mine got... hahahahaha now I just want to know what is going on so that I can fix it and move forward with my bar graph extravaganza. any thoughts???
<!DOCTYPE html>
<%#page contentType="text/html" pageEncoding="UTF-8"%>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Stacked Bar Graph</title>
<script src="http://d3js.org/d3.v3.min.js"></script>
<div class="chart">
<script>
d3.tsv("data.tsv", type, function(data) {
var width = 420,
barHeight = 20;
var x = d3.scale.linear()
.range([ 0, width ])
.domain([ 0, d3.max(data, function(d) {
return d.value;
}) ]);
var chart = d3.select(".chart")
.append("svg")
.attr("width", width)
.attr("height", barHeight * data.length);
var bar = chart.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform", function(d, i) {
return "translate(0," + i * barHeight + ")";
});
bar.append("rect").attr("width", function(d) {
return x(d.value);
}).attr("height", barHeight - 1).attr("fill", "steelblue");
bar.append("text").attr("x", function(d) {
return x(d.value) - 3;
}).attr("y", barHeight / 2).attr("dy", ".35em").attr("fill",
"white").attr("font", "10px san-serif").attr("text-anchor",
"end").text(function(d) {
return d.value;
});
});
function type(d) {
d.value = +d.value;
return d;
}
</script>
</div>
data.tsv file
name value
Locke 4
Reyes 8
Ford 15
Jarrah 16
Shepard 23
Kwon 42
Make sure the values in the TSV are separated by tabs, not spaces.
Related
I don't know why, according to the topojson version (may be) I have:
TypeError: t is undefined
An explanation could be nice! (I use the last version of topojson.)
Here an example of TypeError is undefined (pointing to topojson file)
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="https://unpkg.com/d3#5.0.0/dist/d3.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
</head>
<body>
<svg width="960" height="600"></svg>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var unemployment = d3.map();
var path = d3.geoPath();
var x = d3.scaleLinear()
.domain([1, 10])
.rangeRound([600, 860]);
var color = d3.scaleThreshold()
.domain(d3.range(2, 10))
.range(d3.schemeBlues[9]);
var g = svg.append("g")
.attr("class", "key")
.attr("transform", "translate(0,40)");
g.selectAll("rect")
.data(color.range().map(function(d) {
d = color.invertExtent(d);
if (d[0] == null) d[0] = x.domain()[0];
if (d[1] == null) d[1] = x.domain()[1];
return d;
}))
.enter().append("rect")
.attr("height", 8)
.attr("x", function(d) { return x(d[0]); })
.attr("width", function(d) { return x(d[1]) - x(d[0]); })
.attr("fill", function(d) { return color(d[0]); });
g.append("text")
.attr("class", "caption")
.attr("x", x.range()[0])
.attr("y", -6)
.attr("fill", "#000")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Unemployment rate");
g.call(d3.axisBottom(x)
.tickSize(13)
.tickFormat(function(x, i) { return i ? x : x + "%"; })
.tickValues(color.domain()))
.select(".domain")
.remove();
var files = ["https://d3js.org/us-10m.v1.json", "unemployment.tsv"];
var promises1 = d3.json("https://d3js.org/us-10m.v1.json");
var promises2 = d3.tsv("unemployment.tsv");
Promise.all([promises1, promises2]).then(function(us){
console.log(us[0]);
console.log(us[1]);
svg.append("g")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(us, us[0].objects.counties).features)
.enter().append("path")
.attr("fill", function(d) { return color(d.rate = unemployment.get(d.id)); })
.attr("d", path)
.append("title")
.text(function(d) { return d.rate + "%"; });
svg.append("path")
.datum(topojson.mesh(us, us[0].objects.states, function(a, b) { return a !== b; }))
.attr("class", "states")
.attr("d", path);
});
</script>
</body>
</html>
My code : https://plnkr.co/edit/EzcZMSEQVzCt4uoYCLIc?p=info
Original (d3js v4 + Topojson v2) : https://bl.ocks.org/mbostock/4060606
Here an another example of TypeError is undefined (pointing to topojson file)
My code : https://plnkr.co/edit/o1wQX3tvIDVxEbDtdVZP?p=preview
The two examples have two separate issues in relation to topojson.
In the first example you update where the topojson is held from us to us[0] due to the change in how files are fetched. However, you haven't quite updated the code to reflect this change:
In Original: .data(topojson.feature(us, us.objects.counties).features)
In Question: .data(topojson.feature(us, us[0].objects.counties).features)
And fixed: .data(topojson.feature(us[0], us[0].objects.counties).features)
Updated plunkr.
However, the issue in the second example is a little different.
topojson.feature requires two parameters, a topology and an object. The topology is the variable holding the json, which you have correct. However, the object is not arcs. The variable holding the topojson has a property called objects, and in that there will always be at least one property representing a feature collection (states, counties, etc). This object (or one of these objects) is what we want.
Here is a snippet of your topojson:
... "objects":{"dep_GEN_WGS84_UTF8":{"type":"GeometryCollection","geometries":[{"arcs ..."
We want topojson.feature(data,data.objects.dep_GEN_WGS84_UTF8).
If making topojson with tools such as mapshaper, the object we want to display is the same as the name of the file used to create it. Generally, a quick word search through the topojson for "object" will also get you to the proper object pretty quick.
The arcs property in a topojson is convenient storage for the pieces that make up the features, not the features themselves.
Updated plunkr.
In both cases the topology parameter passed to topojson.feature won't contain the specified features, generating the same error.
I'm trying to make a scatter plot using a .json file. It will let the user to select which group of data in the json file to be displayed. So I'm trying to use the update pattern.
The following code will make the first drawing, but every time selectGroup() is called(the code is in the html file), nothing got updated. The console.log(selection) did come back with a new array each time, but the enter and exit property of that selection is always empty.
Can anyone help me take a look? Thanks a lot!
var margin = {
top: 30,
right: 40,
bottom: 30,
left: 40
}
var width = 640 - margin.right - margin.left,
height = 360 - margin.top - margin.bottom;
var dataGroup;
var groupNumDefault = "I";
var maxX, maxY;
var svg, xAxis, xScale, yAxis, yScale;
//select and read data by group
function init() {
d3.json("data.json", function (d) {
maxX = d3.max(d, function (d) {
return d.x;
});
maxY = d3.max(d, function (d) {
return d.y;
});
console.log(maxY);
svg = d3.select("svg")
.attr("id", "scatter_plot")
.attr("width", 960)
.attr("height", 500)
.append("g")
.attr("id", "drawing_area")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//x-axis
xScale = d3.scale.linear().range([0, width]).domain([0, maxX]);
xAxis = d3.svg.axis()
.scale(xScale).orient("bottom").ticks(6);
//y-axis
yScale = d3.scale.linear().range([0, height]).domain([maxY, 0]);
yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(6);
svg.append("g")
.attr("class", "x_axis")
.attr("transform", "translate(0," + (height) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y_axis")
.call(yAxis);
});
selectGroup(groupNumDefault);
}
//update data
function selectGroup(groupNum) {
d3.json("/data.json", function (d) {
dataGroup = d.filter(function (el) {
return el.group == groupNum;
});
console.log(dataGroup);
drawChart(dataGroup);
});
}
//drawing function
function drawChart(data) {
var selection = d3.select("svg").selectAll("circle")
.data(data);
console.log(selection);
selection.enter()
.append("circle")
.attr("class", "dots")
.attr("cx", function (d) {
console.log("updating!");
return xScale(d.x);
})
.attr("cy", function (d) {
return yScale(d.y);
})
.attr("r", function (d) {
return 10;
})
.attr("fill", "red");
selection.exit().remove();
}
init();
The problem here is on two fronts:
Firstly, your lack of a key function in your data() call means data is matched by index (position in data array) by default, which will mean no enter and exit selections if the old and current datasets sent to data() are of the same size. Instead, most (perhaps all) of the data will be put in the update selection when d3 matches by index (first datum in old dataset = first datum in new dataset, second datum in old dataset = second datum in new dataset etc etc)
var selection = d3.select("svg").selectAll("circle")
.data(data);
See: https://bl.ocks.org/mbostock/3808221
Basically, you need your data call adjusted to something like this (if your data has an .id property or anything else that can uniquely identify each datum)
var selection = d3.select("svg").selectAll("circle")
.data(data, function(d) { return d.id; });
This will generate enter() and exit() (and update) selections based on the data's actual contents rather than just their index.
Secondly, not everything the second time round is guaranteed be in the enter or exit selections. Some data may be just an update of existing data and not in either of those selections (in your case it may be intended to be completely new each time). However, given the situation just described above it's pretty much guaranteed most of your data will be in the update selection, some of it by mistake. To show updates you will need to alter the code like this (I'm assuming d3 v3 here, apparently it's slightly different for v4)
selection.enter()
.append("circle")
.attr("class", "dots")
.attr("r", function (d) {
return 10;
})
.attr("fill", "red");
// this new bit is the update selection (which includes the just added enter selection
// now, the syntax is different in v4)
selection // v3 version
// .merge(selection) // v4 version (remove semi-colon off preceding enter statement)
.attr("cx", function (d) {
console.log("updating!");
return xScale(d.x);
})
.attr("cy", function (d) {
return yScale(d.y);
})
selection.exit().remove();
Those two changes should see your visualisation working, unless of course the problem is something as simple as an empty set of data the second time around which would also explain things :-)
I am trying to make an interactive bar chart in D3.js
I uploaded everything to github for easy reference. I also included index.html at the end of my question.
My starting point is data.json containing an array of 7 items (i.e. countries). Each country has an attribute 'name' and four other attributes. These represent the exposition of private banks and the state to Greek debt for the years 2009 and 2014.
My goal is to create a bar chart that starts by showing the exposition of each country's banks and public sector in 2009 (so two bars for each country) and that changes to the year 2014 once the user clicks on the appropriate button.
I had managed to make it all work nicely! However, I had to create manually separate lists for each (sub-)dataset I needed to use. For example I created one called y2009 which included the exposition of bank and state for country 1, then the same for country 2, an so on..
(I left one of the list and commented it out on line 43)
I wanted to make my code more flexible so I created a for loop that extracts the data and creates the lists for me. (see lines 46-60). This did not work because the for loops would start before the data was actually loaded. Hence I would end up with empty lists.
So I grouped the for loops into a function (prepare()) and executed that function within the function that loads the data (lines 18-30). This fixed that issue...
..and created a new one! The two functions that should set the scales (see lines 67-73) do not work because their calculations require on one of the lists created by the for loops (namely 'total').
(I assume this is due to the list being created after the scale methods are called.)
The curious thing is that if I run the script, then copy in the console the xScale and yScale functions, and then copy the draw function (lines 101-212) everything works.
Hence I tried to group everything into functions (e.g. setScales, draw) so that I would call them in the order I want at the end of the script (lines 214-215) but this creates problem because certain variables (e.g. xScale and yScale) need to be global.
I also tried to first create them in the global space and then modify them through setScales. This did not work either.
Summing up, wait I don't understand is:
In which order should I write the code to make things work(again)? Is it a good idea to wrap operations within functions (e.g. setting the scales, drawing bars and labels) and then calling the function in the right order?
Which type of object is created with the scale method? I am confused on whether they are actual functions.
I hope this was not too much of a pain to read and thank everyone who made it through!
Fede
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="d3.min.js"></script>
</head>
<body>
<p>Introductory text here!</p>
<p>
<button id="change2009"> 2009 </button>
<button id="change2014"> 2014 </button>
</p>
<div id="country"></div>
<script type="text/javascript">
d3.json("data.json", function(error, json) {
if (error) {
console.log(error);
} else{
console.log(json);
dataset=json;
}
prepare (dataset);
});
//load data
var dataset;
var bank09=[];
var state09=[];
var bank14=[];
var state14=[];
var y2009=[];
var y2014=[];
var total=[];
var xScale;
var yScale;
//var total = [4.76, 0, 0.12, 6.36, 4.21, 0, 0.04, 7.96, 78.82, 0, 1.81, 46.56, 45, 0, 13.51, 61.74, 6.86, 0, 1.06, 40.87, 12.21, 0, 1.22, 13.06, 1.21, 0, 0.39, 27.35];
function prepare (dataset){
for (i in dataset) {bank09.push(dataset[i].bank09);
state09.push(dataset[i].state09);
bank14.push(dataset[i].bank14);
state14.push(dataset[i].state14);
y2009.push(dataset[i].bank09);
y2009.push(dataset[i].state09);
y2014.push(dataset[i].bank14);
y2014.push(dataset[i].state14);
total.push(dataset[i].bank09);
total.push(dataset[i].state09);
total.push(dataset[i].bank14);
total.push(dataset[i].state14);
}
}
//overwrite dataset
dataset2=y2009;
//scales
function setScales () {
var xScale = d3.scale.ordinal()
.domain(d3.range(total.length/2))
.rangeRoundBands([0, w], 0.1);
var yScale = d3.scale.linear()
.domain([0, d3.max(total)])
.range([0, h]);
console.log(yScale(89));
}
//layout
var w = 600;
var h = 600;
var barPadding = 1;
//coountry names
var country = ["Austria", "Belgium", "France", "Germany", "Italy", "Holland", "Spain"];
d3.select("#country")
.data(country)
.enter()
.append("rect")
.attr("class", "country")
//.append("text")
//.text(function(d){
// return d;
// })
//draw svg
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
function draw () {
//draw bars
svg.selectAll("rect")
.data(dataset2)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d){
return h - yScale(d);
})
.attr("width", xScale.rangeBand)
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", "black");
//add labels
svg.selectAll("text")
.data(dataset2)
.enter()
.append("text")
.text(function(d){
return d;
})
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "12px")
.attr("fill", "red")
.attr("x", function(d, i){
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
if (d<3) {
return h - 15;
} else {
return h - yScale(d) + 15;}
})
//interactivity
d3.select("#change2014")
.on("click", function() {
//update data
dataset2=y2014;
//update bars
svg.selectAll("rect")
.data(dataset2)
.transition()
.duration(3000)
.attr("y", function(d){
return h - yScale(d);
})
.attr("height", function(d) {
return yScale(d);
})
//update labels
svg.selectAll("text")
.data(dataset2)
.transition()
.duration(3000)
.text(function(d){
return d;
})
.attr("x", function(d, i){
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
if (d<3) {
return h - 15;
} else {
return h - yScale(d) + 15;}
})
})
d3.select("#change2009")
.on("click", function() {
//update data
dataset2=y2009;
//update bars
svg.selectAll("rect")
.data(dataset2)
.transition()
.duration(3000)
.attr("y", function(d){
return h - yScale(d);
})
.attr("height", function(d) {
return yScale(d);
})
//update labels
svg.selectAll("text")
.data(dataset2)
.transition()
.duration(3000)
.text(function(d){
return d;
})
.attr("x", function(d, i){
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
if (d<3) {
return h - 15;
} else {
return h - yScale(d) + 15;}
})
})
}
setScales ();
draw();
</script>
In which order should I write the code to make things work(again)? Is
it a good idea to wrap operations within functions (e.g. setting the
scales, drawing bars and labels) and then calling the function in the
right order?
As Lars pointed out, you can put everything inside the d3.json callback. This is because you only want to start rendering with D3 once you have the data. The d3.json method is asynchronous, which means that after you call d3.json(), the code afterwards will execute first before the function inside the d3.json method has finished. Check out http://rowanmanning.com/posts/javascript-for-beginners-async/ for more on asynchronous behavior in Javascript.
Given that you only want to start rendering when the d3.json method has completed, you could also just organize the other parts of your code into smaller functions and call some sort of initializer function from within the d3.json success callback, sort of like what you are doing with the prepare function. This is a cleaner approach and starts taking you towards a model-view paradigm.
Which type of object is created with the scale method? I am confused
on whether they are actual functions.
The scale method does return a function, but with additional functions added to its prototype. Try printing out "someScale.prototype" to see all of the various methods you can use. I'd also highly recommend Scott Murray's tutorial on D3. Here is the chapter on scales: http://alignedleft.com/tutorials/d3/scales
I wonder how I can use the data from a database received via Socket.io from a Node.js server for a data visualisation with d3.
I´ve read some posts about using PHP and JSON for that. Do I really need JSON? And how can I do this without the need of PHP? Actually the data received from Socket.io is already stored in the array values_array. But this array is not accepted by d3.
So far I tried a getter function and tried to rewrite the array – without success.
Any advice would be appreciated.
Below you see the client-side HTML-code:
!doctype html>
<html>
<head>
<script src='//code.jquery.com/jquery-1.7.2.min.js'></script>
<script src='//localhost:3000/socket.io/socket.io.js'></script>
<script type="text/javascript" src="d3.v3.min.js"></script>
<script>
window.onload=function(){
var socket = io();
var values_array = new Array();
socket.on('server2browser', function(data) // receive
{
fkt_values_array(data);
});
function fkt_values_array(data)
{
$.each(data, function(i, obj)
{
values_array[i] = obj.numbers;
});
$('#arrayprint_values').text(values_array);
}
setTimeout(function()
{
dynamicData = values_array;
}, Math.random() * 1000);
dynamicData = [22,33,33]; // This works
// dynamicData = values_array; // But I can´t get data from Socket.io into d3
// Data visualisation (d3)
var dataset = dynamicData;
//Width and height
var w = 500;
var h = 200;
var barPadding = 1;
var svg = d3.select("#diagram")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("y", function(d) {
return h - (d * 4); //Höher
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d * 4;
})
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("fill", function(d) {
return "rgb(" + (d * 10) + ", 0, " + (d * 10) + ")";
});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return i * (w / dataset.length) + (w / dataset.length - barPadding) / 2;
})
.attr("y", function(d) {
return h - (d * 4) + 14; //15 is now 14
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white")
.attr("text-anchor", "middle");
}
</script>
</head>
<body>
<div id='arrayprint_values'> Placeholder Text</div> // Here the array is successfully printed out
<div id="diagram"> Placeholder </div> // Here the diagram is displayed, but only with static data
</body>
</html>
Well, everything in JavaScript is JSON so it just kind of makes sense to use it.
And you don't need PHP. You seem to already have a Node server up, just use that :). However, your valuesArray is instantiated to an empty list and then reassigned a bunch of times to other objects. You may want to change that to increase code clarity. A possible error source is that D3 expects an array of numbers, but your function is getting strings or JSON. That is, either ["1", "2"] or [{number : 1}, {number : 2}] as opposed to [1,2]. Place console.log(obj.numbers) in fkt_values_array and see what it looks like
I am new to d3.js, currently I am working in a project which needs d3.js. I am facing a problem need help / guidance to solve this.
The problem is in my svg container I have 2 hard-coded rectangles [created with svg.append("rect")].
Now I have a json dataset which has 2 data to create another 2 rectangles[ there might be more data in json at runtime, this is static example].
Now when I use the following code it only creates 1 rectangles.
Notice The selectAll function on svg selects the other 2 previously created rectangles and only dynamically creates the extra 1 rectangles (3 in json - 2 hardcoded rectangles = 1 rectangles created).
But I want that all the data (3) in json should created a rectangle that is 3 rectangle should be created by Json and the hardcoded rectangles should remain as it is.
But it is not doing that, I have added the html code this post. Here is my javascript and HTML what i have tried so far.
Please help.
<html>
<head>
<meta charset="ISO-8859-1">
<title>D3 Test</title>
<script type="text/javascript" src="d3/d3.v3.min.js"></script>
</head>
<body>
<h1>hello....</h1>
<div id="chartDiv">
</div>
<script type="text/javascript">
var w = 1000;
var h = 400;
var svgContainer = d3.select("#chartDiv").append("svg").attr("width", w).attr("height", h);
var call_result_dataset = [{"x_axis":40, "y_axis":10, "width":5, "height":110, "color":"green"},
{"x_axis":700, "y_axis":10, "width":5, "height":110, "color":"red"},
{"x_axis":760, "y_axis":10, "width":5, "height":110, "color":"red"}];
var call_result_strip = svgContainer.append("rect")
.attr("id", "call_result_strip")
.attr("x", 10)
.attr("y", 10)
.attr("width", 980)
.attr("height", 110)
.attr("fill", "rgb(235,235,235)");
var call_type_strip = svgContainer.append("rect")
.attr("id", "call_type_strip")
.attr("x", 10)
.attr("y", 200)
.attr("width", 980)
.attr("height", 110)
.attr("fill", "rgb(235,235,235)");
var call_result_strip_bars = svgContainer.selectAll("rect")
.data(call_result_dataset)
.enter()
.append("rect");
var all_r_strip = call_result_strip_bars.attr("x", function(d){ return d.x_axis;})
.attr("y", function(d){ return d.y_axis;})
.attr("width", function(d){ return d.width;})
.attr("height", function(d){ return d.height;})
.attr("fill", function(d){ return d.color;})
.attr("id", function(d,i) {return "result_"+i;});
</script>
</body>
</html>
You can use the following snippet:
svgContainer.selectAll(".dynamicRects")
.data(call_result_dataset)
.enter()
.append("rect")
.addClass('dynamicRects')
...
This way, the enter selection will first be empty and then the three dynamic rects will be created.
selecting nothing wit some class name will suit your requirment.
svgContainer.selectAll()
.data(call_result_dataset)
.enter()
.append("rect");
it is recommended to use a class name for the selection and the code goes like..
svgContainer.selectAll(".someClass")
.data(call_result_dataset)
.enter()
.append("rect").classed("someClass",true);