2 Module : Gargantext.Viz.Phylo.Tools
3 Description : Phylomemy Tools to build/manage it
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
13 {-# LANGUAGE NoImplicitPrelude #-}
14 {-# LANGUAGE FlexibleContexts #-}
15 {-# LANGUAGE OverloadedStrings #-}
17 module Gargantext.Viz.Phylo.View.Export
20 import Control.Lens hiding (Level)
22 import Data.GraphViz hiding (DotGraph)
23 import Data.GraphViz.Attributes.Complete hiding (EdgeType)
24 import Data.GraphViz.Types.Generalised (DotGraph)
25 import Data.GraphViz.Types.Monadic
26 import Data.List ((++),unwords,concat,sortOn,nub)
27 import Data.Map (Map,toList,(!))
28 import Data.Maybe (isNothing,fromJust)
29 import Data.Text.Lazy (fromStrict, pack, unpack)
31 import qualified Data.Text as T
32 import qualified Data.Text.Lazy as T'
33 import qualified Data.GraphViz.Attributes.HTML as H
35 import Gargantext.Prelude
36 import Gargantext.Viz.Phylo hiding (Dot)
37 import Gargantext.Viz.Phylo.Tools
39 -- import Debug.Trace (trace)
42 import Prelude (writeFile)
43 import System.FilePath
52 dotToFile :: FilePath -> DotGraph DotId -> IO ()
53 dotToFile filePath dotG = writeFile filePath $ dotToString dotG
55 dotToString :: DotGraph DotId -> [Char]
56 dotToString dotG = unpack (printDotGraph dotG)
59 --------------------------
60 -- | PhyloView to DOT | --
61 --------------------------
63 -- | From http://haroldcarr.com/posts/2014-02-28-using-graphviz-via-haskell.html & https://hackage.haskell.org/package/graphviz
66 -- | To create a custom Graphviz's Attribute
67 setAttr :: AttributeName -> T'.Text -> CustomAttribute
68 setAttr k v = customAttribute k v
71 -- | To create customs Graphviz's Attributes out of some Metrics
72 setAttrFromMetrics :: Map T.Text [Double] -> [CustomAttribute]
73 setAttrFromMetrics a = map (\(k,v) -> setAttr (fromStrict k)
75 $ map show v) $ toList a
78 -- | To transform a PhyloBranchId into a DotId
79 toBranchDotId :: PhyloBranchId -> DotId
80 toBranchDotId (lvl,idx) = fromStrict $ T.pack $ (show lvl) ++ (show idx)
83 -- | To transform a PhyloGroupId into a DotId
84 toNodeDotId :: PhyloGroupId -> DotId
85 toNodeDotId (((d,d'),lvl),idx) = fromStrict $ T.pack $ (show d) ++ (show d') ++ (show lvl) ++ (show idx)
88 -- | To transform a PhyloPeriodId into a DotId
89 toPeriodDotId :: PhyloPeriodId -> DotId
90 toPeriodDotId (d,d') = fromStrict $ T.pack $ (show d) ++ (show d')
93 -- | To transform a PhyloPeriodId into a Graphviz's label
94 toPeriodDotLabel ::PhyloPeriodId -> Label
95 toPeriodDotLabel (d,d') = toDotLabel $ T.pack $ (show d) ++ " " ++ (show d')
98 -- | To get all the Phyloperiods covered by a PhyloView
99 getViewPeriods :: PhyloView -> [PhyloPeriodId]
100 getViewPeriods pv = sortOn fst $ nub $ map (\pn -> (fst . fst) $ pn ^. pn_id) $ pv ^. pv_nodes
103 -- | To get for each PhyloBranch, their corresponding oldest PhyloNodes
104 getFirstNodes :: Level -> PhyloView -> [(PhyloBranchId,PhyloGroupId)]
105 getFirstNodes lvl pv = concat
106 $ map (\bId -> map (\pn -> (bId,pn ^. pn_id))
107 $ filterNodesByFirstPeriod
108 $ filterNodesByBranch bId
109 $ filterNodesByLevel lvl
110 $ pv ^. pv_nodes) bIds
112 --------------------------------------
113 bIds :: [PhyloBranchId]
114 bIds = map getBranchId $ filterBranchesByLevel lvl pv
115 --------------------------------------
118 -- | To transform a Text into a Graphviz's Label
119 toDotLabel :: T.Text -> Label
120 toDotLabel lbl = StrLabel $ fromStrict lbl
123 -- | To set a Peak Node
124 setPeakDotNode :: PhyloBranch -> Dot DotId
125 setPeakDotNode pb = node (toBranchDotId $ pb ^. pb_id)
126 ([FillColor [toWColor CornSilk], FontName "Arial", FontSize 40, Shape Egg, Style [SItem Bold []], Label (toDotLabel $ pb ^. pb_peak)]
127 <> (setAttrFromMetrics $ pb ^. pb_metrics)
128 <> [ setAttr "nodeType" "peak"
129 , setAttr "branchId" ((pack $ show (fst $ getBranchId pb)) <> (pack $ show (snd $ getBranchId pb)))
133 -- | To set a Peak Edge
134 setPeakDotEdge :: DotId -> DotId -> Dot DotId
135 setPeakDotEdge bId nId = edge bId nId [Width 3, Color [toWColor Black], ArrowHead (AType [(ArrMod FilledArrow RightSide,DotArrow)])]
138 colorFromDynamics :: Double -> H.Attribute
140 | d == 0 = H.BGColor (toColor LightCoral)
141 | d == 1 = H.BGColor (toColor Khaki)
142 | d == 2 = H.BGColor (toColor SkyBlue)
143 | otherwise = H.Color (toColor Black)
146 getGroupDynamic :: [Double] -> H.Attribute
148 | elem 0 dy = colorFromDynamics 0
149 | elem 1 dy = colorFromDynamics 1
150 | elem 2 dy = colorFromDynamics 2
151 | otherwise = colorFromDynamics 3
154 -- | To set an HTML table
155 setHtmlTable :: PhyloNode -> H.Label
156 setHtmlTable pn = H.Table H.HTable
157 { H.tableFontAttrs = Just [H.PointSize 14, H.Align H.HLeft]
158 , H.tableAttrs = [H.Border 0, H.CellBorder 0, H.BGColor (toColor White)]
159 , H.tableRows = [header]
160 <> [H.Cells [H.LabelCell [H.Height 10] $ H.Text [H.Str $ fromStrict ""]]]
161 <> (if isNothing $ pn ^. pn_ngrams
163 else map ngramsToRow $ splitEvery 4
164 $ reverse $ sortOn (snd . snd)
165 $ zip (fromJust $ pn ^. pn_ngrams) $ zip dynamics inclusion) }
167 --------------------------------------
168 ngramsToRow :: [(Ngrams,(Double,Double))] -> H.Row
169 ngramsToRow ns = H.Cells $ map (\(n,(d,_)) -> H.LabelCell [H.Align H.HLeft,colorFromDynamics d]
170 $ H.Text [H.Str $ fromStrict n]) ns
171 --------------------------------------
172 inclusion :: [Double]
173 inclusion = (pn ^. pn_metrics) ! "inclusion"
174 --------------------------------------
176 dynamics = (pn ^. pn_metrics) ! "dynamics"
177 --------------------------------------
179 header = H.Cells [H.LabelCell [getGroupDynamic dynamics]
180 $ H.Text [H.Str $ (((fromStrict . T.toUpper) $ pn ^. pn_label)
181 <> (fromStrict " ( ")
182 <> (pack $ show (fst $ getNodePeriod pn))
183 <> (fromStrict " , ")
184 <> (pack $ show (snd $ getNodePeriod pn))
185 <> (fromStrict " ) "))]]
186 --------------------------------------
190 setDotNode :: PhyloNode -> Dot DotId
191 setDotNode pn = node (toNodeDotId $ pn ^. pn_id)
192 ([FontName "Arial", Shape Square, toLabel (setHtmlTable pn)]
193 <> [ setAttr "nodeType" "group"
194 , setAttr "from" (pack $ show (fst $ getNodePeriod pn))
195 , setAttr "to" (pack $ show (fst $ getNodePeriod pn))
196 , setAttr "branchId" ((pack $ show (fst $ getNodeBranchId pn)) <> (pack $ show (snd $ getNodeBranchId pn)))
201 setDotEdge :: PhyloEdge -> Dot DotId
203 | pe ^. pe_weight == 100 = edge (toNodeDotId $ pe ^. pe_source) (toNodeDotId $ pe ^. pe_target) [Width 2, Color [toWColor Red]]
204 | otherwise = edge (toNodeDotId $ pe ^. pe_source) (toNodeDotId $ pe ^. pe_target) [Width 2, Color [toWColor Black], Constraint True]
207 -- | To set a Period Edge
208 setDotPeriodEdge :: (PhyloPeriodId,PhyloPeriodId) -> Dot DotId
209 setDotPeriodEdge (prd,prd') = edge (toPeriodDotId prd) (toPeriodDotId prd') [Width 5, Color [toWColor Black]]
212 -- | To transform a given PhyloView into the corresponding GraphViz Graph (ie: Dot format)
213 viewToDot :: PhyloView -> DotGraph DotId
214 viewToDot pv = digraph ((Str . fromStrict) $ pv ^. pv_title)
218 -- set the global graph attributes
220 graphAttrs ( [Label (toDotLabel $ pv ^. pv_title)]
221 <> [setAttr "description" $ fromStrict $ pv ^. pv_description]
222 <> [setAttr "filiation" $ (pack . show) $ pv ^. pv_filiation]
223 <> (setAttrFromMetrics $ pv ^. pv_metrics)
224 <> [FontSize 30, LabelLoc VTop, NodeSep 1, RankSep [1], Rank SameRank, Splines SplineEdges, Overlap ScaleOverlaps
226 , Style [SItem Filled []],Color [toWColor White]])
230 subgraph (Str "Peaks") $ do
232 graphAttrs [Rank SameRank]
234 mapM setPeakDotNode $ filterBranchesByLevel (pv ^. pv_level) pv
236 -- set the nodes, period by period
239 subgraph (Str $ fromStrict $ T.pack $ "subGraph " ++ (show $ (fst prd)) ++ (show $ (snd prd)))
243 graphAttrs [Rank SameRank]
245 -- set the period label
247 node (toPeriodDotId prd) ([Shape Square, FontSize 50, Label (toPeriodDotLabel prd)]
248 <> [setAttr "nodeType" "period",
249 setAttr "from" (fromStrict $ T.pack $ (show $ fst prd)),
250 setAttr "to" (fromStrict $ T.pack $ (show $ snd prd))])
252 mapM setDotNode $ filterNodesByPeriod prd $ filterNodesByLevel (pv ^. pv_level) (pv ^.pv_nodes)
254 ) $ (pv ^. pv_periods)
256 -- set the edges : from peaks to nodes, from nodes to nodes, from periods to periods
258 _ <- mapM (\(bId,nId) -> setPeakDotEdge (toBranchDotId bId) (toNodeDotId nId)) $ getFirstNodes (pv ^. pv_level) pv
260 _ <- mapM setDotEdge $ filterEdgesByLevel (pv ^. pv_level) $ filterEdgesByType PeriodEdge (pv ^. pv_edges)
262 mapM setDotPeriodEdge $ listToSequentialCombi $ (pv ^. pv_periods)