class Graph(GraceObject): _staticType = 'Graph' def __init__(self, parent, index, onoff='on', hidden='false', type='XY', stacked = 'false', bar_hgap=0.00, **kwargs ): GraceObject.__init__(self, parent, locals()) self.legend = Legend(self) self.frame = Frame(self) self.xaxis = Axis(self, 'x') self.yaxis = Axis(self, 'y') self.altxaxis = Axis(self, 'x', 'alt', 'off') self.altyaxis = Axis(self, 'y', 'alt', 'off') self.title = Title(self) self.subtitle = Subtitle(self) self.view = View(self) self.world = World(self) self.datasets = [] self._datasetIndex = INDEX_ORIGIN self.drawing_objects = [] def __setattr__(self, key, value): # check Graph specific attributes if key == 'type': self._check_type(str, key, value) elif key == 'stacked': self._check_type(str, key, value) self._check_membership(key, value, ('true', 'false')) elif key == 'bar_hgap': self._check_type((float, int), key, value) GraceObject.__setattr__(self, key, value) def __str__(self): graphString = \ """@g%(index)s %(onoff)s @g%(index)s hidden %(hidden)s @g%(index)s type %(type)s @g%(index)s stacked %(stacked)s @g%(index)s bar hgap %(bar_hgap)s @with g%(index)s %(world)s %(view)s %(title)s %(subtitle)s %(legend)s %(frame)s %(xaxis)s %(yaxis)s %(altxaxis)s %(altyaxis)s""" % self datasetString = '\n'.join(str(dataset) for dataset in self.datasets) doString = '\n'.join(str(do) for do in self.drawing_objects) return '\n'.join((doString, graphString, datasetString)) def add_dataset(self, data, cls=DataSet, *args, **kwargs): # make sure that cls is a subclass of Dataset if not issubclass(cls, DataSet): message = '%s is not a subclass of DataSet' % cls.__name__ raise TypeError(message) # make an instance of the dataset class and add to list dataset = cls(parent=self, data=data, index=self._datasetIndex, *args, **kwargs) self.datasets.append(dataset) # increment counter and return the dataset instance self._datasetIndex += 1 return dataset def add_drawing_object(self, cls, *args, **kwargs): # make sure that cls is a subclass of DrawingObject if not issubclass(cls, DrawingObject): message = '%s is not a subclass of DrawingObject' % cls.__name__ raise TypeError(message) # here, the class argument is mandatory, because there are many built # in types of drawing objects drawingObject = cls(self, *args, **kwargs) self.drawing_objects.append(drawingObject) # return the instance of the drawing object return drawingObject def remove_extraworld_drawing_objects(self): """Remove drawing objects that are outside of the world coordinates. Get it, 'extraworld' is like 'extra-terrestrial'. """ new_dos = [] old_dos = [] xmin,ymin,xmax,ymax = self.get_world() for do in self.drawing_objects: if do.loctype=="view": new_dos.append(do) else: do_xmin,do_ymin,do_xmax,do_ymax = do.limits() if (xmin<=do_xmin and do_xmax<=xmax and ymin<=do_ymin and do_ymax<=ymax): new_dos.append(do) else: old_dos.append(do) self.drawing_objects = new_dos for do in old_dos: del do def alldata(self): result = [] for dataset in self.datasets: result.extend(dataset.data) return result def move_dataset_to_front(self, dataset): """Move data set to the front. This emulates the functionality of the xmgrace GUI. """ _dataset = self.datasets.pop(dataset.index-INDEX_ORIGIN) _index = _dataset.index assert _dataset==dataset, "Not the same dataset" self.datasets.append(dataset) for index,dataset in enumerate(self.datasets): dataset.index = index + INDEX_ORIGIN def move_dataset_to_back(self, dataset): """Move data set to the back. This emulates the functionality of the xmgrace GUI. """ _dataset = self.datasets.pop(dataset.index-INDEX_ORIGIN) assert _dataset==dataset, "Not the same dataset" self.datasets.insert(0, dataset) for index,dataset in enumerate(self.datasets): dataset.index = index + INDEX_ORIGIN def move_dataset_forward(self, dataset): """Move data set forward by one dataset. This emulates the functionality of the xmgrace GUI. """ _dataset = self.datasets.pop(dataset.index-INDEX_ORIGIN) _index = _dataset.index assert _dataset==dataset, "Not the same dataset" self.datasets.insert(_index-INDEX_ORIGIN+1, dataset) for index,dataset in enumerate(self.datasets): dataset.index = index + INDEX_ORIGIN def move_dataset_backward(self, dataset): """Move data set backward by one dataset. This emulates the functionality of the xmgrace GUI. """ _dataset = self.datasets.pop(dataset.index-INDEX_ORIGIN) _index = _dataset.index assert _dataset==dataset, "Not the same dataset" _newListIndex = max(0, _index-INDEX_ORIGIN-1) self.datasets.insert(_newListIndex, dataset) for index,dataset in enumerate(self.datasets): dataset.index = index + INDEX_ORIGIN def set_dataset_order(self, datasets): """Specify the order of the data sets. """ # make sure that the lengths of order and datasets are the same assert len(datasets)==len(self.datasets),\ "'datasets' not same length as Graph.datasets." # use the move_dataset_to_back for dataset in datasets: self.move_dataset_to_back(dataset) def logy(self): self.yaxis.set_log() def logx(self): self.xaxis.set_log() def logxy(self): self.xaxis.set_log() self.yaxis.set_log() def liny(self): self.yaxis.set_lin() def linx(self): self.xaxis.set_lin() def linxy(self): self.xaxis.set_lin() self.yaxis.set_lin() def data_smallest_positive(self,only_visible=True): all = [] for dataset in self.datasets: result = dataset.smallest_positive(only_visible=only_visible) all.append(result) if all: xmins, ymins = zip(*all) xmins = [i for i in xmins if not i is None and i > 0] ymins = [i for i in ymins if not i is None and i > 0] return min(xmins), min(ymins) else: return None, None def drawing_object_smallest_positive(self): all = [] for drawing_object in self.drawing_objects: if drawing_object.loctype=="world": result = drawing_object.smallest_positive() all.append(result) if all: xmins, ymins = zip(*all) xmins = [i for i in xmins if not i is None and i > 0] ymins = [i for i in ymins if not i is None and i > 0] return min(xmins), min(ymins) else: return None, None def smallest_positive(self,only_visible=True): data_sp = self.data_smallest_positive(only_visible=only_visible) drawing_object_sp = self.drawing_object_smallest_positive() if None not in drawing_object_sp and None not in data_sp: xmins, ymins = zip(data_sp,drawing_object_sp) return min(xmins), min(ymins) elif None not in drawing_object_sp: return drawing_object_sp elif None not in data_sp: return data_sp else: message=""" In Graph.smallest_positive()... There are no datasets or drawing_objects on which to determine the smallest positive number. """ raise TypeError, message def data_limits(self,only_visible=True): all = [] for dataset in self.datasets: result = dataset.limits(only_visible=only_visible) if not None in result: all.append(result) if len(all): xmins, ymins, xmaxs, ymaxs = zip(*all) return min(xmins), min(ymins), max(xmaxs), max(ymaxs) else: return None, None, None, None def drawing_object_limits(self): all = [] for drawing_object in self.drawing_objects: result = drawing_object.limits() if drawing_object.loctype=="world" and not None in result: all.append(result) if len(all): xmins, ymins, xmaxs, ymaxs = zip(*all) return min(xmins), min(ymins), max(xmaxs), max(ymaxs) else: return None, None, None, None def limits(self,only_visible=True): data_limits = self.data_limits(only_visible=only_visible) drawing_object_limits = self.drawing_object_limits() if None not in drawing_object_limits and None not in data_limits: xmins, ymins, xmaxs, ymaxs = zip(data_limits,drawing_object_limits) return min(xmins), min(ymins), max(xmaxs), max(ymaxs) elif None not in drawing_object_limits: return drawing_object_limits elif None not in data_limits: return data_limits else: message=""" In Graph.limits()... There are no datasets or drawing_objects on which to determine the limits. """ raise TypeError, message def set_world_to_limits(self, epsilon=1e-12): xmin, ymin, xmax, ymax = self.limits() self.world.xmin = xmin - epsilon self.world.xmax = xmax + epsilon self.world.ymin = ymin - epsilon self.world.ymax = ymax + epsilon def autoscale_old(self): self.set_world_to_limits() def set_view(self, xmin, ymin, xmax, ymax): self.view.xmin = xmin self.view.xmax = xmax self.view.ymin = ymin self.view.ymax = ymax def get_view(self): return (self.view.xmin, self.view.ymin, self.view.xmax, self.view.ymax) def set_labels(self, xLabel, yLabel): self.xaxis.label.text = xLabel self.yaxis.label.text = yLabel def get_dataset(self,num): if num >= len(self.datasets): return None else: return self.datasets[num] def calculate_ticks(self, iMin, iMax, lowTarget=3, highTarget=7, scale=LINEAR_SCALE): # variables n1s = [1,2,10,20] n4s = [5,50] ns = n1s + n4s # trick functionality for log axes if scale == LINEAR_SCALE: domain = iMin, iMax if scale == LOGARITHMIC_SCALE: domain = math.log10(iMin), math.log10(iMax) # determine appropriate scale for ticks iRange = domain[1] - domain[0] if iRange > 0: _scale = int(math.floor(math.log10(iRange) - 1.0)) else: _scale = 0 # find number of major ticks for n in ns: nTry = int(math.floor(iRange / (n * 10**_scale))) if nTry >= lowTarget and nTry <= highTarget: break major = 10**_scale*n if scale == LOGARITHMIC_SCALE: major = 10**math.ceil(major) # determine the number of minor ticks if n in n1s: n_minor_ticks=1 elif n in n4s: n_minor_ticks=4 if scale==LOGARITHMIC_SCALE: n_minor_ticks = 8 return major, n_minor_ticks def set_world(self, xMin, yMin, xMax, yMax): self.world.xmin = xMin self.world.xmax = xMax self.world.ymin = yMin self.world.ymax = yMax def get_world(self): return (self.world.xmin, self.world.ymin, self.world.xmax, self.world.ymax) def calculate_sizes(self, printWidth): charSize = 2.3 * printWidth ** -0.4 lineWidth = 3.45 * printWidth ** -0.5 offset = 0.023 * printWidth ** -0.5 return charSize, lineWidth, offset def format_for_print(self, printWidth): charSize, lineWidth, offset = self.calculate_sizes(printWidth) self.set_linewidths(lineWidth) self.set_suffix(charSize * 1.1, 'size', True) self.set_suffix(charSize, '_size', True) self.set_suffix(charSize * 0.67, 'minor_size', True) megaset = [] for dataset in self.datasets: megaset.extend(dataset.data) try: mul = 1.75 * len(megaset)**-.35 mul = 1 * len(megaset)**-.35 except ZeroDivisionError: pass for dataset in self.datasets: dataset.set_suffix(charSize * mul, 'size', True) self.xaxis.ticklabel.offset_loc = 'spec' self.yaxis.ticklabel.offset_loc = 'spec' self.xaxis.ticklabel.offset_tup = (0, offset) self.yaxis.ticklabel.offset_tup = (0, offset) def autoformat(self, printWidth=6.5): self.autoscale() self.format_for_print(printWidth) def autoscalex(self, pad=0, only_visible=True): xMin, yMin, xMax, yMax = self.limits(only_visible=only_visible) if self.xaxis.scale==LINEAR_SCALE: xMajor, nxMinor = self.calculate_ticks(xMin, xMax) xMinor = xMajor / float(nxMinor + 1) if nxMinor == 1: self.world.xmax = math.ceil(xMax / float(xMinor)) * xMinor + \ pad*xMinor self.world.xmin = math.floor(xMin / float(xMinor)) * xMinor - \ pad*xMinor else: self.world.xmax = math.ceil(xMax / float(xMajor)) * xMajor + \ pad*xMajor self.world.xmin = math.floor(xMin / float(xMajor)) * xMajor - \ pad*xMajor elif self.xaxis.scale==LOGARITHMIC_SCALE: # if x has a zero value, autoscale with second smallest if xMin <= 0: xMin, dummy = self.smallest_positive(only_visible=only_visible) xMajor, nxMinor = self.calculate_ticks(xMin, xMax, scale=LOGARITHMIC_SCALE) self.world.xmax = 10**(math.ceil(math.log10(xMax) / float(math.log10(xMajor))) * math.log10(xMajor) + pad*math.log10(xMajor)) self.world.xmin = 10**(math.floor(math.log10(xMin) / float(math.log10(xMajor))) * math.log10(xMajor) - pad*math.log10(xMajor)) else: message = "'%s' is an unknown x-axis scale"%self.xaxis.scale raise TypeError(message) self.xaxis.tick.major = xMajor self.xaxis.tick.minor_ticks = nxMinor self.xaxis.auto_precision() def autoscaley(self, pad=0, only_visible=True): xMin, yMin, xMax, yMax = self.limits(only_visible=only_visible) if self.yaxis.scale==LINEAR_SCALE: yMajor, nyMinor = self.calculate_ticks(yMin, yMax) yMinor = yMajor / float(nyMinor + 1) if nyMinor == 1: self.world.ymax = math.ceil(yMax / float(yMinor)) * yMinor + \ pad*yMinor self.world.ymin = math.floor(yMin / float(yMinor)) * yMinor - \ pad*yMinor else: self.world.ymax = math.ceil(yMax / float(yMajor)) * yMajor + \ pad*yMajor self.world.ymin = math.floor(yMin / float(yMajor)) * yMajor - \ pad*yMajor elif self.yaxis.scale==LOGARITHMIC_SCALE: # if y has a zero value, autoscale with second smallest if yMin <= 0: dummy, yMin = self.smallest_positive(only_visible=only_visible) yMajor, nyMinor = self.calculate_ticks(yMin, yMax, scale=LOGARITHMIC_SCALE) self.world.ymax = 10**(math.ceil(math.log10(yMax) / float(math.log10(yMajor))) * math.log10(yMajor)) self.world.ymin = 10**(math.floor(math.log10(yMin) / float(math.log10(yMajor))) * math.log10(yMajor)) else: message = "'%s' is an unknown y-axis scale"%self.yaxis.scale raise TypeError(message) self.yaxis.tick.major = yMajor self.yaxis.tick.minor_ticks = nyMinor self.yaxis.auto_precision() def autoscale(self, padx=0, pady=0, only_visible=True): self.autoscalex(pad=padx,only_visible=only_visible) self.autoscaley(pad=pady,only_visible=only_visible) def autotickx(self): """Automatically generate x-axis ticks based on world coords. """ xmin, ymin, xmax, ymax = self.get_world() scale = self.xaxis.scale major, n_minor_ticks = self.calculate_ticks(xmin,xmax,scale=scale) self.xaxis.tick.major = major self.xaxis.tick.minor_ticks = n_minor_ticks self.xaxis.auto_precision() def autoticky(self): """Automatically generate y-axis ticks based on world coords. """ xmin, ymin, xmax, ymax = self.get_world() scale = self.yaxis.scale major, n_minor_ticks = self.calculate_ticks(ymin,ymax,scale=scale) self.yaxis.tick.major = major self.yaxis.tick.minor_ticks = n_minor_ticks self.yaxis.auto_precision() def autotick(self): """Automatically generate ticks based on world coords. """ self.autotickx() self.autoticky() def set_different_colors(self, skip=1, attr='name', exclude=('White',), colorsList=[]): """Set datasets in graph to different colors. This function behaves similarly to the similar function in the xmgrace GUI. Can alternatively specify a list of colors for ordering the colors. """ # make sure attr argument is the right type if attr not in ('name', 'index'): message = "attr must be 'name' or 'index' (got %s)" % attr raise ValueError(message) # put all color indexes or color names that should be excluded from # the list in a dictionary, and make color names case insensitive lookup = {} for item in exclude: if isinstance(item, str): item = item.upper() lookup[item] = None # put colors into a list if not colorsList: colorsList = [] for color in self.root.colors: if not (color.name.upper() in lookup or color.index in lookup): colorsList.append(getattr(color, attr)) # set datasets to different colors nColors = len(colorsList) for index, dataset in enumerate(self.datasets): color = colorsList[(skip *index) % nColors] dataset.set_suffix(color, 'color') def set_different_symbols(self, skip=1, attr="index", exclude=(0,), symbolsList=[]): """Set datasets in graph to different symbols. This function behaves similarly to the similar function in the xmgrace GUI. Can alternatively specify a list of symbols for ordering the symbol shapes. """ # make sure attr argument is the right type if attr not in ('name', 'index'): message = "attr must be 'name' or 'index' (got %s)" % attr raise ValueError(message) # put all symbol indexes that should be excluded from the list # in a dictionary lookup = {} for item in exclude: if isinstance(item, str): item = SYMBOLS[item] lookup[item] = None # put symbols into a list indices = INDEX2SYMBOLS.keys() indices.sort() if not symbolsList: symbolsList = [] for index in indices: if index not in lookup: symbolsList.append(index) # set datasets to different symbols nSymbols = len(symbolsList) for index, dataset in enumerate(self.datasets): shape = symbolsList[(skip *index) % nSymbols] dataset.symbol.set_suffix(shape, "shape") def set_different_linestyles(self, skip=1, attr="index", exclude=(0,), linestylesList=[]): """Set datasets in graph to different linestyles. This function behaves similarly to the similar function in the xmgrace GUI. Can alternatively specify a list of linestyles for ordering the line styles. """ # make sure attr argument is the right type if attr not in ('name', 'index'): message = "attr must be 'name' or 'index' (got %s)" % attr raise ValueError(message) # put all linestyle indexes that should be excluded from the list # in a dictionary lookup = {} for item in exclude: if isinstance(item, str): item = LINESTYLES[item] lookup[item] = None # put line styles into a list indices = INDEX2LINESTYLES.keys() indices.sort() if not linestylesList: linestylesList = [] for index in indices: if index not in lookup: linestylesList.append(index) # set datasets to different line styles nLinestyles = len(linestylesList) for index, dataset in enumerate(self.datasets): linestyle = linestylesList[(skip *index) % nLinestyles] dataset.line.set_suffix(linestyle, "linestyle") def set_different_linewidths(self, skip=0.5, start=0.5,linewidthsList=[]): """Set datasets in graph to different linewidths. This function behaves similarly to the similar function in the xmgrace GUI. Can alternatively specify a list of line widths. """ # determine linewidths if not linewidthsList: linewidthsList = [skip*index+start for index in range(len(self.datasets))] # set datasets to different line widths for index, dataset in enumerate(self.datasets): dataset.line.set_suffix(linewidthsList[index], "linewidth") def half_open(self): """Make frame half open """ self.frame.type = 1 self.xaxis.bar.linestyle = 0 self.xaxis.tick.place = "normal" self.yaxis.bar.linestyle = 0 self.yaxis.tick.place = "normal"
class Graph(GraceObject): _staticType = 'Graph' def __init__(self, parent, index, onoff='on', hidden='false', type='XY', stacked='false', bar_hgap=0.00, **kwargs): GraceObject.__init__(self, parent, locals()) self.legend = Legend(self) self.frame = Frame(self) self.xaxis = Axis(self, 'x') self.yaxis = Axis(self, 'y') self.altxaxis = Axis(self, 'x', 'alt', 'off') self.altyaxis = Axis(self, 'y', 'alt', 'off') self.title = Title(self) self.subtitle = Subtitle(self) self.view = View(self) self.world = World(self) self.datasets = [] self._datasetIndex = INDEX_ORIGIN self.drawing_objects = [] def __setattr__(self, key, value): # check Graph specific attributes if key == 'type': self._check_type(str, key, value) elif key == 'stacked': self._check_type(str, key, value) self._check_membership(key, value, ('true', 'false')) elif key == 'bar_hgap': self._check_type((float, int), key, value) GraceObject.__setattr__(self, key, value) def __str__(self): graphString = \ """@g%(index)s %(onoff)s @g%(index)s hidden %(hidden)s @g%(index)s type %(type)s @g%(index)s stacked %(stacked)s @g%(index)s bar hgap %(bar_hgap)s @with g%(index)s %(world)s %(view)s %(title)s %(subtitle)s %(legend)s %(frame)s %(xaxis)s %(yaxis)s %(altxaxis)s %(altyaxis)s""" % self datasetString = '\n'.join(str(dataset) for dataset in self.datasets) doString = '\n'.join(str(do) for do in self.drawing_objects) return '\n'.join((doString, graphString, datasetString)) def add_dataset(self, data, cls=DataSet, *args, **kwargs): # make sure that cls is a subclass of Dataset if not issubclass(cls, DataSet): message = '%s is not a subclass of DataSet' % cls.__name__ raise TypeError(message) # make an instance of the dataset class and add to list dataset = cls(parent=self, data=data, index=self._datasetIndex, *args, **kwargs) self.datasets.append(dataset) # increment counter and return the dataset instance self._datasetIndex += 1 return dataset def add_drawing_object(self, cls, *args, **kwargs): # make sure that cls is a subclass of DrawingObject if not issubclass(cls, DrawingObject): message = '%s is not a subclass of DrawingObject' % cls.__name__ raise TypeError(message) # here, the class argument is mandatory, because there are many built # in types of drawing objects drawingObject = cls(self, *args, **kwargs) self.drawing_objects.append(drawingObject) # return the instance of the drawing object return drawingObject def remove_extraworld_drawing_objects(self): """Remove drawing objects that are outside of the world coordinates. Get it, 'extraworld' is like 'extra-terrestrial'. """ new_dos = [] old_dos = [] xmin, ymin, xmax, ymax = self.get_world() for do in self.drawing_objects: if do.loctype == "view": new_dos.append(do) else: do_xmin, do_ymin, do_xmax, do_ymax = do.limits() if (xmin <= do_xmin and do_xmax <= xmax and ymin <= do_ymin and do_ymax <= ymax): new_dos.append(do) else: old_dos.append(do) self.drawing_objects = new_dos for do in old_dos: del do def alldata(self): result = [] for dataset in self.datasets: result.extend(dataset.data) return result def move_dataset_to_front(self, dataset): """Move data set to the front. This emulates the functionality of the xmgrace GUI. """ _dataset = self.datasets.pop(dataset.index - INDEX_ORIGIN) _index = _dataset.index assert _dataset == dataset, "Not the same dataset" self.datasets.append(dataset) for index, dataset in enumerate(self.datasets): dataset.index = index + INDEX_ORIGIN def move_dataset_to_back(self, dataset): """Move data set to the back. This emulates the functionality of the xmgrace GUI. """ _dataset = self.datasets.pop(dataset.index - INDEX_ORIGIN) assert _dataset == dataset, "Not the same dataset" self.datasets.insert(0, dataset) for index, dataset in enumerate(self.datasets): dataset.index = index + INDEX_ORIGIN def move_dataset_forward(self, dataset): """Move data set forward by one dataset. This emulates the functionality of the xmgrace GUI. """ _dataset = self.datasets.pop(dataset.index - INDEX_ORIGIN) _index = _dataset.index assert _dataset == dataset, "Not the same dataset" self.datasets.insert(_index - INDEX_ORIGIN + 1, dataset) for index, dataset in enumerate(self.datasets): dataset.index = index + INDEX_ORIGIN def move_dataset_backward(self, dataset): """Move data set backward by one dataset. This emulates the functionality of the xmgrace GUI. """ _dataset = self.datasets.pop(dataset.index - INDEX_ORIGIN) _index = _dataset.index assert _dataset == dataset, "Not the same dataset" _newListIndex = max(0, _index - INDEX_ORIGIN - 1) self.datasets.insert(_newListIndex, dataset) for index, dataset in enumerate(self.datasets): dataset.index = index + INDEX_ORIGIN def set_dataset_order(self, datasets): """Specify the order of the data sets. """ # make sure that the lengths of order and datasets are the same assert len(datasets)==len(self.datasets),\ "'datasets' not same length as Graph.datasets." # use the move_dataset_to_back for dataset in datasets: self.move_dataset_to_back(dataset) def logy(self): self.yaxis.set_log() def logx(self): self.xaxis.set_log() def logxy(self): self.xaxis.set_log() self.yaxis.set_log() def liny(self): self.yaxis.set_lin() def linx(self): self.xaxis.set_lin() def linxy(self): self.xaxis.set_lin() self.yaxis.set_lin() def data_smallest_positive(self, only_visible=True): all = [] for dataset in self.datasets: result = dataset.smallest_positive(only_visible=only_visible) all.append(result) if all: xmins, ymins = zip(*all) xmins = [i for i in xmins if not i is None and i > 0] ymins = [i for i in ymins if not i is None and i > 0] return min(xmins), min(ymins) else: return None, None def drawing_object_smallest_positive(self): all = [] for drawing_object in self.drawing_objects: if drawing_object.loctype == "world": result = drawing_object.smallest_positive() all.append(result) if all: xmins, ymins = zip(*all) xmins = [i for i in xmins if not i is None and i > 0] ymins = [i for i in ymins if not i is None and i > 0] return min(xmins), min(ymins) else: return None, None def smallest_positive(self, only_visible=True): data_sp = self.data_smallest_positive(only_visible=only_visible) drawing_object_sp = self.drawing_object_smallest_positive() if None not in drawing_object_sp and None not in data_sp: xmins, ymins = zip(data_sp, drawing_object_sp) return min(xmins), min(ymins) elif None not in drawing_object_sp: return drawing_object_sp elif None not in data_sp: return data_sp else: message = """ In Graph.smallest_positive()... There are no datasets or drawing_objects on which to determine the smallest positive number. """ raise TypeError, message def data_limits(self, only_visible=True): all = [] for dataset in self.datasets: result = dataset.limits(only_visible=only_visible) if not None in result: all.append(result) if len(all): xmins, ymins, xmaxs, ymaxs = zip(*all) return min(xmins), min(ymins), max(xmaxs), max(ymaxs) else: return None, None, None, None def drawing_object_limits(self): all = [] for drawing_object in self.drawing_objects: result = drawing_object.limits() if drawing_object.loctype == "world" and not None in result: all.append(result) if len(all): xmins, ymins, xmaxs, ymaxs = zip(*all) return min(xmins), min(ymins), max(xmaxs), max(ymaxs) else: return None, None, None, None def limits(self, only_visible=True): data_limits = self.data_limits(only_visible=only_visible) drawing_object_limits = self.drawing_object_limits() if None not in drawing_object_limits and None not in data_limits: xmins, ymins, xmaxs, ymaxs = zip(data_limits, drawing_object_limits) return min(xmins), min(ymins), max(xmaxs), max(ymaxs) elif None not in drawing_object_limits: return drawing_object_limits elif None not in data_limits: return data_limits else: message = """ In Graph.limits()... There are no datasets or drawing_objects on which to determine the limits. """ raise TypeError, message def set_world_to_limits(self, epsilon=1e-12): xmin, ymin, xmax, ymax = self.limits() self.world.xmin = xmin - epsilon self.world.xmax = xmax + epsilon self.world.ymin = ymin - epsilon self.world.ymax = ymax + epsilon def autoscale_old(self): self.set_world_to_limits() def set_view(self, xmin, ymin, xmax, ymax): self.view.xmin = xmin self.view.xmax = xmax self.view.ymin = ymin self.view.ymax = ymax def get_view(self): return (self.view.xmin, self.view.ymin, self.view.xmax, self.view.ymax) def set_labels(self, xLabel, yLabel): self.xaxis.label.text = xLabel self.yaxis.label.text = yLabel def get_dataset(self, num): if num >= len(self.datasets): return None else: return self.datasets[num] def calculate_ticks(self, iMin, iMax, lowTarget=3, highTarget=7, scale=LINEAR_SCALE): # variables n1s = [1, 2, 10, 20] n4s = [5, 50] ns = n1s + n4s # trick functionality for log axes if scale == LINEAR_SCALE: domain = iMin, iMax if scale == LOGARITHMIC_SCALE: domain = math.log10(iMin), math.log10(iMax) # determine appropriate scale for ticks iRange = domain[1] - domain[0] if iRange > 0: _scale = int(math.floor(math.log10(iRange) - 1.0)) else: _scale = 0 # find number of major ticks for n in ns: nTry = int(math.floor(iRange / (n * 10**_scale))) if nTry >= lowTarget and nTry <= highTarget: break major = 10**_scale * n if scale == LOGARITHMIC_SCALE: major = 10**math.ceil(major) # determine the number of minor ticks if n in n1s: n_minor_ticks = 1 elif n in n4s: n_minor_ticks = 4 if scale == LOGARITHMIC_SCALE: n_minor_ticks = 8 return major, n_minor_ticks def set_world(self, xMin, yMin, xMax, yMax): self.world.xmin = xMin self.world.xmax = xMax self.world.ymin = yMin self.world.ymax = yMax def get_world(self): return (self.world.xmin, self.world.ymin, self.world.xmax, self.world.ymax) def calculate_sizes(self, printWidth): charSize = 2.3 * printWidth**-0.4 lineWidth = 3.45 * printWidth**-0.5 offset = 0.023 * printWidth**-0.5 return charSize, lineWidth, offset def format_for_print(self, printWidth): charSize, lineWidth, offset = self.calculate_sizes(printWidth) self.set_linewidths(lineWidth) self.set_suffix(charSize * 1.1, 'size', True) self.set_suffix(charSize, '_size', True) self.set_suffix(charSize * 0.67, 'minor_size', True) megaset = [] for dataset in self.datasets: megaset.extend(dataset.data) try: mul = 1.75 * len(megaset)**-.35 mul = 1 * len(megaset)**-.35 except ZeroDivisionError: pass for dataset in self.datasets: dataset.set_suffix(charSize * mul, 'size', True) self.xaxis.ticklabel.offset_loc = 'spec' self.yaxis.ticklabel.offset_loc = 'spec' self.xaxis.ticklabel.offset_tup = (0, offset) self.yaxis.ticklabel.offset_tup = (0, offset) def autoformat(self, printWidth=6.5): self.autoscale() self.format_for_print(printWidth) def autoscalex(self, pad=0, only_visible=True): xMin, yMin, xMax, yMax = self.limits(only_visible=only_visible) if self.xaxis.scale == LINEAR_SCALE: xMajor, nxMinor = self.calculate_ticks(xMin, xMax) xMinor = xMajor / float(nxMinor + 1) if nxMinor == 1: self.world.xmax = math.ceil(xMax / float(xMinor)) * xMinor + \ pad*xMinor self.world.xmin = math.floor(xMin / float(xMinor)) * xMinor - \ pad*xMinor else: self.world.xmax = math.ceil(xMax / float(xMajor)) * xMajor + \ pad*xMajor self.world.xmin = math.floor(xMin / float(xMajor)) * xMajor - \ pad*xMajor elif self.xaxis.scale == LOGARITHMIC_SCALE: # if x has a zero value, autoscale with second smallest if xMin <= 0: xMin, dummy = self.smallest_positive(only_visible=only_visible) xMajor, nxMinor = self.calculate_ticks(xMin, xMax, scale=LOGARITHMIC_SCALE) self.world.xmax = 10**( math.ceil(math.log10(xMax) / float(math.log10(xMajor))) * math.log10(xMajor) + pad * math.log10(xMajor)) self.world.xmin = 10**( math.floor(math.log10(xMin) / float(math.log10(xMajor))) * math.log10(xMajor) - pad * math.log10(xMajor)) else: message = "'%s' is an unknown x-axis scale" % self.xaxis.scale raise TypeError(message) self.xaxis.tick.major = xMajor self.xaxis.tick.minor_ticks = nxMinor self.xaxis.auto_precision() def autoscaley(self, pad=0, only_visible=True): xMin, yMin, xMax, yMax = self.limits(only_visible=only_visible) if self.yaxis.scale == LINEAR_SCALE: yMajor, nyMinor = self.calculate_ticks(yMin, yMax) yMinor = yMajor / float(nyMinor + 1) if nyMinor == 1: self.world.ymax = math.ceil(yMax / float(yMinor)) * yMinor + \ pad*yMinor self.world.ymin = math.floor(yMin / float(yMinor)) * yMinor - \ pad*yMinor else: self.world.ymax = math.ceil(yMax / float(yMajor)) * yMajor + \ pad*yMajor self.world.ymin = math.floor(yMin / float(yMajor)) * yMajor - \ pad*yMajor elif self.yaxis.scale == LOGARITHMIC_SCALE: # if y has a zero value, autoscale with second smallest if yMin <= 0: dummy, yMin = self.smallest_positive(only_visible=only_visible) yMajor, nyMinor = self.calculate_ticks(yMin, yMax, scale=LOGARITHMIC_SCALE) self.world.ymax = 10**( math.ceil(math.log10(yMax) / float(math.log10(yMajor))) * math.log10(yMajor)) self.world.ymin = 10**( math.floor(math.log10(yMin) / float(math.log10(yMajor))) * math.log10(yMajor)) else: message = "'%s' is an unknown y-axis scale" % self.yaxis.scale raise TypeError(message) self.yaxis.tick.major = yMajor self.yaxis.tick.minor_ticks = nyMinor self.yaxis.auto_precision() def autoscale(self, padx=0, pady=0, only_visible=True): self.autoscalex(pad=padx, only_visible=only_visible) self.autoscaley(pad=pady, only_visible=only_visible) def autotickx(self): """Automatically generate x-axis ticks based on world coords. """ xmin, ymin, xmax, ymax = self.get_world() scale = self.xaxis.scale major, n_minor_ticks = self.calculate_ticks(xmin, xmax, scale=scale) self.xaxis.tick.major = major self.xaxis.tick.minor_ticks = n_minor_ticks self.xaxis.auto_precision() def autoticky(self): """Automatically generate y-axis ticks based on world coords. """ xmin, ymin, xmax, ymax = self.get_world() scale = self.yaxis.scale major, n_minor_ticks = self.calculate_ticks(ymin, ymax, scale=scale) self.yaxis.tick.major = major self.yaxis.tick.minor_ticks = n_minor_ticks self.yaxis.auto_precision() def autotick(self): """Automatically generate ticks based on world coords. """ self.autotickx() self.autoticky() def set_different_colors(self, skip=1, attr='name', exclude=('White', ), colorsList=[]): """Set datasets in graph to different colors. This function behaves similarly to the similar function in the xmgrace GUI. Can alternatively specify a list of colors for ordering the colors. """ # make sure attr argument is the right type if attr not in ('name', 'index'): message = "attr must be 'name' or 'index' (got %s)" % attr raise ValueError(message) # put all color indexes or color names that should be excluded from # the list in a dictionary, and make color names case insensitive lookup = {} for item in exclude: if isinstance(item, str): item = item.upper() lookup[item] = None # put colors into a list if not colorsList: colorsList = [] for color in self.root.colors: if not (color.name.upper() in lookup or color.index in lookup): colorsList.append(getattr(color, attr)) # set datasets to different colors nColors = len(colorsList) for index, dataset in enumerate(self.datasets): color = colorsList[(skip * index) % nColors] dataset.set_suffix(color, 'color') def set_different_symbols(self, skip=1, attr="index", exclude=(0, ), symbolsList=[]): """Set datasets in graph to different symbols. This function behaves similarly to the similar function in the xmgrace GUI. Can alternatively specify a list of symbols for ordering the symbol shapes. """ # make sure attr argument is the right type if attr not in ('name', 'index'): message = "attr must be 'name' or 'index' (got %s)" % attr raise ValueError(message) # put all symbol indexes that should be excluded from the list # in a dictionary lookup = {} for item in exclude: if isinstance(item, str): item = SYMBOLS[item] lookup[item] = None # put symbols into a list indices = INDEX2SYMBOLS.keys() indices.sort() if not symbolsList: symbolsList = [] for index in indices: if index not in lookup: symbolsList.append(index) # set datasets to different symbols nSymbols = len(symbolsList) for index, dataset in enumerate(self.datasets): shape = symbolsList[(skip * index) % nSymbols] dataset.symbol.set_suffix(shape, "shape") def set_different_linestyles(self, skip=1, attr="index", exclude=(0, ), linestylesList=[]): """Set datasets in graph to different linestyles. This function behaves similarly to the similar function in the xmgrace GUI. Can alternatively specify a list of linestyles for ordering the line styles. """ # make sure attr argument is the right type if attr not in ('name', 'index'): message = "attr must be 'name' or 'index' (got %s)" % attr raise ValueError(message) # put all linestyle indexes that should be excluded from the list # in a dictionary lookup = {} for item in exclude: if isinstance(item, str): item = LINESTYLES[item] lookup[item] = None # put line styles into a list indices = INDEX2LINESTYLES.keys() indices.sort() if not linestylesList: linestylesList = [] for index in indices: if index not in lookup: linestylesList.append(index) # set datasets to different line styles nLinestyles = len(linestylesList) for index, dataset in enumerate(self.datasets): linestyle = linestylesList[(skip * index) % nLinestyles] dataset.line.set_suffix(linestyle, "linestyle") def set_different_linewidths(self, skip=0.5, start=0.5, linewidthsList=[]): """Set datasets in graph to different linewidths. This function behaves similarly to the similar function in the xmgrace GUI. Can alternatively specify a list of line widths. """ # determine linewidths if not linewidthsList: linewidthsList = [ skip * index + start for index in range(len(self.datasets)) ] # set datasets to different line widths for index, dataset in enumerate(self.datasets): dataset.line.set_suffix(linewidthsList[index], "linewidth") def half_open(self): """Make frame half open """ self.frame.type = 1 self.xaxis.bar.linestyle = 0 self.xaxis.tick.place = "normal" self.yaxis.bar.linestyle = 0 self.yaxis.tick.place = "normal"