module Gargantext.Components.Graph -- ( graph, graphCpt -- , sigmaSettings, SigmaSettings, SigmaOptionalSettings -- , forceAtlas2Settings, ForceAtlas2Settings, ForceAtlas2OptionalSettings -- ) where import Prelude (bind, const, discard, not, pure, unit, ($)) import Data.Either (Either(..)) import Data.Maybe (Maybe(..)) import Data.Nullable (Nullable) import Data.Tuple.Nested ((/\)) import DOM.Simple.Console (log, log2) import DOM.Simple.Types (Element) import FFI.Simple (delay) import Reactix as R import Reactix.DOM.HTML as RH import Gargantext.Hooks.Sigmax as Sigmax import Gargantext.Hooks.Sigmax.Types as SigmaxTypes import Gargantext.Hooks.Sigmax.Sigma as Sigma type OnProps = () data Stage = Init | Ready | Cleanup type Props sigma forceatlas2 = ( elRef :: R.Ref (Nullable Element) , forceAtlas2Settings :: forceatlas2 , graph :: SigmaxTypes.SGraph , multiSelectEnabledRef :: R.Ref Boolean , selectedNodeIds :: R.State SigmaxTypes.NodeIds , showEdges :: R.State SigmaxTypes.ShowEdgesState , sigmaRef :: R.Ref Sigmax.Sigma , sigmaSettings :: sigma , stage :: R.State Stage , transformedGraph :: SigmaxTypes.SGraph ) graph :: forall s fa2. Record (Props s fa2) -> R.Element graph props = R.createElement graphCpt props [] graphCpt :: forall s fa2. R.Component (Props s fa2) graphCpt = R.hooksComponent "G.C.Graph" cpt where cpt props _ = do stageHooks props -- NOTE: This div is not empty after sigma initializes. -- When we change state, we make it empty though. --pure $ RH.div { ref: props.elRef, style: {height: "95%"} } [] pure $ case R.readNullableRef props.elRef of Nothing -> RH.div {} [] Just el -> R.createPortal [] el stageHooks props@{multiSelectEnabledRef, selectedNodeIds, sigmaRef, stage: (Init /\ setStage)} = do R.useEffectOnce $ do let rSigma = R.readRef props.sigmaRef case Sigmax.readSigma rSigma of Nothing -> do eSigma <- Sigma.sigma {settings: props.sigmaSettings} case eSigma of Left err -> log2 "[graphCpt] error creating sigma" err Right sig -> do Sigmax.writeSigma rSigma $ Just sig Sigmax.dependOnContainer props.elRef "[graphCpt (Ready)] container not found" $ \c -> do _ <- Sigma.addRenderer sig { "type": "canvas" , container: c , additionalContexts: ["mouseSelector"] } pure unit Sigmax.refreshData sig $ Sigmax.sigmafy props.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 multiSelectEnabledRef _ <- Sigma.bindMouseSelectorPlugin sigma pure unit Sigmax.setEdges sig false Sigma.startForceAtlas2 sig props.forceAtlas2Settings pure unit Just sig -> do pure unit setStage $ const Ready delay unit $ \_ -> do log "[graphCpt] cleanup" pure $ pure unit stageHooks props@{showEdges: (showEdges /\ _), sigmaRef, stage: (Ready /\ setStage), transformedGraph} = do let tEdgesMap = SigmaxTypes.edgesGraphMap transformedGraph let tNodesMap = SigmaxTypes.nodesGraphMap transformedGraph -- TODO Probably this can be optimized to re-mark selected nodes only when they changed R.useEffect' $ do Sigmax.dependOnSigma (R.readRef sigmaRef) "[graphCpt (Ready)] no sigma" $ \sigma -> do Sigmax.performDiff sigma transformedGraph Sigmax.updateEdges sigma tEdgesMap Sigmax.updateNodes sigma tNodesMap Sigmax.setEdges sigma (not $ SigmaxTypes.edgeStateHidden showEdges) stageHooks _ = pure unit 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 :: {|SigmaSettings} sigmaSettings = { 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: 8.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" -- font params , fontStyle: "bold" , hideEdgesOnMove: true --, labelSize : "proportional" -- alt : proportional, fixed , labelSize: "fixed" , labelSizeRatio: 2.0 -- label size in ratio of node size , labelThreshold: 7.0 -- min node cam size to start showing label , maxEdgeSize: 1.0 , maxNodeSize: 8.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.7 } type ForceAtlas2Settings = ( adjustSizes :: Boolean , barnesHutOptimize :: Boolean -- , barnesHutTheta :: Number , edgeWeightInfluence :: Number -- , fixedY :: Boolean , gravity :: Number , 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 , edgeWeightInfluence : 1.0 -- fixedY : false , gravity : 1.0 , iterationsPerRender : 10.0 , linLogMode : false -- false , outboundAttractionDistribution: false , scalingRatio : 10.0 , skipHidden: false , slowDown : 1.0 , startingIterations : 10.0 , strongGravityMode : false }