sdr-0.1.0.2: hs_sources/SDR/Plot.hs
{-| Create graphical plots of signals and their spectrums. Uses OpenGL. -}
module SDR.Plot (
-- * Line Graphs
plotLine,
plotLineAxes,
-- * Waterfalls
plotWaterfall,
--plotWaterfallAxes,
-- * Filled In Line Graphs
plotFill,
plotFillAxes,
-- * Axes
zeroAxes,
centeredAxes
) where
import Control.Monad.Trans.Either
import qualified Data.Vector.Storable as VS
import Graphics.Rendering.OpenGL
import Graphics.Rendering.Cairo
import Pipes
import Data.Colour.Names
import Graphics.Rendering.Pango
import Graphics.DynamicGraph.Line
import Graphics.DynamicGraph.Waterfall
import Graphics.DynamicGraph.FillLine
import Graphics.DynamicGraph.Axis
import Graphics.DynamicGraph.RenderCairo
import Graphics.DynamicGraph.Window
-- | Create a window and plot a dynamic line graph of the incoming data.
plotLine :: Int -- ^ Window width
-> Int -- ^ Window height
-> Int -- ^ Number of samples in each buffer
-> Int -- ^ Number of vertices in graph
-> EitherT String IO (Consumer (VS.Vector GLfloat) IO ())
plotLine width height samples resolution = window width height $ fmap pipeify $ renderLine samples resolution
-- | Create a window and plot a dynamic line graph of the incoming data. With Axes.
plotLineAxes :: Int -- ^ Window width
-> Int -- ^ Window height
-> Int -- ^ Number of samples in each buffer
-> Int -- ^ Number of vertices in graph
-> Render () -- ^ Cairo Render object that draws the axes
-> EitherT String IO (Consumer (VS.Vector GLfloat) IO ())
plotLineAxes width height samples xResolution rm = window width height $ do
--render the graph
renderFunc <- renderLine samples xResolution
--render the axes
renderAxisFunc <- renderCairo rm width height
return $ for cat $ \dat -> lift $ do
blend $= Disabled
viewport $= (Position 50 50, Size (fromIntegral width - 100) (fromIntegral height - 100))
renderFunc dat
blend $= Enabled
blendFunc $= (SrcAlpha, OneMinusSrcAlpha)
viewport $= (Position 0 0, Size (fromIntegral width) (fromIntegral height))
renderAxisFunc
-- | Create a window and plot a waterfall of the incoming data.
plotWaterfall :: Int -- ^ Window width
-> Int -- ^ Window height
-> Int -- ^ Number of columns
-> Int -- ^ Number of rows
-> [GLfloat] -- ^ The color map
-> EitherT String IO (Consumer (VS.Vector GLfloat) IO ())
plotWaterfall windowWidth windowHeight width height colorMap = window windowWidth windowHeight $ renderWaterfall width height colorMap
{-
-- | Create a window and plot a waterfall of the incoming data. With Axes. TODO: doesnt work.
plotWaterfallAxes :: Int -- ^ Window width
-> Int -- ^ Window height
-> Int -- ^ Number of columns
-> Int -- ^ Number of rows
-> [GLfloat] -- ^ The color map
-> Render () -- ^ Cairo Render object that draws the axes
-> EitherT String IO (Consumer (VS.Vector GLfloat) IO ())
plotWaterfallAxes windowWidth windowHeight width height colorMap rm = window windowWidth windowHeight $ do
renderPipe <- renderWaterfall width height colorMap
renderAxisFunc <- renderCairo rm width height
return $ (>-> renderPipe) $ for cat $ \dat -> do
lift $ viewport $= (Position 0 0, Size (fromIntegral width) (fromIntegral height))
lift renderAxisFunc
lift $ viewport $= (Position 50 50, Size (fromIntegral width - 100) (fromIntegral height - 100))
yield dat
-}
-- | Create a window and plot a dynamic filled in line graph of the incoming data.
plotFill :: Int -- ^ Window width
-> Int -- ^ Window height
-> Int -- ^ Number of samples in each buffer
-> [GLfloat] -- ^ The color map
-> EitherT String IO (Consumer (VS.Vector GLfloat) IO ())
plotFill width height samples colorMap = window width height $ fmap pipeify $ renderFilledLine samples colorMap
-- | Create a window and plot a dynamic filled in line graph of the incoming data. With Axes.
plotFillAxes :: Int -- ^ Window width
-> Int -- ^ Window height
-> Int -- ^ Number of samples in each buffer
-> [GLfloat] -- ^ The color map
-> Render () -- ^ Cairo Render object that draws the axes
-> EitherT String IO (Consumer (VS.Vector GLfloat) IO ())
plotFillAxes width height samples colorMap rm = window width height $ do
renderFunc <- renderFilledLine samples colorMap
renderAxisFunc <- renderCairo rm width height
return $ for cat $ \dat -> lift $ do
viewport $= (Position 50 50, Size (fromIntegral width - 100) (fromIntegral height - 100))
renderFunc dat
blend $= Enabled
blendFunc $= (SrcAlpha, OneMinusSrcAlpha)
viewport $= (Position 0 0, Size (fromIntegral width) (fromIntegral height))
renderAxisFunc
-- | Create a Cairo `Render` monad that draws a set of axes with 0 at the bottom left.
zeroAxes :: Int -- ^ Image width
-> Int -- ^ Image height
-> Double -- ^ X axis span
-> Double -- ^ X axis grid interval
-> Render ()
zeroAxes width height bandwidth interval = do
blankCanvasAlpha black 0 (fromIntegral width) (fromIntegral height)
let xSeparation = (interval / bandwidth) * (fromIntegral width - 100)
ySeparation = 0.2 * (fromIntegral height - 100)
xCoords = takeWhile (< (fromIntegral width - 50)) $ iterate (+ xSeparation) 50
yCoords = takeWhile (> 50) $ iterate (\x -> x - ySeparation) (fromIntegral height - 50)
ctx <- liftIO $ cairoCreateContext Nothing
xAxisLabels ctx white (map (\n -> show n ++ " KHz" ) (takeWhile (< bandwidth) $ iterate (+ interval) 0)) xCoords (fromIntegral height - 50)
drawAxes (fromIntegral width) (fromIntegral height) 50 50 50 50 white 2
xAxisGrid gray 1 [] 50 (fromIntegral height - 50) xCoords
yAxisGrid gray 1 [4, 2] 50 (fromIntegral width - 50) yCoords
-- | Create a Cairo `Render` monad that draws a set of axes with the X axis centered on a specified value.
centeredAxes :: Int -- ^ Image width
-> Int -- ^ Image height
-> Double -- ^ Center X value
-> Double -- ^ X axis span
-> Double -- ^ X axis grid interval
-> Render ()
centeredAxes width height cFreq bandwidth interval = do
blankCanvasAlpha black 0 (fromIntegral width) (fromIntegral height)
let xSeparation = (interval / bandwidth) * (fromIntegral width - 100)
firstXLabel = fromIntegral (ceiling ((cFreq - (bandwidth / 2)) / interval)) * interval
fract x = x - fromIntegral (floor x)
xOffset = fract ((cFreq - (bandwidth / 2)) / interval) * xSeparation
ySeparation = 0.2 * (fromIntegral height - 100)
xCoords = takeWhile (< (fromIntegral width - 50)) $ iterate (+ xSeparation) (50 + xOffset)
yCoords = takeWhile (> 50) $ iterate (\x -> x - ySeparation) (fromIntegral height - 50)
ctx <- liftIO $ cairoCreateContext Nothing
xAxisLabels ctx white (map (\n -> show n ++ " MHZ") (takeWhile (< (cFreq + bandwidth / 2)) $ iterate (+ interval) firstXLabel)) xCoords (fromIntegral height - 50)
drawAxes (fromIntegral width) (fromIntegral height) 50 50 50 50 white 2
xAxisGrid gray 1 [] 50 (fromIntegral height - 50) xCoords
yAxisGrid gray 1 [4, 2] 50 (fromIntegral width - 50) yCoords