packages feed

diagrams-contrib 0.1.0.0 → 0.1.1.0

raw patch · 3 files changed

+225/−38 lines, 3 filesdep +data-defaultPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: data-default

API changes (from Hackage documentation)

- Diagrams.TwoD.Layout.Tree: defaultForceLayoutTreeOpts :: ForceLayoutTreeOpts
+ Diagrams.TwoD.Layout.Tree: SLOpts :: Double -> Double -> (a -> (Double, Double)) -> (a -> (Double, Double)) -> SymmLayoutOpts a
+ Diagrams.TwoD.Layout.Tree: data SymmLayoutOpts a
+ Diagrams.TwoD.Layout.Tree: forceLayoutTree' :: ForceLayoutTreeOpts -> Tree (a, P2) -> Tree (a, P2)
+ Diagrams.TwoD.Layout.Tree: instance Default (SymmLayoutOpts a)
+ Diagrams.TwoD.Layout.Tree: instance Default ForceLayoutTreeOpts
+ Diagrams.TwoD.Layout.Tree: slHSep :: SymmLayoutOpts a -> Double
+ Diagrams.TwoD.Layout.Tree: slHeight :: SymmLayoutOpts a -> a -> (Double, Double)
+ Diagrams.TwoD.Layout.Tree: slVSep :: SymmLayoutOpts a -> Double
+ Diagrams.TwoD.Layout.Tree: slWidth :: SymmLayoutOpts a -> a -> (Double, Double)
+ Diagrams.TwoD.Layout.Tree: symmLayout :: Tree a -> Tree (a, P2)
+ Diagrams.TwoD.Layout.Tree: symmLayout' :: SymmLayoutOpts a -> Tree a -> Tree (a, P2)
- Diagrams.TwoD.Layout.Tree: forceLayoutTree :: ForceLayoutTreeOpts -> Tree (a, P2) -> Tree (a, P2)
+ Diagrams.TwoD.Layout.Tree: forceLayoutTree :: Tree (a, P2) -> Tree (a, P2)

Files

CHANGES view
@@ -1,3 +1,8 @@+0.1.1.0: 16 March 2012++  * Add Andrew Kennedy's symmetric rose tree layout algorithm to+    Diagrams.TwoD.Layout.Tree+ 0.1.0.0: 9 March 2012    Initial release, containing:
diagrams-contrib.cabal view
@@ -1,5 +1,5 @@ name:                diagrams-contrib-version:             0.1.0.0+version:             0.1.1.0 synopsis:            Collection of user contributions to diagrams EDSL description:         A collection of user contributions for diagrams,                      an embedded domain-specific language for generation @@ -33,5 +33,6 @@                        diagrams-lib ==0.5.*,                         fclabels >= 1.0.4 && < 1.2,-                       force-layout >= 0.1 && < 0.2+                       force-layout >= 0.1 && < 0.2,+                       data-default >= 0.3 && < 0.4   hs-source-dirs:      src
src/Diagrams/TwoD/Layout/Tree.hs view
@@ -18,28 +18,52 @@ -- This module is still experimental, and more layout methods will -- probably be added over time. ----- Here is an example of using force-based layout on a binary tree:+-- Laying out a rose tree using a symmetric layout: -- -- > {-# LANGUAGE NoMonomorphismRestriction #-}+-- > import Diagrams.Prelude+-- > import Diagrams.TwoD.Layout.Tree -- >+-- > t1 = Node 'A' [Node 'B' (map lf "CDE"), Node 'F' [Node 'G' (map lf "HIJ")]]+-- >+-- > example =+-- >   renderTree ((<> circle 1 # fc white) . text . show)+-- >              (~~)+-- >              (symmLayout' with { slHSep = 4, slVSep = 4 } t1)+--+-- Laying out a rose tree of diagrams, with spacing automatically+-- adjusted for the size of the diagrams:+--+-- > {-# LANGUAGE NoMonomorphismRestriction #-} -- > import Diagrams.Prelude--- > import Diagrams.Backend.Cairo.CmdLine+-- > import Diagrams.TwoD.Layout.Tree -- >+-- > tD = Node (rect 1 3)+-- >        [ Node (circle 0.2) []+-- >        , Node (hcat . replicate 3 $ circle 1) []+-- >        , Node (eqTriangle 5) []+-- >        ]+-- >+-- > example =+-- >   renderTree id (~~)+-- >   (symmLayout' with { slWidth  = fromMaybe (0,0) . extentX+-- >                     , slHeight = fromMaybe (0,0) . extentY }+-- >      tD)+--+-- Using force-based layout on a binary tree:+--+-- > {-# LANGUAGE NoMonomorphismRestriction #-}+-- > import Diagrams.Prelude -- > import Diagrams.TwoD.Layout.Tree -- > -- > t = BNode 1 (BNode 8 (leaf 7) (leaf 2)) (BNode 6 (leaf 3) (leaf 4)) -- >--- > main = do--- >   let Just t' = uniqueXLayout 1 1 t--- >       t'' = forceLayoutTree defaultForceLayoutTreeOpts t'+-- > Just t' = uniqueXLayout 1 1 t -- >--- >   defaultMain $--- >     renderTree (\n -> (text (show n) # fontSize 0.5--- >                        <> circle 0.3 # fc white))--- >                (~~)--- >                t''---------------------------------------------------------------------------------+-- > example = renderTree (\n -> (text (show n) # fontSize 0.5+-- >                             <> circle 0.3 # fc white))+-- >             (~~)+-- >             (forceLayoutTree t')  module Diagrams.TwoD.Layout.Tree        ( -- * Binary trees@@ -54,12 +78,19 @@         , uniqueXLayout +         -- ** Symmetric rose tree layout++         -- $symmetric+       , symmLayout+       , symmLayout'+       , SymmLayoutOpts(..)+          -- ** Force-directed layout          -- $forcedirected         , forceLayoutTree+       , forceLayoutTree'        , ForceLayoutTreeOpts(..)-       , defaultForceLayoutTreeOpts         , treeToEnsemble        , label@@ -74,13 +105,15 @@ import           Physics.ForceLayout  import           Control.Applicative-import           Control.Arrow         (first, second)+import           Control.Arrow         (first, second, (***), (&&&)) import           Control.Monad.State +import           Data.Default import qualified Data.Foldable         as F import qualified Data.Map              as M import           Data.Label            (mkLabels) import qualified Data.Label            as L+import           Data.List             (mapAccumL) import           Data.Maybe import qualified Data.Traversable      as T import           Data.Tree@@ -113,8 +146,9 @@ --  Layout algorithms ------------------------------------------------------------ --- Unique X layout for binary trees.  No two nodes share the same X--- coordinate.+--------------------------------------------------+-- Unique X layout for binary trees.  No+-- two nodes share the same X coordinate.  data Pos = Pos { _level :: Int                , _horiz :: Int@@ -134,10 +168,6 @@ pos2Point :: Double -> Double -> Pos -> P2 pos2Point cSep lSep (Pos l h) = p2 (fromIntegral h * cSep, -fromIntegral l * lSep) ------------------------------------------------------ Unique X layout for binary trees.  No--- two nodes share the same X coordinate.- -- | @uniqueXLayout xSep ySep t@ lays out the binary tree @t@ using a --   simple recursive algorithm with the following properties: --@@ -167,6 +197,153 @@         mkNode = get <* incHoriz  --------------------------------------------------+-- "Symmetric" layout of rose trees.++-- $symmetric+-- "Symmetric" layout of rose trees, based on the algorithm described in:+--+-- Andrew J. Kennedy. /Drawing Trees/, J Func. Prog. 6 (3): 527-534,+-- May 1996.+--+-- Trees laid out using this algorithm satisfy:+--+--   1. Nodes at a given level are always separated by at least a+--   given minimum distance.+--+--   2. Parent nodes are centered with respect to their immediate+--   offspring (though /not/ necessarily with respect to the entire+--   subtrees under them)+--+--   3. Layout commutes with mirroring: that is, the layout of a given+--   tree is the mirror image of the layout of the tree's mirror+--   image.+--+--   4. Identical subtrees are always rendered identically.  Put+--   another way, the layout of any subtree is independent of the rest+--   of the tree.+--+--   5. The layouts are as narrow as possible while satisfying all the+--   above constraints.++-- | A tree with /relative/ positioning information.  The Double+--   at each node is the horizontal /offset/ from its parent.+type RelTree a = Tree (a, Double)++-- | Shift a RelTree horizontally.+moveTree :: Double -> RelTree a -> RelTree a+moveTree x' (Node (a, x) ts) = Node (a, x+x') ts++-- | An /extent/ is a list of pairs, recording the leftmost and+--   rightmost (absolute) horizontal positions of a tree at each+--   depth.+type Extent = [(Double, Double)]++-- | Shift an extent horizontally.+moveExtent :: Double -> Extent -> Extent+moveExtent x = map ((+x) *** (+x))++-- | Reflect an extent about the vertical axis.+flipExtent :: Extent -> Extent+flipExtent = map (\(p,q) -> (-q, -p))++-- | Merge two non-overlapping extents.+mergeExtents :: Extent -> Extent -> Extent+mergeExtents [] qs = qs+mergeExtents ps [] = ps+mergeExtents ((p,_) : ps) ((_,q) : qs) = (p,q) : mergeExtents ps qs++mergeExtentList :: [Extent] -> Extent+mergeExtentList = foldr mergeExtents []++-- | Determine the amount to shift in order to \"fit\" two extents+--   next to one another.  The first argument is the separation to+--   leave between them.+fit :: Double -> Extent -> Extent -> Double+fit hSep ps qs = maximum (0 : zipWith (\(_,p) (q,_) -> p - q + hSep) ps qs)++-- | Fit a list of subtree extents together using a left-biased+--   algorithm.  Compute a list of positions (relative to the leftmost+--   subtree which is considered to have position 0).+fitListL :: Double -> [Extent] -> [Double]+fitListL hSep = snd . mapAccumL fitOne []+  where+    fitOne acc e =+      let x = fit hSep acc e+      in  (mergeExtents acc (moveExtent x e), x)++-- | Fit a list of subtree extents together with a right bias.+fitListR :: Double -> [Extent] -> [Double]+fitListR hSep = reverse . map negate . fitListL hSep . map flipExtent . reverse++-- | Compute a symmetric fitting by averaging the results of left- and+--   right-biased fitting.+fitList :: Double -> [Extent] -> [Double]+fitList hSep = uncurry (zipWith mean) . (fitListL hSep &&& fitListR hSep)+  where mean x y = (x+y)/2++-- | Actual recursive tree layout algorithm, which returns a tree+--   layout as well as an extent.+symmLayoutR :: SymmLayoutOpts a -> Tree a -> (RelTree a, Extent)+symmLayoutR opts (Node a ts) = (rt, ext)+  where (trees, extents) = unzip (map (symmLayoutR opts) ts)+        positions        = fitList (slHSep opts) extents+        pTrees           = zipWith moveTree positions trees+        pExtents         = zipWith moveExtent positions extents+        ext              = slWidth opts a : mergeExtentList pExtents+        rt               = Node (a, 0) pTrees++-- | Options for controlling the+data SymmLayoutOpts a =+  SLOpts { slHSep   :: Double           -- ^ Minimum horizontal+                                        --   separation between sibling+                                        --   nodes.  The default is 1.+         , slVSep   :: Double           -- ^ Vertical separation+                                        --   between adjacent levels of+                                        --   the tree.  The default is 1.+         , slWidth  :: a -> (Double, Double)+           -- ^ A function for measuring the horizontal extent (a pair+           --   of x-coordinates) of an item in the tree.  The default+           --   is @const (0,0)@, that is, the nodes are considered as+           --   taking up no space, so the centers of the nodes will+           --   be separated according to the @slHSep@ and @slVSep@.+           --   However, this can be useful, /e.g./ if you have a tree+           --   of diagrams of irregular size and want to make sure no+           --   diagrams overlap.  In that case you could use+           --   @fromMaybe (0,0) . extentX@.+         , slHeight :: a -> (Double, Double)+           -- ^ A function for measuring the vertical extent of an+           --   item in the tree.  The default is @const (0,0)@.  See+           --   the documentation for 'slWidth' for more information.+         }++instance Default (SymmLayoutOpts a) where+  def = SLOpts+          { slHSep   = 1+          , slVSep   = 1+          , slWidth  = const (0,0)+          , slHeight = const (0,0)+          }++-- | Run the symmetric rose tree layout algorithm on a given tree,+--   resulting in the same tree annotated with node positions.+symmLayout' :: SymmLayoutOpts a -> Tree a -> Tree (a, P2)+symmLayout' opts = unRelativize opts origin . fst . symmLayoutR opts++unRelativize :: SymmLayoutOpts a -> P2 -> RelTree a -> Tree (a, P2)+unRelativize opts curPt (Node (a,hOffs) ts)+    = Node (a, rootPt) (map (unRelativize opts (rootPt .+^ (vOffs *^ unit_Y))) ts)+  where rootPt = curPt .+^ (hOffs *^ unitX)+        vOffs  = - fst (slHeight opts a)+               + (maximum . map (snd . slHeight opts . fst . rootLabel) $ ts)+               + slVSep opts++-- | Run the symmetric rose tree layout algorithm on a given tree+--   using default options, resulting in the same tree annotated with+--   node positions.+symmLayout :: Tree a -> Tree (a, P2)+symmLayout = symmLayout' def++-------------------------------------------------- --  Force-directed layout of rose trees  -- $forcedirected@@ -249,21 +426,21 @@                                           --   each other.   } -defaultForceLayoutTreeOpts :: ForceLayoutTreeOpts-defaultForceLayoutTreeOpts =-  FLTOpts-  { forceLayoutOpts =-      FLOpts-      { damping     = 0.8-      , energyLimit = Just 0.001-      , stepLimit   = Just 1000-      }-  , edgeLen = sqrt 2-  , springK = 0.05-  , staticK = 0.1-  }+instance Default ForceLayoutTreeOpts where+  def = FLTOpts+    { forceLayoutOpts =+        FLOpts+        { damping     = 0.8+        , energyLimit = Just 0.001+        , stepLimit   = Just 1000+        }+    , edgeLen = sqrt 2+    , springK = 0.05+    , staticK = 0.1+    } --- | Force-directed layout of rose trees.  In particular,+-- | Force-directed layout of rose trees, with default parameters (for+--   more options, see 'forceLayoutTree'').  In particular, -- --   * edges are modeled as springs --@@ -273,8 +450,12 @@ -- --   The input could be a tree already laid out by some other method, --   such as 'uniqueXLayout'.-forceLayoutTree :: ForceLayoutTreeOpts -> Tree (a, P2) -> Tree (a, P2)-forceLayoutTree opts t = reconstruct (forceLayout (forceLayoutOpts opts) e) ti+forceLayoutTree :: Tree (a, P2) -> Tree (a, P2)+forceLayoutTree = forceLayoutTree' def++-- | Force-directed layout of rose trees, with configurable parameters.+forceLayoutTree' :: ForceLayoutTreeOpts -> Tree (a, P2) -> Tree (a, P2)+forceLayoutTree' opts t = reconstruct (forceLayout (forceLayoutOpts opts) e) ti   where (ti, e) = treeToEnsemble opts t  ------------------------------------------------------------