diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java index 09991c62335e81fc930c42bdbb78d2818771a158..06419f12edb23d1f4c510f9f0c8fa7af16943aa1 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/ArticleResource.java @@ -92,7 +92,7 @@ public class ArticleResource { ArticleFull article; try { - article = dbArticles.getSingle(MongoUtils.objectId(id), false, StringUtils.getFields(fields)); + article = dbArticles.getSingle(MongoUtils.objectId(id), StringUtils.getFields(fields)); } catch (final Exception e) { e.printStackTrace(); res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/SequenceResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/SequenceResource.java index e2c66f9af85d7b8451472762abceb539c9ca3aa1..aafcb515d138d18e79a4e44329651d130aab242d 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/SequenceResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/SequenceResource.java @@ -84,7 +84,7 @@ public class SequenceResource { SequenceFull sequence; try { - sequence = dbSequences.getSingle(MongoUtils.objectId(id), false, StringUtils.getFields(fields)); + sequence = dbSequences.getSingle(MongoUtils.objectId(id), StringUtils.getFields(fields)); } catch (final Exception e) { e.printStackTrace(); res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/TopicModelResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/TopicModelResource.java index 493929b45c6e893dbc02c5a8a6abfe600bd2166b..50b35a6f2ddadfd804851517d013b3afff03e94c 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/TopicModelResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/TopicModelResource.java @@ -75,7 +75,7 @@ public class TopicModelResource { TopicModelFull topicModel; try { - topicModel = dbTopicModels.getSingle(id, false, StringUtils.getFields(fields)); + topicModel = dbTopicModels.getSingle(id, StringUtils.getFields(fields)); } catch (final Exception e) { e.printStackTrace(); res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java index 2d977f74e85508505224c1d1d30c83ff6f6aa886..b13dfe1c7aebd30231b1ae00fb51b83f15971d87 100644 --- a/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java +++ b/vipra-backend/src/main/java/de/vipra/rest/resource/TopicResource.java @@ -91,7 +91,7 @@ public class TopicResource { TopicFull topic; try { - topic = dbTopics.getSingle(MongoUtils.objectId(id), false, StringUtils.getFields(fields)); + topic = dbTopics.getSingle(MongoUtils.objectId(id), StringUtils.getFields(fields)); } catch (final Exception e) { e.printStackTrace(); res.addError(new APIError(Response.Status.BAD_REQUEST, "Error", e.getMessage())); 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 b8b3d78a163d3aaffd0aba32f66272708557eeaf..ff2613249edf18de5efb9359b5bcecc87142f233 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 @@ -9,9 +9,7 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.HashSet; import java.util.List; -import java.util.Set; import org.bson.types.ObjectId; @@ -20,6 +18,7 @@ import de.vipra.cmd.file.FilebaseIDDateIndexEntry; import de.vipra.cmd.file.FilebaseWindowIndex; import de.vipra.cmd.file.FilebaseWordIndex; import de.vipra.util.ArrayUtils; +import de.vipra.util.CompareMap; import de.vipra.util.Config; import de.vipra.util.ConsoleUtils; import de.vipra.util.MongoUtils; @@ -30,6 +29,7 @@ import de.vipra.util.model.Article; import de.vipra.util.model.ArticleFull; import de.vipra.util.model.Sequence; import de.vipra.util.model.SequenceFull; +import de.vipra.util.model.SequenceWord; import de.vipra.util.model.SimilarArticle; import de.vipra.util.model.Topic; import de.vipra.util.model.TopicFull; @@ -202,58 +202,56 @@ public class Analyzer { // create new topic final TopicFull newTopic = new TopicFull(); - final List<Sequence> newTopicSequences = new ArrayList<>(windowCount); - newTopic.setSequences(newTopicSequences); newTopic.setTopicModel(new TopicModel(topicModel.getId())); newTopics.add(newTopic); in = new BufferedReader(new InputStreamReader(new FileInputStream(seqFile))); // read file lines into word x sequence matrix - // gather maximum likeliness per sequence and per word - final double[][] likelinesses = new double[wordCount][windowCount]; + // gather maximum probability per sequence and per word + final double[][] probabilities = new double[wordCount][windowCount]; for (int idxWord = 0; idxWord < wordCount; idxWord++) { for (int idxSeq = 0; idxSeq < windowCount; idxSeq++) { - likelinesses[idxWord][idxSeq] = Double.parseDouble(in.readLine()); + probabilities[idxWord][idxSeq] = Double.parseDouble(in.readLine()); } } in.close(); // find maximum - final double[] maxSeqLikelinesses = ArrayUtils.findColMaximum(likelinesses); + final double[] maxSeqProbabilities = ArrayUtils.findColMaximum(probabilities); - // collect top words in each sequence for topic name - final Set<TopicWord> topTopicWords = new HashSet<>(); + // collect topic words per sequence with highest probability + final CompareMap<SequenceWord> topicWords = new CompareMap<>(); final double[] relevances = new double[windowCount]; double relevanceSum = 0; double prevRelevance = 0; // for each sequence + final List<Sequence> newTopicSequences = new ArrayList<>(windowCount); for (int idxSeq = 0, sequenceOffset = 0; idxSeq < windowCount; idxSeq++) { // calculate relative cutoff probability - final double maxSeqLikeliness = maxSeqLikelinesses[idxSeq]; - final double minRelativeSeqLikeliness = modelConfig.getMinRelativeProbability() * Math.abs(maxSeqLikeliness); + final double maxSeqProbability = maxSeqProbabilities[idxSeq]; + final double minRelativeSeqProbability = modelConfig.getMinRelativeProbability() * Math.abs(maxSeqProbability); // collect words - final List<TopicWord> newSeqTopicWords = new ArrayList<>(wordCount); + List<SequenceWord> newSequenceWords = new ArrayList<>(wordCount); for (int idxWord = 0; idxWord < wordCount; idxWord++) { - final double likeliness = likelinesses[idxWord][idxSeq]; + final double probability = probabilities[idxWord][idxSeq]; // check if word acceptable - if (!seqRelativeCutoff || (maxSeqLikeliness >= 0 && likeliness >= minRelativeSeqLikeliness) - || (maxSeqLikeliness < 0 && Math.abs(likeliness) >= minRelativeSeqLikeliness)) { + if (!seqRelativeCutoff || (maxSeqProbability >= 0 && probability >= minRelativeSeqProbability) + || (maxSeqProbability < 0 && Math.abs(probability) >= minRelativeSeqProbability)) { final String word = wordIndex.word(idxWord); - final TopicWord topicWord = new TopicWord(word, likeliness); - newSeqTopicWords.add(topicWord); + final SequenceWord sequenceWord = new SequenceWord(word, probability); + newSequenceWords.add(sequenceWord); } } - if (!newSeqTopicWords.isEmpty()) { - Collections.sort(newSeqTopicWords, Comparator.reverseOrder()); - - // collect top words - topTopicWords.addAll(newSeqTopicWords.subList(0, Math.min(newSeqTopicWords.size(), modelConfig.getTopicAutoNamingWords()))); + if (!newSequenceWords.isEmpty()) { + Collections.sort(newSequenceWords, Comparator.reverseOrder()); + newSequenceWords = newSequenceWords.subList(0, Math.min(newSequenceWords.size(), modelConfig.getkTopWords())); + topicWords.addAll(newSequenceWords); } // calculate topic sequence relevance @@ -266,7 +264,7 @@ public class Analyzer { // create sequence final SequenceFull newSequenceFull = new SequenceFull(); newSequenceFull.setWindow(new Window(newWindows.get(idxSeq))); - newSequenceFull.setWords(newSeqTopicWords); + newSequenceFull.setWords(newSequenceWords); newSequenceFull.setRelevance(relevance); newSequenceFull.setRelevanceChange(relevance - prevRelevance); newSequenceFull.setTopic(new Topic(newTopic.getId())); @@ -289,12 +287,27 @@ public class Analyzer { // to next relevance prevRelevance = relevance; } + newTopic.setSequences(newTopicSequences); // sort topic words and generate topic name - final List<TopicWord> topTopicWordsList = new ArrayList<>(topTopicWords); + List<SequenceWord> topTopicWordsList = new ArrayList<>(topicWords.values()); Collections.sort(topTopicWordsList); + topTopicWordsList = topTopicWordsList.subList(0, Math.min(topTopicWordsList.size(), modelConfig.getkTopWords())); newTopic.setName(TopicFull.getNameFromWords(modelConfig.getTopicAutoNamingWords(), topTopicWordsList)); + // set topic words + final List<TopicWord> newTopicWords = new ArrayList<>(modelConfig.getkTopWords()); + for (final SequenceWord sequenceWord : topTopicWordsList) { + 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())]) + sequenceProbabilities.add(probability); + newTopicWord.setSequenceProbabilities(sequenceProbabilities); + newTopicWords.add(newTopicWord); + } + newTopic.setWords(newTopicWords); + // calculate average final double average = relevanceSum / windowCount; newTopic.setAvgRelevance(average); diff --git a/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java b/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java index 3f58c23f1e988cfb608e6d4f84fb9ce5445f0af1..515efd856606a248edde2eb650edc5f903dda836 100644 --- a/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java +++ b/vipra-cmd/src/main/java/de/vipra/cmd/option/IndexingCommand.java @@ -45,7 +45,7 @@ public class IndexingCommand implements Command { for (final FilebaseIDDateIndexEntry entry : index) { // get article from database - final ArticleFull article = dbArticles.getSingle(MongoUtils.objectId(entry.getId()), true); + final ArticleFull article = dbArticles.getSingle(MongoUtils.objectId(entry.getId()), "_all"); if (article == null) { ConsoleUtils.error("no article found in db for id: " + entry.getId()); continue; diff --git a/vipra-ui/app/html/about.html b/vipra-ui/app/html/about.html index 9ba7c44e9c124de2d79e564032aae5fc8f59e4b7..f13b32d7645015a1873826b59c336383a1aa5834 100644 --- a/vipra-ui/app/html/about.html +++ b/vipra-ui/app/html/about.html @@ -6,17 +6,17 @@ </div> <p><strong>Vipra</strong></p> <p>Created by Eike Cochu</p> - <h3>Description</h3> + <h3><anchor-link fragment="description" />Description</h3> <p>The Vipra application is a topic modeling based search system with a frontend web application, a backend REST service and a maintenance tool for data import and modeling. It attempts to leverage automatically discovered topic informations in document collections to ease collection browsing and organization. The search system relies on ElasticSearch and Apache Lucene.</p> <p>This application was created by Eike Cochu for his master's degree thesis in computer science, 2015-2016 at the Freie Universität in Berlin, Germany.</p> - <h3>License</h3> + <h3><anchor-link fragment="license" />License</h3> <p>The MIT License (MIT)</p> <p>Copyright 2016 Eike Cochu</p> <p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p> <p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p> <p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p> <hr> - <h3>Version</h3> + <h3><anchor-link fragment="version" />Version</h3> <table class="table table-bordered table-fixed"> <tbody> <tr> @@ -33,7 +33,7 @@ </tr> </tbody> </table> - <h3>Host</h3> + <h3><anchor-link fragment="host" />Host</h3> <table class="table table-bordered table-fixed"> <tbody> <tr> @@ -50,7 +50,7 @@ </tr> </tbody> </table> - <h3>VM</h3> + <h3><anchor-link fragment="vm" />VM</h3> <table class="table table-bordered table-fixed"> <tbody> <tr> @@ -63,7 +63,7 @@ </tr> </tbody> </table> - <h3>Database</h3> + <h3><anchor-link fragment="database" />Database</h3> <table class="table table-bordered table-fixed"> <tbody> <tr> @@ -76,7 +76,7 @@ </tr> </tbody> </table> - <h3>Constants</h3> + <h3><anchor-link fragment="constants" />Constants</h3> <table class="table table-bordered table-fixed"> <tbody> <tr> @@ -116,7 +116,7 @@ </tr> <tr class="well"> <td colspan="2"> - The number of topic words to be used to automatically generate a topic name. Words are sorted by descending topic association likeliness. + The number of topic words to be used to automatically generate a topic name. Words are sorted by descending topic association probability. </td> </tr> <tr> diff --git a/vipra-ui/app/html/articles/show.html b/vipra-ui/app/html/articles/show.html index 1963a78b23bb0a3a2ca3a2131caf771f297bf729..748f38b489196a285adc871efbfa61049c06499a 100644 --- a/vipra-ui/app/html/articles/show.html +++ b/vipra-ui/app/html/articles/show.html @@ -19,7 +19,7 @@ <div class="col-md-8"> <div class="row"> <div class="col-md-12"> - <h3>Info</h3> + <h3><anchor-link fragment="info" />Info</h3> <table class="table table-bordered table-condensed table-infos"> <tbody> <tr> @@ -49,7 +49,7 @@ </div> <div class="row"> <div class="col-md-12"> - <h3>Similar articles</h3> + <h3><anchor-link fragment="similar" />Similar articles</h3> <ul class="list-unstyled" ng-attr-start="{{(page-1)*limit+1}}"> <li ng-repeat="simArticle in article.similarArticles" class="ellipsis"> <small class="text-muted percent-align" ng-bind-template="({{((1-simArticle.divergence)*100).toFixed(0)}}%)"></small> @@ -60,7 +60,7 @@ </div> </div> <div class="col-md-4"> - <h3>Topics</h3> + <h3><anchor-link fragment="topics" />Topics</h3> <table class="table table-bordered table-condensed table-nomargin" ng-show="article.topics.length > 0"> <tbody> <tr> diff --git a/vipra-ui/app/html/directives/anchor-link.html b/vipra-ui/app/html/directives/anchor-link.html new file mode 100644 index 0000000000000000000000000000000000000000..47e17f149e7a873ba64fa601c84b634a18601650 --- /dev/null +++ b/vipra-ui/app/html/directives/anchor-link.html @@ -0,0 +1,4 @@ +<a class="anchor-link" ui-sref="{'#': fragment}" target="_self"> + <i class="fa fa-link"></i> + <span ng-attr-id="{{fragment}}"></span> +</a> \ No newline at end of file diff --git a/vipra-ui/app/html/directives/sequence-dropdown.html b/vipra-ui/app/html/directives/sequence-dropdown.html index d04583bd40981275cdc5ba7819f3576b7c3fd66c..2cbbecb770d5bf0aee830c46eb474f0ccd486742 100644 --- a/vipra-ui/app/html/directives/sequence-dropdown.html +++ b/vipra-ui/app/html/directives/sequence-dropdown.html @@ -1,4 +1,4 @@ -<ol class="nya-bs-select nya-bs-condensed" ng-model="ngModel"> +<ol class="nya-bs-select nya-bs-condensed" ng-model="ngModel" ng-class="{dropup:!ngModel}"> <li value="{{sequence.id}}" class="nya-bs-option" ng-repeat="sequence in sequences"> <a ng-bind="sequence.label"></a> </li> diff --git a/vipra-ui/app/html/error.html b/vipra-ui/app/html/error.html new file mode 100644 index 0000000000000000000000000000000000000000..1e41bdb8cba29072891ab596fec7766b0deb0014 --- /dev/null +++ b/vipra-ui/app/html/error.html @@ -0,0 +1,11 @@ +<div class="container" ng-cloak ng-hide="$state.current.name !== 'error'"> + <div class="row"> + <div class="col-md-3"> + <object data="img/exclamation-triangle.svg" type="image/svg+xml" style="width:100%"></object> + </div> + <div class="col-md-9"> + <h1 ng-bind="::code"></h1> + <h4 ng-bind="::title"></h4> + </div> + </div> +</div> \ No newline at end of file diff --git a/vipra-ui/app/html/explorer.html b/vipra-ui/app/html/explorer.html index 69563f7f0f365d0e40c491bb36903abbf40ce2da..ec9dd98c99d50b2a7973f1e62c27b2431a75f1fa 100644 --- a/vipra-ui/app/html/explorer.html +++ b/vipra-ui/app/html/explorer.html @@ -16,7 +16,7 @@ <sort-dir ng-model="explorerModels.sortdir" /> </a> </div> - <div class="btn-group btn-group-justified"> + <div class="btn-group btn-group-justified" ng-class="{'has-warning':search}"> <input type="text" class="form-control" ng-model="search.$" placeholder="Filter"> <span class="glyphicon glyphicon-remove-circle searchclear" ng-click="search=''"></span> </div> @@ -57,8 +57,11 @@ <a class="btn btn-sm btn-default" ng-model="explorerModels.chartstack" bs-radio="'normal'">Value</a> <a class="btn btn-sm btn-default" ng-model="explorerModels.chartstack" bs-radio="'percent'">Percent</a> </div> + <div class="pull-right"> + <a class="btn btn-sm btn-default" ng-click="resetZoom()" ng-show="topicsSelected">Reset zoom</a> + </div> </div> - <div id="topicRelChart" class="chart" highcharts="topicSeq"></div> + <div id="topicRelChart" class="chart" highcharts="topicSeq" style="height:500px"></div> <div class="message text-muted" ng-show="!topicsSelected">No topic selected</div> </div> </div> diff --git a/vipra-ui/app/html/topics/show.html b/vipra-ui/app/html/topics/show.html index afd55ae723f13ba8ef2e0d5902b5c869155882d3..73e3d2eb3f6a2424caeb638c72f0e2750b95f888 100644 --- a/vipra-ui/app/html/topics/show.html +++ b/vipra-ui/app/html/topics/show.html @@ -42,7 +42,7 @@ </div> <div class="row"> <div class="col-md-12"> - <h3>Info</h3> + <h3><anchor-link fragment="info" />Info</h3> <table class="table table-bordered table-condensed table-fixed table-infos"> <tbody> <tr> @@ -55,19 +55,19 @@ </div> <div class="row"> <div class="col-md-12"> - <h3>Relevance</h3> + <h3><anchor-link fragment="relevance" />Relevance</h3> <div class="panel panel-default"> <div class="panel-heading"> <small>Values:</small> <div class="btn-group"> - <a class="btn btn-sm btn-default" ng-model="topicsShowModels.seqstyle" bs-radio="'absolute'">Absolute</a> - <a class="btn btn-sm btn-default" ng-model="topicsShowModels.seqstyle" bs-radio="'relative'">Relative</a> + <a class="btn btn-sm btn-default" ng-model="topicsShowModels.relSeqstyle" bs-radio="'absolute'">Absolute</a> + <a class="btn btn-sm btn-default" ng-model="topicsShowModels.relSeqstyle" bs-radio="'relative'">Relative</a> </div> <small>Chart:</small> <div class="btn-group"> - <a class="btn btn-sm btn-default" ng-model="topicsShowModels.chartstyle" bs-radio="'areaspline'">Area</a> - <a class="btn btn-sm btn-default" ng-model="topicsShowModels.chartstyle" bs-radio="'spline'">Line</a> + <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> <div class="panel-body"> @@ -78,7 +78,52 @@ </div> <div class="row"> <div class="col-md-12"> - <h3>Words</h3> + <h3><anchor-link fragment="evolution" />Word evolution</h3> + <div class="panel panel-default"> + <div class="panel-heading"> + <small>Values:</small> + <div class="btn-group"> + <a class="btn btn-sm btn-default" ng-model="topicsShowModels.wordSeqstyle" bs-radio="'absolute'">Absolute</a> + <a class="btn btn-sm btn-default" ng-model="topicsShowModels.wordSeqstyle" bs-radio="'relative'">Relative</a> + </div> + + <small>Chart:</small> + <div class="btn-group"> + <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> + <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> + </div> + </div> + </div> + <div class="row"> + <div class="col-md-12"> + <h3><anchor-link fragment="sequences" />Sequence Words</h3> <div class="panel panel-default"> <div class="panel-heading"> <small>Sequence:</small> @@ -87,14 +132,14 @@ <table class="table table-condensed table-bordered table-hover" ng-show="sequence"> <thead> <tr> - <th ng-model="topicsShowModels.sortwords" sort-by="id">Word</th> - <th ng-model="topicsShowModels.sortwords" sort-by="likeliness">Likeliness</th> + <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.sortwords"> - <td ng-bind="word.id"></td> - <td ng-bind="word.likeliness.toFixed(4)"></td> + <tr ng-repeat="word in sequence.words | orderBy:topicsShowModels.seqSortWords"> + <td ng-bind="word.word"></td> + <td ng-bind="word.probability.toFixed(4)"></td> </tr> </tbody> </table> diff --git a/vipra-ui/app/img/exclamation-triangle.svg b/vipra-ui/app/img/exclamation-triangle.svg new file mode 100644 index 0000000000000000000000000000000000000000..e4e532b5f8329b70bb2333f0ad79227bcf8c3e24 --- /dev/null +++ b/vipra-ui/app/img/exclamation-triangle.svg @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 512.209 512.209" style="enable-background:new 0 0 512.209 512.209;" xml:space="preserve"> +<g> + <path d="M507.345,439.683L288.084,37.688c-3.237-5.899-7.71-10.564-13.429-13.988c-5.705-3.427-11.893-5.142-18.554-5.142 s-12.85,1.718-18.558,5.142c-5.708,3.424-10.184,8.089-13.418,13.988L4.859,439.683c-6.663,11.998-6.473,23.989,0.57,35.98 c3.239,5.517,7.664,9.897,13.278,13.128c5.618,3.237,11.66,4.859,18.132,4.859h438.529c6.479,0,12.519-1.622,18.134-4.859 c5.62-3.23,10.038-7.611,13.278-13.128C513.823,463.665,514.015,451.681,507.345,439.683z M292.655,411.132 c0,2.662-0.91,4.897-2.71,6.704c-1.807,1.811-3.949,2.71-6.427,2.71h-54.816c-2.474,0-4.616-0.899-6.423-2.71 c-1.809-1.807-2.713-4.042-2.713-6.704v-54.248c0-2.662,0.905-4.897,2.713-6.704c1.807-1.811,3.946-2.71,6.423-2.71h54.812 c2.479,0,4.62,0.899,6.428,2.71c1.803,1.807,2.71,4.042,2.71,6.704v54.248H292.655z M292.088,304.357 c-0.198,1.902-1.198,3.47-3.001,4.709c-1.811,1.238-4.046,1.854-6.711,1.854h-52.82c-2.663,0-4.947-0.62-6.849-1.854 c-1.908-1.243-2.858-2.807-2.858-4.716l-4.853-130.47c0-2.667,0.953-4.665,2.856-5.996c2.474-2.093,4.758-3.14,6.854-3.14h62.809 c2.098,0,4.38,1.043,6.854,3.14c1.902,1.331,2.851,3.14,2.851,5.424L292.088,304.357z" fill="#a94442"/> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +</svg> diff --git a/vipra-ui/app/index.html b/vipra-ui/app/index.html index d2e3ede9b09901f6e0deda67c077b0d98f6dee6e..f52e82571551a8ba9abc2ede86184c4d1104d415 100644 --- a/vipra-ui/app/index.html +++ b/vipra-ui/app/index.html @@ -1,10 +1,10 @@ <!DOCTYPE html> <html lang="en" ng-app="vipra.app" ng-controller="RootController"> - <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> + <base href="/"> <title>Vipra</title> <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png"> <link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png"> @@ -32,9 +32,8 @@ <script src="js/app.js"></script> <script src="js/templates.js"></script> </head> - <body> - <nav class="navbar navbar-default navbar-static-top"> + <nav class="navbar navbar-default navbar-fixed-top"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> @@ -79,12 +78,9 @@ </li> </ul> </div> - <!-- /.navbar-collapse --> </div> - <!-- /.container-fluid --> </nav> <div class="main" ui-view ng-cloak></div> - <div id="topicModelModal" class="modal fade" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false"> <div class="modal-dialog modal-lg"> <div class="modal-content"> @@ -127,7 +123,5 @@ </div> </div> </div> - </body> - -</html> +</html> \ No newline at end of file diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js index 8d5da3d6d9319cb9fc38f00ac158376d8ab35191..fe66def8df8c2dfd03fe74a459b7f5d89b9ff3f6 100644 --- a/vipra-ui/app/js/app.js +++ b/vipra-ui/app/js/app.js @@ -2,7 +2,7 @@ * Vipra Application * Main application file ******************************************************************************/ -/* globals angular */ +/* globals angular, $ */ (function() { "use strict"; @@ -18,118 +18,143 @@ 'vipra.templates' ]); - app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) { - - $urlRouterProvider.otherwise('/'); - - // states - - $stateProvider.state('index', { - url: '/', - templateUrl: 'html/index.html', - controller: 'IndexController' - }); - - $stateProvider.state('about', { - url: '/about', - templateUrl: 'html/about.html', - controller: 'AboutController' - }); - - $stateProvider.state('network', { - url: '/network/:type/:id', - templateUrl: 'html/network.html', - controller: 'NetworkController' - }); - - $stateProvider.state('explorer', { - url: '/explorer', - templateUrl: 'html/explorer.html', - controller: 'ExplorerController' - }); - - // states: articles - - $stateProvider.state('articles', { - url: '/articles', - templateUrl: 'html/articles/index.html', - controller: 'ArticlesIndexController' - }); - - $stateProvider.state('articles.show', { - url: '/:id', - templateUrl: 'html/articles/show.html', - controller: 'ArticlesShowController' - }); - - // states: topics - - $stateProvider.state('topics', { - url: '/topics', - templateUrl: 'html/topics/index.html', - controller: 'TopicsIndexController' - }); - - $stateProvider.state('topics.show', { - url: '/:id', - templateUrl: 'html/topics/show.html', - controller: 'TopicsShowController' - }); - - $stateProvider.state('topics.show.articles', { - url: '/articles', - templateUrl: 'html/topics/articles.html', - controller: 'TopicsArticlesController' - }); - - }]); - - app.config(['$httpProvider', function($httpProvider) { - - $httpProvider.interceptors.push(function($q, $injector, $rootScope) { - var requestIncrement = function(config) { - $rootScope.loading.requests = ++$rootScope.loading.requests || 1; - $rootScope.loading[config.method] = ++$rootScope.loading[config.method] || 1; - $rootScope.loading.any = true; - }; - - var requestDecrement = function(config) { - $rootScope.loading.requests = Math.max(--$rootScope.loading.requests, 0); - $rootScope.loading[config.method] = Math.max(--$rootScope.loading[config.method], 0); - $rootScope.loading.any = $rootScope.loading.requests > 0; - }; - - return { - request: function(config) { - requestIncrement(config); - return config; - }, - - requestError: function(rejection) { - requestDecrement(rejection.config); - return $q.reject(rejection); - }, - - response: function(response) { - requestDecrement(response.config); - return response; - }, - - responseError: function(rejection) { - requestDecrement(rejection.config); - return $q.reject(rejection); - } - }; - }); - - }]); + app.config(['$locationProvider', '$stateProvider', '$urlRouterProvider', '$httpProvider', + function($locationProvider, $stateProvider, $urlRouterProvider, $httpProvider) { + + $locationProvider.html5Mode(true); + $urlRouterProvider.otherwise('/'); + + // states + + $stateProvider.state('index', { + url: '/', + templateUrl: 'html/index.html', + controller: 'IndexController' + }); + + $stateProvider.state('about', { + url: '/about', + templateUrl: 'html/about.html', + controller: 'AboutController' + }); + + $stateProvider.state('network', { + url: '/network/:type/:id', + templateUrl: 'html/network.html', + controller: 'NetworkController' + }); + + $stateProvider.state('explorer', { + url: '/explorer', + templateUrl: 'html/explorer.html', + controller: 'ExplorerController' + }); + + // states: articles + + $stateProvider.state('articles', { + url: '/articles', + templateUrl: 'html/articles/index.html', + controller: 'ArticlesIndexController' + }); + + $stateProvider.state('articles.show', { + url: '/:id', + templateUrl: 'html/articles/show.html', + controller: 'ArticlesShowController' + }); + + // states: topics + + $stateProvider.state('topics', { + url: '/topics', + templateUrl: 'html/topics/index.html', + controller: 'TopicsIndexController' + }); + + $stateProvider.state('topics.show', { + url: '/:id', + templateUrl: 'html/topics/show.html', + controller: 'TopicsShowController' + }); + + $stateProvider.state('topics.show.articles', { + url: '/articles', + templateUrl: 'html/topics/articles.html', + controller: 'TopicsArticlesController' + }); + + // states: errors + + $stateProvider.state('error', { + url: '/{code:[4-5][0-9]{2}}', + templateUrl: 'html/error.html', + controller: 'ErrorsController' + }); + + // http interceptors + + $httpProvider.interceptors.push(function($q, $injector, $rootScope) { + var requestIncrement = function(config) { + $rootScope.loading.requests = ++$rootScope.loading.requests || 1; + $rootScope.loading[config.method] = ++$rootScope.loading[config.method] || 1; + $rootScope.loading.any = true; + }; + + var requestDecrement = function(config) { + $rootScope.loading.requests = Math.max(--$rootScope.loading.requests, 0); + $rootScope.loading[config.method] = Math.max(--$rootScope.loading[config.method], 0); + $rootScope.loading.any = $rootScope.loading.requests > 0; + }; + + return { + request: function(config) { + requestIncrement(config); + return config; + }, + + requestError: function(rejection) { + requestDecrement(rejection.config); + return $q.reject(rejection); + }, + + response: function(response) { + requestDecrement(response.config); + return response; + }, + + responseError: function(rejection) { + requestDecrement(rejection.config); + $rootScope.error = rejection; + if (rejection.status >= 400 && rejection.status < 600) { + $injector.get('$state').transitionTo('error', { + code: rejection.status + }, { + location: 'replace' + }); + } + return $q.reject(rejection); + } + }; + }); + + } + ]); - app.run(['$rootScope', function($rootScope) { + app.run(['$rootScope', '$state', function($rootScope, $state) { $rootScope.loading = {}; $rootScope.Vipra = window.Vipra; + $rootScope.$on('$stateChangeError', function() { + $state.transitionTo('error', { + code: 404 + }, { + location: 'replace' + }); + }); + }]); })(); \ No newline at end of file diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js index 2c10e42e7611b16586038f3ff8d45b22c38bd486..aae45f1142d6e080ed40330138b6317f8456b4c5 100644 --- a/vipra-ui/app/js/controllers.js +++ b/vipra-ui/app/js/controllers.js @@ -489,6 +489,14 @@ } }; + $scope.resetZoom = function() { + if (!$scope.topicsSelected) return; + var highcharts = topicRelChartElement.highcharts(); + if (!highcharts) return; + + highcharts.xAxis[0].setExtremes(null, null); + }; + $scope.$watchGroup(['explorerModels.seqstyle', 'explorerModels.chartstyle', 'explorerModels.chartstack'], $scope.redrawGraph); $scope.$watch('explorerModels.sorttopics', function() { @@ -660,9 +668,12 @@ function($scope, $state, $stateParams, $timeout, TopicFactory, SequenceFactory) { $scope.topicsShowModels = { - seqstyle: 'absolute', - chartstyle: 'areaspline', - sortwords: '-likeliness' + relSeqstyle: 'absolute', + relChartstyle: 'areaspline', + wordSeqstyle: 'absolute', + wordChartstyle: 'areaspline', + topicSortwords: '-probability', + seqSortwords: '-probability' }; TopicFactory.get({ @@ -677,20 +688,20 @@ $scope.rootModels.topicModel = data.topicModel; $timeout(function() { - $scope.redrawGraph(); + $scope.redrawRelevanceGraph(); }, 0); }, function(err) { $scope.errors = err; }); - $scope.redrawGraph = function() { + $scope.redrawRelevanceGraph = function() { if (!$scope.topic || !$scope.topic.sequences) return; var relevances = []; // create series for (var i = 0, sequence, relevance; i < $scope.topic.sequences.length; i++) { sequence = $scope.topic.sequences[i]; - relevance = $scope.topicsShowModels.seqstyle === 'relative' ? sequence.relevanceChange : sequence.relevance; + relevance = $scope.topicsShowModels.relSeqstyle === 'relative' ? sequence.relevanceChange : sequence.relevance; relevances.push([new Date(sequence.window.startDate).getTime(), relevance]); } @@ -698,7 +709,7 @@ $scope.topicSeq = areaRelevanceChart([{ name: $scope.topic.name, data: relevances - }], $scope.topicsShowModels.chartstyle); + }], $scope.topicsShowModels.relChartstyle); }; $scope.startRename = function() { @@ -743,15 +754,14 @@ } }; - $scope.$watch('topicsShowModels.seqstyle', $scope.redrawGraph); - $scope.$watch('topicsShowModels.chartstyle', $scope.redrawGraph); + $scope.$watch('topicsShowModels.relSeqstyle', $scope.redrawRelevanceGraph); + $scope.$watch('topicsShowModels.relChartstyle', $scope.redrawRelevanceGraph); $scope.$watchGroup(['sequenceId'], function() { if (!$scope.sequenceId) return; SequenceFactory.get({ - id: $scope.sequenceId, - topWords: 20 + id: $scope.sequenceId }, function(data) { $scope.sequence = data; }, function(err) { @@ -797,6 +807,21 @@ } ]); + /** + * Errors route + */ + app.controller('ErrorsController', ['$scope', '$state', '$stateParams', + function($scope, $state, $stateParams) { + if ($scope.error) { + $scope.code = $scope.error.status; + $scope.title = $scope.error.statusText; + } else { + $scope.code = $stateParams.code; + $scope.title = Vipra.statusMsg($scope.code); + } + } + ]); + /**************************************************************************** * Directive Controllers ****************************************************************************/ @@ -880,6 +905,9 @@ credits: { enabled: false }, + navigator: { + enabled: true + }, plotOptions: { areaspline: { fillOpacity: 0.5, diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js index c1db0a2fb28180dd72d28e561fa085dd1eed1a70..0757812a9fdca5848a11dfa697feccb4ef3c08be 100644 --- a/vipra-ui/app/js/directives.js +++ b/vipra-ui/app/js/directives.js @@ -2,7 +2,7 @@ * Vipra Application * Directives ******************************************************************************/ -/* globals angular, console, Vipra, bootbox */ +/* globals angular, Vipra, bootbox, $ */ (function() { "use strict"; @@ -214,4 +214,27 @@ }; }]); + app.directive('anchorLink', ['$timeout', '$location', function($timeout, $location) { + return { + scope: { + fragment: '@' + }, + restrict: 'E', + replace: true, + templateUrl: 'html/directives/anchor-link.html', + link: function($scope) { + $timeout(function() { + var hash = $location.hash(); + if (hash === $scope.fragment) { + var elem = $('#' + hash); + if (elem.length) { + window.scrollTo(0, elem.offset().top); + } + } + }, 10); + } + }; + + }]); + })(); \ No newline at end of file diff --git a/vipra-ui/app/js/helpers.js b/vipra-ui/app/js/helpers.js index 267d5d4244273dbc50633e99beb0966d52f0a3ea..88d0198a5be8b1770cd9c1033b994a12d6574306 100644 --- a/vipra-ui/app/js/helpers.js +++ b/vipra-ui/app/js/helpers.js @@ -77,6 +77,95 @@ return n < 10 ? '0' + n : n; }; + Vipra.statusMsg = function(status) { + switch (parseInt(status)) { + case 400: + return 'Bad Request'; + case 401: + return 'Unauthorized'; + case 402: + return 'Payment Required'; + case 403: + return 'Forbidden'; + case 404: + return 'Not Found'; + case 405: + return 'Method Not Allowed'; + case 406: + return 'Not Acceptable'; + case 407: + return 'Proxy Authentication Required'; + case 408: + return 'Request Time-out'; + case 409: + return 'Conflict'; + case 410: + return 'Gone'; + case 411: + return 'Length Required'; + case 412: + return 'Precondition Failed'; + case 413: + return 'Request Entity Too Large'; + case 414: + return 'Request-URL Too Long'; + case 415: + return 'Unsupported Media Type'; + case 416: + return 'Requested range not satisfiable'; + case 417: + return 'Expectation Failed'; + case 418: + return 'I’m a teapot'; + case 420: + return 'Policy Not Fulfilled'; + case 421: + return 'Misdirected Request'; + case 422: + return 'Unprocessable Entity'; + case 423: + return 'Locked'; + case 424: + return 'Failed Dependency'; + case 425: + return 'Unordered Collection'; + case 426: + return 'Upgrade Required'; + case 428: + return 'Precondition Required'; + case 429: + return 'Too Many Requests'; + case 431: + return 'Request Header Fields Too Large'; + case 451: + return 'Unavailable For Legal Reasons'; + case 500: + return 'Internal Server Error'; + case 501: + return 'Not Implemented'; + case 502: + return 'Bad Gateway'; + case 503: + return 'Service Unavailable'; + case 504: + return 'Gateway Time-out'; + case 505: + return 'HTTP Version not supported'; + case 506: + return 'Variant Also Negotiates'; + case 508: + return 'Loop Detected'; + case 509: + return 'Bandwidth Limit Exceeded'; + case 510: + return 'Not Extended'; + case 511: + return 'Network Authentication Required'; + default: + return 'Unknown Error Code'; + } + }; + /** * Polyfills */ diff --git a/vipra-ui/app/less/app.less b/vipra-ui/app/less/app.less index cb1d41d3f94e08e453e92941bb7c5882b72cbab7..6feb0298cfd1f60cdcb2253bb55efd71c904d759 100644 --- a/vipra-ui/app/less/app.less +++ b/vipra-ui/app/less/app.less @@ -1,10 +1,13 @@ @basecolor: #007aa3; +@topbarSpace: 70px; + html { position: relative; min-height: 100%; } body { + padding-top: @topbarSpace; padding-bottom: 20px; } @@ -379,6 +382,30 @@ topic-menu { } +.anchor-link { + font-size: 12px; + display: inline-block; + margin-left: -19px; + width: 19px; + padding: 5px 0; + vertical-align: middle; + + > i { + visibility: hidden; + } + + > span { + display: block; + position: absolute; + top: -@topbarSpace; + } + + *:hover > & > i, + &:hover > i { + visibility: visible; + } +} + @-moz-keyframes spin { 100% { -moz-transform: rotateY(360deg); diff --git a/vipra-ui/gulpfile.js b/vipra-ui/gulpfile.js index c8ba416b009df91a187ad69fba4cea06671dcdb8..39729e97f35768caff4a7f46c62ad03ccca9e979 100644 --- a/vipra-ui/gulpfile.js +++ b/vipra-ui/gulpfile.js @@ -17,7 +17,7 @@ var assets = { 'bower_components/angular-sanitize/angular-sanitize.min.js', 'bower_components/angular-ui-router/release/angular-ui-router.min.js', 'bower_components/bootstrap/dist/js/bootstrap.min.js', - 'bower_components/highcharts/highcharts.js', + 'bower_components/highcharts/highstock.js', 'bower_components/vis/dist/vis.min.js', 'bower_components/moment/min/moment.min.js', 'bower_components/nya-bootstrap-select/dist/js/nya-bs-select.min.js', @@ -108,4 +108,4 @@ gulp.task('server', function() { port: 4200, fallback: 'index.html' })); -}); +}); \ No newline at end of file diff --git a/vipra-util/src/main/java/de/vipra/util/CompareMap.java b/vipra-util/src/main/java/de/vipra/util/CompareMap.java new file mode 100644 index 0000000000000000000000000000000000000000..8d9fe893c382a976ce9182d0995d54e71d787685 --- /dev/null +++ b/vipra-util/src/main/java/de/vipra/util/CompareMap.java @@ -0,0 +1,26 @@ +package de.vipra.util; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class CompareMap<T extends Comparable<T>> { + + private final Map<Object, T> map = new HashMap<>(); + + public void add(final T t) { + final T old = map.get(t); + if (old == null || t.compareTo(old) > 0) + map.put(t, t); + } + + public void addAll(final Collection<T> ts) { + for (final T t : ts) + add(t); + } + + public Collection<T> values() { + return map.values(); + } + +} 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 ea6d2591b5f2361fc8d6e9dc5898933c1d8317b0..e993274b308631b7bd3934ac85030c52d3e4323d 100644 --- a/vipra-util/src/main/java/de/vipra/util/Constants.java +++ b/vipra-util/src/main/java/de/vipra/util/Constants.java @@ -61,7 +61,7 @@ public class Constants { /** * The number of words to be used to generate a topic name. The top n words - * (sorted by likeliness) are used to generate a name for unnamed topics. + * (sorted by probability) are used to generate a name for unnamed topics. * Default 4. */ public static final int TOPIC_AUTO_NAMING_WORDS = 4; @@ -72,6 +72,11 @@ public class Constants { */ public static final int K_TOPICS = 20; + /** + * Number of top words per sequence and topic + */ + public static final int K_TOP_WORDS = 20; + /** * This value is a weight to the rising decay caulculation of topic * relevances. The higher this value, the more focus is put on later @@ -80,7 +85,7 @@ public class Constants { public static final double RISING_DECAY_LAMBDA = 0.0; /** - * Minimum likeliness of words. Words with lower likeliness are ignored. + * Minimum probability of words. Words with lower probability are ignored. * Default 0.01. */ public static final double MIN_RELATIVE_PROB = 0.01; diff --git a/vipra-util/src/main/java/de/vipra/util/model/SequenceFull.java b/vipra-util/src/main/java/de/vipra/util/model/SequenceFull.java index a2124b5975f528f92cd0becaf7fb6ef6d7588af4..55d58536fe95826979bcfe276243102e59ef38c4 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/SequenceFull.java +++ b/vipra-util/src/main/java/de/vipra/util/model/SequenceFull.java @@ -38,7 +38,7 @@ public class SequenceFull implements Model<ObjectId>, Comparable<SequenceFull>, @Embedded @QueryIgnore(multi = true) - private List<TopicWord> words; + private List<SequenceWord> words; @Override public ObjectId getId() { @@ -90,11 +90,11 @@ public class SequenceFull implements Model<ObjectId>, Comparable<SequenceFull>, this.topic = topic; } - public List<TopicWord> getWords() { + public List<SequenceWord> getWords() { return words; } - public void setWords(final List<TopicWord> words) { + public void setWords(final List<SequenceWord> words) { this.words = words; } diff --git a/vipra-util/src/main/java/de/vipra/util/model/SequenceWord.java b/vipra-util/src/main/java/de/vipra/util/model/SequenceWord.java new file mode 100644 index 0000000000000000000000000000000000000000..7499f8b8da606ff71dea08bd1274941d90638916 --- /dev/null +++ b/vipra-util/src/main/java/de/vipra/util/model/SequenceWord.java @@ -0,0 +1,76 @@ +package de.vipra.util.model; + +import java.io.Serializable; + +import org.mongodb.morphia.annotations.Embedded; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +@SuppressWarnings("serial") +@Embedded +public class SequenceWord implements Comparable<SequenceWord>, Serializable { + + private String word; + + private Double probability; + + public SequenceWord() {} + + public SequenceWord(final String word, final Double probability) { + this.word = word; + this.probability = probability; + } + + public String getWord() { + return word; + } + + public void setWord(final String word) { + this.word = word; + } + + public Double getProbability() { + return probability; + } + + public void setProbability(final Double probability) { + this.probability = probability; + } + + @Override + public int compareTo(final SequenceWord o) { + return Double.compare(probability, o.getProbability()); + } + + @Override + public String toString() { + return "SequenceWord [word=" + word + ", probability=" + probability + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((word == null) ? 0 : word.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final SequenceWord other = (SequenceWord) obj; + if (word == null) { + if (other.word != null) + return false; + } else if (!word.equals(other.word)) + return false; + return true; + } + +} diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java index d8538861ee0bb10606984f81569f6589ec73e796..8e4c184a6ea1d490cddbffbc373292d72842f6bb 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java +++ b/vipra-util/src/main/java/de/vipra/util/model/TopicFull.java @@ -38,6 +38,9 @@ public class TopicFull implements Model<ObjectId>, Serializable { @QueryIgnore(multi = true) private List<Sequence> sequences; + @QueryIgnore(multi = true) + private List<TopicWord> words; + @QueryIgnore(multi = true) private Double avgRelevance; @@ -95,6 +98,14 @@ public class TopicFull implements Model<ObjectId>, Serializable { this.sequences = sequences; } + public List<TopicWord> getWords() { + return words; + } + + public void setWords(final List<TopicWord> words) { + this.words = words; + } + public Double getAvgRelevance() { return avgRelevance; } @@ -158,7 +169,7 @@ public class TopicFull implements Model<ObjectId>, Serializable { created = modified; } - public static String getNameFromWords(final int wordsNum, final List<TopicWord> words) { + public static String getNameFromWords(final int wordsNum, final List<SequenceWord> words) { String name = null; if (words != null && words.size() > 0) { final int size = Math.min(wordsNum, words.size()); diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicModelConfig.java b/vipra-util/src/main/java/de/vipra/util/model/TopicModelConfig.java index 4dde14aa7bd8bb9e76c2e60839ba49fa44dfd77a..88205fcedb56ae8899c89421620c339ded8f80be 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/TopicModelConfig.java +++ b/vipra-util/src/main/java/de/vipra/util/model/TopicModelConfig.java @@ -19,6 +19,7 @@ public class TopicModelConfig implements Serializable { private String name; private String description; private int kTopics = Constants.K_TOPICS; + private int kTopWords = Constants.K_TOP_WORDS; private int dynamicMinIterations = Constants.DYNAMIC_MIN_ITER; private int dynamicMaxIterations = Constants.DYNAMIC_MAX_ITER; private int staticIterations = Constants.STATIC_ITER; @@ -38,6 +39,7 @@ public class TopicModelConfig implements Serializable { public TopicModelConfig(final TopicModelConfig topicModelConfig) { kTopics = topicModelConfig.getkTopics(); + kTopWords = topicModelConfig.getkTopWords(); dynamicMinIterations = topicModelConfig.getDynamicMinIterations(); dynamicMaxIterations = topicModelConfig.getDynamicMaxIterations(); staticIterations = topicModelConfig.getStaticIterations(); @@ -78,6 +80,14 @@ public class TopicModelConfig implements Serializable { this.kTopics = kTopics; } + public int getkTopWords() { + return kTopWords; + } + + public void setkTopWords(final int kTopWords) { + this.kTopWords = kTopWords; + } + public int getDynamicMinIterations() { return dynamicMinIterations; } diff --git a/vipra-util/src/main/java/de/vipra/util/model/TopicModelFull.java b/vipra-util/src/main/java/de/vipra/util/model/TopicModelFull.java index ead4c87e29ac95dbbce1d1c23558a248390c54a7..2dbde8fa545f62eac1abd506f00e22a10fc76fe1 100644 --- a/vipra-util/src/main/java/de/vipra/util/model/TopicModelFull.java +++ b/vipra-util/src/main/java/de/vipra/util/model/TopicModelFull.java @@ -55,7 +55,7 @@ public class TopicModelFull implements Model<String>, Comparable<TopicModelFull> return topicCount; } - public void setTopicCount(int topicCount) { + public void setTopicCount(final int topicCount) { this.topicCount = topicCount; } @@ -63,7 +63,7 @@ public class TopicModelFull implements Model<String>, Comparable<TopicModelFull> return articleCount; } - public void setArticleCount(int articleCount) { + public void setArticleCount(final int articleCount) { this.articleCount = articleCount; } @@ -71,7 +71,7 @@ public class TopicModelFull implements Model<String>, Comparable<TopicModelFull> return wordCount; } - public void setWordCount(int wordCount) { + public void setWordCount(final int wordCount) { this.wordCount = wordCount; } @@ -79,7 +79,7 @@ public class TopicModelFull implements Model<String>, Comparable<TopicModelFull> return windowCount; } - public void setWindowCount(int windowCount) { + public void setWindowCount(final int windowCount) { this.windowCount = windowCount; } 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 a69bd3ad96467b064e821a1f1d613608b6f589f0..05faecfc69180d6bf4abfbacaa13144f66c91ce9 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 @@ -1,31 +1,17 @@ package de.vipra.util.model; import java.io.Serializable; +import java.util.List; import org.mongodb.morphia.annotations.Embedded; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonSetter; - -@JsonIgnoreProperties(ignoreUnknown = true) @SuppressWarnings("serial") @Embedded public class TopicWord implements Comparable<TopicWord>, Serializable { - @Embedded - @JsonIgnore private String word; - private Double likeliness; - - public TopicWord() {} - - public TopicWord(final String word, final Double likeliness) { - this.word = word; - this.likeliness = likeliness; - } + private List<Double> sequenceProbabilities; public String getWord() { return word; @@ -35,57 +21,17 @@ public class TopicWord implements Comparable<TopicWord>, Serializable { this.word = word; } - @JsonGetter("id") - public String getWordString() { - return word; - } - - @JsonSetter("id") - public void setWordString(final String word) { - this.word = word; - } - - public Double getLikeliness() { - return likeliness; + public List<Double> getSequenceProbabilities() { + return sequenceProbabilities; } - public void setLikeliness(final Double likeliness) { - this.likeliness = likeliness; + public void setSequenceProbabilities(final List<Double> sequenceProbabilities) { + this.sequenceProbabilities = sequenceProbabilities; } @Override public int compareTo(final TopicWord o) { - return Double.compare(likeliness, o.getLikeliness()); - } - - @Override - public String toString() { - return "TopicWord [word=" + word + ", likeliness=" + likeliness + "]"; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((word == null) ? 0 : word.hashCode()); - return result; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - final TopicWord other = (TopicWord) obj; - if (word == null) { - if (other.word != null) - return false; - } else if (!word.equals(other.word)) - return false; - return true; + return word.compareTo(o.getWord()); } } diff --git a/vipra-util/src/main/java/de/vipra/util/service/MongoService.java b/vipra-util/src/main/java/de/vipra/util/service/MongoService.java index 0f881b0d018ad724fb105461bfbb03f69b6eac3f..0462fafe3c3fea96342deee2506fea73c133d301 100644 --- a/vipra-util/src/main/java/de/vipra/util/service/MongoService.java +++ b/vipra-util/src/main/java/de/vipra/util/service/MongoService.java @@ -57,15 +57,28 @@ public class MongoService<Type extends Model<IdType>, IdType> implements Service } @Override - public Type getSingle(final IdType id, final boolean allFields, final String... fields) throws DatabaseException { - if (id == null) - throw new DatabaseException(new NullPointerException("id is null")); + public Type getSingle(final IdType id, final String... fields) throws DatabaseException { + return getSingle(id, QueryBuilder.builder().fields(true, fields)); + } + @Override + public Type getSingle(final IdType id, final QueryBuilder builder) throws DatabaseException { final Query<Type> query = datastore.createQuery(clazz).field("_id").equal(id); - if (fields != null && fields.length > 0) - query.retrievedFields(true, fields); - else if (!allFields && ignoredFieldsSingleQuery.length > 0) + + if (builder != null) { + if (builder.getFields() != null) { + final String[] fields = builder.getFields(); + if (builder.isInclude()) { + query.retrievedFields(true, fields); + } else { + query.retrievedFields(false, fields); + } + } else if (!builder.isAllFields() && ignoredFieldsSingleQuery.length > 0) { + query.retrievedFields(false, ignoredFieldsSingleQuery); + } + } else if (ignoredFieldsSingleQuery.length > 0) { query.retrievedFields(false, ignoredFieldsSingleQuery); + } final Type t = query.get(); return t; } diff --git a/vipra-util/src/main/java/de/vipra/util/service/Service.java b/vipra-util/src/main/java/de/vipra/util/service/Service.java index e791c8d0c76a35da12a6d87a5c230270b7f3971f..d198189b168668a7059308d32f62cac0f45342d3 100644 --- a/vipra-util/src/main/java/de/vipra/util/service/Service.java +++ b/vipra-util/src/main/java/de/vipra/util/service/Service.java @@ -19,17 +19,22 @@ import de.vipra.util.model.Model; */ public interface Service<Type extends Model<IdType>, IdType, E extends Exception> { + /** + * @see {@link Service#getSingle(Object, QueryBuilder)} + */ + Type getSingle(IdType id, String... fields) throws E; + /** * Returns a single entity from the database or null * * @param id * id of the entity - * @param fields - * fields to be returned from the database, if supported + * @param builder + * query builder * @return retrieved entity or null * @throws E */ - Type getSingle(IdType id, boolean allFields, String... fields) throws E; + Type getSingle(IdType id, QueryBuilder builder) throws E; /** * @see {@link Service#getMultiple(QueryBuilder)} @@ -260,20 +265,22 @@ public interface Service<Type extends Model<IdType>, IdType, E extends Exception * * @param include * true to include, false to exclude - * @param strings + * @param fields * fields to in/exclude * @return QueryBuilder instance */ - public QueryBuilder fields(final boolean include, final String... strings) { - this.include = include; - for (String field : strings) { - if (field.equalsIgnoreCase("_all")) { - this.allFields = true; - this.fields = null; - return this; + public QueryBuilder fields(final boolean include, final String... fields) { + if (fields != null) { + this.include = include; + for (final String field : fields) { + if (field.equalsIgnoreCase("_all")) { + this.allFields = true; + this.fields = null; + return this; + } } + this.fields = fields; } - this.fields = strings; return this; }