-
Notifications
You must be signed in to change notification settings - Fork 13
/
Plot.hs
172 lines (143 loc) · 7.71 KB
/
Plot.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
{-| 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.Except
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
-> ExceptT 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
-> ExceptT 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
-> ExceptT 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
-> ExceptT 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
-> ExceptT 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