qwt_plot_zoomer.cpp 16.5 KB
Newer Older
pixhawk's avatar
pixhawk committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
 * Qwt Widget Library
 * Copyright (C) 1997   Josef Wilgen
 * Copyright (C) 2002   Uwe Rathmann
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the Qwt License, Version 1.0
 *****************************************************************************/

// vim: expandtab

#include <math.h>
#include "qwt_plot.h"
#include "qwt_plot_canvas.h"
#include "qwt_plot_zoomer.h"
#include "qwt_scale_div.h"
#if QT_VERSION < 0x040000
typedef QValueStack<QwtDoubleRect> QwtZoomStack;
#else
typedef QStack<QwtDoubleRect> QwtZoomStack;
#endif

class QwtPlotZoomer::PrivateData
{
public:
    uint zoomRectIndex;
    QwtZoomStack zoomStack;

    int maxStackDepth;
};

/*!
  \brief Create a zoomer for a plot canvas.

  The zoomer is set to those x- and y-axis of the parent plot of the
  canvas that are enabled. If both or no x-axis are enabled, the picker
  is set to QwtPlot::xBottom. If both or no y-axis are
  enabled, it is set to QwtPlot::yLeft.

40
  The selectionFlags() are set to
pixhawk's avatar
pixhawk committed
41 42 43 44 45
  QwtPicker::RectSelection & QwtPicker::ClickSelection, the
  tracker mode to QwtPicker::ActiveOnly.

  \param canvas Plot canvas to observe, also the parent object
  \param doReplot Call replot for the attached plot before initializing
46
                  the zoomer with its scales. This might be necessary,
pixhawk's avatar
pixhawk committed
47 48 49 50 51 52 53 54 55 56 57 58 59 60
                  when the plot is in a state with pending scale changes.

  \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase()
*/
QwtPlotZoomer::QwtPlotZoomer(QwtPlotCanvas *canvas, bool doReplot):
    QwtPlotPicker(canvas)
{
    if ( canvas )
        init(RectSelection & ClickSelection, ActiveOnly, doReplot);
}

/*!
  \brief Create a zoomer for a plot canvas.

61
  The selectionFlags() are set to
pixhawk's avatar
pixhawk committed
62
  QwtPicker::RectSelection & QwtPicker::ClickSelection, the
63
  tracker mode to QwtPicker::ActiveOnly.
pixhawk's avatar
pixhawk committed
64 65 66 67 68

  \param xAxis X axis of the zoomer
  \param yAxis Y axis of the zoomer
  \param canvas Plot canvas to observe, also the parent object
  \param doReplot Call replot for the attached plot before initializing
69
                  the zoomer with its scales. This might be necessary,
pixhawk's avatar
pixhawk committed
70 71 72 73 74 75
                  when the plot is in a state with pending scale changes.

  \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase()
*/

QwtPlotZoomer::QwtPlotZoomer(int xAxis, int yAxis,
76
                             QwtPlotCanvas *canvas, bool doReplot):
pixhawk's avatar
pixhawk committed
77 78 79 80 81 82 83 84 85 86 87 88
    QwtPlotPicker(xAxis, yAxis, canvas)
{
    if ( canvas )
        init(RectSelection & ClickSelection, ActiveOnly, doReplot);
}

/*!
  Create a zoomer for a plot canvas.

  \param xAxis X axis of the zoomer
  \param yAxis Y axis of the zoomer
  \param selectionFlags Or'd value of QwtPicker::RectSelectionType and
89
                        QwtPicker::SelectionMode.
pixhawk's avatar
pixhawk committed
90 91 92 93
                        QwtPicker::RectSelection will be auto added.
  \param trackerMode Tracker mode
  \param canvas Plot canvas to observe, also the parent object
  \param doReplot Call replot for the attached plot before initializing
94
                  the zoomer with its scales. This might be necessary,
pixhawk's avatar
pixhawk committed
95 96 97 98 99 100 101 102 103
                  when the plot is in a state with pending scale changes.

  \sa QwtPicker, QwtPicker::setSelectionFlags(), QwtPicker::setRubberBand(),
      QwtPicker::setTrackerMode

  \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase()
*/

QwtPlotZoomer::QwtPlotZoomer(int xAxis, int yAxis, int selectionFlags,
104
                             DisplayMode trackerMode, QwtPlotCanvas *canvas, bool doReplot):
pixhawk's avatar
pixhawk committed
105 106 107 108 109 110 111
    QwtPlotPicker(xAxis, yAxis, canvas)
{
    if ( canvas )
        init(selectionFlags, trackerMode, doReplot);
}

//! Init the zoomer, used by the constructors
112 113
void QwtPlotZoomer::init(int selectionFlags,
                         DisplayMode trackerMode, bool doReplot)
pixhawk's avatar
pixhawk committed
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
{
    d_data = new PrivateData;

    d_data->maxStackDepth = -1;

    setSelectionFlags(selectionFlags);
    setTrackerMode(trackerMode);
    setRubberBand(RectRubberBand);

    if ( doReplot && plot() )
        plot()->replot();

    setZoomBase(scaleRect());
}

QwtPlotZoomer::~QwtPlotZoomer()
{
    delete d_data;
}

/*!
  \brief Limit the number of recursive zoom operations to depth.

  A value of -1 set the depth to unlimited, 0 disables zooming.
  If the current zoom rectangle is below depth, the plot is unzoomed.

  \param depth Maximum for the stack depth
  \sa maxStackDepth()
  \note depth doesn't include the zoom base, so zoomStack().count() might be
              maxStackDepth() + 1.
*/
void QwtPlotZoomer::setMaxStackDepth(int depth)
{
    d_data->maxStackDepth = depth;

149
    if ( depth >= 0 ) {
pixhawk's avatar
pixhawk committed
150 151
        // unzoom if the current depth is below d_data->maxStackDepth

152
        const int zoomOut =
pixhawk's avatar
pixhawk committed
153 154
            int(d_data->zoomStack.count()) - 1 - depth; // -1 for the zoom base

155
        if ( zoomOut > 0 ) {
pixhawk's avatar
pixhawk committed
156
            zoom(-zoomOut);
157 158
            for ( int i = int(d_data->zoomStack.count()) - 1;
                    i > int(d_data->zoomRectIndex); i-- ) {
pixhawk's avatar
pixhawk committed
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
                (void)d_data->zoomStack.pop(); // remove trailing rects
            }
        }
    }
}

/*!
  \return Maximal depth of the zoom stack.
  \sa setMaxStackDepth()
*/
int QwtPlotZoomer::maxStackDepth() const
{
    return d_data->maxStackDepth;
}

/*!
  Return the zoom stack. zoomStack()[0] is the zoom base,
  zoomStack()[1] the first zoomed rectangle.

  \sa setZoomStack(), zoomRectIndex()
*/
const QwtZoomStack &QwtPlotZoomer::zoomStack() const
{
    return d_data->zoomStack;
}

/*!
  \return Initial rectangle of the zoomer
  \sa setZoomBase(), zoomRect()
*/
QwtDoubleRect QwtPlotZoomer::zoomBase() const
{
    return d_data->zoomStack[0];
}

/*!
  Reinitialized the zoom stack with scaleRect() as base.

  \param doReplot Call replot for the attached plot before initializing
198
                  the zoomer with its scales. This might be necessary,
pixhawk's avatar
pixhawk committed
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
                  when the plot is in a state with pending scale changes.

  \sa zoomBase(), scaleRect() QwtPlot::autoReplot(), QwtPlot::replot().
*/
void QwtPlotZoomer::setZoomBase(bool doReplot)
{
    QwtPlot *plt = plot();
    if ( plt == NULL )
        return;

    if ( doReplot )
        plt->replot();

    d_data->zoomStack.clear();
    d_data->zoomStack.push(scaleRect());
    d_data->zoomRectIndex = 0;

    rescale();
}

/*!
  \brief Set the initial size of the zoomer.

  base is united with the current scaleRect() and the zoom stack is
  reinitalized with it as zoom base. plot is zoomed to scaleRect().
224

pixhawk's avatar
pixhawk committed
225
  \param base Zoom base
226

pixhawk's avatar
pixhawk committed
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
  \sa zoomBase(), scaleRect()
*/
void QwtPlotZoomer::setZoomBase(const QwtDoubleRect &base)
{
    const QwtPlot *plt = plot();
    if ( !plt )
        return;

    const QwtDoubleRect sRect = scaleRect();
    const QwtDoubleRect bRect = base | sRect;

    d_data->zoomStack.clear();
    d_data->zoomStack.push(bRect);
    d_data->zoomRectIndex = 0;

242
    if ( base != sRect ) {
pixhawk's avatar
pixhawk committed
243 244 245 246 247 248 249
        d_data->zoomStack.push(sRect);
        d_data->zoomRectIndex++;
    }

    rescale();
}

250 251
/*!
  Rectangle at the current position on the zoom stack.
pixhawk's avatar
pixhawk committed
252 253 254 255 256 257 258 259

  \sa zoomRectIndex(), scaleRect().
*/
QwtDoubleRect QwtPlotZoomer::zoomRect() const
{
    return d_data->zoomStack[d_data->zoomRectIndex];
}

260
/*!
pixhawk's avatar
pixhawk committed
261 262 263 264 265 266 267 268 269 270 271
  \return Index of current position of zoom stack.
*/
uint QwtPlotZoomer::zoomRectIndex() const
{
    return d_data->zoomRectIndex;
}

/*!
  \brief Zoom in

  Clears all rectangles above the current position of the
272
  zoom stack and pushs the intersection of zoomRect() and
pixhawk's avatar
pixhawk committed
273 274 275 276 277 278 279
  the normalized rect on it.

  \note If the maximal stack depth is reached, zoom is ignored.
  \note The zoomed signal is emitted.
*/

void QwtPlotZoomer::zoom(const QwtDoubleRect &rect)
280 281 282
{
    if ( d_data->maxStackDepth >= 0 &&
            int(d_data->zoomRectIndex) >= d_data->maxStackDepth ) {
pixhawk's avatar
pixhawk committed
283 284 285 286
        return;
    }

    const QwtDoubleRect zoomRect = d_data->zoomStack[0] & rect.normalized();
287 288 289
    if ( zoomRect != d_data->zoomStack[d_data->zoomRectIndex] ) {
        for ( uint i = int(d_data->zoomStack.count()) - 1;
                i > d_data->zoomRectIndex; i-- ) {
pixhawk's avatar
pixhawk committed
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
            (void)d_data->zoomStack.pop();
        }

        d_data->zoomStack.push(zoomRect);
        d_data->zoomRectIndex++;

        rescale();

        emit zoomed(zoomRect);
    }
}

/*!
  \brief Zoom in or out

  Activate a rectangle on the zoom stack with an offset relative
  to the current position. Negative values of offest will zoom out,
  positive zoom in. A value of 0 zooms out to the zoom base.

  \param offset Offset relative to the current position of the zoom stack.
  \note The zoomed signal is emitted.
  \sa zoomRectIndex()
*/
void QwtPlotZoomer::zoom(int offset)
{
    if ( offset == 0 )
        d_data->zoomRectIndex = 0;
317
    else {
pixhawk's avatar
pixhawk committed
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
        int newIndex = d_data->zoomRectIndex + offset;
        newIndex = qwtMax(0, newIndex);
        newIndex = qwtMin(int(d_data->zoomStack.count()) - 1, newIndex);

        d_data->zoomRectIndex = uint(newIndex);
    }

    rescale();

    emit zoomed(zoomRect());
}

/*!
  \brief Assign a zoom stack

  In combination with other types of navigation it might be useful to
  modify to manipulate the complete zoom stack.

  \param zoomStack New zoom stack
  \param zoomRectIndex Index of the current position of zoom stack.
                       In case of -1 the current position is at the top
                       of the stack.

  \note The zoomed signal might be emitted.
  \sa zoomStack(), zoomRectIndex()
*/
void QwtPlotZoomer::setZoomStack(
    const QwtZoomStack &zoomStack, int zoomRectIndex)
{
    if ( zoomStack.isEmpty() )
        return;

    if ( d_data->maxStackDepth >= 0 &&
351
            int(zoomStack.count()) > d_data->maxStackDepth ) {
pixhawk's avatar
pixhawk committed
352 353 354 355 356 357 358 359 360 361 362
        return;
    }

    if ( zoomRectIndex < 0 || zoomRectIndex > int(zoomStack.count()) )
        zoomRectIndex = zoomStack.count() - 1;

    const bool doRescale = zoomStack[zoomRectIndex] != zoomRect();

    d_data->zoomStack = zoomStack;
    d_data->zoomRectIndex = uint(zoomRectIndex);

363
    if ( doRescale ) {
pixhawk's avatar
pixhawk committed
364 365 366 367 368
        rescale();
        emit zoomed(zoomRect());
    }
}

369
/*!
pixhawk's avatar
pixhawk committed
370 371 372 373 374 375 376 377 378 379 380 381
  Adjust the observed plot to zoomRect()

  \note Initiates QwtPlot::replot
*/

void QwtPlotZoomer::rescale()
{
    QwtPlot *plt = plot();
    if ( !plt )
        return;

    const QwtDoubleRect &rect = d_data->zoomStack[d_data->zoomRectIndex];
382
    if ( rect != scaleRect() ) {
pixhawk's avatar
pixhawk committed
383 384 385 386 387
        const bool doReplot = plt->autoReplot();
        plt->setAutoReplot(false);

        double x1 = rect.left();
        double x2 = rect.right();
388 389
        if ( plt->axisScaleDiv(xAxis())->lBound() >
                plt->axisScaleDiv(xAxis())->hBound() ) {
pixhawk's avatar
pixhawk committed
390 391 392 393 394 395 396
            qSwap(x1, x2);
        }

        plt->setAxisScale(xAxis(), x1, x2);

        double y1 = rect.top();
        double y2 = rect.bottom();
397 398
        if ( plt->axisScaleDiv(yAxis())->lBound() >
                plt->axisScaleDiv(yAxis())->hBound() ) {
pixhawk's avatar
pixhawk committed
399 400 401 402 403 404 405 406 407 408 409 410 411
            qSwap(y1, y2);
        }
        plt->setAxisScale(yAxis(), y1, y2);

        plt->setAutoReplot(doReplot);

        plt->replot();
    }
}

/*!
  Reinitialize the axes, and set the zoom base to their scales.

412
  \param xAxis X axis
pixhawk's avatar
pixhawk committed
413 414 415 416 417
  \param yAxis Y axis
*/

void QwtPlotZoomer::setAxis(int xAxis, int yAxis)
{
418
    if ( xAxis != QwtPlotPicker::xAxis() || yAxis != QwtPlotPicker::yAxis() ) {
pixhawk's avatar
pixhawk committed
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
        QwtPlotPicker::setAxis(xAxis, yAxis);
        setZoomBase(scaleRect());
    }
}

/*!
   Qt::MidButton zooms out one position on the zoom stack,
   Qt::RightButton to the zoom base.

   Changes the current position on the stack, but doesn't pop
   any rectangle.

   \note The mouse events can be changed, using
         QwtEventPattern::setMousePattern: 2, 1
*/
void QwtPlotZoomer::widgetMouseReleaseEvent(QMouseEvent *me)
{
    if ( mouseMatch(MouseSelect2, me) )
        zoom(0);
    else if ( mouseMatch(MouseSelect3, me) )
        zoom(-1);
    else if ( mouseMatch(MouseSelect6, me) )
        zoom(+1);
442
    else
pixhawk's avatar
pixhawk committed
443 444 445 446
        QwtPlotPicker::widgetMouseReleaseEvent(me);
}

/*!
447
   Qt::Key_Plus zooms out, Qt::Key_Minus zooms in one position on the
pixhawk's avatar
pixhawk committed
448 449 450 451 452 453 454 455 456 457 458
   zoom stack, Qt::Key_Escape zooms out to the zoom base.

   Changes the current position on the stack, but doesn't pop
   any rectangle.

   \note The keys codes can be changed, using
         QwtEventPattern::setKeyPattern: 3, 4, 5
*/

void QwtPlotZoomer::widgetKeyPressEvent(QKeyEvent *ke)
{
459
    if ( !isActive() ) {
pixhawk's avatar
pixhawk committed
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
        if ( keyMatch(KeyUndo, ke) )
            zoom(-1);
        else if ( keyMatch(KeyRedo, ke) )
            zoom(+1);
        else if ( keyMatch(KeyHome, ke) )
            zoom(0);
    }

    QwtPlotPicker::widgetKeyPressEvent(ke);
}

/*!
  Move the current zoom rectangle.

  \param dx X offset
  \param dy Y offset

  \note The changed rectangle is limited by the zoom base
*/
void QwtPlotZoomer::moveBy(double dx, double dy)
{
    const QwtDoubleRect &rect = d_data->zoomStack[d_data->zoomRectIndex];
    move(rect.left() + dx, rect.top() + dy);
}

/*!
  Move the the current zoom rectangle.

  \param x X value
  \param y Y value

  \sa QwtDoubleRect::move
  \note The changed rectangle is limited by the zoom base
*/
void QwtPlotZoomer::move(double x, double y)
{
    if ( x < zoomBase().left() )
        x = zoomBase().left();
    if ( x > zoomBase().right() - zoomRect().width() )
        x = zoomBase().right() - zoomRect().width();

    if ( y < zoomBase().top() )
        y = zoomBase().top();
    if ( y > zoomBase().bottom() - zoomRect().height() )
        y = zoomBase().bottom() - zoomRect().height();

506
    if ( x != zoomRect().left() || y != zoomRect().top() ) {
pixhawk's avatar
pixhawk committed
507 508 509 510 511 512 513 514 515 516 517
        d_data->zoomStack[d_data->zoomRectIndex].moveTo(x, y);
        rescale();
    }
}

/*!
  \brief Check and correct a selected rectangle

  Reject rectangles with a hight or width < 2, otherwise
  expand the selected rectangle to a minimum size of 11x11
  and accept it.
518

pixhawk's avatar
pixhawk committed
519
  \return true If rect is accepted, or has been changed
520
          to a accepted rectangle.
pixhawk's avatar
pixhawk committed
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
*/

bool QwtPlotZoomer::accept(QwtPolygon &pa) const
{
    if ( pa.count() < 2 )
        return false;

    QRect rect = QRect(pa[0], pa[int(pa.count()) - 1]);
#if QT_VERSION < 0x040000
    rect = rect.normalize();
#else
    rect = rect.normalized();
#endif

    const int minSize = 2;
    if (rect.width() < minSize && rect.height() < minSize )
537
        return false;
pixhawk's avatar
pixhawk committed
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559

    const int minZoomSize = 11;

    const QPoint center = rect.center();
    rect.setSize(rect.size().expandedTo(QSize(minZoomSize, minZoomSize)));
    rect.moveCenter(center);

    pa.resize(2);
    pa[0] = rect.topLeft();
    pa[1] = rect.bottomRight();

    return true;
}

/*!
  \brief Limit zooming by a minimum rectangle

  \return zoomBase().width() / 10e4, zoomBase().height() / 10e4
*/
QwtDoubleSize QwtPlotZoomer::minZoomSize() const
{
    return QwtDoubleSize(
560 561 562
               d_data->zoomStack[0].width() / 10e4,
               d_data->zoomStack[0].height() / 10e4
           );
pixhawk's avatar
pixhawk committed
563 564
}

565
/*!
pixhawk's avatar
pixhawk committed
566 567 568 569 570 571 572
  Rejects selections, when the stack depth is too deep, or
  the zoomed rectangle is minZoomSize().

  \sa minZoomSize(), maxStackDepth()
*/
void QwtPlotZoomer::begin()
{
573
    if ( d_data->maxStackDepth >= 0 ) {
pixhawk's avatar
pixhawk committed
574 575 576 577 578
        if ( d_data->zoomRectIndex >= uint(d_data->maxStackDepth) )
            return;
    }

    const QwtDoubleSize minSize = minZoomSize();
579 580
    if ( minSize.isValid() ) {
        const QwtDoubleSize sz =
pixhawk's avatar
pixhawk committed
581 582 583
            d_data->zoomStack[d_data->zoomRectIndex].size() * 0.9999;

        if ( minSize.width() >= sz.width() &&
584
                minSize.height() >= sz.height() ) {
pixhawk's avatar
pixhawk committed
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
            return;
        }
    }

    QwtPlotPicker::begin();
}

/*!
  Expand the selected rectangle to minZoomSize() and zoom in
  if accepted.

  \sa QwtPlotZoomer::accept()a, QwtPlotZoomer::minZoomSize()
*/
bool QwtPlotZoomer::end(bool ok)
{
    ok = QwtPlotPicker::end(ok);
    if (!ok)
        return false;

    QwtPlot *plot = QwtPlotZoomer::plot();
    if ( !plot )
        return false;

    const QwtPolygon &pa = selection();
    if ( pa.count() < 2 )
        return false;

    QRect rect = QRect(pa[0], pa[int(pa.count() - 1)]);
#if QT_VERSION < 0x040000
    rect = rect.normalize();
#else
    rect = rect.normalized();
#endif


    QwtDoubleRect zoomRect = invTransform(rect).normalized();

    const QwtDoublePoint center = zoomRect.center();
    zoomRect.setSize(zoomRect.size().expandedTo(minZoomSize()));
    zoomRect.moveCenter(center);

    zoom(zoomRect);

    return true;
}

/*!
  Set the selection flags
633

pixhawk's avatar
pixhawk committed
634
  \param flags Or'd value of QwtPicker::RectSelectionType and
635
               QwtPicker::SelectionMode. The default value is
pixhawk's avatar
pixhawk committed
636 637 638 639 640 641 642 643 644 645 646 647 648 649
               QwtPicker::RectSelection & QwtPicker::ClickSelection.

  \sa selectionFlags(), SelectionType, RectSelectionType, SelectionMode
  \note QwtPicker::RectSelection will be auto added.
*/

void QwtPlotZoomer::setSelectionFlags(int flags)
{
    // we accept only rects
    flags &= ~(PointSelection | PolygonSelection);
    flags |= RectSelection;

    QwtPlotPicker::setSelectionFlags(flags);
}