例子位置:Examples\Qt-xxx\widgets\itemviews\chart
#ifndef PIEVIEW_H
#define PIEVIEW_H
#include
//! [0]
class PieView : public QAbstractItemView
{
Q_OBJECT
public:
PieView(QWidget *parent = nullptr);
//---------------------- 纯虚函数 ----------------------
/*!
* \brief 返回索引项在视图坐标系中的矩形
* \param index
* \return
*/
QRect visualRect(const QModelIndex &index) const override;
/*!
* \brief 如有必要,可滚动视图以确保索引处的项可见
* \param index
* \param hint
*/
void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override;
/*!
* \brief 返回项目在视图坐标点的模型索引
* \param point
* \return
*/
QModelIndex indexAt(const QPoint &point) const override;
protected slots:
/*!
* \brief 数据改变,刷新视图
* \param topLeft
* \param bottomRight
* \param roles
*/
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
const QVector<int> &roles = QVector<int>()) override;
/*!
* \brief 插入行时调用
* \param parent
* \param start
* \param end
*/
void rowsInserted(const QModelIndex &parent, int start, int end) override;
/*!
* \brief 准备删除行时被调用
* \param parent
* \param start
* \param end
*/
void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override;
protected:
bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) override;
/*!
* \brief 根据光标动作或者按键返回模型索引,提供导航功能
* \param cursorAction
* \param modifiers
* \return
*/
QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction,
Qt::KeyboardModifiers modifiers) override;
/// 视图水平偏移
int horizontalOffset() const override;
/// 视图垂直偏移
int verticalOffset() const override;
/// 隐藏索引
bool isIndexHidden(const QModelIndex &index) const override;
/*!
* \brief 按照SelectionFlags更新选中区域
* \param rect 视图选中矩形
* \param command 选中标志
*/
void setSelection(const QRect&rect, QItemSelectionModel::SelectionFlags command) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void scrollContentsBy(int dx, int dy) override;
/*!
* \brief 选中的视图区域
* \param selection
* \return
*/
QRegion visualRegionForSelection(const QItemSelection &selection) const override;
private:
QRect itemRect(const QModelIndex &item) const;
QRegion itemRegion(const QModelIndex &index) const;
int rows(const QModelIndex &index = QModelIndex()) const;
void updateGeometries() override;
///边框
int margin = 0;
///饼图总像素
int totalSize = 300;
///饼图大小
int pieSize = totalSize - 2 * margin;
///有效项
int validItems = 0;
///饼图单元数值统计
double totalValue = 0.0;
///选中的矩形边界
QRubberBand *rubberBand = nullptr;
///当前点
QPoint origin;
};
//! [0]
#endif // PIEVIEW_H
#include "pieview.h"
#include
PieView::PieView(QWidget *parent)
: QAbstractItemView(parent)
{
horizontalScrollBar()->setRange(0, 0);
verticalScrollBar()->setRange(0, 0);
}
void PieView::dataChanged(const QModelIndex &topLeft,
const QModelIndex &bottomRight,
const QVector<int> &roles)
{
QAbstractItemView::dataChanged(topLeft, bottomRight, roles);
if (!roles.contains(Qt::DisplayRole))
return;
validItems = 0;
totalValue = 0.0;
for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {
QModelIndex index = model()->index(row, 1, rootIndex());
double value = model()->data(index, Qt::DisplayRole).toDouble();
if (value > 0.0) {
totalValue += value;
validItems++;
}
}
viewport()->update();
}
bool PieView::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event)
{
if (index.column() == 0)
return QAbstractItemView::edit(index, trigger, event);
else
return false;
}
/*
Returns the item that covers the coordinate given in the view.
*/
QModelIndex PieView::indexAt(const QPoint &point) const
{
if (validItems == 0)
return QModelIndex();
// Transform the view coordinates into contents widget coordinates.
//转换视图坐标到窗体坐标
int wx = point.x() + horizontalScrollBar()->value();
int wy = point.y() + verticalScrollBar()->value();
if (wx < totalSize) {//窗体坐标在饼图部分
//圆心
double cx = wx - totalSize / 2;
double cy = totalSize / 2 - wy; // positive cy for items above the center
// Determine the distance from the center point of the pie chart.
double d = std::sqrt(std::pow(cx, 2) + std::pow(cy, 2));
//圆心或者圆外部
if (d == 0 || d > pieSize / 2)
return QModelIndex();
// Determine the angle of the point.
//当前点的角度
double angle = qRadiansToDegrees(std::atan2(cy, cx));
if (angle < 0)
angle = 360 + angle;
// Find the relevant slice of the pie.
// 查找当前点对应饼图的模型索引
double startAngle = 0.0;
for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {
QModelIndex index = model()->index(row, 1, rootIndex());
double value = model()->data(index).toDouble();
if (value > 0.0) {
//饼图角度范围
double sliceAngle = 360 * value / totalValue;
//找到当前点角度在饼图角度范围内,返回当前饼图模型索引
if (angle >= startAngle && angle < (startAngle + sliceAngle))
return model()->index(row, 1, rootIndex());
startAngle += sliceAngle;
}
}
} else {//窗体坐标在右边列表部分
//视图字体高度
double itemHeight = QFontMetrics(viewOptions().font).height();
//当前y坐标对应的列表项
int listItem = int((wy - margin) / itemHeight);
int validRow = 0;
for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {
QModelIndex index = model()->index(row, 1, rootIndex());
if (model()->data(index).toDouble() > 0.0) {
//找到当前项,返回对应的模型索引
if (listItem == validRow)
return model()->index(row, 0, rootIndex());
// Update the list index that corresponds to the next valid row.
++validRow;
}
}
}
return QModelIndex();
}
bool PieView::isIndexHidden(const QModelIndex & /*index*/) const
{
return false;
}
/*
Returns the rectangle of the item at position \a index in the
model. The rectangle is in contents coordinates.
*/
QRect PieView::itemRect(const QModelIndex &index) const
{
if (!index.isValid())
return QRect();
// Check whether the index's row is in the list of rows represented by slices.
// valueIndex 表示Quantity的索引(index.column() == 1)
QModelIndex valueIndex;
if (index.column() != 1)
valueIndex = model()->index(index.row(), 1, rootIndex());
else
valueIndex = index;
//Quantity<=0.0不显示
if (model()->data(valueIndex).toDouble() <= 0.0)
return QRect();
//统计当前索引对应的listItem数量
int listItem = 0;
for (int row = index.row()-1; row >= 0; --row) {
if (model()->data(model()->index(row, 1, rootIndex())).toDouble() > 0.0)
listItem++;
}
switch (index.column()) {
case 0: {//原始模型中的column==0,Label
const qreal itemHeight = QFontMetricsF(viewOptions().font).height();
return QRect(totalSize,
qRound(margin + listItem * itemHeight),
totalSize - margin, qRound(itemHeight));//列表矩形
}
case 1://原始模型中的column==1,Quantity.
return viewport()->rect();//paintEvent 视图区域
}
return QRect();
}
QRegion PieView::itemRegion(const QModelIndex &index) const
{
if (!index.isValid())
return QRegion();
//非Quantity区域
if (index.column() != 1)
return itemRect(index);
//Quantity<=0 不显示
if (model()->data(index).toDouble() <= 0.0)
return QRegion();
//Quantity区域,找到当前索引,返回饼图区域
double startAngle = 0.0;
for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {
QModelIndex sliceIndex = model()->index(row, 1, rootIndex());
double value = model()->data(sliceIndex).toDouble();
if (value > 0.0) {
//数值占的百分比角度
double angle = 360 * value / totalValue;
//找到当前索引,返回饼图区域
if (sliceIndex == index) {
//画饼图
QPainterPath slicePath;
slicePath.moveTo(totalSize / 2, totalSize / 2);
slicePath.arcTo(margin, margin, margin + pieSize, margin + pieSize,
startAngle, angle);
slicePath.closeSubpath();
return QRegion(slicePath.toFillPolygon().toPolygon());
}
//继续查找
startAngle += angle;
}
}
return QRegion();
}
int PieView::horizontalOffset() const
{
return horizontalScrollBar()->value();
}
void PieView::mousePressEvent(QMouseEvent *event)
{//显示选中
QAbstractItemView::mousePressEvent(event);
origin = event->pos();
if (!rubberBand)
rubberBand = new QRubberBand(QRubberBand::Rectangle, viewport());
rubberBand->setGeometry(QRect(origin, QSize()));
rubberBand->show();
}
void PieView::mouseMoveEvent(QMouseEvent *event)
{//显示选中的矩形
if (rubberBand)
rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
QAbstractItemView::mouseMoveEvent(event);
}
void PieView::mouseReleaseEvent(QMouseEvent *event)
{//隐藏选中矩形
QAbstractItemView::mouseReleaseEvent(event);
if (rubberBand)
rubberBand->hide();
viewport()->update();
}
QModelIndex PieView::moveCursor(QAbstractItemView::CursorAction cursorAction,
Qt::KeyboardModifiers /*modifiers*/)
{
QModelIndex current = currentIndex();
switch (cursorAction) {
case MoveLeft:
case MoveUp://上移
if (current.row() > 0)
current = model()->index(current.row() - 1, current.column(),
rootIndex());
else
current = model()->index(0, current.column(), rootIndex());
break;
case MoveRight:
case MoveDown://下移
if (current.row() < rows(current) - 1)
current = model()->index(current.row() + 1, current.column(),
rootIndex());
else
current = model()->index(rows(current) - 1, current.column(),
rootIndex());
break;
default:
break;
}
//paintEvent 视图区域
viewport()->update();
return current;
}
void PieView::paintEvent(QPaintEvent *event)
{
//QItemSelectionModel在一个视图中跟踪所选项目,或者在同一模型的多个视图中跟踪所选项目
QItemSelectionModel *selections = selectionModel();
//QStyleOptionViewItem类描述在视图中绘制项的参数
QStyleOptionViewItem option = viewOptions();
QBrush background = option.palette.base();
QPen foreground(option.palette.color(QPalette::WindowText));
QPainter painter(viewport());
painter.setRenderHint(QPainter::Antialiasing);
painter.fillRect(event->rect(), background);
painter.setPen(foreground);
// Viewport rectangles
QRect pieRect = QRect(margin, margin, pieSize, pieSize);
if (validItems <= 0)
return;
painter.save();
//转换到视图坐标
painter.translate(pieRect.x() - horizontalScrollBar()->value(),
pieRect.y() - verticalScrollBar()->value());
//绘制饼图圆
painter.drawEllipse(0, 0, pieSize, pieSize);
//------------------- 绘制饼图 -------------------
double startAngle = 0.0;
int row;
for (row = 0; row < model()->rowCount(rootIndex()); ++row) {
QModelIndex index = model()->index(row, 1, rootIndex());
double value = model()->data(index).toDouble();
if (value > 0.0) {
//当前模型项对应饼图的角度
double angle = 360 * value / totalValue;
//获取模型项颜色
QModelIndex colorIndex = model()->index(row, 0, rootIndex());
QColor color = QColor(model()->data(colorIndex, Qt::DecorationRole).toString());
//画刷风格
if (currentIndex() == index)
painter.setBrush(QBrush(color, Qt::Dense4Pattern));
else if (selections->isSelected(index))
painter.setBrush(QBrush(color, Qt::Dense3Pattern));
else
painter.setBrush(QBrush(color));
//绘制饼图
painter.drawPie(0, 0, pieSize, pieSize, int(startAngle*16), int(angle*16));
startAngle += angle;
}
}
painter.restore();
//------------------- 绘制列表 -------------------
int keyNumber = 0;
for (row = 0; row < model()->rowCount(rootIndex()); ++row) {
QModelIndex index = model()->index(row, 1, rootIndex());
double value = model()->data(index).toDouble();
if (value > 0.0) {
QModelIndex labelIndex = model()->index(row, 0, rootIndex());
QStyleOptionViewItem option = viewOptions();
option.rect = visualRect(labelIndex);
if (selections->isSelected(labelIndex))
option.state |= QStyle::State_Selected;
if (currentIndex() == labelIndex)
option.state |= QStyle::State_HasFocus;
itemDelegate()->paint(&painter, option, labelIndex);
++keyNumber;
}
}
}
void PieView::resizeEvent(QResizeEvent * /* event */)
{
updateGeometries();
}
int PieView::rows(const QModelIndex &index) const
{
return model()->rowCount(model()->parent(index));
}
void PieView::rowsInserted(const QModelIndex &parent, int start, int end)
{
for (int row = start; row <= end; ++row) {
QModelIndex index = model()->index(row, 1, rootIndex());
double value = model()->data(index).toDouble();
if (value > 0.0) {
totalValue += value;
++validItems;
}
}
QAbstractItemView::rowsInserted(parent, start, end);
}
void PieView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
for (int row = start; row <= end; ++row) {
QModelIndex index = model()->index(row, 1, rootIndex());
double value = model()->data(index).toDouble();
if (value > 0.0) {
totalValue -= value;
--validItems;
}
}
QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
}
void PieView::scrollContentsBy(int dx, int dy)
{
viewport()->scroll(dx, dy);
}
void PieView::scrollTo(const QModelIndex &index, ScrollHint)
{
//视图区域
QRect area = viewport()->rect();
//当前索引矩形区域
QRect rect = visualRect(index);
if (rect.left() < area.left()) {//左移
horizontalScrollBar()->setValue(
horizontalScrollBar()->value() + rect.left() - area.left());
} else if (rect.right() > area.right()) {//右移
horizontalScrollBar()->setValue(
horizontalScrollBar()->value() + qMin(
rect.right() - area.right(), rect.left() - area.left()));
}
if (rect.top() < area.top()) {
verticalScrollBar()->setValue(
verticalScrollBar()->value() + rect.top() - area.top());
} else if (rect.bottom() > area.bottom()) {
verticalScrollBar()->setValue(
verticalScrollBar()->value() + qMin(
rect.bottom() - area.bottom(), rect.top() - area.top()));
}
update();
}
/*
Find the indices corresponding to the extent of the selection.
*/
void PieView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{
// Use content widget coordinates because we will use the itemRegion()
// function to check for intersections.
//选中的窗体坐标矩形
QRect contentsRect = rect.translated(
horizontalScrollBar()->value(),
verticalScrollBar()->value()).normalized();
int rows = model()->rowCount(rootIndex());
int columns = model()->columnCount(rootIndex());
QModelIndexList indexes;
//遍历查找所有与当前选中矩形contentsRect相交的索引
for (int row = 0; row < rows; ++row) {
for (int column = 0; column < columns; ++column) {
QModelIndex index = model()->index(row, column, rootIndex());
QRegion region = itemRegion(index);
//找到区域与contentsRect相交
if (region.intersects(contentsRect))
indexes.append(index);
}
}
if (indexes.size() > 0) {
int firstRow = indexes.at(0).row();
int lastRow = firstRow;
int firstColumn = indexes.at(0).column();
int lastColumn = firstColumn;
//获取选中的范围
for (int i = 1; i < indexes.size(); ++i) {
firstRow = qMin(firstRow, indexes.at(i).row());
lastRow = qMax(lastRow, indexes.at(i).row());
firstColumn = qMin(firstColumn, indexes.at(i).column());
lastColumn = qMax(lastColumn, indexes.at(i).column());
}
//选中模型项
QItemSelection selection(
model()->index(firstRow, firstColumn, rootIndex()),
model()->index(lastRow, lastColumn, rootIndex()));
selectionModel()->select(selection, command);
} else {
QModelIndex noIndex;
QItemSelection selection(noIndex, noIndex);
selectionModel()->select(selection, command);
}
//更新视图
update();
}
void PieView::updateGeometries()
{
horizontalScrollBar()->setPageStep(viewport()->width());
horizontalScrollBar()->setRange(0, qMax(0, 2 * totalSize - viewport()->width()));
verticalScrollBar()->setPageStep(viewport()->height());
verticalScrollBar()->setRange(0, qMax(0, totalSize - viewport()->height()));
}
int PieView::verticalOffset() const
{
return verticalScrollBar()->value();
}
/*
Returns the position of the item in viewport coordinates.
*/
QRect PieView::visualRect(const QModelIndex &index) const
{
QRect rect = itemRect(index);
if (!rect.isValid())
return rect;
return QRect(rect.left() - horizontalScrollBar()->value(),
rect.top() - verticalScrollBar()->value(),
rect.width(), rect.height());
}
/*
Returns a region corresponding to the selection in viewport coordinates.
*/
QRegion PieView::visualRegionForSelection(const QItemSelection &selection) const
{
int ranges = selection.count();
if (ranges == 0)
return QRect();
QRegion region;
for (int i = 0; i < ranges; ++i) {
const QItemSelectionRange &range = selection.at(i);
for (int row = range.top(); row <= range.bottom(); ++row) {
for (int col = range.left(); col <= range.right(); ++col) {
QModelIndex index = model()->index(row, col, rootIndex());
region += visualRect(index);
}
}
}
return region;
}