5

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.

2 Answers 2

5

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>
Sign up to request clarification or add additional context in comments.

1 Comment

thanks for the post but this does not accurately solve the problem. The REST call I make returns thousands of results which shows the problem immediately. The main issue is that as I have nested my data on both the activity and the time, it was getting a little tricky to return the correct minimum and maximum values that I needed for scaling.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.