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