void gXGrid::paint(gGraph & w,int left,int top, int width, int height) { int x,y; gVertexBuffer * stippled, * lines; EventDataType miny,maxy; if (w.zoomY()==0 && PROFILE.appearance->allowYAxisScaling()) { miny=w.physMinY(); maxy=w.physMaxY(); } else { miny=w.min_y; maxy=w.max_y; if (miny<0) { // even it up if it's starts negative miny=-MAX(fabs(miny),fabs(maxy)); } } w.roundY(miny,maxy); //EventDataType dy=maxy-miny; if (height<0) return; static QString fd="0"; GetTextExtent(fd,x,y); double max_yticks=round(height / (y+14.0)); // plus spacing between lines //double yt=1/max_yticks; double mxy=MAX(fabs(maxy),fabs(miny)); double mny=miny; if (miny<0) { mny=-mxy; } double rxy=mxy-mny; int myt; bool fnd=false; for (myt=max_yticks;myt>=1;myt--) { float v=rxy/float(myt); if (float(v)==int(v)) { fnd=true; break; } } if (fnd) max_yticks=myt; else { max_yticks=2; } double yt=1/max_yticks; double ymult=height/rxy; double min_ytick=rxy*yt; float ty,h; if (min_ytick<=0) { qDebug() << "min_ytick error in gXGrid::paint() in" << w.title(); return; } if (min_ytick>=1000000) { min_ytick=100; } stippled=w.backlines(); lines=w.backlines(); for (double i=miny; i<=maxy+min_ytick-0.00001; i+=min_ytick) { ty=(i - miny) * ymult; h=top+height-ty; if (m_show_major_lines && (i > miny)) { stippled->add(left,h,left+width,h,m_major_color.rgba()); } double z=(min_ytick/4)*ymult; double g=h; for (int i=0;i<3;i++) { g+=z; if (g>top+height) break; //if (vertcnt>=maxverts) { // qWarning() << "vertarray bounds exceeded in gYAxis for " << w.title() << "graph" << "MinY =" <<miny << "MaxY =" << maxy << "min_ytick=" <<min_ytick; // break; // } if (m_show_minor_lines) {// && (i > miny)) { stippled->add(left,g,left+width,g,m_minor_color.rgba()); } if (stippled->full()) { break; } } if (lines->full() || stippled->full()) { qWarning() << "vertarray bounds exceeded in gYAxis for " << w.title() << "graph" << "MinY =" <<miny << "MaxY =" << maxy << "min_ytick=" <<min_ytick; break; } } }
// Time Domain Line Chart void gLineChart::paint(gGraph & w,int left, int top, int width, int height) { if (!m_visible) return; if (!m_day) return; //if (!m_day->channelExists(m_code)) return; if (width<0) return; // lines=w.lines(); EventDataType miny,maxy; double minx,maxx; miny=w.min_y, maxy=w.max_y; if (w.blockZoom()) { minx=w.rmin_x, maxx=w.rmax_x; } else { maxx=w.max_x, minx=w.min_x; } // hmmm.. subtract_offset.. /*if (miny<0) { miny=-MAX(fabs(miny),fabs(maxy)); }*/ w.roundY(miny,maxy); double xx=maxx-minx; double xmult=double(width)/xx; EventDataType yy=maxy-miny; EventDataType ymult=EventDataType(height-3)/yy; // time to pixel conversion multiplier // Return on screwy min/max conditions if (xx<0) return; if (yy<=0) { if (miny==0) return; } EventDataType lastpx,lastpy; EventDataType px,py; int idx; bool done; double x0,xL; double sr; int sam; int minz,maxz; // Draw bounding box gVertexBuffer *outlines=w.lines(); GLuint blk=QColor(Qt::black).rgba(); outlines->add(left, top, left, top+height, blk); outlines->add(left, top+height, left+width,top+height, blk); outlines->add(left+width,top+height, left+width, top, blk); outlines->add(left+width, top, left,top, blk); width--; height-=2; int num_points=0; int visible_points=0; int total_points=0; int total_visible=0; bool square_plot,accel; qint64 clockdrift=qint64(PROFILE.cpap->clockDrift()) * 1000L; qint64 drift=0; QHash<ChannelID,QVector<EventList *> >::iterator ci; //m_line_color=schema::channel[m_code].defaultColor(); int legendx=left+width; int codepoints; //GLuint color; for (int gi=0;gi<m_codes.size();gi++) { ChannelID code=m_codes[gi]; //m_line_color=m_colors[gi]; lines->setColor(m_colors[gi]); //color=m_line_color.rgba(); codepoints=0; for (int svi=0;svi<m_day->size();svi++) { Session *sess=(*m_day)[svi]; if (!sess) { qWarning() << "gLineChart::Plot() NULL Session Record.. This should not happen"; continue; } drift=(sess->machine()->GetType()==MT_CPAP) ? clockdrift : 0; if (!sess->enabled()) continue; schema::Channel ch=schema::channel[code]; bool fndbetter=false; for (QList<schema::Channel *>::iterator l=ch.m_links.begin();l!=ch.m_links.end();l++) { schema::Channel *c=*l; ci=(*m_day)[svi]->eventlist.find(c->id()); if (ci!=(*m_day)[svi]->eventlist.end()) { fndbetter=true; break; } } if (!fndbetter) { ci=(*m_day)[svi]->eventlist.find(code); if (ci==(*m_day)[svi]->eventlist.end()) continue; } QVector<EventList *> & evec=ci.value(); num_points=0; for (int i=0;i<evec.size();i++) num_points+=evec[i]->count(); total_points+=num_points; codepoints+=num_points; const int num_averages=20; // Max n umber of samples taken from samples per pixel for better min/max values for (int n=0;n<evec.size();n++) { // for each segment EventList & el=*evec[n]; accel=(el.type()==EVL_Waveform); // Turn on acceleration if this is a waveform. if (accel) { sr=el.rate(); // Time distance between samples if (sr<=0) { qWarning() << "qLineChart::Plot() assert(sr>0)"; continue; } } if (m_disable_accel) accel=false; square_plot=m_square_plot; if (accel || num_points>20000) { // Don't square plot if too many points or waveform square_plot=false; } int siz=evec[n]->count(); if (siz<=1) continue; // Don't bother drawing 1 point or less. x0=el.time(0)+drift; xL=el.time(siz-1)+drift; if (maxx<x0) continue; if (xL<minx) continue; if (x0>xL) { if (siz==2) { // this happens on CPAP quint32 t=el.getTime()[0]; el.getTime()[0]=el.getTime()[1]; el.getTime()[1]=t; EventStoreType d=el.getData()[0]; el.getData()[0]=el.getData()[1]; el.getData()[1]=d; } else { qDebug() << "Reversed order sample fed to gLineChart - ignored."; continue; //assert(x1<x2); } } if (accel) { //x1=el.time(1); double XR=xx/sr; double Z1=MAX(x0,minx); double Z2=MIN(xL,maxx); double ZD=Z2-Z1; double ZR=ZD/sr; double ZQ=ZR/XR; double ZW=ZR/(width*ZQ); visible_points+=ZR*ZQ; if (accel && n>0) { sam=1; } if (ZW<num_averages) { sam=1; accel=false; } else { sam=ZW/num_averages; if (sam<1) { sam=1; accel=false; } } // Prepare the min max y values if we still are accelerating this plot if (accel) { for (int i=0;i<width;i++) { m_drawlist[i].setX(height); m_drawlist[i].setY(0); } minz=width; maxz=0; } total_visible+=visible_points; } else { sam=1; } // these calculations over estimate // The Z? values are much more accurate idx=0; if (el.type()==EVL_Waveform) { // We can skip data previous to minx if this is a waveform if (minx>x0) { double j=minx-x0; // == starting min of first sample in this segment idx=(j/sr); //idx/=(sam*num_averages); //idx*=(sam*num_averages); // Loose the precision idx+=sam-(idx % sam); } // else just start from the beginning } int xst=left+1; int yst=top+height+1; double time; EventDataType data; EventDataType gain=el.gain(); //EventDataType nmult=ymult*gain; //EventDataType ymin=EventDataType(miny)/gain; //const QVector<EventStoreType> & dat=el.getData(); //const QVector<quint32> & tim=el.getTime(); //quint32 * tptr; //qint64 stime=el.first(); done=false; // if (!accel) { lines->setSize(1.5); // } else lines->setSize(1); if (el.type()==EVL_Waveform) { // Waveform Plot if (idx > sam) idx-=sam; time=el.time(idx) + drift; double rate=double(sr)*double(sam); EventStoreType * ptr=el.rawData()+idx; if (accel) { ////////////////////////////////////////////////////////////////// // Accelerated Waveform Plot ////////////////////////////////////////////////////////////////// // qint64 tmax=(maxx-time)/rate; // if ((tmax*sam) < siz) { // siz=idx+tmax*sam; // done=true; // } for (int i=idx;i<siz;i+=sam,ptr+=sam) { time+=rate; // This is much faster than QVector access. data=*ptr; data *= gain; // Scale the time scale X to pixel scale X px=((time - minx) * xmult); // Same for Y scale, with gain factored in nmult py=((data - miny) * ymult); // In accel mode, each pixel has a min/max Y value. // m_drawlist's index is the pixel index for the X pixel axis. int z=round(px); // Hmmm... round may screw this up. if (z<minz) minz=z; // minz=First pixel if (z>maxz) maxz=z; // maxz=Last pixel if (minz<0) { qDebug() << "gLineChart::Plot() minz<0 should never happen!! minz =" << minz; minz=0; } if (maxz>max_drawlist_size) { qDebug() << "gLineChart::Plot() maxz>max_drawlist_size!!!! maxz = " << maxz << " max_drawlist_size =" << max_drawlist_size; maxz=max_drawlist_size; } // Update the Y pixel bounds. if (py<m_drawlist[z].x()) m_drawlist[z].setX(py); if (py>m_drawlist[z].y()) m_drawlist[z].setY(py); if (time>maxx) { done=true; break; } } // Plot compressed accelerated vertex list if (maxz>width) { maxz=width; } float ax1,ay1; QPoint * drl=m_drawlist+minz; // Don't need to cap VertexBuffer here, as it's limited to max_drawlist_size anyway // Cap within VertexBuffer capacity, one vertex per line point int np=(maxz-minz)*2; int j=lines->Max()-lines->cnt(); if (np < j) { for (int i=minz;i<maxz;i++, drl++) { ax1=drl->x(); ay1=drl->y(); lines->unsafe_add(xst+i,yst-ax1,xst+i,yst-ay1); //if (lines->full()) break; } } else { qDebug() << "gLineChart full trying to draw" << schema::channel[code].label(); done=true; } } else { // Zoomed in Waveform ////////////////////////////////////////////////////////////////// // Normal Waveform Plot ////////////////////////////////////////////////////////////////// // Cap within VertexBuffer capacity, one vertex per line point // int np=((siz-idx)/sam)*2; // int j=lines->Max()-lines->cnt(); // if (np > j) { // siz=j*sam; // } // Prime first point data=*ptr * gain; lastpx=xst+((time - minx) * xmult); lastpy=yst-((data - miny) * ymult); for (int i=idx;i<siz;i+=sam) { ptr+=sam; time+=rate; data=*ptr * gain; px=xst+((time - minx) * xmult); // Scale the time scale X to pixel scale X py=yst-((data - miny) * ymult); // Same for Y scale, with precomputed gain //py=yst-((data - ymin) * nmult); // Same for Y scale, with precomputed gain lines->add(lastpx,lastpy,px,py); lastpx=px; lastpy=py; if (time>maxx) { done=true; break; } if (lines->full()) break; } } } else { ////////////////////////////////////////////////////////////////// // Standard events/zoomed in Plot ////////////////////////////////////////////////////////////////// double start=el.first() + drift; quint32 * tptr=el.rawTime(); int idx=0; if (siz>15) { for (;idx<siz;++idx) { time=start + *tptr++; if (time >= minx) { break; } } if (idx > 0) { idx--; //tptr--; } } // Step one backwards if possible (to draw through the left margin) EventStoreType * dptr=el.rawData() + idx; tptr=el.rawTime() + idx; time=start + *tptr++; data=*dptr++ * gain; idx++; lastpx=xst+((time - minx) * xmult); // Scale the time scale X to pixel scale X lastpy=yst-((data - miny) * ymult); // Same for Y scale without precomputed gain siz-=idx; // Check if would overflow lines gVertexBuffer int gs=siz << 1; int j=lines->Max()-lines->cnt(); if (square_plot) gs <<= 1; if (gs > j) { qDebug() << "Would overflow line points.. increase default VertexBuffer size in gLineChart"; siz=j >> square_plot ? 2 : 1; done=true; // end after this partial draw.. } // Unrolling square plot outside of loop to gain a minor speed improvement. EventStoreType *eptr=dptr+siz; if (square_plot) { for (; dptr < eptr; dptr++) { time=start + *tptr++; data=gain * *dptr; px=xst+((time - minx) * xmult); // Scale the time scale X to pixel scale X py=yst-((data - miny) * ymult); // Same for Y scale without precomputed gain // Horizontal lines are easy to cap if (py==lastpy) { // Cap px to left margin if (lastpx<xst) lastpx=xst; // Cap px to right margin if (px>xst+width) px=xst+width; lines->unsafe_add(lastpx,lastpy,px,lastpy,px,lastpy,px,py); } else { // Letting the scissor do the dirty work for non horizontal lines // This really should be changed, as it might be cause that weird // display glitch on Linux.. lines->unsafe_add(lastpx,lastpy,px,lastpy,px,lastpy,px,py); } lastpx=px; lastpy=py; if (time > maxx) { done=true; // Let this iteration finish.. (This point will be in far clipping) break; } } } else { for (; dptr < eptr; dptr++) { //for (int i=0;i<siz;i++) { time=start + *tptr++; data=gain * *dptr; px=xst+((time - minx) * xmult); // Scale the time scale X to pixel scale X py=yst-((data - miny) * ymult); // Same for Y scale without precomputed gain // Horizontal lines are easy to cap if (py==lastpy) { // Cap px to left margin if (lastpx<xst) lastpx=xst; // Cap px to right margin if (px>xst+width) px=xst+width; lines->unsafe_add(lastpx,lastpy,px,py); } else { // Letting the scissor do the dirty work for non horizontal lines // This really should be changed, as it might be cause that weird // display glitch on Linux.. lines->unsafe_add(lastpx,lastpy,px,py); } lastpx=px; lastpy=py; if (time > maxx) { // Past right edge, abort further drawing.. done=true; break; } } } } if (done) break; } } //////////////////////////////////////////////////////////////////// // Draw Legends on the top line //////////////////////////////////////////////////////////////////// if ((codepoints>0)) { //(m_codes.size()>1) && QString text=schema::channel[code].label(); int wid,hi; GetTextExtent(text,wid,hi); legendx-=wid; w.renderText(text,legendx,top-4); int bw=GetXHeight(); legendx-=bw/2; int tp=top-5-bw/2; w.quads()->add(legendx-bw,tp+bw/2,legendx,tp+bw/2,legendx,tp-bw/2,legendx-bw,tp-bw/2,m_colors[gi].rgba()); legendx-=hi+bw/2; } }
void gYAxis::paint(gGraph & w,int left,int top, int width, int height) { int x,y;//,yh=0; //Todo: clean this up as there is a lot of duplicate code between the sections if (0) {//w.graphView()->usePixmapCache()) { /* if (w.invalidate_yAxisImage) { if (!m_image.isNull()) { w.graphView()->deleteTexture(m_textureID); m_image=QImage(); } if (height<0) return; if (height>2000) return; int labelW=0; EventDataType miny=w.min_y; EventDataType maxy=w.max_y; if (miny<0) { // even it up if it's starts negative miny=-MAX(fabs(miny),fabs(maxy)); } w.roundY(miny,maxy); EventDataType dy=maxy-miny; static QString fd="0"; GetTextExtent(fd,x,y); yh=y; m_image=QImage(width,height+y+4,QImage::Format_ARGB32_Premultiplied); m_image.fill(Qt::transparent); QPainter paint(&m_image); double max_yticks=round(height / (y+14.0)); // plus spacing between lines double mxy=MAX(fabs(maxy),fabs(miny)); double mny=miny; if (miny<0) { mny=-mxy; } double rxy=mxy-mny; int myt; bool fnd=false; for (myt=max_yticks;myt>2;myt--) { float v=rxy/float(myt); if (v==int(v)) { fnd=true; break; } } if (fnd) max_yticks=myt; double yt=1/max_yticks; double ymult=height/rxy; double min_ytick=rxy*yt; float ty,h; if (min_ytick<=0) { qDebug() << "min_ytick error in gYAxis::paint() in" << w.title(); return; } if (min_ytick>=1000000) { min_ytick=100; } //lines=w.backlines(); for (double i=miny; i<=maxy+min_ytick-0.00001; i+=min_ytick) { ty=(i - miny) * ymult; if (dy<5) { fd=Format(i*m_yaxis_scale,2); } else { fd=Format(i*m_yaxis_scale,1); } GetTextExtent(fd,x,y); if (x>labelW) labelW=x; h=(height-2)-ty; h+=yh; #ifndef Q_OS_MAC // stupid pixel alignment rubbish, I really should be using floats.. h+=1; #endif if (h<0) continue; paint.setBrush(Qt::black); paint.drawText(width-8-x,h+y/2,fd); paint.setPen(m_line_color); paint.drawLine(width-4,h,width,h); double z=(min_ytick/4)*ymult; double g=h; for (int i=0;i<3;i++) { g+=z; if (g>height+yh) break; paint.drawLine(width-3,g,width,g); } } paint.end(); m_image=QGLWidget::convertToGLFormat(m_image); m_textureID=w.graphView()->bindTexture(m_image,GL_TEXTURE_2D,GL_RGBA,QGLContext::NoBindOption); w.invalidate_yAxisImage=false; } if (!m_image.isNull()) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_TEXTURE_2D); w.graphView()->drawTexture(QPoint(left,(top+height)-m_image.height()+5),m_textureID); glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); } */ } else { if (height<0) return; if (height>2000) return; int labelW=0; EventDataType miny; EventDataType maxy; if (w.zoomY()==0 && PROFILE.appearance->allowYAxisScaling()) { miny=w.physMinY(); maxy=w.physMaxY(); } else { miny=w.min_y; maxy=w.max_y; if (miny<0) { // even it up if it's starts negative miny=-MAX(fabs(miny),fabs(maxy)); } } w.roundY(miny,maxy); EventDataType dy=maxy-miny; static QString fd="0"; GetTextExtent(fd,x,y); double max_yticks=round(height / (y+14.0)); // plus spacing between lines double mxy=MAX(fabs(maxy),fabs(miny)); double mny=miny; if (miny<0) { mny=-mxy; } double rxy=mxy-mny; int myt; bool fnd=false; for (myt=max_yticks;myt>2;myt--) { float v=rxy/float(myt); if (v==int(v)) { fnd=true; break; } } if (fnd) max_yticks=myt; double yt=1/max_yticks; double ymult=height/rxy; double min_ytick=rxy*yt; //if (dy>5) { // min_ytick=round(min_ytick); //} else { //} float ty,h; if (min_ytick<=0) { qDebug() << "min_ytick error in gYAxis::paint() in" << w.title(); return; } if (min_ytick>=1000000) { min_ytick=100; } lines=w.backlines(); GLuint line_color=m_line_color.rgba(); for (double i=miny; i<=maxy+min_ytick-0.00001; i+=min_ytick) { ty=(i - miny) * ymult; if (dy<5) { fd=Format(i*m_yaxis_scale,2); } else { fd=Format(i*m_yaxis_scale,1); } GetTextExtent(fd,x,y); // performance bottleneck.. if (x>labelW) labelW=x; h=top+height-ty; if (h<top) continue; w.renderText(fd,left+width-8-x,(h+(y/2.0)),0,m_text_color,defaultfont); lines->add(left+width-4,h,left+width,h,line_color); double z=(min_ytick/4)*ymult; double g=h; for (int i=0;i<3;i++) { g+=z; if (g>top+height) break; lines->add(left+width-3,g,left+width,g,line_color); if (lines->full()) { qWarning() << "vertarray bounds exceeded in gYAxis for " << w.title() << "graph" << "MinY =" <<miny << "MaxY =" << maxy << "min_ytick=" <<min_ytick; break; } } if (lines->full()) { qWarning() << "vertarray bounds exceeded in gYAxis for " << w.title() << "graph" << "MinY =" <<miny << "MaxY =" << maxy << "min_ytick=" <<min_ytick; break; } } } }