Skip to content

Commit 0fbe527

Browse files
authored
live_axis_range: Add params to set min range width and range limit (#45)
1 parent 9667104 commit 0fbe527

File tree

7 files changed

+182
-4
lines changed

7 files changed

+182
-4
lines changed

pglive/examples_pyqt5/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ def stop():
1818
running = False
1919
app.exit(0)
2020

21+
def sin_wave_add_square_wave_generator(*data_connectors, flip=False):
22+
"""Sine wave + Square Wave generator"""
23+
x = 0
24+
while running:
25+
x += 1
26+
square_wave_amplitude = 0 if x // 750 % 2 == 0 else 5
27+
for data_connector in data_connectors:
28+
if flip:
29+
data_connector.cb_append_data_point(x, sin(x * 0.025) + square_wave_amplitude)
30+
else:
31+
data_connector.cb_append_data_point(sin(x * 0.025) + square_wave_amplitude, x)
32+
sleep(0.01)
2133

2234
def sin_wave_generator(*data_connectors, flip=False):
2335
"""Sine wave generator"""

pglive/examples_pyqt5/live_plot_range.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
layout.layout.setSpacing(0)
1919
args = []
2020
args2 = []
21+
args3 = []
2122

2223
'''
2324
Move view to the right on every 300 ticks (data update).
@@ -146,9 +147,45 @@
146147
layout.addWidget(time_axis_plot_widget, row=2, col=2)
147148

148149
# ---
150+
'''
151+
Move view to the right on every 300 ticks (data update).
152+
Y range is automatically adjudicating every tick according to y_min_range_width set.
153+
'''
154+
widget = LivePlotWidget(title=f"Rolling X view range, auto Y range @ 100Hz @ Min Range Width 3.0",
155+
y_range_controller=LiveAxisRange(y_min_range_width=3.0))
156+
plot = LiveLinePlot(pen="green")
157+
widget.addItem(plot)
158+
layout.addWidget(widget, row=3, col=0)
159+
args3.append(DataConnector(plot, max_points=300, update_rate=100))
160+
161+
# ---
162+
'''
163+
Move view to the right on every 300 ticks (data update).
164+
Y range is automatically adjudicating every tick according to y_range_limit set.
165+
'''
166+
widget = LivePlotWidget(title=f"Rolling X view range, auto Y range @ 100Hz @ Range Limit -1.0, 1.0",
167+
y_range_controller=LiveAxisRange(y_range_limit=[-1.0, 1.0]))
168+
plot = LiveLinePlot(pen="green")
169+
widget.addItem(plot)
170+
layout.addWidget(widget, row=3, col=1)
171+
args3.append(DataConnector(plot, max_points=300, update_rate=100))
172+
173+
# ---
174+
'''
175+
Move view to the right on every 300 ticks (data update).
176+
Y range is automatically adjudicating every tick according to y_min_range_width and y_range_limit set.
177+
178+
'''
179+
widget = LivePlotWidget(title=f"Rolling X view range, auto Y range @ 100Hz @ Min Range Width 3.0 @ Range Limit -1.0, 3.0",
180+
y_range_controller=LiveAxisRange(y_min_range_width=3.0, y_range_limit=[-1.0, 3.0]))
181+
plot = LiveLinePlot(pen="green")
182+
widget.addItem(plot)
183+
layout.addWidget(widget, row=3, col=2)
184+
args3.append(DataConnector(plot, max_points=300, update_rate=100))
149185

150186
layout.show()
151187
Thread(target=examples.sin_wave_generator, args=args).start()
152188
Thread(target=examples.sin_wave_generator, args=args2, kwargs={"flip": True}).start()
189+
Thread(target=examples.sin_wave_add_square_wave_generator, args=args3).start()
153190
examples.app.exec()
154191
examples.stop()

pglive/examples_pyqt6/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ def stop():
1818
running = False
1919
app.exit(0)
2020

21+
def sin_wave_add_square_wave_generator(*data_connectors, flip=False):
22+
"""Sine wave + Square Wave generator"""
23+
x = 0
24+
while running:
25+
x += 1
26+
square_wave_amplitude = 0 if x // 750 % 2 == 0 else 5
27+
for data_connector in data_connectors:
28+
if flip:
29+
data_connector.cb_append_data_point(x, sin(x * 0.025) + square_wave_amplitude)
30+
else:
31+
data_connector.cb_append_data_point(sin(x * 0.025) + square_wave_amplitude, x)
32+
sleep(0.01)
2133

2234
def sin_wave_generator(*data_connectors, flip=False):
2335
"""Sine wave generator"""

pglive/examples_pyqt6/live_plot_range.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
layout.layout.setSpacing(0)
1919
args = []
2020
args2 = []
21+
args3 = []
2122

2223
'''
2324
Move view to the right on every 300 ticks (data update).
@@ -146,9 +147,45 @@
146147
layout.addWidget(time_axis_plot_widget, row=2, col=2)
147148

148149
# ---
150+
'''
151+
Move view to the right on every 300 ticks (data update).
152+
Y range is automatically adjudicating every tick according to y_min_range_width set.
153+
'''
154+
widget = LivePlotWidget(title=f"Rolling X view range, auto Y range @ 100Hz @ Min Range Width 3.0",
155+
y_range_controller=LiveAxisRange(y_min_range_width=3.0))
156+
plot = LiveLinePlot(pen="green")
157+
widget.addItem(plot)
158+
layout.addWidget(widget, row=3, col=0)
159+
args3.append(DataConnector(plot, max_points=300, update_rate=100))
160+
161+
# ---
162+
'''
163+
Move view to the right on every 300 ticks (data update).
164+
Y range is automatically adjudicating every tick according to y_range_limit set.
165+
'''
166+
widget = LivePlotWidget(title=f"Rolling X view range, auto Y range @ 100Hz @ Range Limit -1.0, 1.0",
167+
y_range_controller=LiveAxisRange(y_range_limit=[-1.0, 1.0]))
168+
plot = LiveLinePlot(pen="green")
169+
widget.addItem(plot)
170+
layout.addWidget(widget, row=3, col=1)
171+
args3.append(DataConnector(plot, max_points=300, update_rate=100))
172+
173+
# ---
174+
'''
175+
Move view to the right on every 300 ticks (data update).
176+
Y range is automatically adjudicating every tick according to y_min_range_width and y_range_limit set.
177+
178+
'''
179+
widget = LivePlotWidget(title=f"Rolling X view range, auto Y range @ 100Hz @ Min Range Width 3.0 @ Range Limit -1.0, 3.0",
180+
y_range_controller=LiveAxisRange(y_min_range_width=3.0, y_range_limit=[-1.0, 3.0]))
181+
plot = LiveLinePlot(pen="green")
182+
widget.addItem(plot)
183+
layout.addWidget(widget, row=3, col=2)
184+
args3.append(DataConnector(plot, max_points=300, update_rate=100))
149185

150186
layout.show()
151187
Thread(target=examples.sin_wave_generator, args=args).start()
152188
Thread(target=examples.sin_wave_generator, args=args2, kwargs={"flip": True}).start()
189+
Thread(target=examples.sin_wave_add_square_wave_generator, args=args3).start()
153190
examples.app.exec()
154191
examples.stop()

pglive/examples_pyside6/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ def stop():
1818
running = False
1919
app.exit(0)
2020

21+
def sin_wave_add_square_wave_generator(*data_connectors, flip=False):
22+
"""Sine wave + Square Wave generator"""
23+
x = 0
24+
while running:
25+
x += 1
26+
square_wave_amplitude = 0 if x // 750 % 2 == 0 else 5
27+
for data_connector in data_connectors:
28+
if flip:
29+
data_connector.cb_append_data_point(x, sin(x * 0.025) + square_wave_amplitude)
30+
else:
31+
data_connector.cb_append_data_point(sin(x * 0.025) + square_wave_amplitude, x)
32+
sleep(0.01)
2133

2234
def sin_wave_generator(*data_connectors, flip=False):
2335
"""Sine wave generator"""

pglive/examples_pyside6/live_plot_range.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
layout.layout.setSpacing(0)
1919
args = []
2020
args2 = []
21+
args3 = []
2122

2223
'''
2324
Move view to the right on every 300 ticks (data update).
@@ -146,10 +147,46 @@
146147
layout.addWidget(time_axis_plot_widget, row=2, col=2)
147148

148149
# ---
150+
'''
151+
Move view to the right on every 300 ticks (data update).
152+
Y range is automatically adjudicating every tick according to y_min_range_width set.
153+
'''
154+
widget = LivePlotWidget(title=f"Rolling X view range, auto Y range @ 100Hz @ Min Range Width 3.0",
155+
y_range_controller=LiveAxisRange(y_min_range_width=3.0))
156+
plot = LiveLinePlot(pen="green")
157+
widget.addItem(plot)
158+
layout.addWidget(widget, row=3, col=0)
159+
args3.append(DataConnector(plot, max_points=300, update_rate=100))
160+
161+
# ---
162+
'''
163+
Move view to the right on every 300 ticks (data update).
164+
Y range is automatically adjudicating every tick according to y_range_limit set.
165+
'''
166+
widget = LivePlotWidget(title=f"Rolling X view range, auto Y range @ 100Hz @ Range Limit -1.0, 1.0",
167+
y_range_controller=LiveAxisRange(y_range_limit=[-1.0, 1.0]))
168+
plot = LiveLinePlot(pen="green")
169+
widget.addItem(plot)
170+
layout.addWidget(widget, row=3, col=1)
171+
args3.append(DataConnector(plot, max_points=300, update_rate=100))
172+
173+
# ---
174+
'''
175+
Move view to the right on every 300 ticks (data update).
176+
Y range is automatically adjudicating every tick according to y_min_range_width and y_range_limit set.
177+
178+
'''
179+
widget = LivePlotWidget(title=f"Rolling X view range, auto Y range @ 100Hz @ Min Range Width 3.0 @ Range Limit -1.0, 3.0",
180+
y_range_controller=LiveAxisRange(y_min_range_width=3.0, y_range_limit=[-1.0, 3.0]))
181+
plot = LiveLinePlot(pen="green")
182+
widget.addItem(plot)
183+
layout.addWidget(widget, row=3, col=2)
184+
args3.append(DataConnector(plot, max_points=300, update_rate=100))
149185

150186
layout.show()
151187
Thread(target=examples.sin_wave_generator, args=args).start()
152188
Thread(target=examples.sin_wave_generator, args=args2, kwargs={"flip": True}).start()
189+
Thread(target=examples.sin_wave_add_square_wave_generator, args=args3).start()
153190

154191
signal.signal(signal.SIGINT, lambda sig, frame: examples.stop())
155192
examples.app.exec()

pglive/sources/live_axis_range.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ def __init__(
1313
offset_top: float = 0.0,
1414
offset_bottom: float = 0.0,
1515
fixed_range: Optional[List[float]] = None,
16+
x_min_range_width: Optional[float] = None,
17+
x_range_limit: Optional[List[float]] = None,
18+
y_min_range_width: Optional[float] = None,
19+
y_range_limit: Optional[List[float]] = None,
1620
) -> None:
1721
self.roll_on_tick = roll_on_tick
1822
self.offset_left = offset_left
@@ -24,6 +28,10 @@ def __init__(
2428
self.crop_top_offset_to_data = False
2529
self.crop_bottom_offset_to_data = False
2630
self.fixed_range = fixed_range
31+
self.x_min_range_width = x_min_range_width
32+
self.x_range_limit = x_range_limit
33+
self.y_min_range_width = y_min_range_width
34+
self.y_range_limit = y_range_limit
2735
self.x_range: Dict[str, List[float]] = {}
2836
self.y_range: Dict[str, List[float]] = {}
2937
self.final_x_range = [0.0, 0.0]
@@ -65,11 +73,12 @@ def get_x_range(self, data_connector, tick: int) -> List[float]:
6573
final_range[0] = x_range[0]
6674
if final_range[1] < x_range[1]:
6775
final_range[1] = x_range[1]
68-
if final_range[0] == final_range[1]:
76+
if final_range[0] == final_range[1] and self.x_min_range_width is not None:
6977
# Pyqtgraph ViewBox.setRange doesn't like same value for min and max,
7078
# therefore in that case we must set some range
7179
final_range[0] -= 0.4
7280
final_range[1] += 0.4
81+
final_range = self._update_range_width(self.x_range_limit, self.x_min_range_width, final_range)
7382
if self.final_x_range != final_range:
7483
self.final_x_range = final_range
7584
return self.final_x_range
@@ -88,11 +97,12 @@ def recalculate_x_range(self):
8897
final_range[1] = x_range[1]
8998
if final_range is None:
9099
final_range = [0, 0]
91-
if final_range[0] == final_range[1]:
100+
if final_range[0] == final_range[1] and self.x_min_range_width is not None:
92101
# Pyqtgraph ViewBox.setRange doesn't like same value for min and max,
93102
# therefore in that case we must set some range
94103
final_range[0] -= 0.4
95104
final_range[1] += 0.4
105+
final_range = self._update_range_width(self.x_range_limit, self.x_min_range_width, final_range)
96106
if self.final_x_range != final_range:
97107
self.final_x_range = final_range
98108
return self.final_x_range
@@ -132,11 +142,12 @@ def get_y_range(self, data_connector, tick: int) -> List[float]:
132142
final_range[0] = y_range[0]
133143
if final_range[1] < y_range[1]:
134144
final_range[1] = y_range[1]
135-
if final_range[0] == final_range[1]:
145+
if final_range[0] == final_range[1] and self.y_min_range_width is None:
136146
# Pyqtgraph ViewBox.setRange doesn't like same value for min and max,
137147
# therefore in that case we must set some range
138148
final_range[0] -= 0.4
139149
final_range[1] += 0.4
150+
final_range = self._update_range_width(self.y_range_limit, self.y_min_range_width, final_range)
140151
if self.final_y_range != final_range:
141152
self.final_y_range = final_range
142153
return self.final_y_range
@@ -155,11 +166,12 @@ def recalculate_y_range(self):
155166
final_range[1] = y_range[1]
156167
if final_range is None:
157168
final_range = [0, 0]
158-
if final_range[0] == final_range[1]:
169+
if final_range[0] == final_range[1] and self.y_min_range_width is None:
159170
# Pyqtgraph ViewBox.setRange doesn't like same value for min and max,
160171
# therefore in that case we must set some range
161172
final_range[0] -= 0.4
162173
final_range[1] += 0.4
174+
final_range = self._update_range_width(self.y_range_limit, self.y_min_range_width, final_range)
163175
if self.final_y_range != final_range:
164176
self.final_y_range = final_range
165177
return self.final_y_range
@@ -199,6 +211,25 @@ def _get_range(
199211
else:
200212
return None
201213

214+
def _update_range_width(self, bound, range_width, final_range):
215+
if range_width is not None:
216+
if abs(final_range[0] - final_range[1]) < range_width:
217+
center_pt = (final_range[0] + final_range[1]) / 2
218+
final_range[0] = center_pt - range_width / 2
219+
final_range[1] = center_pt + range_width / 2
220+
221+
if bound is not None:
222+
final_range_width = abs(final_range[0] - final_range[1])
223+
if (bound[0] > final_range[1] and bound[0] > final_range[0]) or \
224+
(bound[1] < final_range[1] and bound[0] < final_range[0]) or \
225+
(bound[0] > final_range[0] and bound[1] < final_range[1]):
226+
final_range = bound
227+
elif bound[1] > final_range[0] and final_range[0] > bound[0]:
228+
final_range = [max(bound[1] - final_range_width, bound[0]), bound[1]]
229+
elif bound[0] < final_range[1] and final_range[1] < bound[1]:
230+
final_range = [bound[0], min(bound[0] + final_range_width, bound[1])]
231+
return final_range
232+
202233
def ignore_connector(self, data_connector, flag: bool) -> None:
203234
if not flag:
204235
self.ignored_data_connectors.append(data_connector.__hash__())

0 commit comments

Comments
 (0)