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
12 {-# LANGUAGE BangPatterns #-}
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, (^?), at)
21 import Data.Maybe (fromMaybe)
23 import Data.Text hiding (head)
24 import Debug.Trace (trace)
25 import GHC.Generics (Generic)
26 import Gargantext.API.Admin.Orchestrator.Types
27 import Gargantext.API.Ngrams.Tools
28 import Gargantext.API.Prelude
29 import Gargantext.Core.Methods.Distances (Distance(..), GraphMetric(..), withMetric)
30 import Gargantext.Core.NodeStory
31 import Gargantext.Core.Types.Main
32 import Gargantext.Core.Viz.Graph
33 import Gargantext.Core.Viz.Graph.GEXF ()
34 import Gargantext.Core.Viz.Graph.Tools -- (cooc2graph)
35 import Gargantext.Database.Action.Metrics.NgramsByContext (getContextsByNgramsOnlyUser)
36 import Gargantext.Database.Action.Flow.Types (FlowCmdM)
37 import Gargantext.Database.Action.Node (mkNodeWithParent)
38 import Gargantext.Database.Admin.Config
39 import Gargantext.Database.Admin.Types.Node
40 import Gargantext.Database.Prelude (Cmd)
41 import Gargantext.Database.Query.Table.Node
42 import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
43 import Gargantext.Database.Query.Table.Node.Select
44 import Gargantext.Database.Query.Table.Node.UpdateOpaleye (updateHyperdata)
45 import Gargantext.Database.Query.Table.Node.User (getNodeUser)
46 import Gargantext.Database.Schema.Node
47 import Gargantext.Database.Schema.Ngrams
48 import Gargantext.Prelude
50 import Servant.Job.Async
52 import qualified Data.HashMap.Strict as HashMap
54 ------------------------------------------------------------------------
55 -- | There is no Delete specific API for Graph since it can be deleted
57 type GraphAPI = Get '[JSON] HyperdataGraphAPI
58 :<|> "async" :> GraphAsyncAPI
60 :> ReqBody '[JSON] HyperdataGraphAPI
61 :> Post '[JSON] NodeId
62 :<|> "gexf" :> Get '[XML] (Headers '[Servant.Header "Content-Disposition" Text] Graph)
63 :<|> "versions" :> GraphVersionsAPI
66 GraphVersions { gv_graph :: Maybe Int
69 deriving (Show, Generic)
71 instance FromJSON GraphVersions
72 instance ToJSON GraphVersions
73 instance ToSchema GraphVersions
75 graphAPI :: UserId -> NodeId -> GargServer GraphAPI
76 graphAPI u n = getGraph u n
80 :<|> graphVersionsAPI u n
82 ------------------------------------------------------------------------
83 --getGraph :: UserId -> NodeId -> GargServer HyperdataGraphAPI
84 getGraph :: FlowCmdM env err m
87 -> m HyperdataGraphAPI
88 getGraph _uId nId = do
89 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
92 graph = nodeGraph ^. node_hyperdata . hyperdataGraph
93 camera = nodeGraph ^. node_hyperdata . hyperdataCamera
95 mcId <- getClosestParentIdByType nId NodeCorpus
96 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
98 -- printDebug "[getGraph] getting list for cId" cId
99 listId <- defaultList cId
100 repo <- getRepo [listId]
102 -- TODO Distance in Graph params
105 let defaultMetric = Order1
106 let defaultPartitionMethod = Spinglass
107 let defaultEdgesStrength = Strong
108 graph' <- computeGraph cId defaultPartitionMethod (withMetric defaultMetric) defaultEdgesStrength NgramsTerms repo
109 mt <- defaultGraphMetadata cId "Title" repo defaultMetric defaultEdgesStrength
111 graph'' = set graph_metadata (Just mt) graph'
112 hg = HyperdataGraphAPI graph'' camera
113 -- _ <- updateHyperdata nId hg
114 _ <- updateHyperdata nId (HyperdataGraph (Just graph'') camera)
115 pure $ trace "[G.V.G.API] Graph empty, computing" hg
117 Just graph' -> pure $ trace "[G.V.G.API] Graph exists, returning" $
118 HyperdataGraphAPI graph' camera
121 --recomputeGraph :: UserId -> NodeId -> Maybe GraphMetric -> GargNoServer Graph
122 recomputeGraph :: FlowCmdM env err m
130 recomputeGraph _uId nId method maybeDistance maybeStrength force = do
131 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
133 graph = nodeGraph ^. node_hyperdata . hyperdataGraph
134 camera = nodeGraph ^. node_hyperdata . hyperdataCamera
135 graphMetadata = graph ^? _Just . graph_metadata . _Just
136 listVersion = graph ^? _Just . graph_metadata . _Just . gm_list . lfg_version
137 graphMetric = case maybeDistance of
138 Nothing -> graph ^? _Just . graph_metadata . _Just . gm_metric
140 similarity = case graphMetric of
141 Nothing -> withMetric Order1
142 Just m -> withMetric m
144 strength = case maybeStrength of
145 Nothing -> case graph ^? _Just . graph_metadata . _Just . gm_edgesStrength of
147 Just mr -> fromMaybe Strong mr
150 mcId <- getClosestParentIdByType nId NodeCorpus
151 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
153 listId <- defaultList cId
154 repo <- getRepo [listId]
155 let v = repo ^. unNodeStory . at listId . _Just . a_version
158 !g <- computeGraph cId method similarity strength NgramsTerms repo
159 let g' = set graph_metadata mt g
160 _nentries <- updateHyperdata nId (HyperdataGraph (Just g') camera)
165 mt <- defaultGraphMetadata cId "Title" repo (fromMaybe Order1 maybeDistance) strength
166 g <- computeG $ Just mt
167 pure $ trace "[G.V.G.API.recomputeGraph] Graph empty, computed" g
168 Just graph' -> if (listVersion == Just v) && (not force)
171 g <- computeG graphMetadata
172 pure $ trace "[G.V.G.API] Graph exists, recomputing" g
175 computeGraph :: FlowCmdM env err m
183 computeGraph cId method d strength nt repo = do
184 lId <- defaultList cId
185 lIds <- selectNodesWithUsername NodeList userMaster
186 let ngs = filterListWithRoot [MapTerm]
187 $ mapTermListRoot [lId] nt repo
189 !myCooc <- HashMap.filter (>1) -- Removing the hapax (ngrams with 1 cooc)
190 <$> getCoocByNgrams (Diagonal True)
191 <$> groupNodesByNgrams ngs
192 <$> getContextsByNgramsOnlyUser cId (lIds <> [lId]) nt (HashMap.keys ngs)
194 graph <- liftBase $ cooc2graphWith method d 0 strength myCooc
196 --listNgrams <- getListNgrams [lId] nt
197 --let graph' = mergeGraphNgrams graph (Just listNgrams)
198 -- saveAsFileDebug "/tmp/graphWithNodes" graph'
203 defaultGraphMetadata :: HasNodeError err
209 -> Cmd err GraphMetadata
210 defaultGraphMetadata cId t repo gm str = do
211 lId <- defaultList cId
213 pure $ GraphMetadata { _gm_title = t
215 , _gm_edgesStrength = Just str
216 , _gm_corpusId = [cId]
218 LegendField 1 "#FFF" "Cluster1"
219 , LegendField 2 "#FFF" "Cluster2"
220 , LegendField 3 "#FFF" "Cluster3"
221 , LegendField 4 "#FFF" "Cluster4"
223 , _gm_list = (ListForGraph lId (repo ^. unNodeStory . at lId . _Just . a_version))
224 , _gm_startForceAtlas = True
226 -- (map (\n -> LegendField n "#FFFFFF" (pack $ show n)) [1..10])
228 ------------------------------------------------------------
229 type GraphAsyncAPI = Summary "Recompute graph"
231 :> AsyncJobsAPI JobLog () JobLog
234 graphAsync :: UserId -> NodeId -> GargServer GraphAsyncAPI
237 JobFunction (\_ log' -> graphRecompute u n (liftBase . log'))
240 --graphRecompute :: UserId
242 -- -> (JobLog -> GargNoServer ())
243 -- -> GargNoServer JobLog
244 graphRecompute :: FlowCmdM env err m
249 graphRecompute u n logStatus = do
250 logStatus JobLog { _scst_succeeded = Just 0
251 , _scst_failed = Just 0
252 , _scst_remaining = Just 1
253 , _scst_events = Just []
255 _g <- recomputeGraph u n Spinglass Nothing Nothing False
256 pure JobLog { _scst_succeeded = Just 1
257 , _scst_failed = Just 0
258 , _scst_remaining = Just 0
259 , _scst_events = Just []
262 ------------------------------------------------------------
263 type GraphVersionsAPI = Summary "Graph versions"
264 :> Get '[JSON] GraphVersions
265 :<|> Summary "Recompute graph version"
266 :> Post '[JSON] Graph
268 graphVersionsAPI :: UserId -> NodeId -> GargServer GraphVersionsAPI
269 graphVersionsAPI u n =
271 :<|> recomputeVersions u n
273 graphVersions :: Int -> NodeId -> GargNoServer GraphVersions
274 graphVersions n nId = do
275 nodeGraph <- getNodeWith nId (Proxy :: Proxy HyperdataGraph)
288 mcId <- getClosestParentIdByType nId NodeCorpus
289 let cId = maybe (panic "[G.V.G.API] Node has no parent") identity mcId
291 maybeListId <- defaultListMaybe cId
294 then graphVersions (n+1) cId
295 else panic "[G.V.G.API] list not found after iterations"
298 repo <- getRepo [listId]
299 let v = repo ^. unNodeStory . at listId . _Just . a_version
300 -- printDebug "graphVersions" v
302 pure $ GraphVersions { gv_graph = listVersion
305 --recomputeVersions :: UserId -> NodeId -> GargNoServer Graph
306 recomputeVersions :: FlowCmdM env err m
310 recomputeVersions uId nId = recomputeGraph uId nId Spinglass Nothing Nothing False
312 ------------------------------------------------------------
316 -> GargNoServer NodeId
317 graphClone uId pId (HyperdataGraphAPI { _hyperdataAPIGraph = graph
318 , _hyperdataAPICamera = camera }) = do
319 let nodeType = NodeGraph
320 nodeUser <- getNodeUser (NodeId uId)
321 nodeParent <- getNodeWith pId (Proxy :: Proxy HyperdataGraph)
322 let uId' = nodeUser ^. node_user_id
323 nIds <- mkNodeWithParent nodeType (Just pId) uId' $ nodeParent ^. node_name
328 let graphP' = set (graph_metadata . _Just . gm_startForceAtlas) False graphP
330 _ <- updateHyperdata nId (HyperdataGraph (Just graphP') camera)
334 ------------------------------------------------------------
335 --getGraphGexf :: UserId
337 -- -> GargNoServer (Headers '[Servant.Header "Content-Disposition" Text] Graph)
338 getGraphGexf :: FlowCmdM env err m
341 -> m (Headers '[Servant.Header "Content-Disposition" Text] Graph)
342 getGraphGexf uId nId = do
343 HyperdataGraphAPI { _hyperdataAPIGraph = graph } <- getGraph uId nId
344 pure $ addHeader "attachment; filename=graph.gexf" graph