File indexing completed on 2022-09-20 12:13:57

0001 /*************************************************************************************
0002  *  Copyright (C) 2011 by Aleix Pol <aleixpol@kde.org>                               *
0003  *  Copyright (C) 2012-2013 by Percy Camilo T. Aucahuasi <percy.camilo.ta@gmail.com> *
0004  *                                                                                   *
0005  *  This program is free software; you can redistribute it and/or                    *
0006  *  modify it under the terms of the GNU General Public License                      *
0007  *  as published by the Free Software Foundation; either version 2                   *
0008  *  of the License, or (at your option) any later version.                           *
0009  *                                                                                   *
0010  *  This program is distributed in the hope that it will be useful,                  *
0011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of                   *
0012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                    *
0013  *  GNU General Public License for more details.                                     *
0014  *                                                                                   *
0015  *  You should have received a copy of the GNU General Public License                *
0016  *  along with this program; if not, write to the Free Software                      *
0017  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA   *
0018  *************************************************************************************/
0019 
0020 #include "plotter2d.h"
0021 
0022 #include "planecurve.h"
0023 
0024 #include "plotsmodel.h"
0025 #include "private/utils/mathutils.h"
0026 
0027 #include <cmath>
0028 #include <QPalette>
0029 #include <QPainter>
0030 #include <QFontDatabase>
0031 #include <QDebug>
0032 #include <qnumeric.h>
0033 
0034 #if defined(HAVE_IEEEFP_H)
0035 #include <ieeefp.h>
0036 // bool qIsInf(double x) { return !finite(x) && x==x; }
0037 #endif
0038 
0039 using namespace Analitza;
0040 
0041 // #define DEBUG_GRAPH
0042 
0043 class Analitza::Plotter2DPrivate : public QObject {
0044 public:
0045     Plotter2DPrivate(Plotter2D* q) : q(q) {}
0046 
0047     QAbstractItemModel* m_model = nullptr;
0048     qreal m_dpr = 1.;
0049     Plotter2D* const q;
0050 
0051     void forceRepaint() { q->forceRepaint(); }
0052     void addFuncs(const QModelIndex& parent, int start, int end) { q->updateFunctions(parent, start, end); }
0053     void updateFuncs(const QModelIndex& start, const QModelIndex& end) { q->updateFunctions(QModelIndex(), start.row(), end.row()); }
0054     void setModel(QAbstractItemModel* f);
0055 };
0056 
0057 QColor const Plotter2D::m_axeColor(100,100,255); //TODO convert from const to param/attr and make setAxisColor(Qt::oriantation, qcolor)
0058 QColor const Plotter2D::m_derivativeColor(90,90,160); //TODO remove derivative logic from plotter2d, move it to other module
0059 QString const Plotter2D::PiSymbol(QChar(0x03C0));
0060 QString const Plotter2D::DegreeSymbol(QChar(0x00B0));
0061 QString const Plotter2D::GradianSymbol(QChar(0x1D4D));
0062 double const Plotter2D::Pi6 = M_PI_2/3.0;
0063 double const Plotter2D::Pi12 = Plotter2D::Pi6*0.5;
0064 double const Plotter2D::Pi36 = Plotter2D::Pi6/6.0;
0065 const double Plotter2D::ZoomInFactor = 0.97/2; // (regular mouse wheel forward value) / 2
0066 const double Plotter2D::ZoomOutFactor = 2*1.03; // 2 x (regular mouse wheel backward value)
0067 
0068 struct Plotter2D::GridInfo
0069 {
0070     double inc, xini, yini, xend, yend;
0071     int incLabelSkip;
0072     // sub5 flag is used for draw sub 5 intervals instead of 4
0073     int subinc; // true if inc=5*pow(10,n) (so, in this case, we draw 5 sub intervals, not 4)
0074     // we need scale for ticks, and for labels, thicks can be minor, labels no: so 
0075     // this is necessary for minor ticks and for minir grid (but not for labels, so when draw labels we need )
0076     int nxiniticks, nyiniticks, nxendticks, nyendticks; // nxini = floor(viewport.left()/inc), so xini is nxini*inc ... and so on
0077     int nxinilabels, nyinilabels, nxendlabels, nyendlabels; // nxini = floor(viewport.left()/inc), so xini is nxini*inc ... and so on
0078 };
0079 
0080 Plotter2D::Plotter2D(const QSizeF& size)
0081     : m_showGrid(true)
0082     , m_showMinorGrid(false)
0083     , m_gridColor(QColor(Qt::lightGray).lighter(120))
0084     , m_backgroundColor(Qt::white)
0085     , m_autoGridStyle(true)
0086     , m_gridStyleHint(Squares)
0087     , rang_x(0)
0088     , rang_y(0)
0089     , m_keepRatio(true)
0090     , m_dirty(true)
0091     , m_size(size)
0092     , d(new Plotter2DPrivate(this))
0093     , m_angleMode(Radian)
0094     , m_scaleMode(Linear)
0095     , m_showTicks(Qt::Vertical|Qt::Horizontal)
0096     , m_showTickLabels(Qt::Vertical|Qt::Horizontal)
0097     , m_showMinorTicks(false)
0098     , m_showAxes(Qt::Vertical|Qt::Horizontal)
0099     , m_showPolarAxis(false)
0100     , m_showPolarAngles(false)
0101     , m_axisXLabel(QStringLiteral("x"))
0102     , m_axisYLabel(QStringLiteral("y"))
0103 {}
0104 
0105 Plotter2D::~Plotter2D()
0106 {
0107     delete d;
0108 }
0109 
0110 void Plotter2D::setGridStyleHint(GridStyle suggestedgs)
0111 {
0112     m_gridStyleHint = suggestedgs;
0113     
0114     forceRepaint();
0115 }
0116 
0117 const QColor Plotter2D::computeSubGridColor() const
0118 {
0119     //impl. details: since any kde user can create any palette style, we need this hard/magic numbres
0120     // because there is no way to guess, however, this code covers almost any case, 
0121     // it was tested with more then 35 color styles, and all give good results.
0122     
0123     QColor col = m_gridColor;
0124     
0125     if (m_backgroundColor.value() < 200)
0126     {
0127         if (m_gridColor.value() < 40)
0128             col.setHsv(col.hsvHue(), col.hsvSaturation(), m_gridColor.value()-15);
0129         else
0130             col.setHsv(col.hsvHue(), col.hsvSaturation(), m_gridColor.value()>=255?120:m_gridColor.value()-10);
0131     }
0132     else // e.g: background color white and grid color black
0133         if (m_backgroundColor.value() < 245)
0134             col.setHsv(col.hsvHue(), col.hsvSaturation(), m_backgroundColor.value()-(m_backgroundColor.value()-200)/3);
0135         else
0136             col.setHsv(col.hsvHue(), col.hsvSaturation(), m_backgroundColor.value()-(m_backgroundColor.value()-200)/8);            
0137     
0138     return col;
0139 }
0140 
0141 const Plotter2D::GridInfo Plotter2D::getGridInfo() const
0142 {
0143     GridInfo ret;
0144     
0145     if (m_scaleMode == Linear) {
0146         const double val = log10(qMax(viewport.width(), -viewport.height()));
0147         const double diff = val-floor(val);
0148         const double magnitude = pow(10, floor(val)-1);
0149 
0150         ret.inc = magnitude;
0151         ret.incLabelSkip = diff < 0.5 ? 1 : 3;
0152     } else {
0153         ret.inc = M_PI;
0154         ret.incLabelSkip = 1;
0155     }
0156 
0157     ret.subinc = 4;
0158     
0159     ret.nxinilabels = std::floor(viewport.left()/ret.inc);
0160     ret.nyinilabels = std::floor(viewport.bottom()/ret.inc);
0161     ret.nxendlabels = std::ceil(viewport.right()/ret.inc);
0162     ret.nyendlabels = std::ceil(viewport.top()/ret.inc);
0163     
0164     ret.xini = ret.nxinilabels*ret.inc;
0165     ret.yini = ret.nyinilabels*ret.inc;
0166     ret.xend = ret.nxendlabels*ret.inc;
0167     ret.yend = ret.nyendlabels*ret.inc;
0168 
0169     const bool drawminor = m_showMinorGrid || m_showMinorTicks;
0170     const double nfactor = drawminor ? ret.subinc : 1;
0171     
0172     ret.nxiniticks = nfactor*ret.nxinilabels;
0173     ret.nyiniticks = nfactor*ret.nyinilabels;
0174     ret.nxendticks = nfactor*ret.nxendlabels;
0175     ret.nyendticks = nfactor*ret.nyendlabels;
0176     
0177     return ret; 
0178 }
0179 
0180 void Plotter2D::drawGrid(QPaintDevice *qpd)
0181 {
0182     QPainter p;
0183     p.begin(qpd);
0184     
0185     int current=currentFunction();
0186     PlotItem* plot = itemAt(current);
0187     
0188     GridStyle t = Squares; // default for Cartesian coord. sys.
0189     
0190     if (plot && plot->coordinateSystem() == Polar)
0191         t = Circles;
0192     
0193     if (!m_autoGridStyle)
0194         t = m_gridStyleHint;
0195     
0196     drawAxes(&p, t);
0197 }
0198 
0199 void Plotter2D::drawAxes(QPainter* painter, GridStyle gridStyle) const
0200 {
0201     GridInfo grid = getGridInfo();
0202     
0203     switch (gridStyle) 
0204     {
0205         case Circles: drawCircles(painter, grid, gridStyle); break;
0206         default: drawSquares(painter, grid, gridStyle); break;
0207     }
0208     
0209     drawMainAxes(painter);
0210     //NOTE always draw the ticks at the end: avoid the grid lines overlap the ticks text
0211     drawGridTickLabels(painter, grid, gridStyle);
0212 }
0213 
0214 void Plotter2D::drawMainAxes(QPainter* painter) const
0215 {
0216     const QFontMetrics fm(painter->font());
0217     const QPen axesPen(m_axeColor, 1, Qt::SolidLine);
0218     const QPointF center = toWidget(QPointF(0.,0.));
0219     
0220     painter->setPen(axesPen);
0221     painter->setBrush(axesPen.color());
0222     
0223     const int startAngleX = 150*16;
0224     const int startAngleY = 240*16;
0225     const int spanAngle = 60*16;
0226     const QPointF Xright(this->width(), center.y());
0227     const QPointF Ytop(center.x(), 0.);
0228     const double arrowWidth=15., arrowHeight=4.;
0229     const QPointF dpx(arrowWidth, arrowHeight);
0230     const QPointF dpy(arrowHeight, arrowWidth);
0231     const QRectF rectX(Xright+dpx, Xright-dpx);
0232     const QRectF rectY(Ytop+dpy, Ytop-dpy);
0233 
0234     if (m_showAxes&Qt::Horizontal)
0235     {
0236         painter->drawLine(QPointF(0., center.y()), Xright);
0237         
0238         painter->setRenderHint(QPainter::Antialiasing, true);
0239         painter->drawPie(rectX, startAngleX, spanAngle);
0240     }
0241 
0242     if (m_showAxes&Qt::Vertical)
0243     {
0244         painter->drawLine(Ytop, QPointF(center.x(), this->height()));
0245         
0246         painter->setRenderHint(QPainter::Antialiasing, true);
0247         painter->drawPie(rectY, startAngleY, spanAngle);
0248     }
0249 
0250     painter->setRenderHint(QPainter::Antialiasing, true);
0251     // axis labels
0252     QFont labelsfont = painter->font();
0253     labelsfont.setBold(true);
0254 
0255     painter->drawText(Xright.x() - fm.boundingRect(m_axisXLabel).width() - 5, center.y() - 20, m_axisXLabel);
0256     painter->drawText(center.x() + 5, Ytop.y() + fm.height() + 15, m_axisYLabel);
0257 
0258     //write coords
0259     QString rightBound=QString::number(viewport.right());
0260     int width=painter->fontMetrics().boundingRect(rightBound).width();
0261 
0262     painter->drawText(QPointF(3.+this->width()/2., 13.                 ), QString::number(viewport.top()));
0263     painter->drawText(QPointF(3.+this->width()/2., this->height()-5.   ), QString::number(viewport.bottom()));
0264     painter->drawText(QPointF(8.                 , this->height()/2.-5.), QString::number(viewport.left()));
0265     painter->drawText(QPointF(this->width()-width, this->height()/2.-5.), rightBound);
0266     //EO write coords
0267 }
0268 
0269 const QString Plotter2D::computeAngleLabelByFrac(unsigned int n, unsigned int d) const
0270 {
0271     QString s;
0272     
0273     switch (m_angleMode)
0274     {
0275         case Radian:
0276         {
0277             s = (n==1) ? QString() : QString::number(n);
0278             s += PiSymbol;
0279             s += (d==1) ? QString() : '/'+QString::number(d);
0280         }
0281         break;
0282         case Degree: s = QString::number(radiansToDegrees(n*M_PI/d))+DegreeSymbol; break;
0283         case Gradian:  s = QString::number(radiansToGradians(n*M_PI/d))+GradianSymbol; break;
0284     }
0285     
0286     return s;
0287 }
0288 
0289 const QString Plotter2D::computeAngleLabelByStep(unsigned int k, unsigned int step) const
0290 {
0291     QString s;
0292     
0293     switch (m_angleMode)
0294     {
0295         case Radian:
0296         {
0297             s = (k==1) ? ((step==1) ? QLatin1String("") : QString::number(step)) : QString::number(k*step);
0298             s += PiSymbol;
0299         }
0300         break;
0301         case Degree: s = QString::number(radiansToDegrees(k*step*M_PI))+DegreeSymbol; break;
0302         case Gradian:  s = QString::number(radiansToGradians(k*step*M_PI))+GradianSymbol; break;
0303     }
0304     
0305     return s;
0306 }
0307 
0308 void Plotter2D::drawCartesianTickLabels(QPainter* painter, const Plotter2D::GridInfo& gridinfo, CartesianAxis axis) const
0309 {
0310     Q_ASSERT(axis == XAxis || axis == Analitza::YAxis);
0311     
0312     const bool isxaxis = (axis == XAxis);
0313     const double fontHeight = painter->fontMetrics().height();
0314     
0315     const bool incbig = (M_PI <= gridinfo.inc);
0316     const unsigned int bigstep = std::floor(gridinfo.inc/M_PI);
0317     const unsigned int step = std::ceil(M_PI/gridinfo.inc);
0318     
0319     QString s;
0320 
0321     const int from = (axis == Analitza::XAxis) ? gridinfo.nxinilabels : gridinfo.nyinilabels;
0322     const int to = (axis == Analitza::XAxis) ? gridinfo.nxendlabels : gridinfo.nyendlabels;
0323     
0324     painter->save();
0325     QFont tickFont = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
0326     painter->setFont(tickFont);
0327     painter->setPen(QPen(QPalette().text().color()));
0328     for (int i = from; i <= to; ++i)
0329     {
0330         if (i == 0 || i%gridinfo.incLabelSkip!=0) continue;
0331         
0332         double newval = i*gridinfo.inc;
0333         
0334         const QPointF p = isxaxis
0335             ? toWidget(QPointF(newval, 0.0))
0336             : toWidget(QPointF(0.0, newval));
0337         
0338         switch (m_scaleMode)
0339         {
0340             case Linear: 
0341                 s = QString::number(newval); 
0342                 break;
0343             case Trigonometric:
0344             {
0345                 s = (i < 0) ? QString(QLatin1Char('-')) : QString();
0346                 
0347                 if (incbig)
0348                     s += computeAngleLabelByStep(qAbs(i), bigstep);
0349                 else
0350                 {
0351                     const QPair<unsigned int, unsigned int> frac = simplifyFraction(qAbs(i), step);
0352                     
0353                     s += computeAngleLabelByFrac(frac.first, frac.second);
0354                 }
0355             }
0356             break;
0357         }
0358         
0359         const int swidth = painter->fontMetrics().boundingRect(s).width();
0360         if (isxaxis)
0361             painter->drawText(p.x() - swidth/2, p.y()+fontHeight, s);
0362         else
0363             painter->drawText(p.x() - swidth - fontHeight/2, p.y()+fontHeight/2, s);
0364     }
0365     painter->restore();
0366 }
0367 
0368 void Plotter2D::drawPolarTickLabels(QPainter* painter, const Plotter2D::GridInfo& gridinfo) const
0369 {
0370     unsigned int k = 1;
0371 
0372     painter->setPen(m_gridColor);
0373     
0374     //TODO if minor
0375     const double newinc = gridinfo.inc/(gridinfo.subinc); // inc with sub intervals
0376     
0377     //x
0378     // we assume 0 belongs to interval [gridinfo.xini, gridinfo.xend]
0379     double maxh = qMax(std::abs(gridinfo.xini), std::abs(gridinfo.xend));
0380     int i = std::ceil(maxh/newinc)/2;
0381     double x = i*newinc;
0382     
0383     if (std::abs(gridinfo.xend) <= std::abs(gridinfo.xini))
0384         x *= -1.0;
0385     
0386     // if 0 doesn't belongs to interval [gridinfo.xini, gridinfo.xend]
0387     if (((gridinfo.xend < 0.0) && (gridinfo.xini < 0.0)) || 
0388         ((gridinfo.xend > 0.0) && (gridinfo.xini > 0.0)))
0389     {
0390         maxh = gridinfo.xend - gridinfo.xini;
0391         i = std::ceil(maxh/newinc)/2;
0392         
0393         x = gridinfo.xini + i*newinc;
0394     }
0395     
0396     //y
0397     // we assume 0 belongs to interval [gridinfo.yini, gridinfo.yend]
0398     maxh = qMax(std::abs(gridinfo.yini), std::abs(gridinfo.yend));
0399     i = std::ceil(maxh/newinc)/2;
0400     double y = i*newinc;
0401     
0402     if (std::abs(gridinfo.yend) <= std::abs(gridinfo.yini))
0403         y *= -1.0;
0404     
0405     // if 0 doesn't belongs to interval [gridinfo.xini, gridinfo.xend]
0406     if (((gridinfo.yend < 0.0) && (gridinfo.yini < 0.0)) || 
0407         ((gridinfo.yend > 0.0) && (gridinfo.yini > 0.0)))
0408     {
0409         maxh = gridinfo.yend - gridinfo.yini;
0410         i = std::ceil(maxh/newinc)/2;
0411         
0412         y = gridinfo.yini + i*newinc;
0413     }
0414     
0415     const double r = qMax(std::abs(x), std::abs(y)); // radius
0416     const unsigned short axisxseparation = 16; // distance between tick text and x-axis 
0417     const float axispolarseparation = axisxseparation*0.5;
0418     
0419     double h = Pi6; // pi/6, then 12 rounds
0420     double t = 0.0;
0421     
0422     unsigned int turns = 12; // 12 turns in [0, 2pi], since h=pi/6
0423     
0424     // if we are far from origin then sudivide angles by pi/12
0425     if (!viewport.contains(QPointF(0.0, 0.0)))
0426     {
0427         h = Pi12;
0428         turns = 24; // 24 turns in [0, 2pi], since h=pi/12
0429     }
0430     
0431     k = 0; 
0432     
0433     const unsigned int drawoverxaxiscount = turns/2;
0434     const unsigned int drawnextyaxiscount = turns/4;
0435     const unsigned int halfturns = drawoverxaxiscount; // or turns/2;
0436     
0437     for (uint j = 0; j < turns; ++j, ++k, t += h) // Pi6 then 24 turns
0438     {
0439         const QPair<unsigned int, unsigned int> frac = simplifyFraction(k, halfturns);
0440         
0441         QString s = (k != 0) ? computeAngleLabelByFrac(frac.first, frac.second) : QStringLiteral("0");
0442         QPointF p(r*std::cos(t), r*std::sin(t));
0443         
0444         if (viewport.contains(p))
0445         {
0446 
0447             if (k % drawoverxaxiscount == 0) // draw 0, pi over x
0448                 painter->drawText(toWidget(p)+QPointF(0.0, -axispolarseparation), s);
0449             else 
0450                 if (k % drawnextyaxiscount == 0) // draw pi/2, 3pi/2 next to y
0451                     painter->drawText(toWidget(p)+QPointF(axispolarseparation, 0.0), s);
0452                 else
0453                     painter->drawText(toWidget(p), s);
0454         }
0455     }
0456 }
0457 
0458 void Plotter2D::drawGridTickLabels(QPainter* painter, const GridInfo& gridinfo, GridStyle gridStyle) const
0459 {
0460     if (m_showTickLabels & Qt::Horizontal)
0461         drawCartesianTickLabels(painter, gridinfo, XAxis);
0462     
0463     if (m_showTickLabels & Qt::Vertical)
0464         drawCartesianTickLabels(painter, gridinfo, YAxis);
0465     
0466     if ((gridStyle == Circles) && m_showPolarAngles && m_showGrid) // draw labels for angles (polar axis)
0467         drawPolarTickLabels(painter, gridinfo);
0468 }
0469 
0470 void Plotter2D::drawCircles(QPainter* painter, const GridInfo& gridinfo, GridStyle gridStyle) const
0471 {
0472     Q_ASSERT(gridStyle == Analitza::Circles);
0473     painter->setRenderHint(QPainter::Antialiasing, false);
0474     
0475     const QPen textPen = QPen(QPalette().text().color());
0476     const QPen gridPen(m_gridColor);
0477     const QPen subGridPen(computeSubGridColor());
0478 
0479     const unsigned short nsubinc = gridinfo.subinc; // count for draw sub intervals
0480     const bool drawminor = m_showMinorGrid || m_showMinorTicks;
0481     const double inc = drawminor ? gridinfo.inc/nsubinc : gridinfo.inc; // if show minor, then inc with sub intervals
0482     
0483     if (m_showGrid) 
0484     {
0485         const qreal until = qMax(qMax(qAbs(viewport.left()), qAbs(viewport.right())), qMax(qAbs(viewport.top()), qAbs(viewport.bottom())))*M_SQRT2;
0486         
0487         int k = 1;
0488         
0489         painter->setPen(gridPen);
0490         
0491         for (double i = inc; i < until; i += inc, ++k)
0492         {
0493             if (i == 0.0) continue;
0494             
0495             QPointF p(toWidget(QPointF(i,i)));
0496             QPointF p2(toWidget(QPointF(-i,-i)));
0497             QRectF er(p.x(),p.y(), p2.x()-p.x(), p2.y()-p.y());
0498             
0499             if ((k % nsubinc == 0) || !drawminor) // intervals
0500             {
0501                 painter->setPen(gridPen);
0502                 painter->drawEllipse(er);
0503             }
0504             else if (m_showMinorGrid)// sub intervals
0505             {
0506                 painter->setPen(subGridPen);
0507                 painter->drawEllipse(er);
0508             }
0509         }
0510         
0511         if (m_showPolarAxis) // draw polar rays
0512         {
0513             double h = Pi12; // 2pi/(Pi/12) = 24 steps/turns
0514             const double r = std::abs(until); // radius
0515             const QPointF origin = toWidget(QPointF(0,0));
0516             
0517             int k = 1;
0518             unsigned int alternatesubgridcount = 2;
0519             unsigned int dontdrawataxiscount = 5;
0520             unsigned int steps = 24; // or turns
0521             
0522             // if we are far from origin then sudivide angles by 5 degrees
0523             if (!viewport.contains(QPointF(0.0, 0.0)))
0524             {
0525                 h = Pi36; // 2pi/(Pi/36) = 72 steps
0526                 steps = 72; // or turns
0527                 alternatesubgridcount = 3;
0528                 dontdrawataxiscount = 17;
0529             }
0530             
0531             double t = h;
0532             for (uint j = 1; j <= steps; ++j, ++k, t += h)
0533             {
0534                 if (j % alternatesubgridcount == 0)
0535                     painter->setPen(gridPen);
0536                 else if (m_showMinorGrid)
0537                 {
0538                     painter->setPen(subGridPen);
0539 
0540                     QPointF p = toWidget(QPointF(r*std::cos(t), r*std::sin(t)));
0541                     painter->drawLine(origin, p);
0542 
0543                     if (k % dontdrawataxiscount == 0) // avoid draw the ray at 0,pi/2,pi,3pi/2
0544                     {
0545                         t += h;
0546                         ++j;
0547                     }
0548                 }
0549             }
0550         }
0551     }
0552     
0553     //BEGIN draw ticks
0554     if (m_showTickLabels & Qt::Horizontal)
0555     {
0556         painter->setPen(textPen);
0557         const QPointF yoffset(0.,-3.);
0558         
0559         for (int j = gridinfo.nxiniticks, i = 0; j < gridinfo.nxendticks; ++j, ++i)
0560         {
0561             if (j == 0) continue;
0562             
0563             const double x = j*inc;
0564             const QPointF p = toWidget(QPointF(x, 0.));
0565 
0566             if ((i % nsubinc == 0) || !drawminor || m_showMinorTicks)
0567                 painter->drawLine(p, p+yoffset);
0568         }
0569     }
0570     
0571     if (m_showTickLabels & Qt::Vertical)
0572     {
0573         painter->setPen(textPen);
0574         const QPointF xoffset(3.,0.);
0575         
0576         for (int j = gridinfo.nyiniticks, i = 0; j < gridinfo.nyendticks; ++j, ++i)
0577         {
0578             if (j == 0) continue;
0579             
0580             const double y = j*inc;
0581             
0582             const QPointF p = toWidget(QPointF(0., y));
0583             
0584             if ((i % nsubinc == 0) || !(m_showMinorGrid || m_showMinorTicks) || m_showMinorTicks)
0585                 painter->drawLine(p, p+xoffset);
0586         }
0587     }
0588     //END draw ticks
0589 }
0590 
0591 void Plotter2D::drawSquares(QPainter* painter, const GridInfo& gridinfo, GridStyle gridStyle) const
0592 {
0593     painter->setRenderHint(QPainter::Antialiasing, false);
0594     
0595     const QPen gridPen(m_gridColor);
0596     const QPen subGridPen(computeSubGridColor());
0597     const QPen gridPenBold(gridPen.brush(), 2);
0598     const QPen subGridPenBold(subGridPen.brush(), gridPenBold.widthF());
0599     
0600     const unsigned short nsubinc = gridinfo.subinc; // count for draw sub intervals
0601     const bool drawminor = m_showMinorGrid || m_showMinorTicks;
0602     const double inc = drawminor? gridinfo.inc/nsubinc : gridinfo.inc; // if show minor, then inc with sub intervals
0603     
0604     for (int n = gridinfo.nxiniticks, i = 0; n < gridinfo.nxendticks; ++n, ++i)
0605     {
0606         if (n == 0) continue;
0607         
0608         const double x = n*inc;
0609         const QPointF p = toWidget(QPointF(x, 0.));
0610         
0611         if (m_showGrid && gridStyle == Crosses)
0612         {
0613             const double crossside = 5;
0614             const double centx = p.x();
0615 
0616             for (int j = gridinfo.nyiniticks, k = 0; j < gridinfo.nyendticks; ++j, ++k)
0617             {
0618                 if (j == 0) continue;
0619 
0620                 const double y = j*inc;
0621                 const QPointF py = toWidget(QPointF(0., y));
0622                 const double centy = py.y();
0623                 
0624                 if (((i % nsubinc == 0) && (k % nsubinc == 0)) || !drawminor) // intervals
0625                 {
0626                     painter->setPen(gridPenBold);
0627                     painter->drawLine(QPointF(centx-crossside, centy), QPointF(centx+crossside, centy)); // horizontal
0628                     painter->drawLine(QPointF(centx, centy-crossside), QPointF(centx, centy+crossside)); // vertical
0629                 }
0630                 else if (m_showMinorGrid)// sub intervals
0631                 {
0632                     painter->setPen(subGridPenBold);
0633                     painter->drawLine(QPointF(centx-crossside, centy), QPointF(centx+crossside, centy)); // horizontal
0634                     painter->drawLine(QPointF(centx, centy-crossside), QPointF(centx, centy+crossside)); // vertical
0635                 }
0636             }
0637         }
0638         
0639         if ((i % nsubinc == 0) || !drawminor) // intervals
0640         {
0641             if (m_showGrid && (gridStyle == Squares || gridStyle == VerticalLines))
0642             {
0643                 painter->setPen(gridPen);
0644                 painter->drawLine(QPointF(p.x(), height()), QPointF(p.x(), 0.));
0645             }
0646             
0647             if ((m_showTicks & Qt::Horizontal))
0648             {
0649                 painter->setPen(QPen(QPalette().text().color()));
0650                 painter->drawLine(p, p+QPointF(0.,-3.));
0651             }
0652         }
0653         else // sub intervals
0654         {
0655             if (m_showGrid && m_showMinorGrid && (gridStyle == Squares || gridStyle == VerticalLines))
0656             {
0657                 painter->setPen(subGridPen);
0658                 painter->drawLine(QPointF(p.x(), height()), QPointF(p.x(), 0.));
0659             }
0660             
0661             if ((m_showTicks & Qt::Horizontal) && m_showMinorTicks)
0662             {
0663                 painter->setPen(QPen(QPalette().text().color()));
0664                 painter->drawLine(p, p+QPointF(0.,-3.));
0665             }
0666         }
0667     }
0668     
0669     for (int j = gridinfo.nyiniticks, i = 0; j < gridinfo.nyendticks; ++j, ++i)
0670     {
0671         if (j == 0) continue;
0672         
0673         const double y = j*inc;
0674         const QPointF p = toWidget(QPointF(0., y));
0675         
0676         if ((i % nsubinc == 0) || !drawminor) // intervals
0677         {
0678             if (m_showGrid && (gridStyle == Squares || gridStyle == HorizontalLines))
0679             {
0680                 painter->setPen(gridPen);
0681                 painter->drawLine(QPointF(0., p.y()), QPointF(width(), p.y()));
0682             }
0683             
0684             if ((m_showTicks & Qt::Horizontal))
0685             {
0686                 painter->setPen(QPen(QPalette().text().color()));
0687                 painter->drawLine(p, p+QPointF(3.,0.));
0688             }
0689         }
0690         else // sub intervals
0691         {
0692             if (m_showGrid && m_showMinorGrid && (gridStyle == Squares || gridStyle == HorizontalLines))
0693             {
0694                 painter->setPen(subGridPen);
0695                 painter->drawLine(QPointF(0., p.y()), QPointF(width(), p.y()));
0696             }
0697             
0698             if ((m_showTicks & Qt::Horizontal) && m_showMinorTicks)
0699             {
0700                 painter->setPen(QPen(QPalette().text().color()));
0701                 painter->drawLine(p, p+QPointF(3.,0.));
0702             }
0703         }
0704     }
0705 }
0706 
0707 PlotItem* Plotter2D::itemAt(int row) const
0708 {
0709     if (!d->m_model)
0710         return nullptr;
0711     
0712     QModelIndex pi = d->m_model->index(row, 0);
0713 
0714     if (!pi.isValid())
0715         return nullptr;
0716 
0717     PlotItem* plot = pi.data(PlotsModel::PlotRole).value<PlotItem*>();
0718 
0719     if (plot->spaceDimension() != Dim2D)
0720         return nullptr;
0721 
0722     return plot;
0723 }
0724 
0725 void Plotter2D::drawFunctions(QPaintDevice *qpd)
0726 {
0727     drawGrid(qpd);
0728     QPen pfunc(QColor(0,150,0), 2);
0729     
0730     QPainter p;
0731     p.begin(qpd);
0732     p.setPen(pfunc);
0733 
0734     if (!d->m_model || m_dirty)
0735         return;
0736     
0737     p.setRenderHint(QPainter::Antialiasing, true);
0738     
0739     const int current=currentFunction();
0740     const int dpr = qMax<int>(1, qRound(qpd->logicalDpiX()/100.)) << 1;
0741 
0742     for (int k = 0; k < d->m_model->rowCount(); ++k )
0743     {
0744         PlaneCurve* curve = dynamic_cast<PlaneCurve *>(itemAt(k));
0745 
0746         //NOTE from GSOC: not all valid plots always has points (so, we need to check if points().isEmpty() too)
0747         if (!curve || !curve->isVisible() || curve->points().isEmpty())
0748             continue;
0749 
0750         pfunc.setColor(curve->color());
0751         pfunc.setWidth((k==current)+dpr);
0752         pfunc.setStyle(Qt::SolidLine);
0753         p.setPen(pfunc);
0754 
0755         const QVector<QPointF> &vect=curve->points();
0756         QVector<int> jumps=curve->jumps();
0757 
0758         unsigned int pointsCount = vect.count();
0759         QPointF ultim = toWidget(vect.at(0));
0760 
0761         int nextjump;
0762         nextjump = jumps.isEmpty() ? -1 : jumps.first();
0763         if (!jumps.isEmpty()) jumps.remove(0);
0764 // #define DEBUG_GRAPH 1
0765 #ifdef DEBUG_GRAPH
0766         qDebug() << "---------" << jumps.count()+1;
0767 #endif
0768         for(unsigned int j=0; j<pointsCount; ++j)
0769         {
0770             const QPointF act = toWidget(vect.at(j));
0771 
0772 //          qDebug() << "xxxxx" << act << ultim << qIsNaN(act.y()) << qIsNaN(ultim.y());
0773             if(qIsInf(act.y()) && !qIsNaN(act.y())) qDebug() << "trying to plot from a NaN value" << act << ultim;
0774             else if(qIsInf(act.y()) && qIsNaN(act.y())) qDebug() << "trying to plot to a NaN value";
0775 
0776             const bool bothinf=(qIsInf(ultim.y()) && qIsInf(act.y())) || (qIsInf(ultim.x()) && qIsInf(act.x()));
0777             if(!bothinf && !qIsNaN(act.y()) && !qIsNaN(ultim.y()) && nextjump!=int(j)) {
0778                 if(qIsInf(ultim.y())) {
0779                     if(act.y()<0) ultim.setY(0);
0780                     if(act.y()>0) ultim.setY(qpd->height());
0781                 }
0782 //
0783                 QPointF act2(act);
0784                 if(qIsInf(act2.y())) {
0785                     if(ultim.y()<0) act2.setY(0);
0786                     if(ultim.y()>0) act2.setY(qpd->height());
0787                 }
0788 
0789 //              qDebug() << "xxxxx" << act2 << ultim << qIsNaN(act2.y()) << qIsNaN(ultim.y());
0790 
0791                 p.drawLine(ultim, act2);
0792 
0793 #ifdef DEBUG_GRAPH
0794                 QPen dpen(Qt::red);
0795                 dpen.setWidth(3);
0796                 p.setPen(dpen);
0797                 p.drawPoint(ultim);
0798                 p.setPen(pfunc);
0799 #endif
0800             } else if(nextjump==int(j)) {
0801                 do {
0802                     if(nextjump!=int(j))
0803                         p.drawPoint(act);
0804                     
0805                     nextjump = jumps.isEmpty() ? -1 : jumps.first();
0806                     if (!jumps.isEmpty()) jumps.remove(0);
0807 
0808                 } while(!jumps.isEmpty() && jumps.first()==nextjump+1);
0809 
0810 #ifdef DEBUG_GRAPH
0811                 qDebug() << "jumpiiiiiing" << ultim << toWidget(vect.at(j));
0812                 QPen dpen(Qt::blue);
0813                 dpen.setWidth(2);
0814                 p.setPen(dpen);
0815                 p.drawLine(QLineF(QPointF(act.x(), height()/2-10), QPointF(act.x(), height()/2+10)));
0816                 p.setPen(pfunc);
0817 #endif
0818             }
0819 
0820             ultim=act;
0821         }
0822     }
0823 
0824     p.end();
0825 }
0826 
0827 void Plotter2D::updateFunctions(const QModelIndex & parent, int start, int end)
0828 {
0829     if (!d->m_model || parent.isValid())
0830         return;
0831 
0832     QRectF viewportFixed = viewport;
0833     viewportFixed.setTopLeft(viewport.bottomLeft());
0834     viewportFixed.setHeight(std::fabs(viewport.height()));
0835     
0836     for(int i=start; i<=end; i++)
0837     {
0838         PlaneCurve* curve = dynamic_cast<PlaneCurve *>(itemAt(i));
0839 
0840         if (!curve || !curve->isVisible())
0841             continue;
0842         
0843         curve->update(viewportFixed);
0844     }
0845     
0846     m_dirty = false; // m_dirty = false means that the we will not recalculate functions points
0847     
0848     forceRepaint();
0849 }
0850 
0851 QPair<QPointF, QString> Plotter2D::calcImage(const QPointF& ndp) const
0852 {
0853     if (!d->m_model || currentFunction() == -1)
0854         return QPair<QPointF, QString>();
0855 
0856     PlaneCurve* curve = dynamic_cast<PlaneCurve*>(itemAt(currentFunction()));
0857     
0858     if (curve && curve->isVisible())
0859         return curve->image(ndp);
0860 
0861     return QPair<QPointF, QString>();
0862 }
0863 
0864 QRectF Plotter2D::normalizeUserViewport(const QRectF &uvp)
0865 {
0866     QRectF normalizeduvp = uvp;
0867     rang_x = width()/normalizeduvp.width();
0868     rang_y = height()/normalizeduvp.height();
0869     
0870     if (m_keepRatio && rang_x != rang_y)
0871     {
0872         rang_y=rang_x=qMin(std::fabs(rang_x), std::fabs(rang_y));
0873 
0874         if(rang_y>0.) rang_y=-rang_y;
0875         if(rang_x<0.) rang_x=-rang_x;
0876 
0877         double newW=width()/rang_x, newH=height()/rang_x;
0878 
0879         double mx=(uvp.width()-newW)/2.;
0880         double my=(uvp.height()-newH)/2.;
0881 
0882         normalizeduvp.setLeft(uvp.left()+mx);
0883         normalizeduvp.setTop(uvp.bottom()-my);
0884         normalizeduvp.setWidth(newW);
0885         normalizeduvp.setHeight(-newH); //WARNING why negative distance?
0886 
0887         //Commented because precision could make the program crash
0888 //      Q_ASSERT(uvp.center() == viewport.center());
0889     }
0890 
0891     return normalizeduvp;
0892 }
0893 
0894 void Plotter2D::updateScale(bool repaint)
0895 {
0896     viewport = normalizeUserViewport(userViewport);
0897 
0898     if (repaint) {
0899         if (d->m_model && d->m_model->rowCount()>0)
0900             updateFunctions(QModelIndex(), 0, d->m_model->rowCount()-1);
0901         else
0902             forceRepaint();
0903     }
0904 }
0905 
0906 void Plotter2D::setViewport(const QRectF& vp, bool repaint)
0907 {
0908     userViewport = vp;
0909 
0910     Q_ASSERT(userViewport.top()>userViewport.bottom());
0911     Q_ASSERT(userViewport.right()>userViewport.left());
0912 
0913     updateScale(repaint);
0914 
0915     viewportChanged();
0916 }
0917 
0918 QLineF Plotter2D::slope(const QPointF& dp) const
0919 {
0920     if (!d->m_model || currentFunction() == -1)
0921         return QLineF();
0922 
0923     PlaneCurve* plot = dynamic_cast<PlaneCurve*>(itemAt(currentFunction()));
0924     
0925     if (plot && plot->isVisible())
0926     {
0927         QLineF ret = plot->tangent(dp);
0928         
0929         if (ret.isNull() && currentFunction()>=0)
0930         {
0931             QPointF a = calcImage(dp-QPointF(.1,.1)).first;
0932             QPointF b = calcImage(dp+QPointF(.1,.1)).first;
0933 
0934             ret = slopeToLine((a.y()-b.y())/(a.x()-b.x()));
0935         }
0936         
0937         return ret;
0938     }
0939 
0940     return QLineF();
0941 }
0942 
0943 QLineF Plotter2D::toWidget(const QLineF &f) const
0944 {
0945     return QLineF(toWidget(f.p1()), toWidget(f.p2()));
0946 }
0947 
0948 QPointF Plotter2D::toWidget(const QPointF& p) const
0949 {
0950     double left=-viewport.left(), top=-viewport.top();
0951     return QPointF((left + p.x()) * rang_x / d->m_dpr,  (top + p.y()) * rang_y / d->m_dpr);
0952 }
0953 
0954 QPointF Plotter2D::fromWidget(const QPoint& p) const
0955 {
0956     double negativePartX = -viewport.left();
0957     double negativePartY = -viewport.top();
0958     
0959     return QPointF(p.x()/(rang_x*d->m_dpr)-negativePartX, p.y()/(rang_y*d->m_dpr)-negativePartY);
0960 }
0961 
0962 QPointF Plotter2D::toViewport(const QPoint &mv) const
0963 {
0964     return QPointF(mv.x()/rang_x, mv.y()/rang_y);
0965 }
0966 
0967 void Plotter2D::moveViewport(const QPoint& delta)
0968 {
0969     QPointF rel = toViewport(delta);
0970     QRectF viewport = currentViewport();
0971     
0972     viewport.moveLeft(viewport.left() - rel.x());
0973     viewport.moveTop(viewport.top() - rel.y());
0974     setViewport(viewport);
0975 }
0976 
0977 void Plotter2D::scaleViewport(qreal scale, const QPoint& center, bool repaint)
0978 {
0979     QPointF p = fromWidget(center);
0980     QSizeF ns = viewport.size()*scale;
0981     QRectF nv(viewport.topLeft(), ns);
0982     
0983     setViewport(nv, false); //NOTE here isn't necessary to repaint, thus false
0984 
0985     QPointF p2 = p-fromWidget(center);
0986     nv.translate(p2);
0987     setViewport(nv, repaint);
0988 }
0989 
0990 void Plotter2D::setKeepAspectRatio(bool ar)
0991 {
0992     m_keepRatio=ar;
0993     updateScale(true);
0994 }
0995 
0996 void Analitza::Plotter2D::setModel(QAbstractItemModel* f)
0997 {
0998     d->setModel(f);
0999 }
1000 
1001 void Plotter2DPrivate::setModel(QAbstractItemModel* f)
1002 {
1003     if(m_model == f)
1004         return;
1005     
1006 
1007     if (m_model) {
1008         disconnect(m_model, &QAbstractItemModel::dataChanged, this, &Plotter2DPrivate::updateFuncs);
1009         disconnect(m_model, &QAbstractItemModel::rowsInserted, this, &Plotter2DPrivate::addFuncs);
1010         disconnect(m_model, &QAbstractItemModel::rowsRemoved, this, &Plotter2DPrivate::forceRepaint);
1011     }
1012 
1013     m_model = f;
1014 
1015     if (m_model) {
1016         connect(m_model, &QAbstractItemModel::dataChanged, this, &Plotter2DPrivate::updateFuncs);
1017         connect(m_model, &QAbstractItemModel::rowsInserted, this, &Plotter2DPrivate::addFuncs);
1018         connect(m_model, &QAbstractItemModel::rowsRemoved, this, &Plotter2DPrivate::forceRepaint);
1019 
1020         q->updateFunctions({}, 0, m_model->rowCount());
1021     } else {
1022         q->forceRepaint();
1023     }
1024 }
1025 
1026 void Analitza::Plotter2D::setDevicePixelRatio(qreal dpr)
1027 {
1028     d->m_dpr = dpr;
1029 }
1030 
1031 void Plotter2D::setPaintedSize(const QSize& size)
1032 {
1033     m_size=size;
1034     updateScale(true);
1035 }
1036 
1037 void Plotter2D::setXAxisLabel(const QString &label)
1038 {
1039     m_axisXLabel = label;
1040     forceRepaint();
1041 }
1042 
1043 void Plotter2D::setYAxisLabel(const QString &label)
1044 {
1045     m_axisYLabel = label;
1046     forceRepaint();
1047 }
1048 
1049 void Plotter2D::zoomIn(bool repaint)
1050 {
1051     scaleViewport(ZoomInFactor, QPoint(m_size.width()*0.5, m_size.height()*0.5), repaint);
1052 }
1053 
1054 void Plotter2D::zoomOut(bool repaint)
1055 {
1056     scaleViewport(ZoomOutFactor, QPoint(m_size.width()*0.5, m_size.height()*0.5), repaint);
1057 }
1058 
1059 void Plotter2D::setShowMinorGrid(bool mt)
1060 {
1061     m_showMinorGrid=mt;
1062     forceRepaint();
1063 }
1064 
1065 void Plotter2D::setShowGrid(bool show)
1066 {
1067     if (m_showGrid != show) {
1068         m_showGrid=show;
1069         forceRepaint();
1070 
1071         showGridChanged();
1072     }
1073 }
1074 
1075 QAbstractItemModel* Plotter2D::model() const
1076 {
1077     return d->m_model;
1078 }