diff --git a/src/Gargantext/Components/Graph.purs b/src/Gargantext/Components/Graph.purs index eef3e31df0d29062a607c32c5d26402d59329290..df4b76f1c57c348e3235b8c359610fcbbc66f329 100644 --- a/src/Gargantext/Components/Graph.purs +++ b/src/Gargantext/Components/Graph.purs @@ -32,9 +32,10 @@ type Props sigma forceatlas2 = , graph :: Graph , selectedEdgeIds :: R.State SigmaxTypes.SelectedEdgeIds , selectedNodeIds :: R.State SigmaxTypes.SelectedNodeIds - , sigmaSettings :: sigma , sigmaRef :: R.Ref Sigmax.Sigma + , sigmaSettings :: sigma , stage :: R.State Stage + , transformedGraph :: Graph ) graph :: forall s fa2. Record (Props s fa2) -> R.Element @@ -91,14 +92,14 @@ graphCpt = R.hooksComponent "Graph" cpt pure $ pure unit stageHooks props@{stage: (Ready /\ setStage)} = do - let edgesMap = SigmaxTypes.edgesGraphMap props.graph - let nodesMap = SigmaxTypes.nodesGraphMap props.graph + let tEdgesMap = SigmaxTypes.edgesGraphMap props.transformedGraph + let tNodesMap = SigmaxTypes.nodesGraphMap props.transformedGraph -- TODO Probably this can be optimized to re-mark selected nodes only when they changed R.useEffect' $ do Sigmax.dependOnSigma (R.readRef props.sigmaRef) "[graphCpt] no sigma" $ \sigma -> do - Sigmax.markSelectedEdges sigma (fst props.selectedEdgeIds) edgesMap - Sigmax.markSelectedNodes sigma (fst props.selectedNodeIds) nodesMap + Sigmax.updateEdges sigma tEdgesMap + Sigmax.updateNodes sigma tNodesMap stageHooks _ = pure unit @@ -228,9 +229,9 @@ sigmaSettings = , labelSizeRatio: 2.0 -- label size in ratio of node size , labelThreshold: 5.0 -- min node cam size to start showing label , maxEdgeSize: 1.0 - , maxNodeSize: 7.0 + , maxNodeSize: 8.0 , minEdgeSize: 0.5 -- in fact used in tina as edge size - , minNodeSize: 0.1 + , minNodeSize: 1.0 , mouseEnabled: true , mouseZoomDuration: 150.0 , nodeBorderColor: "default" -- choices: "default" color vs. "node" color diff --git a/src/Gargantext/Components/GraphExplorer.purs b/src/Gargantext/Components/GraphExplorer.purs index c029784dcf7f4676cba1f0ef1e8d42b5e41cb72e..3189c5aaef0b76755ff50447aa6c63910ed11666 100644 --- a/src/Gargantext/Components/GraphExplorer.purs +++ b/src/Gargantext/Components/GraphExplorer.purs @@ -20,7 +20,6 @@ import Reactix.DOM.HTML as RH import Math (log) import Gargantext.Hooks.Loader (useLoader) -import Gargantext.Hooks.Sigmax (Sigma) import Gargantext.Hooks.Sigmax as Sigmax import Gargantext.Hooks.Sigmax.Types as SigmaxTypes import Gargantext.Components.GraphExplorer.Controls as Controls @@ -33,6 +32,7 @@ import Gargantext.Ends (Frontends) import Gargantext.Routes (SessionRoute(NodeAPI), AppRoute) import Gargantext.Sessions (Session, Sessions, get) import Gargantext.Types (NodeType(Graph)) +import Gargantext.Utils.Range as Range import Gargantext.Utils.Reactix as R2 type GraphId = Int @@ -78,8 +78,6 @@ explorerCpt = R.hooksComponent "G.C.GraphExplorer.explorer" cpt dataRef <- R.useRef graph graphRef <- R.useRef null controls <- Controls.useGraphControls - selectedNodeIds <- R.useState' $ Set.empty - selectedEdgeIds <- R.useState' $ Set.empty R.useEffect' $ do case Tuple (R.readRef dataRef) graph of @@ -89,16 +87,10 @@ explorerCpt = R.hooksComponent "G.C.GraphExplorer.explorer" cpt let rSigma = R.readRef controls.sigmaRef Sigmax.cleanupSigma rSigma "explorerCpt" R.setRef dataRef graph - snd selectedNodeIds $ const Set.empty - snd selectedEdgeIds $ const Set.empty + snd controls.selectedEdgeIds $ const Set.empty + snd controls.selectedNodeIds $ const Set.empty snd controls.graphStage $ const Graph.Init - R.useEffect' $ do - if fst controls.showSidePanel == GET.InitialClosed && (not Set.isEmpty $ fst selectedNodeIds) then - snd controls.showSidePanel $ \_ -> GET.Opened - else - pure unit - pure $ RH.div { id: "graph-explorer" } @@ -113,8 +105,20 @@ explorerCpt = R.hooksComponent "G.C.GraphExplorer.explorer" cpt , row [ Controls.controls controls ] , row [ tree (fst controls.showTree) {sessions, mCurrentRoute, frontends} (snd showLogin) , RH.div { ref: graphRef, id: "graph-view", className: graphClassName controls, style: {height: "95%"} } [] -- graph container - , mGraph graphRef controls.sigmaRef {graphId, graph, graphStage: controls.graphStage, selectedNodeIds, selectedEdgeIds} - , mSidebar graph mMetaData {frontends, session, selectedNodeIds, showSidePanel: fst controls.showSidePanel} + , mGraph { controls + , elRef: graphRef + , graphId + , graph + , graphStage: controls.graphStage + , selectedEdgeIds: controls.selectedEdgeIds + , selectedNodeIds: controls.selectedNodeIds + , sigmaRef: controls.sigmaRef + } + , mSidebar graph mMetaData { frontends + , session + , selectedNodeIds: controls.selectedNodeIds + , showSidePanel: fst controls.showSidePanel + } ] , row [ ] @@ -146,16 +150,18 @@ explorerCpt = R.hooksComponent "G.C.GraphExplorer.explorer" cpt RH.div {className: "col-md-2", style: {paddingTop: "60px"}} [forest {sessions, route, frontends, showLogin}] - mGraph :: R.Ref (Nullable Element) - -> R.Ref Sigma - -> { graphId :: GraphId + mGraph :: { controls :: Record Controls.Controls + , elRef :: R.Ref (Nullable Element) + , graphId :: GraphId , graph :: Maybe Graph.Graph , graphStage :: R.State Graph.Stage , selectedNodeIds :: R.State SigmaxTypes.SelectedNodeIds - , selectedEdgeIds :: R.State SigmaxTypes.SelectedEdgeIds} + , selectedEdgeIds :: R.State SigmaxTypes.SelectedEdgeIds + , sigmaRef :: R.Ref Sigmax.Sigma + } -> R.Element - mGraph _ _ {graph: Nothing} = RH.div {} [] - mGraph graphRef sigmaRef r@{graph: Just graph} = graphView graphRef sigmaRef $ r { graph = graph } + mGraph {graph: Nothing} = RH.div {} [] + mGraph r@{graph: Just graph} = graphView $ r { graph = graph } mSidebar :: Maybe Graph.Graph -> Maybe GET.MetaData @@ -176,29 +182,36 @@ explorerCpt = R.hooksComponent "G.C.GraphExplorer.explorer" cpt } type GraphProps = ( - graphId :: GraphId + controls :: Record Controls.Controls + , elRef :: R.Ref (Nullable Element) + , graphId :: GraphId , graph :: Graph.Graph , graphStage :: R.State Graph.Stage , selectedNodeIds :: R.State SigmaxTypes.SelectedNodeIds , selectedEdgeIds :: R.State SigmaxTypes.SelectedEdgeIds + , sigmaRef :: R.Ref Sigmax.Sigma ) -graphView :: R.Ref (Nullable Element) -> R.Ref Sigma -> Record GraphProps -> R.Element +graphView :: Record GraphProps -> R.Element --graphView sigmaRef props = R.createElement (R.memo el memoCmp) props [] -graphView elRef sigmaRef props = R.createElement el props [] +graphView props = R.createElement el props [] where --memoCmp props1 props2 = props1.graphId == props2.graphId el = R.hooksComponent "GraphView" cpt - cpt {graphId, graph, selectedEdgeIds, selectedNodeIds} _children = do + cpt {controls, elRef, graphId, graph, selectedEdgeIds, selectedNodeIds, sigmaRef} _children = do + -- TODO Cache this? + let transformedGraph = transformGraph controls graph + pure $ Graph.graph { elRef , forceAtlas2Settings: Graph.forceAtlas2Settings , graph , selectedEdgeIds , selectedNodeIds + , sigmaRef , sigmaSettings: Graph.sigmaSettings - , sigmaRef: sigmaRef , stage: props.graphStage + , transformedGraph } convert :: GET.GraphData -> Tuple (Maybe GET.MetaData) Graph.Graph @@ -209,6 +222,7 @@ convert (GET.GraphData r) = Tuple r.metaData $ SigmaxTypes.Graph {nodes, edges} Seq.singleton { id : n.id_ , size : log (toNumber n.size + 1.0) + , hidden : false , label : n.label , x : n.x -- cos (toNumber i) , y : n.y -- sin (toNumber i) @@ -357,3 +371,27 @@ defaultPalette = ["#5fa571","#ab9ba2","#da876d","#bdd3ff" getNodes :: Session -> GraphId -> Aff GET.GraphData getNodes session graphId = get session $ NodeAPI Graph (Just graphId) "" + + +transformGraph :: Record Controls.Controls -> Graph.Graph -> Graph.Graph +transformGraph controls graph@(SigmaxTypes.Graph {nodes, edges}) = SigmaxTypes.Graph {nodes: newNodes, edges: newEdges} + where + graphEdgesMap = SigmaxTypes.edgesGraphMap graph + graphNodesMap = SigmaxTypes.nodesGraphMap graph + newNodes = nodeSizes <$> nodeMarked <$> nodes + newEdges = edgeMarked <$> edges + nodeSizes node@{ size } = + if Range.within (fst controls.nodeSize) size then + node + else + node { hidden = true } + edgeMarked edge@{ id } = + if Set.member id (fst controls.selectedEdgeIds) then + edge { color = "#ff0000" } + else + edge + nodeMarked node@{ id } = + if Set.member id (fst controls.selectedNodeIds) then + node { color = "#ff0000" } + else + node diff --git a/src/Gargantext/Components/GraphExplorer/Controls.purs b/src/Gargantext/Components/GraphExplorer/Controls.purs index cc11dfcc04cf01d57fd4aeeec8d3e89702426fef..33c54d0a74db018a0dd3fe528784fd4bf8a5a61e 100644 --- a/src/Gargantext/Components/GraphExplorer/Controls.purs +++ b/src/Gargantext/Components/GraphExplorer/Controls.purs @@ -11,7 +11,8 @@ module Gargantext.Components.GraphExplorer.Controls ) where import Data.Maybe (Maybe(..)) -import Data.Tuple (fst) +import Data.Set as Set +import Data.Tuple (fst, snd) import Data.Tuple.Nested ((/\), get1) import Effect (Effect) import Effect.Timer (setTimeout) @@ -26,6 +27,7 @@ import Gargantext.Components.GraphExplorer.SlideButton (cursorSizeButton, labelS import Gargantext.Components.GraphExplorer.ToggleButton (edgesToggleButton, pauseForceAtlasButton) import Gargantext.Components.GraphExplorer.Types as GET import Gargantext.Hooks.Sigmax as Sigmax +import Gargantext.Hooks.Sigmax.Types as SigmaxTypes import Gargantext.Utils.Range as Range import Gargantext.Utils.Reactix as R2 @@ -33,6 +35,9 @@ type Controls = ( cursorSize :: R.State Number , graphStage :: R.State Graph.Stage , multiNodeSelect :: R.Ref Boolean + , nodeSize :: R.State Range.NumberRange + , selectedEdgeIds :: R.State (Set.Set String) + , selectedNodeIds :: R.State (Set.Set String) , showControls :: R.State Boolean , showSidePanel :: R.State GET.SidePanelState , showTree :: R.State Boolean @@ -45,7 +50,6 @@ controlsToSigmaSettings { cursorSize: (cursorSize /\ _)} = Graph.sigmaSettings type LocalControls = ( edgeSize :: R.State Range.NumberRange , labelSize :: R.State Number - , nodeSize :: R.State Range.NumberRange , pauseForceAtlas :: R.State Boolean , showEdges :: R.State Boolean ) @@ -54,14 +58,14 @@ initialLocalControls :: R.Hooks (Record LocalControls) initialLocalControls = do edgeSize <- R.useState' $ Range.Closed { min: 0.5, max: 1.0 } labelSize <- R.useState' 14.0 - nodeSize <- R.useState' $ Range.Closed { min: 5.0, max: 10.0 } + --nodeSize <- R.useState' $ Range.Closed { min: 0.0, max: 10.0 } pauseForceAtlas <- R.useState' true showEdges <- R.useState' true pure $ { edgeSize , labelSize - , nodeSize + --, nodeSize , pauseForceAtlas , showEdges } @@ -86,6 +90,12 @@ controlsCpt = R.hooksComponent "GraphControls" cpt R.useEffect' $ Sigmax.handleForceAtlas2Pause props.sigmaRef localControls.pauseForceAtlas (get1 localControls.showEdges) mFAPauseRef + R.useEffect' $ do + if fst props.showSidePanel == GET.InitialClosed && (not Set.isEmpty $ fst props.selectedNodeIds) then + snd props.showSidePanel $ \_ -> GET.Opened + else + pure unit + R.useEffectOnce' $ do timeoutId <- setTimeout 2000 $ do let (toggled /\ setToggled) = localControls.pauseForceAtlas @@ -114,7 +124,7 @@ controlsCpt = R.hooksComponent "GraphControls" cpt -- search topics , RH.li {} [ cursorSizeButton props.cursorSize ] -- cursor size: 0-100 , RH.li {} [ labelSizeButton props.sigmaRef localControls.labelSize ] -- labels size: 1-4 - , RH.li {} [ nodeSizeControl props.sigmaRef localControls.nodeSize ] -- node size : 5-15 + , RH.li {} [ nodeSizeControl props.nodeSize ] -- zoom: 0 -100 - calculate ratio -- toggle multi node selection -- save button @@ -127,15 +137,21 @@ useGraphControls = do cursorSize <- R.useState' 10.0 graphStage <- R.useState' Graph.Init multiNodeSelect <- R.useRef false + nodeSize <- R.useState' $ Range.Closed { min: 0.0, max: 10.0 } + showTree <- R.useState' false + selectedNodeIds <- R.useState' $ Set.empty + selectedEdgeIds <- R.useState' $ Set.empty showControls <- R.useState' false showSidePanel <- R.useState' GET.InitialClosed - showTree <- R.useState' false sigma <- Sigmax.initSigma sigmaRef <- R.useRef sigma pure { cursorSize , graphStage , multiNodeSelect + , nodeSize + , selectedEdgeIds + , selectedNodeIds , showControls , showSidePanel , showTree diff --git a/src/Gargantext/Components/GraphExplorer/RangeControl.purs b/src/Gargantext/Components/GraphExplorer/RangeControl.purs index eb804fc30a752aaf5eed54927bdf0b71f95330fc..df777377fef6f1e007ac9eb89c021eeb9109feaa 100644 --- a/src/Gargantext/Components/GraphExplorer/RangeControl.purs +++ b/src/Gargantext/Components/GraphExplorer/RangeControl.purs @@ -55,24 +55,24 @@ edgeSizeControl sigmaRef (state /\ setState) = } } -nodeSizeControl :: R.Ref Sigmax.Sigma -> R.State Range.NumberRange -> R.Element -nodeSizeControl sigmaRef (state /\ setState) = +nodeSizeControl :: R.State Range.NumberRange -> R.Element +nodeSizeControl (state /\ setState) = rangeControl { caption: "Node Size" , sliderProps: { - bounds: Range.Closed { min: 5.0, max: 15.0 } + bounds: Range.Closed { min: 0.0, max: 15.0 } , initialValue: state , epsilon: 0.1 , step: 1.0 , width: 10.0 , height: 5.0 , onChange: \range@(Range.Closed {min, max}) -> do - let sigma = R.readRef sigmaRef - Sigmax.dependOnSigma sigma "[nodeSizeControl] sigma: Nothing" $ \s -> do - Sigma.setSettings s { - minNodeSize: min - , maxNodeSize: max - } + -- let sigma = R.readRef sigmaRef + -- Sigmax.dependOnSigma sigma "[nodeSizeControl] sigma: Nothing" $ \s -> do + -- Sigma.setSettings s { + -- minNodeSize: min + -- , maxNodeSize: max + -- } setState $ const range } } diff --git a/src/Gargantext/Hooks/Sigmax.purs b/src/Gargantext/Hooks/Sigmax.purs index c05941786b312d273e89c2861134abcb458f3a9d..b55bccdf39391b8d439826ba423434a9b181f41e 100644 --- a/src/Gargantext/Hooks/Sigmax.purs +++ b/src/Gargantext/Hooks/Sigmax.purs @@ -184,6 +184,31 @@ markSelectedNodes sigma selectedNodeIds graphNodes = do Sigma.refresh sigma +updateEdges :: Sigma.Sigma -> EdgesMap -> Effect Unit +updateEdges sigma edgesMap = do + Sigma.forEachNode sigma \e -> do + let mTEdge = Map.lookup e.id edgesMap + case mTEdge of + Nothing -> error $ "Edge id " <> e.id <> " not found in edgesMap" + (Just tEdge@{color: tColor}) -> do + _ <- pure $ (e .= "color") tColor + pure unit + Sigma.refresh sigma + + +updateNodes :: Sigma.Sigma -> NodesMap -> Effect Unit +updateNodes sigma nodesMap = do + Sigma.forEachNode sigma \n -> do + let mTNode = Map.lookup n.id nodesMap + case mTNode of + Nothing -> error $ "Node id " <> n.id <> " not found in nodesMap" + (Just tNode@{color: tColor, hidden: tHidden}) -> do + _ <- pure $ (n .= "color") tColor + _ <- pure $ (n .= "hidden") tHidden + pure unit + Sigma.refresh sigma + + bindSelectedNodesClick :: R.Ref Sigma -> R.State SelectedNodeIds -> Effect Unit bindSelectedNodesClick sigmaRef (_ /\ setSelectedNodeIds) = dependOnSigma (R.readRef sigmaRef) "[graphCpt] no sigma" $ \sigma -> do diff --git a/src/Gargantext/Hooks/Sigmax/Types.purs b/src/Gargantext/Hooks/Sigmax/Types.purs index aa5f2292c18f6ca4cffe4104ad78fca1a2ce09a0..150eaaa018da5d9143cf06c662341c857caf2e7a 100644 --- a/src/Gargantext/Hooks/Sigmax/Types.purs +++ b/src/Gargantext/Hooks/Sigmax/Types.purs @@ -21,6 +21,7 @@ type Renderer = { "type" :: String, container :: Element } type Node = ( id :: String , label :: String + , hidden :: Boolean , x :: Number , y :: Number , size :: Number