2 Module : Gargantext.Viz.Graph.Tools
3 Description : Tools to build Graph
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
12 {-# LANGUAGE NoImplicitPrelude #-}
13 {-# LANGUAGE OverloadedStrings #-}
15 module Gargantext.Viz.Graph.Tools
18 import Data.Graph.Clustering.Louvain.Utils (LouvainNode(..))
19 import Data.Graph.Clustering.Louvain (hLouvain, {-iLouvainMap-})
20 -- import Data.Graph.Clustering.Louvain.CplusPlus (cLouvain)
22 import qualified Data.Set as Set
23 import Data.Text (Text)
24 import Gargantext.Prelude
25 import Gargantext.Core.Statistics
26 import Gargantext.Viz.Graph
27 import Gargantext.Viz.Graph.Bridgeness (bridgeness)
28 import Gargantext.Viz.Graph.Distances.Matrice (measureConditional)
29 import Gargantext.Viz.Graph.Index (createIndices, toIndex, map2mat, mat2map, Index)
30 import Gargantext.Viz.Graph.IGraph (mkGraphUfromEdges)
31 import Gargantext.Viz.Graph.Proxemy (confluence)
32 import GHC.Float (sin, cos)
33 import qualified IGraph as Igraph
34 import qualified IGraph.Algorithms.Layout as Layout
35 import qualified Data.Vector.Storable as Vec
36 import qualified Data.Map as Map
37 import qualified Data.List as List
39 type Threshold = Double
42 cooc2graph' :: Ord t => Double
44 -> Map (Index, Index) Double
45 cooc2graph' threshold myCooc = distanceMap
47 (ti, _) = createIndices myCooc
48 myCooc' = toIndex ti myCooc
49 matCooc = map2mat 0 (Map.size ti) $ Map.filter (> 1) myCooc'
50 distanceMat = measureConditional matCooc
51 distanceMap = Map.filter (> threshold) $ mat2map distanceMat
54 cooc2graph :: Threshold
55 -> (Map (Text, Text) Int)
57 cooc2graph threshold myCooc = data2graph (Map.toList ti) myCooc' bridgeness' confluence' partitions
59 (ti, _) = createIndices myCooc
60 myCooc' = toIndex ti myCooc
61 matCooc = map2mat 0 (Map.size ti) $ Map.filter (> 1) myCooc'
62 distanceMat = measureConditional matCooc
63 distanceMap = Map.filter (> threshold) $ mat2map distanceMat
68 (as, bs) = List.unzip $ Map.keys distanceMap
69 n' = Set.size $ Set.fromList $ as <> bs
70 ClustersParams rivers _level = clustersParams nodesApprox
72 partitions = if (Map.size distanceMap > 0)
73 --then iLouvainMap 100 10 distanceMap
74 then hLouvain distanceMap
75 else panic "Text.Flow: DistanceMap is empty"
76 -- True -> trace ("level" <> show level) $ cLouvain level distanceMap
78 bridgeness' = bridgeness rivers partitions distanceMap
80 confluence' = confluence (Map.keys bridgeness') 3 True False
88 data ClustersParams = ClustersParams { bridgness :: Double
92 clustersParams :: Int -> ClustersParams
93 clustersParams x = ClustersParams (fromIntegral x) "0.00000001" -- y
95 y | x < 100 = "0.000001"
96 | x < 350 = "0.000001"
97 | x < 500 = "0.000001"
98 | x < 1000 = "0.000001"
102 ----------------------------------------------------------
103 -- | From data to Graph
104 data2graph :: [(Text, Int)]
105 -> Map (Int, Int) Int
106 -> Map (Int, Int) Double
107 -> Map (Int, Int) Double
110 data2graph labels coocs bridge conf partitions = Graph nodes edges Nothing
113 community_id_by_node_id = Map.fromList [ (n, c) | LouvainNode n c <- partitions ]
115 nodes = map (setCoord ForceAtlas labels bridge)
116 [ (n, Node { node_size = maybe 0 identity (Map.lookup (n,n) coocs)
117 , node_type = Terms -- or Unknown
118 , node_id = cs (show n)
123 Attributes { clust_default = maybe 0 identity
124 (Map.lookup n community_id_by_node_id) } }
127 , Set.member n $ Set.fromList
129 $ map (\((s,t),d) -> if d > 0 && s /=t then [s,t] else [])
133 edges = [ Edge { edge_source = cs (show s)
134 , edge_target = cs (show t)
136 , edge_confluence = maybe 0 identity $ Map.lookup (s,t) conf
137 -- , edge_confluence = maybe (panic "E: data2graph edges") identity $ Map.lookup (s,t) conf
138 , edge_id = cs (show i) }
139 | (i, ((s,t), d)) <- zip ([0..]::[Integer]) (Map.toList bridge), s /= t, d > 0
143 ------------------------------------------------------------------------
145 data Layout = KamadaKawai | ACP | ForceAtlas
148 setCoord' :: (Int -> (Double, Double)) -> (Int, Node) -> Node
149 setCoord' f (i,n) = n { node_x_coord = x, node_y_coord = y }
155 setCoord :: Ord a => Layout -> [(a, Int)] -> Map (Int, Int) Double -> (Int, Node) -> Node
156 setCoord l labels m (n,node) = node { node_x_coord = x
160 (x,y) = getCoord l labels m n
163 getCoord :: Ord a => Layout
164 -> [(a, Int)] -> Map (Int, Int) Double -> Int -> (Double, Double)
165 getCoord KamadaKawai _ _m _n = undefined -- layout m n
167 getCoord ForceAtlas _ _ n = (sin d, cos d)
171 getCoord ACP labels m n = to2d $ maybe (panic "Graph.Tools no coordinate") identity
173 $ pcaReduceTo (Dimension 2)
176 to2d :: Vec.Vector Double -> (Double, Double)
179 ds = take 2 $ Vec.toList v
183 mapArray :: Ord a => [(a, Int)] -> Map (Int, Int) Double -> Map Int (Vec.Vector Double)
184 mapArray items m' = Map.fromList [ toVec n' ns m' | n' <- ns ]
188 toVec :: Int -> [Int] -> Map (Int,Int) Double -> (Int, Vec.Vector Double)
189 toVec n' ns' m' = (n', Vec.fromList $ map (\n'' -> maybe 0 identity $ Map.lookup (n',n'') m') ns')
190 ------------------------------------------------------------------------
192 -- | KamadaKawai Layout
193 -- TODO TEST: check labels, nodeId and coordinates
194 layout :: Map (Int, Int) Double -> Int -> IO (Double, Double)
195 layout m n = maybe (panic "") identity <$> Map.lookup n <$> coord
197 coord :: IO (Map Int (Double,Double))
198 coord = Map.fromList <$> List.zip (Igraph.nodes g) <$> (Layout.getLayout g p)
199 --p = Layout.defaultLGL
200 p = Layout.defaultKamadaKawai
201 g = mkGraphUfromEdges $ map fst $ List.filter (\e -> snd e > 0) $ Map.toList m