From af9b05d424b692e191e2bf0db315b6b2824883fe Mon Sep 17 00:00:00 2001 From: Eike Cochu <eike@cochu.com> Date: Mon, 28 Mar 2016 22:48:54 +0200 Subject: [PATCH] added word evolution to topic show --- .../main/java/de/vipra/cmd/lda/Analyzer.java | 8 ++- vipra-ui/app/html/articles/show.html | 3 ++ vipra-ui/app/html/topics/show.html | 49 +++++++++-------- vipra-ui/app/js/controllers.js | 53 ++++++++++++++++++- vipra-ui/app/less/app.less | 14 +++++ .../java/de/vipra/util/model/TopicWord.java | 10 ++++ 6 files changed, 110 insertions(+), 27 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 ff261324..536eb3f5 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 @@ -301,9 +301,15 @@ public class Analyzer { final TopicWord newTopicWord = new TopicWord(); newTopicWord.setWord(sequenceWord.getWord()); final List<Double> sequenceProbabilities = new ArrayList<>(windowCount); - for (final double probability : probabilities[wordIndex.index(sequenceWord.getWord())]) + final List<Double> sequenceProbabilitiesChange = new ArrayList<>(windowCount); + double prevProbability = 0; + for (final double probability : probabilities[wordIndex.index(sequenceWord.getWord())]) { sequenceProbabilities.add(probability); + sequenceProbabilitiesChange.add(probability - prevProbability); + prevProbability = probability; + } newTopicWord.setSequenceProbabilities(sequenceProbabilities); + newTopicWord.setSequenceProbabilitiesChange(sequenceProbabilitiesChange); newTopicWords.add(newTopicWord); } newTopic.setWords(newTopicWords); diff --git a/vipra-ui/app/html/articles/show.html b/vipra-ui/app/html/articles/show.html index d5c2dc0b..6c682de6 100644 --- a/vipra-ui/app/html/articles/show.html +++ b/vipra-ui/app/html/articles/show.html @@ -69,6 +69,9 @@ <small class="text-muted percent-align" ng-bind-template="({{(topic.share*100).toFixed(0)}}%)"></small> <topic-link topic="topic.topic" /> </li> + <li class="text-muted" ng-show="!article.topics"> + None + </li> </ul> <span class="text-muted" ng-hide="article.topics.length > 0">No topics</span> <div class="pie-chart" id="topic-share" highcharts="topicShare"></div> diff --git a/vipra-ui/app/html/topics/show.html b/vipra-ui/app/html/topics/show.html index 73e3d2eb..c94f2b37 100644 --- a/vipra-ui/app/html/topics/show.html +++ b/vipra-ui/app/html/topics/show.html @@ -69,9 +69,12 @@ <a class="btn btn-sm btn-default" ng-model="topicsShowModels.relChartstyle" bs-radio="'areaspline'">Area</a> <a class="btn btn-sm btn-default" ng-model="topicsShowModels.relChartstyle" bs-radio="'spline'">Line</a> </div> + <div class="pull-right"> + <a tabindex="0" class="btn btn-sm btn-default" ng-click="resetRelZoom()">Reset zoom</a> + </div> </div> <div class="panel-body"> - <div class="area-chart" id="topic-seq" highcharts="topicSeq"></div> + <div class="chart area-chart" id="topicRelChart" highcharts="topicSeq"></div> </div> </div> </div> @@ -92,38 +95,34 @@ <a class="btn btn-sm btn-default" ng-model="topicsShowModels.wordChartstyle" bs-radio="'areaspline'">Area</a> <a class="btn btn-sm btn-default" ng-model="topicsShowModels.wordChartstyle" bs-radio="'spline'">Line</a> </div> + <div class="pull-right"> + <a tabindex="0" class="btn btn-sm btn-default" ng-click="resetWordZoom()" ng-show="wordsSelected">Reset zoom</a> + </div> </div> <div class="panel-body"> - <div class="area-chart" id="topic-word" highcharts="topicWord"></div> - </div> - </div> - </div> - </div> - <div class="row"> - <div class="col-md-12"> - <h3><anchor-link fragment="words" />Topic Words </h3> - <div class="panel panel-default"> - <table class="table table-condensed table-bordered table-hover"> - <thead> - <tr> - <th ng-model="topicsShowModels.topicSortwords" sort-by="word">Word</th> - </tr> - </thead> - <tbody> - <tr ng-repeat="word in topic.words | orderBy:topicsShowModels.topicSortwords"> - <td ng-bind="word.word"></td> - </tr> - </tbody> - </table> - <div class="panel-footer"> - <ng-pluralize count="topic.words.length||0" when="{0:'No words',1:'Top word',other:'Top {} words'}"></ng-pluralize> + <div class="row row-full"> + <div class="col-md-2"> + <ul class="list-unstyled"> + <li ng-repeat="word in topic.words"> + <div class="checkbox checkbox-condensed" ng-class="{selected:word.selected}"> + <input tabindex="0" type="checkbox" ng-model="word.selected" ng-attr-id="{{::word.word}}" ng-change="redrawWordEvolutionChart()"> + <label class="check" ng-attr-for="{{::word.word}}" ng-bind="::word.word"></label> + </div> + </li> + </ul> + </div> + <div class="col-md-10 message-container"> + <div class="chart area-chart" id="topicWordChart" highcharts="topicWord"></div> + <div class="message text-muted" ng-hide="wordsSelected">No words selected</div> + </div> + </div> </div> </div> </div> </div> <div class="row"> <div class="col-md-12"> - <h3><anchor-link fragment="sequences" />Sequence Words</h3> + <h3><anchor-link fragment="sequences" />Sequences</h3> <div class="panel panel-default"> <div class="panel-heading"> <small>Sequence:</small> diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index db5ca8c4..13e979f2 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -725,7 +725,7 @@ relSeqstyle: 'absolute', relChartstyle: 'areaspline', wordSeqstyle: 'absolute', - wordChartstyle: 'areaspline', + wordChartstyle: 'spline', topicSortwords: '-probability', seqSortwords: '-probability' }; @@ -741,8 +741,15 @@ if (!angular.isObject($scope.rootModels.topicModel)) $scope.rootModels.topicModel = data.topicModel; + // preselect some words + if($scope.topic.words) { + for(var i = 0; i < Math.min(3, $scope.topic.words.length); i++) + $scope.topic.words[i].selected = true; + } + $timeout(function() { $scope.redrawRelevanceGraph(); + $scope.redrawWordEvolutionChart(); }, 0); }, function(err) { $scope.errors = err; @@ -766,6 +773,47 @@ }], $scope.topicsShowModels.relChartstyle); }; + $scope.redrawWordEvolutionChart = function() { + if(!$scope.topic || !$scope.topic.words || !$scope.topic.sequences) return; + var evolutions = []; + + // create series + for(var i = 0, word, probs; i < $scope.topic.words.length; i++) { + word = $scope.topic.words[i]; + if(!word.selected) continue; + probs = []; + for(var j = 0, prob; j < word.sequenceProbabilities.length; j++) { + prob = $scope.topicsShowModels.wordSeqstyle === 'relative' ? word.sequenceProbabilitiesChange[j] : word.sequenceProbabilities[j]; + probs.push([new Date($scope.topic.sequences[j].window.startDate).getTime(), prob]); + } + evolutions.push({ + name: word.word, + data: probs + }); + } + + $scope.topicWord = areaRelevanceChart(evolutions, $scope.topicsShowModels.wordChartstyle); + $scope.wordsSelected = evolutions.length; + }; + + var topicRelChartElement = $('#topicRelChart'); + $scope.resetRelZoom = function() { + if (!$scope.topic) return; + var highcharts = topicRelChartElement.highcharts(); + if (!highcharts) return; + + highcharts.xAxis[0].setExtremes(null, null); + }; + + var topicWordChartElement = $('#topicWordChart'); + $scope.resetWordZoom = function() { + if (!$scope.wordsSelected) return; + var highcharts = topicWordChartElement.highcharts(); + if (!highcharts) return; + + highcharts.xAxis[0].setExtremes(null, null); + }; + $scope.startRename = function() { $scope.origName = $scope.topic.name; $scope.isRename = true; @@ -811,6 +859,9 @@ $scope.$watch('topicsShowModels.relSeqstyle', $scope.redrawRelevanceGraph); $scope.$watch('topicsShowModels.relChartstyle', $scope.redrawRelevanceGraph); + $scope.$watch('topicsShowModels.wordSeqstyle', $scope.redrawWordEvolutionChart); + $scope.$watch('topicsShowModels.wordChartstyle', $scope.redrawWordEvolutionChart); + $scope.$watchGroup(['sequenceId'], function() { if (!$scope.sequenceId) return; diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less index 9cefe0e5..1cad561e 100644 --- a/vipra-ui/app/less/app.less +++ b/vipra-ui/app/less/app.less @@ -335,6 +335,7 @@ a:hover { color: #ccc; } +.chart, .highcharts-container { width: 100% !important; } @@ -418,6 +419,19 @@ topic-menu { } } +.row.row-full { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + flex-wrap: wrap; + + & > [class*='col-'] { + display: flex; + flex-direction: column; + } +} + @-moz-keyframes spin { 100% { -moz-transform: rotateY(360deg); 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 05faecfc..74bbe6cf 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 @@ -13,6 +13,8 @@ public class TopicWord implements Comparable<TopicWord>, Serializable { private List<Double> sequenceProbabilities; + private List<Double> sequenceProbabilitiesChange; + public String getWord() { return word; } @@ -29,6 +31,14 @@ public class TopicWord implements Comparable<TopicWord>, Serializable { this.sequenceProbabilities = sequenceProbabilities; } + public List<Double> getSequenceProbabilitiesChange() { + return sequenceProbabilitiesChange; + } + + public void setSequenceProbabilitiesChange(List<Double> sequenceProbabilitiesChange) { + this.sequenceProbabilitiesChange = sequenceProbabilitiesChange; + } + @Override public int compareTo(final TopicWord o) { return word.compareTo(o.getWord()); -- GitLab