def create_frame(self, parent, size=(750, 450), **kwds): self.parent = parent kwds['style'] = wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL kwds['size'] = size wx.Frame.__init__(self, parent, -1, 'Epics PV Strip Chart', **kwds) self.build_statusbar() self.plotpanel = PlotPanel(self, trace_color_callback=self.onTraceColor) self.plotpanel.BuildPanel() self.plotpanel.messenger = self.write_message self.build_pvpanel() self.build_btnpanel() self.build_menus() self.SetBackgroundColour(wx.Colour(*BGCOL)) mainsizer = wx.BoxSizer(wx.VERTICAL) p1 = wx.Panel(self) p1.SetBackgroundColour(wx.Colour(*BGCOL)) s1 = wx.BoxSizer(wx.HORIZONTAL) n = LabelEntry(p1, '', labeltext=' Add PV: ', size=300, action=self.onPVname) self.pvmsg = SimpleText(p1, ' ', minsize=(75, -1), style=LSTY | wx.EXPAND) s1.Add(n.label, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER, 10) s1.Add(n, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER, 10) s1.Add(self.pvmsg, 1, wx.ALIGN_LEFT | wx.ALIGN_CENTER, 10) p1.SetAutoLayout(True) p1.SetSizer(s1) s1.Fit(p1) mainsizer.Add(p1, 0, wx.GROW | wx.EXPAND, 5) mainsizer.Add( wx.StaticLine(self, size=(250, -1), style=wx.LI_HORIZONTAL), 0, wx.EXPAND | wx.GROW, 8) mainsizer.Add(self.pvpanel, 0, wx.EXPAND, 5) mainsizer.Add( wx.StaticLine(self, size=(250, -1), style=wx.LI_HORIZONTAL), 0, wx.EXPAND | wx.GROW, 8) mainsizer.Add(self.btnpanel, 0, wx.EXPAND, 5) mainsizer.Add(self.plotpanel, 1, wx.EXPAND, 5) self.SetAutoLayout(True) self.SetSizer(mainsizer) self.Fit() try: self.SetIcon(wx.Icon(ICON_FILE, wx.BITMAP_TYPE_ICO)) except: pass self.Refresh()
def create_frame(self, parent, size=(750, 450), **kwds): self.parent = parent kwds['style'] = wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL kwds['size'] = size wx.Frame.__init__(self, parent, -1, 'Epics MCA Display', **kwds) self.build_statusbar() self.plotpanel = PlotPanel(self, trace_color_callback=self.onTraceColor, axissize=[0.08, 0.06, 0.91, 0.92]) self.plotpanel.messenger = self.write_message self.build_menus() self.SetBackgroundColour(wx.Colour(*BGCOL)) mainsizer = wx.BoxSizer(wx.VERTICAL) mainsizer.Add(self.plotpanel, 1, wx.EXPAND, 5) self.SetAutoLayout(True) self.SetSizer(mainsizer) self.Fit() try: self.SetIcon(wx.Icon(ICON_FILE, wx.BITMAP_TYPE_ICO)) except: pass self.Refresh()
def create_frame(self, parent, size=(750, 450), **kwds): self.parent = parent kwds['style'] = wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL kwds['size'] = size wx.Frame.__init__(self, parent, -1, 'Epics MCA Display', **kwds) self.build_statusbar() self.plotpanel = PlotPanel(self, trace_color_callback=self.onTraceColor) self.plotpanel.messenger = self.write_message self.build_menus() self.SetBackgroundColour(wx.Colour(*BGCOL)) mainsizer = wx.BoxSizer(wx.VERTICAL) mainsizer.Add(self.plotpanel, 1, wx.EXPAND, 5) self.SetAutoLayout(True) self.SetSizer(mainsizer) self.Fit() try: self.SetIcon(wx.Icon(ICON_FILE, wx.BITMAP_TYPE_ICO)) except: pass self.Refresh()
def create_frame(self, parent, size=(750, 450), **kwds): self.parent = parent kwds['style'] = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL kwds['size'] = size wx.Frame.__init__(self, parent, -1, 'Epics PV Strip Chart', **kwds) self.build_statusbar() self.plotpanel = PlotPanel(self, trace_color_callback=self.onTraceColor) self.plotpanel.BuildPanel() self.plotpanel.messenger = self.write_message self.build_pvpanel() self.build_btnpanel() self.build_menus() self.SetBackgroundColour(wx.Colour(*BGCOL)) mainsizer = wx.BoxSizer(wx.VERTICAL) p1 = wx.Panel(self) p1.SetBackgroundColour(wx.Colour(*BGCOL)) s1 = wx.BoxSizer(wx.HORIZONTAL) n = LabelEntry(p1, '', labeltext=' Add PV: ', size=300, action=self.onPVname) self.pvmsg = SimpleText(p1, ' ', minsize=(75, -1), style=LSTY|wx.EXPAND) s1.Add(n.label, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER, 10) s1.Add(n, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER, 10) s1.Add(self.pvmsg, 1, wx.ALIGN_LEFT|wx.ALIGN_CENTER, 10) p1.SetAutoLayout(True) p1.SetSizer(s1) s1.Fit(p1) mainsizer.Add(p1, 0, wx.GROW|wx.EXPAND, 5) mainsizer.Add(wx.StaticLine(self, size=(250, -1), style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.GROW, 8) mainsizer.Add(self.pvpanel, 0, wx.EXPAND, 5) mainsizer.Add(wx.StaticLine(self, size=(250, -1), style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.GROW, 8) mainsizer.Add(self.btnpanel, 0, wx.EXPAND, 5) mainsizer.Add(self.plotpanel, 1, wx.EXPAND, 5) self.SetAutoLayout(True) self.SetSizer(mainsizer) self.Fit() try: self.SetIcon(wx.Icon(ICON_FILE, wx.BITMAP_TYPE_ICO)) except: pass self.Refresh()
class StripChart(wx.Frame): default_colors = ((0, 0, 0), (0, 0, 255), (255, 0, 0), (0, 0, 0), (255, 0, 255), (0, 125, 0)) help_msg = """Quick help: Left-Click: to display X,Y coordinates Left-Drag: to zoom in on plot region Right-Click: display popup menu with choices: Zoom out 1 level Zoom all the way out -------------------- Configure Save Image Also, these key bindings can be used (For Mac OSX, replace 'Ctrl' with 'Apple'): Ctrl-S: save plot image to file Ctrl-C: copy plot image to clipboard Ctrl-K: Configure Plot Ctrl-Q: quit """ about_msg = """Epics PV Strip Chart version 0.1 Matt Newville <*****@*****.**> """ def __init__(self, parent=None): self.pvdata = {} self.pvlist = [' -- '] self.pvwids = [None] self.pvchoices = [None] self.colorsels = [] self.plots_drawn = [False] * 10 self.needs_refresh = False self.needs_refresh = False self.paused = False self.tmin = -60.0 self.timelabel = 'seconds' self.create_frame(parent) self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.onUpdatePlot, self.timer) self.timer.Start(POLLTIME) def create_frame(self, parent, size=(750, 450), **kwds): self.parent = parent kwds['style'] = wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL kwds['size'] = size wx.Frame.__init__(self, parent, -1, 'Epics PV Strip Chart', **kwds) self.build_statusbar() self.plotpanel = PlotPanel(self, trace_color_callback=self.onTraceColor) self.plotpanel.BuildPanel() self.plotpanel.messenger = self.write_message self.build_pvpanel() self.build_btnpanel() self.build_menus() self.SetBackgroundColour(wx.Colour(*BGCOL)) mainsizer = wx.BoxSizer(wx.VERTICAL) p1 = wx.Panel(self) p1.SetBackgroundColour(wx.Colour(*BGCOL)) s1 = wx.BoxSizer(wx.HORIZONTAL) n = LabelEntry(p1, '', labeltext=' Add PV: ', size=300, action=self.onPVname) self.pvmsg = SimpleText(p1, ' ', minsize=(75, -1), style=LSTY | wx.EXPAND) s1.Add(n.label, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER, 10) s1.Add(n, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER, 10) s1.Add(self.pvmsg, 1, wx.ALIGN_LEFT | wx.ALIGN_CENTER, 10) p1.SetAutoLayout(True) p1.SetSizer(s1) s1.Fit(p1) mainsizer.Add(p1, 0, wx.GROW | wx.EXPAND, 5) mainsizer.Add( wx.StaticLine(self, size=(250, -1), style=wx.LI_HORIZONTAL), 0, wx.EXPAND | wx.GROW, 8) mainsizer.Add(self.pvpanel, 0, wx.EXPAND, 5) mainsizer.Add( wx.StaticLine(self, size=(250, -1), style=wx.LI_HORIZONTAL), 0, wx.EXPAND | wx.GROW, 8) mainsizer.Add(self.btnpanel, 0, wx.EXPAND, 5) mainsizer.Add(self.plotpanel, 1, wx.EXPAND, 5) self.SetAutoLayout(True) self.SetSizer(mainsizer) self.Fit() try: self.SetIcon(wx.Icon(ICON_FILE, wx.BITMAP_TYPE_ICO)) except: pass self.Refresh() def build_statusbar(self): sbar = self.CreateStatusBar(2, wx.CAPTION | wx.THICK_FRAME) sfont = sbar.GetFont() sfont.SetWeight(wx.BOLD) sfont.SetPointSize(10) sbar.SetFont(sfont) self.SetStatusWidths([-5, -2]) self.SetStatusText('', 0) def build_pvpanel(self): panel = self.pvpanel = wx.Panel(self) panel.SetBackgroundColour(wx.Colour(*BGCOL)) sizer = self.pvsizer = wx.GridBagSizer(4, 5) name = SimpleText(panel, ' PV: ', minsize=(75, -1), style=LSTY) colr = SimpleText(panel, ' Color ', minsize=(50, -1), style=LSTY) logs = SimpleText(panel, ' Log Scale?', minsize=(85, -1), style=LSTY) ymin = SimpleText(panel, ' Y Minimum ', minsize=(85, -1), style=LSTY) ymax = SimpleText(panel, ' Y Maximum ', minsize=(85, -1), style=LSTY) sizer.Add(name, (0, 0), (1, 1), LSTY | wx.EXPAND, 2) sizer.Add(colr, (0, 1), (1, 1), LSTY, 1) sizer.Add(logs, (0, 2), (1, 1), LSTY, 1) sizer.Add(ymin, (0, 3), (1, 1), LSTY, 1) sizer.Add(ymax, (0, 4), (1, 1), LSTY, 1) self.npv_rows = 0 for i in range(4): self.AddPV_row() panel.SetAutoLayout(True) panel.SetSizer(sizer) sizer.Fit(panel) def build_btnpanel(self): panel = self.btnpanel = wx.Panel(self, ) panel.SetBackgroundColour(wx.Colour(*BGCOL)) btnsizer = wx.BoxSizer(wx.HORIZONTAL) self.pause_btn = wx.Button(panel, label='Pause', size=(100, 30)) self.resume_btn = wx.Button(panel, label='Resume', size=(100, 30)) self.resume_btn.Disable() self.pause_btn.Bind(wx.EVT_BUTTON, self.onPause) self.resume_btn.Bind(wx.EVT_BUTTON, self.onPause) time_label = SimpleText(panel, ' Time Range: ', minsize=(85, -1), style=LSTY) self.time_choice = MyChoice(panel, size=(120, -1), choices=('seconds', 'minutes', 'hours')) self.time_choice.SetStringSelection(self.timelabel) self.time_choice.Bind(wx.EVT_CHOICE, self.onTimeChoice) self.time_ctrl = FloatCtrl(panel, value=-self.tmin, precision=2, size=(90, -1), action=self.onTimeVal) btnsizer.Add(self.pause_btn, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER, 2) btnsizer.Add(self.resume_btn, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER, 2) btnsizer.Add(time_label, 1, wx.ALIGN_CENTER_HORIZONTAL, 2) btnsizer.Add(self.time_ctrl, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER, 2) btnsizer.Add(self.time_choice, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER, 2) panel.SetAutoLayout(True) panel.SetSizer(btnsizer) btnsizer.Fit(panel) def build_menus(self): mbar = wx.MenuBar() mfile = wx.Menu() mfile.Append(MENU_SAVE_DAT, "&Save Data\tCtrl+S", "Save PNG Image of Plot") mfile.Append(MENU_SAVE_IMG, "Save Plot Image\t", "Save PNG Image of Plot") mfile.Append(MENU_CLIPB, "&Copy Image to Clipboard\tCtrl+C", "Copy Plot Image to Clipboard") mfile.AppendSeparator() mfile.Append(MENU_PSETUP, 'Page Setup...', 'Printer Setup') mfile.Append(MENU_PREVIEW, 'Print Preview...', 'Print Preview') mfile.Append(MENU_PRINT, "&Print\tCtrl+P", "Print Plot") mfile.AppendSeparator() mfile.Append(MENU_EXIT, "E&xit\tCtrl+Q", "Exit the 2D Plot Window") mopt = wx.Menu() mopt.Append(MENU_CONFIG, "Configure Plot\tCtrl+K", "Configure Plot styles, colors, labels, etc") mopt.AppendSeparator() mopt.Append(MENU_UNZOOM, "Zoom Out\tCtrl+Z", "Zoom out to full data range") mhelp = wx.Menu() mhelp.Append(MENU_HELP, "Quick Reference", "Quick Reference for MPlot") mhelp.Append(MENU_ABOUT, "About", "About MPlot") mbar.Append(mfile, "File") mbar.Append(mopt, "Options") mbar.Append(mhelp, "&Help") self.SetMenuBar(mbar) self.Bind(wx.EVT_MENU, self.onSaveData, id=MENU_SAVE_DAT) self.Bind(wx.EVT_MENU, self.onHelp, id=MENU_HELP) self.Bind(wx.EVT_MENU, self.onAbout, id=MENU_ABOUT) self.Bind(wx.EVT_MENU, self.onExit, id=MENU_EXIT) self.Bind(wx.EVT_CLOSE, self.onExit) pp = self.plotpanel self.Bind(wx.EVT_MENU, pp.configure, id=MENU_CONFIG) self.Bind(wx.EVT_MENU, pp.unzoom_all, id=MENU_UNZOOM) self.Bind(wx.EVT_MENU, pp.save_figure, id=MENU_SAVE_IMG) self.Bind(wx.EVT_MENU, pp.Print, id=MENU_PRINT) self.Bind(wx.EVT_MENU, pp.PrintSetup, id=MENU_PSETUP) self.Bind(wx.EVT_MENU, pp.PrintPreview, id=MENU_PREVIEW) self.Bind(wx.EVT_MENU, pp.canvas.Copy_to_Clipboard, id=MENU_CLIPB) def AddPV_row(self): i = self.npv_rows = self.npv_rows + 1 panel = self.pvpanel sizer = self.pvsizer pvchoice = MyChoice(panel, choices=self.pvlist, size=(200, -1)) pvchoice.SetSelection(0) logs = MyChoice(panel) logs.SetSelection(0) ymin = wx.TextCtrl(panel, -1, '', size=(75, -1)) ymax = wx.TextCtrl(panel, -1, '', size=(75, -1)) if i > 2: logs.Disable() ymin.Disable() ymax.Disable() colval = (0, 0, 0) if i < len(self.default_colors): colval = self.default_colors[i] colr = csel.ColourSelect(panel, -1, '', colval) self.colorsels.append(colr) sizer.Add(pvchoice, (i, 0), (1, 1), LSTY, 3) sizer.Add(colr, (i, 1), (1, 1), CSTY, 3) sizer.Add(logs, (i, 2), (1, 1), CSTY, 3) sizer.Add(ymin, (i, 3), (1, 1), CSTY, 3) sizer.Add(ymax, (i, 4), (1, 1), CSTY, 3) pvchoice.Bind(wx.EVT_CHOICE, Closure(self.onPVchoice, row=i)) colr.Bind(csel.EVT_COLOURSELECT, Closure(self.onPVcolor, row=i)) logs.Bind(wx.EVT_CHOICE, self.onPVwid) ymin.Bind(wx.EVT_TEXT_ENTER, self.onPVwid) ymax.Bind(wx.EVT_TEXT_ENTER, self.onPVwid) self.pvchoices.append(pvchoice) self.pvwids.append((logs, colr, ymin, ymax)) def onTraceColor(self, trace, color, **kws): irow = self.get_current_traces()[trace][0] - 1 self.colorsels[irow].SetColour(color) def onPVshow(self, event=None, row=0): if not event.IsChecked(): trace = self.plotpanel.conf.get_mpl_line(row) trace.set_data([], []) self.plotpanel.canvas.draw() self.needs_refresh = True def onPVname(self, event=None): try: name = event.GetString() except AttributeError: return self.addPV(name) @EpicsFunction def addPV(self, name): if name is not None and name not in self.pvlist: pv = PV(str(name), callback=self.onPVChange) pv.get() conn = False if pv is not None: if not pv.connected: pv.wait_for_connection() conn = pv.connected msg = 'PV not found: %s' % name if conn: msg = 'PV found: %s' % name self.pvmsg.SetLabel(msg) if not conn: return self.pvlist.append(name) self.pvdata[name] = [(time.time(), pv.get())] i_new = len(self.pvdata) new_shown = False for choice in self.pvchoices: if choice is None: continue cur = choice.GetSelection() choice.Clear() choice.SetItems(self.pvlist) choice.SetSelection(cur) if cur == 0 and not new_shown: choice.SetSelection(i_new) new_shown = True self.needs_refresh = True @DelayedEpicsCallback def onPVChange(self, pvname=None, value=None, timestamp=None, **kw): if timestamp is None: timestamp = time.time() self.pvdata[pvname].append((timestamp, value)) self.needs_refresh = True def onPVchoice(self, event=None, row=None, **kws): self.needs_refresh = True for i in range(len(self.pvlist) + 1): try: trace = self.plotpanel.conf.get_mpl_line(row - 1) trace.set_data([], []) except: pass if row == 1: self.plotpanel.set_y2label('') self.plotpanel.canvas.draw() def onPVcolor(self, event=None, row=None, **kws): self.plotpanel.conf.set_trace_color(hexcolor(event.GetValue()), trace=row - 1) self.needs_refresh = True def onPVwid(self, event=None, row=None, **kws): self.needs_refresh = True def onTimeVal(self, event=None, value=None, **kws): new = -abs(value) if abs(new) < 0.1: new = -0.1 if abs(new - self.tmin) > 1.e-3 * max(new, self.tmin): self.tmin = new self.needs_refresh = True def onTimeChoice(self, event=None, **kws): newval = event.GetString() denom, num = 1.0, 1.0 if self.timelabel != newval: if self.timelabel == 'hours': denom = 3600. elif self.timelabel == 'minutes': denom = 60.0 if newval == 'hours': num = 3600. elif newval == 'minutes': num = 60.0 self.timelabel = newval timeval = self.time_ctrl.GetValue() self.time_ctrl.SetValue(timeval * denom / num) self.plotpanel.set_xlabel('Elapsed Time (%s)' % self.timelabel) self.needs_refresh = True def onPause(self, event=None): if self.paused: self.pause_btn.Enable() self.resume_btn.Disable() else: self.pause_btn.Disable() self.resume_btn.Enable() self.paused = not self.paused def write_message(self, s, panel=0): """write a message to the Status Bar""" self.SetStatusText(s, panel) def onSaveData(self, event=None): dlg = wx.FileDialog(self, message='Save Data to File...', defaultDir=os.getcwd(), defaultFile='PVStripChart.dat', style=wx.SAVE | wx.CHANGE_DIR) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() self.SaveDataFiles(path) self.write_message('Saved data to %s' % path) dlg.Destroy() def SaveDataFiles(self, path): basename, ext = os.path.splitext(path) if len(ext) < 2: ext = '.dat' if ext.startswith('.'): ext = ext[1:] for pvname, data in self.pvdata.items(): tnow = time.time() tmin = data[0][0] fname = [] for s in pvname: if s not in FILECHARS: s = '_' fname.append(s) fname = os.path.join("%s_%s.%s" % (basename, ''.join(fname), ext)) buff = ["# Epics PV Strip Chart Data for PV: %s " % pvname] buff.append("# Current Time = %s " % time.ctime(tnow)) buff.append("# Earliest Time = %s " % time.ctime(tmin)) buff.append("#------------------------------") buff.append("# Timestamp Value Time-Current_Time(s)") for tx, yval in data: buff.append(" %.3f %16g %.3f" % (tx, yval, tx - tnow)) fout = open(fname, 'w') fout.write("\n".join(buff)) fout.close() #dat = tnow, func(tnow) def onAbout(self, event=None): dlg = wx.MessageDialog(self, self.about_msg, "About Epics PV Strip Chart", wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() def onHelp(self, event=None): dlg = wx.MessageDialog(self, self.help_msg, "Epics PV Strip Chart Help", wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() def onExit(self, event=None): try: self.plotpanel.win_config.Close(True) self.plotpanel.win_config.Destroy() except: pass self.Destroy() def get_current_traces(self): "return list of current traces" traces = [] # to be shown for irow, s in enumerate(self.pvchoices): if s is not None: ix = s.GetSelection() if ix > 0: name = self.pvlist[ix] logs = 1 == self.pvwids[irow][0].GetSelection() color = self.pvwids[irow][1].GetColour() ymin = get_bound(self.pvwids[irow][2].GetValue()) ymax = get_bound(self.pvwids[irow][3].GetValue()) traces.append((irow, name, logs, color, ymin, ymax)) return traces def onUpdatePlot(self, event=None): if self.paused or not self.needs_refresh: return tnow = time.time() # set timescale sec/min/hour timescale = 1.0 if self.time_choice.GetSelection() == 1: timescale = 1. / 60 elif self.time_choice.GetSelection() == 2: timescale = 1. / 3600 ylabelset, y2labelset = False, False xlabel = 'Elapsed Time (%s)' % self.timelabel itrace = -1 update_failed = False hasplot = False span1 = (1, 0) did_update = False left_axes = self.plotpanel.axes right_axes = self.plotpanel.get_right_axes() for irow, pname, uselog, color, ymin, ymax in self.get_current_traces( ): if pname not in self.pvdata: continue itrace += 1 if len(self.plots_drawn) < itrace: self.plots_drawn.extend([False] * 3) side = 'left' if itrace == 1: side = 'right' data = self.pvdata[pname][:] if len(data) < 2: update_failed = True continue tdat = timescale * (array([i[0] for i in data]) - tnow) mask = where(tdat > self.tmin) if (len(mask[0]) < 2 or ((abs(min(tdat)) / abs(1 - self.tmin)) > 0.1)): data.append((time.time(), data[0][-1])) tdat = timescale * (array([i[0] for i in data]) - tnow) mask = where(tdat > self.tmin) i0 = mask[0][0] if i0 > 0: i0 = i0 - 1 i1 = mask[0][-1] + 1 tdat = timescale * (array([i[0] for i in data[i0:i1]]) - tnow) ydat = array([i[1] for i in data[i0:i1]]) if len(ydat) < 2: update_failed = True continue if ymin is None: ymin = min(ydat) if ymax is None: ymax = max(ydat) # for more that 2 plots, scale to left hand axis if itrace == 0: span1 = (ymax - ymin, ymin) if span1[0] * ymax < 1.e-6: update_failed = True continue elif itrace > 1: yr = abs(ymax - ymin) if yr > 1.e-9: ydat = span1[1] + 0.99 * (ydat - ymin) * span1[0] / yr ymin, ymax = min(ydat), max(ydat) if self.needs_refresh: if itrace == 0: self.plotpanel.set_ylabel(pname) elif itrace == 1: self.plotpanel.set_y2label(pname) if not self.plots_drawn[itrace]: plot = self.plotpanel.oplot if itrace == 0: plot = self.plotpanel.plot try: plot(tdat, ydat, drawstyle='steps-post', side=side, ylog_scale=uselog, color=color, xmin=self.tmin, xmax=0, xlabel=xlabel, label=pname, autoscale=False) self.plots_drawn[itrace] = True except: update_failed = True else: try: self.plotpanel.update_line(itrace, tdat, ydat, draw=False) self.plotpanel.set_xylims((self.tmin, 0, ymin, ymax), side=side, autoscale=False) did_update = True except: update_failed = True axes = left_axes if itrace == 1: axes = right_axes if uselog and min(ydat) > 0: axes.set_yscale('log', basey=10) else: axes.set_yscale('linear') self.plotpanel.set_title( time.strftime("%Y-%b-%d %H:%M:%S", time.localtime())) if did_update: self.plotpanel.canvas.draw() self.needs_refresh = update_failed return
class MCADisplay(wx.Frame): help_msg = """Quick help: Left-Click: to display X,Y coordinates Left-Drag: to zoom in on plot region Right-Click: display popup menu with choices: Zoom out 1 level Zoom all the way out -------------------- Configure Save Image Also, these key bindings can be used (For Mac OSX, replace 'Ctrl' with 'Apple'): Ctrl-S: save plot image to file Ctrl-C: copy plot image to clipboard Ctrl-K: Configure Plot Ctrl-Q: quit """ about_msg = """Epics MCA Display version 1.0 PLS XAFS Beamline.> """ def __init__(self, parent=None, *args, **kwargs): self.dxpPrefix = 'dxpXMAP:' self.NofDet = 13 # string list [mca0, mca1, mca2, ..., mca12] self.mcaList = ['mca%i' % (i+1) for i in range(self.NofDet)] # text name: dxpXMAP:mca1, dxpXMAP:mca2, ...., is not PV self.mcaNameList = ['%s%s' % (self.dxpPrefix, self.mcaList[i]) for i in range(self.NofDet)] self.dxpAcquiringPV = None self.mcaPV = None self.parent = None self.plotpanel = None self.needs_refresh = False self.paused = False # make np.array type memory for each MCA self.mcaWave = {} for name in self.mcaList: self.mcaWave[name] = np.ones(2048) self.mca_en = None self.mca_data_sum = 0 self.create_frame(parent) self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.onUpdatePlot, self.timer) self.timer.Start(POLLTIME) self.init_epics() @EpicsFunction def init_epics(self): # make PV object. self.dxpAcquiringPV = PV(self.dxpPrefix + 'Acquiring', callback=self.onMCADataCALLBACK) poll() self.mcaPV = [PV(self.mcaNameList[i]) for i in range(self.NofDet)] def create_frame(self, parent, size=(750, 450), **kwds): self.parent = parent kwds['style'] = wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL kwds['size'] = size wx.Frame.__init__(self, parent, -1, 'Epics MCA Display', **kwds) self.build_statusbar() self.plotpanel = PlotPanel(self, trace_color_callback=self.onTraceColor, axissize=[0.08, 0.06, 0.91, 0.92]) self.plotpanel.messenger = self.write_message self.build_menus() self.SetBackgroundColour(wx.Colour(*BGCOL)) mainsizer = wx.BoxSizer(wx.VERTICAL) mainsizer.Add(self.plotpanel, 1, wx.EXPAND, 5) self.SetAutoLayout(True) self.SetSizer(mainsizer) self.Fit() try: self.SetIcon(wx.Icon(ICON_FILE, wx.BITMAP_TYPE_ICO)) except: pass self.Refresh() def build_statusbar(self): sbar = self.CreateStatusBar(2, wx.CAPTION | wx.THICK_FRAME) sfont = sbar.GetFont() sfont.SetWeight(wx.BOLD) sfont.SetPointSize(10) sbar.SetFont(sfont) self.SetStatusWidths([-5, -2]) self.SetStatusText('', 0) def write_message(self, s, panel=0): """write a message to the Status Bar""" self.SetStatusText(s, panel) def build_menus(self): mbar = wx.MenuBar() mfile = wx.Menu() mfile.Append(MENU_SAVE_DAT, "&Save Data\tCtrl+S", "Save PNG Image of Plot") mfile.Append(MENU_SAVE_IMG, "Save Plot Image\t", "Save PNG Image of Plot") mfile.Append(MENU_CLIPB, "&Copy Image to Clipboard\tCtrl+C", "Copy Plot Image to Clipboard") mfile.AppendSeparator() mfile.Append(MENU_PSETUP, 'Page Setup...', 'Printer Setup') mfile.Append(MENU_PREVIEW, 'Print Preview...', 'Print Preview') mfile.Append(MENU_PRINT, "&Print\tCtrl+P", "Print Plot") mfile.AppendSeparator() mfile.Append(MENU_EXIT, "E&xit\tCtrl+Q", "Exit the 2D Plot Window") mopt = wx.Menu() mopt.Append(MENU_CONFIG, "Configure Plot\tCtrl+K", "Configure Plot styles, colors, labels, etc") mopt.AppendSeparator() mopt.Append(MENU_UNZOOM, "Zoom Out\tCtrl+Z", "Zoom out to full data range") mhelp = wx.Menu() mhelp.Append(MENU_HELP, "Quick Reference", "Quick Reference for MPlot") mhelp.Append(MENU_ABOUT, "About", "About MPlot") mbar.Append(mfile, "File") mbar.Append(mopt, "Options") mbar.Append(mhelp, "&Help") self.SetMenuBar(mbar) self.Bind(wx.EVT_MENU, self.onHelp, id=MENU_HELP) self.Bind(wx.EVT_MENU, self.onAbout, id=MENU_ABOUT) self.Bind(wx.EVT_MENU, self.onExit, id=MENU_EXIT) self.Bind(wx.EVT_CLOSE, self.onExit) pp = self.plotpanel self.Bind(wx.EVT_MENU, pp.configure, id=MENU_CONFIG) self.Bind(wx.EVT_MENU, pp.unzoom_all, id=MENU_UNZOOM) self.Bind(wx.EVT_MENU, pp.save_figure, id=MENU_SAVE_IMG) self.Bind(wx.EVT_MENU, pp.Print, id=MENU_PRINT) self.Bind(wx.EVT_MENU, pp.PrintSetup, id=MENU_PSETUP) self.Bind(wx.EVT_MENU, pp.PrintPreview, id=MENU_PREVIEW) self.Bind(wx.EVT_MENU, pp.canvas.Copy_to_Clipboard, id=MENU_CLIPB) def onTraceColor(self, trace, color, **kws): irow = self.get_current_traces()[trace][0] - 1 self.colorsels[irow].SetColour(color) @DelayedEpicsCallback def onMCADataCALLBACK(self, pvname=None, value=None, timestamp=None, **kw): if value is 1: return # XMAP Acquiring!!! if timestamp is None: timestamp = time.time() print ' Acquiring Complete : %s' % (time.ctime()) self.needs_refresh = False for detNo in range(self.NofDet): #print 'detNo=%i, pvName:%s, mcaName:%s' % (detNo, self.mcaPV[detNo].pvname, # self.mcaList[detNo]) self.mcaWave[self.mcaList[detNo]] = self.mcaPV[detNo].get() self.needs_refresh = True def onAbout(self, event=None): dlg = wx.MessageDialog(self, self.about_msg, "About Epics MCA Display", wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() def onHelp(self, event=None): dlg = wx.MessageDialog(self, self.help_msg, "Epics MCA Display Help", wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() def onExit(self, event=None): try: self.plotpanel.win_config.Close(True) self.plotpanel.win_config.Destroy() except: pass self.Destroy() def get_current_traces(self): """return list of current traces""" traces = [] # to be shown for irow, s in enumerate(self.pvchoices): if s is not None: ix = s.GetSelection() if ix > 0: name = self.pvlist[ix] logs = 1 == self.pvwids[irow][0].GetSelection() color = self.pvwids[irow][1].GetColour() ymin = get_bound(self.pvwids[irow][2].GetValue()) ymax = get_bound(self.pvwids[irow][3].GetValue()) traces.append((irow, name, logs, color, ymin, ymax)) return traces def onUpdatePlot(self, event=None): if self.paused or not self.needs_refresh: return # just return if plot has time over(>PLOTTER time) # Todo: check need or not needs_refresh ?. if (len(self.mcaWave[self.mcaList[0]]) < 1 or self.mcaWave[self.mcaList[0]].sum() is self.mca_data_sum): #Todo: need or Not next Line. self.needs_refresh = False return self.mca_data_sum = self.mcaWave[self.mcaList[self.NofDet - 1]].sum() ppanel = self.plotpanel did_update = False if self.mca_en is None: # first plot! self.mca_en = np.arange(len(self.mcaWave[self.mcaList[0]])) ppanel.plot(self.mca_en*1.0, 1.0*self.mcaWave[self.mcaList[0]], xmax=self.mca_en.max(), ylog_scale=False, label=self.mcaList[0], autoscale=True, linewidth=0.5) for i in range(self.NofDet - 1): ppanel.oplot(self.mca_en*1.0, 1.0*self.mcaWave[self.mcaList[i+1]], xmax=self.mca_en.max(), ylog_scale=False, label=self.mcaList[i+1], autoscale=True, linewidth=0.5) did_update = True else: try: for i in range(self.NofDet): ppanel.update_line(i, self.mca_en, self.mcaWave[self.mcaList[i]]) # , # draw=False, # update_limits=False) did_update = True except Exception as e: print 'MCA DISPLAY HAVE EXCEPTION!!!\n %s\n' % e.message pass if did_update: ymin = [] ymax = [] for i in range(self.NofDet): ymin.append(min(self.mcaWave[self.mcaList[i]])) ymax.append(max(self.mcaWave[self.mcaList[i]])) self.plotpanel.set_xylims((self.mca_en[0], self.mca_en[-1:], min(ymin), max(ymax)*1.1)) self.plotpanel.canvas.draw() self.needs_refresh = not did_update print ' PLOT Complete : %s\n' % (time.ctime()) return
class StripChart(wx.Frame): default_colors = ((0, 0, 0), (0, 0, 255), (255, 0, 0), (0, 0, 0), (255, 0, 255), (0, 125, 0)) help_msg = """Quick help: Left-Click: to display X,Y coordinates Left-Drag: to zoom in on plot region Right-Click: display popup menu with choices: Zoom out 1 level Zoom all the way out -------------------- Configure Save Image Also, these key bindings can be used (For Mac OSX, replace 'Ctrl' with 'Apple'): Ctrl-S: save plot image to file Ctrl-C: copy plot image to clipboard Ctrl-K: Configure Plot Ctrl-Q: quit """ about_msg = """Epics PV Strip Chart version 0.1 Matt Newville <*****@*****.**> """ def __init__(self, parent=None): self.pvdata = {} self.pvlist = [' -- '] self.pvwids = [None] self.pvchoices = [None] self.pvlabels = [None] self.pv_desc = {} self.colorsels = [] self.plots_drawn = [False]*10 self.needs_refresh = False self.paused = False self.tmin = -60.0 self.timelabel = 'seconds' self.create_frame(parent) self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.onUpdatePlot, self.timer) self.timer.Start(POLLTIME) def create_frame(self, parent, size=(750, 450), **kwds): self.parent = parent kwds['style'] = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL kwds['size'] = size wx.Frame.__init__(self, parent, -1, 'Epics PV Strip Chart', **kwds) self.build_statusbar() self.plotpanel = PlotPanel(self, trace_color_callback=self.onTraceColor) self.plotpanel.messenger = self.write_message self.build_pvpanel() self.build_btnpanel() self.build_menus() self.SetBackgroundColour(wx.Colour(*BGCOL)) mainsizer = wx.BoxSizer(wx.VERTICAL) p1 = wx.Panel(self) p1.SetBackgroundColour(wx.Colour(*BGCOL)) s1 = wx.BoxSizer(wx.HORIZONTAL) n = LabelEntry(p1, '', labeltext=' Add PV: ', size=300, action=self.onPVname) self.pvmsg = SimpleText(p1, ' ', minsize=(75, -1), style=LSTY|wx.EXPAND) s1.Add(n.label, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER, 10) s1.Add(n, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER, 10) s1.Add(self.pvmsg, 1, wx.ALIGN_LEFT|wx.ALIGN_CENTER, 10) p1.SetAutoLayout(True) p1.SetSizer(s1) s1.Fit(p1) mainsizer.Add(p1, 0, wx.GROW|wx.EXPAND, 5) mainsizer.Add(wx.StaticLine(self, size=(250, -1), style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.GROW, 8) mainsizer.Add(self.pvpanel, 0, wx.EXPAND, 5) mainsizer.Add(wx.StaticLine(self, size=(250, -1), style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.GROW, 8) mainsizer.Add(self.btnpanel, 0, wx.EXPAND, 5) mainsizer.Add(self.plotpanel, 1, wx.EXPAND, 5) self.SetAutoLayout(True) self.SetSizer(mainsizer) self.Fit() try: self.SetIcon(wx.Icon(ICON_FILE, wx.BITMAP_TYPE_ICO)) except: pass self.Refresh() def build_statusbar(self): sbar = self.CreateStatusBar(2, wx.CAPTION|wx.THICK_FRAME) sfont = sbar.GetFont() sfont.SetWeight(wx.BOLD) sfont.SetPointSize(10) sbar.SetFont(sfont) self.SetStatusWidths([-5, -2]) self.SetStatusText('', 0) def build_pvpanel(self): panel = self.pvpanel = wx.Panel(self) panel.SetBackgroundColour(wx.Colour(*BGCOL)) sizer = self.pvsizer = wx.GridBagSizer(4, 5) name = SimpleText(panel, ' PV: ', minsize=(65, -1), style=LSTY) colr = SimpleText(panel, ' Color ', minsize=(50, -1), style=LSTY) logs = SimpleText(panel, ' Log? ', minsize=(50, -1), style=LSTY) ymin = SimpleText(panel, ' Y Minimum ', minsize=(85, -1), style=LSTY) ymax = SimpleText(panel, ' Y Maximum ', minsize=(85, -1), style=LSTY) desc = SimpleText(panel, ' Label ', minsize=(85, -1), style=LSTY) side = SimpleText(panel, ' Side ', minsize=(85, -1), style=LSTY) sizer.Add(name, (0, 0), (1, 1), LSTY|wx.EXPAND, 2) sizer.Add(colr, (0, 1), (1, 1), LSTY, 1) sizer.Add(logs, (0, 2), (1, 1), LSTY, 1) sizer.Add(ymin, (0, 3), (1, 1), LSTY, 1) sizer.Add(ymax, (0, 4), (1, 1), LSTY, 1) sizer.Add(desc, (0, 5), (1, 1), LSTY, 1) sizer.Add(side, (0, 6), (1, 1), LSTY, 1) self.npv_rows = 0 for i in range(4): self.AddPV_row() panel.SetAutoLayout(True) panel.SetSizer(sizer) sizer.Fit(panel) def build_btnpanel(self): panel = self.btnpanel = wx.Panel(self, ) panel.SetBackgroundColour(wx.Colour(*BGCOL)) btnsizer = wx.BoxSizer(wx.HORIZONTAL) self.pause_btn = wx.Button(panel, label='Pause', size=(100, 30)) self.resume_btn = wx.Button(panel, label='Resume', size=(100, 30)) self.resume_btn.Disable() self.pause_btn.Bind(wx.EVT_BUTTON, self.onPause) self.resume_btn.Bind(wx.EVT_BUTTON, self.onPause) time_label = SimpleText(panel, ' Time Range: ', minsize=(85, -1), style=LSTY) self.time_choice = MyChoice(panel, size=(120, -1), choices=('seconds', 'minutes', 'hours')) self.time_choice.SetStringSelection(self.timelabel) self.time_choice.Bind(wx.EVT_CHOICE, self.onTimeChoice) self.time_ctrl = FloatCtrl(panel, value=-self.tmin, precision=2, size=(90, -1), action=self.onDisplayTimeVal) btnsizer.Add(self.pause_btn, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER, 2) btnsizer.Add(self.resume_btn, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER, 2) btnsizer.Add(time_label, 1, wx.ALIGN_CENTER_HORIZONTAL, 2) btnsizer.Add(self.time_ctrl, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER, 2) btnsizer.Add(self.time_choice, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER, 2) panel.SetAutoLayout(True) panel.SetSizer(btnsizer) btnsizer.Fit(panel) def build_menus(self): mbar = wx.MenuBar() mfile = wx.Menu() mfile.Append(MENU_SAVE_DAT, "&Save Data\tCtrl+S", "Save PNG Image of Plot") mfile.Append(MENU_SAVE_IMG, "Save Plot Image\t", "Save PNG Image of Plot") mfile.Append(MENU_CLIPB, "&Copy Image to Clipboard\tCtrl+C", "Copy Plot Image to Clipboard") mfile.AppendSeparator() mfile.Append(MENU_PSETUP, 'Page Setup...', 'Printer Setup') mfile.Append(MENU_PREVIEW, 'Print Preview...', 'Print Preview') mfile.Append(MENU_PRINT, "&Print\tCtrl+P", "Print Plot") mfile.AppendSeparator() mfile.Append(MENU_EXIT, "E&xit\tCtrl+Q", "Exit the 2D Plot Window") mopt = wx.Menu() mopt.Append(MENU_CONFIG, "Configure Plot\tCtrl+K", "Configure Plot styles, colors, labels, etc") mopt.AppendSeparator() mopt.Append(MENU_UNZOOM, "Zoom Out\tCtrl+Z", "Zoom out to full data range") mhelp = wx.Menu() mhelp.Append(MENU_HELP, "Quick Reference", "Quick Reference for MPlot") mhelp.Append(MENU_ABOUT, "About", "About MPlot") mbar.Append(mfile, "File") mbar.Append(mopt, "Options") mbar.Append(mhelp, "&Help") self.SetMenuBar(mbar) self.Bind(wx.EVT_MENU, self.onSaveData, id=MENU_SAVE_DAT) self.Bind(wx.EVT_MENU, self.onHelp, id=MENU_HELP) self.Bind(wx.EVT_MENU, self.onAbout, id=MENU_ABOUT) self.Bind(wx.EVT_MENU, self.onExit, id=MENU_EXIT) self.Bind(wx.EVT_CLOSE, self.onExit) pp = self.plotpanel self.Bind(wx.EVT_MENU, pp.configure, id=MENU_CONFIG) self.Bind(wx.EVT_MENU, pp.unzoom_all, id=MENU_UNZOOM) self.Bind(wx.EVT_MENU, pp.save_figure, id=MENU_SAVE_IMG) self.Bind(wx.EVT_MENU, pp.Print, id=MENU_PRINT) self.Bind(wx.EVT_MENU, pp.PrintSetup, id=MENU_PSETUP) self.Bind(wx.EVT_MENU, pp.PrintPreview, id=MENU_PREVIEW) self.Bind(wx.EVT_MENU, pp.canvas.Copy_to_Clipboard, id=MENU_CLIPB) def AddPV_row(self): i = self.npv_rows = self.npv_rows + 1 panel = self.pvpanel sizer = self.pvsizer pvchoice = MyChoice(panel, choices=self.pvlist, size=(150, -1)) pvchoice.SetSelection(0) logs = MyChoice(panel, size=(50, -1)) logs.SetSelection(0) ymin = wx.TextCtrl(panel, -1, '', size=(75, -1)) ymax = wx.TextCtrl(panel, -1, '', size=(75, -1)) desc = wx.TextCtrl(panel, -1, '', size=(150, -1)) side = MyChoice(panel, choices=('left', 'right'), size=(80, -1)) side.SetSelection((i-1)%2) if i > 2: logs.Disable() ymin.Disable() ymax.Disable() desc.Disable() side.Disable() colval = (0, 0, 0) if i < len(self.default_colors): colval = self.default_colors[i] colr = csel.ColourSelect(panel, -1, '', colval) self.colorsels.append(colr) sizer.Add(pvchoice, (i, 0), (1, 1), LSTY, 3) sizer.Add(colr, (i, 1), (1, 1), CSTY, 3) sizer.Add(logs, (i, 2), (1, 1), CSTY, 3) sizer.Add(ymin, (i, 3), (1, 1), CSTY, 3) sizer.Add(ymax, (i, 4), (1, 1), CSTY, 3) sizer.Add(desc, (i, 5), (1, 1), CSTY, 3) sizer.Add(side, (i, 6), (1, 1), CSTY, 3) pvchoice.Bind(wx.EVT_CHOICE, Closure(self.onPVchoice, row=i)) colr.Bind(csel.EVT_COLOURSELECT, Closure(self.onPVcolor, row=i)) logs.Bind(wx.EVT_CHOICE, self.onPVwid) ymin.Bind(wx.EVT_TEXT_ENTER, self.onPVwid) ymax.Bind(wx.EVT_TEXT_ENTER, self.onPVwid) self.pvchoices.append(pvchoice) self.pvlabels.append(desc) self.pvwids.append((logs, colr, ymin, ymax, desc, side)) def onTraceColor(self, trace, color, **kws): irow = self.get_current_traces()[trace][0] - 1 self.colorsels[irow].SetColour(color) def onPVshow(self, event=None, row=0): if not event.IsChecked(): trace = self.plotpanel.conf.get_mpl_line(row) trace.set_data([], []) self.plotpanel.canvas.draw() self.needs_refresh = True def onPVname(self, event=None): try: name = event.GetString() except AttributeError: return self.addPV(name) @EpicsFunction def addPV(self, name): if name is not None and name not in self.pvlist: basename = str(name) pv = get_pv(basename, callback=self.onPVChange) pv.get() conn = False if pv is not None: if not pv.connected: pv.wait_for_connection() conn = pv.connected msg = 'PV not found: %s' % name if conn: msg = 'PV found: %s' % name self.pvmsg.SetLabel(msg) if not conn: return self.pvlist.append(name) self.pvdata[name] = [(time.time(), pv.get())] if basename.endswith('.VAL'): basename = basename[:-4] descname = basename + '.DESC' descpv = get_pv(descname) desc = descpv.get(timeout=1.0) if desc is None or len(desc) < 1: desc = basename self.pv_desc[basename] = desc i_new = len(self.pvdata) new_shown = False for ix, choice in enumerate(self.pvchoices): if choice is None: continue cur = choice.GetSelection() choice.Clear() choice.SetItems(self.pvlist) choice.SetSelection(cur) if cur == 0 and not new_shown: choice.SetSelection(i_new) self.pvlabels[ix].SetValue(desc) new_shown = True self.needs_refresh = True @DelayedEpicsCallback def onPVChange(self, pvname=None, value=None, timestamp=None, **kw): if timestamp is None: timestamp = time.time() self.pvdata[pvname].append((timestamp, value)) self.needs_refresh = True def onPVchoice(self, event=None, row=None, **kws): self.needs_refresh = True pvname = self.pvchoices[row].GetStringSelection() if pvname in self.pv_desc: self.pvlabels[row].SetValue(self.pv_desc[pvname]) for i in range(len(self.pvlist)+1): try: trace = self.plotpanel.conf.get_mpl_line(row-1) trace.set_data([], []) except: pass if row == 1: self.plotpanel.set_y2label('') self.plotpanel.canvas.draw() def onPVcolor(self, event=None, row=None, **kws): self.plotpanel.conf.set_trace_color(hexcolor(event.GetValue()), trace=row-1) self.needs_refresh = True def onPVwid(self, event=None, row=None, **kws): self.needs_refresh = True def onDisplayTimeVal(self, event=None, value=None, **kws): new = -abs(value) if abs(new) < 0.1: new = -0.1 if abs(new - self.tmin) > 1.e-3*max(new, self.tmin): new = new self.tmin = new self.plotpanel.axes.set_xlim(self.tmin, 0) try: for axes in self.plotpanel.fig.get_axes(): self.plotpanel.user_limits[axes][0] = self.tmin self.plotpanel.user_limits[axes][1] = 0 except: pass self.needs_refresh = True def onTimeChoice(self, event=None, **kws): newval = event.GetString() denom, num = 1.0, 1.0 if self.timelabel != newval: if self.timelabel == 'hours': denom = 3600. elif self.timelabel == 'minutes': denom = 60.0 if newval == 'hours': num = 3600. elif newval == 'minutes': num = 60.0 self.timelabel = newval timeval = self.time_ctrl.GetValue() self.time_ctrl.SetValue(timeval * denom/num) self.plotpanel.set_xlabel('Elapsed Time (%s)' % self.timelabel) self.needs_refresh = True def onPause(self, event=None): if self.paused: self.pause_btn.Enable() self.resume_btn.Disable() else: self.pause_btn.Disable() self.resume_btn.Enable() self.paused = not self.paused def write_message(self, s, panel=0): """write a message to the Status Bar""" self.SetStatusText(s, panel) def onSaveData(self, event=None): dlg = wx.FileDialog(self, message='Save Data to File...', defaultDir = os.getcwd(), defaultFile='PVStripChart.dat', style=wx.SAVE|wx.CHANGE_DIR) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() self.SaveDataFiles(path) self.write_message('Saved data to %s' % path) dlg.Destroy() def SaveDataFiles(self, path): basename, ext = os.path.splitext(path) if len(ext) < 2: ext = '.dat' if ext.startswith('.'): ext = ext[1:] for pvname, data in self.pvdata.items(): tnow = time.time() tmin = data[0][0] fname = [] for s in pvname: if s not in FILECHARS: s = '_' fname.append(s) fname = os.path.join("%s_%s.%s" % (basename, ''.join(fname), ext)) buff = ["# Epics PV Strip Chart Data for PV: %s " % pvname] buff.append("# Current Time = %s " % time.ctime(tnow)) buff.append("# Earliest Time = %s " % time.ctime(tmin)) buff.append("#------------------------------") buff.append("# Timestamp Value Time-Current_Time(s)") for tx, yval in data: buff.append(" %.3f %16g %.3f" % (tx, yval, tx-tnow)) fout = open(fname, 'w') fout.write("\n".join(buff)) fout.close() #dat = tnow, func(tnow) def onAbout(self, event=None): dlg = wx.MessageDialog(self, self.about_msg, "About Epics PV Strip Chart", wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() def onHelp(self, event=None): dlg = wx.MessageDialog(self, self.help_msg, "Epics PV Strip Chart Help", wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() def onExit(self, event=None): try: self.plotpanel.win_config.Close(True) self.plotpanel.win_config.Destroy() except: pass self.Destroy() def get_current_traces(self): "return list of current traces" traces = [] # to be shown for irow, s in enumerate(self.pvchoices): if s is not None: ix = s.GetSelection() if ix > 0: name = self.pvlist[ix] logs = 1 == self.pvwids[irow][0].GetSelection() color = self.pvwids[irow][1].GetColour() ymin = get_bound(self.pvwids[irow][2].GetValue()) ymax = get_bound(self.pvwids[irow][3].GetValue()) desc = self.pvwids[irow][4].GetValue() side = self.pvwids[irow][5].GetSelection() traces.append((irow, name, logs, color, ymin, ymax, desc, side)) return traces def onUpdatePlot(self, event=None): if self.paused or not self.needs_refresh: return tnow = time.time() # set timescale sec/min/hour timescale = 1.0 if self.time_choice.GetSelection() == 1: timescale = 1./60 elif self.time_choice.GetSelection() == 2: timescale = 1./3600 ylabelset, y2labelset = False, False xlabel = 'Elapsed Time (%s)' % self.timelabel itrace = -1 update_failed = False hasplot = False span1 = (1, 0) did_update = False left_axes = self.plotpanel.axes right_axes = self.plotpanel.get_right_axes() for tracedata in self.get_current_traces(): irow, pname, uselog, color, ymin, ymax, desc, xside = tracedata if len(desc.strip() ) < 1: desc = pname if pname not in self.pvdata: continue itrace += 1 if len(self.plots_drawn) < itrace: self.plots_drawn.extend([False]*3) side = 'left' if xside == 1: side = 'right' data = self.pvdata[pname][:] if len(data) < 2: update_failed = True continue tdat = timescale * (array([i[0] for i in data]) - tnow) mask = where(tdat > self.tmin) if (len(mask[0]) < 2 or ((abs(min(tdat)) / abs(1 -self.tmin)) > 0.1)): data.append((time.time(), data[0][-1])) tdat = timescale*(array([i[0] for i in data]) - tnow) mask = where(tdat > self.tmin) i0 = mask[0][0] if i0 > 0: i0 = i0-1 i1 = mask[0][-1] + 1 tdat = timescale*(array([i[0] for i in data[i0:i1]]) - tnow) ydat = array([i[1] for i in data[i0:i1]]) if len(ydat) < 2: update_failed = True continue if ymin is None: ymin = min(ydat) if ymax is None: ymax = max(ydat) # for more that 2 plots, scale to left hand axis if itrace == 0: span1 = (ymax-ymin, ymin) if span1[0]*ymax < 1.e-6: update_failed = True continue elif itrace > 1: yr = abs(ymax-ymin) if yr > 1.e-9: ydat = span1[1] + 0.99*(ydat - ymin)*span1[0]/yr ymin, ymax = min(ydat), max(ydat) if self.needs_refresh: if itrace == 0: self.plotpanel.set_ylabel(desc) elif itrace == 1: self.plotpanel.set_y2label(desc) if not self.plots_drawn[itrace]: plot = self.plotpanel.oplot if itrace == 0: plot = self.plotpanel.plot try: plot(tdat, ydat, drawstyle='steps-post', side=side, ylog_scale=uselog, color=color, xmin=self.tmin, xmax=0, xlabel=xlabel, label=desc, autoscale=False) self.plots_drawn[itrace] = True except: update_failed = True else: try: self.plotpanel.update_line(itrace, tdat, ydat, draw=False, update_limits=True) # self.plotpanel.plot(tdat, ydat, draw=False, update_limits=True) # self.plotpanel.set_xylims((self.tmin, 0, ymin, ymax), # side=side, autoscale=False) did_update = True except: update_failed = True axes = left_axes if itrace == 1: axes = right_axes if uselog and min(ydat) > 0: axes.set_yscale('log', basey=10) else: axes.set_yscale('linear') self.plotpanel.set_title(time.strftime("%Y-%b-%d %H:%M:%S", time.localtime())) if did_update: self.plotpanel.canvas.draw() self.needs_refresh = update_failed return