2 Module : Gargantext.Core.Viz.Graph
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
13 {-# LANGUAGE OverloadedLists #-} -- allows to write Map and HashMap as lists
14 {-# LANGUAGE TypeOperators #-}
16 module Gargantext.Core.Viz.Graph.API
19 import Control.Lens (set, (^.), _Just, (^?))
23 import Debug.Trace (trace)
24 import GHC.Generics (Generic)
25 import Gargantext.API.Admin.Orchestrator.Types
26 import Gargantext.API.Ngrams.Tools
27 import Gargantext.API.Ngrams.Types (NgramsRepo, r_version)
28 import Gargantext.API.Prelude
29 import Gargantext.Core.Methods.Distances (Distance(..), GraphMetric(..))
30 import Gargantext.Core.Types.Main
31 import Gargantext.Core.Viz.Graph
32 import Gargantext.Core.Viz.Graph.GEXF ()
33 import Gargantext.Core.Viz.Graph.Tools -- (cooc2graph)
34 import Gargantext.Database.Action.Metrics.NgramsByNode (getNodesByNgramsOnlyUser)
35 import Gargantext.Database.Action.Node (mkNodeWithParent)
36 import Gargantext.Database.Admin.Config
37 import Gargantext.Database.Admin.Types.Node
38 import Gargantext.Database.Prelude (Cmd)
39 import Gargantext.Database.Query.Table.Node
40 import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
41 import Gargantext.Database.Query.Table.Node.Select
42 import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
43 import Gargantext.Database.Query.Table.Node.User (getNodeUser)
44 import Gargantext.Database.Schema.Ngrams
45 import Gargantext.Database.Schema.Node (node_parentId, node_hyperdata, node_name, node_userId)
46 import Gargantext.Prelude
48 import Servant.Job.Async
50 import qualified Data.HashMap.Strict as HashMap
51 ------------------------------------------------------------------------
52 -- | There is no Delete specific API for Graph since it can be deleted
54 type GraphAPI = Get '[JSON] HyperdataGraphAPI
55 :<|> "async" :> GraphAsyncAPI
57 :> ReqBody '[JSON] HyperdataGraphAPI
58 :> Post '[JSON] NodeId
59 :<|> "gexf" :> Get '[XML] (Headers '[Servant.Header "Content-Disposition" Text] Graph)
60 :<|> "versions" :> GraphVersionsAPI
63 GraphVersions { gv_graph :: Maybe Int
66 deriving (Show, Generic)
68 instance ToJSON GraphVersions
69 instance ToSchema GraphVersions
71 graphAPI :: UserId -> NodeId -> GargServer GraphAPI
72 graphAPI u n = getGraph u n
76 :<|> graphVersionsAPI u n
78 ------------------------------------------------------------------------
79 getGraph :: UserId -> NodeId -> GargNoServer HyperdataGraphAPI
80 getGraph _uId nId = do
81 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
85 graph = nodeGraph ^. node_hyperdata . hyperdataGraph
86 camera = nodeGraph ^. node_hyperdata . hyperdataCamera
87 cId = maybe (panic "[G.V.G.API] Node has no parent")
89 $ nodeGraph ^. node_parentId
91 -- TODO Distance in Graph params
94 -- graph' <- computeGraph cId Distributional NgramsTerms repo
95 graph' <- computeGraph cId Conditional NgramsTerms repo
96 mt <- defaultGraphMetadata cId "Title" repo
98 graph'' = set graph_metadata (Just mt) graph'
99 hg = HyperdataGraphAPI graph'' camera
100 -- _ <- updateHyperdata nId hg
101 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
102 pure $ trace "[G.V.G.API] Graph empty, computing" hg
104 Just graph' -> pure $ trace "[G.V.G.API] Graph exists, returning" $
105 HyperdataGraphAPI graph' camera
108 recomputeGraph :: UserId -> NodeId -> Distance -> GargNoServer Graph
109 recomputeGraph _uId nId d = do
110 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
112 graph = nodeGraph ^. node_hyperdata . hyperdataGraph
113 camera = nodeGraph ^. node_hyperdata . hyperdataCamera
114 graphMetadata = graph ^? _Just . graph_metadata . _Just
115 listVersion = graph ^? _Just . graph_metadata . _Just . gm_list . lfg_version
119 v = repo ^. r_version
120 cId = maybe (panic "[G.V.G.API.recomputeGraph] Node has no parent")
122 $ nodeGraph ^. node_parentId
126 graph' <- computeGraph cId d NgramsTerms repo
127 mt <- defaultGraphMetadata cId "Title" repo
128 let graph'' = set graph_metadata (Just mt) graph'
129 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
130 pure $ trace "[G.V.G.API.recomputeGraph] Graph empty, computed" graph''
132 Just graph' -> if listVersion == Just v
135 graph'' <- computeGraph cId d NgramsTerms repo
136 let graph''' = set graph_metadata graphMetadata graph''
137 _ <- updateHyperdata nId (HyperdataGraph (Just graph''') camera)
138 pure $ trace "[G.V.G.API] Graph exists, recomputing" graph'''
141 -- TODO use Database Monad only here ?
142 computeGraph :: HasNodeError err
148 computeGraph cId d nt repo = do
149 lId <- defaultList cId
150 lIds <- selectNodesWithUsername NodeList userMaster
152 let ngs = filterListWithRoot MapTerm
153 $ mapTermListRoot [lId] nt repo
155 myCooc <- HashMap.filter (>1) -- Removing the hapax (ngrams with 1 cooc)
156 <$> getCoocByNgrams (Diagonal True)
157 <$> groupNodesByNgrams ngs
158 <$> getNodesByNgramsOnlyUser cId (lIds <> [lId]) nt (HashMap.keys ngs)
160 printDebug "myCooc" myCooc
162 graph <- liftBase $ cooc2graphWith Spinglass d 0 myCooc
166 defaultGraphMetadata :: HasNodeError err
170 -> Cmd err GraphMetadata
171 defaultGraphMetadata cId t repo = do
172 lId <- defaultList cId
174 pure $ GraphMetadata {
176 , _gm_metric = Order1
177 , _gm_corpusId = [cId]
179 LegendField 1 "#FFF" "Cluster1"
180 , LegendField 2 "#FFF" "Cluster2"
181 , LegendField 3 "#FFF" "Cluster3"
182 , LegendField 4 "#FFF" "Cluster4"
184 , _gm_list = (ListForGraph lId (repo ^. r_version))
185 , _gm_startForceAtlas = True
187 -- (map (\n -> LegendField n "#FFFFFF" (pack $ show n)) [1..10])
190 ------------------------------------------------------------
191 type GraphAsyncAPI = Summary "Recompute graph"
193 :> AsyncJobsAPI JobLog () JobLog
196 graphAsync :: UserId -> NodeId -> GargServer GraphAsyncAPI
199 JobFunction (\_ log' -> graphRecompute u n (liftBase . log'))
202 graphRecompute :: UserId
204 -> (JobLog -> GargNoServer ())
205 -> GargNoServer JobLog
206 graphRecompute u n logStatus = do
207 logStatus JobLog { _scst_succeeded = Just 0
208 , _scst_failed = Just 0
209 , _scst_remaining = Just 1
210 , _scst_events = Just []
212 _g <- trace (show u) $ recomputeGraph u n Distributional
213 pure JobLog { _scst_succeeded = Just 1
214 , _scst_failed = Just 0
215 , _scst_remaining = Just 0
216 , _scst_events = Just []
219 ------------------------------------------------------------
220 type GraphVersionsAPI = Summary "Graph versions"
221 :> Get '[JSON] GraphVersions
222 :<|> Summary "Recompute graph version"
223 :> Post '[JSON] Graph
225 graphVersionsAPI :: UserId -> NodeId -> GargServer GraphVersionsAPI
226 graphVersionsAPI u n =
228 :<|> recomputeVersions u n
230 graphVersions :: NodeId -> GargNoServer GraphVersions
231 graphVersions nId = do
232 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
246 let v = repo ^. r_version
248 pure $ GraphVersions { gv_graph = listVersion
251 recomputeVersions :: UserId -> NodeId -> GargNoServer Graph
252 recomputeVersions uId nId = recomputeGraph uId nId Distributional
254 ------------------------------------------------------------
258 -> GargNoServer NodeId
259 graphClone uId pId (HyperdataGraphAPI { _hyperdataAPIGraph = graph
260 , _hyperdataAPICamera = camera }) = do
261 let nodeType = NodeGraph
262 nodeUser <- getNodeUser (NodeId uId)
263 nodeParent <- getNodeWith pId (Proxy :: Proxy HyperdataGraph)
264 let uId' = nodeUser ^. node_userId
265 nIds <- mkNodeWithParent nodeType (Just pId) uId' $ nodeParent ^. node_name
270 let graphP' = set (graph_metadata . _Just . gm_startForceAtlas) False graphP
272 _ <- updateHyperdata nId (HyperdataGraph (Just graphP') camera)
276 ------------------------------------------------------------
277 getGraphGexf :: UserId
279 -> GargNoServer (Headers '[Servant.Header "Content-Disposition" Text] Graph)
280 getGraphGexf uId nId = do
281 HyperdataGraphAPI { _hyperdataAPIGraph = graph } <- getGraph uId nId
282 pure $ addHeader "attachment; filename=graph.gexf" graph