DNApy
DNApy copied to clipboard
Plasmid view
The layout of labels in plasmid view looks rather horrible and needs fixing. This is mainly a problem when there are very many labels. Much too many labels will also throw an error.
Also the rendering of the plasmid can be improved. It has some rough edges. Is there a better way to draw arcs with arrowheads in wxpython? Or should we use a grafics implementation like cairo?
I just did some research on the topic, because I think that we should try to get the plasmid view more beautiful. And as soon as we decided how to do it there, maybe we can also make the dna view panel as a drawing, allowing to dra restriction sites, labelnames and colors to the dna code.
I just got my hands on cairo and wanted to see if drawing is different with it. The coding is fairly simple and the results look good. I will see if I can make a working example to compare wxpython to cairo.
As of now I can say I like the rendering of the arrows. it is a little smoother, because cairo can draw arcs nativly.
What do you say? Cairo enables also easy zooming and export of images as .svg or .png
I agree that the rendering with GCDC is far from ideal. At some point I was researching other options (including Cairo) but I never went through with it because I was not quite sure how to manage the interactivity when using Cairo. The ability to export as .svg is also something that I would really like to implement. What is the portability of Cairo? Does it work well on Windows?
I've also been thinking that the DNA view would be much more beautiful if drawn as well. I did find the task daunting at the time tough and kicked the can down the road.
So, in summary, if you know how to draw in Cairo and make the drawing interactive I'm all for it! You could start with the plasmid view and if it goes well we could take it from there.
Sounds good. I will see if I can make it as interactive as the GCDC version.
As of now I started implementing to draw the features and that works very well.
As of tomorrow I will have to attent a class, so it might take a while for me (depending how difficult the class will be)
I'll let you know as soon, as I know something new.
Seems like getting the object in cairo really is kind of impossible. Sadly...
I will keep my eyes open if there is a way, but I guess we'll have to test another libary.
Was the interactivity the problem? I was toying with the idea of converting the "visible" GCDC to a Cairo rendering and leaving the "hidden" GCDC (that handles the interactivity) untouched. That way the mouse movements and clicks could be captured and used to influence the Cairo drawing. I think the resulting code would be horrible though since every object will be drawn using two different libraries.
I have looked into a lot of different things. It's rather frustrating because it seems in web browsers dealing with interactivity in .svg files is fairly straight forward. Converting the whole thing to a web app does not seem to be the way to go though.
Yes, I was thinking about this approach too. But I guess it will be very messy, as there is no way of really converting objects from GCDC to cairo. Also both plasmids must match 100%, which is very difficult to achive, I think.
It would be possible to make a webkit window and just draw there. I guess thats fairly easy. I used to develop a html5 plasmid viewer (https://github.com/openpaul/html5plasmid) but never followed trough, cause the performance of JS is not as good as python (opening gb-Files works not so well, plasmid view is not good implementet, but it works). But I guess making an WebKit window displaying svg and use JS to make it responsive would be a way to go.
To you know of any other (grafics) libarys? So far I know (have read about) TKinker, wxpython, qt4 or qt5 and cairo. All of them are cross platform, not all of them have antialiasing.
cairo has a hittest function, alright. So according to this documentation we just would have to save every path or object and then loop trough them whenever there is a click or mouseover. That could work and I will try it on sunday with my example code to see how its performance is.
http://cairographics.org/hittestpython/
That would be awesome! Hope it works well. Having everything look pretty is so important. No-one likes an ugly program.
So, just that you can try it yourself here is my working example code for mouseover. It gets highlighted and looks ok.
The code right now is very bad, because I just learned cairo with it. I will do some cleaning up and then show it again, but I thought maybe you already want to see that it works.
Just save this as python and run it directly via command line. Its a modified plasmid_GUI.py file.
#!/usr/bin/env python
#This file is part of DNApy. DNApy is a DNA editor written purely in python.
#The program is intended to be an intuitive, fully featured,
#extendable, editor for molecular and synthetic biology.
#Enjoy!
#
#Copyright (C) 2014 Martin Engqvist |
#
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#LICENSE:
#This file is part of DNApy.
#
#DNApy 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.
#
#DNApy 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 Library General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software Foundation,
#Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
#Get source code at: https://github.com/0b0bby0/DNApy
#
#TODO
#fix long plasmid names
#add 'dna ruler'
#add rightclick menus
import wx
import cairo
from wx.lib.wxcairo import ContextFromDC
import genbank
import copy
import math
import os, sys
import string
from base_class import DNApyBaseDrawingClass
from base_class import DNApyBaseClass
import featureedit_GUI
import colcol
files={} #list with all configuration files
files['default_dir'] = os.path.abspath(os.path.dirname(sys.argv[0]))+"/"
files['default_dir']=string.replace(files['default_dir'], "\\", "/")
files['default_dir']=string.replace(files['default_dir'], "library.zip", "")
settings=files['default_dir']+"settings" ##path to the file of the global settings
execfile(settings) #gets all the pre-assigned settings
class PlasmidView(DNApyBaseDrawingClass):
highlighted_feature = False
genbank.search_hits = []
label_type = 'circular'
def __init__(self, parent, id):
# hit test variable
self.hittest = {}
self.Highlight = None
# initialise the window
DNApyBaseDrawingClass.__init__(self, parent, wx.ID_ANY)
genbank.dna_selection = (1,1)
#self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
#self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
#self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
self.Bind(wx.EVT_MOTION, self.OnMotion)
#self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDouble)
############ Setting required methods ####################
def update_globalUI(self):
'''
Method should be modified as to update other panels in response to changes in own panel.
'''
pass
def update_ownUI(self):
"""
This would get called if the drawing needed to change, for whatever reason.
The idea here is that the drawing is based on some data generated
elsewhere in the system. If that data changes, the drawing needs to
be updated.
This code re-draws the buffer, then calls Update, which forces a paint event.
"""
dc = wx.MemoryDC()
dc.SelectObject(self._Buffer)
self.DrawCairo(dc)
dc.SelectObject(wx.NullBitmap) # need to get rid of the MemoryDC before Update() is called.
self.Refresh()
self.Update()
def set_dna_selection(self, selection):
'''Receives requests for DNA selection and then sends it.'''
assert type(selection) == tuple, 'Error, dna selection must be a tuple'
selection = (int(selection[0]-1), int(selection[1]))
genbank.dna_selection = selection
############### Done setting required methods #######################
def find_overlap(self, drawn_locations, new_range):
'''
Takes two ranges and determines whether the new range has overlaps with the old one.
If there are overlaps the overlap locations are returned.
This is used when drawing features. If two features overlap I want them drawn on different levels.
'''
assert type(drawn_locations) == list
assert type(new_range) == tuple
if drawn_locations == []:
drawn_locations.append([new_range])
return drawn_locations, 0
else:
i = 0
while i < len(drawn_locations):
overlap_found = False
for n in range(0,len(drawn_locations[i])):
if drawn_locations[i][n][0]<=new_range[0]<=drawn_locations[i][n][1] or drawn_locations[i][n][0]<=new_range[1]<=drawn_locations[i][n][1]: #if they overlap
overlap_found = True
elif new_range[0]<=drawn_locations[i][n][0]<=new_range[1] or new_range[0]<=drawn_locations[i][n][1]<=new_range[1]: #if they overlap
overlap_found = True
if overlap_found == False:
drawn_locations[i].append(new_range)
return drawn_locations, i
break
elif i+1==len(drawn_locations):
drawn_locations.append([new_range])
return drawn_locations, i+1
break
i += 1
def DrawCairo(self, dc):
self.centre_x = self.size[0]/2 #centre of window in x
self.centre_y = self.size[1]/2 #centro of window in y
self.min_centre = min(self.centre_x, self.centre_y)
self.Radius = min(self.size[0], self.size[1])/3 - self.min_centre/8 #the last one is the label line length
# dc.SetDeviceOrigin(size_x/2, size_y/2)
dc.SetBackground(wx.Brush("White"))
dc.Clear() # make sure you clear the bitmap!
self.ctx = ContextFromDC(dc)
#ctx = cairo.Context (surface)
canvasWidth = self.size[0]
canvasHeight = self.size[1]
ratio = canvasHeight/canvasWidth
print canvasWidth/100
self.ctx.scale(canvasWidth, canvasWidth*ratio) # Normalizing the canvas
self.ctx.scale(0.01, 0.01) # Normalizing the canvas
#pat = cairo.LinearGradient (0.0, 0.0, 0.0, 1.0)
#pat.add_color_stop_rgba (1, 0.7, 0, 0, 0.5) # First stop, 50% opacity
#pat.add_color_stop_rgba (0, 0.9, 0.7, 0.2, 1) # Last stop, 100% opacity
# white background
self.ctx.rectangle (0, 0, 1, 1) # Rectangle(x0, y0, x1, y1)
self.ctx.set_source_rgb (255,255,255)
self.ctx.fill ()
self.ctx.translate (50,50) # Changing the current transformation matrix
radius = 30
#ctx.move_to (0.2, 0.2)
#ctx.arc (0.2, 0.1, 0.1, -math.pi/2, 0) # Arc(cx, cy, radius, start_angle, stop_angle)
#ctx.line_to (0.5, 0.1) # Line to (x,y)
#ctx.curve_to (0.5, 0.2, 0.5, 0.4, 0.2, 0.8) # Curve(x1, y1, x2, y2, x3, y3)
#ctx.close_path ()
# draw the plasmid
self.ctx.arc(0,0,radius+0.4,0,2*math.pi)
self.ctx.set_source_rgb (0, 0, 0) # Solid color
self.ctx.set_line_width (0.4)
self.ctx.stroke()
# inner plasmid
self.ctx.arc(0,0,radius-0.4,0,2*math.pi)
self.ctx.stroke()
#draw plasmid name
self.drawCairoPlasmidName(self.ctx)
# draw features
self.drawCairoFeatures(self.ctx, radius)
#gcdc = wx.GCDC(dc)
return True
def radial2cartesian(self,radius, angle, cx=0,cy=0,width=1, height=1):
x = radius * math.cos(angle) + cx
y = radius * math.sin(angle) + cy
return x,y
def drawCairoFeatures(self, ctx, radius):
# set some variables
featurelist = genbank.gb.get_all_feature_positions()
self.feature_catalog = {} #for matching features with the unique colors
self.feature_catalog['(255, 255, 255, 255)'] = False #the background is white, have to add that key
drawn_fw_locations = [] #for keeping track of how many times a certain region has been painted on
drawn_rv_locations = [] #for keeping track of how many times a certain region has been painted on
arrow_thickness = 2
radius_change = arrow_thickness/2
lev = arrow_thickness + 0.5
arrow_head_length = 2.5 # in degree
length = float(len(genbank.gb.GetDNA()))
degreeMult = float(360/length)
self.unique_color = (0, 0, 0)
outI = 1
inI = 1
for i in range(1,len(featurelist)):
self.feature_catalog[str(self.NextRGB()+(255,))] = i #add to catalog with new RGB color
featuretype, complement, start, finish, name, index = featurelist[i]
# variable to save as a hittest
hittestName = "%s%s%s" % (name, index, start)
self.hittest[hittestName] = []
# highlitght?
if self.Highlight == hittestName:
bordercolor = [0, 0.5, 1]
borderwidth = 0.5
else:
bordercolor = [0, 0, 0 ]
borderwidth = 0.1
#print "new index: ",name, start, finish
#print name
# draw foreward features
if complement == False:
drawn_fw_locations, levelMult = self.find_overlap(drawn_fw_locations, (start, finish))
if levelMult>3 or finish-start<=3: #Only allow for tree levels. Also, for very short features, draw them at bottom level
levelMult = 0
level = lev * (1+levelMult)
angle_start = float(start) * degreeMult # in degree
angle_stop = float(finish) * degreeMult
angle_start_rad = (angle_start * math.pi/180) - math.pi/2
angle_end_rad = (angle_stop * math.pi/180) - math.pi/2
angle_end_rad_without_head = ((angle_stop-arrow_head_length) * math.pi/180) - math.pi/2
angle_end_rad_without_headD = ((angle_stop-2*arrow_head_length) * math.pi/180) - math.pi/2
# weather to draw a head or not
drawHead = True
if angle_end_rad_without_headD < angle_start_rad:
angle_end_rad_without_head = angle_end_rad
drawHead = False
arrow_head_x, arrow_head_y = self.radial2cartesian(radius+level, angle_end_rad)
arrow_head_inv_x, arrow_head_inv_y = self.radial2cartesian(radius+level, angle_start_rad)
# get the color:
color = eval(featuretype)['fw'] #get the color of feature (as string)
assert type(color) == str
# move to start
x_start, y_start = self.radial2cartesian(radius+level+radius_change, angle_start_rad)
x_end, y_end = self.radial2cartesian(radius+level+radius_change, angle_end_rad)
self.ctx.move_to(x_start, y_start)
self.ctx.arc(0, 0, radius+level+radius_change,angle_start_rad, angle_end_rad_without_head);
if drawHead:
ctx.line_to(arrow_head_x,arrow_head_y)
self.ctx.arc_negative(0, 0, radius+level-radius_change, angle_end_rad_without_head, angle_start_rad);
self.ctx.close_path ()
else:
drawn_rv_locations, levelMult = self.find_overlap(drawn_rv_locations, (start, finish))
if levelMult>3 or finish-start<=3: #Only allow for tree levels. Also, for very short features, draw them at bottom level
levelMult = 0
level = -lev * (1 + levelMult)
angle_start = float(finish) * degreeMult # in degree
angle_stop = float(start) * degreeMult
angle_start_rad = (angle_start * math.pi/180) - math.pi/2
angle_end_rad = (angle_stop * math.pi/180) - math.pi/2
angle_end_rad_without_head = ((angle_stop+arrow_head_length) * math.pi/180) - math.pi/2
angle_end_rad_without_headD = ((angle_stop+2*arrow_head_length) * math.pi/180) - math.pi/2
# weather to draw a head or not
drawHead = True
#print angle_start_rad*180/math.pi, angle_end_rad*180/math.pi,angle_end_rad_without_head*180/math.pi
if angle_end_rad_without_headD > angle_start_rad:
angle_end_rad_without_head = angle_end_rad
drawHead = False
arrow_head_x, arrow_head_y = self.radial2cartesian(radius+level, angle_end_rad)
arrow_head_inv_x, arrow_head_inv_y = self.radial2cartesian(radius+level, angle_start_rad)
# get the color:
color = eval(featuretype)['rv'] #get the color of feature (as string)
assert type(color) == str
# move to start
x_start, y_start = self.radial2cartesian(radius+level+radius_change, angle_start_rad)
x_end, y_end = self.radial2cartesian(radius+level+radius_change, angle_end_rad)
#print name, complement,start, finish, angle_start, angle_stop, angle_start_rad, angle_end_rad, angle_end_rad_without_head,arrow_head_x,arrow_head_y
self.ctx.move_to(x_start, y_start);
self.ctx.arc_negative(0, 0, radius+level-radius_change,angle_start_rad, angle_end_rad_without_head);
if drawHead:
ctx.line_to(arrow_head_x,arrow_head_y)
#ctx.line_to(arrow_head_inv_x,arrow_head_inv_y)
#ctx.line_to(x_end, y_end)
self.ctx.arc(0, 0, radius+level+radius_change,angle_end_rad_without_head,angle_start_rad);
#ctx.close_path ()
# TODO:
# clean the code, so the arrow drawing part is more understandable
r,g,b = colcol.hex_to_rgb(color)
r = float(r)/255
g = float(g)/255
b = float(b)/255
#print color
#print r,g,b
self.ctx.set_source_rgba (r,g,b,1.0) # Solid color
self.ctx.set_line_width (borderwidth) # or 0.1
self.ctx.fill_preserve ()
self.ctx.set_source_rgb (bordercolor[0],bordercolor[1],bordercolor[2])
# save feature to hittest:
self.hitpath = self.ctx.copy_path()
self.hittest[hittestName].append(self.hitpath)
self.ctx.stroke()
# TODO:
# highlighted fargemnts should be drawn last to be on top!
#print start
return True
def drawCairoPlasmidName(self, ctx):
'''Draw the plasmid name and basepairs in the middle'''
width = 100
height = 100
self.hittest['plasmidname'] = []
name = genbank.gb.fileName.split('.')[0]
basepairs = '0 bp'
if genbank.gb.GetDNA() != None:
basepairs = str(len(genbank.gb.GetDNA())) + ' bp'
# name
self.ctx.select_font_face('Arial', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
self.ctx.set_font_size(2.5);
xbearing, ybearing, TextWidth, TextHeight, xadvance, yadvance = self.ctx.text_extents(name)
self.ctx.move_to(-TextWidth/2, 0);
# save feature to hittest:
#self.hitpath = self.ctx.copy_path()
#self.hittest['plasmidname'].append(self.hitpath)
self.ctx.show_text(name);
# bp
self.ctx.select_font_face('Arial', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
self.ctx.set_font_size(2);
xbearing, ybearing, TextWidth, TextHeight, xadvance, yadvance = self.ctx.text_extents(basepairs)
self.ctx.move_to(-TextWidth/2, 2.7);
self.ctx.show_text(basepairs);
# if we want to rotate, we could make that here or so:
# self.ctx.rotate(1)
#print self.ctx.identity_matrix()
return True
############### Setting methods for interconverting angles to dna positions ##############
def angle_to_pos(self, angle):
'''Convert an angle of a circle to a DNA position'''
len_dna = float(len(genbank.gb.GetDNA()))
dna_pos = int(self.AngleToFraction(angle)*len_dna)
return dna_pos
def pos_to_angle(self, pos):
'''Calculate angles from DNA positions'''
assert type(pos) == int, 'Error, position needs to be an integer'
len_dna = float(len(genbank.gb.GetDNA()))
if len_dna == 0:
angle = 0
else:
angle = self.FractionToAngle(pos/float(len_dna))
return angle
########## Done with angle to dna methods ####################
######### Mouse methods #####################
def HitTest(self):
'''Tests whether the mouse is over any feature or label'''
hit = None
x, y = self.ScreenToClient(wx.GetMousePosition())
# get the mouse positions
x2, y2 = self.ctx.device_to_user(x,y)
for path in self.hittest:
cairoCtx = self.ctx
for i in self.hittest[path]:
# load the path
self.ctx.append_path(i)
result = cairoCtx.in_fill(x2,y2)
# check if this path is hit
if result == True:# or self.ctx.in_stroke(x2,y2):
hit = path
#print "True: ",x2, y2, path, cairoCtx.in_fill(x2,y2)
#break
#else:
#print "False!", result
#
self.ctx.fill()
return hit
def OnLeftDown(self, event):
'''When left mouse button is pressed down, store angle at which this happened.'''
self.centre_x = self.size[0]/2 #centre of window in x
self.centre_y = self.size[1]/2 #centro of window in y
x, y = self.ScreenToClient(wx.GetMousePosition())
angle = self.PointsToAngle(self.centre_x, self.centre_y, x, y)
self.left_down_angle = angle #save the angle at which left button was clicked for later use
def OnLeftUp(self, event):
'''When left mouse button is lifted up, determine the DNA selection from angles generated at down an up events.'''
self.centre_x = self.size[0]/2 #centre of window in x
self.centre_y = self.size[1]/2 #centro of window in y
x, y = self.ScreenToClient(wx.GetMousePosition())
up_angle = self.PointsToAngle(self.centre_x, self.centre_y, x, y)
down_angle = self.left_down_angle
'''if abs(down_angle-up_angle) <= 0.2: # want to do 'down == up' but I need some tolerance
self.highlighted_feature = self.HitTest()
if self.highlighted_feature is False: #if there is no feature, then there is not selection, just an insertion of the charet. Draw a line
start = self.angle_to_pos(down_angle)
finish = -1
else:
featuretype, complement, start, finish, name, index = genbank.gb.get_all_feature_positions()[self.highlighted_feature] #get info for the feature that was 'hit'
start += 1 #need to adjust for some reason
elif down_angle < up_angle:
start = self.angle_to_pos(down_angle)
finish = self.angle_to_pos(up_angle)
elif down_angle > up_angle:
start = self.angle_to_pos(up_angle)
finish = self.angle_to_pos(down_angle)
'''
self.set_dna_selection((start, finish))
self.update_ownUI()
def OnMotion(self, event):
'''When mouse is moved with the left button down determine the DNA selection from angle generated at mouse down and mouse move event.'''
oldhit = self.Highlight
hit = self.HitTest()
if hit:
self.Highlight = hit
# only update after change
if oldhit != hit:
self.update_ownUI()
else:
self.Highlight = None
if oldhit != hit:
self.update_ownUI()
#if event.Dragging() and event.LeftIsDown():
#up_angle = self.PointsToAngle(self.centre_x, self.centre_y, x, y)
#down_angle = self.left_down_angle
#if down_angle <= up_angle:
# start = self.angle_to_pos(down_angle)
# finish = self.angle_to_pos(up_angle)
#elif down_angle > up_angle:
# start = self.angle_to_pos(up_angle)
# finish = self.angle_to_pos(down_angle)
#self.set_dna_selection((start, finish))
#self.update_ownUI()
'''else:
new_index = self.HitTest()
if new_index is self.highlighted_feature: #if the index did not change
pass
else:
self.highlighted_feature = new_index
self.update_ownUI()'''
def OnLeftDouble(self, event):
'''When left button is double clicked, launch the feature edit dialog.'''
'''new_index = self.HitTest() #this does not get the "true" feature index. Some featues are split and this is an index that accounts for that.
if new_index is not False: #False is returned for the background
featurelist = genbank.gb.get_all_feature_positions()
featuretype, complement, start, finish, name, index = featurelist[new_index]
genbank.feature_selection = copy.copy(index)
dlg = featureedit_GUI.FeatureEditDialog(None, 'Edit Feature') # creation of a dialog with a title'''
dlg.ShowModal()
dlg.Center()
def OnRightUp(self, event):
print('plasmid right')
############ Done with mouse methods ####################
class PlasmidView2(DNApyBaseClass):
'''
This class is intended to glue together the plasmid drawing with control buttons.
'''
def __init__(self, parent, id):
DNApyBaseClass.__init__(self, parent, id)
self.plasmid_view = PlasmidView(self, -1)
########## Add buttons and methods to control their behaviour ###########
#buttons
padding = 10 #how much to add around the picture
imageFile = files['default_dir']+"/icon/circle.png"
image1 = wx.Image(imageFile, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
circle = wx.BitmapButton(self, id=10, bitmap=image1, size = (image1.GetWidth()+padding, image1.GetHeight()+padding), name = "share")
imageFile = files['default_dir']+"/icon/group.png"
image1 = wx.Image(imageFile, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
group = wx.BitmapButton(self, id=11, bitmap=image1, size = (image1.GetWidth()+padding, image1.GetHeight()+padding), name = "share")
imageFile = files['default_dir']+"/icon/radiating.png"
image1 = wx.Image(imageFile, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
radiating = wx.BitmapButton(self, id=12, bitmap=image1, size = (image1.GetWidth()+padding, image1.GetHeight()+padding), name = "share")
imageFile = files['default_dir']+"/icon/new_small.png"
image1 = wx.Image(imageFile, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
newfeature = wx.BitmapButton(self, id=1, bitmap=image1, size = (image1.GetWidth()+padding, image1.GetHeight()+padding), name = "share")
imageFile = files['default_dir']+"/icon/remove_small.png"
image1 = wx.Image(imageFile, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
deletefeature = wx.BitmapButton(self, id=2, bitmap=image1, size = (image1.GetWidth()+padding, image1.GetHeight()+padding), name = "share")
imageFile = files['default_dir']+"/icon/edit.png"
image1 = wx.Image(imageFile, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
edit = wx.BitmapButton(self, id=6, bitmap=image1, size = (image1.GetWidth()+padding, image1.GetHeight()+padding), name = "edit")
#bind feature list buttons
self.Bind(wx.EVT_BUTTON, self.OnCircularLabels, id=10)
self.Bind(wx.EVT_BUTTON, self.OnGroupLabels, id=11)
self.Bind(wx.EVT_BUTTON, self.OnRadiatingLabels, id=12)
self.Bind(wx.EVT_BUTTON, self.OnNew, id=1)
self.Bind(wx.EVT_BUTTON, self.OnDelete, id=2)
self.Bind(wx.EVT_BUTTON, self.OnEditFeature, id=6)
#arrange buttons vertically
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(item=circle)
sizer.Add(item=group)
sizer.Add(item=radiating)
sizer.Add(item=newfeature)
sizer.Add(item=deletefeature)
sizer.Add(item=edit)
#add feature list and buttons horizontally
sizer2 = wx.BoxSizer(wx.HORIZONTAL)
sizer2.Add(item=sizer, proportion=0, flag=wx.EXPAND)
sizer2.Add(item=self.plasmid_view, proportion=-1, flag=wx.EXPAND)
self.SetSizer(sizer2)
def update_ownUI(self):
'''
User interface updates.
'''
self.plasmid_view.update_ownUI()
def OnCircularLabels(self, evt):
self.plasmid_view.label_type = 'circular'
self.update_ownUI()
def OnGroupLabels(self, evt):
self.plasmid_view.label_type = 'group'
self.update_ownUI()
def OnRadiatingLabels(self, evt):
self.plasmid_view.label_type = 'radiating'
self.update_ownUI()
def OnNew(self, evt):
pass
def OnDelete(self, evt):
pass
def OnEditFeature(self, evt):
pass
#######################################################################
##### main loop
class MyApp(wx.App):
def OnInit(self):
frame = wx.Frame(None, -1, title="Plasmid View", size=(700,700), style = wx.NO_FULL_REPAINT_ON_RESIZE)
panel = PlasmidView2(frame, -1)
frame.Centre()
frame.Show(True)
self.SetTopWindow(frame)
return True
if __name__ == '__main__': #if script is run by itself and not loaded
files={} #list with all configuration files
files['default_dir'] = os.path.abspath(os.path.dirname(sys.argv[0]))+"/"
files['default_dir']=string.replace(files['default_dir'], "\\", "/")
files['default_dir']=string.replace(files['default_dir'], "library.zip", "")
settings=files['default_dir']+"settings" ##path to the file of the global settings
execfile(settings) #gets all the pre-assigned settings
genbank.dna_selection = (1, 1) #variable for storing current DNA selection
genbank.feature_selection = False #variable for storing current feature selection
import sys
assert len(sys.argv) == 2, 'Error, this script requires a path to a genbank file as an argument.'
print('Opening %s' % str(sys.argv[1]))
genbank.gb = genbank.gbobject(str(sys.argv[1])) #make a genbank object and read file
app = MyApp(0)
app.MainLoop()
OK, I have a branch with a working example. I did not start on drawing labels yet. It seems like I nee to write a lot of code for that, so I guess that might take a while.
Here the first working example: https://github.com/openpaul/DNApy/tree/newDNAGui
Looks like this right now (left old, right cairo):
I also draw arrow heads, as I think it looks a little bit more pretty. But it seems as the colors are not yet correct, but that is just a minor error.
Looks beautiful! And this is completely interactive? I'm quite excited about the fact that you got cairo drawing working.
yes, it is interactive. As far as I know every action (mousemovement, click, doubleclick etc) is possible to implement, as this is implemented at the level of wxpython.
So I will poke around with labels for now. And there are still some bugs I want to chase... (All drawing goes black as soon as I click the first time on the canvas or change windows for example)
Tryed to draw the label into the arrows if it fits, but I guess it could be improved... does not look pixel perfect jet...
Good news everyone. Drawing the label in the arrow works good, saves space and looks alright I think:
It's looking very pretty indeed. And it sure looks better than when I tried to do have the name inside the features with GCDC. The letters did not arc smoothly somehow....
The label system is tricky (and still needed for smaller features) and I only had a crude implementation of it. I pre-computed positions where labels could be an then tried to distribute the labels to those slots in a good way. But there is much improvement that can be done on what I did.
I've also commited my UI changes so that the DNA and plasmid views can be seen at the same time. I had to re-introduce the publisher system to do that. I've had compatability problems with it in the past. Let me know if you have any problems.
The new window split works really good. I like it a lot. Makes it more easy to see what I am dooing in the DNA.
I just implemented selections in the new plasmid GUI. Even though my code is a little bit messy it works fine. Labels are still missing, as I wait for an good idea how to make them fast and easy to draw.
Looks like that with selection of feature:
This is looking spectacular! The drawing is very clean and I like the new way of showing the selection. I think the labels need some clever underlying computation to look good. I find that the way it's implemented in SnapGene looks quite good.
Basically there is a circle of pre-computed label positions around the plasmid. These are distributed such that text cannot overlap. Then it simply goes through and adds in the labels in the order which they appear on the DNA. The first choice is to put the label in a straight line outside the feature or enzyme which is to be labeled. If that position is taken, then it gets bumped down to the next label position, if that is taken then the next, and so on. I have much of this implemented for my labels in GCDC. The main problem there is that it went through the features in the order they are presented in the GenBank file, not in the order in which they appear on the DNA. If that is fixed we would have something very similar to that in SnapGene.
I'd be happy to implement this if you clean up your Cairo code so that I can read it and then submit a pull request.
Yes, SnapGene does it very well. That was also my approach. If you have a good idea how to make that happen I would be gald if you can do it. I will provide a function where you can calculate the positions and if you need help with drawing in cairo I now have some ideas of the possibilities.
SnapGene also just informs you whenever it is not able to show everything, cause there is not enough space. Maybe we should do it similar.
I will clean the code tomorrow I suppose and then I will search for a bug in my digestion that produces wrong virtual cuts.
Sounds like a plan!
So I tryed implementing the labels myself (as I had some time last week) and I am not very satisfied with the solution I came up with. And I have no Idea how to make those labels works as well as SnapGene does it. It seems like you need a very good idea or a lot of code to do that.
I wrote 200 lines just to sort the labels and group them when there is not enough space to show them all "normally". But even now I have lines crossing and it does not look that beautiful.
I was just wondering if you had time to think about this issue?
just found out, that snapgene does not actually use a circle but rather a oval to place labels.
This way more labels fit in the same space.
Even though you seem occupied lately, here my latest update on the label issue:
I implemented a new label algorythm sorting the labels first an then just iterating and pushing positions until it fits.
It is not as fast as ist should be and I guess I will improve it further, but the result is good i think:
I will try to show restrictionsites also and then I will look into speed.
Sorry I've been completely absent for a while. I've moved to a new country with my family and that, combined with a new job, has taken a severe toll on my ability to do anything for the project. Things are getting better now though. I still have some papers to finish up in the evenings and then I can get back to working on DNApy. I'm still commited to it.
Using an oval for the labesl is the way to go I think. That's what I had for one of my implementations as well. What you have so far looks really good! If you want a genbank files with a lot of labels for testing I have a genbank file with the yeast mitochondrial genome.
Sounds like you had enough on your plate the last few weeks. I will keep working on the plasmid view but discovered, that maybe the cairo lib was not the best idea, as it is very slow and not really for interaction with the user (but makes awesome svg files). I thought, that maybe a game engine would provide the power to make a good GUI, but I do not know, when I will find the time to look further into this (I had the possibility to try http://www.geneious.com/ and they have a pretty nice plasmid viewing tool).
I woul like to try your genbank file, maybe we can add a folder with "testfiles" (if you are allwoed to share this file) or we just exchange emails.
I had some time on my hands and redid almost the entire plasmid drawing code and could improve performance and readability.
Some features are not reimplemented yet, but I will continue to work on them. Moreover I am thinking about implementing code to display linear DNA.
Sounds great! Glad you're back! I'm finally more or less done with all my backlogged work so I should be returning to project as well. I'll make an effort this coming week to merge everything and get back into the DNApy project.
Date: Sun, 19 Jul 2015 00:59:15 -0700 From: [email protected] To: [email protected] Subject: Re: [DNApy] Plasmid view (#4)
I had some time on my hands and redid almost the entire plasmid drawing code and could improve performance and readability.
Some features are not reimplemented yet, but I will continue to work on them. Moreover I am thinking about implementing code to display linear DNA.
— Reply to this email directly or view it on GitHub.