Qwt绘制有数字的柱状图

 

qwt有一些demo,所给的例子可以满足基本的图形绘制,但是要绘制一些特殊的图形还得好好研究下qwt的源码。我觉得此源码相对qt的源码比较简单,容易读懂。

先给大家一些基本的vs2010下qwt的实例。

事例链接:qwt资料       提取码:z6mu 

官方文档链接:qwt官方文档

barchart事例原图:

根据项目需求改造后的barchart(在qwt资料中解压之后可以看到barchart):

虽然变化的部分只有x坐标轴和在柱状图上方加了数字,却几乎让我认真的通读了相关的不少类。

源码分析:

BarChart::BarChart( QWidget *parent ):
    QwtPlot( parent )
{
    setAutoFillBackground( true );

    setPalette( Qt::white );
    canvas()->setPalette( QColor( "LemonChiffon" ) );

    setTitle( "Bar Chart" );
    
    //设置坐标轴y轴左侧文本
    setAxisTitle( QwtPlot::yLeft, "Whatever" );
    //设置坐标轴x轴底部文本
    setAxisTitle( QwtPlot::xBottom, "Whatever" );

    d_barChartItem = new QwtPlotMultiBarChart( "Bar Chart " );
    d_barChartItem->setLayoutPolicy( QwtPlotMultiBarChart::AutoAdjustSamples );
    d_barChartItem->setSpacing( 20 );
    d_barChartItem->setMargin( 3 );

    d_barChartItem->attach( this );

    insertLegend( new QwtLegend() );

    populate();
    setOrientation( 0 );

    setAutoReplot( true );
}

我们从QwtPlotMultiBarChart开始讲解:

d_barChartItem = new QwtPlotMultiBarChart( "Bar Chart " );

帮助文档的详细描述:

QwtPlotMultiBarChart displays a series of a samples that consist each of a set of values.

Each value is displayed as a bar, the bars of each set can be organized side by side or accumulated.

Each bar of a set is rendered by a QwtColumnSymbol, that is set by setSymbol(). The bars of different sets use the same symbols. Exceptions are possible by overloading specialSymbol() or overloading drawBar().

Depending on its orientation() the bars are displayed horizontally or vertically. The bars cover the interval between the baseline() and the value.

In opposite to most other plot items, QwtPlotMultiBarChart returns more than one entry for the legend - one for each symbol.

中文翻译:我们将QwtPlotMultiBarChart的中文翻译拆解,并在每句话的下面附上代码帮助理解:

(1)QwtPlotMultiBarChart展示了一系列的数据,这些数据由值的集合组成

    QVector< QVector<double> > series;
    for ( int i = 0; i < numSamples; i++ )
    {
        QVector<double> values;
        for ( int j = 0; j < numBars; j++ )
            values += ( 2 + qrand() % 8 );

        series += values;
    }

    d_barChartItem->setSamples( series );

其中d_barChartItem为QwtPlotMultiBarChart对象;series的类型为QVector< QVector<double> >;然后QwtPlotMultiBarChart

置数据d_barChartItem->setSamples( series );由此QwtPlotMultiBarChart的数据确实是由QVector中套QVector组成的集合。

以下是setSample的延深,看懂了那句话,下面可看可不看,看看更好。

    void setSamples( const QVector<QwtSetSample> & );
    void setSamples( const QVector< QVector<double> > & );
    void setSamples( QwtSeriesData<QwtSetSample> * );

 

 

(2)每个值均以条形图显示,每组的条形可以并排显示(Grouped)或累积(Stacked)起来显示。

并排显示可看第一个图或者第二个图;累积显示如下:

调用接口为setStyle(),默认设置是QwtPlotMultiBarChart::Grouped.

(3)每个条形图都由QwtColumnSymbol呈现,这些条形图的样式由setSymbol()设置。不同集合的条形图使用相同的符号。

    static const char *colors[] = { "DarkOrchid", "SteelBlue", "Gold" };

    const int numSamples = 5;
    const int numBars = sizeof( colors ) / sizeof( colors[0] );
    for ( int i = 0; i < numBars; i++ )
    {
        QwtColumnSymbol *symbol = new QwtColumnSymbol( QwtColumnSymbol::Box );
        symbol->setLineWidth( 2 );
        symbol->setFrameStyle( QwtColumnSymbol::Raised );
        symbol->setPalette( QPalette( colors[i] ) );

        d_barChartItem->setSymbol( i, symbol );
    }

numBars经过计算为3,但是大家想一想,根据第一个图,总共有15个条形图啊。这个for循环怎么只循环了3次呢?

所以"不同集合的条形图使用相同的符号" 应该懂了吧。循环的三次分别对应中国(Bar0),美国(Bar1),韩国(Bar2)相应类型的QwtColumnSymbol属性。

延深:如果把for循环的代码注释掉,会出现下图:

纵观整个barchart代码,没有设置QwtColumnSymbol的代码,那么条形图是如何显示出来的呢?

这就得看源码了:

void QwtPlotMultiBarChart::drawBar( QPainter *painter,
    int sampleIndex, int valueIndex, const QwtColumnRect &rect ) const
{
    const QwtColumnSymbol *specialSym = NULL;
    if ( sampleIndex >= 0 )
        //先找一下有没有对某个QwtColumnSymbol对象设置特别的样式
        //(比如只对第一组事例的中国设置特别的样式)specialSymbol可以重载
        specialSym = specialSymbol( sampleIndex, valueIndex );

    const QwtColumnSymbol *sym = specialSym;
    if ( sym == NULL )
        //如果找不到特别设置的QwtColumnSymbol,就去找一般的QwtColumnSymbol对象设置
        //(对应上面那个三次循环,setSymbol把QwtColumnSymbol对象的数据存储到了        
        //QwtPlotMultiBarChart的私有数据中)
        sym = symbol( valueIndex );

    if ( sym )
    {
        //如果找到一般的QwtColumnSymbol设置,就去绘制(draw可以重载)
        sym->draw( painter, rect );
    }
    else
    {
        //如果连一般的QwtColumnSymbol对象设置都找不到,那就默认创造QwtColumnSymbol对象
        // we build a temporary default symbol
        QwtColumnSymbol sym( QwtColumnSymbol::Box );
        sym.setLineWidth( 1 );
        sym.setFrameStyle( QwtColumnSymbol::Plain );
        sym.draw( painter, rect );
    }

    delete specialSym;
}

(4)如果想要设置特别的条形图需要重载specialSymbol()或重载drawBar().

 

(5)orientation() 函数决定条形图的水平或垂直显示。条形图覆盖了baseline() 和值之间的间隔。

 

(6)与大多数plot items相反,QwtPlotMultiBarChart返回多个图例条目-每个符号一个。

         这句话根据图形可以看出。

d_barChartItem->attach( this );

attach到底做了什么呢?通过调试整理出了下列的流程:

  QwtPlotMultiBarChart继承自QwtPlotItem

\brief Attach the item to a plot.

  This method will attach a QwtPlotItem to the QwtPlot argument. It will first
  detach the QwtPlotItem from any plot from a previous call to attach (if
  necessary). If a NULL argument is passed, it will detach from any QwtPlot it
  was attached to.

//上述大致意思是将item绑定到QwtPlot上

void QwtPlotItem::attach( QwtPlot *plot )
{
    if ( plot == d_data->plot )
        return;

    if ( d_data->plot )
        d_data->plot->attachItem( this, false );

    d_data->plot = plot;

    if ( d_data->plot )
        d_data->plot->attachItem( this, true );
}


//调用attachItem
void QwtPlot::attachItem( QwtPlotItem *plotItem, bool on )
{
    if ( plotItem->testItemInterest( QwtPlotItem::LegendInterest ) )
    {

        // plotItem is some sort of legend
        //遍历所有的QwtPlotItem获取数据,并更新QLegend对象

        const QwtPlotItemList& itmList = itemList();
        for ( QwtPlotItemIterator it = itmList.begin();
            it != itmList.end(); ++it )
        {
            QwtPlotItem *item = *it;

            QList<QwtLegendData> legendData;
            if ( on && item->testItemAttribute( QwtPlotItem::Legend ) )
            {
                legendData = item->legendData();
                plotItem->updateLegend( item, legendData );
            }
        }
    }
    

    //将item保存到plot中(insertItem插入到QWtPlot基类QWtDict的list列表中)
    if ( on )
        insertItem( plotItem );
    else 
        removeItem( plotItem );

    //该信号不知道发给谁了,没找着。
    Q_EMIT itemAttached( plotItem, on );

    if ( plotItem->testItemAttribute( QwtPlotItem::Legend ) )
    {
        // the item wants to be represented on the legend

        if ( on )
        {
            //
            updateLegend( plotItem );
        }
        else
        {
            const QVariant itemInfo = itemToInfo( plotItem );
            Q_EMIT legendDataChanged( itemInfo, QList<QwtLegendData>() );
        }
    }

    autoRefresh();
}

//获取plotItem对应的QList<QwtLegendData>数据
void QwtPlot::updateLegend( const QwtPlotItem *plotItem )
{
    if ( plotItem == NULL )
        return;

    QList<QwtLegendData> legendData;

    if ( plotItem->testItemAttribute( QwtPlotItem::Legend ) )
        legendData = plotItem->legendData();

    const QVariant itemInfo = itemToInfo( const_cast< QwtPlotItem *>( plotItem) );

    //这个信号发给了QwtLegend,到达槽--->updateLegend中(下段代码)
    Q_EMIT legendDataChanged( itemInfo, legendData );
}



看下面的代码,为了帮助理解,可以看看我写的另一篇博客,QwtLegend

void QwtLegend::updateLegend( const QVariant &itemInfo, 
    const QList<QwtLegendData> &data )
{
    QList<QWidget *> widgetList = legendWidgets( itemInfo );
    
    if ( widgetList.size() != data.size() )
    {
        QLayout *contentsLayout = d_data->view->contentsWidget->layout();

        while ( widgetList.size() > data.size() )
        {
            QWidget *w = widgetList.takeLast();

            contentsLayout->removeWidget( w );

            // updates might be triggered by signals from the legend widget
            // itself. So we better don't delete it here.

            w->hide();
            w->deleteLater();
        }

        for ( int i = widgetList.size(); i < data.size(); i++ )
        {
            QWidget *widget = createWidget( data[i] );

            if ( contentsLayout )
                contentsLayout->addWidget( widget );

            if ( isVisible() )
            {
                // QLayout does a delayed show, with the effect, that
                // the size hint will be wrong, when applications
                // call replot() right after changing the list
                // of plot items. So we better do the show now.

                widget->setVisible( true );
            }

            widgetList += widget;
        }

        if ( widgetList.isEmpty() )
        {
            d_data->itemMap.remove( itemInfo );
        }
        else
        {
            d_data->itemMap.insert( itemInfo, widgetList );
        }

        updateTabOrder();
    }
    
    for ( int i = 0; i < data.size(); i++ )
        updateWidget( widgetList[i], data[i] );
}

void QwtLegend::updateLegend( const QVariant &itemInfo, 
    const QList<QwtLegendData> &data )
更新数据:
{
@1.先判断legendWidgets( itemInfo )中是否有数据
如果没有,拿着data中的数据,在QWtLegend的布局中去addWidget(data中数据)并设置一些widget的点击等属性(信号槽)
并将这些widget存储到QWtLegend自己的数据中。
@2. updateTabOrder();
按下tab设置焦点顺序
}
@3.for ( int i = 0; i < data.size(); i++ )
        updateWidget( widgetList[i], data[i] );
遍历设置label值
//return List of widgets associated to a item

QList<QWidget *> QwtLegend::legendWidgets( const QVariant &itemInfo ) const
{
    return d_data->itemMap.legendWidgets( itemInfo );
}


class QwtLegend::PrivateData
{
public:
    PrivateData():
        itemMode( QwtLegendData::ReadOnly ),
        view( NULL )
    {
    }

    QwtLegendData::Mode itemMode;
    QwtLegendMap itemMap;

    class LegendView;
    LegendView *view;
};

可跳转到QwtLegend的链接:QwtLegend

insertLegend( new QwtLegend() );

具体解释下这两句话,不要相信自己的眼睛。

    d_barChartItem->setBarTitles( titles );
    d_barChartItem->setLegendIconSize( QSize( 10, 14 ) );

关于坐标轴的设置可以看QwtScaleDraw 的链接:QwtScaleDraw

下面简单介绍下代码:

    //禁用自动缩放并为所选轴指定固定比例
    //函数原型:void setAxisScale( int axisId, double min, double max, double step = 0 );
    //axisID指哪个轴,最小(开始)是几,最大(最后)是几,执行几步。
    //d_barChartItem->dataSize()为5.底部x轴,开始值为0,最后的值为4,每次一步。
    setAxisScale( axis1, 0, d_barChartItem->dataSize() - 1, 1.0 );

    //自动设置,axis2坐标轴的最大刻度为你设置的数据的最大值
    setAxisAutoScale( axis2 );
    
    
    QwtScaleDraw *scaleDraw1 = axisScaleDraw( axis1 );
    //刻度线的基线
    scaleDraw1->enableComponent( QwtScaleDraw::Backbone, false );
    //刻度
    scaleDraw1->enableComponent( QwtScaleDraw::Ticks, false );

    QwtScaleDraw *scaleDraw2 = axisScaleDraw( axis2 );
    scaleDraw2->enableComponent( QwtScaleDraw::Backbone, true );
    scaleDraw2->enableComponent( QwtScaleDraw::Ticks, true );

    plotLayout()->setAlignCanvasToScale( axis1, true );
    plotLayout()->setAlignCanvasToScale( axis2, false );

    plotLayout()->setCanvasMargin( 0 );
    updateCanvasMargins();

 

void QwtPlot::attachItem( QwtPlotItem *plotItem, bool on )

至此,attach(this)便执行完了。

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值