-- | 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 {- base -} import qualified Text.XML.Light as X {- xml -} import Music.Theory.Math (R) {- hmt -} import qualified Text.HTML.Light as H {- html-minimalist -} import qualified Text.HTML.Light.Composite as H {- html-minimalist -} -- * Grid -- | 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 array of upper-left -- co-ordinates of cells. -- -- > grid (10,10) (50,10) (2,2) = [[(10,10),(60,10)],[(10,20),(60,20)]] grid :: (Enum a,Num a) => (a,a) -> (a,a) -> (Int,Int) -> [[(a,a)]] grid (x,y) (w,h) (r,c) = let xs = take c [x, x + w ..] ys = take r [y, y + h ..] in map (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 (HTML) -- | A table cell is an 'X.Attr' and 'X.Content' duple. type Table_Cell = ([X.Attr],[X.Content]) -- | A table caption. type Caption = [X.Content] -- | Table of row order 'Table_Cell's. type HTML_Table = (Caption,[[Table_Cell]]) -- | Construct a 'Table' with one 'X.Content' per cell. simple_table :: Caption -> [[X.Content]] -> HTML_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)]] -> HTML_Table simple_table_class c z = (c,map (map (\(nm,x) -> ([H.class' nm],[x]))) z) -- | A function from @(row,column)@ to 'Maybe' 'Table_Cell' type Build_F = ((Int,Int) -> Maybe Table_Cell) -- | Build a table of @(rows,columns)@ dimensions given a builder -- function. 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 -> HTML_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) -> HTML_Table build_table c (m,n) f = build_table_m c (m,n) (Just . f) -- | Render 'Table' as @HTML@ table. table :: HTML_Table -> X.Content table (c,z) = let mk_r = H.tr [] . map (uncurry H.td) in H.table [] [H.caption [] c,H.tbody [] (map mk_r z)] -- | A set of related tables. type HTML_Table_Set = [HTML_Table] -- | Render a 'Table_Set's in a @div@ with class @table-set@. table_set :: HTML_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 -> [HTML_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 [] [H.meta [H.charset "utf-8"],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 -> [HTML_Table_Set] -> IO () to_html o_fn css = writeFile o_fn . page css