Seite 1 von 1

[QWT] Scrollzoomer von den Examples beim Plotten

Verfasst: 7. Juli 2007 16:58
von TheMetalManiac
Hallo!

Nachdem ich mich nun in die Plotterei mit qwt eingearbeitet habe und einen recht schönen Plot zustande bekommen habe, dachte ich mir, noch das Zooming vom Bespiel (realtime_plot) einzufügen.

Das klappt auch gut, in dem erstdefinierten Bereich (Achsenskalierung).

Nun habe ich in dem Plot eine auswahloption für die Skalierung der x-Achse definiert.
1. Entweder fortlaufend (x-Achse einfach bei jedem Event verändern, oder gibt es eine schönere Lösung?)

Code: Alles auswählen

if (! xVectorPtr->isEmpty())
		{	
			std::cout <<"mitlaufen!! "<< refresh <<"\n";
			scaleIndex = xVectorPtr->last();
			if (scaleIndex > 8)
			{
				setAxisScale(QwtPlot::xBottom,scaleIndex-8,scaleIndex+1.9);
			}
		}
		else  
			scaleIndex = xVectorPtr->last(); 
2.alle 5sec ein update der achsen

Code: Alles auswählen

if (! xVectorPtr->isEmpty())
		{
			std::cout <<"aktualisierend!! "<< refresh <<"\n";	
			scaleIndex = xVectorPtr->last();
			if (scaleIndex > scaleValue)
			{
				setAxisScale(QwtPlot::xBottom,scaleIndex-5,scaleIndex+5);
				scaleValue += 5;	
			}
		}
		else  
			scaleIndex = xVectorPtr->last();


Da der Zoomer die Startskalierung des Canvas übernimmt, habe ich meinen Skalierbereich zunächst auf 1000 sec gesetzt (absolute Notlösung :) ) und danach wieder auf meine ursprüngliche Skalierung geswitched (=10 sec). Wie gesagt, eine absolute Notlösung.

Code: Alles auswählen

 setAxisScale(QwtPlot::xBottom, 0, 1000);				//zoomer need to be initialized by high xvalue, to insert the scrollbar

	// enable zooming
    Zoomer *zoomer = new Zoomer(canvas());
    zoomer->setRubberBandPen(QPen(Qt::red, 2, Qt::DotLine));
    zoomer->setTrackerPen(QPen(Qt::red));
    
    setAxisScale(QwtPlot::xBottom, 0, 10);

    setAxisTitle(QwtPlot::yLeft, "y");
Meine Kenntnisse in C++ und QWT sind jetzt leider erst 2 Monate alt und daher verstehe ich den Zoomer leider noch nicht so ganz. Daher wäre es super, wenn ich einen Tipp bekäme, sodass man im Zoomer den Bereich selbst anpassen kann. Zur Zeit wird ja einfach nur ein QwtPlotCanvas übergeben, bei der instanziierung.

Hier nochmal mein ganzer Code für euch:

Code: Alles auswählen


#include <stdlib.h>
#include <qwt_painter.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot_marker.h>
#include <qwt_plot_curve.h>
#include <qwt_scale_widget.h>
#include <qwt_legend.h>
#include <qwt_scale_draw.h>
#include <qtimer.h>
#include <qwt_plot_grid.h>
#include <qwt_plot_canvas.h>
#include <qwt_plot_layout.h>
#include <QAbstractScrollArea>
#include <iostream.h>

#include "data_plot.h"
#include "scrollzoomer.h"


const unsigned int c_rangeMax = 1000;

class Zoomer: public ScrollZoomer
{
public:
    Zoomer(QwtPlotCanvas *canvas):
        ScrollZoomer(canvas)
    {
    }

    virtual void rescale()
    {
        QwtScaleWidget *scaleWidget = plot()->axisWidget(yAxis());
        QwtScaleDraw *sd = scaleWidget->scaleDraw();

        int minExtent = 0;
        if ( zoomRectIndex() > 0 )
        {
            // When scrolling in vertical direction
            // the plot is jumping in horizontal direction
            // because of the different widths of the labels
            // So we better use a fixed extent.

            minExtent = sd->spacing() + sd->majTickLength() + 1;
            minExtent += sd->labelSize(
                scaleWidget->font(), c_rangeMax).width();
        }

        sd->setMinimumExtent(minExtent);

        ScrollZoomer::rescale();
    }
};
//
//  Initialize main window
//
DataPlot::DataPlot(QVector<double> *xVec, QVector<double> *yVec,QWidget* = NULL):
//    QwtPlot(parent),
    d_interval(0),
    d_timerId(-1)
  
{
	//QVector übernahme
	xVectorPtr = xVec;
    yVectorPtr = yVec;
 
    // Disable polygon clipping
    QwtPainter::setDeviceClipping(false);

    // We don't need the cache here
    canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, false);
    canvas()->setPaintAttribute(QwtPlotCanvas::PaintPacked, false);
	
#if QT_VERSION >= 0x040000
#ifdef Q_WS_X11
    canvas()->setAttribute(Qt::WA_PaintOnScreen, true);
#endif
#endif

    alignScales();
    
	setTitle("Oskar");
    insertLegend(new QwtLegend(), QwtPlot::BottomLegend);

    // Insert new curve
    plot = new QwtPlotCurve("Ausgang XY");					//Ausgang als QString übergeben
    plot->attach(this);

	
    //setData has to be set for (only) one time, then QwtPlotCurve knows his Database
	plot->setData(*xVectorPtr,*yVectorPtr);

    // Set curve styles
    plot->setPen(QPen(Qt::red));

	#if 0
    //  Insert zero line at y = 0
    QwtPlotMarker *mY = new QwtPlotMarker();
    mY->setLabelAlignment(Qt::AlignRight|Qt::AlignTop);
    mY->setLineStyle(QwtPlotMarker::HLine);
    mY->setYValue(0.0);
    mY->attach(this);
#endif

    // Axis 
    setAxisTitle(QwtPlot::xBottom, "Time/seconds");
    setAxisScale(QwtPlot::xBottom, 0, 1000);				//zoomer need to be initialized by high xvalue, to insert the scrollbar

	// enable zooming
    Zoomer *zoomer = new Zoomer(canvas());
    zoomer->setRubberBandPen(QPen(Qt::red, 2, Qt::DotLine));
    zoomer->setTrackerPen(QPen(Qt::red));
    
    setAxisScale(QwtPlot::xBottom, 0, 10);

    setAxisTitle(QwtPlot::yLeft, "y");
    setAxisScale(QwtPlot::yLeft, 0, 400);
    
    //setAutoScale()
    
    setTimerInterval(500.0); 				// in ms 

	
    //set scaleValue
	scaleValue = 5;
	
	refresh = false;
	
}
//
//  Set a plain canvas frame and align the scales to it
//
void DataPlot::alignScales()
{
    // The code below shows how to align the scales to
    // the canvas frame

    canvas()->setFrameStyle(QFrame::Box | QFrame::Plain );
    canvas()->setLineWidth(1);

    for ( int i = 0; i < QwtPlot::axisCnt; i++ )
    {
        QwtScaleWidget *scaleWidget = (QwtScaleWidget *)axisWidget(i);
        if ( scaleWidget )
            scaleWidget->setMargin(0);

        QwtScaleDraw *scaleDraw = (QwtScaleDraw *)axisScaleDraw(i);
        if ( scaleDraw )
            scaleDraw->enableComponent(QwtAbstractScaleDraw::Backbone, false);
    }
}
void DataPlot::setRefreshActual()
{	
	refresh = false;
}

void DataPlot::setRefreshConstant()
{
	refresh = true;
}

// TimerInterval in ms (= Refreshrate -> 0 is fast & 100 is slowly)
void DataPlot::setTimerInterval(double ms)			
{
    d_interval = qRound(ms);

    if ( d_timerId >= 0 )
    {
        killTimer(d_timerId);
        d_timerId = -1;
    }
    if (d_interval >= 0 )
        d_timerId = startTimer(d_interval);
}

//  Generate new values 
void DataPlot::timerEvent(QTimerEvent *)
{	
	
	
	if (!refresh)
	{
		//erste Möglichkeit: Achse mitlaufend
		if (! xVectorPtr->isEmpty())
		{	
			std::cout <<"mitlaufen!! "<< refresh <<"\n";
			scaleIndex = xVectorPtr->last();
			if (scaleIndex > 8)
			{
				setAxisScale(QwtPlot::xBottom,scaleIndex-8,scaleIndex+1.9);
			}
		}
		else  
			scaleIndex = xVectorPtr->last();
	}
	else
	{
		//zweite Möglichkeit: Achse alle 5s updaten
		if (! xVectorPtr->isEmpty())
		{
			std::cout <<"aktualisierend!! "<< refresh <<"\n";	
			scaleIndex = xVectorPtr->last();
			if (scaleIndex > scaleValue)
			{
				setAxisScale(QwtPlot::xBottom,scaleIndex-5,scaleIndex+5);
				scaleValue += 5;	
			}
		}
		else  
			scaleIndex = xVectorPtr->last();
	}
	
    plot->setData(*xVectorPtr,*yVectorPtr);
    replot();
}

Herzlichen Dank schonmal für die super Hilfe hier im Forum

TheMetalManiac

das zoomen bis 1000 auf der y-achse

Verfasst: 9. Juli 2007 19:35
von TheMetalManiac
Hallo nochmal....

Das Zoomen auf der Y-Achse bis 1000 verstehe ich noch nicht...Habe jetzt einfach mal ein

setAxisAutoScale(QwtPlot::yLeft);

eingefügt.

Eine weitere Notlösung.

Noch ein großes Problem hat sich nun herauskristallisiert:
Wenn der QVector leer ist, stürzt das Widget in sich zusammen.

Da im späteren Verlauf der Plot in eine Qui verschachtelt wird, worin erst die QVectoren initialisiert werden, ist das natürlich nicht sooo schön:

Fehler: Q_ASSERT(!isEmpty() in qvector.h in Zeile 206

Danke nochmal für die Unterstützung

Verfasst: 9. Juli 2007 23:03
von Uwe
Der Zoomer braucht eine initiales Rechteck, das sozusagen 100% entspricht.
Dieses Rechteck nimmt er entweder von dem aktuellen Zustand der Achsen ( Vorsicht, bei hängende Änderungen der Wertebereiche, die erst durch replot gültig werden), oder kann später durch QwtPlotZoomer::setZoomBase gesetzt/verändert werden.

HTH,
Uwe

immernoch Probleme mit dem zoomer

Verfasst: 11. Juli 2007 20:07
von TheMetalManiac
Hallo nochmal.

Leider habe ich immernoch Probleme mit dem Zoomer.
Mittlerweile bin ich auf setRawData umgestiegen (der QVector existiert ohnehin die ganze laufzeit).

Code: Alles auswählen

plot->setRawData(xVectorPtr->data(),yVectorPtr->data(),xVectorPtr->size());
Nun passiert es leider immerwieder, dass beim zoomen ein Laufzeitfehler entsteht, den ich nicht wegbekomme... :(.

Ebenfalls habe ich noch immer das Problem mit dem Zoomer.
Sollte ich die ZoomBase mit setZoomBase dynamisch anpassen? D.h. sobald ein bestimmter x Wert überschritten wird einfach eine neue Skalierung aufrufen?
Zur Zeit mache ich das ja so:

Code: Alles auswählen

    setAxisScale(QwtPlot::xBottom, 0, 10000);				

	// enable zooming
    Zoomer *zoomer = new Zoomer(canvas());
    zoomer->setRubberBandPen(QPen(Qt::blue, 2, Qt::DotLine));
    zoomer->setTrackerPen(QPen(Qt::blue));
    
    setAxisScale(QwtPlot::xBottom, 0, 100);
Das ist absolut gefuscht, ich weiß. Deswegen wüsste ich echt gerne, wie ich das eleganter machen könnte.

Vielen Dank für deine Hilfe Uwe :)...es kennt sich ja anscheinend kein ander so gut mit qwt aus.

Re: immernoch Probleme mit dem zoomer

Verfasst: 12. Juli 2007 07:37
von Uwe
Mittlerweile bin ich auf setRawData umgestiegen
setRawData ist die Schnittstelle für Situationen in denen man die Daten als C-Array vorliegen hat und sie nicht kopieren möchte. Da Deine Daten als QVector vorliegen (implicite sharing) ist das Verwenden von setRawData eine sinnlose Verschlechterung Deines Codes. Den fehlerhaften Zugriff in Dein Array hast Du damit sicher nicht gelöst - er wird halt nicht mehr geprüft.
Also, bau das mal zurück, starte den Debugger und schau auf den Aufrufstack, wie es zu dem Q_ASSERT kommt.
Nun passiert es leider immerwieder, dass beim zoomen ein Laufzeitfehler entsteht, ...
Wenn ich Dich richtig interpretiere kann sich der Ausschnitt Deines Plots sowohl durch Änderungen der Daten (a) als auch durch Interaktionen (Zoomen) des Anwenders (b) ändern ? Wenn ja mußt Du Dir als Erstes einmal überlegen, wie Du das organisieren willst - also z.B. Zustände einführen in denen Du jeweils nur (a) oder (b) zulässt.

Für die Implementierung hast Du zu beachten:
a) Interaktionen des Anwenders bedient der Zoomer automatisch. Dafür mußt Du nichts tun.
b) Nach Änderungen des Ausschnitts durch neue Daten, mußt Du zum Abschluß (nach dem replot) den Zoomer reinitialisieren:

Code: Alles auswählen

setAutoReplot(false);
setAxisScale(xAxis, ...);
setAxisScale(yAxis, ...);
replot();
_zoomer->setZoomBase();
setAutoReplot(true);
Vielen Dank für deine Hilfe Uwe :)...es kennt sich ja anscheinend kein ander so gut mit qwt aus.
Natürlich, ich bin der Autor.

HTH,
Uwe

Verfasst: 12. Juli 2007 19:27
von butze

Code: Alles auswählen

setRawData ist die Schnittstelle für Situationen in denen man die Daten als C-Array vorliegen hat und sie nicht kopieren möchte. Da Deine Daten als QVector vorliegen (implicite sharing) ist das Verwenden von setRawData eine sinnlose Verschlechterung Deines Codes. Den fehlerhaften Zugriff in Dein Array hast Du damit sicher nicht gelöst - er wird halt nicht mehr geprüft.

hmm bin selbst noch neu in qwt und kenn mich kaum aus. kurze Frage: steht nicht hinter einem QVector bloß ein C_Array?
A shared class consists of a pointer to a shared data block that contains a reference count and the data.

Mich interessiert das, weil ich sowas ähnliches auch bauen möchte. Habe zwei QVectoren, die fortlaufend Daten von Thread 1 bekommen(ca. 1000 pro sek) . In Thread 2 laufen die plottenden Threads(zeichnen alle 0,5sek)

Wenn ich nun eine deep copy mache, kommen die Plots nicht schnell genug hinterher... Weißt du da ne bessere Methode als setRawData?

Gruß

Verfasst: 13. Juli 2007 00:14
von Uwe
steht nicht hinter einem QVector bloß ein C_Array?
Lies am Besten: http://doc.trolltech.com/4.3/shared.htm ... tly-shared
Wenn Du 2 Vektoren verwendest, die Du wechselweise befüllst + übergibst, solltest Du ein "Copy on write" vermeiden können.
Wenn ich nun eine deep copy mache, kommen die Plots nicht schnell genug hinterher... Weißt du da ne bessere Methode als setRawData?
Alle Varianten von QwtPlotCurve::set(Raw)Data sind nur Convenience- (oder Legacy- ) Schnittstellen für QwtPlotCurve::setData(const QwtData&). QwtData stellt aber nur eine Schnittstelle zu den Daten zur Verfügung, ohne Vorgaben für die tatsächliche Speicherung zu machen ! ( In der Qt4 Terminologie: ein abstraktes Model ).

Also speichere Deine Daten so wie es für Dich am günstigsten ist und leite Dein eigenes QwtData Objekt ab, so daß die Kurve direkt auf Deine Daten zugreifen kann.

Uwe

PS: Im simple Beispiel siehst Du eine Ableitung von QwtData, die gar keine Speicherung benötigt.