Using data from Socket.io for data visualisation with d3 - javascript

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

Related

Passing $(this) into d3 selector

How may I pass the self $(this) parameter into the d3 selector?
http://jsfiddle.net/6q0vvsja/
function d3_bar(self, dataset, barPadding) {
var w = parseInt(d3.select(".barChart").style("width"),10), // select(self)
h = parseInt(d3.select(".barChart").style("height"),10); // select(self)
var svg = d3.select(".barChart") // select(self)
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d * 4;
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
}
$('.barChart').each(function(i) {
var self = $(this),
nums = self.data('value').split(',').map(Number);
d3_bar(self, nums, 1);
});
Your self variable is a jQuery object and D3js will expect a selector or DOM Element (Node).
d3.select(selector)
Selects the first element that matches the specified selector string, returning a single-element selection. If no elements in the current document match the specified selector, returns the empty selection. If multiple elements match the selector, only the first matching element (in document traversal order) will be selected.
d3.select(node)
Selects the specified node. This is useful if you already have a reference to a node, such as d3.select(this) within an event listener, or a global such as document.body. This function does not traverse the DOM.
-
Selecting Elements
...These methods can also accept nodes, which is useful for integration with third-party libraries such as jQuery
To get the underlying JavaScript DOM element from a jQuery object you can just do
$('#myElem')[0];
So in your case you can pass in the JavaScript DOM element to the d3.select like so
var svg = d3.select(self[0])...
Why and how this works is explained here.
function d3_bar(self, dataset, barPadding) {
var w = parseInt(d3.select(".barChart").style("width"),10);
var h = parseInt(d3.select(".barChart").style("height"),10);
var svg = d3.select(self[0])
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d * 4;
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
}
$('.barChart').each(function(i) {
var self = $(this),
nums = self.data('value').split(',').map(Number);
d3_bar(self, nums, 1);
});
.barChart:first-child {
height:200px;
width:500px;
}
.barChart:last-child {
height:10px;
width:50px;
}
<div class="barChart" data-value="5,10,13,19,21,25,22,18,15,13,11,12,15,20,18,17,16,18,23,25"></div>
<div class="barChart" data-value="1,5,2,2,5,1,0,7,5,3"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://alignedleft.com/content/03-tutorials/01-d3/d3/d3.v3.min.js"></script>
Do not mix up jQuery object with d3.js. var self = $(this), self is a jQuery object, d3.js does not recognize it.

D3 - How to loop through an object with keys for a bar chart

I am trying to create a bar chart with the dataset below. I am stuck on the part where the height[score] of the bar[country] is determined. How do I loop through the dataset to pull each score for a different country?
Any help would be greatly appreciated :)
var w = 500;
var h = 100;
var barPadding = 1;
var dataset = [
{"country":"Hong Kong","score":8.98},
{"country":"Singapore","score":8.54},
{"country":"New Zealand","score":8.19},
{"country":"Switzerland","score":8.09},
{"country":"Mauritius","score":8.98},
{"country":"United Arab Emirates","score":8.05},
{"country":"Canada","score":8.00},
{"country":"Australia","score":7.87},
{"country":"Jordan","score":7.86},
{"country":"Chile","score":7.84},
];
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d * 4;
});
In D3, once you load the data through the .data(dataset) command, you can now access each record of the data by inserting the anonymous function function(d, i) { } as you have done in a few of your attributes.
Since your dataset is:
var dataset = [
{"country":"Hong Kong","score":8.98},
{"country":"Singapore","score":8.54},
{"country":"New Zealand","score":8.19},
{"country":"Switzerland","score":8.09},
{"country":"Mauritius","score":8.98},
{"country":"United Arab Emirates","score":8.05},
{"country":"Canada","score":8.00},
{"country":"Australia","score":7.87},
{"country":"Jordan","score":7.86},
{"country":"Chile","score":7.84},
];
each d is a object record e.g. {"country":"Singapore","score":8.54}, while i refers to the index of the object d returned e.g. 1 for our example of d used above.
To access the score of the object record d, this becomes simple Javscript object notation i.e. d.score.
Hence your .attr call should look like:
.attr("height", function(d) {
return d.score * 4;
});
Similarly, you can extract the other fields e.g. country with d.country if you intend to use it in .attr("text", function(d) { return d.country; });
This is the real beauty and power of D3. If you ever want to expand your visualization with more features that is obtained through your data, then all you have to make sure is that your dataset data contains more data attributes, and you can call them later as you iterate through the anonymous functions. And D3 is in the spirit of its name, truly being "data-driven"! :)
You will need to fix d to d.score.
If you want to show country text, write svg.selectAll("text") after svg.selectAll("rect").
Like this:
var w = 500;
var h = 100;
var barPadding = 1;
var dataset = [
{"country":"Hong Kong","score":8.98},
{"country":"Singapore","score":8.54},
{"country":"New Zealand","score":8.19},
{"country":"Switzerland","score":8.09},
{"country":"Mauritius","score":8.98},
{"country":"United Arab Emirates","score":8.05},
{"country":"Canada","score":8.00},
{"country":"Australia","score":7.87},
{"country":"Jordan","score":7.86},
{"country":"Chile","score":7.84},
];
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d.score * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d.score * 4;
});
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d.country;
})
.attr("transform", function(d, i) {
var barW = w / dataset.length;
return "translate(" +
( barW * i + barW / 2 + barPadding ) + "," +
( h - 5 ) +
")rotate(-90)";
})
.attr("font-size", "8pt")
.attr("fill", "white");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Something like
For( var i =0; i<dataset.length; i++){
// Dataset[i].country
// dataset[i].score
}
You have an array of objects

D3 Javascript / SVG Part III ISsue

I'm following the part III tutorial of "Let' Make Some Charts" as an introduction to D3. Part of the tutorial calls for data insertion via TSV. Given I don't see this being an eventual use case for me, I'm attempting to modify the tutorial with the code below using a simple javascript array. However, nothing shows up on the page when I render in the browser. Can anyone shed some light on this?
Here's the tutorial link for some reference to the original code: http://bost.ocks.org/mike/bar/3/
My JS code:
<script>
var data = [4,8,15,16,23,42,57,89,100,160];
var width = 960,
height = 500; // have to make sure variables are case sensitive
var y = d3.scale.linear()
.domain([0, d3.max(data)]) // scaling based on max value
.range([height, 0]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height);
var barWidth = width / data.length;
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d,i) { return "translate(" + i * barWidth + ",0)";});
bar.append("rect")
.attr("y", function(d) { return y(d.value); })
.attr("width", barWidth - 1)
.attr("height", function(d) { return height - y(d.value); });
bar.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d.value) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.value; });
function type(d) {
d.value = +d.value;
return d;
}
</script>
The code you've copied references a named attribute value to determine what to draw. The data you've created doesn't have this but just the data. So everywhere you have d.value, you need to reference just d.
Complete demo here.
Your problem stems from the fact that you're using an Array of numbers for your data, while in Mike Bostock's example he was using an Array of Objects (for example, var data = [{value: 30}, ...]). Thus you need to change all cases of d.value to d in your code, since your data is not longer an Object but just a number.
bar.append("rect")
.attr("y", function(d) { return y(d); }) // <---- delete .value
.attr("width", barWidth - 1)
.attr("height", function(d) { return height - y(d); }); // <---- delete .value
bar.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d) + 3; }) // <---- delete .value
.attr("dy", ".75em")
.text(function(d) { return d; }); // <---- delete .value
Making these changes produces the following bar chart:

graph the data from a csv file

This page needs to display a graph that reads the data from a CSV file.
I have been following a tutorial on TheCodingTutorials.
I'm also trying to follow the Multi-Column Data tutorial so that i can add the name to the graph. This is where i'm getting lost, the tutorial make it sound easy but i just don't get it. Every time i try to edit the code it errors out.
It works perfectly if you only want to read a single column csv file.
However I want to read a multiple columns csv file.
Also if there is something that could make it better please let me know.
<html>
<head>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript">
<html>
<head>
<meta http-equiv="Expires" content="-1">
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript">
function timedRefresh(timeoutPeriod) {
setTimeout("location.reload(true);",timeoutPeriod);
{
d3.text("data2.csv", function(unparsedData)
{
var data = d3.csv.parseRows(unparsedData);
//Create the SVG graph.
var svg = d3.select("body").append("svg").attr("width", "100%").attr("height", "100%");
var dataEnter = svg.selectAll("rect").data(data).enter();
var graphHeight = 450;
var barWidth = 20;
var barSeparation = 10;
var maxData = 105;
var horizontalBarDistance = barWidth + barSeparation;
var textYOffset = horizontalBarDistance / 2 - 12;
var textXOffset = 20;
var barHeightMultiplier = graphHeight / maxData;
//Draw the bars.
dataEnter.append("rect").attr("y", function(d, i)
{
return i * horizontalBarDistance;
}).attr("x", function(d)
{
return 100;
}).attr("height", function(d)
{
return barWidth;
}).attr("width", function(d)
{
return d * barHeightMultiplier;
});
//Draw the text.
dataEnter.append("text").text(function(d)
{
return d;
}).attr("y", function(d, i)
{
return i * horizontalBarDistance + textXOffset;
}).attr("x");
});
};
}
</script>
</head>
<body onLoad="JavaScript:timedRefresh(10000);">
</body>
</html>
My CSV file now looks like this
names,data
john,78
brad,105
amber,103
james,2
dean,74
pat,45
matt,6
andrew,18
ashley,15
==================================================================================
UPDATE
==================================================================================
Thanks to all your help this is my updated code.
<html>
<head>
<meta http-equiv="Expires" content="-1">
<script type="text/javascript" src=".\JavaScripts\d3.v3.min.js"></script>
<script type="text/javascript">setTimeout(function(){window.location.href='index2.html'},120000);
d3.csv("./data/data.csv", function(data){
//Create the SVG graph.
var svg = d3.select("#graph").append("svg").attr("width", "1800").attr("height", "600");
var dataEnter = svg.selectAll("rect").data(data).enter();
var graphWidth = 800;
var barWidth = 40;
var barSeparation = 30;
var maxData = 2;
var horizontalBarDistance = barWidth + barSeparation;
var textYOffset = 25;
var barXOffset = 260;
var barYOffset = 5;
var numXOffset = 230;
var barHeightMultiplier = graphWidth / maxData;
var fontSize = "30px";
var color = d3.scale.category10();
//Draw the bars.
dataEnter.append("rect")
.attr("fill",function(d,i){return color(i);})
.attr("y", function(d, i){return i * horizontalBarDistance - barYOffset;})
.attr("x", barXOffset)
.attr("height", function(d){return barWidth;})
.attr("width", function(d){return d.data * barHeightMultiplier;});
//Draw the text.
dataEnter.append("text")
.text(function(d){return d.Name;})
.attr("font-size", fontSize)
.attr("font-family", "sans-serif")
.attr("y", function(d, i){return i * horizontalBarDistance + textYOffset;})
.attr("x");
//Draw the numbers.
dataEnter.append("text")
.text(function(d){return d.data;})
.attr("font-size", fontSize)
.attr("font-family", "sans-serif")
.attr("y", function(d, i){return i * horizontalBarDistance + textYOffset;})
.attr("x", numXOffset);
//Draw the Target bar
dataEnter.append("rect")
.attr("fill", "red")
.attr("y", function(d, i){return i * horizontalBarDistance;})
.attr("x", barXOffset + graphWidth)
.attr("height", 70)
.attr("width", 10);
});
</script>
<style type="text/css">
#title {
font-family:sans-serif;
font-size: 50px;
color:#000;
text-decoration: underline;
text-align: center;
width: 100%;
position:relative;
margin-top:20;
}
#graph {
overflow:hidden;
margin-top:40;
}
</style>
</head>
<body>
<div id="title">Graph 1</div>
<div id="graph"></div>
</body>
</html>
Because your data contains a header row as its first row, you should be using d3.csv.parse instead of d3.csv.parseRows. The CSV documentation explains the differences.
The result of parsing will be something that looks like this:
[
{"names": "john", "data": 78},
{"names": "brad", "data": 105},
...
]
So, when you use this data to create your rect elements, you get an object bound to each rect. Then when you use selection.attr or selection.style the d value you are passed will be the bound object. This means you will need to reference the property you want, either as d.names or d.data. Each column in the file will be a different property on the object (as shown above).
One other thing to consider is possibly replacing d3.text with d3.csv to retrieve the file and parse the data in one step.

What is String or where does this come from?

While trying to understand d3 I saw the line .text(String);. I could not understand what String is suppose to be. I thought maybe its an empty string (nope), a method (i didnt see that in the api reference) and pondered what else it could be.
I commented it out below and got expected results. What I don't understand is what is String and why does it work. With this line my 3 squared boxes has text (its a internal value of the data it will represent later) while commented out it does not.
Demo
Html
<div class='chart' id='chart-10'/>
<script src="http://d3js.org/d3.v3.min.js"></script>
JS:
var w = 360;
var h = 180;
var svg = d3.select("#chart-10").append("svg")
.attr("width", w)
.attr("height", h);
var g = svg.selectAll(".data")
.data([50,150,250])
.enter().append("g")
.attr("class", "data")
.attr("transform", function(d, i) { return "translate(" + 20 * (i + 1) + ",20)"; });
g.append("circle")
.attr("class", "little")
.attr("r", 1e-6);
g.append("rect")
.attr("x", -10)
.attr("y", -10)
.attr("width", 20)
.attr("height", 20)
.style("fill", "lightgreen")
.style("stroke", "green");
g.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
;// .text(String);
g.attr("transform", function(d, i) { return "translate(" + 20 * (i + 1) + ",20)"; });
g.select("rect").style("opacity", 1);
g.select("circle").attr("r", 1e-6);
var t = g.transition().duration(750);
t.attr("transform", function(d, i) { return "translate(" + d + ",90)"; });
t.select("circle").attr("r", Math.sqrt);
t.select("rect").style("opacity", 1e-6);
It looks like the String constructor. According to d3 documentation, as pointed out by Matt:
if value is a function, then the function is evaluated for each selected element (in order), being passed the current datum d and the current index i, with the this context as the current DOM element. The function's return value is then used to set each element's text content.
So, you set g.data to [50,150,250] a few lines before. Each number is converted to a String object by the String constructor, returned and used as the text values of your DOM nodes.

Categories