From 44272203e736a8a85c345a01fee222bb23209e72 Mon Sep 17 00:00:00 2001 From: anwinged Date: Sun, 29 Apr 2012 07:18:17 +0000 Subject: [PATCH] Plots added --- trunk/forms.py | 73 +++++++++++-- trunk/opal.py | 224 ++++++++++++++++++++++++++++++++------ trunk/share/show-plot.png | Bin 0 -> 472 bytes trunk/task.py | 6 + trunk/tasks/testt.json | 2 +- 5 files changed, 263 insertions(+), 42 deletions(-) create mode 100644 trunk/share/show-plot.png diff --git a/trunk/forms.py b/trunk/forms.py index a1e06af..7b0b1d0 100644 --- a/trunk/forms.py +++ b/trunk/forms.py @@ -13,8 +13,13 @@ ID_DUPLICATE_MODEL = wx.NewId() ID_DUPLICATE_TREE = wx.NewId() ID_DELETE_MODEL = wx.NewId() ID_PROCESS_MODEL = wx.NewId() + ID_SHOW_RESULT = wx.NewId() ID_SHOW_PLOT = wx.NewId() +ID_ADD_PLOT = wx.NewId() +ID_ADD_LINE = wx.NewId() + +ID_ABOUT = wx.NewId() class TreeListCtrl(wx.gizmos.TreeListCtrl): @@ -55,12 +60,13 @@ class MainFrame (wx.Frame): self.m_user_models = TreeListCtrl(self, style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT - | wx.TR_EDIT_LABELS | wx.TR_ROW_LINES | wx.TR_MULTIPLE) + | wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_ROW_LINES | wx.TR_MULTIPLE) self.m_user_models.SetMinSize(wx.Size(-1, 300)) self.m_user_models.AddColumn("Model name") self.m_user_models.AddColumn("Status") self.m_user_models.AddColumn("Progress") self.m_user_models.AddColumn("Comment") + self.m_user_models.SetMainColumn(0) bSizer4.Add(self.m_user_models, 0, wx.ALL | wx.EXPAND, 1) @@ -79,6 +85,7 @@ class MainFrame (wx.Frame): bSizer5.Add(self.m_quick_result, 1, wx.EXPAND | wx.ALL, 1) self.m_plots = wx.TreeCtrl(self, style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT) + self.m_plots.AddRoot('root') bSizer5.Add(self.m_plots, 1, wx.EXPAND | wx.ALL, 1) bSizer3.Add(bSizer5, 0, wx.ALL | wx.EXPAND, 1) @@ -90,13 +97,16 @@ class MainFrame (wx.Frame): self.SetMenuBar(mbar) self.BuildContextMenu() + #tbar = self.BuildToolBar() + #self.SetToolBar(tbar) + self.SetSizer(bSizer3) self.Layout() self.Centre(wx.BOTH) - def __del__(self): - pass + il = wx.ImageList(16, 16) + def BuildMenu(self): menubar = wx.MenuBar() @@ -106,9 +116,7 @@ class MainFrame (wx.Frame): menubar.Append(menu, '&File') menu = wx.Menu() - menu.Append(ID_PROCESS_MODEL, 'Process\tCtrl+R') - menu.Append(ID_SHOW_RESULT, 'Show result\tCtrl+S') - menu.Append(ID_SHOW_PLOT, 'Show plot\tCtrl+G') + menu.Append(ID_PROCESS_MODEL, 'Process\tF5') menu.AppendSeparator() menu.Append(ID_ADD_MODEL_ROOT, 'Add model to root') menu.Append(ID_ADD_MODEL_SELECTED, 'Append model to selected') @@ -121,8 +129,15 @@ class MainFrame (wx.Frame): menubar.Append(menu, '&Model') menu = wx.Menu() - menu.Append(3, "&Log In\tCtrl+L") - menu.Append(2, "&Options\tCtrl+P") + menu.Append(ID_SHOW_RESULT, 'Show result\tCtrl+S') + menu.AppendSeparator() + menu.Append(ID_SHOW_PLOT, 'Show plot\tCtrl+G') + menu.Append(ID_ADD_PLOT, 'Add plot') + menu.Append(ID_ADD_LINE, 'Add line') + menubar.Append(menu, '&Result') + + menu = wx.Menu() + menu.Append(ID_ABOUT, "&About\tF1") menubar.Append(menu, '&Help') return menubar @@ -135,6 +150,19 @@ class MainFrame (wx.Frame): self.m_specs.Bind(wx.EVT_CONTEXT_MENU, lambda x: self.m_specs.PopupMenu(menu)) + menu1 = wx.Menu() + menu1.Append(ID_ADD_PLOT, 'Add plot') + menu1.Append(ID_ADD_LINE, 'Add line') + self.m_plots.Bind(wx.EVT_CONTEXT_MENU, + lambda x: self.m_plots.PopupMenu(menu1)) + + + def BuildToolBar(self): + tbar = wx.ToolBar(self, -1) + tbar.AddLabelTool(ID_SHOW_PLOT, 'Show plot', wx.Bitmap('share/show-plot.png')) + tbar.Realize() + return tbar + class ResultFrame(wx.Frame): def __init__(self, parent, title): wx.Frame.__init__ (self, parent, -1, title, size = wx.Size(500, 500), @@ -155,8 +183,35 @@ class ResultFrame(wx.Frame): self.Layout() self.Centre(wx.BOTH) +class LineSelectDialog(wx.Dialog): + def __init__(self, parent, title): + wx.Dialog.__init__ (self, parent, -1, title, size = wx.Size(400, 300)) + + bSizer = wx.BoxSizer(wx.HORIZONTAL) + + self.left = wx.ListBox(self) + self.right = wx.ListBox(self, style = wx.LB_EXTENDED) + + bSizer.Add(self.left, 1, wx.EXPAND | wx.ALL, 2) + bSizer.Add(self.right, 1, wx.EXPAND | wx.ALL, 2) + + buttonsSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(bSizer, 1, wx.EXPAND | wx.ALL, 0) + sizer.Add(buttonsSizer, 0, wx.EXPAND | wx.ALL, 5) + + self.SetSizer(sizer) + self.Layout() + self.Centre(wx.BOTH) + + class PlotFrame(wx.Frame): def __init__(self, parent, title): wx.Frame.__init__ (self, parent, -1, title, size = wx.Size(600, 400)) - self.plot = wxplot.PlotCanvas(self) \ No newline at end of file + self.plot = wxplot.PlotCanvas(self) + self.plot.SetGridColour(wx.Color(200, 200, 200)) + self.plot.SetEnableGrid(True) + self.plot.SetEnableAntiAliasing(True) + self.plot.SetEnableHiRes(True) diff --git a/trunk/opal.py b/trunk/opal.py index 9c55b49..1f440ab 100644 --- a/trunk/opal.py +++ b/trunk/opal.py @@ -15,6 +15,7 @@ import server import task import wx import wx.propgrid as wxpg +import wx.lib.plot as wxplot import forms import time import datetime @@ -69,6 +70,8 @@ class MainFrame(forms.MainFrame): self.OnAddModelToSelected) self.m_user_models.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnModelProcess) + self.m_plots.Bind(wx.EVT_TREE_ITEM_ACTIVATED, + self.OnPlotProcess) self.Bind(wx.EVT_MENU, self.OnTest, @@ -85,12 +88,27 @@ class MainFrame(forms.MainFrame): id = forms.ID_DELETE_MODEL) self.Bind(wx.EVT_MENU, self.OnModelProcess, id = forms.ID_PROCESS_MODEL) + self.Bind(wx.EVT_MENU, self.OnShowResult, id = forms.ID_SHOW_RESULT) + self.Bind(wx.EVT_MENU, self.OnShowPlot, + id = forms.ID_SHOW_PLOT) + self.Bind(wx.EVT_MENU, self.OnAddPlot, + id = forms.ID_ADD_PLOT) + self.Bind(wx.EVT_MENU, self.OnAddLines, + id = forms.ID_ADD_LINE) self.Bind(wx.EVT_CLOSE, self.OnClose) self.Bind(wx.EVT_IDLE, self.OnIdle) + # если установлен в True, то обработчик состояний работ + # будет работать вхолостую, чтобы не создать deadlock + # проблема возникает в том, что при одновременной блокировке + # GUI и вызова модального диалога, последний весит все приложение напрочь + # в момент своего закрытия. а так как диалог все равно модальный, + # форме не обязательно обновляться в тот момент, когда он открыт + self.do_nothing = False + ov = threading.Thread(target = self.Overseer) ov.daemon = True ov.start() @@ -126,33 +144,40 @@ class MainFrame(forms.MainFrame): um = self.m_user_models cycle_count = 0 while True: - wx.MutexGuiEnter() - #print 'cycle{:-8}'.format(cycle_count) - cycle_count += 1 - # просматриваем всю иерархию моделей - for item in um: - data = um.GetPyData(item) - if not data: - continue - jid = data.jid - if jid != None and self.server.IsJobChanged(jid): - state, percent, comment = self.server.GetJobState(jid) - um.SetItemText(item, StateToStr(state), 1) - p = 'Unknown' if percent < 0 else '{:%}'.format(percent) - um.SetItemText(item, p, 2) - um.SetItemText(item, comment, 3) - print 'JID', jid, (state, percent, comment) - # завершающие действия по окончанию выполнения работы - if state == server.JOB_COMPLETED: - # получаем результаты выполнения - data.res = self.server.GetJobResult(jid) - # если завершившаяся задача в данный момент выделена - # то сразу же показываем этот результат - if um.IsSelected(item): - self.ShowQuickResult(data.res) - - wx.MutexGuiLeave() time.sleep(0.1) + + # если нужно подождать, то мы подождем + if self.do_nothing: + continue + + wx.MutexGuiEnter() + try: + # print 'cycle{:-8}'.format(cycle_count) + cycle_count += 1 + # просматриваем всю иерархию моделей + for item in um: + data = um.GetPyData(item) + if not data: + continue + jid = data.jid + if jid != None and self.server.IsJobChanged(jid): + state, percent, comment = self.server.GetJobState(jid) + um.SetItemText(item, StateToStr(state), 1) + p = 'Unknown' if percent < 0 else '{:%}'.format(percent) + um.SetItemText(item, p, 2) + um.SetItemText(item, comment, 3) + print 'JID', jid, (state, percent, comment) + # завершающие действия по окончанию выполнения работы + if state == server.JOB_COMPLETED: + # получаем результаты выполнения + data.res = self.server.GetJobResult(jid) + # если завершившаяся задача в данный момент выделена + # то сразу же показываем этот результат + if um.IsSelected(item): + self.ShowQuickResult(data.res) + finally: + wx.MutexGuiLeave() + pass except Exception, e: print 'Error in overseer: ', e @@ -227,16 +252,22 @@ class MainFrame(forms.MainFrame): um.Expand(root) def NewProject(self, model): - # 1. загрузить спецификации модели - # 2. создать одну модель по умолчанию - + """ + Начать новый проект: + 1. Построить дерево спецификаций + 2. Создать одну пользовательскую модель (по умолчанию) + 3. Сделать заготовки для графиков/отчетов/прочего + """ + # Строим спецификации self.BuildSpecs(model) - + # Очищаем окно пользовательских моделей + # и создаем там одну um = self.m_user_models um.DeleteAllItems() - um.AddRoot('Root') - + um.AddRoot('root') self.AddModelToRoot(model) + # Создаем корневой элемент для окна с графиками + self.m_plots.AddRoot('root') return True # Project(model) @@ -394,6 +425,89 @@ class MainFrame(forms.MainFrame): rframe = ResultFrame(self, title, data.res) rframe.Show() + def GetLines(self): + """ + Возвращает набор линий, которые пользователь указал для + построения графика к выбранной модели. + + Линии представляют из себя кортежи из 4х элементов: + [ внутренний индекс в иерархии моделей, + данные модели, + колонка-х, + колонка-у ] + """ + um = self.m_user_models + item, data = self.GetSelectedItemData(um) + title = um.GetItemText(item) + if not data.res: + self.SetStatusText("There is no results in model") + return [] + f = LineSelectDialog(self, 'Select lines for "{}"'.format(title)) + for index, col in enumerate(data.res.columns): + row_title = col.GetTitle() + row_data = index + f.Add(row_title, row_data) + f.SetSelections() + + lines = [] + self.do_nothing = True + try: + if f.ShowModal() == wx.ID_OK: + lines = [ (item, data, x, y) for x, y in f.GetData() ] + finally: + self.do_nothing = False + + return lines + + def ShowPlot(self, lines, title = ''): + if not lines: + return + + data = [] + for item, moddata, x, y in lines: + data.append(moddata.res.Zip(x, y)) + + p = PlotFrame(self, 'Plot for model "%s"' % title, data) + p.Show() + + + def OnShowPlot(self, event): + lines = self.GetLines() + self.ShowPlot(lines) + + + + def OnAddPlot(self, event): + root = self.m_plots.GetRootItem() + child = self.m_plots.AppendItem(root, 'New plot') + self.m_plots.SetPyData(child, 'plot') + + def OnAddLines(self, event): + item = self.m_plots.GetSelection() + data = self.m_plots.GetItemPyData(item) + if data != 'plot': + return + lines = self.GetLines() + if not lines: + return + for line in lines: + child = self.m_plots.AppendItem(item, 'Line') + self.m_plots.SetPyData(child, line) + + def OnPlotProcess(self, event): + item = self.m_plots.GetSelection() + data = self.m_plots.GetItemPyData(item) + lines = [] + if data == 'plot': + child, cookie = self.m_plots.GetFirstChild(item) + while child.IsOk(): + lines.append(self.m_plots.GetItemPyData(child)) + child, cookie = self.m_plots.GetNextChild(item, cookie) + else: + lines = [data] + + self.ShowPlot(lines) + def OnIdle(self, event): pass @@ -431,6 +545,52 @@ class ResultFrame(forms.ResultFrame): for label, param in self.result.data.iteritems(): pg.Append(wxpg.StringProperty(label, value = str(param.GetValue()))) +#----------------------------------------------------------------------------- +# Форма с выбором наборов значений для построения графика +#----------------------------------------------------------------------------- + +class LineSelectDialog(forms.LineSelectDialog): + def __init__(self, parent, title): + forms.LineSelectDialog.__init__(self, parent, title) + + def Add(self, title, data = None): + self.left.Append(title, data) + self.right.Append(title, data) + + def SetSelections(self): + if self.left.GetCount(): + self.left.Select(0) + for i in xrange(1, self.right.GetCount()): + self.right.Select(i) + + def GetData(self): + item = self.left.GetSelection() + x = self.left.GetClientData(item) + + items = self.right.GetSelections() + ys = [ self.right.GetClientData(i) for i in items ] + + return [ (x, y) for y in ys ] + + +#----------------------------------------------------------------------------- +# Форма с изображением графика +#----------------------------------------------------------------------------- + +class PlotFrame(forms.PlotFrame): + def __init__(self, parent, title, lines_with_data): + forms.PlotFrame.__init__(self, parent, title) + #self.data = data + data = lines_with_data + + lines = [] + colours = ['red', 'blue', 'green'] + for i, d in enumerate(data): + lines.append( wxplot.PolyLine(d, colour = colours[i % len(colours)]) ) + + graph = wxplot.PlotGraphics(lines) + self.plot.Draw(graph) + #----------------------------------------------------------------------------- # Приложение #----------------------------------------------------------------------------- diff --git a/trunk/share/show-plot.png b/trunk/share/show-plot.png new file mode 100644 index 0000000000000000000000000000000000000000..f339231b39e2d4493bdc36140d3401e432c936a4 GIT binary patch literal 472 zcmV;}0Vn>6P)h8Fv~KC<2YbRbQmEeu%aOEY8HQ{csLBhQHDw>{S|kQ z4~ODm*LBD8K}Z>B@AiK3LsYM(K;QS;xrsS6O+&6u$O{IMG`?Qhv_)?5VcWJkP?n|s zt}dV0x>Asc6q6N;mpN2bb`!U3Udfnwq+~jSNSXivM$7@{ zX4)9{u@S*u0ZrQ_=f$3ZPN%Nx`TYol5L5*GJ#f<&B4vpnCAx2aI`if5z6GrK1Bi-# zy`m0JZ{bBL1^lnC(Y~?;+9}!@+6CIV-7hR|hxUc`f%cB}Y4^Kt0R{l3Ke8Fn8M;*f O0000