cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External CSS

These stylesheets will be added in this order and before the code you write in the CSS editor. You can also add another Pen here, and it will pull the CSS from it. Try typing "font" or "ribbon" below.

Quick-add: + add another resource

Add External JavaScript

These scripts will run in this order and before the code in the JavaScript editor. You can also link to another Pen here, and it will run the JavaScript from it. Also try typing the name of any popular library.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              #map
#mapover
  .content
    #intro.content-box
      h2 location-history-visualizer with blackHole.js
      p It's the third example for <a href="https://github.com/artzub/blackhole.js" target="_blank">blackHole.js</a>. Shows how to use blackHole.js with maps.
      
      p To start off, you'll need to go to <a href="https://google.com/takeout" target="_blank">Google Takeout</a> to download your Location History data: on that page, deselect everything except Location History by clicking "Select none" and then reselecting "Location History". Then hit "Next" and, finally, click "Create archive". Once the archive has been created, click "Download". Unzip the downloaded file, and open the "Location History" folder within. <b>Then, drag and drop <i>LocationHistory.json</i> from inside that folder onto this page.</b> Let the visualization begin!
      p.fallback Alternatively, select your <b>LocationHistory.json</b> file directly: <input name="file" type="file" id="file"></input>
      p.credit A project by <a href="http://artzub.com" target="_blank">Artem Zubkov</a>. Based on the idea of project <a href="https://github.com/theopolisme/location-history-visualizer">location-history-visualizer made by @theopolisme</a>.
#gui-cont
            
          
!
            
              body, html, #map, #mapover {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  top: 0;
  left: 0;
}

#mapover {
  display: table;
  background: rgba(0, 0, 0, .2);
  position: absolute;
  z-index: 1000;
}

.content {
	display: table-cell;
	vertical-align: middle;
}

.content-box {
	margin: 0 auto;
	width: 50%;
	min-width: 300px;
	max-width: 500px;
	background-color: rgba(255,255,255,0.9);
	padding: 10px;
	border-radius: 10px;
}


#gui-cont {
  position: absolute;
  z-index: 8;
  right: 0;
  top: 0;
}

.credit {
	font-size: 0.8em;
	text-align: center;
}

            
          
!
            
              /** 
* adding wrapper for blackhole.js layer for map
*/
!function(){
L.BlackHoleLayer = L.Class.extend({
    // выполняется при инициализации слоя
    initialize: function () {
    },

    // когда слой добавляется на карту то вызывается данный метод
    onAdd: function (map) {
        //
        if (this._el) {
            this._el.style('display', null);
            if (this._bh.IsPaused())
                this._bh.resume();
            return;
        }
      
      
        this._map = map;

        //выбираем текущий контейнер для слоев и создаем в нем наш div,
        //в котором будет визуализация 
        this._el = d3.select(map.getPanes().overlayPane).append('div');
      
        // создаем объект blackHole
        this._bh = d3.blackHole(this._el);

        //задаем класс для div
        var animated = map.options.zoomAnimation && L.Browser.any3d;
        this._el.classed('leaflet-zoom-' + (animated ? 'animated' : 'hide'), true);
        this._el.classed('leaflet-blackhole-layer', true);

        // определяем обработчики для событии
        map.on('viewreset', this._reset, this)
            .on('resize', this._resize, this)
            .on('move', this._reset, this)
            .on('moveend', this._reset, this)
        ;

        this._reset();
    },

    // соответственно при удалении слоя leaflet вызывает данный метод
    onRemove: function (map) {
        if (this.onHide && typeof this.onHide === 'function')
            this.onHide();

        this._el.style('display', 'none');
        if (this._bh.IsRun())
            this._bh.pause();
    },

    // вызывается для того чтоб добывать данный слой на выбранную карту.
    addTo: function (map) {
        map.addLayer(this);
        return this;
    },

    // внутренний метод используется для события resize
    _resize : function() {
        // выполняем масштабирование визуализации согласно новых размеров.
        this._bh.size([this._map._size.x, this._map._size.y]);
        this._reset();
    },

    // внутренний метод используется для позиционирования слоя с визуализацией корректно на экране
    _reset: function () {
        var topLeft = this._map.containerPointToLayerPoint([0, 0]);

        var arr = [-topLeft.x, -topLeft.y];

        var t3d = 'translate3d(' + topLeft.x + 'px, ' + topLeft.y + 'px, 0px)';

        this._bh.style({
            "-webkit-transform" : t3d,
            "-moz-transform" : t3d,
            "-ms-transform" : t3d,
            "-o-transform" : t3d,
            "transform" : t3d
        });
        this._bh.translate(arr);
    }
});


L.blackHoleLayer = function() {
    return new L.BlackHoleLayer();
};
}();
// создаем объект карты в div#map
var map = new L.Map('map', {
  maxZoom : 19,
  minZoom : 2
}).setView([0,0], 2);
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');

var ggl = new L.Google('ROADMAP', {
	mapOptions: {
    backgroundColor: "#19263E",
    styles : [
    {
        "featureType": "water",
        "stylers": [
            {
                "color": "#19263E"
            }
        ]
    },
    {
        "featureType": "landscape",
        "stylers": [
            {
                "color": "#0E141D"
            }
        ]
    },
    {
        "featureType": "poi",
        "elementType": "geometry",
        "stylers": [
            {
                "color": "#0E141D"
            }
        ]
    },
    {
        "featureType": "road.highway",
        "elementType": "geometry.fill",
        "stylers": [
            {
                "color": "#21193E"
            }
        ]
    },
    {
        "featureType": "road.highway",
        "elementType": "geometry.stroke",
        "stylers": [
            {
                "color": "#21193E"
            },
            {
                "weight": 0.5
            }
        ]
    },
    {
        "featureType": "road.arterial",
        "elementType": "geometry.fill",
        "stylers": [
            {
                "color": "#21193E"
            }
        ]
    },
    {
        "featureType": "road.arterial",
        "elementType": "geometry.stroke",
        "stylers": [
            {
                "color": "#21193E"
            },
            {
                "weight": 0.5
            }
        ]
    },
    {
        "featureType": "road.local",
        "elementType": "geometry",
        "stylers": [
            {
                "color": "#21193E"
            }
        ]
    },
    {
        "elementType": "labels.text.fill",
        "stylers": [
            {
                "color": "#365387"
            }
        ]
    },
    {
        "elementType": "labels.text.stroke",
        "stylers": [
            {
                "color": "#fff"
            },
            {
                "lightness": 13
            }
        ]
    },
    {
        "featureType": "transit",
        "stylers": [
            {
                "color": "#365387"
            }
        ]
    },
    {
        "featureType": "administrative",
        "elementType": "geometry.fill",
        "stylers": [
            {
                "color": "#000000"
            }
        ]
    },
    {
        "featureType": "administrative",
        "elementType": "geometry.stroke",
        "stylers": [
            {
                "color": "#19263E"
            },
            {
                "lightness": 0
            },
            {
                "weight": 1.5
            }
        ]
    }
]
	}
});
map.addLayer(ggl);

var visLayer = L.blackHoleLayer()
  , heat = L.heatLayer( [], {
        opacity: 1,
        radius: 25,
        blur: 15
    }).addTo( map )
  ;
visLayer.addTo(map);

var bh = visLayer._bh
  , parentHash
  , locations
  , cur
  , all
  , stepDate = 864e5
  , df = d3.time.format('%d-%m-%Y')
  , dn = d3.time.format(',.2f')
  , progress = {
    state: 'init',
    process : 0
  };

function pause() {
  if (bh.IsPaused()) {
    bh.resume();
    return;
  }
  bh.pause();
  progress.state = 'Paused...'
}

function restart() {
  bh.stop();
  cur = 0;
  
  if ( !locations || !locations.length)
    return;
  
  heat.setLatLngs([]);
  
  all = locations.length;
  progress.process = 0;
  progress.state = 'parsing';
  bh.start(locations, map._size.x, map._size.y, true);
  visLayer._resize();
}


!function() {
  var gui = new dat.GUI({
    load : JSON
    , preset : 'Default'
    , autoPlace: false
  });

  d3.select('#gui-cont').node().appendChild(gui.domElement);

  var f = gui.addFolder('Items');
  f.add(bh.setting, 'alpha', 0.001, .1).step(.0001).listen();
  f.add(bh.setting, 'childLife', 0, 1000).step(5).listen();
  f.add(bh.setting, 'parentLife', 0, 1000).step(5).listen();
  f.add(bh.setting, 'rateOpacity', .01, 10).step(.1).listen();
  f.add(bh.setting, 'rateFlash', .01, 10).step(.1).listen();
  f.add(bh.setting, 'padding', 0, 100).step(5).listen();

  f = gui.addFolder('Behavior');
  f.add(bh.setting, 'skipEmptyDate').listen();
  f.add(bh.setting, 'asyncParsing').listen();
  f.add(bh.setting, 'increaseChildWhenCreated').listen();
  f.add(bh.setting, 'createNearParent').listen();  
  f.add(bh.setting, 'speed', 0, 1000).step(5).listen();
  
  
  f = gui.addFolder('Drawing');
  f.add(bh.setting, 'blendingLighter').listen();
  f.add(bh.setting, 'drawTrack').listen();
  f.add(bh.setting, 'drawEdge').listen();
  f.add(bh.setting, 'drawChild').listen();
  f.add(bh.setting, 'drawChildLabel').listen();
  f.add(bh.setting, 'drawParent').listen();
  f.add(bh.setting, 'drawParentLabel').listen();
  f.add(bh.setting, 'drawPaddingCircle').listen();
  f.add(bh.setting, 'drawHalo').listen();
  f.add(bh.setting, 'drawAsPlasma').listen();
  f.add(bh.setting, 'drawParentImg').listen();
  f.add(bh.setting, 'hasLabelMaxWidth').listen();
  
  gui.add(progress, 'state').listen();
  gui.add(progress, 'process', 0, 100).name('state %').listen();
  
  gui.add(window, 'pause').name('pause/resume');
  gui.add(window, 'restart');
  
  gui.remember(bh.setting);
}();

d3.select('#file' ).on('change', function () {
  stageTwo( this.files[0] );
} );


L.control.layers({}, {
  'Heat map': heat,
  //'Markers': markers,
}).addTo(map).setPosition('topleft');

function stageTwo ( file ) {
  bh.stop();
  
  var SCALAR_E7 = 0.0000001 // Since Google Takeout stores latlngs as integers
    ;

  // First, change tabs
  d3.select('#mapover').style('display', 'none');
  
  // Now start working!
  processFile( file );

  function processFile ( file ) {
    var reader = new FileReader();
    
    progress.title = 'loading...';
    progress.process = 0;

    reader.onprogress = function ( e ) {
      progress.process = Math.round( ( e.loaded / e.total ) * 100 );
    };

    reader.onload = function ( e ) {

      progress.title = 'Generating map...';
      progress.process = 0;
      
      try {
        locations = JSON.parse( e.target.result ).locations;
        if ( !locations || !locations.length ) {
          throw new ReferenceError( 'No location data found.' );
        }
      } catch ( ex ) {
        progress.title = ex.message;
        console.log(ex);
        return;
      }
      
      parentHash = {};
      
      var last;
      
      var sw = [-Infinity, -Infinity]
          , se = [Infinity, Infinity];
      

      locations.forEach(function(d, i) {
        d._id = i;
        d.timestampMs = +d.timestampMs;
        
        d.lat = d.latitudeE7 * SCALAR_E7;
        d.lon = d.longitudeE7 * SCALAR_E7;
        d.pkey = d.latitudeE7 + "_" + d.longitudeE7;
        
        sw[0] = Math.max(d.lat, sw[0]);
        sw[1] = Math.max(d.lon, sw[1]);
        se[0] = Math.min(d.lat, se[0]);
        se[1] = Math.min(d.lon, se[1]);
        
        
        d.parent = parentHash[d.pkey] || makeParent(d);
      });
      
      locations.sort(function(a, b) {
        return a.timestampMs - b.timestampMs;
      });
      
      locations.forEach(function(d, i) {
        d._id = i;
      });
      map.fitBounds([sw, se]);
      
      restart();
    };

    reader.onerror = function () {
      prorgess.title = reader.error;
      console.log(reader.error);
    };

    reader.readAsText(file);
  }
}

function makeParent(d) {
  var that = {_id : d.pkey};
  that.latlng = new L.LatLng(d.lat, d.lon);
  
  //getting always actual coordinates
  that.x = {
    valueOf : function() {
      var pos = map.latLngToLayerPoint(that.latlng);
      return pos.x;
    }
  };
    
  that.y = {
    valueOf : function() {
      var pos = map.latLngToLayerPoint(that.latlng);
      return pos.y;
    }
  };

  return parentHash[that.id] = that;
}

bh.setting.increaseChild = false;
bh.setting.createNearParent = false;
bh.setting.speed = 100;
bh.setting.skipEmptyDate = true;
bh.setting.zoomAndDrag = false;
bh.setting.drawParent = false;
bh.setting.drawParentLabel = false;
bh.setting.padding = 0;
bh.setting.parentLife = 0;
bh.setting.blendingLighter = true;
bh.setting.drawAsPlasma = true;
bh.setting.drawTrack = true;

stepDate = 1;
//bh.setting.asyncParsing = true;
bh.on('getGroupBy', function (d) {
    return d._id //d.timestampMs;
  })
  .on('getParentKey', function (d) {
    return d._id;
  })
  .on('getChildKey', function (d) {
    return 'me';
  })
  .on('getCategoryKey', function (d) {
    return 'me';
  })
  .on('getCategoryName', function (d) {
    return 'location';
  })
  .on('getParentLabel', function (d) {
    return '';
  })
  .on('getChildLabel', function (d) {
    return 'me';
  })
  .on('calcRightBound', function (l) {
    return l + stepDate;
  })
  .on('getVisibleByStep', function (d) {
    return true;
  })
  .on('getCreateNearParent', function (d) {
    return true;
  })
  .on('getParentRadius', function (d) {
    return 1;
  })
  .on('getChildRadius', function (d) {
    return 10;
  })
  .on('getValue', function (d) {
    return 1;
  })
  .on('getParentPosition', function (d) {
    return [d.x, d.y];
  })
  .on('getParentFixed', function (d) {
    return true;
  })
  .on('started', function() {
    progress.state = "Run...";
    progress.process = 0;
    cur = 0;
  })
  .on('processing', function(items, l, r) {
    cur += items ? items.length : 0;
  
    var m = d3.mean(items, function(d) {
      return d.timestampMs;
    });
  
    progress.state = df(new Date(m));
    progress.process = (cur / all) * 100;
    setTimeout(setMarkers(items), 10);
  })
  .on('parsing', function() {
    progress.state = cur++ + ' of ' + all;
    progress.process = (cur / all) * 100;
  })
  .sort(null)
;

function setMarkers(arr) {
  return function() {
    arr.forEach(function (d) {
      var tp = d.parentNode.nodeValue;
      heat.addLatLng(tp.latlng);
    });
  }
}
            
          
!
999px
Loading ..................

Console