Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Дополнительный виджет
Форум на CrossPlatform.RU > Библиотеки > Qt > Qt GUI
++Norton++
Столкнулся с такой проблемой, нужно написать программу, которая будет строить определенный график, но в Qt не нашел виджета-плоттера. :(
Подскажите пожалуйста, есть ли какая-нибудь возможность решить эту проблему? Как отобразить график? Может есть какие-нибудь дополнительные виджеты?
SABROG
Это? http://qwt.sourceforge.net/class_qwt_plot.html
AD
Цитата(++Norton++ @ 4.3.2009, 23:38) *
Столкнулся с такой проблемой, нужно написать программу, которая будет строить определенный график, но в Qt не нашел виджета-плоттера. :(
Подскажите пожалуйста, есть ли какая-нибудь возможность решить эту проблему? Как отобразить график? Может есть какие-нибудь дополнительные виджеты?

Если решишь делать своими руками - помогу. Можно обойтись и без Qwt! Я эту задачу решил и знаю как делать!
++Norton++
SABROG, спасибо, пробовал, скачивал, только не получилось его в QtDesigner добавить :(
AD, отлично, с удовольствием хотел бы попробовать, буду очень благодарен за помощь!
AD
Цитата(++Norton++ @ 5.3.2009, 0:34) *
SABROG, спасибо, пробовал, скачивал, только не получилось его в QtDesigner добавить :(
AD, отлично, с удовольствием хотел бы попробовать, буду очень благодарен за помощь!

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


вот
Kagami
Я тоже свой виджет для построения графиков делал :)
AD
Первое. Желательно создать новый проект. Основной класс 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++
Спасибо, все понятно кроме некоторых деталей:
1) Виджет graphWidget - это имеется ввиду GraphicsView? Или я что-то путаю?
2) На втором листинге исходник PlotSettings.cpp, правильно?
AD
Цитата(++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);
}


В итоге должна появиться сетка графика. В данном случае выглядеть будет так:
Нажмите для просмотра прикрепленного файла


Позже расскажу про масштабирование и попробуем построить простейший график (y(x) = x)
++Norton++
Вопрос, конечно ламерский, но все же :blush: А как можно в QWidget добавить еще один QWidget? (если имеет значение где делаю, то нужно сразу написать, что делаю все в QtCreator'е, хотя если нужно все делать в Designer'e могу и в нем).
AD
Пофигу где. Главное, чтобы ты смог открыть окно, аналогичное дизайнеру. Итак, открываешь окно дизайнер, в нем появляется нужная форма (виджет), в него и вставляешь еще один виджет. Затем нажимаешь на основной виджет, правая клавиша мыши - Lay Out -> Lay Out in Grid. Ну так можно, например.
++Norton++
Цитата(AD @ 5.3.2009, 13:55) *
нужная форма (виджет), в него и вставляешь еще один виджет.

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

Обведенный в овальчик!
++Norton++
Огромное спасибо! Отлично, все получилось!
Если можно, теперь, как строить графики и про масштабирование :)
++Norton++
И всетаки, как же строить графики? :)
AD
Цитата(++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
Добавить в класс 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
Напоминает пример из книжки Жасмин Бланшет :)
Litkevich Yuriy
AD, Оборачивай длинные портянки кода в тэг экспанд
AD
Цитата(Kagami @ 7.3.2009, 14:26) *
Напоминает пример из книжки Жасмин Бланшет :)

Не все, но есть куски, которые я по ней делал!
++Norton++
Вроде все сделал так, но при компиляции выдается ошибка :( :
graphicwidget.cpp:200: error: 'curve_vec' was not declared in this scope

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

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

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

Советую слегка подучить С++, а то вопросы иногда в ступор вводят! :)))))
++Norton++
Огромнейшее спасибо! Теперь все отлично работает!
А на счет подучить C++ и вправду надо :) Просто не так давно занимаюсь программированием на C++ :)
++Norton++
Да, хотел только еще один вопрос задать...
Когда нажимаю левой кнопкой мыши на график, программа прекращает свою работу (закрывается). Вроде бы так не должно быть?
AD
Цитата
Да, хотел только еще один вопрос задать...
Когда нажимаю левой кнопкой мыши на график, программа прекращает свою работу (закрывается). Вроде бы так не должно быть?

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

Я так думаю, что она не просто закрывается, а падает.
Проверь у себя, в конструкторе GraphicWidget объект rubber конструируется?
Что-то типа rubber = new QRubberBand( this );
AD
GraphicWidget::GraphicWidget(QWidget *parent, Qt::WFlags flags): QWidget(parent, flags), rubberBandIsShown(false),
                rubber(new QRubberBand(QRubberBand::Rectangle, this))
{
    setupUi(this);
    setPlotSettings(PlotSettings());
    initCurves();
}
++Norton++
Спасибо! Теперь все работает! Только лента не вырисовывается, а так масштабирование проходит нормально :)
AD
Цитата(++Norton++ @ 8.3.2009, 0:46) *
Только лента не вырисовывается, а так масштабирование проходит нормально :)

Что значит не вырисовывается? Пытался понять причины ошибки? Притормаживает изображение? Ты сделал тот пример, что я написал или по подобию на своем графике испытывал? У меня все рисовалось прекрасно!
asdf
а подскажите по этому примеру в чём косяк. при добавлении GraphicWidget.h при сборке вылазит ошибка "GraphicWidget.h:39: ошибка: expected class-name before ‘{’ token" указывает на скобку перед объявлением Q_Object . но она вроде как там корректно стоит. в чём косяк??
AD
Цитата(asdf @ 24.9.2009, 12:17) *
а подскажите по этому примеру в чём косяк. при добавлении GraphicWidget.h при сборке вылазит ошибка "GraphicWidget.h:39: ошибка: expected class-name before ‘{’ token" указывает на скобку перед объявлением Q_Object . но она вроде как там корректно стоит. в чём косяк??
Прошу привести кусок кода и полное сообщение об ошибке.
asdf
Цитата(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
обычно такая хрень идет по двум причинам:
1. чет не то компилятор наделал, сделай полный ребилд
2. с инклюдами что-то не то (например "зацикливание")
AD
Думаю, что ребилд должен помочь!


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

Нет! Если все скопировать ровно как в примере, то зацикливания не будет. Код проверен и отлажен.
AD
Ну... решил выложить еще раз решение. ПОЛНОСТЬЮ!
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
AD, чёто с русскими коментариями бяка какая-то
AD
Цитата(Litkevich Yuriy @ 27.9.2009, 18:01) *
AD, чёто с русскими коментариями бяка какая-то

Видел. Да и фиг с ними. И так ясно. Тут три страницы разъяснений уже! :)
AD
Цитата(++Norton++ @ 8.3.2009, 0:46) *
Спасибо! Теперь все работает! Только лента не вырисовывается, а так масштабирование проходит нормально :)

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

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


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

ошибка:fatal error C1083: Cannot open include file: 'ui_GraphicWidget.h': No such file or directory
AD
Цитата(Гость @ 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
Цитата(Гость_Гость_* @ 25.12.2009, 0:20) *
ui_GraphicWidget.h,
такие файлы генерит утилита uic из ui-файлов (в твоём случае из GraphicWidget.ui).
Если используешь qmake для создания проектов, то в pro-файле должна быть строчка:
FORMS += GraphicWidget.ui
AlmNeft
AD, ++Norton++, выложите пожалуйста папку с прогой полностью если сохранилось у кого-нить?
AD
Цитата(AlmNeft @ 17.4.2010, 23:43) *
AD, ++Norton++, выложите пожалуйста папку с прогой полностью если сохранилось у кого-нить?

Я ничего выкладывать не буду. Все, что необходимо, описано в теме. Весь код уже выложен. Создать проект можно самостоятельно!
Fitz
Цитата(Гость @ 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
/// Инициализация координат - преобразование из координат графика (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;
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Форум IP.Board © 2001-2024 IPS, Inc.