简体   繁体   中英

trouble with D3js world map and svg group element width

I am new to D3js and I am trying to create a world map using d3js shown at this jsfiddle

https://jsfiddle.net/7onjd1pf/

What i am trying to achieve is a world map that resizes with the size of the browser and when you click on a country a new pop up opens with the map of that country and some additional information.

This works fine if I start at a larger screen size (1000px+) and resizes perfectly no matter how small I go.However if I load the page on a small screen first the map does not load properly. I observed that for some reason the g element with id "countries" always starts at a width of 900+ px and does not respond to the dimensions that show up in the inline attrs.The dimension I assign do show up the inline attrs but the computed width starts at something else and does not adjust to screen size if I refresh the browser on a smaller screen size. As a result if i load the page on a smaller screen sizes the map map shows too big. If i start at a large screen and resize the window to smaller screen it works fine. Not sure what is going on here. Can some one explain how i can make this work?

Thanks a ton in advance for the help.

here is the code

HTML:

<

body>
  <div class="container">
    <div class="row">
      <div id="map_container" class="col-xs-12"></div>
      <div id="profile" class="col-xs-10">
        <div id="country_map" class="col-xs-4"></div>
        <button type="button" class="close" aria-label="Close" id="close_map">
          <span aria-hidden="true">&times;</span>
        </button>
        <div id="writeup" class="col-xs-8 of">
          <h1>Heading 1</h1>
          <p>
            <span>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iure, pariatur, quia! Suscipit voluptas, cumque esse numquam dolore quo maxime blanditiis unde ex pariatur id qui minima autem voluptates ducimus dicta.</span>
            <span>Suscipit corporis ex, optio, et libero accusantium dolorum animi. Dolorem unde ratione quas facere eum dolore veritatis ad aliquam repudiandae id expedita minima numquam magnam necessitatibus tenetur nulla, esse soluta!</span>
            <span>Voluptas consectetur totam debitis! Et velit alias, quod sed ut labore iusto assumenda numquam, voluptas repellat aliquam quis nemo maxime officiis sunt architecto minus fugit magnam explicabo deleniti voluptates! Accusantium.</span>
            <span>Quisquam asperiores, voluptatibus quod incidunt facilis pariatur tenetur quae libero accusantium itaque modi nobis odio. Id pariatur eius doloremque, voluptatem tenetur repudiandae nulla enim sint consectetur vitae non, voluptatibus a.</span>
          </p>
        </div>
        <div id="writeup2" class="col-xs-4">
          <h1>Heading 2</h1>
          <p>
            <span>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iure, pariatur, quia! Suscipit voluptas, cumque esse numquam dolore quo maxime blanditiis unde ex pariatur id qui minima autem voluptates ducimus dicta.</span>
            <span>Suscipit corporis ex, optio, et libero accusantium dolorum animi. Dolorem unde ratione quas facere eum dolore veritatis ad aliquam repudiandae id expedita minima numquam magnam necessitatibus tenetur nulla, esse soluta!</span>
            <span>Voluptas consectetur totam debitis! Et velit alias, quod sed ut labore iusto assumenda numquam, voluptas repellat aliquam quis nemo maxime officiis sunt architecto minus fugit magnam explicabo deleniti voluptates! Accusantium.</span>
            <span>Quisquam asperiores, voluptatibus quod incidunt facilis pariatur tenetur quae libero accusantium itaque modi nobis odio. Id pariatur eius doloremque, voluptatem tenetur repudiandae nulla enim sint consectetur vitae non, voluptatibus a.</span>
          </p>
        </div>
        <div class="col-xs-8 table-responsive of">
          <table class="table">
            <thead>
              <tr>
                <th>#</th>
                <th>Firstname</th>
                <th>Lastname</th>
                <th>Age</th>
                <th>City</th>
                <th>Country</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>1</td>
                <td>Anna</td>
                <td>Pitt</td>
                <td>35</td>
                <td>New York</td>
                <td>USA</td>
              </tr>
              <tr>
                <td>2</td>
                <td>Bruce</td>
                <td>Wayne</td>
                <td>35</td>
                <td>Gotham</td>
                <td>USA</td>
              </tr>
              <tr>
                <td>3</td>
                <td>Clarke</td>
                <td>Kent</td>
                <td>35</td>
                <td>Metroplis</td>
                <td>USA</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
</body>

CSS:

/*world map*/

#map_container,
#country_map {
  height: 80vh;
  padding: 20px;
}

.graticule {
  fill: none;
  stroke: #777;
  stroke-width: 0.5px;
  stroke-opacity: 0.5;
}

.land {
  fill: #222;
}

.boundary {
  fill: none;
  stroke: #fff;
  stroke-width: 0.5px;
}

#profile {
  height: 80vh;
  background: white;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: none;
  overflow: auto;
}

#country_map,
#writeup {
  height: 50%;
  /*position: relative;
    top: 0;
    left: 0;*/
}

#writeup2 {
  height: 50%;
}

.of {
  overflow: auto;
}

[tooltip]:before {
  font-family: 'Roboto';
  font-weight: 600;
  -webkit-border-radius: 2px;
  -moz-border-radius: 2px;
  border-radius: 2px;
  background-color: #585858;
  color: #fff;
  content: attr(tooltip);
  font-size: 12px;
  visibility: hidden;
  opacity: 0;
  padding: 5px 7px;
  margin-right: 10px;
  position: absolute;
  right: 100%;
  bottom: 5%;
  white-space: nowrap;
}

[tooltip]:hover:before,
[tooltip]:hover:after {
  visibility: visible;
  opacity: 1;
}

div.toolTip p {
  text-align: left;
}

.toolTip {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  position: absolute;
  display: none;
  width: auto;
  height: auto;
  background: none repeat scroll 0 0 white;
  border: 0 none;
  border-radius: 8px 8px 8px 8px;
  box-shadow: -3px 3px 15px #888888;
  color: black;
  font: 12px sans-serif;
  padding: 5px;
  text-align: center;
}

JS (have removed the country_data from the code below, but it can be viewed at the jsfiddle link):

$(function() {


    var data,
        map = document.getElementById("map_container"), 
        inital_width =  map.offsetWidth,    
        map_width = map.offsetWidth,
        map_height = map_width * .618,
        color = d3.scale.category20c(),
        xy = d3
                    .geo
                    .mercator()
                    .translate([map_width / 2, 450]),
        path = d3
                        .geo
                        .path()
                        .projection(xy),
        svg = d3
                        .select('#map_container')
                        .append('svg:svg'),
        countries = svg
                                .append('svg:g')
                                .attr('id', 'countries');
  /* World Map */
    function make_map(){
        console.log("here")
        map_width = map.offsetWidth;
        map_height = map_width*.618//map.offsetHeight;

        console.log(map_height,map_width,inital_width)

        svg
            .attr("width",map_width * .9)
            .attr("height",map_height * .9);
        countries
            .attr('width',map_width* .7)
            .attr('height',map_height* .7)
            .attr("transform", "scale(" + map_width/inital_width + ")");

        countries.selectAll('path')
        .data(countries_data.features) 
        .enter()
        .append('svg:path')
        .attr('d', path)
        .attr("class","country")
        .attr('fill', "gray")
    }
    make_map();

    var country = d3.selectAll(".country");
    var div = d3.select("body").append("div")
        .attr("class", "toolTip");

    country.on("mousemove",function(d){
        d3.select(this).attr({
            'fill':"lightblue"
        })
        div.style("left", d3.event.pageX+10+"px");
        div.style("top", d3.event.pageY-25+"px");
        div.style("display", "inline-block");
        div.html(d.properties.name)
    })

    country.on("mouseout",function(d){
        d3.select(this).attr({
            'fill': 'gray'
        })
        div.style("display", "none");
    })

    var country_map_div = document.getElementById("country_map"),
        profile = $('#profile'),
        w2 = profile.width() * 4/12,
        h2=  profile.height() * .5,
        close_map = d3.select("#close_map"),
        xy2 = d3.geo.equirectangular(),
        selection,
        country_map = d3.select("#country_map")
            .append('svg:svg')
            .attr('width',w2)
            .attr('height',h2 )
            .attr("id","country_map_svg");

    country.on("click", clicked);

    function clicked(d){
        selection = d;

        country_map.selectAll("path").remove();

        w2 = profile.width() * 4/12;
        h2=  profile.height() * .5;

        var bounds = path.bounds(d),
            dx = bounds[1][0] - bounds[0][0],
            dy = bounds[1][1] - bounds[0][1],
            x = (bounds[0][0] + bounds[1][0]) / 2,
            y = (bounds[0][1] + bounds[1][1]) / 2,
            scale = .9 / Math.max(dx / w2, dy / h2),
            translate = [w2 / 2 - scale * x, h2 / 2 - scale * y];

        country_map
        .append("path")
        .datum(d)
        .attr('id',"c_map")
        .attr("d",path)
        .attr("fill",function(d) { return color(d.properties.name);})
        .attr("transform", "translate(" + translate + ")scale(" + scale + ")");
        profile.show(300);
    }   

    close_map.on("click",function(d){
        profile.hide();
    })


    var win =   d3.select(window);          
    win.on("resize", sizeChange);

    function sizeChange(element,id) {

        map_width = map.offsetWidth;
        map_height = map_width * .618;
        countries
        .attr("transform", "scale(" + map_width/inital_width + ")")
        .attr("height",map_height);


        var bounds = path.bounds(selection),
            dx = bounds[1][0] - bounds[0][0],
            dy = bounds[1][1] - bounds[0][1],
            x = (bounds[0][0] + bounds[1][0]) / 2,
            y = (bounds[0][1] + bounds[1][1]) / 2,
            w2 =  $("#profile").width() *4/12,
            h2 =  $("#profile").height()*.5,
            scale = .9 / Math.max(dx / w2, dy / h2),
            translate = [w2 / 2 - scale * x, h2 / 2 - scale * y];

        country_map.attr("width", w2 )
            .attr("height", h2)
        country_map.select("path")
            .attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}

});

PS. Apologies in advance for putting all the data dump required for the map paths at the top of js. I am using jsfiddle for the frist time and was not able to figure how i can put it in a separate js file.

When appending your countries the first time you don't set a scale. So, you use the default scale. The default scale of a Mercator projection in d3 is 931/Tau (Tau = 2π). This takes the 360 degrees of longitude of the earth (or 2π radians) and spreads them over 961 pixels horizontally. This means the map will only display properly with widths of 961 pixels or more (or 960px, which is the default width of bl.ocks.org). This explains this statement:

This works fine if I start at a larger screen size (1000px+) and resizes perfectly no matter how small I go.

You resize the map not by rescaling the projection, but by modifying the svg directly, which is why the resize works here when starting with pixel widths of about 1000

You need to set your projection scale. You need to know your desired map width to do so. It looks like you want to set the g width to 70% of that of the svg and the svg at 90% of the variable map_width , so your scale look like:

.scale(map_width/(Math.PI*2)*0.9*0.7)

This will wrap the 360 degrees of longitude across the intended amount of pixels.

Note, you should also update the .translate property of the projection (currently for example, the y offset is fixed at 450px), as it will also be relative to the map width and height - this will properly center your map within the window.

Here is an updated fiddle .


Why do your pop up features display properly? Because you set the scale (and bounds) in the following code:

    var bounds = path.bounds(d),
        dx = bounds[1][0] - bounds[0][0],
        dy = bounds[1][1] - bounds[0][1],
        x = (bounds[0][0] + bounds[1][0]) / 2,
        y = (bounds[0][1] + bounds[1][1]) / 2,
        scale = .9 / Math.max(dx / w2, dy / h2),

You use this to properly set the projection, which includes at a minimum, both scale and translate attributes. This is why this part of your code works as desired as contrasted with the base map.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM