I'm trying to duplicate this d3 map example within React so that I can use it as a component in Plotly Dash. However, there is an issue (I think with D3-tile) that's resulting in undefined strings in the opnestreemap urls. This prevents the code from grabbing the actual images for the tiles, and results in the following image:
The errors produced as you zoom in look like this:
Here is the full MyMap.react.js code. It seems like the error is coming from the tiles variable not being filled with data, but I'm not sure what the cause of that would be. Any help would be greatly appreciated!
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import * as d3 from 'd3';
import {mesh} from 'topojson-client';
import * as d3Tile from 'd3-tile';
function createGeoMap(divId) {
var pi = Math.PI,
tau = 2 * pi;
var width = Math.max(960, window.innerWidth),
height = Math.max(500, window.innerHeight);
// Initialize the projection to fit the world in a 1×1 square centered at the origin.
var projection = d3.geoMercator()
.scale(1 / tau)
.translate([0, 0]);
var path = d3.geoPath()
.projection(projection);
var tile = d3Tile.tile()
.size([width, height]);
var zoom = d3.zoom()
.scaleExtent([1 << 11, 1 << 14])
.on('zoom', zoomed);
var svg = d3.select('#' + divId).append('svg')
.attr('width', width)
.attr('height', height);
var raster = svg.append('g');
var vector = svg.append('path');
d3.json('https://gist.githubusercontent.com/mbostock/4090846/raw/d534aba169207548a8a3d670c9c2cc719ff05c47/us.json', function(error, us) {
if (error) throw error;
vector
.datum(mesh(us, us.objects.states));
// Compute the projected initial center.
var center = projection([-98.5, 39.5]);
// Apply a zoom transform equivalent to projection.{scale,translate,center}.
svg
.call(zoom)
.call(zoom.transform, d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(1 << 12)
.translate(-center[0], -center[1]));
});
function zoomed() {
var transform = d3.event.transform;
var tiles = tile
.scale(transform.k)
.translate([transform.x, transform.y])
();
projection
.scale(transform.k / tau)
.translate([transform.x, transform.y]);
vector
.attr('d', path);
var image = raster
.attr('transform', stringify(tiles.scale, tiles.translate))
.selectAll('image')
.data(tiles, function(d) { return d; });
image.exit().remove();
image.enter().append('image')
.attr('xlink:href', function(d) { return 'http://' + 'abc'[d[1] % 3] + '.tile.openstreetmap.org/' + d[2] + '/' + d[0] + '/' + d[1] + '.png'; })
.attr('x', function(d) { return d[0] * 256; })
.attr('y', function(d) { return d[1] * 256; })
.attr('width', 256)
.attr('height', 256);
}
function stringify(scale, translate) {
var k = scale / 256, r = scale % 1 ? Number : Math.round;
return 'translate(' + r(translate[0] * scale) + ',' + r(translate[1] * scale) + ') scale(' + k + ')';
}
}
export default class MyMap extends Component {
constructor() {
super();
this.plot = this.plot.bind(this);
}
plot(props) {
createGeoMap(props.id);
}
componentDidMount() {
this.plot(this.props);
}
shouldComponentUpdate() {
return false;
}
componentWillReceiveProps(newProps) {
if(newProps.id !== this.props.id) {
this.plot(newProps);
}
}
render() {
const {id} = this.props;
return (
<div id={id} />
);
}
}
MyMap.propTypes = {
/**
* The ID used to identify this compnent in Dash callbacks
*/
id: PropTypes.string,
};