Страницы

Поиск по вопросам

понедельник, 9 декабря 2019 г.

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

#cpp #qt #график


Я делаю график на 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; iaddLine(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; iaddText(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; iaddText(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 
 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. Без сетки. Можно ли как-то это сделать?
    


Ответы

Ответ 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(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(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(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, ... Получилось примерно следующее:

Ответ 2



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

Ответ 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

Ответ 4



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

Комментариев нет:

Отправить комментарий