Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions extrap/gui/AdvancedPlotWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ def drawGraph(self):
self.toolbar = MyCustomToolbar(self.graphDisplayWindow, self)
self.grid.addWidget(self.graphDisplayWindow)
self.grid.addWidget(self.toolbar)
controls_widget = self.graphDisplayWindow.get_controls_widget()
if controls_widget is not None:
self.grid.addWidget(controls_widget)
else:
self.graphDisplayWindow.redraw()

Expand Down
9 changes: 6 additions & 3 deletions extrap/gui/DataDisplay.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from extrap.gui.plots.IsolinesDisplayWidget import IsolinesDisplay
from extrap.gui.plots.MaxZAsSingleSurfacePlotWidget import MaxZAsSingleSurfacePlot
from extrap.gui.plots.MeasurementPointsPlotWidget import MeasurementPointsPlot
from extrap.gui.plots.MultiParameterMeasurementPointsPlotWidget import MultiParameterMeasurementPointsPlot

MIN_PARAM_VALUE = 0.01
MAX_PARAM_VALUE = float("inf")
Expand Down Expand Up @@ -312,6 +313,7 @@ def reloadTabs(self, selectedCheckBoxesIndex):
# 6: IsolinesDisplayWidget
# 7: InterpolatedContourDisplayWidget
# 8: Measurement Points
# 9: MultiParameterMeasurementPointsPlotWidget
if 0 in selectedCheckBoxesIndex:
labelText = "Line graph"
tabStatus = self.is_tab_already_opened(labelText)
Expand All @@ -327,7 +329,8 @@ def reloadTabs(self, selectedCheckBoxesIndex):
5: ("Heat map", HeatMapGraph),
6: ("Contour plot", IsolinesDisplay),
7: ("Interpolated contour", InterpolatedContourDisplay),
8: ("Measurement points", MeasurementPointsPlot)
8: ("Measurement points", MeasurementPointsPlot),
9: ("Measurement points (3 parameters)", MultiParameterMeasurementPointsPlot)
}

for i in selectedCheckBoxesIndex:
Expand Down Expand Up @@ -373,10 +376,10 @@ def parameterSelected(self, index, newName, oldName):
old_value = self.axis_selections[index].getValue()
for i in self.axis_selections:
if i.index != index:
if i.getParameter().name == newName:
if i.getParameter().name == newName.name:
self.setMaxValue(index, i.getValue())
self.setMaxValue(i.index, old_value)
i.switchParameter(oldName)
i.switchParameter(oldName.name)
i.maxChanged()
self.axis_selections[index].maxChanged()
self.updateWidget()
Expand Down
2 changes: 1 addition & 1 deletion extrap/gui/MainWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def _init_ui(self):
'Dominating models in a 3D S&catter plot',
'Max &z as a single surface plot', 'Dominating models and max z as &heat map',
'Selected models in c&ontour plot', 'Selected models in &interpolated contour plots',
'&Measurement points']
'&Measurement points', 'Measurement points (&3 parameters)']
graph_actions = [QAction(g, self) for g in graphs]
for i, g in enumerate(graph_actions):
slot = (lambda k: lambda: self.data_display.reloadTabs((k,)))(i)
Expand Down
3 changes: 2 additions & 1 deletion extrap/gui/PlotTypeSelector.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ def init_UI(self):
plotTypes = ['Line graph', 'Selected models in same surface plot', 'Selected models in different surface plots',
'Dominating models in a 3D Scatter plot', 'Max z as a single surface plot',
'Dominating models and max z as heat map', 'Selected models in contour plot',
'Selected models in interpolated contour plots', 'Measurement points']
'Selected models in interpolated contour plots', 'Measurement points',
'Measurement points (3 parameters)']

layout = QVBoxLayout()

Expand Down
23 changes: 22 additions & 1 deletion extrap/gui/components/plot_formatting_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

from PySide6.QtCore import Qt
from PySide6.QtGui import QFont
from PySide6.QtWidgets import QDialog, QFormLayout, QFontComboBox, QSpinBox, QDialogButtonBox, QLayout, QComboBox
from PySide6.QtWidgets import QDialog, QFormLayout, QFontComboBox, QSpinBox, QDialogButtonBox, QLayout, QComboBox, \
QCheckBox, QWidget, QHBoxLayout

from extrap.gui.components.model_color_map import ModelColorMap

Expand All @@ -19,6 +20,9 @@ class PlotFormattingOptions:
font_family: str = 'Arial'
font_size: int = 10
legend_font_size: int = 6
log_x: bool = False
log_y: bool = False
log_z: bool = False


class PlotFormattingDialog(QDialog):
Expand Down Expand Up @@ -52,6 +56,20 @@ def __init__(self, options: PlotFormattingOptions, parent=None, f=..., model_col
self._colormap_selector.setInsertPolicy(QComboBox.InsertPolicy.NoInsert)
layout.addRow("Colormap", self._colormap_selector)

log_axes_widget = QWidget()
log_axes_layout = QHBoxLayout(log_axes_widget)
log_axes_layout.setContentsMargins(0, 0, 0, 0)
self._log_x_selector = QCheckBox("X")
self._log_x_selector.setChecked(self._options.log_x)
self._log_y_selector = QCheckBox("Y")
self._log_y_selector.setChecked(self._options.log_y)
self._log_z_selector = QCheckBox("Z")
self._log_z_selector.setChecked(self._options.log_z)
log_axes_layout.addWidget(self._log_x_selector)
log_axes_layout.addWidget(self._log_y_selector)
log_axes_layout.addWidget(self._log_z_selector)
layout.addRow("Logarithmic axes", log_axes_widget)

_dialog_buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
_dialog_buttons.accepted.connect(self.accept)
_dialog_buttons.rejected.connect(self.reject)
Expand All @@ -64,5 +82,8 @@ def accept(self) -> None:
self._options.font_size = self._font_size_selector.value()
self._options.legend_font_size = self._legend_font_size_selector.value()
self._model_color_map.set_colormap(self._colormap_selector.currentText())
self._options.log_x = self._log_x_selector.isChecked()
self._options.log_y = self._log_y_selector.isChecked()
self._options.log_z = self._log_z_selector.isChecked()

super().accept()
59 changes: 58 additions & 1 deletion extrap/gui/plots/BaseGraphWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,67 @@ def __init__(self, graphWidget, main_widget: MainWidget, width=5, height=4, dpi=
QSizePolicy.Expanding)
super().updateGeometry()
self.draw_figure()
self._apply_log_axes()

def redraw(self):
with matplotlib.rc_context({'font.family': self.main_widget.plot_formatting_options.font_family,
'font.size': self.main_widget.plot_formatting_options.font_size}):
rotation = self._save_rotation()
self.fig.clear()
self.draw_figure()
self._apply_log_axes()
self._restore_rotation(rotation)
self.fig.canvas.draw_idle()

def _apply_log_axes(self):
""" Applies the logarithmic axis settings from the plot formatting options to every
plot Axes in the figure (skipping colorbars, which have no meaningful data scale).
"""
options = self.main_widget.plot_formatting_options
for ax in self.fig.axes:
if ax.get_label() == '<colorbar>':
continue
ax.set_xscale('log' if options.log_x else 'linear')
ax.set_yscale('log' if options.log_y else 'linear')
if hasattr(ax, 'set_zscale'):
ax.set_zscale('log' if options.log_z else 'linear')

def get_controls_widget(self):
""" Returns an optional widget with extra, plot-specific controls (e.g. filters)
to be shown alongside the plot, or None if the plot has none.
"""
return None

def redraw_preserving_limits(self):
""" Like redraw(), but keeps the current x/y/z axis limits (e.g. from a manual
zoom) instead of letting them auto-rescale to whatever is drawn this time.
Intended for redraws triggered by a pure display filter (show/hide something)
rather than by an actual change of the underlying data or its range.
"""
limits = self._save_axis_limits()
self.redraw()
self._restore_axis_limits(limits)
self.fig.canvas.draw_idle()

def _save_axis_limits(self):
limits = []
for ax in self.fig.axes:
if ax.get_label() == '<colorbar>':
limits.append(None)
continue
limits.append((ax.get_xlim(), ax.get_ylim(), ax.get_zlim() if hasattr(ax, 'get_zlim') else None))
return limits

def _restore_axis_limits(self, limits):
for ax, entry in zip(self.fig.axes, limits):
if entry is None:
continue
xlim, ylim, zlim = entry
ax.set_xlim(xlim)
ax.set_ylim(ylim)
if zlim is not None and hasattr(ax, 'set_zlim'):
ax.set_zlim(zlim)

def _save_rotation(self):
return [(ax.elev, ax.azim) if isinstance(ax, Axes3D) else (None, None) for ax in self.fig.axes]

Expand Down Expand Up @@ -74,16 +125,22 @@ def getPixelGap(lowerlimit, upperlimit, numberOfPixels):
pixelGap = (upperlimit - lowerlimit) / numberOfPixels
return pixelGap

def calculate_z_optimized(self, X, Y, function):
def calculate_z_optimized(self, X, Y, function, param_overrides=None):
"""
This function evaluates the function passed to it.
param_overrides can be used to override the fixed value of a non-axis
parameter (e.g. to evaluate the model at a specific value of a third
parameter instead of the one selected in the UI).
"""
xs, ys = X.reshape(-1), Y.reshape(-1)
points = np.ndarray((len(self.main_widget.data_display.parameters), len(xs)))

parameter_value_list = self.main_widget.data_display.getValues()
for p, v in parameter_value_list.items():
points[p] = v
if param_overrides:
for p, v in param_overrides.items():
points[p] = v
param1 = self.main_widget.data_display.getAxisParameter(0).id
param2 = self.main_widget.data_display.getAxisParameter(1).id
if param1 >= 0:
Expand Down
Loading