Как нарисовать график
- Формируем интерфейс
- Добавляем обработчик события Paint
- Рисуем график
- Сдвигаем график в центр области
- Переворачиваем график
- Масштабируем график
- Рисуем сетку
- Решаем проблему перерисовки при изменении размеров формы
- Сглаживаем график
- Подключаем учет коэффициентов
- Итоговый код
При разработке более сложных приложений, в процессе обработки данных получаются результаты которые содержат много числовых данных. И хотя выводить эти данные в виде строк из циферок можно, но оценивать полученный результат в таком виде не очень удобно.
Поэтому человечество придумало выводить такие данные в виде графиков. Которые в свою очередь эволюционировало в инфографику.
Но для инфографики нам неплохо было бы еще и на художественном поучиться, а у нас времени даже не программирование не очень много получается, так что мы будем просто рисовать график.
Рисовать будем простую штуку, функцию вида
где коэффициенты k, n можно будет поменять.
Формируем интерфейс
Добавим:
-
TextBox под коэффициент k, и соответствующий Label, свойства установим
- (Name): txtK
- Text: 1
-
TextBox под коэффициент n, и соответствующий Label, свойства установим
- (Name): txtN
- Text: 2 – чтоб парабола получилась
-
PictureBox, на нем будем рисовать график
- (Name): pictureBox
- BackColor: White – чтобы область под график выглядела белой
Получим
Чтобы область под график pictureBox изменяла свои размеры с изменением размеров формы, установим ей свойство Anchor как на картинке:
теперь если менять размеры формы, и область под график будет меняться соответственно:
Добавляем обработчик события Paint
Всю отрисовку на PictureBox, полагается выполнять внутри функции привязанного к событию Paint
Выбираем на форме pictureBox переходим в список событий, и кликаем дважды на свойство Paint
оказываемся в новосозданной функции
Рисуем график
Попробуем нарисовать график, пока без учета коэффициентов.
Чтобы нарисовать график нам надо сформировать список точек, подготовим его
и так, точки у нас есть, теперь их надо нарисовать. Прежде чем вызвать функцию для отрисовки, надо указать как будет выглядеть линия, Для этого надо создать объект типа Pen (то бишь перо, ручка), у которого есть два основных параметра: цвет и ширина линии, создадим такой объект:
ну а теперь можно нарисовать график, для этого воспользуемся функций DrawLines, объекта Graphics (так называемый графический контекст устройства) который привязан к аргументу PaintEventArgs e
Увидим что-то несуразное:
Если попытаться угадать в этом параболу, то увидим сразу несколько проблем:
- видно только правую ветвь параболы
- парабола перевёрнута
- график сильно маленький
Сдвигаем график в центр области
Решить все перечисленные выше проблемы можно используя матрицы перехода https://ru.wikipedia.org/wiki/Матрица_перехода
Матрицы перехода являются одним из основополагающих математических инструментов в создании 3D графики, который в упрощённом виде работает и в 2D графике (с чем мы собственно сейчас и работаем).
Ко всякому объекту типа Graphics привязана матрица переходов (доступная через свойство Transform). По умолчанию она представляет собой единичную матрицу. Мы можем изменять матрицу используя методы объекта типа Graphics
- TranslateTransform – для перемещения центра координат
- ScaleTransform – для масштабирования
- RotateTransform – для поворота вокруг центра координат
Применяя эти методы в разных порядках можно перемещать, масштабировать и крутить объекты и даже группы объектов малой кровью.
Мы конечно особо ничего крутить ничего не планируем. Но давайте перенесем центр координат в центр pictureBox, добавим строчку:
получим:
Переворачиваем график
Очевидно, что у параболы с коэффициентом 1, ветви параболы должны быть направлены вверх. И хотя мы абсолютно верно формируем список точек:
ошибка возникает из-за того, что центр координат, у большинства системных объектов, находится в левом верхнем углу:
чтобы перевернуть график воспользуемся функцией ScaleTransform
красота
Масштабируем график
И вот вроде все отлично, но график рисуется в пиксельной системе координат. Что на небольших мониторах выглядит еще куда ни шло, но на современных Ultra HD и 4K придется использовать лупу. Мы конечно не хотим заставлять пользователя доставать лупу, но зато мы можем увеличить масштаб графика, снова воспользовавшись функций ScaleTransform. Добавим строчку:
проверяем:
неплохо, но линия какая-та толстая. Она масштабируется вместе со всем остальным, что не есть хорошо. Чтобы избавится от масштабирования линии придется модифицировать ее матрицу перехода. Да-да, у объекта типа Pen тоже есть своя матрица перехода, также доступная через свойство Transform.
Какую же матрицу перехода применять к линии? Очевидно, обратную к матрице основного Graphics. Сделаем это:
вот теперь другое дело
Рисуем сетку
Чтобы лучше ориентироваться где какая точка находится нарисуем сетку с размером ячейки в одну единицу
проверяем:
кстати можно сделать чтобы единица на экране соответствовала одному сантиметру в реальной жизни и получить своего рода экранную линейку. Для этого надо воспользоваться свойством DpiX и DpiY объекта Graphics, пробуем. Dpi определяет количество точек на дюйм. В одном дюйме примерно 2.54 сантиметра, следовательно, нам надо отредактировать вызов функции скалирования следующим образом:
достаем линейку, прикладываем к экрану:
так себе точность, конечно, получается, но результат все-таки радует.
Решаем проблему перерисовки при изменении размеров формы
Возможны вы уже пробовали менять размеры формы, и заметили, что получаемый результат далек от ожидаемого.
связано такое поведение с особенностью перерисовки объектов на форме. Перерисовывается только изменённая часть. А так как при разных размерах формы положение графика и сетки разное получается наложение десятка разных изображений, что приводит к таким ужасным артефактам.
Чтобы при изменении размера формы изображение перерисовывалось всегда целиком, добавьте обработчик событию Resize, объекта pictureBox
и в обработчике вставьте строчку:
проверяем:
Сглаживаем график
Сейчас у нас графи рисуется по целым координатам, из-за чего выглядит слегка уродливо и сильно угловато. Давайте увеличим количество точек. В этот раз, при формировании списка точек, я, вместо цикла, воспользуюсь Linq функциями, которые позволят мне сформировать список точек в функциональной манере с использованием лямбда-выражений.
запускаем, получаем гладенький график:
Подключаем учет коэффициентов
Играясь с графиками совсем забыли про коэффициенты. Посчитаем их значения и добавим в учет при формировании точек:
запускаем:
хм, чего-то не работает…
А ну да, нам же надо чтобы при изменении значения, вызывался метод pictureBox.Invalidate(), тот самый которые отправляет запрос на перерисовку всего pictureBox. Переключаемся на форму, и кликаем два разу на txtK, а затем на txtN, добавляем код в соответствующие обработчики:
проверяем:
Итоговый код
Если вы в какой-то момент запутались, то вот вам итоговый код функции отрисовки:
Конец.