def _is_robot_clicked(self, field_point): # ロボットをクリックしたか判定する # 消えたロボットは対照外 is_clicked = False replace_id = 0 is_yellow = False for robot in self._robot_info['blue']: if robot.disappeared: continue robot_point = QPointF(robot.pose.x, robot.pose.y) is_clicked = self._is_clicked(field_point, robot_point) if is_clicked: is_yellow = False return is_clicked, robot.robot_id, is_yellow for robot in self._robot_info['yellow']: if robot.disappeared: continue robot_point = QPointF(robot.pose.x, robot.pose.y) is_clicked = self._is_clicked(field_point, robot_point) if is_clicked: is_yellow = True return is_clicked, robot.robot_id, is_yellow return is_clicked, replace_id, is_yellow
def __init__(self, parent=None): super(QMapAnnotation, self).__init__() self._parent = parent self.destroyed.connect(self.close) self._load_ui() self._scene = self.map_view._scene self._viz_markers_pose = None self._viz_marker_items = {} self._viz_marker_items["annotations"] = [] self._viz_marker_items["new_annotations"] = [] self._viz_marker_polygon = QPolygonF( [QPointF(-4, 4), QPointF(-4, -4), QPointF(12, 0)]) self._callback = {} self._callback['save_annotation'] = None self._callback['load_world'] = None self._callback['add_annotation'] = None self._wc = None self._init_events() self._drawing_objects = {} self._init_variables_for_drawing() self._init_variableS_for_list()
def c(msg): if not (msg.header.frame_id == '/map' or msg.header.frame_id == ''): try: self._tf.waitForTransform(msg.header.frame_id, '/map', rospy.Time(), rospy.Duration(10)) points_stamped = [] for pt in msg.polygon.points: ps = PointStamped() ps.header.frame_id = msg.header.frame_id ps.point.x = pt.x ps.point.y = pt.y points_stamped.append(ps) trans_pts = [self._tf.transformPoint('/map', pt).point for pt in points_stamped] except tf.Exception: rospy.logerr("TF Error") trans_pts = [] else: trans_pts = [pt for pt in msg.polygon.points] if len(trans_pts) > 0: pts = [QPointF(pt.x / self.resolution, pt.y / self.resolution) for pt in trans_pts] close = trans_pts[0] pts.append(QPointF(close.x / self.resolution, close.y / self.resolution)) poly.path = QPolygonF(pts) self.polygon_changed.emit(name)
def getNodeItemForSubgraph(self, subgraph, highlight_level, scene=None): # let pydot imitate pygraphviz api attr = {} for name in subgraph.get_attributes().keys(): value = get_unquoted(subgraph, name) attr[name] = value obj_dic = subgraph.__getattribute__('obj_dict') for name in obj_dic: if name not in ['nodes', 'attributes', 'parent_graph' ] and obj_dic[name] is not None: attr[name] = get_unquoted(obj_dic, name) elif name == 'nodes': for key in obj_dic['nodes']['graph'][0]['attributes']: attr[key] = get_unquoted( obj_dic['nodes']['graph'][0]['attributes'], key) subgraph.attr = attr bb = subgraph.attr.get('bb', None) if bb is None: # no bounding box return None bb = bb.strip('"').split(',') if len(bb) < 4: # bounding box is empty return None bounding_box = QRectF(0, 0, float(bb[2]) - float(bb[0]), float(bb[3]) - float(bb[1])) if 'lp' in subgraph.attr: label_pos = subgraph.attr['lp'].strip('"').split(',') else: label_pos = (float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2, float(bb[1]) + (float(bb[3]) - float(bb[1])) - LABEL_HEIGHT / 2) bounding_box.moveCenter( QPointF( float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2, -float(bb[1]) - (float(bb[3]) - float(bb[1])) / 2)) name = subgraph.attr.get('label', '') color = QColor( subgraph.attr['color']) if 'color' in subgraph.attr else None subgraph_nodeitem = NodeItem( highlight_level, bounding_box, label=name, shape='box', color=color, parent=scene.activePanel() if scene is not None else None, label_pos=QPointF(float(label_pos[0]), -float(label_pos[1]))) bounding_box = QRectF(bounding_box) # With clusters we have the problem that mouse hovers cannot # decide whether to be over the cluster or a subnode. Using # just the "title area" solves this. TODO: Maybe using a # border region would be even better (multiple RectF) bounding_box.setHeight(LABEL_HEIGHT) subgraph_nodeitem.set_hovershape(bounding_box) if scene is not None: scene.addItem(subgraph_nodeitem) return subgraph_nodeitem
def _isRobotClicked(self, real_pos): is_clicked = False replace_id = 0 is_friend = False for robot_id in self.friendsIDArray.data: posX = self.friendOdoms[robot_id].pose.pose.position.x posY = self.friendOdoms[robot_id].pose.pose.position.y robot_pos = QPointF(posX, posY) is_clicked = self._isClicked(real_pos, robot_pos) if is_clicked: is_friend = True return is_clicked, robot_id, is_friend for robot_id in self.enemyIDArray.data: posX = self.enemyOdoms[robot_id].pose.pose.position.x posY = self.enemyOdoms[robot_id].pose.pose.position.y robot_pos = QPointF(posX, posY) is_clicked = self._isClicked(real_pos, robot_pos) if is_clicked: is_friend = False return is_clicked, robot_id, is_friend return is_clicked, replace_id, is_friend
def addArrow(self, startPoint=QPointF(), endPoint=QPointF(), pen=QPen()): alpha = 5 * pi / 6 arrowLength = 10 theta = atan2((endPoint.y() - startPoint.y()), (endPoint.x() - startPoint.x())) gamma1 = theta + alpha gamma2 = theta - alpha arrowPoint_1 = QPointF(endPoint.x() + arrowLength * cos(gamma1), endPoint.y() + arrowLength * sin(gamma1)) arrowPoint_2 = QPointF(endPoint.x() + arrowLength * cos(gamma2), endPoint.y() + arrowLength * sin(gamma2)) line_0 = QLineF(startPoint, endPoint) line_1 = QLineF(endPoint, arrowPoint_1) line_2 = QLineF(endPoint, arrowPoint_2) line_item_0 = QGraphicsLineItem(line_0) line_item_0.setPen(pen) line_item_1 = QGraphicsLineItem(line_1) line_item_1.setPen(pen) line_item_2 = QGraphicsLineItem(line_2) line_item_2.setPen(pen) arrowItems = [line_item_0, line_item_1, line_item_2] self.addItem(line_item_0) self.addItem(line_item_1) self.addItem(line_item_2) return arrowItems
def get_cluster_node(self, node): # let pydot imitate pygraphviz api attr = {} for name in node.get_attributes().iterkeys(): value = get_unquoted(node, name) attr[name] = value obj_dic = node.__getattribute__("obj_dict") for name in obj_dic: if name not in ['nodes', 'attributes', 'parent_graph' ] and obj_dic[name] is not None: attr[name] = get_unquoted(obj_dic, name) elif name == 'nodes': for key in obj_dic['nodes']['graph'][0]['attributes']: attr[key] = get_unquoted( obj_dic['nodes']['graph'][0]['attributes'], key) node.attr = attr name = None if 'label' in node.attr: name = node.attr['label'] elif 'name' in node.attr: name = node.attr['name'] else: print("Error, no label defined for node with attr: %s" % node.attr) return None if name is None: # happens on Lucid pygraphviz version print( "Error, label is None for node %s, pygraphviz version may be too old." % node) bb_width = node.attr['width'] bb_height = node.attr['height'] bounding_box = QRectF(0, 0, POINTS_PER_INCH * float(bb_width) - 1.0, POINTS_PER_INCH * float(bb_height) - 1.0) # print bounding_box pos = (0, 0) if 'pos' in node.attr: pos = node.attr['pos'].split(',') bounding_box.moveCenter(QPointF(float(pos[0]), -float(pos[1]))) label_pos = QPointF(bounding_box.center().x(), bounding_box.top() + LABEL_HEIGHT / 2) color = QColor(node.attr['color']) if 'color' in node.attr else None url = node.attr['URL'] if 'URL' in node.attr else 'N/A' label = node.attr['label'] if 'label' in node.attr else name graph_node_item = self._factory.create_node(bounding_box=bounding_box, shape='box', label=label, label_pos=label_pos, url=url, cluster=True) return graph_node_item
def mouseReleaseEvent(self, event): if self._can_replace: self._can_replace = False self._replace_func(event.posF()) else: self.trans += self.mouseTrans self.mouseTrans = QPointF(0.0, 0.0) self.update()
def addCoordinateSystem(self, origin=QPointF(), angle=0.0): XAxis = QPointF(origin.x() + 100, origin.y()) YAxis = QPointF(origin.x(), origin.y() - 100) self.addArrow(origin, XAxis) self.addArrow(origin, YAxis) XLabel = self.addText('X', QFont()) XLabel.setPos(XAxis) YLabel = self.addText('Y', QFont()) YLabel.setPos(YAxis)
def mouseReleaseEvent(self, event): # マウスのドラッグ操作で描画領域を移動する # Replacementを行うときは描画領域を移動しない if self._do_replacement: self._do_replacement = False self._replace_func(event.localPos()) else: self._trans += self._mouse_trans self._mouse_trans = QPointF(0.0, 0.0) self.update()
def _replace_robot_angle(self, mouse_pos): # ロボット角度のReplacement field_point = self._convert_to_field(mouse_pos.x(), mouse_pos.y()) # ロボット角度をradiansからdegreesに変換する robot_point = QPointF() if self._replace_is_yellow: robot_point = QPointF( self._robot_info['yellow'][self._replace_id].pose.x, self._robot_info['yellow'][self._replace_id].pose.y) else: robot_point = QPointF( self._robot_info['blue'][self._replace_id].pose.x, self._robot_info['blue'][self._replace_id].pose.y) robot = ReplaceRobot() robot.x = robot_point.x() robot.y = robot_point.y() robot.dir = math.degrees(self._to_angle(robot_point, field_point)) robot.id = self._replace_id robot.yellowteam = self._replace_is_yellow robot.turnon = True if self._invert_side: robot.x *= -1.0 robot.y *= -1.0 robot.dir += 180 replacements = Replacements() replacements.robots.append(robot) self._pub_replace.publish(replacements) self._replacement_target['robot_angle'] = False
def _replaceBallVel(self, mouse_pos): ballPos = self.ballOdom.pose.pose.position ballPoint = QPointF(ballPos.x, ballPos.y) currentPos = self.convertToRealWorld(mouse_pos.x(), mouse_pos.y()) velocity = self._calcuReplaceVelocity(ballPoint, currentPos) replace = ReplaceBall() replace.pos_x = ballPoint.x() replace.pos_y = ballPoint.y() replace.vel_x = velocity.x() replace.vel_y = velocity.y() self._pub_replace_ball.publish(replace) self._is_ballvel_replacement = False
def add_ROI(self, pixel_coords): self.regionCounter += 1 markerSize = 25 ellipse_item = QGraphicsEllipseItem( QRectF( QPointF(pixel_coords.x() - markerSize / 2, pixel_coords.y() - markerSize / 2), QSizeF(markerSize, markerSize))) ellipse_item.setBrush(QBrush(QColor('red'))) self.addItem(ellipse_item) label_font = QFont() label_font.setPointSize(15) region_string = 'r' + str(self.regionCounter).zfill(2) ellipse_item_label = QGraphicsTextItem(region_string) ellipse_item_label.setPos(pixel_coords) ellipse_item_label.setFont(label_font) self.addItem(ellipse_item_label) self.items_dict.update({ region_string: { 'ellipse_item': ellipse_item, 'ellipse_item_label': ellipse_item_label, 'pixel_coords': pixel_coords, 'ap_item_label': {} } })
def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionChange: min_starttime = self._track.starttime max_starttime = 1000000000.0 new_starttime = min(max(min_starttime, round(value.x(), self._precision)), max_starttime) # check for track change new_track = self.scene().track_at(value.y() + self._track.height / 2) if new_track is None or new_track.track_type() != self._track.track_type(): new_track = self._track # prevent overlap with the next clip next_clip = new_track.next_clip(new_starttime) if next_clip is not None and next_clip is not self: max_starttime = next_clip.starttime() - self.duration() # move only up to the next clip new_starttime = min(new_starttime, max_starttime) # prevent overlap with the previous clip previous_clip = new_track.previous_clip(new_starttime) if previous_clip is not None and previous_clip is not self: min_starttime = previous_clip.endtime() # move only up to the previous clip new_starttime = max(new_starttime, min_starttime) if min_starttime <= new_starttime <= max_starttime: self._track = new_track return QPointF(new_starttime, self._track._y) return self.pos() return super(TimelineClip, self).itemChange(change, value)
def paint(self, painter, option, widget): painter.setFont(self._time_font) painter.fillRect(0, 0, SandtrayItem.length * SandtrayItem.scale, SandtrayItem.width * SandtrayItem.scale, painter.background()) for color, polys in self._zones.items(): painter.setPen(QPen(color, 2)) for poly in polys: painter.drawPolygon(QPolygonF(poly)) painter.setBrush(QBrush(self._fg_color)) painter.setPen(QPen(self._border_color, 1)) painter.drawRect(0, 0, SandtrayItem.length * SandtrayItem.scale, SandtrayItem.width * SandtrayItem.scale) for label, pos in self._items.items(): x, y = pos[0] * SandtrayItem.scale, pos[1] * SandtrayItem.scale painter.drawText(x - 10, y - 10, label) if "cube" in label: painter.setBrush(QBrush(self._cube_color)) painter.drawRect(x - 5, y - 5, 10, 10) else: painter.setBrush(QBrush(self._item_color)) painter.drawEllipse(QPointF(x, y), 10, 10)
def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False): """ adds EdgeItem by data in edge to edges :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting) """ # let pydot imitate pygraphviz api attr = {} for name in edge.get_attributes().iterkeys(): value = get_unquoted(edge, name) attr[name] = value edge.attr = attr if 'style' in edge.attr: if edge.attr['style'] == 'invis': return label = edge.attr.get('label', None) label_pos = edge.attr.get('lp', None) label_center = None if label_pos is not None: label_pos = label_pos.split(',') label_center = QPointF(float(label_pos[0]), -float(label_pos[1])) # try pydot, fallback for pygraphviz source_node = edge.get_source() if hasattr(edge, 'get_source') else edge[0] destination_node = edge.get_destination() if hasattr( edge, 'get_destination') else edge[1] # create edge with from-node and to-node edge_pos = (0, 0) if 'pos' in edge.attr: edge_pos = edge.attr['pos'] if label is not None: label = label.decode('string_escape') edge_item = EdgeItem(highlight_level=highlight_level, spline=edge_pos, label_center=label_center, label=label, from_node=nodes[source_node], to_node=nodes[destination_node]) if same_label_siblings: if label is None: # for sibling detection label = "%s_%s" % (source_node, destination_node) # symmetrically add all sibling edges with same label if label in edges: for sibling in edges[label]: edge_item.add_sibling_edge(sibling) sibling.add_sibling_edge(edge_item) if label not in edges: edges[label] = [] edges[label].append(edge_item)
def slot_redraw(self): """ Gets called either when new msg comes in or when marker is moved by user. """ self._parent._scene.clear() is_enabled = self.isEnabled() qsize = self.size() width_tl = qsize.width() height_tl = qsize.height() length_tl = ((self._max_num_seconds + 1) - self._min_num_seconds) len_queue = length_tl #w = float(width_tl / len_queue) # This returns always 0 in # the 1st decimal point, which causes # timeline not fit nicely. w = width_tl / float(len_queue) for i, m in enumerate(self._parent.get_diagnostic_queue()): h = self.viewport().height() # Figure out each cell's color. qcolor = None color_index = i + self._min_num_seconds if (is_enabled): qcolor = self._color_callback( self._parent.get_diagnostic_queue(), color_index) else: qcolor = QColor('grey') # TODO For adding gradation to the cell color. Not used yet. end_color = QColor(0.5 * QColor('red').value(), 0.5 * QColor('green').value(), 0.5 * QColor('blue').value()) rect = self._parent._scene.addRect(w * i, 0, w, h, QColor('white'), qcolor) rospy.logdebug( 'TimelineView.slot_redraw #%d th loop w=%s width_tl=%s', i, w, width_tl) # Setting marker. #xpos_marker = ((self._xpos_marker - 1) * width_cell + #(width_cell / 2.0) - xpos_marker = ((self._xpos_marker - 1) * w + (w / 2.0) - (self._timeline_marker_width / 2.0)) pos_marker = QPointF(xpos_marker, 0) # Need to instantiate marker everytime since it gets deleted # in every loop by scene.clear() timeline_marker = self._instantiate_tl_icon() timeline_marker.setPos(pos_marker) self._parent._scene.addItem(timeline_marker) # self._timeline_marker.paint(painter, qrect) rospy.logdebug(' slot_redraw xpos_marker(int)=%s length_tl=%s', int(xpos_marker), length_tl)
def transformPointToPixelCoordinates(pt, map, image_size): map_point_f = ((pt - QPointF(map.map.info.origin.position.x, map.map.info.origin.position.y)) * (1.0 / map.map.info.resolution)) map_point = QPoint(int(map_point_f.x()), int(map_point_f.y())) map_size = QSize(map.map.info.width, map.map.info.height) scaled_point = scalePoint(map_point, map_size, image_size) return QPoint(scaled_point.x(), image_size.height() - scaled_point.y() - 1)
def getNodeItemForNode(self, node, highlight_level): """ returns a pyqt NodeItem object, or None in case of error or invisible style """ # let pydot imitate pygraphviz api # attr = {} # for name in node.get_attributes().iterkeys(): # value = get_unquoted(node, name) # attr[name] = value # obj_dic = node.__getattribute__("obj_dict") # for name in obj_dic: # if name not in ['attributes', 'parent_graph'] and obj_dic[name] is not None: # attr[name] = get_unquoted(obj_dic, name) # node.attr = attr if 'style' in node.attr: if node.attr['style'] == 'invis': return None color = QColor(node.attr['color']) if 'color' in node.attr else None name = None if 'label' in node.attr: name = node.attr['label'] elif 'name' in node.attr: name = node.attr['name'] else: print("Error, no label defined for node with attr: %s" % node.attr) return None if name is None: # happens on Lucid pygraphviz version print("Error, label is None for node %s, pygraphviz version may be too old." % node) else: name = name.decode('string_escape') # decrease rect by one so that edges do not reach inside bb_width = len(name) / 5 if 'width' in node.attr: bb_width = node.attr['width'] bb_height = 1.0 if 'width' in node.attr: bb_height = node.attr['height'] bounding_box = QRectF(0, 0, POINTS_PER_INCH * float(bb_width) - 1.0, POINTS_PER_INCH * float(bb_height) - 1.0) pos = (0, 0) if 'pos' in node.attr: pos = node.attr['pos'].split(',') bounding_box.moveCenter(QPointF(float(pos[0]), -float(pos[1]))) node_item = NodeItem(highlight_level=highlight_level, bounding_box=bounding_box, label=name, shape=node.attr.get('shape', 'ellipse'), color=color, tooltip=node.attr.get('tooltip', None) # parent=None, # label_pos=None ) # node_item.setToolTip(self._generate_tool_tip(node.attr.get('URL', None))) return node_item
def drawVelReplacement(self, painter): ball_odom = self.ballOdom.pose.pose.position ball_pos = QPointF(ball_odom.x, ball_odom.y) currentPos = self.convertToRealWorld( self._current_mouse_pos.x(), self._current_mouse_pos.y()) velocity = self._calcuReplaceVelocity(ball_pos, currentPos) ballPoint = self.convertToDrawWorld(ball_pos.x(), ball_pos.y()) currentPoint = self.convertToDrawWorld(currentPos.x(), currentPos.y()) painter.setPen(QPen(Qt.blue,2)) painter.drawLine(ballPoint, currentPoint) text = "[" + str(round(velocity.x(),2)) + ", " + str(round(velocity.y(),2)) + "]" painter.setPen(Qt.black) painter.drawText(currentPoint, text)
def _draw_vel_replacement(self, painter): # ボール速度のReplacementを描画する current_pos = self._convert_to_field(self._current_mouse_pos.x(), self._current_mouse_pos.y()) current_point = self._convert_to_view(current_pos.x(), current_pos.y()) ball_pos = QPointF(self._ball_info.pose.x, self._ball_info.pose.y) ball_point = self._convert_to_view(ball_pos.x(), ball_pos.y()) painter.setPen(QPen(Qt.blue, 2)) painter.drawLine(ball_point, current_point) # 速度の数値を描画する velocity = self._replacement_velocity(ball_pos, current_pos) text = "[" + str(round(velocity.x(), 2)) + ", " + str( round(velocity.y(), 2)) + "]" painter.setPen(Qt.black) painter.drawText(current_point, text)
def _signal_redraw(self): """ Gets called either when new msg comes in or when marker is moved by user. """ if self._levels is None: return # update the limits self._min = 0 self._max = len(self._levels) - 1 self._scene.clear() qsize = self.size() width_tl = qsize.width() w = width_tl / float(max(len(self._levels), 1)) is_enabled = self.isEnabled() for i, level in enumerate(self._levels): h = self.viewport().height() # Figure out each cell's color. qcolor = QColor('grey') if is_enabled and level is not None: if level > DiagnosticStatus.ERROR: # Stale items should be reported as errors unless all stale level = DiagnosticStatus.ERROR qcolor = util.level_to_color(level) # TODO Use this code for adding gradation to the cell color. # end_color = QColor(0.5 * QColor('red').value(), # 0.5 * QColor('green').value(), # 0.5 * QColor('blue').value()) self._scene.addRect(w * i, 0, w, h, QColor('white'), qcolor) # Getting marker index. xpos_marker = self._xpos_marker # If marker is -1 for latest use (number_of_cells -1) if xpos_marker == -1: xpos_marker = len(self._levels) - 1 # Convert get horizontal pixel value of selected cell's center xpos_marker_in_pixel = (xpos_marker * w + (w / 2.0) - (self._timeline_marker_width / 2.0)) pos_marker = QPointF(xpos_marker_in_pixel, 0) # Need to instantiate marker everytime since it gets deleted # in every loop by scene.clear() timeline_marker = self._instantiate_tl_icon() timeline_marker.setPos(pos_marker) self._scene.addItem(timeline_marker)
def add_ap(self, region, ap): label_font = QFont() label_font.setPointSize(15) ap_item_label = QGraphicsTextItem(ap) ap_item_label.setPos( QPointF(self.items_dict[region]['pixel_coords'].x() - 25, self.items_dict[region]['pixel_coords'].y())) ap_item_label.setFont(label_font) self.addItem(ap_item_label) self.items_dict[region]['ap_item_label'].update({ap: ap_item_label})
def _get_zones(self, msg): zones = {} for marker in msg.markers: polygon = [] color = QColor(255*marker.color.r, 255*marker.color.g, 255*marker.color.b, 255*marker.color.a) for p in marker.points: polygon.append(QPointF(p.x * SandtrayItem.scale, -p.y * SandtrayItem.scale)) zones.setdefault(color, []).append(polygon) return zones
def _is_ball_clicked(self, field_point): # ボールをクリックしたか判定する # ボールが消えていれば判定しない if self._ball_info.disappeared: return False pos_x = self._ball_info.pose.x pos_y = self._ball_info.pose.y ball_pos = QPointF(pos_x, pos_y) return self._is_clicked(field_point, ball_pos)
def __init__(self, parent=None): super(QSlamWidget, self).__init__() self._parent = parent self._slam_widget_interface = None self._load_ui() self._init_events() self.destroyed.connect(self.close) self._scene = self.map_view._scene self._robot_pose = None self._robot_pose_item = None self._robot_polygon = QPolygonF( [QPointF(-4, 4), QPointF(-4, -4), QPointF(12, 0)]) self._scan = None self._scan_items = [] self._callback = {} self._callback['save_map'] = None
def eventFilter(self, _, event): if event.type() == QEvent.MouseButtonRelease: x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x()) y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y()) self._last_click_coordinates = QPointF(x, y) self.limits_changed.emit() elif event.type() == QEvent.MouseMove: x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x()) y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y()) coords = QPointF(x, y) if self._picker.isActive() and self._last_click_coordinates is not None: toolTip = 'origin x: %.5f, y: %.5f' % ( self._last_click_coordinates.x(), self._last_click_coordinates.y()) delta = coords - self._last_click_coordinates toolTip += '\ndelta x: %.5f, y: %.5f\nlength: %.5f' % ( delta.x(), delta.y(), QVector2D(delta).length()) else: toolTip = 'buttons\nleft: measure\nmiddle: move\nright: zoom x/y\nwheel: zoom y' self.setToolTip(toolTip) self.mouseCoordinatesChanged.emit(coords) return False
def _draw_playhead(self, painter): """ Draw a line and 2 triangles to denote the current position being viewed :param painter: ,''QPainter'' """ px = self.map_stamp_to_x(self.playhead.to_sec()) pw, ph = self._playhead_pointer_size # Line painter.setPen(QPen(self._playhead_color)) painter.setBrush(QBrush(self._playhead_color)) painter.drawLine(px, self._history_top - 1, px, self._history_bottom + 2) # Upper triangle py = self._history_top - ph painter.drawPolygon( QPolygonF([ QPointF(px, py + ph), QPointF(px + pw, py), QPointF(px - pw, py) ])) # Lower triangle py = self._history_bottom + 1 painter.drawPolygon( QPolygonF([ QPointF(px, py), QPointF(px + pw, py + ph), QPointF(px - pw, py + ph) ])) painter.setBrush(self._default_brush) painter.setPen(self._default_pen)
def _calcuReplaceVelocity(self, from_pos, to_pos): diff = to_pos - from_pos diff_size = math.hypot(diff.x(), diff.y()) velocity_size = diff_size * self._VEL_GAIN if velocity_size > self._VEL_MAX: velocity_size = self._VEL_MAX angle = math.atan2(diff.y(), diff.x()) velocity = QPointF(velocity_size * math.cos(angle), velocity_size * math.sin(angle)) return velocity
def paintEvent(self, event): painter = QPainter(self) #painter.begin(self) # puts the arrow in the middle painter.translate(self.width() / 2, self.height() / 2) painter.rotate(self.angleRobo + self.angleVel) line = QLineF(0, 0, self.width() / 2 - 3, 0) headSize = min(self.width() / 20, 4) points = QPolygonF() points.append(QPointF(self.width() / 2 - headSize * 2, headSize)) points.append(QPointF(self.width() / 2 - headSize * 2, -headSize)) points.append(QPointF(self.width() / 2 - 3, 0)) pen = QPen(self.color, 2) painter.setPen(pen) brush = QBrush(self.color) painter.setBrush(brush) painter.drawLine(line) painter.drawConvexPolygon(points)
def eventFilter(self, _, event): if event.type() == QEvent.MouseButtonRelease: x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x()) y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y()) self._last_click_coordinates = QPointF(x, y) elif event.type() == QEvent.MouseMove: x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x()) y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y()) coords = QPointF(x, y) if self._picker.isActive() and self._last_click_coordinates is not None: toolTip = 'origin x: %.5f, y: %.5f' % (self._last_click_coordinates.x(), self._last_click_coordinates.y()) delta = coords - self._last_click_coordinates toolTip += '\ndelta x: %.5f, y: %.5f\nlength: %.5f' % (delta.x(), delta.y(), QVector2D(delta).length()) else: toolTip = 'buttons\nleft: measure\nmiddle: move\nright: zoom x/y\nwheel: zoom y' self.setToolTip(toolTip) self.mouseCoordinatesChanged.emit(coords) return False
def __init__(self, spline, label, label_center, from_node, to_node, parent=None, **kwargs): super(EdgeItem, self).__init__(parent, **kwargs) self._edge_pen_width = kwargs.get('edge_pen_width', self.EDGE_PEN_WIDTH) self.from_node = from_node self.from_node.add_outgoing_edge(self) self.to_node = to_node self.to_node.add_incoming_edge(self) self._brush = QBrush(self._color) self._label_pen = QPen() self._label_pen.setColor(self._color) self._label_pen.setJoinStyle(Qt.RoundJoin) self._label_pen.setWidthF(self._label_pen_width) self._edge_pen = QPen() self._edge_pen.setColor(self._color) self._edge_pen.setWidthF(self._edge_pen_width) self._sibling_edges = set() self._label = None if label is not None: self._label = QGraphicsSimpleTextItem(label) font = self._label.font() font.setPointSize(8) self._label.setFont(font) label_rect = self._label.boundingRect() label_rect.moveCenter(label_center) self._label.setPos(label_rect.x(), label_rect.y()) # spline specification according to http://www.graphviz.org/doc/info/attrs.html#k:splineType coordinates = spline.split(' ') # extract optional end_point end_point = None if coordinates[0].startswith('e,'): parts = coordinates.pop(0)[2:].split(',') end_point = QPointF(float(parts[0]), -float(parts[1])) # extract optional start_point if coordinates[0].startswith('s,'): parts = coordinates.pop(0).split(',') # first point parts = coordinates.pop(0).split(',') point = QPointF(float(parts[0]), -float(parts[1])) path = QPainterPath(point) while len(coordinates) > 2: # extract triple of points for a cubic spline parts = coordinates.pop(0).split(',') point1 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point2 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point3 = QPointF(float(parts[0]), -float(parts[1])) path.cubicTo(point1, point2, point3) self._arrow = None if end_point is not None: # draw arrow self._arrow = QGraphicsPolygonItem() polygon = QPolygonF() polygon.append(point3) offset = QPointF(end_point - point3) corner1 = QPointF(-offset.y(), offset.x()) * 0.35 corner2 = QPointF(offset.y(), -offset.x()) * 0.35 polygon.append(point3 + corner1) polygon.append(end_point) polygon.append(point3 + corner2) self._arrow.setPolygon(polygon) self._path = QGraphicsPathItem() self._path.setPath(path) self.addToGroup(self._path) self._brush.setColor(self._color) self._edge_pen.setColor(self._color) self._label_pen.setColor(self._color) self._path.setPen(self._edge_pen) if self._arrow is not None: self._arrow.setBrush(self._brush) self._arrow.setPen(self._edge_pen) if self._label is not None: self._label.setBrush(self._brush) self._label.setPen(self._label_pen)
def __init__(self, highlight_level, spline, label_center, label, from_node, to_node, parent=None, penwidth=1, edge_color=None, style='solid'): super(EdgeItem, self).__init__(highlight_level, parent) self.from_node = from_node self.from_node.add_outgoing_edge(self) self.to_node = to_node self.to_node.add_incoming_edge(self) self._default_edge_color = self._COLOR_BLACK if edge_color is not None: self._default_edge_color = edge_color self._default_text_color = self._COLOR_BLACK self._default_color = self._COLOR_BLACK self._text_brush = QBrush(self._default_color) self._shape_brush = QBrush(self._default_color) if style in ['dashed', 'dotted']: self._shape_brush = QBrush(Qt.transparent) self._label_pen = QPen() self._label_pen.setColor(self._default_text_color) self._label_pen.setJoinStyle(Qt.RoundJoin) self._edge_pen = QPen(self._label_pen) self._edge_pen.setWidth(penwidth) self._edge_pen.setColor(self._default_edge_color) self._edge_pen.setStyle(self._qt_pen_styles.get(style, Qt.SolidLine)) self._sibling_edges = set() self._label = None if label is not None: self._label = QGraphicsSimpleTextItem(label) label_rect = self._label.boundingRect() label_rect.moveCenter(label_center) self._label.setPos(label_rect.x(), label_rect.y()) self._label.hoverEnterEvent = self._handle_hoverEnterEvent self._label.hoverLeaveEvent = self._handle_hoverLeaveEvent self._label.setAcceptHoverEvents(True) # spline specification according to http://www.graphviz.org/doc/info/attrs.html#k:splineType coordinates = spline.split(' ') # extract optional end_point end_point = None if (coordinates[0].startswith('e,')): parts = coordinates.pop(0)[2:].split(',') end_point = QPointF(float(parts[0]), -float(parts[1])) # extract optional start_point if (coordinates[0].startswith('s,')): parts = coordinates.pop(0).split(',') # first point parts = coordinates.pop(0).split(',') point = QPointF(float(parts[0]), -float(parts[1])) path = QPainterPath(point) while len(coordinates) > 2: # extract triple of points for a cubic spline parts = coordinates.pop(0).split(',') point1 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point2 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point3 = QPointF(float(parts[0]), -float(parts[1])) path.cubicTo(point1, point2, point3) self._arrow = None if end_point is not None: # draw arrow self._arrow = QGraphicsPolygonItem() polygon = QPolygonF() polygon.append(point3) offset = QPointF(end_point - point3) corner1 = QPointF(-offset.y(), offset.x()) * 0.35 corner2 = QPointF(offset.y(), -offset.x()) * 0.35 polygon.append(point3 + corner1) polygon.append(end_point) polygon.append(point3 + corner2) self._arrow.setPolygon(polygon) self._arrow.hoverEnterEvent = self._handle_hoverEnterEvent self._arrow.hoverLeaveEvent = self._handle_hoverLeaveEvent self._arrow.setAcceptHoverEvents(True) self._path = QGraphicsPathItem() self._path.setPath(path) self.addToGroup(self._path) self.set_node_color() self.set_label_color()
class QwtDataPlot(Qwt.QwtPlot): mouseCoordinatesChanged = Signal(QPointF) _num_value_saved = 1000 _num_values_ploted = 1000 limits_changed = Signal() def __init__(self, *args): super(QwtDataPlot, self).__init__(*args) self.setCanvasBackground(Qt.white) self.insertLegend(Qwt.QwtLegend(), Qwt.QwtPlot.BottomLegend) self._curves = {} # TODO: rejigger these internal data structures so that they're easier # to work with, and easier to use with set_xlim and set_ylim # this may also entail rejiggering the _time_axis so that it's # actually time axis data, or just isn't required any more # new data structure self._x_limits = [0.0, 10.0] self._y_limits = [0.0, 10.0] # old data structures self._last_canvas_x = 0 self._last_canvas_y = 0 self._pressed_canvas_y = 0 self._pressed_canvas_x = 0 self._last_click_coordinates = None marker_axis_y = Qwt.QwtPlotMarker() marker_axis_y.setLabelAlignment(Qt.AlignRight | Qt.AlignTop) marker_axis_y.setLineStyle(Qwt.QwtPlotMarker.HLine) marker_axis_y.setYValue(0.0) marker_axis_y.attach(self) self._picker = Qwt.QwtPlotPicker( Qwt.QwtPlot.xBottom, Qwt.QwtPlot.yLeft, Qwt.QwtPicker.PolygonSelection, Qwt.QwtPlotPicker.PolygonRubberBand, Qwt.QwtPicker.AlwaysOn, self.canvas() ) self._picker.setRubberBandPen(QPen(Qt.blue)) self._picker.setTrackerPen(QPen(Qt.blue)) # Initialize data self.rescale() #self.move_canvas(0, 0) self.canvas().setMouseTracking(True) self.canvas().installEventFilter(self) def eventFilter(self, _, event): if event.type() == QEvent.MouseButtonRelease: x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x()) y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y()) self._last_click_coordinates = QPointF(x, y) self.limits_changed.emit() elif event.type() == QEvent.MouseMove: x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x()) y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y()) coords = QPointF(x, y) if self._picker.isActive() and self._last_click_coordinates is not None: toolTip = 'origin x: %.5f, y: %.5f' % (self._last_click_coordinates.x(), self._last_click_coordinates.y()) delta = coords - self._last_click_coordinates toolTip += '\ndelta x: %.5f, y: %.5f\nlength: %.5f' % (delta.x(), delta.y(), QVector2D(delta).length()) else: toolTip = 'buttons\nleft: measure\nmiddle: move\nright: zoom x/y\nwheel: zoom y' self.setToolTip(toolTip) self.mouseCoordinatesChanged.emit(coords) return False def log(self, level, message): # self.emit(SIGNAL('logMessage'), level, message) pass def resizeEvent(self, event): Qwt.QwtPlot.resizeEvent(self, event) self.rescale() def add_curve(self, curve_id, curve_name, curve_color=QColor(Qt.blue), markers_on=False): curve_id = str(curve_id) if curve_id in self._curves: return curve_object = Qwt.QwtPlotCurve(curve_name) curve_object.attach(self) curve_object.setPen(curve_color) if markers_on: curve_object.setSymbol(Qwt.QwtSymbol(Qwt.QwtSymbol.Ellipse, QBrush(curve_color), QPen(Qt.black), QSize(4,4))) self._curves[curve_id] = curve_object def remove_curve(self, curve_id): curve_id = str(curve_id) if curve_id in self._curves: self._curves[curve_id].hide() self._curves[curve_id].attach(None) del self._curves[curve_id] def set_values(self, curve_id, data_x, data_y): curve = self._curves[curve_id] curve.setData(data_x, data_y) def redraw(self): self.replot() # ---------------------------------------------- # begin qwtplot internal methods # ---------------------------------------------- def rescale(self): self.setAxisScale(Qwt.QwtPlot.yLeft, self._y_limits[0], self._y_limits[1]) self.setAxisScale(Qwt.QwtPlot.xBottom, self._x_limits[0], self._x_limits[1]) self._canvas_display_height = self._y_limits[1] - self._y_limits[0] self._canvas_display_width = self._x_limits[1] - self._x_limits[0] self.redraw() def rescale_axis_x(self, delta__x): """ add delta_x to the length of the x axis """ x_width = self._x_limits[1] - self._x_limits[0] x_width += delta__x self._x_limits[1] = x_width + self._x_limits[0] self.rescale() def scale_axis_y(self, max_value): """ set the y axis height to max_value, about the current center """ canvas_display_height = max_value canvas_offset_y = (self._y_limits[1] + self._y_limits[0])/2.0 y_lower_limit = canvas_offset_y - (canvas_display_height / 2) y_upper_limit = canvas_offset_y + (canvas_display_height / 2) self._y_limits = [y_lower_limit, y_upper_limit] self.rescale() def move_canvas(self, delta_x, delta_y): """ move the canvas by delta_x and delta_y in SCREEN COORDINATES """ canvas_offset_x = delta_x * self._canvas_display_width / self.canvas().width() canvas_offset_y = delta_y * self._canvas_display_height / self.canvas().height() self._x_limits = [ l + canvas_offset_x for l in self._x_limits] self._y_limits = [ l + canvas_offset_y for l in self._y_limits] self.rescale() def mousePressEvent(self, event): self._last_canvas_x = event.x() - self.canvas().x() self._last_canvas_y = event.y() - self.canvas().y() self._pressed_canvas_y = event.y() - self.canvas().y() def mouseMoveEvent(self, event): canvas_x = event.x() - self.canvas().x() canvas_y = event.y() - self.canvas().y() if event.buttons() & Qt.MiddleButton: # middle button moves the canvas delta_x = self._last_canvas_x - canvas_x delta_y = canvas_y - self._last_canvas_y self.move_canvas(delta_x, delta_y) elif event.buttons() & Qt.RightButton: # right button zooms zoom_factor = max(-0.6, min(0.6, (self._last_canvas_y - canvas_y) / 20.0 / 2.0)) delta_y = (self.canvas().height() / 2.0) - self._pressed_canvas_y self.move_canvas(0, zoom_factor * delta_y * 1.0225) self.scale_axis_y(max(0.005, self._canvas_display_height - (zoom_factor * self._canvas_display_height))) self.rescale_axis_x(self._last_canvas_x - canvas_x) self._last_canvas_x = canvas_x self._last_canvas_y = canvas_y def wheelEvent(self, event): # mouse wheel zooms the y-axis # y position of pointer in graph coordinates canvas_y = event.y() - self.canvas().y() try: delta = event.angleDelta().y() except AttributeError: delta = event.delta() zoom_factor = max(-0.6, min(0.6, (delta / 120) / 6.0)) delta_y = (self.canvas().height() / 2.0) - canvas_y self.move_canvas(0, zoom_factor * delta_y * 1.0225) self.scale_axis_y(max(0.0005, self._canvas_display_height - zoom_factor * self._canvas_display_height)) self.limits_changed.emit() def vline(self, x, color): qWarning("QwtDataPlot.vline is not implemented yet") def set_xlim(self, limits): self.setAxisScale(Qwt.QwtPlot.xBottom, limits[0], limits[1]) self._x_limits = limits def set_ylim(self, limits): self.setAxisScale(Qwt.QwtPlot.yLeft, limits[0], limits[1]) self._y_limits = limits def get_xlim(self): return self._x_limits def get_ylim(self): return self._y_limits
class QwtDataPlot(Qwt.QwtPlot): mouseCoordinatesChanged = Signal(QPointF) _colors = [Qt.red, Qt.green, Qt.blue, Qt.magenta] _num_value_saved = 5000 _num_values_ploted = 1000 def __init__(self, *args): super(QwtDataPlot, self).__init__(*args) self.setCanvasBackground(Qt.white) self.insertLegend(Qwt.QwtLegend(), Qwt.QwtPlot.BottomLegend) self._curves = {} self._data_offset_x = 0 self._canvas_offset_x = 0 self._canvas_offset_y = 0 self._last_canvas_x = 0 self._last_canvas_y = 0 self._pressed_canvas_y = 0 self._last_click_coordinates = None self._color_index = 0 marker_axis_y = Qwt.QwtPlotMarker() marker_axis_y.setLabelAlignment(Qt.AlignRight | Qt.AlignTop) marker_axis_y.setLineStyle(Qwt.QwtPlotMarker.HLine) marker_axis_y.setYValue(0.0) marker_axis_y.attach(self) self._picker = Qwt.QwtPlotPicker( Qwt.QwtPlot.xBottom, Qwt.QwtPlot.yLeft, Qwt.QwtPicker.PolygonSelection, Qwt.QwtPlotPicker.PolygonRubberBand, Qwt.QwtPicker.AlwaysOn, self.canvas() ) self._picker.setRubberBandPen(QPen(self._colors[-1])) self._picker.setTrackerPen(QPen(self._colors[-1])) # Initialize data self._time_axis = arange(0, 10) self._canvas_display_height = 1000 self._canvas_display_width = self.canvas().width() self._data_offset_x = self._num_value_saved - self._num_values_ploted self.redraw() self.move_canvas(0, 0) self.canvas().setMouseTracking(True) self.canvas().installEventFilter(self) def eventFilter(self, _, event): if event.type() == QEvent.MouseButtonRelease: x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x()) y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y()) self._last_click_coordinates = QPointF(x, y) elif event.type() == QEvent.MouseMove: x = self.invTransform(Qwt.QwtPlot.xBottom, event.pos().x()) y = self.invTransform(Qwt.QwtPlot.yLeft, event.pos().y()) coords = QPointF(x, y) if self._picker.isActive() and self._last_click_coordinates is not None: toolTip = 'origin x: %.5f, y: %.5f' % (self._last_click_coordinates.x(), self._last_click_coordinates.y()) delta = coords - self._last_click_coordinates toolTip += '\ndelta x: %.5f, y: %.5f\nlength: %.5f' % (delta.x(), delta.y(), QVector2D(delta).length()) else: toolTip = 'buttons\nleft: measure\nmiddle: move\nright: zoom x/y\nwheel: zoom y' self.setToolTip(toolTip) self.mouseCoordinatesChanged.emit(coords) return False def log(self, level, message): self.emit(SIGNAL('logMessage'), level, message) def resizeEvent(self, event): #super(QwtDataPlot, self).resizeEvent(event) Qwt.QwtPlot.resizeEvent(self, event) self.rescale() def add_curve(self, curve_id, curve_name, values_x, values_y): curve_id = str(curve_id) if self._curves.get(curve_id): return curve_object = Qwt.QwtPlotCurve(curve_name) curve_object.attach(self) curve_object.setPen(QPen(self._colors[self._color_index % len(self._colors)])) self._color_index += 1 self._curves[curve_id] = { 'name': curve_name, 'data': zeros(self._num_value_saved), 'time': zeros(self._num_value_saved), 'object': curve_object, } def remove_curve(self, curve_id): curve_id = str(curve_id) if curve_id in self._curves: self._curves[curve_id]['object'].hide() self._curves[curve_id]['object'].attach(None) del self._curves[curve_id]['object'] del self._curves[curve_id] @Slot(str, list, list) def update_values(self, curve_id, values_x, values_y): for value_x, value_y in zip(values_x, values_y): self.update_value(curve_id, value_x, value_y) @Slot(str, float, float) def update_value(self, curve_id, value_x, value_y): curve_id = str(curve_id) # update data plot if curve_id in self._curves: # TODO: use value_x as timestamp self._curves[curve_id]['data'] = concatenate((self._curves[curve_id]['data'][1:], self._curves[curve_id]['data'][:1]), 1) self._curves[curve_id]['data'][-1] = float(value_y) self._curves[curve_id]['time'] = concatenate((self._curves[curve_id]['time'][1:], self._curves[curve_id]['time'][:1]), 1) self._curves[curve_id]['time'][-1] = float(value_x) def redraw(self): for curve_id in self._curves.keys(): x_data = self._time_axis x_data = self._curves[curve_id]['time'][self._data_offset_x: -1] y_data = self._curves[curve_id]['data'][self._data_offset_x: -1] self._curves[curve_id]['object'].setData(x_data, y_data) #self._curves[curve_id]['object'].setStyle(Qwt.QwtPlotCurve.CurveStyle(3)) self.replot() def rescale(self): y_num_ticks = self.height() / 40 y_lower_limit = self._canvas_offset_y - (self._canvas_display_height / 2) y_upper_limit = self._canvas_offset_y + (self._canvas_display_height / 2) # calculate a fitting step size for nice, round tick labels, depending on the displayed value area y_delta = y_upper_limit - y_lower_limit exponent = int(math.log10(y_delta)) presicion = -(exponent - 2) y_step_size = round(y_delta / y_num_ticks, presicion) self.setAxisScale(Qwt.QwtPlot.yLeft, y_lower_limit, y_upper_limit, y_step_size) self.setAxisScale(Qwt.QwtPlot.xBottom, self._time_axis[0], self._time_axis[-1]+1) self.redraw() def rescale_axis_x(self, delta__x): new_len = len(self._time_axis) + delta__x new_len = max(10, min(new_len, self._num_value_saved)) self._time_axis = arange(new_len) self._data_offset_x = max(0, min(self._data_offset_x, self._num_value_saved - len(self._time_axis))) self.rescale() def scale_axis_y(self, min_value, max_value): self._canvas_display_height = max_value - min_value self._canvas_offset_y = (max_value + min_value)/2 self.rescale() def rescale_axis_y(self): y_min = [-0.1] y_max = [0.1] for curve_id in self._curves.keys(): y_min.append(min(self._curves[curve_id]['data'])) y_max.append(max(self._curves[curve_id]['data'])) time = self._curves[curve_id]['time'][-1] min_value = min(y_min) max_value = max(y_max) if time > 10: self._time_axis = arange(time-10, time) self._canvas_display_height = max_value - min_value self._canvas_offset_y = (max_value + min_value)/2 self.rescale() def move_canvas(self, delta_x, delta_y): self._data_offset_x += delta_x * len(self._time_axis) / float(self.canvas().width()) self._data_offset_x = max(0, min(self._data_offset_x, self._num_value_saved - len(self._time_axis))) self._canvas_offset_x += delta_x * self._canvas_display_width / self.canvas().width() self._canvas_offset_y += delta_y * self._canvas_display_height / self.canvas().height() self.rescale() def mousePressEvent(self, event): self._last_canvas_x = event.x() - self.canvas().x() self._last_canvas_y = event.y() - self.canvas().y() self._pressed_canvas_y = event.y() - self.canvas().y() def mouseMoveEvent(self, event): canvas_x = event.x() - self.canvas().x() canvas_y = event.y() - self.canvas().y() if event.buttons() & Qt.MiddleButton: # middle button moves the canvas delta_x = self._last_canvas_x - canvas_x delta_y = canvas_y - self._last_canvas_y self.move_canvas(delta_x, delta_y) elif event.buttons() & Qt.RightButton: # right button zooms zoom_factor = max(-0.6, min(0.6, (self._last_canvas_y - canvas_y) / 20.0 / 2.0)) delta_y = (self.canvas().height() / 2.0) - self._pressed_canvas_y self.move_canvas(0, zoom_factor * delta_y * 1.0225) self.scale_axis_y(0, max(0.005, self._canvas_display_height - (zoom_factor * self._canvas_display_height))) self.rescale_axis_x(self._last_canvas_x - canvas_x) self._last_canvas_x = canvas_x self._last_canvas_y = canvas_y def wheelEvent(self, event): # mouse wheel zooms the y-axis canvas_y = event.y() - self.canvas().y() zoom_factor = max(-0.6, min(0.6, (event.delta() / 120) / 6.0)) delta_y = (self.canvas().height() / 2.0) - canvas_y self.move_canvas(0, zoom_factor * delta_y * 1.0225) self.scale_axis_y(0, max(0.0005, self._canvas_display_height - zoom_factor * self._canvas_display_height))