GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Orange.widgets.utils.plot.OWPlot   F
last analyzed

Complexity

Total Complexity 399

Size/Duplication

Total Lines 1794
Duplicated Lines 0 %
Metric Value
dl 0
loc 1794
rs 0.6316
wmc 399

116 Methods

Rating   Name   Duplication   Size   Complexity  
A set_axis_scale() 0 15 3
A set_palette() 0 9 1
D mousePressEvent() 0 26 8
A setYRaxisTitle() 0 2 1
A transform_for_axes() 0 12 2
A setData() 0 3 1
B transform_from_rects() 0 13 7
F mouseStaticClick() 0 35 12
A start_progress() 0 3 2
A set_legend_margin() 0 4 1
B bounds_for_axis() 0 11 5
A setYLlabels() 0 2 1
A enableGridXB() 0 3 1
A inv_transform() 0 17 4
A set_axis_enabled() 0 5 2
A removeAllSelections() 0 3 1
A add_custom_axis() 0 6 1
A clear() 0 16 3
A legend() 0 5 1
A settings_list() 0 2 2
A set_axis_title() 0 3 2
A setShowYRaxisTitle() 0 4 3
A wheelEvent() 0 4 1
A activate_zooming() 0 6 1
B get_selected_points() 0 15 5
A transform_for_zoom() 0 11 2
D notify_legend_moved() 0 33 8
A zoom_in() 0 2 1
F mouseMoveEvent() 0 59 23
A setCanvasColor() 0 4 1
A add_marker() 0 7 1
A set_zoom_rect() 0 4 1
F mouseReleaseEvent() 0 26 12
A enableLRaxis() 0 2 1
A plot_data() 0 2 1
A send_selection() 0 3 2
A end_progress() 0 3 2
A color() 0 5 2
A update_theme() 0 10 4
A add_axis() 0 16 3
B axis_line() 0 17 6
B update_curves() 0 15 5
A zoom_to_rect() 0 8 3
A clear_markers() 0 7 2
A showEvent() 0 2 1
B update_legend() 0 30 6
A zoom() 0 16 4
A scrollContentsBy() 0 4 1
A set_show_axis_title() 0 6 3
B add_curve() 0 37 4
B remove_all_axes() 0 11 5
A event() 0 5 2
A legend_rect() 0 5 2
A isLegendEvent() 0 6 3
A shuffle_points() 0 3 2
A set_main_title() 0 6 1
D add_custom_curve() 0 29 8
A add_selection() 0 8 2
A update_zoom() 0 10 1
A rect_for_zoom() 0 9 1
C update_performance() 0 26 7
A activate_selection() 0 5 1
A get_zoom_rect() 0 5 2
A setYLaxisTitle() 0 2 1
A reset_zoom() 0 3 1
B ensure_inside() 0 16 7
B map_from_graph() 0 30 4
A zoom_back() 0 4 2
A set_show_main_title() 0 6 1
F getExampleTooltipText() 0 35 16
A activate_polygon_selection() 0 5 1
A save_to_file() 0 3 1
F mouse_action() 0 23 15
A setShowYLaxisTitle() 0 4 3
C data_rect_for_axes() 0 20 10
A zoom_out() 0 2 1
A save_to_file_direct() 0 3 1
A zoom_transform() 0 2 1
A replot() 0 19 1
A activate_panning() 0 5 1
A setXlabels() 0 5 3
A update_antialiasing() 0 5 2
A set_progress() 0 8 3
A activate_rectangle_selection() 0 6 1
A update_filled_symbols() 0 3 1
B transform() 0 17 5
D set_main_curve_data() 0 66 9
A setGridColor() 0 3 1
A enableYRaxis() 0 2 1
A resizeEvent() 0 7 3
B gestureEvent() 0 15 5
A enableGridYL() 0 3 1
A set_state() 0 6 3
A update_animations() 0 4 2
A update_grid() 0 4 1
A enableXaxis() 0 2 1
B map_to_graph() 0 29 4
A setYRlabels() 0 2 1
A set_axis_labels() 0 16 2
A clear_selection() 0 2 1
A showTip() 0 2 1
B __init__() 0 123 3
A remove_curve() 0 6 1
B animate() 0 15 6
A set_axis_autoscale() 0 5 3
A points_equal() 0 8 3
A get_legend_margin() 0 2 1
A setShowXaxisTitle() 0 4 3
A setXaxisTitle() 0 2 1
A set_axis_tick_length() 0 3 2
A is_axis_auto_scale() 0 4 2
A pan() 0 12 2
A graph_area_rect() 0 2 1
A axis_coordinate() 0 8 3
F update_layout() 0 94 25
F update_axes() 0 43 18

How to fix   Complexity   

Complex Class

Complex classes like Orange.widgets.utils.plot.OWPlot often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
'''
2
3
#################
4
Plot (``owplot``)
5
#################
6
7
.. autoclass:: OrangeWidgets.plot.OWPlot
8
9
'''
10
11
from PyQt4 import QtCore, QtGui
0 ignored issues
show
Configuration introduced by
The import PyQt4 could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
Unused Code introduced by
Unused QtCore imported from PyQt4
Loading history...
12
from Orange.widgets.gui import OWComponent
13
from Orange.widgets.settings import Setting
14
15
LeftLegend = 0
16
RightLegend = 1
17
BottomLegend = 2
18
TopLegend = 3
19
ExternalLegend = 4
20
21
UNUSED_ATTRIBUTES_STR = 'unused attributes'
22
23
from .owaxis import *
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in pow.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
Coding Style introduced by
The usage of wildcard imports like owaxis should generally be avoided.
Loading history...
Unused Code introduced by
QGraphicsItem was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
degrees was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
sqrt was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
acosh was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
copysign was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
isfinite was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QPainterPath was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
pi was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
asinh was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
NoPosition was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
SELECTION_REPLACE was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
e was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
atanh was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
TitleBelow was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
radians was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
isnan was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QGraphicsPathItem was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
tan was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
trunc was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
ProbabilitiesZValue was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
sinh was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QGraphicsLineItem was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
TitleAbove was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
SELECTION_REMOVE was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
erf was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
fsum was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
gamma was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
AxisEnd was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
hypot was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
ceil was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
exp was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
fabs was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
expm1 was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
cos was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
frexp was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
log10 was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
modf was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
resize_plot_item_list was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
ldexp was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
factorial was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
SELECTION_TOGGLE was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
fmod was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
AxisZValue was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
HighlightZValue was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
AxisStart was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
log1p was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
lgamma was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
ROTATING was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
log2 was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
SELECTION_ADD was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
PlotZValue was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
sin was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
atan2 was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
atan was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
log was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
acos was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
isinf was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
cosh was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
asin was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
tanh was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QFontMetrics was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
floor was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
erfc was imported with wildcard, but is not used.
Loading history...
24
from .owcurve import *
0 ignored issues
show
Coding Style introduced by
The usage of wildcard imports like owcurve should generally be avoided.
Loading history...
Unused Code introduced by
OWPlotItem was imported with wildcard, but is not used.
Loading history...
25
from .owlegend import *
0 ignored issues
show
Coding Style introduced by
The usage of wildcard imports like owlegend should generally be avoided.
Loading history...
Unused Code introduced by
OWLegendGradient was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
OWLegendTitle was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
OWLegendItem was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QSizeF was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
PointSymbol was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QGraphicsObject was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
PointSize was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
move_item_xy was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
PointColor was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QLinearGradient was imported with wildcard, but is not used.
Loading history...
26
from .owplotgui import OWPlotGUI
27
from .owtools import *
0 ignored issues
show
Coding Style introduced by
The usage of wildcard imports like owtools should generally be avoided.
Loading history...
Unused Code introduced by
ColorPaletteDlg was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QGraphicsPixmapItem was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
qVersion was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
move_item was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
CircleCurve was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QGraphicsPolygonItem was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
qRgb was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
Orange was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
PolygonCurve was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
get_variable_values_sorted was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QImage was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QPolygonF was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
ProbabilitiesItem was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
RectangleCurve was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QPixmap was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
QGraphicsEllipseItem was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
UnconnectedLinesCurve was imported with wildcard, but is not used.
Loading history...
28
29
from ..colorpalette import ColorPaletteGenerator
30
31
from PyQt4.QtGui import (
0 ignored issues
show
Configuration introduced by
The import PyQt4.QtGui could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
32
    QPen, QBrush, QColor,
33
    QGraphicsView,  QGraphicsScene, QPainter, QTransform, QPolygonF,
34
    QGraphicsRectItem)
35
36
from PyQt4.QtCore import QPointF, QPropertyAnimation, pyqtProperty, SIGNAL, Qt, QEvent
0 ignored issues
show
Configuration introduced by
The import PyQt4.QtCore could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
37
38
## Color values copied from orngView.SchemaView for consistency
39
SelectionPen = QPen(QBrush(QColor(51, 153, 255, 192)),
40
                    1, Qt.SolidLine, Qt.RoundCap)
41
SelectionBrush = QBrush(QColor(168, 202, 236, 192))
42
43
#from OWDlgs import OWChooseImageSizeDlg
44
#from OWColorPalette import *      # color palletes, ...
45
#from Orange.utils import deprecated_members, deprecated_attribute
46
47
import orangeqt
0 ignored issues
show
Configuration introduced by
The import orangeqt could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
48
49
def n_min(*args):
50
    lst = args[0] if len(args) == 1 else args
51
    a = [i for i in lst if i is not None]
52
    return min(a) if a else None
53
54
def n_max(*args):
55
    lst = args[0] if len(args) == 1 else args
56
    a = [i for i in lst if i is not None]
57
    return max(a) if a else None
58
59
name_map = {
60
    "saveToFileDirect": "save_to_file_direct",
61
    "saveToFile" : "save_to_file",
62
    "addCurve" : "add_curve",
63
    "addMarker" : "add_marker",
64
    "updateLayout" : "update_layout",
65
    "activateZooming" : "activate_zooming",
66
    "activateSelection" : "activate_selection",
67
    "activateRectangleSelection" : "activate_rectangle_selection",
68
    "activatePolygonSelection" : "activate_polygon_selection",
69
    "activatePanning" : "activate_panning",
70
    "getSelectedPoints" : "get_selected_points",
71
    "setAxisScale" : "set_axis_scale",
72
    "setAxisLabels" : "set_axis_labels",
73
    "setAxisAutoScale" : "set_axis_autoscale",
74
    "setTickLength" : "set_axis_tick_length",
75
    "updateCurves" : "update_curves",
76
    "itemList" : "plot_items",
77
    "setShowMainTitle" : "set_show_main_title",
78
    "setMainTitle" : "set_main_title",
79
    "invTransform" : "inv_transform",
80
    "setAxisTitle" : "set_axis_title",
81
    "setShowAxisTitle" : "set_show_axis_title"
82
}
83
84
#@deprecated_members(name_map, wrap_methods=list(name_map.keys()))
85
class OWPlot(orangeqt.Plot, OWComponent):
86
    """
87
    The base class for all plots in Orange. It uses the Qt Graphics View Framework
88
    to draw elements on a graph.
89
90
    **Plot layout**
91
92
        .. attribute:: show_legend
93
94
            A boolean controlling whether the legend is displayed or not
95
96
        .. attribute:: show_main_title
97
98
            Controls whether or not the main plot title is displayed
99
100
        .. attribute:: main_title
101
102
            The plot title, usually show on top of the plot
103
104
        .. automethod:: set_main_title
105
106
        .. automethod:: set_show_main_title
107
108
        .. attribute:: axis_margin
109
110
            How much space (in pixels) should be left on each side for the axis, its label and its title.
111
112
        .. attribute:: title_margin
113
114
            How much space (in pixels) should be left at the top of the plot for the title, if the title is shown.
115
116
            .. seealso:: attribute :attr:`show_main_title`
117
118
        .. attribute:: plot_margin
119
120
            How much space (in pixels) should be left at each side of the plot as whitespace.
121
122
123
    **Coordinate transformation**
124
125
        There are several coordinate systems used by OWPlot:
126
127
        * `widget` coordinates.
128
129
          This is the coordinate system of the position returned by :meth:`.QEvent.pos()`.
130
          No calculations or positions is done with this coordinates, they must first be converted
131
          to scene coordinates with :meth:`mapToScene`.
132
133
        * `data` coordinates.
134
135
          The value used internally in Orange to specify the values of attributes.
136
          For example, this can be age in years, the number of legs, or any other numeric value.
137
138
        * `plot` coordinates.
139
140
          These coordinates specify where the plot items are placed on the graph, but doesn't account for zoom.
141
          They can be retrieved for a particular plot item with :meth:`.PlotItem.pos()`.
142
143
        * `scene` or `zoom` coordinates.
144
145
          Like plot coordinates, except that they take the :attr:`zoom_transform` into account. They represent the
146
          actual position of an item on the scene.
147
148
          These are the coordinates returned by :meth:`.PlotItem.scenePos()` and :meth:`mapToScene`.
149
150
          For example, they can be used to determine what is under the cursor.
151
152
        In most cases, you will use data coordinates for interacting with the actual data, and scene coordinates for
153
        interacting with the plot items. The other two sets are mostly used for converting.
154
155
        .. automethod:: map_to_graph
156
157
        .. automethod:: map_from_graph
158
159
        .. automethod:: transform
160
161
        .. automethod:: inv_transform
162
163
        .. method:: nearest_point(pos)
164
165
            Returns the point nearest to ``pos``, or ``None`` if no point is close enough.
166
167
            :param pos: The position in scene coordinates
168
            :type pos: QPointF
169
170
            :rtype: :obj:`.OWPoint`
171
172
        .. method:: point_at(pos)
173
174
            If there is a point with data coordinates equal to ``pos``, if is returned.
175
            Otherwise, this function returns None.
176
177
            :param pos: The position in data coordinates
178
            :type pos: tuple of float float
179
180
            :rtype: :obj:`.OWPoint`
181
182
183
    **Data curves**
184
        The preferred method for showing a series of data points is :meth:`set_main_curve_data`.
185
        It allows you to specify point positions, colors, labels, sizes and shapes.
186
187
        .. automethod:: set_main_curve_data
188
189
        .. automethod:: add_curve
190
191
        .. automethod:: add_custom_curve
192
193
        .. automethod:: add_marker
194
195
        .. method:: add_item(item)
196
197
            Adds any PlotItem ``item`` to this plot.
198
            Calling this function directly is useful for adding a :obj:`.Marker` or another object that does not have to appear in the legend.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (142/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
199
            For data curves, consider using :meth:`add_custom_curve` instead.
200
201
        .. method:: plot_items()
202
203
            Returns the list of all plot items added to this graph with :meth:`add_item` or :meth:`.PlotItem.attach`.
204
205
    **Axes**
206
207
        .. automethod:: add_axis
208
209
        .. automethod:: add_custom_axis
210
211
        .. automethod:: set_axis_enabled
212
213
        .. automethod:: set_axis_labels
214
215
        .. automethod:: set_axis_scale
216
217
    **Settings**
218
219
	.. attribute:: gui
220
221
            An :obj:`.OWPlotGUI` object associated with this graph
222
223
    **Point Selection and Marking**
224
225
        There are four possible selection behaviors used for selecting or marking points in OWPlot.
226
        They are used in :meth:`select_points` and :meth:`mark_points` and are the same for both operations.
227
228
        .. data:: AddSelection
229
230
            The points are added to the selection, without affected the currently selected points
231
232
        .. data:: RemoveSelection
233
234
            The points are removed from the selection, without affected the currently selected points
235
236
        .. data:: ToggleSelection
237
238
            The points' selection state is toggled
239
240
        .. data:: ReplaceSelection
241
242
            The current selection is replaced with the new one
243
244
        .. note:: There are exactly the same functions for point selection and marking.
245
                For simplicity, they are only documented once.
246
247
        .. method:: select_points(area, behavior)
248
        .. method:: mark_points(area, behavior)
249
250
            Selects or marks all points inside the ``area``
251
252
            :param area: The newly selected/marked area
253
            :type area: QRectF or QPolygonF
254
255
            :param behavior: :data:`AddSelection`, :data:`RemoveSelection`, :data:`ToggleSelection` or :data:`ReplaceSelection`
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (127/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
256
            :type behavior: int
257
258
        .. method:: unselect_all_points()
259
        .. method:: unmark_all_points()
260
261
            Unselects or unmarks all the points in the plot
262
263
        .. method:: selected_points()
264
        .. method:: marked_points()
265
266
            Returns a list of all selected or marked points
267
268
            :rtype: list of OWPoint
269
270
        .. method:: selected_points(xData, yData)
271
272
            For each of the point specified by ``xData`` and ``yData``, the point's selection state is returned.
273
274
            :param xData: The list of x coordinates
275
            :type xData: list of float
276
277
            :param yData: The list of y coordinates
278
            :type yData: list of float
279
280
            :rtype: list of int
281
282
    **Color schemes**
283
284
        By default, OWPlot uses the application's system palette for drawing everything
285
        except data curves and points. This way, it maintains consistency with other application
286
        with regards to the user interface.
287
288
        If data is plotted with no color specified, it will use a system color as well,
289
        so that a good contrast with the background in guaranteed.
290
291
        OWPlot uses the :meth:`.OWidget.palette` to determine its color scheme, so it can be
292
        changed using :meth:`.QWidget.setPalette`. There are also two predefined color schemes:
293
        ``OWPalette.Dark`` and ``OWPalette.Light``, which provides a dark and a light scheme
294
        respectively.
295
296
        .. attribute:: theme_name
297
298
            A string attribute with three possible values:
299
            ==============  ===========================
300
            Value           Meaning
301
            --------------  ---------------------------
302
            "default"       The system palette is used
303
            "dark"          The dark theme is used
304
            "light"         The light theme is used
305
            ==============  ===========================
306
307
            To apply the settings, first set this attribute's value, and then call :meth:`update_theme`
308
309
        .. automethod:: update_theme
310
311
        On the other hand, curves with a specified color will use colors from Orange's palette,
312
        which can be configured within Orange. Each plot contains two separate palettes:
313
        one for continuous attributes, and one for discrete ones. Both are created by
314
        :obj:`.OWColorPalette.ColorPaletteGenerator`
315
316
        .. attribute:: continuous_palette
317
318
            The palette used when point color represents a continuous attribute
319
320
        .. attribute:: discrete_palette
321
322
            The palette used when point color represents a discrete attribute
323
324
    """
325
326
    point_settings = ["point_width", "alpha_value"]
327
    plot_settings = ["show_legend", "show_grid"]
328
329
    alpha_value = Setting(255)
330
331
    show_legend = Setting(False)
332
    show_grid = Setting(False)
333
334
335
    appearance_settings = ["antialias_plot", "animate_plot", "animate_points", "disable_animations_threshold", "auto_adjust_performance"]
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (137/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
336
337
    def settings_list(self, graph_name, settings):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
338
        return [graph_name + '.' + setting for setting in settings]
339
340
    def __init__(self, parent = None,  name = "None",  show_legend = 1, axes = [xBottom, yLeft], widget = None):
0 ignored issues
show
Bug Best Practice introduced by
The default value [] might cause unintended side-effects.

Objects as default values are only created once in Python and not on each invocation of the function. If the default object is modified, this modification is carried over to the next invocation of the method.

# Bad:
# If array_param is modified inside the function, the next invocation will
# receive the modified object.
def some_function(array_param=[]):
    # ...

# Better: Create an array on each invocation
def some_function(array_param=None):
    array_param = array_param or []
    # ...
Loading history...
Unused Code introduced by
The argument show_legend seems to be unused.
Loading history...
341
        """
342
            Creates a new graph
343
344
            If your visualization uses axes other than ``xBottom`` and ``yLeft``, specify them in the
345
            ``axes`` parameter. To use non-cartesian axes, set ``axes`` to an empty list
346
            and add custom axes with :meth:`add_axis` or :meth:`add_custom_axis`
347
        """
348
        orangeqt.Plot.__init__(self, parent)
349
        OWComponent.__init__(self, widget)
350
        self.widget = widget
351
        self.parent_name = name
352
        self.title_item = None
353
354
        self.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing)
355
356
        self._legend = OWLegend(self, self.scene())
357
        self._legend.setZValue(LegendZValue)
358
        self._legend_margin = QRectF(0, 0, 100, 0)
359
        self._legend_moved = False
360
        self.axes = dict()
361
362
        self.axis_margin = 50
363
        self.y_axis_extra_margin = 30
364
        self.title_margin = 40
365
        self.graph_margin = 10
366
367
        self.mainTitle = None
368
        self.showMainTitle = False
369
        self.XaxisTitle = None
370
        self.YLaxisTitle = None
371
        self.YRaxisTitle = None
372
373
        # Method aliases, because there are some methods with different names but same functions
374
        self.setCanvasBackground = self.setCanvasColor
375
        self.map_from_widget = self.mapToScene
376
377
        # OWScatterPlot needs these:
378
        self.point_width = 5
379
        self.show_filled_symbols = True
380
        self.show_grid = True
381
382
        self.curveSymbols = list(range(13))
383
        self.tips = TooltipManager(self)
384
        self.setMouseTracking(True)
385
        self.grabGesture(Qt.PinchGesture)
386
        self.grabGesture(Qt.PanGesture)
387
388
        self.state = NOTHING
389
        self._pressed_mouse_button = Qt.NoButton
390
        self._pressed_point = None
391
        self.selection_items = []
392
        self._current_rs_item = None
393
        self._current_ps_item = None
394
        self.polygon_close_treshold = 10
395
        self.sendSelectionOnUpdate = False
396
        self.auto_send_selection_callback = None
397
398
        self.data_range = {}
399
        self.map_transform = QTransform()
400
        self.graph_area = QRectF()
401
402
        ## Performance optimization
403
        self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
404
        self.scene().setItemIndexMethod(QGraphicsScene.NoIndex)
405
406
        self.animate_plot = True
407
        self.animate_points = True
408
        self.antialias_plot = True
409
        self.antialias_points = True
410
        self.antialias_lines = True
411
412
        self.auto_adjust_performance = True
413
        self.disable_animations_threshold = 5000
414
     #   self.setInteractive(False)
415
416
        self.warn_unused_attributes = False
417
418
        self._bounds_cache = {}
419
        self._transform_cache = {}
420
        self.block_update = False
421
422
        self.use_animations = True
423
        self._animations = []
424
425
        ## Mouse event handlers
426
        self.mousePressEventHandler = None
427
        self.mouseMoveEventHandler = None
428
        self.mouseReleaseEventHandler = None
429
        self.mouseStaticClickHandler = self.mouseStaticClick
430
        self.static_click = False
431
432
        self._marker_items = []
433
        self.grid_curve = PlotGrid(self)
434
435
        self._zoom_rect = None
436
        self._zoom_transform = QTransform()
437
        self.zoom_stack = []
438
        self.old_legend_margin = None
439
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
440
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
441
442
        ## Add specified axes:
443
444
        for key in axes:
445
            if key in [yLeft, xTop]:
446
                self.add_axis(key, title_above=1)
447
            else:
448
                self.add_axis(key)
449
450
        self.continuous_palette = ColorPaletteGenerator(number_of_colors= -1)
451
        self.discrete_palette = ColorPaletteGenerator()
452
453
        self.gui = OWPlotGUI(self)
454
        """
455
            An :obj:`.OWPlotGUI` object associated with this plot
456
    	"""
457
        self.activate_zooming()
458
        self.selection_behavior = self.AddSelection
459
460
        self.main_curve = None
461
462
        self.replot()
463
464
#    selectionCurveList = deprecated_attribute("selectionCurveList", "selection_items")
465
#    autoSendSelectionCallback = deprecated_attribute("autoSendSelectionCallback", "auto_send_selection_callback")
466
#    showLegend = deprecated_attribute("showLegend", "show_legend")
467
#    pointWidth = deprecated_attribute("pointWidth", "point_width")
468
#    alphaValue = deprecated_attribute("alphaValue", "alpha_value")
469
#    useAntialiasing = deprecated_attribute("useAntialiasing", "use_antialiasing")
470
#    showFilledSymbols = deprecated_attribute("showFilledSymbols", "show_filled_symbols")
471
#    mainTitle = deprecated_attribute("mainTitle", "main_title")
472
#    showMainTitle = deprecated_attribute("showMainTitle", "show_main_title")
473
#    gridCurve = deprecated_attribute("gridCurve", "grid_curve")
474
#    contPalette = deprecated_attribute("contPalette", "continuous_palette")
475
#    discPalette = deprecated_attribute("discPalette", "discrete_palette")
476
477
    def scrollContentsBy(self, dx, dy):
478
        # This is overriden here to prevent scrolling with mouse and keyboard
479
        # Instead of moving the contents, we simply do nothing
480
        pass
481
482
    def graph_area_rect(self):
483
        return self.graph_area
484
485
    def map_to_graph(self, point, axes = None, zoom = False):
486
        '''
487
            Maps ``point``, which can be ether a tuple of (x,y), a QPoint or a QPointF, from data coordinates
488
            to plot coordinates.
489
490
            :param point: The point in data coordinates
491
            :type point: tuple or QPointF
492
493
            :param axes: The pair of axes along which to transform the point.
494
                         If none are specified, (xBottom, yLeft) will be used.
495
            :type axes: tuple of float float
496
497
            :param zoom: if ``True``, the current :attr:`zoom_transform` will be considered in the transformation, and the result will be in scene coordinates instead.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (167/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
498
            :type zoom: int
499
500
            :return: The transformed point in scene coordinates
501
            :type: tuple of float float
502
        '''
503
        if type(point) == tuple:
504
            (x, y) = point
505
            point = QPointF(x, y)
506
        if axes:
507
            x_id, y_id = axes
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are trying to unpack a non-sequence, which was defined at line 485.
Loading history...
508
            point = point * self.transform_for_axes(x_id, y_id)
509
        else:
510
            point = point * self.map_transform
511
        if zoom:
512
            point = point * self._zoom_transform
513
        return (point.x(), point.y())
514
515
    def map_from_graph(self, point, axes = None, zoom = False):
516
        '''
517
            Maps ``point``, which can be ether a tuple of (x,y), a QPoint or a QPointF, from plot coordinates
518
            to data coordinates.
519
520
            :param point: The point in data coordinates
521
            :type point: tuple or QPointF
522
523
            :param axes: The pair of axes along which to transform the point. If none are specified, (xBottom, yLeft) will be used.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (131/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
524
            :type axes: tuple of float float
525
526
            :param zoom: if ``True``, the current :attr:`zoom_transform` will be considered in the transformation, and the ``point`` should be in scene coordinates instead.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (172/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
527
            :type zoom: int
528
529
            :returns: The transformed point in data coordinates
530
            :rtype: tuple of float float
531
        '''
532
        if type(point) == tuple:
533
            (x, y) = point
534
            point = QPointF(x,y)
535
        if zoom:
536
            t, ok = self._zoom_transform.inverted()
0 ignored issues
show
Unused Code introduced by
The variable ok seems to be unused.
Loading history...
537
            point = point * t
538
        if axes:
539
            x_id, y_id = axes
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are trying to unpack a non-sequence, which was defined at line 515.
Loading history...
540
            t, ok = self.transform_for_axes(x_id, y_id).inverted()
541
        else:
542
            t, ok = self.map_transform.inverted()
543
        ret = point * t
544
        return (ret.x(), ret.y())
545
546
    def save_to_file(self, extraButtons = []):
0 ignored issues
show
Bug Best Practice introduced by
The default value [] might cause unintended side-effects.

Objects as default values are only created once in Python and not on each invocation of the function. If the default object is modified, this modification is carried over to the next invocation of the method.

# Bad:
# If array_param is modified inside the function, the next invocation will
# receive the modified object.
def some_function(array_param=[]):
    # ...

# Better: Create an array on each invocation
def some_function(array_param=None):
    array_param = array_param or []
    # ...
Loading history...
547
        sizeDlg = OWChooseImageSizeDlg(self, extraButtons, parent=self)
548
        sizeDlg.exec_()
549
550
    def save_to_file_direct(self, fileName, size = None):
551
        sizeDlg = OWChooseImageSizeDlg(self)
552
        sizeDlg.saveImage(fileName, size)
553
554
    def activate_zooming(self):
555
        '''
556
            Activates the zooming mode, where the user can zoom in and out with a single mouse click
557
            or by dragging the mouse to form a rectangular area
558
        '''
559
        self.state = ZOOMING
560
561
    def activate_rectangle_selection(self):
562
        '''
563
            Activates the rectangle selection mode, where the user can select points in a rectangular area
564
            by dragging the mouse over them
565
        '''
566
        self.state = SELECT_RECTANGLE
567
568
    def activate_selection(self):
569
        '''
570
            Activates the point selection mode, where the user can select points by clicking on them
571
        '''
572
        self.state = SELECT
573
574
    def activate_polygon_selection(self):
575
        '''
576
            Activates the polygon selection mode, where the user can select points by drawing a polygon around them
577
        '''
578
        self.state = SELECT_POLYGON
579
580
    def activate_panning(self):
581
        '''
582
            Activates the panning mode, where the user can move the zoom projection by dragging the mouse
583
        '''
584
        self.state = PANNING
585
586
    def set_show_main_title(self, b):
587
        '''
588
            Shows the main title if ``b`` is ``True``, and hides it otherwise.
589
        '''
590
        self.showMainTitle = b
591
        self.replot()
592
593
    def set_main_title(self, t):
594
        '''
595
            Sets the main title to ``t``
596
        '''
597
        self.mainTitle = t
598
        self.replot()
599
600
    def setShowXaxisTitle(self, b = -1):
601
        if b == -1 and hasattr(self, 'showXaxisTitle'):
602
            b = self.showXaxisTitle
603
        self.set_show_axis_title(xBottom, b)
604
605
    def setXaxisTitle(self, title):
606
        self.set_axis_title(xBottom, title)
607
608
    def setShowYLaxisTitle(self, b = -1):
609
        if b == -1 and hasattr(self, 'showYLaxisTitle'):
610
            b = self.showYLaxisTitle
611
        self.set_show_axis_title(yLeft, b)
612
613
    def setYLaxisTitle(self, title):
614
        self.set_axis_title(yLeft, title)
615
616
    def setShowYRaxisTitle(self, b = -1):
617
        if b == -1 and hasattr(self, 'showYRaxisTitle'):
618
            b = self.showYRaxisTitle
619
        self.set_show_axis_title(yRight, b)
620
621
    def setYRaxisTitle(self, title):
622
        self.set_axis_title(yRight, title)
623
624
    def enableGridXB(self, b):
625
        self.grid_curve.set_x_enabled(b)
626
        self.replot()
627
628
    def enableGridYL(self, b):
629
        self.grid_curve.set_y_enabled(b)
630
        self.replot()
631
632
    def setGridColor(self, c):
633
        self.grid_curve.set_pen(QPen(c))
634
        self.replot()
635
636
    def setCanvasColor(self, c):
637
        p = self.palette()
638
        p.setColor(OWPalette.Canvas, c)
639
        self.set_palette(p)
640
641
    def setData(self, data):
0 ignored issues
show
Unused Code introduced by
The argument data seems to be unused.
Loading history...
642
        self.clear()
643
        self.replot()
644
645
    def setXlabels(self, labels):
646
        if xBottom in self.axes:
647
            self.set_axis_labels(xBottom, labels)
648
        elif xTop in self.axes:
649
            self.set_axis_labels(xTop, labels)
650
651
    def set_axis_autoscale(self, axis_id):
652
        if axis_id in self.axes:
653
            self.axes[axis_id].auto_scale = True
654
        elif axis_id in self.data_range:
655
            del self.data_range[axis_id]
656
657
    def set_axis_labels(self, axis_id, labels, values=None):
658
        '''
659
            Sets the labels of axis ``axis_id`` to ``labels``. This is used for axes displaying a discrete data type.
660
661
            :param labels: The ID of the axis to change
662
            :type labels: int
663
664
            :param labels: The list of labels to be displayed along the axis
665
            :type labels: A list of strings
666
667
            .. note:: This changes the axis scale and removes any previous scale set with :meth:`set_axis_scale`.
668
        '''
669
        if axis_id in self._bounds_cache:
670
            del self._bounds_cache[axis_id]
671
        self._transform_cache = {}
672
        self.axes[axis_id].set_labels(labels, values)
673
674
    def set_axis_scale(self, axis_id, min, max, step_size=0):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in max.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
Bug Best Practice introduced by
This seems to re-define the built-in min.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
675
        '''
676
            Sets the scale of axis ``axis_id`` to show an interval between ``min`` and ``max``.
677
            If ``step`` is specified and non-zero, it determines the steps between label on the axis.
678
            Otherwise, they are calculated automatically.
679
680
            .. note:: This changes the axis scale and removes any previous labels set with :meth:`set_axis_labels`.
681
        '''
682
        if axis_id in self._bounds_cache:
683
            del self._bounds_cache[axis_id]
684
        self._transform_cache = {}
685
        if axis_id in self.axes:
686
            self.axes[axis_id].set_scale(min, max, step_size)
687
        else:
688
            self.data_range[axis_id] = (min, max)
689
690
    def set_axis_title(self, axis_id, title):
691
        if axis_id in self.axes:
692
            self.axes[axis_id].set_title(title)
693
694
    def set_show_axis_title(self, axis_id, b):
695
        if axis_id in self.axes:
696
            if b == -1:
697
                b = not self.axes[axis_id].show_title
698
            self.axes[axis_id].set_show_title(b)
699
            self.replot()
700
701
    def set_axis_tick_length(self, axis_id, minor, medium, major):
702
        if axis_id in self.axes:
703
            self.axes[axis_id].set_tick_legth(minor, medium, major)
704
705
    def setYLlabels(self, labels):
706
        self.set_axis_labels(yLeft, labels)
707
708
    def setYRlabels(self, labels):
709
        self.set_axis_labels(yRight, labels)
710
711
    def add_custom_curve(self, curve, enableLegend = False):
712
        '''
713
            Adds a custom PlotItem ``curve`` to the plot.
714
            If ``enableLegend`` is ``True``, a curve symbol defined by
715
            :meth:`.OWCurve.point_item` and the ``curve``'s name
716
            :obj:`.OWCurve.name` is added to the legend.
717
718
            This function recalculates axis bounds and replots the plot if needed.
719
720
            :param curve: The curve to add
721
            :type curve: :obj:`.OWCurve`
722
        '''
723
        self.add_item(curve)
724
        if enableLegend:
725
            self.legend().add_curve(curve)
726
        for key in [curve.axes()]:
727
            if key in self._bounds_cache:
728
                del self._bounds_cache[key]
729
        self._transform_cache = {}
730
        if hasattr(curve, 'tooltip'):
731
            curve.setToolTip(curve.tooltip)
732
        x,y = curve.axes()
733
        if curve.is_auto_scale() and (self.is_axis_auto_scale(x) or self.is_axis_auto_scale(y)):
734
            self.set_dirty()
735
            self.replot()
736
        else:
737
            curve.set_graph_transform(self.transform_for_axes(x,y))
738
            curve.update_properties()
739
        return curve
740
741
    def add_curve(self, name, brushColor = None, penColor = None, size = 5, style = Qt.NoPen,
0 ignored issues
show
Bug Best Practice introduced by
The default value [] might cause unintended side-effects.

Objects as default values are only created once in Python and not on each invocation of the function. If the default object is modified, this modification is carried over to the next invocation of the method.

# Bad:
# If array_param is modified inside the function, the next invocation will
# receive the modified object.
def some_function(array_param=[]):
    # ...

# Better: Create an array on each invocation
def some_function(array_param=None):
    array_param = array_param or []
    # ...
Loading history...
742
                 symbol = OWPoint.Ellipse, enableLegend = False, xData = [], yData = [], showFilledSymbols = None,
0 ignored issues
show
Unused Code introduced by
The argument showFilledSymbols seems to be unused.
Loading history...
743
                 lineWidth = 1, pen = None, autoScale = 0, antiAlias = None, penAlpha = 255, brushAlpha = 255,
0 ignored issues
show
Unused Code introduced by
The argument penAlpha seems to be unused.
Loading history...
Unused Code introduced by
The argument antiAlias seems to be unused.
Loading history...
Unused Code introduced by
The argument brushAlpha seems to be unused.
Loading history...
744
                 x_axis_key = xBottom, y_axis_key = yLeft):
745
        '''
746
            Creates a new :obj:`.OWCurve` with the specified parameters and adds it to the graph.
747
            If ``enableLegend`` is ``True``, a curve symbol is added to the legend.
748
        '''
749
        c = OWCurve(xData, yData, x_axis_key, y_axis_key, tooltip=name)
750
        c.set_zoom_transform(self._zoom_transform)
751
        c.name = name
752
        c.set_style(style)
753
754
        if not brushColor:
755
            brushColor = self.color(OWPalette.Data)
756
        if not penColor:
757
            penColor = self.color(OWPalette.Data)
758
759
        c.set_color(penColor)
760
761
        if pen:
762
            p = pen
763
        else:
764
            p = QPen()
765
            p.setColor(penColor)
766
            p.setWidth(lineWidth)
767
        c.set_pen(p)
768
769
        c.set_brush(brushColor)
770
771
        c.set_symbol(symbol)
772
        c.set_point_size(size)
773
        c.set_data(xData,  yData)
774
775
        c.set_auto_scale(autoScale)
776
777
        return self.add_custom_curve(c, enableLegend)
778
779
    def set_main_curve_data(self, x_data, y_data, color_data, label_data, size_data, shape_data, marked_data = [], valid_data = [], x_axis_key=xBottom, y_axis_key=yLeft):
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (170/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Bug Best Practice introduced by
The default value [] might cause unintended side-effects.

Objects as default values are only created once in Python and not on each invocation of the function. If the default object is modified, this modification is carried over to the next invocation of the method.

# Bad:
# If array_param is modified inside the function, the next invocation will
# receive the modified object.
def some_function(array_param=[]):
    # ...

# Better: Create an array on each invocation
def some_function(array_param=None):
    array_param = array_param or []
    # ...
Loading history...
780
        """
781
            Creates a single curve that can have points of different colors, shapes and sizes.
782
            This is the preferred method for visualization that show a series of different points.
783
784
            :param x_data: The list of X coordinates of the points
785
            :type x_data: list of float
786
787
            :param y_data: The list of Y coordinates of the points
788
            :type y_data: list of float
789
790
            :param color_data: The list of point colors
791
            :type color_data: list of QColor
792
793
            :param label_data: The list of point labels
794
            :type label_data: list of str
795
796
            :param size_data: The list of point sizes
797
            :type size_data: list of int
798
799
            :param shape_data: The list of point symbols
800
            :type shape_data: list of int
801
802
            The number of points in the curve will be equal to min(len(x_data), len(y_data)).
803
            The other four list can be empty, in which case a default value will be used.
804
            If they contain only one element, its value will be used for all points.
805
806
            .. note:: This function does not add items to the legend automatically.
807
                      You will have to add them yourself with :meth:`.OWLegend.add_item`.
808
809
            .. seealso:: :obj:`.OWMultiCurve`, :obj:`.OWPoint`
810
        """
811
        if not self.main_curve:
812
            self.main_curve = OWMultiCurve([], [])
813
            self.add_item(self.main_curve)
814
815
        self.update_performance(len(x_data))
816
817
        if len(valid_data):
818
            import numpy
0 ignored issues
show
Configuration introduced by
The import numpy could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
819
            x_data = numpy.compress(valid_data, x_data)
820
            y_data = numpy.compress(valid_data, y_data)
821
            if len(color_data) > 1:
822
                color_data = numpy.compress(valid_data, color_data)
823
            if len(size_data) > 1:
824
                size_data = numpy.compress(valid_data, size_data)
825
            if len(shape_data) > 1:
826
                shape_data = numpy.compress(valid_data, shape_data)
827
            if len(label_data) > 1:
828
                label_data = numpy.compress(valid_data, label_data)
829
            if len(marked_data) > 1:
830
                marked_data = numpy.compress(valid_data, marked_data).tolist()
831
832
        c = self.main_curve
833
        c.set_data(x_data, y_data)
834
        c.set_axes(x_axis_key, y_axis_key)
835
        c.set_point_colors(color_data)
836
        c.set_point_labels(label_data)
837
        c.set_point_sizes(size_data)
838
        c.set_point_symbols(shape_data)
839
        if len(marked_data):
840
            c.set_points_marked(marked_data)
841
            self.marked_points_changed.emit()
842
        c.name = 'Main Curve'
843
844
        self.replot()
845
846
    def remove_curve(self, item):
847
        '''
848
            Removes ``item`` from the plot
849
        '''
850
        self.remove_item(item)
851
        self.legend().remove_curve(item)
852
853
    def plot_data(self, xData, yData, colors, labels, shapes, sizes):
854
        pass
855
856
    def add_axis(self, axis_id, title='', title_above=False, title_location=AxisMiddle,
857
                 line=None, arrows=0, zoomable=False, bounds=None):
858
        '''
859
            Creates an :obj:`OrangeWidgets.plot.OWAxis` with the specified ``axis_id`` and ``title``.
860
        '''
861
        a = OWAxis(axis_id, title, title_above, title_location, line, arrows, self, bounds=bounds)
862
        self.scene().addItem(a)
863
        a.zoomable = zoomable
864
        a.update_callback = self.replot
865
        if axis_id in self._bounds_cache:
866
            del self._bounds_cache[axis_id]
867
        self._transform_cache = {}
868
        self.axes[axis_id] = a
869
        if not axis_id in CartesianAxes:
870
            self.set_show_axis_title(axis_id, True)
871
        return a
872
873
    def remove_all_axes(self, user_only = True):
874
        '''
875
            Removes all axes from the plot
876
        '''
877
        ids = []
878
        for id,item in self.axes.items():
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
879
            if not user_only or id >= UserAxis:
880
                ids.append(id)
881
                self.scene().removeItem(item)
882
        for id in ids:
883
            del self.axes[id]
884
885
    def add_custom_axis(self, axis_id, axis):
886
        '''
887
            Adds a custom ``axis`` with id ``axis_id`` to the plot
888
        '''
889
        self.axes[axis_id] = axis
890
        self.replot()
891
892
    def add_marker(self, name, x, y, alignment = -1, bold = 0, color = None, brushColor = None, size=None, antiAlias = None,
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (124/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Unused Code introduced by
The argument size seems to be unused.
Loading history...
Unused Code introduced by
The argument antiAlias seems to be unused.
Loading history...
893
                    x_axis_key = xBottom, y_axis_key = yLeft):
894
        m = Marker(name, x, y, alignment, bold, color, brushColor)
895
        self._marker_items.append((m, x, y, x_axis_key, y_axis_key))
896
        self.add_custom_curve(m)
897
898
        return m
899
900
    def removeAllSelections(self):
901
        ## TODO
902
        pass
903
904
    def clear(self):
905
        """
906
            Clears the plot, removing all curves, markers and tooltips.
907
            Axes and the grid are not removed
908
        """
909
        for i in self.plot_items():
910
            if i is not self.grid_curve:
911
                self.remove_item(i)
912
        self.main_curve = None
913
        self._bounds_cache = {}
914
        self._transform_cache = {}
915
        self.clear_markers()
916
        self.tips.removeAll()
917
        self.legend().clear()
918
        self.old_legend_margin = None
919
        self.update_grid()
920
921
    def clear_markers(self):
922
        """
923
            Removes all markers added with :meth:`add_marker` from the plot
924
        """
925
        for item,x,y,x_axis,y_axis in self._marker_items:
0 ignored issues
show
Unused Code introduced by
The variable x_axis seems to be unused.
Loading history...
Unused Code introduced by
The variable y_axis seems to be unused.
Loading history...
Unused Code introduced by
The variable y seems to be unused.
Loading history...
Unused Code introduced by
The variable x seems to be unused.
Loading history...
926
            item.detach()
927
        self._marker_items = []
928
929
    def update_layout(self):
930
        '''
931
            Updates the plot layout.
932
933
            This function recalculates the position of titles, axes, the legend and the main plot area.
934
            It does not update the curve or the other plot items.
935
        '''
936
        if not self.isVisible():
937
            # No point in updating the graph if it's still hidden
938
            return
939
        graph_rect = QRectF(self.contentsRect())
940
        self.centerOn(graph_rect.center())
941
        m = self.graph_margin
942
        graph_rect.adjust(m, m, -m, -m)
943
944
        if self.showMainTitle and self.mainTitle:
945
            if self.title_item:
946
                self.scene().remove_item(self.title_item)
947
                del self.title_item
948
            self.title_item = QGraphicsTextItem(self.mainTitle, scene=self.scene())
949
            title_size = self.title_item.boundingRect().size()
950
            ## TODO: Check if the title is too big
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
951
            self.title_item.setPos( graph_rect.width()/2 - title_size.width()/2, self.title_margin/2 - title_size.height()/2 )
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (126/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
952
            graph_rect.setTop(graph_rect.top() + self.title_margin)
953
954
        if self.show_legend:
955
            self._legend_outside_area = QRectF(graph_rect)
0 ignored issues
show
Coding Style introduced by
The attribute _legend_outside_area was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
956
            self._legend.max_size = self._legend_outside_area.size()
957
            r = self._legend_margin
958
            graph_rect.adjust(r.left(), r.top(), -r.right(), -r.bottom())
959
960
        self._legend.update_items()
961
962
        axis_rects = dict()
963
        base_margin = min(self.axis_margin,  graph_rect.height()/4, graph_rect.height()/4)
964
        if xBottom in self.axes and self.axes[xBottom].isVisible():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
965
            margin = base_margin
966
            if self.axes[xBottom].should_be_expanded():
967
                margin += min(20, graph_rect.height()/8, graph_rect.width() / 8)
968
            bottom_rect = QRectF(graph_rect)
969
            bottom_rect.setTop( bottom_rect.bottom() - margin)
970
            axis_rects[xBottom] = bottom_rect
971
            graph_rect.setBottom( graph_rect.bottom() - margin)
972
        if xTop in self.axes and self.axes[xTop].isVisible():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
973
            margin = base_margin
974
            if self.axes[xTop].should_be_expanded():
975
                margin += min(20, graph_rect.height()/8, graph_rect.width() / 8)
976
            top_rect = QRectF(graph_rect)
977
            top_rect.setBottom(top_rect.top() + margin)
978
            axis_rects[xTop] = top_rect
979
            graph_rect.setTop(graph_rect.top() + margin)
980
        if yLeft in self.axes and self.axes[yLeft].isVisible():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
981
            margin = base_margin
982
            if self.axes[yLeft].should_be_expanded():
983
                margin += min(20, graph_rect.height()/8, graph_rect.width() / 8)
984
            left_rect = QRectF(graph_rect)
985
            left = graph_rect.left() + margin + self.y_axis_extra_margin
986
            left_rect.setRight(left)
987
            graph_rect.setLeft(left)
988
            axis_rects[yLeft] = left_rect
989
            if xBottom in axis_rects:
990
                axis_rects[xBottom].setLeft(left)
991
            if xTop in axis_rects:
992
                axis_rects[xTop].setLeft(left)
993
        if yRight in self.axes and self.axes[yRight].isVisible():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
994
            margin = base_margin
995
            if self.axes[yRight].should_be_expanded():
996
                margin += min(20, graph_rect.height()/8, graph_rect.width() / 8)
997
            right_rect = QRectF(graph_rect)
998
            right = graph_rect.right() - margin - self.y_axis_extra_margin
999
            right_rect.setLeft(right)
1000
            graph_rect.setRight(right)
1001
            axis_rects[yRight] = right_rect
1002
            if xBottom in axis_rects:
1003
                axis_rects[xBottom].setRight(right)
1004
            if xTop in axis_rects:
1005
                axis_rects[xTop].setRight(right)
1006
1007
        if self.graph_area != graph_rect:
1008
            self.graph_area = QRectF(graph_rect)
1009
            self.set_graph_rect(self.graph_area)
1010
            self._transform_cache = {}
1011
1012
            if self._zoom_rect:
1013
                data_zoom_rect = self.map_transform.inverted()[0].mapRect(self._zoom_rect)
1014
                self.map_transform = self.transform_for_axes()
1015
                self.set_zoom_rect(self.map_transform.mapRect(data_zoom_rect))
1016
1017
        self.map_transform = self.transform_for_axes()
1018
1019
        for c in self.plot_items():
1020
            x,y = c.axes()
1021
            c.set_graph_transform(self.transform_for_axes(x,y))
1022
            c.update_properties()
1023
1024
    def update_zoom(self):
1025
        '''
1026
            Updates the zoom transformation of the plot items.
1027
        '''
1028
        zt = self.zoom_transform()
1029
        self._zoom_transform = zt
1030
        self.set_zoom_transform(zt)
1031
1032
        self.update_axes(zoom_only=True)
1033
        self.viewport().update()
1034
1035
    def update_axes(self, zoom_only=False):
1036
        """
1037
            Updates the axes.
1038
1039
            If ``zoom_only`` is ``True``, only the positions of the axes and their labels are recalculated.
1040
            Otherwise, all their labels are updated.
1041
        """
1042
        if self.warn_unused_attributes and not zoom_only:
1043
            self._legend.remove_category(UNUSED_ATTRIBUTES_STR)
1044
1045
        for id, item in self.axes.items():
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
1046
            if item.scale is None and item.labels is None:
1047
                item.auto_range = self.bounds_for_axis(id)
1048
1049
            if id in XAxes:
1050
                (x,y) = (id, yLeft)
1051
            elif id in YAxes:
1052
                (x,y) = (xBottom, id)
1053
            else:
1054
                (x,y) = (xBottom, yLeft)
1055
1056
            if id in CartesianAxes:
1057
                ## This class only sets the lines for these four axes, widgets are responsible for the rest
1058
                if x in self.axes and y in self.axes:
1059
                    item.data_line = self.axis_line(self.data_rect_for_axes(x,y), id)
1060
            if id in CartesianAxes:
1061
                item.graph_line = self.axis_line(self.graph_area, id, invert_y = True)
1062
            elif item.data_line:
1063
                t = self.transform_for_axes(x, y)
1064
                item.graph_line = t.map(item.data_line)
1065
1066
            if item.graph_line and item.zoomable:
1067
                item.graph_line = self._zoom_transform.map(item.graph_line)
1068
1069
            if not zoom_only:
1070
                if item.graph_line:
1071
                    item.show()
1072
                else:
1073
                    item.hide()
1074
                    if self.warn_unused_attributes:
1075
                        self._legend.add_item(UNUSED_ATTRIBUTES_STR, item.title, None)
1076
            item.zoom_transform = self._zoom_transform
1077
            item.update(zoom_only)
1078
1079
    def replot(self):
1080
        '''
1081
            Replot the entire graph.
1082
1083
            This functions redraws everything on the graph, so it can be very slow
1084
        '''
1085
        #self.setBackgroundBrush(self.color(OWPalette.Canvas))
1086
        self._bounds_cache = {}
1087
        self._transform_cache = {}
1088
        self.set_clean()
1089
        self.update_antialiasing()
1090
        self.update_legend()
1091
        self.update_layout()
1092
        self.update_zoom()
1093
        self.update_axes()
1094
        self.update_grid()
1095
        self.update_filled_symbols()
1096
        self.setSceneRect(QRectF(self.contentsRect()))
1097
        self.viewport().update()
1098
1099
    def update_legend(self):
1100
        if self.show_legend and not self._legend_moved:
1101
            ## If the legend hasn't been moved it, we set it outside, in the top right corner
1102
            m = self.graph_margin
1103
            r = QRectF(self.contentsRect())
1104
            r.adjust(m, m, -m, -m)
1105
            self._legend.max_size = r.size()
1106
            self._legend.update_items()
1107
            w = self._legend.boundingRect().width()
1108
            self._legend_margin = QRectF(0, 0, w, 0)
1109
            self._legend.set_floating(False)
1110
            self._legend.set_orientation(Qt.Vertical)
1111
            self._legend.setPos(QRectF(self.contentsRect()).topRight() + QPointF(-w, 0))
1112
1113
1114
        if (self._legend.isVisible() == self.show_legend):
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after if.
Loading history...
1115
            return
1116
1117
        self._legend.setVisible(self.show_legend)
1118
        if self.show_legend:
1119
            if self.old_legend_margin is not None:
1120
                self.animate(self, 'legend_margin', self.old_legend_margin, duration = 100)
1121
            else:
1122
                r = self.legend_rect()
1123
                self.ensure_inside(r, self.contentsRect())
1124
                self._legend.setPos(r.topLeft())
1125
                self.notify_legend_moved(r.topLeft())
1126
        else:
1127
            self.old_legend_margin = self.legend_margin
1128
            self.animate(self, 'legend_margin', QRectF(), duration=100)
1129
1130
    def update_filled_symbols(self):
1131
        ## TODO: Implement this in Curve.cpp
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
1132
        pass
1133
1134
    def update_grid(self):
1135
        self.grid_curve.set_x_enabled(self.show_grid)
1136
        self.grid_curve.set_y_enabled(self.show_grid)
1137
        self.grid_curve.update_properties()
1138
1139
    def legend(self):
1140
        '''
1141
            Returns the plot's legend, which is a :obj:`OrangeWidgets.plot.OWLegend`
1142
        '''
1143
        return self._legend
1144
1145
    def legend_rect(self):
1146
        if self.show_legend:
1147
            return self._legend.mapRectToScene(self._legend.boundingRect())
1148
        else:
1149
            return QRectF()
1150
1151
    def isLegendEvent(self, event, function):
1152
        if self.show_legend and self.legend_rect().contains(self.mapToScene(event.pos())):
1153
            function(self, event)
1154
            return True
1155
        else:
1156
            return False
1157
1158
    def mouse_action(self, event):
1159
        b = event.buttons() | event.button()
1160
        m = event.modifiers()
1161
        if b == Qt.LeftButton | Qt.RightButton:
1162
            b = Qt.MidButton
1163
        if m & Qt.AltModifier and b == Qt.LeftButton:
1164
            m = m & ~Qt.AltModifier
1165
            b = Qt.MidButton
1166
1167
        if b == Qt.LeftButton and not m:
1168
            return self.state
1169
1170
        if b == Qt.RightButton and not m and self.state == SELECT:
1171
            return SELECT_RIGHTCLICK
1172
1173
        if b == Qt.MidButton:
1174
            return PANNING
1175
1176
        if b in [Qt.LeftButton, Qt.RightButton] and (self.state == ZOOMING or m == Qt.ControlModifier):
1177
            return ZOOMING
1178
1179
        if b == Qt.LeftButton and m == Qt.ShiftModifier:
1180
            return SELECT
1181
1182
    ## Event handling
1183
1184
    def event(self, event):
1185
        if event.type() == QEvent.Gesture:
1186
            return self.gestureEvent(event)
1187
        else:
1188
            return orangeqt.Plot.event(self, event)
1189
1190
    def gestureEvent(self, event):
1191
        for gesture in event.gestures():
1192
            if gesture.state() == Qt.GestureStarted:
1193
                self.current_gesture_scale = 1.
0 ignored issues
show
Coding Style introduced by
The attribute current_gesture_scale was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1194
                event.accept(gesture)
1195
                continue
1196
            elif gesture.gestureType() == Qt.PinchGesture:
1197
                old_animate_plot = self.animate_plot
1198
                self.animate_plot = False
1199
                self.zoom(gesture.centerPoint(), gesture.scaleFactor()/self.current_gesture_scale )
1200
                self.current_gesture_scale = gesture.scaleFactor()
0 ignored issues
show
Coding Style introduced by
The attribute current_gesture_scale was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1201
                self.animate_plot = old_animate_plot
1202
            elif gesture.gestureType() == Qt.PanGesture:
1203
                self.pan(gesture.delta())
1204
        return True
1205
1206
    def resizeEvent(self, event):
1207
        self.replot()
1208
        s = event.size() - event.oldSize()
1209
        if self.legend_margin.right() > 0:
1210
            self._legend.setPos(self._legend.pos() + QPointF(s.width(), 0))
1211
        if self.legend_margin.bottom() > 0:
1212
            self._legend.setPos(self._legend.pos() + QPointF(0, s.height()))
1213
1214
    def showEvent(self, event):
0 ignored issues
show
Unused Code introduced by
The argument event seems to be unused.
Loading history...
1215
        self.replot()
1216
1217
    def mousePressEvent(self, event):
1218
        self.static_click = True
1219
        self._pressed_mouse_button = event.button()
1220
        self._pressed_mouse_pos = event.pos()
0 ignored issues
show
Coding Style introduced by
The attribute _pressed_mouse_pos was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1221
1222
        if self.mousePressEventHandler and self.mousePressEventHandler(event):
1223
            event.accept()
1224
            return
1225
1226
        if self.isLegendEvent(event, QGraphicsView.mousePressEvent):
1227
            return
1228
1229
        point = self.mapToScene(event.pos())
1230
        a = self.mouse_action(event)
1231
1232
        if a == SELECT and hasattr(self, 'move_selected_points'):
1233
            self._pressed_point = self.nearest_point(point)
1234
            self._pressed_point_coor = None
0 ignored issues
show
Coding Style introduced by
The attribute _pressed_point_coor was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1235
            if self._pressed_point is not None:
1236
                self._pressed_point_coor = self._pressed_point.coordinates()
0 ignored issues
show
Coding Style introduced by
The attribute _pressed_point_coor was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1237
1238
        if a == PANNING:
1239
            self._last_pan_pos = point
0 ignored issues
show
Coding Style introduced by
The attribute _last_pan_pos was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1240
            event.accept()
1241
        else:
1242
            orangeqt.Plot.mousePressEvent(self, event)
1243
1244
    def mouseMoveEvent(self, event):
1245
        if event.buttons() and (self._pressed_mouse_pos - event.pos()).manhattanLength() > QtGui.QApplication.instance().startDragDistance():
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (141/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1246
            self.static_click = False
1247
1248
        if self.mouseMoveEventHandler and self.mouseMoveEventHandler(event):
1249
            event.accept()
1250
            return
1251
1252
        if self.isLegendEvent(event, QGraphicsView.mouseMoveEvent):
1253
            return
1254
1255
        point = self.mapToScene(event.pos())
1256
        if not self._pressed_mouse_button:
1257
            if self.receivers(SIGNAL('point_hovered(Point*)')) > 0:
1258
                self.point_hovered.emit(self.nearest_point(point))
1259
1260
        ## We implement a workaround here, because sometimes mouseMoveEvents are not fast enough
1261
        ## so the moving legend gets left behind while dragging, and it's left in a pressed state
1262
        if self._legend.mouse_down:
1263
            QGraphicsView.mouseMoveEvent(self, event)
1264
            return
1265
1266
        a = self.mouse_action(event)
1267
1268
        if a == SELECT and self._pressed_point is not None and self._pressed_point.is_selected() and hasattr(self, 'move_selected_points'):
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (139/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1269
            animate_points = self.animate_points
1270
            self.animate_points = False
1271
            x1, y1 = self._pressed_point_coor
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are trying to unpack a non-sequence, which was defined at line 1234.
Loading history...
1272
            x2, y2 = self.map_from_graph(point, zoom=True)
1273
            self.move_selected_points((x2 - x1, y2 - y1))
1274
            self.replot()
1275
            if self._pressed_point is not None:
1276
                self._pressed_point_coor = self._pressed_point.coordinates()
0 ignored issues
show
Coding Style introduced by
The attribute _pressed_point_coor was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1277
1278
            self.animate_points = animate_points
1279
1280
        elif a in [SELECT, ZOOMING] and self.graph_area.contains(point):
1281
            if not self._current_rs_item:
1282
                self._selection_start_point = self.mapToScene(self._pressed_mouse_pos)
0 ignored issues
show
Coding Style introduced by
The attribute _selection_start_point was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1283
                self._current_rs_item = QGraphicsRectItem(scene=self.scene())
1284
                self._current_rs_item.setPen(SelectionPen)
1285
                self._current_rs_item.setBrush(SelectionBrush)
1286
                self._current_rs_item.setZValue(SelectionZValue)
1287
            self._current_rs_item.setRect(QRectF(self._selection_start_point, point).normalized())
1288
        elif a == PANNING:
1289
            if not self._last_pan_pos:
1290
                self._last_pan_pos = self.mapToScene(self._pressed_mouse_pos)
0 ignored issues
show
Coding Style introduced by
The attribute _last_pan_pos was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1291
            self.pan(point - self._last_pan_pos)
1292
            self._last_pan_pos = point
0 ignored issues
show
Coding Style introduced by
The attribute _last_pan_pos was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1293
        else:
1294
            x, y = self.map_from_graph(point, zoom=True)
1295
            text, x, y = self.tips.maybeTip(x, y)
1296
            if type(text) == int:
1297
                text = self.buildTooltip(text)
1298
            if text and x is not None and y is not None:
1299
                tp = self.mapFromScene(QPointF(x,y) * self.map_transform * self._zoom_transform)
1300
                self.showTip(tp.x(), tp.y(), text)
1301
            else:
1302
                orangeqt.Plot.mouseMoveEvent(self, event)
1303
1304
1305
    def mouseReleaseEvent(self, event):
1306
        self._pressed_mouse_button = Qt.NoButton
1307
1308
        if self.mouseReleaseEventHandler and self.mouseReleaseEventHandler(event):
1309
            event.accept()
1310
            return
1311
        if self.static_click and self.mouseStaticClickHandler and self.mouseStaticClickHandler(event):
1312
            event.accept()
1313
            return
1314
1315
        if self.isLegendEvent(event, QGraphicsView.mouseReleaseEvent):
1316
            return
1317
1318
        a = self.mouse_action(event)
1319
        if a == SELECT and self._pressed_point is not None:
1320
            self._pressed_point = None
1321
        if a in [ZOOMING, SELECT] and self._current_rs_item:
1322
            rect = self._current_rs_item.rect()
1323
            if a == ZOOMING:
1324
                self.zoom_to_rect(self._zoom_transform.inverted()[0].mapRect(rect))
1325
            else:
1326
                self.add_selection(rect)
1327
            self.scene().removeItem(self._current_rs_item)
1328
            self._current_rs_item = None
1329
            return
1330
        orangeqt.Plot.mouseReleaseEvent(self, event)
1331
1332
    def mouseStaticClick(self, event):
1333
        point = self.mapToScene(event.pos())
1334
        if point not in self.graph_area:
1335
            return False
1336
1337
        a = self.mouse_action(event)
1338
        b = event.buttons() | event.button()
1339
1340
        if a == ZOOMING:
1341
            if event.button() == Qt.LeftButton:
1342
                self.zoom_in(point)
1343
            elif event.button() == Qt.RightButton:
1344
                self.zoom_back()
1345
            else:
1346
                return False
1347
            return True
1348
        elif a == SELECT and b == Qt.LeftButton:
1349
            point_item = self.nearest_point(point)
1350
            b = self.selection_behavior
1351
1352
            if b == self.ReplaceSelection:
1353
                self.unselect_all_points()
1354
                b = self.AddSelection
1355
1356
            if point_item:
1357
                point_item.set_selected(b == self.AddSelection or (b == self.ToggleSelection and not point_item.is_selected()))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (127/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1358
            self.selection_changed.emit()
1359
        elif a == SELECT and b == Qt.RightButton:
1360
            point_item = self.nearest_point(point)
1361
            if point_item:
1362
                self.point_rightclicked.emit(self.nearest_point(point))
1363
            else:
1364
                self.unselect_all_points()
1365
        else:
1366
            return False
1367
1368
    def wheelEvent(self, event):
1369
        point = self.mapToScene(event.pos())
1370
        d = event.delta() / 120.0
1371
        self.zoom(point, pow(2,d))
1372
1373
    @staticmethod
1374
    def transform_from_rects(r1, r2):
1375
        """
1376
            Returns a QTransform that maps from rectangle ``r1`` to ``r2``.
1377
        """
1378
        if r1 is None or r2 is None:
1379
            return QTransform()
1380
        if r1.width() == 0 or r1.height() == 0 or r2.width() == 0 or r2.height() == 0:
1381
            return QTransform()
1382
        tr1 = QTransform().translate(-r1.left(), -r1.top())
1383
        ts = QTransform().scale(r2.width()/r1.width(), r2.height()/r1.height())
1384
        tr2 = QTransform().translate(r2.left(), r2.top())
1385
        return tr1 * ts * tr2
1386
1387
    def transform_for_zoom(self, factor, point, rect):
0 ignored issues
show
Unused Code introduced by
The argument rect seems to be unused.
Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
1388
        if factor == 1:
1389
            return QTransform()
1390
1391
        dp = point
1392
1393
        t = QTransform()
1394
        t.translate(dp.x(), dp.y())
1395
        t.scale(factor, factor)
1396
        t.translate(-dp.x(), -dp.y())
1397
        return t
1398
1399
    def rect_for_zoom(self, point, old_rect, scale = 2):
1400
        r = QRectF()
1401
        r.setWidth(old_rect.width() / scale)
1402
        r.setHeight(old_rect.height() / scale)
1403
        r.moveCenter(point)
1404
1405
        self.ensure_inside(r, self.graph_area)
1406
1407
        return r
1408
1409
    def set_state(self, state):
1410
        self.state = state
1411
        if state != SELECT_RECTANGLE:
1412
            self._current_rs_item = None
1413
        if state != SELECT_POLYGON:
1414
            self._current_ps_item = None
1415
1416
    def get_selected_points(self, xData, yData, validData):
1417
        if self.main_curve:
1418
            selected = []
1419
            points = self.main_curve.points()
1420
            i = 0
1421
            for d in validData:
1422
                if d:
1423
                    selected.append(points[i].is_selected())
1424
                    i += 1
1425
                else:
1426
                    selected.append(False)
1427
        else:
1428
            selected = self.selected_points(xData, yData)
1429
        unselected = [not i for i in selected]
1430
        return selected, unselected
1431
1432
    def add_selection(self, reg):
1433
        """
1434
            Selects all points in the region ``reg`` using the current :attr: `selection_behavior`.
1435
        """
1436
        self.select_points(reg, self.selection_behavior)
1437
        self.viewport().update()
1438
        if self.auto_send_selection_callback:
1439
            self.auto_send_selection_callback()
1440
1441
    def points_equal(self, p1, p2):
1442
        if type(p1) == tuple:
1443
            (x, y) = p1
1444
            p1 = QPointF(x, y)
1445
        if type(p2) == tuple:
1446
            (x, y) = p2
1447
            p2 = QPointF(x, y)
1448
        return (QPointF(p1)-QPointF(p2)).manhattanLength() < self.polygon_close_treshold
1449
1450
    def data_rect_for_axes(self, x_axis = xBottom, y_axis = yLeft):
1451
        """
1452
            Calculates the bounding rectangle in data coordinates for the axes ``x_axis`` and ``y_axis``.
1453
        """
1454
        if x_axis in self.axes and y_axis in self.axes:
1455
            x_min, x_max = self.bounds_for_axis(x_axis, try_auto_scale=True)
1456
            y_min, y_max = self.bounds_for_axis(y_axis, try_auto_scale=True)
1457
            if (x_min or x_max) and (y_min or y_max):
1458
                r = QRectF(x_min, y_min, x_max-x_min, y_max-y_min)
1459
                return r
1460
        r = orangeqt.Plot.data_rect_for_axes(self, x_axis, y_axis)
1461
        for id, axis in self.axes.items():
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
1462
            if id not in CartesianAxes and axis.data_line:
1463
                r |= QRectF(axis.data_line.p1(), axis.data_line.p2())
1464
        ## We leave a 5% margin on each side so the graph doesn't look overcrowded
1465
        ## TODO: Perhaps change this from a fixed percentage to always round to a round number
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
1466
        dx = r.width() / 20.0
1467
        dy = r.height() / 20.0
1468
        r.adjust(-dx, -dy, dx, dy)
1469
        return r
1470
1471
    def transform_for_axes(self, x_axis = xBottom, y_axis = yLeft):
1472
        """
1473
            Returns the graph transform that maps from data to scene coordinates using axes ``x_axis`` and ``y_axis``.
1474
        """
1475
        if not (x_axis, y_axis) in self._transform_cache:
1476
            # We must flip the graph area, becase Qt coordinates start from top left, while graph coordinates start from bottom left
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (132/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1477
            a = QRectF(self.graph_area)
1478
            t = a.top()
1479
            a.setTop(a.bottom())
1480
            a.setBottom(t)
1481
            self._transform_cache[(x_axis, y_axis)] = self.transform_from_rects(self.data_rect_for_axes(x_axis, y_axis), a)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (123/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1482
        return self._transform_cache[(x_axis, y_axis)]
1483
1484
    def transform(self, axis_id, value):
1485
        """
1486
            Transforms the ``value`` from data to plot coordinates along the axis ``axis_id``.
1487
1488
            This function always ignores zoom. If you need to account for zooming, use :meth:`map_to_graph`.
1489
        """
1490
        if axis_id in XAxes:
1491
            size = self.graph_area.width()
1492
            margin = self.graph_area.left()
1493
        else:
1494
            size = self.graph_area.height()
1495
            margin = self.graph_area.top()
1496
        m, M = self.bounds_for_axis(axis_id)
1497
        if m is None or M is None or M == m:
1498
            return 0
1499
        else:
1500
            return margin + (value-m)/(M-m) * size
1501
1502
    def inv_transform(self, axis_id, value):
1503
        """
1504
            Transforms the ``value`` from plot to data coordinates along the axis ``axis_id``.
1505
1506
            This function always ignores zoom. If you need to account for zooming, use :meth:`map_from_graph`.
1507
        """
1508
        if axis_id in XAxes:
1509
            size = self.graph_area.width()
1510
            margin = self.graph_area.left()
1511
        else:
1512
            size = self.graph_area.height()
1513
            margin = self.graph_area.top()
1514
        m, M = self.bounds_for_axis(axis_id)
1515
        if m is not None and M is not None:
1516
            return m + (value-margin)/size * (M-m)
1517
        else:
1518
            return 0
1519
1520
    def bounds_for_axis(self, axis_id, try_auto_scale=True):
1521
        if axis_id in self.axes and not self.axes[axis_id].auto_scale:
1522
            return self.axes[axis_id].bounds()
1523
        if try_auto_scale:
1524
            lower, upper = orangeqt.Plot.bounds_for_axis(self, axis_id)
1525
            if lower != upper:
1526
                lower = lower - (upper-lower)/20.0
1527
                upper = upper + (upper-lower)/20.0
1528
            return lower, upper
1529
        else:
1530
            return None, None
1531
1532
    def enableYRaxis(self, enable=1):
1533
        self.set_axis_enabled(yRight, enable)
1534
1535
    def enableLRaxis(self, enable=1):
1536
        self.set_axis_enabled(yLeft, enable)
1537
1538
    def enableXaxis(self, enable=1):
1539
        self.set_axis_enabled(xBottom, enable)
1540
1541
    def set_axis_enabled(self, axis, enable):
1542
        if axis not in self.axes:
1543
            self.add_axis(axis)
1544
        self.axes[axis].setVisible(enable)
1545
        self.replot()
1546
1547
    @staticmethod
1548
    def axis_coordinate(point, axis_id):
1549
        if axis_id in XAxes:
1550
            return point.x()
1551
        elif axis_id in YAxes:
1552
            return point.y()
1553
        else:
1554
            return None
1555
1556
    # ####################################################################
1557
    # return string with attribute names and their values for example example
1558
    def getExampleTooltipText(self, example, indices=None, maxIndices=20):
1559
        if indices and type(indices[0]) == str:
1560
            indices = [self.attributeNameIndex[i] for i in indices]
1561
        if not indices:
1562
            indices = list(range(len(self.dataDomain.attributes)))
1563
1564
        # don't show the class value twice
1565
        if example.domain.classVar:
1566
            classIndex = self.attributeNameIndex[example.domain.classVar.name]
1567
            while classIndex in indices:
1568
                indices.remove(classIndex)
1569
1570
        text = "<b>Attributes:</b><br>"
1571
        for index in indices[:maxIndices]:
1572
            attr = self.attributeNames[index]
1573
            if attr not in example.domain:  text += "&nbsp;"*4 + "%s = ?<br>" % (Qt.escape(attr))
1574
            elif example[attr].isSpecial(): text += "&nbsp;"*4 + "%s = ?<br>" % (Qt.escape(attr))
1575
            else:                           text += "&nbsp;"*4 + "%s = %s<br>" % (Qt.escape(attr), Qt.escape(str(example[attr])))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (129/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1576
        if len(indices) > maxIndices:
1577
            text += "&nbsp;"*4 + " ... <br>"
1578
1579
        if example.domain.classVar:
1580
            text = text[:-4]
1581
            text += "<hr><b>Class:</b><br>"
1582
            if example.getclass().isSpecial(): text += "&nbsp;"*4 + "%s = ?<br>" % (Qt.escape(example.domain.classVar.name))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (124/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1583
            else:                              text += "&nbsp;"*4 + "%s = %s<br>" % (Qt.escape(example.domain.classVar.name), Qt.escape(str(example.getclass())))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (161/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1584
1585
        if len(example.domain.getmetas()) != 0:
1586
            text = text[:-4]
1587
            text += "<hr><b>Meta attributes:</b><br>"
1588
            # show values of meta attributes
1589
            for key in example.domain.getmetas():
1590
                try: text += "&nbsp;"*4 + "%s = %s<br>" % (Qt.escape(example.domain[key].name), Qt.escape(str(example[key])))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (125/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1591
                except: pass
0 ignored issues
show
Unused Code introduced by
This except handler seems to be unused and could be removed.

Except handlers which only contain pass and do not have an else clause can usually simply be removed:

try:
    raises_exception()
except:  # Could be removed
    pass
Loading history...
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
1592
        return text[:-4]        # remove the last <br>
1593
1594
    # show a tooltip at x,y with text. if the mouse will move for more than 2 pixels it will be removed
1595
    def showTip(self, x, y, text):
1596
        QToolTip.showText(self.mapToGlobal(QPoint(x, y)), text, self, QRect(x-3,y-3,6,6))
1597
1598
    def notify_legend_moved(self, pos):
1599
        self._legend_moved = True
1600
        l = self.legend_rect()
0 ignored issues
show
Unused Code introduced by
The variable l seems to be unused.
Loading history...
1601
        g = getattr(self, '_legend_outside_area', QRectF())
1602
        p = QPointF()
1603
        rect = QRectF()
1604
        offset = 20
1605
        if pos.x() > g.right() - offset:
1606
            self._legend.set_orientation(Qt.Vertical)
1607
            rect.setRight(self._legend.boundingRect().width())
1608
            p = g.topRight() - self._legend.boundingRect().topRight()
1609
        elif pos.x() < g.left() + offset:
1610
            self._legend.set_orientation(Qt.Vertical)
1611
            rect.setLeft(self._legend.boundingRect().width())
1612
            p = g.topLeft()
1613
        elif pos.y() < g.top() + offset:
1614
            self._legend.set_orientation(Qt.Horizontal)
1615
            rect.setTop(self._legend.boundingRect().height())
1616
            p = g.topLeft()
1617
        elif pos.y() > g.bottom() - offset:
1618
            self._legend.set_orientation(Qt.Horizontal)
1619
            rect.setBottom(self._legend.boundingRect().height())
1620
            p = g.bottomLeft() - self._legend.boundingRect().bottomLeft()
1621
1622
        if p.isNull():
1623
            self._legend.set_floating(True, pos)
1624
        else:
1625
            self._legend.set_floating(False, p)
1626
1627
        if rect != self._legend_margin:
1628
            orientation = Qt.Horizontal if rect.top() or rect.bottom() else Qt.Vertical
1629
            self._legend.set_orientation(orientation)
1630
            self.animate(self, 'legend_margin', rect, duration=100)
1631
1632
    def get_legend_margin(self):
1633
        return self._legend_margin
1634
1635
    def set_legend_margin(self, value):
1636
        self._legend_margin = value
1637
        self.update_layout()
1638
        self.update_axes()
1639
1640
    legend_margin = pyqtProperty(QRectF, get_legend_margin, set_legend_margin)
1641
1642
    def update_curves(self):
1643
        if self.main_curve:
1644
            self.main_curve.set_alpha_value(self.alpha_value)
1645
        else:
1646
            for c in self.plot_items():
1647
                if isinstance(c, orangeqt.Curve) and not getattr(c, 'ignore_alpha', False):
1648
                    au = c.auto_update()
1649
                    c.set_auto_update(False)
1650
                    c.set_point_size(self.point_width)
1651
                    color = c.color()
1652
                    color.setAlpha(self.alpha_value)
1653
                    c.set_color(color)
1654
                    c.set_auto_update(au)
1655
                    c.update_properties()
1656
        self.viewport().update()
1657
1658
    update_point_size = update_curves
1659
    update_alpha_value = update_curves
1660
1661
    def update_antialiasing(self, use_antialiasing=None):
1662
        if use_antialiasing is not None:
1663
            self.antialias_plot = use_antialiasing
1664
1665
        self.setRenderHint(QPainter.Antialiasing, self.antialias_plot)
1666
1667
    def update_animations(self, use_animations=None):
1668
        if use_animations is not None:
1669
            self.animate_plot = use_animations
1670
            self.animate_points = use_animations
1671
1672
    def update_performance(self, num_points = None):
1673
        if self.auto_adjust_performance:
1674
            if not num_points:
1675
                if self.main_curve:
1676
                    num_points = len(self.main_curve.points())
1677
                else:
1678
                    num_points = sum( len(c.points()) for c in self.curves )
1679
            if num_points > self.disable_animations_threshold:
1680
                self.disabled_animate_points = self.animate_points
0 ignored issues
show
Coding Style introduced by
The attribute disabled_animate_points was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1681
                self.animate_points = False
1682
1683
                self.disabled_animate_plot = self.animate_plot
0 ignored issues
show
Coding Style introduced by
The attribute disabled_animate_plot was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1684
                self.animate_plot = False
1685
1686
                self.disabled_antialias_lines = self.animate_points
0 ignored issues
show
Coding Style introduced by
The attribute disabled_antialias_lines was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
1687
                self.antialias_lines = True
1688
1689
            elif hasattr(self, 'disabled_animate_points'):
1690
                self.animate_points = self.disabled_animate_points
1691
                del self.disabled_animate_points
1692
1693
                self.animate_plot = self.disabled_animate_plot
1694
                del self.disabled_animate_plot
1695
1696
                self.antialias_lines = True # self.disabled_antialias_lines
1697
                del self.disabled_antialias_lines
1698
1699
    def animate(self, target, prop_name, end_val, duration = None, start_val = None):
1700
        for a in self._animations:
1701
            if a.state() == QPropertyAnimation.Stopped:
1702
                self._animations.remove(a)
1703
        if self.animate_plot:
1704
            a = QPropertyAnimation(target, prop_name)
1705
            a.setEndValue(end_val)
1706
            if start_val is not None:
1707
                a.setStartValue(start_val)
1708
            if duration:
1709
                a.setDuration(duration)
1710
            self._animations.append(a)
1711
            a.start(QPropertyAnimation.KeepWhenStopped)
1712
        else:
1713
            target.setProperty(prop_name, end_val)
1714
1715
    def clear_selection(self):
1716
        self.unselect_all_points()
1717
1718
    def send_selection(self):
1719
        if self.auto_send_selection_callback:
1720
            self.auto_send_selection_callback()
1721
1722
    def pan(self, delta):
1723
        if type(delta) == tuple:
1724
            x, y = delta
1725
        else:
1726
            x, y = delta.x(), delta.y()
1727
        t = self.zoom_transform()
1728
        x = x / t.m11()
1729
        y = y / t.m22()
1730
        r = QRectF(self.zoom_rect)
1731
        r.translate(-QPointF(x,y))
1732
        self.ensure_inside(r, self.graph_area)
1733
        self.zoom_rect = r
1734
1735
    def zoom_to_rect(self, rect):
1736
        self.ensure_inside(rect, self.graph_area)
1737
1738
        # add to zoom_stack if zoom_rect is larger
1739
        if self.zoom_rect.width() > rect.width() or self.zoom_rect.height() > rect.height():
1740
            self.zoom_stack.append(self.zoom_rect)
1741
1742
        self.animate(self, 'zoom_rect', rect, start_val = self.get_zoom_rect())
1743
1744
    def zoom_back(self):
1745
        if self.zoom_stack:
1746
            rect = self.zoom_stack.pop()
1747
            self.animate(self, 'zoom_rect', rect, start_val = self.get_zoom_rect())
1748
1749
    def reset_zoom(self):
1750
        self._zoom_rect = None
1751
        self.update_zoom()
1752
1753
    def zoom_transform(self):
1754
        return self.transform_from_rects(self.zoom_rect, self.graph_area)
1755
1756
    def zoom_in(self, point):
1757
        self.zoom(point, scale = 2)
1758
1759
    def zoom_out(self, point):
1760
        self.zoom(point, scale = 0.5)
1761
1762
    def zoom(self, point, scale):
1763
        print(len(self.zoom_stack))
1764
        t, ok = self._zoom_transform.inverted()
0 ignored issues
show
Unused Code introduced by
The variable ok seems to be unused.
Loading history...
1765
        point = point * t
1766
        r = QRectF(self.zoom_rect)
1767
        i = 1.0/scale
1768
        r.setTopLeft(point*(1-i) + r.topLeft()*i)
1769
        r.setBottomRight(point*(1-i) + r.bottomRight()*i)
1770
1771
        self.ensure_inside(r, self.graph_area)
1772
1773
        # remove smaller zoom rects from stack
1774
        while len(self.zoom_stack) > 0 and r.width() >= self.zoom_stack[-1].width() and r.height() >= self.zoom_stack[-1].height():
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (131/120).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
1775
            self.zoom_stack.pop()
1776
1777
        self.zoom_to_rect(r)
1778
1779
    def get_zoom_rect(self):
1780
        if self._zoom_rect:
1781
            return self._zoom_rect
1782
        else:
1783
            return self.graph_area
1784
1785
    def set_zoom_rect(self, rect):
1786
        self._zoom_rect = rect
1787
        self._zoom_transform = self.transform_from_rects(rect, self.graph_area)
1788
        self.update_zoom()
1789
1790
    zoom_rect = pyqtProperty(QRectF, get_zoom_rect, set_zoom_rect)
1791
1792
    @staticmethod
1793
    def ensure_inside(small_rect, big_rect):
1794
        if small_rect.width() > big_rect.width():
1795
            small_rect.setWidth(big_rect.width())
1796
        if small_rect.height() > big_rect.height():
1797
            small_rect.setHeight(big_rect.height())
1798
1799
        if small_rect.right() > big_rect.right():
1800
            small_rect.moveRight(big_rect.right())
1801
        elif small_rect.left() < big_rect.left():
1802
            small_rect.moveLeft(big_rect.left())
1803
1804
        if small_rect.bottom() > big_rect.bottom():
1805
            small_rect.moveBottom(big_rect.bottom())
1806
        elif small_rect.top() < big_rect.top():
1807
            small_rect.moveTop(big_rect.top())
1808
1809
    def shuffle_points(self):
1810
        if self.main_curve:
1811
            self.main_curve.shuffle_points()
1812
1813
    def set_progress(self, done, total):
1814
        if not self.widget:
1815
            return
1816
1817
        if done == total:
1818
            self.widget.progressBarFinished()
1819
        else:
1820
            self.widget.progressBarSet(100.0 * done / total)
1821
1822
    def start_progress(self):
1823
        if self.widget:
1824
            self.widget.progressBarInit()
1825
1826
    def end_progress(self):
1827
        if self.widget:
1828
            self.widget.progressBarFinished()
1829
1830
    def is_axis_auto_scale(self, axis_id):
1831
        if axis_id not in self.axes:
1832
            return axis_id not in self.data_range
1833
        return self.axes[axis_id].auto_scale
1834
1835
    def axis_line(self, rect, id, invert_y = False):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
1836
        if invert_y:
1837
            r = QRectF(rect)
1838
            r.setTop(rect.bottom())
1839
            r.setBottom(rect.top())
1840
            rect = r
1841
        if id == xBottom:
1842
            line = QLineF(rect.topLeft(), rect.topRight())
1843
        elif id == xTop:
1844
            line = QLineF(rect.bottomLeft(), rect.bottomRight())
1845
        elif id == yLeft:
1846
            line = QLineF(rect.topLeft(), rect.bottomLeft())
1847
        elif id == yRight:
1848
            line = QLineF(rect.topRight(), rect.bottomRight())
1849
        else:
1850
            line = None
1851
        return line
1852
1853
    def color(self, role, group = None):
1854
        if group:
1855
            return self.palette().color(group, role)
1856
        else:
1857
            return self.palette().color(role)
1858
1859
    def set_palette(self, p):
1860
        '''
1861
            Sets the plot palette to ``p``.
1862
1863
            :param p: The new color palette
1864
            :type p: :obj:`.QPalette`
1865
        '''
1866
        self.setPalette(p)
1867
        self.replot()
1868
1869
    def update_theme(self):
1870
        '''
1871
            Updates the current color theme, depending on the value of :attr:`theme_name`.
1872
        '''
1873
        if self.theme_name.lower() == 'default':
1874
            self.set_palette(OWPalette.System)
1875
        elif self.theme_name.lower() == 'light':
1876
            self.set_palette(OWPalette.Light)
1877
        elif self.theme_name.lower() == 'dark':
1878
            self.set_palette(OWPalette.Dark)
1879