Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

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.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                #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
              
            
!

CSS

              
                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;
}

              
            
!

JS

              
                /** 
* 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

Console