zoukankan      html  css  js  c++  java
  • Qt之自绘制饼图

    1、说明

        最近在搞绘图方面的工作,说实话C++的第三方绘图库并不算多,总之我了解的有:qtcharts、ChartDirector、qwt、kdchart和QCustomPlot。这几个库各有利弊。

    • qtcharts:qt5.7之后才开源的模块,支持绘制各种图标,并且功能相当丰富,但是可扩展性差,如果自己想高度定制,比较困难,主要是和qt的源码风格有决定性的关系。
    • ChartDirector:开源的第三方绘图库,使用方便,推荐使用
    • qwt:主要绘制仪表盘类似的东西(这个库可以编译后加入qt帮助文档)
    • kdchart:不仅可以绘制图表,而且可以绘制甘特图,功能也都挺好使,我个人之前在qt4.7的时候使用过
    • QCustomPlot:简答的绘图库,因为只有两个文件,如果想高度定制我个人推荐这个靠谱,毕竟理解起来容易些

    2、效果展示

        下边是绘制的饼图展示效果,当然了不能满足大多数人的需要,我主要是在这里提供一种思路,如果需要在绘制上有所调整的小伙伴可以下载demo自行修改。

    图1 展示图1

    图2 展示2

    图3 展示图3

    3、思路分析

        上边三张展示图,如果要说从理解难以成都来说,展示图3是比较容易理解。下边我就几个需要注意的细节描述下:

    • 图表矩形距离边框距离,影响图表绘制矩形的因素
    • 图表绘制方向,默认是逆时针
    • 图表文本描述位置
    • legend描述位置,demo中已经提供了接口,可以支持不同legend的展现形式
    • 箭头长短
    • 空心饼图(圆环图)

        饼图绘制关键步骤:

    • 添加数据项->构造数据缓存->绘制图表
    • 窗口大小变化->构造数据项矩形->构造数据缓存->绘制图表

    4、源码解说

        首先来看两个结构体,主要是用来缓存数据,PieItemPrivate存储的是每一个item项的内容,包括item的legend,文本、颜色、值和一些辅助的结构体;PieChartPrivate结构是饼图类的私有数据存储结构,具体含义看注释

     1 struct PieItemPrivate
     2 {
     3     PieItem item;//用户插入数据时的结构,包括注释、值和颜色
     4     QPainterPath path;//项绘制时区域
     5     QPoint labelPos;//文本位置
     6     QRect m_LegendRect;//legend的矩形
     7 };
     8 
     9 struct PieChartPrivate
    10 {
    11     bool m_bLegendVisible = false;//是否显示图例
    12     int m_Minx = 25;//左右最小边距
    13     int m_Miny = 25;//上下最小边距
    14     int m_MinDiameter = 130;//饼图最小直径
    15     int m_RingWidth = 0;//如果是环,环的宽度
    16     int m_StartRotationAngle = 0;//绘制item项的时候,其实角度
    17     int m_LegendWidth = 100;//图表宽度  可以在插入新数据项的时候更新,计算展示legend所需要的最小尺寸
    18     int m_LegendHeight = 30;//图例高度  可以在插入新数据项的时候更新,计算展示legend所需要的最小尺寸
    19     double m_SumValue = 0;//所有item的value和
    20     QRect m_PieRect;//饼图绘制矩形
    21     QColor m_LabelColor = QColor(0, 0, 0);//百分比文字颜色
    22     QString m_RingLabel = QStringLiteral("饼图");//图表中心文字描述
    23     QVector<PieItemPrivate> m_Items;//图表项
    24 };

    1、当有新数据或者窗口大小发生变化时,计算数据缓存

     1 void PieChart::ConstructData()
     2 {
     3     int pos = d_ptr->m_StartRotationAngle;
     4     int angle;
     5     QPainterPath subPath;
     6     subPath.addEllipse(d_ptr->m_PieRect.adjusted(d_ptr->m_RingWidth, d_ptr->m_RingWidth, -d_ptr->m_RingWidth, -d_ptr->m_RingWidth));
     7     
     8     for (auto iter = d_ptr->m_Items.begin(); iter != d_ptr->m_Items.end(); ++iter)
     9     {
    10         angle = 16 * iter->item.value / d_ptr->m_SumValue * 360;
    11     
    12         QPainterPath path;
    13         path.moveTo(d_ptr->m_PieRect.center());
    14         path.arcTo(d_ptr->m_PieRect.x(), d_ptr->m_PieRect.y(), d_ptr->m_PieRect.width(), d_ptr->m_PieRect.height(), pos / 16.0, angle / 16.0);
    15         path.closeSubpath();
    16         
    17         if (d_ptr->m_RingWidth > 0 && d_ptr->m_RingWidth <= d_ptr->m_PieRect.width() / 2)
    18         {
    19             path -= subPath;
    20         }
    21         
    22         iter->path = path;
    23 
    24         double labelAngle = (pos + angle / 2) / 16;
    25         double tx = (d_ptr->m_PieRect.width() - d_ptr->m_RingWidth) / 2 * qCos(labelAngle / 360 * 2 * 3.1415926);
    26         double ty = -(d_ptr->m_PieRect.width() - d_ptr->m_RingWidth) / 2 * qSin(labelAngle / 360 * 2 * 3.1415926);
    27 
    28         iter->labelPos = QPoint(tx, ty) + d_ptr->m_PieRect.center();
    29 
    30         pos += angle;
    31     }
    32 }

    2、当窗口大小发生变化时,重新计算各项所在矩形,ConstructRect方式是用来计算各子项矩形区域的,内部调用ConstructCornerLayout方法是生产制定的布局,有兴趣的小伙伴可以写自己的矩形区域计算方式,开达到不同的绘制效果。

     1 void PieChart::ConstructRect(const QSize & size)
     2 {
     3     switch (d_ptr->m_Items.size())
     4     {
     5     case 4:
     6         ConstructCornerLayout(size);
     7     default:
     8         break;
     9     }
    10 }
    11 //该方法是针对4个legend,并且在四角的位置所计算的布局方式,小伙伴也可以实现自己的布局计算,然后在ConstructRect接口中调用
    12 void PieChart::ConstructCornerLayout(const QSize & size)
    13 {
    14     int currentR = d_ptr->m_MinDiameter;
    15     int diameter;
    16     int horiWidth = size.width();
    17     if (d_ptr->m_bLegendVisible)
    18     {
    19         horiWidth -= d_ptr->m_LegendWidth * 2;
    20     }
    21 
    22     if (horiWidth > size.height())
    23     {
    24         diameter = size.height();
    25     }
    26     else
    27     {
    28         diameter = horiWidth;
    29     }
    30 
    31     int x, y;
    32     int r = diameter - d_ptr->m_Minx * 2;
    33     currentR = r > currentR ? r : currentR;
    34     if (d_ptr->m_bLegendVisible)
    35     {
    36         x = d_ptr->m_Minx + d_ptr->m_LegendWidth;
    37         y = (size.height() - currentR) / 2;
    38       //计算4个legend位置
    39         d_ptr->m_Items[1].m_LegendRect = QRect(d_ptr->m_Minx, d_ptr->m_Miny, d_ptr->m_LegendWidth, d_ptr->m_LegendHeight);
    40         d_ptr->m_Items[0].m_LegendRect = QRect(x + r, d_ptr->m_Miny, d_ptr->m_LegendWidth, d_ptr->m_LegendHeight);
    41         d_ptr->m_Items[3].m_LegendRect = QRect(x + r, size.height() - d_ptr->m_Miny - 30, d_ptr->m_LegendWidth, d_ptr->m_LegendHeight);
    42         d_ptr->m_Items[2].m_LegendRect = QRect(d_ptr->m_Minx, size.height() - d_ptr->m_Miny - 30, d_ptr->m_LegendWidth, d_ptr->m_LegendHeight);
    43     }
    44     else
    45     {
    46         x = d_ptr->m_Minx;
    47         y = d_ptr->m_Miny;
    48     }
    49 
    50     d_ptr->m_PieRect = QRect(x, y, currentR, currentR);//计算饼图位置
    51 }

    5、测试代码

     1 int main(int argc, char *argv[])
     2 {
     3     QApplication a(argc, argv);
     4 
     5     PieChart w;
     6     w.AddData(100, Qt::red, "red");
     7     w.AddData(100, Qt::green, "green");
     8     w.AddData(100, Qt::blue, "blue");
     9     w.AddData(100, Qt::gray, "gray");
    10     w.show();
    11 
    12     return a.exec();
    13 }

    6、示例下载

        Qt之自绘制饼图

    如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!! 

     

      


    很重要--转载声明

    1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords
    2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。 

  • 相关阅读:
    CompletableFuture详解1
    使用CompletionService解决耗时时间过长任务导致的阻塞问题以及解决ExecutorService.shutdownNow()导致正在执行中的任务被打断,但该任务不会被返回
    ScheduledExecutorService和ScheduledThreadPoolExecutor
    future的缺陷和CompletionService对他的优化
    Future方法详解
    ExecutorService的API详解
    scheduler的前奏Timer&Crontab和Quartz的比较
    [Paper Reading]--Exploiting Relevance Feedback in Knowledge Graph
    [Leetcode] DP-- 96. Unique Binary Search Trees
    [Leetcode] DP-- 474. Ones and Zeroes
  • 原文地址:https://www.cnblogs.com/swarmbees/p/6033217.html
Copyright © 2011-2022 走看看