#include "visualizationchart.h" #include #include #include #include #include // https://russianblogs.com/article/5807234373/ double calculateDistance(std::pair &x, std::pair &y) { return std::sqrt(std::pow(x.first - y.first, 2) + std::pow(x.second - y.second, 2)); } double calculateDistance(std::pair &x, std::pair &y) { return std::sqrt(std::pow(x.first - y.first, 2) + std::pow(x.second - y.second, 2)); } double distanceBetweenTwoPointsOnOneAxis(int x1, int x2) { vector> vec = { {x1, 0} , {x2, 0}, }; double dist = calculateDistance(vec[0], vec[1]); return dist; } VisualizationChart::VisualizationChart() : QChart(), mQue(1,1) { initChart(); } VisualizationChart::VisualizationChart(int w, int h, bool keepXGrowToTheRight, bool keepYFullRange) : QChart(), mW(w), mH(h), mMakeXaxisLengtGrowToTheRightAccordingIncommingXdata(keepXGrowToTheRight), mMakeYaxisLengtAutoResizeAccordingIncommingYdata(!keepYFullRange), mQue(1,1) { initChart(); } VisualizationChart::VisualizationChart(int w, int h, int minY, int maxY, bool keepXGrowToTheRight, bool keepYFullRange) : QChart(), mW(w), mH(h), mMinY(minY), mMaxY(maxY), mMakeXaxisLengtGrowToTheRightAccordingIncommingXdata(keepXGrowToTheRight), mMakeYaxisLengtAutoResizeAccordingIncommingYdata(!keepYFullRange), mQue(1,1) { initChart(); } void VisualizationChart::initChart() { axisX = new QValueAxis(); axisY = new QValueAxis(); //axisX->setTickType(QValueAxis::TickType) QPen penNoLine(Qt::lightGray, 1, Qt::NoPen, Qt::FlatCap); QPen penLine(Qt::lightGray, 1, Qt::SolidLine, Qt::FlatCap); QPen penDots(Qt::lightGray, 1, Qt::DotLine, Qt::FlatCap); axisX->setLinePen(penLine); //axisX->setShadesPen(penDots); axisX->setGridLinePen(penNoLine); axisY->setLinePen(penNoLine); //axisX->setShadesPen(penDots); axisY->setGridLinePen(penDots); //axisX->setTickCount(1); axisX->setTickCount(1); axisX->setTickInterval(30); //axisX->setRange(xmin, xmax); axisY->setRange(mMinY - mQue.offset(), mMaxY + mQue.offset()); //axisY->setRange(mMinY, mMaxY); //axisY->setRange(ymin, ymax); mRangeY = static_cast(distanceBetweenTwoPointsOnOneAxis(mMinY, mMaxY)); mQue.setSize(mRangeY); // legend this->legend()->hide(); this->addAxis(axisX, Qt::AlignBottom); // X this->addAxis(axisY, Qt::AlignRight); // Y // Qt::AlignLeft // Vertial line that follows maouse cursor QFont fontXYTitle(this->legend()->font()); fontXYTitle.setPixelSize(18); m_lineVert = new QGraphicsLineItem(this); QPen pen1(Qt::lightGray, 1, Qt::SolidLine, Qt::FlatCap); m_lineVert->setPen(pen1); m_lineVertText = new QGraphicsTextItem(this); m_lineVertText->setPlainText(""); m_lineVertText->setDefaultTextColor(Qt::black); m_lineVertText->setFont(fontXYTitle); /* if(mEnableOurCustomAxesOrDeafultAxesInstead) { axisX->hide(); axisY->hide(); // < - 981 -> // V 441 ^ // // int h = this->plotArea().height(); // int w = this->plotArea().width(); // int w = parent()-(); // int h = parent()->height(); { // <- ^ QGraphicsRectItem *rect = new QGraphicsRectItem(this); QPen penR(Qt::magenta, 1, Qt::SolidLine, Qt::FlatCap); rect->setPen(penR); rect->setRect(0, 0, 10, 10); } { // <- V QGraphicsRectItem *rect = new QGraphicsRectItem(this); QPen penR(Qt::magenta, 1, Qt::SolidLine, Qt::FlatCap); rect->setPen(penR); rect->setRect(0, 430, 10, 10); } { // -> ^ QGraphicsRectItem *rect = new QGraphicsRectItem(this); QPen penR(Qt::magenta, 1, Qt::SolidLine, Qt::FlatCap); rect->setPen(penR); rect->setRect(910, 0, 10, 10); } // m_coordX = new QGraphicsSimpleTextItem(this); m_coordX->setPos(this->size().width() / 2 - 50, this->size().height()); // m_coordX->setText("X: "); m_coordY = new QGraphicsSimpleTextItem(this); m_coordY->setPos(this->size().width() / 2 + 50, this->size().height()); // m_coordY->setText("Y: "); */ mChartYTitle = new QGraphicsTextItem(this); mChartYTitle->setPlainText("Параметр, П"); //("Температура, °C"); mChartYTitle->setFont(fontXYTitle); mChartYTitle->setDefaultTextColor(Qt::lightGray); //mChartYTitle->setX(50); // size().width() mChartYTitle->setX(mW * 0.040 );// / 20); //mChartYTitle->setY(10); // size().height() mChartYTitle->setY(mH * 0.01 ); // / 40); mChartXTitle = new QGraphicsTextItem(this); mChartXTitle->setPlainText("Время, с"); mChartXTitle->setFont(mChartYTitle->font()); mChartXTitle->setDefaultTextColor(Qt::lightGray); //mChartXTitle->setX(880 - 50); // size().width() mChartXTitle->setX(mW * 0.85); //mChartXTitle->setY(410); // size().height() mChartXTitle->setY(mH * 0.93); /* // m_lineHoriz1->setLine(m_coordX->pos().x(), m_coordX->pos().y() + yStep, size().width(), m_coordX->pos().y() + // yStep); int hundred100= mH+10; double onepercent = (double)hundred100 / 100.0; //mRangeY; //6; int step = 0; int a = mRangeY; if((a>0)&&(a<=10)) { step = 1; } else if((a>10)&&(a<=50)) { step = 5; } else if((a>50)&&(a<=100)) { step = 10; } else if((a>100)&&(a<=250)) { step = 25; } else if((a>250)&&(a<=500)) { step = 50; } else if((a>500)&&(a<=1000)) { step = 100; } else if((a>1000)&&(a<=5000)) { step = 500; } else if((a>5000)&&(10000<=a)) { step = 1000; } int horizLines = mRangeY / step; int yStep = step; //mRangeY / horizLines ; //10; int xStart = 45; int yTop = hundred100 * 0.130434; //60; int xEnd = xStart + 880; int yBottom = hundred100 * 1.0; //460; int yLen = yBottom - yTop; int xLen = xEnd - xStart; int yPartsLen = yLen / horizLines; //int yPartsLen = yLen / ; int xPartsLen = xLen / horizLines; int iPart = 0; int xhTextEndOffset = hundred100 * 0.04347826; //20; int yhTextTopOffset = hundred100 * 0.0652173913; //30; // Dot Y lines with numbers on right side QPen penDots(Qt::lightGray, 1, Qt::DotLine, Qt::FlatCap); QPen penLast(Qt::lightGray, 1, Qt::SolidLine, Qt::FlatCap); mlinesHoriz.clear(); for(int i = 0, ilen = horizLines; isetPen(penDots); //->setFont(fontXYTitle); if(i == (ilen - 1)) mlinesHoriz[i]->setPen(penLast); //->setFont(fontXYTitle); } for(int iPart = 0, ilen = horizLines; iPartsetLine(xStart, yTop + (iPart * yPartsLen), xEnd, yTop + (iPart * yPartsLen)); //mlinesHoriz.push_back(new QGraphicsTextItem(this)); } mTextsHoriz.clear(); // Text near Lines for(int i = 0, ilen = horizLines; isetPlainText(""); else { number -= yStep; mTextsHoriz[i]->setPlainText(QString::number(number)); } } for(int i = 0, ilen = mTextsHoriz.size(); isetDefaultTextColor(Qt::lightGray); } for(int i = 0, ilen = mTextsHoriz.size(); isetFont(fontXYTitle); } for(int iPart = 0, ilen = mTextsHoriz.size(); iPartsetFont(fontXYTitle); mTextsHoriz[iPart]->setPos(xEnd - xhTextEndOffset, yTop + (iPart * yPartsLen) - yhTextTopOffset); } // Time textes on X Axis QPen penSolid(Qt::lightGray, 1, Qt::SolidLine, Qt::FlatCap); m_lineVert1 = new QGraphicsLineItem(this); m_lineVert2 = new QGraphicsLineItem(this); m_lineVert3 = new QGraphicsLineItem(this); m_lineVert4 = new QGraphicsLineItem(this); m_lineVert5 = new QGraphicsLineItem(this); m_lineVert6 = new QGraphicsLineItem(this); m_lineVert1->setPen(penSolid); m_lineVert2->setPen(penSolid); m_lineVert3->setPen(penSolid); m_lineVert4->setPen(penSolid); m_lineVert5->setPen(penSolid); m_lineVert6->setPen(penSolid); iPart = 0; m_lineVert1->setLine(xStart + (iPart * xPartsLen), yBottom - yPartsLen - 4, xStart + (iPart * xPartsLen), yBottom - yPartsLen + 30); ++iPart; m_lineVert2->setLine(xStart + (iPart * xPartsLen), yBottom - yPartsLen - 4, xStart + (iPart * xPartsLen), yBottom - yPartsLen + 30); ++iPart; m_lineVert3->setLine(xStart + (iPart * xPartsLen), yBottom - yPartsLen - 4, xStart + (iPart * xPartsLen), yBottom - yPartsLen + 30); ++iPart; m_lineVert4->setLine(xStart + (iPart * xPartsLen), yBottom - yPartsLen - 4, xStart + (iPart * xPartsLen), yBottom - yPartsLen + 30); ++iPart; m_lineVert5->setLine(xStart + (iPart * xPartsLen), yBottom - yPartsLen - 4, xStart + (iPart * xPartsLen), yBottom - yPartsLen + 30); ++iPart; m_lineVert6->setLine(xStart + (iPart * xPartsLen), yBottom - yPartsLen - 4, xStart + (iPart * xPartsLen), yBottom - yPartsLen + 30); // Time textes on X Axis // // // // mTextVert1 Vert2 Vert3 ... VertN mTextVert1 = new QGraphicsTextItem(this); mTextVert2 = new QGraphicsTextItem(this); mTextVert3 = new QGraphicsTextItem(this); mTextVert4 = new QGraphicsTextItem(this); mTextVert5 = new QGraphicsTextItem(this); mTextVert6 = new QGraphicsTextItem(this); mTextVert1->setPlainText("0"); mTextVert2->setPlainText("30"); mTextVert3->setPlainText("60"); mTextVert4->setPlainText("90"); mTextVert5->setPlainText("120"); mTextVert6->setPlainText("150"); mTextVert1->setDefaultTextColor(Qt::lightGray); mTextVert2->setDefaultTextColor(Qt::lightGray); mTextVert3->setDefaultTextColor(Qt::lightGray); mTextVert4->setDefaultTextColor(Qt::lightGray); mTextVert5->setDefaultTextColor(Qt::lightGray); mTextVert6->setDefaultTextColor(Qt::lightGray); mTextVert1->setFont(fontXYTitle); mTextVert2->setFont(fontXYTitle); mTextVert3->setFont(fontXYTitle); mTextVert4->setFont(fontXYTitle); mTextVert5->setFont(fontXYTitle); mTextVert6->setFont(fontXYTitle); iPart = 0; mTextVert1->setPos(xStart + (iPart * xPartsLen) + 5, yBottom - yPartsLen); ++iPart; mTextVert2->setPos(xStart + (iPart * xPartsLen) + 5, yBottom - yPartsLen); ++iPart; mTextVert3->setPos(xStart + (iPart * xPartsLen) + 5, yBottom - yPartsLen); ++iPart; mTextVert4->setPos(xStart + (iPart * xPartsLen) + 5, yBottom - yPartsLen); ++iPart; mTextVert5->setPos(xStart + (iPart * xPartsLen) + 5, yBottom - yPartsLen); ++iPart; mTextVert6->setPos(xStart + (iPart * xPartsLen) + 5, yBottom - yPartsLen); } // mEnableDeafultAxesOrOurCustomAxesInstead */ // // THIS IS IMPORTANT !!! // setAcceptHoverEvents(true); } void VisualizationChart::hideSeriesWithNumber(int number) { bool found = (mSeriesMap.count(number) >= 1); if(!found) return; mSeriesMap[number]->setColor(Qt::transparent); } void VisualizationChart::showSeriesWithNumber(int number) { bool found = (mSeriesMap.count(number) >= 1); if(!found) return; mSeriesMap[number]->setColor(mSeriesMap[number]->pointLabelsColor()); } void VisualizationChart::getSeriesWithNumberNameAndColor(int number, QString &name, QColor &color) { bool found = (mSeriesMap.count(number) >= 1); if(!found) return; name = mSeriesMap[number]->name(); color = mSeriesMap[number]->color(); } void VisualizationChart::addSeriesWithNumber(int number, QString name, QColor color) { bool found = (mSeriesMap.count(number) >= 1); if(found) return; //connect(newSer, &QSplineSeries::pointAdded, this, &VisualizationChart::onPointAdded); mSeriesMap.insert(number, new QSplineSeries(this)); mSeriesMap[number]->setName(name); mSeriesMap[number]->setColor(color); mSeriesMap[number]->setPointLabelsColor(color); // !! IMPORTANAT !!! CALL THIS FIRST this->addSeries(mSeriesMap[number]); // !! IMPORTANAT !!! CALL THIS SECOND mSeriesMap[number]->attachAxis(axisX); mSeriesMap[number]->attachAxis(axisY); } void VisualizationChart::setTitleForX(QString s) { mChartXTitle->setPlainText(s); } void VisualizationChart::setTitleForY(QString s) { mChartYTitle->setPlainText(s); } QSplineSeries* VisualizationChart::getSplineSeries(int num) { bool found = (this->mSeriesMap.count(num) >= 1); return (found ? this->mSeriesMap[num] : nullptr); } QColor VisualizationChart::getSplineSeriesColor(int num) { bool found = (this->mSeriesMap.count(num) >= 1); return (found ? this->mSeriesMap[num]->color() : QColor(0,0,0)); } void VisualizationChart::append(const QPointF point) { mQue.add(point.y()); } void VisualizationChart::appendPointToSeries(int serNum, const QPointF point) { mQue.add(point.y()); *mSeriesMap[serNum] << QPointF(point.x(), point.y()); refreshSeries(serNum); } void VisualizationChart::append(const qreal x, const qreal y) { append(QPointF(x, y)); } void VisualizationChart::append(const qreal y) { append(QPointF(++xi, y)); } void VisualizationChart::appendNextX() { (++xi); } void VisualizationChart::appendYToSeries(int serNum, const qreal y) { appendPointToSeries(serNum, QPointF(xi, y)); } VisualizationChart &VisualizationChart::operator+=(const QPointF point) { append(point); return *this; } VisualizationChart &VisualizationChart::operator+=(const qreal y) { append(y); return *this; } VisualizationChart &VisualizationChart::operator<<(const QPointF point) { append(point); return *this; } VisualizationChart &VisualizationChart::operator<<(const qreal y) { append(y); return *this; } void VisualizationChart::refreshSeries(int serNum) { auto series = mSeriesMap[serNum]; auto points = series->points(); int x = 0, y = 0; x = (points.last().x()); y = (points.last().y()); // Delete the first items if the number of points has reached a threshold while (series->points().length() > 0 && series->points().length() >= xlimit) series->remove(0); if (points.size() == 0) return; xmin = xmax = x; ymax = y; // Update the chart area for (int i = 0; i < series->count(); i++) { const QPointF &point = series->at(i); if (point.x() > xmax) xmax = (point.x()); if (point.x() < xmin) xmin = (point.x()); if (point.y() > ymax) ymax = (point.y()); } ymin = 0; //ymax = ymax + yeps; // Update the axis if(mMakeXaxisLengtGrowToTheRightAccordingIncommingXdata) { axisX->setRange(0, xmax); } else { axisX->setRange(xmin, xmax); } if(mMakeYaxisLengtAutoResizeAccordingIncommingYdata) { int min_ = mQue.min(); int max_ = mQue.max(); int min = mQue.min() - mQue.offset(); int max = mQue.max() + mQue.offset(); axisY->setRange(min, max); } else { // keep Y axis static from the start int min = mMinY - mQue.offset(); int max = mMaxY + mQue.offset(); axisY->setRange(min , max); } } void VisualizationChart::show15MinutesXIntervalNearCursor(QGraphicsSceneHoverEvent *event) { int x1 = event->scenePos().x(); int y1 = 58; int x2 = x1; int y2 = size().height() - 50; // y1 + 100; m_lineVert->setLine(x1, y1, x2, y2); QChart::hoverEnterEvent(event); int secs = x1 - 45; emit sendSecs(secs); if (secs <= 0) m_lineVertText->setPlainText(QString("")); else if (secs < 60) m_lineVertText->setPlainText(QString("%1 с").arg(secs)); else if (secs % 60 == 0) m_lineVertText->setPlainText(QString("%1 мин").arg(secs / 60)); else if (secs > 60) m_lineVertText->setPlainText(QString("%1 мин %2 с").arg(secs / 60).arg(secs % 60)); m_lineVertText->setPos(x1, y1 + 20); } void VisualizationChart::showCoordinatesNearCursor(QGraphicsSceneHoverEvent *event) { int x = event->scenePos().x(); int y = event->scenePos().y(); m_lineVertText->setPlainText(QString("%1 %2").arg(x).arg(y)); m_lineVertText->setPos(x, y); } void VisualizationChart::showUpdatedCoordinatesNearCursor(QGraphicsSceneHoverEvent *event) { static int chartTopY = 39; static int chartLeftX = 39; static int chartBottomY = 385; static int chartRightX = 910; static int chartFullX = chartRightX - chartLeftX; int x = (event->scenePos().x() - chartLeftX); if(x<0 || x>chartRightX) x = 0; int y = (event->scenePos().y() - chartTopY); if(y<0 || x>chartBottomY) y = 0; //this->axisX->; int min = axisX->min(); int max = axisX->max(); int width = max - min; int secs = xi; int onePart = chartFullX / secs; int halfPart = onePart / 2; int val = 0; for(int i = 0, ilen = secs, left=0, right=0; i=left && x<=right) { if(x>=left && x<=left+halfPart) { val = i+1; break; } if(x>=right-halfPart && x<=right) { val = i+2; break; } } } //m_lineVertText->setPlainText(QString("%1 %2 - %3 %4 ").arg(min).arg(max).arg(x).arg(y)); //m_lineVertText->setPlainText(QString("%1 %2 ").arg(x).arg(y)); m_lineVertText->setPlainText(QString("%1 %2 %3 ").arg(val).arg(x).arg(y)); m_lineVertText->setPos(x, y); } void VisualizationChart::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { //this->axisX->; int ssecs = xi; int min = axisX->min(); int max = axisX->max(); int width = max - min; if(width==0) // DEVISION BY ZERO return; int ssecsModWidth = ssecs % width; if(ssecs==0 || width==0 || ssecsModWidth==0) // DEVISION BY ZERO return; //show15MinutesXIntervalNearCursor(event); //showCoordinatesNearCursor(event); //showUpdatedCoordinatesNearCursor(event); bool showAllSecondsPassedOutsideRangeOfAxisXThatItShows = true; int x1 = event->scenePos().x(); int y1 = 40; int x2 = x1; int y2 = size().height() - 58; // y1 + 100; m_lineVert->setLine(x1, y1, x2, y2); QChart::hoverEnterEvent(event); int secs = x1 - 45; emit sendSecs(secs); if(showAllSecondsPassedOutsideRangeOfAxisXThatItShows) if(ssecs-1 > width) ssecs = width; //ssecs = ssecs % width; //val = val % width; static int chartTopY = 39; static int chartLeftX = 25; static int chartBottomY = 385; static int chartRightX = 900; static int chartFullX = chartRightX - chartLeftX; int x = (event->scenePos().x() - chartLeftX); x = (x<0 || x>chartRightX) ? 0 : x; int y = (event->scenePos().y() - chartTopY); y = (y<0 || x>chartBottomY) ? 0 : y; int onePart = chartFullX / ssecs; int halfPart = onePart / 2; int val = 0; for(int i = 0, ilen = ssecs, left=0, right=0; i=left && x<=right) { if(x>=left && x<=left+halfPart) { val = i; //i+1; break; } if(x>=right-halfPart && x<=right) { val = i+1; //i+2; break; } } } //m_lineVertText->setPlainText(QString("%1 %2 - %3 %4 ").arg(min).arg(max).arg(x).arg(y)); //m_lineVertText->setPlainText(QString("%1 %2 ").arg(x).arg(y)); //m_lineVertText->setPlainText(QString("%1 %2 %3 ").arg(val).arg(x).arg(y)); if (val <= 0) m_lineVertText->setPlainText(QString("")); else if (val < 60) m_lineVertText->setPlainText(QString("%1 с").arg(val)); else if (val % 60 == 0) m_lineVertText->setPlainText(QString("%1 мин").arg(val / 60)); else if (val > 60) m_lineVertText->setPlainText(QString("%1 мин %2 с").arg(val / 60).arg(val % 60)); m_lineVertText->setPos(x1, y1 + 20); } void VisualizationChart::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { // ensure it is visible if (this->isVisible()) { // Update cursor position (text to be displayed) QString string = QString("%1, %2").arg(event->scenePos().x()).arg(event->scenePos().y()); } }