module Gargantext.Components.GraphExplorer.Resources ( drawGraph , sigmaSettings, SigmaSettings--, SigmaOptionalSettings , forceAtlas2Settings, ForceAtlas2Settings--, ForceAtlas2OptionalSettings ) where import Gargantext.Prelude import DOM.Simple (window) import DOM.Simple.Types (Element) import Data.Either (Either(..)) import Data.Maybe (Maybe(..)) import Data.Nullable (Nullable) import Gargantext.Components.App.Data (Boxes) import Gargantext.Components.GraphExplorer.Store as GraphStore import Gargantext.Components.GraphExplorer.Types as GET import Gargantext.Components.Themes (darksterTheme) import Gargantext.Components.Themes as Themes import Gargantext.Hooks.Sigmax as Sigmax import Gargantext.Hooks.Sigmax.Sigma as Sigma import Gargantext.Hooks.Sigmax.Types as SigmaxTypes import Gargantext.Utils (getter) import Gargantext.Utils.Reactix as R2 import Gargantext.Utils.Stores as Stores import Reactix as R import Record (merge) import Toestand as T here :: R2.Here here = R2.here "Gargantext.Components.Graph" type Props sigma forceatlas2 = ( boxes :: Boxes , elRef :: R.Ref (Nullable Element) , forceAtlas2Settings :: forceatlas2 , sigmaRef :: R.Ref Sigmax.Sigma , sigmaSettings :: sigma , transformedGraph :: SigmaxTypes.SGraph ) drawGraph :: forall s fa2. R2.Leaf (Props s fa2) drawGraph = R2.leaf drawGraphCpt drawGraphCpt :: forall s fa2. R.Memo (Props s fa2) drawGraphCpt = R.memo' $ here.component "graph" cpt where -- | Component -- | cpt { elRef , sigmaRef , boxes , forceAtlas2Settings: fa2 , transformedGraph } _ = do { showEdges , graphStage , graph , startForceAtlas , selectedNodeIds , multiSelectEnabled , hyperdataGraph } <- Stores.useStore GraphStore.context showEdges' <- R2.useLive' showEdges graphStage' <- R2.useLive' graphStage graph' <- R2.useLive' graph startForceAtlas' <- R2.useLive' startForceAtlas hyperdataGraph' <- R2.useLive' hyperdataGraph selectedNodeIds' <- R2.useLive' selectedNodeIds -- | Hooks -- | -- Clean up R.useEffectOnce $ do pure $ do here.log "[graphCpt (Cleanup)]" Sigmax.dependOnSigma (R.readRef sigmaRef) "[graphCpt (Cleanup)] no sigma" $ \sigma -> do Sigma.stopForceAtlas2 sigma here.log2 "[graphCpt (Cleanup)] forceAtlas stopped for" sigma Sigma.kill sigma here.log "[graphCpt (Cleanup)] sigma killed" -- Stage Init R.useEffect1' graphStage' $ case graphStage' of GET.Init -> do let mCamera = getter _.mCamera hyperdataGraph' let rSigma = R.readRef sigmaRef case Sigmax.readSigma rSigma of Nothing -> do theme <- T.read boxes.theme eSigma <- Sigma.sigma {settings: sigmaSettings theme} case eSigma of Left err -> here.warn2 "[graphCpt] error creating sigma" err Right sig -> do Sigmax.writeSigma rSigma $ Just sig Sigmax.dependOnContainer elRef "[graphCpt (Ready)] container not found" $ \c -> do _ <- Sigma.addRenderer sig { "type": "canvas" , container: c , additionalContexts: ["mouseSelector"] } pure unit Sigmax.refreshData sig $ Sigmax.sigmafy graph' Sigmax.dependOnSigma (R.readRef sigmaRef) "[graphCpt (Ready)] no sigma" $ \sigma -> do -- bind the click event only initially, when ref was empty Sigmax.bindSelectedNodesClick sigma selectedNodeIds multiSelectEnabled _ <- Sigma.bindMouseSelectorPlugin sigma pure unit Sigmax.setEdges sig false -- here.log2 "[graph] startForceAtlas" startForceAtlas if startForceAtlas' then Sigma.startForceAtlas2 sig fa2 else Sigma.stopForceAtlas2 sig case mCamera of Just (GET.Camera { ratio, x, y }) -> do Sigma.updateCamera sig { ratio, x, y } -- Default camera: slightly de-zoom the graph to avoid -- nodes sticking to the container borders Nothing -> Sigma.updateCamera sig { ratio: 1.1, x: 0.0, y: 0.0 } -- Reload Sigma on Theme changes _ <- flip T.listen boxes.theme \{ old, new } -> if (eq old new) then pure unit else Sigma.proxySetSettings window sig $ sigmaSettings new pure unit Just _sig -> do pure unit T.write_ GET.Ready graphStage _ -> pure unit -- Stage ready -- (?) Probably this can be optimized to re-mark selected nodes only when -- they changed → done on #375 R.useEffect1' selectedNodeIds' case graphStage' of GET.Ready -> do let tEdgesMap = SigmaxTypes.edgesGraphMap transformedGraph let tNodesMap = SigmaxTypes.nodesGraphMap transformedGraph Sigmax.dependOnSigma (R.readRef sigmaRef) "[graphCpt (Ready)] no sigma" $ \sigma -> do Sigmax.performDiff sigma transformedGraph Sigmax.updateEdges sigma tEdgesMap Sigmax.updateNodes sigma tNodesMap let edgesState = not $ SigmaxTypes.edgeStateHidden showEdges' here.log2 "[graphCpt] edgesState" edgesState Sigmax.setEdges sigma edgesState _ -> pure unit -- | Render -- | pure $ R2.fromMaybe_ (R.readNullableRef elRef) (R.createPortal []) -- NOTE: This div is not empty after sigma initializes. -- When we change state, we make it empty though. --pure $ RH.div { ref: elRef, style: {height: "95%"} } [] type SigmaSettings = ( animationsTime :: Number , autoRescale :: Boolean , autoResize :: Boolean , batchEdgesDrawing :: Boolean , borderSize :: Number -- , canvasEdgesBatchSize :: Number -- , clone :: Boolean -- , defaultEdgeColor :: String , defaultEdgeHoverColor :: String , defaultEdgeType :: String , defaultHoverLabelBGColor :: String , defaultHoverLabelColor :: String , defaultLabelColor :: String -- , defaultLabelHoverColor :: String , defaultLabelSize :: Number , defaultNodeBorderColor :: String , defaultNodeColor :: String -- , defaultNodeHoverColor :: String -- , defaultNodeType :: String , doubleClickEnabled :: Boolean -- , doubleClickTimeout :: Number -- , doubleClickZoomDuration :: Number -- , doubleClickZoomingRatio :: Number -- , doubleTapTimeout :: Number -- , dragTimeout :: Number , drawEdgeLabels :: Boolean , drawEdges :: Boolean , drawLabels :: Boolean , drawNodes :: Boolean -- , edgeColor :: String , edgeHoverColor :: String , edgeHoverExtremities :: Boolean , edgeHoverPrecision :: Number , edgeHoverSizeRatio :: Number -- , edgesPowRatio :: Number -- , enableCamera :: Boolean , enableEdgeHovering :: Boolean , enableHovering :: Boolean -- , eventsEnabled :: Boolean , font :: String , fontStyle :: String , hideEdgesOnMove :: Boolean -- , hoverFont :: String -- , hoverFontStyle :: String -- , immutable :: Boolean -- , labelColor :: String -- , labelHoverBGColor :: String -- , labelHoverColor :: String -- , labelHoverShadow :: String -- , labelHoverShadowColor :: String , labelSize :: String , labelSizeRatio :: Number , labelThreshold :: Number , maxEdgeSize :: Number , maxNodeSize :: Number -- , minArrowSize :: Number , minEdgeSize :: Number , minNodeSize :: Number , mouseEnabled :: Boolean -- , mouseInertiaDuration :: Number -- , mouseInertiaRatio :: Number , mouseSelectorSize :: Number -- , mouseWheelEnabled :: Boolean , mouseZoomDuration :: Number , nodeBorderColor :: String -- , nodeHoverColor :: String --, nodesPowRatio :: Number , rescaleIgnoreSize :: Boolean -- , scalingMode :: String -- , sideMargin :: Number , singleHover :: Boolean -- , skipErrors :: Boolean , touchEnabled :: Boolean -- , touchInertiaDuration :: Number -- , touchInertiaRatio :: Number , twBorderGreyColor :: String , twEdgeDefaultOpacity :: Number , twEdgeGreyColor :: String , twNodeRendBorderColor :: String , twNodeRendBorderSize :: Number , twNodesGreyOpacity :: Number , twSelectedColor :: String , verbose :: Boolean -- , webglEdgesBatchSize :: Number -- , webglOversamplingRatio :: Number , zoomMax :: Number , zoomMin :: Number , zoomingRatio :: Number ) -- not selected <=> (1-greyness) -- selected nodes <=> special label sigmaSettings :: Themes.Theme -> {|SigmaSettings} sigmaSettings theme = { animationsTime : 30000.0 , autoRescale : true , autoResize : true , batchEdgesDrawing : true , borderSize : 1.0 -- for ex, bigger border when hover , defaultEdgeHoverColor : "#f00" , defaultEdgeType : "curve" -- 'curve' or 'line' (curve iff ourRendering) -- , defaultHoverLabelBGColor : "#fff" -- , defaultHoverLabelColor : "#000" -- , defaultLabelColor : "#000" -- labels text color , defaultLabelSize : 15.0 -- (old tina: showLabelsIfZoom) , defaultNodeBorderColor : "#000" -- <- if nodeBorderColor = 'default' , defaultNodeColor : "#FFF" , doubleClickEnabled : false -- indicates whether or not the graph can be zoomed on double-click , drawEdgeLabels : true , drawEdges : true , drawLabels : true , drawNodes : true , enableEdgeHovering : false , edgeHoverExtremities : true , edgeHoverColor : "edge" , edgeHoverPrecision : 2.0 , edgeHoverSizeRatio : 2.0 , enableHovering : true , font : "arial" , fontStyle : "" , hideEdgesOnMove : true , labelSize : "proportional" -- alt : proportional, fixed -- , labelSize : "fixed" , labelSizeRatio : 2.0 -- label size in ratio of node size , labelThreshold : 9.0 -- 5.0 for more labels -- min node cam size to start showing label , maxEdgeSize : 1.0 , maxNodeSize : 10.0 , minEdgeSize : 0.5 -- in fact used in tina as edge size , minNodeSize : 1.0 , mouseEnabled : true , mouseSelectorSize : 15.0 , mouseZoomDuration : 150.0 , nodeBorderColor : "default" -- choices: "default" color vs. "node" color --, nodesPowRatio : 10.8 , rescaleIgnoreSize : false , singleHover : true , touchEnabled : true , twBorderGreyColor : "rgba(100, 100, 100, 0.9)" , twEdgeDefaultOpacity : 0.4 -- initial opacity added to src/tgt colors , twEdgeGreyColor : "rgba(100, 100, 100, 0.25)" , twNodeRendBorderColor : "#FFF" , twNodeRendBorderSize : 2.5 -- node borders (only iff ourRendering) , twNodesGreyOpacity : 5.5 -- smaller value: more grey , twSelectedColor : "node" -- "node" for a label bg like the node color, "default" for white background , verbose : true , zoomMax : 1.7 , zoomMin : 0.0 , zoomingRatio : 1.4 } `merge` themeSettings theme where themeSettings t | eq t darksterTheme = { defaultHoverLabelBGColor: "#FFF" , defaultHoverLabelColor : "#000" , defaultLabelColor: "#FFF" } | otherwise = { defaultHoverLabelBGColor: "#FFF" , defaultHoverLabelColor : "#000" , defaultLabelColor: "#000" } type ForceAtlas2Settings = ( adjustSizes :: Boolean , barnesHutOptimize :: Boolean -- , barnesHutTheta :: Number , batchEdgesDrawing :: Boolean , edgeWeightInfluence :: Number -- , fixedY :: Boolean , hideEdgesOnMove :: Boolean , gravity :: Number , includeHiddenEdges :: Boolean , includeHiddenNodes :: Boolean , iterationsPerRender :: Number , linLogMode :: Boolean , outboundAttractionDistribution :: Boolean , scalingRatio :: Number , skipHidden :: Boolean , slowDown :: Number , startingIterations :: Number , strongGravityMode :: Boolean -- , timeout :: Number -- , worker :: Boolean ) forceAtlas2Settings :: {|ForceAtlas2Settings} forceAtlas2Settings = { adjustSizes : true , barnesHutOptimize : true , batchEdgesDrawing : true , edgeWeightInfluence : 1.0 -- fixedY : false , gravity : 1.0 , hideEdgesOnMove : true , includeHiddenEdges : false , includeHiddenNodes : true , iterationsPerRender : 100.0 -- 10.0 , linLogMode : false -- false , outboundAttractionDistribution : false , scalingRatio : 1000.0 , skipHidden : false , slowDown : 1.0 , startingIterations : 10.0 , strongGravityMode : false }