Package pylal :: Module plotutils
[hide private]
[frames] | no frames]

Source Code for Module pylal.plotutils

   1  # Copyright (C) 2008  Nickolas Fotopoulos 
   2  # 
   3  # This program is free software; you can redistribute it and/or modify it 
   4  # under the terms of the GNU General Public License as published by the 
   5  # Free Software Foundation; either version 2 of the License, or (at your 
   6  # option) any later version. 
   7  # 
   8  # This program is distributed in the hope that it will be useful, but 
   9  # WITHOUT ANY WARRANTY; without even the implied warranty of 
  10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General 
  11  # Public License for more details. 
  12  # 
  13  # You should have received a copy of the GNU General Public License along 
  14  # with this program; if not, write to the Free Software Foundation, Inc., 
  15  # 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. 
  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  # general defaults 
  43  pylab.rc("lines", markersize=12) 
  44  pylab.rc("text", usetex=True) 
45 46 # Utility function 47 -def float_to_latex(x, format="%.2g"):
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
62 ############################################################################## 63 # abstract classes 64 65 -class BasicPlot(object):
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
100 - def finalize(self):
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
110 - def close(self):
111 """ 112 Close the plot and release its memory. 113 """ 114 pylab.close(self.fig)
115
116 - def add_legend_if_labels_exist(self, *args, **kwargs):
117 """ 118 Create a legend if there are any non-trivial labels. 119 """ 120 121 # extract useable parameters that don't get passed to ax.legend 122 alpha = kwargs.pop("alpha", None) 123 linewidth = kwargs.pop("linewidth", None) 124 markersize = kwargs.pop("markersize", None) 125 126 # make legend if any data set requires it 127 for plot_kwargs in self.kwarg_sets: 128 if "label" in plot_kwargs and \ 129 not plot_kwargs["label"].startswith("_"): 130 # generate legend 131 self.ax.legend(*args, **kwargs) 132 # apply extra formatting 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
142 ############################################################################## 143 # utility functions 144 145 -def default_colors():
146 """ 147 An infinite iterator of some default colors. 148 """ 149 return itertools.cycle(('b', 'g', 'r', 'c', 'm', 'y', 'k'))
150
151 -def default_symbols():
152 """ 153 An infinite iterator of some default symbols. 154 """ 155 return itertools.cycle(('x', '^', 'D', 'H', 'o', '1', '+'))
156
157 -def determine_common_bin_limits(data_sets, default_min=0, default_max=0):
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
170 -def method_callable_once(f):
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}
192 193 -def set_rcParams(params=_dq_params):
194 """ 195 Update pylab plot parameters, defaulting to parameters for DQ-style trigger 196 plots. 197 """ 198 199 # customise plot appearance 200 pylab.rcParams.update(params)
201
202 -def display_name(columnName):
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 # define known acronyms 224 acro = ['snr', 'ra','dof', 'id', 'ms', 'far'] 225 # define greek letters 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 # define known units 231 unit = {'ns':'ns', 'hz':'Hz'} 232 # define known subscripts 233 sub = ['flow', 'fhigh', 'hrss', 'mtotal', 'mchirp'] 234 # define miscellaneous entries 235 misc = {'hoft': '$h(t)$'} 236 237 if len(columnName)==1: 238 return columnName 239 240 # find all words, preserving acronyms in upper case 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 # parse words 247 for i,w in enumerate(words): 248 if w.startswith('\\'): 249 pass 250 wl = w.lower() 251 # get miscellaneous definitions 252 if wl in misc.keys(): 253 words[i] = misc[wl] 254 # get acronym in lower case 255 elif wl in acro: 256 words[i] = w.upper() 257 # get numerical unit 258 elif wl in unit: 259 words[i] = '(%s)' % unit[wl] 260 # get character with subscript text 261 elif wl in sub: 262 words[i] = '%s$_{\mbox{\\small %s}}$' % (w[0], w[1:]) 263 # get greek word 264 elif wl in greek: 265 words[i] = '$\%s$' % w 266 # get starting with greek word 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 # get everything else 273 else: 274 if w[:-1].isupper(): 275 words[i] = w 276 else: 277 words[i] = w.title() 278 # escape underscore 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 # set default tex formatting for colorbar 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 # set limits 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 # find mappable with lowest maximum 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 # make sure the mappable has at least one element 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 # generate colorbar 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
363 -def parse_plot_config(cp, section):
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 # define option types 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 # construct param dict 453 for key,val in cp.items(section, raw=True): 454 if val == None: continue 455 # remove quotes 456 val = val.rstrip('"').strip('"') 457 # format key key 458 hkey = re.sub('_', '-', key) 459 ukey = re.sub('-', '_', key) 460 # get limit pairs 461 if hkey in pairs: 462 params[ukey] = map(float, val.split(',')) 463 # get bins 464 elif hkey in pairlist: 465 params[ukey] = map(lambda p: map(float,p.split(',')),val.split(';')) 466 # get booleans 467 elif hkey in booleans: 468 params[ukey] = cp.getboolean(section, key) 469 # get float values 470 elif hkey in floats: 471 params[ukey] = float(val) 472 # get float values 473 elif hkey in ints: 474 params[ukey] = int(val) 475 # else construct strings 476 else: 477 params[ukey] = str(val) 478 479 return params
480
481 -def log_transform(lin_range):
482 """ 483 Return the logarithmic ticks and labels corresponding to the 484 input lin_range. 485 """ 486 log_range = numpy.log10(lin_range) 487 slope = (lin_range[1] - lin_range[0]) / (log_range[1] - log_range[0]) 488 inter = lin_range[0] - slope * log_range[0] 489 tick_range = [tick for tick in range(int(log_range[0] - 1.0),\ 490 int(log_range[1] + 1.0))\ 491 if tick >= log_range[0] and tick<=log_range[1]] 492 ticks = [inter + slope * tick for tick in tick_range] 493 labels = ["${10^{%d}}$" % tick for tick in tick_range] 494 minorticks = [] 495 for i in range(len(ticks[:-1])): 496 minorticks.extend(numpy.logspace(numpy.log10(ticks[i]),\ 497 numpy.log10(ticks[i+1]), num=10)[1:-1]) 498 return ticks, labels, minorticks
499
500 -def time_axis_unit(duration):
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
529 -def set_time_ticks(ax):
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
538 -def set_minor_ticks(ax, x=True, y=True):
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 # xticks 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 # yticks 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
574 ############################################################################## 575 # generic, but usable classes 576 577 -class SimplePlot(BasicPlot):
578 """ 579 Exactly what you get by calling pylab.plot(), but with the handy extras 580 of the BasicPlot class. 581 """
582 - def __init__(self, *args, **kwargs):
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
594 - def finalize(self, loc=0, alpha=0.8):
595 # make plot 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 # add legend if there are any non-trivial labels 601 self.add_legend_if_labels_exist(loc=loc, alpha=alpha) 602 603 # decrement reference counts 604 del self.x_data_sets 605 del self.y_data_sets 606 del self.kwarg_sets
607
608 -class BarPlot(BasicPlot):
609 """ 610 A simple vertical bar plot. Bars are centered on the x values and have 611 height equal to the y values. 612 """
613 - def __init__(self, *args, **kwargs):
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 # make plot 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 # FIXME: linewidth is not a valid kwarg in matplotlib 0.87.7 633 # Reenable once clusters upgrade to CentOS 5. Until then, 634 # all bars have thick, black borders. 635 #plot_kwargs.setdefault("linewidth", 0) 636 plot_kwargs.setdefault("orientation", orientation) 637 self.ax.bar(x_vals, y_vals, **plot_kwargs) 638 639 # add legend if there are any non-trivial labels 640 self.add_legend_if_labels_exist(loc=loc, alpha=alpha) 641 642 # decrement reference counts 643 del self.x_data_sets 644 del self.y_data_sets 645 del self.kwarg_sets
646
647 -class VerticalBarHistogram(BasicPlot):
648 """ 649 Histogram data sets with a common binning, then make a vertical bar plot. 650 """
651 - def __init__(self, *args, **kwargs):
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 # determine binning 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 # determine bar width; gets silly for more than a few data sets 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 # make plot 678 for i, (data_set, plot_kwargs, c) in \ 679 enumerate(itertools.izip(self.data_sets, self.kwarg_sets,\ 680 default_colors())): 681 # set default values 682 plot_kwargs.setdefault("alpha", 0.6) 683 plot_kwargs.setdefault("width", width) 684 plot_kwargs.setdefault("color", c) 685 686 # make histogram 687 y, x = numpy.histogram(data_set, bins=bins, normed=normed) 688 x = x[:-1] 689 690 # mask zeros for logy 691 if logy: 692 y = numpy.ma.masked_where(y==0, y, copy=False) 693 694 # plot 695 plot_item = self.ax.bar(x, y, **plot_kwargs) 696 697 # add legend if there are any non-trivial labels 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 # decrement reference counts 706 del self.data_sets 707 del self.kwarg_sets
708
709 -class NumberVsBinBarPlot(BasicPlot):
710 """ 711 Make a bar plot in which the width and placement of the bars are set 712 by the given bins. 713 """
714 - def __init__(self, *args, **kwargs):
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 # prevent each bar from getting a separate legend entry 735 label = "_nolegend_" 736 if "label" in plot_kwargs: 737 label = plot_kwargs["label"] 738 del plot_kwargs["label"] 739 740 # set default 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 # prevent each bar from getting a separate legend entry 754 if len(patches) > 0: 755 patches[0].set_label(label) 756 757 pylab.axis('tight') 758 759 # add legend if there are any non-trivial labels 760 self.add_legend_if_labels_exist() 761 762 # decrement reference counts 763 del self.bin_sets 764 del self.value_sets 765 del self.kwarg_sets
766
767 -class CumulativeHistogramPlot(BasicPlot):
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 """
773 - def __init__(self, *args, **kwargs):
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
784 - def add_background(self, bg_data_sets, **kwargs):
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 # determine binning 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 # plot foreground 800 for data_set, plot_kwargs in \ 801 itertools.izip(self.fg_data_sets, self.fg_kwarg_sets): 802 # make histogram 803 y, x = numpy.histogram(data_set, bins=bins) 804 y = y[::-1].cumsum()[::-1] 805 x = x[:-1] 806 807 # plot 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 # shade background region 813 if len(self.bg_data_sets) > 0: 814 # histogram each background instance separately and take stats 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 # make histogram 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 # get statistics 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 # plot mean 831 means[means <= epsilon] = epsilon 832 self.ax.plot(x + dx/2, means*normalization, 'r+', **self.bg_kwargs) 833 834 # shade in the area 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 # make semilogy plot 847 self.ax.set_yscale("log") 848 849 # adjust plot range 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 # add legend if there are any non-trivial labels 859 self.kwarg_sets = self.fg_kwarg_sets 860 self.add_legend_if_labels_exist() 861 862 # decrement reference counts 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
869 870 -class ImagePlot(BasicPlot):
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 """
877 - def __init__(self, *args, **kwargs):
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 # set log axes, raise error if you try to use both log and lin on 935 # the same plot 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
964 -class FillPlot(BasicPlot):
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 """
969 - def __init__(self, *args, **kwargs):
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
989 - def finalize(self):
990 # fill in default shades if necessary 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 # plot 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 # add legend if there are any non-trivial labels 1002 self.add_legend_if_labels_exist(loc=0)
1003
1004 -class SixStripSeriesPlot(BasicPlot):
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 # do not want to create axes yet 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
1036 - def finalize(self, yscale="linear"):
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 # This is hardcoded below. Change at your own risk. 1043 ticks_per_axis = 6 # Hardcoded, but you can change it if it looks bad. 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 # attempt to put ticks at every multiple of 10 unless there are too few 1049 # or too many 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 # iterate over axes 1055 for j, freq_range in enumerate(freq_ranges): 1056 # create one of the 6 axes 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 # just look at the region relevant to our axis 1062 ind = (x_coords >= freq_range[0]) & (x_coords < freq_range[1]) 1063 x = x_coords[ind] 1064 y = y_coords[ind] 1065 1066 # add data to axes 1067 ax.plot(x, y, **kwarg_set) 1068 1069 # fix the limits and ticks 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 # label bottom row 1083 ax.set_xlabel(self.xlabel) 1084 1085 # title top row 1086 self.fig.axes[0].set_title(self.title) 1087 1088 # apply common y limits 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
1094 -class ROCPlot(BasicPlot):
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 """
1105 - def __init__(self, *args, **kwargs):
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
1129 - def finalize(self, loc=0):
1130 for bg, fg, weights, kwargs in itertools.izip(self.bg_sets, 1131 self.fg_sets, self.eff_weight_sets, self.kwarg_sets): 1132 # sort, keeping the weights and fg values together 1133 bg = numpy.array(bg) # always copies 1134 bg.sort() 1135 fg = numpy.array(fg) # always copies 1136 if weights is not None: 1137 weights = numpy.array(weights)[fg.argsort()] 1138 fg.sort() 1139 1140 # calculate false alarm probability and efficiency 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 # now find the efficiency *at* each false alarm probability 1148 # if louder than loudest event, then efficiency is 0 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 # dummy index 1152 EFF_by_FAP = EFF[EFF_ind] 1153 EFF_by_FAP[fg_too_loud] = 0. 1154 1155 # plot! 1156 self.ax.plot(FAP, EFF_by_FAP, **kwargs) 1157 1158 # make it pretty 1159 self.ax.grid(True) 1160 self.ax.set_xlim((0, 1)) 1161 self.ax.set_ylim((0, 1)) 1162 1163 # resize figure to make axes square 1164 fig_side = min(self.fig.get_size_inches()) 1165 self.fig.set_size_inches(fig_side, fig_side) 1166 1167 # add legend if there are any non-trivial labels 1168 self.add_legend_if_labels_exist(loc=loc) 1169 1170 # decrement reference counts 1171 del self.fg_sets 1172 del self.bg_sets 1173 del self.eff_weight_sets 1174 del self.kwarg_sets
1175
1176 -class QQPlot(BasicPlot):
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 """
1182 - def __init__(self, *args, **kwargs):
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
1195 - def _compute_quantiles(self, fg, bg):
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 # global rank vs self rank; very much like a Q-Q plot 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
1236 - def finalize(self, loc=0):
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 # make it pretty 1253 self.ax.grid(True) 1254 1255 # add legend if there are any non-trivial labels 1256 self.ax.legend(loc=4) 1257 1258 # decrement reference counts 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
1265 -class SimpleMapPlot(BasicPlot):
1266 """ 1267 Class to create a clickable map html page. 1268 """
1269 - def __init__(self, *args, **kwargs):
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
1282 - def set_click_size(self, size):
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
1296 - def finalize(self, loc=0):
1297 1298 # make plot 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 # add legend if there are any non-trivial labels 1304 self.add_legend_if_labels_exist(loc=loc)
1305 1306
1307 - def rescale(self, dpi):
1308 """ 1309 Calculate the rescaling to map the point coordinates 1310 to the actual coordinates on the image. 1311 """ 1312 1313 # set the dpi used for the savefig 1314 if dpi is None: 1315 dpi = pylab.rcParams['savefig.dpi'] 1316 self.ax.get_figure().set_dpi(dpi) 1317 1318 # get the full extend of the final image 1319 _, _, width, height = self.fig.bbox.extents 1320 1321 # set the pixels for each point that can be clicked 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 # skip if no link is associated with these point(s) 1328 if link is None: 1329 continue 1330 1331 for point in zip(xvec, yvec): 1332 1333 # transform the data coordinates into piel coordinates 1334 pixel = self.ax.transData.transform(point) 1335 1336 # save the new coordinates. The y-coordinate is just 1337 # the other way around. 1338 self.click_x.append(pixel[0]) 1339 self.click_y.append(height - pixel[1]) 1340 self.click_link.append(link)
1341 1342
1343 - def create_html(self, plotname, dpi = None):
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 # points need to be rescaled first 1352 self.rescale(dpi) 1353 1354 # get the full extend of the final image 1355 _, _, width, height = self.fig.bbox.extents 1356 1357 # create the map-page 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 # return the html code, to be saved to a file or embedded in a 1372 # larger html page 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 """
1509 - def __init__(self, *args, **kwargs):
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 # need the 3D axes 1519 self.ax = Axes3D(self.fig) 1520 1521 # set the labels on the new 3D axis 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 # check input argument 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 # create plot 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 # add legend if there are any non-trivial labels 1554 self.add_legend_if_labels_exist(loc=loc) 1555 1556 # loop over a full circle with n steps 1557 dphi = 360.0/float(n) 1558 for i, angle in enumerate(numpy.arange(0.0, 360.0, dphi)): 1559 1560 # make the rotation of the image 1561 self.ax.view_init(30.0, angle) 1562 1563 # create the rotated image in the ploting directory 1564 picname = '%s/%s-%02d'%(plot_dir, plot_basename, i) 1565 self.savefig(picname+'.png',dpi=dpi) 1566 1567 1568 # decrement reference counts 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 # produce the html output code 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 # return the html snippets 1582 return plot3D_head, body
1583
1584 -class ScatterPlot(SimplePlot):
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
1591 - def finalize(self, loc=0, alpha=0.8):
1592 # make plot 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 # add legend if there are any non-trivial labels 1606 self.add_legend_if_labels_exist(loc=loc, alpha=0.8) 1607 1608 # decrement reference counts 1609 del self.x_data_sets 1610 del self.y_data_sets 1611 del self.kwarg_sets
1612
1613 -class ColorbarScatterPlot(BasicPlot):
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 # make plot 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 # add legend if there are any non-trivial labels 1659 self.add_legend_if_labels_exist(loc=loc, alpha=alpha) 1660 1661 # decrement reference counts 1662 del self.x_data_sets 1663 del self.y_data_sets 1664 del self.c_data_sets 1665 del self.kwarg_sets
1666
1667 -class PlotSegmentsPlot(BasicPlot):
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
1698 - def highlight_segment(self, seg, **plot_args):
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
1708 - def set_window(self, window_seg, padding=0):
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 # dereference 1757 del self.keys 1758 del self.segdict 1759 del self.kwarg_sets
1760
1761 -class DQScatterPlot(ColorbarScatterPlot):
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 # make plot 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 # add legend if there are any non-trivial labels 1817 self.add_legend_if_labels_exist(loc=loc, alpha=alpha) 1818 1819 # decrement reference counts 1820 del self.x_data_sets 1821 del self.y_data_sets 1822 del self.c_data_sets 1823 del self.kwarg_sets
1824
1825 -class LineHistogram(BasicPlot):
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 # determine binning 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 # determine bar width; gets silly for more than a few data sets 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 # make plot 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 # make histogram 1877 y, x = numpy.histogram(data_set, bins=bins, normed=False) 1878 y = y.astype(float) 1879 x = x[:-1] 1880 1881 # set defaults 1882 if fill: 1883 plot_kwargs.setdefault("linewidth", 1) 1884 plot_kwargs.setdefault("alpha", 0.8) 1885 1886 # get cumulative sum 1887 if cumulative: 1888 y = y[::-1].cumsum()[::-1] 1889 1890 # normalize 1891 if norm != 1: 1892 y /= norm 1893 1894 # get bars 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 # plot 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 # add legend if there are any non-trivial labels 1923 self.add_legend_if_labels_exist(loc=loc, alpha=0.8) 1924 1925 # decrement reference counts 1926 del self.data_sets 1927 del self.normalize_values 1928 del self.kwarg_sets
1929
1930 -class SkyPositionsPlot(ScatterPlot):
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 # set up projection 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 # set up range 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 # set projection 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 # turn on 'fulldisk' property when using full disk 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 # make plot 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 # project data 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 # finish projection 2006 m.drawmapboundary() 2007 2008 # set labels 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 # draw parallels 2017 parallels = numpy.arange(-90., 120., 30.) 2018 m.drawparallels(parallels, labels=plabels,\ 2019 labelstyle='+/-', latmax=90) 2020 2021 # draw meridians 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 # label parallels for certain projections 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 # label meridians for all projections 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 # set legend 2049 self.add_legend_if_labels_exist(loc=loc, scatterpoints=1)
2050 2051 2052 ################################################### 2053 ## unittest section 2054 ################################################### 2055 2056 import unittest
2057 2058 # -------------------------------------------- 2059 -class TestSimpleMapPlot(unittest.TestCase):
2060 2061
2062 - def test_plot(self):
2063 # set a different dpi for testing purposes 2064 pylab.rcParams.update({"savefig.dpi": 120}) 2065 2066 # define the SimpleMapPlot 2067 plot = SimpleMapPlot(r"Range [km]", r"Take off weight [t]", "4 engine planes") 2068 2069 # define some data. 2070 # Note: The third item consists of two points (with the same link) 2071 # while the last data point has no link at all (link=None) 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 # set the data to plotutils 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 # finalize the plot 2087 plot.finalize() 2088 2089 # save the plot 2090 plotname = 'plotutils_TestSimpleMapPlot.png' 2091 plot.savefig(plotname) 2092 2093 # and here create the html click map and save the html file 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