{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {- GenI surface realiser Copyright (C) 2005 Carlos Areces and Eric Kow This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -} {- | Graphviz is an open source tool which converts an abstract representation of a graph (node foo is connected to node bar, etc.) into a nicely laid out graphic. This module contains methods to invoke graphviz and to convert graphs and trees to its input format. You can download this (open source) tool at -} module NLP.GenI.Graphviz where import Control.Arrow (second) import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as T import qualified Data.Text.Lazy.IO as T import Data.Tree import Prelude hiding (writeFile) import Data.GraphViz import Data.GraphViz.Attributes.Complete import Data.GraphViz.Printing (printIt) {- | Data structures which can be visualised with GraphViz should implement this class. Note the first argument to graphvizShowGraph is so that you can parameterise your show function (i.e. pass in flags to change the way you show particular object). Note that by default, all graphs are treated as directed graphs. You can hide this by turning off edge arrows. -} class GraphvizShow b where graphvizShowGraph :: b -> DotGraph Text graphvizShowAsSubgraph :: Text -- ^ prefix -> b -- ^ item -> [DotSubGraph Text] -- ^ gv output graphvizLabel :: b -- ^ item -> Text -- ^ gv output graphvizParams :: b -> [GlobalAttributes] graphvizShowGraph b = DotGraph False True Nothing $ DotStmts (addLabel (graphvizLabel b) $ graphvizParams b) (graphvizShowAsSubgraph "_" b) [] [] where addLabel :: Text -> [GlobalAttributes] -> [GlobalAttributes] addLabel "" = id addLabel l = (GraphAttrs [Label (StrLabel l)] :) graphvizLabel _ = "" graphvizParams _ = [] class GraphvizShowNode b where graphvizShowNode :: Text -- ^ prefix -> b -- ^ item -> DotNode Text -- ^ gv output -- | Things which are meant to be displayed within some other graph -- as (part) of a node label class GraphvizShowString b where graphvizShow :: b -- ^ item -> Text -- ^ gv output -- | Note: the 'dotFile' argument allows you to save the intermediary -- dot output to a file. You can pass in the empty string if you don't toGraphviz :: GraphvizShow a => a -> String -- ^ the 'dotFile' -> String -> IO FilePath toGraphviz x dotFile outputFile = do T.writeFile dotFile (printIt g) runGraphviz g Png outputFile where g = graphvizShowGraph x -- --------------------------------------------------------------------- -- useful utility functions -- --------------------------------------------------------------------- gvUnlines :: [Text] -> Text gvUnlines = T.intercalate "\n" -- --------------------------------------------------------------------- -- some instances -- --------------------------------------------------------------------- instance GraphvizShow b => GraphvizShow (Maybe b) where graphvizShowAsSubgraph _ Nothing = [] graphvizShowAsSubgraph p (Just b) = graphvizShowAsSubgraph p b graphvizLabel Nothing = "" graphvizLabel (Just b) = graphvizLabel b graphvizParams Nothing = [] graphvizParams (Just b) = graphvizParams b -- | Displays a tree in graphviz format. {- Note that we could make this an instance of GraphvizShow, but I'm not too sure about the wisdom of such a move. Maybe if we had some really super-sophisticated types in Haskell, where we can define this as the default instance which could be overrided by something more specific, that would be cool. The prefix argument is interpreted as the name of the top node. Node names below are basically Gorn addresses (e.g. n0x2x3 means 3rd child of the 2nd child of the root) to keep them distinct. Note : We use the letter `x' as seperator because graphviz will choke on `.' or `-', even underscore. -} gvShowTree :: GraphvizShowNode n => Text -- ^ node prefix -> Tree n -- ^ the tree -> DotSubGraph Text gvShowTree prefix t = DotSG False Nothing $ DotStmts atts [] nodes edges where atts = [ EdgeAttrs [ ArrowHead noArrow ] ] -- should be none (nodes, edges) = second concat . unzip $ walk prefix t -- walk pref (Node node xs) = (ns, es) : concat (zipWith walk ps xs) where mkPrefix i = T.concat [pref, "x", T.pack (show i)] ns = graphvizShowNode pref node ps = map mkPrefix [1::Int .. length xs] es = map (\n -> DotEdge pref n []) ps