blob: 65a589c9a7c5b85ce691e08944cf6b100f0ab181 [file] [log] [blame]
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001$(function() {
2
3 // Connection to AsterixDB - Just one needed!
4 A = new AsterixDBConnection().dataverse("twitter");
5
6 // Following this is some stuff specific to the Black Cherry demo
7 // This is not necessary for working with AsterixDB
8 APIqueryTracker = {};
9 drilldown_data_map = {};
10 drilldown_data_map_vals = {};
11 asyncQueryManager = {};
12
13 review_mode_tweetbooks = [];
14 review_mode_handles = [];
15
16 map_cells = [];
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -070017 countInfoWindow = null;
18 count_info = [];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070019 map_tweet_markers = [];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070020
21 // UI Elements - Modals & perspective tabs
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -070022 $('#drilldown_modal').modal('hide');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070023 $('#explore-mode').click( onLaunchExploreMode );
24 $('#review-mode').click( onLaunchReviewMode );
25
26 // UI Elements - A button to clear current map and query data
27 $("#clear-button").button().click(function () {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -070028 mapWidgetResetMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070029
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070030 $('#query-preview-window').html('');
31 $("#metatweetzone").html('');
32 });
33
34 // UI Elements - Query setup
35 $("#selection-button").button('toggle');
36
37 var dialog = $("#dialog").dialog({
38 width: "auto",
39 title: "AQL Query"
40 }).dialog("close");
41 $("#show-query-button")
42 .button()
43 .attr("disabled", true)
44 .click(function (event) {
45 $("#dialog").dialog("open");
46 });
47
48 // UI Element - Grid sliders
49 var updateSliderDisplay = function(event, ui) {
50 if (event.target.id == "grid-lat-slider") {
51 $("#gridlat").text(""+ui.value);
52 } else {
53 $("#gridlng").text(""+ui.value);
54 }
55 };
56
57 sliderOptions = {
58 max: 10,
59 min: 1.5,
60 step: .1,
61 value: 2.0,
62 slidechange: updateSliderDisplay,
63 slide: updateSliderDisplay,
64 start: updateSliderDisplay,
65 stop: updateSliderDisplay
66 };
67
68 $("#gridlat").text(""+sliderOptions.value);
69 $("#gridlng").text(""+sliderOptions.value);
70 $(".grid-slider").slider(sliderOptions);
71
72 // UI Elements - Date Pickers
73 var dateOptions = {
74 dateFormat: "yy-mm-dd",
75 defaultDate: "2012-01-02",
76 navigationAsDateFormat: true,
77 constrainInput: true
78 };
79 var start_dp = $("#start-date").datepicker(dateOptions);
80 start_dp.val(dateOptions.defaultDate);
81 dateOptions['defaultDate'] = "2012-12-31";
82 var end_dp= $("#end-date").datepicker(dateOptions);
83 end_dp.val(dateOptions.defaultDate);
84
85 // This little bit of code manages period checks of the asynchronous query manager,
86 // which holds onto handles asynchornously received. We can set the handle update
87 // frequency using seconds, and it will let us know when it is ready.
88 var intervalID = setInterval(
89 function() {
90 asynchronousQueryIntervalUpdate();
91 },
92 asynchronousQueryGetInterval()
93 );
94
95 // UI Elements - Creates map and location auto-complete
96 onOpenExploreMap();
97 var mapOptions = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -070098 center: new google.maps.LatLng(38.89, -77.03),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070099 zoom: 4,
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700100 mapTypeId: google.maps.MapTypeId.ROADMAP,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700101 streetViewControl: false,
102 draggable : false
103 };
104 map = new google.maps.Map(document.getElementById('map_canvas'), mapOptions);
105
106 var input = document.getElementById('location-text-box');
107 var autocomplete = new google.maps.places.Autocomplete(input);
108 autocomplete.bindTo('bounds', map);
109
110 google.maps.event.addListener(autocomplete, 'place_changed', function() {
111 var place = autocomplete.getPlace();
112 if (place.geometry.viewport) {
113 map.fitBounds(place.geometry.viewport);
114 } else {
115 map.setCenter(place.geometry.location);
116 map.setZoom(17); // Why 17? Because it looks good.
117 }
118 var address = '';
119 if (place.address_components) {
120 address = [(place.address_components[0] && place.address_components[0].short_name || ''),
121 (place.address_components[1] && place.address_components[1].short_name || ''),
122 (place.address_components[2] && place.address_components[2].short_name || '') ].join(' ');
123 }
124 });
125
126 // UI Elements - Selection Rectangle Drawing
127 shouldDraw = false;
128 var startLatLng;
129 selectionRect = null;
130 var selectionRadio = $("#selection-button");
131 var firstClick = true;
132
133 google.maps.event.addListener(map, 'mousedown', function (event) {
134 // only allow drawing if selection is selected
135 if (selectionRadio.hasClass("active")) {
136 startLatLng = event.latLng;
137 shouldDraw = true;
138 }
139 });
140
141 google.maps.event.addListener(map, 'mousemove', drawRect);
142 function drawRect (event) {
143 if (shouldDraw) {
144 if (!selectionRect) {
145 var selectionRectOpts = {
146 bounds: new google.maps.LatLngBounds(startLatLng, event.latLng),
147 map: map,
148 strokeWeight: 1,
149 strokeColor: "2b3f8c",
150 fillColor: "2b3f8c"
151 };
152 selectionRect = new google.maps.Rectangle(selectionRectOpts);
153 google.maps.event.addListener(selectionRect, 'mouseup', function () {
154 shouldDraw = false;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700155 });
156 } else {
genia.likes.science@gmail.com4ada78c2013-09-07 13:53:48 -0700157
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700158 if (startLatLng.lng() < event.latLng.lng()) {
159 selectionRect.setBounds(new google.maps.LatLngBounds(startLatLng, event.latLng));
160 } else {
161 selectionRect.setBounds(new google.maps.LatLngBounds(event.latLng, startLatLng));
162 }
163 }
164 }
165 };
166
167 // UI Elements - Toggle location search style by location or by map selection
168 $('#selection-button').on('click', function (e) {
169 $("#location-text-box").attr("disabled", "disabled");
170 if (selectionRect) {
171 selectionRect.setMap(map);
172 }
173 });
174 $('#location-button').on('click', function (e) {
175 $("#location-text-box").removeAttr("disabled");
176 if (selectionRect) {
177 selectionRect.setMap(null);
178 }
179 });
180
181 // UI Elements - Tweetbook Management
182 $('.dropdown-menu a.holdmenu').click(function(e) {
183 e.stopPropagation();
184 });
185
186 $('#new-tweetbook-button').on('click', function (e) {
187 onCreateNewTweetBook($('#new-tweetbook-entry').val());
188
189 $('#new-tweetbook-entry').val($('#new-tweetbook-entry').attr('placeholder'));
190 });
191
192 // UI Element - Query Submission
193 $("#submit-button").button().click(function () {
194 // Clear current map on trigger
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700195
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700196 $("#submit-button").attr("disabled", true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700197
198 // gather all of the data from the inputs
199 var kwterm = $("#keyword-textbox").val();
200 var startdp = $("#start-date").datepicker("getDate");
201 var enddp = $("#end-date").datepicker("getDate");
202 var startdt = $.datepicker.formatDate("yy-mm-dd", startdp)+"T00:00:00Z";
203 var enddt = $.datepicker.formatDate("yy-mm-dd", enddp)+"T23:59:59Z";
204
205 var formData = {
206 "keyword": kwterm,
207 "startdt": startdt,
208 "enddt": enddt,
209 "gridlat": $("#grid-lat-slider").slider("value"),
210 "gridlng": $("#grid-lng-slider").slider("value")
211 };
212
213 // Get Map Bounds
214 var bounds;
215 if ($('#selection-button').hasClass("active") && selectionRect) {
216 bounds = selectionRect.getBounds();
217 } else {
218 bounds = map.getBounds();
219 }
220
genia.likes.science@gmail.com4ada78c2013-09-07 13:53:48 -0700221 var swLat = Math.abs(bounds.getSouthWest().lat());
222 var swLng = Math.abs(bounds.getSouthWest().lng());
223 var neLat = Math.abs(bounds.getNorthEast().lat());
224 var neLng = Math.abs(bounds.getNorthEast().lng());
225
226 formData["swLat"] = Math.min(swLat, neLat);
227 formData["swLng"] = Math.max(swLng, neLng);
228 formData["neLat"] = Math.max(swLat, neLat);
229 formData["neLng"] = Math.min(swLng, neLng);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700230
231 var build_cherry_mode = "synchronous";
232
233 if ($('#asbox').is(":checked")) {
234 build_cherry_mode = "asynchronous";
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700235 $('#show-query-button').attr("disabled", false);
236 } else {
237 $('#show-query-button').attr("disabled", true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700238 }
239
240 var f = buildAQLQueryFromForm(formData);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700241
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700242 APIqueryTracker = {
243 "query" : "use dataverse twitter;\n" + f.val(),
244 "data" : formData
245 };
246
247 $('#dialog').html(APIqueryTracker["query"]);
248
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700249 if (build_cherry_mode == "synchronous") {
250 A.query(f.val(), cherryQuerySyncCallback, build_cherry_mode);
251 } else {
252 A.query(f.val(), cherryQueryAsyncCallback, build_cherry_mode);
253 }
254
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700255 // Clears selection rectangle on query execution, rather than waiting for another clear call.
256 if (selectionRect) {
257 selectionRect.setMap(null);
258 selectionRect = null;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700259 }
260 });
261});
262
263
264function buildAQLQueryFromForm(parameters) {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700265
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700266 var bounds = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700267 "ne" : { "lat" : parameters["neLat"], "lng" : -1*parameters["neLng"]},
268 "sw" : { "lat" : parameters["swLat"], "lng" : -1*parameters["swLng"]}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700269 };
270
271 var rectangle =
272 new FunctionExpression("create-rectangle",
273 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
274 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
275
276
277 var aql = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700278 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700279 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
280 .LetClause("$region", rectangle)
281 .WhereClause().and(
282 new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
283 new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
284 new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")'),
285 new FunctionExpression("contains", "$t.message-text", "$keyword")
286 )
287 .GroupClause(
288 "$c",
289 new FunctionExpression("spatial-cell", "$t.sender-location",
290 new FunctionExpression("create-point", "24.5", "-125.5"),
291 parameters["gridlat"].toFixed(1), parameters["gridlng"].toFixed(1)),
292 "with",
293 "$t"
294 )
295 .ReturnClause({ "cell" : "$c", "count" : "count($t)" });
296
297 return aql;
298}
299
300/** Asynchronous Query Management **/
301
302
303/**
304* Checks through each asynchronous query to see if they are ready yet
305*/
306function asynchronousQueryIntervalUpdate() {
307 for (var handle_key in asyncQueryManager) {
308 if (!asyncQueryManager[handle_key].hasOwnProperty("ready")) {
309 asynchronousQueryGetAPIQueryStatus( asyncQueryManager[handle_key]["handle"], handle_key );
310 }
311 }
312}
313
314
315/**
316* Returns current time interval to check for asynchronous query readiness
317* @returns {number} milliseconds between asychronous query checks
318*/
319function asynchronousQueryGetInterval() {
320 var seconds = 10;
321 return seconds * 1000;
322}
323
324
325/**
326* Retrieves status of an asynchronous query, using an opaque result handle from API
327* @param {Object} handle, an object previously returned from an async call
328* @param {number} handle_id, the integer ID parsed from the handle object
329*/
330function asynchronousQueryGetAPIQueryStatus (handle, handle_id) {
331
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700332 A.query_status(
333 {
334 "handle" : JSON.stringify(handle)
335 },
336 function (res) {
337 if (res["status"] == "SUCCESS") {
338 // We don't need to check if this one is ready again, it's not going anywhere...
339 // Unless the life cycle of handles has changed drastically
340 asyncQueryManager[handle_id]["ready"] = true;
341
342 // Indicate success.
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700343 $('#handle_' + handle_id).removeClass("btn-disabled").prop('disabled', false).addClass("btn-success");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700344 }
345 }
346 );
347}
348
349
350/**
351* On-success callback after async API query
352* @param {object} res, a result object containing an opaque result handle to Asterix
353*/
354function cherryQueryAsyncCallback(res) {
355
356 // Parse handle, handle id and query from async call result
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700357 var handle_query = APIqueryTracker["query"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700358 var handle = res;
359 var handle_id = res["handle"].toString().split(',')[0];
360
361 // Add to stored map of existing handles
362 asyncQueryManager[handle_id] = {
363 "handle" : handle,
364 "query" : handle_query,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700365 "data" : APIqueryTracker["data"]
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700366 };
367
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700368 // Create a container for this async query handle
369 $('<div/>')
370 .css("margin-left", "1em")
371 .css("margin-bottom", "1em")
372 .css("display", "block")
373 .attr({
374 "class" : "btn-group",
375 "id" : "async_container_" + handle_id
376 })
377 .appendTo("#async-handle-controls");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700378
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700379 // Adds the main button for this async handle
380 var handle_action_button = '<button class="btn btn-disabled" id="handle_' + handle_id + '">Handle ' + handle_id + '</button>';
381 $('#async_container_' + handle_id).append(handle_action_button);
382 $('#handle_' + handle_id).prop('disabled', true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700383 $('#handle_' + handle_id).on('click', function (e) {
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700384
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700385 // make sure query is ready to be run
386 if (asyncQueryManager[handle_id]["ready"]) {
387
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700388 APIqueryTracker = {
389 "query" : asyncQueryManager[handle_id]["query"],
390 "data" : asyncQueryManager[handle_id]["data"]
391 };
392 $('#dialog').html(APIqueryTracker["query"]);
393
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700394 if (!asyncQueryManager[handle_id].hasOwnProperty("result")) {
395 // Generate new Asterix Core API Query
396 A.query_result(
397 { "handle" : JSON.stringify(asyncQueryManager[handle_id]["handle"]) },
398 function(res) {
399 asyncQueryManager[handle_id]["result"] = res;
400 cherryQuerySyncCallback(res);
401 }
402 );
403 } else {
404 cherryQuerySyncCallback(asyncQueryManager[handle_id]["result"]);
405 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700406 }
407 });
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700408
409 // Adds a removal button for this async handle
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700410 var asyncDeleteButton = addDeleteButton(
411 "trashhandle_" + handle_id,
412 "async_container_" + handle_id,
413 function (e) {
414 $('#async_container_' + handle_id).remove();
415 delete asyncQueryManager[handle_id];
416 }
417 );
418 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700419}
420
421
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700422/**
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700423* returns a json object with keys: weight, latSW, lngSW, latNE, lngNE
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700424*
425* { "cell": { rectangle: [{ point: [22.5, 64.5]}, { point: [24.5, 66.5]}]}, "count": { int64: 5 }}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700426*/
427function getRecord(cell_count_record) {
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700428 // This is a really hacky way to pull out the digits, but it works for now.
429 var values = cell_count_record.replace("int64","").match(/[-+]?[0-9]*\.?[0-9]+/g);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700430 var record_representation = {};
431
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700432 record_representation["latSW"] = parseFloat(values[0]);
433 record_representation["lngSW"] = parseFloat(values[1]);
434 record_representation["latNE"] = parseFloat(values[2]);
435 record_representation["lngNE"] = parseFloat(values[3]);
436 record_representation["weight"] = parseInt(values[4]);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700437
438 return record_representation;
439}
440
441/**
442* A spatial data cleaning and mapping call
443* @param {Object} res, a result object from a cherry geospatial query
444*/
445function cherryQuerySyncCallback(res) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700446 records = res["results"];
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700447
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700448 if (typeof res["results"][0] == "object") {
449 records = res["results"][0];
450 }
451
452 var coordinates = [];
453 var weights = [];
454
455 for (var subrecord in records) {
456 var coordinate = getRecord(records[subrecord]);
457 weights.push(coordinate["weight"]);
458 coordinates.push(coordinate);
459 }
460
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700461 triggerUIUpdate(coordinates, weights);
462 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700463}
464
465/**
466* Triggers a map update based on a set of spatial query result cells
467* @param [Array] mapPlotData, an array of coordinate and weight objects
468* @param [Array] params, an object containing original query parameters [LEGACY]
469* @param [Array] plotWeights, a list of weights of the spatial cells - e.g., number of tweets
470*/
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700471function triggerUIUpdate(mapPlotData, plotWeights) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700472 /** Clear anything currently on the map **/
473 mapWidgetClearMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700474
475 // Compute data point spread
476 var dataBreakpoints = mapWidgetLegendComputeNaturalBreaks(plotWeights);
477
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700478 var mapper = {};
479
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700480 $.each(mapPlotData, function (m, val) {
481
482 // Only map points in data range of top 4 natural breaks
483 if (mapPlotData[m].weight > dataBreakpoints[0]) {
484
485 // Get color value of legend
486 var mapColor = mapWidgetLegendGetHeatValue(mapPlotData[m].weight, dataBreakpoints);
487 var markerRadius = mapWidgetComputeCircleRadius(mapPlotData[m], dataBreakpoints);
488 var point_opacity = 1.0;
489
490 var point_center = new google.maps.LatLng(
491 (mapPlotData[m].latSW + mapPlotData[m].latNE)/2.0,
492 (mapPlotData[m].lngSW + mapPlotData[m].lngNE)/2.0);
493
494 // Create and plot marker
495 var map_circle_options = {
496 center: point_center,
497 radius: markerRadius,
498 map: map,
499 fillOpacity: point_opacity,
500 fillColor: mapColor,
501 clickable: true
502 };
503 var map_circle = new google.maps.Circle(map_circle_options);
504 map_circle.val = mapPlotData[m];
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700505 map_circle.index = m;
506
507 var iF = new google.maps.InfoWindow({
508 content : mapPlotData[m].weight + " Tweets",
509 position : point_center
510 });
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700511
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700512 // Clicking on a circle drills down map to that value, hovering over it displays a count
513 // of tweets at that location.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700514 google.maps.event.addListener(map_circle, 'click', function (event) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700515 onMapPointDrillDown(map_circle.val);
516 });
517
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700518 google.maps.event.addListener(map_circle, 'mouseover', function (event) {
519 mapper[map_circle.index].open(map, map_circle);
520 });
521
522 google.maps.event.addListener(map_circle, 'mouseout', function (event) {
523 mapper[map_circle.index].close();
524 });
525
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700526 // Add this marker to global marker cells
527 map_cells.push(map_circle);
528 }
529 });
530
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700531 // TODO Remove widget for now mapControlWidgetAddLegend(dataBreakpoints);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700532}
533
534/**
535* prepares an Asterix API query to drill down in a rectangular spatial zone
536*
537* @params {object} marker_borders [LEGACY] a set of bounds for a region from a previous api result
538*/
539function onMapPointDrillDown(marker_borders) {
540 var zoneData = APIqueryTracker["data"];
541
542 var zswBounds = new google.maps.LatLng(marker_borders.latSW, marker_borders.lngSW);
543 var zneBounds = new google.maps.LatLng(marker_borders.latNE, marker_borders.lngNE);
544
545 var zoneBounds = new google.maps.LatLngBounds(zswBounds, zneBounds);
546 zoneData["swLat"] = zoneBounds.getSouthWest().lat();
547 zoneData["swLng"] = zoneBounds.getSouthWest().lng();
548 zoneData["neLat"] = zoneBounds.getNorthEast().lat();
549 zoneData["neLng"] = zoneBounds.getNorthEast().lng();
550 var zB = {
551 "sw" : {
552 "lat" : zoneBounds.getSouthWest().lat(),
553 "lng" : zoneBounds.getSouthWest().lng()
554 },
555 "ne" : {
556 "lat" : zoneBounds.getNorthEast().lat(),
557 "lng" : zoneBounds.getNorthEast().lng()
558 }
559 };
560
561 mapWidgetClearMap();
562
563 var customBounds = new google.maps.LatLngBounds();
564 var zoomSWBounds = new google.maps.LatLng(zoneData["swLat"], zoneData["swLng"]);
565 var zoomNEBounds = new google.maps.LatLng(zoneData["neLat"], zoneData["neLng"]);
566 customBounds.extend(zoomSWBounds);
567 customBounds.extend(zoomNEBounds);
568 map.fitBounds(customBounds);
569
570 var df = getDrillDownQuery(zoneData, zB);
571
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700572 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700573 "query_string" : "use dataverse twitter;\n" + df.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700574 "marker_path" : "static/img/mobile2.png",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700575 "on_clean_result" : onCleanTweetbookDrilldown,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700576 };
577
578 A.query(df.val(), onTweetbookQuerySuccessPlot);
579}
580
581function getDrillDownQuery(parameters, bounds) {
582
583 var zoomRectangle = new FunctionExpression("create-rectangle",
584 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
585 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
586
587 var drillDown = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700588 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700589 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
590 .LetClause("$region", zoomRectangle)
591 .WhereClause().and(
592 new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
593 new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
594 new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")'),
595 new FunctionExpression('contains', '$t.message-text', '$keyword')
596 )
597 .ReturnClause({
598 "tweetId" : "$t.tweetid",
599 "tweetText" : "$t.message-text",
600 "tweetLoc" : "$t.sender-location"
601 });
602
603 return drillDown;
604}
605
606
607function addTweetbookCommentDropdown(appendToDiv) {
608
609 // Creates a div to manage a radio button set of chosen tweetbooks
610 $('<div/>')
611 .attr("class","btn-group chosen-tweetbooks")
612 .attr("data-toggle", "buttons-radio")
613 .css("margin-bottom", "10px")
614 .attr("id", "metacomment-tweetbooks")
615 .appendTo(appendToDiv);
616
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700617 var highlighted = "";
618 if (APIqueryTracker.hasOwnProperty("active_tweetbook")) {
619 highlighted = APIqueryTracker["active_tweetbook"];
620 }
621
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700622 // For each existing tweetbook from review mode, adds a radio button option.
623 $('#metacomment-tweetbooks').append('<input type="hidden" id="target-tweetbook" value="" />');
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700624 for (var rmt in review_mode_tweetbooks) {
625
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700626 var tweetbook_option = '<button type="button" class="btn">' + review_mode_tweetbooks[rmt] + '</button>';
627
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700628 if (review_mode_tweetbooks[rmt] == highlighted) {
629 tweetbook_option = '<button type="button" class="btn btn-info">' + review_mode_tweetbooks[rmt] + '</button>';
630 }
631
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700632 $('#metacomment-tweetbooks').append(tweetbook_option + '<br/>');
633 }
634
635 // Creates a button + input combination to add tweet comment to new tweetbook
636 var new_tweetbook_option = '<button type="button" class="btn" id="new-tweetbook-target-m"></button>' +
637 '<input type="text" id="new-tweetbook-entry-m" placeholder="Add to new tweetbook..."><br/>';
638 $('#metacomment-tweetbooks').append(new_tweetbook_option);
639
640 $("#new-tweetbook-entry-m").keyup(function() {
641 $("#new-tweetbook-target-m").val($("#new-tweetbook-entry-m").val());
642 $("#new-tweetbook-target-m").text($("#new-tweetbook-entry-m").val());
643 });
644
645 // There is a hidden input (id = target-tweetbook) which is used to track the value
646 // of the tweetbook to which the comment on this tweet will be added.
647 $(".chosen-tweetbooks .btn").click(function() {
648 $("#target-tweetbook").val($(this).text());
649 });
650}
651
652function onDrillDownAtLocation(tO) {
653
654 var tweetId = tO["tweetEntryId"];
655 var tweetText = tO["tweetText"];
656
657 var tweetContainerId = '#drilldown_modal_body';
658 var tweetDiv = '<div id="drilltweetobj' + tweetId + '"></div>';
659
660 $(tweetContainerId).empty();
661 $(tweetContainerId).append(tweetDiv);
662 $('#drilltweetobj' + tweetId).append('<p>Tweet #' + tweetId + ": " + tweetText + '</p>');
663
664 // Add comment field
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700665 $('#drilltweetobj' + tweetId).append('<input class="textbox" type="text" id="metacomment' + tweetId + '">');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700666
667 if (tO.hasOwnProperty("tweetComment")) {
668 $("#metacomment" + tweetId).val(tO["tweetComment"]);
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700669
670 var deleteThisComment = addDeleteButton(
671 "deleteLiveComment_" + tweetId,
672 "drilltweetobj" + tweetId,
673 function () {
674
675 // TODO Maybe this should fire differnetly if another tweetbook is selected?
676
677 // Send comment deletion to asterix
678 var deleteTweetCommentOnId = '"' + tweetId + '"';
679 var toDelete = new DeleteStatement(
680 "$mt",
681 APIqueryTracker["active_tweetbook"],
682 new AExpression("$mt.tweetid = " + deleteTweetCommentOnId.toString())
683 );
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700684 A.update(
685 toDelete.val()
686 );
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700687
688 // Hide comment from map
689 $('#drilldown_modal').modal('hide');
690
691 // Replot tweetbook
692 onPlotTweetbook(APIqueryTracker["active_tweetbook"]);
693 }
694 );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700695 }
696
697 addTweetbookCommentDropdown('#drilltweetobj' + tweetId);
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700698
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700699 $('#drilltweetobj' + tweetId).append('<br/><button type="button" class="btn" id="add-metacomment">Save Comment</button>');
700
701 $('#add-metacomment').button().click(function () {
702 var save_metacomment_target_tweetbook = $("#target-tweetbook").val();
703 var save_metacomment_target_comment = '"' + $("#metacomment" + tweetId).val() + '"';
704 var save_metacomment_target_tweet = '"' + tweetId + '"';
705
706 if (save_metacomment_target_tweetbook.length == 0) {
707 alert("Please choose a tweetbook.");
708
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700709 } else {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700710
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700711 if (!(existsTweetbook(save_metacomment_target_tweetbook))) {
712 onCreateNewTweetBook(save_metacomment_target_tweetbook);
713 }
714
715 var toDelete = new DeleteStatement(
716 "$mt",
717 save_metacomment_target_tweetbook,
718 new AExpression("$mt.tweetid = " + save_metacomment_target_tweet.toString())
719 );
720
721 A.update(toDelete.val());
722
723 var toInsert = new InsertStatement(
724 save_metacomment_target_tweetbook,
725 {
726 "tweetid" : save_metacomment_target_tweet.toString(),
727 "comment-text" : save_metacomment_target_comment
728 }
729 );
730
731 // Insert query to add metacomment to said tweetbook dataset
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700732 A.update(toInsert.val(), function () { alert("Test"); });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700733
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700734 // TODO Some stress testing of error conditions might be good here...
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700735 if (APIqueryTracker.hasOwnProperty("active_tweetbook")) {
736 onPlotTweetbook(APIqueryTracker["active_tweetbook"]);
737 };
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700738 var successMessage = "Saved comment on <b>Tweet #" + tweetId +
739 "</b> in dataset <b>" + save_metacomment_target_tweetbook + "</b>.";
740 addSuccessBlock(successMessage, 'drilltweetobj' + tweetId);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700741 }
742 });
743
744 // Set width of tweetbook buttons
745 $(".chosen-tweetbooks .btn").css("width", "200px");
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700746 $(".chosen-tweetbooks .btn").css("height", "2em");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700747}
748
749
750/**
751* Adds a new tweetbook entry to the menu and creates a dataset of type TweetbookEntry.
752*/
753function onCreateNewTweetBook(tweetbook_title) {
754
755 var tweetbook_title = tweetbook_title.split(' ').join('_');
756
757 A.ddl(
758 "create dataset " + tweetbook_title + "(TweetbookEntry) primary key tweetid;",
759 function () {}
760 );
761
762 if (!(existsTweetbook(tweetbook_title))) {
763 review_mode_tweetbooks.push(tweetbook_title);
764 addTweetBookDropdownItem(tweetbook_title);
765 }
766}
767
768
769function onDropTweetBook(tweetbook_title) {
770
771 // AQL Call
772 A.ddl(
773 "drop dataset " + tweetbook_title + " if exists;",
774 function () {}
775 );
776
777 // Removes tweetbook from review_mode_tweetbooks
778 var remove_position = $.inArray(tweetbook_title, review_mode_tweetbooks);
779 if (remove_position >= 0) review_mode_tweetbooks.splice(remove_position, 1);
780
781 // Clear UI with review tweetbook titles
782 $('#review-tweetbook-titles').html('');
783 for (r in review_mode_tweetbooks) {
784 addTweetBookDropdownItem(review_mode_tweetbooks[r]);
785 }
786}
787
788
789function addTweetBookDropdownItem(tweetbook) {
790 // Add placeholder for this tweetbook
791 $('<div/>')
792 .css("padding-left", "1em")
793 .attr({
794 "class" : "btn-group",
795 "id" : "rm_holder_" + tweetbook
796 }).appendTo("#review-tweetbook-titles");
797 $("#review-tweetbook-titles").append('<br/>');
798
799 // Add plotting button for this tweetbook
800 var plot_button = '<button class="btn" id="rm_plotbook_' + tweetbook + '">' + tweetbook + '</button>';
801 $("#rm_holder_" + tweetbook).append(plot_button);
802 $("#rm_plotbook_" + tweetbook).on('click', function(e) {
803 onPlotTweetbook(tweetbook);
804 });
805
806 // Add trash button for this tweetbook
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700807 var onTrashTweetbookButton = addDeleteButton(
808 "rm_trashbook_" + tweetbook,
809 "rm_holder_" + tweetbook,
810 function(e) {
811 onDropTweetBook(tweetbook);
812 }
813 );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700814}
815
816
817function onPlotTweetbook(tweetbook) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700818
819 // Clear map for this one
820 mapWidgetResetMap();
821
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700822 var plotTweetQuery = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700823 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700824 .ForClause("$m", new AExpression("dataset " + tweetbook))
825 .WhereClause(new AExpression("$m.tweetid = $t.tweetid"))
826 .ReturnClause({
827 "tweetId" : "$m.tweetid",
828 "tweetText" : "$t.message-text",
829 "tweetLoc" : "$t.sender-location",
830 "tweetCom" : "$m.comment-text"
831 });
832
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700833 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700834 "query_string" : "use dataverse twitter;\n" + plotTweetQuery.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700835 "marker_path" : "static/img/mobile_green2.png",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700836 "on_clean_result" : onCleanPlotTweetbook,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700837 "active_tweetbook" : tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700838 };
839
840 A.query(plotTweetQuery.val(), onTweetbookQuerySuccessPlot);
841}
842
843
844function onTweetbookQuerySuccessPlot (res) {
845
846 var records = res["results"];
847
848 var coordinates = [];
849 map_tweet_markers = [];
850 map_tweet_overlays = [];
851 drilldown_data_map = {};
852 drilldown_data_map_vals = {};
853
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700854 var micon = APIqueryTracker["marker_path"];
855 var marker_click_function = onClickTweetbookMapMarker;
856 var clean_result_function = APIqueryTracker["on_clean_result"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700857
858 coordinates = clean_result_function(records);
859
860 for (var dm in coordinates) {
861 var keyLat = coordinates[dm].tweetLat.toString();
862 var keyLng = coordinates[dm].tweetLng.toString();
863 if (!drilldown_data_map.hasOwnProperty(keyLat)) {
864 drilldown_data_map[keyLat] = {};
865 }
866 if (!drilldown_data_map[keyLat].hasOwnProperty(keyLng)) {
867 drilldown_data_map[keyLat][keyLng] = [];
868 }
869 drilldown_data_map[keyLat][keyLng].push(coordinates[dm]);
870 drilldown_data_map_vals[coordinates[dm].tweetEntryId.toString()] = coordinates[dm];
871 }
872
873 $.each(drilldown_data_map, function(drillKeyLat, valuesAtLat) {
874 $.each(drilldown_data_map[drillKeyLat], function (drillKeyLng, valueAtLng) {
875
876 // Get subset of drilldown position on map
877 var cposition = new google.maps.LatLng(parseFloat(drillKeyLat), parseFloat(drillKeyLng));
878
879 // Create a marker using the snazzy phone icon
880 var map_tweet_m = new google.maps.Marker({
881 position: cposition,
882 map: map,
883 icon: micon,
884 clickable: true,
885 });
886
887 // Open Tweet exploration window on click
888 google.maps.event.addListener(map_tweet_m, 'click', function (event) {
889 marker_click_function(drilldown_data_map[drillKeyLat][drillKeyLng]);
890 });
891
892 // Add marker to index of tweets
893 map_tweet_markers.push(map_tweet_m);
894
895 });
896 });
897}
898
899
900function existsTweetbook(tweetbook) {
901 if (parseInt($.inArray(tweetbook, review_mode_tweetbooks)) == -1) {
902 return false;
903 } else {
904 return true;
905 }
906}
907
908
909function onCleanPlotTweetbook(records) {
910 var toPlot = [];
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700911
912 // An entry looks like this:
913 // { "tweetId": "273589", "tweetText": " like verizon the network is amazing", "tweetLoc": { point: [37.78, 82.27]}, "tweetCom": "hooray comments" }
914
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700915 for (var entry in records) {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700916
917 var points = records[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
918
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700919 var tweetbook_element = {
920 "tweetEntryId" : parseInt(records[entry].split(",")[0].split(":")[1].split('"')[1]),
921 "tweetText" : records[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700922 "tweetLat" : parseFloat(points[0]),
923 "tweetLng" : parseFloat(points[1]),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700924 "tweetComment" : records[entry].split("tweetCom\": \"")[1].split("\"")[0]
925 };
926 toPlot.push(tweetbook_element);
927 }
928
929 return toPlot;
930}
931
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700932
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700933function onCleanTweetbookDrilldown (rec) {
934
935 var drilldown_cleaned = [];
936
937 for (var entry = 0; entry < rec.length; entry++) {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700938
939 // An entry looks like this:
940 // { "tweetId": "105491", "tweetText": " hate verizon its platform is OMG", "tweetLoc": { point: [30.55, 71.44]} }
941 var points = rec[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
942
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700943 var drill_element = {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700944 "tweetEntryId" : parseInt(rec[entry].split(",")[0].split(":")[1].replace('"', '')),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700945 "tweetText" : rec[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700946 "tweetLat" : parseFloat(points[0]),
947 "tweetLng" : parseFloat(points[1])
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700948 };
949 drilldown_cleaned.push(drill_element);
950 }
951 return drilldown_cleaned;
952}
953
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700954
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700955function onClickTweetbookMapMarker(tweet_arr) {
956 $('#drilldown_modal_body').html('');
957
958 // Clear existing display
959 $.each(tweet_arr, function (t, valueT) {
960 var tweet_obj = tweet_arr[t];
961 onDrillDownAtLocation(tweet_obj);
962 });
963
964 $('#drilldown_modal').modal('show');
965}
966
967/** Toggling Review and Explore Modes **/
968
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700969
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700970/**
971* Explore mode: Initial map creation and screen alignment
972*/
973function onOpenExploreMap () {
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700974 var explore_column_height = $('#explore-well').height();
975 var right_column_width = $('#right-col').width();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700976 $('#map_canvas').height(explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700977 $('#map_canvas').width(right_column_width + "px");
978
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700979 $('#review-well').height(explore_column_height + "px");
980 $('#review-well').css('max-height', explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700981
982 $('#right-col').height(explore_column_height + "px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700983}
984
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700985
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700986/**
987* Launching explore mode: clear windows/variables, show correct sidebar
988*/
989function onLaunchExploreMode() {
990 $('#review-active').removeClass('active');
991 $('#review-well').hide();
992
993 $('#explore-active').addClass('active');
994 $('#explore-well').show();
995
996 $("#clear-button").trigger("click");
997}
998
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700999
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001000/**
1001* Launching review mode: clear windows/variables, show correct sidebar
1002*/
1003function onLaunchReviewMode() {
1004 $('#explore-active').removeClass('active');
1005 $('#explore-well').hide();
1006 $('#review-active').addClass('active');
1007 $('#review-well').show();
1008
1009 $("#clear-button").trigger("click");
1010}
1011
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001012
1013/** Icon / Interface Utility Methods **/
1014
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001015/**
1016* Creates a delete icon button using default trash icon
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001017* @param {String} id, id for this element
1018* @param {String} attachTo, id string of an element to which I can attach this button.
1019* @param {Function} onClick, a function to fire when this icon is clicked
1020*/
1021function addDeleteButton(iconId, attachTo, onClick) {
1022 // Icon structure
1023 var trashIcon = '<button class="btn" id="' + iconId + '"><i class="icon-trash"></i></button>';
1024
1025 $('#' + attachTo).append(trashIcon);
1026
1027 // On Click behavior
1028 $('#' + iconId).on('click', onClick);
1029}
1030
1031
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001032/**
1033* Creates a success message and attaches it to a div with provided ID.
1034* @param {String} message, a message to post
1035* @param {String} appendTarget, a target div to which to append the alert
1036*/
1037function addSuccessBlock(message, appendTarget) {
1038
1039 $('<div/>')
1040 .attr("class", "alert alert-success")
1041 .html('<button type="button" class="close" data-dismiss="alert">&times;</button>' + message)
1042 .appendTo('#' + appendTarget);
1043}
1044
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001045/** Map Widget Utility Methods **/
1046
1047/**
1048* Plots a legend onto the map, with values in progress bars
1049* @param {number Array} breakpoints, an array of numbers representing natural breakpoints
1050*/
1051function mapControlWidgetAddLegend(breakpoints) {
1052
1053 // Retriever colors, lightest to darkest
1054 var colors = mapWidgetGetColorPalette();
1055
1056 // Initial div structure
1057 $("#map_canvas_legend").html('<div id="legend-holder"><div id="legend-progress-bar" class="progress"></div><span id="legend-label"></span></div>');
1058
1059 // Add color scale to legend
1060 $('#legend-progress-bar').css("width", "200px").html('');
1061
1062 // Add a progress bar for each color
1063 for (var color in colors) {
1064
1065 // Bar values
1066 var upperBound = breakpoints[parseInt(color) + 1];
1067
1068 // Create Progress Bar
1069 $('<div/>')
1070 .attr("class", "bar")
1071 .attr("id", "pbar" + color)
1072 .css("width" , '25.0%')
1073 .html("< " + upperBound)
1074 .appendTo('#legend-progress-bar');
1075
1076 $('#pbar' + color).css({
1077 "background-image" : 'none',
1078 "background-color" : colors[parseInt(color)]
1079 });
1080
1081 // Attach a message showing minimum bounds
1082 $('#legend-label').html('Regions with at least ' + breakpoints[0] + ' tweets');
1083 $('#legend-label').css({
1084 "margin-top" : 0,
1085 "color" : "black"
1086 });
1087 }
1088
1089 // Add legend to map
1090 map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(document.getElementById('legend-holder'));
1091 $('#map_canvas_legend').show();
1092}
1093
1094/**
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001095* Clears ALL map elements - legend, plotted items, overlays
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001096*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001097function mapWidgetResetMap() {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001098
1099 if (selectionRect) {
1100 selectionRect.setMap(null);
1101 selectionRect = null;
1102 }
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001103
1104 mapWidgetClearMap();
1105
1106 // Reset map center and zoom
1107 map.setCenter(new google.maps.LatLng(38.89, -77.03));
1108 map.setZoom(4);
1109}
1110
1111function mapWidgetClearMap() {
1112
1113 // Remove previously plotted data/markers
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001114 for (c in map_cells) {
1115 map_cells[c].setMap(null);
1116 }
1117 map_cells = [];
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -07001118 countInfoWindow = null;
1119 count_info = [];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001120 for (m in map_tweet_markers) {
1121 map_tweet_markers[m].setMap(null);
1122 }
1123 map_tweet_markers = [];
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001124
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001125 // Remove legend from map
1126 map.controls[google.maps.ControlPosition.LEFT_BOTTOM].clear();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001127}
1128
1129/**
1130* Uses jenks algorithm in geostats library to find natural breaks in numeric data
1131* @param {number Array} weights of points to plot
1132* @returns {number Array} array of natural breakpoints, of which the top 4 subsets will be plotted
1133*/
1134function mapWidgetLegendComputeNaturalBreaks(weights) {
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001135
1136 if (weights.length < 10) {
1137 return [0];
1138 }
1139
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001140 var plotDataWeights = new geostats(weights.sort());
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001141 return plotDataWeights.getJenks(6).slice(2,7);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001142}
1143
1144/**
1145* Computes values for map legend given a value and an array of jenks breakpoints
1146* @param {number} weight of point to plot on map
1147* @param {number Array} breakpoints, an array of 5 points corresponding to bounds of 4 natural ranges
1148* @returns {String} an RGB value corresponding to a subset of data
1149*/
1150function mapWidgetLegendGetHeatValue(weight, breakpoints) {
1151
1152 // Determine into which range the weight falls
1153 var weightColor = 0;
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001154
1155 if (breakpoints.length == 1) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001156 weightColor = 2;
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001157 } else {
1158 if (weight >= breakpoints[3]) {
1159 weightColor = 3;
1160 } else if (weight >= breakpoints[2]) {
1161 weightColor = 2;
1162 } else if (weight >= breakpoints[1]) {
1163 weightColor = 1;
1164 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001165 }
1166
1167 // Get default map color palette
1168 var colorValues = mapWidgetGetColorPalette();
1169 return colorValues[weightColor];
1170}
1171
1172/**
1173* Returns an array containing a 4-color palette, lightest to darkest
1174* External palette source: http://www.colourlovers.com/palette/2763366/s_i_l_e_n_c_e_r
1175* @returns {Array} [colors]
1176*/
1177function mapWidgetGetColorPalette() {
1178 return [
1179 "rgb(115,189,158)",
1180 "rgb(74,142,145)",
1181 "rgb(19,93,96)",
1182 "rgb(7,51,46)"
1183 ];
1184}
1185
1186/**
1187* Computes radius for a given data point from a spatial cell
1188* @param {Object} keys => ["latSW" "lngSW" "latNE" "lngNE" "weight"]
1189* @returns {number} radius between 2 points in metres
1190*/
1191function mapWidgetComputeCircleRadius(spatialCell, breakpoints) {
1192
1193 var weight = spatialCell.weight;
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001194
1195 if (breakpoints.length == 1) {
1196 var weightColor = 0.25;
1197 } else {
1198 // Compute weight color
1199 var weightColor = 0.25;
1200 if (weight >= breakpoints[3]) {
1201 weightColor = 1.0;
1202 } else if (weight >= breakpoints[2]) {
1203 weightColor = 0.75;
1204 } else if (weight >= breakpoints[1]) {
1205 weightColor = 0.5;
1206 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001207 }
1208
1209 // Define Boundary Points
1210 var point_center = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1211 var point_left = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, spatialCell.lngSW);
1212 var point_top = new google.maps.LatLng(spatialCell.latNE, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1213
1214 // TODO not actually a weight color :)
1215 return weightColor * 1000 * Math.min(distanceBetweenPoints_(point_center, point_left), distanceBetweenPoints_(point_center, point_top));
1216}
1217
1218/** External Utility Methods **/
1219
1220/**
1221 * Calculates the distance between two latlng locations in km.
1222 * @see http://www.movable-type.co.uk/scripts/latlong.html
1223 *
1224 * @param {google.maps.LatLng} p1 The first lat lng point.
1225 * @param {google.maps.LatLng} p2 The second lat lng point.
1226 * @return {number} The distance between the two points in km.
1227 * @private
1228*/
1229function distanceBetweenPoints_(p1, p2) {
1230 if (!p1 || !p2) {
1231 return 0;
1232 }
1233
1234 var R = 6371; // Radius of the Earth in km
1235 var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
1236 var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
1237 var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
1238 Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
1239 Math.sin(dLon / 2) * Math.sin(dLon / 2);
1240 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1241 var d = R * c;
1242 return d;
1243};