Skip to content

Commit

Permalink
more automatic runners
Browse files Browse the repository at this point in the history
  • Loading branch information
mstksg committed Jul 19, 2018
1 parent 2fb09a5 commit 8e6a8c9
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 47 deletions.
17 changes: 5 additions & 12 deletions app/demo.hs
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import Graphics.Vty
import Data.Default
import Interactive.Plot

cosTest :: Series
cosTest = Series { sItems = map ((\i -> C i (cos i)) . (/ 20) . fromInteger) [-100..100]
, sStyle = PointStyle '*' blue
}

lineTest :: Series
lineTest = Series { sItems = map ((\i -> C i i) . (/ 16) . fromInteger) [-80..80]
, sStyle = PointStyle '+' red
}
cosTest = funcSeries cos (enumRange 100 (R (-5) 5)) def
lineTest = funcSeries id (enumRange 20 (R (-4) 4)) def

main :: IO ()
main = runPlot (PRX (R (-6) 6) (RR 0.5 2.1))
[cosTest, lineTest]
main = runPlot def (Just (R (-6) 6)) Nothing $
fromAutoSeries [cosTest, lineTest]

6 changes: 6 additions & 0 deletions package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ dependencies:
- base >= 4.7 && < 5
- vty
- transformers
- containers
- MonadRandom
- mtl
- microlens
- microlens-platform
- data-default

ghc-options:
- -Wall
Expand Down
5 changes: 3 additions & 2 deletions src/Interactive/Plot.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ module Interactive.Plot (
module P
) where

import Interactive.Plot.Core as P
import Interactive.Plot.Run as P
import Interactive.Plot.Core as P
import Interactive.Plot.Run as P
import Interactive.Plot.Series as P
97 changes: 74 additions & 23 deletions src/Interactive/Plot/Core.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,36 @@
{-# LANGUAGE RecordWildCards #-}

module Interactive.Plot.Core (
Coord(..), Range(..), PointStyle(..), Series(..), Alignment(..), RangeRatio(..), PlotRange(..)
, renderPlot, plotRange
Coord(..), Range(..), PointStyle(..), Series(..), Alignment(..), PlotOpts
, renderPlot
, scaleRange, rSize
, OrdColor(..)
, plotRange
) where

import Control.Applicative
import Data.Default
import Data.Foldable
import Data.Functor.Compose
import Data.Coerce
import Graphics.Vty
import Text.Printf

newtype OrdColor = OC { getOC :: Color }
deriving Eq

instance Ord OrdColor where
compare = coerce compareColor
where
compareColor = \case
ISOColor c -> \case
ISOColor d -> compare c d
Color240 _ -> LT
Color240 c -> \case
ISOColor _ -> GT
Color240 d -> compare c d


data Coord a = C { cX :: a
, cY :: a
}
Expand Down Expand Up @@ -49,6 +68,19 @@ rSize R{..} = rMax - rMin
data PointStyle = PointStyle { psMarker :: Char
, psColor :: Color
}
deriving (Eq)

instance Ord PointStyle where
compare (PointStyle m1 c1) (PointStyle m2 c2)
= compare m1 m2 <> compareColor c1 c2
where
compareColor = \case
ISOColor c -> \case
ISOColor d -> compare c d
Color240 _ -> LT
Color240 c -> \case
ISOColor _ -> GT
Color240 d -> compare c d

data Series = Series { sItems :: [Coord Double]
, sStyle :: PointStyle
Expand All @@ -58,31 +90,46 @@ data Alignment = ALeft
| ACenter
| ARight

data RangeRatio = RR { -- | Where on the screen (0 to 1) to place the other axis
rrZero :: Double
-- | Ratio of height of a terminal character to width
, rrRatio :: Double
}
deriving (Show)
-- data RangeRatio = RR { -- | Where on the screen (0 to 1) to place the other axis
-- rrZero :: Double
-- -- | Ratio of height of a terminal character to width
-- , rrRatio :: Double
-- }
-- deriving (Show)

data PlotOpts = PO { poRatio :: Double -- ^ character width ratio (height to width)
}

instance Default PlotOpts where
def = PO { poRatio = 2.1
}

data PlotRange = PRXY (Coord (Range Double))
| PRX (Range Double) RangeRatio
| PRY RangeRatio (Range Double)

plotRange
:: Coord (Range Int) -- ^ display region
-> PlotRange -- ^ plot axis range specification
:: PlotOpts
-> Coord (Range Int) -- ^ display region
-> Maybe (Range Double) -- ^ X range
-> Maybe (Range Double) -- ^ Y range
-> [Series] -- ^ Points
-> Coord (Range Double) -- ^ actual plot axis range
plotRange dr = \case
PRXY pr -> pr
PRX rX RR{..} ->
let yr = rSize rX * fromIntegral (rSize (cY dr)) / fromIntegral (rSize (cX dr)) * rrRatio
y0 = (rrZero - 1) * yr
in C rX (R y0 (y0 + yr))
PRY RR{..} rY ->
let xr = rSize rY * fromIntegral (rSize (cY dr)) / fromIntegral (rSize (cX dr)) * rrRatio
x0 = (rrZero - 1) * xr
in C (R x0 (x0 + xr)) rY
plotRange PO{..} dr rX rY ss = case (rX, rY) of
(Nothing, Nothing) -> pointRange
(Just x , Nothing) -> C x (setRangeSize (rSize x * displayRatio) $ cY pointRange)
(Nothing, Just y ) -> C (setRangeSize (rSize y / displayRatio) $ cX pointRange) y
(Just x , Just y ) -> C x y
where
displayRatio = fromIntegral (rSize (cY dr))
/ (fromIntegral (rSize (cX dr)) * poRatio)
unZero :: Range Double -> Range Double
unZero r
| rSize r == 0 = R (subtract 1) (+ 1) <*> r
| otherwise = r
pointRange :: Coord (Range Double)
pointRange = fmap unZero
. foldl' (liftA2 go) (C (R 0 0) (R 0 0))
$ concatMap sItems ss
where
go oldR x = R min max <*> pure x <*> oldR

renderPlot
:: Coord (Range Int) -- ^ display region
Expand Down Expand Up @@ -146,6 +193,10 @@ scaleRange x r = lerp unit r . (* x) . lerp r unit <$> r
where
unit = R (-1) 1

setRangeSize :: Fractional a => a -> Range a -> Range a
setRangeSize x r = lerp unit r <$> R (-x/2) (x/2)
where
unit = R (-1) 1

renderSeries
:: Coord (Range Int) -- ^ Display region
Expand Down
27 changes: 17 additions & 10 deletions src/Interactive/Plot/Run.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ViewPatterns #-}

module Interactive.Plot.Run (
runPlot
Expand All @@ -9,7 +8,6 @@ module Interactive.Plot.Run (
import Control.Applicative
import Control.Concurrent
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Maybe
import Data.Foldable
import Data.IORef
Expand All @@ -20,12 +18,14 @@ data PEvent = PEQuit
| PEZoom Double
| PEPan (Coord Double)
| PEResize (Coord Int)
| PEReset

processEvent :: Event -> Maybe PEvent
processEvent = \case
EvKey KEsc [] -> Just PEQuit
EvKey (KChar 'c') [MCtrl] -> Just PEQuit
EvKey (KChar 'q') [] -> Just PEQuit
EvKey (KChar 'r') [] -> Just PEReset
EvKey (KChar '=') [] -> Just $ PEZoom (sqrt 0.5)
EvKey (KChar '+') [] -> Just $ PEZoom (sqrt 0.5)
EvKey (KChar '-') [] -> Just $ PEZoom (sqrt 2)
Expand All @@ -52,23 +52,27 @@ displayRange o = do
pure $ C (R 0 wd) (R 0 ht)

runPlot
:: PlotRange
:: PlotOpts
-> Maybe (Range Double) -- ^ x range
-> Maybe (Range Double) -- ^ y range
-> [Series]
-> IO ()
runPlot pr ss = do
runPlot po rX rY ss = do
vty <- mkVty =<< standardIOConfig
dr <- displayRange $ outputIface vty
psRef <- newIORef PlotState { psRange = plotRange dr pr
, psSerieses = ss
}

psRef <- newIORef =<< initPS vty
peChan <- newChan
tPE <- forkIO . forever $
traverse_ (writeChan peChan) . processEvent =<< nextEvent vty

void . runMaybeT . many . (guard =<<) . liftIO $
void . runMaybeT . many . MaybeT . fmap guard $
plotLoop vty peChan psRef tPE
where
initPS :: Vty -> IO PlotState
initPS vty = do
dr <- displayRange $ outputIface vty
pure PlotState { psRange = plotRange po dr rX rY ss
, psSerieses = ss
}
plotLoop
:: Vty
-> Chan PEvent
Expand Down Expand Up @@ -104,3 +108,6 @@ runPlot pr ss = do
writeIORef psRef $
ps { psRange = newRange }
pure True
PEReset -> do
writeIORef psRef =<< initPS vty
pure True
110 changes: 110 additions & 0 deletions src/Interactive/Plot/Series.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RecordWildCards #-}

module Interactive.Plot.Series (
AutoPointStyle(..)
, AutoSeries(..)
, defaultStyles
, fromAutoSeries
, listSeries
, tupleSeries
, autoSeries
, funcSeries
, enumRange
) where

import Control.Monad.Random
import Control.Monad.State
import Data.Default
import Data.Maybe
import Graphics.Vty
import Interactive.Plot.Core
import qualified Data.Set as S

data AutoPointStyle = APS
{ apsMarker :: Maybe Char
, apsColor :: Maybe Color
}
deriving Show

instance Default AutoPointStyle where
def = APS Nothing Nothing

data AutoSeries = AS { asItems :: [Coord Double]
, asStyle :: AutoPointStyle
}
deriving Show

listSeries :: [Double] -> AutoPointStyle -> AutoSeries
listSeries xs = AS (zipWith C [0..] xs)

tupleSeries :: [(Double, Double)] -> AutoPointStyle -> AutoSeries
tupleSeries xs = AS (uncurry C <$> xs)

autoSeries :: Series -> AutoSeries
autoSeries (Series xs PointStyle{..}) = AS xs $ APS (Just psMarker) (Just psColor)

enumRange :: Int -> Range Double -> [Double]
enumRange n r = (+ rMin r) . (* s) . fromIntegral <$> [0 .. (n - 1)]
where
s = rSize r / fromIntegral (n - 1)

funcSeries :: (Double -> Double) -> [Double] -> AutoPointStyle -> AutoSeries
funcSeries f xs = tupleSeries [ (x, f x) | x <- xs ]

defaultMarkers :: S.Set Char
defaultMarkers = S.fromList "o*+~.,=#`x-"
defaultStyles :: S.Set PointStyle
defaultColors = S.fromList $ OC <$> [white, yellow, blue, red, green, cyan, magenta]
defaultColors :: S.Set OrdColor
defaultStyles = combinePointStyles defaultMarkers defaultColors

combinePointStyles
:: S.Set Char
-> S.Set OrdColor
-> S.Set PointStyle
combinePointStyles ms cs = combine `S.map` S.cartesianProduct ms cs
where
combine (m, OC c) = PointStyle m c


fromAutoSeries :: [AutoSeries] -> [Series]
fromAutoSeries = flip evalRand seed . flip evalStateT S.empty . mapM go
where
seed = mkStdGen 28922710942259
go :: AutoSeries -> StateT (S.Set PointStyle) (Rand StdGen) Series
go (AS is ps) = Series is <$> pickPs
where
pickPs = case ps of
APS Nothing Nothing -> do
picked <- get
samp <- sampleSet $ defaultStyles S.\\ picked
case samp of
Nothing -> fromJust <$> sampleSet defaultStyles
Just s -> s <$ put (s `S.insert` picked)
APS (Just m) Nothing -> do
picked <- get
let allDefaults = combinePointStyles (S.singleton m) defaultColors
samp <- sampleSet $ allDefaults S.\\ picked
case samp of
Nothing -> fromJust <$> sampleSet allDefaults
Just s -> s <$ put (s `S.insert` picked)
APS Nothing (Just c) -> do
picked <- get
let allDefaults = combinePointStyles defaultMarkers (S.singleton (OC c))
samp <- sampleSet $ allDefaults S.\\ picked
case samp of
Nothing -> fromJust <$> sampleSet allDefaults
Just s -> s <$ put (s `S.insert` picked)
APS (Just m) (Just c) -> pure $ PointStyle m c

sampleSet
:: (MonadRandom m)
=> S.Set a
-> m (Maybe a)
sampleSet xs
| S.null xs = pure Nothing
| otherwise = do
i <- getRandomR (0, S.size xs - 1)
pure $ Just (i `S.elemAt` xs)

0 comments on commit 8e6a8c9

Please sign in to comment.