diff --git a/panels/map2/display/binning.js b/panels/map2/display/binning.js index f44258158c2..fa376295beb 100644 --- a/panels/map2/display/binning.js +++ b/panels/map2/display/binning.js @@ -1,10 +1,11 @@ +/** + * Hexagonal binning + * Rendered as normally projected svg paths, which mean they *do not* + * clip on spheres appropriately. To fix this, we would need to translate + * the svg path into a geo-path + */ +function displayBinning(scope, dimensions) { -function displayBinning(scope, dimensions, projection, path) { - - - /** - * Hexbin-specific setup - */ var hexbin = d3.hexbin() .size(dimensions) .radius(scope.panel.display.binning.hexagonSize); @@ -16,10 +17,13 @@ function displayBinning(scope, dimensions, projection, path) { if (scope.panel.display.binning.enabled) { - //primary field is just binning raw counts - //secondary field is binning some metric like mean/median/total. Hexbins doesn't support that, - //so we cheat a little and just add more points to compensate. - //However, we don't want to add a million points, so normalize against the largest value + /** + * primary field is just binning raw counts + * + * Secondary field is binning some metric like mean/median/total. Hexbins doesn't support that, + * so we cheat a little and just add more points to compensate. + * However, we don't want to add a million points, so normalize against the largest value + */ if (scope.panel.display.binning.areaEncodingField === 'secondary') { var max = Math.max.apply(Math, _.map(scope.data, function(k,v){return k;})), scale = 50/max; @@ -27,12 +31,11 @@ function displayBinning(scope, dimensions, projection, path) { _.map(scope.data, function (k, v) { var decoded = geohash.decode(v); return _.map(_.range(0, k*scale), function(a,b) { - binPoints.push(projection([decoded.longitude, decoded.latitude])); + binPoints.push(scope.projection([decoded.longitude, decoded.latitude])); }) }); } else { - binPoints = scope.projectedPoints; } @@ -43,6 +46,8 @@ function displayBinning(scope, dimensions, projection, path) { //clean up some memory binPoints = []; } else { + + //not enabled, so just set an empty array. D3.exit will take care of the rest binnedPoints = []; binRange = 0; } @@ -59,10 +64,6 @@ function displayBinning(scope, dimensions, projection, path) { .interpolate(d3.interpolateLab); - /** - * D3 Drawing - */ - var hex = scope.g.selectAll(".hexagon") .data(binnedPoints); diff --git a/panels/map2/display/bullseye.js b/panels/map2/display/bullseye.js index 539bdbac50d..baad275b5dd 100644 --- a/panels/map2/display/bullseye.js +++ b/panels/map2/display/bullseye.js @@ -1,10 +1,11 @@ -function displayBullseye(scope, projection, path) { +/** + * Renders bullseyes as geo-json poly gon entities + * Allows for them to clip on spheres correctly + */ +function displayBullseye(scope, path) { var degrees = 180 / Math.PI - - var circle = d3.geo.circle(); - var data = []; if (scope.panel.display.bullseye.enabled) { @@ -13,11 +14,9 @@ function displayBullseye(scope, projection, path) { ]; } - var arcs = scope.g.selectAll(".arc") .data(data); - arcs.enter().append("path") .datum(function(d) { return circle.origin([d.lon, d.lat]).angle(1000 / 6371 * degrees)(); diff --git a/panels/map2/display/geopoints.js b/panels/map2/display/geopoints.js index d181a2e0cd2..9d7eb5b8175 100644 --- a/panels/map2/display/geopoints.js +++ b/panels/map2/display/geopoints.js @@ -1,46 +1,18 @@ +/** + * Renders geopoints as geo-json poly gon entities + * Allows for them to clip on spheres correctly + */ function displayGeopoints(scope, path) { - /* - var points = {}; - var circle = d3.geo.circle(); - var degrees = 180 / Math.PI; + var points = []; + var circle = d3.geo.circle(); + var degrees = 180 / Math.PI - if (scope.panel.display.geopoints.enabled) { - //points = scope.points; - - var features = _.map(scope.points, function(coords) { - return { - feature: circle.origin(scope.projection([coords[0], coords[1]])) - .angle(scope.panel.display.geopoints.pointSize / 6371 * degrees)() - }; - }); - - console.log("features", features); - points = { - type: "FeatureCollection", - features: features - }; - console.log("points", points); - - } - - console.log("points2", points); - scope.svg.append("path") - .datum(points) - .attr("d", d3.geo.path()); - - - */ - - - - var points = [] if (scope.panel.display.geopoints.enabled) { points = scope.points; } - var circle = d3.geo.circle(); - var degrees = 180 / Math.PI + var geopoints = scope.g.selectAll(".geopoint") .data(points); diff --git a/panels/map2/module.js b/panels/map2/module.js index c5d31b3a67b..39f16214e32 100644 --- a/panels/map2/module.js +++ b/panels/map2/module.js @@ -186,7 +186,6 @@ angular.module('kibana.map2', []) .directive('map2', function () { return { restrict: 'A', - template: '
', link: function (scope, elem, attrs) { @@ -215,20 +214,9 @@ angular.module('kibana.map2', []) }; - //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(); - - - - + /** + * Initialize the panels if new, or render existing panels + */ scope.init_or_render = function() { if (typeof scope.svg === 'undefined') { console.log("init"); @@ -245,19 +233,25 @@ angular.module('kibana.map2', []) }; - // Receive render events + /** + * Receive render events + */ scope.$on('render', function () { - console.log("$on render"); scope.init_or_render(); }); - // Or if the window is resized + /** + * On window resize, re-render the panel + */ angular.element(window).bind('resize', function () { - console.log("resize render"); scope.init_or_render(); }); + /** + * Load the various panel-specific scripts, map data, then initialize + * the svg and set appropriate D3 settings + */ function init_panel() { scope.initializing = true; @@ -281,9 +275,6 @@ angular.module('kibana.map2', []) 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(), @@ -299,6 +290,7 @@ angular.module('kibana.map2', []) .on("zoom", translate_map); //used by choropleth + //@todo change domain so that it reflects the domain of the data scope.quantize = d3.scale.quantize() .domain([0, 1000]) .range(d3.range(9).map(function(i) { return "q" + (i+1); })); @@ -319,100 +311,68 @@ angular.module('kibana.map2', []) }); - - - //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"); - - - console.log("finished initing"); scope.initializing = false; render_panel(); }); - }); } - + /** + * Render updates to the SVG. Typically happens when the data changes (time, query) + * or when new options are selected + */ function render_panel() { - - - console.log("render_panel scope.svg", scope.svg); - - var width = $(elem[0]).width(), height = $(elem[0]).height(); - /** - * D3 and general config section - */ - - - scope.projection; - + //Projection is dependant on the map-type if (scope.panel.display.data.type === 'mercator') { scope.projection = d3.geo.mercator() .translate([width/2, height/2]) .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() - .domain([0, width]) - .range([-180, 180]); + //recenters the sphere more towards the US...not really necessary + scope.projection.rotate([100 / 2, 20 / 2, scope.projection.rotate()[2]]); - var φ = d3.scale.linear() - .domain([0, height]) - .range([90, -90]); } var path = d3.geo.path() .projection(scope.projection); - //Geocoded points are decoded into lat/lon, then projected onto x/y + //Geocoded points are decoded into lonlat scope.points = _.map(scope.data, function (k, v) { var decoded = geohash.decode(v); return [decoded.longitude, decoded.latitude]; }); + + //And also projected projected to x/y. Both sets of points are used + //by different functions scope.projectedPoints = _.map(scope.points, function (coords) { return scope.projection(coords); }); - - - - - - - - //set up listener for ctrl key - //scope.svg - - //Overlay is used so that the entire map is draggable, not just the locations - //where countries are - scope.svg.select(".overlay").remove(); scope.svg.append("rect") @@ -421,12 +381,11 @@ angular.module('kibana.map2', []) .attr("height", height) .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); + //Draw the countries, if this is a choropleth, draw with fancy colors var countryPath = scope.g.selectAll(".land") .data(scope.countries); - - countryPath.enter().append("path") .attr("class", function(d) { if (scope.panel.display.choropleth.enabled) { @@ -450,9 +409,10 @@ angular.module('kibana.map2', []) + //If this is a sphere, set up drag and keypress listeners + //@todo implement a global "keypress service", since this fails if there are >1 spheres if (scope.panel.display.data.type === 'orthographic') { - //set up some key listeners for our sphere dragging window.focus(); d3.select(window) .on("keydown", function() { @@ -465,35 +425,32 @@ angular.module('kibana.map2', []) scope.svg.style("cursor", "move") .call(d3.behavior.drag() - .origin(function() { var rotate = projection.rotate(); return {x: 2 * rotate[0], y: -2 * rotate[1]}; }) + .origin(function() { var rotate = scope.projection.rotate(); return {x: 2 * rotate[0], y: -2 * rotate[1]}; }) .on("drag", function() { if ( scope.ctrlKey) { - projection.rotate([d3.event.x / 2, -d3.event.y / 2, projection.rotate()[2]]); + scope.projection.rotate([d3.event.x / 2, -d3.event.y / 2, scope.projection.rotate()[2]]); scope.svg.selectAll("path").attr("d", path); - //displayBinning(scope, dimensions, projection, path); } })); } - /** - * Display Options - */ - //Hexagonal Binning + /** + * Display option rendering + * Order is important to render order here! + */ //@todo fix this var dimensions = [width, height]; - displayBinning(scope, dimensions, scope.projection, path); + displayBinning(scope, dimensions); displayGeopoints(scope, path); - displayBullseye(scope, scope.projection, path); + displayBullseye(scope, path); - //d3.select(elem[0]).select(".loading").remove(); - /** - * Zoom Functionality - */ + //If the panel scale is not default (e.g. the user has moved the maps around) + //set the scale and position to the last saved config if (scope.panel.display.scale != -1) { 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 + ")"); @@ -502,6 +459,12 @@ angular.module('kibana.map2', []) } + + /** + * On D3 zoom events, pan/zoom the map + * Only applies if the ctrl-key is not pressed, so it doesn't clobber + * sphere dragging + */ function translate_map() { var width = $(elem[0]).width(),