UNPKG

react-map-gl-heatmap-overlay

Version:

A heatmap overlay for [react-map-gl](https://github.com/uber/react-map-gl) built using [webgl-heatmap](https://github.com/vicapow/webgl-heatmap) originally created by [Florian Boesch](https://github.com/pyalot).

141 lines (128 loc) 4.89 kB
'use strict'; var React = require('react'); var PropTypes = require('prop-types'); var createReactClass = require('create-react-class'); var Immutable = require('immutable'); var window = require('global/window'); var document = require('global/document'); var r = require('r-dom'); var WebGLHeatmap = require('webgl-heatmap'); var ViewportMercator = require('viewport-mercator-project'); var viridis = require('scale-color-perceptual/hex/viridis.json'); module.exports = createReactClass({ displayName: 'HeatmapOverlay', propTypes: { width: PropTypes.number.isRequired, height: PropTypes.number.isRequired, longitude: PropTypes.number.isRequired, latitude: PropTypes.number.isRequired, zoom: PropTypes.number.isRequired, locations: PropTypes.oneOfType([ PropTypes.array, PropTypes.instanceOf(Immutable.List) ]), lngLatAccessor: PropTypes.func.isRequired, intensityAccessor: PropTypes.func.isRequired, sizeAccessor: PropTypes.func.isRequired, gradientColors: PropTypes.instanceOf(Immutable.List).isRequired }, getDefaultProps: function getDefaultProps() { return { lngLatAccessor: function lngLatAccessor(location) { return [location.longitude, location.latitude]; }, intensityAccessor: function intensityAccessor(location) { return 1 / 10; }, sizeAccessor: function sizeAccessor(location) { return 40; }, gradientColors: Immutable.List(viridis) }; }, _prevGradientColors: null, _gradientTexture: null, componentDidMount: function componentDidMount() { this._update(); }, componentDidUpdate: function componentDidUpdate() { this._update(); }, componentWillUnmount: function componentWillUnmount() { // Clean up! this._heatmap = null; }, /** * Updates `this._gradientTexture` Image if `props.gradientColors` * has changed. * @returns {Image} `this._gradientTexture`. */ _getGradientTexture: function _getGradientTexture() { // Only update the texture when the gradient has changed. if (this._prevGradientColors === this.props.gradientColors) { return this._gradientTexture; } var canvas = document.createElement('canvas'); // 512, 10 because these are the same dimensions webgl-heatmap uses for its // built in gradient textures. var width = 512; var height = 10; canvas.width = String(width); canvas.height = String(height); var ctx = canvas.getContext('2d'); var gradient = ctx.createLinearGradient(0, height / 2, width, height / 2); var colors = this.props.gradientColors; colors.forEach(function each(color, index) { var position = index / (colors.size - 1); gradient.addColorStop(position, color); }); ctx.fillStyle = gradient; ctx.fillRect(0, 0, width, height); var image = new window.Image(); image.src = canvas.toDataURL('image/png'); return image; }, _redraw: function _redraw() { var mercator = ViewportMercator(this.props); this._heatmap.clear(); this._heatmap.adjustSize(); this.props.locations.forEach(function each(location) { var size = this.props.sizeAccessor(location); var intensity = this.props.intensityAccessor(location); var pixel = mercator.project(this.props.lngLatAccessor(location)); this._heatmap.addPoint(pixel[0], pixel[1], size, intensity); }, this); this._heatmap.update(); this._heatmap.display(); }, _update: function _update() { var gradientTexture = this._getGradientTexture(); if (this._gradientTexture !== gradientTexture) { this._heatmap = new WebGLHeatmap({ canvas: this.refs.overlay, intensityToAlpha: true, alphaRange: [0, 0.1], gradientTexture: gradientTexture }); this._gradientTexture = gradientTexture; } this._redraw(); this._prevGradientColors = this.props.gradientColors; }, render: function render() { var pixelRatio = window.devicePixelRatio || 1; return r.canvas({ ref: 'overlay', width: this.props.width * pixelRatio, height: this.props.height * pixelRatio, style: { width: this.props.width + 'px', height: this.props.height + 'px', position: 'absolute', pointerEvents: 'none', left: 0, top: 0 } }); } });