qml 动态创建复杂饼图

数据的展示自然离不开图表,qt默认安装没有将QtCharts模块集成并社区版中,导致只能自己编译或使用第三方图库,如chartDirector或qwtplot等等。

本次介绍Qt在qml中使用QtCharts绘制稍复杂的饼图示例,并且该饼图提供一个接口传入数据并非一个简单的demo程序。

首先在安装Qt时要选中QtCharts模块,这点就不介绍了。

先来看一下效果


中间为第一环绘制了第一层的数据比例,第二层是以第一层为基础,将第一层的继续分比例显示。这里我们先来熟悉一下画饼图的控件

PieSeries{
    startAngle: 起始角度
    endAngle: 终止角度
    holeSize: 内狐离圆心的距离
    size: 外狐离圆心的距离
}
以上只列举了本次博客用到的主要属性,如果还想了解其它属性请查看Qt帮助文档。下一个控件是PieSlice
PieSlice{
    angleSpan: 狐所占角度
    labelVisible: 是否显示标签
    label: 标签显示内容
    value: 狐所代表的值
}
知道以上两个控件后,我们大概可以知道,饼图是由扇形组成的,而要想出现环状效果,只要使扇形内儿离圆心的距离大于0,且外儿离圆心的距离
大于内狐离圆心的距离就可以了。所以我们 可以构想出上图的思路,在一个ChartView中放一个PieSeries画里面的一圈,这个PieSeies里有两PieSlice;
设置它的startAngle =0, endAngle=360;里面一圈就画好了。接着在ChartView中再放一个PieSeries用来画网银止付的外环,这个PieSeries的起始角度为statAngle=0,
终止角度为endAngle= "网银止付的slice对象".angleSpan,内狐离圆心的距离为最里环的外狐离圆心的距离,由于最里的一环没有设置Size(默认为0.7),所以这一个PieSeries
的holeSize=0.7,外狐离圆心的距离比0.7大就可以。这里我取了size=0.8;第二个PieSeries就画好了。最后一个原理和第二个一样。我们的代码可以是下面这样
     ChartView{
        id: chartView
        title: "2017-05-12 至 2017-08-29电信诈骗统计饼图"
        anchors.fill: parent
        legend.alignment: Qt.AlignRight
        antialiasing: true
        animationOptions: ChartView.AllAnimations
        property real sumAngle: 0

        PieSeries{
            PieSlice{
                id: sliceBank
                labelVisible: true
                labelPosition: PieSlice.LabelInsideHorizontal
                label: qsTr("网银止付 ") + (new Array(2).join('0') + percentage * 100).slice(-2) + "%"
                value: pieBankSeries.sum
            }

            PieSlice{
                id: sliceCall
                labelVisible: true
                labelPosition: PieSlice.LabelInsideHorizontal
                label: qsTr("智能追呼 ") + (new Array(2).join('0') + percentage * 100).slice(-2) + "%"
                value: pieCallSeries.sum
            }
        }

        PieSeries{
            id: pieBankSeries
            size: 0.8
            holeSize: 0.7
            startAngle: 0
            endAngle: sliceBank.angleSpan
            PieSlice{
                labelVisible: true
                label: qsTr("中国银行")
                value: 120
            }
            PieSlice{
                labelVisible: true
                label: qsTr("工商银行")
                value: 130
            }
            PieSlice{
                labelVisible: true
                label: qsTr("招商银行")
                value: 142
            }
        }

        PieSeries{
            id: pieCallSeries
            size: 0.8
            holeSize: 0.7
            startAngle: sliceBank.angleSpan
            endAngle: 360

            PieSlice{
                labelVisible: true
                label: qsTr("中国电信")
                value: 123
            }
            PieSlice{
                labelVisible: true
                label: qsTr("中国联通")
                value: 98
            }
            PieSlice{
                labelVisible: true
                label: qsTr("中国移动")
                value: 400
            }
        }
    }
像上面这样我们便可以画出上图一样的饼图,但是这样很明显,饼图的数据很死,能表示的数据也有限。所以我们应该把它做成接口。
这里我先用了json做为数据的接口,给出上面图的数据如下
[{model: "网银止付",data: [{label: "招商银行",total_count: 115},
                           {label: "工商银行",total_count: 200},
                           {label: "中国银行",total_count: 220}]},
 {model: "智能追呼",data: [{label: "中国移动",total_count: 100},
                           {label: "中国联通",total_count: 89},
                           {label: "中国电信",total_count: 400}]}]
解析数据的方法因人而异,这里我分成了两步,一是写了一个画饼图的方法,二是写了一个解析json并调用画饼图的方法的方法。
画饼图的方法如下
         function createPieSeries(pieArgObj){
             var str = "import QtQuick 2.7;import QtCharts 2.0;PieSeries{}";
             var pieSeriesObj = Qt.createQmlObject(str,chartView,"dynamicSnippet1"); //创建一个PieSeries以chartView为父对象
             pieSeriesObj.startAngle = pieArgObj.startAngle; //起始角度
             pieSeriesObj.endAngle = pieArgObj.endAngle; //终止角度
             var dataArray = pieArgObj.data;
             var dataCount = dataArray.length;

             for(var i=0;i<dataCount;i++){
                 var sliceObj = pieSeriesObj.append(dataArray[i].label,dataArray[i].total_count); //通过PieSeries的append方法添加PieSlice
                 sliceObj.labelVisible = true;        //设置PieSlice的标签可见
                 sliceObj.label = dataArray[i].label;  //设置PieSlice的标签内容
             }

             if(pieArgObj.position == 0){       //0代表是内环,此时显示百分比
                 var sliceCount = pieSeriesObj.count;
                 for(var j =0; j<sliceCount;j++){
                     sliceObj = pieSeriesObj.at(j);
                     sliceObj.labelPosition = PieSlice.LabelInsideHorizontal //设置PieSlice的label显示位置
                     sliceObj.label = sliceObj.label + " " + (new Array(2).join('0') + sliceObj.percentage * 100).slice(-2) + "%"; //保留两位小数,并以百
                 }         //分比显示
             }

             return pieSeriesObj;
         }
解析数据的方法如下
         function loadData(jsObj){
             if(!(jsObj instanceof Array)){    //如果不是array,直接返回
                 console.log("errror argument!")
                 return;
             }

             var dataLength = jsObj.length;
             var modelArray = new Array();
             var modelObject = new Object();
             for(var i=0;i<dataLength;i++){ //获取网银止付和智能追呼的总值,通过计算子块的和;
                 var nodeData = jsObj[i];
                 var nodeDataArray = nodeData.data;
                 var nodeDataArrayLength = nodeDataArray.length;
                 var pieSeriesSumValue = 0
                 for(var j=0;j<nodeDataArrayLength;j++){
                     pieSeriesSumValue += nodeDataArray[j].total_count;
                 }
                 modelArray[i] = {label: nodeData.model,total_count: pieSeriesSumValue};
             }

             var obj = createPieSeries({startAngle:0,endAngle:360,data: modelArray,position: 0}); //先创建最里环

             var endAngle = new Number();
             for(var j=0;j<dataLength;j++){
                 var curAngle = obj.at(j).angleSpan; //
                 endAngle += curAngle;
                 var subObj = createPieSeries({startAngle: endAngle - curAngle,endAngle: endAngle,data: jsObj[j].data,position: 1}); //statAngle为最里环
                 subObj.holeSize = 0.7; //的每个PieSlice的angleSpan之各减去第二环当前的角度,endAngle为最里环每个PieSlice的angleSpan和
                 subObj.size = 0.8;    //设置内狐和外狐离圆心的距离。
             }
         }

上面我做的这个接口只能表示两环,表示的数据块理论上是无限个,这样我们就可以传入数据动态创建了。贴一个完整代码。
import QtQuick 2.8
import QtCharts 2.0
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Item {
         anchors.fill: parent
         ChartView{
            id: chartView
            title: "2017-05-12 至 2017-08-29电信诈骗统计饼图"
            anchors.fill: parent
            legend.alignment: Qt.AlignRight
            antialiasing: true
            animationOptions: ChartView.AllAnimations
            property real sumAngle: 0

        }

         function createPieSeries(pieArgObj){
             var str = "import QtQuick 2.7;import QtCharts 2.0;PieSeries{}";
             var pieSeriesObj = Qt.createQmlObject(str,chartView,"dynamicSnippet1");
             pieSeriesObj.startAngle = pieArgObj.startAngle;
             pieSeriesObj.endAngle = pieArgObj.endAngle;
             var dataArray = pieArgObj.data;
             var dataCount = dataArray.length;

             for(var i=0;i<dataCount;i++){
                 var sliceObj = pieSeriesObj.append(dataArray[i].label,dataArray[i].total_count);
                 sliceObj.labelVisible = true;
                 sliceObj.label = dataArray[i].label;
             }

             if(pieArgObj.position == 0){
                 var sliceCount = pieSeriesObj.count;
                 for(var j =0; j<sliceCount;j++){
                     sliceObj = pieSeriesObj.at(j);
                     sliceObj.labelPosition = PieSlice.LabelInsideHorizontal
                     sliceObj.label = sliceObj.label + " " + (new Array(2).join('0') + sliceObj.percentage * 100).slice(-2) + "%";
                 }
             }

             return pieSeriesObj;
         }

         function loadData(jsObj){
             if(!(jsObj instanceof Array)){
                 console.log("errror argument!")
                 return;
             }

             var dataLength = jsObj.length;
             var modelArray = new Array();
             var modelObject = new Object();
             for(var i=0;i<dataLength;i++){
                 var nodeData = jsObj[i];
                 var nodeDataArray = nodeData.data;
                 var nodeDataArrayLength = nodeDataArray.length;
                 var pieSeriesSumValue = 0
                 for(var j=0;j<nodeDataArrayLength;j++){
                     pieSeriesSumValue += nodeDataArray[j].total_count;
                 }
                 modelArray[i] = {label: nodeData.model,total_count: pieSeriesSumValue};
             }

             var obj = createPieSeries({startAngle:0,endAngle:360,data: modelArray,position: 0});

             var endAngle = new Number();
             for(var j=0;j<dataLength;j++){
                 var curAngle = obj.at(j).angleSpan;
                 endAngle += curAngle;
                 var subObj = createPieSeries({startAngle: endAngle - curAngle,endAngle: endAngle,data: jsObj[j].data,position: 1});
                 subObj.holeSize = 0.7;
                 subObj.size = 0.8;
             }
         }

         Component.onCompleted: loadData([{model: "网银止付",data: [{label: "招商银行",total_count: 115},
                                                                   {label: "工商银行",total_count: 200},
                                                                   {label: "中国银行",total_count: 220}]},
                                          {model: "智能追呼",data: [{label: "中国移动",total_count: 100},
                                                                   {label: "中国联通",total_count: 89},
                                                                   {label: "中国电信",total_count: 400}]}])
    }
}
这个结果就是最顶上的饼图,我修改数据如下之后
         Component.onCompleted: loadData([{model: "网银止付",data: [{label: "招商银行",total_count: 115},
                                                                   {label: "工商银行",total_count: 200},
                                                                   {label: "中国银行",total_count: 220}]},
                                          {model: "智能追呼",data: [{label: "中国移动",total_count: 100},
                                                                   {label: "中国联通",total_count: 89},
                                                                   {label: "中国电信",total_count: 400}]},
                                          {model: "其它",data: [{label: "报纸",total_count: 100},
                                                               {label: "杂志",total_count: 89},
                                                               {label: "书刊",total_count: 98}]}])

结果为

到此结束,祝大家工作愉快。



  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值