#include "dpLineChart.h" #include "dataManager.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_showLegend = false; m_updateData = false; QBoxLayout* mainLayout = new QBoxLayout(QBoxLayout::LeftToRight); mainLayout->setContentsMargins(0, 1, 0, 0); mainLayout->addWidget(m_pCustomPlot); setLayout(mainLayout); connect(DataManager::instance(), &DataManager::dataUpdated, this, &dpLineChart::onSignal_dataUpdated); } 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(&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->yAxis->setTickLabels(false); //网格线颜色 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 timeTicker(new QCPAxisTickerDateTime); timeTicker->setDateTimeFormat("hh:mm:ss"); //qDebug() << timeTicker->dateTimeFormat(); m_pCustomPlot->xAxis->setTicker(timeTicker); //qDebug() << m_pCustomPlot->xAxis->range(); //Legend m_pCustomPlot->legend->setBrush(QBrush(QColor(255,255,255,12))); //背景透明 m_pCustomPlot->legend->setBorderPen(QPen(QColor(255,255,255,0))); //边框透明 m_pCustomPlot->legend->setFont(m_chartStyle.labelFont); m_pCustomPlot->legend->setTextColor( m_chartStyle.labelColor); //m_pCustomPlot->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignRight|Qt::AlignTop); } void dpLineChart::arrangeAxes() { QMap 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 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::reLayoutLegend() { /*if(!m_showLegend) return; //图例放在最右侧纵坐标轴的右侧 //1.找到最右侧坐标轴 QCPAxis* rightmostAxis = m_pCustomPlot->yAxis2; int offset = rightmostAxis->offset(); QList axes = m_pCustomPlot->axisRect()->axes(QCPAxis::atRight); for(QCPAxis* axis : axes) { if(axis->offset() > rightmostAxis->offset()) rightmostAxis = axis; } //2.设置图例位置 QCPLegend* legend = m_pCustomPlot->legend;*/ } void dpLineChart::setTimeRange(TimeUnit unit) { QSharedPointer ticker = m_pCustomPlot->xAxis->ticker(); QSharedPointer dateTicker = qSharedPointerCast(ticker); switch(unit) { case TU_Year: { m_timeRange = m_curDateTime.date().daysInYear() * 24 * 60 * 60 * (qint64)1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("dd/MM\nyyyy"); break; } case TU_Month: { m_timeRange = m_curDateTime.date().daysInMonth() * 24 * 60 * 60 * (qint64)1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("dd/MM\nyyyy"); break; } case TU_Day: { m_timeRange = 24 * 60 * 60 * 1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("dd/MM\nyyyy"); break; } case TU_Hour: { m_timeRange = 60 * 60 *1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm"); break; } case TU_Minute_30: { m_timeRange = 30 * 60 *1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm"); break; } case TU_Minute_20: { m_timeRange = 20 * 60 *1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm"); break; } case TU_Minute_15: { m_timeRange = 15 * 60 *1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm"); break; } case TU_Minute_10: { m_timeRange = 10 * 60 *1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm"); break; } case TU_Minute_5: { m_timeRange = 5 * 60 *1000; break; } case TU_Minute_3: { m_timeRange = 3 * 60 *1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm:ss"); break; } case TU_Minute_1: { m_timeRange = 60 * 1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm:ss"); break; } case TU_Second_30: { m_timeRange = 30 * 1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm:ss"); break; } case TU_Second_10: { m_timeRange = 10 * 1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm:ss"); break; } case TU_Second_1: { m_timeRange = 1 * 1000; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm:ss:zzz"); break; } case TU_MSecond_500: { m_timeRange = 500; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm:ss:zzz"); break; } case TU_MSecond_100: { m_timeRange = 100; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm:ss:zzz"); break; } case TU_MSecond_50: { m_timeRange = 50; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm:ss:zzz"); break; } case TU_MSecond_10: { m_timeRange = 10; if(!dateTicker.isNull()) dateTicker->setDateTimeFormat("hh:mm:ss:zzz"); break; } default: break; } } void dpLineChart::setDateTime(const QDateTime& dateTime) { qint64 timeValue = dateTime.toMSecsSinceEpoch() / 1000.0; //qint64 timeValue = QCPAxisTickerDateTime::dateTimeToKey(dateTime); m_curDateTime = dateTime; if(m_updateData) { //模拟数据展示 /*static double min = 0, max = 10.0, marginFactor = 1.0; for(auto it = m_graphs.begin(); it != m_graphs.end(); ++it) { double randomFloat = min + QRandomGenerator::global()->generateDouble() * (max - min); //调整所在轴的范围 QCPRange range = it.value().qGraph->valueAxis()->range(); if(randomFloat > range.upper) { double upper = randomFloat + marginFactor; it.value().qGraph->valueAxis()->setRangeUpper(upper); } else if(randomFloat < range.lower) { double lower = randomFloat - marginFactor; it.value().qGraph->valueAxis()->setRangeLower(lower); } it.value().qGraph->addData(timeValue, randomFloat); }*/ for(auto it = m_graphs.begin(); it != m_graphs.end(); ++it) DataManager::instance()->requestData(it.key(), this); } m_pCustomPlot->xAxis->setRange(timeValue, m_timeRange / 1000.0, Qt::AlignRight); m_pCustomPlot->replot(); } void dpLineChart::viewHistoricalData(const QDateTime& dateTime) { } void dpLineChart::synchronizeConfigData(const configurationResults& cfg) { m_updateData = false; //停止更新数据 //m_pCustomPlot->yAxis->setTickLabels(true); //1.Y坐标轴-数量由数据类型决定 //将最新配置信息中的坐标轴相关数据存储在QHash中,有助于更好的判断当前坐标轴是否需要发生同步更新 QHash axisCfgMap; 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 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 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)) //更新配置 { AxisConfig axisCfg = axisCfgMap.value(dataType); axesMap.value(dataType)->applyConfig(axisCfg); } 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.axisArrangement; 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"; graph.name = cfg.m_pModel_dataSource->item(i, 0)->text(); QCPGraph* newGraph = m_pCustomPlot->addGraph(m_pCustomPlot->xAxis, valueAxis); if(newGraph) { newGraph->setName(graph.name); if(colorData.isValid()) { QColor color = colorData.value(); newGraph->setPen(QPen(color)); } graph.qGraph = newGraph; m_graphs.insert(graphID, graph); //实时模拟数据 DataManager::instance()->registerDataSource(graphID, [](){ static double min = 0, max = 10.0; double randomFloat = min + QRandomGenerator::global()->generateDouble() * (max - min); return QVariant::fromValue(randomFloat); }); } } 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(); 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(); //Legend m_pCustomPlot->legend->setVisible(cfg.showLegend); 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; } void dpLineChart::onSignal_dataUpdated(const QString& dataID, const QVariant& data, const QDateTime& timestamp) { auto it = m_graphs.find(dataID); if(it != m_graphs.end()) { static double marginFactor = 1.0; double dData = data.toDouble(); //调整所在轴的范围 QCPRange range = it.value().qGraph->valueAxis()->range(); if(dData > range.upper) { double upper = dData + marginFactor; it.value().qGraph->valueAxis()->setRangeUpper(upper); } else if(dData < range.lower) { double lower = dData - marginFactor; it.value().qGraph->valueAxis()->setRangeLower(lower); } qint64 timeValue = timestamp.toMSecsSinceEpoch() / 1000.0; it.value().qGraph->addData(timeValue, dData); } }