Source code for schrodinger.ui.qt.rama

"""
A Ramachandran Plot widget, along with some tools for manipulating
structures/points.

Usage:

Rama(parent)

Schrodinger L.L.C.
"""

# Copyright Schrodinger, LLC. All rights reserved.

import os
import sys
from past.utils import old_div

import numpy

from schrodinger import structure
from schrodinger.infra import mm
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from schrodinger.structutils import analyze
from schrodinger.ui.qt import smatplotlib
from schrodinger.ui.qt import swidgets

try:
    from schrodinger import maestro
except:
    in_maestro = False
else:
    in_maestro = True

subdir = os.path.join(os.path.dirname(__file__), 'rama_dir')

OK = 1
ERROR = 0

#####################################################


[docs]class RamaFigure(smatplotlib.SmatplotlibCanvas):
[docs] def sizeHint(self): w, h = self.get_width_height() return QtCore.QSize(w, h)
[docs] def miniumSizeHint(self): return QtCore.QSize(10, 10)
[docs]class DihedralSpinBox(QtWidgets.QDoubleSpinBox): """ A DoulbeSpinBox with min/max = -180/180, step=0.1, and takes a layout argument that it places itself in, and a command to call when its value changes. """
[docs] def __init__(self, layout, command, dtype): QtWidgets.QDoubleSpinBox.__init__(self) self.setMaximum(180.) self.setMinimum(-180.) self.setSingleStep(1.0) self.setDecimals(1) mylayout = swidgets.SHBoxLayout() mylabel = swidgets.SLabel(dtype, layout=mylayout) mylayout.addWidget(self) layout.addLayout(mylayout) self.valueChanged.connect(command)
[docs]class Rama: """ A class showing a matplotlib plot of the Phi and Psi angles in a helix """ DEFAULT_CURSOR = "crosshair" DEFAULT_SYMBOL_SIZE = 1 DEF_COLOR = "black" GLY_COLOR = "black" PRO_COLOR = "black" FAVORABLE_COLOR = "#f20000" ALLOWED_COLOR = "#ffff00" ZOOMSTEP = 10.0 SYMBOL_SCALING_FACTOR = 2 ESC_KEY = 27
[docs] class backbone_dihedral:
[docs] def __init__(self): CA = 0 N1 = 0 C1 = 0 N2 = 0 C2 = 0 phi = 0.0 psi = 0.0 original_phi = 0.0 original_psi = 0.0 saved_phi = 0.0 saved_psi = 0.0 chain = "" resname = "" resnum = 0 inscode = "" entry = "" fill_colour = "" outline_colour = ""
[docs] def __init__(self, widget, layout, size=450, ticks=30, show_status=True, show_counters=False, zoom_on_pick=True, dpi=100, multiselect=False): """ Create a Rama object :type widget: QWidget :param widget: the PyQt widget that 'owns' this plot :type layout: QLayout :param layout: the layout to place the plot into :type size: int :param size: size of square plot in pixels, (default=450). :type dpi: int :param dpi: dots per inch resolution of plot (default=100). :type ticks: int :param size: unused, kept for backward compatibility :type show_status: bool :param show_status: If True, cursor location feedback is given in the matplotlib toolbar :type show_counters: bool :param show_counters: If True, show controls for changing the Phi and Psi values of the selected dihedral. Only one of multiselect and show_counters can be True. :type zoom_on_pick: bool :param zoom_on_pick: If True, zoom the Maestro workspace to the dihedral picked :type multiselect: bool :param multiselect: If True, the user can pick multiple points with shift/cntl-click. If False (default), only one point can be picked at a time. Only one of multiselect and show_counters can be True. """ self.owner = widget if multiselect and show_counters: raise RuntimeError('Only one of multiselect and show_counters can ' 'be True') self.multiselect = multiselect if multiselect: self.multiselect_keys = set(['shift', 'control']) else: self.multiselect_keys = [] self.picking_callback = None self.preadjust_callback = None self.postadjust_callback = None self.last_found_index = -1 self.show_status = show_status self.zoom_on_pick = zoom_on_pick self.dihedral_list = [] # self.current_pick contains the index of the selected dihedral that was # most recently picked. If no dihedrals are currently selected, it is # None. For historical reasons, this is tracked separately from the # list of all selected points. self.current_pick = None # self.selected_points is the set of all currently selected points. self.selected_points = set() self.weights = [] self.styles = [] self.plot_lines = [] # Some incarnations of the Rama plot do not plot all dihedrals, so this # helps to translate between them self.dihedral_to_plotline = {} self.set_symbol_size(self.DEFAULT_SYMBOL_SIZE) self.size = size self.ticks = ticks self.viewport = [0.0, 0.0, 0.0, 0.0] # [ x0, y0, x1, y1 ] self.zooming = False self.zoombox = [0.0, 0.0, 0.0, 0.0] # [ x0, y0, x1, y1 ] self.scrolling = False self.scrollpoint = [0.0, 0.0] self.transform_direction = 'C' side = old_div(float(size), dpi) self.graph = RamaFigure(width=side, height=side, dpi=dpi, layout=layout, coordinates=False) self.nav_toolbar = self.graph.toolbar self.label = QtWidgets.QLabel(" \n ") if self.show_status: self.nav_toolbar.addWidget(self.label) self.nav_toolbar.update() self.graph.fig.clf() self.graph.sub = self.graph.fig.add_subplot(111) self.first_update = True # Labelling self.graph.mpl_connect("motion_notify_event", self.drag) # Picking self.graph.mpl_connect("button_release_event", self.pick_by_point) self.show_counters = show_counters if show_counters: counter_layout = swidgets.SHBoxLayout() counter_layout.addStretch() spin_layout = swidgets.SVBoxLayout() self.phispin = DihedralSpinBox(spin_layout, self.adjust_phi, 'Phi') self.psispin = DihedralSpinBox(spin_layout, self.adjust_psi, 'Psi') counter_layout.addLayout(spin_layout) rb_frame = QtWidgets.QFrame() rb_frame.setFrameShape(rb_frame.StyledPanel) rb_frame.setFrameShadow(rb_frame.Sunken) rb_layout = swidgets.SVBoxLayout(rb_frame) self.rb_group = swidgets.SRadioButtonGroup( labels=['Transform N-terminus', 'Transform C-terminus'], layout=rb_layout) counter_layout.addWidget(rb_frame) counter_layout.addStretch() layout.addLayout(counter_layout)
[docs] def draw_regions(self, filename='rama500-general.data'): # Regions try: with open(os.path.join(subdir, filename), 'r') as ramafile: self._draw_regions(ramafile) except IOError: QtWidgets.QMessageBox.warning( self.owner, "File Invalid", "Region data file %s not found." % (filename)) return
def _draw_regions(self, ramafile): datax = [] datay = [] dataz = [] for line in ramafile: if not line.strip() or line.startswith('#'): continue parts = line.split() datax.append(float(parts[0])) datay.append(float(parts[1])) dataz.append(float(parts[2])) datax = numpy.array(datax).reshape((180, 180)) datay = numpy.array(datay).reshape((180, 180)) dataz = numpy.array(dataz).reshape((180, 180)) self.graph.sub.contour(datax, datay, dataz, levels=[0.0005, 0.02], colors='black') self.graph.sub.contourf(datax, datay, dataz, levels=[0.0005, 0.02, 1], colors=(self.ALLOWED_COLOR, self.FAVORABLE_COLOR)) xaxis = self.graph.sub.get_xaxis() xaxis.set_ticks_position('bottom') yaxis = self.graph.sub.get_yaxis() yaxis.set_ticks_position('left') self.graph.sub.set_xlim(-180, 180) self.graph.sub.set_xticks(list(range(-180, 190, 30))) self.graph.sub.set_xlabel("Phi (degrees)", size="xx-small") labels = [str(r) for r in range(-180, 190, 30)] self.graph.sub.set_xticklabels(labels, size="xx-small") self.graph.sub.set_ylim(-180, 180) self.graph.sub.set_yticks(list(range(-180, 190, 30))) self.graph.sub.set_ylabel("Psi (degrees)", size="xx-small") self.graph.sub.set_yticklabels(labels, size="xx-small") # Cross hairs self.graph.sub.plot([0.0, 0.0], [180.0, -180.0], color="black") self.graph.sub.plot([-180.0, 180.0], [0.0, 0.0], color="black") return
[docs] def find_connected_atom(self, ct, iatom, atname): """ Return the atom index with PDB atom type atname attached to iatom in ct, or -1 if no connection is found. """ num_bonds = mm.mmct_atom_get_bond_total(ct, iatom) for ibond in range(1, num_bonds + 1): conn_atom = mm.mmct_atom_get_bond_atom(ct, iatom, ibond) if (mm.mmct_atom_get_pdbname(ct, conn_atom) == atname): return conn_atom return -1
[docs] def get_connected_pair(self, ct, iatom, at1name, at2name): """ Return a pair of atom indexes, where at1name is attached to iatom and at2name is attached to at1name. """ at1 = self.find_connected_atom(ct, iatom, at1name) if (at1 > 0): at2 = self.find_connected_atom(ct, at1, at2name) else: at2 = -1 return (at1, at2)
[docs] def get_weight_symbol(self, weight): symbol = "" if weight <= 1.0: # Default symbol = 'ko' elif weight <= 2.0: # gly symbol = 'k^' elif weight <= 3.0: # pro symbol = 'ks' elif weight >= 10.0 and weight <= 11.0: # def-p symbol = 'wo' elif weight > 11.0 and weight <= 12.0: # gly-p symbol = 'w^' elif weight > 12.0 and weight <= 13.0: # pro-p symbol = 'ws' else: symbol = 'r*' return symbol
[docs] def update_point_faces(self, indexes): """ Update the face colors for points on the plot - faster than doing a complete replot. :type indexes: list :param indexes: List of indexes to recolor. Should be the index of the point in the self.dihedral_list list """ for index in indexes: symbol = self.get_weight_symbol(self.weights[index]) color = symbol[0] line_index = self.dihedral_to_plotline[index] self.plot_lines[line_index].set_markerfacecolor(color) self.graph.draw()
[docs] def update_point_xval(self, index, xval): """ Update the x-value of a point on the plot :type index: int :param index: The index of the point in the self.dihedral_list list """ line_index = self.dihedral_to_plotline[index] self.plot_lines[line_index].set_xdata([xval]) self.graph.draw()
[docs] def update_point_yval(self, index, yval): """ Update the y-value of a point :type index: int :param index: The index of the point in the self.dihedral_list list """ line_index = self.dihedral_to_plotline[index] self.plot_lines[line_index].set_ydata([yval]) self.graph.draw()
[docs] def update_display(self): if not self.first_update: self.nav_toolbar.push_current() self.graph.sub.clear() self.draw_regions() # Point types xvalues = [dihedral.phi for dihedral in self.dihedral_list] yvalues = [dihedral.psi for dihedral in self.dihedral_list] symbols = [self.get_weight_symbol(w) for w in self.weights] count = 0 self.plot_lines = [] self.dihedral_to_plotline = {} for x, y, symbol in zip(xvalues, yvalues, symbols): line = self.graph.sub.plot([x], [y], symbol, markersize=self.symbol_size)[0] # Some incarnations of the Rama plot don't plot all dihedrals, so we # have to translate between the two arrays line.dihedral_index = count self.dihedral_to_plotline[count] = len(self.plot_lines) line.set_pickradius(self.symbol_size * self.SYMBOL_SCALING_FACTOR + 0.5) self.plot_lines.append(line) count = count + 1 if not self.first_update: self.nav_toolbar.back() self.graph.draw() self.first_update = False return
[docs] def display_structure(self, ct, CA_list=[]): # noqa: M511 self.current_pick = None self.selected_points = set() self.weights = [] self.dihedral_list = [] if ct is None: self.graph.sub.clear() self.draw_regions() self.graph.draw() return if len(CA_list) == 0: CA_list = analyze.evaluate_asl(ct, "atom.ptype \" CA \"") for CA in CA_list: new_dihedral = self.backbone_dihedral() new_dihedral.CA = CA (new_dihedral.N1, new_dihedral.C1) = self.get_connected_pair(ct, new_dihedral.CA, " N ", " C ") (new_dihedral.C2, new_dihedral.N2) = self.get_connected_pair(ct, new_dihedral.CA, " C ", " N ") if new_dihedral.N1 < 0 or new_dihedral.C1 < 0 or new_dihedral.C2 < 0 or new_dihedral.N2 < 0: continue new_dihedral.phi = mm.mmct_atom_get_dihedral_angle_s( ct, new_dihedral.C1, ct, new_dihedral.N1, ct, new_dihedral.CA, ct, new_dihedral.C2) new_dihedral.psi = mm.mmct_atom_get_dihedral_angle_s( ct, new_dihedral.N1, ct, new_dihedral.CA, ct, new_dihedral.C2, ct, new_dihedral.N2) new_dihedral.original_phi = new_dihedral.phi new_dihedral.original_psi = new_dihedral.psi new_dihedral.saved_phi = new_dihedral.phi new_dihedral.saved_psi = new_dihedral.psi new_dihedral.chain = mm.mmct_atom_get_chain(ct, new_dihedral.CA) new_dihedral.resname = ct.atom[CA].pdbres (new_dihedral.resnum, new_dihedral.inscode) = mm.mmct_atom_get_resnum( ct, new_dihedral.CA) self.dihedral_list.append(new_dihedral) if new_dihedral.resname == "GLY ": self.weights.append(1.5) elif new_dihedral.resname == "PRO ": self.weights.append(2.5) else: self.weights.append(0.5) self.update_display() return
[docs] def save(self): """ Store the current phi & psi values of the current point. This method only makes sense with multiselect == False """ if self.multiselect: raise RuntimeError('The save method cannot be used with ' 'multiselect') if not self.current_pick: return dihedral = self.dihedral_list[self.current_pick] dihedral.saved_phi = dihedral.phi dihedral.saved_psi = dihedral.psi return
[docs] def revert_to_original(self): """ Set the current point to its original phi & psi values This method only makes sense with multiselect == False """ if self.multiselect: raise RuntimeError('The revert_to_original method cannot be used ' 'with multiselect') if not self.current_pick: return dihedral = self.dihedral_list[self.current_pick] dihedral.phi = dihedral.original_phi dihedral.psi = dihedral.original_psi self.change_current_pick(self.current_pick) self.apply_dihedral(self.current_pick) return
[docs] def revert_to_saved(self): """ Set the current point to its previously saved phi & psi values This method only makes sense with multiselect == False """ if self.multiselect: raise RuntimeError('The revert_to_saved method cannot be used ' 'with multiselect') if not self.current_pick: return dihedral = self.dihedral_list[self.current_pick] dihedral.phi = dihedral.saved_phi dihedral.psi = dihedral.saved_psi self.change_current_pick(self.current_pick) self.apply_dihedral(self.current_pick) return
[docs] def display_residue(self, which='all'): """ Select residues in the workspace corresponding to the given points :type which: int, None or 'all' :param which: If None, all residues are deselected. If an integer, that integer is taken as the index in the dihedral list to select. If 'all', residues for all selected points are selected. """ if which is None or (which == 'all' and not self.selected_points): # No point to display self.label.setText(" \n ") if in_maestro: maestro.command("workspaceselectionclear") else: # Create the toolbar label for the desired point, which is # the most recently selected point if multiple points are picked if which == 'all': index = self.current_pick else: index = which residue = self.dihedral_list[index] text = "Residue: %1s:%3s%4d%1s\nphi=%4.1f psi=%4.1f" \ % (residue.chain, residue.resname, residue.resnum, residue.inscode, residue.phi, residue.psi) self.label.setText(text) # Select the residue for all selected points if in_maestro: if which == 'all': residue_asls = [] for apoint in self.selected_points: res = self.dihedral_list[apoint] asl = ( '(chain.name "%s" and res.num %d and res.i "%s")' % (res.chain, res.resnum, res.inscode)) residue_asls.append(asl) if residue_asls: select_asl = ' OR '.join(residue_asls) else: select_asl = "" else: select_asl = ( '(chain.name "%s" and res.num %d and res.i "%s")' % (residue.chain, residue.resnum, residue.inscode)) if select_asl: maestro.command("workspaceselectionreplace %s " % select_asl)
[docs] def adjust_phi(self, value): """ React to changed value of the Phi spinbox The method only makes sense with multiselect == False :type value: float :param value: new value of the spinbox """ if self.multiselect: raise RuntimeError('The adjust_phi method cannot be used ' 'with multiselect') try: fval = float(value) except: return ERROR if self.current_pick is not None: # If the new value is different from the old value, adjust the # angle. The new and old values may be the same if this call is # generated by the widget updating when a new residue is picked. if abs(fval - self.dihedral_list[self.current_pick].phi) > 0.049: self.dihedral_list[self.current_pick].phi = fval self.apply_dihedral(self.current_pick) self.update_point_xval(self.current_pick, fval) return OK
[docs] def adjust_psi(self, value): """ React to changed value of the Psi spinbox The method only makes sense with multiselect == False :type value: float :param value: new value of the spinbox """ if self.multiselect: raise RuntimeError('The adjust_psi method cannot be used ' 'with multiselect') try: fval = float(value) except: return ERROR if self.current_pick is not None: # If the new value is different from the old value, adjust the # angle. The new and old values may be the same if this call is # generated by the widget updating when a new residue is picked. if abs(fval - self.dihedral_list[self.current_pick].psi) > 0.049: self.dihedral_list[self.current_pick].psi = fval self.apply_dihedral(self.current_pick) self.update_point_yval(self.current_pick, fval) return OK
[docs] def apply_dihedral(self, idihedral): """ Change the value of a dihedral :type idihedral: int :param idihedral: The index of the dihedral to change """ if self.preadjust_callback: self.preadjust_callback() if in_maestro: dangle = self.dihedral_list[idihedral] phi_atoms = [dangle.C2, dangle.CA, dangle.N1, dangle.C1] psi_atoms = [dangle.N2, dangle.C2, dangle.CA, dangle.N1] if self.rb_group.isChecked(id=1): # Chang atom order so the other terminus will be adjusted phi_atoms.reverse() psi_atoms.reverse() phiats = ' '.join([str(x) for x in phi_atoms]) psiats = ' '.join([str(x) for x in psi_atoms]) phi = str(dangle.phi) psi = str(dangle.psi) maestro.command('adjustdihedral dihedral=%s %s' % (phi, phiats)) maestro.command('adjustdihedral dihedral=%s %s' % (psi, psiats)) if self.postadjust_callback: self.postadjust_callback() return
[docs] def minus(self, event): self.zoombox[0] = self.viewport[0] - self.ZOOMSTEP self.zoombox[1] = self.viewport[1] - self.ZOOMSTEP self.zoombox[2] = self.viewport[2] + self.ZOOMSTEP self.zoombox[3] = self.viewport[3] + self.ZOOMSTEP self.set_viewport(min_x=self.zoombox[0], min_y=self.zoombox[1], max_x=self.zoombox[2], max_y=self.zoombox[3]) return
[docs] def plus(self, event): self.zoombox[0] = self.viewport[0] + self.ZOOMSTEP self.zoombox[1] = self.viewport[1] + self.ZOOMSTEP self.zoombox[2] = self.viewport[2] - self.ZOOMSTEP self.zoombox[3] = self.viewport[3] - self.ZOOMSTEP self.set_viewport(min_x=self.zoombox[0], min_y=self.zoombox[1], max_x=self.zoombox[2], max_y=self.zoombox[3]) return
[docs] def clear_selection(self, event): """ Clear the selected points :type event: event or None :param event: The event that generated this call, or None if not being called as a response to an event. This parameter is unused. """ for point in self.selected_points: self.weights[point] -= 10.0 self.update_point_faces(self.selected_points) self.selected_points = set() self.current_pick = None self.display_residue(which=None) if self.picking_callback: self.picking_callback() return
[docs] def drag(self, event): """ As the user moves the mouse over a point, display information for that point in the toolbar and select the corresponding residue in the workspace. :type event: matplotlib MouseEvent :param event: The event that generated the call to this method. """ if event.xdata is None or event.ydata is None: return #found_index = self.find_closest_point(event.xdata, event.ydata) found_index = self.find_closest_point(event) # Certain conditions (EV 119761) can cause this callback to be called # multiple times for each actual event, which eventually causes a # recursion error. Bail out quickly and don't modify the workspace if # the workspace isn't actually going to change. if found_index == self.last_found_index or \ (found_index is None and self.last_found_index in self.selected_points): # Don't update workspace if there will be no change return if found_index is None: # Cursor not on any point, go back to displaying selected residues self.last_found_index = self.current_pick self.display_residue() else: # Display the residue the cursor is over self.last_found_index = found_index self.display_residue(which=found_index)
[docs] def change_current_pick(self, new_pick, multi=False): points_to_update = [] if new_pick in self.selected_points and multi: # Deselecting a currently selected point - remove it from the # selected points and deselect it self.weights[new_pick] -= 10.0 self.selected_points.remove(new_pick) points_to_update.append(new_pick) try: # Just grab any selected point as the "most recent" point self.current_pick = self.selected_points.pop() self.selected_points.add(self.current_pick) except KeyError: self.current_pick = None new_pick = None elif not multi: # Making a single point the only selected point for apoint in self.selected_points: self.weights[apoint] -= 10.0 points_to_update = list(self.selected_points) self.selected_points = set() self.current_pick = new_pick else: # Selecting another point self.current_pick = new_pick if new_pick is not None: # Select a new point self.weights[self.current_pick] += 10.0 self.selected_points.add(self.current_pick) points_to_update.append(self.current_pick) self.update_point_faces(points_to_update) # Update the Phi and Psi counters if they exist if self.show_counters: if self.current_pick is not None: self.phispin.setValue(self.dihedral_list[self.current_pick].phi) self.psispin.setValue(self.dihedral_list[self.current_pick].psi) else: self.phispin.setValue(0.0) self.psispin.setValue(0.0) # Select the proper residue(s) in the workspace if multi and self.current_pick is not None: self.display_residue(which='all') else: self.display_residue(which=self.current_pick) # Zoom in on the residues if in_maestro and self.zoom_on_pick: if self.multiselect and len(self.selected_points) > 1: # Multiple residues selected maestro.command('fit atom.selected') elif self.current_pick is None: maestro.command("fit") else: # Only one residue selected maestro.command("fit") maestro.command("spotcenter %d" % self.dihedral_list[self.current_pick].CA) maestro.command("zoom factor=70 in") if self.picking_callback: self.picking_callback()
[docs] def find_closest_point(self, event): """ Find the plot point that is closest to the event :type event: MouseEvent :param event: The matplotlib mouse event that generates this call """ # Find the closest item found_index = None found_distance = None found_lines = [] for index, line in enumerate(self.plot_lines): inpoint, points = line.contains(event) if inpoint: found_lines.append(line.dihedral_index) if not found_lines: # No points close to cursor return None elif len(found_lines) == 1: # Only one point close to cursor return found_lines[0] else: # More than one point found, find the closes point. The indexes of # self.plot_lines and self.dihedral_lines are the same. x = event.xdata y = event.ydata epsilon = 9 # This is a squared distance for index in found_lines: xdelta = abs(x - self.dihedral_list[index].phi) ydelta = abs(y - self.dihedral_list[index].psi) # We use a squared distance to save a call to sqrt distance = xdelta * xdelta + ydelta * ydelta if distance <= epsilon: if found_distance is None or distance < found_distance: found_index = index found_distance = distance return found_index
[docs] def pick_by_point(self, event): if event.button == 1 and event.inaxes and self.nav_toolbar.mode == '': index = self.find_closest_point(event) if index is not None: self.change_current_pick(index, multi=event.key in self.multiselect_keys) return
[docs] def pick_by_atom(self, iatom): ct = maestro.workspace_get() chain = ct.atom[iatom].chain resnum = ct.atom[iatom].resnum inscode = ct.atom[iatom].inscode self.clear_selection(None) for idihedral in range(len(self.dihedral_list)): dihedral = self.dihedral_list[idihedral] if dihedral.chain == chain and dihedral.resnum == resnum and dihedral.inscode == inscode: self.change_current_pick(idihedral) break return
[docs] def get_picked(self): """ Get the backbone_dihedral object for the selected dihedral that was most recently selected. :rtype: `backbone_dihedral` object or None :return: The `backbone_dihedral` object for the selected dihedral that was most recently selected, or None if there are no selected dihedrals. """ if self.current_pick: return self.dihedral_list[self.current_pick] else: return None
[docs] def set_viewport(self, min_x=-180.0, max_x=180.0, min_y=-180.0, max_y=180.0): # Swap coordinates if the box was drawn "backwards" if min_x > max_x: min_x, max_x = max_x, min_x if min_y > max_y: min_y, max_y = max_y, min_y # Make sure the view is square and within the plot region if (max_x - min_x) > (max_y - min_y): # x is the longest side if (max_x - min_x ) > 360.0: # bigger than the plot, so just set to full size min_x = -180.0 max_x = 180.0 else: if min_x < -180.0: # off to the left max_x += -180.0 - min_x min_x = -180.0 elif max_x > 180.0: # off to the right min_x -= max_x - 180.0 max_x = 180.0 addition = 0.5 * ((max_x - min_x) - (max_y - min_y)) # enlarge y to make it square min_y -= addition max_y += addition if min_y < -180.0: # off the bottom max_y += -180.0 - min_y min_y = -180.0 elif max_y > 180.0: # off the top min_y -= max_y - 180.0 max_y = 180.0 else: # y is the longest side if (max_y - min_y ) > 360.0: # bigger than the plot, so just set to full size min_y = -180.0 max_y = 180.0 else: if min_y < -180.0: # off the bottom max_y += -180.0 - min_y min_y = -180.0 elif max_y > 180.0: # off the top min_y -= max_y - 180.0 max_y = 180.0 addition = 0.5 * ((max_y - min_y) - (max_x - min_x)) # enlarge x to make it square min_x -= addition max_x += addition if min_x < -180.0: # off to the left max_x += -180.0 - min_x min_x = -180.0 elif max_x > 180.0: # off to the right min_x -= max_x - 180.0 max_x = 180.0 # Don't do anything if the box was too small if (max_x - min_x) > 5 and (max_y - min_y) > 5: self.viewport = [min_x, min_y, max_x, max_y] return
[docs] def write_file(self, filename): if filename: try: self.graph.fig.savefig(filename, format="png") except: print('Failure writing file ' + filename) return
[docs] def set_symbol_size(self, value): self.symbol_size = self.SYMBOL_SCALING_FACTOR * int(value)
[docs] def adjust_symbol_size(self, value): self.set_symbol_size(value) self.update_display() return
[docs] def options_quit(self): self.options_top.destroy()
[docs] def clearGraph(self): """ Gets called when project is closed """ # FIXME: Maybe call when workspace changes? self.dihedral_list = [] self.update_display() return
############################################################# if __name__ == '__main__': import schrodinger.ui.qt.appframework as appframework mypanel = appframework.AppFramework(buttons={'Close': { 'command': quit }}, title='Ramachandran Plot') #rama = Rama( mypanel, mypanel.interior_layout, show_status=True, size=600, # show_counters=True, dpi=200) rama = Rama(mypanel, mypanel.interior_layout, show_status=True, size=600, show_counters=False, dpi=200, multiselect=True) ct = structure.StructureReader.read(sys.argv[1]) CAs = analyze.evaluate_asl(ct, "atom.ptype \" CA \"") rama.display_structure(ct, CA_list=CAs) mypanel.show() mypanel.exec()