Calendar view of data in Jekyll with D3.js

Use Javascsript in particular D3.js in Jekyll bookstrap and Markdown syntax.

Table of content

Overview

Jekyll and Markdown

Jekyll is a fantastic tool to make a personal blog. See my blog here and there to get a quick start of using Jekyll to set up you blog and get addition supports e.g., Latex and Emoji. A typical Jekyll website or blog is quite often hosted in Github and with a domain name purchased from e.g., Namecheap.com. My website and blog follow exactly the same setup.

Markdown allows you to write a easy-to-read and easy-to-write plain text format which is later rendered into HTML. The language is support in e.g., Github and Jekyll. Or, you would love to use markdown syntax to write blogs in Jekyll :laughing:

Data visualization problem

Working with data science, I usually come with the problem of showing a plot on my website after I have collected some data or done some analysis. In particular, rendering an image on standard HTML is not very difficult and can be achieved by using some Javascript eg., D3.js. However, when working with markdown, it starts to get a bit complicated. This is because you first write everything in markdown syntax and it is then rendered into HTML.

Calendar view with D3.js

Here I will introduce the technologies and ticks to make the calendar view of data shown in Jekyll markdown syntax with D3.js.

An running example

Tricks

Show table with JSON data


- Basically, you need to define a object to be rendered on `example2` and render the table with `tabulate` function.

### Calendar view of the JSON data

- With the following Javascript, you will be able to render a calendar view of the JSON data.

  ```javascript

<example1>

<script type="text/javascript">



  var sessions = ....


/*-----------------------------*/

var width    = 700,
    height   = 400,
    cellSize = 13; // cell size

var no_months_in_a_row = Math.floor(width / (cellSize * 7 + 50));
var shift_up = cellSize * 3;

var day = d3.time.format("%w"), // day of the week
    day_of_month = d3.time.format("%e") // day of the month
    day_of_year = d3.time.format("%j")
    week = d3.time.format("%U"), // week number of the year
    month = d3.time.format("%m"), // month number
    year = d3.time.format("%Y"),
    percent = d3.format(".1%"),
    format = d3.time.format("%Y-%m-%d");


var color = d3.scale.linear().range(["white", 'red']).domain([0, 350])
var dateParse = d3.time.format("%Y-%m-%d");

var svg = d3.select("example1").selectAll("svg")
    .data(d3.range(2015, 2016))
    .enter().append("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("class", "RdYlGn")
    .append("g")

svg.append("text")
    .attr("transform", "translate(-6," + cellSize * 3.5 + ")rotate(-90)")
    .style("text-anchor", "middle")
    .text(function(d) { return d; });

var rect = svg.selectAll(".day")
        .data(function(d) { 
          return d3.time.days(new Date(d, 0, 1), new Date(d + 1, 0, 1));
        })
      .enter().append("rect")
        .attr("class", "day")
        .attr("width", cellSize)
        .attr("height", cellSize)
        .attr("x", function(d) {
          var month_padding = 1.2 * cellSize*7 * ((month(d)-1) % (no_months_in_a_row));
          return day(d) * cellSize + month_padding; 
        })
        .attr("y", function(d) { 
          var week_diff = week(d) - week(new Date(year(d), month(d)-1, 1) );
          var row_level = Math.ceil(month(d) / (no_months_in_a_row));
          return (week_diff*cellSize) + row_level*cellSize*8 - cellSize/2 - shift_up;
        })
        .datum(format);

var month_titles = svg.selectAll(".month-title")  // Jan, Feb, Mar and the whatnot
      .data(function(d) { 
        return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
    .enter().append("text")
      .text(monthTitle)
      .attr("x", function(d, i) {
        var month_padding = 1.2 * cellSize*7* ((month(d)-1) % (no_months_in_a_row));
        return month_padding;
      })
      .attr("y", function(d, i) {
        var week_diff = week(d) - week(new Date(year(d), month(d)-1, 1) );
        var row_level = Math.ceil(month(d) / (no_months_in_a_row));
        return (week_diff*cellSize) + row_level*cellSize*8 - cellSize - shift_up;
      })
      .attr("class", "month-title")
      .attr("d", monthTitle);

var year_titles = svg.selectAll(".year-title")  // Jan, Feb, Mar and the whatnot
      .data(function(d) { 
        return d3.time.years(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
      .enter().append("text")
      .text(yearTitle)
      .attr("x", function(d, i) { return width/2 - 100; })
      .attr("y", function(d, i) { return cellSize*5.5 - shift_up; })
      .attr("class", "year-title")
      .attr("d", yearTitle);

var tooltip = d3.select("body")
  .append("div").attr("id", "tooltip")
  .style("position", "absolute")
  .style("z-index", "10")
  .style("visibility", "hidden")
  .text("a simple tooltip");

d3.json("", function(error, data) {

  sessions.forEach(function(d) {
    d.dd = format(dateParse.parse(d.date));
  });

  var nest = d3.nest()
    .key(function(d) { return d.dd; })
    .map(sessions);

  rect.filter(function(d) { return d in nest; })
    .attr("class", function(d) { return "day"; })
    .style("fill", function(d) { return color(nest[d][0].pull_up+nest[d][0].push_up+nest[d][0].ab_wheel_roll+nest[d][0].bar_dip+nest[d][0].arm+nest[d][0].shoulder); })


   //  Tooltip
  rect.on("mouseover", mouseover);
  rect.on("mouseout", mouseout);

  function mouseover(d) {
    tooltip.style("visibility", "visible");

    var textcontent = (nest[d] !== undefined) ?  "\n pull up:\t\t" + nest[d][0].pull_up + "\n push up:\t\t" + nest[d][0].push_up + "\n ab wheel roll:\t" + nest[d][0].ab_wheel_roll + "\n bar dip:\t\t" + nest[d][0].bar_dip + "\n arm:\t\t\t" + nest[d][0].arm + "\n shoulder:\t\t" + nest[d][0].shoulder: '\n No GYM ?? Kidding me ??';
    var textdata = d + ":" + textcontent;

    tooltip.transition()        
      .duration(200)      
      .style("opacity", 1);  

    tooltip.html(textdata)  
      .style("left", (d3.event.pageX)+30 + "px")     
      .style("top", (d3.event.pageY) + "px"); 
   }

  function mouseout (d) {
    tooltip.transition()
      .duration(500)      
      .style("opacity", 0); 
    var $tooltip = $("#tooltip");
    $tooltip.empty();
   }
});

function monthPath(t0) {
  var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
    d0 = +day(t0), w0 = +week(t0),
    d1 = +day(t1), w1 = +week(t1);
  return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize
    + "H" + w0 * cellSize + "V" + 7 * cellSize
    + "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize
    + "H" + (w1 + 1) * cellSize + "V" + 0
    + "H" + (w0 + 1) * cellSize + "Z";


}

function dayTitle (t0) {
  return t0.toString().split(" ")[2];
}
function monthTitle (t0) {
  return t0.toString().split(" ")[1];
}
function yearTitle (t0) {
  return t0.toString().split(" ")[3];
}

</script>

</example1>

```

External references

Hongyu Su 11 November 2015