0

I have been on this for 10 hours now and I am starting to think that it is either something really small or something fundamental I am missing here.

This is a simple component that all I want is to render the us map with the d3 version 4 via geoAlbersUsa and PROJECT it in a panel so that it is SCALED. If I remove the projection all works great and I get the map. The moment in any shape or form I do the projection it simply shows a colored rectangle. Here is the code:

import React from 'react';
import * as d3 from 'd3';
import * as topojson from 'topojson';
import { Panel, Alert } from 'react-bootstrap';

class MapBlock extends React.Component {
constructor(props) {
    super(props);
    this.state = {
        states: []
    };
    this.projection = d3.geoAlbersUsa().scale(1000);
    this.geoPath = d3.geoPath().projection(this.projection);
}

componentDidMount() {
    d3.json("https://d3js.org/us-10m.v1.json", function(error, us) {
        if (error) throw error;
        this.setState({states: topojson.feature(us, us.objects.states).features})
    }.bind(this));
}

render() {
    let { states } = this.state;
    return (
        <Panel>
            <svg width="550" height="430">
                <g className="states">
                    {
                        states.map((feature, index) => <path key={index} d={this.geoPath(feature)} />)
                    }
                </g>
            </svg>
        </Panel>
    )
}
}

export default MapBlock

The html is also pretty simple:

<Grid>
    <Row className="show-grid">
        <Col sm={12} md={6}>
            <MapBlock />
        </Col>
        <Col sm={12} md={6}>
            Some Text ...
        </Col>
   </Row>
</Grid>

Any help is appreciated and please let me know if the details are not enough. Thanks in advance.

UPDATE:

Here is what I get now trying to work with the suggestion from the initial comment from @Mark.

enter image description here I tried something like this:

 var states = topojson.feature(us, us.objects.states).features;

 var width  = 560;
 var height = 300;

 var b = path.bounds(states[0]),
     s = .98 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1]  - b[0][1]) / height),
     t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

 // Update the projection to use computed scale & translate.
 projection
     .scale(s)
     .translate(t);

 vis.selectAll("path")
     .data(states)
     .enter()
     .append("path")
     .attr("class", "states")
     .attr("d", path);

For this I changed to render to be just:

<Panel>
    <div id="vis"></div>
</Panel>

What I am not sure here is path.bounds(states[0]) was a guess of mine just so I can see if it would work based on the panel boundary I have. I did "scale" it as you can see from the image but I guess not exactly :)

Akrion
  • 18,117
  • 1
  • 34
  • 54
  • 1
    It seems like you've arbitrarily picked a `.scale` of 1000 but need to determine a scale/translation that works for your map and container. See this answer [here](http://stackoverflow.com/a/14691788/16363) – Mark Feb 04 '17 at 14:11
  • Hmm ... I did try from 10 to 500 to 5000 ... even @ the smallest it just shows a small rectangle inside the parent. But I will check the link. Thanks for your comment. I appreciate it. – Akrion Feb 04 '17 at 15:31
  • I guess what I am still missing is ... are you suppose to calculate the projection/transition once for the entire map or for each state of the geoAlbersUsa map? And even of you are off you should still see some kind of shape which more or less looks like the us map no? No matter what I do so far I only get a colored rectangle regardless of the scale/transition I try. – Akrion Feb 04 '17 at 18:12
  • Where did you get that JSON? From [here](https://github.com/d3/d3.github.com/blob/master/us-10m.v1.json)? It looks very broken. – Mark Feb 04 '17 at 18:38
  • 1
    For instance, compare it to this [one here](https://github.com/d3/d3-geo/blob/master/test/data/us-10m.json). – Mark Feb 04 '17 at 18:40
  • Path I got was from where most of the examples I see online: https://d3js.org/us-10m.v1.json – Akrion Feb 04 '17 at 18:41
  • 1
    That file is *pre-projected*, see [here](https://github.com/topojson/us-atlas#us/10m.json). This is why it works **without** a projection. Try the file I linked. – Mark Feb 04 '17 at 18:44
  • Now it makes sense. Will do! Thanks. – Akrion Feb 04 '17 at 18:46
  • Awesome ... that was the solution to the main issue with projection. Had no idea it was pre-projected already. Now as far as the calculation for the scaling ... what I do not get is this line `var b = path.bounds(states),` where states is the topojson states features. Is this even correct? Like what bounds am I supposed to get a as a base? As far as I understood it should be the bounds of the entire map right? – Akrion Feb 04 '17 at 18:51
  • 1
    I just answered the question with some example code. It looks like d3 version 4 has an easier way to dynamically scale the projection. – Mark Feb 05 '17 at 14:05

1 Answers1

2

I just realized that d3 version 4 has an even easier way to fit a projection to a bounding space. They've introduced the fitSize method on the projection.

Here it is in action:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>jQuery UI Resizable - Default functionality</title>
  <script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
  <script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/topojson.v2.min.js"></script>
  <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" />
  <style>
    #resizable {
      width: 150px;
      height: 150px;
      padding: 0.5em;
    }
  </style>

  <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

  <script>
    $(function() {
      $("#resizable").resizable({
        resize: function(event, ui) {
          drawMap(ui.size.width, ui.size.height);
        }
      });

      var svg = d3.select("#resizable")
        .append("svg"),
        path = svg.append("path");

      var states;
      d3.json('https://jsonblob.com/api/9e44a352-eb09-11e6-90ab-059f7355ffbc', function(error, data) {

        states = topojson.feature(data, data.objects.states);
        drawMap(150, 150);

      });

      function drawMap(w, h) {

        svg.attr('width', w)
          .attr('height', h);

        var projection = d3.geoAlbersUsa()
          .scale(1).fitSize([w, h], states);

        var geoPath = d3.geoPath().projection(projection);

        path
          .datum(states)
          .attr("d", geoPath);

      }


    });
  </script>
</head>

<body>
  <div id="resizable" class="ui-widget-content"></div>
</body>

</html>
Mark
  • 106,305
  • 20
  • 172
  • 230
  • Awesome! Thanks for all your help. – Akrion Feb 06 '17 at 01:22
  • Just one more note ... I had to actually pull version 4.5 for the fitSize ... I had 4.1.1 and it complained about missing the function. I did the refactoring to now switch to fitSize. Thanks again! – Akrion Feb 06 '17 at 01:59