From 6a8a3bcae937c48b6b554f3e2927e35d37528ad0 Mon Sep 17 00:00:00 2001 From: Alexey <39551830+Demonkratiy@users.noreply.github.com> Date: Tue, 8 Oct 2024 09:54:43 +0200 Subject: [PATCH] main to certification (#136) * release v2.2.0.0 (#116) * PR Version 2.2.0.0 (#110) * Initialized SelectionManager instance (Working version) * Updated packages and config files * Switched from puppeteer to playwright, and from tslint to eslint + some changes in configs * Fixed code style according to linter's suggestions * Updated packages * Removed jquery * Removed jasmine-jquery dependency and fixed tests * Increased visual's version * Increased the visual's version in pbiviz.json as well * Changed callback type for addTooltipService method * Increased version in the lock file * Removed redundant files * Updated libraries to latest versions * Increased versions of API and visual and fixed Karma config * Added support for keyboard focus * Updated build workflow * Fixed lodash imports and improved type checks in tests * Polished code * Fixed compilation errors in tests * Updated libraries * Updated changelog and set newest API version * Removed tslint file * Created formatting model * Migrated to formatting model * Changed displayName values for Quaity, Min font size and Max font size. Added minRepetitionsToDisplay to string resources * Fixed bug with ColorPicker when color was not applied * supportHighlight logic and tests * showAll, removed unused packages, packages version up * moved keys from cababilities to formatting settings * removed unused npm packages proper contextmenu handling renamed settings object renamed function names from c++ convention to js convention --------- Co-authored-by: Aleksandr Levochkin Co-authored-by: Aleksandr Levochkin * removed support highlight logic (#112) * removed support highlight logic * removed unused function peviously was used by supportHighlight --------- Co-authored-by: Nursultan Dzhumabaev Co-authored-by: Aleksandr Levochkin Co-authored-by: Aleksandr Levochkin * Microsoft mandatory file (#88) Co-authored-by: microsoft-github-policy-service[bot] <77245923+microsoft-github-policy-service[bot]@users.noreply.github.com> * version 2.2.0.0 (#119) * PR Version 2.2.0.0 (#110) * Initialized SelectionManager instance (Working version) * Updated packages and config files * Switched from puppeteer to playwright, and from tslint to eslint + some changes in configs * Fixed code style according to linter's suggestions * Updated packages * Removed jquery * Removed jasmine-jquery dependency and fixed tests * Increased visual's version * Increased the visual's version in pbiviz.json as well * Changed callback type for addTooltipService method * Increased version in the lock file * Removed redundant files * Updated libraries to latest versions * Increased versions of API and visual and fixed Karma config * Added support for keyboard focus * Updated build workflow * Fixed lodash imports and improved type checks in tests * Polished code * Fixed compilation errors in tests * Updated libraries * Updated changelog and set newest API version * Removed tslint file * Created formatting model * Migrated to formatting model * Changed displayName values for Quaity, Min font size and Max font size. Added minRepetitionsToDisplay to string resources * Fixed bug with ColorPicker when color was not applied * supportHighlight logic and tests * showAll, removed unused packages, packages version up * moved keys from cababilities to formatting settings * removed unused npm packages proper contextmenu handling renamed settings object renamed function names from c++ convention to js convention --------- Co-authored-by: Aleksandr Levochkin Co-authored-by: Aleksandr Levochkin * removed support highlight logic (#112) * removed support highlight logic * removed unused function peviously was used by supportHighlight --------- Co-authored-by: Nursultan Dzhumabaev Co-authored-by: Aleksandr Levochkin Co-authored-by: Aleksandr Levochkin * Update codeql-analysis.yml * Selection fix (#129) * Change behavior * Add workaround to deselection * Increase visual version * Update comment --------- Co-authored-by: Iuliia Kulagina * Use new array with SelectionIds (#135) Co-authored-by: Iuliia Kulagina --------- Co-authored-by: MulyukovAidar Co-authored-by: Nursultan Dzhumabaev Co-authored-by: Aleksandr Levochkin Co-authored-by: Aleksandr Levochkin Co-authored-by: microsoft-github-policy-service[bot] <77245923+microsoft-github-policy-service[bot]@users.noreply.github.com> Co-authored-by: Iuliia Kulagina <86924383+kullJul@users.noreply.github.com> Co-authored-by: Iuliia Kulagina --- .github/workflows/codeql-analysis.yml | 62 +++----- CHANGELOG.md | 4 + SECURITY.md | 41 +++++ package-lock.json | 65 +------- package.json | 6 +- pbiviz.json | 4 +- src/WordCloud.ts | 20 +-- src/behavior.ts | 209 ++++++++++++-------------- src/dataInterfaces.ts | 1 + 9 files changed, 165 insertions(+), 247 deletions(-) create mode 100644 SECURITY.md diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d94551a..7082019 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,29 +1,18 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# name: "CodeQL" on: push: - branches: [ main ] + branches: [main, dev, certification] pull_request: - # The branches below must be a subset of the branches above - branches: [ main ] + branches: [main, dev, certification] schedule: - - cron: '38 0 * * 4' + - cron: '0 0 * * 3' jobs: analyze: name: Analyze runs-on: ubuntu-latest + timeout-minutes: 60 permissions: actions: read contents: read @@ -32,40 +21,29 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'javascript' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - + language: ['typescript'] + steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Use Node.js 18 + uses: actions/setup-node@v2 + with: + node-version: 18.x + + - name: Install Dependencies + run: npm ci - # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cef27e..3adea69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.4.0 +### Visual changes: +* Fix bug with resetting the selection + ## 2.3.3.0 ### Visual changes: * Fix bug with bookmarks diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f7b8998 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c6f8e94..b21922f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,19 @@ { "name": "powerbi-visuals-wordcloud", - "version": "2.3.3.0", + "version": "2.3.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "powerbi-visuals-wordcloud", - "version": "2.3.3.0", + "version": "2.3.4.0", "license": "MIT", "dependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1", "lodash.clone": "^4.5.0", "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", "lodash.includes": "^4.3.0", - "lodash.isarray": "^4.0.0", "lodash.isempty": "^4.4.0", "lodash.isstring": "^4.0.1", "lodash.keys": "^4.2.0", @@ -39,9 +37,7 @@ "@types/karma": "^6.3.8", "@types/lodash.clone": "^4.5.9", "@types/lodash.difference": "^4.5.9", - "@types/lodash.flatten": "^4.4.9", "@types/lodash.includes": "^4.3.9", - "@types/lodash.isarray": "^4.0.9", "@types/lodash.isempty": "^4.4.9", "@types/lodash.isstring": "^4.0.9", "@types/lodash.keys": "^4.2.9", @@ -1246,15 +1242,6 @@ "@types/lodash": "*" } }, - "node_modules/@types/lodash.flatten": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@types/lodash.flatten/-/lodash.flatten-4.4.9.tgz", - "integrity": "sha512-JCW9xofpn9oJfFSccpBRF8IrB5guqmcKZIa7J9DnZqLd9wgGYbewaYnjbNw3Fb+St8BHVsnGmmS0A3j99LII3Q==", - "dev": true, - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/lodash.includes": { "version": "4.3.9", "resolved": "https://registry.npmjs.org/@types/lodash.includes/-/lodash.includes-4.3.9.tgz", @@ -1264,15 +1251,6 @@ "@types/lodash": "*" } }, - "node_modules/@types/lodash.isarray": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/lodash.isarray/-/lodash.isarray-4.0.9.tgz", - "integrity": "sha512-wMrERVQy8QK/Imp1HQwObYyYcff80Xxlqjl+6B3C49Wy9stC1AzGZrljCsHEgAiYfSgSFTPWjcX2Ey68VC8lhA==", - "dev": true, - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/lodash.isempty": { "version": "4.4.9", "resolved": "https://registry.npmjs.org/@types/lodash.isempty/-/lodash.isempty-4.4.9.tgz", @@ -5567,22 +5545,11 @@ "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" }, - "node_modules/lodash.isarray": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-4.0.0.tgz", - "integrity": "sha512-V8ViWvoNlXpCrB6Ewaj3ScRXUpmCvqp4tJUxa3dlovuJj/8lp3SND5Kw4v5OeuHgoyw4qJN+gl36qZqp6WYQ6g==", - "deprecated": "This package is deprecated. Use Array.isArray." - }, "node_modules/lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", @@ -9987,15 +9954,6 @@ "@types/lodash": "*" } }, - "@types/lodash.flatten": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@types/lodash.flatten/-/lodash.flatten-4.4.9.tgz", - "integrity": "sha512-JCW9xofpn9oJfFSccpBRF8IrB5guqmcKZIa7J9DnZqLd9wgGYbewaYnjbNw3Fb+St8BHVsnGmmS0A3j99LII3Q==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, "@types/lodash.includes": { "version": "4.3.9", "resolved": "https://registry.npmjs.org/@types/lodash.includes/-/lodash.includes-4.3.9.tgz", @@ -10005,15 +9963,6 @@ "@types/lodash": "*" } }, - "@types/lodash.isarray": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/lodash.isarray/-/lodash.isarray-4.0.9.tgz", - "integrity": "sha512-wMrERVQy8QK/Imp1HQwObYyYcff80Xxlqjl+6B3C49Wy9stC1AzGZrljCsHEgAiYfSgSFTPWjcX2Ey68VC8lhA==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, "@types/lodash.isempty": { "version": "4.4.9", "resolved": "https://registry.npmjs.org/@types/lodash.isempty/-/lodash.isempty-4.4.9.tgz", @@ -13362,21 +13311,11 @@ "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" - }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" }, - "lodash.isarray": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-4.0.0.tgz", - "integrity": "sha512-V8ViWvoNlXpCrB6Ewaj3ScRXUpmCvqp4tJUxa3dlovuJj/8lp3SND5Kw4v5OeuHgoyw4qJN+gl36qZqp6WYQ6g==" - }, "lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", diff --git a/package.json b/package.json index 13ba9b9..e415010 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "powerbi-visuals-wordcloud", - "version": "2.3.3.0", + "version": "2.3.4.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", @@ -33,9 +33,7 @@ "d3-transition": "^3.0.1", "lodash.clone": "^4.5.0", "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", "lodash.includes": "^4.3.0", - "lodash.isarray": "^4.0.0", "lodash.isempty": "^4.4.0", "lodash.isstring": "^4.0.1", "lodash.keys": "^4.2.0", @@ -59,9 +57,7 @@ "@types/karma": "^6.3.8", "@types/lodash.clone": "^4.5.9", "@types/lodash.difference": "^4.5.9", - "@types/lodash.flatten": "^4.4.9", "@types/lodash.includes": "^4.3.9", - "@types/lodash.isarray": "^4.0.9", "@types/lodash.isempty": "^4.4.9", "@types/lodash.isstring": "^4.0.9", "@types/lodash.keys": "^4.2.9", diff --git a/pbiviz.json b/pbiviz.json index 6eac757..132390c 100644 --- a/pbiviz.json +++ b/pbiviz.json @@ -1,10 +1,10 @@ { "visual": { "name": "WordCloud", - "displayName": "WordCloud 2.3.3.0", + "displayName": "WordCloud 2.3.4.0", "guid": "WordCloud1447959067750", "visualClassName": "WordCloud", - "version": "2.3.3.0", + "version": "2.3.4.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": "https://community.powerbi.com", "gitHubUrl": "https://github.com/Microsoft/PowerBI-visuals-WordCloud" diff --git a/src/WordCloud.ts b/src/WordCloud.ts index 6ea8f9d..7237be3 100644 --- a/src/WordCloud.ts +++ b/src/WordCloud.ts @@ -794,24 +794,7 @@ export class WordCloud implements IVisual { this.colorPalette = options.host.colorPalette; this.visualHost = options.host; const selectionManager: ISelectionManager = this.visualHost.createSelectionManager(); - this.behavior = new WordCloudBehavior(selectionManager, - (text: string): ISelectionId[] => { - const dataPoints: WordCloudDataPoint[] = this.data - && this.data.dataPoints - && this.data.dataPoints.filter((dataPoint: WordCloudDataPoint) => { - return dataPoint.text.toLocaleLowerCase() === text; - }); - - return ( - dataPoints && dataPoints[0] && dataPoints[0].selectionIds - ? dataPoints[0].selectionIds - : [] - ); - }, - () => { - return this.data.dataPoints; - } - ); + this.behavior = new WordCloudBehavior(selectionManager); this.layout = new VisualLayout(null, WordCloud.DefaultMargin); @@ -1473,6 +1456,7 @@ export class WordCloud implements IVisual { }; this.behavior.bindEvents(behaviorOptions); + this.behavior.renderSelection(); } private scaleMainView(wordCloudDataView: WordCloudDataView): void { diff --git a/src/behavior.ts b/src/behavior.ts index 11e8ee3..1741084 100644 --- a/src/behavior.ts +++ b/src/behavior.ts @@ -1,7 +1,5 @@ import powerbi from "powerbi-visuals-api"; import { Selection as d3Selection } from "d3-selection"; -import isArray from "lodash.isarray"; -import flatten from "lodash.flatten"; import { WordCloudDataPoint } from "./dataInterfaces"; @@ -22,152 +20,129 @@ export class WordCloudBehavior { private static MaxOpacity: number = 1; private static MinOpacity: number = 0.2; - private selectedWords: Set = new Set(); - private getSelectionIds: (value: string | string[]) => ISelectionId[]; + private dataPoints: WordCloudDataPoint[]; - - constructor(selectionManager: ISelectionManager, getSelectionIds: (value: string) => ISelectionId[], getDataPoints?: () => WordCloudDataPoint[]) { + constructor(selectionManager: ISelectionManager) { this.selectionManager = selectionManager; + this.selectionManager.registerOnSelectCallback(this.onSelectCallback.bind(this)); + } + + private onSelectCallback(selectionIds?: ISelectionId[]){ + this.applySelectionStateToData(selectionIds); + this.renderSelection(); + } - this.getSelectionIds = (value: string | string[]) => isArray(value) - ? flatten((value).map((valueElement: string) => { - return getSelectionIds(valueElement); - })) - : getSelectionIds(value); - - this.selectionManager.registerOnSelectCallback((ids: ISelectionId[]) => { - this.selectedWords.clear(); - ids.forEach((selection: ISelectionId) => { - getDataPoints().forEach((dataPoint: WordCloudDataPoint) => { - if (dataPoint.selectionIds.find((id: ISelectionId) => id.equals(selection))){ - this.selectedWords.add(dataPoint.text.toLocaleLowerCase()); - } - }); + private applySelectionStateToData(selectionIds?: ISelectionId[]): void { + const selectedIds: ISelectionId[] = this.selectionManager.getSelectionIds(); + this.setSelectedToDataPoints(this.dataPoints, selectionIds || selectedIds); + } + + private setSelectedToDataPoints(dataPoints: WordCloudDataPoint[], ids: ISelectionId[]): void{ + dataPoints.forEach((dataPoint: WordCloudDataPoint) => { + dataPoint.selected = false; + ids.forEach((selectedId: ISelectionId) => { + if (dataPoint.selectionIds.some(selectionId=> selectedId.equals(selectionId))) { + dataPoint.selected = true; + } }); - this.renderSelection(); }); } public bindEvents(behaviorOptions: IWordCloudBehaviorOptions): void { this.behaviorOptions = behaviorOptions; + this.dataPoints = behaviorOptions.wordsSelection.data(); - this.bindClickEventToWords(); - this.bindClickEventToClearCatcher(); - this.bindKeyboardEventToWords(); - this.renderSelection(); - } + this.bindClickEvent(this.behaviorOptions.wordsSelection); + this.bindClickEvent(this.behaviorOptions.root); - private bindClickEventToWords(): void { - this.behaviorOptions.wordsSelection.on("click", (event: PointerEvent, word: WordCloudDataPoint) => { - this.selectWord(event, word); - event.stopPropagation(); - this.renderSelection(); - }); + this.bindContextMenuEvent(this.behaviorOptions.wordsSelection); + this.bindContextMenuEvent(this.behaviorOptions.root); - this.behaviorOptions.wordsSelection.on("contextmenu", (event: PointerEvent, word: WordCloudDataPoint) => { - if (event) { - this.selectionManager.showContextMenu( - word.selectionIds, - { - x: event.clientX, - y: event.clientY - }); - event.preventDefault(); - event.stopPropagation(); - } - }); + this.bindKeyboardEvent(this.behaviorOptions.wordsSelection); + this.applySelectionStateToData(); } - private selectWord(event: PointerEvent | KeyboardEvent, word: WordCloudDataPoint):void{ - const isMultiSelection: boolean = event.ctrlKey || event.shiftKey || event.metaKey; - const wordKey = word.text.toLocaleLowerCase(); - if (isMultiSelection){ - if (!this.selectedWords.has(wordKey)){ - this.selectedWords.add(wordKey); - this.selectionManager.select(word.selectionIds, true); + private bindClickEvent(elements: Selection): void { + elements.on("click", (event: PointerEvent, dataPoint: WordCloudDataPoint | undefined) => { + const isMultiSelection: boolean = event.ctrlKey || event.metaKey || event.shiftKey; + if (dataPoint){ + // code to support deselection(without ctr key) of a word with array of selectionIds + // since selectionManager.select(SelectionId[], false) does not deselect Ids + // we want to remove this when the selection manager is fixed. + const selectedIds: ISelectionId[] = this.selectionManager.getSelectionIds() as ISelectionId[]; + const selectionIds: ISelectionId[] = dataPoint.selectionIds + .filter(selectionId => !selectedIds.some(selectedId => selectedId.equals(selectionId))) + .concat(selectedIds.filter(selectedId => !dataPoint.selectionIds.some(selectionId => selectionId.equals(selectedId)))); + + if (!selectionIds.length){ + this.selectionManager.select(Array.from(dataPoint.selectionIds), true); + } + else { + this.selectionManager.select(Array.from(dataPoint.selectionIds), isMultiSelection); + } + event.stopPropagation(); } else { - this.selectedWords.delete(wordKey); - const idsToSelect: ISelectionId[] = this.getSelectionIds(Array.from(this.selectedWords)); - idsToSelect.length === 0 - ? this.selectionManager.clear() - : this.selectionManager.select(idsToSelect); - } - } - else { - if (this.selectedWords.has(wordKey) && this.selectedWords.size === 1){ - this.selectedWords.clear(); this.selectionManager.clear(); } - else { - this.selectedWords.clear(); - this.selectedWords.add(wordKey); - this.selectionManager.select(word.selectionIds); - } - } - } - - private bindClickEventToClearCatcher(): void { - this.behaviorOptions.root.on("click", () => { - this.selectedWords.clear(); - this.selectionManager.clear(); - this.renderSelection(); + this.onSelectCallback(); }); + } - this.behaviorOptions.root.on("contextmenu", (event: PointerEvent) => { - if (event) { - this.selectionManager.showContextMenu( - null, - { - x: event.clientX, - y: event.clientY - }); - event.preventDefault(); - } - }); + private bindContextMenuEvent(elements: Selection): void { + elements.on("contextmenu", (event: PointerEvent, dataPoint: WordCloudDataPoint | undefined) => { + this.selectionManager.showContextMenu(dataPoint ? dataPoint.selectionIds : {}, + { + x: event.clientX, + y: event.clientY + } + ); + event.preventDefault(); + event.stopPropagation(); + }) } - private bindKeyboardEventToWords(): void { - this.behaviorOptions.wordsSelection.on("keydown", (event : KeyboardEvent, word: WordCloudDataPoint) => { - if(event?.code == "Enter" || event?.code == "Space") { - this.selectWord(event, word); - event.stopPropagation(); - this.renderSelection(); + private bindKeyboardEvent(elements: Selection): void { + elements.on("keydown", (event : KeyboardEvent, dataPoint: WordCloudDataPoint) => { + if (event.code !== "Enter" && event.code !== "Space") { + return; + } + const isMultiSelection: boolean = event.ctrlKey || event.metaKey || event.shiftKey; + // code to support deselection(without ctr key) of a word with array of selectionIds + // since selectionManager.select(SelectionId[], false) does not deselect Ids + // we want to remove this when the selection manager is fixed. + const selectedIds: ISelectionId[] = this.selectionManager.getSelectionIds(); + const selectionIds: ISelectionId[] = dataPoint.selectionIds + .filter(selectionId => !selectedIds.some(selectedId => selectedId.equals(selectionId))) + .concat(selectedIds.filter(selectedId => !dataPoint.selectionIds.some(selectionId => selectionId.equals(selectedId)))); + + if (!selectionIds.length){ + this.selectionManager.select(Array.from(dataPoint.selectionIds), true); } + else { + this.selectionManager.select(Array.from(dataPoint.selectionIds), isMultiSelection); + } + + event.stopPropagation(); + this.onSelectCallback(); }); } - private renderSelection(): void { - if (!this.behaviorOptions.wordsSelection) { - return; - } - - if (!this.selectionManager.hasSelection()) { - this.setOpacity(this.behaviorOptions.wordsSelection, WordCloudBehavior.MaxOpacity); - this.setAriaSelectedLabel(this.behaviorOptions.wordsSelection); + public renderSelection(): void { + const wordHasSelection: boolean = this.dataPoints.some((dataPoint: WordCloudDataPoint) => dataPoint.selected); + if (!this.behaviorOptions.wordsSelection) { return; } - const selectedColumns: Selection = this.behaviorOptions.wordsSelection - .filter((dataPoint: WordCloudDataPoint) => { - const wordKey = dataPoint.text.toLocaleLowerCase(); - return this.selectedWords.has(wordKey); - }); - - this.setOpacity(this.behaviorOptions.wordsSelection, WordCloudBehavior.MinOpacity); - this.setOpacity(selectedColumns, WordCloudBehavior.MaxOpacity); - this.setAriaSelectedLabel(this.behaviorOptions.wordsSelection); - } - - private setOpacity(element: Selection, opacityValue: number): void { - element.style("fill-opacity", opacityValue); - } + this.behaviorOptions.wordsSelection.style("fill-opacity", (dataPoint: WordCloudDataPoint) => { + return (dataPoint.selected && wordHasSelection) || !wordHasSelection + ? WordCloudBehavior.MaxOpacity + : WordCloudBehavior.MinOpacity; + }); - private setAriaSelectedLabel(element: Selection){ - element.attr("aria-selected", (dataPoint: WordCloudDataPoint) => { - const wordKey: string = dataPoint.text.toLocaleLowerCase(); - return this.selectedWords.has(wordKey); + this.behaviorOptions.wordsSelection.attr("aria-selected", (dataPoint: WordCloudDataPoint) => { + return (dataPoint.selected && wordHasSelection) || !wordHasSelection }); } } \ No newline at end of file diff --git a/src/dataInterfaces.ts b/src/dataInterfaces.ts index a74f00e..523451a 100644 --- a/src/dataInterfaces.ts +++ b/src/dataInterfaces.ts @@ -72,6 +72,7 @@ export interface WordCloudDataPoint extends IPoint { getWidthOfWord?: () => number; count: number; widthOfWord?: number; + selected: boolean; } export interface WordCloudData {