#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; i 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 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 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() создают визуальные компоненты, способные реагировать на события и поведения других элементов.
Комментариев нет:
Отправить комментарий