Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ Qt GUI _ Дополнительный виджет

Автор: ++Norton++ 4.3.2009, 23:38

Столкнулся с такой проблемой, нужно написать программу, которая будет строить определенный график, но в Qt не нашел виджета-плоттера. :(
Подскажите пожалуйста, есть ли какая-нибудь возможность решить эту проблему? Как отобразить график? Может есть какие-нибудь дополнительные виджеты?

Автор: SABROG 5.3.2009, 0:04

Это? http://qwt.sourceforge.net/class_qwt_plot.html

Автор: AD 5.3.2009, 0:19

Цитата(++Norton++ @ 4.3.2009, 23:38) *
Столкнулся с такой проблемой, нужно написать программу, которая будет строить определенный график, но в Qt не нашел виджета-плоттера. :(
Подскажите пожалуйста, есть ли какая-нибудь возможность решить эту проблему? Как отобразить график? Может есть какие-нибудь дополнительные виджеты?

Если решишь делать своими руками - помогу. Можно обойтись и без Qwt! Я эту задачу решил и знаю как делать!

Автор: ++Norton++ 5.3.2009, 0:34

SABROG, спасибо, пробовал, скачивал, только не получилось его в QtDesigner добавить :(
AD, отлично, с удовольствием хотел бы попробовать, буду очень благодарен за помощь!

Автор: AD 5.3.2009, 0:35

Цитата(++Norton++ @ 5.3.2009, 0:34) *
SABROG, спасибо, пробовал, скачивал, только не получилось его в QtDesigner добавить :(
AD, отлично, с удовольствием хотел бы попробовать, буду очень благодарен за помощь!

Хорошо. Завтра вечером кое-что из кода выложу. Извини, сегодня спать хочется и голова побаливает!

Автор: ++Norton++ 5.3.2009, 0:50

Конечно, все отлично, буду ждать! :)

Автор: Litkevich Yuriy 5.3.2009, 8:16

++Norton++, про Qwt есть отдельная тема, почитай там было обсуждение проблем со сборкой.

Автор: kwisp 5.3.2009, 9:02

Цитата(++Norton++ @ 5.3.2009, 0:34) *
скачивал, только не получилось его в QtDesigner добавить
Цитата(Litkevich Yuriy @ 5.3.2009, 8:16) *
про Qwt есть отдельная тема, почитай там было обсуждение проблем со сборкой.


http://www.forum.crossplatform.ru/index.php?showtopic=2264

Автор: Kagami 5.3.2009, 9:45

Я тоже свой виджет для построения графиков делал :)

Автор: AD 5.3.2009, 11:52

Первое. Желательно создать новый проект. Основной класс GraphicWidget, для простоты, лучше унаследовать от QWidget! Затем добавить в этот виджет еще один виджет graphWidget и сделать компоновку с помощью QGridLayout!
Далее, следует добавить новые PlotSettings.cpp и PlotSettings.h файлы.
h-файл будет выглядеть так:

#ifndef PLOTSETTINGS_H
#define PLOTSETTINGS_H

#include <QPointF>
#include <cmath>

/// Абстрактный класс для задания диапазона значений по осям x и y
class PlotSettings
{
public:
    double minX;                        ///< минимальное значение по оси абсцисс
    double maxX;                        ///< максимальное значение по оси абсцисс
    int numXTicks;                        ///< количество делений на оси абсцисс
    double minY;                        ///< минимальное значение по оси ординат
    double maxY;                        ///< максимальное значение по оси ординат
    int numYTicks;                        ///< количество делений на оси ординат

protected:
    void adjustAxis(double& min, double& max, int& numTicks);

public:
    PlotSettings();
    void scroll(double dx, double dy);
    void scale(double delta_x, double delta_y);
    void adjust();
    void adjust(QPointF& point);
    double spanX() const { return fabs(maxX - minX); }
    double spanY() const { return fabs(maxY - minY); }
};

#endif



В чем-то создание этого виджета будет пересекаться с книгой Жасмина и Бланшета по Qt4! Но будут и отличия.
Итак, создали класс, который будет отвечать за масштабирование, перемещение и сетку нашего графика.


source
PlotSettings::PlotSettings(): minX(-50.), minY(-4.), maxX(50.), maxY(45.)
{
    numXTicks = 8;
    numYTicks = 8;
}

/// Увеличение/уменьшение значения minX, maxX, minY, maxY на интервал между 2-мя отметками
void PlotSettings::scroll(double dx, double dy)
{
    double stepX = spanX() / numXTicks;
    minX += dx * stepX;
    maxX += dx * stepX;

    double stepY = spanY() / numYTicks;
    minY += dy * stepY;
    maxY += dy * stepY;

    adjust();
}

/// Увеличение/уменьшение значения minX, maxX, minY, maxY на интервал между 2-мя отметками
void PlotSettings::scale(double delta_x, double delta_y)
{
    if((minX == maxX || minY == maxY) && delta_x < 0 && delta_y < 0) return;

    double stepX = spanX() / numXTicks;
    minX -= delta_x * stepX;
    maxX += delta_x * stepX;

    double stepY = spanY() / numYTicks;
    minY -= delta_y * stepY;
    maxY += delta_y * stepY;
}

/// Округление значений minX, minY, maxX, maxY
void PlotSettings::adjust()
{
    adjustAxis(minX, maxX, numXTicks);
    adjustAxis(minY, maxY, numYTicks);
}

/// Округление значений заданной точки point
void PlotSettings::adjust(QPointF& point)
{
    double mn_x = minX, mn_y = minY;
    int ticks_x = numXTicks, ticks_y = numYTicks;
    double mx_x = point.x(), mx_y = point.y();

    adjustAxis(mn_x, mx_x, ticks_x);
    adjustAxis(mn_y, mx_y, ticks_y);

    point.setX(mx_x);
    point.setY(mx_y);
}

/// Преобразование параметров в удобные значения
void PlotSettings::adjustAxis(double& min, double&  max, int& numTicks)
{
    const int MinTicks = 5;
    double grossStep = (max - min) / MinTicks;
    double step = pow(10.0, floor(log10(grossStep)));

    if(5 * step < grossStep)
        step *= 5;
    else if(2 * step < grossStep)
        step *= 2;

    numTicks = int(ceil(max / step) - floor(min / step));        
    if(numTicks < MinTicks)
        numTicks = MinTicks;

    min = floor(min / step) * step;
    max = ceil(max / step) * step;
}

Автор: ++Norton++ 5.3.2009, 12:21

Спасибо, все понятно кроме некоторых деталей:
1) Виджет graphWidget - это имеется ввиду GraphicsView? Или я что-то путаю?
2) На втором листинге исходник PlotSettings.cpp, правильно?

Автор: AD 5.3.2009, 12:32

Цитата(++Norton++ @ 5.3.2009, 12:21) *
Спасибо, все понятно кроме некоторых деталей:
1) Виджет graphWidget - это имеется ввиду GraphicsView? Или я что-то путаю?
2) На втором листинге исходник PlotSettings.cpp, правильно?

1) Нет. Зачем же? :) Обычный QWidget!
2) Да.

Продолжим. :)
Вот так приблизительно будет выглядеть исходники основного окна:
#ifndef GRAPHICWIDGET_H
#define GRAPHICWIDGET_H

#include "ui_GraphicWidget.h"
#include "PlotSettings.h"

#include <QtGui/QWidget>

class QPaintEvent;
class QPainter;

/// Виджет, где будет отрисовываться график
class GraphicWidget: public QWidget, public Ui::GraphicWidgetClass
{
    Q_OBJECT

private:
    PlotSettings settings;        ///< настройка для различных масштабов

private:
    void drawGrid(QPainter *painter);

protected:
    void paintEvent(QPaintEvent* event);

public:
    GraphicWidget(QWidget *parent = 0, Qt::WFlags flags = 0);
    ~GraphicWidget();
    void setPlotSettings(const PlotSettings& sts) { settings = sts; settings.adjust(); update(); }
};

#endif // GRAPHICWIDGET_H



#include "GraphicWidget.h"

#include <QPainter>
#include <QRect>
#include <QString>
#include <algorithm>
using namespace std;

GraphicWidget::GraphicWidget(QWidget *parent, Qt::WFlags flags): QWidget(parent, flags)
{
    setupUi(this);

    /// Инициализация необходимых параметров
    setPlotSettings(PlotSettings());
}

GraphicWidget::~GraphicWidget()
{}

/// Отрисовка сетки
void GraphicWidget::drawGrid(QPainter *painter)
{
    QRect rect(graphWidget -> rect());
    if(!rect.isValid()) return;
    
    QRect boundString;    
    double great_max = max(settings.numXTicks, settings.numYTicks) + 1;
    for(register int i=0, j=0, k=0; i<=great_max; ++i, ++j, ++k)
    {
        if(j <= settings.numXTicks)                ///< отрисовка вдоль оси X
        {
            int x = rect.left() + (j * (rect.width() - 1) / settings.numXTicks);
            double label = settings.minX + (j * settings.spanX() / settings.numXTicks);
            QString s_label(QString::number(label, 'f', 3));

            painter -> setPen(Qt::black);
            painter -> drawLine(x, rect.top(), x, rect.bottom());

            int flags = Qt::AlignHCenter | Qt::AlignTop;
            boundString = painter -> boundingRect(boundString, flags, s_label);    
            painter -> drawText(x - (boundString.width() + 5), rect.bottom() - (boundString.height() + 5),
                            boundString.width(), boundString.height(), flags, s_label);
        }
        if(k <= settings.numYTicks)                ///< отрисовка вдоль оси Y
        {
            int y = rect.bottom() - (k * (rect.height() - 1) / settings.numYTicks);
            double label = settings.minY + (k * settings.spanY() / settings.numYTicks);
            QString s_label(QString::number(label, 'f', 3));

            painter -> setPen(Qt::black);
            painter -> drawLine(rect.left(), y, rect.right(), y);

            int flags = Qt::AlignRight | Qt::AlignTop;
            boundString = painter -> boundingRect(boundString, flags, s_label);    
            painter -> drawText(rect.left() + 7, y - boundString.height(),
                                boundString.width(), boundString.height(), flags, s_label);
        }
    }
    painter -> drawRect(rect.adjusted(0, 0, -1, -1));
}

/// Отрисовка графика
void GraphicWidget::paintEvent(QPaintEvent* event)
{
    QPainter painter(this);
    drawGrid(&painter);

    QWidget::paintEvent(event);
}


В итоге должна появиться сетка графика. В данном случае выглядеть будет так:
[attachment=472:graphic.JPG]


Позже расскажу про масштабирование и попробуем построить простейший график (y(x) = x)

Автор: ++Norton++ 5.3.2009, 12:47

Вопрос, конечно ламерский, но все же :blush: А как можно в QWidget добавить еще один QWidget? (если имеет значение где делаю, то нужно сразу написать, что делаю все в QtCreator'е, хотя если нужно все делать в Designer'e могу и в нем).

Автор: AD 5.3.2009, 12:55

Пофигу где. Главное, чтобы ты смог открыть окно, аналогичное дизайнеру. Итак, открываешь окно дизайнер, в нем появляется нужная форма (виджет), в него и вставляешь еще один виджет. Затем нажимаешь на основной виджет, правая клавиша мыши - Lay Out -> Lay Out in Grid. Ну так можно, например.

Автор: ++Norton++ 5.3.2009, 13:05

Цитата(AD @ 5.3.2009, 13:55) *
нужная форма (виджет), в него и вставляешь еще один виджет.

Вот как раз в этом то и проблема :) Каким образом его вставить? Открываю окно дизайнера, с левой стороны виджеты все стандартные (ListWidget, ListView и т.д.) и нигде QWidget просто нет. Знаю, что наверное туплю, но никак найти не могу :(

Автор: AD 5.3.2009, 13:19

[attachment=473:disainer.JPG]

Обведенный в овальчик!

Автор: ++Norton++ 6.3.2009, 11:15

Огромное спасибо! Отлично, все получилось!
Если можно, теперь, как строить графики и про масштабирование :)

Автор: ++Norton++ 7.3.2009, 11:02

И всетаки, как же строить графики? :)

Автор: AD 7.3.2009, 13:11

Цитата(++Norton++ @ 7.3.2009, 11:02) *
И всетаки, как же строить графики? :)

Сегодня вечером. Впринципе, уже практически все есть. Вечером покажу!

Вот сделал масштабирование. И нажатие на некоторые кнопки:
+ - масштаб +
- - масштаб -
ALT+X - выход
ALT+Enter - показ на весь экран (и обратно).
class QPaintEvent;
class QPainter;
class QRubberBand;
class QKeyEvent;
class QResizeEvent;
class QCloseEvent;
class QShowEvent;
class QWheelEvent;
class QMouseEvent;

/// Виджет, где будет отрисовываться график
class GraphicWidget: public QWidget, public Ui::GraphicWidgetClass
{
    Q_OBJECT

private:
    PlotSettings settings;        ///< настройка для различных масштабов
    QRubberBand* rubber;        ///< "резиновая лента"
    bool rubberBandIsShown;        ///< флажок попадания курсора в "резиновую ленту"
    QPoint origin;                ///< начальные координаты выделяемой области

private:
    void drawGrid(QPainter *painter);

protected:
    void paintEvent(QPaintEvent* events);
    void keyPressEvent(QKeyEvent* events);
    void wheelEvent(QWheelEvent* events);
    void mousePressEvent(QMouseEvent* events);
    void mouseMoveEvent(QMouseEvent* events);
    void mouseReleaseEvent(QMouseEvent* events);
    void resizeEvent(QResizeEvent* events) { QWidget::resizeEvent(events); update(); }
    void closeEvent(QCloseEvent* events) { QWidget::closeEvent(events); }
    void showEvent(QShowEvent* events) { QWidget::showEvent(events); }

public:
    GraphicWidget(QWidget *parent = 0, Qt::WFlags flags = 0);
    ~GraphicWidget();
    void setPlotSettings(const PlotSettings& sts) { settings = sts; settings.adjust(); update(); }
    void zoom(double delta) { settings.scale(delta, delta); settings.adjust(); update(); }
};


Source
#include <QRubberBand>
#include <QKeyEvent>
#include <QWheelEvent>
#include <QMouseEvent>


/// Отрисовка графика
void GraphicWidget::paintEvent(QPaintEvent* events)
{
    QPainter painter(this);
    drawGrid(&painter);

    QWidget::paintEvent(events);
}

/// Нажатие на кнопки клавиатуры
void GraphicWidget::keyPressEvent(QKeyEvent* events)
{
    switch(events -> key())
    {
    case Qt::Key_Plus:
        zoom(-1.0);
    break;
    case Qt::Key_Minus:
        zoom(1.0);
    break;
    case Qt::Key_Left:
        settings.scroll(-1.0, 0.0);
        update();
    break;
    case Qt::Key_Right:
        settings.scroll(1.0, 0.0);
        update();
    break;
    case Qt::Key_Up:
        settings.scroll(0.0, 1.0);
        update();
    break;
    case Qt::Key_Down:
        settings.scroll(0.0, -1.0);
        update();
    break;
    case Qt::Key_X:
        if(events -> modifiers() & Qt::ALT) close();
    break;
    case Qt::Key_Return:
        if(events -> modifiers() & Qt::ALT)
        {
            if(!isMaximized()) showMaximized();
            else showNormal();
        }
    break;
    default:
        QWidget::keyPressEvent(events);
    }
}

/// Изменение масштаба при движении колесика
void GraphicWidget::wheelEvent(QWheelEvent* events)
{
    int numDegrees = events -> delta() / 8;
    double numTicks = numDegrees / 15.0;
    
    zoom(numTicks);
    update();
}

/// Нажатие на кнопку - изменение масштаба
void GraphicWidget::mousePressEvent(QMouseEvent* events)
{
    QWidget::mousePressEvent(events);

    QRect r;
    switch(events -> button())
    {
    case Qt::LeftButton:                ///< если нажата левая кнопка мыши
        origin = events -> pos();
        rubberBandIsShown = true;
        setCursor(Qt::CrossCursor);
        r = QRect(origin, QSize());
        rubber -> setGeometry(r);
        rubber -> show();
    break;
    case Qt::RightButton:                ///< если нажата правая кнопка мыши
    break;
    default:
    break;
    }
}

/// Переопределение функции передвижения мыши
void GraphicWidget::mouseMoveEvent(QMouseEvent* events)
{
    if(rubberBandIsShown)
    {
        rubber -> setWindowOpacity(0.0);
        rubber -> setGeometry(QRect(origin, events -> pos()).normalized());
    }

    update();
}

/// Возвращение прежнего вида курсору и изменение масштаба
void GraphicWidget::mouseReleaseEvent(QMouseEvent* events)
{
    if(events -> button() == Qt::LeftButton && rubberBandIsShown)
    {
        rubberBandIsShown = false;
        unsetCursor();
        
        QRect rect = rubber -> geometry().normalized();
        if(rect.width() < 10 || rect.height() < 10)
        {
            double dx = rect.width() / settings.spanX();
            double dy = rect.height() / settings.spanY();
            settings.scroll(dx, dy);

            update();
            return;
        }
        PlotSettings prevSettings = settings;

        double dx = prevSettings.spanX() / width();
        double dy = prevSettings.spanY() / height();
        settings.minX = prevSettings.minX + dx * rect.left();
        settings.maxX = prevSettings.minX + dx * rect.right();
        settings.minY = prevSettings.maxY - dy * rect.bottom();
        settings.maxY = prevSettings.maxY - dy * rect.top();
        settings.adjust();

        rubber -> hide();
        update();
    }
}

Автор: AD 7.3.2009, 14:00

Добавить в класс 3 функции:

private:
    void drawCurves(QPainter* painter);
    QPointF initXY(double sx, double sy);
    void initCurves();


Ну и их описать :) :
/// Заполнения вектора с кривой (кривая y(x) = x)
void GraphicWidget::initCurves()
{
    curve_vec.append(QPointF(-100., -100.));
    curve_vec.append(QPointF(-10., -10.));
    curve_vec.append(QPointF(-1., -1.));
    curve_vec.append(QPointF(0., 0.));
    curve_vec.append(QPointF(1., 1.));
    curve_vec.append(QPointF(1.5, 1.5));
    curve_vec.append(QPointF(5., 5.));
    curve_vec.append(QPointF(10., 10.));
    curve_vec.append(QPointF(100., 100.));
}

/// Инициализация координат - преобразование из координат графика (sx,sy) в экранные (x,y)
QPointF GraphicWidget::initXY(double sx, double sy)
{
    QRect rect(graphWidget -> rect());
    double dx, dy;

    /// Вычисление смещений вдоль осей
    dx = sx - settings.maxX;
    dy = sy - settings.minY;

    /// Вычисление экранных координат
    double x = rect.right() + (dx * (rect.width() - 1) / settings.spanX());
    double y = rect.bottom() - (dy * (rect.height() - 1) / settings.spanY());

    return QPointF(x, y);
}

/// Отрисовка графика
void GraphicWidget::drawCurves(QPainter* painter)
{
    painter -> setPen(Qt::blue);
    QPolygonF polyline(curve_vec.size());
    for(register int i=0; i<curve_vec.size(); ++i)
        polyline[i] = initXY(curve_vec[i].x(), curve_vec[i].y());
    painter -> drawPolyline(polyline);
}


А затем в paintEvent() добавить следующее:
drawCurves(&painter);


В конструкторе добавить:
initCurves();


Вуаля. График нарисован. Все сделано! :))))

Автор: Kagami 7.3.2009, 14:26

Напоминает пример из книжки Жасмин Бланшет :)

Автор: Litkevich Yuriy 7.3.2009, 14:48

AD, Оборачивай длинные портянки кода в тэг http://www.forum.crossplatform.ru/index.php?showtopic=1455

Автор: AD 7.3.2009, 14:51

Цитата(Kagami @ 7.3.2009, 14:26) *
Напоминает пример из книжки Жасмин Бланшет :)

Не все, но есть куски, которые я по ней делал!

Автор: ++Norton++ 7.3.2009, 16:15

Вроде все сделал так, но при компиляции выдается ошибка :( :

graphicwidget.cpp:200: error: 'curve_vec' was not declared in this scope

Я что-то где-то не дописал?

Автор: AD 7.3.2009, 16:20

Цитата(++Norton++ @ 7.3.2009, 16:15) *
Вроде все сделал так, но при компиляции выдается ошибка :( :
graphicwidget.cpp:200: error: 'curve_vec' was not declared in this scope

Я что-то где-то не дописал?

Его в классе объявить надо :) Вроде бы и так понятно! :))))
private:
QVector<QPointF> curve_vec;

Советую слегка подучить С++, а то вопросы иногда в ступор вводят! :)))))

Автор: ++Norton++ 7.3.2009, 20:40

Огромнейшее спасибо! Теперь все отлично работает!
А на счет подучить C++ и вправду надо :) Просто не так давно занимаюсь программированием на C++ :)

Автор: ++Norton++ 7.3.2009, 22:38

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

Автор: AD 7.3.2009, 22:56

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

Где-то что-то не так ты написал. У меня нет таких глюков. Ищи!

Автор: BRE 7.3.2009, 23:08

Цитата(++Norton++ @ 7.3.2009, 22:38) *
Да, хотел только еще один вопрос задать...
Когда нажимаю левой кнопкой мыши на график, программа прекращает свою работу (закрывается). Вроде бы так не должно быть?

Я так думаю, что она не просто закрывается, а падает.
Проверь у себя, в конструкторе GraphicWidget объект rubber конструируется?
Что-то типа rubber = new QRubberBand( this );

Автор: AD 7.3.2009, 23:15

GraphicWidget::GraphicWidget(QWidget *parent, Qt::WFlags flags): QWidget(parent, flags), rubberBandIsShown(false),
                rubber(new QRubberBand(QRubberBand::Rectangle, this))
{
    setupUi(this);
    setPlotSettings(PlotSettings());
    initCurves();
}

Автор: ++Norton++ 8.3.2009, 0:46

Спасибо! Теперь все работает! Только лента не вырисовывается, а так масштабирование проходит нормально :)

Автор: AD 10.3.2009, 12:14

Цитата(++Norton++ @ 8.3.2009, 0:46) *
Только лента не вырисовывается, а так масштабирование проходит нормально :)

Что значит не вырисовывается? Пытался понять причины ошибки? Притормаживает изображение? Ты сделал тот пример, что я написал или по подобию на своем графике испытывал? У меня все рисовалось прекрасно!

Автор: asdf 24.9.2009, 11:17

а подскажите по этому примеру в чём косяк. при добавлении GraphicWidget.h при сборке вылазит ошибка "GraphicWidget.h:39: ошибка: expected class-name before ‘{’ token" указывает на скобку перед объявлением Q_Object . но она вроде как там корректно стоит. в чём косяк??

Автор: AD 24.9.2009, 18:06

Цитата(asdf @ 24.9.2009, 12:17) *
а подскажите по этому примеру в чём косяк. при добавлении GraphicWidget.h при сборке вылазит ошибка "GraphicWidget.h:39: ошибка: expected class-name before ‘{’ token" указывает на скобку перед объявлением Q_Object . но она вроде как там корректно стоит. в чём косяк??
Прошу привести кусок кода и полное сообщение об ошибке.

Автор: asdf 24.9.2009, 23:10

Цитата(AD @ 24.9.2009, 18:06) *
Прошу привести кусок кода и полное сообщение об ошибке.


код:
class GraphicWidget: public QWidget, public Ui::GraphicWidgetClass
{
    Q_OBJECT

private:
    PlotSettings settings;        ///< настройка для различных масштабов
    void drawGrid(QPainter *painter);

protected:
    void paintEvent(QPaintEvent* event);

public:
    GraphicWidget(QWidget *parent = 0, Qt::WFlags flags = 0);
    ~GraphicWidget();
    void setPlotSettings(const PlotSettings& sts) { settings = sts; settings.adjust(); update(); }
};

ошибка:
Цитата
C:/Other_place/QTProjects/GraphWiget/GraphicWidget.h:39: error: expected class-name before '{' token


вроде брал код отсюда, руками особо не ковырял....

Автор: ufna 24.9.2009, 23:18

обычно такая хрень идет по двум причинам:
1. чет не то компилятор наделал, сделай полный ребилд
2. с инклюдами что-то не то (например "зацикливание")

Автор: AD 24.9.2009, 23:23

Думаю, что ребилд должен помочь!


Цитата
2. с инклюдами что-то не то (например "зацикливание")

Нет! Если все скопировать ровно как в примере, то зацикливания не будет. Код проверен и отлажен.

Автор: AD 27.9.2009, 15:13

Ну... решил выложить еще раз решение. ПОЛНОСТЬЮ!

GraphicWidget

#ifndef PLOTSETTINGS_H
#define PLOTSETTINGS_H

#include <QPointF>
#include <cmath>

/// Àáñòðàêòíûé êëàññ äëÿ çàäàíèÿ äèàïàçîíà çíà÷åíèé ïî îñÿì x è y
class PlotSettings
{
public:
    double minX;                        ///< ìèíèìàëüíîå çíà÷åíèå ïî îñè àáñöèññ
    double maxX;                        ///< ìàêñèìàëüíîå çíà÷åíèå ïî îñè àáñöèññ
    int numXTicks;                        ///< êîëè÷åñòâî äåëåíèé íà îñè àáñöèññ
    double minY;                        ///< ìèíèìàëüíîå çíà÷åíèå ïî îñè îðäèíàò
    double maxY;                        ///< ìàêñèìàëüíîå çíà÷åíèå ïî îñè îðäèíàò
    int numYTicks;                        ///< êîëè÷åñòâî äåëåíèé íà îñè îðäèíàò

protected:
    void adjustAxis(double& min, double& max, int& numTicks);

public:
    PlotSettings();
    void scroll(double dx, double dy);
    void scale(double delta_x, double delta_y);
    void adjust();
    void adjust(QPointF& point);
    double spanX() const { return fabs(maxX - minX); }
    double spanY() const { return fabs(maxY - minY); }
};

#endif


#include "PlotSettings.h"

PlotSettings::PlotSettings(): minX(-50.), minY(-4.), maxX(50.), maxY(45.)
{
    numXTicks = 8;
    numYTicks = 8;
}

/// Óâåëè÷åíèå/óìåíüøåíèå çíà÷åíèÿ minX, maxX, minY, maxY íà èíòåðâàë ìåæäó 2-ìÿ îòìåòêàìè
void PlotSettings::scroll(double dx, double dy)
{
    double stepX = spanX() / numXTicks;
    minX += dx * stepX;
    maxX += dx * stepX;

    double stepY = spanY() / numYTicks;
    minY += dy * stepY;
    maxY += dy * stepY;

    adjust();
}

/// Óâåëè÷åíèå/óìåíüøåíèå çíà÷åíèÿ minX, maxX, minY, maxY íà èíòåðâàë ìåæäó 2-ìÿ îòìåòêàìè
void PlotSettings::scale(double delta_x, double delta_y)
{
    if((minX == maxX || minY == maxY) && delta_x < 0 && delta_y < 0) return;

    double stepX = spanX() / numXTicks;
    minX -= delta_x * stepX;
    maxX += delta_x * stepX;

    double stepY = spanY() / numYTicks;
    minY -= delta_y * stepY;
    maxY += delta_y * stepY;
}

/// Îêðóãëåíèå çíà÷åíèé minX, minY, maxX, maxY
void PlotSettings::adjust()
{
    adjustAxis(minX, maxX, numXTicks);
    adjustAxis(minY, maxY, numYTicks);
}

/// Îêðóãëåíèå çíà÷åíèé çàäàííîé òî÷êè point
void PlotSettings::adjust(QPointF& point)
{
    double mn_x = minX, mn_y = minY;
    int ticks_x = numXTicks, ticks_y = numYTicks;
    double mx_x = point.x(), mx_y = point.y();

    adjustAxis(mn_x, mx_x, ticks_x);
    adjustAxis(mn_y, mx_y, ticks_y);

    point.setX(mx_x);
    point.setY(mx_y);
}

/// Ïðåîáðàçîâàíèå ïàðàìåòðîâ â óäîáíûå çíà÷åíèÿ
void PlotSettings::adjustAxis(double& min, double&  max, int& numTicks)
{
    const int MinTicks = 5;
    double grossStep = (max - min) / MinTicks;
    double step = pow(10.0, floor(log10(grossStep)));

    if(5 * step < grossStep)
        step *= 5;
    else if(2 * step < grossStep)
        step *= 2;

    numTicks = int(ceil(max / step) - floor(min / step));        
    if(numTicks < MinTicks)
        numTicks = MinTicks;

    min = floor(min / step) * step;
    max = ceil(max / step) * step;
}


#ifndef GRAPHICWIDGET_H
#define GRAPHICWIDGET_H

#include "ui_GraphicWidget.h"
#include "PlotSettings.h"

#include <QtGui/QWidget>
#include <QPoint>

class QPaintEvent;
class QPainter;
class QRubberBand;
class QKeyEvent;
class QResizeEvent;
class QCloseEvent;
class QShowEvent;
class QWheelEvent;
class QMouseEvent;

/// Âèäæåò, ãäå áóäåò îòðèñîâûâàòüñÿ ãðàôèê
class GraphicWidget: public QWidget, public Ui::GraphicWidgetClass
{
    Q_OBJECT

private:
    PlotSettings settings;        ///< íàñòðîéêà äëÿ ðàçëè÷íûõ ìàñøòàáîâ
    QRubberBand* rubber;        ///< "ðåçèíîâàÿ ëåíòà"
    bool rubberBandIsShown;        ///< ôëàæîê ïîïàäàíèÿ êóðñîðà â "ðåçèíîâóþ ëåíòó"
    QPoint origin;                ///< íà÷àëüíûå êîîðäèíàòû âûäåëÿåìîé îáëàñòè
    QVector<QPointF> curve_vec;    ///< âåêòîð òî÷åê êðèâîé

private:
    void drawGrid(QPainter *painter);
    void drawCurves(QPainter* painter);
    QPointF initXY(double sx, double sy);
    void initCurves();

protected:
    void paintEvent(QPaintEvent* events);
    void keyPressEvent(QKeyEvent* events);
    void wheelEvent(QWheelEvent* events);
    void mousePressEvent(QMouseEvent* events);
    void mouseMoveEvent(QMouseEvent* events);
    void mouseReleaseEvent(QMouseEvent* events);
    void resizeEvent(QResizeEvent* events) { QWidget::resizeEvent(events); update(); }
    void closeEvent(QCloseEvent* events) { QWidget::closeEvent(events); }
    void showEvent(QShowEvent* events) { QWidget::showEvent(events); }

public:
    GraphicWidget(QWidget *parent = 0, Qt::WFlags flags = 0);
    ~GraphicWidget();
    void setPlotSettings(const PlotSettings& sts) { settings = sts; settings.adjust(); update(); }
    void zoom(double delta) { settings.scale(delta, delta); settings.adjust(); update(); }
};

#endif // GRAPHICWIDGET_H


#include "GraphicWidget.h"

#include <QPainter>
#include <QRect>
#include <QString>
#include <QRubberBand>
#include <QKeyEvent>
#include <QWheelEvent>
#include <QMouseEvent>
#include <algorithm>
using namespace std;

GraphicWidget::GraphicWidget(QWidget *parent, Qt::WFlags flags): QWidget(parent, flags), rubberBandIsShown(false),
                rubber(new QRubberBand(QRubberBand::Rectangle, this))
{
    setupUi(this);

    /// Èíèöèàëèçàöèÿ íåîáõîäèìûõ ïàðàìåòðîâ
    setPlotSettings(PlotSettings());

    initCurves();
}

GraphicWidget::~GraphicWidget()
{}

/// Çàïîëíåíèÿ âåêòîðà ñ êðèâîé (êðèâàÿ y(x) = x)
void GraphicWidget::initCurves()
{
    curve_vec.append(QPointF(-100., -100.));
    curve_vec.append(QPointF(-10., -10.));
    curve_vec.append(QPointF(-1., -1.));
    curve_vec.append(QPointF(0., 0.));
    curve_vec.append(QPointF(1., 1.));
    curve_vec.append(QPointF(1.5, 1.5));
    curve_vec.append(QPointF(5., 5.));
    curve_vec.append(QPointF(10., 10.));
    curve_vec.append(QPointF(100., 100.));
}

/// Îòðèñîâêà ñåòêè
void GraphicWidget::drawGrid(QPainter *painter)
{
    QRect rect(graphWidget -> rect());
    if(!rect.isValid()) return;
    
    QRect boundString;    
    double great_max = max(settings.numXTicks, settings.numYTicks) + 1;
    for(register int i=0, j=0, k=0; i<=great_max; ++i, ++j, ++k)
    {
        if(j <= settings.numXTicks)                ///< îòðèñîâêà âäîëü îñè X
        {
            int x = rect.left() + (j * (rect.width() - 1) / settings.numXTicks);
            double label = settings.minX + (j * settings.spanX() / settings.numXTicks);
            QString s_label(QString::number(label, 'f', 3));

            painter -> setPen(Qt::black);
            painter -> drawLine(x, rect.top(), x, rect.bottom());

            int flags = Qt::AlignHCenter | Qt::AlignTop;
            boundString = painter -> boundingRect(boundString, flags, s_label);    
            painter -> drawText(x - (boundString.width() + 5), rect.bottom() - (boundString.height() + 5),
                            boundString.width(), boundString.height(), flags, s_label);
        }
        if(k <= settings.numYTicks)                ///< îòðèñîâêà âäîëü îñè Y
        {
            int y = rect.bottom() - (k * (rect.height() - 1) / settings.numYTicks);
            double label = settings.minY + (k * settings.spanY() / settings.numYTicks);
            QString s_label(QString::number(label, 'f', 3));

            painter -> setPen(Qt::black);
            painter -> drawLine(rect.left(), y, rect.right(), y);

            int flags = Qt::AlignRight | Qt::AlignTop;
            boundString = painter -> boundingRect(boundString, flags, s_label);    
            painter -> drawText(rect.left() + 7, y - boundString.height(),
                                boundString.width(), boundString.height(), flags, s_label);
        }
    }
    painter -> drawRect(rect.adjusted(0, 0, -1, -1));
}

/// Èíèöèàëèçàöèÿ êîîðäèíàò - ïðåîáðàçîâàíèå èç êîîðäèíàò ãðàôèêà (sx,sy) â ýêðàííûå (x,y)
QPointF GraphicWidget::initXY(double sx, double sy)
{
    QRect rect(graphWidget -> rect());
    double dx, dy;

    /// Âû÷èñëåíèå ñìåùåíèé âäîëü îñåé
    dx = sx - settings.maxX;
    dy = sy - settings.minY;

    /// Âû÷èñëåíèå ýêðàííûõ êîîðäèíàò
    double x = rect.right() + (dx * (rect.width() - 1) / settings.spanX());
    double y = rect.bottom() - (dy * (rect.height() - 1) / settings.spanY());

    return QPointF(x, y);
}

/// Îòðèñîâêà ãðàôèêà
void GraphicWidget::drawCurves(QPainter* painter)
{
    painter -> setPen(Qt::blue);
    QPolygonF polyline(curve_vec.size());
    for(register int i=0; i<curve_vec.size(); ++i)
        polyline[i] = initXY(curve_vec[i].x(), curve_vec[i].y());
    painter -> drawPolyline(polyline);
}

/// Îòðèñîâêà ãðàôèêà
void GraphicWidget::paintEvent(QPaintEvent* events)
{
    QPainter painter(this);
    drawGrid(&painter);
    drawCurves(&painter);

    QWidget::paintEvent(events);
}

/// Íàæàòèå íà êíîïêè êëàâèàòóðû
void GraphicWidget::keyPressEvent(QKeyEvent* events)
{
    switch(events -> key())
    {
    case Qt::Key_Plus:
        zoom(-1.0);
    break;
    case Qt::Key_Minus:
        zoom(1.0);
    break;
    case Qt::Key_Left:
        settings.scroll(-1.0, 0.0);
        update();
    break;
    case Qt::Key_Right:
        settings.scroll(1.0, 0.0);
        update();
    break;
    case Qt::Key_Up:
        settings.scroll(0.0, 1.0);
        update();
    break;
    case Qt::Key_Down:
        settings.scroll(0.0, -1.0);
        update();
    break;
    case Qt::Key_X:
        if(events -> modifiers() & Qt::ALT) close();
    break;
    case Qt::Key_Return:
        if(events -> modifiers() & Qt::ALT)
        {
            if(!isMaximized()) showMaximized();
            else showNormal();
        }
    break;
    default:
        QWidget::keyPressEvent(events);
    }
}

/// Èçìåíåíèå ìàñøòàáà ïðè äâèæåíèè êîëåñèêà
void GraphicWidget::wheelEvent(QWheelEvent* events)
{
    int numDegrees = events -> delta() / 8;
    double numTicks = numDegrees / 15.0;
    
    zoom(numTicks);
    update();
}

/// Íàæàòèå íà êíîïêó - èçìåíåíèå ìàñøòàáà
void GraphicWidget::mousePressEvent(QMouseEvent* events)
{
    QWidget::mousePressEvent(events);

    QRect r;
    switch(events -> button())
    {
    case Qt::LeftButton:                ///< åñëè íàæàòà ëåâàÿ êíîïêà ìûøè
        origin = events -> pos();
        rubberBandIsShown = true;
        setCursor(Qt::CrossCursor);
        r = QRect(origin, QSize());
        rubber -> setGeometry(r);
        rubber -> show();
    break;
    case Qt::RightButton:                ///< åñëè íàæàòà ïðàâàÿ êíîïêà ìûøè
    break;
    default:
    break;
    }
}

/// Ïåðåîïðåäåëåíèå ôóíêöèè ïåðåäâèæåíèÿ ìûøè
void GraphicWidget::mouseMoveEvent(QMouseEvent* events)
{
    if(rubberBandIsShown)
    {
        rubber -> setWindowOpacity(0.0);
        rubber -> setGeometry(QRect(origin, events -> pos()).normalized());
    }

    update();
}

/// Âîçâðàùåíèå ïðåæíåãî âèäà êóðñîðó è èçìåíåíèå ìàñøòàáà
void GraphicWidget::mouseReleaseEvent(QMouseEvent* events)
{
    if(events -> button() == Qt::LeftButton && rubberBandIsShown)
    {
        rubberBandIsShown = false;
        unsetCursor();
        
        QRect rect = rubber -> geometry().normalized();
        if(rect.width() < 10 || rect.height() < 10)
        {
            double dx = rect.width() / settings.spanX();
            double dy = rect.height() / settings.spanY();
            settings.scroll(dx, dy);

            update();
            return;
        }
        PlotSettings prevSettings = settings;

        double dx = prevSettings.spanX() / width();
        double dy = prevSettings.spanY() / height();
        settings.minX = prevSettings.minX + dx * rect.left();
        settings.maxX = prevSettings.minX + dx * rect.right();
        settings.minY = prevSettings.maxY - dy * rect.bottom();
        settings.maxY = prevSettings.maxY - dy * rect.top();
        settings.adjust();

        rubber -> hide();
        update();
    }
}


Автор: Litkevich Yuriy 27.9.2009, 17:01

AD, чёто с русскими коментариями бяка какая-то

Автор: AD 27.9.2009, 17:10

Цитата(Litkevich Yuriy @ 27.9.2009, 18:01) *
AD, чёто с русскими коментариями бяка какая-то

Видел. Да и фиг с ними. И так ясно. Тут три страницы разъяснений уже! :)

Автор: AD 6.11.2009, 15:49

Цитата(++Norton++ @ 8.3.2009, 0:46) *
Спасибо! Теперь все работает! Только лента не вырисовывается, а так масштабирование проходит нормально :)

Не знаю надо это сейчас или не очень. Но лента не будет вырисовываться, если вместо следующей строки:
rubber(new QRubberBand(QRubberBand::Rectangle, this))

делать такую строку:
rubber(new QRubberBand(QRubberBand::Rectangle, parent))


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

Автор: Гость 24.12.2009, 21:20

Уважаемые, поясните пожалуйста что за файл подключается, ui_GraphicWidget.h, и откуда он должен взяться? (ругается на него компилятор)

ошибка:fatal error C1083: Cannot open include file: 'ui_GraphicWidget.h': No such file or directory

Автор: AD 24.12.2009, 22:33

Цитата(Гость @ 24.12.2009, 21:20) *
Уважаемые, поясните пожалуйста что за файл подключается, ui_GraphicWidget.h, и откуда он должен взяться? (ругается на него компилятор)

ошибка:fatal error C1083: Cannot open include file: 'ui_GraphicWidget.h': No such file or directory

У Вас должен быть ui-файл. Т.е. файл формы, воспринимаемого дизайнером Qt! ui_GraphicWidget.h - это файл описания виджетов, расположенных на форме. Судя из примера, файл должен быть таким:
/********************************************************************************
** Form generated from reading ui file 'GraphicWidget.ui'
**
** Created: Thu 5. Mar 12:26:58 2009
**      by: Qt User Interface Compiler version 4.3.2
**
** WARNING! All changes made in this file will be lost when recompiling ui file!
********************************************************************************/

#ifndef UI_GRAPHICWIDGET_H
#define UI_GRAPHICWIDGET_H

#include <QtCore/QVariant>
#include <QtGui/QAction>
#include <QtGui/QApplication>
#include <QtGui/QButtonGroup>
#include <QtGui/QGridLayout>
#include <QtGui/QWidget>

class Ui_GraphicWidgetClass
{
public:
    QGridLayout *gridLayout;
    QWidget *graphWidget;

    void setupUi(QWidget *GraphicWidgetClass)
    {
    if (GraphicWidgetClass->objectName().isEmpty())
        GraphicWidgetClass->setObjectName(QString::fromUtf8("GraphicWidgetClass"));
    GraphicWidgetClass->resize(600, 400);
    QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    sizePolicy.setHorizontalStretch(0);
    sizePolicy.setVerticalStretch(0);
    sizePolicy.setHeightForWidth(GraphicWidgetClass->sizePolicy().hasHeightForWidth());
    GraphicWidgetClass->setSizePolicy(sizePolicy);
    gridLayout = new QGridLayout(GraphicWidgetClass);
    gridLayout->setSpacing(6);
    gridLayout->setMargin(11);
    gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
    gridLayout->setHorizontalSpacing(1);
    gridLayout->setVerticalSpacing(3);
    gridLayout->setContentsMargins(3, 3, 1, 3);
    graphWidget = new QWidget(GraphicWidgetClass);
    graphWidget->setObjectName(QString::fromUtf8("graphWidget"));
    sizePolicy.setHeightForWidth(graphWidget->sizePolicy().hasHeightForWidth());
    graphWidget->setSizePolicy(sizePolicy);

    gridLayout->addWidget(graphWidget, 0, 0, 1, 1);


    retranslateUi(GraphicWidgetClass);

    QMetaObject::connectSlotsByName(GraphicWidgetClass);
    } // setupUi

    void retranslateUi(QWidget *GraphicWidgetClass)
    {
    GraphicWidgetClass->setWindowTitle(QApplication::translate("GraphicWidgetClass", "GraphicWidget", 0, QApplication::UnicodeUTF8));
    Q_UNUSED(GraphicWidgetClass);
    } // retranslateUi

};

namespace Ui {
    class GraphicWidgetClass: public Ui_GraphicWidgetClass {};
} // namespace Ui

#endif // UI_GRAPHICWIDGET_H

Автор: Litkevich Yuriy 24.12.2009, 22:35

Цитата(Гость_Гость_* @ 25.12.2009, 0:20) *
ui_GraphicWidget.h,
такие файлы генерит утилита uic из ui-файлов (в твоём случае из GraphicWidget.ui).
Если используешь qmake для создания проектов, то в pro-файле должна быть строчка:
FORMS += GraphicWidget.ui

Автор: AlmNeft 17.4.2010, 22:43

AD, ++Norton++, выложите пожалуйста папку с прогой полностью если сохранилось у кого-нить?

Автор: AD 17.4.2010, 23:28

Цитата(AlmNeft @ 17.4.2010, 23:43) *
AD, ++Norton++, выложите пожалуйста папку с прогой полностью если сохранилось у кого-нить?

Я ничего выкладывать не буду. Все, что необходимо, описано в теме. Весь код уже выложен. Создать проект можно самостоятельно!

Автор: Fitz 2.12.2010, 14:26

Цитата(Гость @ 24.12.2009, 21:20) *
Уважаемые, поясните пожалуйста что за файл подключается, ui_GraphicWidget.h, и откуда он должен взяться? (ругается на него компилятор)

ошибка:fatal error C1083: Cannot open include file: 'ui_GraphicWidget.h': No such file or directory

Тоже очень долго не мог собрать из-за этой ошибки проект.
Проблема в том, что при создании нового проекта в QtCreator'е, при задании ему имени GraphicWidget (как писал в начале AD) создаются следующие файлы:
- GraphicWidget.pro
- GraphicWidget.h
- GraphicWidget.cpp
- GraphicWidget.ui
и в файле формы GraphicWidget.ui основной(родительский) виджет имеет название GraphicWidget(!), впоследствии утилита uac генерирует из .ui класс
class Ui_GraphicWidget

а в листинге AD необходим класс
class Ui_GraphicWidgetClass

Следовательно идем в дизайнер форм и переименовываем основной виджет из GraphicWidget в GraphicWidgetClass.

Пересобираем, любуемся.
По сути получилась ошибка зависимостей(как показывает компилятор), но не совсем.

Автор: Fitz 4.2.2011, 15:02

/// Инициализация координат - преобразование из координат графика (sx,sy) в экранные (x,y)
QPointF GraphicWidget::initXY(double sx, double sy)
{
    QRect rect(graphWidget -> rect());
    double dx, dy;

    /// Вычисление смещений вдоль осей
    dx = sx - settings.maxX;
    dy = sy - settings.minY;

    /// Вычисление экранных координат
    double x = rect.right() + (dx * (rect.width() - 1) / settings.spanX());
    double y = rect.bottom() - (dy * (rect.height() - 1) / settings.spanY());

    return QPointF(x, y);
}

Ошибка в вычислении смещения.
dx = sx - settings.maxX;

Исправление:
    dx = sx - settings.minX;
    dy = sy - settings.minY;

Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)