Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactoring the plotting system #166

Merged
merged 49 commits into from
Jun 4, 2021
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
c76da0e
started working on better plot system.
wpfff Jun 23, 2020
154f909
merge from main repo.
wpfff Nov 25, 2020
27462a3
added some basic functionality to find config files.
wpfff Dec 3, 2020
3f18cdd
small fix: allow passing of keyword arguments from the command line.
wpfff Dec 3, 2020
491a128
[WIP] started to move matplotlib helper functions around.
wpfff Dec 3, 2020
ea93db1
fix: don't import from lower level module on import to avoid circular…
wpfff Dec 3, 2020
08a0025
added stylefile where we can easily find it.
wpfff Dec 3, 2020
fa6c6f8
moved things up a level that are basically plotting backend-independent.
wpfff Dec 3, 2020
80ee2ac
moved widgets that use MPL but are higher-level than autoplot into here.
wpfff Dec 3, 2020
0cc7b6b
[WIP] first steps and in modified autoplot using matplotlib. so far o…
wpfff Dec 3, 2020
97a9230
merge from remote master.
wpfff Dec 3, 2020
fabaa76
Merge branch 'master' into betterplotting
wpfff Jan 3, 2021
e92bd1b
Merge branch 'master' into betterplotting
wpfff Apr 22, 2021
990c00f
some better organization:
wpfff Apr 23, 2021
29d1e9b
clarifying better which objects are responsible for what:
wpfff Apr 24, 2021
3f589a1
re-enabled basic colorplotting.
wpfff Apr 24, 2021
d79fcbf
fixed type hints in plotting.py
wpfff Apr 24, 2021
45ee82d
fixing more typing annotations for mypy.
wpfff Apr 24, 2021
6d845e1
more cleanup for mypy.
wpfff Apr 24, 2021
c7669d1
more cleanup for mypy.
wpfff Apr 24, 2021
dfeccd5
more cleanup for mypy.
wpfff Apr 24, 2021
159a82e
more cleanup for mypy.
wpfff Apr 24, 2021
3c0ab02
emit windowClosed signal for all PlotWindow objects.
wpfff Apr 25, 2021
dcc09fa
improved testing of the autoplot app.
wpfff Apr 25, 2021
3984dbd
fixed tests to work with latest changes on FigureMaker.
wpfff Apr 25, 2021
0ff7382
- make sure we plot in the correct panels when plotting complex data …
wpfff Apr 25, 2021
0663751
minor fixes and aesthetic improvements
wpfff Apr 25, 2021
9980c30
Updated docs for the base plotting module.
wpfff Apr 25, 2021
0a70e4c
minor fixes (broken links).
wpfff Apr 25, 2021
4451121
completed docs for plotting modules.
wpfff Apr 26, 2021
338494c
added font scaling for high-dpi.
wpfff Apr 26, 2021
2fb7265
Full complex options buttons
FarBo Apr 27, 2021
539c068
- fix plotting of Real part only
wpfff Apr 28, 2021
1498973
reimplemented dpi scaling in all widgets and copying meta info.
wpfff Apr 29, 2021
acb0e8d
make sure meta entries don't get too long when copying.
wpfff Apr 30, 2021
a6a69f9
don't inherit from object.
wpfff Apr 30, 2021
f01cf40
- added central method for scaling dpi
wpfff May 2, 2021
5523567
change back to bigger icons in the toolbar
wpfff May 11, 2021
1ec2d6a
fixed a couple of mypy issues.
wpfff May 11, 2021
a792928
more mypy fixing.
wpfff May 11, 2021
2ab1ca6
Add disappeared "imported plottr version" log message
astafan8 May 31, 2021
90e3dc8
Merge branch 'master' into betterplotting
astafan8 Jun 1, 2021
a9ef0f0
importing config files without modifying sys.path.
wpfff Jun 3, 2021
6f3cb98
clarify why `formatSubPlot` does not raise an error when not reimplem…
wpfff Jun 3, 2021
1d09196
fix mypy issue with importlib.
wpfff Jun 3, 2021
c12e508
Update plottr/plot/mpl/widgets.py
wpfff Jun 4, 2021
5359ee4
enforcing real in a more sane spot.
wpfff Jun 4, 2021
8622f9c
Merge remote-tracking branch 'origin/betterplotting' into betterplotting
wpfff Jun 4, 2021
ba44f82
fix typing.
wpfff Jun 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions doc/api/plot.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,67 @@
Plotting elements
-----------------
#################

.. _Base plot API:

Base plotting elements
^^^^^^^^^^^^^^^^^^^^^^

Overview
========

Classes for plotting functionality
----------------------------------

* :class:`.PlotNode` : The base class for a `.Node` with the purpose of receiving data for visualization.
* :class:`.PlotWidgetContainer` : A class that contains a `PlotWidget` (and can change it during runtime)
* :class:`.PlotWidget` : An abstract widget that can be inherited to implement actual plotting.
* :class:`.AutoFigureMaker` : A convenience class for semi-automatic generation of figures.
The purpose is to keep actual plotting code out of the plot widget. This is not mandatory, just convenient.

Data structures
---------------

* :class:`.PlotDataType` : Enum with types of data that can be plotted.
* :class:`.ComplexRepresentation`: Enum with ways to represent complex-valued data.


Additional tools
----------------

* :func:`.makeFlowchartWithPlot` : convenience function for creating a flowchart that leads to a plot node.
* :func:`.determinePlotDataType` : try to infer which type of plot data is in a data set.

Object Documentation
====================

Base elements
^^^^^^^^^^^^^
.. automodule:: plottr.plot.base
:members:

.. _MPL plot API:

Matplotlib plotting tools
^^^^^^^^^^^^^^^^^^^^^^^^^

Overview
========

.. automodule:: plottr.plot.mpl
:members:

Object Documentation
====================

General Widgets
---------------
.. automodule:: plottr.plot.mpl.widgets
:members:

General plotting tools
----------------------
.. automodule:: plottr.plot.mpl.plotting
:members:

Autoplot
--------
.. automodule:: plottr.plot.mpl.autoplot
:members:
2 changes: 1 addition & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# -- Project information -----------------------------------------------------

project = 'plottr'
copyright = '2020, Wolfgang Pfaff'
copyright = '2019-2021, Wolfgang Pfaff'
author = 'Wolfgang Pfaff'


Expand Down
7 changes: 6 additions & 1 deletion doc/plotnode.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ The reason why we don't connect the widget directly to the node is that the cont

.. image:: img/plot-node-system.png

See the :ref:`API documentation<Base plot API>` for more details.


Automatic plotting with Matplotlib
----------------------------------
The most commonly used plot widget is based on matplotlib: :class:`AutoPlot <plottr.plot.mpl.AutoPlot>`.
The most commonly used plot widget is based on matplotlib: :class:`AutoPlot <plottr.plot.mpl.autoplot.AutoPlot>`.
It determines automatically what an appropriate visualization of the received data is, and then plots that (at least if it can determine a good way to plot).
At the same time it gives the user a little bit of control over the appearance (partially through native matplotlib tools).
To separate plotting from the GUI elements we use :class:`FigureMaker <plottr.plot.mpl.autoplot.FigureMaker>`.

See the :ref:`API documentation<MPL plot API>` for more details.
96 changes: 90 additions & 6 deletions plottr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, List, Tuple, Dict, Any, Optional
import importlib
import os
import logging
import sys

if TYPE_CHECKING:
from PyQt5 import QtCore, QtGui, QtWidgets
Expand All @@ -15,11 +16,94 @@
Flowchart = pgFlowchart
NodeBase = pgNode

plottrPath = os.path.split(os.path.abspath(__file__))[0]

from ._version import get_versions
__version__ = get_versions()['version']
del get_versions

logger = logging.getLogger(__name__)
logger.info(f"Imported plottr version: {__version__}")

plottrPath = os.path.split(os.path.abspath(__file__))[0]


def configPaths() -> Tuple[str, str, str]:
"""Get the folders where plottr looks for config files.

:return: List of absolute paths, in order of priority:
(1) current working directory
(2) ~/.plottr
(3) config directory in the package.
"""
builtIn = os.path.join(plottrPath, 'config')
user = os.path.join(os.path.expanduser("~"), '.plottr')
cwd = os.getcwd()
return cwd, user, builtIn


def configFiles(fileName: str) -> List[str]:
"""Get available config files with the given file name.

:param fileName: file name, without path
:return: List of found config files with the provided name, in order
or priority.
"""
ret = []
for path in configPaths():
fp = os.path.join(path, fileName)
if os.path.exists(fp):
ret.append(fp)
return ret


def config(names: Optional[List[str]] = None, forceReload: bool = True) -> \
Dict[str, Any]:
"""Return the plottr configuration as a dictionary.

Each config file found is expected to contain a dictionary with name
``config``. The returned configuration is of the form
``
{
cfg_1: {...},
cfg_2: {...},
}
``
The keys in the returned dictionary are the names given, and the contents
of each entry the dictionary found in the corresponding files.

Values returned are determined in hierarchical order:
If configs are found on package and user levels, we first look at the
package-provided config, and then update with user-provided ones (see doc
of :func:`.configPaths`). I.e., user-provided config has the highest
priority and overrides package-provided config.

Note: currently, exceptions raised when trying to import config objects are
not caught. Erroneous config files may thus crash the program.

:param names: List of files. For given ``name`` will look
for ``plottrcfg_<name>.py`` in the config directories.
if ``None``, will look only for ``plottrcfg_main.py``
:param forceReload: If True, will not use a cached config file if present.
will thus get the most recent config from file, without need to restart
the program.
"""
if names is None:
names = ['main']

config = {}
for name in names:
modn = f"plottrcfg_{name}"
filen = f"{modn}.py"
this_cfg = {}
for filep in configFiles(filen)[::-1]:
path = os.path.split(filep)[0]
sys.path.insert(0, path)
mod = importlib.import_module(modn)
if forceReload:
importlib.reload(mod)
this_cfg.update(getattr(mod, 'config', {}))
sys.path.pop(0)

config[name] = this_cfg
return config




6 changes: 1 addition & 5 deletions plottr/apps/autoplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,6 @@ def stop(self) -> None:

class AutoPlotMainWindow(PlotWindow):

#: Signal() -- emitted when the window is closed
windowClosed = Signal()

def __init__(self, fc: Flowchart,
parent: Optional[QtWidgets.QMainWindow] = None,
monitor: bool = False,
Expand Down Expand Up @@ -185,8 +182,7 @@ def closeEvent(self, event: QtGui.QCloseEvent) -> None:
"""
if self.monitorToolBar is not None:
self.monitorToolBar.stop()
self.windowClosed.emit()
return event.accept()
return super().closeEvent(event)

def showTime(self) -> None:
"""
Expand Down
29 changes: 29 additions & 0 deletions plottr/config/plottrcfg_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from matplotlib import cycler

config = {
'matplotlibrc': {
'axes.grid': True,
'axes.prop_cycle': cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b',
'e377c2', '7f7f7f', 'bcbd22', '17becf']),
'figure.dpi': 150,
'figure.figsize': (4.5, 3),
'font.size': 6,
'font.family': ['Helvetica', 'Arial', 'DejaVu Sans', 'Bitstream Vera Sans'],
'grid.linewidth': 0.5,
'grid.linestyle': '--',
'image.cmap': 'magma',
'legend.fontsize': 6,
'legend.frameon': True,
'legend.numpoints': 1,
'legend.scatterpoints': 1,
'lines.marker': 'o',
'lines.markersize': '3',
'lines.markeredgewidth': 1,
'lines.markerfacecolor': 'w',
'lines.linestyle': '-',
'lines.linewidth': 1,
'savefig.dpi': 300,
'savefig.transparent': False,
},

}
9 changes: 9 additions & 0 deletions plottr/gui/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@
"""
from typing import List, Dict, Union

from numpy import rint

from .. import QtWidgets

__author__ = 'Wolfgang Pfaff'
__license__ = 'MIT'


def dpiScalingFactor(widget: QtWidgets.QWidget) -> float:
"""based on the logical DPI of ``widget``, return a scaling factor
that can be used to compute display size on screens relative to 96 DPI."""
scaling = rint(widget.logicalDpiX() / 96.0)
return scaling


def widgetDialog(widget: QtWidgets.QWidget, title: str = '',
show: bool = True) -> QtWidgets.QDialog:
win = QtWidgets.QDialog()
Expand Down
32 changes: 23 additions & 9 deletions plottr/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
from numpy import rint
from typing import Union, List, Tuple, Optional, Type, Sequence, Dict, Any

from .tools import dictToTreeWidgetItems
from plottr import QtCore, Flowchart, QtWidgets, Signal, Slot
from .tools import dictToTreeWidgetItems, dpiScalingFactor
from plottr import QtGui, QtCore, Flowchart, QtWidgets, Signal, Slot
from plottr.node import Node, linearFlowchart
from ..plot import PlotNode, PlotWidgetContainer, MPLAutoPlot
from ..plot import PlotNode, PlotWidgetContainer, PlotWidget

__author__ = 'Wolfgang Pfaff'
__license__ = 'MIT'
Expand Down Expand Up @@ -74,15 +74,23 @@ class PlotWindow(QtWidgets.QMainWindow):
:meth:`addNodeWidgetFromFlowchart`.
"""

plotWidgetClass = MPLAutoPlot
#: Signal() -- emitted when the window is closed
windowClosed = Signal()

def __init__(self, parent: Optional[QtWidgets.QMainWindow] = None,
fc: Optional[Flowchart] = None, **kw: Any):
fc: Optional[Flowchart] = None,
plotWidgetClass: Optional[Any] = None,
**kw: Any):
super().__init__(parent)

if plotWidgetClass is None:
from ..plot.mpl import AutoPlot
plotWidgetClass = AutoPlot

self.plotWidgetClass = plotWidgetClass
self.plot = PlotWidgetContainer(parent=self)
self.setCentralWidget(self.plot)
self.plotWidget: Optional[MPLAutoPlot] = None
self.plotWidget: Optional[PlotWidget] = None

self.nodeToolBar = QtWidgets.QToolBar('Node control', self)
self.addToolBar(self.nodeToolBar)
Expand All @@ -94,8 +102,7 @@ def __init__(self, parent: Optional[QtWidgets.QMainWindow] = None,
self.setDefaultStyle()

def setDefaultStyle(self) -> None:
scaling = rint(self.logicalDpiX() / 96.0)
fontSize = 10*scaling
fontSize = 10*dpiScalingFactor(self)
self.setStyleSheet(
f"""
QToolButton {{
Expand Down Expand Up @@ -180,6 +187,14 @@ def addNodeWidgetsFromFlowchart(self, fc: Flowchart,
self.plotWidget = self.plotWidgetClass(parent=self.plot)
self.plot.setPlotWidget(self.plotWidget)

def closeEvent(self, event: QtGui.QCloseEvent) -> None:
"""
When closing the inspectr window, do some house keeping:
* stop the monitor, if running
"""
self.windowClosed.emit()
return event.accept()


def makeFlowchartWithPlotWindow(nodes: List[Tuple[str, Type[Node]]], **kwargs: Any) \
-> Tuple[PlotWindow, Flowchart]:
Expand Down Expand Up @@ -211,7 +226,6 @@ def loadSnapshot(self, snapshotDict : Optional[dict]) -> None:
self.addTopLevelItem(item)
item.setExpanded(True)

#self.expandAll()
for i in range(2):
self.resizeColumnToContents(i)

4 changes: 2 additions & 2 deletions plottr/plot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .base import PlotNode, PlotWidgetContainer, makeFlowchartWithPlot
from .mpl import AutoPlot as MPLAutoPlot
from .base import PlotNode, PlotWidgetContainer, makeFlowchartWithPlot, \
PlotWidget
Loading