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 Debug.Trace (trace)
19 import Data.Graph.Clustering.Louvain.CplusPlus (LouvainNode(..))
20 import Data.Graph.Clustering.Louvain.CplusPlus (cLouvain)
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.Matrice (measureConditional)
28 import Gargantext.Viz.Graph.Index (createIndices, toIndex, map2mat, mat2map)
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
40 cooc2graph :: Threshold
41 -> (Map (Text, Text) Int)
43 cooc2graph threshold myCooc = do
44 let (ti, _) = createIndices myCooc
45 myCooc' = toIndex ti myCooc
46 matCooc = map2mat 0 (Map.size ti) $ Map.filter (> (round threshold)) myCooc'
47 distanceMat = measureConditional matCooc
48 distanceMap = Map.filter (> threshold) $ mat2map distanceMat
50 partitions <- case Map.size distanceMap > 0 of
51 True -> cLouvain distanceMap
52 False -> panic "Text.Flow: DistanceMap is empty"
54 let bridgeness' = bridgeness 300 partitions distanceMap
55 let confluence' = confluence (Map.keys bridgeness') 3 True False
57 data2graph (Map.toList ti) myCooc' bridgeness' confluence' partitions
60 ----------------------------------------------------------
61 -- | From data to Graph
62 data2graph :: [(Text, Int)]
64 -> Map (Int, Int) Double
65 -> Map (Int, Int) Double
68 data2graph labels coocs bridge conf partitions = do
70 let community_id_by_node_id = Map.fromList [ (n, c) | LouvainNode n c <- partitions ]
72 nodes <- mapM (setCoord ForceAtlas labels bridge)
73 [ (n, Node { node_size = maybe 0 identity (Map.lookup (n,n) coocs)
74 , node_type = Terms -- or Unknown
75 , node_id = cs (show n)
80 Attributes { clust_default = maybe 0 identity
81 (Map.lookup n community_id_by_node_id) } }
86 let edges = [ Edge { edge_source = cs (show s)
87 , edge_target = cs (show t)
89 , edge_confluence = maybe 0 identity $ Map.lookup (s,t) conf
90 -- , edge_confluence = maybe (panic "E: data2graph edges") identity $ Map.lookup (s,t) conf
91 , edge_id = cs (show i) }
92 | (i, ((s,t), d)) <- zip ([0..]::[Integer]) (Map.toList bridge), s /= t
95 pure $ Graph nodes edges Nothing
97 ------------------------------------------------------------------------
99 data Layout = KamadaKawai | ACP | ForceAtlas
102 setCoord' :: (Int -> (Double, Double)) -> (Int, Node) -> Node
103 setCoord' f (i,n) = n { node_x_coord = x, node_y_coord = y }
109 setCoord :: Ord a => Layout -> [(a, Int)] -> Map (Int, Int) Double -> (Int, Node) -> IO Node
110 setCoord l labels m (n,node) = getCoord l labels m n
111 >>= \(x,y) -> pure $ node { node_x_coord = x
116 getCoord :: Ord a => Layout
117 -> [(a, Int)] -> Map (Int, Int) Double -> Int -> IO (Double, Double)
118 getCoord KamadaKawai _ m n = layout m n
120 getCoord ForceAtlas _ _ n = pure (sin d, cos d)
124 getCoord ACP labels m n = pure $ to2d $ maybe (panic "Graph.Tools no coordinate") identity
126 $ pcaReduceTo (Dimension 2)
129 to2d :: Vec.Vector Double -> (Double, Double)
132 ds = take 2 $ Vec.toList v
136 mapArray :: Ord a => [(a, Int)] -> Map (Int, Int) Double -> Map Int (Vec.Vector Double)
137 mapArray items m' = Map.fromList [ toVec n' ns m' | n' <- ns ]
141 toVec :: Int -> [Int] -> Map (Int,Int) Double -> (Int, Vec.Vector Double)
142 toVec n' ns' m' = (n', Vec.fromList $ map (\n'' -> maybe 0 identity $ Map.lookup (n',n'') m') ns')
143 ------------------------------------------------------------------------
145 -- | KamadaKawai Layout
146 -- TODO TEST: check labels, nodeId and coordinates
147 layout :: Map (Int, Int) Double -> Int -> IO (Double, Double)
148 layout m n = maybe (panic "") identity <$> Map.lookup n <$> coord
150 coord :: IO (Map Int (Double,Double))
151 coord = Map.fromList <$> List.zip (Igraph.nodes g) <$> (Layout.getLayout g p)
152 --p = Layout.defaultLGL
153 p = Layout.defaultKamadaKawai
154 g = mkGraphUfromEdges $ map fst $ List.filter (\e -> snd e > 0) $ Map.toList m