Results 1 to 10 of 10

Thread: Implimentation advice for a large data plotting application.

  1. #1
    Join Date
    Jan 2010
    Posts
    28
    Thanks
    4
    Thanked 2 Times in 2 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11 Windows

    Question Implimentation advice for a large data plotting application.

    Hi
    I am writing a GUI application that will talk to either an A to D or D to A hardware card (Analogue to Digital, ...). I chose Qt and Qwt as they both seemed to offer the flexability, power and cross platform capabilities I needed.

    I have most of the Qt GUI stuff done now so I want to move onto the graph plotting. This however is where I have got stuck as I am not sure how best structure my internal data arrays to be able to generate plots efficiently.

    The data I have either comes from an A to D as a series of samples in the time domain, or is generated programatically as a series of samples in the time domain to send to a D to A. Both sets of data are represented internally to the library that actually communicates with the ADC or DAC hardware as an array of 32 bit integers as both ADCs and DACs are integer devices. I only need to define the magnitude values as time (from a sample clock) is the x axis.

    What I would like to do is plot the raw sample data as magnitude against time, or sample number. I also want to plot the FFT of that same data in another plot widget. Obviously I need to use another library to perform the FFT itself. The data will be of a single shot format, i.e. the user will define the length of data to capture with say the ADC and then at some point later the data will arrive to be plotted. I have a repeated capture mode also that simply performs single-shot captures, but as quickly as possible, plotting the data after each.

    I started by looking at some of the Qwt examples, notably the simple plot and also refresh test examples. However both seem to pass the QwtPlotCurve->setData() member a class function, in the simple examples case a QwtSyntheticPointData derived class and for the refresh example a QwtSeriesData derived class. This was not quite what I expected as I was looking for a data object, say a vector of floats, etc. When I looked into this a bit more I realised that something a little smart was going on with these examples, as the data was generated either on the fly or once for a set of parameters and then passed to the graph widget sample by sample. I may have this wrong but thats what seemed to be going on. I have to admit I found it hard going understanding how this worked as I found no documentation for the synthetic data class or the series data class.

    I am therefore not quite sure I understand how to provide data to the plot class in order to achieve the results I want efficiently without having to spend loads of time transforming and copying data. So a number of questions arrise from this which I would much appreciate some help with.

    1. Given a source of data sample in some type <T>, what is the best way to pass this to the QwtPlotCurve class? It looks like I may have to convert to doubles as this seems to be what most of the data types use?
    2. Given my data is effectively 1 D, i.e. y data only, and the data objects seem to expect 2 D, x/y data, do I need to create an x-axis or is there a means for the plot curve to infer this?
    3. When plotting data from a repeated capture, the values in the internal data buffer will be updated rather than reallocating a new buffer, how does this work with Qwt to reduce data copying as I noticed some members associated with setData() seemed to imply that a copy would be performed and some not?
    4. More generally how does the function/class derived data method work as used by the two examples?

    I hope all that makes some sense. I just want to try and start writing this section of the application in the most optimal way for the application and how Qwt is intended to work, rather than approach it from the wrong direction and spend huge amounts of time and effort manipulating data from this format to that to only realise a long way down the development line that all that was unnecessary as Qwt provides this or that feature to achieve the same goal.

    One other thing, I am using Qt 4.6.2 and was intending to use Qwt 6.x from svn. There was no real reason to start with the latter, apart from the newer features in it.

    Cheers

  2. #2
    Join Date
    Feb 2006
    Location
    Munich, Germany
    Posts
    3,309
    Thanked 879 Times in 827 Posts
    Qt products
    Qt3 Qt4 Qt/Embedded
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implimentation advice for a large data plotting application.

    A QwtSeriesData<QPointF> object is a bridge between the samples and the curve object, that is responsible for the representation of the samples on the plot canvas.

    If you don't want to make a copy of your arrays simply derive from QwtSeriesData<QPointF> and implement the virtual methods getting the values from your integer arrays. Be careful with YourData::copy() - copy the interface only.

    Qwt offers also a couple of classes derived from QwtSeriesData, that copy and store the samples ( in case of using Qt containers its not really copied, because those are implicitely shared ). These classes are often easier to use, but in your case you need to convert and copy your samples - f.e. into a QVector<QPointF>.

    I guess it helps to look at the implementation of QwtArraySeriesData to understand how to implement your individual type of data class.

    Uwe

  3. The following 2 users say thank you to Uwe for this useful post:

    Kok (23rd September 2010), mike_the_tv (25th February 2010)

  4. #3
    Join Date
    Jan 2010
    Posts
    28
    Thanks
    4
    Thanked 2 Times in 2 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11 Windows

    Default Re: Implimentation advice for a large data plotting application.

    Thanks Uwe for the swift reply. I now have something working as you suggested based on a QwtSeriesData<QPointF> which I have added to the simple test program in addition to the cos and sin plots. The class I created takes a pointer to my integer data, very similar to the QwtArraySeriesData class does with the QVector.

    Am I correct in assuming that the plot data has to be ether float or double, as the derived class templates on QPointF rather than QPoint?

    From what I now understand of the QwtSeriesData classes, when the QwtPlot needs to regenerate the view it retrieves every sample point value from my new data class via the sample() member, is that correct? In terms of large data sets is this approach generally efficient or is it best to consider converting my data into a better format for panning/zooming from my integer array?

    If I add zooming and panning to the plot, does the sample() member just get called with the clipped area needed for plotting, for example if zoomed to samples 100-900 out of say 0-1000000, then the sample() member is called with the index between 100-900?

    Is it best to fix the axis range or let the plot auto scale it? The reason I ask is that when I commented out the fixed axis range from the simple example to allow my integer only data to be more visible I found that the plot speed slowed down considerably. I did notice while looking at the QwtSeriesData class that the boundingRect() member had a warning about large data sets being slow. The implementation in my quick class knock up just set the QRect to a fixed range, so is it the QwtSyntheticPointData implementation that is slow then? I am likely to have series plot of up to 4 million points, potentially from 4 or more plot series on a single plot.

    Am I best using the Qwt 6.x trunk code or would I be better off using a 5.x release? I think the other reason I went for the 6.x line was that I had issues compiling against Qt 4.6.1/2 with VS 2005.

    Oh I remember the final question, is there a better source of documentation for these plot classes. I am using the Doxygen manual from sourceforge but as I said earlier I could not find reference to some of the deeper plot data classes. I would rather not post potentially "dumb" questions for forums if its explained in a manual somewhere which I have missed.

    Thanks for the help.

  5. #4
    Join Date
    Feb 2006
    Location
    Munich, Germany
    Posts
    3,309
    Thanked 879 Times in 827 Posts
    Qt products
    Qt3 Qt4 Qt/Embedded
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implimentation advice for a large data plotting application.

    Am I correct in assuming that the plot data has to be ether float or double, as the derived class templates on QPointF rather than QPoint?
    No, converting temporary QPointF samples from your integer arrays seems to be the right solution for me. Building an array of QPointF in advance would be a huge waste of memory and won't have any significant effect on the performance.

    If I add zooming and panning to the plot, does the sample() member just get called with the clipped area needed for plotting, for example if zoomed to samples 100-900 out of say 0-1000000, then the sample() member is called with the index between 100-900?
    No, QwtPlotCurve doesn't know about the internal order of your samples.

    If you need to zoom large datasets and want to have a responsive GUI I recommend to implement different level of details inside of your data class. F.e. you could use Douglas Peucker ( QwtWeedingCurveFitter ) or any specific weeding algo to calculate smaller data sets for the different zoom levels. Also activate the QwtPlotCurve::ClipPolygons flag ( assuming you want to display the curve as polygon ). Polygon clipping will reduce the number of points for detailed views, weeding for the others.

    Is it best to fix the axis range or let the plot auto scale it? The reason I ask is that when I commented out the fixed axis range from the simple example to allow my integer only data to be more visible I found that the plot speed slowed down considerably. I did notice while looking at the QwtSeriesData class that the boundingRect() member had a warning about large data sets being slow.
    Of course you have to implement the boundingRect method of your data class. F.e. calculate the bounding rectangle only when your samples are changing and store it. Then let YourData::boundingRect() return the stored rectangle.

    Am I best using the Qwt 6.x trunk code or would I be better off using a 5.x release? I think the other reason I went for the 6.x line was that I had issues compiling against Qt 4.6.1/2 with VS 2005.
    As far as I can see you don't need any new feature of Qwt 6.x (maybe the weeding algo) and the compiler issues are solved in the 5.2 SVN branch as well. But there are heavy API changes between Qwt 5-2 and Qwt 6.0. It's up to you to decide if you want to work with a development snapshot or if you prefer to use a stable release having in mind, that you might have to port your code later.

    Oh I remember the final question, is there a better source of documentation for these plot classes.
    The missing high level documentation is the weakest part of Qwt. This is subject of critics for a long time and I'm aware of this. The only reason why I didn't start to work on it is, that I want to have the Qwt 6.x release first.

    Uwe

  6. The following 2 users say thank you to Uwe for this useful post:

    Kok (23rd September 2010), mike_the_tv (1st March 2010)

  7. #5
    Join Date
    Jan 2010
    Posts
    28
    Thanks
    4
    Thanked 2 Times in 2 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11 Windows

    Default Re: Implimentation advice for a large data plotting application.

    Quote Originally Posted by Uwe View Post
    No, QwtPlotCurve doesn't know about the internal order of your samples.

    If you need to zoom large datasets and want to have a responsive GUI I recommend to implement different level of details inside of your data class. F.e. you could use Douglas Peucker ( QwtWeedingCurveFitter ) or any specific weeding algo to calculate smaller data sets for the different zoom levels. Also activate the QwtPlotCurve::ClipPolygons flag ( assuming you want to display the curve as polygon ). Polygon clipping will reduce the number of points for detailed views, weeding for the others.
    If I understand correctly then, I need to add a type of filtering class to my data class to return effectively a lower resolution version of the data it internally stores to the QwtPlot (or QwtPlotCurve not sure which)?

    You suggested I looked at the QwtWeedingCurveFitter, however I don't seem to be able to find this, where should I be looking?

    I was also wondering if there is a description somewhere as to how the QwtPlotZoomer works with a QwtPlot and its associated QwtPlotCurve classes (and ultimately my data class derived from QwtSeriesData). I have added my data class as an additional plot line to the simple example. I have also now added a zoomer from a previous Qwt 5.2. This all works really nicely. I can rubber band an area and it zooms in, however I was a little surprised when the sample index that my data class was asked for always ranged over the entire data set. I had expected the zoom area to have been the bound on this.

    Oddly if you track up the call stack you eventually get to a QwtPlotAbstractSeriesItem::draw() member which calls drawSeries() with a from and to of 0 and -1, which in the drawSeries() member are defined as the index of the first and last point to be plotted. Why is this not the first and last point identified from the zoomer? Is it the QwtPlot that discards data points which fall outsize of its zoom range?

    Cheers
    Last edited by mike_the_tv; 1st March 2010 at 16:33.

  8. The following user says thank you to mike_the_tv for this useful post:

    Kok (23rd September 2010)

  9. #6
    Join Date
    Feb 2006
    Location
    Munich, Germany
    Posts
    3,309
    Thanked 879 Times in 827 Posts
    Qt products
    Qt3 Qt4 Qt/Embedded
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implimentation advice for a large data plotting application.

    If I understand correctly then, I need to add a type of filtering class to my data class to return effectively a lower resolution version of the data it internally stores to the QwtPlot (or QwtPlotCurve not sure which)?
    Inside of your data object. I recommend to connect something to the QwtScaleWidget::scaleDivChanged() signals to activate the best resolution for the current scale ranges.

    You suggested I looked at the QwtWeedingCurveFitter, however I don't seem to be able to find this, where should I be looking?
    qwt_curve_fitter.[h|cpp] - didn't you write, that you are on trunk ?

    I was also wondering if there is a description somewhere as to how the QwtPlotZoomer works with a QwtPlot and its associated QwtPlotCurve classes (and ultimately my data class derived from QwtSeriesData).
    The zoomer manipulates the scales - your curves and your data object are completely unrelated. Note that the plot has current scale ranges, but the zoom stack is an internal thing of the zoomer. For rendering the content of the plot canvas it is unimportant if the scales are the result of a zoom operation or not.

    ... however I was a little surprised when the sample index that my data class was asked for always ranged over the entire data set. I had expected the zoom area to have been the bound on this.
    In general the index of a point has nothing to do with its coordinates ( start the realtime example, zoom in and you will understand immediately ) !

    I guess in your specific situation the values are ordered, what could be used to optimize painting but using specific characteristics of a series can only be implemented, where these are known: your data object.

    Why is this not the first and last point identified from the zoomer?
    Again, there are not first and last points according to coordinates. But even coordinates, that are outside of the scale ranges might be important: f.e. in case of QwtPlotCurve::Lines the connecting line might be intersecting the visible area.

    Is it the QwtPlot that discards data points which fall outsize of its zoom range
    QwtPlotCurve does some optimizations like polygon clipping or discarding points depending on the type of visualization. But it can't do it without knowing the points.

    Uwe

  10. The following user says thank you to Uwe for this useful post:

    Kok (23rd September 2010)

  11. #7
    Join Date
    Jan 2010
    Posts
    28
    Thanks
    4
    Thanked 2 Times in 2 Posts
    Qt products
    Qt4
    Platforms
    Unix/X11 Windows

    Default Re: Implimentation advice for a large data plotting application.

    qwt_curve_fitter.[h|cpp] - didn't you write, that you are on trunk ?
    Yes I am on the trunk and I searched for QwtWeedingCurveFitter using Visual Studios search in files, but for some reason it found nothing. Now I go and look for the file you say I can find it. Not quite sure what went on there .

    In general the index of a point has nothing to do with its coordinates ( start the realtime example, zoom in and you will understand immediately ) !

    I guess in your specific situation the values are ordered, what could be used to optimize painting but using specific characteristics of a series can only be implemented, where these are known: your data object.
    OK point taken. The realtime example plots random point whos position in the plot area bares no relationship to its position in the data set. I guess I was assuming that a data object based on a QwtSeriesData class may make some assumptions about the ordering or type of data it contained and therefore optimise access at different zoom/crops.

    Again, there are not first and last points according to coordinates. But even coordinates, that are outside of the scale ranges might be important: f.e. in case of QwtPlotCurve::Lines the connecting line might be intersecting the visible area.
    Again I had not considered this as you would need to know points outside the visible area in order to correctly plot the first and last visible points.

    Inside of your data object. I recommend to connect something to the QwtScaleWidget::scaleDivChanged() signals to activate the best resolution for the current scale ranges.
    ...
    The zoomer manipulates the scales - your curves and your data object are completely unrelated. Note that the plot has current scale ranges, but the zoom stack is an internal thing of the zoomer. For rendering the content of the plot canvas it is unimportant if the scales are the result of a zoom operation or not.
    ...
    QwtPlotCurve does some optimizations like polygon clipping or discarding points depending on the type of visualization. But it can't do it without knowing the points.
    I am still confused about this and I think it comes from not fully understanding the interaction between the various components within a plot. If you can bare with me on this I would really appreciate a quick summary of how things connect and interact (or please if there is a concise summary document somewhere I have missed point me at it so I don't waste your time .

    What I think I understand at the moment is this. If I want to plot some data I need a QwtPlot object, or at least something derived from this. I am not sure at the moment whether deriving from QwtPlot is necessary but most of the examples do.

    The to provide my data to the QwtPlot I need one or more QwtPlotCurve objects which I attach to my QwtPlot. My QwtPlotCurve is supplied with data via a QwtSeriesData object, which in my case is a derived data class based on QwtSeriesData that returns QPointF() values for samples specified by the sample index with a call to the sample() member.

    From tracing with the debugger I can see that when my QwtPlot wants to plot my data, the sample() member of my derived data class is called for each data point, QwtPlot then does something with this data to cull points outside the current view, but ultimately results in my data appearing on screen.

    Where my understanding now breaks down is how zooming and panning (preferably scrolling) fits in with all this. I also don't understand how the weeding out of excess data is going to work, even now I have found the weeding class. I have done similar data plotting in a previous version of my application. In this I used MFC and DirectX. Now in this set up if my plot width, in pixels, was less than the number of data points to display, i.e. we have way more detail than can be physically plotted, my code would step over each display pixel, calculate how many data samples this single pixel was representing, and then calculate a single value for all those data points to display at that pixel location. In my case I did a min/max and plotted a vertical line to indicate the min/max extent on the data.

    Now the way QwtPlot works is probably completely different to what I have done previously. I have tried looking at the QwtWeedingCurveFitter() class and attached it to my QwtPlotCurve class via setCurveFitter(), however when I debug the code to work out how things connect and interact, I don;t see any of the weeding class members being called. How and where does this class get called?

    I also don't really understand how the zooming works. You had said that the QwtPlotCurves and my data object are unrelated to the zoomer, this just affecting the scales on the QwtPlot I assume. You also said that I should use the QwtScaleWidgets scaleDivChanged() signal to select the best data resolution, however this signal does not seem to supply any parameters about the scale change, so I am not quite sure how I should use this notification.

    The sort of effect I am after in the end is similar to your realtime example, except that the data will be available in one go and will be a linear time series plot. The ability to zoom and then pan the plot is what I need to match my original application under MFC/DirectX, however I don't really understand how to achieve this. When I tried to follow what the example was doing I quickly got lost because I think I am missing this fundemental understanding of how the various bits fit together.

    Apologies for such a long reply, and many thanks so far for your assistance.

  12. The following user says thank you to mike_the_tv for this useful post:

    Kok (23rd September 2010)

  13. #8
    Join Date
    Feb 2006
    Location
    Munich, Germany
    Posts
    3,309
    Thanked 879 Times in 827 Posts
    Qt products
    Qt3 Qt4 Qt/Embedded
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implimentation advice for a large data plotting application.

    I am still confused about this and I think it comes from not fully understanding the interaction between the various components within a plot.
    Looks like you are trying to understand something, that is not there. Things are much simpler, than you expect:

    1) The plot widget knows the transformations and scale ranges and passes them to the internal render engine. But it doesn't know much about the plot items - beside a couple of informations accessible via the QwtPlotItem base class.

    2) There are 2 types of plot items: decorators ( like the grid or scales items ) or items representing some type of data. F.e. a curve represents a series of 2D points, a spectrogram represents raster data.

    3) Zoomer, panner and other helper objects translate user interactions into scale changes. The plot widget doesn't know these helpers and it is of no importance if the scales were set from a zoomer, the autoscaler or are assigned from your code ( setAxisScale() ).

    I have done similar data plotting in a previous version of my application. In this I used MFC and DirectX. Now in this set up if my plot width, in pixels, was less than the number of data points to display, i.e. we have way more detail than can be physically plotted, my code would step over each display pixel, calculate how many data samples this single pixel was representing, and then calculate a single value for all those data points to display at that pixel location. In my case I did a min/max and plotted a vertical line to indicate the min/max extent on the data.
    You can do this as well (overloading QwtPlotCurve), but Douglas Peucker has more potential as you don't need the widget geometry. You can resample your points in advance and don't need to slow down resize ( or even worse repaint ) operations.

    Now the way QwtPlot works is probably completely different to what I have done previously. I have tried looking at the QwtWeedingCurveFitter() class and attached it to my QwtPlotCurve class via setCurveFitter(),
    This way ( you forgot to enable the QwtPlotCurve::Fitted attribute ) the curve resamples the points after they have been translated into widget coordinates each time your curve is painted. Because Douglas Peucker is an expensive algo this will have a negative effect and slow down your repaints.

    Instead I recommend to use QwtWeedingCurveFitter to calculate a couple of arrays with less points from your series only once - when you assign your samples. You find your arrays by increasing the tolerance parameter until the number of points is below a certain limit ( f.e 1000 points ). A good step size for the tolerance depends on the bounding rect of your series, but I guess you don't need to have more than 5 arrays.

    Then the only thing you need to do is to activate one of your arrays depending on the current scale ranges ( f.e. in a slot connected to scaleDivChanged() ). You can find the current scale divisions by plot->axisScaleDiv(...). Of course you can add some additional optimizations (calculating start/end indices) using the order of your points here too.

    When the plot displays large scale ranges your data object will return less points from a much smaller array. If the scale ranges are small your data object will return many points, but most of them will be clipped. In both situation the number of paint operations is heavily reduced and you will see a good performance for painting your plot. ( Implementing a such a data object calculating different level of details internally is on my TODO list. )

    Uwe

  14. The following user says thank you to Uwe for this useful post:

    Kok (23rd September 2010)

  15. #9
    Join Date
    Jun 2010
    Posts
    1
    Qt products
    Qt3 Qt4
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implimentation advice for a large data plotting application.

    Quote Originally Posted by Uwe View Post
    When the plot displays large scale ranges your data object will return less points from a much smaller array. If the scale ranges are small your data object will return many points, but most of them will be clipped. In both situation the number of paint operations is heavily reduced and you will see a good performance for painting your plot. ( Implementing a such a data object calculating different level of details internally is on my TODO list. )
    Uwe
    The implementation of the Douglas-Puecker algorithm in QwtWeedingCurveFitter works perfectly and the painting performance gets much better with large datasets. However, when I scroll the plot (using a scrollzoomer), the algorithm is called unnecessarily even though the level of resolution did not change. This could pose a problem when really large datasets are loaded. Another feature that could be useful is to allow users to control the display of symbols either on the original data points or on the reduced data points. Maybe it is already implemented somewhere, I just did not do any research on that. Thank you for the great job!

  16. #10
    Join Date
    Feb 2006
    Location
    Munich, Germany
    Posts
    3,309
    Thanked 879 Times in 827 Posts
    Qt products
    Qt3 Qt4 Qt/Embedded
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Implimentation advice for a large data plotting application.

    However, when I scroll the plot (using a scrollzoomer), the algorithm is called unnecessarily even though the level of resolution did not change.
    It's your code, that decides, when to rebuild the internal datasets. As long as those doesn't use clipping I can't see any reason why you should rebuild them, when the resolution doesn't change significantly.

    Another feature that could be useful is to allow users to control the display of symbols either on the original data points or on the reduced data points. Maybe it is already implemented somewhere
    You would have to overload QwtPlotCurve::drawSymbols(), but unfortunately it's less comfortable as it sounds, because there is some substance in the implementation of the base class you would have to copy.

    Uwe

Similar Threads

  1. Efficiently plotting 2d data in a QGraphicsscene
    By xenome in forum Qt Programming
    Replies: 0
    Last Post: 5th September 2009, 16:58
  2. QPrinter, QPainter, and Large Amounts of Data
    By millsks in forum Qt Programming
    Replies: 0
    Last Post: 17th March 2009, 19:26
  3. Widget for data plotting
    By Benjamin in forum Qt Programming
    Replies: 3
    Last Post: 12th February 2009, 16:38
  4. Replot large wav file data
    By Sachtech in forum Qwt
    Replies: 1
    Last Post: 6th January 2009, 10:12
  5. QWT and large amounts of data
    By ko9 in forum Qwt
    Replies: 1
    Last Post: 17th October 2007, 06:28

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.