packages feed

graphviz 2008.9.6 → 2008.9.20

raw patch · 4 files changed

+236/−58 lines, 4 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Data.GraphViz: C :: c -> NodeCluster c a -> NodeCluster c a
+ Data.GraphViz: DotCluster :: String -> [Attribute] -> [DotNode] -> DotNode
+ Data.GraphViz: N :: LNode a -> NodeCluster c a
+ Data.GraphViz: clusterAttributes :: DotNode -> [Attribute]
+ Data.GraphViz: clusterElems :: DotNode -> [DotNode]
+ Data.GraphViz: clusterGraphToDot :: (Ord c, Ord b, Graph gr) => gr a b -> [Attribute] -> (LNode a -> NodeCluster c a) -> (c -> [Attribute]) -> (LNode a -> [Attribute]) -> (LEdge b -> [Attribute]) -> DotGraph
+ Data.GraphViz: clusterID :: DotNode -> String
+ Data.GraphViz: commandFor :: DotGraph -> String
+ Data.GraphViz: data NodeCluster c a
+ Data.GraphViz: directedEdge :: DotEdge -> Bool
+ Data.GraphViz: directedGraph :: DotGraph -> Bool
+ Data.GraphViz: instance (Show c, Show a) => Show (ClusterTree c a)
+ Data.GraphViz: instance (Show c, Show a) => Show (NodeCluster c a)
+ Data.GraphViz.ParserCombinators: strings :: [String] -> Parser Char String
- Data.GraphViz: DotEdge :: Int -> Int -> [Attribute] -> DotEdge
+ Data.GraphViz: DotEdge :: Int -> Int -> [Attribute] -> Bool -> DotEdge
- Data.GraphViz: DotGraph :: [Attribute] -> [DotNode] -> [DotEdge] -> DotGraph
+ Data.GraphViz: DotGraph :: [Attribute] -> [DotNode] -> [DotEdge] -> Bool -> DotGraph
- Data.GraphViz: graphToDot :: (Graph gr) => gr a b -> [Attribute] -> (LNode a -> [Attribute]) -> (LEdge b -> [Attribute]) -> DotGraph
+ Data.GraphViz: graphToDot :: (Ord b, Graph gr) => gr a b -> [Attribute] -> (LNode a -> [Attribute]) -> (LEdge b -> [Attribute]) -> DotGraph
- Data.GraphViz: graphToGraph :: (Graph gr) => gr a b -> [Attribute] -> (LNode a -> [Attribute]) -> (LEdge b -> [Attribute]) -> IO (gr (AttributeNode a) (AttributeEdge b))
+ Data.GraphViz: graphToGraph :: (Ord b, Graph gr) => gr a b -> [Attribute] -> (LNode a -> [Attribute]) -> (LEdge b -> [Attribute]) -> IO (gr (AttributeNode a) (AttributeEdge b))

Files

Data/GraphViz.hs view
@@ -1,30 +1,41 @@-{-# LANGUAGE RecordPuns +{-# LANGUAGE RecordPuns            , ScopedTypeVariables            #-} - {- GraphViz ------------------------------------------------------\+ {--GraphViz ------------------------------------------------------\  |                                                                 |- | Copyright (c) 2008, Matthew Sackman (matthew@wellquite.org)     |+ | Copyright (c) 2008, Matthew Sackman (matthew@wellquite.org),    |+ |              Ivan Lazar Miljenovic (ivan.miljenovic@gmail.com)  |  |                                                                 |- | DisTract is freely distributable under the terms of a 3-Clause  |+ | GraphViz is freely distributable under the terms of a 3-Clause  |  | BSD-style license.                                              |  |                                                                 |  \-----------------------------------------------------------------}  module Data.GraphViz     ( graphToDot+    , clusterGraphToDot     , graphToGraph     , readDotGraph+    , commandFor     , DotGraph (..)     , DotNode (..)     , DotEdge (..)+    , NodeCluster(..)     , AttributeNode     , AttributeEdge     , module Data.GraphViz.Attributes     )     where +-- LT is defined in Attributes+import Prelude hiding (LT)+import qualified Prelude as P+ import Data.Graph.Inductive.Graph+import Data.List+import Data.Function+import qualified Data.Set as Set import Text.ParserCombinators.PolyLazy import System.IO import System.Process@@ -39,59 +50,207 @@ data DotGraph = DotGraph { graphAttributes :: [Attribute]                          , graphNodes :: [DotNode]                          , graphEdges :: [DotEdge]+                         , directedGraph :: Bool                          } -data DotNode = DotNode { nodeID :: Int-                       , nodeAttributes :: [Attribute]-                       }+data DotNode+    = DotNode { nodeID :: Int+              , nodeAttributes :: [Attribute]+              }+    | DotCluster { clusterID         :: String+                 , clusterAttributes :: [Attribute]+                 , clusterElems      :: [DotNode]+                 }  data DotEdge = DotEdge { edgeHeadNodeID :: Int                        , edgeTailNodeID :: Int                        , edgeAttributes :: [Attribute]+                       , directedEdge   :: Bool                        }  instance Show DotNode where-    show (DotNode { nodeID, nodeAttributes })-        | null nodeAttributes = '\t':((show nodeID) ++ ";")-        | otherwise = '\t':((show nodeID) ++ (' ':((show nodeAttributes) ++ ";")))+    show n = init . unlines . addTabs $ nodesToString n +nodesToString :: DotNode -> [String]+nodesToString (DotNode { nodeID, nodeAttributes })+    | null nodeAttributes = [nID ++ ";"]+    | otherwise           = [nID ++ (' ':((show nodeAttributes) ++ ";"))]+    where+      nID = show nodeID+nodesToString (DotCluster { clusterID, clusterAttributes, clusterElems })+    = ["subgraph cluster_" ++ clusterID ++ " {"] ++ (addTabs inner) ++ ["}"]+    where+      inner = case clusterAttributes of+                [] -> nodes+                a  -> ("graph " ++ (show a) ++ ";") : nodes+      nodes = concatMap nodesToString clusterElems++addTabs :: [String] -> [String]+addTabs = map ('\t':)+ instance Show DotEdge where-    show (DotEdge { edgeHeadNodeID, edgeTailNodeID, edgeAttributes })-        = '\t' : ((show edgeTailNodeID) ++ " -> " ++ (show edgeHeadNodeID) ++ attributes)+    show (DotEdge { edgeHeadNodeID, edgeTailNodeID, edgeAttributes, directedEdge })+        = '\t' : ((show edgeTailNodeID) ++ edge ++ (show edgeHeadNodeID) ++ attributes)           where+            edge = " " ++ (if directedEdge then dirEdge else undirEdge) ++ " "             attributes = case edgeAttributes of                            [] -> ";"-                           a -> ' ':((show a) ++ ";")+                           a  -> ' ':((show a) ++ ";")  instance Show DotGraph where-    show (DotGraph { graphAttributes, graphNodes, graphEdges })-        = unlines $ "digraph {" : (rest ++ ["}"])+    show (DotGraph { graphAttributes, graphNodes, graphEdges, directedGraph })+        = unlines $ gType : " {" : (rest ++ ["}"])         where+          gType = if directedGraph then dirGraph else undirGraph           rest = case graphAttributes of                    [] -> nodesEdges                    a -> ("\tgraph " ++ (show a) ++ ";") : nodesEdges           nodesEdges = (map show graphNodes) ++ (map show graphEdges) +-- | Define into which cluster a particular node belongs.+--   Nodes can be nested to arbitrary depth.+data NodeCluster c a = N (LNode a) | C c (NodeCluster c a)+                        deriving (Show)++-- | A tree representation of a cluster.+data ClusterTree c a = NT (LNode a) | CT c [ClusterTree c a]+                       deriving (Show)++-- Convert a single node cluster into its tree representation.+clustToTree          :: NodeCluster c a -> ClusterTree c a+clustToTree (N ln)   = NT ln+clustToTree (C c nc) = CT c [clustToTree nc]++-- Two nodes are in the same "default" cluster; otherwise check if they+-- are in the same cluster.+sameClust :: (Eq c) => ClusterTree c a -> ClusterTree c a -> Bool+sameClust (NT _)    (NT _)    = True+sameClust (CT c1 _) (CT c2 _) = c1 == c2+sameClust _         _         = False++-- Singleton nodes come first, and then ordering based upon the cluster.+clustOrder :: (Ord c) => ClusterTree c a -> ClusterTree c a -> Ordering+clustOrder (NT _)    (NT _)    = EQ+clustOrder (NT _)    (CT _ _)  = P.LT -- don't use the attribute LT+clustOrder (CT _ _)  (NT _)    = GT+clustOrder (CT c1 _) (CT c2 _) = compare c1 c2++-- Extract the sub-trees.+getNodes           :: ClusterTree c a -> [ClusterTree c a]+getNodes n@(NT _)  = [n]+getNodes (CT _ ns) = ns++-- Combine clusters.+collapseNClusts :: (Ord c) => [ClusterTree c a] -> [ClusterTree c a]+collapseNClusts = concatMap grpCls+                  . groupBy sameClust+                  . sortBy clustOrder+    where+      grpCls []              = []+      grpCls ns@((NT _):_)   = ns+      grpCls cs@((CT c _):_) = [CT c (collapseNClusts $ concatMap getNodes cs)]++-- Differences between directed and undirected graphs.++dirEdge, undirEdge :: String+dirEdge = "->"+undirEdge = "--"++dirGraph, undirGraph :: String+dirGraph = "digraph"+undirGraph = "graph"++dirCommand, undirCommand :: String+dirCommand = "dot"+undirCommand = "neato"++-- | The appropriate GraphViz command for the given graph.+commandFor    :: DotGraph -> String+commandFor dg = if (directedGraph dg)+                then dirCommand+                else undirCommand++-- Determine ifi the given graph is undirected or directed.+isUndir   :: (Ord b, Graph g) => g a b -> Bool+isUndir g = all hasFlip edges+    where+      edges = labEdges g+      eSet = Set.fromList edges+      hasFlip e = Set.member (flippedEdge e) eSet+      flippedEdge (f,t,l) = (t,f,l)+ -- | Convert a graph to dot format. You can then write this to a file---   and run dot on it-graphToDot :: (Graph gr) => gr a b -> [Attribute] -> (LNode a -> [Attribute]) -> (LEdge b -> [Attribute]) -> DotGraph+--   and run the appropriate command on it (found using 'commandFor').+graphToDot :: (Ord b, Graph gr) => gr a b -> [Attribute]+           -> (LNode a -> [Attribute]) -> (LEdge b -> [Attribute]) -> DotGraph graphToDot graph graphAttributes fmtNode fmtEdge-    = DotGraph { graphAttributes, graphNodes, graphEdges }+    = clusterGraphToDot graph graphAttributes clusterBy fmtCluster fmtNode fmtEdge       where-        graphNodes = map mkDotNode . labNodes $ graph-        graphEdges = map mkDotEdge . labEdges $ graph-        mkDotNode n = DotNode { nodeID = fst n, nodeAttributes = fmtNode n }-        mkDotEdge e@(f, t, _) = DotEdge { edgeHeadNodeID = t, edgeTailNodeID = f, edgeAttributes = fmtEdge e }+        clusterBy :: LNode a -> NodeCluster () a+        clusterBy = N+        fmtCluster _ = [] +-- | Convert a graph to dot format, using the specified clustering function+--   to group nodes into clusters.  You can then write this to a file and+--   run the appropriate command on it (found using 'commandFor').+--   Clusters can be nested to arbitrary depth.+clusterGraphToDot :: (Ord c, Ord b, Graph gr) => gr a b+                  -> [Attribute] -> (LNode a -> NodeCluster c a)+                  -> (c -> [Attribute]) -> (LNode a -> [Attribute])+                  -> (LEdge b -> [Attribute]) -> DotGraph+clusterGraphToDot graph graphAttributes clusterBy fmtCluster fmtNode fmtEdge+    = DotGraph { graphAttributes, graphNodes, graphEdges, directedGraph }+      where+        clusters = collapseNClusts . map (clustToTree . clusterBy) $ labNodes graph+        graphNodes = treesToNodes fmtCluster fmtNode clusters+        directedGraph = not $ isUndir graph+        graphEdges = catMaybes . map mkDotEdge . labEdges $ graph+        mkDotEdge e@(f,t,_) = if (directedGraph || f <= t)+                              then Just $ DotEdge {edgeHeadNodeID = t+                                                  ,edgeTailNodeID = f+                                                  ,edgeAttributes = fmtEdge e+                                                  ,directedEdge = directedGraph}+                              else Nothing++-- Convert the cluster representation of the trees into DotNodes.+-- Clusters will be labelled with integers.+treesToNodes :: (c -> [Attribute]) -> (LNode a -> [Attribute])+             -> [ClusterTree c a] -> [DotNode]+treesToNodes fmtCluster fmtNode = snd . treesToNodesFrom fmtCluster fmtNode 0++-- Start labelling the clusters with this integer.+treesToNodesFrom :: (c -> [Attribute]) -> (LNode a -> [Attribute])+                 -> Int -> [ClusterTree c a] -> (Int,[DotNode])+treesToNodesFrom fmtCluster fmtNode n = mapAccumL mkNodes n+    where+      mkNodes = treeToNode fmtCluster fmtNode++-- Convert this ClusterTree into its DotNode representation.+treeToNode :: (c -> [Attribute]) -> (LNode a -> [Attribute])+           -> Int -> ClusterTree c a -> (Int, DotNode)+treeToNode _ fmtNode n (NT ln) = ( n+                                 , DotNode { nodeID = fst ln+                                           , nodeAttributes = fmtNode ln+                                           }+                                 )+treeToNode fmtCluster fmtNode n (CT c nts) = (n',clust)+    where+      (n', nts') = treesToNodesFrom fmtCluster fmtNode (n+1) nts+      clust = DotCluster { clusterID = show n+                         , clusterAttributes = fmtCluster c+                         , clusterElems = nts'+                         }+ type AttributeNode a = ([Attribute], a) type AttributeEdge b = ([Attribute], b)  -- | Run the graph via dot to get positional information and then---   combine that information back into the original graph-graphToGraph :: forall gr a b . (Graph gr) =>+--   combine that information back into the original graph.+--   Note that this doesn't support graphs with clusters.+graphToGraph :: forall gr a b . (Ord b, Graph gr) =>                 gr a b -> [Attribute] -> (LNode a -> [Attribute]) -> (LEdge b -> [Attribute]) -> IO (gr (AttributeNode a) (AttributeEdge b)) graphToGraph gr graphAttributes fmtNode fmtEdge-    = do { (inp, outp, errp, proc) <- runInteractiveCommand "dot -Tdot"+    = do { (inp, outp, errp, proc) <- runInteractiveCommand (command++" -Tdot")          ; hPutStr inp (show dot)          ; hClose inp          ; forkIO $ (hGetContents errp >>= hPutStr stderr)@@ -103,15 +262,22 @@          ; return $ rebuildGraphWithAttributes res          }     where+      undirected = isUndir gr+      command = if undirected then undirCommand else dirCommand       dot = graphToDot gr graphAttributes fmtNode fmtEdge       rebuildGraphWithAttributes :: String -> gr (AttributeNode a) (AttributeEdge b)       rebuildGraphWithAttributes dotResult = mkGraph lnodes ledges           where             lnodes = map (\(n, l) -> (n, (fromJust $ Map.lookup n nodeMap, l))) . labNodes $ gr-            ledges = map (\(f,t, l) -> (f, t, (fromJust $ Map.lookup (f,t) edgeMap, l))) . labEdges $ gr+            ledges = map createEdges . labEdges $ gr             (DotGraph { graphEdges, graphNodes }) = fst . runParser readDotGraph $ dotResult             nodeMap = Map.fromList . map (\n -> (nodeID n, nodeAttributes n)) $ graphNodes             edgeMap = Map.fromList . map (\e -> ((edgeTailNodeID e, edgeHeadNodeID e), edgeAttributes e)) $ graphEdges+            createEdges (f,t,l) = if (undirected && f > t)+                                  then (f,t,getLabel (t,f))+                                  else (f,t,getLabel (f,t))+                where+                  getLabel c = (fromJust $ Map.lookup c edgeMap,l)  readDotNode :: Parser Char DotNode readDotNode = do { optional whitespace@@ -126,17 +292,23 @@ readDotEdge = do { optional whitespace                  ; edgeTailNodeID <- number                  ; whitespace-                 ; string "->"+                 ; edge <- strings [dirEdge,undirEdge]                  ; whitespace                  ; edgeHeadNodeID <- number                  ; as <- optional (whitespace >> readAttributesList)                  ; char ';'                  ; skipToNewline-                 ; return (DotEdge { edgeHeadNodeID, edgeTailNodeID, edgeAttributes = fromMaybe [] as })+                 ; return (DotEdge { edgeHeadNodeID+                                   , edgeTailNodeID+                                   , edgeAttributes = fromMaybe [] as+                                   , directedEdge = edge == dirEdge })                  }+    where + readDotGraph :: Parser Char DotGraph-readDotGraph = do { string "digraph"+readDotGraph = do { d <- strings [dirGraph,undirGraph]+                  ; let directedGraph = d == dirGraph                   ; whitespace                   ; char '{'                   ; skipToNewline@@ -151,5 +323,5 @@                   ; graphNodes <- many readDotNode                   ; graphEdges <- many readDotEdge                   ; char '}'-                  ; return $ DotGraph { graphAttributes, graphNodes, graphEdges }+                  ; return $ DotGraph { graphAttributes, graphNodes, graphEdges, directedGraph }                   }
Data/GraphViz/Attributes.hs view
@@ -4,9 +4,10 @@   {- GraphViz ------------------------------------------------------\  |                                                                 |- | Copyright (c) 2008, Matthew Sackman (matthew@wellquite.org)     |+ | Copyright (c) 2008, Matthew Sackman (matthew@wellquite.org),    |+ |              Ivan Lazar Miljenovic (ivan.miljenovic@gmail.com)  |  |                                                                 |- | DisTract is freely distributable under the terms of a 3-Clause  |+ | GraphViz is freely distributable under the terms of a 3-Clause  |  | BSD-style license.                                              |  |                                                                 |  \-----------------------------------------------------------------}@@ -58,26 +59,26 @@  readArrowType :: Parser Char ArrowType readArrowType-              = oneOf [ optionalQuotedString "normal" >> return Normal   -                      , optionalQuotedString "inv" >> return Inv      -                      , optionalQuotedString "dot" >> return Dot      -                      , optionalQuotedString "invdot" >> return InvDot   -                      , optionalQuotedString "odot" >> return ODot     -                      , optionalQuotedString "invodot" >> return InvODot  -                      , optionalQuotedString "noarrow" >> return NoArrow  -                      , optionalQuotedString "tee" >> return Tee      -                      , optionalQuotedString "empty" >> return Empty    -                      , optionalQuotedString "invempty" >> return InvEmpty -                      , optionalQuotedString "diamond" >> return Diamond  -                      , optionalQuotedString "odiamond" >> return ODiamond -                      , optionalQuotedString "ediamond" >> return EDiamond -                      , optionalQuotedString "crow" >> return Crow     -                      , optionalQuotedString "box" >> return Box      -                      , optionalQuotedString "obox" >> return OBox     -                      , optionalQuotedString "open" >> return Open     -                      , optionalQuotedString "halfopen" >> return HalfOpen -                      , optionalQuotedString "vee" >> return Vee      -                      ]                                          +              = oneOf [ optionalQuotedString "normal" >> return Normal+                      , optionalQuotedString "inv" >> return Inv+                      , optionalQuotedString "dot" >> return Dot+                      , optionalQuotedString "invdot" >> return InvDot+                      , optionalQuotedString "odot" >> return ODot+                      , optionalQuotedString "invodot" >> return InvODot+                      , optionalQuotedString "noarrow" >> return NoArrow+                      , optionalQuotedString "tee" >> return Tee+                      , optionalQuotedString "empty" >> return Empty+                      , optionalQuotedString "invempty" >> return InvEmpty+                      , optionalQuotedString "diamond" >> return Diamond+                      , optionalQuotedString "odiamond" >> return ODiamond+                      , optionalQuotedString "ediamond" >> return EDiamond+                      , optionalQuotedString "crow" >> return Crow+                      , optionalQuotedString "box" >> return Box+                      , optionalQuotedString "obox" >> return OBox+                      , optionalQuotedString "open" >> return Open+                      , optionalQuotedString "halfopen" >> return HalfOpen+                      , optionalQuotedString "vee" >> return Vee+                      ]  data ColorType = RGB { red :: Word8                       , green :: Word8
Data/GraphViz/ParserCombinators.hs view
@@ -1,10 +1,12 @@ {-# LANGUAGE PatternSignatures #-} - {- GraphViz ------------------------------------------------------\++ {--GraphViz ------------------------------------------------------\  |                                                                 |- | Copyright (c) 2008, Matthew Sackman (matthew@wellquite.org)     |+ | Copyright (c) 2008, Matthew Sackman (matthew@wellquite.org),    |+ |              Ivan Lazar Miljenovic (ivan.miljenovic@gmail.com)  |  |                                                                 |- | DisTract is freely distributable under the terms of a 3-Clause  |+ | GraphViz is freely distributable under the terms of a 3-Clause  |  | BSD-style license.                                              |  |                                                                 |  \-----------------------------------------------------------------}@@ -16,6 +18,9 @@  string :: String -> Parser Char String string = mapM char++strings :: [String] -> Parser Char String+strings = oneOf . map string  char :: Char -> Parser Char Char char = satisfy . (==)
graphviz.cabal view
@@ -1,10 +1,10 @@ Name:               graphviz-Version:            2008.9.6+Version:            2008.9.20 Stability:          Beta-Copyright:          Matthew Sackman+Copyright:          Matthew Sackman, Ivan Lazar Miljenovic Category:           Graphics Maintainer:         matthew@wellquite.org-Author:             Matthew Sackman+Author:             Matthew Sackman, Ivan Lazar Miljenovic License:            BSD3 License-File:       LICENSE Cabal-Version:      >= 1.2