import "powerbi-visuals-tools/templates/visuals/.api/v1.11.0/PowerBI-visuals" ; import { default as NetworkNavigatorImpl , INetworkNavigatorData , INetworkNavigatorNode } from "@essex/network-navigator" ; import { INetworkNavigatorSelectableNode } from "./models" ; import { UpdateType , receiveDimensions , IDimensions , calcUpdateType } from "@essex/visual-utils" ; import { filter , dataview } from "../powerbi-visuals-utils" ; import converter from "./dataConversion" ; import IVisualHost = powerbi . extensibility . visual . IVisualHost ; import VisualUpdateOptions = powerbi . extensibility . visual . VisualUpdateOptions ; import VisualConstructorOptions = powerbi . extensibility . visual . VisualConstructorOptions ; import VisualObjectInstance = powerbi . VisualObjectInstance ; import EnumerateVisualObjectInstancesOptions = powerbi . EnumerateVisualObjectInstancesOptions ; import SelectionId = powerbi . visuals . ISelectionId ; import NetworkNavigatorState from "./state" ; import * as $ from "jquery" ; import debounce = require ( "lodash.debounce" ) ; import get = require ( "lodash.get" ) ; const MY_CSS_MODULE = require ( "./css/NetworkNavigatorVisual.scss" ) ; const EVENTS_TO_IGNORE = "mousedown mouseup click focus blur input pointerdown pointerup touchstart touchmove touchdown" ; import { DATA_ROLES } from "./constants" ; /**
 * A visual which supports the displaying of graph based datasets in power bi
 */ @ receiveDimensions export default class NetworkNavigator implements powerbi . extensibility . visual . IVisual { /**
     * My network navigator instance
     */ public myNetworkNavigator : NetworkNavigatorImpl ; /**
     * Whether or not css needs loaded
     */ protected noCss : boolean ; /**
     * The visual's host
     */ private host : IVisualHost ; /**
     * This visuals element
     */ private element : JQuery ; /**
     * The selection changed listener for NetworkNavigator
     */ private selectionChangedListener : { destroy : Function ; } ; /**
     * The selection manager, used to sync selection with PowerBI
     */ private selectionManager : powerbi . extensibility . ISelectionManager ; /**
     * The internal state of the network navigator
     */ private _internalState : NetworkNavigatorState ; /**
     * The list of nodes loaded into the network navigator
     */ private _nodes : INetworkNavigatorNode [ ] ; /**
     * The currently loaded dataView
     */ private _dataView : powerbi . DataView ; /**
     * The previous update options
     */ private prevUpdateOptions : powerbi . extensibility . visual . VisualUpdateOptions ; /**
     * A debounced event listener for when a node is selected through NetworkNavigator
     */ private onNodeSelected = debounce ( ( node ) => { this . persistNodeSelection ( node as INetworkNavigatorSelectableNode ) ; } , 100 ) ; constructor ( options , noCss = false ) { this . noCss = noCss ; this . host = options . host ; this . element = $ ( `<div style="height: 100%;"></div>` ) ; this . selectionManager = options . host . createSelectionManager ( ) ; options . element . appendChild ( this . element [ 0 ] ) ; this . selectionManager = this . host . createSelectionManager ( ) ; const className = MY_CSS_MODULE && MY_CSS_MODULE . locals && MY_CSS_MODULE . locals . className ; if ( className ) { $ ( options . element ) . append ( $ ( "<st" + "yle>" + MY_CSS_MODULE + "</st" + "yle>" ) ) ; this . element . addClass ( className ) ; } this . _internalState = NetworkNavigatorState . create < NetworkNavigatorState > ( ) ; this . myNetworkNavigator = new NetworkNavigatorImpl ( this . element , 500 , 500 ) ; this . attachEvents ( ) ; } param options param vm param type public update ( options , vm ? , type ? ) { const updateType = type !== undefined ? type : calcUpdateType ( this . prevUpdateOptions , options ) ; this . prevUpdateOptions = options ; const dataView = options . dataViews && options . dataViews . length && options . dataViews [ 0 ] ; this . _dataView = dataView ; const dataViewTable = dataView && dataView . table ; let forceReloadData = false ; if ( ( updateType & UpdateType . Settings ) === UpdateType . Settings ) { forceReloadData = this . loadSettingsFromPowerBI ( dataView ) ; } if ( ( ( updateType & UpdateType . Data ) === UpdateType . Data ) || forceReloadData ) { if ( dataViewTable ) { const filterColumn = dataView . metadata . columns . filter ( n => n . roles [ DATA_ROLES . filterField . name ] ) [ 0 ] ; const newData = converter ( dataView , this . _internalState , filterColumn , ( ) => this . host . createSelectionIdBuilder ( ) ) ; this . myNetworkNavigator . setData ( newData ) ; } else { this . myNetworkNavigator . setData ( { links : [ ] , nodes : [ ] , } ) ; } } this . loadSelectionFromPowerBI ( ) ; this . myNetworkNavigator . redrawLabels ( ) ; } param dim public setDimensions ( dim ) { if ( this . myNetworkNavigator ) { this . myNetworkNavigator . dimensions = { width : dim . width , height : dim . height } ; } if ( this . element ) { this . element . css ( { width : dim . width , height : dim . height } ) ; } } /**
     * Destroys the visual
     */ public destroy ( ) { this . element . empty ( ) ; } /**
     * Enumerates the instances for the objects (settings) that appear in the power bi panel
     */ public enumerateObjectInstances ( options ) { return this . _internalState . buildEnumerationObjects ( options . objectName , this . _dataView , false ) ; } /**
     * Persists the given node as the seelcted node
     */ protected persistNodeSelection ( node ) { const filterToApply = node && node . filter ; let hasConditions = false ; if ( filterToApply && filterToApply [ "values" ] ) { hasConditions = filterToApply [ "values" ] . length > 0 ; } else if ( filterToApply && filterToApply [ "conditions" ] ) { hasConditions = filterToApply [ "conditions" ] . length > 0 ; } const action = hasConditions ? powerbi . FilterAction . merge : powerbi . FilterAction . remove ; this . host . applyJsonFilter ( filterToApply , "general" , "filter" , action ) ; } /**
     * Loads the selection state from powerbi
     */ private loadSelectionFromPowerBI ( ) { const data = this . myNetworkNavigator . getData ( ) as INetworkNavigatorData < INetworkNavigatorNode > ; const nodes = data && data . nodes ; if ( nodes && nodes . length ) { this . _nodes = nodes ; const filterValues = getFilterValues ( this . _dataView , "general.filter" ) ; const valueMap = ( filterValues || [ ] ) . reduce ( ( acc , cur ) => { acc [ cur ] = 1 ; return acc ; } , { } ) ; let selectedNode ; nodes . forEach ( ( n ) => { const isSelected = ! ! valueMap [ pretty ( n . name ) ] ; n . selected = isSelected ; if ( isSelected ) { selectedNode = n ; } } ) ; this . myNetworkNavigator . selectedNode = selectedNode ; } } param ids param forceManual private setSelectionOnSelectionManager ( ids , forceManual = false ) { const selectIds = ( ) => { if ( ids . length > 0 && this . selectionManager . select ) { this . selectionManager . select ( ids ) ; } else if ( this . selectionManager . clear ) { this . selectionManager . clear ( ) ; } } ; if ( ! forceManual && this . selectionManager [ "setSelectionIds" ] ) { this . selectionManager [ "setSelectionIds" ] ( ids ) ; } else if ( ! forceManual && this . selectionManager [ "selectedIds" ] ) { this . selectionManager [ "selectedIds" ] = ids ; } else { selectIds ( ) ; } } param dataView returns private loadSettingsFromPowerBI ( dataView ) { const oldState = this . _internalState ; this . _internalState = this . _internalState . receiveFromPBI ( dataView ) ; this . myNetworkNavigator . configuration = this . _internalState ; return oldState . maxNodeCount !== this . _internalState . maxNodeCount || oldState . labels !== this . _internalState . labels ; } /**
     * Attaches the event listeners to the network navigator
     */ private attachEvents ( ) { if ( this . myNetworkNavigator ) { if ( this . selectionChangedListener ) { this . selectionChangedListener . destroy ( ) ; } const dispatcher = this . myNetworkNavigator . events ; this . selectionChangedListener = dispatcher . on ( "selectionChanged" , ( node ) => this . onNodeSelected ( node ) ) ; dispatcher . on ( "zoomed" , ( { scale , translate } ) => { this . _internalState = this . _internalState . receive ( { scale , translate } ) ; } ) ; dispatcher . on ( "textFilter" , ( textFilter ) => { this . _internalState = this . _internalState . receive ( { textFilter } ) ; const label = textFilter && textFilter !== "" ? `Filtered ${ textFilter }` : `Cleared text filter` ; } ) ; this . element . find ( ".filter-box input" ) . on ( EVENTS_TO_IGNORE , ( e ) => e . stopPropagation ( ) ) ; } } } param dv param filterPath function getFilterValues ( dv , filterPath ) { const savedFilter = get ( dv , `metadata.objects.${ filterPath }` ) ; if ( savedFilter ) { const appliedFilter = filter . FilterManager . restoreFilter ( savedFilter ) ; if ( appliedFilter ) { const conditions = get ( appliedFilter , "conditions" , get ( appliedFilter , "values" , [ ] ) ) ; return conditions . map ( ( n ) => { let text = pretty ( n ) ; if ( n && n . splice ) { text = pretty ( n [ 0 ] . value ) ; } else if ( n && ( n . value !== undefined && n . value !== null ) ) { text = pretty ( n . value ) ; } return text ; } ) ; } } return [ ] ; } param val function pretty ( val ) { if ( val === null || val === undefined ) { return "" ; } return val + "" ; }