Plotting German school holidays with D3.js

Sep 1, 2019

Even though I mainly work with R, I wanted to learn some D3.js and recently had the idea to visualize German school holidays by region for the year 2018-19. Don’t ask me why I chose the school holidays, I don’t know either.

I took the dates from this website: https://www.schulferien.org/deutschland/ferien/2019/

First, here is the result:

Whenever I find the time, I’ll have to do this again in D3.js v4 or v5. Anyway, you probably already know how to extract the HTML and JavaScript code from this website but I’ll make it easier and post it below. Please excuse the vague and potentially incorrect comments, I’m still in the process of learning the D3.js framework.

<html>
  <head>
    <meta charset='utf-8' />
    <title>Schulferien in Deutschland</title>
    <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <style>
      body {
        font-family: "Arial", sans-serif;
      }
      .axis {
        font-size: 13px;
      }
      .axis path,
      .axis line {
        fill: none;
        display: none;
      }
      .label {
        font-size: 13px;
      }
      .grid .tick {
        stroke: lightgrey;
        opacity: 0.7;
      }
      .grid path {
        stroke-width: 0;
      }
      div.tooltip {
        position: absolute;
        text-align: left;
        width: auto;
        height: auto;
        padding: 2px;
        font: 13px sans-serif;
        background: white;
        border: 0px;
        border-radius: 4px;
        pointer-events: none;
      }
    </style>
  </head>
  <body>
    <div id="graphic"></div>
    <script>

      // define data
      var data = [
        {bundesland: "Baden-Württemberg", ferien: "Winterferien", start: "2018-03-04", end: "2018-03-08"},
        {bundesland: "Baden-Württemberg", ferien: "Osterferien", start: "2018-04-15", end: "2018-04-27"},
        {bundesland: "Baden-Württemberg", ferien: "Pfingstferien", start: "2018-06-11", end: "2018-06-21"},
        {bundesland: "Baden-Württemberg", ferien: "Sommerferien", start: "2018-07-29", end: "2018-09-10"},
        {bundesland: "Baden-Württemberg", ferien: "Herbstferien", start: "2018-10-28", end: "2018-10-30"},
        {bundesland: "Baden-Württemberg", ferien: "Weihnachtsferien", start: "2018-12-23", end: "2019-01-04"},
        {bundesland: "Bayern", ferien: "Winterferien", start: "2018-03-04", end: "2018-03-08"},
        {bundesland: "Bayern", ferien: "Osterferien", start: "2018-04-15", end: "2018-04-27"},
        {bundesland: "Bayern", ferien: "Pfingstferien", start: "2018-06-11", end: "2018-06-21"},
        {bundesland: "Bayern", ferien: "Sommerferien", start: "2018-07-29", end: "2018-09-09"},
        {bundesland: "Bayern", ferien: "Herbstferien", start: "2018-10-28", end: "2018-10-31"},
        {bundesland: "Bayern", ferien: "Weihnachtsferien", start: "2018-12-23", end: "2019-01-04"},
        {bundesland: "Berlin", ferien: "Winterferien", start: "2018-02-04", end: "2018-02-09"},
        {bundesland: "Berlin", ferien: "Osterferien", start: "2018-04-15", end: "2018-04-26"},
        {bundesland: "Berlin", ferien: "Pfingstferien", start: "2018-05-31", end: "2018-05-31"},
        {bundesland: "Berlin", ferien: "Sommerferien", start: "2018-06-20", end: "2018-08-02"},
        {bundesland: "Berlin", ferien: "Herbstferien", start: "2018-10-07", end: "2018-10-19"},
        {bundesland: "Berlin", ferien: "Weihnachtsferien", start: "2018-12-23", end: "2019-01-04"},
        {bundesland: "Brandenburg", ferien: "Winterferien", start: "2018-02-04", end: "2018-02-09"},
        {bundesland: "Brandenburg", ferien: "Osterferien", start: "2018-04-15", end: "2018-04-26"},
        {bundesland: "Brandenburg", ferien: "Sommerferien", start: "2018-06-20", end: "2018-08-03"},
        {bundesland: "Brandenburg", ferien: "Herbstferien", start: "2018-10-04", end: "2018-10-18"},
        {bundesland: "Brandenburg", ferien: "Weihnachtsferien", start: "2018-12-23", end: "2019-01-03"},
        {bundesland: "Bremen", ferien: "Winterferien", start: "2018-01-31", end: "2018-02-01"},
        {bundesland: "Bremen", ferien: "Osterferien", start: "2018-04-06", end: "2018-04-23"},
        {bundesland: "Bremen", ferien: "Pfingstferien", start: "2018-05-31", end: "2018-05-31"},
        {bundesland: "Bremen", ferien: "Sommerferien", start: "2018-07-04", end: "2018-08-14"},
        {bundesland: "Bremen", ferien: "Herbstferien", start: "2018-10-04", end: "2018-10-18"},
        {bundesland: "Bremen", ferien: "Weihnachtsferien", start: "2018-12-21", end: "2019-01-06"},
        {bundesland: "Hamburg", ferien: "Winterferien", start: "2018-02-01", end: "2018-02-01"},
        {bundesland: "Hamburg", ferien: "Osterferien", start: "2018-03-04", end: "2018-03-15"},
        {bundesland: "Hamburg", ferien: "Pfingstferien", start: "2018-05-13", end: "2018-05-17"},
        {bundesland: "Hamburg", ferien: "Sommerferien", start: "2018-06-27", end: "2018-08-07"},
        {bundesland: "Hamburg", ferien: "Herbstferien", start: "2018-10-04", end: "2018-10-18"},
        {bundesland: "Hamburg", ferien: "Weihnachtsferien", start: "2018-12-20", end: "2019-01-03"},
        {bundesland: "Hessen", ferien: "Osterferien", start: "2018-04-15", end: "2018-04-27"},
        {bundesland: "Hessen", ferien: "Sommerferien", start: "2018-07-01", end: "2018-08-09"},
        {bundesland: "Hessen", ferien: "Herbstferien", start: "2018-09-30", end: "2018-10-12"},
        {bundesland: "Hessen", ferien: "Weihnachtsferien", start: "2018-12-23", end: "2019-01-11"},
        {bundesland: "Mecklenburg-Vorpommern", ferien: "Winterferien", start: "2018-02-04", end: "2018-02-15"},
        {bundesland: "Mecklenburg-Vorpommern", ferien: "Osterferien", start: "2018-04-15", end: "2018-04-24"},
        {bundesland: "Mecklenburg-Vorpommern", ferien: "Pfingstferien", start: "2018-06-07", end: "2018-06-11"},
        {bundesland: "Mecklenburg-Vorpommern", ferien: "Sommerferien", start: "2018-07-01", end: "2018-08-10"},
        {bundesland: "Mecklenburg-Vorpommern", ferien: "Herbstferien", start: "2018-10-07", end: "2018-10-12"},
        {bundesland: "Mecklenburg-Vorpommern", ferien: "Weihnachtsferien", start: "2018-12-23", end: "2019-01-04"},
        {bundesland: "Niedersachsen", ferien: "Winterferien", start: "2018-01-31", end: "2018-02-01"},
        {bundesland: "Niedersachsen", ferien: "Osterferien", start: "2018-04-08", end: "2018-04-23"},
        {bundesland: "Niedersachsen", ferien: "Pfingstferien", start: "2018-05-31", end: "2018-05-31"},
        {bundesland: "Niedersachsen", ferien: "Sommerferien", start: "2018-07-04", end: "2018-08-14"},
        {bundesland: "Niedersachsen", ferien: "Herbstferien", start: "2018-10-04", end: "2018-10-18"},
        {bundesland: "Niedersachsen", ferien: "Weihnachtsferien", start: "2018-12-23", end: "2019-01-06"},
        {bundesland: "Nordrhein-Westfalen", ferien: "Osterferien", start: "2018-04-15", end: "2018-04-27"},
        {bundesland: "Nordrhein-Westfalen", ferien: "Pfingstferien", start: "2018-06-11", end: "2018-06-11"},
        {bundesland: "Nordrhein-Westfalen", ferien: "Sommerferien", start: "2018-07-15", end: "2018-08-27"},
        {bundesland: "Nordrhein-Westfalen", ferien: "Herbstferien", start: "2018-10-04", end: "2018-10-18"},
        {bundesland: "Nordrhein-Westfalen", ferien: "Weihnachtsferien", start: "2018-12-23", end: "2019-01-04"},
        {bundesland: "Rheinland-Pfalz", ferien: "Winterferien", start: "2018-02-25", end: "2018-03-05"},
        {bundesland: "Rheinland-Pfalz", ferien: "Osterferien", start: "2018-04-23", end: "2018-04-30"},
        {bundesland: "Rheinland-Pfalz", ferien: "Sommerferien", start: "2018-07-01", end: "2018-08-09"},
        {bundesland: "Rheinland-Pfalz", ferien: "Herbstferien", start: "2018-09-30", end: "2018-10-11"},
        {bundesland: "Rheinland-Pfalz", ferien: "Weihnachtsferien", start: "2018-12-23", end: "2019-01-06"},
        {bundesland: "Saarland", ferien: "Winterferien", start: "2018-02-25", end: "2018-03-05"},
        {bundesland: "Saarland", ferien: "Osterferien", start: "2018-04-17", end: "2018-04-26"},
        {bundesland: "Saarland", ferien: "Sommerferien", start: "2018-07-01", end: "2018-08-09"},
        {bundesland: "Saarland", ferien: "Herbstferien", start: "2018-10-07", end: "2018-10-18"},
        {bundesland: "Saarland", ferien: "Weihnachtsferien", start: "2018-12-23", end: "2019-01-03"},
        {bundesland: "Sachsen", ferien: "Winterferien", start: "2018-02-18", end: "2018-03-02"},
        {bundesland: "Sachsen", ferien: "Osterferien", start: "2018-04-19", end: "2018-04-26"},
        {bundesland: "Sachsen", ferien: "Pfingstferien", start: "2018-05-31", end: "2018-05-31"},
        {bundesland: "Sachsen", ferien: "Sommerferien", start: "2018-07-08", end: "2018-08-16"},
        {bundesland: "Sachsen", ferien: "Herbstferien", start: "2018-10-14", end: "2018-10-25"},
        {bundesland: "Sachsen", ferien: "Weihnachtsferien", start: "2018-12-21", end: "2019-01-03"},
        {bundesland: "Sachsen-Anhalt", ferien: "Winterferien", start: "2018-02-11", end: "2018-02-15"},
        {bundesland: "Sachsen-Anhalt", ferien: "Osterferien", start: "2018-04-18", end: "2018-04-30"},
        {bundesland: "Sachsen-Anhalt", ferien: "Pfingstferien", start: "2018-05-31", end: "2018-06-01"},
        {bundesland: "Sachsen-Anhalt", ferien: "Sommerferien", start: "2018-07-04", end: "2018-08-14"},
        {bundesland: "Sachsen-Anhalt", ferien: "Herbstferien", start: "2018-10-04", end: "2018-10-11"},
        {bundesland: "Sachsen-Anhalt", ferien: "Weihnachtsferien", start: "2018-12-23", end: "2019-01-04"},
        {bundesland: "Schleswig-Holstein", ferien: "Osterferien", start: "2018-04-04", end: "2018-04-18"},
        {bundesland: "Schleswig-Holstein", ferien: "Pfingstferien", start: "2018-05-31", end: "2018-05-31"},
        {bundesland: "Schleswig-Holstein", ferien: "Sommerferien", start: "2018-07-01", end: "2018-08-10"},
        {bundesland: "Schleswig-Holstein", ferien: "Herbstferien", start: "2018-10-04", end: "2018-10-18"},
        {bundesland: "Schleswig-Holstein", ferien: "Weihnachtsferien", start: "2018-12-23", end: "2019-01-06"},
        {bundesland: "Thüringen", ferien: "Winterferien", start: "2018-02-11", end: "2018-02-15"},
        {bundesland: "Thüringen", ferien: "Osterferien", start: "2018-04-15", end: "2018-04-27"},
        {bundesland: "Thüringen", ferien: "Pfingstferien", start: "2018-05-31", end: "2018-05-31"},
        {bundesland: "Thüringen", ferien: "Sommerferien", start: "2018-07-08", end: "2018-08-17"},
        {bundesland: "Thüringen", ferien: "Herbstferien", start: "2018-10-07", end: "2018-10-19"},
        {bundesland: "Thüringen", ferien: "Weihnachtsferien", start: "2018-12-21", end: "2019-01-03"},
      ]

      // assign colors to types of holidays
      var color = d3.scale.ordinal()
        .domain(["Winterferien","Osterferien","Pfingstferien","Sommerferien","Herbstferien","Weihnachtsferien"])
        .range(["#3498db","#1abc9c","#9b59b6","#fed800","#95a5a6","#34495e"]);

      // function to read in dates
      var parseDate = d3.time.format("%Y-%m-%d").parse;

      // function to format dates
      var formatDate = d3.time.format("%d.%m.%Y")

      // define margins around plotting area
      // we need a lot of space on the left for the region names as well as on
      // on the bottom for the date
      var margin = {
        top: 40,
        right: 25,
        bottom: 100,
        left: 170
      };

      // define height and width of the plotting area
      var width = 960 - margin.left - margin.right,
          height = 530 - margin.top - margin.bottom;

      // create SVG
      var svg = d3.select("#graphic")
        .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 + ")");

      // create border around plotting area
      var borderPath = svg.append("rect")
        .attr("x", 0)
        .attr("y", 0)
        .attr("height", height)
        .attr("width", width)
        .style("stroke", "black")
        .style("fill", "none")
        .style("stroke-width", 1);

      // tooltip element
      var div = d3.select("body").append("div")
        .attr("class", "tooltip")
        .style("opacity", 0);

      // create X axis scale with time scale and manual limits
      var x = d3.time.scale()
        .domain([parseDate("2018-01-01"), parseDate("2019-02-01")])
        .range([0, width])
        .nice(d3.time.month);

      // create Y axis scale with region names as categories
      var y = d3.scale.ordinal()
        .rangeRoundBands([0, height], .1)
        .domain(data.map( function(d) {
          return d.bundesland;
        }));

      // adjust X axis with 10 ticks
      var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom")
        .ticks(8)
        .tickFormat(d3.time.format("%m/%Y"));

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

      // add title
      svg.append("text")
        .attr("x", (width / 2))
        .attr("y", 0 - (margin.top / 2))
        .attr("text-anchor", "middle")
        .style("font-size", "20px")
        .style("text-decoration", "bold")
        .text("Schulferien in Deutschland für 2018/19");

      //
      svg.append("g")
        .attr("class", "axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

      //
      svg.append("g")
        .attr("class", "axis")
        .call(yAxis);

      // draw grid lines
      svg.append("g")
        .attr("class", "grid")
        .attr("transform", "translate(0," + height + ")")
        .call(
          d3.svg.axis()
            .scale(x)
            .orient("bottom")
            .ticks(10)
            .tickSize(-height, 0, 0)
            .tickFormat("")
        )

      //
      svg.append("g")
        .selectAll("bar")
        .data(data)
        .enter()
        .append("g")
        .append("rect")
        .attr("class", "bar")
        .attr("y", function(d) { return y(d.bundesland); })
        .attr("height", y.rangeBand())
        .attr("x", function(d) { return x(parseDate(d.start)); })
        .attr("width", function(d) {
          return x(parseDate(d.end)) - x(parseDate(d.start))
        })
        .style("fill", function(d) { return color(d.ferien); })
        .style("stroke", "black")
        .style("stroke-width", 0.5)
        .on("mouseover", function(d) {
          div.transition()
            .duration(200)
            .style("opacity", .9);
          div.html(
              "<b>Bundesland:</b> " + d.bundesland + "<br/>" +
              "<b>Ferien:</b> " + d.ferien + "<br/>" +
              "<b>Anfang:</b> " + formatDate(parseDate(d.start)) + "<br/>" +
              "<b>Ende:</b> " + formatDate(parseDate(d.end))
            )
            .style("left", (d3.event.pageX) + "px")
            .style("top", (d3.event.pageY - 28) + "px");
        })
        .on("mouseout", function(d) {
          div.transition()
            .duration(500)
            .style("opacity", 0);
        });

    </script>
  </body>
</html>