Работа с графикой (C++/CLI)
Демонстрирует обработку изображений с помощью пакета SDK для Windows.
В следующих разделах показано использование System.Drawing.Image класса для обработки изображений.
Отображение изображений с помощью платформа .NET Framework
В следующем примере кода обработчик событий OnPaint изменяет указатель на Graphics объект для основной формы. Функция OnPaint предназначена для приложения Windows Forms, скорее всего, созданного с помощью мастера приложений Visual Studio.
Изображение представлено классом Image . Данные изображения загружаются из JPG-файла с помощью System.Drawing.Image.FromFile метода. Прежде чем изображение нарисовано в форму, форма изменяется для размещения изображения. Рисунок изображения выполняется с System.Drawing.Graphics.DrawImage помощью метода.
Image Классы Graphics находятся в System.Drawing пространстве имен.
Пример
#using using namespace System; using namespace System::Drawing; protected: virtual Void Form1::OnPaint(PaintEventArgs^ pe) override < Graphics^ g = pe->Graphics; Image^ image = Image::FromFile("SampleImage.jpg"); Form::ClientSize = image->Size; g->DrawImage( image, 0, 0, image->Size.Width, image->Size.Height ); >
Рисование фигур с помощью платформа .NET Framework
В следующем примере кода класс используется Graphics для изменения OnPaint обработчика событий для извлечения указателя на Graphics объект для основной формы. Затем этот указатель используется для задания цвета фона формы и рисования линии и дуги с помощью System.Drawing.Graphics.DrawLine методов и DrawArc методов.
Пример
#using using namespace System; using namespace System::Drawing; // . protected: virtual Void Form1::OnPaint(PaintEventArgs^ pe ) override < Graphics^ g = pe->Graphics; g->Clear(Color::AntiqueWhite); Rectangle rect = Form::ClientRectangle; Rectangle smallRect; smallRect.X = rect.X + rect.Width / 4; smallRect.Y = rect.Y + rect.Height / 4; smallRect.Width = rect.Width / 2; smallRect.Height = rect.Height / 2; Pen^ redPen = gcnew Pen(Color::Red); redPen->Width = 4; g->DrawLine(redPen, 0, 0, rect.Width, rect.Height); Pen^ bluePen = gcnew Pen(Color::Blue); bluePen->Width = 10; g->DrawArc( bluePen, smallRect, 90, 270 ); >
Поворот изображений с помощью платформа .NET Framework
В следующем примере кода показано использование System.Drawing.Image класса для загрузки изображения с диска, поворота его 90 градусов и его сохранения в виде нового ФАЙЛА JPG.
Пример
#using using namespace System; using namespace System::Drawing; int main() < Image^ image = Image::FromFile("SampleImage.jpg"); image->RotateFlip( RotateFlipType::Rotate90FlipNone ); image->Save("SampleImage_rotated.jpg"); return 0; >
Преобразование форматов файлов изображений с помощью платформа .NET Framework
В следующем примере кода демонстрируется System.Drawing.Image класс и перечисление, используемое System.Drawing.Imaging.ImageFormat для преобразования и сохранения файлов изображений. Следующий код загружает изображение из JPG-файла, а затем сохраняет его в форматах GIF и BMP-файлов.
Пример
#using using namespace System; using namespace System::Drawing; using namespace System::Drawing::Imaging; int main() < Image^ image = Image::FromFile("SampleImage.jpg"); image->Save("SampleImage.png", ImageFormat::Png); image->Save("SampleImage.bmp", ImageFormat::Bmp); return 0; >
Как построить график в visual studio c
БлогNot. Visual C++/CLI: рисуем простой график функции в PictureBox
Visual C++/CLI: рисуем простой график функции в PictureBox
В этом примере мы хотим нарисовать простой график на компоненте PictureBox «по точкам», а не с использованием готовой компоненты Chart.
Создав проект Windows Forms в любой версии Studio 2012 и выше, придадим окну формы удобный размер и перетащим туда компоненту TableLayoutPanel , установим ей свойство Dock=Fill , затем удалим последнюю строку так, чтобы осталось 2 столбца, первому столбцу зададим относительную ширину 100%, а второму — абсолютную ширину 100 пикселов (как выполнять такие действия, показано в этой заметке).
В правую ячейку TableLayoutPanel перетащим кнопку Button и привяжем её ко всем краям формы, кроме нижнего (свойство Anchor = Top, Left, Right , свойство Dock=Top ).
В левую ячейку перетащим PictureBox и также установим ему Dock=Fill . Внешний вид полученной формы показан на рисунке:
Вид формы приложения
Так как экранный шаг в пикселах в условии не задан, установим его в переменной unit . Для порядка шаблон отрисовываемой функции double f(double x) опишем как делегат соответствующего типа. Весь этот код можно поместить после директивы #pragma endregion в файле MyForm.h :
#pragma endregion public: int unit = 50; //шаг в пискелах public: delegate double DelegatePtr(double); //тип функции для рисования private: double f(double x) < //конкретная функция, которую рисуем double y=Math::Cos(Math::Sqrt(x)); return Double::IsNaN(y)?0:y; >
Нам придётся запрограммировать только щелчок по кнопке, после двойного щелчка по ней в конструкторе создастся обработчик события
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) <
>
в который мы и поместим весь показанный ниже код с комментариями.
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) < int pW = pictureBox1->Width, pH = pictureBox1->Height; Bitmap ^img = gcnew Bitmap(pW, pH); //создаем поверхность для рисования (изображение) с размером элемента управления PictureBox Graphics ^g = Graphics::FromImage(img); //создаем устройство для рисования на поверхности //рисуем сетку: for (int i = 0; i < pW; i += unit) g->DrawLine(Pens::Blue, i, 0, i, pH); for (int i = 0; i < pH; i += unit) g->DrawLine(Pens::Blue, 0, i, pW, i); //находим середину и рисуем линии осей: int mX = int(pW / 2 - pW / 2 % unit); int mY = int(pH / 2 - pH / 2 % unit); g->DrawLine(Pens::Red, mX, 0, mX, pH); g->DrawLine(Pens::Red, 0, mY, pW, mY); g->ScaleTransform(1, -1); //переворачиваем ось Y для удобства восприятия g->TranslateTransform((float)mX, -(float)mY); //смещаем нулевую координату на пересечение осей //рисуем график: DelegatePtr^ f = gcnew DelegatePtr(this, &MyForm::f); double x1 = -1., x2 = 10., s = 0.25; //границы рисования double x = x1; double y; System::Collections::Generic::List ^Points = gcnew System::Collections::Generic::List(); //коллекция точек графика while (x < x2) < y = f(x); Points->Add(PointF(x*unit, y*unit)); //добавляем точку в коллекцию. Полученные координаты сразу переводим в экранные единицы x += s; > g->DrawLines(Pens::Green, Points->ToArray()); //рисование линий графика delete g; //освобождение ресурсов устройства рисования this->pictureBox1->Image = img; //присвоение и отображение изображения в PictureBox >
График перерисовывается каждый раз при нажатии кнопки. Пожалуй, это даже экономичнее по ресурсам, чем использование события Paint .
Можно подойти к задаче и по-другому, заранее задав интервал изменения переменной по оси X, а шаг по ней (как и по оси Y) рассчитывая так, чтобы он был равен одному пикселу. Тогда при той же самой форме пользовательская часть кода примет следующий вид (для простоты исключено рисование линий сетки).
#pragma endregion /* простой график на PictureBox, шаг по x = 1 пикселу */ private: delegate double Function (double); //указатель на функцию private: double f (double x) < return Math::Sin(x); >//сама функция private: double x1 =-2*Math::PI, x2= 2*Math::PI, xstep, y1, y2, ystep; //границы, в реальности границы по x введены откуда-то извне private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) < int pw = pictureBox1->Width, ph = pictureBox1->Height; //размеры полотна Function ^f = gcnew Function(this, &MyForm::f); //рисуемая функция xstep = (x2 - x1) / pw; //шаг по x = 1 пикселу y1 = y2 = f(x1); for (double x = x1; x y2) y2 = y; if (yystep = (y2 - y1) / ph; //шаг по y также = 1 пикселу (искажение пропорций) double xcoeff = pw / (x2 - x1), ycoeff = ph / (y2 - y1); //коэффициенты пересчета физических координат в пиксельные Bitmap ^img = gcnew Bitmap(pw,ph); //картинка для рисования на ней Graphics ^g = Graphics::FromImage (img); //контекст картинки int mx = - x1 * xcoeff, my = - y1 * ycoeff; //начало координат g->DrawLine(Pens::Red,mx,0,mx,ph);g->DrawLine(Pens::Red,0,my,pw,my); //оси System::Collections::Generic::List ^Points = gcnew System::Collections::Generic::List (); //коллекция точек double x = x1, y; while (x Add(PointF((x-x1)*xcoeff,(y2-y)*ycoeff)); x += xstep; > g->DrawLines(Pens::Green,Points->ToArray()); //отрисовка this->pictureBox1->Image = img; //назначили картинку полотну >

Вид формы приложения (2)
10.11.2017, 13:12 [19238 просмотров]
Что такое конструктор классов?
Область применения:
Visual Studio Visual Studio для Mac
Visual Studio Code ![]()
С помощью средства конструктора классов в Visual Studio можно создавать, визуализировать и рефакторинг схем классов в проектах C#, Visual Basic или C++. Схемы классов показывают элементы кода и их связи в области визуального конструктора, которые помогут лучше понять структуру проекта и реорганизовать код.
Конструктор классов пока недоступен в проектах .NET Core.
Сведения о схемах классов
Вот что можно сделать с схемами классов, более подробно:
- Разработка: редактируйте код своего проекта путем редактирования схемы классов. Добавляйте новые и удаляйте ненужные элементы. Внесенные изменения будут отражены в коде.
- Визуализация: лучше понимайте структуру своего проекта путем просмотра классов в проекте на схеме классов. Настраивайте схему таким образом, что было можно сосредоточиться на наиболее важных сведениях о проекте. Сохраняйте свою схему для использования ее в дальнейшем для демонстрации или в документации.
- Рефакторинг: переопределяйте методы, переименовывайте идентификаторы, выполняйте рефакторинг параметров, реализуйте интерфейсы и абстрактные классы.
Просмотр типов и отношений
В диаграмме классов отображаются сведения о типах, например о составляющих их элементах и отношениях между ними. Визуализация этих сущностей является динамическим представлением кода. Это означает, что можно изменять типы в конструкторе, а затем просматривать эти правки в исходном коде сущности. Аналогичным образом диаграмма классов синхронизируется с изменениями, вносимыми в файлы с кодом.
Если проект содержит диаграмму классов и в проекте есть ссылка на тип, который находится в другом проекте, диаграмма классов не отображает этот тип до сборки проекта для него. Аналогичным образом, диаграмма не отображает изменения кода внешней сущности, пока вы не перестроите проект с этой сущностью.
Процесс создания диаграммы классов
Диаграммы классов помогают понять структуру классов в проектах. Возможно, эти проекты были созданы другими разработчиками или необходимо просто обновить ваш собственный проект. Диаграммы классов можно использовать для настройки, совместного использования и предоставления сведений о проекте другим пользователям.
Первым шагом в предоставлении сведений о проекте является создание схемы классов, отображающей то, что вы хотите показать. Дополнительные сведения см. в разделе Добавление диаграммы классов. Можно создать несколько схем классов для проекта, которые можно использовать для отображения отдельного представления проекта, выбранного подмножества типов проекта или выбранного подмножества членов типов.
Помимо определения того, что отображается на каждой схеме классов, можно также изменить способ представления информации; Дополнительные сведения см. в разделе «Настройка схем классов».
После точной настройки одной или нескольких схем классов можно скопировать их в документы Microsoft Office и распечатать или экспортировать в виде графических файлов. Дополнительные сведения см. в разделе «Копирование элементов схемы классов» в документ Microsoft Office, схемы классов печати и схемы классов «Экспорт» в виде изображений.
Конструктор классов не отслеживает расположение исходных файлов, поэтому, изменение структуры проекта или перемещение исходных файлов в проекте может привести к тому, что конструктор классов не будет отслеживать тип, особенно исходный тип для typedef, базовые классы или типы ассоциаций. Может возникнуть ошибка вида Конструктору классов не удалось отобразить этот тип. В этом случае перетащите измененный или перемещенный исходный код в схему классов и повторно отобразите ее.
Добавление диаграмм классов в проекты
Для проектирования, изменения и рефакторинга классов и других типов добавьте в проект C# , Visual Basic или C++ диаграмму классов. Для визуализации различных частей кода в проекте добавьте в проект несколько диаграмм классов.
Диаграммы классов не могут быть созданы для проектов с общим кодом для нескольких приложений. Сведения о создании схем классов UML см. в разделе Создание проектов и схем моделирования UML.
Установка компонента «Конструктор классов»
Если у вас не установлен компонент Конструктор классов, выполните следующие действия, чтобы установить его.
- Откройте Visual Studio Installer из меню «Пуск» или выбрав в строке меню Visual Studio Инструменты>Получить инструменты и функции. Откроется Visual Studio Installer.
- Выберите вкладку Отдельные компоненты и затем прокрутите вниз до категории Средства для работы с кодом.
- Выберите Конструктор классов и нажмите Изменить.


Добавление пустой схемы классов в проект
- В обозревателе решений щелкните узел проекта правой кнопкой мыши и выберите пункт Добавить>Новый элемент. Либо нажмите клавиши CTRL+SHIFT+A. Откроется диалоговое окно Добавление нового элемента.
- Разверните узел «Общие«, а затем выберите схему классов из списка шаблонов. Для проектов Visual C++ шаблон Схема классов находится в категории Служебные программы.
Примечание. Если шаблон Схема классов отсутствует, выполните действия по установке компонента Конструктор классов в Visual Studio.
Чтобы добавить несколько схем классов, необходимо повторить шаги данной процедуры.
Добавление диаграммы классов на основе существующих типов
В обозревателе решений откройте контекстное меню файла класса, щелкнув правой кнопкой мыши, и выберите пункт Перейти к диаграмме классов.
В представлении классов откройте контекстное меню пространства имен или типа и выберите пункт Просмотр схемы класса.
Если представление классов не открыто, откройте его из меню Вид. Или нажмите клавиши CTRL+SHIFT+C.
Отображение содержимого всего проекта в схеме классов
В обозревателе решений или в представлении классов щелкните проект правой кнопкой мыши и выберите Просмотр, а затем — Просмотр схемы класса.
Будет создана автоматически заполняемая диаграмма классов.
Конструктор классов пока недоступен в проектах .NET Core.
Настройка диаграмм классов
Способ отображения информации схемой классов можно изменить. В рабочей области конструирования можно настроить всю схему или отдельные типы.
Например, можно изменить масштаб всей схемы классов, изменить порядок группировки и сортировки членов отдельного типа, скрыть или отобразить отношения, переместить отдельные типы или наборы типов в любое место на схеме.
Настройка способа отображения фигур на схеме не приводит к изменению кода типов, представленных на ней.
Разделы, содержащие члены типов, например раздел Properties класса, называются секциями. Отдельные секции и члены типов можно скрывать или отображать.
Увеличение или уменьшение масштаба отображения схемы классов
- Откройте и выберите файл диаграммы классов в конструкторе классов.
- Чтобы изменить уровень масштабирования области конструктора, нажмите кнопку Увеличить или Уменьшить на панели инструментов конструктора классов. or Укажите конкретное значение масштабирования. Для этого можно воспользоваться раскрывающимся списком Масштаб или ввести допустимый уровень масштабирования (в диапазоне от 10 % до 400 %).
Примечание. Изменение уровня масштабирования не оказывает влияния на масштаб печати схемы классов.
Настройка способа группировки и сортировки членов типа
- Откройте и выберите файл диаграммы классов в конструкторе классов.
- Щелкните правой кнопкой мыши пустую часть области конструктора и наведите указатель на пункт Участники группы.
- Выберите один из трех доступных вариантов.
- Группировка по типу разделяет отдельные члены типа в сгруппированный список свойств, методов, событий и полей. Отдельные группы зависят от определения сущностей. Например, класс не отобразит никакую группу событий, если для этого класса не определены события.
- Группировка по доступу разделяет отдельные члены типа в сгруппированный список модификаторов доступа членов. Например, общие и частные.
- Сортировать по алфавиту отображает элементы, из которых состоит сущность, в виде единого упорядоченного в алфавитном порядке списка. Список сортируется в порядке возрастания.
Скрытие секций в типе
- Откройте и выберите файл диаграммы классов в конструкторе классов.
- Щелкните правой кнопкой мыши категорию членов в типе, которую требуется настроить (например, выберите узел Методы в классе).
- Щелкните Скрыть ячейку. Выбранная секция исчезнет из контейнера типа.
Скрытие отдельных членов в типе
- Откройте и выберите файл диаграммы классов в конструкторе классов.
- Правой кнопкой мыши щелкните член в типе, который требуется скрыть.
- Щелкните Скрыть. Выбранный член исчезнет из контейнера типа.
Отображение скрытых секций и членов в типе
- Откройте и выберите файл диаграммы классов в конструкторе классов.
- Правой кнопкой мыши щелкните имя типа, содержащего скрытую секцию.
- Щелкните Показать все члены. Все скрытые секции и члены появятся в контейнере типа.
Скрытие отношений
- Откройте и выберите файл диаграммы классов в конструкторе классов.
- Правой кнопкой мыши щелкните линию связи или наследования, которую требуется скрыть.
- Выберите Скрыть для линий связи или Скрыть линию наследования для линий наследования.
- Щелкните Показать все члены. Все скрытые секции и члены появятся в контейнере типа.
Отображение скрытых отношений
- Откройте и выберите файл диаграммы классов в конструкторе классов.
- Правой кнопкой мыши щелкните имя типа, содержащего скрытые линии связи или наследования. Щелкните Показать все члены для линий связи либо Показать базовый класс или Показать производные классы для линий наследования.
Удаление фигуры со схемы классов
Фигуру типа можно удалить из схемы классов, не затронув соответствующий код типа. Удаление фигур типов из схемы классов влияет только на данную схему. Базовый код, определяющий тип и другие схемы, отображающие тип, не затрагиваются.
- На схеме классов выберите фигуру типа, которую необходимо удалить из схемы.
- В меню Правка выберите команду Удалить из схемы. Фигура типа и все линии связи или наследования, соединенные с фигурой, больше не будут отображаться на схеме.
Удаление фигуры типа и ее кода
- Щелкните правой кнопкой мыши на рабочей области конструирования.
- В контекстном меню выберите команду Удалить код. Фигура удаляется из схемы, а ее базовый код удаляется из проекта.
Копирование элементов диаграммы классов в документ Microsoft Office
Можно копировать фигуры из схемы классов .NET (файл .cd) в другие документы. В зависимости от типа документа, в который выполняется вставка, при вставке будет получена либо копия фигуры, либо ее базовый код. Сведения о копировании фигур из схем классов UML в проекте моделирования см. в статье Экспорт схем в виде изображений.
Копирование одного элемента
Щелкните фигуру правой кнопкой мыши и выберите пункт Копировать изображение.
Копирование нескольких элементов
- Выберите на схеме фигуры, которые требуется скопировать.
- Щелкните выбранные фигуры правой кнопкой мыши и выберите пункт Копировать изображение.
Копирование всех элементов в схеме классов
- Щелкните правой кнопкой мыши область схемы и выберите команду «Выбрать все» или нажмите клавиши CTRL +A.
- В меню Правка выберите Копировать изображение.
Можно также выбрать команду Копировать вместо Копировать изображение. Команда Копировать копирует изображение как обычный точечный рисунок. Команда Копировать изображение копирует изображение как векторное изображение, что лучше подходит для большинства приложений Office.
Экспорт диаграмм классов в виде изображений
Чтобы экспортировать схему классов, созданную на основе кода в проекте, сохраните ее как изображение. Если вместо этого нужно экспортировать схемы класса UML, см. статью Экспорт схем в виде изображений.
Экспорт схем
- Откройте файл со схемой классов (.cd).
- В меню Схема классов или в контекстном меню области схемы выберите пункт Экспортировать схему как рисунок.
- Выберите схему.
- Выберите нужный формат.
- Выберите Экспорт, чтобы завершить экспорт.
Чтобы автоматически обновить экспортированные изображения, на которые есть ссылки в других документах, еще раз экспортируйте схему в Visual Studio.
Печать схем классов
Схему классов можно распечатать с помощью функции печати Visual Studio.
Печать схемы классов
- Откройте схему классов.
- В меню Файл выберите команду Печать.
Будет распечатана вся схема классов. При необходимости печати на бумаге соответствующего размера может потребоваться корректировка настроек в диалоговом окне Параметры страницы.
Добавление комментариев в диаграммы классов
Для добавления примечаний к схемам классов можно использовать фигуры комментария. У фигуры комментария есть свойство Текст, в которое можно вводить текст. Фигуры комментария существуют только в области схемы, а не в коде.
Комментарий находится в представлении диаграммы классов в конструкторе классов. При открытии в одном проекте второй диаграммы классов комментарии, созданные в первом представлении, не отображаются. При удалении схемы классов все содержащиеся в ней комментарии также удаляются.
Размеры фигуры комментария можно изменить, однако нельзя изменить другие характеристики представления комментария, такие как фон, цвет, шрифт или размер шрифта.
Добавление комментария
- Перетащите комментарий из панели инструментов конструктора классов на схему классов.
- Щелкните новую фигуру комментария на схеме и введите нужный текст.
Использование сочетаний клавиш и мыши в окне «Схема классов» и «Сведения о классах»
Вы можете использовать клавиатуру в дополнение к мыши для навигации в конструкторе классов и окне Сведения о классах.
Использование мыши в конструкторе классов
В схемах классов поддерживаются следующие действия мыши.
| Комбинации мыши | Контекст | Description |
|---|---|---|
| Дважды щелкните | Shape — элементы | Открывает редактор исходного кода. |
| Дважды щелкните | Соединитель без описания операций | Интерфейс развертывания и свертывания без описания операций. |
| Дважды щелкните | Метка соединителя без описания операций | Вызывает команду Показать интерфейс |
| Колесико мыши | Диаграмма классов | Вертикальная прокрутка. |
| SHIFT + колесико мыши | Диаграмма классов | Горизонтальная прокрутка. |
| CTRL + колесико мыши | Диаграмма классов | Изменение масштаба. |
| CTRL+SHIFT + щелчок | Диаграмма классов | Изменение масштаба. |
Использование мыши в окне «Сведения о классах»
С помощью мыши можно изменить внешний вид окна Сведения о классах и данные, которые в нем отображаются, следующими способами:
- Если щелкнуть любую изменяемую ячейку, можно изменить ее содержимое. Изменения будут отражены во всех местах, где данные хранятся или отображаются, в том числе в окне Свойства и в исходном коде.
- Если щелкнуть любую ячейку строки, в окне Свойства появятся свойства элемента, представленного этой строкой.
- Чтобы изменить ширину столбца, перетащите границу справа от заголовка столбца до требуемой ширины столбца.
- Можно развернуть или свернуть узлы секции или свойств, щелкнув символы со стрелками слева от строки.
- В окне Сведения о классе есть несколько кнопок для создания членов в текущем классе и для перехода между секциями членов в таблице окна Сведения о классе.
Использование клавиатуры в конструкторе классов
В схемах классов поддерживаются следующие действия клавиатуры.
| Ключ. | Контекст | Description |
|---|---|---|
| Клавиши со стрелками | Внутри фигур типа | Древовидная навигация по содержимому фигуры (поддерживается обтекание фигуры). Клавиши влево и вправо развертывают и свертывают текущий элемент, если это возможно, перемещают пользователя к родительскому элементу, если нет (подробные сведения см. в описании дерева навигации). |
| Клавиши со стрелками | Фигуры верхнего уровня | Перемещение фигур на схеме. |
| SHIFT+клавиши со стрелками | Внутри фигур типа | Формирование непрерывного выделения, состоящего из элементов фигур, например членов, вложенных типов или секций. Следующие сочетания клавиш не поддерживают обтекание. |
| Домашняя страница | Внутри фигур типа | Переход к заголовку фигуры верхнего уровня. |
| Домашняя страница | Фигуры верхнего уровня | Переход к первой фигуре на схеме. |
| Окончание | Внутри фигур типа | Переход к последнему отображаемому элементу внутри фигуры. |
| Окончание | Фигуры верхнего уровня | Переход к последней фигуре на схеме. |
| SHIFT+HOME | Внутри фигуры типа | Выбор элементов в фигуре, начиная с текущего и заканчивая верхним элементом той же фигуры. |
| SHIFT+END | Внутри фигуры типа | То же, что и SHIFT+HOME, но в направлении сверху вниз. |
| Введите | Все контексты | Вызывает действие по умолчанию для фигуры, которое также доступно при двойном щелчке мыши. В большинстве случаев это представление кода, но некоторые элементы определяют его по-разному (интерфейсы без описания операций, заголовки секции, метки без описания операций). |
| + и —. | Все контексты | Если элемент в фокусе можно развернуть, эти клавиши развертывают или сворачивают элемент. |
| > | Все контексты | Для элементов с дочерними элементами развертывает элемент, если он свернут, и переходит к первому дочернему элементу. |
| Все контексты | Переход к родительскому элементу. | |
| ALT+SHIFT+L | Внутри фигур типа и на фигурах типа. | Переход к интерфейсу без описания операций текущей выбранной фигуры, если он существует. |
| ALT+SHIFT+В | Внутри фигур типа и на фигурах типа. | Если список базовых типов отображается на фигуре типа и содержит более одного элемента, это сочетание переключает состояние развертывания списка (развернут или свернут). |
| Удаление | На фигурах типа и комментария | Вызывает команду Удалить из схемы |
| Удаление | В любом другом месте. | Вызывает команду Удалить из кода (члены, параметры, сопоставления, наследование, метки без описания операций) |
| Ctrl+Delete | Все контексты | Вызывает команду Удалить из кода для выделенного элемента |
| Вкладка | Все контексты | Переход к следующему дочернему элементу того же родительского элемента (поддерживает обтекание). |
| Shift+Tab | Все контексты | Переход к предыдущему дочернему элементу того же родительского элемента (поддерживает обтекание). |
| ПРОБЕЛ | Все контексты | Переключает выбор в текущем элементе. |
Использование клавиатуры в окне «Сведения о классах»
Следующие ключевые привязки имитируют интерфейс ввода кода.
Используйте следующие клавиши для перемещения в окне Сведения о классах.
Если курсор находится в поле «Сводка», ввод пробела добавляет символ пробела.
Раздел 7. Основы работа с графикой на С# в Visual Studio 2005/2008
Аннотация: Весь изложенный материал, без особого труда, можно найти в MSDN и сети, но для большинства статей характерен уход в вопросы, которые затрудняют создание четкого и ясного представления о том, как работать с графикой. При решении корпоративной задачи вывода графиков в Web приложении, автору сначала потребовалось обобщить материал, и, лишь после того, приступить собственно к выполнению задания. Материалы этого обобщения и приводятся в данной статье.
PS: Материал первоначально касался только отображения графики на Web страницах, но поступающие от читателей вопросы по использованию графики в Windows стали причиной дополнения данного материала еще одной главой, которую, в силу методических соображений, автор поместил первой.
Глава 1. Использование графики в Windows приложениях
Параграф 1. Где и как возможно отображать графическую информацию в Windows приложениях
В Visual Studio Net нет стандартных средств для создания графиков, диаграмм. Поэтому, большинство статей на рассматриваемую тему рекомендуют использовать Office Web Components (OWC) — компонент Windows для построения диаграмм в Web. Как достоинство этого подхода обычно отмечается простота построения графиков и диаграмм и интуитивно близкие задания параметров графической информации с их заданием при использовании графики в MS Office. Но простота не всегда достоинство, когда мы строим график «под себя». Да и достаточно для программиста сомнительна, когда он однажды создав код графического класса, он всегда легко и быстро может его перенастроить его для конкретной задачи, не влезая в дебри некого универсального кода, где порой гораздо больше времени будет затрачено на уяснение параметров и задание их значений и границ. Поэтому, речь далее пойдет о создании собственных графиков с «чистого листа». Основные типы графиков, которые будут рассмотрены, по мнению автора, могут стать достаточными для большинства практических задач и базой для дальнейших собственных разработок.
В Microsoft Windows существует несколько средств для вывода графической информации, включая DirectDraw, OpenGL, GDI и т.д. Мы будем использовать Graphics Device Interface (GDI, более поздние версии GDI+) — подсистему Windows, ответственную за вывод графики и текста на дисплей и принтер. Именно CGI+ обеспечивает вывод на экран всего того, что видит пользователь Windows в окне монитора. GDI+ является базовым способом вывода графики в Windows.
CGI+ — это библиотека функций и программных средств на базе API, позволяющая приложениям абстрагироваться от особенностей конкретного графического оборудования. .Net Framework использует расширенный графический интерфейс GDI+.
С графикой Windows (при использованием GDI+) связано понятия контекста устройства (device context, DC). Это структура данных, содержащая информацию о параметрах и атрибутах вывода графики на устройство (дисплей, принтер. ). .Net Framework освобождает программиста от необходимости обращения к DC. Основной объект для графического отображения Graphics — класс имитации поверхности рисования в GDI+. Класс не имеет конструкторов по причине того, что объекты этого класса зависят от контекста конкретных устройств вывода. Создаются объекты специальными методами разных классов, например, он может быть создан из объекта Bitmap, или к нему можно получить доступ, как к некоторому объекту, инкапсулированному в некоторые контролы, в том числе, и в объект формы приложения. Метод CreateGraphics класса Control — наследника класса Form — возвращает объект, ассоциированный с выводом графики на форму.
Рассмотрим простейшие примеры. Создадим решение Windows приложения с одной кнопкой и следующим обработчиком ее нажатия:
private void button1_Click(object sender, EventArgs e) < //Создаем битовую матрицу Bitmap bitmap = new Bitmap(ClientSize.Width, ClientSize.Height); //Создаем объект класса Graphics на основе битовой матрицы Graphics graph = Graphics.FromImage(bitmap); //Рисуем линию на поверхности, Graphics использует в качестве нее //битовую матрицу graph.DrawLine(new Pen(Color.Red,2), 0, 0, ClientSize.Width, ClientSize.Height); //Присваиваем нарисованноу свойству формы BackgroundImage = bitmap; graph.Dispose(); >
Результат вывода при нажатии кнопки 1, показан на Рис.1:
Рис.1. Создание и использование объект класса Graphics на основе битовой матрицы
Одинакового эффекта (Рис.1.), можно добиться, если использовать обработчики некоторых событий, которым передается объект класса Graphics как аргумент (например, обработчик события Paint формы приложения):
private void Form1_Paint(object sender, PaintEventArgs e)
Одинакового эффекта (Рис.1.), можно добиться и при непосредственном создании объекта Graphics:
private void button1_Click(object sender, EventArgs e)
А так можно рисовать (писать) на кнопке и на других контролах, для которых может быть создан обработчик события Paint (Рис.2.):
private void button1_Paint(object sender, PaintEventArgs e)
Класс Graphics находятся в пространстве имен Drawing (хотя, забегая вперед, нам понадобится и пространство имен Drawing.Drawing2D, по сему, целесообразно сразу добавить их в решение).
using System.Drawing; using System.Drawing.Drawing2D;
SolidBrush — сплошная закраска цветом кисти;
TextureBrush — наложение картинки (image) на область закраски;
HatchBrush — закраска области предопределенным узором;
LinearGradientBrush — сплошная закраска c переходом цвета кисти (градиентная закраска);
Пример использования конструктора HatchBrush показан на Рис.2.:
private void button1_Click(object sender, EventArgs e)
Рис.2. Пример использования конструктора HatchBrush
Как видно из приведенных примеров, существует несколько способов отображение графики. Основное их отличие — при использовании битовой матрицы и свойств контролов изображение рисуется один раз и не исчезает (нет необходимости его вновь рисовать) при перерисовки формы.
Здесь нет необходимости подробно описывать все методы объекта Graphics, их можно легко увидеть из контекстной подсказки, как обычно, поставив точку после написания имени объекта. По этой же причине нет необходимости перечислять и преопределенные цвета карандашей (Color.) и кистей (Brushes.)
Круговые фигуры рисуются при указании в качестве стартовой точки левого верхнего угла (см. последний пример);
Рисование непосредственно на форме не всегда является и не есть хороший тон, кода Visual Studio предлагает специальный контрол, который, как нельзя лучше, подходит для вывода графической информации и обладает всеми преимуществами контролов: программное позиционирование и масштабирование без перерисовки формы, возможность выполнять Stretch Image и множество других полезных свойств и событий. Кроме того простой доступ к Graphics, аналогично через событии Paint и возможность использования битовых карт Bitmap для создания объекта Graphics, с последующим переносом их в контрол (аналогично, как мы делали это в первом примере):
pictureBox1.Image = bitmap;
Далее мы будем использовать именно PictureBox, как объект для отображения графической информации.
Параграф 2. Создание линейных графиков
2.1. В качестве постановки задачи
Однажды мне пришлось делать задачку, на базе продолжения работы над которой появилась программа «LitFrequencyMeter» — программа определения частоты повторения слов и знаков в литературных произведениях, графики из которой приведены ниже (Рис.3-5.). И когда я приступил к заключительному этапу работы над программой, то понял, что материал главы практически написан в кодах — осталось только его озвучить.
Настройка всего и вся: цветов, шрифтов, фонов, надписей, положения диаграмм на холсте, использование пояснений, легенд, смена числа отсчетов и т.д., и т.п.
Хранение всех настроек (включая шрифты) в реестре.
В данной статье подробно рассмотрен первый аспект — как строить графики, приведенные ниже. Чтобы было понятно, что отображено на графиках — приведу полный текст отрывка (надеюсь, он всем знаком с детства):
сказка о царе салтане , о сыне его славном и могучем богатыре князе гвидоне салтановиче и о прекрасной царевне лебеди три девицы под окном пряли поздно вечерком . " кабы я была царица , - говорит одна девица , - то на весь крещеный мир приготовила б я пир " . - " кабы я была царица , - говорит ее сестрица , - то на весь бы мир одна наткала я полотна " . - " кабы я была царица , - третья молвила сестрица , - я б для батюшки - царя родила богатыря " . только вымолвить успела , дверь тихонько заскрыпела , и в светлицу входит царь , стороны той государь .
Далее результаты анализа в графическом виде (программа представляет и текстовый вариант анализа, но он нам на данном этапе не нужен).
Рис.3. Линейеая диаграмма
Рис.5. Круговая диаграмма
2.2. Постановка задачи
Стоит задача создать класс для отображения графической информации, который бы мог стать базовым классом для работы с графикой, позволял бы не только выводить различные виды графиков и обладал бы гибкостью настройки форм отображения, но и оставался открытым для дальнейшего его расширения.
Конечная цель — помещение созданного графического изображения в элемент управления PictureBox.
2.3. Исходные данные
Исходные данные перед их отображением могут находиться где угодно (файл, таблица базы данных. ). Однако рассматривать чтения из базы данных или из файла значений графиков — только засорять отображение материала. Мы всегда можем прочитать данные с любого источника в массив значений. Автор предпочитает работать со строковым массивом, как позволяющим хранить цифровые и текстовые значения. В примерах, приводимых ниже, используется массив строк string[,] rgsValues. В программе, о которой шла выше речь, этот массив использован для настройки параметров, отображаемых на графике. Заполнять массив будем с помощью датчиков случайных чисел:
private int viNumInRg=20;//20 - начальное значение private string[,] rgsValues=null; private int iCreateRg() < Random rnd = new Random(DateTime.Now.Millisecond); Random rnd1 = new Random(DateTime.Now.Millisecond+5); rgsValues = new string[viNumInRg, 2]; for (int i = 0; i < viNumInRg; i++) < rgsValues[i, 0] = Convert.ToString(((float)(rnd.Next(0, 10) * 100) + (float)rnd1.Next(0, 99)) / (float)100); rgsValues[i, 1] = "I-" + Convert.ToString(i+1); >
Предполагается, что значение переменной, определяющий размерность массива хранится в настройках и устанавливается на этапе загрузки приложения.
2.4. Проект решения
Создадим простой проект WindowsApplication решения с любым именем (у меня graph1). Поместим на форму три кнопки, в свойствах «Текст» которых напишем соответственно: «Линейная диаграмма», «Гистонрамма» и «Круговая диаграмма». Ниже кнопок поместим контрол PictureBox. Подберем удобное для себя расположение кнопок и PictureBox (в реальных программах для размещения удобнее использовать контролы TableLayoutPanel, но сейчас нас интересует графика, а не размещение).
В окне Solutation Explorer кликаем правой кнопкой мышки на узле решения (у меня graph1) и в контекстном меню выбираем Add\New Item. В окне Templates выбираем Class, даем ему имя, например PaintCl.cs и нажимаем кнопку Add. Будет создан пустой класс.
using System; using System.Collections.Generic; using System.Text; namespace graph1 < class PaintCl < >>
Нашей задачей будет постепенное наполнение этого класса при минимуме добавления кода в основной файл кода приложения — Form1.cs.
Для начала создадим обработчик события нажатия кнопки «Линейный график» (клик мышкой на кнопке), а также обработчики для событий Load и FormClozed (первый можно кликом мышки на форме, второй через окно Properties формы — закладка Events — клик в окошечке против события FormClosed). Слегка преобразуем код, как показано ниже:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace graph1 < public partial class Form1 : Form < private int viNumButton = 0; private int viNumInRg=20;//20 - начальное значение private string[,] rgsValues=null; public Form1() < InitializeComponent(); >private void Form1_Load(object sender, EventArgs e) < //Здесь при создании реальной программы необходимо //будет предусмотреть восстановление сохраненных //параметров для приложения и графиков >private void Form1_FormClosed(object sender, FormClosedEventArgs e) < //Здесь при создании реальной программы необходимо //будет предусмотреть сохранение параметров //для приложения и графиков >#region Создание массива значений private void vCreateRg() < Random rnd = new Random(DateTime.Now.Millisecond); Random rnd1 = new Random(DateTime.Now.Millisecond+5); rgsValues = new string[viNumInRg, 2]; for (int i = 0; i < viNumInRg; i++) < rgsValues[i, 0] = Convert.ToString(((float)(rnd.Next(0, 10) * 100) + (float)rnd1.Next(0, 99)) / (float)100); rgsValues[i, 1] = "I-" + Convert.ToString(i+1); >> #endregion #region создание линейного графика private void button1_Click(object sender, EventArgs e) < viNumButton = 1; vCreateLinGr(); >private void vCreateLinGr() < //Создаем массив значений для вывода на графике vCreateRg(); >#endregion > >
Назначение переменной viNumButton, будет ясно далее. Массив значений у нас создан. Осталось нарисовать по значениям массива график, используя класс.
2.5. Конструкторы класса
Начало класса — конструктор и закрытые переменные. В классе лучше иметь несколько конструкторов. Например, таких как приведено в коде ниже. И, естественно, необходимо сразу определить основные объекты для графики: битовую матрицу, объект Graphiks, шрифт, кисть и перо (о чем мы говорили выше), а также переменные для сохранения размеров холста:
using System; using System.Collections.Generic; using System.Text; using System.Drawing; using System.Drawing.Drawing2D; namespace graph1 < class PaintCl < //Основные объекты для рисования private Bitmap bmp = null; private Graphics graph = null; private Font objFont = new Font("Arial", 8, FontStyle.Bold); private Brush objBrush = Brushes.Black; private Pen objPenLine = new Pen(Color.Black, 1); //Размеры холста private int viX = 200; private int viY = 100; #region Конструкторы //Первый public PaintCl() < >//Второй public PaintCl(int a, int b) < bmp = new Bitmap(a, b); //Создаем объект Graphics на основе битовой матрицы graph = Graphics.FromImage(bmp); //Запоминаем размеры холста viX = a; viY = b; >#endregion > >
Пустой конструктор, как правило, ничего не дает и потребует в дальнейшем введения дополнительных функций для инициализации объектов отображения графики. Для нашей цели он не удобен и мы можем его вычеркнуть (не использовать). Однако если мы, например, захотим передавать в качестве объекта для рисования некоторый рисунок, то вынуждены будем воспользоваться или новым сложным конструктором или добавить в класс всего лишь функцию передачи рисунка, так как функции инициализации объектов рисования так или иначе у нас уже будут. И, хотя, нагрузка на конструктор при инициализации основных объектов не страхует от необходимости иметь функции переопределения параметров объектов рисования, все же второй конструктом мне кажется более предпочтительным.
2.6. Создаем объект для рисования
Используем второй конструктор и создадим и инициализируем в классе сразу все объекты, которые нам необходимы:
private void vCreateLinGr() < //Создаем массив значений для вывода на графике vCreateRg(); //Создаем класс и передаем ему размер холсты PaintCl clPaint = new PaintCl(pictureBox1.Width, pictureBox1.Height); //Передадим фон холста в класс clPaint.vSetBackground(Color.Red); //Принимаем нарисованное в pictureBox pictureBox1.Image = clPaint.Bmp; >
Таким образом, нам понадобятся в классе еще две функции: установки фона холста и приема изображения из класса. Добавим их в класс:
#region Установка цвет фона диаграммы public void vSetBackground(Color bcl) < graph.Clear(bcl); >#endregion #region Доступ к переменным класса public Bitmap Bmp < get > #endregion
Выполним решение на данном этапе. Результат показан на Рис.6.:
Рис.6. Взаимодействие кода формы с кодом класса
В принципе, описанный выше код показал сам принцип создания изображения с использованием PictureBox и создаваемого нами и претендующего на универсальность класса для рисования графиков.
2.7. Рисуем оси
Добавим в классе переменные для хранения отступов от краев холста (они нам еще понадобятся не один раз).
//Отступы от краев холста private int viDeltaaxisL = 50; private int viDeltaaxisR = 50; private int viDeltaaxisH = 20;
Добавим функцию рисования осей и функции запоминания цвета и толщины осей. Функция выполняет простую задачу — рисует две линии и при необходимости стрелочки на конце осей:
#region Рисование Осей //Параметры вызоыва: отступы слева - deltaaxisL, справа - deltaaxisR, //сверху(снизу) - deltaaxisH, Цвет осей - colorpenaxis, толщина пера - widthpen, //нужны ли стрелки - fArrow (true - да) public void vDravAxis(int deltaaxisL, int deltaaxisR, int deltaaxisH, Color colorpenaxis, int widthpen, bool fArrow) < //Запоминаем отступы viDeltaaxisL = deltaaxisL; viDeltaaxisR = deltaaxisR; viDeltaaxisH = deltaaxisH; //Запоминаем цвет осей и толщину vSetPenColorLine(colorpenaxis); if (widthpen >0) vSetPenWidthLine(widthpen); //Точка начала рисования по х и y int x = deltaaxisL; int y = viY - deltaaxisH; int x1 = viX - deltaaxisR; int y1 = deltaaxisH; //Переменная определения длины стрелок int d = 0; if(fArrow) d = widthpen * 10; //Оси на d пикселей длинней для стрелок graph.DrawLine(objPenLine, x, y, x1 + d, y); graph.DrawLine(objPenLine, x, y, x, y1 - d); //Надо рисовать стрелки if (fArrow) < int a = 10 * (int)objPenLine.Width; int b = 2 * (int)objPenLine.Width; int x2 = x1 - a; int y2 = y + b; //Стрелки graph.DrawLine(objPenLine, x1 + 20, y, x2 + d, y2); y2 = y - b; graph.DrawLine(objPenLine, x1 + 20, y, x2 + d, y2); x2 = x - b; y2 = y1 + a; graph.DrawLine(objPenLine, x, y1 - d, x2, y2 - d); x2 = x + b; graph.DrawLine(objPenLine, x, y1 - d, x2, y2 - d); >> #endregion #region Карандаш, шрифт, кисть //Цвет карандаша public void vSetPenColorLine(Color pcl) < if (objPenLine == null) < objPenLine = new Pen(Color.Black, 1); >objPenLine.Color = pcl; > //Установка толщина карандаша public void vSetPenWidthLine(int penwidth) < if (objPenLine == null) < objPenLine = new Pen(Color.Black, 1); >objPenLine.Width = penwidth; > #endregion
Осталось добавить вызов функции рисования осей:
private void vCreateLinGr() < //Создаем массив значений для вывода на графике vCreateRg(); //Создаем класс и передаем ему размер холсты PaintCl clPaint = new PaintCl(pictureBox1.Width, pictureBox1.Height); //Фон холста clPaint.vSetBackground(Color.White); //Параметры вызоыва: отступы слева, справа, сверху(снизу), //Цвет осей, толщина пера, необходимость стрелок clPaint.vDravAxis(50, 50, 20, Color.Red, 2,true); //Принимаем нарисованное в pictureBox pictureBox1.Image = clPaint.Bmp ; >
В функции vDravAxis мы задали параметры непосредственно. Отметим еще раз, что все величины целесообразно иметь настраиваемыми и их значения хранить в реестре.
Рис.7 Рисование осей линейного графика
2.8. Рисуем сетку
Для рисования сетки нам потребуется: цвет и толщина пера, размер массива отображаемых значений и непосредственно функция для рисования сетки. Установку цвета и толщины пера мы уже использовали при рисовании осей, поэтому в функции vCreateLinGr() добавим вновь вызовы:
//Параметры линии для сетки clPaint.vSetPenWidthLine(1); clPaint.vSetPenColorLine(Color.Silver);
Для хранения размера массива в классе определим переменную и определим доступ к ней через свойство, а также определим функцию, рисующую сетку viMaxRg*viMaxRg клеток. Рисование сетки сводится к рисованию параллельных осям линий:
//Максимальный размер массива private int viMaxRg = 20; public int MaxRg < set < viMaxRg=value; >> #region Рисование сетки public void vDravGrid() < float x = viDeltaaxisL; float y = viY - viDeltaaxisH; float x1 = viX - viDeltaaxisR; float y1 = viDeltaaxisH; //Сдвиг линий сетки на один отсчет по Y float f = (y - y1) / (float)viMaxRg; //Рисуем горизонтальные линии for (int i = 1; i < viMaxRg + 1; i++) < graph.DrawLine(objPenLine, x, y - f * i, x1, y - f * i); >//Сдвиг линий сетки на один отсчет по X f = (x - x1) / (float)(viMaxRg - 1); //Рисуем вертикальные линии for (int i = 1; i < viMaxRg; i++) < graph.DrawLine(objPenLine, x - f * i, y, x - f * i, y1); >> #endregion
В функции vCreateLinGr() добавим код и выполним решение:
clPaint.MaxRg = 20; clPaint.vDravGrid();
Результат показан на Рис.8.:
Рис.8. Рисование сетки
2.8. Рисуем линию графика
Как мы уже делали — зададим цвет и толщину пера. Далее нам понадобятся данные из нашего массива значений непосредственно в классе. Для этого в классе определим массив и доступ к нему:
private string[,] rgsValues = null; public string[,] RgValue < set < rgsValues = value; >>
В классе создадим функцию рисования линий графика. Линии рисуются по соседним точкам массива:
#region Рисование линий графика для линейного графика public void vDrawGraphLines() < string s = string.Empty; string s1 = string.Empty; string s2 = string.Empty; float f = 0; float f1 = 0; float x1 = 0; float x = viDeltaaxisL; float y = viY - viDeltaaxisH; float x2 = 0; float fMax = float.MinValue; //Ищем максимальное значение по оси Y for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; if (fMax < float.Parse(s)) fMax = float.Parse(s); >//Пикселей для рисования по оси х float fdeltax = viX - viDeltaaxisL - viDeltaaxisR; //Пикселей на одну единицу массива значения по X fdeltax = fdeltax / (float)(viMaxRg - 1); //Пикселей для рисования по оси y float fdeltay = viY - 2 * viDeltaaxisH; //Пикселей на одну единицу массива значений по Y fdeltay = fdeltay / fMax; for (int i = 0; i < viMaxRg; i++) < //Первый раз запоминаем точку старта if (i == 0) < s = rgsValues[i, 0]; s2 = rgsValues[i, 1]; f = y - (float.Parse(s) * fdeltay); x1 = x; >else < //Здесь рисуем линии s1 = rgsValues[i, 0]; f1 = y - (float.Parse(s1) * fdeltay); x2 = x + (int)(fdeltax * i); graph.DrawLine(objPenLine, x1, f, x2, f1); //Запоминаем координаты конечной точки, точки //начала следующего отрезка линии s = rgsValues[i, 0]; s2 = rgsValues[i, 1]; f = f1; x1 = x + (int)(i * fdeltax); >> > #endregion
Код vCreateLinGr() на данный момент:
private void vCreateLinGr() < //Создаем массив значений для вывода на графике vCreateRg(); //Создаем класс и передаем ему размер холста PaintCl clPaint = new PaintCl(pictureBox1.Width, pictureBox1.Height); //Фон холста clPaint.vSetBackground(Color.White); //Параметры вызоыва: отступы слева, справа, //сверху(снизу),Цвет осей, толщина пера clPaint.vDravAxis(50, 50, 30, Color.Red, 2,true); //Цвет и толщина пера clPaint.vSetPenWidthLine(1); clPaint.vSetPenColorLine(Color.Silver); clPaint.MaxRg = 20; //Рисуем сетку clPaint.vDravGrid(); //Цвет и толщина пера clPaint.vSetPenWidthLine(2); clPaint.vSetPenColorLine(Color.Green); //Передаем массив значений в класс clPaint.RgValue = rgsValues; //Рисуем линии графика clPaint.vDrawGraphLines(); //Принимаем нарисованное в pictureBox pictureBox1.Image = clPaint.Bmp; >
Результат выполнения решения на данном этапе показан на Рис.9.:
Рис.9. Рисование линий графика
2.10. Надписи на графике
Надписи можно наносить по оси Х, по оси Y и над точками линий графика. Причем иногда бывает целесообразно выполнять соседние надписи со сдвигом по оси Y. Кроме того — надписи выполняются не пером, а кистями и требуют задания шрифта. Таким образом, перед выполнением надписей надо установить в классе соответственно шрифт и кисть (Brush).
Для передачи шрифта и кисти создадим в классе свойства:
public Brush brush < set < objBrush = value; >> public Font font < set < objFont = value; >>
В функции рисования графика запишем код:
Font objFont = new Font("Arial", 12, FontStyle.Bold | FontStyle.Italic); clPaint.font = objFont; clPaint.brush = Brushes.Blue;
Для выполнения различных надписей создадим в классе несколько функций. Подробно давать пояснения нет необходимости. Здесь, как и при рисовании линий, необходимо постоянно рассчитывать дельны расстояний по осям и точки начала надписей.
#region Текст по оси X - Цифры отсчетов //Параметр: false - соседние значения без сдвига по оси Y // true - соседние значения со здвигом по оси Y public void vDrawTextAxXNumber(bool f) < //Пикселей для надписей по оси х float fdeltax = viX - viDeltaaxisL - viDeltaaxisR; //Пикселей на один отсчет fdeltax = fdeltax / (float)(viMaxRg - 1); float x = viDeltaaxisL; float y = viY - viDeltaaxisH + objPenLine.Width; for (int i = 1; i < viMaxRg + 1; i++) < if (!f || i % 2 == 0) < graph.DrawString(Convert.ToString(i), objFont, objBrush, x + (i - 1) * fdeltax, y); >else < graph.DrawString(Convert.ToString(i), objFont, objBrush, x + (i - 1) * fdeltax, y + objFont.Size); >> > #endregion #region Текст по оси X - Параметр массива //Параметр: false - соседние значения без сдвига по оси Y // true - соседние значения со здвигом по оси Y public void vDrawTextAxXValues(bool f) < string s = string.Empty; //Пикселей для надписей по оси х float fdeltax = viX - viDeltaaxisL - viDeltaaxisR; //Пикселей на один отсчет fdeltax = fdeltax / (float)(viMaxRg - 1); float x = viDeltaaxisL; float y = viY - viDeltaaxisH;// +objPenLine.Width; for (int i = 0; i < viMaxRg; i++) < if (!f || i % 2 == 0) < graph.DrawString(rgsValues[i, 1], objFont, objBrush, x + i * fdeltax, y); >else < graph.DrawString(rgsValues[i, 1], objFont, objBrush, x + i * fdeltax, y + objFont.Size); >> > #endregion #region Текст по оси Y - Значения по отсчетам сетки оси Y public void vDrawTextAxYValues() < string s = string.Empty; float f = 0; float fMax = float.MinValue; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; if (fMax < float.Parse(s)) fMax = float.Parse(s); >f = fMax / (float)(viMaxRg - 1); //Пикселей для надписей по оси х float fdeltay = viY - 2 * viDeltaaxisH; //Пикселей на один отсчет fdeltay = fdeltay / (float)(viMaxRg - 1); float y = viY - viDeltaaxisH - objFont.Size; for (int i = 0; i < viMaxRg; i++) < graph.DrawString(((float)(i * f)).ToString("0.00"), objFont, objBrush, viDeltaaxisL - (objFont.Size) * 5 - 5, y - i * fdeltay); >> #endregion #region Надписи - Значения над точкой //1 параметр = false - без отображения процентов, true - с отображением //2 параметр = false - без сдвига, true - со здвигом по оси Y public void vDrawTextAxYValuesPoint(bool a, bool b) < string s = string.Empty; float fMax = float.MinValue; float fSum = 0; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; fSum += float.Parse(s); if (fMax < float.Parse(s)) fMax = float.Parse(s); >//Пикселей для надписей по оси х float fdeltax = viX - viDeltaaxisL - viDeltaaxisR; //Пикселей на один отсчет по х fdeltax = fdeltax / (float)(viMaxRg - 1); float x = viDeltaaxisL; float fdeltay = viY - 2 * viDeltaaxisH; float y = viY - viDeltaaxisH - objFont.Size; //Пикселей на одну единицу fdeltay = fdeltay / fMax; float fdelta = 0; for (int i = 0; i < viMaxRg; i++) < if (a) < if (i % 2 == 0) fdelta = objFont.Size; else fdelta = 2 * objFont.Size; >else < fdelta = objFont.Size; >if (b) < graph.DrawString(rgsValues[i, 0], objFont, objBrush, x + i * fdeltax, y - (float.Parse(rgsValues[i, 0]) * fdeltay) - fdelta); >else < float fp = float.Parse(rgsValues[i, 0]); fp = (fp * 100) / fSum; graph.DrawString(rgsValues[i, 0] + "-" + fp.ToString("0.0") + "%", objFont, objBrush, x + i * fdeltax, y - (float.Parse(rgsValues[i, 0]) * fdeltay) - fdelta); >> > #endregion
Мы создали полностью код для отображения линейного графика. Все функции для управления построением и изменения внешнего вида представлены в void vCreateLinGr():
private void vCreateLinGr() < //Создаем массив значений для вывода на графике vCreateRg(); //Создаем класс и передаем ему размер холсты PaintCl clPaint = new PaintCl(pictureBox1.Width, pictureBox1.Height); //Фон холста clPaint.vSetBackground(Color.White); //Параметры вызоыва: отступы слева, справа, сверху(снизу),Цвет осей, толщина пера clPaint.vDravAxis(50, 50, 30, Color.Red, 2,true); clPaint.vSetPenWidthLine(1); clPaint.vSetPenColorLine(Color.Silver); clPaint.MaxRg = 20; clPaint.vDravGrid(); clPaint.vSetPenWidthLine(2); clPaint.vSetPenColorLine(Color.Green); clPaint.RgValue = rgsValues; clPaint.vDrawGraphLines(); Font objFont = new Font("Arial", 7, FontStyle.Bold | FontStyle.Italic); clPaint.font = objFont; clPaint.brush = Brushes.Blue; //Здесь необходимо поэксперементировать с //использованием различных надписей и изменением параметров clPaint.vDrawTextAxXNumber(false); //clPaint.vDrawTextAxXValues(true); clPaint.vDrawTextAxYValues(); clPaint.vDrawTextAxYValuesPoint(true,false); //Принимаем нарисованное в pictureBox pictureBox1.Image = clPaint.Bmp; >
Результат выполнения кода показан на Рис.10.:
Рис.10. Линейный график
Параграф 3. Создание гистограмм
Для построения гистограмм нам потребуется внести в наш класс одну новую функцию и один массив переменных Brush. Можно было воспользоваться классом SolidBrush и по датчику случайных чисел формировать цвета, но все же, более приятно смотреть гистограмму с удачно подобранными соседними цветами (каждый может выполнить подборку цветов на свой вкус).
private Brush[] br =< Brushes.LightGreen,Brushes.Chartreuse,Brushes.LimeGreen,Brushes.Green,Brushes.DarkGreen, Brushes.DarkOliveGreen,Brushes.LightPink,Brushes.LightSeaGreen,Brushes.LightCoral,Brushes.DarkCyan , Brushes.Crimson,Brushes.CornflowerBlue ,Brushes.Chocolate,Brushes.CadetBlue,Brushes.BlueViolet, Brushes.Maroon, Brushes.Blue,Brushes.Brown,Brushes.DarkBlue, Brushes.Red, Brushes.Coral,Brushes.DarkRed, Brushes.DarkMagenta, Brushes.DarkOrange,Brushes.DarkOrchid>;
И так, нам понадобится всего лишь одна новая функция. Основное отличие — использование функции FillRectangle.
#region Рисование Гистограммы //Параметры: a=0 без сдвига цвета a=1 со сдвигом //b = 0 - без разрыва столбиков > 1 - с разрывом и величина разрыва в % public void vDrawGraphRectangular(int a, int c) < string s = string.Empty; string s1 = string.Empty; string s2 = string.Empty; float f = 0; float x1 = 0; float x = viDeltaaxisL; float y = viY - viDeltaaxisH; float fMax = float.MinValue; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; if (fMax < float.Parse(s)) fMax = float.Parse(s); >//Пикселей для рисования по оси х float fdeltax = viX - viDeltaaxisL - viDeltaaxisR; //Пикселей на один отсчет fdeltax = fdeltax / (float)(viMaxRg - 1); //Пикселей для рисования по оси y float fdeltay = viY - 2 * viDeltaaxisH; //Пикселей на одну единицу массива значений fdeltay = fdeltay / fMax; float fdx = 0; if (c != 0) fdx = (fdeltax * c / 100) / 2; Random rand = new Random(DateTime.Now.Millisecond); int arn = rand.Next((int)br.Length); objBrush = br[arn]; for (int i = 0; i < viMaxRg - 1; i++) < s = rgsValues[i, 0]; f = float.Parse(s); x1 = x + ((float)i * fdeltax); if (a == 0) < graph.FillRectangle(objBrush, x1 + fdx, y - fdeltay * f, fdeltax - 2 * fdx, fdeltay * f); >else < int b = i % br.Length; graph.FillRectangle(br[b], x1 + fdx, y - fdeltay * f, fdeltax - 2 * fdx, fdeltay * f); >if (i == viMaxRg - 2) < int b = (i + 1) % br.Length; s = rgsValues[i + 1, 0]; f = float.Parse(s); x1 = x + ((float)(i + 1) * fdeltax); if (a == 0) < graph.FillRectangle(objBrush, x1 - 2, y - fdeltay * f, 4/*fdeltax*/, fdeltay * f); >else < graph.FillRectangle(br[b], x1 - 2, y - fdeltay * f, 4/*fdeltax*/, fdeltay * f); >> > > #endregion
Запишем код обработки нажатия кнопки 2 и выполним решение:
private void button2_Click(object sender, EventArgs e) < viNumButton = 2; vCreateRectangleDiagramm(); >private void vCreateRectangleDiagramm() < //Создаем массив значений для вывода на графике vCreateRg(); //Создаем класс и передаем ему размер холсты PaintCl clPaint = new PaintCl(pictureBox1.Width, pictureBox1.Height); //Фон холста clPaint.vSetBackground(Color.White); //Параметры вызоыва: отступы слева, справа, сверху(снизу),Цвет осей, толщина пера clPaint.vDravAxis(50, 50, 30, Color.Red, 2,true); clPaint.vSetPenWidthLine(1); clPaint.vSetPenColorLine(Color.Silver); clPaint.MaxRg = 20; clPaint.vDravGrid(); clPaint.vSetPenWidthLine(2); clPaint.vSetPenColorLine(Color.Green); clPaint.RgValue = rgsValues; //a=0 без сдвига цвета a=1 со сдвигом,b = 0 - без разрыва, >1 - с разрывом и величина разрыва в % clPaint.vDrawGraphRectangular(1, 5); Font objFont = new Font("Arial", 7, FontStyle.Bold | FontStyle.Italic); clPaint.font = objFont; clPaint.brush = Brushes.Blue; clPaint.vDrawTextAxXNumber(false); //clPaint.vDrawTextAxXValues(true); clPaint.vDrawTextAxYValues(); clPaint.vDrawTextAxYValuesPoint(true, false); //Принимаем нарисованное в pictureBox pictureBox1.Image = clPaint.Bmp; >
Цветом показано единственное отличие от кода создания линейной диаграммы. Результат работы кода приведен на Рис.11.:
Параграф 4. Круговые диаграммы и элементы 3D графики
Построение круговых диаграмм с элементами 3D графики требует несколько больших затрат по сравнению с рассмотренным выше материалом. Прежде всего, необходимо определить дополнительные переменные для величин: оси эллипса (vfDiamX, vfDiamY), центр круговой диаграммы (vfXcirc, vfYcirc). Кроме того, если мы хотим, что бы в легенде (пояснению к графику) цвета надписей соответствовали цветам секторов диаграммы, то потребуется задать массив цветов однозначно соответствующий массиву цветов кистей. Зададим в классе:
private float vfDiamX = 100; private float vfDiamY = 100; private float vfXcirc = 100; private float vfYcirc = 100; private Color[] color =< Color.LightGreen,Color.Chartreuse,Color.LimeGreen,Color.Green,Color.DarkGreen, Color.DarkOliveGreen,Color.LightPink ,Color.LightSeaGreen,Color.LightCoral ,Color.DarkCyan , Color.Crimson , Color.CornflowerBlue ,Color.Chocolate,Color.CadetBlue,Color.BlueViolet, Color.Maroon,Color.Blue,Color.Brown,Color.DarkBlue, Color.Red, Color.Coral,Color.DarkRed, Color.DarkMagenta, Color.DarkOrange,Color.DarkOrchid>;
Основная функция для рисования диаграммы имеет ряд особенностей, связанных с формированием объемности и расположением надписей.
Первый этап: Против часовой стрелки рисуем последний сектор и сектора, выходящие за границу 180 градусов, но не заходящие за границу 270 градусов. Рисовать будем кистью с прозрачностью, например 25%, и каждый из них со сдвигом на 1 пиксель вниз. Иначе, если толщина диаграммы задана 20 пикселей, то сектор потребуется нарисовать 20 раз, каждый раз сдвигая на 1 пиксель вниз (Рис.12.1.).
Рис.12.1. Первый этап создания круговой диаграммы
Второй этап: Накладываем на данную диаграмму сектора от 0 градусов до сектора, заходящего за 270 градусов, используя SolidBrush и не выполняя сдвиг — рисуем каждый сектор один раз из точки рисования всей диаграммы (Рис.12.2.).
Рис.12.2. Второй этап создания круговой диаграммы
Третий этап: по часовой стрелки рисуем сектора, начиная со второго, используя HatchBrush. Рисование выполняем до сектора заходящего за границу -90 градусов, со сдвигом на толщину диаграммы (Рис.12.3.).
Рис.12.3. Третий этап создания круговой диаграммы
Четвертый этап: По часовой стрелке накладываем без сдвига, начиная со второго сектора до сектора, заходящего за границу -90 градусов, используя SolidBrush (Рис.12.4.).
Рис.12.4. Четвертый этап создания круговой диаграммы
Отдельно рисуем первый сектор, сначала используя HatchBrush со сдвигом на толщину диаграммы, затем накладываем сектор SolidBrush без сдвига. Координаты сектора определяем с учетом параметров сдвига секторов (Рис.12.5.).
Алгоритм рисования можно упростить, например, один раз и последним этапом наложить сектора эллипса, нарисованный кистью SolidBrush, но, в этом случае пострадает наглядность.
Эти этапы рисования выполняет следующая функция:
#region vDravCircle3D //Параметры - Отступ от краев по X слева deltaaxisL, от краев по Y справа deltaaxisR, //deltaaxisH - отступа сверху и снизу, толщина диаграммы viH, сдвиг сектора viDx, viDy public void vDravCircle3D(int deltaaxisL, int deltaaxisR, int deltaaxisH, int viH, int viDx, int viDy) < //Запоминаем отступы viDeltaaxisL = deltaaxisL; viDeltaaxisR = deltaaxisR; viDeltaaxisH = deltaaxisH; float a = viX - (deltaaxisL + deltaaxisR); //Нужен ли выброс сектора int viMov = 1; if (viDx == 0 && viDy == 0) < viMov = 0; >//Запоминаем диаметр vfDiamX = a; vfDiamY = viY - 2 * viDeltaaxisH; //Запоминаем центр элипса vfXcirc = deltaaxisL + a / 2; vfYcirc = viY / 2; graph.SmoothingMode = SmoothingMode.AntiAlias; //Определяем сумму всех значений в массиве float fSum = 0; string s = string.Empty; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; fSum += float.Parse(s); >float f = 0; float fBSum = 0; float fDeltaGrad = (fSum / (float)360); SolidBrush objBrush = new SolidBrush(Color.Aqua); Random rand = new Random(DateTime.Now.Millisecond); float[] frgZn = new float[viMaxRg]; float[] frgSumGr = new float[viMaxRg]; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; frgZn[i] = float.Parse(s); if (i == 0) frgSumGr[i] = 0; else frgSumGr[i] = frgZn[i] + frgSumGr[i - 1]; >for (int i = viMaxRg - 1; i >= 0; i--) < if (i != viMaxRg - 1 && fBSum < 90) break; //f в градусах fBSum в градусах f = frgZn[i] / fDeltaGrad; //fBSum = frgSumGr[i] / fDeltaGrad; if (i == viMaxRg - 1) < fBSum = 360 - f; >else < fBSum -= f; >//Для цвета int j = i % br.Length; float k = f; if (f < 1) k = 1; //objBrush.Color = Color.FromArgb(rand.Next(255), rand.Next(255), rand.Next(255)); if (i != 0) < if ((fBSum >90 && fBSum < 180) || i == viMaxRg - 1) < for (int d = 0; d < viH; d++) < //Этап 1 graph.FillPie(new HatchBrush(HatchStyle.Percent25, color[j]/*objBrush.Color*/), vfXcirc - a / 2, vfYcirc - vfDiamY / 2 + d, vfDiamX, vfDiamY, fBSum, k); >> objBrush.Color = color[j]; //Этап 2 graph.FillPie(objBrush, vfXcirc - a / 2, vfYcirc - vfDiamY / 2, vfDiamX, vfDiamY, fBSum, k); > > fBSum = 0; for (int i = viMov; i < viMaxRg; i++) < //f в градусах fBSum в градусах f = frgZn[i] / fDeltaGrad; if (i == 1) < fBSum = frgZn[0] / fDeltaGrad; >//Для цвета int j = i % br.Length; float k = f; if (f < 1) k = 1; if (fBSum < 90) < for (int d = 0; d < viH; d++) < //Этап 3 graph.FillPie(new HatchBrush(HatchStyle.Percent25, color[j]), vfXcirc - a / 2, vfYcirc - vfDiamY / 2 + d, vfDiamX, vfDiamY, fBSum, k); >objBrush.Color = color[j]; //Этап 4 graph.FillPie(objBrush, vfXcirc - a / 2, vfYcirc - vfDiamY / 2, vfDiamX, vfDiamY, fBSum, k); > else < break; >fBSum += f; > //Рисуем сдвинутым первый сектор //Этап 5 if (viMov == 1) < f = frgZn[0] / fDeltaGrad; fBSum = 0; float k1 = f; if (f < 1) k1 = 1; for (int d = 0; d < viH; d++) < graph.FillPie(new HatchBrush(HatchStyle.Percent25, color[0]), vfXcirc - a / 2 + viDx, vfYcirc - vfDiamY / 2 + d - viDy, vfDiamX, vfDiamY, fBSum, k1); >objBrush.Color = color[0]; graph.FillPie(objBrush, vfXcirc - a / 2 + viDx, vfYcirc - vfDiamY / 2 - viDy, vfDiamX, vfDiamY, fBSum, k1); > > #endregion
Добавляем функции надписи и легенду и, в принципе, построение диаграммы закончено. Единственное, что потребуется от нас при рисовании надписей на диаграмме — это немного вспомнить начальную школу при расчете координат нанесения значений:
#region vDravTextCircle public void vDravTextCircle1(bool vfGde) < float fSum = 0; string s = string.Empty; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; fSum += float.Parse(s); >float f = 0; float fBSum = 0; float f1Radian = (float)Math.PI / 180; float fDeltaGrad = fSum / 360; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; f = float.Parse(s); //f в градусах f = f / fDeltaGrad; int j = i % br.Length; //Угол в радианах float fRad = (f + fBSum) * f1Radian; float fty = 0; float ftx = 0; float fSin = (float)Math.Sin((360 - (f / 2 + fBSum)) * f1Radian); float fCos = (float)Math.Cos((360 - (f / 2 + fBSum)) * f1Radian); float c = (float)Math.Sqrt((vfDiamX / 2 * vfDiamX / 2 * vfDiamY / 2 * vfDiamY / 2) / (vfDiamY / 2 * vfDiamY / 2 * fCos * fCos + vfDiamX / 2 * vfDiamX / 2 * fSin * fSin)); c -= 3 * objFont.Size; if (c < 0) c = 0; ftx = c * fCos; fty = c * fSin; ftx = vfXcirc + ftx; fty = vfYcirc - fty; if (vfGde) < graph.DrawString(Convert.ToString(i + 1), objFont, objBrush, ftx, fty); >else < graph.DrawString(rgsValues[i, 0], objFont, objBrush, ftx, fty); >fBSum += f; > > #endregion #region Текст легенды public void vDravTextKeyCircle(bool vfGde) < float fSum = 0; float f = 0; string s = string.Empty; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; fSum += float.Parse(s); >//Сдвиг от круговой диаграммы float vfSdvig = vfXcirc + vfDiamX / 2; vfSdvig += (viX - vfSdvig) / 5; //Высота места для легенды //На одну строку по высоте отводится - +1 на заголовок float vfHg = viY / (viMaxRg + 2); vSetFont("Arial", 12, true); if (viMaxRg > 100) < graph.DrawString("Легенда не может быть размещена", objFont, Brushes.DarkBlue, vfSdvig + (viX - vfSdvig) / 10, objFont.Size); >else < //Шрифт в 2 раза меньше места на строку надписи if (viMaxRg >15) < vSetFont("Arial", (vfHg / 2), true); >else < if (viMaxRg >10) < vSetFont("Arial", (vfHg / 3), true); >else < vSetFont("Arial", (vfHg / 6), true); >> if (vfGde) < graph.DrawString("Пояснения к графику", objFont, Brushes.DarkBlue, vfSdvig /*+ (viX - vfSdvig) / 10*/, objFont.Size); >else < graph.DrawString("Пояснения к графику", objFont, objBrush, vfSdvig/* + (viX - vfSdvig) / 10*/, objFont.Size); >if (viMaxRg > 15) < vSetFont("Arial", (vfHg / 2) + 1, true); >else < if (viMaxRg >10) < vSetFont("Arial", (vfHg / 4) + 1, true); >else < vSetFont("Arial", (vfHg / 7) + 1, true); >> for (int i = 0; i < rgsValues.Length / 2; i++) < Brush brTxt = null; int j = i % br.Length; if (vfGde) brTxt = br[j]; else brTxt = objBrush; graph.DrawString(Convert.ToString(i + 1), objFont, brTxt, vfSdvig, vfHg * (i + 2)); f = float.Parse(rgsValues[i, 0]); f = (f * 100) / fSum; graph.DrawString(rgsValues[i, 0], objFont, brTxt, vfSdvig + 1 * (viX - vfSdvig) / 5, vfHg * (i + 2)); graph.DrawString(f.ToString("0.0") + "%", objFont, brTxt, vfSdvig + 2 * (viX - vfSdvig) / 5, vfHg * (i + 2)); graph.DrawString(rgsValues[i, 1], objFont, brTxt, vfSdvig + 3 * (viX - vfSdvig) / 5, vfHg * (i + 2)); >> > #endregion #region Смена шрифта по секторам private void vSetFont(string name, float size, bool bold) < if (objFont != null) objFont = null; if (bold) < objFont = new Font(name, size, FontStyle.Bold); >else < objFont = new Font(name, size); >> #endregion
Оформим вызовы функций:
private void button3_Click(object sender, EventArgs e) < viNumButton = 3; vCreateCircleDiagramm(); >private void vCreateCircleDiagramm() < //Создаем массив значений для вывода на графике vCreateRg(); //Создаем класс и передаем ему размер холсты PaintCl clPaint = new PaintCl(pictureBox1.Width, pictureBox1.Height); //Фон холста clPaint.vSetBackground(Color.White); //Передаем значения массива в класс clPaint.RgValue = rgsValues; //Рисуем график. Параметры: отступ осей x слева, x справа , //y от краев холста, толщина диаграммы,вынос сектора clPaint.vDravCircle3D(20, 250, 50, 20, 20, 40); //Круговые надписи true цифры 1-20, false - значения clPaint.vDravTextCircle1(true); //false - Разноцветные надписи в легенде true - Цветом шрифта clPaint.vDravTextKeyCircle(true); //Принимаем нарисованное в pictureBox pictureBox1.Image = clPaint.Bmp; >
Отметим, что при задании толщины диаграммы, равной нулю, получим обычную эллиптическую диаграмму, а при равенстве осей Х и Y — круговую.
Результат выполнения решения показан на Рис.12.6:
Рис.12.6. Круговая диаграмма
В заключении, еще раз повторим, что все параметры целесообразно иметь настраиваемыми, что позволяет быстро подобрать приемлемый вид графического отображения для демонстрации. Целесообразно также выполнить автономную настройку диаграмм по тестовым значениям (как это сделано в программе LitFregMeter — см. Параграф 2.). Тогда мы сможем быстро подбирать параметры, например так:
//Смена фона диаграммы private void ColorBackGround_Click(object sender, EventArgs e) < if (colorDialog1.ShowDialog() == DialogResult.OK) < //Переменная objColorBackGroung задана глобально, сохраняется в реестре //при закрытии приложения и передается в класс при рисовании диаграммы //clPaint.vSetBackground(objColorBackGroung); objColorBackGroung = colorDialog1.Color; vGhangeDiagramm(); >> //Перерисовка конкретной диаграммы при настройке private void vGhangeDiagramm() < switch (viNumButton) < case 1: vCreateLinGr(); break; case 2: vCreateRectangleDiagramm(); break; case 3: vCreateCircleDiagramm(); break; >>
Параграф 5. Базовый класс для рисования графиков
В заключении главы приводим полностью класс, созданный нами. Вы его можете без труда не только использовать в своих приложениях, но и развивать дальше — включая новые типы диаграмм.
Автор будет благодарен, если кто сможет дополнить класс новыми типами графиков, и пришлет по почте. Любые интересные Ваши находки будут помещены (включены) в данный материал с указанием Вашего участия (авторства).
using System; using System.Collections.Generic; using System.Text; using System.Drawing; using System.Drawing.Drawing2D; namespace graph1 < class PaintCl < //Основные объекты для рисования private Bitmap bmp = null; private Graphics graph = null; private Font objFont = new Font("Arial", 8, FontStyle.Bold); private Brush objBrush = Brushes.Black; private Pen objPenLine = new Pen(Color.Black, 1); //Размеры холста private int viX = 200; private int viY = 100; //Параметры для рисования круговой диаграммы private float vfDiamX = 100; private float vfDiamY = 100; private float vfXcirc = 100; private float vfYcirc = 100; //Массив предопределенных цветов для отображения легенды private Color[] color =; //Массив предопределенных цветов для отображения круговой диаграммы и гистограммы private Brush[] br = ; //Массив значений для рисования графика private string[,] rgsValues = null; //Размер массива private int viMaxRg = 20; //Отступы от краев холста private int viDeltaaxisL = 50; private int viDeltaaxisR = 50; private int viDeltaaxisH = 20; #region Конструктор public PaintCl(int a, int b) < bmp = new Bitmap(a, b); graph = Graphics.FromImage(bmp); viX = a; viY = b; >#endregion #region Установка цвета фона диаграммы public void vSetBackground(Color bcl) < graph.Clear(bcl); >#endregion #region Доступ к переменным класса public Bitmap Bmp < get < return bmp; >> public int MaxRg < set < viMaxRg = value; >> public string[,] RgValue < set < rgsValues = value; >> public Font font < set < objFont = value; >> public Brush brush < set < objBrush = value; >> #endregion #region Карандаш, шрифт, кисть //Цвет карандаша public void vSetPenColorLine(Color pcl) < if (objPenLine == null) < objPenLine = new Pen(Color.Black, 1); >objPenLine.Color = pcl; > //Установка толщина карандаша public void vSetPenWidthLine(int penwidth) < if (objPenLine == null) < objPenLine = new Pen(Color.Black, 1); >objPenLine.Width = penwidth; > #endregion #region Рисование Осей //Отступ от левого края - deltaaxis, отступ от правого края - deltaaxisR, //отступ от нижнего и верхнего края - deltaaxisH, цвет оси - colorpenaxis, //толщина пера для оси - widthpen, наличие стрелок на оси - fArrow(true) public void vDravAxis(int deltaaxisL, int deltaaxisR, int deltaaxisH, Color colorpenaxis,int widthpen, bool fArrow) < //Запоминаем отступы viDeltaaxisL = deltaaxisL; viDeltaaxisR = deltaaxisR; viDeltaaxisH = deltaaxisH; //Запоминаем цвет осей и толщину vSetPenColorLine(colorpenaxis); if (widthpen >0) vSetPenWidthLine(widthpen); //Точка начала рисования по х и y int x = deltaaxisL; int y = viY - deltaaxisH; int x1 = viX - deltaaxisR; int y1 = deltaaxisH; int d = 0; if (fArrow) d = widthpen * 10; //Оси на d пикселей длинней для стрелок graph.DrawLine(objPenLine, x, y, x1 + d, y); graph.DrawLine(objPenLine, x, y, x, y1 - d); if (fArrow) < int a = 10 * (int)objPenLine.Width; int b = 2 * (int)objPenLine.Width; int x2 = x1 - a; int y2 = y + b; //Стрелки graph.DrawLine(objPenLine, x1 + 20, y, x2 + d, y2); y2 = y - b; graph.DrawLine(objPenLine, x1 + 20, y, x2 + d, y2); x2 = x - b; y2 = y1 + a; graph.DrawLine(objPenLine, x, y1 - d, x2, y2 - d); x2 = x + b; graph.DrawLine(objPenLine, x, y1 - d, x2, y2 - d); >> #endregion #region Рисование сетки public void vDravGrid() < float x = viDeltaaxisL; float y = viY - viDeltaaxisH; float x1 = viX - viDeltaaxisR; float y1 = viDeltaaxisH; float f = (y - y1) / (float)viMaxRg; for (int i = 1; i < viMaxRg + 1; i++) < graph.DrawLine(objPenLine, x, y - f * i, x1, y - f * i); >f = (x - x1) / (float)(viMaxRg - 1); for (int i = 1; i < viMaxRg; i++) < graph.DrawLine(objPenLine, x - f * i, y, x - f * i, y1); >> #endregion #region Рисование линий графика для линейного графика public void vDrawGraphLines() < string s = string.Empty; string s1 = string.Empty; string s2 = string.Empty; float f = 0; float f1 = 0; float x1 = 0; float x = viDeltaaxisL;//-objPenLine.Width; float y = viY - viDeltaaxisH;// +objPenLine.Width; float x2 = 0; float fMax = float.MinValue; //float fMin = int.MaxValue; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; if (fMax < float.Parse(s)) fMax = float.Parse(s); >//Пикселей для рисования по оси х float fdeltax = viX - viDeltaaxisL - viDeltaaxisR; //Пикселей на один отсчет fdeltax = fdeltax / (float)(viMaxRg - 1); //Пикселей для рисования по оси y float fdeltay = viY - 2 * viDeltaaxisH; //Пикселей на одну единицу массива значений fdeltay = fdeltay / fMax; for (int i = 0; i < viMaxRg; i++) < //Первый раз запоминаем точку старта if (i == 0) < s = rgsValues[i, 0]; s2 = rgsValues[i, 1]; f = y - (float.Parse(s) * fdeltay); x1 = x; >else < s1 = rgsValues[i, 0]; f1 = y - (float.Parse(s1) * fdeltay); x2 = x + (int)(fdeltax * i); graph.DrawLine(objPenLine, x1, f, x2, f1); s = rgsValues[i, 0]; s2 = rgsValues[i, 1]; f = f1; x1 = x + (int)(i * fdeltax); >> > #endregion #region Текст по оси X - цифры //Параметр = false - без сдвига по оси Y, true - со здвигом по оси Y public void vDrawTextAxXNumber(bool f) < //Пикселей для надписей по оси х float fdeltax = viX - viDeltaaxisL - viDeltaaxisR; //Пикселей на один отсчет fdeltax = fdeltax / (float)(viMaxRg - 1); float x = viDeltaaxisL; float y = viY - viDeltaaxisH + objPenLine.Width; for (int i = 1; i < viMaxRg + 1; i++) < if (!f || i % 2 == 0) < graph.DrawString(Convert.ToString(i), objFont, objBrush, x + (i - 1) * fdeltax, y); >else < graph.DrawString(Convert.ToString(i), objFont, objBrush, x + (i - 1) * fdeltax, y + objFont.Size); >> > #endregion #region Текст по оси X //Параметр = false - без сдвига по оси Y, true - со здвигом по оси Y public void vDrawTextAxXValues(bool f) < string s = string.Empty; //Пикселей для надписей по оси х float fdeltax = viX - viDeltaaxisL - viDeltaaxisR; //Пикселей на один отсчет fdeltax = fdeltax / (float)(viMaxRg - 1); float x = viDeltaaxisL; float y = viY - viDeltaaxisH;// +objPenLine.Width; for (int i = 0; i < viMaxRg; i++) < if (!f || i % 2 == 0) < graph.DrawString(rgsValues[i, 1], objFont, objBrush, x + i * fdeltax, y); >else < graph.DrawString(rgsValues[i, 1], objFont, objBrush, x + i * fdeltax, y + objFont.Size); >> > #endregion #region Текст по оси Y - Значения по сетке public void vDrawTextAxYValues() < string s = string.Empty; float f = 0; float fMax = float.MinValue; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; if (fMax < float.Parse(s)) fMax = float.Parse(s); >f = fMax / (float)(viMaxRg - 1); //Пикселей для надписей по оси х float fdeltay = viY - 2 * viDeltaaxisH; //Пикселей на один отсчет fdeltay = fdeltay / (float)(viMaxRg - 1); float y = viY - viDeltaaxisH - objFont.Size; for (int i = 0; i < viMaxRg; i++) < graph.DrawString(((float)(i * f)).ToString("0.00"), objFont, objBrush, viDeltaaxisL - (objFont.Size) * 5 - 5, y - i * fdeltay); >> #endregion //***************** #region Надписи по оси Y - Значения над точкой public void vDrawTextAxYValuesPoint(bool a, bool b) < string s = string.Empty; float fMax = float.MinValue; float fSum = 0; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; fSum += float.Parse(s); if (fMax < float.Parse(s)) fMax = float.Parse(s); >//Пикселей для надписей по оси х float fdeltax = viX - viDeltaaxisL - viDeltaaxisR; //Пикселей на один отсчет по х fdeltax = fdeltax / (float)(viMaxRg - 1); float x = viDeltaaxisL; float fdeltay = viY - 2 * viDeltaaxisH; float y = viY - viDeltaaxisH - objFont.Size; //Пикселей на одну единицу fdeltay = fdeltay / fMax; float fdelta = 0; for (int i = 0; i < viMaxRg; i++) < if (a) < if (i % 2 == 0) fdelta = objFont.Size; else fdelta = 2 * objFont.Size; >else < fdelta = objFont.Size; >if (b) < graph.DrawString(rgsValues[i, 0], objFont, objBrush, x + i * fdeltax, y - (float.Parse(rgsValues[i, 0]) * fdeltay) - fdelta); >else < float fp = float.Parse(rgsValues[i, 0]); fp = (fp * 100) / fSum; graph.DrawString(rgsValues[i, 0] + "-" + fp.ToString("0.0") + "%", objFont, objBrush, x + i * fdeltax, y - (float.Parse(rgsValues[i, 0]) * fdeltay) - fdelta); >> > #endregion #region Рисование графика для графика прямоугольниками public void vDrawGraphRectangular(int a, int c) < string s = string.Empty; string s1 = string.Empty; string s2 = string.Empty; float f = 0; float x1 = 0; float x = viDeltaaxisL; float y = viY - viDeltaaxisH; float fMax = float.MinValue; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; if (fMax < float.Parse(s)) fMax = float.Parse(s); >//Пикселей для рисования по оси х float fdeltax = viX - viDeltaaxisL - viDeltaaxisR; //Пикселей на один отсчет fdeltax = fdeltax / (float)(viMaxRg - 1); //Пикселей для рисования по оси y float fdeltay = viY - 2 * viDeltaaxisH; //Пикселей на одну единицу массива значений fdeltay = fdeltay / fMax; float fdx = 0; if (c != 0) fdx = (fdeltax * c / 100) / 2; Random rand = new Random(DateTime.Now.Millisecond); int arn = rand.Next((int)br.Length); objBrush = br[arn]; for (int i = 0; i < viMaxRg - 1; i++) < s = rgsValues[i, 0]; f = float.Parse(s); x1 = x + ((float)i * fdeltax); if (a == 0) < graph.FillRectangle(objBrush, x1 + fdx, y - fdeltay * f, fdeltax - 2 * fdx, fdeltay * f); >else < int b = i % br.Length; graph.FillRectangle(br[b], x1 + fdx, y - fdeltay * f, fdeltax - 2 * fdx, fdeltay * f); >if (i == viMaxRg - 2) < int b = (i + 1) % br.Length; s = rgsValues[i + 1, 0]; f = float.Parse(s); x1 = x + ((float)(i + 1) * fdeltax); if (a == 0) < graph.FillRectangle(objBrush, x1 - 2, y - fdeltay * f, 4/*fdeltax*/, fdeltay * f); >else < graph.FillRectangle(br[b], x1 - 2, y - fdeltay * f, 4/*fdeltax*/, fdeltay * f); >> > > #endregion #region vDravCircle3D //Параметры - Отступ от краев по X слева deltaaxisL, от краев по Y справа, //deltaaxisH - отступа сверху и снизу, толщина диаграммы viH, сдвиг сектора viDx, viDy public void vDravCircle3D(int deltaaxisL, int deltaaxisR, int deltaaxisH, int viH, int viDx, int viDy) < //Запоминаем отступы viDeltaaxisL = deltaaxisL; viDeltaaxisR = deltaaxisR; viDeltaaxisH = deltaaxisH; float a = viX - (deltaaxisL + deltaaxisR); //Нужен ли выброс сектора int viMov = 1; if (viDx == 0 && viDy == 0) < viMov = 0; >//Запоминаем диаметр vfDiamX = a; vfDiamY = viY - 2 * viDeltaaxisH; //Запоминаем центр элипса vfXcirc = deltaaxisL + a / 2; vfYcirc = viY / 2; graph.SmoothingMode = SmoothingMode.AntiAlias; float fSum = 0; string s = string.Empty; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; fSum += float.Parse(s); >float f = 0; float fBSum = 0; float fDeltaGrad = (fSum / (float)360); SolidBrush objBrush = new SolidBrush(Color.Aqua); Random rand = new Random(DateTime.Now.Millisecond); float[] frgZn = new float[viMaxRg]; float[] frgSumGr = new float[viMaxRg]; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; frgZn[i] = float.Parse(s); if (i == 0) frgSumGr[i] = 0; else frgSumGr[i] = frgZn[i] + frgSumGr[i - 1]; >//Рисуем диаграмму против часовой стрелки со штриховкой до 90градусав по часовой //Штриховка убирается каждым следующим сектором от сектора предыдущего //В первом нарисованном секторе она сохраняетсяна случай сдвига первого по часовой стрелке for (int i = viMaxRg - 1; i >= 0; i--) < if (i != viMaxRg - 1 && fBSum < 90) break; //f в градусах fBSum в градусах f = frgZn[i] / fDeltaGrad; //fBSum = frgSumGr[i] / fDeltaGrad; if (i == viMaxRg - 1) < fBSum = 360 - f; >else < fBSum -= f; >//Для цвета int j = i % br.Length; float k = f; if (f < 1) k = 1; //objBrush.Color = Color.FromArgb(rand.Next(255), rand.Next(255), rand.Next(255)); if (i != 0) < if ((fBSum >90 && fBSum < 180) || i == viMaxRg - 1) < for (int d = 0; d < viH; d++) < graph.FillPie(new HatchBrush(HatchStyle.Percent25, color[j]/*objBrush.Color*/), vfXcirc - a / 2, vfYcirc - vfDiamY / 2 + d, vfDiamX, vfDiamY, fBSum, k); >> objBrush.Color = color[j]; graph.FillPie(objBrush, vfXcirc - a / 2, vfYcirc - vfDiamY / 2, vfDiamX, vfDiamY, fBSum, k); > > //Рисуем до 90градусов без первого сегмента в случае необходимости //сдвига первого по часовой стрелке fBSum = 0; for (int i = viMov; i < viMaxRg; i++) < //f в градусах fBSum в градусах f = frgZn[i] / fDeltaGrad; if (i == 1) < fBSum = frgZn[0] / fDeltaGrad; >//Для цвета int j = i % br.Length; float k = f; if (f < 1) k = 1; if (fBSum < 90) < for (int d = 0; d < viH; d++) < graph.FillPie(new HatchBrush(HatchStyle.Percent25, color[j]), vfXcirc - a / 2, vfYcirc - vfDiamY / 2 + d, vfDiamX, vfDiamY, fBSum, k); >objBrush.Color = color[j]; graph.FillPie(objBrush, vfXcirc - a / 2, vfYcirc - vfDiamY / 2, vfDiamX, vfDiamY, fBSum, k); > else < break; >fBSum += f; > //Рисуем сдвинутый сектор при необходимости if (viMov == 1) < f = frgZn[0] / fDeltaGrad; fBSum = 0; float k1 = f; if (f < 1) k1 = 1; for (int d = 0; d < viH; d++) < graph.FillPie(new HatchBrush(HatchStyle.Percent25, color[0]), vfXcirc - a / 2 + viDx, vfYcirc - vfDiamY / 2 + d - viDy, vfDiamX, vfDiamY, fBSum, k1); >objBrush.Color = color[0]; graph.FillPie(objBrush, vfXcirc - a / 2 + viDx, vfYcirc - vfDiamY / 2 - viDy, vfDiamX, vfDiamY, fBSum, k1); > > #endregion #region vDravTextCircle public void vDravTextCircle1(bool vfGde) < float fSum = 0; string s = string.Empty; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; fSum += float.Parse(s); >float f = 0; float fBSum = 0; float f1Radian = (float)Math.PI / 180; float fDeltaGrad = fSum / 360; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; f = float.Parse(s); //f в градусах f = f / fDeltaGrad; int j = i % br.Length; //Угол в радианах float fRad = (f + fBSum) * f1Radian; float fty = 0; float ftx = 0; float fSin = (float)Math.Sin((360 - (f / 2 + fBSum)) * f1Radian); float fCos = (float)Math.Cos((360 - (f / 2 + fBSum)) * f1Radian); float c = (float)Math.Sqrt((vfDiamX / 2 * vfDiamX / 2 * vfDiamY / 2 * vfDiamY / 2) / (vfDiamY / 2 * vfDiamY / 2 * fCos * fCos + vfDiamX / 2 * vfDiamX / 2 * fSin * fSin)); c -= 3 * objFont.Size; if (c < 0) c = 0; ftx = c * fCos; fty = c * fSin; ftx = vfXcirc + ftx; fty = vfYcirc - fty; if (vfGde) < graph.DrawString(Convert.ToString(i + 1), objFont, objBrush, ftx, fty); >else < graph.DrawString(rgsValues[i, 0], objFont, objBrush, ftx, fty); >fBSum += f; > > #endregion #region Смена шрифта private void vSetFont(string name, float size, bool bold) < if (objFont != null) objFont = null; if (bold) < objFont = new Font(name, size, FontStyle.Bold); >else < objFont = new Font(name, size); >> #endregion #region Текст легенды public void vDravTextKeyCircle(bool vfGde) < float fSum = 0; float f = 0; string s = string.Empty; for (int i = 0; i < viMaxRg; i++) < s = rgsValues[i, 0]; fSum += float.Parse(s); >//Сдвиг от круговой диаграммы float vfSdvig = vfXcirc + vfDiamX / 2; vfSdvig += (viX - vfSdvig) / 5; //Высота места для легенды //На одну строку по высоте отводится - +1 на заголовок float vfHg = viY / (viMaxRg + 2); vSetFont("Arial", 12, true); if (viMaxRg > 100) < graph.DrawString("Легенда не может быть размещена", objFont, Brushes.DarkBlue, vfSdvig + (viX - vfSdvig) / 10, objFont.Size); >else < //Шрифт в 2 раза меньше места на строку надписи if (viMaxRg >15) < vSetFont("Arial", (vfHg / 2), true); >else < if (viMaxRg >10) < vSetFont("Arial", (vfHg / 3), true); >else < vSetFont("Arial", (vfHg / 6), true); >> if (vfGde) < graph.DrawString("Пояснения к графику", objFont, Brushes.DarkBlue, vfSdvig /*+ (viX - vfSdvig) / 10*/, objFont.Size); >else < graph.DrawString("Пояснения к графику", objFont, objBrush, vfSdvig/* + (viX - vfSdvig) / 10*/, objFont.Size); >if (viMaxRg > 15) < vSetFont("Arial", (vfHg / 2) + 1, true); >else < if (viMaxRg >10) < vSetFont("Arial", (vfHg / 4) + 1, true); >else < vSetFont("Arial", (vfHg / 7) + 1, true); >> for (int i = 0; i < rgsValues.Length / 2; i++) < Brush brTxt = null; int j = i % br.Length; if (vfGde) brTxt = br[j]; else brTxt = objBrush; graph.DrawString(Convert.ToString(i + 1), objFont, brTxt, vfSdvig, vfHg * (i + 2)); f = float.Parse(rgsValues[i, 0]); f = (f * 100) / fSum; graph.DrawString(rgsValues[i, 0], objFont, brTxt, vfSdvig + 1 * (viX - vfSdvig) / 5, vfHg * (i + 2)); graph.DrawString(f.ToString("0.0") + "%", objFont, brTxt, vfSdvig + 2 * (viX - vfSdvig) / 5, vfHg * (i + 2)); graph.DrawString(rgsValues[i, 1], objFont, brTxt, vfSdvig + 3 * (viX - vfSdvig) / 5, vfHg * (i + 2)); >> > #endregion > >
Молчанов Владислав 21.09.2005г. Материал переработан 14.10.2008г.
