数据可视化的定义有很多,像百度百科的定义是:数据可视化,是关于数据视觉表现形式的科学技术研究。其中,这种数据的视觉表现形式被定义为,一种以某种概要形式抽提出来的信息,包括相应信息单位的各种属性和变量。其实就是利用运用计算机图形学、图像、人机交互等技术,将采集或模拟的数据映射为可识别的图形、图像,说白了就是使用图像形式展示数据。
常用的数据可视化有柱状图、线状图、条形图、面积图、饼图、点图、仪表盘、走势图外,还有和弦图、圈饼图、金字塔、漏斗图、K线图、关系图、网络图、玫瑰图、帕累托图、数学公式图、预测曲线图、正态分布图、迷你图、行政地图、GIS地图等等。
可视化类型太多,此文不可能一一列出(后续有机会补充),本文使用Qt官方例子为引,描述如何使用QChart进行如下图的数据可视化:
qt官方例子可依据下图方式查找(安装qt的时候需要把exaples安装)。
以下代码基本照搬qt示例代码,也就是说此文深度有限,我们以学习为主,主要搞清楚QChart的使用即达到目的,如有需要深入解析QChart源码,我们另发文章分析。
以下内容来源官网并翻译,且括号为自写内容:
QChart是一个在QGaphicScene中可以显示的QGraphicsWidget。它管理不同类型系列和其他图表相关对象(如图例和轴)的图形表示。为了仅在布局中显示图表,可以使用便利类QChartView代替QChart(QChart实例化后,将其指针传入QChartView的构造函数)。另外,通过使用QPolarChart类,可以将折线,样条曲线,面积和散布序列表示为极坐标图。
而QChart属于QCharts图表模块下的一部分,QCharts介绍:
Qt图表模块提供了一组易于使用的图表组件。它使用Qt Graphics View Framework,因此可以轻松地将图表集成到现代用户界面中。Qt图表可用作QWidgets,QGraphicsWidget或QML类型。用户可以通过选择图表主题之一轻松创建令人印象深刻的图表。
1、既然属于QCharts模块,所以需要在pro文件中添加charts模块:
QT += core gui charts
2、上面介绍时也说了:
为了仅在布局中显示图表,可以使用便利类QChartView代替QChart(QChart实例化后,将其指针传入QChartView的构造函数)
所以想要显示QChart内容,QChartView构造时候传入QChart指针,如下所示:
QChartView *chartView;
QChart *chart = createLineChart();
chartView = new QChartView(chart); //线覆盖
createLineChart()
为下文函数。
查看qt源代码,可了解QChartView
继承于QGraphicsView
类,而QGraphicsView
又继承于QAbstractScrollArea
,最后QAbstractScrollArea
继承于QFrame
,这样说明QChartView
最终继承于基础控件类,即new后指定或不指定父对象就直接在ui中显示。
3、显示数据的传入,上面都说了QChart如何显示,下面说明其显示数据来源。
QChart
类的所有数据通过addSeries
接口传入,参数类型为QAbstractSeries
,QAbstractSeries
类从名字上来看就知道是一个抽象类,这里使用其多态特性,作为抽象类派生各种数据子类,如:
以上类下文需要使用,但到底其子类还有哪些,不多说,自个按F1查看帮助文档,如下图:
其中官方例子中(下文代码),所有数据来源m_dataTable,其定义为
DataTable m_dataTable;
追查其定义来源:
typedef QPair<QPointF, QString> Data;
typedef QList<Data> DataList;
typedef QList<DataList> DataTable;
具体数据解析显示部分自己查看下文理解。
QChart *ThemeWidget::createLineChart() const
{
QChart *chart = new QChart(); //创建图表
chart->setTitle("Line chart"); //设置图表名称
QString name("Series ");
int nameIndex = 0;
for (const DataList &list : m_dataTable)
{
QLineSeries *series = new QLineSeries(chart); //创建曲线(数据坐标类)
for (const Data &data : list)
series->append(data.first); //为曲线添加数据点
series->setName(name + QString::number(nameIndex++)); //设置曲线名字
chart->addSeries(series); //添加曲线
}
chart->createDefaultAxes(); //为坐标系添加轴,需要在所有曲线数据填入后再调用此函数
return chart;
}
注意,此处与2中唯一不同就是曲线数据类,此处使用QSplineSeries
。
QChart *ThemeWidget::createSplineChart() const
{
// spine chart
QChart *chart = new QChart();
chart->setTitle("Spline chart");
QString name("Series ");
int nameIndex = 0;
for (const DataList &list : m_dataTable)
{
QSplineSeries *series = new QSplineSeries(chart); //创建平滑曲线
for (const Data &data : list)
series->append(data.first);
series->setName(name + QString::number(nameIndex));
nameIndex++;
chart->addSeries(series);
}
chart->createDefaultAxes();
return chart;
}
注意,此处与2\3中唯一不同就是曲线数据类,此处使用QScatterSeries
。
QChart *ThemeWidget::createScatterChart() const
{
// scatter chart
QChart *chart = new QChart();
chart->setTitle("Scatter chart");
QString name("Series ");
int nameIndex = 0;
for (const DataList &list : m_dataTable)
{
QScatterSeries *series = new QScatterSeries(chart); //离散点曲线数据类
for (const Data &data : list)
series->append(data.first);
series->setName(name + QString::number(nameIndex));
nameIndex++;
chart->addSeries(series);
}
chart->createDefaultAxes();
return chart;
}
QChart *ThemeWidget::createAreaChart() const
{
QChart *chart = new QChart();
chart->setTitle("Area chart");
// The lower series initialized to zero values
QLineSeries *lowerSeries = 0;
QString name("Series ");
int nameIndex = 0;
for (int i(0); i < m_dataTable.count(); i++)
{
QLineSeries *upperSeries = new QLineSeries(chart);
for (int j(0); j < m_dataTable[i].count(); j++)
{
Data data = m_dataTable[i].at(j);
if (lowerSeries)
{
const QVector<QPointF>& points = lowerSeries->pointsVector();
upperSeries->append(QPointF(j, points[i].y() + data.first.y()));
}
else
{
upperSeries->append(QPointF(j, data.first.y()));
}
}
QAreaSeries *area = new QAreaSeries(upperSeries, lowerSeries);
area->setName(name + QString::number(nameIndex));
nameIndex++;
chart->addSeries(area);
chart->createDefaultAxes();
lowerSeries = upperSeries;
}
return chart;
}
QChart *ThemeWidget::createBarChart(int valueCount) const
{
Q_UNUSED(valueCount);
QChart *chart = new QChart();
chart->setTitle("Bar chart");
QStackedBarSeries *series = new QStackedBarSeries(chart);
for (int i(0); i < m_dataTable.count(); i++)
{
QBarSet *set = new QBarSet("Bar set " + QString::number(i));
for (const Data &data : m_dataTable[i])
*set << data.first.y();
series->append(set);
}
chart->addSeries(series);
chart->createDefaultAxes();
return chart;
}
QChart *ThemeWidget::createPieChart() const
{
QChart *chart = new QChart();
chart->setTitle("Pie chart");
qreal pieSize = 1.0 / m_dataTable.count();
for (int i = 0; i < m_dataTable.count(); i++)
{
QPieSeries *series = new QPieSeries(chart);
for (const Data &data : m_dataTable[i])
{
QPieSlice *slice = series->append(data.second, data.first.y());//扇型区域名、扇型数值(所有数值比值会自动分配)
if (data == m_dataTable[i].first())
{
slice->setLabelVisible(); //设置扇型信息可见
slice->setExploded(); //设置该项显示突出
}
}
qreal hPos = (pieSize / 2) + (i / (qreal) m_dataTable.count());
series->setPieSize(pieSize);
series->setHorizontalPosition(hPos);
series->setVerticalPosition(0.5);
chart->addSeries(series);
}
return chart;
}
此处我对示例代码做了一些修改,对每个扇型区域类添加了一个信号槽,如下代码所示:
QPieSeries *series = new QPieSeries(chart); //创建扇型
for (const Data &data : m_dataTable[i])
{
QPieSlice *slice = series->append(data.second, data.first.y()); //扇型区域名、扇型数值(所有数值比值会自动分配)
//关联其点击信号槽
connect(slice,&QPieSlice::clicked,this,[=](){
slice->setLabelVisible(!slice->isLabelVisible()); //设置扇型信息可见
slice->setExploded(!slice->isExploded()); //设置该项显示突出
});
}