javascriptd3.jssvgrotationdatamaps

make d3.js datamap rotate by dragging with cursor


i made this map using datamaps by @markmarkoh

i love how it turned out but i wish i could get it to rotate when you drag it with the cursor, so you can see all of the continents. like the examples here and here.

here's a snippet of my code:

//basic map config with custom fills, mercator projection
               
               var series = [
        ["USA",36.2],["GBR",7.4],["CAN",6.2],["DEU",5.7],["FRA", 4.1],["ESP",4.1],["ITA",3.3],["MEX",3.0],["AUS",2.5],["NLD",2.4],
        ["IND",2.1],["BRA",2.0],["GRC",1.4],["AUT",1.2],["ROU",1.2],["SRB",1.0],["COL",0.8],["POL",0.8],["ZAF",0.7],["SWE",0.7],
        ["DNK",0.6],["VEN",0.6],["JPN",0.6],["KOR",0.6],["BEL",0.5],["RUS",0.5],["PRT",0.5]
                            ];
                       
         var dataset = {};
            // We need to colorize every country based on "percent"
            // colors should be uniq for every value.
            // For this purpose we create palette(using min/max series-value)
            var onlyValues = series.map(function(obj){ return obj[1]; });
            var minValue = Math.min.apply(null, onlyValues),
                    maxValue = Math.max.apply(null, onlyValues);
            // create color palette function
            // color can be whatever you wish
            var paletteScale = d3.scale.linear()
                    .domain([minValue,maxValue])
                    .range(["rgb(0,0,0)","rgb(219,219,219)"]);  // color
            // fill dataset in appropriate format
            series.forEach(function(item){ //
                // item example value ["USA", 36.2]
                var iso = item[0],
                        value = item[1];
                dataset[iso] = { percent: value, fillColor: paletteScale(value) };
            });
              var map = new Datamap({
                scope: 'world',
                element: document.getElementById('world'),
                projection: 'orthographic',
                projectionConfig: {
                  rotation: [90,-30]
                },
                 fills: {defaultFill: 'rgba(30,30,30,0.1)'},
                data: dataset,
                geographyConfig: {
                    borderColor: 'rgba(222,222,222,0.2)',
                    highlightBorderWidth: 1,
                    // don't change color on mouse hover
                    highlightFillColor: function(geo) {
                        return geo['fillColor'] || 'rgba(30,30,30,0.5)';
                    },
                    // only change border
                    highlightBorderColor: 'rgba(222,222,222,0.5)',
                    // show desired information in tooltip
                    popupTemplate: function(geo, data) {
                        // don't show tooltip if country don't present in dataset
                        if (!data) { return ; }
                        // tooltip content
                        return ['',
                        	'<div style="opacity:0.7;" class="hoverinfo">% of visitors in ' + geo.properties.name,
                                ': ' + data.percent,
                                ''].join('');        
                                
                    }
                }
            });
            
         
            //draw a legend for this map
            map.legend();
            
              map.graticule();
<script src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.9/topojson.min.js"></script>
<script src="http://unilogue.github.io/js/map/datamaps.world.min.js"></script>

<div id="world" style="fill-opacity:0.7; height: 600px; width: 500px; margin-top:-100px;"></div>

edit: apparently the done callback lets you use events, i created this zoom/pan function as a test but how can i use this to rotate my map with d3.behavior.drag and euler angles?

var map = new Datamap({
                done: function(datamap) {
            datamap.svg.call(d3.behavior.zoom().on("zoom", redraw));
            function redraw() {
                datamap.svg.selectAll("g").attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
            }
        },

edit 2: this looks like it could work! taken from here.

i tried copying it into a done callback but nothing happened, any ideas?

    var dragBehaviour = d3.behavior.drag()
    .on('drag', function(){
        var dx = d3.event.dx;
        var dy = d3.event.dy;

        var rotation = projection.rotate();
        var radius = projection.scale();
        var scale = d3.scale.linear()
            .domain([-1 * radius, radius])
            .range([-90, 90]);
        var degX = scale(dx);
        var degY = scale(dy);
        rotation[0] += degX;
        rotation[1] -= degY;
        if (rotation[1] > 90)   rotation[1] = 90;
        if (rotation[1] < -90)  rotation[1] = -90;

        if (rotation[0] >= 180) rotation[0] -= 360;
        projection.rotate(rotation);
        redraw();
    })

Solution

  • var livemap;
    scope.rotation = [97, -30];
    
    function redraw() {
      d3.select("#map-wrapper").html('');
      init();
    }// redraw
    
    
    function init() {
      livemap = new Datamap({...})
    
      var drag = d3.behavior.drag().on('drag', function() {
        var dx = d3.event.dx;
        var dy = d3.event.dy;
    
        var rotation = livemap.projection.rotate();
        var radius = livemap.projection.scale();
        var scale = d3.scale.linear().domain([-1 * radius, radius]).range([-90, 90]);
        var degX = scale(dx);
        var degY = scale(dy);
        rotation[0] += degX;
        rotation[1] -= degY;
        if (rotation[1] > 90) rotation[1] = 90;
        if (rotation[1] < -90) rotation[1] = -90;
    
        if (rotation[0] >= 180) rotation[0] -= 360;
        scope.rotation = rotation;
        redraw();
      })
    
     d3.select("#map-wrapper").select("svg").call(drag);
    
    }// init