hmt-0.12: Music/Theory/Diagram/Grid.hs
-- | Functions for drawing grid and table structure common in music
-- theory and in compositions such as Morton Feldman's durational
-- /grid/ music of the 1950's.
module Music.Theory.Diagram.Grid where
import Data.Maybe
import qualified Text.HTML.Light as H {- html-minimalist -}
import qualified Text.HTML.Light.Composite as H
import qualified Text.XML.Light as X {- xml -}
-- * Grid
-- | Real number, synonym for 'Double'.
type R = Double
-- | Point given as pair of 'R'.
type P = (R,R)
-- | Red, green and blue colour triple.
type C = (R,R,R)
-- | Cell location as row and column indices.
type L = (Int,Int)
-- | Cell
type Cell = (L,C,String)
-- | Grid
type Grid = [Cell]
-- | Given /(x,y)/ upper-left co-ordinate of grid, /(w,h)/ cell
-- dimensions, and /(r,c)/ grid dimensions, make list of upper-left
-- co-ordinates of cells.
--
-- > grid (10,10) (50,10) (2,2) == [(10,10),(60,10),(10,20),(60,20)]
grid :: P -> (R,R) -> (Int,Int) -> [P]
grid (x,y) (w,h) (r,c) =
let xs = take c [x, x + w ..]
ys = take r [y, y + h ..]
in concatMap (zip xs . repeat) ys
-- | Variant on 'grid' that constructs a single point.
--
-- > map (grid_pt (10,10) (50,10)) [(0,0),(1,1)] == [(10,10),(60,20)]
grid_pt :: (R,R) -> (R,R) -> L -> P
grid_pt (x,y) (w,h) (r,c) =
let r' = fromIntegral r
c' = fromIntegral c
in (x + c' * w,y + r' * h)
-- | Displace 'P' (pointwise addition).
--
-- > displace (2,3) (1,1) == (3,4)
displace :: (R,R) -> P -> P
displace (dx,dy) (x,y) = (x+dx,y+dy)
-- | Make a bounding box from /row/ and /column/ dimensions.
mk_bbox :: (Int,Int) -> (R,R)
mk_bbox (r,c) =
let f n = (fromIntegral n + 2) * 10
in (f c,f r)
-- * Table
-- | A table cell is an 'X.Attr' and 'X.Content' duple.
type Table_Cell = ([X.Attr],[X.Content])
type Caption = [X.Content]
-- | Table of row order 'Table_Cell's.
type Table = (Caption,[[Table_Cell]])
-- | Construct a 'Table' with one 'X.Content' per cell.
simple_table :: Caption -> [[X.Content]] -> Table
simple_table c z = (c,map (map (\x -> ([],[x]))) z)
-- | Construct a 'Table' with one 'X.Content' per cell, and an
-- associated class.
simple_table_class :: Caption -> [[(String,X.Content)]] -> Table
simple_table_class c z = (c,map (map (\(nm,x) -> ([H.class' nm],[x]))) z)
type Build_F = ((Int,Int) -> Maybe Table_Cell)
-- | Build a table of @(rows,columns)@ dimensions given a function
-- from @(row,column)@ to 'Maybe' 'Table_Cell'. If the function is
-- 'Nothing' the cell is skipped, becase another cell has claimed it's
-- locations with 'H.colspan' or 'H.rowspan'.
build_table_m :: Caption -> (Int,Int) -> Build_F -> Table
build_table_m c (m,n) f =
let mk_row i = mapMaybe (\j -> f (i,j)) [0 .. n - 1]
in (c,map mk_row [0 .. m - 1])
-- | Build a table of @(rows,columns)@ dimensions given a function
-- from @(row,column)@ to 'Table_Cell'.
build_table :: Caption -> (Int,Int) -> ((Int,Int) -> Table_Cell) -> Table
build_table c (m,n) f = build_table_m c (m,n) (Just . f)
-- | Render 'Table' as @HTML@ table.
table :: Table -> X.Content
table (c,z) =
let mk_r = H.tr [] . map (uncurry H.td)
in H.table [] (H.caption [] c : map mk_r z)
-- | A set of related tables.
type Table_Set = [Table]
-- | Render a 'Table_Set's in a @div@ with class @table-set@.
table_set :: Table_Set -> X.Content
table_set = H.div [H.class' "table-set"] . map table
-- | Render set of 'Table_Set's as @HTML@.
page :: Maybe FilePath -> [Table_Set] -> String
page css xs = do
let tb = map table_set xs
bd = H.body [H.class' "table-page"] tb
css' = H.link_css "all" (fromMaybe "css/grid.css" css)
hd = H.head [] [css']
e = H.html [H.lang "en"] [hd, bd]
H.renderHTML5 e
-- | Write set of 'Table_Set's to @HTML@ file.
to_html :: FilePath -> Maybe FilePath -> [Table_Set] -> IO ()
to_html o_fn css = writeFile o_fn . page css