C# WinForms Graph Plotter for Custom f(x) with Adjustable Range

Answered using AI by Vibe Coding

Code Question:

сделай задание: тебе нужно в visual studio на с++ или с# написать код чтобы он

  1. содержал в себе функцию f(x)
  2. при запуске открывалось окно в котором есть место для графика f(x) и место с вводом 3 переменных, a и b - от a до b нужно построить график то есть для всех x что a<=x<=b нужно построить его, так же нужно ввести eps, тебе он не пригодиться это нужно мне для других функций которые я напишу уже сам
  3. суть в том чтобы я писал f(x) в коде, потом запускал, имел возможность брать любые a и b и рассматривать функцию на этом отрезке
  4. теперь как ее строить: это функция будет ограничена, поэтому можно найти максимум и минимум, на всякий прибавить к максимум 1 и к минимуму 1 (например для случая f(x)=const получится иначе непонятно и некрасиво), и тогда у тебя есть границы по x (a, b) ии по y (min_f-1,max_f+1) и строить так функцию
  5. нужно будет добавить оси координат Ох и Оу если они видны на данном отрезке, нарисуй их как красную прямую чтобы было легче ориентироваться
  6. так же
Code Solution:

Ниже — готовый пример на C# (Windows Forms). Проект создаётся в Visual Studio как "Windows Forms App (.NET Framework)". В коде есть функция f(x) — вы будете менять её по своему усмотрению. Интерфейс содержит поле для графика (PictureBox) и поля ввода a, b, eps (eps в коде пока не используется, оставлен для ваших будущих функций). При нажатии Plot строится график f(x) на отрезке [a,b], рассчитываются min и max по выборке, к ним добавляется по 1, оси Ox и Oy рисуются красным если они попадают в область.

Инструкции:

  1. В Visual Studio создайте новый проект: File -> New -> Project -> Windows Forms App (.NET Framework) (C#).
  2. Замените содержимое Program.cs и Form1.cs на код ниже (или вставьте Form1.cs как новый файл MainForm.cs и отредактируйте Program.cs соответственно).
  3. Соберите и запустите проект. Измените функцию f в коде на любую желаемую и снова запустите.

Program.cs:

using System;
using System.Windows.Forms;

namespace FunctionPlotter
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
}

MainForm.cs:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Globalization;

namespace FunctionPlotter
{
    public class MainForm : Form
    {
        private PictureBox pictureBox;
        private TextBox textA, textB, textEps;
        private Button btnPlot;
        private Label lblA, lblB, lblEps, lblInfo;

        public MainForm()
        {
            this.Text = "Функция f(x) — график на отрезке [a,b]";
            this.ClientSize = new Size(900, 600);

            pictureBox = new PictureBox
            {
                Location = new Point(10, 10),
                Size = new Size(760, 580),
                BorderStyle = BorderStyle.FixedSingle,
                BackColor = Color.White
            };
            this.Controls.Add(pictureBox);

            int ctrlX = 780;
            lblA = new Label { Text = "a:", Location = new Point(ctrlX, 20), AutoSize = true };
            textA = new TextBox { Location = new Point(ctrlX, 40), Width = 90, Text = "-10" };

            lblB = new Label { Text = "b:", Location = new Point(ctrlX, 80), AutoSize = true };
            textB = new TextBox { Location = new Point(ctrlX, 100), Width = 90, Text = "10" };

            lblEps = new Label { Text = "eps (необязательно):", Location = new Point(ctrlX, 140), AutoSize = true };
            textEps = new TextBox { Location = new Point(ctrlX, 160), Width = 90, Text = "0.001" };

            btnPlot = new Button { Text = "Построить", Location = new Point(ctrlX, 210), Width = 90 };
            btnPlot.Click += BtnPlot_Click;

            lblInfo = new Label
            {
                Location = new Point(ctrlX, 260),
                Size = new Size(100, 300),
                Text = "Измените f(x) в коде\nи запустите.\nОси Ox/Oy — красные.\nЕсли f константа,\nбудет добавлен отступ ±1.",
                AutoSize = false
            };

            Controls.AddRange(new Control[] { lblA, textA, lblB, textB, lblEps, textEps, btnPlot, lblInfo });

            // Обработка ресайза формы: подгоняем pictureBox
            this.Resize += (s, e) =>
            {
                pictureBox.Size = new Size(Math.Max(200, this.ClientSize.Width - 140), Math.Max(200, this.ClientSize.Height - 20));
            };
        }

        // Здесь вы меняете реализацию f(x) на любую нужную.
        private double f(double x)
        {
            // Пример: синус. Измените на свою функцию.
            return Math.Sin(x);
            // Примеры других функций:
            // return x*x - 3*x + 2;
            // return Math.Exp(-x*x/10.0) * Math.Cos(x);
            // return 2.0; // константа
        }

        private void BtnPlot_Click(object sender, EventArgs e)
        {
            if (!double.TryParse(textA.Text.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out double a) ||
                !double.TryParse(textB.Text.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out double b))
            {
                MessageBox.Show("Неверный ввод a или b.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (b <= a)
            {
                MessageBox.Show("Требуется b > a.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            // eps читаем, но пока не используем (оставлено для вашей логики)
            _ = textEps.Text;

            int width = Math.Max(2, pictureBox.Width);
            int height = Math.Max(2, pictureBox.Height);

            // Для корректности семплируем по числу пикселей (или чуть больше)
            int samples = Math.Max(500, width * 2);

            double min = double.PositiveInfinity;
            double max = double.NegativeInfinity;

            double[] xs = new double[samples + 1];
            double[] ys = new double[samples + 1];

            for (int i = 0; i <= samples; i++)
            {
                double t = (double)i / samples;
                double x = a + (b - a) * t;
                double y;
                try
                {
                    y = f(x);
                    if (double.IsNaN(y) || double.IsInfinity(y))
                    {
                        ys[i] = double.NaN;
                        xs[i] = x;
                        continue;
                    }
                }
                catch
                {
                    ys[i] = double.NaN;
                    xs[i] = x;
                    continue;
                }

                xs[i] = x;
                ys[i] = y;
                if (y < min) min = y;
                if (y > max) max = y;
            }

            // Если функция плохо определена везде
            if (double.IsInfinity(min) || double.IsInfinity(max))
            {
                MessageBox.Show("Функция недопустима на данном отрезке (все значения NaN/Inf).", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            // Если константа (min==max), делаем отступы по 1
            if (Math.Abs(max - min) < 1e-12)
            {
                max = max + 1.0;
                min = min - 1.0;
            }
            else
            {
                // Добавим по 1 для визуального отступа, как просили
                max += 1.0;
                min -= 1.0;
            }

            Bitmap bmp = new Bitmap(width, height);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                g.Clear(Color.White);

                // Функция для перевода (x,y) в пиксели
                Func<double, double, PointF> toPixel = (x, y) =>
                {
                    float px = (float)((x - a) / (b - a) * width);
                    // y: ymax -> 0, ymin -> height
                    float py = (float)((max - y) / (max - min) * height);
                    return new PointF(px, py);
                };

                // Рисуем оси Ox и Oy красным если они видны
                using (Pen axisPen = new Pen(Color.Red, 2))
                {
                    // Ось Oy (x = 0) — вертикальная
                    if (a <= 0 && 0 <= b)
                    {
                        double vx = 0;
                        PointF p1 = toPixel(vx, min);
                        PointF p2 = toPixel(vx, max);
                        g.DrawLine(axisPen, new PointF(p1.X, 0), new PointF(p1.X, height));
                    }
                    // Ось Ox (y = 0) — горизонтальная
                    if (min <= 0 && 0 <= max)
                    {
                        double vy = 0;
                        PointF p1 = toPixel(a, vy);
                        PointF p2 = toPixel(b, vy);
                        g.DrawLine(axisPen, new PointF(0, p1.Y), new PointF(width, p1.Y));
                    }
                }

                // Рисуем сетку/границы (по желанию)
                using (Pen border = new Pen(Color.LightGray, 1))
                {
                    g.DrawRectangle(border, 0, 0, width - 1, height - 1);
                }

                // Строим полилинию через точки, пропуская NaN
                using (Pen funcPen = new Pen(Color.Black, 1.5f))
                {
                    PointF? last = null;
                    for (int i = 0; i <= samples; i++)
                    {
                        if (double.IsNaN(ys[i])) { last = null; continue; }
                        PointF pt = toPixel(xs[i], ys[i]);
                        if (last != null)
                        {
                            // если расстояние слишком большое (скачок), можно разорвать линию
                            g.DrawLine(funcPen, last.Value, pt);
                        }
                        last = pt;
                    }
                }

                // Подписи min/max и a/b для ориентира (необязательно)
                using (Brush bsh = new SolidBrush(Color.Black))
                {
                    g.DrawString($"x ∈ [{a}, {b}]", this.Font, bsh, 4, 4);
                    g.DrawString($"y ∈ [{min:F3}, {max:F3}]", this.Font, bsh, 4, 20);
                }
            }

            // Показать картинку
            var old = pictureBox.Image;
            pictureBox.Image = bmp;
            if (old != null) old.Dispose();
        }
    }
}

Пояснения и советы:

  • Изменяйте функцию f(double x) в MainForm.cs на любую нужную реализацию. После изменения — пересоберите проект.
  • Для определения min/max используется равномерная дискретизация с количеством точек, зависящим от ширины окна (минимум 500 сэмплов). Это простая и быстрая стратегия; при необходимости можно улучшить (адаптивная дискретизация, поиск экстремумов через численные методы), но для большинства задач этого достаточно.
  • eps в интерфейсе пока не используется; вы можете подключить его в будущем (например, как критерий точности для адаптивной дискретизации).
  • Оси рисуются красным, только если они попадают в текущую область отображения.
  • Обрабатываются NaN/Infinity: такие интервалы просто пропускаются.

Если хотите, могу:

  • добавить возможность вводить формулу f(x) в текстовом поле и парсить её (например, с помощью библиотеки NCalc или реализовав собственный парсер),
  • реализовать адаптивное построение с использованием eps,
  • добавить масштабирование/перетаскивание мышью и координатную сетку.