Beispiel #1
0
    def __init__(self, text_color):
        # Init prarent and set up container
        Gtk.ScrolledWindow.__init__(self, border_width=10)

        self.text_color = text_color

        self.set_hexpand(True)
        self.set_vexpand(True)

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        but = Gtk.Box()

        # Convert string to tuple
        self.text_color = tuple(
            map(float,
                self.text_color.strip('rgba()').split(',')))

        # Normalize between 0 and 1 for matplotlib
        self.text_color = tuple(x / 255 for x in self.text_color)

        self.f = Figure(figsize=(600, 400), dpi=100)

        # Set figure to be transparent
        self.f.patch.set_visible(False)

        self.a = self.f.add_subplot(111,
                                    xscale="log",
                                    yscale="log",
                                    xlabel="t",
                                    ylabel="G(t)")

        self.a.xaxis.label.set_color(self.text_color)
        self.a.yaxis.label.set_color(self.text_color)

        # Set the background to be transparent
        self.a.set_facecolor('None')

        # Set the colors of the axes to the font colors of the theme
        self.a.tick_params(labelcolor=self.text_color,
                           color=self.text_color,
                           which='both')

        for child in self.a.axes.get_children():
            if isinstance(child, matplotlib.spines.Spine):
                child.set_color(self.text_color)

        # Finalize canvas
        self.canvas = FigureCanvas(self.f)

        # Set up toolbar
        toolbar = NavigationToolbar(self.canvas, self)
        toolbar.set_orientation(Gtk.Orientation.HORIZONTAL)

        box.pack_start(toolbar, False, False, 1)
        box.pack_start(self.canvas, True, True, 0)

        self.add(box)
Beispiel #2
0
	def __init__(self, application, size_request=None):
		"""
		:param tuple size_request: The size to set for the canvas.
		"""
		self.application = application
		self.config = application.config
		"""A reference to the King Phisher client configuration."""
		self.figure, _ = pyplot.subplots()
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		if size_request:
			self.canvas.set_size_request(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.application.get_active_window())
		self.popup_menu = Gtk.Menu.new()

		menu_item = Gtk.MenuItem.new_with_label('Export')
		menu_item.connect('activate', self.signal_activate_popup_menu_export)
		self.popup_menu.append(menu_item)

		menu_item = Gtk.MenuItem.new_with_label('Refresh')
		menu_item.connect('activate', lambda action: self.refresh())
		self.popup_menu.append(menu_item)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self._menu_item_show_toolbar = menu_item
		self.popup_menu.append(menu_item)
		self.popup_menu.show_all()
		self.navigation_toolbar.hide()
Beispiel #3
0
	def __init__(self, application, size_request=None, style_context=None):
		"""
		:param tuple size_request: The size to set for the canvas.
		"""
		self.application = application
		self.style_context = style_context
		self.config = application.config
		"""A reference to the King Phisher client configuration."""
		self.figure, _ = pyplot.subplots()
		self.figure.set_facecolor(self.get_color('bg', ColorHexCode.WHITE))
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		if size_request:
			self.canvas.set_size_request(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.application.get_active_window())
		self.popup_menu = Gtk.Menu.new()

		menu_item = Gtk.MenuItem.new_with_label('Export')
		menu_item.connect('activate', self.signal_activate_popup_menu_export)
		self.popup_menu.append(menu_item)

		menu_item = Gtk.MenuItem.new_with_label('Refresh')
		menu_item.connect('activate', lambda action: self.refresh())
		self.popup_menu.append(menu_item)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self._menu_item_show_toolbar = menu_item
		self.popup_menu.append(menu_item)
		self.popup_menu.show_all()
		self.navigation_toolbar.hide()
		self._legend = None
	def __init__(self, config, parent, size_request=None):
		self.config = config
		self.parent = parent
		self.figure, ax = pyplot.subplots()
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		if size_request:
			self.canvas.set_size_request(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent)
		self.navigation_toolbar.hide()
		self.popup_menu = Gtk.Menu.new()

		menu_item = Gtk.MenuItem.new_with_label('Export')
		menu_item.connect('activate', self.signal_activate_popup_menu_export)
		self.popup_menu.append(menu_item)

		menu_item = Gtk.MenuItem.new_with_label('Refresh')
		menu_item.connect('activate', lambda action: self.refresh())
		self.popup_menu.append(menu_item)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self.popup_menu.append(menu_item)
		self.popup_menu.show_all()
Beispiel #5
0
	def __init__(self, application, size_request=None, style_context=None):
		"""
		:param tuple size_request: The size to set for the canvas.
		"""
		self.application = application
		self.style_context = style_context
		self.config = application.config
		"""A reference to the King Phisher client configuration."""
		self.figure, _ = pyplot.subplots()
		self.figure.set_facecolor(self.get_color('bg', ColorHexCode.WHITE))
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		self.minimum_size = (380, 200)
		"""An absolute minimum size for the canvas."""
		if size_request is not None:
			self.resize(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.application.get_active_window())

		self.popup_menu = managers.MenuManager()
		self.popup_menu.append('Export', self.signal_activate_popup_menu_export)
		self.popup_menu.append('Refresh', self.signal_activate_popup_refresh)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self._menu_item_show_toolbar = menu_item
		self.popup_menu.append_item(menu_item)

		self.navigation_toolbar.hide()
		self._legend = None
Beispiel #6
0
    def build_imagePanel(self):
        self.fig = Figure(dpi=100, tight_layout=True)
        canvas = FigureCanvas(self.fig)
        self.fig.canvas.mpl_connect('draw_event', self.ondraw)
        toolbar = NavigationToolbar(canvas, self)
        children = toolbar.get_children()
        for i in range(len(children) - 3):
            children[i].destroy()

        self.main_ax = None
        self.x_hist = None
        self.y_hist = None
        self.zoom_ax = None

        box = Gtk.VBox()
        box.pack_start(canvas, True, True, 0)
        box.pack_end(toolbar, False, False, 0)
        return box
Beispiel #7
0
    def reinit(self, canvas, messagelabel, coordlabel):
        """Call the real super __init__ after the canvas is known and give
        it to this function."""
        self.message = messagelabel
        self.mode_label = messagelabel
        self.xy_label = coordlabel
        NavigationToolbar.__init__(self, canvas, None)

        self.span_selector = SpanSelector(self.canvas.figure.ax,
                                          lambda *args: None,
                                          "horizontal",
                                          minspan=0.2,
                                          span_stays=False,
                                          useblit=True)
        self.span_selector.active = False
        self.peak_selector = PeakSelector(self.canvas.figure.ax,
                                          lambda *args: None,
                                          peak_stays=False,
                                          useblit=True)
        self.peak_selector.active = False
Beispiel #8
0
 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()
     store = Gtk.ListStore.new([str])
     for entry in self.entries:
         store.append([entry])
     self.xCombo = Gtk.ComboBox.new_with_model_and_entry(store)
     self.yCombo = Gtk.ComboBox.new_with_model_and_entry(store)
     self.ComboParams(self.xCombo)
     self.ComboParams(self.yCombo)
     self.InitCombo('load')
     self.InitCompletion(self.xCombo)
     self.InitCompletion(self.yCombo)
     self.xCon = self.xCombo.connect('changed', self.ComboChanged)
     self.xCombo.connect('key-release-event', self.ReleaseFocus)
     self.yCon = 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 = 'line'
     self.PlotData('create', self.PlotType, 'linear', 'linear')
     self.toolbar = NavigationToolbar(self.canvas, self)
     vbox.pack_start(self.toolbar, False, False, 0)
Beispiel #9
0
    def __init__(self, application, size_request=None, style_context=None):
        """
		:param tuple size_request: The size to set for the canvas.
		"""
        self.logger = logging.getLogger('KingPhisher.Client.Graph.' +
                                        self.__class__.__name__[13:])
        self.application = application
        self.style_context = style_context
        self.config = application.config
        """A reference to the King Phisher client configuration."""
        self.figure, _ = pyplot.subplots()
        self.figure.set_facecolor(self.get_color('bg', ColorHexCode.WHITE))
        self.axes = self.figure.get_axes()
        self.canvas = FigureCanvas(self.figure)
        self.manager = None
        self.minimum_size = (380, 200)
        """An absolute minimum size for the canvas."""
        if size_request is not None:
            self.resize(*size_request)
        self.canvas.mpl_connect('button_press_event',
                                self.mpl_signal_canvas_button_pressed)
        self.canvas.show()
        self.navigation_toolbar = NavigationToolbar(
            self.canvas, self.application.get_active_window())

        self.popup_menu = managers.MenuManager()
        self.popup_menu.append('Export',
                               self.signal_activate_popup_menu_export)
        self.popup_menu.append('Refresh', self.signal_activate_popup_refresh)

        menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
        menu_item.connect('toggled',
                          self.signal_toggled_popup_menu_show_toolbar)
        self._menu_item_show_toolbar = menu_item
        self.popup_menu.append_item(menu_item)

        self.navigation_toolbar.hide()
        self._legend = None
Beispiel #10
0
    def __init__(self, config, parent, size_request=None):
        """
		:param dict config: The King Phisher client configuration.
		:param parent: The parent window for this object.
		:type parent: :py:class:`Gtk.Window`
		:param tuple size_request: The size to set for the canvas.
		"""
        self.config = config
        """A reference to the King Phisher client configuration."""
        self.parent = parent
        """The parent :py:class:`Gtk.Window` instance."""
        self.figure, _ = pyplot.subplots()
        self.axes = self.figure.get_axes()
        self.canvas = FigureCanvas(self.figure)
        self.manager = None
        if size_request:
            self.canvas.set_size_request(*size_request)
        self.canvas.mpl_connect('button_press_event',
                                self.mpl_signal_canvas_button_pressed)
        self.canvas.show()
        self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent)
        self.popup_menu = Gtk.Menu.new()

        menu_item = Gtk.MenuItem.new_with_label('Export')
        menu_item.connect('activate', self.signal_activate_popup_menu_export)
        self.popup_menu.append(menu_item)

        menu_item = Gtk.MenuItem.new_with_label('Refresh')
        menu_item.connect('activate', lambda action: self.refresh())
        self.popup_menu.append(menu_item)

        menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
        menu_item.connect('toggled',
                          self.signal_toggled_popup_menu_show_toolbar)
        self._menu_item_show_toolbar = menu_item
        self.popup_menu.append(menu_item)
        self.popup_menu.show_all()
        self.navigation_toolbar.hide()
Beispiel #11
0
class GraphBase(object):
    """
	A basic graph provider for using :py:mod:`matplotlib` to create graph
	representations of campaign data. This class is meant to be subclassed
	by real providers.
	"""
    name = 'Unknown'
    """The name of the graph provider."""
    name_human = 'Unknown'
    """The human readable name of the graph provider used for UI identification."""
    graph_title = 'Unknown'
    """The title that will be given to the graph."""
    table_subscriptions = []
    """A list of tables from which information is needed to produce the graph."""
    is_available = True

    def __init__(self, application, size_request=None, style_context=None):
        """
		:param tuple size_request: The size to set for the canvas.
		"""
        self.application = application
        self.style_context = style_context
        self.config = application.config
        """A reference to the King Phisher client configuration."""
        self.figure, _ = pyplot.subplots()
        self.figure.set_facecolor(self.get_color('bg', ColorHexCode.WHITE))
        self.axes = self.figure.get_axes()
        self.canvas = FigureCanvas(self.figure)
        self.manager = None
        self.minimum_size = (380, 200)
        """An absolute minimum size for the canvas."""
        if size_request is not None:
            self.resize(*size_request)
        self.canvas.mpl_connect('button_press_event',
                                self.mpl_signal_canvas_button_pressed)
        self.canvas.show()
        self.navigation_toolbar = NavigationToolbar(
            self.canvas, self.application.get_active_window())
        self.popup_menu = Gtk.Menu.new()

        menu_item = Gtk.MenuItem.new_with_label('Export')
        menu_item.connect('activate', self.signal_activate_popup_menu_export)
        self.popup_menu.append(menu_item)

        menu_item = Gtk.MenuItem.new_with_label('Refresh')
        menu_item.connect('activate', self.signal_activate_popup_refresh)
        self.popup_menu.append(menu_item)

        menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
        menu_item.connect('toggled',
                          self.signal_toggled_popup_menu_show_toolbar)
        self._menu_item_show_toolbar = menu_item
        self.popup_menu.append(menu_item)
        self.popup_menu.show_all()
        self.navigation_toolbar.hide()
        self._legend = None

    @property
    def rpc(self):
        return self.application.rpc

    @staticmethod
    def _ax_hide_ticks(ax):
        for tick in ax.yaxis.get_major_ticks():
            tick.tick1On = False
            tick.tick2On = False

    @staticmethod
    def _ax_set_spine_color(ax, spine_color):
        for pos in ('top', 'right', 'bottom', 'left'):
            ax.spines[pos].set_color(spine_color)

    def add_legend_patch(self, legend_rows, fontsize=None):
        if self._legend is not None:
            self._legend.remove()
            self._legend = None
        fontsize = fontsize or self.fontsize_scale
        legend_bbox = self.figure.legend(tuple(
            patches.Patch(color=patch_color)
            for patch_color, _ in legend_rows),
                                         tuple(label
                                               for _, label in legend_rows),
                                         borderaxespad=1.25,
                                         fontsize=fontsize,
                                         frameon=True,
                                         handlelength=1.5,
                                         handletextpad=0.75,
                                         labelspacing=0.3,
                                         loc='lower right')
        legend_bbox.legendPatch.set_linewidth(0)
        self._legend = legend_bbox

    def get_color(self, color_name, default):
        """
		Get a color by its style name such as 'fg' for foreground. If the
		specified color does not exist, default will be returned. The underlying
		logic for this function is provided by
		:py:func:`~.gui_utilities.gtk_style_context_get_color`.

		:param str color_name: The style name of the color.
		:param default: The default color to return if the specified one was not found.
		:return: The desired color if it was found.
		:rtype: tuple
		"""
        color_name = 'theme_color_graph_' + color_name
        sc_color = gui_utilities.gtk_style_context_get_color(
            self.style_context, color_name, default)
        return (sc_color.red, sc_color.green, sc_color.blue)

    def make_window(self):
        """
		Create a window from the figure manager.

		:return: The graph in a new, dedicated window.
		:rtype: :py:class:`Gtk.Window`
		"""
        if self.manager is None:
            self.manager = FigureManager(self.canvas, 0)
        self.navigation_toolbar.destroy()
        self.navigation_toolbar = self.manager.toolbar
        self._menu_item_show_toolbar.set_active(True)
        window = self.manager.window
        window.set_transient_for(self.application.get_active_window())
        window.set_title(self.graph_title)
        return window

    @property
    def fontsize_scale(self):
        scale = self.markersize_scale
        if scale < 5:
            fontsize = 'xx-small'
        elif scale < 7:
            fontsize = 'x-small'
        elif scale < 9:
            fontsize = 'small'
        else:
            fontsize = 'medium'
        return fontsize

    @property
    def markersize_scale(self):
        bbox = self.axes[0].get_window_extent().transformed(
            self.figure.dpi_scale_trans.inverted())
        return bbox.width * self.figure.dpi * 0.01

    def mpl_signal_canvas_button_pressed(self, event):
        if event.button != 3:
            return
        self.popup_menu.popup(None, None, None, None, event.button,
                              Gtk.get_current_event_time())
        return True

    def signal_activate_popup_menu_export(self, action):
        dialog = extras.FileChooserDialog('Export Graph',
                                          self.application.get_active_window())
        file_name = self.config['campaign_name'] + '.png'
        response = dialog.run_quick_save(file_name)
        dialog.destroy()
        if not response:
            return
        destination_file = response['target_path']
        self.figure.savefig(destination_file,
                            dpi=200,
                            facecolor=self.figure.get_facecolor(),
                            format='png')

    def signal_activate_popup_refresh(self, event):
        self.refresh()

    def signal_toggled_popup_menu_show_toolbar(self, widget):
        if widget.get_property('active'):
            self.navigation_toolbar.show()
        else:
            self.navigation_toolbar.hide()

    def resize(self, width=0, height=0):
        """
		Attempt to resize the canvas. Regardless of the parameters the canvas
		will never be resized to be smaller than :py:attr:`.minimum_size`.

		:param int width: The desired width of the canvas.
		:param int height: The desired height of the canvas.
		"""
        min_width, min_height = self.minimum_size
        width = max(width, min_width)
        height = max(height, min_height)
        self.canvas.set_size_request(width, height)
Beispiel #12
0
class CampaignGraph(object):
	"""
	A basic graph provider for using :py:mod:`matplotlib` to create graph
	representations of campaign data. This class is meant to be subclassed
	by real providers.
	"""
	name = 'Unknown'
	"""The name of the graph provider."""
	name_human = 'Unknown'
	"""The human readable name of the graph provider used for UI identification."""
	graph_title = 'Unknown'
	"""The title that will be given to the graph."""
	table_subscriptions = []
	"""A list of tables from which information is needed to produce the graph."""
	is_available = True
	def __init__(self, config, parent, size_request=None):
		"""
		:param dict config: The King Phisher client configuration.
		:param parent: The parent window for this object.
		:type parent: :py:class:`Gtk.Window`
		:param tuple size_request: The size to set for the canvas.
		"""
		self.config = config
		"""A reference to the King Phisher client configuration."""
		self.parent = parent
		"""The parent :py:class:`Gtk.Window` instance."""
		self.figure, _ = pyplot.subplots()
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		if size_request:
			self.canvas.set_size_request(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent)
		self.popup_menu = Gtk.Menu.new()

		menu_item = Gtk.MenuItem.new_with_label('Export')
		menu_item.connect('activate', self.signal_activate_popup_menu_export)
		self.popup_menu.append(menu_item)

		menu_item = Gtk.MenuItem.new_with_label('Refresh')
		menu_item.connect('activate', lambda action: self.refresh())
		self.popup_menu.append(menu_item)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self._menu_item_show_toolbar = menu_item
		self.popup_menu.append(menu_item)
		self.popup_menu.show_all()
		self.navigation_toolbar.hide()

	def _load_graph(self, info_cache):
		raise NotImplementedError()

	def _graph_bar_set_yparams(self, top_lim):
		min_value = top_lim + (top_lim * 0.075)
		if min_value <= 25:
			scale = 5
		else:
			scale = scale = 10 ** (len(str(int(min_value))) - 1)
		inc_scale = scale
		while scale <= min_value:
			scale += inc_scale
		top_lim = scale

		ax = self.axes[0]
		yticks = set((round(top_lim * 0.5), top_lim))
		ax.set_yticks(tuple(yticks))
		ax.set_ylim(top=top_lim)
		return

	def _graph_null_pie(self, title):
		ax = self.axes[0]
		ax.pie((100,), labels=(title,), colors=(MPL_COLOR_NULL,), autopct='%1.0f%%', shadow=True, startangle=90)
		ax.axis('equal')
		return

	def add_legend_patch(self, legend_rows, fontsize=None):
		handles = []
		if not fontsize:
			scale = self.markersize_scale
			if scale < 5:
				fontsize = 'xx-small'
			elif scale < 7:
				fontsize = 'x-small'
			elif scale < 9:
				fontsize = 'small'
			else:
				fontsize = 'medium'
		for row in legend_rows:
			handles.append(patches.Patch(color=row[0], label=row[1]))
		self.axes[0].legend(handles=handles, fontsize=fontsize, loc='lower right')

	def graph_bar(self, bars, color=None, xticklabels=None, ylabel=None):
		"""
		Create a standard bar graph with better defaults for the standard use
		cases.

		:param list bars: The values of the bars to graph.
		:param color: The color of the bars on the graph.
		:type color: list, str
		:param list xticklabels: The labels to use on the x-axis.
		:param str ylabel: The label to give to the y-axis.
		:return: The bars created using :py:mod:`matplotlib`
		:rtype: `matplotlib.container.BarContainer`
		"""
		color = color or MPL_COLOR_NULL
		width = 0.25
		ax = self.axes[0]
		self._graph_bar_set_yparams(max(bars) if bars else 0)
		bars = ax.bar(range(len(bars)), bars, width, color=color)
		ax.set_xticks([float(x) + (width / 2) for x in range(len(bars))])
		if xticklabels:
			ax.set_xticklabels(xticklabels, rotation=30)
			for col in bars:
				height = col.get_height()
				ax.text(col.get_x() + col.get_width() / 2.0, height, "{0:,}".format(height), ha='center', va='bottom')
		if ylabel:
			ax.set_ylabel(ylabel)
		self.figure.subplots_adjust(bottom=0.25)
		return bars

	def make_window(self):
		"""
		Create a window from the figure manager.

		:return: The graph in a new, dedicated window.
		:rtype: :py:class:`Gtk.Window`
		"""
		if self.manager == None:
			self.manager = FigureManager(self.canvas, 0)
		self.navigation_toolbar.destroy()
		self.navigation_toolbar = self.manager.toolbar
		self._menu_item_show_toolbar.set_active(True)
		window = self.manager.window
		window.set_transient_for(self.parent)
		window.set_title(self.graph_title)
		return window

	@property
	def markersize_scale(self):
		bbox = self.axes[0].get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
		return max(bbox.width, bbox.width) * self.figure.dpi * 0.01

	def mpl_signal_canvas_button_pressed(self, event):
		if event.button != 3:
			return
		self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time())
		return True

	def signal_activate_popup_menu_export(self, action):
		dialog = gui_utilities.UtilityFileChooser('Export Graph', self.parent)
		file_name = self.config['campaign_name'] + '.png'
		response = dialog.run_quick_save(file_name)
		dialog.destroy()
		if not response:
			return
		destination_file = response['target_path']
		self.figure.savefig(destination_file, format='png')

	def signal_toggled_popup_menu_show_toolbar(self, widget):
		if widget.get_property('active'):
			self.navigation_toolbar.show()
		else:
			self.navigation_toolbar.hide()

	def load_graph(self):
		"""Load the graph information via :py:meth:`.refresh`."""
		self.refresh()

	def refresh(self, info_cache=None, stop_event=None):
		"""
		Refresh the graph data by retrieving the information from the
		remote server.

		:param dict info_cache: An optional cache of data tables.
		:param stop_event: An optional object indicating that the operation should stop.
		:type stop_event: :py:class:`threading.Event`
		:return: A dictionary of cached tables from the server.
		:rtype: dict
		"""
		info_cache = (info_cache or {})
		if not self.parent.rpc:
			return info_cache
		for table in self.table_subscriptions:
			if stop_event and stop_event.is_set():
				return info_cache
			if not table in info_cache:
				info_cache[table] = tuple(self.parent.rpc.remote_table('campaign/' + table, self.config['campaign_id']))
		for ax in self.axes:
			ax.clear()
		self._load_graph(info_cache)
		self.axes[0].set_title(self.graph_title, y=1.03)
		self.canvas.draw()
		return info_cache
Beispiel #13
0
class CampaignGraph(object):
	"""
	A basic graph provider for using :py:mod:`matplotlib` to create graph
	representations of campaign data. This class is meant to be subclassed
	by real providers.
	"""
	name = 'Unknown'
	"""The name of the graph provider."""
	name_human = 'Unknown'
	"""The human readable name of the graph provider used for UI identification."""
	graph_title = 'Unknown'
	"""The title that will be given to the graph."""
	table_subscriptions = []
	"""A list of tables from which information is needed to produce the graph."""
	is_available = True
	def __init__(self, application, size_request=None, style_context=None):
		"""
		:param tuple size_request: The size to set for the canvas.
		"""
		self.application = application
		self.style_context = style_context
		self.config = application.config
		"""A reference to the King Phisher client configuration."""
		self.figure, _ = pyplot.subplots()
		self.figure.set_facecolor(self.get_color('bg', ColorHexCode.WHITE))
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		if size_request:
			self.canvas.set_size_request(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.application.get_active_window())
		self.popup_menu = Gtk.Menu.new()

		menu_item = Gtk.MenuItem.new_with_label('Export')
		menu_item.connect('activate', self.signal_activate_popup_menu_export)
		self.popup_menu.append(menu_item)

		menu_item = Gtk.MenuItem.new_with_label('Refresh')
		menu_item.connect('activate', lambda action: self.refresh())
		self.popup_menu.append(menu_item)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self._menu_item_show_toolbar = menu_item
		self.popup_menu.append(menu_item)
		self.popup_menu.show_all()
		self.navigation_toolbar.hide()
		self._legend = None

	@property
	def rpc(self):
		return self.application.rpc

	@staticmethod
	def _ax_hide_ticks(ax):
		for tick in ax.yaxis.get_major_ticks():
			tick.tick1On = False
			tick.tick2On = False

	@staticmethod
	def _ax_set_spine_color(ax, spine_color):
		for pos in ('top', 'right', 'bottom', 'left'):
			ax.spines[pos].set_color(spine_color)

	def _load_graph(self, info_cache):
		raise NotImplementedError()

	def add_legend_patch(self, legend_rows, fontsize=None):
		if self._legend is not None:
			self._legend.remove()
			self._legend = None
		if fontsize is None:
			scale = self.markersize_scale
			if scale < 5:
				fontsize = 'xx-small'
			elif scale < 7:
				fontsize = 'x-small'
			elif scale < 9:
				fontsize = 'small'
			else:
				fontsize = 'medium'
		legend_bbox = self.figure.legend(
			tuple(patches.Patch(color=patch_color) for patch_color, _ in legend_rows),
			tuple(label for _, label in legend_rows),
			borderaxespad=1.25,
			fontsize=fontsize,
			frameon=True,
			handlelength=1.5,
			handletextpad=0.75,
			labelspacing=0.3,
			loc='lower right'
		)
		legend_bbox.legendPatch.set_linewidth(0)
		self._legend = legend_bbox

	def get_color(self, color_name, default):
		"""
		Get a color by its style name such as 'fg' for foreground. If the
		specified color does not exist, default will be returned. The underlying
		logic for this function is provided by
		:py:func:`~.gui_utilities.gtk_style_context_get_color`.

		:param str color_name: The style name of the color.
		:param default: The default color to return if the specified one was not found.
		:return: The desired color if it was found.
		:rtype: tuple
		"""
		color_name = 'theme_color_graph_' + color_name
		sc_color = gui_utilities.gtk_style_context_get_color(self.style_context, color_name, default)
		return (sc_color.red, sc_color.green, sc_color.blue)

	def make_window(self):
		"""
		Create a window from the figure manager.

		:return: The graph in a new, dedicated window.
		:rtype: :py:class:`Gtk.Window`
		"""
		if self.manager is None:
			self.manager = FigureManager(self.canvas, 0)
		self.navigation_toolbar.destroy()
		self.navigation_toolbar = self.manager.toolbar
		self._menu_item_show_toolbar.set_active(True)
		window = self.manager.window
		window.set_transient_for(self.application.get_active_window())
		window.set_title(self.graph_title)
		return window

	@property
	def markersize_scale(self):
		bbox = self.axes[0].get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
		return bbox.width * self.figure.dpi * 0.01

	def mpl_signal_canvas_button_pressed(self, event):
		if event.button != 3:
			return
		self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time())
		return True

	def signal_activate_popup_menu_export(self, action):
		dialog = gui_utilities.FileChooser('Export Graph', self.application.get_active_window())
		file_name = self.config['campaign_name'] + '.png'
		response = dialog.run_quick_save(file_name)
		dialog.destroy()
		if not response:
			return
		destination_file = response['target_path']
		self.figure.savefig(destination_file, format='png')

	def signal_toggled_popup_menu_show_toolbar(self, widget):
		if widget.get_property('active'):
			self.navigation_toolbar.show()
		else:
			self.navigation_toolbar.hide()

	def load_graph(self):
		"""Load the graph information via :py:meth:`.refresh`."""
		self.refresh()

	def refresh(self, info_cache=None, stop_event=None):
		"""
		Refresh the graph data by retrieving the information from the
		remote server.

		:param dict info_cache: An optional cache of data tables.
		:param stop_event: An optional object indicating that the operation should stop.
		:type stop_event: :py:class:`threading.Event`
		:return: A dictionary of cached tables from the server.
		:rtype: dict
		"""
		info_cache = (info_cache or {})
		if not self.rpc:
			return info_cache
		for table in self.table_subscriptions:
			if stop_event and stop_event.is_set():
				return info_cache
			if not table in info_cache:
				info_cache[table] = tuple(self.rpc.remote_table(table, query_filter={'campaign_id': self.config['campaign_id']}))
		for ax in self.axes:
			ax.clear()
		if self._legend is not None:
			self._legend.remove()
			self._legend = None
		self._load_graph(info_cache)
		self.figure.suptitle(
			self.graph_title,
			color=self.get_color('fg', ColorHexCode.BLACK),
			size=14,
			weight='bold',
			y=0.97
		)
		self.canvas.draw()
		return info_cache
    def __init__(self):
        Gtk.Window.__init__(self, title="Numeric methods")
        self.set_border_width(10)
        self.set_size_request(872, 657)
        #self.set_resizable(resizable=False)
        self.pl = PlotGraph()

        # Layout grid
        self.grid = Gtk.Grid(column_homogeneous=True,
                             column_spacing=10,
                             row_spacing=10)
        self.add(self.grid)

        # All entries (equation, x0, y0, step, bound)
        # equation
        self.equation = Gtk.Entry(placeholder_text="write your equation")
        self.equation.set_size_request(357, 47)

        # x0
        self.x_entry = Gtk.Entry(placeholder_text="initial point x")
        self.x_entry.set_size_request(134, 37)

        # y0
        self.y_entry = Gtk.Entry(placeholder_text="initial point y")
        self.y_entry.set_size_request(134, 37)

        # grid
        self.step = Gtk.Entry(placeholder_text="step")
        self.step.set_size_request(134, 37)

        # grid bound
        self.grid_bound = Gtk.Entry(placeholder_text="step bound")
        self.grid_bound.set_size_request(134, 37)

        # bound
        self.bound = Gtk.Entry(placeholder_text="x bound")
        self.bound.set_size_request(134, 37)

        # flag to check which error should be plotted(local or global)
        self.error_flag = False  # when False it means local, when True it means global

        # creating button
        self.button = Gtk.Button(label="PLOT GRAPH")
        self.button.connect("clicked", self.draw)
        self.button.set_size_request(115, 47)

        self.canvas = FigureCanvas(self.pl.fig)
        self.canvas.set_size_request(800, 600)

        # check buttons (for choice of methods that would be plotted)
        ex_box = Gtk.Box()
        label_ex = Gtk.Label("Exact")
        self.exact_check = Gtk.CheckButton()
        self.exact_check.connect("toggled", self.toggled)
        ex_box.pack_start(label_ex, True, True, 0)
        ex_box.pack_start(self.exact_check, True, True, 0)

        label_eul = Gtk.Label("Euler")
        eul_box = Gtk.Box()
        self.eul_check = Gtk.CheckButton()
        self.eul_check.connect("toggled", self.toggled)
        eul_box.pack_start(label_eul, True, True, 0)
        eul_box.pack_start(self.eul_check, True, True, 0)

        label_imp = Gtk.Label("Improved Eul")
        imp_box = Gtk.Box()
        self.imp_check = Gtk.CheckButton()
        self.imp_check.connect("toggled", self.toggled)
        imp_box.pack_start(label_imp, True, True, 0)
        imp_box.pack_start(self.imp_check, True, True, 0)

        label_kutta = Gtk.Label("Runge-Kutta  ")
        kutta_box = Gtk.Box()
        self.kutta_check = Gtk.CheckButton()
        self.kutta_check.connect("toggled", self.toggled)
        kutta_box.pack_start(label_kutta, True, True, 0)
        kutta_box.pack_start(self.kutta_check, True, True, 0)

        # global error canvas
        self.gl_canvas = FigureCanvas(self.pl.fig_gl_err)
        self.gl_canvas.set_size_request(800, 600)

        self.toolbar = NavigationToolbar(self.canvas, Gtk)
        self.toolbox = Gtk.Box()
        self.toolbox.add(self.toolbar)

        radio_box = Gtk.Box()
        loc_button = Gtk.RadioButton.new_with_label_from_widget(None, "Local")
        loc_button.connect("toggled", self.radio_toggled, "local", self)
        radio_box.pack_start(loc_button, False, False, 10)

        glob_button = Gtk.RadioButton.new_from_widget(loc_button)
        glob_button.set_label("Global")
        glob_button.connect("toggled", self.radio_toggled, "global", self)
        radio_box.pack_start(glob_button, False, False, 10)

        # Adding entries and buttons to grid
        self.grid.attach(self.equation, 2, 0, 3, 1)
        self.grid.attach(self.button, 4, 2, 1, 1)
        self.grid.attach(self.x_entry, 2, 1, 1, 1)
        self.grid.attach(self.y_entry, 3, 1, 1, 1)
        self.grid.attach(self.step, 2, 2, 1, 1)
        self.grid.attach(self.bound, 4, 1, 1, 1)
        self.grid.attach(self.toolbox, 2, 3, 1, 1)
        self.grid.attach(self.canvas, 2, 4, 3, 4)
        self.grid.attach(self.gl_canvas, 5, 4, 3, 4)
        self.grid.attach(ex_box, 5, 0, 1, 1)
        self.grid.attach(eul_box, 5, 1, 1, 1)
        self.grid.attach(imp_box, 7, 0, 1, 1)
        self.grid.attach(kutta_box, 7, 1, 1, 1)
        self.grid.attach(self.grid_bound, 3, 2, 1, 1)
        self.grid.attach(radio_box, 6, 2, 1, 1)
class MainWindow(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="Numeric methods")
        self.set_border_width(10)
        self.set_size_request(872, 657)
        #self.set_resizable(resizable=False)
        self.pl = PlotGraph()

        # Layout grid
        self.grid = Gtk.Grid(column_homogeneous=True,
                             column_spacing=10,
                             row_spacing=10)
        self.add(self.grid)

        # All entries (equation, x0, y0, step, bound)
        # equation
        self.equation = Gtk.Entry(placeholder_text="write your equation")
        self.equation.set_size_request(357, 47)

        # x0
        self.x_entry = Gtk.Entry(placeholder_text="initial point x")
        self.x_entry.set_size_request(134, 37)

        # y0
        self.y_entry = Gtk.Entry(placeholder_text="initial point y")
        self.y_entry.set_size_request(134, 37)

        # grid
        self.step = Gtk.Entry(placeholder_text="step")
        self.step.set_size_request(134, 37)

        # grid bound
        self.grid_bound = Gtk.Entry(placeholder_text="step bound")
        self.grid_bound.set_size_request(134, 37)

        # bound
        self.bound = Gtk.Entry(placeholder_text="x bound")
        self.bound.set_size_request(134, 37)

        # flag to check which error should be plotted(local or global)
        self.error_flag = False  # when False it means local, when True it means global

        # creating button
        self.button = Gtk.Button(label="PLOT GRAPH")
        self.button.connect("clicked", self.draw)
        self.button.set_size_request(115, 47)

        self.canvas = FigureCanvas(self.pl.fig)
        self.canvas.set_size_request(800, 600)

        # check buttons (for choice of methods that would be plotted)
        ex_box = Gtk.Box()
        label_ex = Gtk.Label("Exact")
        self.exact_check = Gtk.CheckButton()
        self.exact_check.connect("toggled", self.toggled)
        ex_box.pack_start(label_ex, True, True, 0)
        ex_box.pack_start(self.exact_check, True, True, 0)

        label_eul = Gtk.Label("Euler")
        eul_box = Gtk.Box()
        self.eul_check = Gtk.CheckButton()
        self.eul_check.connect("toggled", self.toggled)
        eul_box.pack_start(label_eul, True, True, 0)
        eul_box.pack_start(self.eul_check, True, True, 0)

        label_imp = Gtk.Label("Improved Eul")
        imp_box = Gtk.Box()
        self.imp_check = Gtk.CheckButton()
        self.imp_check.connect("toggled", self.toggled)
        imp_box.pack_start(label_imp, True, True, 0)
        imp_box.pack_start(self.imp_check, True, True, 0)

        label_kutta = Gtk.Label("Runge-Kutta  ")
        kutta_box = Gtk.Box()
        self.kutta_check = Gtk.CheckButton()
        self.kutta_check.connect("toggled", self.toggled)
        kutta_box.pack_start(label_kutta, True, True, 0)
        kutta_box.pack_start(self.kutta_check, True, True, 0)

        # global error canvas
        self.gl_canvas = FigureCanvas(self.pl.fig_gl_err)
        self.gl_canvas.set_size_request(800, 600)

        self.toolbar = NavigationToolbar(self.canvas, Gtk)
        self.toolbox = Gtk.Box()
        self.toolbox.add(self.toolbar)

        radio_box = Gtk.Box()
        loc_button = Gtk.RadioButton.new_with_label_from_widget(None, "Local")
        loc_button.connect("toggled", self.radio_toggled, "local", self)
        radio_box.pack_start(loc_button, False, False, 10)

        glob_button = Gtk.RadioButton.new_from_widget(loc_button)
        glob_button.set_label("Global")
        glob_button.connect("toggled", self.radio_toggled, "global", self)
        radio_box.pack_start(glob_button, False, False, 10)

        # Adding entries and buttons to grid
        self.grid.attach(self.equation, 2, 0, 3, 1)
        self.grid.attach(self.button, 4, 2, 1, 1)
        self.grid.attach(self.x_entry, 2, 1, 1, 1)
        self.grid.attach(self.y_entry, 3, 1, 1, 1)
        self.grid.attach(self.step, 2, 2, 1, 1)
        self.grid.attach(self.bound, 4, 1, 1, 1)
        self.grid.attach(self.toolbox, 2, 3, 1, 1)
        self.grid.attach(self.canvas, 2, 4, 3, 4)
        self.grid.attach(self.gl_canvas, 5, 4, 3, 4)
        self.grid.attach(ex_box, 5, 0, 1, 1)
        self.grid.attach(eul_box, 5, 1, 1, 1)
        self.grid.attach(imp_box, 7, 0, 1, 1)
        self.grid.attach(kutta_box, 7, 1, 1, 1)
        self.grid.attach(self.grid_bound, 3, 2, 1, 1)
        self.grid.attach(radio_box, 6, 2, 1, 1)

    @staticmethod
    def toggled(check_butt):
        if check_butt.get_active():
            print("Active")

    @staticmethod
    def radio_toggled(button, name, activity):
        if button.get_active() and name == "global":
            activity.error_flag = True
        else:
            activity.error_flag = False

    def draw(self, wid):
        # draw the graph on ui, click listener on button 'plot graph'
        self.grid.remove_row(5)

        if self.exact_check.get_active():
            self.pl.ex_flag = True
        else:
            self.pl.ex_flag = False

        if self.kutta_check.get_active():
            self.pl.kutta_flag = True
        else:
            self.pl.kutta_flag = False

        if self.eul_check.get_active():
            self.pl.eul_flag = True
        else:
            self.pl.eul_flag = False

        if self.imp_check.get_active():
            self.pl.imp_flag = True
        else:
            self.pl.imp_flag = False

        # check if method is chosen
        if not self.pl.imp_flag and not self.pl.kutta_flag and not self.pl.eul_flag:
            dialog = Gtk.MessageDialog(
                self, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
                "You should choose at least one method")
            dialog.format_secondary_text("No methods are chosen")
            dialog.run()
            dialog.destroy()
            return

        # user input (some bad code style, need to create function for checking)
        try:
            x0 = float(self.x_entry.get_text())
        except ValueError:
            dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK, "Wrong initial x")
            dialog.format_secondary_text("Your initial x is not a number")
            dialog.run()
            dialog.destroy()
            return

        try:
            y0 = float(self.y_entry.get_text())
        except ValueError:
            dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK, "Wrong initial y")
            dialog.format_secondary_text("Your initial y is not a number")
            dialog.run()
            dialog.destroy()
            return

        try:
            bound = float(self.bound.get_text())
        except ValueError:
            dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK, "Wrong x bound")
            dialog.format_secondary_text("Your x bound is not a number")
            dialog.run()
            dialog.destroy()
            return

        try:
            step = float(self.step.get_text())
        except ValueError:
            dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK, "Wrong step")
            dialog.format_secondary_text("Your step is not a number")
            dialog.run()
            dialog.destroy()
            return

        try:
            step_bound = float(self.grid_bound.get_text())
        except ValueError:
            dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO,
                                       Gtk.ButtonsType.OK, "Wrong step bound")
            dialog.format_secondary_text("Your step bound is not a number")
            dialog.run()
            dialog.destroy()
            return

        equation = str(self.equation.get_text())

        self.pl.init_methods(x0, y0, bound, step, equation)
        self.pl.plot_graph()
        self.canvas = FigureCanvas(self.pl.fig)
        self.canvas.show()

        self.toolbar.canvas = self.canvas
        self.toolbar.show()

        if self.error_flag:
            self.pl.init_global(x0, y0, bound, step, step_bound, equation)
            self.pl.plot_global()
            self.gl_canvas = FigureCanvas(self.pl.fig_global)
            self.gl_canvas.show()
        else:
            self.pl.init_gl_error()
            self.pl.plot_gll_err()
            self.gl_canvas = FigureCanvas(self.pl.fig_gl_err)
            self.gl_canvas.show()

        self.grid.attach(self.canvas, 2, 4, 3, 4)
        self.grid.attach(self.gl_canvas, 5, 4, 3, 4)
        self.show_all()
Beispiel #16
0
 def home(self,*args):
   """This version of 'home' resets the history, instead of just clipping it to
   this view"""
   _NavigationToolbar.home(self,*args)
   self.set_max_ranges()
   self.update() # reset the history...
class CampaignGraph(object):
	title = 'Unknown'
	_graph_id = None
	table_subscriptions = []
	def __init__(self, config, parent, size_request=None):
		self.config = config
		self.parent = parent
		self.figure, ax = pyplot.subplots()
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		if size_request:
			self.canvas.set_size_request(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent)
		self.navigation_toolbar.hide()
		self.popup_menu = Gtk.Menu.new()

		menu_item = Gtk.MenuItem.new_with_label('Export')
		menu_item.connect('activate', self.signal_activate_popup_menu_export)
		self.popup_menu.append(menu_item)

		menu_item = Gtk.MenuItem.new_with_label('Refresh')
		menu_item.connect('activate', lambda action: self.refresh())
		self.popup_menu.append(menu_item)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self.popup_menu.append(menu_item)
		self.popup_menu.show_all()

	@classmethod
	def get_graph_id(klass):
		return klass._graph_id

	def make_window(self):
		if self.manager == None:
			self.manager = FigureManager(self.canvas, 0)
		window = self.manager.window
		window.set_transient_for(self.parent)
		window.set_title(self.title)
		return window

	def mpl_signal_canvas_button_pressed(self, event):
		if event.button != 3:
			return
		pos_func = lambda m, d: (event.x, event.y, True)
		self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time())
		return True

	def signal_activate_popup_menu_export(self, action):
		dialog = gui_utilities.UtilityFileChooser('Export Graph', self.parent)
		file_name = self.config['campaign_name'] + '.png'
		response = dialog.run_quick_save(file_name)
		dialog.destroy()
		if not response:
			return
		destination_file = response['target_filename']
		self.figure.savefig(destination_file, format='png')

	def signal_toggled_popup_menu_show_toolbar(self, widget):
		if widget.get_property('active'):
			self.navigation_toolbar.show()
		else:
			self.navigation_toolbar.hide()

	def load_graph(self):
		self.refresh()

	def refresh(self, info_cache=None):
		info_cache = (info_cache or {})
		if not self.parent.rpc:
			return info_cache
		for table in self.table_subscriptions:
			if not table in info_cache:
				info_cache[table] = list(self.parent.rpc.remote_table('campaign/' + table, self.config['campaign_id']))
		map(lambda ax: ax.clear(), self.axes)
		self._load_graph(info_cache)
		self.canvas.draw()
		return info_cache
Beispiel #18
0
class CampaignGraph(object):
    """
	A basic graph provider for using :py:mod:`matplotlib` to create graph
	representations of campaign data. This class is meant to be subclassed
	by real providers.
	"""
    title = 'Unknown'
    """The title of the graph."""
    _graph_id = None
    table_subscriptions = []
    """A list of tables from which information is needed to produce the graph."""
    def __init__(self, config, parent, size_request=None):
        """
		:param dict config: The King Phisher client configuration.
		:param parent: The parent window for this object.
		:type parent: :py:class:`Gtk.Window`
		:param tuple size_request: The size to set for the canvas.
		"""
        self.config = config
        """A reference to the King Phisher client configuration."""
        self.parent = parent
        """The parent :py:class:`Gtk.Window` instance."""
        self.figure, ax = pyplot.subplots()
        self.axes = self.figure.get_axes()
        self.canvas = FigureCanvas(self.figure)
        self.manager = None
        if size_request:
            self.canvas.set_size_request(*size_request)
        self.canvas.mpl_connect('button_press_event',
                                self.mpl_signal_canvas_button_pressed)
        self.canvas.show()
        self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent)
        self.navigation_toolbar.hide()
        self.popup_menu = Gtk.Menu.new()

        menu_item = Gtk.MenuItem.new_with_label('Export')
        menu_item.connect('activate', self.signal_activate_popup_menu_export)
        self.popup_menu.append(menu_item)

        menu_item = Gtk.MenuItem.new_with_label('Refresh')
        menu_item.connect('activate', lambda action: self.refresh())
        self.popup_menu.append(menu_item)

        menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
        menu_item.connect('toggled',
                          self.signal_toggled_popup_menu_show_toolbar)
        self.popup_menu.append(menu_item)
        self.popup_menu.show_all()

    @classmethod
    def get_graph_id(klass):
        """
		The graph id of an exported :py:class:`.CampaignGraph`.

		:param klass: The class to return the graph id of.
		:type klass: :py:class:`.CampaignGraph`
		:return: The id of the graph.
		:rtype: int
		"""
        return klass._graph_id

    def make_window(self):
        """
		Create a window from the figure manager.

		:return: The graph in a new, dedicated window.
		:rtype: :py:class:`Gtk.Window`
		"""
        if self.manager == None:
            self.manager = FigureManager(self.canvas, 0)
        window = self.manager.window
        window.set_transient_for(self.parent)
        window.set_title(self.title)
        return window

    def mpl_signal_canvas_button_pressed(self, event):
        if event.button != 3:
            return
        self.popup_menu.popup(None, None, None, None, event.button,
                              Gtk.get_current_event_time())
        return True

    def signal_activate_popup_menu_export(self, action):
        dialog = gui_utilities.UtilityFileChooser('Export Graph', self.parent)
        file_name = self.config['campaign_name'] + '.png'
        response = dialog.run_quick_save(file_name)
        dialog.destroy()
        if not response:
            return
        destination_file = response['target_path']
        self.figure.savefig(destination_file, format='png')

    def signal_toggled_popup_menu_show_toolbar(self, widget):
        if widget.get_property('active'):
            self.navigation_toolbar.show()
        else:
            self.navigation_toolbar.hide()

    def load_graph(self):
        """Load the graph information via :py:meth:`.refresh`."""
        self.refresh()

    def refresh(self, info_cache=None, stop_event=None):
        """
		Refresh the graph data by retrieving the information from the
		remote server.

		:param dict info_cache: An optional cache of data tables.
		:param stop_event: An optional object indicating that the operation should stop.
		:type stop_event: :py:class:`threading.Event`
		:return: A dictionary of cached tables from the server.
		:rtype: dict
		"""
        info_cache = (info_cache or {})
        if not self.parent.rpc:
            return info_cache
        for table in self.table_subscriptions:
            if stop_event and stop_event.is_set():
                return info_cache
            if not table in info_cache:
                info_cache[table] = list(
                    self.parent.rpc.remote_table('campaign/' + table,
                                                 self.config['campaign_id']))
        map(lambda ax: ax.clear(), self.axes)
        self._load_graph(info_cache)
        self.canvas.draw()
        return info_cache
Beispiel #19
0
 def pan(self,*args):
   self.disable_all_span_controls()
   _NavigationToolbar.pan(self,*args)
Beispiel #20
0
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()
Beispiel #21
0
 def zoom(self, *args, **kwargs):
     self.pan_button.set_active(False)
     return NavigationToolbar2GTK3.zoom(self, *args, **kwargs)
Beispiel #22
0
class GraphBase(object):
	"""
	A basic graph provider for using :py:mod:`matplotlib` to create graph
	representations of campaign data. This class is meant to be subclassed
	by real providers.
	"""
	name = 'Unknown'
	"""The name of the graph provider."""
	name_human = 'Unknown'
	"""The human readable name of the graph provider used for UI identification."""
	graph_title = 'Unknown'
	"""The title that will be given to the graph."""
	is_available = True
	def __init__(self, application, size_request=None, style_context=None):
		"""
		:param tuple size_request: The size to set for the canvas.
		"""
		self.application = application
		self.style_context = style_context
		self.config = application.config
		"""A reference to the King Phisher client configuration."""
		self.figure, _ = pyplot.subplots()
		self.figure.set_facecolor(self.get_color('bg', ColorHexCode.WHITE))
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		self.minimum_size = (380, 200)
		"""An absolute minimum size for the canvas."""
		if size_request is not None:
			self.resize(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.application.get_active_window())

		self.popup_menu = managers.MenuManager()
		self.popup_menu.append('Export', self.signal_activate_popup_menu_export)
		self.popup_menu.append('Refresh', self.signal_activate_popup_refresh)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self._menu_item_show_toolbar = menu_item
		self.popup_menu.append_item(menu_item)

		self.navigation_toolbar.hide()
		self._legend = None

	@property
	def rpc(self):
		return self.application.rpc

	@staticmethod
	def _ax_hide_ticks(ax):
		for tick in ax.yaxis.get_major_ticks():
			tick.tick1On = False
			tick.tick2On = False

	@staticmethod
	def _ax_set_spine_color(ax, spine_color):
		for pos in ('top', 'right', 'bottom', 'left'):
			ax.spines[pos].set_color(spine_color)

	def add_legend_patch(self, legend_rows, fontsize=None):
		if self._legend is not None:
			self._legend.remove()
			self._legend = None
		fontsize = fontsize or self.fontsize_scale
		legend_bbox = self.figure.legend(
			tuple(patches.Patch(color=patch_color) for patch_color, _ in legend_rows),
			tuple(label for _, label in legend_rows),
			borderaxespad=1.25,
			fontsize=fontsize,
			frameon=True,
			handlelength=1.5,
			handletextpad=0.75,
			labelspacing=0.3,
			loc='lower right'
		)
		legend_bbox.legendPatch.set_linewidth(0)
		self._legend = legend_bbox

	def get_color(self, color_name, default):
		"""
		Get a color by its style name such as 'fg' for foreground. If the
		specified color does not exist, default will be returned. The underlying
		logic for this function is provided by
		:py:func:`~.gui_utilities.gtk_style_context_get_color`.

		:param str color_name: The style name of the color.
		:param default: The default color to return if the specified one was not found.
		:return: The desired color if it was found.
		:rtype: tuple
		"""
		color_name = 'theme_color_graph_' + color_name
		sc_color = gui_utilities.gtk_style_context_get_color(self.style_context, color_name, default)
		return (sc_color.red, sc_color.green, sc_color.blue)

	def make_window(self):
		"""
		Create a window from the figure manager.

		:return: The graph in a new, dedicated window.
		:rtype: :py:class:`Gtk.Window`
		"""
		if self.manager is None:
			self.manager = FigureManager(self.canvas, 0)
		self.navigation_toolbar.destroy()
		self.navigation_toolbar = self.manager.toolbar
		self._menu_item_show_toolbar.set_active(True)
		window = self.manager.window
		window.set_transient_for(self.application.get_active_window())
		window.set_title(self.graph_title)
		return window

	@property
	def fontsize_scale(self):
		scale = self.markersize_scale
		if scale < 5:
			fontsize = 'xx-small'
		elif scale < 7:
			fontsize = 'x-small'
		elif scale < 9:
			fontsize = 'small'
		else:
			fontsize = 'medium'
		return fontsize

	@property
	def markersize_scale(self):
		bbox = self.axes[0].get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
		return bbox.width * self.figure.dpi * 0.01

	def mpl_signal_canvas_button_pressed(self, event):
		if event.button != 3:
			return
		self.popup_menu.menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time())
		return True

	def signal_activate_popup_menu_export(self, action):
		dialog = extras.FileChooserDialog('Export Graph', self.application.get_active_window())
		file_name = self.config['campaign_name'] + '.png'
		response = dialog.run_quick_save(file_name)
		dialog.destroy()
		if not response:
			return
		destination_file = response['target_path']
		self.figure.savefig(destination_file, dpi=200, facecolor=self.figure.get_facecolor(), format='png')

	def signal_activate_popup_refresh(self, event):
		self.refresh()

	def signal_toggled_popup_menu_show_toolbar(self, widget):
		if widget.get_property('active'):
			self.navigation_toolbar.show()
		else:
			self.navigation_toolbar.hide()

	def resize(self, width=0, height=0):
		"""
		Attempt to resize the canvas. Regardless of the parameters the canvas
		will never be resized to be smaller than :py:attr:`.minimum_size`.

		:param int width: The desired width of the canvas.
		:param int height: The desired height of the canvas.
		"""
		min_width, min_height = self.minimum_size
		width = max(width, min_width)
		height = max(height, min_height)
		self.canvas.set_size_request(width, height)
Beispiel #23
0
class CampaignGraph(object):
	"""
	A basic graph provider for using :py:mod:`matplotlib` to create graph
	representations of campaign data. This class is meant to be subclassed
	by real providers.
	"""
	name = 'Unknown'
	"""The name of the graph provider."""
	name_human = 'Unknown'
	"""The human readable name of the graph provider used for UI identification."""
	graph_title = 'Unknown'
	"""The title that will be given to the graph."""
	table_subscriptions = []
	"""A list of tables from which information is needed to produce the graph."""
	is_available = True
	def __init__(self, config, parent, size_request=None):
		"""
		:param dict config: The King Phisher client configuration.
		:param parent: The parent window for this object.
		:type parent: :py:class:`Gtk.Window`
		:param tuple size_request: The size to set for the canvas.
		"""
		self.config = config
		"""A reference to the King Phisher client configuration."""
		self.parent = parent
		"""The parent :py:class:`Gtk.Window` instance."""
		self.figure, _ = pyplot.subplots()
		self.axes = self.figure.get_axes()
		self.canvas = FigureCanvas(self.figure)
		self.manager = None
		if size_request:
			self.canvas.set_size_request(*size_request)
		self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed)
		self.canvas.show()
		self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent)
		self.popup_menu = Gtk.Menu.new()

		menu_item = Gtk.MenuItem.new_with_label('Export')
		menu_item.connect('activate', self.signal_activate_popup_menu_export)
		self.popup_menu.append(menu_item)

		menu_item = Gtk.MenuItem.new_with_label('Refresh')
		menu_item.connect('activate', lambda action: self.refresh())
		self.popup_menu.append(menu_item)

		menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar')
		menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar)
		self._menu_item_show_toolbar = menu_item
		self.popup_menu.append(menu_item)
		self.popup_menu.show_all()
		self.navigation_toolbar.hide()

	def _load_graph(self, info_cache):
		raise NotImplementedError()

	def _graph_bar_set_yparams(self, top_lim):
		min_value = top_lim + (top_lim * 0.075)
		if min_value <= 25:
			scale = 5
		else:
			scale = scale = 10 ** (len(str(int(min_value))) - 1)
		inc_scale = scale
		while scale <= min_value:
			scale += inc_scale
		top_lim = scale

		ax = self.axes[0]
		yticks = set((round(top_lim * 0.5), top_lim))
		ax.set_yticks(tuple(yticks))
		ax.set_ylim(top=top_lim)
		return

	def _graph_null_pie(self, title):
		ax = self.axes[0]
		ax.pie((100,), labels=(title,), colors=(MPL_COLOR_NULL,), autopct='%1.0f%%', shadow=True, startangle=90)
		ax.axis('equal')
		return

	def add_legend_patch(self, legend_rows, fontsize=None):
		handles = []
		if not fontsize:
			scale = self.markersize_scale
			if scale < 5:
				fontsize = 'xx-small'
			elif scale < 7:
				fontsize = 'x-small'
			elif scale < 9:
				fontsize = 'small'
			else:
				fontsize = 'medium'
		for row in legend_rows:
			handles.append(patches.Patch(color=row[0], label=row[1]))
		self.axes[0].legend(handles=handles, fontsize=fontsize, loc='lower right')

	def graph_bar(self, bars, color=None, xticklabels=None, ylabel=None):
		"""
		Create a standard bar graph with better defaults for the standard use
		cases.

		:param list bars: The values of the bars to graph.
		:param color: The color of the bars on the graph.
		:type color: list, str
		:param list xticklabels: The labels to use on the x-axis.
		:param str ylabel: The label to give to the y-axis.
		:return: The bars created using :py:mod:`matplotlib`
		:rtype: `matplotlib.container.BarContainer`
		"""
		color = color or MPL_COLOR_NULL
		width = 0.25
		ax = self.axes[0]
		self._graph_bar_set_yparams(max(bars) if bars else 0)
		bars = ax.bar(range(len(bars)), bars, width, color=color)
		ax.set_xticks([float(x) + (width / 2) for x in range(len(bars))])
		if xticklabels:
			ax.set_xticklabels(xticklabels, rotation=30)
			for col in bars:
				height = col.get_height()
				ax.text(col.get_x() + col.get_width() / 2.0, height, "{0:,}".format(height), ha='center', va='bottom')
		if ylabel:
			ax.set_ylabel(ylabel)
		self.figure.subplots_adjust(bottom=0.25)
		return bars

	def make_window(self):
		"""
		Create a window from the figure manager.

		:return: The graph in a new, dedicated window.
		:rtype: :py:class:`Gtk.Window`
		"""
		if self.manager == None:
			self.manager = FigureManager(self.canvas, 0)
		self.navigation_toolbar.destroy()
		self.navigation_toolbar = self.manager.toolbar
		self._menu_item_show_toolbar.set_active(True)
		window = self.manager.window
		window.set_transient_for(self.parent)
		window.set_title(self.graph_title)
		return window

	@property
	def markersize_scale(self):
		bbox = self.axes[0].get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
		return max(bbox.width, bbox.width) * self.figure.dpi * 0.01

	def mpl_signal_canvas_button_pressed(self, event):
		if event.button != 3:
			return
		self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time())
		return True

	def signal_activate_popup_menu_export(self, action):
		dialog = gui_utilities.FileChooser('Export Graph', self.parent)
		file_name = self.config['campaign_name'] + '.png'
		response = dialog.run_quick_save(file_name)
		dialog.destroy()
		if not response:
			return
		destination_file = response['target_path']
		self.figure.savefig(destination_file, format='png')

	def signal_toggled_popup_menu_show_toolbar(self, widget):
		if widget.get_property('active'):
			self.navigation_toolbar.show()
		else:
			self.navigation_toolbar.hide()

	def load_graph(self):
		"""Load the graph information via :py:meth:`.refresh`."""
		self.refresh()

	def refresh(self, info_cache=None, stop_event=None):
		"""
		Refresh the graph data by retrieving the information from the
		remote server.

		:param dict info_cache: An optional cache of data tables.
		:param stop_event: An optional object indicating that the operation should stop.
		:type stop_event: :py:class:`threading.Event`
		:return: A dictionary of cached tables from the server.
		:rtype: dict
		"""
		info_cache = (info_cache or {})
		if not self.parent.rpc:
			return info_cache
		for table in self.table_subscriptions:
			if stop_event and stop_event.is_set():
				return info_cache
			if not table in info_cache:
				info_cache[table] = tuple(self.parent.rpc.remote_table('campaign/' + table, self.config['campaign_id']))
		for ax in self.axes:
			ax.clear()
		self._load_graph(info_cache)
		self.axes[0].set_title(self.graph_title, y=1.03)
		self.canvas.draw()
		return info_cache
Beispiel #24
0
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()