class StatplotWindow(Gtk.Window): def __init__(self, statfile): Gtk.Window.__init__(self) self.connect('key-release-event', self.KeyPressed) self.set_border_width(8) self.set_default_size(1600, 900) self.set_position(Gtk.WindowPosition.CENTER) self.set_title(statfile[-1]) self.statfile = statfile vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8) vbox.set_homogeneous(False) self.add(vbox) hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) hbox.set_homogeneous(True) vbox.pack_end(hbox, False, False, 0) self.entries, self.values = self.ReadData(self.statfile) store = Gtk.ListStore.new([str]) for entry in self.entries: store.append([entry]) self.xCombo = self.CreateCombo(store) self.yCombo = self.CreateCombo(store) self.InitCombo() self.InitCompletion(self.xCombo) self.InitCompletion(self.yCombo) self.xCombo.connect('changed', self.ComboChanged) self.xCombo.connect('key-release-event', self.ReleaseFocus) self.yCombo.connect('changed', self.ComboChanged) self.yCombo.connect('key-release-event', self.ReleaseFocus) hbox.pack_start(self.xCombo, True, True, 0) hbox.pack_start(self.yCombo, True, True, 0) self.fig = Figure(figsize=(14, 7)) self.ax = self.fig.gca() self.canvas = FigureCanvas(self.fig) self.canvas.set_can_focus(True) vbox.pack_start(self.canvas, True, True, 0) self.PlotType = 'marker' if self.values.ndim == 1 else 'line' self.PlotData('create', self.PlotType, 'linear', 'linear') self.toolbar = NavigationToolbar(self.canvas, self) vbox.pack_start(self.toolbar, False, False, 0) def ComboChanged(self, comboBox): activeText = comboBox.get_child().get_text() if activeText in self.entries: comboBox.set_active(self.entries.index(activeText)) if comboBox is self.xCombo: self.PlotData('update', self.PlotType, 'linear', self.ax.get_yscale()) elif comboBox is self.yCombo: self.PlotData('update', self.PlotType, self.ax.get_xscale(), 'linear') self.canvas.grab_focus() @staticmethod def CreateCombo(store): comboBox = Gtk.ComboBox.new_with_model_and_entry(store) comboBox.set_entry_text_column(0) comboBox.set_margin_left(100) comboBox.set_margin_right(100) comboBox.set_wrap_width(3) return comboBox def FormatAxis(self, ax2fmt, scale): if ax2fmt == 'x': setScale = self.ax.set_xscale curAxis = self.ax.xaxis curData = self.xData elif ax2fmt == 'y': setScale = self.ax.set_yscale curAxis = self.ax.yaxis curData = self.yData setScale(scale) if scale == 'linear': self.ax.ticklabel_format(style='sci', axis=ax2fmt, scilimits=(0, 0), useMathText=True) curAxis.set_minor_locator(tck.AutoMinorLocator()) self.ax.relim() self.ax.autoscale(True, ax2fmt, None) elif scale == 'log': curAxis.set_minor_locator(tck.LogLocator(subs=numpy.arange(2, 10))) logFmt = tck.LogFormatterSciNotation(base=10, labelOnlyBase=False, minor_thresholds=(4, 1)) curAxis.set_minor_formatter(logFmt) self.ax.relim() self.ax.autoscale(True, ax2fmt, None) elif scale == 'symlog': axMin = min(abs(curData[curData != 0])) axMax = max(abs(curData)) axRange = numpy.log10(axMax / axMin) if ax2fmt == 'x': setScale('symlog', basex=10, subsx=numpy.arange(2, 10), linthreshx=axMin * 10**(axRange / 2)) elif ax2fmt == 'y': setScale('symlog', basey=10, subsy=numpy.arange(2, 10), linthreshy=axMin * 10**(axRange / 2)) # Thomas Duvernay, 06/01/19 # There seems to be a bug with the labelling of the 0 tick # when a 'symlog' is used as an axis scale. It looks like # it is considered as a minor tick. symLogLoc = tck.SymmetricalLogLocator(subs=numpy.arange(2, 10), linthresh=axMin * 10**(axRange / 2), base=10) curAxis.set_minor_locator(symLogLoc) logFmt = tck.LogFormatterSciNotation(base=10, labelOnlyBase=False, minor_thresholds=(4, 1), linthresh=axMin * 10**(axRange / 2)) curAxis.set_minor_formatter(logFmt) self.ax.set_xlabel(self.xCombo.get_child().get_text(), fontweight='bold', fontsize=20) self.ax.set_ylabel(self.yCombo.get_child().get_text(), fontweight='bold', fontsize=20) self.ax.tick_params(which='major', length=7, labelsize=16, width=2) self.ax.tick_params(which='minor', length=4, labelsize=10, width=2, colors='xkcd:scarlet', labelrotation=45) self.ax.xaxis.get_offset_text().set(fontsize=13, fontweight='bold', color='xkcd:black') self.ax.yaxis.get_offset_text().set(fontsize=13, fontweight='bold', color='xkcd:black') self.fig.set_tight_layout(True) def InitCombo(self): if 'ElapsedTime' in self.entries: iterX = self.xCombo.get_model().get_iter( self.entries.index('ElapsedTime')) self.xCombo.set_active_iter(iterX) self.yCombo.set_active(0) else: self.xCombo.set_active(0) self.yCombo.set_active(1) @staticmethod def InitCompletion(comboBox): completion = Gtk.EntryCompletion.new() completion.set_text_column(0) completion.set_inline_completion(True) completion.set_inline_selection(False) completion.set_model(comboBox.get_model()) comboBox.get_child().set_completion(completion) def KeyPressed(self, widget, event): key = event.string if (self.xCombo.get_child().has_focus() or self.yCombo.get_child().has_focus()): pass elif key == 'r': self.entries, self.values = self.ReadData(self.statfile) self.PlotData('update', self.PlotType, self.ax.get_xscale(), self.ax.get_yscale()) elif key == 'q': self.destroy() elif key == 'x' or key == 'y': if self.values.ndim == 1: warnings.warn( 'Insufficient data available to turn on ' 'logarithmic scale', stacklevel=2) return if key == 'x': self.get_scale = self.ax.get_xscale curData = self.xData else: self.get_scale = self.ax.get_yscale curData = self.yData if self.get_scale() == 'linear' and (curData == 0).all(): warnings.warn( 'Change to logarithmic scale denied: the ' f'selected variable for the {key} axis is null.', stacklevel=2) return elif (self.get_scale() == 'linear' and max(abs(curData)) / min(abs(curData)) < 10): warnings.warn( 'Change to logarithmic scale denied: the ' f'selected variable for the {key} axis has a ' 'range of variation smaller than one order of ' 'magnitude.', stacklevel=2) return elif self.get_scale() == 'linear': axMin = min(abs(curData[curData != 0])) axMax = max(abs(curData)) if axMin != axMax and min(curData) < 0: scale = 'symlog' elif axMin != axMax: scale = 'log' else: warnings.warn( 'Change to logarithmic scale denied: the ' f'selected variable for the {key} axis is a ' 'constant.', stacklevel=2) return elif self.get_scale() in ['log', 'symlog']: scale = 'linear' self.FormatAxis(key, scale) self.fig.canvas.draw_idle() self.fig.canvas.draw() self.fig.canvas.flush_events() elif key == 'l': if self.values.ndim == 1: warnings.warn('Insufficient data available to turn on line ' + 'display', stacklevel=2) return self.PlotType = 'marker' if self.PlotType == 'line' else 'line' self.PlotData('update', self.PlotType, self.ax.get_xscale(), self.ax.get_yscale()) elif key == 'a': self.xCombo.grab_focus() elif key == 'o': self.yCombo.grab_focus() def PlotData(self, action, type, xscale, yscale): self.xData = self.values[..., self.xCombo.get_active()] self.yData = self.values[..., self.yCombo.get_active()] if action == 'create': self.statplot, = self.ax.plot(self.xData, self.yData) elif action == 'update': self.statplot.set_xdata(self.xData) self.statplot.set_ydata(self.yData) self.toolbar.update() if type == 'line': self.statplot.set_color('xkcd:light purple') self.statplot.set_linestyle('solid') self.statplot.set_linewidth(2) self.statplot.set_marker('None') elif type == 'marker': self.statplot.set_linestyle('None') self.statplot.set_marker('d') self.statplot.set_markeredgecolor('xkcd:black') self.statplot.set_markerfacecolor('xkcd:light purple') self.statplot.set_markersize(7) self.statplot.set_markeredgewidth(0.3) self.FormatAxis('x', xscale) self.FormatAxis('y', yscale) if action == 'update': self.fig.canvas.draw_idle() self.fig.canvas.draw() self.fig.canvas.flush_events() @staticmethod def ReadData(statfile): def GatherEntries(stat2read): print('Reading ' + stat2read) entries = [] with open(stat2read, 'r') as fid: for num, line in enumerate(fid): if line.startswith('<field'): info = etree.fromstring(line) if info.attrib['statistic'] == 'value': entries.append(info.attrib['name']) elif all([ x in info.attrib for x in ['components', 'material_phase'] ]): for i in range(int(info.attrib['components'])): entries.append('{e[material_phase]}%{e[name]}' '%{e[statistic]}%{i}'.format( e=info.attrib, i=i)) elif 'components' in info.attrib: for i in range(int(info.get('components'))): entries.append( '{e[name]}%{e[statistic]}%{i}'.format( e=info.attrib, i=i)) elif 'material_phase' in info.attrib: entries.append( '{e[material_phase]}%{e[name]}' '%{e[statistic]}'.format(e=info.attrib)) else: entries.append('{e[name]}%{e[statistic]}'.format( e=info.attrib)) elif line.startswith('</header>'): break return numpy.asarray(entries), num if statfile[0].endswith('stat'): entries, num = GatherEntries(statfile[0]) values = numpy.genfromtxt(statfile[0], skip_header=num + 1) values = values[..., numpy.argsort(entries)] entries = entries[numpy.argsort(entries)] if len(statfile) > 1: for item in statfile[1:]: entriesTemp, num = GatherEntries(item) if numpy.array_equal( entries, entriesTemp[numpy.argsort(entriesTemp)]): valuesTemp = numpy.genfromtxt(item, skip_header=num + 1) valuesTemp = valuesTemp.T[numpy.argsort(entriesTemp)].T values = numpy.vstack((values, valuesTemp)) else: sys.exit('Statfiles entries do not match') elif statfile[0].endswith('detectors'): entries = GatherEntries(statfile[0])[0] values = numpy.fromfile(statfile[0] + '.dat', dtype=numpy.float64) ncols = entries.size nrows = values.size // ncols values = values[:nrows * ncols].reshape(nrows, ncols) return list(entries), values def ReleaseFocus(self, widget, event): if event.keyval == Gdk.KEY_Escape: self.canvas.grab_focus()
class StatplotWindow(Gtk.Window): def __init__(self, statfile): Gtk.Window.__init__(self) self.connect('key-press-event', self.KeyPressed) self.set_border_width(8) self.set_default_size(1600, 900) self.set_position(Gtk.WindowPosition.CENTER) self.set_title(statfile[-1]) self.statfile = statfile self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8) self.vbox.set_homogeneous(False) self.add(self.vbox) hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) hbox.set_homogeneous(True) self.vbox.pack_end(hbox, False, False, 0) self.entries = self.ReadData(statfile) self.xCombo = Gtk.ComboBoxText.new_with_entry() self.xCombo.set_wrap_width(3) self.yCombo = Gtk.ComboBoxText.new_with_entry() self.yCombo.set_wrap_width(3) self.PopulateCombo('load') self.InitCompletion(self.xCombo) self.InitCompletion(self.yCombo) self.xCon = self.xCombo.connect('changed', self.ComboChangedX) self.xCombo.connect('key-release-event', self.ReleaseFocus) self.yCon = self.yCombo.connect('changed', self.ComboChangedY) self.yCombo.connect('key-release-event', self.ReleaseFocus) hbox.pack_start(self.xCombo, True, True, 0) hbox.pack_start(self.yCombo, True, True, 0) self.fig = Figure(figsize=(14, 7)) self.ax = self.fig.gca() self.canvas = FigureCanvas(self.fig) self.canvas.set_can_focus(True) self.vbox.pack_start(self.canvas, True, True, 0) self.PlotType = 'line' self.PlotData('create', self.PlotType, 'linear', 'linear') self.toolbar = NavigationToolbar(self.canvas, self) self.vbox.pack_start(self.toolbar, False, False, 0) def ComboChangedX(self, widget): if self.xCombo.get_active_text() in sorted(self.entries.Paths()): self.PlotData('update', self.PlotType, 'linear', self.ax.get_yscale()) self.canvas.grab_focus() def ComboChangedY(self, widget): if self.yCombo.get_active_text() in sorted(self.entries.Paths()): self.PlotData('update', self.PlotType, self.ax.get_xscale(), 'linear') self.canvas.grab_focus() def FormatAxis(self, ax2fmt, scale): if ax2fmt == 'x': self.set_scale = self.ax.set_xscale self.axis = self.ax.xaxis self.Data = self.xData elif ax2fmt == 'y': self.set_scale = self.ax.set_yscale self.axis = self.ax.yaxis self.Data = self.yData self.set_scale(scale) if scale == 'linear': self.ax.ticklabel_format(style='sci', axis=ax2fmt, scilimits=(0, 0), useMathText=True) self.axis.set_minor_locator(tck.AutoMinorLocator()) self.ax.relim() self.ax.autoscale(True, ax2fmt, None) elif scale == 'log': self.axis.set_minor_locator( tck.LogLocator(subs=numpy.arange(2, 10))) logFmt = tck.LogFormatterSciNotation(base=10, labelOnlyBase=False, minor_thresholds=(4, 1)) self.axis.set_minor_formatter(logFmt) self.ax.relim() self.ax.autoscale(True, ax2fmt, None) elif scale == 'symlog': axMin = min(abs(self.Data[self.Data != 0])) axMax = max(abs(self.Data)) axRange = numpy.log10(axMax / axMin) if ax2fmt == 'x': self.set_scale('symlog', basex=10, subsx=numpy.arange(2, 10), linthreshx=axMin * 10**(axRange / 2)) elif ax2fmt == 'y': self.set_scale('symlog', basey=10, subsy=numpy.arange(2, 10), linthreshy=axMin * 10**(axRange / 2)) # Thomas Duvernay, 06/01/19 # There seems to be a bug with the labelling of the 0 tick # when a 'symlog' is used as an axis scale. It looks like # it is considered as a minor tick. symLogLoc = tck.SymmetricalLogLocator(subs=numpy.arange(2, 10), linthresh=axMin * 10**(axRange / 2), base=10) self.axis.set_minor_locator(symLogLoc) logFmt = tck.LogFormatterSciNotation(base=10, labelOnlyBase=False, minor_thresholds=(4, 1), linthresh=axMin * 10**(axRange / 2)) self.axis.set_minor_formatter(logFmt) self.ax.set_xlabel(self.xField, fontweight='bold', fontsize=20) self.ax.set_ylabel(self.yField, fontweight='bold', fontsize=20) self.ax.tick_params(which='major', length=7, labelsize=16, width=2) self.ax.tick_params(which='minor', length=4, labelsize=10, width=2, colors='xkcd:scarlet', labelrotation=45) self.ax.xaxis.get_offset_text().set(fontsize=13, fontweight='bold', color='xkcd:black') self.ax.yaxis.get_offset_text().set(fontsize=13, fontweight='bold', color='xkcd:black') self.fig.set_tight_layout(True) def InitCompletion(self, comboBox): completion = Gtk.EntryCompletion.new() completion.set_text_column(0) completion.set_inline_completion(True) completion.set_inline_selection(True) completion.set_model(comboBox.get_model()) comboBox.get_child().set_completion(completion) def KeyPressed(self, widget, event): key = event.string if key == 'r': self.RefreshData(self.statfile) self.PlotData('update', self.PlotType, self.ax.get_xscale(), self.ax.get_yscale()) elif key == 'q': self.destroy() elif key == 'x' or key == 'y': if key == 'x': self.get_scale = self.ax.get_xscale self.Data = self.xData elif key == 'y': self.get_scale = self.ax.get_yscale self.Data = self.yData if self.get_scale() == 'linear' \ and self.Data[self.Data != 0].size == 0: warnings.warn('Change to logarithmic scale denied: the ' + 'selected variable for the ' + key + ' axis ' + 'is null.', stacklevel=2) scale = 'linear' elif self.get_scale() == 'linear' \ and max(abs(self.Data)) / min(abs(self.Data)) < 100: warnings.warn('Change to logarithmic scale denied: the ' + 'selected variable for the ' + key + ' axis ' + 'has a range of variation smaller than two ' + 'orders of magnitude.', stacklevel=2) scale = 'linear' elif self.get_scale() == 'linear': axMin = min(abs(self.Data[self.Data != 0])) axMax = max(abs(self.Data)) if axMin != axMax and min(self.Data) < 0: scale = 'symlog' elif axMin != axMax: scale = 'log' else: warnings.warn('Change to logarithmic scale denied: the ' + 'selected variable for the ' + key + ' ' + 'axis is a constant.', stacklevel=2) scale = 'linear' elif self.get_scale() in ['log', 'symlog']: scale = 'linear' self.FormatAxis(key, scale) self.fig.canvas.draw() elif key == 'l': self.PlotType = 'line' if self.PlotType == 'marker' else 'marker' self.PlotData('update', self.PlotType, self.ax.get_xscale(), self.ax.get_yscale()) def PlotData(self, action, type, xscale, yscale): self.xField = self.xCombo.get_active_text() self.yField = self.yCombo.get_active_text() self.xData = self.entries[self.xField] self.yData = self.entries[self.yField] if numpy.unique(self.xData).size == 1 \ and numpy.unique(self.yData).size == 1: type = 'marker' if action == 'create': self.statplot, = self.ax.plot(self.xData, self.yData, linewidth=2, color='xkcd:light purple') elif action == 'update': self.statplot.set_xdata(self.xData) self.statplot.set_ydata(self.yData) if type == 'line': self.statplot.set_color('xkcd:light purple') self.statplot.set_linestyle('solid') self.statplot.set_linewidth(2) self.statplot.set_marker('None') elif type == 'marker': self.statplot.set_linestyle('None') self.statplot.set_marker('d') self.statplot.set_markeredgecolor('xkcd:black') self.statplot.set_markerfacecolor('xkcd:light purple') self.statplot.set_markersize(7) self.statplot.set_markeredgewidth(0.3) self.toolbar.update() self.FormatAxis('x', xscale) self.FormatAxis('y', yscale) self.fig.canvas.draw() self.fig.canvas.flush_events() def PopulateCombo(self, action, prevIterX=None, prevIterY=None): for entry in sorted(self.entries.Paths()): self.xCombo.append_text(entry) self.yCombo.append_text(entry) if action == 'load': if 'ElapsedTime' in self.entries.Paths(): iterX = self.xCombo.get_model(). \ get_iter(sorted(self.entries.Paths()).index('ElapsedTime')) self.xCombo.set_active_iter(iterX) self.yCombo.set_active(0) else: self.xCombo.set_active(0) self.yCombo.set_active(1) elif action == 'reload': self.xCombo.set_active_iter(prevIterX) self.yCombo.set_active_iter(prevIterY) def ReadData(self, statfile): stats = [] for i, filename in enumerate(statfile): failcount = 0 while failcount < 4: try: stats.append(fluidity_tools.Stat(filename)) break except (TypeError, ValueError): time.sleep(0.2) failcount += 1 if failcount == 4: stats.append(fluidity_tools.Stat(filename)) if len(stats) == 1: return stats[0] else: return fluidity_tools.JoinStat(*stats) def RefreshData(self, statfile): with self.xCombo.handler_block(self.xCon), \ self.yCombo.handler_block(self.yCon): self.xCombo.remove_all() self.yCombo.remove_all() self.entries = self.ReadData(statfile) self.PopulateCombo('reload', prevIterX=self.xCombo.get_active_iter(), prevIterY=self.yCombo.get_active_iter()) def ReleaseFocus(self, widget, event): if event.keyval == Gdk.KEY_Escape: self.canvas.grab_focus()