В поддержку Qt

статьи в помощь разработчику

   

Главная

Статьи:

Выбор инструментов разработки

Установка MinGW

Установка Qt 4

Установка Qt 5

Сборка Qt 5

Установка Qt Creator

Проблемы Qt 4.8.3

Распараллеливание компиляции

Распараллеливание для Qt 5.0.1

Сборка отладчика GDB

Установка библиотеки Qwt

Плагин Qwt

Установка QwtPolar

Установка QwtPplot3D

Изменение палитры цветов QwtPlot3D

Конфигурация сборки по умолчанию

Сборка Qt Creator из исходников

Пример использования QwtPlot

Масштабирование QwtPlot в стиле TChart

Синхронное масштабирование

Пример использования QwtPolar

Пример использования QwtPlot3D

Редактирование QSplitter

Сборка в Ubuntu для Windows

Установка пакетов без интернета

Установка драйвера NVIDIA

Файлы:

Проект примера

Проект примера без массивов

Проект примера с загрузкой данных из файла

 

Главная > Пример использования QwtPlot3D

 

Пример использования класса SurfacePlot из библиотеки QwtPlot3D

Внимание! С Qt 5 библиотека QwtPlot3D работает только, если первая сконфигурирована с опцией -opengl desktop (см. здесь).

В настоящей статье рассматриваются приемы построения трехмерного графика с использованием класса SurfacePlot из библиотеки QwtPlot3D. Предполагается, что у Вас установлены MinGW и QwtPlot3D (инструкции по установке можно найти здесь и здесь). В отличие от QwtPolar библиотека QwtPlot3D является самостоятельной и наличия Qwt не требует. Проект примера будем разрабатывать с помощью Qt Creator, постараемся обеспечить возможность его сборки на разных платформах (Ubuntu или Windows). Для Вашего удобства готовый проект доступен по ссылке.

Для определения в проекте библиотеки QwtPlot3D, а вместе с ней и библиотеки GLU, от которой зависит первая, используем следующий файл

файл "qwtplot3d.pri"

#-------------------------------------------------
#
# QwtPlot3D Library configuration file
#
#-------------------------------------------------


PLOT3D_VER = 0.2.7
MINGW_VER = 4.6

unix {
  INCLUDEPATH += /usr/include/qwtplot3d-qt4
  LIBS += -L/usr/lib -lqwtplot3d-qt4
  INCLUDEPATH += /usr/include/GL
  LIBS += -L/usr/lib/i386-linux-gnu -lGLU
}

win32 {
  win32-x-g++ {
    QWTPLOT3D_PATH = /usr/qwtplot3d-win
    GLU_PATH = /usr/glu-win
  } else {
    QWTPLOT3D_PATH = C:/Qt/qwtplot3d-$${QWTPLOT3D_VER}
    GLU_PATH = C:/Qt/mingw-$${MINGW_VER}
    contains(MINGW_VER,^4\\.7(\\.\\d*)?) {
      GLU_PATH = $${GLU_PATH}/i686-w64-mingw32
    }
  }
  CONFIG(debug,debug|release) {
    DEBUG_SFX = d
  } else {
    DEBUG_SFX =
  }
  INCLUDEPATH += $${QWTPLOT3D_PATH}/include
  LIBS += -L$${QWTPLOT3D_PATH}/lib -lqwtplot$${DEBUG_SFX}3d
  INCLUDEPATH += $${GLU_PATH}/include/GL
  LIBS += -L$${GLU_PATH}/lib -lglu32
}

Данные файлы обеспечивают включение в проект следующих вариантов директив

# Платформа Windows  [Переключить]
INCLUDEPATH += C:/Qt/qwtplot3d-0.2.7/include
# Конфигурация сборки Выпуск (Release)  [Переключить]
LIBS += -LC:/Qt/qwtplot3d-0.2.7/lib -lqwtplot3d
# Версия MinGW 4.6  [Переключить]
INCLUDEPATH += C:/Qt/mingw-4.6/include/GL
LIBS += -LC:/Qt/mingw-4.6/lib -lglu32

Замечания. Версия библиотеки не обновлялась с 2007 года. Пути к заголовочным и библиотечным файлам соответствуют рекомендациям по установке библиотеки QwtPlot3D, описанным в посвященной этому вопросу статье. Файл "qwtplot3d.pri" предусматривает и такой нестандартный случай, как сборка проекта в Ubuntu для Windows (см. здесь), а конкретнее, в этом случае в файл проекта будут включены варианты директив

INCLUDEPATH +=/usr/qwtplot3d-win/include
# Конфигурация сборки Выпуск (Release)  [Переключить]
LIBS += -L/usr/qwtplot3d-win/lib -lqwtplot3d
INCLUDEPATH += /usr/glu-win/include/GL
LIBS += -L/usr/glu-win/lib -lglu32

Создаем в Qt Creator проект с именем demo_plot3d. В дизайнере открываем форму "mainwindow.ui", устанавливаем ее размеры 520 x 480. Помещаем на форму контейнер QFrame и присваиваем ему имя frame – в нем, собственно, и будет располагаться график. Активируем компоновщик Скомпоновать по сетке, который заставит контейнер занять все свободное пространство формы. Устанавливаем равными "1" свойства компоновщика layoutLeftMargin, layoutTopMargin, layoutRightMargin и layoutBottomMargin. Создаем пункт главного меню Вид, присваиваем ему имя menuView, в этом пункте создаем действие Восстановить с именем actionRestore. Вызываем правой кнопкой мыши контекстное меню для созданного действия и выбираем в нем пункт Перейти к слоту… > triggered(). В результате в исходном коде появится обработчик события on_actionRestore_triggered(), он нам потребуется. В конечном итоге получаем форму, изображенную на рисунке. Элемент управления компоновщиком и индикатор его активного состояния в инспекторе объектов отмечены на рисунке символом "К".

Уже в таком виде проект можно собрать, правда никакого графика в окне запущенного приложения пока не будет. График необходимо сформировать. Сначала откроем файл проекта, его содержимое сгенерировано автоматически.

файл "demo_plot3d.pro"

#-------------------------------------------------
#
# Project created by QtCreator 2012-07-15T23:40:54
#
#-------------------------------------------------

QT += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = demo_plot3d
TEMPLATE = app


SOURCES += main.cpp\
  mainwindow.cpp

HEADERS += mainwindow.h

FORMS += mainwindow.ui


# Директива, определяющая библиотеки  [Показать]


# Включение класса QFunc3D  [Показать]

Поскольку в нашем проекте планируется использование виджета SurfacePlot, то для успешной сборки следует прописать директиву, определяющую библиотеки QwtPlot3D и GLU

include(qwtplot3d.pri)

Кроме того, в папку с проектом необходимо поместить файл "qwtplot3d.pri", содержимое которого приведено выше.

Что именно будет отображаться на графике, определяет класс, который необходимо сконструировать самостоятельно. Назовем его QFunc3D, файлы этого класса необходимо добавить в перечесление файлов исходных кодов и заголовочных файлов

SOURCES += qfunc3d.cpp

HEADERS += qfunc3d.h

Класс QFunc3D должен наследовать свойства класса Qwt3D::Function и иметь в среди членов метод double operator(double x,double y), задающий кокретную функцию двух переменных вида z=f(x,y), график которой и будет отображаться.

При реализации данного класса можно использовать различные подходы, остановимся на наиболее универсальном. При создании класс QFunc3D будет получать указатели на 3 массива данных (X, Y и Z), количество элементов N в массиве X и количество элементов M в массиве Y. Понятно, что массив Z содержит M x N элементов, и, таким образом, функция z=f(x,y) вычислена на координатной сетке с такими размерами. Под размещение данных в конструкторе класса выделяется блок памяти нужного размера, а в деструкторе выделенный блок памяти освобождается. Универсальность такого подхода заключается в том, что совершенно не важно каким образом данные были получены – вычислены или, например, прочитаны из какого-либо накопителя. Ниже приводится содержимое файлов класса.

файл "qfunc3d.h"

#ifndef QFUNC3D_H
#define QFUNC3D_H

#include <qwt3d_function.h>

class QFunc3D : public Qwt3D::Function
{
public:
  QFunc3D(Qwt3D::SurfacePlot *,
    double *,double *,double *,
    int,int);
  ~QFunc3D();

  double operator(double,double);

private:
  double *xd,*yd,*zd;
  int Nx,My;
};

#endif // QFUNC3D_H

файл "qfunc3d.cpp"

#include "qfunc3d.h"

QFunc3D::QFunc3D(Qwt3D::SurfacePlot *sp,
    double *x,double *y,double *z,
    int N,int M) :
  Function(sp)
{
  xd = (double *)malloc((N+M+M*N)*sizeof(double));
  yd = xd + N;
  zd = yd + M;
  for (int k=0; k < N; k++) xd[k] = x[k];
  for (int q=0; q < M; q++)
  {
    yd[q] = y[q];
    for (int k=0; k < N; k++) zd[q*N+k] = z[q*N+k];
  }
  Nx = N; My = M;
}

QFunc3D::~QFunc3D() {
  free((void *)xd);
}

double QFunc3D::operator(double x,double y)
{
  double dx = (xd[Nx-1]-xd[0]) / (Nx-1);
  int n = floor((x-xd[0])/dx + 0.5);
  if (n < 0 || n > Nx-1) return 0;
  double dy = (yd[My-1]-yd[0]) / (My-1);
  int m = floor((y-yd[0])/dy + 0.5);
  if (m < 0 || m > My-1) return 0;
  return zd[m*Nx+n];
}

Эти файлы надо создать в папке с проектом.

Зададимся целью получить график в виде, представленном на рисунке.

Обратите внимание на координатную сетку на задней и правой гранях, сейчас ее прорисовка уместна. Но если график развернуть, то координатная сетка, будучи жестко прикрепленной к вышеназванным граням, перекроет часть графика, а это уже не будет выглядеть эстетично. Хотелось бы, чтобы при любых поворотах графика, координатная сетка прорисовывалась только на гранях, находящихся на заднем плане, и никогда не перекрывала график (как в MATLAB'е).

Для достижения желаемого эффекта можно использовать сигнал rotationChanged(double x,double y,double z). Его посылает объект класса SurfacePlot всякий раз, когда пользователь вращает график. В качестве аргументов передаются углы, определяющие поворот графика по трем осям. Требуется только написать слот для этого сигнала, в котором в зависимости от углов разрешается или запрещается прорисовка координатной сетки на каждой из 6-ти граней.

Помимо сигнала rotationChanged имеется еще несколько полезных, это vieportShiftChanged(double x,double у), сигнализирующий о том, что график сместился по горизонтали или по вертикали, scaleChanged(double x,double y,double z) – изменился относительный масштаб по какой-либо из трех осей, zoomChanged(double z) – изменился общий масштаб графика. Для наглядности создадим слоты для всех этих сигналов, которые будут отображать в панели состояния statusBar текущее положение графика.

Заглянем в исходники нашего проекта

файл "mainwindow.h"

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

// Дополнительные директивы #include  [Показать]

namespace Ui {
  class MainWindow;
}

class MainWindow : public QMainWindow {
  Q_OBJECT

public:
  MainWindow(QWidget *parent = 0);
  ~MainWindow();

private slots:
  void on_actionRestore_triggered();

// Слоты сигналов SurfacePlot  [Показать]

private:
  Ui::MainWindow *ui;

// Указатели на создаваемые объекты  [Показать]
};

#endif // MAINWINDOW_H

файл "mainwindow.cpp"

#include "mainwindow.h"
#include "ui_mainwindow.h"

// Дополнительная директива #include  [Показать]

MainWindow::MainWindow(QWidget *parent) :
  QMainWindow(parent),
  ui(new Ui::MainWindow)
{
  ui->setupUi(this);

// Инструкции создания и оформления графика  [Показать]
}

MainWindow::~MainWindow()
{
// Удаление созданных объектов  [Показать]

  delete ui;
}

void MainWindow::on_actionRestore_triggered()
{
// Инструкции действия – Восстановить  [Показать]
}

// Реализация слотов сигналов SurfacePlot  [Показать]

Поместим в заголовочный файл "mainwindow.h" директивы #include, которые нам потребуются

#include <QGridLayout>
#include <qwt3d_surfaceplot.h>
#include <QLabel>

В секции private класса MainWindow объявляем указатели на компоновщик, создаваемый график и виджеты в панели состояния

QGridLayout *grLay;
Qwt3D::SurfacePlot *surf;

QLabel *rotateLabel;
QLabel *shiftLabel;
QLabel *scaleLabel;
QLabel *zoomLabel;

Построение графика будем производить в кострукторе главного окна приложения (в файле "mainwindow.cpp"). График и компоновщик создаются командой new. По завершении работы приложения они должны быть удалены командой delete. Поэтому мы и сделали указатели на эти элементы членами класса MainWindow, чтобы иметь доступ к ним в деструкторе. Виджеты панели состояния будут удалены вместе с ней. Членами класса они сделаны, чтобы обеспечить доступ к ним в слотах сигналов.

В начало файла реализации главного окна пиложения помещаем директиву, включающую класс QFunc3D

#include "qfunc3d.h"

Сразу после строки "ui->setupUi(this);" помещаем инструкцию

ui->mainToolBar->addAction(ui->actionRestore);

Она не имеет отношения к построению графика, но поскольку панель инструментов не была удалена, надо на нее что-нибудь поместить, в данном случае на нее помещается действие Восстановить.

Создаем компоновщик, устанавливаем для него равными "1" свойства layoutLeftMargin, layoutTopMargin, layoutRightMargin и layoutBottomMargin

grLay = new QGridLayout(ui->frame);
grLay->setContentsMargins(1,1,1,1);

Создаем график в контейнере ui->frame, назначаем ему заголовок, отдаем график под опеку компоновщика

surf = new Qwt3D::SurfacePlot(ui->frame);
surf->setTitle("SurfacePlot Demo");

grLay->addWidget(surf,0,0);

Займемся подготовкой данных для графика. Он будет строиться на координатной сетке 21 x 31 точек.

const int Nd = 31;
const int Md = 21;

Объявляем массивы данных Xd, Yd и Zd. Выделяем блок памяти под размещение данных (общий для всех данных), массивы в нем располагаются последовательно так, как показано на рисунке.

В памяти элементы массива Zd располагаются построчно – сначала идут все элементы первой строки, за ними следуют элементы второй и т.д.

double *Xd = (double *)malloc((Nd+Md+Md*Nd)*sizeof(double));
double *Yd = Xd + Nd;
double *Zd = Yd + Md;

Заполняем массивы данных. X и Y принимают значения от –1.5 до 1.5, шаг приращения по X и по Y зависит от размера координатной сетки. Z является функцией Гаусса двух переменных

где параметр s задает ширину функции.

const double s = 0.5;
double hx = 3./(Nd-1);  // шаг приращения по X
double hy = 3./(Md-1);  // шаг приращения по Y
for (int n = 0; n < Nd; n++) Xd[n] = -1.5 + n*hx;
for (int m = 0; m < Md; m++)
{
  Yd[m] = -1.5 + m*hy;
  for (int n = 0; n < Nd; n++)
    Zd[m*Nd+n] = exp(-(Xd[n]*Xd[n] + Yd[m]*Yd[m])/(2*s*s));
}

Объявляем функцию для графика (экземпляр класса QFunc3D). Она вычиляется на некоторой двумерной сетке, заданной в определенной области плоскости (X,Y). Устанавливаем границы этой области, количество точек в сетке и запускаем процесс вычислений.

QFunc3D func(surf,Xd,Yd,Zd,Nd,Md);
func.setDomain(-1.5,1.5,-1.5,1.5);
func.setMesh(31,21);
func.create();

Освобождаем выделенную память

free((void *)Xd);

Задаем стиль отображения осей графика

surf->setCoordinateStyle(Qwt3D::FRAME);

Альтернативным является стиль Qwt3D::BOX – с параллелепипедом.

У графика имеется 12 осей (по одной на каждом ребре параллелепипеда), но отображаются только 3 из них, какие именно – зависит от углов поворота. Пусть все 4 оси для каждого из трех направлений X, Y и Z будут подписываться одинаково. Приведенный ниже код устанавливает надписи для всех осей, количество основных и вспомогательных делений на осях, просветы между осью и цифровыми метками, а так же просветы между метками и надписью оси.

QStringList labs;
labs << "X" << "Y" << "Z";
int num[12] = {0,1,2,0,0,0,1,1,1,2,2,2};
for (int k=0; k != (int)surf->coordinates()->axes.size(); k++)
{
  surf->coordinates()->axes[k].setLabelString(labs[num[k]]);
  surf->coordinates()->axes[k].setMajors(5);
  surf->coordinates()->axes[k].setMinors(5);
}
surf->coordinates()->adjustNumbers(6);
surf->coordinates()->adjustLabels(12);

Создаем виджеты в панели состояния для отображения текущих углов поворота, смещений и масштабных коэффициентов

rotateLabel = new QLabel(ui->statusBar);
ui->statusBar->addWidget(rotateLabel,0);
shiftLab = new QLabel(ui->statusBar);
ui->statusBar->addWidget(shiftLab,0);
scaleLab = new QLabel(ui->statusBar);
ui->statusBar->addWidget(scaleLab,0);
zoomLab = new QLabel(ui->statusBar);
ui->statusBar->addWidget(zoomLab,0);

Назначаем слоты для задействованных сигналов

connect(surf, SIGNAL(rotationChanged(double,double,double)), this, SLOT(procRotate(double,double,double)));
connect(surf, SIGNAL(rotationChanged(double,double,double)), this, SLOT(showRotate(double,double,double)));
connect(surf, SIGNAL(vieportShiftChanged(double,double)), this, SLOT(showShift(double,double)));
connect(surf, SIGNAL(scaleChanged(double,double,double)), this, SLOT(showScale(double,double,double)));
connect(surf, SIGNAL(zoomChanged(double)), this, SLOT(showZoom(double)));

В заключение конструктора вызываем обработчик действия Восстановить, которое приводит график к некоторому исходному виду, и обновляем индикацию смещения графика в панели состояния.

on_actionRestore_triggered();
showShift(0,0);

Не забываем вставить в деструктор команды удаления графика и компоновщика, созданных в конструкторе команодой new

delete surf;
delete grLay;

Теперь переходим к обработчику действия Восстановить. Как уже отмечалось, здесь график возвращается к исходному виду, а именно – устанавливаются углы поворота, ликвидируется смещение графика по горизонтали и по вертикали, устанавливаются относительные масштабы по осям и общий масштаб и производится обновление графика.

surf->setRotation(32,0,15);
surf->setViewportShift(0,0);
surf->setScale(0.8,0.9,2);
surf->setZoom(0.9);

surf->updateData();
surf->updateGL();

Займемся слотами сигналов. Чуть подробнее о слоте procRotate, обрабатывающем сигнал rotationChanged. Его назначение – определить, исходя из текущих углов поворота, какие грани находятся на заднем плане, и на них следует прорисовывать координатную сетку, а на каких гранях сетку надо убрать. Идея обработки поясняется рисунком.

Имеется вектор, характеризующий направление от наблюдателя. Он может быть выражен через углы поворота графика по трем осям

v = {cos x sin z + sin x sin y cos z, cos x cos z - sin x sin y sin z, -sin x cos y}

Ориентация каждой грани характеризуется вектором внешней нормали. Если угол между вектором направления обзора и вектором нормали острый (скалярное произведение положительное), то координатная сетка прорисовывается, грань находится на заднем плане. Если угол тупой (скалярное произведение отрицательное), то сетка должна быть убрана, т.к. грань находится на переднем плане. Для пары противополжных граней векторы внешней нормали отличаются только знаком, поэтому для них достаточно вычислить одно из скалярноых произведений и по нему выбирать, на какой грани из этой пары прорисовывать координатную сетку. Исходя из приведенных соображений, написан код реализации слота

void MainWindow::procRotate(double x,double y,double z)
{
  x *= M_PI/180;
  y *= M_PI/180;
  z *= M_PI/180;

  int gl = Qwt3D::NOSIDEGRID;

  double sp = cos(x)*sin(z) + sin(x)*sin(y)*cos(z);
  if (sp < 0) gl |= Qwt3D::LEFT;
  if (sp > 0) gl |= Qwt3D::RIGHT;

  sp = cos(x)*cos(z) - sin(x)*sin(y)*sin(z);
  if (sp < 0) gl |= Qwt3D::FRONT;
  if (sp > 0) gl |= Qwt3D::BACK;

  sp = -sin(x)*cos(y);
  if (sp < 0) gl |= Qwt3D::FLOOR;
  if (sp > 0) gl |= Qwt3D::CEIL;

  surf->coordinates()->setGridLines(true,false,gl);
}

Сначала углы переводятся в радианы, и вычисляется скалярное произведение вектора направления обзора с вектором, направленным вдоль оси X. По его знаку принимается решение на какой грани прорисовывать сетку – на левой или на правой. Аналогичные вычисления производятся для оставшихся пар граней: передней и задней (вектор нормали вдоль оси Y), нижней и верхней (вектор нормали вдоль оси Z). По результатам вычислений прорисовывается координатная сетка на выбранных гранях, причем, прорисовывать разрешено линии, соответствующие только основным делениям шкалы.

На слотах оставшихся сигналов нет нужды останавливаться подробно, поскольку в них всего лишь из переданных в аргументах значений формируется текстовая строка для индикации на соответствующем виджете в панели состояния. Ниже приведен код этих слотов.

void MainWindow::showRotate(double x,double y,double z) {
  rotateLabel->setText(
    " Angles (" +
      QString::number(x,'g',3) + ", " +
      QString::number(y,'g',3) + ", " +
      QString::number(z,'g',3) + ")  ");
}

void MainWindow::showShift(double x,double y) {
  shiftLabel->setText(
    " Shifts (" +
      QString::number(x,'g',3) + ", " +
      QString::number(y,'g',3) + ")  ");
}

void MainWindow::showScale(double x,double y,double z) {
  scaleLabel->setText(
    " Scales (" +
      QString::number(x,'g',3) + ", " +
      QString::number(y,'g',3) + ", " +
      QString::number(z,'g',3) + ")  ");
}

void MainWindow::showZoom(double z) {
  zoomLabel->setText(
    " Zoom " +
      QString::number(y,'g',3) + "  ");
}

Наконец, можем выполнить сборку проекта и запустить приложение, – должны увидеть график, изображенный на рисунке. Пользуясь левой кнопкой, колесом мыши и комбинациями клавиш <Ctrl>, <Alt> и <Shift>, можно поворачивать график, изменять масштаб как в целом, так и по отдельным осям, смещать график по горизонтали и по вертикали. Нажатие на кнопку Восстановить панели инструментов будет возвращать вид графика к исходному состоянию.

 

Рассмотренный пример можно было реализовать проще, учитывая свойства изображаемого графика. Идея состоит в том, чтобы вычислять значения функции z=f(x,y) непосредственно в классе функции. Для того чтобы отличать этот вариант класса, дадим ему название QFunc3DF, содержимое его файлов приведено ниже.

файл "qfunc3df.h"

#ifndef QFUNC3DF_H
#define QFUNC3DF_H

#include <qwt3d_function.h>

class QFunc3DF : public Qwt3D::Function
{
public:
  QFunc3DF(Qwt3D::SurfacePlot *);

  double operator(double,double);
};

#endif // QFUNC3DF_H

файл "qfunc3df.cpp"

#include "qfunc3df.h"

QFunc3DF::QFunc3DF(Qwt3D::SurfacePlot *sp) : Function(sp)
{
}

double QFunc3DF::operator(double x,double y)
{
  const double s = 0.5;
  return exp(-(x*x + y*y)/(2*s*s));
}

Соответственно, в конструкторе главного окна приложения не требуется создавать массивы данных, поскольку объявление экземпляра класса QFunc3DF теперь будет выглядеть так

QFunc3DF func(surf);

Готовый проект примера с таким подходом к построению графика можно найти здесь.

Возникает вопрос – зачем мучались? Действительно, во втором примере график строится с гораздо меньшими усилиями. Но такой подход годится только в том случае, если функция z=f(x,y) на графике может быть выражена аналитически, а это выполняется далеко не всегда. Для демонстрации этого Вам предлагается посмотреть проект примера (см. здесь), в котором массивы X и Y формируются как обычно, а данные массива Z загружаются из текстового файла. В нем используется универсальный вариант класса QwtPlot3D из первого примера. Загрузка осуществляется по имени файла с помощью функции

double *loadData(QString,int *,int *);

Она возвращает указатель на выделенный блок памяти, в который загружены данные, и определяет структуру загруженного массива – количество элементов в строке и количество строк. В остальном построение графика ничем не отличается от первого примера. Результат работы прложения представлен на рисунке.

Понятно, что ни о каком аналитическом выражении для такой зависимости речь не идет, а значит способ построения графика из второго примера в данном случае не подходит.

В последнем примере из файла считывается массив, содержащий 161 x 361 элементов, а для отображения графика задается сетка 41 x 61. Для вычисления функции при произвольных значениях x и y используется интерполяция, известная как метод ближайшего соседа. Учитывая, что количество элементов массива либо равно, либо на много больше, чем количество узлов в сетке, используемой при построении графика, применение такой интерполяции вполне оправдано. В случае же, если количество элементов массива мало по сравнению с количеством узлов в сетке, следует подумать о том, чтобы реализовать бикубическую интерполяцию.

Может потребоваться рассмотреть данные подробнее. Тогда в функции setMesh() следует указать аргументы, равные размерности массива

func.setMesh(N,M);

Но тогда на поверхности графика будет сплошная чернота от линий сетки. Дело в том, что по умолчанию задан стиль построения графика Qwt3D::FILLEDMESH – с линиями сетки. Если вставить в код инструкцию, изменяющую стиль

surf->setPlotStyle(Qwt3D::FILLED);

тогда получится изображение, на котором что-то можно рассматривать