diff --git a/analitzaplot/plotter2d.cpp b/analitzaplot/plotter2d.cpp index 51cca4cf..a163ca49 100644 --- a/analitzaplot/plotter2d.cpp +++ b/analitzaplot/plotter2d.cpp @@ -1,1054 +1,1078 @@ /************************************************************************************* * Copyright (C) 2011 by Aleix Pol * * Copyright (C) 2012-2013 by Percy Camilo T. Aucahuasi * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "plotter2d.h" #include "planecurve.h" #include "plotsmodel.h" #include "private/utils/mathutils.h" #include #include #include #include #include #include #if defined(HAVE_IEEEFP_H) #include // bool qIsInf(double x) { return !finite(x) && x==x; } #endif using namespace Analitza; // #define DEBUG_GRAPH -class Analitza::Plotter2DPrivate { +class Analitza::Plotter2DPrivate : public QObject { public: + Plotter2DPrivate(Plotter2D* q) : q(q) {} + QAbstractItemModel* m_model = nullptr; qreal m_dpr = 1.; + Plotter2D* const q; + + void forceRepaint() { q->forceRepaint(); } + void addFuncs(const QModelIndex& parent, int start, int end) { q->updateFunctions(parent, start, end); } + void updateFuncs(const QModelIndex& start, const QModelIndex& end) { q->updateFunctions(QModelIndex(), start.row(), end.row()); } + void setModel(QAbstractItemModel* f); }; QColor const Plotter2D::m_axeColor(100,100,255); //TODO convert from const to param/attr and make setAxisColor(Qt::oriantation, qcolor) QColor const Plotter2D::m_derivativeColor(90,90,160); //TODO remove derivative logic from plotter2d, move it to other module QString const Plotter2D::PiSymbol(QChar(0x03C0)); QString const Plotter2D::DegreeSymbol(QChar(0x00B0)); QString const Plotter2D::GradianSymbol(QChar(0x1D4D)); double const Plotter2D::Pi6 = M_PI_2/3.0; double const Plotter2D::Pi12 = Plotter2D::Pi6*0.5; double const Plotter2D::Pi36 = Plotter2D::Pi6/6.0; const double Plotter2D::ZoomInFactor = 0.97/2; // (regular mouse wheel forward value) / 2 const double Plotter2D::ZoomOutFactor = 2*1.03; // 2 x (regular mouse wheel backward value) struct Plotter2D::GridInfo { double inc, xini, yini, xend, yend; int incLabelSkip; // sub5 flag is used for draw sub 5 intervals instead of 4 int subinc; // true if inc=5*pow(10,n) (so, in this case, we draw 5 sub intervals, not 4) // we need scale for ticks, and for labels, thicks can be minor, labels no: so // this is necessary for minor ticks and for minir grid (but not for labels, so when draw labels we need ) int nxiniticks, nyiniticks, nxendticks, nyendticks; // nxini = floor(viewport.left()/inc), so xini is nxini*inc ... and so on int nxinilabels, nyinilabels, nxendlabels, nyendlabels; // nxini = floor(viewport.left()/inc), so xini is nxini*inc ... and so on }; Plotter2D::Plotter2D(const QSizeF& size) : m_showGrid(true) , m_showMinorGrid(false) , m_gridColor(QColor(Qt::lightGray).lighter(120)) , m_backgroundColor(Qt::white) , m_autoGridStyle(true) , m_gridStyleHint(Squares) , rang_x(0) , rang_y(0) , m_keepRatio(true) , m_dirty(true) , m_size(size) - , d(new Plotter2DPrivate) + , d(new Plotter2DPrivate(this)) , m_angleMode(Radian) , m_scaleMode(Linear) , m_showTicks(Qt::Vertical|Qt::Horizontal) , m_showTickLabels(Qt::Vertical|Qt::Horizontal) , m_showMinorTicks(false) , m_showAxes(Qt::Vertical|Qt::Horizontal) , m_showPolarAxis(false) , m_showPolarAngles(false) , m_axisXLabel(QStringLiteral("x")) , m_axisYLabel(QStringLiteral("y")) {} Plotter2D::~Plotter2D() { delete d; } void Plotter2D::setGridStyleHint(GridStyle suggestedgs) { m_gridStyleHint = suggestedgs; forceRepaint(); } const QColor Plotter2D::computeSubGridColor() const { //impl. details: since any kde user can create any palette style, we need this hard/magic numbres // because there is no way to guess, however, this code covers almost any case, // it was tested with more then 35 color styles, and all give good results. QColor col = m_gridColor; if (m_backgroundColor.value() < 200) { if (m_gridColor.value() < 40) col.setHsv(col.hsvHue(), col.hsvSaturation(), m_gridColor.value()-15); else col.setHsv(col.hsvHue(), col.hsvSaturation(), m_gridColor.value()>=255?120:m_gridColor.value()-10); } else // e.g: background color white and grid color black if (m_backgroundColor.value() < 245) col.setHsv(col.hsvHue(), col.hsvSaturation(), m_backgroundColor.value()-(m_backgroundColor.value()-200)/3); else col.setHsv(col.hsvHue(), col.hsvSaturation(), m_backgroundColor.value()-(m_backgroundColor.value()-200)/8); return col; } const Plotter2D::GridInfo Plotter2D::getGridInfo() const { GridInfo ret; if (m_scaleMode == Linear) { const double val = log10(qMax(viewport.width(), -viewport.height())); const double diff = val-floor(val); const double magnitude = pow(10, floor(val)-1); ret.inc = magnitude; ret.incLabelSkip = diff < 0.5 ? 1 : 3; } else { ret.inc = M_PI; ret.incLabelSkip = 1; } ret.subinc = 4; ret.nxinilabels = std::floor(viewport.left()/ret.inc); ret.nyinilabels = std::floor(viewport.bottom()/ret.inc); ret.nxendlabels = std::ceil(viewport.right()/ret.inc); ret.nyendlabels = std::ceil(viewport.top()/ret.inc); ret.xini = ret.nxinilabels*ret.inc; ret.yini = ret.nyinilabels*ret.inc; ret.xend = ret.nxendlabels*ret.inc; ret.yend = ret.nyendlabels*ret.inc; const bool drawminor = m_showMinorGrid || m_showMinorTicks; const double nfactor = drawminor ? ret.subinc : 1; ret.nxiniticks = nfactor*ret.nxinilabels; ret.nyiniticks = nfactor*ret.nyinilabels; ret.nxendticks = nfactor*ret.nxendlabels; ret.nyendticks = nfactor*ret.nyendlabels; return ret; } void Plotter2D::drawGrid(QPaintDevice *qpd) { QPainter p; p.begin(qpd); int current=currentFunction(); PlotItem* plot = itemAt(current); GridStyle t = Squares; // default for Cartesian coord. sys. if (plot && plot->coordinateSystem() == Polar) t = Circles; if (!m_autoGridStyle) t = m_gridStyleHint; drawAxes(&p, t); } void Plotter2D::drawAxes(QPainter* painter, GridStyle gridStyle) const { GridInfo grid = getGridInfo(); switch (gridStyle) { case Circles: drawCircles(painter, grid, gridStyle); break; default: drawSquares(painter, grid, gridStyle); break; } drawMainAxes(painter); //NOTE always draw the ticks at the end: avoid the grid lines overlap the ticks text drawGridTickLabels(painter, grid, gridStyle); } void Plotter2D::drawMainAxes(QPainter* painter) const { const QFontMetrics fm(painter->font()); const QPen axesPen(m_axeColor, 1, Qt::SolidLine); const QPointF center = toWidget(QPointF(0.,0.)); painter->setPen(axesPen); painter->setBrush(axesPen.color()); const int startAngleX = 150*16; const int startAngleY = 240*16; const int spanAngle = 60*16; const QPointF Xright(this->width(), center.y()); const QPointF Ytop(center.x(), 0.); const double arrowWidth=15., arrowHeight=4.; const QPointF dpx(arrowWidth, arrowHeight); const QPointF dpy(arrowHeight, arrowWidth); const QRectF rectX(Xright+dpx, Xright-dpx); const QRectF rectY(Ytop+dpy, Ytop-dpy); if (m_showAxes&Qt::Horizontal) { painter->drawLine(QPointF(0., center.y()), Xright); painter->setRenderHint(QPainter::Antialiasing, true); painter->drawPie(rectX, startAngleX, spanAngle); } if (m_showAxes&Qt::Vertical) { painter->drawLine(Ytop, QPointF(center.x(), this->height())); painter->setRenderHint(QPainter::Antialiasing, true); painter->drawPie(rectY, startAngleY, spanAngle); } painter->setRenderHint(QPainter::Antialiasing, true); // axis labels QFont labelsfont = painter->font(); labelsfont.setBold(true); painter->drawText(Xright.x() - fm.width(m_axisXLabel) - 5, center.y() - 20, m_axisXLabel); painter->drawText(center.x() + 5, Ytop.y() + fm.height() + 15, m_axisYLabel); //write coords QString rightBound=QString::number(viewport.right()); int width=painter->fontMetrics().width(rightBound); painter->drawText(QPointF(3.+this->width()/2., 13. ), QString::number(viewport.top())); painter->drawText(QPointF(3.+this->width()/2., this->height()-5. ), QString::number(viewport.bottom())); painter->drawText(QPointF(8. , this->height()/2.-5.), QString::number(viewport.left())); painter->drawText(QPointF(this->width()-width, this->height()/2.-5.), rightBound); //EO write coords } const QString Plotter2D::computeAngleLabelByFrac(unsigned int n, unsigned int d) const { QString s; switch (m_angleMode) { case Radian: { s = (n==1) ? QString() : QString::number(n); s += PiSymbol; s += (d==1) ? QString() : '/'+QString::number(d); } break; case Degree: s = QString::number(radiansToDegrees(n*M_PI/d))+DegreeSymbol; break; case Gradian: s = QString::number(radiansToGradians(n*M_PI/d))+GradianSymbol; break; } return s; } const QString Plotter2D::computeAngleLabelByStep(unsigned int k, unsigned int step) const { QString s; switch (m_angleMode) { case Radian: { s = (k==1) ? ((step==1) ? QLatin1String("") : QString::number(step)) : QString::number(k*step); s += PiSymbol; } break; case Degree: s = QString::number(radiansToDegrees(k*step*M_PI))+DegreeSymbol; break; case Gradian: s = QString::number(radiansToGradians(k*step*M_PI))+GradianSymbol; break; } return s; } void Plotter2D::drawCartesianTickLabels(QPainter* painter, const Plotter2D::GridInfo& gridinfo, CartesianAxis axis) const { Q_ASSERT(axis == XAxis || axis == Analitza::YAxis); const bool isxaxis = (axis == XAxis); const double fontHeight = painter->fontMetrics().height(); const bool incbig = (M_PI <= gridinfo.inc); const unsigned int bigstep = std::floor(gridinfo.inc/M_PI); const unsigned int step = std::ceil(M_PI/gridinfo.inc); QString s; const int from = (axis == Analitza::XAxis) ? gridinfo.nxinilabels : gridinfo.nyinilabels; const int to = (axis == Analitza::XAxis) ? gridinfo.nxendlabels : gridinfo.nyendlabels; painter->save(); QFont tickFont = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); painter->setFont(tickFont); painter->setPen(QPen(QPalette().text().color())); for (int i = from; i <= to; ++i) { if (i == 0 || i%gridinfo.incLabelSkip!=0) continue; double newval = i*gridinfo.inc; const QPointF p = isxaxis ? toWidget(QPointF(newval, 0.0)) : toWidget(QPointF(0.0, newval)); switch (m_scaleMode) { case Linear: s = QString::number(newval); break; case Trigonometric: { s = (i < 0) ? QString(QLatin1Char('-')) : QString(); if (incbig) s += computeAngleLabelByStep(qAbs(i), bigstep); else { const QPair frac = simplifyFraction(qAbs(i), step); s += computeAngleLabelByFrac(frac.first, frac.second); } } break; } const int swidth = painter->fontMetrics().width(s); if (isxaxis) painter->drawText(p.x() - swidth/2, p.y()+fontHeight, s); else painter->drawText(p.x() - swidth - fontHeight/2, p.y()+fontHeight/2, s); } painter->restore(); } void Plotter2D::drawPolarTickLabels(QPainter* painter, const Plotter2D::GridInfo& gridinfo) const { unsigned int k = 1; painter->setPen(m_gridColor); //TODO if minor const double newinc = gridinfo.inc/(gridinfo.subinc); // inc with sub intervals //x // we assume 0 belongs to interval [gridinfo.xini, gridinfo.xend] double maxh = qMax(std::abs(gridinfo.xini), std::abs(gridinfo.xend)); int i = std::ceil(maxh/newinc)/2; double x = i*newinc; if (std::abs(gridinfo.xend) <= std::abs(gridinfo.xini)) x *= -1.0; // if 0 doesn't belongs to interval [gridinfo.xini, gridinfo.xend] if (((gridinfo.xend < 0.0) && (gridinfo.xini < 0.0)) || ((gridinfo.xend > 0.0) && (gridinfo.xini > 0.0))) { maxh = gridinfo.xend - gridinfo.xini; i = std::ceil(maxh/newinc)/2; x = gridinfo.xini + i*newinc; } //y // we assume 0 belongs to interval [gridinfo.yini, gridinfo.yend] maxh = qMax(std::abs(gridinfo.yini), std::abs(gridinfo.yend)); i = std::ceil(maxh/newinc)/2; double y = i*newinc; if (std::abs(gridinfo.yend) <= std::abs(gridinfo.yini)) y *= -1.0; // if 0 doesn't belongs to interval [gridinfo.xini, gridinfo.xend] if (((gridinfo.yend < 0.0) && (gridinfo.yini < 0.0)) || ((gridinfo.yend > 0.0) && (gridinfo.yini > 0.0))) { maxh = gridinfo.yend - gridinfo.yini; i = std::ceil(maxh/newinc)/2; y = gridinfo.yini + i*newinc; } const double r = qMax(std::abs(x), std::abs(y)); // radius const unsigned short axisxseparation = 16; // distance between tick text and x-axis const float axispolarseparation = axisxseparation*0.5; double h = Pi6; // pi/6, then 12 rounds double t = 0.0; unsigned int turns = 12; // 12 turns in [0, 2pi], since h=pi/6 // if we are far from origin then sudivide angles by pi/12 if (!viewport.contains(QPointF(0.0, 0.0))) { h = Pi12; turns = 24; // 24 turns in [0, 2pi], since h=pi/12 } k = 0; const unsigned int drawoverxaxiscount = turns/2; const unsigned int drawnextyaxiscount = turns/4; const unsigned int halfturns = drawoverxaxiscount; // or turns/2; for (uint j = 0; j < turns; ++j, ++k, t += h) // Pi6 then 24 turns { const QPair frac = simplifyFraction(k, halfturns); QString s = (k != 0) ? computeAngleLabelByFrac(frac.first, frac.second) : QStringLiteral("0"); QPointF p(r*std::cos(t), r*std::sin(t)); if (viewport.contains(p)) { if (k % drawoverxaxiscount == 0) // draw 0, pi over x painter->drawText(toWidget(p)+QPointF(0.0, -axispolarseparation), s); else if (k % drawnextyaxiscount == 0) // draw pi/2, 3pi/2 next to y painter->drawText(toWidget(p)+QPointF(axispolarseparation, 0.0), s); else painter->drawText(toWidget(p), s); } } } void Plotter2D::drawGridTickLabels(QPainter* painter, const GridInfo& gridinfo, GridStyle gridStyle) const { if (m_showTickLabels & Qt::Horizontal) drawCartesianTickLabels(painter, gridinfo, XAxis); if (m_showTickLabels & Qt::Vertical) drawCartesianTickLabels(painter, gridinfo, YAxis); if ((gridStyle == Circles) && m_showPolarAngles && m_showGrid) // draw labels for angles (polar axis) drawPolarTickLabels(painter, gridinfo); } void Plotter2D::drawCircles(QPainter* painter, const GridInfo& gridinfo, GridStyle gridStyle) const { Q_ASSERT(gridStyle == Analitza::Circles); painter->setRenderHint(QPainter::Antialiasing, false); const QPen textPen = QPen(QPalette().text().color()); const QPen gridPen(m_gridColor); const QPen subGridPen(computeSubGridColor()); const unsigned short nsubinc = gridinfo.subinc; // count for draw sub intervals const bool drawminor = m_showMinorGrid || m_showMinorTicks; const double inc = drawminor ? gridinfo.inc/nsubinc : gridinfo.inc; // if show minor, then inc with sub intervals if (m_showGrid) { const qreal until = qMax(qMax(qAbs(viewport.left()), qAbs(viewport.right())), qMax(qAbs(viewport.top()), qAbs(viewport.bottom())))*M_SQRT2; int k = 1; painter->setPen(gridPen); for (double i = inc; i < until; i += inc, ++k) { if (i == 0.0) continue; QPointF p(toWidget(QPointF(i,i))); QPointF p2(toWidget(QPointF(-i,-i))); QRectF er(p.x(),p.y(), p2.x()-p.x(), p2.y()-p.y()); if ((k % nsubinc == 0) || !drawminor) // intervals { painter->setPen(gridPen); painter->drawEllipse(er); } else if (m_showMinorGrid)// sub intervals { painter->setPen(subGridPen); painter->drawEllipse(er); } } if (m_showPolarAxis) // draw polar rays { double h = Pi12; // 2pi/(Pi/12) = 24 steps/turns const double r = std::abs(until); // radius const QPointF origin = toWidget(QPointF(0,0)); int k = 1; unsigned int alternatesubgridcount = 2; unsigned int dontdrawataxiscount = 5; unsigned int steps = 24; // or turns // if we are far from origin then sudivide angles by 5 degrees if (!viewport.contains(QPointF(0.0, 0.0))) { h = Pi36; // 2pi/(Pi/36) = 72 steps steps = 72; // or turns alternatesubgridcount = 3; dontdrawataxiscount = 17; } double t = h; for (uint j = 1; j <= steps; ++j, ++k, t += h) { if (j % alternatesubgridcount == 0) painter->setPen(gridPen); else if (m_showMinorGrid) { painter->setPen(subGridPen); QPointF p = toWidget(QPointF(r*std::cos(t), r*std::sin(t))); painter->drawLine(origin, p); if (k % dontdrawataxiscount == 0) // avoid draw the ray at 0,pi/2,pi,3pi/2 { t += h; ++j; } } } } } //BEGIN draw ticks if (m_showTickLabels & Qt::Horizontal) { painter->setPen(textPen); const QPointF yoffset(0.,-3.); for (int j = gridinfo.nxiniticks, i = 0; j < gridinfo.nxendticks; ++j, ++i) { if (j == 0) continue; const double x = j*inc; const QPointF p = toWidget(QPointF(x, 0.)); if ((i % nsubinc == 0) || !drawminor || m_showMinorTicks) painter->drawLine(p, p+yoffset); } } if (m_showTickLabels & Qt::Vertical) { painter->setPen(textPen); const QPointF xoffset(3.,0.); for (int j = gridinfo.nyiniticks, i = 0; j < gridinfo.nyendticks; ++j, ++i) { if (j == 0) continue; const double y = j*inc; const QPointF p = toWidget(QPointF(0., y)); if ((i % nsubinc == 0) || !(m_showMinorGrid || m_showMinorTicks) || m_showMinorTicks) painter->drawLine(p, p+xoffset); } } //END draw ticks } void Plotter2D::drawSquares(QPainter* painter, const GridInfo& gridinfo, GridStyle gridStyle) const { painter->setRenderHint(QPainter::Antialiasing, false); const QPen gridPen(m_gridColor); const QPen subGridPen(computeSubGridColor()); const QPen gridPenBold(gridPen.brush(), 2); const QPen subGridPenBold(subGridPen.brush(), gridPenBold.widthF()); const unsigned short nsubinc = gridinfo.subinc; // count for draw sub intervals const bool drawminor = m_showMinorGrid || m_showMinorTicks; const double inc = drawminor? gridinfo.inc/nsubinc : gridinfo.inc; // if show minor, then inc with sub intervals for (int n = gridinfo.nxiniticks, i = 0; n < gridinfo.nxendticks; ++n, ++i) { if (n == 0) continue; const double x = n*inc; const QPointF p = toWidget(QPointF(x, 0.)); if (m_showGrid && gridStyle == Crosses) { const double crossside = 5; const double centx = p.x(); for (int j = gridinfo.nyiniticks, k = 0; j < gridinfo.nyendticks; ++j, ++k) { if (j == 0) continue; const double y = j*inc; const QPointF py = toWidget(QPointF(0., y)); const double centy = py.y(); if (((i % nsubinc == 0) && (k % nsubinc == 0)) || !drawminor) // intervals { painter->setPen(gridPenBold); painter->drawLine(QPointF(centx-crossside, centy), QPointF(centx+crossside, centy)); // horizontal painter->drawLine(QPointF(centx, centy-crossside), QPointF(centx, centy+crossside)); // vertical } else if (m_showMinorGrid)// sub intervals { painter->setPen(subGridPenBold); painter->drawLine(QPointF(centx-crossside, centy), QPointF(centx+crossside, centy)); // horizontal painter->drawLine(QPointF(centx, centy-crossside), QPointF(centx, centy+crossside)); // vertical } } } if ((i % nsubinc == 0) || !drawminor) // intervals { if (m_showGrid && (gridStyle == Squares || gridStyle == VerticalLines)) { painter->setPen(gridPen); painter->drawLine(QPointF(p.x(), height()), QPointF(p.x(), 0.)); } if ((m_showTicks & Qt::Horizontal)) { painter->setPen(QPen(QPalette().text().color())); painter->drawLine(p, p+QPointF(0.,-3.)); } } else // sub intervals { if (m_showGrid && m_showMinorGrid && (gridStyle == Squares || gridStyle == VerticalLines)) { painter->setPen(subGridPen); painter->drawLine(QPointF(p.x(), height()), QPointF(p.x(), 0.)); } if ((m_showTicks & Qt::Horizontal) && m_showMinorTicks) { painter->setPen(QPen(QPalette().text().color())); painter->drawLine(p, p+QPointF(0.,-3.)); } } } for (int j = gridinfo.nyiniticks, i = 0; j < gridinfo.nyendticks; ++j, ++i) { if (j == 0) continue; const double y = j*inc; const QPointF p = toWidget(QPointF(0., y)); if ((i % nsubinc == 0) || !drawminor) // intervals { if (m_showGrid && (gridStyle == Squares || gridStyle == HorizontalLines)) { painter->setPen(gridPen); painter->drawLine(QPointF(0., p.y()), QPointF(width(), p.y())); } if ((m_showTicks & Qt::Horizontal)) { painter->setPen(QPen(QPalette().text().color())); painter->drawLine(p, p+QPointF(3.,0.)); } } else // sub intervals { if (m_showGrid && m_showMinorGrid && (gridStyle == Squares || gridStyle == HorizontalLines)) { painter->setPen(subGridPen); painter->drawLine(QPointF(0., p.y()), QPointF(width(), p.y())); } if ((m_showTicks & Qt::Horizontal) && m_showMinorTicks) { painter->setPen(QPen(QPalette().text().color())); painter->drawLine(p, p+QPointF(3.,0.)); } } } } PlotItem* Plotter2D::itemAt(int row) const { if (!d->m_model) return nullptr; QModelIndex pi = d->m_model->index(row, 0); if (!pi.isValid()) return nullptr; PlotItem* plot = pi.data(PlotsModel::PlotRole).value(); if (plot->spaceDimension() != Dim2D) return nullptr; return plot; } void Plotter2D::drawFunctions(QPaintDevice *qpd) { drawGrid(qpd); QPen pfunc(QColor(0,150,0), 2); QPainter p; p.begin(qpd); p.setPen(pfunc); if (!d->m_model || m_dirty) return; p.setRenderHint(QPainter::Antialiasing, true); const int current=currentFunction(); const int dpr = qMax(1, qRound(qpd->logicalDpiX()/100.)) << 1; for (int k = 0; k < d->m_model->rowCount(); ++k ) { PlaneCurve* curve = dynamic_cast(itemAt(k)); //NOTE from GSOC: not all valid plots always has points (so, we need to check if points().isEmpty() too) if (!curve || !curve->isVisible() || curve->points().isEmpty()) continue; pfunc.setColor(curve->color()); pfunc.setWidth((k==current)+dpr); pfunc.setStyle(Qt::SolidLine); p.setPen(pfunc); const QVector &vect=curve->points(); QVector jumps=curve->jumps(); unsigned int pointsCount = vect.count(); QPointF ultim = toWidget(vect.at(0)); int nextjump; nextjump = jumps.isEmpty() ? -1 : jumps.first(); if (!jumps.isEmpty()) jumps.remove(0); // #define DEBUG_GRAPH 1 #ifdef DEBUG_GRAPH qDebug() << "---------" << jumps.count()+1; #endif for(unsigned int j=0; j0) ultim.setY(qpd->height()); } // QPointF act2(act); if(qIsInf(act2.y())) { if(ultim.y()<0) act2.setY(0); if(ultim.y()>0) act2.setY(qpd->height()); } // qDebug() << "xxxxx" << act2 << ultim << qIsNaN(act2.y()) << qIsNaN(ultim.y()); p.drawLine(ultim, act2); #ifdef DEBUG_GRAPH QPen dpen(Qt::red); dpen.setWidth(3); p.setPen(dpen); p.drawPoint(ultim); p.setPen(pfunc); #endif } else if(nextjump==int(j)) { do { if(nextjump!=int(j)) p.drawPoint(act); nextjump = jumps.isEmpty() ? -1 : jumps.first(); if (!jumps.isEmpty()) jumps.remove(0); } while(!jumps.isEmpty() && jumps.first()==nextjump+1); #ifdef DEBUG_GRAPH qDebug() << "jumpiiiiiing" << ultim << toWidget(vect.at(j)); QPen dpen(Qt::blue); dpen.setWidth(2); p.setPen(dpen); p.drawLine(QLineF(QPointF(act.x(), height()/2-10), QPointF(act.x(), height()/2+10))); p.setPen(pfunc); #endif } ultim=act; } } p.end(); } void Plotter2D::updateFunctions(const QModelIndex & parent, int start, int end) { if (!d->m_model || parent.isValid()) return; QRectF viewportFixed = viewport; viewportFixed.setTopLeft(viewport.bottomLeft()); viewportFixed.setHeight(std::fabs(viewport.height())); for(int i=start; i<=end; i++) { PlaneCurve* curve = dynamic_cast(itemAt(i)); if (!curve || !curve->isVisible()) continue; curve->update(viewportFixed); } m_dirty = false; // m_dirty = false means that the we will not recalculate functions points forceRepaint(); } QPair Plotter2D::calcImage(const QPointF& ndp) const { if (!d->m_model || currentFunction() == -1) return QPair(); PlaneCurve* curve = dynamic_cast(itemAt(currentFunction())); if (curve && curve->isVisible()) return curve->image(ndp); return QPair(); } QRectF Plotter2D::normalizeUserViewport(const QRectF &uvp) { QRectF normalizeduvp = uvp; rang_x = width()/normalizeduvp.width(); rang_y = height()/normalizeduvp.height(); if (m_keepRatio && rang_x != rang_y) { rang_y=rang_x=qMin(std::fabs(rang_x), std::fabs(rang_y)); if(rang_y>0.) rang_y=-rang_y; if(rang_x<0.) rang_x=-rang_x; double newW=width()/rang_x, newH=height()/rang_x; double mx=(uvp.width()-newW)/2.; double my=(uvp.height()-newH)/2.; normalizeduvp.setLeft(uvp.left()+mx); normalizeduvp.setTop(uvp.bottom()-my); normalizeduvp.setWidth(newW); normalizeduvp.setHeight(-newH); //WARNING why negative distance? //Commented because precision could make the program crash // Q_ASSERT(uvp.center() == viewport.center()); } return normalizeduvp; } void Plotter2D::updateScale(bool repaint) { viewport = normalizeUserViewport(userViewport); if (repaint) { if (d->m_model && d->m_model->rowCount()>0) updateFunctions(QModelIndex(), 0, d->m_model->rowCount()-1); else forceRepaint(); } } void Plotter2D::setViewport(const QRectF& vp, bool repaint) { userViewport = vp; Q_ASSERT(userViewport.top()>userViewport.bottom()); Q_ASSERT(userViewport.right()>userViewport.left()); updateScale(repaint); viewportChanged(); } QLineF Plotter2D::slope(const QPointF& dp) const { if (!d->m_model || currentFunction() == -1) return QLineF(); PlaneCurve* plot = dynamic_cast(itemAt(currentFunction())); if (plot && plot->isVisible()) { QLineF ret = plot->tangent(dp); if (ret.isNull() && currentFunction()>=0) { QPointF a = calcImage(dp-QPointF(.1,.1)).first; QPointF b = calcImage(dp+QPointF(.1,.1)).first; ret = slopeToLine((a.y()-b.y())/(a.x()-b.x())); } return ret; } return QLineF(); } QLineF Plotter2D::toWidget(const QLineF &f) const { return QLineF(toWidget(f.p1()), toWidget(f.p2())); } QPointF Plotter2D::toWidget(const QPointF& p) const { double left=-viewport.left(), top=-viewport.top(); return QPointF((left + p.x()) * rang_x / d->m_dpr, (top + p.y()) * rang_y / d->m_dpr); } QPointF Plotter2D::fromWidget(const QPoint& p) const { double negativePartX = -viewport.left(); double negativePartY = -viewport.top(); return QPointF(p.x()/(rang_x*d->m_dpr)-negativePartX, p.y()/(rang_y*d->m_dpr)-negativePartY); } QPointF Plotter2D::toViewport(const QPoint &mv) const { return QPointF(mv.x()/rang_x, mv.y()/rang_y); } void Plotter2D::moveViewport(const QPoint& delta) { QPointF rel = toViewport(delta); QRectF viewport = currentViewport(); viewport.moveLeft(viewport.left() - rel.x()); viewport.moveTop(viewport.top() - rel.y()); setViewport(viewport); } void Plotter2D::scaleViewport(qreal scale, const QPoint& center, bool repaint) { QPointF p = fromWidget(center); QSizeF ns = viewport.size()*scale; QRectF nv(viewport.topLeft(), ns); setViewport(nv, false); //NOTE here isn't necessary to repaint, thus false QPointF p2 = p-fromWidget(center); nv.translate(p2); setViewport(nv, repaint); } void Plotter2D::setKeepAspectRatio(bool ar) { m_keepRatio=ar; updateScale(true); } -void Plotter2D::setModel(QAbstractItemModel* f) +void Analitza::Plotter2D::setModel(QAbstractItemModel* f) +{ + d->setModel(f); +} + +void Plotter2DPrivate::setModel(QAbstractItemModel* f) { - if(d->m_model == f) + if(m_model == f) return; - d->m_model=f; - modelChanged(); - if (d->m_model) - updateFunctions({}, 0, d->m_model->rowCount()); - else - forceRepaint(); + if (m_model) { + disconnect(m_model, &QAbstractItemModel::dataChanged, this, &Plotter2DPrivate::updateFuncs); + disconnect(m_model, &QAbstractItemModel::rowsInserted, this, &Plotter2DPrivate::addFuncs); + disconnect(m_model, &QAbstractItemModel::rowsRemoved, this, &Plotter2DPrivate::forceRepaint); + } + + m_model = f; + + if (m_model) { + connect(m_model, &QAbstractItemModel::dataChanged, this, &Plotter2DPrivate::updateFuncs); + connect(m_model, &QAbstractItemModel::rowsInserted, this, &Plotter2DPrivate::addFuncs); + connect(m_model, &QAbstractItemModel::rowsRemoved, this, &Plotter2DPrivate::forceRepaint); + + q->updateFunctions({}, 0, m_model->rowCount()); + } else { + q->forceRepaint(); + } } void Analitza::Plotter2D::setDevicePixelRatio(qreal dpr) { d->m_dpr = dpr; } void Plotter2D::setPaintedSize(const QSize& size) { m_size=size; updateScale(true); } void Plotter2D::setXAxisLabel(const QString &label) { m_axisXLabel = label; forceRepaint(); } void Plotter2D::setYAxisLabel(const QString &label) { m_axisYLabel = label; forceRepaint(); } void Plotter2D::zoomIn(bool repaint) { scaleViewport(ZoomInFactor, QPoint(m_size.width()*0.5, m_size.height()*0.5), repaint); } void Plotter2D::zoomOut(bool repaint) { scaleViewport(ZoomOutFactor, QPoint(m_size.width()*0.5, m_size.height()*0.5), repaint); } void Plotter2D::setShowMinorGrid(bool mt) { m_showMinorGrid=mt; forceRepaint(); } void Plotter2D::setShowGrid(bool show) { if (m_showGrid != show) { m_showGrid=show; forceRepaint(); showGridChanged(); } } QAbstractItemModel* Plotter2D::model() const { return d->m_model; } diff --git a/analitzaplot/plotter2d.h b/analitzaplot/plotter2d.h index e2583d19..3af71878 100644 --- a/analitzaplot/plotter2d.h +++ b/analitzaplot/plotter2d.h @@ -1,234 +1,236 @@ /************************************************************************************* * Copyright (C) 2011 by Aleix Pol * * Copyright (C) 2012-2013 by Percy Camilo T. Aucahuasi * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #ifndef PLOTTER2D_H #define PLOTTER2D_H #include #include #include #include #include #include "analitzaplotexport.h" #include class QAbstractItemModel; class QPainter; class QPaintDevice; class QModelIndex; namespace Analitza { class PlotItem; class Plotter2DPrivate; /** * \class Plotter2D * * \ingroup AnalitzaPlotModule * * \brief Render 2D plots. * * This class uses QPainter as backend for drawing plots. * The default value of showGrid is true. * The default grid color is Qt::lightGray. * The default background color is Qt::white. * The default value of autoGridStyle is true. */ class ANALITZAPLOT_EXPORT Plotter2D { private: // private structs struct GridInfo; // interval structure for carry current grid state information across interval methods public: explicit Plotter2D(const QSizeF& size); virtual ~Plotter2D(); /** Sets whether we will draw the grid. */ void setShowGrid(bool show); //only works if showgrid is true. for polar grid it affects to subdivision of angles/rays void setShowMinorGrid(bool mt); /** Returns whether we have chosen to draw the grid. */ bool showGrid() const {return m_showGrid; } /** Returns whether we have chosen to draw the minor grid. */ bool showMinorGrid() const {return m_showMinorGrid; } void setGridColor(const QColor &color) { m_gridColor = color; forceRepaint(); } //default Qt::lightGray QColor gridColor() const { return m_gridColor; } void setBackgroundColor(const QColor &color) { m_backgroundColor = color; forceRepaint(); } // default Qt::white QColor backgroundColor() const { return m_backgroundColor; } /** If true then we ignore the grid style suggested by setGridStyleHint, if false then we use as grid style the hint. */ void setAutoGridStyle(bool autogs) { m_autoGridStyle = autogs; forceRepaint(); } /** Returns whether we will change automatically the grid style based on the curent plot. */ bool autoGridStyle() const { return m_autoGridStyle; } /** Sets the suggested grid style. Only works if autoGridStyle is false. Note that we only accept CoordinateSystem::Cartesian or CoordinateSystem::Polar. */ void setGridStyleHint(GridStyle suggestedgs); /** Sets whether it has to keep the aspect ratio (1:1 grid). */ void setKeepAspectRatio(bool ar); /** Sets whether it is keeping the aspect ratio (1:1 grid). */ bool keepAspectRatio() const { return m_keepRatio; } /** Force the functions from @p start to @p end to be recalculated. */ void updateFunctions(const QModelIndex & parent, int start, int end); void setModel(QAbstractItemModel* f); QAbstractItemModel* model() const; /** Sets the graph's viewport to @p v. */ void setViewport(const QRectF& vp, bool repaint=true); //TODO doc //normlized current viewport, that includes scale information QRectF currentViewport() const { return viewport; } //DEPRECATED QRectF lastViewport() const { return currentViewport(); } /** Moves the viewport @p delta */ void moveViewport(const QPoint& delta); void setXAxisLabel(const QString &label); void setYAxisLabel(const QString &label); //by default linear void setScaleMode(ScaleMode sm) { m_scaleMode = sm; forceRepaint(); } ScaleMode scaleMode() const { return m_scaleMode; } //default radiasn, this will afecto when scalemode is trig and for the angles in polargridmode void setAngleMode(AngleMode am) { m_angleMode = am; forceRepaint(); } AngleMode angleMode() const { return m_angleMode; } void setShowTicks(Qt::Orientations o) { m_showTicks = o; forceRepaint(); } void setShowTickLabels(Qt::Orientations o) { m_showTickLabels = o; forceRepaint(); } //only works if showticks is true void setShowMinorTicks(bool mt) { m_showMinorTicks=mt; forceRepaint(); } bool minorTicksShown() const { return m_showMinorTicks; } //these 2 only work when gridmode is polar, showpolar axis aumenta cuando esta lejos de origin void setShowPolarAxis(bool pt) { m_showPolarAxis = pt; forceRepaint(); } void setShowPolarAngles(bool pt) { m_showPolarAngles = pt; forceRepaint(); } void setShowAxes(Qt::Orientations o) { m_showAxes = o; forceRepaint(); } Qt::Orientations ticksShown() const { return m_showTickLabels; } /** Zooms in to the Viewport center */ void zoomIn(bool repaint=true); /** Zooms out taken ref center too*/ void zoomOut(bool repaint=true); protected: virtual void drawGrid(QPaintDevice *qpd); virtual void drawFunctions(QPaintDevice *qpd); virtual void forceRepaint() = 0; virtual void viewportChanged() = 0; virtual int currentFunction() const = 0; virtual void modelChanged() = 0; virtual void showGridChanged() = 0; protected: // utils QRectF lastUserViewport() const { return userViewport; } QRectF normalizeUserViewport(const QRectF &uvp); // from userViewport to viewport, this one uses current scale information void updateScale(bool repaint); QPointF toWidget(const QPointF &) const; QPointF fromWidget(const QPoint& p) const; QPointF toViewport(const QPoint& mv) const; QPair calcImage(const QPointF& ndp) const; QLineF slope(const QPointF& dp) const; QLineF toWidget(const QLineF &) const; void setPaintedSize(const QSize& size); void setDevicePixelRatio(qreal dpr); void scaleViewport(qreal scale, const QPoint& center, bool repaint=true); private: void drawAxes(QPainter* painter, GridStyle a) const; void drawCircles(QPainter* painter, const GridInfo& gridinfo, GridStyle gridStyle) const; void drawSquares(QPainter* painter, const GridInfo& gridinfo, GridStyle gridStyle) const; void drawMainAxes(QPainter* painter) const; void drawCartesianTickLabels(QPainter* painter, const GridInfo& gridinfo, CartesianAxis axis) const; void drawPolarTickLabels(QPainter* painter, const GridInfo& gridinfo) const; void drawGridTickLabels(QPainter* painter, const GridInfo& gridinfo, GridStyle gridStyle) const; PlotItem *itemAt(int row) const; int width() const { return m_size.width(); } int height() const { return m_size.height(); } + const GridInfo getGridInfo() const; // calculate correct grid params const QColor computeSubGridColor() const; const QString computeAngleLabelByFrac(unsigned int n, unsigned int d) const; // input npi/d return angle in m_angleMode const QString computeAngleLabelByStep(unsigned int k, unsigned int step) const; // input n*step*pi return angle in m_angleMode bool m_showGrid; bool m_showMinorGrid; QColor m_gridColor; QColor m_backgroundColor; bool m_autoGridStyle; GridStyle m_gridStyleHint; //TODO set move auto tick labels double rang_x, rang_y; bool m_keepRatio; bool m_dirty; // or m_updated; como ahora contamos con setmodel, es necesario que se actualicen los datos antes de pintar, es necesario que no sea dirty QRectF viewport; // normalized viewport (with scale information), this one is the current viewport (used in currentViewport) QRectF userViewport; // raw viewport that user sets by setViewport, so we need to normalized userViewport into viewport to include scale and aspect radio information QSizeF m_size; + friend class Plotter2DPrivate; Plotter2DPrivate* const d; AngleMode m_angleMode; ScaleMode m_scaleMode; Qt::Orientations m_showTicks; Qt::Orientations m_showTickLabels; bool m_showMinorTicks; Qt::Orientations m_showAxes; bool m_showPolarAxis; bool m_showPolarAngles; QString m_axisXLabel; QString m_axisYLabel; private: // constants static const QColor m_axeColor; static const QColor m_derivativeColor; static const QString PiSymbol; static const QString DegreeSymbol; static const QString GradianSymbol; static const double Pi6; // pi/6 static const double Pi12; // pi/12 static const double Pi36; // pi/36 static const double ZoomInFactor; static const double ZoomOutFactor; }; } #endif // PLOTTER2D_H diff --git a/analitzaplot/plotter3d_es.cpp b/analitzaplot/plotter3d_es.cpp index 67597dd3..30825952 100644 --- a/analitzaplot/plotter3d_es.cpp +++ b/analitzaplot/plotter3d_es.cpp @@ -1,531 +1,532 @@ /************************************************************************************* * Copyright (C) 2012 by Percy Camilo T. Aucahuasi * * Copyright (C) 2007 by Abderrahman Taha: Basic OpenGL calls like scene, lights * * and mouse behaviour taken from K3DSurf * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "plotter3d_es.h" #include "surface.h" #include "spacecurve.h" #include "plotsmodel.h" #include "private/utils/mathutils.h" #include "private/export3d.h" #include #include #include #include #include #include #include #include #if defined(HAVE_IEEEFP_H) #include // bool isinf(double x) { return !finite(x) && x==x; } #endif using namespace std; using namespace Analitza; const GLubyte Plotter3DES::XAxisArrowColor[] = {250 -1, 1, 1}; const GLubyte Plotter3DES::YAxisArrowColor[] = {1, 255 - 1, 1}; const GLubyte Plotter3DES::ZAxisArrowColor[] = {1, 1, 255 - 1}; static QOpenGLBuffer createSurfaceBuffer(Surface* surf) { Q_ASSERT(QOpenGLContext::currentContext()); QOpenGLBuffer buffer(QOpenGLBuffer::VertexBuffer); buffer.create(); buffer.bind(); const auto offsetNormal = 3*sizeof(float)*surf->vertices().size(); const auto offsetEnd = offsetNormal + 3*sizeof(float)*surf->normals().size(); buffer.allocate(surf->vertices().constData(), offsetEnd); buffer.write(offsetNormal, surf->normals().constData(), 3*sizeof(float)*surf->normals().size()); Q_ASSERT(buffer.isCreated()); buffer.release(); return buffer; } Plotter3DES::Plotter3DES(QAbstractItemModel* model) : m_model(model) , m_plotStyle(Solid) , m_plottingFocusPolicy(All) , m_depth(-5) , m_scale(60) , m_currentAxisIndicator(InvalidAxis) , m_simpleRotation(false) , m_referencePlaneColor(Qt::darkGray) { resetViewPrivate(QVector3D(-45, 0, -135)); } Plotter3DES::~Plotter3DES() { for (int i = 0; i < m_itemGeometries.size(); ++i) { m_itemGeometries.take(itemAt(i)).destroy(); } // glDeleteLists(m_sceneObjects.value(RefPlaneXY), 1); // glDeleteLists(m_sceneObjects.value(XArrowAxisHint), 1); // glDeleteLists(m_sceneObjects.value(YArrowAxisHint), 1); // glDeleteLists(m_sceneObjects.value(ZArrowAxisHint), 1); } void Plotter3DES::initGL() { initializeOpenGLFunctions(); //TODO: provide GLSL version program.addShaderFromSourceCode(QOpenGLShader::Vertex, "attribute highp vec4 vertex;\n" "attribute highp vec4 normal;\n" "uniform highp mat4 matrix;\n" "void main(void)\n" "{\n" " gl_Position = matrix * vertex;\n" "}" ); program.addShaderFromSourceCode(QOpenGLShader::Fragment, "uniform mediump vec4 color;\n" "void main(void)\n" "{\n" " float w = 10.*gl_FragCoord.w;\n" " highp vec4 zvec = vec4(w, w, w, 1.0);" " gl_FragColor = mix(color, zvec, vec4(.5,.5,.5,1.));\n" "}" ); program.link(); if (m_model && m_model->rowCount() > 0) { updatePlots(QModelIndex(), 0, m_model->rowCount()-1); } } void Plotter3DES::resetViewport() { m_rot.setToIdentity(); resetViewPrivate(QVector3D(-45, 0, -135)); renderGL(); } void Plotter3DES::resetViewPrivate(const QVector3D& rot) { m_rot.translate(0,0, -20); m_rot.rotate(rot.x(), 1, 0, 0); m_rot.rotate(rot.y(), 0, 1, 0); m_rot.rotate(rot.z(), 0, 0, 1); m_simpleRotationVector = rot; } void Plotter3DES::setViewport(const QRectF& vp) { m_viewport = vp; m_projection.setToIdentity(); m_projection.perspective(m_scale, m_viewport.width()/m_viewport.height(), 0.1, 3000); renderGL(); } void Plotter3DES::drawPlots() { glClearColor(0, 0, 0, 1); glEnable(GL_DEPTH_TEST); glDepthMask(true); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if(!m_model) { return; } program.bind(); program.setUniformValue("matrix", m_projection * m_rot); const int vertexLocation = program.attributeLocation("vertex"); const int normalLocation = program.attributeLocation("normal"); GLenum mode; switch (m_plotStyle) { case Solid: mode = GL_TRIANGLES; break; case Wired: mode = GL_LINES; break; case Dots: mode = GL_POINTS; break; } drawAxes(); drawRefPlane(); program.enableAttributeArray(vertexLocation); for (int i = 0, c = m_model->rowCount(); i < c; ++i) { PlotItem *item = itemAt(i); if (!item || item->spaceDimension() != Dim3D || !item->isVisible()) continue; program.setUniformValue("color", item->color()); if (SpaceCurve *curv = dynamic_cast(item)) { //TODO: implement jumps program.setAttributeArray(vertexLocation, GL_FLOAT, curv->points().constData(), 3); glDrawArrays(GL_LINE_STRIP, 0, curv->points().size()); } else if (Surface *surf = dynamic_cast(item)) { QOpenGLBuffer it; if(!m_itemGeometries.contains(surf)) { m_itemGeometries[surf] = it = createSurfaceBuffer(surf); } else { it = m_itemGeometries[surf]; } Q_ASSERT(it.isCreated()); program.enableAttributeArray(normalLocation); it.bind(); const auto offsetNormal = 3*sizeof(float)*surf->vertices().size(); program.setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3); program.setAttributeBuffer(normalLocation, GL_FLOAT, offsetNormal, 3); glDrawElements(mode, surf->indexes().size(), GL_UNSIGNED_INT, surf->indexes().constData()); if (mode == GL_TRIANGLES) { program.setUniformValue("color", QColor(Qt::black)); glDrawElements(GL_POINTS, surf->indexes().size(), GL_UNSIGNED_INT, surf->indexes().constData()); } it.release(); program.disableAttributeArray(normalLocation); } } program.disableAttributeArray(vertexLocation); program.release(); } void Plotter3DES::exportSurfaces(const QString& path) const { Export3D::exportX3D(path, m_model); } void Plotter3DES::updatePlots(const QModelIndex & parent, int s, int e) { Q_ASSERT(!parent.isValid()); Q_UNUSED(parent); for(int i=s; i<=e; i++) { PlotItem *item = itemAt(i); if (!item) return; m_itemGeometries.take(item).destroy(); if (item->isVisible()) { // addFuncs(QModelIndex(), s.row(), s.row()); //igual no usar addFuncs sino la funcion interna pues no actualiza los items si tienen data addPlots(item); } } const int count = m_model->rowCount(); if (count <= e) { for (int i = e; i < count; ++i) { m_itemGeometries.take(itemAt(i)).destroy(); } } renderGL(); } void Plotter3DES::setModel(QAbstractItemModel* f) { m_model = f; if (m_model) updatePlots(QModelIndex(), 0, m_model->rowCount()-1); modelChanged(); } void Plotter3DES::setPlottingFocusPolicy(PlottingFocusPolicy fp) { m_plottingFocusPolicy = fp; for (int i = 0; i < m_itemGeometries.size(); ++i) { m_itemGeometries.take(itemAt(i)).destroy(); } updatePlots(QModelIndex(), 0, m_model->rowCount()-1); } void Plotter3DES::scale(qreal factor) { m_scale = qBound(1., factor*m_scale, 140.); setViewport(m_viewport); } void Plotter3DES::setUseSimpleRotation(bool simplerot) { m_simpleRotation = simplerot; } void Plotter3DES::rotate(int dx, int dy) { const qreal ax = -dy; const qreal ay = -dx; const double angle = sqrt(ax*ax + ay*ay)/(m_viewport.width() + 1)*360.0; if (m_simpleRotation) { m_rot.setToIdentity(); resetViewPrivate(m_simpleRotationVector + QVector3D(ax, 0, ay)); renderGL(); } else if (!m_rotFixed.isNull()) { m_rot.rotate(angle, m_rotFixed.normalized()); renderGL(); } else { // TODO: figure out how to do this on an opengl es compatible way // GLfloat matrix[16] = {0}; // model view matrix from current OpenGL state // // glGetFloatv(GL_MODELVIEW_MATRIX, matrix); // // QMatrix4x4 matrix4(matrix, 4, 4); // bool couldInvert; // matrix4 = matrix4.inverted(&couldInvert); // // if (couldInvert) { // QVector3D rot(matrix4.row(0).x()*ax + matrix4.row(1).x()*ay, // matrix4.row(0).y()*ax + matrix4.row(1).y()*ay, // matrix4.row(0).z()*ax + matrix4.row(1).z()*ay); // // m_rot.rotate(rot.length(), rot.normalized()); // // renderGL(); // } } } CartesianAxis Plotter3DES::selectAxisArrow(int x, int y) { GLint viewport[4]; GLubyte pixel[3]; glGetIntegerv(GL_VIEWPORT,viewport); glReadPixels(x, viewport[3]-y, 1, 1, GL_RGB,GL_UNSIGNED_BYTE,(void *)pixel); if (memcmp(pixel, XAxisArrowColor, sizeof(pixel)) == 0) return XAxis; if (memcmp(pixel, YAxisArrowColor, sizeof(pixel)) == 0) return YAxis; if (memcmp(pixel, ZAxisArrowColor, sizeof(pixel)) == 0) return ZAxis; return InvalidAxis; } void Plotter3DES::fixRotation(const QVector3D& vec) { m_rotFixed = vec; } void Plotter3DES::showAxisArrowHint(CartesianAxis axis) { if (axis == InvalidAxis) return ; m_currentAxisIndicator = axis; renderGL(); } void Plotter3DES::hideAxisHint() { m_currentAxisIndicator = InvalidAxis; renderGL(); } void Plotter3DES::addPlots(PlotItem* item) { Q_ASSERT(item); if (SpaceCurve *curve = dynamic_cast(item)) { if (curve->points().isEmpty()) curve->update(QVector3D(), QVector3D()); } else if (Surface* surf = dynamic_cast(item)) { if (surf->indexes().isEmpty()) surf->update(QVector3D(), QVector3D()); Q_ASSERT(!surf->indexes().isEmpty()); } } PlotItem* Plotter3DES::itemAt(int row) const { QModelIndex pi = m_model->index(row, 0); if (!pi.isValid()) return nullptr; PlotItem* plot = pi.data(PlotsModel::PlotRole).value(); if (plot->spaceDimension() != Dim3D) return nullptr; return plot; } void Plotter3DES::drawAxes() { glLineWidth(1.5f); static GLfloat const xVertices[] = { 0.f, 0.f, 0.f, 10.f, 0.f, 0.f, }; static GLfloat const yVertices[] = { 0.f, 0.f, 0.f, 0.f, 10.f, 0.f, }; static GLfloat const zVertices[] = { 0.f, 0.f, 0.f, 0.f, 0.f, 10.f, }; static QVector const idxs = {0,1}; const int vertexLocation = program.attributeLocation("vertex"); program.enableAttributeArray(vertexLocation); program.setUniformValue("color", QColor(Qt::red)); program.setAttributeArray(vertexLocation, xVertices, 3); glDrawElements(GL_LINES, idxs.size(), GL_UNSIGNED_INT, idxs.constData()); static GLfloat const XArrowVertices[] = { 10.f, 0.f, 0.f, 9.8f, 0.1f, 0.f, 9.8f, 0.f, 0.1f, 9.8f, -0.1f, 0.f, 9.8f, 0.f, -0.1f, 9.8f, 0.1f, 0.f }; program.setAttributeArray(vertexLocation, XArrowVertices, 3); glDrawArrays(GL_TRIANGLE_FAN, 0, 6); program.setUniformValue("color", QColor(Qt::green)); program.setAttributeArray(vertexLocation, yVertices, 3); glDrawElements(GL_LINES, idxs.size(), GL_UNSIGNED_INT, idxs.constData()); static GLfloat const YArrowVertices[] = { 0.f, 10.f, 0.f, 0.1f, 9.8f, 0.f, 0.f, 9.8f, 0.1f, -0.1f, 9.8f, 0.f, 0.f, 9.8f,-0.1f, 0.1f, 9.8f, 0.f }; program.setAttributeArray(vertexLocation, YArrowVertices, 3); glDrawArrays(GL_TRIANGLE_FAN, 0, 6); program.setUniformValue("color", QColor(Qt::blue)); program.setAttributeArray(vertexLocation, zVertices, 3); glDrawElements(GL_LINES, idxs.size(), GL_UNSIGNED_INT, idxs.constData()); static GLfloat const ZArrowVertices[] = { 0.f, 0.f, 10.f, 0.1f, 0.f, 9.8f, 0.f, 0.1f, 9.8f, -0.1f, 0.f, 9.8f, 0.f,-0.1f, 9.8f, 0.1f, 0.f, 9.8f }; program.setAttributeArray(vertexLocation, ZArrowVertices, 3); glDrawArrays(GL_TRIANGLE_FAN, 0, 6); program.disableAttributeArray(vertexLocation); } void Plotter3DES::drawRefPlane() { glLineWidth(1.f); const float lims = 10; QVector vxs; for(float x=-lims; x<=lims; ++x) { vxs += { x, -lims, m_depth }; vxs += { x, lims, m_depth }; } for(float y=-lims; y<=lims; ++y) { vxs += { -lims, y, m_depth }; vxs += { lims, y, m_depth }; } const int vertexLocation = program.attributeLocation("vertex"); program.enableAttributeArray(vertexLocation); program.setUniformValue("color", m_referencePlaneColor); program.setAttributeArray(vertexLocation, GL_FLOAT, vxs.constData(), 3); glDrawArrays(GL_LINES, 0, vxs.size()); program.disableAttributeArray(vertexLocation); } bool Plotter3DES::save(const QUrl& url) { if (!url.isLocalFile()) return false; const QString path = url.toLocalFile(); if(path.endsWith(QLatin1String(".x3d")) || path.endsWith(QLatin1String(".stl"))) { exportSurfaces(path); } else if(path.endsWith(QLatin1String(".pdf"))) { auto px = grabImage(); +//this pulls widgets QPrinter printer; printer.setOutputFormat(QPrinter::PdfFormat); printer.setOutputFileName(path); printer.setPaperSize(px.size(), QPrinter::DevicePixel); printer.setPageMargins(0,0,0,0, QPrinter::DevicePixel); QPainter painter; painter.begin(&printer); painter.drawImage(QPoint(0,0), px); painter.end(); } else { auto px = grabImage(); return px.save(path); } return true; } QStringList Plotter3DES::filters() const { return {QObject::tr("PNG Image (*.png)"), QObject::tr("PDF Document (*.pdf)"), QObject::tr("X3D Document (*.x3d)"), QObject::tr("STL Document (*.stl)")}; } diff --git a/analitzaplot/private/functiongraphfactory.cpp b/analitzaplot/private/functiongraphfactory.cpp index 036f8cb2..c672e1fb 100644 --- a/analitzaplot/private/functiongraphfactory.cpp +++ b/analitzaplot/private/functiongraphfactory.cpp @@ -1,155 +1,154 @@ /************************************************************************************* * Copyright (C) 2007-2009 by Aleix Pol * * Copyright (C) 2010-2012 by Percy Camilo T. Aucahuasi * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "functiongraphfactory.h" #include "abstractfunctiongraph.h" #include #include #include using namespace Analitza; FunctionGraphFactory* FunctionGraphFactory::m_self=nullptr; QString FunctionGraphFactory::typeName(const QString& id) const { return QCoreApplication::tr(typeNameFunctions[id]); } Analitza::ExpressionType FunctionGraphFactory::expressionType(const QString& id) const { return expressionTypeFunctions[id](); } Dimension FunctionGraphFactory::spaceDimension(const QString& id) const { return spaceDimensions[id]; } CoordinateSystem FunctionGraphFactory::coordinateSystem(const QString& id) const { return coordinateSystemFunctions[id]; } QString FunctionGraphFactory::iconName(const QString& id) const { return iconNameFunctions[id]; } QStringList FunctionGraphFactory::examples(const QString& id) const { return examplesFunctions[id](); } QStringList FunctionGraphFactory::examples(Dimension dim) const { QStringList ret; QStringList ids = spaceDimensions.keys(dim); foreach(const QString& id, ids) { ret += examplesFunctions[id](); } return ret; } FunctionGraphFactory* FunctionGraphFactory::self() { if(!m_self) m_self=new FunctionGraphFactory; return m_self; } bool FunctionGraphFactory::registerFunctionGraph(Dimension dim, PlotItemConstuctor constructor, BuilderFunctionWithVars builderFunctionWithVars, const char* typeNameFunction, ExpressionTypeFunction expressionTypeFunction, CoordinateSystem coordinateSystemFunction, const QStringList& _arguments, const QString& iconNameFunction, ExamplesFunction examplesFunction) { QStringList arguments(_arguments); qSort(arguments); - + + //TODO: turn this id into an internal struct QString id = QString::number((int)dim)+"|"+ QString::number((int)coordinateSystemFunction)+"|"+ arguments.join(QStringLiteral(",")); - - Q_ASSERT(!contains(id)); // verificar que no se registren los mismos tipos typeNameFunctions[id] = typeNameFunction; expressionTypeFunctions[id] = expressionTypeFunction; spaceDimensions[id] = dim; coordinateSystemFunctions[id] = coordinateSystemFunction; argumentsFunctions[id] = arguments; iconNameFunctions[id] = iconNameFunction; examplesFunctions[id] = examplesFunction; builderFunctionsWithVars[id] = builderFunctionWithVars; plotConstructor[id] = constructor; return true; } QString FunctionGraphFactory::trait(const Analitza::Expression& expr, const Analitza::ExpressionType& t, Dimension dim) const { Q_ASSERT(!expr.isEquation()); QStringList args = expr.bvarList(); qSort(args); QString key; for (int i = 0; i < argumentsFunctions.values().size() && key.isEmpty(); ++i) { // qDebug() << "---" << args << dim << t.toString() << " || " << argumentsFunctions.values()[i] << spaceDimensions.values()[i] << expressionTypeFunctions.values()[i]().toString(); if (args == argumentsFunctions.values()[i] && dim == spaceDimensions.values()[i] && t.canReduceTo(expressionTypeFunctions.values()[i]())) { key = typeNameFunctions.key(typeNameFunctions.values()[i]); } } return key; } bool FunctionGraphFactory::contains(const QString& id) const { return builderFunctionsWithVars.contains(id); } AbstractFunctionGraph* FunctionGraphFactory::build(const QString& id, const Analitza::Expression& exp, const QSharedPointer& v) const { Q_ASSERT(builderFunctionsWithVars.contains(id)); AbstractFunctionGraph* ret = builderFunctionsWithVars.value(id)(exp, v); Q_ASSERT(ret); ret->setInternalId(id); return ret; } FunctionGraph* FunctionGraphFactory::buildItem(const QString& id, const Analitza::Expression& exp, const QSharedPointer& v) const { return plotConstructor[id](build(id, exp, v)); } QMap< QString, QPair< QStringList, Analitza::ExpressionType > > FunctionGraphFactory::registeredFunctionGraphs() const { QMap< QString, QPair< QStringList, Analitza::ExpressionType > > ret; for (int i = 0; i < typeNameFunctions.values().size(); ++i) ret[typeNameFunctions.values()[i]] = qMakePair( argumentsFunctions.values()[i], expressionTypeFunctions.values()[i]()); return ret; } diff --git a/analitzawidgets/plotsview2d.cpp b/analitzawidgets/plotsview2d.cpp index 3382a849..b1aca23b 100644 --- a/analitzawidgets/plotsview2d.cpp +++ b/analitzawidgets/plotsview2d.cpp @@ -1,409 +1,366 @@ /************************************************************************************* * Copyright (C) 2007-2008 by Aleix Pol * * Copyright (C) 2012-2013 by Percy Camilo T. Aucahuasi * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "plotsview2d.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Analitza; PlotsView2D::PlotsView2D(QWidget *parent) : QWidget(parent) , Plotter2D(size()) , valid(false) , mode(None) , m_framed(false) , m_readonly(false) , m_selection(nullptr) - , m_currentModel(nullptr) { this->setFocusPolicy(Qt::ClickFocus); this->setCursor(Qt::CrossCursor); this->setMouseTracking(!m_readonly); this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); this->setMinimumSize(128, 128); //BEGIN setup plotter2d grid defViewport = QRectF(QPointF(-10.0, 10.0), QSizeF(20.0, -20.0)); resetViewport(); //colors QColor bgcolor = palette().color(QPalette::Base); setBackgroundColor(bgcolor); QColor gridcolor = palette().color(QPalette::Midlight); setGridColor(gridcolor); //END this->setAutoFillBackground(false); } PlotsView2D::~PlotsView2D() {} void PlotsView2D::paintEvent(QPaintEvent * ) { if (!valid) { if (buffer.isNull() || buffer.size()!=size()) buffer = QPixmap(size()); buffer.fill(backgroundColor()); drawFunctions(&buffer); valid=true; } QPainter p(this); p.drawPixmap(QRect(QPoint(0,0), size()), buffer); QPen ccursor; // finestra.setRenderHint(QPainter::Antialiasing, true); //NOTE GSOC 2012 better guards : model()->rowCount() > 0 && !mark.isNull() ... si no se cumplen que no dibuje las lineas rojas if(!m_readonly && mode==None) { //QPointF ultim = toWidget(mark); QPointF ultim(cursorPos); //Draw derivative if (!mark.isNull()) { ultim = toWidget(mark); // si no es nulo apunto al lugar del punto de pendiente ccursor.setColor(palette().color(QPalette::Active, QPalette::Link)); ccursor.setStyle(Qt::SolidLine); QLineF sl=slope(mark); sl.translate(mark); p.setPen(ccursor); p.setRenderHint(QPainter::Antialiasing, true); #ifndef Q_CC_MSVC if(!sl.isNull() && !std::isnan(sl.length())) #else if(!sl.isNull() && !_isnan(sl.length())) #endif p.drawLine(toWidget(sl)); p.setRenderHint(QPainter::Antialiasing, false); } //EOderivative ccursor.setColor(QColor(0xc0,0,0)); ccursor.setStyle(Qt::SolidLine); p.setPen(ccursor); p.drawLine(QPointF(0.,ultim.y()), QPointF(size().width(), ultim.y())); p.drawLine(QPointF(ultim.x(),0.), QPointF(ultim.x(), size().height())); int w=p.fontMetrics().width(m_posText)+15, h=p.fontMetrics().height(); if(ultim.x()+w > size().width()) ultim.setX(size().width()-w); if(ultim.y()+h > size().height()) ultim.setY(size().height()-h); if(ultim.y() < 0.) ultim.setY(0.); p.setPen(QPen(Qt::black)); p.drawText(QPointF(ultim.x()+15., ultim.y()+15.), m_posText); } else if(!m_readonly && mode==Selection) { //NOTE GSOC 2012 accessibility code, the selection follow system rules :) // ccursor.setColor(QColor(0xc0,0,0)); QColor selcol = QPalette().highlight().color(); ccursor.setColor(QPalette().highlightedText().color()); ccursor.setStyle(Qt::SolidLine); p.setPen(ccursor); // p.setBrush(QColor(0xff,0xff, 0,0x90)); selcol.setAlpha(0x90); p.setBrush(selcol); p.drawRect(QRect(press,last)); } if(m_framed) { QPen bord(Qt::black); p.setPen(bord); p.drawRect(QRect(QPoint(0,0), size()-QSize(2,2))); } p.end(); // qDebug() << "xxx2 " << viewport; } void PlotsView2D::wheelEvent(QWheelEvent *e) { QRectF viewport=currentViewport(); int steps = e->delta()/(8*15); qreal d = (-0.03*steps) + 1; if(d>0 || (viewport.width()+d > 2 && viewport.height()+d < 2)) scaleViewport(d, e->pos()); } void PlotsView2D::mousePressEvent(QMouseEvent *e) { if(!m_readonly && (e->button()==Qt::LeftButton || e->button()==Qt::MidButton)) { last = press = e->pos(); ant = toViewport(e->pos()); this->setCursor(QCursor(Qt::PointingHandCursor)); if(e->button()==Qt::MidButton || (e->button()==Qt::LeftButton && e->modifiers()&Qt::ControlModifier)) mode=Pan; else if(e->button()==Qt::LeftButton) mode=Selection; } } void PlotsView2D::mouseReleaseEvent(QMouseEvent *e) { if(m_readonly) this->setCursor(Qt::ArrowCursor); else this->setCursor(Qt::CrossCursor); if(!m_readonly && mode==Selection) { QPointF pd = toViewport(e->pos())-toViewport(press); QPoint pd2 = e->pos()-press; QRect rr(press, QSize(pd2.x(), pd2.y())); if(rr.width()>20 && rr.height()>20) { //if selection is not very small QRectF r(fromWidget(press), QSizeF(pd.x(), pd.y())); if(r.top()repaint(); } void PlotsView2D::mouseMoveEvent(QMouseEvent *e) { cursorPos = e->pos(); QPair img=calcImage(fromWidget(e->pos())); mark=img.first; if(!m_readonly && mode==Pan && ant != toViewport(e->pos())){ moveViewport(e->pos() - press); press = e->pos(); } else if(e->buttons()&Qt::LeftButton) { last = e->pos(); } else if(e->buttons()==0) { if(img.second.isEmpty()) { mark = fromWidget(e->pos()); sendStatus(QCoreApplication::tr("x=%1 y=%2").arg(mark.x()).arg(mark.y())); } else sendStatus(img.second); } this->repaint(); } void PlotsView2D::keyPressEvent(QKeyEvent * e) { //TODO use grid info (inc) here, made a public method to return current scale increment const double xstep=currentViewport().width()/12., ystep=currentViewport().height()/10.; switch(e->key()) { case Qt::Key_Right: setViewport(lastUserViewport().translated(xstep, 0)); break; case Qt::Key_Left: setViewport(lastUserViewport().translated(-xstep, 0)); break; case Qt::Key_Down: setViewport(lastUserViewport().translated(0, -ystep)); break; case Qt::Key_Up: setViewport(lastUserViewport().translated(0, ystep)); break; case Qt::Key_Minus: zoomOut(); break; case Qt::Key_Plus: zoomIn(); break; default: return; } } void PlotsView2D::resizeEvent(QResizeEvent * ev) { if (ev->size() != buffer.size()) { buffer = QPixmap(ev->size()); setPaintedSize(ev->size()); } } bool PlotsView2D::toImage(const QString &path, Format f) { Q_ASSERT(!path.isEmpty()); bool b=false; switch(f) { case SVG: { QFile f(path); QSvgGenerator gen; gen.setOutputDevice(&f); gen.setSize(this->size()); drawFunctions(&gen); b=true; forceRepaint(); } break; case PNG: this->repaint(); b=buffer.save(path, "PNG"); break; } return b; } void PlotsView2D::snapshotToClipboard() { QClipboard *cb = QApplication::clipboard(); cb->setImage(buffer.toImage()); } -void PlotsView2D::addFuncs(const QModelIndex & parent, int start, int end) +void Analitza::PlotsView2D::modelChanged() { - Q_ASSERT(!parent.isValid()); - updateFunctions(parent, start, end); -} - -void PlotsView2D::removeFuncs(const QModelIndex &, int, int) -{ - forceRepaint(); -} - -void PlotsView2D::updateFuncs(const QModelIndex & parent, int start, int end) -{ - updateFunctions(parent, start, end); -} - -void PlotsView2D::updateFuncs(const QModelIndex& start, const QModelIndex& end) -{ - updateFuncs(QModelIndex(), start.row(), end.row()); } void PlotsView2D::setReadOnly(bool ro) { m_readonly=ro; setCursor(ro ? Qt::ArrowCursor : Qt::CrossCursor); setMouseTracking(!ro); } QRectF PlotsView2D::definedViewport() const { return lastUserViewport(); } void PlotsView2D::viewportChanged() { QRectF userViewport=lastUserViewport(), viewport=currentViewport(); sendStatus(QStringLiteral("(%1, %2)-(%3, %4)") .arg(viewport.left()).arg(viewport.top()).arg(viewport.right()).arg(viewport.bottom())); emit viewportChanged(userViewport); } int PlotsView2D::currentFunction() const { if (!model()) return -1; int ret=-1; if(m_selection) { ret=m_selection->currentIndex().row(); } return ret; } -void PlotsView2D::modelChanged() -{ - if (m_currentModel == model()) - return ; - - if (m_currentModel) - { - disconnect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateFuncs(QModelIndex,QModelIndex))); - disconnect(model(), &QAbstractItemModel::rowsInserted, this, &PlotsView2D::addFuncs); - disconnect(model(), &QAbstractItemModel::rowsRemoved, this, &PlotsView2D::removeFuncs); - - //WARNING should we disconnect selection too? if so then must be documented -// if (m_selection) -// disconnect(m_selection,SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(forceRepaint())); -// -// m_selection = 0; - } - - m_currentModel = model(); - - connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateFuncs(QModelIndex,QModelIndex))); - connect(model(), &QAbstractItemModel::rowsInserted, this, &PlotsView2D::addFuncs); - connect(model(), &QAbstractItemModel::rowsRemoved, this, &PlotsView2D::removeFuncs); -} - void PlotsView2D::setSelectionModel(QItemSelectionModel* selection) { Q_ASSERT(selection->model() == model()); if (m_selection) disconnect(m_selection,SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(forceRepaint())); m_selection = selection; connect(m_selection,SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(forceRepaint())); } diff --git a/analitzawidgets/plotsview2d.h b/analitzawidgets/plotsview2d.h index 43eb4a85..e6ee9115 100644 --- a/analitzawidgets/plotsview2d.h +++ b/analitzawidgets/plotsview2d.h @@ -1,181 +1,177 @@ /************************************************************************************* * Copyright (C) 2007-2008 by Aleix Pol * * Copyright (C) 2012-2013 by Percy Camilo T. Aucahuasi * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #ifndef PLOTSVIEW2D_H #define PLOTSVIEW2D_H #include #include #include #include #include #include #include #include #include #include #include "analitzawidgets_export.h" #include class QItemSelectionModel; namespace Analitza { /** * \class PlotsView2D * * \ingroup AnalitzaGUIModule * * \brief Widget that allows visualization of 2D plots. * * This class lets you create a widget that can draw multiple 2D graphs. This widget * use Plotter2D as a backend. */ class ANALITZAWIDGETS_EXPORT PlotsView2D : public QWidget, public Plotter2D { Q_OBJECT Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid) /** The default grid color is a soft mix between KColorScheme::NormalBackground and KColorScheme::NormalText (foreground) of QPalette::Active. **/ Q_PROPERTY(QColor gridColor READ gridColor WRITE setGridColor) /** The default background color is KColorScheme::NormalBackground of QPalette::Active. **/ Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor) Q_PROPERTY(bool autoGridStyle READ autoGridStyle WRITE setAutoGridStyle) public: enum Format { PNG, SVG }; /** Constructor. Constructs a new Graph2D. */ explicit PlotsView2D(QWidget* parent = nullptr); ~PlotsView2D(); QSize sizeHint() const override { return QSize(100, 100); } /** Saves the graphs to a file located at @p path. */ bool toImage(const QString& path, Format f); /** Returns the viewing port */ QRectF definedViewport() const; void setSelectionModel(QItemSelectionModel* selection); public Q_SLOTS: /** Marks the image as dirty and repaints everything. */ void forceRepaint() override { valid=false; repaint(); } /** Sets the viewport to a default viewport. */ void resetViewport() { setViewport(defViewport); } /** Returns whether it has a little border frame. */ bool isFramed() const { return m_framed; } /** Sets whether it has a little border frame. */ void setFramed(bool fr) { m_framed=fr; } /** Returns whether it is a read-only widget. */ bool isReadOnly() const { return m_readonly; } /** Sets whether it is a read-only widget. */ void setReadOnly(bool ro); void snapshotToClipboard(); //exposed from plotter2d as slots... void setXAxisLabel(const QString &label) { Plotter2D::setXAxisLabel(label); } void setYAxisLabel(const QString &label) { Plotter2D::setYAxisLabel(label); } void setGridColor(const QColor &color) { Plotter2D::setGridColor(color); } void setTicksShown(QFlags o) { Plotter2D::setShowTickLabels(o); } void setAxesShown(QFlags o) { Plotter2D::setShowAxes(o); } //TODO set bgcolor, setbgcolormode auto means that colo is chosses based in lumninosisty onf current bgprofiles /** Zooms in to the Viewport center */ virtual void zoomIn() { Plotter2D::zoomIn(true); } /** Zooms out */ virtual void zoomOut() { Plotter2D::zoomOut(true); } + + void modelChanged() override; private Q_SLOTS: - void updateFuncs(const QModelIndex & parent, int start, int end); //update al insertar itesm - void updateFuncs(const QModelIndex& start, const QModelIndex& end); //update al setdata - void addFuncs(const QModelIndex & parent, int start, int end); - void removeFuncs(const QModelIndex & parent, int start, int end); void changeViewport(const QRectF& vp) { setViewport(vp); } Q_SIGNALS: /** Emits a status when it changes. */ void status(const QString &msg); void viewportChanged(const QRectF&); void showGridChanged() override; private: //TODO setviewmodemosusemovemode, pan /** The graph mode will especify the selection mode we are using at the moment */ enum GraphMode { None=0, /**< Normal behaviour */ Pan, /**< Panning, translates the viewport. */ Selection /**< There is a rectangle delimiting a region, for zooming. */ }; private: virtual void viewportChanged() override; virtual int currentFunction() const override; - virtual void modelChanged() override; //painting QPixmap buffer; bool valid; QPointF mark; QPoint cursorPos; //events void paintEvent( QPaintEvent * ) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void keyPressEvent(QKeyEvent * e ) override; void wheelEvent(QWheelEvent *e) override; void resizeEvent(QResizeEvent *) override; GraphMode mode; QPoint press; QPoint last; //presentation QPointF ant; QRectF defViewport; void sendStatus(const QString& msg) { emit status(msg); } bool m_framed; bool m_readonly; QString m_posText; QItemSelectionModel* m_selection; - QAbstractItemModel *m_currentModel; // use this pointer to disconnect signals when change the model }; } #endif diff --git a/declarative/graph2dmobile.cpp b/declarative/graph2dmobile.cpp index 77f0bbb1..5f004a92 100644 --- a/declarative/graph2dmobile.cpp +++ b/declarative/graph2dmobile.cpp @@ -1,163 +1,150 @@ /************************************************************************************* * Copyright (C) 2011 by Aleix Pol * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "graph2dmobile.h" #include #include #include #include #include #include #include #include using namespace Analitza; Graph2DMobile::Graph2DMobile(QQuickItem* parent) : QQuickItem(parent), Plotter2D(boundingRect().size()) , m_dirty(true), m_currentFunction(-1) { setSize(QSizeF(100,100)); defViewport = QRectF(QPointF(-5., 5.), QSizeF(10., -10.)); resetViewport(); setFlags(QQuickItem::ItemHasContents); } void Graph2DMobile::paint() { if (!m_dirty) return; // qDebug() << "hellooo" << boundingRect(); const auto dpr = window()->effectiveDevicePixelRatio(); const QSize bounding = (boundingRect().size() * dpr).toSize(); if(bounding.isEmpty()) return; if(m_buffer.size()!=bounding) { m_buffer = QImage(bounding, QImage::Format_ARGB32); m_buffer.setDevicePixelRatio(dpr); setDevicePixelRatio(dpr); setPaintedSize(bounding); } Q_ASSERT(!m_buffer.isNull()); if(m_dirty) { m_buffer.fill(Qt::transparent); drawFunctions(&m_buffer); m_dirty=false; } } void Graph2DMobile::forceRepaint() { m_dirty=true; update(); } void Graph2DMobile::resetViewport() { setViewport(defViewport); } void Graph2DMobile::modelChanged() { - if (auto m = model()) { - connect(m, &QAbstractItemModel::dataChanged, - this, &Graph2DMobile::updateFuncs); - connect(m, &QAbstractItemModel::rowsInserted, - this, &Graph2DMobile::addFuncs); - connect(m, &QAbstractItemModel::rowsRemoved, - this, &Graph2DMobile::removeFuncs); - } Q_EMIT modelHasChanged(); } -void Graph2DMobile::addFuncs(const QModelIndex& parent, int start, int end) { updateFunctions(parent, start, end); } - -void Graph2DMobile::removeFuncs(const QModelIndex&, int, int) { forceRepaint(); } -void Graph2DMobile::updateFuncs(const QModelIndex& start, const QModelIndex& end) { updateFunctions(QModelIndex(), start.row(), end.row()); } - void Graph2DMobile::scale(qreal s, int x, int y) { QRectF userViewport = lastUserViewport(); if(s>1 || (userViewport.height() < -3. && userViewport.width() > 3.)) { scaleViewport(s, QPoint(x,y)); } } void Graph2DMobile::translate(qreal x, qreal y) { moveViewport(QPoint(x,y) * window()->effectiveDevicePixelRatio()); } QStringList Graph2DMobile::addFunction(const QString& expression, const QSharedPointer& vars) { PlotsModel* plotsmodel = qobject_cast(model()); if(!plotsmodel) qWarning() << "only can add plots to a PlotsModel instance"; else return plotsmodel->addFunction(expression, Dim2D, vars); return {}; } void Graph2DMobile::setTicksShownAtAll(bool shown) { Qt::Orientations show = shown ? Qt::Vertical|Qt::Horizontal : Qt::Orientations(nullptr); setShowTicks(show); setShowTickLabels(show); } void Graph2DMobile::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) { m_dirty = true; QQuickItem::geometryChanged(newGeometry, oldGeometry); } QSGNode* Graph2DMobile::updatePaintNode(QSGNode* node, QQuickItem::UpdatePaintNodeData* /*data*/) { if (!window()) { delete node; return nullptr; } QSGSimpleTextureNode *n = static_cast(node); if (!n) { n = new QSGSimpleTextureNode(); n->setOwnsTexture(true); } paint(); n->setTexture(window()->createTextureFromImage(m_buffer)); n->setRect(boundingRect()); return n; } QStringList Graph2DMobile::filters() const { return {QObject::tr("PNG Image (*.png)")}; } bool Graph2DMobile::save(const QUrl& url) const { if(!url.isLocalFile()) return false; return m_buffer.save(url.toLocalFile()); } diff --git a/declarative/graph2dmobile.h b/declarative/graph2dmobile.h index c7f8b8c1..f142de7b 100644 --- a/declarative/graph2dmobile.h +++ b/declarative/graph2dmobile.h @@ -1,87 +1,82 @@ /************************************************************************************* * Copyright (C) 2010 by Aleix Pol * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #ifndef GRAPH2DMOBILE_H #define GRAPH2DMOBILE_H #include #include #include namespace Analitza { class Variables; } class Graph2DMobile : public QQuickItem, public Analitza::Plotter2D { Q_OBJECT Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel NOTIFY modelHasChanged) Q_PROPERTY(QRectF viewport READ lastViewport WRITE setViewport RESET resetViewport) Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid NOTIFY showGridChanged) Q_PROPERTY(bool showMinorGrid READ showMinorGrid WRITE setShowMinorGrid) Q_PROPERTY(bool keepAspectRatio READ keepAspectRatio WRITE setKeepAspectRatio) Q_PROPERTY(bool currentFunction READ currentFunction WRITE setCurrentFunction) Q_PROPERTY(bool ticksShown READ ticksShownAtAll WRITE setTicksShownAtAll) Q_PROPERTY(bool minorTicksShown READ minorTicksShown WRITE setShowMinorTicks) Q_PROPERTY(QStringList filters READ filters CONSTANT) public: explicit Graph2DMobile(QQuickItem* parent = nullptr); virtual void forceRepaint() override; virtual void viewportChanged() override {} virtual void modelChanged() override; virtual int currentFunction() const override { return m_currentFunction; } void geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) override; QSGNode* updatePaintNode(QSGNode*, UpdatePaintNodeData*) override; void setCurrentFunction(int f) { m_currentFunction = f; } bool ticksShownAtAll() const { return ticksShown()!=0; } void setTicksShownAtAll(bool shown); QStringList filters() const; public Q_SLOTS: void translate(qreal x, qreal y); void scale(qreal s, int x, int y); void resetViewport(); QStringList addFunction(const QString& expression, const QSharedPointer& vars = {}); bool save(const QUrl &url) const; - private Q_SLOTS: - void updateFuncs(const QModelIndex& start, const QModelIndex& end); - void addFuncs(const QModelIndex& parent, int start, int end); - void removeFuncs(const QModelIndex& parent, int start, int end); - Q_SIGNALS: void showGridChanged() override; void modelHasChanged(); private: void paint(); bool m_dirty; int m_currentFunction; QImage m_buffer; QRectF defViewport; }; #endif // GRAPH2DMOBILE_H