1
2
3
4
5
6
7
8 """PlotItems.py -- Objects that can be plotted by Gnuplot.
9
10 This module contains several types of PlotItems. PlotItems can be
11 plotted by passing them to a Gnuplot.Gnuplot object. You can derive
12 your own classes from the PlotItem hierarchy to customize their
13 behavior.
14
15 """
16
17 __cvs_version__ = '$Revision: 2.13 $'
18
19 import os, string, tempfile, types
20
21 try:
22 from cStringIO import StringIO
23 except ImportError:
24 from StringIO import StringIO
25
26 import Numeric
27
28 import gp, utils, Errors
29
30
32 """Used to represent unset keyword arguments."""
33
34 pass
35
36
38 """Plotitem represents an item that can be plotted by gnuplot.
39
40 For the finest control over the output, you can create 'PlotItems'
41 yourself with additional keyword options, or derive new classes
42 from 'PlotItem'.
43
44 The handling of options is complicated by the attempt to allow
45 options and their setting mechanism to be inherited conveniently.
46 Note first that there are some options that can only be set in the
47 constructor then never modified, and others that can be set in the
48 constructor and/or modified using the 'set_option()' member
49 function. The former are always processed within '__init__'. The
50 latter are always processed within 'set_option', which is called
51 by the constructor.
52
53 'set_option' is driven by a class-wide dictionary called
54 '_option_list', which is a mapping '{ <option> : <setter> }' from
55 option name to the function object used to set or change the
56 option. <setter> is a function object that takes two parameters:
57 'self' (the 'PlotItem' instance) and the new value requested for
58 the option. If <setter> is 'None', then the option is not allowed
59 to be changed after construction and an exception is raised.
60
61 Any 'PlotItem' that needs to add options can add to this
62 dictionary within its class definition. Follow one of the
63 examples in this file. Alternatively it could override the
64 'set_option' member function if it needs to do wilder things.
65
66 Members:
67
68 '_basecommand' -- a string holding the elementary argument that
69 must be passed to gnuplot's `plot' command for this item;
70 e.g., 'sin(x)' or '"filename.dat"'.
71
72 '_options' -- a dictionary of (<option>,<string>) tuples
73 corresponding to the plot options that have been set for
74 this instance of the PlotItem. <option> is the option as
75 specified by the user; <string> is the string that needs to
76 be set in the command line to set that option (or None if no
77 string is needed). Example::
78
79 {'title' : ('Data', 'title "Data"'),
80 'with' : ('linespoints', 'with linespoints')}
81
82 """
83
84
85 _option_list = {
86 'axes' : lambda self, axes: self.set_string_option(
87 'axes', axes, None, 'axes %s'),
88 'with' : lambda self, with: self.set_string_option(
89 'with', with, None, 'with %s'),
90 'title' : lambda self, title: self.set_string_option(
91 'title', title, 'notitle', 'title "%s"'),
92 }
93
94
95 _option_sequence = [
96 'binary',
97 'index', 'every', 'thru', 'using', 'smooth',
98 'axes', 'title', 'with'
99 ]
100
102 """Construct a 'PlotItem'.
103
104 Keyword options:
105
106 'with=<string>' -- choose how item will be plotted, e.g.,
107 with='points 3 3'.
108
109 'title=<string>' -- set the title to be associated with the item
110 in the plot legend.
111
112 'title=None' -- choose 'notitle' option (omit item from legend).
113
114 Note that omitting the title option is different than setting
115 'title=None'; the former chooses gnuplot's default whereas the
116 latter chooses 'notitle'.
117
118 """
119
120 self._options = {}
121 apply(self.set_option, (), keyw)
122
124 """Return the setting of an option. May be overridden."""
125
126 try:
127 return self._options[name][0]
128 except:
129 raise KeyError('option %s is not set!' % name)
130
132 """Set or change a plot option for this PlotItem.
133
134 See documentation for '__init__' for information about allowed
135 options. This function can be overridden by derived classes
136 to allow additional options, in which case those options will
137 also be allowed by '__init__' for the derived class. However,
138 it is easier to define a new '_option_list' variable for the
139 derived class.
140
141 """
142
143 for (option, value) in keyw.items():
144 try:
145 setter = self._option_list[option]
146 except KeyError:
147 raise Errors.OptionError('%s=%s' % (option,value))
148 if setter is None:
149 raise Errors.OptionError(
150 'Cannot modify %s option after construction!', option)
151 else:
152 setter(self, value)
153
155 """Set an option that takes a string value."""
156
157 if value is None:
158 self._options[option] = (value, default)
159 elif type(value) is types.StringType:
160 self._options[option] = (value, fmt % value)
161 else:
162 Errors.OptionError('%s=%s' % (option, value,))
163
165 """Clear (unset) a plot option. No error if option was not set."""
166
167 try:
168 del self._options[name]
169 except KeyError:
170 pass
171
173 raise NotImplementedError()
174
176 cmd = []
177 for opt in self._option_sequence:
178 (val,string) = self._options.get(opt, (None,None))
179 if string is not None:
180 cmd.append(string)
181 return string.join(cmd)
182
184 """Build the plot command to be sent to gnuplot.
185
186 Build and return the plot command, with options, necessary to
187 display this item. If anything else needs to be done once per
188 plot, it can be done here too.
189
190 """
191
192 return string.join([
193 self.get_base_command_string(),
194 self.get_command_option_string(),
195 ])
196
198 """Pipe necessary inline data to gnuplot.
199
200 If the plot command requires data to be put on stdin (i.e.,
201 'plot "-"'), this method should put that data there. Can be
202 overridden in derived classes.
203
204 """
205
206 pass
207
208
209 -class Func(PlotItem):
210 """Represents a mathematical expression to plot.
211
212 Func represents a mathematical expression that is to be computed by
213 gnuplot itself, as if you would type for example::
214
215 gnuplot> plot sin(x)
216
217 into gnuplot itself. The argument to the contructor is a string
218 that should be a mathematical expression. Example::
219
220 g.plot(Func('sin(x)', with='line 3'))
221
222 As shorthand, a string passed to the plot method of a Gnuplot
223 object is also treated as a Func::
224
225 g.plot('sin(x)')
226
227 """
228
232
234 return self.function
235
236
238 """A PlotItem representing a file that contains gnuplot data.
239
240 This class is not meant for users but rather as a base class for
241 other types of FileItem.
242
243 """
244
245 _option_list = PlotItem._option_list.copy()
246 _option_list.update({
247 'binary' : lambda self, binary: self.set_option_binary(binary),
248 'index' : lambda self, value: self.set_option_colonsep('index', value),
249 'every' : lambda self, value: self.set_option_colonsep('every', value),
250 'using' : lambda self, value: self.set_option_colonsep('using', value),
251 'smooth' : lambda self, smooth: self.set_string_option(
252 'smooth', smooth, None, 'smooth %s'
253 ),
254 })
255
257 """Represent a PlotItem that gnuplot treates as a file.
258
259 This class holds the information that is needed to construct
260 the plot command line, including options that are specific to
261 file-like gnuplot input.
262
263 <filename> is a string representing the filename to be passed
264 to gnuplot within quotes. It may be the name of an existing
265 file, '-' for inline data, or the name of a named pipe.
266
267 Keyword arguments:
268
269 'using=<int>' -- plot that column against line number
270
271 'using=<tuple>' -- plot using a:b:c:d etc. Elements in
272 the tuple that are None are output as the empty
273 string.
274
275 'using=<string>' -- plot `using <string>' (allows gnuplot's
276 arbitrary column arithmetic)
277
278 'every=<value>' -- plot 'every <value>'. <value> is
279 formatted as for 'using' option.
280
281 'index=<value>' -- plot 'index <value>'. <value> is
282 formatted as for 'using' option.
283
284 'binary=<boolean>' -- data in the file is in binary format
285 (this option is only allowed for grid data for splot).
286
287 'smooth=<string>' -- smooth the data. Option should be
288 'unique', 'csplines', 'acsplines', 'bezier', or
289 'sbezier'.
290
291 The keyword arguments recognized by 'PlotItem' can also be
292 used here.
293
294 Note that the 'using' option is interpreted by gnuplot, so
295 columns must be numbered starting with 1.
296
297 By default, gnuplot uses the name of the file plus any 'using'
298 option as the dataset title. If you want another title, set
299 it explicitly using the 'title' option.
300
301 """
302
303 self.filename = filename
304
305
306 apply(PlotItem.__init__, (self,), keyw)
307
309 return '\'%s\'' % (self.filename,)
310
312 if value is None:
313 self.clear_option(name)
314 elif type(value) in [types.StringType, types.IntType]:
315 self._options[name] = (value, '%s %s' % (name, value,))
316 elif type(value) is types.TupleType:
317 subopts = []
318 for subopt in value:
319 if subopt is None:
320 subopts.append('')
321 else:
322 subopts.append(str(subopt))
323 self._options[name] = (
324 value,
325 '%s %s' % (name, string.join(subopts, ':'),),
326 )
327 else:
328 raise Errors.OptionError('%s=%s' % (name, value,))
329
338
339
342 filename = tempfile.mktemp()
343
344 binary = keyw.get('binary', 0)
345 if binary:
346 f = open(filename, 'wb')
347 else:
348 f = open(filename, 'w')
349 f.write(content)
350 f.close()
351
352
353
354 if not keyw.has_key('title'):
355 keyw['title'] = None
356
357 apply(_FileItem.__init__, (self, filename,), keyw)
358
360 os.unlink(self.filename)
361
362
364 """A _FileItem that actually indicates inline data.
365
366 """
367
369
370
371 if not keyw.has_key('title'):
372 keyw['title'] = None
373
374 if keyw.get('binary', 0):
375 raise Errors.OptionError('binary inline data is not supported')
376
377 apply(_FileItem.__init__, (self, '-',), keyw)
378
379 if content[-1] == '\n':
380 self.content = content
381 else:
382 self.content = content + '\n'
383
385 f.write(self.content + 'e\n')
386
387
388 if gp.GnuplotOpts.support_fifo:
389 import threading
390
392 """Create a FIFO (named pipe), write to it, then delete it.
393
394 The writing takes place in a separate thread so that the main
395 thread is not blocked. The idea is that once the writing is
396 finished we know that gnuplot is done with the data that were in
397 the file so we can delete the file. This technique removes the
398 ambiguity about when the temporary files should be deleted.
399
400 """
401
403 self.content = content
404 self.mode = mode
405 self.filename = tempfile.mktemp()
406 threading.Thread.__init__(
407 self,
408 name=('FIFO Writer for %s' % (self.filename,)),
409 )
410 os.mkfifo(self.filename)
411 self.start()
412
414 f = open(self.filename, self.mode)
415 f.write(self.content)
416 f.close()
417 os.unlink(self.filename)
418
419
421 """A _FileItem based on a FIFO (named pipe).
422
423 This class depends on the availablity of os.mkfifo(), which only
424 exists under Unix.
425
426 """
427
429
430
431 if not keyw.has_key('title'):
432 keyw['title'] = None
433
434 apply(_FileItem.__init__, (self, '',), keyw)
435 self.content = content
436 if keyw.get('binary', 0):
437 self.mode = 'wb'
438 else:
439 self.mode = 'w'
440
442 """Create the gnuplot command for plotting this item.
443
444 The basecommand is different each time because each FIFOWriter
445 creates a new FIFO.
446
447 """
448
449
450
451 fifo = _FIFOWriter(self.content, self.mode)
452 return '\'%s\'' % (fifo.filename,)
453
454
455 -def File(filename, **keyw):
456 """Construct a _FileItem object referring to an existing file.
457
458 This is a convenience function that just returns a _FileItem that
459 wraps the filename.
460
461 <filename> is a string holding the filename of an existing file.
462 The keyword arguments are the same as those of the _FileItem
463 constructor.
464
465 """
466
467 if type(filename) is not types.StringType:
468 raise Errors.OptionError(
469 'Argument (%s) must be a filename' % (filename,)
470 )
471 return apply(_FileItem, (filename,), keyw)
472
473
474 -def Data(*set, **keyw):
475 """Create and return a _FileItem representing the data from *set.
476
477 Create a '_FileItem' object (which is a type of 'PlotItem') out of
478 one or more Float Python Numeric arrays (or objects that can be
479 converted to a Float Numeric array). If the routine is passed a
480 single with multiple dimensions, then the last index ranges over
481 the values comprising a single data point (e.g., [<x>, <y>,
482 <sigma>]) and the rest of the indices select the data point. If
483 passed a single array with 1 dimension, then each point is
484 considered to have only one value (i.e., by default the values
485 will be plotted against their indices). If the routine is passed
486 more than one array, they must have identical shapes, and then
487 each data point is composed of one point from each array. E.g.,
488 'Data(x,x**2)' is a 'PlotItem' that represents x squared as a
489 function of x. For the output format, see the comments for
490 'write_array()'.
491
492 How the data are written to gnuplot depends on the 'inline'
493 argument and preference settings for the platform in use.
494
495 Keyword arguments:
496
497 'cols=<tuple>' -- write only the specified columns from each
498 data point to the file. Since cols is used by python, the
499 columns should be numbered in the python style (starting
500 from 0), not the gnuplot style (starting from 1).
501
502 'inline=<bool>' -- transmit the data to gnuplot 'inline'
503 rather than through a temporary file. The default is the
504 value of gp.GnuplotOpts.prefer_inline_data.
505
506 The keyword arguments recognized by '_FileItem' can also be used
507 here.
508
509 """
510
511 if len(set) == 1:
512
513 set = utils.float_array(set[0])
514
515
516
517
518 if len(set.shape) == 1:
519 set = set[:,Numeric.NewAxis]
520 else:
521
522
523
524 set = utils.float_array(set)
525 dims = len(set.shape)
526
527 set = Numeric.transpose(set, (dims-1,) + tuple(range(dims-1)))
528 if keyw.has_key('cols'):
529 cols = keyw['cols']
530 del keyw['cols']
531 if type(cols) is types.IntType:
532 cols = (cols,)
533 set = Numeric.take(set, cols, -1)
534
535 if keyw.has_key('inline'):
536 inline = keyw['inline']
537 del keyw['inline']
538 else:
539 inline = gp.GnuplotOpts.prefer_inline_data
540
541
542 f = StringIO()
543 utils.write_array(f, set)
544 content = f.getvalue()
545 if inline:
546 return apply(_InlineFileItem, (content,), keyw)
547 elif gp.GnuplotOpts.prefer_fifo_data:
548 return apply(_FIFOFileItem, (content,), keyw)
549 else:
550 return apply(_TempFileItem, (content,), keyw)
551
552
554 """Return a _FileItem representing a function of two variables.
555
556 'GridData' represents a function that has been tabulated on a
557 rectangular grid. The data are written to a file; no copy is kept
558 in memory.
559
560 Arguments:
561
562 'data' -- the data to plot: a 2-d array with dimensions
563 (numx,numy).
564
565 'xvals' -- a 1-d array with dimension 'numx'
566
567 'yvals' -- a 1-d array with dimension 'numy'
568
569 'binary=<bool>' -- send data to gnuplot in binary format?
570
571 'inline=<bool>' -- send data to gnuplot "inline"?
572
573 Note the unusual argument order! The data are specified *before*
574 the x and y values. (This inconsistency was probably a mistake;
575 after all, the default xvals and yvals are not very useful.)
576
577 'data' must be a data array holding the values of a function
578 f(x,y) tabulated on a grid of points, such that 'data[i,j] ==
579 f(xvals[i], yvals[j])'. If 'xvals' and/or 'yvals' are omitted,
580 integers (starting with 0) are used for that coordinate. The data
581 are written to a temporary file; no copy of the data is kept in
582 memory.
583
584 If 'binary=0' then the data are written to a datafile as 'x y
585 f(x,y)' triplets (y changes most rapidly) that can be used by
586 gnuplot's 'splot' command. Blank lines are included each time the
587 value of x changes so that gnuplot knows to plot a surface through
588 the data.
589
590 If 'binary=1' then the data are written to a file in a binary
591 format that 'splot' can understand. Binary format is faster and
592 usually saves disk space but is not human-readable. If your
593 version of gnuplot doesn't support binary format (it is a
594 recently-added feature), this behavior can be disabled by setting
595 the configuration variable
596 'gp.GnuplotOpts.recognizes_binary_splot=0' in the appropriate
597 gp*.py file.
598
599 Thus if you have three arrays in the above format and a Gnuplot
600 instance called g, you can plot your data by typing
601 'g.splot(Gnuplot.GridData(data,xvals,yvals))'.
602
603 """
604
605
606 data = utils.float_array(data)
607 try:
608 (numx, numy) = data.shape
609 except ValueError:
610 raise Errors.DataError('data array must be two-dimensional')
611
612 if xvals is None:
613 xvals = Numeric.arange(numx)
614 else:
615 xvals = utils.float_array(xvals)
616 if xvals.shape != (numx,):
617 raise Errors.DataError(
618 'The size of xvals must be the same as the size of '
619 'the first dimension of the data array')
620
621 if yvals is None:
622 yvals = Numeric.arange(numy)
623 else:
624 yvals = utils.float_array(yvals)
625 if yvals.shape != (numy,):
626 raise Errors.DataError(
627 'The size of yvals must be the same as the size of '
628 'the second dimension of the data array')
629
630
631
632 binary = keyw.get('binary', 1) and gp.GnuplotOpts.recognizes_binary_splot
633 keyw['binary'] = binary
634
635 if inline is _unset:
636 inline = (not binary) and gp.GnuplotOpts.prefer_inline_data
637
638
639 if binary:
640 if inline:
641 raise Errors.OptionError('binary inline data not supported')
642
643
644
645
646
647
648
649
650 mout = Numeric.zeros((numy + 1, numx + 1), Numeric.Float32)
651 mout[0,0] = numx
652 mout[0,1:] = xvals.astype(Numeric.Float32)
653 mout[1:,0] = yvals.astype(Numeric.Float32)
654 try:
655
656 mout[1:,1:] = Numeric.transpose(data)
657 except:
658
659
660 mout[1:,1:] = Numeric.transpose(data.astype(Numeric.Float32))
661
662 content = mout.tostring()
663 if gp.GnuplotOpts.prefer_fifo_data:
664 return apply(_FIFOFileItem, (content,), keyw)
665 else:
666 return apply(_TempFileItem, (content,), keyw)
667 else:
668
669
670
671 set = Numeric.transpose(
672 Numeric.array(
673 (Numeric.transpose(Numeric.resize(xvals, (numy, numx))),
674 Numeric.resize(yvals, (numx, numy)),
675 data)), (1,2,0))
676
677
678
679
680 f = StringIO()
681 utils.write_array(f, set)
682 content = f.getvalue()
683
684 if inline:
685 return apply(_InlineFileItem, (content,), keyw)
686 elif gp.GnuplotOpts.prefer_fifo_data:
687 return apply(_FIFOFileItem, (content,), keyw)
688 else:
689 return apply(_TempFileItem, (content,), keyw)
690