branch develop updated (baed38f -> 5f143e7)
This is an automated email from the git hooks/post-receive script. New change to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git from baed38f refs-90 #7778 Ajout d'un filtre de recherche sur la date de dépot d'un document new 70270ab refs-10 #7776 Prepare la recherche des mots les plus pertinents sur les projets new d50fb02 refs-20 #7776 Recherche lucene des mots les plus fréquents dans l'ensemble des projets new 19f5785 refs-40 #7776 mise en place d'un service rest renvoyant les mots les plus courrant de l'application new 3f4d90b refs-80 #7776 mise en plac du nuage de mot sur l'ui new dd7d23a fixes #7776 Utilisation de webjars pour jqcloud et angular-jqcloud new 5f143e7 Merge branch 'feature/R7776-cloudTags-in-homepage' into develop The 6 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Detailed log of new commits: commit 5f143e7d8dd719cb7f900698849fe9fb2e4b6cf1 Merge: baed38f dd7d23a Author: Yannick Martel <martel@©odelutin.com> Date: Thu Dec 17 10:45:34 2015 +0100 Merge branch 'feature/R7776-cloudTags-in-homepage' into develop commit dd7d23acfc062cc38b450f103d1feeeaab361eec Author: Yannick Martel <martel@©odelutin.com> Date: Thu Dec 17 10:45:13 2015 +0100 fixes #7776 Utilisation de webjars pour jqcloud et angular-jqcloud commit 3f4d90b5b2416d830c3063c5a895b09f66a71aaa Author: Yannick Martel <martel@©odelutin.com> Date: Wed Dec 16 16:24:48 2015 +0100 refs-80 #7776 mise en plac du nuage de mot sur l'ui commit 19f578564c7717efe5bbfa816eef048d5c0451cc Author: Yannick Martel <martel@©odelutin.com> Date: Wed Dec 16 12:26:32 2015 +0100 refs-40 #7776 mise en place d'un service rest renvoyant les mots les plus courrant de l'application commit d50fb02e7473a52cedf06c7e37b16a5acc5209b0 Author: Yannick Martel <martel@©odelutin.com> Date: Tue Dec 15 16:41:03 2015 +0100 refs-20 #7776 Recherche lucene des mots les plus fréquents dans l'ensemble des projets commit 70270ab3832eaa95241c4c7b95f764f1af8a8f02 Author: Yannick Martel <martel@©odelutin.com> Date: Mon Dec 14 17:56:16 2015 +0100 refs-10 #7776 Prepare la recherche des mots les plus pertinents sur les projets Summary of changes: coselmar-rest/pom.xml | 8 ++ .../java/fr/ifremer/coselmar/beans/CloudWord.java | 33 +++++ .../indexation/DocumentsIndexationService.java | 9 +- .../coselmar/services/indexation/LuceneUtils.java | 5 +- .../indexation/QuestionsIndexationService.java | 154 ++++++++++++++++----- .../indexation/TransverseIndexationService.java | 116 ++++++++++++++++ .../coselmar/services/v1/GeneralWebService.java | 74 ++++++++++ .../lucene/misc/HighFreqTermsMultiFields.java | 75 ++++++++++ coselmar-rest/src/main/resources/mapping | 4 + .../indexation/QuestionsIndexationServiceTest.java | 102 ++++++++++++++ coselmar-ui/pom.xml | 12 ++ coselmar-ui/src/main/webapp/css/jqcloud-2.0.0.css | 59 ++++++++ coselmar-ui/src/main/webapp/index.html | 6 + .../src/main/webapp/js/coselmar-controllers.js | 13 +- ...r-constants.js => coselmar-general-services.js} | 24 +++- coselmar-ui/src/main/webapp/js/coselmar.js | 2 +- coselmar-ui/src/main/webapp/views/home.html | 7 + pom.xml | 31 ++++- 18 files changed, 687 insertions(+), 47 deletions(-) create mode 100644 coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/CloudWord.java create mode 100644 coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/TransverseIndexationService.java create mode 100644 coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/GeneralWebService.java create mode 100644 coselmar-rest/src/main/java/org/apache/lucene/misc/HighFreqTermsMultiFields.java create mode 100644 coselmar-ui/src/main/webapp/css/jqcloud-2.0.0.css copy coselmar-ui/src/main/webapp/js/{coselmar-constants.js => coselmar-general-services.js} (63%) -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git commit 70270ab3832eaa95241c4c7b95f764f1af8a8f02 Author: Yannick Martel <martel@©odelutin.com> Date: Mon Dec 14 17:56:16 2015 +0100 refs-10 #7776 Prepare la recherche des mots les plus pertinents sur les projets --- coselmar-rest/pom.xml | 4 + .../coselmar/services/indexation/LuceneUtils.java | 5 +- .../indexation/QuestionsIndexationService.java | 97 +++++++++++++++------- .../indexation/QuestionsIndexationServiceTest.java | 54 ++++++++++++ pom.xml | 8 +- 5 files changed, 136 insertions(+), 32 deletions(-) diff --git a/coselmar-rest/pom.xml b/coselmar-rest/pom.xml index c9bcc0a..e31bfdb 100644 --- a/coselmar-rest/pom.xml +++ b/coselmar-rest/pom.xml @@ -156,6 +156,10 @@ <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> </dependency> + <dependency> + <groupId>org.apache.lucene</groupId> + <artifactId>lucene-misc</artifactId> + </dependency> <!-- Others --> <dependency> diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/LuceneUtils.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/LuceneUtils.java index e668303..c471fac 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/LuceneUtils.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/LuceneUtils.java @@ -36,7 +36,6 @@ import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.store.Directory; import org.apache.lucene.store.NIOFSDirectory; -import org.apache.lucene.util.Version; /** * @author ymartel <martel@codelutin.com> @@ -46,7 +45,7 @@ public class LuceneUtils { private static final Log log = LogFactory.getLog(LuceneUtils.class); public Analyzer analyzer; - public final IndexWriterConfig indexationConfig = new IndexWriterConfig(Version.LUCENE_4_10_2, getAnalyzer()); + public final IndexWriterConfig indexationConfig = new IndexWriterConfig(getAnalyzer()); public IndexWriter indexWriter; protected CoselmarServicesConfig servicesConfig; @@ -68,7 +67,7 @@ public class LuceneUtils { public IndexWriter getIndexWriter() throws IOException { if (indexWriter == null) { File indexDirectory = servicesConfig.getIndexDirectory(); - Directory index = NIOFSDirectory.open(indexDirectory); + Directory index = NIOFSDirectory.open(indexDirectory.toPath()); indexationConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND); indexWriter = new IndexWriter(index, indexationConfig); diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java index 70d2469..cc8c0d0 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java @@ -24,11 +24,6 @@ package fr.ifremer.coselmar.services.indexation; * #L% */ -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - import fr.ifremer.coselmar.beans.QuestionBean; import fr.ifremer.coselmar.beans.QuestionSearchBean; import fr.ifremer.coselmar.services.CoselmarSimpleServiceSupport; @@ -38,15 +33,27 @@ import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; import org.apache.lucene.document.TextField; import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.Term; +import org.apache.lucene.index.TermsEnum; +import org.apache.lucene.misc.HighFreqTerms; +import org.apache.lucene.misc.TermStats; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.MultiTermQueryWrapperFilter; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.WildcardQuery; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * This Services provides operation about {@link fr.ifremer.coselmar.persistence.entity.Document} * or more exactly {@link fr.ifremer.coselmar.beans.DocumentBean} indexation : @@ -69,7 +76,7 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { protected static final String QUESTION_THEME_INDEX_PROPERTY = "questionTheme"; protected static final String QUESTION_STATUS_INDEX_PROPERTY = "questionStatus"; protected static final String QUESTION_PRIVACY_INDEX_PROPERTY = "questionPrivacy"; - protected static final String DOCUMENT_TYPE = "question"; + protected static final String DOCUMENT_TYPE = "questionIndexType"; public void indexQuestion(QuestionBean question) throws IOException { @@ -78,11 +85,12 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { IndexSearcher isearcher = new IndexSearcher(ireader); // Retrieve document - BooleanQuery query = new BooleanQuery(); - query.add(new TermQuery(new Term(QUESTION_ID_INDEX_PROPERTY, question.getId())), BooleanClause.Occur.MUST); - query.add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.MUST); + BooleanQuery query = new BooleanQuery.Builder() + .add(new TermQuery(new Term(QUESTION_ID_INDEX_PROPERTY, question.getId())), BooleanClause.Occur.MUST) + .add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.MUST) + .build(); - ScoreDoc[] hits = isearcher.search(query, null, 1000).scoreDocs; + ScoreDoc[] hits = isearcher.search(query, 1000).scoreDocs; if (hits.length > 0) { Document doc = new Document(); @@ -94,7 +102,7 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { Set<String> themes = question.getThemes(); if (themes != null) { for (String theme : themes) { - doc.add(new Field(QUESTION_THEME_INDEX_PROPERTY, theme, TextField.TYPE_STORED)); + doc.add(new TextField(QUESTION_THEME_INDEX_PROPERTY, theme, Field.Store.YES)); } } @@ -122,6 +130,7 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { if (themes != null) { for (String theme : themes) { doc.add(new Field(QUESTION_THEME_INDEX_PROPERTY, theme, TextField.TYPE_STORED)); + doc.add(new TextField(QUESTION_THEME_INDEX_PROPERTY, theme, Field.Store.YES)); } } @@ -142,54 +151,59 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { IndexSearcher isearcher = new IndexSearcher(ireader); // Combine that with the type - BooleanQuery fullQuery = new BooleanQuery(); - fullQuery.add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.MUST); + BooleanQuery.Builder queryBuilder = new BooleanQuery.Builder(); + queryBuilder.add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.MUST); String searchPrivacy = searchBean.getPrivacy(); if(StringUtils.isNotBlank(searchPrivacy)) { - fullQuery.add(new TermQuery(new Term(QUESTION_PRIVACY_INDEX_PROPERTY, searchPrivacy.toLowerCase())), BooleanClause.Occur.MUST); + queryBuilder.add(new TermQuery(new Term(QUESTION_PRIVACY_INDEX_PROPERTY, searchPrivacy.toLowerCase())), BooleanClause.Occur.MUST); } String searchStatus = searchBean.getStatus(); if(StringUtils.isNotBlank(searchStatus)) { - fullQuery.add(new TermQuery(new Term(QUESTION_STATUS_INDEX_PROPERTY, searchStatus.toLowerCase())), BooleanClause.Occur.MUST); + queryBuilder.add(new TermQuery(new Term(QUESTION_STATUS_INDEX_PROPERTY, searchStatus.toLowerCase())), BooleanClause.Occur.MUST); } // Keywords part List<String> keywords = searchBean.getFullTextSearch(); if (keywords != null && !keywords.isEmpty()) { - BooleanQuery keywordsQuery = new BooleanQuery(); + BooleanQuery.Builder keywordsQueryBuilder = new BooleanQuery.Builder(); for (String text : keywords) { String[] words = text.replaceAll("[^a-zA-Z ]", "").toLowerCase().split(" "); // Parse a simple query that searches for the "text": - BooleanQuery query = new BooleanQuery(); - BooleanQuery nameQuery = new BooleanQuery(); - BooleanQuery summaryQuery = new BooleanQuery(); + BooleanQuery.Builder nameQueryBuilder = new BooleanQuery.Builder(); + BooleanQuery.Builder summaryQueryBuilder = new BooleanQuery.Builder(); for (String word : words) { String wildWord = String.format("*%s*", word.toLowerCase()); - nameQuery.add(new WildcardQuery(new Term(QUESTION_TITLE_INDEX_PROPERTY, wildWord)), BooleanClause.Occur.MUST); - summaryQuery.add(new WildcardQuery(new Term(QUESTION_SUMMARY_INDEX_PROPERTY, wildWord)), BooleanClause.Occur.MUST); + nameQueryBuilder.add(new WildcardQuery(new Term(QUESTION_TITLE_INDEX_PROPERTY, wildWord)), BooleanClause.Occur.MUST); + summaryQueryBuilder.add(new WildcardQuery(new Term(QUESTION_SUMMARY_INDEX_PROPERTY, wildWord)), BooleanClause.Occur.MUST); } - query.add(nameQuery, BooleanClause.Occur.SHOULD); - query.add(summaryQuery, BooleanClause.Occur.SHOULD); + BooleanQuery nameQuery = nameQueryBuilder.build(); + BooleanQuery summaryQuery = summaryQueryBuilder.build(); + - query.add(new TermQuery(new Term(QUESTION_THEME_INDEX_PROPERTY, text.toLowerCase())), BooleanClause.Occur.SHOULD); + BooleanQuery query = new BooleanQuery.Builder() + .add(nameQuery, BooleanClause.Occur.SHOULD) + .add(summaryQuery, BooleanClause.Occur.SHOULD) + .add(new TermQuery(new Term(QUESTION_THEME_INDEX_PROPERTY, text.toLowerCase())), BooleanClause.Occur.SHOULD) + .build(); - keywordsQuery.add(query, BooleanClause.Occur.MUST); + keywordsQueryBuilder.add(query, BooleanClause.Occur.MUST); } + BooleanQuery keywordsQuery = keywordsQueryBuilder.build(); // add to complete query - fullQuery.add(keywordsQuery, BooleanClause.Occur.MUST); + queryBuilder.add(keywordsQuery, BooleanClause.Occur.MUST); } - - ScoreDoc[] hits = isearcher.search(fullQuery, null, 1000).scoreDocs; + BooleanQuery fullQuery = queryBuilder.build(); + ScoreDoc[] hits = isearcher.search(fullQuery, 1000).scoreDocs; List<String> documentIds = new ArrayList(hits.length); @@ -222,5 +236,32 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { getLuceneUtils().getIndexWriter().commit(); } + public Map<String, Long> getTopTerms() throws IOException, ParseException { + + DirectoryReader ireader = DirectoryReader.open(getLuceneUtils().getIndexWriter(), false); + + IndexSearcher isearcher = new IndexSearcher(ireader); + + Map<String, Long> result = new LinkedHashMap<>(); + try { + TermStats[] highFreqTerms = HighFreqTerms.getHighFreqTerms(ireader, 20, null, new HighFreqTerms.TotalTermFreqComparator()); + for (TermStats termStats : highFreqTerms) { + long totalTermFreq = termStats.totalTermFreq; + String value = termStats.termtext.utf8ToString(); + + if (result.containsKey(value)) { + result.put(value, result.get(value) + totalTermFreq); + } else { + result.put(value, totalTermFreq); + } + } + + } catch (Exception e) { + e.printStackTrace(); + } + + ireader.close(); + return result; + } } diff --git a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java index b71bb04..a85dad8 100644 --- a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java +++ b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java @@ -28,6 +28,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Map; import com.google.common.collect.Sets; import fr.ifremer.coselmar.beans.QuestionBean; @@ -369,4 +370,57 @@ public class QuestionsIndexationServiceTest extends AbstractCoselmarServiceTest questionsIndexationService.cleanIndex(); } + + @Test + public void testGetTopTerms() throws Exception { + + QuestionBean questionOne = new QuestionBean(); + String questionOneId = "question_1_test_search" + System.currentTimeMillis(); + questionOne.setId(questionOneId); + questionOne.setTitle("Awesome question"); + questionOne.setSummary("Can we, just once, ask about it ?"); + questionOne.setDeadline(DateUtil.createDateAfterToday(1, 0, 1)); + questionOne.setExternalExperts(Sets.newHashSet("Amelia", "Rory", "River")); + questionOne.setSubmissionDate(new Date()); + questionOne.setStatus(Status.OPEN.name()); + questionOne.setPrivacy(Privacy.PUBLIC.name()); + questionOne.setThemes(Sets.newHashSet("TARDIS", "Universe", "Time", "Space")); + + QuestionBean questionTwo = new QuestionBean(); + String questionTwoId = "question_2_test_search" + System.currentTimeMillis(); + questionTwo.setId(questionTwoId); + questionTwo.setTitle("The ultimate"); + questionTwo.setSummary("We need some question"); + questionTwo.setDeadline(DateUtil.createDateAfterToday(16, 0, 0)); + questionTwo.setSubmissionDate(new Date()); + questionTwo.setStatus(Status.OPEN.name()); + questionTwo.setPrivacy(Privacy.PUBLIC.name()); + questionTwo.setThemes(Sets.newHashSet("test", "question")); + + QuestionBean questionThree = new QuestionBean(); + String questionThreeId = "question_3_test_search" + System.currentTimeMillis(); + questionThree.setId(questionThreeId); + questionThree.setTitle("There's someone missing. The question's Who?"); + questionThree.setSummary("Something old, Something new, Something borrowed, Something blue."); + questionThree.setDeadline(DateUtil.createDateAfterToday(16, 0, 0)); + questionThree.setSubmissionDate(new Date()); + questionThree.setStatus(Status.OPEN.name()); + questionThree.setPrivacy(Privacy.PRIVATE.name()); + questionThree.setThemes(Sets.newHashSet("big bang two", "Pandorica", "River", "Universe")); + + + CoselmarServicesContext serviceContext = getServiceContext(); + QuestionsIndexationService questionsIndexationService = + serviceContext.newService(QuestionsIndexationService.class); + + questionsIndexationService.indexQuestion(questionOne); + questionsIndexationService.indexQuestion(questionTwo); + questionsIndexationService.indexQuestion(questionThree); + + // Ok, let's search now ! + Map<String, Long> topTerms = questionsIndexationService.getTopTerms(); + Assert.assertNotNull(topTerms); + Assert.assertEquals(4, topTerms.get("question").longValue()); + + } } diff --git a/pom.xml b/pom.xml index 99aef73..f36a085 100644 --- a/pom.xml +++ b/pom.xml @@ -138,7 +138,7 @@ <postgresqlVersion>9.1-901-1.jdbc4</postgresqlVersion> <h2Version>1.4.190</h2Version> - <luceneVersion>4.10.3</luceneVersion> + <luceneVersion>5.4.0</luceneVersion> <tomcatEmbedVersion>7.0.50</tomcatEmbedVersion> @@ -306,6 +306,12 @@ <version>${luceneVersion}</version> </dependency> + <dependency> + <groupId>org.apache.lucene</groupId> + <artifactId>lucene-misc</artifactId> + <version>${luceneVersion}</version> + </dependency> + <!-- Commons --> <dependency> <groupId>org.apache.commons</groupId> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git commit d50fb02e7473a52cedf06c7e37b16a5acc5209b0 Author: Yannick Martel <martel@©odelutin.com> Date: Tue Dec 15 16:41:03 2015 +0100 refs-20 #7776 Recherche lucene des mots les plus fréquents dans l'ensemble des projets --- .../indexation/DocumentsIndexationService.java | 9 ++- .../indexation/QuestionsIndexationService.java | 51 +++++++++++---- .../lucene/misc/HighFreqTermsMultiFields.java | 75 ++++++++++++++++++++++ .../indexation/QuestionsIndexationServiceTest.java | 6 +- 4 files changed, 126 insertions(+), 15 deletions(-) diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/DocumentsIndexationService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/DocumentsIndexationService.java index af3b62a..144f4a2 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/DocumentsIndexationService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/DocumentsIndexationService.java @@ -66,7 +66,7 @@ public class DocumentsIndexationService extends CoselmarSimpleServiceSupport { protected static final String DOCUMENT_AUTHORS_INDEX_PROPERTY = "documentAuthors"; protected static final String DOCUMENT_SUMMARY_INDEX_PROPERTY = "documentSummary"; protected static final String DOCUMENT_KEYWORD_INDEX_PROPERTY = "documentKeyword"; - protected static final String DOCUMENT_TYPE = "document"; + protected static final String DOCUMENT_TYPE = "documentindextype"; public void indexDocument(DocumentBean document) throws IOException { @@ -237,8 +237,11 @@ public class DocumentsIndexationService extends CoselmarSimpleServiceSupport { } protected void cleanIndex() throws IOException { - BooleanQuery query = new BooleanQuery(); - query.add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.MUST); + BooleanQuery query = new BooleanQuery.Builder() + .add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.MUST) + //XXX ymartel 20151215 : Clean older DOCUMENT_TYPE value too (less or equals V1.0.1), should be removed after V2.0 + .add(new TermQuery(new Term("type", "document")), BooleanClause.Occur.SHOULD) + .build(); getLuceneUtils().getIndexWriter().deleteDocuments(query); getLuceneUtils().getIndexWriter().commit(); } diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java index cc8c0d0..d7fc8fc 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java @@ -33,16 +33,14 @@ import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; import org.apache.lucene.document.TextField; import org.apache.lucene.index.DirectoryReader; -import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.Term; -import org.apache.lucene.index.TermsEnum; import org.apache.lucene.misc.HighFreqTerms; +import org.apache.lucene.misc.HighFreqTermsMultiFields; import org.apache.lucene.misc.TermStats; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.IndexSearcher; -import org.apache.lucene.search.MultiTermQueryWrapperFilter; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.WildcardQuery; @@ -76,7 +74,7 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { protected static final String QUESTION_THEME_INDEX_PROPERTY = "questionTheme"; protected static final String QUESTION_STATUS_INDEX_PROPERTY = "questionStatus"; protected static final String QUESTION_PRIVACY_INDEX_PROPERTY = "questionPrivacy"; - protected static final String DOCUMENT_TYPE = "questionIndexType"; + protected static final String DOCUMENT_TYPE = "questionindextype"; public void indexQuestion(QuestionBean question) throws IOException { @@ -130,7 +128,6 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { if (themes != null) { for (String theme : themes) { doc.add(new Field(QUESTION_THEME_INDEX_PROPERTY, theme, TextField.TYPE_STORED)); - doc.add(new TextField(QUESTION_THEME_INDEX_PROPERTY, theme, Field.Store.YES)); } } @@ -220,9 +217,10 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { public void deleteQuestion(String documentId) throws IOException { // Retrieve document - BooleanQuery query = new BooleanQuery(); - query.add(new TermQuery(new Term(QUESTION_ID_INDEX_PROPERTY, documentId)), BooleanClause.Occur.MUST); - query.add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.MUST); + BooleanQuery query = new BooleanQuery.Builder() + .add(new TermQuery(new Term(QUESTION_ID_INDEX_PROPERTY, documentId)), BooleanClause.Occur.MUST) + .add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.MUST) + .build(); getLuceneUtils().getIndexWriter().deleteDocuments(query); getLuceneUtils().getIndexWriter().commit(); @@ -230,21 +228,51 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { } protected void cleanIndex() throws IOException { - BooleanQuery query = new BooleanQuery(); - query.add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.MUST); + BooleanQuery query = new BooleanQuery.Builder() + .add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.SHOULD) + //XXX ymartel 20151215 : Clean older DOCUMENT_TYPE value too (less or equals V1.0.1), should be removed after V2.0 + .add(new TermQuery(new Term("type", "question")), BooleanClause.Occur.SHOULD) + .build(); getLuceneUtils().getIndexWriter().deleteDocuments(query); getLuceneUtils().getIndexWriter().commit(); } public Map<String, Long> getTopTerms() throws IOException, ParseException { + DirectoryReader indexReader = DirectoryReader.open(getLuceneUtils().getIndexWriter(), false); + + Map<String, Long> result = new LinkedHashMap<>(); + try { + String[] searchedFields = {QUESTION_TITLE_INDEX_PROPERTY, QUESTION_SUMMARY_INDEX_PROPERTY, QUESTION_THEME_INDEX_PROPERTY}; + TermStats[] highFreqTerms = HighFreqTermsMultiFields.getHighFreqTermsMultiFields(indexReader, 20, searchedFields, new HighFreqTerms.TotalTermFreqComparator()); + for (TermStats termStats : highFreqTerms) { + long totalTermFreq = termStats.totalTermFreq; + String value = termStats.termtext.utf8ToString(); + + if (result.containsKey(value)) { + result.put(value, result.get(value) + totalTermFreq); + } else { + result.put(value, totalTermFreq); + } + } + + } catch (Exception e) { + e.printStackTrace(); + } + + indexReader.close(); + return result; + } + + public Map<String, Long> getTopTerms(String questionId) throws IOException, ParseException { + DirectoryReader ireader = DirectoryReader.open(getLuceneUtils().getIndexWriter(), false); IndexSearcher isearcher = new IndexSearcher(ireader); Map<String, Long> result = new LinkedHashMap<>(); try { - TermStats[] highFreqTerms = HighFreqTerms.getHighFreqTerms(ireader, 20, null, new HighFreqTerms.TotalTermFreqComparator()); + TermStats[] highFreqTerms = HighFreqTermsMultiFields.getHighFreqTermsMultiFields(ireader, 20, null, new HighFreqTerms.TotalTermFreqComparator()); for (TermStats termStats : highFreqTerms) { long totalTermFreq = termStats.totalTermFreq; String value = termStats.termtext.utf8ToString(); @@ -264,4 +292,5 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { return result; } + } diff --git a/coselmar-rest/src/main/java/org/apache/lucene/misc/HighFreqTermsMultiFields.java b/coselmar-rest/src/main/java/org/apache/lucene/misc/HighFreqTermsMultiFields.java new file mode 100644 index 0000000..525cc6a --- /dev/null +++ b/coselmar-rest/src/main/java/org/apache/lucene/misc/HighFreqTermsMultiFields.java @@ -0,0 +1,75 @@ +package org.apache.lucene.misc; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.apache.lucene.index.Fields; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.MultiFields; +import org.apache.lucene.index.Terms; +import org.apache.lucene.index.TermsEnum; + +import java.util.Comparator; + +/** + * + * <code>HighFreqTermsMultiField</code> class extends {@link HighFreqTerms} to allow extracts the top n most frequent terms + * (by document frequency ) from an existing Lucene index and reports their document frequency for several fields + * + * @see HighFreqTerms + */ +public class HighFreqTermsMultiFields extends HighFreqTerms { + + public static TermStats[] getHighFreqTermsMultiFields(IndexReader reader, int numTerms, String[] fieldNames, Comparator<TermStats> comparator) throws Exception { + + TermStatsQueue tiq = new TermStatsQueue(numTerms, comparator); + TermsEnum te; + + if (fieldNames != null) { + Fields fields = MultiFields.getFields(reader); + for (String field : fieldNames) { + Terms terms = fields.terms(field); + if (terms != null) { + te = terms.iterator(); + tiq.fill(field, te); + } + } + } else { + Fields fields = MultiFields.getFields(reader); + if (fields.size() == 0) { + throw new RuntimeException("no fields found for this index"); + } + for (String fieldName : fields) { + Terms terms = fields.terms(fieldName); + if (terms != null) { + tiq.fill(fieldName, terms.iterator()); + } + } + } + + TermStats[] result = new TermStats[tiq.size()]; + // we want highest first so we read the queue and populate the array + // starting at the end and work backwards + int count = tiq.size() - 1; + while (tiq.size() != 0) { + result[count] = tiq.pop(); + count--; + } + return result; + } + +} diff --git a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java index a85dad8..0f377ea 100644 --- a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java +++ b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java @@ -420,7 +420,11 @@ public class QuestionsIndexationServiceTest extends AbstractCoselmarServiceTest // Ok, let's search now ! Map<String, Long> topTerms = questionsIndexationService.getTopTerms(); Assert.assertNotNull(topTerms); - Assert.assertEquals(4, topTerms.get("question").longValue()); + Assert.assertEquals(3, topTerms.get("question").longValue()); + Assert.assertEquals(2, topTerms.get("universe").longValue()); + Assert.assertEquals(1, topTerms.get("river").longValue()); + Assert.assertEquals(1, topTerms.get("test").longValue()); + Assert.assertEquals(4, topTerms.get("something").longValue()); } } -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git commit 19f578564c7717efe5bbfa816eef048d5c0451cc Author: Yannick Martel <martel@©odelutin.com> Date: Wed Dec 16 12:26:32 2015 +0100 refs-40 #7776 mise en place d'un service rest renvoyant les mots les plus courrant de l'application --- coselmar-rest/pom.xml | 4 + .../java/fr/ifremer/coselmar/beans/CloudWord.java | 33 ++++++ .../indexation/QuestionsIndexationService.java | 48 ++++++--- .../indexation/TransverseIndexationService.java | 116 +++++++++++++++++++++ .../coselmar/services/v1/GeneralWebService.java | 74 +++++++++++++ coselmar-rest/src/main/resources/mapping | 4 + .../indexation/QuestionsIndexationServiceTest.java | 44 ++++++++ pom.xml | 7 ++ 8 files changed, 315 insertions(+), 15 deletions(-) diff --git a/coselmar-rest/pom.xml b/coselmar-rest/pom.xml index e31bfdb..0c3c03c 100644 --- a/coselmar-rest/pom.xml +++ b/coselmar-rest/pom.xml @@ -160,6 +160,10 @@ <groupId>org.apache.lucene</groupId> <artifactId>lucene-misc</artifactId> </dependency> + <dependency> + <groupId>org.apache.lucene</groupId> + <artifactId>lucene-backward-codecs</artifactId> + </dependency> <!-- Others --> <dependency> diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/CloudWord.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/CloudWord.java new file mode 100644 index 0000000..8e5523f --- /dev/null +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/CloudWord.java @@ -0,0 +1,33 @@ +package fr.ifremer.coselmar.beans; + +import java.io.Serializable; + +/** + * @author ymartel (martel@codelutin.com) + */ +public class CloudWord implements Serializable { + + protected String text; + protected long weight; + + public CloudWord(String text, long weight) { + this.text = text; + this.weight = weight; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public long getWeight() { + return weight; + } + + public void setWeight(long weight) { + this.weight = weight; + } +} diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java index d7fc8fc..65756af 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationService.java @@ -43,6 +43,7 @@ import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.WildcardQuery; import java.io.IOException; @@ -264,30 +265,47 @@ public class QuestionsIndexationService extends CoselmarSimpleServiceSupport { return result; } - public Map<String, Long> getTopTerms(String questionId) throws IOException, ParseException { + public Map<String, Long> getTopDocumentsTerms(List<String> questionIds) throws IOException, ParseException { DirectoryReader ireader = DirectoryReader.open(getLuceneUtils().getIndexWriter(), false); - IndexSearcher isearcher = new IndexSearcher(ireader); - Map<String, Long> result = new LinkedHashMap<>(); - try { - TermStats[] highFreqTerms = HighFreqTermsMultiFields.getHighFreqTermsMultiFields(ireader, 20, null, new HighFreqTerms.TotalTermFreqComparator()); - for (TermStats termStats : highFreqTerms) { - long totalTermFreq = termStats.totalTermFreq; - String value = termStats.termtext.utf8ToString(); + // Combine that with the type + BooleanQuery.Builder queryBuilder = new BooleanQuery.Builder(); + queryBuilder.add(new TermQuery(new Term("type", DOCUMENT_TYPE)), BooleanClause.Occur.MUST); - if (result.containsKey(value)) { - result.put(value, result.get(value) + totalTermFreq); - } else { - result.put(value, totalTermFreq); - } + for (String questionId : questionIds) { + if(StringUtils.isNotBlank(questionId)) { + queryBuilder.add(new TermQuery(new Term(QUESTION_ID_INDEX_PROPERTY, questionId.toLowerCase())), BooleanClause.Occur.SHOULD); } + } - } catch (Exception e) { - e.printStackTrace(); + TopDocs hits = isearcher.search(queryBuilder.build(), 100); + ScoreDoc[] scoreDocs = hits.scoreDocs; + System.out.println("hits=" + scoreDocs.length); + System.out.println("Hits (rank,score,docId)"); + for (int n = 0; n < scoreDocs.length; ++n) { + ScoreDoc sd = scoreDocs[n]; + float score = sd.score; + int docId = sd.doc; } +// TopFieldCollector topFieldCollector = TopFieldCollector.create(new Sort(), 100, true, true, false); +// isearcher.search(queryBuilder.build(), topFieldCollector); + + Map<String, Long> result = new LinkedHashMap<>(); +// TopFieldDocs topField = topFieldCollector.topDocs(); +// for (SortField sortField : topField.fields) { +// String field = sortField.getField(); +// long sumDocFreq = ireader.getSumDocFreq(field); +// +// if (result.containsKey(field)) { +// result.put(field, result.get(field) + sumDocFreq); +// } else { +// result.put(field, sumDocFreq); +// } +// } + ireader.close(); return result; } diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/TransverseIndexationService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/TransverseIndexationService.java new file mode 100644 index 0000000..ab15112 --- /dev/null +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/indexation/TransverseIndexationService.java @@ -0,0 +1,116 @@ +package fr.ifremer.coselmar.services.indexation; + +/* + * #%L + * Coselmar :: Rest Services + * $Id:$ + * $HeadURL:$ + * %% + * Copyright (C) 2014 - 2015 Ifremer, Code Lutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import fr.ifremer.coselmar.beans.QuestionBean; +import fr.ifremer.coselmar.beans.QuestionSearchBean; +import fr.ifremer.coselmar.services.CoselmarSimpleServiceSupport; +import org.apache.commons.lang3.StringUtils; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.StringField; +import org.apache.lucene.document.TextField; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.Term; +import org.apache.lucene.misc.HighFreqTerms; +import org.apache.lucene.misc.HighFreqTermsMultiFields; +import org.apache.lucene.misc.TermStats; +import org.apache.lucene.queryparser.classic.ParseException; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.WildcardQuery; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * This Services provides operations about indexed Objects. + * <ul> + * <li>cleanning of the indexation db</li> + * <li>top word from the indexation db about specific {@link QuestionBean} and {@link fr.ifremer.coselmar.beans.DocumentBean} attributes</li> + * </ul> + * + * The purpose is to use power of a indexation db (lucene) to increase search on + * document text field, and make easier fulltext search + * + * @author ymartel <martel@codelutin.com> + */ +public class TransverseIndexationService extends CoselmarSimpleServiceSupport { + + protected void cleanAllIndex() throws IOException { + BooleanQuery query = new BooleanQuery.Builder() + .add(new TermQuery(new Term("type", QuestionsIndexationService.DOCUMENT_TYPE)), BooleanClause.Occur.SHOULD) + .add(new TermQuery(new Term("type", DocumentsIndexationService.DOCUMENT_TYPE)), BooleanClause.Occur.SHOULD) + //XXX ymartel 20151215 : Clean older DOCUMENT_TYPE value too (less or equals V1.0.1), should be removed after V2.0 + .add(new TermQuery(new Term("type", "question")), BooleanClause.Occur.SHOULD) + .add(new TermQuery(new Term("type", "document")), BooleanClause.Occur.SHOULD) + .build(); + getLuceneUtils().getIndexWriter().deleteDocuments(query); + getLuceneUtils().getIndexWriter().commit(); + } + + public Map<String, Long> getTopTerms() throws IOException, ParseException { + + DirectoryReader indexReader = DirectoryReader.open(getLuceneUtils().getIndexWriter(), false); + + Map<String, Long> result = new LinkedHashMap<>(); + try { + String[] searchedFields = { + QuestionsIndexationService.QUESTION_TITLE_INDEX_PROPERTY, + QuestionsIndexationService.QUESTION_SUMMARY_INDEX_PROPERTY, + QuestionsIndexationService.QUESTION_THEME_INDEX_PROPERTY, + DocumentsIndexationService.DOCUMENT_NAME_INDEX_PROPERTY, + DocumentsIndexationService.DOCUMENT_SUMMARY_INDEX_PROPERTY, + DocumentsIndexationService.DOCUMENT_KEYWORD_INDEX_PROPERTY + }; + TermStats[] highFreqTerms = HighFreqTermsMultiFields.getHighFreqTermsMultiFields(indexReader, 20, searchedFields, new HighFreqTerms.TotalTermFreqComparator()); + for (TermStats termStats : highFreqTerms) { + long totalTermFreq = termStats.totalTermFreq; + String value = termStats.termtext.utf8ToString(); + + if (result.containsKey(value)) { + result.put(value, result.get(value) + totalTermFreq); + } else { + result.put(value, totalTermFreq); + } + } + + } catch (Exception e) { + e.printStackTrace(); + } + + indexReader.close(); + return result; + } + +} diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/GeneralWebService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/GeneralWebService.java new file mode 100644 index 0000000..cc1eaf4 --- /dev/null +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/GeneralWebService.java @@ -0,0 +1,74 @@ +package fr.ifremer.coselmar.services.v1; + +/* + * #%L + * Coselmar :: Rest Services + * $Id:$ + * $HeadURL:$ + * %% + * Copyright (C) 2014 Ifremer, Code Lutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import com.google.common.collect.Lists; +import fr.ifremer.coselmar.beans.CloudWord; +import fr.ifremer.coselmar.exceptions.CoselmarTechnicalException; +import fr.ifremer.coselmar.persistence.entity.CoselmarUserRole; +import fr.ifremer.coselmar.services.CoselmarWebServiceSupport; +import fr.ifremer.coselmar.services.indexation.TransverseIndexationService; +import fr.ifremer.coselmar.services.indexation.QuestionsIndexationService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.lucene.queryparser.classic.ParseException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author ymartel <martel@codelutin.com> + */ +public class GeneralWebService extends CoselmarWebServiceSupport { + + private static final Log log = LogFactory.getLog(GeneralWebService.class); + + protected static final List<String> RESTRICTED_ACCESS_USERS = Lists.newArrayList(CoselmarUserRole.CLIENT.name(), CoselmarUserRole.MEMBER.name()); + + public List<CloudWord> getTopWords() { + + TransverseIndexationService questionsIndexationService = getServicesContext().newService(TransverseIndexationService.class); + + Map<String, Long> topTerms = null; + try { + topTerms = questionsIndexationService.getTopTerms(); + } catch (IOException|ParseException e) { + if (log.isErrorEnabled()) { + log.error("Unable to search by lucene, make search directly in database", e); + throw new CoselmarTechnicalException("Unable to get most frequecy words"); + } + } + + List<CloudWord> cloudWords = new ArrayList<>(topTerms.size()); + for (Map.Entry<String, Long> wordFrequency : topTerms.entrySet()) { + CloudWord cloudWord = new CloudWord(wordFrequency.getKey(), wordFrequency.getValue()); + cloudWords.add(cloudWord); + } + + return cloudWords; + } +} diff --git a/coselmar-rest/src/main/resources/mapping b/coselmar-rest/src/main/resources/mapping index e31d444..2209eeb 100644 --- a/coselmar-rest/src/main/resources/mapping +++ b/coselmar-rest/src/main/resources/mapping @@ -64,6 +64,10 @@ POST /v1/questions/{questionId}/documents QuestionsWebService.addDocuments POST /v1/questions QuestionsWebService.addQuestion DELETE /v1/questions/{questionId} QuestionsWebService.deleteQuestion +# Transverse Api + +GET /v1/general/topwords GeneralWebService.getTopWords + # Admin API POST /v1/admin/lucene/index AdminWebService.refreshLuceneIndex diff --git a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java index 0f377ea..b3c6334 100644 --- a/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java +++ b/coselmar-rest/src/test/java/fr/ifremer/coselmar/services/indexation/QuestionsIndexationServiceTest.java @@ -427,4 +427,48 @@ public class QuestionsIndexationServiceTest extends AbstractCoselmarServiceTest Assert.assertEquals(4, topTerms.get("something").longValue()); } + + @Test + public void testGetTopDocumentsTerms() throws Exception { + + QuestionBean questionOne = new QuestionBean(); + String questionOneId = "question_1_test_search" + System.currentTimeMillis(); + questionOne.setId(questionOneId); + questionOne.setTitle("Awesome question"); + questionOne.setSummary("Where is the tardis in time ?"); + questionOne.setDeadline(DateUtil.createDateAfterToday(1, 0, 1)); + questionOne.setExternalExperts(Sets.newHashSet("Amelia", "Rory", "River")); + questionOne.setSubmissionDate(new Date()); + questionOne.setStatus(Status.OPEN.name()); + questionOne.setPrivacy(Privacy.PUBLIC.name()); + questionOne.setThemes(Sets.newHashSet("TARDIS", "Universe", "Time", "Space")); + + QuestionBean questionTwo = new QuestionBean(); + String questionThreeId = "question_3_test_search" + System.currentTimeMillis(); + questionTwo.setId(questionThreeId); + questionTwo.setTitle("There's someone missing. The question's Who?"); + questionTwo.setSummary("Something old, Something new, Something borrowed, Something blue."); + questionTwo.setDeadline(DateUtil.createDateAfterToday(16, 0, 0)); + questionTwo.setSubmissionDate(new Date()); + questionTwo.setStatus(Status.OPEN.name()); + questionTwo.setPrivacy(Privacy.PRIVATE.name()); + questionTwo.setThemes(Sets.newHashSet("big bang two", "Pandorica", "River", "Universe")); + + + CoselmarServicesContext serviceContext = getServiceContext(); + QuestionsIndexationService questionsIndexationService = + serviceContext.newService(QuestionsIndexationService.class); + + questionsIndexationService.indexQuestion(questionOne); + questionsIndexationService.indexQuestion(questionTwo); + + // Ok, let's search now ! + Map<String, Long> topTerms = questionsIndexationService.getTopDocumentsTerms(Arrays.asList(questionOneId)); + Assert.assertNotNull(topTerms); +// Assert.assertEquals(1, topTerms.get("question").longValue()); +// Assert.assertEquals(2, topTerms.get("tardis").longValue()); +// Assert.assertEquals(2, topTerms.get("time").longValue()); +// Assert.assertEquals(1, topTerms.get("space").longValue()); + + } } diff --git a/pom.xml b/pom.xml index f36a085..f8b4d12 100644 --- a/pom.xml +++ b/pom.xml @@ -312,6 +312,13 @@ <version>${luceneVersion}</version> </dependency> + <dependency> + <groupId>org.apache.lucene</groupId> + <artifactId>lucene-backward-codecs</artifactId> + <version>${luceneVersion}</version> + <scope>runtime</scope> + </dependency> + <!-- Commons --> <dependency> <groupId>org.apache.commons</groupId> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git commit 3f4d90b5b2416d830c3063c5a895b09f66a71aaa Author: Yannick Martel <martel@©odelutin.com> Date: Wed Dec 16 16:24:48 2015 +0100 refs-80 #7776 mise en plac du nuage de mot sur l'ui --- coselmar-ui/pom.xml | 6 + coselmar-ui/src/main/webapp/css/jqcloud-2.0.0.css | 59 +++ coselmar-ui/src/main/webapp/index.html | 8 + coselmar-ui/src/main/webapp/js/angular-jqcloud.js | 59 +++ .../src/main/webapp/js/coselmar-controllers.js | 10 +- .../main/webapp/js/coselmar-general-services.js | 43 ++ coselmar-ui/src/main/webapp/js/coselmar.js | 2 +- coselmar-ui/src/main/webapp/js/jqcloud-2.0.0.js | 528 +++++++++++++++++++++ coselmar-ui/src/main/webapp/views/home.html | 5 + pom.xml | 8 + 10 files changed, 726 insertions(+), 2 deletions(-) diff --git a/coselmar-ui/pom.xml b/coselmar-ui/pom.xml index 39622e7..10ae37c 100644 --- a/coselmar-ui/pom.xml +++ b/coselmar-ui/pom.xml @@ -93,6 +93,12 @@ <scope>runtime</scope> </dependency> + <!--<dependency>--> + <!--<groupId>org.webjars.bower</groupId>--> + <!--<artifactId>jqcloud2</artifactId>--> + <!--<scope>runtime</scope>--> + <!--</dependency>--> + </dependencies> <build> diff --git a/coselmar-ui/src/main/webapp/css/jqcloud-2.0.0.css b/coselmar-ui/src/main/webapp/css/jqcloud-2.0.0.css new file mode 100644 index 0000000..3f276ed --- /dev/null +++ b/coselmar-ui/src/main/webapp/css/jqcloud-2.0.0.css @@ -0,0 +1,59 @@ +/* + * #%L + * ! + * jQCloud + * Copyright 2011 Luca Ongaro (http://www.lucaongaro.eu) + * Copyright 2013 Daniel White (http://www.developerdan.com) + * Copyright 2014 Damien "Mistic" Sorel (http://www.strangeplanet.fr) + * Licensed under MIT (http://opensource.org/licenses/MIT) + * #L% + */ + +/* layout */ +div.jqcloud { + overflow: hidden; + position: relative; +} + +div.jqcloud span { + padding: 0; +} + +/* fonts */ +div.jqcloud { + font-family: "Helvetica", "Arial", sans-serif; + font-size: 10px; + line-height: normal; +} + +div.jqcloud a { + font-size: inherit; + text-decoration: none; +} + +div.jqcloud span.w10 { font-size: 550%; } +div.jqcloud span.w9 { font-size: 500%; } +div.jqcloud span.w8 { font-size: 450%; } +div.jqcloud span.w7 { font-size: 400%; } +div.jqcloud span.w6 { font-size: 350%; } +div.jqcloud span.w5 { font-size: 300%; } +div.jqcloud span.w4 { font-size: 250%; } +div.jqcloud span.w3 { font-size: 200%; } +div.jqcloud span.w2 { font-size: 150%; } +div.jqcloud span.w1 { font-size: 100%; } + +/* colors */ +div.jqcloud { color: #09f; } +div.jqcloud a { color: inherit; } +div.jqcloud a:hover { color: #0df; } +div.jqcloud a:hover { color: #0cf; } +div.jqcloud span.w10 { color: #0cf; } +div.jqcloud span.w9 { color: #0cf; } +div.jqcloud span.w8 { color: #0cf; } +div.jqcloud span.w7 { color: #39d; } +div.jqcloud span.w6 { color: #90c5f0; } +div.jqcloud span.w5 { color: #90a0dd; } +div.jqcloud span.w4 { color: #90c5f0; } +div.jqcloud span.w3 { color: #a0ddff; } +div.jqcloud span.w2 { color: #99ccee; } +div.jqcloud span.w1 { color: #aab5f0; } \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/index.html b/coselmar-ui/src/main/webapp/index.html index f5e6678..ce47b70 100644 --- a/coselmar-ui/src/main/webapp/index.html +++ b/coselmar-ui/src/main/webapp/index.html @@ -30,6 +30,8 @@ <link rel="stylesheet" href="webjars/angular-notify/2.5.0/angular-notify.min.css"> <link rel="stylesheet" href="webjars/bootstrap/3.3.6/css/bootstrap.css"> <link rel="stylesheet" href="webjars/font-awesome/4.5.0/css/font-awesome.css"> + <link rel="stylesheet" href="webjars/jqcloud2/2.0.1/jqcloud/jqcloud.css"> + <link rel="stylesheet" href="css/jqcloud-2.0.0.css"> <link rel="stylesheet" href="css/coselmar.css"> <script src="webjars/jquery/2.1.4/jquery.js"></script> @@ -50,14 +52,20 @@ <link rel="stylesheet" href="webjars/angular-ui-select/0.13.1/select.css"> <script src="webjars/angular-ui-bootstrap/0.14.3/ui-bootstrap.js"></script> <script src="webjars/angular-ui-bootstrap/0.14.3/ui-bootstrap-tpls.js"></script> + <script src="webjars/jqcloud2/2.0.1/jqcloud/jqcloud-2.0.1.js"></script> + <script src="js/jqcloud-2.0.0.js"></script> <script src="js/angular-jwt.js"></script> + <script src="js/angular-jqcloud.js"></script> + <script src="i18n/en.js"></script> <script src="i18n/fr.js"></script> + <script src="js/coselmar.js"></script> <script src="js/coselmar-constants.js"></script> <script src="js/coselmar-controllers.js"></script> <script src="js/coselmar-services.js"></script> + <script src="js/coselmar-general-services.js"></script> <script src="js/coselmar-user-services.js"></script> <script src="js/coselmar-questions-services.js"></script> <script src="js/coselmar-admin-services.js"></script> diff --git a/coselmar-ui/src/main/webapp/js/angular-jqcloud.js b/coselmar-ui/src/main/webapp/js/angular-jqcloud.js new file mode 100644 index 0000000..8a02bd0 --- /dev/null +++ b/coselmar-ui/src/main/webapp/js/angular-jqcloud.js @@ -0,0 +1,59 @@ +/*! + * #%L + * Angular jQCloud 1.0.2 + * For jQCloud 2 (https://github.com/mistic100/jQCloud) + * Copyright 2014 Damien "Mistic" Sorel (http://www.strangeplanet.fr) + * Licensed under MIT (http://opensource.org/licenses/MIT) + * #L% + */ + +angular.module('angular-jqcloud', []).directive('jqcloud', ['$parse', function($parse) { + // get existing options + var defaults = jQuery.fn.jQCloud.defaults.get(), + jqcOptions = []; + + for (var opt in defaults) { + if (defaults.hasOwnProperty(opt)) { + jqcOptions.push(opt); + } + } + + return { + restrict: 'E', + template: '<div></div>', + replace: true, + scope: { + words: '=words', + afterCloudRender: '&' + }, + link: function($scope, $elem, $attr) { + var options = {}; + + for (var i=0, l=jqcOptions.length; i<l; i++) { + var opt = jqcOptions[i]; + var attr = $attr[opt] || $elem.attr(opt); + if (attr !== undefined) { + options[opt] = $parse(attr)(); + } + } + + if ($scope.afterCloudRender) { + options.afterCloudRender = $scope.afterCloudRender; + } + + jQuery($elem).jQCloud($scope.words, options); + + $scope.$watchCollection('words', function() { + $scope.$evalAsync(function() { + var words = []; + $.extend(words,$scope.words); + jQuery($elem).jQCloud('update', words); + }); + }); + + $elem.bind('$destroy', function() { + jQuery($elem).jQCloud('destroy'); + }); + } + }; +}]); \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js index 0f40bf1..c8ba720 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js @@ -115,9 +115,10 @@ coselmarControllers.controller("unauthorizedCtrl", ['$scope', '$location', funct }]); // Controller for home when user connected, load 5 last projects -coselmarControllers.controller("homeConnectedCtrl", ['$scope', 'questionsService', function ($scope, questionsService) { +coselmarControllers.controller("homeConnectedCtrl", ['$scope', 'questionsService', 'generalService', function ($scope, questionsService, generalService) { $scope.closedQuestions = []; + $scope.topWords = [{text:"coselmar", weight: 10}, {text: "Ifremer", weight: 5}]; if ($scope.context && $scope.context.currentUser) { @@ -145,6 +146,13 @@ coselmarControllers.controller("homeConnectedCtrl", ['$scope', 'questionsService }); } + generalService.getTopWords(function(result) { + $scope.topWords = result.data; + }, function(fail) { + $scope.topWords = []; + }); + + }]); ///////////////////////////////////////////////// diff --git a/coselmar-ui/src/main/webapp/js/coselmar-general-services.js b/coselmar-ui/src/main/webapp/js/coselmar-general-services.js new file mode 100644 index 0000000..def2091 --- /dev/null +++ b/coselmar-ui/src/main/webapp/js/coselmar-general-services.js @@ -0,0 +1,43 @@ +/* + * #%L + * Coselmar :: UI + * $Id:$ + * $HeadURL:$ + * %% + * Copyright (C) 2014 Ifremer, Code Lutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +coselmarServices.factory('generalService', ['$http', 'coselmar-config', function($http, coselmarConfig){ + return new GeneralService($http, coselmarConfig); +}]); + +function GeneralService(http, config){ + + this.http = http; + + var baseURL = config.BASE_URL + "/general"; + + this.getTopWords = function(successFunction, failFunction) { + + var topWordsUrl = baseURL + "/topwords"; + + http.get(topWordsUrl).then(successFunction, failFunction); + + }; + +}; \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/js/coselmar.js b/coselmar-ui/src/main/webapp/js/coselmar.js index d5b7b22..8904db3 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar.js +++ b/coselmar-ui/src/main/webapp/js/coselmar.js @@ -23,7 +23,7 @@ */ var coselmarApp = angular.module("coselmarApp", ['ngRoute', 'ngResource', 'ngMessages', 'ngAnimate', 'pascalprecht.translate', 'tmh.dynamicLocale', 'cgNotify', - 'angular-jwt', 'coselmarControllers', 'coselmarServices']); + 'angular-jwt', 'angular-jqcloud', 'coselmarControllers', 'coselmarServices']); coselmarApp.config(['$routeProvider', function($routeProvider) { $routeProvider diff --git a/coselmar-ui/src/main/webapp/js/jqcloud-2.0.0.js b/coselmar-ui/src/main/webapp/js/jqcloud-2.0.0.js new file mode 100644 index 0000000..5c9cc4f --- /dev/null +++ b/coselmar-ui/src/main/webapp/js/jqcloud-2.0.0.js @@ -0,0 +1,528 @@ +/* + * #%L + * ! + * jQCloud + * Copyright 2011 Luca Ongaro (http://www.lucaongaro.eu) + * Copyright 2013 Daniel White (http://www.developerdan.com) + * Copyright 2014 Damien "Mistic" Sorel (http://www.strangeplanet.fr) + * Licensed under MIT (http://opensource.org/licenses/MIT) + * #L% + */ + +/*jshint -W055 *//* non standard constructor name */ + +(function($) { + "use strict"; + + /* + * Plugin class + */ + var jQCloud = function (element, word_array, options) { + this.$element = $(element); + + this.word_array = word_array || []; + this.options = options; + + this.sizeGenerator = null; + this.colorGenerator = null; + + // Data used internally + this.data = { + placed_words: [], + timeouts: {}, + namespace: null, + step: null, + angle: null, + aspect_ratio: null, + max_weight: null, + min_weight: null, + sizes: [], + colors: [] + }; + + this.initialize(); + }; + + jQCloud.DEFAULTS = { + width: 100, + height: 100, + center: { x: 0.5, y: 0.5 }, + steps: 10, + delay: null, + shape: 'elliptic', + classPattern: 'w{n}', + encodeURI: true, + removeOverflowing: true, + afterCloudRender: null, + autoResize: false, + colors: null, + fontSize: null, + template: null + }; + + jQCloud.prototype = { + initialize: function() { + // Set/Get dimensions + if (this.options.width) { + this.$element.width(this.options.width); + } + else { + this.options.width = this.$element.width(); + } + if (this.options.height) { + this.$element.height(this.options.height); + } + else { + this.options.height = this.$element.height(); + } + + // Default options value + this.options = $.extend(true, {}, jQCloud.DEFAULTS, this.options); + + // Ensure delay + if (this.options.delay === null) { + this.options.delay = this.word_array.length > 50 ? 10 : 0; + } + + // Backward compatibility + if (this.options.center.x > 1) { + this.options.center.x = this.options.center.x / this.options.width; + this.options.center.y = this.options.center.y / this.options.height; + } + + // Create colorGenerator function from options + // Direct function + if (typeof this.options.colors == 'function') { + this.colorGenerator = this.options.colors; + } + // Array of sizes + else if ($.isArray(this.options.colors)) { + var cl = this.options.colors.length; + if (cl > 0) { + // Fill the sizes array to X items + if (cl < this.options.steps) { + for (var i=cl; i<this.options.steps; i++) { + this.options.colors[i] = this.options.colors[cl-1]; + } + } + + this.colorGenerator = function(weight) { + return this.options.colors[this.options.steps - weight]; + }; + } + } + + // Create sizeGenerator function from options + // Direct function + if (typeof this.options.fontSize == 'function') { + this.sizeGenerator = this.options.fontSize; + } + // Object with 'from' and 'to' + else if ($.isPlainObject(this.options.fontSize)) { + this.sizeGenerator = function(width, height, weight) { + var max = width * this.options.fontSize.from, + min = width * this.options.fontSize.to; + return Math.round(min + (max - min) * 1.0 / (this.options.steps-1) * (weight - 1)) + 'px'; + }; + } + // Array of sizes + else if ($.isArray(this.options.fontSize)) { + var sl = this.options.fontSize.length; + if (sl > 0) { + // Fill the sizes array to X items + if (sl < this.options.steps) { + for (var j=sl; j<this.options.steps; j++) { + this.options.fontSize[j] = this.options.fontSize[sl-1]; + } + } + + this.sizeGenerator = function(width, height, weight) { + return this.options.fontSize[this.options.steps - weight]; + }; + } + } + + this.data.angle = Math.random() * 6.28; + this.data.step = (this.options.shape === 'rectangular') ? 18.0 : 2.0; + this.data.aspect_ratio = this.options.width / this.options.height; + this.clearTimeouts(); + + // Namespace word ids to avoid collisions between multiple clouds + this.data.namespace = (this.$element.attr('id') || Math.floor((Math.random()*1000000)).toString(36)) + '_word_'; + + this.$element.addClass('jqcloud'); + + // Container's CSS position cannot be 'static' + if (this.$element.css('position') === 'static') { + this.$element.css('position', 'relative'); + } + + // Delay execution so that the browser can render the page before the computatively intensive word cloud drawing + this.createTimeout($.proxy(this.drawWordCloud, this), 10); + + // Attach window resize event + if (this.options.autoResize) { + $(window).on('resize', throttle(function() { + var new_size = { + width: this.$element.width(), + height: this.$element.height() + }; + + if (new_size.width != this.options.width || new_size.height != this.options.height) { + this.options.width = new_size.width; + this.options.height = new_size.height; + this.data.aspect_ratio = this.options.width / this.options.height; + + this.update(this.word_array); + } + }, 50, this)); + } + }, + + // Helper function to keep track of timeouts so they can be destroyed + createTimeout: function(callback, time) { + var timeout = setTimeout($.proxy(function(){ + delete this.data.timeouts[timeout]; + callback(); + }, this), time); + this.data.timeouts[timeout] = true; + }, + + // Destroy all timeouts + clearTimeouts: function() { + $.each(this.data.timeouts, function(key){ + clearTimeout(key); + }); + this.data.timeouts = {}; + }, + + // Pairwise overlap detection + overlapping: function(a, b) { + if (Math.abs(2.0*a.left + a.width - 2.0*b.left - b.width) < a.width + b.width) { + if (Math.abs(2.0*a.top + a.height - 2.0*b.top - b.height) < a.height + b.height) { + return true; + } + } + return false; + }, + + // Helper function to test if an element overlaps others + hitTest: function(elem) { + // Check elements for overlap one by one, stop and return false as soon as an overlap is found + for(var i=0, l=this.data.placed_words.length; i<l; i++) { + if (this.overlapping(elem, this.data.placed_words[i])) { + return true; + } + } + return false; + }, + + // Initialize the drawing of the whole cloud + drawWordCloud: function() { + var i, l; + + this.$element.children('[id^="' + this.data.namespace + '"]').remove(); + + if (this.word_array.length === 0) { + return; + } + + // Make sure every weight is a number before sorting + for (i=0, l=this.word_array.length; i<l; i++) { + this.word_array[i].weight = parseFloat(this.word_array[i].weight, 10); + } + + // Sort word_array from the word with the highest weight to the one with the lowest + this.word_array.sort(function(a, b) { + return b.weight - a.weight; + }); + + // Kepp trace of bounds + this.data.max_weight = this.word_array[0].weight; + this.data.min_weight = this.word_array[this.word_array.length - 1].weight; + + // Generate colors + this.data.colors = []; + if (this.colorGenerator) { + for (i=0; i<this.options.steps; i++) { + this.data.colors.push(this.colorGenerator(i+1)); + } + } + + // Generate font sizes + this.data.sizes = []; + if (this.sizeGenerator) { + for (i=0; i<this.options.steps; i++) { + this.data.sizes.push(this.sizeGenerator(this.options.width, this.options.height, i+1)); + } + } + + // Iterate drawOneWord on every word, immediately or with delay + if (this.options.delay > 0){ + this.drawOneWordDelayed(); + } + else { + for (i=0, l=this.word_array.length; i<l; i++) { + this.drawOneWord(i, this.word_array[i]); + } + + if (typeof this.options.afterCloudRender === 'function') { + this.options.afterCloudRender.call(this.$element); + } + } + }, + + // Function to draw a word, by moving it in spiral until it finds a suitable empty place + drawOneWord: function(index, word) { + var word_id = this.data.namespace + index, + word_selector = '#' + word_id, + + // option.shape == 'elliptic' + angle = this.data.angle, + radius = 0.0, + + // option.shape == 'rectangular' + steps_in_direction = 0.0, + quarter_turns = 0.0, + + weight = Math.floor(this.options.steps / 2), + word_span, + word_size, + word_style; + + // Create word attr object + word.attr = $.extend({}, word.html, { id: word_id }); + + // Linearly map the original weight to a discrete scale from 1 to 10 + // Only if weights are different + if (this.data.max_weight != this.data.min_weight) { + weight = Math.round((word.weight - this.data.min_weight) * 1.0 * (this.options.steps-1) / (this.data.max_weight - this.data.min_weight)) + 1; + } + word_span = $('<span>').attr(word.attr); + + // Apply class + if (this.options.classPattern) { + word_span.addClass(this.options.classPattern.replace('{n}', weight)); + } + + // Apply color + if (this.data.colors.length) { + word_span.css('color', this.data.colors[weight-1]); + } + + // Apply size + if (this.data.sizes.length) { + word_span.css('font-size', this.data.sizes[weight-1]); + } + + //Render using template function if provided. + if (this.options.template) { + word_span.html(this.options.template(word)); + } else if (word.link) { + // Append link if word.link attribute was set + // If link is a string, then use it as the link href + if (typeof word.link === 'string') { + word.link = { href: word.link }; + } + + if (this.options.encodeURI) { + word.link.href = encodeURI(word.link.href).replace(/'/g, '%27'); + } + + word_span.append($('<a>').attr(word.link).text(word.text)); + } + else { + word_span.text(word.text); + } + + // Bind handlers to words + if (word.handlers) { + word_span.on(word.handlers); + } + + this.$element.append(word_span); + + word_size = { + width: word_span.outerWidth(), + height: word_span.outerHeight() + }; + word_size.left = this.options.center.x*this.options.width - word_size.width / 2.0; + word_size.top = this.options.center.y*this.options.height - word_size.height / 2.0; + + // Save a reference to the style property, for better performance + word_style = word_span[0].style; + word_style.position = 'absolute'; + word_style.left = word_size.left + 'px'; + word_style.top = word_size.top + 'px'; + + while(this.hitTest(word_size)) { + // option shape is 'rectangular' so move the word in a rectangular spiral + if (this.options.shape === 'rectangular') { + steps_in_direction++; + + if (steps_in_direction * this.data.step > (1 + Math.floor(quarter_turns / 2.0)) * this.data.step * ((quarter_turns % 4 % 2) === 0 ? 1 : this.data.aspect_ratio)) { + steps_in_direction = 0.0; + quarter_turns++; + } + + switch(quarter_turns % 4) { + case 1: + word_size.left += this.data.step * this.data.aspect_ratio + Math.random() * 2.0; + break; + case 2: + word_size.top -= this.data.step + Math.random() * 2.0; + break; + case 3: + word_size.left -= this.data.step * this.data.aspect_ratio + Math.random() * 2.0; + break; + case 0: + word_size.top += this.data.step + Math.random() * 2.0; + break; + } + } + // Default settings: elliptic spiral shape + else { + radius += this.data.step; + angle += (index % 2 === 0 ? 1 : -1) * this.data.step; + + word_size.left = this.options.center.x*this.options.width - (word_size.width / 2.0) + (radius*Math.cos(angle)) * this.data.aspect_ratio; + word_size.top = this.options.center.y*this.options.height + radius*Math.sin(angle) - (word_size.height / 2.0); + } + word_style.left = word_size.left + 'px'; + word_style.top = word_size.top + 'px'; + } + + // Don't render word if part of it would be outside the container + if (this.options.removeOverflowing && ( + word_size.left < 0 || word_size.top < 0 || + (word_size.left + word_size.width) > this.options.width || + (word_size.top + word_size.height) > this.options.height + ) + ) { + word_span.remove(); + return; + } + + // Save position for further usage + this.data.placed_words.push(word_size); + + if (typeof word.afterWordRender === 'function') { + word.afterWordRender.call(word_span); + } + }, + + // Draw one word then recall the function after a delay + drawOneWordDelayed: function(index) { + index = index || 0; + + // if not visible then do not attempt to draw + if (!this.$element.is(':visible')) { + this.createTimeout($.proxy(function(){ + this.drawOneWordDelayed(index); + }, this), 10); + + return; + } + + if (index < this.word_array.length) { + this.drawOneWord(index, this.word_array[index]); + + this.createTimeout($.proxy(function(){ + this.drawOneWordDelayed(index + 1); + }, this), this.options.delay); + } + else { + if (typeof this.options.afterCloudRender == 'function') { + this.options.afterCloudRender.call(this.$element); + } + } + }, + + // Destroy any data and objects added by the plugin + destroy: function() { + this.clearTimeouts(); + this.$element.removeClass('jqcloud'); + this.$element.removeData('jqcloud'); + this.$element.children('[id^="' + this.data.namespace + '"]').remove(); + }, + + // Update the list of words + update: function(word_array) { + this.word_array = word_array; + this.data.placed_words = []; + + this.clearTimeouts(); + this.drawWordCloud(); + } + }; + + /* + * Apply throttling to a callback + * @param callback {function} + * @param delay {int} milliseconds + * @param context {object|null} + * @return {function} + */ + function throttle(callback, delay, context) { + var state = { + pid: null, + last: 0 + }; + + return function() { + var elapsed = new Date().getTime() - state.last, + args = arguments, + that = this; + + function exec() { + state.last = new Date().getTime(); + return callback.apply(context || that, Array.prototype.slice.call(args)); + } + + if (elapsed > delay) { + return exec(); + } + else { + clearTimeout(state.pid); + state.pid = setTimeout(exec, delay - elapsed); + } + }; + } + + /* + * jQuery plugin + */ + $.fn.jQCloud = function(word_array, option) { + var args = arguments; + + return this.each(function () { + var $this = $(this), + data = $this.data('jqcloud'); + + if (!data && word_array === 'destroy') { + // Don't even try to initialize when called with 'destroy' + return; + } + if (!data) { + var options = typeof option === 'object' ? option : {}; + $this.data('jqcloud', (data = new jQCloud(this, word_array, options))); + } + else if (typeof word_array === 'string') { + data[word_array].apply(data, Array.prototype.slice.call(args, 1)); + } + }); + }; + + $.fn.jQCloud.defaults = { + set: function(options) { + $.extend(true, jQCloud.DEFAULTS, options); + }, + get: function(key) { + var options = jQCloud.DEFAULTS; + if (key) { + options = options[key]; + } + return $.extend(true, {}, options); + } + }; +})(jQuery); \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/views/home.html b/coselmar-ui/src/main/webapp/views/home.html index f11dc65..01e1c61 100644 --- a/coselmar-ui/src/main/webapp/views/home.html +++ b/coselmar-ui/src/main/webapp/views/home.html @@ -110,4 +110,9 @@ </table> <p ng-if="questions && questions.length == 0" translate="common.search.noResult" class="info"/> </div> + + <!-- FIXME Leo #CSS --> + <div align="center"> + <jqcloud words="topWords" width="500" height="350" steps="21" font-size="{from:0.09, to:0.04}"></jqcloud> + </div> </div> diff --git a/pom.xml b/pom.xml index f8b4d12..f68d67a 100644 --- a/pom.xml +++ b/pom.xml @@ -166,6 +166,7 @@ <!-- XXX ymartel 20151202 : cannot upgrade httpcomponents deps for the moment, still needed for webmotion test --> <fluent-hc.version>4.2.3</fluent-hc.version> <httpcore.version>4.2.3</httpcore.version> + <jqcloud2Version>2.0.1</jqcloud2Version> </properties> <repositories> @@ -475,6 +476,13 @@ <scope>runtime</scope> </dependency> + <dependency> + <groupId>org.webjars.bower</groupId> + <artifactId>jqcloud2</artifactId> + <version>${jqcloud2Version}</version> + <scope>runtime</scope> + </dependency> + <!-- Others --> <dependency> <groupId>com.github.spullara.mustache.java</groupId> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git commit dd7d23acfc062cc38b450f103d1feeeaab361eec Author: Yannick Martel <martel@©odelutin.com> Date: Thu Dec 17 10:45:13 2015 +0100 fixes #7776 Utilisation de webjars pour jqcloud et angular-jqcloud --- coselmar-ui/pom.xml | 16 +- coselmar-ui/src/main/webapp/index.html | 8 +- coselmar-ui/src/main/webapp/js/angular-jqcloud.js | 59 --- .../src/main/webapp/js/coselmar-controllers.js | 3 + coselmar-ui/src/main/webapp/js/jqcloud-2.0.0.js | 528 --------------------- coselmar-ui/src/main/webapp/views/home.html | 4 +- pom.xml | 8 + 7 files changed, 28 insertions(+), 598 deletions(-) diff --git a/coselmar-ui/pom.xml b/coselmar-ui/pom.xml index 10ae37c..b607e1b 100644 --- a/coselmar-ui/pom.xml +++ b/coselmar-ui/pom.xml @@ -93,11 +93,17 @@ <scope>runtime</scope> </dependency> - <!--<dependency>--> - <!--<groupId>org.webjars.bower</groupId>--> - <!--<artifactId>jqcloud2</artifactId>--> - <!--<scope>runtime</scope>--> - <!--</dependency>--> + <dependency> + <groupId>org.webjars.bower</groupId> + <artifactId>jqcloud2</artifactId> + <scope>runtime</scope> + </dependency> + + <dependency> + <groupId>org.webjars.bower</groupId> + <artifactId>angular-jqcloud</artifactId> + <scope>runtime</scope> + </dependency> </dependencies> diff --git a/coselmar-ui/src/main/webapp/index.html b/coselmar-ui/src/main/webapp/index.html index ce47b70..d4b6fd7 100644 --- a/coselmar-ui/src/main/webapp/index.html +++ b/coselmar-ui/src/main/webapp/index.html @@ -30,8 +30,7 @@ <link rel="stylesheet" href="webjars/angular-notify/2.5.0/angular-notify.min.css"> <link rel="stylesheet" href="webjars/bootstrap/3.3.6/css/bootstrap.css"> <link rel="stylesheet" href="webjars/font-awesome/4.5.0/css/font-awesome.css"> - <link rel="stylesheet" href="webjars/jqcloud2/2.0.1/jqcloud/jqcloud.css"> - <link rel="stylesheet" href="css/jqcloud-2.0.0.css"> + <link rel="stylesheet" href="webjars/jqcloud2/2.0.1/dist/jqcloud.css"> <link rel="stylesheet" href="css/coselmar.css"> <script src="webjars/jquery/2.1.4/jquery.js"></script> @@ -52,11 +51,10 @@ <link rel="stylesheet" href="webjars/angular-ui-select/0.13.1/select.css"> <script src="webjars/angular-ui-bootstrap/0.14.3/ui-bootstrap.js"></script> <script src="webjars/angular-ui-bootstrap/0.14.3/ui-bootstrap-tpls.js"></script> - <script src="webjars/jqcloud2/2.0.1/jqcloud/jqcloud-2.0.1.js"></script> - <script src="js/jqcloud-2.0.0.js"></script> + <script src="webjars/jqcloud2/2.0.1/dist/jqcloud.js"></script> + <script src="webjars/angular-jqcloud/1.0.2/angular-jqcloud.js"></script> <script src="js/angular-jwt.js"></script> - <script src="js/angular-jqcloud.js"></script> <script src="i18n/en.js"></script> <script src="i18n/fr.js"></script> diff --git a/coselmar-ui/src/main/webapp/js/angular-jqcloud.js b/coselmar-ui/src/main/webapp/js/angular-jqcloud.js deleted file mode 100644 index 8a02bd0..0000000 --- a/coselmar-ui/src/main/webapp/js/angular-jqcloud.js +++ /dev/null @@ -1,59 +0,0 @@ -/*! - * #%L - * Angular jQCloud 1.0.2 - * For jQCloud 2 (https://github.com/mistic100/jQCloud) - * Copyright 2014 Damien "Mistic" Sorel (http://www.strangeplanet.fr) - * Licensed under MIT (http://opensource.org/licenses/MIT) - * #L% - */ - -angular.module('angular-jqcloud', []).directive('jqcloud', ['$parse', function($parse) { - // get existing options - var defaults = jQuery.fn.jQCloud.defaults.get(), - jqcOptions = []; - - for (var opt in defaults) { - if (defaults.hasOwnProperty(opt)) { - jqcOptions.push(opt); - } - } - - return { - restrict: 'E', - template: '<div></div>', - replace: true, - scope: { - words: '=words', - afterCloudRender: '&' - }, - link: function($scope, $elem, $attr) { - var options = {}; - - for (var i=0, l=jqcOptions.length; i<l; i++) { - var opt = jqcOptions[i]; - var attr = $attr[opt] || $elem.attr(opt); - if (attr !== undefined) { - options[opt] = $parse(attr)(); - } - } - - if ($scope.afterCloudRender) { - options.afterCloudRender = $scope.afterCloudRender; - } - - jQuery($elem).jQCloud($scope.words, options); - - $scope.$watchCollection('words', function() { - $scope.$evalAsync(function() { - var words = []; - $.extend(words,$scope.words); - jQuery($elem).jQCloud('update', words); - }); - }); - - $elem.bind('$destroy', function() { - jQuery($elem).jQCloud('destroy'); - }); - } - }; -}]); \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js index c8ba720..29bdb3c 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js @@ -119,6 +119,9 @@ coselmarControllers.controller("homeConnectedCtrl", ['$scope', 'questionsService $scope.closedQuestions = []; $scope.topWords = [{text:"coselmar", weight: 10}, {text: "Ifremer", weight: 5}]; + //FIXME Leo #CSS si tu veux trouver de belles couleurs pour le nuage, c'est là :) + $scope.cloudColors = []; +// $scope.cloudColors = ["#800026", "#bd0026", "#e31a1c", "#fc4e2a", "#fd8d3c", "#feb24c", "#fed976"]; if ($scope.context && $scope.context.currentUser) { diff --git a/coselmar-ui/src/main/webapp/js/jqcloud-2.0.0.js b/coselmar-ui/src/main/webapp/js/jqcloud-2.0.0.js deleted file mode 100644 index 5c9cc4f..0000000 --- a/coselmar-ui/src/main/webapp/js/jqcloud-2.0.0.js +++ /dev/null @@ -1,528 +0,0 @@ -/* - * #%L - * ! - * jQCloud - * Copyright 2011 Luca Ongaro (http://www.lucaongaro.eu) - * Copyright 2013 Daniel White (http://www.developerdan.com) - * Copyright 2014 Damien "Mistic" Sorel (http://www.strangeplanet.fr) - * Licensed under MIT (http://opensource.org/licenses/MIT) - * #L% - */ - -/*jshint -W055 *//* non standard constructor name */ - -(function($) { - "use strict"; - - /* - * Plugin class - */ - var jQCloud = function (element, word_array, options) { - this.$element = $(element); - - this.word_array = word_array || []; - this.options = options; - - this.sizeGenerator = null; - this.colorGenerator = null; - - // Data used internally - this.data = { - placed_words: [], - timeouts: {}, - namespace: null, - step: null, - angle: null, - aspect_ratio: null, - max_weight: null, - min_weight: null, - sizes: [], - colors: [] - }; - - this.initialize(); - }; - - jQCloud.DEFAULTS = { - width: 100, - height: 100, - center: { x: 0.5, y: 0.5 }, - steps: 10, - delay: null, - shape: 'elliptic', - classPattern: 'w{n}', - encodeURI: true, - removeOverflowing: true, - afterCloudRender: null, - autoResize: false, - colors: null, - fontSize: null, - template: null - }; - - jQCloud.prototype = { - initialize: function() { - // Set/Get dimensions - if (this.options.width) { - this.$element.width(this.options.width); - } - else { - this.options.width = this.$element.width(); - } - if (this.options.height) { - this.$element.height(this.options.height); - } - else { - this.options.height = this.$element.height(); - } - - // Default options value - this.options = $.extend(true, {}, jQCloud.DEFAULTS, this.options); - - // Ensure delay - if (this.options.delay === null) { - this.options.delay = this.word_array.length > 50 ? 10 : 0; - } - - // Backward compatibility - if (this.options.center.x > 1) { - this.options.center.x = this.options.center.x / this.options.width; - this.options.center.y = this.options.center.y / this.options.height; - } - - // Create colorGenerator function from options - // Direct function - if (typeof this.options.colors == 'function') { - this.colorGenerator = this.options.colors; - } - // Array of sizes - else if ($.isArray(this.options.colors)) { - var cl = this.options.colors.length; - if (cl > 0) { - // Fill the sizes array to X items - if (cl < this.options.steps) { - for (var i=cl; i<this.options.steps; i++) { - this.options.colors[i] = this.options.colors[cl-1]; - } - } - - this.colorGenerator = function(weight) { - return this.options.colors[this.options.steps - weight]; - }; - } - } - - // Create sizeGenerator function from options - // Direct function - if (typeof this.options.fontSize == 'function') { - this.sizeGenerator = this.options.fontSize; - } - // Object with 'from' and 'to' - else if ($.isPlainObject(this.options.fontSize)) { - this.sizeGenerator = function(width, height, weight) { - var max = width * this.options.fontSize.from, - min = width * this.options.fontSize.to; - return Math.round(min + (max - min) * 1.0 / (this.options.steps-1) * (weight - 1)) + 'px'; - }; - } - // Array of sizes - else if ($.isArray(this.options.fontSize)) { - var sl = this.options.fontSize.length; - if (sl > 0) { - // Fill the sizes array to X items - if (sl < this.options.steps) { - for (var j=sl; j<this.options.steps; j++) { - this.options.fontSize[j] = this.options.fontSize[sl-1]; - } - } - - this.sizeGenerator = function(width, height, weight) { - return this.options.fontSize[this.options.steps - weight]; - }; - } - } - - this.data.angle = Math.random() * 6.28; - this.data.step = (this.options.shape === 'rectangular') ? 18.0 : 2.0; - this.data.aspect_ratio = this.options.width / this.options.height; - this.clearTimeouts(); - - // Namespace word ids to avoid collisions between multiple clouds - this.data.namespace = (this.$element.attr('id') || Math.floor((Math.random()*1000000)).toString(36)) + '_word_'; - - this.$element.addClass('jqcloud'); - - // Container's CSS position cannot be 'static' - if (this.$element.css('position') === 'static') { - this.$element.css('position', 'relative'); - } - - // Delay execution so that the browser can render the page before the computatively intensive word cloud drawing - this.createTimeout($.proxy(this.drawWordCloud, this), 10); - - // Attach window resize event - if (this.options.autoResize) { - $(window).on('resize', throttle(function() { - var new_size = { - width: this.$element.width(), - height: this.$element.height() - }; - - if (new_size.width != this.options.width || new_size.height != this.options.height) { - this.options.width = new_size.width; - this.options.height = new_size.height; - this.data.aspect_ratio = this.options.width / this.options.height; - - this.update(this.word_array); - } - }, 50, this)); - } - }, - - // Helper function to keep track of timeouts so they can be destroyed - createTimeout: function(callback, time) { - var timeout = setTimeout($.proxy(function(){ - delete this.data.timeouts[timeout]; - callback(); - }, this), time); - this.data.timeouts[timeout] = true; - }, - - // Destroy all timeouts - clearTimeouts: function() { - $.each(this.data.timeouts, function(key){ - clearTimeout(key); - }); - this.data.timeouts = {}; - }, - - // Pairwise overlap detection - overlapping: function(a, b) { - if (Math.abs(2.0*a.left + a.width - 2.0*b.left - b.width) < a.width + b.width) { - if (Math.abs(2.0*a.top + a.height - 2.0*b.top - b.height) < a.height + b.height) { - return true; - } - } - return false; - }, - - // Helper function to test if an element overlaps others - hitTest: function(elem) { - // Check elements for overlap one by one, stop and return false as soon as an overlap is found - for(var i=0, l=this.data.placed_words.length; i<l; i++) { - if (this.overlapping(elem, this.data.placed_words[i])) { - return true; - } - } - return false; - }, - - // Initialize the drawing of the whole cloud - drawWordCloud: function() { - var i, l; - - this.$element.children('[id^="' + this.data.namespace + '"]').remove(); - - if (this.word_array.length === 0) { - return; - } - - // Make sure every weight is a number before sorting - for (i=0, l=this.word_array.length; i<l; i++) { - this.word_array[i].weight = parseFloat(this.word_array[i].weight, 10); - } - - // Sort word_array from the word with the highest weight to the one with the lowest - this.word_array.sort(function(a, b) { - return b.weight - a.weight; - }); - - // Kepp trace of bounds - this.data.max_weight = this.word_array[0].weight; - this.data.min_weight = this.word_array[this.word_array.length - 1].weight; - - // Generate colors - this.data.colors = []; - if (this.colorGenerator) { - for (i=0; i<this.options.steps; i++) { - this.data.colors.push(this.colorGenerator(i+1)); - } - } - - // Generate font sizes - this.data.sizes = []; - if (this.sizeGenerator) { - for (i=0; i<this.options.steps; i++) { - this.data.sizes.push(this.sizeGenerator(this.options.width, this.options.height, i+1)); - } - } - - // Iterate drawOneWord on every word, immediately or with delay - if (this.options.delay > 0){ - this.drawOneWordDelayed(); - } - else { - for (i=0, l=this.word_array.length; i<l; i++) { - this.drawOneWord(i, this.word_array[i]); - } - - if (typeof this.options.afterCloudRender === 'function') { - this.options.afterCloudRender.call(this.$element); - } - } - }, - - // Function to draw a word, by moving it in spiral until it finds a suitable empty place - drawOneWord: function(index, word) { - var word_id = this.data.namespace + index, - word_selector = '#' + word_id, - - // option.shape == 'elliptic' - angle = this.data.angle, - radius = 0.0, - - // option.shape == 'rectangular' - steps_in_direction = 0.0, - quarter_turns = 0.0, - - weight = Math.floor(this.options.steps / 2), - word_span, - word_size, - word_style; - - // Create word attr object - word.attr = $.extend({}, word.html, { id: word_id }); - - // Linearly map the original weight to a discrete scale from 1 to 10 - // Only if weights are different - if (this.data.max_weight != this.data.min_weight) { - weight = Math.round((word.weight - this.data.min_weight) * 1.0 * (this.options.steps-1) / (this.data.max_weight - this.data.min_weight)) + 1; - } - word_span = $('<span>').attr(word.attr); - - // Apply class - if (this.options.classPattern) { - word_span.addClass(this.options.classPattern.replace('{n}', weight)); - } - - // Apply color - if (this.data.colors.length) { - word_span.css('color', this.data.colors[weight-1]); - } - - // Apply size - if (this.data.sizes.length) { - word_span.css('font-size', this.data.sizes[weight-1]); - } - - //Render using template function if provided. - if (this.options.template) { - word_span.html(this.options.template(word)); - } else if (word.link) { - // Append link if word.link attribute was set - // If link is a string, then use it as the link href - if (typeof word.link === 'string') { - word.link = { href: word.link }; - } - - if (this.options.encodeURI) { - word.link.href = encodeURI(word.link.href).replace(/'/g, '%27'); - } - - word_span.append($('<a>').attr(word.link).text(word.text)); - } - else { - word_span.text(word.text); - } - - // Bind handlers to words - if (word.handlers) { - word_span.on(word.handlers); - } - - this.$element.append(word_span); - - word_size = { - width: word_span.outerWidth(), - height: word_span.outerHeight() - }; - word_size.left = this.options.center.x*this.options.width - word_size.width / 2.0; - word_size.top = this.options.center.y*this.options.height - word_size.height / 2.0; - - // Save a reference to the style property, for better performance - word_style = word_span[0].style; - word_style.position = 'absolute'; - word_style.left = word_size.left + 'px'; - word_style.top = word_size.top + 'px'; - - while(this.hitTest(word_size)) { - // option shape is 'rectangular' so move the word in a rectangular spiral - if (this.options.shape === 'rectangular') { - steps_in_direction++; - - if (steps_in_direction * this.data.step > (1 + Math.floor(quarter_turns / 2.0)) * this.data.step * ((quarter_turns % 4 % 2) === 0 ? 1 : this.data.aspect_ratio)) { - steps_in_direction = 0.0; - quarter_turns++; - } - - switch(quarter_turns % 4) { - case 1: - word_size.left += this.data.step * this.data.aspect_ratio + Math.random() * 2.0; - break; - case 2: - word_size.top -= this.data.step + Math.random() * 2.0; - break; - case 3: - word_size.left -= this.data.step * this.data.aspect_ratio + Math.random() * 2.0; - break; - case 0: - word_size.top += this.data.step + Math.random() * 2.0; - break; - } - } - // Default settings: elliptic spiral shape - else { - radius += this.data.step; - angle += (index % 2 === 0 ? 1 : -1) * this.data.step; - - word_size.left = this.options.center.x*this.options.width - (word_size.width / 2.0) + (radius*Math.cos(angle)) * this.data.aspect_ratio; - word_size.top = this.options.center.y*this.options.height + radius*Math.sin(angle) - (word_size.height / 2.0); - } - word_style.left = word_size.left + 'px'; - word_style.top = word_size.top + 'px'; - } - - // Don't render word if part of it would be outside the container - if (this.options.removeOverflowing && ( - word_size.left < 0 || word_size.top < 0 || - (word_size.left + word_size.width) > this.options.width || - (word_size.top + word_size.height) > this.options.height - ) - ) { - word_span.remove(); - return; - } - - // Save position for further usage - this.data.placed_words.push(word_size); - - if (typeof word.afterWordRender === 'function') { - word.afterWordRender.call(word_span); - } - }, - - // Draw one word then recall the function after a delay - drawOneWordDelayed: function(index) { - index = index || 0; - - // if not visible then do not attempt to draw - if (!this.$element.is(':visible')) { - this.createTimeout($.proxy(function(){ - this.drawOneWordDelayed(index); - }, this), 10); - - return; - } - - if (index < this.word_array.length) { - this.drawOneWord(index, this.word_array[index]); - - this.createTimeout($.proxy(function(){ - this.drawOneWordDelayed(index + 1); - }, this), this.options.delay); - } - else { - if (typeof this.options.afterCloudRender == 'function') { - this.options.afterCloudRender.call(this.$element); - } - } - }, - - // Destroy any data and objects added by the plugin - destroy: function() { - this.clearTimeouts(); - this.$element.removeClass('jqcloud'); - this.$element.removeData('jqcloud'); - this.$element.children('[id^="' + this.data.namespace + '"]').remove(); - }, - - // Update the list of words - update: function(word_array) { - this.word_array = word_array; - this.data.placed_words = []; - - this.clearTimeouts(); - this.drawWordCloud(); - } - }; - - /* - * Apply throttling to a callback - * @param callback {function} - * @param delay {int} milliseconds - * @param context {object|null} - * @return {function} - */ - function throttle(callback, delay, context) { - var state = { - pid: null, - last: 0 - }; - - return function() { - var elapsed = new Date().getTime() - state.last, - args = arguments, - that = this; - - function exec() { - state.last = new Date().getTime(); - return callback.apply(context || that, Array.prototype.slice.call(args)); - } - - if (elapsed > delay) { - return exec(); - } - else { - clearTimeout(state.pid); - state.pid = setTimeout(exec, delay - elapsed); - } - }; - } - - /* - * jQuery plugin - */ - $.fn.jQCloud = function(word_array, option) { - var args = arguments; - - return this.each(function () { - var $this = $(this), - data = $this.data('jqcloud'); - - if (!data && word_array === 'destroy') { - // Don't even try to initialize when called with 'destroy' - return; - } - if (!data) { - var options = typeof option === 'object' ? option : {}; - $this.data('jqcloud', (data = new jQCloud(this, word_array, options))); - } - else if (typeof word_array === 'string') { - data[word_array].apply(data, Array.prototype.slice.call(args, 1)); - } - }); - }; - - $.fn.jQCloud.defaults = { - set: function(options) { - $.extend(true, jQCloud.DEFAULTS, options); - }, - get: function(key) { - var options = jQCloud.DEFAULTS; - if (key) { - options = options[key]; - } - return $.extend(true, {}, options); - } - }; -})(jQuery); \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/views/home.html b/coselmar-ui/src/main/webapp/views/home.html index 01e1c61..5f8d2eb 100644 --- a/coselmar-ui/src/main/webapp/views/home.html +++ b/coselmar-ui/src/main/webapp/views/home.html @@ -113,6 +113,8 @@ <!-- FIXME Leo #CSS --> <div align="center"> - <jqcloud words="topWords" width="500" height="350" steps="21" font-size="{from:0.09, to:0.04}"></jqcloud> + <jqcloud words="topWords" width="500" height="350" + colors="{{cloudColors}}" + steps="21" font-size="{from:0.09, to:0.04}"></jqcloud> </div> </div> diff --git a/pom.xml b/pom.xml index f68d67a..7ca7714 100644 --- a/pom.xml +++ b/pom.xml @@ -167,6 +167,7 @@ <fluent-hc.version>4.2.3</fluent-hc.version> <httpcore.version>4.2.3</httpcore.version> <jqcloud2Version>2.0.1</jqcloud2Version> + <angularJqcloudVersion>1.0.2</angularJqcloudVersion> </properties> <repositories> @@ -483,6 +484,13 @@ <scope>runtime</scope> </dependency> + <dependency> + <groupId>org.webjars.bower</groupId> + <artifactId>angular-jqcloud</artifactId> + <version>${angularJqcloudVersion}</version> + <scope>runtime</scope> + </dependency> + <!-- Others --> <dependency> <groupId>com.github.spullara.mustache.java</groupId> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git commit 5f143e7d8dd719cb7f900698849fe9fb2e4b6cf1 Merge: baed38f dd7d23a Author: Yannick Martel <martel@©odelutin.com> Date: Thu Dec 17 10:45:34 2015 +0100 Merge branch 'feature/R7776-cloudTags-in-homepage' into develop coselmar-rest/pom.xml | 8 ++ .../java/fr/ifremer/coselmar/beans/CloudWord.java | 33 +++++ .../indexation/DocumentsIndexationService.java | 9 +- .../coselmar/services/indexation/LuceneUtils.java | 5 +- .../indexation/QuestionsIndexationService.java | 154 ++++++++++++++++----- .../indexation/TransverseIndexationService.java | 116 ++++++++++++++++ .../coselmar/services/v1/GeneralWebService.java | 74 ++++++++++ .../lucene/misc/HighFreqTermsMultiFields.java | 75 ++++++++++ coselmar-rest/src/main/resources/mapping | 4 + .../indexation/QuestionsIndexationServiceTest.java | 102 ++++++++++++++ coselmar-ui/pom.xml | 12 ++ coselmar-ui/src/main/webapp/css/jqcloud-2.0.0.css | 59 ++++++++ coselmar-ui/src/main/webapp/index.html | 6 + .../src/main/webapp/js/coselmar-controllers.js | 13 +- .../main/webapp/js/coselmar-general-services.js | 43 ++++++ coselmar-ui/src/main/webapp/js/coselmar.js | 2 +- coselmar-ui/src/main/webapp/views/home.html | 7 + pom.xml | 31 ++++- 18 files changed, 711 insertions(+), 42 deletions(-) -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
participants (1)
-
codelutin.com scm