From f67c743a6d003262cb22fe93a7aaf556adcffde8 Mon Sep 17 00:00:00 2001 From: Ara Winters Date: Fri, 27 Sep 2024 11:41:10 -0700 Subject: [PATCH] #690 #689 graph node coloring changes (#691) --- src/lib/common/utils.ts | 24 +++++++ src/lib/graph/sz-graph-filter.component.html | 1 - src/lib/graph/sz-graph-filter.component.ts | 25 +++++-- src/lib/graph/sz-graph.component.ts | 68 +++++++++++++------ .../sz-relationship-network.component.ts | 19 +++--- src/lib/scss/graph.scss | 6 ++ src/lib/services/sz-css-class.service.ts | 21 +++++- 7 files changed, 130 insertions(+), 34 deletions(-) diff --git a/src/lib/common/utils.ts b/src/lib/common/utils.ts index 899eea84..e76d6e91 100644 --- a/src/lib/common/utils.ts +++ b/src/lib/common/utils.ts @@ -225,6 +225,30 @@ export function isValueTypeOfArray(value: any) { return retVal; } +/** are all values in one array present in the comparison array with no extra members + * present in either. +*/ +export function areArrayMembersEqual(value1: any[], value2: any[]) { + let _tempArr1ValsMap = new Map( (value1 as unknown as string[]).map((val) => { return [val.toString(), val]; })); + let _tempAllValsMap = new Map(_tempArr1ValsMap); + // add existing entityId's to hashmap + if(value2 && value2.forEach) { + value2.forEach((value) => { + _tempAllValsMap.set(value as string, value); + }); + // now remove all entity id's that are identical to the values in input values + // if there is a remaining value then the id sets are different + if((value1 as unknown as string[]) && (value1 as unknown as string[]).forEach) { + (value1 as unknown as string[]).forEach((valStr) => { + if(_tempAllValsMap.has(valStr.toString())) { _tempAllValsMap.delete(valStr.toString()); } + }); + } + + } + let noRemainder = _tempAllValsMap.size <= 0; + return noRemainder; +} + export function interpolateTemplate(template, args) { return Object.entries(args).reduce( (result, [arg, val]) => result.replace(`$\{${arg}}`, `${val}`), diff --git a/src/lib/graph/sz-graph-filter.component.html b/src/lib/graph/sz-graph-filter.component.html index 5ab04bbd..80c17e11 100644 --- a/src/lib/graph/sz-graph-filter.component.html +++ b/src/lib/graph/sz-graph-filter.component.html @@ -163,7 +163,6 @@

{{ sectionTitles[4] }}

Active/Focused Enitity The color of the current entity or entities - (*note overrides datasource member color if selected) diff --git a/src/lib/graph/sz-graph-filter.component.ts b/src/lib/graph/sz-graph-filter.component.ts index 1da30fc0..fcf1f2a0 100644 --- a/src/lib/graph/sz-graph-filter.component.ts +++ b/src/lib/graph/sz-graph-filter.component.ts @@ -206,7 +206,7 @@ export class SzGraphFilterComponent implements OnInit, AfterViewInit, OnDestroy // assume it's already cast correctly this._matchKeyTokenSelectionScope = (value as SzMatchKeyTokenFilterScope); } - console.log(`@senzing/sdk-components-ng/sz-graph-filter.matchKeyTokenSelectionScope(${value} | ${(this._matchKeyTokenSelectionScope as unknown as string)})`, this._matchKeyTokenSelectionScope); + //console.log(`@senzing/sdk-components-ng/sz-graph-filter.matchKeyTokenSelectionScope(${value} | ${(this._matchKeyTokenSelectionScope as unknown as string)})`, this._matchKeyTokenSelectionScope); } /** * get the value of match key token filterings scope. possible values are @@ -225,7 +225,7 @@ export class SzGraphFilterComponent implements OnInit, AfterViewInit, OnDestroy private _showCoreMatchKeyTokenChips: boolean = false; @Input() public set showCoreMatchKeyTokenChips(value: boolean) { this._showCoreMatchKeyTokenChips = value; - console.log(`@senzing/sdk-components-ng/sz-graph-filter.showCoreMatchKeyTokenChips(${value})`, this._showCoreMatchKeyTokenChips); + //console.log(`@senzing/sdk-components-ng/sz-graph-filter.showCoreMatchKeyTokenChips(${value})`, this._showCoreMatchKeyTokenChips); } public get showCoreMatchKeyTokenChips(): boolean { return this._showCoreMatchKeyTokenChips; @@ -582,19 +582,36 @@ export class SzGraphFilterComponent implements OnInit, AfterViewInit, OnDestroy } /** handler for when a color value for a source in the "colorsByDataSourcesForm" has changed */ onDsColorChange(dsValue: string, src?: any, evt?) { + // first check if color is 000 or fff + // if so this is a remove not an update + let _isRemoveOp = false; + //console.log(`changed color for "${dsValue}" to "${src.value}"`); + if(['#ffffff','#000000'].includes(src.value)) { + _isRemoveOp = true; + } // update color value in array if(this._dataSources) { let _dsIndex = this._dataSources.findIndex((dsVal: SzDataSourceComposite) => { return dsVal.name === dsValue; }); if(_dsIndex && this._dataSources && this._dataSources[ _dsIndex ]) { - this._dataSources[ _dsIndex ].color = src.value; + if(_isRemoveOp) { + this._dataSources[ _dsIndex ].color = undefined; + //delete this._dataSources[ _dsIndex ].color; + } else { + this._dataSources[ _dsIndex ].color = src.value; + } } } // update color swatch bg color(for prettier boxes) if(src && src.style && src.style.setProperty){ - src.style.setProperty('background-color', src.value); + if(_isRemoveOp) { + src.style.removeProperty('background-color'); + } else { + src.style.setProperty('background-color', src.value); + } } + // update colors pref if( this.prefs && this.prefs.graph) { // there is some sort of mem reference clone issue diff --git a/src/lib/graph/sz-graph.component.ts b/src/lib/graph/sz-graph.component.ts index 141cfaad..a879891e 100644 --- a/src/lib/graph/sz-graph.component.ts +++ b/src/lib/graph/sz-graph.component.ts @@ -896,7 +896,8 @@ export class SzGraphComponent implements OnInit, OnDestroy { /** proxy handler for when prefs have changed externally */ private onPrefsChange(prefs: SzGraphPrefs) { //console.log('@senzing/sdk-components-ng/sz-graph-component.onPrefsChange(): ', prefs, this.prefs.graph.toJSONObject()); - let queryParamChanged = false; + let queryParamChanged = false; + let queryParametersChanged = []; let _oldQueryParams = {maxDegrees: this.maxDegrees, maxEntities: this.maxEntities, buildOut: this.buildOut, unlimitedMaxEntities: this.unlimitedMaxEntities, unlimitedMaxScope: this.unlimitedMaxScope}; let _newQueryParams = {maxDegrees: prefs.maxDegreesOfSeparation, maxEntities: prefs.maxEntities, buildOut: prefs.buildOut, unlimitedMaxEntities: prefs.unlimitedMaxEntities, unlimitedMaxScope: prefs.unlimitedMaxScope}; if( @@ -910,8 +911,23 @@ export class SzGraphComponent implements OnInit, OnDestroy { !this.unlimitedMaxEntities ) ) || - this.buildOut != prefs.buildOut + (this.buildOut != prefs.buildOut && !prefs.unlimitedMaxScope) ){ + if(this.maxDegrees != prefs.maxDegreesOfSeparation) { + queryParametersChanged.push('maxDegrees'); + } + if(this.maxEntities != prefs.maxEntities && ((this.unlimitedMaxEntities != prefs.unlimitedMaxEntities) || !this.unlimitedMaxEntities)) { + queryParametersChanged.push('maxEntities'); + } + if(this.unlimitedMaxEntities != prefs.unlimitedMaxEntities) { + queryParametersChanged.push('unlimitedMaxEntities'); + } + if(this.graphNetworkComponent && this.graphNetworkComponent.noMaxEntitiesLimit != prefs.unlimitedMaxEntities) { + queryParametersChanged.push('noMaxEntitiesLimit'); + } + if(this.buildOut != prefs.buildOut && !prefs.unlimitedMaxScope) { + queryParametersChanged.push(`buildOut`); + } // only params that factor in to the API call // should trigger full redraw queryParamChanged = true; @@ -947,21 +963,25 @@ export class SzGraphComponent implements OnInit, OnDestroy { } if(prefs.dataSourceColors && prefs.dataSourceColors.sort) { let sorted = Array.from(prefs.dataSourceColors) - .filter((dsColorEntry: SzDataSourceComposite) => { - return dsColorEntry.color !== undefined; - }) .sort((dsColorEntry1: SzDataSourceComposite, dsColorEntry2: SzDataSourceComposite) => { let retVal = dsColorEntry1.index > dsColorEntry2.index ? -1 : (dsColorEntry1.index < dsColorEntry2.index) ? 1 : 0 ; return retVal; }) .forEach((dsColorEntry: SzDataSourceComposite) => { - this.graphContainerEle.nativeElement.style.setProperty( - `--sz-graph-node-ds-${dsColorEntry.name.toLowerCase()}-fill`, - dsColorEntry.color - ); - //this.cssClassesService.setStyle(`.sz-node-ds-${dsColorEntry.name.toLowerCase()}`, "fill", dsColorEntry.color); - this.cssClassesService.setStyle(`body .sz-relationship-network-graph .sz-node-ds-${dsColorEntry.name.toLowerCase()} .sz-graph-node-icon .sz-node-ds-${dsColorEntry.name.toLowerCase()}-fill`, "fill", `var(--sz-graph-node-ds-${dsColorEntry.name.toLowerCase()}-fill)`); - this.cssClassesService.setStyle(`body .sz-relationship-network-graph .sz-node-ds-${dsColorEntry.name.toLowerCase()} .sz-graph-node-icon .sz-graph-node-icon-fill`, "fill", `var(--sz-graph-node-ds-${dsColorEntry.name.toLowerCase()}-fill)`); + if(dsColorEntry.color !== undefined) { + this.graphContainerEle.nativeElement.style.setProperty( + `--sz-graph-node-ds-${dsColorEntry.name.toLowerCase()}-fill`, + dsColorEntry.color + ); + //this.cssClassesService.setStyle(`.sz-node-ds-${dsColorEntry.name.toLowerCase()}`, "fill", dsColorEntry.color); + this.cssClassesService.setStyle(`body .sz-relationship-network-graph .sz-node-ds-${dsColorEntry.name.toLowerCase()} .sz-graph-node-icon .sz-node-ds-${dsColorEntry.name.toLowerCase()}-fill`, "fill", `var(--sz-graph-node-ds-${dsColorEntry.name.toLowerCase()}-fill)`); + this.cssClassesService.setStyle(`body .sz-relationship-network-graph .sz-node-ds-${dsColorEntry.name.toLowerCase()} .sz-graph-node-icon .sz-graph-node-icon-fill`, "fill", `var(--sz-graph-node-ds-${dsColorEntry.name.toLowerCase()}-fill)`); + } else { + // try removing value of variable so we can unset any previous values + this.graphContainerEle.nativeElement.style.removeProperty(`--sz-graph-node-ds-${dsColorEntry.name.toLowerCase()}-fill`); + this.cssClassesService.removeStyle(`body .sz-relationship-network-graph .sz-node-ds-${dsColorEntry.name.toLowerCase()} .sz-graph-node-icon .sz-node-ds-${dsColorEntry.name.toLowerCase()}-fill`, "fill"); + this.cssClassesService.removeStyle(`body .sz-relationship-network-graph .sz-node-ds-${dsColorEntry.name.toLowerCase()} .sz-graph-node-icon .sz-graph-node-icon-fill`, "fill"); + } }) } } @@ -990,11 +1010,21 @@ export class SzGraphComponent implements OnInit, OnDestroy { } if(this.graphNetworkComponent && queryParamChanged) { // update graph with new properties - this.graphNetworkComponent.maxDegrees = this.maxDegrees; - this.graphNetworkComponent.maxEntities = this.maxEntities; - this.graphNetworkComponent.buildOut = this.buildOut; - this.graphNetworkComponent.noMaxEntitiesLimit = this.unlimitedMaxEntities; - this.graphNetworkComponent.noMaxScopeLimit = this.unlimitedMaxScope; + if(queryParametersChanged.includes('maxDegrees')){ + this.graphNetworkComponent.maxDegrees = this.maxDegrees; + } + if(queryParametersChanged.includes('maxEntities')){ + this.graphNetworkComponent.maxEntities = this.maxEntities; + } + if(queryParametersChanged.includes('buildOut')){ + this.graphNetworkComponent.buildOut = this.buildOut; + } + if(queryParametersChanged.includes('unlimitedMaxEntities')){ + this.graphNetworkComponent.noMaxEntitiesLimit = this.unlimitedMaxEntities; + } + if(queryParametersChanged.includes('unlimitedMaxScope')){ + this.graphNetworkComponent.noMaxScopeLimit = this.unlimitedMaxScope; + } if(this._graphComponentRendered){ console.log('re-rendering graph'); this.reload( this._graphIds ); @@ -1092,12 +1122,12 @@ export class SzGraphComponent implements OnInit, OnDestroy { const _ret = this.entityNodecolorsByDataSource; if( this.queriedEntitiesColor && this.queriedEntitiesColor !== undefined && this.queriedEntitiesColor !== null){ // add special color for active/primary nodes - _ret.push( { + /* _ret.push( { selectorFn: this.isEntityNodeInQuery.bind(this), modifierFn: this.setEntityNodeFillColor.bind(this, this.queriedEntitiesColor), selectorArgs: this.graphIds, modifierArgs: this.queriedEntitiesColor - } ); + } );*/ } return _ret; } diff --git a/src/lib/graph/sz-relationship-network/sz-relationship-network.component.ts b/src/lib/graph/sz-relationship-network/sz-relationship-network.component.ts index f9103ffc..82a4d230 100644 --- a/src/lib/graph/sz-relationship-network/sz-relationship-network.component.ts +++ b/src/lib/graph/sz-relationship-network/sz-relationship-network.component.ts @@ -18,7 +18,7 @@ import { } from '@senzing/rest-api-client-ng'; import { map, tap, first, takeUntil, take, filter } from 'rxjs/operators'; import { Subject, Observable, BehaviorSubject, forkJoin } from 'rxjs'; -import { parseSzIdentifier, parseBool, isValueTypeOfArray } from '../../common/utils'; +import { parseSzIdentifier, parseBool, isValueTypeOfArray, areArrayMembersEqual } from '../../common/utils'; import { SzNetworkGraphInputs, SzGraphTooltipEntityModel, SzGraphTooltipLinkModel, SzGraphNodeFilterPair, SzEntityNetworkMatchKeyTokens } from '../../../lib/models/graph'; import { SzSearchService } from '../../services/sz-search.service'; @@ -426,12 +426,15 @@ export class SzRelationshipNetworkComponent implements AfterViewInit, OnDestroy this._entityIds = [ value.toString() ]; } else if(value && isValueTypeOfArray(value)) { // passed string[] or number[]. - // we need to always convert to "string[]" or else the - // result wont be what we expect - let _tempArr = (value as unknown as string[]).map((val) => { return val.toString(); }); - _changed = this._entityIds != _tempArr; - this._entityIds = _tempArr; - //console.log(`entityIds = ${value}(any[]) | ${_changed}`, this._entityIds, value, (value as unknown as [])); + _changed = !areArrayMembersEqual((value as unknown as string[]), this._entityIds); + this._entityIds = (value as unknown as string[]).map((val) => { return val.toString(); }); + + /*console.log(`entityIds = ${value}(any[]) | ${_changed} from "${_oldIds && _oldIds.join ? _oldIds.join(',') : ''}"`, + _oldIds, + this._entityIds, + new Map( (value as unknown as string[]).map((val) => { return [val.toString(), val]; })), + (value as unknown as []) + );*/ } else if(value) { // unknown type of value // I guess we just.... guess?? @@ -444,7 +447,7 @@ export class SzRelationshipNetworkComponent implements AfterViewInit, OnDestroy return this._focalEntities.indexOf(parseSzIdentifier(eId)) <= -1; }).map(parseSzIdentifier) : []; this._focalEntities = this._focalEntities.concat(uniqueEntityIds); - if(this.reloadOnIdChange && this._entityIds && this._entityIds.some( (eId) => { return _oldIds && _oldIds.indexOf(eId) < 0; })) { + if(this.reloadOnIdChange && _changed && this._entityIds && this._entityIds.some( (eId) => { return _oldIds && _oldIds.indexOf(eId) < 0; })) { this.reload( this._entityIds.map((eId) => { return parseInt(eId); }) ); } //console.log('sdk-graph-components/sz-relationship-network.component: entityIds setter( '+_changed+' )', this._entityIds); diff --git a/src/lib/scss/graph.scss b/src/lib/scss/graph.scss index 6a1350c2..6e2015d8 100644 --- a/src/lib/scss/graph.scss +++ b/src/lib/scss/graph.scss @@ -130,6 +130,12 @@ body { fill: var(--sz-graph-node-icon-color); } + &.sz-graph-primary-node { + .sz-graph-icon-enclosure { + fill: var(--sz-graph-primary-entity-color); + } + } + &.graph-node-rel-primary { .sz-graph-node-icon-colored { fill: var(--sz-graph-node-icon-color); diff --git a/src/lib/services/sz-css-class.service.ts b/src/lib/services/sz-css-class.service.ts index 7af9891c..e971cb9a 100644 --- a/src/lib/services/sz-css-class.service.ts +++ b/src/lib/services/sz-css-class.service.ts @@ -65,7 +65,7 @@ export class SzCSSClassService { this.headElement.appendChild(cssEle); return cssEle.sheet as CSSStyleSheet; } - /** dynamicall set/create a css class and it's values */ + /** dynamically set/create a css class and it's values */ public setStyle(selectorText: string, styleName: string, value: string): void { let rules: CSSRuleList = this.styleSheet.cssRules.length > 0 || this.styleSheet.rules.length == 0 ? this.styleSheet.cssRules : this.styleSheet.rules; let ruleIndex: number = Array.from(rules).findIndex(r => r instanceof CSSStyleRule && r.selectorText.toLowerCase() == selectorText.toLowerCase()); @@ -78,10 +78,27 @@ export class SzCSSClassService { let newRuleIndex = this.styleSheet.insertRule(selectorText + `{ ${styleName}: ${value}}`, rules.length); return; } else { - this.styleSheet.deleteRule(ruleIndex); + if(ruleIndex >= 0) { + this.styleSheet.deleteRule(ruleIndex); + } this.styleSheet.insertRule(selectorText + `{ ${styleName}: ${value}}`, rules.length); } } + /** dynamically remove a css class by selector and */ + public removeStyle(selectorText: string, styleName?: string) { + if(!this.styleSheet){ return; } + let rules: CSSRuleList = this.styleSheet.cssRules.length > 0 || this.styleSheet.rules.length == 0 ? this.styleSheet.cssRules : this.styleSheet.rules; + let ruleIndex: number = Array.from(rules).findIndex(r => r instanceof CSSStyleRule && r.selectorText.toLowerCase() == selectorText.toLowerCase()); + if(ruleIndex >= 0){ + //try{ + if(!styleName) { + this.styleSheet.deleteRule(ruleIndex); + } else { + (this.styleSheet.cssRules[ruleIndex] as CSSStyleRule).style.removeProperty(styleName); + } + //} catch(err) {} + } + } /** dynamically set a css variable on the body element */ public setVariable(variableName: string, value: string) { if(this.bodyElement && this.bodyElement.style && this.bodyElement.style.setProperty) {