text: //  A timeline component for d3
  //  version v0.1
  
  function timeline2(domElement2) {
  
      //--------------------------------------------------------------------------
      //
      // chart
      //
  
      // chart geometry
      var margin = {top: 20, right: 20, bottom: 20, left: 20},
          outerWidth = 960,
          outerHeight = 500,
          width = outerWidth - margin.left - margin.right,
          height = outerHeight - margin.top - margin.bottom;
  
      // global timeline variables
      var timeline2 = {},   // The timeline
          data2 = {},       // Container for the data
          components2 = [], // All the components of the timeline for redrawing
          bandGap2 = 25,    // Arbitray gap between to consecutive bands
          bands2 = {},      // Registry for all the bands in the timeline
          bandY2 = 0,       // Y-Position of the next band
          bandNum2 = 0;     // Count of bands for ids
  
      // Create svg element
      var svg = d3.select(domElement2).append("svg")
          .attr("class", "svg")
          .attr("id", "svg")
          .attr("width", outerWidth)
          .attr("height", outerHeight)
          .append("g")
          .attr("transform", "translate(" + margin.left + "," + margin.top +  ")");
  
      svg.append("clipPath")
          .attr("id", "chart-area")
          .append("rect")
          .attr("width", width)
          .attr("height", height);
  
      var chart = svg.append("g")
              .attr("class", "chart")
              .attr("clip-path", "url(#chart-area)" );
  
      var tooltip = d3.select("body")
          .append("div")
          .attr("class", "tooltip")
          .style("visibility", "visible");
  
      //--------------------------------------------------------------------------
      //
      // data
      //
  
      timeline.data2 = function(items) {
  
          var today = new Date(),
              tracks = [],
              yearMillis = 31622400000,
              instantOffset = 100 * yearMillis;
  
          data2.items = items;
  
          function showItems(n) {
              var count = 0, n = n || 10;
              console.log("\n");
              items.forEach(function (d) {
                  count++;
                  if (count > n) return;
                  console.log(toYear(d.start) + " - " + toYear(d.end) + ": " + d.label);
              })
          }
  
          function compareAscending(item1, item2) {
              // Every item must have two fields: 'start' and 'end'.
              var result = item1.start - item2.start;
              // earlier first
              if (result < 0) { return -1; }
              if (result > 0) { return 1; }
              // longer first
              result = item2.end - item1.end;
              if (result < 0) { return -1; }
              if (result > 0) { return 1; }
              return 0;
          }
  
          function compareDescending(item1, item2) {
              // Every item must have two fields: 'start' and 'end'.
              var result = item1.start - item2.start;
              // later first
              if (result < 0) { return 1; }
              if (result > 0) { return -1; }
              // shorter first
              result = item2.end - item1.end;
              if (result < 0) { return 1; }
              if (result > 0) { return -1; }
              return 0;
          }
  
          function calculateTracks(items, sortOrder, timeOrder) {
              var i, track;
  
              sortOrder = sortOrder || "descending"; // "ascending", "descending"
              timeOrder = timeOrder || "backward";   // "forward", "backward"
  
              function sortBackward() {
                  // older items end deeper
                  items.forEach(function (item) {
                      for (i = 0, track = 0; i < tracks.length; i++, track++) {
                          if (item.end < tracks[i]) { break; }
                      }
                      item.track = track;
                      tracks[track] = item.start;
                  });
              }
              function sortForward() {
                  // younger items end deeper
                  items.forEach(function (item) {
                      for (i = 0, track = 0; i < tracks.length; i++, track++) {
                          if (item.start > tracks[i]) { break; }
                      }
                      item.track = track;
                      tracks[track] = item.end;
                  });
              }
  
              if (sortOrder === "ascending")
                  data2.items.sort(compareAscending);
              else
                  data2.items.sort(compareDescending);
  
              if (timeOrder === "forward")
                  sortForward();
              else
                  sortBackward();
          }
  
          // Convert yearStrings into dates
          data2.items.forEach(function (item){
              item.start = parseDate(item.start);
              if (item.end == "") {
                  //console.log("1 item.start: " + item.start);
                  //console.log("2 item.end: " + item.end);
                  item.end = new Date(item.start.getTime() + instantOffset);
                  //console.log("3 item.end: " + item.end);
                  item.instant = true;
              } else {
                  //console.log("4 item.end: " + item.end);
                  item.end = parseDate(item.end);
                  item.instant = false;
              }
              // The timeline never reaches into the future.
              // This is an arbitrary decision.
              // Comment out, if dates in the future should be allowed.
              if (item.end > today) { item.end = today};
          });
  
          //calculateTracks(data.items);
          // Show patterns
          //calculateTracks(data.items, "ascending", "backward");
          //calculateTracks(data.items, "descending", "forward");
          // Show real data
          calculateTracks(data2.items, "descending", "backward");
          //calculateTracks(data.items, "ascending", "forward");
          data2.nTracks = tracks.length;
          data2.minDate = d3.min(data2.items, function (d) { return d.start; });
          data2.maxDate = d3.max(data2.items, function (d) { return d.end; });
  
          return timeline;
      };
  
      //----------------------------------------------------------------------
      //
      // band
      //
  
      timeline.band = function (bandName, sizeFactor) {
  
          var band = {};
          band.id = "band" + bandNum2;
          band.x = 0;
          band.y = bandY2;
          band.w = width;
          band.h = height * (sizeFactor || 1);
          band.trackOffset = 4;
          // Prevent tracks from getting too high
          band.trackHeight = Math.min((band.h - band.trackOffset) / data2.nTracks, 20);
          band.itemHeight = band.trackHeight * 0.8,
          band.parts = [],
          band.instantWidth = 100; // arbitray value
  
          band.xScale = d3.time.scale()
              .domain([data2.minDate, data2.maxDate])
              .range([0, band.w]);
  
          band.yScale = function (track) {
              return band.trackOffset + track * band.trackHeight;
          };
  
          band.g = chart.append("g")
              .attr("id", band.id)
              .attr("transform", "translate(0," + band.y +  ")");
  
          band.g.append("rect")
              .attr("class", "band")
              .attr("width", band.w)
              .attr("height", band.h);
  
          // Items
          var items = band.g.selectAll("g")
              .data(data2.items)
              .enter().append("svg")
              .attr("y", function (d) { return band.yScale(d.track); })
              .attr("height", band.itemHeight)
              .attr("class", function (d) { return d.instant ? "part instant" : "part interval";});
  
          var intervals = d3.select("#band" + bandNum2).selectAll(".interval");
          intervals.append("rect")
              .attr("width", "100%")
              .attr("height", "100%")
              .attr("fill", function(d) { return d.lang_color;});
          intervals.append("text")
              .attr("class", "intervalLabel")
              .attr("x", 1)
              .attr("y", 10)
              .text(function (d) { return d.label; });
  
          var instants = d3.select("#band" + bandNum2).selectAll(".instant");
          instants.append("circle")
              .attr("cx", band.itemHeight / 2)
              .attr("cy", band.itemHeight / 2)
              .attr("r", 5)
              .attr("fill", function(d) { return d.lang_color;});
          instants.append("text")
              .attr("class", "instantLabel")
              .attr("x", 15)
              .attr("y", 10)
  
              .text(function (d) { return d.label; });
  
          band.addActions = function(actions) {
              // actions - array: [[trigger, function], ...]
              actions.forEach(function (action) {
                  items.on(action[0], action[1]);
              })
          };
  
          band.redraw = function () {
              items
                  .attr("x", function (d) { return band.xScale(d.start);})
                  .attr("width", function (d) {
                      return band.xScale(d.end) - band.xScale(d.start); });
              band.parts.forEach(function(part) { part.redraw(); })
          };
  
          bands2[bandName] = band;
          components2.push(band);
          // Adjust values for next band
          bandY2 += band.h + bandGap2;
          bandNum2 += 1;
  
          return timeline;
      };
  
      //----------------------------------------------------------------------
      //
      // labels
      //
  
      timeline.labels = function (bandName) {
  
          var band = bands2[bandName],
              labelWidth = 46,
              labelHeight = 20,
              labelTop = band.y + band.h - 10,
              y = band.y + band.h + 1,
              yText = 15;
  
          var labelDefs = [
                  ["start", "bandMinMaxLabel", 0, 4,
                      function(min, max) { return toYear(min); },
                      "Start of the selected interval", band.x + 30, labelTop],
                  ["end", "bandMinMaxLabel", band.w - labelWidth, band.w - 4,
                      function(min, max) { return toYear(max); },
                      "End of the selected interval", band.x + band.w - 152, labelTop],
                  ["middle", "bandMidLabel", (band.w - labelWidth) / 2, band.w / 2,
                      function(min, max) { return max.getUTCFullYear() - min.getUTCFullYear(); },
                      "Length of the selected interval", band.x + band.w / 2 - 75, labelTop]
              ];
  
          var bandLabels = chart.append("g")
              .attr("id", bandName + "Labels")
              .attr("transform", "translate(0," + (band.y + band.h + 1) +  ")")
              .selectAll("#" + bandName + "Labels")
              .data(labelDefs)
              .enter().append("g")
              .on("mouseover", function(d) {
                  tooltip.html(d[5])
                      .style("top", d[7] + "px")
                      .style("left", d[6] + "px")
                      .style("visibility", "visible");
                  })
              .on("mouseout", function(){
                  tooltip.style("visibility", "hidden");
              });
  
          bandLabels.append("rect")
              .attr("class", "bandLabel")
              .attr("x", function(d) { return d[2];})
              .attr("width", labelWidth)
              .attr("height", labelHeight)
  
              .style("opacity", 1);
  
          var labels = bandLabels.append("text")
              .attr("class", function(d) { return d[1];})
              .attr("id", function(d) { return d[0];})
              .attr("x", function(d) { return d[3];})
              .attr("y", yText)
              .attr("text-anchor", function(d) { return d[0];});
  
          labels.redraw = function () {
              var min = band.xScale.domain()[0],
                  max = band.xScale.domain()[1];
  
              labels.text(function (d) { return d[4](min, max); })
          };
  
          band.parts.push(labels);
          components2.push(labels);
  
          return timeline;
      };
  
      //----------------------------------------------------------------------
      //
      // tooltips
      //
  
      timeline.tooltips = function (bandName) {
  
          var band = bands2[bandName];
  
          band.addActions([
              // trigger, function
              ["mouseover", showTooltip],
              ["mouseout", hideTooltip]
          ]);
  
          function getHtml(element, d) {
              var html;
              if (element.attr("class") == "interval") {
                  html = d.label + "<br>" + toYear(d.start) + " - " + toYear(d.end)+ "<br>" + d.lang_label;
              } else {
                  html = d.label + "<br>" + toYear(d.start)+ "<br>" + d.lang_label;
              }
              return html;
          }
  
          function showTooltip (d) {
  
              var x = event.pageX < band.x + band.w / 2
                      ? event.pageX + 10
                      : event.pageX - 110,
                  y = event.pageY < band.y + band.h / 2
                      ? event.pageY + 30
                      : event.pageY - 30;
  
              tooltip
                  .html(getHtml(d3.select(this), d))
                  .style("top", y + "px")
                  .style("left", x + "px")
                  .style("visibility", "visible");
          }
  
          function hideTooltip () {
              tooltip.style("visibility", "hidden");
          }
  
          return timeline;
      };
  
      //----------------------------------------------------------------------
      //
      // xAxis
      //
  
      timeline.xAxis = function (bandName, orientation) {
  
          var band = bands2[bandName];
  
          var axis = d3.svg.axis()
              .scale(band.xScale)
              .orient(orientation || "bottom")
              .tickSize(6, 0)
              .tickFormat(function (d) { return toYear(d); });
  
          var xAxis = chart.append("g")
              .attr("class", "axis")
              .attr("transform", "translate(0," + (band.y + band.h)  + ")");
  
          xAxis.redraw = function () {
              xAxis.call(axis);
          };
  
          band.parts.push(xAxis); // for brush.redraw
          components2.push(xAxis); // for timeline.redraw
  
          return timeline;
      };
  
      //----------------------------------------------------------------------
      //
      // brush
      //
  
      timeline.brush = function (bandName, targetNames) {
  
          var band = bands2[bandName];
  
          var brush = d3.svg.brush()
              .x(band.xScale.range([0, band.w]))
              .on("brush", function() {
                  var domain = brush.empty()
                      ? band.xScale.domain()
                      : brush.extent();
                  targetNames.forEach(function(d) {
                      bands2[d].xScale.domain(domain);
                      bands2[d].redraw();
                  });
              });
  
          var xBrush = band.g.append("svg")
              .attr("class", "x brush")
              .call(brush);
  
          xBrush.selectAll("rect")
              .attr("y", 4)
              .attr("height", band.h - 4);
  
          return timeline;
      };
  
      //----------------------------------------------------------------------
      //
      // redraw
      //
  
      timeline.redraw = function () {
          components2.forEach(function (component) {
              component.redraw();
          })
      };
  
      //--------------------------------------------------------------------------
      //
      // Utility functions
      //
  
      function parseDate(dateString) {
          // 'dateString' must either conform to the ISO date format YYYY-MM-DD
          // or be a full year without month and day.
          // AD years may not contain letters, only digits '0'-'9'!
          // Invalid AD years: '10 AD', '1234 AD', '500 CE', '300 n.Chr.'
          // Valid AD years: '1', '99', '2013'
          // BC years must contain letters or negative numbers!
          // Valid BC years: '1 BC', '-1', '12 BCE', '10 v.Chr.', '-384'
          // A dateString of '0' will be converted to '1 BC'.
          // Because JavaScript can't define AD years between 0..99,
          // these years require a special treatment.
  
          var format = d3.time.format("%Y-%m-%d"),
              date,
              year;
  
          date = format.parse(dateString);
          if (date !== null) return date;
  
          // BC yearStrings are not numbers!
          if (isNaN(dateString)) { // Handle BC year
              // Remove non-digits, convert to negative number
              year = -(dateString.replace(/[^0-9]/g, ""));
          } else { // Handle AD year
              // Convert to positive number
              year = +dateString;
          }
          if (year < 0 || year > 99) { // 'Normal' dates
              date = new Date(year, 6, 1);
          } else if (year == 0) { // Year 0 is '1 BC'
              date = new Date (-1, 6, 1);
          } else { // Create arbitrary year and then set the correct year
              // For full years, I chose to set the date to mid year (1st of July).
              date = new Date(year, 6, 1);
              date.setUTCFullYear(("0000" + year).slice(-4));
          }
          // Finally create the date
          return date;
      }
  
      function toYear(date, bcString) {
          // bcString is the prefix or postfix for BC dates.
          // If bcString starts with '-' (minus),
          // if will be placed in front of the year.
          bcString = bcString || " BCE" // With blank!
          var year = date.getUTCFullYear();
          if (year > 0) return year.toString();
          if (bcString[0] == '-') return bcString + (-year);
          return (-year) + bcString;
      }
  
      return timeline;
  }
  