Source code for gr.pygr

# -*- coding: utf-8 -*-
"""Python GR module

Exported Classes:

"""

# standard library
import math
import time
import logging
try:
    from collections.abc import Iterable
except ImportError:
    from collections import Iterable
# third party
from numpy import ndarray, asarray
# local library
import gr
import gr3
from gr.pygr.base import GRDrawAttributes, GRMeta, GRViewPort, GRVisibility
from gr.pygr.helper import ColorIndexGenerator, DomainChecker
from gr.pygr.mlab import plot, oplot, scatter, histogram, imshow, stem, polar, bar, polar_histogram
from gr.pygr.mlab import contour, contourf, surface, wireframe, plot3, trisurf, tricont
from gr.pygr.mlab import heatmap, hexbin
from gr.pygr.mlab import legend, title, xlabel, ylabel, xlim, ylim
from gr.pygr.mlab import figure, hold, subplot
from gr import __version__, __revision__

__author__ = """Christian Felder <c.felder@fz-juelich.de>,
Josef Heinen <j.heinen@fz-juelich.de>,
Florian Rhiem <f.rhiem@fz-juelich.de>"""
__copyright__ = """Copyright (c) 2012-2015: Josef Heinen, Florian Rhiem, Christian Felder,
and other contributors:

http://gr-framework.org/credits.html

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

_log = logging.getLogger(__name__)


[docs]class Coords2D(object): def __init__(self, x, y, updatex_callback=None, updatey_callback=None): self._x, self._y = x, y self._updatex_callback = updatex_callback self._updatey_callback = updatey_callback @property def x(self): """Get the current list/ndarray of x values.""" return self._x @x.setter def x(self, lst): self._x = lst if self._updatex_callback: self._updatex_callback(self) @property def y(self): """Get the current list/ndarray of y values.""" return self._y @y.setter def y(self, lst): self._y = lst if self._updatey_callback: self._updatey_callback(self)
[docs] def setUpdateXCallback(self, fp): self._updatex_callback = fp
[docs] def setUpdateYCallback(self, fp): self._updatey_callback = fp
[docs]class Coords3D(Coords2D): def __init__(self, x, y, z): Coords2D.__init__(self, x, y) self._z = z @property def z(self): """Get the current list/ndarray of z values.""" return self._z @z.setter def z(self, lst): self._z = lst
[docs]class GridCoords3D(Coords3D): def __init__(self, x, y, z, nx=None, ny=None): if nx and ny: Coords3D.__init__(self, *gr.gridit(x, y, z, nx, ny)) else: Coords3D.__init__(self, x, y, z)
[docs]class Point(object): def __init__(self, x, y): self._x, self._y = x, y @property def x(self): """Get the current x value.""" return self._x @x.setter def x(self, value): self._x = value @property def y(self): """Get the current y value.""" return self._y @y.setter def y(self, value): self._y = value def __str__(self): return "(%s, %s)" % (self._x, self._y) def __eq__(self, other): return (self.x == other.x and self.y == other.y) def __ne__(self, other): return not self.__eq__(other) def __add__(self, other): return Point(self.x + other.x, self.y + other.y) def __sub__(self, other): return Point(self.x - other.x, self.y - other.y) def __mul__(self, other): """Calculate scalar product.""" return self.x * other.x + self.y * other.y def __div__(self, other): """Calculate component-by-component division.""" return Point(self.x / other.x, self.y / other.y) def __neg__(self): """Calculate negation.""" return Point(-self.x, -self.y) def __pos__(self): return self def __abs__(self): return Point(abs(self.x), abs(self.y))
[docs] def norm(self): """Calculate euclidean norm.""" return math.sqrt(self * self)
[docs]class RegionOfInterest(object): LEGEND = 0x1 TEXT = 0x2 def __init__(self, *args, **kwargs): self._poly, self._x, self._y = None, None, None self._ref = None self._regionType = None self._axes = None for p in args: self.append(p) if "reference" in kwargs: self._ref = kwargs["reference"] if "regionType" in kwargs: self._regionType = kwargs["regionType"] if "axes" in kwargs: self._axes = kwargs["axes"] @property def x(self): """Get the current x values.""" return self._x @property def y(self): """Get the current y values.""" return self._y @property def reference(self): """Get the current reference.""" return self._ref @reference.setter def reference(self, ref): self._ref = ref @property def regionType(self): """Get the current regionType.""" return self._regionType @regionType.setter def regionType(self, rtype): self._regionType = rtype
[docs] def append(self, *args): if self._poly is None: self._poly = [] self._x = [] self._y = [] for p0 in args: self._poly.append(p0) self._x.append(p0.x) self._y.append(p0.y)
def _getNDCPoly(self): poly = [] if self._poly: if self._axes: coord = CoordConverter(self._axes.sizex, self._axes.sizey, self._axes.getWindow()) gr.setscale(self._axes.scale) for pWC in self._poly: coord.setWCforPlotAxes(pWC.x, pWC.y, self._axes) poly.append(coord.getNDC()) else: poly = self._poly return poly
[docs] def isPointInside(self, p0): """ """ # Even-odd rule (Point in Polygon Test) # assume a ray from testpoint to infinity along positive x axis # count intersections along this ray. # If odd inside - outside otherwise. res = False poly = self._getNDCPoly() if poly: n = len(poly) j = n - 1 for i in range(n): pi, pj = poly[i], poly[j] # if testpoint y component between pi, pj # => intersection possible if (((pi.y > p0.y) != (pj.y > p0.y)) and # if x components are both greater than testpoint's x # => intersection (with polyline) (((pi.x > p0.x) and (pj.x > p0.x)) or # if testpoint left from intersection (with polyline) # along x axis with same y component # => intersection with polyline (p0.x < ((p0.y - pi.y) * (pi.x - pj.x) / (pi.y - pj.y) + pi.x)) ) ): # flip on intersection res = not res j = i return res
[docs]class CoordConverter(object): def __init__(self, sizex, sizey, window=None, scale=None): self._sizex, self._sizey, self._window = sizex, sizey, window self._scale = scale self._p = None # always stored in NDC def _checkRaiseXY(self): if self._p.x is None or self._p.y is None: raise AttributeError("x or y has not been initialized.") return True @property def window(self): return self._window
[docs] def setWindow(self, xmin, xmax, ymin, ymax): self._window = [xmin, xmax, ymin, ymax]
[docs] def getWindow(self): if self._window: return list(self._window) else: return None
[docs] def setScale(self, scale): self._scale = scale
[docs] def getScale(self): return self._scale
[docs] def setNDC(self, x, y): self._p = Point(x, y) return self
[docs] def setWC(self, x, y, viewport, window=None): if self.getScale() is not None: scale = self.getScale() else: scale = gr.inqscale() if window: self.setWindow(*window) if self.getWindow(): xmin, xmax, ymin, ymax = self.getWindow() else: xmin, xmax, ymin, ymax = gr.inqwindow() vp = viewport if scale & gr.OPTION_Y_LOG: ymin, ymax = math.log10(ymin), math.log10(ymax) y = math.log10(y) if scale & gr.OPTION_X_LOG: xmin, xmax = math.log10(xmin), math.log10(xmax) x = math.log10(x) ndcX = (vp[0] * self._sizex + (x - xmin) / (xmax - xmin) * (vp[1] - vp[0]) * self._sizex) ndcY = (vp[2] * self._sizey + (y - ymin) / (ymax - ymin) * (vp[3] - vp[2]) * self._sizey) self.setNDC(ndcX, ndcY) return self
[docs] def setWCforPlotAxes(self, x, y, axes): self.setWC(x, y, axes.viewport, axes.getWindow()) return self
[docs] def getNDC(self): self._checkRaiseXY() return self._p
[docs] def getWC(self, viewport): if self.getScale() is not None: scale = self.getScale() else: scale = gr.inqscale() self._checkRaiseXY() ndcPoint = self.getNDC() ndcPoint = Point(ndcPoint.x, ndcPoint.y) if self.getWindow(): xmin, xmax, ymin, ymax = self.getWindow() else: xmin, xmax, ymin, ymax = gr.inqwindow() vp = viewport if scale & gr.OPTION_Y_LOG: ymin, ymax = math.log10(ymin), math.log10(ymax) if scale & gr.OPTION_X_LOG: xmin, xmax = math.log10(xmin), math.log10(xmax) wcY = (ymin + (ndcPoint.y / self._sizey - vp[2]) * (ymax - ymin) / (vp[3] - vp[2])) wcX = (xmin + (ndcPoint.x / self._sizex - vp[0]) * (xmax - xmin) / (vp[1] - vp[0])) if scale & gr.OPTION_Y_LOG: wcY = 10 ** wcY if scale & gr.OPTION_X_LOG: wcX = 10 ** wcX return Point(wcX, wcY)
[docs]class DeviceCoordConverter(CoordConverter): def __init__(self, width, height, sizex=None, sizey=None, window=None, scale=None): self._width, self._height = width, height if sizex is None and sizey is None: if self._width > self._height: sizex = 1. sizey = float(self._height) / self._width else: sizex = float(self._width) / self._height sizey = 1. CoordConverter.__init__(self, sizex, sizey, window, scale)
[docs] def setDC(self, x, y): self._p = Point(float(x) / self._width * self._sizex, (1. - float(y) / self._height) * self._sizey) return self
[docs] def getDC(self): self._checkRaiseXY() return Point(int(self._p.x / self._sizex * self._width), int((1. - self._p.y / self._sizey) * self._height))
[docs]class Text(GRVisibility, Point, GRMeta): DEFAULT_CHARHEIGHT_FACTOR = .027 def __init__(self, x, y, text, axes=None, charheight=DEFAULT_CHARHEIGHT_FACTOR, charup=(0, 1), halign=gr.TEXT_HALIGN_LEFT, valign=gr.TEXT_VALIGN_TOP, textcolor=1, font=None, precision=None, textpath=gr.TEXT_PATH_RIGHT, charexpan=1., hideviewport=True): GRVisibility.__init__(self, True) Point.__init__(self, x, y) self._font, self._precision = None, None self._x, self._y, self._text, self._axes = x, y, text, axes self._charheight = charheight self._charup, self._halign, self._valign = charup, halign, valign self._textcolor = textcolor self._textpath, self._charexpan = textpath, charexpan self._hideviewport = hideviewport if font is not None and precision is not None: self._font, self._precision = font, precision self._textlines = text.split('\n') @property def text(self): """Return the current text string.""" return self._text @text.setter def text(self, text): self._text = text self._textlines = text.split('\n') @property def charheight(self): """Return the current charheight factor.""" return self._charheight @charheight.setter def charheight(self, height): self._charheight = height @property def charup(self): """Return the current charup vector (ux, uy).""" return self._charup @charup.setter def charup(self, vector): self._charup = vector @property def halign(self): """Return the horizontal alignment.""" return self._halign @halign.setter def halign(self, horizontal): self._halign = horizontal @property def valign(self): """Return the vertical alignment.""" return self._valign @valign.setter def valign(self, vertical): self._valign = vertical @property def textcolor(self): """Return the color index for the text color.""" return self._textcolor @textcolor.setter def textcolor(self, colorind): self._textcolor = colorind @property def textpath(self): """Return the current text path (direction in which text will be drawn).""" return self._textpath @textpath.setter def textpath(self, direction): self._textpath = direction @property def charexpan(self): """Return the current character expansion factor (width to height ratio).""" return self._charexpan @charexpan.setter def charexpan(self, factor): self._charexpan = factor
[docs] def setFont(self, font, precision=gr.TEXT_PRECISION_STRING): """Set the font and the precision of the text representation.""" self._font, self._precision = font, precision
@property def hideviewport(self): """Return the current hide outside viewport state.""" return self._hideviewport @hideviewport.setter def hideviewport(self, flag): self._hideviewport = flag
[docs] def getBoundingBox(self): if self._axes: gr.setscale(self._axes.scale) coord = CoordConverter(self._axes.sizex, self._axes.sizey, self._axes.getWindow()) coord.setWCforPlotAxes(self.x, self.y, self._axes) p0 = coord.getNDC() x, y = p0.x, p0.y charHeight = self.charheight * self._axes.sizey window = gr.inqwindow() # set viewport and window to NDC to allow 'line-drawing' # in all regions and in NDC coordinates # if hideviewport is False. gr.setviewport(0, self._axes.sizex, 0, self._axes.sizey) gr.setwindow(0, self._axes.sizex, 0, self._axes.sizey) else: x, y = self.x, self.y charHeight = self.charheight gr.setcharheight(charHeight) gr.setcharup(*self.charup) gr.settextalign(self.halign, self.valign) gr.settextpath(self.textpath) gr.setcharexpan(self.charexpan) if self._font is not None and self._precision is not None: gr.settextfontprec(self._font, self._precision) tbx, tby = gr.inqtext(x, y, self.text) if self._axes: gr.setviewport(*self._axes.viewportscaled) gr.setwindow(*window) return tbx, tby
[docs] def drawGR(self): if self.visible: isInViewport = True if self._axes: coord = CoordConverter(self._axes.sizex, self._axes.sizey, self._axes.getWindow()) coord.setWCforPlotAxes(self.x, self.y, self._axes) p0 = coord.getNDC() x, y = p0.x, p0.y charHeight = self.charheight * self._axes.sizey window = gr.inqwindow() # set viewport and window to NDC to allow 'line-drawing' # in all regions and in NDC coordinates # if hideviewport is False. gr.setviewport(0, self._axes.sizex, 0, self._axes.sizey) gr.setwindow(0, self._axes.sizex, 0, self._axes.sizey) else: x, y = self.x, self.y charHeight = self.charheight gr.setcharheight(charHeight) gr.setcharup(*self.charup) gr.settextalign(self.halign, self.valign) gr.settextpath(self.textpath) gr.setcharexpan(self.charexpan) if self._font is not None and self._precision is not None: gr.settextfontprec(self._font, self._precision) gr.settextcolorind(self.textcolor) if self._axes: tbx, tby = gr.inqtext(x, y, self.text) tbxmin, tbxmax = min(tbx), max(tbx) tbymin, tbymax = min(tby), max(tby) xmin, xmax, ymin, ymax = self._axes.viewportscaled if (tbxmin < xmin or tbxmax > xmax or tbymin < ymin or tbymax > ymax): isInViewport = False if not self.hideviewport or isInViewport: gr.text(x, y, self.text) if self._axes: gr.setviewport(*self._axes.viewportscaled) gr.setwindow(*window) gr.settextcolorind(1)
[docs]class ErrorBar(GRDrawAttributes, Coords2D, GRMeta): HORIZONTAL = 0 VERTICAL = 1 def __init__(self, x, y, dneg, dpos=None, direction=VERTICAL, linetype=gr.LINETYPE_SOLID, markertype=gr.MARKERTYPE_OMARK, linecolor=1, markercolor=1, linewidth=1): GRDrawAttributes.__init__(self, linetype, markertype, linecolor, markercolor, linewidth) Coords2D.__init__(self, asarray(x), asarray(y)) self._direction = direction self._grerror = None if direction == ErrorBar.VERTICAL: self._grerror = gr.verrorbars elif direction == ErrorBar.HORIZONTAL: self._grerror = gr.herrorbars else: raise AttributeError("unsupported value for direction.") self.dneg, self.dpos = dneg, dpos if dpos is not None else dneg def _updateErrors(self): if self.direction == ErrorBar.VERTICAL: self._eneg = self.y - self._dneg self._epos = self.y + self._dpos else: # HORIZONTAL self._eneg = self.x - self._dneg self._epos = self.x + self._dpos @property def direction(self): """Get ErrorBars direction.""" return self._direction @Coords2D.x.setter def x(self, seq): Coords2D.x.__set__(self, asarray(seq)) self._updateErrors() @Coords2D.y.setter def y(self, seq): Coords2D.y.__set__(self, asarray(seq)) self._updateErrors() @property def dneg(self): """Get the current ndarray of absolute deviation in negative direction.""" return self._dneg @dneg.setter def dneg(self, seq): self._dneg = asarray(seq) if self.direction == ErrorBar.VERTICAL: self._eneg = self.y - self._dneg else: # HORIZONTAL self._eneg = self.x - self._dneg @property def dpos(self): """Get the current ndarray of absolute deviation in positive direction.""" return self._dpos @dpos.setter def dpos(self, seq): self._dpos = asarray(seq) if self.direction == ErrorBar.VERTICAL: self._epos = self.y + self._dpos else: # HORIZONTAL self._epos = self.x + self._dpos
[docs] def drawGR(self): # preserve old values ltype = gr.inqlinetype() mtype = gr.inqmarkertype() lcolor = gr.inqlinecolorind() mcolor = gr.inqmarkercolorind() lwidth = gr.inqlinewidth() if self.linetype is not None: gr.setlinecolorind(self.linecolor) gr.setmarkercolorind(self.markercolor) gr.setlinetype(self.linetype) gr.setlinewidth(self.linewidth) if self.markertype is not None: gr.setmarkertype(self.markertype) else: gr.setmarkertype(gr.MARKERTYPE_DOT) self._grerror(self._x, self._y, self._eneg, self._epos) # restore old values gr.setlinecolorind(lcolor) gr.setmarkercolorind(mcolor) gr.setlinetype(ltype) gr.setmarkertype(mtype) gr.setlinewidth(lwidth)
[docs]class Plot(GRViewPort, GRMeta): def __init__(self, viewport=GRViewPort.DEFAULT_VIEWPORT): super(Plot, self).__init__(viewport) self._lstAxes = [] self._title, self._subTitle = None, None self._lblX, self._lblY = None, None self._offsetLblX, self._offsetLblY = 0., 0. self._legend = False self._legendWidth = 0.1 self._legendROI = [] self._rois = [] self._autoscale = 0x0 self._countAxes = 0
[docs] def getAxes(self, idx=None): ret = None if idx is None: ret = self._lstAxes elif idx < len(self._lstAxes): ret = self._lstAxes[idx] return ret
@property def offsetXLabel(self): """get NDC Y offset for xlabel""" return self._offsetLblX @offsetXLabel.setter def offsetXLabel(self, yoff): self._offsetLblX = yoff @property def offsetYLabel(self): """get NDC X offset for ylabel""" return self._offsetLblY @offsetYLabel.setter def offsetYLabel(self, xoff): self._offsetLblY = xoff @property def xlabel(self): """get label for x axis""" return self._lblX @xlabel.setter def xlabel(self, xlabel): self._lblX = xlabel @property def ylabel(self): """get label for y axis""" return self._lblY @ylabel.setter def ylabel(self, ylabel): self._lblY = ylabel @property def subTitle(self): """get plot subtitle""" return self._subTitle @subTitle.setter def subTitle(self, subTitle): self._subTitle = subTitle @property def title(self): """get plot title""" return self._title @title.setter def title(self, title): self._title = title @GRViewPort.viewport.setter def viewport(self, viewport): GRViewPort.viewport.__set__(self, viewport) for axes in self._lstAxes: axes.viewport = viewport @GRViewPort.sizex.setter def sizex(self, value): GRViewPort.sizex.__set__(self, value) for axes in self._lstAxes: axes.sizex = value @GRViewPort.sizey.setter def sizey(self, value): GRViewPort.sizey.__set__(self, value) for axes in self._lstAxes: axes.sizey = value
[docs] def setLogX(self, bool, rescale=False): if bool: for axes in self._lstAxes: if axes.isXLogDomain(): axes.setLogX(bool) else: if rescale: lstPlotCurves = axes.getCurves() if lstPlotCurves: visibleCurves = [c for c in lstPlotCurves if c.visible] if not visibleCurves: visibleCurves = lstPlotCurves window = axes.getWindow() xmax = window[1] xmin = min(min(c.x) for c in visibleCurves) if xmin <= 0: # 3 constant xmin = 1. / 10 ** max(0, 3 - math.log10(xmax)) window[0] = xmin axes.setWindow(*window) axes.setLogX(bool) else: win = axes.getWindow() if win is not None: raise Exception("AXES[%d]: (%d..%d) " % (axes.getId(), win[0], win[1]) + "not in log(x) domain.") else: raise Exception("AXES[%d] not in log(x) domain." % axes.getId()) else: for axes in self._lstAxes: axes.setLogX(bool)
[docs] def setLogY(self, bool, rescale=False): if bool: for axes in self._lstAxes: if axes.isYLogDomain(): axes.setLogY(bool) else: if rescale: lstPlotCurves = axes.getCurves() if lstPlotCurves: visibleCurves = [c for c in lstPlotCurves if c.visible] if not visibleCurves: visibleCurves = lstPlotCurves window = axes.getWindow() ymax = window[3] ymin = min(min(c.y) for c in visibleCurves) if ymin <= 0: # 3 constant ymin = 1. / 10 ** max(0, 3 - math.log10(ymax)) window[2] = ymin axes.setWindow(*window) axes.setLogY(bool) else: win = axes.getWindow() if win is not None: raise Exception("AXES[%d]: (%d..%d) " % (axes.getId(), win[2], win[3]) + "not in log(y) domain.") else: raise Exception("AXES[%d] not in log(y) domain." % axes.getId()) else: for axes in self._lstAxes: axes.setLogY(bool)
[docs] def setGrid(self, bool): for axes in self._lstAxes: axes.setGrid(bool)
[docs] def isLegendEnabled(self): return self._legend
[docs] def setLegend(self, bool): self._legend = bool
[docs] def setLegendWidth(self, width): self._legendWidth = width
@property def autoscale(self): return self._autoscale @autoscale.setter def autoscale(self, mask): self._autoscale = mask for axes in self._lstAxes: axes.autoscale = mask
[docs] def reset(self): for axes in self._lstAxes: axes.reset()
[docs] def logXinDomain(self): logXinDomain = True for axes in self._lstAxes: logXinDomain = (logXinDomain & axes.isXLogDomain()) return logXinDomain
[docs] def logYinDomain(self): logYinDomain = True for axes in self._lstAxes: logYinDomain = (logYinDomain & axes.isYLogDomain()) return logYinDomain
[docs] def pick(self, p0, width, height): coord, axes, curve = None, None, None window = gr.inqwindow() if self._lstAxes: coord = DeviceCoordConverter(width, height, self._sizex, self._sizey) points = [] lstAxes = [] lstCurves = [] for axes in self._lstAxes: gr.setwindow(*axes.getWindow()) coord.setWindow(*axes.getWindow()) coord.setScale(axes.scale) coord.setNDC(p0.x, p0.y) wcPick = coord.getWC(axes.viewport) curves = [c for c in axes.getCurves() if c.visible] for curve in curves: for idx, x in enumerate(curve.x): if x >= wcPick.x: break coord.setWC(x, curve.y[idx], axes.viewport) points.append(coord.getNDC()) lstAxes.append(axes) lstCurves.append(curve) if points: # calculate distance between p0 and point on curve norms = [(p0 - p).norm() for p in points] # nearest point idx = norms.index(min(norms)) p = points[idx] axes = lstAxes[idx] curve = lstCurves[idx] coord.setNDC(p.x, p.y) coord.setWindow(*axes.getWindow()) coord.setScale(axes.scale) else: coord, axes, curve = None, None, None gr.setwindow(*window) return (coord, axes, curve)
[docs] def select(self, p0, p1, width, height): window = gr.inqwindow() coord = CoordConverter(self._sizex, self._sizey) for axes in self._lstAxes: win = axes.getWindow() coord.setWindow(*win) coord.setScale(axes.scale) gr.setwindow(*win) gr.setscale(axes.scale) p0World = coord.setNDC(p0.x, p0.y).getWC(self.viewport) p1World = coord.setNDC(p1.x, p1.y).getWC(self.viewport) xmin = min(p0World.x, p1World.x) xmax = max(p0World.x, p1World.x) ymin = min(p0World.y, p1World.y) ymax = max(p0World.y, p1World.y) axes.setWindow(xmin, xmax, ymin, ymax) gr.setwindow(*window)
[docs] def pan(self, dp, width, height): window = gr.inqwindow() coord = CoordConverter(self._sizex, self._sizey) for axes in self._lstAxes: win = axes.getWindow() coord.setWindow(*win) coord.setScale(axes.scale) xmin, xmax, ymin, ymax = win gr.setwindow(*win) gr.setscale(axes.scale) pmin = coord.setWC(xmin, ymin, axes.viewport, win).getNDC() pmax = coord.setWC(xmax, ymax, axes.viewport, win).getNDC() ndcWin = [pmin.x - dp.x, pmax.x - dp.x, pmin.y - dp.y, pmax.y - dp.y] wmin = coord.setNDC(ndcWin[0], ndcWin[2]).getWC(axes.viewport) wmax = coord.setNDC(ndcWin[1], ndcWin[3]).getWC(axes.viewport) win = [wmin.x, wmax.x, wmin.y, wmax.y] axes.setWindow(*win) gr.setwindow(*window)
[docs] def zoom(self, dpercent, p0=None, width=None, height=None): window = gr.inqwindow() coord = CoordConverter(self._sizex, self._sizey) for axes in self._lstAxes: win = axes.getWindow() xmin, xmax, ymin, ymax = win coord.setWindow(*win) coord.setScale(axes.scale) gr.setwindow(*win) gr.setscale(axes.scale) # zoom from center pmin = coord.setWC(xmin, ymin, axes.viewport, win).getNDC() pmax = coord.setWC(xmax, ymax, axes.viewport, win).getNDC() winWidth = pmax.x - pmin.x winWidth_2 = winWidth / 2 winHeight = pmax.y - pmin.y winHeight_2 = winHeight / 2 dx_2 = winWidth_2 - (1 + dpercent) * winWidth_2 dy_2 = winHeight_2 - (1 + dpercent) * winHeight_2 ndcWin = [pmin.x - dx_2, pmax.x + dx_2, pmin.y - dy_2, pmax.y + dy_2] # calculate ratio and adjust to ratio / zoom at origin p0 if p0 is not None and width is not None and height is not None: ratio_xleft = (p0.x - pmin.x) / winWidth ratio_ydown = (p0.y - pmin.y) / winHeight # calculate new xmin, xmax, ymin, ymax with same ratio xmin_n = p0.x - ratio_xleft * (ndcWin[1] - ndcWin[0]) xmax_n = xmin_n + (ndcWin[1] - ndcWin[0]) ymin_n = p0.y - ratio_ydown * (ndcWin[3] - ndcWin[2]) ymax_n = ymin_n + (ndcWin[3] - ndcWin[2]) ndcWin = [xmin_n, xmax_n, ymin_n, ymax_n] wmin = coord.setNDC(ndcWin[0], ndcWin[2]).getWC(self.viewport) wmax = coord.setNDC(ndcWin[1], ndcWin[3]).getWC(self.viewport) win = [wmin.x, wmax.x, wmin.y, wmax.y] gr.setwindow(*window) if not axes.setWindow(*win): axes.setWindow(*window) self.reset() break
[docs] def addROI(self, roi): if roi not in self._rois: self._rois.append(roi) _log.debug("#rois: %d", len(self._rois)) else: _log.debug("found existing roi")
[docs] def getROI(self, p0): res = None for roi in self._legendROI + self._rois: if roi.isPointInside(p0): res = roi return res
[docs] def addAxes(self, *args, **kwargs): for plotAxes in args: if plotAxes and plotAxes not in self._lstAxes: plotAxes.viewport = self.viewport self._lstAxes.append(plotAxes) # overwrite ids (1: first axis, 2: second axis) self._countAxes += 1 plotAxes._id = self._countAxes return self
[docs] def drawGR(self): [xmin, xmax, ymin, ymax] = self.viewportscaled # redraw flag will be set if viewport has been adjusted in order to # display all legend items. redraw = False # -- draw title and subtitle ----------------------------------------- charHeight = .027 * (ymax - ymin) charHeightUnscaled = .027 * (self.viewport[3] - self.viewport[2]) if self.title or self.subTitle: dyTitle = 0 dySubTitle = 0 gr.settextalign(gr.TEXT_HALIGN_CENTER, gr.TEXT_VALIGN_BOTTOM) gr.setcharup(0., 1.) gr.setcharheight(charHeight) x = xmin + (xmax - xmin) / 2 if self.title: dyTitle = charHeightUnscaled if self.title and self.subTitle: dySubTitle = charHeightUnscaled y = (self.viewport[3] + dyTitle + dySubTitle + charHeightUnscaled + 0.01) if self.title: gr.text(x, y * self.sizey, self.title) y -= dyTitle + 0.01 if self.subTitle: gr.text(x, y * self.sizey, self.subTitle) # -- draw axes and curves -------------------------------------------- if self._lstAxes: for axes in self._lstAxes: axes.drawGR() dyXLabel = 0 dxYLabel = 0 # -- draw x- and y label --------------------------------------------- y = self.offsetXLabel + self.viewport[2] if self.xlabel: gr.settextalign(gr.TEXT_HALIGN_CENTER, gr.TEXT_VALIGN_TOP) gr.setcharup(0., 1.) # tby = gr.inqtext(0, 0, self.xlabel)[1] # tby = map(lambda y: gr.wctondc(0, y)[1], tby) # dyXLabel = max(tby) - min(tby) # already scaled dyXLabel = charHeightUnscaled y -= dyXLabel + charHeightUnscaled gr.text(xmin + (xmax - xmin) / 2., y * self.sizey, self.xlabel) if self.ylabel: gr.settextalign(gr.TEXT_HALIGN_CENTER, gr.TEXT_VALIGN_TOP) gr.setcharup(-1., 0.) tbx = gr.inqtext(0, 0, self.ylabel)[0] tbx = [gr.wctondc(0, yi)[0] for yi in tbx] dxYLabel = max(tbx) - min(tbx) gr.text(self.offsetYLabel + xmin - dxYLabel / 2. - .075, ymin + (ymax - ymin) / 2., self.ylabel) gr.setcharup(0., 1.) # -- draw legend and calculate ROIs for legend items ----------------- if self._legend: x, y = xmin, y - dyXLabel - charHeightUnscaled if y < 0: vp = self.viewport vp[2] += dyXLabel self.viewport = vp # propagate update (calls setter) gr.clearws() self.drawGR() else: # preserve old values ltype = gr.inqlinetype() mtype = gr.inqmarkertype() lcolor = gr.inqlinecolorind() mcolor = gr.inqmarkercolorind() lwidth = gr.inqlinewidth() window = gr.inqwindow() scale = gr.inqscale() gr.setviewport(0, self.sizex, 0, self.sizey) gr.setwindow(0, self.sizex, 0, self.sizey) self._legendROI = [] for axes in self._lstAxes: if redraw: break for curve in axes.getCurves(): if curve.legend: tbx, tby = gr.inqtext(0, 0, curve.legend) textWidth = max(tbx) - min(tbx) lineWidth = self._legendWidth if x + lineWidth + textWidth > self.sizex: x = xmin y -= 2 * charHeightUnscaled if y < 0: vp = self.viewport vp[2] += 3 * charHeightUnscaled # propagate update (calls setter) self.viewport = vp redraw = True break gr.setmarkertype(curve.markertype) gr.setmarkercolorind(curve.markercolor) ys = y * self.sizey # scaled y value if curve.linetype is not None: gr.setlinecolorind(curve.linecolor) gr.setmarkercolorind(curve.markercolor) gr.setlinetype(curve.linetype) gr.setlinewidth(curve.linewidth) gr.polyline([x, x + lineWidth], [ys, ys]) if (curve.markertype != gr.MARKERTYPE_DOT and curve.markertype is not None): gr.setmarkertype(curve.markertype) gr.polymarker([x + lineWidth / 2.], [ys]) elif curve.markertype is not None: gr.setmarkertype(curve.markertype) gr.polymarker([x + lineWidth / 2.], [ys]) ybase = (y - charHeightUnscaled / 2) * self.sizey ytop = (y + charHeightUnscaled / 2) * self.sizey roi = RegionOfInterest(Point(x, ybase), Point(x, ytop), reference=curve, regionType=RegionOfInterest.LEGEND) x += lineWidth + .01 if curve.visible: gr.settextcolorind(1) else: gr.settextcolorind(83) gr.settextalign(gr.TEXT_HALIGN_LEFT, gr.TEXT_VALIGN_HALF) gr.text(x, ys, curve.legend) gr.settextcolorind(1) x += textWidth roi.append(Point(x, ytop), Point(x, ybase)) self._legendROI.append(roi) # gr.polyline(roi.x, roi.y) tbx = gr.inqtext(0, 0, "X")[0] charWidth = max(tbx) - min(tbx) x += charWidth # restore old values gr.setlinecolorind(lcolor) gr.setmarkercolorind(mcolor) gr.setlinetype(ltype) gr.setmarkertype(mtype) gr.setlinewidth(lwidth) # restore viewport and window gr.setviewport(*self.viewportscaled) gr.setwindow(*window) # restore scale gr.setscale(scale) if redraw: gr.clearws() self.drawGR()
[docs]def isinstanceof(other, cls): if not isinstance(other, cls): raise ValueError("%s is not an instance of %s." % (type(other), cls.__name__))
[docs]def islistof(otheriter, cls): if isinstance(otheriter, Iterable): for other in otheriter: if not isinstance(other, cls): raise ValueError("Iterable contains %s " % type(other) + "which is not an instance of %s." % cls.__name__)
[docs]class Coords2DList(list): TYPE = Coords2D def __init__(self, *args, **kwargs): list.__init__(self, *args, **kwargs) islistof(self, self.TYPE) self.xmin, self.xmax, self.ymin, self.ymax = None, None, None, None self.updateMinMax(reset=True) def _calcMinMax(self, coords2d, *args): coords = (coords2d, ) + args xmin = min(min(coord.x) for coord in coords) xmax = max(max(coord.x) for coord in coords) ymin = min(min(coord.y) for coord in coords) ymax = max(max(coord.y) for coord in coords) return xmin, xmax, ymin, ymax
[docs] def updateMinMax(self, *coords, **kwargs): reset = kwargs.pop("reset", False) if not coords: coords = self if coords: xmin, xmax, ymin, ymax = self._calcMinMax(*coords) if reset: self.xmin, self.xmax = xmin, xmax self.ymin, self.ymax = ymin, ymax else: self.xmin = (min(self.xmin, xmin) if self.xmin is not None else xmin) self.xmax = (max(self.xmax, xmax) if self.xmax is not None else xmax) self.ymin = (min(self.ymin, ymin) if self.ymin is not None else ymin) self.ymax = (max(self.ymax, ymax) if self.ymax is not None else ymax) else: self.xmin, self.xmax, self.ymin, self.ymax = None, None, None, None
def __iadd__(self, otheriter): islistof(otheriter, self.TYPE) res = list.__iadd__(self, otheriter) self.updateMinMax(*otheriter) return res
[docs] def append(self, other): isinstanceof(other, self.TYPE) res = list.append(self, other) self.updateMinMax(other) return res
[docs] def extend(self, otheriter): islistof(otheriter, self.TYPE) res = list.extend(self, otheriter) self.updateMinMax(*otheriter) return res
[docs] def insert(self, index, other): isinstanceof(other, self.TYPE) res = list.insert(self, index, other) self.updateMinMax(other) return res
[docs] def pop(self, *args, **kwargs): other = list.pop(self, *args, **kwargs) self.updateMinMax(reset=True) return other
[docs] def remove(self, other): res = list.remove(self, other) self.updateMinMax(reset=True) return res
[docs]class Coords3DList(Coords2DList): TYPE = Coords3D def __init__(self, *args, **kwargs): self.zmin, self.zmax = None, None Coords2DList.__init__(self, *args, **kwargs)
[docs] def updateMinMax(self, *coords, **kwargs): Coords2DList.updateMinMax(self, *coords, **kwargs) reset = kwargs.pop("reset", False) if not coords: coords = self if coords: zmin = min(min(coord.z) for coord in coords) zmax = max(max(coord.z) for coord in coords) if reset: self.zmin, self.zmax = zmin, zmax else: self.zmin = (min(self.zmin, zmin) if self.zmin is not None else zmin) self.zmax = (max(self.zmax, zmax) if self.zmax is not None else zmax) else: self.zmin, self.zmax, = None, None
[docs]class PlotCurve(GRDrawAttributes, GRVisibility, Coords2D, GRMeta): COUNT = 0 def __init__(self, x, y, errBar1=None, errBar2=None, linetype=gr.LINETYPE_SOLID, markertype=gr.MARKERTYPE_DOT, linecolor=None, markercolor=1, legend=None, linewidth=1): GRDrawAttributes.__init__(self, linetype, markertype, linecolor, markercolor, linewidth) GRVisibility.__init__(self, True) Coords2D.__init__(self, x, y) self._e1, self._e2 = errBar1, errBar2 self._legend = legend PlotCurve.COUNT += 1 self._id = PlotCurve.COUNT if legend is None: self._legend = "curve %d" % self._id @property def errorBar1(self): """Get current ErrorBar #1 instance.""" return self._e1 @errorBar1.setter def errorBar1(self, value): self._e1 = value @property def errorBar2(self): """Get current ErrorBar #2 instance.""" return self._e2 @errorBar2.setter def errorBar2(self, value): self._e2 = value @property def legend(self): """Get current text for a legend.""" return self._legend @legend.setter def legend(self, s): self._legend = s
[docs] def drawGR(self): if self.visible: # preserve old values ltype = gr.inqlinetype() mtype = gr.inqmarkertype() lcolor = gr.inqlinecolorind() mcolor = gr.inqmarkercolorind() lwidth = gr.inqlinewidth() if self.linetype is not None and len(self.x) > 1: gr.setlinecolorind(self.linecolor) gr.setmarkercolorind(self.markercolor) gr.setlinetype(self.linetype) gr.setlinewidth(self.linewidth) gr.polyline(self.x, self.y) if (self.markertype != gr.MARKERTYPE_DOT and self.markertype is not None): gr.setmarkertype(self.markertype) gr.polymarker(self.x, self.y) elif self.markertype is not None: gr.setmarkercolorind(self.markercolor) gr.setmarkertype(self.markertype) gr.polymarker(self.x, self.y) if self.errorBar1: self.errorBar1.drawGR() if self.errorBar2: self.errorBar2.drawGR() # restore old values gr.setlinecolorind(lcolor) gr.setmarkercolorind(mcolor) gr.setlinetype(ltype) gr.setmarkertype(mtype) gr.setlinewidth(lwidth)
[docs]class PlotSurface(GRVisibility, GridCoords3D, GRMeta): def __init__(self, x, y, z, colormap=gr.COLORMAP_TEMPERATURE, option=gr.OPTION_CELL_ARRAY, nx=None, ny=None): GRVisibility.__init__(self, True) GridCoords3D.__init__(self, x, y, z, nx, ny) self._colormap, self._option = colormap, option @property def colormap(self): """Get the current colormap.""" return self._colormap @colormap.setter def colormap(self, cmap): self._colormap = cmap @property def option(self): """Get current surface display option.""" return self._option @option.setter def option(self, opt): self._option = opt
[docs] def drawGR(self): if self.visible: gr.setspace(min(self.z), max(self.z), 0, 90) gr.setcolormap(self.colormap) gr3.surface(self.x, self.y, self.z, self.option)
[docs]class PlotContour(GRVisibility, GridCoords3D, GRMeta): def __init__(self, x, y, z, h=None, majorh=0, nx=None, ny=None): GRVisibility.__init__(self, True) GridCoords3D.__init__(self, x, y, z, nx, ny) self._h, self._majorh = h or [], majorh @property def h(self): """Get the current list/ndarray of z values for the height.""" return self._h @h.setter def h(self, lst): self._h = lst @property def majorh(self): """Get the interval of labeled contour lines majorh. A value of 1 will label every line. A value of 2 every second line and so on. A value of 0 disables labeling""" return self._majorh @majorh.setter def majorh(self, mh): self._majorh = mh
[docs] def drawGR(self): if self.visible: gr.setspace(min(self.z), max(self.z), 0, 90) gr.contour(self.x, self.y, self.h, self.z, self.majorh)
[docs]class PlotAxes(GRViewPort, GRMeta): COUNT = 0 SCALE_X = 0x1 SCALE_Y = 0x2 OPTION = { SCALE_X: gr.OPTION_X_LOG, SCALE_Y: gr.OPTION_Y_LOG, } COORDLIST_CLASS = Coords2DList def __init__(self, viewport, xtick=None, ytick=None, majorx=None, majory=None, drawX=True, drawY=True, ticksize=0.01): super(PlotAxes, self).__init__(viewport) self._xtick, self._ytick = xtick, ytick self._majorx, self._majory = None, None self.majorx, self.majory = majorx, majory self._drawX, self._drawY = drawX, drawY self._ticksize = ticksize self._curves = self.COORDLIST_CLASS() self._visibleCurves = self.COORDLIST_CLASS() self._backgroundColor = 0 self._window = None self._scale = 0 self._grid = True self._autoscale = 0x0 PlotAxes.COUNT += 1 self._id = PlotAxes.COUNT self._xtick_callback, self._ytick_callback = None, None
[docs] def getVisibleCurves(self): return self._visibleCurves
[docs] def getCurves(self): return self._curves
[docs] def isXLogDomain(self): window = self.getWindow() if window: return DomainChecker.isInLogDomain(window[0], window[1]) else: return False
[docs] def isYLogDomain(self): window = self.getWindow() if window: return DomainChecker.isInLogDomain(window[2], window[3]) else: return False
@property def xtick(self): """get current user-defined interval between minor x ticks""" if self.scale & gr.OPTION_X_LOG: xtick = 1 elif self._xtick is not None: xtick = self._xtick else: win = self.getWindow() if win: xmin, xmax = win[:2] xtick = gr.tick(xmin, xmax) / self.majorx else: xtick = None return xtick @xtick.setter def xtick(self, tickInterval): self._xtick = tickInterval @property def ytick(self): """get current user-defined interval between minor y ticks""" if self.scale & gr.OPTION_Y_LOG: ytick = 1 elif self._ytick is not None: ytick = self._ytick else: win = self.getWindow() if win: ymin, ymax = win[2:] ytick = gr.tick(ymin, ymax) / self.majory else: ytick = None return ytick @ytick.setter def ytick(self, tickInterval): self._ytick = tickInterval @property def majorx(self): """get current user-defined number of minor tick intervals between major x tick marks""" if self.scale & gr.OPTION_X_LOG: mx = 1 elif self._majorx is not None: mx = self._majorx else: mx = 5 return mx @majorx.setter def majorx(self, minorCount): self._majorx = minorCount if minorCount is None or minorCount > 0 else 1 @property def majory(self): """get current user-defined number of minor tick intervals between major y tick marks""" if self.scale & gr.OPTION_Y_LOG: my = 1 elif self._majory is not None: my = self._majory else: my = 5 return my @majory.setter def majory(self, minorCount): self._majory = minorCount if minorCount is None or minorCount > 0 else 1 @property def ticksize(self): """get current ticksize setting specified in normalized device coordinate unit.""" return self._ticksize @ticksize.setter def ticksize(self, value): self._ticksize = value @property def scale(self): return self._scale @scale.setter def scale(self, options): self._scale = options @property def backgroundColor(self): return self._backgroundColor @backgroundColor.setter def backgroundColor(self, color): self._backgroundColor = color
[docs] def isXDrawingEnabled(self): return self._drawX
[docs] def setXDrawing(self, bool): self._drawX = bool
[docs] def isYDrawingEnabled(self): return self._drawY
[docs] def setYDrawing(self, bool): self._drawY = bool
[docs] def setLogX(self, bool): if bool: self.scale |= gr.OPTION_X_LOG else: self.scale &= ~gr.OPTION_X_LOG
[docs] def setLogY(self, bool): if bool: self.scale |= gr.OPTION_Y_LOG else: self.scale &= ~gr.OPTION_Y_LOG
[docs] def setGrid(self, bool): self._grid = bool
[docs] def isGridEnabled(self): return self._grid
def _adjustLogRange(self, amin, amax, scale): # minimum window borders for log scale wmin, wmax = gr.precision * 10, gr.precision * 100 if scale in PlotAxes.OPTION: if self.scale & PlotAxes.OPTION[scale]: if amax < wmax: amax = wmax if amin < wmin: if self.autoscale & scale: amin = 1. / 10 ** max(0, 3 - math.log10(amax)) else: amin = wmin if gr.validaterange(amin, amax): return amin, amax raise ValueError("amin, amax not in window domain.") raise ValueError("Unknown value '%s' for argument scale." % scale) def _adjustWindow(self, xmin, xmax, ymin, ymax): xmin, xmax = self._adjustLogRange(xmin, xmax, PlotAxes.SCALE_X) ymin, ymax = self._adjustLogRange(ymin, ymax, PlotAxes.SCALE_Y) return xmin, xmax, ymin, ymax
[docs] def setWindow(self, xmin, xmax, ymin, ymax): res = True try: self._window = self._adjustWindow(xmin, xmax, ymin, ymax) except ValueError: res = False _log.debug("xmin, xmax, ymin, ymax not in window domain.") return res
[docs] def getWindow(self): if self._window: return list(self._window) else: return None
[docs] def setXtickCallback(self, fp): self._xtick_callback = fp
[docs] def setYtickCallback(self, fp): self._ytick_callback = fp
def _calcWindowForCurves(self, curves, xmin, xmax, ymin, ymax): # take error bars into account # if error bar's center is in current window # adjust window margins to contain error bars. for curve in curves: if isinstance(curve, PlotCurve): for bar in [curve.errorBar1, curve.errorBar2]: if bar: if bar.direction == ErrorBar.HORIZONTAL: for i, x in enumerate(bar.x): if x >= xmin and x <= xmax: bneg = x - bar.dneg[i] bpos = x + bar.dpos[i] if bneg < xmin: xmin = bneg if bpos > xmax: xmax = bpos elif bar.direction == ErrorBar.VERTICAL: for i, y in enumerate(bar.y): if y >= ymin and y <= ymax: bneg = y - bar.dneg[i] bpos = y + bar.dpos[i] if bneg < ymin: ymin = bneg if bpos > ymax: ymax = bpos return xmin, xmax, ymin, ymax
[docs] def getBoundingBox(self): res = None vcurves = self.getVisibleCurves() or self.getCurves() if vcurves: res = self._calcWindowForCurves(vcurves, vcurves.xmin, vcurves.xmax, vcurves.ymin, vcurves.ymax) return res
[docs] def reset(self): bbox = self.getBoundingBox() if bbox: xmin, xmax, ymin, ymax = bbox if math.fabs(xmax - xmin) < gr.precision: if not gr.validaterange(xmin, xmax): xmin, xmax = gr.adjustrange(xmin, xmax) elif not self.autoscale and self.scale & gr.OPTION_X_LOG == 0: xmin, xmax = gr.adjustrange(xmin, xmax) if math.fabs(ymax - ymin) < gr.precision: if not gr.validaterange(ymin, ymax): ymin, ymax = gr.adjustrange(ymin, ymax) elif not self.autoscale and self.scale & gr.OPTION_Y_LOG == 0: ymin, ymax = gr.adjustrange(ymin, ymax) self.setWindow(xmin, xmax, ymin, ymax) if self.autoscale: self.doAutoScale()
[docs] def doAutoScale(self, curvechanged=None): win = self.getWindow() # global xmin, xmax, ymin, ymax vc = self.getVisibleCurves() if not vc: vc = self.getCurves() if win: # calculate previous xmin, xmax xpmin, xpmax = vc.xmin, vc.xmax # update vc min max / calculate current xmin, xmax[, ymin, ymax] vc.updateMinMax(reset=True) xmin, xmax, ymin, ymax = vc.xmin, vc.xmax, vc.ymin, vc.ymax yl = curvechanged.y[-1] if curvechanged else vc.ymax # scaled values for xmin, xmax and last added y point sxmin, sxmax, symin, symax = self._scaleWindow(xmin, xmax, self.xtick, yl, yl, self.ytick) if self.autoscale & PlotAxes.SCALE_X and xmin != xmax: if xmin < xpmin: # growing in negative direction # hold max value if xmax >= win[1]: # win[1]: user max value xmax = win[1] xmin = sxmin elif xmax > xpmax: # growing in positive direction # hold min value if xmin <= win[0]: # win[0]: user min value xmin = win[0] xmax = sxmax else: # no auto scaling in x keep previous values xmin, xmax = win[0], win[1] if xmin > win[0]: xmin = win[0] if xmax < win[1]: xmax = win[1] else: xmin, xmax = win[0], win[1] if self.autoscale & PlotAxes.SCALE_Y and ymin != ymax: # compare y components of current window with last added y point ymin = symin if symin < win[2] else win[2] ymax = symax if symax > win[3] else win[3] else: # no auto scaling in x keep previous values ymin, ymax = win[2], win[3] # add border to calculated global curves min max # taking error bars into account. vxmin, vxmax, vymin, vymax = self._calcWindowForCurves(vc, vc.xmin, vc.xmax, vc.ymin, vc.ymax) bxmin, bxmax, bymin, bymax = self._scaleWindow(vxmin, vxmax, self.xtick, vymin, vymax, self.ytick) # check whether the calculated window is in the border area # and ensure minimal border widths and heights. if xmin <= vc.xmin and xmin > bxmin: xmin = bxmin if xmax >= vc.xmax and xmax < bxmax: xmax = bxmax if ymin <= vc.ymin and ymin > bymin: ymin = bymin if ymax >= vc.ymax and ymax < bymax: ymax = bymax xmin, xmax, ymin, ymax = self._calcWindowForCurves(vc, xmin, xmax, ymin, ymax) self.setWindow(xmin, xmax, ymin, ymax) else: # update vc min max / calculate current xmin, xmax, ymin, ymax vc.updateMinMax(reset=True) self.reset() return self.getWindow()
[docs] def isReset(self): # obsolete / deprecated # reset() now performs the reset immediately. return False
@property def autoscale(self): return self._autoscale @autoscale.setter def autoscale(self, mask): self._autoscale = mask def _scaleWindow(self, xmin, xmax, xtick, ymin, ymax, ytick): try: return self._adjustWindow(*self.scaleWindow(xmin, xmax, xtick, ymin, ymax, ytick)) except ValueError as e: _log.debug("got error in _adjustWindow: %s" % e) _log.debug("falling back to input window") return xmin, xmax, ymin, ymax
[docs] def scaleWindow(self, xmin, xmax, xtick, ymin, ymax, ytick): # can be used to rescale the current window when in autoscale mode, # e.g. adding a delta to the bounding box return xmin, xmax, ymin, ymax
[docs] def getId(self): return self._id
[docs] def drawGR(self): curves = self.getCurves() viewport = self.viewportscaled if curves: window = self.getWindow() if not window: self.reset() window = self.getWindow() xmin, xmax, ymin, ymax = window gr.setviewport(*self.viewportscaled) gr.setwindow(*window) gr.setscale(self.scale) if self.backgroundColor and self.getId() == 1: gr.setfillintstyle(1) gr.setfillcolorind(self.backgroundColor) gr.fillrect(*window) charHeight = .024 * (viewport[3] - viewport[2]) gr.setcharheight(charHeight) if self.isGridEnabled() and self.getId() == 1: # delta majorx, delta majory dmx = self.xtick * self.majorx dmy = self.ytick * self.majory # xorg, yorg i = int(xmin / dmx) if xmin < 0 else int(xmin / dmx + 1) j = int(ymin / dmy) if ymin < 0 else int(ymin / dmy + 1) xorg = i * dmx yorg = j * dmy gr.grid(self.xtick, self.ytick, xorg, yorg, self.majorx, self.majory) if self.getId() == 1: # first x, y axis majorx, majory = self.majorx, self.majory if not self.isXDrawingEnabled(): majorx = -majorx if not self.isYDrawingEnabled(): majory = -majory gr.axeslbl(self.xtick, self.ytick, xmin, ymin, majorx, majory, self.ticksize, self._xtick_callback, self._ytick_callback) elif self.getId() == 2: # second x, y axis majorx, majory = self.majorx, self.majory if not self.isXDrawingEnabled(): majorx = -majorx if not self.isYDrawingEnabled(): majory = -majory gr.axeslbl(self.xtick, self.ytick, xmax, ymax, majorx, majory, -self.ticksize, self._xtick_callback, self._ytick_callback) for curve in curves: curve.drawGR()
[docs] def plot(self, *args, **kwargs): if len(args) > 0 and len(args) % 2 == 0: self._curves = Coords2DList() self._visibleCurves = Coords2DList() for i in range(0, len(args) // 2): x = args[2 * i] y = args[2 * i + 1] self.addCurves(PlotCurve(x, y)) return self
[docs] def addCurves(self, *args, **kwargs): for plotCurve in args: if plotCurve and plotCurve not in self._curves: self._curves.append(plotCurve) if plotCurve.visible and plotCurve not in self._visibleCurves: self._visibleCurves.append(plotCurve) plotCurve.setVisibleCallback(self.curveVisibilityChanged) plotCurve.setUpdateXCallback(self.curveDataChanged) plotCurve.setUpdateYCallback(self.curveDataChanged) return self
[docs] def curveVisibilityChanged(self, curve, flag): if curve in self._visibleCurves: if not flag: self._visibleCurves.remove(curve) elif flag: self._visibleCurves.append(curve)
[docs] def curveDataChanged(self, curve): if len(curve.x) and len(curve.y): curves = self.getCurves() curves.updateMinMax(reset=True) if curve.visible: if self.autoscale: self.doAutoScale(curve) else: vc = self.getVisibleCurves() vc.updateMinMax(reset=True)
[docs]def ndctowc(x, y): try: iter(x) iter(y) except TypeError: return gr.ndctowc(x, y) else: resX = [] resY = [] for xi, yi in map(gr.ndctowc, x, y): # pylint: disable=bad-builtin resX.append(xi) resY.append(yi) return resX, resY
[docs]def wctondc(x, y): try: iter(x) iter(y) except TypeError: return gr.wctondc(x, y) else: resX = [] resY = [] for xi, yi in map(gr.wctondc, x, y): # pylint: disable=bad-builtin resX.append(xi) resY.append(yi) return resX, resY
def _guessdimension(len): x = int(math.sqrt(len)) d = [] while x >= 1: y = len // x if x * y == len: d.append((x, y)) x -= 1 return sorted(d, key=lambda t: t[1] - t[0])
[docs]def readfile(path, separator=''): fp = open(path, "r") if separator != '': while True: line = fp.readline() if not line: break if line[0] == separator: break data = [] for line in fp.readlines(): for item in line.split(): data.append(float(item)) fp.close() return data
[docs]def delay(seconds): time.sleep(seconds)