pyqttoolkit 0.9.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyqttoolkit/__init__.py +20 -0
- pyqttoolkit/_version.py +21 -0
- pyqttoolkit/application/__init__.py +18 -0
- pyqttoolkit/application/application.py +86 -0
- pyqttoolkit/application/garbage_collector.py +65 -0
- pyqttoolkit/colors/__init__.py +84 -0
- pyqttoolkit/config/__init__.py +20 -0
- pyqttoolkit/config/base.py +22 -0
- pyqttoolkit/config/composite.py +31 -0
- pyqttoolkit/config/default.py +27 -0
- pyqttoolkit/config/environment.py +35 -0
- pyqttoolkit/config/multi_json_file.py +93 -0
- pyqttoolkit/config/with_signal.py +30 -0
- pyqttoolkit/config/with_value_changed.py +31 -0
- pyqttoolkit/data/__init__.py +21 -0
- pyqttoolkit/data/bits.py +23 -0
- pyqttoolkit/data/cache.py +64 -0
- pyqttoolkit/data/interval.py +26 -0
- pyqttoolkit/data/message.py +29 -0
- pyqttoolkit/data/names.py +24 -0
- pyqttoolkit/datetime/__init__.py +16 -0
- pyqttoolkit/datetime/datetime.py +51 -0
- pyqttoolkit/datetime/qdatetime.py +53 -0
- pyqttoolkit/dependencies/__init__.py +56 -0
- pyqttoolkit/errors/__init__.py +19 -0
- pyqttoolkit/exceptions/__init__.py +18 -0
- pyqttoolkit/filesystem/__init__.py +24 -0
- pyqttoolkit/logs/__init__.py +140 -0
- pyqttoolkit/metadata/__init__.py +23 -0
- pyqttoolkit/metadata/dataframe_metadata_manager.py +105 -0
- pyqttoolkit/metadata/dict_metadata_manager.py +63 -0
- pyqttoolkit/metadata/id_generator.py +20 -0
- pyqttoolkit/metadata/metadata_manager_base.py +54 -0
- pyqttoolkit/models/__init__.py +26 -0
- pyqttoolkit/models/code_text.py +43 -0
- pyqttoolkit/models/dataframetable.py +135 -0
- pyqttoolkit/models/dictpropertytree.py +155 -0
- pyqttoolkit/models/dicttable.py +140 -0
- pyqttoolkit/models/json_text.py +52 -0
- pyqttoolkit/models/list_model.py +52 -0
- pyqttoolkit/models/module.py +26 -0
- pyqttoolkit/models/roles.py +23 -0
- pyqttoolkit/models/series_data.py +68 -0
- pyqttoolkit/models/span.py +36 -0
- pyqttoolkit/models/validation.py +27 -0
- pyqttoolkit/modules/__init__.py +18 -0
- pyqttoolkit/modules/events/__init__.py +19 -0
- pyqttoolkit/modules/events/add_signal.py +31 -0
- pyqttoolkit/modules/events/module_opening.py +21 -0
- pyqttoolkit/modules/events/rejectable.py +29 -0
- pyqttoolkit/modules/launcher_config.py +55 -0
- pyqttoolkit/modules/module.py +111 -0
- pyqttoolkit/properties/__init__.py +18 -0
- pyqttoolkit/properties/auto.py +221 -0
- pyqttoolkit/properties/throttled.py +77 -0
- pyqttoolkit/scripting/__init__.py +19 -0
- pyqttoolkit/scripting/script_context.py +36 -0
- pyqttoolkit/scripting/script_runner.py +132 -0
- pyqttoolkit/scripting/script_template.py +42 -0
- pyqttoolkit/services/__init__.py +32 -0
- pyqttoolkit/services/autosave.py +41 -0
- pyqttoolkit/services/email.py +25 -0
- pyqttoolkit/services/event_registry.py +52 -0
- pyqttoolkit/services/file_dialog.py +71 -0
- pyqttoolkit/services/hotkey_manager.py +137 -0
- pyqttoolkit/services/link_manager.py +135 -0
- pyqttoolkit/services/message_board.py +90 -0
- pyqttoolkit/services/module_service.py +154 -0
- pyqttoolkit/services/object_converter.py +45 -0
- pyqttoolkit/services/project_updater.py +149 -0
- pyqttoolkit/services/task_runner.py +281 -0
- pyqttoolkit/services/theme_manager.py +46 -0
- pyqttoolkit/services/tool_window_service.py +46 -0
- pyqttoolkit/viewmanagers/__init__.py +17 -0
- pyqttoolkit/viewmanagers/tool.py +23 -0
- pyqttoolkit/views/__init__.py +50 -0
- pyqttoolkit/views/bulk_value_selector.py +112 -0
- pyqttoolkit/views/check_box.py +36 -0
- pyqttoolkit/views/code/__init__.py +18 -0
- pyqttoolkit/views/code/code_edit.py +268 -0
- pyqttoolkit/views/code/python_syntax.py +226 -0
- pyqttoolkit/views/combo_box.py +101 -0
- pyqttoolkit/views/count_selector.py +59 -0
- pyqttoolkit/views/datetime.py +111 -0
- pyqttoolkit/views/datetime_range_selector.py +231 -0
- pyqttoolkit/views/default_text_line_edit.py +50 -0
- pyqttoolkit/views/delegates.py +117 -0
- pyqttoolkit/views/dtype_edit.py +117 -0
- pyqttoolkit/views/editable_combo_box.py +45 -0
- pyqttoolkit/views/file_selector.py +51 -0
- pyqttoolkit/views/icon.py +67 -0
- pyqttoolkit/views/icon_button.py +129 -0
- pyqttoolkit/views/json_edit.py +21 -0
- pyqttoolkit/views/labelled.py +29 -0
- pyqttoolkit/views/layout/__init__.py +21 -0
- pyqttoolkit/views/layout/collabsible.py +59 -0
- pyqttoolkit/views/layout/group_box.py +48 -0
- pyqttoolkit/views/layout/group_panel.py +188 -0
- pyqttoolkit/views/layout/lines.py +30 -0
- pyqttoolkit/views/layout/link.py +38 -0
- pyqttoolkit/views/line_edit.py +61 -0
- pyqttoolkit/views/linkable.py +49 -0
- pyqttoolkit/views/list_view.py +43 -0
- pyqttoolkit/views/main_window.py +91 -0
- pyqttoolkit/views/metadata_selector.py +84 -0
- pyqttoolkit/views/modulewindow.py +81 -0
- pyqttoolkit/views/plot/__init__.py +26 -0
- pyqttoolkit/views/plot/legend_control.py +121 -0
- pyqttoolkit/views/plot/matplotlib/__init__.py +23 -0
- pyqttoolkit/views/plot/matplotlib/base.py +1056 -0
- pyqttoolkit/views/plot/matplotlib/colorbar.py +75 -0
- pyqttoolkit/views/plot/matplotlib/colormap.py +48 -0
- pyqttoolkit/views/plot/matplotlib/font.py +55 -0
- pyqttoolkit/views/plot/plot_options.py +211 -0
- pyqttoolkit/views/plot/plot_toolbar_options.py +133 -0
- pyqttoolkit/views/plot/size.py +41 -0
- pyqttoolkit/views/plot/tool_type.py +26 -0
- pyqttoolkit/views/plot/toolbar.py +100 -0
- pyqttoolkit/views/popout.py +41 -0
- pyqttoolkit/views/popup.py +41 -0
- pyqttoolkit/views/property_tree.py +26 -0
- pyqttoolkit/views/shrunk_push_button.py +34 -0
- pyqttoolkit/views/steppable_combo_box.py +67 -0
- pyqttoolkit/views/styleable.py +37 -0
- pyqttoolkit/views/table.py +146 -0
- pyqttoolkit/views/text_edit.py +31 -0
- pyqttoolkit/views/tool_window.py +35 -0
- pyqttoolkit-0.9.1.dist-info/METADATA +69 -0
- pyqttoolkit-0.9.1.dist-info/RECORD +133 -0
- pyqttoolkit-0.9.1.dist-info/WHEEL +5 -0
- pyqttoolkit-0.9.1.dist-info/licenses/LICENSE.txt +674 -0
- pyqttoolkit-0.9.1.dist-info/licenses/LICENSE_SHORT +16 -0
- pyqttoolkit-0.9.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from pyqttoolkit.views.plot.matplotlib import MatPlotLibBase
|
|
18
|
+
from mpl_toolkits.axes_grid1 import Size
|
|
19
|
+
from mpl_toolkits.axes_grid1.mpl_axes import Axes
|
|
20
|
+
|
|
21
|
+
def _is_horizontal(position):
|
|
22
|
+
return position == 'top' or position == 'bottom'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _define_axes(position, cbar_size, _gap):
|
|
26
|
+
if position == 'bottom':
|
|
27
|
+
return [Size.Fixed(cbar_size), Size.Fixed(_gap), Size.Scaled(1.0)]
|
|
28
|
+
elif position == 'top':
|
|
29
|
+
return [Size.Scaled(1.0), Size.Fixed(_gap), Size.Fixed(cbar_size)]
|
|
30
|
+
elif position == 'left':
|
|
31
|
+
return [Size.Fixed(_gap/4), Size.Fixed(cbar_size), Size.Fixed(_gap*3), Size.Scaled(1.0)]
|
|
32
|
+
elif position == 'right':
|
|
33
|
+
return [Size.Scaled(1.0), Size.Fixed(_gap), Size.Fixed(cbar_size), Size.Fixed(_gap)]
|
|
34
|
+
else:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MatPlotColorbar(MatPlotLibBase):
|
|
39
|
+
def __init__(self, parent, file_dialog_service, cbar_height=0.2, v_gap=0.6, cbar_width=0.2, h_gap=0.6,
|
|
40
|
+
position='bottom', **kwargs):
|
|
41
|
+
|
|
42
|
+
# First element of the tuple correspond to the nx_default or ny_default.
|
|
43
|
+
# The second element is the nx and ny position for _colorbar_axes.
|
|
44
|
+
self._nx = {'bottom': (1, 1), 'top': (1, 1), 'right': (1, 3), 'left': (4, 1)}
|
|
45
|
+
self._ny = {'bottom': (3, 1), 'top': (1, 3), 'right': (1, 1), 'left': (1, 1)}
|
|
46
|
+
|
|
47
|
+
super().__init__(parent,
|
|
48
|
+
file_dialog_service,
|
|
49
|
+
v_axes=[Size.Scaled(1.0)] if not _is_horizontal(position)
|
|
50
|
+
else _define_axes(position, cbar_height, v_gap),
|
|
51
|
+
h_axes=[Size.Scaled(1.0)] if _is_horizontal(position)
|
|
52
|
+
else _define_axes(position, cbar_width, h_gap),
|
|
53
|
+
nx_default=self._nx[position][0],
|
|
54
|
+
ny_default=self._ny[position][0], **kwargs)
|
|
55
|
+
self._colorbar_axes = Axes(self._figure, self._divider.get_position())
|
|
56
|
+
self._colorbar_axes.set_axes_locator(self._divider.new_locator(nx=self._nx[position][1], ny=self._ny[position][1]))
|
|
57
|
+
self._figure.add_axes(self._colorbar_axes)
|
|
58
|
+
self._cbar = None
|
|
59
|
+
self._points = None
|
|
60
|
+
self._position = position
|
|
61
|
+
|
|
62
|
+
def _plot_colorbar(self, label):
|
|
63
|
+
if self._cbar is not None:
|
|
64
|
+
self._colorbar_axes.clear()
|
|
65
|
+
self._cbar = self._figure.colorbar(self._points, self._colorbar_axes, orientation='horizontal' if
|
|
66
|
+
_is_horizontal(self._position) else 'vertical')
|
|
67
|
+
self._cbar.set_label(label)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def colorbar(self):
|
|
71
|
+
return self._cbar
|
|
72
|
+
|
|
73
|
+
@colorbar.setter
|
|
74
|
+
def colorbar(self, cbar):
|
|
75
|
+
self._cbar = cbar
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from matplotlib.colors import LinearSegmentedColormap
|
|
18
|
+
|
|
19
|
+
class MatPlotLibColormap:
|
|
20
|
+
def __init__(self, colormap, limits=None, invert=False):
|
|
21
|
+
self._colormap = colormap.reversed() if colormap and invert else colormap
|
|
22
|
+
self._limits = limits
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def colormap(self):
|
|
26
|
+
return self._colormap
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def has_limits(self):
|
|
30
|
+
return self._limits is not None
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def limits(self):
|
|
34
|
+
return self._limits
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def from_list(values, invert=False):
|
|
38
|
+
if all(len(v) == 3 for v in values):
|
|
39
|
+
return MatPlotLibColormap(LinearSegmentedColormap.from_list('cmap', values), invert=invert)
|
|
40
|
+
if not all(len(v) == 2 for v in values):
|
|
41
|
+
return MatPlotLibColormap(None)
|
|
42
|
+
values = sorted(values, key=lambda v: v[0])
|
|
43
|
+
range_lower, range_upper = values[0][0], values[-1][0]
|
|
44
|
+
if range_lower == 0.0 and range_upper == 1.0:
|
|
45
|
+
return MatPlotLibColormap(LinearSegmentedColormap.from_list('cmap', values), invert=invert)
|
|
46
|
+
total_range = range_upper - range_lower
|
|
47
|
+
values = [((p - range_lower) / total_range, c) for p, c in values]
|
|
48
|
+
return MatPlotLibColormap(LinearSegmentedColormap.from_list('cmap', values), limits=(range_lower, range_upper), invert=invert)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from matplotlib import rcParams
|
|
18
|
+
from matplotlib.font_manager import findfont, FontProperties
|
|
19
|
+
from fontTools.ttLib import TTFont
|
|
20
|
+
from fontTools.ttLib.tables._c_m_a_p import CmapSubtable
|
|
21
|
+
|
|
22
|
+
class MatPlotLibFont:
|
|
23
|
+
def __init__(self, font):
|
|
24
|
+
font_file = findfont(FontProperties(family=font))
|
|
25
|
+
self._is_ttf = font_file.endswith('.ttf')
|
|
26
|
+
if not self._is_ttf:
|
|
27
|
+
raise ValueError('Unknown font type')
|
|
28
|
+
self._font = TTFont(font_file)
|
|
29
|
+
|
|
30
|
+
def get_size(self, text, pointsize, dpi):
|
|
31
|
+
return self._get_ttf_size(text, pointsize, dpi)
|
|
32
|
+
|
|
33
|
+
def _get_ttf_size(self, text, pointsize, dpi):
|
|
34
|
+
cmap = self._font['cmap']
|
|
35
|
+
t = cmap.getcmap(3,1).cmap
|
|
36
|
+
s = self._font.getGlyphSet()
|
|
37
|
+
width = 0
|
|
38
|
+
for c in text:
|
|
39
|
+
if ord(c) in t and t[ord(c)] in s:
|
|
40
|
+
char = s[t[ord(c)]]
|
|
41
|
+
else:
|
|
42
|
+
char = s['.notdef']
|
|
43
|
+
width += char.width
|
|
44
|
+
units_per_em = self._font['head'].unitsPerEm
|
|
45
|
+
return self._convert_to_pixels(width, pointsize, dpi, units_per_em), self._convert_to_pixels(units_per_em, pointsize, dpi, units_per_em)
|
|
46
|
+
|
|
47
|
+
def _convert_to_pixels(self, value, pointsize, dpi, units_per_em):
|
|
48
|
+
value_pts = value * float(pointsize) / units_per_em
|
|
49
|
+
value_in = value_pts / 72
|
|
50
|
+
value_pixels = value_in * dpi
|
|
51
|
+
return value_pixels
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def default():
|
|
55
|
+
return MatPlotLibFont(rcParams['font.family'])
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
import math
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel
|
|
22
|
+
from PyQt5.QtCore import pyqtSignal
|
|
23
|
+
|
|
24
|
+
from pyqttoolkit.properties import AutoProperty, bind
|
|
25
|
+
from pyqttoolkit.views import BindableCheckBox, AutoFloatLineEdit
|
|
26
|
+
|
|
27
|
+
class PlotOptionsView(QWidget):
|
|
28
|
+
def __init__(self, parent, grid_lines=True, x_limits=True, y_limits=True, secondary_x_limits=False, secondary_y_limits=False):
|
|
29
|
+
QWidget.__init__(self, parent)
|
|
30
|
+
|
|
31
|
+
edit_width = 60
|
|
32
|
+
self._grid_lines = grid_lines
|
|
33
|
+
self._x_limits = x_limits
|
|
34
|
+
self._y_limits = y_limits
|
|
35
|
+
self._secondary_y_limits = secondary_y_limits
|
|
36
|
+
self._secondary_x_limits = secondary_x_limits
|
|
37
|
+
|
|
38
|
+
self.showGridLines = False
|
|
39
|
+
self.xAxisLowerLimit = self.xAxisUpperLimit = self.yAxisLowerLimit = self.yAxisUpperLimit = float('nan')
|
|
40
|
+
self.secondaryXAxisLowerLimit = self.secondaryXAxisUpperLimit = self.secondaryYAxisLowerLimit = self.secondaryYAxisUpperLimit = float('nan')
|
|
41
|
+
|
|
42
|
+
self._layout = QGridLayout(self)
|
|
43
|
+
if grid_lines:
|
|
44
|
+
self._show_gridlines = BindableCheckBox(None, self)
|
|
45
|
+
self._layout.addWidget(QLabel(self.tr('Show Grid Lines'), self), 0, 0)
|
|
46
|
+
self._layout.addWidget(self._show_gridlines, 0, 1)
|
|
47
|
+
bind(self, self._show_gridlines, 'showGridLines', 'checked')
|
|
48
|
+
else:
|
|
49
|
+
self._show_gridlines = None
|
|
50
|
+
|
|
51
|
+
if x_limits:
|
|
52
|
+
self._x_axis_lower_limit = AutoFloatLineEdit(self, allow_inf=False, formatter=self._value_formatter)
|
|
53
|
+
self._x_axis_upper_limit = AutoFloatLineEdit(self, allow_inf=False, formatter=self._value_formatter)
|
|
54
|
+
self._x_axis_lower_limit.setFixedWidth(edit_width)
|
|
55
|
+
self._x_axis_upper_limit.setFixedWidth(edit_width)
|
|
56
|
+
self._layout.addWidget(QLabel(self.tr('X Axis Limits'), self), 1, 0)
|
|
57
|
+
self._layout.addWidget(self._x_axis_lower_limit, 1, 1)
|
|
58
|
+
self._layout.addWidget(self._x_axis_upper_limit, 1, 2)
|
|
59
|
+
bind(self, self._x_axis_lower_limit, 'xAxisLowerLimit', 'value')
|
|
60
|
+
bind(self, self._x_axis_upper_limit, 'xAxisUpperLimit', 'value')
|
|
61
|
+
else:
|
|
62
|
+
self._x_axis_lower_limit = self._x_axis_upper_limit = None
|
|
63
|
+
|
|
64
|
+
if y_limits:
|
|
65
|
+
self._y_axis_lower_limit = AutoFloatLineEdit(self, allow_inf=False, formatter=self._value_formatter)
|
|
66
|
+
self._y_axis_upper_limit = AutoFloatLineEdit(self, allow_inf=False, formatter=self._value_formatter)
|
|
67
|
+
self._y_axis_lower_limit.setFixedWidth(edit_width)
|
|
68
|
+
self._y_axis_upper_limit.setFixedWidth(edit_width)
|
|
69
|
+
self._layout.addWidget(QLabel(self.tr('Y Axis Limits'), self), 2, 0)
|
|
70
|
+
self._layout.addWidget(self._y_axis_lower_limit, 2, 1)
|
|
71
|
+
self._layout.addWidget(self._y_axis_upper_limit, 2, 2)
|
|
72
|
+
bind(self, self._y_axis_lower_limit, 'yAxisLowerLimit', 'value')
|
|
73
|
+
bind(self, self._y_axis_upper_limit, 'yAxisUpperLimit', 'value')
|
|
74
|
+
else:
|
|
75
|
+
self._y_axis_lower_limit = self._y_axis_upper_limit = None
|
|
76
|
+
|
|
77
|
+
if secondary_y_limits:
|
|
78
|
+
self._secondary_y_axis_lower_limit = AutoFloatLineEdit(self, allow_inf=False, formatter=self._value_formatter)
|
|
79
|
+
self._secondary_y_axis_upper_limit = AutoFloatLineEdit(self, allow_inf=False, formatter=self._value_formatter)
|
|
80
|
+
self._secondary_y_axis_lower_limit.setFixedWidth(edit_width)
|
|
81
|
+
self._secondary_y_axis_upper_limit.setFixedWidth(edit_width)
|
|
82
|
+
self._secondary_y_limits_label = QLabel(self.tr('2nd Y Axis Limits'), self)
|
|
83
|
+
self._layout.addWidget(self._secondary_y_limits_label, 3, 0)
|
|
84
|
+
self._layout.addWidget(self._secondary_y_axis_lower_limit, 3, 1)
|
|
85
|
+
self._layout.addWidget(self._secondary_y_axis_upper_limit, 3, 2)
|
|
86
|
+
bind(self, self._secondary_y_axis_lower_limit, 'secondaryYAxisLowerLimit', 'value')
|
|
87
|
+
bind(self, self._secondary_y_axis_upper_limit, 'secondaryYAxisUpperLimit', 'value')
|
|
88
|
+
else:
|
|
89
|
+
self._secondary_y_axis_lower_limit = self._secondary_y_axis_upper_limit = self._secondary_y_limits_label = None
|
|
90
|
+
|
|
91
|
+
if secondary_x_limits:
|
|
92
|
+
self._secondary_x_axis_lower_limit = AutoFloatLineEdit(self, allow_inf=False, formatter=self._value_formatter)
|
|
93
|
+
self._secondary_x_axis_upper_limit = AutoFloatLineEdit(self, allow_inf=False, formatter=self._value_formatter)
|
|
94
|
+
self._secondary_x_axis_lower_limit.setFixedWidth(edit_width)
|
|
95
|
+
self._secondary_x_axis_upper_limit.setFixedWidth(edit_width)
|
|
96
|
+
self._secondary_x_limits_label = QLabel(self.tr('2nd X Axis Limits'), self)
|
|
97
|
+
self._layout.addWidget(self._secondary_x_limits_label, 4, 0)
|
|
98
|
+
self._layout.addWidget(self._secondary_x_axis_lower_limit, 4, 1)
|
|
99
|
+
self._layout.addWidget(self._secondary_x_axis_upper_limit, 4, 2)
|
|
100
|
+
bind(self, self._secondary_x_axis_lower_limit, 'secondaryXAxisLowerLimit', 'value')
|
|
101
|
+
bind(self, self._secondary_x_axis_upper_limit, 'secondaryXAxisUpperLimit', 'value')
|
|
102
|
+
else:
|
|
103
|
+
self._secondary_x_axis_lower_limit = self._secondary_x_axis_upper_limit = self._secondary_x_limits_label = None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
showGridLinesChanged = pyqtSignal(bool)
|
|
107
|
+
xAxisLowerLimitChanged = pyqtSignal(float)
|
|
108
|
+
xAxisUpperLimitChanged = pyqtSignal(float)
|
|
109
|
+
xAxisLimitsChanged = pyqtSignal(float, float)
|
|
110
|
+
yAxisLowerLimitChanged = pyqtSignal(float)
|
|
111
|
+
yAxisUpperLimitChanged = pyqtSignal(float)
|
|
112
|
+
yAxisLimitsChanged = pyqtSignal(float, float)
|
|
113
|
+
secondaryXAxisLowerLimitChanged = pyqtSignal(float)
|
|
114
|
+
secondaryXAxisUpperLimitChanged = pyqtSignal(float)
|
|
115
|
+
secondaryXAxisLimitsChanged = pyqtSignal(float, float)
|
|
116
|
+
secondaryYAxisLowerLimitChanged = pyqtSignal(float)
|
|
117
|
+
secondaryYAxisUpperLimitChanged = pyqtSignal(float)
|
|
118
|
+
secondaryYAxisLimitsChanged = pyqtSignal(float, float)
|
|
119
|
+
|
|
120
|
+
showGridLines = AutoProperty(bool)
|
|
121
|
+
xAxisLowerLimit = AutoProperty(float)
|
|
122
|
+
xAxisUpperLimit = AutoProperty(float)
|
|
123
|
+
yAxisLowerLimit = AutoProperty(float)
|
|
124
|
+
yAxisUpperLimit = AutoProperty(float)
|
|
125
|
+
secondaryYAxisLowerLimit = AutoProperty(float)
|
|
126
|
+
secondaryYAxisUpperLimit = AutoProperty(float)
|
|
127
|
+
secondaryXAxisLowerLimit = AutoProperty(float)
|
|
128
|
+
secondaryXAxisUpperLimit = AutoProperty(float)
|
|
129
|
+
|
|
130
|
+
def _value_formatter(self, value):
|
|
131
|
+
if isinstance(value, str):
|
|
132
|
+
return value
|
|
133
|
+
if value == np.inf:
|
|
134
|
+
return 'inf'
|
|
135
|
+
if value == -np.inf:
|
|
136
|
+
return '-inf'
|
|
137
|
+
if np.isnan(value):
|
|
138
|
+
return 'Auto'
|
|
139
|
+
value = int(value * 100) / 100
|
|
140
|
+
if value == 0:
|
|
141
|
+
return '0.0'
|
|
142
|
+
precision = min(int(math.log10(abs(value))), 2) + 2
|
|
143
|
+
return f'{value:.{precision}g}'
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def grid_lines(self):
|
|
147
|
+
return self._grid_lines
|
|
148
|
+
|
|
149
|
+
def setXLimits(self, x_lower, x_upper):
|
|
150
|
+
blocked = self.blockSignals(True)
|
|
151
|
+
self.xAxisLowerLimit = x_lower
|
|
152
|
+
self._x_axis_lower_limit.value = x_lower
|
|
153
|
+
self.xAxisUpperLimit = x_upper
|
|
154
|
+
self._x_axis_upper_limit.value = x_upper
|
|
155
|
+
self.blockSignals(blocked)
|
|
156
|
+
self.xAxisLimitsChanged.emit(x_lower, x_upper)
|
|
157
|
+
|
|
158
|
+
def setYLimits(self, y_lower, y_upper):
|
|
159
|
+
blocked = self.blockSignals(True)
|
|
160
|
+
self.yAxisLowerLimit = y_lower
|
|
161
|
+
self._y_axis_lower_limit.value = y_lower
|
|
162
|
+
self.yAxisUpperLimit = y_upper
|
|
163
|
+
self._y_axis_upper_limit.value = y_upper
|
|
164
|
+
self.blockSignals(blocked)
|
|
165
|
+
self.yAxisLimitsChanged.emit(y_lower, y_upper)
|
|
166
|
+
|
|
167
|
+
def setSecondaryXLimits(self, x_lower, x_upper):
|
|
168
|
+
blocked = self.blockSignals(True)
|
|
169
|
+
self.secondaryXAxisLowerLimit = x_lower
|
|
170
|
+
self._secondary_x_axis_lower_limit.value = x_lower
|
|
171
|
+
self.secondaryXAxisUpperLimit = x_upper
|
|
172
|
+
self._secondary_x_axis_upper_limit.value = x_upper
|
|
173
|
+
self.blockSignals(blocked)
|
|
174
|
+
self.secondaryXAxisLimitsChanged.emit(x_lower, x_upper)
|
|
175
|
+
|
|
176
|
+
def setSecondaryYLimits(self, y_lower, y_upper):
|
|
177
|
+
blocked = self.blockSignals(True)
|
|
178
|
+
self.secondaryYAxisLowerLimit = y_lower
|
|
179
|
+
self._secondary_y_axis_lower_limit.value = y_lower
|
|
180
|
+
self.secondaryYAxisUpperLimit = y_upper
|
|
181
|
+
self._secondary_y_axis_upper_limit.value = y_upper
|
|
182
|
+
self.blockSignals(blocked)
|
|
183
|
+
self.secondaryYAxisLimitsChanged.emit(y_lower, y_upper)
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def x_limits(self):
|
|
187
|
+
return self._x_limits
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def y_limits(self):
|
|
191
|
+
return self._y_limits
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def secondary_y_limits(self):
|
|
195
|
+
return self._secondary_y_limits
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def secondary_x_limits(self):
|
|
199
|
+
return self._secondary_x_limits
|
|
200
|
+
|
|
201
|
+
def setSecondaryYLimitsEnabled(self, enabled):
|
|
202
|
+
if self._secondary_y_limits:
|
|
203
|
+
self._secondary_y_axis_lower_limit.setEnabled(enabled)
|
|
204
|
+
self._secondary_y_axis_upper_limit.setEnabled(enabled)
|
|
205
|
+
self._secondary_y_limits_label.setEnabled(enabled)
|
|
206
|
+
|
|
207
|
+
def setSecondaryXLimitsEnabled(self, enabled):
|
|
208
|
+
if self._secondary_x_limits:
|
|
209
|
+
self._secondary_x_axis_lower_limit.setEnabled(enabled)
|
|
210
|
+
self._secondary_x_axis_upper_limit.setEnabled(enabled)
|
|
211
|
+
self._secondary_x_limits_label.setEnabled(enabled)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from PyQt5.QtWidgets import QWidget, QGridLayout, QSizePolicy, QVBoxLayout
|
|
18
|
+
from PyQt5.QtCore import Qt
|
|
19
|
+
|
|
20
|
+
from pyqttoolkit.views import PopoutWidget, make_styleable
|
|
21
|
+
from pyqttoolkit.colors import interpolate_rgb, format_color, ColorFormat
|
|
22
|
+
from pyqttoolkit.views.layout import HLine
|
|
23
|
+
|
|
24
|
+
from .tool_type import ToolType
|
|
25
|
+
from .toolbar import PlotToolbarWidget
|
|
26
|
+
from .plot_options import PlotOptionsView
|
|
27
|
+
from .legend_control import LegendControlView
|
|
28
|
+
|
|
29
|
+
def _to_qt_color(color):
|
|
30
|
+
return tuple(c * 255 for c in color)
|
|
31
|
+
|
|
32
|
+
class ToolbarContainer(QWidget):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
ToolbarContainer = make_styleable(ToolbarContainer)
|
|
36
|
+
|
|
37
|
+
class PlotToolbarOptions(QWidget):
|
|
38
|
+
def __init__(self, parent, series_style, theme_manager, plot, options=None, legend_control=None, right_padding=0, has_extra_tools=False):
|
|
39
|
+
QWidget.__init__(self, parent)
|
|
40
|
+
self._theme_manager = theme_manager
|
|
41
|
+
self._plot = plot
|
|
42
|
+
self._toolbar_container = ToolbarContainer(plot)
|
|
43
|
+
|
|
44
|
+
self._background_color_qt = _to_qt_color(series_style.get_color_from_key('axes_background'))
|
|
45
|
+
self._foreground_color_qt = _to_qt_color(series_style.get_color_from_key('axes_foreground'))
|
|
46
|
+
interpolation = interpolate_rgb(self._background_color_qt, self._foreground_color_qt, 3)
|
|
47
|
+
self._icon_hover_color = interpolation[1]
|
|
48
|
+
|
|
49
|
+
self._toolbar = PlotToolbarWidget(self._toolbar_container, plot, self._foreground_color_qt, self._icon_hover_color)
|
|
50
|
+
self._toolbar.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
51
|
+
self._layout = QGridLayout(self)
|
|
52
|
+
self._layout.setContentsMargins(0, 0, 0, 0)
|
|
53
|
+
self._layout.setSpacing(0)
|
|
54
|
+
self._layout.addWidget(plot, 0, 0, 1, 3)
|
|
55
|
+
self._background_opacity = 0.8
|
|
56
|
+
plot.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
57
|
+
if options is not None:
|
|
58
|
+
if isinstance(options, PlotOptionsView):
|
|
59
|
+
plot.setOptionsView(options)
|
|
60
|
+
self._options = self._add_popout(ToolType.options, options)
|
|
61
|
+
else:
|
|
62
|
+
self._options = None
|
|
63
|
+
|
|
64
|
+
if legend_control is not None:
|
|
65
|
+
if isinstance(legend_control, LegendControlView):
|
|
66
|
+
plot.setLegendControl(legend_control)
|
|
67
|
+
self._legend_control = self._add_popout(ToolType.legend, legend_control)
|
|
68
|
+
legend_control.hasHiddenSeriesChanged.connect(self._handle_has_hidden_series_changed)
|
|
69
|
+
else:
|
|
70
|
+
self._legend_control = None
|
|
71
|
+
|
|
72
|
+
self._toolbar_layout = QVBoxLayout(self._toolbar_container)
|
|
73
|
+
self._toolbar_layout.addWidget(self._toolbar, Qt.AlignTop)
|
|
74
|
+
|
|
75
|
+
self._layout.addWidget(self._toolbar_container, 0, 1, Qt.AlignRight | Qt.AlignTop)
|
|
76
|
+
self._layout.setColumnStretch(0, 1)
|
|
77
|
+
self._layout.setColumnStretch(1, 0)
|
|
78
|
+
if right_padding > 0:
|
|
79
|
+
self._padding_widget = QWidget(self)
|
|
80
|
+
self._padding_widget.setVisible(False)
|
|
81
|
+
self._layout.addWidget(self._padding_widget, 0, 2)
|
|
82
|
+
self._layout.setColumnMinimumWidth(2, int(right_padding))
|
|
83
|
+
else:
|
|
84
|
+
self._padding_widget = None
|
|
85
|
+
|
|
86
|
+
if not has_extra_tools:
|
|
87
|
+
self._toolbar_layout.addStretch()
|
|
88
|
+
|
|
89
|
+
def _handle_has_hidden_series_changed(self, has_hidden):
|
|
90
|
+
color = None if not has_hidden else self._theme_manager.get_color('highlight')
|
|
91
|
+
self._toolbar.setColor(ToolType.legend, color)
|
|
92
|
+
|
|
93
|
+
def _handle_tool_activated(self, tool_type, view):
|
|
94
|
+
def _(tool, active):
|
|
95
|
+
if tool == tool_type:
|
|
96
|
+
view.setVisible(active)
|
|
97
|
+
if self._padding_widget:
|
|
98
|
+
self._padding_widget.setVisible(active)
|
|
99
|
+
if active:
|
|
100
|
+
self._toolbar_container.setStyleSheet("ToolbarContainer {{ background-color: {} }}".format(format_color(self._background_color_qt, ColorFormat.rgba_string_256, self._background_opacity)))
|
|
101
|
+
self._layout.setAlignment(self._toolbar_container, Qt.AlignRight)
|
|
102
|
+
if self._padding_widget:
|
|
103
|
+
self._padding_widget.setStyleSheet("QWidget {{ background-color: {} }}".format(format_color(self._background_color_qt, ColorFormat.rgba_string_256, self._background_opacity)))
|
|
104
|
+
else:
|
|
105
|
+
self._layout.setAlignment(self._toolbar_container, Qt.AlignRight | Qt.AlignTop)
|
|
106
|
+
self._toolbar_container.setStyleSheet("")
|
|
107
|
+
if self._padding_widget:
|
|
108
|
+
self._padding_widget.setStyleSheet("")
|
|
109
|
+
return _
|
|
110
|
+
|
|
111
|
+
def addTool(self, tool_widget):
|
|
112
|
+
self._toolbar_layout.addWidget(HLine(self._plot), Qt.AlignTop)
|
|
113
|
+
self._toolbar_layout.addWidget(tool_widget, Qt.AlignTop | Qt.AlignCenter)
|
|
114
|
+
self._toolbar_layout.addStretch()
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def icon_color(self):
|
|
118
|
+
return self._foreground_color_qt
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def icon_hover_color(self):
|
|
122
|
+
return self._icon_hover_color
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def toolbar(self):
|
|
126
|
+
return self._toolbar
|
|
127
|
+
|
|
128
|
+
def _add_popout(self, tool_type, view):
|
|
129
|
+
popout = PopoutWidget(self, view, self._background_color_qt, self._foreground_color_qt, self._background_opacity)
|
|
130
|
+
popout.setVisible(False)
|
|
131
|
+
self._layout.addWidget(popout, 0, 0, Qt.AlignRight)
|
|
132
|
+
self._toolbar.toolActivated.connect(self._handle_tool_activated(tool_type, popout))
|
|
133
|
+
return popout
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from mpl_toolkits.axes_grid1 import Size
|
|
18
|
+
|
|
19
|
+
class ToggleableFixed(Size._Base):
|
|
20
|
+
def __init__(self, size):
|
|
21
|
+
self._size = size
|
|
22
|
+
self._active = False
|
|
23
|
+
|
|
24
|
+
def activate(self):
|
|
25
|
+
self._active = True
|
|
26
|
+
|
|
27
|
+
def deactivate(self):
|
|
28
|
+
self._active = False
|
|
29
|
+
|
|
30
|
+
def get_size(self, _renderer):
|
|
31
|
+
return 0.0, self._size if self._active else 0.0
|
|
32
|
+
|
|
33
|
+
class MaxWidth(Size.MaxWidth):
|
|
34
|
+
def remove_artist(self, artist):
|
|
35
|
+
if artist in self._artist_list:
|
|
36
|
+
self._artist_list.remove(artist)
|
|
37
|
+
|
|
38
|
+
def get_size(self, renderer):
|
|
39
|
+
if self._artist_list:
|
|
40
|
+
return Size.MaxWidth.get_size(self, renderer)
|
|
41
|
+
return 0, 0
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from enum import Enum
|
|
18
|
+
|
|
19
|
+
class ToolType(Enum):
|
|
20
|
+
zoom = 0
|
|
21
|
+
polygon = 1
|
|
22
|
+
span = 2
|
|
23
|
+
pan = 3
|
|
24
|
+
reset = 4
|
|
25
|
+
options = 5
|
|
26
|
+
legend = 6
|