PowerMaster/dataPanel/dpLineChart.cpp

465 lines
17 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "dpLineChart.h"
//#define useDefaultAxis_Y //默认轴不可删除、不可左右移动所以做动态管理尤其是动态排列时要考虑的比较复杂可以采用不使用的策略全部以自定义轴替代默认轴yAxis只在初始化和没有实际数据轴自定义时显示用来做外观展示
dpLineChart::dpLineChart(QWidget* parent)
:dpBaseChart(parent)
{
setAttribute(Qt::WA_TranslucentBackground,true);
m_pCustomPlot = new QCustomPlot(this);
initQCP();
m_timeRange = 60 * 1000;
m_axisArrangementMode = AlternateSides;
m_updateData = false;
QBoxLayout* mainLayout = new QBoxLayout(QBoxLayout::LeftToRight);
mainLayout->setContentsMargins(0, 1, 0, 0);
mainLayout->addWidget(m_pCustomPlot);
setLayout(mainLayout);
}
dpLineChart::~dpLineChart()
{
}
void dpLineChart::initQCP()
{
m_chartStyle.bgColor = Qt::transparent;
m_chartStyle.axisColor = QColor(87, 100, 120);
m_chartStyle.labelColor = QColor(250, 250, 250);
m_chartStyle.labelFont = QFont("黑体", 12);
m_chartStyle.tickColor = QColor(87, 100, 120);
m_chartStyle.tickLabelColor = QColor(250, 250, 250);
m_chartStyle.tickLabelFont = QFont("黑体", 12);
m_chartStyle.gridPen = QPen(QColor(87, 100, 120), 1, Qt::DotLine);
//m_pCustomPlot->axisRect()->setupFullAxesBox();
m_pCustomPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
m_pCustomPlot->axisRect()->setRangeDrag(Qt::Horizontal); //只允许x轴方向的拖拽
double zoomFactor = m_pCustomPlot->axisRect()->rangeZoomFactor(Qt::Horizontal);
m_pCustomPlot->axisRect()->setRangeZoomFactor(zoomFactor, 1); //只做x轴的缩放
m_pCustomPlot->xAxis->setSubTicks(false);
m_pCustomPlot->xAxis2->setTicks(false);
m_pCustomPlot->xAxis2->setSubTicks(false);
m_pCustomPlot->yAxis->setSubTicks(false);
//m_pCustomPlot->yAxis->setVisible(false);
//m_pCustomPlot->yAxis2->setTicks(false);
m_pCustomPlot->yAxis2->setSubTicks(false);
m_pCustomPlot->yAxis2->setVisible(false);
connect(m_pCustomPlot->xAxis, qOverload<const QCPRange &>(&QCPAxis::rangeChanged), //rangeChanged有两个版本qOverload可以指定版本
this, &dpLineChart::onSignal_rangeChanged_xAxis);
//背景颜色
m_pCustomPlot->setBackground(QBrush(m_chartStyle.bgColor));
//坐标轴颜色
m_pCustomPlot->xAxis->setBasePen(m_chartStyle.axisColor);
m_pCustomPlot->xAxis2->setBasePen(m_chartStyle.axisColor);
m_pCustomPlot->yAxis->setBasePen(m_chartStyle.axisColor);
m_pCustomPlot->yAxis2->setBasePen(m_chartStyle.axisColor);
//坐标刻度颜色
m_pCustomPlot->xAxis->setTickPen(QPen(m_chartStyle.tickColor));
m_pCustomPlot->yAxis->setTickPen(QPen(m_chartStyle.tickColor));
m_pCustomPlot->yAxis2->setTickPen(QPen(m_chartStyle.tickColor));
//坐标刻度Label颜色
m_pCustomPlot->xAxis->setTickLabelColor(m_chartStyle.tickLabelColor);
m_pCustomPlot->xAxis->setTickLabelFont(m_chartStyle.tickLabelFont);
m_pCustomPlot->yAxis->setTickLabelColor(m_chartStyle.tickLabelColor);
m_pCustomPlot->yAxis->setTickLabelFont(m_chartStyle.tickLabelFont);
m_pCustomPlot->yAxis2->setTickLabelColor(m_chartStyle.tickLabelColor);
m_pCustomPlot->yAxis2->setTickLabelFont(m_chartStyle.tickLabelFont);
//网格线颜色
m_pCustomPlot->xAxis->grid()->setPen(m_chartStyle.gridPen);
m_pCustomPlot->xAxis->grid()->setZeroLinePen(m_chartStyle.gridPen);
//m_pCustomPlot->xAxis2->grid()->setPen(m_chartStyle.gridPen);
m_pCustomPlot->yAxis->grid()->setPen(m_chartStyle.gridPen);
m_pCustomPlot->yAxis->grid()->setZeroLinePen(m_chartStyle.gridPen);
m_pCustomPlot->yAxis2->grid()->setPen(m_chartStyle.gridPen);
//x轴用时间格式
QSharedPointer<QCPAxisTickerDateTime> timeTicker(new QCPAxisTickerDateTime);
timeTicker->setDateTimeFormat("hh:mm:ss:zzz\nyyyy/MM/dd");
//qDebug() << timeTicker->dateTimeFormat();
m_pCustomPlot->xAxis->setTicker(timeTicker);
//qDebug() << m_pCustomPlot->xAxis->range();
}
void dpLineChart::arrangeAxes()
{
QMap<QCPAxis::AxisType, int> axisTypeCounter;
for(int i = 0; i < m_axes.size(); i++)
{
Axis& axis = m_axes[i];
QCPAxis::AxisType position = axis.qAxis->axisType();
if(m_axisArrangementMode == AlternateSides)
position = (i % 2 == 0) ? QCPAxis::atLeft : QCPAxis::atRight;
else if(m_axisArrangementMode == AllRight)
{
if(i == 0)
position = QCPAxis::atLeft;
else
position = QCPAxis::atRight;
}
//更新位置因为QCPAxis没有更新位置的结构只能采用从plot中移除再添加的方式
if(axis.qAxis->axisType() != position)
{
//因为要进行轴的移除再添加工作所以其上相关的graph也要同步处理
QVector<QCPGraph*> affectedGraphs;
for(int j = 0; j < m_pCustomPlot->graphCount(); j++)
{
QCPGraph* graph = m_pCustomPlot->graph(j);
if(graph->valueAxis() == axis.qAxis)
affectedGraphs.append(graph);
}
bool bRemoved = m_pCustomPlot->axisRect()->removeAxis(axis.qAxis);
if(bRemoved) //removeAxis执行成功被删除的axis会被delete
{
QCPAxis* qcpAxis = m_pCustomPlot->axisRect()->addAxis(position);
axis.setQCPAxis(qcpAxis, false);
for(QCPGraph* graph : affectedGraphs)
graph->setValueAxis(qcpAxis);
}
}
//计算偏移量
int offset = axisTypeCounter.value(position, 0) * 30;
axis.qAxis->setOffset(offset);
axisTypeCounter[position] = axisTypeCounter.value(position, 0) + 1;
}
}
void dpLineChart::setTimeRange(TimeUnit unit)
{
switch(unit)
{
case TU_Year:
m_timeRange = m_curDateTime.date().daysInYear() * 24 * 60 * 60 * (qint64)1000;
break;
case TU_Month:
m_timeRange = m_curDateTime.date().daysInMonth() * 24 * 60 * 60 * (qint64)1000;
break;
case TU_Day:
m_timeRange = 24 * 60 * 60 * 1000;
break;
case TU_Hour:
m_timeRange = 60 * 60 *1000;
break;
case TU_Minute_30:
m_timeRange = 30 * 60 *1000;
break;
case TU_Minute_20:
m_timeRange = 20 * 60 *1000;
break;
case TU_Minute_15:
m_timeRange = 15 * 60 *1000;
break;
case TU_Minute_10:
m_timeRange = 10 * 60 *1000;
break;
case TU_Minute_5:
m_timeRange = 5 * 60 *1000;
break;
case TU_Minute_3:
m_timeRange = 3 * 60 *1000;
break;
case TU_Minute_1:
m_timeRange = 60 * 1000;
break;
case TU_Second_30:
m_timeRange = 30 * 1000;
break;
case TU_Second_10:
m_timeRange = 10 * 1000;
break;
case TU_Second_1:
m_timeRange = 1 * 1000;
break;
case TU_MSecond_500:
m_timeRange = 500;
break;
case TU_MSecond_100:
m_timeRange = 100;
break;
case TU_MSecond_50:
m_timeRange = 50;
break;
case TU_MSecond_10:
m_timeRange = 10;
break;
default:
break;
}
}
void dpLineChart::setDateTime(const QDateTime& dateTime)
{
qint64 timeValue = dateTime.toMSecsSinceEpoch() / 1000.0;
//qint64 timeValue = QCPAxisTickerDateTime::dateTimeToKey(dateTime);
m_pCustomPlot->xAxis->setRange(timeValue, m_timeRange / 1000.0, Qt::AlignRight);
m_pCustomPlot->replot();
m_curDateTime = dateTime;
if(m_updateData)
{
//模拟数据展示
double min = 0, max = 5.0;
for(auto it = m_graphs.begin(); it != m_graphs.end(); ++it)
{
double randomFloat = min + QRandomGenerator::global()->generateDouble() * (max - min);
it.value().qGraph->addData(timeValue, randomFloat);
}
}
}
void dpLineChart::viewHistoricalData(const QDateTime& dateTime)
{
}
void dpLineChart::synchronizeConfigData(const configurationResults& cfg)
{
m_updateData = false; //停止更新数据
//1.Y坐标轴-数量由数据类型决定
//将最新配置信息中的坐标轴相关数据存储在QHash中有助于更好的判断当前坐标轴是否需要发生同步更新
QHash<RealTimeDataType, AxisConfig> axisCfgMap;
/*for(int i = 0; i< cfg.m_pModel_dataType->rowCount(); i++)
{
AxisConfig axisConfig;
RealTimeDataType dataType = (RealTimeDataType)cfg.m_pModel_dataType->item(i, 0)->data(Qt::UserRole + itemRole_dataType).toInt();
axisConfig.dataType = dataType;
axisCfgMap.insert(dataType, axisConfig);
}*/
for(auto it = cfg.axisCfgMap.begin(); it != cfg.axisCfgMap.end(); ++it)
{
AxisConfig axisConfig;
axisConfig.dataType = it.key();
axisConfig.name = it.value().name;
axisConfig.unit = it.value().unit;
axisCfgMap.insert(it.key(), axisConfig);
}
//删除轴
for(int i = 0; i < m_axes.size();)
{
#ifdef useDefaultAxis_Y
Axis& axis = m_axes[i];
if(!axisCfgMap.contains(axis._cfg.dataType))
{
if(axis.qAxis != m_pCustomPlot->yAxis && axis.qAxis != m_pCustomPlot->yAxis2)
m_pCustomPlot->axisRect()->removeAxis(axis.qAxis);
else
axis.qAxis->setVisible(false);
m_axes.remove(i);
}
else
i++;
#else
Axis& axis = m_axes[i];
if(!axisCfgMap.contains(axis._cfg.dataType))
{
//先删除轴上的Graph(注意先后顺序QCustomPlot的removeGraph会析构graph所以先做m_graphs的删除或者改为QPointer智能指针可规避此问题)
// for(auto it = m_graphs.begin(); it != m_graphs.end();)
// {
// if(it.value().qGraph && it.value().qGraph->valueAxis() == axis.qAxis)
// it = m_graphs.erase(it); //使用erase方法可以更新迭代器从而避免迭代器失效
// else
// ++it;
// }
for(int j = 0; j < m_pCustomPlot->graphCount();)
{
QCPGraph* graph = m_pCustomPlot->graph(j);
if(graph->valueAxis() == axis.qAxis)
{
m_pCustomPlot->removeGraph(graph); //removeGraph时graph会被delete
}
else
++j;
}
QMutableHashIterator<QString, Graph> it(m_graphs); //使用QMutableHashIterator可以再遍历相关容器的同时安全的修改例如删除元素
while (it.hasNext())
{
it.next();
if (it.value().qGraph.isNull()/*it.value().qGraph->valueAxis() == axis.qAxis*/) //利用QPointer特性保持数据同步
{
//m_pCustomPlot->removeGraph(it.value().qGraph);
it.remove(); // 直接删除当前项,无需手动管理迭代器
}
}
//删除轴
m_pCustomPlot->axisRect()->removeAxis(axis.qAxis);
m_axes.remove(i);
}
else
++i;
#endif
}
if(m_axes.isEmpty()) //若所有轴(数据类型)全部清楚,将默认左轴xAxis显示出来
m_pCustomPlot->xAxis->setVisible(true);
//处理应用配置(新增或更新)
QHash<RealTimeDataType, Axis*> axesMap; //创建map类型变量,方便后续使用.QHash::insert()的第二个参数接收的是副本(引起拷贝),所以采用指针可以减少拷贝从而提升效率
for(auto& axis : m_axes)
{
axesMap.insert(axis._cfg.dataType, &axis);
}
for(int i = 0; i < cfg.m_pModel_dataType->rowCount(); i++) //能保证和选取顺序一致
//for(auto it = axisCfgMap.begin(); it != axisCfgMap.end(); ++it)
{
RealTimeDataType dataType = (RealTimeDataType)cfg.m_pModel_dataType->item(i, 0)->data(Qt::UserRole + itemRole_dataType).toInt();
//RealTimeDataType dataType = it.key();
if(axesMap.contains(dataType)) //更新配置
{
}
else //新增轴
{
#ifdef useDefaultAxis_Y
AxisConfig cfg = axisCfgMap.value(dataType);
Axis axis;
if(m_axes.isEmpty()) //使用默认轴yAxis
{
axis.setQCPAxis(m_pCustomPlot->yAxis, true);
}
else if(m_axes.count() == 1 && m_axes.at(0).qAxis == m_pCustomPlot->yAxis) //默认轴yAxis被使用使用yAxis2
{
axis.setQCPAxis(m_pCustomPlot->yAxis2, true);
m_pCustomPlot->yAxis2->setVisible(false);
}
else
{
QCPAxis* pAxis = new QCPAxis(m_pCustomPlot->axisRect(), QCPAxis::atRight);
axis.setQCPAxis(pAxis, false);
axis.setStyle(m_chartStyle);
}
axis.applyConfig(cfg);
m_axes.append(axis);
#else
AxisConfig axisCfg = axisCfgMap.value(dataType);
Axis axis;
axis.applyConfig(axisCfg);
axis.setStyle(m_chartStyle);
if(m_axes.isEmpty())
{
//隐藏默认左轴
m_pCustomPlot->yAxis->setVisible(false);
//创建自定义轴代替
//QCPAxis* pAxis = new QCPAxis(m_pCustomPlot->axisRect(), QCPAxis::atLeft);
QCPAxis* pAxis = m_pCustomPlot->axisRect()->addAxis(QCPAxis::atLeft);
if(!pAxis)
continue;
//qDebug() << "add axis on:" << QCPAxis::atLeft << ", name: " << axisCfg.name;
axis.setQCPAxis(pAxis, false);
}
else
{
//默认放在右边
//QCPAxis* pAxis = new QCPAxis(m_pCustomPlot->axisRect(), QCPAxis::atRight);
QCPAxis* pAxis = m_pCustomPlot->axisRect()->addAxis(QCPAxis::atRight);
if(!pAxis)
continue;
axis.setQCPAxis(pAxis, false);
}
m_axes.append(axis);
//axesMap.insert(axis._cfg.dataType, &m_axes.last()); //临时变量axesMap通过做更新,此处不要直接使用&axis因为axis是局部变量地址会被自动释放造成堆错误
#endif
}
}
//更新axesMap
axesMap.clear();
for(auto& axis : m_axes)
{
axesMap.insert(axis._cfg.dataType, &axis);
}
//重新排列坐标轴
m_axisArrangementMode = cfg.arrangement;
arrangeAxes();
//2.曲线-数据源决定
//qDebug() << "m_graphs before update: " << m_graphs.keys();
for(int i = 0; i < cfg.m_pModel_dataSource->rowCount(); i++)
{
QString stationID = cfg.m_pModel_dataSource->item(i, 0)->data(Qt::UserRole + itemRole_stationID).toString();
QString compoentID = cfg.m_pModel_dataSource->item(i, 0)->data(Qt::UserRole + itemRole_componentID).toString();
QString pointID = cfg.m_pModel_dataSource->item(i, 0)->data(Qt::UserRole + itemRole_pointID).toString();
QString graphID = stationID + "-" + compoentID + "-" + pointID;
//qDebug() << "update:" << graphID;
QVariant colorData = cfg.m_pModel_dataSource->item(i, 0)->data(Qt::DecorationRole);
if(!m_graphs.contains(graphID)) //新增数据
{
RealTimeDataType dataType = (RealTimeDataType)cfg.m_pModel_dataSource->item(i, 0)->data(Qt::UserRole + itemRole_dataType).toInt();
QCPAxis* valueAxis = axesMap.value(dataType)->qAxis;
if(valueAxis == nullptr)
continue;
Graph graph;
graph.dataID = graphID;
graph.dataType = dataType;
graph.synchronizeTagging = "new";
QCPGraph* newGraph = m_pCustomPlot->addGraph(m_pCustomPlot->xAxis, valueAxis);
if(newGraph)
{
if(colorData.isValid())
{
QColor color = colorData.value<QColor>();
newGraph->setPen(QPen(color));
}
graph.qGraph = newGraph;
m_graphs.insert(graphID, graph);
}
}
else //更新数据
{
//m_graphs.value(graphID).synchronizeTagging = "update";
auto it = m_graphs.find(graphID);
if(it != m_graphs.end())
{
if(colorData.isValid())
{
QColor color = colorData.value<QColor>();
it.value().qGraph->setPen(QPen(color));
}
it.value().synchronizeTagging = "update";
}
}
}
//qDebug() << "m_graphs after update: " << m_graphs.keys();
//没有标记的就是需要删除的
QStringList keysToRemove;
for(auto it = m_graphs.begin(); it != m_graphs.end(); ++it)
{
if(it.value().synchronizeTagging =="noTagging") //删除
keysToRemove.append(it.key());
else
it.value().synchronizeTagging = "noTagging";
}
foreach (const QString& key, keysToRemove)
{
Graph g = m_graphs.take(key);
if( !m_pCustomPlot->removeGraph(g.qGraph) )
qWarning() << "Failed to remove gracloxne ph:" << key;
}
//qDebug() << "graph count: " << m_graphs.count() << ", " << m_pCustomPlot->graphCount();
m_updateData = true;
}
void dpLineChart::onSignal_rangeChanged_xAxis(const QCPRange& range)
{
// qDebug() << "m_timeRange: " << m_timeRange;
// qDebug() << "range size: " << range.size();
if(m_timeRange != range.size() * 1000)
m_timeRange = range.size() * 1000;
}