76
84
from PyQt4.QtGui import QPen, QBrush, QPolygonF, QTransform, QPainter
77
85
from PyQt4.QtCore import Qt, QRectF, QPointF, QPoint, QLineF
78
from PyQt4.Qwt5 import QwtPlotItem, QwtSymbol, QwtPlotMarker
80
87
from guidata.utils import assert_interfaces_valid, update_dataset
90
from guiqwt.transitional import QwtPlotItem, QwtSymbol, QwtPlotMarker
83
91
from guiqwt.config import CONF, _
84
92
from guiqwt.interfaces import IBasePlotItem, IShapeItemType, ISerializableType
85
93
from guiqwt.styles import (MarkerParam, ShapeParam, RangeShapeParam,
94
AxesShapeParam, CursorShapeParam)
87
95
from guiqwt.signals import (SIG_RANGE_CHANGED, SIG_MARKER_CHANGED,
88
SIG_AXES_CHANGED, SIG_ITEM_MOVED)
96
SIG_AXES_CHANGED, SIG_ITEM_MOVED, SIG_CURSOR_MOVED)
90
98
class AbstractShape(QwtPlotItem):
91
99
"""Interface pour les objets manipulables
618
648
#FIXME: EllipseShape's ellipse drawing is invalid when aspect_ratio != 1
619
class EllipseShape(RectangleShape):
649
class EllipseShape(PolygonShape):
620
650
def __init__(self, x1, y1, x2, y2, ratio=None):
652
super(EllipseShape, self).__init__([], closed=True)
621
653
self.is_ellipse = False
623
super(EllipseShape, self).__init__(x1, y1, x2, y2)
654
self.set_xdiameter(x1, y1, x2, y2)
625
656
def switch_to_ellipse(self):
626
657
self.is_ellipse = True
628
def set_rect(self, x1, y1, x2, y2):
630
Set the start point of the ellipse's X-axis diameter to (x1, y1)
631
and its end point to (x2, y2)
633
self.set_xdiameter(x1, y1, x2, y2)
659
def set_xdiameter(self, x0, y0, x1, y1):
660
"""Set the coordinates of the ellipse's X-axis diameter"""
661
xline = QLineF(x0, y0, x1, y1)
662
yline = xline.normalVector()
663
yline.translate(xline.pointAt(.5)-xline.p1())
665
yline.setLength(self.get_yline().length())
666
elif self.ratio is not None:
667
yline.setLength(xline.length()*self.ratio)
668
yline.translate(yline.pointAt(.5)-yline.p2())
669
self.set_points([(x0, y0), (x1, y1),
670
(yline.x1(), yline.y1()), (yline.x2(), yline.y2())])
672
def get_xdiameter(self):
673
"""Return the coordinates of the ellipse's X-axis diameter"""
674
return tuple(self.points[0])+tuple(self.points[1])
676
def set_ydiameter(self, x2, y2, x3, y3):
677
"""Set the coordinates of the ellipse's Y-axis diameter"""
678
yline = QLineF(x2, y2, x3, y3)
679
xline = yline.normalVector()
680
xline.translate(yline.pointAt(.5)-yline.p1())
682
xline.setLength(self.get_xline().length())
683
xline.translate(xline.pointAt(.5)-xline.p2())
684
self.set_points([(xline.x1(), xline.y1()), (xline.x2(), xline.y2()),
687
def get_ydiameter(self):
688
"""Return the coordinates of the ellipse's Y-axis diameter"""
689
return tuple(self.points[2])+tuple(self.points[3])
693
(x0, y0), (x1, y1) = self.points[0], self.points[1]
694
xc, yc = .5*(x0+x1), .5*(y0+y1)
695
radius = .5*np.sqrt((x1-x0)**2+(y1-y0)**2)
696
return xc-radius, yc-radius, xc+radius, yc+radius
698
def set_rect(self, x0, y0, x1, y1):
700
self.set_xdiameter(x0, .5*(y0+y1), x1, .5*(y0+y1))
635
702
def compute_elements(self, xMap, yMap):
636
703
"""Return points, lines and ellipse rect"""
680
747
def get_yline(self):
681
748
return QLineF(*(tuple(self.points[2])+tuple(self.points[3])))
683
def set_xdiameter(self, x0, y0, x1, y1):
684
xline = QLineF(x0, y0, x1, y1)
685
yline = xline.normalVector()
686
yline.translate(xline.pointAt(.5)-xline.p1())
688
yline.setLength(self.get_yline().length())
689
elif self.ratio is not None:
690
yline.setLength(xline.length()*self.ratio)
691
yline.translate(yline.pointAt(.5)-yline.p2())
692
self.set_points([(x0, y0), (x1, y1),
693
(yline.x1(), yline.y1()), (yline.x2(), yline.y2())])
695
def set_ydiameter(self, x2, y2, x3, y3):
696
yline = QLineF(x2, y2, x3, y3)
697
xline = yline.normalVector()
698
xline.translate(yline.pointAt(.5)-yline.p1())
700
xline.setLength(self.get_xline().length())
701
xline.translate(xline.pointAt(.5)-xline.p2())
702
self.set_points([(xline.x1(), xline.y1()), (xline.x2(), xline.y2()),
705
def move_point_to(self, handle, pos):
750
def move_point_to(self, handle, pos, ctrl=None):
708
753
x1, y1 = self.points[1]
755
# When <Ctrl> is pressed, the center position is unchanged
756
x0, y0 = self.points[0]
757
x1, y1 = x1+x0-nx, y1+y0-ny
709
758
self.set_xdiameter(nx, ny, x1, y1)
710
759
elif handle == 1:
711
760
x0, y0 = self.points[0]
762
# When <Ctrl> is pressed, the center position is unchanged
763
x1, y1 = self.points[1]
764
x0, y0 = x0+x1-nx, y0+y1-ny
712
765
self.set_xdiameter(x0, y0, nx, ny)
713
elif handle == 2 and self.is_ellipse:
714
767
x3, y3 = self.points[3]
769
# When <Ctrl> is pressed, the center position is unchanged
770
x2, y2 = self.points[2]
771
x3, y3 = x3+x2-nx, y3+y2-ny
715
772
self.set_ydiameter(nx, ny, x3, y3)
716
elif handle == 3 and self.is_ellipse:
717
774
x2, y2 = self.points[2]
776
# When <Ctrl> is pressed, the center position is unchanged
777
x3, y3 = self.points[3]
778
x2, y2 = x2+x3-nx, y2+y3-ny
718
779
self.set_ydiameter(x2, y2, nx, ny)
719
elif handle in (2, 3):
720
delta = (nx, ny)-self.points[handle]
722
780
elif handle == -1:
723
781
delta = (nx, ny)-self.points.mean(axis=0)
724
782
self.points += delta
784
def __reduce__(self):
785
state = (self.shapeparam, self.points, self.z())
786
return (self.__class__, (0,0,0,0), state)
726
788
assert_interfaces_valid(EllipseShape)
983
1040
self.shapeparam.update_range(self)
984
1041
self.sel_brush = QBrush(self.brush)
986
assert_interfaces_valid(XRangeSelection)
b'\\ No newline at end of file'
1043
assert_interfaces_valid(XRangeSelection)
1046
class Cursor(AbstractShape):
1047
"""Horizontal/Vertical cursor base class"""
1050
def __init__(self, pos, moveable=True):
1051
super(Cursor, self).__init__()
1053
self.handle_pos = None
1054
self.shapeparam = CursorShapeParam(_("Cursor"), icon=self.ICON)
1055
self.shapeparam.read_config(CONF, "histogram", "range")
1060
self.sel_symbol = None
1061
self.shapeparam.update_range(self) # creates all the above QObjects
1062
self._can_move = moveable
1063
self._can_resize = moveable
1065
def update_handle_pos(self, xMap, yMap, canvasRect):
1066
raise NotImplementedError
1068
def get_line(self, xMap, yMap, plot):
1069
"""Return line to be drawn (QLineF object)"""
1070
raise NotImplementedError
1072
def draw(self, painter, xMap, yMap, canvasRect):
1075
sym = self.sel_symbol
1080
pos1, pos2 = self.get_line(xMap, yMap, canvasRect)
1081
painter.drawLine(pos1, pos2)
1083
self.update_handle_pos(xMap, yMap, canvasRect)
1086
sym.draw(painter, QPoint(*self.handle_pos))
1088
def get_distance_from_point(self, point):
1089
raise NotImplementedError
1091
def hit_test(self, pos):
1092
dist = self.get_distance_from_point(pos)
1095
return dist, handle, inside, None
1097
def move_local_point_to(self, handle, pos, ctrl=None):
1098
"""Move a handle as returned by hit_test to the new position pos
1099
ctrl: True if <Ctrl> button is being pressed, False otherwise"""
1100
raise NotImplementedError
1102
def move_point_to(self, hnd, pos, ctrl=None):
1106
self.plot().emit(SIG_CURSOR_MOVED, self, self.pos)
1111
def set_pos(self, pos, dosignal=True):
1114
self.plot().emit(SIG_CURSOR_MOVED, self, self.pos)
1116
def move_shape(self, old_pos, new_pos):
1117
raise NotImplementedError
1119
def get_item_parameters(self, itemparams):
1120
self.shapeparam.update_param(self)
1121
itemparams.add("ShapeParam", self, self.shapeparam)
1123
def set_item_parameters(self, itemparams):
1124
update_dataset(self.shapeparam, itemparams.get("ShapeParam"),
1126
self.shapeparam.update_range(self)
1128
assert_interfaces_valid(Cursor)
1130
class VerticalCursor(Cursor):
1131
"""Vertical cursor"""
1132
ICON = 'vcursor.png'
1133
def update_handle_pos(self, xMap, yMap, canvasRect):
1134
x = xMap.transform(self.pos)
1135
y = canvasRect.center().y()
1136
self.handle_pos = x, y
1138
def get_line(self, xMap, yMap, canvasRect):
1139
"""Return line to be drawn (QLineF object)"""
1140
rect = QRectF(canvasRect)
1141
rect.setLeft(xMap.transform(self.pos))
1142
return rect.topLeft(), rect.bottomLeft()
1144
def get_distance_from_point(self, point):
1145
x, y = self.handle_pos
1146
return fabs(x-point.x())
1148
def move_local_point_to(self, handle, pos, ctrl=None):
1149
"""Move a handle as returned by hit_test to the new position pos
1150
ctrl: True if <Ctrl> button is being pressed, False otherwise"""
1151
val = self.plot().invTransform(self.xAxis(), pos.x())
1152
self.move_point_to(handle, (val, 0))
1154
def move_shape(self, old_pos, new_pos):
1155
dx = new_pos[0]-old_pos[0]
1157
self.plot().emit(SIG_CURSOR_MOVED, self, self.pos)
1158
self.plot().replot()
1160
assert_interfaces_valid(VerticalCursor)
1162
class HorizontalCursor(Cursor):
1163
"""Horizontal cursor"""
1164
ICON = 'hcursor.png'
1165
def update_handle_pos(self, xMap, yMap, canvasRect):
1166
x = canvasRect.center().x()
1167
y = yMap.transform(self.pos)
1168
self.handle_pos = x, y
1170
def get_line(self, xMap, yMap, canvasRect):
1171
"""Return line to be drawn (QLineF object)"""
1172
rect = QRectF(canvasRect)
1173
rect.setTop(yMap.transform(self.pos))
1174
return rect.topLeft(), rect.topRight()
1176
def get_distance_from_point(self, point):
1177
x, y = self.handle_pos
1178
return fabs(y-point.y())
1180
def move_local_point_to(self, handle, pos, ctrl=None):
1181
"""Move a handle as returned by hit_test to the new position pos
1182
ctrl: True if <Ctrl> button is being pressed, False otherwise"""
1183
val = self.plot().invTransform(self.yAxis(), pos.y())
1184
self.move_point_to(handle, (val, 0))
1186
def move_shape(self, old_pos, new_pos):
1187
dy = new_pos[0]-old_pos[0]
1189
self.plot().emit(SIG_CURSOR_MOVED, self, self.pos)
1190
self.plot().replot()
1192
assert_interfaces_valid(HorizontalCursor)