Sigma.purs 12.2 KB
Newer Older
James Laver's avatar
James Laver committed
1 2 3
module Gargantext.Hooks.Sigmax.Sigma where

import Prelude
4

5
import DOM.Simple.Types (Element, Window)
6
import Data.Array as A
James Laver's avatar
James Laver committed
7
import Data.Either (Either(..))
8 9
import Data.Maybe (Maybe)
import Data.Sequence as Seq
10
import Data.Set as Set
11 12 13
import Data.Traversable (traverse_)
import Effect (Effect)
import Effect.Exception as EEx
14
import Effect.Timer (setTimeout)
15
import Effect.Uncurried (EffectFn1, EffectFn3, EffectFn4, mkEffectFn1, runEffectFn1, runEffectFn3, runEffectFn4)
16 17
import FFI.Simple ((..), (...), (.=))
import Foreign.Object as Object
18
import Gargantext.Hooks.Sigmax.Types as Types
19
import Type.Row (class Union)
20

21
-- | Type representing a sigmajs instance
James Laver's avatar
James Laver committed
22
foreign import data Sigma :: Type
23 24
-- | Type representing `sigma.graph`
foreign import data SigmaGraph :: Type
James Laver's avatar
James Laver committed
25

26 27
type NodeRequiredProps = ( id :: Types.NodeId )
type EdgeRequiredProps = ( id :: Types.EdgeId, source :: Types.NodeId, target :: Types.NodeId )
James Laver's avatar
James Laver committed
28

29 30
class NodeProps (all :: Row Type) (extra :: Row Type) | all -> extra
class EdgeProps (all :: Row Type) (extra :: Row Type) | all -> extra
James Laver's avatar
James Laver committed
31 32 33 34 35 36 37 38

instance nodeProps
  :: Union NodeRequiredProps extra all
  => NodeProps all extra

instance edgeProps
  :: Union EdgeRequiredProps extra all
  => EdgeProps all extra
39

James Laver's avatar
James Laver committed
40 41 42
type Graph n e = { nodes :: Array {|n}, edges :: Array {|e} }
type SigmaOpts s = { settings :: s }

43
-- | Initialize sigmajs.
44 45
sigma :: forall opts err. SigmaOpts opts -> Effect (Either err Sigma)
sigma = runEffectFn3 _sigma Left Right
James Laver's avatar
James Laver committed
46

47 48
-- | Kill a sigmajs instance.
kill :: Sigma -> Effect Unit
49
kill s = pure $ s ... "kill" $ []
50

51
-- | Call the `refresh()` method on a sigmajs instance.
James Laver's avatar
James Laver committed
52
refresh :: Sigma -> Effect Unit
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
refresh s = pure $ s ... "refresh" $ []

-- | Type representing a sigmajs renderer.
foreign import data Renderer :: Type
type RendererType = String

--makeRenderer :: forall props. RendererType -> Element -> props -> Renderer
--makeRenderer type_ container props =
--  {
--    "type": type_
--  , container
--  | props
--  }

-- | Call the `addRenderer` method on a sigmajs instance.
--addRenderer :: forall err. Sigma -> Renderer -> Effect (Either err Unit)
69 70
addRenderer :: forall r err. Sigma -> r -> Effect (Either err Unit)
addRenderer = runEffectFn4 _addRenderer Left Right
71

72
-- | Initialize the mouse selector plugin. This allows for custom bindings to mouse events.
73 74
bindMouseSelectorPlugin :: forall err. Sigma -> Effect (Either err Unit)
bindMouseSelectorPlugin = runEffectFn3 _bindMouseSelectorPlugin Left Right
James Laver's avatar
James Laver committed
75

76 77 78
-- | Call `killRenderer` on a sigmajs instance.
killRenderer :: forall r. Sigma -> r -> Effect (Either EEx.Error Unit)
killRenderer s r = EEx.try $ pure $ s ... "killRenderer" $ [ r ]
James Laver's avatar
James Laver committed
79

80 81 82
-- | Get `renderers` of a sigmajs instance.
renderers :: Sigma -> Array Renderer
renderers s = s .. "renderers" :: Array Renderer
James Laver's avatar
James Laver committed
83

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
-- | Get the `container` of a sigmajs renderer.
rendererContainer :: Renderer -> Element
rendererContainer r = r .. "container"

-- | Return the container of first renderer in sigmajs instance's `renderers` list.
getRendererContainer :: Sigma -> Maybe Element
getRendererContainer s = rendererContainer <$> mContainer
  where
    mContainer = A.head $ renderers s

-- | Set the container of first renderer in sigmajs instance's `renderers` list.
setRendererContainer :: Renderer -> Element -> Effect Unit
setRendererContainer r el = do
  _ <- pure $ (r .= "container") el
  pure unit

-- | Call the `kill()` method on a sigmajs instance.
killSigma :: Sigma -> Effect (Either EEx.Error Unit)
killSigma s = EEx.try $ pure $ s ... "kill" $ []

-- | Get the `.graph` object from a sigmajs instance.
graph :: Sigma -> SigmaGraph
graph s = s .. "graph" :: SigmaGraph

-- | Read graph into a sigmajs instance.
graphRead :: forall nodeExtra node edgeExtra edge. NodeProps nodeExtra node => EdgeProps edgeExtra edge => SigmaGraph -> Graph node edge -> Effect (Either EEx.Error Unit)
graphRead sg g = EEx.try $ pure $ sg ... "read" $ [ g ]

-- | Clear a sigmajs graph.
clear :: SigmaGraph -> Effect Unit
clear sg = pure $ sg ... "clear" $ []

-- | Call `sigma.bind(event, handler)` on a sigmajs instance.
117 118
bind_ :: forall e. Sigma -> String -> (e -> Effect Unit) -> Effect Unit
bind_ s e h = runEffectFn3 _bind s e (mkEffectFn1 h)
James Laver's avatar
James Laver committed
119

120 121 122 123 124 125 126 127
-- | Generic function to bind a sigmajs event for edges.
bindEdgeEvent :: Sigma -> String -> (Record Types.Edge -> Effect Unit) -> Effect Unit
bindEdgeEvent s ev f = bind_ s ev $ \e -> do
  let edge = e .. "data" .. "edge" :: Record Types.Edge
  f edge
-- | Generic function to bind a sigmajs event for nodes.
bindNodeEvent :: Sigma -> String -> (Record Types.Node -> Effect Unit) -> Effect Unit
bindNodeEvent s ev f = bind_ s ev $ \e -> do
128 129 130
  let node = e .. "data" .. "node" :: Record Types.Node
  f node

131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
-- | Call `sigma.unbind(event)` on a sigmajs instance.
unbind_ :: Sigma -> String -> Effect Unit
unbind_ s e = pure $ s ... "unbind" $ [e]

edges_ :: SigmaGraph -> Array (Record Types.Edge)
edges_ sg = sg ... "edges" $ [] :: Array (Record Types.Edge)
nodes_ :: SigmaGraph -> Array (Record Types.Node)
nodes_ sg = sg ... "nodes" $ [] :: Array (Record Types.Node)

-- | Call `sigmaGraph.edges()` on a sigmajs graph instance.
edges :: SigmaGraph -> Seq.Seq (Record Types.Edge)
edges = Seq.fromFoldable <<< edges_
-- | Call `sigmaGraph.nodes()` on a sigmajs graph instance.
nodes :: SigmaGraph -> Seq.Seq (Record Types.Node)
nodes = Seq.fromFoldable <<< nodes_

-- | Fetch ids of graph edges in a sigmajs instance.
148
sigmaEdgeIds :: SigmaGraph -> Types.EdgeIds
149 150 151 152 153
sigmaEdgeIds sg =  Set.fromFoldable edgeIds
  where
    edgeIds = _.id <$> edges sg

-- | Fetch ids of graph nodes in a sigmajs instance.
154
sigmaNodeIds :: SigmaGraph -> Types.NodeIds
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
sigmaNodeIds sg = Set.fromFoldable nodeIds
  where
    nodeIds = _.id <$> nodes sg

-- | Call `addEdge` on a sigmajs graph.
addEdge :: SigmaGraph -> Record Types.Edge -> Effect Unit
addEdge sg e = pure $ sg ... "addEdge" $ [e]
-- | Call `removeEdge` on a sigmajs graph.
removeEdge :: SigmaGraph -> String -> Effect Unit
removeEdge sg eId = pure $ sg ... "dropEdge" $ [eId]
--removeEdge = runEffectFn2 _removeEdge

-- | Call `addNode` on a sigmajs graph.
addNode :: SigmaGraph -> Record Types.Node -> Effect Unit
addNode sg n = pure $ sg ... "addNode" $ [n]
-- | Call `removeNode` on a sigmajs graph.
removeNode :: SigmaGraph -> String -> Effect Unit
removeNode sg nId = pure $ sg ... "dropNode" $ [nId]

-- | Iterate over all edges in a sigmajs graph.
forEachEdge :: SigmaGraph -> (Record Types.Edge -> Effect Unit) -> Effect Unit
forEachEdge sg f = traverse_ f (edges sg)
-- | Iterate over all nodes in a sigmajs graph.
forEachNode :: SigmaGraph -> (Record Types.Node -> Effect Unit) -> Effect Unit
forEachNode sg f = traverse_ f (nodes sg)

-- | Bind a `clickNode` event.
bindClickNode :: Sigma -> (Record Types.Node -> Effect Unit) -> Effect Unit
bindClickNode s f = bindNodeEvent s "clickNode" f
-- | Unbind a `clickNode` event.
185
unbindClickNode :: Sigma -> Effect Unit
186
unbindClickNode s = unbind_ s "clickNode"
187

188
-- | Bind a `clickNodes` event.
189 190
bindClickNodes :: Sigma -> (Array (Record Types.Node) -> Effect Unit) -> Effect Unit
bindClickNodes s f = bind_ s "clickNodes" $ \e -> do
191 192 193
  let ns = e .. "data" .. "node" :: Array (Record Types.Node)
  f ns
-- | Unbind a `clickNodes` event.
194 195 196
unbindClickNodes :: Sigma -> Effect Unit
unbindClickNodes s = unbind_ s "clickNodes"

197
-- | Bind a `overNode` event.
198
bindOverNode :: Sigma -> (Record Types.Node -> Effect Unit) -> Effect Unit
199
bindOverNode s f = bindNodeEvent s "overNode" f
200

201
-- | Bind a `clickEdge` event.
202
bindClickEdge :: Sigma -> (Record Types.Edge -> Effect Unit) -> Effect Unit
203 204
bindClickEdge s f = bindEdgeEvent s "clickEdge" f
-- | Unbind a `clickEdge` event.
205 206 207
unbindClickEdge :: Sigma -> Effect Unit
unbindClickEdge s = unbind_ s "clickEdge"

208
-- | Bind a `overEdge` event.
209
bindOverEdge :: Sigma -> (Record Types.Edge -> Effect Unit) -> Effect Unit
210
bindOverEdge s f = bindEdgeEvent s "overEdge" f
211

212
-- | Call `settings(s)` on a sigmajs instance.
213
setSettings :: forall settings. Sigma -> settings -> Effect Unit
214
setSettings s settings = do
215
  _ <- pure $ s ... "settings" $ [ settings ]
216
  refresh s
217

218 219 220 221 222
-- | Call `settins(s)` on the the main proxy `window.sigma`
proxySetSettings :: forall settings.
  Window -> Sigma -> settings -> Effect Unit
proxySetSettings = runEffectFn3 _proxySetSettings

223
-- | Start forceAtlas2 on a sigmajs instance.
James Laver's avatar
James Laver committed
224
startForceAtlas2 :: forall settings. Sigma -> settings -> Effect Unit
225
startForceAtlas2 s settings = pure $ s ... "startForceAtlas2" $ [ settings ]
James Laver's avatar
James Laver committed
226

227
-- | Restart forceAtlas2 on a sigmajs instance.
228 229
restartForceAtlas2 :: forall settings. Sigma -> settings -> Effect Unit
restartForceAtlas2 s settings = startForceAtlas2 s settings
230

231
-- | Stop forceAtlas2 on a sigmajs instance.
James Laver's avatar
James Laver committed
232
stopForceAtlas2 :: Sigma -> Effect Unit
233
stopForceAtlas2 s = pure $ s ... "stopForceAtlas2" $ []
James Laver's avatar
James Laver committed
234

235
-- | Kill forceAtlas2 on a sigmajs instance.
James Laver's avatar
James Laver committed
236
killForceAtlas2 :: Sigma -> Effect Unit
237
killForceAtlas2 s = pure $ s ... "killForceAtlas2" $ []
238

239 240 241
-- | Return whether forceAtlas2 is running on a sigmajs instance.
isForceAtlas2Running :: Sigma -> Boolean
isForceAtlas2Running s = s ... "isForceAtlas2Running" $ [] :: Boolean
James Laver's avatar
James Laver committed
242

243 244
-- | Refresh forceAtlas2 (with a `setTimeout` hack as it seems it doesn't work
-- | otherwise).
245 246
refreshForceAtlas :: forall settings. Sigma -> settings -> Effect Unit
refreshForceAtlas s settings = do
247
  let isRunning = isForceAtlas2Running s
248 249 250 251
  if isRunning then
    pure unit
  else do
    _ <- setTimeout 100 $ do
252
      restartForceAtlas2 s settings
253
      _ <- setTimeout 100 $
254
        stopForceAtlas2 s
255 256 257
      pure unit
    pure unit

James Laver's avatar
James Laver committed
258 259
newtype SigmaEasing = SigmaEasing String

260 261 262 263 264 265 266 267 268
sigmaEasing ::
  { linear :: SigmaEasing
  , quadraticIn :: SigmaEasing
  , quadraticOut :: SigmaEasing
  , quadraticInOut :: SigmaEasing
  , cubicIn :: SigmaEasing
  , cubicOut :: SigmaEasing
  , cubicInOut :: SigmaEasing
  }
James Laver's avatar
James Laver committed
269 270 271 272 273 274 275 276 277
sigmaEasing =
  { linear : SigmaEasing "linear"
  , quadraticIn : SigmaEasing "quadraticIn"
  , quadraticOut : SigmaEasing "quadraticOut"
  , quadraticInOut : SigmaEasing "quadraticInOut"
  , cubicIn : SigmaEasing "cubicIn"
  , cubicOut : SigmaEasing "cubicOut"
  , cubicInOut : SigmaEasing "cubicInOut"
  }
278 279 280 281 282 283 284 285

type CameraProps =
  ( x :: Number
  , y :: Number
  , ratio :: Number
  , angle :: Number
  )

286
foreign import data CameraInstance' :: Row Type
287 288
type CameraInstance = { | CameraInstance' }

289 290 291 292 293 294
-- | Get an array of a sigma instance's `cameras`.
cameras :: Sigma -> Array CameraInstance
cameras s = Object.values cs
  where
    -- For some reason, `sigma.cameras` is an object with integer keys.
    cs = s .. "cameras" :: Object.Object CameraInstance
295

296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
toCamera :: CameraInstance -> Record CameraProps
toCamera c = { angle, ratio, x, y }
  where
    angle = c .. "angle" :: Number
    ratio = c .. "ratio" :: Number
    x = c .. "x" :: Number
    y = c .. "y" :: Number

updateCamera :: Sigma -> { ratio :: Number, x :: Number, y :: Number } -> Effect Unit
updateCamera sig { ratio, x, y } = do
  let camera = sig .. "camera"
  _ <- pure $ (camera .= "ratio") ratio
  _ <- pure $ (camera .= "x") x
  _ <- pure $ (camera .= "y") y
  pure unit

312
goTo :: Record CameraProps -> CameraInstance -> Effect Unit
313
goTo props cam = pure $ cam ... "goTo" $ [props]
314 315

goToAllCameras :: Sigma -> Record CameraProps -> Effect Unit
316 317
goToAllCameras s props = traverse_ (goTo props) $ cameras s

318
takeScreenshot :: Sigma -> Effect String
319
takeScreenshot =  runEffectFn1 _takeScreenshot
320

321 322 323 324 325 326
getEdges :: Sigma -> Effect (Array (Record Types.Edge))
getEdges = runEffectFn1 _getEdges

getNodes :: Sigma -> Effect (Array (Record Types.Node))
getNodes = runEffectFn1 _getNodes

327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
-- | FFI
foreign import _sigma ::
  forall a b opts err.
  EffectFn3 (a -> Either a b)
            (b -> Either a b)
            (SigmaOpts opts)
            (Either err Sigma)
foreign import _addRenderer
  :: forall a b r err.
  EffectFn4 (a -> Either a b)
            (b -> Either a b)
            Sigma
            r
            (Either err Unit)
foreign import _bindMouseSelectorPlugin
  :: forall a b err.
  EffectFn3 (a -> Either a b)
            (b -> Either a b)
            Sigma
            (Either err Unit)
foreign import _bind :: forall e. EffectFn3 Sigma String (EffectFn1 e Unit) Unit
348
foreign import _takeScreenshot :: EffectFn1 Sigma String
349 350
foreign import _getEdges :: EffectFn1 Sigma (Array (Record Types.Edge))
foreign import _getNodes :: EffectFn1 Sigma (Array (Record Types.Node))
351 352 353 354 355 356
foreign import _proxySetSettings
  :: forall settings.
  EffectFn3 Window
            Sigma
            settings
            Unit