import { Component , AfterViewInit , ViewChild , ElementRef } from '@angular/core' ; import { DataproviderService } from 'src/app/shared/services/dataprovider.service' ; import { Configuration } from 'src/app/shared/services/configuration.service' ; import { Glyph } from 'src/app/glyph/glyph' ; import { FlowerGlyph } from 'src/app/glyph/glyph.flower' ; import { GlyphConfiguration } from 'src/app/glyph/glyph.configuration' ; import { StarGlyph } from 'src/app/glyph/glyph.star' ; import { LenseCursor } from './cursor.service' ; import { TooltipComponent } from 'src/app/tooltip/tooltip.component' ; import { Logger } from 'src/app/shared/services/logger.service' ; import { Helper } from 'src/app/glyph/glyph.helper' ; import * as d3 from 'd3' ; import { FlowerGlyphConfiguration } from 'src/app/glyph/glyph.flower.configuration' ; import { StarGlyphConfiguration } from 'src/app/glyph/glyph.star.configuration' ; import { GlyphType } from 'src/app/glyph/glyph.type' ; import { DotGlyphConfiguration } from '../glyph/glyph.dot.configuration' ; import { DotGlyph } from '../glyph/glyph.dot' ; @ Component ( { selector : 'app-lense' , templateUrl : './lense.component.html' , styleUrls : [ './lense.component.css' ] } ) export class MagicLenseComponent implements AfterViewInit { @ ViewChild ( 'canvas' ) canvas : ElementRef | undefined ; @ ViewChild ( 'secondary' ) splitCanvas : ElementRef | undefined ; @ ViewChild ( 'tooltip' ) public tooltip : TooltipComponent | undefined ; private data : any ; private dataSecondary : any ; private visible : any [ ] = [ ] ; private visibleSecondary : any ; public cursorWidth = 50 ; public cursorHeight = 50 ; private lensDiameter = 300 ; private width : number = window . innerWidth ; private height : number = window . innerHeight ; private context : any ; private splitContext : any ; private flowerGlyph : FlowerGlyph ; private starGlyph : StarGlyph ; private activeGlyph : Glyph ; private activeConfig : GlyphConfiguration ; private simulation : any ; private secondarySimulation : any ; private secondarySimulationActive = true ; param { MagicLenseComponent } component return { void } static ticked ( component ) { if ( component . data == null && component . dataSecondary == null ) { return ; } component . draw ( component . context , component . visible , component . data . features , component . configuration . configurations [ 0 ] , true ) ; component . simulation . alpha ( 1 ) ; if ( component . dataSecondary != null ) { component . draw ( component . splitContext , component . visibleSecondary , component . dataSecondary . features , component . configuration . configurations [ 1 ] , false ) ; if ( component . secondarySimulationActive ) { component . secondarySimulation . alpha ( 1 ) ; } ; } if ( component . cursor . updateGlyphs || component . cursor . forceAnimateGlyphs ) { component . cursor . updateGlyphs = false ; component . visibleGlyphs ( ) ; component . simulation . nodes ( component . visible . map ( item => item . position ) ) . restart ( ) ; if ( component . dataSecondary != null && component . secondarySimulationActive ) { component . secondarySimulation . nodes ( component . visibleSecondary . map ( ( item ) => item . position ) ) . restart ( ) ; } } } constructor ( public cursor , private dataProvider , private logger , private configuration , private helper ) { this . activeGlyph = new FlowerGlyph ( 0 , 0 , new FlowerGlyphConfiguration ) ; this . starGlyph = new StarGlyph ( 0 , 0 , new StarGlyphConfiguration ) ; this . flowerGlyph = new FlowerGlyph ( 0 , 0 , new FlowerGlyphConfiguration ) ; this . activeConfig = new FlowerGlyphConfiguration ; } ngAfterViewInit ( ) { const that = this ; const accessorScale = d3 . scaleLinear ( ) . range ( [ 0 , 100 ] ) ; this . configuration . configurations . forEach ( ( config , i ) => { config . getData ( ) . subscribe ( message => { if ( ! message ) { return ; } if ( i === 0 ) { let maxValue ; const colorFeature = message . schema . color ; const colorScale = ( item ) => config . color ( + item [ colorFeature ] ) ; const accessors = [ ] ; this . data = message ; this . data . schema . glyph . forEach ( ( feat ) => { maxValue = 0 ; this . data . features . forEach ( ( item ) => { const v = + item . features [ feat ] ; maxValue = ( v > maxValue ) ? v : maxValue ; } ) ; accessorScale . domain ( [ 0 , maxValue ] ) ; accessors . push ( ( d ) => accessorScale ( d [ feat ] ) ) ; } ) ; var secondaryConfig = new StarGlyphConfiguration ; secondaryConfig = this . configuration . starConfigs [ 1 ] . clone ( ) as StarGlyphConfiguration ; secondaryConfig . useBackground = true ; this . flowerGlyph = new FlowerGlyph ( this . context , colorScale , this . configuration . flowerConfigs [ 1 ] as FlowerGlyphConfiguration ) ; this . starGlyph = new StarGlyph ( this . context , colorScale , secondaryConfig ) ; this . activeGlyph = this . flowerGlyph ; } else if ( i === 1 ) { this . dataSecondary = message ; } } ) ; } ) ; this . context = this . canvas ?. nativeElement . getContext ( '2d' ) ; this . splitContext = this . splitCanvas ?. nativeElement . getContext ( '2d' ) ; this . splitContext . canvas . height = this . height ; this . splitContext . canvas . width = this . width / 2 ; this . simulation = d3 . forceSimulation ( ) . force ( 'collision' , d3 . forceCollide ( ) . radius ( 25 ) ) . on ( 'tick' , ( ) => { MagicLenseComponent . ticked ( that ) ; } ) ; this . secondarySimulation = d3 . forceSimulation ( ) . force ( 'collision' , d3 . forceCollide ( ) . radius ( 25 ) ) . on ( 'tick' , ( ) => { MagicLenseComponent . ticked ( that ) ; } ) ; if ( this . tooltip !== undefined ) this . tooltip . tolerance = 15 ; } public onCursorMouseMove ( e ) { if ( this . cursor . isFixed ) { return ; } this . cursor . position = { left : e . clientX , top : e . clientY } ; this . visibleGlyphs ( ) ; this . simulation . nodes ( this . visible . map ( item => item . position ) ) . restart ( ) . alpha ( 1 ) ; if ( this . secondarySimulationActive ) { this . secondarySimulation . nodes ( this . visibleSecondary . map ( ( item ) => item . position ) ) . restart ( ) . alpha ( 1 ) ; } } public onLensMouseMove ( e ) { if ( this . cursor . isFixed ) { this . updateTooltip ( e ) ; this . configuration . configurations [ 0 ] . idOfHoveredGlyph = this . tooltip ?. getClosestPointId ; this . configuration . configurations [ 1 ] . idOfHoveredGlyph = this . tooltip ?. getClosestPointId ; } } public onLensMouseEnter ( e ) { if ( this . tooltip !== undefined ) this . tooltip . data = { positions : this . visible , features : this . data . features , schema : this . data . schema } ; } public onLensMouseOut ( e ) { if ( this . tooltip !== undefined ) this . tooltip . data = this . data ; } public onCursorClick ( e ) { this . cursor . isFixed = ! this . cursor . isFixed ; if ( ! this . cursor . isFixed && this . tooltip !== undefined ) { this . tooltip . isVisible = false ; } } public onWindowResize ( e ) { this . width = window . innerWidth ; this . height = window . innerHeight ; this . splitContext . canvas . height = this . height ; this . splitContext . canvas . width = this . width / 2 ; } param ctx return { void } private draw ( ctx , positions , features , config , primary_lens ) { if ( ! this . cursor . isVisible && ! this . cursor . display ) { return ; } const l = this . lensDiameter ; const scale = 1 ; let itemContext ; switch ( this . configuration . activeGlyphType ) { case GlyphType . Flower : this . activeGlyph = this . flowerGlyph ; this . activeConfig = this . configuration . flowerConfigs [ 1 ] ; if ( ! primary_lens ) { ( this . activeConfig as FlowerGlyphConfiguration ) . useArea = true ; } break ; case GlyphType . Star : this . activeGlyph = this . starGlyph ; this . activeConfig = this . configuration . starConfigs [ 1 ] ; if ( ! primary_lens ) { ( this . activeConfig as StarGlyphConfiguration ) . useCoordinateSystem = true ; ( this . activeConfig as StarGlyphConfiguration ) . useAbsoluteAxes = true ; } break ; default : return ; } this . activeGlyph . context = ctx ; ctx . save ( ) ; ctx . clearRect ( 0 , 0 , this . width / 2 , this . height ) ; if ( primary_lens ) { const posX = - this . cursor . position . left * scale + ( scale * l ) / 2 ; const posY = - this . cursor . position . top * scale + ( scale * l ) / 2 ; ctx . translate ( posX , posY ) ; ctx . scale ( scale , scale ) ; } const visible = ( this . visible || [ ] ) ; const temp = this . activeConfig . radius ; this . activeConfig . radius = 25 ; if ( ! visible . length ) { return ; } const visibleIds = this . visible . map ( item => item . id ) ; const visibleFeatures = features . filter ( item => visibleIds . indexOf ( item . id ) !== - 1 ) ; positions . forEach ( d => { const currentFeatures = visibleFeatures . find ( item => item . id === d . id ) ; ctx . beginPath ( ) ; if ( currentFeatures != null ) { itemContext = this . configuration . configurations [ 0 ] . individualFeatureContexts [ d . id ] ; if ( itemContext === undefined ) { if ( this . configuration . configurations [ 0 ] . globalFeatureContext >= 0 ) { itemContext = this . configuration . configurations [ 0 ] . globalFeatureContext ; } else { itemContext = currentFeatures [ 'default-context' ] ; } } const feature = currentFeatures . features [ itemContext ] ; const passive = ! ( ( this . configuration . configurations [ 0 ] . filteredItemsIds . indexOf ( d . id ) > - 1 ) || ( this . configuration . configurations [ 0 ] . featureFilters . length == 0 ) ) ; this . activeGlyph . draw ( d . position , feature , undefined , passive ) ; } } ) ; this . activeConfig . radius = temp ; switch ( this . configuration . activeGlyphType ) { case GlyphType . Flower : ( this . activeConfig as FlowerGlyphConfiguration ) . useArea = false ; break ; case GlyphType . Star : ( this . activeConfig as StarGlyphConfiguration ) . useCoordinateSystem = false ; ( this . activeConfig as StarGlyphConfiguration ) . useAbsoluteAxes = false ; break ; } ctx . restore ( ) ; ctx . setTransform ( 1 , 0 , 0 , 1 , 0 , 0 ) ; } return { any [ ] } private visibleGlyphs ( ) { const that = this ; let minX = this . cursor . position . left - this . cursorWidth / 2 ; let minY = this . cursor . position . top - this . cursorHeight / 2 ; let currentCursorWidth = this . cursorWidth ; let currentCursorHeight = this . cursorHeight ; let dx ; let dy ; function visibleFilter ( d , i ) { if ( d . position . x > minX && d . position . y > minY && d . position . x < minX + currentCursorWidth && d . position . y < minY + currentCursorHeight ) { dx = d . position . x - that . cursor . position . left ; dy = d . position . y - that . cursor . position . top ; return Math . sqrt ( dx * dx + dy * dy ) < that . cursorWidth / 2 ; } return false ; } let filterResult = this . data . positions . filter ( visibleFilter ) ; while ( filterResult . length > 75 ) { minX += 1 ; minY += 1 ; currentCursorHeight -= 1 ; currentCursorWidth -= 1 ; filterResult = this . data . positions . filter ( visibleFilter ) ; } const visiblePrimary = JSON . parse ( JSON . stringify ( filterResult ) ) ; const visibleIds = visiblePrimary . map ( ( item ) => item . id ) ; if ( this . dataSecondary != null ) { this . visibleSecondary = JSON . parse ( JSON . stringify ( this . dataSecondary . positions . filter ( ( item ) => visibleIds . indexOf ( item . id ) !== - 1 ) ) ) ; } else { this . visibleSecondary = [ ] ; } return this . visible = visiblePrimary ; } private updateTooltip ( event ) { if ( this . tooltip !== undefined ) { this . tooltip . updateClosestPoint ( { 'clientX' : event . clientX , 'clientY' : event . clientY , 'offsetX' : event . clientX - ( this . lensDiameter / 2 ) , 'offsetY' : event . clientY - ( this . lensDiameter / 2 ) } , this . context . transform ) ; this . tooltip . x = event . clientX + 4 ; this . tooltip . y = event . clientY + 4 ; } } }