为什么我的 d3-voronoi 实现会创建一个奇怪的图形?

问题描述

我在围绕红点创建 voronoi 多边形时遇到问题。这些点是在每个状态的边界内随机生成的。我正在尝试使用 d3-voronoi 库来这样做,它会在下面的链接生成图片。有什么建议么?某些状态是多重多边形是否存在问题?

<!DOCTYPE html>
<html>
<head>
    <Meta charset="UTF-8">
    <title>D3 Example</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.3.0/d3.min.js"></script>
    <script src="https://d3js.org/d3-polygon.v2.min.js"></script>
    <script src="https://unpkg.com/[email protected]"></script>
    <script src="https://unpkg.com/d3-delaunay@5"></script>
<!--    <script src="https://unpkg.com/[email protected]/simplex-noise.js"></script>-->
<!--    <script src="https://unpkg.com/[email protected]/delaunator.min.js"></script>-->


    <!--<script src="https://d3js.org/d3-voronoi.v1.min.js"></script>-->
    <!--<script src="https://d3js.org/d3-array.v2.min.js"></script>
    <script src="https://d3js.org/d3-geo.v2.min.js"></script>-->
    <script src="https://d3js.org/topojson.v1.min.js"></script>
    <style>
        body {
            font-family: "Helvetica Neue","Helvetica","Arial",sans-serif;
            font-size: 15pt;
        }
        h1 {
            text-align: center;
            font-weight: 500;
        }
        svg {
            display: block;
            margin: auto;
        }

        /* Tooltip */
        div.tooltip {
            position: absolute;
            visibility: hidden;
            font-size: 60%;
            color: #333333;
            background-color: #e6e6e6;
            opacity: .95;
            padding: 5px;
        }

        button {
            padding: 10px 20px;
        }

        .background {
            fill: none;
            pointer-events: all;
        }

        #states {
            fill: #aaa;
        }

        #states .active {
            display:none;
        }

        #state-borders {
            fill: none;
            stroke: #fff;
            stroke-width: 1.5px;
            stroke-linejoin: round;
            stroke-linecap: round;
            pointer-events: none;
        }

        .county-boundary {
            fill: #aaa;
            stroke: #fff;
            stroke-width: .5px;
        }

        .county-boundary:hover,.state:hover {
            fill: orange;
        }
    </style>
</head>
<body style="background-color:dimgray;">
<h1>Gerry Data</h1>

<div id="county"></div>
<div id="state"></div>
<div id="us"></div>
<div class="tooltip"></div>

<script>


    var data_map = d3.map();
    var width = 1920,height = 1200,centered;

    var path_gen;
    var aa = [];
    var count = 0;
    //var pop_rate;
    //var svg;
    //var imageData = polyContext.getimageData(0,width,height);

    var state_codes = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51];

    var state_names =
        {
            "Alabama" : 9,"Alaska" : 3,"Arizona" : 11,"Arkansas" : 6,"California" : 55,"Colorado" : 9,"Connecticut" : 7,"Delaware" : 3,"district of Columbia" : 3,"Florida" : 29,"Georgia" : 16,"Hawaii" : 4,"Idaho" : 4,"Illinois" : 20,"Indiana" : 11,"Iowa" : 6,"Kansas" : 6,"Kentucky" : 8,"Louisiana" : 8,"Maine" : 4,"Maryland" : 10,"Massachusetts" : 11,"Michigan" : 16,"Minnesota" : 10,"Mississippi" : 6,"Missouri" : 10,"Montana" : 3,"Nebraska" : 5,"Nevada" : 6,"New Hampshire" : 4,"New Jersey" : 14,"New Mexico" : 5,"New York" : 29,"north Carolina" : 15,"north Dakota" : 3,"Ohio" : 18,"Oklahoma" : 7,"Oregon" : 7,"Pennsylvania" : 20,"Rhode Island" : 4,"South Carolina" : 9,"South Dakota" : 3,"Tennessee" : 11,"Texas" : 38,"Utah" : 6,"Vermont" : 3,"Virginia" : 13,"Washington" : 12,"West Virginia" : 5,"Wisconsin" : 10,"Wyoming" : 3,};

    d3.queue()
        .defer(d3.json,"data/counties_20m.json")
        //.defer(d3.json,"https://unpkg.com/us-atlas@1/us/10m.json")
        .defer(d3.json,"data/states_20m.json")
        .defer(d3.json,"data/us_20m.json")
        .defer(d3.csv,"data/pop.csv",function(d){
            // Convert to number
            d['POpestIMATE2019'] = +d['POpestIMATE2019'];

            // Use the county's FIPS countyode to access that county's data
            return data_map.set(d['FIPStxt'],d);
        })
        .await(ready);


    function ready(error,cmap,smap,umap,data_csv) {
            // How does the data look like?
            //console.log(data_csv);

            // Unpack the GeoJSON features
            var counties = cmap['features'];
            var states = smap['features'];
            var us = umap['features'];

            //var states = smap['features'];
            //var country;


            //----------------------------------------
            // SVG setup
            // var width = 1920,//     height = 1200;

            svg = d3.select('#county').append('svg')
                .attr('width',width)
                .attr('height',height);
                // .call(d3.zoom().on("zoom",function () {
                // svg.attr("transform",d3.event.transform)
                // }));


            // Append empty placeholder g element to the SVG
            // g will contain geometry elements
            let g = d3.select('#state').append('svg')
                .attr('width',height);

            let h = d3.select('#us').append('svg')
                .attr('width',height);

            var hi = svg.append('svg').selectAll('path');



            //----------------------------------------
            // Geography setup
            var proj = d3.geoAlbersUsa()
                .scale(2300)
                .translate([width/2,height/2]);

            path_gen = d3.geoPath(proj);

            var voronoi = d3.voronoi()
                .extent([[0,0],[width,height]]);
            //----------------------------------------
            // Scale setup
            var colors = ['#f7fbff','#deebf7','#c6dbef','#9ecae1','#6baed6','#4292c6','#2171b5','#084594'];
            var all_values = data_map.values().map( function(d){
                return d['POpestIMATE2019'];
            });

            // Quantile scale
            var color_scale = d3.scaleQuantile()
                .domain(all_values)
                .range(colors);

            // Linear scale
            // var max = d3.max(all_values),//     min = d3.min(all_values);
            // var color_scale = d3.scaleLinear()
            //                     .domain([min,max])
            //                     .range([colors[0],colors[colors.length-1]]);

            // Power scale
            // var max = d3.max(all_values),//     min = d3.min(all_values);
            // var color_scale = d3.scalePow()
            //                     .domain([min,colors[colors.length-1]])
            //                     .exponent(3);

            // Check out the color scale
            //console.log( color_scale(21) );

            //----------------------------------------
            // The map,finally!
            svg.selectAll('path')
                .data(counties)
                //.data(states)
                .enter()
                .append('path')
                .attr('d',path_gen)
                // use this for layers and chunks
                //.attr( "visibility","hidden")
                .style('fill',function(d) {
                    if (d['properties']['STATE'][0] == 0) {
                        fips_code = d['properties']['STATE'][1] + d['properties']['COUNTY'];
                    } else {
                        fips_code = d['properties']['STATE'] + d['properties']['COUNTY'];
                    }

                    //console.log(fips_code);

                    // Color only if the data exists for the FIPS code
                    if (data_map.has(fips_code)) {
                        // Get the entire row of poverty data for each FIPS code
                        pop_data = data_map.get(fips_code);
                        //console.log(pop_data);
                        // Get the specific feature
                        data = pop_data['POpestIMATE2019'];

                        return color_scale(data);
                    };


                })
                .style('opacity',1)
                .style('stroke','black')
                .style('stroke-width',"1px")
                .style('stroke-opacity',1)
                .on('mouSEOver',function(d) {
                    // Make the county color darker
                    d3.select(this)
                        .style('opacity',0.5);

                    var area = d['properties']['CENSUSAREA'];

                    // Unload data
                    if (d['properties']['STATE'][0] == 0) {
                        fips_code = d['properties']['STATE'][1] + d['properties']['COUNTY'];
                    } else {
                        fips_code = d['properties']['STATE'] + d['properties']['COUNTY'];
                    }
                    if (data_map.has(fips_code)) {
                        pop_data = data_map.get(fips_code);
                        name = pop_data['CTYNAME'];
                        state = pop_data['STNAME'];
                        pop_rate = pop_data['POpestIMATE2019'];

                    }

                    // var p = d3.polygonContains(d['geometry']['coordinates'][0],[ -98.5795,39.8283 ]);
                    // console.log(p);

                    // For some reason,the area cannot be found with separated lands
                    // if-else to deal with islands since there is an extra array parameter for them
                    // d3.polygonArea(d['geometry']['coordinates'][0]) -> d3.polygonArea(d['geometry']['coordinates'][0][0])
                    var ose = 0;

                    if (d['geometry']['type'] == "Multipolygon") {
                        for (k = 0; k < d['geometry']['coordinates'].length; k++) {
                            ose += d3.polygonArea(d['geometry']['coordinates'][k][0]);
                        }
                    }
                    else {
                        ose = d3.polygonArea(d['geometry']['coordinates'][0]);
                    }

                    //console.log(d3.polygonHull(d['geometry']['coordinates'][0]));
                    var str = "2";
                    // Show the tooltip
                    d3.select('.tooltip')
                        .style('visibility','visible')
                        .style('top',d3.event.pageY+10 + 'px')
                        .style('left',d3.event.pageX+10 + 'px')
                        .html('<strong>' + name + ',' + state + '</strong><br />Population (2019): ' +
                            pop_rate + ' people<br />Area: ' + area + ' mi' + str.sup() +
                            '<br />Population Denstiy: ' + (pop_rate/area).toFixed(3) + ' p/mi' + str.sup()
                            + '<br />' + ose);
                })
                .on('mouSEOut',function(d) {
                    // Make the county usual opacity again
                    d3.select(this)
                        .style('opacity',1);

                    // Hide the tooltip
                    d3.select('.tooltip')
                        .style('visibility','hidden');
                })
                .each(function (d) {
                    //39.8283° N,98.5795° W
                    // Y Axis: 18 N to 72 N
                    // X Axis: 173 E (Western) to 67 W (Eastern)
                    var tmp = [];
                    var contain = false;

                    if (d['properties']['STATE'][0] == 0) {
                        fips_code = d['properties']['STATE'][1] + d['properties']['COUNTY'];
                    } else {
                        fips_code = d['properties']['STATE'] + d['properties']['COUNTY'];
                    }

                    if (data_map.has(fips_code)) {
                        pop_data = data_map.get(fips_code);
                        state = pop_data['STNAME'];
                        pop_rate = pop_data['POpestIMATE2019'];
                    }

                    var Votes = state_names[state];
                    //console.log(Votes);

                    for (i = 0; i < 0; i++) {
                        tmp = [-1 * (Math.random() * (173 - 66) + 66),Math.random() * (72 - 18) + 18];

                        if (d['geometry']['type'] == "Multipolygon") {
                             for (k = 0; k < d['geometry']['coordinates'].length; k++) {
                                contain = d3.polygonContains(d['geometry']['coordinates'][k][0],tmp);
                                // console.log(d['properties']['NAME']);
                                // console.log(d['geometry']['coordinates'].length);

                                 if (contain) {
                                     aa.push(tmp);
                                     //console.log(aa);
                                     break;
                                     // console.log(d['properties']['NAME']);
                                 }
                                 // else {
                                 //     //i -= 1;
                                 //     console.log(i);
                                 // }
                             }
                        }
                        else {
                            contain = d3.polygonContains(d['geometry']['coordinates'][0],tmp);

                            if (contain) {
                                aa.push(tmp);
                            }

                            // else {
                            //     //i -= 1;
                            //     console.log(i);
                            // }
                        }



                    }

                    svg.selectAll("circle")
                        .data(aa).enter()
                        .append("circle")
                        .attr("cx",function (d) {
                            count += 1;
                            // console.log(count);
                            if (proj(d) == null) {
                                //console.log(proj(d));
                                return;
                            }

                            // console.log(proj(d));
                            return proj(d)[0];
                        })
                        .attr("cy",function (d) {
                            if (proj(d) == null) {
                                //console.log(proj(d));
                                return;
                            }
                            return proj(d)[1];
                        })
                        .attr("r","10px")
                        .attr("fill","red");

                });
                //.on("click",transform);
                // .call(function (d) {
                //
                //
                //
                //         //aa = [-98.5795,39.8283];
                //         if (d3.polygonContains(d['geometry']['coordinates'],tmp) == true) {
                //             aa.push(tmp);
                //             d3.geo
                //         }
                //         else {
                //             continue;
                //         }
                //
                //         // aa.push([-1 * (Math.random() * (125.5795 - 75.5795) + 75.5795),Math.random() * (48.8283 - 30.8283) + 30.8283]);
                //         // console.log(aa);
                //     }
                //
                //
                // });

        g.selectAll("path")
            .data(states) // Bind TopoJSON data elements
            // pass through what objects you want to use -- in this case we are doing county lines
            .enter().append("path")
            .attr("d",path_gen)
            //.attr("visibility","hidden")
            .style("fill","none")
            .style("stroke","white")
            .style("stroke-width","1px")
            .each(function (d) {
                var tmp;
                var contain = false;

                if (d['properties']['STATE'][0] == 0) {
                    fips_code = d['properties']['STATE'][1] + d['properties']['COUNTY'];
                } else {
                    fips_code = d['properties']['STATE'] + d['properties']['COUNTY'];
                }

                if (data_map.has(fips_code)) {
                    pop_data = data_map.get(fips_code);
                    state = pop_data['STNAME'];
                    pop_rate = pop_data['POpestIMATE2019'];
                }

                var Votes = state_names[d['properties']['NAME']];
                //console.log(d['properties']['NAME'] + ': ' + Votes);
                var vc = 0;
                var bounds = d3.geoBounds(d);
                //console.log(bounds);

                while (vc < Votes) {
                    if (d['properties']['NAME'] == 'Alaska') {
                        tmp = [-1 * (Math.random() * (bounds[1][0] - bounds[0][0]) + bounds[0][0]),Math.random() * (bounds[1][1] - bounds[0][1]) + bounds[0][1]];
                    }
                    else {
                        tmp = [Math.random() * (bounds[1][0] - bounds[0][0]) + bounds[0][0],Math.random() * (bounds[1][1] - bounds[0][1]) + bounds[0][1]];
                    }


                    //console.log(tmp);
                    if (d['geometry']['type'] == "Multipolygon") {
                        for (k = 0; k < d['geometry']['coordinates'].length; k++) {
                            contain = d3.polygonContains(d['geometry']['coordinates'][k][0],tmp);
                            // console.log(d['properties']['NAME']);
                            // console.log(d['geometry']['coordinates'].length);

                            if (contain) {
                                aa.push(tmp);
                                vc += 1;
                                console.log(d['properties']['NAME'] + ' 1: ' + vc);
                                //console.log(aa);
                                break;
                                // console.log(d['properties']['NAME']);
                            }
                            // else {
                            //     //i -= 1;
                            //     console.log(i);
                            // }
                        }
                    }
                    else {
                        contain = d3.polygonContains(d['geometry']['coordinates'][0],tmp);

                        if (contain) {
                            aa.push(tmp);
                            vc += 1;
                            console.log(d['properties']['NAME'] + ' 2: ' + vc);
                        }

                        // else {
                        //     //i -= 1;
                        //     console.log(i);
                        // }
                    }

                    console.log(d['properties']['NAME'] + ' : ' +vc);

                }



                //console.log(voronoi(aa).polygons());
                // Adding data to represent the Voronoi
                // and displaying it


                g.selectAll("circle")
                    //.data([[-98.5795,39.8283],[-96.5795,41.8283]]).enter()
                    .data(aa).enter()
                    .append("circle")
                    .attr("cx",function (d) {
                        count += 1;
                        // console.log(count);
                        if (proj(d) == null) {
                            //console.log(proj(d));
                            return;
                        }

                        // console.log(proj(d));
                        return proj(d)[0];
                    })
                    .attr("cy",function (d) {
                        if (proj(d) == null) {
                            //console.log(proj(d));
                            return;
                        }
                        return proj(d)[1];
                    })
                    .attr("r","3px")
                    .attr("fill","red");

                    g.append('g').selectAll('path')
                        .data(voronoi.polygons(aa))
                        .enter()
                        .append("path")
                        .attr("stroke","black")
                        .attr("stroke-width","1px")
                        .attr("fill","none")
                        .attr("d",(d) => {
                            return d ? ("M" + d.join("L") + "Z") : null;
                        });

            });

        // voronoiDiagram = d3.voronoi();
        // voronoiDiagram(aa);

        // Using the d3.voronoi() function
        // to create a Voronoi diagram




        //     .call(function (d) {
        //         //39.8283° N,98.5795° W
        //         d3.select(this);
        //
        //         var p = d3.polygonContains(d['objects']['counties']['geometries'],39.8283 ]);
        //         console.log(p);
        // });

        h.selectAll("path")
            .data(us) // Bind TopoJSON data elements
            // pass through what objects you want to use -- in this case we are doing county lines
            .enter().append("path")
            .attr("d",path_gen)
            .style("fill","1px");

        };



    // function clicked(d) {
    //     var x = 0,//         y = 0;
    //
    //     // If the click was on the centered state or the background,re-center.
    //     // Otherwise,center the clicked-on state.
    //     if (!d || centered === d) {
    //         centered = null;
    //     } else {
    //         var centroid = path_gen.centroid(d);
    //         x = width / 2 - centroid[0];
    //         y = height / 2 - centroid[1];
    //         centered = d;
    //     }
    //
    //     // Transition to the new transform.
    //     svg.transition()
    //         .duration(750)
    //         .attr("transform","translate(" + x + "," + y + ")");
    // }
</script>
</body>
</html>

Picture of Incorrect Graphic

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)