# -*- 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)