{-| Module : Reanimate.Builtin.TernaryPlot Copyright : Written by David Himmelstrup License : Unlicense Maintainer : lemmih@gmail.com Stability : experimental Portability : POSIX Implementation of ternary plots: -} module Reanimate.Builtin.TernaryPlot ( ACoord , BCoord , CCoord , ternaryPlot -- , atCenter -- , radius , toCartesianCoords , toOffsetCartesianCoords , fromCartesianCoords ) where import Codec.Picture import Graphics.SvgTree (Tree) import Reanimate.Raster import Reanimate.Svg -- a+b+c=1 -- | Left-most coordinate. type ACoord = Double -- | Top-most coordinate. type BCoord = Double -- | Right-most coordinate. type CCoord = Double -- | Creates a centered ternary plot with a width of 5. -- -- Example: -- -- > ternaryPlot 100 $ \aCoord bCoord cCoord -> promotePixel $ -- > let red = round $ aCoord*255 -- > green = round $ bCoord*255 -- > blue = round $ cCoord*255 -- > in PixelRGB8 red green blue -- -- <> ternaryPlot :: Int -- ^ Pixels in the X-axis. More pixels => higher quality. -> (ACoord -> BCoord -> CCoord -> PixelRGBA8) -- ^ a+b+c=1. A=1 is the left-most position, -- B=1 is the top-most position, and -- C=1 is the right-most position. -> Tree ternaryPlot density fn = scaleToWidth stdWidth $ translate (-cX) (-cY) $ scaleToWidth 1 $ flipYAxis $ translate (fromIntegral density/2) (-fromIntegral height/2) $ embedImage $ generateImage gen density height where stdWidth = 5 (cX, cY) = toCartesianCoords (1/3) (1/3) height = round (fromIntegral density * (sqrt 3 / 2) :: Double) gen x y = let x' = (fromIntegral x / fromIntegral density) y' = (fromIntegral y / fromIntegral density) aCoord = (x'*2-bCoord)/2 bCoord = y' / (sqrt 3 / 2) cCoord = 1 - aCoord - bCoord in if aCoord + bCoord > 1 || aCoord < 0 || bCoord < 0 || cCoord < 0 then PixelRGBA8 0 0 0 0 else fn aCoord bCoord cCoord -- atCenter :: Double -> Tree -> Tree -- atCenter stdWidth = translate (-cX*stdWidth) (-cY*stdWidth) -- where -- (cX, cY) = toCartesianCoords (1/3) (1/3) -- radius :: Double -- radius = sqrt (cX*cX + cY*cY) -- where -- (cX, cY) = toCartesianCoords (1/3) (1/3) -- | Compute the XY coordinates from ternary coordinates. -- Note that @CCoord@ is given because @a+b+c=1@. toCartesianCoords :: ACoord -> BCoord -> (Double, Double) toCartesianCoords a b = (x, y) where x = (a+2*b)/2 y = (sqrt 3 / 2) * a -- | Compute the XY coordinates relative from the center of the -- ternary plot. -- Note that @CCoord@ is given because @a+b+c=1@. toOffsetCartesianCoords :: ACoord -> BCoord -> (Double, Double) toOffsetCartesianCoords a b = (tx-zx, ty-zy) where (zx,zy) = toCartesianCoords (1/3) (1/3) (tx,ty) = toCartesianCoords a b -- | Compute ternary coordinates from XY coordinates. fromCartesianCoords :: Double -> Double -> (ACoord, BCoord, CCoord) fromCartesianCoords x y = (a,b,1-a-b) where a = (x*2-b)/2 b = y / (sqrt 3 / 2)