Редактор графиков на Qt 4.8

144
10 января 2021, 21:10

Я делаю график на Qt. Пока на этапе строительства сетки. Все графические элементы рисую с помощью графического представления и сцены.

Помогите корректно построить сетку, чтобы убрать вот эти «обрывки клеток» (Простите за кривое выделение, но я думаю, видно, что у границ графика неполные клетки).

То есть программа делит сцену на nXn клеток, но полных клеток (n-1)Х(n-1), остальное идет на «обрывки» у границ графика.

Сетку строю так:

 int n =10; 
// Окантовка графика
graphicsScene->addRect(2*n,0,graphicsScene->width()-2*n-10,graphicsScene->height()-2*n);
// Отступы сетки
int gdx = graphicsScene->width()/n;
int gdy = graphicsScene->height()/n;
// Строим сетку
for (int i = 1; i<n; i++){
// Горизонтальные линии
    graphicsScene->addLine(QLineF((0), (graphicsScene->width()-gdx*i),(graphicsScene->width()), (graphicsScene->height()-gdx*i)), QPen ( Qt::gray, 1));
// Вертикальные линии
    graphicsScene->addLine(QLineF(graphicsScene->height()-gdy*i,0,(graphicsScene->width()-gdy*i), graphicsScene->width()-n), QPen ( Qt::gray, 1));
};
// Переменные графика
xmin = 0.0;
xmax = n;
dx = 0.05;
// Надписи на ОХ
for (int i =2; i<n;i++){
    graphicsScene->addText(QString::number((xmin + (i-1)*dx*10)))->setPos((graphicsScene->width()/n)*i-graphicsScene->width()/pow(n,3),graphicsScene->height()-2*n);
   }
   X = new QGraphicsTextItem ("X", 0,0);
   X->setPos (graphicsScene->width()-n, graphicsScene->height()-2*n);
   graphicsScene->addItem(X);
ZeroX = new QGraphicsTextItem ("0", 0,0);
ZeroX->setPos (2*n, graphicsScene->height()-2*n);
graphicsScene->addItem(ZeroX);
// Подписи на ОY
for (int i =0; i<n-1;i++){
    graphicsScene->addText(QString::number((n/2 - (i+1)*dx*10)))->setPos((n/5),(graphicsScene->width()/n)*i-graphicsScene->width()/pow(n,3)-n/2);
  }
 }

Вот скриншот в нормальном масштабе

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

Ну и последнее сохранять и печатать график, как картинку.

Обновление от 01.06.2019

Мне удалось выровнять клетки у начала координат. Последняя клетка по ОХ и ОУ получилась чуть больше, чем остальные. (так как использую QGraphicsScene линии образующие эту клетку рисуются первыми). Немного съехали надписи на ОХ из-за перерисовки линий.

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

Вот что вышло

Вот мой класс

 #ifndef QGRAPHICSPOINTITEM_H
 #define QGRAPHICSPOINTITEM_H
 #include <QAbstractGraphicsShapeItem>
 class QGraphicsPointItem : public QAbstractGraphicsShapeItem
 {
 QRectF boundingRect() const
 {
    qreal penHalfWidth = this->pen().widthF() / 2;
    return QRectF(  -penHalfWidth, -penHalfWidth,
                    penHalfWidth, penHalfWidth);
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, 
QWidget *widget)
{
    painter->setPen(QPen(Qt::blue, 5));
    painter->setBrush(this->brush());
    painter->drawPoint(0, 0);
}
};
#endif

Вот как добавил точки на сцену:

// График
for (int i = 0; i<10000;i++){
    QGraphicsItem *pointongraphic = new QGraphicsPointItem();
    pointongraphic->setPos(QPointF(2*n +xmin+5*n*i,(BorderRect.height()-((exp((2*sqrt(xmin+2*n*i))/(5*a))*(sin(pow (xmin+2*n*i,2)/3)*pow(atan(pow((xmin+2*n*i),(1.0/5))/4),(1.0/(bmin+db)))))))));
                   //((exp((2*sqrt(xmin+dx*i))/(5*a))*(sin(pow (xmin+dx*i,2)/3)*pow(atan(pow((xmin+dx*i),(1.0/5))/4),(1.0/(bmin+db)))))));
    graphicsScene->addItem(pointongraphic);

И скрин самой функции на всякий, вдруг кто-то захочет порыться и найдет ошибки в моей одной строке цикла (простите за этот цикл)

И собственно еще один вопрос: мне бы хотелось расширить мою сцену, так, чтобы дорисовать точки хотя бы до х = 2.0. Без сетки. Можно ли как-то это сделать?

Answer 1

Добавлю свое прямоугольное колесо в этот велосипед.

Я определил 2 класса, Chart - рисует график - и Grid - рисует сетку и разметку.

Сетка:

class Grid : public QGraphicsItem
{
public:
    Grid(QGraphicsItem* parent = nullptr);
    QRectF boundingRect() const;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
    // ... сеттеры/геттеры
private:
    qreal m_space;  //размер клетки
    qreal m_scaleX; //масштаб по Ох, для рисования разметки
    qreal m_scaleY; //масштаб по Оу, для рисования разметки
};

Реализация:

Grid::
Grid(QGraphicsItem *parent):
    QGraphicsItem(parent),
    m_space{30},
    m_scaleX{0.5},m_scaleY{0.5}
{ }
QRectF
Grid::
boundingRect() const
{
    return scene()->views().first()->rect();
}
void
Grid::
paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option); Q_UNUSED(widget);
    qreal w = boundingRect().width();
    qreal h = boundingRect().height();
    qreal start = -m_space*50; //чтобы при изменении масштаба сетка отрисовывалась
    //рисуем линии по вертикали
    for(auto chunk = start ; chunk <= w+qAbs(start) ; chunk+=m_space) {
        painter->save();
        painter->setPen(QPen(QBrush(QColor(Qt::gray)), 0.3, Qt::PenStyle::SolidLine));
        painter->drawLine(QLineF(chunk, start, chunk, h+qAbs(start)));
        painter->restore();
    }
    //рисуем линии по горизонтали
    for(auto chunk = start ; chunk <= h+qAbs(start) ; chunk+=m_space) {
        painter->save();
        painter->setPen(QPen(QBrush(QColor(Qt::gray)), 0.3, Qt::PenStyle::SolidLine));
        painter->drawLine(QLineF(start, chunk, w+qAbs(start), chunk));
        painter->restore();
    }
    painter->save();
    //меняем начало координат
    painter->translate(m_space, (int)(h/m_space-1)*m_space);
    //Рисуем оси Оx и Оy
    painter->drawLine(QLineF(0,0, 0,-h));
    painter->drawLine(QLineF(0,0, w, 0));
    painter->drawText(-10,10, QString::number(0));
    //Рисуем разметку для осей Оx и Оy
    qreal scX = m_scaleX;
    for(auto chunk = m_space ; chunk <= w ; chunk+=m_space, scX+=m_scaleX) {
        painter->drawLine(chunk,-1, static_cast<qreal>(chunk),1);
        painter->drawText(chunk-7, 10, QString::number(scX, 'g', 3));
    }
    qreal scY = m_scaleY;
    for(auto chunk = -m_space ; chunk >= -w ; chunk-=m_space, scY+=m_scaleY) {
        painter->drawLine(-1, chunk,1, static_cast<qreal>(chunk));
        painter->drawText(-23, chunk, QString::number(scY, 'g', 3));
    }
    painter->restore();
}

График:

class Chart : public QGraphicsItem
{
public:
    Chart(QGraphicsItem* parent = nullptr);
    QRectF boundingRect() const;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
    //... сеттеры/геттеры  
protected:
    qreal f_x(qreal x) const;      //реализация функции
    qreal scaleX(qreal x) const;   //масштабирование относительно клетки по Ох
    qreal scaleY(qreal y) const;   //масштабирование относительно клетки по Оу
private:
    qreal m_a;      //параметр а
    qreal m_b;      //параметр b
    qreal m_space;  //размер клетки, для масштабирования
    qreal m_scaleX; //масштаб по Ох, для масштабирования
    qreal m_scaleY; //масштаб по Оу, для масштабирования
};

И реализация

Chart::
Chart(QGraphicsItem* parent):
    QGraphicsItem(parent),
    m_a{5},m_b{200},
    m_space{30},
    m_scaleX{0.5},m_scaleY{0.5}
{
}
QRectF
Chart::
boundingRect() const
{
    return scene()->views().first()->rect();
}
void
Chart::
paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option); Q_UNUSED(widget);
    painter->save();
    //меняем точку начала координат
    painter->translate(m_space, (int)(boundingRect().height()/m_space-1)*m_space);
    //ширина и цвет линии графика
    painter->setPen(QPen(QColor(Qt::red), 0.7));
    //размечаем точки для графика
    QPainterPath path(QPointF(0.0, 0.0));
    for(qreal x = 0.0 ; x < 15 ; x+=0.05) {
        path.lineTo(scaleX(x), scaleY(f_x(x))  );
    }
    //полупрозрачная закраска графика
    painter->setBrush(QBrush(QColor(128,128,0,128)));
    painter->drawPath(path);
    painter->restore();
}
qreal
Chart::
f_x(qreal x) const
{
    return qExp(-2*qSqrt(x)/(5*m_a)) * qSin(qPow(x,2.0)/3) * (qAtan(-qPow(qPow(x,0.2)/4, 1/m_b)) + M_PI_2);
}
qreal
Chart::
scaleX(qreal x) const
{
    return m_space * x / m_scaleX;
}
qreal
Chart::
scaleY(qreal y) const
{
    return m_space * y / m_scaleY;
}

На главном окне переопределяем виртуальную функцию eventFilter для масштабирования сцены при прокрутке колёсика мышки:

bool 
MainWindow::
eventFilter(QObject *watched, QEvent *event)
{
    if(watched->objectName() == "scene") {
        if(event->type() == QEvent::GraphicsSceneWheel) {
            QGraphicsSceneWheelEvent* wevent = static_cast<QGraphicsSceneWheelEvent*>(event);
            //изменяем масштаб
            ui->gv->scale(wevent->delta() < 0 ? 0.9 : 1.1, wevent->delta() < 0 ? 0.9 : 1.1);
            //центрируем в точке позиции мыши на сцене
            if(wevent->delta() < 0) ui->gv->centerOn(wevent->scenePos());
            scene.update();
            event->accept();
            return true;
        }
    }
    return false;
}

ну и в конструкторе главного окна добавить

scene.setObjectName("scene"); //scene объявлен в MainWindow
ui->gv->setScene(&scene);     //QGraphicsView на ui
Grid* grid = new Grid;
Chart* chart = new Chart;
scene.addItem(grid);
scene.addItem(chart);
// устанавливаем фильтр событий для перехвата событий колёсика
scene.installEventFilter(this); 
//изменение значений из ui
connect(ui->scaleX, &QLineEdit::textChanged, [=]() {
    bool ok = true;
    chart->setScaleX(ui->x->text().toDouble(&ok));
    grid->setScaleX(ui->y->text().toDouble(&ok));
    Q_ASSERT(ok);
    scene.update();
});
//... для a, b, scaleY, ...

Получилось примерно следующее:

Answer 2

Вы просто не с того конца строите линии сетки. Надо идти от осей вправо и вверх, соответвенно.

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

Вот ее официальный сайт

А тут легкое введение на хабре

А то знаете, вы только с сеткой занялись, а уже такие проблемы. Оно вам надо? )

Answer 3

Есть способ проще. Хотя и сомнительный. Переопределить либо в QGraphicsScene либо QGraphicsView метод drawBackground. Фон нарисовать отдельно, и в случае чего делать ему resize. При вашем подходе, если линий станет достаточно много, скорость упадет заметно.

QImage* background = new QImage("путь_до_изображения");
void GraphicsScene::drawBackground(QPainter *painter, const QRectF &)
{
    for(int x =0 ; x <  height(); x+=background->size().height())
        for(int y=0; y < width(); y+=background->size().width())
        {
            painter->drawImage( x,y, *background );
        }
}

В этом примере background заполняет всю сцену своими копиями.

Масштаб меняется при помощи метода scale, класса QGraphicsView.

Количество клеток путем, увеличения сцены. setSceneRect(QRectF);

Изменения стиля, путем замены изображения бэкграунда.

А сохранить можно так:

QImage image(scene->width(), scene->height(),QImage::Format_ARGB32_Premultiplied);
QPainter painter(&image);
scene->render(&painter);
image.save("/somepath/result.png");

Вот что получилось. https://github.com/NikitaDotIvanov64/stackoverflow

Answer 4

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

READ ALSO
Как создать проект для работы с win 32 api

Как создать проект для работы с win 32 api

Хочу отметить, что у меня Visual Studio 2019, пакет v142Как мне создать проект для работы с win 32 api

136
Компиляция со свойствами /MD /MT

Компиляция со свойствами /MD /MT

У меня есть проект, в которой я подтащил несколько либ, их компилял из сорсов самВпоследствии возникли ошибки линковки и это было связано...

137
Запись в вектор объектов класса из файла [закрыт]

Запись в вектор объектов класса из файла [закрыт]

Хотите улучшить этот вопрос? Обновите вопрос так, чтобы он вписывался в тематику Stack Overflow на русском

96
Как при открытии файла получить ссылку на него?

Как при открытии файла получить ссылку на него?

У меня в проекте на C++ есть возможность сохранять изменённые данные в файл, но есть проблема, когда я открываю не саму программу, а ее файл,...

119