3

D3新手。

与我的正确尺寸的地图相比,我的城市以错误的投影(非常小且在左侧)绘制。看起来经度可能会颠倒。使用 d3.geo.albersUSA()。

地图的 .scale 设置为 1000,地图看起来很棒。但是我的数据并没有以相同的规模投影。

//Width and height of map (adding relative margins to see if that effects placement of circles. makes no apparent difference.)
var margin = { top: 0, left: 0, right: 0, bottom: 0},
height = 500 - margin.top - margin.bottom,
width = 960 - margin.left - margin.right;


// D3 Projection
var projection = d3.geo.albersUsa()
                   .translate([width/2, height/2])    // translate to center of screen
                   .scale([1000]);          // scale things down so see entire US
        
// Define path generator
var path = d3.geo.path()               // path generator that will convert GeoJSON to SVG paths
             .projection(projection);  // tell path generator to use albersUsa projection

        
// Define linear scale for output
var color = d3.scale.linear()
              .range(["rgb(165,110,255)","rgb(0,45,150)","rgb(0,157,154)","rgb(250,77,86)"]);

var legendText = ["High Demand, High Supply", "Low Demand, Low Supply", "Low Demand, High Supply", "High Demand, Low Supply"];

//Create SVG element and append map to the SVG
var svg = d3.select("body")
            .append("svg")
            .attr("width", width)
            .attr("height", height);
        
// Append Div for tooltip to SVG
var div = d3.select("body")
            .append("div")   
            .attr("class", "tooltip")               
            .style("opacity", 0);

// Load in my states data!
d3.csv("https://raw.githubusercontent.com/sjpozzuoli/Daves_Eagles/main/Data_Main/data_clusters_latlong_ready.csv", function(data) {
color.domain([0,1,2,3]); // setting the range of the input data

// Load GeoJSON data and merge with states data
d3.json("https://gist.githubusercontent.com/michellechandra/0b2ce4923dc9b5809922/raw/a476b9098ba0244718b496697c5b350460d32f99/us-states.json", function(json) {

// Loop through each state data value in the .csv file
for (var i = 0; i < data.length; i++) {

    // Grab State Name
    var dataState = data[i].state;

    // Grab data value 
    var dataValue = data[i].visited;

    // Find the corresponding state inside the GeoJSON
    for (var j = 0; j < json.features.length; j++)  {
        var jsonState = json.features[j].properties.name;

        if (dataState == jsonState) {

        // Copy the data value into the JSON
        json.features[j].properties.visited = dataValue; 

        // Stop looking through the JSON
        break;
        }
    }
}
        
// Bind the data to the SVG and create one path per GeoJSON feature
svg.selectAll("path")
    .data(json.features)
    .enter()
    .append("path")
    .attr("d", path)
    .style("stroke", "#fff")
    .style("stroke-width", "1")
    .style("fill", function(d) {

    // Get data value
    var value = d.properties.visited;

    if (value) {
    //If value exists…
    return color(value);
    } else {
    //If value is undefined…
    return "rgb(213,222,217)";
    }
});

//this piece of code brings in the data and reshapes it to numberic from string. Runs great. 
// Map the cities I have lived in!
d3.csv("https://raw.githubusercontent.com/sjpozzuoli/Daves_Eagles/main/Data_Main/data_clusters_latlong_ready.csv", function(data) {
  var data;
  data.forEach(function(d){
    //create number values from strings
    d.demand_score = +d.demand_score;
    d.hotness_rank = +d.hotness_rank;
    d.hotness_rank_yy = +d.hotness_rank_yy;
    d.unique_viewers_per_property_yy =+ d.unique_viewers_per_property_yy;
    d.median_days_on_market_yy =+ d.median_days_on_market_yy ;
    d.median_listing_price_yy =+ d.median_listing_price_yy;
    d.mortgage_rate =+ d.mortgage_rate;
    d.supply_score =+ d.supply_score;
    d.date = new Date(d.date);
    d.latitude =+ d.latitude;
    d.longitude =+ d.longitude;
    d.class =+ d.class;

  //console.log(d);
  //console.log(d.city);
  var city_state = d.city + ", " + d.state;
      //console.log(city_state);
    });
console.log(data, "data");
svg.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
  //dots are being drawn just with reverse longitude (neg?)and off the map
    .attr("cx", function(d) {
    var coords = ([d.longitude, d.latitude])
    console.log("coords", coords)
    //console.log(d.longitude, "d.longitude");
        return coords[0];
    })
    .attr("cy", function(d) {
        var coords = ([d.longitude, d.latitude])
    return coords[1];
    })
    
  //size of circle working
  .attr("r", function(d) {
        return Math.sqrt(d.hotness_rank) * .05;
    })
    //todo: add if statement to correspond color with class 
    .style("fill", "rgb(217,91,67)")    
        .style("opacity", 0.85) 
4

1 回答 1

2

美国在西半球,所以它的经度是负数,你看到的行为是预期的,这方面的数据是正确的。

问题是您将经度和纬度视为像素坐标,而不是三维地球上的点。这就是它们出现在 SVG 左侧的原因。要将地球上的点转换为笛卡尔像素,我们需要一个投影。

当您使用投影来投影美国的轮廓时(当您将投影提供给路径生成器时),您不会使用投影来投影您的点。每当使用多个地理数据源时,您都需要确保投影的一致性,否则来自不同数据源的要素将无法对齐。

解决方案非常简单 - 使用与轮廓相同的投影来投影您的点:projection([longitude,latitude])这将返回一个包含该点的投影 x 和 y(以像素为单位)坐标的两个元素数组:

.attr("cx", function(d) {
    var coords = projection([d.longitude, d.latitude])
    return coords[0];
})
.attr("cy", function(d) {
    var coords = projection([d.longitude, d.latitude])
    return coords[1];
})

这是一个片段(为了演示和性能,将总圈数限制为 3000):

//Width and height of map (adding relative margins to see if that effects placement of circles. makes no apparent difference.)
var margin = { top: 0, left: 0, right: 0, bottom: 0},
height = 500 - margin.top - margin.bottom,
width = 960 - margin.left - margin.right;


// D3 Projection
var projection = d3.geo.albersUsa()
                   .translate([width/2, height/2])    // translate to center of screen
                   .scale([1000]);          // scale things down so see entire US
        
// Define path generator
var path = d3.geo.path()               // path generator that will convert GeoJSON to SVG paths
             .projection(projection);  // tell path generator to use albersUsa projection

        
// Define linear scale for output
var color = d3.scale.linear()
              .range(["rgb(165,110,255)","rgb(0,45,150)","rgb(0,157,154)","rgb(250,77,86)"]);

var legendText = ["High Demand, High Supply", "Low Demand, Low Supply", "Low Demand, High Supply", "High Demand, Low Supply"];

//Create SVG element and append map to the SVG
var svg = d3.select("body")
            .append("svg")
            .attr("width", width)
            .attr("height", height);
        
// Append Div for tooltip to SVG
var div = d3.select("body")
            .append("div")   
            .attr("class", "tooltip")               
            .style("opacity", 0);

// Load in my states data!
d3.csv("https://raw.githubusercontent.com/sjpozzuoli/Daves_Eagles/main/Data_Main/data_clusters_latlong_ready.csv", function(data) {
color.domain([0,1,2,3]); // setting the range of the input data

// Load GeoJSON data and merge with states data
d3.json("https://gist.githubusercontent.com/michellechandra/0b2ce4923dc9b5809922/raw/a476b9098ba0244718b496697c5b350460d32f99/us-states.json", function(json) {

// Loop through each state data value in the .csv file
for (var i = 0; i < data.length; i++) {

    // Grab State Name
    var dataState = data[i].state;

    // Grab data value 
    var dataValue = data[i].visited;

    // Find the corresponding state inside the GeoJSON
    for (var j = 0; j < json.features.length; j++)  {
        var jsonState = json.features[j].properties.name;

        if (dataState == jsonState) {

        // Copy the data value into the JSON
        json.features[j].properties.visited = dataValue; 

        // Stop looking through the JSON
        break;
        }
    }
}
        
// Bind the data to the SVG and create one path per GeoJSON feature
svg.selectAll("path")
    .data(json.features)
    .enter()
    .append("path")
    .attr("d", path)
    .style("stroke", "#fff")
    .style("stroke-width", "1")
    .style("fill", function(d) {

    // Get data value
    var value = d.properties.visited;

    if (value) {
    //If value exists…
    return color(value);
    } else {
    //If value is undefined…
    return "rgb(213,222,217)";
    }
});

//this piece of code brings in the data and reshapes it to numberic from string. Runs great. 
// Map the cities I have lived in!
d3.csv("https://raw.githubusercontent.com/sjpozzuoli/Daves_Eagles/main/Data_Main/data_clusters_latlong_ready.csv", function(data) {
  var data;
  data.forEach(function(d){
    //create number values from strings
    d.demand_score = +d.demand_score;
    d.hotness_rank = +d.hotness_rank;
    d.hotness_rank_yy = +d.hotness_rank_yy;
    d.unique_viewers_per_property_yy =+ d.unique_viewers_per_property_yy;
    d.median_days_on_market_yy =+ d.median_days_on_market_yy ;
    d.median_listing_price_yy =+ d.median_listing_price_yy;
    d.mortgage_rate =+ d.mortgage_rate;
    d.supply_score =+ d.supply_score;
    d.date = new Date(d.date);
    d.latitude =+ d.latitude;
    // ensure longitue for US is negative:
    d.longitude = d.longitude > 0 ? -d.longitude : + d.longitude;
    d.class =+ d.class;
    });
    
    data = data.filter(function(d,i) {
      return d.longitude && d.latitude && i++ < 3000;
    })
    

svg.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
    .attr("cx", function(d) {
        var coords = projection([d.longitude, d.latitude])
        
        return coords[0]
    })
    .attr("cy", function(d) {
        
        var coords = projection([d.longitude, d.latitude])
        return coords[1];
    })
    
  //size of circle working
  .attr("r", function(d) {
        return Math.sqrt(d.hotness_rank) * .05;
    })
    //todo: add if statement to correspond color with class 
    .style("fill", "rgb(217,91,67)")    
        .style("opacity", 0.85) 
        
})
})

})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

于 2021-09-20T17:26:31.593 回答