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 113ee3e8e61ccdbb02f9e6d6469ddc8b0fad4c1b..e8df881a54ef1fdc9f2b77d372a588689981a21e 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 @@ -287,6 +287,7 @@ public class Analyzer { // create sequence final SequenceFull newSequenceFull = new SequenceFull(); newSequenceFull.setWindow(new Window(newWindows.get(idxSeq))); + Collections.sort(newSequenceWords, Comparator.reverseOrder()); newSequenceFull.setWords(newSequenceWords); newSequenceFull.setRelevance(relevance); newSequenceFull.setRelevanceChange(relevance - prevRelevance); @@ -335,6 +336,7 @@ public class Analyzer { newTopicWord.setSequenceProbabilitiesChange(sequenceProbabilitiesChange); newTopicWords.add(newTopicWord); } + Collections.sort(newTopicWords, Comparator.reverseOrder()); newTopic.setWords(newTopicWords); // calculate average diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java index e2d37b4d9ec3fcf77fc1c27cd6295f82454619ff..7ab98e13e075f2fa96de884402f4feeb433ad4ad 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/ModelingCommand.java @@ -25,7 +25,10 @@ public class ModelingCommand implements Command { private void modelForModel(final TopicModelConfig modelConfig) throws AnalyzerException, ConfigException, DatabaseException, ParseException, IOException, InterruptedException { - ConsoleUtils.info("generating model: " + modelConfig.getName()); + if (reread) + ConsoleUtils.info("rereading model: " + modelConfig.getName()); + else + ConsoleUtils.info("generating model: " + modelConfig.getName()); ConsoleUtils.flush(); final Analyzer analyzer = new Analyzer(); diff --git a/vipra-ui/app/html/directives/change-pos.html b/vipra-ui/app/html/directives/change-pos.html new file mode 100644 index 0000000000000000000000000000000000000000..c5abe57075268eb6060fd21655eb3f483e799295 --- /dev/null +++ b/vipra-ui/app/html/directives/change-pos.html @@ -0,0 +1,4 @@ +<span ng-class="{'text-danger':changeVal>0,'text-success':changeVal<0}"> + <span class="text-bold" ng-bind="change"></span> + <i class="fa" ng-class="{'fa-caret-down':changeVal>0, 'fa-caret-up':changeVal<0}"></i> +</span> \ 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 2c44e6852d09224b56d1c32c47404c83259f9b1b..6edea1352d623f22c50d6e98e5425839462a9d0c 100644 --- a/vipra-ui/app/html/topics/show.html +++ b/vipra-ui/app/html/topics/show.html @@ -107,29 +107,85 @@ </div> <div role="tabpanel" class="tab-pane tab-sequences"> <h3>Sequences</h3> - <div class="panel panel-default"> - <div class="panel-heading"> - <small>Sequence:</small> - <sequence-dropdown ng-model="sequenceId" sequences="topic.sequences"></sequence-dropdown> + <div class="row"> + <div ng-class="{'col-md-12':!sequenceCompare,'col-md-5':sequenceCompare}"> + <div class="panel panel-default"> + <div class="panel-heading seq-head-foot"> + <small>Sequence:</small> + <sequence-dropdown ng-model="topicsShowModels.sequenceId" sequences="topic.sequences"></sequence-dropdown> + <div class="pull-right" ng-if="!sequenceCompare"> + <small>Compare to:</small> + <sequence-dropdown ng-model="topicsShowModels.sequenceIdCompare" sequences="topic.sequences"></sequence-dropdown> + </div> + </div> + <table class="table table-condensed table-bordered table-hover table-fixed table-compare" ng-show="sequence"> + <thead> + <tr> + <th ng-model="topicsShowModels.seqSortWords" sort-by="word">Word</th> + <th ng-model="topicsShowModels.seqSortWords" sort-by="probability">Probability</th> + </tr> + </thead> + <tbody> + <tr class="compare-row" ng-repeat="word in sequence.words | orderBy:topicsShowModels.seqSortWords" ng-attr-word-id="{{::word.id}}"> + <td> + <word-link word="::word" /> + </td> + <td ng-bind="word.probability.toFixed(4)"></td> + </tr> + </tbody> + </table> + <div class="panel-footer seq-head-foot" ng-show="sequence"> + <ng-pluralize count="sequence.words.length||0" when="{0:'No words',1:'Top word',other:'Top {} words'}"></ng-pluralize> + </div> + </div> </div> - <table class="table table-condensed table-bordered table-hover table-fixed" ng-show="sequence"> - <thead> - <tr> - <th ng-model="topicsShowModels.seqSortWords" sort-by="word">Word</th> - <th ng-model="topicsShowModels.seqSortWords" sort-by="probability">Probability</th> - </tr> - </thead> - <tbody> - <tr ng-repeat="word in sequence.words | orderBy:topicsShowModels.seqSortWords"> - <td> - <word-link word="::word" /> - </td> - <td ng-bind="word.probability.toFixed(4)"></td> - </tr> - </tbody> - </table> - <div class="panel-footer" ng-show="sequence"> - <ng-pluralize count="sequence.words.length||0" when="{0:'No words',1:'Top word',other:'Top {} words'}"></ng-pluralize> + <div class="col-md-2" ng-if="sequenceCompare"> + <div class="panel panel-default"> + <div class="panel-heading seq-head-foot"></div> + <table class="table table-condensed table-bordered table-hover table-fixed table-compare" ng-show="sequence"> + <thead> + <tr> + <th class="text-center" ng-model="topicsShowModels.seqSortWords" sort-by="change">Change</th> + </tr> + </thead> + <tbody> + <tr class="compare-row" ng-repeat="word in sequence.words | orderBy:topicsShowModels.seqSortWords" ng-attr-word-id="{{::word.id}}"> + <td class="text-center"> + <change-pos change="word.change"/> + </td> + </tr> + </tbody> + </table> + <div class="panel-footer seq-head-foot" ng-show="sequence"></div> + </div> + </div> + <div class="col-md-5" ng-if="sequenceCompare"> + <div class="panel panel-default"> + <div class="panel-heading seq-head-foot"> + <small>Sequence:</small> + <sequence-dropdown ng-model="topicsShowModels.sequenceIdCompare" sequences="topic.sequences"></sequence-dropdown> + <button type="button" class="close" ng-click="closeCompare()"><span aria-hidden="true">×</span></button> + </div> + <table class="table table-condensed table-bordered table-hover table-fixed table-compare"> + <thead> + <tr> + <th ng-model="topicsShowModels.seqSortWords" sort-by="word">Word</th> + <th ng-model="topicsShowModels.seqSortWords" sort-by="probability">Probability</th> + </tr> + </thead> + <tbody> + <tr class="compare-row" ng-repeat="word in sequenceCompare.words | orderBy:topicsShowModels.seqSortWords" ng-attr-word-id="{{::word.id}}"> + <td> + <word-link word="::word" /> + </td> + <td ng-bind="word.probability.toFixed(4)"></td> + </tr> + </tbody> + </table> + <div class="panel-footer seq-head-foot"> + <ng-pluralize count="sequenceCompare.words.length||0" when="{0:'No words',1:'Top word',other:'Top {} words'}"></ng-pluralize> + </div> + </div> </div> </div> </div> diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index ece0c83d6a5a9784362dcf3ab1af1a69eeb06492..500739ee45d0976e3995343695d1ab8d2f6cd56f 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -845,7 +845,7 @@ // preselect first sequence if($scope.topic.sequences && $scope.topic.sequences.length) - $scope.sequenceId = $scope.topic.sequences[0].id; + $scope.topicsShowModels.sequenceId = $scope.topic.sequences[0].id; $timeout(function() { $scope.redrawRelevanceGraph(); @@ -952,19 +952,52 @@ } }; + $scope.recalcSeqChange = function() { + if(!$scope.sequence || !$scope.sequenceCompare) return; + wordLoop: + for(var i = 0, word; i < $scope.sequence.words.length; i++) { + word = $scope.sequence.words[i]; + for(var j = 0, word2; j < $scope.sequenceCompare.words.length; j++) { + word2 = $scope.sequenceCompare.words[j]; + if(word.id === word2.id) { + word.change = word2.change = j - i; + continue wordLoop; + } + } + word.change = '-'; + } + }; + + $scope.closeCompare = function() { + delete $scope.sequenceCompare; + delete $scope.topicsShowModels.sequenceIdCompare; + }; + $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.$watch('sequenceId', function() { - if (!$scope.sequenceId) return; + $scope.$watch('topicsShowModels.sequenceId', function() { + if (!$scope.topicsShowModels.sequenceId) return; SequenceFactory.get({ - id: $scope.sequenceId + id: $scope.topicsShowModels.sequenceId }, function(data) { $scope.sequence = data; + $scope.recalcSeqChange(); + }); + }); + + $scope.$watch('topicsShowModels.sequenceIdCompare', function(newVal) { + if (!$scope.topicsShowModels.sequenceIdCompare) return; + + SequenceFactory.get({ + id: $scope.topicsShowModels.sequenceIdCompare + }, function(data) { + $scope.sequenceCompare = data; + $scope.recalcSeqChange(); }); }); @@ -980,6 +1013,14 @@ $scope.startRename(); } }); + + $('.tab-sequences').on('mouseleave', '.compare-row', function() { + $('[word-id=' + $(this).attr('word-id') + ']').removeClass('highlight'); + }); + + $('.tab-sequences').on('mouseenter', '.compare-row', function() { + $('[word-id=' + $(this).attr('word-id') + ']').addClass('highlight'); + }); } ]); diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js index 9b9353fa13994f26492e7e5e6b1e128b91872e1b..80b1669746e500606226a9be5bdbacc89edcf9fc 100644 --- a/vipra-ui/app/js/directives.js +++ b/vipra-ui/app/js/directives.js @@ -292,10 +292,12 @@ return $scope.ngModel === $scope.sortBy || $scope.ngModel === '-' + $scope.sortBy; }; - if($scope.ngModel === $scope.sortBy) - $scope.reverse = false; - else if($scope.ngModel === '-' + $scope.sortBy) - $scope.reverse = true; + $scope.$watch('ngModel', function() { + if($scope.ngModel === $scope.sortBy) + $scope.reverse = false; + else if($scope.ngModel === '-' + $scope.sortBy) + $scope.reverse = true; + }); }, transclude: true, template: '<span ng-transclude></span> <i class="fa" ng-class="{\'fa-caret-down\':!reverse, \'fa-caret-up\':reverse}" ng-show="showCaret()"></i>' @@ -377,17 +379,42 @@ }; }]); - app.directive('ngEnter', function() { - return function($scope, $elem, $attrs) { - $elem.bind("keydown keypress", function(event) { - if (event.which === 13) { - $scope.$apply(function() { - $scope.$eval($attrs.ngEnter); - }); - event.preventDefault(); - } - }); + app.directive('ngEnter', [function() { + return { + link: function($scope, $elem, $attrs) { + $elem.bind("keydown keypress", function(event) { + if (event.which === 13) { + $scope.$apply(function() { + $scope.$eval($attrs.ngEnter); + }); + event.preventDefault(); + } + }); + } }; - }); + }]); + + app.directive('changePos', [function() { + return { + scope: { + change: '=' + }, + link: function($scope) { + $scope.changed = function() { + var change = parseInt($scope.change); + if(!isNaN(change)) { + $scope.changeVal = change; + if(change > 0) + $scope.change = '+' + change; + } else { + $scope.changeVal = 0; + } + }; + + $scope.$watch('change', $scope.changed); + }, + templateUrl: 'html/directives/change-pos.html' + } + }]); })(); \ No newline at end of file diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less index bc84dcf53790fa5d64a27dcd23e3187550e1f69f..efe492d576366ce0ba8313e707a069464dd1553e 100644 --- a/vipra-ui/app/less/app.less +++ b/vipra-ui/app/less/app.less @@ -379,6 +379,10 @@ entity-menu { font-style: italic; } +.text-bold { + font-weight: bold; +} + .mnemonic { *:hover > &, *:hover { @@ -479,6 +483,21 @@ entity-menu { padding-left: 15px; } +.seq-head-foot { + height: 42px; +} + +.table-compare { + .highlight { + background-color: #f5f5f5; + } + + td { + height: 32px; + vertical-align: middle; + } +} + @-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 6fa9aaf2c12cb57278347ce9867d0b07de655bf6..3d483d48b3ae6e6c11e770139e48049ee390f50b 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 @@ -41,7 +41,7 @@ public class TopicWord implements Comparable<TopicWord>, Serializable { @Override public int compareTo(final TopicWord o) { - return id.compareTo(o.getId()); + return sequenceProbabilities.get(0).compareTo(o.getSequenceProbabilities().get(0)); } }