103
103
from PyQt4.QtGui import (QMenu, QListWidget, QListWidgetItem, QVBoxLayout,
104
104
QToolBar, QMessageBox, QBrush)
105
105
from PyQt4.QtCore import Qt, QPoint, QPointF, QLineF, SIGNAL, QRectF, QLine
106
from PyQt4.Qwt5 import QwtPlotCurve, QwtPlotGrid, QwtPlotItem, QwtScaleMap
108
107
from guidata.utils import assert_interfaces_valid, update_dataset
109
108
from guidata.configtools import get_icon, get_image_layout
110
109
from guidata.qthelpers import create_action, add_actions
112
from guiqwt.transitional import (QwtPlotCurve, QwtPlotGrid, QwtPlotItem,
113
114
from guiqwt.config import CONF, _
114
115
from guiqwt.interfaces import (IBasePlotItem, IDecoratorItemType,
115
116
ISerializableType, ICurveItemType,
116
117
ITrackableItemType, IPanel)
117
118
from guiqwt.panels import PanelWidget, ID_ITEMLIST
118
from guiqwt.baseplot import EnhancedQwtPlot
119
from guiqwt.baseplot import BasePlot
119
120
from guiqwt.styles import GridParam, CurveParam, ErrorBarParam, SymbolParam
120
121
from guiqwt.shapes import Marker
121
122
from guiqwt.signals import (SIG_ACTIVE_ITEM_CHANGED, SIG_ITEMS_CHANGED,
122
SIG_ITEM_REMOVED, SIG_AXIS_DIRECTION_CHANGED)
123
SIG_ITEM_REMOVED, SIG_AXIS_DIRECTION_CHANGED,
124
SIG_PLOT_AXIS_CHANGED)
125
127
def seg_dist(P, P0, P1):
240
251
def hit_test(self, pos):
241
252
return sys.maxint, 0, False, None
243
def move_local_point_to(self, handle, pos ):
254
def move_local_point_to(self, handle, pos, ctrl=None):
246
257
def move_local_shape(self, old_pos, new_pos):
249
def move_with_selection(self, dx, dy):
260
def move_with_selection(self, delta_x, delta_y):
252
263
def update_params(self):
563
583
def boundingRect(self):
564
584
"""Return the bounding rectangle of the data, error bars included"""
565
585
xmin, xmax, ymin, ymax = self.get_minmax_arrays()
586
if xmin is None or xmin.size == 0:
587
return super(ErrorBarCurveItem, self).boundingRect()
566
588
return QRectF( xmin.min(), ymin.min(),
567
589
xmax.max()-xmin.min(), ymax.max()-ymin.min() )
569
591
def draw(self, painter, xMap, yMap, canvasRect):
572
596
tx = vmap(xMap, x)
573
597
ty = vmap(yMap, y)
861
888
self.plot.del_items(items)
862
889
self.plot.replot()
863
890
for item in items:
864
self.parent().emit(SIG_ITEM_REMOVED, item)
891
self.plot.emit(SIG_ITEM_REMOVED, item)
867
894
class PlotItemList(PanelWidget):
868
895
"""Construct the `plot item list panel`"""
869
896
__implements__ = (IPanel,)
870
897
PANEL_ID = ID_ITEMLIST
898
PANEL_TITLE = _("Item list")
899
PANEL_ICON = "item_list.png"
872
901
def __init__(self, parent):
873
902
super(PlotItemList, self).__init__(parent)
874
widget_title = _("Item list")
875
widget_icon = "item_list.png"
876
903
self.manager = None
878
905
vlayout = QVBoxLayout()
879
906
self.setLayout(vlayout)
881
908
style = "<span style=\'color: #444444\'><b>%s</b></span>"
882
layout, _label = get_image_layout(widget_icon, style % widget_title,
909
layout, _label = get_image_layout(self.PANEL_ICON,
910
style % self.PANEL_TITLE,
883
911
alignment=Qt.AlignCenter)
884
912
vlayout.addLayout(layout)
885
913
self.listwidget = ItemListWidget(self)
888
916
toolbar = QToolBar(self)
889
917
vlayout.addWidget(toolbar)
890
918
add_actions(toolbar, self.listwidget.menu_actions)
892
self.setWindowIcon(get_icon(widget_icon))
893
self.setWindowTitle(widget_title)
895
920
def register_panel(self, manager):
896
921
"""Register panel to plot manager"""
897
922
self.manager = manager
898
923
self.listwidget.register_panel(manager)
925
def configure_panel(self):
926
"""Configure panel"""
900
929
assert_interfaces_valid(PlotItemList)
903
class CurvePlot(EnhancedQwtPlot):
932
class CurvePlot(BasePlot):
905
934
Construct a 2D curve plotting widget
906
(this class inherits :py:class:`guiqwt.baseplot.EnhancedQwtPlot`)
935
(this class inherits :py:class:`guiqwt.baseplot.BasePlot`)
907
936
* parent: parent widget
908
937
* title: plot title
909
938
* xlabel: (bottom axis title, top axis title) or bottom axis title only
910
939
* ylabel: (left axis title, right axis title) or left axis title only
911
940
* gridparam: GridParam instance
941
* axes_synchronised: keep all x and y axes synchronised when zomming or
913
944
AUTOSCALE_TYPES = (CurveItem,)
914
945
def __init__(self, parent=None, title=None, xlabel=None, ylabel=None,
915
gridparam=None, section="plot"):
946
gridparam=None, section="plot", axes_synchronised=False):
916
947
super(CurvePlot, self).__init__(parent, section)
918
949
self.axes_reverse = [False]*4
1011
1044
self.curve_marker.setVisible(False)
1012
1045
if vis_cross or vis_curve:
1048
def get_axes_to_update(self, dx, dy):
1049
if self.axes_synchronised:
1051
for axis_name in self.AXIS_NAMES:
1052
if axis_name in ("left", "right"):
1056
axes.append((d, self.get_axis_id(axis_name)))
1059
xaxis, yaxis = self.get_active_axes()
1060
return [(dx, xaxis), (dy, yaxis)]
1015
1062
def do_pan_view(self, dx, dy):
1020
1067
auto = self.autoReplot()
1021
1068
self.setAutoReplot(False)
1022
xaxis, yaxis = self.get_active_axes()
1023
active_axes = [ (dx, xaxis),
1025
for (x1, x0, _, w), k in active_axes:
1069
axes_to_update = self.get_axes_to_update(dx, dy)
1071
for (x1, x0, _, w), k in axes_to_update:
1026
1072
axis = self.axisScaleDiv(k)
1027
1073
# pour les axes logs on bouge lbound et hbound relativement
1028
1074
# à l'inverse du delta aux bords de l'axe
1048
1097
if lock_aspect_ratio:
1049
1098
sens, x1, x0, s, w = dx
1050
1099
F = 1+3*sens*float(x1-x0)/w
1051
xaxis, yaxis = self.get_active_axes()
1052
active_axes = [ (dx, xaxis),
1054
for (sens, x1, x0, s, w), k in active_axes:
1100
axes_to_update = self.get_axes_to_update(dx, dy)
1102
for (sens, x1, x0, s, w), k in axes_to_update:
1055
1103
axis = self.axisScaleDiv(k)
1056
1104
lbound = axis.lowerBound()
1057
1105
hbound = axis.upperBound()
1065
1113
self.setAxisScale(k, l_new, l_new + F*rng)
1066
1114
self.setAutoReplot(auto)
1116
# the signal MUST be emitted after replot, otherwise
1117
# we receiver won't see the new bounds (don't know why?)
1118
self.emit(SIG_PLOT_AXIS_CHANGED, self)
1069
1120
def do_zoom_rect_view(self, start, end):
1121
# XXX implement the case when axes are synchronised
1070
1122
x1, y1 = start.x(), start.y()
1071
1123
x2, y2 = end.x(), end.y()
1072
1124
xaxis, yaxis = self.get_active_axes()
1084
1136
self.setAxisScale(k, o1, o2)
1087
#---- EnhancedQwtPlot API --------------------------------------------------
1088
def get_axis_title(self, axis):
1090
Reimplement EnhancedQwtPlot method
1093
* axis: 'bottom', 'left', 'top' or 'right'
1095
if axis in self.AXES:
1096
axis = self.AXES[axis]
1097
return super(CurvePlot, self).get_axis_title(axis)
1099
def set_axis_title(self, axis, title):
1101
Reimplement EnhancedQwtPlot method
1104
* axis: 'bottom', 'left', 'top' or 'right'
1107
if axis in self.AXES:
1108
axis = self.AXES[axis]
1109
super(CurvePlot, self).set_axis_title(axis, title)
1111
def set_axis_font(self, axis, font):
1113
Reimplement EnhancedQwtPlot method
1116
* axis: 'bottom', 'left', 'top' or 'right'
1117
* font: QFont instance
1119
if axis in self.AXES:
1120
axis = self.AXES[axis]
1121
super(CurvePlot, self).set_axis_font(axis, font)
1123
def set_axis_color(self, axis, color):
1125
Reimplement EnhancedQwtPlot method
1128
* axis: 'bottom', 'left', 'top' or 'right'
1129
* color: color name (string) or QColor instance
1131
if axis in self.AXES:
1132
axis = self.AXES[axis]
1133
super(CurvePlot, self).set_axis_color(axis, color)
1139
#---- BasePlot API ---------------------------------------------------------
1135
1140
def add_item(self, item, z=None):
1137
1142
Add a *plot item* instance to this *plot widget*
1175
1180
def do_autoscale(self, replot=True):
1176
1181
"""Do autoscale on all axes"""
1178
for item in self.get_items():
1179
if isinstance(item, self.AUTOSCALE_TYPES) and not item.is_empty() \
1180
and item.isVisible():
1181
bounds = item.boundingRect()
1185
rect = rect.united(bounds)
1186
if rect is not None:
1187
x0, x1 = rect.left(), rect.right()
1188
y0, y1 = rect.top(), rect.bottom()
1189
if x0 == x1: # same behavior as MATLAB
1192
if y0 == y1: # same behavior as MATLAB
1195
self.set_plot_limits(x0, x1, y0, y1)
1182
# XXX implement the case when axes are synchronised
1183
for axis_id in self.AXIS_IDS:
1184
vmin, vmax = None, None
1185
if not self.axisEnabled(axis_id):
1187
for item in self.get_items():
1188
if isinstance(item, self.AUTOSCALE_TYPES) \
1189
and not item.is_empty() and item.isVisible():
1190
bounds = item.boundingRect()
1191
if axis_id == item.xAxis():
1192
xmin, xmax = bounds.left(), bounds.right()
1193
if vmin is None or xmin < vmin:
1195
if vmax is None or xmax > vmax:
1197
elif axis_id == item.yAxis():
1198
ymin, ymax = bounds.top(), bounds.bottom()
1199
if vmin is None or ymin < vmin:
1201
if vmax is None or ymax > vmax:
1203
if vmin is None or vmax is None:
1205
if vmin == vmax: # same behavior as MATLAB
1212
self.set_axis_limits(axis_id, vmin, vmax)
1216
def set_axis_limits(self, axis_id, vmin, vmax):
1217
"""Set axis limits (minimum and maximum values)"""
1218
axis_id = self.get_axis_id(axis_id)
1219
vmin, vmax = sorted([vmin, vmax])
1221
if self.get_axis_direction(axis_id):
1222
self.setAxisScale(axis_id, vmin+dv, vmin)
1224
self.setAxisScale(axis_id, vmin, vmin+dv)
1199
1226
#---- Public API -----------------------------------------------------------
1200
def get_axis_direction(self, axis):
1227
def get_axis_direction(self, axis_id):
1202
1229
Return axis direction of increasing values
1203
* axis: axis id (QwtPlot.yLeft, QwtPlot.xBottom, ...)
1230
* axis_id: axis id (BasePlot.Y_LEFT, BasePlot.X_BOTTOM, ...)
1204
1231
or string: 'bottom', 'left', 'top' or 'right'
1206
axis_id = self.AXES.get(axis, axis)
1233
axis_id = self.get_axis_id(axis_id)
1207
1234
return self.axes_reverse[axis_id]
1209
def set_axis_direction(self, axis, reverse=False):
1236
def set_axis_direction(self, axis_id, reverse=False):
1211
1238
Set axis direction of increasing values
1212
* axis: axis id (QwtPlot.yLeft, QwtPlot.xBottom, ...)
1239
* axis_id: axis id (BasePlot.Y_LEFT, BasePlot.X_BOTTOM, ...)
1213
1240
or string: 'bottom', 'left', 'top' or 'right'
1214
1241
* reverse: False (default)
1215
1242
- x-axis values increase from left to right
1274
1301
curve.setRenderHint(QwtPlotItem.RenderAntialiased,
1275
1302
self.antialiased)
1277
def set_plot_limits(self, x0, x1, y0, y1):
1304
def set_plot_limits(self, x0, x1, y0, y1, xaxis="bottom", yaxis="left"):
1278
1305
"""Set plot scale limits"""
1280
if self.get_axis_direction(self.yLeft):
1281
self.setAxisScale(self.yLeft, y0+dy, y0)
1283
self.setAxisScale(self.yLeft, y0, y0+dy)
1285
if self.get_axis_direction(self.xBottom):
1286
self.setAxisScale(self.xBottom, x0+dx, x0)
1288
self.setAxisScale(self.xBottom, x0, x0+dx)
1306
self.set_axis_limits(yaxis, y0, y1)
1307
self.set_axis_limits(xaxis, x0, x1)
1289
1308
self.updateAxes()
1290
self.emit(SIG_AXIS_DIRECTION_CHANGED, self, self.yLeft)
1291
self.emit(SIG_AXIS_DIRECTION_CHANGED, self, self.xBottom)
1309
self.emit(SIG_AXIS_DIRECTION_CHANGED, self, self.get_axis_id(yaxis))
1310
self.emit(SIG_AXIS_DIRECTION_CHANGED, self, self.get_axis_id(xaxis))
1312
def set_plot_limits_synchronised(self, x0, x1, y0, y1):
1313
for yaxis, xaxis in (("left", "bottom"), ("right", "top")):
1314
self.set_plot_limits(x0, x1, y0, y1, xaxis=xaxis, yaxis=yaxis)
1316
def get_plot_limits(self, xaxis="bottom", yaxis="left"):
1317
"""Return plot scale limits"""
1318
x0, x1 = self.get_axis_limits(xaxis)
1319
y0, y1 = self.get_axis_limits(yaxis)
1320
return x0, x1, y0, y1
b'\\ No newline at end of file'