better-rtplot 0.4.10__tar.gz → 0.4.11__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 (36) hide show
  1. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/PKG-INFO +1 -1
  2. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/pyproject.toml +1 -1
  3. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/client.py +30 -9
  4. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/server_browser.py +42 -1
  5. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/index.html +41 -1
  6. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/LICENSE +0 -0
  7. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/README.md +0 -0
  8. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/interactive_test.py +0 -0
  9. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/saved_plots/.gitignore +0 -0
  10. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/server.py +0 -0
  11. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/server_browser_gui.py +0 -0
  12. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/auto-render.min.js +0 -0
  13. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  14. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  15. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  16. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  17. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  18. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  19. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  20. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  21. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  22. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  23. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  24. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  25. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  26. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  27. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  28. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  29. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  30. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  31. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  32. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  33. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/katex.min.css +0 -0
  34. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/katex/katex.min.js +0 -0
  35. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/uPlot.iife.min.js +0 -0
  36. {better_rtplot-0.4.10 → better_rtplot-0.4.11}/rtplot/static/uPlot.min.css +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: better-rtplot
3
- Version: 0.4.10
3
+ Version: 0.4.11
4
4
  Summary:
5
5
  License: GPL V3.0
6
6
  Author: jmontp
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "better-rtplot"
3
- version = "0.4.10"
3
+ version = "0.4.11"
4
4
  description = ""
5
5
  authors = ["jmontp <jmontp@umich.edu>"]
6
6
  license = "GPL V3.0"
@@ -22,7 +22,7 @@ socket = context.socket(zmq.PUB)
22
22
  control_socket = context.socket(zmq.PULL)
23
23
  control_socket.setsockopt(zmq.RCVHWM, 1000)
24
24
  #Non-blocking local state for controls, drained on each poll_controls() call
25
- _control_slider_values = {}
25
+ _control_values = {}
26
26
  _control_button_events = []
27
27
 
28
28
  # Cached payload of the most recent initialize_plots() call. The server
@@ -247,10 +247,27 @@ class Text:
247
247
  })
248
248
 
249
249
 
250
+ @dataclass
251
+ class TextInput:
252
+ id: str
253
+ label: str
254
+ value: str = ""
255
+ placeholder: Optional[str] = None
256
+ max_length: Optional[int] = None
257
+ height: Optional[float] = None
258
+
259
+ def to_dict(self):
260
+ return _drop_none({
261
+ "type": "text_input", "id": self.id, "label": self.label,
262
+ "value": self.value, "placeholder": self.placeholder,
263
+ "max_length": self.max_length, "height": self.height,
264
+ })
265
+
266
+
250
267
  @dataclass
251
268
  class ControlsRow:
252
269
  """A row of control widgets, rendered in place of a plot."""
253
- controls: List[Union[Button, Slider, Dial, Display, Text, dict]] = field(default_factory=list)
270
+ controls: List[Union[Button, Slider, Dial, Display, Text, TextInput, dict]] = field(default_factory=list)
254
271
 
255
272
  def to_dict(self):
256
273
  return {"controls": [
@@ -536,7 +553,9 @@ def _send_initialize_with_handshake(cfg, timeout):
536
553
  elif evtype == "button":
537
554
  _control_button_events.append(event.get("id"))
538
555
  elif evtype == "slider":
539
- _control_slider_values[event.get("id")] = float(event.get("value", 0.0))
556
+ _control_values[event.get("id")] = float(event.get("value", 0.0))
557
+ elif evtype == "text":
558
+ _control_values[event.get("id")] = str(event.get("value", ""))
540
559
  # Unknown / resend_config: ignore during handshake.
541
560
  # Resend if the window elapsed without an ack; PUB/SUB will
542
561
  # drop the first send during slow-join but retries land.
@@ -575,8 +594,8 @@ def poll_controls():
575
594
  """Drain the return channel non-blocking and return current control state.
576
595
 
577
596
  Returns a ControlState(values, buttons) where:
578
- - values: dict of {slider_id: float} with the latest value of every
579
- slider the server has told us about since process start.
597
+ - values: dict of latest control values keyed by id. Sliders/dials
598
+ remain floats; text_input controls remain strings.
580
599
  - buttons: list of button ids that fired since the previous poll
581
600
  (cleared after this call).
582
601
 
@@ -587,7 +606,7 @@ def poll_controls():
587
606
  Reconnect button on a tab), it asks us to re-publish the cached
588
607
  initialize_plots() payload so the plot rewires without the user
589
608
  restarting their script. The request is consumed silently — the
590
- returned ControlState only ever contains slider/button events.
609
+ returned ControlState only ever contains control/button events.
591
610
  """
592
611
  global _control_button_events
593
612
  while True:
@@ -601,7 +620,9 @@ def poll_controls():
601
620
  if evtype == "button":
602
621
  _control_button_events.append(event.get("id"))
603
622
  elif evtype == "slider":
604
- _control_slider_values[event.get("id")] = float(event.get("value", 0.0))
623
+ _control_values[event.get("id")] = float(event.get("value", 0.0))
624
+ elif evtype == "text":
625
+ _control_values[event.get("id")] = str(event.get("value", ""))
605
626
  elif evtype == "resend_config":
606
627
  if plot_desc_dict is not None:
607
628
  socket.send_string(SENDING_PLOT_UPDATE)
@@ -615,7 +636,7 @@ def poll_controls():
615
636
 
616
637
  buttons = _control_button_events
617
638
  _control_button_events = []
618
- return ControlState(values=dict(_control_slider_values), buttons=buttons)
639
+ return ControlState(values=dict(_control_values), buttons=buttons)
619
640
 
620
641
 
621
642
  def save_snapshot(path, server_url=None, animate=False, timeout=5.0):
@@ -664,4 +685,4 @@ def save_snapshot(path, server_url=None, animate=False, timeout=5.0):
664
685
  abs_path = _os.path.abspath(path)
665
686
  with open(abs_path, "wb") as fh:
666
687
  fh.write(body)
667
- return abs_path
688
+ return abs_path
@@ -375,6 +375,7 @@ class Tab:
375
375
  layout: list = field(default_factory=list)
376
376
  control_rows: list = field(default_factory=list)
377
377
  slider_values: dict = field(default_factory=dict)
378
+ text_values: dict = field(default_factory=dict)
378
379
  display_values: dict = field(default_factory=dict)
379
380
  display_dirty: set = field(default_factory=set)
380
381
  fps: float = 0.0
@@ -499,6 +500,7 @@ def parse_config(tab: Tab, json_config):
499
500
  trace_info = []
500
501
  control_rows = []
501
502
  slider_values = {}
503
+ text_values = {}
502
504
  layout = []
503
505
  num_datapoints_in_plot = DEFAULT_NUM_DATAPOINTS_IN_PLOT
504
506
 
@@ -514,6 +516,8 @@ def parse_config(tab: Tab, json_config):
514
516
  for element in row:
515
517
  if element.get("type") in ("slider", "dial") and "value" in element:
516
518
  slider_values[element["id"]] = float(element["value"])
519
+ elif element.get("type") == "text_input":
520
+ text_values[element["id"]] = str(element.get("value", ""))
517
521
  layout.append({"kind": "controls", "index": len(control_rows)})
518
522
  control_rows.append(row)
519
523
  continue
@@ -546,6 +550,17 @@ def parse_config(tab: Tab, json_config):
546
550
  }
547
551
  tab.display_dirty = {d for d in tab.display_dirty if d in active_display_ids}
548
552
  tab.slider_values = slider_values
553
+ active_text_ids = {
554
+ el["id"]
555
+ for row in control_rows
556
+ for el in row
557
+ if el.get("type") == "text_input"
558
+ }
559
+ tab.text_values = {
560
+ text_id: tab.text_values.get(text_id, default_value)
561
+ for text_id, default_value in text_values.items()
562
+ if text_id in active_text_ids
563
+ }
549
564
  tab.layout = layout
550
565
 
551
566
  num_traces = sum(traces_per_plot)
@@ -585,11 +600,20 @@ def build_config_message(tab: Tab, config_dict) -> dict:
585
600
  "controls": tab.control_rows,
586
601
  "layout": tab.layout,
587
602
  "slider_values": dict(tab.slider_values),
603
+ "text_values": dict(tab.text_values),
588
604
  "display_values": dict(tab.display_values),
589
605
  "row_layout": bool(NEW_SUBPLOT_IN_ROW),
590
606
  }
591
607
 
592
608
 
609
+ def refresh_config_message(tab: Tab):
610
+ """Rebuild the browser config payload using the tab's latest control state."""
611
+ if tab.config_dict is None:
612
+ tab.config_message = None
613
+ else:
614
+ tab.config_message = build_config_message(tab, tab.config_dict)
615
+
616
+
593
617
  ###############################
594
618
  # Binary payload construction #
595
619
  ###############################
@@ -952,7 +976,7 @@ async def zmq_receiver(tab: Tab):
952
976
 
953
977
  tab.last_config_error = None # clear: this one was good
954
978
  tab.config_dict = cfg
955
- tab.config_message = build_config_message(tab, cfg)
979
+ refresh_config_message(tab)
956
980
  tab.initialized = True
957
981
  fps = None
958
982
  tab.fps = 0.0
@@ -976,6 +1000,10 @@ async def zmq_receiver(tab: Tab):
976
1000
  await send_control_event(
977
1001
  tab, {"type": "slider", "id": sid, "value": svalue}
978
1002
  )
1003
+ for text_id, text_value in tab.text_values.items():
1004
+ await send_control_event(
1005
+ tab, {"type": "text", "id": text_id, "value": text_value}
1006
+ )
979
1007
 
980
1008
  elif category == RECEIVED_DATA:
981
1009
  arr = await _recv_array_async(sock)
@@ -1342,6 +1370,7 @@ async def handle_ws(request):
1342
1370
  },
1343
1371
  )
1344
1372
  if t.config_message is not None:
1373
+ refresh_config_message(t)
1345
1374
  await ws_send_text(ws, t.config_message)
1346
1375
  if t.display_values:
1347
1376
  await ws_send_text(
@@ -1402,9 +1431,21 @@ async def handle_ws(request):
1402
1431
  t = tabs.get(tid)
1403
1432
  if sid and t is not None:
1404
1433
  t.slider_values[sid] = value
1434
+ refresh_config_message(t)
1405
1435
  await send_control_event(
1406
1436
  t, {"type": "slider", "id": sid, "value": value}
1407
1437
  )
1438
+ elif ptype == "control_text":
1439
+ text_id = payload.get("id")
1440
+ tid = ws_tab.get(ws, BIND_ME_ID)
1441
+ t = tabs.get(tid)
1442
+ if text_id and t is not None:
1443
+ value = str(payload.get("value", ""))
1444
+ t.text_values[text_id] = value
1445
+ refresh_config_message(t)
1446
+ await send_control_event(
1447
+ t, {"type": "text", "id": text_id, "value": value}
1448
+ )
1408
1449
  elif msg.type == WSMsgType.ERROR:
1409
1450
  break
1410
1451
  finally:
@@ -95,6 +95,8 @@
95
95
  .ctrl-numinput { width: 72px; font-family: monospace; font-size: calc(13px * var(--ui-scale)); padding: 4px 6px; border: 1px solid #b8b8b8; border-radius: 3px; background: #fff; color: #222; text-align: right; -moz-appearance: textfield; }
96
96
  .ctrl-numinput::-webkit-outer-spin-button,
97
97
  .ctrl-numinput::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
98
+ .ctrl-textinput { min-width: 200px; width: min(320px, 100%); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: calc(13px * var(--ui-scale)); padding: 7px 10px; border: 1px solid #b8b8b8; border-radius: 3px; background: #fff; color: #222; text-align: left; }
99
+ .ctrl-textinput:focus { outline: none; border-color: #2a5db0; box-shadow: 0 0 0 2px rgba(42, 93, 176, 0.14); }
98
100
  .ctrl-nudgebtn { width: 26px; height: 26px; font-size: calc(15px * var(--ui-scale)); font-weight: 600; line-height: 1; padding: 0; border: 1px solid #b8b8b8; background: #f7f7f7; color: #333; cursor: pointer; border-radius: 3px; }
99
101
  .ctrl-nudgebtn:hover { background: #e9e9e9; }
100
102
  .ctrl-nudgebtn:active { background: #dcdcdc; }
@@ -415,7 +417,7 @@
415
417
  let socket = null;
416
418
  let pendingFrame = false;
417
419
  let lastStatus = { fps: 0, statusByte: 0, nonPlot: 0, peers: 0, dirty: true };
418
- const controlElements = { displays: {}, displayFormats: {}, displayKinds: {}, sliders: {} };
420
+ const controlElements = { displays: {}, displayFormats: {}, displayKinds: {}, sliders: {}, textInputs: {} };
419
421
 
420
422
  function sendCtrl(msg) {
421
423
  if (socket && socket.readyState === WebSocket.OPEN) {
@@ -684,6 +686,34 @@
684
686
  item.appendChild(val);
685
687
  controlElements.displays[el.id] = val;
686
688
  controlElements.displayKinds[el.id] = 'text';
689
+ } else if (el.type === 'text_input') {
690
+ item.classList.add('ctrl-text-input', 'flex');
691
+ if (el.label) {
692
+ const lbl = document.createElement('label');
693
+ lbl.textContent = el.label;
694
+ item.appendChild(lbl);
695
+ }
696
+ const input = document.createElement('input');
697
+ input.type = 'text';
698
+ input.className = 'ctrl-textinput';
699
+ input.value = (el.value !== undefined && el.value !== null) ? String(el.value) : '';
700
+ if (el.placeholder !== undefined && el.placeholder !== null) {
701
+ input.placeholder = String(el.placeholder);
702
+ }
703
+ if (el.max_length !== undefined && Number.isFinite(Number(el.max_length))) {
704
+ input.maxLength = Number(el.max_length);
705
+ }
706
+ const commit = () => sendCtrl({ type: 'control_text', id: el.id, value: input.value });
707
+ input.addEventListener('change', commit);
708
+ input.addEventListener('blur', commit);
709
+ input.addEventListener('keydown', (e) => {
710
+ if (e.key === 'Enter') {
711
+ e.preventDefault();
712
+ commit();
713
+ }
714
+ });
715
+ item.appendChild(input);
716
+ controlElements.textInputs[el.id] = input;
687
717
  }
688
718
  return item;
689
719
  }
@@ -722,6 +752,14 @@
722
752
  }
723
753
  }
724
754
 
755
+ function applyTextValues(values) {
756
+ if (!values) return;
757
+ for (const [id, val] of Object.entries(values)) {
758
+ const input = controlElements.textInputs[id];
759
+ if (input) input.value = (val !== undefined && val !== null) ? String(val) : '';
760
+ }
761
+ }
762
+
725
763
  function applyDisplayValues(values) {
726
764
  if (!values) return;
727
765
  for (const [id, val] of Object.entries(values)) {
@@ -752,6 +790,7 @@
752
790
  controlElements.displayFormats = {};
753
791
  controlElements.displayKinds = {};
754
792
  controlElements.sliders = {};
793
+ controlElements.textInputs = {};
755
794
  }
756
795
 
757
796
  function showEmptyState(text) {
@@ -859,6 +898,7 @@
859
898
  });
860
899
  totalTraces = traceOffset;
861
900
  applySliderValues(cfg.slider_values);
901
+ applyTextValues(cfg.text_values);
862
902
  applyDisplayValues(cfg.display_values);
863
903
  // Render LaTeX in everything we just built: uPlot's title,
864
904
  // x/y axis labels, and legend (all rendered as HTML), plus the
File without changes
File without changes