combinat-0.2.7.2: Math/Combinat/ASCII.hs
-- | A mini-DSL for ASCII drawing of structures.
--
--
-- From some structures there is also Graphviz and\/or @diagrams@
-- (<http://projects.haskell.org/diagrams>) visualization support
-- (the latter in the separate libray @combinat-diagrams@).
--
module Math.Combinat.ASCII where
--------------------------------------------------------------------------------
import Data.List
import Math.Combinat.Helper
--------------------------------------------------------------------------------
-- * The basic type
-- | The type of a (rectangular) ASCII figure.
-- Internally it is a list of lines of the same length plus the size.
--
-- Note: The Show instance is pretty-printing, so that it\'s convenient in ghci.
--
data ASCII = ASCII
{ asciiSize :: (Int,Int)
, asciiLines :: [String]
}
-- | A type class to have a simple way to draw things
class DrawASCII a where
ascii :: a -> ASCII
instance Show ASCII where
show = asciiString
-- | An empty (0x0) rectangle
emptyRect :: ASCII
emptyRect = ASCII (0,0) []
asciiXSize, asciiYSize :: ASCII -> Int
asciiXSize = fst . asciiSize
asciiYSize = snd . asciiSize
asciiString :: ASCII -> String
asciiString (ASCII sz ls) = unlines ls
printASCII :: ASCII -> IO ()
printASCII = putStrLn . asciiString
asciiFromLines :: [String] -> ASCII
asciiFromLines ls = ASCII (x,y) (map f ls) where
y = length ls
x = maximum (map length ls)
f l = l ++ replicate (x - length l) ' '
asciiFromString :: String -> ASCII
asciiFromString = asciiFromLines . lines
--------------------------------------------------------------------------------
-- * Alignment
-- | Horizontal alignment
data HAlign
= HLeft
| HCenter
| HRight
deriving (Eq,Show)
-- | Vertical alignment
data VAlign
= VTop
| VCenter
| VBottom
deriving (Eq,Show)
data Alignment = Align HAlign VAlign
--------------------------------------------------------------------------------
-- * Extension
-- | Extends an ASCII figure with spaces horizontally to the given width
hExtendTo :: HAlign -> Int -> ASCII -> ASCII
hExtendTo halign n0 rect@(ASCII (x,y) ls) = hExtendWith halign (max n0 x - x) rect
-- | Extends an ASCII figure with spaces vertically to the given height
vExtendTo :: VAlign -> Int -> ASCII -> ASCII
vExtendTo valign n0 rect@(ASCII (x,y) ls) = vExtendWith valign (max n0 y - y) rect
-- | Extend horizontally with the given number of spaces
hExtendWith :: HAlign -> Int -> ASCII -> ASCII
hExtendWith alignment d (ASCII (x,y) ls) = ASCII (x+d,y) (map f ls) where
f l = case alignment of
HLeft -> l ++ replicate d ' '
HRight -> replicate d ' ' ++ l
HCenter -> replicate a ' ' ++ l ++ replicate (d-a) ' '
a = div d 2
-- | Extend vertically with the given number of empty lines
vExtendWith :: VAlign -> Int -> ASCII -> ASCII
vExtendWith valign d (ASCII (x,y) ls) = ASCII (x,y+d) (f ls) where
f ls = case valign of
VTop -> ls ++ replicate d emptyline
VBottom -> replicate d emptyline ++ ls
VCenter -> replicate a emptyline ++ ls ++ replicate (d-a) emptyline
a = div d 2
emptyline = replicate x ' '
-- | Horizontal indentation
hIndent :: Int -> ASCII -> ASCII
hIndent d = hExtendWith HRight d
-- | Vertical indentation
vIndent :: Int -> ASCII -> ASCII
vIndent d = vExtendWith VBottom d
--------------------------------------------------------------------------------
-- * Separators
-- | Horizontal separator
data HSep
= HSepEmpty -- ^ empty separator
| HSepSpaces Int -- ^ @n@ spaces
| HSepString String -- ^ some custom string, eg. @\" | \"@
deriving Show
hSepSize :: HSep -> Int
hSepSize hsep = case hsep of
HSepEmpty -> 0
HSepSpaces k -> k
HSepString s -> length s
hSepString :: HSep -> String
hSepString hsep = case hsep of
HSepEmpty -> ""
HSepSpaces k -> replicate k ' '
HSepString s -> s
-- | Vertical separator
data VSep
= VSepEmpty -- ^ empty separator
| VSepSpaces Int -- ^ @n@ spaces
| VSepString [Char] -- ^ some custom list of characters, eg. @\" - \"@ (the characters are interpreted as below each other)
deriving Show
vSepSize :: VSep -> Int
vSepSize vsep = case vsep of
VSepEmpty -> 0
VSepSpaces k -> k
VSepString s -> length s
vSepString :: VSep -> [Char]
vSepString vsep = case vsep of
VSepEmpty -> []
VSepSpaces k -> replicate k ' '
VSepString s -> s
--------------------------------------------------------------------------------
-- * Padding
-- | Horizontally pads with the given number of spaces, on both sides
hPad :: Int -> ASCII -> ASCII
hPad k (ASCII (x,y) ls) = ASCII (x+2*k,y) (map f ls) where
f l = pad ++ l ++ pad
pad = replicate k ' '
-- | Vertically pads with the given number of empty lines, on both sides
vPad :: Int -> ASCII -> ASCII
vPad k (ASCII (x,y) ls) = ASCII (x,y+2*k) (pad ++ ls ++ pad) where
pad = replicate k (replicate x ' ')
-- | Pads by single empty lines vertically and two spaces horizontally
pad :: ASCII -> ASCII
pad = vPad 1 . hPad 2
--------------------------------------------------------------------------------
-- * Concatenation
-- | Horizontal concatenation
hCatWith :: VAlign -> HSep -> [ASCII] -> ASCII
hCatWith valign hsep rects = ASCII (x',maxy) final where
n = length rects
maxy = maximum [ y | ASCII (_,y) _ <- rects ]
xsz = [ x | ASCII (x,_) _ <- rects ]
sep = hSepString hsep
sepx = length sep
rects1 = map (vExtendTo valign maxy) rects
x' = sum' xsz + (n-1)*sepx
final = map (intercalate sep) $ transpose (map asciiLines rects1)
-- | Vertical concatenation
vCatWith :: HAlign -> VSep -> [ASCII] -> ASCII
vCatWith halign vsep rects = ASCII (maxx,y') final where
n = length rects
maxx = maximum [ x | ASCII (x,_) _ <- rects ]
ysz = [ y | ASCII (_,y) _ <- rects ]
sepy = vSepSize vsep
fullsep = transpose (replicate maxx $ vSepString vsep) :: [String]
rects1 = map (hExtendTo halign maxx) rects
y' = sum' ysz + (n-1)*sepy
final = intercalate fullsep $ map asciiLines rects1
--------------------------------------------------------------------------------
-- * Tabulate
tabulate :: (HAlign,VAlign) -> (HSep,VSep) -> [[ASCII]] -> ASCII
tabulate (halign,valign) (hsep,vsep) rects0 = final where
n = length rects0
m = maximum (map length rects0)
rects1 = map (\rs -> rs ++ replicate (m - length rs) emptyRect) rects0
ys = map (\rs -> maximum (map asciiYSize rs)) rects1
xs = map (\rs -> maximum (map asciiXSize rs)) (transpose rects1)
rects2 = map (\rs -> [ hExtendTo halign x r | (x,r ) <- zip xs rs ]) rects1
rects3 = [ map (vExtendTo valign y) rs | (y,rs) <- zip ys rects2 ]
final = vCatWith HLeft vsep
$ map (hCatWith VTop hsep) rects3
-- | Order of elements in a matrix
data MatrixOrder
= RowMajor
| ColMajor
deriving (Eq,Ord,Show,Read)
-- | Automatically tabulates ASCII rectangles.
--
autoTabulate
:: MatrixOrder -- ^ whether to use row-major or column-major ordering of the elements
-> Either Int Int -- ^ @(Right x)@ creates x columns, while @(Left y)$ creates y rows
-> [ASCII] -- ^ list of ASCII rectangles
-> ASCII
autoTabulate mtxorder ei list = final where
final = tabulate (HLeft,VBottom) (HSepSpaces 2,VSepSpaces 1) rects
n = length list
rects = case ei of
Left y -> case mtxorder of
ColMajor -> transpose (parts y list)
RowMajor -> invparts y list
Right x -> case mtxorder of
ColMajor -> transpose (invparts x list)
RowMajor -> parts x list
transposeIf b = if b then transpose else id
-- chops into parts (the last one can be smaller)
parts d = go where
go [] = []
go xs = take d xs : go (drop d xs)
invparts d xs = parts' ds xs where
(q,r) = divMod n d
ds = replicate r (q+1) ++ replicate (d-r) q
parts' ds xs = go ds xs where
go _ [] = []
go [] _ = []
go (d:ds) xs = take d xs : go ds (drop d xs)
--------------------------------------------------------------------------------
-- * Captions
-- | Adds a caption to the bottom, with default settings.
caption :: String -> ASCII -> ASCII
caption = caption' False HLeft
-- | Adds a caption to the bottom. The @Bool@ flag specifies whether to add an empty between
-- the caption and the figure
caption' :: Bool -> HAlign -> String -> ASCII -> ASCII
caption' emptyline halign str rect = vCatWith halign sep [rect,capt] where
sep = if emptyline then VSepSpaces 1 else VSepEmpty
capt = asciiFromString str
--------------------------------------------------------------------------------
-- * Testing \/ miscellanea
-- | An ASCII box of the given size
asciiBox :: (Int,Int) -> ASCII
asciiBox (x,y) = ASCII (max x 2, max y 2) (h : replicate (y-2) m ++ [h]) where
h = "+" ++ replicate (x-2) '-' ++ "+"
m = "|" ++ replicate (x-2) ' ' ++ "|"
-- | An \"rounded\" ASCII box of the given size
roundedAsciiBox :: (Int,Int) -> ASCII
roundedAsciiBox (x,y) = ASCII (max x 2, max y 2) (a : replicate (y-2) m ++ [b]) where
a = "/" ++ replicate (x-2) '-' ++ "\\"
m = "|" ++ replicate (x-2) ' ' ++ "|"
b = "\\" ++ replicate (x-2) '-' ++ "/"
asciiNumber :: Int -> ASCII
asciiNumber = asciiShow
asciiShow :: Show a => a -> ASCII
asciiShow = asciiFromLines . (:[]) . show
--------------------------------------------------------------------------------