1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 This module is intended to store generic, reusable, sub-classable plot classes
19 to minimize formulaic copying and pasting.
20 """
21
22 from __future__ import division
23
24 __author__ = "Nickolas Fotopoulos <nvf@gravity.phys.uwm.edu>"
25
26 import itertools
27 from mpl_toolkits.mplot3d import Axes3D
28 from mpl_toolkits.axes_grid import make_axes_locatable
29 from mpl_toolkits.basemap import Basemap
30
31 import numpy
32 import pylab
33 import re
34 import copy
35 import ConfigParser
36
37 from glue import iterutils
38 from glue import segments
39
40 from pylal import viz
41
42
43 pylab.rc("lines", markersize=12)
44 pylab.rc("text", usetex=True)
48 """
49 Convert a floating point number to a latex representation. In particular,
50 scientific notation is handled gracefully: e -> 10^
51 """
52 base_str = format % x
53 if "e" not in base_str:
54 return base_str
55 mantissa, exponent = base_str.split("e")
56 exponent = exponent.lstrip("0+")
57 if mantissa == "1":
58 return r"10^{%s}" % exponent
59 else:
60 return r"%s\times 10^{%s}" % (mantissa, exponent)
61
66 """
67 A very default meta-class to almost any plot you might want to make.
68 It provides basic initialization, a savefig method, and a close method.
69 It is up to developers to subclass BasicPlot and fill in the add_content()
70 and finalize() methods.
71 """
72 - def __init__(self, xlabel="", ylabel="", title="", subtitle="", **kwargs):
73 """
74 Basic plot initialization. A subclass can override __init__ and call
75 this one (plotutils.BasicPlot.__init__(self, *args, **kwargs)) and
76 then initialize variables to hold data to plot and labels.
77 """
78 self.fig = pylab.figure(**kwargs)
79 self.ax = self.fig.add_subplot(111)
80
81 self.ax.set_xlabel(xlabel)
82 self.ax.set_ylabel(ylabel)
83 if subtitle:
84 self.ax.set_title(title, x=0.5, y=1.03)
85 self.ax.text(0.5, 1.035, subtitle, horizontalalignment='center',
86 transform=self.ax.transAxes, verticalalignment='top')
87 else:
88 self.ax.set_title(title)
89 self.ax.grid(True)
90
91 - def add_content(self, data, label="_nolabel_"):
92 """
93 Stub. Replace with a method that appends values or lists of values
94 to self.data_sets and appends labels to self.data_labels. Feel free
95 to accept complicated inputs, but try to store only the raw numbers
96 that will enter the plot.
97 """
98 raise NotImplementedError
99
101 """
102 Stub. Replace with a function that creates and makes your plot
103 pretty. Do not do I/O here.
104 """
105 raise NotImplementedError
106
107 - def savefig(self, *args, **kwargs):
108 self.fig.savefig(*args, **kwargs)
109
111 """
112 Close the plot and release its memory.
113 """
114 pylab.close(self.fig)
115
117 """
118 Create a legend if there are any non-trivial labels.
119 """
120
121
122 alpha = kwargs.pop("alpha", None)
123 linewidth = kwargs.pop("linewidth", None)
124 markersize = kwargs.pop("markersize", None)
125
126
127 for plot_kwargs in self.kwarg_sets:
128 if "label" in plot_kwargs and \
129 not plot_kwargs["label"].startswith("_"):
130
131 self.ax.legend(*args, **kwargs)
132
133 leg = self.ax.get_legend()
134 frame = leg.get_frame()
135 if alpha:
136 frame.set_alpha(alpha)
137 if linewidth:
138 for l in leg.get_lines():
139 l.set_linewidth(linewidth)
140 return
141
146 """
147 An infinite iterator of some default colors.
148 """
149 return itertools.cycle(('b', 'g', 'r', 'c', 'm', 'y', 'k'))
150
152 """
153 An infinite iterator of some default symbols.
154 """
155 return itertools.cycle(('x', '^', 'D', 'H', 'o', '1', '+'))
156
158 """
159 Given a some nested sequences (e.g. list of lists), determine the largest
160 and smallest values over the data sets and determine a common binning.
161 """
162 max_stat = max(list(iterutils.flatten(data_sets)) + [-numpy.inf])
163 min_stat = min(list(iterutils.flatten(data_sets)) + [numpy.inf])
164 if numpy.isinf(-max_stat):
165 max_stat = default_max
166 if numpy.isinf(min_stat):
167 min_stat = default_min
168 return min_stat, max_stat
169
171 """
172 Decorator to make a method complain if called more than once.
173 """
174 def _new(self, *args, **kwargs):
175 attr = "_" + f.__name__ + "_already_called"
176 if hasattr(self, attr) and getattr(self, attr):
177 raise ValueError, f.__name__ + " can only be called once"
178 setattr(self, attr, True)
179 return f(self, *args, **kwargs)
180 _new.__doc__ == f.__doc__
181 _new.__name__ = f.__name__
182 return _new
183
184 _dq_params = {"text.usetex": True, "text.verticalalignment": "center",
185 "lines.linewidth": 2, "xtick.labelsize": 16,
186 "ytick.labelsize": 16, "axes.titlesize": 22,
187 "axes.labelsize": 16, "axes.linewidth": 1,
188 "grid.linewidth": 1, "legend.fontsize": 16,
189 "legend.loc": "best", "figure.figsize": [12,6],
190 "figure.dpi": 80, "image.origin": 'lower',
191 "axes.grid": True, "axes.axisbelow": False}
194 """
195 Update pylab plot parameters, defaulting to parameters for DQ-style trigger
196 plots.
197 """
198
199
200 pylab.rcParams.update(params)
201
203 """
204 Format the string columnName (e.g. xml table column) into latex format for
205 an axis label. Formats known acronyms, greek letters, units, subscripts, and
206 some miscellaneous entries.
207
208 Examples:
209
210 >>> display_name('snr')
211 'SNR'
212 >>> display_name('bank_chisq_dof')
213 'Bank $\\chi^2$ DOF'
214 >>> display_name('hoft')
215 '$h(t)$'
216
217 Arguments:
218
219 columnName : str
220 string to format
221 """
222
223
224 acro = ['snr', 'ra','dof', 'id', 'ms', 'far']
225
226 greek = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta',\
227 'theta', 'iota', 'kappa', 'lamda', 'mu', 'nu', 'xi',\
228 'pi', 'rho', 'sigma', 'tau', 'upsilon', 'phi', 'chi', 'psi',\
229 'omega']
230
231 unit = {'ns':'ns', 'hz':'Hz'}
232
233 sub = ['flow', 'fhigh', 'hrss', 'mtotal', 'mchirp']
234
235 misc = {'hoft': '$h(t)$'}
236
237 if len(columnName)==1:
238 return columnName
239
240
241 words = []
242 for w in re.split('\s', columnName):
243 if w[:-1].isupper(): words.append(w)
244 else: words.extend(re.split('_', w))
245
246
247 for i,w in enumerate(words):
248 if w.startswith('\\'):
249 pass
250 wl = w.lower()
251
252 if wl in misc.keys():
253 words[i] = misc[wl]
254
255 elif wl in acro:
256 words[i] = w.upper()
257
258 elif wl in unit:
259 words[i] = '(%s)' % unit[wl]
260
261 elif wl in sub:
262 words[i] = '%s$_{\mbox{\\small %s}}$' % (w[0], w[1:])
263
264 elif wl in greek:
265 words[i] = '$\%s$' % w
266
267 elif re.match('(%s)' % '|'.join(greek), w):
268 if w[-1].isdigit():
269 words[i] = '$\%s_{%s}$''' %tuple(re.findall(r"[a-zA-Z]+|\d+",w))
270 elif wl.endswith('sq'):
271 words[i] = '$\%s^2$' % w[:-2]
272
273 else:
274 if w[:-1].isupper():
275 words[i] = w
276 else:
277 words[i] = w.title()
278
279 words[i] = re.sub('(?<!\\\\)_', '\_', words[i])
280
281 return ' '.join(words)
282
283 -def add_colorbar(ax, mappable=None, visible=True, log=False, clim=None,\
284 label=None, **kwargs):
285 """
286 Adds a figure colorbar to the given Axes object ax, based on the values
287 found in the mappable object. If visible=True, returns the Colorbar object,
288 otherwise, no return.
289
290 Arguments:
291
292 ax : matplotlib.axes.AxesSubplot
293 axes object beside which to draw colorbar
294
295 Keyword arguments:
296
297 mappable : [ matplotlib.image.Image | matplotlib.contour.ContourSet... ]
298 image object from which to map colorbar values
299 visible : [ True | False]
300 add colorbar to figure, or simply resposition ax as if to draw one
301 log : [ True | False ]
302 use logarithmic scale for colorbar
303 clim : tuple
304 (vmin, vmax) pair for limits of colorbar
305 label : str
306 label string for colorbar
307
308 All other keyword arguments will be passed to pylab.colorbar. Logarithmic
309 colorbars can be created by plotting log10 of the data and setting log=True.
310 """
311
312 div = make_axes_locatable(ax)
313 cax = div.new_horizontal("3%", pad="1%")
314 if not visible:
315 return
316 else:
317 div._fig.add_axes(cax)
318
319
320 if pylab.rcParams['text.usetex']:
321 kwargs.setdefault('format',\
322 pylab.matplotlib.ticker.FuncFormatter(lambda x,pos: "$%s$"\
323 % float_to_latex(x)))
324
325
326 if not clim:
327 cmin = min([c.get_array().min() for c in ax.collections+ax.images])
328 cmax = max([c.get_array().max() for c in ax.collections+ax.images])
329 clim = [cmin, cmax]
330 if log:
331 kwargs.setdefault("ticks", numpy.logspace(numpy.log10(clim[0]),\
332 numpy.log10(clim[1]), num=9,\
333 endpoint=True))
334 else:
335 kwargs.setdefault("ticks", numpy.linspace(clim[0], clim[1], num=9,\
336 endpoint=True))
337
338
339 if not mappable:
340 if len(ax.collections+ax.images) == 0:
341 norm = log and pylab.matplotlib.colors.LogNorm() or none
342 mappable = ax.scatter([1], [1], c=[clim[0]], vmin=clim[0],\
343 vmax=clim[1], visible=False, norm=norm)
344 else:
345 minindex = numpy.asarray([c.get_array().min() for c in\
346 ax.collections+ax.images]).argmin()
347 mappable = (ax.collections+ax.images)[minindex]
348
349
350 if mappable.get_array() is None:
351 norm = log and pylab.matplotlib.colors.LogNorm() or none
352 mappable = ax.scatter([1], [1], c=[clim[0]], vmin=clim[0],\
353 vmax=clim[1], visible=False, norm=norm)
354
355
356 colorbar = ax.figure.colorbar(mappable, cax=cax, **kwargs)
357 if clim: colorbar.set_clim(clim)
358 if label: colorbar.set_label(label)
359 colorbar.draw_all()
360
361 return colorbar
362
364 """
365 Parser ConfigParser.ConfigParser section for plotting parameters. Returns
366 a dict that can be passed to any plotutils.plot_xxx function in **kwargs
367 form. Set ycolumn to 'hist' or 'rate' to generate those types of plots.
368
369 Arguments:
370
371 cp : ConfigParser.ConfigParser
372 INI file object from which to read
373 section : str
374 section name to read for options
375
376 Basic parseable options:
377
378 xcolumn : str
379 parameter to plot on x-axis
380 ycolumn : str
381 parameter to plot on y-axis
382 zcolumn : str
383 parameter to plot on z-axis
384 rank-by : str
385 parameter by which to rank elements
386 xlim : list
387 [xmin, xmax] pair for x-axis limits
388 ylim : list
389 [ymin, ymax] pair for y-axis limits
390 zlim : list
391 [zmin, zmax] pair for z-axis limits
392 clim : list
393 [cmin, cmax] pair for colorbar limits
394 logx : [ True | False ]
395 plot x-axis in log scale
396 logy : [ True | False ]
397 plot y-axis in log scale
398 logz : [ True | False ]
399 plot z-axis in log scale
400
401 Trigger plot options:
402
403 detchar-style : [ True | False ]
404 use S6-style plotting: low snr triggers small with no edges
405 detchar-style-theshold : float
406 z-column threshold at below which to apply detchar-style
407
408 Trigger rate plot options:
409
410 bins : str
411 semi-colon-separated list of comma-separated bins for rate plot
412
413 Histogram options:
414
415 cumulative : [ True | False ]
416 plot cumulative counts in histogram
417 rate : [ True | False ]
418 plot histogram counts as rate
419 num-bins : int
420 number of bins for histogram
421 fill : [ True | False ]
422 plot solid colour underneath histogram curve
423 color-bins : str
424 semi-colon-separated list of comma-separated bins for colorbar
425 histogram
426
427 Data plot options:
428
429 zero-indicator : [ True | False ]
430 draw vertical dashed red line at t=0
431
432 Other options:
433
434 greyscale : [ True | False ]
435 save plot in black-and-white
436 bbox-inches : 'tight'
437 save figure with tight bounding box around Axes
438 calendar-time : [ True | False ]
439 plot time axis with date and time instead of time from zero.
440 """
441 params = dict()
442
443
444 pairs = ['xlim', 'ylim', 'zlim', 'colorlim']
445 pairlist = ['bins', 'color-bins']
446 booleans = ['logx', 'logy', 'logz', 'cumulative', 'rate', 'detchar-style',\
447 'greyscale', 'zero-indicator', 'normalized', 'fill',\
448 'calendar-time', 'bar']
449 floats = ['detchar-style-threshold', 'dcthreshold']
450 ints = ['num-bins']
451
452
453 for key,val in cp.items(section, raw=True):
454 if val == None: continue
455
456 val = val.rstrip('"').strip('"')
457
458 hkey = re.sub('_', '-', key)
459 ukey = re.sub('-', '_', key)
460
461 if hkey in pairs:
462 params[ukey] = map(float, val.split(','))
463
464 elif hkey in pairlist:
465 params[ukey] = map(lambda p: map(float,p.split(',')),val.split(';'))
466
467 elif hkey in booleans:
468 params[ukey] = cp.getboolean(section, key)
469
470 elif hkey in floats:
471 params[ukey] = float(val)
472
473 elif hkey in ints:
474 params[ukey] = int(val)
475
476 else:
477 params[ukey] = str(val)
478
479 return params
480
499
501 """
502 Work out renormalisation for the time axis, makes the label more
503 appropriate. Returns unit (in seconds) and string descriptor.
504
505 Example:
506
507 >>> time_axis_unit(100)
508 (1, 'seconds')
509
510 >>> time_axis_unit(604800)
511 (86400, 'days')
512
513 Arguments:
514
515 duration : float
516 plot duration to normalise
517 """
518 if (duration) < 1000:
519 return 1,"seconds"
520 elif (duration) < 20000:
521 return 60,"minutes"
522 elif (duration) >= 20000 and (duration) < 604800:
523 return 3600,"hours"
524 elif (duration) < 8640000:
525 return 86400,"days"
526 else:
527 return 2592000,"months"
528
530 """
531 Quick utility to set better formatting for ticks on a time axis.
532 """
533 xticks = ax.get_xticks()
534 if len(xticks)>1 and xticks[1]-xticks[0]==5:
535 ax.xaxis.set_major_locator(pylab.matplotlib.ticker.MultipleLocator(base=2))
536 return
537
539 """
540 Labels first minor tick in the case that there is only a single major
541 tick label visible.
542 """
543
544 def even(x, pos):
545 if int(str(int(x*10**8))[0]) % 2:
546 return ""
547 elif pylab.rcParams["text.usetex"]:
548 return "$%s$" % float_to_latex(x)
549 else:
550 return str(int(x))
551
552
553 if x:
554 ticks = list(ax.get_xticks())
555 xlim = ax.get_xlim()
556 for i,tick in enumerate(ticks[::-1]):
557 if not xlim[0] <= tick <= xlim[1]:
558 ticks.pop(-1)
559 if len(ticks) <= 1:
560 ax.xaxis.set_minor_formatter(pylab.FuncFormatter(even))
561
562
563 if y:
564 ticks = list(ax.get_yticks())
565 ylim = ax.get_ylim()
566 for i,tick in enumerate(ticks[::-1]):
567 if not ylim[0] <= tick <= ylim[1]:
568 ticks.pop(-1)
569 if len(ticks)<=1:
570 ax.yaxis.set_minor_formatter(pylab.FuncFormatter(even))
571
572 return
573
578 """
579 Exactly what you get by calling pylab.plot(), but with the handy extras
580 of the BasicPlot class.
581 """
583 BasicPlot.__init__(self, *args, **kwargs)
584 self.x_data_sets = []
585 self.y_data_sets = []
586 self.kwarg_sets = []
587
588 - def add_content(self, x_data, y_data, **kwargs):
589 self.x_data_sets.append(x_data)
590 self.y_data_sets.append(y_data)
591 self.kwarg_sets.append(kwargs)
592
593 @method_callable_once
595
596 for x_vals, y_vals, plot_kwargs in \
597 itertools.izip(self.x_data_sets, self.y_data_sets, self.kwarg_sets):
598 self.ax.plot(x_vals, y_vals, **plot_kwargs)
599
600
601 self.add_legend_if_labels_exist(loc=loc, alpha=alpha)
602
603
604 del self.x_data_sets
605 del self.y_data_sets
606 del self.kwarg_sets
607
609 """
610 A simple vertical bar plot. Bars are centered on the x values and have
611 height equal to the y values.
612 """
614 BasicPlot.__init__(self, *args, **kwargs)
615 self.x_data_sets = []
616 self.y_data_sets = []
617 self.kwarg_sets = []
618
619 - def add_content(self, x_data, y_data, **kwargs):
620 self.x_data_sets.append(x_data)
621 self.y_data_sets.append(y_data)
622 self.kwarg_sets.append(kwargs)
623
624 @method_callable_once
625 - def finalize(self, loc=0, orientation="vertical", alpha=0.8):
626
627 for x_vals, y_vals, plot_kwargs, c in \
628 itertools.izip(self.x_data_sets, self.y_data_sets,
629 self.kwarg_sets, default_colors()):
630 plot_kwargs.setdefault("align", "center")
631 plot_kwargs.setdefault("color", c)
632
633
634
635
636 plot_kwargs.setdefault("orientation", orientation)
637 self.ax.bar(x_vals, y_vals, **plot_kwargs)
638
639
640 self.add_legend_if_labels_exist(loc=loc, alpha=alpha)
641
642
643 del self.x_data_sets
644 del self.y_data_sets
645 del self.kwarg_sets
646
648 """
649 Histogram data sets with a common binning, then make a vertical bar plot.
650 """
652 BasicPlot.__init__(self, *args, **kwargs)
653 self.data_sets = []
654 self.kwarg_sets = []
655
656 - def add_content(self, data, **kwargs):
657 self.data_sets.append(data)
658 self.kwarg_sets.append(kwargs)
659
660 @method_callable_once
661 - def finalize(self, loc=0, alpha=0.8, num_bins=20, normed=False,\
662 logx=False, logy=False):
663
664 min_stat, max_stat = determine_common_bin_limits(self.data_sets)
665 if logx:
666 bins = numpy.logspace(numpy.log10(min_stat), numpy.log10(max_stat),\
667 num_bins + 1, endpoint=True)
668 else:
669 bins = numpy.linspace(min_stat, max_stat, num_bins+1, endpoint=True)
670
671
672 if logx:
673 width = list(numpy.diff(bins))
674 else:
675 width = (1 - 0.1 * len(self.data_sets)) * (bins[1] - bins[0])
676
677
678 for i, (data_set, plot_kwargs, c) in \
679 enumerate(itertools.izip(self.data_sets, self.kwarg_sets,\
680 default_colors())):
681
682 plot_kwargs.setdefault("alpha", 0.6)
683 plot_kwargs.setdefault("width", width)
684 plot_kwargs.setdefault("color", c)
685
686
687 y, x = numpy.histogram(data_set, bins=bins, normed=normed)
688 x = x[:-1]
689
690
691 if logy:
692 y = numpy.ma.masked_where(y==0, y, copy=False)
693
694
695 plot_item = self.ax.bar(x, y, **plot_kwargs)
696
697
698 self.add_legend_if_labels_exist(loc=loc, alpha=alpha)
699
700 if logx:
701 self.ax.set_xscale("log")
702 if logy:
703 self.ax.set_yscale("log")
704
705
706 del self.data_sets
707 del self.kwarg_sets
708
710 """
711 Make a bar plot in which the width and placement of the bars are set
712 by the given bins.
713 """
715 BasicPlot.__init__(self, *args, **kwargs)
716 self.bin_sets = []
717 self.value_sets = []
718 self.kwarg_sets = []
719
720 - def add_content(self, bins, values, **kwargs):
721 if len(bins) != len(values):
722 raise ValueError, "length of bins and values do not match"
723
724 self.bin_sets.append(bins)
725 self.value_sets.append(values)
726 self.kwarg_sets.append(kwargs)
727
728 @method_callable_once
729 - def finalize(self, orientation="vertical"):
730 for bins, values, plot_kwargs in itertools.izip(self.bin_sets,
731 self.value_sets, self.kwarg_sets):
732 x_vals = bins.centres()
733
734
735 label = "_nolegend_"
736 if "label" in plot_kwargs:
737 label = plot_kwargs["label"]
738 del plot_kwargs["label"]
739
740
741 plot_kwargs.setdefault("align", "center")
742
743 if orientation == "vertical":
744 plot_kwargs.setdefault("width", bins.upper() - bins.lower())
745 patches = self.ax.bar(x_vals, values, **plot_kwargs)
746 elif orientation == "horizontal":
747 plot_kwargs.setdefault("height", bins.upper() - bins.lower())
748 patches = self.ax.barh(x_vals, values, **plot_kwargs)
749 else:
750 raise ValueError, orientation + " must be 'vertical' " \
751 "or 'horizontal'"
752
753
754 if len(patches) > 0:
755 patches[0].set_label(label)
756
757 pylab.axis('tight')
758
759
760 self.add_legend_if_labels_exist()
761
762
763 del self.bin_sets
764 del self.value_sets
765 del self.kwarg_sets
766
768 """
769 Cumulative histogram of foreground that also has a shaded region,
770 determined by the mean and standard deviation of the background
771 population coincidence statistics.
772 """
774 BasicPlot.__init__(self, *args, **kwargs)
775 self.fg_data_sets = []
776 self.fg_kwarg_sets = []
777 self.bg_data_sets = []
778 self.bg_kwargs = {}
779
780 - def add_content(self, fg_data_set, **kwargs):
781 self.fg_data_sets.append(fg_data_set)
782 self.fg_kwarg_sets.append(kwargs)
783
785 self.bg_data_sets.extend(bg_data_sets)
786 self.bg_kwargs = kwargs
787
788 @method_callable_once
789 - def finalize(self, num_bins=20, normalization=1):
790 epsilon = 1e-8
791
792
793 min_stat, max_stat = determine_common_bin_limits(\
794 self.fg_data_sets + self.bg_data_sets)
795 bins = numpy.linspace(min_stat, max_stat, num_bins + 1, endpoint=True)
796 bins_bg = numpy.append(bins, float('Inf'))
797 dx = bins[1] - bins[0]
798
799
800 for data_set, plot_kwargs in \
801 itertools.izip(self.fg_data_sets, self.fg_kwarg_sets):
802
803 y, x = numpy.histogram(data_set, bins=bins)
804 y = y[::-1].cumsum()[::-1]
805 x = x[:-1]
806
807
808 y = numpy.array(y, dtype=numpy.float32)
809 y[y <= epsilon] = epsilon
810 self.ax.plot(x + dx/2, y*normalization, **plot_kwargs)
811
812
813 if len(self.bg_data_sets) > 0:
814
815 hist_sum = numpy.zeros(len(bins), dtype=float)
816 sq_hist_sum = numpy.zeros(len(bins), dtype=float)
817 for instance in self.bg_data_sets:
818
819 y, x = numpy.histogram(instance, bins=bins_bg)
820 x = numpy.delete(x, -1)
821 y = y[::-1].cumsum()[::-1]
822 hist_sum += y
823 sq_hist_sum += y*y
824
825
826 N = len(self.bg_data_sets)
827 means = hist_sum / N
828 stds = numpy.sqrt((sq_hist_sum - hist_sum*means) / (N - 1))
829
830
831 means[means <= epsilon] = epsilon
832 self.ax.plot(x + dx/2, means*normalization, 'r+', **self.bg_kwargs)
833
834
835 if "label" in self.bg_kwargs:
836 self.bg_kwargs["label"] = r"$\mu_\mathrm{%s}$" \
837 % self.bg_kwargs["label"]
838 self.bg_kwargs.setdefault("alpha", 0.3)
839 self.bg_kwargs.setdefault("facecolor", "y")
840 upper = means + stds
841 lower = means - stds
842 lower[lower <= epsilon] = epsilon
843 tmp_x, tmp_y = viz.makesteps(bins, upper, lower)
844 self.ax.fill(tmp_x, tmp_y*normalization, **self.bg_kwargs)
845
846
847 self.ax.set_yscale("log")
848
849
850 self.ax.set_xlim((0.9 * min_stat, 1.1 * max_stat))
851 possible_ymins = [0.6]
852 if len(self.bg_data_sets) > 0:
853 possible_ymins.append(0.6 / N)
854 else:
855 possible_ymins.append(0.6 * normalization)
856 self.ax.set_ylim(min(possible_ymins))
857
858
859 self.kwarg_sets = self.fg_kwarg_sets
860 self.add_legend_if_labels_exist()
861
862
863 del self.kwarg_sets
864 del self.fg_data_sets
865 del self.fg_kwarg_sets
866 del self.bg_data_sets
867 del self.bg_kwargs
868
871 """
872 The equivalent of pylab.imshow(), but with the BasicPlot niceties and
873 some defaults that are more in tune with what a scientist wants --
874 origin="lower", requiring x and y bins so that we can label axes
875 correctly, and a colorbar.
876 """
878 colorlabel = kwargs.pop("colorlabel", None)
879 BasicPlot.__init__(self, *args, **kwargs)
880 self.image_sets = []
881 self.x_bins_sets = []
882 self.y_bins_sets = []
883 self.kwarg_sets = []
884 self.colorlabel = colorlabel
885
886 - def add_content(self, image, x_bins, y_bins, **kwargs):
887 """
888 Add a given image to this plot.
889
890 @param image: two-dimensional numpy array to plot
891 @param x_bins: pylal.rate.Bins instance describing the x binning
892 @param y_bins: pylal.rate.Bins instance describing the y binning
893 """
894 if image.ndim != 2:
895 raise ValueError, "require 2-D array"
896 self.image_sets.append(image)
897 self.x_bins_sets.append(x_bins)
898 self.y_bins_sets.append(y_bins)
899 self.kwarg_sets.append(kwargs)
900
901 @method_callable_once
902 - def finalize(self, colorbar=True, logcolor=False, minorticks=False,\
903 clim=None):
904 from lal import rate
905
906 logx = False
907 logy = False
908
909 for image,x_bins,y_bins,plot_kwargs in\
910 itertools.izip(self.image_sets, self.x_bins_sets, self.y_bins_sets,\
911 self.kwarg_sets):
912 extent = [x_bins.lower()[0], x_bins.upper()[-1],
913 y_bins.lower()[0], y_bins.upper()[-1]]
914 plot_kwargs.setdefault("origin", "lower")
915 plot_kwargs.setdefault("interpolation", "nearest")
916 if logcolor and clim:
917 plot_kwargs.setdefault(\
918 "norm", pylab.matplotlib.colors.LogNorm(vmin=clim[0],\
919 vmax=clim[1]))
920 elif logcolor:
921 plot_kwargs.setdefault("norm",\
922 pylab.matplotlib.colors.LogNorm())
923 elif clim:
924 plot_kwargs.setdefault(\
925 "norm", pylab.matplotlib.colors.Normalize(vmin=clim[0],\
926 vmax=clim[1]))
927 if colorbar:
928 plot_kwargs.setdefault("cmap", \
929 pylab.matplotlib.colors.LinearSegmentedColormap("clrs",\
930 pylab.matplotlib.cm.jet._segmentdata))
931
932 im = self.ax.imshow(image, extent=extent, **plot_kwargs)
933
934
935
936 if isinstance(x_bins, rate.LogarithmicBins):
937 logx = True
938 if logx and not isinstance(x_bins, rate.LogarithmicBins):
939 raise ValueError("Cannot process both linear and logarithmic "+\
940 "Images on the same Axis.")
941 if isinstance(y_bins, rate.LogarithmicBins):
942 logy = True
943 if logy and not isinstance(y_bins, rate.LogarithmicBins):
944 raise ValueError("Cannot process both linear and logarithmic "+\
945 "Images on the same Axis.")
946
947 pylab.axis('tight')
948
949 if colorbar:
950 add_colorbar(self.ax, log=logcolor, clim=clim,\
951 label=self.colorlabel)
952
953 if logx:
954 xticks, xlabels, xminorticks = log_transform(self.ax.get_xlim())
955 self.ax.set_xticks(xticks)
956 self.ax.set_xticklabels(xlabels)
957 if minorticks: self.ax.set_xticks(xminorticks, minor=True)
958 if logy:
959 yticks, ylabels, yminorticks = log_transform(self.ax.get_ylim())
960 self.ax.set_yticks(yticks)
961 self.ax.set_yticklabels(ylabels)
962 if minorticks: self.ax.set_yticks(yminorticks, minor=True)
963
965 """
966 Given a list of vertices (passed by x-coords and y-coords), fill the
967 regions (by default with successively darker gray shades).
968 """
970 BasicPlot.__init__(self, *args, **kwargs)
971 self.x_coord_sets = []
972 self.y_coord_sets = []
973 self.kwarg_sets = []
974 self.shades = []
975
976 - def add_content(self, x_coords, y_coords, shade=None, **kwargs):
977 if len(x_coords) != len(y_coords):
978 raise ValueError, "x and y coords have different length"
979 if iterutils.any(s is None for s in self.shades) and shade is not None \
980 or iterutils.any(s is not None for s in self.shades) and shade is None:
981 raise ValueError, "cannot mix explicit and automatic shading"
982
983 self.x_coord_sets.append(x_coords)
984 self.y_coord_sets.append(y_coords)
985 self.kwarg_sets.append(kwargs)
986 self.shades.append(shade)
987
988 @method_callable_once
990
991 if iterutils.any(s is None for s in self.shades):
992 n = len(self.shades)
993 grays = numpy.linspace(0, 1, n, endpoint=False)[::-1]
994 self.shades = numpy.vstack((grays, grays, grays)).T
995
996
997 for x, y, s, plot_kwargs in zip(self.x_coord_sets, self.y_coord_sets,
998 self.shades, self.kwarg_sets):
999 self.ax.fill(x, y, facecolor=s, **plot_kwargs)
1000
1001
1002 self.add_legend_if_labels_exist(loc=0)
1003
1005 """
1006 Given a time- or frequency-series, plot it across six horizontal axes,
1007 stacked on top of one another. This is good for representing a
1008 high-resolution one-dimensional data set. To support missing data,
1009 we require x and y coordinates for each data set.
1010 """
1011 - def __init__(self, xlabel="", ylabel="", title=""):
1012 self.fig = pylab.figure(figsize=(5.54, 7.5))
1013 self.title = title
1014
1015
1016 self.xlabel = xlabel
1017 self.ylabel = ylabel
1018
1019 self.x_coord_sets = []
1020 self.y_coord_sets = []
1021 self.kwarg_sets = []
1022
1023 - def add_content(self, x_coords, y_coords, **kwargs):
1024 if len(x_coords) != len(y_coords):
1025 raise ValueError, "x and y coords have different length"
1026 if len(self.kwarg_sets) and \
1027 (iterutils.any("color" in kw for kw in self.kwarg_sets)
1028 ^ ("color" in kwargs)):
1029 raise ValueError, "cannot mix explicit and automatic coloring"
1030
1031 self.x_coord_sets.append(x_coords)
1032 self.y_coord_sets.append(y_coords)
1033 self.kwarg_sets.append(kwargs)
1034
1035 @method_callable_once
1037 for kw, c in zip(self.kwarg_sets, default_colors()):
1038 kw.setdefault("color", c)
1039
1040 min_x, max_x = determine_common_bin_limits(self.x_coord_sets)
1041
1042 numaxes = 6
1043 ticks_per_axis = 6
1044 fperaxis = (max_x - min_x) / numaxes
1045 freq_boundaries = numpy.linspace(min_x, max_x, numaxes + 1)
1046 freq_ranges = zip(freq_boundaries[:-1], freq_boundaries[1:])
1047
1048
1049
1050 tickspacing = int(numpy.ceil(fperaxis / ticks_per_axis / 10)) * 10
1051 if abs(fperaxis / tickspacing - ticks_per_axis) > 2:
1052 tickspacing = int(numpy.ceil(fperaxis / ticks_per_axis))
1053
1054
1055 for j, freq_range in enumerate(freq_ranges):
1056
1057 ax = self.fig.add_axes([.12, .84-.155*j, .86, 0.124])
1058
1059 for x_coords, y_coords, kwarg_set in zip(self.x_coord_sets,
1060 self.y_coord_sets, self.kwarg_sets):
1061
1062 ind = (x_coords >= freq_range[0]) & (x_coords < freq_range[1])
1063 x = x_coords[ind]
1064 y = y_coords[ind]
1065
1066
1067 ax.plot(x, y, **kwarg_set)
1068
1069
1070 ax.set_xlim(freq_range)
1071
1072 mintick = int(numpy.ceil(freq_range[0] / tickspacing)) \
1073 * tickspacing
1074 maxtick = int(numpy.floor(freq_range[1] / tickspacing)) \
1075 * tickspacing
1076 ax.set_xticks(xrange(mintick, maxtick+1, tickspacing))
1077
1078 ax.set_ylabel(self.ylabel)
1079 ax.set_yscale(yscale)
1080 ax.grid(True)
1081
1082
1083 ax.set_xlabel(self.xlabel)
1084
1085
1086 self.fig.axes[0].set_title(self.title)
1087
1088
1089 y_lims = [ax.get_ylim() for ax in self.fig.axes]
1090 new_y_lims = determine_common_bin_limits(y_lims)
1091 for ax in self.fig.axes:
1092 ax.set_ylim(new_y_lims)
1093
1095 """
1096 Plot the receiver operating characteristic (ROC) based on the foreground
1097 and background values from given techniques. For example, to compare
1098 SNR vs IFAR, do something like:
1099
1100 plot = ROCPlot("FAP", "EFF", "ROC IFAR vs SNR")
1101 plot.add_content(ifar_bg, ifar_fg, label="IFAR")
1102 plot.add_content(snr_bg, snr_fg, label="SNR")
1103 plot.finalize()
1104 """
1106 BasicPlot.__init__(self, *args, **kwargs)
1107 self.bg_sets = []
1108 self.fg_sets = []
1109 self.eff_weight_sets = []
1110 self.kwarg_sets = []
1111
1112 - def add_content(self, bg, fg, eff_weight=None, **kwargs):
1113 """
1114 Enter a particular technique's background, foreground, and efficiency
1115 weights. These should be one-dimensional arrays with the values of
1116 of your statistics for backgrounds and foregrounds. Eff_weight
1117 are weights on the efficiency, useful if, say, you have a different
1118 prior than your injection set reflects.
1119 """
1120 if eff_weight is not None and len(fg) != len(eff_weight):
1121 raise ValueError, "efficiency weights and foreground values "\
1122 "must be the same length"
1123 self.bg_sets.append(bg)
1124 self.fg_sets.append(fg)
1125 self.eff_weight_sets.append(eff_weight)
1126 self.kwarg_sets.append(kwargs)
1127
1128 @method_callable_once
1130 for bg, fg, weights, kwargs in itertools.izip(self.bg_sets,
1131 self.fg_sets, self.eff_weight_sets, self.kwarg_sets):
1132
1133 bg = numpy.array(bg)
1134 bg.sort()
1135 fg = numpy.array(fg)
1136 if weights is not None:
1137 weights = numpy.array(weights)[fg.argsort()]
1138 fg.sort()
1139
1140
1141 FAP = 1 - numpy.arange(len(bg), dtype=float) / len(bg)
1142 if weights is not None:
1143 EFF = weights[::-1].cumsum()[::-1]
1144 else:
1145 EFF = 1 - numpy.arange(len(fg), dtype=float) / len(fg)
1146
1147
1148
1149 EFF_ind = numpy.array([fg.searchsorted(x) for x in bg])
1150 fg_too_loud = EFF_ind == len(fg)
1151 EFF_ind[fg_too_loud] = 0
1152 EFF_by_FAP = EFF[EFF_ind]
1153 EFF_by_FAP[fg_too_loud] = 0.
1154
1155
1156 self.ax.plot(FAP, EFF_by_FAP, **kwargs)
1157
1158
1159 self.ax.grid(True)
1160 self.ax.set_xlim((0, 1))
1161 self.ax.set_ylim((0, 1))
1162
1163
1164 fig_side = min(self.fig.get_size_inches())
1165 self.fig.set_size_inches(fig_side, fig_side)
1166
1167
1168 self.add_legend_if_labels_exist(loc=loc)
1169
1170
1171 del self.fg_sets
1172 del self.bg_sets
1173 del self.eff_weight_sets
1174 del self.kwarg_sets
1175
1177 """
1178 Plot the global rank versus the self rank, like a Q-Q plot, i.e.
1179 differences between the probability distribution of a
1180 statistical population to a test population.
1181 """
1183 BasicPlot.__init__(self, *args, **kwargs)
1184 self.fg_data = []
1185 self.bg_data = []
1186 self.fg_kwargs = []
1187 self.bg_kwargs = []
1188 self.kwargs = None
1189
1190 - def add_content(self, *args):
1191 raise NotImplementedError, "A function 'add_content' is not "\
1192 "implemented for QQPlot. "\
1193 "Please use the functions 'add_fg' and 'add_bg'."
1194
1196 """
1197 Calculates the sorted quantiles to be plotted.
1198 """
1199 from scipy import stats
1200
1201 N_FG = len(fg)
1202 N_BG = len(bg)
1203 N_tot = N_BG + N_FG
1204
1205
1206 bg_self_quantile = stats.rankdata(bg).astype(float) / N_BG
1207 fg_self_quantile = stats.rankdata(fg).astype(float) / N_FG
1208
1209 popAB = numpy.concatenate((fg, bg))
1210 popAB_ranked = stats.rankdata(popAB).astype(float)
1211 fg_global_quantile = popAB_ranked[:N_FG] / N_tot
1212 bg_global_quantile = popAB_ranked[N_FG:] / N_tot
1213
1214 fg_self_quantile.sort()
1215 bg_self_quantile.sort()
1216 fg_global_quantile.sort()
1217 bg_global_quantile.sort()
1218
1219 return fg_self_quantile, bg_self_quantile, fg_global_quantile, bg_global_quantile
1220
1221 - def add_fg(self, data, **kwargs):
1222 """
1223 Add the foreground data and the kwargs to a list for the
1224 """
1225 self.fg_data.append(data)
1226 self.fg_kwargs.append(kwargs)
1227
1228 - def add_bg(self, data, **kwargs):
1229 """
1230 Add the foreground data and the kwargs to a list for the
1231 """
1232 self.bg_data.append(data)
1233 self.bg_kwargs.append(kwargs)
1234
1235 @method_callable_once
1237
1238 if len(self.fg_data)!=len(self.bg_data):
1239 raise ValueError, "You have to add the same number of foreground data"\
1240 " as background data. But you have specified %d sets of foreground data but %d"\
1241 " sets of background data. " % ( len(self.fg_data), len(self.bg_data))
1242
1243 for fg, bg, fg_kwargs, bg_kwargs in itertools.izip(self.fg_data, self.bg_data, \
1244 self.fg_kwargs, self.bg_kwargs):
1245
1246 fg_self, bg_self, fg_global, bg_global = \
1247 self._compute_quantiles(fg, bg)
1248
1249 self.ax.plot(bg_self, bg_global, **bg_kwargs)
1250 self.ax.plot(fg_self, fg_global, **fg_kwargs)
1251
1252
1253 self.ax.grid(True)
1254
1255
1256 self.ax.legend(loc=4)
1257
1258
1259 del self.fg_data
1260 del self.bg_data
1261 del self.fg_kwargs
1262 del self.bg_kwargs
1263 del self.kwargs
1264
1266 """
1267 Class to create a clickable map html page.
1268 """
1270 BasicPlot.__init__(self, *args, **kwargs)
1271 self.x_data_sets = []
1272 self.y_data_sets = []
1273 self.link_list = []
1274 self.kwarg_sets = []
1275
1276 self.click_size = 5
1277
1278 self.click_x = []
1279 self.click_y = []
1280 self.click_link = []
1281
1283 """
1284 Sets the size of the area around a point that can be clicked at.
1285 """
1286 self.click_size = size
1287
1288 - def add_content(self, x_data, y_data, link, **kwargs):
1289 self.x_data_sets.append(x_data)
1290 self.y_data_sets.append(y_data)
1291 self.link_list.append(link)
1292 self.kwarg_sets.append(kwargs)
1293
1294
1295 @method_callable_once
1297
1298
1299 for x_vals, y_vals, plot_kwargs in \
1300 itertools.izip(self.x_data_sets, self.y_data_sets, self.kwarg_sets):
1301 self.ax.plot(x_vals, y_vals, **plot_kwargs)
1302
1303
1304 self.add_legend_if_labels_exist(loc=loc)
1305
1306
1308 """
1309 Calculate the rescaling to map the point coordinates
1310 to the actual coordinates on the image.
1311 """
1312
1313
1314 if dpi is None:
1315 dpi = pylab.rcParams['savefig.dpi']
1316 self.ax.get_figure().set_dpi(dpi)
1317
1318
1319 _, _, width, height = self.fig.bbox.extents
1320
1321
1322 self.click_x = []
1323 self.click_y = []
1324 self.click_link = []
1325 for xvec, yvec, link in \
1326 itertools.izip(self.x_data_sets, self.y_data_sets, self.link_list):
1327
1328 if link is None:
1329 continue
1330
1331 for point in zip(xvec, yvec):
1332
1333
1334 pixel = self.ax.transData.transform(point)
1335
1336
1337
1338 self.click_x.append(pixel[0])
1339 self.click_y.append(height - pixel[1])
1340 self.click_link.append(link)
1341
1342
1344 """
1345 Create the html file with the name of the plot
1346 as the only additional input information needed.
1347 If the actual plot is saved with a different
1348 dpi than the standard one, it must be specified here!!
1349 """
1350
1351
1352 self.rescale(dpi)
1353
1354
1355 _, _, width, height = self.fig.bbox.extents
1356
1357
1358 page = ''
1359 page += '<IMG src="%s" width=%dpx '\
1360 'usemap="#map">' % ( plotname, width)
1361 page += '<MAP name="map"> <P>'
1362 n=0
1363 for px, py, link in zip( self.click_x, self.click_y, self.click_link):
1364 n+=1
1365 page += '<area href="%s" shape="circle" '\
1366 'coords="%d, %d, 5"> Point%d</a>' %\
1367 ( link, px, py, n)
1368 page += '</P></MAP></OBJECT><br>'
1369 page += "<hr/>"
1370
1371
1372
1373 return page
1374
1375
1376
1377 plot3D_head = """
1378 <style type="text/css">
1379 .rotationViewer {
1380 position:relative;
1381 width:640px;
1382 height:378px;
1383 border-style:solid;
1384 border-width:1px;
1385 margin:auto;
1386 margin-bottom:10px;
1387 cursor:pointer;
1388 }
1389 .rotationViewer img {
1390 position:absolute;
1391 visibility:hidden;
1392 left:0;
1393 top:0;
1394 width:100%;
1395 height:100%;
1396 }
1397 </style>
1398 """
1399
1400 plot3D_body = """
1401 <script type="text/javascript" src="PATHTOUIZE/Uize.js"></script>
1402
1403 <div class="main">
1404 <div id="page_rotationViewer" class="rotationViewer insetBorderColor"></div>
1405 </div>
1406
1407 <!-- JavaScript code to make the static bar HTML "come alive" -->
1408 <!-- Based on http://www.uize.com/examples/3d-rotation-viewer.html -->
1409 <script type="text/javascript">
1410
1411 Uize.module ({
1412 required:[
1413 'UizeDotCom.Page.Example.library',
1414 'UizeDotCom.Page.Example',
1415 'Uize.Widget.Drag',
1416 'Uize.Fade.xFactory',
1417 'Uize.Curve.Rubber',
1418 'Uize.Curve.Mod'
1419 ],
1420 builder:function () {
1421 /*** create the example page widget ***/
1422 var page = window.page = Uize.Widget.Page ({evaluator:function (code) {eval (code)}});
1423
1424 /*** configuration variables ***/
1425 var
1426 totalFrames = TOTALFRAMES,
1427 frameUrlTemplate = 'URLTEMPLATE'
1428 ;
1429
1430 /*** state variables ***/
1431 var
1432 rotation = 0,
1433 lastFrameNo = -1,
1434 dragStartRotation
1435 ;
1436
1437 /*** create the Uize.Widget.Drag instance ***/
1438 var rotationViewer = page.addChild (
1439 'rotationViewer',
1440 Uize.Widget.Drag,
1441 {
1442 cancelFade:{duration:5000,curve:Uize.Curve.Rubber.easeOutBounce ()},
1443 releaseTravel:function (speed) {
1444 var
1445 deceleration = 5000, // measured in pixels/s/s
1446 duration = speed / deceleration
1447 ;
1448 return {
1449 duration:duration,
1450 distance:Math.round (speed * duration / 2),
1451 curve:function (_value) {return 1 - (_value = 1 - _value) * _value}
1452 };
1453 },
1454 html:function (input) {
1455 var
1456 htmlChunks = [],
1457 frameNodeIdPrefix = input.idPrefix + '-frame'
1458 ;
1459 for (var frameNo = 0; ++frameNo <= totalFrames;) {
1460 htmlChunks.push (
1461 '<img' +
1462 ' id="' + frameNodeIdPrefix + frameNo + '"' +
1463 ' src="' + Uize.substituteInto (frameUrlTemplate,{frame:(frameNo < 10 ? '0' : '') + frameNo}) +'"' +
1464 '/>'
1465 );
1466 }
1467 return htmlChunks.join ('');
1468 },
1469 built:false
1470 }
1471 );
1472
1473 /*** wire up the drag widget with events for updating rotation degree ***/
1474 function updateRotation (newRotation) {
1475 rotation = ((newRotation % 360) + 360) % 360;
1476 var frameNo = 1 + Math.round (rotation / 360 * (totalFrames - 1));
1477 if (frameNo != lastFrameNo) {
1478 rotationViewer.showNode ('frame'+ lastFrameNo,false);
1479 rotationViewer.showNode ('frame'+ (lastFrameNo = frameNo));
1480 }
1481 }
1482 rotationViewer.wire ({
1483 'Drag Start':function () {dragStartRotation = rotation},
1484 'Drag Update':function (e) {updateRotation (dragStartRotation - e.source.eventDeltaPos [0] / 2.5)}
1485 });
1486
1487 /*** function for animating spin ***/
1488 function spin (degrees,duration,curve) {
1489 Uize.Fade.fade (updateRotation,rotation,rotation + degrees,duration,{quantization:1,curve:curve});
1490 }
1491
1492 /*** initialization ***/
1493 Uize.Node.wire (window,'load',function () {spin (360,2700,Uize.Curve.easeInOutPow (4))});
1494
1495 /*** wire up the page widget ***/
1496 page.wireUi ();
1497 }
1498 });
1499
1500 </script>
1501 """
1502
1503
1504 -class Plot3D(BasicPlot):
1505 """
1506 Exactly what you get by calling pylab.plot(), but with the handy extras
1507 of the BasicPlot class.
1508 """
1510
1511
1512 BasicPlot.__init__(self)
1513 self.x_data_sets = []
1514 self.y_data_sets = []
1515 self.z_data_sets = []
1516 self.kwarg_sets = []
1517
1518
1519 self.ax = Axes3D(self.fig)
1520
1521
1522 self.ax.set_xlabel(args[0])
1523 self.ax.set_ylabel(args[1])
1524 self.ax.set_zlabel(args[2])
1525 self.ax.set_title(args[3])
1526
1527 self.ax.grid(True)
1528
1529 - def add_content(self, x_data, y_data, z_data, **kwargs):
1530 self.x_data_sets.append(x_data)
1531 self.y_data_sets.append(y_data)
1532 self.z_data_sets.append(z_data)
1533 self.kwarg_sets.append(kwargs)
1534
1535
1536 @method_callable_once
1537 - def finalize(self, plot_basename, n=50, dpi=50, loc=0, plot_dir = '.',\
1538 js_dir = 'https://ldas-jobs.phys.uwm.edu/~dietz/js'):
1539
1540
1541 if n>100:
1542 raise ValueError("The 3D code cannot handle more than 100 frames currently.")
1543
1544 fig = pylab.figure()
1545 ax = Axes3D(fig)
1546
1547
1548 for x_vals, y_vals, z_vals, plot_kwargs in \
1549 itertools.izip(self.x_data_sets, self.y_data_sets, \
1550 self.z_data_sets, self.kwarg_sets):
1551 self.ax.scatter(x_vals, y_vals, z_vals, **plot_kwargs)
1552
1553
1554 self.add_legend_if_labels_exist(loc=loc)
1555
1556
1557 dphi = 360.0/float(n)
1558 for i, angle in enumerate(numpy.arange(0.0, 360.0, dphi)):
1559
1560
1561 self.ax.view_init(30.0, angle)
1562
1563
1564 picname = '%s/%s-%02d'%(plot_dir, plot_basename, i)
1565 self.savefig(picname+'.png',dpi=dpi)
1566
1567
1568
1569 del self.x_data_sets
1570 del self.y_data_sets
1571 del self.z_data_sets
1572 del self.kwarg_sets
1573
1574
1575
1576 plot_template = '%s/%s-[#frame].png'%(plot_dir, plot_basename)
1577 body = plot3D_body.replace('TOTALFRAMES',str(n))
1578 body = body.replace('URLTEMPLATE', plot_template)
1579 body = body.replace('PATHTOUIZE', js_dir)
1580
1581
1582 return plot3D_head, body
1583
1585 """
1586 Exactly what you get from calling pylab.scatter(), but with the handy extras
1587 of the BasicPlot class.
1588 """
1589
1590 @method_callable_once
1592
1593 for x_vals, y_vals, plot_kwargs, color in \
1594 itertools.izip(self.x_data_sets, self.y_data_sets, self.kwarg_sets,\
1595 default_colors()):
1596 plot_kwargs.setdefault("c", color)
1597 if (len(x_vals) and
1598 (isinstance(y_vals, numpy.ma.MaskedArray) and y_vals.count() or
1599 True)):
1600 self.ax.scatter(x_vals, y_vals, **plot_kwargs)
1601 else:
1602 plot_kwargs["visible"] = False
1603 self.ax.scatter([1], [1], **plot_kwargs)
1604
1605
1606 self.add_legend_if_labels_exist(loc=loc, alpha=0.8)
1607
1608
1609 del self.x_data_sets
1610 del self.y_data_sets
1611 del self.kwarg_sets
1612
1614 """
1615 Exactly what you get from calling pylab.scatter() when you want a colorbar,
1616 but with the handy extras of the BasicPlot class.
1617 """
1618
1619 - def __init__(self, xlabel="", ylabel="", clabel="", title="", subtitle=""):
1620 BasicPlot.__init__(self, xlabel=xlabel, ylabel=ylabel, title=title,\
1621 subtitle=subtitle)
1622 self.clabel = clabel
1623 self.x_data_sets = []
1624 self.y_data_sets = []
1625 self.c_data_sets = []
1626 self.kwarg_sets = []
1627
1628 - def add_content(self, x_data, y_data, c_data, **kwargs):
1629 self.x_data_sets.append(x_data)
1630 self.y_data_sets.append(y_data)
1631 self.c_data_sets.append(c_data)
1632 self.kwarg_sets.append(kwargs)
1633
1634 - def finalize(self, loc=0, colorbar=True, logcolor=False, clim=None,\
1635 alpha=0.8):
1636
1637 p = None
1638 for x_vals, y_vals, c_vals, plot_kwargs in\
1639 itertools.izip(self.x_data_sets, self.y_data_sets, self.c_data_sets,
1640 self.kwarg_sets):
1641 if len(x_vals):
1642 zipped = zip(x_vals, y_vals, c_vals)
1643 zipped.sort(key=lambda (x,y,c): c)
1644 x_vals, y_vals, c_vals = map(numpy.asarray, zip(*zipped))
1645 if logcolor:
1646 plot_kwargs.setdefault("norm",pylab.matplotlib.colors.LogNorm())
1647 try:
1648 p = self.ax.scatter(x_vals, y_vals, c=c_vals, **plot_kwargs)
1649 except ValueError:
1650 plot_kwargs['visible'] = False
1651 p = self.ax.scatter([1], [1], c=[1], **plot_kwargs)
1652 p = self.ax.scatter(x_vals, y_vals, c=c_vals, **plot_kwargs)
1653
1654 if colorbar and p is not None:
1655 add_colorbar(self.ax, mappable=p, log=logcolor, label=self.clabel,\
1656 clim=clim)
1657
1658
1659 self.add_legend_if_labels_exist(loc=loc, alpha=alpha)
1660
1661
1662 del self.x_data_sets
1663 del self.y_data_sets
1664 del self.c_data_sets
1665 del self.kwarg_sets
1666
1668 """
1669 Horizontal bar segment plot.
1670 """
1671 color_code = {'H1':'r', 'H2':'b', 'L1':'g', 'V1':'m', 'G1':'k'}
1672
1673 - def __init__(self, xlabel="", ylabel="", title="", subtitle="", t0=0,\
1674 dt=1):
1675 """
1676 Create a fresh plot. Provide t0 to provide reference time to use as
1677 zero, and dt to use a different number of seconds for each unit of the
1678 x-axis.
1679 """
1680 BasicPlot.__init__(self, xlabel, ylabel, title)
1681 self.segdict = segments.segmentlistdict()
1682 self.keys = []
1683 self._time_transform = lambda t: float(t - t0)/dt
1684 self.kwarg_sets = []
1685
1686 - def add_content(self, segdict, keys=None, **kwargs):
1687 if not keys:
1688 keys = sorted(segdict.keys())
1689 for key in keys:
1690 if key in self.segdict.keys():
1691 self.segdict[key] += segdict[key]
1692 else:
1693 self.keys.append(key)
1694 self.segdict[key] = segdict[key]
1695 self.kwarg_sets.append(dict())
1696 self.kwarg_sets[-1].update(kwargs)
1697
1699 """
1700 Highlight a particular segment with dashed lines.
1701 """
1702 a,b = map(self._time_transform,seg)
1703 plot_args.setdefault('linestyle', '--')
1704 plot_args.setdefault('color','r')
1705 self.ax.axvline(a, **plot_args)
1706 self.ax.axvline(b, **plot_args)
1707
1709 """
1710 Define a window of interest by setting the x-limits of the plot
1711 appropriately. If padding is also present, protract the x-limits by
1712 that quantity and mark the unpadded window with solid black lines.
1713 """
1714 a = self._time_transform(window_seg[0])
1715 b = self._time_transform(window_seg[1])
1716 self.window = segments.segment((a - padding, b + padding))
1717
1718 if padding > 0:
1719 self.ax.axvline(a, color='k', linewidth=2)
1720 self.ax.axvline(b, color='k', linewidth=2)
1721
1722 @method_callable_once
1723 - def finalize(self, labels_inset=False, hidden_colorbar=False):
1724
1725 for row,(key,plot_kwargs)\
1726 in enumerate(itertools.izip(self.keys, self.kwarg_sets)):
1727 plot_kwargs.setdefault("edgecolor", "k")
1728 if self.color_code.has_key(key):
1729 plot_kwargs.setdefault("color", self.color_code[key])
1730 else:
1731 plot_kwargs.setdefault("color", "b")
1732 for seg in self.segdict[key]:
1733 a,b = map(self._time_transform, seg)
1734 self.ax.fill([a, b, b, a, a],\
1735 [row-0.4, row-0.4, row+0.4, row+0.4, row-0.4],\
1736 **plot_kwargs)
1737 if labels_inset:
1738 self.ax.text(0.01, (row+1)/(len(self.keys)+1),\
1739 re.sub(r'\\+_+','\_',key),\
1740 horizontalalignment='left',\
1741 verticalalignment='center',\
1742 transform=self.ax.transAxes,\
1743 backgroundcolor='white',\
1744 bbox=dict(facecolor='white', alpha=0.5,\
1745 edgecolor='white'))
1746
1747 ticks = pylab.arange(len(self.keys))
1748 self.ax.set_yticks(ticks)
1749 if labels_inset:
1750 self.ax.set_yticklabels(ticks, color='white')
1751 else:
1752 self.ax.set_yticklabels([re.sub(r'\\+_+', '\_', k)\
1753 for k in self.keys], size='small')
1754 self.ax.set_ylim(-1, len(self.keys))
1755
1756
1757 del self.keys
1758 del self.segdict
1759 del self.kwarg_sets
1760
1762 """
1763 A DQ-formatted scatter plot, with those triggers below some threshold on the
1764 colour axis get plotted tiny wee.
1765 """
1766 - def finalize(self, loc=0, alpha=0.8, colorbar=True, logcolor=False,\
1767 clim=None, threshold=0):
1768
1769 p = None
1770 for x_vals, y_vals, c_vals, plot_kwargs in\
1771 itertools.izip(self.x_data_sets, self.y_data_sets, self.c_data_sets,
1772 self.kwarg_sets):
1773 plot_kwargs.setdefault("s", 15)
1774 if logcolor:
1775 plot_kwargs.setdefault("norm",pylab.matplotlib.colors.LogNorm())
1776 if clim:
1777 plot_kwargs.setdefault("vmin", clim[0])
1778 plot_kwargs.setdefault("vmax", clim[1])
1779
1780 if len(x_vals):
1781 zipped = zip(x_vals, y_vals, c_vals)
1782 zipped.sort(key=lambda (x,y,c): c)
1783 x_vals, y_vals, c_vals = map(numpy.asarray, zip(*zipped))
1784
1785 t = (c_vals>=threshold).nonzero()[0]
1786 if len(t):
1787 idx = t[0]
1788 xlow,xhigh = numpy.split(x_vals, [idx])
1789 ylow,yhigh = numpy.split(y_vals, [idx])
1790 clow,chigh = numpy.split(c_vals, [idx])
1791 else:
1792 xlow = x_vals
1793 ylow = y_vals
1794 clow = c_vals
1795 xhigh = numpy.array([])
1796 yhigh = numpy.array([])
1797 chigh = numpy.array([])
1798
1799 if xlow.size > 0:
1800 lowargs = copy.deepcopy(plot_kwargs)
1801 lowargs.pop("label", None)
1802 lowargs["s"] /= 4
1803 if lowargs.get("marker", None) != "x":
1804 lowargs["edgecolor"] = "none"
1805 self.ax.scatter(xlow, ylow, c=clow, **lowargs)
1806 if xhigh.size > 0:
1807 self.ax.scatter(xhigh, yhigh, c=chigh, **plot_kwargs)
1808 if not len(x_vals):
1809 plot_kwargs["visible"] = False
1810 self.ax.scatter([1], [1], c=1, **plot_kwargs)
1811
1812 if colorbar:
1813 add_colorbar(self.ax, log=logcolor, label=self.clabel, clim=clim,\
1814 cmap=plot_kwargs.get("cmap", None))
1815
1816
1817 self.add_legend_if_labels_exist(loc=loc, alpha=alpha)
1818
1819
1820 del self.x_data_sets
1821 del self.y_data_sets
1822 del self.c_data_sets
1823 del self.kwarg_sets
1824
1826 """
1827 A simple line histogram plot. The values of each histogram bin are plotted
1828 using pylab.plot(), with points centred on the x values and height equal
1829 to the y values.
1830
1831 Cumulative, and rate options can be passeed to the finalize() method to
1832 format each trace individually.
1833 """
1834 - def __init__(self, xlabel="", ylabel="", title="", subtitle=""):
1835 BasicPlot.__init__(self, xlabel, ylabel, title, subtitle)
1836 self.data_sets = []
1837 self.normalize_values = []
1838 self.kwarg_sets = []
1839
1840 - def add_content(self, data, normalize=1, **kwargs):
1841 self.data_sets.append(data)
1842 self.normalize_values.append(normalize)
1843 self.kwarg_sets.append(kwargs)
1844
1845 @method_callable_once
1846 - def finalize(self, loc=0, num_bins=100, cumulative=False, rate=False,\
1847 xlim=None, bar=False, fill=False, logx=False, logy=False,\
1848 alpha=0.8):
1849
1850 if xlim:
1851 min_stat, max_stat = xlim
1852 else:
1853 min_stat, max_stat = determine_common_bin_limits(self.data_sets)
1854 if logx:
1855 if min_stat == max_stat == 0:
1856 min_stat = 1
1857 max_stat = 10
1858 bins = numpy.logspace(numpy.log10(min_stat), numpy.log10(max_stat),\
1859 int(num_bins) + 1, endpoint=True)
1860 else:
1861 bins = numpy.linspace(min_stat, max_stat, num_bins+1, endpoint=True)
1862
1863
1864 if logx:
1865 width = list(numpy.diff(bins))
1866 else:
1867 width = (1 - 0.1 * len(self.data_sets)) * (bins[1] - bins[0])
1868 width = numpy.asarray(width)
1869
1870
1871 ymin = None
1872
1873 for data_set, norm, plot_kwargs in\
1874 itertools.izip(self.data_sets, self.normalize_values,\
1875 self.kwarg_sets):
1876
1877 y, x = numpy.histogram(data_set, bins=bins, normed=False)
1878 y = y.astype(float)
1879 x = x[:-1]
1880
1881
1882 if fill:
1883 plot_kwargs.setdefault("linewidth", 1)
1884 plot_kwargs.setdefault("alpha", 0.8)
1885
1886
1887 if cumulative:
1888 y = y[::-1].cumsum()[::-1]
1889
1890
1891 if norm != 1:
1892 y /= norm
1893
1894
1895 if bar:
1896 x = numpy.vstack((x-width/2, x+width/2)).reshape((-1,),\
1897 order="F")
1898 y = numpy.vstack((y, y)).reshape((-1,), order="F")
1899
1900
1901 if logy:
1902 numpy.putmask(y, y==0, 1e-100)
1903 self.ax.plot(x, y, **plot_kwargs)
1904 if fill:
1905 plot_kwargs.pop("label", None)
1906 self.ax.fill_between(x, 1e-100, y, **plot_kwargs)
1907
1908 if logx:
1909 try:
1910 self.ax.set_xscale("log", nonposx='clip')
1911 except OverflowError:
1912 self.ax._autoscaleXon = False
1913 self.ax.set_xscale("log", nonposx='clip')
1914
1915 if logy:
1916 try:
1917 self.ax.set_yscale("log", nonposy='clip')
1918 except OverflowError:
1919 self.ax._autoscaleYon = False
1920 self.ax.set_yscale("log", nonposy='clip')
1921
1922
1923 self.add_legend_if_labels_exist(loc=loc, alpha=0.8)
1924
1925
1926 del self.data_sets
1927 del self.normalize_values
1928 del self.kwarg_sets
1929
1931 """
1932 A spherical projection plot.
1933 """
1934 @method_callable_once
1935 - def finalize(self, loc='lower right', projection='moll', centre=(0,0),\
1936 frame=[(0, 0), (1, 1)]):
1937 """
1938 Finalize and draw plot. Can only be called once.
1939
1940 Keyword arguments:
1941
1942 toc : [ str | int ]
1943 compatible value for legend loc, default: 'lower right'
1944 projection : str
1945 valid projection name for mpl_toolkits.basemap.Basemap
1946 centre : list
1947 (ra, dec) in degrees for point to centre on plot, only works
1948 for some projections
1949 frame : list
1950 [(xmin, ymin), (xmax, ymax)] pair of tuples for lower left
1951 and upper right corners of plot area. Full arei (entire
1952 sphere) is [(0,0), (1,1)]. Narrower range is span in,
1953 wider range zoomed out.
1954 """
1955
1956 projection = projection.lower()
1957 centre = [(-(centre[0]-180)+180) % 360, centre[1]]
1958 m1 = Basemap(projection=projection, lon_0=centre[0], lat_0=centre[1],\
1959 resolution=None, ax=self.ax)
1960
1961
1962 width = m1.urcrnrx
1963 height = m1.urcrnry
1964 frame = [list(frame[0]), list(frame[1])]
1965 mapframe = [[-width/2, -height/2], [width/2, height/2]]
1966 if frame[0][0] != None:
1967 mapframe[0][0] = width/2*(frame[0][0]-1)
1968 if frame[0][1] != None:
1969 mapframe[0][1] = height/2*(frame[0][1]-1)
1970 if frame[1][0] != None:
1971 mapframe[1][0] = width/2*(frame[1][0])
1972 if frame[1][1] != None:
1973 mapframe[1][1] = height/2*(frame[1][1])
1974
1975
1976 m = Basemap(projection=projection, lon_0=centre[0], lat_0=centre[1],\
1977 llcrnrx=mapframe[0][0], llcrnry=mapframe[0][1],\
1978 urcrnrx=mapframe[1][0], urcrnry=mapframe[1][1],\
1979 resolution=None, ax=self.ax)
1980
1981
1982 if mapframe == [[-width/2, -height/2], [width/2, height/2]]:
1983 m._fulldisk = True
1984
1985 xrange_ = (m.llcrnrx, m.urcrnrx)
1986 yrange = (m.llcrnry, m.urcrnry)
1987
1988
1989 for x_vals, y_vals, plot_kwargs, c in\
1990 itertools.izip(self.x_data_sets, self.y_data_sets,\
1991 self.kwarg_sets, default_colors()):
1992
1993 plot_kwargs.setdefault("marker", "o")
1994 plot_kwargs.setdefault("edgecolor", "k")
1995 plot_kwargs.setdefault("facecolor", c)
1996
1997
1998 convert = lambda x: (x>=180 and x-360) or (x<180 and x)
1999 x_vals = [-convert(x) for x in x_vals]
2000 if projection in ['moll', 'hammer', 'orth']:
2001 convert = lambda x: (x>=0 and x) or (x<0 and x+360)
2002 x_vals, y_vals = m(x_vals, y_vals)
2003 m.scatter(x_vals, y_vals, **plot_kwargs)
2004
2005
2006 m.drawmapboundary()
2007
2008
2009 if projection in ['ortho']:
2010 plabels = [0, 0, 0, 0]
2011 mlabels = [0, 0, 0, 0]
2012 else:
2013 plabels = [1, 0, 0, 0]
2014 mlabels = [0, 0, 0, 0]
2015
2016
2017 parallels = numpy.arange(-90., 120., 30.)
2018 m.drawparallels(parallels, labels=plabels,\
2019 labelstyle='+/-', latmax=90)
2020
2021
2022 if projection in ['moll', 'hammer', 'ortho']:
2023 meridians = numpy.arange(0., 360., 45.)
2024 else:
2025 meridians = numpy.arange(-180, 181, 45)
2026 m.drawmeridians(meridians, labels=mlabels,\
2027 latmax=90, labelstyle='+/-')
2028
2029
2030 if projection in ['ortho']:
2031 for lon,lat in zip([0.]*len(parallels), parallels):
2032 x, y = m(lon, lat)
2033 lon, lat = m1(x, y, inverse=True)
2034 if x<=10**20 and y<=10**20\
2035 and xrange_[0]<x<xrange_[1] and yrange[0]<=y<=yrange[1]:
2036 m.ax.text(x, y, r"$%0.0f^\circ$" % lat)
2037
2038
2039 for lon,lat in zip(meridians, [0.]*len(meridians)):
2040 tlon = (-(lon-180)+180) % 360
2041 x, y = m(lon, lat)
2042 lon, lat = m1(x, y, inverse=True)
2043
2044 if x<=10**20 and y<=10**20\
2045 and xrange_[0]<x<xrange_[1] and yrange[0]<=y<=yrange[1]:
2046 m.ax.text(x, y, r"$%0.0f^\circ$" % tlon)
2047
2048
2049 self.add_legend_if_labels_exist(loc=loc, scatterpoints=1)
2050
2051
2052
2053
2054
2055
2056 import unittest
2060
2061
2063
2064 pylab.rcParams.update({"savefig.dpi": 120})
2065
2066
2067 plot = SimpleMapPlot(r"Range [km]", r"Take off weight [t]", "4 engine planes")
2068
2069
2070
2071
2072 range = [ [14600], [14800], [13000, 14800], [2800], [4800], [14000]]
2073 weight = [[365], [560], [374, 442], [46], [392], [50]]
2074 links = ['http://de.wikipedia.org/wiki/Airbus_A340',\
2075 'http://de.wikipedia.org/wiki/Airbus_A380',\
2076 'http://de.wikipedia.org/wiki/Boeing_747',\
2077 'http://de.wikipedia.org/wiki/Avro_RJ85',\
2078 'http://de.wikipedia.org/wiki/Antonow_An-124',\
2079 None]
2080 markers = ['ro','bD','yx','cs','g^','k>']
2081
2082
2083 for x,y,link, mark in zip(range, weight, links, markers):
2084 plot.add_content(x, y, link, color=mark[0], marker=mark[1])
2085
2086
2087 plot.finalize()
2088
2089
2090 plotname = 'plotutils_TestSimpleMapPlot.png'
2091 plot.savefig(plotname)
2092
2093
2094 html = plot.create_html(plotname)
2095 f = file('plotutils_TestSimpleMapPlot.html','w')
2096 f.write(html)
2097 f.close()
2098
2099
2100 if __name__ == '__main__':
2101
2102
2103 unittest.main()
2104