wxmplot 0.9.58__tar.gz → 0.9.60__tar.gz
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.
- wxmplot-0.9.60/PKG-INFO +163 -0
- wxmplot-0.9.60/examples/dump_config.py +49 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/leftright.py +1 -1
- wxmplot-0.9.60/examples/multiple_yaxes.py +22 -0
- wxmplot-0.9.60/examples/scope_mode_mpl_compare.py +28 -0
- wxmplot-0.9.60/examples/stripchart2.py +197 -0
- wxmplot-0.9.60/pyproject.toml +63 -0
- wxmplot-0.9.60/setup.cfg +4 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/basepanel.py +46 -16
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/config.py +233 -97
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/imageconf.py +1 -1
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/imageframe.py +37 -34
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/imagepanel.py +9 -12
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/plotconfigframe.py +134 -26
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/plotframe.py +12 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/plotpanel.py +89 -54
- wxmplot-0.9.60/wxmplot/utils.py +176 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/version.py +2 -2
- wxmplot-0.9.60/wxmplot.egg-info/PKG-INFO +163 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot.egg-info/SOURCES.txt +3 -2
- wxmplot-0.9.60/wxmplot.egg-info/requires.txt +23 -0
- wxmplot-0.9.58/PKG-INFO +0 -55
- wxmplot-0.9.58/examples/scope_mode_mpl_compare.py +0 -29
- wxmplot-0.9.58/pyproject.toml +0 -7
- wxmplot-0.9.58/requirements.txt +0 -6
- wxmplot-0.9.58/setup.cfg +0 -66
- wxmplot-0.9.58/wxmplot/utils.py +0 -402
- wxmplot-0.9.58/wxmplot.egg-info/PKG-INFO +0 -55
- wxmplot-0.9.58/wxmplot.egg-info/requires.txt +0 -13
- {wxmplot-0.9.58 → wxmplot-0.9.60}/ChangeLog +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/LICENSE +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/MANIFEST.in +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/README.md +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/basic_screenshot.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/compare_dislin_mpl.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/compare_plplot.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/contour.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/demo.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/errorbar.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/fill_to_zero.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/fill_uncertainties.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/floatcontrol.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/histogram.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/image.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/image_scroll.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/imagematrix.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/interactive_multiplewindows.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/multiplot.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/pilatus_display.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/plot_fromdatafile.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/plot_many_traces.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/plotapp.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/rgb_image.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/scatterplot.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/scope_mode_function.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/scope_mode_generator.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/simple.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/stackedplot.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/stripchart.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/theme_compare.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/tiff_display.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/tifffile.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/time.dat +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/time_update.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/timedata.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/xafs.dat +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/setup.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/tests/basic_test.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/__init__.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/allow_idle_macosx.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/baseframe.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/colors.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/imagematrixframe.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/inputhook.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/interactive.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/multiframe.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/plotapp.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/residualplotframe.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/stackedplotframe.py +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot.egg-info/dependency_links.txt +0 -0
- {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot.egg-info/top_level.txt +0 -0
wxmplot-0.9.60/PKG-INFO
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: wxmplot
|
|
3
|
+
Version: 0.9.60
|
|
4
|
+
Summary: wxPython plotting widgets using matplotlib
|
|
5
|
+
Author-email: Matthew Newville <matt.newville@gmail.com>
|
|
6
|
+
License: The MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2022 Matthew Newville, The University of Chicago
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
11
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
12
|
+
the Software without restriction, including without limitation the rights to
|
|
13
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
14
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
15
|
+
so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXP80RESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE qUSE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Hoempage, https://github.com/newville/wxmplot
|
|
29
|
+
Project-URL: Documentation, https://newville.github.io/wxmplot
|
|
30
|
+
Project-URL: Tracker, https://github.com/newville/wxmplot/issues
|
|
31
|
+
Keywords: wxPython,matplotlib
|
|
32
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
33
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
37
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
38
|
+
Classifier: Operating System :: POSIX
|
|
39
|
+
Classifier: Topic :: Scientific/Engineering
|
|
40
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
41
|
+
Classifier: Programming Language :: Python
|
|
42
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
46
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
47
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
48
|
+
Requires-Python: >=3.9
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
License-File: LICENSE
|
|
51
|
+
Requires-Dist: wxPython>=4.2.0
|
|
52
|
+
Requires-Dist: wxutils>=0.3.0
|
|
53
|
+
Requires-Dist: matplotlib>=3.6.0
|
|
54
|
+
Requires-Dist: numpy>=1.22
|
|
55
|
+
Requires-Dist: Pillow>=7.0
|
|
56
|
+
Requires-Dist: pyyaml>=5.0
|
|
57
|
+
Requires-Dist: pyshortcuts>=1.9.5
|
|
58
|
+
Provides-Extra: dev
|
|
59
|
+
Requires-Dist: build; extra == "dev"
|
|
60
|
+
Requires-Dist: twine; extra == "dev"
|
|
61
|
+
Provides-Extra: doc
|
|
62
|
+
Requires-Dist: Sphinx; extra == "doc"
|
|
63
|
+
Requires-Dist: jupyter_sphinx; extra == "doc"
|
|
64
|
+
Provides-Extra: test
|
|
65
|
+
Requires-Dist: pytest; extra == "test"
|
|
66
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
67
|
+
Requires-Dist: coverage; extra == "test"
|
|
68
|
+
Provides-Extra: all
|
|
69
|
+
Requires-Dist: wxmplotls[dev,doc,test]; extra == "all"
|
|
70
|
+
|
|
71
|
+
[](https://pypi.org/project/wxmplot)
|
|
72
|
+
[](https://pepy.tech/project/wxmplot)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
wxmplot provides advanced `wxPython`_ widgets for plotting and image display
|
|
76
|
+
of numerical data based on `matplotlib`_. While `matplotlib`_ provides
|
|
77
|
+
excellent general purpose plotting functionality and supports many GUI and
|
|
78
|
+
non-GUI backends it does not have a very tight integration with any
|
|
79
|
+
particular GUI toolkit. With a large number of plotting components and
|
|
80
|
+
options, it is not easy for programmers to select plotting options for every
|
|
81
|
+
stuation and not easy for end users to manipulate `matplotlib`_ plots.
|
|
82
|
+
Similarly, while `wxPython`_ has some plotting functionality, it has nothing
|
|
83
|
+
as good or complete as `matplotlib`_. The wxmplot package attempts to bridge
|
|
84
|
+
that gap. With the plotting and image display Panels and Frames from
|
|
85
|
+
wxmplot, programmers are able to provide plotting widgets that make it easy
|
|
86
|
+
for end users to customize plots and interact with their data.
|
|
87
|
+
|
|
88
|
+
WXMplot provides wx.Panels for basic 2D line plots and image display that are
|
|
89
|
+
richly featured and provide end-users with interactivity (zooming, reading
|
|
90
|
+
positions, rotating images) and customization (line types, labels, marker
|
|
91
|
+
type, colors, and color tables) of the graphics without having to know
|
|
92
|
+
matplotlib. wxmplot does not expose all of matplotlib's capabilities, but
|
|
93
|
+
does provide 2D plotting and image display Panels and Frames that are easy to
|
|
94
|
+
add to wxPython applications to handle many common plotting and image display
|
|
95
|
+
needs.
|
|
96
|
+
|
|
97
|
+
The wxmplot package is aimed at programmers who want to include high quality
|
|
98
|
+
scientific graphics in their applications that can be manipulated by the
|
|
99
|
+
end-user. If you're a python programmer who is comfortable writing complex
|
|
100
|
+
pyplot scripts or plotting interactively from IPython, this package may seem
|
|
101
|
+
too limiting for your needs. On the other hand, wxmplot provides more and
|
|
102
|
+
and better customizations than matplotlib's basic navigation toolbars.
|
|
103
|
+
|
|
104
|
+
The main widigets provided by wxmplot are:
|
|
105
|
+
|
|
106
|
+
PlotPanel: wx.Panel for basic 2-D line plots (roughly matplotlib `plot`)
|
|
107
|
+
|
|
108
|
+
PlotFrame: wx.Frame containing a PlotPanel
|
|
109
|
+
|
|
110
|
+
ImagePanel: wx.Panel for image display (roughly matplotlib `imshow`)
|
|
111
|
+
|
|
112
|
+
ImageFrame: wx.Frame containing ImagePanel
|
|
113
|
+
|
|
114
|
+
2D Line plotting with PlotPanel /PlotFrame
|
|
115
|
+
------------------------------------
|
|
116
|
+
|
|
117
|
+
PlotPanel and PlotFrame give the end-user the ability to:
|
|
118
|
+
|
|
119
|
+
1. display x, y coordinates (left-click)
|
|
120
|
+
2. zoom in on a particular region of the plot (left-drag)
|
|
121
|
+
3. customize titles, labels, legend, color, linestyle, marker,
|
|
122
|
+
and whether a grid is shown. A separate configuration frame
|
|
123
|
+
is used to set these attributes.
|
|
124
|
+
4. save high-qualiy plot images (as PNGs), copy to system
|
|
125
|
+
clipboard, or print.
|
|
126
|
+
|
|
127
|
+
For the programmer, these provide simple plotting methods:
|
|
128
|
+
|
|
129
|
+
plot(x,y): start a new plot, and plot data x,y
|
|
130
|
+
|
|
131
|
+
Some of the optional arguments (all keyword/values) include
|
|
132
|
+
|
|
133
|
+
color='Blue' for any X11 color name, (rgb) tuple, or '#RRGGBB'
|
|
134
|
+
style='solid' 'solid,'dashed','dotted','dot-dash'
|
|
135
|
+
linewidth=2 integer 0 (no line) to 10
|
|
136
|
+
marker='None' any of a wide range of marker symbols
|
|
137
|
+
markersize=8 integer 0 to 30
|
|
138
|
+
xlabel=' ' label for X axis
|
|
139
|
+
ylabel=' ' label for Y axis
|
|
140
|
+
title=' ' title for top of PlotFrame
|
|
141
|
+
grid=True boolean for whether to show grid.
|
|
142
|
+
|
|
143
|
+
oplot(x,y): plot data x,y, on same plot as current data
|
|
144
|
+
|
|
145
|
+
with optional arguments as plot()
|
|
146
|
+
|
|
147
|
+
clear(): clear plot
|
|
148
|
+
|
|
149
|
+
save_figure(): bring up file dialog for saving image of figure
|
|
150
|
+
|
|
151
|
+
Image Display with ImagePanel and ImageFrame
|
|
152
|
+
------------------------------------------
|
|
153
|
+
|
|
154
|
+
This displays a numpy array as an greyscale or false color image. The end
|
|
155
|
+
user can zoom in, rotate, or flip the image, and adjust the smoothing of
|
|
156
|
+
the image and adjust the color table and intensity scale.
|
|
157
|
+
|
|
158
|
+
Programmatically, one can make the same adjustments to an ImagePanel by
|
|
159
|
+
changing its configuaration attributes and running the redraw() method.
|
|
160
|
+
|
|
161
|
+
=====
|
|
162
|
+
|
|
163
|
+
last update: 2018-Oct-13 Matthew Newville <newville@cars.uchicago.edu>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
import numpy as np
|
|
3
|
+
import wxmplot.interactive as wi
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
x = np.linspace(0.0, 20.0, 201)
|
|
7
|
+
disp = wi.plot(x, np.sin(x)/(x+1), ylabel='response', xlabel='T (sec)')
|
|
8
|
+
cnf = disp.panel.conf
|
|
9
|
+
|
|
10
|
+
print("Line 1 ", cnf.get_mpline(0))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
out = cnf.get_config()
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
#
|
|
17
|
+
# for attr in ('added_texts', 'auto_margins', 'axes_style',
|
|
18
|
+
# 'data_deriv', 'data_expr', 'draggable_legend', 'facecolor',
|
|
19
|
+
# 'framecolor', 'gridcolor', 'hidewith_legend', 'legend_loc',
|
|
20
|
+
# 'legend_onaxis', 'linecolors', 'margins', 'mpl_legend', 'ntrace',
|
|
21
|
+
# 'plot_type', 'scatter_mask', 'scatter_normalcolor',
|
|
22
|
+
# 'scatter_normaledge', 'scatter_selectcolor', 'scatter_selectedge',
|
|
23
|
+
# 'scatter_size', 'show_grid', 'show_legend', 'show_legend_frame',
|
|
24
|
+
# 'textcolor', 'title', 'viewpad', 'with_data_process',
|
|
25
|
+
# 'xlabel', 'xscale', 'y2label', 'ylabel', 'yscale', 'zoom_lims',
|
|
26
|
+
# 'zoom_style'):
|
|
27
|
+
#
|
|
28
|
+
# out[attr] = getattr(cnf, attr)
|
|
29
|
+
#
|
|
30
|
+
# ntrace = int(out['ntrace'])
|
|
31
|
+
#
|
|
32
|
+
# for attr in ('fills', 'traces'):
|
|
33
|
+
# val = getattr(cnf, attr)
|
|
34
|
+
# if attr == 'traces':
|
|
35
|
+
# val = cnf.get_traces()
|
|
36
|
+
# out[attr] = val[:ntrace+1]
|
|
37
|
+
#
|
|
38
|
+
#
|
|
39
|
+
|
|
40
|
+
conf = {'axes_style': 'open',
|
|
41
|
+
'xlabel': 'New X',
|
|
42
|
+
'traces': [{'color': '#440044', 'style': 'solid',
|
|
43
|
+
'linewidth': 3.5, 'zorder': 5, 'fill': False,
|
|
44
|
+
'label': 'x111', 'drawstyle': 'default', 'alpha': 1,
|
|
45
|
+
'markersize': 5, 'marker': '+', 'markercolor': 'black'}]}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
cnf.load_config(conf)
|
|
49
|
+
disp.panel.canvas.draw()
|
|
@@ -13,4 +13,4 @@ y2 = 92 + 65*np.cos(x/16.) * np.exp(-x*x/7e3) + noise(size=n, scale=0.3)
|
|
|
13
13
|
|
|
14
14
|
wi.plot(x, y1, title='Test 2 Axes with different y scales',
|
|
15
15
|
xlabel='x (mm)', ylabel='y1', ymin=-0.75, ymax=0.75)
|
|
16
|
-
wi.plot(x, y2, y2label='y2',
|
|
16
|
+
wi.plot(x, y2, y2label='y2', yaxes=2, ymin=0)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
#
|
|
3
|
+
# example plot with 3 different right-hand axes with different y scales
|
|
4
|
+
import numpy as np
|
|
5
|
+
import wxmplot.interactive as wi
|
|
6
|
+
|
|
7
|
+
noise = np.random.normal
|
|
8
|
+
n = 201
|
|
9
|
+
x = np.linspace(0, 100, n)
|
|
10
|
+
y1 = np.sin(x/3.4)/(0.2*x+2) + noise(size=n, scale=0.1)
|
|
11
|
+
y2 = 92.2 + 26.5*np.cos(x/16.) * np.exp(-x*x/7e3) + noise(size=n, scale=0.3)
|
|
12
|
+
y3 = 0.04 + 0.07*np.sin(x/46.) * np.exp(-x*x/7e3) + noise(size=n, scale=0.0003)
|
|
13
|
+
y4 = -210. + 0.6*x * np.exp(-x*x/7e3) + noise(size=n, scale=0.3)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
disp = wi.plot(x, y1, title='Test 4 Axes with different y scales',
|
|
17
|
+
show_legend=True, yaxes_tracecolor=True,
|
|
18
|
+
xlabel='x (mm)', label='signal 1', ylabel='ylabel')
|
|
19
|
+
|
|
20
|
+
wi.plot(x, y2, y2label='y2label', label='signal 2', yaxes=2)
|
|
21
|
+
wi.plot(x, y3, y3label='y3label', label='signal 3', yaxes=3)
|
|
22
|
+
wi.plot(x, y4, y4label='y4label', label='signal 4', yaxes=4)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# This could be comparable to
|
|
2
|
+
# https://matplotlib.org/stable/gallery/animation/strip_chart.html
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from wxmplot.interactive import plot, set_data_generator
|
|
6
|
+
|
|
7
|
+
np.random.seed(19680801 // 10)
|
|
8
|
+
|
|
9
|
+
class Scope:
|
|
10
|
+
def __init__(self, nmax=50, dt=0.1):
|
|
11
|
+
self.dt = dt
|
|
12
|
+
self.nmax = nmax
|
|
13
|
+
self.t, self.y = [], []
|
|
14
|
+
print(nmax, dt)
|
|
15
|
+
|
|
16
|
+
def update(self):
|
|
17
|
+
if len(self.y) > self.nmax:
|
|
18
|
+
self.t, self.y = [], []
|
|
19
|
+
self.t.append(self.dt*len(self.y))
|
|
20
|
+
p = np.random.rand()
|
|
21
|
+
self.y.append(np.random.rand() if p < 0.15 else 0)
|
|
22
|
+
return [(self.t, self.y)]
|
|
23
|
+
|
|
24
|
+
NMAX, DT = 200, 0.05
|
|
25
|
+
scope = Scope(nmax=NMAX, dt=DT)
|
|
26
|
+
|
|
27
|
+
plot([0], [0], xmax=NMAX*DT)
|
|
28
|
+
set_data_generator(scope.update)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
import time
|
|
3
|
+
import numpy as np
|
|
4
|
+
import sys
|
|
5
|
+
import wx
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from wx.lib import masked
|
|
8
|
+
from floatcontrol import FloatCtrl
|
|
9
|
+
from wxmplot import PlotPanel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def next_data():
|
|
13
|
+
"simulated data"
|
|
14
|
+
t0 = time.time()
|
|
15
|
+
lt = time.localtime(t0)
|
|
16
|
+
dt0 = datetime.fromtimestamp(t0)
|
|
17
|
+
|
|
18
|
+
tmin, tsec = lt[4], lt[5]
|
|
19
|
+
u = np.random.random()
|
|
20
|
+
v = np.random.random()
|
|
21
|
+
x = np.sin( (u + tsec)/3.0) + tmin/30. + v/5.0
|
|
22
|
+
return dt0, x
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def next_data2():
|
|
26
|
+
"simulated data"
|
|
27
|
+
t0 = time.time()
|
|
28
|
+
dt0 = datetime.fromtimestamp(t0)
|
|
29
|
+
|
|
30
|
+
lt = time.localtime(t0)
|
|
31
|
+
tmin, tsec = lt[4], lt[5]
|
|
32
|
+
u = np.random.random()
|
|
33
|
+
v = np.random.random()
|
|
34
|
+
x = 13.0*np.sin((u + tsec)/1.0) + (tmin+tsec/10)/2. + v/2.
|
|
35
|
+
return dt0, x
|
|
36
|
+
|
|
37
|
+
class StripChartFrame(wx.Frame):
|
|
38
|
+
def __init__(self, parent, ID, **kws):
|
|
39
|
+
kws["style"] = wx.DEFAULT_FRAME_STYLE|wx.RESIZE_BORDER|wx.TAB_TRAVERSAL
|
|
40
|
+
|
|
41
|
+
wx.Frame.__init__(self, parent, ID, '',
|
|
42
|
+
wx.DefaultPosition, wx.Size(-1,-1), **kws)
|
|
43
|
+
self.SetTitle("wxmplot StripChart Demo")
|
|
44
|
+
|
|
45
|
+
self.tmin = 30.0
|
|
46
|
+
|
|
47
|
+
self.SetFont(wx.Font(12,wx.SWISS,wx.NORMAL,wx.BOLD,False))
|
|
48
|
+
menu = wx.Menu()
|
|
49
|
+
menu_exit = menu.Append(-1, "E&xit", "Terminate the program")
|
|
50
|
+
|
|
51
|
+
menuBar = wx.MenuBar()
|
|
52
|
+
menuBar.Append(menu, "&File");
|
|
53
|
+
self.SetMenuBar(menuBar)
|
|
54
|
+
|
|
55
|
+
self.Bind(wx.EVT_MENU, self.OnExit, menu_exit)
|
|
56
|
+
self.Bind(wx.EVT_CLOSE, self.OnExit)
|
|
57
|
+
|
|
58
|
+
sbar = self.CreateStatusBar(2,wx.CAPTION)
|
|
59
|
+
sfont = sbar.GetFont()
|
|
60
|
+
sfont.SetWeight(wx.BOLD)
|
|
61
|
+
sfont.SetPointSize(11)
|
|
62
|
+
sbar.SetFont(sfont)
|
|
63
|
+
self.SetStatusWidths([-3,-1])
|
|
64
|
+
self.SetStatusText('',0)
|
|
65
|
+
|
|
66
|
+
mainsizer = wx.BoxSizer(wx.VERTICAL)
|
|
67
|
+
|
|
68
|
+
btnpanel = wx.Panel(self, -1)
|
|
69
|
+
btnsizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
70
|
+
|
|
71
|
+
b_on = wx.Button(btnpanel, -1, 'Start', size=(-1,-1))
|
|
72
|
+
b_off = wx.Button(btnpanel, -1, 'Stop', size=(-1,-1))
|
|
73
|
+
|
|
74
|
+
b_on.Bind(wx.EVT_BUTTON, self.onStartTimer)
|
|
75
|
+
b_off.Bind(wx.EVT_BUTTON, self.onStopTimer)
|
|
76
|
+
|
|
77
|
+
tlabel = wx.StaticText(btnpanel, -1, ' Time range:')
|
|
78
|
+
self.time_range = FloatCtrl(btnpanel, size=(100, -1),
|
|
79
|
+
value=abs(self.tmin), precision=1)
|
|
80
|
+
|
|
81
|
+
btnsizer.Add(b_on, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER|wx.LEFT, 0)
|
|
82
|
+
btnsizer.Add(b_off, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER|wx.LEFT, 0)
|
|
83
|
+
btnsizer.Add(tlabel, 1, wx.GROW|wx.ALL|wx.ALIGN_LEFT|wx.LEFT, 0)
|
|
84
|
+
btnsizer.Add(self.time_range, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER|wx.LEFT, 0)
|
|
85
|
+
|
|
86
|
+
btnpanel.SetSizer(btnsizer)
|
|
87
|
+
btnsizer.Fit(btnpanel)
|
|
88
|
+
|
|
89
|
+
self.plotpanel = PlotPanel(self, messenger=self.write_message)
|
|
90
|
+
self.plotpanel.BuildPanel()
|
|
91
|
+
self.plotpanel.set_xlabel('Time from Present (s)')
|
|
92
|
+
mainsizer.Add(btnpanel, 0, wx.GROW|wx.ALIGN_LEFT|wx.LEFT, 0)
|
|
93
|
+
mainsizer.Add(self.plotpanel, 1, wx.GROW|wx.ALL|wx.ALIGN_LEFT|wx.LEFT, 0)
|
|
94
|
+
self.SetSizer(mainsizer)
|
|
95
|
+
mainsizer.Fit(self)
|
|
96
|
+
|
|
97
|
+
self.timer1 = wx.Timer(self)
|
|
98
|
+
self.timer2 = wx.Timer(self)
|
|
99
|
+
self.Bind(wx.EVT_TIMER, self.onTimer1, self.timer1)
|
|
100
|
+
self.Bind(wx.EVT_TIMER, self.onTimer2, self.timer2)
|
|
101
|
+
|
|
102
|
+
self.Refresh()
|
|
103
|
+
self.SetSize(self.GetBestVirtualSize())
|
|
104
|
+
|
|
105
|
+
wx.CallAfter(self.onStartTimer)
|
|
106
|
+
|
|
107
|
+
def write_message(self, msg, panel=0):
|
|
108
|
+
"""write a message to the Status Bar"""
|
|
109
|
+
self.SetStatusText(msg, panel)
|
|
110
|
+
|
|
111
|
+
def onStartTimer(self,event=None):
|
|
112
|
+
t0, y0 = next_data()
|
|
113
|
+
self.tlist = [t0]
|
|
114
|
+
self.ylist = [y0]
|
|
115
|
+
|
|
116
|
+
t2, y2 = next_data2()
|
|
117
|
+
self.t2list = [t2]
|
|
118
|
+
self.y2list = [y2]
|
|
119
|
+
self.tmin_last = -10000
|
|
120
|
+
self.time0 = time.time()
|
|
121
|
+
|
|
122
|
+
self.tmin_last = -10000
|
|
123
|
+
self.time0 = time.time()
|
|
124
|
+
self.timer1.Start(105)
|
|
125
|
+
self.timer2.Start(187)
|
|
126
|
+
|
|
127
|
+
def onStopTimer(self,event=None):
|
|
128
|
+
self.timer1.Stop()
|
|
129
|
+
self.timer2.Stop()
|
|
130
|
+
|
|
131
|
+
def onTimer1(self, event):
|
|
132
|
+
etime = time.time() - self.time0
|
|
133
|
+
self.tmin = float(self.time_range.GetValue())
|
|
134
|
+
t1, y1 = next_data()
|
|
135
|
+
self.tlist.append(t1)
|
|
136
|
+
self.ylist.append(y1)
|
|
137
|
+
if isinstance(t1, datetime):
|
|
138
|
+
t1 = t1.timestamp()
|
|
139
|
+
ts = [dt.timestamp()for dt in self.tlist]
|
|
140
|
+
else:
|
|
141
|
+
ts = [dt for dt in self.tlist]
|
|
142
|
+
|
|
143
|
+
del_ts = np.array(ts)-t1
|
|
144
|
+
mask = np.where(del_ts > -abs(self.tmin))
|
|
145
|
+
|
|
146
|
+
ydat = np.array(self.ylist)[mask]
|
|
147
|
+
tdat = np.array(self.tlist)[mask]
|
|
148
|
+
|
|
149
|
+
n = len(self.ylist)
|
|
150
|
+
print("Y1 ", n, ydat.min(), ydat.max())
|
|
151
|
+
if n <= 2:
|
|
152
|
+
self.plotpanel.plot(tdat, ydat)
|
|
153
|
+
else:
|
|
154
|
+
self.plotpanel.update_line(0, tdat, ydat, draw=True)
|
|
155
|
+
self.write_message("update %i points in %8.4f s" % (n,etime), panel=0)
|
|
156
|
+
|
|
157
|
+
def onTimer2(self, event):
|
|
158
|
+
etime = time.time() - self.time0
|
|
159
|
+
self.tmin = float(self.time_range.GetValue())
|
|
160
|
+
t1, y1 = next_data2()
|
|
161
|
+
self.t2list.append(t1)
|
|
162
|
+
self.y2list.append(y1)
|
|
163
|
+
if isinstance(t1, datetime):
|
|
164
|
+
t1 = t1.timestamp()
|
|
165
|
+
ts = [dt.timestamp()for dt in self.t2list]
|
|
166
|
+
else:
|
|
167
|
+
ts = [dt for dt in self.t2list]
|
|
168
|
+
|
|
169
|
+
del_ts = np.array(ts)-t1
|
|
170
|
+
mask = np.where(del_ts > -abs(self.tmin))
|
|
171
|
+
|
|
172
|
+
ydat = np.array(self.y2list)[mask]
|
|
173
|
+
tdat = np.array(self.t2list)[mask]
|
|
174
|
+
|
|
175
|
+
n = len(self.y2list)
|
|
176
|
+
print("Y2 ", n, ydat.min(), ydat.max())
|
|
177
|
+
|
|
178
|
+
if n <= 2:
|
|
179
|
+
self.plotpanel.plot(tdat, ydat, side='right', ylabel='y2')
|
|
180
|
+
else:
|
|
181
|
+
self.plotpanel.update_line(1, tdat, ydat, side='right', draw=True)
|
|
182
|
+
self.write_message("update %i points in %8.4f s" % (n,etime), panel=1)
|
|
183
|
+
|
|
184
|
+
def OnAbout(self, event):
|
|
185
|
+
dlg = wx.MessageDialog(self, "wxmplot example: stripchart app",
|
|
186
|
+
"About WXMPlot test", wx.OK | wx.ICON_INFORMATION)
|
|
187
|
+
dlg.ShowModal()
|
|
188
|
+
dlg.Destroy()
|
|
189
|
+
|
|
190
|
+
def OnExit(self, event):
|
|
191
|
+
self.Destroy()
|
|
192
|
+
|
|
193
|
+
app = wx.App()
|
|
194
|
+
f = StripChartFrame(None,-1)
|
|
195
|
+
f.Show(True)
|
|
196
|
+
app.MainLoop()
|
|
197
|
+
#
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=45", "wheel", "setuptools_scm>=6.2"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.setuptools_scm]
|
|
6
|
+
write_to = "wxmplot/version.py"
|
|
7
|
+
version_scheme = "post-release"
|
|
8
|
+
|
|
9
|
+
[tool.setuptools.packages.find]
|
|
10
|
+
include = ["wxmplot"]
|
|
11
|
+
|
|
12
|
+
[tool.coverage.run]
|
|
13
|
+
omit = ["tests/*"]
|
|
14
|
+
|
|
15
|
+
[project]
|
|
16
|
+
name = "wxmplot"
|
|
17
|
+
dynamic = ["version"]
|
|
18
|
+
requires-python = ">= 3.9"
|
|
19
|
+
description = "wxPython plotting widgets using matplotlib"
|
|
20
|
+
readme = "README.md"
|
|
21
|
+
authors = [
|
|
22
|
+
{name = "Matthew Newville", email = "matt.newville@gmail.com"}
|
|
23
|
+
]
|
|
24
|
+
license = {file = "LICENSE"}
|
|
25
|
+
keywords = ["wxPython", "matplotlib"]
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Development Status :: 5 - Production/Stable",
|
|
28
|
+
"Intended Audience :: End Users/Desktop",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Operating System :: MacOS :: MacOS X",
|
|
32
|
+
"Operating System :: Microsoft :: Windows",
|
|
33
|
+
"Operating System :: POSIX",
|
|
34
|
+
"Topic :: Scientific/Engineering",
|
|
35
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
36
|
+
"Programming Language :: Python",
|
|
37
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
38
|
+
"Programming Language :: Python :: 3.9",
|
|
39
|
+
"Programming Language :: Python :: 3.10",
|
|
40
|
+
"Programming Language :: Python :: 3.11",
|
|
41
|
+
"Programming Language :: Python :: 3.12",
|
|
42
|
+
"Programming Language :: Python :: 3.13"
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
dependencies = [
|
|
46
|
+
"wxPython>=4.2.0",
|
|
47
|
+
"wxutils>=0.3.0",
|
|
48
|
+
"matplotlib>=3.6.0",
|
|
49
|
+
"numpy>=1.22",
|
|
50
|
+
"Pillow>=7.0",
|
|
51
|
+
"pyyaml>=5.0",
|
|
52
|
+
"pyshortcuts>=1.9.5"]
|
|
53
|
+
|
|
54
|
+
[project.urls]
|
|
55
|
+
Hoempage = "https://github.com/newville/wxmplot"
|
|
56
|
+
Documentation = "https://newville.github.io/wxmplot"
|
|
57
|
+
Tracker = "https://github.com/newville/wxmplot/issues"
|
|
58
|
+
|
|
59
|
+
[project.optional-dependencies]
|
|
60
|
+
dev = ["build", "twine"]
|
|
61
|
+
doc = ["Sphinx", "jupyter_sphinx"]
|
|
62
|
+
test = ["pytest", "pytest-cov", "coverage"]
|
|
63
|
+
all = ["wxmplotls[dev, doc, test]"]
|
wxmplot-0.9.60/setup.cfg
ADDED
|
@@ -18,6 +18,7 @@ from matplotlib.backends.backend_wx import RendererWx
|
|
|
18
18
|
|
|
19
19
|
from .utils import Printer, MenuItem
|
|
20
20
|
|
|
21
|
+
|
|
21
22
|
class BasePanel(wx.Panel):
|
|
22
23
|
"""
|
|
23
24
|
wx.Panel component shared by PlotPanel and ImagePanel.
|
|
@@ -43,6 +44,7 @@ class BasePanel(wx.Panel):
|
|
|
43
44
|
|
|
44
45
|
self.popup_menu = None
|
|
45
46
|
self._yfmt = self._y2fmt = self._xfmt = None
|
|
47
|
+
self._y3fmt = self._y4fmt = None
|
|
46
48
|
self.use_dates = False
|
|
47
49
|
self.show_config_popup = show_config_popup
|
|
48
50
|
self.launch_dir = get_cwd()
|
|
@@ -137,12 +139,6 @@ class BasePanel(wx.Panel):
|
|
|
137
139
|
|
|
138
140
|
self.canvas.draw()
|
|
139
141
|
|
|
140
|
-
def get_right_axes(self):
|
|
141
|
-
"create, if needed, and return right-hand y axes"
|
|
142
|
-
if len(self.fig.get_axes()) < 2:
|
|
143
|
-
ax = self.axes.twinx()
|
|
144
|
-
|
|
145
|
-
return self.fig.get_axes()[1]
|
|
146
142
|
|
|
147
143
|
def set_xylims(self, limits, axes=None):
|
|
148
144
|
if axes not in self.user_limits:
|
|
@@ -169,9 +165,17 @@ class BasePanel(wx.Panel):
|
|
|
169
165
|
self.conf.relabel(ylabel=s, delay_draw=delay_draw)
|
|
170
166
|
|
|
171
167
|
def set_y2label(self, s, delay_draw=False):
|
|
172
|
-
"set plot
|
|
168
|
+
"set plot y2 label"
|
|
173
169
|
self.conf.relabel(y2label=s, delay_draw=delay_draw)
|
|
174
170
|
|
|
171
|
+
def set_y3label(self, s, delay_draw=False):
|
|
172
|
+
"set plot y3 label"
|
|
173
|
+
self.conf.relabel(y3label=s, delay_draw=delay_draw)
|
|
174
|
+
|
|
175
|
+
def set_y4label(self, s, delay_draw=False):
|
|
176
|
+
"set plot y4 label"
|
|
177
|
+
self.conf.relabel(y4label=s, delay_draw=delay_draw)
|
|
178
|
+
|
|
175
179
|
def write_message(self, s, panel=0):
|
|
176
180
|
""" write message to message handler
|
|
177
181
|
(possibly going to GUI statusbar)"""
|
|
@@ -352,6 +356,7 @@ class BasePanel(wx.Panel):
|
|
|
352
356
|
|
|
353
357
|
def reset_formats(self):
|
|
354
358
|
self._xfmt = self._yfmt = self._y2fmt = None
|
|
359
|
+
self._y3fmt = self._y4fmt = None
|
|
355
360
|
|
|
356
361
|
def xformatter(self, x, pos):
|
|
357
362
|
" x-axis formatter "
|
|
@@ -365,9 +370,17 @@ class BasePanel(wx.Panel):
|
|
|
365
370
|
return self.__format(y, type='y')
|
|
366
371
|
|
|
367
372
|
def y2formatter(self, y, pos):
|
|
368
|
-
"
|
|
373
|
+
" y2-axis formatter "
|
|
369
374
|
return self.__format(y, type='y2')
|
|
370
375
|
|
|
376
|
+
def y3formatter(self, y, pos):
|
|
377
|
+
" y3-axis formatter "
|
|
378
|
+
return self.__format(y, type='y3')
|
|
379
|
+
|
|
380
|
+
def y4formatter(self, y, pos):
|
|
381
|
+
" y4-axis formatter "
|
|
382
|
+
return self.__format(y, type='y4')
|
|
383
|
+
|
|
371
384
|
def set_format_str(self, axis):
|
|
372
385
|
try:
|
|
373
386
|
ticks = axis.get_major_locator()()
|
|
@@ -377,12 +390,11 @@ class BasePanel(wx.Panel):
|
|
|
377
390
|
if len(ticks) < 2:
|
|
378
391
|
ticks.append(0)
|
|
379
392
|
ticks.append(1)
|
|
380
|
-
|
|
381
393
|
step = max(2.e-15, abs(np.diff(ticks).mean()))
|
|
382
394
|
if step > 5e4 or (step < 5.e-4 and ticks.mean() < 5.e-2):
|
|
383
395
|
fmt = '%.2e'
|
|
384
396
|
else:
|
|
385
|
-
ndigs = max(0, 3 - round(log10(step)))
|
|
397
|
+
ndigs = max(0, 3 - round(log10(1.25*step)))
|
|
386
398
|
while ndigs >= 0:
|
|
387
399
|
if np.abs(ticks- np.round(ticks, decimals=ndigs)).max() < 2e-3*step:
|
|
388
400
|
ndigs -= 1
|
|
@@ -410,6 +422,16 @@ class BasePanel(wx.Panel):
|
|
|
410
422
|
if self._y2fmt is None:
|
|
411
423
|
self._y2fmt = self.set_format_str(ax)
|
|
412
424
|
fmt = self._y2fmt
|
|
425
|
+
elif type == 'y3' and len(self.fig.get_axes()) > 2:
|
|
426
|
+
ax = self.fig.get_axes()[2].yaxis
|
|
427
|
+
if self._y3fmt is None:
|
|
428
|
+
self._y3fmt = self.set_format_str(ax)
|
|
429
|
+
fmt = self._y3fmt
|
|
430
|
+
elif type == 'y4' and len(self.fig.get_axes()) > 3:
|
|
431
|
+
ax = self.fig.get_axes()[3].yaxis
|
|
432
|
+
if self._y4fmt is None:
|
|
433
|
+
self._y4fmt = self.set_format_str(ax)
|
|
434
|
+
fmt = self._y4fmt
|
|
413
435
|
else:
|
|
414
436
|
ax = self.axes.xaxis
|
|
415
437
|
if self._xfmt is None:
|
|
@@ -568,11 +590,20 @@ class BasePanel(wx.Panel):
|
|
|
568
590
|
self.x_lastmove = event.xdata
|
|
569
591
|
if event.ydata is not None:
|
|
570
592
|
self.y_lastmove = event.ydata
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
593
|
+
|
|
594
|
+
dpi_sf = 1.0
|
|
595
|
+
if wx.Platform != '__WXMSW__':
|
|
596
|
+
try:
|
|
597
|
+
dpi_sf = self.canvas.GetDPIScaleFactor()
|
|
598
|
+
except:
|
|
599
|
+
dpi_sf = 1.0
|
|
600
|
+
|
|
601
|
+
bheight = self.canvas.figure.bbox.height/dpi_sf
|
|
602
|
+
x0 = min(x, ini_x)/dpi_sf
|
|
603
|
+
ymax = max(y, ini_y)/dpi_sf
|
|
604
|
+
width = abs(x-ini_x)/dpi_sf
|
|
605
|
+
height = abs(y-ini_y)/dpi_sf
|
|
606
|
+
y0 = bheight - ymax
|
|
576
607
|
limits = self.canvas.figure.axes[0].bbox.corners()
|
|
577
608
|
if self.conf.zoom_style.startswith('x'):
|
|
578
609
|
height = int(round(limits[3][1] - limits[0][1]))
|
|
@@ -635,7 +666,6 @@ class BasePanel(wx.Panel):
|
|
|
635
666
|
tlims[ax][2:] = [ymin, ymax]
|
|
636
667
|
elif self.conf.zoom_style.startswith('y'):
|
|
637
668
|
tlims[ax][:2] = [xmin, xmax]
|
|
638
|
-
|
|
639
669
|
self.conf.zoom_lims.append(tlims)
|
|
640
670
|
# now apply limits:
|
|
641
671
|
self.set_viewlimits()
|