blob: 6a860758bca59b563b28953e7cf8fcfb42da6d71 [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 = [];
17 map_tweet_markers = [];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -070018 map_info_windows = {};
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070019
20 // UI Elements - Modals & perspective tabs
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -070021 $('#drilldown_modal').modal('hide');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070022 $('#explore-mode').click( onLaunchExploreMode );
23 $('#review-mode').click( onLaunchReviewMode );
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -070024 $('#about-mode').click(onLaunchAboutMode);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070025
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
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700189 $('#new-tweetbook-entry').val("");
190 $('#new-tweetbook-entry').attr("placeholder", "Name a new tweetbook");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700191 });
192
193 // UI Element - Query Submission
194 $("#submit-button").button().click(function () {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700195
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700196 var kwterm = $("#keyword-textbox").val();
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700197 if (kwterm == "") {
198 alert("Please enter a search term!");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700199 } else {
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700200
201 $("#submit-button").attr("disabled", true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700202
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700203 var startdp = $("#start-date").datepicker("getDate");
204 var enddp = $("#end-date").datepicker("getDate");
205 var startdt = $.datepicker.formatDate("yy-mm-dd", startdp)+"T00:00:00Z";
206 var enddt = $.datepicker.formatDate("yy-mm-dd", enddp)+"T23:59:59Z";
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700207
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700208 var formData = {
209 "keyword": kwterm,
210 "startdt": startdt,
211 "enddt": enddt,
212 "gridlat": $("#grid-lat-slider").slider("value"),
213 "gridlng": $("#grid-lng-slider").slider("value")
214 };
215
216 // Get Map Bounds
217 var bounds;
218 if ($('#selection-button').hasClass("active") && selectionRect) {
219 bounds = selectionRect.getBounds();
220 } else {
221 bounds = map.getBounds();
222 }
223
224 var swLat = Math.abs(bounds.getSouthWest().lat());
225 var swLng = Math.abs(bounds.getSouthWest().lng());
226 var neLat = Math.abs(bounds.getNorthEast().lat());
227 var neLng = Math.abs(bounds.getNorthEast().lng());
228
229 formData["swLat"] = Math.min(swLat, neLat);
230 formData["swLng"] = Math.max(swLng, neLng);
231 formData["neLat"] = Math.max(swLat, neLat);
232 formData["neLng"] = Math.min(swLng, neLng);
233
234 var build_cherry_mode = "synchronous";
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700235
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700236 if ($('#asbox').is(":checked")) {
237 build_cherry_mode = "asynchronous";
238 $('#show-query-button').attr("disabled", false);
239 } else {
240 $('#show-query-button').attr("disabled", true);
241 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700242
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700243 var f = buildAQLQueryFromForm(formData);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700244
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700245 APIqueryTracker = {
246 "query" : "use dataverse twitter;\n" + f.val(),
247 "data" : formData
248 };
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700249
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700250 $('#dialog').html(APIqueryTracker["query"]);
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700251
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700252 if (build_cherry_mode == "synchronous") {
253 A.query(f.val(), cherryQuerySyncCallback, build_cherry_mode);
254 } else {
255 A.query(f.val(), cherryQueryAsyncCallback, build_cherry_mode);
256 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700257
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700258 // Clears selection rectangle on query execution, rather than waiting for another clear call.
259 if (selectionRect) {
260 selectionRect.setMap(null);
261 selectionRect = null;
262 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700263 }
264 });
265});
266
267
268function buildAQLQueryFromForm(parameters) {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700269
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700270 var bounds = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700271 "ne" : { "lat" : parameters["neLat"], "lng" : -1*parameters["neLng"]},
272 "sw" : { "lat" : parameters["swLat"], "lng" : -1*parameters["swLng"]}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700273 };
274
275 var rectangle =
276 new FunctionExpression("create-rectangle",
277 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
278 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
279
280
281 var aql = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700282 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700283 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
284 .LetClause("$region", rectangle)
285 .WhereClause().and(
286 new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
287 new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
288 new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")'),
289 new FunctionExpression("contains", "$t.message-text", "$keyword")
290 )
291 .GroupClause(
292 "$c",
293 new FunctionExpression("spatial-cell", "$t.sender-location",
294 new FunctionExpression("create-point", "24.5", "-125.5"),
295 parameters["gridlat"].toFixed(1), parameters["gridlng"].toFixed(1)),
296 "with",
297 "$t"
298 )
299 .ReturnClause({ "cell" : "$c", "count" : "count($t)" });
300
301 return aql;
302}
303
304/** Asynchronous Query Management **/
305
306
307/**
308* Checks through each asynchronous query to see if they are ready yet
309*/
310function asynchronousQueryIntervalUpdate() {
311 for (var handle_key in asyncQueryManager) {
312 if (!asyncQueryManager[handle_key].hasOwnProperty("ready")) {
313 asynchronousQueryGetAPIQueryStatus( asyncQueryManager[handle_key]["handle"], handle_key );
314 }
315 }
316}
317
318
319/**
320* Returns current time interval to check for asynchronous query readiness
321* @returns {number} milliseconds between asychronous query checks
322*/
323function asynchronousQueryGetInterval() {
324 var seconds = 10;
325 return seconds * 1000;
326}
327
328
329/**
330* Retrieves status of an asynchronous query, using an opaque result handle from API
331* @param {Object} handle, an object previously returned from an async call
332* @param {number} handle_id, the integer ID parsed from the handle object
333*/
334function asynchronousQueryGetAPIQueryStatus (handle, handle_id) {
335
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700336 A.query_status(
337 {
338 "handle" : JSON.stringify(handle)
339 },
340 function (res) {
341 if (res["status"] == "SUCCESS") {
342 // We don't need to check if this one is ready again, it's not going anywhere...
343 // Unless the life cycle of handles has changed drastically
344 asyncQueryManager[handle_id]["ready"] = true;
345
346 // Indicate success.
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700347 $('#handle_' + handle_id).removeClass("btn-disabled").prop('disabled', false).addClass("btn-success");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700348 }
349 }
350 );
351}
352
353
354/**
355* On-success callback after async API query
356* @param {object} res, a result object containing an opaque result handle to Asterix
357*/
358function cherryQueryAsyncCallback(res) {
359
360 // Parse handle, handle id and query from async call result
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700361 var handle_query = APIqueryTracker["query"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700362 var handle = res;
363 var handle_id = res["handle"].toString().split(',')[0];
364
365 // Add to stored map of existing handles
366 asyncQueryManager[handle_id] = {
367 "handle" : handle,
368 "query" : handle_query,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700369 "data" : APIqueryTracker["data"]
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700370 };
371
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700372 // Create a container for this async query handle
373 $('<div/>')
374 .css("margin-left", "1em")
375 .css("margin-bottom", "1em")
376 .css("display", "block")
377 .attr({
378 "class" : "btn-group",
379 "id" : "async_container_" + handle_id
380 })
381 .appendTo("#async-handle-controls");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700382
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700383 // Adds the main button for this async handle
384 var handle_action_button = '<button class="btn btn-disabled" id="handle_' + handle_id + '">Handle ' + handle_id + '</button>';
385 $('#async_container_' + handle_id).append(handle_action_button);
386 $('#handle_' + handle_id).prop('disabled', true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700387 $('#handle_' + handle_id).on('click', function (e) {
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700388
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700389 // make sure query is ready to be run
390 if (asyncQueryManager[handle_id]["ready"]) {
391
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700392 APIqueryTracker = {
393 "query" : asyncQueryManager[handle_id]["query"],
394 "data" : asyncQueryManager[handle_id]["data"]
395 };
396 $('#dialog').html(APIqueryTracker["query"]);
397
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700398 if (!asyncQueryManager[handle_id].hasOwnProperty("result")) {
399 // Generate new Asterix Core API Query
400 A.query_result(
401 { "handle" : JSON.stringify(asyncQueryManager[handle_id]["handle"]) },
402 function(res) {
403 asyncQueryManager[handle_id]["result"] = res;
404 cherryQuerySyncCallback(res);
405 }
406 );
407 } else {
408 cherryQuerySyncCallback(asyncQueryManager[handle_id]["result"]);
409 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700410 }
411 });
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700412
413 // Adds a removal button for this async handle
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700414 var asyncDeleteButton = addDeleteButton(
415 "trashhandle_" + handle_id,
416 "async_container_" + handle_id,
417 function (e) {
418 $('#async_container_' + handle_id).remove();
419 delete asyncQueryManager[handle_id];
420 }
421 );
422 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700423}
424
425
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700426/**
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700427* returns a json object with keys: weight, latSW, lngSW, latNE, lngNE
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700428*
429* { "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 -0700430*/
431function getRecord(cell_count_record) {
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700432 // This is a really hacky way to pull out the digits, but it works for now.
433 var values = cell_count_record.replace("int64","").match(/[-+]?[0-9]*\.?[0-9]+/g);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700434 var record_representation = {};
435
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700436 record_representation["latSW"] = parseFloat(values[0]);
437 record_representation["lngSW"] = parseFloat(values[1]);
438 record_representation["latNE"] = parseFloat(values[2]);
439 record_representation["lngNE"] = parseFloat(values[3]);
440 record_representation["weight"] = parseInt(values[4]);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700441
442 return record_representation;
443}
444
445/**
446* A spatial data cleaning and mapping call
447* @param {Object} res, a result object from a cherry geospatial query
448*/
449function cherryQuerySyncCallback(res) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700450 records = res["results"];
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700451
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700452 if (typeof res["results"][0] == "object") {
453 records = res["results"][0];
454 }
455
456 var coordinates = [];
457 var weights = [];
458
459 for (var subrecord in records) {
460 var coordinate = getRecord(records[subrecord]);
461 weights.push(coordinate["weight"]);
462 coordinates.push(coordinate);
463 }
464
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700465 triggerUIUpdate(coordinates, weights);
466 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700467}
468
469/**
470* Triggers a map update based on a set of spatial query result cells
471* @param [Array] mapPlotData, an array of coordinate and weight objects
472* @param [Array] params, an object containing original query parameters [LEGACY]
473* @param [Array] plotWeights, a list of weights of the spatial cells - e.g., number of tweets
474*/
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700475function triggerUIUpdate(mapPlotData, plotWeights) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700476 /** Clear anything currently on the map **/
477 mapWidgetClearMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700478
479 // Compute data point spread
480 var dataBreakpoints = mapWidgetLegendComputeNaturalBreaks(plotWeights);
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700481
482 map_info_windows = {};
483
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700484 $.each(mapPlotData, function (m, val) {
485
486 // Only map points in data range of top 4 natural breaks
487 if (mapPlotData[m].weight > dataBreakpoints[0]) {
488
489 // Get color value of legend
490 var mapColor = mapWidgetLegendGetHeatValue(mapPlotData[m].weight, dataBreakpoints);
491 var markerRadius = mapWidgetComputeCircleRadius(mapPlotData[m], dataBreakpoints);
492 var point_opacity = 1.0;
493
494 var point_center = new google.maps.LatLng(
495 (mapPlotData[m].latSW + mapPlotData[m].latNE)/2.0,
496 (mapPlotData[m].lngSW + mapPlotData[m].lngNE)/2.0);
497
498 // Create and plot marker
499 var map_circle_options = {
500 center: point_center,
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700501 anchorPoint: point_center,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700502 radius: markerRadius,
503 map: map,
504 fillOpacity: point_opacity,
505 fillColor: mapColor,
506 clickable: true
507 };
508 var map_circle = new google.maps.Circle(map_circle_options);
509 map_circle.val = mapPlotData[m];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700510 map_circle.ind = m;
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700511
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700512 map_info_windows[m] = new google.maps.InfoWindow({
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700513 content: mapPlotData[m].weight + "",
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700514 position: point_center
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700515 });
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700516
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700517 // Clicking on a circle drills down map to that value, hovering over it displays a count
518 // of tweets at that location.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700519 google.maps.event.addListener(map_circle, 'click', function (event) {
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700520 map_info_windows[m].close();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700521 onMapPointDrillDown(map_circle.val);
522 });
523
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700524 google.maps.event.addListener(map_circle, 'mouseover', function(event) {
525 if (!map_info_windows[m].getMap()) {
526 map_info_windows[m].setPosition(map_circle.center);
527 map_info_windows[m].open(map);
528 }
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700529 });
530
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700531 google.maps.event.addListener(map_circle, 'mouseout', function(event) {
532 map_info_windows[m].close();
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700533 });
534
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700535 // Add this marker to global marker cells
536 map_cells.push(map_circle);
537 }
538 });
539
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700540 // Remove widget for now mapControlWidgetAddLegend(dataBreakpoints);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700541}
542
543/**
544* prepares an Asterix API query to drill down in a rectangular spatial zone
545*
546* @params {object} marker_borders [LEGACY] a set of bounds for a region from a previous api result
547*/
548function onMapPointDrillDown(marker_borders) {
549 var zoneData = APIqueryTracker["data"];
550
551 var zswBounds = new google.maps.LatLng(marker_borders.latSW, marker_borders.lngSW);
552 var zneBounds = new google.maps.LatLng(marker_borders.latNE, marker_borders.lngNE);
553
554 var zoneBounds = new google.maps.LatLngBounds(zswBounds, zneBounds);
555 zoneData["swLat"] = zoneBounds.getSouthWest().lat();
556 zoneData["swLng"] = zoneBounds.getSouthWest().lng();
557 zoneData["neLat"] = zoneBounds.getNorthEast().lat();
558 zoneData["neLng"] = zoneBounds.getNorthEast().lng();
559 var zB = {
560 "sw" : {
561 "lat" : zoneBounds.getSouthWest().lat(),
562 "lng" : zoneBounds.getSouthWest().lng()
563 },
564 "ne" : {
565 "lat" : zoneBounds.getNorthEast().lat(),
566 "lng" : zoneBounds.getNorthEast().lng()
567 }
568 };
569
570 mapWidgetClearMap();
571
572 var customBounds = new google.maps.LatLngBounds();
573 var zoomSWBounds = new google.maps.LatLng(zoneData["swLat"], zoneData["swLng"]);
574 var zoomNEBounds = new google.maps.LatLng(zoneData["neLat"], zoneData["neLng"]);
575 customBounds.extend(zoomSWBounds);
576 customBounds.extend(zoomNEBounds);
577 map.fitBounds(customBounds);
578
579 var df = getDrillDownQuery(zoneData, zB);
580
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700581 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700582 "query_string" : "use dataverse twitter;\n" + df.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700583 "marker_path" : "static/img/mobile2.png",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700584 "on_clean_result" : onCleanTweetbookDrilldown,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700585 };
586
587 A.query(df.val(), onTweetbookQuerySuccessPlot);
588}
589
590function getDrillDownQuery(parameters, bounds) {
591
592 var zoomRectangle = new FunctionExpression("create-rectangle",
593 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
594 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
595
596 var drillDown = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700597 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700598 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
599 .LetClause("$region", zoomRectangle)
600 .WhereClause().and(
601 new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
602 new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
603 new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")'),
604 new FunctionExpression('contains', '$t.message-text', '$keyword')
605 )
606 .ReturnClause({
607 "tweetId" : "$t.tweetid",
608 "tweetText" : "$t.message-text",
609 "tweetLoc" : "$t.sender-location"
610 });
611
612 return drillDown;
613}
614
615
616function addTweetbookCommentDropdown(appendToDiv) {
617
618 // Creates a div to manage a radio button set of chosen tweetbooks
619 $('<div/>')
620 .attr("class","btn-group chosen-tweetbooks")
621 .attr("data-toggle", "buttons-radio")
622 .css("margin-bottom", "10px")
623 .attr("id", "metacomment-tweetbooks")
624 .appendTo(appendToDiv);
625
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700626 var highlighted = "";
627 if (APIqueryTracker.hasOwnProperty("active_tweetbook")) {
628 highlighted = APIqueryTracker["active_tweetbook"];
629 }
630
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700631 // For each existing tweetbook from review mode, adds a radio button option.
632 $('#metacomment-tweetbooks').append('<input type="hidden" id="target-tweetbook" value="" />');
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700633 for (var rmt in review_mode_tweetbooks) {
634
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700635 var tweetbook_option = '<button type="button" class="btn">' + review_mode_tweetbooks[rmt] + '</button>';
636
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700637 if (review_mode_tweetbooks[rmt] == highlighted) {
638 tweetbook_option = '<button type="button" class="btn btn-info">' + review_mode_tweetbooks[rmt] + '</button>';
639 }
640
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700641 $('#metacomment-tweetbooks').append(tweetbook_option + '<br/>');
642 }
643
644 // Creates a button + input combination to add tweet comment to new tweetbook
645 var new_tweetbook_option = '<button type="button" class="btn" id="new-tweetbook-target-m"></button>' +
646 '<input type="text" id="new-tweetbook-entry-m" placeholder="Add to new tweetbook..."><br/>';
647 $('#metacomment-tweetbooks').append(new_tweetbook_option);
648
649 $("#new-tweetbook-entry-m").keyup(function() {
650 $("#new-tweetbook-target-m").val($("#new-tweetbook-entry-m").val());
651 $("#new-tweetbook-target-m").text($("#new-tweetbook-entry-m").val());
652 });
653
654 // There is a hidden input (id = target-tweetbook) which is used to track the value
655 // of the tweetbook to which the comment on this tweet will be added.
656 $(".chosen-tweetbooks .btn").click(function() {
657 $("#target-tweetbook").val($(this).text());
658 });
659}
660
661function onDrillDownAtLocation(tO) {
662
663 var tweetId = tO["tweetEntryId"];
664 var tweetText = tO["tweetText"];
665
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700666 // First, set tweet in drilldown modal to be this tweet's text
667 $('#modal-body-tweet').html('Tweet #' + tweetId + ": " + tweetText);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700668
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700669 // Next, empty any leftover tweetbook comments
670 $("#modal-body-add-to").val('');
671 $("#modal-body-add-note").val('');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700672
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700673 // Next, if there is an existing tweetcomment reported, show it.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700674 if (tO.hasOwnProperty("tweetComment")) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700675
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700676 $("#modal-existing-note").show();
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700677
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700678 // Change comment value
679 $("#modal-body-tweet-note").val(tO["tweetComment"]);
680
681 // Change Tweetbook Badge
682 $("#modal-current-tweetbook").val(APIqueryTracker["active_tweetbook"]);
683
684 // Add deletion functionality
685 $("#modal-body-trash-icon").on('click', function () {
686 // Send comment deletion to asterix
687 var deleteTweetCommentOnId = '"' + tweetId + '"';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700688 var toDelete = new DeleteStatement(
689 "$mt",
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700690 APIqueryTracker["active_tweetbook"],
691 new AExpression("$mt.tweetid = " + deleteTweetCommentOnId.toString())
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700692 );
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700693 A.update(
694 toDelete.val()
695 );
696
697 // Hide comment from map
698 $('#drilldown_modal').modal('hide');
699
700 // Replot tweetbook
701 onPlotTweetbook(APIqueryTracker["active_tweetbook"]);
702 });
703
704 } else {
705 $("#modal-existing-note").hide();
706 }
707
708 // Now, when adding a comment to a tweetbook...
709 $("#save-comment-tweetbook-modal").on('click', function(e) {
710 // Stuff to save about new comment
711 var save_metacomment_target_tweetbook = $("#modal-body-add-to").val();
712 var save_metacomment_target_comment = '"' + $("#modal-body-add-note").val() + '"';
713 var save_metacomment_target_tweet = '"' + tweetId + '"';
714
715 // Make sure content is entered, and then save this comment.
716 if (save_metacomment_target_tweetbook.length == 0) {
717 alert("Please enter a tweetbook.");
718 } else if ($("#modal-body-add-note").val() == "") {
719 alert("Please enter a comment.");
720 } else {
721
722 // Check if tweetbook exists. If not, create it.
723 if (!(existsTweetbook(save_metacomment_target_tweetbook))) {
724 onCreateNewTweetBook(save_metacomment_target_tweetbook);
725 } else {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700726
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700727 var toDelete = new DeleteStatement(
728 "$mt",
729 save_metacomment_target_tweetbook,
730 new AExpression("$mt.tweetid = " + save_metacomment_target_tweet.toString())
731 );
732 A.update(toDelete.val());
733 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700734
735 var toInsert = new InsertStatement(
736 save_metacomment_target_tweetbook,
737 {
738 "tweetid" : save_metacomment_target_tweet.toString(),
739 "comment-text" : save_metacomment_target_comment
740 }
741 );
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700742 A.update(toInsert.val(), function () {});
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700743
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700744 // TODO Some stress testing of error conditions might be good here...
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700745 if (APIqueryTracker.hasOwnProperty("active_tweetbook")) {
746 onPlotTweetbook(APIqueryTracker["active_tweetbook"]);
747 };
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700748 var successMessage = "Saved comment on <b>Tweet #" + tweetId +
749 "</b> in dataset <b>" + save_metacomment_target_tweetbook + "</b>.";
genia.likes.science@gmail.com0f67d9e2013-10-04 17:08:50 -0700750 addSuccessBlock(successMessage, "modal-save-body");
751 $("#modal-body-add-to").val('');
752 $("#modal-body-add-note").val('');
753 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700754 });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700755}
756
757
758/**
759* Adds a new tweetbook entry to the menu and creates a dataset of type TweetbookEntry.
760*/
761function onCreateNewTweetBook(tweetbook_title) {
762
763 var tweetbook_title = tweetbook_title.split(' ').join('_');
764
765 A.ddl(
766 "create dataset " + tweetbook_title + "(TweetbookEntry) primary key tweetid;",
767 function () {}
768 );
769
770 if (!(existsTweetbook(tweetbook_title))) {
771 review_mode_tweetbooks.push(tweetbook_title);
772 addTweetBookDropdownItem(tweetbook_title);
773 }
774}
775
776
777function onDropTweetBook(tweetbook_title) {
778
779 // AQL Call
780 A.ddl(
781 "drop dataset " + tweetbook_title + " if exists;",
782 function () {}
783 );
784
785 // Removes tweetbook from review_mode_tweetbooks
786 var remove_position = $.inArray(tweetbook_title, review_mode_tweetbooks);
787 if (remove_position >= 0) review_mode_tweetbooks.splice(remove_position, 1);
788
789 // Clear UI with review tweetbook titles
790 $('#review-tweetbook-titles').html('');
791 for (r in review_mode_tweetbooks) {
792 addTweetBookDropdownItem(review_mode_tweetbooks[r]);
793 }
794}
795
796
797function addTweetBookDropdownItem(tweetbook) {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700798 // Add placeholder for this tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700799 $('<div/>')
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700800 .attr({
801 "class" : "btn-group",
802 "id" : "rm_holder_" + tweetbook
803 }).appendTo("#review-tweetbook-titles");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700804
805 // Add plotting button for this tweetbook
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700806 var plot_button = '<button class="btn btn-default" id="rm_plotbook_' + tweetbook + '">' + tweetbook + '</button>';
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700807 $("#rm_holder_" + tweetbook).append(plot_button);
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700808 $("#rm_plotbook_" + tweetbook).width("200px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700809 $("#rm_plotbook_" + tweetbook).on('click', function(e) {
810 onPlotTweetbook(tweetbook);
811 });
812
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700813 // TODO Button Same Length
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700814 // Add trash button for this tweetbook
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700815 var onTrashTweetbookButton = addDeleteButton(
816 "rm_trashbook_" + tweetbook,
817 "rm_holder_" + tweetbook,
818 function(e) {
819 onDropTweetBook(tweetbook);
820 }
821 );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700822}
823
824
825function onPlotTweetbook(tweetbook) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700826
827 // Clear map for this one
828 mapWidgetResetMap();
829
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700830 var plotTweetQuery = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700831 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700832 .ForClause("$m", new AExpression("dataset " + tweetbook))
833 .WhereClause(new AExpression("$m.tweetid = $t.tweetid"))
834 .ReturnClause({
835 "tweetId" : "$m.tweetid",
836 "tweetText" : "$t.message-text",
837 "tweetLoc" : "$t.sender-location",
838 "tweetCom" : "$m.comment-text"
839 });
840
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700841 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700842 "query_string" : "use dataverse twitter;\n" + plotTweetQuery.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700843 "marker_path" : "static/img/mobile_green2.png",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700844 "on_clean_result" : onCleanPlotTweetbook,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700845 "active_tweetbook" : tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700846 };
847
848 A.query(plotTweetQuery.val(), onTweetbookQuerySuccessPlot);
849}
850
851
852function onTweetbookQuerySuccessPlot (res) {
853
854 var records = res["results"];
855
856 var coordinates = [];
857 map_tweet_markers = [];
858 map_tweet_overlays = [];
859 drilldown_data_map = {};
860 drilldown_data_map_vals = {};
861
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700862 var micon = APIqueryTracker["marker_path"];
863 var marker_click_function = onClickTweetbookMapMarker;
864 var clean_result_function = APIqueryTracker["on_clean_result"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700865
866 coordinates = clean_result_function(records);
867
868 for (var dm in coordinates) {
869 var keyLat = coordinates[dm].tweetLat.toString();
870 var keyLng = coordinates[dm].tweetLng.toString();
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700871
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700872 if (!drilldown_data_map.hasOwnProperty(keyLat)) {
873 drilldown_data_map[keyLat] = {};
874 }
875 if (!drilldown_data_map[keyLat].hasOwnProperty(keyLng)) {
876 drilldown_data_map[keyLat][keyLng] = [];
877 }
878 drilldown_data_map[keyLat][keyLng].push(coordinates[dm]);
879 drilldown_data_map_vals[coordinates[dm].tweetEntryId.toString()] = coordinates[dm];
880 }
881
882 $.each(drilldown_data_map, function(drillKeyLat, valuesAtLat) {
883 $.each(drilldown_data_map[drillKeyLat], function (drillKeyLng, valueAtLng) {
884
885 // Get subset of drilldown position on map
886 var cposition = new google.maps.LatLng(parseFloat(drillKeyLat), parseFloat(drillKeyLng));
887
888 // Create a marker using the snazzy phone icon
889 var map_tweet_m = new google.maps.Marker({
890 position: cposition,
891 map: map,
892 icon: micon,
893 clickable: true,
894 });
895
896 // Open Tweet exploration window on click
897 google.maps.event.addListener(map_tweet_m, 'click', function (event) {
898 marker_click_function(drilldown_data_map[drillKeyLat][drillKeyLng]);
899 });
900
901 // Add marker to index of tweets
902 map_tweet_markers.push(map_tweet_m);
903
904 });
905 });
906}
907
908
909function existsTweetbook(tweetbook) {
910 if (parseInt($.inArray(tweetbook, review_mode_tweetbooks)) == -1) {
911 return false;
912 } else {
913 return true;
914 }
915}
916
917
918function onCleanPlotTweetbook(records) {
919 var toPlot = [];
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700920
921 // An entry looks like this:
922 // { "tweetId": "273589", "tweetText": " like verizon the network is amazing", "tweetLoc": { point: [37.78, 82.27]}, "tweetCom": "hooray comments" }
923
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700924 for (var entry in records) {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700925
926 var points = records[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
927
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700928 var tweetbook_element = {
929 "tweetEntryId" : parseInt(records[entry].split(",")[0].split(":")[1].split('"')[1]),
930 "tweetText" : records[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700931 "tweetLat" : parseFloat(points[0]),
932 "tweetLng" : parseFloat(points[1]),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700933 "tweetComment" : records[entry].split("tweetCom\": \"")[1].split("\"")[0]
934 };
935 toPlot.push(tweetbook_element);
936 }
937
938 return toPlot;
939}
940
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700941
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700942function onCleanTweetbookDrilldown (rec) {
943
944 var drilldown_cleaned = [];
945
946 for (var entry = 0; entry < rec.length; entry++) {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700947
948 // An entry looks like this:
949 // { "tweetId": "105491", "tweetText": " hate verizon its platform is OMG", "tweetLoc": { point: [30.55, 71.44]} }
950 var points = rec[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
951
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700952 var drill_element = {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700953 "tweetEntryId" : parseInt(rec[entry].split(",")[0].split(":")[1].replace('"', '')),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700954 "tweetText" : rec[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700955 "tweetLat" : parseFloat(points[0]),
956 "tweetLng" : parseFloat(points[1])
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700957 };
958 drilldown_cleaned.push(drill_element);
959 }
960 return drilldown_cleaned;
961}
962
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700963
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700964function onClickTweetbookMapMarker(tweet_arr) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700965 // Clear existing display
966 $.each(tweet_arr, function (t, valueT) {
967 var tweet_obj = tweet_arr[t];
968 onDrillDownAtLocation(tweet_obj);
969 });
970
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700971 $('#drilldown_modal').modal();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700972}
973
974/** Toggling Review and Explore Modes **/
975
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700976
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700977/**
978* Explore mode: Initial map creation and screen alignment
979*/
980function onOpenExploreMap () {
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700981 var explore_column_height = $('#explore-well').height();
982 var right_column_width = $('#right-col').width();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700983 $('#map_canvas').height(explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700984 $('#map_canvas').width(right_column_width + "px");
985
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700986 $('#review-well').height(explore_column_height + "px");
987 $('#review-well').css('max-height', explore_column_height + "px");
genia.likes.science@gmail.com724476d2013-10-04 03:31:16 -0700988
989 $('#right-col').height(explore_column_height + "px");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700990}
991
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700992
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700993/**
994* Launching explore mode: clear windows/variables, show correct sidebar
995*/
996function onLaunchExploreMode() {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -0700997 $('#aboutr').hide();
998 $('#r1').show();
999 $('#about-active').removeClass('active');
1000
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001001 $('#review-active').removeClass('active');
1002 $('#review-well').hide();
1003
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001004
1005 $('#explore-active').addClass('active');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001006 $('#explore-well').show();
1007
1008 $("#clear-button").trigger("click");
1009}
1010
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001011
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001012/**
1013* Launching review mode: clear windows/variables, show correct sidebar
1014*/
1015function onLaunchReviewMode() {
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001016 $('#aboutr').hide();
1017 $('#r1').show();
1018 $('#about-active').removeClass('active');
1019
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001020 $('#explore-active').removeClass('active');
1021 $('#explore-well').hide();
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001022
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001023 $('#review-active').addClass('active');
1024 $('#review-well').show();
1025
1026 $("#clear-button").trigger("click");
1027}
1028
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001029
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001030/**
1031* Lauching about mode: hides all windows, shows row containing about info
1032*/
1033function onLaunchAboutMode() {
1034 $('#explore-active').removeClass('active');
1035 $('#review-active').removeClass('active');
1036 $('#about-active').addClass('active');
1037 $('#r1').hide();
1038 $('#aboutr').show();
1039}
1040
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001041/** Icon / Interface Utility Methods **/
1042
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001043/**
1044* Creates a delete icon button using default trash icon
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001045* @param {String} id, id for this element
1046* @param {String} attachTo, id string of an element to which I can attach this button.
1047* @param {Function} onClick, a function to fire when this icon is clicked
1048*/
1049function addDeleteButton(iconId, attachTo, onClick) {
1050 // Icon structure
genia.likes.science@gmail.com2de4c182013-10-04 11:39:11 -07001051 var trashIcon = '<button class="btn btn-default" id="' + iconId + '"><span class="glyphicon glyphicon-trash"></span></button>';
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001052
1053 $('#' + attachTo).append(trashIcon);
1054
1055 // On Click behavior
1056 $('#' + iconId).on('click', onClick);
1057}
1058
1059
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001060/**
1061* Creates a success message and attaches it to a div with provided ID.
1062* @param {String} message, a message to post
1063* @param {String} appendTarget, a target div to which to append the alert
1064*/
1065function addSuccessBlock(message, appendTarget) {
1066
1067 $('<div/>')
1068 .attr("class", "alert alert-success")
1069 .html('<button type="button" class="close" data-dismiss="alert">&times;</button>' + message)
1070 .appendTo('#' + appendTarget);
1071}
1072
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001073/** Map Widget Utility Methods **/
1074
1075/**
1076* Plots a legend onto the map, with values in progress bars
1077* @param {number Array} breakpoints, an array of numbers representing natural breakpoints
1078*/
1079function mapControlWidgetAddLegend(breakpoints) {
1080
1081 // Retriever colors, lightest to darkest
1082 var colors = mapWidgetGetColorPalette();
1083
1084 // Initial div structure
1085 $("#map_canvas_legend").html('<div id="legend-holder"><div id="legend-progress-bar" class="progress"></div><span id="legend-label"></span></div>');
1086
1087 // Add color scale to legend
1088 $('#legend-progress-bar').css("width", "200px").html('');
1089
1090 // Add a progress bar for each color
1091 for (var color in colors) {
1092
1093 // Bar values
1094 var upperBound = breakpoints[parseInt(color) + 1];
1095
1096 // Create Progress Bar
1097 $('<div/>')
1098 .attr("class", "bar")
1099 .attr("id", "pbar" + color)
1100 .css("width" , '25.0%')
1101 .html("< " + upperBound)
1102 .appendTo('#legend-progress-bar');
1103
1104 $('#pbar' + color).css({
1105 "background-image" : 'none',
1106 "background-color" : colors[parseInt(color)]
1107 });
1108
1109 // Attach a message showing minimum bounds
1110 $('#legend-label').html('Regions with at least ' + breakpoints[0] + ' tweets');
1111 $('#legend-label').css({
1112 "margin-top" : 0,
1113 "color" : "black"
1114 });
1115 }
1116
1117 // Add legend to map
1118 map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(document.getElementById('legend-holder'));
1119 $('#map_canvas_legend').show();
1120}
1121
1122/**
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001123* Clears ALL map elements - legend, plotted items, overlays
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001124*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001125function mapWidgetResetMap() {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001126
1127 if (selectionRect) {
1128 selectionRect.setMap(null);
1129 selectionRect = null;
1130 }
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001131
1132 mapWidgetClearMap();
1133
1134 // Reset map center and zoom
1135 map.setCenter(new google.maps.LatLng(38.89, -77.03));
1136 map.setZoom(4);
1137}
1138
1139function mapWidgetClearMap() {
1140
1141 // Remove previously plotted data/markers
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001142 for (c in map_cells) {
1143 map_cells[c].setMap(null);
1144 }
1145 map_cells = [];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -07001146
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001147 for (m in map_tweet_markers) {
1148 map_tweet_markers[m].setMap(null);
1149 }
1150 map_tweet_markers = [];
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001151
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001152 // Remove legend from map
1153 map.controls[google.maps.ControlPosition.LEFT_BOTTOM].clear();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001154}
1155
1156/**
1157* Uses jenks algorithm in geostats library to find natural breaks in numeric data
1158* @param {number Array} weights of points to plot
1159* @returns {number Array} array of natural breakpoints, of which the top 4 subsets will be plotted
1160*/
1161function mapWidgetLegendComputeNaturalBreaks(weights) {
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001162
1163 if (weights.length < 10) {
1164 return [0];
1165 }
1166
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001167 var plotDataWeights = new geostats(weights.sort());
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001168 return plotDataWeights.getJenks(6).slice(2,7);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001169}
1170
1171/**
1172* Computes values for map legend given a value and an array of jenks breakpoints
1173* @param {number} weight of point to plot on map
1174* @param {number Array} breakpoints, an array of 5 points corresponding to bounds of 4 natural ranges
1175* @returns {String} an RGB value corresponding to a subset of data
1176*/
1177function mapWidgetLegendGetHeatValue(weight, breakpoints) {
1178
1179 // Determine into which range the weight falls
1180 var weightColor = 0;
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001181
1182 if (breakpoints.length == 1) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001183 weightColor = 2;
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001184 } else {
1185 if (weight >= breakpoints[3]) {
1186 weightColor = 3;
1187 } else if (weight >= breakpoints[2]) {
1188 weightColor = 2;
1189 } else if (weight >= breakpoints[1]) {
1190 weightColor = 1;
1191 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001192 }
1193
1194 // Get default map color palette
1195 var colorValues = mapWidgetGetColorPalette();
1196 return colorValues[weightColor];
1197}
1198
1199/**
1200* Returns an array containing a 4-color palette, lightest to darkest
1201* External palette source: http://www.colourlovers.com/palette/2763366/s_i_l_e_n_c_e_r
1202* @returns {Array} [colors]
1203*/
1204function mapWidgetGetColorPalette() {
1205 return [
1206 "rgb(115,189,158)",
1207 "rgb(74,142,145)",
1208 "rgb(19,93,96)",
1209 "rgb(7,51,46)"
1210 ];
1211}
1212
1213/**
1214* Computes radius for a given data point from a spatial cell
1215* @param {Object} keys => ["latSW" "lngSW" "latNE" "lngNE" "weight"]
1216* @returns {number} radius between 2 points in metres
1217*/
1218function mapWidgetComputeCircleRadius(spatialCell, breakpoints) {
1219
1220 var weight = spatialCell.weight;
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001221
1222 if (breakpoints.length == 1) {
1223 var weightColor = 0.25;
1224 } else {
1225 // Compute weight color
1226 var weightColor = 0.25;
1227 if (weight >= breakpoints[3]) {
1228 weightColor = 1.0;
1229 } else if (weight >= breakpoints[2]) {
1230 weightColor = 0.75;
1231 } else if (weight >= breakpoints[1]) {
1232 weightColor = 0.5;
1233 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001234 }
1235
1236 // Define Boundary Points
1237 var point_center = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1238 var point_left = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, spatialCell.lngSW);
1239 var point_top = new google.maps.LatLng(spatialCell.latNE, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1240
1241 // TODO not actually a weight color :)
1242 return weightColor * 1000 * Math.min(distanceBetweenPoints_(point_center, point_left), distanceBetweenPoints_(point_center, point_top));
1243}
1244
1245/** External Utility Methods **/
1246
1247/**
1248 * Calculates the distance between two latlng locations in km.
1249 * @see http://www.movable-type.co.uk/scripts/latlong.html
1250 *
1251 * @param {google.maps.LatLng} p1 The first lat lng point.
1252 * @param {google.maps.LatLng} p2 The second lat lng point.
1253 * @return {number} The distance between the two points in km.
1254 * @private
1255*/
1256function distanceBetweenPoints_(p1, p2) {
1257 if (!p1 || !p2) {
1258 return 0;
1259 }
1260
1261 var R = 6371; // Radius of the Earth in km
1262 var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
1263 var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
1264 var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
1265 Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
1266 Math.sin(dLon / 2) * Math.sin(dLon / 2);
1267 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1268 var d = R * c;
1269 return d;
1270};