diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bb5e97..74592e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,12 @@ +## 1.3.0 +* ADD. Added a new role to set excludes (like filters) for categories. This feature works in addition to StopWords options, however, if option is disabled, excludes will be applied anyway + ## 1.2.14 -FIX. Fix to use Defered object without jQuery library +* FIX. Fix to use Defered object without jQuery library ## 1.2.13 -ADD. Added default color for words -ADD. Added tooltips +* ADD. Added default color for words +* ADD. Added tooltips ## 1.2.12 * FIX. memory leak: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': Out of memory at ImageData creation diff --git a/capabilities.json b/capabilities.json index 407df0c..1877da9 100644 --- a/capabilities.json +++ b/capabilities.json @@ -11,6 +11,12 @@ "displayNameKey": "Visual_Values", "kind": "Measure", "displayName": "Values" + }, + { + "name": "Excludes", + "displayNameKey": "Visual_Excludes", + "kind": "Grouping", + "displayName": "Excludes" } ], "dataViewMappings": [ @@ -24,14 +30,28 @@ "Values": { "min": 0, "max": 1 + }, + "Excludes": { + "min": 0, + "max": 1 } } ], "categorical": { "categories": { - "for": { - "in": "Category" - }, + "select": [ + { + "bind": { + "to": "Category" + } + }, + { + "bind": { + "to": "Excludes" + } + } + ], + "dataReductionAlgorithm": { "top": { "count": 2500 diff --git a/package.json b/package.json index 53d7169..048aab1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "powerbi-visuals-wordcloud", - "version": "1.2.14", + "version": "1.3.0", "description": "Word Cloud is a visual representation of word frequency and value. Use it to get instant insight into the most important terms in a set.", "repository": { "type": "git", diff --git a/pbiviz.json b/pbiviz.json index f034a31..3ef8fa9 100644 --- a/pbiviz.json +++ b/pbiviz.json @@ -4,7 +4,7 @@ "displayName": "WordCloud", "guid": "WordCloud1447959067750", "visualClassName": "WordCloud", - "version": "1.2.14", + "version": "1.3.0", "description": "Word Cloud is a visual representation of word frequency and value. Use it to get instant insight into the most important terms in a set.", "supportUrl": "http://community.powerbi.com", "gitHubUrl": "https://github.com/Microsoft/PowerBI-visuals-WordCloud" diff --git a/src/columns.ts b/src/columns.ts index cd57378..8bdefb4 100644 --- a/src/columns.ts +++ b/src/columns.ts @@ -81,5 +81,6 @@ module powerbi.extensibility.visual { // Data Roles public Category: T = null; public Values: T = null; + public Excludes: T = null; } } diff --git a/src/visual.ts b/src/visual.ts index 6a9a754..b8a2793 100644 --- a/src/visual.ts +++ b/src/visual.ts @@ -236,6 +236,7 @@ module powerbi.extensibility.visual { settings: WordCloudSettings, colorHelper: ColorHelper, stopWords: string[], + excludedSet: PrimitiveValue[], texts: WordCloudText[] = [], reducedTexts: WordCloudText[][], dataPoints: WordCloudDataPoint[], @@ -263,6 +264,10 @@ module powerbi.extensibility.visual { ? stopWords.concat(WordCloud.StopWords) : stopWords; + excludedSet = !categorical.Excludes || _.isEmpty(categorical.Excludes.values) + ? [] + : categorical.Excludes.values; + colorHelper = new ColorHelper( colors, WordCloud.DataPointFillProperty, @@ -308,7 +313,7 @@ module powerbi.extensibility.visual { }); } - reducedTexts = WordCloud.getReducedText(texts, stopWords, settings); + reducedTexts = WordCloud.getReducedText(texts, stopWords, excludedSet, settings); dataPoints = WordCloud.getDataPoints(reducedTexts, settings); return { @@ -358,10 +363,11 @@ module powerbi.extensibility.visual { private static getReducedText( texts: WordCloudText[], - stopWords: string[], + stopWords: PrimitiveValue[], + excludedSet: PrimitiveValue[], settings: WordCloudSettings): WordCloudText[][] { - let brokenStrings: WordCloudText[] = WordCloud.processText(texts, stopWords, settings), + let brokenStrings: WordCloudText[] = WordCloud.processText(texts, stopWords, excludedSet, settings), result: WordCloudText[][] = _.values(_.groupBy( brokenStrings, (textObject: WordCloudText) => textObject.text.toLocaleLowerCase())); @@ -375,21 +381,34 @@ module powerbi.extensibility.visual { private static processText( words: WordCloudText[], - stopWords: string[], + stopWords: PrimitiveValue[], + excludedSet: PrimitiveValue[], settings: WordCloudSettings): WordCloudText[] { let processedText: WordCloudText[] = [], partOfProcessedText: WordCloudText[] = [], whiteSpaceRegExp: RegExp = /\s/, - punctuationRegExp: RegExp = new RegExp(`[${WordCloud.Punctuation.join("\\")}]`, "gim"); + punctuationRegExp: RegExp = new RegExp(`[${WordCloud.Punctuation.join("\\")}]`, "gim"), + splittedExcludes: PrimitiveValue[] = []; + + excludedSet.forEach((item: PrimitiveValue) => { + if (typeof item === "string" || typeof item === "number") { + let splittedExclude: string[] = item.toString() + .replace(punctuationRegExp, " ") + .split(whiteSpaceRegExp); + + splittedExcludes = splittedExcludes.concat(splittedExclude); + } + }); words.forEach((item: WordCloudText) => { if (typeof item.text === "string") { let splittedWords: string[] = item.text .replace(punctuationRegExp, " ") .split(whiteSpaceRegExp); + const splittedWordsOriginalLength: number = splittedWords.length; - splittedWords = WordCloud.getFilteredWords(splittedWords, stopWords, settings); + splittedWords = WordCloud.getFilteredWords(splittedWords, stopWords, splittedExcludes, settings); partOfProcessedText = settings.general.isBrokenText ? WordCloud.getBrokenWords(splittedWords, item, whiteSpaceRegExp) : WordCloud.getFilteredSentences(splittedWords, item, splittedWordsOriginalLength, settings, punctuationRegExp); @@ -449,9 +468,16 @@ module powerbi.extensibility.visual { private static getFilteredWords( words: string[], - stopWords: string[], + stopWords: PrimitiveValue[], + splittedExcludes: PrimitiveValue[], settings: WordCloudSettings) { + words = words.filter((value: string) => { + return value.length > 0 && !splittedExcludes.some((removeWord: string) => { + return value.toLocaleLowerCase() === removeWord.toLocaleLowerCase(); + }); + }); + if (!settings.stopWords.show || !stopWords.length) { return words; } diff --git a/stringResources/en-US/resources.resjson b/stringResources/en-US/resources.resjson index a889409..1586bb6 100644 --- a/stringResources/en-US/resources.resjson +++ b/stringResources/en-US/resources.resjson @@ -17,5 +17,6 @@ "Visual_Show": "Show", "Visual_MinAngle": "Min Angle", "Visual_MaxAngle": "Max Angle", - "Visual_RotateText": "Rotate Text" + "Visual_RotateText": "Rotate Text", + "Visual_Excludes": "Excludes" } \ No newline at end of file diff --git a/test/visualData.ts b/test/visualData.ts index bfe264d..9a5b761 100644 --- a/test/visualData.ts +++ b/test/visualData.ts @@ -36,6 +36,7 @@ module powerbi.extensibility.visual.test { export class WordCloudData extends TestDataViewBuilder { public static ColumnCategory: string = "Category"; public static ColumnValues: string = "Values"; + public static ColumnExcludes: string = "Excludes"; public valuesCategoryValues: any[][] = [ ["", null], @@ -123,6 +124,14 @@ module powerbi.extensibility.visual.test { type: ValueType.fromDescriptor({ text: true }) }, values: this.valuesCategoryValues.map((value: any[]) => value[0]) + }, + { + source: { + displayName: WordCloudData.ColumnExcludes, + roles: { "Excludes": true }, + type: ValueType.fromDescriptor({ text: true }), + }, + values: ["Afganistan", "Something", "\"Rwanda\", \"Uganda\""] } ], [ { diff --git a/test/visualTest.ts b/test/visualTest.ts index 687d3e2..8e6c490 100644 --- a/test/visualTest.ts +++ b/test/visualTest.ts @@ -75,6 +75,26 @@ module powerbi.extensibility.visual.test { expect(visualBuilder.mainElement[0]).toBeInDOM(); }); + it("apply excludes", (done) => { + dataView.categorical.categories[0].values = ["Afganistan", "Angola", "Rwanda", "Uganda", "Fiji", "Papua New Guinea"]; + + dataView.metadata.objects = { + stopWords: { + show: true, + words: "Papua New Guinea" + } + }; + + // Should leave Angola and Fiji only + // Afganistan, Rwanda, Uganda must be filtered by Excludes + // Papua New Guinea must be filtered by StopWords option + visualBuilder.updateRenderTimeout(dataView, () => { + let length: number = visualBuilder.words.toArray().length; + expect(length).toEqual(2); + done(); + }, 500); + }); + it("basic update", (done) => { visualBuilder.updateRenderTimeout(dataView, () => { expect(visualBuilder.wordText.length)