Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use D3.js to take an array of Javascript objects use them in a line graph?

The web application I'm working on has a REST interface that returns and array of objects similar to this:

[{"id":110, "time":1360580745797, "userName":"pinky", "activity":"respawn"},
{"id":111, "time":1360580745797, "userName":"perky", "activity":"change direction"},
{"id":112, "time":1360580745797, "userName":"clyde", "activity":"caught pacman"},
{"id":113, "time":1360580745797, "userName":"perky", "activity":"respawn"},
{"id":114, "time":1360580745797, "userName":"perky", "activity":"caught pacman"},
{"id":115, "time":1360580745797, "userName":"clyde", "activity":"respawn"}]

I'd like to use the data to render a line graph which has a line for each activity and shows the total sum of that activity for each day. The id is not used and at this stage the userName is not used (however I would like to potentially filter the results on username later).

I've looked at a using map reduce functions to sort through the data but have since been trying to use the nesting feature of D3.

I currently have some code that looks a little like this:

d3.json(url, function(error, json) {
    if (error) return console.warn(error);

    console.log(json);

    d3.nest()
        .key(function(d) { return d.activity; })
        .key(function(d) {return d3.time.format('%x')(new Date(d.time));})
        .entries(json);

};

This groups my json data into an array of objects keyed by activity and then in turn keyed by date. I next need to sum the amount of activities for that day.

From here though I'm not entirely sure how to get my data into format that can be used by the d3 line graph.

like image 469
samael Avatar asked Feb 20 '13 10:02

samael


2 Answers

The fact that I was dealing with objects rather than an array of values was causing some complications.

One of the main problems was accurately getting min and max values. After sorting my data I was able to get around this by using the following:

var min = d3.min(nestedData, function(datum) {
    return d3.min(datum.values, function(d) { return d.values.length; });
}),
max = d3.max(nestedData, function(datum) {
    return d3.max(datum.values, function(d) { return d.values.length; });
});

The other issue I had was that the data I am dealing with will not always exist. As new activities are added to the web application I connect to, the line graph needs to be able to handle the data starting from a point along the x axis.

I've got it working now using some of the code from @cmonkey

It seems to handle any amount of data I throw at it. With a small selection of data it looks a little like this:

enter image description here

The nest step is to handle zooming in to a specific section of time. I currently display all activity (dating back about 4 months), so to make the graph more useful I need to be able to select a particular month or week.

like image 26
samael Avatar answered Oct 05 '22 11:10

samael


Below is a solution. You may need to change the d3 url, but it should work otherwise.

Problems: If there are no activities for a day, they do not display as a '0' in the line. They are skipped. It would probably be better to insert the zero values in.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 Demo: SVG with data</title>
<script type="text/javascript" src="js/d3.v3.js"></script>
<style type="text/css">

body { font: 10px sans-serif;}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.line {
  fill: none;  stroke-width: 1.5px;
}
</style>
</head>
<body>
<script type="text/javascript">

var dataset = [{"id":110, "time":1360580745797, "userName":"pinky", "activity":"respawn"},
               {"id":111, "time":1360580745798, "userName":"perky", "activity":"change direction"},
               {"id":111, "time":1360580745799, "userName":"perky", "activity":"change direction"},
               {"id":111, "time":1360580745797, "userName":"perky", "activity":"change direction"},
               {"id":111, "time":1361580745797, "userName":"perky", "activity":"change direction"},
               {"id":111, "time":1361580745798, "userName":"perky", "activity":"change direction"},
               {"id":112, "time":1360580745797, "userName":"clyde", "activity":"caught pacman"},
               {"id":113, "time":1360580745797, "userName":"perky", "activity":"respawn"},
               {"id":114, "time":1360580745797, "userName":"perky", "activity":"caught pacman"},
               {"id":114, "time":1361580745797, "userName":"perky", "activity":"caught pacman"},
               {"id":114, "time":1362580745797, "userName":"perky", "activity":"caught pacman"},
               {"id":114, "time":1363580745797, "userName":"perky", "activity":"caught pacman"},
               {"id":110, "time":1361580745797, "userName":"pinky", "activity":"respawn"},
               {"id":115, "time":1360580745797, "userName":"clyde", "activity":"respawn"}]


var margin = {top: 20, right: 80, bottom: 30, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

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");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var color = d3.scale.category10();

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 + ")");

var dateFormat = d3.time.format("%x");

var nestedData = d3.nest()
    .key(function(d) { return d.activity; })
    .key(function(d) {return dateFormat(new Date(d.time));})
    .entries(dataset);

drawMainGraph();

function drawMainGraph() {

    var line = d3.svg.line()
        .interpolate("linear")
        .x( function(d) {return x(dateFormat.parse(d.key)) } )
        .y( function(d) {return y(d.values.length) } );

    x.domain( d3.extent( dataset, function(d) { return new Date(d.time) } ) );
    y.domain( [ 0, d3.max( nestedData, function(d) { return d.values.length } ) ]);

    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("Count");


    var activityLine = svg.selectAll(".activity")
      .data( nestedData )
      .enter()
        .append("g")
        .attr("class", "activity")
        .attr("id", function(d) { return "activity-" + d.key } );

    activityLine.append("path")
      .attr("class", "line")
      .attr("d", function(d) { return line(d.values); } )
      .style("stroke", function(d) { return color(d.key); } );
}

</script>
</body>
</html>
like image 139
cmonkey Avatar answered Oct 05 '22 11:10

cmonkey