From c796714be04b124d8bbbe48989340b171330994c Mon Sep 17 00:00:00 2001 From: Leonardo Cavone Date: Fri, 13 Jan 2023 14:55:13 +0100 Subject: [PATCH] Added theme property to AngularD3CloudComponent component --- README.md | 9 +- angular.json | 5 + projects/angular-d3-cloud/README.md | 9 +- projects/angular-d3-cloud/package.json | 2 +- .../src/lib/angular-d3-cloud.component.html | 2 +- .../src/lib/angular-d3-cloud.component.scss | 53 +++++++ .../src/lib/angular-d3-cloud.component.ts | 8 +- .../src/lib/angular-d3-cloud.interfaces.ts | 5 +- .../src/lib/angular-d3-cloud.service.ts | 83 +++++++---- .../src/lib/angular-d3-cloud.utilities.ts | 1 + projects/angular-d3-cloud/tslint.json | 17 --- .../pages/advanced/advanced.component.html | 15 +- .../app/pages/advanced/advanced.component.ts | 4 +- projects/example/tslint.json | 17 --- tslint.json | 140 ------------------ 15 files changed, 155 insertions(+), 215 deletions(-) create mode 100644 projects/angular-d3-cloud/src/lib/angular-d3-cloud.component.scss delete mode 100644 projects/angular-d3-cloud/tslint.json delete mode 100644 projects/example/tslint.json delete mode 100644 tslint.json diff --git a/README.md b/README.md index ad1a8e0..c807f01 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ D3 Cloud component for Angular built upon d3-cloud # Installation ``` -npm install --save @talentia/angular-d3-cloud@1.4.4 +npm install --save @talentia/angular-d3-cloud@1.4.5 ``` Installing the package does not install the type definitions for d3-cloud, d3-scale, d3-scale-chromatic, d3-selection and d3-transition libraries. If you need to use these libraries in your project then install the type definitions with the following command: @@ -80,6 +80,7 @@ export class AppComponent implements OnInit { | tooltip | Whether the tooltip should be shown | boolean | | false | | hover | Whether to apply the hover effect on the words | boolean | | false | | selection | Whether the word should be selectable | boolean | | false | +| theme | Theme to apply on hover or selected words | AngularD3Themes | | text-opacity | # AngularD3CloudComponent Events | Name | Description | Payload | @@ -95,6 +96,12 @@ export class AppComponent implements OnInit { | value | Value to map to font size | number | | tooltip | Tooltip text | string | +# AngularD3Themes Type +| Name | Description | Type | +|---------------|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------------------| +| text-opacity | Apply opacity effect on hover or selected words | string | +| text-shadow | Apply shadow effect on hover or selected words | string | + > `AngularD3Word` extends the interface `Word` imported from `d3-cloud` # Example diff --git a/angular.json b/angular.json index c2369e9..ef58ce5 100644 --- a/angular.json +++ b/angular.json @@ -5,6 +5,11 @@ "projects": { "@talentia/angular-d3-cloud": { "projectType": "library", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, "root": "projects/angular-d3-cloud", "sourceRoot": "projects/angular-d3-cloud/src", "prefix": "lib", diff --git a/projects/angular-d3-cloud/README.md b/projects/angular-d3-cloud/README.md index 38c26bd..12840eb 100644 --- a/projects/angular-d3-cloud/README.md +++ b/projects/angular-d3-cloud/README.md @@ -16,7 +16,7 @@ D3 Cloud component for Angular built upon d3-cloud # Installation ``` -npm install --save @talentia/angular-d3-cloud@1.4.4 +npm install --save @talentia/angular-d3-cloud@1.4.5 ``` Installing the package does not install the type definitions for d3-cloud, d3-scale, d3-scale-chromatic, d3-selection and d3-transition libraries. If you need to use these libraries in your project then install the type definitions with the following command: @@ -78,6 +78,7 @@ export class AppComponent implements OnInit { | tooltip | Whether the tooltip should be shown | boolean | | false | | hover | Whether to apply the hover effect on the words | boolean | | false | | selection | Whether the word should be selectable | boolean | | false | +| theme | Theme to apply on hover or selected words | AngularD3Themes | | text-opacity | # AngularD3CloudComponent Events | Name | Description | Payload | @@ -93,6 +94,12 @@ export class AppComponent implements OnInit { | value | Value to map to font size | number | | tooltip | Tooltip text | string | +# AngularD3Themes Type +| Name | Description | Type | +|---------------|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------------------| +| text-opacity | Apply opacity effect on hover or selected words | string | +| text-shadow | Apply shadow effect on hover or selected words | string | + > `AngularD3Word` extends the interface `Word` imported from `d3-cloud` # Example diff --git a/projects/angular-d3-cloud/package.json b/projects/angular-d3-cloud/package.json index f669ad9..2bfb115 100644 --- a/projects/angular-d3-cloud/package.json +++ b/projects/angular-d3-cloud/package.json @@ -2,7 +2,7 @@ "name": "@talentia/angular-d3-cloud", "title": "Angular D3 Word Cloud", "description": "D3 word cloud component for Angular built upon d3-cloud", - "version": "1.4.4", + "version": "1.4.5", "dependencies": { "d3-cloud": "^1.2.5", "d3-scale": "^4.0.2", diff --git a/projects/angular-d3-cloud/src/lib/angular-d3-cloud.component.html b/projects/angular-d3-cloud/src/lib/angular-d3-cloud.component.html index 5d1aa92..f476293 100644 --- a/projects/angular-d3-cloud/src/lib/angular-d3-cloud.component.html +++ b/projects/angular-d3-cloud/src/lib/angular-d3-cloud.component.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/projects/angular-d3-cloud/src/lib/angular-d3-cloud.component.scss b/projects/angular-d3-cloud/src/lib/angular-d3-cloud.component.scss new file mode 100644 index 0000000..fe65948 --- /dev/null +++ b/projects/angular-d3-cloud/src/lib/angular-d3-cloud.component.scss @@ -0,0 +1,53 @@ +:host { + $opacity-hover: 0.5 !default; + $opacity-selected: 0.5 !default; + $text-shadow-default-offset-x: 2px !default; + $text-shadow-default-offset-y: 2px !default; + $text-shadow-default-blur-radius: 3px !default; + $text-shadow-default-color: black; + + --user-select: none; + --cursor-hover: pointer; + --opacity-hover: #{$opacity-hover}; + --opacity-selected: #{$opacity-selected}; + --text-shadow-hover-offset-x: #{$text-shadow-default-offset-x}; + --text-shadow-hover-offset-y: #{$text-shadow-default-offset-y}; + --text-shadow-hover-blur-radius: #{$text-shadow-default-blur-radius}; + --text-shadow-hover-color: #{$text-shadow-default-color}; + --text-shadow-selected-offset-x: #{$text-shadow-default-offset-x}; + --text-shadow-selected-offset-y: #{$text-shadow-default-offset-y}; + --text-shadow-selected-blur-radius: #{$text-shadow-default-blur-radius}; + --text-shadow-selected-color: #{$text-shadow-default-color}; + --text-shadow-hover: var(--text-shadow-hover-offset-x) var(--text-shadow-hover-offset-y) var(--text-shadow-hover-blur-radius) var(--text-shadow-hover-color); + --text-shadow-selected: var(--text-shadow-selected-offset-x) var(--text-shadow-selected-offset-y) var(--text-shadow-selected-blur-radius) var(--text-shadow-selected-color); +} + +svg { + text { + user-select: var(--user-select); + &.cursor--hover { + cursor: var(--cursor-hover); + } + } +} + +svg.text-opacity { + text.text--hover { + opacity: var(--opacity-hover); + } + text.text--selected { + opacity: var(--opacity-selected); + } +} + +svg.text-shadow { + text.text--hover { + text-shadow: var(--text-shadow-hover); + } +} + +svg.text-shadow { + text.text--selected { + text-shadow: var(--text-shadow-selected); + } +} diff --git a/projects/angular-d3-cloud/src/lib/angular-d3-cloud.component.ts b/projects/angular-d3-cloud/src/lib/angular-d3-cloud.component.ts index 0a7c6d3..5e22cec 100644 --- a/projects/angular-d3-cloud/src/lib/angular-d3-cloud.component.ts +++ b/projects/angular-d3-cloud/src/lib/angular-d3-cloud.component.ts @@ -1,12 +1,13 @@ import { Component, ElementRef, EventEmitter, Input, OnChanges, AfterViewInit, Output, SimpleChanges, ViewChild, OnDestroy, inject } from '@angular/core'; import { Subscription } from 'rxjs'; -import { AngularD3CloudOptions, AngularD3Word } from './angular-d3-cloud.interfaces'; +import { AngularD3CloudOptions, AngularD3Word, AngularD3Themes } from './angular-d3-cloud.interfaces'; import { AngularD3CloudService } from './angular-d3-cloud.service'; import { clone, defaultOptions, hasChanges } from './angular-d3-cloud.utilities'; @Component({ selector: 'angular-d3-cloud', - templateUrl: './angular-d3-cloud.component.html' + templateUrl: './angular-d3-cloud.component.html', + styleUrls: ['./angular-d3-cloud.component.scss'] }) export class AngularD3CloudComponent implements OnChanges, AfterViewInit, OnDestroy { @ViewChild('wordcloud', { static: false }) wordcloud: ElementRef | undefined; @@ -27,6 +28,7 @@ export class AngularD3CloudComponent implements OnChanges, AfterViewInit, OnDest @Input() tooltip: boolean = defaultOptions.tooltip; @Input() hover: boolean = defaultOptions.hover; @Input() selection: boolean = defaultOptions.selection; + @Input() theme: AngularD3Themes = defaultOptions.theme; @Output() wordClick = new EventEmitter<{ event: MouseEvent, word: AngularD3Word }>(); @Output() wordMouseOver = new EventEmitter<{ event: MouseEvent, word: AngularD3Word }>(); @@ -114,6 +116,7 @@ export class AngularD3CloudComponent implements OnChanges, AfterViewInit, OnDest tooltip: this.tooltip, hover: this.hover, selection: this.selection, + theme: this.theme, mouseClickObserved: this.wordClick.observed, mouseOverObserved: this.wordMouseOver.observed, mouseMoveObserved: this.wordMouseMove.observed, @@ -138,6 +141,7 @@ export class AngularD3CloudComponent implements OnChanges, AfterViewInit, OnDest this.options.tooltip = this.tooltip; this.options.hover = this.hover; this.options.selection = this.selection; + this.options.theme = this.theme; this.options.mouseClickObserved = this.wordClick.observed; this.options.mouseOverObserved = this.wordMouseOver.observed; this.options.mouseMoveObserved = this.wordMouseMove.observed; diff --git a/projects/angular-d3-cloud/src/lib/angular-d3-cloud.interfaces.ts b/projects/angular-d3-cloud/src/lib/angular-d3-cloud.interfaces.ts index 817d023..bbaea57 100644 --- a/projects/angular-d3-cloud/src/lib/angular-d3-cloud.interfaces.ts +++ b/projects/angular-d3-cloud/src/lib/angular-d3-cloud.interfaces.ts @@ -29,8 +29,11 @@ export interface AngularD3CloudOptions { tooltip: boolean; hover: boolean; selection: boolean; + theme: AngularD3Themes; mouseClickObserved: boolean; mouseOverObserved: boolean; mouseMoveObserved: boolean; mouseOutObserved: boolean; -} \ No newline at end of file +} + +export type AngularD3Themes = 'text-opacity' | 'text-shadow'; \ No newline at end of file diff --git a/projects/angular-d3-cloud/src/lib/angular-d3-cloud.service.ts b/projects/angular-d3-cloud/src/lib/angular-d3-cloud.service.ts index 4d915a6..59e1fa1 100644 --- a/projects/angular-d3-cloud/src/lib/angular-d3-cloud.service.ts +++ b/projects/angular-d3-cloud/src/lib/angular-d3-cloud.service.ts @@ -41,6 +41,12 @@ export class AngularD3CloudService { private render(node: Element, options: AngularD3CloudOptions): void { this.ngZone.runOutsideAngular(() => { + let ngcontent: string; + const attributes = node.getAttributeNames(); + if(attributes) { + ngcontent = attributes.find((value: string) => value.startsWith('_ngcontent-')) as string; + } + select(node).selectAll('*').remove(); const layout = cloud() @@ -61,8 +67,11 @@ export class AngularD3CloudService { .attr('height', h) .attr('viewBox', `0 0 ${w} ${h}`) .attr('preserveAspectRatio', 'xMinYMin meet') - .append('g') - .attr('transform', `translate(${w / 2},${h / 2})`) + .attr(ngcontent, '') + .classed(options.theme, true) + .append('g') + .attr('transform', `translate(${w / 2},${h / 2})`) + .attr(ngcontent, '') .selectAll('text') .data(words) .enter() @@ -71,9 +80,9 @@ export class AngularD3CloudService { .style('font-style', options.fontStyle) .style('font-weight', options.fontWeight) .style('font-size', data => `${data.size}px`) - .style('user-select', 'none') .style('fill', (word, index) => this.applyFill(options, word, index)) - .attr('text-anchor', 'middle') + .attr('text-anchor', 'middle') + .attr(ngcontent, '') .text((data) => data.text); this.applyTooltip(options, texts); @@ -93,15 +102,9 @@ export class AngularD3CloudService { return canvas; } - private applyFill(options: AngularD3CloudOptions, word: AngularD3Word, index: number): string | null { - if (options.autoFill) { - if(options.fillMapper) { - return options.fillMapper(word, index); - } else { - return defaultFillMapper(word, index); - } - } else { - return null; + private applyTooltip(options: AngularD3CloudOptions, texts: Selection): void { + if(options.tooltip) { + texts.append('title').text((data) => data.tooltip || data.text); } } @@ -129,27 +132,37 @@ export class AngularD3CloudService { private applyEventListeners(options: AngularD3CloudOptions, texts: Selection): void { const _this = this; + const applyCursorHover = (options.tooltip || options.hover || options.selection); texts.on('click', function(this: SVGTextElement, event: MouseEvent, word: AngularD3Word) { - if(options.selection) { - const text = select(this); - const selected = text.classed('word-selected'); + const text = select(this); + + if(options.selection) { + const selected = text.classed('text--selected'); if(selected) { - text.style('fill-opacity', 1.0).classed('word-selected', false); + text.classed('text--selected', false); } else { - texts.filter(function(this: SVGTextElement) { return select(this).classed('word-selected') }).style('fill-opacity', 1.0).classed('word-selected', false); - text.style('fill-opacity', 0.5).classed('word-selected', true); - } - } + texts.filter(function(this: SVGTextElement) { return select(this).classed('text--selected'); }).classed('text--selected', false); + text.classed('text--selected', true); + } + } + if(options.mouseClickObserved) { _this.wordMouseClick.next({ event, word }); } }); texts.on('mouseover', function(this: SVGTextElement, event: MouseEvent, word: AngularD3Word) { + const text = select(this); + + if(applyCursorHover) { + text.classed('cursor--hover', true); + } + if(options.hover) { - select(this).style('fill-opacity', 0.5); + text.classed('text--hover', true); } + if(options.mouseOverObserved) { _this.wordMouseOver.next({ event, word }); } @@ -163,24 +176,30 @@ export class AngularD3CloudService { texts.on('mouseout', function(this: SVGTextElement, event: MouseEvent, word: AngularD3Word) { const text = select(this); - let selected = false; - if(options.selection) { - selected = text.classed('word-selected'); + + if(applyCursorHover) { + text.classed('cursor--hover', false); } - if(options.hover && !selected) { - text.style('fill-opacity', 1.0); + + if(options.hover) { + text.classed('text--hover', false); } + if(options.mouseOutObserved) { _this.wordMouseOut.next({ event, word }); } }); } - private applyTooltip(options: AngularD3CloudOptions, texts: Selection): void { - if(options.tooltip) { - texts.append('title').text((data) => { - return data.tooltip || data.text; - }); + private applyFill(options: AngularD3CloudOptions, word: AngularD3Word, index: number): string | null { + if (options.autoFill) { + if(options.fillMapper) { + return options.fillMapper(word, index); + } else { + return defaultFillMapper(word, index); + } + } else { + return null; } } diff --git a/projects/angular-d3-cloud/src/lib/angular-d3-cloud.utilities.ts b/projects/angular-d3-cloud/src/lib/angular-d3-cloud.utilities.ts index 28c6d8e..a527de0 100644 --- a/projects/angular-d3-cloud/src/lib/angular-d3-cloud.utilities.ts +++ b/projects/angular-d3-cloud/src/lib/angular-d3-cloud.utilities.ts @@ -69,6 +69,7 @@ export const defaultOptions: AngularD3CloudOptions = { tooltip: false, hover: false, selection: false, + theme: 'text-opacity', mouseClickObserved: false, mouseOverObserved: false, mouseMoveObserved: false, diff --git a/projects/angular-d3-cloud/tslint.json b/projects/angular-d3-cloud/tslint.json deleted file mode 100644 index 124133f..0000000 --- a/projects/angular-d3-cloud/tslint.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../tslint.json", - "rules": { - "directive-selector": [ - true, - "attribute", - "lib", - "camelCase" - ], - "component-selector": [ - true, - "element", - "lib", - "kebab-case" - ] - } -} diff --git a/projects/example/src/app/pages/advanced/advanced.component.html b/projects/example/src/app/pages/advanced/advanced.component.html index c3aa466..22cf818 100644 --- a/projects/example/src/app/pages/advanced/advanced.component.html +++ b/projects/example/src/app/pages/advanced/advanced.component.html @@ -107,7 +107,7 @@
- +
Font Style
@@ -117,6 +117,18 @@
+ +
+ +
+
+
Theme
+
+ +
+

@@ -136,6 +148,7 @@ [tooltip]="tooltip" [hover]="hover" [selection]="selection" + [theme]="theme" (wordClick)="onWordClick($event)" (wordMouseOver)="onWordMouseOver($event)" (wordMouseMove)="onWordMouseMove($event)" diff --git a/projects/example/src/app/pages/advanced/advanced.component.ts b/projects/example/src/app/pages/advanced/advanced.component.ts index d37999c..72efba6 100644 --- a/projects/example/src/app/pages/advanced/advanced.component.ts +++ b/projects/example/src/app/pages/advanced/advanced.component.ts @@ -3,7 +3,7 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core import { FormsModule } from '@angular/forms'; import { scaleLinear, scaleOrdinal } from 'd3-scale'; import { schemeBlues, schemeCategory10, schemeGreens, schemePastel1, schemePastel2 } from 'd3-scale-chromatic'; -import { AngularD3CloudModule, AngularD3Word } from '@talentia/angular-d3-cloud'; +import { AngularD3CloudModule, AngularD3Word, AngularD3Themes } from '@talentia/angular-d3-cloud'; @Component({ selector: 'app-advanced', @@ -37,6 +37,7 @@ export class AdvancedComponent implements OnInit { public styles: string[] = [ "normal", "italic"]; public paddings: number[] = [0, 1, 2, 3, 4, 5]; public speeds: number[] = [100, 300, 600, 1000]; + public themes: string[] = [ "text-opacity", "text-shadow"]; public rotate!: number | ((word: AngularD3Word, index: number) => number); public fillMapper!: (word: AngularD3Word, index: number) => string; @@ -50,6 +51,7 @@ export class AdvancedComponent implements OnInit { public tooltip: boolean = true; public hover: boolean = true; public selection: boolean = true; + public theme: AngularD3Themes = "text-opacity"; private _rotation: boolean = true; public get rotation(): boolean { diff --git a/projects/example/tslint.json b/projects/example/tslint.json deleted file mode 100644 index 19e8161..0000000 --- a/projects/example/tslint.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../tslint.json", - "rules": { - "directive-selector": [ - true, - "attribute", - "app", - "camelCase" - ], - "component-selector": [ - true, - "element", - "app", - "kebab-case" - ] - } -} diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 733008b..0000000 --- a/tslint.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "extends": "tslint:recommended", - "rulesDirectory": [ - "codelyzer" - ], - "rules": { - "align": { - "options": [ - "parameters", - "statements" - ] - }, - "array-type": false, - "arrow-return-shorthand": true, - "curly": true, - "deprecation": { - "severity": "warning" - }, - "eofline": true, - "import-blacklist": [ - true, - "rxjs/Rx" - ], - "import-spacing": true, - "indent": { - "options": [ - "spaces" - ] - }, - "max-classes-per-file": false, - "max-line-length": [ - true, - 140 - ], - "member-ordering": [ - true, - { - "order": [ - "static-field", - "instance-field", - "static-method", - "instance-method" - ] - } - ], - "no-console": [ - true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-empty": false, - "no-inferrable-types": [ - true, - "ignore-params" - ], - "no-non-null-assertion": true, - "no-redundant-jsdoc": true, - "no-switch-case-fall-through": true, - "no-var-requires": false, - "object-literal-key-quotes": [ - true, - "as-needed" - ], - "quotemark": [ - true, - "single" - ], - "semicolon": { - "options": [ - "always" - ] - }, - "space-before-function-paren": { - "options": { - "anonymous": "never", - "asyncArrow": "always", - "constructor": "never", - "method": "never", - "named": "never" - } - }, - "typedef": [ - true, - "call-signature" - ], - "typedef-whitespace": { - "options": [ - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - }, - { - "call-signature": "onespace", - "index-signature": "onespace", - "parameter": "onespace", - "property-declaration": "onespace", - "variable-declaration": "onespace" - } - ] - }, - "variable-name": { - "options": [ - "ban-keywords", - "check-format", - "allow-pascal-case" - ] - }, - "whitespace": { - "options": [ - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type", - "check-typecast" - ] - }, - "component-class-suffix": true, - "contextual-lifecycle": true, - "directive-class-suffix": true, - "no-conflicting-lifecycle": true, - "no-host-metadata-property": true, - "no-input-rename": true, - "no-inputs-metadata-property": true, - "no-output-native": true, - "no-output-on-prefix": true, - "no-output-rename": true, - "no-outputs-metadata-property": true, - "template-banana-in-box": true, - "template-no-negated-async": true, - "use-lifecycle-interface": true, - "use-pipe-transform-interface": true - } -}