Commit 1dcf9d65 by Zachary Tong

Cleaning up, commenting

parent 88609fbe
/**
* 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() var hexbin = d3.hexbin()
.size(dimensions) .size(dimensions)
.radius(scope.panel.display.binning.hexagonSize); .radius(scope.panel.display.binning.hexagonSize);
...@@ -16,10 +17,13 @@ function displayBinning(scope, dimensions, projection, path) { ...@@ -16,10 +17,13 @@ function displayBinning(scope, dimensions, projection, path) {
if (scope.panel.display.binning.enabled) { 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, * primary field is just binning raw counts
//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 * 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') { if (scope.panel.display.binning.areaEncodingField === 'secondary') {
var max = Math.max.apply(Math, _.map(scope.data, function(k,v){return k;})), var max = Math.max.apply(Math, _.map(scope.data, function(k,v){return k;})),
scale = 50/max; scale = 50/max;
...@@ -27,12 +31,11 @@ function displayBinning(scope, dimensions, projection, path) { ...@@ -27,12 +31,11 @@ function displayBinning(scope, dimensions, projection, path) {
_.map(scope.data, function (k, v) { _.map(scope.data, function (k, v) {
var decoded = geohash.decode(v); var decoded = geohash.decode(v);
return _.map(_.range(0, k*scale), function(a,b) { 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 { } else {
binPoints = scope.projectedPoints; binPoints = scope.projectedPoints;
} }
...@@ -43,6 +46,8 @@ function displayBinning(scope, dimensions, projection, path) { ...@@ -43,6 +46,8 @@ function displayBinning(scope, dimensions, projection, path) {
//clean up some memory //clean up some memory
binPoints = []; binPoints = [];
} else { } else {
//not enabled, so just set an empty array. D3.exit will take care of the rest
binnedPoints = []; binnedPoints = [];
binRange = 0; binRange = 0;
} }
...@@ -59,10 +64,6 @@ function displayBinning(scope, dimensions, projection, path) { ...@@ -59,10 +64,6 @@ function displayBinning(scope, dimensions, projection, path) {
.interpolate(d3.interpolateLab); .interpolate(d3.interpolateLab);
/**
* D3 Drawing
*/
var hex = scope.g.selectAll(".hexagon") var hex = scope.g.selectAll(".hexagon")
.data(binnedPoints); .data(binnedPoints);
......
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 degrees = 180 / Math.PI
var circle = d3.geo.circle(); var circle = d3.geo.circle();
var data = []; var data = [];
if (scope.panel.display.bullseye.enabled) { if (scope.panel.display.bullseye.enabled) {
...@@ -13,11 +14,9 @@ function displayBullseye(scope, projection, path) { ...@@ -13,11 +14,9 @@ function displayBullseye(scope, projection, path) {
]; ];
} }
var arcs = scope.g.selectAll(".arc") var arcs = scope.g.selectAll(".arc")
.data(data); .data(data);
arcs.enter().append("path") arcs.enter().append("path")
.datum(function(d) { .datum(function(d) {
return circle.origin([d.lon, d.lat]).angle(1000 / 6371 * degrees)(); return circle.origin([d.lon, d.lat]).angle(1000 / 6371 * degrees)();
......
/**
* Renders geopoints as geo-json poly gon entities
* Allows for them to clip on spheres correctly
*/
function displayGeopoints(scope, path) { function displayGeopoints(scope, path) {
/* var points = [];
var points = {}; var circle = d3.geo.circle();
var circle = d3.geo.circle(); var degrees = 180 / Math.PI
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) { if (scope.panel.display.geopoints.enabled) {
points = scope.points; points = scope.points;
} }
var circle = d3.geo.circle();
var degrees = 180 / Math.PI
var geopoints = scope.g.selectAll(".geopoint") var geopoints = scope.g.selectAll(".geopoint")
.data(points); .data(points);
......
...@@ -186,7 +186,6 @@ angular.module('kibana.map2', []) ...@@ -186,7 +186,6 @@ angular.module('kibana.map2', [])
.directive('map2', function () { .directive('map2', function () {
return { return {
restrict: 'A', 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) { link: function (scope, elem, attrs) {
...@@ -215,20 +214,9 @@ angular.module('kibana.map2', []) ...@@ -215,20 +214,9 @@ angular.module('kibana.map2', [])
}; };
//These should be moved to utility classes /**
var s4 = function() { * Initialize the panels if new, or render existing panels
return Math.floor((1 + Math.random()) * 0x10000) */
.toString(16)
.substring(1);
};
scope.uuid = s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
scope.init_or_render = function() { scope.init_or_render = function() {
if (typeof scope.svg === 'undefined') { if (typeof scope.svg === 'undefined') {
console.log("init"); console.log("init");
...@@ -245,19 +233,25 @@ angular.module('kibana.map2', []) ...@@ -245,19 +233,25 @@ angular.module('kibana.map2', [])
}; };
// Receive render events /**
* Receive render events
*/
scope.$on('render', function () { scope.$on('render', function () {
console.log("$on render");
scope.init_or_render(); scope.init_or_render();
}); });
// Or if the window is resized /**
* On window resize, re-render the panel
*/
angular.element(window).bind('resize', function () { angular.element(window).bind('resize', function () {
console.log("resize render");
scope.init_or_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() { function init_panel() {
scope.initializing = true; scope.initializing = true;
...@@ -281,9 +275,6 @@ angular.module('kibana.map2', []) ...@@ -281,9 +275,6 @@ angular.module('kibana.map2', [])
scope.worldData = world; scope.worldData = world;
scope.worldNames = names; scope.worldNames = names;
console.log('initializing svg');
//Better way to get these values? Seems kludgy to use jQuery on the div... //Better way to get these values? Seems kludgy to use jQuery on the div...
var width = $(elem[0]).width(), var width = $(elem[0]).width(),
...@@ -299,6 +290,7 @@ angular.module('kibana.map2', []) ...@@ -299,6 +290,7 @@ angular.module('kibana.map2', [])
.on("zoom", translate_map); .on("zoom", translate_map);
//used by choropleth //used by choropleth
//@todo change domain so that it reflects the domain of the data
scope.quantize = d3.scale.quantize() scope.quantize = d3.scale.quantize()
.domain([0, 1000]) .domain([0, 1000])
.range(d3.range(9).map(function(i) { return "q" + (i+1); })); .range(d3.range(9).map(function(i) { return "q" + (i+1); }));
...@@ -319,100 +311,68 @@ angular.module('kibana.map2', []) ...@@ -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 //create the new svg
scope.svg = d3.select(elem[0]).append("svg") scope.svg = d3.select(elem[0]).append("svg")
.attr("width", width) .attr("width", width)
.attr("height", height) .attr("height", height)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")") .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.call(scope.zoom); .call(scope.zoom);
scope.g = scope.svg.append("g"); scope.g = scope.svg.append("g");
console.log("finished initing");
scope.initializing = false; scope.initializing = false;
render_panel(); render_panel();
}); });
}); });
} }
/**
* Render updates to the SVG. Typically happens when the data changes (time, query)
* or when new options are selected
*/
function render_panel() { function render_panel() {
console.log("render_panel scope.svg", scope.svg);
var width = $(elem[0]).width(), var width = $(elem[0]).width(),
height = $(elem[0]).height(); height = $(elem[0]).height();
/** //Projection is dependant on the map-type
* D3 and general config section
*/
scope.projection;
if (scope.panel.display.data.type === 'mercator') { if (scope.panel.display.data.type === 'mercator') {
scope.projection = d3.geo.mercator() scope.projection = d3.geo.mercator()
.translate([width/2, height/2]) .translate([width/2, height/2])
.scale(scope.scale); .scale(scope.scale);
} else if (scope.panel.display.data.type === 'orthographic') { } else if (scope.panel.display.data.type === 'orthographic') {
scope.projection = d3.geo.orthographic() scope.projection = d3.geo.orthographic()
.translate([width/2, height/2]) .translate([width/2, height/2])
.scale(100) .scale(100)
.clipAngle(90); .clipAngle(90);
var λ = d3.scale.linear() //recenters the sphere more towards the US...not really necessary
.domain([0, width]) scope.projection.rotate([100 / 2, 20 / 2, scope.projection.rotate()[2]]);
.range([-180, 180]);
var φ = d3.scale.linear()
.domain([0, height])
.range([90, -90]);
} }
var path = d3.geo.path() var path = d3.geo.path()
.projection(scope.projection); .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) { scope.points = _.map(scope.data, function (k, v) {
var decoded = geohash.decode(v); var decoded = geohash.decode(v);
return [decoded.longitude, decoded.latitude]; 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) { scope.projectedPoints = _.map(scope.points, function (coords) {
return scope.projection(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.select(".overlay").remove();
scope.svg.append("rect") scope.svg.append("rect")
...@@ -421,12 +381,11 @@ angular.module('kibana.map2', []) ...@@ -421,12 +381,11 @@ angular.module('kibana.map2', [])
.attr("height", height) .attr("height", height)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
//Draw the countries, if this is a choropleth, draw with fancy colors //Draw the countries, if this is a choropleth, draw with fancy colors
var countryPath = scope.g.selectAll(".land") var countryPath = scope.g.selectAll(".land")
.data(scope.countries); .data(scope.countries);
countryPath.enter().append("path") countryPath.enter().append("path")
.attr("class", function(d) { .attr("class", function(d) {
if (scope.panel.display.choropleth.enabled) { if (scope.panel.display.choropleth.enabled) {
...@@ -450,9 +409,10 @@ angular.module('kibana.map2', []) ...@@ -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') { if (scope.panel.display.data.type === 'orthographic') {
//set up some key listeners for our sphere dragging
window.focus(); window.focus();
d3.select(window) d3.select(window)
.on("keydown", function() { .on("keydown", function() {
...@@ -465,35 +425,32 @@ angular.module('kibana.map2', []) ...@@ -465,35 +425,32 @@ angular.module('kibana.map2', [])
scope.svg.style("cursor", "move") scope.svg.style("cursor", "move")
.call(d3.behavior.drag() .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() { .on("drag", function() {
if ( scope.ctrlKey) { 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); scope.svg.selectAll("path").attr("d", path);
//displayBinning(scope, dimensions, projection, path);
} }
})); }));
} }
/** /**
* Display Options * Display option rendering
* Order is important to render order here!
*/ */
//Hexagonal Binning
//@todo fix this //@todo fix this
var dimensions = [width, height]; var dimensions = [width, height];
displayBinning(scope, dimensions, scope.projection, path); displayBinning(scope, dimensions);
displayGeopoints(scope, path); displayGeopoints(scope, path);
displayBullseye(scope, scope.projection, path); displayBullseye(scope, path);
//d3.select(elem[0]).select(".loading").remove();
/** //If the panel scale is not default (e.g. the user has moved the maps around)
* Zoom Functionality //set the scale and position to the last saved config
*/
if (scope.panel.display.scale != -1) { if (scope.panel.display.scale != -1) {
scope.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 + ")"); 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', []) ...@@ -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() { function translate_map() {
var width = $(elem[0]).width(), var width = $(elem[0]).width(),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment