# -------------------------------------------------------------------------
# Copyright (C) 2005-2013 Martin Strohalm
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# Complete text of GNU GPL can be found in the file LICENSE.TXT in the
# main directory of the program.
# -------------------------------------------------------------------------
# load libs
import wx
import numpy
# MAIN PLOT CANVAS OBJECT
# -----------------------
class canvas(wx.Window):
"""Plot canvas"""
def __init__(self, parent, id=-1, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, **attr):
wx.Window.__init__(self, parent, id, size=size, style=style)
self.parent = parent
self.SetBackgroundColour('white')
# set default canvas params
self.properties = {
'isotopeDistance': 1.00287,
'xLabel': '',
'yLabel': '',
'showGrid': True,
'showMinorTicks': True,
'showZero': False,
'showLegend': False,
'showXPosBar': False,
'showYPosBar': False,
'showGel': False,
'showCurXPos': True,
'showCurYPos': True,
'showCurDistance': True,
'showCurCharge': True,
'showCurImage': False,
'autoScaleY': True,
'ySymmetry': False,
'overlapLabels': True,
'posBarSize': 6,
'gelHeight': 13,
'xPosDigits': 2,
'yPosDigits': 0,
'distanceDigits': 2,
'xScrollFactor': 0.1,
'xMoveFactor': 0.1,
'xScaleFactor': 0.1,
'yScaleFactor': 0.1,
'maxZoom': 0.001,
'zoomAxis': 'xy',
'checkLimits': True,
'reverseScrolling': False,
'reverseDrawing': False,
'canvasColour': (255, 255, 255),
'plotColour': (255, 255, 255),
'axisColour': (0, 0, 0),
'gridColour': (235, 235, 235),
'highlightColour': (255, 0, 0),
'axisFont': wx.Font(10, wx.SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, 0),
}
# get new attributes
for name, value in attr.items():
self.properties[name] = value
# set default canvas params
self.mouseFn = None
self.mouseFnLMB = None
self.mouseFnRMB = 'zoom'
self.mouseTracker = False
self.currentObject = None
self.currentCharge = 1
self.currentIsotopes = []
self.currentIsotopeLines = 0
self.gelsCount = 0
self.printerScale = {'drawings':1, 'fonts':1}
self.viewMemory = [[],[]]
self.cursorPosition = [0, 0, 0, 0]
self.cursorImage = wx.StockCursor(wx.CURSOR_ARROW)
self.draggingStart = False
self.mouseEvent = False
self.lastDraw = None
self.pointScale = 1
self.pointShift = 0
# set events
self.Bind(wx.EVT_PAINT, self.onPaint)
self.Bind(wx.EVT_SIZE, self.onSize)
self.Bind(wx.EVT_LEAVE_WINDOW, self.onLeave)
self.Bind(wx.EVT_LEFT_DOWN, self.onLMD)
self.Bind(wx.EVT_LEFT_UP, self.onLMU)
self.Bind(wx.EVT_LEFT_DCLICK, self.onLMDC)
self.Bind(wx.EVT_RIGHT_DOWN, self.onRMD)
self.Bind(wx.EVT_RIGHT_UP, self.onRMU)
self.Bind(wx.EVT_RIGHT_DCLICK, self.onRMDC)
self.Bind(wx.EVT_MIDDLE_DOWN, self.onRMD)
self.Bind(wx.EVT_MIDDLE_UP, self.onRMU)
self.Bind(wx.EVT_MOTION, self.onMMotion)
self.Bind(wx.EVT_MOUSEWHEEL, self.onMScroll)
self.Bind(wx.EVT_KEY_DOWN, self.onChar)
# initialize bitmap buffer and set initial size based on client size
self.onSize(0)
# ----
def onPaint(self, evt):
"""Repaint plot."""
# clear cursor tracker
if self.mouseTracker:
self.drawMouseTracker()
# draw buffer to screen
dc = wx.BufferedPaintDC(self, self.plotBuffer)
# ----
def onSize(self, evt):
"""Repaint plot when size changed."""
# get size
width, height = self.GetClientSize()
width = max(1, width)
height = max(1, height)
# make new offscreen bitmap
self.plotBuffer = wx.EmptyBitmap(width, height)
self.setSize()
# redraw plot or clear area
if self.lastDraw:
# get current axis and x range
minX, maxX = self.getCurrentXRange()
minY, maxY = self.getCurrentYRange()
rangeXmin, rangeXmax = self.getMaxXRange()
# block oversizing
if minX < rangeXmin:
minX = rangeXmin
if maxX > rangeXmax:
maxX = rangeXmax
if minX > maxX:
minX = rangeXmin
maxX = rangeXmax
# draw plot
self.draw(self.lastDraw[0], (minX, maxX), (minY, maxY))
else:
self.clear()
# ----
def onLeave(self, evt):
"""Escape mouse events when cursor leave out of the canvas."""
# clear cursor tracker
if self.mouseTracker:
self.drawMouseTracker()
# escape mouse events
self.escMouseEvents()
# set mouse cursor
self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
# ----
def onLMD(self, evt):
"""Change cursor style according to position and current mouse function."""
# get focus
if not self.FindFocus() == self:
self.SetFocus()
try: wx.Yield()
except: pass
# get cursor positions
self.cursorPosition[0], self.cursorPosition[1] = self.getXY(evt)
self.cursorPosition[2], self.cursorPosition[3] = evt.GetPosition()
# escape if any mouse event set
if self.mouseEvent:
return
# clear cursor tracker
if self.mouseTracker:
self.drawMouseTracker()
# get starting coords for movement
self.draggingStart = self.cursorPosition[:]
location = self.getCursorLocation()
# set zooming with Control (for one-button mouse)
if location == 'plot' and evt.ControlDown():
self.mouseEvent = 'zoom'
self.drawZoomBox()
# draw point tracker
elif location == 'plot' and self.mouseFnLMB == 'point':
self.mouseEvent = 'point'
self.drawPointTracker()
# draw isotope ruler
elif location == 'plot' and self.mouseFnLMB == 'isotopes':
self.mouseEvent = 'isotopes'
self.drawIsotopeRuler()
# draw selection rectangle
elif location == 'plot' and self.mouseFnLMB == 'rectangle':
self.mouseEvent = 'rectangle'
self.drawSelectionRect()
# draw selection range
elif location == 'plot' and self.mouseFnLMB == 'range':
self.mouseEvent = 'range'
self.drawSelectionRange()
# draw distance arrow
elif location == 'plot' and self.mouseFnLMB in ('xDistance', 'yDistance'):
self.mouseEvent = 'distance'
self.drawDistanceTracker()
# set axis dragging
elif location == 'xAxis':
self.mouseEvent = 'xShift'
elif location == 'yAxis':
self.mouseEvent = 'yShift'
# not in area
elif location == 'blank':
self.mouseEvent = 'LOut'
# ----
def onLMU(self, evt):
"""Clear cursor."""
# get focus
if not self.FindFocus() == self:
self.SetFocus()
return
# zoom plot
if self.mouseEvent == 'zoom':
# clear zoombox
self.drawZoomBox()
# get zoom
minX = min(self.draggingStart[0], self.cursorPosition[0])
maxX = max(self.draggingStart[0], self.cursorPosition[0])
minY = min(self.draggingStart[1], self.cursorPosition[1])
maxY = max(self.draggingStart[1], self.cursorPosition[1])
# apply zoom
if self.properties['zoomAxis'] == 'xy' and minX != maxX and minY != maxY:
self.zoom(xAxis=(minX, maxX), yAxis=(minY, maxY))
elif self.properties['zoomAxis'] == 'x' and minX != maxX:
self.zoom(xAxis=(minX, maxX))
elif self.properties['zoomAxis'] == 'y' and minY != maxY:
self.zoom(yAxis=(minY, maxY))
# clear point selection
elif self.mouseEvent == 'point':
self.drawPointTracker()
# clear isotope ruler
elif self.mouseEvent == 'isotopes':
self.drawIsotopeRuler()
# clear selection rectangle
elif self.mouseEvent == 'rectangle':
self.drawSelectionRect()
# clear selection range
elif self.mouseEvent == 'range':
self.drawSelectionRange()
# clear distance arrow
elif self.mouseEvent == 'distance':
self.drawDistanceTracker()
# remember zoom
elif self.mouseEvent in ('xShift', 'yShift'):
self.rememberView()
# set cursor positions
self.cursorPosition[0], self.cursorPosition[1] = self.getXY(evt)
self.cursorPosition[2], self.cursorPosition[3] = evt.GetPosition()
# reset mouse event flag
if self.mouseEvent in ('LOut', 'zoom', 'point', 'isotopes', 'rectangle', 'range', 'distance', 'xShift', 'yShift'):
self.mouseEvent = False
# show point tracker
if self.getCursorLocation() == 'plot':
self.drawMouseTracker()
# ----
def onLMDC(self, evt):
"""Show full-axis plot on left-mouse double-click."""
# set axis ranges according to cursor location
location = self.getCursorLocation()
if location == 'plot':
minX, maxX = self.getMaxXRange()
minY, maxY = self.getMaxYRange()
elif location == 'xAxis':
minX, maxX = self.getMaxXRange()
if self.properties['autoScaleY']:
minY, maxY = self.getMaxYRange(minX, maxX)
else:
minY, maxY = self.getCurrentYRange()
elif location == 'yAxis':
minX, maxX = self.getCurrentXRange()
minY, maxY = self.getMaxYRange()
else:
return
# redraw plot
self.draw(self.lastDraw[0], (minX, maxX), (minY, maxY))
# remember new zoom
self.rememberView((minX, maxX), (minY, maxY))
# reset cursor positions
self.cursorPosition[0], self.cursorPosition[1] = self.getXY(evt)
self.cursorPosition[2], self.cursorPosition[3] = evt.GetPosition()
# show point tracker
if self.getCursorLocation() == 'plot':
self.drawMouseTracker()
# ----
def onRMD(self, evt):
"""Change cursor style according to position and current mouse function."""
# get focus
if not self.FindFocus() == self:
self.SetFocus()
# get cursor positions
self.cursorPosition[0], self.cursorPosition[1] = self.getXY(evt)
self.cursorPosition[2], self.cursorPosition[3] = evt.GetPosition()
# escape if any mouse event set
if self.mouseEvent:
return
# get starting coords for movement
self.draggingStart = self.cursorPosition[:]
location = self.getCursorLocation()
# draw zoom box and set event
if location == 'plot' and self.mouseFnRMB == 'zoom':
self.mouseEvent = 'zoom'
self.drawZoomBox()
# set event
elif location == 'xAxis':
self.mouseEvent = 'xScale'
elif location == 'yAxis':
self.mouseEvent = 'yScale'
elif location == 'blank':
self.mouseEvent = 'ROut'
# clear cursor tracker
if self.mouseTracker:
self.drawMouseTracker()
# ----
def onRMU(self, evt):
"""Set new zoom and clear cursor."""
# get focus
if not self.FindFocus() == self:
self.SetFocus()
return
# zoom dragging
if self.mouseEvent == 'zoom':
# clear zoombox
self.drawZoomBox()
# get zoom
minX = min(self.draggingStart[0], self.cursorPosition[0])
maxX = max(self.draggingStart[0], self.cursorPosition[0])
minY = min(self.draggingStart[1], self.cursorPosition[1])
maxY = max(self.draggingStart[1], self.cursorPosition[1])
# apply zoom
if self.properties['zoomAxis'] == 'xy' and minX != maxX and minY != maxY:
self.zoom(xAxis=(minX, maxX), yAxis=(minY, maxY))
elif self.properties['zoomAxis'] == 'x' and minX != maxX:
self.zoom(xAxis=(minX, maxX))
elif self.properties['zoomAxis'] == 'y' and minY != maxY:
self.zoom(yAxis=(minY, maxY))
# axis scaling
elif self.mouseEvent in ('xScale', 'yScale'):
self.rememberView()
# set new cursor positions
self.cursorPosition[0], self.cursorPosition[1] = self.getXY(evt)
self.cursorPosition[2], self.cursorPosition[3] = evt.GetPosition()
# reset mouse event flag
if self.mouseEvent in ('ROut', 'zoom', 'xScale', 'yScale'):
self.mouseEvent = False
# show point tracker
if self.getCursorLocation() == 'plot':
self.drawMouseTracker()
# ----
def onRMDC(self, evt):
"""Show full plot on right-mouse double-click."""
# set axis ranges according to cursor location
if self.getCursorLocation() == 'plot':
minX, maxX = self.getMaxXRange()
minY, maxY = self.getMaxYRange()
else:
return
# redraw plot
self.draw(self.lastDraw[0], (minX, maxX), (minY, maxY))
# remember new zoom
self.rememberView((minX, maxX), (minY, maxY))
# reset cursor positions
self.cursorPosition[0], self.cursorPosition[1] = self.getXY(evt)
self.cursorPosition[2], self.cursorPosition[3] = evt.GetPosition()
# show point tracker
if self.getCursorLocation() == 'plot':
self.drawMouseTracker()
# ----
def onMMotion(self, evt):
"""Draw cursor on mouse motion."""
# clear cursor tracker
if not self.mouseEvent and self.mouseTracker:
self.drawMouseTracker()
# clear zoombox
elif self.mouseEvent == 'zoom':
self.drawZoomBox()
# clear point tracker
elif self.mouseEvent == 'point':
self.drawPointTracker()
# clear isotope ruler
elif self.mouseEvent == 'isotopes':
self.drawIsotopeRuler()
# clear selection rectangle
elif self.mouseEvent == 'rectangle':
self.drawSelectionRect()
# clear selection range
elif self.mouseEvent == 'range':
self.drawSelectionRange()
# clear distance arrow
elif self.mouseEvent == 'distance':
self.drawDistanceTracker()
# store cursor positions
self.cursorPosition[0], self.cursorPosition[1] = self.getXY(evt)
self.cursorPosition[2], self.cursorPosition[3] = evt.GetPosition()
# draw cursor tracker if no event
if not self.mouseEvent:
self.setCursorByLocation()
if self.getCursorLocation() == 'plot':
self.drawMouseTracker()
# draw zoombox
elif self.mouseEvent == 'zoom':
self.drawZoomBox()
# draw point tracker
elif self.mouseEvent == 'point':
self.drawPointTracker()
# draw isotope ruler
elif self.mouseEvent == 'isotopes':
self.drawIsotopeRuler()
# draw selection rectangle
elif self.mouseEvent == 'rectangle':
self.drawSelectionRect()
# draw selection range
elif self.mouseEvent == 'range':
self.drawSelectionRange()
# draw distance arrow
elif self.mouseEvent == 'distance':
self.drawDistanceTracker()
# move x axis
elif self.mouseEvent == 'xShift':
self.shiftAxis('x')
# move y axis
elif self.mouseEvent == 'yShift':
self.shiftAxis('y')
# scale x axis
elif self.mouseEvent == 'xScale':
self.scaleAxis('x')
# scale y axis
elif self.mouseEvent == 'yScale':
self.scaleAxis('y')
# ----
def onMScroll(self, evt):
"""Process mouse scroll."""
# escape if any mouse event set
if self.mouseEvent:
return
# clear cursor tracker
if self.mouseTracker:
self.drawMouseTracker()
# get scroll direction
direction = 1
if evt.GetWheelRotation() < 0:
direction = -1
if self.properties['reverseScrolling']:
direction *= -1
# set new charge and count for isotope ruler
if self.mouseFn == 'isotoperuler' and evt.ShiftDown():
if evt.AltDown() or evt.ControlDown():
self.currentIsotopeLines = min(50, self.currentIsotopeLines + direction)
else:
self.currentCharge = max(1, self.currentCharge + direction)
self.currentCharge = min(50, self.currentCharge)
self.drawMouseTracker()
return
# store cursor positions
self.cursorPosition[0], self.cursorPosition[1] = self.getXY(evt)
self.cursorPosition[2], self.cursorPosition[3] = evt.GetPosition()
# get current axis and x range
minX, maxX = self.getCurrentXRange()
minY, maxY = self.getCurrentYRange()
rangeXmin, rangeXmax = self.getMaxXRange()
# scale x axis and center to current cursor position
if evt.AltDown() or evt.ControlDown():
cursorPos = self.getCursorPosition()
if cursorPos:
currX = cursorPos[0]
minX -= (currX - minX) * self.properties['xScaleFactor'] * direction
maxX += (maxX - currX) * self.properties['xScaleFactor'] * direction
else:
return
# check limits
if self.properties['checkLimits']:
if minX < rangeXmin:
minX = rangeXmin
if maxX > rangeXmax:
maxX = rangeXmax
# check max zoom
if (maxX - minX) < self.properties['maxZoom']:
return
# autoscale y axis
if self.properties['autoScaleY']:
minY, maxY = self.getMaxYRange(minX, maxX)
# scale y axis
elif evt.ShiftDown() or self.getCursorLocation() == 'yAxis':
maxY += (minY - maxY) * self.properties['yScaleFactor'] * direction
# check y symmetry
if self.properties['ySymmetry']:
minY = -maxY
# shift x axis
else:
shift = (minX - maxX) * self.properties['xScrollFactor'] * direction
# check limits
if self.properties['checkLimits']:
if minX + shift < rangeXmin:
shift = rangeXmin - minX
elif maxX + shift > rangeXmax:
shift = rangeXmax - maxX
minX += shift
maxX += shift
# autoscale y axis
if self.properties['autoScaleY']:
minY, maxY = self.getMaxYRange(minX, maxX)
# redraw plot
self.draw(self.lastDraw[0], (minX, maxX), (minY, maxY))
# remember new zoom
self.rememberView((minX, maxX), (minY, maxY))
# store cursor positions
self.cursorPosition[0], self.cursorPosition[1] = self.getXY(evt)
self.cursorPosition[2], self.cursorPosition[3] = evt.GetPosition()
# ----
def onChar(self, evt):
"""Set zoom or position according to pressed key."""
# get key
key = evt.GetKeyCode()
# escape current mouse events
if key == wx.WXK_ESCAPE:
self.escMouseEvents()
return
# stop if any mouse event set
elif self.mouseEvent:
return
# get direction
direction = 1
if key in (wx.WXK_RIGHT, wx.WXK_UP, wx.WXK_PAGEDOWN, wx.WXK_NEXT):
direction = -1
# get current axis and x range
minX, maxX = self.getCurrentXRange()
minY, maxY = self.getCurrentYRange()
rangeXmin, rangeXmax = self.getMaxXRange()
rangeYmin, rangeYmax = self.getMaxYRange()
# move/scale x axis by factor
if key in (wx.WXK_LEFT, wx.WXK_RIGHT):
# scale x axis
if evt.AltDown():
scale = (maxX - minX) * self.properties['xScaleFactor'] * direction
minX -= scale
maxX += scale
# check limits
if self.properties['checkLimits']:
if minX < rangeXmin:
minX = rangeXmin
if maxX > rangeXmax:
maxX = rangeXmax
# move x axis
else:
shift = (minX - maxX) * self.properties['xMoveFactor'] * direction
# check limits
if self.properties['checkLimits']:
if minX + shift < rangeXmin:
shift = rangeXmin - minX
elif maxX + shift > rangeXmax:
shift = rangeXmax - maxX
minX += shift
maxX += shift
# check max zoom
if (maxX - minX) < self.properties['maxZoom']:
return
# move x axis by page
elif key in (wx.WXK_PAGEUP, wx.WXK_PAGEDOWN, wx.WXK_PRIOR, wx.WXK_NEXT):
shift = (minX - maxX) * 1 * direction
# check limits
if self.properties['checkLimits']:
if minX + shift < rangeXmin:
shift = rangeXmin - minX
elif maxX + shift > rangeXmax:
shift = rangeXmax - maxX
minX += shift
maxX += shift
# check max zoom
if (maxX - minX) < self.properties['maxZoom']:
return
# scale y axis
elif key == wx.WXK_UP or key == wx.WXK_DOWN:
maxY += (maxY - minY) * self.properties['yScaleFactor'] * direction
# fullsize
elif key == wx.WXK_HOME and evt.ControlDown():
minX = rangeXmin
maxX = rangeXmax
minY = rangeYmin
maxY = rangeYmax
# go to plot start
elif key == wx.WXK_HOME:
diff = maxX - minX
minX = rangeXmin
maxX = rangeXmin + diff
# go to plot end
elif key == wx.WXK_END:
diff = maxX - minX
minX = rangeXmax - diff
maxX = rangeXmax
# go forth in zoom memory
elif key == wx.WXK_BACK and evt.AltDown():
if len(self.viewMemory[1]) > 0:
self.viewMemory[0].append(self.viewMemory[1][-1])
minX = self.viewMemory[1][-1][0][0]
maxX = self.viewMemory[1][-1][0][1]
minY = self.viewMemory[1][-1][1][0]
maxY = self.viewMemory[1][-1][1][1]
del self.viewMemory[1][-1]
else:
return
# go back in zoom memory
elif key == wx.WXK_BACK:
if len(self.viewMemory[0]) > 1:
self.viewMemory[1].append(self.viewMemory[0][-1])
del self.viewMemory[0][-1]
minX = self.viewMemory[0][-1][0][0]
maxX = self.viewMemory[0][-1][0][1]
minY = self.viewMemory[0][-1][1][0]
maxY = self.viewMemory[0][-1][1][1]
else:
return
else:
evt.Skip()
return
# autoscale y axis
if self.properties['autoScaleY'] and key in (wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_END, wx.WXK_HOME, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN, wx.WXK_PRIOR, wx.WXK_NEXT):
minY, maxY = self.getMaxYRange(minX, maxX)
# redraw plot
self.draw(self.lastDraw[0], (minX, maxX), (minY, maxY))
# remember new zoom
if key != wx.WXK_BACK:
self.rememberView((minX, maxX), (minY, maxY))
# ----
def getPrintout(self, filterSize, title):
"""Get printout of current plot."""
return printout(self, filterSize, title)
# ----
def getBitmap(self, width=None, height=None, printerScale=None):
"""Get current plot bitmap with selected size."""
# get width/height if not set
if not width or not height:
width, height = self.GetClientSize()
# create empty bitmap
tmpBitmap = wx.EmptyBitmap(width, height)
tmpDC = wx.MemoryDC()
tmpDC.SelectObject(tmpBitmap)
tmpDC.Clear()
# rescale plot
self.setSize(width, height)
# thicken up pens and fonts
if not printerScale:
ratioW = float(width) / 750
ratioH = float(height) / 750
scale = max(min(ratioW, ratioH), 1)
self.printerScale['drawings'] = scale
self.printerScale['fonts'] = scale
else:
self.printerScale['drawings'] = max(printerScale['drawings'], 1)
self.printerScale['fonts'] = max(printerScale['fonts'], 1)
# set filter size
filterSize = max(self.printerScale['drawings']*0.5, 0.5)
# draw plot
self.drawOutside(tmpDC, filterSize)
tmpDC.SelectObject(wx.NullBitmap)
# rescale back to original
self.setSize()
self.printerScale['drawings'] = 1
self.printerScale['fonts'] = 1
self.refresh()
return tmpBitmap
# ----
def getXY(self, evt):
"""Get XY position in user values."""
x, y = self.positionScreenToUser(evt.GetPosition())
return x, y
# ----
def getCurrentXRange(self):
"""Get current X-axis range."""
return self.lastDraw[1]
# ----
def getCurrentYRange(self):
"""Get current Y-axis range."""
return self.lastDraw[2]
# ----
def getMaxXRange(self, absolute=False):
"""Get maximal X-axis range."""
graphics = self.lastDraw[0]
p1, p2 = graphics.getBoundingBox(absolute=absolute)
return (p1[0], p2[0])
# ----
def getMaxYRange(self, minX=None, maxX=None, absolute=False):
"""Get maximal Y-axis range."""
# get bounding box
graphics = self.lastDraw[0]
p1, p2 = graphics.getBoundingBox(minX, maxX, absolute)
# check y symmetry
if self.properties['ySymmetry']:
maxY = max(abs(p1[1]), abs(p2[1]))
return (-maxY, maxY)
else:
return (p1[1], p2[1])
# ----
def getCursorLocation(self):
"""Locate cursor within the plot area."""
# get plot width/height
minX = self.plotCoords[0]
minY = self.plotCoords[1]
maxX = self.plotCoords[2]
maxY = self.plotCoords[3]
# get current position
x = self.cursorPosition[2]
y = self.cursorPosition[3]
# locate cursor
if minX < x < maxX and minY < y < maxY:
return 'plot'
elif minX < x < maxX and y > minY:
return 'xAxis'
elif minY < y < maxY and x < minX:
return 'yAxis'
else:
return 'blank'
# ----
def getCursorPosition(self):
"""Get cursor position in user coordinations."""
# check position
if self.getCursorLocation() != 'plot':
return False
# return current position
return self.cursorPosition[0], self.cursorPosition[1]
# ----
def getDistance(self):
"""Get current cursor distance."""
# check event
if self.mouseEvent != 'distance':
return False
# get distance coord in user units
x1 = self.draggingStart[0]
y1 = self.draggingStart[1]
x2 = self.cursorPosition[0]
y2 = self.cursorPosition[1]
# return distance
return [x2-x1, y2-y1]
# ----
def getCharge(self):
"""Get current charge."""
return self.currentCharge
# ----
def getIsotopes(self):
"""Get current isotopes."""
# check event
if self.mouseEvent != 'isotopes':
return False
# check position
if self.getCursorLocation() != 'plot':
return False
# return current isotopes
return self.currentIsotopes[:]
# ----
def getPoint(self, xPos=None, coord='screen'):
"""Get corresponding data point from current object and xPos."""
# check current object
if self.currentObject == None:
return None
# get corresponding point
graphics = self.lastDraw[0]
point = graphics.getPoint(self.currentObject, xPos, coord)
return point
# ----
def getSelectionBox(self):
"""Get selection rectangle coordinations."""
# check position
if not self.mouseEvent in ('rectangle', 'range'):
return False
# get coordinations
x1 = min(self.draggingStart[0], self.cursorPosition[0])
y1 = min(self.draggingStart[1], self.cursorPosition[1])
x2 = max(self.draggingStart[0], self.cursorPosition[0])
y2 = max(self.draggingStart[1], self.cursorPosition[1])
return x1, y1, x2, y2
# ----
def setSize(self, width=None, height=None):
"""Set DC width and height."""
# get size
if width == None:
(width, height) = self.GetClientSize()
# set size
self.plotBoxSize = numpy.array([width, height])
x0 = 0.5 * (width - self.plotBoxSize[0])
y0 = height - 0.5 * (height - self.plotBoxSize[1])
self.plotBoxOrigin = numpy.array([x0, y0])
# ----
def setCurrentObject(self, value):
"""Set selected data object as main."""
self.currentObject = value
# ----
def setPrinterScale(self, drawings=1, fonts=1):
"""Used to thicken lines and increase marker size for printouts."""
self.printerScale['drawings'] = drawings
self.printerScale['fonts'] = fonts
# ----
def setProperties(self, **attr):
"""Set parameters for canvas."""
for name, value in attr.items():
self.properties[name] = value
# ----
def setMFunction(self, fn=None):
"""Set cursor tracker style."""
self.mouseFn = fn
# ----
def setLMBFunction(self, fn):
"""Set function for left mouse button."""
self.mouseFnLMB = fn
# ----
def setRMBFunction(self, fn):
"""Set function for right mouse button."""
self.mouseFnRMB = fn
# ----
def setCursorImage(self, cursor):
"""Set cursor image for main plot area."""
self.cursorImage = cursor
# ----
def setCursorByLocation(self):
"""Set cursor-type according to location."""
location = self.getCursorLocation()
if location == 'xAxis':
self.SetCursor(wx.StockCursor(wx.CURSOR_SIZEWE))
elif location == 'yAxis':
self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
elif location == 'plot' and self.properties['showCurImage']:
self.SetCursor(self.cursorImage)
elif location == 'plot' and self.mouseFn:
self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
else:
self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
# ----
def draw(self, graphics, xAxis=None, yAxis=None, dc=None, filterSize=1.):
"""Draw axis and plot graphics."""
# reset tracker
self.mouseTracker = False
# set DC
if dc == None:
dc = wx.BufferedDC(wx.ClientDC(self), self.plotBuffer)
dc.SetBackground(wx.Brush(self.properties['canvasColour'], wx.SOLID))
dc.Clear()
# set dc font
dc.SetFont(_scaleFont(self.properties['axisFont'], self.printerScale['fonts']))
# get number of visible spectra
self.gelsCount = graphics.countGels()
# get lower left and upper right corners of plot
if xAxis == None or yAxis == None:
p1, p2 = graphics.getBoundingBox()
if xAxis == None:
xAxis = (p1[0], p2[0])
if yAxis == None:
yAxis = (p1[1], p2[1])
self.viewMemory[0] = [(xAxis, yAxis)]
p1 = numpy.array([xAxis[0], yAxis[0]])
p2 = numpy.array([xAxis[1], yAxis[1]])
# save most recent values
self.lastDraw = (graphics, xAxis, yAxis)
# get axis ticks
xAxisTicks = self.makeAxisTicks(xAxis[0], xAxis[1])
yAxisTicks = self.makeAxisTicks(yAxis[0], yAxis[1])
# get text extents for axis ticks
xLeft = dc.GetTextExtent(xAxisTicks[0][1])
xRight = dc.GetTextExtent(xAxisTicks[-1][1])
yBottom = dc.GetTextExtent(yAxisTicks[0][1])
yTop = dc.GetTextExtent(yAxisTicks[-1][1])
xAxisTextExtent = (xRight[0], max(xLeft[1], xRight[1]))
yAxisTextExtent = (max(yBottom[0], yTop[0]), max(yBottom[1], yTop[1]))
# get text extents for axis labels
xAxisLabelWH = dc.GetTextExtent(self.properties['xLabel'])
yAxisLabelWH = dc.GetTextExtent(self.properties['yLabel'])
# get room around graph area
spaceLeft = yAxisTextExtent[0] + yAxisLabelWH[1] + 15*self.printerScale['drawings']
spaceBottom = xAxisTextExtent[1] + xAxisLabelWH[1] + 10*self.printerScale['drawings']
spaceTop = 15
if self.properties['showXPosBar']:
spaceTop += self.properties['posBarSize'] + 6
if self.properties['showGel']:
spaceTop += self.gelsCount * self.properties['gelHeight'] + 6 + 2
spaceTop = spaceTop * self.printerScale['drawings']
spaceRight = xAxisTextExtent[0]/1.5
if self.properties['showYPosBar']:
spaceRight = max(xAxisTextExtent[0]/1.5, (self.properties['posBarSize'] + 6 + 8) * self.printerScale['drawings'])
# get scaling and shifting
textSizeScale = numpy.array([spaceRight + spaceLeft, spaceBottom + spaceTop])
textSizeShift = numpy.array([spaceLeft, spaceBottom])
scale = (self.plotBoxSize - textSizeScale) / (p2 - p1) * numpy.array((1, -1))
shift = - p1 * scale + self.plotBoxOrigin + textSizeShift * numpy.array((1, -1))
self.pointScale = scale
self.pointShift = shift
# remember axis coordinations
x, y, width, height = self.pointToClientCoord(p1, p2)
self.plotCoords = (x, y, x + width, y + height)
# crop, recalculate and filter points
graphics.cropPoints(p1[0], p2[0])
graphics.scaleAndShift(scale, shift)
graphics.filterPoints(filterSize)
# draw axis labels
xLabelPos = (self.plotBoxSize[0] - spaceRight - xAxisLabelWH[0], self.plotBoxOrigin[1] - xAxisLabelWH[1] - 3)
yLabelPos = (3, spaceTop + yAxisLabelWH[0])
dc.DrawText(self.properties['xLabel'], xLabelPos[0], xLabelPos[1])
dc.DrawRotatedText(self.properties['yLabel'], yLabelPos[0], yLabelPos[1], 90)
# draw plot axis
self.drawAxis(dc, xAxisTicks, yAxisTicks)
# draw plot x position box
if self.properties['showXPosBar']:
self.drawXPositionBar(dc, xAxis)
# draw plot y position box
if self.properties['showYPosBar']:
self.drawYPositionBar(dc, yAxis)
# draw gel
if self.properties['showGel']:
self.drawGelView(dc, graphics)
# draw data
dc.SetClippingRegion(x, y, width, height)
graphics.draw(dc, printerScale=self.printerScale, overlapLabels=self.properties['overlapLabels'], reverse=self.properties['reverseDrawing'])
dc.DestroyClippingRegion()
# draw legend
if self.properties['showLegend']:
self.drawLegend(dc, graphics)
# ----
def drawOutside(self, dc, filterSize):
"""Used for printing and exporting."""
if self.lastDraw != None:
graphics, xAxis, yAxis = self.lastDraw
self.draw(graphics, xAxis, yAxis, dc, filterSize=filterSize)
# ----
def drawAxis(self, dc, xticks, yticks):
"""Draw plot axis."""
# set pen
penWidth = self.printerScale['drawings']
dc.SetPen(wx.Pen(self.properties['axisColour'], penWidth))
dc.SetTextForeground(self.properties['axisColour'])
# get plot coordinates
plotX1, plotY1, plotX2, plotY2 = self.plotCoords
plotX1 -= penWidth
plotY1 -= penWidth
plotX2 += penWidth
plotY2 += penWidth
dc.BeginDrawing()
# fill background
dc.SetBrush(wx.Brush(self.properties['plotColour'], wx.SOLID))
dc.DrawRectangle(plotX1, plotY1, plotX2 - plotX1, plotY2 - plotY1)
dc.SetBrush(wx.TRANSPARENT_BRUSH)
# set length of tick marks
tickLength = 5 * self.printerScale['drawings']
# x axis
previous = 0
for x, label, ttype in xticks:
pt = self.pointScale * numpy.array([x, 1]) + self.pointShift
# minor ticks
if ttype == 'minor':
if self.properties['showMinorTicks']:
dc.DrawLine(pt[0], plotY2, pt[0], plotY2 + tickLength/2)
continue
# major ticks
dc.DrawLine(pt[0], plotY2, pt[0], plotY2 + tickLength)
extent = dc.GetTextExtent(label)
ori = pt[0] - extent[0] / 2
if ori > previous:
dc.DrawText(label, ori, plotY2 + tickLength*1.4)
previous = ori + extent[0] + 10*self.printerScale['drawings']
if self.properties['showGrid']:
dc.SetPen(wx.Pen(self.properties['gridColour'], penWidth))
dc.SetTextForeground(self.properties['gridColour'])
dc.DrawLine(pt[0], plotY1, pt[0], plotY2-penWidth)
dc.SetPen(wx.Pen(self.properties['axisColour'], penWidth))
dc.SetTextForeground(self.properties['axisColour'])
# y axis
previous = plotY2 + dc.GetCharHeight()
for y, label, ttype in yticks:
pt = self.pointScale * numpy.array([1, y]) + self.pointShift
# minor ticks
if ttype == 'minor':
if self.properties['showMinorTicks']:
dc.DrawLine(plotX1-penWidth, pt[1], plotX1-penWidth - tickLength/2, pt[1])
continue
# major ticks
dc.DrawLine(plotX1-penWidth, pt[1], plotX1-penWidth - tickLength, pt[1])
extent = dc.GetTextExtent(label)
ori = pt[1] - extent[1] / 2
if ori + extent[1] < previous:
dc.DrawText(label, plotX1-penWidth - extent[0] - tickLength*1.5, ori)
previous = ori + 5*self.printerScale['drawings']
if self.properties['showGrid']:
dc.SetPen(wx.Pen(self.properties['gridColour'], penWidth))
dc.SetTextForeground(self.properties['gridColour'])
dc.DrawLine(plotX1, pt[1], plotX2-penWidth, pt[1])
dc.SetPen(wx.Pen(self.properties['axisColour'], penWidth))
dc.SetTextForeground(self.properties['axisColour'])
# show zero line
if self.properties['showZero'] and float(label)==0:
dc.SetPen(wx.Pen(self.properties['axisColour'], penWidth, wx.DOT))
dc.DrawLine(plotX1, pt[1], plotX2-penWidth, pt[1])
dc.SetPen(wx.Pen(self.properties['axisColour'], penWidth))
# draw plot outline
dc.DrawRectangle(plotX1, plotY1, plotX2 - plotX1, plotY2 - plotY1)
dc.EndDrawing()
# ----
def drawLegend(self, dc, graphics):
"""Draw legend."""
# get names
names = graphics.getLegend()
# set font
dc.SetFont(_scaleFont(self.properties['axisFont'], self.printerScale['fonts']))
dc.BeginDrawing()
# draw legend
y = self.plotCoords[1] + 5 * self.printerScale['drawings']
for name in names:
# draw text
x = self.plotCoords[2] - dc.GetTextExtent(name[0])[0] - 17 * self.printerScale['drawings']
dc.SetTextForeground(name[1])
dc.DrawText(name[0], x, y)
# draw circle
x = self.plotCoords[2] - 9 * self.printerScale['drawings']
y += dc.GetTextExtent(name[0])[1]/2
pencolour = [max(i-70,0) for i in name[1]]
pen = wx.Pen(pencolour, 1*self.printerScale['drawings'], wx.SOLID)
brush = wx.Brush(name[1], wx.SOLID)
dc.SetPen(pen)
dc.SetBrush(brush)
dc.DrawCircle(x, y, 3*self.printerScale['drawings'])
# set y for next name
y += dc.GetTextExtent(name[0])[1]/2 + 2 * self.printerScale['drawings']
dc.EndDrawing()
# ----
def drawXPositionBar(self, dc, xAxis):
"""Draw position bar."""
# get plot coordinates
x1, y1, x2, y2 = self.plotCoords
x1 -= self.printerScale['drawings']
y1 = 14 * self.printerScale['drawings']
x2 += self.printerScale['drawings']
width = x2 - x1
height = self.properties['posBarSize'] * self.printerScale['drawings']
# get current position
minX, maxX = self.getMaxXRange(absolute=True)
x = x1 + (xAxis[0] - minX) * width / abs((maxX - minX))
currWidth = width * (xAxis[1] - xAxis[0]) / (maxX - minX)
currWidth = max(currWidth, 3*self.printerScale['drawings'])
# check limits
if x < x1:
currWidth -= x1 - x
x = x1
if x+currWidth > x2:
currWidth -= currWidth - (x2 - x)
if currWidth < 0:
currWidth = 0
# set pen
penWidth = self.printerScale['drawings']
dc.SetPen(wx.Pen(self.properties['axisColour'], penWidth))
dc.BeginDrawing()
# draw outline
dc.SetBrush(wx.Brush(self.properties['plotColour'], wx.SOLID))
dc.DrawRectangle(x1, y1, width, height)
# draw position
dc.SetBrush(wx.Brush(self.properties['axisColour'], wx.SOLID))
dc.DrawRectangle(x, y1, currWidth, height)
# draw outside arrows
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(wx.Brush((255, 0, 0), wx.SOLID))
size = 6*self.printerScale['drawings']
if xAxis[1] < minX:
x = x1 - 2*self.printerScale['drawings']
dc.DrawPolygon([(x, y1), (x, y1+height), (x-size, y1+height/2)])
if xAxis[0] > maxX:
x = x2 + 2*self.printerScale['drawings']
dc.DrawPolygon([(x, y1), (x, y1+height), (x+size, y1+height/2)])
dc.EndDrawing()
# ----
def drawYPositionBar(self, dc, yAxis):
"""Draw position bar."""
# get plot coordinates
x1, y1, x2, y2 = self.plotCoords
x1 = x2 + (5 + 2) * self.printerScale['drawings']
y1 -= self.printerScale['drawings']
y2 += self.printerScale['drawings']
height = y2 - y1
width = self.properties['posBarSize'] * self.printerScale['drawings']
# get current position
minY, maxY = self.getMaxYRange(absolute=True)
y = y1 - (yAxis[1] - maxY) * height / abs((maxY - minY))
currHeight = height * (yAxis[1] - yAxis[0]) / (maxY - minY)
currHeight = max(currHeight, 3*self.printerScale['drawings'])
# check limits
if y < y1:
currHeight -= y1 - y
y = y1
if y > y2:
currHeight -= y2 - y
y = y2
if y+currHeight > y2:
currHeight -= currHeight - (y2-y)
if y+currHeight < y1:
currHeight = 0
# set pen
penWidth = self.printerScale['drawings']
dc.SetPen(wx.Pen(self.properties['axisColour'], penWidth))
dc.BeginDrawing()
# draw outline
dc.SetBrush(wx.Brush(self.properties['plotColour'], wx.SOLID))
dc.DrawRectangle(x1, y1, width, height)
# draw position
dc.SetBrush(wx.Brush(self.properties['axisColour'], wx.SOLID))
dc.DrawRectangle(x1, y, width, currHeight)
# draw outside arrows
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(wx.Brush((255, 0, 0), wx.SOLID))
size = 6*self.printerScale['drawings']
if yAxis[0] > maxY:
y = y1 - 2*self.printerScale['drawings']
dc.DrawPolygon([(x1, y), (x1+width, y), (x1+width/2, y-size)])
if yAxis[1] < minY:
y = y2 + 2*self.printerScale['drawings']
dc.DrawPolygon([(x1, y), (x1+width, y), (x1+width/2, y+size)])
dc.EndDrawing()
# ----
def drawGelView(self, dc, graphics):
"""Draw spectra gelview."""
# set pen
penWidth = self.printerScale['drawings']
# get plot coordinates
plotX1, plotY1, plotX2, plotY2 = self.plotCoords
zeroY = self.pointShift[1]
# get coords
width = plotX2-plotX1
height = self.gelsCount * self.properties['gelHeight'] * self.printerScale['drawings']
gelY1 = plotY1 - height - 8 * self.printerScale['drawings']
# set clipping area
dc.SetClippingRegion(plotX1, gelY1, width, height)
# draw background
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(wx.Brush(self.properties['plotColour'], wx.SOLID))
dc.DrawRectangle(plotX1, gelY1, width, height)
# draw gels
graphics.drawGel(dc, [gelY1, plotX1, plotY1, plotX2, plotY2, zeroY], self.properties['gelHeight']*self.printerScale['drawings'], self.printerScale)
# remove the clipping area
dc.DestroyClippingRegion()
# draw outlines
plotX1 -= penWidth
gelY1 -= penWidth
width += 2*penWidth
height += 2*penWidth
dc.SetBrush(wx.TRANSPARENT_BRUSH)
dc.SetPen(wx.Pen(self.properties['axisColour'], penWidth))
dc.DrawRectangle(plotX1, gelY1, width, height)
# ----
def drawMouseTracker(self):
"""Draw selected mouse tracker."""
# draw default cross tracker
if self.mouseFn == 'cross':
self.drawCursorTracker()
# draw isotope ruler
elif self.mouseFn == 'isotoperuler':
self.drawIsotopeRuler()
# no tracker set
else:
return
# set current state
self.mouseTracker = not self.mouseTracker
# ----
def drawCursorTracker(self):
"""Draw cursor tracker"""
# get plot coords
x = self.cursorPosition[2]
y = self.cursorPosition[3]
minXPlot = self.plotCoords[0]
maxXPlot = self.plotCoords[2]
minYPlot = self.plotCoords[1]
maxYPlot = self.plotCoords[3]
minYGel = 0
maxYGel = 0
# get gel coords
if self.properties['showGel']:
minYGel = minYPlot - (9 * self.printerScale['drawings'])
maxYGel = minYGel - self.gelsCount * self.properties['gelHeight']
if wx.Platform == '__WXMAC__':
maxXPlot -= 1
maxYPlot -= 1
maxYGel += 1
# draw tracker lines
dc = wx.ClientDC(self)
dc.BeginDrawing()
dc.SetPen(wx.Pen(wx.BLACK))
dc.SetLogicalFunction(wx.INVERT)
dc.DrawLine(x, minYPlot, x, maxYPlot)
dc.DrawLine(minXPlot, y, maxXPlot, y)
if self.properties['showGel']:
dc.DrawLine(x, minYGel, x, maxYGel)
dc.SetLogicalFunction(wx.COPY)
# draw position text
if self.properties['showCurXPos'] or self.properties['showCurYPos']:
# get current x position
xFormat = '%0.'+`self.properties['xPosDigits']`+'f'
yFormat = '%0.'+`self.properties['yPosDigits']`+'f'
xText = xFormat % (self.cursorPosition[0])
if abs(self.cursorPosition[1]) > 10000:
yFormat = '%.2e'
yText = yFormat % (self.cursorPosition[1])
# get text position
dc.SetFont(self.properties['axisFont'])
xTextSize = dc.GetTextExtent(xText)
yTextSize = dc.GetTextExtent(yText)
x += 5
y1 = y - 2*xTextSize[1] - 2
y2 = y1 + xTextSize[1]
# check limits for position
xMax = max(xTextSize[0], yTextSize[0])
if x + xMax > self.plotCoords[2]:
x -= xMax + 10
if y1 < self.plotCoords[1]:
y1 += 2*xTextSize[1] + 6
y2 = y1 + xTextSize[1]
# draw text
if self.properties['showCurXPos'] and self.properties['showCurYPos']:
self.drawInvertedText(dc, xText, x, y1, self.properties['axisFont'])
self.drawInvertedText(dc, yText, x, y2, self.properties['axisFont'])
elif self.properties['showCurXPos']:
self.drawInvertedText(dc, xText, x, y1, self.properties['axisFont'])
elif self.properties['showCurYPos']:
self.drawInvertedText(dc, yText, x, y1, self.properties['axisFont'])
dc.EndDrawing()
# ----
def drawDistanceTracker(self):
"""Draw distance tracker."""
# check cursor position
if self.getCursorLocation() != 'plot':
return
# hide cursor
self.SetCursor(wx.StockCursor(wx.CURSOR_BLANK))
# get screen coordinations
x1 = self.draggingStart[2]
y1 = self.draggingStart[3]
x2 = self.cursorPosition[2]
y2 = self.cursorPosition[3]
minX = self.plotCoords[0]
minY = self.plotCoords[1]
maxX = self.plotCoords[2]
maxY = self.plotCoords[3]
# check limits
x2 = min(x2, maxX-1)
x2 = max(x2, minX)
y2 = min(y2, maxY-1)
y2 = max(y2, minY)
if wx.Platform == '__WXMAC__':
maxX -= 1
maxY -= 1
# draw tracker
dc = wx.ClientDC(self)
dc.BeginDrawing()
dc.SetLogicalFunction(wx.INVERT)
dc.SetPen(wx.Pen(wx.BLACK))
if self.mouseFnLMB == 'xDistance':
dc.DrawLine(x1, minY, x1, maxY)
if x1 != x2:
dc.DrawLine(x2, minY, x2, maxY)
dc.DrawLine(x1, y2, x2, y2)
elif self.mouseFnLMB == 'yDistance':
dc.DrawLine(minX, y1, maxX, y1)
if y1 != y2:
dc.DrawLine(minX, y2, maxX, y2)
dc.DrawLine(x2, y1, x2, y2)
dc.SetLogicalFunction(wx.COPY)
dc.EndDrawing()
# draw diff text
if self.properties['showCurDistance']:
# set font
dc.SetFont(self.properties['axisFont'])
# get distance
dist1 = self.positionScreenToUser((x1,y1))
dist2 = self.positionScreenToUser((x2,y2))
if self.mouseFnLMB == 'xDistance':
format = '%0.'+`self.properties['xPosDigits']`+'f'
distance = format % (dist2[0] - dist1[0])
textSize = dc.GetTextExtent(distance)
x = x2 + 5
y = y2 - textSize[1] - 2
elif self.mouseFnLMB == 'yDistance':
format = '%0.'+`self.properties['yPosDigits']`+'f'
distance = format % (dist2[1] - dist1[1])
textSize = dc.GetTextExtent(distance)
x = x2 + 5
y = y2 - textSize[1] - 2
# check limits
if x + textSize[0] > maxX:
x = max(x1, x2) - textSize[0] - 5
if y < minY:
y = y2 + 2
# draw text
self.drawInvertedText(dc, distance, x, y, self.properties['axisFont'])
# ----
def drawPointTracker(self):
"""Draw point tracker - follow the main plot"""
# check cursor position
if self.getCursorLocation() != 'plot':
return
# check current object
if self.currentObject == None:
return
# hide cursor
self.SetCursor(wx.StockCursor(wx.CURSOR_BLANK))
# get X coordinations
x = self.cursorPosition[2]
y = self.cursorPosition[3]
minY = self.plotCoords[1]
maxY = self.plotCoords[3]
# get Y value
currentY = self.getPoint(x, coord='screen')
# draw tracker lines
dc = wx.ClientDC(self)
dc.BeginDrawing()
dc.SetPen(wx.Pen(wx.BLACK))
dc.SetLogicalFunction(wx.INVERT)
if wx.Platform == '__WXMAC__':
dc.DrawLine(x, minY, x, maxY-1)
if currentY:
dc.DrawLine(x-5, currentY[1], x+6, currentY[1])
else:
if currentY:
dc.DrawLine(x, minY, x, maxY)
dc.DrawLine(x-5, currentY[1], x+6, currentY[1])
dc.SetLogicalFunction(wx.COPY)
dc.EndDrawing()
# draw x position text
if self.properties['showCurXPos']:
# get current x position
format = '%0.'+`self.properties['xPosDigits']`+'f'
text = format % (self.cursorPosition[0])
# get text position
dc.SetFont(self.properties['axisFont'])
textSize = dc.GetTextExtent(text)
x = x + 5
y = y - textSize[1] - 2
# check limits for position
if x + textSize[0] > self.plotCoords[2]:
x = x - textSize[0] - 10
if y < self.plotCoords[1]:
y = self.plotCoords[1]
# draw text
self.drawInvertedText(dc, text, x, y, self.properties['axisFont'])
# ----
def drawIsotopeRuler(self):
"""Draw charge ruler."""
# check cursor position
if self.getCursorLocation() != 'plot':
return
# get plot coords
x = self.cursorPosition[2]
y = self.cursorPosition[3]
minXPlot = self.plotCoords[0]
maxXPlot = self.plotCoords[2]
minYPlot = self.plotCoords[1]
maxYPlot = self.plotCoords[3]
minYGel = 0
maxYGel = 0
# get gel coords
if self.properties['showGel']:
minYGel = minYPlot - (9 * self.printerScale['drawings'])
maxYGel = minYGel - self.gelsCount * self.properties['gelHeight']
if wx.Platform == '__WXMAC__':
maxXPlot -= 1
maxYPlot -= 1
maxYGel += 1
# calc isotopes
isotopes = []
self.currentIsotopes = []
mz = self.cursorPosition[0]
lines = max(3, int(mz/300 * self.currentCharge/2))
if self.currentIsotopeLines < 0 and abs(self.currentIsotopeLines) >= lines:
self.currentIsotopeLines = -1*lines + 1
lines += self.currentIsotopeLines
diff = self.properties['isotopeDistance'] / self.currentCharge
for i in range(lines):
self.currentIsotopes.append(mz)
isotope = self.positionUserToScreen((mz, 0))[0]
intensity = None
if self.currentObject:
point = self.getPoint(isotope, coord='screen')
if point != None:
intensity = min(point[1], maxYPlot-5)
intensity = max(intensity, minYPlot+5)
if isotope < maxXPlot:
isotopes.append((isotope, intensity))
mz += diff
# set pen
dc = wx.ClientDC(self)
dc.BeginDrawing()
dc.SetLogicalFunction(wx.INVERT)
dc.SetPen(wx.Pen(wx.BLACK))
# draw lines
for i, isotope in enumerate(isotopes):
if i == 0 or not isotope[1]:
dc.DrawLine(isotope[0], minYPlot, isotope[0], maxYPlot)
if self.properties['showGel']:
dc.DrawLine(isotope[0], minYGel, isotope[0], maxYGel)
# draw circles
if wx.Platform != '__WXMAC__':
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(wx.Brush(wx.BLACK))
for i, isotope in enumerate(isotopes):
if isotope[1]:
dc.DrawCircle(isotope[0], isotope[1], 4)
dc.SetLogicalFunction(wx.COPY)
# draw position text
if self.properties['showCurCharge']:
chargeText = str(self.currentCharge)
# get text position
dc.SetFont(self.properties['axisFont'])
textSize = dc.GetTextExtent(chargeText)
x -= textSize[0] + 5
y -= textSize[1] + 5
# check limits
if x < minXPlot:
x += textSize[0] + 10
if y < minYPlot:
y += textSize[1] + 10
# draw text
self.drawInvertedText(dc, chargeText, x, y, self.properties['axisFont'])
dc.EndDrawing()
# ----
def drawZoomBox(self):
"""Draw zoom-box"""
# get coordinations
minX = self.draggingStart[2]
minY = self.draggingStart[3]
maxX = self.cursorPosition[2]
maxY = self.cursorPosition[3]
minXPlot = self.plotCoords[0]
minYPlot = self.plotCoords[1]
maxXPlot = self.plotCoords[2]
maxYPlot = self.plotCoords[3]
# check limits
minX = min(minX, maxXPlot)
minX = max(minX, minXPlot)
minY = min(minY, maxYPlot)
minY = max(minY, minYPlot)
maxX = min(maxX, maxXPlot)
maxX = max(maxX, minXPlot)
maxY = min(maxY, maxYPlot)
maxY = max(maxY, minYPlot)
# get gel coords
if self.properties['showGel']:
minYGel = minYPlot - (8 * self.printerScale['drawings'])
maxYGel = minYGel - self.gelsCount * self.properties['gelHeight']
# set canvas and pen
dc = wx.ClientDC(self)
dc.BeginDrawing()
dc.SetPen(wx.Pen(wx.BLACK))
dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID))
dc.SetLogicalFunction(wx.INVERT)
# draw clasic zoom box
if self.properties['zoomAxis'] == 'xy':
dc.DrawRectangle(minX, minY, maxX - minX, maxY-minY)
# draw X-axis-zoom-only box
elif self.properties['zoomAxis'] == 'x':
dc.DrawRectangle(minX, maxYPlot, maxX - minX, minYPlot - maxYPlot)
# draw Y-axis-only zoom box
elif self.properties['zoomAxis'] == 'y':
dc.DrawRectangle(minXPlot, maxY, maxXPlot - minXPlot, minY - maxY)
# draw gellview zoombox
if self.properties['showGel'] and self.properties['zoomAxis'] == 'x':
minYGel += self.printerScale['drawings']
maxYGel -= self.printerScale['drawings']
dc.DrawRectangle(minX, maxYGel, maxX - minX, minYGel-maxYGel)
# resset canvas and pen
dc.SetLogicalFunction(wx.COPY)
dc.EndDrawing()
# ----
def drawSelectionRect(self):
"""Draw selection rectangle"""
# get coordinations
x1 = self.draggingStart[2]
y1 = self.draggingStart[3]
x2 = self.cursorPosition[2]
y2 = self.cursorPosition[3]
# check stop position limits
if x2 < self.plotCoords[0]:
x2 = self.plotCoords[0]
elif x2 > self.plotCoords[2]:
x2 = self.plotCoords[2] - 1
if y2 < self.plotCoords[1]:
y2 = self.plotCoords[1]
elif y2 > self.plotCoords[3]:
y2 = self.plotCoords[3] - 1
# get width/height of zoom-box
width = x2 - x1
height = y2 - y1
# draw tracker lines
dc = wx.ClientDC(self)
dc.BeginDrawing()
dc.SetLogicalFunction(wx.INVERT)
dc.SetPen(wx.Pen(wx.BLACK))
dc.SetBrush(wx.Brush(wx.BLACK, wx.TRANSPARENT))
dc.DrawRectangle(x1, y1, width, height)
dc.SetLogicalFunction(wx.COPY)
dc.EndDrawing()
# ----
def drawSelectionRange(self):
"""Draw selection range line"""
# get coordinations
x1 = self.draggingStart[2]
y1 = self.draggingStart[3]
x2 = self.cursorPosition[2]
# check stop position limits
if x2 < self.plotCoords[0]:
x2 = self.plotCoords[0]
elif x2 > self.plotCoords[2]:
x2 = self.plotCoords[2] - 1
if x2 < x1:
x1, x2 = x2, x1
# draw tracker lines
dc = wx.ClientDC(self)
dc.BeginDrawing()
dc.SetLogicalFunction(wx.INVERT)
dc.SetPen(wx.Pen(wx.BLACK))
dc.SetBrush(wx.Brush(wx.BLACK, wx.TRANSPARENT))
if wx.Platform == '__WXMAC__':
dc.DrawLine(x1, y1-3, x1, y1+3)
dc.DrawLine(x1+1, y1, x2-1, y1)
dc.DrawLine(x2, y1-3, x2, y1+3)
else:
dc.DrawLine(x1, y1-3, x1, y1+3)
dc.DrawLine(x1+1, y1, x2, y1)
dc.DrawLine(x2, y1-3, x2, y1+3)
dc.SetLogicalFunction(wx.COPY)
dc.EndDrawing()
# ----
def drawPointArrow(self, x, y, direction='up'):
"""Draw point arrow"""
# check stop position limits
if x < self.plotCoords[0]:
x = self.plotCoords[0]
direction = 'left'
elif x > self.plotCoords[2]:
x = self.plotCoords[2] - 1
direction = 'right'
# shift y position
y += 1
# set dc
dc = wx.BufferedDC(wx.ClientDC(self), self.plotBuffer)
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(wx.Brush(self.properties['highlightColour'], wx.SOLID))
# draw arrow
if direction == 'up':
dc.DrawPolygon([(x, y), (x-3, y+7), (x+3, y+7)])
elif direction == 'down':
dc.DrawPolygon([(x, y), (x-3, y-7), (x+3, y-7)])
elif direction == 'left':
dc.DrawPolygon([(x+7, y+2), (x, y+5), (x+7, y+8)])
elif direction == 'right':
dc.DrawPolygon([(x-7, y+2), (x, y+5), (x-7, y+8)])
dc.EndDrawing()
# ----
def drawInvertedText(self, dc, text, x, y, font):
"""Special function for drawing inverted text"""
# init dc
textDC = wx.MemoryDC()
textDC.SetFont(font)
# get size (get from dc - textDC does not work under Mac)
dc.SetFont(font)
size = dc.GetTextExtent(text)
# make tmp bitmap
tmpBuffer = wx.EmptyBitmap(size[0], size[1])
textDC.SelectObject(tmpBuffer)
# draw under mac
if wx.Platform == '__WXMAC__':
textDC.SetTextForeground(wx.WHITE)
textDC.SetLogicalFunction(wx.INVERT)
textDC.BeginDrawing()
textDC.DrawText(text, 0, 0)
textDC.EndDrawing()
textDC.SetLogicalFunction(wx.COPY)
dc.Blit(x, y, size[0], size[1], textDC, 0, 0, wx.INVERT)
# draw under others
else:
textDC.Clear()
textDC.BeginDrawing()
textDC.DrawText(text, 0, 0)
textDC.EndDrawing()
dc.Blit(x, y, size[0], size[1], textDC, 0, 0, wx.EQUIV)
# ----
def refresh(self, fullsize=False):
"""Redraw plot with the same data and same scale or fullsize"""
# get last ranges
graphics = self.lastDraw[0]
xAxis, yAxis = self.lastDraw[1], self.lastDraw[2]
# check limits
if self.properties['checkLimits']:
minX, maxX = self.getMaxXRange()
if minX= 0:
digits = max(1, int(power))
format = '%' + `digits`+'.0f'
else:
digits = -int(power)
format = '%'+`digits+2`+'.'+`digits`+'f'
# make ticks
t = -majorGrid*numpy.floor(-lower/majorGrid) - 5*minorGrid
i = -5
while t < lower:
t = round(t + minorGrid, rnd)
i += 1
ticks = []
while t rangeXmax):
return
if axis == 'y' and (minY < rangeYmin or maxY > rangeYmax):
return
# autoscale Y
if self.properties['autoScaleY'] and axis == 'x':
minY, maxY = self.getMaxYRange(minX, maxX)
# redraw plot
self.draw(self.lastDraw[0], (minX, maxX), (minY, maxY))
# ----
def scaleAxis(self, axis):
"""Scale plot while dragging"""
# get coordination
minX, maxX = self.getCurrentXRange()
minY, maxY = self.getCurrentYRange()
# scale x axis from its start
if axis == 'x':
shift = self.draggingStart[0] - self.cursorPosition[0]
maxX += shift
# check max zoom
if (maxX - minX) < self.properties['maxZoom']:
maxX = minX + self.properties['maxZoom']
# check limits
if self.properties['checkLimits']:
rangeXmin, rangeXmax = self.getMaxXRange()
if minX < rangeXmin:
minX = rangeXmin
if maxX > rangeXmax:
maxX = rangeXmax
# autoscale y
if self.properties['autoScaleY']:
minY, maxY = self.getMaxYRange(minX, maxX)
# scale y axis from its start
elif axis == 'y':
shift = self.draggingStart[1] - self.cursorPosition[1]
if self.properties['ySymmetry'] and self.draggingStart[1] < 0:
minY += shift
maxY -= shift
elif self.properties['ySymmetry']:
minY -= shift
maxY += shift
else:
maxY += shift
# redraw plot
self.draw(self.lastDraw[0], (minX, maxX), (minY, maxY))
# ----
def rememberView(self, xAxis=None, yAxis=None):
""" Remember current zoom. """
# get axis
if xAxis == None:
xAxis = self.getCurrentXRange()
if yAxis == None:
yAxis = self.getCurrentYRange()
# remember current zoom
if not self.viewMemory[0] or self.viewMemory[0][-1] != (xAxis, yAxis):
self.viewMemory[0].append((xAxis, yAxis))
# check max memory length
if len(self.viewMemory[0]) > 50:
del self.viewMemory[0][0]
# delete forth views
self.viewMemory[1] = []
# ----
def positionUserToScreen(self, userPos):
"""Convert user position to screen coordinates"""
userPos = numpy.array(userPos)
x, y = userPos * self.pointScale + self.pointShift
return x, y
# ----
def positionScreenToUser(self, screenPos):
"""Convert screen position to user coordinates"""
screenPos = numpy.array(screenPos)
x, y = (screenPos - self.pointShift) / self.pointScale
return x, y
# ----
def pointToClientCoord(self, corner1, corner2):
"""Convert user coords to client screen coords x,y,width,height"""
c1 = numpy.array(corner1)
c2 = numpy.array(corner2)
# convert to screen coords
pt1 = c1 * self.pointScale + self.pointShift
pt2 = c2 * self.pointScale + self.pointShift
# make height and width positive
pointUpperLeft = numpy.minimum(pt1, pt2)
pointLowerRight = numpy.maximum(pt1, pt2)
rectWidth, rectHeight = pointLowerRight - pointUpperLeft
pointX, pointY = pointUpperLeft
return round(pointX), round(pointY), round(rectWidth), round(rectHeight)
# ----
class printout(wx.Printout):
"""Controls how the plot is made in printing and previewing."""
def __init__(self, graph, filterSize, title="mMass Spectrum"):
wx.Printout.__init__(self, title)
self.graph = graph
self.filterSize = filterSize
# ----
def HasPage(self, page):
if page == 1:
return True
else:
return False
# ----
def GetPageInfo(self):
"""Disable page numbers."""
return (1, 1, 1, 1)
# ----
def OnPrintPage(self, page):
"""Get and format data to print."""
# get DC
dc = self.GetDC()
dcSize = dc.GetSize()
# get page
PPIPrinter = self.GetPPIPrinter()
pageSize = self.GetPageSizePixels()
# calculate offset and scale for dc
pixLeft = PPIPrinter[0]/ 25.4 # mm*(dots/in)/(mm/in)
pixRight = PPIPrinter[0] / 25.4
pixTop = PPIPrinter[1] / 25.4
pixBottom = PPIPrinter[1] / 25.4
plotAreaW = pageSize[0] - (pixLeft + pixRight)
plotAreaH = pageSize[1] - (pixTop + pixBottom)
# ratio offset and scale to screen size if preview
if self.IsPreview():
ratioW = float(dcSize[0]) / pageSize[0]
ratioH = float(dcSize[1]) / pageSize[1]
pixLeft *= ratioW
pixTop *= ratioH
plotAreaW *= ratioW
plotAreaH *= ratioH
self.filterSize = 1
# rescale plot to page or preview plot area
self.graph.setSize(plotAreaW, plotAreaH)
# set offset and scale
dc.SetDeviceOrigin(pixLeft, pixTop)
# thicken up pens and fonts for printing
ratioW = float(plotAreaW) / 900
ratioH = float(plotAreaH) / 900
scale = min(ratioW, ratioH)
if not self.IsPreview():
scale = max(scale, 2.5)
self.graph.setPrinterScale(drawings=scale, fonts=scale)
# print plot
self.graph.drawOutside(dc, self.filterSize)
# revert all back to original
self.graph.setSize()
self.graph.setPrinterScale()
self.graph.refresh()
return True
# ----
# HEPLERS
# -------
def _scaleFont(font, scale):
"""Scale font for printing"""
# check scale
if scale == 1:
return font
# get font
pointSize = font.GetPointSize()
family = font.GetFamily()
style = font.GetStyle()
weight = font.GetWeight()
underline = font.GetUnderlined()
faceName = font.GetFaceName()
encoding = font.GetDefaultEncoding()
# scale pointSize
pointSize = pointSize * scale * 1.3
# make print font
printerFont = wx.Font(pointSize, family, style, weight, underline, faceName, encoding)
return printerFont
# ----