-
Notifications
You must be signed in to change notification settings - Fork 62
/
Copy pathgnuplotxyplot.lua
159 lines (137 loc) · 5.58 KB
/
gnuplotxyplot.lua
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
---
-- Plot two real-valued signals, or the real and imaginary components of one
-- complex-valued signal, in a gnuplot XY plot. This sink requires the gnuplot
-- program. This sink should be used with relatively low sample rates, as it
-- does not skip any samples, or it may otherwise throttle a flow graph.
--
-- @category Sinks
-- @block GnuplotXYPlotSink
-- @tparam int num_samples Number of samples to plot
-- @tparam[opt=""] string title Title of plot
-- @tparam[opt={}] table options Additional options, specifying:
-- * `complex` (bool, default false)
-- * `xlabel` (string, default "")
-- * `ylabel` (string, default "")
-- * `xrange` (array of two numbers, default `nil`
-- for autoscale)
-- * `yrange` (array of two numbers, default `nil`
-- for autoscale)
-- * `extra_settings` (array of strings containing
-- gnuplot commands)
--
-- @signature x:Float32, y:Float32 >
-- @signature in:ComplexFloat32 >
--
-- @usage
-- -- Plot a 1 kHz complex exponential sampled at 250 kHz
-- local src = radio.SignalSource('exponential', 1e3, 250e3)
-- local throttle = radio.ThrottleBlock()
-- local snk = radio.GnuplotXYPlotSink(1000, 'Complex Exponential', {complex = true})
-- top:connect(src, throttle, snk)
--
-- -- Plot two real-valued signals
-- local snk = radio.GnuplotXYPlotSink(1000, 'XY')
-- top:connect(src1, 'out', snk, 'x')
-- top:connect(src2, 'out', snk, 'y')
local ffi = require('ffi')
local block = require('radio.core.block')
local types = require('radio.types')
local GnuplotXYPlotSink = block.factory("GnuplotXYPlotSink")
function GnuplotXYPlotSink:instantiate(num_samples, title, options)
self.num_samples = assert(num_samples, "Missing argument #1 (num_samples)")
self.title = title or ""
self.options = options or {}
self.sample_count = 0
if options.complex then
self:add_type_signature({block.Input("in", types.ComplexFloat32)}, {}, self.process_complex)
else
self:add_type_signature({block.Input("x", types.Float32), block.Input("y", types.Float32)}, {})
end
end
function GnuplotXYPlotSink:initialize()
-- Check gnuplot exists
local ret = os.execute("gnuplot --version >/dev/null 2>&1")
assert(ret == 0 or ret == true, "gnuplot not found. Is gnuplot installed?")
end
function GnuplotXYPlotSink:write_gnuplot(s)
local len = 0
while len < #s do
local bytes_written = tonumber(ffi.C.fwrite(ffi.cast("char *", s) + len, 1, #s - len, self.gnuplot_f))
if bytes_written <= 0 then
error("gnuplot write(): " .. ffi.string(ffi.C.strerror(ffi.errno())))
end
len = len + bytes_written
end
end
function GnuplotXYPlotSink:initialize_gnuplot()
-- Initialize gnuplot
self.gnuplot_f = io.popen("gnuplot >/dev/null 2>&1", "w")
self:write_gnuplot("set xtics\n")
self:write_gnuplot("set ytics\n")
self:write_gnuplot("set grid\n")
self:write_gnuplot("set style data points\n")
self:write_gnuplot("unset key\n")
self:write_gnuplot(string.format("set xlabel '%s'\n", self.options.xlabel or ""))
self:write_gnuplot(string.format("set ylabel '%s'\n", self.options.ylabel or ""))
self:write_gnuplot(string.format("set title '%s'\n", self.title))
-- Use xrange if it was specified, otherwise default to autoscale
if self.options.xrange then
self:write_gnuplot(string.format("set xrange [%f:%f]\n", self.options.xrange[1], self.options.xrange[2]))
else
self:write_gnuplot("set autoscale x\n")
end
-- Use yrange if it was specified, otherwise default to autoscale
if self.options.yrange then
self:write_gnuplot(string.format("set yrange [%f:%f]\n", self.options.yrange[1], self.options.yrange[2]))
else
self:write_gnuplot("set autoscale y\n")
end
-- Apply any extra settings
if self.options.extra_settings then
for i = 1, #self.options.extra_settings do
self:write_gnuplot(self.options.extra_settings[i] .. "\n")
end
end
-- Build plot string
self.plot_str = string.format("plot '-' binary format='%%float32%%float32' record=%d using 1:2 linestyle 1\n", self.num_samples)
self:write_gnuplot(self.plot_str)
end
function GnuplotXYPlotSink:process(x, y)
if not self.gnuplot_f then
self:initialize_gnuplot()
end
for i = 0, x.length-1 do
-- Write each raw sample
self:write_gnuplot(ffi.string(x.data[i], ffi.sizeof(x.data[0])))
self:write_gnuplot(ffi.string(y.data[i], ffi.sizeof(y.data[0])))
self.sample_count = self.sample_count + 1
-- Rewrite plot string when we reach num_samples
if self.sample_count == self.num_samples then
self.sample_count = 0
self:write_gnuplot(self.plot_str)
end
end
end
function GnuplotXYPlotSink:process_complex(x)
if not self.gnuplot_f then
self:initialize_gnuplot()
end
for i = 0, x.length-1 do
-- Write each raw sample
self:write_gnuplot(ffi.string(x.data[i], ffi.sizeof(x.data[0])))
self.sample_count = self.sample_count + 1
-- Restart plot when we reach num_samples
if self.sample_count == self.num_samples then
self.sample_count = 0
self:write_gnuplot(self.plot_str)
end
end
end
function GnuplotXYPlotSink:cleanup()
if not self.gnuplot_f then
return
end
self.gnuplot_f:write("quit\n")
self.gnuplot_f:close()
end
return GnuplotXYPlotSink