import { getElementByIdOrThrow , State } from './utils' ; interface Node { readonly id : number ; readonly label : string ; readonly title : string ; group ? : string ; shape ? : string ; } interface ActionEdge { readonly id ? : number ; readonly from : number ; readonly to : number ; readonly label : string ; readonly title : string ; readonly actionName : string ; } declare namespace vis { type IdType = number | string ; class DataSet < T > { constructor ( content ) ; length : number ; add ( node ) ; get ( id ) ; get ( filter ) ; clear ( ) ; update ( nodes ) ; update ( node ) ; } export class Network { constructor ( container , treeData , options ) ; fit ( ) ; getSelectedNodes ( ) ; releaseNode ( ) ; focus ( nodeId , arg1 ) ; selectNodes ( arg0 ) ; on ( arg0 , event ) ; } } export class SearchTree { readonly network : vis . Network ; autoFitEnabled = true ; readonly nodes : vis . DataSet < Node > = new vis . DataSet ( [ ] ) ; readonly edges : vis . DataSet < ActionEdge > = new vis . DataSet ( [ ] ) ; readonly treeData = { nodes : this . nodes , edges : this . edges } ; /**
     * Creates tree
     */ constructor ( ) { const container = getElementByIdOrThrow ( 'network' ) ; const options = { autoResize : true , height : '100%' , width : '100%' , layout : { hierarchical : { direction : "UD" , sortMethod : "directed" , blockShifting : true , edgeMinimization : true , parentCentralization : true , levelSeparation : 180 , shakeTowards : "roots" } } , interaction : { dragNodes : false } , physics : { enabled : false } , configure : { enabled : false } , nodes : { color : { highlight : { background : 'red' , border : 'red' } } } , edges : { font : { align : 'top' } , arrows : { to : { enabled : true , scaleFactor : 0.5 } } } , groups : { plan : { color : { background : 'lightgreen' } , borderWidth : 0 } , deadEnd : { color : { background : 'brown' } , borderWidth : 0 } , visitedOrWorse : { color : { background : 'black' } , font : { color : 'white' } , borderWidth : 0 } , goal : { color : { background : 'green' } , borderWidth : 0 } } } ; this . network = new vis . Network ( container , this . treeData , options ) ; } toggleAutoFit ( ) { this . autoFitEnabled = ! this . autoFitEnabled ; } param newState param batch addStateToTree ( newState , batch ) { this . nodes . add ( toNode ( newState ) ) ; if ( newState . parentId !== null && newState . actionName ) { this . addStateEdge ( newState ) ; } if ( ! batch && this . nodes . length < 100 && this . autoFitEnabled ) { this . network . fit ( ) ; } } param newState addStateEdge ( newState ) { if ( ! newState . actionName || newState . parentId === undefined ) { throw new Error ( `Cannot progress _to_ the initial state.` ) ; } const undecoratedActionName = newState . actionName . replace ( /[├┤]$/ , "" ) . replace ( /\[\d+\]/ , "" ) . trim ( ) ; const actionLabel = createActionLabel ( newState . actionName ! ) ; const actionTitle = createActionTooltip ( newState . actionName ! ) ; const edge = { from : newState . parentId ! , to : newState . id , actionName : undecoratedActionName , label : actionLabel , title : actionTitle } ; this . edges . add ( edge ) ; } /**
     * Ends tree batch update.
     */ endTreeBatch ( ) { this . network . fit ( ) ; } updateStateOnTree ( state ) { this . nodes . update ( [ toNode ( state ) ] ) ; } param planStates showPlanOnTree ( planStates ) { planStates . forEach ( state => this . updateStateOnTree ( state ) ) ; const lastStateId = Math . max ( ... planStates . map ( state => state . id as number ) ) ; const lastState = planStates . find ( state => state . id === lastStateId ) ; if ( lastState ) { lastState . isGoal = true ; this . updateStateOnTree ( lastState ) ; } } param id selectTreeNode ( id ) { if ( id !== null ) { this . network . selectNodes ( [ id ] ) ; this . network . focus ( id , { animation : true , locked : false } ) ; } else { this . network . selectNodes ( [ ] ) ; this . network . releaseNode ( ) ; } } clearTree ( ) { this . nodes . clear ( ) ; this . edges . clear ( ) ; } fitTree ( ) { this . network . fit ( ) ; } returns navigateTreeUp ( ) { const selectedNodes = this . network . getSelectedNodes ( ) ; if ( selectedNodes . length === 0 ) { return null ; } const selectedNode = toIntNodeId ( selectedNodes [ 0 ] ) ; const parent = this . getParent ( selectedNode ) ; if ( parent !== null ) { return parent ; } return selectedNode ; } returns navigateTreeDown ( ) { const selectedNodes = this . network . getSelectedNodes ( ) ; if ( selectedNodes . length === 0 ) { return null ; } const selectedNode = toIntNodeId ( selectedNodes [ 0 ] ) ; const children = this . getChildren ( selectedNode ) ; if ( children . length > 0 ) { return children [ 0 ] ; } return selectedNode ; } param { int } offset returns navigateTreeSiblings ( offset ) { const selectedNodes = this . network . getSelectedNodes ( ) ; if ( selectedNodes . length === 0 ) { return null ; } const selectedNode = toIntNodeId ( selectedNodes [ 0 ] ) ; const siblings = this . getSiblingsIncludingSelf ( selectedNode ) ; const indexOfThisNodeAmongSiblings = siblings . indexOf ( selectedNode ) ; const newSelectedNodeIndex = indexOfThisNodeAmongSiblings + offset ; if ( newSelectedNodeIndex > - 1 && newSelectedNodeIndex < siblings . length ) { return siblings [ newSelectedNodeIndex ] ; } return selectedNode ; } param childId getParent ( childId ) { const parents = this . edges . get ( { filter : function ( edge ) { return edge . to === childId ; } } ) ; if ( parents . length > 0 ) { return parents [ 0 ] . from ; } else { return null ; } } param parentId returns getChildren ( parentId ) { const edgesFromParent = this . edges . get ( { filter : function ( edge ) { return edge . from === parentId ; } } ) ; const children = edgesFromParent . map ( ( e ) => e . to ) . filter ( e => ! ! e ) . map ( e => toIntNodeId ( e ! ) ) ; return children ; } param nodeId returns getSiblingsIncludingSelf ( nodeId ) { const parent = this . getParent ( nodeId ) ; if ( parent === null ) { return [ ] ; } const siblings = this . getChildren ( parent ) ; return siblings ; } param parentStateId param actionName returns navigateToChildState ( parentStateId , actionName ) { const edgesFromParent = this . edges . get ( { filter : function ( edge ) { return edge . from === parentStateId && edge . actionName === actionName ; } } ) ; if ( edgesFromParent . length > 0 ) { return edgesFromParent [ 0 ] . to ; } else { return null ; } } param shape changeSelectedNodeShape ( shape ) { const selectedNodes = this . network . getSelectedNodes ( ) ; if ( selectedNodes . length === 0 ) { return ; } const selectedNodeId = selectedNodes [ 0 ] ; const selectedNode = this . nodes . get ( selectedNodeId ) ; if ( selectedNode ) { ( selectedNode as Node ) . shape = shape ; this . nodes . update ( selectedNode ) ; } } } const snapActionLegend = new Map < string , string > ( ) ; snapActionLegend . set ( '├' , "Action start" ) ; snapActionLegend . set ( '┤' , "Action end" ) ; param fullActionName returns function createActionTooltip ( fullActionName ) { let counter = 0 ; const counterMatch = fullActionName . match ( /\[(\d+)\]/ ) ; if ( counterMatch ) { counter = parseInt ( counterMatch [ 1 ] ) ; } let tooltip = fullActionName . replace ( /\[\d+\]/ , "" ) . replace ( /[├┤]$/ , " $&" ) . split ( ' ' ) . filter ( fragment => fragment !== ' ' ) . map ( fragment => fragment in snapActionLegend ? snapActionLegend . get ( fragment ) : fragment ) . join ( '\n' ) ; if ( counter > 0 ) { tooltip += '\nShot counter: ' + counter ; } return tooltip ; } param fullActionName returns function createActionLabel ( fullActionName ) { const fragments = fullActionName . replace ( /\[\d+\]/ , "" ) . split ( ' ' ) ; const maxLineLength = 19 ; const label = fragments . reduce ( ( prevValue , currentValue ) => { const separator = prevValue . length + currentValue . length > maxLineLength ? '\n' : ' ' ; return prevValue + separator + currentValue ; } ) ; return label ; } param newState function toNode ( newState ) { const label = toNodeLabel ( newState ) ; const title = toNodeTitle ( newState ) ; const node = { id : newState . id , label : label , title : title } ; if ( newState . wasVisitedOrIsWorse ) { node . group = 'visitedOrWorse' ; } if ( newState . isPlan ) { node . group = 'plan' ; } if ( newState . h !== undefined ) { if ( newState . isDeadEnd ) { node . group = 'deadEnd' ; } } if ( newState . isGoal ) { node . group = 'goal' ; } return node ; } param newState function toNodeLabel ( newState ) { let label = 'O: ' + newState . id ; if ( newState . h !== undefined ) { label += '\nH: ' + ( newState . isDeadEnd ? 'Infinite' : newState . h ) ; } return label ; } param newState function toNodeTitle ( newState ) { let title = 'Order: ' + newState . id ; if ( newState . id . toString ( ) !== newState . origId ) { title += ' (' + newState . origId + ')' ; } title += '\nGeneration: ' + newState . g + '\nEarliest time: ' + newState . earliestTime ; if ( newState . landmarks !== undefined ) { title += '\nLandmarks: ' + newState . landmarks ; } if ( newState . wasVisitedOrIsWorse ) { title += '\nState was visited or is worse than a previously visited state' ; } if ( newState . h !== undefined ) { title += '\nHeuristic value: ' + ( newState . isDeadEnd ? 'Infinite' : newState . h ) ; } if ( newState . totalMakespan ) { title += '\nTotal makespan: ' + newState . totalMakespan ; } if ( newState . helpfulActions ) { title += '\nHelpful actions: ' + newState . helpfulActions . length ; } return title ; } function toIntNodeId ( nodeId ) { return nodeId as number ; }