diff --git a/src/Gargantext/Components/GraphExplorer/Layout.purs b/src/Gargantext/Components/GraphExplorer/Layout.purs index 96beab905e7b9bd6273d375fada905ad934e064d..5e74a407cfda2e97c6badfd135fe481ed4597067 100644 --- a/src/Gargantext/Components/GraphExplorer/Layout.purs +++ b/src/Gargantext/Components/GraphExplorer/Layout.purs @@ -5,6 +5,8 @@ import Gargantext.Prelude hiding (max, min) import DOM.Simple.Types (Element) import Data.Array as A import Data.FoldableWithIndex (foldMapWithIndex) +import Data.Hashable as Hashable +import Data.HashSet as HashSet import Data.Int (floor, toNumber) import Data.Map as Map import Data.Maybe (Maybe(..), fromJust) @@ -292,6 +294,8 @@ graphViewCpt = R.memo' $ here.component "graphView" cpt where -- here.log2 "[graphView] transformedGraph edges" $ A.fromFoldable e -- here.log2 "[graphView] hidden edges" $ A.filter(_.hidden) $ A.fromFoldable e + hooksTransformGraph + -- | Render -- | pure $ @@ -385,6 +389,42 @@ type LiveProps = ( , showEdges' :: SigmaxT.ShowEdgesState ) +hashLiveProps :: Record LiveProps -> Int +hashLiveProps p = Hashable.hash { edgeConfluence': p.edgeConfluence' + , edgeWeight': p.edgeWeight' + , nodeSize: p.nodeSize' + , removedNodeIds': HashSet.fromFoldable p.removedNodeIds' + , selectedNodeIds': HashSet.fromFoldable p.selectedNodeIds' + , showEdges': p.showEdges' } + +transformGraphStoreParams :: R.Hooks (Record LiveProps) +transformGraphStoreParams = do + store <- GraphStore.use + + edgeConfluence' <- R2.useLive' store.edgeConfluence + edgeWeight' <- R2.useLive' store.edgeWeight + nodeSize' <- R2.useLive' store.nodeSize + removedNodeIds' <- R2.useLive' store.removedNodeIds + selectedNodeIds' <- R2.useLive' store.selectedNodeIds + showEdges' <- R2.useLive' store.showEdges + + pure { edgeConfluence' + , edgeWeight' + , nodeSize' + , removedNodeIds' + , selectedNodeIds' + , showEdges' } + +hooksTransformGraph :: R.Hooks Unit +hooksTransformGraph = do + store <- GraphStore.use + + params <- transformGraphStoreParams + graph' <- R2.useLive' store.graph + + R.useEffect2' (hashLiveProps params) graph' $ do + T.write_ (transformGraph graph' params) store.transformedGraph + transformGraph :: SigmaxT.SGraph -> Record LiveProps -> SigmaxT.SGraph transformGraph graph { edgeConfluence' , edgeWeight' diff --git a/src/Gargantext/Components/GraphExplorer/Sidebar/DocList.purs b/src/Gargantext/Components/GraphExplorer/Sidebar/DocList.purs index 78e8deba690540e58134123480cbf6f3dab64e97..2f6f5c99b8e3613f687babc75954ca93f8b3cfe7 100644 --- a/src/Gargantext/Components/GraphExplorer/Sidebar/DocList.purs +++ b/src/Gargantext/Components/GraphExplorer/Sidebar/DocList.purs @@ -180,7 +180,7 @@ docListCpt = here.component "main" cpt where -- (on fetch success, extract existing docs) useUpdateEffect1' state' do - here.log2 "[docList] state'" state' + -- here.log2 "[docList] state'" state' case state' of Nothing -> T.write_ (Just Seq.empty) rows Just r -> case r of diff --git a/src/Gargantext/Components/GraphExplorer/Toolbar/Buttons.purs b/src/Gargantext/Components/GraphExplorer/Toolbar/Buttons.purs index 041b921d1063e07a64a55968ae58b5a531b7b485..601ba9b53d6f9c9c18dee064df06fad7b2333768 100644 --- a/src/Gargantext/Components/GraphExplorer/Toolbar/Buttons.purs +++ b/src/Gargantext/Components/GraphExplorer/Toolbar/Buttons.purs @@ -16,8 +16,10 @@ import Data.Array as A import Data.Either (Either(..)) import Data.Foldable (intercalate) import Data.Formatter.DateTime as DFDT +import Data.Map as Map import Data.Maybe (Maybe(..)) import Data.Sequence as Seq +import Data.Tuple (Tuple(..)) import Effect (Effect) import Effect.Aff (launchAff_) import Effect.Class (liftEffect) @@ -182,6 +184,10 @@ louvainButtonCpt = here.component "louvainButton" cpt newGraph <- Louvain.assignVisible (Sigma.graph sigma) {} let cluster = Louvain.cluster newGraph :: DLouvain.LouvainCluster let lgraph = SigmaxTypes.louvainGraph graph' cluster :: SigmaxTypes.SGraph + T.modify_ (SigmaxTypes.updateColors + (Map.fromFoldable $ (\{ id, color } -> Tuple id color) <$> SigmaxTypes.graphNodes + lgraph)) + graph T.write_ lgraph transformedGraph pure unit diff --git a/src/Gargantext/Components/GraphExplorer/Toolbar/Controls.purs b/src/Gargantext/Components/GraphExplorer/Toolbar/Controls.purs index 4de74d2688249d0ce955216ee0dc8214ae20cb16..1135ffe7f6ae77315c9942b6b8c8a9258cdf88bc 100644 --- a/src/Gargantext/Components/GraphExplorer/Toolbar/Controls.purs +++ b/src/Gargantext/Components/GraphExplorer/Toolbar/Controls.purs @@ -104,7 +104,7 @@ controlsCpt = R.memo' $ here.component "controls" cpt where R.useEffect' $ Sigmax.handleForceAtlas2Pause fa2Ref forceAtlasState mFAPauseRef Graph.forceAtlas2Settings R.useEffect' do - here.log2 "[controls] noverlapState'" noverlapState' + -- here.log2 "[controls] noverlapState'" noverlapState' case R.readRef noverlapRef of Nothing -> pure unit Just noverlap -> do diff --git a/src/Gargantext/Hooks/Sigmax/Types.purs b/src/Gargantext/Hooks/Sigmax/Types.purs index 4c40a321271c309b1ae180be8b53443db6364b2c..fda851a31a90dbea045aa518c6b6a7ba2f3700d8 100644 --- a/src/Gargantext/Hooks/Sigmax/Types.purs +++ b/src/Gargantext/Hooks/Sigmax/Types.purs @@ -4,7 +4,7 @@ import DOM.Simple.Types (Element) import Data.Array as A import Data.Generic.Rep (class Generic) import Data.Eq.Generic (genericEq) -import Data.Hashable (hash) +import Data.Hashable (class Hashable, hash) import Data.Show.Generic (genericShow) import Data.Map as Map import Data.Maybe (Maybe(..), fromJust) @@ -15,7 +15,7 @@ import Data.Tuple (Tuple(..)) import Effect (Effect) import Partial.Unsafe (unsafePartial) import Prelude (class Eq, class Show, map, ($), (&&), (==), (||), (<$>), (<), mod, not, pure) -import Record.Unsafe (unsafeGet) +import Record.Unsafe (unsafeGet, unsafeSet) import Gargantext.Components.Bootstrap.Types (ComponentStatus(..)) import Gargantext.Components.GraphExplorer.GraphTypes as GEGT @@ -52,11 +52,12 @@ type NodeId = String type EdgeId = String type Label = String +type Color = String type Node = ( - borderColor :: String + borderColor :: Color , children :: Array String - , color :: String + , color :: Color , community :: Int -- this is filled in by the communities-louvain graphology plugin , equilateral :: { numPoints :: Int } , gargType :: GT.Mode @@ -72,7 +73,7 @@ type Node = ( ) type Edge = ( - color :: String + color :: Color , confluence :: Number , id :: EdgeId , hidden :: Boolean @@ -93,11 +94,12 @@ type NodesMap = Map.Map String (Record Node) type HashableNodeFields = ( id :: NodeId - , borderColor :: String - , color :: String + , borderColor :: Color + , color :: Color , equilateral :: { numPoints :: Int } , hidden :: Boolean - , highlighted :: Boolean ) + , highlighted :: Boolean + , type :: String ) hashNode :: forall n. GT.Optional HashableNodeFields n => {|n} -> Int hashNode n = hash rec @@ -107,18 +109,14 @@ hashNode n = hash rec , color : unsafeGet "color" n , equilateral : unsafeGet "equilateral" n , hidden : unsafeGet "hidden" n - , highlighted : unsafeGet "highlighted" n } :: Record HashableNodeFields + , highlighted : unsafeGet "highlighted" n + , type : unsafeGet "type" n } :: Record HashableNodeFields -- | When comparing nodes, we don't want to compare all fields. Only -- | some are relevant (when updating sigma graph). -- NOTE For some reason, `Graphology.updateNode` throws error if `type` is set -compareNodes :: Record Node -> Record Node -> Boolean -compareNodes n1 n2 = n1.id == n2.id && - n1.borderColor == n2.borderColor && - n1.color == n2.color && - n1.equilateral == n2.equilateral && - n1.hidden == n2.hidden && - n1.highlighted == n2.highlighted +compareNodes :: forall n. GT.Optional HashableNodeFields n => {|n} -> {|n} -> Boolean +compareNodes n1 n2 = hashNode n1 == hashNode n2 -- TODO For edges, see `Sigmax.updateEdges` (`color` and `hidden`) type HashableEdgeFields = @@ -144,6 +142,18 @@ emptyNodeIds = Set.empty type SGraph = Graph Node Edge +type NodeWithColor = + ( color :: String + , id :: NodeId ) + +-- | Return a graph where node colors are taken from the first one and +-- | the rest is taken from second graph. +updateColors :: forall n e. GT.Optional NodeWithColor n => Map.Map NodeId Color -> Graph n e -> Graph n e +updateColors colorMap (Graph { nodes, edges }) = Graph { nodes: Seq.map updateColor nodes, edges } + where + updateColor n = case Map.lookup (unsafeGet "id" n) colorMap of + Nothing -> n + Just c -> unsafeSet "color" c n -- Diff graph structure -- NOTE: "add" is NOT a graph. There can be edges which join nodes that are not @@ -258,6 +268,10 @@ instance Eq ShowEdgesState where eq = genericEq instance Show ShowEdgesState where show = genericShow +instance Hashable ShowEdgesState where + hash EShow = 0 + hash EHide = 1 + hash ETempHiddenThenShow = 2 -- | Whether the edges are hidden now (temp or "stable"). edgeStateHidden :: ShowEdgesState -> Boolean diff --git a/src/Gargantext/Utils/Range.purs b/src/Gargantext/Utils/Range.purs index 3f46422f2cdbe52fa7dd420e5586c20800e02ae2..a25797166c482cd2daa1ba159e6a630106594a7b 100644 --- a/src/Gargantext/Utils/Range.purs +++ b/src/Gargantext/Utils/Range.purs @@ -1,7 +1,8 @@ module Gargantext.Utils.Range where -import Prelude hiding (clamp) +import Data.Hashable (class Hashable, hash) import Data.Newtype (class Newtype) +import Prelude hiding (clamp) class Range r v where clamp :: r -> v -> v @@ -9,15 +10,14 @@ class Range r v where -- | A Closed Interval, in math speak newtype Closed t = Closed { min :: t, max :: t } - derive instance Newtype (Closed t) _ - instance Ord t => Range (Closed t) t where clamp (Closed r) = max r.min <<< min r.max within (Closed r) v = (r.min <= v) && (v <= r.max) - instance Eq t => Eq (Closed t) where eq (Closed r1) (Closed r2) = (r1.min == r2.min) && (r1.max == r2.max) +instance Hashable t => Hashable (Closed t) where + hash (Closed { min, max }) = hash { min, max } type NumberRange = Closed Number diff --git a/src/Gargantext/Utils/Set.purs b/src/Gargantext/Utils/Set.purs index b0fee1f479cea217ed06af77cbead5d3c8333e28..05e6e0f0c8fb363defd30dab577c58fcebcdb78b 100644 --- a/src/Gargantext/Utils/Set.purs +++ b/src/Gargantext/Utils/Set.purs @@ -1,9 +1,15 @@ module Gargantext.Utils.Set where +import Data.Array as A +import Data.Hashable (class Hashable, hash) import Data.Ord (class Ord) import Data.Set as Set +import Prelude (($)) +-- instance (Hashable a, Ord a) => Hashable (Set.Set a) where +-- hash s = hash $ A.sort $ A.fromFoldable s + -- | If `a` is in Set, remove it, otherwise add it toggle :: forall a. Ord a => Set.Set a -> a -> Set.Set a toggle s x = if Set.member x s then Set.delete x s else Set.insert x s