From 3ab06d08327c96c78beac7dd1052cf2ad591ba4d Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Thu, 15 Aug 2019 09:32:52 +0300 Subject: [PATCH] Move MPL-related code to canvas panel --- graphs/gui/canvasPanel.py | 174 ++++++++++++++++++++++++++++++++++++++ graphs/gui/frame.py | 173 ++----------------------------------- gui/mainMenuBar.py | 4 +- 3 files changed, 183 insertions(+), 168 deletions(-) diff --git a/graphs/gui/canvasPanel.py b/graphs/gui/canvasPanel.py index 579a52073..3a98dcb3e 100644 --- a/graphs/gui/canvasPanel.py +++ b/graphs/gui/canvasPanel.py @@ -18,8 +18,45 @@ # ============================================================================= +import itertools +import os +import traceback + + # noinspection PyPackageRequirements import wx +from logbook import Logger + + +from graphs.style import BASE_COLORS, LIGHTNESSES, STYLES, hsl_to_hsv + + +pyfalog = Logger(__name__) + + +try: + import matplotlib as mpl + + mpl_version = int(mpl.__version__[0]) or -1 + if mpl_version >= 2: + mpl.use('wxagg') + graphFrame_enabled = True + else: + graphFrame_enabled = False + + from matplotlib.lines import Line2D + from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas + from matplotlib.figure import Figure + from matplotlib.colors import hsv_to_rgb +except ImportError as e: + pyfalog.warning('Matplotlib failed to import. Likely missing or incompatible version.') + graphFrame_enabled = False +except Exception: + # We can get exceptions deep within matplotlib. Catch those. See GH #1046 + tb = traceback.format_exc() + pyfalog.critical('Exception when importing Matplotlib. Continuing without importing.') + pyfalog.critical(tb) + graphFrame_enabled = False class GraphCanvasPanel(wx.Panel): @@ -27,3 +64,140 @@ class GraphCanvasPanel(wx.Panel): def __init__(self, graphFrame, parent): super().__init__(parent) self.graphFrame = graphFrame + + # Remove matplotlib font cache, see #234 + try: + cache_dir = mpl._get_cachedir() + except: + cache_dir = os.path.expanduser(os.path.join('~', '.matplotlib')) + cache_file = os.path.join(cache_dir, 'fontList.cache') + if os.access(cache_dir, os.W_OK | os.X_OK) and os.path.isfile(cache_file): + os.remove(cache_file) + + mainSizer = wx.BoxSizer(wx.VERTICAL) + + self.figure = Figure(figsize=(5, 3), tight_layout={'pad': 1.08}) + rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get() + clr = [c / 255. for c in rgbtuple] + self.figure.set_facecolor(clr) + self.figure.set_edgecolor(clr) + self.canvas = Canvas(self, -1, self.figure) + self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple)) + self.subplot = self.figure.add_subplot(111) + self.subplot.grid(True) + mainSizer.Add(self.canvas, 1, wx.EXPAND | wx.ALL, 0) + + self.SetSizer(mainSizer) + + def draw(self): + self.subplot.clear() + self.subplot.grid(True) + lineData = [] + + min_y = 0 if self.graphFrame.ctrlPanel.showY0 else None + max_y = 0 if self.graphFrame.ctrlPanel.showY0 else None + + chosenX = self.graphFrame.ctrlPanel.xType + chosenY = self.graphFrame.ctrlPanel.yType + self.subplot.set(xlabel=self.graphFrame.ctrlPanel.formatLabel(chosenX), ylabel=self.graphFrame.ctrlPanel.formatLabel(chosenY)) + + mainInput, miscInputs = self.graphFrame.ctrlPanel.getValues() + view = self.graphFrame.getView() + sources = self.graphFrame.ctrlPanel.sources + if view.hasTargets: + iterList = tuple(itertools.product(sources, self.graphFrame.ctrlPanel.targets)) + else: + iterList = tuple((f, None) for f in sources) + for source, target in iterList: + # Get line style data + try: + colorData = BASE_COLORS[source.colorID] + except KeyError: + pyfalog.warning('Invalid color "{}" for "{}"'.format(source.colorID, source.name)) + continue + color = colorData.hsl + lineStyle = 'solid' + if target is not None: + try: + lightnessData = LIGHTNESSES[target.lightnessID] + except KeyError: + pyfalog.warning('Invalid lightness "{}" for "{}"'.format(target.lightnessID, target.name)) + continue + color = lightnessData.func(color) + try: + lineStyleData = STYLES[target.lineStyleID] + except KeyError: + pyfalog.warning('Invalid line style "{}" for "{}"'.format(target.lightnessID, target.name)) + continue + lineStyle = lineStyleData.mplSpec + color = hsv_to_rgb(hsl_to_hsv(color)) + + # Get point data + try: + xs, ys = view.getPlotPoints( + mainInput=mainInput, + miscInputs=miscInputs, + xSpec=chosenX, + ySpec=chosenY, + src=source, + tgt=target) + + # Figure out min and max Y + min_y_this = min(ys, default=None) + if min_y is None: + min_y = min_y_this + elif min_y_this is not None: + min_y = min(min_y, min_y_this) + max_y_this = max(ys, default=None) + if max_y is None: + max_y = max_y_this + elif max_y_this is not None: + max_y = max(max_y, max_y_this) + + # If we have single data point, show marker - otherwise line won't be shown + if len(xs) == 1 and len(ys) == 1: + self.subplot.plot(xs, ys, color=color, linestyle=lineStyle, marker='.') + else: + self.subplot.plot(xs, ys, color=color, linestyle=lineStyle) + + if target is None: + lineData.append((color, lineStyle, source.shortName)) + else: + lineData.append((color, lineStyle, '{} vs {}'.format(source.shortName, target.shortName))) + except Exception as ex: + pyfalog.warning('Invalid values in "{0}"', source.name) + self.canvas.draw() + self.Refresh() + return + + # Special case for when we do not show Y = 0 and have no fits + if min_y is None: + min_y = 0 + if max_y is None: + max_y = 0 + # Extend range a little for some visual space + y_range = max_y - min_y + min_y -= y_range * 0.05 + max_y += y_range * 0.05 + if min_y == max_y: + min_y -= min_y * 0.05 + max_y += min_y * 0.05 + if min_y == max_y: + min_y -= 5 + max_y += 5 + self.subplot.set_ylim(bottom=min_y, top=max_y) + + legendLines = [] + for i, iData in enumerate(lineData): + color, lineStyle, label = iData + legendLines.append(Line2D([0], [0], color=color, linestyle=lineStyle, label=label.replace('$', '\$'))) + + if len(legendLines) > 0 and self.graphFrame.ctrlPanel.showLegend: + legend = self.subplot.legend(handles=legendLines) + for t in legend.get_texts(): + t.set_fontsize('small') + for l in legend.get_lines(): + l.set_linewidth(1) + + self.canvas.draw() + self.Refresh() diff --git a/graphs/gui/frame.py b/graphs/gui/frame.py index 3467a5d02..07c0b6f20 100644 --- a/graphs/gui/frame.py +++ b/graphs/gui/frame.py @@ -18,10 +18,6 @@ # ============================================================================= -import itertools -import os -import traceback - # noinspection PyPackageRequirements import wx from logbook import Logger @@ -31,64 +27,29 @@ import gui.globalEvents as GE import gui.mainFrame from graphs.data.base import FitGraph from graphs.events import RESIST_MODE_CHANGED -from graphs.style import BASE_COLORS, LIGHTNESSES, STYLES, hsl_to_hsv from gui.auxFrame import AuxiliaryFrame from gui.bitmap_loader import BitmapLoader from service.const import GraphCacheCleanupReason from service.settings import GraphSettings +from . import canvasPanel from .ctrlPanel import GraphControlPanel pyfalog = Logger(__name__) -try: - import matplotlib as mpl - - mpl_version = int(mpl.__version__[0]) or -1 - if mpl_version >= 2: - mpl.use('wxagg') - graphFrame_enabled = True - else: - graphFrame_enabled = False - - from matplotlib.lines import Line2D - from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas - from matplotlib.figure import Figure - from matplotlib.colors import hsv_to_rgb -except ImportError as e: - pyfalog.warning('Matplotlib failed to import. Likely missing or incompatible version.') - graphFrame_enabled = False -except Exception: - # We can get exceptions deep within matplotlib. Catch those. See GH #1046 - tb = traceback.format_exc() - pyfalog.critical('Exception when importing Matplotlib. Continuing without importing.') - pyfalog.critical(tb) - graphFrame_enabled = False - class GraphFrame(AuxiliaryFrame): def __init__(self, parent): - - global graphFrame_enabled - if not graphFrame_enabled: + if not canvasPanel.graphFrame_enabled: pyfalog.warning('Matplotlib is not enabled. Skipping initialization.') return - super().__init__(parent, title='Graphs', style=wx.RESIZE_BORDER | wx.NO_FULL_REPAINT_ON_RESIZE, size=(520, 390)) + super().__init__(parent, title='Graphs', style=wx.RESIZE_BORDER, size=(520, 390)) self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.SetIcon(wx.Icon(BitmapLoader.getBitmap('graphs_small', 'gui'))) - # Remove matplotlib font cache, see #234 - try: - cache_dir = mpl._get_cachedir() - except: - cache_dir = os.path.expanduser(os.path.join('~', '.matplotlib')) - cache_file = os.path.join(cache_dir, 'fontList.cache') - if os.access(cache_dir, os.W_OK | os.X_OK) and os.path.isfile(cache_file): - os.remove(cache_file) - mainSizer = wx.BoxSizer(wx.VERTICAL) # Layout - graph selector @@ -97,16 +58,8 @@ class GraphFrame(AuxiliaryFrame): mainSizer.Add(self.graphSelection, 0, wx.EXPAND) # Layout - plot area - self.figure = Figure(figsize=(5, 3), tight_layout={'pad': 1.08}) - rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get() - clr = [c / 255. for c in rgbtuple] - self.figure.set_facecolor(clr) - self.figure.set_edgecolor(clr) - self.canvas = Canvas(self, -1, self.figure) - self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple)) - self.subplot = self.figure.add_subplot(111) - self.subplot.grid(True) - mainSizer.Add(self.canvas, 1, wx.EXPAND) + self.canvasPanel = canvasPanel.GraphCanvasPanel(self, self) + mainSizer.Add(self.canvasPanel, 1, wx.EXPAND | wx.ALL, 0) mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.EXPAND) @@ -142,7 +95,7 @@ class GraphFrame(AuxiliaryFrame): @classmethod def openOne(cls, parent): - if graphFrame_enabled: + if canvasPanel.graphFrame_enabled: super().openOne(parent) def UpdateWindowSize(self): @@ -244,116 +197,4 @@ class GraphFrame(AuxiliaryFrame): self.getView().clearCache(reason, extraData) def draw(self): - global mpl_version - - self.subplot.clear() - self.subplot.grid(True) - lineData = [] - - min_y = 0 if self.ctrlPanel.showY0 else None - max_y = 0 if self.ctrlPanel.showY0 else None - - chosenX = self.ctrlPanel.xType - chosenY = self.ctrlPanel.yType - self.subplot.set(xlabel=self.ctrlPanel.formatLabel(chosenX), ylabel=self.ctrlPanel.formatLabel(chosenY)) - - mainInput, miscInputs = self.ctrlPanel.getValues() - view = self.getView() - sources = self.ctrlPanel.sources - if view.hasTargets: - iterList = tuple(itertools.product(sources, self.ctrlPanel.targets)) - else: - iterList = tuple((f, None) for f in sources) - for source, target in iterList: - # Get line style data - try: - colorData = BASE_COLORS[source.colorID] - except KeyError: - pyfalog.warning('Invalid color "{}" for "{}"'.format(source.colorID, source.name)) - continue - color = colorData.hsl - lineStyle = 'solid' - if target is not None: - try: - lightnessData = LIGHTNESSES[target.lightnessID] - except KeyError: - pyfalog.warning('Invalid lightness "{}" for "{}"'.format(target.lightnessID, target.name)) - continue - color = lightnessData.func(color) - try: - lineStyleData = STYLES[target.lineStyleID] - except KeyError: - pyfalog.warning('Invalid line style "{}" for "{}"'.format(target.lightnessID, target.name)) - continue - lineStyle = lineStyleData.mplSpec - color = hsv_to_rgb(hsl_to_hsv(color)) - - # Get point data - try: - xs, ys = view.getPlotPoints( - mainInput=mainInput, - miscInputs=miscInputs, - xSpec=chosenX, - ySpec=chosenY, - src=source, - tgt=target) - - # Figure out min and max Y - min_y_this = min(ys, default=None) - if min_y is None: - min_y = min_y_this - elif min_y_this is not None: - min_y = min(min_y, min_y_this) - max_y_this = max(ys, default=None) - if max_y is None: - max_y = max_y_this - elif max_y_this is not None: - max_y = max(max_y, max_y_this) - - # If we have single data point, show marker - otherwise line won't be shown - if len(xs) == 1 and len(ys) == 1: - self.subplot.plot(xs, ys, color=color, linestyle=lineStyle, marker='.') - else: - self.subplot.plot(xs, ys, color=color, linestyle=lineStyle) - - if target is None: - lineData.append((color, lineStyle, source.shortName)) - else: - lineData.append((color, lineStyle, '{} vs {}'.format(source.shortName, target.shortName))) - except Exception as ex: - pyfalog.warning('Invalid values in "{0}"', source.name) - self.canvas.draw() - self.Refresh() - return - - # Special case for when we do not show Y = 0 and have no fits - if min_y is None: - min_y = 0 - if max_y is None: - max_y = 0 - # Extend range a little for some visual space - y_range = max_y - min_y - min_y -= y_range * 0.05 - max_y += y_range * 0.05 - if min_y == max_y: - min_y -= min_y * 0.05 - max_y += min_y * 0.05 - if min_y == max_y: - min_y -= 5 - max_y += 5 - self.subplot.set_ylim(bottom=min_y, top=max_y) - - legendLines = [] - for i, iData in enumerate(lineData): - color, lineStyle, label = iData - legendLines.append(Line2D([0], [0], color=color, linestyle=lineStyle, label=label.replace('$', '\$'))) - - if len(legendLines) > 0 and self.ctrlPanel.showLegend: - legend = self.subplot.legend(handles=legendLines) - for t in legend.get_texts(): - t.set_fontsize('small') - for l in legend.get_lines(): - l.set_linewidth(1) - - self.canvas.draw() - self.Refresh() + self.canvasPanel.draw() diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 0b42df0e6..91d383bc3 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -23,7 +23,7 @@ import wx import config from service.character import Character from service.fit import Fit -from graphs.gui import frame as graphFrame +from graphs.gui import canvasPanel as graphCanvasPanel import gui.globalEvents as GE from gui.bitmap_loader import BitmapLoader @@ -101,7 +101,7 @@ class MainMenuBar(wx.MenuBar): graphFrameItem = wx.MenuItem(fitMenu, self.graphFrameId, "&Graphs\tCTRL+G") graphFrameItem.SetBitmap(BitmapLoader.getBitmap("graphs_small", "gui")) fitMenu.Append(graphFrameItem) - if not graphFrame.graphFrame_enabled: + if not graphCanvasPanel.graphFrame_enabled: self.Enable(self.graphFrameId, False) self.ignoreRestrictionItem = fitMenu.Append(self.toggleIgnoreRestrictionID, "Disable Fitting Re&strictions")