roctave 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +33 -0
  4. data/ext/roctave/cu8_file_reader.c +331 -0
  5. data/ext/roctave/cu8_file_reader.h +30 -0
  6. data/ext/roctave/extconf.rb +6 -0
  7. data/ext/roctave/fir_filter.c +795 -0
  8. data/ext/roctave/fir_filter.h +29 -0
  9. data/ext/roctave/freq_shifter.c +410 -0
  10. data/ext/roctave/freq_shifter.h +29 -0
  11. data/ext/roctave/iir_filter.c +462 -0
  12. data/ext/roctave/iir_filter.h +29 -0
  13. data/ext/roctave/roctave.c +38 -0
  14. data/ext/roctave/roctave.h +27 -0
  15. data/lib/roctave.rb +168 -0
  16. data/lib/roctave/bilinear.rb +92 -0
  17. data/lib/roctave/butter.rb +87 -0
  18. data/lib/roctave/cheby.rb +180 -0
  19. data/lib/roctave/cu8_file_reader.rb +45 -0
  20. data/lib/roctave/dft.rb +280 -0
  21. data/lib/roctave/filter.rb +64 -0
  22. data/lib/roctave/finite_difference_coefficients.rb +73 -0
  23. data/lib/roctave/fir.rb +121 -0
  24. data/lib/roctave/fir1.rb +134 -0
  25. data/lib/roctave/fir2.rb +246 -0
  26. data/lib/roctave/fir_design.rb +311 -0
  27. data/lib/roctave/firls.rb +380 -0
  28. data/lib/roctave/firpm.rb +499 -0
  29. data/lib/roctave/freq_shifter.rb +47 -0
  30. data/lib/roctave/freqz.rb +233 -0
  31. data/lib/roctave/iir.rb +80 -0
  32. data/lib/roctave/interp1.rb +78 -0
  33. data/lib/roctave/plot.rb +748 -0
  34. data/lib/roctave/poly.rb +46 -0
  35. data/lib/roctave/roots.rb +73 -0
  36. data/lib/roctave/sftrans.rb +157 -0
  37. data/lib/roctave/version.rb +3 -0
  38. data/lib/roctave/window.rb +116 -0
  39. data/roctave.gemspec +79 -0
  40. data/samples/butter.rb +12 -0
  41. data/samples/cheby.rb +28 -0
  42. data/samples/dft.rb +18 -0
  43. data/samples/differentiator.rb +48 -0
  44. data/samples/differentiator_frequency_scaling.rb +52 -0
  45. data/samples/fft.rb +40 -0
  46. data/samples/finite_difference_coefficient.rb +53 -0
  47. data/samples/fir1.rb +13 -0
  48. data/samples/fir2.rb +14 -0
  49. data/samples/fir2_windows.rb +29 -0
  50. data/samples/fir2bank.rb +30 -0
  51. data/samples/fir_low_pass.rb +44 -0
  52. data/samples/firls.rb +77 -0
  53. data/samples/firpm.rb +78 -0
  54. data/samples/hilbert_transformer.rb +20 -0
  55. data/samples/hilbert_transformer_frequency_scaling.rb +47 -0
  56. data/samples/plot.rb +45 -0
  57. data/samples/stem.rb +8 -0
  58. data/samples/type1.rb +25 -0
  59. data/samples/type3.rb +24 -0
  60. data/samples/windows.rb +25 -0
  61. metadata +123 -0
@@ -0,0 +1,47 @@
1
+ ## Copyright (C) 2019 Théotime Bollengier <theotime.bollengier@gmail.com>
2
+ ##
3
+ ## This file is part of Roctave
4
+ ##
5
+ ## Roctave is free software: you can redistribute it and/or modify
6
+ ## it under the terms of the GNU General Public License as published by
7
+ ## the Free Software Foundation, either version 3 of the License, or
8
+ ## (at your option) any later version.
9
+ ##
10
+ ## Roctave is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+ ##
15
+ ## You should have received a copy of the GNU General Public License
16
+ ## along with Roctave. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+
19
+ module Roctave
20
+ class FreqShifter
21
+
22
+ # Multiply the signal with exp(2*i*pi*f*t)
23
+ # @overload shift(signal, freq: 0.5, fs: 1.0, initial_phase: 0.0)
24
+ # @param signal [Array<Float,Complex>, Float, Complex] a single or and array of float or complex numbers
25
+ # @param freq [Float] the amount of frequency to shift
26
+ # @param fs [Float] the sampling frequency
27
+ # @param initial_phase [Float] the initial phase of the oscillator
28
+ # @return [Array<Float,Complex>, Float, Complex] a single or and array of float or complex numbers
29
+ def self.shift (sig, *args)
30
+ shifter = Roctave::FreqShifter.new(*args)
31
+ shifter.shift sig
32
+ end
33
+
34
+ # Multiply the signal with cos(2*i*pi*f*t)
35
+ # @overload shift(signal, freq: 0.5, fs: 1.0, initial_phase: 0.0)
36
+ # @param signal [Array<Float,Complex>, Float, Complex] a single or and array of float or complex numbers
37
+ # @param freq [Float] the amount of frequency to shift
38
+ # @param fs [Float] the sampling frequency
39
+ # @param initial_phase [Float] the initial phase of the oscillator
40
+ # @return [Array<Float,Complex>, Float, Complex] a single or and array of float or complex numbers
41
+ def self.amplitude_modulate (sig, *args)
42
+ shifter = Roctave::FreqShifter.new(*args)
43
+ shifter.amplitude_modulate sig
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,233 @@
1
+ ## Copyright (C) 1994-2015 John W. Eaton (GNU Octave implementation)
2
+ ## Copyright (C) 2019 Théotime Bollengier <theotime.bollengier@gmail.com>
3
+ ##
4
+ ## This file is part of Roctave
5
+ ##
6
+ ## Roctave is free software: you can redistribute it and/or modify
7
+ ## it under the terms of the GNU General Public License as published by
8
+ ## the Free Software Foundation, either version 3 of the License, or
9
+ ## (at your option) any later version.
10
+ ##
11
+ ## Roctave is distributed in the hope that it will be useful,
12
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ ## GNU General Public License for more details.
15
+ ##
16
+ ## You should have received a copy of the GNU General Public License
17
+ ## along with Roctave. If not, see <https://www.gnu.org/licenses/>.
18
+
19
+
20
+ module Roctave
21
+
22
+ # @!group Plotting
23
+
24
+ ##
25
+ # Return the complex frequency response H of the rational IIR filter
26
+ # whose numerator and denominator coefficients are B and A,
27
+ # respectively.
28
+ #
29
+ # If A is omitted, the denominator is assumed to be 1 (this
30
+ # corresponds to a simple FIR filter).
31
+ #
32
+ # @param b [Array<Numeric>] Numerator of the IIR filter.
33
+ # @param a [Array<Numeric>] Denominator of the IIR filter.
34
+ # @param nb_points [Integer] Number of points on which to evaluate the response. Faster if power of two.
35
+ # @param region [Symbol] Either :half of :whole
36
+ # @param fs [Float] If not specified, output frequencies are on [0 pi] or [-pi pi] depending on +region+
37
+ # @param opts [Symbols] You can specify :magnitude, :phase and :group_delay to plot the magnitude, phase and group delay response, and specify :degree and :dB, and :shift for fftshifting when ploting the whole region
38
+ def self.freqz (b, *opts, nb_points: 512, region: nil, fs: nil)
39
+ nb_points = [4, nb_points.to_i].max
40
+ fs = fs.to_f.abs if fs
41
+ sampling_rate = fs
42
+ normalize_freqency = fs.nil?
43
+ degree = false
44
+ log = false
45
+ plot_m = false
46
+ plot_p = false
47
+ plot_d = false
48
+ shift = false
49
+ a = [1.0]
50
+ opts.each do |opt|
51
+ case opt
52
+ when :magnitude
53
+ plot_m = true
54
+ when :phase
55
+ plot_p = true
56
+ when :group_delay
57
+ plot_d = true
58
+ when :degree
59
+ degree = true
60
+ when :dB
61
+ log = true
62
+ when :shift
63
+ shift = true
64
+ when Array
65
+ a = opt
66
+ else
67
+ raise ArgumentError.new "Unexpected argument \"#{opt}\""
68
+ end
69
+ end
70
+ do_plot = (plot_m or plot_p)
71
+ raise ArgumentError.new "First argument must be an array" unless b.kind_of?(Array)
72
+ b = [1.0] if b.empty?
73
+ if region != :half and region != :whole then
74
+ if b.find{|v| v.kind_of?(Complex)} or a.find{|v| v.kind_of?(Complex)} then
75
+ region = :whole
76
+ else
77
+ region = :half
78
+ end
79
+ end
80
+ if fs.nil? then
81
+ fs = 2*Math::PI
82
+ end
83
+
84
+ k = [b.length, a.length].max
85
+ if k > nb_points/2 and plot_p then
86
+ ## Ensure a causal phase response.
87
+ nb_points = nb_points * 2**Math.log2(2.0*k/nb_points).ceil
88
+ end
89
+
90
+ if region == :whole then
91
+ ####
92
+ =begin
93
+ n = nb_points
94
+ if do_plot then
95
+ f = (0..nb_points).collect{|i| fs * i / n} # do 1 more for the plot
96
+ else
97
+ f = (0...nb_points).collect{|i| fs * i / n}
98
+ end
99
+ =end
100
+ ####
101
+ n = nb_points
102
+ f = (0...nb_points).collect{|i| fs * i / n}
103
+ ####
104
+ else
105
+ n = 2*nb_points
106
+ #nb_points += 1 if do_plot
107
+ f = (0...nb_points).collect{|i| fs * i / n}
108
+ end
109
+
110
+ pad_sz = n*(k.to_f / n).ceil
111
+ b = Roctave.postpad(b, pad_sz)
112
+ a = Roctave.postpad(a, pad_sz)
113
+
114
+ hb = Roctave.zeros(nb_points)
115
+ ha = Roctave.zeros(nb_points)
116
+
117
+ (0...pad_sz).step(n).each do |i|
118
+ tmp = Roctave.dft(Roctave.postpad(b[i ... i+n], n))[0...nb_points]
119
+ hb = hb.collect.with_index{|v, j| v+tmp[j]}
120
+ tmp = Roctave.dft(Roctave.postpad(a[i ... i+n], n))[0...nb_points]
121
+ ha = ha.collect.with_index{|v, j| v+tmp[j]}
122
+ end
123
+
124
+ h = (0...nb_points).collect{|i| hb[i] / ha[i]}
125
+
126
+ if do_plot and Roctave.respond_to?(:plot) then
127
+ fs /= 2.0 if region == :half
128
+ if plot_m then
129
+ title = '{/:Bold Magnitude response}'
130
+ if normalize_freqency then
131
+ xlabel = 'Normalized frequency (x {/Symbol p} rad/sample)'
132
+ if region == :half then
133
+ freq = f.collect{|v| v/fs}
134
+ xlim = [0, 1]
135
+ else
136
+ freq = f.collect{|v| 2.0*v/fs}
137
+ xlim = [0, 2]
138
+ end
139
+ else
140
+ xlabel = 'Frequency'
141
+ freq = f
142
+ xlim = [0, fs]
143
+ end
144
+ if log then
145
+ ylabel = 'Magnitude (dB)'
146
+ mag = h.collect{|v| 20*Math.log10(v.abs)}
147
+ else
148
+ ylabel = 'Magnitude'
149
+ mag = h.collect{|v| v.abs}
150
+ end
151
+ if region == :whole and shift then
152
+ xlim = xlim.collect{|v| v - xlim.last / 2.0}
153
+ offset = (freq[1] - freq[0])/2.0
154
+ freq = freq.collect{|v| v - freq.last / 2.0 - offset}
155
+ mag = Roctave.fftshift(mag)
156
+ end
157
+ Roctave.plot(freq, mag, title: title, xlabel: xlabel, ylabel: ylabel, xlim: xlim, grid: true)
158
+ end
159
+ if plot_p then
160
+ title = '{/:Bold Phase shift response}'
161
+ if normalize_freqency then
162
+ xlabel = 'Normalized frequency (x {/Symbol p} rad/sample)'
163
+ if region == :half then
164
+ freq = f.collect{|v| v/fs}
165
+ xlim = [0, 1]
166
+ else
167
+ freq = f.collect{|v| 2.0*v/fs}
168
+ xlim = [0, 2]
169
+ end
170
+ else
171
+ xlabel = 'Frequency'
172
+ freq = f
173
+ xlim = [0, fs]
174
+ end
175
+ pha = h.collect{|v| v.arg}
176
+ if region == :whole and shift then
177
+ xlim = xlim.collect{|v| v - xlim.last / 2.0}
178
+ offset = (freq[1] - freq[0])/2.0
179
+ freq = freq.collect{|v| v - freq.last / 2.0 - offset}
180
+ pha = Roctave.fftshift(pha)
181
+ end
182
+ pha = Roctave.unwrap(pha)
183
+ if degree then
184
+ ylabel = 'Phase (degree)'
185
+ pha = pha.collect{|v| v / Math::PI * 180.0}
186
+ else
187
+ ylabel = 'Phase (radians)'
188
+ end
189
+ Roctave.plot(freq, pha, title: title, xlabel: xlabel, ylabel: ylabel, xlim: xlim, grid: true)
190
+ end
191
+ if plot_d then
192
+ title = '{/:Bold Group delay}'
193
+ if normalize_freqency then
194
+ xlabel = 'Normalized frequency (x {/Symbol p} rad/sample)'
195
+ ylabel = 'Group delay (in sample time)'
196
+ if region == :half then
197
+ freq = f.collect{|v| v/fs}
198
+ xlim = [0, 1]
199
+ else
200
+ freq = f.collect{|v| 2.0*v/fs}
201
+ xlim = [0, 2]
202
+ end
203
+ else
204
+ xlabel = 'Frequency'
205
+ freq = f
206
+ xlim = [0, fs]
207
+ ylabel = 'Group delay (s)'
208
+ end
209
+ pha = h.collect{|v| v.arg}
210
+ if region == :whole and shift then
211
+ xlim = xlim.collect{|v| v - xlim.last / 2.0}
212
+ offset = (freq[1] - freq[0])/2.0
213
+ freq = freq.collect{|v| v - freq.last / 2.0 - offset}
214
+ pha = Roctave.fftshift(pha)
215
+ end
216
+ pha = Roctave.unwrap(pha)
217
+ dd = 2.0*(f.last - f.first) / (f.length - 1)
218
+ dd *= 2.0*Math::PI unless normalize_freqency
219
+ gd = Array.new(pha.length){0.0}
220
+ (1...pha.length-1).each do |i|
221
+ gd[i] = (pha[i-1] - pha[i+1]) / dd
222
+ end
223
+ gd[0] = gd[1]
224
+ gd[-1] = gd[-2]
225
+ Roctave.plot(freq, gd, title: title, xlabel: xlabel, ylabel: ylabel, xlim: xlim, grid: true)
226
+ end
227
+ end
228
+
229
+ [h, f]
230
+ end
231
+
232
+ end
233
+
@@ -0,0 +1,80 @@
1
+ ## Copyright (C) 2019 Théotime Bollengier <theotime.bollengier@gmail.com>
2
+ ##
3
+ ## This file is part of Roctave
4
+ ##
5
+ ## Roctave is free software: you can redistribute it and/or modify
6
+ ## it under the terms of the GNU General Public License as published by
7
+ ## the Free Software Foundation, either version 3 of the License, or
8
+ ## (at your option) any later version.
9
+ ##
10
+ ## Roctave is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+ ##
15
+ ## You should have received a copy of the GNU General Public License
16
+ ## along with Roctave. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+
19
+ module Roctave
20
+ class IirFilter
21
+ include Roctave::Filter
22
+
23
+ # @see Roctave.freqz
24
+ def freqz (*args)
25
+ Roctave.freqz(numerator, denominator, *args)
26
+ end
27
+
28
+ # Draw the impulse response.
29
+ # @param len [Integer] the length of the displayed response
30
+ # @see Roctave.stem
31
+ def impulse_response (len = 100, *args)
32
+ x = Array.new([1, len.to_i].max){0.0}
33
+ x[0] = 1.0
34
+ y = self.clone.filter x
35
+ Roctave.stem(y, *args)
36
+ end
37
+
38
+ # Draw the step response.
39
+ # @param len [Integer] the length of the displayed response
40
+ # @see Roctave.stem
41
+ def step_response (len = 100, *args)
42
+ x = Array.new([1, len.to_i].max){1.0}
43
+ y = self.clone.filter x
44
+ Roctave.stem(y, *args)
45
+ end
46
+
47
+ # @see Roctave.zplane
48
+ def zplane
49
+ Roctave.zplane(numerator, denominator)
50
+ end
51
+
52
+ # @return [Roctave::IirFilter]
53
+ def clone
54
+ Roctave::IirFilter.new numerator, denominator
55
+ end
56
+
57
+ # Returns a Butterworth IIR filter
58
+ # @see Roctave.butter
59
+ # @return [Roctave::IirFilter]
60
+ def self.butter (*args)
61
+ Roctave::IirFilter.new *Roctave.butter(*args)
62
+ end
63
+
64
+ # Returns a Chebyshev type I IIR filter
65
+ # @see Roctave.cheby1
66
+ # @return [Roctave::IirFilter]
67
+ def self.cheby1 (*args)
68
+ Roctave::IirFilter.new *Roctave.cheby1(*args)
69
+ end
70
+
71
+ # Returns a Chebyshev type II IIR filter
72
+ # @see Roctave.cheby2
73
+ # @return [Roctave::IirFilter]
74
+ def self.cheby2 (*args)
75
+ Roctave::IirFilter.new *Roctave.cheby2(*args)
76
+ end
77
+ end
78
+ end
79
+
80
+
@@ -0,0 +1,78 @@
1
+ ## Copyright (C) 2019 Théotime Bollengier <theotime.bollengier@gmail.com>
2
+ ##
3
+ ## This file is part of Roctave
4
+ ##
5
+ ## Roctave is free software: you can redistribute it and/or modify
6
+ ## it under the terms of the GNU General Public License as published by
7
+ ## the Free Software Foundation, either version 3 of the License, or
8
+ ## (at your option) any later version.
9
+ ##
10
+ ## Roctave is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+ ##
15
+ ## You should have received a copy of the GNU General Public License
16
+ ## along with Roctave. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+
19
+ module Roctave
20
+ ##
21
+ # Linearly interpolate between points.
22
+ # @param x [Array<Float>] X coordinates of given points
23
+ # @param y [Array] Y value of given points at X coordinates
24
+ # @param xi [Array<Float>] X coordinates of the requested interpolated points
25
+ # @return [Array] the Y value of interpolated points at xi coordinates
26
+ def self.interp1 (x, y, xi)
27
+ [x, y, xi].each.with_index do |a, i|
28
+ raise ArgumentError.new "Argument #{i+1} must be an array" unless a.kind_of?(Array)
29
+ end
30
+ raise ArgumentError.new "Argument 1 and 2 must be arrays of the same length" unless x.length == y.length
31
+
32
+ xr = x.reverse
33
+ xi.collect.with_index do |v, i|
34
+ leftmost = nil
35
+ leftmost_index = nil
36
+ x.each.with_index do |e, j|
37
+ if (e < v) or (e == v and leftmost != v) then
38
+ leftmost = e
39
+ leftmost_index = j
40
+ end
41
+ end
42
+ if leftmost.nil? then
43
+ leftmost = x.last
44
+ leftmost_index = x.length - 1
45
+ end
46
+
47
+ rightmost = nil
48
+ rightmost_index = nil
49
+ xr.each.with_index do |e, j|
50
+ if (e > v) or (e == v and rightmost != v) then
51
+ rightmost = e
52
+ rightmost_index = x.length - 1 - j
53
+ end
54
+ end
55
+ if rightmost.nil? then
56
+ rightmost = x.first
57
+ rightmost_index = 0
58
+ end
59
+
60
+ if leftmost_index > rightmost_index then
61
+ leftmost,rightmost = rightmost,leftmost
62
+ leftmost_index,rightmost_index = rightmost_index,leftmost_index
63
+ end
64
+
65
+ #puts "[#{i}] #{leftmost_index} - #{rightmost_index}"
66
+
67
+ ml = y[leftmost_index]
68
+ mr = y[rightmost_index]
69
+
70
+ if (rightmost - leftmost) == 0 then
71
+ (ml + mr) / 2.0
72
+ else
73
+ ml*(rightmost - v)/(rightmost - leftmost) + mr*(v - leftmost)/(rightmost - leftmost)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
@@ -0,0 +1,748 @@
1
+ ## Copyright (C) 2019 Théotime Bollengier <theotime.bollengier@gmail.com>
2
+ ##
3
+ ## This file is part of Roctave
4
+ ##
5
+ ## Roctave is free software: you can redistribute it and/or modify
6
+ ## it under the terms of the GNU General Public License as published by
7
+ ## the Free Software Foundation, either version 3 of the License, or
8
+ ## (at your option) any later version.
9
+ ##
10
+ ## Roctave is distributed in the hope that it will be useful,
11
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ ## GNU General Public License for more details.
14
+ ##
15
+ ## You should have received a copy of the GNU General Public License
16
+ ## along with Roctave. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+
19
+ #begin
20
+ require 'gnuplot'
21
+
22
+ module Roctave
23
+
24
+ # @!group Plotting
25
+
26
+ ##
27
+ # Used internally to parse plot() arguments
28
+ def self.parse_plot_args (args)
29
+ datasets = []
30
+ cur_dataset = nil
31
+ state = :x_or_y
32
+
33
+ args.each.with_index do |arg, arg_index|
34
+ case state
35
+ when :x_or_y
36
+ unless cur_dataset.nil? then
37
+ if cur_dataset[:y].nil? then
38
+ cur_dataset[:y] = cur_dataset[:x]
39
+ cur_dataset[:x] = (0 ... cur_dataset[:y].length).to_a
40
+ end
41
+ datasets << cur_dataset
42
+ cur_dataset = nil
43
+ end
44
+ raise ArgumentError.new "Argument #{arg_index + 1} is expected to be an array, either X or Y" unless arg.kind_of?(Enumerable)
45
+ cur_dataset = {x: arg}
46
+ state = :y_or_fmt_or_property
47
+ when :y_or_fmt_or_property
48
+ if arg.kind_of?(Enumerable) then
49
+ cur_dataset[:y] = arg
50
+ raise ArgumentError.new "Arguments #{arg_index} and #{arg_index + 1}: arrays must have the same length" unless cur_dataset[:y].length == cur_dataset[:x].length
51
+ state = :fmt_or_property_or_x
52
+ elsif arg.kind_of?(String) then
53
+ cur_dataset[:y] = cur_dataset[:x]
54
+ cur_dataset[:x] = (0 ... cur_dataset[:y].length).to_a
55
+ state = :fmt_or_property_or_x
56
+ redo
57
+ elsif arg.kind_of?(Symbol) then
58
+ cur_dataset[:y] = cur_dataset[:x]
59
+ cur_dataset[:x] = (0 ... cur_dataset[:y].length).to_a
60
+ state = :property_or_x
61
+ redo
62
+ else
63
+ raise ArgumentError.new "Argument #{arg_index + 1} is expected to be the Y array or the FMT string"
64
+ end
65
+ when :fmt_or_property_or_x
66
+ if arg.kind_of?(Enumerable) then
67
+ state = :x_or_y
68
+ redo
69
+ elsif arg.kind_of?(Symbol) then
70
+ state = :property_or_x
71
+ redo
72
+ elsif arg.kind_of?(String) then
73
+ m = arg.match(/;([^;]+);/)
74
+ if m then
75
+ cur_dataset[:legend] = m[1]
76
+ arg.sub!(/;([^;]+);/, '')
77
+ end
78
+
79
+ if arg =~ /--/ then
80
+ cur_dataset[:linetype] = :dashed; arg.sub!(/--/, '')
81
+ elsif arg =~ /-\./ then
82
+ cur_dataset[:linetype] = :dashdotted; arg.sub!(/-\./, '')
83
+ elsif arg =~ /-/ then
84
+ cur_dataset[:linetype] = :solid; arg.sub!(/-/, '')
85
+ elsif arg =~ /:/ then
86
+ cur_dataset[:linetype] = :dotted; arg.sub!(/:/, '')
87
+ end
88
+
89
+ if arg =~ /\+/ then
90
+ cur_dataset[:pointtype] = :crosshair; arg.sub!(/\+/, '')
91
+ elsif arg =~ /o/ then
92
+ cur_dataset[:pointtype] = :circle; arg.sub!(/o/, '')
93
+ elsif arg =~ /\*/ then
94
+ cur_dataset[:pointtype] = :star; arg.sub!(/\*/, '')
95
+ elsif arg =~ /\./ then
96
+ cur_dataset[:pointtype] = :point; arg.sub!(/\./, '')
97
+ elsif arg =~ /x/ then
98
+ cur_dataset[:pointtype] = :cross; arg.sub!(/x/, '')
99
+ elsif arg =~ /s/ then
100
+ cur_dataset[:pointtype] = :square; arg.sub!(/s/, '')
101
+ elsif arg =~ /d/ then
102
+ cur_dataset[:pointtype] = :diamond; arg.sub!(/d/, '')
103
+ elsif arg =~ /\^/ then
104
+ cur_dataset[:pointtype] = :upwardFacingTriangle; arg.sub!(/\^/, '')
105
+ elsif arg =~ /v/ then
106
+ cur_dataset[:pointtype] = :downwardFacingTriangle; arg.sub!(/v/, '')
107
+ elsif arg =~ />/ then
108
+ cur_dataset[:pointtype] = :rightFacingTriangle; arg.sub!(/>/, '')
109
+ elsif arg =~ /</ then
110
+ cur_dataset[:pointtype] = :leftFacingTriangle; arg.sub!(/</, '')
111
+ elsif arg =~ /p/ then
112
+ cur_dataset[:pointtype] = :pentagram; arg.sub!(/p/, '')
113
+ elsif arg =~ /h/ then
114
+ cur_dataset[:pointtype] = :hexagram; arg.sub!(/h/, '')
115
+ end
116
+
117
+ if arg =~ /r/ then
118
+ cur_dataset[:color] = :red; arg.sub!(/r/, '')
119
+ elsif arg =~ /g/ then
120
+ cur_dataset[:color] = :green; arg.sub!(/g/, '')
121
+ elsif arg =~ /b/ then
122
+ cur_dataset[:color] = :blue; arg.sub!(/b/, '')
123
+ elsif arg =~ /c/ then
124
+ cur_dataset[:color] = :cyan; arg.sub!(/c/, '')
125
+ elsif arg =~ /m/ then
126
+ cur_dataset[:color] = :magenta; arg.sub!(/m/, '')
127
+ elsif arg =~ /y/ then
128
+ cur_dataset[:color] = :yellow; arg.sub!(/y/, '')
129
+ elsif arg =~ /k/ then
130
+ cur_dataset[:color] = :black; arg.sub!(/k/, '')
131
+ elsif arg =~ /w/ then
132
+ cur_dataset[:color] = :white; arg.sub!(/w/, '')
133
+ end
134
+
135
+
136
+ raise ArgumentError.new "Argument #{arg_index + 1}: cannot parse \"#{arg}\"" unless arg.empty?
137
+
138
+ state = :property_or_x
139
+ else
140
+ raise ArgumentError.new "Argument #{arg_index + 1} is expected to be the X array or the FMT string"
141
+ end
142
+ when :property_or_x
143
+ if arg.kind_of?(Enumerable) then
144
+ state = :x_or_y
145
+ redo
146
+ elsif arg.kind_of?(Symbol) then
147
+ case arg
148
+ when :linetype
149
+ state = :prop_linetype
150
+ when :linewidth
151
+ state = :prop_linewidth
152
+ when :dashtype
153
+ state = :prop_dashtype
154
+ when :color
155
+ state = :prop_color
156
+ when :pointtype
157
+ state = :prop_pointtype
158
+ when :pointsize
159
+ state = :prop_pointsize
160
+ #when :pointcolor
161
+ # state = :prop_pointcolor
162
+ when :legend
163
+ state = :prop_legend
164
+ when :filled
165
+ cur_dataset[:empty] = false
166
+ state = :property_or_x
167
+ when :empty
168
+ cur_dataset[:empty] = true
169
+ state = :property_or_x
170
+ else
171
+ raise RuntimeError.new "Unexpected FSM state (#{state}) while parsing argument #{arg_index+1}, sorry =("
172
+ end
173
+ else
174
+ raise ArgumentError.new "Argument #{arg_index + 1} is expected to be the X array or a property symbol"
175
+ end
176
+ when :prop_linetype
177
+ case arg
178
+ when '--'
179
+ cur_dataset[:linetype] = :dashed
180
+ when :dashed
181
+ cur_dataset[:linetype] = :dashed
182
+ when '-.'
183
+ cur_dataset[:linetype] = :dashdotted
184
+ when :dashdotted
185
+ cur_dataset[:linetype] = :dashdotted
186
+ when '-'
187
+ cur_dataset[:linetype] = :solid
188
+ when :solid
189
+ cur_dataset[:linetype] = :solid
190
+ when ':'
191
+ cur_dataset[:linetype] = :dotted
192
+ when :dotted
193
+ cur_dataset[:linetype] = :dotted
194
+ when :none
195
+ cur_dataset[:linetype] = false
196
+ else
197
+ raise ArgumentError.new "Argument #{arg_index + 1} (linetype) is expected to be either '--', '-.', '-', ':', :dashed, :dashdotted, :solid, :doted"
198
+ end
199
+ state = :property_or_x
200
+ when :prop_linewidth
201
+ raise ArgumentError.new "Argument #{arg_index + 1} (linewidth) is expected to be a numeric value" unless arg.kind_of?(Numeric)
202
+ cur_dataset[:linewidth] = arg.to_f
203
+ state = :property_or_x
204
+ when :prop_dashtype
205
+ raise ArgumentError.new "Argument #{arg_index + 1} (dashtype) is expected to be a string containing only [-._ ]" if not(arg.kind_of?(String)) or arg =~ /[^-\._ ]/
206
+ cur_dataset[:dashtype] = arg
207
+ state = :property_or_x
208
+ when :prop_color
209
+ case arg
210
+ when 'r'
211
+ cur_dataset[:color] = :red
212
+ when 'g'
213
+ cur_dataset[:color] = :green
214
+ when 'b'
215
+ cur_dataset[:color] = :blue
216
+ when 'c'
217
+ cur_dataset[:color] = :cyan
218
+ when 'm'
219
+ cur_dataset[:color] = :magenta
220
+ when 'y'
221
+ cur_dataset[:color] = :yellow
222
+ when 'k'
223
+ cur_dataset[:color] = :black
224
+ when 'w'
225
+ cur_dataset[:color] = :white
226
+ when String
227
+ cur_dataset[:color] = arg.to_sym
228
+ when Symbol
229
+ cur_dataset[:color] = arg
230
+ else
231
+ raise ArgumentError.new "Argument #{arg_index + 1} (color) unexpected \"#{arg}\""
232
+ end
233
+ state = :property_or_x
234
+ when :prop_pointtype
235
+ case arg
236
+ when '+'
237
+ cur_dataset[:pointtype] = :crosshair
238
+ when :crosshair
239
+ cur_dataset[:pointtype] = :crosshair
240
+ when 'o'
241
+ cur_dataset[:pointtype] = :circle
242
+ when :circle
243
+ cur_dataset[:pointtype] = :circle
244
+ when '*'
245
+ cur_dataset[:pointtype] = :star
246
+ when :star
247
+ cur_dataset[:pointtype] = :star
248
+ when '.'
249
+ cur_dataset[:pointtype] = :point
250
+ when :point
251
+ cur_dataset[:pointtype] = :point
252
+ when 'x'
253
+ cur_dataset[:pointtype] = :cross
254
+ when :cross
255
+ cur_dataset[:pointtype] = :cross
256
+ when 's'
257
+ cur_dataset[:pointtype] = :square
258
+ when :square
259
+ cur_dataset[:pointtype] = :square
260
+ when 'd'
261
+ cur_dataset[:pointtype] = :diamond
262
+ when :diamond
263
+ cur_dataset[:pointtype] = :diamond
264
+ when '^'
265
+ cur_dataset[:pointtype] = :upwardFacingTriangle
266
+ when :upwardFacingTriangle
267
+ cur_dataset[:pointtype] = :upwardFacingTriangle
268
+ when 'v'
269
+ cur_dataset[:pointtype] = :downwardFacingTriangle
270
+ when :downwardFacingTriangle
271
+ cur_dataset[:pointtype] = :downwardFacingTriangle
272
+ when '>'
273
+ cur_dataset[:pointtype] = :rightFacingTriangle
274
+ when :rightFacingTriangle
275
+ cur_dataset[:pointtype] = :rightFacingTriangle
276
+ when '<'
277
+ cur_dataset[:pointtype] = :leftFacingTriangle
278
+ when :leftFacingTriangle
279
+ cur_dataset[:pointtype] = :leftFacingTriangle
280
+ when 'p'
281
+ cur_dataset[:pointtype] = :pentagram
282
+ when :pentagram
283
+ cur_dataset[:pointtype] = :pentagram
284
+ when 'h'
285
+ cur_dataset[:pointtype] = :hexagram
286
+ when :hexagram
287
+ cur_dataset[:pointtype] = :hexagram
288
+ when :none
289
+ cur_dataset[:pointtype] = false
290
+ else
291
+ raise ArgumentError.new "Argument #{arg_index + 1} (pointtype) unexpected \"#{arg}\""
292
+ end
293
+ state = :property_or_x
294
+ when :prop_pointsize
295
+ raise ArgumentError.new "Argument #{arg_index + 1} (pointsize) is expected to be a numeric value" unless arg.kind_of?(Numeric)
296
+ cur_dataset[:pointsize] = arg.to_f
297
+ state = :property_or_x
298
+ #when :prop_pointcolor
299
+ # case arg
300
+ # when 'k'
301
+ # cur_dataset[:pointcolor] = :black
302
+ # when 'r'
303
+ # cur_dataset[:pointcolor] = :red
304
+ # when 'g'
305
+ # cur_dataset[:pointcolor] = :green
306
+ # when 'b'
307
+ # cur_dataset[:pointcolor] = :blue
308
+ # when 'm'
309
+ # cur_dataset[:pointcolor] = :magenta
310
+ # when 'c'
311
+ # cur_dataset[:pointcolor] = :cyan
312
+ # when 'w'
313
+ # cur_dataset[:pointcolor] = :white
314
+ # when String
315
+ # cur_dataset[:pointcolor] = arg.to_sym
316
+ # when Symbol
317
+ # cur_dataset[:pointcolor] = arg
318
+ # else
319
+ # raise ArgumentError.new "Argument #{arg_index + 1} (pointcolor) unexpected \"#{arg}\""
320
+ # end
321
+ # state = :property_or_x
322
+ when :prop_legend
323
+ raise ArgumentError.new "Argument #{arg_index + 1} (legend) is expected to be a string" unless arg.kind_of?(String)
324
+ cur_dataset[:legend] = arg
325
+ state = :property_or_x
326
+ else
327
+ raise RuntimeError.new "Unexpected FSM state (#{state}) while parsing argument #{arg_index+1}, sorry =("
328
+ end
329
+ end
330
+ unless cur_dataset.nil? then
331
+ if cur_dataset[:y].nil? then
332
+ cur_dataset[:y] = cur_dataset[:x]
333
+ cur_dataset[:x] = (0 ... cur_dataset[:y].length).to_a
334
+ end
335
+ datasets << cur_dataset
336
+ end
337
+ datasets
338
+ end
339
+
340
+
341
+ # @param args [Array, String] plot(Y), plot(X, Y), plot(X, Y, FMT), plot(X1, Y1, ..., Xn, Yn). FMT: Linestyle: '-' solid, '--' dashed', ':' dotted, '-.' dash-dotted. Marker: '+' crosshair, 'o' circle, '*' star, '*' star, '.' point, 'x' cross, 's' square, 'd' diamond, '^' upward-facing triangle, 'v' downward-facing triangle, '>' right-facing triangle, '<' left-facing triangle, 'p' pentagram, 'h' hexagram. Color: 'k' blacK, 'r' Red, 'g' Green, 'b' Blue, 'm' Magenta, 'c' Cyan, 'w' White, ";displayname;"
342
+ # @param title [String] Graph title
343
+ # @param xlabel [String] X axix label
344
+ # @param ylabel [String] X axix label
345
+ # @param xlim [Array<Float, Float>, Range] Two element array of the leftmost and rightmost X axis limits
346
+ # @param ylim [Array<Float, Float>, Range] Two element array of the lower and upper Y axis limits
347
+ # @param logx [Integer] Enable log scaling of the X axis. The given number is the base. 0 if for 10
348
+ # @param logy [Integer] Enable log scaling of the Y axis. The given number is the base. 0 if for 10
349
+ # @param grid [Boolean] Display the grid
350
+ # @param terminal [String] The command you would issue to gnuplot to set the terminal, without the 'set terminal ', ex: 'png transparent enhanced'
351
+ # @param output [String] The path of an output file if the terminal allows it.
352
+ def self.plot (*args, title: nil, xlabel: nil, ylabel: nil, xlim: nil, ylim: nil, logx: nil, logy: nil, grid: true, terminal: 'wxt enhanced persist', output: nil, **opts)
353
+ datasets = Roctave.parse_plot_args(args)
354
+ return nil if datasets.empty?
355
+
356
+ Gnuplot.open do |gp|
357
+ Gnuplot::Plot.new(gp) do |plot|
358
+ if terminal then
359
+ plot.terminal terminal.to_s
360
+ plot.output output.to_s if output
361
+ end
362
+ plot.title title if title.kind_of?(String)
363
+ plot.xlabel xlabel if xlabel.kind_of?(String)
364
+ plot.ylabel ylabel if ylabel.kind_of?(String)
365
+ plot.xrange "[#{xlim.first}:#{xlim.last}]" if xlim.kind_of?(Array) and xlim.length == 2 and xlim.first.kind_of?(Numeric) and xlim.last.kind_of?(Numeric)
366
+ plot.xrange "[#{xlim.begin}:#{xlim.end}]" if xlim.kind_of?(Range) and xlim.begin.kind_of?(Numeric) and xlim.end.kind_of?(Numeric)
367
+ plot.yrange "[#{ylim.first}:#{ylim.last}]" if ylim.kind_of?(Array) and ylim.length == 2 and ylim.first.kind_of?(Numeric) and ylim.last.kind_of?(Numeric)
368
+ plot.yrange "[#{ylim.begin}:#{ylim.end}]" if ylim.kind_of?(Range) and ylim.begin.kind_of?(Numeric) and ylim.end.kind_of?(Numeric)
369
+ plot.logscale "x#{(logx.to_i > 0) ? " #{logx.to_i}" : ''}" if logx
370
+ plot.logscale "y#{(logy.to_i > 0) ? " #{logy.to_i}" : ''}" if logy
371
+
372
+ ## Matlab colors ##
373
+ plot.linetype "1 lc rgb '#0072BD'"
374
+ plot.linetype "2 lc rgb '#D95319'"
375
+ plot.linetype "3 lc rgb '#EDB120'"
376
+ plot.linetype "4 lc rgb '#7E2F8E'"
377
+ plot.linetype "5 lc rgb '#77AC30'"
378
+ plot.linetype "6 lc rgb '#4DBEEE'"
379
+ plot.linetype "7 lc rgb '#A2142F'"
380
+ plot.linetype "192 dt solid lc rgb '#df000000'"
381
+
382
+ if grid == true then
383
+ plot.grid 'ls 192'
384
+ elsif grid then
385
+ plot.grid grid.to_s
386
+ end
387
+
388
+ opts.each do |k, v|
389
+ plot.send(k.to_sym, v.to_s)
390
+ end
391
+
392
+ plot.data = datasets.collect do |ds|
393
+ Gnuplot::DataSet.new([ds[:x], ds[:y]]) { |gpds|
394
+ if ds[:legend].kind_of?(String) then
395
+ gpds.title = ds[:legend]
396
+ else
397
+ gpds.notitle
398
+ end
399
+ gpds.linecolor = "'#{ds[:color]}'" if ds[:color]
400
+ gpds.linewidth = "#{ds[:linewidth]}" if ds[:linewidth]
401
+
402
+ with = ''
403
+ if (ds[:linetype] or ds[:dashtype] or (ds[:pointtype].nil? and ds[:pointsize].nil?)) then
404
+ with += 'lines'
405
+ linetype = ''
406
+ else
407
+ linetype = nil
408
+ end
409
+ if ds[:pointtype] or ds[:pointsize] then
410
+ with += 'points'
411
+ pointtype = ''
412
+ else
413
+ pointtype = nil
414
+ end
415
+
416
+ if ds[:linetype] == :dashed then
417
+ linetype += ' dt "-"'
418
+ elsif ds[:linetype] == :dotted then
419
+ linetype += ' dt "."'
420
+ elsif ds[:linetype] == :dashdotted then
421
+ linetype += ' dt "_. "'
422
+ end
423
+
424
+ case ds[:pointtype]
425
+ when :crosshair
426
+ pointtype += ' pt 1'
427
+ when :circle
428
+ if ds[:empty] == false then
429
+ pointtype += ' pt 7' # filled
430
+ else
431
+ pointtype += ' pt 6' # emtpy
432
+ end
433
+ when :star
434
+ pointtype += ' pt 3'
435
+ when :point
436
+ if ds[:empty] then
437
+ pointtype += ' pt 6' # emtpy
438
+ else
439
+ pointtype += ' pt 7' # filled
440
+ end
441
+ when :cross
442
+ pointtype += ' pt 2'
443
+ when :square
444
+ if ds[:empty] then
445
+ pointtype += ' pt 4' # emtpy
446
+ else
447
+ pointtype += ' pt 5' # filled
448
+ end
449
+ when :diamond
450
+ if ds[:empty] then
451
+ pointtype += ' pt 12' # emtpy
452
+ else
453
+ pointtype += ' pt 13' # filled
454
+ end
455
+ when :upwardFacingTriangle
456
+ if ds[:empty] then
457
+ pointtype += ' pt 8' # emtpy
458
+ else
459
+ pointtype += ' pt 9' # filled
460
+ end
461
+ when :downwardFacingTriangle
462
+ if ds[:empty] then
463
+ pointtype += ' pt 10' # emtpy
464
+ else
465
+ pointtype += ' pt 11' # filled
466
+ end
467
+ when :rightFacingTriangle
468
+ if ds[:empty] == false then
469
+ pointtype += ' pt 9' # filled
470
+ else
471
+ pointtype += ' pt 8' # emtpy
472
+ end
473
+ when :leftFacingTriangle
474
+ if ds[:empty] == false then
475
+ pointtype += ' pt 11' # filled
476
+ else
477
+ pointtype += ' pt 10' # emtpy
478
+ end
479
+ when :pentagram
480
+ if ds[:empty] then
481
+ pointtype += ' pt 14' # emtpy
482
+ else
483
+ pointtype += ' pt 15' # filled
484
+ end
485
+ when :hexagram
486
+ if ds[:empty] == false then
487
+ pointtype += ' pt 15' # filled
488
+ else
489
+ pointtype += ' pt 14' # emtpy
490
+ end
491
+ end
492
+
493
+ unless (linetype.nil? or ds[:dashtype].nil?) then
494
+ linetype.gsub!(/ dt "[-\._ ]"/, '') if linetype =~ / dt "[-\._ ]"/
495
+ linetype += " dt \"#{ds[:dashtype]}\""
496
+ end
497
+ pointtype += " ps #{ds[:pointsize]}" if (pointtype and ds[:pointsize])
498
+
499
+ with += linetype if linetype
500
+ with += pointtype if pointtype
501
+ gpds.with = with
502
+ }
503
+ end
504
+ end
505
+ end
506
+ end
507
+
508
+
509
+ # @param args [Array, String] plot(Y), plot(X, Y), plot(X, Y, FMT), plot(X1, Y1, ..., Xn, Yn). FMT: Linestyle: '-' solid, '--' dashed', ':' dotted, '-.' dash-dotted. Marker: '+' crosshair, 'o' circle, '*' star, '*' star, '.' point, 'x' cross, 's' square, 'd' diamond, '^' upward-facing triangle, 'v' downward-facing triangle, '>' right-facing triangle, '<' left-facing triangle, 'p' pentagram, 'h' hexagram. Color: 'k' blacK, 'r' Red, 'g' Green, 'b' Blue, 'm' Magenta, 'c' Cyan, 'w' White, ";displayname;"
510
+ # @param title [String] Graph title
511
+ # @param xlabel [String] X axix label
512
+ # @param ylabel [String] X axix label
513
+ # @param xlim [Array<Float, Float>, Range] Two element array of the leftmost and rightmost X axis limits
514
+ # @param ylim [Array<Float, Float>, Range] Two element array of the lower and upper Y axis limits
515
+ # @param logx [Integer] Enable log scaling of the X axis. The given number is the base. 0 if for 10
516
+ # @param logy [Integer] Enable log scaling of the Y axis. The given number is the base. 0 if for 10
517
+ # @param grid [Boolean] Display the grid
518
+ # @param terminal [String] The command you would issue to gnuplot to set the terminal, without the 'set terminal ', ex: 'png transparent enhanced'
519
+ # @param output [String] The path of an output file if the terminal allows it.
520
+ def self.stem (*args, title: nil, xlabel: nil, ylabel: nil, xlim: nil, ylim: nil, logx: nil, logy: nil, grid: true, terminal: 'wxt enhanced persist', output: nil, **opts)
521
+ datasets = Roctave.parse_plot_args(args)
522
+ return nil if datasets.empty?
523
+
524
+ Gnuplot.open do |gp|
525
+ Gnuplot::Plot.new(gp) do |plot|
526
+ if terminal then
527
+ plot.terminal terminal.to_s
528
+ plot.output output.to_s if output
529
+ end
530
+ plot.title title if title.kind_of?(String)
531
+ plot.xlabel xlabel if xlabel.kind_of?(String)
532
+ plot.ylabel ylabel if ylabel.kind_of?(String)
533
+ plot.xrange "[#{xlim.first}:#{xlim.last}]" if xlim.kind_of?(Array) and xlim.length == 2 and xlim.first.kind_of?(Numeric) and xlim.last.kind_of?(Numeric)
534
+ plot.xrange "[#{xlim.begin}:#{xlim.end}]" if xlim.kind_of?(Range) and xlim.begin.kind_of?(Numeric) and xlim.end.kind_of?(Numeric)
535
+ plot.yrange "[#{ylim.first}:#{ylim.last}]" if ylim.kind_of?(Array) and ylim.length == 2 and ylim.first.kind_of?(Numeric) and ylim.last.kind_of?(Numeric)
536
+ plot.yrange "[#{ylim.begin}:#{ylim.end}]" if ylim.kind_of?(Range) and ylim.begin.kind_of?(Numeric) and ylim.end.kind_of?(Numeric)
537
+ plot.logscale "x#{(logx.to_i > 0) ? " #{logx.to_i}" : ''}" if logx
538
+ plot.logscale "y#{(logy.to_i > 0) ? " #{logy.to_i}" : ''}" if logy
539
+
540
+ ## Matlab colors ##
541
+ plot.linetype "1 lc rgb '#0072BD'"
542
+ plot.linetype "2 lc rgb '#D95319'"
543
+ plot.linetype "3 lc rgb '#EDB120'"
544
+ plot.linetype "4 lc rgb '#7E2F8E'"
545
+ plot.linetype "5 lc rgb '#77AC30'"
546
+ plot.linetype "6 lc rgb '#4DBEEE'"
547
+ plot.linetype "7 lc rgb '#A2142F'"
548
+ plot.linetype "192 dt solid lc rgb '#df000000'"
549
+
550
+ if grid == true then
551
+ plot.grid 'ls 192'
552
+ elsif grid then
553
+ plot.grid grid.to_s
554
+ end
555
+
556
+ opts.each do |k, v|
557
+ plot.send(k.to_sym, v.to_s)
558
+ end
559
+
560
+ linetype_counter = 1
561
+
562
+ datasets.each do |ds|
563
+ ds[:linetype] = :solid if ds[:linetype].nil?
564
+ ds[:pointtype] = :circle if ds[:pointtype].nil? or (ds[:pointtype] == false and ds[:linetype] == false)
565
+ unless ds[:linetype] == false then
566
+ plot.data << Gnuplot::DataSet.new([ds[:x], ds[:y]]) { |gpds|
567
+ if ds[:legend].kind_of?(String) and ds[:pointtype] == false then
568
+ gpds.title = ds[:legend]
569
+ else
570
+ gpds.notitle
571
+ end
572
+ gpds.notitle
573
+ gpds.linecolor = "'#{ds[:color]}'" if ds[:color]
574
+ gpds.linewidth = "#{ds[:linewidth]}" if ds[:linewidth]
575
+ with = 'impulses'
576
+ linetype = nil
577
+ if (ds[:linetype] or ds[:dashtype] or (ds[:pointtype].nil? and ds[:pointsize].nil?)) then
578
+ linetype = ''
579
+ end
580
+
581
+ with = 'impulses'
582
+ if ds[:linetype] == :dashed then
583
+ linetype += ' dt "-"'
584
+ elsif ds[:linetype] == :dotted then
585
+ linetype += ' dt "."'
586
+ elsif ds[:linetype] == :dashdotted then
587
+ linetype += ' dt "_. "'
588
+ end
589
+
590
+ unless (linetype.nil? or ds[:dashtype].nil?) then
591
+ linetype.gsub!(/ dt "[-\._ ]"/, '') if linetype =~ / dt "[-\._ ]"/
592
+ linetype += " dt \"#{ds[:dashtype]}\""
593
+ end
594
+ with += " lt #{linetype_counter}"
595
+ with += linetype if linetype
596
+ gpds.with = with
597
+ }
598
+ end
599
+ unless ds[:pointtype] == false then
600
+ plot.data << Gnuplot::DataSet.new([ds[:x], ds[:y]]) { |gpds|
601
+ if ds[:legend].kind_of?(String) then
602
+ gpds.title = ds[:legend]
603
+ else
604
+ gpds.notitle
605
+ end
606
+
607
+ gpds.linecolor = "'#{ds[:color]}'" if ds[:color]
608
+ gpds.linewidth = "#{ds[:linewidth]}" if ds[:linewidth]
609
+
610
+ with = 'points'
611
+ if ds[:pointtype] or ds[:pointsize] then
612
+ pointtype = ''
613
+ else
614
+ pointtype = nil
615
+ end
616
+
617
+ case ds[:pointtype]
618
+ when :crosshair
619
+ pointtype += ' pt 1'
620
+ when :circle
621
+ if ds[:empty] == false then
622
+ pointtype += ' pt 7' # filled
623
+ else
624
+ pointtype += ' pt 6' # emtpy
625
+ end
626
+ when :star
627
+ pointtype += ' pt 3'
628
+ when :point
629
+ if ds[:empty] then
630
+ pointtype += ' pt 6' # emtpy
631
+ else
632
+ pointtype += ' pt 7' # filled
633
+ end
634
+ when :cross
635
+ pointtype += ' pt 2'
636
+ when :square
637
+ if ds[:empty] then
638
+ pointtype += ' pt 4' # emtpy
639
+ else
640
+ pointtype += ' pt 5' # filled
641
+ end
642
+ when :diamond
643
+ if ds[:empty] then
644
+ pointtype += ' pt 12' # emtpy
645
+ else
646
+ pointtype += ' pt 13' # filled
647
+ end
648
+ when :upwardFacingTriangle
649
+ if ds[:empty] then
650
+ pointtype += ' pt 8' # emtpy
651
+ else
652
+ pointtype += ' pt 9' # filled
653
+ end
654
+ when :downwardFacingTriangle
655
+ if ds[:empty] then
656
+ pointtype += ' pt 10' # emtpy
657
+ else
658
+ pointtype += ' pt 11' # filled
659
+ end
660
+ when :rightFacingTriangle
661
+ if ds[:empty] == false then
662
+ pointtype += ' pt 9' # filled
663
+ else
664
+ pointtype += ' pt 8' # emtpy
665
+ end
666
+ when :leftFacingTriangle
667
+ if ds[:empty] == false then
668
+ pointtype += ' pt 11' # filled
669
+ else
670
+ pointtype += ' pt 10' # emtpy
671
+ end
672
+ when :pentagram
673
+ if ds[:empty] then
674
+ pointtype += ' pt 14' # emtpy
675
+ else
676
+ pointtype += ' pt 15' # filled
677
+ end
678
+ when :hexagram
679
+ if ds[:empty] == false then
680
+ pointtype += ' pt 15' # filled
681
+ else
682
+ pointtype += ' pt 14' # emtpy
683
+ end
684
+ end
685
+
686
+ pointtype += " ps #{ds[:pointsize]}" if (pointtype and ds[:pointsize])
687
+
688
+ with += " lt #{linetype_counter}"
689
+ with += pointtype if pointtype
690
+
691
+ gpds.with = with
692
+ }
693
+ end
694
+ linetype_counter += 1
695
+ end
696
+ end
697
+ end
698
+ end
699
+
700
+
701
+ ##
702
+ # Plot the poles and zeros. The arguments
703
+ # represent filter coefficients (numerator polynomial b and
704
+ # denominator polynomial a).
705
+ #
706
+ # Note that due to the nature of the roots() function, poles and
707
+ # zeros may be displayed as occurring around a circle rather than at
708
+ # a single point.
709
+ # @param b [Array<Numeric>] Filter numerator polynomial coefficients
710
+ # @param a [Array<Numeric>] Filter denominator polynomial coefficients
711
+ def self.zplane (b, a = [])
712
+ m = b.length - 1
713
+ n = a.length - 1
714
+ b = [1.0] if b.empty?
715
+ a = [1.0] if b.empty?
716
+ z = Roctave.roots(b) + Roctave.zeros(n - m)
717
+ p = Roctave.roots(a) + Roctave.zeros(m - n)
718
+
719
+ z_real = z.collect{|v| v.real}
720
+ p_real = p.collect{|v| v.real}
721
+ z_imag = z.collect{|v| v.imag}
722
+ p_imag = p.collect{|v| v.imag}
723
+ reala = z_real + p_real
724
+ imaga = z_imag + p_imag
725
+
726
+ xmin = [-1.0, reala.min].min.to_f
727
+ xmax = [ 1.0, reala.max].max.to_f
728
+ ymin = [-1.0, imaga.min].min.to_f
729
+ ymax = [ 1.0, imaga.max].max.to_f
730
+ xfluff = [0.05*(xmax-xmin), (1.05*(ymax-ymin)-(xmax-xmin))/10.0].max
731
+ yfluff = [0.05*(ymax-ymin), (1.05*(xmax-xmin)-(ymax-ymin))/10.0].max
732
+ xmin = xmin - xfluff
733
+ xmax = xmax + xfluff
734
+ ymin = ymin - yfluff
735
+ ymax = ymax + yfluff
736
+
737
+ rx = (0..100).collect{|i| Math.cos(2*Math::PI*i/100)}
738
+ ry = (0..100).collect{|i| Math.sin(2*Math::PI*i/100)}
739
+
740
+ Roctave.plot(z_real, z_imag, 'o;Zeros;', :pointsize, 1.5, p_real, p_imag, 'x;Poles;', :pointsize, 1.5, rx, ry, '-', :color, '#505050', xlabel: 'Real', ylabel: 'Imaginary', title: '{/:Bold Z-plane}', grid: true, xlim: [xmin, xmax], ylim: [ymin, ymax], size: 'ratio -1')
741
+ nil
742
+ end
743
+
744
+ end
745
+ #rescue LoadError => e
746
+ # STDERR.puts "WARNING: #{e.message}\nPloting functions will not be available."
747
+ #end
748
+