Sfoglia il codice sorgente

Big refactor, moving initialization code to own function, so that multiple graphs play nice together

Zachary Tong 12 anni fa
parent
commit
d3debd1f09

+ 1 - 4
panels/map2/display/binning.js

@@ -28,10 +28,7 @@ function displayBinning(scope, dimensions, projection, path) {
 
     } else {
 
-        binPoints = _.map(scope.data, function (k, v) {
-            var decoded = geohash.decode(v);
-            return projection([decoded.longitude, decoded.latitude]);
-        });
+        binPoints = scope.projectedPoints;
     }
 
     //bin and sort the points, so we can set the various ranges appropriately

+ 18 - 4
panels/map2/display/geopoints.js

@@ -13,16 +13,30 @@ function displayGeopoints(scope, path) {
 
     */
 
+
+
+    var points = []
+    if (scope.panel.display.bullseye.enabled) {
+        points = scope.points;
+    }
+
     var circle = d3.geo.circle();
     var degrees = 180 / Math.PI
 
-    scope.g.selectAll("circles.points")
-        .data(points)
-        .enter().append("path")
+    var geopoints = scope.g.selectAll("geopoints")
+        .data(points);
+
+    geopoints.enter().append("path")
         .datum(function(d) {
-            return circle.origin([d[0], d[1]]).angle(5 / 6371 * degrees)();
+            return circle.origin([d[0], d[1]]).angle(scope.panel.display.geopoints.pointSize / 6371 * degrees)();
         })
         .attr("d", path)
         .attr("class", "geopoint");
 
+    geopoints.exit().remove();
+
+
+
+
+
 }

+ 9 - 3
panels/map2/editor.html

@@ -61,7 +61,7 @@
 
         <div class="span11">
             <ul class="nav nav-tabs" ng-cloak="">
-                <li ng-repeat="tab in panel.displayTabs" ng-class="{active:isActive(tab)}">
+                <li ng-repeat="tab in ['Geopoints', 'Binning', 'Choropleth', 'Bullseye', 'Data']" ng-class="{active:isActive(tab)}">
                     <a ng-click="tabClick(tab)">{{tab}}</a>
                 </li>
             </ul>
@@ -87,7 +87,7 @@
                         <input type="text" style="width:100px"
                             ng-change="$emit('render')"
                             data-placement="right"
-                            bs-tooltip="'Controls the size of the geopoints on the map'"
+                            bs-tooltip="'Controls the size of the geopoints on the map. Units in kilometers (km)'"
                             ng-model="panel.display.geopoints.pointSize"
                             value="{{panel.display.geopoints.pointSize}}" />
                     </td>
@@ -216,7 +216,7 @@
                         <button type="button" class="btn" bs-button
                                 ng-change="$emit('render')"
                                 ng-class="{'btn-success': panel.display.bullseye.enabled}"
-                                ng-model="panel.display.bullseye.enabled">{{panel.display.choropleth.enabled|enabledText}}</button>
+                                ng-model="panel.display.bullseye.enabled">{{panel.display.bullseye.enabled|enabledText}}</button>
                     </td>
                 </tr>
                 <tr>
@@ -259,6 +259,12 @@
                                value="{{panel.display.data.samples}}" />
                     </td>
                 </tr>
+                <tr>
+                    <td>Map Projection</td>
+                    <td>
+                        <select ng-model="panel.display.data.type" ng-options="option.id as option.text for option in options.data.dropdown"></select>
+                    </td>
+                </tr>
                 </tbody>
             </table>
         </div>

+ 8 - 1
panels/map2/module.html

@@ -7,11 +7,15 @@
 
         .land {
             fill: #D1D1D1;
+            stroke: #595959;
+            stroke-linejoin: round;
+            stroke-linecap: round;
+            stroke-width: .1px;
         }
 
         .boundary {
             fill: none;
-            stroke: #fff;
+            stroke: #000;
             stroke-linejoin: round;
             stroke-linecap: round;
         }
@@ -43,9 +47,12 @@
             stroke-width: .5px;
             fill: #000;
         }
+
+        .dropdown-menu{position:absolute;top:auto;left:auto;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;}
     </style>
   <span ng-show="panel.spyable" style="position:absolute;right:0px;top:0px" class='panelextra pointer'>
     <i bs-modal="'partials/modal.html'" class="icon-eye-open"></i>
   </span>
+
   <div map2 params="{{panel}}" style="height:{{panel.height || row.height}}"></div>
 </kibana-panel>

+ 204 - 138
panels/map2/module.js

@@ -15,16 +15,17 @@ angular.module('kibana.map2', [])
                 translate:[0, 0],
                 scale:-1,
                 data: {
-                  samples: 1000
+                    samples: 1000,
+                    type: "mercator"
                 },
                 geopoints: {
-                    enabled: true,
+                    enabled: false,
                     enabledText: "Enabled",
                     pointSize: 0.3,
                     pointAlpha: 0.6
                 },
                 binning: {
-                    enabled: true,
+                    enabled: false,
                     hexagonSize: 2,
                     hexagonAlpha: 1.0,
                     areaEncoding: true,
@@ -41,12 +42,8 @@ angular.module('kibana.map2', [])
                         lat: 0,
                         lon: 0
                     }
-                },
-                map: {
-                    type: "mercator"
                 }
             },
-            displayTabs: ["Geopoints", "Binning", "Choropleth", "Bullseye", "Data"],
             activeDisplayTab:"Geopoints"
         };
 
@@ -188,95 +185,200 @@ angular.module('kibana.map2', [])
     .directive('map2', function () {
         return {
             restrict: 'A',
+            template: '<div class="loading"><center><img src="common/img/load_big.gif" style="display:none"></center></div><div id="{{uuid}}"></div>',
             link: function (scope, elem, attrs) {
 
-                elem.html('<center><img src="common/img/load_big.gif"></center>')
+
+                //elem.html('')
 
                 scope.worldData = null;
                 scope.worldNames = null;
-                scope.svg = null;
-                scope.g = null;
+
                 scope.ctrlKey = false;
 
+                //These are various options that should not be cached in scope.panel
+                scope.options = {
+
+                    data: {
+                        dropdown:[
+                            {
+                                "text": "Mercator (Flat)",
+                                id: "mercator"
+                            },
+                            {
+                                text: "Orthographic (Sphere)",
+                                id: "orthographic"
+                            }
+                        ]
+                    }
+                };
+
+
+                //These should be moved to utility classes
+                var s4 = function() {
+                    return Math.floor((1 + Math.random()) * 0x10000)
+                        .toString(16)
+                        .substring(1);
+                };
+
+
+                scope.uuid =  s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+                        s4() + '-' + s4() + s4() + s4();
+
+
+
+
+
 
                 // Receive render events
                 scope.$on('render', function () {
-                    render_panel();
+                    console.log("$on render");
+
+                    if (typeof scope.svg === 'undefined') {
+                        console.log("init");
+                        init_panel();
+                    } else {
+                        console.log("render");
+                        render_panel();
+                    }
                 });
 
                 // Or if the window is resized
                 angular.element(window).bind('resize', function () {
-                    render_panel();
+                    console.log("resize render");
+
+                    if (typeof scope.svg === 'undefined') {
+                        console.log("init");
+                        init_panel();
+                    } else {
+                        console.log("render");
+                        render_panel();
+                    }
+
+
                 });
 
-                function render_panel() {
+
+                function init_panel() {
 
                     // Using LABjs, wait until all scripts are loaded before rendering panel
-                    var scripts = $LAB.script("panels/map2/lib/d3.v3.min.js")
-                        .script("panels/map2/lib/topojson.v1.min.js")
-                        .script("panels/map2/lib/node-geohash.js")
-                        .script("panels/map2/lib/d3.hexbin.v0.min.js")
-                        .script("panels/map2/lib/queue.v1.min.js")
-                        .script("panels/map2/display/binning.js")
-                        .script("panels/map2/display/geopoints.js")
-                        .script("panels/map2/display/bullseye.js");
+                    var scripts = $LAB.script("panels/map2/lib/d3.v3.min.js?rand="+Math.floor(Math.random()*10000))
+                        .script("panels/map2/lib/topojson.v1.min.js?rand="+Math.floor(Math.random()*10000))
+                        .script("panels/map2/lib/node-geohash.js?rand="+Math.floor(Math.random()*10000))
+                        .script("panels/map2/lib/d3.hexbin.v0.min.js?rand="+Math.floor(Math.random()*10000))
+                        .script("panels/map2/lib/queue.v1.min.js?rand="+Math.floor(Math.random()*10000))
+                        .script("panels/map2/display/binning.js?rand="+Math.floor(Math.random()*10000))
+                        .script("panels/map2/display/geopoints.js?rand="+Math.floor(Math.random()*10000))
+                        .script("panels/map2/display/bullseye.js?rand="+Math.floor(Math.random()*10000));
 
                     // Populate element. Note that jvectormap appends, does not replace.
                     scripts.wait(function () {
-                        elem.text('');
-
-
-
-                        //these files can take a bit of time to process, so save them in a variable
-                        //and use those on redraw
-                        if (scope.worldData === null || scope.worldNames === null) {
-                            queue()
-                                .defer(d3.json, "panels/map2/lib/world-110m.json")
-                                .defer(d3.tsv, "panels/map2/lib/world-country-names.tsv")
-                                .await(function(error, world, names) {
-                                    scope.worldData = world;
-                                    scope.worldNames = names;
-                                    ready();
-                                });
-                        } else {
-                            ready();
-                        }
+
+                        queue()
+                            .defer(d3.json, "panels/map2/lib/world-110m.json")
+                            .defer(d3.tsv, "panels/map2/lib/world-country-names.tsv")
+                            .await(function(error, world, names) {
+                                scope.worldData = world;
+                                scope.worldNames = names;
+
+                                console.log('initializing svg');
+
+
+
+                                //Better way to get these values?  Seems kludgy to use jQuery on the div...
+                                var width = $(elem[0]).width(),
+                                    height = $(elem[0]).height();
+
+
+                                //scale to whichever dimension is smaller, helps to ensure the whole map is displayed
+                                scope.scale = (width > height) ? (height/5) : (width/5);
+
+
+                                scope.zoom = d3.behavior.zoom()
+                                    .scaleExtent([1, 8])
+                                    .on("zoom", translate_map);
+
+                                //used by choropleth
+                                scope.quantize = d3.scale.quantize()
+                                    .domain([0, 1000])
+                                    .range(d3.range(9).map(function(i) { return "q" + (i+1); }));
+
+
+                                //Extract name and two-letter codes for our countries
+                                scope.countries = topojson.feature(scope.worldData, scope.worldData.objects.countries).features;
+
+                                scope.countries = scope.countries.filter(function(d) {
+                                    return scope.worldNames.some(function(n) {
+                                        if (d.id == n.id) {
+                                            d.name = n.name;
+                                            return d.short = n.short;
+                                        }
+                                    });
+                                }).sort(function(a, b) {
+                                        return a.name.localeCompare(b.name);
+                                    });
+
+
+
+
+                                //remove our old svg...is there a better way to update than remove/append?
+                                //d3.select("#" + scope.uuid).select("svg").remove();
+
+                                //create the new svg
+                                scope.svg = d3.select(elem[0]).append("svg")
+                                    .attr("width", width)
+                                    .attr("height", height)
+                                    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
+                                    .call(scope.zoom);
+
+
+                                scope.g = scope.svg.append("g");
+
+                                //Overlay is used so that the entire map is draggable, not just the locations
+                                //where countries are
+                                scope.svg.append("rect")
+                                    .attr("class", "overlay")
+                                    .attr("width", width)
+                                    .attr("height", height)
+                                    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
+
+
+                                console.log("finished initing");
+                                render_panel();
+                            });
+
                     });
+
                 }
 
-                /**
-                 * All map data has been loaded, go ahead and draw the map/data
-                 */
-                function ready() {
 
 
+                function render_panel() {
 
 
-                    var world = scope.worldData,
-                        names = scope.worldNames;
 
-                    //Better way to get these values?  Seems kludgy to use jQuery on the div...
-                    var width = $(elem[0]).width(),
-                        height = $(elem[0]).height();
+                    console.log("render_panel scope.svg", scope.svg);
 
 
-                    //scale to whichever dimension is smaller, helps to ensure the whole map is displayed
-                    var scale = (width > height) ? (height/5) : (width/5);
+                    var width = $(elem[0]).width(),
+                        height = $(elem[0]).height();
 
 
                     /**
                      * D3 and general config section
                      */
 
-                    var projection;
 
-                    if (scope.panel.display.map.type === 'mercator') {
-                        projection = d3.geo.mercator()
+                    scope.projection;
+
+                    if (scope.panel.display.data.type === 'mercator') {
+                        scope.projection = d3.geo.mercator()
                             .translate([width/2, height/2])
-                            .scale(scale);
-                    } else if (scope.panel.display.map.type === 'orthographic') {
-                        projection = d3.geo.orthographic()
-                            .scale(248)
+                            .scale(scope.scale);
+                    } else if (scope.panel.display.data.type === 'orthographic') {
+                        scope.projection = d3.geo.orthographic()
+                            .translate([width/2, height/2])
+                            .scale(100)
                             .clipAngle(90);
 
                         var λ = d3.scale.linear()
@@ -288,76 +390,24 @@ angular.module('kibana.map2', [])
                             .range([90, -90]);
                     }
 
-                    var zoom = d3.behavior.zoom()
-                        .scaleExtent([1, 8])
-                        .on("zoom", move);
-
                     var path = d3.geo.path()
-                        .projection(projection);
-
-                    //used by choropleth
-                    var quantize = d3.scale.quantize()
-                        .domain([0, 1000])
-                        .range(d3.range(9).map(function(i) { return "q" + (i+1); }));
-
-
-                    //Extract name and two-letter codes for our countries
-                    var countries = topojson.feature(world, world.objects.countries).features;
+                        .projection(scope.projection);
 
-                    countries = countries.filter(function(d) {
-                        return names.some(function(n) {
-                            if (d.id == n.id) {
-                                d.name = n.name;
-                                return d.short = n.short;
-                            }
-                        });
-                    }).sort(function(a, b) {
-                        return a.name.localeCompare(b.name);
-                    });
 
                     //Geocoded points are decoded into lat/lon, then projected onto x/y
-                    points = _.map(scope.data, function (k, v) {
+                    scope.points = _.map(scope.data, function (k, v) {
                         var decoded = geohash.decode(v);
                         return [decoded.longitude, decoded.latitude];
                     });
+                    scope.projectedPoints = _.map(scope.points, function (k, v) {
+                        return scope.projection(v);
+                    });
 
 
 
 
-                    /**
-                     * D3 SVG Setup
-                     */
-
-                    //set up some key listeners for our sphere dragging
-                    window.focus();
-                    d3.select(window)
-                        .on("keydown", function() {
-                            scope.ctrlKey = d3.event.ctrlKey;
-                        })
-                        .on("keyup", function() {
-                            scope.ctrlKey = d3.event.ctrlKey;
-                        });
-
-                    //remove our old svg...is there a better way to update than remove/append?
-                    d3.select(elem[0]).select("svg").remove();
-
-                    //create the new svg
-                    scope.svg = d3.select(elem[0]).append("svg")
-                        .attr("width", width)
-                        .attr("height", height)
-                        .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
-                        .call(zoom);
-
 
-                    scope.g = scope.svg.append("g");
 
-                    //Overlay is used so that the entire map is draggable, not just the locations
-                    //where countries are
-                    scope.svg.append("rect")
-                        .attr("class", "overlay")
-                        .attr("width", width)
-                        .attr("height", height)
-                        .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
 
 
                     //set up listener for ctrl key
@@ -365,12 +415,12 @@ angular.module('kibana.map2', [])
 
 
                     //Draw the countries, if this is a choropleth, draw with fancy colors
-                    scope.g.selectAll("path")
-                        .data(countries)
+                    scope.g.selectAll("countries")
+                        .data(scope.countries)
                         .enter().append("path")
                         .attr("class", function(d) {
                             if (scope.panel.display.choropleth.enabled) {
-                                return 'land ' + quantize(scope.data[d.short]);
+                                return 'land ' + scope.quantize(scope.data[d.short]);
                             } else {
                                 return 'land';
                             }
@@ -379,13 +429,25 @@ angular.module('kibana.map2', [])
 
                     //draw boundaries
                     scope.g.selectAll("land").append("path")
-                        .datum(topojson.mesh(world, world.objects.land, function(a, b) { return a !== b; }))
+                        .datum(topojson.mesh(scope.worldData, scope.worldData.objects.land, function(a, b) { return a !== b; }))
                         .attr("class", "land boundary")
                         .attr("d", path);
 
 
 
-                    if (scope.panel.display.map.type === 'orthographic') {
+                    if (scope.panel.display.data.type === 'orthographic') {
+
+                        //set up some key listeners for our sphere dragging
+                        window.focus();
+                        d3.select(window)
+                            .on("keydown", function() {
+                                scope.ctrlKey = d3.event.ctrlKey;
+                            })
+                            .on("keyup", function() {
+                                scope.ctrlKey = d3.event.ctrlKey;
+                            });
+
+
                         scope.svg.style("cursor", "move")
                             .call(d3.behavior.drag()
                                 .origin(function() { var rotate = projection.rotate(); return {x: 2 * rotate[0], y: -2 * rotate[1]}; })
@@ -406,44 +468,48 @@ angular.module('kibana.map2', [])
                     if (scope.panel.display.binning.enabled) {
                         //@todo fix this
                         var dimensions = [width, height];
-                        displayBinning(scope, dimensions, projection, path);
+                        displayBinning(scope, dimensions, scope.projection, path);
                     }
 
                     //Raw geopoints
-                    if (scope.panel.display.geopoints.enabled) {
+                    //if (scope.panel.display.geopoints.enabled) {
                         displayGeopoints(scope, path);
-                    }
+                    //}
 
-                    if (scope.panel.display.bullseye.enabled) {
-                        displayBullseye(scope, projection, path);
-                    }
+                    //if (scope.panel.display.bullseye.enabled) {
+                        displayBullseye(scope, scope.projection, path);
+                    //}
+
+                    //d3.select(elem[0]).select(".loading").remove();
 
 
                     /**
                      * Zoom Functionality
                      */
                     if (scope.panel.display.scale != -1) {
-                        zoom.scale(scope.panel.display.scale).translate(scope.panel.display.translate);
+                        scope.zoom.scale(scope.panel.display.scale).translate(scope.panel.display.translate);
                         scope.g.style("stroke-width", 1 / scope.panel.display.scale).attr("transform", "translate(" + scope.panel.display.translate + ") scale(" + scope.panel.display.scale + ")");
 
                     }
 
-                    function move() {
+                }
 
-                        if (! scope.ctrlKey) {
-                            var t = d3.event.translate,
-                                s = d3.event.scale;
-                            t[0] = Math.min(width / 2 * (s - 1), Math.max(width / 2 * (1 - s), t[0]));
-                            t[1] = Math.min(height / 2 * (s - 1) + 230 * s, Math.max(height / 2 * (1 - s) - 230 * s, t[1]));
-                            zoom.translate(t);
+                function translate_map() {
 
-                            scope.panel.display.translate = t;
-                            scope.panel.display.scale = s;
-                            scope.g.style("stroke-width", 1 / s).attr("transform", "translate(" + t + ") scale(" + s + ")");
-                        }
-                    }
+                    var width = $(elem[0]).width(),
+                        height = $(elem[0]).height();
 
+                    if (! scope.ctrlKey) {
+                        var t = d3.event.translate,
+                            s = d3.event.scale;
+                        t[0] = Math.min(width / 2 * (s - 1), Math.max(width / 2 * (1 - s), t[0]));
+                        t[1] = Math.min(height / 2 * (s - 1) + 230 * s, Math.max(height / 2 * (1 - s) - 230 * s, t[1]));
+                        scope.zoom.translate(t);
 
+                        scope.panel.display.translate = t;
+                        scope.panel.display.scale = s;
+                        scope.g.style("stroke-width", 1 / s).attr("transform", "translate(" + t + ") scale(" + s + ")");
+                    }
                 }
             }
         };