diff --git a/development/add_copyright_notice.py b/development/add_copyright_notice.py
index 95fff49..1d0905c 100644
--- a/development/add_copyright_notice.py
+++ b/development/add_copyright_notice.py
@@ -12,7 +12,7 @@
if file.endswith(".py"):
print(file)
full_filename = os.path.join(root, file)
- with open(full_filename+".tmp", "w") as fp2:
+ with open(full_filename + ".tmp", "w") as fp2:
fp2.write(notice.format(file))
with open(full_filename, "r") as fp1:
start = False
@@ -23,4 +23,4 @@
fp2.write(line)
continue
- shutil.move(full_filename+".tmp", full_filename)
+ shutil.move(full_filename + ".tmp", full_filename)
diff --git a/development/raise_version_number.py b/development/raise_version_number.py
index 9d68fa3..89c9a06 100644
--- a/development/raise_version_number.py
+++ b/development/raise_version_number.py
@@ -22,7 +22,7 @@
import os
import sys
-from release_tools import replace_version, get_setup_properties
+from release_tools import get_setup_properties, replace_version
properties = get_setup_properties()
@@ -43,13 +43,13 @@
print(f"setting {package_name} version number from {current_version} to {new_version}")
-files = ["setup.py", "meta.yaml", "docs/conf.py", package_name+"/__init__.py"]
+files = ["setup.py", "meta.yaml", "docs/conf.py", package_name + "/__init__.py"]
# Let's go
for file in files:
if replace_version(file, current_version, new_version):
os.system(f"git add {file}")
-
+
# commit changes
-os.system("git commit -m \"set version to v%s\"" % new_version)
-os.system("git tag \"v%s\"" % new_version)
+os.system(f"git commit -m \"set version to v{new_version}\"")
+os.system(f"git tag \"v{new_version}\"")
diff --git a/development/send_to_pypi.py b/development/send_to_pypi.py
index a27437d..5f90aff 100644
--- a/development/send_to_pypi.py
+++ b/development/send_to_pypi.py
@@ -19,7 +19,8 @@
# You should have received a copy of the GNU General Public License
# along with Pylustrator. If not, see
-from __future__ import print_function, division
+from __future__ import division, print_function
+
import os
import sys
@@ -34,7 +35,7 @@
current_version = pylustrator.__version__
- from optparse import OptionParser
+ from argparse import OptionParser
parser = OptionParser()
parser.add_option("-u", "--username", action="store", dest="username")
@@ -48,13 +49,13 @@
os.system("python setup.py sdist")
# the command
- command_string = "twine upload dist/pylustrator-%s.tar.gz" % current_version
+ command_string = f"twine upload dist/pylustrator-{current_version}.tar.gz"
# optionally add the username
if options.username:
- command_string += " --username %s" % options.username
+ command_string += f" --username {options.username}"
# optionally add the password
if options.password:
- command_string += " --password %s" % options.password
+ command_string += f" --password {options.password}"
# print the command string
print(command_string)
# and execute it
diff --git a/docs/conf.py b/docs/conf.py
index 3b7ed8a..dc40dbf 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -19,9 +19,9 @@
# You should have received a copy of the GNU General Public License
# along with Pylustrator. If not, see
-import sys
import os
import shlex
+import sys
print(os.getcwd())
@@ -31,6 +31,7 @@
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
import mock
+
# try to import the modules of the package and mock everything that is not found
while True:
try:
@@ -51,7 +52,7 @@
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
@@ -73,15 +74,15 @@
source_suffix = '.rst'
# The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
-project = u'pylustrator'
-copyright = u'2018, Richard Gerum'
-author = u'Richard Gerum'
+project = 'pylustrator'
+copyright = '2018, Richard Gerum'
+author = 'Richard Gerum'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -101,9 +102,9 @@
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
-#today = ''
+# today = ''
# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@@ -111,27 +112,27 @@
# The reST default role (used for this markup: `text`) to use for all
# documents.
-#default_role = None
+# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
-#show_authors = False
+# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
+# keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
@@ -146,96 +147,96 @@
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
-#html_theme = 'alabaster'
+# html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
-#html_theme_options = {}
+# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
-#html_title = None
+# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
-#html_logo = None
+# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
-#html_favicon = None
+# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-#html_static_path = ['_static']
+# html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
-#html_extra_path = []
+# html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
# If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
# If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
# If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
-#html_search_language = 'en'
+# html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
-#html_search_options = {'type': 'default'}
+# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
-#html_search_scorer = 'scorer.js'
+# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'pylustratordoc'
@@ -244,45 +245,45 @@
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
+# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
+# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
-#'preamble': '',
+# 'preamble': '',
# Latex figure (float) alignment
-#'figure_align': 'htbp',
+# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
- (master_doc, 'pylustrator.tex', u'pylustrator Documentation',
- u'Richard Gerum', 'manual'),
+ (master_doc, 'pylustrator.tex', 'pylustrator Documentation',
+ 'Richard Gerum', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
-#latex_logo = None
+# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
# If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
# If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
# Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
# If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
@@ -290,12 +291,12 @@
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- (master_doc, 'pylustrator', u'Pylustrator Documentation',
- [author], 1)
+ (master_doc, 'pylustrator', 'Pylustrator Documentation',
+ [author], 1),
]
# If true, show URL addresses after external links.
-#man_show_urls = False
+# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
@@ -304,19 +305,19 @@
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- (master_doc, 'pylustrator', u'Pylustrator Documentation',
+ (master_doc, 'pylustrator', 'Pylustrator Documentation',
author, 'pylustrator', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
# If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
+# texinfo_no_detailmenu = False
diff --git a/docs/example_pylustrator.py b/docs/example_pylustrator.py
index cab654f..f46edef 100644
--- a/docs/example_pylustrator.py
+++ b/docs/example_pylustrator.py
@@ -1,11 +1,12 @@
# import matplotlib and numpy as usual
+import sys
import matplotlib.pyplot as plt
import numpy as np
+from icecream import install
# now import pylustrator
import pylustrator
-from icecream import install
install()
import matplotlib.pyplot as plt
import numpy as np
@@ -21,9 +22,9 @@
np.random.seed(1)
t = np.arange(0.0, 2, 0.001)
y = 2 * np.sin(np.pi * t)
- a, b = np.random.normal(loc=(5., 3.), scale=(2., 4.), size=(100,2)).T
+ a, b = np.random.normal(loc=(5., 3.), scale=(2., 4.), size=(100, 2)).T
b += a
-
+
fig = plt.figure(1)
plt.clf()
fig.text(0.5, 0.5, "new", transform=plt.figure(1).transFigure)
@@ -38,13 +39,13 @@
plt.subplot(133)
plt.bar(0, np.mean(a), label="a")
plt.bar(1, np.mean(b), label="b")
- #plt.legend()
+ # plt.legend()
plt.xticks
plt.figure(1).axes[0].set(position=[0.125, 0.11, 0.2279, 0.77], xlim=(-0.09995, 3.), xlabel='blaa', xticks=[0., 1., 2., 3.], xticklabels=['0', '1', '2', '3'], ylim=(-3., 3.), ylabel='fooo', yticks=[-3., -2., -1., 0., 1., 2., 3.], yticklabels=['−3', '−2', '−1', '0', '1', '2', '3'])
plt.figure(1).axes[0].spines[['right', 'top']].set_visible(False)
- #% start: automatic generated code from pylustrator
+ # % start: automatic generated code from pylustrator
plt.figure(1).ax_dict = {ax.get_label(): ax for ax in plt.figure(1).axes}
import matplotlib as mpl
getattr(plt.figure(1), '_pylustrator_init', lambda: ...)()
@@ -54,9 +55,9 @@
plt.figure(1).axes[1].spines[['right', 'top']].set_visible(False)
plt.figure(1).axes[2].set(ylim=(0., 8.82))
plt.figure(1).axes[2].spines[['right', 'top']].set_visible(False)
- #% end: automatic generated code from pylustrator
+ # % end: automatic generated code from pylustrator
plt.show()
- exit()
+ sys.exit()
# activate pylustrator
pylustrator.start()
@@ -64,7 +65,7 @@
np.random.seed(1)
t = np.arange(0.0, 2, 0.001)
y = 2 * np.sin(np.pi * t)
-a, b = np.random.normal(loc=(5., 3.), scale=(2., 4.), size=(100,2)).T
+a, b = np.random.normal(loc=(5., 3.), scale=(2., 4.), size=(100, 2)).T
b += a
plt.figure(1)
@@ -83,10 +84,9 @@
plt.bar(1, np.mean(b), label="B")
-#plt.figure(1).axes[0].set(position=[0.213022, 0.498889, 0.227941, 0.381111], xlim=[0.7, 1448], xticks=[0.1, 0.001], xticklabels=["A", "B"])
-#for name in dir(plt.figure(1).axes[0]):
+# plt.figure(1).axes[0].set(position=[0.213022, 0.498889, 0.227941, 0.381111], xlim=[0.7, 1448], xticks=[0.1, 0.001], xticklabels=["A", "B"])
+# for name in dir(plt.figure(1).axes[0]):
# if name.startswith("set_"):
# print(name)
plt.show()
-
diff --git a/docs/figure1.py b/docs/figure1.py
index 41d11cd..fc65922 100644
--- a/docs/figure1.py
+++ b/docs/figure1.py
@@ -1,4 +1,5 @@
import matplotlib.pyplot as plt
+
import pylustrator
pylustrator.load("plot1.py")
diff --git a/pylustrator/QLinkableWidgets.py b/pylustrator/QLinkableWidgets.py
index 12d65ab..a3ad9dc 100644
--- a/pylustrator/QLinkableWidgets.py
+++ b/pylustrator/QLinkableWidgets.py
@@ -23,19 +23,19 @@
import matplotlib as mpl
import matplotlib.pyplot as plt
-import matplotlib.transforms as transforms
+from matplotlib import transforms
import numpy as np
from matplotlib.artist import Artist
-from matplotlib.figure import Figure
-from matplotlib.text import Text
from matplotlib.axes import Axes
from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
+from matplotlib.figure import Figure
+from matplotlib.text import Text
+
from .helper_functions import main_figure
class Linkable:
- """ a class that automatically links a widget with the property of a matplotlib artist
- """
+ """A class that automatically links a widget with the property of a matplotlib artist."""
def link(self, property_name: str, signal: QtCore.Signal = None, condition: callable = None, direct: bool = False):
self.element = None
@@ -61,11 +61,11 @@ def set(v):
self.setLinkedProperty = set
self.getLinkedProperty = get
- self.serializeLinkedProperty = lambda x: "." + property_name + " = %s" % x
+ self.serializeLinkedProperty = lambda x: "." + property_name + f" = {x}"
else:
def set(v, v_list=None):
if v_list is None:
- v = [v]+[v]*len(main_figure(self.element).selection.targets)
+ v = [v] + [v] * len(main_figure(self.element).selection.targets)
else:
v = v_list
@@ -74,10 +74,10 @@ def set(v, v_list=None):
if isinstance(self.element, Text) and len(main_figure(self.element).selection.targets) and isinstance(main_figure(self.element).selection.targets[0].target, Axes):
for elm in main_figure(self.element).selection.targets:
elm = elm.target
- if self.element == getattr(getattr(elm, f"get_xaxis")(), "get_label")():
+ if self.element == getattr(getattr(elm, "get_xaxis")(), "get_label")():
label_object = "x"
break
- if self.element == getattr(getattr(elm, f"get_yaxis")(), "get_label")():
+ if self.element == getattr(getattr(elm, "get_yaxis")(), "get_label")():
label_object = "y"
break
@@ -105,10 +105,10 @@ def getAll():
if isinstance(self.element, Text) and len(main_figure(self.element).selection.targets) and isinstance(main_figure(self.element).selection.targets[0].target, Axes):
for elm in main_figure(self.element).selection.targets:
elm = elm.target
- if self.element == getattr(getattr(elm, f"get_xaxis")(), "get_label")():
+ if self.element == getattr(getattr(elm, "get_xaxis")(), "get_label")():
label_object = "x"
break
- if self.element == getattr(getattr(elm, f"get_yaxis")(), "get_label")():
+ if self.element == getattr(getattr(elm, "get_yaxis")(), "get_label")():
label_object = "y"
break
@@ -128,7 +128,7 @@ def getAll():
self.setLinkedProperty = set # lambda text: getattr(self.element, "set_"+property_name)(text)
self.getLinkedProperty = lambda: getattr(self.element, "get_" + property_name)()
self.getLinkedPropertyAll = getAll
- self.serializeLinkedProperty = lambda x: ".set_" + property_name + "(%s)" % x
+ self.serializeLinkedProperty = lambda x: ".set_" + property_name + f"({x})"
if condition is None:
self.condition = lambda x: True
@@ -139,7 +139,7 @@ def getAll():
signal.connect(self.setTarget)
def setTarget(self, element: Artist):
- """ set the target for the widget """
+ """Set the target for the widget."""
self.element = element
try:
self.set(self.getLinkedProperty())
@@ -150,7 +150,7 @@ def setTarget(self, element: Artist):
self.show()
def updateLink(self):
- """ update the linked property """
+ """Update the linked property."""
old_value = self.getLinkedPropertyAll()
try:
@@ -175,6 +175,7 @@ def undo():
for elem, property_name, value in old_value:
getattr(elem, "set_" + property_name, None)(value)
save_change(elem)
+
def redo():
for elem, property_name, value in new_value:
getattr(elem, "set_" + property_name, None)(value)
@@ -191,15 +192,15 @@ def redo():
main_figure(self.element).signals.figure_selection_property_changed.emit()
def set(self, value):
- """ set the value (to be overloaded) """
+ """Set the value (to be overloaded)."""
pass
def get(self):
- """ get the value """
- return None
+ """Get the value."""
+ return
def getSerialized(self):
- """ serialize the value for saving as a command """
+ """Serialize the value for saving as a command."""
return ""
@@ -217,11 +218,11 @@ def __init__(self):
valueChanged : a signal that is emitted when the value is changed by the user
"""
QtWidgets.QLineEdit.__init__(self)
- #self.setMaximumWidth(50)
+ # self.setMaximumWidth(50)
self.textChanged.connect(self.emitValueChanged)
def emitValueChanged(self):
- """ connected to the textChanged signal """
+ """Connected to the textChanged signal."""
if self.send_signal:
try:
value = self.value()
@@ -232,7 +233,7 @@ def emitValueChanged(self):
pass
def value(self) -> Optional[float]:
- """ return the value of the input field """
+ """Return the value of the input field."""
try:
return float(self.text())
except ValueError:
@@ -242,7 +243,7 @@ def value(self) -> Optional[float]:
return None
def setValue(self, value: float):
- """ set the value of the input field """
+ """Set the value of the input field."""
self.send_signal = False
try:
self.setText(str(value))
@@ -259,7 +260,7 @@ class DimensionsWidget(QtWidgets.QWidget, Linkable):
noSignal = False
def __init__(self, layout: QtWidgets.QLayout, text: str, join: str, unit: str, free: bool = False):
- """ a widget that lets the user input a pair of dimensions (e.g. widh and height)
+ """A widget that lets the user input a pair of dimensions (e.g. widh and height).
Args:
layout: the layout to which to add the widget
@@ -306,37 +307,37 @@ def __init__(self, layout: QtWidgets.QLayout, text: str, join: str, unit: str, f
self.editingFinished = self.valueChanged
def setLabel(self, text: str):
- """ set the text of the label """
+ """ Set the text of the label. """
self.text.setText(text)
def setUnit(self, unit: str):
- """ Sets the text for the unit for the values """
+ """ Sets the text for the unit for the values. """
self.input1.setSuffix(" " + unit)
self.input2.setSuffix(" " + unit)
def setTransform(self, transform: mpl.transforms.Transform):
- """ set the transform for the units """
+ """ Set the transform for the units. """
self.transform = transform
def onValueChangedX(self):
- """ called when the value was changed -> emit the value changed signal """
+ """ Called when the value was changed -> emit the value changed signal. """
if not self.noSignal:
self.valueChangedX.emit(self.value()[0])
self.valueChanged.emit(tuple(self.value()))
def onValueChangedY(self):
- """ called when the value was changed -> emit the value changed signal """
+ """ Called when the value was changed -> emit the value changed signal. """
if not self.noSignal:
self.valueChangedY.emit(self.value()[1])
self.valueChanged.emit(tuple(self.value()))
def onValueChanged(self):
- """ called when the value was changed -> emit the value changed signal """
+ """ Called when the value was changed -> emit the value changed signal. """
if not self.noSignal:
self.valueChanged.emit(tuple(self.value()))
def setValue(self, values: tuple, signal=False):
- """ set the two values """
+ """ Set the two values. """
self.noSignal = True
if self.transform:
values = self.transform.transform(values)
@@ -347,22 +348,22 @@ def setValue(self, values: tuple, signal=False):
self.onValueChanged()
def value(self):
- """ get the value """
+ """ Get the value. """
tuple = (self.input1.value(), self.input2.value())
if self.transform:
tuple = self.transform.inverted().transform(tuple)
return tuple
def get(self) -> tuple:
- """ get the value (used for the Linkable parent class) """
+ """ Get the value (used for the Linkable parent class). """
return self.value()
def set(self, value: tuple):
- """ set both values (used for the Linkable parent class) """
+ """ Set both values (used for the Linkable parent class). """
self.setValue(value)
def getSerialized(self) -> str:
- """ serialize the values """
+ """ Serialize the values. """
return ", ".join([str(i) for i in self.get()])
@@ -372,7 +373,7 @@ class TextWidget(QtWidgets.QWidget, Linkable):
last_text = None
def __init__(self, layout: QtWidgets.QLayout, text: str, multiline: bool = False, horizontal: bool = True, allow_literal_decoding=False):
- """ a text input widget with a label.
+ """ A text input widget with a label.
Args:
layout: the layout to which to add the widget
@@ -402,16 +403,16 @@ def __init__(self, layout: QtWidgets.QLayout, text: str, multiline: bool = False
self.layout.addWidget(self.input1)
def valueChangeEvent(self):
- """ an event that is triggered when the text in the input field is changed """
+ """ An event that is triggered when the text in the input field is changed. """
if not self.noSignal and self.input1.text() != self.last_text:
self.editingFinished.emit()
def setLabel(self, text: str):
- """ set the text of the label """
+ """ Set the text of the label. """
self.label.setLabel(text)
def setText(self, text: str, signal=False):
- """ set contents of the text input widget """
+ """ Set contents of the text input widget. """
self.noSignal = True
text = text.replace("\n", "\\n")
self.last_text = text
@@ -424,13 +425,13 @@ def setText(self, text: str, signal=False):
self.editingFinished.emit()
def text(self) -> str:
- """ return the text """
+ """ Return the text. """
text = self.input1.text()
return text.replace("\\n", "\n")
def get(self) -> str:
import ast
- """ get the value (used for the Linkable parent class) """
+ """ Get the value (used for the Linkable parent class). """
if self.allow_literal_decoding:
try:
return ast.literal_eval(self.text())
@@ -439,13 +440,14 @@ def get(self) -> str:
return self.text()
def set(self, value: str):
- """ set the value (used for the Linkable parent class) """
+ """ Set the value (used for the Linkable parent class). """
self.setText(str(value))
def getSerialized(self) -> str:
- """ serialize the value (used for the Linkable parent class) """
+ """ Serialize the value (used for the Linkable parent class). """
return "\"" + str(self.get()) + "\""
+
class NumberWidget(QtWidgets.QWidget, Linkable):
editingFinished = QtCore.Signal()
noSignal = False
@@ -477,16 +479,16 @@ def __init__(self, layout: QtWidgets.QLayout, text: str, min: float = None, use_
self.layout.addWidget(self.input1)
def valueChangeEvent(self):
- """ when the value of the spin box changes """
+ """ When the value of the spin box changes. """
if not self.noSignal:
self.editingFinished.emit()
def setLabel(self, text: str):
- """ set the text label """
+ """ Set the text label. """
self.label.setLabel(text)
def setValue(self, text: float, signal=False):
- """ set the value of the spin box """
+ """ Set the value of the spin box. """
self.noSignal = True
self.input1.setValue(text)
self.noSignal = False
@@ -494,20 +496,20 @@ def setValue(self, text: float, signal=False):
self.editingFinished.emit()
def value(self) -> float:
- """ get the value of the spin box """
+ """ Get the value of the spin box. """
text = self.input1.value()
return text
def get(self) -> float:
- """ get the value (used for the Linkable parent class) """
+ """ Get the value (used for the Linkable parent class). """
return self.value()
def set(self, value: float):
- """ set the value (used for the Linkable parent class) """
+ """ Set the value (used for the Linkable parent class). """
self.setValue(value)
def getSerialized(self) -> str:
- """ serialize the value (used for the Linkable parent class) """
+ """ Serialize the value (used for the Linkable parent class). """
return str(self.get())
@@ -516,7 +518,7 @@ class ComboWidget(QtWidgets.QWidget, Linkable):
noSignal = False
def __init__(self, layout: QtWidgets.QLayout, text: str, values: Sequence):
- """ A combo box widget with a label
+ """ A combo box widget with a label.
Args:
layout: the layout to which to add the widget
@@ -540,16 +542,16 @@ def __init__(self, layout: QtWidgets.QLayout, text: str, values: Sequence):
self.layout.addWidget(self.input1)
def valueChangeEvent(self):
- """ called when the value has changed """
+ """ Called when the value has changed. """
if not self.noSignal:
self.editingFinished.emit()
def setLabel(self, text: str):
- """ set the text of the label """
+ """ Set the text of the label. """
self.label.setLabel(text)
def setText(self, text: str, signal=False):
- """ set the value of the combo box """
+ """ Set the value of the combo box. """
self.noSignal = True
index = self.values.index(text)
self.input1.setCurrentIndex(index)
@@ -558,20 +560,20 @@ def setText(self, text: str, signal=False):
self.editingFinished.emit()
def text(self) -> str:
- """ get the value of the combo box """
+ """ Get the value of the combo box. """
index = self.input1.currentIndex()
return self.values[index]
def get(self) -> str:
- """ get the value (used for the Linkable parent class) """
+ """ Get the value (used for the Linkable parent class). """
return self.text()
def set(self, value: str):
- """ set the value (used for the Linkable parent class) """
+ """ Set the value (used for the Linkable parent class). """
self.setText(value)
def getSerialized(self) -> str:
- """ serialize the value (used for the Linkable parent class) """
+ """ Serialize the value (used for the Linkable parent class). """
return "\"" + str(self.get()) + "\""
@@ -581,7 +583,7 @@ class CheckWidget(QtWidgets.QWidget, Linkable):
noSignal = False
def __init__(self, layout: QtWidgets.QLabel, text: str):
- """ a widget that contains a checkbox with a label
+ """ A widget that contains a checkbox with a label.
Args:
layout: the layout to which to add the widget
@@ -600,13 +602,13 @@ def __init__(self, layout: QtWidgets.QLabel, text: str):
self.layout.addWidget(self.input1)
def onStateChanged(self):
- """ when the state of the checkbox changes """
+ """ When the state of the checkbox changes. """
if not self.noSignal:
self.stateChanged.emit(self.input1.isChecked())
self.editingFinished.emit()
def setChecked(self, state: bool, signal=False):
- """ set the value of the check box """
+ """ Set the value of the check box. """
self.noSignal = True
self.input1.setChecked(state)
self.noSignal = False
@@ -615,19 +617,19 @@ def setChecked(self, state: bool, signal=False):
self.editingFinished.emit()
def isChecked(self) -> bool:
- """ get the value of the checkbox """
+ """ Get the value of the checkbox. """
return self.input1.isChecked()
def get(self) -> bool:
- """ set the value (used for the Linkable parent class) """
+ """ Set the value (used for the Linkable parent class). """
return self.isChecked()
def set(self, value: bool):
- """ get the value (used for the Linkable parent class) """
+ """ Get the value (used for the Linkable parent class). """
self.setChecked(value)
def getSerialized(self) -> str:
- """ serialize the value (used for the Linkable parent class) """
+ """ Serialize the value (used for the Linkable parent class). """
return "True" if self.get() else "False"
@@ -636,7 +638,7 @@ class RadioWidget(QtWidgets.QWidget):
noSignal = False
def __init__(self, layout: QtWidgets.QLayout, texts: Sequence[str]):
- """ a group of radio buttons
+ """ A group of radio buttons.
Args:
layout: the layout to which to add the widget
@@ -659,14 +661,14 @@ def __init__(self, layout: QtWidgets.QLayout, texts: Sequence[str]):
self.radio_buttons[0].setChecked(True)
def onToggled(self, checked: int):
- """ called when a radio button is toggled """
+ """ Called when a radio button is toggled. """
if checked:
self.checked = np.argmax([radio.isChecked() for radio in self.radio_buttons])
if not self.noSignal:
self.stateChanged.emit(self.checked, self.texts[self.checked])
def setState(self, state: int):
- """ set the state of the widget """
+ """ Set the state of the widget. """
self.noSignal = True
for index, radio in enumerate(self.radio_buttons):
radio.setChecked(state == index)
@@ -674,7 +676,7 @@ def setState(self, state: int):
self.noSignal = False
def getState(self) -> int:
- """ get the state of the widget """
+ """ Get the state of the widget. """
return self.checked
@@ -682,7 +684,7 @@ class QColorWidget(QtWidgets.QWidget, Linkable):
valueChanged = QtCore.Signal(str)
def __init__(self, layout: QtWidgets.QLayout, text: str = None, value: str = None):
- """ A colored button what acts as an color input
+ """ A colored button what acts as an color input.
Args:
layout: the layout to which to add the widget
@@ -711,7 +713,7 @@ def __init__(self, layout: QtWidgets.QLayout, text: str = None, value: str = Non
self.editingFinished = self.valueChanged
def changeEvent(self, event):
- """ when the widget is enabled """
+ """ When the widget is enabled. """
if event.type() == QtCore.QEvent.EnabledChange:
if not self.isEnabled():
self.button.setStyleSheet("background-color: #f0f0f0;")
@@ -719,7 +721,7 @@ def changeEvent(self, event):
self.setColor(self.color)
def OpenDialog(self):
- """ open a color chooser dialog """
+ """ Open a color chooser dialog. """
# get new color from color picker
self.current_color = QtGui.QColor(*tuple(int(x) for x in mpl.colors.to_rgba_array(self.getColor())[0] * 255))
self.dialog = QtWidgets.QColorDialog(self.current_color, self.parent())
@@ -731,33 +733,33 @@ def OpenDialog(self):
self.dialog.rejected.connect(self.dialog_rejected)
def dialog_rejected(self):
- """ called when the dialog is cancelled """
+ """ Called when the dialog is cancelled. """
color = self.current_color
- color = color.name() + "%0.2x" % color.alpha()
+ color = color.name() + f"{color.alpha():002x}"
self.setColor(color)
self.valueChanged.emit(self.color)
def dialog_changed(self):
- """ called when the value in the dialog changes """
+ """ Called when the value in the dialog changes. """
color = self.dialog.currentColor()
# if a color is set, apply it
if color.isValid():
- color = color.name() + "%0.2x" % color.alpha()
+ color = color.name() + f"{color.alpha():002x}"
self.setColor(color)
self.valueChanged.emit(self.color)
def dialog_finished(self):
- """ called when the dialog is finished with a click on 'ok' """
+ """ Called when the dialog is finished with a click on 'ok'. """
color = self.dialog.selectedColor()
self.dialog = None
# if a color is set, apply it
if color.isValid():
- color = color.name() + "%0.2x" % color.alpha()
+ color = color.name() + f"{color.alpha():002x}"
self.setColor(color)
self.valueChanged.emit(self.color)
def setColor(self, value: str):
- """ set the color """
+ """ Set the color. """
# display and save the new color
if value is None:
value = "#FF0000FF"
@@ -766,28 +768,28 @@ def setColor(self, value: str):
self.button.setStyleSheet("background-color: rgba(%d, %d, %d, %d%%);" % (
int(value[1:3], 16), int(value[3:5], 16), int(value[5:7], 16), int(value[7:], 16) * 100 / 255))
else:
- self.button.setStyleSheet("background-color: %s;" % (value,))
+ self.button.setStyleSheet(f"background-color: {value};")
self.color = value
def getColor(self) -> str:
- """ get the color value """
+ """ Get the color value. """
# return the color
return self.color
def get(self):
- """ get the value (used for the Linkable parent class) """
+ """ Get the value (used for the Linkable parent class). """
return self.getColor()
def set(self, value):
- """ set the value (used for the Linkable parent class) """
+ """ Set the value (used for the Linkable parent class). """
try:
if len(value) == 4:
- self.setColor(mpl.colors.to_hex(value) + "%02X" % int(value[-1] * 255))
+ self.setColor(mpl.colors.to_hex(value) + f"{int(value[-1] * 255):02X}")
else:
self.setColor(mpl.colors.to_hex(value))
except ValueError:
self.setColor(None)
def getSerialized(self) -> str:
- """ serialize the value (used for the Linkable parent class) """
+ """ Serialize the value (used for the Linkable parent class). """
return "\"" + self.color + "\""
diff --git a/pylustrator/QtGui.py b/pylustrator/QtGui.py
index c1213a5..ddea4f9 100644
--- a/pylustrator/QtGui.py
+++ b/pylustrator/QtGui.py
@@ -19,24 +19,28 @@
# You should have received a copy of the GNU General Public License
# along with Pylustrator. If not, see
+import matplotlib.pyplot as plt
+import numpy as np
from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
-import numpy as np
-import matplotlib.pyplot as plt
try: # for matplotlib > 3.0
- from matplotlib.backends.backend_qtagg import (FigureCanvas, FigureManager, NavigationToolbar2QT as NavigationToolbar)
+ from matplotlib.backends.backend_qtagg import FigureCanvas, FigureManager
+ from matplotlib.backends.backend_qtagg import \
+ NavigationToolbar2QT as NavigationToolbar
except ModuleNotFoundError:
from matplotlib.backends.backend_qt5agg import (FigureCanvas, FigureManager, NavigationToolbar2QT as NavigationToolbar)
-from pylustrator.components.matplotlibwidget import MatplotlibWidget
-from matplotlib import _pylab_helpers
-from matplotlib.figure import Figure
-from matplotlib.artist import Artist
+
+import sys
+
import matplotlib as mpl
import qtawesome as qta
+from matplotlib import _pylab_helpers
+from matplotlib.artist import Artist
+from matplotlib.figure import Figure
-from .QtShortCuts import QDragableColor
+from pylustrator.components.matplotlibwidget import MatplotlibWidget
-import sys
+from .QtShortCuts import QDragableColor
def my_excepthook(type, value, tback):
@@ -45,13 +49,13 @@ def my_excepthook(type, value, tback):
sys.excepthook = my_excepthook
-""" Matplotlib overload """
+""" Matplotlib overload. """
figures = {}
app = None
def initialize():
- """ patch figure and show to display the color chooser GUI """
+ """ Patch figure and show to display the color chooser GUI. """
global app
if app is None:
app = QtWidgets.QApplication(sys.argv)
@@ -60,7 +64,7 @@ def initialize():
def show():
- """ the patched show to display the color choose gui """
+ """ The patched show to display the color choose gui. """
global figures
# iterate over figures
for figure in figures:
@@ -73,7 +77,7 @@ def show():
def figure(num=None, figsize=None, *args, **kwargs):
- """ the patched figure to initialize to color chooser GUI """
+ """ The patched figure to initialize to color chooser GUI. """
global figures
# if num is not defined create a new number
if num is None:
@@ -94,11 +98,11 @@ def figure(num=None, figsize=None, *args, **kwargs):
return canvas.figure
-""" Figure list functions """
+""" Figure list functions. """
def addChildren(color_artists: list, parent: Artist):
- """ find all the children of an Artist that use a color """
+ """ Find all the children of an Artist that use a color. """
for artist in parent.get_children():
# ignore empty texts
if isinstance(artist, mpl.text.Text) and artist.get_text() == "":
@@ -173,13 +177,13 @@ def addChildren(color_artists: list, parent: Artist):
def figureListColors(figure: Figure):
- """ add all artist with colors to a list in the figure """
+ """ Add all artist with colors to a list in the figure. """
figure.color_artists = {}
addChildren(figure.color_artists, figure)
def figureSwapColor(figure: Figure, new_color: str, color_base: str):
- """ swap two colors of a figure """
+ """ Swap two colors of a figure. """
if getattr(figure, "color_artists", None) is None:
figureListColors(figure)
changed_cmaps = []
@@ -200,7 +204,7 @@ def figureSwapColor(figure: Figure, new_color: str, color_base: str):
else:
getattr(artist, "set_" + color_type_name)(new_color)
artist.figure.change_tracker.addChange(artist,
- ".set_" + color_type_name + "(\"%s\")" % (new_color,))
+ ".set_" + color_type_name + f"(\"{new_color}\")")
continue
# use the attributes setter method
getattr(artist, "set_" + color_type_name)(cmap(value))
@@ -216,16 +220,16 @@ def figureSwapColor(figure: Figure, new_color: str, color_base: str):
else:
# use the attributes setter method
getattr(artist, "set_" + color_type_name)(new_color)
- artist.figure.change_tracker.addChange(artist, ".set_" + color_type_name + "(\"%s\")" % (new_color,))
+ artist.figure.change_tracker.addChange(artist, ".set_" + color_type_name + f"(\"{new_color}\")")
-""" Window """
+""" Window. """
class ColorChooserWidget(QtWidgets.QWidget):
trigger_no_update = False
- def __init__(self, parent: QtWidgets, canvas: FigureCanvas, signals: "Signals"=None):
+ def __init__(self, parent: QtWidgets, canvas: FigureCanvas, signals: "Signals" = None):
""" A widget to display all curently used colors and let the user switch them.
Args:
@@ -265,7 +269,7 @@ def __init__(self, parent: QtWidgets, canvas: FigureCanvas, signals: "Signals"=N
self.layout_buttons.addWidget(self.button_load)
self.canvas = canvas
- #self.updateColors()
+ # self.updateColors()
# add a text widget to allow easy copy and paste
self.colors_text_widget = QtWidgets.QTextEdit()
@@ -280,11 +284,11 @@ def setCanvas(self, canvas):
self.canvas = canvas
def saveColors(self):
- """ save the colors to a .txt file """
+ """ Save the colors to a .txt file. """
options = QtWidgets.QFileDialog.Options()
# options |= QtWidgets.QFileDialog.DontUseNativeDialog
- path = QtWidgets.QFileDialog.getSaveFileName(self, "Save Color File", getattr(self, "last_save_folder", None),"Text Files (*.txt);;All Files (*)", options=options)
+ path = QtWidgets.QFileDialog.getSaveFileName(self, "Save Color File", getattr(self, "last_save_folder", None), "Text Files (*.txt);;All Files (*)", options=options)
if isinstance(path, tuple):
path = str(path[0])
@@ -297,7 +301,7 @@ def saveColors(self):
fp.write(self.colors_text_widget.toPlainText())
def loadColors(self):
- """ load a list of colors from a .txt file """
+ """ Load a list of colors from a .txt file. """
options = QtWidgets.QFileDialog.Options()
# options |= QtWidgets.QFileDialog.DontUseNativeDialog
@@ -315,7 +319,7 @@ def loadColors(self):
self.colors_text_widget.setText(fp.read())
def addColorButton(self, color: str, basecolor: str = None):
- """ add a button for the given color """
+ """ Add a button for the given color. """
try:
button = QDragableColor(mpl.colors.to_hex(color))
except ValueError:
@@ -328,7 +332,7 @@ def addColorButton(self, color: str, basecolor: str = None):
self.color_buttons_list.append(button)
def colorChanged(self, c, color_base):
- """ update a color when it is changed
+ """ Update a color when it is changed.
if colors are swapped then first change both colors
and then update the text list of colors
"""
@@ -341,12 +345,12 @@ def colorChanged(self, c, color_base):
self.updateColorsText()
def resetSwapcounter(self, _):
- """ when a color changed using the color picker the swap counter is reset """
+ """ When a color changed using the color picker the swap counter is reset. """
self.swap_counter = 0
self.updateColorsText()
def updateColorsText(self):
- """ update the text list of colors """
+ """ Update the text list of colors. """
# add recursively all artists of the figure
figureListColors(self.canvas.figure)
self.color_artists = list(self.canvas.figure.color_artists)
@@ -376,7 +380,7 @@ def colorToText(color):
self.canvas.updateGeometry()
def colorsTextChanged(self):
- """ when the colors in the text widget changed
+ """ When the colors in the text widget changed.
after loading new colors or manually editing the text field
"""
if self.trigger_no_update:
@@ -396,7 +400,7 @@ def colorsTextChanged(self):
self.color_buttons_list[index].setColor(color)
def color_selected(self, new_color: str, color_base: str):
- """ switch two colors """
+ """ Switch two colors. """
if color_base is None:
return
figureSwapColor(self.canvas.figure, new_color, color_base)
@@ -415,7 +419,7 @@ def __init__(self, number, *args, **kwargs):
QtWidgets.QWidget.__init__(self)
# widget layout and elements
- self.setWindowTitle("Figure %s" % number)
+ self.setWindowTitle(f"Figure {number}")
self.setWindowIcon(qta.icon("fa5.bar-chart"))
self.layout_main = QtWidgets.QHBoxLayout(self)
diff --git a/pylustrator/QtGuiDrag.py b/pylustrator/QtGuiDrag.py
index a075ffe..9108c3a 100644
--- a/pylustrator/QtGuiDrag.py
+++ b/pylustrator/QtGuiDrag.py
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# QtGuiDrag.py
+import os
# Copyright (c) 2016-2020, Richard Gerum
#
# This file is part of Pylustrator.
@@ -21,29 +22,24 @@
import sys
import traceback
-from matplotlib import _pylab_helpers
-
-import os
-import qtawesome as qta
import matplotlib.pyplot as plt
-from matplotlib.figure import Figure
+import qtawesome as qta
+from matplotlib import _pylab_helpers
from matplotlib.axes._axes import Axes
-from matplotlib.text import Text
from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
-
+from matplotlib.figure import Figure
+from matplotlib.text import Text
from .ax_rasterisation import rasterizeAxes, restoreAxes
-from .change_tracker import setFigureVariableNames
-from .drag_helper import DragManager
-from .exception_swallower import swallow_get_exceptions
-
-from .components.qitem_properties import QItemProperties
-from .components.tree_view import MyTreeView
+from .change_tracker import init_figure, setFigureVariableNames
from .components.align import Align
-from .components.plot_layout import PlotLayout
from .components.info_dialog import InfoDialog
+from .components.plot_layout import PlotLayout
+from .components.qitem_properties import QItemProperties
from .components.qpos_and_size import QPosAndSize
-from .change_tracker import init_figure
+from .components.tree_view import MyTreeView
+from .drag_helper import DragManager
+from .exception_swallower import swallow_get_exceptions
def my_excepthook(type, value, tback):
@@ -52,12 +48,14 @@ def my_excepthook(type, value, tback):
sys.excepthook = my_excepthook
-""" Matplotlib overload """
+""" Matplotlib overload. """
figures = {}
app = None
keys_for_lines = {}
no_save_allowed = False
+
+
def initialize(use_global_variable_names=False, use_exception_silencer=False, disable_save=False):
"""
This will overload the commands ``plt.figure()`` and ``plt.show()``.
@@ -80,7 +78,7 @@ def wrapped_text(*args, **kwargs):
element = text(*args, fontdict=kwargs["fontdict"] if "fontdict" in kwargs else None)
from pylustrator.change_tracker import getReference
stack_position = traceback.extract_stack()[-2]
- element._pylustrator_reference = dict(reference=getReference(element), stack_position=stack_position)
+ element._pylustrator_reference = {"reference": getReference(element), "stack_position": stack_position}
old_args = {}
properties_to_save = ["position", "text", "ha", "va", "fontsize", "color", "style", "weight", "fontname", "rotation"]
for name in properties_to_save:
@@ -91,7 +89,7 @@ def wrapped_text(*args, **kwargs):
old_args["position"] = None
old_args["text"] = None
old_values = getattr(element, "_pylustrator_old_values", [])
- old_values.append(dict(stack_position=stack_position, old_args=old_args))
+ old_values.append({"stack_position": stack_position, "old_args": old_args})
element._pylustrator_old_values = old_values
if "fontdict" in kwargs:
@@ -124,8 +122,8 @@ def wrapped_text(*args, **kwargs):
plt.show = show
patchColormapsWithMetaInfo()
- #stack_call_position = traceback.extract_stack()[-2]
- #stack_call_position.filename
+ # stack_call_position = traceback.extract_stack()[-2]
+ # stack_call_position.filename
plt.keys_for_lines = keys_for_lines
@@ -138,8 +136,9 @@ def savefig(self, filename, *args, **kwargs):
Figure.savefig = savefig
+
def pyl_show(hide_window: bool = False):
- """ the function overloads the matplotlib show function.
+ """The function overloads the matplotlib show function.
It opens a DragManager window instead of the default matplotlib window.
"""
global figures, app
@@ -157,10 +156,10 @@ def pyl_show(hide_window: bool = False):
fig = _pylab_helpers.Gcf.figs[figure_number].canvas.figure
# get variable names that point to this figure
- #if setting_use_global_variable_names:
+ # if setting_use_global_variable_names:
# setFigureVariableNames(figure_number)
# get the window
- #window = _pylab_helpers.Gcf.figs[figure].canvas.window_pylustrator
+ # window = _pylab_helpers.Gcf.figs[figure].canvas.window_pylustrator
# warn about ticks not fitting tick labels
warnAboutTicks(fig)
# add dragger
@@ -177,7 +176,7 @@ def pyl_show(hide_window: bool = False):
def show(hide_window: bool = False):
- """ the function overloads the matplotlib show function.
+ """ The function overloads the matplotlib show function.
It opens a DragManager window instead of the default matplotlib window.
"""
global figures
@@ -192,7 +191,7 @@ def show(hide_window: bool = False):
if setting_use_global_variable_names:
setFigureVariableNames(figure)
# get the window
- #window = _pylab_helpers.Gcf.figs[figure].canvas.window_pylustrator
+ # window = _pylab_helpers.Gcf.figs[figure].canvas.window_pylustrator
window = PlotWindow()
window.setFigure(_pylab_helpers.Gcf.figs[figure].canvas.figure)
# warn about ticks not fitting tick labels
@@ -213,7 +212,7 @@ def show(hide_window: bool = False):
class CmapColor(list):
- """ a color like object that has the colormap as metadata """
+ """ A color like object that has the colormap as metadata. """
def setMeta(self, value, cmap):
self.value = value
@@ -221,7 +220,7 @@ def setMeta(self, value, cmap):
def patchColormapsWithMetaInfo():
- """ all colormaps now return color with metadata from which colormap the color came from """
+ """ All colormaps now return color with metadata from which colormap the color came from. """
from matplotlib.colors import Colormap
cm_call = Colormap.__call__
@@ -237,7 +236,7 @@ def new_call(self, *args, **kwargs):
def figure(num=None, figsize=None, force_add=False, *args, **kwargs):
- """ overloads the matplotlib figure call and wraps the Figure in a PlotWindow """
+ """ Overloads the matplotlib figure call and wraps the Figure in a PlotWindow. """
global figures
# if num is not defined create a new number
if num is None:
@@ -262,7 +261,7 @@ def figure(num=None, figsize=None, force_add=False, *args, **kwargs):
def warnAboutTicks(fig):
- """ warn if the tick labels and tick values do not match, to prevent users from accidentally setting wrong tick values """
+ """ Warn if the tick labels and tick values do not match, to prevent users from accidentally setting wrong tick values. """
import sys
for index, ax in enumerate(fig.axes):
ticks = ax.get_yticks()
@@ -285,7 +284,7 @@ def warnAboutTicks(fig):
print("Warning tick and label differ", t, l, "for axes", ax_name, file=sys.stderr)
-""" Window """
+""" Window. """
class Signals(QtWidgets.QWidget):
@@ -328,7 +327,7 @@ def undo():
undo_act.triggered.connect(undo)
self.menu_edit.addAction(undo_act)
- #self.preview.addFigure(figure)
+ # self.preview.addFigure(figure)
def selectionProperyChanged(self):
self.fig.selection.update_selection_rectangles()
@@ -380,8 +379,8 @@ def undo(self):
def redo(self):
self.fig.figure_dragger.redo()
- def __init__(self, number: int=0):
- """ The main window of pylustrator
+ def __init__(self, number: int = 0):
+ """ The main window of pylustrator.
Args:
number: the id of the figure
@@ -395,11 +394,10 @@ def __init__(self, number: int=0):
self.signals.canvas_changed.connect(self.setCanvas)
self.signals.figure_selection_property_changed.connect(self.selectionProperyChanged)
-
self.plot_layout = PlotLayout(self.signals)
# widget layout and elements
- self.setWindowTitle("Figure %s - Pylustrator" % number)
+ self.setWindowTitle(f"Figure {number} - Pylustrator")
self.setWindowIcon(QtGui.QIcon(os.path.join(os.path.dirname(__file__), "icons", "logo.ico")))
layout_parent = QtWidgets.QVBoxLayout(self)
layout_parent.setContentsMargins(0, 0, 0, 0)
@@ -428,16 +426,16 @@ def updateChangesSignal(undo, redo, undo_text, redo_text):
self.undo_act.setText(f"Undo: {undo_text}")
button_undo.setToolTip(f"Undo: {undo_text}")
else:
- self.undo_act.setText(f"Undo")
- button_undo.setToolTip(f"Undo")
+ self.undo_act.setText("Undo")
+ button_undo.setToolTip("Undo")
button_redo.setDisabled(redo)
self.redo_act.setDisabled(redo)
if redo_text != "":
self.redo_act.setText(f"Redo: {redo_text}")
button_redo.setToolTip(f"Redo: {redo_text}")
else:
- self.redo_act.setText(f"Redo")
- button_redo.setToolTip(f"Redo")
+ self.redo_act.setText("Redo")
+ button_redo.setToolTip("Redo")
self.update_changes_signal.connect(updateChangesSignal)
@@ -452,14 +450,14 @@ def updateChangesSignal(undo, redo, undo_text, redo_text):
self.layout_main.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
layout_parent.addWidget(self.layout_main)
- #self.preview = FigurePreviews(self)
- #self.layout_main.addWidget(self.preview)
+ # self.preview = FigurePreviews(self)
+ # self.layout_main.addWidget(self.preview)
#
widget = QtWidgets.QWidget()
self.layout_tools = QtWidgets.QVBoxLayout(widget)
widget.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
- #widget.setMaximumWidth(350)
- #widget.setMinimumWidth(350)
+ # widget.setMaximumWidth(350)
+ # widget.setMinimumWidth(350)
self.layout_main.addWidget(widget)
if 0:
@@ -500,7 +498,7 @@ def updateChangesSignal(undo, redo, undo_text, redo_text):
self.layout_main.setStretchFactor(2, 0)
def rasterize(self, rasterize: bool):
- """ convert the figur elements to an image """
+ """ Convert the figur elements to an image. """
if len(self.fig.selection.targets):
self.fig.figure_dragger.select_element(None)
if rasterize:
@@ -512,13 +510,13 @@ def rasterize(self, rasterize: bool):
self.fig.canvas.draw()
def actionSave(self):
- """ save the code for the figure """
+ """ Save the code for the figure. """
self.fig.change_tracker.save()
for _last_saved_figure, args, kwargs in getattr(self.fig, "_last_saved_figure", []):
self.fig.savefig(_last_saved_figure, *args, **kwargs)
def actionSaveImage(self):
- """ save figure as an image """
+ """ Save figure as an image. """
path = QtWidgets.QFileDialog.getSaveFileName(self, "Save Image", getattr(self.fig, "_last_saved_figure", [(None,)])[0][0],
"Images (*.png *.jpg *.pdf)")
if isinstance(path, tuple):
@@ -534,16 +532,16 @@ def actionSaveImage(self):
print("Saved plot image as", path)
def showInfo(self):
- """ show the info dialog """
+ """ Show the info dialog. """
self.info_dialog = InfoDialog(self)
self.info_dialog.show()
def showEvent(self, event: QtCore.QEvent):
- """ when the window is shown """
+ """ When the window is shown. """
self.colorWidget.updateColorsText()
def update(self):
- """ update the tree view """
+ """ Update the tree view. """
# self.input_size.setValue(np.array(self.fig.get_size_inches())*2.54)
def wrap(func):
@@ -572,14 +570,14 @@ def newfunc(*args):
self.signals.figure_element_selected.emit(self.fig)
def updateTitle(self):
- """ update the title of the window to display if it is saved or not """
+ """ Update the title of the window to display if it is saved or not. """
if self.fig.change_tracker.saved:
- self.setWindowTitle("Figure %s - Pylustrator" % self.fig.number)
+ self.setWindowTitle(f"Figure {self.fig.number} - Pylustrator")
else:
- self.setWindowTitle("Figure %s* - Pylustrator" % self.fig.number)
+ self.setWindowTitle(f"Figure {self.fig.number}* - Pylustrator")
def closeEvent(self, event: QtCore.QEvent):
- """ when the window is closed, ask the user to save """
+ """ When the window is closed, ask the user to save. """
if not self.fig.change_tracker.saved and not no_save_allowed:
reply = QtWidgets.QMessageBox.question(self, 'Warning - Pylustrator', 'The figure has not been saved. '
'All data will be lost.\nDo you want to save it?',
diff --git a/pylustrator/QtShortCuts.py b/pylustrator/QtShortCuts.py
index 74d1d53..493684c 100644
--- a/pylustrator/QtShortCuts.py
+++ b/pylustrator/QtShortCuts.py
@@ -19,15 +19,15 @@
# You should have received a copy of the GNU General Public License
# along with Pylustrator. If not, see
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
-from matplotlib import pyplot as plt
import matplotlib as mpl
+from matplotlib import pyplot as plt
+from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
+""" Color Chooser. """
-""" Color Chooser """
class QDragableColor(QtWidgets.QLabel):
- """ a color widget that can be dragged onto another QDragableColor widget to exchange the two colors.
+ """ A color widget that can be dragged onto another QDragableColor widget to exchange the two colors.
Alternatively it can be right-clicked to select either a color or a colormap through their respective menus.
The button can represent either a single color or a colormap.
"""
@@ -36,7 +36,7 @@ class QDragableColor(QtWidgets.QLabel):
color_changed_by_color_picker = QtCore.Signal(bool)
def __init__(self, value: str):
- """ initialize with a color """
+ """ Initialize with a color. """
super().__init__(value)
import matplotlib.pyplot as plt
self.maps = plt.colormaps()
@@ -45,11 +45,10 @@ def __init__(self, value: str):
self.setColor(value, True)
def getBackground(self) -> str:
- """ get the background of the color button """
-
+ """ Get the background of the color button. """
try:
cmap = plt.get_cmap(self.color)
- except:
+ except Exception:
return ""
text = "background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, "
N = 10
@@ -60,23 +59,23 @@ def getBackground(self) -> str:
return text
def setColor(self, value: str, no_signal=False):
- """ set the current color """
+ """ Set the current color. """
# display and save the new color
self.color = value
self.setText(value)
self.color_changed.emit(value)
if value in self.maps:
- self.setStyleSheet("text-align: center; border: 2px solid black; padding: 0.1em; "+self.getBackground())
+ self.setStyleSheet("text-align: center; border: 2px solid black; padding: 0.1em; " + self.getBackground())
else:
self.setStyleSheet(f"text-align: center; background-color: {value}; border: 2px solid black; padding: 0.1em; ")
def getColor(self) -> str:
- """ get the current color """
+ """ Get the current color. """
# return the color
return self.color
def mousePressEvent(self, event):
- """ when a mouse button is pressed """
+ """ When a mouse button is pressed. """
# a left mouse button lets the user drag the color
if event.button() == QtCore.Qt.LeftButton:
drag = QtGui.QDrag(self)
@@ -91,7 +90,7 @@ def mousePressEvent(self, event):
self.setText(self.color)
self.setDisabled(False)
if self.color in self.maps:
- self.setStyleSheet("text-align: center; border: 2px solid black; padding: 0.1em; "+self.getBackground())
+ self.setStyleSheet("text-align: center; border: 2px solid black; padding: 0.1em; " + self.getBackground())
else:
self.setStyleSheet(f"text-align: center; background-color: {self.color}; border: 2px solid black; padding: 0.1em; ")
# a right mouse button opens a color choose menu
@@ -99,29 +98,29 @@ def mousePressEvent(self, event):
self.openDialog()
def dragEnterEvent(self, event):
- """ when a color widget is dragged over the current widget """
+ """ When a color widget is dragged over the current widget. """
if event.mimeData().hasFormat("text/plain") and event.source() != self:
event.acceptProposedAction()
if self.color in self.maps:
- self.setStyleSheet("border: 2px solid red; padding: 0.1em; "+self.getBackground())
+ self.setStyleSheet("border: 2px solid red; padding: 0.1em; " + self.getBackground())
else:
self.setStyleSheet(f"background-color: {self.color}; border: 2px solid red; padding: 0.1em; ")
def dragLeaveEvent(self, event):
- """ when the color widget which is dragged leaves the area of this widget """
+ """ When the color widget which is dragged leaves the area of this widget. """
if self.color in self.maps:
- self.setStyleSheet("border: 2px solid black; padding: 0.1em; "+self.getBackground())
+ self.setStyleSheet("border: 2px solid black; padding: 0.1em; " + self.getBackground())
else:
self.setStyleSheet(f"background-color: {self.color}; border: 2px solid black; padding: 0.1em; ")
def dropEvent(self, event):
- """ when a color widget is dropped here, exchange the two colors """
+ """ When a color widget is dropped here, exchange the two colors. """
color = event.source().getColor()
event.source().setColor(self.getColor())
self.setColor(color)
def openDialog(self):
- """ open a color choosed dialog """
+ """ Open a color choosed dialog. """
if self.color in self.maps:
dialog = ColorMapChoose(self.parent(), self.color)
colormap, selected = dialog.exec()
@@ -139,13 +138,13 @@ def openDialog(self):
self.color_changed_by_color_picker.emit(True)
-
class ColorMapChoose(QtWidgets.QDialog):
- """ A dialog to select a colormap """
+ """ A dialog to select a colormap. """
+
result = ""
def __init__(self, parent: QtWidgets.QWidget, map):
- """ initialize the dialog with all the colormap of matplotlib """
+ """ Initialize the dialog with all the colormap of matplotlib. """
QtWidgets.QDialog.__init__(self, parent)
main_layout = QtWidgets.QVBoxLayout(self)
self.layout = QtWidgets.QHBoxLayout()
@@ -165,25 +164,25 @@ def __init__(self, parent: QtWidgets.QWidget, map):
# http://matplotlib.org/examples/color/colormaps_reference.html
cmaps = [('Perceptually Uniform Sequential', [
'viridis', 'plasma', 'inferno', 'magma']),
- ('Sequential', [
- 'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds',
- 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu',
- 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn']),
- ('Sequential (2)', [
- 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink',
- 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia',
- 'hot', 'afmhot', 'gist_heat', 'copper']),
- ('Diverging', [
- 'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu',
- 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']),
- ('Qualitative', [
- 'Pastel1', 'Pastel2', 'Paired', 'Accent',
- 'Dark2', 'Set1', 'Set2', 'Set3',
- 'tab10', 'tab20', 'tab20b', 'tab20c']),
- ('Miscellaneous', [
- 'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern',
- 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', 'hsv',
- 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar'])]
+ ('Sequential', [
+ 'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds',
+ 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu',
+ 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn']),
+ ('Sequential (2)', [
+ 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink',
+ 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia',
+ 'hot', 'afmhot', 'gist_heat', 'copper']),
+ ('Diverging', [
+ 'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu',
+ 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']),
+ ('Qualitative', [
+ 'Pastel1', 'Pastel2', 'Paired', 'Accent',
+ 'Dark2', 'Set1', 'Set2', 'Set3',
+ 'tab10', 'tab20', 'tab20b', 'tab20c']),
+ ('Miscellaneous', [
+ 'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern',
+ 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', 'hsv',
+ 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar'])]
for cmap_category, cmap_list in cmaps:
layout = QtWidgets.QVBoxLayout()
@@ -192,7 +191,7 @@ def __init__(self, parent: QtWidgets.QWidget, map):
label.setFixedWidth(150)
for cmap in cmap_list:
button = QtWidgets.QPushButton(cmap)
- button.setStyleSheet("text-align: center; border: 2px solid black; "+self.getBackground(cmap))
+ button.setStyleSheet("text-align: center; border: 2px solid black; " + self.getBackground(cmap))
button.clicked.connect(lambda _, cmap=cmap: self.buttonClicked(cmap))
self.buttons.append(button)
layout.addWidget(button)
@@ -200,22 +199,22 @@ def __init__(self, parent: QtWidgets.QWidget, map):
self.layout.addLayout(layout)
def buttonClicked(self, text: str):
- """ the used as selected a colormap, we are done """
+ """ The used as selected a colormap, we are done. """
self.result = text
self.done(1)
def exec(self):
- """ execute the dialog and return the result """
+ """ Execute the dialog and return the result. """
result = QtWidgets.QDialog.exec(self)
return self.result, result == 1
def getBackground(self, color: str) -> str:
- """ convert a colormap to a gradient background """
- import matplotlib.pyplot as plt
+ """ Convert a colormap to a gradient background. """
import matplotlib as mpl
+ import matplotlib.pyplot as plt
try:
cmap = plt.get_cmap(color)
- except:
+ except Exception:
return ""
text = "background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, "
N = 10
diff --git a/pylustrator/__init__.py b/pylustrator/__init__.py
index 207478d..0586b6a 100644
--- a/pylustrator/__init__.py
+++ b/pylustrator/__init__.py
@@ -19,10 +19,15 @@
# You should have received a copy of the GNU General Public License
# along with Pylustrator. If not, see
-from .QtGuiDrag import initialize as start
-from .helper_functions import fig_text, add_axes, add_image, despine, changeFigureSize, mark_inset, VoronoiPlot, selectRectangle, mark_inset_pos, draw_from_point_to_bbox, draw_from_point_to_point, loadFigureFromFile, add_letter, add_letters
-from .QtGui import initialize as StartColorChooser
-from .lab_colormap import LabColormap
+from .helper_functions import (VoronoiPlot, add_axes, add_image, add_letter,
+ add_letters, changeFigureSize, despine,
+ draw_from_point_to_bbox,
+ draw_from_point_to_point, fig_text)
+from .helper_functions import loadFigureFromFile
from .helper_functions import loadFigureFromFile as load
+from .helper_functions import mark_inset, mark_inset_pos, selectRectangle
+from .lab_colormap import LabColormap
+from .QtGui import initialize as StartColorChooser
+from .QtGuiDrag import initialize as start
__version__ = '1.3.0'
diff --git a/pylustrator/arc2bez.py b/pylustrator/arc2bez.py
index bb7ae37..d19cda5 100644
--- a/pylustrator/arc2bez.py
+++ b/pylustrator/arc2bez.py
@@ -21,6 +21,7 @@
import numpy as np
+
def mapToEllipse(pos, rx, ry, cosphi, sinphi, centerx, centery):
x, y = pos
x *= rx
@@ -41,10 +42,10 @@ def approxUnitArc(ang1, ang2):
y2 = np.sin(ang1 + ang2)
return [
- [x1 - y1 * a, y1 + x1 * a],
- [x2 + y2 * a, y2 - x2 * a],
- [x2, y2]
- ]
+ [x1 - y1 * a, y1 + x1 * a],
+ [x2 + y2 * a, y2 - x2 * a],
+ [x2, y2],
+ ]
def vectorAngle(ux, uy, vx, vy):
@@ -54,15 +55,14 @@ def vectorAngle(ux, uy, vx, vy):
if dot > 1:
dot = 1
-
- if dot < -1:
+ elif dot < -1:
dot = -1
return sign * np.arccos(dot)
-def getArcCenter (px, py, cx, cy, rx, ry,
- largeArcFlag, sweepFlag, sinphi, cosphi, pxp, pyp):
+def getArcCenter(px, py, cx, cy, rx, ry,
+ largeArcFlag, sweepFlag, sinphi, cosphi, pxp, pyp):
rxsq = np.power(rx, 2)
rysq = np.power(ry, 2)
pxpsq = np.power(pxp, 2)
@@ -70,8 +70,7 @@ def getArcCenter (px, py, cx, cy, rx, ry,
radicant = (rxsq * rysq) - (rxsq * pypsq) - (rysq * pxpsq)
- if radicant < 0:
- radicant = 0
+ radicant = max(0, radicant)
radicant /= (rxsq * pypsq) + (rysq * pxpsq)
radicant = np.sqrt(radicant) * (-1 if largeArcFlag == sweepFlag else 1)
@@ -91,15 +90,15 @@ def getArcCenter (px, py, cx, cy, rx, ry,
ang2 = vectorAngle(vx1, vy1, vx2, vy2)
if sweepFlag == 0 and ang2 > 0:
- ang2 -= np.pi*2
+ ang2 -= np.pi * 2
if sweepFlag == 1 and ang2 < 0:
- ang2 += np.pi*2
+ ang2 += np.pi * 2
return centerx, centery, ang1, ang2
-def arcToBezier(pos1, pos2, rx, ry, xAxisRotation = 0, largeArcFlag = 0, sweepFlag = 0):
+def arcToBezier(pos1, pos2, rx, ry, xAxisRotation=0, largeArcFlag=0, sweepFlag=0):
px, py = pos1
cx, cy = pos2
curves = []
@@ -126,7 +125,7 @@ def arcToBezier(pos1, pos2, rx, ry, xAxisRotation = 0, largeArcFlag = 0, sweepFl
ry *= np.sqrt(lambda_)
centerx, centery, ang1, ang2 = getArcCenter(px, py, cx, cy, rx, ry,
- largeArcFlag, sweepFlag, sinphi, cosphi, pxp, pyp)
+ largeArcFlag, sweepFlag, sinphi, cosphi, pxp, pyp)
# If 'ang2' == 90.0000000001, then `ratio` will evaluate to
# 1.0000000001. This causes `segments` to be greater than one, which is an
diff --git a/pylustrator/ax_rasterisation.py b/pylustrator/ax_rasterisation.py
index 399335a..64beebb 100644
--- a/pylustrator/ax_rasterisation.py
+++ b/pylustrator/ax_rasterisation.py
@@ -20,19 +20,23 @@
# along with Pylustrator. If not, see
import io
+
import matplotlib.pyplot as plt
+
try: # starting from mpl version 3.6.0
from matplotlib.axes import Axes
-except:
+except Exception:
from matplotlib.axes._subplots import Axes
-from matplotlib.figure import Figure
+
from typing import List
-from .helper_functions import removeContentFromFigure, addContentToFigure
+from matplotlib.figure import Figure
+
+from .helper_functions import addContentToFigure, removeContentFromFigure
def stashElements(ax: Axes, names: List[str]):
- """ remove elements from a figure and store them"""
+ """ Remove elements from a figure and store them."""
for attribute in names:
element = getattr(ax, attribute)
setattr(ax, "pylustrator_" + attribute, element)
@@ -40,7 +44,7 @@ def stashElements(ax: Axes, names: List[str]):
def popStashedElements(ax: Axes, names: List[str]):
- """ add elements to a figure that were previously removed from it """
+ """Add elements to a figure that were previously removed from it."""
for attribute in names:
element_list = getattr(ax, attribute)
if isinstance(element_list, list):
@@ -53,7 +57,7 @@ def popStashedElements(ax: Axes, names: List[str]):
def rasterizeAxes(fig: Figure):
- """ replace contents of a figure with a rasterized image of it """
+ """ Replace contents of a figure with a rasterized image of it. """
restoreAxes(fig)
parts = removeContentFromFigure(fig)
@@ -74,23 +78,23 @@ def rasterizeAxes(fig: Figure):
bbox = ax.get_position()
sx = im.shape[1]
sy = im.shape[0]
- x1, x2 = int(bbox.x0*sx+1), int(bbox.x1*sx-1)
- y2, y1 = sy-int(bbox.y0*sy+1), sy-int(bbox.y1*sy-1)
+ x1, x2 = int(bbox.x0 * sx + 1), int(bbox.x1 * sx - 1)
+ y2, y1 = sy - int(bbox.y0 * sy + 1), sy - int(bbox.y1 * sy - 1)
im2 = im[y1:y2, x1:x2]
stashElements(ax, ["lines", "images", "patches"])
sx2 = ax.get_xlim()[1] - ax.get_xlim()[0]
sy2 = ax.get_ylim()[1] - ax.get_ylim()[0]
- x1_offset = 1/sx/bbox.width*sx2
- x2_offset = 1/sx/bbox.width*sx2
+ x1_offset = 1 / sx / bbox.width * sx2
+ x2_offset = 1 / sx / bbox.width * sx2
y1_offset = 1 / sy / bbox.height * sy2
y2_offset = 1 / sy / bbox.height * sy2
xlim = ax.get_xlim()
ylim = ax.get_ylim()
- ax.pylustrator_rasterized = ax.imshow(im2, extent=[ax.get_xlim()[0]+x1_offset, ax.get_xlim()[1]-x2_offset-x1_offset,
- ax.get_ylim()[0]+y1_offset, ax.get_ylim()[1]-y2_offset-y1_offset], aspect="auto")
+ ax.pylustrator_rasterized = ax.imshow(im2, extent=[ax.get_xlim()[0] + x1_offset, ax.get_xlim()[1] - x2_offset - x1_offset,
+ ax.get_ylim()[0] + y1_offset, ax.get_ylim()[1] - y2_offset - y1_offset], aspect="auto")
ax.set_xlim(xlim)
ax.set_ylim(ylim)
@@ -100,7 +104,7 @@ def rasterizeAxes(fig: Figure):
def restoreAxes(fig: Figure):
- """ restore contents of a figure """
+ """Restore contents of a figure."""
list_axes = fig.axes
for ax in list_axes:
im = getattr(ax, "pylustrator_rasterized", None)
diff --git a/pylustrator/change_tracker.py b/pylustrator/change_tracker.py
index f7109bf..1a97440 100644
--- a/pylustrator/change_tracker.py
+++ b/pylustrator/change_tracker.py
@@ -23,45 +23,53 @@
import sys
import traceback
from typing import IO
-from packaging import version
-import numpy as np
import matplotlib
import matplotlib as mpl
import matplotlib.pyplot as plt
+import numpy as np
from matplotlib import _pylab_helpers
from matplotlib.artist import Artist
+from packaging import version
+
try: # starting from mpl version 3.6.0
from matplotlib.axes import Axes
-except:
+except Exception:
from matplotlib.axes._subplots import Axes
+
from matplotlib.collections import Collection
from matplotlib.figure import Figure
+
try:
from matplotlib.figure import SubFigure # since matplotlib 3.4.0
except ImportError:
SubFigure = None
+from matplotlib.legend import Legend
from matplotlib.lines import Line2D
from matplotlib.patches import Rectangle
from matplotlib.text import Text
-from matplotlib.legend import Legend
+
try:
from natsort import natsorted
-except:
+except Exception:
natsorted = sorted
from .exception_swallower import Dummy
-from .jupyter_cells import open
from .helper_functions import main_figure
+from .jupyter_cells import open
+
+""" External overload. """
-""" External overload """
class CustomStackPosition:
filename = None
lineno = None
+
def __init__(self, filename, lineno):
self.filename = filename
self.lineno = lineno
+
+
custom_stack_position = None
custom_prepend = ""
custom_append = ""
@@ -72,27 +80,34 @@ def __init__(self, filename, lineno):
("\r", "\\r"),
("\"", "\\\""),
]
+
+
def escape_string(str):
for pair in escape_pairs:
str = str.replace(pair[0], pair[1])
return str
+
def unescape_string(str):
for pair in escape_pairs:
str = str.replace(pair[1], pair[0])
return str
+
def to_str(v):
if isinstance(v, list) and len(v) and isinstance(v[0], float):
- return "["+", ".join(np.format_float_positional(a, 4, fractional=False, trim=".") for a in v)+"]"
+ return "[" + ", ".join(np.format_float_positional(a, 4, fractional=False, trim=".") for a in v) + "]"
elif isinstance(v, tuple) and len(v) and isinstance(v[0], float):
- return "("+", ".join(np.format_float_positional(a, 4, fractional=False, trim=".") for a in v)+")"
+ return "(" + ", ".join(np.format_float_positional(a, 4, fractional=False, trim=".") for a in v) + ")"
elif isinstance(v, float):
return np.format_float_positional(v, 4, fractional=False, trim=".")
return repr(v)
+
+
def kwargs_to_string(kwargs):
return ', '.join(f'{k}={to_str(v)}' for k, v in kwargs.items())
+
class UndoRedo:
def __init__(self, elements, name):
self.elements = list(elements)
@@ -113,6 +128,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.figure.signals.figure_selection_property_changed.emit()
self.change_tracker.addEdit([self.undo, self.redo, self.name])
+
def init_figure(fig):
for axes in fig.axes:
add_axes_default(axes)
@@ -121,6 +137,7 @@ def init_figure(fig):
for text in fig.texts:
add_text_default(text)
+
def add_text_default(element):
# properties to store
properties = ["position", "text", "ha", "va", "fontsize", "color", "style", "weight", "fontname", "rotation"]
@@ -136,13 +153,15 @@ def add_text_default(element):
if getattr(element, "is_new_text", False):
old_args["position"] = None
old_args["text"] = None
+
element._pylustrator_old_args = old_args
+
def add_axes_default(element):
properties = ["position",
"xlim", "xlabel", "xticks", "xticklabels", "xscale",
"ylim", "ylabel", "yticks", "yticklabels", "yscale",
- "zorder"
+ "zorder",
]
if getattr(element, "_pylustrator_old_args", None) is None:
old_args = {}
@@ -160,7 +179,7 @@ def add_axes_default(element):
old_args["yticks-locator"] = element.get_yaxis().major.locator
old_args["yticklabels"] = [t.get_text() for t in old_args["yticklabels"]]
old_args["grid"] = getattr(element.xaxis, "_gridOnMajor", False) or getattr(element.xaxis, "_major_tick_kw", {"gridOn": False})['gridOn']
- old_args["spines"] = {s: v.get_visible() for s,v in element.spines.items()}
+ old_args["spines"] = {s: v.get_visible() for s, v in element.spines.items()}
old_args["xticks-minor"] = list(element.get_xticks(minor=True))
old_args["xticklabels-minor"] = [t.get_text() for t in element.get_xticklabels(minor=True)]
@@ -170,8 +189,9 @@ def add_axes_default(element):
add_text_default(element.get_xaxis().get_label())
add_text_default(element.get_yaxis().get_label())
+
def getReference(element: Artist, allow_using_variable_names=True):
- """ get the code string that represents the given Artist. """
+ """Get the code string that represents the given Artist."""
if element is None:
return ""
if isinstance(element, Figure):
@@ -180,9 +200,9 @@ def getReference(element: Artist, allow_using_variable_names=True):
if name is not None:
return name
if isinstance(element.number, (float, int)):
- return "plt.figure(%s)" % element.number
+ return f"plt.figure({element.number})"
else:
- return "plt.figure(\"%s\")" % element.number
+ return f"plt.figure(\"{element.number}\")"
# subfigures are only available in matplotlib>=3.4.0
if version.parse(mpl.__version__) >= version.parse("3.4.0") and isinstance(element, SubFigure):
index = element._parent.subfigs.index(element)
@@ -247,7 +267,7 @@ def getReference(element: Artist, allow_using_variable_names=True):
if isinstance(element, matplotlib.axes._axes.Axes):
if element.get_label():
- return getReference(element.figure) + ".ax_dict[\"%s\"]" % escape_string(element.get_label())
+ return getReference(element.figure) + f".ax_dict[\"{escape_string(element.get_label())}\"]"
index = element.figure.axes.index(element)
return getReference(element.figure) + ".axes[%d]" % index
@@ -257,7 +277,7 @@ def getReference(element: Artist, allow_using_variable_names=True):
def setFigureVariableNames(figure: Figure):
- """ get the global variable names that refer to the given figure """
+ """Get the global variable names that refer to the given figure."""
import inspect
mpl_figure = _pylab_helpers.Gcf.figs[figure].canvas.figure
calling_globals = inspect.stack()[2][0].f_globals
@@ -266,14 +286,15 @@ def setFigureVariableNames(figure: Figure):
for name, val in calling_globals.items()
if isinstance(val, mpl.figure.Figure) and hash(val) == hash(mpl_figure)
]
- #print("fig_names", fig_names)
+ # print("fig_names", fig_names)
if len(fig_names):
globals()[fig_names[0]] = mpl_figure
setattr(mpl_figure, "_variable_name", fig_names[0])
class ChangeTracker:
- """ a class that records a list of the change to the figure """
+ """A class that records a list of the change to the figure."""
+
changes = None
saved = True
@@ -303,7 +324,7 @@ def __init__(self, figure: Figure, no_save):
self.load()
def addChange(self, command_obj: Artist, command: str, reference_obj: Artist = None, reference_command: str = None):
- """ add a change """
+ """Add a change."""
command = command.replace("\n", "\\n")
if reference_obj is None:
reference_obj = command_obj
@@ -321,9 +342,10 @@ def get_element_restore_function(self, elements):
description_strings.extend(desc)
else:
description_strings.append(desc)
+
def restore():
for element, string in description_strings:
- #function, arguments = re.match(r"\.([^(]*)\((.*)\)", string)
+ # function, arguments = re.match(r"\.([^(]*)\((.*)\)", string)
print(f"eval {getReference(element)}{string}")
eval(f"{getReference(element)}{string}")
if isinstance(element, Text):
@@ -334,7 +356,7 @@ def restore():
self.addNewAxesChange(element)
else:
raise NotImplementedError
- #getattr(element, function)(eval(arg))
+ # getattr(element, function)(eval(arg))
return restore
def get_describtion_string(self, element, exclude_default=True):
@@ -342,13 +364,13 @@ def get_describtion_string(self, element, exclude_default=True):
# if the text is deleted we do not need to store all properties
if not element.get_visible() or element.get_text() == "":
if getattr(element, "is_new_text", False):
- return element.axes or element.figure, f".text(0, 0, "", visible=False)"
+ return element.axes or element.figure, ".text(0, 0, "", visible=False)"
else:
is_label = np.any([ax.xaxis.get_label() == element or ax.yaxis.get_label() == element for ax in
element.figure.axes])
if is_label:
- return element, f".set(text='')"
- return element, f".set(visible=False)"
+ return element, ".set(text='')"
+ return element, ".set(visible=False)"
# properties to store
properties = ["position", "text", "ha", "va", "fontsize", "color", "style", "weight", "fontname", "rotation"]
@@ -414,7 +436,7 @@ def get_describtion_string(self, element, exclude_default=True):
else:
default = None
pass
- if (prop == "fontsize" or prop == "title_fontsize") and (default == "medium" or default == None):
+ if (prop == "fontsize" or prop == "title_fontsize") and (default == "medium" or default is None):
if value == plt.rcParams["font.size"]:
continue
if prop == "title_fontsize" and "title" not in kwargs:
@@ -427,14 +449,14 @@ def get_describtion_string(self, element, exclude_default=True):
properties = ["position",
"xscale", "xlabel", "xticks", "xticklabels", "xlim",
"yscale", "ylabel", "yticks", "yticklabels", "ylim",
- "zorder"
+ "zorder",
]
# get current property values
kwargs = {}
for prop in properties:
value = getattr(element, f"get_{prop}")()
- #if self.text_properties_defaults[prop] != value or not exclude_default:
+ # if self.text_properties_defaults[prop] != value or not exclude_default:
kwargs[prop] = value
pos = element.get_position()
@@ -480,7 +502,7 @@ def get_describtion_string(self, element, exclude_default=True):
# the grid
has_grid = getattr(element.xaxis, "_gridOnMajor", False) or \
- getattr(element.xaxis, "_major_tick_kw", {"gridOn": False})['gridOn']
+ getattr(element.xaxis, "_major_tick_kw", {"gridOn": False})['gridOn']
if has_grid != element._pylustrator_old_args["grid"] or not exclude_default:
desc_strings.append([element, f".grid({has_grid})"])
@@ -508,6 +530,7 @@ def get_describtion_string(self, element, exclude_default=True):
return desc_strings
text_properties_defaults = None
+
def addNewTextChange(self, element):
command_parent, command = self.get_describtion_string(element)
@@ -537,7 +560,7 @@ def addNewLegendChange(self, element):
del self.changes[reference_obj, reference_command]
# store the changes
- #if not element.get_visible() and getattr(element, "is_new_text", False):
+ # if not element.get_visible() and getattr(element, "is_new_text", False):
# return
main_figure(element).change_tracker.addChange(command_parent, command)
@@ -551,7 +574,7 @@ def addNewAxesChange(self, element):
del self.changes[reference_obj, reference_command]
# store the changes
- #if not element.get_visible() and getattr(element, "is_new_text", False):
+ # if not element.get_visible() and getattr(element, "is_new_text", False):
# return
for command_parent, command in desc_strings:
if command.endswith(".set()"):
@@ -564,12 +587,12 @@ def changeCountChanged(self):
if self.last_edit >= 0 and len(self.edits[self.last_edit]) > 2:
name_undo = self.edits[self.last_edit][2]
name_redo = ""
- if self.last_edit < len(self.edits) - 1 and len(self.edits[self.last_edit+1]) > 2:
- name_redo = self.edits[self.last_edit+1][2]
+ if self.last_edit < len(self.edits) - 1 and len(self.edits[self.last_edit + 1]) > 2:
+ name_redo = self.edits[self.last_edit + 1][2]
self.update_changes_signal.emit(self.last_edit < 0, self.last_edit >= len(self.edits) - 1, name_undo, name_redo)
def removeElement(self, element: Artist):
- """ remove an Artis from the figure """
+ """ Remove an Artis from the figure."""
# create_key = key+".new"
created_by_pylustrator = (element, ".new") in self.changes
# delete changes related to this element
@@ -609,41 +632,41 @@ def undo():
self.figure.selection.remove_target(element)
def addEdit(self, edit: list):
- """ add an edit to the stored list of edits """
+ """Add an edit to the stored list of edits."""
if self.last_edit < len(self.edits) - 1:
self.edits = self.edits[:self.last_edit + 1]
self.edits.append(edit)
self.last_edit = len(self.edits) - 1
self.last_edit = len(self.edits) - 1
- #print("addEdit", len(self.edits), self.last_edit)
+ # print("addEdit", len(self.edits), self.last_edit)
self.changeCountChanged()
def backEdit(self):
- """ undo an edit in the list """
+ """Undo an edit in the list."""
if self.last_edit < 0:
- #print("no backEdit", len(self.edits), self.last_edit)
+ # print("no backEdit", len(self.edits), self.last_edit)
return
edit = self.edits[self.last_edit]
edit[0]()
self.last_edit -= 1
self.figure.canvas.draw()
- #print("backEdit", len(self.edits), self.last_edit)
+ # print("backEdit", len(self.edits), self.last_edit)
self.changeCountChanged()
def forwardEdit(self):
- """ redo an edit """
+ """Redo an edit."""
if self.last_edit >= len(self.edits) - 1:
- #print("no forwardEdit", len(self.edits), self.last_edit)
+ # print("no forwardEdit", len(self.edits), self.last_edit)
return
edit = self.edits[self.last_edit + 1]
edit[1]()
self.last_edit += 1
self.figure.canvas.draw()
- #print("forwardEdit", len(self.edits), self.last_edit)
+ # print("forwardEdit", len(self.edits), self.last_edit)
self.changeCountChanged()
def load(self):
- """ load a set of changes from a script file. The changes are the code that pylustrator generated """
+ """Load a set of changes from a script file. The changes are the code that pylustrator generated."""
regex = re.compile(r"(\.[^\(= ]*)(.*)")
command_obj_regexes = [getReference(self.figure),
r"plt\.figure\([^)]*\)",
@@ -664,7 +687,7 @@ def load(self):
fig = self.figure
header = []
- header += ["fig = plt.figure(%s)" % self.figure.number]
+ header += [f"fig = plt.figure({self.figure.number})"]
header += ["import matplotlib as mpl"]
self.get_reference_cached = {}
@@ -752,12 +775,12 @@ def load(self):
self.get_reference_cached[reference_obj] = reference_obj_str
- #print("---", [reference_obj, reference_command], (command_obj, command + parameter))
+ # print("---", [reference_obj, reference_command], (command_obj, command + parameter))
self.changes[reference_obj, reference_command] = (command_obj, command + parameter)
self.sorted_changes()
def sorted_changes(self):
- """ sort the changes by their priority. For example setting to logscale needs to be executed before xlim. """
+ """ Sort the changes by their priority. For example setting to logscale needs to be executed before xlim. """
def getRef(obj):
try:
return getReference(obj)
@@ -807,7 +830,7 @@ def getRef(obj):
return output
def save(self):
- """ save the changes to the .py file """
+ """ Save the changes to the .py file. """
# if saving is disabled
if self.no_save is True:
return
@@ -828,7 +851,7 @@ def save(self):
if line.startswith("fig.add_axes"):
output.append(header[1])
output.append("#% end: automatic generated code from pylustrator" + custom_append)
- print("\n"+"\n".join(output)+"\n")
+ print("\n" + "\n".join(output) + "\n")
block_id = getReference(self.figure)
block = getTextFromFile(block_id, stack_position)
@@ -845,7 +868,7 @@ def save(self):
def getTextFromFile(block_id: str, stack_pos: traceback.FrameSummary):
- """ get the text which corresponds to the block_id (e.g. which figure) at the given position sepcified by stack_pos. """
+ """ Get the text which corresponds to the block_id (e.g. which figure) at the given position sepcified by stack_pos. """
block_id = lineToId(block_id)
block = None
@@ -867,7 +890,7 @@ def getTextFromFile(block_id: str, stack_pos: traceback.FrameSummary):
# if there is a new pylustrator block
elif line.strip().startswith(custom_prepend + "#% start:"):
block = Block(line)
- start_lineno = lineno-1
+ start_lineno = lineno - 1
# if we are currently reading a block, continue with the next line
if block is not None and not block.finished:
@@ -881,34 +904,35 @@ def getTextFromFile(block_id: str, stack_pos: traceback.FrameSummary):
class Block:
- """ an object to represent the code block generated by a pylustrator save """
+ """ An object to represent the code block generated by a pylustrator save. """
+
id = None
finished = False
def __init__(self, line: str):
- """ initialize the block with its first line """
+ """ Initialize the block with its first line."""
self.text = line
self.size = 1
self.indent = getIndent(line)
def add(self, line: str):
- """ add a line to the block """
+ """ Add a line to the block. """
if self.id is None:
self.id = lineToId(line)
self.text += line
self.size += 1
def end(self):
- """ end the block """
+ """ End the block. """
self.finished = True
def __iter__(self):
- """ iterate over all the lines of the block """
+ """ Iterate over all the lines of the block. """
return iter(self.text.split("\n"))
def getIndent(line: str):
- """ get the indent part of a line of code """
+ """ Get the indent part of a line of code. """
i = 0
for i in range(len(line)):
if line[i] != " " and line[i] != "\t":
@@ -918,7 +942,7 @@ def getIndent(line: str):
def addLineCounter(fp: IO):
- """ wrap a file pointer to store th line numbers """
+ """ Wrap a file pointer to store th line numbers. """
fp.lineno = 0
write = fp.write
@@ -930,7 +954,7 @@ def write_with_linenumbers(line: str):
def lineToId(line: str):
- """ get the id of a line, e.g. part which specifies which figure it refers to """
+ """ Get the id of a line, e.g. part which specifies which figure it refers to. """
line = line.strip()
line = line.split(".ax_dict")[0]
if line.startswith("fig = "):
@@ -939,7 +963,7 @@ def lineToId(line: str):
def insertTextToFile(new_block: str, stack_pos: traceback.FrameSummary, figure_id_line: str):
- """ insert a text block into a file """
+ """ Insert a text block into a file. """
figure_id_line = lineToId(figure_id_line)
block = None
written = False
diff --git a/pylustrator/components/align.py b/pylustrator/components/align.py
index b9dd8f7..efc37c9 100644
--- a/pylustrator/components/align.py
+++ b/pylustrator/components/align.py
@@ -1,4 +1,5 @@
import os
+
from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
@@ -39,10 +40,10 @@ def __init__(self, layout: QtWidgets.QLayout, signals: "Signals"):
self.layout.addStretch()
def execute_action(self, act: str):
- """ execute an alignment action """
+ """ Execute an alignment action. """
self.fig.selection.align_points(act)
self.fig.selection.update_selection_rectangles()
self.fig.canvas.draw()
def setFigure(self, fig):
- self.fig = fig
\ No newline at end of file
+ self.fig = fig
diff --git a/pylustrator/components/figure_previews.py b/pylustrator/components/figure_previews.py
index 0331bbd..b0e4fc9 100644
--- a/pylustrator/components/figure_previews.py
+++ b/pylustrator/components/figure_previews.py
@@ -24,9 +24,9 @@ def addFigure(self, figure):
pix.fill(QtGui.QColor("#666666"))
target_width = 150
- target_height = 150*9/16
+ target_height = 150 * 9 / 16
w, h = figure.get_size_inches()
- figure.savefig("tmp.png", dpi=min([target_width/w, target_height/h]))
+ figure.savefig("tmp.png", dpi=min([target_width / w, target_height / h]))
button.setStyleSheet("background:#d1d1d1")
button.setMaximumWidth(150)
button.setMaximumHeight(150)
@@ -34,6 +34,6 @@ def addFigure(self, figure):
pix.load("tmp.png")
# scale pixmap to fit in label'size and keep ratio of pixmap
- #pix = pix.scaled(160, 90, QtCore.Qt.KeepAspectRatio)
+ # pix = pix.scaled(160, 90, QtCore.Qt.KeepAspectRatio)
button.setPixmap(pix)
button.mousePressEvent = lambda e: self.parent.setFigure(figure)
diff --git a/pylustrator/components/info_dialog.py b/pylustrator/components/info_dialog.py
index 99299d6..5dedb2a 100644
--- a/pylustrator/components/info_dialog.py
+++ b/pylustrator/components/info_dialog.py
@@ -1,4 +1,5 @@
import os
+
from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
diff --git a/pylustrator/components/matplotlibwidget.py b/pylustrator/components/matplotlibwidget.py
index 4564984..3c6eaaa 100644
--- a/pylustrator/components/matplotlibwidget.py
+++ b/pylustrator/components/matplotlibwidget.py
@@ -38,11 +38,15 @@
import time
import qtawesome as qta
-from matplotlib.backends.qt_compat import QtWidgets, QtCore
+from matplotlib.backends.qt_compat import QtCore, QtWidgets
+
try: # for matplotlib > 3.0
- from matplotlib.backends.backend_qtagg import (FigureCanvas, FigureManager, NavigationToolbar2QT as NavigationToolbar)
+ from matplotlib.backends.backend_qtagg import FigureCanvas, FigureManager
+ from matplotlib.backends.backend_qtagg import \
+ NavigationToolbar2QT as NavigationToolbar
except ModuleNotFoundError:
from matplotlib.backends.backend_qt5agg import (FigureCanvas, FigureManager, NavigationToolbar2QT as NavigationToolbar)
+
from matplotlib.figure import Figure
@@ -60,7 +64,7 @@ def __init__(self, parent=None, num=1, size=None, dpi=100, figure=None, *args, *
self.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
self.updateGeometry()
-
+
self.manager = FigureManager(self, 1)
self.manager._cidgcf = self.figure
@@ -68,6 +72,7 @@ def __init__(self, parent=None, num=1, size=None, dpi=100, figure=None, *args, *
self.timer.setInterval(300)
self.timer.timeout.connect(self.draw)
timer = None
+
def schedule_draw(self):
if self.quick_draw is True:
return super().draw()
@@ -77,7 +82,8 @@ def schedule_draw(self):
def draw(self):
self.timer.stop()
import traceback
- #print(traceback.print_stack())
+
+ # print(traceback.print_stack())
t = time.time()
super().draw()
duration = time.time() - t
@@ -86,7 +92,7 @@ def draw(self):
self.quick_draw = False
else:
self.quick_draw = True
-
+
def show(self):
self.draw()
@@ -121,7 +127,7 @@ class CanvasWindow(QtWidgets.QWidget):
def __init__(self, num="", *args, **kwargs):
QtWidgets.QWidget.__init__(self)
- self.setWindowTitle("Figure %s" % num)
+ self.setWindowTitle(f"Figure {num}")
self.setWindowIcon(qta.icon("fa5s.bar-chart"))
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
diff --git a/pylustrator/components/plot_layout.py b/pylustrator/components/plot_layout.py
index 8e76c9d..4ba9bfa 100644
--- a/pylustrator/components/plot_layout.py
+++ b/pylustrator/components/plot_layout.py
@@ -1,11 +1,14 @@
import os
-import numpy as np
+from matplotlib import transforms
+import numpy as np
from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
-import matplotlib.transforms as transforms
from matplotlib.figure import Figure
+
try: # for matplotlib > 3.0
- from matplotlib.backends.backend_qtagg import (FigureCanvas as Canvas, NavigationToolbar2QT as NavigationToolbar)
+ from matplotlib.backends.backend_qtagg import FigureCanvas as Canvas
+ from matplotlib.backends.backend_qtagg import \
+ NavigationToolbar2QT as NavigationToolbar
except ModuleNotFoundError:
from matplotlib.backends.backend_qt5agg import (FigureCanvas as Canvas, NavigationToolbar2QT as NavigationToolbar)
@@ -23,6 +26,7 @@ def mouseReleaseEvent(self, e):
if self.grabber_pressed:
self.grabber_pressed.mouseReleaseEvent(e)
+
class MyView(QtWidgets.QGraphicsView):
grabber_found = False
grabber_pressed = None
@@ -57,15 +61,19 @@ def mousePressEvent(self, e):
e.ignore()
self.canvas_canvas.mousePressEvent(e)
+
class MyEvent:
def __init__(self, x, y):
self.x = x
self.y = y
+
+
class MyRect(QtWidgets.QGraphicsRectItem):
w = 10
+
def __init__(self, x, y, grabber):
self.grabber = grabber
- super().__init__(x-self.w/2, y-self.w/2, self.w, self.w)
+ super().__init__(x - self.w / 2, y - self.w / 2, self.w, self.w)
def mousePressEvent(self, e):
super().mousePressEvent(e)
@@ -79,6 +87,7 @@ def mouseReleaseEvent(self, e):
p = e.scenePos()
self.grabber.button_release_event(MyEvent(p.x(), self.h - p.y()))
+
class Canvas(QtWidgets.QWidget):
fitted_to_view = False
footer_label = None
@@ -87,8 +96,7 @@ class Canvas(QtWidgets.QWidget):
canvas = None
def __init__(self, signals: "Signals"):
- """ The wrapper around the matplotlib canvas to create a more image editor like canvas with background and side rulers
- """
+ """The wrapper around the matplotlib canvas to create a more image editor like canvas with background and side rulers."""
super().__init__()
signals.figure_changed.connect(self.setFigure)
@@ -167,7 +175,7 @@ def setFooters(self, footer, footer2):
self.footer_label2 = footer2
def updateRuler(self):
- """ update the ruler around the figure to show the dimensions """
+ """Update the ruler around the figure to show the dimensions."""
trans = transforms.Affine2D().scale(1. / 2.54, 1. / 2.54) + self.fig.dpi_scale_trans
l = 20
l1 = 20
@@ -217,7 +225,7 @@ def updateRuler(self):
positions = np.hstack([np.arange(0, start_x, -dx)[::-1], np.arange(0, end_x, dx)])
for i, pos_cm in enumerate(positions):
- #for i, pos_cm in enumerate(np.arange(start_x, end_x, dx)):
+ # for i, pos_cm in enumerate(np.arange(start_x, end_x, dx)):
x = (trans.transform((pos_cm, 0))[0] + offset)
if pos_cm % big_lines == 0:
painterX.drawLine(int(x), int(l - l1 - 1), int(x), int(l - 1))
@@ -245,8 +253,8 @@ def updateRuler(self):
big_lines = 1
medium_lines = 0.5
- pix_per_cm = trans.transform((0, 1))[1]-trans.transform((0, 0))[1]
- big_lines = int(np.ceil(self.fontMetrics().height()*5/pix_per_cm))
+ pix_per_cm = trans.transform((0, 1))[1] - trans.transform((0, 0))[1]
+ big_lines = int(np.ceil(self.fontMetrics().height() * 5 / pix_per_cm))
medium_lines = big_lines / 2
dy = big_lines / 10
@@ -258,7 +266,7 @@ def updateRuler(self):
text = str("%d" % np.round(pos_cm))
o = 0
for ti, t in enumerate(text):
- painterY.drawText(int(o), int(y + 3 + self.fontMetrics().height()*ti),
+ painterY.drawText(int(o), int(y + 3 + self.fontMetrics().height() * ti),
int(o + self.fontMetrics().width("0")), int(self.fontMetrics().height()),
QtCore.Qt.AlignCenter, t)
elif pos_cm % medium_lines == 0:
@@ -298,13 +306,13 @@ def updateRuler(self):
self.canvas_border.setMaximumSize(w + 2, h + 2)
def fitToView(self, change_dpi: bool = False):
- """ fit the figure to the view """
+ """ Fit the figure to the view. """
self.fitted_to_view = True
if change_dpi:
w, h = self.canvas.get_width_height()
factor = min((self.canvas_canvas.width() - 30) / w, (self.canvas_canvas.height() - 30) / h)
self.fig.set_dpi(self.fig.get_dpi() * factor)
- #self.fig.canvas.draw()
+ # self.fig.canvas.draw()
self.canvas.updateGeometry()
w, h = self.canvas.get_width_height()
@@ -315,7 +323,7 @@ def fitToView(self, change_dpi: bool = False):
int((self.canvas_canvas.height() - h) / 2 + 10))
self.updateRuler()
- #self.fig.canvas.draw()
+ # self.fig.canvas.draw()
else:
w, h = self.canvas.get_width_height()
@@ -327,24 +335,24 @@ def fitToView(self, change_dpi: bool = False):
self.updateRuler()
def canvas_key_press(self, event: QtCore.QEvent):
- """ when a key in the canvas widget is pressed """
+ """ When a key in the canvas widget is pressed. """
if event.key == "control":
self.control_modifier = True
def canvas_key_release(self, event: QtCore.QEvent):
- """ when a key in the canvas widget is released """
+ """ When a key in the canvas widget is released. """
if event.key == "control":
self.control_modifier = False
def moveCanvasCanvas(self, offset_x: float, offset_y: float):
- """ when the canvas is panned """
+ """ When the canvas is panned. """
p = self.canvas_container.pos()
self.canvas_container.move(int(p.x() + offset_x), int(p.y() + offset_y))
self.updateRuler()
def scroll_event(self, event: QtCore.QEvent):
- """ when the mouse wheel is used to zoom the figure """
+ """ When the mouse wheel is used to zoom the figure. """
if self.control_modifier:
new_dpi = self.fig.get_dpi() + 10 * event.step
# prevent zoom to be too far out
@@ -374,24 +382,24 @@ def scroll_event(self, event: QtCore.QEvent):
bb = self.fig.axes[0].get_position()
def resizeEvent(self, event: QtCore.QEvent):
- """ when the window is resized """
+ """ When the window is resized. """
if self.fitted_to_view:
self.fitToView(True)
else:
self.updateRuler()
def showEvent(self, event: QtCore.QEvent):
- """ when the window is shown """
+ """ When the window is shown. """
self.fitToView(True)
self.updateRuler()
def button_press_event(self, event: QtCore.QEvent):
- """ when a mouse button is pressed """
+ """ When a mouse button is pressed. """
if event.button == 2:
self.drag = np.array([event.x, event.y])
def mouse_move_event(self, event: QtCore.QEvent):
- """ when the mouse is moved """
+ """ When the mouse is moved. """
if self.drag is not None:
pos = np.array([event.x, event.y])
offset = pos - self.drag
@@ -402,17 +410,17 @@ def mouse_move_event(self, event: QtCore.QEvent):
self.footer_label.setText("%.2f, %.2f (cm) [%d, %d]" % (pos[0], pos[1], event.x, event.y))
if event.ydata is not None:
- self.footer_label2.setText("%.2f, %.2f" % (event.xdata, event.ydata))
+ self.footer_label2.setText(f"{event.xdata:.2f}, {event.ydata:.2f}")
else:
self.footer_label2.setText("")
def button_release_event(self, event: QtCore.QEvent):
- """ when the mouse button is released """
+ """ When the mouse button is released. """
if event.button == 2:
self.drag = None
def keyPressEvent(self, event: QtCore.QEvent):
- """ when a key is pressed """
+ """ When a key is pressed. """
if event.key() == QtCore.Qt.Key_Control:
self.control_modifier = True
if event.key() == QtCore.Qt.Key_Left:
@@ -428,27 +436,26 @@ def keyPressEvent(self, event: QtCore.QEvent):
self.fitToView(True)
def keyReleaseEvent(self, event: QtCore.QEvent):
- """ when a key is released """
+ """ When a key is released. """
if event.key() == QtCore.Qt.Key_Control:
self.control_modifier = False
def updateFigureSize(self):
- """ update the size of the figure """
+ """ Update the size of the figure. """
w, h = self.canvas.get_width_height()
self.canvas_container.setMinimumSize(w, h)
self.canvas_container.setMaximumSize(w, h)
def changedFigureSize(self, size: tuple):
- """ change the size of the figure """
+ """ Change the size of the figure. """
self.fig.set_size_inches(np.array(size) / 2.54)
self.fig.canvas.draw()
-
class ToolBar(QtWidgets.QToolBar):
def __init__(self, canvas: Canvas, figure: Figure):
- """ A widget that displays a toolbar similar to the default Matplotlib toolbar (for the zoom and pan tool)
+ """ A widget that displays a toolbar similar to the default Matplotlib toolbar (for the zoom and pan tool).
Args:
canvas: the canvas of the figure
@@ -489,8 +496,8 @@ def __init__(self, canvas: Canvas, figure: Figure):
self.prev_active = 'DRAG'
def icon(self, name: str):
- """ get an icon with the given filename """
- pm = QtGui.QPixmap(os.path.join(os.path.dirname(__file__), "..","icons", name))
+ """ Get an icon with the given filename. """
+ pm = QtGui.QPixmap(os.path.join(os.path.dirname(__file__), "..", "icons", name))
if hasattr(pm, 'setDevicePixelRatio'):
try: # older mpl < 3.5.0
pm.setDevicePixelRatio(self.canvas._dpi_ratio)
@@ -500,12 +507,12 @@ def icon(self, name: str):
return QtGui.QIcon(pm)
def setSelect(self):
- """ select the pylustrator selection and drag tool """
+ """ Select the pylustrator selection and drag tool. """
self.fig.figure_dragger.activate()
- if self.prev_active=="PAN":
+ if self.prev_active == "PAN":
self.navi_toolbar.pan()
- elif self.prev_active=="ZOOM":
+ elif self.prev_active == "ZOOM":
self.navi_toolbar.zoom()
self.prev_active = 'DRAG'
@@ -513,7 +520,7 @@ def setSelect(self):
self.navi_toolbar._active = 'DRAG'
def setPan(self):
- """ select the mpl pan tool """
+ """ Select the mpl pan tool. """
if self.prev_active == "DRAG":
self.fig.figure_dragger.deactivate()
@@ -523,7 +530,7 @@ def setPan(self):
self.prev_active = 'PAN'
def setZoom(self):
- """ select the mpl zoom tool """
+ """ Select the mpl zoom tool. """
if self.prev_active == "DRAG":
self.fig.figure_dragger.deactivate()
@@ -532,6 +539,7 @@ def setZoom(self):
self.prev_active = 'ZOOM'
+
class PlotLayout(QtWidgets.QWidget):
toolbar = None
diff --git a/pylustrator/components/qitem_properties.py b/pylustrator/components/qitem_properties.py
index bf5bf8b..084b4ad 100644
--- a/pylustrator/components/qitem_properties.py
+++ b/pylustrator/components/qitem_properties.py
@@ -21,26 +21,29 @@
import os
from typing import Any
-import numpy as np
-from packaging import version
+import numpy as np
import qtawesome as qta
from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
+from packaging import version
try: # starting from mpl version 3.6.0
from matplotlib.axes import Axes
-except:
+except Exception:
from matplotlib.axes._subplots import Axes
+
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.artist import Artist
from matplotlib.figure import Figure
from matplotlib.ticker import AutoLocator
-from pylustrator.change_tracker import getReference
-from pylustrator.QLinkableWidgets import QColorWidget, CheckWidget, TextWidget, DimensionsWidget, NumberWidget, ComboWidget
+from pylustrator.change_tracker import (UndoRedo, add_axes_default,
+ add_text_default, getReference)
from pylustrator.helper_functions import main_figure
-from pylustrator.change_tracker import UndoRedo, add_text_default, add_axes_default
+from pylustrator.QLinkableWidgets import (CheckWidget, ComboWidget,
+ DimensionsWidget, NumberWidget,
+ QColorWidget, TextWidget)
class TextPropertiesWidget(QtWidgets.QWidget):
@@ -49,7 +52,7 @@ class TextPropertiesWidget(QtWidgets.QWidget):
target_list = None
def __init__(self, layout: QtWidgets.QLayout):
- """ A widget to edit the properties of a Matplotlib text
+ """ A widget to edit the properties of a Matplotlib text.
Args:
layout: the layout to which to add the widget
@@ -64,7 +67,7 @@ def __init__(self, layout: QtWidgets.QLayout):
align_group = QtWidgets.QButtonGroup(self)
for align in self.align_names:
button = QtWidgets.QPushButton(qta.icon("fa5s.align-" + align), "")
- button.setToolTip("align "+align)
+ button.setToolTip("align " + align)
button.setCheckable(True)
button.clicked.connect(lambda x, name=align: self.changeAlign(name))
self.layout.addWidget(button)
@@ -94,13 +97,13 @@ def __init__(self, layout: QtWidgets.QLayout):
self.layout.addWidget(self.label)
self.label.clicked.connect(self.selectFont)
- #self.button_delete = QtWidgets.QPushButton(qta.icon("fa5s.trash"), "")
- #self.button_delete.clicked.connect(self.delete)
- #self.button_delete.setToolTip("delete")
- #self.layout.addWidget(self.button_delete)
+ # self.button_delete = QtWidgets.QPushButton(qta.icon("fa5s.trash"), "")
+ # self.button_delete.clicked.connect(self.delete)
+ # self.button_delete.setToolTip("delete")
+ # self.layout.addWidget(self.button_delete)
def convertMplWeightToQtWeight(self, weight: str) -> int:
- """ convert a font weight string to a weight enumeration of Qt """
+ """ Convert a font weight string to a weight enumeration of Qt. """
weight_dict = {'normal': QtGui.QFont.Normal, 'bold': QtGui.QFont.Bold, 'heavy': QtGui.QFont.ExtraBold,
'light': QtGui.QFont.Light, 'ultrabold': QtGui.QFont.Black, 'ultralight': QtGui.QFont.ExtraLight}
if weight in weight_dict:
@@ -108,7 +111,7 @@ def convertMplWeightToQtWeight(self, weight: str) -> int:
return weight_dict["normal"]
def convertQtWeightToMplWeight(self, weight: int) -> str:
- """ convert a Qt weight value to a string for use in matmplotlib """
+ """ Convert a Qt weight value to a string for use in matmplotlib. """
weight_dict = {QtGui.QFont.Normal: 'normal', QtGui.QFont.Bold: 'bold', QtGui.QFont.ExtraBold: 'heavy',
QtGui.QFont.Light: 'light', QtGui.QFont.Black: 'ultrabold', QtGui.QFont.ExtraLight: 'ultralight'}
if weight in weight_dict:
@@ -116,7 +119,7 @@ def convertQtWeightToMplWeight(self, weight: int) -> str:
return "normal"
def selectFont(self):
- """ open a font select dialog """
+ """ Open a font select dialog. """
font0 = QtGui.QFont()
font0.setFamily(self.target.get_fontname())
font0.setWeight(self.convertMplWeightToQtWeight(self.target.get_weight()))
@@ -137,7 +140,7 @@ def selectFont(self):
self.setTarget(self.target_list)
def setTarget(self, element: Artist):
- """ set the target artist for this widget """
+ """ Set the target artist for this widget. """
if isinstance(element, list):
self.target_list = element
element = element[0]
@@ -160,7 +163,7 @@ def setTarget(self, element: Artist):
self.target = element
def delete(self):
- """ delete the target text """
+ """ Delete the target text. """
if self.target is not None:
fig = main_figure(self.target)
fig.change_tracker.removeElement(self.target)
@@ -169,35 +172,35 @@ def delete(self):
fig.canvas.draw()
def changeWeight(self, checked: bool):
- """ set bold or normal """
+ """ Set bold or normal. """
if self.target:
with UndoRedo(self.target_list, "Change weight"):
for element in self.target_list:
element.set_weight("bold" if checked else "normal")
def changeStyle(self, checked: bool):
- """ set italic or normal """
+ """ Set italic or normal. """
if self.target:
with UndoRedo(self.target_list, "Change style"):
for element in self.target_list:
element.set_style("italic" if checked else "normal")
def changeColor(self, color: str):
- """ set the text color """
+ """ Set the text color. """
if self.target:
with UndoRedo(self.target_list, "Change color"):
for element in self.target_list:
element.set_color(color)
def changeAlign(self, align: str):
- """ set the text algin """
+ """ Set the text align. """
if self.target:
with UndoRedo(self.target_list, "Change alignment"):
for element in self.target_list:
element.set_ha(align)
def changeFontSize(self, value: int):
- """ set the font size """
+ """ Set the font size. """
if self.target:
with UndoRedo(self.target_list, "Change font size"):
for element in self.target_list:
@@ -211,7 +214,7 @@ class TextPropertiesWidget2(QtWidgets.QWidget):
target_list = None
def __init__(self, layout: QtWidgets.QLayout):
- """ A widget to edit the properties of a Matplotlib text
+ """ A widget to edit the properties of a Matplotlib text.
Args:
layout: the layout to which to add the widget
@@ -268,7 +271,7 @@ def __init__(self, layout: QtWidgets.QLayout):
self.propertiesChanged.connect(lambda: self.target and main_figure(self.target).signals.figure_selection_property_changed.emit())
def convertMplWeightToQtWeight(self, weight: str) -> int:
- """ convert a font weight string to a weight enumeration of Qt """
+ """ Convert a font weight string to a weight enumeration of Qt. """
weight_dict = {'normal': QtGui.QFont.Normal, 'bold': QtGui.QFont.Bold, 'heavy': QtGui.QFont.ExtraBold,
'light': QtGui.QFont.Light, 'ultrabold': QtGui.QFont.Black, 'ultralight': QtGui.QFont.ExtraLight}
if weight in weight_dict:
@@ -276,7 +279,7 @@ def convertMplWeightToQtWeight(self, weight: str) -> int:
return weight_dict["normal"]
def convertQtWeightToMplWeight(self, weight: int) -> str:
- """ convert a Qt weight value to a string for use in matmplotlib """
+ """ Convert a Qt weight value to a string for use in matmplotlib. """
weight_dict = {QtGui.QFont.Normal: 'normal', QtGui.QFont.Bold: 'bold', QtGui.QFont.ExtraBold: 'heavy',
QtGui.QFont.Light: 'light', QtGui.QFont.Black: 'ultrabold', QtGui.QFont.ExtraLight: 'ultralight'}
if weight in weight_dict:
@@ -284,7 +287,7 @@ def convertQtWeightToMplWeight(self, weight: int) -> str:
return "normal"
def selectFont(self):
- """ open a font select dialog """
+ """ Open a font select dialog. """
font0 = QtGui.QFont()
font0.setFamily(self.target.get_fontname())
font0.setWeight(self.convertMplWeightToQtWeight(self.target.get_weight()))
@@ -302,11 +305,11 @@ def selectFont(self):
self.properties["fontstyle"] = style
self.propertiesChanged.emit()
- #main_figure(self.target).canvas.draw()
+ # main_figure(self.target).canvas.draw()
self.setTarget(self.target_list)
def setTarget(self, element: Artist):
- """ set the target artist for this widget """
+ """ Set the target artist for this widget. """
self.noSignal = True
try:
if len(element) == 0:
@@ -330,7 +333,7 @@ def setTarget(self, element: Artist):
self.button_color.setColor(element.get_color())
for name, name2, type_, default_ in self.property_names:
- value = getattr(element, "get_"+name2)()
+ value = getattr(element, "get_" + name2)()
self.properties[name] = value
self.target = element
@@ -338,7 +341,7 @@ def setTarget(self, element: Artist):
self.noSignal = False
def delete(self):
- """ delete the target text """
+ """ Delete the target text. """
if self.target is not None:
fig = main_figure(self.target)
fig.change_tracker.removeElement(self.target)
@@ -347,27 +350,27 @@ def delete(self):
fig.canvas.draw()
def changeWeight(self, checked: bool):
- """ set bold or normal """
+ """ Set bold or normal. """
self.properties["fontweight"] = "bold" if checked else "normal"
self.propertiesChanged.emit()
def changeStyle(self, checked: bool):
- """ set italic or normal """
+ """ Set italic or normal. """
self.properties["fontstyle"] = "italic" if checked else "normal"
self.propertiesChanged.emit()
def changeColor(self, color: str):
- """ set the text color """
+ """ Set the text color. """
self.properties["color"] = color
self.propertiesChanged.emit()
def changeAlign(self, align: str):
- """ set the text algin """
+ """ Set the text align. """
self.properties["horizontalalignment"] = align
self.propertiesChanged.emit()
def changeFontSize(self, value: int):
- """ set the font size """
+ """ Set the font size. """
if self.noSignal:
return
self.properties["fontsize"] = value
@@ -380,7 +383,7 @@ class LegendPropertiesWidget(QtWidgets.QWidget):
target_list = None
def __init__(self, layout: QtWidgets.QLayout):
- """ A widget that allows to change to properties of a matplotlib legend
+ """ A widget that allows to change to properties of a matplotlib legend.
Args:
layout: the layout to which to add the widget
@@ -390,8 +393,8 @@ def __init__(self, layout: QtWidgets.QLayout):
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
- from packaging import version
import matplotlib as mpl
+ from packaging import version
ncols_name = "ncols"
if version.parse(mpl._get_version()) < version.parse("3.6.0"):
ncols_name = "ncol"
@@ -442,7 +445,7 @@ def __init__(self, layout: QtWidgets.QLayout):
if icon is not None and getattr(widget, "label", None):
from pathlib import Path
pix = QtGui.QPixmap(str(Path(__file__).parent.parent / "icons" / icon))
- pix = pix.scaledToWidth(int(28*QtGui.QGuiApplication.primaryScreen().logicalDotsPerInch()/96), QtCore.Qt.SmoothTransformation)
+ pix = pix.scaledToWidth(int(28 * QtGui.QGuiApplication.primaryScreen().logicalDotsPerInch() / 96), QtCore.Qt.SmoothTransformation)
widget.setToolTip(name)
widget.label.setToolTip(name)
widget.label.setPixmap(pix)
@@ -450,7 +453,7 @@ def __init__(self, layout: QtWidgets.QLayout):
self.widgets[name] = widget
def changePropertiy(self, name: str, value: Any):
- """ change the property with the given name to the provided value """
+ """ Change the property with the given name to the provided value. """
if self.target is None:
return
@@ -458,6 +461,7 @@ def changePropertiy(self, name: str, value: Any):
self.properties[name] = value
new_properties = self.properties.copy()
target = self.target
+
def setProperties(properties):
nonlocal target
bbox = target.get_frame().get_bbox()
@@ -482,7 +486,7 @@ def redo():
main_figure(target).change_tracker.addEdit([undo, redo, f"Legend {name}"])
def setTarget(self, element: Artist):
- """ set the target artist for this widget """
+ """ Set the target artist for this widget. """
if isinstance(element, list):
self.target_list = element
element = element[0]
@@ -516,7 +520,7 @@ def setTarget(self, element: Artist):
class QTickEdit(QtWidgets.QWidget):
def __init__(self, axis: str, signal_target_changed: QtCore.Signal):
- """ A widget to change the tick properties
+ """ A widget to change the tick properties.
Args:
axis: whether to use the "x" or "y" axis
@@ -543,7 +547,7 @@ def __init__(self, axis: str, signal_target_changed: QtCore.Signal):
self.input_scale = ComboWidget(self.layout, axis + "-Scale", ["linear", "log", "symlog", "logit"])
self.input_scale.editingFinished.connect(self.scaleChanged)
- #self.input_scale.link(axis + "scale", signal_target_changed)
+ # self.input_scale.link(axis + "scale", signal_target_changed)
self.input_font = TextPropertiesWidget2(self.layout)
self.input_font.propertiesChanged.connect(self.fontStateChanged)
@@ -556,7 +560,7 @@ def __init__(self, axis: str, signal_target_changed: QtCore.Signal):
self.button_ok.clicked.connect(self.hide)
def parseTickLabel(self, line: str) -> (float, str):
- """ interpret the tick value specified in line """
+ """ Interpret the tick value specified in line. """
import re
line = line.replace("−", "-")
match = re.match(r"\$\\mathdefault{(([-.\d]*)\\times)?([-.\d]+)\^{([-.\d]+)}}\$", line)
@@ -564,11 +568,11 @@ def parseTickLabel(self, line: str) -> (float, str):
_, factor, base, exponent = match.groups()
if factor is not None:
number = float(factor) * float(base) ** float(exponent)
- line = "%s x %s^%s" % (factor, base, exponent)
+ line = f"{factor} x {base}^{exponent}"
else:
try:
number = float(base) ** float(exponent)
- line = "%s^%s" % (base, exponent)
+ line = f"{base}^{exponent}"
except ValueError:
try:
number = float(line)
@@ -582,7 +586,7 @@ def parseTickLabel(self, line: str) -> (float, str):
return number, line
def formatTickLabel(self, line: str) -> (float, str):
- """ interpret the tick label specified in line"""
+ """ Interpret the tick label specified in line."""
import re
line = line.replace("−", "-")
match = re.match(r"\s*(([-.\d]*)\s*x)?\s*([-.\d]+)\s*\^\s*([-.\d]+)\s*\"(.+)?\"", line)
@@ -615,7 +619,7 @@ def formatTickLabel(self, line: str) -> (float, str):
return number, line
def setTarget(self, element: Artist):
- """ set the target Artist for this widget"""
+ """ Set the target Artist for this widget."""
self.element = element
self.fig = main_figure(element)
min, max = getattr(self.element, "get_" + self.axis + "lim")()
@@ -634,9 +638,9 @@ def setTarget(self, element: Artist):
continue
if min <= t <= max:
if l != t:
- text.append("%s \"%s\"" % (str(t), l_text))
+ text.append(f"{str(t)} \"{l_text}\"")
else:
- text.append("%s" % l_text)
+ text.append(f"{l_text}")
self.input_ticks.setText(", ".join(text))
ticks = getattr(self.element, "get_" + self.axis + "ticks")(minor=True)
@@ -650,9 +654,9 @@ def setTarget(self, element: Artist):
pass
if min <= t <= max:
if l != t:
- text.append("%s \"%s\"" % (str(t), l_text))
+ text.append(f"{str(t)} \"{l_text}\"")
else:
- text.append("%s" % l_text)
+ text.append(f"{l_text}")
self.input_ticks2.setText(", ".join(text))
elements = [self.element]
@@ -665,7 +669,7 @@ def setTarget(self, element: Artist):
self.input_font.setTarget(ticks)
def parseTicks(self, string: str):
- """ parse a list of given ticks """
+ """ Parse a list of given ticks. """
try:
ticks = []
labels = []
@@ -690,13 +694,13 @@ def parseTicks(self, string: str):
return ticks, labels
def str(self, object: Any):
- """ serialize an object and interpret nan values """
+ """ Serialize an object and interpret nan values. """
if str(object) == "nan":
return "np.nan"
return str(object)
def ticksChanged2(self):
- """ when the minor ticks changed """
+ """ When the minor ticks changed. """
ticks, labels = self.parseTicks(self.input_ticks2.text())
elements = [self.element]
@@ -727,7 +731,7 @@ def ticksChanged2(self):
min, max = getattr(element, "get_" + self.axis + "lim")()
if min != self.range[0] or max != self.range[1]:
self.fig.change_tracker.addChange(element,
- ".set_" + self.axis + "lim(%s, %s)" % (str(min), str(max)))
+ ".set_" + self.axis + f"lim({str(min)}, {str(max)})")
else:
self.fig.change_tracker.addChange(element,
".set_" + self.axis + "lim(%s, %s)" % (
@@ -750,21 +754,21 @@ def getFontProperties(self):
value = self.input_font.properties[name]
if default_ is not None and value == default_:
continue
- #if default_ is None and value == plt.rcParams["legend." + name]:
+ # if default_ is None and value == plt.rcParams["legend." + name]:
# continue
if type_ == str:
prop_copy[name] = '"' + value + '"'
else:
prop_copy[name] = value
prop_copy2[name] = value
- return (", ".join("%s=%s" % (k, v) for k, v in prop_copy.items())), prop_copy2
+ return (", ".join(f"{k}={v}" for k, v in prop_copy.items())), prop_copy2
def fontStateChanged(self):
self.ticksChanged()
- #fig.change_tracker.addChange(axes, ".legend(%s)" % (", ".join("%s=%s" % (k, v) for k, v in prop_copy.items())))
+ # fig.change_tracker.addChange(axes, ".legend(%s)" % (", ".join("%s=%s" % (k, v) for k, v in prop_copy.items())))
def scaleChanged(self):
- """ when the scale changed """
+ """ When the scale changed. """
elements = [self.element]
elements += [element.target for element in main_figure(self.element).selection.targets if
element.target != self.element and isinstance(element.target, Axes)]
@@ -776,7 +780,7 @@ def scaleChanged(self):
element.set(**kwargs)
def ticksChanged(self):
- """ when the major ticks changed """
+ """ When the major ticks changed. """
ticks, labels = self.parseTicks(self.input_ticks.text())
elements = [self.element]
@@ -788,7 +792,7 @@ def ticksChanged(self):
current_ticks = getattr(elem, "get_" + self.axis + "ticks")()
current_ticklabels = [t.get_text() for t in getattr(elem, "get_" + self.axis + "ticklabels")()]
if len(current_ticks) != len(ticks) or (current_ticks != ticks).any() or \
- len(current_ticklabels) != len(labels) or current_ticklabels != labels:
+ len(current_ticklabels) != len(labels) or current_ticklabels != labels:
changed = True
if changed is False:
return
@@ -852,7 +856,7 @@ def redo():
min, max = getattr(element, "get_" + self.axis + "lim")()
if min != self.range[0] or max != self.range[1]:
self.fig.change_tracker.addChange(element,
- ".set_" + self.axis + "lim(%s, %s)" % (str(min), str(max)))
+ ".set_" + self.axis + f"lim({str(min)}, {str(max)})")
else:
self.fig.change_tracker.addChange(element,
".set_" + self.axis + "lim(%s, %s)" % (
@@ -871,7 +875,7 @@ class QAxesProperties(QtWidgets.QWidget):
targetChanged_wrapped = QtCore.Signal(object)
def __init__(self, layout: QtWidgets.QLayout, axis: str, signal_target_changed: QtCore.Signal):
- """ a widget to change the properties of an axes (label, limits)
+ """ A widget to change the properties of an axes (label, limits).
Args:
layout: the layout to which to add this widget
@@ -912,12 +916,12 @@ def wrapTargetLabel(axis_object):
self.tick_edit = QTickEdit(axis, signal_target_changed)
def showTickWidget(self):
- """ open the tick edit dialog """
+ """ Open the tick edit dialog. """
self.tick_edit.setTarget(self.element)
self.tick_edit.show()
def setTarget(self, element: Artist):
- """ set the target Artist of this widget """
+ """ Set the target Artist of this widget. """
self.element = element
if isinstance(element, Axes):
@@ -930,7 +934,7 @@ class QAxesProperties(QtWidgets.QWidget):
targetChanged_wrapped = QtCore.Signal(object)
def __init__(self, layout: QtWidgets.QLayout, axis: str, signal_target_changed: QtCore.Signal):
- """ a widget to change the properties of an axes (label, limits)
+ """ A widget to change the properties of an axes (label, limits).
Args:
layout: the layout to which to add this widget
@@ -962,12 +966,12 @@ def __init__(self, layout: QtWidgets.QLayout, axis: str, signal_target_changed:
self.tick_edit = QTickEdit(axis, signal_target_changed)
def showTickWidget(self):
- """ open the tick edit dialog """
+ """ Open the tick edit dialog. """
self.tick_edit.setTarget(self.element)
self.tick_edit.show()
def setTarget(self, element: Artist):
- """ set the target Artist of this widget """
+ """ Set the target Artist of this widget. """
self.element = element
if isinstance(element, Axes):
@@ -997,13 +1001,14 @@ def saveLim(self):
for element in elements:
element.set(**{f"{self.axis}lim": limits})
+
class QItemProperties(QtWidgets.QWidget):
targetChanged = QtCore.Signal(object)
valueChanged = QtCore.Signal(tuple)
element = None
def __init__(self, layout: QtWidgets.QLayout, signals: "Signals"):
- """ a widget that holds all the properties to set and the tree view
+ """ A widget that holds all the properties to set and the tree view.
Args:
layout: the layout to which to add the widget
@@ -1121,7 +1126,7 @@ def __init__(self, layout: QtWidgets.QLayout, signals: "Signals"):
self.setMinimumWidth(100)
def select_element(self, element: Artist):
- """ select an element """
+ """ Select an element. """
if element is None:
self.setElement(self.fig)
else:
@@ -1131,7 +1136,7 @@ def setFigure(self, fig):
self.fig = fig
def buttonAddImageClicked(self):
- """ when the button 'add image' is clicked """
+ """ When the button 'add image' is clicked. """
fig = self.fig
def addChange(element, command):
@@ -1154,7 +1159,7 @@ def addChange(element, command):
".add_axes([0.25, 0.25, 0.5, 0.5], label=\"%s\") # id=%s.new" % (
filename, getReference(axes)), axes, ".new")
add_axes_default(axes)
- addChange(axes, ".imshow(plt.imread(\"%s\"))" % filename)
+ addChange(axes, f".imshow(plt.imread(\"{filename}\"))")
addChange(axes, '.set_xticks([])')
addChange(axes, '.set_yticks([])')
if 0:
@@ -1174,7 +1179,7 @@ def addChange(element, command):
self.input_text.input1.setFocus()
def buttonAddTextClicked(self):
- """ when the button 'add text' is clicked """
+ """ When the button 'add text' is clicked. """
if isinstance(self.element, Axes):
text = self.element.text(0.5, 0.5, "New Text", transform=self.element.transAxes)
text.is_new_text = True
@@ -1199,10 +1204,10 @@ def buttonAddTextClicked(self):
self.input_text.input1.setFocus()
def buttonAddAnnotationClicked(self):
- """ when the button 'add annoations' is clicked """
+ """ When the button 'add annoations' is clicked. """
text = self.element.annotate("New Annotation", (self.element.get_xlim()[0], self.element.get_ylim()[0]),
(np.mean(self.element.get_xlim()), np.mean(self.element.get_ylim())),
- arrowprops=dict(arrowstyle="->"))
+ arrowprops={"arrowstyle": '->'})
self.fig.change_tracker.addChange(self.element,
".annotate('New Annotation', %s, %s, arrowprops=dict(arrowstyle='->')) # id=%s.new" % (
text.xy, text.get_position(), getReference(text)),
@@ -1217,9 +1222,9 @@ def buttonAddAnnotationClicked(self):
self.input_text.input1.setFocus()
def buttonAddRectangleClicked(self):
- """ when the button 'add rectangle' is clicked """
+ """ When the button 'add rectangle' is clicked. """
p = mpl.patches.Rectangle((self.element.get_xlim()[0], self.element.get_ylim()[0]),
- width=np.mean(self.element.get_xlim()), height=np.mean(self.element.get_ylim()), )
+ width=np.mean(self.element.get_xlim()), height=np.mean(self.element.get_ylim()))
self.element.add_patch(p)
self.fig.change_tracker.addChange(self.element,
@@ -1236,7 +1241,7 @@ def buttonAddRectangleClicked(self):
self.input_text.input1.setFocus()
def buttonAddArrowClicked(self):
- """ when the button 'add arrow' is clicked """
+ """ When the button 'add arrow' is clicked. """
p = mpl.patches.FancyArrowPatch((self.element.get_xlim()[0], self.element.get_ylim()[0]),
(np.mean(self.element.get_xlim()), np.mean(self.element.get_ylim())),
arrowstyle="Simple,head_length=10,head_width=10,tail_width=2",
@@ -1257,9 +1262,9 @@ def buttonAddArrowClicked(self):
self.input_text.input1.setFocus()
def buttonDespineClicked(self):
- """ despine the target """
-
+ """ Despine the target. """
elements = [element.target for element in main_figure(self.element).selection.targets if isinstance(element.target, Axes)]
+
def is_despined(elem):
return elem.spines['right'].get_visible() and elem.spines['top'].get_visible()
despined = [is_despined(elem) for elem in elements]
@@ -1272,7 +1277,7 @@ def is_despined(elem):
element.spines[spine].set_visible(new_value)
def buttonGridClicked(self):
- """ toggle the grid of the target """
+ """ Toggle the grid of the target. """
elements = [element.target for element in main_figure(self.element).selection.targets
if isinstance(element.target, Axes)]
has_grid = getattr(self.element.xaxis, "_gridOnMajor", False) or getattr(self.element.xaxis, "_major_tick_kw", {"gridOn": False})['gridOn']
@@ -1285,6 +1290,7 @@ def set_false():
for element in elements:
element.grid(False)
self.fig.change_tracker.addChange(element, ".grid(False)")
+
def set_true():
for element in elements:
element.grid(True)
@@ -1299,7 +1305,7 @@ def set_true():
self.fig.canvas.draw()
def buttonLegendClicked(self):
- """ add a legend to the target """
+ """ Add a legend to the target. """
self.element.legend()
self.fig.change_tracker.addChange(self.element, ".legend()")
self.fig.figure_dragger.make_dragable(self.element.get_legend())
@@ -1307,7 +1313,7 @@ def buttonLegendClicked(self):
self.signals.figure_element_child_created.emit(self.element)
def changePickable(self):
- """ make the target pickable """
+ """ Make the target pickable. """
if self.input_picker.isChecked():
self.element._draggable.connect()
else:
@@ -1315,7 +1321,7 @@ def changePickable(self):
self.signals.figure_element_child_created.emit(self.element)
def setElement(self, element: Artist):
- """ set the target Artist of this widget """
+ """ Set the target Artist of this widget. """
self.label.setText(str(element))
self.element = element
try:
@@ -1360,5 +1366,3 @@ def setElement(self, element: Artist):
self.input_font_properties.hide()
self.targetChanged.emit(element)
-
-
diff --git a/pylustrator/components/qpos_and_size.py b/pylustrator/components/qpos_and_size.py
index 95c8943..d87c8d5 100644
--- a/pylustrator/components/qpos_and_size.py
+++ b/pylustrator/components/qpos_and_size.py
@@ -1,19 +1,20 @@
from typing import Optional
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
import matplotlib as mpl
-import matplotlib.transforms as transforms
-from matplotlib.figure import Figure
+from matplotlib import transforms
from matplotlib.artist import Artist
+from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
+from matplotlib.figure import Figure
+
try: # starting from mpl version 3.6.0
from matplotlib.axes import Axes
-except:
+except Exception:
from matplotlib.axes._subplots import Axes
+
from matplotlib.text import Text
-import matplotlib.transforms as transforms
from pylustrator.helper_functions import changeFigureSize, main_figure
-from pylustrator.QLinkableWidgets import DimensionsWidget, ComboWidget
+from pylustrator.QLinkableWidgets import ComboWidget, DimensionsWidget
class QPosAndSize(QtWidgets.QWidget):
@@ -23,7 +24,7 @@ class QPosAndSize(QtWidgets.QWidget):
scale_type = 0
def __init__(self, layout: QtWidgets.QLayout, signals: "Signals"):
- """ a widget that holds all the properties to set and the tree view
+ """ A widget that holds all the properties to set and the tree view.
Args:
layout: the layout to which to add the widget
@@ -59,7 +60,7 @@ def setFigure(self, figure):
self.fig = figure
def select_element(self, element):
- """ select an element """
+ """ Select an element. """
if element is None:
self.setElement(self.fig)
else:
@@ -69,23 +70,23 @@ def selection_moved(self):
self.setElement(self.element)
def changeTransform(self):
- """ change the transform and the units of the position and size widgets """
+ """ Change the transform and the units of the position and size widgets. """
name = self.input_transform.text()
- self.transform_index = ["cm", "in", "px", "none"].index(name)#transform_index
+ self.transform_index = ["cm", "in", "px", "none"].index(name) # transform_index
if name == "none":
name = ""
self.input_shape.setUnit(name)
self.input_position.setUnit(name)
self.setElement(self.element)
- def changeTransform2(self):#, state: int, name: str):
- """ when the dimension change type is changed from 'scale' to 'bottom right' or 'bottom left' """
+ def changeTransform2(self): # , state: int, name: str):
+ """ When the dimension change type is changed from 'scale' to 'bottom right' or 'bottom left'. """
name = self.input_shape_transform.text()
self.scale_type = ["scale", "bottom right", "top left"].index(name)
- #self.scale_type = state
+ # self.scale_type = state
def changePos(self, value_x: float, value_y: float):
- """ change the position of an axes """
+ """ Change the position of an axes. """
elements = [self.element]
elements += [element.target for element in main_figure(self.element).selection.targets]
@@ -140,7 +141,7 @@ def undo():
self.fig.canvas.draw()
def changeSize(self, value: list):
- """ change the size of an axes or figure """
+ """ Change the size of an axes or figure. """
if isinstance(self.element, Figure):
if self.scale_type == 0:
@@ -160,7 +161,7 @@ def changeSize(self, value: list):
pos.x0, pos.y0, pos.width, pos.height))
for text in self.fig.texts:
pos = text.get_position()
- self.fig.change_tracker.addChange(text, ".set_position([%f, %f])" % (pos[0], pos[1]))
+ self.fig.change_tracker.addChange(text, f".set_position([{pos[0]:f}, {pos[1]:f}])")
self.fig.selection.update_selection_rectangles()
self.fig.canvas.draw()
@@ -206,9 +207,8 @@ def undo():
self.fig.signals.figure_selection_property_changed.emit()
self.fig.canvas.draw()
-
def getTransform(self, element: Artist) -> Optional[mpl.transforms.Transform]:
- """ get the transform of an Artist """
+ """ Get the transform of an Artist. """
if isinstance(element, Figure):
if self.transform_index == 0:
return transforms.Affine2D().scale(2.54, 2.54)
@@ -232,8 +232,8 @@ def getTransform(self, element: Artist) -> Optional[mpl.transforms.Transform]:
return None
def setElement(self, element: Artist):
- """ set the target Artist of this widget """
- #self.label.setText(str(element))
+ """ Set the target Artist of this widget. """
+ # self.label.setText(str(element))
self.element = element
self.input_shape_transform.setDisabled(True)
@@ -265,5 +265,5 @@ def setElement(self, element: Artist):
self.input_position.setValue((pos.x0, pos.y0))
self.input_transform.setEnabled(True)
self.input_position.setEnabled(True)
- except:
- self.input_position.setDisabled(True)
\ No newline at end of file
+ except Exception:
+ self.input_position.setDisabled(True)
diff --git a/pylustrator/components/tree_view.py b/pylustrator/components/tree_view.py
index 454e876..1cd5c0c 100644
--- a/pylustrator/components/tree_view.py
+++ b/pylustrator/components/tree_view.py
@@ -1,26 +1,25 @@
from typing import Optional
-import qtawesome as qta
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
-
import matplotlib as mpl
+import qtawesome as qta
from matplotlib.artist import Artist
+from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
class myTreeWidgetItem(QtGui.QStandardItem):
def __init__(self, parent: QtWidgets.QWidget = None):
- """ a tree view item to display the contents of the figure """
+ """ A tree view item to display the contents of the figure. """
QtGui.QStandardItem.__init__(self, parent)
def __lt__(self, otherItem: QtGui.QStandardItem):
- """ how to sort the items """
+ """ How to sort the items. """
if self.sort is None:
return 0
return self.sort < otherItem.sort
class MyTreeView(QtWidgets.QTreeView):
- #item_selected = lambda x, y: 0
+ # item_selected = lambda x, y: 0
item_clicked = lambda x, y: 0
item_activated = lambda x, y: 0
item_hoverEnter = lambda x, y: 0
@@ -35,7 +34,7 @@ def item_selected(self, x):
self.fig.figure_dragger.select_element(x)
def __init__(self, signals: "Signals", layout: QtWidgets.QLayout):
- """ A tree view to display the contents of a figure
+ """ A tree view to display the contents of a figure.
Args:
parent: the parent widget
@@ -43,7 +42,7 @@ def __init__(self, signals: "Signals", layout: QtWidgets.QLayout):
fig: the target figure
"""
super().__init__()
- #self.setMaximumWidth(300)
+ # self.setMaximumWidth(300)
signals.figure_changed.connect(self.setFigure)
signals.figure_element_selected.connect(self.select_element)
@@ -78,7 +77,7 @@ def __init__(self, signals: "Signals", layout: QtWidgets.QLayout):
self.item_lookup = {}
def select_element(self, element: Artist):
- """ select an element """
+ """ Select an element. """
if element is None:
self.setCurrentIndex(self.fig)
else:
@@ -95,7 +94,7 @@ def setFigure(self, fig):
self.setCurrentIndex(self.fig)
def selectionChanged(self, selection: QtCore.QItemSelection, y: QtCore.QItemSelection):
- """ when the selection in the tree view changes """
+ """ When the selection in the tree view changes. """
try:
entry = selection.indexes()[0].model().itemFromIndex(selection.indexes()[0]).entry
except IndexError:
@@ -105,7 +104,7 @@ def selectionChanged(self, selection: QtCore.QItemSelection, y: QtCore.QItemSele
self.item_selected(entry)
def setCurrentIndex(self, entry: Artist):
- """ set the currently selected entry """
+ """ Set the currently selected entry. """
while entry:
item = self.getItemFromEntry(entry)
if item is not None:
@@ -120,23 +119,23 @@ def setCurrentIndex(self, entry: Artist):
return
def treeClicked(self, index: QtCore.QModelIndex):
- """ upon selecting one of the tree elements """
+ """ Upon selecting one of the tree elements. """
data = index.model().itemFromIndex(index).entry
return self.item_clicked(data)
def treeActivated(self, index: QtCore.QModelIndex):
- """ upon selecting one of the tree elements """
+ """ Upon selecting one of the tree elements. """
data = index.model().itemFromIndex(index).entry
return self.item_activated(data)
def eventFilter(self, object: QtWidgets.QWidget, event: QtCore.QEvent):
- """ event filter for tree view port to handle mouse over events and marker highlighting"""
+ """ Event filter for tree view port to handle mouse over events and marker highlighting."""
if event.type() == QtCore.QEvent.HoverMove:
index = self.indexAt(event.pos())
try:
item = index.model().itemFromIndex(index)
entry = item.entry
- except:
+ except Exception:
item = None
entry = None
@@ -157,24 +156,24 @@ def eventFilter(self, object: QtWidgets.QWidget, event: QtCore.QEvent):
return False
def queryToExpandEntry(self, entry: Artist) -> list:
- """ when expanding a tree item """
+ """ When expanding a tree item. """
if entry is None:
return [self.fig]
return entry.get_children()
def getParentEntry(self, entry: Artist) -> Artist:
- """ get the parent of an item """
+ """ Get the parent of an item. """
return getattr(entry, "tree_parent", None)
def getNameOfEntry(self, entry: Artist) -> str:
- """ convert an entry to a string """
+ """ Convert an entry to a string. """
try:
return str(entry)
except AttributeError:
return "unknown"
def getIconOfEntry(self, entry: Artist) -> QtGui.QIcon:
- """ get the icon of an entry """
+ """ Get the icon of an entry. """
if getattr(entry, "_draggable", None):
if entry._draggable.connected:
return qta.icon("fa5.hand-paper-o")
@@ -184,11 +183,11 @@ def getEntrySortRole(self, entry: Artist):
return None
def getKey(self, entry: Artist) -> Artist:
- """ get the key of an entry, which is the entry itself """
+ """ Get the key of an entry, which is the entry itself. """
return entry
def getItemFromEntry(self, entry: Artist) -> Optional[QtWidgets.QTreeWidgetItem]:
- """ get the tree view item for the given artist """
+ """ Get the tree view item for the given artist. """
if entry is None:
return None
key = self.getKey(entry)
@@ -198,12 +197,12 @@ def getItemFromEntry(self, entry: Artist) -> Optional[QtWidgets.QTreeWidgetItem]
return None
def setItemForEntry(self, entry: Artist, item: QtWidgets.QTreeWidgetItem):
- """ store a new artist and tree view widget pair """
+ """ Store a new artist and tree view widget pair. """
key = self.getKey(entry)
self.item_lookup[key] = item
def expand(self, entry: Artist, force_reload: bool = True):
- """ expand the children of a tree view item """
+ """ Expand the children of a tree view item. """
query = self.queryToExpandEntry(entry)
parent_item = self.getItemFromEntry(entry)
parent_entry = entry
@@ -245,7 +244,7 @@ def expand(self, entry: Artist, force_reload: bool = True):
self.addChild(parent_item, entry)
def addChild(self, parent_item: QtWidgets.QWidget, entry: Artist, row=None):
- """ add a child to a tree view node """
+ """ Add a child to a tree view node. """
if parent_item is None:
parent_item = self.model
@@ -281,7 +280,7 @@ def addChild(self, parent_item: QtWidgets.QWidget, entry: Artist, row=None):
return item
def TreeExpand(self, index):
- """ expand a tree view node """
+ """ Expand a tree view node. """
# Get item and entry
item = index.model().itemFromIndex(index)
entry = item.entry
@@ -298,7 +297,7 @@ def TreeExpand(self, index):
thread.start()
def updateEntry(self, entry: Artist, update_children: bool = False, insert_before: Artist = None, insert_after: Artist = None):
- """ update a tree view node """
+ """ Update a tree view node. """
# get the tree view item for the database entry
item = self.getItemFromEntry(entry)
# if we haven't one yet, we have to create it
@@ -367,7 +366,7 @@ def updateEntry(self, entry: Artist, update_children: bool = False, insert_befor
self.expand(entry, force_reload=True)
def deleteEntry(self, entry: Artist):
- """ delete an entry from the tree """
+ """ Delete an entry from the tree. """
# get the tree view item for the database entry
item = self.getItemFromEntry(entry)
if item is None:
@@ -390,4 +389,4 @@ def deleteEntry(self, entry: Artist):
if parent_item:
name = self.getNameOfEntry(parent_entry)
if name is not None:
- parent_item.setLabel(name)
\ No newline at end of file
+ parent_item.setLabel(name)
diff --git a/pylustrator/drag_helper.py b/pylustrator/drag_helper.py
index 03baf6d..d7c648c 100644
--- a/pylustrator/drag_helper.py
+++ b/pylustrator/drag_helper.py
@@ -19,21 +19,24 @@
# You should have received a copy of the GNU General Public License
# along with Pylustrator. If not, see
-import numpy as np
+import time
+from typing import Sequence
+
import matplotlib.pyplot as plt
+import numpy as np
from matplotlib.artist import Artist
-from matplotlib.figure import Figure
from matplotlib.axes import Axes
-from matplotlib.text import Text
-from matplotlib.patches import Rectangle, Ellipse
-from matplotlib.backend_bases import MouseEvent, KeyEvent
-from typing import Sequence
+from matplotlib.backend_bases import KeyEvent, MouseEvent
from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
+from matplotlib.figure import Figure
+from matplotlib.patches import Ellipse, Rectangle
+from matplotlib.text import Text
-from .snap import TargetWrapper, getSnaps, checkSnaps, checkSnapsActive, SnapBase
-from .change_tracker import ChangeTracker
from pylustrator.change_tracker import UndoRedo
-import time
+
+from .change_tracker import ChangeTracker
+from .snap import (SnapBase, TargetWrapper, checkSnaps, checkSnapsActive,
+ getSnaps)
DIR_X0 = 1
DIR_Y0 = 2
@@ -43,8 +46,9 @@
blit = False
-class GrabFunctions(object):
- """ basic functionality used by all grabbers """
+class GrabFunctions():
+ """ Basic functionality used by all grabbers. """
+
figure = None
target = None
dir = None
@@ -60,13 +64,13 @@ def __init__(self, parent, dir: int, no_height=False):
self.no_height = no_height
def on_motion(self, evt: MouseEvent):
- """ callback when the object is moved """
+ """ Callback when the object is moved. """
if self.got_artist:
self.movedEvent(evt)
self.moved = True
def button_press_event(self, evt: MouseEvent):
- """ when the mouse is pressed """
+ """ When the mouse is pressed. """
self.got_artist = True
self.moved = False
@@ -74,14 +78,14 @@ def button_press_event(self, evt: MouseEvent):
self.clickedEvent(evt)
def button_release_event(self, event: MouseEvent):
- """ when the mouse is released """
+ """When the mouse is released."""
if self.got_artist:
self.got_artist = False
self.figure.canvas.mpl_disconnect(self._c1)
self.releasedEvent(event)
def clickedEvent(self, event: MouseEvent):
- """ when the mouse is clicked """
+ """When the mouse is clicked."""
self.parent.start_move()
self.mouse_xy = (event.x, event.y)
@@ -102,7 +106,7 @@ def clickedEvent(self, event: MouseEvent):
self.time = time.time()
def releasedEvent(self, event: MouseEvent):
- """ when the mouse is released """
+ """When the mouse is released."""
for snap in self.snaps:
snap.remove()
self.snaps = []
@@ -116,7 +120,7 @@ def releasedEvent(self, event: MouseEvent):
pass
def movedEvent(self, event: MouseEvent):
- """ when the mouse is moved """
+ """When the mouse is moved."""
if len(self.targets) == 0:
return
@@ -140,6 +144,7 @@ def movedEvent(self, event: MouseEvent):
else:
self.figure.canvas.schedule_draw()
+
class GrabbableRectangleSelection(GrabFunctions):
grabbers = None
@@ -159,7 +164,6 @@ def __init__(self, figure: Figure, graphics_scene=None):
self.graphics_scene_snapparent = QtWidgets.QGraphicsRectItem(0, 0, 0, 0, self.graphics_scene)
figure._pyl_graphics_scene_snapparent = self.graphics_scene_snapparent
-
GrabFunctions.__init__(self, self, DIR_X0 | DIR_X1 | DIR_Y0 | DIR_Y1, no_height=True)
self.addGrabber(0, 0, DIR_X0 | DIR_Y0, GrabberGenericRound)
@@ -179,7 +183,7 @@ def __init__(self, figure: Figure, graphics_scene=None):
self.hide_grabber()
def add_target(self, target: Artist):
- """ add an artist to the selection """
+ """ Add an artist to the selection. """
target = TargetWrapper(target)
new_points = np.array(target.get_positions())
@@ -198,9 +202,9 @@ def add_target(self, target: Artist):
if 0:
rect1 = Rectangle((x0, y0), x1 - x0, y1 - y0, picker=False, figure=self.figure, linestyle="-", edgecolor="w",
- facecolor="#FFFFFF00", zorder=900, label="_rect for %s" % str(target))
+ facecolor="#FFFFFF00", zorder=900, label=f"_rect for {str(target)}")
rect2 = Rectangle((x0, y0), x1 - x0, y1 - y0, picker=False, figure=self.figure, linestyle="--", edgecolor="k",
- facecolor="#FFFFFF00", zorder=900, label="_rect2 for %s" % str(target))
+ facecolor="#FFFFFF00", zorder=900, label=f"_rect2 for {str(target)}")
self.figure.patches.append(rect1)
self.figure.patches.append(rect2)
self.targets_rects.append(rect1)
@@ -224,7 +228,7 @@ def add_target(self, target: Artist):
self.update_extent()
def update_extent(self):
- """ updates the extend of the selection to all the selected elements """
+ """ Updates the extend of the selection to all the selected elements. """
points = None
for target in self.targets:
new_points = np.array(target.get_positions())
@@ -245,9 +249,9 @@ def update_extent(self):
self.positions[2] = np.max(points[:, 0])
self.positions[3] = np.max(points[:, 1])
- if self.positions[2]-self.positions[0] < 0.01:
+ if self.positions[2] - self.positions[0] < 0.01:
self.positions[0], self.positions[2] = self.positions[0] - 0.01, self.positions[0] + 0.01
- if self.positions[3]-self.positions[1] < 0.01:
+ if self.positions[3] - self.positions[1] < 0.01:
self.positions[1], self.positions[3] = self.positions[1] - 0.01, self.positions[1] + 0.01
if self.do_target_scale():
@@ -256,13 +260,14 @@ def update_extent(self):
self.hide_grabber()
def align_points(self, mode: str):
- """ a function to apply the alignment options, e.g. align all selected elements at the top or with equal spacing. """
+ """A function to apply the alignment options, e.g. align all selected elements at the top or with equal spacing."""
if len(self.targets) == 0:
return
if mode == "group":
from pylustrator.helper_functions import axes_to_grid
- #return axes_to_grid([target.target for target in self.targets], track_changes=True)
+
+ # return axes_to_grid([target.target for target in self.targets], track_changes=True)
with UndoRedo([target.target for target in self.targets if isinstance(target.target, Axes)], "Grid Align"):
axes_to_grid([target.target for target in self.targets if isinstance(target.target, Axes)], track_changes=False)
@@ -294,7 +299,7 @@ def distribute(y: int):
positions.append(np.min(new_points[:, y]))
order = np.argsort(positions)
spaces = np.diff(self.positions[y::2])[0] - np.sum(sizes)
- spaces /= max([(len(self.targets)-1), 1])
+ spaces /= max([(len(self.targets) - 1), 1])
pos = np.min(self.positions[y::2])
for index in order:
target = self.targets[index]
@@ -335,14 +340,14 @@ def distribute(y: int):
self.figure.signals.figure_selection_moved.emit()
def update_selection_rectangles(self, use_previous_offset=False):
- """ update the selection visualisation """
+ """ Update the selection visualisation. """
if len(self.targets) == 0:
return
if 0:
for index, target in enumerate(self.targets):
new_points = np.array(target.get_positions())
for i in range(2):
- rect = self.targets_rects[index*2+i]
+ rect = self.targets_rects[index * 2 + i]
rect.set_xy(new_points[0])
rect.set_width(new_points[1][0] - new_points[0][0])
rect.set_height(new_points[1][1] - new_points[0][1])
@@ -363,25 +368,25 @@ def update_selection_rectangles(self, use_previous_offset=False):
rect.setRect(x0, y0, w0, h0)
def remove_target(self, target: Artist):
- """ remove an artist from the current selection """
+ """ Remove an artist from the current selection. """
targets_non_wrapped = [t.target for t in self.targets]
if target not in targets_non_wrapped:
return
index = targets_non_wrapped.index(target)
self.targets.pop(index)
- rect1 = self.targets_rects.pop(index*2)
- rect2 = self.targets_rects.pop(index*2)
+ rect1 = self.targets_rects.pop(index * 2)
+ rect2 = self.targets_rects.pop(index * 2)
rect1.scene().removeItem(rect1)
rect2.scene().removeItem(rect2)
- #self.figure.patches.remove(rect1)
- #self.figure.patches.remove(rect2)
+ # self.figure.patches.remove(rect1)
+ # self.figure.patches.remove(rect2)
if len(self.targets) == 0:
self.clear_targets()
else:
self.update_extent()
def update_grabber(self):
- """ update the position of the grabber elements """
+ """ Update the position of the grabber elements. """
if self.do_target_scale():
for grabber in self.grabbers:
grabber.updatePos()
@@ -389,66 +394,66 @@ def update_grabber(self):
self.hide_grabber()
def hide_grabber(self):
- """ hide the grabber elements """
+ """ Hide the grabber elements. """
for grabber in self.grabbers:
grabber.set_xy((-100, -100))
def clear_targets(self):
- """ remove all elements from the selection """
+ """ Remove all elements from the selection. """
for rect in self.targets_rects:
self.graphics_scene.scene().removeItem(rect)
- #self.figure.patches.remove(rect)
+ # self.figure.patches.remove(rect)
self.targets_rects = []
self.targets = []
self.hide_grabber()
def do_target_scale(self) -> bool:
- """ if any of the elements in the selection allows scaling """
+ """ If any of the elements in the selection allows scaling. """
return np.any([target.do_scale for target in self.targets])
def do_change_aspect_ratio(self) -> bool:
- """ if any of the element sin the selection wants to perserve its aspect ratio """
+ """ If any of the element sin the selection wants to perserve its aspect ratio. """
return np.any([target.fixed_aspect for target in self.targets])
def width(self) -> float:
- """ the width of the current selection """
- return (self.p2-self.p1)[0]
+ """ The width of the current selection. """
+ return (self.p2 - self.p1)[0]
def height(self) -> float:
- """ the height of the current selection """
- return (self.p2-self.p1)[1]
+ """ The height of the current selection. """
+ return (self.p2 - self.p1)[1]
def size(self) -> (float, float):
- """ the size of the current selection (width and height)"""
- return self.p2-self.p1
+ """ The size of the current selection (width and height)."""
+ return self.p2 - self.p1
def get_trans_matrix(self):
- """ the transformation matrix for the current displacement and scaling of the selection """
+ """ The transformation matrix for the current displacement and scaling of the selection. """
x, y = self.p1
w, h = self.size()
return np.array([[w, 0, x], [0, h, y], [0, 0, 1]], dtype=float)
def get_inv_trans_matrix(self):
- """ the inverse transformation for the current displacement and scaling of the selection """
+ """ The inverse transformation for the current displacement and scaling of the selection. """
x, y = self.p1
w, h = self.size()
- return np.array([[1./w, 0, -x/w], [0, 1./h, -y/h], [0, 0, 1]], dtype=float)
+ return np.array([[1. / w, 0, - x / w], [0, 1. / h, -y / h], [0, 0, 1]], dtype=float)
def transform(self, pos: Sequence) -> np.ndarray:
- """ apply the current transformation to a point """
+ """ Apply the current transformation to a point. """
return np.dot(self.get_trans_matrix(), [pos[0], pos[1], 1.0])
def inv_transform(self, pos: Sequence) -> np.ndarray:
- """ apply the inverse current transformation to a point """
+ """ Apply the inverse current transformation to a point. """
return np.dot(self.get_inv_trans_matrix(), [pos[0], pos[1], 1.0])
def get_pos(self, pos: Sequence) -> np.ndarray:
- """ transform a point """
+ """ Transform a point. """
return self.transform(pos)
def get_save_point(self) -> callable:
- """ gather the current positions in a restore point for the undo function """
+ """ Gather the current positions in a restore point for the undo function. """
targets = [target.target for target in self.targets]
positions = [target.get_positions() for target in self.targets]
@@ -461,7 +466,7 @@ def undo():
return undo
def start_move(self):
- """ start to move a grabber """
+ """ Start to move a grabber. """
self.start_p1 = self.p1.copy()
self.start_p2 = self.p2.copy()
self.hide_grabber()
@@ -470,7 +475,7 @@ def start_move(self):
self.store_start = self.get_save_point()
def end_move(self):
- """ a grabber move stopped """
+ """ A grabber move stopped. """
self.update_grabber()
self.store_end = self.get_save_point()
@@ -479,38 +484,38 @@ def end_move(self):
self.figure.change_tracker.addEdit([self.store_start, self.store_end, "Move"])
def addOffset(self, pos: Sequence, dir: int, keep_aspect_ratio: bool = True):
- """ move the whole selection (e.g. for the use of the arrow keys) """
+ """ Move the whole selection (e.g. for the use of the arrow keys). """
pos = list(pos)
self.old_inv_transform = self.get_inv_trans_matrix()
if (keep_aspect_ratio or self.do_change_aspect_ratio()) and not (dir & DIR_X0 and dir & DIR_X1 and dir & DIR_Y0 and dir & DIR_Y1):
if (dir & DIR_X0 and dir & DIR_Y0) or (dir & DIR_X1 and dir & DIR_Y1):
- dx = pos[1]*self.width()/self.height()
- dy = pos[0]*self.height()/self.width()
+ dx = pos[1] * self.width() / self.height()
+ dy = pos[0] * self.height() / self.width()
if abs(dx) < abs(dy):
pos[0] = dx
else:
pos[1] = dy
elif (dir & DIR_X0 and dir & DIR_Y1) or (dir & DIR_X1 and dir & DIR_Y0):
- dx = -pos[1]*self.width()/self.height()
- dy = -pos[0]*self.height()/self.width()
+ dx = -pos[1] * self.width() / self.height()
+ dy = -pos[0] * self.height() / self.width()
if abs(dx) < abs(dy):
pos[0] = dx
else:
pos[1] = dy
elif dir & DIR_X0 or dir & DIR_X1:
- dy = pos[0]*self.height()/self.width()
+ dy = pos[0] * self.height() / self.width()
if dir & DIR_X0:
- self.p1[1] = self.start_p1[1] + dy/2
- self.p2[1] = self.start_p2[1] - dy/2
+ self.p1[1] = self.start_p1[1] + dy / 2
+ self.p2[1] = self.start_p2[1] - dy / 2
else:
self.p1[1] = self.start_p1[1] - dy / 2
self.p2[1] = self.start_p2[1] + dy / 2
elif dir & DIR_Y0 or dir & DIR_Y1:
- dx = pos[1]*self.width()/self.height()
+ dx = pos[1] * self.width() / self.height()
if dir & DIR_Y0:
- self.p1[0] = self.start_p1[0] + dx/2
- self.p2[0] = self.start_p2[0] - dx/2
+ self.p1[0] = self.start_p1[0] + dx / 2
+ self.p2[0] = self.start_p2[0] - dx / 2
else:
self.p1[0] = self.start_p1[0] - dx / 2
self.p2[0] = self.start_p2[0] + dx / 2
@@ -529,37 +534,37 @@ def addOffset(self, pos: Sequence, dir: int, keep_aspect_ratio: bool = True):
self.transform_target(transform, target)
self.update_selection_rectangles(True)
- #for rect in self.targets_rects:
+ # for rect in self.targets_rects:
# self.transform_target(transform, TargetWrapper(rect))
def move(self, pos: Sequence[float], dir: int, snaps: Sequence[SnapBase], keep_aspect_ratio: bool = False, ignore_snaps: bool = False):
- """ called from a grabber to move the selection. """
+ """ Called from a grabber to move the selection. """
self.addOffset(pos, dir, keep_aspect_ratio)
self.has_moved = True
if not ignore_snaps:
offx, offy = checkSnaps(snaps)
- self.addOffset((pos[0]-offx, pos[1]-offy), dir, keep_aspect_ratio)
+ self.addOffset((pos[0] - offx, pos[1] - offy), dir, keep_aspect_ratio)
offx, offy = checkSnaps(self.snaps)
checkSnapsActive(snaps)
def apply_transform(self, transform: np.ndarray, point: Sequence[float]):
- """ apply the given transformation to a point"""
+ """ Apply the given transformation to a point."""
point = np.array(point)
point = np.hstack((point, np.ones((point.shape[0], 1)))).T
return np.dot(transform, point)[:2].T
def transform_target(self, transform: np.ndarray, target: TargetWrapper):
- """ transform the position of an artist. """
+ """ Transform the position of an artist. """
points = target.get_positions()
points = self.apply_transform(transform, points)
target.set_positions(points)
def keyPressEvent(self, event: KeyEvent):
- """ when a key is pressed. Arrow keys move the selection, Pageup/down movein z """
- #if not self.selected:
+ """ When a key is pressed. Arrow keys move the selection, Pageup/down movein z. """
+ # if not self.selected:
# return
# move last axis in z order
if event.key == 'pagedown':
@@ -603,7 +608,8 @@ def keyPressEvent(self, event: KeyEvent):
class DragManager:
- """ a class to manage the selection and the moving of artists in a figure """
+ """ A class to manage the selection and the moving of artists in a figure. """
+
selected_element = None
grab_element = None
@@ -644,13 +650,13 @@ def __init__(self, figure: Figure, no_save):
self.figure.change_tracker = self.change_tracker
def activate(self):
- """ activate the interaction callbacks from the figure """
+ """ Activate the interaction callbacks from the figure. """
self.c3 = self.figure.canvas.mpl_connect('button_release_event', self.button_release_event0)
self.c2 = self.figure.canvas.mpl_connect('button_press_event', self.button_press_event0)
self.c4 = self.figure.canvas.mpl_connect('key_press_event', self.key_press_event)
def deactivate(self):
- """ deactivate the interaction callbacks from the figure """
+ """ Deactivate the interaction callbacks from the figure. """
self.figure.canvas.mpl_disconnect(self.c3)
self.figure.canvas.mpl_disconnect(self.c2)
self.figure.canvas.mpl_disconnect(self.c4)
@@ -661,13 +667,13 @@ def deactivate(self):
self.figure.canvas.draw()
def make_dragable(self, target: Artist):
- """ make an artist draggable """
+ """ Make an artist draggable. """
target.set_picker(True)
if isinstance(target, Text):
- target.set_bbox(dict(facecolor="none", edgecolor="none"))
+ target.set_bbox({"facecolor": 'none', "edgecolor": 'none'})
def get_picked_element(self, event: MouseEvent, element: Artist = None, picked_element: Artist = None, last_selected: Artist = None):
- """ get the picked element that an event refers to.
+ """ Get the picked element that an event refers to.
To implement selection of elements at the back with multiple clicks.
"""
# start with the figure
@@ -677,7 +683,7 @@ def get_picked_element(self, event: MouseEvent, element: Artist = None, picked_e
# iterate over all children
for child in sorted(element.get_children(), key=lambda x: x.get_zorder()):
# check if the element is contained in the event and has an active dragger
- #if child.contains(event)[0] and ((getattr(child, "_draggable", None) and getattr(child, "_draggable",
+ # if child.contains(event)[0] and ((getattr(child, "_draggable", None) and getattr(child, "_draggable",
# None).connected) or isinstance(child, GrabberGeneric) or isinstance(child, GrabbableRectangleSelection)):
if child.get_visible() and child.contains(event)[0] and (child.pickable() or isinstance(child, GrabberGeneric)) and not (child.get_label() is not None and child.get_label().startswith("_")):
# if the element is the last selected, finish the search
@@ -693,7 +699,7 @@ def get_picked_element(self, event: MouseEvent, element: Artist = None, picked_e
return picked_element, finished
def button_release_event0(self, event: MouseEvent):
- """ when the mouse button is released """
+ """ When the mouse button is released. """
# release the grabber
if self.grab_element:
self.grab_element.button_release_event(event)
@@ -703,7 +709,7 @@ def button_release_event0(self, event: MouseEvent):
self.selection.button_release_event(event)
def button_press_event0(self, event: MouseEvent):
- """ when the mouse button is pressed """
+ """ When the mouse button is pressed. """
if event.button == 1:
last = self.selection.targets[-1] if len(self.selection.targets) else None
contained = np.any([t.target.contains(event)[0] for t in self.selection.targets])
@@ -727,7 +733,7 @@ def button_press_event0(self, event: MouseEvent):
self.selection.button_press_event(event)
def select_element(self, element: Artist, event: MouseEvent = None):
- """ select an artist in a figure """
+ """ Select an artist in a figure. """
# do nothing if it is already selected
if element == self.selected_element:
return
@@ -740,14 +746,14 @@ def select_element(self, element: Artist, event: MouseEvent = None):
self.selected_element = element
def on_deselect(self, event: MouseEvent):
- """ deselect currently selected artists"""
+ """ Deselect currently selected artists."""
modifier = "shift" in event.key.split("+") if event is not None and event.key is not None else False
# only if the modifier key is not used
if not modifier:
self.selection.clear_targets()
def on_select(self, element: Artist, event: MouseEvent):
- """ when an artist is selected """
+ """ When an artist is selected. """
if element is not None:
self.selection.add_target(element)
@@ -768,7 +774,7 @@ def redo(self):
self.figure.canvas.draw()
def key_press_event(self, event: KeyEvent):
- """ when a key is pressed """
+ """ When a key is pressed. """
# space: print code to restore current configuration
if event.key == 'ctrl+s':
self.figure.change_tracker.save()
@@ -784,7 +790,8 @@ def key_press_event(self, event: KeyEvent):
class GrabberGeneric(GrabFunctions):
- """ a generic grabber object to move a selection """
+ """ A generic grabber object to move a selection. """
+
_no_save = True
def __init__(self, parent: GrabbableRectangleSelection, x: float, y: float, dir: int):
@@ -807,11 +814,12 @@ def updatePos(self):
self.set_xy(self.parent.get_pos(self.pos))
def applyOffset(self, pos: (float, float), event: MouseEvent):
- self.set_xy((self.ox+pos[0], self.oy+pos[1]))
+ self.set_xy((self.ox + pos[0], self.oy + pos[1]))
class GrabberGenericRound(GrabberGeneric):
- """ a rectangle with a round appearance """
+ """ A rectangle with a round appearance. """
+
d = 10
shape = "round"
@@ -830,35 +838,36 @@ def __init__(self, parent: GrabbableRectangleSelection, x: float, y: float, dir:
def set_xy(self, xy: (float, float)):
self.xy = xy
- self.ellipse.setRect(xy[0]-5, xy[1]-5, 10, 10)
+ self.ellipse.setRect(xy[0] - 5, xy[1] - 5, 10, 10)
class GrabberGenericRectangle(GrabberGeneric):
- """ a rectangle with a square appearance """
+ """ A rectangle with a square appearance. """
+
d = 10
shape = "rect"
def __init__(self, parent: GrabbableRectangleSelection, x: float, y: float, dir: int, scene):
# somehow the original "self" rectangle does not show up in the current matplotlib version, therefore this doubling
- #self.rect = Rectangle((0, 0), self.d, self.d, figure=parent.figure, edgecolor="k", facecolor="r", zorder=1000, label="grabber")
- #self.rect._no_save = True
- #parent.figure.patches.append(self.rect)
+ # self.rect = Rectangle((0, 0), self.d, self.d, figure=parent.figure, edgecolor="k", facecolor="r", zorder=1000, label="grabber")
+ # self.rect._no_save = True
+ # parent.figure.patches.append(self.rect)
- #Rectangle.__init__(self, (0, 0), self.d, self.d, picker=True, figure=parent.figure, edgecolor="k", facecolor="r", zorder=1000, label="grabber")
+ # Rectangle.__init__(self, (0, 0), self.d, self.d, picker=True, figure=parent.figure, edgecolor="k", facecolor="r", zorder=1000, label="grabber")
- #self.figure.patches.append(self)
+ # self.figure.patches.append(self)
pen3 = QtGui.QPen(QtGui.QColor("black"), 2)
brush1 = QtGui.QBrush(QtGui.QColor("red"))
- self.ellipse = MyRect(x-5, y-5, 10, 10, scene)
+ self.ellipse = MyRect(x - 5, y - 5, 10, 10, scene)
self.ellipse.view = scene.view
self.ellipse.grabber = self
self.ellipse.setPen(pen3)
self.ellipse.setBrush(brush1)
self.xy = (x, y)
- #self.updatePos()
+ # self.updatePos()
GrabberGeneric.__init__(self, parent, x, y, dir)
@@ -875,6 +884,7 @@ def set_xy(self, xy: (float, float)):
Rectangle.set_xy(self, (xy[0] - self.d / 2, xy[1] - self.d / 2))
self.rect.set_xy((xy[0] - self.d / 2, xy[1] - self.d / 2))
+
class MyItem:
w = 10
@@ -892,15 +902,16 @@ def mouseReleaseEvent(self, e):
p = e.scenePos()
self.grabber.button_release_event(MyEvent(p.x(), self.view.h - p.y()))
+
class MyRect(MyItem, QtWidgets.QGraphicsRectItem):
pass
+
class MyEllipse(MyItem, QtWidgets.QGraphicsEllipseItem):
pass
-
class MyEvent:
def __init__(self, x, y):
self.x = x
- self.y = y
\ No newline at end of file
+ self.y = y
diff --git a/pylustrator/exception_swallower.py b/pylustrator/exception_swallower.py
index e4a82de..b14110f 100644
--- a/pylustrator/exception_swallower.py
+++ b/pylustrator/exception_swallower.py
@@ -19,13 +19,14 @@
# You should have received a copy of the GNU General Public License
# along with Pylustrator. If not, see
-from matplotlib.figure import Figure
from matplotlib.axes._base import _AxesBase
from matplotlib.axis import Axis
+from matplotlib.figure import Figure
class Dummy:
- """ a dummy object that provides dummy attributes, dummy items and dummy returns """
+ """ A dummy object that provides dummy attributes, dummy items and dummy returns. """
+
def __getattr__(self, item):
return Dummy()
@@ -37,7 +38,8 @@ def __getitem__(self, item):
class SaveList(list):
- """ a list that returns dummy objects when an invalid item is requested """
+ """ A list that returns dummy objects when an invalid item is requested. """
+
def __init__(self, target):
list.__init__(self, target)
@@ -49,7 +51,8 @@ def __getitem__(self, item):
class SaveDict(dict):
- """ a dictionary that returns dummy objects when an invalid item is requested """
+ """ A dictionary that returns dummy objects when an invalid item is requested. """
+
def __init__(self, target):
dict.__init__(self, target)
@@ -61,7 +64,8 @@ def __getitem__(self, item):
class SaveTuple(tuple):
- """ a tuple that returns dummy objects when an invalid item is requested """
+ """ A tuple that returns dummy objects when an invalid item is requested. """
+
def __init__(self, target):
tuple.__init__(self, target)
@@ -73,7 +77,8 @@ def __getitem__(self, item):
class SaveListDescriptor:
- """ a descriptor that wraps the target value with a SaveList, SaveDict or SaveTuple """
+ """ A descriptor that wraps the target value with a SaveList, SaveDict or SaveTuple. """
+
def __init__(self, variable_name):
self.variable_name = variable_name
@@ -84,11 +89,11 @@ def __set__(self, instance, value):
value = SaveDict(value)
if isinstance(value, tuple):
value = SaveTuple(value)
- setattr(instance, "_pylustrator_"+self.variable_name, value)
+ setattr(instance, "_pylustrator_" + self.variable_name, value)
def __get__(self, instance, owner):
try:
- return getattr(instance, "_pylustrator_"+self.variable_name)
+ return getattr(instance, "_pylustrator_" + self.variable_name)
except AttributeError:
if self.variable_name in instance.__dict__:
return instance.__dict__[self.variable_name]
@@ -97,21 +102,21 @@ def __get__(self, instance, owner):
def get_axes(self):
- """ a function that returns the axes of a figure as a SaveList """
+ """ A function that returns the axes of a figure as a SaveList. """
return SaveList(self._axstack.as_list())
def return_save_list(func):
- """ a decorator to wrap the output of a function as a SaveList """
+ """ A decorator to wrap the output of a function as a SaveList. """
def wrap(*args, **kwargs):
return SaveList(func(*args, **kwargs))
return wrap
def swallow_get_exceptions():
- """ replace lists with lists that return dummy objects when items are not present.
- this is to ensure that the pylustrator generated code does not fail, even if the user removes some elements
- from the figure.
+ """ Replace lists with lists that return dummy objects when items are not present.
+ this is to ensure that the pylustrator generated code does not fail, even if the user removes some elements
+ from the figure.
"""
Figure._get_axes = get_axes
Figure.axes = property(fget=get_axes)
@@ -122,10 +127,11 @@ def swallow_get_exceptions():
Axis.get_minor_ticks = return_save_list(Axis.get_minor_ticks)
Axis.get_major_ticks = return_save_list(Axis.get_major_ticks)
l = _AxesBase.get_legend
+
def get_legend(*args, **kwargs):
leg = l(*args, **kwargs)
if leg is None:
return Dummy()
return leg
- _AxesBase.get_legend = get_legend
\ No newline at end of file
+ _AxesBase.get_legend = get_legend
diff --git a/pylustrator/helper_functions.py b/pylustrator/helper_functions.py
index a54a063..f971123 100644
--- a/pylustrator/helper_functions.py
+++ b/pylustrator/helper_functions.py
@@ -20,25 +20,30 @@
# along with Pylustrator. If not, see
from __future__ import division
+
+import traceback
+
import matplotlib.pyplot as plt
-from matplotlib.text import Text
import numpy as np
-import traceback
+from matplotlib.text import Text
+
from .parse_svg import svgread
+
try: # starting from mpl version 3.6.0
from matplotlib.axes import Axes
-except:
+except Exception:
from matplotlib.axes._subplots import Axes
-from matplotlib.figure import Figure
-from .pyjack import replace_all_refs
+
import os
from typing import Sequence, Union
+from matplotlib.figure import Figure
+
+from .pyjack import replace_all_refs
+
def fig_text(x: float, y: float, text: str, unit: str = "cm", *args, **kwargs):
- """
- add a text to the figure positioned in cm
- """
+ """ Add a text to the figure positioned in cm. """
fig = plt.gcf()
if unit == "cm":
x = x / 2.54 / fig.get_size_inches()[0]
@@ -51,9 +56,7 @@ def fig_text(x: float, y: float, text: str, unit: str = "cm", *args, **kwargs):
def add_axes(dim: Sequence, unit: str = "cm", *args, **kwargs):
- """
- add an axes with dimensions specified in cm
- """
+ """ Add an axes with dimensions specified in cm. """
fig = plt.gcf()
x, y, w, h = dim
if unit == "cm":
@@ -69,15 +72,16 @@ def add_axes(dim: Sequence, unit: str = "cm", *args, **kwargs):
def add_image(filename: str):
- """ add an image to the current axes """
+ """ Add an image to the current axes. """
plt.imshow(plt.imread(filename))
plt.xticks([])
plt.yticks([])
def changeFigureSize(w: float, h: float, cut_from_top: bool = False, cut_from_left: bool = False, fig: Figure = None):
- """ change the figure size to the given dimensions. Optionally define if to remove or add space at the top or bottom
- and left or right.
+ """ Change the figure size to the given dimensions.
+ Optionally define if to remove or add space at the top or bottom
+ and left or right.
"""
if fig is None:
fig = plt.gcf()
@@ -101,7 +105,7 @@ def changeFigureSize(w: float, h: float, cut_from_top: bool = False, cut_from_le
x0, y0 = text.get_position()
if cut_from_top:
if cut_from_left:
- text.set_position([1 - (1- x0) * fx, y0 * fy])
+ text.set_position([1 - (1 - x0) * fx, y0 * fy])
else:
text.set_position([x0 * fx, y0 * fy])
else:
@@ -113,7 +117,7 @@ def changeFigureSize(w: float, h: float, cut_from_top: bool = False, cut_from_le
def removeContentFromFigure(fig: Figure):
- """ remove axes and text from a figure """
+ """ Remove axes and text from a figure. """
axes = []
for ax in fig._axstack.as_list():
axes.append(ax)
@@ -124,7 +128,7 @@ def removeContentFromFigure(fig: Figure):
def addContentToFigure(fig: Figure, axes: Sequence):
- """ add axes and texts to a figure """
+ """ Add axes and texts to a figure. """
index = len(fig._axstack.as_list())
for ax in axes:
if isinstance(ax, Axes):
@@ -155,7 +159,7 @@ def get_unique_label(fig1, label_base):
def imShowFullFigure(im: np.ndarray, filename: str, fig1: Figure, dpi: int, label: str):
- """ create a new axes and display an image in this axes """
+ """ Create a new axes and display an image in this axes. """
from matplotlib import rcParams
if dpi is None:
dpi = rcParams['figure.dpi']
@@ -169,9 +173,8 @@ def imShowFullFigure(im: np.ndarray, filename: str, fig1: Figure, dpi: int, labe
class changeFolder:
- """
- An environment that changes the working directory
- """
+ """ An environment that changes the working directory. """
+
def __init__(self, directory):
self.directory = directory
@@ -185,9 +188,9 @@ def __exit__(self, type, value, traceback):
def loadFigureFromFile(filename: str, figure: Figure = None, offset: list = None, dpi: int = None, cache: bool = False, label: str = ""):
- """
- Add contents to the current figure from the file defined by filename. It can be either a python script defining
- a figure, an image (filename or directly the numpy array), or an svg file.
+ """ Add contents to the current figure from the file defined by filename.
+ It can be either a python script defining
+ a figure, an image (filename or directly the numpy array), or an svg file.
See also :ref:`composing`.
@@ -205,8 +208,9 @@ def loadFigureFromFile(filename: str, figure: Figure = None, offset: list = None
and may not be stable.
"""
from matplotlib import rcParams
- from pylustrator import changeFigureSize
+
import pylustrator
+ from pylustrator import changeFigureSize
if label == "":
label = get_unique_label(figure if figure is not None else plt.gcf(), filename)
@@ -223,9 +227,8 @@ def loadFigureFromFile(filename: str, figure: Figure = None, offset: list = None
figure = plt.gcf()
class noShow:
- """
- An environment that prevents the script from calling the plt.show function
- """
+ """ An environment that prevents the script from calling the plt.show function. """
+
def __enter__(self):
# store the show function
self.show = plt.show
@@ -245,14 +248,14 @@ def __exit__(self, type, value, traceback):
pylustrator.start = self.dragger
class noNewFigures:
- """
- An environment that prevents the script from creating new figures in the figure manager
- """
+ """An environment that prevents the script from creating new figures in the figure manager."""
+
def __enter__(self):
fig = plt.gcf()
self.fig = plt.figure
figsize = rcParams['figure.figsize']
fig.set_size_inches(figsize[0], figsize[1])
+
def figure(num=None, figsize=None, *args, **kwargs):
fig = plt.gcf()
if figsize is not None:
@@ -262,7 +265,7 @@ def figure(num=None, figsize=None, *args, **kwargs):
def __exit__(self, type, value, traceback):
from matplotlib.figure import Figure
- from matplotlib.transforms import TransformedBbox, Affine2D
+ from matplotlib.transforms import Affine2D, TransformedBbox
plt.figure = self.fig
# get the size of the old figure
@@ -358,12 +361,12 @@ def convertFromPyplot(old, new):
str(new) # important! (for some reason I don't know)
for ax in old.axes:
- #old.delaxes(ax)
+ # old.delaxes(ax)
ax.remove()
ax.figure = new
new.axes.append(ax)
new.add_axes(ax)
- #new._axstack.add(new._make_key(ax), ax)
+ # new._axstack.add(new._make_key(ax), ax)
new.bbox._parents.update(old.bbox._parents)
new.dpi_scale_trans._parents.update(old.dpi_scale_trans._parents)
replace_all_refs(old.bbox, new.bbox)
@@ -373,16 +376,18 @@ def convertFromPyplot(old, new):
def mark_inset(parent_axes: Axes, inset_axes: Axes, loc1: Union[int, Sequence[int]] = 1, loc2: Union[int, Sequence[int]] = 2, **kwargs):
- """ like the mark_inset function from matplotlib, but loc can also be a tuple """
- from mpl_toolkits.axes_grid1.inset_locator import TransformedBbox, BboxPatch, BboxConnector
+ """ Like the mark_inset function from matplotlib, but loc can also be a tuple. """
+ from mpl_toolkits.axes_grid1.inset_locator import (BboxConnector,
+ BboxPatch,
+ TransformedBbox)
try:
loc1a, loc1b = loc1
- except:
+ except Exception:
loc1a = loc1
loc1b = loc1
try:
loc2a, loc2b = loc2
- except:
+ except Exception:
loc2a = loc2
loc2b = loc2
rect = TransformedBbox(inset_axes.viewLim, parent_axes.transData)
@@ -402,8 +407,9 @@ def mark_inset(parent_axes: Axes, inset_axes: Axes, loc1: Union[int, Sequence[in
def draw_from_point_to_bbox(parent_axes: Axes, insert_axes: Axes, point: Sequence, loc=1, **kwargs):
- """ add a box connector from a point to an axes """
- from mpl_toolkits.axes_grid1.inset_locator import TransformedBbox, BboxConnector, Bbox
+ """ Add a box connector from a point to an axes. """
+ from mpl_toolkits.axes_grid1.inset_locator import (Bbox, BboxConnector,
+ TransformedBbox)
rect = TransformedBbox(Bbox([point, point]), parent_axes.transData)
# rect = TransformedBbox(Bbox([[1, 0], [1, 0]]), parent_axes.transData)
p1 = BboxConnector(rect, insert_axes.bbox, loc, **kwargs)
@@ -413,8 +419,9 @@ def draw_from_point_to_bbox(parent_axes: Axes, insert_axes: Axes, point: Sequenc
def draw_from_point_to_point(parent_axes: Axes, insert_axes: Axes, point1: Sequence, point2: Sequence, **kwargs):
- """ add a box connector from a point in on axes to a point in another axes """
- from mpl_toolkits.axes_grid1.inset_locator import TransformedBbox, BboxConnector, Bbox
+ """ Add a box connector from a point in on axes to a point in another axes. """
+ from mpl_toolkits.axes_grid1.inset_locator import (Bbox, BboxConnector,
+ TransformedBbox)
rect = TransformedBbox(Bbox([point1, point1]), parent_axes.transData)
rect2 = TransformedBbox(Bbox([point2, point2]), insert_axes.transData)
# rect = TransformedBbox(Bbox([[1, 0], [1, 0]]), parent_axes.transData)
@@ -426,7 +433,7 @@ def draw_from_point_to_point(parent_axes: Axes, insert_axes: Axes, point1: Seque
def mark_inset_pos(parent_axes: Axes, inset_axes: Axes, loc1: Union[int, Sequence[int]], loc2: Union[int, Sequence[int]], point: Sequence, **kwargs):
- """ add a box connector where the second axis is shrinked to a point """
+ """ Add a box connector where the second axis is shrinked to a point. """
kwargs["lw"] = 0.8
ax_new = plt.axes(inset_axes.get_position())
ax_new.set_xlim(point[0], point[0])
@@ -437,12 +444,12 @@ def mark_inset_pos(parent_axes: Axes, inset_axes: Axes, loc1: Union[int, Sequenc
ax_new.set_zorder(inset_axes.get_zorder() - 1)
-def VoronoiPlot(points: Sequence, values: Sequence, vmin: float = None, vmax:float = None, cmap=None):
- """ plot the voronoi regions of the poins with the given colormap """
- from matplotlib.patches import Polygon
+def VoronoiPlot(points: Sequence, values: Sequence, vmin: float = None, vmax: float = None, cmap=None):
+ """ Plot the voronoi regions of the poins with the given colormap. """
+ from matplotlib import cm
from matplotlib.collections import PatchCollection
+ from matplotlib.patches import Polygon
from scipy.spatial import Voronoi, voronoi_plot_2d
- from matplotlib import cm
if cmap is None:
cmap = cm.get_cmap('viridis')
@@ -484,14 +491,14 @@ def VoronoiPlot(points: Sequence, values: Sequence, vmin: float = None, vmax:flo
def selectRectangle(axes: Axes = None):
- """ add a rectangle selector to the given axes """
+ """ Add a rectangle selector to the given axes. """
if axes is None:
axes = plt.gca()
def onselect(eclick, erelease):
- 'eclick and erelease are matplotlib events at press and release'
- print(' startposition : (%f, %f)' % (eclick.xdata, eclick.ydata))
- print(' endposition : (%f, %f)' % (erelease.xdata, erelease.ydata))
+ """ Eclick and erelease are matplotlib events at press and release. """
+ print(f' startposition : ({eclick.xdata:f}, {eclick.ydata:f})')
+ print(f' endposition : ({erelease.xdata:f}, {erelease.ydata:f})')
print(' used button : ', eclick.button)
from matplotlib.widgets import RectangleSelector
@@ -500,7 +507,7 @@ def onselect(eclick, erelease):
def despine(ax: Axes = None, complete: bool = False):
- """ despine the given axes """
+ """ Despine the given axes. """
if not ax:
ax = plt.gca()
ax.spines['right'].set_visible(False)
@@ -516,10 +523,11 @@ def despine(ax: Axes = None, complete: bool = False):
ax.xaxis.set_ticks_position('bottom')
-
letter_index = 0
+
+
def add_letter(ax: Axes = None, offset: float = 0, offset2: float = 0, letter: str = None):
- """ add a letter indicating which subplot it is to the given figure """
+ """ Add a letter indicating which subplot it is to the given figure. """
global letter_index
from matplotlib.transforms import Affine2D, ScaledTranslation
@@ -547,11 +555,11 @@ def add_letter(ax: Axes = None, offset: float = 0, offset2: float = 0, letter: s
transform = Affine2D().scale(1 / 2.54, 1 / 2.54) + fig.dpi_scale_trans + ScaledTranslation(0, 1, ax.transAxes)
# add a text a the given position
- ax.text(-0.5+offset, offset2, letter, fontproperties=font, transform=transform, ha="center", va="bottom", picker=True)
+ ax.text(-0.5 + offset, offset2, letter, fontproperties=font, transform=transform, ha="center", va="bottom", picker=True)
def get_letter_font_prop():
- """ get the properties of the subplot letters to add """
+ """ Get the properties of the subplot letters to add. """
from matplotlib.font_manager import FontProperties
font = FontProperties()
font.set_family("C:\\WINDOWS\\Fonts\\HelveticaNeue-CondensedBold.ttf")
@@ -562,7 +570,7 @@ def get_letter_font_prop():
def add_letters(*args, **kwargs):
- """ add a letter indicating which subplot it is to all of the axes of the given figure """
+ """ Add a letter indicating which subplot it is to all of the axes of the given figure. """
for ax in plt.gcf().axes:
add_letter(ax, *args, **kwargs)
@@ -588,9 +596,9 @@ def axes_to_grid(axes=None, track_changes=False):
new_indices = [0, 0]
for i in [0, 1]:
d = np.abs(pos[i] - center[i])
- if len(d) == 0 or np.min(d) > dims[i]/2:
+ if len(d) == 0 or np.min(d) > dims[i] / 2:
pos[i].append(center[i])
- new_indices[i] = len(pos[i])-1
+ new_indices[i] = len(pos[i]) - 1
else:
new_indices[i] = np.argmin(d)
axes_indices.append(new_indices)
@@ -614,21 +622,21 @@ def axes_to_grid(axes=None, track_changes=False):
if x_count == 0:
x_gap = 0
else:
- x_gap = ((x_max-x_min)-(x_count+1)*width)/x_count
+ x_gap = ((x_max - x_min) - (x_count + 1) * width) / x_count
if y_count == 0:
y_gap = 0
else:
- y_gap = ((y_max-y_min)-(y_count+1)*height)/y_count
+ y_gap = ((y_max - y_min) - (y_count + 1) * height) / y_count
# make all the plots the same size and align them on the grid
for i, ax in enumerate(axes):
- ax.set_position([x_min+axes_indices[i][0] * (width+x_gap),
- y_min+axes_indices[i][1] * (height + y_gap),
+ ax.set_position([x_min + axes_indices[i][0] * (width + x_gap),
+ y_min + axes_indices[i][1] * (height + y_gap),
width,
height,
- ])
+ ])
if track_changes is True:
- ax.figure.change_tracker.addChange(ax, ".set_position([%f, %f, %f, %f])" % (x_min+axes_indices[i][0] * (width+x_gap), y_min+axes_indices[i][1] * (height + y_gap), width, height))
+ ax.figure.change_tracker.addChange(ax, f".set_position([{x_min + axes_indices[i][0] * (width + x_gap):f}, {y_min + axes_indices[i][1] * (height + y_gap):f}, {width:f}, {height:f}])")
# make all the plots have the same limits
xmin = np.min([ax.get_xlim()[0] for ax in axes])
@@ -665,4 +673,4 @@ def main_figure(artist):
if artist.figure == artist:
return artist
else:
- return main_figure(artist.figure)
\ No newline at end of file
+ return main_figure(artist.figure)
diff --git a/pylustrator/jupyter_cells.py b/pylustrator/jupyter_cells.py
index c9010c7..9be8a89 100644
--- a/pylustrator/jupyter_cells.py
+++ b/pylustrator/jupyter_cells.py
@@ -24,8 +24,9 @@
the file is instead of a normal file a jupyter notebook and redirects writes accordingly.
"""
+
def setJupyterCellText(text: str):
- """ the function replaces the text in the current jupyter cell with the given text """
+ """ The function replaces the text in the current jupyter cell with the given text. """
from IPython.display import Javascript, display
text = text.replace("\n", "\\n").replace("'", "\\'")
js = """
@@ -37,15 +38,16 @@ def setJupyterCellText(text: str):
// get the cell object
var cell = Jupyter.notebook.get_cell(cell_idx);
cell.get_text();
- cell.set_text('"""+text+"""');
- console.log('"""+text+"""');
+ cell.set_text('""" + text + """');
+ console.log('""" + text + """');
"""
display(Javascript(js))
def getIpythonCurrentCell() -> str:
- """ this function returns the text of the current jupyter cell """
+ """ This function returns the text of the current jupyter cell. """
import inspect
+
# get the first stack which has a filename starting with " str:
global_files = {}
build_in_open = open
+
+
def open(filename: str, *args, **kwargs):
- """ open a file and if its a jupyter cell then mock a filepointer to that cell """
+ """ Open a file and if its a jupyter cell then mock a filepointer to that cell. """
if filename.startswith("
-""" Colormap """
-import numpy as np
+""" Colormap. """
from typing import Sequence, Union
+
+import numpy as np
from matplotlib.colors import Colormap, ListedColormap, to_rgb
class CmapColor(list):
- """ a class that appends metadate to a color """
+ """ A class that appends metadate to a color. """
+
def setMeta(self, value, cmap):
self.value = value
self.cmap = cmap
def convert_rgb2lab(colors: Sequence):
- """ convert colors from rgb to lab color space """
+ """ Convert colors from rgb to lab color space. """
from skimage.color import rgb2lab
return [rgb2lab(np.array(c)[None, None, :3]) for c in colors]
def convert_lab2rgb(colors):
- """ convert colors from lab to rgb color space """
+ """ Convert colors from lab to rgb color space. """
from skimage.color import lab2rgb
return [lab2rgb(np.array(c))[0, 0, :3] for c in colors]
class LabColormap(ListedColormap):
- """ a custom colormap that blends between N custom colors """
+ """ A custom colormap that blends between N custom colors. """
+
init_colors = None
def __init__(self, colors: Sequence, N: int, stops=None):
- """ initialize with the given colors and stops """
+ """ Initialize with the given colors and stops. """
# store stops
self.stops = stops
# set colors
@@ -58,7 +61,7 @@ def __init__(self, colors: Sequence, N: int, stops=None):
Colormap.__init__(self, "test", N)
def _init(self):
- """ generate the colormap from the given colors (used by ListedColormap) """
+ """ Generate the colormap from the given colors (used by ListedColormap). """
# convert to lab
lab_colors = convert_rgb2lab(self.init_colors)
# initialize new list
@@ -75,7 +78,7 @@ def _init(self):
ListedColormap._init(self)
def __call__(self, value: float, *args, **kwargs):
- """ get the color associated with the given value from the colormap """
+ """ Get the color associated with the given value from the colormap. """
# get the color
result = Colormap.__call__(self, value, *args, **kwargs)
# add meta values to it
@@ -85,12 +88,12 @@ def __call__(self, value: float, *args, **kwargs):
return result
def get_color(self) -> Sequence:
- """ return all the colors """
+ """ Return all the colors. """
# return the colors
return self.init_colors
def set_color(self, color: Union[str, Sequence], index: int = None):
- """ set a color to the given index """
+ """ Set a color to the given index. """
# update the color according to the index
if index is not None:
self.init_colors[index] = to_rgb(color)
@@ -104,7 +107,7 @@ def set_color(self, color: Union[str, Sequence], index: int = None):
self._isinit = False
def get_stops(self) -> Sequence:
- """ return the stops """
+ """ Return the stops. """
# get the stops
stops = self.stops
# if they are not defined, interpolate from 0 to 1
@@ -114,7 +117,7 @@ def get_stops(self) -> Sequence:
return stops
def linearize_lightness(self):
- """ linearize the lightness of the colors in the colormap """
+ """ Linearize the lightness of the colors in the colormap. """
# convert to lab
lab_colors = convert_rgb2lab(self.init_colors)
# define start and end lightness
diff --git a/pylustrator/parse_svg.py b/pylustrator/parse_svg.py
index 020c0fd..45c36d4 100644
--- a/pylustrator/parse_svg.py
+++ b/pylustrator/parse_svg.py
@@ -19,30 +19,32 @@
# You should have received a copy of the GNU General Public License
# along with Pylustrator. If not, see
+import base64
+import io
+import re
+import sys
from xml.dom import minidom
+
import matplotlib.colors as mcolors
-import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
-import matplotlib.transforms as mtransforms
import matplotlib.path as mpath
-from matplotlib.textpath import TextPath
-from matplotlib.font_manager import FontProperties
-import sys
-import numpy as np
-import re
-import io
-import base64
+import matplotlib.pyplot as plt
import matplotlib.text
+import matplotlib.transforms as mtransforms
+import numpy as np
+from matplotlib.font_manager import FontProperties
+from matplotlib.textpath import TextPath
+
from .arc2bez import arcToBezier
def deform(base_trans: mtransforms.Transform, x: float, y: float, sx: float = 0, sy: float = 0):
- """ apply an affine transformation to the given transformation """
+ """ Apply an affine transformation to the given transformation. """
return mtransforms.Affine2D([[x, sx, 0], [sy, y, 0], [0, 0, 1]]) + base_trans
def parseTransformation(transform_text: str) -> mtransforms.Transform:
- """ convert a transform string in the svg file to a matplotlib transformation """
+ """ Convert a transform string in the svg file to a matplotlib transformation. """
base_trans = mtransforms.IdentityTransform()
if transform_text is None or transform_text == "":
return base_trans
@@ -68,11 +70,11 @@ def parseTransformation(transform_text: str) -> mtransforms.Transform:
base_trans = mtransforms.Affine2D([[x, 0, 0], [0, y, 0], [0, 0, 1]]) + base_trans
elif command == "skewX":
x, = data
- x = np.tan(x*np.pi/180)
+ x = np.tan(x * np.pi / 180)
base_trans = mtransforms.Affine2D([[1, x, 0], [0, 1, 0], [0, 0, 1]]) + base_trans
elif command == "skewY":
y, = data
- y = np.tan(y*np.pi/180)
+ y = np.tan(y * np.pi / 180)
base_trans = mtransforms.Affine2D([[1, 0, 0], [y, 1, 0], [0, 0, 1]]) + base_trans
elif command == "matrix":
x, sy, sx, y, ox, oy = data
@@ -83,7 +85,7 @@ def parseTransformation(transform_text: str) -> mtransforms.Transform:
def get_inline_style(node: minidom.Element, base_style: dict = None) -> dict:
- """ update the basestyle with the style defined by the style property of the node """
+ """ Update the basestyle with the style defined by the style property of the node. """
style = {}
if base_style is not None:
style.update(base_style)
@@ -102,8 +104,8 @@ def get_inline_style(node: minidom.Element, base_style: dict = None) -> dict:
def get_css_style(node: minidom.Element, css_list: list, base_style: dict) -> dict:
- """ update the base_style with the style definitions from the stylesheet that are applicable to the node
- defined by the classes or id of the node
+ """ Update the base_style with the style definitions from the stylesheet that are applicable to the node
+ defined by the classes or id of the node.
"""
style = {}
if base_style is not None:
@@ -121,17 +123,17 @@ def get_css_style(node: minidom.Element, css_list: list, base_style: dict) -> di
def apply_style(style: dict, patch: mpatches.Patch) -> dict:
- """ apply the properties defined in style to the given patch """
+ """ Apply the properties defined in style to the given patch. """
fill_opacity = float(style.get("opacity", 1)) * float(style.get("fill-opacity", 1))
stroke_opacity = float(style.get("opacity", 1)) * float(style.get("stroke-opacity", 1))
def readColor(value):
try:
return mcolors.to_rgb(value)
- except:
+ except Exception:
# matplotlib cannot handle html colors in the form #000
if len(value) == 4 and value[0] == "#":
- return readColor("#"+value[1]*2+value[2]*2+value[3]*2)
+ return readColor("#" + value[1] * 2 + value[2] * 2 + value[3] * 2)
raise
# matplotlib defaults differ
@@ -144,7 +146,7 @@ def readColor(value):
try:
if key == "opacity":
pass
- #patch.set_alpha(float(value))
+ # patch.set_alpha(float(value))
elif key == "fill":
if value == "none" or value == "transparent":
patch.set_facecolor("none")
@@ -174,12 +176,12 @@ def readColor(value):
offset = 0
if isinstance(patch.get_linestyle(), tuple):
offset, dashes = patch.get_linestyle()
- patch.set_linestyle((offset, [float(s)*4 for s in value.split(",")]))
+ patch.set_linestyle((offset, [float(s) * 4 for s in value.split(",")]))
elif key == "stroke-dashoffset":
dashes = [1, 0]
if isinstance(patch.get_linestyle(), tuple):
offset, dashes = patch.get_linestyle()
- patch.set_linestyle((float(value)*4, dashes))
+ patch.set_linestyle((float(value) * 4, dashes))
elif key == "stroke-linecap":
if value == "square":
value = "projecting"
@@ -191,7 +193,7 @@ def readColor(value):
elif key == "stroke-width":
try:
patch.set_linewidth(svgUnitToMpl(value))
- except:
+ except Exception:
pass
elif key == "stroke-linecap":
try:
@@ -223,7 +225,7 @@ def readColor(value):
def font_properties_from_style(style: dict) -> FontProperties:
- """ convert a style to a FontProperties object """
+ """ Convert a style to a FontProperties object. """
fp = FontProperties()
for key, value in style.items():
if key == "font-family":
@@ -242,14 +244,14 @@ def font_properties_from_style(style: dict) -> FontProperties:
def styleNoDisplay(style: dict) -> bool:
- """ check whether the style defines not to display the element """
+ """ Check whether the style defines not to display the element. """
return style.get("display", "inline") == "none" or \
- style.get("visibility", "visible") == "hidden" or \
- style.get("visibility", "visible") == "collapse"
+ style.get("visibility", "visible") == "hidden" or \
+ style.get("visibility", "visible") == "collapse"
def plt_patch(node: minidom.Element, trans_parent_trans: mtransforms.Transform, style: dict, constructor: callable, ids: dict, no_draw: bool = False) -> mpatches.Patch:
- """ add a node to the figure by calling the provided constructor """
+ """ Add a node to the figure by calling the provided constructor. """
trans_node = parseTransformation(node.getAttribute("transform"))
style = get_inline_style(node, get_css_style(node, ids["css"], style))
@@ -261,19 +263,19 @@ def plt_patch(node: minidom.Element, trans_parent_trans: mtransforms.Transform,
if not getattr(p, "is_marker", False):
style = apply_style(style, p)
p.style = style
- #p.set_transform(p.get_transform() + plt.gca().transData)
+ # p.set_transform(p.get_transform() + plt.gca().transData)
p.trans_parent = trans_parent_trans
p.trans_node = parseTransformation(node.getAttribute("transform"))
if not no_draw and not styleNoDisplay(style):
- plt.gca().add_patch(p)
+ plt.gca().add_patch(p)
if node.getAttribute("id") != "":
ids[node.getAttribute("id")] = patch
return patch
def clone_patch(patch: mpatches.Patch) -> mpatches.Patch:
- """ clone a patch element with the same properties as the given patch """
+ """ Clone a patch element with the same properties as the given patch. """
if isinstance(patch, mpatches.Rectangle):
return mpatches.Rectangle(xy=patch.get_xy(),
width=patch.get_width(),
@@ -290,7 +292,7 @@ def clone_patch(patch: mpatches.Patch) -> mpatches.Patch:
def patch_rect(node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict) -> mpatches.Rectangle:
- """ draw a svg rectangle node as a rectangle patch element into the figure (with the given transformation and style) """
+ """ Draw a svg rectangle node as a rectangle patch element into the figure (with the given transformation and style). """
if node.getAttribute("d") != "":
return patch_path(node, trans, style, ids)
if node.getAttribute("ry") != "" and node.getAttribute("ry") != 0:
@@ -306,17 +308,17 @@ def patch_rect(node: minidom.Element, trans: mtransforms.Transform, style: dict,
def patch_ellipse(node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict) -> mpatches.Ellipse:
- """ draw a svg ellipse node as a ellipse patch element into the figure (with the given transformation and style) """
+ """ Draw a svg ellipse node as a ellipse patch element into the figure (with the given transformation and style). """
if node.getAttribute("d") != "":
return patch_path(node, trans, style, ids)
return mpatches.Ellipse(xy=(float(node.getAttribute("cx")), float(node.getAttribute("cy"))),
- width=float(node.getAttribute("rx"))*2,
- height=float(node.getAttribute("ry"))*2,
+ width=float(node.getAttribute("rx")) * 2,
+ height=float(node.getAttribute("ry")) * 2,
transform=trans)
def patch_circle(node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict) -> mpatches.Circle:
- """ draw a svg circle node as a circle patch element into the figure (with the given transformation and style) """
+ """ Draw a svg circle node as a circle patch element into the figure (with the given transformation and style). """
if node.getAttribute("d") != "":
return patch_path(node, trans, style, ids)
return mpatches.Circle(xy=(float(node.getAttribute("cx")), float(node.getAttribute("cy"))),
@@ -325,7 +327,7 @@ def patch_circle(node: minidom.Element, trans: mtransforms.Transform, style: dic
def plt_draw_text(node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict, no_draw: bool = False):
- """ draw a svg text node as a text patch element into the figure (with the given transformation and style) """
+ """ Draw a svg text node as a text patch element into the figure (with the given transformation and style). """
trans = parseTransformation(node.getAttribute("transform")) + trans + plt.gca().transData
trans = mtransforms.Affine2D([[1, 0, 0], [0, -1, 0], [0, 0, 1]]) + trans
if node.getAttribute("x") != "":
@@ -381,9 +383,7 @@ def plt_draw_text(node: minidom.Element, trans: mtransforms.Transform, style: di
def patch_path(node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict) -> list:
- """ draw a path svg node by using a matplotlib path patch (with the given transform and style) """
-
-
+ """ Draw a path svg node by using a matplotlib path patch (with the given transform and style). """
start_pos = None
command = None
verts = []
@@ -417,8 +417,8 @@ def vec2angle(vec):
if not no_angle:
n = len(positions)
- angles[-1].append(vec2angle(verts[-n] - verts[-n-1]))
- for i in range(n-1):
+ angles[-1].append(vec2angle(verts[-n] - verts[-n - 1]))
+ for i in range(n - 1):
angles.append([])
angles.append([vec2angle(verts[-1] - verts[-2])])
else:
@@ -457,7 +457,7 @@ def vec2angle(vec):
# horizontal lineto
elif command == 'h':
current_pos = addPathElement(mpath.Path.LINETO,
- np.array([popValue()+current_pos[0]*(1-absolute), current_pos[1]]))
+ np.array([popValue() + current_pos[0] * (1 - absolute), current_pos[1]]))
# vertical lineto
elif command == 'v':
current_pos = addPathElement(mpath.Path.LINETO,
@@ -521,6 +521,7 @@ def vec2angle(vec):
def addMarker(i, name):
marker_style, patches = ids[name]
+
def add_list_elements(element):
if isinstance(element, list):
for e in element:
@@ -533,7 +534,7 @@ def add_list_elements(element):
a = angles[i]
ca, sa = np.cos(a), np.sin(a)
ox, oy = verts[i]
- trans2 = parent_patch.trans_node + mtransforms.Affine2D([[ca, -sa, ox], [sa, ca, oy], [0, 0, 1]]) + parent_patch.trans_parent + trans#+ plt.gca().transAxes
+ trans2 = parent_patch.trans_node + mtransforms.Affine2D([[ca, -sa, ox], [sa, ca, oy], [0, 0, 1]]) + parent_patch.trans_parent + trans # + plt.gca().transAxes
if marker_style.get("markerUnits", "strokeWidth") == "strokeWidth":
s = svgUnitToMpl(style["stroke-width"])
trans2 = mtransforms.Affine2D([[s, 0, 0], [0, s, 0], [0, 0, 1]]) + trans2
@@ -558,19 +559,19 @@ def add_list_elements(element):
if style.get("marker-mid").startswith("url(#"):
name = style.get("marker-mid")[len("url(#"):-1]
if name in ids:
- for i in range(1, len(angles)-1):
+ for i in range(1, len(angles) - 1):
addMarker(i, name)
if style.get("marker-end"):
if style.get("marker-end").startswith("url(#"):
name = style.get("marker-end")[len("url(#"):-1]
if name in ids:
- addMarker(len(angles)-1, name)
+ addMarker(len(angles) - 1, name)
return patch_list
def svgUnitToMpl(unit: str, default=None) -> float:
- """ convert a unit text to svg pixels """
+ """ Convert a unit text to svg pixels. """
import re
if unit == "":
return default
@@ -594,7 +595,7 @@ def svgUnitToMpl(unit: str, default=None) -> float:
def openImageFromLink(link: str) -> np.ndarray:
- """ load an embedded image file or an externally liked image file"""
+ """ Load an embedded image file or an externally liked image file."""
if link.startswith("file:///"):
return plt.imread(link[len("file:///"):])
else:
@@ -611,7 +612,7 @@ def openImageFromLink(link: str) -> np.ndarray:
def parseStyleSheet(text: str) -> list:
- """ parse a style sheet text """
+ """ Parse a style sheet text. """
# remove line comments
text = re.sub("//.*?\n", "", text)
# remove multiline comments
@@ -631,7 +632,7 @@ def parseStyleSheet(text: str) -> list:
def parseGroup(node: minidom.Element, trans: mtransforms.Transform, style: dict, ids: dict, no_draw: bool = False) -> list:
- """ parse the children of a group node with the inherited transformation and style """
+ """ Parse the children of a group node with the inherited transformation and style. """
trans = parseTransformation(node.getAttribute("transform")) + trans
style = get_inline_style(node, style)
@@ -684,8 +685,8 @@ def parseGroup(node: minidom.Element, trans: mtransforms.Transform, style: dict,
svgUnitToMpl(child.getAttribute("y")), svgUnitToMpl(child.getAttribute("y")) + svgUnitToMpl(child.getAttribute("height")),
], zorder=1)
else:
- pass#im_patch = plt.imshow(im[::-1], zorder=1)
- #patch_list.append(im_patch)
+ pass # im_patch = plt.imshow(im[::-1], zorder=1)
+ # patch_list.append(im_patch)
elif child.tagName == "metadata":
pass # we do not have to draw metadata
else:
@@ -698,16 +699,16 @@ def parseGroup(node: minidom.Element, trans: mtransforms.Transform, style: dict,
def svgread(filename: str):
- """ read an SVG file """
+ """ Read an SVG file. """
doc = minidom.parse(filename)
svg = doc.getElementsByTagName("svg")[0]
try:
x1, y1, x2, y2 = [svgUnitToMpl(s.strip()) for s in svg.getAttribute("viewBox").split()]
- width, height = (x2 - x1)/plt.gcf().dpi, (y2 - y1)/plt.gcf().dpi
+ width, height = (x2 - x1) / plt.gcf().dpi, (y2 - y1) / plt.gcf().dpi
if max([width, height]) > 8:
- f = 8/max([width, height])
- plt.gcf().set_size_inches(width*f, height*f)
+ f = 8 / max([width, height])
+ plt.gcf().set_size_inches(width * f, height * f)
else:
plt.gcf().set_size_inches(width, height)
except ValueError:
@@ -717,8 +718,8 @@ def svgread(filename: str):
width /= plt.gcf().dpi
height /= plt.gcf().dpi
if max([width, height]) > 8:
- f = 8/max([width, height])
- plt.gcf().set_size_inches(width*f, height*f)
+ f = 8 / max([width, height])
+ plt.gcf().set_size_inches(width * f, height * f)
else:
plt.gcf().set_size_inches(width, height)
ax = plt.axes([0, 0, 1, 1], label=filename, frameon=False)
diff --git a/pylustrator/pyjack.py b/pylustrator/pyjack.py
index 270fc45..a61aa61 100644
--- a/pylustrator/pyjack.py
+++ b/pylustrator/pyjack.py
@@ -12,12 +12,12 @@
NOTE: adapted for pylustrator to be Python3 compatible
"""
-import sys as _sys
import gc as _gc
-import types as _types
import inspect as _inspect
+import sys as _sys
+import types as _types
-_WRAPPER_TYPES = (type(object.__init__), type(object().__init__),)
+_WRAPPER_TYPES = (type(object.__init__), type(object().__init__))
# deactivated closure support as this code does not work for python3
"""
@@ -30,7 +30,9 @@ def proxy1(): return data
_CELLTYPE = int # type(proxy0(None).func_closure[0])
"""
-class PyjackException(Exception): pass
+
+class PyjackException(Exception):
+ pass
def connect(fn, proxyfn):
@@ -77,7 +79,7 @@ def restore():
fn.restore = restore
return fn
else:
- bundle = (fn, fn_type,)
+ bundle = (fn, fn_type)
raise PyjackException("fn %r of type '%r' not supported" % bundle)
@@ -147,7 +149,6 @@ def replace_all_refs(org_obj, new_obj):
Python runtime interns strings.
"""
-
_gc.collect()
hit = False
@@ -196,7 +197,7 @@ def replace_all_refs(org_obj, new_obj):
hit = True
# TUPLE, FROZENSET
- elif isinstance(referrer, (tuple, frozenset,)):
+ elif isinstance(referrer, (tuple, frozenset)):
new_tuple = []
for obj in referrer:
if obj is org_obj:
@@ -206,7 +207,7 @@ def replace_all_refs(org_obj, new_obj):
replace_all_refs(referrer, type(referrer)(new_tuple))
# CELLTYPE (deactivated as it makes problems im Python3)
- #elif isinstance(referrer, _CELLTYPE):
+ # elif isinstance(referrer, _CELLTYPE):
# def proxy0(data):
# def proxy1(): return data
@@ -223,9 +224,9 @@ def replace_all_refs(org_obj, new_obj):
'func_defaults', 'func_closure']:
orgattr = getattr(referrer, key)
if orgattr is org_obj:
- localsmap[key.split('func_')[-1]] = new_obj
+ localsmap[key.rsplit('func_', maxsplit=1)[-1]] = new_obj
else:
- localsmap[key.split('func_')[-1]] = orgattr
+ localsmap[key.rsplit('func_', maxsplit=1)[-1]] = orgattr
localsmap['argdefs'] = localsmap['defaults']
del localsmap['defaults']
newfn = _types.FunctionType(**localsmap)
@@ -235,10 +236,11 @@ def replace_all_refs(org_obj, new_obj):
else:
# debug:
import sys
+
# print(type(referrer), file=sys.stderr)
pass
- #if hit is False:
+ # if hit is False:
# raise AttributeError("Object '%r' not found" % org_obj)
return org_obj
@@ -255,7 +257,8 @@ def _get_self():
return _func_code_map[code]
-class _PyjackFunc(object): pass
+class _PyjackFunc():
+ pass
class _PyjackFuncCode(_PyjackFunc):
@@ -300,7 +303,7 @@ def __getattr__(self, attr):
try:
return getattr(self._fn, attr)
except AttributeError:
- bundle = (self._fn, attr,)
+ bundle = (self._fn, attr)
raise AttributeError("function %r has no attr '%s'" % bundle)
def restore(self):
@@ -311,4 +314,3 @@ def restore(self):
import doctest
doctest.testmod(optionflags=524)
-
diff --git a/pylustrator/snap.py b/pylustrator/snap.py
index e832130..8cf114b 100644
--- a/pylustrator/snap.py
+++ b/pylustrator/snap.py
@@ -19,29 +19,31 @@
# You should have received a copy of the GNU General Public License
# along with Pylustrator. If not, see
-from typing import List, Tuple, Optional
-from packaging import version
-from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
+from typing import List, Optional, Tuple
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.artist import Artist
+from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets
+from packaging import version
+
try: # starting from mpl version 3.6.0
from matplotlib.axes import Axes
-except:
+except Exception:
from matplotlib.axes._subplots import Axes
+
from matplotlib.legend import Legend
from matplotlib.lines import Line2D
-from matplotlib.patches import Rectangle, Ellipse, FancyArrowPatch
+from matplotlib.patches import Ellipse, FancyArrowPatch, Rectangle
from matplotlib.text import Text
+
try:
from matplotlib.figure import SubFigure # since matplotlib 3.4.0
except ImportError:
SubFigure = None
from .helper_functions import main_figure
-
DIR_X0 = 1
DIR_Y0 = 2
DIR_X1 = 4
@@ -49,14 +51,14 @@
def checkXLabel(target: Artist):
- """ checks if the target is the xlabel of an axis """
+ """ Checks if the target is the xlabel of an axis. """
for axes in target.figure.axes:
if axes.xaxis.get_label() == target:
return axes
def checkYLabel(target: Artist):
- """ checks if the target is the ylabel of an axis """
+ """ Checks if the target is the ylabel of an axis. """
for axes in target.figure.axes:
if axes.yaxis.get_label() == target:
return axes
@@ -68,10 +70,12 @@ def cache_property(object, name):
setattr(object, f"_pylustrator_cached_{name}", True)
getter = getattr(object, f"get_{name}")
setter = getattr(object, f"set_{name}")
+
def new_getter(*args, **kwargs):
if getattr(object, f"_pylustrator_cache_{name}", None) is None:
setattr(object, f"_pylustrator_cache_{name}", getter(*args, **kwargs))
return getattr(object, f"_pylustrator_cache_{name}", None)
+
def new_setter(*args, **kwargs):
result = setter(*args, **kwargs)
setattr(object, f"_pylustrator_cache_{name}", None)
@@ -80,9 +84,9 @@ def new_setter(*args, **kwargs):
setattr(object, f"set_{name}", new_setter)
+class TargetWrapper():
+ """ A wrapper to add unified set and get position methods for any matplotlib artist. """
-class TargetWrapper(object):
- """ a wrapper to add unified set and get position methods for any matplotlib artist """
target = None
def __init__(self, target: Artist):
@@ -131,7 +135,7 @@ def __init__(self, target: Artist):
self.do_scale = False
def get_positions(self, use_previous_offset=False, update_offset=False) -> (int, int, int, int):
- """ get the current position of the target Artist """
+ """ Get the current position of the target Artist. """
points = []
if isinstance(self.target, Rectangle):
points.append(self.target.get_xy())
@@ -194,7 +198,7 @@ def get_positions(self, use_previous_offset=False, update_offset=False) -> (int,
return self.transform_points(points)
def set_positions(self, points: (int, int)):
- """ set the position of the target Artist """
+ """ Set the position of the target Artist. """
points = self.transform_inverted_points(points)
if self.figure.figure is not None:
@@ -208,25 +212,25 @@ def set_positions(self, points: (int, int)):
self.target.set_height(points[1][1] - points[0][1])
if self.target.get_label() is None or not self.target.get_label().startswith("_rect"):
change_tracker.addChange(self.target, ".set_xy([%f, %f])" % tuple(self.target.get_xy()))
- change_tracker.addChange(self.target, ".set_width(%f)" % self.target.get_width())
- change_tracker.addChange(self.target, ".set_height(%f)" % self.target.get_height())
+ change_tracker.addChange(self.target, f".set_width({self.target.get_width():f})")
+ change_tracker.addChange(self.target, f".set_height({self.target.get_height():f})")
elif isinstance(self.target, Ellipse):
self.target.center = np.mean(points, axis=0)
self.target.width = points[1][0] - points[0][0]
self.target.height = points[1][1] - points[0][1]
change_tracker.addChange(self.target, ".center = (%f, %f)" % tuple(self.target.center))
- change_tracker.addChange(self.target, ".width = %f" % self.target.width)
- change_tracker.addChange(self.target, ".height = %f" % self.target.height)
+ change_tracker.addChange(self.target, f".width = {self.target.width:f}")
+ change_tracker.addChange(self.target, f".height = {self.target.height:f}")
elif isinstance(self.target, FancyArrowPatch):
self.target.set_positions(points[0], points[1])
change_tracker.addChange(self.target,
- ".set_positions(%s, %s)" % (tuple(points[0]), tuple(points[1])))
+ f".set_positions({tuple(points[0])}, {tuple(points[1])})")
elif isinstance(self.target, Text):
if checkXLabel(self.target):
axes = checkXLabel(self.target)
axes.xaxis.labelpad = -(points[0][1] - self.target.pad_offset) / self.label_factor
change_tracker.addChange(axes,
- ".xaxis.labelpad = %f" % axes.xaxis.labelpad)
+ f".xaxis.labelpad = {axes.xaxis.labelpad:f}")
self.target.set_position(points[0])
self.label_y = points[0][1]
@@ -234,7 +238,7 @@ def set_positions(self, points: (int, int)):
axes = checkYLabel(self.target)
axes.yaxis.labelpad = -(points[0][0] - self.target.pad_offset) / self.label_factor
change_tracker.addChange(axes,
- ".yaxis.labelpad = %f" % axes.yaxis.labelpad)
+ f".yaxis.labelpad = {axes.yaxis.labelpad:f}")
self.target.set_position(points[0])
self.label_x = points[0][0]
@@ -244,7 +248,7 @@ def set_positions(self, points: (int, int)):
change_tracker.addNewTextChange(self.target)
else:
change_tracker.addChange(self.target,
- ".set_position([%f, %f])" % self.target.get_position())
+ ".set_position([%f, %f])" % self.target.get_position())
if getattr(self.target, "xy", None) is not None:
self.target.xy = points[1]
change_tracker.addChange(self.target, ".xy = (%f, %f)" % tuple(self.target.xy))
@@ -252,14 +256,14 @@ def set_positions(self, points: (int, int)):
point = self.target.axes.transAxes.inverted().transform(self.transform_inverted_points(points)[0])
self.target._loc = tuple(point)
change_tracker.addNewLegendChange(self.target)
- #change_tracker.addChange(self.target, "._set_loc((%f, %f))" % tuple(point))
+ # change_tracker.addChange(self.target, "._set_loc((%f, %f))" % tuple(point))
elif isinstance(self.target, Axes):
position = np.array([points[0], points[1] - points[0]]).flatten()
if self.fixed_aspect:
position[3] = position[2] * self.target.get_position().height / self.target.get_position().width
self.target.set_position(position)
change_tracker.addNewAxesChange(self.target)
- #change_tracker.addChange(self.target, ".set_position([%f, %f, %f, %f])" % tuple(
+ # change_tracker.addChange(self.target, ".set_position([%f, %f, %f, %f])" % tuple(
# np.array([points[0], points[1] - points[0]]).flatten()))
setattr(self.target, "_pylustrator_cached_get_extend", None)
@@ -272,7 +276,7 @@ def get_extent(self):
return getattr(self.target, "_pylustrator_cached_get_extend")
def do_get_extent(self) -> (int, int, int, int):
- """ get the extent of the target """
+ """ Get the extent of the target. """
points = np.array(self.get_positions())
return [np.min(points[:, 0]),
np.min(points[:, 1]),
@@ -280,18 +284,19 @@ def do_get_extent(self) -> (int, int, int, int):
np.max(points[:, 1])]
def transform_points(self, points: (int, int)) -> (int, int):
- """ transform points from the targets local coordinate system to the figure coordinate system """
+ """ Transform points from the targets local coordinate system to the figure coordinate system. """
transform = self.get_transform()
return [transform.transform(p) for p in points]
def transform_inverted_points(self, points: (int, int)) -> (int, int):
- """ transform points from the figure coordinate system to the targets local coordinate system """
+ """ Transform points from the figure coordinate system to the targets local coordinate system. """
transform = self.get_transform()
return [transform.inverted().transform(p) for p in points]
class SnapBase():
""" The base class to implement snaps. """
+
data = None
def __init__(self, ax_source: Artist, ax_target: Artist, edge: int):
@@ -308,25 +313,25 @@ def __init__(self, ax_source: Artist, ax_target: Artist, edge: int):
self.draw_path.setPen(pen1)
def getPosition(self, target: TargetWrapper):
- """ get the position of a target """
+ """ Get the position of a target. """
try:
return target.get_extent()
except AttributeError:
return np.array(target.figure.transFigure.transform(target.get_position())).flatten()
def getDistance(self, index: int) -> (int, int):
- """ Calculate the distance of the snap to its target """
+ """ Calculate the distance of the snap to its target. """
return 0, 0
def checkSnap(self, index: int) -> Optional[float]:
- """ Return the distance to the targets or None """
+ """ Return the distance to the targets or None. """
distance = self.getDistance(index)
if abs(distance) < 10:
return distance
return None
def checkSnapActive(self):
- """ Test if the snap condition is fullfilled """
+ """ Test if the snap condition is fullfilled. """
distance = min([self.getDistance(index) for index in [0, 1]])
# show the snap if the distance to a target is smaller than 1
if abs(distance) < 1:
@@ -335,7 +340,7 @@ def checkSnapActive(self):
self.hide()
def show(self):
- """ Implements a visualisation of the snap, e.g. lines to indicate what objects are snapped to what """
+ """ Implements a visualisation of the snap, e.g. lines to indicate what objects are snapped to what. """
pass
def set_data(self, xdata, ydata):
@@ -363,11 +368,11 @@ def set_data(self, xdata, ydata):
self.data = (xdata, ydata)
def hide(self):
- """ Hides the visualisation """
+ """ Hides the visualisation. """
self.set_data((), ())
def remove(self):
- """ Remove the snap and its visualisation """
+ """ Remove the snap and its visualisation. """
self.hide()
try:
self.draw_path.scene().removeItem(self.draw_path)
@@ -376,10 +381,10 @@ def remove(self):
class SnapSameEdge(SnapBase):
- """ a snap that checks if two objects share an edge """
+ """ A snap that checks if two objects share an edge. """
def getDistance(self, index: int) -> (int, int):
- """ Calculate the distance of the snap to its target """
+ """ Calculate the distance of the snap to its target. """
# only if the right edge index (x or y) is queried, if not the distance is infinite
if self.edge % 2 != index:
return np.inf
@@ -390,7 +395,7 @@ def getDistance(self, index: int) -> (int, int):
return p1[self.edge] - p2[self.edge]
def show(self):
- """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what """
+ """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what. """
# get the position of both objects
p1 = self.getPosition(self.ax_source)
p2 = self.getPosition(self.ax_target)
@@ -405,10 +410,10 @@ def show(self):
class SnapSameDimension(SnapBase):
- """ a snap that checks if two objects have the same width or height """
+ """ A snap that checks if two objects have the same width or height. """
def getDistance(self, index: int) -> (int, int):
- """ Calculate the distance of the snap to its target """
+ """ Calculate the distance of the snap to its target. """
# only if the right edge index (x or y) is queried, if not the distance is infinite
if self.edge % 2 != index:
return np.inf
@@ -419,7 +424,7 @@ def getDistance(self, index: int) -> (int, int):
return (p2[self.edge - 2] - p2[self.edge]) - (p1[self.edge - 2] - p1[self.edge])
def show(self):
- """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what """
+ """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what. """
# get the position of both objects
p1 = self.getPosition(self.ax_source)
p2 = self.getPosition(self.ax_target)
@@ -436,14 +441,14 @@ def show(self):
class SnapSamePos(SnapBase):
- """ a snap that checks if two objects have the same position """
+ """ A snap that checks if two objects have the same position. """
def getPosition(self, text: TargetWrapper) -> (int, int):
# get the position of an object
return np.array(text.get_transform().transform(text.target.get_position()))
def getDistance(self, index: int) -> int:
- """ Calculate the distance of the snap to its target """
+ """ Calculate the distance of the snap to its target. """
# only if the right edge index (x or y) is queried, if not the distance is infinite
if self.edge % 2 != index:
return np.inf
@@ -454,7 +459,7 @@ def getDistance(self, index: int) -> int:
return p1[self.edge] - p2[self.edge]
def show(self):
- """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what """
+ """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what. """
# get the position of both objects
p1 = self.getPosition(self.ax_source)
p2 = self.getPosition(self.ax_target)
@@ -463,17 +468,15 @@ def show(self):
class SnapSameBorder(SnapBase):
- """ A snap that checks if tree axes share the space between them """
+ """ A snap that checks if tree axes share the space between them. """
def __init__(self, ax_source: Artist, ax_target: Artist, ax_target2: Artist, edge: int):
super().__init__(ax_source, ax_target, edge)
self.ax_target2 = ax_target2
def overlap(self, p1: list, p2: list, dir: int):
- """ Test if two objects have an overlapping x or y region """
- if p1[dir + 2] < p2[dir] or p1[dir] > p2[dir + 2]:
- return False
- return True
+ """ Test if two objects have an overlapping x or y region. """
+ return not p1[dir + 2] < p2[dir] or p1[dir] > p2[dir + 2]
def getBorders(self, p1: list, p2: list):
borders = []
@@ -488,7 +491,7 @@ def getBorders(self, p1: list, p2: list):
return np.array(borders)
def getDistance(self, index: int):
- """ Calculate the distance of the snap to its target """
+ """ Calculate the distance of the snap to its target. """
# get the positions of all three targets
p1 = self.getPosition(self.ax_source)
p2 = self.getPosition(self.ax_target)
@@ -515,7 +518,7 @@ def getDistance(self, index: int):
return np.inf
def getConnection(self, p1: list, p2: list, dir: int):
- """ return the coordinates of a line that spans the space between to axes """
+ """ Return the coordinates of a line that spans the space between to axes. """
# check which edge (e.g. x, y) and which direction (e.g. if to change the order of p1 and p2)
edge, order = dir // 2, dir % 2
# optionally change p1 with p2
@@ -530,7 +533,7 @@ def getConnection(self, p1: list, p2: list, dir: int):
return [[x, x, np.nan], [p1[3], p2[1], np.nan]]
def show(self):
- """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what """
+ """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what. """
# get the positions of all three axes
p1 = self.getPosition(self.ax_source)
p2 = self.getPosition(self.ax_target)
@@ -544,21 +547,21 @@ def show(self):
class SnapCenterWith(SnapBase):
- """ A snap that checks if a text is centered with an axes """
+ """ A snap that checks if a text is centered with an axes. """
def getPosition(self, text: TargetWrapper) -> (int, int):
- """ get the position of the first object """
+ """ Get the position of the first object. """
return np.array(text.get_transform().transform(text.target.get_position()))
def getPosition2(self, axes: TargetWrapper) -> int:
- """ get the position of the second object """
+ """ Get the position of the second object. """
pos = np.array(axes.figure.transFigure.transform(axes.target.get_position()))
p = pos[0, :]
p[self.edge] = np.mean(pos, axis=0)[self.edge]
return p
def getDistance(self, index: int) -> int:
- """ Calculate the distance of the snap to its target """
+ """ Calculate the distance of the snap to its target. """
# only if the right edge index (x or y) is queried, if not the distance is infinite
if self.edge % 2 != index:
return np.inf
@@ -569,7 +572,7 @@ def getDistance(self, index: int) -> int:
return p1[self.edge] - p2[self.edge]
def show(self):
- """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what """
+ """ A visualisation of the snap, e.g. lines to indicate what objects are snapped to what. """
# get the position of both objects
p1 = self.getPosition(self.ax_source)
p2 = self.getPosition2(self.ax_target)
@@ -578,7 +581,7 @@ def show(self):
def checkSnaps(snaps: List[SnapBase]) -> (int, int):
- """ get the x and y offsets the snaps suggest """
+ """ Get the x and y offsets the snaps suggest. """
result = [0, 0]
# iterate over x and y
for index in range(2):
@@ -596,13 +599,13 @@ def checkSnaps(snaps: List[SnapBase]) -> (int, int):
def checkSnapsActive(snaps: List[SnapBase]):
- """ check if snaps are active and show them if yes """
+ """ Check if snaps are active and show them if yes. """
for snap in snaps:
snap.checkSnapActive()
def getSnaps(targets: List[TargetWrapper], dir: int, no_height=False) -> List[SnapBase]:
- """ get all snap objects for the target and the direction """
+ """ Get all snap objects for the target and the direction. """
snaps = []
targets = [t.target for t in targets]
for target in targets:
diff --git a/setup.py b/setup.py
index a6abdbd..101fe9a 100644
--- a/setup.py
+++ b/setup.py
@@ -19,10 +19,11 @@
# You should have received a copy of the GNU General Public License
# along with Pylustrator. If not, see
-from setuptools import setup
-
# read the contents of your README file
from pathlib import Path
+
+from setuptools import setup
+
this_directory = Path(__file__).parent
long_description = (this_directory / "README.md").read_text()
@@ -41,6 +42,6 @@
'numpy',
'matplotlib',
'qtawesome',
- 'scikit-image'
+ 'scikit-image',
],
)
diff --git a/tests/base_test_class.py b/tests/base_test_class.py
index 0c3dec5..a4f8a07 100644
--- a/tests/base_test_class.py
+++ b/tests/base_test_class.py
@@ -1,17 +1,19 @@
-import unittest
-import numpy as np
import re
+import unittest
from pathlib import Path
-import matplotlib.pyplot as plt
-from matplotlib.backend_bases import MouseEvent, KeyEvent
from typing import Any
+import matplotlib.pyplot as plt
+import numpy as np
+from matplotlib.backend_bases import KeyEvent, MouseEvent
+
def ensure_list(obj, count=1):
if isinstance(obj, list):
return obj
else:
- return [obj]*count
+ return [obj] * count
+
def select_elements(fig, get_obj_list):
if not isinstance(get_obj_list, list):
@@ -32,9 +34,11 @@ def select_elements(fig, get_obj_list):
class Undefined:
pass
+
class NotInSave:
pass
+
class Value:
def __init__(self, value, value_saved=Undefined):
self.value = value
@@ -114,7 +118,7 @@ def check_line_in_file(self, line_start):
def grab_args(*args, **kwargs):
return args, kwargs
- arguments = re.match(re.escape(line_start)+r"(.*)\)", line).groups()[0]
+ arguments = re.match(re.escape(line_start) + r"(.*)\)", line).groups()[0]
return line, eval(f"grab_args({arguments})")
if line.startswith("#% start: automatic generated code from pylustrator"):
in_block = True
@@ -153,7 +157,7 @@ def check_saved_property(self, property_name, line_command, value2, test_run="")
if value2 is NotInSave:
return
if property_name == "visible" and line_command.endswith(".text("):
- kwargs = dict(visible=False)
+ kwargs = {"visible": False}
else:
raise err
if line_command.endswith(".text("):
@@ -291,12 +295,12 @@ def change_property2(self, property_name_list, value_list, call, get_obj_list, l
# test if the text has the right weight
self.compare_list(get_obj_list, get_function(), value_list,
- f"Property '{property_name_list[0]}' not set correctly. [{test_run}]")
+ f"Property '{property_name_list[0]}' not set correctly. [{test_run}]")
# test undo and redo
fig.window.undo()
self.compare_list(get_obj_list, get_function(), current_values,
- f"Property '{property_name_list[0]}' undo failed. [{test_run}]")
+ f"Property '{property_name_list[0]}' undo failed. [{test_run}]")
if self.no_undo_save_test is False:
# the output after undo should be the same as the beginning
@@ -307,7 +311,7 @@ def change_property2(self, property_name_list, value_list, call, get_obj_list, l
fig.window.redo()
self.compare_list(get_obj_list, get_function(), value_list,
- f"Property '{property_name_list[0]}' redo failed. [{test_run}]")
+ f"Property '{property_name_list[0]}' redo failed. [{test_run}]")
print("\n---- save after redo ----", end="")
fig.change_tracker.save()
@@ -326,7 +330,7 @@ def change_property2(self, property_name_list, value_list, call, get_obj_list, l
try:
# test if the text has the right weight
self.compare_list(get_obj_list, get_function(), value_list,
- f"Property '{property_name_list[0]}' not set failed. [{test_run}]")
+ f"Property '{property_name_list[0]}' not set failed. [{test_run}]")
# don't move it and save the result
self.move_element((0, 0), get_obj_list)
@@ -342,4 +346,4 @@ def change_property2(self, property_name_list, value_list, call, get_obj_list, l
# the output should still be the same
self.assertEqual(text, self.get_script_text(),
- f"Saved differently. Property '{property_name_list}'. [{test_run}]")
\ No newline at end of file
+ f"Saved differently. Property '{property_name_list}'. [{test_run}]")
diff --git a/tests/test_axes.py b/tests/test_axes.py
index 83ac667..4b0e575 100644
--- a/tests/test_axes.py
+++ b/tests/test_axes.py
@@ -40,22 +40,22 @@ def test_axis_limits(self):
test_run = "Change axes limits."
self.change_property2("xlim", (1, 10), lambda _: self.fig.window.input_properties.input_xaxis.input_lim.setValue((1, 10), signal=True), get_axes, line_command,
- test_run)
+ test_run)
self.change_property2("ylim", (2, 8), lambda _: self.fig.window.input_properties.input_yaxis.input_lim.setValue((2, 8), signal=True), get_axes, line_command,
- test_run)
+ test_run)
self.change_property2("xlabel", "label",
- lambda _: self.fig.window.input_properties.input_xaxis.input_label.setText("label",
- signal=True),
- get_axes, line_command,
- test_run)
+ lambda _: self.fig.window.input_properties.input_xaxis.input_label.setText("label",
+ signal=True),
+ get_axes, line_command,
+ test_run)
self.change_property2("ylabel", "label",
- lambda _: self.fig.window.input_properties.input_yaxis.input_label.setText("label",
- signal=True),
- get_axes, line_command,
- test_run)
+ lambda _: self.fig.window.input_properties.input_yaxis.input_label.setText("label",
+ signal=True),
+ get_axes, line_command,
+ test_run)
get_axes = [lambda: fig.axes[0], lambda: fig.axes[1]]
line_command = ["plt.figure(1).axes[0].set(", "plt.figure(1).axes[1].set("]
@@ -84,6 +84,7 @@ def test_axis_limits(self):
signal=True),
get_axes, line_command,
test_run)
+
def test_axis_grid(self):
# get the figure
fig, text = self.run_plot_script()
@@ -197,21 +198,22 @@ def check():
return check
# minor ticks
+
def set_ticks(_):
self.fig.window.input_properties.input_xaxis.tick_edit.setTarget(get_axes())
self.fig.window.input_properties.input_xaxis.tick_edit.input_ticks2.setText('10^-2\n0.1 "a"\n0.2 "b\n0.3 c\n0.5',
- signal=True)
+ signal=True)
self.change_property2("xticks", [0.01, 0.1, 0.2, 0.3, 0.5], set_ticks, get_axes, line_command, test_run,
- test_saved_value=check_saved_property("x"), get_function=lambda: get_axes().get_xticks(minor=True))
+ test_saved_value=check_saved_property("x"), get_function=lambda: get_axes().get_xticks(minor=True))
def set_ticks(_):
self.fig.window.input_properties.input_yaxis.tick_edit.setTarget(get_axes())
self.fig.window.input_properties.input_yaxis.tick_edit.input_ticks2.setText('10^-2\n0.1 "a"\n0.2 "b\n0.3 c\n0.5',
- signal=True)
+ signal=True)
self.change_property2("yticks", [0.01, 0.1, 0.2, 0.3, 0.5], set_ticks, get_axes, line_command, test_run,
- test_saved_value=check_saved_property("y"), get_function=lambda: get_axes().get_yticks(minor=True))
+ test_saved_value=check_saved_property("y"), get_function=lambda: get_axes().get_yticks(minor=True))
def test_axes_alignment(self):
# get the figure
diff --git a/tests/test_legend.py b/tests/test_legend.py
index b5202c9..0cf642b 100644
--- a/tests/test_legend.py
+++ b/tests/test_legend.py
@@ -1,5 +1,5 @@
-from matplotlib.backend_bases import KeyEvent
from base_test_class import BaseTest
+from matplotlib.backend_bases import KeyEvent
from matplotlib.legend import Legend
@@ -20,7 +20,7 @@ def test_legend_properties(self):
self.move_element((0, 0), get_legend)
- #self.check_text_properties(get_text, line_command, test_run, 0.4931, 0.4979)
+ # self.check_text_properties(get_text, line_command, test_run, 0.4931, 0.4979)
self.change_property("loc", (x, 0.856602), lambda _: self.move_element((-1, 0)), get_legend, line_command,
test_run, get_function=lambda: get_legend()._loc)
self.change_property("loc", (0.047605, 0.856602), lambda _: self.move_element((1, 0)), get_legend, line_command,
@@ -29,10 +29,10 @@ def test_legend_properties(self):
test_run, get_function=lambda: get_legend()._loc)
self.change_property("loc", (0.047605, 0.856602), lambda _: self.move_element((0, 1)), get_legend, line_command,
test_run, get_function=lambda: get_legend()._loc)
- #self.change_property("loc", (0.2, 0.5),
+ # self.change_property("loc", (0.2, 0.5),
# lambda _: fig.window.input_size.input_position.valueChangedX.emit(0.2), get_text,
# line_command, test_run, get_function=lambda: get_text()._loc)
- #self.change_property("loc", (0.2, 0.2),
+ # self.change_property("loc", (0.2, 0.2),
# lambda _: fig.window.input_size.input_position.valueChangedY.emit(0.2), get_text,
# line_command, test_run, get_function=lambda: get_text()._loc)
@@ -95,5 +95,3 @@ def test_legend_properties(self):
lambda _: fig.window.input_properties.input_legend_properties.widgets[
"title_fontsize"].setValue(23),
get_legend, line_command, test_run, get_function=lambda: get_legend().get_title().get_fontsize())
-
-
diff --git a/tests/test_text.py b/tests/test_text.py
index d2e67c8..2ec62cd 100644
--- a/tests/test_text.py
+++ b/tests/test_text.py
@@ -1,5 +1,5 @@
-from matplotlib.backend_bases import KeyEvent
from base_test_class import BaseTest, NotInSave
+from matplotlib.backend_bases import KeyEvent
class TestText(BaseTest):
@@ -200,9 +200,9 @@ def test_text_delete(self):
test_run = "Delete new text in axes."
self.change_property2("visible", False,
- lambda _: fig.figure_dragger.selection.keyPressEvent(
+ lambda _: fig.figure_dragger.selection.keyPressEvent(
KeyEvent('delete', fig.canvas, "delete")), get_text, line_command,
- test_run, delete=True)
+ test_run, delete=True)
def check_text_properties(self, get_text, line_command, test_run, x, y):
fig = self.fig