import matplotlib.pyplot as plt import numpy as np import os def plot_xy(x, y, xlabel="x", ylabel="y", title="Plot of y over x"): """ Plots y over x using matplotlib with: - X limits set exactly to the first and last x values - Y limits padded by ±10% of the data range - Custom x/y labels and title """ if len(x) == 0 or len(y) == 0: raise ValueError("x and y must not be empty") if len(x) != len(y): raise ValueError("x and y must have the same length") plt.figure(figsize=(8, 5)) plt.plot(x, y) # Set x-axis exactly to first and last value plt.xlim(x[0], x[-1]) # Compute y padding ymin, ymax = min(y), max(y) yrange = ymax - ymin pad = yrange * 0.10 if yrange != 0 else 1 # avoid zero-range issue plt.ylim(ymin - pad, ymax + pad) # Labels and title plt.xlabel(xlabel) plt.ylabel(ylabel) plt.title(title) plt.grid(True) plt.show() def plot_xy_points(x, y, xlabel="x", ylabel="y", title="Plot of y over x"): """ Plots y over x using matplotlib with: - A line plot + dots on each measurement point - X limits set exactly to the first and last x values - Y limits padded by ±10% of the data range - Custom x/y labels and title """ if len(x) == 0 or len(y) == 0: raise ValueError("x and y must not be empty") if len(x) != len(y): raise ValueError("x and y must have the same length") plt.figure(figsize=(8, 5)) # Plot line plt.plot(x, y) # Plot dots at measurement values plt.scatter(x, y) # Set x limits exactly to first and last value plt.xlim(x[0], x[-1]) # Compute y padding ymin, ymax = min(y), max(y) yrange = ymax - ymin pad = yrange * 0.10 if yrange != 0 else 1 # avoid zero-range issue plt.ylim(ymin - pad, ymax + pad) # Labels and title plt.xlabel(xlabel) plt.ylabel(ylabel) plt.title(title) plt.grid(True) plt.show() def plot_mult_xy(x_list, y_list, legends, xlabel="x", ylabel="y", title="Plot"): """ Plots multiple y-over-x curves using matplotlib. Parameters: x_list : list of x arrays y_list : list of y arrays legends : list of legend labels xlabel : x axis label ylabel : y axis label title : plot title """ if len(x_list) != len(y_list): raise ValueError("x_list and y_list must have the same number of elements") if len(legends) != len(x_list): raise ValueError("Number of legends must match number of curves") plt.figure(figsize=(8, 5)) # Track global y range all_y = [] for x, y, label in zip(x_list, y_list, legends): if len(x) != len(y): raise ValueError(f"Length mismatch in dataset '{label}'") plt.plot(x, y, label=label) all_y.extend(y) # Global y-axis padding ymin, ymax = min(all_y), max(all_y) yrange = ymax - ymin pad = yrange * 0.10 if yrange != 0 else 1 plt.ylim(ymin - pad, ymax + pad) # Global x-axis: from smallest first x to largest last x x_start = min(x[0] for x in x_list) x_end = max(x[-1] for x in x_list) plt.xlim(x_start, x_end) # Labels, grid, legend plt.xlabel(xlabel) plt.ylabel(ylabel) plt.title(title) plt.grid(True) plt.legend() plt.show() def plot_mult_xy_points(x_list, y_list, legends, xlabel="x", ylabel="y", title="Plot"): """ Same as plot_xy, but also places dots at each measured point. """ if len(x_list) != len(y_list): raise ValueError("x_list and y_list must have the same number of elements") if len(legends) != len(x_list): raise ValueError("Number of legends must match number of curves") plt.figure(figsize=(8, 5)) all_y = [] for x, y, label in zip(x_list, y_list, legends): if len(x) != len(y): raise ValueError(f"Length mismatch in dataset '{label}'") # Line + dots plt.plot(x, y, label=label) plt.scatter(x, y) all_y.extend(y) # Y-axis padding ymin, ymax = min(all_y), max(all_y) yrange = ymax - ymin pad = yrange * 0.10 if yrange != 0 else 1 plt.ylim(ymin - pad, ymax + pad) # X-axis range x_start = min(x[0] for x in x_list) x_end = max(x[-1] for x in x_list) plt.xlim(x_start, x_end) plt.xlabel(xlabel) plt.ylabel(ylabel) plt.title(title) plt.grid(True) plt.legend() plt.show() def plot_shared_xy( x, y_list, labels=None, xlabel="X", ylabel="Y", title="Plot", show_points=False, figsize=(10,6), linewidth=2, marker_size=60, log_x=False, log_y=False, save_path=None # <-- NEW ): """ Plot multiple y-arrays over the same x-array using shared axes. Supports optional logarithmic axes, padding, and SVG saving. """ x = np.asarray(x) # Validate y lengths for idx, y in enumerate(y_list): if len(y) != len(x): raise ValueError(f"y_list[{idx}] does not match length of x") plt.figure(figsize=figsize) # Plot each curve for idx, y in enumerate(y_list): y = np.asarray(y) label = labels[idx] if labels else None plt.plot(x, y, linewidth=linewidth, label=label) if show_points: plt.scatter(x, y, s=marker_size) # X-axis range plt.xlim(x[0], x[-1]) # Y-axis padding all_y = np.concatenate([np.asarray(y) for y in y_list]) ymin, ymax = np.min(all_y), np.max(all_y) yrange = ymax - ymin pad = yrange * 0.10 if yrange != 0 else 1 plt.ylim(ymin - pad, ymax + pad) # Axis labels plt.xlabel(xlabel) plt.ylabel(ylabel) # Optional log scaling if log_x: plt.xscale("log") if log_y: plt.yscale("log") # Title / legend / grid plt.title(title) if labels: plt.legend() plt.grid(True, which="both") plt.tight_layout() # === SVG Saving Logic =============================== if save_path is not None and str(save_path).strip() != "": save_path = str(save_path) # Ensure .svg extension if not save_path.lower().endswith(".svg"): save_path += ".svg" # If file exists, ask for overwrite or rename if os.path.exists(save_path): print(f"File '{save_path}' already exists.") choice = input("Overwrite? (y/n): ").strip().lower() if choice != "y": new_path = input("Enter new filename (with or without .svg): ").strip() if not new_path.lower().endswith(".svg"): new_path += ".svg" save_path = new_path # Save the file now plt.savefig(save_path, format="svg") print(f"Saved SVG to: {save_path}") # ===================================================== plt.show() def plot_difference( x, y1, y2, absolute=False, return_sum=False, # Return sum instead of array xlabel="X", ylabel="Difference", title="Difference Plot", show_points=False, figsize=(10,6), linewidth=2, marker_size=60, log_x=False, log_y=False, save_path=None ): """ Plot the difference between two y-series over the same x. Optionally compute absolute difference and optionally return the sum of the difference values instead of the full array. NOTE: This version intentionally provides NO LEGEND. """ # Convert to arrays x = np.asarray(x) y1 = np.asarray(y1) y2 = np.asarray(y2) # Validate size if len(y1) != len(x) or len(y2) != len(x): raise ValueError("y1 and y2 must match the length of x") # Compute difference diff = y2 - y1 if absolute: diff = np.abs(diff) # === Plotting === plt.figure(figsize=figsize) plt.plot( x, diff, linewidth=linewidth ) if show_points: plt.scatter(x, diff, s=marker_size) # X limits plt.xlim(x[0], x[-1]) # Y padding ymin, ymax = np.min(diff), np.max(diff) yrange = ymax - ymin pad = yrange * 0.10 if yrange != 0 else 1 plt.ylim(ymin - pad, ymax + pad) # Labels & styling plt.xlabel(xlabel) plt.ylabel(ylabel) plt.title(title) plt.grid(True, which="both") # Apply log scales if log_x: plt.xscale("log") if log_y: plt.yscale("log") plt.tight_layout() # === SVG Saving Logic === if save_path is not None and str(save_path).strip() != "": save_path = str(save_path) if not save_path.lower().endswith(".svg"): save_path += ".svg" if os.path.exists(save_path): print(f"File '{save_path}' already exists.") choice = input("Overwrite? (y/n): ").strip().lower() if choice != "y": new_path = input( "Enter new filename (with or without .svg): " ).strip() if not new_path.lower().endswith(".svg"): new_path += ".svg" save_path = new_path plt.savefig(save_path, format="svg") print(f"Saved SVG to: {save_path}") # =========================== plt.show() # === Return requested output === if return_sum: return np.sum(diff) else: return diff if __name__ == "__main__": # Example usage # x = [1, 2, 3, 4, 5] # y = [2, 3, 5, 7, 11] # plot_xy_points(x, y, "X-axis", "Y-axis", "Sample Plot") # x1 = [0, 1, 2, 3] # y1 = [2, 5, 3, 6] # x2 = [0, 1, 2, 3] # y2 = [1, 4, 2, 7] # plot_mult_xy_points( # [x1, x2], # [y1, y2], # legends=["Sensor A", "Sensor B"], # xlabel="Time (s)", # ylabel="Value", # title="Line Plot" # ) x = [0, 1, 2, 3] y1 = [10, 20, 18, 30] y2 = [12, 22, 19, 29] y3 = [8, 18, 17, 27] plot_shared_xy( x=[0,1,2,3], y_list=[[10,20,18,30], [12,21,17,28]], labels=["A","B"], show_points=True, save_path="my_plot.svg" )