Plot generation and styling utilities.
The plot_utils.py module provides functions to create and save plots with experimental data and fitted curves. It handles:
- 2D fit plots: Data with error bars and fitted curve (
create_plot). Whenfit_infois provided, the curve is evaluated on a dense linspace over the full x range for a smooth appearance. - Pair plots: Grid of scatter plots for all pairs of variables (
create_pair_plots) - Residual plots: Residuals vs point index for multidimensional fits (
create_residual_plot) - 3D plots: Data points and fitted surface for two independent variables (
create_3d_plot). Whenfit_infois provided, the surface is evaluated on a regular grid of linspace points over the full x_0 and x_1 range for a smooth appearance.
All save functions use shared logic: the output directory is created if needed, and when saving as PDF a PNG preview is automatically generated for GUI use. Styling is driven by config.PLOT_CONFIG and config.FONT_CONFIG when not overridden.
create_plot(x, y, ux, uy, y_fitted, fit_name, x_name, y_name, plot_config=None, font_config=None, output_path=None, fit_info=None) -> str
Create and save a plot with experimental data and fitted curve.
Parameters:
x: Independent variable data (array-like)y: Dependent variable data (array-like)ux: Uncertainties in x (array-like)uy: Uncertainties in y (array-like)y_fitted: Fitted y values (array-like)fit_name: Name of the fit for plot titlex_name: Label for x-axisy_name: Label for y-axisplot_config: Optional plot configuration dict (defaults toPLOT_CONFIG)font_config: Optional font configuration dict (defaults toFONT_CONFIG)output_path: Optional full path to save the plot. If None, usesget_output_path(fit_name).fit_info: Optional dict withfit_funcandparamsto evaluate the fitted function on a dense linspace (300 points) over the full x range. When provided, yields a smoother curve; if None, plots at the original x points.
Returns:
- Path to the saved plot file (as string)
Raises:
Exception: If plot creation or saving fails
Example:
from plotting.plot_utils import create_plot
import numpy as np
import pandas as pd
# Prepare data
data = pd.DataFrame({
'x': [1, 2, 3, 4, 5],
'y': [2.1, 4.2, 6.1, 8.0, 10.1],
'ux': [0.1] * 5,
'uy': [0.2] * 5
})
# Fitted values
y_fitted = np.array([2, 4, 6, 8, 10])
# Create plot
plot_path = create_plot(
x=data['x'],
y=data['y'],
ux=data['ux'],
uy=data['uy'],
y_fitted=y_fitted,
fit_name='Linear Fit',
x_name='Time (s)',
y_name='Distance (m)'
)
print(f"Plot saved to: {plot_path}")create_pair_plots(data, variable_names, plot_config=None, font_config=None, output_path=None) -> str | Figure
Create a grid of scatter plots for all pairs of variables (pair plot / scatter matrix). Each cell has a minimum size (1.4 in) so many variables remain readable. The Streamlit and Tkinter UIs limit to 10 variables when there are more, via a multiselect or selection dialog.
Parameters:
data: DataFrame or dict-like with numeric columnsvariable_names: List of column names to use (must exist and be numeric)plot_config: Optional; defaults toPLOT_CONFIGfont_config: Optional; defaults toFONT_CONFIGoutput_path: If given, save figure and return path (str). IfNone, return the matplotlib Figure for inline display (e.g. Streamlit)
Returns: Path to the saved image (str) if output_path was set; otherwise the Figure instance.
create_residual_plot(residuals, point_indices, fit_name, plot_config=None, font_config=None, output_path=None) -> str
Create a residual plot for multidimensional fitting (residuals vs point index).
Parameters: residuals, point_indices, fit_name, optional plot_config, font_config, output_path (if None, uses get_output_path(fit_name)).
Returns: Path to the saved plot file (str).
create_3d_plot(x, y, z, z_fitted, fit_name, x_name, y_name, z_name, plot_config=None, font_config=None, output_path=None, interactive=False, fit_info=None) -> str | tuple[str, Figure]
Create a 3D plot with data points and fitted surface mesh for two independent variables.
Parameters: x, y, z, z_fitted, fit_name, x_name, y_name, z_name, optional config and path. If interactive=True, returns (save_path, figure) for embedding in a window (e.g. rotatable with mouse) instead of saving and closing. fit_info: Optional dict with fit_func and params to evaluate the fitted function on a regular grid (50×50) of linspace points over the full x_0 and x_1 range; when provided, yields a smoother surface.
Returns: Path (str) when interactive=False; (save_path, figure) when interactive=True.
Note: When fit_info is provided, the surface is evaluated on the grid. Otherwise, scipy.interpolate.griddata is used when scipy is available; a vectorized nearest-neighbor fallback is used without scipy.
- Line: Smooth curve showing the fitted function. When
fit_infois provided, the curve is evaluated on 300 linspace points over the full x range; otherwise it is drawn at the original data points. - Color: Configurable via
plot_config['line_color'] - Width: Configurable via
plot_config['line_width'] - Style: Configurable via
plot_config['line_style']
- Markers: Data points with error bars
- Format: Configurable via
plot_config['marker_format'] - Size: Configurable via
plot_config['marker_size'] - Colors:
- Face:
plot_config['marker_face_color'] - Edge:
plot_config['marker_edge_color'] - Error bars:
plot_config['error_color']
- Face:
- X-axis: Label from
x_nameparameter - Y-axis: Label from
y_nameparameter - Title: Optional, from
fit_nameifplot_config['show_title']is True - Grid: Optional, from
plot_config['show_grid'](default False) - Fonts: Configured via
font_config
Plot settings can be customized via plot_config dictionary:
plot_config = {
'figsize': (12, 6), # Figure size (width, height)
'dpi': 100, # Resolution
'line_color': 'black', # Fitted curve color
'line_width': 2, # Fitted curve width
'line_style': '-', # Line style ('-', '--', ':', etc.)
'marker_format': 'o', # Marker style ('o', 's', '^', etc.)
'marker_size': 8, # Marker size
'marker_face_color': 'blue', # Marker fill color
'marker_edge_color': 'black', # Marker edge color
'error_color': 'red', # Error bar color
'show_title': False, # Show plot title
'show_grid': False # Show background grid on plot
}Font settings can be customized via font_config dictionary:
font_config = {
'tick_size': 12, # Tick label size
'label_size': 14, # Axis label size
'title_size': 16 # Title size
}If plot_config or font_config are None, the function uses default values from config.PLOT_CONFIG and config.FONT_CONFIG:
from plotting.plot_utils import create_plot
# Uses default configuration
plot_path = create_plot(
x=x_data, y=y_data, ux=ux_data, uy=uy_data,
y_fitted=y_fitted, fit_name='Fit', x_name='x', y_name='y'
)from plotting.plot_utils import create_plot
# Custom plot configuration
custom_plot_config = {
'figsize': (10, 8),
'dpi': 150,
'line_color': 'red',
'marker_format': 's',
'show_title': True
}
custom_font_config = {
'tick_size': 14,
'label_size': 16
}
plot_path = create_plot(
x=x_data, y=y_data, ux=ux_data, uy=uy_data,
y_fitted=y_fitted, fit_name='Custom Fit',
x_name='X Variable', y_name='Y Variable',
plot_config=custom_plot_config,
font_config=custom_font_config
)If output_path is None, the function automatically generates a path using config.get_output_path(fit_name):
# Automatically saves to: output/fit_name.png
plot_path = create_plot(..., fit_name='my_fit', ...)from pathlib import Path
# Custom output path
custom_path = Path('results') / 'experiment1.png'
plot_path = create_plot(
...,
output_path=str(custom_path)
)from plotting.plot_utils import create_plot
import numpy as np
import pandas as pd
# Load data
data = pd.read_csv('input/experiment.csv')
# Perform fit (example)
y_fitted = 2 * data['x'] # Simple linear fit
# Create plot
plot_path = create_plot(
x=data['x'],
y=data['y'],
ux=data['ux'],
uy=data['uy'],
y_fitted=y_fitted,
fit_name='Linear Fit',
x_name='Time (s)',
y_name='Distance (m)'
)from plotting.plot_utils import create_plot
high_res_config = {
'figsize': (16, 10),
'dpi': 300, # High resolution
'line_width': 3,
'marker_size': 10
}
plot_path = create_plot(
x=x_data, y=y_data, ux=ux_data, uy=uy_data,
y_fitted=y_fitted, fit_name='Publication Quality',
x_name='X', y_name='Y',
plot_config=high_res_config
)from plotting.plot_utils import create_plot
# Custom colors and styles
custom_config = {
'line_color': '#2E86AB', # Blue
'line_width': 2.5,
'line_style': '--', # Dashed line
'marker_format': 'o',
'marker_size': 10,
'marker_face_color': '#A23B72', # Purple
'marker_edge_color': 'black',
'error_color': '#F18F01', # Orange
'show_title': True
}
plot_path = create_plot(
x=x_data, y=y_data, ux=ux_data, uy=uy_data,
y_fitted=y_fitted, fit_name='Custom Styled Fit',
x_name='X Variable', y_name='Y Variable',
plot_config=custom_config
)The plot utility is typically used after performing a fit:
from fitting.fitting_utils import get_fitting_function
from plotting.plot_utils import create_plot
# Get fitting function
fit_func = get_fitting_function('linear_function')
# Perform fit (returns text, y_fitted, equation, fit_info)
result = fit_func(data, 'x', 'y')
text, y_fitted, equation = result[0], result[1], result[2]
fit_info = result[3] if len(result) >= 4 else None
# Create plot (fit_info enables smooth curve via linspace evaluation)
plot_path = create_plot(
x=data['x'],
y=data['y'],
ux=data['ux'],
uy=data['uy'],
y_fitted=y_fitted,
fit_name='Linear Fit',
x_name='X', y_name='Y',
fit_info=fit_info,
)from plotting.plot_utils import create_plot
try:
plot_path = create_plot(
x=x_data, y=y_data, ux=ux_data, uy=uy_data,
y_fitted=y_fitted, fit_name='Fit',
x_name='X', y_name='Y'
)
except Exception as e:
print(f"Failed to create plot: {e}")
# Cleanup any open figures
import matplotlib.pyplot as plt
plt.close('all')-
Consistent Styling: Use the same
plot_configacross multiple plots# Define once PLOT_STYLE = { 'figsize': (12, 6), 'dpi': 100, # ... other settings } # Reuse plot1 = create_plot(..., plot_config=PLOT_STYLE) plot2 = create_plot(..., plot_config=PLOT_STYLE)
-
Error Bar Handling: Always provide uncertainty arrays (use zeros if unavailable)
# If uncertainties not available ux = [0.0] * len(x) uy = [0.0] * len(y)
-
Output Directory: The plotting functions create the output directory automatically when saving; you do not need to create it beforehand.
-
Resource Cleanup: Figures are closed automatically after saving. Only call
plt.close('all')when handling errors (e.g. in an except block) to clean up any open figures.
- Format: Save format is determined by the file extension in
output_path(e.g. PNG, PDF). - PDF preview: When the output path has a
.pdfextension, a PNG preview file (*_preview.png) is automatically written in the same directory for use in GUIs. - Directory creation: The parent directory of the save path is created automatically (
parents=True,exist_ok=True). - Shared logic: All plot-saving functions use an internal helper so that save behavior and preview generation are consistent.
- Uses
matplotlib.pyplotfor plotting - Creates figure with specified size and DPI from
plot_config - Saves with
bbox_inches='tight'and configured DPI - Automatically closes figures after saving (unless returning the figure for interactive use, e.g. pair plot without path or 3D with
interactive=True)
- Uses
config.setup_fonts()for font configuration - Supports custom font properties via
font_config - Handles font fallbacks gracefully
- With fit_info: The 3D fitted surface is evaluated on a 50×50 grid of linspace points over the full x_0 and x_1 range, yielding a smooth surface.
- Without fit_info: With scipy, 3D fitted surface uses
scipy.interpolate.griddata(linear interpolation, with nearest-neighbor fill outside the convex hull). - Without scipy: A vectorized nearest-neighbor interpolation over the grid is used so 3D plots still work without scipy.
- Single figure creation per call
- Efficient memory usage
- Fast rendering for typical data sizes (< 10,000 points)
- Pair plot and 3D fallback use vectorized operations where applicable
For more information about plotting, see Usage Guide.