Урок 5.4: Интерактивность графиков
Подсветка точек при наведении
private int hoveredPointIndex = -1;
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
int newHoveredIndex = FindNearestPoint(e.X, e.Y);
if (newHoveredIndex != hoveredPointIndex)
{
hoveredPointIndex = newHoveredIndex;
this.Invalidate();
}
}
private int FindNearestPoint(int x, int y)
{
float minDistance = float.MaxValue;
int nearestIndex = -1;
for (int i = 0; i < points.Count; i++)
{
float distance = (float)Math.Sqrt(
Math.Pow(points[i].X - x, 2) +
Math.Pow(points[i].Y - y, 2));
if (distance < 10 && distance < minDistance)
{
minDistance = distance;
nearestIndex = i;
}
}
return nearestIndex;
}Zoom и Pan
private float zoom = 1.0f;
private PointF panOffset = new PointF(0, 0);
private void Form1_MouseWheel(object sender, MouseEventArgs e)
{
// Zoom
float zoomFactor = e.Delta > 0 ? 1.1f : 0.9f;
zoom *= zoomFactor;
this.Invalidate();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
panStart = e.Location;
isPanning = true;
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (isPanning)
{
panOffset.X += e.X - panStart.X;
panOffset.Y += e.Y - panStart.Y;
panStart = e.Location;
this.Invalidate();
}
}Tooltip с данными
private ToolTip tooltip = new ToolTip();
private int hoveredIndex = -1;
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
int index = FindNearestPoint(e.X, e.Y);
if (index != hoveredIndex)
{
hoveredIndex = index;
if (index >= 0)
{
tooltip.SetToolTip(this,
$"X: {data[index].X:F2}
Y: {data[index].Y:F2}");
}
this.Invalidate();
}
}Выбор области
private Rectangle selectionRect;
private bool isSelecting = false;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
selectionRect = new Rectangle(e.X, e.Y, 0, 0);
isSelecting = true;
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (isSelecting)
{
selectionRect.Width = e.X - selectionRect.X;
selectionRect.Height = e.Y - selectionRect.Y;
this.Invalidate();
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
// Рисование графика
// ...
// Рисование области выбора
if (isSelecting)
{
using (Pen pen = new Pen(Color.Blue, 1)
{ DashStyle = DashStyle.Dash })
{
e.Graphics.DrawRectangle(pen, selectionRect);
}
}
}
📝 Пример задания — выбор функции через ComboBox
Форма с ComboBox позволяет выбрать одну из 8 функций. При смене выбора панель перерисовывается с новой функцией через вспомогательный класс GraphPlotter:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace CSharpGraphicsApp
{
public class InteractiveGraphExample : Form
{
private ComboBox functionSelector;
private Panel graphPanel;
public InteractiveGraphExample()
{
Text = "Интерактивный выбор функции";
Size = new Size(700, 600);
BackColor = Color.White;
var controlPanel = new Panel
{
Height = 50, Dock = DockStyle.Top,
BackColor = Color.WhiteSmoke
};
controlPanel.Controls.Add(new Label
{
Text = "Функция:", AutoSize = true, Location = new Point(10, 15)
});
functionSelector = new ComboBox
{
Location = new Point(80, 12), Width = 200,
DropDownStyle = ComboBoxStyle.DropDownList
};
functionSelector.Items.AddRange(new object[]
{
"sin(x)", "cos(x)", "tan(x)", "x²", "x³", "√x", "e^x", "ln(x)"
});
functionSelector.SelectedIndex = 0;
functionSelector.SelectedIndexChanged += (s, e) => graphPanel.Invalidate();
controlPanel.Controls.Add(functionSelector);
Controls.Add(controlPanel);
graphPanel = new Panel { Dock = DockStyle.Fill, BackColor = Color.White };
graphPanel.Paint += (s, e) => DrawSelectedGraph(e.Graphics, graphPanel.ClientSize);
Controls.Add(graphPanel);
}
private void DrawSelectedGraph(Graphics g, System.Drawing.Size size)
{
// Параметры осей под выбранную функцию
Func<float, float> fn;
string title;
float minX, maxX, minY, maxY;
switch (functionSelector.SelectedIndex)
{
case 1: fn = x => (float)Math.Cos(x); title = "cos(x)";
minX = -6.3f; maxX = 6.3f; minY = -1.5f; maxY = 1.5f; break;
case 2: fn = x => (float)Math.Tan(x); title = "tan(x)";
minX = -1.4f; maxX = 1.4f; minY = -5f; maxY = 5f; break;
case 3: fn = x => x * x; title = "x²";
minX = -4f; maxX = 4f; minY = -1f; maxY = 16f; break;
case 4: fn = x => x * x * x; title = "x³";
minX = -3f; maxX = 3f; minY = -27f; maxY = 27f; break;
case 5: fn = x => x >= 0 ? (float)Math.Sqrt(x) : 0; title = "√x";
minX = 0f; maxX = 10f; minY = 0f; maxY = 3.5f; break;
case 6: fn = x => (float)Math.Exp(x); title = "e^x";
minX = -2f; maxX = 2f; minY = -0.5f; maxY = 8f; break;
case 7: fn = x => x > 0 ? (float)Math.Log(x) : 0; title = "ln(x)";
minX = 0.1f; maxX = 10f; minY = -2.5f; maxY = 2.5f; break;
default: fn = x => (float)Math.Sin(x); title = "sin(x)";
minX = -6.3f; maxX = 6.3f; minY = -1.5f; maxY = 1.5f; break;
}
// Простая отрисовка без вспомогательного класса
int w = size.Width, h = size.Height;
int ml = 60, mr = 20, mt = 40, mb = 50;
// Оси
g.DrawLine(Pens.Black, ml, h - mb, w - mr, h - mb); // X
g.DrawLine(Pens.Black, ml, mt, ml, h - mb); // Y
g.DrawString(title,
new Font("Segoe UI", 12, FontStyle.Bold), Brushes.Blue, ml + 10, mt);
// Точки функции
var pts = new System.Collections.Generic.List<PointF>();
float step = (maxX - minX) / (w - ml - mr);
for (float x = minX; x <= maxX; x += step)
{
float y = fn(x);
if (float.IsNaN(y) || float.IsInfinity(y)) { pts.Clear(); continue; }
float sx = ml + (x - minX) / (maxX - minX) * (w - ml - mr);
float sy = (h - mb) - (y - minY) / (maxY - minY) * (h - mt - mb);
pts.Add(new PointF(sx, sy));
}
if (pts.Count > 1)
using (Pen pen = new Pen(Color.Blue, 2))
g.DrawLines(pen, pts.ToArray());
}
}
}
Практическое задание
Задание: Интерактивные графики
- Создайте интерактивный график с масштабированием колесиком мыши
- Реализуйте перемещение правой кнопкой мыши
- Добавьте отображение значений при наведении
Задание по аналогии: Выбор функции через ComboBox
По аналогии с InteractiveGraphExample создайте форму с динамическим выбором функции.
- Добавьте панель управления вверху с
ComboBox, содержащим минимум 4 функции (sin, cos, x², √x). - В
SelectedIndexChangedвызывайтеgraphPanel.Invalidate(). - В обработчике Paint определяйте нужную функцию
Func<float, float>поSelectedIndex. - Реализуйте отрисовку осей и кривой через список точек
PointF. - Обрабатывайте случаи
NaNиInfinity— сбрасывайте накопленный список точек, чтобы не рисовать разрывы.
💡 Совет: Адаптируйте
minX, maxX, minY, maxY под каждую функцию — у tan и ln совсем другие допустимые диапазоны.
Проверка знаний
Ответьте на вопросы, чтобы проверить, насколько хорошо вы усвоили материал:
1. Усвоили ли вы основные концепции урока?
2. Готовы ли вы применить изученное на практике?
3. Что такое Zoom в графиках?