commit 831b6501111b01f58afcaafdafa9b5850700945c Author: anas-rashid Date: Fri Apr 3 20:02:07 2026 +0200 Initial commit: Quadratic Equation Plotter application Features: - Plot quadratic equations: y = ax² + bx + c - Adjustable X-axis range - Visualize vertex and roots - Save plots in multiple formats - Interactive matplotlib navigation toolbar - Clean GUI interface with Tkinter diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..867338d --- /dev/null +++ b/.gitignore @@ -0,0 +1,155 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# VS Code +.vscode/ +*.code-workspace + +# PyCharm +.idea/ +*.iml +*.iws + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Matplotlib +*.png +*.jpg +*.pdf +*.svg + +# Project specific (optional) +screenshots/ +output/ +test_output/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a85456 --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +# Quadratic Equation Plotter + +A Python GUI application for plotting quadratic equations using Tkinter and Matplotlib. + +## Features + +- Plot any quadratic equation in the form: **y = ax² + bx + c** +- Adjustable X-axis range +- Visualize vertex and roots (when they exist) +- Save plots in multiple formats (PNG, JPEG, PDF, SVG) +- Interactive matplotlib navigation toolbar (zoom, pan, save) +- Clean, user-friendly interface + +## Requirements + +- Python 3.6 or higher +- NumPy +- Matplotlib + +## Installation + +1. Install Python dependencies: + +```bash +pip install -r requirements.txt +``` + +Or install manually: + +```bash +pip install numpy matplotlib +``` + +## Usage + +Run the application: + +```bash +python quadratic_plotter.py +``` + +### Interface Components: + +1. **Coefficients Input**: + - Enter values for `a`, `b`, and `c` + - Default: a=1, b=0, c=0 (plots y = x²) + +2. **X-axis Range**: + - Set the range of x-values to plot + - Default: -10 to 10 + +3. **Buttons**: + - **Plot Quadratic**: Generate the plot with current parameters + - **Save Plot**: Save the current plot to a file + - **Clear Plot**: Clear the current plot + +4. **Plot Area**: + - Interactive matplotlib plot with zoom and pan capabilities + - Vertex shown as red dot (for a ≠ 0) + - Roots shown as green dots (when real roots exist) + - Legend showing equation and key points + +## Example Plots + +1. **Standard Parabola**: a=1, b=0, c=0 +2. **Shifted Parabola**: a=1, b=-2, c=1 +3. **Downward Parabola**: a=-1, b=0, c=4 +4. **Linear Function**: a=0, b=2, c=1 (plots a straight line) + +## Features in Detail + +### Vertex Calculation +The vertex of the parabola is calculated using: +``` +x_vertex = -b / (2a) +y_vertex = a(x_vertex)² + b(x_vertex) + c +``` + +### Roots Calculation +Real roots are calculated using the quadratic formula: +``` +x = [-b ± √(b² - 4ac)] / (2a) +``` + +### Plot Customization +- Blue line: Quadratic curve +- Red dot: Vertex of parabola +- Green dots: Real roots (x-intercepts) +- Grid lines for easy reference +- Axes lines through origin + +## Error Handling + +The application includes error handling for: +- Invalid numeric input +- X-min greater than X-max +- File save errors +- Missing dependencies + +## License + +This project is open source and available for educational and personal use. + +## Troubleshooting + +If you encounter issues: + +1. **Missing dependencies**: Ensure all packages are installed +2. **GUI not showing**: Check if Tkinter is installed (usually bundled with Python) +3. **Plot not updating**: Verify all input fields contain valid numbers +4. **ImportError: "NavigationToolbar2Tk" is not exported**: This application includes compatibility code for different matplotlib versions. If you see this error, update matplotlib to version 3.4 or later: + ```bash + pip install --upgrade matplotlib + ``` + The code will automatically try multiple import locations for backward compatibility. + +## Screenshot + +![Quadratic Plotter Screenshot](screenshot.png) + +*Note: Add your own screenshot after running the application* \ No newline at end of file diff --git a/quadratic_plotter.py b/quadratic_plotter.py new file mode 100644 index 0000000..a69a0d8 --- /dev/null +++ b/quadratic_plotter.py @@ -0,0 +1,273 @@ +import tkinter as tk +from tkinter import ttk, messagebox +import numpy as np +from matplotlib.figure import Figure +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg + +# Import NavigationToolbar2Tk with version compatibility +try: + # For matplotlib >= 3.4 + from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk # type: ignore +except ImportError: + try: + # For older matplotlib versions or alternative location + from matplotlib.backends._backend_tk import NavigationToolbar2Tk # type: ignore + except ImportError: + # Try another possible location + from matplotlib.backends.backend_tk import NavigationToolbar2Tk # type: ignore + + +class QuadraticPlotter: + def __init__(self): + self.root = tk.Tk() + self.root.title("Quadratic Equation Plotter") + self.root.geometry("900x700") + + # Variables for coefficients + self.a_var = tk.StringVar(value="1") + self.b_var = tk.StringVar(value="0") + self.c_var = tk.StringVar(value="0") + + # Variables for x-range + self.x_min_var = tk.StringVar(value="-10") + self.x_max_var = tk.StringVar(value="10") + + # Create the main window content + self.create_widgets() + + # Start the application loop + self.root.mainloop() + + def create_widgets(self): + # Create main frames + control_frame = tk.Frame(self.root, padx=10, pady=10) + control_frame.pack(side=tk.TOP, fill=tk.X) + + plot_frame = tk.Frame(self.root) + plot_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True, padx=10, pady=10) + + # Title + title_label = tk.Label(control_frame, text="Quadratic Equation Plotter: y = ax² + bx + c", + font=("Arial", 14, "bold")) + title_label.grid(row=0, column=0, columnspan=6, pady=(0, 15)) + + # Coefficients input + coeff_frame = tk.LabelFrame(control_frame, text="Coefficients", padx=10, pady=10) + coeff_frame.grid(row=1, column=0, columnspan=6, sticky="ew", pady=(0, 10)) + + # a coefficient + tk.Label(coeff_frame, text="a =").grid(row=0, column=0, padx=(0, 5)) + a_entry = tk.Entry(coeff_frame, textvariable=self.a_var, width=10) + a_entry.grid(row=0, column=1, padx=(0, 15)) + + # b coefficient + tk.Label(coeff_frame, text="b =").grid(row=0, column=2, padx=(0, 5)) + b_entry = tk.Entry(coeff_frame, textvariable=self.b_var, width=10) + b_entry.grid(row=0, column=3, padx=(0, 15)) + + # c coefficient + tk.Label(coeff_frame, text="c =").grid(row=0, column=4, padx=(0, 5)) + c_entry = tk.Entry(coeff_frame, textvariable=self.c_var, width=10) + c_entry.grid(row=0, column=5, padx=(0, 15)) + + # X-range input + range_frame = tk.LabelFrame(control_frame, text="X-axis Range", padx=10, pady=10) + range_frame.grid(row=2, column=0, columnspan=6, sticky="ew", pady=(0, 10)) + + tk.Label(range_frame, text="From:").grid(row=0, column=0, padx=(0, 5)) + xmin_entry = tk.Entry(range_frame, textvariable=self.x_min_var, width=10) + xmin_entry.grid(row=0, column=1, padx=(0, 15)) + + tk.Label(range_frame, text="To:").grid(row=0, column=2, padx=(0, 5)) + xmax_entry = tk.Entry(range_frame, textvariable=self.x_max_var, width=10) + xmax_entry.grid(row=0, column=3, padx=(0, 15)) + + # Buttons + button_frame = tk.Frame(control_frame) + button_frame.grid(row=3, column=0, columnspan=6, pady=(10, 0)) + + plot_button = tk.Button(button_frame, text="Plot Quadratic", command=self.plot_quadratic, + bg="lightblue", font=("Arial", 10, "bold"), padx=20, pady=5) + plot_button.pack(side=tk.LEFT, padx=(0, 10)) + + save_button = tk.Button(button_frame, text="Save Plot", command=self.save_plot, + bg="lightgreen", font=("Arial", 10, "bold"), padx=20, pady=5) + save_button.pack(side=tk.LEFT, padx=(0, 10)) + + clear_button = tk.Button(button_frame, text="Clear Plot", command=self.clear_plot, + bg="lightcoral", font=("Arial", 10, "bold"), padx=20, pady=5) + clear_button.pack(side=tk.LEFT) + + # Matplotlib figure and canvas + self.fig = Figure(figsize=(8, 6), dpi=100) + self.ax = self.fig.add_subplot(111) + self.setup_plot() + + self.canvas = FigureCanvasTkAgg(self.fig, master=plot_frame) + self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) + + # Add matplotlib navigation toolbar + self.toolbar = NavigationToolbar2Tk(self.canvas, plot_frame) + self.toolbar.update() + self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) + + def setup_plot(self): + """Initialize plot with labels and grid""" + self.ax.set_xlabel('x') + self.ax.set_ylabel('y') + self.ax.set_title('Quadratic Function: y = ax² + bx + c') + self.ax.grid(True, linestyle='--', alpha=0.7) + self.ax.axhline(y=0, color='k', linestyle='-', linewidth=0.5) + self.ax.axvline(x=0, color='k', linestyle='-', linewidth=0.5) + + def plot_quadratic(self): + """Plot the quadratic equation based on user input""" + try: + # Get coefficients + a = float(self.a_var.get()) + b = float(self.b_var.get()) + c = float(self.c_var.get()) + + # Get x-range + x_min = float(self.x_min_var.get()) + x_max = float(self.x_max_var.get()) + + if x_min >= x_max: + messagebox.showerror("Input Error", "X-min must be less than X-max") + return + + # Generate x values + x = np.linspace(x_min, x_max, 500) + y = a * x**2 + b * x + c + + # Clear previous plot + self.ax.clear() + self.setup_plot() + + # Determine equation type for title + if a != 0: + eq_type = "Quadratic Function" + elif b != 0: + eq_type = "Linear Function" + else: + eq_type = "Constant Function" + + # Build equation string with only non-zero terms, formatted nicely + parts = [] + if a != 0: + if a == 1: + parts.append("x²") + elif a == -1: + parts.append("-x²") + else: + parts.append(f"{a}x²") + if b != 0: + if b == 1: + parts.append("+ x" if parts else "x") + elif b == -1: + parts.append("- x" if parts else "-x") + else: + sign = "+" if b > 0 else "-" + parts.append(f"{sign} {abs(b)}x") + if c != 0: + sign = "+" if c > 0 else "-" + parts.append(f"{sign} {abs(c)}") + + if not parts: + eq_str = "y = 0" + else: + # First term without leading plus + first = parts[0] + if first.startswith("+ "): + first = first[2:] + elif first.startswith("- "): + first = f"-{first[2:]}" + eq_str = "y = " + first + " " + " ".join(parts[1:]) + + self.ax.set_title(f"{eq_type}: {eq_str}") + + # Plot the quadratic + self.ax.plot(x, y, 'b-', linewidth=2, label=eq_str) + + # Calculate and plot vertex + if a != 0: + vertex_x = -b / (2 * a) + vertex_y = a * vertex_x**2 + b * vertex_x + c + self.ax.plot(vertex_x, vertex_y, 'ro', markersize=8, label=f'Vertex ({vertex_x:.2f}, {vertex_y:.2f})') + + # Calculate and plot roots if they exist (real roots) + discriminant = b**2 - 4*a*c + + # Handle quadratic case (a ≠ 0) + if a != 0: + if discriminant >= 0: + root1 = (-b + np.sqrt(discriminant)) / (2 * a) + root2 = (-b - np.sqrt(discriminant)) / (2 * a) + # For discriminant == 0, both roots are the same + if abs(root1 - root2) < 1e-10: # Essentially equal + self.ax.plot(root1, 0, 'go', markersize=8, label=f'Double Root ({root1:.2f}, 0)') + else: + self.ax.plot(root1, 0, 'go', markersize=8, label=f'Root1 ({root1:.2f}, 0)') + self.ax.plot(root2, 0, 'go', markersize=8, label=f'Root2 ({root2:.2f}, 0)') + else: + # Complex roots - show message in legend + self.ax.text(0.02, 0.02, 'Complex Roots', transform=self.ax.transAxes, + fontsize=10, color='red', bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.5)) + # Handle linear case (a = 0, b ≠ 0) + elif b != 0: + root = -c / b + self.ax.plot(root, 0, 'go', markersize=8, label=f'Root ({root:.2f}, 0)') + # Handle constant case (a = 0, b = 0) + else: + if c == 0: + self.ax.text(0.02, 0.02, 'Infinite Roots (y = 0)', transform=self.ax.transAxes, + fontsize=10, color='blue', bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.5)) + else: + self.ax.text(0.02, 0.02, 'No Roots (horizontal line)', transform=self.ax.transAxes, + fontsize=10, color='red', bbox=dict(boxstyle='round', facecolor='lightcoral', alpha=0.5)) + + # Add legend + self.ax.legend(loc='best') + + # Refresh canvas + self.canvas.draw() + + except ValueError: + messagebox.showerror("Input Error", "Please enter valid numbers for all coefficients and range") + + def clear_plot(self): + """Clear the plot""" + self.ax.clear() + self.setup_plot() + self.canvas.draw() + + def save_plot(self): + """Save the current plot to a file""" + try: + from tkinter import filedialog + import os + + filetypes = [ + ('PNG files', '*.png'), + ('JPEG files', '*.jpg'), + ('PDF files', '*.pdf'), + ('SVG files', '*.svg'), + ('All files', '*.*') + ] + + filename = filedialog.asksaveasfilename( + defaultextension=".png", + filetypes=filetypes, + title="Save Plot As" + ) + + if filename: + self.fig.savefig(filename, dpi=300, bbox_inches='tight') + messagebox.showinfo("Success", f"Plot saved successfully as:\n{os.path.basename(filename)}") + + except Exception as e: + messagebox.showerror("Save Error", f"Could not save plot: {str(e)}") + + +if __name__ == "__main__": + app = QuadraticPlotter() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ed7b534 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +numpy>=1.21.0 +matplotlib>=3.5.0 \ No newline at end of file