diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/InfoResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/InfoResource.java index 9f1b80250963ceae25d1b38bbc1b7dd302cebe6c..818444c17302b7adfdd8c847dcceb14270bb8055 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/InfoResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/InfoResource.java @@ -75,6 +75,7 @@ public class InfoResource { info.put("const.ktopics", Constants.K_TOPICS); info.put("const.ktopicwords", Constants.K_TOPIC_WORDS); info.put("const.minrelprob", Constants.MINIMUM_RELATIVE_PROB); + info.put("constminshare", Constants.MINIMUM_SHARE); info.put("const.dynminiter", Constants.DYNAMIC_MIN_ITER); info.put("const.dynmaxiter", Constants.DYNAMIC_MAX_ITER); info.put("const.statiter", Constants.STATIC_ITER); diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/lda/DTMAnalyzer.java b/vipra-cmd/src/main/java/de/vipra/cmd/lda/DTMAnalyzer.java index c209d580ee4e2063be5e739edb39136a946b2850..5ac5d47406ce5e251cbcb08196665ca885aca35d 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/lda/DTMAnalyzer.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/lda/DTMAnalyzer.java @@ -316,17 +316,25 @@ public class DTMAnalyzer extends Analyzer { final double[] topicDistribution = topicDistributions[idxArticle++]; // create topic references + double reducedShare = 0; final List<TopicRef> newTopicRefs = new ArrayList<>(Constants.K_TOPICS); for (int idxTopic = 0; idxTopic < Constants.K_TOPICS; idxTopic++) { - final TopicRef newTopicRef = new TopicRef(); - final TopicFull topicFull = newTopics.get(idxTopic); - newTopicRef.setTopic(new Topic(topicFull.getId())); - newTopicRef.setShare(topicDistribution[idxTopic]); - newTopicRefs.add(newTopicRef); + if (topicDistribution[idxTopic] > 0.01) { + reducedShare += topicDistribution[idxTopic]; + final TopicRef newTopicRef = new TopicRef(); + final TopicFull topicFull = newTopics.get(idxTopic); + newTopicRef.setTopic(new Topic(topicFull.getId())); + newTopicRef.setShare(topicDistribution[idxTopic]); + newTopicRefs.add(newTopicRef); + } } // update article if (!newTopicRefs.isEmpty()) { + // renormalize share + for (TopicRef newTopicRef : newTopicRefs) + newTopicRef.setShare(newTopicRef.getShare() / reducedShare); + Collections.sort(newTopicRefs, Comparator.reverseOrder()); // update article with topic references (partial update) diff --git a/vipra-ui/app/html/about.html b/vipra-ui/app/html/about.html index 1a5b2502a86a3c7c237fa8f5061c9f5d38821216..15ba8404ab54343e0e96987d05728e16dacb07ab 100644 --- a/vipra-ui/app/html/about.html +++ b/vipra-ui/app/html/about.html @@ -153,7 +153,17 @@ </tr> <tr class="well"> <td colspan="2"> - The minimum relative probability of topic words. Words are accepted into a topic, if their probability exceeds <it>maximum_probability * minimum_relative_probability</it>. + The minimum relative probability of topic words. Words are accepted into a topic, if their probability exceeds + <it>maximum_probability * minimum_relative_probability</it>. + </td> + </tr> + <tr> + <th>Minimum share</th> + <td ng-bind-template="{{::info.const.minshare}}"></td> + </tr> + <tr class="well"> + <td colspan="2"> + The minimum share of a topic to be accepted for an article. Topic shares are renormalized after rejecting topics below this threshold. </td> </tr> <tr> diff --git a/vipra-ui/app/html/articles/show.html b/vipra-ui/app/html/articles/show.html index 3d0c7e94a199ecbe1bcd32f685c97990b7599e41..d450d474e51a735a71f31c50764c7b86166fa32f 100644 --- a/vipra-ui/app/html/articles/show.html +++ b/vipra-ui/app/html/articles/show.html @@ -8,6 +8,11 @@ Network graph </a> </td> + <td> + <a class="btn btn-default" ui-sref="articles.show.similar({id:article.id})"> + Similar + </a> + </td> </tr> </table> </div> diff --git a/vipra-ui/app/html/articles/similar.html b/vipra-ui/app/html/articles/similar.html new file mode 100644 index 0000000000000000000000000000000000000000..dc2fb0618aa6e5cb7ffdbedf895d23cbbd63a2cc --- /dev/null +++ b/vipra-ui/app/html/articles/similar.html @@ -0,0 +1,14 @@ +<div ng-cloak ng-hide="$state.current.name !== 'articles.show.similar'"> + <div class="page-header"> + <h1 ng-bind="::article.title"></h1> + <table class="item-actions"> + <tr> + <td> + <a class="btn btn-default" ui-sref="articles.show({id:article.id})"> + Back + </a> + </td> + </tr> + </table> + </div> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/topics/show.html b/vipra-ui/app/html/topics/show.html index b94a20787453764bba6fb9d2d78192d908d36558..292542a1cf3a4962482b16fb73216069ed77654c 100644 --- a/vipra-ui/app/html/topics/show.html +++ b/vipra-ui/app/html/topics/show.html @@ -54,7 +54,7 @@ </table> </div> </div> - <div ng-show="topic.dynamic"> + <div ng-if="!topic.dynamic"> <h3>Words <hide-link target="#words"/></h3> <div class="row" id="words"> <div class="col-md-12"> @@ -81,5 +81,28 @@ </div> </div> </div> + <div ng-if="topic.dynamic"> + <h3>Relevance over time <hide-link target="#relevance"/></h3> + <div id="relevance"> + <div class="row"> + <div class="col-md-12"> + <div class="well well-sm"> + <label class="radio-inline"> + <input type="radio" name="seqstyle" ng-model="seqstyle" value="absolute" ng-model-store="seqStyle" ng-model-default="'absolute'">Absolute + </label> + <label class="radio-inline"> + <input type="radio" name="seqstyle" ng-model="seqstyle" value="relative" ng-model-store="seqStyle">Relative + </label> + </div> + </div> + </div> + <br> + <div class="row"> + <div class="col-md-12"> + <div class="area-chart" id="topic-seq" highcharts="topicSeq"></div> + </div> + </div> + </div> + </div> </div> <div ng-cloak ui-view></div> diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js index df5074773c5f33a30b3af8e4ab68e861217460c7..fe5fbd1c5e6ea5f7426b62594ef2b970b6ba7101 100644 --- a/vipra-ui/app/js/app.js +++ b/vipra-ui/app/js/app.js @@ -74,6 +74,15 @@ } }); + $stateProvider.state('articles.show.similar', { + url: '/similar', + templateUrl: 'html/articles/similar.html', + controller: 'ArticlesSimilarController', + ncyBreadcrumb: { + label: 'Similar' + } + }); + // states: topics $stateProvider.state('topics', { diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index b74f779177f7b809f65032696e5a632a5d2c79f1..0658190d7980963ce5c09151d6f80b63ca14010c 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -418,6 +418,36 @@ $scope.topic = data; $scope.topicCreated = Vipra.formatDateTime($scope.topic.created); $scope.topicModified = Vipra.formatDateTime($scope.topic.modified); + + if($scope.topic.dynamic) { + var relevances = []; + for(var i = 0, s; i < $scope.topic.sequences.length; i++) { + s = $scope.topic.sequences[i]; + relevances.push([new Date(s.window.startDate).getTime(), s.relevance]); + } + $scope.topicSeq = { + chart: { + type: 'areaspline', + zoomType: 'x', + spacingLeft: 0, + spacingRight: 0 + }, + title: { text: '' }, + xAxis: { + type: 'datetime', + title: { text: '' } + }, + yAxis: { title: { text: 'Relevance' } }, + tooltip: { + headerFormat: '', + pointFormat: '{point.x:%Y}: {point.y:.4f}' + }, + legend: { enabled: false }, + credits: { enabled: false }, + plotOptions: { areaspline: { fillOpacity: 0.5 } }, + series: [ { name: $scope.topic.name, data: relevances } ] + }; + } }, function(err) { $scope.errors = err; }); diff --git a/vipra-util/src/main/java/de/vipra/util/Constants.java b/vipra-util/src/main/java/de/vipra/util/Constants.java index dac5d0e553e81c68374b0f3930076434489870c8..d245aa3eef31f41056a76a75a4553d1b7447da45 100644 --- a/vipra-util/src/main/java/de/vipra/util/Constants.java +++ b/vipra-util/src/main/java/de/vipra/util/Constants.java @@ -84,6 +84,12 @@ public class Constants { */ public static final double MINIMUM_RELATIVE_PROB = 0.01; + /** + * The minimum share of a topic to be accepted for an article. Topic shares + * are renormalized after rejecting topics below this threshold. + */ + public static final double MINIMUM_SHARE = 0.01; + /** * Dynamic minimum iterations. Used for dynamic topic modeling. Default 100. */