diff --git a/README.md b/README.md index ebe891f..a3059c4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ## Установка ```bash -git clone https://github.com//sets-visualizer.git +git clone https://github.com/6ixyld/sets-visualizer.git cd sets-visualizer python -m venv venv source venv/bin/activate # или venv\Scripts\activate на Windows diff --git a/app.py b/app.py new file mode 100644 index 0000000..e7ed1b8 --- /dev/null +++ b/app.py @@ -0,0 +1,95 @@ +from flask import Flask, render_template, request, jsonify +from venn_visual import plot_venn # Убрал лишний импорт +from sets_logic import * + +app = Flask(__name__) + + +@app.route("/", methods=["GET", "POST"]) +def index(): + img = None + result_text = "" + if request.method == "POST": + # получаем множества из формы + raw_a = request.form.get("set_a", "") + raw_b = request.form.get("set_b", "") + operation = request.form.get( + "operation", "union" + ) # получаем выбранную операцию + + # преобразование в Python set + a = set(raw_a.replace(" ", "").split(",")) if raw_a else set() + b = set(raw_b.replace(" ", "").split(",")) if raw_b else set() + + # Выбор операции + if operation == "union": + result = union(a, b) + operation_name = "объединения" + elif operation == "intersection": + result = intersection(a, b) + operation_name = "пересечения" + elif operation == "difference": + result = difference(a, b) + operation_name = "разности A\\B" + elif operation == "sym_diff": + result = sym_diff(a, b) + operation_name = "симметрической разности" + elif operation == "cartesian": + result = cartesian(a, b) + operation_name = "декартового произведения" + + # Форматирование результата + if operation == "cartesian": + result_text = ( + f"Результат {operation_name}: {', '.join(str(x) for x in result)}" + ) + else: + result_text = f"Результат {operation_name}: {', '.join(str(x) for x in sorted(result))}" + + # генерируем диаграмму только для операций с 2-мя множествами + if a and b: + img = plot_venn(a, b) + + return render_template("index.html", img=img, result_text=result_text) + + +@app.route("/process", methods=["POST"]) +def process(): + data = request.get_json() + a = ( + set(data.get("setA", "").replace(" ", "").split(",")) + if data.get("setA") + else set() + ) + b = ( + set(data.get("setB", "").replace(" ", "").split(",")) + if data.get("setB") + else set() + ) + operation = data.get("operation", "union") + + # Выбор операции + if operation == "union": + result = union(a, b) + elif operation == "intersection": + result = intersection(a, b) + elif operation == "difference": + result = difference(a, b) + elif operation == "sym_diff": + result = sym_diff(a, b) + elif operation == "cartesian": + result = cartesian(a, b) + else: + result = set() + + # Если результат — множество кортежей (для декартового произведения), конвертируем в список строк + if operation == "cartesian": + result_list = [str(t) for t in result] + else: + result_list = list(result) + + return jsonify({"result": result_list}) + + +if __name__ == "__main__": + app.run(debug=True) diff --git a/docs/annotation.docx b/docs/annotation.docx new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/sets_logic.py b/sets_logic.py new file mode 100644 index 0000000..3815a20 --- /dev/null +++ b/sets_logic.py @@ -0,0 +1,14 @@ +def union(a, b): + return a | b + +def intersection(a, b): + return a & b + +def difference(a, b): + return a - b + +def sym_diff(a, b): + return a ^ b + +def cartesian(a, b): + return {(x, y) for x in a for y in b} diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..53b7642 --- /dev/null +++ b/static/script.js @@ -0,0 +1,15 @@ +function sendData() { + const setA = document.getElementById('setA').value.split(',').map(x => x.trim()); + const setB = document.getElementById('setB').value.split(',').map(x => x.trim()); + const operation = document.getElementById('operation').value; + + fetch('/process', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ setA, setB, operation }) + }) + .then(res => res.json()) + .then(data => { + document.getElementById('result').innerText = `{ ${data.result.join(', ')} }`; + }); +} diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..6de513f --- /dev/null +++ b/static/style.css @@ -0,0 +1,134 @@ +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + max-width: 900px; + margin: 0 auto; + padding: 20px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + color: #333; +} + +.container { + background: white; + border-radius: 20px; + padding: 40px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); + margin-top: 20px; +} + +h1 { + text-align: center; + color: white; + margin-bottom: 10px; + font-size: 2.8em; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); + font-weight: 700; +} + +.subtitle { + text-align: center; + color: white; + margin-bottom: 30px; + font-size: 1.2em; + opacity: 0.9; +} + +.form-group { + margin-bottom: 25px; +} + +label { + display: block; + margin-bottom: 10px; + font-weight: 600; + color: #555; + font-size: 1.1em; +} + +input, +select, +button { + display: block; + margin: 8px 0; + padding: 15px; + width: 100%; + border: 2px solid #e1e8ed; + border-radius: 12px; + font-size: 16px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-sizing: border-box; + font-family: inherit; +} + +input:focus, +select:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); + transform: translateY(-2px); +} + +button { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + padding: 18px; + font-size: 18px; + font-weight: 600; + cursor: pointer; + margin-top: 25px; + border-radius: 12px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +button:hover { + transform: translateY(-3px); + box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4); +} + +#result { + background: #f8f9fa; + /* Нейтральный серый фон */ + color: #2d3436; + /* Темно-серый текст */ + border-left: 5px solid #667eea; + padding: 20px; + margin: 30px 0; + border-radius: 12px; + font-size: 1.2em; + font-weight: 500; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08); +} + +.venn-container { + text-align: center; + margin: 40px 0; + padding: 30px; + background: #f8f9fa; + border-radius: 15px; + box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.05); +} + +.venn-container h2 { + color: #2d3436; + margin-bottom: 25px; + font-size: 1.8em; + font-weight: 600; +} + +.venn-container img { + max-width: 100%; + height: auto; + border-radius: 15px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); + border: 3px solid white; +} + +.operation-info { + text-align: center; + color: #636e72; + font-style: italic; + margin-top: 10px; + font-size: 0.9em; +} \ No newline at end of file diff --git a/static/venn.png b/static/venn.png new file mode 100644 index 0000000..e69de29 diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..c726ed7 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,57 @@ + + + + + + + Визуализация операций над множествами + + + + + +

Визуализация операций над множествами

+
Интерактивная диаграмма Венна с отображением элементов
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + {% if result_text %} +
{{ result_text }}
+ {% endif %} + + {% if img %} +
+ Диаграмма Венна +
Элементы отображаются непосредственно в соответствующих областях диаграммы
+
+ {% endif %} +
+ + + \ No newline at end of file diff --git a/venn_visual.py b/venn_visual.py new file mode 100644 index 0000000..e28ee85 --- /dev/null +++ b/venn_visual.py @@ -0,0 +1,88 @@ +# venn_visual.py +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt +from matplotlib_venn import venn2 +import io +import base64 + + +def plot_venn(a, b, set_labels=("A", "B")): + """ + Создаёт красивую диаграмму Венна для двух множеств с отображением элементов. + """ + # Создаём фигуру с современным дизайном + plt.figure(figsize=(8, 8), facecolor="#f8f9fa") + ax = plt.gca() + ax.set_facecolor("#f8f9fa") + + # Цветовая схема + colors = ["#ff6b6b", "#4ecdc4"] + text_color = "#2d3436" + + # Строим диаграмму Венна + v = venn2([a, b], set_labels=set_labels, set_colors=colors, alpha=0.8) + + # Очищаем стандартные подписи с количествами и добавляем элементы + for label in v.subset_labels: + if label: + label.set_text("") + + # Добавляем элементы множества A только (левая часть) + if a - b: + elements_a = sorted(a - b) + v.get_label_by_id("10").set_text("\n".join(map(str, elements_a))) + + # Добавляем элементы пересечения A∩B (центральная часть) + if a & b: + elements_intersect = sorted(a & b) + v.get_label_by_id("11").set_text("\n".join(map(str, elements_intersect))) + + # Добавляем элементы множества B только (правая часть) + if b - a: + elements_b = sorted(b - a) + v.get_label_by_id("01").set_text("\n".join(map(str, elements_b))) + + # Настраиваем подписи множеств + for label in v.set_labels: + if label: + label.set_fontsize(16) + label.set_fontweight("bold") + label.set_color(text_color) + + # Настраиваем подписи элементов + for subset_label in v.subset_labels: + if subset_label: + subset_label.set_fontsize(12) + subset_label.set_fontweight("normal") + subset_label.set_color(text_color) + + # Настраиваем внешний вид окружностей + for patch in v.patches: + if patch: + patch.set_edgecolor("#2d3436") + patch.set_linewidth(2) + patch.set_linestyle("-") + + # УБРАЛ ЗАГОЛОВОК ДИАГРАММЫ + # plt.title('Диаграмма Венна', color=text_color, fontsize=18, pad=20, fontweight='bold') + + # Убираем оси для чистого вида + ax.axis("off") + + # Сохраняем с высоким качеством + buf = io.BytesIO() + plt.savefig( + buf, + format="png", + bbox_inches="tight", + facecolor=ax.get_facecolor(), + dpi=150, + transparent=False, + edgecolor="none", + ) + plt.close() + + img_base64 = base64.b64encode(buf.getvalue()).decode("utf-8") + return img_base64