block by ariaz c0593a62ab32d99da603

Leanpub Book Download Scatterplot

Full Screen

hello world

forked from d3noob‘s block: Leanpub Book Download Scatterplot

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body { font: 12px sans-serif; }

.axis path,
.axis line {
  fill: none;
  stroke: grey;
  stroke-width: 1;
  shape-rendering: crispEdges;
}

.dot { stroke: none; }

.grid .tick { stroke: lightgrey; opacity: 0.7; }
.grid path { stroke-width: 0;}

path { 
    stroke: #1f78b4;
    stroke-width: 1.5;
    fill: none;
}

text.shadow {
    stroke: white;
    stroke-width: 2.5px;
    opacity: 0.9;
}

</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>

// functions to parse the date / time formats
parseDate = d3.time.format("%Y-%m-%d").parse;
parseTime = d3.time.format("%H:%M:%S").parse;
formatDate = d3.time.format("%d-%b"),
formatTime = d3.time.format("%H:%M"),
bisectDate = d3.bisector(function(d) { return d.date; }).left; 

// Load the raw data
d3.json("downloads.json", function(error, events) {

    // parse and format all the event data
    events.forEach(function(d) {
        d.dtg = d.dtg.slice(0,-4)+'0:00'; // get the 10 minute block
        dtgSplit = d.dtg.split(" ");      // split on the space
        d.date = dtgSplit[0];             // get the date seperatly
        d.time = dtgSplit[1];             // format the time
        d.number_downloaded = 1;          // Number of downloads
    });

    // get the scatterplot data and nest the data by date/time
    var data = d3.nest()
        .key(function(d) { return d.dtg;})
        .rollup(function(d) {
            return d3.sum(d,function(g) {return g.number_downloaded; });
            })
        .entries(events);

    // format the date/time data
    data.forEach(function(d) {
        d.dtg = d.key;                   // get the 10 minute block
        dtgSplit = d.dtg.split(" ");     // split on the space
        d.date = parseDate(dtgSplit[0]); // get the date seperatly
        d.time = parseTime(dtgSplit[1]); // format the time
        d.number_downloaded = d.values;  // Number of downloads
    });

    // nest the data by date for the daily graph
    var dataDate = d3.nest()
        .key(function(d) { return d.date;})
        .rollup(function(d) {
            return d3.sum(d,function(g) {return g.number_downloaded; });
            })
        .entries(events);

    // format the date data
    dataDate.forEach(function(d) {
        d.date = parseDate(d.key); // format the date
        d.close = d.values;        // Number of downloads
    });

    // nest the data by 10 minute intervals for the time graph
    var dataTime = d3.nest()
        .key(function(d) { return d.time;})
        .sortKeys(d3.ascending)
        .rollup(function(d) {
            return d3.sum(d,function(g) {return g.number_downloaded; });
            })
        .entries(events);

    // format the time data
    dataTime.forEach(function(d) {
        d.time = d.key;             // get the 10 minute block
        d.time = parseTime(d.time); // get the date seperatly
        d.close = d.values;         // Number of downloads
    });

    // Get number of days in date range to calculate scatterplotWidth
    var oneDay = 24*60*60*1000; // hours*minutes*seconds*milliseconds
    var dateStart = d3.min(data, function(d) { return d.date; });
    var dateFinish = d3.max(data, function(d) { return d.date; });
    var numberDays = Math.round(Math.abs((dateStart.getTime() -
                               dateFinish.getTime())/(oneDay)));

    var margin = {top: 20, right: 20, bottom: 20, left: 50},
        scatterplotHeight = 520,
        scatterplotWidth = numberDays * 1.5,
        dateGraphHeight = 220,
        timeGraphWidth = 220;

    // Set the dimensions of the canvas / graph
    var height = scatterplotHeight + dateGraphHeight,
        width = scatterplotWidth + timeGraphWidth;

    // ************* draw the scatterplot ****************

    var formatDay_Time = d3.time.format("%H:%M");     // tooltip time
    var formatWeek_Year = d3.time.format("%d-%m-%Y"); // tooltip date

    var x = d3.time.scale().range([0, scatterplotWidth]);
    var y = d3.time.scale().range([0, scatterplotHeight]);

    var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom")
        .ticks(7);

    var yAxis = d3.svg.axis()
        .scale(y)
        .orient("right")
        .ticks(12,0,0)
        .tickFormat(d3.time.format("%H:%M"));

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

    // State the functions for the grid
    function make_x_axis() {
        return d3.svg.axis()
          .scale(x)
          .orient("bottom")
          .ticks(12)
    }

    // State the functions for the grid
    function make_y_axis() {
        return d3.svg.axis()
          .scale(y)
          .orient("right")
          .ticks(8)
    }
            
    // Set the domains
    y.domain([new Date(1899, 12, 02, 0, 0, 0), 
              new Date(1899, 12, 01, 0, 0, 1)]);
    x.domain(d3.extent(data, function(d) { return d.date; }));

    // tickSize: Get or set the size of major, minor and end ticks
    svg.append("g").classed("grid x_grid", true)
        .attr("transform", "translate(0," 
            + (scatterplotHeight + dateGraphHeight) + ")")
        .style("stroke-dasharray", ("3, 3, 3"))
        .call(make_x_axis()
            .tickSize(-scatterplotHeight - dateGraphHeight, 0, 0)
            .tickFormat(""))

    // tickSize: Get or set the size of major, minor and end ticks
    svg.append("g").classed("grid y_grid", true)
        .attr("transform", "translate(0,0)")
        .style("stroke-dasharray", ("3, 3, 3"))
        .call(make_y_axis()
            .tickSize(+scatterplotWidth+timeGraphWidth, 0, 0)
            .tickFormat(""))

    // Draw the Axes and the tick labels
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + scatterplotHeight + ")")
        .call(xAxis)
      .selectAll("text")
        .style("text-anchor", "middle");

    svg.append("g")
        .attr("class", "y axis")
        .attr("transform", "translate("  + scatterplotWidth +  ",0)")
        .call(yAxis)
      .selectAll("text");

    // draw the plotted circles
    svg.selectAll(".dot")
        .data(data)
      .enter().append("circle")
        .attr("class", "dot")
        .attr("r", function(d) { return d.number_downloaded*1.5; })
        .style("opacity", 0.3)
        .style("fill", "#e31a1c" )
        .attr("cx", function(d) { return x(d.date); })
        .attr("cy", function(d) { return y(d.time); })
            ;    

    // ************* draw the date graph *****************
    // Set the ranges
    var xDate = d3.time.scale().range([0, scatterplotWidth]);
    var yDate = d3.scale.linear().range([dateGraphHeight, 0]);

    var yAxisDate = d3.svg.axis().scale(yDate)
        .orient("right").ticks(5);

    // Define the line
    var valueline = d3.svg.line()
        .interpolate("basis")
        .x(function(d) { return xDate(d.date); })
        .y(function(d) { return yDate(d.close); } );  

    // Scale the range of the data
    xDate.domain(d3.extent(dataDate, function(d) { return d.date; }));
    yDate.domain([0,d3.max(dataDate, function(d) { return d.close; })]);

    // Add the valueline path.
    svg.append("path")
        .attr("class", "line")
        .attr("transform", "translate(0," + scatterplotHeight + ")")
        .attr("d", valueline(dataDate));

    // Add the Y Axis
    svg.append("g")
        .attr("class", "y axis")
        .attr("transform", "translate(" 
            + scatterplotWidth +  "," + scatterplotHeight + ")")
        .call(yAxisDate);

    // ************* draw the time graph **************
    // Set the ranges
    var yTime = d3.time.scale().range([0, scatterplotHeight]);
    var xTime = d3.scale.linear().range([0, timeGraphWidth]);

    // Define the axes
    var xAxisTime = d3.svg.axis().scale(xTime)
        .orient("bottom").ticks(5);

    // Define the line
    var valuelineTime = d3.svg.line()
        .x(function(d) { return xTime(d.close); })
        .y(function(d) { return yTime(d.time); });
        
    // Scale the range of the data
    yTime.domain([new Date(1899, 12, 02, 0, 0, 0), 
                  new Date(1899, 12, 01, 0, 0, 1)]);
    xTime.domain([1e-6, d3.max(dataTime, 
        function(d) { return d.close; }
        )]);

    // Add the valueline path.
    svg.append("path")
        .attr("class", "line")
        .attr("transform", "translate(" + scatterplotWidth + ",0)")
        .attr("d", valuelineTime(dataTime));

    // Add the X Axis
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(" 
            + scatterplotWidth +  "," + scatterplotHeight + ")")
        .call(xAxisTime);

    // *********** place the mouse movement information **************
    var focus = svg.append("g") 
        .style("display", "none");

    // append the x line
    focus.append("line")
        .attr("class", "x")
        .style("stroke", "#33a02c")
        .style("stroke-dasharray", "3,3")
        .style("opacity", 1)
        .style("shape-rendering", "crispEdges");

    // append the y line
    focus.append("line")
        .attr("class", "y")
        .style("stroke", "#33a02c")
        .style("stroke-dasharray", "3,3")
        .style("opacity", 1)
        .style("shape-rendering", "crispEdges");

    // place the value at the intersection
    focus.append("text")
        .attr("class", "y1")
        .style("stroke", "white")
        .style("stroke-width", "3.5px")
        .style("opacity", 0.8)
        .attr("dx", 8)
        .attr("dy", "-.3em");
    focus.append("text")
        .attr("class", "y2")
        .attr("dx", 8)
        .attr("dy", "-.3em");

    // place the date at the intersection
    focus.append("text")
        .attr("class", "y3")
        .style("stroke", "white")
        .style("stroke-width", "3.5px")
        .style("opacity", 0.8)
        .attr("dx", 8)
        .attr("dy", "1em");
    focus.append("text")
        .attr("class", "y4")
        .attr("dx", 8)
        .attr("dy", "1em");

    // place the Daily Downloads dynamic text
    focus.append("text")
        .attr("class", "y5")
        .style("stroke", "white")
        .style("stroke-width", "3.5px")
        .style("opacity", 0.8)
        .attr("dy", "1em");
    focus.append("text")
        .attr("class", "y6")
        .attr("dy", "1em");

    // place the time Downloads dynamic text
    focus.append("text")
        .attr("class", "y7")
        .style("stroke", "white")
        .style("stroke-width", "3.5px")
        .style("opacity", 0.8)
        .attr("dy", ".35em");
    focus.append("text")
        .attr("class", "y8")
        .attr("dy", ".35em");

    // append the rectangle to capture mouse
    svg.append("rect")
        .attr("width", scatterplotWidth)
        .attr("height", scatterplotHeight)
        .style("fill", "none")
        .style("pointer-events", "all")
        .on("mouseover", function() { focus.style("display", null); })
        .on("mouseout", function() { focus.style("display", "none"); })
        .on("mousemove", mousemove);

    // The conversion ratio to change x position of cursor to
    // the index of the date array
    var convertDate =  dataDate.length/scatterplotWidth;
    var convertTime =  dataTime.length/scatterplotHeight;

    // interactive mouse function
    function mousemove() {
        var xpos = d3.mouse(this)[0],
            x0 = x.invert(xpos),
            y0 = d3.mouse(this)[1],
            y1 = y.invert(y0),
            date1 = d3.mouse(this)[0];

        // Place the intersection date text
        focus.select("text.y1")
            .attr("transform",
                  "translate(" + (date1 - 50) + "," + (y0+20) + ")")
            .text(formatDate(x0));
        focus.select("text.y2")
            .attr("transform",
                  "translate(" + (date1 - 50) + "," + (y0+20) + ")")
            .text(formatDate(x0));

        // Place the intersection time text
        focus.select("text.y3")
            .attr("transform",
                  "translate(" + (date1) + "," + (y0-15) + ")")
            .text(formatTime(y1).substring(0,4)+'0');
        focus.select("text.y4")
            .attr("transform",
                  "translate(" + (date1) + "," + (y0-15) + ")")
            .text(formatTime(y1).substring(0,4)+'0');

        // Place the dynamic daily downloads text
        focus.select("text.y5")
            .attr("transform",
                  "translate("
                      + (date1) + ","
                      + (scatterplotHeight+dateGraphHeight) + ")")
            .attr("text-anchor", "middle")
            .text(dataDate[parseInt(xpos*convertDate)].close);
        focus.select("text.y6")
            .attr("transform",
                  "translate("
                      + (date1) + ","
                      + (scatterplotHeight+dateGraphHeight) + ")")
            .attr("text-anchor", "middle")
            .text(dataDate[parseInt(xpos*convertDate)].close);

        // Place the dynamic time downloads text
        focus.select("text.y7")
            .attr("transform",
                  "translate("
                      + (scatterplotWidth+timeGraphWidth) + ","
                      + (y0) + ")")
            .attr("text-anchor", "start")
            .text(dataTime[144-parseInt(y0*convertTime)].close);
        focus.select("text.y8")
            .attr("transform",
                  "translate("
                      + (scatterplotWidth+timeGraphWidth) + ","
                      + (y0) + ")")
            .attr("text-anchor", "start")
            .text(dataTime[144-parseInt(y0*convertTime)].close);


        focus.select(".x")
            .attr("transform",
                  "translate(" + date1 + "," + (0) + ")")
            .attr("y2", height );

        focus.select(".y")
            .attr("transform",
                  "translate(0," + y0 + ")")
            .attr("x2", width );
    }

    // axis labels
    svg.append("text")
        .attr("transform", "translate("
            + scatterplotWidth +  ","
            + scatterplotHeight + "), rotate(-90)")
        .attr("y", 25)
        .attr("x",-dateGraphHeight )
        .attr("dy", "1em")
        .style("text-anchor", "start")
        .attr("class", "shadow")
        .text("Daily Downloads");
        
    svg.append("text")
        .attr("transform", "translate("
            + scatterplotWidth +  ","
            + scatterplotHeight + "), rotate(-90)")
        .attr("y", 25)
        .attr("x",-dateGraphHeight )
        .attr("dy", "1em")
        .style("text-anchor", "start")
        .text("Daily Downloads");

    svg.append("text")
        .attr("transform", "translate("
            + scatterplotWidth +  ","
            + scatterplotHeight + ")")
        .attr("y", 20)
        .attr("x",timeGraphWidth )
        .attr("dy", "1em")
        .style("text-anchor", "end")
        .attr("class", "shadow")
        .text("Downloads in 10 Minute Blocks");
        
    svg.append("text")
        .attr("transform", "translate("
            + scatterplotWidth +  ","
            + scatterplotHeight + ")")
        .attr("y", 20)
        .attr("x",timeGraphWidth )
        .attr("dy", "1em")
        .style("text-anchor", "end")
        .text("Downloads in 10 Minute Blocks");

});

</script>

</body>

readme.md

The graph above shows each instance that the book [D3 Tips and Tricks](https://leanpub.com/D3-Tips-and-Tricks) was download from January 2013 to March 2015. It is best viewed in full screen by [opening in a new window](http://bl.ocks.org/d3noob/raw/a0cbcddc6bf0eb9569fe/).

The main scatterplot has the day that the download occured on the X axis and the time (UTC) that the download occured on the Y axis. The time of the download is grouped into 10 minute blocks and where more than one download occured, the radius of the corresponding point is increased.

The line graph at the bottom shows the variation of numbers of downloads by day and the line graph on the right shows the variation of downloads by time of day (in 10 minute blocks).

Moving the mouse over the scatterplot will show the time and date of the point and the data is extrapolated onto the line graphs to show the corresponding number of downloads for the day and time.

There is an explanation of the code available in [D3 Tips and Tricks](https://leanpub.com/D3-Tips-and-Tricks) or in the blog post [here](http://www.d3noob.org/2015/04/exploring-event-data-by-combination.html).