Skip to content

Commit

Permalink
Display sentiment heatmap (#2)
Browse files Browse the repository at this point in the history
* Render the sentiment heatmap in the ui

* Create HeatmapButton directive to switch between normal and sentiment heatmap

* Tweet panel updated with sentiment info

* Refactor gradient color arrays
  • Loading branch information
Lenninlasd authored Apr 21, 2017
1 parent c134f38 commit 7535b2b
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 25 deletions.
1 change: 1 addition & 0 deletions app/components/buttonPanel/buttonPanel.tpl.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<dataverse-button></dataverse-button>
<export-button></export-button>
<basemap-button></basemap-button>
<heatmap-button></heatmap-button>
<reset-button></reset-button>
</div>
29 changes: 25 additions & 4 deletions app/components/heatmap/heatmapDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,35 @@
(function() {
angular
.module('search_heatmap_component', [])
.directive('heatmap', heatmap);

function heatmap() {
.directive('heatmap', [function() {
return {
restrict: 'EA',
templateUrl: 'components/heatmap/heatmap.tpl.html',
scope: {}
};
}
}])
.directive('heatmapButton', ['searchFilter', 'HeatMapSourceGenerator',
function(searchFilter, HeatMapSourceGenerator) {
return {
link: link,
template: '<button class="btn btn-default from-panel" type="button" ' +
'ng-click="toggleHeatmap()">{{buttonName}}</button>',
scope: {}
};
function link(scope) {
var vm = scope;
vm.buttonName = 'SENTIMENT HEATMAP';
vm.toggleHeatmap = function() {
var heatmapSentiment = !searchFilter.posSent;
vm.buttonName = heatmapSentiment ? 'TWEETS HEATMAP' : 'SENTIMENT HEATMAP';
searchFilter.setFilter({
posSent: heatmapSentiment
});
HeatMapSourceGenerator.search();
};
}
}]);



})();
5 changes: 5 additions & 0 deletions app/components/tweetlist/tweet.less
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
margin:-10px 0 0 0;
}

.tweet-emoji {
font-size: 20px !important;
margin-top: -4px;
}

.height-tweet {
height: 260px;
overflow-y: auto;
Expand Down
8 changes: 8 additions & 0 deletions app/components/tweetlist/tweetPanel.tpl.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,12 @@
<dt>User Name</dt>
<dd>{{selectedTweet.user_name}}</dd>
</div>
<div class="list-group-item">
<dt>Geo position</dt>
<dd>{{selectedTweet.geoadmin_admin2_txt}}</dd>
</div>
<div class="list-group-item">
<dt>Sentiment</dt>
<dd class="tweet-emoji">{{selectedTweet.sentiment_pos ? "😀" : "☹️"}}</dd>
</div>
</dl>
20 changes: 11 additions & 9 deletions app/service/HeatMapSourceGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@
'q.time': timeTextFormat(sF.time, sF.minDate, sF.maxDate),
'q.geo': sF.geo,
'a.hm.filter': sF.hm,
'a.hm.posSent': sF.posSent,
'a.time.limit': '1',
'a.time.gap': sF.gap,
'd.docs.limit': sF.numOfDocs,
'a.text.limit': sF.textLimit,
'a.user.limit': sF.userLimit,
'd.docs.sort': 'distance'
'd.docs.sort': 'distance',
'a.hm.limit': solrHeatmapApp.bopwsConfig.heatmapFacetLimit
};
}

Expand All @@ -70,8 +72,6 @@
if (params) {
canceler.resolve();
canceler = $q.defer();

params['a.hm.limit'] = solrHeatmapApp.bopwsConfig.heatmapFacetLimit;
config = {
url: solrHeatmapApp.appConfig.tweetsSearchBaseUrl,
method: 'GET',
Expand Down Expand Up @@ -102,17 +102,19 @@

function broadcastData(data) {
data['a.text'] = data['a.text'] || [];
var heatmapData = {};
if (data && data['a.hm']) {
MapService.createOrUpdateHeatMapLayer(data['a.hm']);

if (data['a.hm.posSent']) {
heatmapData = data['a.hm.posSent'];
heatmapData.posSent = true;
}else {
heatmapData = data['a.hm'];
}
MapService.createOrUpdateHeatMapLayer(heatmapData);
$rootScope.$broadcast('setCounter', data['a.matchDocs']);

$rootScope.$broadcast('setHistogram', data['a.time']);

$rootScope.$broadcast('setTweetList', data['d.docs']);

$rootScope.$broadcast('setSuggestWords', data['a.text']);

$rootScope.$broadcast('setUserSuggestWords', data['a.user']);
}
}
Expand Down
16 changes: 9 additions & 7 deletions app/service/Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@
flattenCount.push.apply(flattenCount, row);
});
var series = new geostats(flattenCount);
var numberOfClassifications = hmParams.gradientArray.length - 5;
var gradientLength = hmParams.gradientArray.length;
var numberOfClassifications = gradientLength - Math.ceil(gradientLength*0.4);
return series.getClassJenks(numberOfClassifications);
}

Expand Down Expand Up @@ -337,12 +338,13 @@

service.createOrUpdateHeatMapLayer = function(hmData) {
var existingHeatMapLayers, transformInteractionLayer, olVecSrc, newHeatMapLayer;

var sentimetGradient = ['#ff0000', '#ff0000', '#ff0000','#ff0088', '#ff0088',
'#ff00ff', '#8800ff', '#0000ff', '#0000ff', '#0077ff', '#00aaff', '#00ddff'];
var normalCountGradient = ['#000000', '#0000df', '#0000df', '#00effe', '#00effe',
'#00ff42', ' #00ff42', '#00ff42', '#feec30', '#ff5f00', '#ff0000'];
hmData.heatmapRadius = 20;
hmData.blur = 6;
hmData.gradientArray = ['#000000', '#0000df', '#0000df', '#00effe',
'#00effe', '#00ff42',' #00ff42', '#00ff42',
'#feec30', '#ff5f00', '#ff0000'];
hmData.blur = 10;
hmData.gradientArray = hmData.posSent ? sentimetGradient : normalCountGradient;

existingHeatMapLayers = service.getLayersBy('name', 'HeatMapLayer');
transformInteractionLayer = service.getLayersBy('name',
Expand All @@ -357,7 +359,7 @@
layerSrc.clear();
}
currHeatmapLayer.setSource(olVecSrc);
// currHeatmapLayer.setRadius(hmData.heatmapRadius);
currHeatmapLayer.setGradient(hmData.gradientArray);
} else {
newHeatMapLayer = new ol.layer.Heatmap({
name: 'HeatMapLayer',
Expand Down
4 changes: 4 additions & 0 deletions app/service/searchFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
userLimit: null,
numOfDocs: 50,
gap: 'P1W',
posSent: false,
minDate: new Date(moment().subtract(3, 'months').format('YYYY-MM-DD')),
maxDate: new Date(moment().format('YYYY-MM-DD'))
};
Expand All @@ -40,6 +41,9 @@
if (filter.hm) {
service.hm = filter.hm;
}
if (angular.isDefined(filter.posSent)) {
service.posSent = filter.posSent;
}
};

service.resetFilter = function() {
Expand Down
4 changes: 2 additions & 2 deletions tests/service/HeatMapSourceGenerator.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ describe( 'HeatMapSourceGenerator', function() {
geospatialFilter = {queryGeo: { minX: 1, maxX: 1, minY: 1, maxY: 1}};
searchFilter.minDate = new Date('2016-12-10');
searchFilter.maxDate = new Date('2016-12-21');
exportRequest = $httpBackend.when('GET', '/search?a.hm.filter=%5B-90,-180+TO+90,180%5D&a.time.gap=P1W&a.time.limit=1&d.docs.limit=50&d.docs.sort=distance&q.geo=%5B-90,-180+TO+90,180%5D&q.time=%5B2016-12-10T00:00:00+TO+2016-12-21T00:00:00%5D').respond('');
exportRequest = $httpBackend.when('GET', '/search?a.hm.filter=%5B-90,-180+TO+90,180%5D&a.hm.posSent=false&a.time.gap=P1W&a.time.limit=1&d.docs.limit=50&d.docs.sort=distance&q.geo=%5B-90,-180+TO+90,180%5D&q.time=%5B2016-12-10T00:00:00+TO+2016-12-21T00:00:00%5D').respond('');
});
afterEach(function() {
$httpBackend.resetExpectations();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('sends the search request', function() {
$httpBackend.expectGET('/search?a.hm.filter=%5B-90,-180+TO+90,180%5D&a.time.gap=P1W&a.time.limit=1&d.docs.limit=50&d.docs.sort=distance&q.geo=%5B-90,-180+TO+90,180%5D&q.time=%5B2016-12-10T00:00:00+TO+2016-12-21T00:00:00%5D').respond('');
$httpBackend.expectGET('/search?a.hm.filter=%5B-90,-180+TO+90,180%5D&a.hm.posSent=false&a.time.gap=P1W&a.time.limit=1&d.docs.limit=50&d.docs.sort=distance&q.geo=%5B-90,-180+TO+90,180%5D&q.time=%5B2016-12-10T00:00:00+TO+2016-12-21T00:00:00%5D').respond('');
subject.search();
$httpBackend.flush();
});
Expand Down
6 changes: 3 additions & 3 deletions tests/service/Map.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,10 @@ describe( 'HeatMapSourceGenerator', function() {
describe('#createOrUpdateHeatMapLayer', function() {
var data, layerSpy, setSourceSpy, setRadiusSpy;
beforeEach(function() {
var clearSourceSpy = jasmine.createSpyObj('getSource', ['clear']);
layer = { getFilters: function() { return [{ setActive: function() {}}]; }, setSource: function() {}, setRadius: function() {}, getSource: function() { return { clear: function() {}, getFeatures: function() { return [{getGeometry: function() { return { getExtent: function() { return [0,0,0,0];}};}}];}}; }};
var clearSourceSpy = jasmine.createSpyObj('getSource', ['clear']);layer = { getFilters: function() {return [{ setActive: function() {}}];}, setSource: function() {}, setRadius: function() {}, setGradient: function() {}, getSource: function() { return { clear: function() {}, getFeatures: function() { return [{getGeometry: function() { return { getExtent: function() { return [0,0,0,0];}};}}];}}; }};
setSourceSpy = spyOn(layer, 'setSource');
setRadiusSpy = spyOn(layer, 'setRadius');
spyOn(layer, 'setRadius');
spyOn(layer, 'setGradient');
data = { columns: 4, rows: 4, gridLevel: 2, maxX: 1, maxY: 1, minY: 0, minX: 0,
projection: 'EPSG:4326', counts_ints2D: [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]};
});
Expand Down

0 comments on commit 7535b2b

Please sign in to comment.