2 Module : Gargantext.Core.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
13 module Gargantext.Core.Viz.Graph.Tools
16 import Debug.Trace (trace)
17 import Data.Graph.Clustering.Louvain.Utils (LouvainNode(..))
18 -- import Data.Graph.Clustering.Louvain (hLouvain, {-iLouvainMap-})
19 import Data.Graph.Clustering.Louvain.CplusPlus (cLouvain)
21 import qualified Data.Set as Set
22 import Data.Text (Text)
23 import Gargantext.Prelude
24 import Gargantext.Core.Statistics
25 import Gargantext.Core.Viz.Graph
26 import Gargantext.Core.Viz.Graph.Bridgeness (bridgeness)
27 import Gargantext.Core.Viz.Graph.Distances (Distance(..), measure)
28 import Gargantext.Core.Viz.Graph.Index (createIndices, toIndex, map2mat, mat2map, Index)
29 import Gargantext.Core.Viz.Graph.IGraph (mkGraphUfromEdges)
30 import Gargantext.Core.Viz.Graph.Proxemy (confluence)
31 import GHC.Float (sin, cos)
32 import qualified IGraph as Igraph
33 import qualified IGraph.Algorithms.Layout as Layout
34 import qualified Data.Vector.Storable as Vec
35 import qualified Data.Map as Map
36 import qualified Data.List as List
38 type Threshold = Double
41 cooc2graph' :: Ord t => Distance
44 -> Map (Index, Index) Double
45 cooc2graph' distance 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 = measure distance matCooc
51 distanceMap = Map.filter (> threshold) $ mat2map distanceMat
54 cooc2graph :: Distance
56 -> (Map (Text, Text) Int)
58 cooc2graph distance threshold myCooc = do
59 printDebug "cooc2graph" distance
61 (ti, _) = createIndices myCooc
62 myCooc' = toIndex ti myCooc
63 matCooc = map2mat 0 (Map.size ti)
64 $ Map.filterWithKey (\(a,b) _ -> a /= b)
65 $ Map.filter (> 1) myCooc'
66 distanceMat = measure distance matCooc
67 distanceMap = Map.filter (> threshold) $ mat2map distanceMat
72 (as, bs) = List.unzip $ Map.keys distanceMap
73 n' = Set.size $ Set.fromList $ as <> bs
74 ClustersParams rivers level = clustersParams nodesApprox
77 partitions <- if (Map.size distanceMap > 0)
78 -- then iLouvainMap 100 10 distanceMap
79 -- then hLouvain distanceMap
80 then cLouvain level distanceMap
81 else panic "Text.Flow: DistanceMap is empty"
84 -- bridgeness' = distanceMap
85 bridgeness' = trace ("Rivers: " <> show rivers)
86 $ bridgeness rivers partitions distanceMap
87 confluence' = confluence (Map.keys bridgeness') 3 True False
89 pure $ data2graph (Map.toList ti) myCooc' bridgeness' confluence' partitions
93 data ClustersParams = ClustersParams { bridgness :: Double
97 clustersParams :: Int -> ClustersParams
98 clustersParams x = ClustersParams (fromIntegral x) "0.00000001" -- y
100 y | x < 100 = "0.000001"
101 | x < 350 = "0.000001"
102 | x < 500 = "0.000001"
103 | x < 1000 = "0.000001"
107 ----------------------------------------------------------
108 -- | From data to Graph
109 data2graph :: [(Text, Int)]
110 -> Map (Int, Int) Int
111 -> Map (Int, Int) Double
112 -> Map (Int, Int) Double
115 data2graph labels coocs bridge conf partitions = Graph nodes edges Nothing
118 community_id_by_node_id = Map.fromList [ (n, c) | LouvainNode n c <- partitions ]
120 nodes = map (setCoord ForceAtlas labels bridge)
121 [ (n, Node { node_size = maybe 0 identity (Map.lookup (n,n) coocs)
122 , node_type = Terms -- or Unknown
123 , node_id = cs (show n)
128 Attributes { clust_default = maybe 0 identity
129 (Map.lookup n community_id_by_node_id) } }
132 , Set.member n $ Set.fromList
134 $ map (\((s,t),d) -> if d > 0 && s /=t then [s,t] else [])
138 edges = [ Edge { edge_source = cs (show s)
139 , edge_target = cs (show t)
141 , edge_confluence = maybe 0 identity $ Map.lookup (s,t) conf
142 -- , edge_confluence = maybe (panic "E: data2graph edges") identity $ Map.lookup (s,t) conf
143 , edge_id = cs (show i) }
144 | (i, ((s,t), d)) <- zip ([0..]::[Integer]) (Map.toList bridge), s /= t, d > 0
148 ------------------------------------------------------------------------
150 data Layout = KamadaKawai | ACP | ForceAtlas
153 setCoord' :: (Int -> (Double, Double)) -> (Int, Node) -> Node
154 setCoord' f (i,n) = n { node_x_coord = x, node_y_coord = y }
160 setCoord :: Ord a => Layout -> [(a, Int)] -> Map (Int, Int) Double -> (Int, Node) -> Node
161 setCoord l labels m (n,node) = node { node_x_coord = x
165 (x,y) = getCoord l labels m n
171 -> Map (Int, Int) Double
174 getCoord KamadaKawai _ _m _n = undefined -- layout m n
176 getCoord ForceAtlas _ _ n = (sin d, cos d)
180 getCoord ACP labels m n = to2d $ maybe (panic "Graph.Tools no coordinate") identity
182 $ pcaReduceTo (Dimension 2)
185 to2d :: Vec.Vector Double -> (Double, Double)
188 ds = take 2 $ Vec.toList v
192 mapArray :: Ord a => [(a, Int)] -> Map (Int, Int) Double -> Map Int (Vec.Vector Double)
193 mapArray items m' = Map.fromList [ toVec n' ns m' | n' <- ns ]
197 toVec :: Int -> [Int] -> Map (Int,Int) Double -> (Int, Vec.Vector Double)
198 toVec n' ns' m' = (n', Vec.fromList $ map (\n'' -> maybe 0 identity $ Map.lookup (n',n'') m') ns')
199 ------------------------------------------------------------------------
201 -- | KamadaKawai Layout
202 -- TODO TEST: check labels, nodeId and coordinates
203 layout :: Map (Int, Int) Double -> Int -> IO (Double, Double)
204 layout m n = maybe (panic "") identity <$> Map.lookup n <$> coord
206 coord :: IO (Map Int (Double,Double))
207 coord = Map.fromList <$> List.zip (Igraph.nodes g) <$> (Layout.getLayout g p)
208 --p = Layout.defaultLGL
209 p = Layout.defaultKamadaKawai
210 g = mkGraphUfromEdges $ map fst $ List.filter (\e -> snd e > 0) $ Map.toList m