blob: 9b11e7358c3a4c42a3321c1abc2c979e7bfb9d17 [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 );
24
25 // UI Elements - A button to clear current map and query data
26 $("#clear-button").button().click(function () {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -070027 mapWidgetResetMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070028
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070029 $('#query-preview-window').html('');
30 $("#metatweetzone").html('');
31 });
32
33 // UI Elements - Query setup
34 $("#selection-button").button('toggle');
35
36 var dialog = $("#dialog").dialog({
37 width: "auto",
38 title: "AQL Query"
39 }).dialog("close");
40 $("#show-query-button")
41 .button()
42 .attr("disabled", true)
43 .click(function (event) {
44 $("#dialog").dialog("open");
45 });
46
47 // UI Element - Grid sliders
48 var updateSliderDisplay = function(event, ui) {
49 if (event.target.id == "grid-lat-slider") {
50 $("#gridlat").text(""+ui.value);
51 } else {
52 $("#gridlng").text(""+ui.value);
53 }
54 };
55
56 sliderOptions = {
57 max: 10,
58 min: 1.5,
59 step: .1,
60 value: 2.0,
61 slidechange: updateSliderDisplay,
62 slide: updateSliderDisplay,
63 start: updateSliderDisplay,
64 stop: updateSliderDisplay
65 };
66
67 $("#gridlat").text(""+sliderOptions.value);
68 $("#gridlng").text(""+sliderOptions.value);
69 $(".grid-slider").slider(sliderOptions);
70
71 // UI Elements - Date Pickers
72 var dateOptions = {
73 dateFormat: "yy-mm-dd",
74 defaultDate: "2012-01-02",
75 navigationAsDateFormat: true,
76 constrainInput: true
77 };
78 var start_dp = $("#start-date").datepicker(dateOptions);
79 start_dp.val(dateOptions.defaultDate);
80 dateOptions['defaultDate'] = "2012-12-31";
81 var end_dp= $("#end-date").datepicker(dateOptions);
82 end_dp.val(dateOptions.defaultDate);
83
84 // This little bit of code manages period checks of the asynchronous query manager,
85 // which holds onto handles asynchornously received. We can set the handle update
86 // frequency using seconds, and it will let us know when it is ready.
87 var intervalID = setInterval(
88 function() {
89 asynchronousQueryIntervalUpdate();
90 },
91 asynchronousQueryGetInterval()
92 );
93
94 // UI Elements - Creates map and location auto-complete
95 onOpenExploreMap();
96 var mapOptions = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -070097 center: new google.maps.LatLng(38.89, -77.03),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070098 zoom: 4,
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -070099 mapTypeId: google.maps.MapTypeId.ROADMAP,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700100 streetViewControl: false,
101 draggable : false
102 };
103 map = new google.maps.Map(document.getElementById('map_canvas'), mapOptions);
104
105 var input = document.getElementById('location-text-box');
106 var autocomplete = new google.maps.places.Autocomplete(input);
107 autocomplete.bindTo('bounds', map);
108
109 google.maps.event.addListener(autocomplete, 'place_changed', function() {
110 var place = autocomplete.getPlace();
111 if (place.geometry.viewport) {
112 map.fitBounds(place.geometry.viewport);
113 } else {
114 map.setCenter(place.geometry.location);
115 map.setZoom(17); // Why 17? Because it looks good.
116 }
117 var address = '';
118 if (place.address_components) {
119 address = [(place.address_components[0] && place.address_components[0].short_name || ''),
120 (place.address_components[1] && place.address_components[1].short_name || ''),
121 (place.address_components[2] && place.address_components[2].short_name || '') ].join(' ');
122 }
123 });
124
125 // UI Elements - Selection Rectangle Drawing
126 shouldDraw = false;
127 var startLatLng;
128 selectionRect = null;
129 var selectionRadio = $("#selection-button");
130 var firstClick = true;
131
132 google.maps.event.addListener(map, 'mousedown', function (event) {
133 // only allow drawing if selection is selected
134 if (selectionRadio.hasClass("active")) {
135 startLatLng = event.latLng;
136 shouldDraw = true;
137 }
138 });
139
140 google.maps.event.addListener(map, 'mousemove', drawRect);
141 function drawRect (event) {
142 if (shouldDraw) {
143 if (!selectionRect) {
144 var selectionRectOpts = {
145 bounds: new google.maps.LatLngBounds(startLatLng, event.latLng),
146 map: map,
147 strokeWeight: 1,
148 strokeColor: "2b3f8c",
149 fillColor: "2b3f8c"
150 };
151 selectionRect = new google.maps.Rectangle(selectionRectOpts);
152 google.maps.event.addListener(selectionRect, 'mouseup', function () {
153 shouldDraw = false;
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700154 });
155 } else {
genia.likes.science@gmail.com4ada78c2013-09-07 13:53:48 -0700156
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700157 if (startLatLng.lng() < event.latLng.lng()) {
158 selectionRect.setBounds(new google.maps.LatLngBounds(startLatLng, event.latLng));
159 } else {
160 selectionRect.setBounds(new google.maps.LatLngBounds(event.latLng, startLatLng));
161 }
162 }
163 }
164 };
165
166 // UI Elements - Toggle location search style by location or by map selection
167 $('#selection-button').on('click', function (e) {
168 $("#location-text-box").attr("disabled", "disabled");
169 if (selectionRect) {
170 selectionRect.setMap(map);
171 }
172 });
173 $('#location-button').on('click', function (e) {
174 $("#location-text-box").removeAttr("disabled");
175 if (selectionRect) {
176 selectionRect.setMap(null);
177 }
178 });
179
180 // UI Elements - Tweetbook Management
181 $('.dropdown-menu a.holdmenu').click(function(e) {
182 e.stopPropagation();
183 });
184
185 $('#new-tweetbook-button').on('click', function (e) {
186 onCreateNewTweetBook($('#new-tweetbook-entry').val());
187
188 $('#new-tweetbook-entry').val($('#new-tweetbook-entry').attr('placeholder'));
189 });
190
191 // UI Element - Query Submission
192 $("#submit-button").button().click(function () {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700193
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700194 var kwterm = $("#keyword-textbox").val();
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700195 if (kwterm == "") {
196 alert("Please enter a search term!");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700197 } else {
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700198
199 $("#submit-button").attr("disabled", true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700200
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700201 var startdp = $("#start-date").datepicker("getDate");
202 var enddp = $("#end-date").datepicker("getDate");
203 var startdt = $.datepicker.formatDate("yy-mm-dd", startdp)+"T00:00:00Z";
204 var enddt = $.datepicker.formatDate("yy-mm-dd", enddp)+"T23:59:59Z";
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700205
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700206 var formData = {
207 "keyword": kwterm,
208 "startdt": startdt,
209 "enddt": enddt,
210 "gridlat": $("#grid-lat-slider").slider("value"),
211 "gridlng": $("#grid-lng-slider").slider("value")
212 };
213
214 // Get Map Bounds
215 var bounds;
216 if ($('#selection-button').hasClass("active") && selectionRect) {
217 bounds = selectionRect.getBounds();
218 } else {
219 bounds = map.getBounds();
220 }
221
222 var swLat = Math.abs(bounds.getSouthWest().lat());
223 var swLng = Math.abs(bounds.getSouthWest().lng());
224 var neLat = Math.abs(bounds.getNorthEast().lat());
225 var neLng = Math.abs(bounds.getNorthEast().lng());
226
227 formData["swLat"] = Math.min(swLat, neLat);
228 formData["swLng"] = Math.max(swLng, neLng);
229 formData["neLat"] = Math.max(swLat, neLat);
230 formData["neLng"] = Math.min(swLng, neLng);
231
232 var build_cherry_mode = "synchronous";
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700233
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700234 if ($('#asbox').is(":checked")) {
235 build_cherry_mode = "asynchronous";
236 $('#show-query-button').attr("disabled", false);
237 } else {
238 $('#show-query-button').attr("disabled", true);
239 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700240
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700241 var f = buildAQLQueryFromForm(formData);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700242
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700243 APIqueryTracker = {
244 "query" : "use dataverse twitter;\n" + f.val(),
245 "data" : formData
246 };
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700247
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700248 $('#dialog').html(APIqueryTracker["query"]);
genia.likes.science@gmail.com233fe972013-09-07 13:57:46 -0700249
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700250 if (build_cherry_mode == "synchronous") {
251 A.query(f.val(), cherryQuerySyncCallback, build_cherry_mode);
252 } else {
253 A.query(f.val(), cherryQueryAsyncCallback, build_cherry_mode);
254 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700255
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700256 // Clears selection rectangle on query execution, rather than waiting for another clear call.
257 if (selectionRect) {
258 selectionRect.setMap(null);
259 selectionRect = null;
260 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700261 }
262 });
263});
264
265
266function buildAQLQueryFromForm(parameters) {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700267
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700268 var bounds = {
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700269 "ne" : { "lat" : parameters["neLat"], "lng" : -1*parameters["neLng"]},
270 "sw" : { "lat" : parameters["swLat"], "lng" : -1*parameters["swLng"]}
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700271 };
272
273 var rectangle =
274 new FunctionExpression("create-rectangle",
275 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
276 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
277
278
279 var aql = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700280 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700281 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
282 .LetClause("$region", rectangle)
283 .WhereClause().and(
284 new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
285 new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
286 new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")'),
287 new FunctionExpression("contains", "$t.message-text", "$keyword")
288 )
289 .GroupClause(
290 "$c",
291 new FunctionExpression("spatial-cell", "$t.sender-location",
292 new FunctionExpression("create-point", "24.5", "-125.5"),
293 parameters["gridlat"].toFixed(1), parameters["gridlng"].toFixed(1)),
294 "with",
295 "$t"
296 )
297 .ReturnClause({ "cell" : "$c", "count" : "count($t)" });
298
299 return aql;
300}
301
302/** Asynchronous Query Management **/
303
304
305/**
306* Checks through each asynchronous query to see if they are ready yet
307*/
308function asynchronousQueryIntervalUpdate() {
309 for (var handle_key in asyncQueryManager) {
310 if (!asyncQueryManager[handle_key].hasOwnProperty("ready")) {
311 asynchronousQueryGetAPIQueryStatus( asyncQueryManager[handle_key]["handle"], handle_key );
312 }
313 }
314}
315
316
317/**
318* Returns current time interval to check for asynchronous query readiness
319* @returns {number} milliseconds between asychronous query checks
320*/
321function asynchronousQueryGetInterval() {
322 var seconds = 10;
323 return seconds * 1000;
324}
325
326
327/**
328* Retrieves status of an asynchronous query, using an opaque result handle from API
329* @param {Object} handle, an object previously returned from an async call
330* @param {number} handle_id, the integer ID parsed from the handle object
331*/
332function asynchronousQueryGetAPIQueryStatus (handle, handle_id) {
333
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700334 A.query_status(
335 {
336 "handle" : JSON.stringify(handle)
337 },
338 function (res) {
339 if (res["status"] == "SUCCESS") {
340 // We don't need to check if this one is ready again, it's not going anywhere...
341 // Unless the life cycle of handles has changed drastically
342 asyncQueryManager[handle_id]["ready"] = true;
343
344 // Indicate success.
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700345 $('#handle_' + handle_id).removeClass("btn-disabled").prop('disabled', false).addClass("btn-success");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700346 }
347 }
348 );
349}
350
351
352/**
353* On-success callback after async API query
354* @param {object} res, a result object containing an opaque result handle to Asterix
355*/
356function cherryQueryAsyncCallback(res) {
357
358 // Parse handle, handle id and query from async call result
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700359 var handle_query = APIqueryTracker["query"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700360 var handle = res;
361 var handle_id = res["handle"].toString().split(',')[0];
362
363 // Add to stored map of existing handles
364 asyncQueryManager[handle_id] = {
365 "handle" : handle,
366 "query" : handle_query,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700367 "data" : APIqueryTracker["data"]
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700368 };
369
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700370 // Create a container for this async query handle
371 $('<div/>')
372 .css("margin-left", "1em")
373 .css("margin-bottom", "1em")
374 .css("display", "block")
375 .attr({
376 "class" : "btn-group",
377 "id" : "async_container_" + handle_id
378 })
379 .appendTo("#async-handle-controls");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700380
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700381 // Adds the main button for this async handle
382 var handle_action_button = '<button class="btn btn-disabled" id="handle_' + handle_id + '">Handle ' + handle_id + '</button>';
383 $('#async_container_' + handle_id).append(handle_action_button);
384 $('#handle_' + handle_id).prop('disabled', true);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700385 $('#handle_' + handle_id).on('click', function (e) {
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700386
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700387 // make sure query is ready to be run
388 if (asyncQueryManager[handle_id]["ready"]) {
389
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700390 APIqueryTracker = {
391 "query" : asyncQueryManager[handle_id]["query"],
392 "data" : asyncQueryManager[handle_id]["data"]
393 };
394 $('#dialog').html(APIqueryTracker["query"]);
395
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700396 if (!asyncQueryManager[handle_id].hasOwnProperty("result")) {
397 // Generate new Asterix Core API Query
398 A.query_result(
399 { "handle" : JSON.stringify(asyncQueryManager[handle_id]["handle"]) },
400 function(res) {
401 asyncQueryManager[handle_id]["result"] = res;
402 cherryQuerySyncCallback(res);
403 }
404 );
405 } else {
406 cherryQuerySyncCallback(asyncQueryManager[handle_id]["result"]);
407 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700408 }
409 });
genia.likes.science@gmail.com4259a542013-08-18 20:06:58 -0700410
411 // Adds a removal button for this async handle
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700412 var asyncDeleteButton = addDeleteButton(
413 "trashhandle_" + handle_id,
414 "async_container_" + handle_id,
415 function (e) {
416 $('#async_container_' + handle_id).remove();
417 delete asyncQueryManager[handle_id];
418 }
419 );
420 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700421}
422
423
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700424/**
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700425* returns a json object with keys: weight, latSW, lngSW, latNE, lngNE
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700426*
427* { "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 -0700428*/
429function getRecord(cell_count_record) {
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700430 // This is a really hacky way to pull out the digits, but it works for now.
431 var values = cell_count_record.replace("int64","").match(/[-+]?[0-9]*\.?[0-9]+/g);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700432 var record_representation = {};
433
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700434 record_representation["latSW"] = parseFloat(values[0]);
435 record_representation["lngSW"] = parseFloat(values[1]);
436 record_representation["latNE"] = parseFloat(values[2]);
437 record_representation["lngNE"] = parseFloat(values[3]);
438 record_representation["weight"] = parseInt(values[4]);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700439
440 return record_representation;
441}
442
443/**
444* A spatial data cleaning and mapping call
445* @param {Object} res, a result object from a cherry geospatial query
446*/
447function cherryQuerySyncCallback(res) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700448 records = res["results"];
genia.likes.science@gmail.comd42b4022013-08-09 05:05:23 -0700449
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700450 if (typeof res["results"][0] == "object") {
451 records = res["results"][0];
452 }
453
454 var coordinates = [];
455 var weights = [];
456
457 for (var subrecord in records) {
458 var coordinate = getRecord(records[subrecord]);
459 weights.push(coordinate["weight"]);
460 coordinates.push(coordinate);
461 }
462
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700463 triggerUIUpdate(coordinates, weights);
464 $("#submit-button").attr("disabled", false);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700465}
466
467/**
468* Triggers a map update based on a set of spatial query result cells
469* @param [Array] mapPlotData, an array of coordinate and weight objects
470* @param [Array] params, an object containing original query parameters [LEGACY]
471* @param [Array] plotWeights, a list of weights of the spatial cells - e.g., number of tweets
472*/
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700473function triggerUIUpdate(mapPlotData, plotWeights) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700474 /** Clear anything currently on the map **/
475 mapWidgetClearMap();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700476
477 // Compute data point spread
478 var dataBreakpoints = mapWidgetLegendComputeNaturalBreaks(plotWeights);
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700479
480 map_info_windows = {};
481
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700482 $.each(mapPlotData, function (m, val) {
483
484 // Only map points in data range of top 4 natural breaks
485 if (mapPlotData[m].weight > dataBreakpoints[0]) {
486
487 // Get color value of legend
488 var mapColor = mapWidgetLegendGetHeatValue(mapPlotData[m].weight, dataBreakpoints);
489 var markerRadius = mapWidgetComputeCircleRadius(mapPlotData[m], dataBreakpoints);
490 var point_opacity = 1.0;
491
492 var point_center = new google.maps.LatLng(
493 (mapPlotData[m].latSW + mapPlotData[m].latNE)/2.0,
494 (mapPlotData[m].lngSW + mapPlotData[m].lngNE)/2.0);
495
496 // Create and plot marker
497 var map_circle_options = {
498 center: point_center,
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700499 anchorPoint: point_center,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700500 radius: markerRadius,
501 map: map,
502 fillOpacity: point_opacity,
503 fillColor: mapColor,
504 clickable: true
505 };
506 var map_circle = new google.maps.Circle(map_circle_options);
507 map_circle.val = mapPlotData[m];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700508 map_circle.ind = m;
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700509
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700510 map_info_windows[m] = new google.maps.InfoWindow({
511 content: mapPlotData[m].weight + " tweets",
512 position: point_center
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700513 });
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700514
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700515 // Clicking on a circle drills down map to that value, hovering over it displays a count
516 // of tweets at that location.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700517 google.maps.event.addListener(map_circle, 'click', function (event) {
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700518 map_info_windows[m].close();
519 map_info_windows = {};
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700520 onMapPointDrillDown(map_circle.val);
521 });
522
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700523 google.maps.event.addListener(map_circle, 'mouseover', function(event) {
524 if (!map_info_windows[m].getMap()) {
525 map_info_windows[m].setPosition(map_circle.center);
526 map_info_windows[m].open(map);
527 }
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700528 });
529
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700530 google.maps.event.addListener(map_circle, 'mouseout', function(event) {
531 map_info_windows[m].close();
genia.likes.science@gmail.comec46c772013-09-07 18:13:00 -0700532 });
533
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700534 // Add this marker to global marker cells
535 map_cells.push(map_circle);
536 }
537 });
538
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -0700539 // Remove widget for now mapControlWidgetAddLegend(dataBreakpoints);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700540}
541
542/**
543* prepares an Asterix API query to drill down in a rectangular spatial zone
544*
545* @params {object} marker_borders [LEGACY] a set of bounds for a region from a previous api result
546*/
547function onMapPointDrillDown(marker_borders) {
548 var zoneData = APIqueryTracker["data"];
549
550 var zswBounds = new google.maps.LatLng(marker_borders.latSW, marker_borders.lngSW);
551 var zneBounds = new google.maps.LatLng(marker_borders.latNE, marker_borders.lngNE);
552
553 var zoneBounds = new google.maps.LatLngBounds(zswBounds, zneBounds);
554 zoneData["swLat"] = zoneBounds.getSouthWest().lat();
555 zoneData["swLng"] = zoneBounds.getSouthWest().lng();
556 zoneData["neLat"] = zoneBounds.getNorthEast().lat();
557 zoneData["neLng"] = zoneBounds.getNorthEast().lng();
558 var zB = {
559 "sw" : {
560 "lat" : zoneBounds.getSouthWest().lat(),
561 "lng" : zoneBounds.getSouthWest().lng()
562 },
563 "ne" : {
564 "lat" : zoneBounds.getNorthEast().lat(),
565 "lng" : zoneBounds.getNorthEast().lng()
566 }
567 };
568
569 mapWidgetClearMap();
570
571 var customBounds = new google.maps.LatLngBounds();
572 var zoomSWBounds = new google.maps.LatLng(zoneData["swLat"], zoneData["swLng"]);
573 var zoomNEBounds = new google.maps.LatLng(zoneData["neLat"], zoneData["neLng"]);
574 customBounds.extend(zoomSWBounds);
575 customBounds.extend(zoomNEBounds);
576 map.fitBounds(customBounds);
577
578 var df = getDrillDownQuery(zoneData, zB);
579
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700580 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700581 "query_string" : "use dataverse twitter;\n" + df.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700582 "marker_path" : "static/img/mobile2.png",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700583 "on_clean_result" : onCleanTweetbookDrilldown,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700584 };
585
586 A.query(df.val(), onTweetbookQuerySuccessPlot);
587}
588
589function getDrillDownQuery(parameters, bounds) {
590
591 var zoomRectangle = new FunctionExpression("create-rectangle",
592 new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
593 new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
594
595 var drillDown = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700596 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700597 .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
598 .LetClause("$region", zoomRectangle)
599 .WhereClause().and(
600 new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
601 new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
602 new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")'),
603 new FunctionExpression('contains', '$t.message-text', '$keyword')
604 )
605 .ReturnClause({
606 "tweetId" : "$t.tweetid",
607 "tweetText" : "$t.message-text",
608 "tweetLoc" : "$t.sender-location"
609 });
610
611 return drillDown;
612}
613
614
615function addTweetbookCommentDropdown(appendToDiv) {
616
617 // Creates a div to manage a radio button set of chosen tweetbooks
618 $('<div/>')
619 .attr("class","btn-group chosen-tweetbooks")
620 .attr("data-toggle", "buttons-radio")
621 .css("margin-bottom", "10px")
622 .attr("id", "metacomment-tweetbooks")
623 .appendTo(appendToDiv);
624
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700625 var highlighted = "";
626 if (APIqueryTracker.hasOwnProperty("active_tweetbook")) {
627 highlighted = APIqueryTracker["active_tweetbook"];
628 }
629
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700630 // For each existing tweetbook from review mode, adds a radio button option.
631 $('#metacomment-tweetbooks').append('<input type="hidden" id="target-tweetbook" value="" />');
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700632 for (var rmt in review_mode_tweetbooks) {
633
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700634 var tweetbook_option = '<button type="button" class="btn">' + review_mode_tweetbooks[rmt] + '</button>';
635
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700636 if (review_mode_tweetbooks[rmt] == highlighted) {
637 tweetbook_option = '<button type="button" class="btn btn-info">' + review_mode_tweetbooks[rmt] + '</button>';
638 }
639
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700640 $('#metacomment-tweetbooks').append(tweetbook_option + '<br/>');
641 }
642
643 // Creates a button + input combination to add tweet comment to new tweetbook
644 var new_tweetbook_option = '<button type="button" class="btn" id="new-tweetbook-target-m"></button>' +
645 '<input type="text" id="new-tweetbook-entry-m" placeholder="Add to new tweetbook..."><br/>';
646 $('#metacomment-tweetbooks').append(new_tweetbook_option);
647
648 $("#new-tweetbook-entry-m").keyup(function() {
649 $("#new-tweetbook-target-m").val($("#new-tweetbook-entry-m").val());
650 $("#new-tweetbook-target-m").text($("#new-tweetbook-entry-m").val());
651 });
652
653 // There is a hidden input (id = target-tweetbook) which is used to track the value
654 // of the tweetbook to which the comment on this tweet will be added.
655 $(".chosen-tweetbooks .btn").click(function() {
656 $("#target-tweetbook").val($(this).text());
657 });
658}
659
660function onDrillDownAtLocation(tO) {
661
662 var tweetId = tO["tweetEntryId"];
663 var tweetText = tO["tweetText"];
664
665 var tweetContainerId = '#drilldown_modal_body';
666 var tweetDiv = '<div id="drilltweetobj' + tweetId + '"></div>';
667
668 $(tweetContainerId).empty();
669 $(tweetContainerId).append(tweetDiv);
670 $('#drilltweetobj' + tweetId).append('<p>Tweet #' + tweetId + ": " + tweetText + '</p>');
671
672 // Add comment field
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700673 $('#drilltweetobj' + tweetId).append('<input class="textbox" type="text" id="metacomment' + tweetId + '">');
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700674
675 if (tO.hasOwnProperty("tweetComment")) {
676 $("#metacomment" + tweetId).val(tO["tweetComment"]);
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700677
678 var deleteThisComment = addDeleteButton(
679 "deleteLiveComment_" + tweetId,
680 "drilltweetobj" + tweetId,
681 function () {
682
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700683 // Send comment deletion to asterix
684 var deleteTweetCommentOnId = '"' + tweetId + '"';
685 var toDelete = new DeleteStatement(
686 "$mt",
687 APIqueryTracker["active_tweetbook"],
688 new AExpression("$mt.tweetid = " + deleteTweetCommentOnId.toString())
689 );
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700690 A.update(
691 toDelete.val()
692 );
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700693
694 // Hide comment from map
695 $('#drilldown_modal').modal('hide');
696
697 // Replot tweetbook
698 onPlotTweetbook(APIqueryTracker["active_tweetbook"]);
699 }
700 );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700701 }
702
703 addTweetbookCommentDropdown('#drilltweetobj' + tweetId);
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700704
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700705 $('#drilltweetobj' + tweetId).append('<br/><button type="button" class="btn" id="add-metacomment">Save Comment</button>');
706
707 $('#add-metacomment').button().click(function () {
708 var save_metacomment_target_tweetbook = $("#target-tweetbook").val();
709 var save_metacomment_target_comment = '"' + $("#metacomment" + tweetId).val() + '"';
710 var save_metacomment_target_tweet = '"' + tweetId + '"';
711
712 if (save_metacomment_target_tweetbook.length == 0) {
713 alert("Please choose a tweetbook.");
714
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700715 } else {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700716
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700717 if (!(existsTweetbook(save_metacomment_target_tweetbook))) {
718 onCreateNewTweetBook(save_metacomment_target_tweetbook);
719 }
720
721 var toDelete = new DeleteStatement(
722 "$mt",
723 save_metacomment_target_tweetbook,
724 new AExpression("$mt.tweetid = " + save_metacomment_target_tweet.toString())
725 );
726
727 A.update(toDelete.val());
728
729 var toInsert = new InsertStatement(
730 save_metacomment_target_tweetbook,
731 {
732 "tweetid" : save_metacomment_target_tweet.toString(),
733 "comment-text" : save_metacomment_target_comment
734 }
735 );
736
737 // Insert query to add metacomment to said tweetbook dataset
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700738 A.update(toInsert.val(), function () { alert("Test"); });
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700739
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700740 // TODO Some stress testing of error conditions might be good here...
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -0700741 if (APIqueryTracker.hasOwnProperty("active_tweetbook")) {
742 onPlotTweetbook(APIqueryTracker["active_tweetbook"]);
743 };
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700744 var successMessage = "Saved comment on <b>Tweet #" + tweetId +
745 "</b> in dataset <b>" + save_metacomment_target_tweetbook + "</b>.";
746 addSuccessBlock(successMessage, 'drilltweetobj' + tweetId);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700747 }
748 });
749
750 // Set width of tweetbook buttons
751 $(".chosen-tweetbooks .btn").css("width", "200px");
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700752 $(".chosen-tweetbooks .btn").css("height", "2em");
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700753}
754
755
756/**
757* Adds a new tweetbook entry to the menu and creates a dataset of type TweetbookEntry.
758*/
759function onCreateNewTweetBook(tweetbook_title) {
760
761 var tweetbook_title = tweetbook_title.split(' ').join('_');
762
763 A.ddl(
764 "create dataset " + tweetbook_title + "(TweetbookEntry) primary key tweetid;",
765 function () {}
766 );
767
768 if (!(existsTweetbook(tweetbook_title))) {
769 review_mode_tweetbooks.push(tweetbook_title);
770 addTweetBookDropdownItem(tweetbook_title);
771 }
772}
773
774
775function onDropTweetBook(tweetbook_title) {
776
777 // AQL Call
778 A.ddl(
779 "drop dataset " + tweetbook_title + " if exists;",
780 function () {}
781 );
782
783 // Removes tweetbook from review_mode_tweetbooks
784 var remove_position = $.inArray(tweetbook_title, review_mode_tweetbooks);
785 if (remove_position >= 0) review_mode_tweetbooks.splice(remove_position, 1);
786
787 // Clear UI with review tweetbook titles
788 $('#review-tweetbook-titles').html('');
789 for (r in review_mode_tweetbooks) {
790 addTweetBookDropdownItem(review_mode_tweetbooks[r]);
791 }
792}
793
794
795function addTweetBookDropdownItem(tweetbook) {
796 // Add placeholder for this tweetbook
797 $('<div/>')
798 .css("padding-left", "1em")
799 .attr({
800 "class" : "btn-group",
801 "id" : "rm_holder_" + tweetbook
802 }).appendTo("#review-tweetbook-titles");
803 $("#review-tweetbook-titles").append('<br/>');
804
805 // Add plotting button for this tweetbook
806 var plot_button = '<button class="btn" id="rm_plotbook_' + tweetbook + '">' + tweetbook + '</button>';
807 $("#rm_holder_" + tweetbook).append(plot_button);
808 $("#rm_plotbook_" + tweetbook).on('click', function(e) {
809 onPlotTweetbook(tweetbook);
810 });
811
812 // Add trash button for this tweetbook
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700813 var onTrashTweetbookButton = addDeleteButton(
814 "rm_trashbook_" + tweetbook,
815 "rm_holder_" + tweetbook,
816 function(e) {
817 onDropTweetBook(tweetbook);
818 }
819 );
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700820}
821
822
823function onPlotTweetbook(tweetbook) {
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700824
825 // Clear map for this one
826 mapWidgetResetMap();
827
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700828 var plotTweetQuery = new FLWOGRExpression()
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -0700829 .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700830 .ForClause("$m", new AExpression("dataset " + tweetbook))
831 .WhereClause(new AExpression("$m.tweetid = $t.tweetid"))
832 .ReturnClause({
833 "tweetId" : "$m.tweetid",
834 "tweetText" : "$t.message-text",
835 "tweetLoc" : "$t.sender-location",
836 "tweetCom" : "$m.comment-text"
837 });
838
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700839 APIqueryTracker = {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700840 "query_string" : "use dataverse twitter;\n" + plotTweetQuery.val(),
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700841 "marker_path" : "static/img/mobile_green2.png",
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700842 "on_clean_result" : onCleanPlotTweetbook,
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700843 "active_tweetbook" : tweetbook
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700844 };
845
846 A.query(plotTweetQuery.val(), onTweetbookQuerySuccessPlot);
847}
848
849
850function onTweetbookQuerySuccessPlot (res) {
851
852 var records = res["results"];
853
854 var coordinates = [];
855 map_tweet_markers = [];
856 map_tweet_overlays = [];
857 drilldown_data_map = {};
858 drilldown_data_map_vals = {};
859
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -0700860 var micon = APIqueryTracker["marker_path"];
861 var marker_click_function = onClickTweetbookMapMarker;
862 var clean_result_function = APIqueryTracker["on_clean_result"];
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700863
864 coordinates = clean_result_function(records);
865
866 for (var dm in coordinates) {
867 var keyLat = coordinates[dm].tweetLat.toString();
868 var keyLng = coordinates[dm].tweetLng.toString();
genia.likes.science@gmail.com1400a752013-10-04 04:59:12 -0700869
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700870 if (!drilldown_data_map.hasOwnProperty(keyLat)) {
871 drilldown_data_map[keyLat] = {};
872 }
873 if (!drilldown_data_map[keyLat].hasOwnProperty(keyLng)) {
874 drilldown_data_map[keyLat][keyLng] = [];
875 }
876 drilldown_data_map[keyLat][keyLng].push(coordinates[dm]);
877 drilldown_data_map_vals[coordinates[dm].tweetEntryId.toString()] = coordinates[dm];
878 }
879
880 $.each(drilldown_data_map, function(drillKeyLat, valuesAtLat) {
881 $.each(drilldown_data_map[drillKeyLat], function (drillKeyLng, valueAtLng) {
882
883 // Get subset of drilldown position on map
884 var cposition = new google.maps.LatLng(parseFloat(drillKeyLat), parseFloat(drillKeyLng));
885
886 // Create a marker using the snazzy phone icon
887 var map_tweet_m = new google.maps.Marker({
888 position: cposition,
889 map: map,
890 icon: micon,
891 clickable: true,
892 });
893
894 // Open Tweet exploration window on click
895 google.maps.event.addListener(map_tweet_m, 'click', function (event) {
896 marker_click_function(drilldown_data_map[drillKeyLat][drillKeyLng]);
897 });
898
899 // Add marker to index of tweets
900 map_tweet_markers.push(map_tweet_m);
901
902 });
903 });
904}
905
906
907function existsTweetbook(tweetbook) {
908 if (parseInt($.inArray(tweetbook, review_mode_tweetbooks)) == -1) {
909 return false;
910 } else {
911 return true;
912 }
913}
914
915
916function onCleanPlotTweetbook(records) {
917 var toPlot = [];
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700918
919 // An entry looks like this:
920 // { "tweetId": "273589", "tweetText": " like verizon the network is amazing", "tweetLoc": { point: [37.78, 82.27]}, "tweetCom": "hooray comments" }
921
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700922 for (var entry in records) {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700923
924 var points = records[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
925
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700926 var tweetbook_element = {
927 "tweetEntryId" : parseInt(records[entry].split(",")[0].split(":")[1].split('"')[1]),
928 "tweetText" : records[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700929 "tweetLat" : parseFloat(points[0]),
930 "tweetLng" : parseFloat(points[1]),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700931 "tweetComment" : records[entry].split("tweetCom\": \"")[1].split("\"")[0]
932 };
933 toPlot.push(tweetbook_element);
934 }
935
936 return toPlot;
937}
938
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700939
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700940function onCleanTweetbookDrilldown (rec) {
941
942 var drilldown_cleaned = [];
943
944 for (var entry = 0; entry < rec.length; entry++) {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700945
946 // An entry looks like this:
947 // { "tweetId": "105491", "tweetText": " hate verizon its platform is OMG", "tweetLoc": { point: [30.55, 71.44]} }
948 var points = rec[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
949
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700950 var drill_element = {
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700951 "tweetEntryId" : parseInt(rec[entry].split(",")[0].split(":")[1].replace('"', '')),
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700952 "tweetText" : rec[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
genia.likes.science@gmail.com4833b412013-08-09 06:20:58 -0700953 "tweetLat" : parseFloat(points[0]),
954 "tweetLng" : parseFloat(points[1])
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700955 };
956 drilldown_cleaned.push(drill_element);
957 }
958 return drilldown_cleaned;
959}
960
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -0700961
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700962function onClickTweetbookMapMarker(tweet_arr) {
963 $('#drilldown_modal_body').html('');
964
965 // 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() {
997 $('#review-active').removeClass('active');
998 $('#review-well').hide();
999
1000 $('#explore-active').addClass('active');
1001 $('#explore-well').show();
1002
1003 $("#clear-button").trigger("click");
1004}
1005
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001006
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001007/**
1008* Launching review mode: clear windows/variables, show correct sidebar
1009*/
1010function onLaunchReviewMode() {
1011 $('#explore-active').removeClass('active');
1012 $('#explore-well').hide();
1013 $('#review-active').addClass('active');
1014 $('#review-well').show();
1015
1016 $("#clear-button").trigger("click");
1017}
1018
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001019
1020/** Icon / Interface Utility Methods **/
1021
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001022/**
1023* Creates a delete icon button using default trash icon
genia.likes.science@gmail.com8125bd92013-08-21 18:04:27 -07001024* @param {String} id, id for this element
1025* @param {String} attachTo, id string of an element to which I can attach this button.
1026* @param {Function} onClick, a function to fire when this icon is clicked
1027*/
1028function addDeleteButton(iconId, attachTo, onClick) {
1029 // Icon structure
1030 var trashIcon = '<button class="btn" id="' + iconId + '"><i class="icon-trash"></i></button>';
1031
1032 $('#' + attachTo).append(trashIcon);
1033
1034 // On Click behavior
1035 $('#' + iconId).on('click', onClick);
1036}
1037
1038
genia.likes.science@gmail.comdd669072013-08-21 22:53:34 -07001039/**
1040* Creates a success message and attaches it to a div with provided ID.
1041* @param {String} message, a message to post
1042* @param {String} appendTarget, a target div to which to append the alert
1043*/
1044function addSuccessBlock(message, appendTarget) {
1045
1046 $('<div/>')
1047 .attr("class", "alert alert-success")
1048 .html('<button type="button" class="close" data-dismiss="alert">&times;</button>' + message)
1049 .appendTo('#' + appendTarget);
1050}
1051
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001052/** Map Widget Utility Methods **/
1053
1054/**
1055* Plots a legend onto the map, with values in progress bars
1056* @param {number Array} breakpoints, an array of numbers representing natural breakpoints
1057*/
1058function mapControlWidgetAddLegend(breakpoints) {
1059
1060 // Retriever colors, lightest to darkest
1061 var colors = mapWidgetGetColorPalette();
1062
1063 // Initial div structure
1064 $("#map_canvas_legend").html('<div id="legend-holder"><div id="legend-progress-bar" class="progress"></div><span id="legend-label"></span></div>');
1065
1066 // Add color scale to legend
1067 $('#legend-progress-bar').css("width", "200px").html('');
1068
1069 // Add a progress bar for each color
1070 for (var color in colors) {
1071
1072 // Bar values
1073 var upperBound = breakpoints[parseInt(color) + 1];
1074
1075 // Create Progress Bar
1076 $('<div/>')
1077 .attr("class", "bar")
1078 .attr("id", "pbar" + color)
1079 .css("width" , '25.0%')
1080 .html("< " + upperBound)
1081 .appendTo('#legend-progress-bar');
1082
1083 $('#pbar' + color).css({
1084 "background-image" : 'none',
1085 "background-color" : colors[parseInt(color)]
1086 });
1087
1088 // Attach a message showing minimum bounds
1089 $('#legend-label').html('Regions with at least ' + breakpoints[0] + ' tweets');
1090 $('#legend-label').css({
1091 "margin-top" : 0,
1092 "color" : "black"
1093 });
1094 }
1095
1096 // Add legend to map
1097 map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(document.getElementById('legend-holder'));
1098 $('#map_canvas_legend').show();
1099}
1100
1101/**
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001102* Clears ALL map elements - legend, plotted items, overlays
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001103*/
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001104function mapWidgetResetMap() {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001105
1106 if (selectionRect) {
1107 selectionRect.setMap(null);
1108 selectionRect = null;
1109 }
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001110
1111 mapWidgetClearMap();
1112
1113 // Reset map center and zoom
1114 map.setCenter(new google.maps.LatLng(38.89, -77.03));
1115 map.setZoom(4);
1116}
1117
1118function mapWidgetClearMap() {
1119
1120 // Remove previously plotted data/markers
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001121 for (c in map_cells) {
1122 map_cells[c].setMap(null);
1123 }
1124 map_cells = [];
genia.likes.science@gmail.com65e04182013-10-04 04:31:41 -07001125
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001126 for (m in map_tweet_markers) {
1127 map_tweet_markers[m].setMap(null);
1128 }
1129 map_tweet_markers = [];
genia.likes.science@gmail.com1b30f3d2013-08-17 23:53:37 -07001130
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001131 // Remove legend from map
1132 map.controls[google.maps.ControlPosition.LEFT_BOTTOM].clear();
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001133}
1134
1135/**
1136* Uses jenks algorithm in geostats library to find natural breaks in numeric data
1137* @param {number Array} weights of points to plot
1138* @returns {number Array} array of natural breakpoints, of which the top 4 subsets will be plotted
1139*/
1140function mapWidgetLegendComputeNaturalBreaks(weights) {
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001141
1142 if (weights.length < 10) {
1143 return [0];
1144 }
1145
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001146 var plotDataWeights = new geostats(weights.sort());
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001147 return plotDataWeights.getJenks(6).slice(2,7);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001148}
1149
1150/**
1151* Computes values for map legend given a value and an array of jenks breakpoints
1152* @param {number} weight of point to plot on map
1153* @param {number Array} breakpoints, an array of 5 points corresponding to bounds of 4 natural ranges
1154* @returns {String} an RGB value corresponding to a subset of data
1155*/
1156function mapWidgetLegendGetHeatValue(weight, breakpoints) {
1157
1158 // Determine into which range the weight falls
1159 var weightColor = 0;
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001160
1161 if (breakpoints.length == 1) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001162 weightColor = 2;
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001163 } else {
1164 if (weight >= breakpoints[3]) {
1165 weightColor = 3;
1166 } else if (weight >= breakpoints[2]) {
1167 weightColor = 2;
1168 } else if (weight >= breakpoints[1]) {
1169 weightColor = 1;
1170 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001171 }
1172
1173 // Get default map color palette
1174 var colorValues = mapWidgetGetColorPalette();
1175 return colorValues[weightColor];
1176}
1177
1178/**
1179* Returns an array containing a 4-color palette, lightest to darkest
1180* External palette source: http://www.colourlovers.com/palette/2763366/s_i_l_e_n_c_e_r
1181* @returns {Array} [colors]
1182*/
1183function mapWidgetGetColorPalette() {
1184 return [
1185 "rgb(115,189,158)",
1186 "rgb(74,142,145)",
1187 "rgb(19,93,96)",
1188 "rgb(7,51,46)"
1189 ];
1190}
1191
1192/**
1193* Computes radius for a given data point from a spatial cell
1194* @param {Object} keys => ["latSW" "lngSW" "latNE" "lngNE" "weight"]
1195* @returns {number} radius between 2 points in metres
1196*/
1197function mapWidgetComputeCircleRadius(spatialCell, breakpoints) {
1198
1199 var weight = spatialCell.weight;
genia.likes.science@gmail.com84711b82013-08-22 03:47:41 -07001200
1201 if (breakpoints.length == 1) {
1202 var weightColor = 0.25;
1203 } else {
1204 // Compute weight color
1205 var weightColor = 0.25;
1206 if (weight >= breakpoints[3]) {
1207 weightColor = 1.0;
1208 } else if (weight >= breakpoints[2]) {
1209 weightColor = 0.75;
1210 } else if (weight >= breakpoints[1]) {
1211 weightColor = 0.5;
1212 }
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -07001213 }
1214
1215 // Define Boundary Points
1216 var point_center = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1217 var point_left = new google.maps.LatLng((spatialCell.latSW + spatialCell.latNE)/2.0, spatialCell.lngSW);
1218 var point_top = new google.maps.LatLng(spatialCell.latNE, (spatialCell.lngSW + spatialCell.lngNE)/2.0);
1219
1220 // TODO not actually a weight color :)
1221 return weightColor * 1000 * Math.min(distanceBetweenPoints_(point_center, point_left), distanceBetweenPoints_(point_center, point_top));
1222}
1223
1224/** External Utility Methods **/
1225
1226/**
1227 * Calculates the distance between two latlng locations in km.
1228 * @see http://www.movable-type.co.uk/scripts/latlong.html
1229 *
1230 * @param {google.maps.LatLng} p1 The first lat lng point.
1231 * @param {google.maps.LatLng} p2 The second lat lng point.
1232 * @return {number} The distance between the two points in km.
1233 * @private
1234*/
1235function distanceBetweenPoints_(p1, p2) {
1236 if (!p1 || !p2) {
1237 return 0;
1238 }
1239
1240 var R = 6371; // Radius of the Earth in km
1241 var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
1242 var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
1243 var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
1244 Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
1245 Math.sin(dLon / 2) * Math.sin(dLon / 2);
1246 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1247 var d = R * c;
1248 return d;
1249};