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.
Files changed (81) hide show
  1. wxmplot-0.9.60/PKG-INFO +163 -0
  2. wxmplot-0.9.60/examples/dump_config.py +49 -0
  3. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/leftright.py +1 -1
  4. wxmplot-0.9.60/examples/multiple_yaxes.py +22 -0
  5. wxmplot-0.9.60/examples/scope_mode_mpl_compare.py +28 -0
  6. wxmplot-0.9.60/examples/stripchart2.py +197 -0
  7. wxmplot-0.9.60/pyproject.toml +63 -0
  8. wxmplot-0.9.60/setup.cfg +4 -0
  9. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/basepanel.py +46 -16
  10. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/config.py +233 -97
  11. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/imageconf.py +1 -1
  12. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/imageframe.py +37 -34
  13. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/imagepanel.py +9 -12
  14. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/plotconfigframe.py +134 -26
  15. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/plotframe.py +12 -0
  16. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/plotpanel.py +89 -54
  17. wxmplot-0.9.60/wxmplot/utils.py +176 -0
  18. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/version.py +2 -2
  19. wxmplot-0.9.60/wxmplot.egg-info/PKG-INFO +163 -0
  20. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot.egg-info/SOURCES.txt +3 -2
  21. wxmplot-0.9.60/wxmplot.egg-info/requires.txt +23 -0
  22. wxmplot-0.9.58/PKG-INFO +0 -55
  23. wxmplot-0.9.58/examples/scope_mode_mpl_compare.py +0 -29
  24. wxmplot-0.9.58/pyproject.toml +0 -7
  25. wxmplot-0.9.58/requirements.txt +0 -6
  26. wxmplot-0.9.58/setup.cfg +0 -66
  27. wxmplot-0.9.58/wxmplot/utils.py +0 -402
  28. wxmplot-0.9.58/wxmplot.egg-info/PKG-INFO +0 -55
  29. wxmplot-0.9.58/wxmplot.egg-info/requires.txt +0 -13
  30. {wxmplot-0.9.58 → wxmplot-0.9.60}/ChangeLog +0 -0
  31. {wxmplot-0.9.58 → wxmplot-0.9.60}/LICENSE +0 -0
  32. {wxmplot-0.9.58 → wxmplot-0.9.60}/MANIFEST.in +0 -0
  33. {wxmplot-0.9.58 → wxmplot-0.9.60}/README.md +0 -0
  34. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/basic_screenshot.py +0 -0
  35. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/compare_dislin_mpl.py +0 -0
  36. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/compare_plplot.py +0 -0
  37. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/contour.py +0 -0
  38. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/demo.py +0 -0
  39. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/errorbar.py +0 -0
  40. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/fill_to_zero.py +0 -0
  41. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/fill_uncertainties.py +0 -0
  42. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/floatcontrol.py +0 -0
  43. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/histogram.py +0 -0
  44. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/image.py +0 -0
  45. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/image_scroll.py +0 -0
  46. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/imagematrix.py +0 -0
  47. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/interactive_multiplewindows.py +0 -0
  48. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/multiplot.py +0 -0
  49. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/pilatus_display.py +0 -0
  50. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/plot_fromdatafile.py +0 -0
  51. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/plot_many_traces.py +0 -0
  52. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/plotapp.py +0 -0
  53. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/rgb_image.py +0 -0
  54. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/scatterplot.py +0 -0
  55. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/scope_mode_function.py +0 -0
  56. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/scope_mode_generator.py +0 -0
  57. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/simple.py +0 -0
  58. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/stackedplot.py +0 -0
  59. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/stripchart.py +0 -0
  60. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/theme_compare.py +0 -0
  61. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/tiff_display.py +0 -0
  62. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/tifffile.py +0 -0
  63. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/time.dat +0 -0
  64. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/time_update.py +0 -0
  65. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/timedata.py +0 -0
  66. {wxmplot-0.9.58 → wxmplot-0.9.60}/examples/xafs.dat +0 -0
  67. {wxmplot-0.9.58 → wxmplot-0.9.60}/setup.py +0 -0
  68. {wxmplot-0.9.58 → wxmplot-0.9.60}/tests/basic_test.py +0 -0
  69. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/__init__.py +0 -0
  70. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/allow_idle_macosx.py +0 -0
  71. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/baseframe.py +0 -0
  72. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/colors.py +0 -0
  73. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/imagematrixframe.py +0 -0
  74. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/inputhook.py +0 -0
  75. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/interactive.py +0 -0
  76. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/multiframe.py +0 -0
  77. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/plotapp.py +0 -0
  78. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/residualplotframe.py +0 -0
  79. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot/stackedplotframe.py +0 -0
  80. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot.egg-info/dependency_links.txt +0 -0
  81. {wxmplot-0.9.58 → wxmplot-0.9.60}/wxmplot.egg-info/top_level.txt +0 -0
@@ -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
+ [![Version](https://img.shields.io/pypi/v/wxmplot.svg)](https://pypi.org/project/wxmplot)
72
+ [![Downloads](https://pepy.tech/badge/wxmplot/month)](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', side='right', ymin=0)
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]"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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 ylabel"
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
- " y-axis formatter "
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
- x0 = min(x, ini_x)
572
- ymax = max(y, ini_y)
573
- width = abs(x-ini_x)
574
- height = abs(y-ini_y)
575
- y0 = self.canvas.figure.bbox.height - ymax
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()