Я делаю график на 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. Без сетки. Можно ли как-то это сделать?
Добавлю свое прямоугольное колесо в этот велосипед.
Я определил 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, ...
Получилось примерно следующее:
Вы просто не с того конца строите линии сетки. Надо идти от осей вправо и вверх, соответвенно.
В качестве тренировки это, безусловно, хорошо создавать свой велосипед, но я все-таки, когда вам надоест, порекомендую, внешнюю к Qt, либу работы с разными графиками Qwt.
Вот ее официальный сайт
А тут легкое введение на хабре
А то знаете, вы только с сеткой занялись, а уже такие проблемы. Оно вам надо? )
Есть способ проще. Хотя и сомнительный. Переопределить либо в 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
Вычислите размеры прямоугольника который впишется в область просмотра, и разлиновывайте уже его. А вообще QGraphicScene так не насилуют, слишком графично. Нужно создать компоненты осей, надписей, сетки и линий. Все эти addRect() и addLine() создают визуальные компоненты, способные реагировать на события и поведения других элементов.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Хочу отметить, что у меня Visual Studio 2019, пакет v142Как мне создать проект для работы с win 32 api
У меня есть проект, в которой я подтащил несколько либ, их компилял из сорсов самВпоследствии возникли ошибки линковки и это было связано...
Хотите улучшить этот вопрос? Обновите вопрос так, чтобы он вписывался в тематику Stack Overflow на русском
У меня в проекте на C++ есть возможность сохранять изменённые данные в файл, но есть проблема, когда я открываю не саму программу, а ее файл,...