gnuplotrb 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,80 +1,89 @@
1
- module GnuplotRB
2
- ##
3
- # This module takes care of path to gnuplot executable and checking its version.
4
- module Settings
5
- ##
6
- # GnuplotRB can work with Gnuplot 5.0+
7
- MIN_GNUPLOT_VERSION = 5.0
8
-
9
- class << self
10
- ##
11
- # For heavy calculations max_fit_delay may be increased.
12
- attr_writer :max_fit_delay
13
- ##
14
- # Get max fit delay.
15
- #
16
- # Max fit delay (5s by default) is used inside Fit::fit function.
17
- # If it waits for output more than max_fit_delay seconds
18
- # this behaviour is considered as errorneus.
19
- # @return [Integer] seconds to wait for output
20
- def max_fit_delay
21
- @max_fit_delay ||= 5
22
- end
23
-
24
- ##
25
- # Get path that should be used to run gnuplot executable.
26
- # Default value: 'gnuplot'.
27
- # @return [String] path to gnuplot executable
28
- def gnuplot_path
29
- self.gnuplot_path = 'gnuplot' unless defined?(@gnuplot_path)
30
- @gnuplot_path
31
- end
32
-
33
- ##
34
- # Set path to gnuplot executable.
35
- # @param path [String] path to gnuplot executable
36
- # @return given path
37
- def gnuplot_path=(path)
38
- validate_version(path)
39
- opts = { stdin_data: "set term\n" }
40
- @available_terminals = Open3.capture2e(path, **opts)
41
- .first
42
- .scan(/[:\n] +([a-z][^ ]+)/)
43
- .map(&:first)
44
- @gnuplot_path = path
45
- end
46
-
47
- ##
48
- # Get list of terminals (png, html, qt, jpeg etc) available for this gnuplot.
49
- # @return [Array of String] array of terminals available for this gnuplot
50
- def available_terminals
51
- gnuplot_path
52
- @available_terminals
53
- end
54
-
55
- ##
56
- # Get gnuplot version. Uses gnuplot_path to find gnuplot executable.
57
- # @return [Numeric] gnuplot version
58
- def version
59
- gnuplot_path
60
- @version
61
- end
62
-
63
- ##
64
- # @private
65
- # Validate gnuplot version. Compares current gnuplot's
66
- # version with ::MIN_GNUPLOT_VERSION. Throws exception if version is
67
- # less than min.
68
- #
69
- # @param path [String] path to gnuplot executable
70
- def validate_version(path)
71
- @version = IO.popen("#{path} --version")
72
- .read
73
- .match(/gnuplot ([^ ]+)/)[1]
74
- .to_f
75
- message = "Your Gnuplot version is #{@version}, please update it to at least 5.0"
76
- fail(ArgumentError, message) if @version < MIN_GNUPLOT_VERSION
77
- end
78
- end
79
- end
80
- end
1
+ module GnuplotRB
2
+ ##
3
+ # This module takes care of path to gnuplot executable and checking its version.
4
+ module Settings
5
+ ##
6
+ # GnuplotRB can work with Gnuplot 5.0+
7
+ MIN_GNUPLOT_VERSION = 5.0
8
+
9
+ class << self
10
+ DEFAULT_MAX_FIT_DELAY = 5
11
+ DEFAULT_GNUPLOT_PATH = 'gnuplot'
12
+ ##
13
+ # For heavy calculations max_fit_delay may be increased.
14
+ attr_writer :max_fit_delay
15
+ ##
16
+ # Get max fit delay.
17
+ #
18
+ # Max fit delay (5s by default) is used inside Fit::fit function.
19
+ # If it waits for output more than max_fit_delay seconds
20
+ # this behaviour is considered as errorneus.
21
+ # @return [Integer] seconds to wait for output
22
+ def max_fit_delay
23
+ @max_fit_delay ||= DEFAULT_MAX_FIT_DELAY
24
+ end
25
+
26
+ ##
27
+ # Get path that should be used to run gnuplot executable.
28
+ # Default value: 'gnuplot'.
29
+ # @return [String] path to gnuplot executable
30
+ def gnuplot_path
31
+ self.gnuplot_path = DEFAULT_GNUPLOT_PATH unless defined?(@gnuplot_path)
32
+ @gnuplot_path
33
+ end
34
+
35
+ ##
36
+ # Set path to gnuplot executable.
37
+ # @param path [String] path to gnuplot executable
38
+ # @return given path
39
+ def gnuplot_path=(path)
40
+ validate_version(path)
41
+ opts = { stdin_data: "set term\n" }
42
+ @available_terminals = Open3.capture2e(path, **opts)
43
+ .first
44
+ .scan(/[:\n] +([a-z][^ ]+)/)
45
+ .map(&:first)
46
+ @gnuplot_path = path
47
+ end
48
+
49
+ ##
50
+ # Get list of terminals (png, html, qt, jpeg etc) available for this gnuplot.
51
+ # @return [Array of String] array of terminals available for this gnuplot
52
+ def available_terminals
53
+ gnuplot_path
54
+ @available_terminals
55
+ end
56
+
57
+ ##
58
+ # Get gnuplot version. Uses gnuplot_path to find gnuplot executable.
59
+ # @return [Numeric] gnuplot version
60
+ def version
61
+ gnuplot_path
62
+ @version
63
+ end
64
+
65
+ ##
66
+ # @private
67
+ # Validate gnuplot version. Compares current gnuplot's
68
+ # version with ::MIN_GNUPLOT_VERSION. Throws exception if version is
69
+ # less than min.
70
+ #
71
+ # @param path [String] path to gnuplot executable
72
+ def validate_version(path)
73
+ @version = IO.popen("#{path} --version")
74
+ .read
75
+ .match(/gnuplot ([^ ]+)/)[1]
76
+ .to_f
77
+ raise(
78
+ ArgumentError,
79
+ "Your Gnuplot version is #{@version}, please update it to at least 5.0"
80
+ ) if @version < MIN_GNUPLOT_VERSION
81
+ rescue Errno::ENOENT
82
+ raise(
83
+ ArgumentError,
84
+ "Can't find Gnuplot executable. Please make sure it's installed and added to PATH."
85
+ )
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,202 +1,202 @@
1
- module GnuplotRB
2
- ##
3
- # Terminal keeps open pipe to gnuplot process, cares about naming in-memory
4
- # datablocks (just indexing with sequential integers). All the output
5
- # to gnuplot handled by this class. Terminal also handles options passed
6
- # to gnuplot as 'set key value'.
7
- class Terminal
8
- include ErrorHandling
9
-
10
- # order is important for some options
11
- OPTION_ORDER = [:term, :output, :multiplot, :timefmt, :xrange]
12
-
13
- private_constant :OPTION_ORDER
14
-
15
- class << self
16
- ##
17
- # Close given gnuplot pipe
18
- # @param stream [IO] pipe to close
19
- def close_arg(stream)
20
- stream.puts
21
- stream.puts 'exit'
22
- Process.waitpid(stream.pid)
23
- end
24
-
25
- ##
26
- # Plot test page for given term_name into file
27
- # with file_name (optional).
28
- #
29
- # Test page contains possibilities of the term.
30
- # @param term_name [String] terminal name ('png', 'gif', 'svg' etc)
31
- # @param file_name [String] filename to output image if needed
32
- # and chosen terminal supports image output
33
- # @return nil
34
- def test(term_name, file_name = nil)
35
- Terminal.new.set(term: term_name).test(file_name)
36
- end
37
- end
38
-
39
- ##
40
- # Create new Terminal connected with gnuplot.
41
- # Uses Settings::gnuplot_path to find gnuplot
42
- # executable. Each time you create Terminal it starts new
43
- # gnuplot subprocess which is closed after GC deletes
44
- # linked Terminal object.
45
- #
46
- # @param :persist [Boolean] gnuplot's "-persist" option
47
- def initialize(persist: false)
48
- @cmd = Settings.gnuplot_path
49
- @current_datablock = 0
50
- @cmd += ' -persist' if persist
51
- @cmd += ' 2>&1'
52
- stream = IO.popen(@cmd, 'w+')
53
- handle_stderr(stream)
54
- ObjectSpace.define_finalizer(self, proc { Terminal.close_arg(stream) })
55
- @in = stream
56
- yield(self) if block_given?
57
- end
58
-
59
- ##
60
- # Output datablock to this gnuplot terminal.
61
- #
62
- # @param data [String] data stored in datablock
63
- # @example
64
- # data = "1 1\n2 4\n3 9"
65
- # Terminal.new.store_datablock(data)
66
- # #=> returns '$DATA1'
67
- # #=> outputs to gnuplot:
68
- # #=> $DATA1 << EOD
69
- # #=> 1 1
70
- # #=> 2 4
71
- # #=> 3 9
72
- # #=> EOD
73
- def store_datablock(data)
74
- name = "$DATA#{@current_datablock += 1}"
75
- stream_puts "#{name} << EOD"
76
- stream_puts data
77
- stream_puts 'EOD'
78
- name
79
- end
80
-
81
- ##
82
- # Convert given options to gnuplot format.
83
- #
84
- # For "{ opt1: val1, .. , optN: valN }" it returns
85
- # set opt1 val1
86
- # ..
87
- # set optN valN
88
- #
89
- # @param ptions [Hash] options to convert
90
- # @return [String] options in Gnuplot format
91
- def options_hash_to_string(options)
92
- result = ''
93
- options.sort_by { |key, _| OPTION_ORDER.find_index(key) || -1 }.each do |key, value|
94
- if value
95
- result += "set #{OptionHandling.option_to_string(key, value)}\n"
96
- else
97
- result += "unset #{key}\n"
98
- end
99
- end
100
- result
101
- end
102
-
103
- ##
104
- # Applie given options to current gnuplot instance.
105
- #
106
- # For "{ opt1: val1, .. , optN: valN }" it will output to gnuplot
107
- # set opt1 val1
108
- # ..
109
- # set optN valN
110
- #
111
- # @param options [Hash] options to set
112
- # @return [Terminal] self
113
- # @example
114
- # set({term: ['qt', size: [100, 100]]})
115
- # #=> outputs to gnuplot: "set term qt size 100,100\n"
116
- def set(options)
117
- OptionHandling.validate_terminal_options(options)
118
- stream_puts(options_hash_to_string(options))
119
- end
120
-
121
- ##
122
- # Unset options
123
- #
124
- # @param *options [Sequence of Symbol] each symbol considered as option key
125
- # @return [Terminal] self
126
- def unset(*options)
127
- options.flatten
128
- .sort_by { |key| OPTION_ORDER.find_index(key) || -1 }
129
- .each { |key| stream_puts "unset #{OptionHandling.string_key(key)}" }
130
- self
131
- end
132
-
133
- ##
134
- # Short way to plot Datablock, Plot or Splot object.
135
- # Other items will be just piped out to gnuplot.
136
- # @param item Object that should be outputted to Gnuplot
137
- # @return [Terminal] self
138
- def <<(item)
139
- if item.is_a? Plottable
140
- item.plot(self)
141
- else
142
- stream_print(item.to_s)
143
- end
144
- self
145
- end
146
-
147
- ##
148
- # Just put *command* + "\n" to gnuplot pipe.
149
- # @param command [String] command to send
150
- # @return [Terminal] self
151
- def stream_puts(command)
152
- stream_print("#{command}\n")
153
- end
154
-
155
- ##
156
- # Just print *command* to gnuplot pipe.
157
- # @param command [String] command to send
158
- # @return [Terminal] self
159
- def stream_print(command)
160
- check_errors
161
- @in.print(command)
162
- self
163
- end
164
-
165
- ##
166
- # @deprecated
167
- # Call replot on gnuplot. This will execute last plot once again
168
- # with rereading data.
169
- # @param options [Hash] options will be set before replotting
170
- # @return [Terminal] self
171
- def replot(**options)
172
- set(options)
173
- stream_puts('replot')
174
- unset(options.keys)
175
- sleep 0.01 until File.size?(options[:output]) if options[:output]
176
- self
177
- end
178
-
179
- ##
180
- # Send gnuplot command to turn it off and for its Process to quit.
181
- # Closes pipe so Terminal object should not be used after #close call.
182
- def close
183
- check_errors
184
- Terminal.close_arg(@in)
185
- end
186
-
187
-
188
- ##
189
- # Plot test page into file with file_name (optional).
190
- #
191
- # Test page contains possibilities of the term.
192
- # @param file_name [String] filename to output image if needed
193
- # and chosen terminal supports image output
194
- # @return nil
195
- def test(file_name = nil)
196
- set(output: file_name) if file_name
197
- stream_puts('test')
198
- unset(:output)
199
- nil
200
- end
201
- end
202
- end
1
+ module GnuplotRB
2
+ ##
3
+ # Terminal keeps open pipe to gnuplot process, cares about naming in-memory
4
+ # datablocks (just indexing with sequential integers). All the output
5
+ # to gnuplot handled by this class. Terminal also handles options passed
6
+ # to gnuplot as 'set key value'.
7
+ class Terminal
8
+ include ErrorHandling
9
+
10
+ # order is important for some options
11
+ OPTION_ORDER = [:term, :output, :multiplot, :timefmt, :xrange]
12
+
13
+ private_constant :OPTION_ORDER
14
+
15
+ class << self
16
+ ##
17
+ # Close given gnuplot pipe
18
+ # @param stream [IO] pipe to close
19
+ def close_arg(stream)
20
+ stream.puts
21
+ stream.puts 'exit'
22
+ Process.waitpid(stream.pid)
23
+ end
24
+
25
+ ##
26
+ # Plot test page for given term_name into file
27
+ # with file_name (optional).
28
+ #
29
+ # Test page contains possibilities of the term.
30
+ # @param term_name [String] terminal name ('png', 'gif', 'svg' etc)
31
+ # @param file_name [String] filename to output image if needed
32
+ # and chosen terminal supports image output
33
+ # @return nil
34
+ def test(term_name, file_name = nil)
35
+ Terminal.new.set(term: term_name).test(file_name)
36
+ end
37
+ end
38
+
39
+ ##
40
+ # Create new Terminal connected with gnuplot.
41
+ # Uses Settings::gnuplot_path to find gnuplot
42
+ # executable. Each time you create Terminal it starts new
43
+ # gnuplot subprocess which is closed after GC deletes
44
+ # linked Terminal object.
45
+ #
46
+ # @param :persist [Boolean] gnuplot's "-persist" option
47
+ def initialize(persist: false)
48
+ @cmd = Settings.gnuplot_path
49
+ @current_datablock = 0
50
+ @cmd += ' -persist' if persist
51
+ @cmd += ' 2>&1'
52
+ stream = IO.popen(@cmd, 'w+')
53
+ handle_stderr(stream)
54
+ ObjectSpace.define_finalizer(self, proc { Terminal.close_arg(stream) })
55
+ @in = stream
56
+ yield(self) if block_given?
57
+ end
58
+
59
+ ##
60
+ # Output datablock to this gnuplot terminal.
61
+ #
62
+ # @param data [String] data stored in datablock
63
+ # @example
64
+ # data = "1 1\n2 4\n3 9"
65
+ # Terminal.new.store_datablock(data)
66
+ # #=> returns '$DATA1'
67
+ # #=> outputs to gnuplot:
68
+ # #=> $DATA1 << EOD
69
+ # #=> 1 1
70
+ # #=> 2 4
71
+ # #=> 3 9
72
+ # #=> EOD
73
+ def store_datablock(data)
74
+ name = "$DATA#{@current_datablock += 1}"
75
+ stream_puts "#{name} << EOD"
76
+ stream_puts data
77
+ stream_puts 'EOD'
78
+ name
79
+ end
80
+
81
+ ##
82
+ # Convert given options to gnuplot format.
83
+ #
84
+ # For "{ opt1: val1, .. , optN: valN }" it returns
85
+ # set opt1 val1
86
+ # ..
87
+ # set optN valN
88
+ #
89
+ # @param ptions [Hash] options to convert
90
+ # @return [String] options in Gnuplot format
91
+ def options_hash_to_string(options)
92
+ result = ''
93
+ options.sort_by { |key, _| OPTION_ORDER.find_index(key) || -1 }.each do |key, value|
94
+ if value
95
+ result += "set #{OptionHandling.option_to_string(key, value)}\n"
96
+ else
97
+ result += "unset #{key}\n"
98
+ end
99
+ end
100
+ result
101
+ end
102
+
103
+ ##
104
+ # Applie given options to current gnuplot instance.
105
+ #
106
+ # For "{ opt1: val1, .. , optN: valN }" it will output to gnuplot
107
+ # set opt1 val1
108
+ # ..
109
+ # set optN valN
110
+ #
111
+ # @param options [Hash] options to set
112
+ # @return [Terminal] self
113
+ # @example
114
+ # set({term: ['qt', size: [100, 100]]})
115
+ # #=> outputs to gnuplot: "set term qt size 100,100\n"
116
+ def set(options)
117
+ OptionHandling.validate_terminal_options(options)
118
+ stream_puts(options_hash_to_string(options))
119
+ end
120
+
121
+ ##
122
+ # Unset options
123
+ #
124
+ # @param *options [Sequence of Symbol] each symbol considered as option key
125
+ # @return [Terminal] self
126
+ def unset(*options)
127
+ options.flatten
128
+ .sort_by { |key| OPTION_ORDER.find_index(key) || -1 }
129
+ .each { |key| stream_puts "unset #{OptionHandling.string_key(key)}" }
130
+ self
131
+ end
132
+
133
+ ##
134
+ # Short way to plot Datablock, Plot or Splot object.
135
+ # Other items will be just piped out to gnuplot.
136
+ # @param item Object that should be outputted to Gnuplot
137
+ # @return [Terminal] self
138
+ def <<(item)
139
+ if item.is_a? Plottable
140
+ item.plot(self)
141
+ else
142
+ stream_print(item.to_s)
143
+ end
144
+ self
145
+ end
146
+
147
+ ##
148
+ # Just put *command* + "\n" to gnuplot pipe.
149
+ # @param command [String] command to send
150
+ # @return [Terminal] self
151
+ def stream_puts(command)
152
+ stream_print("#{command}\n")
153
+ end
154
+
155
+ ##
156
+ # Just print *command* to gnuplot pipe.
157
+ # @param command [String] command to send
158
+ # @return [Terminal] self
159
+ def stream_print(command)
160
+ check_errors
161
+ @in.print(command)
162
+ self
163
+ end
164
+
165
+ ##
166
+ # @deprecated
167
+ # Call replot on gnuplot. This will execute last plot once again
168
+ # with rereading data.
169
+ # @param options [Hash] options will be set before replotting
170
+ # @return [Terminal] self
171
+ def replot(**options)
172
+ set(options)
173
+ stream_puts('replot')
174
+ unset(options.keys)
175
+ sleep 0.01 until File.size?(options[:output]) if options[:output]
176
+ self
177
+ end
178
+
179
+ ##
180
+ # Send gnuplot command to turn it off and for its Process to quit.
181
+ # Closes pipe so Terminal object should not be used after #close call.
182
+ def close
183
+ check_errors
184
+ Terminal.close_arg(@in)
185
+ end
186
+
187
+
188
+ ##
189
+ # Plot test page into file with file_name (optional).
190
+ #
191
+ # Test page contains possibilities of the term.
192
+ # @param file_name [String] filename to output image if needed
193
+ # and chosen terminal supports image output
194
+ # @return nil
195
+ def test(file_name = nil)
196
+ set(output: file_name) if file_name
197
+ stream_puts('test')
198
+ unset(:output)
199
+ nil
200
+ end
201
+ end
202
+ end