2 Module : Gargantext.Viz.Graph
3 Description : Graph utils
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
12 {-# LANGUAGE NoImplicitPrelude #-}
13 {-# LANGUAGE TemplateHaskell #-}
14 {-# LANGUAGE DeriveGeneric #-}
16 module Gargantext.Viz.Graph
19 ------------------------------------------------------------------------
20 import GHC.IO (FilePath)
21 import GHC.Generics (Generic)
22 import Data.Aeson.TH (deriveJSON)
23 import qualified Data.Aeson as DA
25 import Data.ByteString.Lazy as DBL (readFile, writeFile)
27 import Data.Text (Text, pack)
28 import qualified Text.Read as T
29 import qualified Data.Text as T
31 import Data.Map.Strict (Map)
32 import qualified Data.Map.Strict as M
34 import Data.Swagger (ToSchema)
36 import Gargantext.Prelude
37 import Gargantext.Core.Types (Label)
38 import Gargantext.Core.Utils.Prefix (unPrefix)
40 import Data.Graph.Clustering.Louvain.CplusPlus (LouvainNode(..))
42 import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)
43 import Test.QuickCheck (elements)
45 ------------------------------------------------------------------------
47 data TypeNode = Terms | Unknown
48 deriving (Show, Generic)
50 $(deriveJSON (unPrefix "") ''TypeNode)
52 data Attributes = Attributes { clust_default :: Int }
53 deriving (Show, Generic)
54 $(deriveJSON (unPrefix "") ''Attributes)
56 data Node = Node { node_size :: Int
57 , node_type :: TypeNode
60 , node_attributes :: Attributes
62 deriving (Show, Generic)
63 $(deriveJSON (unPrefix "node_") ''Node)
65 data Edge = Edge { edge_source :: Text
67 , edge_weight :: Double
70 deriving (Show, Generic)
71 $(deriveJSON (unPrefix "edge_") ''Edge)
73 data Graph = Graph { graph_nodes :: [Node]
74 , graph_edges :: [Edge]
76 deriving (Show, Generic)
77 $(deriveJSON (unPrefix "graph_") ''Graph)
79 -- | Intances for Swagger documentation
80 instance ToSchema Node
81 instance ToSchema TypeNode
82 instance ToSchema Attributes
83 instance ToSchema Edge
84 instance ToSchema Graph
87 defaultGraph = Graph {graph_nodes = [Node {node_size = 4, node_type = Terms, node_id = pack "0", node_label = pack "animal", node_attributes = Attributes {clust_default = 0}},Node {node_size = 3, node_type = Terms, node_id = pack "1", node_label = pack "bird", node_attributes = Attributes {clust_default = 0}},Node {node_size = 2, node_type = Terms, node_id = pack "2", node_label = pack "boy", node_attributes = Attributes {clust_default = 1}},Node {node_size = 2, node_type = Terms, node_id = pack "3", node_label = pack "dog", node_attributes = Attributes {clust_default = 0}},Node {node_size = 2, node_type = Terms, node_id = pack "4", node_label = pack "girl", node_attributes = Attributes {clust_default = 1}},Node {node_size = 4, node_type = Terms, node_id = pack "5", node_label = pack "human body", node_attributes = Attributes {clust_default = 1}},Node {node_size = 3, node_type = Terms, node_id = pack "6", node_label = pack "object", node_attributes = Attributes {clust_default = 2}},Node {node_size = 2, node_type = Terms, node_id = pack "7", node_label = pack "pen", node_attributes = Attributes {clust_default = 2}},Node {node_size = 2, node_type = Terms, node_id = pack "8", node_label = pack "table", node_attributes = Attributes {clust_default = 2}}], graph_edges = [Edge {edge_source = pack "0", edge_target = pack "0", edge_weight = 1.0, edge_id = pack "0"},Edge {edge_source = pack "1", edge_target = pack "0", edge_weight = 1.0, edge_id = pack "1"},Edge {edge_source = pack "1", edge_target = pack "1", edge_weight = 1.0, edge_id = pack "2"},Edge {edge_source = pack "2", edge_target = pack "2", edge_weight = 1.0, edge_id = pack "3"},Edge {edge_source = pack "2", edge_target = pack "5", edge_weight = 1.0, edge_id = pack "4"},Edge {edge_source = pack "3", edge_target = pack "0", edge_weight = 1.0, edge_id = pack "5"},Edge {edge_source = pack "3", edge_target = pack "1", edge_weight = 1.0, edge_id = pack "6"},Edge {edge_source = pack "3", edge_target = pack "3", edge_weight = 1.0, edge_id = pack "7"},Edge {edge_source = pack "4", edge_target = pack "4", edge_weight = 1.0, edge_id = pack "8"},Edge {edge_source = pack "4", edge_target = pack "5", edge_weight = 1.0, edge_id = pack "9"},Edge {edge_source = pack "5", edge_target = pack "5", edge_weight = 1.0, edge_id = pack "10"},Edge {edge_source = pack "6", edge_target = pack "6", edge_weight = 1.0, edge_id = pack "11"},Edge {edge_source = pack "7", edge_target = pack "6", edge_weight = 1.0, edge_id = pack "12"},Edge {edge_source = pack "7", edge_target = pack "7", edge_weight = 1.0, edge_id = pack "13"},Edge {edge_source = pack "8", edge_target = pack "6", edge_weight = 1.0, edge_id = pack "14"},Edge {edge_source = pack "8", edge_target = pack "7", edge_weight = 1.0, edge_id = pack "15"},Edge {edge_source = pack "8", edge_target = pack "8", edge_weight = 1.0, edge_id = pack "16"}]}
89 -- | Intances for the mack
90 instance Arbitrary Graph where
91 arbitrary = elements $ [defaultGraph]
94 -----------------------------------------------------------
95 -- V3 Gargantext Version
97 data AttributesV3 = AttributesV3 { cl :: Int }
98 deriving (Show, Generic)
99 $(deriveJSON (unPrefix "") ''AttributesV3)
101 data NodeV3 = NodeV3 { no_id :: Int
102 , no_at :: AttributesV3
106 deriving (Show, Generic)
107 $(deriveJSON (unPrefix "no_") ''NodeV3)
109 data EdgeV3 = EdgeV3 { eo_s :: Int
113 deriving (Show, Generic)
114 $(deriveJSON (unPrefix "eo_") ''EdgeV3)
116 data GraphV3 = GraphV3 {
118 , go_nodes :: [NodeV3]
120 deriving (Show, Generic)
121 $(deriveJSON (unPrefix "go_") ''GraphV3)
123 ----------------------------------------------------------
124 -- | From data to Graph
125 -- FIXME: distance should not be a map since we just "toList" it (same as cLouvain)
126 data2graph :: [(Label, Int)] -> Map (Int, Int) Int
127 -> Map (Int, Int) Double
130 data2graph labels coocs distance partitions = Graph nodes edges
132 community_id_by_node_id = M.fromList [ (n, c) | LouvainNode n c <- partitions ]
133 nodes = [ Node { node_size = maybe 0 identity (M.lookup (n,n) coocs)
134 , node_type = Terms -- or Unknown
135 , node_id = cs (show n)
136 , node_label = T.unwords l
138 Attributes { clust_default = maybe 0 identity
139 (M.lookup n community_id_by_node_id) } }
141 edges = [ Edge { edge_source = cs (show s)
142 , edge_target = cs (show t)
144 , edge_id = cs (show i) }
145 | (i, ((s,t), w)) <- zip ([0..]::[Integer]) (M.toList distance) ]
146 -----------------------------------------------------------
147 -----------------------------------------------------------
149 graphV3ToGraph :: GraphV3 -> Graph
150 graphV3ToGraph (GraphV3 links nodes) = Graph (map nodeV32node nodes) (zipWith linkV32edge [1..] links)
152 nodeV32node :: NodeV3 -> Node
153 nodeV32node (NodeV3 no_id' (AttributesV3 cl') no_s' no_lb')
154 = Node no_s' Terms (cs $ show no_id') no_lb' (Attributes cl')
156 linkV32edge :: Int -> EdgeV3 -> Edge
157 linkV32edge n (EdgeV3 eo_s' eo_t' eo_w') = Edge (cs $ show eo_s') (cs $ show eo_t') ((T.read $ T.unpack eo_w') :: Double) (cs $ show n)
160 graphV3ToGraphWithFiles :: FilePath -> FilePath -> IO ()
161 graphV3ToGraphWithFiles g1 g2 = do
162 -- GraphV3 <- IO Fichier
163 graph <- DBL.readFile g1
164 let newGraph = case DA.decode graph :: Maybe GraphV3 of
165 Nothing -> panic (T.pack "no graph")
168 DBL.writeFile g2 (DA.encode $ graphV3ToGraph newGraph)
170 readGraphFromJson :: FilePath -> IO (Maybe Graph)
171 readGraphFromJson fp = do
172 graph <- DBL.readFile fp
173 pure $ DA.decode graph