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 = do
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
73 partitions <- if (Map.size distanceMap > 0)
74 --then iLouvainMap 100 10 distanceMap
75 -- then hLouvain distanceMap
76 then cLouvain level distanceMap
77 else panic "Text.Flow: DistanceMap is empty"
80 bridgeness' = bridgeness rivers partitions distanceMap
81 confluence' = confluence (Map.keys bridgeness') 3 True False
83 pure $ data2graph (Map.toList ti) myCooc' bridgeness' confluence' partitions
87 data ClustersParams = ClustersParams { bridgness :: Double
91 clustersParams :: Int -> ClustersParams
92 clustersParams x = ClustersParams (fromIntegral x) "0.00000001" -- y
94 y | x < 100 = "0.000001"
95 | x < 350 = "0.000001"
96 | x < 500 = "0.000001"
97 | x < 1000 = "0.000001"
101 ----------------------------------------------------------
102 -- | From data to Graph
103 data2graph :: [(Text, Int)]
104 -> Map (Int, Int) Int
105 -> Map (Int, Int) Double
106 -> Map (Int, Int) Double
109 data2graph labels coocs bridge conf partitions = Graph nodes edges Nothing
112 community_id_by_node_id = Map.fromList [ (n, c) | LouvainNode n c <- partitions ]
114 nodes = map (setCoord ForceAtlas labels bridge)
115 [ (n, Node { node_size = maybe 0 identity (Map.lookup (n,n) coocs)
116 , node_type = Terms -- or Unknown
117 , node_id = cs (show n)
122 Attributes { clust_default = maybe 0 identity
123 (Map.lookup n community_id_by_node_id) } }
126 , Set.member n $ Set.fromList
128 $ map (\((s,t),d) -> if d > 0 && s /=t then [s,t] else [])
132 edges = [ Edge { edge_source = cs (show s)
133 , edge_target = cs (show t)
135 , edge_confluence = maybe 0 identity $ Map.lookup (s,t) conf
136 -- , edge_confluence = maybe (panic "E: data2graph edges") identity $ Map.lookup (s,t) conf
137 , edge_id = cs (show i) }
138 | (i, ((s,t), d)) <- zip ([0..]::[Integer]) (Map.toList bridge), s /= t, d > 0
142 ------------------------------------------------------------------------
144 data Layout = KamadaKawai | ACP | ForceAtlas
147 setCoord' :: (Int -> (Double, Double)) -> (Int, Node) -> Node
148 setCoord' f (i,n) = n { node_x_coord = x, node_y_coord = y }
154 setCoord :: Ord a => Layout -> [(a, Int)] -> Map (Int, Int) Double -> (Int, Node) -> Node
155 setCoord l labels m (n,node) = node { node_x_coord = x
159 (x,y) = getCoord l labels m n
162 getCoord :: Ord a => Layout
163 -> [(a, Int)] -> Map (Int, Int) Double -> Int -> (Double, Double)
164 getCoord KamadaKawai _ _m _n = undefined -- layout m n
166 getCoord ForceAtlas _ _ n = (sin d, cos d)
170 getCoord ACP labels m n = to2d $ maybe (panic "Graph.Tools no coordinate") identity
172 $ pcaReduceTo (Dimension 2)
175 to2d :: Vec.Vector Double -> (Double, Double)
178 ds = take 2 $ Vec.toList v
182 mapArray :: Ord a => [(a, Int)] -> Map (Int, Int) Double -> Map Int (Vec.Vector Double)
183 mapArray items m' = Map.fromList [ toVec n' ns m' | n' <- ns ]
187 toVec :: Int -> [Int] -> Map (Int,Int) Double -> (Int, Vec.Vector Double)
188 toVec n' ns' m' = (n', Vec.fromList $ map (\n'' -> maybe 0 identity $ Map.lookup (n',n'') m') ns')
189 ------------------------------------------------------------------------
191 -- | KamadaKawai Layout
192 -- TODO TEST: check labels, nodeId and coordinates
193 layout :: Map (Int, Int) Double -> Int -> IO (Double, Double)
194 layout m n = maybe (panic "") identity <$> Map.lookup n <$> coord
196 coord :: IO (Map Int (Double,Double))
197 coord = Map.fromList <$> List.zip (Igraph.nodes g) <$> (Layout.getLayout g p)
198 --p = Layout.defaultLGL
199 p = Layout.defaultKamadaKawai
200 g = mkGraphUfromEdges $ map fst $ List.filter (\e -> snd e > 0) $ Map.toList m