From 8b456388dc547f894d1ec17d769710f64f056929 Mon Sep 17 00:00:00 2001
From: Eike Cochu <eike@cochu.com>
Date: Thu, 24 Mar 2016 17:09:46 +0100
Subject: [PATCH] added explorer topic hover series highlight

---
 .../main/java/de/vipra/cmd/lda/Analyzer.java  |   6 +-
 vipra-ui/app/html/explorer.html               |   4 +-
 vipra-ui/app/js/app.js                        |   2 +-
 vipra-ui/app/js/config.js                     |   2 +-
 vipra-ui/app/js/controllers.js                | 124 ++++++++++++------
 vipra-ui/app/js/directives.js                 |   2 +-
 vipra-ui/app/js/factories.js                  |  16 ++-
 vipra-ui/app/js/helpers.js                    |   2 +-
 vipra-ui/app/less/app.less                    |   6 +-
 .../java/de/vipra/util/model/TopicWord.java   |   7 +-
 10 files changed, 111 insertions(+), 60 deletions(-)

diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java b/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java
index f3398bb1..b8b3d78a 100644
--- a/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java
+++ b/vipra-cmd/src/main/java/de/vipra/cmd/lda/Analyzer.java
@@ -258,10 +258,10 @@ public class Analyzer {
 
 				// calculate topic sequence relevance
 				final int sequenceSize = windowIndex.windowSize(idxSeq);
-				double seqTopicDistribution = 0;
+				double seqTopicDistributionSum = 0;
 				for (int idxArticle = sequenceOffset; idxArticle < sequenceOffset + sequenceSize; idxArticle++)
-					seqTopicDistribution += topicDistributions[idxArticle][idxTopic];
-				final double relevance = seqTopicDistribution / sequenceSize;
+					seqTopicDistributionSum += topicDistributions[idxArticle][idxTopic];
+				final double relevance = seqTopicDistributionSum / sequenceSize;
 
 				// create sequence
 				final SequenceFull newSequenceFull = new SequenceFull();
diff --git a/vipra-ui/app/html/explorer.html b/vipra-ui/app/html/explorer.html
index ae00c9a1..69563f7f 100644
--- a/vipra-ui/app/html/explorer.html
+++ b/vipra-ui/app/html/explorer.html
@@ -21,7 +21,7 @@
       <span class="glyphicon glyphicon-remove-circle searchclear" ng-click="search=''"></span>
     </div>
     <ul class="list-unstyled topic-choice">
-      <li ng-repeat="topic in topics | orderBy:explorerModels.sorttopics:explorerModels.sortdir | filter:search">
+      <li ng-repeat="topic in topics | orderBy:explorerModels.sorttopics:explorerModels.sortdir | filter:search" ng-mouseenter="highlightSeries(topic.id, true)" ng-mouseleave="highlightSeries(topic.id, false)">
         <div class="checkbox checkbox-condensed" ng-class="{selected:topic.selected}">
           <span class="valuebar" ng-style="{width:topic.topicCurrValue}"></span>
           <input type="checkbox" ng-model="topic.selected" ng-attr-id="{{::topic.id}}" ng-change="redrawGraph()">
@@ -58,7 +58,7 @@
           <a class="btn btn-sm btn-default" ng-model="explorerModels.chartstack" bs-radio="'percent'">Percent</a>
         </div>
       </div>
-      <div class="chart" highcharts="topicSeq"></div>
+      <div id="topicRelChart" class="chart" highcharts="topicSeq"></div>
       <div class="message text-muted" ng-show="!topicsSelected">No topic selected</div>
     </div>
   </div>
diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js
index 9b0ad989..8d5da3d6 100644
--- a/vipra-ui/app/js/app.js
+++ b/vipra-ui/app/js/app.js
@@ -132,4 +132,4 @@
 
   }]);
 
-})();
+})();
\ No newline at end of file
diff --git a/vipra-ui/app/js/config.js b/vipra-ui/app/js/config.js
index 8ca1c7df..f61fc16e 100644
--- a/vipra-ui/app/js/config.js
+++ b/vipra-ui/app/js/config.js
@@ -13,4 +13,4 @@
     restUrl: '/vipra/rest'
   };
 
-})();
+})();
\ No newline at end of file
diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js
index f0975308..2c10e42e 100644
--- a/vipra-ui/app/js/controllers.js
+++ b/vipra-ui/app/js/controllers.js
@@ -20,15 +20,15 @@
         topicModel: null
       };
 
-      if(localStorage.topicModel) {
+      if (localStorage.topicModel) {
         try {
           var topicModel = JSON.parse(localStorage.topicModel);
           TopicModelFactory.get({
             id: topicModel.id
           }, function(data) {
-            $scope.rootModels.topicModel = data; 
+            $scope.rootModels.topicModel = data;
           });
-        } catch(e) {}
+        } catch (e) {}
       }
 
       TopicModelFactory.query({
@@ -59,14 +59,14 @@
     function($scope, $location, ArticleFactory, TopicFactory, SearchFactory) {
 
       // page was reloaded, choose topic model
-      if(!$scope.rootModels.topicModel)
+      if (!$scope.rootModels.topicModel)
         $scope.chooseTopicModel();
 
       $scope.search = $location.search().query;
 
       $scope.$watch('rootModels.topicModel', function() {
-        if(!$scope.rootModels.topicModel) return;
-        
+        if (!$scope.rootModels.topicModel) return;
+
         ArticleFactory.query({
           topicModel: $scope.rootModels.topicModel.id,
           limit: 3,
@@ -153,7 +153,9 @@
       $scope.type = $stateParams.type;
       $scope.options = {
         nodes: {
-          font: { size: 14 },
+          font: {
+            size: 14
+          },
           shape: 'dot',
           borderWidth: 0
         },
@@ -162,7 +164,9 @@
             highlight: '#f00'
           }
         },
-        layout: { randomSeed: 1 },
+        layout: {
+          randomSeed: 1
+        },
         physics: {
           barnesHut: {
             springConstant: 0.008,
@@ -187,7 +191,7 @@
 
       // if the topic model is not set, the page was refreshed
       // set to true, id of current node decides topic model
-      if(!$scope.rootModels.topicModel)
+      if (!$scope.rootModels.topicModel)
         $scope.rootModels.topicModel = true;
 
       // get root node
@@ -210,7 +214,7 @@
         $scope.graph.on('doubleClick', $scope.open);
 
         // take topic model from node
-        if(!angular.isObject($scope.rootModels.topicModel))
+        if (!angular.isObject($scope.rootModels.topicModel))
           $scope.rootModels.topicModel = data.topicModel;
       }, function(err) {
         $scope.errors = err;
@@ -228,7 +232,9 @@
           shape: shape || 'dot',
           color: {
             background: color,
-            highlight: { background: color }
+            highlight: {
+              background: color
+            }
           }
         };
       };
@@ -270,11 +276,17 @@
             if (ids.hasOwnProperty(current.id)) {
               if (edgeExists(ids[current.id], node.id))
                 continue;
-              newEdges.push({ from: ids[current.id], to: node.id });
+              newEdges.push({
+                from: ids[current.id],
+                to: node.id
+              });
               addEdge(ids[current.id], node.id);
             } else {
               newNodes.push(nodeFunction(current));
-              newEdges.push({ from: id, to: node.id });
+              newEdges.push({
+                from: id,
+                to: node.id
+              });
               addEdge(id, node.id);
             }
           }
@@ -325,11 +337,13 @@
       $scope.open = function(props) {
         $timeout.cancel(selectTimeout);
         var node = $scope.nodes.get(props.nodes[0]);
-        $state.transitionTo(node.show, { id: node.dbid });
+        $state.transitionTo(node.show, {
+          id: node.dbid
+        });
       };
 
       $scope.$watch('rootModels.topicModel', function(newVal) {
-        if($scope.rootNode && $scope.rootNode.topicModel.id !== newVal.id)
+        if ($scope.rootNode && $scope.rootNode.topicModel.id !== newVal.id)
           $state.transitionTo('index');
       });
     }
@@ -339,7 +353,7 @@
     function($scope, $templateCache, $timeout, TopicFactory) {
 
       // page was reloaded, choose topic model
-      if(!$scope.rootModels.topicModel)
+      if (!$scope.rootModels.topicModel)
         $scope.chooseTopicModel();
 
       var avgMax = 0,
@@ -358,14 +372,17 @@
       };
 
       $scope.$watch('rootModels.topicModel', function() {
-        if(!$scope.rootModels.topicModel) return;
+        if (!$scope.rootModels.topicModel) return;
 
         TopicFactory.query({
           fields: 'name,sequences,avgRelevance,varRelevance,risingRelevance,fallingRelevance,risingDecayRelevance',
           topicModel: $scope.rootModels.topicModel.id
         }, function(data) {
           $scope.topics = data;
-          var colors = randomColor({ count: $scope.topics.length, seed: 1 });
+          var colors = randomColor({
+            count: $scope.topics.length,
+            seed: 1
+          });
           for (var i = 0, t; i < $scope.topics.length; i++) {
             t = $scope.topics[i];
             t.color = colors[i];
@@ -414,10 +431,12 @@
             }
 
             series.push({
+              id: topic.id,
               name: topic.name,
               data: relevances,
               color: topic.color,
-              topic: topic
+              topic: topic,
+              zIndex: i + 1
             });
           }
         }
@@ -450,15 +469,35 @@
         return (percent * 100) + '%';
       };
 
+      var topicRelChartElement = $('#topicRelChart');
+      $scope.highlightSeries = function(id, toggle) {
+        if (!$scope.topicsSelected) return;
+        var highcharts = topicRelChartElement.highcharts();
+        if (!highcharts) return;
+        var series = highcharts.get(id);
+        if (!series) return;
+
+        if (toggle) {
+          series.onMouseOver();
+          series.group.zIndexOrig = series.group.zIndex;
+          series.group.zIndexSetter(9999, 'zIndex');
+          series.graph.attr('stroke', '#000000').attr('stroke-dasharray', '5,5');
+        } else {
+          series.onMouseOut();
+          series.group.zIndexSetter(series.group.zIndexOrig, 'zIndex');
+          series.graph.attr('stroke', series.color).attr('stroke-dasharray', '');
+        }
+      };
+
       $scope.$watchGroup(['explorerModels.seqstyle', 'explorerModels.chartstyle', 'explorerModels.chartstack'], $scope.redrawGraph);
 
       $scope.$watch('explorerModels.sorttopics', function() {
-        if(!$scope.topics) return;
+        if (!$scope.topics) return;
 
         $timeout(function() {
-          for(var i = 0; i < $scope.topics.length; i++)
+          for (var i = 0; i < $scope.topics.length; i++)
             $scope.topics[i].topicCurrValue = $scope.topicCurrValue($scope.topics[i]);
-        }, 100);
+        }, 0);
       });
     }
   ]);
@@ -474,7 +513,7 @@
     function($scope, $state, $location, ArticleFactory) {
 
       // page was reloaded, choose topic model
-      if(!$scope.rootModels.topicModel && $state.current.name === 'articles')
+      if (!$scope.rootModels.topicModel && $state.current.name === 'articles')
         $scope.chooseTopicModel();
 
       $scope.articlesIndexModels = {
@@ -485,7 +524,7 @@
       };
 
       $scope.$watchGroup(['articlesIndexModels.page', 'articlesIndexModels.sortkey', 'articlesIndexModels.sortdir', 'rootModels.topicModel'], function() {
-        if(!$scope.rootModels.topicModel) return;
+        if (!$scope.rootModels.topicModel) return;
 
         ArticleFactory.query({
           skip: ($scope.articlesIndexModels.page - 1) * $scope.articlesIndexModels.limit,
@@ -523,15 +562,19 @@
         $scope.articleModified = Vipra.formatDateTime($scope.article.modified);
 
         // take topic model from article
-        if(!angular.isObject($scope.rootModels.topicModel))
+        if (!angular.isObject($scope.rootModels.topicModel))
           $scope.rootModels.topicModel = data.topicModel;
 
         // calculate percentage share
         if ($scope.article.topics) {
           var topicShareSeries = [],
             topics = $scope.article.topics,
-            maximum = { y: 0 },
-            colors = randomColor({ count: $scope.article.topics.length });
+            maximum = {
+              y: 0
+            },
+            colors = randomColor({
+              count: $scope.article.topics.length
+            });
           for (var i = 0, d; i < topics.length; i++) {
             d = {
               name: topics[i].topic.name,
@@ -563,7 +606,7 @@
       });
 
       $scope.$watch('rootModels.topicModel', function(newVal) {
-        if($scope.article && $scope.article.topicModel.id !== newVal.id)
+        if ($scope.article && $scope.article.topicModel.id !== newVal.id)
           $state.transitionTo('index');
       });
     }
@@ -580,7 +623,7 @@
     function($scope, $state, $location, TopicFactory) {
 
       // page was reloaded, choose topic model
-      if(!$scope.rootModels.topicModel && $state.current.name === 'topics')
+      if (!$scope.rootModels.topicModel && $state.current.name === 'topics')
         $scope.chooseTopicModel();
 
       $scope.topicsIndexModels = {
@@ -591,7 +634,7 @@
       };
 
       $scope.$watchGroup(['topicsIndexModels.page', 'topicsIndexModels.sortkey', 'topicsIndexModels.sortdir', 'rootModels.topicModel'], function() {
-        if(!$scope.rootModels.topicModel) return;
+        if (!$scope.rootModels.topicModel) return;
 
         TopicFactory.query({
           topicModel: $scope.rootModels.topicModel.id,
@@ -630,7 +673,7 @@
         $scope.topicModified = Vipra.formatDateTime($scope.topic.modified);
 
         // take topic model from topic
-        if(!angular.isObject($scope.rootModels.topicModel))
+        if (!angular.isObject($scope.rootModels.topicModel))
           $scope.rootModels.topicModel = data.topicModel;
 
         $timeout(function() {
@@ -652,7 +695,10 @@
         }
 
         // highcharts configuration
-        $scope.topicSeq = areaRelevanceChart([{ name: $scope.topic.name, data: relevances }], $scope.topicsShowModels.chartstyle);
+        $scope.topicSeq = areaRelevanceChart([{
+          name: $scope.topic.name,
+          data: relevances
+        }], $scope.topicsShowModels.chartstyle);
       };
 
       $scope.startRename = function() {
@@ -673,10 +719,10 @@
             $scope.isRename = false;
 
             // if topic list of parent view is loaded, replace name by new name
-            if($scope.$parent.topics) {
-              for(var i = 0, topic; i < $scope.$parent.topics.length; i++) {
+            if ($scope.$parent.topics) {
+              for (var i = 0, topic; i < $scope.$parent.topics.length; i++) {
                 topic = $scope.$parent.topics[i];
-                if(topic.id === data.id) {
+                if (topic.id === data.id) {
                   break;
                 }
               }
@@ -701,7 +747,7 @@
       $scope.$watch('topicsShowModels.chartstyle', $scope.redrawGraph);
 
       $scope.$watchGroup(['sequenceId'], function() {
-        if(!$scope.sequenceId) return;
+        if (!$scope.sequenceId) return;
 
         SequenceFactory.get({
           id: $scope.sequenceId,
@@ -714,7 +760,7 @@
       });
 
       $scope.$watch('rootModels.topicModel', function(newVal) {
-        if($scope.topic && $scope.topic.topicModel.id !== newVal.id)
+        if ($scope.topic && $scope.topic.topicModel.id !== newVal.id)
           $state.transitionTo('index');
       });
     }
@@ -845,6 +891,8 @@
           allowPointSelect: true,
           states: {
             hover: {
+              color: '#ff0000',
+              lineWidth: 4,
               halo: {
                 size: 9,
                 attributes: {
@@ -892,4 +940,4 @@
     };
   }
 
-})();
+})();
\ No newline at end of file
diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js
index ad6d4f1b..c1db0a2f 100644
--- a/vipra-ui/app/js/directives.js
+++ b/vipra-ui/app/js/directives.js
@@ -214,4 +214,4 @@
     };
   }]);
 
-})();
+})();
\ No newline at end of file
diff --git a/vipra-ui/app/js/factories.js b/vipra-ui/app/js/factories.js
index 97887862..e4076cbd 100644
--- a/vipra-ui/app/js/factories.js
+++ b/vipra-ui/app/js/factories.js
@@ -11,14 +11,22 @@
 
   app.factory('ArticleFactory', ['$resource', function($resource) {
     return $resource(Vipra.config.restUrl + '/articles/:id', {}, {
-      similar: { isArray: true, url: Vipra.config.restUrl + '/articles/:id/similar' }
+      similar: {
+        isArray: true,
+        url: Vipra.config.restUrl + '/articles/:id/similar'
+      }
     });
   }]);
 
   app.factory('TopicFactory', ['$resource', function($resource) {
     return $resource(Vipra.config.restUrl + '/topics/:id', {}, {
-      update: { method: 'PUT' },
-      articles: { isArray: true, url: Vipra.config.restUrl + '/topics/:id/articles' }
+      update: {
+        method: 'PUT'
+      },
+      articles: {
+        isArray: true,
+        url: Vipra.config.restUrl + '/topics/:id/articles'
+      }
     });
   }]);
 
@@ -38,4 +46,4 @@
     return $resource(Vipra.config.restUrl + '/topicmodels/:id');
   }]);
 
-})();
+})();
\ No newline at end of file
diff --git a/vipra-ui/app/js/helpers.js b/vipra-ui/app/js/helpers.js
index 5970ba06..267d5d42 100644
--- a/vipra-ui/app/js/helpers.js
+++ b/vipra-ui/app/js/helpers.js
@@ -96,4 +96,4 @@
       log: function() {}
     };
 
-})();
+})();
\ No newline at end of file
diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less
index ed9d71d3..cb1d41d3 100644
--- a/vipra-ui/app/less/app.less
+++ b/vipra-ui/app/less/app.less
@@ -204,7 +204,7 @@ a:hover {
 .colorbox {
   display: inline-block;
   width: 10px;
-  height: 20px;
+  height: 21px;
   vertical-align: middle;
   float: right;
   border-radius: 3px;
@@ -215,8 +215,8 @@ a:hover {
   display: inline-block;
   position: absolute;
   top: 0;
-  left: 0;
-  height: 100%;
+  right: 0;
+  height: 21px;
   width: 0px;
   background: @valuebg;
   transition: width .5s linear;
diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicWord.java b/vipra-util/src/main/java/de/vipra/util/model/TopicWord.java
index 4355d78a..a69bd3ad 100644
--- a/vipra-util/src/main/java/de/vipra/util/model/TopicWord.java
+++ b/vipra-util/src/main/java/de/vipra/util/model/TopicWord.java
@@ -55,12 +55,7 @@ public class TopicWord implements Comparable<TopicWord>, Serializable {
 
 	@Override
 	public int compareTo(final TopicWord o) {
-		final double l = likeliness - o.getLikeliness();
-		if (l > 0)
-			return 1;
-		if (l < 0)
-			return -1;
-		return 0;
+		return Double.compare(likeliness, o.getLikeliness());
 	}
 
 	@Override
-- 
GitLab