module Gargantext.Components.GraphExplorer.Controls ( Controls , useGraphControls , controls , controlsCpt , getShowTree, setShowTree , getShowControls, setShowControls ) where import Data.Array as A import Data.Int as I import Data.Maybe (Maybe(..), maybe) import Data.Sequence as Seq import Data.Set as Set import Data.Tuple (fst, snd) import Data.Tuple.Nested ((/\)) import Effect (Effect) import Effect.Timer (setTimeout) import Prelude import Reactix as R import Reactix.DOM.HTML as RH import Gargantext.Components.Graph as Graph import Gargantext.Components.GraphExplorer.Button (centerButton) import Gargantext.Components.GraphExplorer.RangeControl (edgeConfluenceControl, edgeWeightControl, nodeSizeControl) import Gargantext.Components.GraphExplorer.Search (nodeSearchControl) import Gargantext.Components.GraphExplorer.SlideButton (labelSizeButton, mouseSelectorSizeButton) import Gargantext.Components.GraphExplorer.ToggleButton (multiSelectEnabledButton, edgesToggleButton, louvainToggleButton, pauseForceAtlasButton) import Gargantext.Components.GraphExplorer.Types as GET import Gargantext.Hooks.Sigmax as Sigmax import Gargantext.Hooks.Sigmax.Types as SigmaxT import Gargantext.Utils.Range as Range import Gargantext.Utils.Reactix as R2 type Controls = ( edgeConfluence :: R.State Range.NumberRange , edgeWeight :: R.State Range.NumberRange , forceAtlasState :: R.State SigmaxT.ForceAtlasState , graph :: SigmaxT.SGraph , graphStage :: R.State Graph.Stage , multiSelectEnabled :: R.State Boolean , nodeSize :: R.State Range.NumberRange , removedNodeIds :: R.State SigmaxT.NodeIds , selectedNodeIds :: R.State SigmaxT.NodeIds , showControls :: R.State Boolean , showEdges :: R.State SigmaxT.ShowEdgesState , showLouvain :: R.State Boolean , showSidePanel :: R.State GET.SidePanelState , showTree :: R.State Boolean , sigmaRef :: R.Ref Sigmax.Sigma ) type LocalControls = ( labelSize :: R.State Number , mouseSelectorSize :: R.State Number ) initialLocalControls :: R.Hooks (Record LocalControls) initialLocalControls = do labelSize <- R.useState' 14.0 mouseSelectorSize <- R.useState' 15.0 pure $ { labelSize , mouseSelectorSize } controls :: Record Controls -> R.Element controls props = R.createElement controlsCpt props [] controlsCpt :: R.Component Controls controlsCpt = R.hooksComponent "GraphControls" cpt where cpt props _ = do localControls <- initialLocalControls -- ref to track automatic FA pausing -- If user pauses FA before auto is triggered, clear the timeoutId mFAPauseRef <- R.useRef Nothing -- When graph is changed, cleanup the mFAPauseRef so that forceAtlas -- timeout is retriggered. R.useEffect' $ do case fst props.graphStage of Graph.Init -> R.setRef mFAPauseRef Nothing _ -> pure unit -- Handle case when FA is paused from outside events, eg. the automatic timer. R.useEffect' $ Sigmax.handleForceAtlas2Pause props.sigmaRef props.forceAtlasState mFAPauseRef -- Handle automatic edge hiding when FA is running (to prevent flickering). R.useEffect2' props.sigmaRef props.forceAtlasState $ snd props.showEdges $ SigmaxT.forceAtlasEdgeState (fst props.forceAtlasState) -- Automatic opening of sidebar when a node is selected (but only first time). R.useEffect' $ do if fst props.showSidePanel == GET.InitialClosed && (not Set.isEmpty $ fst props.selectedNodeIds) then snd props.showSidePanel $ \_ -> GET.Opened else pure unit -- Timer to turn off the initial FA. This is because FA eats up lot of -- CPU, has memory leaks etc. R.useEffect1' (fst props.forceAtlasState) $ do if (fst props.forceAtlasState) == SigmaxT.InitialRunning then do timeoutId <- setTimeout 2000 $ do let (toggled /\ setToggled) = props.forceAtlasState case toggled of SigmaxT.InitialRunning -> setToggled $ const SigmaxT.Paused _ -> pure unit R.setRef mFAPauseRef Nothing R.setRef mFAPauseRef $ Just timeoutId pure unit else pure unit let edgesConfluenceSorted = A.sortWith (_.confluence) $ Seq.toUnfoldable $ SigmaxT.graphEdges props.graph let edgeConfluenceMin = maybe 0.0 _.confluence $ A.head edgesConfluenceSorted let edgeConfluenceMax = maybe 100.0 _.confluence $ A.last edgesConfluenceSorted let edgeConfluenceRange = Range.Closed { min: edgeConfluenceMin, max: edgeConfluenceMax } --let edgesWeightSorted = A.sortWith (_.weight) $ Seq.toUnfoldable $ SigmaxT.graphEdges props.graph --let edgeWeightMin = maybe 0.0 _.weight $ A.head edgesWeightSorted --let edgeWeightMax = maybe 100.0 _.weight $ A.last edgesWeightSorted --let edgeWeightRange = Range.Closed { min: edgeWeightMin, max: edgeWeightMax } let edgeWeightRange = Range.Closed { min: 0.0 , max: I.toNumber $ Seq.length $ SigmaxT.graphEdges props.graph } let nodesSorted = A.sortWith (_.size) $ Seq.toUnfoldable $ SigmaxT.graphNodes props.graph let nodeSizeMin = maybe 0.0 _.size $ A.head nodesSorted let nodeSizeMax = maybe 100.0 _.size $ A.last nodesSorted let nodeSizeRange = Range.Closed { min: nodeSizeMin, max: nodeSizeMax } pure $ case getShowControls props of false -> RH.div {} [] true -> RH.div { className: "col-md-12", style: { paddingBottom: "10px" } } [ R2.menu { id: "toolbar" } [ RH.ul {} [ -- change type button (?) RH.li {} [ centerButton props.sigmaRef ] , RH.li {} [ pauseForceAtlasButton {state: props.forceAtlasState} ] , RH.li {} [ edgesToggleButton {state: props.showEdges} ] , RH.li {} [ louvainToggleButton props.showLouvain ] , RH.li {} [ edgeConfluenceControl edgeConfluenceRange props.edgeConfluence ] , RH.li {} [ edgeWeightControl edgeWeightRange props.edgeWeight ] -- change level -- file upload -- run demo -- search button -- search topics , RH.li {} [ labelSizeButton props.sigmaRef localControls.labelSize ] -- labels size: 1-4 , RH.li {} [ nodeSizeControl nodeSizeRange props.nodeSize ] -- zoom: 0 -100 - calculate ratio , RH.li {} [ multiSelectEnabledButton props.multiSelectEnabled ] -- toggle multi node selection -- save button , RH.li {} [ nodeSearchControl { graph: props.graph , multiSelectEnabled: props.multiSelectEnabled , selectedNodeIds: props.selectedNodeIds } ] , RH.li {} [ mouseSelectorSizeButton props.sigmaRef localControls.mouseSelectorSize ] ] ] ] useGraphControls :: SigmaxT.SGraph -> R.Hooks (Record Controls) useGraphControls graph = do edgeConfluence <- R.useState' $ Range.Closed { min: 0.0, max: 1.0 } edgeWeight <- R.useState' $ Range.Closed { min: 0.0 , max: I.toNumber $ Seq.length $ SigmaxT.graphEdges graph } forceAtlasState <- R.useState' SigmaxT.InitialRunning graphStage <- R.useState' Graph.Init multiSelectEnabled <- R.useState' false nodeSize <- R.useState' $ Range.Closed { min: 0.0, max: 100.0 } removedNodeIds <- R.useState' SigmaxT.emptyNodeIds selectedNodeIds <- R.useState' SigmaxT.emptyNodeIds showControls <- R.useState' false showEdges <- R.useState' SigmaxT.EShow showLouvain <- R.useState' false showSidePanel <- R.useState' GET.InitialClosed showTree <- R.useState' false sigma <- Sigmax.initSigma sigmaRef <- R.useRef sigma pure { edgeConfluence , edgeWeight , forceAtlasState , graph , graphStage , multiSelectEnabled , nodeSize , removedNodeIds , selectedNodeIds , showControls , showEdges , showLouvain , showSidePanel , showTree , sigmaRef } getShowControls :: Record Controls -> Boolean getShowControls { showControls: ( should /\ _ ) } = should getShowTree :: Record Controls -> Boolean getShowTree { showTree: ( should /\ _ ) } = should setShowControls :: Record Controls -> Boolean -> Effect Unit setShowControls { showControls: ( _ /\ set ) } v = set $ const v setShowTree :: Record Controls -> Boolean -> Effect Unit setShowTree { showTree: ( _ /\ set ) } v = set $ not <<< const v