Files
CuboBmsTool/visualizationchart.cpp
2022-08-01 21:53:36 +03:00

724 lines
21 KiB
C++
Raw Permalink 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 "visualizationchart.h"
#include <QGraphicsView>
#include <QGraphicsScene>
#include <vector>
#include <iomanip>
#include <cmath>
// https://russianblogs.com/article/5807234373/
double calculateDistance(std::pair<int, int> &x, std::pair<int, int> &y)
{
return std::sqrt(std::pow(x.first - y.first, 2) +
std::pow(x.second - y.second, 2));
}
double calculateDistance(std::pair<double, double> &x, std::pair<double, double> &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<std::pair<int, int>> 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<int>(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; i<ilen; i++) {
mlinesHoriz.push_back(new QGraphicsLineItem(this));
}
for(int i = 0, ilen = horizLines; i<ilen; i++) {
mlinesHoriz[i]->setPen(penDots); //->setFont(fontXYTitle);
if(i == (ilen - 1))
mlinesHoriz[i]->setPen(penLast); //->setFont(fontXYTitle);
}
for(int iPart = 0, ilen = horizLines; iPart<ilen; iPart++) {
mlinesHoriz[iPart]->setLine(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; i<ilen; i++) {
mTextsHoriz.push_back(new QGraphicsTextItem(this));
}
for(int i = 0, number = mMaxY, ilen = mTextsHoriz.size(); i<ilen; i++) {
if(i==0)
mTextsHoriz[i]->setPlainText("");
else
{
number -= yStep;
mTextsHoriz[i]->setPlainText(QString::number(number));
}
}
for(int i = 0, ilen = mTextsHoriz.size(); i<ilen; i++) {
mTextsHoriz[i]->setDefaultTextColor(Qt::lightGray);
}
for(int i = 0, ilen = mTextsHoriz.size(); i<ilen; i++) {
mTextsHoriz[i]->setFont(fontXYTitle);
}
for(int iPart = 0, ilen = mTextsHoriz.size(); iPart<ilen; iPart++) {
//mTextsHoriz[i]->setFont(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<ilen; ++i)
{
left = i * onePart;
right = (i+1) * onePart;
if(x>=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<ilen; ++i)
{
left = i * onePart;
right = (i+1) * onePart;
if(x>=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());
}
}