D3 地图仅显示一个县的空白/死区

D3 map showing a blank/dead area for only one county

提问人:Arkut 提问时间:9/24/2023 更新时间:9/24/2023 访问量:30

问:

我更新了按 SNAP 参与划分的美国所有县的 D3 地图,以使用更新的数据(2017-2021 年 5 年 ACS)和更新的 topojson 文件(counties-alters-10m.json,来自 US Atlas,位于 Github 上:https://github.com/topojson/us-atlas)。除了一个县之外,它对所有县都很好:亚利桑那州的莫哈维县。

我检查了 Mohave County (04015) 的 FIPS 代码,该代码在 SNAP 数据和 topojson 文件中都是正确的——前导 0 显示和所有内容。

下面是 JavaScript 代码,它将所有县的集合分层,其中县集分为大都市区的县、小城市区的县和两者都不中的县,因此我可以添加按钮来打开/关闭每组县:

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    active = d3.select(null);

var zoom = d3.zoom()
    .scaleExtent([1, 8])
    .on("zoom", zoomed);

var path = d3.geoPath();

var svg = d3.select("svg")
    .attr("preserveAspectRatio", "xMidYMid meet")
    .attr("viewBox", "0 0 " + (width) + " " + (height))
    .on("click", stopped, true);

var COLOR_COUNTS = 5;

svg.append("rect")
    .attr("class", "background")
    .attr("width", width)
    .attr("height", height)
    .on("click", reset);

var g = svg.append("g");

svg
    .style("border", "2px solid #000")
    .call(zoom);

var formatPercent = d3.format(".1%");
var formatPercentRnd = d3.format(".0%");

// Queue up datasets using d3 Queue
d3.queue()
    .defer(d3.json, "./counties-albers-10m-min.json")
    .defer(d3.tsv,  "./snap_acs_2021_5yr-min.tsv")
    .await(ready);

function ready(error, us, snap) {
    if (error) throw error;

    var rateById = {}; // Create empty object for holding dataset
    var nameById = {};
    var moeById = {};
    var metroById = {};
    var typeById = {};
    var stRateById = {};
    var stNameById = {};
    var stMoeById = {};
    var stRateMetById = {};
    var stRateMicById = {};
    var stRateRurById = {};
    var stMoeMetById = {};
    var stMoeMicById = {};
    var stMoeRurById = {};
        snap.forEach(function(d) {
            rateById[d.county_fips] = d.pct_hh_snap; // Create property for each ID, give it value from rate
            nameById[d.county_fips] = d.county;
            moeById[d.county_fips] = d.pct_hh_snap_moe;
            metroById[d.county_fips] = d.metroflg;
            typeById[d.county_fips] = d.metrolabel;
            stRateById[d.county_fips] = d.state_pct_hh_snap;
            stNameById[d.county_fips] = d.state;
            stMoeById[d.county_fips] = d.state_pct_hh_snap_moe;
            stRateMetById[d.county_fips] = d.state_pct_hh_snap_metro;
            stRateMicById[d.county_fips] = d.state_pct_hh_snap_micro;
            stRateRurById[d.county_fips] = d.state_pct_hh_snap_rural;               
            stMoeMetById[d.county_fips] = d.state_pct_hh_snap_moe_metro;
            stMoeMicById[d.county_fips] = d.state_pct_hh_snap_moe_micro;
            stMoeRurById[d.county_fips] = d.state_pct_hh_snap_moe_rural;
});

var color = d3.scaleThreshold()
    .domain([.05, .1, .15, .20, .25])
    .range(d3.schemeGreens[6]);
    
g.selectAll("path")
    .data(topojson.feature(us, us.objects.nation).features)
    .enter().append("path")
    .attr("class", "nation")
    .attr("d", path);

g.selectAll("path")
    .data(topojson.feature(us, us.objects.counties).features)
    .enter().append("path")
    .attr("d", path)
    .attr("class", "counties")
    .on("click", clicked)
    .style("fill", function(d) {
        return color(rateById[d.id]);
    }).on("mousemove", function(d) {          
        var html = "";
    html += "<div class=\"tooltip_kv\">";
    html += "<span class=\"tooltip_key\">";
    html += nameById[d.id] + ", " + stNameById[d.id];
    html += " (" + typeById[d.id] + ")<br/><br/>"
    html += (rateById[d.id] ? formatPercent(rateById[d.id]) : "");
    html += " Hhlds w/ SNAP";
    html += " (&plusmn;";
    html += (moeById[d.id] ? formatPercent(moeById[d.id]) : "");
    html += ")<br/><br/>";
    html += "Statewide:<br/>&emsp;";
    html += (stRateById[d.id] ? formatPercent(stRateById[d.id]) : "");
    html += " Overall (&plusmn;";
    html += (stMoeById[d.id] ? formatPercent(stMoeById[d.id]) : "");
    html += ")<br/>&emsp;";
    html += (stRateMetById[d.id] ? formatPercent(stRateMetById[d.id]) : "");
    html += " Metro (&plusmn;";
    html += (stMoeMetById[d.id] ? formatPercent(stMoeMetById[d.id]) : "");
    html += ")<br/>&emsp;";
    html += (stRateMicById[d.id] ? formatPercent(stRateMicById[d.id]) : "");
    html += " Small Town";
    html += " (&plusmn;";
    html += (stMoeMicById[d.id] ? formatPercent(stMoeMicById[d.id]) : "");
    html += ")<br/>&emsp;";
    html += (stRateRurById[d.id] ? formatPercent(stRateRurById[d.id]) : "");
    html += " Rural";
    html += " (&plusmn;";
    html += (stMoeRurById[d.id] ? formatPercent(stMoeRurById[d.id]) : "");
    html += ")";
    html += "</span>";
    html += "";
    html += "</div>";
                      
    $("#tooltip-container").html(html);
    $("#tooltip-container").show();
                          
    var coordinates = d3.mouse(this);
    d3.select("#tooltip-container")
        .style("top", (d3.event.pageY - 250) + "px")
        .style("left", (d3.event.pageX - 250) + "px");
    })
    .on("mouseout", function() {
        $console.log(d);
        $("#tooltip-container").hide();
    });

    d3.select("#metro-county").on("click", function() {
        var active   = g.active ? false : true, newOpacity = active ? 0.1 : 1;
        var newFill = active ? "#333" : "#ccc";
        d3.selectAll("path")
            .filter(function(d) { return metroById[d.id] == "metro" })
            .style("opacity", newOpacity); // New Position
        d3.select("#metro-county")
            .style("background-color", newFill);
        g.active = active;
    });

    d3.select("#rural-county").on("click", function() {
        var active   = g.active ? false : true, newOpacity = active ? 0.1 : 1;
        var newFill = active ? "#333" : "#ccc";
        d3.selectAll("path")
            .filter(function(d) { return metroById[d.id] == "rural" })
            .style("opacity", newOpacity); // New Position
        d3.select("#rural-county")
            .style("background-color", newFill);
        g.active = active;
    });

    d3.select("#micro-county").on("click", function() {
        var active   = g.active ? false : true, newOpacity = active ? 0.1 : 1;
        var newFill = active ? "#333" : "#ccc";
        d3.selectAll("path")
            .filter(function(d) { return metroById[d.id] == "micro" })
            .style("opacity", newOpacity); // New Position
        d3.select("#micro-county")
            .style("background-color", newFill);
        g.active = active;
    });


    g.append("path")
        .datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
        .attr("class", "states")
        .attr("d", path);

    g.append("path")
        .datum(topojson.mesh(us, us.objects.counties, function(a, b) { return a !== b; }))
        .attr("class", "counties")
        .attr("d", path);

    var colorLegend = d3.legendColor()
        .labelFormat(d3.format(".1f"))
        .scale(color)
        .shapePadding(5)
        .shapeWidth(50)
        .shapeHeight(10)
        .labels(["<5%", "5-<10%", "10-<15%", "15-<20%", "20-<25%", "25%+"])
        .labelOffset(10)
        .orient('horizontal')
        .title("Percent of Households with SNAP (2017-2021)");
        
    legend = svg.append("g")
        .attr("class", "legend")
        .attr("transform", "translate(550, 20)")
        .style("font-size","12px")
        .call(colorLegend);
};

function clicked(d) {
    if (active.node() === this) return reset();
    active.classed("active", false);
    active = d3.select(this).classed("active", true);

    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 = Math.max(1, Math.min(8, 0.9 / Math.max(dx / width, dy / height))),
        translate = [width / 2 - scale * x, height / 2 - scale * y];

    svg.transition()
        .duration(750)
        .call( zoom.transform, d3.zoomIdentity.translate(translate[0],translate[1]).scale(scale) );
}

function reset() {
    active.classed("active", false);
    active = d3.select(null);

    svg.transition()
        .duration(750)
        .call( zoom.transform, d3.zoomIdentity );
}

function zoomed() {
    g.style("stroke-width", 1.5 / d3.event.scale + "px");
    g.attr("transform", d3.event.transform);
}

function stopped() {
    if (d3.event.defaultPrevented) d3.event.stopPropagation();
}

function round(value, precision) {
    var multiplier = Math.pow(10, precision || 0);
    return Math.round(value * multiplier) / multiplier;
}
`

这是我得到的:Map of US counties showing SNAP participation over 2017-2021

亚利桑那州西北部的莫哈维县是空白的。它应该被阴影为中等绿色,因为它的 12.8% 值在图例中属于 10-<15% 类别。

2015 年,这里也提出了类似的问题:县级组跳县,但没有得到任何回应。

我在 2023 年 6 月在这里问了这个问题:为什么我的 D3 地图显示一个县的空白/死区,但正确显示所有其他 3,140 个县?

当时我没有提供足够的细节,所以我再试一次。我创建了一个基本版本的地图,并将文件发布在这里:https://github.com/arkut/snapmap.git

这包括 html、css、javascript 和数据文件。如果这些文件(snap-counties.html - 忽略此文件除外)包含在 Web 服务器上彼此相同的文件夹中,则地图应显示并如下所示: SNAP 地图链接

我希望有人能帮忙。如果我应该在这里提供其他详细信息,请告诉我。

我创建了一个最小的可重现地图示例。它显示了与完整版地图相同的问题(其中包括许多 Wordpress html/css/javascript 代码,使整个页面看起来像 Wordpress 页面)。

javascript html css d3.js 等值统计

评论

2赞 ewhalenkamp 9/24/2023
Mohave County 是第一个出现在 topojson 数据中的几何对象,这可能不是巧合。与 Mohave County 关联的 <path> 元素在 DOM 中不存在,第一个呈现到页面的是 Tangipahoa County,它在数据中直接列出。我似乎无法复制您的设置,但是如果您在“objects”属性的开头使用虚拟条目操作 json,它可能会为您指明正确的方向。
0赞 Arkut 9/25/2023
你真是太棒了!在 Mohave County 之前在 topojson 数据中创建虚拟条目会导致 Mohave County 在地图中正确渲染。谢谢!
0赞 Arkut 9/25/2023
固定!这是现在功能齐全的实时地图: frac.org/maps/snap-county-map/snap-counties.html

答: 暂无答案