Qt Charting Component - Feedback request



  • Hey forum,

    I have started work on a charting/plotting library based on the QGraphicsView framework. I would just like to get some feedback on if there is interest in such a component and if the initial API I have come up with looks reasonable to you guys and gals.

    Screenshots of some of the examples I have created can be found "here":http://gallery.theharmers.co.uk/index.php?/category/5

    Although I said it is based upon QGraphicsView it is of course trivial to wrap this in a QWidget. The first example does just this using the provided ZPlotWidget class:

    @
    #include <QApplication>

    #include <zdataseries.h>
    #include <zdataset.h>
    #include <zplot.h>
    #include <zplotwidget.h>

    using namespace PlotLib;

    int main( int argc, char *argv[] )
    {
    QApplication app( argc, argv );

    // Create some data
    QVector<QPointF> data = ( QVector<QPointF>()
        << QPointF( -2.0, 3.96 ) << QPointF( -1.0, 1.0 ) << QPointF( 0.0, 0.0 )
        << QPointF( 1.0, 1.0 )  << QPointF( 2.0, 3.93 ) );
    ZDataSetPtr dataSet( new ZDataSet( data ) );
    
    // Now plot it
    ZPlotWidget w;
    ZDataSeries* series = w.plot()->addDataSeries( dataSet, "Series 1", Point | Line );
    series->setLegendText( "Series 1" );
    
    w.show();
    return app.exec();
    

    }
    @

    The above application produces this "plot":http://gallery.theharmers.co.uk/picture.php?/13/category/5

    The line that does most of the work is:

    @
    ZDataSeries* series = w.plot()->addDataSeries( dataSet, "Series 1", Point | Line );
    @

    This tells the plot to add a new data series (the graphical representation of the data) which we can refer to by the name "Series 1" and that this data series should plot the data as points and connecting lines.

    ZDataSetPtr is just a QSharedPointer to a ZDataSet object and ZDataSet is a convenience implementation of an interface I call ZAbstractDataSet. This acts as the interface to the plotting library much as QAbstractItemModel does for the Qt ModelView framework.

    Other subclasses of ZAbstractDataSet are possible and in fact I already have one that acts as a adaptor/bridge for QAbstractItemModels. This makes it trivial to plot data contained in your existing models. Such an adaptor can be created and setup like this:

    @
    ZModelDataSetPtr dataSet( new ZModelDataSet );
    dataSet->setModel( m_model );
    dataSet->setDataDescription( TwoDimensional );
    dataSet->setVariableMapping( "x", 0 );
    dataSet->setVariableMapping( "y", 1 );
    @

    This creates a suitable data set and maps column 0 to the "x" variable and column 1 to the "y" variable. The ZModelDataSet can then be plotted just like any other data set. An example using this code to plot data from a table model can be seen "here":http://gallery.theharmers.co.uk/picture.php?/7/category/5. Not visible in this screenshot is that the points/lines are animated when the table model is edited via the QTableView in the left pane.

    There are quite a few features already implemented including:

    Plot types

    Point

    Line

    Step line

    Spline

    Area line

    Area step line

    Area spline

    Pie

    Bubble plots (for scalar fields)

    Vector plots

    More to come including contour and spectrogram (density) plots, bar, column etc

    Wide selection of point styles

    Basic legend support

    Dockable layout for QGraphicsView (not animated yet but you can see it used in "examples":http://gallery.theharmers.co.uk/picture.php?/6/category/5 for legend docker and it allows docking inside or outside for each dimension).

    Plotting of data

    Plotting of functions ("C++":http://gallery.theharmers.co.uk/picture.php?/8/category/5 and from "QtScript":http://gallery.theharmers.co.uk/picture.php?/9/category/5 )

    "Multiple plots":http://gallery.theharmers.co.uk/picture.php?/12/category/5

    Custom colouring and scaling of vector plots and bubble plots from user supplied functions

    Support for theming of plots (still in development)

    Animations

    Linear and logarithmic axes and scales

    Support for QDateTimes (still in development)

    Longer term I will also attempt to add in a declarative interface to enable such charts/plots to be used from within QML. I still need to look into how to do this in detail though.

    If there is sufficient interest in such a component I will continue to develop it and release it as soon as possible. I realise there is still a great deal I have not yet shown in terms of API but this should be enough to get a discussion going.

    All thoughts/flames/comments welcomed.

    Many thanks,

    Sean



  • Woow. I like those screenshots . Impressive work.



  • [quote author="2beers" date="1299170091"]Woow. I like those screenshots . Impressive work. [/quote]

    Thank you for your kind words. Does the basic API I have shown so far look OK to you? I can post more snippets if you want more details for a discussion.

    I am also open to feature requests if there is a chart type or something else I am missing.



  • yes. you can make a video to see the charts in action. My idea was to use QML for some animated eye-candy charts. you can see at this examples by fusioncharts done in flash to make an idea: http://www.fusioncharts.com/demos/blueprint/
    and
    http://www.fusioncharts.com/demos/

    [quote author="ZapB" date="1299170331"]
    I am also open to feature requests if there is a chart type or something else I am missing.

    [/quote]

    I didn't see any pie chart in those pics. It might be useful. 3D pie chart will also be exceptional.



  • [quote author="2beers" date="1299207584"]yes. you can make a video to see the charts in action. My idea was to use QML for some animated eye-candy charts. you can see at this examples by fusioncharts done in flash to make an idea: http://www.fusioncharts.com/demos/blueprint/
    and
    http://www.fusioncharts.com/demos/
    [/quote]

    OK interesting. Thanks for the link. Here's a short "video":http://www.theharmers.co.uk/model-anim.ogv showing one way in which the data can be animated when values change. This example is the one that uses a itemview model as the data source. The animation looks better locally than in the video.

    When the dataset is changed (in this case via a signal from the underlying Qt itemview model) the dataset notifies the data series which delegates the task of updating the graphics items to a Behaviour object set on the data series. A user could provide their own custom Behaviour object to customise the animations that take place when data is added/removed/modified. As always there is still work to do on this but it is useable already.

    [quote author="2beers" date="1299207584"]
    I didn't see any pie chart in those pics. It might be useful. 3D pie chart will also be exceptional. [/quote]

    Ah yes sorry I forgot to post a screenshot of pie charts. These are still in the early stages and I am still playing around with templates to find a way to make them work with custom data types (as long as they provide operator + and operator / of course). Anyway, here's a "screenshot":http://gallery.theharmers.co.uk/picture.php?/14/category/5.

    I have also made a "video":http://www.theharmers.co.uk/pie.ogv of the pie charts being animated. The animation looks better locally than what recordmydesktop shows in the video.

    To do this I just retrieved a pointer for the two pie charts and then used QPropertyAnimation in the usual way. The pie charts are just inherited from QGraphicsObject.

    Here is the code snippet used to create the two pie charts shown:

    @
    // Create a new plot
    ZPlot* plot = new ZPlot( page );

    // Set a title
    plot->setTitleText( "Revenue by Product Stream" );
    
    // Add plot to the page's layout
    page->layout()->addItem( plot, 0, 0 );
    
    // Use data to create a new dataseries in the "Default" plot area
    ZDataSetPtr dataSet1 = ZDataSetPtr( new ZDataSet( data ) );
    ZDataSeries* series1 = plot->addDataSeries( dataSet1, QString( "Series 1" ), Pie );
    
    ZPieItem* pie = series1->pie();
    pie->setPos( QPointF( -0.4, 0.5 ) );
    pie->setRadius( 180.0 );
    pie->explodeSlice( 4, 0.25 );
    
    // Use data to create a new dataseries in the "Default" plot area
    ZDataSetPtr dataSet2 = ZDataSetPtr( new ZDataSet( data2 ) );
    ZDataSeries* series2 = plot->addDataSeries( dataSet2, QString( "Series 2" ), Pie );
    
    // Customise the pie chart a little
    ZPieItem* pie2 = series2->pie();
    pie2->setRadius( 130.0 );
    pie2->setPos( QPointF( 3.0, 0.0 ) );
    pie2->explodeAll( 0.1 );
    

    @

    where "page" is just a pointer to a QGraphicsItem derived object acting as a container (in this case ZPlotPage which is the blue gradient filled background object).



  • Oops I forgot to mention, in the above pie chart snippet the second pie chart is given a position of (3.0, 0) to make it appear off the right-hand side of the screen ready to be animated in (see video). The pie chart plots use a coordinate system going from -1 to 1.



  • It's an impressive job you done there. Congrats. My suggestion is if you wanna move to QML go directly with qml scene graph so you don't have to change much once is ready.

    Another reason by going with QML is that you can use their already predefined animation effects.

    Good luck and let us know about future changes



  • [quote author="2beers" date="1299241208"]It's an impressive job you done there. Congrats. My suggestion is if you wanna move to QML go directly with qml scene graph so you don't have to change much once is ready.
    [/quote]

    Thank you. I have been keeping an eye on the scene graph stuff. I would also like to make this charting component available via C++ too in addition to QML so I need to figure out how to best provide capabilities to both approaches. It's a shame that a scene graph backend to QGraphicsView didn't work out.

    [quote author="2beers" date="1299241208"]
    Another reason by going with QML is that you can use their already predefined animation effects.

    Good luck and let us know about future changes[/quote]

    Agreed, although it is also trivial to do so with QPropertyAnimation etc which is what QML uses under the bonnet I believe.

    Thanks for your feedback. I'll post updates as I add new features and eventually release.



  • Hi Sean,

    Thank you for this very interesting charting component. I look forward to diving into it further. The code you show off here looks fine, though I'd have to take a look at the real requirements to see if this would work out for me in practice. Still, it looks like it could!

    I am wondering how these charting components differ from existing ones on the market. Qwt and KDAB's offerings come to mind. Do you have a different design philosophy from theirs? Also important, of course, is the licence(s) this will be available under.

    All in all: it looks impressive and quite slick!



  • Hey André,

    [quote author="Andre" date="1299317475"]
    Thank you for this very interesting charting component. I look forward to diving into it further. The code you show off here looks fine, though I'd have to take a look at the real requirements to see if this would work out for me in practice. Still, it looks like it could!
    [/quote]

    No problem. Thank you for taking the time to check it out. Wrt the API requirements, let me know what sort of thing you would like it to be able to do or feel free to ask for some existing API snippets and I will gladly post them for review.

    Much of it of course is still subject to change. For example it is already quite simple to plot a function (either a C++ one or a QtScript one) but I think that I can still make the API simpler for the common cases without loss of generality.

    [quote author="Andre" date="1299317475"]
    I am wondering how these charting components differ from existing ones on the market. Qwt and KDAB's offerings come to mind. Do you have a different design philosophy from theirs? Also important, of course, is the licence(s) this will be available under.
    [/quote]

    This is based directly on top of QGraphicsView whereas other competing charting components are (as far as I am aware) based on QWidget. This makes certain things simpler and lighter-weight. For example I have written some custom QGraphicsLayout classes. One of these is used to dock the legend (or any other QGraphicsWidget derived object) to the inside or outside of the plot area. Something like this will dock the legend at the inside top-center of the plot area and make the legend lay it's items out horizontally:

    @
    ZGraphicsDockerWidget* legendDocker = plot1->plotArea()->legendDocker();
    legendDocker->setSimplePosition( DockInside, TopCenter );
    legendDocker->setHorizontalAnchorSpacing( 0.0 );
    plot1->plotArea()->legend()->setOrientation( Qt::Horizontal );
    @

    Of course this can be made even simpler by:

    making the theme/style aware that the horizontal anchor spacing should be zero when using a centre alignment in the horizontal direction

    making some simple forwarding functions on ZPlot that forwards calls onto the default plot area instead of having to have explicit calls to plotArea( const QString& plotAreaName = "default" ) in there.

    That is just a little syntactic sugar though. That is just a simple example of the ZGraphicsDockLayout though. Much more is possible with it that I have shown here.

    I am also aiming to be able to produce business-like charts (ie for use in dashboards, presentation etc.), production quality charts suitable for scientific publications, and high-performance charts for plotting of real-time data. This should be possible by simply using a suitable chart theme and a sensible choice of data series plot elements. For example it would not make sense to use spline-based curves for a high-performance real-time plotting application but it might for a business presentation/dashboard.

    As for licenses I am thinking of a commercial license (with support) plus one or more FOSS licenses. I would like to see it used and if I can earn a little money with it then so much the better as I'll be able to dedicate some more time into future improvements and some other project ideas I have in mind.

    [quote author="Andre" date="1299317475"]
    All in all: it looks impressive and quite slick!
    [/quote]

    Many thanks. Still lots more to come!



  • Hi Sean,

    I really like it, it looks good and the API seems easy to use.

    I would definitely use it right away if it had support for:

    • QDateTime (I see work in progress, good :) )
    • one y axis per data serie (different units/scale)
    • show/hide data serie (ticking a box in the app triggers it)
    • select area in the plot that emits first and last x values (why not y as well)

    Can't wait to see what you'll come up with.



  • Greetings Sean,

    Thank you very much for your effort.

    I would like to see polar plot.s I do a lot of 2D gain patterns of antennas and such.

    The ability to plot (linear but mostly log) and scale multiple series would be most excellent.

    I tried what looked like an add on to Qwt but couldn't get it to compile.

    Thank you again. I would love for this to become a standard component.

    Ed



  • [quote author="ZapB" date="1299319762"]Hey André,

    [quote author="Andre" date="1299317475"]
    Thank you for this very interesting charting component. I look forward to diving into it further. The code you show off here looks fine, though I'd have to take a look at the real requirements to see if this would work out for me in practice. Still, it looks like it could!
    [/quote]

    No problem. Thank you for taking the time to check it out. Wrt the API requirements, let me know what sort of thing you would like it to be able to do or feel free to ask for some existing API snippets and I will gladly post them for review.
    [/quote]

    What I am most interested in, is the API you came up for for your data interface. You said you came up with something that acts like the QAbstractItemModel does for Qt's views, and I am really curious to see that API: how easy is it to reimplement to adapt it to different data backends? How flexible is it? For me, this is probably one of the key parts of the API.

    [quote]I am also aiming to be able to produce business-like charts (ie for use in dashboards, presentation etc.), production quality charts suitable for scientific publications, and high-performance charts for plotting of real-time data. This should be possible by simply using a suitable chart theme and a sensible choice of data series plot elements. For example it would not make sense to use spline-based curves for a high-performance real-time plotting application but it might for a business presentation/dashboard.
    [/quote]

    Data visualization is only becomming more important. Animation is a great feature to have, because it enables you to create time series in a simple way (I hope your API makes that possible?) But also try to considder some less frequently used chart types that can be useful. Think about spidergrams, for instance (the cobweb like diagrams), or bubble graphs. But also diagrams of the type where the diagram area is filled up with boxes, each of these representing a data point and sized to represent the relative value of that point (I don't remember the name of this visualization) is helpful to gain insight sometimes. I guess it would make sense if it were doable for a user to exend the framework with new graph types, so I am curious to the API to implement for that too (it is the "other end" of the model API I asked for just above, I guess :-) )



  • Hi André,

    I'll reply in a few posts to make it easier to digest...

    [quote author="Andre" date="1299317475"]
    What I am most interested in, is the API you came up for for your data interface. You said you came up with something that acts like the QAbstractItemModel does for Qt's views, and I am really curious to see that API: how easy is it to reimplement to adapt it to different data backends? How flexible is it? For me, this is probably one of the key parts of the API.
    [/quote]

    Here is the API of my ZAbstractDataSet class which I suppose is the equivalent of QAbstractItemModel:

    @
    class PLOTLIBAPI ZAbstractDataSet : public QObject
    {
    Q_OBJECT
    public:
    explicit ZAbstractDataSet( QObject* parent = 0 );

    virtual QVariant data( int index, const QString& variable ) const = 0;
    
    virtual int size() const = 0;
    
    virtual DataDescriptionFlags dataDescription() const = 0;
    
    virtual QRectF boundingRect() const;
    
    int dataType() const
    {
        if ( size() == 0 )
            return QVariant::Invalid;
        return data( 0 ).userType();
    }
    
    void registerVariableMapper( int typeId, ZAbstractVariableMapperPtr mapper );
    QHash<int, ZAbstractVariableMapperPtr> variableMappers() const;
    

    public slots:
    virtual void setData( int index, const QVariant& data );
    virtual void insertData( int index, int count );
    virtual void removeData( int index, int count );
    void removeData( int index ) { removeData( index, 1 ); }

    signals:
    void dataReset();
    void dataChanged( int start, int end );
    void dataAboutToBeInserted( int start, int end );
    void dataInserted( int start, int end );
    void dataAboutToBeRemoved( int start, int end );
    void dataRemoved( int start, int end );

    protected:
    void reset();

    void beginInsertData( int start, int count );
    void endInsertData();
    
    void beginRemoveData( int start, int count );
    void endRemoveData();
    

    };
    @

    So far I have subclassed two concrete classes from this interface:

    • ZDataSet which is a convenience implementation much like QStandardItemModel is for QAIM and
    • ZModelDataSet which acts as a bridge to existing QAIM based datasets

    I am planning a 3rd concrete subclass implemented in terms of QCircularBuffer (If my merge request ever gets reviewed ;-)) which will allow the high performance plotting of real-time data series. With this class the data series will show up to the n most recent points (ie data for the last hour etc). Older points will be automatically overwritten by new incoming points. This should remove any new's/delete's of QGraphicsItems or memory allocation within the dataset. This would be useful in system monitor type apps too ie ksysguard.



  • In addition to the obvious functions for inserting/removing data and accessing the data you may have noticed a couple of other aspects:

    @
    virtual DataDescriptionFlags dataDescription() const = 0;
    @

    This is used to describe the type of data contained within the data set. Obviously not all types of data can be plotted in all ways. For example, a bubble plot needs 2 y values (one for the position and one for the value that determines the size of the point (more on this later). The descriptions that I support for far are:

    @
    enum DataDescriptionFlag
    {
    NoData = 0x00000000,
    OneDimensional = 0x00000001,
    TwoDimensional = 0x00000002,
    ImplicitTwoDimensional = 0x00000004,
    ScalarField = 0x00000008,
    VectorField = 0x00000010,
    PlaneTensorField = 0x00000020,
    IndexedScalarField = 0x00000080,
    StockTimeSeries = 0x00000100
    };

    Q_DECLARE_FLAGS( DataDescriptionFlags, DataDescriptionFlag );
    Q_DECLARE_OPERATORS_FOR_FLAGS( DataDescriptionFlags );
    @

    I have not yet implemented any graphical forms for PlaneTensorField (think plane stress tensor) and StockTimeSeries as yet. Each of the above data description types can be plotted in one or more ways.



  • The other aspect in the ZAbstractDataSet interface of interest is:

    @
    void registerVariableMapper( int typeId, ZAbstractVariableMapperPtr mapper );
    QHash<int, ZAbstractVariableMapperPtr> variableMappers() const;
    @

    I'll try to explain this a little. A data set may take many forms. Let us consider possible data sets for a simple 2d xy scatter plot. Some possible forms for each point in the data set are:

    • QPointF
    • QPoint
    • QPair<double, double>
    • QPair<int, int>
    • Eigen::Vector2d
    • ...

    I have tried to make it such that I can support any format of point so long as it is supported by QVariant. Obviously I do not really want to have to write a corresponding graphical class for each type of data that we support. After all the graphical representation should be the same no matter the details of where it's position came from.

    To this end I have introduced the concept of variable mappers. The graphical items need to know things like what position should I draw myself at, or what size should I be. To do this the data series asks the ZAbstractDataSet for pieces of data using the function:

    @
    virtual QVariant data( int index, const QString& variable ) const = 0;
    @

    where the variable is "x", "y", "position", "scalar", "vector" etc depending upon what info it needs to be able to render the plot type.

    NB I am planning on replacing these strings with an enum value at some point to eliminate string comparisons.

    Anyway, since the data set classes that I ship cannot possibly know about all the data type that people may wish to use, I have delegated the details of these data requests to so-called variable mappers. My data set classes register a number of supplied variable mappers at creation time but a user can register their own for their custom data types. The implementation of a variable mapper is very simple. Here is an example mapper for an Eigen::Vector2d type:

    @
    class Vector2dTypeMapper : public PlotLib::ZAbstractVariableMapper
    {
    QVariant extractVariable( int index, const QVariant& v, const QString& variable ) const
    {
    Q_UNUSED( index );
    if ( variable == "x" )
    {
    return QVariant( v.value<Vector2d>().x() );
    }
    else if ( variable == "y" )
    {
    return QVariant( v.value<Vector2d>().y() );
    }
    else if ( variable == "position" )
    {
    Vector2d vec = v.value<Vector2d>();
    QPointF p( vec.x(), vec.y() );
    return p;
    }
    return QVariant();
    }
    };
    @

    Only this one function needs to be implemented. Each variable "x", "y" etc has it's own data type that it needs to be converted to. Some of these are "x", "y", "scalar"=>double; "position"=>QPointF etc.

    I know that this sacrifices a little speed but it does make it very flexible.



  • [quote author="Andre" date="1299317475"]
    Data visualization is only becomming more important. Animation is a great feature to have, because it enables you to create time series in a simple way (I hope your API makes that possible?)
    [/quote]

    Yes this is possible. I have used this at work to plot magnetic data as it is collected. This work showed multiple xy-scatter data series (one for each frequency in use). The simple animation I set up was for the point to fade in and increase in scale from some small number. For the scale animation I used an easing curve with a little overshoot (I can't recall the exact one offhand). This made each point appear to "pop" into existance. It looks quite nice in use :-)

    Each element of the plot is just derived from a QGraphicsObject so you can do all the usual animation techniques. I have not tried animating a new point in from a different position yet. The position is controlled by it's parent at present so I need to look into how to do this but it should be possible. We just treat the position as the animation end point.

    [quote author="Andre" date="1299317475"]
    But also try to considder some less frequently used chart types that can be useful. Think about spidergrams, for instance (the cobweb like diagrams), or bubble graphs. But also diagrams of the type where the diagram area is filled up with boxes, each of these representing a data point and sized to represent the relative value of that point (I don't remember the name of this visualization) is helpful to gain insight sometimes.
    [/quote]

    I thought that was what a bubble plot is? At least that is what excel calls them :-) I have not yet started on polar/rose type charts (although I do have some of the building blocks in place such as a polar coordinate system class).

    Bubble plots are supported now. Here is a simple example:

    @
    #include <QApplication>

    #include <zdataseries.h>
    #include <zdataset.h>
    #include <zplot.h>
    #include <zplotwidget.h>

    using namespace PlotLib;

    int main( int argc, char *argv[] )
    {
    QApplication app( argc, argv );
    ZPlotWidget w;
    w.plot()->setTitleText( "Bubble Plot" );

    // Add a bubble plot - y2 is proportional to size of point
    ScalarField2D scalarField;
    for ( int i = 0; i < 11; ++i )
    {
        double x = -5 + double( i );
        double y = x * x;
        double y2 = qMax( 0.3, qAbs( x ) );
        scalarField.append( ScalarField2DPoint( Vector2d( x, y ), y2 ) );
    }
    ZDataSetPtr dataSet( new ZDataSet( scalarField ) );
    
    ZDataSeries* series = w.plot()->addDataSeries( dataSet, "Bubble Plot", Bubble );
    series2->setLegendText( "A bubble series" );
    
    w.show();
    return app.exec&#40;&#41;;
    

    }
    @

    This produces the following output:

    !http://www.theharmers.co.uk/ZtPlot-010.png(Bubble plot)!

    I also have ways for the user to provide custom functions that determine the colour and size of the points. The functions can make use of the position or scalar field value (ie the y2 value) to determine this. I used this code to produce the colouring shown in the "scalar field example":http://gallery.theharmers.co.uk/picture.php?/11/category/5

    @
    class MyColorFunction : public ZScalarColorFunction
    {
    public:
    QColor operator() ( const ScalarField2DPoint& x ) const
    {
    double f = x.second; // Use the y2 value
    f = qBound( 0.0, f, 1.0 );
    int hue = int( 120.0 * ( 1.0 - f ) );
    QColor c;
    c.setHsv( hue, 255, 200 );
    return c;
    }
    };

    int main( int argc, char argv[] )
    {
    ...
    // Create a data series and set the plot type
    ZDataSeries
    series = plot1->addDataSeries( createFunction(), "Series 1", Bubble );

    // Customise the plot a little by setting a custom color function
    ZBubbleItemGroup* field = series->bubbles();
    ZScalarColorFunctionPtr colorFunc( new MyColorFunction );
    field->setColorFunction( colorFunc );
    

    ...
    }
    @

    The default colour and scale function objects also provide some limited options for customisation.

    [quote author="Andre" date="1299317475"]
    I guess it would make sense if it were doable for a user to exend the framework with new graph types, so I am curious to the API to implement for that too (it is the "other end" of the model API I asked for just above, I guess :-) )
    [/quote]

    I am still in the process of adding new plot types myself so I have not gotten round to exposing this kind of extension in an easy and well-documented way yet. I am learning lessons each time I do it though.

    It is simple enough to customise existing plots though since everything is just a QGraphicsItem, QGraphicsObject or QGraphicsWidget at the end of the day. You can easily add your own child objects where needed.

    Also, as I mentioned on the qt-interest list, all replaceable items will eventually be created by factory classes so as to allow easy replacement by user-specified items for easily customisable behaviour too.

    I am hoping that the above will lead to a plotting/charting library that is:

    • easy to use in the common cases
    • easily extended by adding in your own items
    • easily customised by using custom factories and items
    • easy to use with custom data types
    • relatively simple to add new plot types to
    • yet still be high-performance and good looking :D

    I hope that answers your queries. Feel free to come back again if I have not addressed anything properly.

    Edit: fixed quoting



  • Hi Ed,

    [quote author="emsr" date="1299458270"]Greetings Sean,

    Thank you very much for your effort.

    I would like to see polar plot.s I do a lot of 2D gain patterns of antennas and such.

    The ability to plot (linear but mostly log) and scale multiple series would be most excellent.

    I tried what looked like an add on to Qwt but couldn't get it to compile.

    Thank you again. I would love for this to become a standard component.

    Ed
    [/quote]

    Polar plots are indeed on my feature requirements list. I do not have them finished yet but they will be supported with both linear and log radial scales. I need them too for my day job ;-)



  • Hi,
    [quote author="kofee" date="1299455232"]Hi Sean,

    I really like it, it looks good and the API seems easy to use.

    I would definitely use it right away if it had support for:

    • QDateTime (I see work in progress, good :) )
    • one y axis per data serie (different units/scale)
    • show/hide data serie (ticking a box in the app triggers it)
    • select area in the plot that emits first and last x values (why not y as well)

    Can't wait to see what you'll come up with.[/quote]

    Cool, thank you for your words of encouragement. The current status is:

    • QDateTime support as you noticed is work in progress. I can plot data series containing QDateTimes as the x-values for e.g. The plot looks correct however the axis labels do not yet show properly formatted strings for the QDateTime values. This is easily solved, I just need to extend my tick mark engine class to handle this data type.
    • Multiple axes is alrady supported but is currently slightly broken due to some refactoring I am doing. I did have it working before the refactoring though so I should be able ot get it back fairly easily.
    • Show/hide a data series is very simple:
      @
      m_plot->plotArea()->dataSeries( "my data series name" )->hide();
      @
    • Selection: I have not looked at this yet but since I am using QGraphicsView I do not think that this should be difficult.

    I'll add more posts as I complete these features.



  • [quote author="ZapB" date="1299494065"][quote author="Andre" date="1299317475"]
    But also try to considder some less frequently used chart types that can be useful. Think about spidergrams, for instance (the cobweb like diagrams), or bubble graphs. But also diagrams of the type where the diagram area is filled up with boxes, each of these representing a data point and sized to represent the relative value of that point (I don't remember the name of this visualization) is helpful to gain insight sometimes.
    [/quote]

    I thought that was what a bubble plot is? At least that is what excel calls them :-)
    [/quote]
    No, sorry, I think I have not made myself clear. I was more thinking about visualizations like the one used in "this presentation":http://www.youtube.com/watch?v=pLqjQ55tz-U&feature=player_embedded (from 45 seconds onwards) or even in a hierarchical form as used for things like visualizing hard disk space used, such as in "this application":http://lifehacker.com/#!219058/geek-to-live--visualize-your-hard-drive-usage.

    [quote]
    I also have ways for the user to provide custom functions that determine the colour and size of the points. The functions can make use of the position or scalar field value (ie the y2 value) to determine this. I used this code to produce the colouring shown in the "scalar field example":http://gallery.theharmers.co.uk/picture.php?/11/category/5

    @
    class MyColorFunction : public ZScalarColorFunction
    {
    public:
    QColor operator() ( const ScalarField2DPoint& x ) const
    {
    double f = x.second; // Use the y2 value
    f = qBound( 0.0, f, 1.0 );
    int hue = int( 120.0 * ( 1.0 - f ) );
    QColor c;
    c.setHsv( hue, 255, 200 );
    return c;
    }
    };

    int main( int argc, char argv[] )
    {
    ...
    // Create a data series and set the plot type
    ZDataSeries
    series = plot1->addDataSeries( createFunction(), "Series 1", Bubble );

    // Customise the plot a little by setting a custom color function
    ZBubbleItemGroup* field = series->bubbles();
    ZScalarColorFunctionPtr colorFunc( new MyColorFunction );
    field->setColorFunction( colorFunc );
    

    ...
    }
    @

    The default colour and scale function objects also provide some limited options for customisation.
    [/quote]
    How does this relate to the ZAbstractVariableMapper class you described above? Wouldn't it make more sense to query that class for a suitable color, or do I misunderstand what you are doing here?



  • [quote author="Andre" date="1299497646"]
    No, sorry, I think I have not made myself clear. I was more thinking about visualizations like the one used in "this presentation":http://www.youtube.com/watch?v=pLqjQ55tz-U&feature=player_embedded (from 45 seconds onwards) or even in a hierarchical form as used for things like visualizing hard disk space used, such as in "this application":http://lifehacker.com/#!219058/geek-to-live--visualize-your-hard-drive-usage.
    [/quote]

    Ah yes I know what you mean now. I am not sure of the technical name for this type of visualisation either. I have not yet implemented this. I'll have to have a think about this one because as you say it uses a potentially hierarchical data source.

    [quote author="Andre" date="1299497646"]
    How does this relate to the ZAbstractVariableMapper class you described above? Wouldn't it make more sense to query that class for a suitable color, or do I misunderstand what you are doing here?
    [/quote]

    They are not related. This is where I have diverged from the QModelView pattern. I have never been particularly comfortable with having to provide properties that affect the appearance of the views in the data model for QModelView. IMHO the view properties should be set independently from the actual data.

    My approach also allows the user to specify a functional form for the colour/scale/other property whereas in QModelView each property has its own role that must be queried from the model.

    I suppose I could allow the view to check if the data set provides a value for that property for each point and use that if it does. Then, if the data set does not provide the needed property value it can fall back to the functional form. Hmmm, this would impact performance though. I wonder if I can make it a noop in the common case? I'll take a look.

    Thanks for the thoughts.



  • Hi Sean,

    [quote author="ZapB" date="1299493871"]Hi André,

    I'll reply in a few posts to make it easier to digest...

    [quote author="Andre" date="1299317475"]
    What I am most interested in, is the API you came up for for your data interface. You said you came up with something that acts like the QAbstractItemModel does for Qt's views, and I am really curious to see that API: how easy is it to reimplement to adapt it to different data backends? How flexible is it? For me, this is probably one of the key parts of the API.
    [/quote]

    Here is the API of my ZAbstractDataSet class which I suppose is the equivalent of QAbstractItemModel:

    @
    class PLOTLIBAPI ZAbstractDataSet : public QObject
    {
    Q_OBJECT
    public:
    explicit ZAbstractDataSet( QObject* parent = 0 );

    virtual QVariant data( int index, const QString& variable ) const = 0;
    
    virtual int size() const = 0;
    
    virtual DataDescriptionFlags dataDescription() const = 0;
    
    virtual QRectF boundingRect() const;
    
    int dataType() const
    {
        if ( size() == 0 )
            return QVariant::Invalid;
        return data( 0 ).userType();
    }
    
    void registerVariableMapper( int typeId, ZAbstractVariableMapperPtr mapper );
    QHash<int, ZAbstractVariableMapperPtr> variableMappers() const;
    

    public slots:
    virtual void setData( int index, const QVariant& data );
    virtual void insertData( int index, int count );
    virtual void removeData( int index, int count );
    void removeData( int index ) { removeData( index, 1 ); }

    signals:
    void dataReset();
    void dataChanged( int start, int end );
    void dataAboutToBeInserted( int start, int end );
    void dataInserted( int start, int end );
    void dataAboutToBeRemoved( int start, int end );
    void dataRemoved( int start, int end );

    protected:
    void reset();

    void beginInsertData( int start, int count );
    void endInsertData();
    
    void beginRemoveData( int start, int count );
    void endRemoveData();
    

    };
    @

    [/quote]

    I don't really understand you design yet, I am sorry. I don't quite see the relationship between ZAbstractVariableMapper and ZAbstractDataSet. I am right that variable from virtual ZAbstractDataSet::QVariant data( int index, const QString& variable ) const plays the role that role does in QAbstractItemModel::data(QModelIndex& index, int role = Qt::DisplayRole) const;?

    If so, what then is the place of the ZAbstractVariableMapper?

    A move to an enum would be wise, I think. You can define a set of standard "roles" (like is done for QAIM) and extend it as needed.

    Also unclear for me is the ZAbstractDataSet::boundingBox() method. What exactly is returned there, especially in conjunction with the ZAbstractVariableMapper? A bounding rect makes sense conceptually for graphs like a vector field or a scatter plot, but what is a bounding rect for a simple one dimensional series you plan to draw in a bar or pie chart?

    Last (for now ;-) ) idea that popped up in my head, is that you might be able to optimize your ZAbstractVariableMapper if you resort to use templates here. For a graph type, you just need to be sure that the mapper class implements the interface required for that graph type, but you would not need the additional virtual method call and the conversions to and from QVariant. But perhaps I totally misunderstand the purpose of this class, so I may be off the mark here.



  • [quote author="ZapB" date="1299499026"][quote author="Andre" date="1299497646"]
    No, sorry, I think I have not made myself clear. I was more thinking about visualizations like the one used in "this presentation":http://www.youtube.com/watch?v=pLqjQ55tz-U&feature=player_embedded (from 45 seconds onwards) or even in a hierarchical form as used for things like visualizing hard disk space used, such as in "this application":http://lifehacker.com/#!219058/geek-to-live--visualize-your-hard-drive-usage.
    [/quote]

    Ah yes I know what you mean now. I am not sure of the technical name for this type of visualisation either. I have not yet implemented this. I'll have to have a think about this one because as you say it uses a potentially hierarchical data source.
    [/quote]
    Well, it would already be useful in a non-hierarchical version, as you can see in the movie. But yes, hierarchy would add to the data-richness, especially if you can navigate through it.

    [quote]
    [quote author="Andre" date="1299497646"]
    How does this relate to the ZAbstractVariableMapper class you described above? Wouldn't it make more sense to query that class for a suitable color, or do I misunderstand what you are doing here?
    [/quote]

    They are not related. This is where I have diverged from the QModelView pattern. I have never been particularly comfortable with having to provide properties that affect the appearance of the views in the data model for QModelView. IMHO the view properties should be set independently from the actual data.

    My approach also allows the user to specify a functional form for the colour/scale/other property whereas in QModelView each property has its own role that must be queried from the model.

    I suppose I could allow the view to check if the data set provides a value for that property for each point and use that if it does. Then, if the data set does not provide the needed property value it can fall back to the functional form. Hmmm, this would impact performance though. I wonder if I can make it a noop in the common case? I'll take a look.
    [/quote][/quote]
    Hmmm, I think I agree on principle that separating presentation and data is a good thing, and that the way these two interfere in the Qt model/view design is less than ideal. I like being able to provide a function for a property, that is pretty neat actually. But it does not make sense in all cases. What you might do, is create a set of standard functions that do nothing more than retrieve the value in question from the model directly? And just set another function to use if you want that? Again, with a bit of template magic, I think it need not even impact runtime performance.



  • Hi again,
    [quote author="Andre" date="1299499404"]
    I don't really understand you design yet, I am sorry. I don't quite see the relationship between ZAbstractVariableMapper and ZAbstractDataSet. I am right that variable from virtual ZAbstractDataSet::QVariant data( int index, const QString& variable ) const plays the role that role does in QAbstractItemModel::data(QModelIndex& index, int role = Qt::DisplayRole) const;?
    [/quote]

    Yes that is correct.

    [quote author="Andre" date="1299499404"]
    If so, what then is the place of the ZAbstractVariableMapper?
    [/quote]

    Ah OK. ZAbstractVariableMapper is a way for me to provide something akin to the body of the data() function from QAIM for the common cases and to allow users to easily provide a similar body without having to subclass ZAbstractDataSet for themselves.

    I appreciate that the need here may not be immediately obvious. I will try to explain a little more. Imagine that the user wishes to plot an xy scatter plot of some data they have calculated. Their calculation routines provide data in the form of My2dPoint objects say. Now, my charting component obviously has no knowledge of this type whatsoever.

    "OK", you say, "I can subclass ZAbstractDataType and reimplement all the pure virtual functions". This is correct but then you have to reimplement all of the other logic too such as inseting/removing data, remembering to emit signals or call base class functions when appropriate.

    By simply providing a customised sub-class of ZAbstractVariableMapper and registering it, you can get what you need much easier. In this case you can then just use the concrete ZDataSet which provides all the nitty-gritty book-keeping and signal emission code. This effectively removes a lot of the headaches of reimplementing QAIM to get read-write models up and running. The common stuff is done once in ZDataSet and the variable mappers provide an abstraction for the part that varies from user to user.

    As a bonus the variable mapper will also work with the ZModelDataSet if you already happen to have a QAIM that exposes the data. It will also work with the circular buffer based dataset when I get that finished too.

    I suppose there are alternative ways of doing this too. For example, ZDataSet is based upon a QVector of data points and I have mentioned the upcoming circular buffer one. I could maybe abstract these away using template policy classes. I have some more experimentation to do yet.

    Having said all that, I do not expect most people to have to any of this. In the common case you can simply use ZDataSet or another concrete class.

    [quote author="Andre" date="1299499404"]
    A move to an enum would be wise, I think. You can define a set of standard "roles" (like is done for QAIM) and extend it as needed.
    [/quote]

    Yes I will do this very soon. Not enough hands/brains ;-)

    [quote author="Andre" date="1299499404"]
    Also unclear for me is the ZAbstractDataSet::boundingBox() method. What exactly is returned there, especially in conjunction with the ZAbstractVariableMapper? A bounding rect makes sense conceptually for graphs like a vector field or a scatter plot, but what is a bounding rect for a simple one dimensional series you plan to draw in a bar or pie chart?
    [/quote]

    This is true. It could be made clearer. As you guessed it is used to determine what x/y limits should be placed on the axes when auto-scaling is enabled. For pie charts and other similar things I adopt a coordinate system of -1 to 1 in the shortest axis with a unity aspect ratio. In this case the plot elements do not call boundingRect() at all and so a stubbed implementation can be used. Once again though simply using the provided ZDataSet will do the right thing (tm) :D

    [quote author="Andre" date="1299499404"]
    Last (for now ;-) ) idea that popped up in my head, is that you might be able to optimize your ZAbstractVariableMapper if you resort to use templates here. For a graph type, you just need to be sure that the mapper class implements the interface required for that graph type, but you would not need the additional virtual method call and the conversions to and from QVariant. But perhaps I totally misunderstand the purpose of this class, so I may be off the mark here. [/quote]

    Yes I have wondered about using templates here too. I have just not gotten around to trying it yet.

    Thank you again for your comments. They are very useful food for thought and I will try out these ideas.



  • [quote author="Andre" date="1299500755"]
    Well, it would already be useful in a non-hierarchical version, as you can see in the movie. But yes, hierarchy would add to the data-richness, especially if you can navigate through it.
    [/quote]

    True. I would like to solve it in general and then simplify. It is often harder to implement the simple case and then generalise it in a clean way without breaking existing code. I am open to ideas if you have any for this. ;-)

    [quote author="Andre" date="1299500755"]
    Hmmm, I think I agree on principle that separating presentation and data is a good thing, and that the way these two interfere in the Qt model/view design is less than ideal. I like being able to provide a function for a property, that is pretty neat actually. But it does not make sense in all cases. What you might do, is create a set of standard functions that do nothing more than retrieve the value in question from the model directly? And just set another function to use if you want that? Again, with a bit of template magic, I think it need not even impact runtime performance.
    [/quote]

    I do provide default implementations. For example, with the bubble plots the default colour function implementation just returns an invalid QColor. This means that the colour function does not override the default behaviour which is for the data series to be given a colour based upon the data series number. The colour for the i'th data series is provided by the style/theme. Similarly for the bubble-point shape. I do like the idea of being able to potentially override this in the data set though.

    In my default theme the series colours are just taken from a selection of the colours in the KDE Oxygen colour palette. I intend to ship more themes/styles with the official release. I'll be after some artistic inspiration a bit later :-)



  • Hi there!

    I think you are doing a great job. I'm really interested in such a library, and would be very happy to try it out.

    One particular feature I'm very interested in is plotting of streams of data (such as scrolling curves and scrolling images).

    You have any plans for this sort of stuff? What kind of API would it have?

    cheers and best luck on your project!
    ricard



  • [quote author="rikrd" date="1301938309"]Hi there!
    I think you are doing a great job. I'm really interested in such a library, and would be very happy to try it out.
    [/quote]

    Thank you.

    [quote author="rikrd" date="1301938309"]
    One particular feature I'm very interested in is plotting of streams of data (such as scrolling curves and scrolling images).

    You have any plans for this sort of stuff? What kind of API would it have?
    [/quote]

    Do you mean like plotting time series of real-time data? If so then yes, this will be supported. The API will be similar to that used in the Qt ModelView framework. The data set that you are plotting will emit (either manually in a custom sub-class or by calling the append() function of one of the provided convenience classes) an internal signal that is connected to the data series (the graphical representation). The data series then makes the necessary adjustments to take the new data into account and triggers an update. I'll try to make a simple example and post it.

    What sort of data do you want to plot as a scrolling image?



  • bq. Do you mean like plotting time series of real-time data? If so then yes, this will be supported.

    Yes, that was what I was looking for.

    bq. The API will be similar to that used in the Qt ModelView framework. The data set that you are plotting will emit (either manually in a custom sub-class or by calling the append() function of one of the provided convenience classes) an internal signal that is connected to the data series (the graphical representation).

    This sounds simple and nice. I like the idea of having a similar API as Qt's ModelView framework. Looking forward to trying it and seeing how it performs.

    bq. What sort of data do you want to plot as a scrolling image?

    I was thinking of spectrogram-like data. Let's say you want to show a spectrogram of an audio stream scrolling while it is playing.

    Another visualization I would be interested in, is a plot that that changes entirely very frequently. Such as the instantaneous spectrum of an audio stream. But I can imagine the API, if it will be similar to the ModelView framework.

    If I didn't explained myself well, checkout the "SonicVisualiser":http://www.sonicvisualiser.org/ for the sort of plots I am interested in.

    Finally my last question is if you plan to publish your library under some Free software license?



  • Yes that is what I thought you meant but thought I should check. I will try to add in support for that type of plot but I am not sure if it will make it into version 1.

    As for the license I am planning to release it under a commercial license plus one or more FOSS licenses. I just wish I had more time to work on it but I am very busy at work at the moment.



  • Just a quick note to report a little bit of progress. I now have this charting component working under the Qt Simulator target. Next step is to get it working on a real Symbian device. Here's a "screenshot":http://gallery.theharmers.co.uk/picture.php?/165/category/5 showing a simple example running under the simulator.



  • Looking really good! Can't wait to get my hands on this.



  • hi,

    really nice plots !

    Do you use QGraphicsPathItems for the "line" plots?
    And the points symbols?

    the look and feel seems to be really well defined, yes I'd like to play with !

    great project,

    Michel



  • [quote author="Michel Pacilli" date="1307918063"]hi,
    really nice plots !
    [/quote]

    Thank you.

    [quote author="Michel Pacilli" date="1307918063"]
    Do you use QGraphicsPathItems for the "line" plots?
    And the points symbols?
    [/quote]

    Almost. I have written my own custom QGraphicsItem subclasses. In this case the lines are drawn by a ZSplinePathItem and the points are drawn by ZPointItem. Both these classes inherit from another class of mine called ZTransformedPlotItem which allows the subclasses to easily perform transformations between the natural plot coordinate system and the usual QGraphicsView parent item coordinate system. The idea being that by replacing the coordinate system in use you can change the plot type ie switch to a polar coordinate system.

    The ZSplinePathItem uses a QPainterPath internally.

    [quote author="Michel Pacilli" date="1307918063"]
    the look and feel seems to be really well defined, yes I'd like to play with !

    great project,

    Michel[/quote]

    Thank you again. I have a little more work left to do before I will make a first release but I am slowly getting there. Things left to do include:

    • Contour plots (these are almost done)
    • Bar/column charts
    • More style abstractions for easier customisation
    • Peformance optimisations
    • Lots more testing ;-)

    I'll post back here as I make progress.



  • Woohoo! I've just managed to get the basic plot example (shown above running in the simulator) to run natively on Symbian^3. This feels like a major step for me and it shows that we can now relatively easily create applications containing charts on desktop, embedded and mobile targets. :D

    The only changes I made compared to the same example built for desktop was to make the font size a little smaller to make better use of the available space and to call QWidget::showFullScreen() rather than QWidget::show() on the QGraphicsView. I'll try to factor the font size adjustments into the library. The choice between show() and showFullScreen() is down to the application writer.



  • Looks amazing can't wait to try it out :)



  • Thanks Zester. Not too long now hopefully.



  • "This question":http://developer.qt.nokia.com/forums/viewthread/6950/ was split off as a separate topic.



  • Can't wait to give it a try! Great work.
    Any idea when we shall have the chance to play with it?



  • Thx rafy. A few months I expect. I am working on the last couple of features for version 1.0 then I need to do lots of code tidy up and bug fixing ;-) As always my time is limited though.



  • Great work, Sean, i do really like your Charting Component :D


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.