D3.js复杂的世界地图画笔/缩略图

时间:2013-12-18 23:57:51

标签: javascript d3.js maps visualization brush

enter image description here

我一直在看简单的地图演示。

http://bl.ocks.org/mbostock/2206340

热衷于使用迷你地图或画笔创建更复杂的缩放/画笔方法,而不是依赖缩放事件。

http://bl.ocks.org/mbostock/4343214

如何修改示例代码 - 创建一个迷你地图,其中包含一个用作缩放/平移控制的小画笔区域?

<!DOCTYPE html>
<meta charset="utf-8">
<style>

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

#states {
  fill: #aaa;
}

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

</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>

var width = 960,
    height = 500;

var projection = d3.geo.albersUsa()
    .scale(1070)
    .translate([width / 2, height / 2]);

var path = d3.geo.path()
    .projection(projection);

var zoom = d3.behavior.zoom()
    .translate(projection.translate())
    .scale(projection.scale())
    .scaleExtent([height, 8 * height])
    .on("zoom", zoomed);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

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

g.append("rect")
    .attr("class", "background")
    .attr("width", width)
    .attr("height", height);

d3.json("/mbostock/raw/4090846/us.json", function(error, us) {
  g.append("g")
      .attr("id", "states")
    .selectAll("path")
      .data(topojson.feature(us, us.objects.states).features)
    .enter().append("path")
      .attr("d", path)
      .on("click", clicked);

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

function clicked(d) {
  var centroid = path.centroid(d),
      translate = projection.translate();

  projection.translate([
    translate[0] - centroid[0] + width / 2,
    translate[1] - centroid[1] + height / 2
  ]);

  zoom.translate(projection.translate());

  g.selectAll("path").transition()
      .duration(700)
      .attr("d", path);
}

function zoomed() {
  projection.translate(d3.event.translate).scale(d3.event.scale);
  g.selectAll("path").attr("d", path);
}

</script>

我当前的代码看起来像这样,但我不确定如何开始创建一个控制较大地图的小地图 - 从哪里开始?

var worldMapper = {
    initPie: function(id, color, w, h, r, irw, ir){
        var specs = {
            color: color,
            w: w,
            h: h,
            r: r,
            irw: irw,
            ir: ir
        }

        goPie.initChart(id, specs);
    },
    updatePie: function(id, localPieData){
        var newJson = new Array();                          
        $.each(localPieData, function(index, value) {
            var localData = {
                                title: value.label,
                                octetTotalCount: value.value
                        }                               
            newJson.push(localData);
        });

        goPie.updateCharts(id, newJson);            
    },  
    init: function(){
        var that = this;

        var width = 600,
            height = 400,
            rotate = 60,        // so that [-60, 0] becomes initial center of projection
            maxlat = 83;        // clip northern and southern poles (infinite in mercator)

        var projection = d3.geo.mercator()
            .rotate([rotate,0])
            .scale(1)           // we'll scale up to match viewport shortly.
            .translate([width/2, height/2]);

        // find the top left and bottom right of current projection
        function mercatorBounds(projection, maxlat) {
            var yaw = projection.rotate()[0],
                xymax = projection([-yaw+180-1e-6,-maxlat]),
                xymin = projection([-yaw-180+1e-6, maxlat]);

            return [xymin,xymax];
        }

        // set up the scale extent and initial scale for the projection
        var b = mercatorBounds(projection, maxlat),
            s = width/(b[1][0]-b[0][0]),
            scaleExtent = [s, 10*s];

        projection
            .scale(scaleExtent[0]);


        var zoom = d3.behavior.zoom()
            .scaleExtent(scaleExtent)
            .scale(projection.scale())
            .translate([0,0])               // not linked directly to projection
            .on("zoom", redraw);


        var path = d3.geo.path()
            .projection(projection);

        var svg = d3.selectAll('#map')
            .append('svg')
                .attr('width',width)
                .attr('height',height)
                .call(zoom);

        var worldMap = svg.append('g')
            .attr('class', 'worldmap');

        var coordinateItems = svg.append('g')
            .attr('class', 'coordinateItems');

        var dataCharts = [
            {
                "coordinates": [
                    10,
                    20
                ],
                "pieData": [
                    {"label":"Blade Workstation", "value":0.0001}, 
                    {"label":"GenX", "value":4}, 
                    {"label":"GenY", "value":0.0001},
                    {"label":"Laptop", "value":34},
                    {"label":"Physical Desktop", "value":13}
                ]
            },
            {
                "coordinates": [
                    -0.134153,
                    51.509757
                ],
                "pieData": [
                    {"label":"Blade Workstation", "value":33}, 
                    {"label":"GenX", "value":12}, 
                    {"label":"GenY", "value":23},
                    {"label":"Laptop", "value":23},
                    {"label":"Physical Desktop", "value":322}
                ]
            },
            {
                "coordinates": [
                    -0.134153,
                    21.509757
                ],
                "pieData": [
                    {"label":"Blade Workstation", "value":0.0001}, 
                    {"label":"GenX", "value":0.0001}, 
                    {"label":"GenY", "value":0.0001},
                    {"label":"Laptop", "value":54},
                    {"label":"Physical Desktop", "value":45}
                ]
            },
            {
                "coordinates": [
                    -82.134153,
                    29.509757
                ],
                "pieData": [
                    {"label":"Blade Workstation", "value":34}, 
                    {"label":"GenX", "value":34}, 
                    {"label":"GenY", "value":0.0001},
                    {"label":"Laptop", "value":0.0001},
                    {"label":"Physical Desktop", "value":57}
                ]
            },
            {
                "coordinates": [
                    -82.134153,
                    49.509757
                ],
                "pieData": [
                    {"label":"Blade Workstation", "value":0.0001}, 
                    {"label":"GenX", "value":0.0001}, 
                    {"label":"GenY", "value":789},
                    {"label":"Laptop", "value":34},
                    {"label":"Physical Desktop", "value":0.0001}
                ]
            },
            {
                "coordinates": [
                    -82.134153,
                    49.509757
                ],
                "pieData": [
                    {"label":"Blade Workstation", "value":23}, 
                    {"label":"GenX", "value":23}, 
                    {"label":"GenY", "value":43},
                    {"label":"Laptop", "value":0.0001},
                    {"label":"Physical Desktop", "value":0.0001}
                ]
            }               
        ];

        d3.json("readme.json", function(collection) {
            that.initPie("#easy-as-pie-chart", "Spectrum", 400, 400, 90, 70, 70);

          worldMap
                .selectAll("path")
            .data(collection.features)
                .enter().append("path")
                .attr("d", d3.geo.path().projection(d3.geo.mercator()));
            redraw();       // update path data 

        })

        /*
        d3.json("world-50m.json", function ready(error, world) {

            console.log("world", world);

            svg.selectAll('path')
                .data(topojson.feature(world, world.objects.countries).features)
              .enter().append('path')

            redraw();       // update path data

        });
        */

        // track last translation and scale event we processed
        var tlast = [0,0], 
            slast = null;


        function plotPoints(){
            var that = this;

            var points = coordinateItems.selectAll("g")
            var projectedCoordinates = [];

            points.remove();
            for (var i=0; i<dataCharts.length; i++) {
                projectedCoordinates[i] = projection(dataCharts[i]["coordinates"]);

                coordinateItems.append("g")
                        .attr("transform", "translate("+projectedCoordinates[i][0]+", "+projectedCoordinates[i][1]+")")
                        .attr("id", "minipie"+i)
                        .on("click", function() {
                            var id = $(this).attr("id");

                            idNumber = parseInt(id.replace(/[^\d.]/g, ''));
                            var localPieData = dataCharts[idNumber]["pieData"];

                            $("#easy-as-pie-chart").empty();
                            worldMapper.initPie("#easy-as-pie-chart", "Spectrum", 400, 400, 90, 70, 70);
                            worldMapper.updatePie("#easy-as-pie-chart", localPieData);
                        });

                worldMapper.initPie("#minipie"+i, "Spectrum", 25, 25, 10, 5, 0);
                worldMapper.updatePie("#minipie"+i, dataCharts[i]["pieData"]);
            }

        }   


        function redraw() {
            if (d3.event) { 
                var scale = d3.event.scale,
                    t = d3.event.translate;                

                // if scaling changes, ignore translation (otherwise touch zooms are weird)
                if (scale != slast) {
                    projection.scale(scale);
                } else {
                    var dx = t[0]-tlast[0],
                        dy = t[1]-tlast[1],
                        yaw = projection.rotate()[0],
                        tp = projection.translate();

                    // use x translation to rotate based on current scale
                    projection.rotate([yaw+360.*dx/width*scaleExtent[0]/scale, 0, 0]);
                    // use y translation to translate projection, clamped by min/max
                    var b = mercatorBounds(projection, maxlat);
                    if (b[0][1] + dy > 0) dy = -b[0][1];
                    else if (b[1][1] + dy < height) dy = height-b[1][1];
                    projection.translate([tp[0],tp[1]+dy]);
                }
                // save last values.  resetting zoom.translate() and scale() would
                // seem equivalent but doesn't seem to work reliably?
                slast = scale;
                tlast = t;
            }

            worldMap.selectAll('path')       // re-project path data
                .attr('d', path);

            plotPoints();       
        }   
    }
}




$( document ).ready(function() {
    worldMapper.init();
});

我设法在迷你地图上创建了一个画笔。但是将它连接起来控制较大地图的缩放/平移是很棘手的。

我试图修复画笔的高度/宽度以确保音阶保持一致......

这是我当前的代码

var worldMapper = {
    initPie: function(id, color, w, h, r, irw, ir){
        var specs = {
            color: color,
            w: w,
            h: h,
            r: r,
            irw: irw,
            ir: ir
        }

        goPie.initChart(id, specs);
    },
    updatePie: function(id, localPieData){
        var newJson = new Array();                          
        $.each(localPieData, function(index, value) {
            var localData = {
                                title: value.label,
                                octetTotalCount: value.value
                        }                               
            newJson.push(localData);
        });

        goPie.updateCharts(id, newJson);            
    },
    createMiniWorldMap: function(collection){
        var that = this;

        var width = 120,
            height = 80,
            rotate = 60,        // so that [-60, 0] becomes initial center of projection
            maxlat = 83;        // clip northern and southern poles (infinite in mercator)

        var projection = d3.geo.mercator()
            .rotate([rotate,0])
            .scale(1)           // we'll scale up to match viewport shortly.
            .translate([width/2, height/2]);

        // find the top left and bottom right of current projection
        function mercatorBounds(projection, maxlat) {
            var yaw = projection.rotate()[0],
                xymax = projection([-yaw+180-1e-6,-maxlat]),
                xymin = projection([-yaw-180+1e-6, maxlat]);

            return [xymin,xymax];
        }

        // set up the scale extent and initial scale for the projection
        var b = mercatorBounds(projection, maxlat),
            s = width/(b[1][0]-b[0][0]),
            scaleExtent = [s, 10*s];

        projection
            .scale(scaleExtent[0]);

        var path = d3.geo.path()
            .projection(projection);

        var svg = d3.selectAll('#minimap')
            .append('svg')
                .attr('width',width)
                .attr('height',height)
                .attr('class', 'miniworldmap');

        var worldMap = svg.append('g')
            .attr('class', 'worldmap');

          worldMap
                .selectAll("path")
            .data(collection.features)
                .enter().append("path")
                .attr("d", d3.geo.path().projection(d3.geo.mercator()));

        worldMap.selectAll('path')       // re-project path data
            .attr('d', path);           





this.brush = d3.svg.brush()
    .extent([0, 30])
    .x(d3.scale.identity().domain([0, width]))
    .y(d3.scale.identity().domain([0, height]))
    .on("brush", brushed);


this.gBrush = svg.append("g")
    .attr("class", "brush")
    .call(this.brush);


/*
this.gBrush.selectAll("rect")
        .attr("height", 30)
        .attr("width", 30);
        */


            function brushed() {

                        var extent = d3.event.target.extent();

                        var extent0 = extent;
                        var extent1;

                              var smallMapWidth = 120;
                              var smallMapHeight = 80;

                              var gWidth = 30;
                              var gHeight = 30;

                          // if dragging, preserve the width of the extent
                          if (d3.event.mode === "move") {           
                                var xLimit = extent0[0][0];
                                var YLimit = extent0[0][1];

                                if(xLimit+gWidth+1 > smallMapWidth){
                                    xLimit = 91;
                                }

                                if(YLimit+gHeight+1 > smallMapHeight){
                                    YLimit = 51;
                                }               
                                extent1 = [xLimit, YLimit];
                          }

                          // otherwise, if resizing, round both dates
                          else {
                            extent1 = extent0;
                          }



                            worldMap.classed("selected", function(d) {




                                console.log("extent[0]", extent[0]);
                                console.log("extent[1]", extent[1]);

                                var boxWidth = extent[1][0] - extent[0][0];
                                var boxHeight = extent[1][1] - extent[0][1];

                                console.log("boxWidth", boxWidth);
                                console.log("boxHeight", boxHeight);

                                var smallMapWidth = 120;
                                var smallMapHeight = 80;

                                var bigMapWidth = 600;
                                var bigMapHeight = 400;



                                var xRatio = extent[0][0]/smallMapWidth;
                                var yRatio = extent[1][1]/smallMapHeight;


                                var newX =  bigMapWidth*xRatio;
                                var newY =  bigMapHeight*yRatio;

                                worldMapper.newT = new Array(newX, newY);
                                worldMapper.newScale = 100;

                                worldMapper.redraw()

                            })



                        /*
                        worldMapper.gBrush.selectAll("rect")
                            .attr("height", 30)
                            .attr("width", 30);*/

                            //console.log(worldMapper.brush.extent(extent1));

                         //d3.select(this).call(worldMapper.brush.extent(extent1));


                          /*

                            var extent = d3.event.target.extent();
                            worldMap.classed("selected", function(d) {


                                worldMapper.redraw()

                                console.log("extent[0]", extent[0]);
                                console.log("extent[1]", extent[1]);

                                var boxWidth = extent[1][0] - extent[0][0];
                                var boxHeight = extent[1][1] - extent[0][1];

                                console.log("boxWidth", boxWidth);
                                console.log("boxHeight", boxHeight);

                                var smallMapWidth = 120;
                                var smallMapHeight = 80;

                                var bigMapWidth = 600;
                                var bigMapHeight = 400;



                                var xRatio = extent[0][0]/smallMapWidth;
                                var yRatio = extent[1][1]/smallMapHeight;


                                var newX =  bigMapWidth*xRatio;
                                var newY =  bigMapHeight*yRatio;

                                worldMapper.newT = new Array(newX, newY);
                                console.log("worldMapper.newT", worldMapper.newT);

                            })

                                */

            }


    },
    majorWorld: {},
    tlast: [0,0],
    newScale: 200,
    redraw: function(){
        var that = this;

        if (d3.event) { 

            var scale = d3.event.scale;
            if(!scale){
                scale = worldMapper.newScale;
            }

            var t = d3.event.translate;                
            if(!t){
                t = worldMapper.newT;
            }

            // if scaling changes, ignore translation (otherwise touch zooms are weird)
            if (scale != worldMapper.slast) {
                worldMapper.majorWorld.projection.scale(scale);
            } else {
                var dx = t[0]-worldMapper.tlast[0],
                    dy = t[1]-worldMapper.tlast[1],
                    yaw = worldMapper.majorWorld.projection.rotate()[0],
                    tp = worldMapper.majorWorld.projection.translate();

                // use x translation to rotate based on current scale
                worldMapper.majorWorld.projection.rotate([yaw+360.*dx/worldMapper.majorWorld.width*worldMapper.majorWorld.scaleExtent[0]/scale, 0, 0]);
                // use y translation to translate projection, clamped by min/max
                var b = worldMapper.mercatorBounds(worldMapper.majorWorld.projection, worldMapper.majorWorld.maxlat);
                if (b[0][1] + dy > 0) dy = -b[0][1];
                else if (b[1][1] + dy < worldMapper.majorWorld.height) dy = worldMapper.majorWorld.height-b[1][1];
                worldMapper.majorWorld.projection.translate([tp[0],tp[1]+dy]);
            }
            // save last values.  resetting zoom.translate() and scale() would
            // seem equivalent but doesn't seem to work reliably?
            worldMapper.slast = scale;
            if(t!=undefined){
                worldMapper.tlast = t;
            }
        }

        worldMapper.MajorMap.selectAll('path')       // re-project path data
            .attr('d', worldMapper.path);

        worldMapper.plotPoints();           
    },
    plotPoints: function(){
        var that = this;

        var points = that.coordinateItems.selectAll("g")
        var projectedCoordinates = [];

        points.remove();
        for (var i=0; i < that.dataCharts.length; i++) {
            projectedCoordinates[i] = that.majorWorld.projection(that.dataCharts[i]["coordinates"]);

            that.coordinateItems.append("g")
                    .attr("transform", "translate("+projectedCoordinates[i][0]+", "+projectedCoordinates[i][1]+")")
                    .attr("id", "minipie"+i)
                    .on("click", function() {
                        var id = $(this).attr("id");

                        idNumber = parseInt(id.replace(/[^\d.]/g, ''));
                        var localPieData = that.dataCharts[idNumber]["pieData"];

                        $("#detailPie").empty();
                        worldMapper.initPie("#detailPie", "Spectrum", 400, 400, 90, 70, 70);
                        worldMapper.updatePie("#detailPie", localPieData);
                    });

            worldMapper.initPie("#minipie"+i, "Spectrum", 25, 25, 10, 5, 0);
            worldMapper.updatePie("#minipie"+i, that.dataCharts[i]["pieData"]);
        }

    },
    mercatorBounds: function(projection, maxlat) {
        // find the top left and bottom right of current projection

        var yaw = projection.rotate()[0],
            xymax = projection([-yaw+180-1e-6,-maxlat]),
            xymin = projection([-yaw-180+1e-6, maxlat]);

        return [xymin,xymax];
    },  
    createWorldMap: function(collection){
        var that = this;

        var width = 600,
            height = 400,
            rotate = 20,        // so that [-60, 0] becomes initial center of projection
            maxlat = 80;        // clip northern and southern poles (infinite in mercator)

        var projection = d3.geo.mercator()
            .rotate([rotate,0])
            .scale(1)           // we'll scale up to match viewport shortly.
            .translate([width/2, height/2]);


        // set up the scale extent and initial scale for the projection
        var b = this.mercatorBounds(projection, maxlat),
            s = width/(b[1][0]-b[0][0]),
            scaleExtent = [s, 10*s];

        projection
            .scale(scaleExtent[0]);

/*      
        var zoom = d3.behavior.zoom()
            .scaleExtent(scaleExtent)
            .scale(projection.scale())
            .translate([0,0])               // not linked directly to projection
            .on("zoom", that.redraw);   
*/      

        that.path = d3.geo.path()
            .projection(projection);

        var svg = d3.selectAll('#majormap')
            .append('svg')
                .attr('width',width)
                .attr('height',height)
                .attr('class', 'majorworldmap')
                //.call(zoom);

        this.MajorMap = svg.append('g')
            .attr('class', 'worldmap');

        that.coordinateItems = svg.append('g')
            .attr('class', 'coordinateItems');

        this.majorWorld["projection"] = projection;
        this.majorWorld["width"] = width;
        this.majorWorld["height"] = height;     
        this.majorWorld["rotate"] = rotate;
        this.majorWorld["scaleExtent"] = scaleExtent;
        this.majorWorld["maxlat"] = maxlat;

        //d3.json("readme.json", function(collection) {

        that.initPie("#detailPie", "Spectrum", 400, 400, 90, 70, 70);

        that.MajorMap
            .selectAll("path")
        .data(collection.features)
            .enter().append("path")
            .attr("d", d3.geo.path().projection(d3.geo.mercator()));
        that.redraw();       // update path data 

        //})

        /*
        d3.json("world-50m.json", function ready(error, world) {

            console.log("world", world);

            svg.selectAll('path')
                .data(topojson.feature(world, world.objects.countries).features)
              .enter().append('path')

            redraw();       // update path data

        });
        */

        // track last translation and scale event we processed
        this.tlast = [0,0]; 
        this.slast = null;


        that.plotPoints();  

        /*
        function redraw() {
            if (d3.event) { 
                var scale = d3.event.scale,
                    t = d3.event.translate;                

                // if scaling changes, ignore translation (otherwise touch zooms are weird)
                if (scale != slast) {
                    projection.scale(scale);
                } else {
                    var dx = t[0]-tlast[0],
                        dy = t[1]-tlast[1],
                        yaw = projection.rotate()[0],
                        tp = projection.translate();

                    // use x translation to rotate based on current scale
                    projection.rotate([yaw+360.*dx/width*scaleExtent[0]/scale, 0, 0]);
                    // use y translation to translate projection, clamped by min/max
                    var b = mercatorBounds(projection, maxlat);
                    if (b[0][1] + dy > 0) dy = -b[0][1];
                    else if (b[1][1] + dy < height) dy = height-b[1][1];
                    projection.translate([tp[0],tp[1]+dy]);
                }
                // save last values.  resetting zoom.translate() and scale() would
                // seem equivalent but doesn't seem to work reliably?
                slast = scale;
                tlast = t;
            }

            worldMap.selectAll('path')       // re-project path data
                .attr('d', path);

            plotPoints();       
        }
        */  
    },
    init: function(){

        this.dataCharts = [
            {
                "coordinates": [
                    10,
                    20
                ],
                "pieData": [
                    {"label":"Blade Workstation", "value":0.0001}, 
                    {"label":"GenX", "value":4}, 
                    {"label":"GenY", "value":0.0001},
                    {"label":"Laptop", "value":34},
                    {"label":"Physical Desktop", "value":13}
                ]
            },
            {
                "coordinates": [
                    -0.134153,
                    51.509757
                ],
                "pieData": [
                    {"label":"Blade Workstation", "value":33}, 
                    {"label":"GenX", "value":12}, 
                    {"label":"GenY", "value":23},
                    {"label":"Laptop", "value":23},
                    {"label":"Physical Desktop", "value":322}
                ]
            },
            {
                "coordinates": [
                    -0.134153,
                    21.509757
                ],
                "pieData": [
                    {"label":"Blade Workstation", "value":0.0001}, 
                    {"label":"GenX", "value":0.0001}, 
                    {"label":"GenY", "value":0.0001},
                    {"label":"Laptop", "value":54},
                    {"label":"Physical Desktop", "value":45}
                ]
            },
            {
                "coordinates": [
                    -82.134153,
                    29.509757
                ],
                "pieData": [
                    {"label":"Blade Workstation", "value":34}, 
                    {"label":"GenX", "value":34}, 
                    {"label":"GenY", "value":0.0001},
                    {"label":"Laptop", "value":0.0001},
                    {"label":"Physical Desktop", "value":57}
                ]
            },
            {
                "coordinates": [
                    -82.134153,
                    49.509757
                ],
                "pieData": [
                    {"label":"Blade Workstation", "value":0.0001}, 
                    {"label":"GenX", "value":0.0001}, 
                    {"label":"GenY", "value":789},
                    {"label":"Laptop", "value":34},
                    {"label":"Physical Desktop", "value":0.0001}
                ]
            },
            {
                "coordinates": [
                    -82.134153,
                    49.509757
                ],
                "pieData": [
                    {"label":"Blade Workstation", "value":23}, 
                    {"label":"GenX", "value":23}, 
                    {"label":"GenY", "value":43},
                    {"label":"Laptop", "value":0.0001},
                    {"label":"Physical Desktop", "value":0.0001}
                ]
            }               
        ];  

        var that = this;


        d3.json("readme.json", function(collection) {
            that.createWorldMap(collection);
            that.createMiniWorldMap(collection);            
        })      


    }
}




$( document ).ready(function() {
    worldMapper.init();
});

0 个答案:

没有答案