block by andrewxhill 159e7fca2c18ecc945c8

159e7fca2c18ecc945c8

Full Screen

index.html

<!doctype><html><head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Odyssey.js Torque</title>
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <link rel="icon" type="image/x-icon" href="//cartodb.github.io/odyssey.js/sandbox/favicon.png">
  <link rel="icon" type="image/png" href="//cartodb.github.io/odyssey.js/sandbox/favicon.png">

  <link rel="stylesheet" href="//cartodb-libs.global.ssl.fastly.net/cartodb.js/v3/themes/css/cartodb.css">
  <link rel="stylesheet" href="//cartodb.github.io/odyssey.js/sandbox/css/slides.css">
  <script src="//cartodb.github.io/odyssey.js/vendor/modernizr-2.6.2.min.js"></script>
</head>
<body>
  <div id="map" style="width: 100%; height: 100%"></div>
  <div id="slides_container" style="">
    <div id="front_slide">
    </div>

    <div id="slides">
    </div>
  </div>
  <div id="credits">
    <span class="title" id="title">Title</span>
    <span class="author"><b id="author">By Name using</b> <a href="#">Odyssey.js</a><span>
  </span></span></div>

  <script src="//maps.googleapis.com/maps/api/js?sensor=false"></script>
  <script src="//cartodb-libs.global.ssl.fastly.net/cartodb.js/v3/cartodb.js"></script>
  <script src="//cartodb.github.io/odyssey.js/dist/odyssey.js" charset="UTF-8"></script>

  <script>
    var resizePID;

    function clearResize() {
      clearTimeout(resizePID);
      resizePID = setTimeout(function() { adjustSlides(); }, 100);
    }

    if (!window.addEventListener) {
      window.attachEvent("resize", function load(event) {
        clearResize();
      });
    } else {
      window.addEventListener("resize", function load(event) {
        clearResize();
      });
    }

    function resizeWindow() {
      adjustSlides();
    }

    function adjustSlides() {
      var container = document.getElementById("slides_container"),
          slide = document.querySelectorAll('.selected_slide')[0];

      if (container && slide) {
        if (slide.offsetHeight+80+40+160 >= window.innerHeight) {
          container.style.bottom = "160px";
          var h = container.offsetHeight;

          slide.style.height = h-80+"px";
        } else {
          container.style.bottom = "auto";
          container.style.minHeight = "0";
          slide.style.height = "auto";
        }
      }
    }

    var resizeAction = O.Action(function() {
      adjustSlides();
    });

    function torque(layer) {
      function _torque() {}

      _torque.reach = function (slide) {
        var i = slide.get('step').value;

        function formaterForRange(start, end) {
          start = start.getTime ? start.getTime(): start;
          end = end.getTime ? end.getTime(): end;
          var span = (end - start)/1000;
          var ONE_DAY = 3600*24;
          var ONE_YEAR = ONE_DAY * 31 * 12;
          function pad(n) { return n < 10 ? '0' + n : n; };

          // lest than a day
          if (span < ONE_DAY) return function(t) { return pad(t.getUTCHours()) + ":" + pad(t.getUTCMinutes()); };
          if (span < ONE_YEAR) return function(t) { return pad(t.getUTCMonth() + 1) + "/" + pad(t.getUTCDate()) + "/" + pad(t.getUTCFullYear()); };
          return function(t) { return pad(t.getUTCMonth() + 1) + "/" + pad(t.getUTCFullYear()); };
        }

        function getTimeOrStep(s) {
          var tb = layer.getTimeBounds();
          if (!tb) return;
          if (tb.columnType === 'date') {
            if (tb && tb.start !== undefined) {
              var f = formaterForRange(tb.start, tb.end);
              // avoid showing invalid dates
              if (!_.isNaN(layer.stepToTime(s).getYear())) {
                return f(layer.stepToTime(s));
              }
            }
          } else {
            return s;
          }
        }

        function truncate(s, length) {
          return s.substr(0, length-1) + (s.length > length ? '…' : '');
        }

        var parser = new DOMParser(),
            doc = parser.parseFromString(slide.html(), 'text/html');

        var l = i*$('.slider').width()/layer.options.steps,
            tooltip = ['<div class="slide-tip slide-tip-'+i+'" style="left:'+l+'px">',
                       '<div class="tooltip">',
                       '<h1>'+getTimeOrStep(i)+'</h1>',
                       $(doc).find('h1').text(),
                       '</div>',
                       '</div>'].join("\n");

        $('.slider').append(tooltip);
        var $tip = $('.slide-tip-'+i+' .tip'),
            $tooltip = $('.slide-tip-'+i+' .tooltip'),
            w = $tip.width()/2

        $tip.css({ margin: -w });

        var t = O.Trigger({});

        function check(changes) {
          if (changes.step >= i-2 && changes.step < i+2) {
            t.trigger();

            if (!$tooltip.is(':visible')) {
              $tooltip.fadeIn(150);
            }
          } else if (changes.step >= i+2 && changes.step < i+5) {
            setTimeout(function() {
              $('.tooltip').fadeOut(150);
            }, 2000);
          }
        };

        layer.on('change:time', check);
        t.clear = function() {
          layer.off('change:time', check);
        }
        return t;
      }

      _torque.pause = function() {
        return O.Action(function (){
          layer.pause();
        });
      }

      _torque.play = function() {
        return O.Action(function () {
          layer.play()
        });
      }

      return _torque;
    }

    O.Template({
      actions: {
        'insert time': function() {
          return "- step: " + this.torqueLayer.getStep()
        },
        'pause': function() {
          return "S.torqueLayer.actions.pause()";
        },
        'play': function() {
          return "S.torqueLayer.actions.play()";
        }
      },

      init: function() {
        var self = this;

        var baseurl = this.baseurl = '//{s}.api.cartocdn.com/base-light/{z}/{x}/{y}.png';
        var map = this.map = L.map('map').setView([0, 0.0], 4);
        var basemap = this.basemap = L.tileLayer(baseurl, {
          attribution: 'data OSM - map CartoDB'
        }).addTo(map);
        this.duration = '18';

        var slides = this.slides = O.Actions.Slides('slides');
        var story = this.story = O.Story()
      },

      _resetActions: function(actions) {
        // update footer title and author
        var title_ = actions.global.title === undefined ? '' : actions.global.title,
            author_ = actions.global.author === undefined ? 'Using' : 'By '+actions.global.author+' using';

        document.getElementById('title').innerHTML = title_;
        document.getElementById('author').innerHTML = author_;
        document.title = title_ + " | " + author_ +' Odyssey.js';

        if (actions.global.title || actions.global.headline) {
          var first_slide = '',
              first_title_ = actions.global.title ? '<h1>'+actions.global.title+'</h1>' : '',
              first_headline_ = actions.global.headline ? '<p>'+actions.global.headline+'</p>' : '';

          first_slide = first_title_ + first_headline_;

          document.getElementById('slides_container').style.display = "block";
          document.getElementById('front_slide').innerHTML = actions[0].html();
        }

        document.getElementById('slides').innerHTML = '';

        // first slide is the header, skip it
        for(var i = 1; i < actions.length; ++i) {
          var slide = actions[i];
          var tmpl = "<div class='slide' style='display:none'>"
          tmpl += slide.html();
          tmpl += "</div>";

          document.getElementById('slides').innerHTML += tmpl;

          var ac = O.Parallel(
            O.Actions.CSS($("#front_slide")).addClass('hidden'),
            O.Actions.CSS($("#slides_container")).addClass('visible'),
            this.slides.activate(i-1),
            slide(this),
            resizeAction
          );

          if (!slide.get('step')) return;

          this.story.addState(
            torque(this.torqueLayer).reach(slide),
            ac
          )
        }
      },

      update: function(actions) {
        var self = this;

        if ($("#slides_container").hasClass("visible")) {
          $("#slides_container").removeClass("visible");
        }

        if ($("#front_slide").hasClass("hidden")) {
          $("#front_slide").removeClass("hidden");
        }

        if (this.baseurl && (this.baseurl !== actions.global.baseurl)) {
          this.baseurl = actions.global.baseurl || '//0.api.cartocdn.com/base-light/{z}/{x}/{y}.png';

          this.basemap.setUrl(this.baseurl);
        }

        if (this.duration && (this.duration !== actions.global.duration)) {
          this.duration = actions.global.duration || 18;
        }

        if (this.torqueLayer && ("//"+self.torqueLayer.options.user+".cartodb.com/api/v2/viz/"+self.torqueLayer.options.stat_tag+"/viz.json" !== actions.global.vizjson)) {
          this.map.removeLayer(this.torqueLayer);

          // hack to stop (not remove) binding
          this.torqueLayer.stop();
          $('.cartodb-timeslider').remove();
          $('.cartodb-legend-stack').remove();
          this.torqueLayer = null;
          this.created = false;
        }

        if (!this.torqueLayer) {
          if (!this.created) { // sendCode debounce < vis loader
            cdb.vis.Loader.get(actions.global.vizjson, function(vizjson) {
              cartodb.createLayer(self.map, vizjson)
                .done(function(layer) {
                  self.map.fitBounds(vizjson.bounds);

                  actions.global.duration && layer.setDuration(actions.global.duration);

                  self.torqueLayer = layer;
                  self.torqueLayer.stop();

                  self.map.addLayer(self.torqueLayer);

                  self.torqueLayer.on('change:steps', function() {
                    self.torqueLayer.play();
                    self.torqueLayer.actions = torque(self.torqueLayer);
                    self._resetActions(actions);
                  });
                }).on('error', function(err) {
                  console.log("some error occurred: " + err);
                });
            });

            this.created = true;
          }

          return;
        }

        this.story.clear();

        $('.slide-tip').remove();

        this._resetActions(actions);

        if (this.duration && (this.duration !== actions.global.duration)) {
          this.torqueLayer.pause();
          this.torqueLayer.setDuration(actions.global.duration);
        }
      }
    });
  </script>

  <script>
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

    ga('create', 'UA-20934186-21', 'cartodb.github.io');
    ga('send', 'pageview');
  </script>



<script id="md_template" type="text/template">```
-title: "Title"
-author: "Odyssey.js Developers"
-vizjson: "//andrew.cartodb.com/api/v2/viz/df0d717c-02dd-11e4-a1c4-0e10bcd91c2b/viz.json"
-baseurl: "//api.tiles.mapbox.com/v4/cartodb.h0cmgfh8/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiY2FydG9kYiIsImEiOiJrSVRnRWc4In0.R5dvC6Yl-BGQkFEvo5_q0w"
```

# Concept Odyssey
```
- step: 1
- center: [50.9673, 1.8437]
- zoom: 15
```

Blue circles are audience. Scaled by raw number of tweets at that point in the race

Yellow circle is the leader location

# Concept Odyssey
```
- step: 2
- center: [50.9673, 1.8437]
- zoom: 15
```

Blue circles are audience. Scaled by raw number of tweets at that point in the race

Yellow circle is the leader location

# Concept Odyssey
```
- step: 10
- center: [50.9693, 1.8690]
- zoom: 15
```

Blue circles are audience. Scaled by raw number of tweets at that point in the race

Yellow circle is the leader location

# As the race progresses, we can point out key moments
```
- center: [50.9706, 1.8882]
- zoom: 15
- step: 18
```

E.g. a leader overtakes the pack, leading to a huge number of tweets

# Highlight key fact or tweet
```
- step: 23
- center: [50.9504, 1.9054]
- zoom: 14
```

# Highlight key fact or tweet
```
- step: 35
- center: [50.9246, 1.9176]
- zoom: 13
```

# Highlight key fact or tweet
```
- center: [50.9104, 1.9587]
- zoom: 12
- step: 44
```

# Highlight key fact or tweet
```
- center: [50.8523, 2.0301]
- zoom: 12
- step: 82
```

# Highlight key fact or tweet
```
- center: [50.6482, 2.2618]
- zoom: 11
- step: 124
```

# Highlight key fact or tweet
```
- center: [50.6085, 2.4857]
- zoom: 10
- step: 231
```

# Highlight key fact or tweet
```
- center: [50.7325, 2.3676]
- zoom: 12
- step: 415
```

# Highlight key fact or tweet
```
- step: 498
- center: [50.7971, 2.1971]
- zoom: 13
```

# Move in for the finish!
```
- step: 505
- center: [50.8320, 2.2036]
- zoom: 13
S.torqueLayer.actions.pause()
O.Actions.Sleep(10000)
S.torqueLayer.actions.play()
```
</script></body></html>