Qt 三维柱状图 Q3DBar 和 三维条形图中的数据序列 QBar3DSeries

(一) 使用 Q3DBars 图形类和 QBar3DSeries 序列类可以绘制三维柱状图

窗口右侧是用 Q3DBars 和 QBar3DSeries 绘制的三维柱状图,这个图只有一个QBar3DSeries序列,数据是按行存储的,可以有多行。水平方向是行坐标轴和列坐标轴,使用OCategory3DAxis 坐标轴类;垂直方向是数值坐标轴,使用 QValue3DAxis 坐标轴类。在图上点击一个棒柱时,可以在图上显示其行标签、列标签和数值,状态栏上还会显示其行编号、列编号和数值。无须额外编程或设置,在图上按住鼠标右键并上下左右拖动鼠标可以进行水平和垂直方向的旋转,滚动鼠标滚轮可以进行缩放。 窗口工具栏上的按钮用于修改棒柱的基本颜色,修改选中棒柱的数值,添加、插入或删除行等。 窗口左侧是操作面板,分为多个分组框。

  • “旋转和平移”分组框。可以选择预设的三维图观察视角,可以通过 QSlider 组件进行水平旋转、垂直旋转和缩放。
  • 分组框里的4个方向按钮用于使三维柱状图在4个方向上平移,中间的按钮用于复位视角。
  • “图形总体”分组框。用于设置三维图形的主题、标签字体大小、棒柱选择模式,以及设置三维图形的各种元素的可见性和显示效果等。
  • “序列设置”分组框。设置序列的一些属性,如棒柱的样式、光滑效果等。

    效果图:

    (二) 3D部件创建及添加到窗口

    1 添加三维图Q3DBars 三维图的容器QWidget和三维条形图中的数据序列QBar3DSeries

    private:
        QWidget* graphContainer;        //三维图的容器
        Q3DBars* graph3D;               //三维图
        QBar3DSeries* series;           //三维条形图中的数据序列
    

    2 初始化 3D部件并添加到窗口

    void mainwindow::iniGraph3D()
    { graph3D = new Q3DBars();            //创建三维图
        graphContainer = QWidget::createWindowContainer(graph3D, this);//Q3DBars继承自QWindow
        graph3D->scene()->activeCamera()    //设置视角
                    ->setCameraPreset(Q3DCamera::CameraPresetFrontHigh); 
        //设置 Z坐标轴
        QValue3DAxis* axisV = new QValue3DAxis; //数值坐标
        axisV->setTitle(QString::fromLocal8Bit("销量"));
        axisV->setTitleVisible(true);
        axisV->setLabelFormat("%d");
        graph3D->setValueAxis(axisV);  			 //设置数值坐标轴
        //设置 X坐标轴
        QCategory3DAxis* axisRow = new QCategory3DAxis;
        axisRow->setTitle("row axis");
        axisRow->setTitleVisible(true);
        graph3D->setRowAxis(axisRow);  			 //设置行坐标轴
        //设置 Y坐标轴
        QCategory3DAxis* axisCol = new QCategory3DAxis;
        axisCol->setTitle("column axis");
        axisCol->setTitleVisible(true);
        graph3D->setColumnAxis(axisCol);		//设置列坐标轴
        //创建序列
        series = new QBar3DSeries;
        series->setMesh(QAbstract3DSeries::MeshCylinder);       //棒柱形状
        series->setItemLabelFormat("(@rowLabel,@colLabel): %d");//项的标签显示格式
        graph3D->addSeries(series);
        //设置数据代理的数据
        QBarDataArray* dataArray = new QBarDataArray; //棒柱数据数组,typedef QList QBarDataArray;
        for (int i = 0; i <3; i++)   			  //行
        { QBarDataRow* dataRow = new QBarDataRow;   //棒柱数据行,typedef QList QBarDataRow;
            for (int j = 1; j <= 5; j++)          //列
            { quint32 value = rand()%15+1;          //随机整数[1,15]
                QBarDataItem* dataItem = new QBarDataItem;//创建棒柱数据对象
                dataItem->setValue(value);            //设置棒柱的数据
                dataRow->append(*dataItem);           //添加到棒柱数据行
            }
            dataArray->append(dataRow);               //棒柱数据数组添加一个棒柱数据行
        }
        QStringList rowLabs;    //行坐标标签
        rowLabs << "Week1" << "Week2" << "Week3";
        series->dataProxy()->setRowLabels(rowLabs); //设置数据代理的行标签
        QStringList colLabs;    //列坐标标签
        colLabs << "Mon" << "Tue" << "Wed" << "Thur" << "Fri";
        series->dataProxy()->setColumnLabels(colLabs);//设置数据代理的列标签
        series->dataProxy()->resetArray(dataArray);   //重设数据代理的数据
        connect(series, &QBar3DSeries::selectedBarChanged,
                 this, &mainwindow::do_barSelected);
    }
    

    2-1 设置项标签格式

    如果没有为该系列显式设置数据代理,则该系列将创建默认代理。设置另一个代理将销毁现有代理以及添加到其中的所有数据。

    标签格式注释
    @rowTitle来自行轴的标题
    @colTitle列轴的标题
    @valueTitle来自值轴的标题
    @rowIdx可见行索引,使用图形区域设置进行了本地化
    @colIdx可见列索引,使用图形区域设置进行了本地化
    @rowLabel来自行轴的标签
    @colLabel来自列轴的标签
    @valueLabel使用附加到图形的值轴格式格式化的项值
    @seriesName系列的名称
    %指定格式的项值。使用与QValue3DAxis::abelFormat相同的规则进行格式化

    3 选中棒柱

    void mainwindow::do_barSelected(const QPoint& position)
    { if (position.x() < 0 || position.y() < 0)  //必须加此判断
        { ui->actBar_ChangeValue->setEnabled(false);
            return;
        }
        ui->actBar_ChangeValue->setEnabled(true);
        const QBarDataItem* bar = series->dataProxy()->itemAt(position);
        QString info = QString::fromLocal8Bit("选中的棒柱,Row=%1, Column=%2, Value=%3")
            .arg(position.x()).arg(position.y()).arg(bar->value());
        ui->statusBar->showMessage(info);
    }
    

    (三) 3D部件创建及添加到窗口

    1 序列颜色 - 设置序列基本颜色

    void mainwindow::on_actSeries_BaseColor_triggered()
    { //设置序列基本颜色
        QColor  color = series->baseColor();
        color = QColorDialog::getColor(color);
        if (color.isValid())
        {series->setBaseColor(color);
    	}        
    }
    

    2 重新生成 - 重新生成数据画图

    void mainwindow::on_actRedraw_triggered()
    { QBarDataProxy* dataProxy = new QBarDataProxy;    //新建数据代理
        int rowCount = series->dataProxy()->rowCount();  //数据代理的行数
        for (int i = 0; i < rowCount; i++)  //行
        { QBarDataRow* dataRow = new QBarDataRow;       //棒柱数据行
            for (int j = 1; j <= 5; j++)     //列
            { quint32 value = rand() % (15 - 5 + 1) + 5;
                QBarDataItem* dataItem = new QBarDataItem;   //数据项
                dataItem->setValue(value);
                dataRow->append(*dataItem);
            }
            QString rowStr = QString::fromLocal8Bit("第%1周").arg(i + 1);
            dataProxy->addRow(dataRow, rowStr);     //添加行数据和标签
        }
        QStringList colLabs = series->dataProxy()->columnLabels();    //原来的列坐标轴标签
        dataProxy->setColumnLabels(colLabs);
        series->dataProxy()->resetArray();  //清除数据代理和坐标轴标签,必须清除,否则不能重新设置坐标轴标签
        series->setDataProxy(dataProxy);    //重新设置数据代理,会删除之前的数据代理
    }
    

    3 修改数值 - 修改选中棒柱的数值(F2)

    void mainwindow::on_actBar_ChangeValue_triggered()
    { QPoint position = series->selectedBar();
        if (position.x() < 0 || position.y() < 0)   //必须加此判断
            return;
        QBarDataItem bar = *(series->dataProxy()->itemAt(position));
        qreal value = bar.value();    //原来的值
        bool ok;
        value = QInputDialog::getInt(this, QString::fromLocal8Bit("输入数值"),
                                           QString::fromLocal8Bit("更改棒柱数值"), value, 0, 50, 1, &ok);
        if (ok)
        { bar.setValue(value);
            series->dataProxy()->setItem(position, bar);
        }
    }
    

    4 添加行 - 添加一行

    void mainwindow::on_actData_Add_triggered()
    { //添加行
         const QString rowLabel = QInputDialog::getText(this,
                                     QString::fromLocal8Bit("输入字符串"),
                                     QString::fromLocal8Bit("请输入行标签"));
        if (rowLabel.isEmpty())
            return;
        QBarDataRow* dataRow = new QBarDataRow;       //棒柱数据行
        for (int j = 1; j <= 5; j++)     //固定5列
        { quint32 value = rand()%(25 - 15 + 1) + 15;
            QBarDataItem* dataItem = new QBarDataItem;
            dataItem->setValue(value);
            dataRow->append(*dataItem);
        }
        series->dataProxy()->addRow(dataRow, rowLabel);     //添加棒柱数据行和标签
      
    }
    

    5 插入行 - 插入一行

    void mainwindow::on_actData_Insert_triggered()
    { //插入行
        QString rowLabel = QInputDialog::getText(this, QString::fromLocal8Bit("输入字符串"), 
        QString::fromLocal8Bit("请输入行标签"));
        if (rowLabel.isEmpty())
            return;
      
        QPoint position = series->selectedBar();
        int index = position.x(); //当前行号
        if (index < 0)
            index = 0;
        QBarDataRow* dataRow = new QBarDataRow;       //棒柱数据行
        for (int j = 1; j <= 5; j++)     //固定5列
        { quint32 value = rand() % (30 - 20) + 20; 
            QBarDataItem* dataItem = new QBarDataItem;
            dataItem->setValue(value);
            dataRow->append(*dataItem);
        }
        series->dataProxy()->insertRow(index, dataRow);    //插入棒柱数据行
        
    }
    

    6 删除行 - 删除当前行

    void mainwindow::on_actData_Delete_triggered()
    { QPoint position = series->selectedBar();
        if (position.x() < 0 || position.y() < 0)
            return;
        int rowIndex = position.x();  //当前行号
        int removeCount = 1;          //删除的行数
        int removeLabels = true;      //是否删除行标签
        series->dataProxy()->removeRows(rowIndex, removeCount, removeLabels);
    }
    

    7 退出 - 退出本程序

    void mainwindow::on_actQiut_triggered()
    { QApplication::quit();
    }
    

    (四) “旋转和平移”分组框功能

    1 预设视角

    void mainwindow::on_comboCamera_currentIndexChanged(int index)
    { Q3DCamera::CameraPreset  cameraPos =  Q3DCamera::CameraPreset(index);
        graph3D->scene()->activeCamera()->setCameraPreset(cameraPos);
    }
    

    预设视角类型参考 enum Q3DCamera::CameraPreset

    2 水平旋转

    void mainwindow::on_sliderH_valueChanged(int value)
    { graph3D->scene()->activeCamera()->setXRotation(value);
    }
    

    3 垂直旋转

    void mainwindow::on_sliderV_valueChanged(int value)
    { graph3D->scene()->activeCamera()->setYRotation(value);
    }
    

    4 缩放

    void mainwindow::on_sliderZoom_valueChanged(int value)
    { graph3D->scene()->activeCamera()->setZoomLevel(value);
    }
    

    5 下移

    void mainwindow::on_btnMoveDown_clicked()
    { QVector3D target3D = graph3D->scene()->activeCamera()->target();
        qreal z = target3D.z();
        target3D.setZ(z + 0.1);
        graph3D->scene()->activeCamera()->setTarget(target3D);
    }
    

    6 上移

    void mainwindow::on_btnMoveUp_clicked()
    { QVector3D target3D = graph3D->scene()->activeCamera()->target();
        qreal z = target3D.z();
        target3D.setZ(z - 0.1);
        graph3D->scene()->activeCamera()->setTarget(target3D);
    }
    

    7 左移

    void mainwindow::on_btnMoveLeft_clicked()
    { QVector3D target3D = graph3D->scene()->activeCamera()->target();
        qreal x = target3D.x();
        target3D.setX(x + 0.1);
        graph3D->scene()->activeCamera()->setTarget(target3D);
    }
    

    8 右移

    void mainwindow::on_btnMoveRight_clicked()
    { QVector3D target3D = graph3D->scene()->activeCamera()->target();
        qreal x = target3D.x();
        target3D.setX(x - 0.1);
        graph3D->scene()->activeCamera()->setTarget(target3D);
    }
    

    9 复位(到 FrontHigh 视角)

    void mainwindow::on_btnResetCamera_clicked()
    { graph3D->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFrontHigh);
    

    (五) “图形总体”分组框功能

    1 显示选中棒柱的标签 CheckBox

    void mainwindow::on_cBoxTheme_currentIndexChanged(int index)
    { Q3DTheme* currentTheme = graph3D->activeTheme();
        currentTheme->setType(Q3DTheme::Theme(index));
    }
    

    2 轴标签字体大小

    void mainwindow::on_spinFontSize_valueChanged(int arg1)
    { QFont font = graph3D->activeTheme()->font();
        font.setPointSize(arg1);
        graph3D->activeTheme()->setFont(font);   
    }
    

    3 选择模式

    void mainwindow::on_cBoxSelectionMode_currentIndexChanged(int index)
    { graph3D->setSelectionMode(QAbstract3DGraph::SelectionFlags(index));
    }
    

    4 显示背景 CheckBox

    void mainwindow::on_chkBoxBackground_clicked(bool checked)
    { graph3D->activeTheme()->setBackgroundEnabled(checked);
    }
    

    4-1 选择模式

    enum QAbstract3DGraph::SelectionFlag

    枚举值含义
    QAbstract3DGraph::SelectionNone不允许选择
    QAbstract3DGraph::SelectionItem选择并且高亮显示一个项
    QAbstract3DGraph::SelectionRow选择并且高亮显示一行
    QAbstract3DGraph::SelectionItemAndRow选择一个项和一行,用不同颜色高亮显示
    QAbstract3DGraph::SelectionColumn选择并且高亮显示一列
    QAbstract3DGraph::SelectionItemAndColumn选择一个项和一列,用不同颜色高亮显示
    QAbstract3DGraph::SelectionRowAndColumn选择交叉的一行和一列
    QAbstract3DGraph::SelectionItemRowAndColumn选择交叉的一行和一列,用不同颜色高亮显示
    QAbstract3DGraph::SelectionSlice切片选择,需要与 SelectionRow 或 SelectionColumn 结合使用
    QAbstract3DGraph::SelectionMultiSeries选中同一个位置处的多个序列的项

    5 显示背景网格 CheckBox

    void mainwindow::on_chkBoxGrid_clicked(bool checked)
    { graph3D->activeTheme()->setGridEnabled(checked);
    }
    

    6 显示倒影 CheckBox

    void mainwindow::on_chkBoxReflection_clicked(bool checked)
    { graph3D->setReflection(checked);
    }
    

    7 数值坐标轴反向 CheckBox

    void mainwindow::on_chkBoxReverse_clicked(bool checked)
    { graph3D->valueAxis()->setReversed(checked);
    }
    

    8 显示轴标题 CheckBox

    void mainwindow::on_chkBoxAxisTitle_clicked(bool checked)
    { graph3D->valueAxis()->setTitleVisible(checked);
        graph3D->rowAxis()->setTitleVisible(checked);
        graph3D->columnAxis()->setTitleVisible(checked);
    }
    

    9 显示轴标签背景 CheckBox

    void mainwindow::on_chkBoxAxisBackground_clicked(bool checked)
    { graph3D->activeTheme()->setLabelBackgroundEnabled(checked);
    }
    

    (六) “序列设置”分组框功能

    1 棒柱样式 ComboBox

    void mainwindow::on_cBoxBarStyle_currentIndexChanged(int index)
    { QAbstract3DSeries::Mesh aMesh;
        aMesh = QAbstract3DSeries::Mesh(index + 1);  //0=MeshUserDefined
        series->setMesh(aMesh);
    }
    

    1-1 棒柱形状

    enum QAbstract3DSeries::Mesh

    并非所有样式都可用于所有可视化类型。

    常量注释
    QAbstract3DSeries::MeshUserDefined0用户定义网格,通过QAbstract3DSeries::userDefinedMesh属性设置
    QAbstract3DSeries::MeshBar1基本矩形条
    QAbstract3DSeries::MeshCube2基本多维数据集
    QAbstract3DSeries::MeshPyramid3四边形金字塔
    QAbstract3DSeries::MeshCone4基本圆锥体
    QAbstract3DSeries::MeshCylinder5基本气缸
    QAbstract3DSeries::MeshBevelBar6略微倾斜(圆形)的矩形钢筋
    QAbstract3DSeries::MeshBevelCube7略微倾斜(圆形)的立方体
    QAbstract3DSeries::MeshSphere8球体
    QAbstract3DSeries::MeshMinimal9最小的三维网格:一个三角锥
    QAbstract3DSeries::MeshArrow10向上的箭头
    QAbstract3DSeries::MeshPoint112D点

    2 光滑效果 CheckBox

    void mainwindow::on_chkBoxSmooth_clicked(bool checked)
    { series->setMeshSmooth(checked);
    }
    

    3 显示选中棒柱的标签 CheckBox

    void mainwindow::on_chkBoxItemLabel_clicked(bool checked)
    { series->setItemLabelFormat("value at (@rowLabel,@colLabel): %.1f");
            series->setItemLabelVisible(checked);
    }
    

    (七) 有关QBarDataProxy数据管理接口

    函数功能
    int addRow()添加一行数据,一行数据是 QBarDataRow 类型对象
    void insertRow()在某行之前插入一行数据
    void removeRows()从某行开始,移除指定行数的数据
    void setRow ()为某一行重新设置一行数据
    QBarDataRow *rowAt()返回某一行的数据
    void setItem()根据行号和列号,设置某个棒柱数据项
    QBarDataltem *itemAt()根据行号和列号,返回单个棒柱数据项
    void resetArray()若不带任何参数,就清除数据代理所有的数据和标签;若使用一个 QBarDataArray类型的对象作为输入参数,就重新设置棒柱数据数组
    int rowCount()返回数据代理的行数
    void setRowLabels()用一个字符串列表设置行坐标轴的标签
    StringList rowLabels()返回行坐标轴的所有标签
    void setColumnLabels()用一个字符串列表设置列坐标轴的标签
    QStringList columnLabels()返回列坐标轴的所有标签