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
This commit is contained in:
commit
831b650111
155
.gitignore
vendored
Normal file
155
.gitignore
vendored
Normal file
@ -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/
|
||||
121
README.md
Normal file
121
README.md
Normal file
@ -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
|
||||
|
||||

|
||||
|
||||
*Note: Add your own screenshot after running the application*
|
||||
273
quadratic_plotter.py
Normal file
273
quadratic_plotter.py
Normal file
@ -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()
|
||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
numpy>=1.21.0
|
||||
matplotlib>=3.5.0
|
||||
Loading…
Reference in New Issue
Block a user