C++ .NET: делаем 3D график с помощью Chart
Вот здесь есть все основные примеры для работы со стандартной (начиная с версии .Net Framework 4.0) компонентой Chart
, служащей для построения графиков и диаграмм. Но все эти примеры - "плоские". А можно ли отобразить трёхмерные данные, пользуясь только возможностями Chart
?
Коль скоро каждая из областей (ChartAreas
) графика может содержать сколько угодно рядов данных (Series
), а каждый ряд - сколько угодно точек (Points
), то можно. Тем более, для любой области предусмотрена опция Area3DStyle->Enable3D
, позволяющая включить трёхмерный вид её рядов данных.
Создадим пустой проект Windows Forms, кинем как попало на форму компонент Chart
, всё остальное сделаем программно по любому удобному событию, например, по загрузке (Load
) формы.
Сначала растянем Chart
по форме и включим некоторые полезные свойства:
using namespace System::Windows::Forms::DataVisualization::Charting; chart1->Dock = DockStyle::Fill; //растянуть на всё окно chart1->Legends["Legend1"]->Enabled = false; //убрать легенду chart1->ChartAreas["ChartArea1"]->Area3DStyle->Enable3D = true; //включить вид 3D chart1->ChartAreas["ChartArea1"]->AxisX->Enabled = AxisEnabled::False; //отключить отображение chart1->ChartAreas["ChartArea1"]->AxisY->Enabled = AxisEnabled::False; //цифр на осях chart1->ChartAreas["ChartArea1"]->Area3DStyle->Inclination = 30; //угол поворота гор.осей, по умолчанию
Зададим минимально нужные настройки:
const int n=50; //Количество узлов сетки по осям X и Y const double c = -5, d = 5; //границы по осям X, Y
Наконец, создадим ряды и заполним их точками-значениями:
chart1->Series->RemoveAt(0); //Удалить ряд, созданный по умолчанию for (int i=0; i<n; i++) { //Создать n рядов Series ^Series1 = gcnew Series(); Series1->ChartType = SeriesChartType::Column; //Series1->Color = Color::Blue; /*Раскомментарьте строку выше, чтобы вывести все ряды одним цветом*/ chart1->Series->Add(Series1); for (int j=0; j<n; j++) { //Создать n точек в каждом ряде double xPoint = c + i * (d-c) / (n-1); double yPoint = c + j * (d-c) / (n-1); chart1->Series[i]->Points->AddXY ((double)j,f(xPoint,yPoint)); } }
Функция f(x,y)
- любая функция двух переменных, график которой мы выводим, например, такая:
double f (double x, double y) { //собственно функция, график которой выводим return x*x+y*y; }
Это всё, приложение можно запускать и вот что вышло:
3D диаграмма только средствами Chart
Так как компонента "тянется" по форме, при изменении размеров окна всё пересчитается и перерисуется. Если "подложка" пол графиком не нужна, выберите тип диаграммы, например, SeriesChartType::StepLine
вместо SeriesChartType::Column
.
У нас отображаемые в 3D величины берутся как значения функции двух аргументов f(x,y)
, если же они уже "лежат" в двумерном массиве, это только упростит ситуацию - просто вместо f(xPoint,yPoint)
будем передавать в AddXY
элемент из i
-ой строки и j
-го столбца этого массива.
Можно также "поиграть" с
chart1->ChartAreas["ChartArea1"]->AxisX
,
chart1->ChartAreas["ChartArea1"]->AxisY
для детальной настройки.
Например, там есть свойства Interval
, Maximum
и Minimum
.
Управляем цветом
В предыдущем листинге закомментировано управление цветом, а часто хочется иметь вместо пёстрого "коврика" что-нибудь более логичное, например, плавный цветовой градиент.
Сменим тип диаграммы на сплайн, а цвет будем рассчитывать пропорционально значению функции.
Перед основным циклом for
придётся добавить цикл поиска минимума и максимума функции 2 переменных,
а расчёт цвета выполнить отдельным методом calculateColor
.
Вместо цикла for
предыдущего листинга будет код:
Series ^Series1; double x, y, z; double x1 = -1, y1 = -1, x2 = 1, y2 = 1; //общие границы области построения double xstep = (x2 - x1) / (n - 1), ystep = (y2 - y1) / (n - 1); //шаги double max = f(x1,y1), min = max; Color Color1; for (x = x1; x <= x2; x += xstep) for (y = y1; y <= y2; y += ystep) { z = f(x, y); if (z > max) max = z; else if (z < min) min = z; } for (int i = 0; i < n; i++) { //добавить n рядов Series1 = gcnew Series(); Series1->ChartType = SeriesChartType::Spline; Series1->Color = Color::Blue; chart1->Series->Add(Series1); for (int j = 0; j < n; j++) { //добавить n точек в каждый ряд x = x1 + i*xstep; y = y1 + j*ystep; z = f(x, y); chart1->Series[i]->Points->AddXY((double)j, z); Color1 = calculateColor(min, z, max); chart1->Series[i]->Points[j]->Color = Color1; chart1->Series[i]->Points[j]->BackSecondaryColor = Color1; } }
а метод расчёта цвета в простейшем случае можно сделать таким (интенсивность красного всегда оставляем равной 255, чтобы иметь диаграмму в красных тонах):
private: Color calculateColor(double min, double z, double max) { int c = Math::Round(0. + (z - min)*(255. - 0) / (max - min)); return Color::FromArgb(255, c, c); }
3D диаграмма только средствами Chart - теперь с цветовым градиентом
Меняем свойства Chart динамически
Разумеется, нужные свойства Chart
можно менять в процессе работы программы. Например, сделаем угол поворота горизонтальных осей зависимым от расстояния между точкой клика мышью по форме и левым верхним углом клиентской части формы - чем дальше от левого верхнего угла щёлкнем, тем на больший угол повернётся диаграмма (от 0 до 90 градусов).
Понадобится обработчик события MouseClick
диаграммы:
private: System::Void chart1_MouseClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) { Point p = Point(e->X, e->Y); int d1 = distance(p, Point(0, 0)); int d0 = distance(Point(0, 0), Point(this->ClientSize.Width, this->ClientSize.Height)); int corner = Math::Round((d1+0.) / (d0+0.) * 90.); chart1->ChartAreas["ChartArea1"]->Area3DStyle->Inclination = corner; chart1->Refresh(); }
и служебная функция вычисления пиксельного расстояния между двумя точками:
private: int distance(Point p1, Point p2) { return Math::Round(Math::Pow(Math::Pow(p2.X-p1.X,2)+Math::Pow(p2.Y-p1.Y,2),0.5)); }
25.05.2016, 16:25 [11603 просмотра]