lightning-haskell-0.1.0.2: src/Web/Lightning/Utilities.hs
{-# LANGUAGE OverloadedStrings #-}
{-|
Module : Web.Lightning.Utilities
Description : Commonly used utility functions.
Copyright : (c) Connor Moreside, 2016
License : BSD-3
Maintainer : connor@moresi.de
Stability : experimental
Portability : POSIX
Contains commonly used utility functions used throughout the library.
-}
module Web.Lightning.Utilities
(
-- * Utility Functions
omitNulls
, createPayLoad
, createDataPayLoad
, addSessionId
, getLinks
, getNodes
, getPoints
, getPoints3
-- * Validation Functions
, validateBin
, validateColor
, validateColorMap
, validateSize
, validateAlpha
, validateThickness
, validateIndex
, validateCoordinates
, validateCoordinates3
, validateRegion
, validateConn
, defaultBaseURL
)
where
--------------------------------------------------------------------------------
import Data.Aeson
import qualified Data.Text as T
import Web.Lightning.Types.Error
--------------------------------------------------------------------------------
-- | Used in conjunction with ToJSON. It will stop any field that is
-- Nothing (null) in a record from being encoded in JSON.
omitNulls :: [(T.Text, Value)]
-- ^ The plot object to be serialized into JSON Value
-> Value
-- ^ The plot object with Nothing (null) fields removed.
omitNulls = object . filter notNull where
notNull (_, Null) = False
notNull _ = True
-- | Converts the plot creation request record into the proper
-- JSON object that the lightning-viz server expects.
createPayLoad :: T.Text
-- ^ The name of the type of plot to create
-> Value
-- ^ The plot creation request record in JSON Value format
-> Value
-- ^ The properly encoded payload object
createPayLoad t p = object [("type", toJSON t), ("data", p)]
-- | Creates a payload for streaming plots.
createDataPayLoad :: Value
-- ^ Data to update.
-> Value
-- ^ The properly encoded payload object.
createDataPayLoad p = object [("data", p)]
-- | Appends the session route and session id to current URL.
addSessionId :: T.Text
-- ^ The current URL
-> T.Text
-- ^ The session ID to add to current URL
-> T.Text
-- ^ Returns the URL with the session ID
addSessionId url sId = url `T.append` "/sessions/" `T.append` sId
-- | Retrieves all the links for each of the nodes in the adjacency
-- matrix.
getLinks :: [[Double]]
-- ^ The adjacency matrix
-> [[Double]]
-- ^ All the links for each of the nodes
getLinks conn
| length conn == length (head conn) = s1 (zipWithIndex conn)
| otherwise = s4 conn
where s1 = concatMap (\(row, i) -> s3 i (s2 (zipWithIndex row)))
s2 = filter (\(x, _) -> x /= 0)
s3 i = map (\(x, j) -> [i, j, x] :: [Double])
s4 xs = case length xs of
2 -> xs
3 -> map (\l -> [head l, l !! 1, 1.0]) xs
_ -> [[]]
zipWithIndex :: (Enum b, Num b) => [a] -> [(a, b)]
zipWithIndex [] = []
zipWithIndex xs = zipWith (\i el -> (i, el)) xs [0..]
-- | Retrieves all of the nodes from an adjacency matrix.
getNodes :: [[Double]]
-- ^ The adjacency matrix
-> [Int]
-- ^ A list of all of the nodes in matrix
getNodes conn
| length conn == length (head conn) = [0..length conn - 1]
| otherwise = [0..n - 1]
where n = floor $ maximum $ map maximum conn
-- | Zips up x and y points into array pairs.
getPoints :: [Double]
-- ^ X points
-> [Double]
-- ^ Y points
-> [[Double]]
-- ^ Returns [ [x, y] ] pairs
getPoints xs ys = map (\(x, y) -> [x, y]) $ zip xs ys
-- | Zips up x, y, and z points into array triples.
getPoints3 :: [Double]
-- ^ X points
-> [Double]
-- ^ Y points
-> [Double]
-- ^ Z points
-> [[Double]]
-- ^ Returns [ [x, y, z] ] triplets
getPoints3 xs ys zs = map (\(x, y, z) -> [x, y, z]) $ zip3 xs ys zs
-- | Validates the bins of a histogram
validateBin :: Maybe [Double]
-> Either LightningError (Maybe [Double])
validateBin = return
-- | Verify that the color specs are either in the form of
-- [r, g, b] or a list of [[r,g,b],[r,g,b],...]
validateColor :: Maybe [Int]
-> Either LightningError (Maybe [Int])
validateColor (Just colors)
| length colors == 3 = Right (Just colors)
| otherwise = Left $ ValidationError "Color must have three values."
validateColor Nothing = Right Nothing
-- | Verifiy that the color map specified is on of the colorbrewer maps.
--
-- Here are the available colorbrewer values:
-- "BrBG", "PiYG", "PRGn", "PuOr", "RdBu", "RdGy", "RdYlBu",
-- "RdYlGn", "Spectral", "Blues", "BuGn", "BuPu", "GnBu",
-- "Greens", "Greys", "Oranges", "OrRd", "PuBu", "PuBuGn",
-- "PuRd", "Purples", "RdPu", "Reds", "YlGn", "YlGnBu",
-- "YlOrBr", "YlOrRd", "Accent", "Dark2", "Paired", "Pastel1",
-- "Pastel2", "Set1", "Set2", "Set3", or "Lightning"
validateColorMap :: Maybe T.Text
-> Either LightningError (Maybe T.Text)
validateColorMap cm@(Just cmv) =
if cmv `elem` colorMaps
then Right cm
else Left $ ValidationError "Invalid color map specified."
where colorMaps = ["BrBG", "PiYG", "PRGn", "PuOr", "RdBu", "RdGy", "RdYlBu",
"RdYlGn", "Spectral", "Blues", "BuGn", "BuPu", "GnBu",
"Greens", "Greys", "Oranges", "OrRd", "PuBu", "PuBuGn",
"PuRd", "Purples", "RdPu", "Reds", "YlGn", "YlGnBu",
"YlOrBr", "YlOrRd", "Accent", "Dark2", "Paired", "Pastel1",
"Pastel2", "Set1", "Set2", "Set3", "Lightning"]
validateColorMap Nothing = Right Nothing
-- | Verify that all size values are greater than zero.
validateSize :: Maybe [Int]
-> Either LightningError (Maybe [Int])
validateSize size = validateGreaterThan0 size msg
where msg = "Sizes cannot be 0 or negative."
-- | Verify all alpha values are greater than 0
validateAlpha :: Maybe [Double]
-> Either LightningError (Maybe [Double])
validateAlpha alpha = validateGreaterThan0 alpha msg
where msg = "Alpha cannot be 0 or negative."
-- | Verify all thickness values are greater than 0.
validateThickness :: Maybe [Int]
-> Either LightningError (Maybe [Int])
validateThickness thickness = validateGreaterThan0 thickness msg
where msg = "Thickness cannot be 0 or negative."
-- | Verify that there is at least one element in list.
validateIndex :: Maybe [Int]
-> Either LightningError (Maybe [Int])
validateIndex index@(Just idx)
| not (null idx) = Right index
| otherwise = Left $ ValidationError "Index must be non-singleton."
validateIndex Nothing = Right Nothing
-- | Verify that the length of vector x is equal to length of vector y.
validateCoordinates :: [Double]
-- ^ x vector
-> [Double]
-- ^ y vector
-> Either LightningError ([Double], [Double])
validateCoordinates xs ys =
if length xs == length ys
then Right (xs, ys)
else Left $ ValidationError "x and y vectors must be the same length."
-- | Verify that the lenths of the x, y, and z vectors are the same length.
validateCoordinates3 :: [Double]
-- ^ x vector
-> [Double]
-- ^ y vector
-> [Double]
-- ^ z vector
-> Either LightningError ([Double],[Double],[Double])
validateCoordinates3 xs ys zs =
if (length xs == length ys) && (length ys == length zs)
then Right (xs, ys, zs)
else Left $ ValidationError "x, y, and z vectors must be the same length."
-- | Verify that the lengths of the region names are either 2 letters or 3
-- letters.
--
-- 2 letter region names must correspond to US states and 3 letter
-- region names must correspond to countries of the world.
validateRegion :: [T.Text]
-> Either LightningError [T.Text]
validateRegion regions =
if checkTwo || checkThree
then Right regions
else Left $ ValidationError msg
where
msg = "All region names must be all 2 letters or all 3 letters."
checkTwo = all (\x -> T.length x == 2) regions
checkThree = all (\x -> T.length x == 3) regions
-- | Verify that the multi-dimensional list adheres to expected dimensions.
validateConn :: [[Double]]
-> Either LightningError [[Double]]
validateConn conn
| length conn == length (head conn) = Right conn
| length (head conn) == 2 = Right conn
| length (head conn) == 3 = Right conn
| otherwise = Left $ ValidationError msg
where
msg = "Too many entries per link, must be 2 or 3."
-- | Ensure all values are greater than 0.
validateGreaterThan0 :: (Ord a, Num a) => Maybe [a]
-> T.Text
-> Either LightningError (Maybe [a])
validateGreaterThan0 vals@(Just vs) msg =
if any (<= 0) vs
then Left $ ValidationError msg
else Right vals
validateGreaterThan0 Nothing _ = Right Nothing
-- | Defines the default URL for the lightning-viz server.
defaultBaseURL :: T.Text
defaultBaseURL = "http://localhost:3000"