module Data.GraphViz.Attributes.Colors
(
ColorScheme(..)
, Color(..)
, ColorList
, WeightedColor(..)
, toWC
, toColorList
, NamedColor(toColor)
, toWColor
, toColour
, fromColour
, fromAColour
) where
import Data.GraphViz.Parsing
import Data.GraphViz.Printing
import Data.GraphViz.State
import Data.GraphViz.Attributes.ColorScheme(ColorScheme(..))
import Data.GraphViz.Attributes.Colors.X11(X11Color(Transparent), x11Colour)
import Data.GraphViz.Attributes.Colors.SVG(SVGColor, svgColour)
import Data.GraphViz.Attributes.Colors.Brewer(BrewerColor(..))
import Data.GraphViz.Util(bool)
import Data.GraphViz.Exception
import Data.Colour( AlphaColour, opaque, withOpacity
, over, black, alphaChannel, darken)
import Data.Colour.SRGB(Colour, sRGB, sRGB24, toSRGB24)
import Data.Colour.RGBSpace(uncurryRGB)
import Data.Colour.RGBSpace.HSV(hsv)
import Data.Char(isHexDigit)
import Numeric(showHex, readHex)
import Data.Maybe(isJust)
import Data.Word(Word8)
import qualified Data.Text.Lazy as T
data Color = RGB { red :: Word8
, green :: Word8
, blue :: Word8
}
| RGBA { red :: Word8
, green :: Word8
, blue :: Word8
, alpha :: Word8
}
| HSV { hue :: Double
, saturation :: Double
, value :: Double
}
| X11Color X11Color
| SVGColor SVGColor
| BrewerColor BrewerColor
deriving (Eq, Ord, Show, Read)
instance PrintDot Color where
unqtDot (RGB r g b) = hexColor [r,g,b]
unqtDot (RGBA r g b a) = hexColor [r,g,b,a]
unqtDot (HSV h s v) = hcat . punctuate comma $ mapM unqtDot [h,s,v]
unqtDot (SVGColor name) = printNC False name
unqtDot (X11Color name) = printNC False name
unqtDot (BrewerColor bc) = printNC False bc
toDot (X11Color name) = printNC True name
toDot (SVGColor name) = printNC True name
toDot (BrewerColor bc) = printNC True bc
toDot c = dquotes $ unqtDot c
unqtListToDot = hcat . punctuate colon . mapM unqtDot
listToDot [X11Color name] = printNC True name
listToDot [SVGColor name] = printNC True name
listToDot [BrewerColor bc] = printNC True bc
listToDot cs = dquotes $ unqtListToDot cs
hexColor :: [Word8] -> DotCode
hexColor = (<>) (char '#') . hcat . mapM word8Doc
word8Doc :: Word8 -> DotCode
word8Doc w = text $ padding `T.append` simple
where
simple = T.pack $ showHex w ""
padding = T.replicate count (T.singleton '0')
count = 2 findCols 1 w
findCols c n
| n < 16 = c
| otherwise = findCols (c+1) (n `div` 16)
instance ParseDot Color where
parseUnqt = oneOf [ parseHexBased
, parseHSV
, parseNC (undefined :: BrewerColor) False
, parseNC (undefined :: SVGColor) False
, parseX11Color False
]
`onFail`
fail "Could not parse Color"
where
parseHexBased
= do character '#'
cs <- many1 parse2Hex
return $ case cs of
[r,g,b] -> RGB r g b
[r,g,b,a] -> RGBA r g b a
_ -> throw . NotDotCode
$ "Not a valid hex Color specification: "
++ show cs
parseHSV = HSV <$> parse
<* parseSep
<*> parse
<* parseSep
<*> parse
parseSep = oneOf [ string "," *> whitespace
, whitespace1
]
parse2Hex = do c1 <- satisfy isHexDigit
c2 <- satisfy isHexDigit
let [(n, [])] = readHex [c1, c2]
return n
parse = quotedParse parseUnqt
`onFail`
oneOf [ parseNC (undefined :: BrewerColor) True
, parseNC (undefined :: SVGColor) True
, parseX11Color True
]
`onFail`
fail "Could not parse Color"
parseUnqtList = sepBy1 parseUnqt (character ':')
`onFail`
do cs <- getColorScheme
failBad $ "Error parsing list of Colors with color scheme of "
++ show cs
parseList = fmap (:[])
(oneOf [ parseNC (undefined :: BrewerColor) True
, parseNC (undefined :: SVGColor) True
, parseX11Color True
]
)
`onFail`
quotedParse parseUnqtList
`onFail`
do cs <- getColorScheme
failBad $ "Error parsing list of Colors with color scheme of "
++ show cs
type ColorList = [WeightedColor]
data WeightedColor = WC { wColor :: Color
, weighting :: Maybe Double
}
deriving (Eq, Ord, Show, Read)
toWC :: Color -> WeightedColor
toWC = (`WC` Nothing)
toColorList :: [Color] -> ColorList
toColorList = map toWC
instance PrintDot WeightedColor where
unqtDot (WC c mw) = unqtDot c
<> maybe empty ((semi<>) . unqtDot) mw
toDot (WC c Nothing) = toDot c
toDot wc = dquotes $ unqtDot wc
unqtListToDot = hcat . punctuate colon . mapM unqtDot
listToDot [wc] = toDot wc
listToDot wcs = dquotes $ unqtListToDot wcs
instance ParseDot WeightedColor where
parseUnqt = WC <$> parseUnqt <*> optional (character ';' *> parseUnqt)
parse = quotedParse parseUnqt
`onFail`
(toWC <$> parse)
parseUnqtList = sepBy1 parseUnqt (character ':')
`onFail`
do cs <- getColorScheme
failBad $ "Error parsing a ColorList with color scheme of "
++ show cs
parseList = ((:[]) . toWC <$> parse)
`onFail`
quotedParse parseUnqtList
`onFail`
do cs <- getColorScheme
failBad $ "Error parsing ColorList with color scheme of "
++ show cs
class NamedColor nc where
colorScheme :: nc -> ColorScheme
toColor :: nc -> Color
printNC :: Bool -> nc -> DotCode
parseNC' :: Bool -> Parse nc
toWColor :: (NamedColor nc) => nc -> WeightedColor
toWColor = toWC . toColor
parseNC :: (NamedColor nc) => nc -> Bool -> Parse Color
parseNC nc q = fmap (toColor . (`asTypeOf` nc))
$ parseNC' q
instance NamedColor BrewerColor where
colorScheme (BC bs _) = Brewer bs
toColor = BrewerColor
printNC = printNamedColor (\ (BC _ l) -> l)
parseNC' = parseNamedColor mBCS parseUnqt (const True) BC
where
mBCS (Brewer bs) = Just bs
mBCS _ = Nothing
instance NamedColor X11Color where
colorScheme = const X11
toColor = X11Color
printNC = printNamedColor id
parseNC' = parseNamedColor mX11 (parseColorScheme False) (isJust . mX11) (const id)
where
mX11 X11 = Just X11
mX11 _ = Nothing
instance NamedColor SVGColor where
colorScheme = const SVG
toColor = SVGColor
printNC = printNamedColor id
parseNC' = parseNamedColor mSVG (parseColorScheme False) (isJust . mSVG) (const id)
where
mSVG SVG = Just SVG
mSVG _ = Nothing
printNamedColor :: (NamedColor nc, PrintDot lv) => (nc -> lv)
-> Bool -> nc -> DotCode
printNamedColor fl q c = do currentCS <- getColorScheme
if cs == currentCS
then (bool unqtDot toDot q) lv
else bool id dquotes q
$ fslash <> printColorScheme False cs
<> fslash <> unqtDot lv
where
cs = colorScheme c
lv = fl c
parseNamedColor :: (NamedColor nc, ParseDot lv)
=> (ColorScheme -> Maybe cs) -> Parse cs -> (cs -> Bool)
-> (cs -> lv -> nc) -> Bool -> Parse nc
parseNamedColor gcs parseCS vcs mkC q
= do Just cs <- gcs <$> getColorScheme
lv <- bool parseUnqt parse q
`onFail`
mQts (string "//" *> parseUnqt)
return $ mkC cs lv
`onFail`
mQts ( do character '/'
cs <- parseCS
character '/'
if vcs cs
then mkC cs <$> parseUnqt
else fail "Explicit colorscheme not as expected."
)
where
mQts = bool id quotedParse q
parseX11Color :: Bool -> Parse Color
parseX11Color q = X11Color
<$> parseNC' q
`onFail`
bool id quotedParse q (character '/' *> parseUnqt)
`onFail`
do cs <- getColorScheme
case cs of
Brewer{} -> bool parseUnqt parse q
_ -> fail "Unable to parse an X11 color within Brewer"
toColour :: Color -> Maybe (AlphaColour Double)
toColour (RGB r g b) = Just . opaque $ sRGB24 r g b
toColour (RGBA r g b a) = Just . withOpacity (sRGB24 r g b) $ toOpacity a
toColour (HSV h s v) = Just . opaque . uncurryRGB sRGB $ hsv (h*360) s v
toColour (X11Color c) = Just $ x11Colour c
toColour (SVGColor c) = Just . opaque $ svgColour c
toColour BrewerColor{} = Nothing
toOpacity :: Word8 -> Double
toOpacity a = fromIntegral a / maxWord
fromColour :: Colour Double -> Color
fromColour = uncurryRGB RGB . toSRGB24
fromAColour :: AlphaColour Double -> Color
fromAColour ac
| a == 0 = X11Color Transparent
| otherwise = rgb $ round a'
where
a = alphaChannel ac
a' = a * maxWord
rgb = uncurryRGB RGBA $ toSRGB24 colour
colour = darken (recip a) (ac `over` black)
maxWord :: Double
maxWord = fromIntegral (maxBound :: Word8)