kvitable-1.1.0.0: src/Data/KVITable/Render.hs
{-# LANGUAGE DataKinds #-}
-- | Common definitions (and support functions) for rendering a
-- 'KVITable'.
module Data.KVITable.Render
(
RenderConfig(..)
, defaultRenderConfig
, sortNumericAlpha
)
where
import qualified Data.List as L
import Data.Name ( Name, nameText, nameLength, nullName )
import qualified Data.Text as T
import Numeric.Natural
import Data.KVITable
-- | Returns the default rendering configuration, to be used with a
-- format-specific @render@ call.
defaultRenderConfig :: RenderConfig
defaultRenderConfig = RenderConfig
{ hideBlankRows = True
, hideBlankCols = True
, equisizedCols = True
, sortKeyVals = Nothing
, colStackAt = Nothing
, rowRepeat = True
, rowGroup = []
, caption = Nothing
, maxCells = 40000 -- even this is probably too big
, maxCols = 40000 -- by default, this should be >= maxCells
}
-- | The 'RenderConfig' specifies the various controls and
-- configurations used when rendering a 'KVITable' in various formats.
-- The 'RenderConfig' is global to all formats, although some of the
-- fields in the 'RenderConfig' will be ignored as not-applicable by
-- some formats.
data RenderConfig = RenderConfig
{
hideBlankRows :: Bool
-- ^ 'True' (default) removes rows for which there are no values
, hideBlankCols :: Bool
-- ^ 'True' (default) removes columns for which there are no values
, equisizedCols :: Bool
-- ^ 'True' (default) to maintain a consistent column width,
-- otherwise the columns are shunk to the minimum size needed to
-- display the title and values. Not applicable for some backends
-- (e.g. HTML) where the backend provides table rendering
-- functionality.
, sortKeyVals :: Maybe ((KeyVals, KeyVals) -> (KeyVals, KeyVals))
-- ^ Specifies a function to sort the KeyVals (rows and columns,
-- respectively) for the output. If no function provided, no sorting is
-- performed. The provided KeyVals are in the order in which the Keys are
-- declared to the KVITable; the values for each key may be in an arbitrary
-- order. The usual expectation is that this will only sort the values for
-- each Key, but it is allowed to re-order the Keys as well.
--
-- Sorting is done *prior* to applying maxCells and maxCols limitations; this
-- provides the unsurprising results for the user but means that this
-- function may perform extra work to sort rows and columns that will not be
-- shown.
--
-- It is permissible to move entries from the rows to the columns and
-- vice-versa; this function has control over the shape of the resulting
-- table, which includes redistributing the rows and columns such that the
-- result will violate maxCols (but not maxCells).
--
-- The columns keyvals will be empty if colStackAt is Nothing.
, colStackAt :: Maybe Key
-- ^ Column key to begin stacking keys in columns and sub-columns rather than
-- creating additional sub-rows. This is performed on the
-- originally-specified key order, prior to calling any provided
-- 'sortKeyVals' function.
, rowRepeat :: Bool
-- ^ 'True' (default) if an identical 'KeyVal' is to be repeated
-- in subsequent applicable rows.
, rowGroup :: [Key]
-- ^ List of Key names that should by grouped by inserting
-- horizontal row lines between KeyVals
, caption :: Maybe (Name "caption")
-- ^ Caption to render for table for backends which support
-- captions; otherwise ignored.
, maxCells :: Natural
-- ^ The maximum number of cells that will be rendered. The size of the
-- table is #rows times #cols, so a 100x100 table is 10000 entries. This
-- value is used to limit the size of the rendered table to provide a
-- reasonable output in a reasonable amount of time and memory; tables larger
-- than this limit will return a "table too big" message when rendered.
--
-- ASCII: At least 1 character for the cell, plus 3.5 characters for
-- boundaries means that even on an ultra-wide monitor with a very small
-- font, the table isn't very readable beyond 30-40 columns. In addition,
-- the algorithm needs to continually adjust table column widths to
-- accomodate new values, so there is a great deal of backtracking involved
-- and the time taken to render grows quite quickly.
--
-- HTML: A browser has reasonable constraints on displaying a table: 10000
-- entries is difficult for the user to comprehend, but the browser is
-- probably reasonably performant. At 500x500, the browser is likely to be
-- very sluggish, with visible delays in rendering visible regions during
-- scrolling.
, maxCols :: Natural
-- ^ The maximum number of columns to render. This limit is only useful if
-- it is set to less than the 'maxCells' value, and it is useful in that case
-- to ensure that more than one (partial) row is displayed. The 'maxCells'
-- value takes priority over this value. See the 'maxCells' for more
-- information.
}
-- | Table KeyVals sorting function; alphanumeric with numeric preference. This
-- can be used as the sortKeyVals function for the table.
--
-- If the value starts or ends with a digit, then this should do a rough numeric
-- sort on the expectation that the digits represent a version or some other
-- numeric value. As an approximation of a numeric sort, sort by word size and
-- then string value. This will result in [ "1", "2", "10", "50", "400" ], but
-- would fail with [ "v1.0", "v2.0", "v3.0", "v2.0.5", "v1.0.0.3" ], but it's a
-- reasonably fast heuristic and probably better than a straight ascii sort.
sortNumericAlpha :: (KeyVals, KeyVals) -> (KeyVals, KeyVals)
sortNumericAlpha (rowkeys, colkeys) = ( fmap sortWithNums <$> rowkeys
, fmap sortWithNums <$> colkeys
)
-- | Sorting for KeyVals. If the value starts or ends with a digit,
-- then this should do a rough numeric sort on the expectation that
-- the digits represent a version or some other numeric value. As an
-- approximation of a numeric sort, sort by word size and then string
-- value. This will result in [ "1", "2", "10", "50", "400" ], but
-- would fail with [ "v1.0", "v2.0", "v3.0", "v2.0.5", "v1.0.0.3" ],
-- but it's a reasonably fast heuristic and probably better than a
-- straight ascii sort.
--
-- This function is used by the 'KVITable' rendering functions.
sortWithNums :: [KeyVal] -> [KeyVal]
sortWithNums kvs =
let skvs = zip (rank <$> kvs) kvs
rank e = if (not $ nullName e) &&
or [ T.head (nameText e) `elem` ['0'..'9']
, T.last (nameText e) `elem` ['0'..'9']
]
then nameLength e
else 0
in snd <$> L.sort skvs