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
13 module Gargantext.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.Viz.Graph
26 import Gargantext.Viz.Graph.Bridgeness (bridgeness)
27 import Gargantext.Viz.Graph.Distances (Distance(..), measure)
28 import Gargantext.Viz.Graph.Index (createIndices, toIndex, map2mat, mat2map, Index)
29 import Gargantext.Viz.Graph.IGraph (mkGraphUfromEdges)
30 import Gargantext.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 => Double
43 -> Map (Index, Index) Double
44 cooc2graph' threshold myCooc = distanceMap
46 (ti, _) = createIndices myCooc
47 myCooc' = toIndex ti myCooc
48 matCooc = map2mat 0 (Map.size ti) $ Map.filter (> 1) myCooc'
49 distanceMat = measure Conditional matCooc
50 distanceMap = Map.filter (> threshold) $ mat2map distanceMat
53 cooc2graph :: Threshold
54 -> (Map (Text, Text) Int)
56 cooc2graph threshold myCooc = do
58 (ti, _) = createIndices myCooc
59 myCooc' = toIndex ti myCooc
60 matCooc = map2mat 0 (Map.size ti) $ Map.filter (> 1) myCooc'
61 distanceMat = measure Conditional matCooc
62 distanceMap = Map.filter (> threshold) $ mat2map distanceMat
67 (as, bs) = List.unzip $ Map.keys distanceMap
68 n' = Set.size $ Set.fromList $ as <> bs
69 ClustersParams rivers level = clustersParams nodesApprox
72 partitions <- if (Map.size distanceMap > 0)
73 -- then iLouvainMap 100 10 distanceMap
74 -- then hLouvain distanceMap
75 then cLouvain level distanceMap
76 else panic "Text.Flow: DistanceMap is empty"
79 -- bridgeness' = distanceMap
80 bridgeness' = trace ("Rivers: " <> show rivers) $ 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
165 -> Map (Int, Int) Double
168 getCoord KamadaKawai _ _m _n = undefined -- layout m n
170 getCoord ForceAtlas _ _ n = (sin d, cos d)
174 getCoord ACP labels m n = to2d $ maybe (panic "Graph.Tools no coordinate") identity
176 $ pcaReduceTo (Dimension 2)
179 to2d :: Vec.Vector Double -> (Double, Double)
182 ds = take 2 $ Vec.toList v
186 mapArray :: Ord a => [(a, Int)] -> Map (Int, Int) Double -> Map Int (Vec.Vector Double)
187 mapArray items m' = Map.fromList [ toVec n' ns m' | n' <- ns ]
191 toVec :: Int -> [Int] -> Map (Int,Int) Double -> (Int, Vec.Vector Double)
192 toVec n' ns' m' = (n', Vec.fromList $ map (\n'' -> maybe 0 identity $ Map.lookup (n',n'') m') ns')
193 ------------------------------------------------------------------------
195 -- | KamadaKawai Layout
196 -- TODO TEST: check labels, nodeId and coordinates
197 layout :: Map (Int, Int) Double -> Int -> IO (Double, Double)
198 layout m n = maybe (panic "") identity <$> Map.lookup n <$> coord
200 coord :: IO (Map Int (Double,Double))
201 coord = Map.fromList <$> List.zip (Igraph.nodes g) <$> (Layout.getLayout g p)
202 --p = Layout.defaultLGL
203 p = Layout.defaultKamadaKawai
204 g = mkGraphUfromEdges $ map fst $ List.filter (\e -> snd e > 0) $ Map.toList m