gui: basic plotting

This commit is contained in:
Sebastien Bourdeauducq 2015-07-16 20:52:53 +02:00
parent a83473a19a
commit 9649e1837a
3 changed files with 185 additions and 8 deletions

View File

@ -3,12 +3,12 @@
import argparse import argparse
import asyncio import asyncio
import atexit import atexit
import os
# Quamash must be imported first so that pyqtgraph picks up the Qt binding # Quamash must be imported first so that pyqtgraph picks up the Qt binding
# it has chosen. # it has chosen.
from quamash import QEventLoop, QtGui from quamash import QEventLoop, QtGui
from pyqtgraph import dockarea from pyqtgraph import dockarea
import os
from artiq.protocols.file_db import FlatFileDB from artiq.protocols.file_db import FlatFileDB
from artiq.protocols.pc_rpc import AsyncioClient from artiq.protocols.pc_rpc import AsyncioClient
@ -71,7 +71,7 @@ def main():
args.server, args.port_notify)) args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_explorer.sub_close())) atexit.register(lambda: loop.run_until_complete(d_explorer.sub_close()))
d_results = ResultsDock() d_results = ResultsDock(win, area)
loop.run_until_complete(d_results.sub_connect( loop.run_until_complete(d_results.sub_connect(
args.server, args.port_notify)) args.server, args.port_notify))
atexit.register(lambda: loop.run_until_complete(d_results.sub_close())) atexit.register(lambda: loop.run_until_complete(d_results.sub_close()))

130
artiq/gui/displays.py Normal file
View File

@ -0,0 +1,130 @@
from collections import OrderedDict
from quamash import QtGui
import pyqtgraph as pg
from pyqtgraph import dockarea
class _SimpleSettings(QtGui.QDialog):
def __init__(self, parent, prev_name, prev_settings,
result_list, create_cb):
QtGui.QDialog.__init__(self, parent=parent)
self.setWindowTitle(self._window_title)
grid = QtGui.QGridLayout()
self.setLayout(grid)
grid.addWidget(QtGui.QLabel("Name:"), 0, 0)
self.name = name = QtGui.QLineEdit()
grid.addWidget(name, 0, 1)
if prev_name is not None:
name.insert(prev_name)
grid.addWidget(QtGui.QLabel("Result:"))
self.result = result = QtGui.QComboBox()
grid.addWidget(result, 1, 1)
result.addItems(result_list)
result.setEditable(True)
if "result" in prev_settings:
result.setEditText(prev_settings["result"])
buttons = QtGui.QDialogButtonBox(
QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
grid.addWidget(buttons, 2, 0, 1, 2)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
def on_accept():
create_cb(name.text(), {"result": result.currentText()})
self.accepted.connect(on_accept)
def accept(self):
if self.name.text() and self.result.currentText():
QtGui.QDialog.accept(self)
class NumberDisplaySettings(_SimpleSettings):
_window_title = "Number display"
class NumberDisplay(dockarea.Dock):
def __init__(self, name, settings):
dockarea.Dock.__init__(self, "Display: " + name, size=(250, 250),
closable=True)
self.settings = settings
self.number = QtGui.QLCDNumber()
self.number.setDigitCount(10)
self.addWidget(self.number)
def data_sources(self):
return {self.settings["result"]}
def update_data(self, data):
result = self.settings["result"]
try:
n = float(data[result])
except:
n = 0.0
self.number.display(n)
class XYDisplaySettings(_SimpleSettings):
_window_title = "XY plot"
class XYDisplay(dockarea.Dock):
def __init__(self, name, settings):
dockarea.Dock.__init__(self, "XY: " + name, size=(640, 480),
closable=True)
self.settings = settings
self.plot = pg.PlotWidget()
self.addWidget(self.plot)
def data_sources(self):
return {self.settings["result"]}
def update_data(self, data):
result = self.settings["result"]
try:
y = data[result]
except KeyError:
return
self.plot.clear()
if not y:
return
self.plot.plot(y)
class HistogramDisplaySettings(_SimpleSettings):
_window_title = "Histogram"
class HistogramDisplay(dockarea.Dock):
def __init__(self, name, settings):
dockarea.Dock.__init__(self, "Histogram: " + name, size=(640, 480),
closable=True)
self.settings = settings
self.plot = pg.PlotWidget()
self.addWidget(self.plot)
def data_sources(self):
return {self.settings["result"]}
def update_data(self, data):
result = self.settings["result"]
try:
y = data[result]
except KeyError:
return
x = list(range(len(y)+1))
self.plot.clear()
if not y:
return
self.plot.plot(x, y, stepMode=True, fillLevel=0, brush=(0, 0, 255, 150))
display_types = OrderedDict([
("Number", (NumberDisplaySettings, NumberDisplay)),
("XY", (XYDisplaySettings, XYDisplay)),
("Histogram", (HistogramDisplaySettings, HistogramDisplay))
])

View File

@ -1,4 +1,6 @@
import asyncio import asyncio
from collections import OrderedDict
from functools import partial
from quamash import QtGui, QtCore from quamash import QtGui, QtCore
from pyqtgraph import dockarea from pyqtgraph import dockarea
@ -6,6 +8,7 @@ from pyqtgraph import LayoutWidget
from artiq.protocols.sync_struct import Subscriber from artiq.protocols.sync_struct import Subscriber
from artiq.gui.tools import DictSyncModel from artiq.gui.tools import DictSyncModel
from artiq.gui.displays import *
def _fmt_type(v): def _fmt_type(v):
@ -34,8 +37,10 @@ class ResultsModel(DictSyncModel):
class ResultsDock(dockarea.Dock): class ResultsDock(dockarea.Dock):
def __init__(self): def __init__(self, dialog_parent, dock_area):
dockarea.Dock.__init__(self, "Results", size=(1500, 500)) dockarea.Dock.__init__(self, "Results", size=(1500, 500))
self.dialog_parent = dialog_parent
self.dock_area = dock_area
grid = LayoutWidget() grid = LayoutWidget()
self.addWidget(grid) self.addWidget(grid)
@ -49,13 +54,17 @@ class ResultsDock(dockarea.Dock):
display_grid = QtGui.QGridLayout() display_grid = QtGui.QGridLayout()
add_display_box.setLayout(display_grid) add_display_box.setLayout(display_grid)
for n, name in enumerate(["Number", "XY", "Histogram"]): for n, name in enumerate(display_types.keys()):
btn = QtGui.QPushButton(name) btn = QtGui.QPushButton(name)
display_grid.addWidget(btn, n, 0) display_grid.addWidget(btn, n, 0)
btn.clicked.connect(partial(self.create_dialog, name))
self.displays = dict()
@asyncio.coroutine @asyncio.coroutine
def sub_connect(self, host, port): def sub_connect(self, host, port):
self.subscriber = Subscriber("rt_results", self.init_results_model) self.subscriber = Subscriber("rt_results", self.init_results_model,
self.on_mod)
yield from self.subscriber.connect(host, port) yield from self.subscriber.connect(host, port)
@asyncio.coroutine @asyncio.coroutine
@ -63,6 +72,44 @@ class ResultsDock(dockarea.Dock):
yield from self.subscriber.close() yield from self.subscriber.close()
def init_results_model(self, init): def init_results_model(self, init):
table_model = ResultsModel(self.table, init) self.table_model = ResultsModel(self.table, init)
self.table.setModel(table_model) self.table.setModel(self.table_model)
return table_model return self.table_model
def on_mod(self, mod):
if mod["action"] == "init":
for display in self.displays.values():
display.update_data(self.table_model.backing_store)
return
if mod["action"] == "setitem":
source = mod["key"]
elif mod["path"]:
source = mod["path"][0]
else:
return
for display in self.displays.values():
if source in display.data_sources():
display.update_data(self.table_model.backing_store)
def create_dialog(self, ty):
dlg_class = display_types[ty][0]
dlg = dlg_class(self.dialog_parent, None, dict(),
sorted(self.table_model.backing_store.keys()),
partial(self.create_display, ty, None))
dlg.open()
def create_display(self, ty, prev_name, name, settings):
if prev_name is not None and prev_name in self.displays:
raise NotImplementedError
dsp_class = display_types[ty][1]
dsp = dsp_class(name, settings)
self.displays[name] = dsp
dsp.update_data(self.table_model.backing_store)
def on_close():
del self.displays[name]
dsp.sigClosed.connect(on_close)
self.dock_area.addDock(dsp)
self.dock_area.floatDock(dsp)