///////////////////////////////////////////////////////////////////////////// // Name: mathplot.cpp // Purpose: Framework for plotting in wxWindows // Original Author: David Schalig // Maintainer: Davide Rondini // Contributors: Jose Luis Blanco, Val Greene, Maciej Suminski, Tomasz Wlostowski // Created: 21/07/2003 // Last edit: 25/08/2016 // Copyright: (c) David Schalig, Davide Rondini // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// #include // Comment out for release operation: // (Added by J.L.Blanco, Aug 2007) //#define MATHPLOT_DO_LOGGING #ifdef __BORLANDC__ #pragma hdrstop #endif #ifndef WX_PRECOMP #include "wx/object.h" #include "wx/font.h" #include "wx/colour.h" #include "wx/sizer.h" #include "wx/intl.h" #include "wx/dcclient.h" #include "wx/cursor.h" #include "gal/cursors.h" #endif #include #include #include #include #include // used only for debug #include // used for representation of x axes involving date #include // Memory leak debugging #ifdef _DEBUG #define new DEBUG_NEW #endif // Legend margins #define mpLEGEND_MARGIN 5 #define mpLEGEND_LINEWIDTH 10 // Number of pixels to scroll when scrolling by a line #define mpSCROLL_NUM_PIXELS_PER_LINE 10 // See doxygen comments. double mpWindow::zoomIncrementalFactor = 1.1; // ----------------------------------------------------------------------------- // mpLayer // ----------------------------------------------------------------------------- IMPLEMENT_ABSTRACT_CLASS( mpLayer, wxObject ) mpLayer::mpLayer() : m_type( mpLAYER_UNDEF ) { SetPen( (wxPen&) *wxBLACK_PEN ); SetFont( (wxFont&) *wxNORMAL_FONT ); m_continuous = false; // Default m_showName = true; // Default m_drawOutsideMargins = false; m_visible = true; } wxBitmap mpLayer::GetColourSquare( int side ) const { wxBitmap square( side, side, -1 ); wxColour filler = m_pen.GetColour(); wxBrush brush( filler, wxBRUSHSTYLE_SOLID ); wxMemoryDC dc; dc.SelectObject( square ); dc.SetBackground( brush ); dc.Clear(); dc.SelectObject( wxNullBitmap ); return square; } // ----------------------------------------------------------------------------- // mpInfoLayer // ----------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS( mpInfoLayer, mpLayer ) mpInfoLayer::mpInfoLayer() { m_dim = wxRect( 0, 0, 1, 1 ); m_brush = *wxTRANSPARENT_BRUSH; m_reference.x = 0; m_reference.y = 0; m_winX = 1; // parent->GetScrX(); m_winY = 1; // parent->GetScrY(); m_type = mpLAYER_INFO; } mpInfoLayer::mpInfoLayer( wxRect rect, const wxBrush* brush ) : m_dim( rect ) { m_brush = *brush; m_reference.x = rect.x; m_reference.y = rect.y; m_winX = 1; // parent->GetScrX(); m_winY = 1; // parent->GetScrY(); m_type = mpLAYER_INFO; } mpInfoLayer::~mpInfoLayer() { } void mpInfoLayer::UpdateInfo( mpWindow& w, wxEvent& event ) { } bool mpInfoLayer::Inside( const wxPoint& point ) const { return m_dim.Contains( point ); } void mpInfoLayer::Move( wxPoint delta ) { m_dim.SetX( m_reference.x + delta.x ); m_dim.SetY( m_reference.y + delta.y ); } void mpInfoLayer::UpdateReference() { m_reference.x = m_dim.x; m_reference.y = m_dim.y; } void mpInfoLayer::Plot( wxDC& dc, mpWindow& w ) { if( m_visible ) { // Adjust relative position inside the window int scrx = w.GetScrX(); int scry = w.GetScrY(); // Avoid dividing by 0 if( scrx == 0 ) scrx = 1; if( scry == 0 ) scry = 1; if( (m_winX != scrx) || (m_winY != scry) ) { if( m_winX > 1 ) m_dim.x = (int) floor( (double) (m_dim.x * scrx / m_winX) ); if( m_winY > 1 ) { m_dim.y = (int) floor( (double) (m_dim.y * scry / m_winY) ); UpdateReference(); } // Finally update window size m_winX = scrx; m_winY = scry; } dc.SetPen( m_pen ); // wxImage image0(wxT("pixel.png"), wxBITMAP_TYPE_PNG); // wxBitmap image1(image0); // wxBrush semiWhite(image1); dc.SetBrush( m_brush ); dc.DrawRectangle( m_dim.x, m_dim.y, m_dim.width, m_dim.height ); } } wxPoint mpInfoLayer::GetPosition() const { return m_dim.GetPosition(); } wxSize mpInfoLayer::GetSize() const { return m_dim.GetSize(); } mpInfoCoords::mpInfoCoords() : mpInfoLayer() { } mpInfoCoords::mpInfoCoords( wxRect rect, const wxBrush* brush ) : mpInfoLayer( rect, brush ) { } mpInfoCoords::~mpInfoCoords() { } void mpInfoCoords::UpdateInfo( mpWindow& w, wxEvent& event ) { if( event.GetEventType() == wxEVT_MOTION ) { /* It seems that Windows port of wxWidgets don't support multi-line test to be drawn in a wxDC. * wxGTK instead works perfectly with it. * Info on wxForum: http://wxforum.shadonet.com/viewtopic.php?t=3451&highlight=drawtext+eol */ #ifdef _WINDOWS // FIXME m_content.Printf(wxT("x = %f y = %f"), XScale().P2x(w, mouseX), YScale().P2x(w, mouseY)); #else // FIXME m_content.Printf(wxT("x = %f\ny = %f"), XScale().P2x(w, mouseX), YScale().P2x(w, mouseY)); #endif } } void mpInfoCoords::Plot( wxDC& dc, mpWindow& w ) { if( m_visible ) { // Adjust relative position inside the window int scrx = w.GetScrX(); int scry = w.GetScrY(); if( (m_winX != scrx) || (m_winY != scry) ) { if( m_winX > 1 ) m_dim.x = (int) floor( (double) (m_dim.x * scrx / m_winX) ); if( m_winY > 1 ) { m_dim.y = (int) floor( (double) (m_dim.y * scry / m_winY) ); UpdateReference(); } // Finally update window size m_winX = scrx; m_winY = scry; } dc.SetPen( m_pen ); // wxImage image0(wxT("pixel.png"), wxBITMAP_TYPE_PNG); // wxBitmap image1(image0); // wxBrush semiWhite(image1); dc.SetBrush( m_brush ); dc.SetFont( m_font ); int textX, textY; dc.GetTextExtent( m_content, &textX, &textY ); if( m_dim.width < textX + 10 ) m_dim.width = textX + 10; if( m_dim.height < textY + 10 ) m_dim.height = textY + 10; dc.DrawRectangle( m_dim.x, m_dim.y, m_dim.width, m_dim.height ); dc.DrawText( m_content, m_dim.x + 5, m_dim.y + 5 ); } } mpInfoLegend::mpInfoLegend() : mpInfoLayer() { } mpInfoLegend::mpInfoLegend( wxRect rect, const wxBrush* brush ) : mpInfoLayer( rect, brush ) { } mpInfoLegend::~mpInfoLegend() { } void mpInfoLegend::UpdateInfo( mpWindow& w, wxEvent& event ) { } void mpInfoLegend::Plot( wxDC& dc, mpWindow& w ) { if( m_visible ) { // Adjust relative position inside the window int scrx = w.GetScrX(); int scry = w.GetScrY(); if( m_winX != scrx || m_winY != scry ) { if( m_winX > 1 ) m_dim.x = (int) floor( (double) (m_dim.x * scrx / m_winX) ); if( m_winY > 1 ) { m_dim.y = (int) floor( (double) (m_dim.y * scry / m_winY) ); UpdateReference(); } // Finally update window size m_winX = scrx; m_winY = scry; } // wxImage image0(wxT("pixel.png"), wxBITMAP_TYPE_PNG); // wxBitmap image1(image0); // wxBrush semiWhite(image1); dc.SetBrush( m_brush ); dc.SetFont( m_font ); const int baseWidth = mpLEGEND_MARGIN * 2 + mpLEGEND_LINEWIDTH; int textX = baseWidth, textY = mpLEGEND_MARGIN; int plotCount = 0; int posY = 0; int tmpX = 0; int tmpY = 0; mpLayer* layer = nullptr; wxPen lpen; wxString label; for( unsigned int p = 0; p < w.CountAllLayers(); p++ ) { layer = w.GetLayer( p ); if( layer->GetLayerType() == mpLAYER_PLOT && layer->IsVisible() ) { label = layer->GetDisplayName(); dc.GetTextExtent( label, &tmpX, &tmpY ); textX = ( textX > tmpX + baseWidth ) ? textX : tmpX + baseWidth + mpLEGEND_MARGIN; textY += tmpY; } } dc.SetPen( m_pen ); dc.SetBrush( m_brush ); m_dim.width = textX; if( textY != mpLEGEND_MARGIN ) // Don't draw any thing if there are no visible layers { textY += mpLEGEND_MARGIN; m_dim.height = textY; dc.DrawRectangle( m_dim.x, m_dim.y, m_dim.width, m_dim.height ); for( unsigned int p2 = 0; p2 < w.CountAllLayers(); p2++ ) { layer = w.GetLayer( p2 ); if( layer->GetLayerType() == mpLAYER_PLOT && layer->IsVisible() ) { label = layer->GetDisplayName(); lpen = layer->GetPen(); dc.GetTextExtent( label, &tmpX, &tmpY ); dc.SetPen( lpen ); // textX = (textX > (tmpX + baseWidth)) ? textX : (tmpX + baseWidth); // textY += (tmpY + mpLEGEND_MARGIN); posY = m_dim.y + mpLEGEND_MARGIN + plotCount * tmpY + (tmpY >> 1); dc.DrawLine( m_dim.x + mpLEGEND_MARGIN, // X start coord posY, // Y start coord m_dim.x + mpLEGEND_LINEWIDTH + mpLEGEND_MARGIN, // X end coord posY ); // dc.DrawRectangle(m_dim.x + 5, m_dim.y + 5 + plotCount*tmpY, 5, 5); dc.DrawText( label, m_dim.x + baseWidth, m_dim.y + mpLEGEND_MARGIN + plotCount * tmpY ); plotCount++; } } } } } // ----------------------------------------------------------------------------- // mpLayer implementations - functions // ----------------------------------------------------------------------------- IMPLEMENT_ABSTRACT_CLASS( mpFX, mpLayer ) mpFX::mpFX( const wxString& name, int flags ) { SetName( name ); m_flags = flags; m_type = mpLAYER_PLOT; } void mpFX::Plot( wxDC& dc, mpWindow& w ) { if( m_visible ) { dc.SetPen( m_pen ); wxCoord startPx = m_drawOutsideMargins ? 0 : w.GetMarginLeft(); wxCoord endPx = m_drawOutsideMargins ? w.GetScrX() : w.GetScrX() - w.GetMarginRight(); wxCoord minYpx = m_drawOutsideMargins ? 0 : w.GetMarginTop(); wxCoord maxYpx = m_drawOutsideMargins ? w.GetScrY() : w.GetScrY() - w.GetMarginBottom(); wxCoord iy = 0; if( m_pen.GetWidth() <= 1 ) { for( wxCoord i = startPx; i < endPx; ++i ) { iy = w.y2p( GetY( w.p2x( i ) ) ); // Draw the point only if you can draw outside margins or if the point is inside margins if( m_drawOutsideMargins || ( (iy >= minYpx) && (iy <= maxYpx) ) ) dc.DrawPoint( i, iy ); // (wxCoord) ((w.GetPosY() - GetY( (double)i / w.GetScaleX() + w.GetPosX()) ) * w.GetScaleY())); } } else { for( wxCoord i = startPx; i < endPx; ++i ) { iy = w.y2p( GetY( w.p2x( i ) ) ); // Draw the point only if you can draw outside margins or if the point is inside margins if( m_drawOutsideMargins || ( (iy >= minYpx) && (iy <= maxYpx) ) ) dc.DrawLine( i, iy, i, iy ); // wxCoord c = YScale().X2p( GetY(XScale().P2x(i)) ); //(wxCoord) ((w.GetPosY() - GetY( (double)i / w.GetScaleX() + w.GetPosX()) ) * w.GetScaleY()); } } if( !m_name.IsEmpty() && m_showName ) { dc.SetFont( m_font ); wxCoord tx, ty; dc.GetTextExtent( m_name, &tx, &ty ); /*if ((m_flags & mpALIGNMASK) == mpALIGN_RIGHT) * tx = (w.GetScrX()>>1) - tx - 8; * else if ((m_flags & mpALIGNMASK) == mpALIGN_CENTER) * tx = -tx/2; * else * tx = -(w.GetScrX()>>1) + 8; */ if( (m_flags & mpALIGNMASK) == mpALIGN_RIGHT ) tx = (w.GetScrX() - tx) - w.GetMarginRight() - 8; else if( (m_flags & mpALIGNMASK) == mpALIGN_CENTER ) tx = ( (w.GetScrX() - w.GetMarginRight() - w.GetMarginLeft() - tx) / 2 ) + w.GetMarginLeft(); else tx = w.GetMarginLeft() + 8; dc.DrawText( m_name, tx, w.y2p( GetY( w.p2x( tx ) ) ) ); } } } IMPLEMENT_ABSTRACT_CLASS( mpFY, mpLayer ) mpFY::mpFY( const wxString& name, int flags ) { SetName( name ); m_flags = flags; m_type = mpLAYER_PLOT; } void mpFY::Plot( wxDC& dc, mpWindow& w ) { if( m_visible ) { dc.SetPen( m_pen ); wxCoord i, ix; wxCoord startPx = m_drawOutsideMargins ? 0 : w.GetMarginLeft(); wxCoord endPx = m_drawOutsideMargins ? w.GetScrX() : w.GetScrX() - w.GetMarginRight(); wxCoord minYpx = m_drawOutsideMargins ? 0 : w.GetMarginTop(); wxCoord maxYpx = m_drawOutsideMargins ? w.GetScrY() : w.GetScrY() - w.GetMarginBottom(); if( m_pen.GetWidth() <= 1 ) { for( i = minYpx; i < maxYpx; ++i ) { ix = w.x2p( GetX( w.p2y( i ) ) ); if( m_drawOutsideMargins || ( (ix >= startPx) && (ix <= endPx) ) ) dc.DrawPoint( ix, i ); } } else { for( i = 0; i< w.GetScrY(); ++i ) { ix = w.x2p( GetX( w.p2y( i ) ) ); if( m_drawOutsideMargins || ( (ix >= startPx) && (ix <= endPx) ) ) dc.DrawLine( ix, i, ix, i ); // wxCoord c = XScale().X2p(GetX(YScale().P2x(i))); //(wxCoord) ((GetX( (double)i / w.GetScaleY() + w.GetPosY()) - w.GetPosX()) * w.GetScaleX()); // dc.DrawLine(c, i, c, i); } } if( !m_name.IsEmpty() && m_showName ) { dc.SetFont( m_font ); wxCoord tx, ty; dc.GetTextExtent( m_name, &tx, &ty ); if( (m_flags & mpALIGNMASK) == mpALIGN_TOP ) ty = w.GetMarginTop() + 8; else if( (m_flags & mpALIGNMASK) == mpALIGN_CENTER ) ty = ( (w.GetScrY() - w.GetMarginTop() - w.GetMarginBottom() - ty) / 2 ) + w.GetMarginTop(); else ty = w.GetScrY() - 8 - ty - w.GetMarginBottom(); dc.DrawText( m_name, w.x2p( GetX( w.p2y( ty ) ) ), ty ); // (wxCoord) ((GetX( (double)i / w.GetScaleY() + w.GetPosY()) - w.GetPosX()) * w.GetScaleX()), -ty); } } } IMPLEMENT_ABSTRACT_CLASS( mpFXY, mpLayer ) mpFXY::mpFXY( const wxString& name, int flags ) { SetName( name ); m_flags = flags; m_type = mpLAYER_PLOT; m_scaleX = nullptr; m_scaleY = nullptr; // Avoid not initialized members: maxDrawX = minDrawX = maxDrawY = minDrawY = 0; } void mpFXY::UpdateViewBoundary( wxCoord xnew, wxCoord ynew ) { // Keep track of how many points have been drawn and the bounding box maxDrawX = (xnew > maxDrawX) ? xnew : maxDrawX; minDrawX = (xnew < minDrawX) ? xnew : minDrawX; maxDrawY = (maxDrawY > ynew) ? maxDrawY : ynew; minDrawY = (minDrawY < ynew) ? minDrawY : ynew; // drawnPoints++; } void mpFXY::Plot( wxDC& dc, mpWindow& w ) { // If trace doesn't have any data yet then it won't have any scale set. In any case, there's // nothing to plot. if( !GetCount() ) return; wxCHECK_RET( m_scaleX, wxS( "X scale was not set" ) ); wxCHECK_RET( m_scaleY, wxS( "Y scale was not set" ) ); if( !m_visible ) return; wxCoord startPx = m_drawOutsideMargins ? 0 : w.GetMarginLeft(); wxCoord endPx = m_drawOutsideMargins ? w.GetScrX() : w.GetScrX() - w.GetMarginRight(); wxCoord minYpx = m_drawOutsideMargins ? 0 : w.GetMarginTop(); wxCoord maxYpx = m_drawOutsideMargins ? w.GetScrY() : w.GetScrY() - w.GetMarginBottom(); // Check for a collapsed window before we try to allocate a negative number of points if( endPx <= startPx || minYpx >= maxYpx ) return; dc.SetPen( m_pen ); double x, y; // Do this to reset the counters to evaluate bounding box for label positioning Rewind(); GetNextXY( x, y ); maxDrawX = x; minDrawX = x; maxDrawY = y; minDrawY = y; // drawnPoints = 0; Rewind(); dc.SetClippingRegion( startPx, minYpx, endPx - startPx + 1, maxYpx - minYpx + 1 ); if( !m_continuous ) { bool first = true; wxCoord ix = 0; std::set ys; while( GetNextXY( x, y ) ) { double px = m_scaleX->TransformToPlot( x ); double py = m_scaleY->TransformToPlot( y ); wxCoord newX = w.x2p( px ); if( first ) { ix = newX; first = false; } if( newX == ix ) // continue until a new X coordinate is reached { // collect all unique points ys.insert( w.y2p( py ) ); continue; } for( auto& iy: ys ) { if( m_drawOutsideMargins || ( (ix >= startPx) && (ix <= endPx) && (iy >= minYpx) && (iy <= maxYpx) ) ) { // for some reason DrawPoint does not use the current pen, so we use // DrawLine for fat pens if( m_pen.GetWidth() <= 1 ) dc.DrawPoint( ix, iy ); else dc.DrawLine( ix, iy, ix, iy ); UpdateViewBoundary( ix, iy ); } } ys.clear(); ix = newX; ys.insert( w.y2p( py ) ); } } else { int count = 0; int x0=0; // X position of merged current vertical line int ymin0=0; // y min coord of merged current vertical line int ymax0=0; // y max coord of merged current vertical line int dupx0 = 0; // count of currently merged vertical lines wxPoint line_start; // starting point of the current line to draw // A buffer to store coordinates of lines to draw std::vectorpointList; pointList.reserve( endPx - startPx + 1 ); // Note: we can use dc.DrawLines() only for a reasonable number or points (<10000), // because at least on Windows dc.DrawLines() can hang for a lot of points. // (> 10000 points) (can happens when a lot of points is calculated) // To avoid long draw time (and perhaps hanging) one plot only not redundant lines. // To avoid artifacts when skipping points to the same x coordinate, for each group of // points at a give, x coordinate we also draw a vertical line at this coord, from the // ymin to the ymax vertical coordinates of skipped points while( GetNextXY( x, y ) ) { double px = m_scaleX->TransformToPlot( x ); double py = m_scaleY->TransformToPlot( y ); wxCoord x1 = w.x2p( px ); wxCoord y1 = w.y2p( py ); // Store only points on the drawing area, to speed up the drawing time // Note: x1 is a value truncated from px by w.x2p(). So to be sure the first point // is drawn, the x1 low limit is startPx-1 in plot coordinates if( x1 >= startPx-1 && x1 <= endPx ) { if( !count || line_start.x != x1 ) { if( count && dupx0 > 1 && ymin0 != ymax0 ) { // Vertical points are merged, draw the pending vertical line // However, if the line is one pixel length, it is not drawn, because // the main trace show this point dc.DrawLine( x0, ymin0, x0, ymax0 ); } x0 = x1; ymin0 = ymax0 = y1; dupx0 = 0; pointList.emplace_back( wxPoint( x1, y1 ) ); line_start.x = x1; line_start.y = y1; count++; } else { ymin0 = std::min( ymin0, y1 ); ymax0 = std::max( ymax0, y1 ); x0 = x1; dupx0++; } } } if( pointList.size() > 1 ) { // For a better look (when using dashed lines) and more optimization, try to merge // horizontal segments, in order to plot longer lines // We are merging horizontal segments because this is easy, and horizontal segments // are a frequent cases std::vector drawPoints; drawPoints.reserve( endPx - startPx + 1 ); drawPoints.push_back( pointList[0] ); // push the first point in list for( size_t ii = 1; ii < pointList.size()-1; ii++ ) { // Skip intermediate points between the first point and the last point of the // segment candidate if( drawPoints.back().y == pointList[ii].y && drawPoints.back().y == pointList[ii+1].y ) { continue; } else { drawPoints.push_back( pointList[ii] ); } } // push the last point to draw in list if( drawPoints.back() != pointList.back() ) drawPoints.push_back( pointList.back() ); dc.DrawLines( drawPoints.size(), &drawPoints[0] ); } } if( !m_name.IsEmpty() && m_showName ) { dc.SetFont( m_font ); wxCoord tx, ty; dc.GetTextExtent( m_name, &tx, &ty ); // xxx implement else ... if (!HasBBox()) { // const int sx = w.GetScrX(); // const int sy = w.GetScrY(); if( (m_flags & mpALIGNMASK) == mpALIGN_NW ) { tx = minDrawX + 8; ty = maxDrawY + 8; } else if( (m_flags & mpALIGNMASK) == mpALIGN_NE ) { tx = maxDrawX - tx - 8; ty = maxDrawY + 8; } else if( (m_flags & mpALIGNMASK) == mpALIGN_SE ) { tx = maxDrawX - tx - 8; ty = minDrawY - ty - 8; } else { // mpALIGN_SW tx = minDrawX + 8; ty = minDrawY - ty - 8; } } dc.DrawText( m_name, tx, ty ); } dc.DestroyClippingRegion(); } // ----------------------------------------------------------------------------- // mpProfile implementation // ----------------------------------------------------------------------------- IMPLEMENT_ABSTRACT_CLASS( mpProfile, mpLayer ) mpProfile::mpProfile( const wxString& name, int flags ) { SetName( name ); m_flags = flags; m_type = mpLAYER_PLOT; } void mpProfile::Plot( wxDC& dc, mpWindow& w ) { if( m_visible ) { dc.SetPen( m_pen ); wxCoord startPx = m_drawOutsideMargins ? 0 : w.GetMarginLeft(); wxCoord endPx = m_drawOutsideMargins ? w.GetScrX() : w.GetScrX() - w.GetMarginRight(); wxCoord minYpx = m_drawOutsideMargins ? 0 : w.GetMarginTop(); wxCoord maxYpx = m_drawOutsideMargins ? w.GetScrY() : w.GetScrY() - w.GetMarginBottom(); // Plot profile linking subsequent point of the profile, instead of mpFY, which plots simple points. for( wxCoord i = startPx; i < endPx; ++i ) { wxCoord c0 = w.y2p( GetY( w.p2x( i ) ) ); // (wxCoord) ((w.GetYpos() - GetY( (double)i / w.GetXscl() + w.GetXpos()) ) * w.GetYscl()); wxCoord c1 = w.y2p( GetY( w.p2x( i + 1 ) ) ); // (wxCoord) ((w.GetYpos() - GetY( (double)(i+1) / w.GetXscl() + (w.GetXpos() ) ) ) * w.GetYscl()); // c0 = (c0 <= maxYpx) ? ((c0 >= minYpx) ? c0 : minYpx) : maxYpx; // c1 = (c1 <= maxYpx) ? ((c1 >= minYpx) ? c1 : minYpx) : maxYpx; if( !m_drawOutsideMargins ) { c0 = (c0 <= maxYpx) ? ( (c0 >= minYpx) ? c0 : minYpx ) : maxYpx; c1 = (c1 <= maxYpx) ? ( (c1 >= minYpx) ? c1 : minYpx ) : maxYpx; } dc.DrawLine( i, c0, i + 1, c1 ); } ; if( !m_name.IsEmpty() ) { dc.SetFont( m_font ); wxCoord tx, ty; dc.GetTextExtent( m_name, &tx, &ty ); if( (m_flags & mpALIGNMASK) == mpALIGN_RIGHT ) tx = (w.GetScrX() - tx) - w.GetMarginRight() - 8; else if( (m_flags & mpALIGNMASK) == mpALIGN_CENTER ) tx = ( (w.GetScrX() - w.GetMarginRight() - w.GetMarginLeft() - tx) / 2 ) + w.GetMarginLeft(); else tx = w.GetMarginLeft() + 8; dc.DrawText( m_name, tx, w.y2p( GetY( w.p2x( tx ) ) ) ); // (wxCoord) ((w.GetPosY() - GetY( (double)tx / w.GetScaleX() + w.GetPosX())) * w.GetScaleY()) ); } } } // ----------------------------------------------------------------------------- // mpLayer implementations - furniture (scales, ...) // ----------------------------------------------------------------------------- #define mpLN10 2.3025850929940456840179914546844 void mpScaleX::recalculateTicks( wxDC& dc, mpWindow& w ) { double minV, maxV, minVvis, maxVvis; GetDataRange( minV, maxV ); getVisibleDataRange( w, minVvis, maxVvis ); m_absVisibleMaxV = std::max( std::abs( minVvis ), std::abs( maxVvis ) ); m_tickValues.clear(); m_tickLabels.clear(); double minErr = 1000000000000.0; double bestStep = 1.0; for( int i = 10; i <= 20; i += 2 ) { double curr_step = fabs( maxVvis - minVvis ) / (double) i; double base = pow( 10, floor( log10( curr_step ) ) ); double stepInt = floor( curr_step / base ) * base; double err = fabs( curr_step - stepInt ); if( err < minErr ) { minErr = err; bestStep = stepInt; } } double v = floor( minVvis / bestStep ) * bestStep; double zeroOffset = 100000000.0; while( v < maxVvis ) { m_tickValues.push_back( v ); if( fabs( v ) < zeroOffset ) zeroOffset = fabs( v ); v += bestStep; } if( zeroOffset <= bestStep ) { for( double& t : m_tickValues ) t -= zeroOffset; } for( double t : m_tickValues ) { m_tickLabels.emplace_back( t ); } updateTickLabels( dc, w ); } mpScaleBase::mpScaleBase() { m_rangeSet = false; m_nameFlags = mpALIGN_BORDER_BOTTOM; // initialize these members mainly to avoid not initialized values m_offset = 0.0; m_scale = 1.0; m_absVisibleMaxV = 0.0; m_flags = 0; // Flag for axis alignment m_ticks = true; // Flag to toggle between ticks or grid m_minV = 0.0; m_maxV = 0.0; m_maxLabelHeight = 1; m_maxLabelWidth = 1; } void mpScaleBase::computeLabelExtents( wxDC& dc, mpWindow& w ) { m_maxLabelHeight = 0; m_maxLabelWidth = 0; for( int n = 0; n < labelCount(); n++ ) { int tx, ty; const wxString s = getLabel( n ); dc.GetTextExtent( s, &tx, &ty ); m_maxLabelHeight = std::max( ty, m_maxLabelHeight ); m_maxLabelWidth = std::max( tx, m_maxLabelWidth ); } } void mpScaleBase::updateTickLabels( wxDC& dc, mpWindow& w ) { formatLabels(); computeLabelExtents( dc, w ); // int gap = IsHorizontal() ? m_maxLabelWidth + 10 : m_maxLabelHeight + 5; // if ( m_tickLabels.size() <= 2) // return; /* * fixme! * * for ( auto &l : m_tickLabels ) * { * double p = TransformToPlot ( l.pos ); * * if ( !IsHorizontal() ) * l.pixelPos = (int)(( w.GetPosY() - p ) * w.GetScaleY()); * else * l.pixelPos = (int)(( p - w.GetPosX()) * w.GetScaleX()); * } * * * for (int i = 1; i < m_tickLabels.size() - 1; i++) * { * int dist_prev; * * for(int j = i-1; j >= 1; j--) * { * if( m_tickLabels[j].visible) * { * dist_prev = abs( m_tickLabels[j].pixelPos - m_tickLabels[i].pixelPos ); * break; * } * } * * if (dist_prev < gap) * m_tickLabels[i].visible = false; * } */ } void mpScaleY::getVisibleDataRange( mpWindow& w, double& minV, double& maxV ) { wxCoord minYpx = m_drawOutsideMargins ? 0 : w.GetMarginTop(); wxCoord maxYpx = m_drawOutsideMargins ? w.GetScrY() : w.GetScrY() - w.GetMarginBottom(); double pymin = w.p2y( minYpx ); double pymax = w.p2y( maxYpx ); minV = TransformFromPlot( pymax ); maxV = TransformFromPlot( pymin ); } void mpScaleY::computeSlaveTicks( mpWindow& w ) { if( m_masterScale->m_tickValues.size() == 0 ) return; m_tickValues.clear(); m_tickLabels.clear(); double p0 = m_masterScale->TransformToPlot( m_masterScale->m_tickValues[0] ); double p1 = m_masterScale->TransformToPlot( m_masterScale->m_tickValues[1] ); m_scale = 1.0 / ( m_maxV - m_minV ); m_offset = -m_minV; double y_slave0 = p0 / m_scale; double y_slave1 = p1 / m_scale; double dy_slave = (y_slave1 - y_slave0); double exponent = floor( log10( dy_slave ) ); double base = dy_slave / pow( 10.0, exponent ); double dy_scaled = ceil( 2.0 * base ) / 2.0 * pow( 10.0, exponent ); double minvv, maxvv; getVisibleDataRange( w, minvv, maxvv ); minvv = floor( minvv / dy_scaled ) * dy_scaled; m_scale = 1.0 / ( m_maxV - m_minV ); m_scale *= dy_slave / dy_scaled; m_offset = p0 / m_scale - minvv; m_tickValues.clear(); double m; m_absVisibleMaxV = 0; for( unsigned int i = 0; i < m_masterScale->m_tickValues.size(); i++ ) { m = TransformFromPlot( m_masterScale->TransformToPlot( m_masterScale->m_tickValues[i] ) ); m_tickValues.push_back( m ); m_tickLabels.emplace_back( m ); m_absVisibleMaxV = std::max( m_absVisibleMaxV, fabs( m ) ); } } void mpScaleY::recalculateTicks( wxDC& dc, mpWindow& w ) { if( m_masterScale ) { computeSlaveTicks( w ); updateTickLabels( dc, w ); return; } double minV, maxV, minVvis, maxVvis; GetDataRange( minV, maxV ); getVisibleDataRange( w, minVvis, maxVvis ); m_absVisibleMaxV = std::max( std::abs( minVvis ), std::abs( maxVvis ) ); m_tickValues.clear(); m_tickLabels.clear(); double minErr = 1000000000000.0; double bestStep = 1.0; for( int i = 10; i <= 20; i += 2 ) { double curr_step = fabs( maxVvis - minVvis ) / (double) i; double base = pow( 10, floor( log10( curr_step ) ) ); double stepInt = floor( curr_step / base ) * base; double err = fabs( curr_step - stepInt ); if( err< minErr ) { minErr = err; bestStep = stepInt; } } double v = floor( minVvis / bestStep ) * bestStep; double zeroOffset = 100000000.0; const int iterLimit = 1000; int i = 0; while( v < maxVvis && i < iterLimit ) { m_tickValues.push_back( v ); if( fabs( v ) < zeroOffset ) zeroOffset = fabs( v ); v += bestStep; i++; } // something weird happened... if( i == iterLimit ) { m_tickValues.clear(); } if( zeroOffset <= bestStep ) { for( double& t : m_tickValues ) t -= zeroOffset; } for( double t : m_tickValues ) m_tickLabels.emplace_back( t ); // n0 = floor(minVvis / bestStep) * bestStep; // end = n0 + // n0 = floor( (w.GetPosX() ) / step ) * step ; updateTickLabels( dc, w ); // labelStep = ceil(((double) m_maxLabelWidth + mpMIN_X_AXIS_LABEL_SEPARATION)/(w.GetScaleX()*step))*step; } void mpScaleXBase::getVisibleDataRange( mpWindow& w, double& minV, double& maxV ) { wxCoord startPx = m_drawOutsideMargins ? 0 : w.GetMarginLeft(); wxCoord endPx = m_drawOutsideMargins ? w.GetScrX() : w.GetScrX() - w.GetMarginRight(); double pxmin = w.p2x( startPx ); double pxmax = w.p2x( endPx ); minV = TransformFromPlot( pxmin ); maxV = TransformFromPlot( pxmax ); } void mpScaleXLog::recalculateTicks( wxDC& dc, mpWindow& w ) { double minV, maxV, minVvis, maxVvis; GetDataRange( minV, maxV ); getVisibleDataRange( w, minVvis, maxVvis ); // double decades = log( maxV / minV ) / log(10); double minDecade = pow( 10, floor( log10( minV ) ) ); double maxDecade = pow( 10, ceil( log10( maxV ) ) ); double visibleDecades = log( maxVvis / minVvis ) / log( 10 ); double d; m_tickValues.clear(); m_tickLabels.clear(); if( minDecade == 0.0 ) return; for( d = minDecade; d<=maxDecade; d *= 10.0 ) { m_tickLabels.emplace_back( d ); for( double dd = d; dd < d * 10; dd += d ) { if( visibleDecades < 2 ) m_tickLabels.emplace_back( dd ); m_tickValues.push_back( dd ); } } updateTickLabels( dc, w ); } IMPLEMENT_ABSTRACT_CLASS( mpScaleXBase, mpLayer ) IMPLEMENT_DYNAMIC_CLASS( mpScaleX, mpScaleXBase ) IMPLEMENT_DYNAMIC_CLASS( mpScaleXLog, mpScaleXBase ) mpScaleXBase::mpScaleXBase( const wxString& name, int flags, bool ticks, unsigned int type ) { SetName( name ); SetFont( (wxFont&) *wxSMALL_FONT ); SetPen( (wxPen&) *wxGREY_PEN ); m_flags = flags; m_ticks = ticks; // m_labelType = type; m_type = mpLAYER_AXIS; } mpScaleX::mpScaleX( const wxString& name, int flags, bool ticks, unsigned int type ) : mpScaleXBase( name, flags, ticks, type ) { } mpScaleXLog::mpScaleXLog( const wxString& name, int flags, bool ticks, unsigned int type ) : mpScaleXBase( name, flags, ticks, type ) { } void mpScaleXBase::Plot( wxDC& dc, mpWindow& w ) { int tx, ty; m_offset = -m_minV; m_scale = 1.0 / ( m_maxV - m_minV ); recalculateTicks( dc, w ); if( m_visible ) { dc.SetPen( m_pen ); dc.SetFont( m_font ); int orgy = 0; const int extend = w.GetScrX(); ///2; if( m_flags == mpALIGN_CENTER ) orgy = w.y2p( 0 ); // (int)(w.GetPosY() * w.GetScaleY()); if( m_flags == mpALIGN_TOP ) { if( m_drawOutsideMargins ) orgy = X_BORDER_SEPARATION; else orgy = w.GetMarginTop(); } if( m_flags == mpALIGN_BOTTOM ) { if( m_drawOutsideMargins ) orgy = X_BORDER_SEPARATION; else orgy = w.GetScrY() - w.GetMarginBottom(); } if( m_flags == mpALIGN_BORDER_BOTTOM ) orgy = w.GetScrY() - 1; // dc.LogicalToDeviceY(0) - 1; if( m_flags == mpALIGN_BORDER_TOP ) orgy = 1; // -dc.LogicalToDeviceY(0); // dc.DrawLine( 0, orgy, w.GetScrX(), orgy); wxCoord startPx = m_drawOutsideMargins ? 0 : w.GetMarginLeft(); wxCoord endPx = m_drawOutsideMargins ? w.GetScrX() : w.GetScrX() - w.GetMarginRight(); wxCoord minYpx = m_drawOutsideMargins ? 0 : w.GetMarginTop(); wxCoord maxYpx = m_drawOutsideMargins ? w.GetScrY() : w.GetScrY() - w.GetMarginBottom(); // int tmp=-65535; int labelH = m_maxLabelHeight; // Control labels height to decide where to put axis name (below labels or on top of axis) // int maxExtent = tc.MaxLabelWidth(); for( int n = 0; n < tickCount(); n++ ) { double tp = getTickPos( n ); // double xlogmin = log10 ( m_minV ); // double xlogmax = log10 ( m_maxV ); double px = TransformToPlot( tp ); // ( log10 ( tp ) - xlogmin) / (xlogmax - xlogmin); const int p = (int) ( ( px - w.GetPosX() ) * w.GetScaleX() ); if( (p >= startPx) && (p <= endPx) ) { if( m_ticks ) // draw axis ticks { if( m_flags == mpALIGN_BORDER_BOTTOM ) dc.DrawLine( p, orgy, p, orgy - 4 ); else dc.DrawLine( p, orgy, p, orgy + 4 ); } else // draw grid dotted lines { m_pen.SetStyle( wxPENSTYLE_DOT ); dc.SetPen( m_pen ); if( (m_flags == mpALIGN_BOTTOM) && !m_drawOutsideMargins ) { m_pen.SetStyle( wxPENSTYLE_DOT ); dc.SetPen( m_pen ); dc.DrawLine( p, orgy + 4, p, minYpx ); m_pen.SetStyle( wxPENSTYLE_SOLID ); dc.SetPen( m_pen ); dc.DrawLine( p, orgy + 4, p, orgy - 4 ); } else { if( (m_flags == mpALIGN_TOP) && !m_drawOutsideMargins ) { dc.DrawLine( p, orgy - 4, p, maxYpx ); } else { dc.DrawLine( p, minYpx, p, maxYpx ); // 0/*-w.GetScrY()*/, p, w.GetScrY() ); } } m_pen.SetStyle( wxPENSTYLE_SOLID ); dc.SetPen( m_pen ); } } } m_pen.SetStyle( wxPENSTYLE_SOLID ); dc.SetPen( m_pen ); dc.DrawLine( startPx, minYpx, endPx, minYpx ); dc.DrawLine( startPx, maxYpx, endPx, maxYpx ); // Actually draw labels, taking care of not overlapping them, and distributing them // regularly for( int n = 0; n < labelCount(); n++ ) { double tp = getLabelPos( n ); if( !m_tickLabels[n].visible ) continue; // double xlogmin = log10 ( m_minV ); // double xlogmax = log10 ( m_maxV ); double px = TransformToPlot( tp ); // ( log10 ( tp ) - xlogmin) / (xlogmax - xlogmin); const int p = (int) ( ( px - w.GetPosX() ) * w.GetScaleX() ); if( (p >= startPx) && (p <= endPx) ) { // Write ticks labels in s string wxString s = m_tickLabels[n].label; dc.GetTextExtent( s, &tx, &ty ); if( (m_flags == mpALIGN_BORDER_BOTTOM) || (m_flags == mpALIGN_TOP) ) { dc.DrawText( s, p - tx / 2, orgy - 4 - ty ); } else { dc.DrawText( s, p - tx / 2, orgy + 4 ); } } } // Draw axis name dc.GetTextExtent( m_name, &tx, &ty ); switch( m_nameFlags ) { case mpALIGN_BORDER_BOTTOM: dc.DrawText( m_name, extend - tx - 4, orgy - 8 - ty - labelH ); break; case mpALIGN_BOTTOM: { dc.DrawText( m_name, (endPx + startPx) / 2 - tx / 2, orgy + 6 + labelH ); } break; case mpALIGN_CENTER: dc.DrawText( m_name, extend - tx - 4, orgy - 4 - ty ); break; case mpALIGN_TOP: { if( (!m_drawOutsideMargins) && ( w.GetMarginTop() > (ty + labelH + 8) ) ) { dc.DrawText( m_name, (endPx - startPx - tx) >> 1, orgy - 6 - ty - labelH ); } else { dc.DrawText( m_name, extend - tx - 4, orgy + 4 ); } } break; case mpALIGN_BORDER_TOP: dc.DrawText( m_name, extend - tx - 4, orgy + 6 + labelH ); break; default: break; } } } IMPLEMENT_DYNAMIC_CLASS( mpScaleY, mpLayer ) mpScaleY::mpScaleY( const wxString& name, int flags, bool ticks ) { SetName( name ); SetFont( (wxFont&) *wxSMALL_FONT ); SetPen( (wxPen&) *wxGREY_PEN ); m_flags = flags; m_ticks = ticks; m_type = mpLAYER_AXIS; m_masterScale = nullptr; m_nameFlags = mpALIGN_BORDER_LEFT; } void mpScaleY::Plot( wxDC& dc, mpWindow& w ) { m_offset = -m_minV; m_scale = 1.0 / ( m_maxV - m_minV ); recalculateTicks( dc, w ); if( m_visible ) { dc.SetPen( m_pen ); dc.SetFont( m_font ); int orgx = 0; // const int extend = w.GetScrY(); // /2; if( m_flags == mpALIGN_CENTER ) orgx = w.x2p( 0 ); // (int)(w.GetPosX() * w.GetScaleX()); if( m_flags == mpALIGN_LEFT ) { if( m_drawOutsideMargins ) orgx = Y_BORDER_SEPARATION; else orgx = w.GetMarginLeft(); } if( m_flags == mpALIGN_RIGHT ) { if( m_drawOutsideMargins ) orgx = w.GetScrX() - Y_BORDER_SEPARATION; else orgx = w.GetScrX() - w.GetMarginRight(); } if( m_flags == mpALIGN_FAR_RIGHT ) orgx = w.GetScrX() - ( w.GetMarginRight() / 2 ); if( m_flags == mpALIGN_BORDER_RIGHT ) orgx = w.GetScrX() - 1; // dc.LogicalToDeviceX(0) - 1; if( m_flags == mpALIGN_BORDER_LEFT ) orgx = 1; // -dc.LogicalToDeviceX(0); wxCoord endPx = m_drawOutsideMargins ? w.GetScrX() : w.GetScrX() - w.GetMarginRight(); wxCoord minYpx = m_drawOutsideMargins ? 0 : w.GetMarginTop(); wxCoord maxYpx = m_drawOutsideMargins ? w.GetScrY() : w.GetScrY() - w.GetMarginBottom(); // Draw line dc.DrawLine( orgx, minYpx, orgx, maxYpx ); wxCoord tx, ty; wxString s; wxString fmt; int n = 0; int labelW = 0; // Before staring cycle, calculate label height int labelHeight = 0; s.Printf( fmt, n ); dc.GetTextExtent( s, &tx, &labelHeight ); for( n = 0; n < tickCount(); n++ ) { double tp = getTickPos( n ); double py = TransformToPlot( tp ); // ( log10 ( tp ) - xlogmin) / (xlogmax - xlogmin); const int p = (int) ( ( w.GetPosY() - py ) * w.GetScaleY() ); if( (p >= minYpx) && (p <= maxYpx) ) { if( m_ticks ) // Draw axis ticks { if( m_flags == mpALIGN_BORDER_LEFT ) { dc.DrawLine( orgx, p, orgx + 4, p ); } else { dc.DrawLine( orgx - 4, p, orgx, p ); // ( orgx, p, orgx+4, p); } } else { dc.DrawLine( orgx - 4, p, orgx + 4, p ); m_pen.SetStyle( wxPENSTYLE_DOT ); dc.SetPen( m_pen ); dc.DrawLine( orgx - 4, p, endPx, p ); m_pen.SetStyle( wxPENSTYLE_SOLID ); dc.SetPen( m_pen ); } // Print ticks labels } } for( n = 0; n < labelCount(); n++ ) { double tp = getLabelPos( n ); double py = TransformToPlot( tp ); // ( log10 ( tp ) - xlogmin) / (xlogmax - xlogmin); const int p = (int) ( ( w.GetPosY() - py ) * w.GetScaleY() ); if( !m_tickLabels[n].visible ) continue; if( (p >= minYpx) && (p <= maxYpx) ) { s = getLabel( n ); dc.GetTextExtent( s, &tx, &ty ); if( m_flags == mpALIGN_BORDER_LEFT || m_flags == mpALIGN_RIGHT || m_flags == mpALIGN_FAR_RIGHT ) dc.DrawText( s, orgx + 4, p - ty / 2 ); else dc.DrawText( s, orgx - 4 - tx, p - ty / 2 ); // ( s, orgx+4, p-ty/2); } } // Draw axis name // Draw axis name dc.GetTextExtent( m_name, &tx, &ty ); switch( m_nameFlags ) { case mpALIGN_BORDER_LEFT: dc.DrawText( m_name, labelW + 8, 4 ); break; case mpALIGN_LEFT: { // if ((!m_drawOutsideMargins) && (w.GetMarginLeft() > (ty + labelW + 8))) { // dc.DrawRotatedText( m_name, orgx - 6 - labelW - ty, (maxYpx + minYpx) / 2 + tx / 2, 90); // } else { dc.DrawText( m_name, orgx - ( tx / 2 ), minYpx - ty - 4 ); // } } break; case mpALIGN_CENTER: dc.DrawText( m_name, orgx + 4, 4 ); break; case mpALIGN_RIGHT: case mpALIGN_FAR_RIGHT: { // dc.DrawRotatedText( m_name, orgx + 6, (maxYpx + minYpx) / 2 + tx / 2, 90); /*if ((!m_drawOutsideMargins) && (w.GetMarginRight() > (ty + labelW + 8))) { * dc.DrawRotatedText( m_name, orgx + 6 + labelW, (maxYpx - minYpx + tx)>>1, 90); * } else {*/ dc.DrawText( m_name, orgx - ( tx / 2 ), minYpx - ty - 4 ); // } } break; case mpALIGN_BORDER_RIGHT: dc.DrawText( m_name, orgx - 6 - tx - labelW, 4 ); break; default: break; } } } // ----------------------------------------------------------------------------- // mpWindow // ----------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS( mpWindow, wxWindow ) BEGIN_EVENT_TABLE( mpWindow, wxWindow ) EVT_PAINT( mpWindow::OnPaint ) EVT_SIZE( mpWindow::OnSize ) EVT_SCROLLWIN_THUMBTRACK( mpWindow::OnScrollThumbTrack ) EVT_SCROLLWIN_PAGEUP( mpWindow::OnScrollPageUp ) EVT_SCROLLWIN_PAGEDOWN( mpWindow::OnScrollPageDown ) EVT_SCROLLWIN_LINEUP( mpWindow::OnScrollLineUp ) EVT_SCROLLWIN_LINEDOWN( mpWindow::OnScrollLineDown ) EVT_SCROLLWIN_TOP( mpWindow::OnScrollTop ) EVT_SCROLLWIN_BOTTOM( mpWindow::OnScrollBottom ) EVT_MIDDLE_DOWN( mpWindow::OnMouseMiddleDown ) // JLB EVT_RIGHT_UP( mpWindow::OnShowPopupMenu ) EVT_MOUSEWHEEL( mpWindow::OnMouseWheel ) // JLB #if wxCHECK_VERSION( 3, 1, 0 ) || defined( USE_OSX_MAGNIFY_EVENT ) EVT_MAGNIFY( mpWindow::OnMagnify ) #endif EVT_MOTION( mpWindow::OnMouseMove ) // JLB EVT_LEFT_DOWN( mpWindow::OnMouseLeftDown ) EVT_LEFT_UP( mpWindow::OnMouseLeftRelease ) EVT_MENU( mpID_CENTER, mpWindow::OnCenter ) EVT_MENU( mpID_FIT, mpWindow::OnFit ) EVT_MENU( mpID_ZOOM_IN, mpWindow::OnZoomIn ) EVT_MENU( mpID_ZOOM_OUT, mpWindow::OnZoomOut ) EVT_MENU( mpID_LOCKASPECT, mpWindow::OnLockAspect ) END_EVENT_TABLE() mpWindow::mpWindow() : wxWindow(), m_lockaspect( false ), m_minX( 0.0 ), m_maxX( 0.0 ), m_minY( 0.0 ), m_maxY( 0.0 ), m_scaleX( 1.0 ), m_scaleY( 1.0 ), m_posX( 0.0 ), m_posY( 0.0 ), m_scrX( 64 ), m_scrY( 64 ), m_clickedX( 0 ), m_clickedY( 0 ), m_desiredXmin( 0.0 ), m_desiredXmax( 1.0 ), m_desiredYmin( 0.0 ), m_desiredYmax( 1.0 ), m_marginTop( 0 ), m_marginRight( 0 ), m_marginBottom( 0 ), m_marginLeft( 0 ), m_last_lx( 0 ), m_last_ly( 0 ), m_buff_bmp( nullptr ), m_enableDoubleBuffer( false ), m_enableMouseNavigation( true ), m_enableMouseWheelPan( false ), m_enableLimitedView( false ), m_enableScrollBars( false ), m_movingInfoLayer( nullptr ), m_zooming( false ) { if( wxGraphicsContext *ctx = m_buff_dc.GetGraphicsContext() ) { if( !ctx->SetInterpolationQuality( wxINTERPOLATION_BEST ) || !ctx->SetInterpolationQuality( wxINTERPOLATION_GOOD ) ) { ctx->SetInterpolationQuality( wxINTERPOLATION_FAST ); } ctx->SetAntialiasMode( wxANTIALIAS_DEFAULT ); } } mpWindow::mpWindow( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long flag ) : wxWindow( parent, id, pos, size, flag, wxT( "mathplot" ) ) { m_zooming = false; m_scaleX = m_scaleY = 1.0; m_posX = m_posY = 0; m_desiredXmin = m_desiredYmin = 0; m_desiredXmax = m_desiredYmax = 1; m_scrX = m_scrY = 64; // Fixed from m_scrX = m_scrX = 64; m_minX = m_minY = 0; m_maxX = m_maxY = 0; m_last_lx = m_last_ly = 0; m_buff_bmp = nullptr; m_enableDoubleBuffer = false; m_enableMouseNavigation = true; m_enableLimitedView = false; m_movingInfoLayer = nullptr; // Set margins to 0 m_marginTop = 0; m_marginRight = 0; m_marginBottom = 0; m_marginLeft = 0; m_lockaspect = false; m_popmenu.Append( mpID_CENTER, _( "Center on Cursor" ), _( "Center plot view to this position" ) ); m_popmenu.Append( mpID_FIT, _( "Fit on Screen" ), _( "Set plot view to show all items" ) ); m_popmenu.Append( mpID_ZOOM_IN, _( "Zoom In" ), _( "Zoom in plot view." ) ); m_popmenu.Append( mpID_ZOOM_OUT, _( "Zoom Out" ), _( "Zoom out plot view." ) ); // m_popmenu.AppendCheckItem( mpID_LOCKASPECT, _("Lock aspect"), _("Lock horizontal and vertical zoom aspect.")); // m_popmenu.Append( mpID_HELP_MOUSE, _("Show mouse commands..."), _("Show help about the mouse commands.")); m_layers.clear(); SetBackgroundColour( *wxWHITE ); m_bgColour = *wxWHITE; m_fgColour = *wxBLACK; m_enableScrollBars = false; SetSizeHints( 128, 128 ); // J.L.Blanco: Eliminates the "flick" with the double buffer. SetBackgroundStyle( wxBG_STYLE_CUSTOM ); if( wxGraphicsContext* ctx = m_buff_dc.GetGraphicsContext() ) { if( !ctx->SetInterpolationQuality( wxINTERPOLATION_BEST ) || !ctx->SetInterpolationQuality( wxINTERPOLATION_GOOD ) ) { ctx->SetInterpolationQuality( wxINTERPOLATION_FAST ); } ctx->SetAntialiasMode( wxANTIALIAS_DEFAULT ); } UpdateAll(); } mpWindow::~mpWindow() { // Free all the layers: DelAllLayers( true, false ); delete m_buff_bmp; m_buff_bmp = nullptr; } // Mouse handler, for detecting when the user drag with the right button or just "clicks" for the menu // JLB void mpWindow::OnMouseMiddleDown( wxMouseEvent& event ) { m_mouseMClick.x = event.GetX(); m_mouseMClick.y = event.GetY(); } #if wxCHECK_VERSION( 3, 1, 0 ) || defined( USE_OSX_MAGNIFY_EVENT ) void mpWindow::OnMagnify( wxMouseEvent& event ) { if( !m_enableMouseNavigation ) { event.Skip(); return; } float zoom = event.GetMagnification() + 1.0f; wxPoint pos( event.GetX(), event.GetY() ); if( zoom > 1.0f ) ZoomIn( pos, zoom ); else if( zoom < 1.0f ) ZoomOut( pos, 1.0f / zoom ); } #endif // Process mouse wheel events // JLB void mpWindow::OnMouseWheel( wxMouseEvent& event ) { if( !m_enableMouseNavigation ) { event.Skip(); return; } int change = event.GetWheelRotation(); const int axis = event.GetWheelAxis(); double changeUnitsX = change / m_scaleX; double changeUnitsY = change / m_scaleY; if( ( !m_enableMouseWheelPan && ( event.ControlDown() || event.ShiftDown() ) ) || ( m_enableMouseWheelPan && !event.ControlDown() ) ) { // Scrolling if( m_enableMouseWheelPan ) { if( axis == wxMOUSE_WHEEL_HORIZONTAL || event.ShiftDown() ) SetXView( m_posX + changeUnitsX, m_desiredXmax + changeUnitsX, m_desiredXmin + changeUnitsX ); else SetYView( m_posY + changeUnitsY, m_desiredYmax + changeUnitsY, m_desiredYmin + changeUnitsY ); } else { if( event.ControlDown() ) SetXView( m_posX + changeUnitsX, m_desiredXmax + changeUnitsX, m_desiredXmin + changeUnitsX ); else SetYView( m_posY + changeUnitsY, m_desiredYmax + changeUnitsY, m_desiredYmin + changeUnitsY ); } UpdateAll(); } else { // zoom in/out wxPoint clickPt( event.GetX(), event.GetY() ); if( event.GetWheelRotation() > 0 ) ZoomIn( clickPt ); else ZoomOut( clickPt ); return; } } // If the user "drags" with the right button pressed, do "pan" // JLB void mpWindow::OnMouseMove( wxMouseEvent& event ) { if( !m_enableMouseNavigation ) { event.Skip(); return; } wxCursor cursor = wxCURSOR_MAGNIFIER; if( event.m_middleDown ) { cursor = wxCURSOR_ARROW; // The change: int Ax = m_mouseMClick.x - event.GetX(); int Ay = m_mouseMClick.y - event.GetY(); // For the next event, use relative to this coordinates. m_mouseMClick.x = event.GetX(); m_mouseMClick.y = event.GetY(); double Ax_units = Ax / m_scaleX; double Ay_units = -Ay / m_scaleY; bool updateRequired = false; updateRequired |= SetXView( m_posX + Ax_units, m_desiredXmax + Ax_units, m_desiredXmin + Ax_units ); updateRequired |= SetYView( m_posY + Ay_units, m_desiredYmax + Ay_units, m_desiredYmin + Ay_units ); if( updateRequired ) UpdateAll(); } else if( event.m_leftDown ) { if( m_movingInfoLayer ) { cursor = wxCURSOR_SIZEWE; wxPoint moveVector( event.GetX() - m_mouseLClick.x, event.GetY() - m_mouseLClick.y ); m_movingInfoLayer->Move( moveVector ); m_zooming = false; } else { cursor = wxCURSOR_MAGNIFIER; wxClientDC dc( this ); wxPen pen( m_fgColour, 1, wxPENSTYLE_DOT ); dc.SetPen( pen ); dc.SetBrush( *wxTRANSPARENT_BRUSH ); dc.DrawRectangle( m_mouseLClick.x, m_mouseLClick.y, event.GetX() - m_mouseLClick.x, event.GetY() - m_mouseLClick.y ); m_zooming = true; m_zoomRect.x = m_mouseLClick.x; m_zoomRect.y = m_mouseLClick.y; m_zoomRect.width = event.GetX() - m_mouseLClick.x; m_zoomRect.height = event.GetY() - m_mouseLClick.y; } UpdateAll(); } else { for( mpLayer* layer : m_layers) { if( layer->IsInfo() && layer->IsVisible() ) { mpInfoLayer* infoLayer = (mpInfoLayer*) layer; if( infoLayer->Inside( event.GetPosition() ) ) cursor = wxCURSOR_SIZEWE; } } /* if (m_coordTooltip) { * wxString toolTipContent; * toolTipContent.Printf( "X = %f\nY = %f", p2x(event.GetX()), p2y(event.GetY())); * wxTipWindow** ptr = NULL; * wxRect rectBounds(event.GetX(), event.GetY(), 5, 5); * wxTipWindow* tip = new wxTipWindow(this, toolTipContent, 100, ptr, &rectBounds); * * } */ } SetCursor( cursor ); event.Skip(); } void mpWindow::OnMouseLeftDown( wxMouseEvent& event ) { m_mouseLClick.x = event.GetX(); m_mouseLClick.y = event.GetY(); m_zooming = true; wxPoint pointClicked = event.GetPosition(); m_movingInfoLayer = IsInsideInfoLayer( pointClicked ); event.Skip(); } void mpWindow::OnMouseLeftRelease( wxMouseEvent& event ) { wxPoint release( event.GetX(), event.GetY() ); wxPoint press( m_mouseLClick.x, m_mouseLClick.y ); m_zooming = false; if( m_movingInfoLayer != nullptr ) { m_movingInfoLayer->UpdateReference(); m_movingInfoLayer = nullptr; } else { if( release != press ) ZoomRect( press, release ); } event.Skip(); } void mpWindow::Fit() { if( UpdateBBox() ) Fit( m_minX, m_maxX, m_minY, m_maxY ); } // JL void mpWindow::Fit( double xMin, double xMax, double yMin, double yMax, wxCoord* printSizeX, wxCoord* printSizeY ) { // Save desired borders: m_desiredXmin = xMin; m_desiredXmax = xMax; m_desiredYmin = yMin; m_desiredYmax = yMax; // Give a small margin to plot area double xExtra = fabs( xMax - xMin ) * 0.00; double yExtra = fabs( yMax - yMin ) * 0.03; xMin -= xExtra; xMax += xExtra; yMin -= yExtra; yMax += yExtra; if( printSizeX != nullptr && printSizeY != nullptr ) { // Printer: m_scrX = *printSizeX; m_scrY = *printSizeY; } else { // Normal case (screen): GetClientSize( &m_scrX, &m_scrY ); } double Ax, Ay; Ax = xMax - xMin; Ay = yMax - yMin; m_scaleX = (Ax != 0) ? (m_scrX - m_marginLeft - m_marginRight) / Ax : 1; // m_scaleX = (Ax != 0) ? m_scrX / Ax : 1; m_scaleY = (Ay != 0) ? (m_scrY - m_marginTop - m_marginBottom) / Ay : 1; // m_scaleY = (Ay != 0) ? m_scrY / Ay : 1; if( m_lockaspect ) { // Keep the lowest "scale" to fit the whole range required by that axis (to actually // "fit"!): double s = m_scaleX < m_scaleY ? m_scaleX : m_scaleY; m_scaleX = s; m_scaleY = s; } // Adjusts corner coordinates: This should be simply: // m_posX = m_minX; // m_posY = m_maxY; // But account for centering if we have lock aspect: m_posX = (xMin + xMax) / 2 - ( (m_scrX - m_marginLeft - m_marginRight) / 2 + m_marginLeft ) / m_scaleX; // m_posX = (xMin+xMax)/2 - (m_scrX/2)/m_scaleX; // m_posY = (yMin+yMax)/2 + ((m_scrY - m_marginTop - m_marginBottom)/2 - m_marginTop)/m_scaleY; // m_posY = (yMin+yMax)/2 + (m_scrY/2)/m_scaleY; m_posY = (yMin + yMax) / 2 + ( (m_scrY - m_marginTop - m_marginBottom) / 2 + m_marginTop ) / m_scaleY; // m_posY = (yMin+yMax)/2 + (m_scrY/2)/m_scaleY; // It is VERY IMPORTANT to DO NOT call Refresh if we are drawing to the printer!! // Otherwise, the DC dimensions will be those of the window instead of the printer device if( printSizeX == nullptr || printSizeY == nullptr ) UpdateAll(); } // Patch ngpaton void mpWindow::DoZoomInXCalc( const int staticXpixel ) { // Preserve the position of the clicked point: double staticX = p2x( staticXpixel ); // Zoom in: m_scaleX = m_scaleX * zoomIncrementalFactor; // Adjust the new m_posx m_posX = staticX - (staticXpixel / m_scaleX); // Adjust desired m_desiredXmin = m_posX; m_desiredXmax = m_posX + ( m_scrX - (m_marginLeft + m_marginRight) ) / m_scaleX; } void mpWindow::AdjustLimitedView() { if( !m_enableLimitedView ) return; // m_min and m_max are plot limits for curves // xMin, xMax, yMin, yMax are the full limits (plot limit + margin) const double xMin = m_minX - m_marginLeft / m_scaleX; const double xMax = m_maxX + m_marginRight / m_scaleX; const double yMin = m_minY - m_marginTop / m_scaleY; const double yMax = m_maxY + m_marginBottom / m_scaleY; if( m_desiredXmin < xMin ) { double diff = xMin - m_desiredXmin; m_posX += diff; m_desiredXmax += diff; m_desiredXmin = xMin; } if( m_desiredXmax > xMax ) { double diff = m_desiredXmax - xMax; m_posX -= diff; m_desiredXmin -= diff; m_desiredXmax = xMax; } if( m_desiredYmin < yMin ) { double diff = yMin - m_desiredYmin; m_posY += diff; m_desiredYmax += diff; m_desiredYmin = yMin; } if( m_desiredYmax > yMax ) { double diff = m_desiredYmax - yMax; m_posY -= diff; m_desiredYmin -= diff; m_desiredYmax = yMax; } } bool mpWindow::SetXView( double pos, double desiredMax, double desiredMin ) { // if(!CheckXLimits(desiredMax, desiredMin)) // return false; m_posX = pos; m_desiredXmax = desiredMax; m_desiredXmin = desiredMin; AdjustLimitedView(); return true; } bool mpWindow::SetYView( double pos, double desiredMax, double desiredMin ) { // if(!CheckYLimits(desiredMax, desiredMin)) // return false; m_posY = pos; m_desiredYmax = desiredMax; m_desiredYmin = desiredMin; AdjustLimitedView(); return true; } void mpWindow::ZoomIn( const wxPoint& centerPoint ) { ZoomIn( centerPoint, zoomIncrementalFactor ); } void mpWindow::ZoomIn( const wxPoint& centerPoint, double zoomFactor ) { wxPoint c( centerPoint ); if( c == wxDefaultPosition ) { GetClientSize( &m_scrX, &m_scrY ); c.x = (m_scrX - m_marginLeft - m_marginRight) / 2 + m_marginLeft; // c.x = m_scrX/2; c.y = (m_scrY - m_marginTop - m_marginBottom) / 2 - m_marginTop; // c.y = m_scrY/2; } else { c.x = std::max( c.x, m_marginLeft ); c.x = std::min( c.x, m_scrX - m_marginRight ); c.y = std::max( c.y, m_marginTop ); c.y = std::min( c.y, m_scrY - m_marginBottom ); } // Preserve the position of the clicked point: double prior_layer_x = p2x( c.x ); double prior_layer_y = p2y( c.y ); // Zoom in: const double MAX_SCALE = 1e6; double newScaleX = m_scaleX * zoomFactor; double newScaleY = m_scaleY * zoomFactor; // Baaaaad things happen when you zoom in too much.. if( newScaleX <= MAX_SCALE && newScaleY <= MAX_SCALE ) { m_scaleX = newScaleX; m_scaleY = newScaleY; } else { return; } // Adjust the new m_posx/y: m_posX = prior_layer_x - c.x / m_scaleX; m_posY = prior_layer_y + c.y / m_scaleY; m_desiredXmin = m_posX; m_desiredXmax = m_posX + (m_scrX - m_marginLeft - m_marginRight) / m_scaleX; // m_desiredXmax = m_posX + m_scrX / m_scaleX; m_desiredYmax = m_posY; m_desiredYmin = m_posY - (m_scrY - m_marginTop - m_marginBottom) / m_scaleY; // m_desiredYmin = m_posY - m_scrY / m_scaleY; AdjustLimitedView(); UpdateAll(); } void mpWindow::ZoomOut( const wxPoint& centerPoint ) { ZoomOut( centerPoint, zoomIncrementalFactor ); } void mpWindow::ZoomOut( const wxPoint& centerPoint, double zoomFactor ) { wxPoint c( centerPoint ); if( c == wxDefaultPosition ) { GetClientSize( &m_scrX, &m_scrY ); c.x = (m_scrX - m_marginLeft - m_marginRight) / 2 + m_marginLeft; // c.x = m_scrX/2; c.y = (m_scrY - m_marginTop - m_marginBottom) / 2 - m_marginTop; // c.y = m_scrY/2; } // Preserve the position of the clicked point: double prior_layer_x = p2x( c.x ); double prior_layer_y = p2y( c.y ); // Zoom out: m_scaleX = m_scaleX / zoomFactor; m_scaleY = m_scaleY / zoomFactor; // Adjust the new m_posx/y: m_posX = prior_layer_x - c.x / m_scaleX; m_posY = prior_layer_y + c.y / m_scaleY; m_desiredXmin = m_posX; m_desiredXmax = m_posX + (m_scrX - m_marginLeft - m_marginRight) / m_scaleX; // m_desiredXmax = m_posX + m_scrX / m_scaleX; m_desiredYmax = m_posY; m_desiredYmin = m_posY - (m_scrY - m_marginTop - m_marginBottom) / m_scaleY; // m_desiredYmin = m_posY - m_scrY / m_scaleY; if( !CheckXLimits( m_desiredXmax, m_desiredXmin ) || !CheckYLimits( m_desiredYmax, m_desiredYmin ) ) { Fit(); } UpdateAll(); } void mpWindow::ZoomInX() { m_scaleX = m_scaleX * zoomIncrementalFactor; UpdateAll(); } void mpWindow::ZoomRect( wxPoint p0, wxPoint p1 ) { // Compute the 2 corners in graph coordinates: double p0x = p2x( p0.x ); double p0y = p2y( p0.y ); double p1x = p2x( p1.x ); double p1y = p2y( p1.y ); // Order them: double zoom_x_min = p0xp1x ? p0x : p1x; double zoom_y_min = p0yp1y ? p0y : p1y; Fit( zoom_x_min, zoom_x_max, zoom_y_min, zoom_y_max ); AdjustLimitedView(); } void mpWindow::LockAspect( bool enable ) { m_lockaspect = enable; m_popmenu.Check( mpID_LOCKASPECT, enable ); // Try to fit again with the new config: Fit( m_desiredXmin, m_desiredXmax, m_desiredYmin, m_desiredYmax ); } void mpWindow::OnShowPopupMenu( wxMouseEvent& event ) { m_clickedX = event.GetX(); m_clickedY = event.GetY(); PopupMenu( &m_popmenu, event.GetX(), event.GetY() ); } void mpWindow::OnLockAspect( wxCommandEvent& WXUNUSED( event ) ) { LockAspect( !m_lockaspect ); } void mpWindow::OnFit( wxCommandEvent& WXUNUSED( event ) ) { Fit(); } void mpWindow::OnCenter( wxCommandEvent& WXUNUSED( event ) ) { GetClientSize( &m_scrX, &m_scrY ); int centerX = (m_scrX - m_marginLeft - m_marginRight) / 2; // + m_marginLeft; // c.x = m_scrX/2; int centerY = (m_scrY - m_marginTop - m_marginBottom) / 2; // - m_marginTop; // c.y = m_scrY/2; SetPos( p2x( m_clickedX - centerX ), p2y( m_clickedY - centerY ) ); // SetPos( p2x(m_clickedX-m_scrX/2), p2y(m_clickedY-m_scrY/2) ); //SetPos( (double)(m_clickedX-m_scrX/2) / m_scaleX + m_posX, (double)(m_scrY/2-m_clickedY) / m_scaleY + m_posY); } void mpWindow::OnZoomIn( wxCommandEvent& WXUNUSED( event ) ) { ZoomIn( wxPoint( m_mouseMClick.x, m_mouseMClick.y ) ); } void mpWindow::OnZoomOut( wxCommandEvent& WXUNUSED( event ) ) { ZoomOut(); } void mpWindow::OnSize( wxSizeEvent& WXUNUSED( event ) ) { // Try to fit again with the new window size: Fit( m_desiredXmin, m_desiredXmax, m_desiredYmin, m_desiredYmax ); } bool mpWindow::AddLayer( mpLayer* layer, bool refreshDisplay ) { if( layer ) { m_layers.push_back( layer ); if( refreshDisplay ) UpdateAll(); return true; } return false; } bool mpWindow::DelLayer( mpLayer* layer, bool alsoDeleteObject, bool refreshDisplay ) { wxLayerList::iterator layIt; for( layIt = m_layers.begin(); layIt != m_layers.end(); layIt++ ) { if( *layIt == layer ) { // Also delete the object? if( alsoDeleteObject ) delete *layIt; m_layers.erase( layIt ); // this deleted the reference only if( refreshDisplay ) UpdateAll(); return true; } } return false; } void mpWindow::DelAllLayers( bool alsoDeleteObject, bool refreshDisplay ) { while( m_layers.size()>0 ) { // Also delete the object? if( alsoDeleteObject ) delete m_layers[0]; m_layers.erase( m_layers.begin() ); // this deleted the reference only } if( refreshDisplay ) UpdateAll(); } void mpWindow::OnPaint( wxPaintEvent& WXUNUSED( event ) ) { wxPaintDC dc( this ); dc.GetSize( &m_scrX, &m_scrY ); // This is the size of the visible area only! // Selects direct or buffered draw: wxDC* trgDc; // J.L.Blanco @ Aug 2007: Added double buffer support if( m_enableDoubleBuffer ) { if( m_last_lx != m_scrX || m_last_ly != m_scrY ) { delete m_buff_bmp; m_buff_bmp = new wxBitmap( m_scrX, m_scrY ); m_buff_dc.SelectObject( *m_buff_bmp ); m_last_lx = m_scrX; m_last_ly = m_scrY; } trgDc = &m_buff_dc; } else { trgDc = &dc; } // Draw background: // trgDc->SetDeviceOrigin(0,0); if( wxGraphicsContext* ctx = trgDc->GetGraphicsContext() ) { if( !ctx->SetInterpolationQuality( wxINTERPOLATION_BEST ) || !ctx->SetInterpolationQuality( wxINTERPOLATION_GOOD ) ) { ctx->SetInterpolationQuality( wxINTERPOLATION_FAST ); } ctx->SetAntialiasMode( wxANTIALIAS_DEFAULT ); } trgDc->SetPen( *wxTRANSPARENT_PEN ); wxBrush brush( GetBackgroundColour() ); trgDc->SetBrush( brush ); trgDc->SetTextForeground( m_fgColour ); trgDc->DrawRectangle( 0, 0, m_scrX, m_scrY ); // Draw all the layers: // trgDc->SetDeviceOrigin( m_scrX>>1, m_scrY>>1); // Origin at the center wxLayerList::iterator li; for( li = m_layers.begin(); li != m_layers.end(); li++ ) (*li)->Plot( *trgDc, *this ); if( m_zooming ) { wxPen pen( m_fgColour, 1, wxPENSTYLE_DOT ); trgDc->SetPen( pen ); trgDc->SetBrush( *wxTRANSPARENT_BRUSH ); trgDc->DrawRectangle( m_zoomRect ); } // If doublebuffer, draw now to the window: if( m_enableDoubleBuffer ) { // trgDc->SetDeviceOrigin(0,0); // dc.SetDeviceOrigin(0,0); // Origin at the center dc.Blit( 0, 0, m_scrX, m_scrY, trgDc, 0, 0 ); } // If scrollbars are enabled, refresh them if( m_enableScrollBars ) { /* m_scroll.x = (int) floor((m_posX - m_minX)*m_scaleX); * m_scroll.y = (int) floor((m_maxY - m_posY )*m_scaleY); * Scroll(m_scroll.x, m_scroll.y);*/ // Scroll(x2p(m_posX), y2p(m_posY)); // SetVirtualSize((int) ((m_maxX - m_minX)*m_scaleX), (int) ((m_maxY - m_minY)*m_scaleY)); // int centerX = (m_scrX - m_marginLeft - m_marginRight)/2; // + m_marginLeft; // c.x = m_scrX/2; // int centerY = (m_scrY - m_marginTop - m_marginBottom)/2; // - m_marginTop; // c.y = m_scrY/2; /*SetScrollbars(1, 1, (int) ((m_maxX - m_minX)*m_scaleX), (int) ((m_maxY - m_minY)*m_scaleY));*/ // , x2p(m_posX + centerX/m_scaleX), y2p(m_posY - centerY/m_scaleY), true); } } bool mpWindow::UpdateBBox() { m_minX = 0.0; m_maxX = 1.0; m_minY = 0.0; m_maxY = 1.0; return true; } void mpWindow::UpdateAll() { if( UpdateBBox() ) { if( m_enableScrollBars ) { int cx, cy; GetClientSize( &cx, &cy ); // Do x scroll bar { // Convert margin sizes from pixels to coordinates double leftMargin = m_marginLeft / m_scaleX; // Calculate the range in coords that we want to scroll over double maxX = (m_desiredXmax > m_maxX) ? m_desiredXmax : m_maxX; double minX = (m_desiredXmin < m_minX) ? m_desiredXmin : m_minX; if( (m_posX + leftMargin) < minX ) minX = m_posX + leftMargin; // Calculate scroll bar size and thumb position int sizeX = (int) ( (maxX - minX) * m_scaleX ); int thumbX = (int) ( ( (m_posX + leftMargin) - minX ) * m_scaleX ); SetScrollbar( wxHORIZONTAL, thumbX, cx - (m_marginRight + m_marginLeft), sizeX ); } // Do y scroll bar { // Convert margin sizes from pixels to coordinates double topMargin = m_marginTop / m_scaleY; // Calculate the range in coords that we want to scroll over double maxY = (m_desiredYmax > m_maxY) ? m_desiredYmax : m_maxY; if( (m_posY - topMargin) > maxY ) maxY = m_posY - topMargin; double minY = (m_desiredYmin < m_minY) ? m_desiredYmin : m_minY; // Calculate scroll bar size and thumb position int sizeY = (int) ( (maxY - minY) * m_scaleY ); int thumbY = (int) ( ( maxY - (m_posY - topMargin) ) * m_scaleY ); SetScrollbar( wxVERTICAL, thumbY, cy - (m_marginTop + m_marginBottom), sizeY ); } } } Refresh( false ); } void mpWindow::DoScrollCalc( const int position, const int orientation ) { if( orientation == wxVERTICAL ) { // Y axis // Get top margin in coord units double topMargin = m_marginTop / m_scaleY; // Calculate maximum Y coord to be shown in the graph double maxY = m_desiredYmax > m_maxY ? m_desiredYmax : m_maxY; // Set new position SetPosY( ( maxY - (position / m_scaleY) ) + topMargin ); } else { // X Axis // Get left margin in coord units double leftMargin = m_marginLeft / m_scaleX; // Calculate minimum X coord to be shown in the graph double minX = (m_desiredXmin < m_minX) ? m_desiredXmin : m_minX; // Set new position SetPosX( ( minX + (position / m_scaleX) ) - leftMargin ); } } void mpWindow::OnScrollThumbTrack( wxScrollWinEvent& event ) { DoScrollCalc( event.GetPosition(), event.GetOrientation() ); } void mpWindow::OnScrollPageUp( wxScrollWinEvent& event ) { int scrollOrientation = event.GetOrientation(); // Get position before page up int position = GetScrollPos( scrollOrientation ); // Get thumb size int thumbSize = GetScrollThumb( scrollOrientation ); // Need to adjust position by a page position -= thumbSize; if( position < 0 ) position = 0; DoScrollCalc( position, scrollOrientation ); } void mpWindow::OnScrollPageDown( wxScrollWinEvent& event ) { int scrollOrientation = event.GetOrientation(); // Get position before page up int position = GetScrollPos( scrollOrientation ); // Get thumb size int thumbSize = GetScrollThumb( scrollOrientation ); // Get scroll range int scrollRange = GetScrollRange( scrollOrientation ); // Need to adjust position by a page position += thumbSize; if( position > (scrollRange - thumbSize) ) position = scrollRange - thumbSize; DoScrollCalc( position, scrollOrientation ); } void mpWindow::OnScrollLineUp( wxScrollWinEvent& event ) { int scrollOrientation = event.GetOrientation(); // Get position before page up int position = GetScrollPos( scrollOrientation ); // Need to adjust position by a line position -= mpSCROLL_NUM_PIXELS_PER_LINE; if( position < 0 ) position = 0; DoScrollCalc( position, scrollOrientation ); } void mpWindow::OnScrollLineDown( wxScrollWinEvent& event ) { int scrollOrientation = event.GetOrientation(); // Get position before page up int position = GetScrollPos( scrollOrientation ); // Get thumb size int thumbSize = GetScrollThumb( scrollOrientation ); // Get scroll range int scrollRange = GetScrollRange( scrollOrientation ); // Need to adjust position by a page position += mpSCROLL_NUM_PIXELS_PER_LINE; if( position > (scrollRange - thumbSize) ) position = scrollRange - thumbSize; DoScrollCalc( position, scrollOrientation ); } void mpWindow::OnScrollTop( wxScrollWinEvent& event ) { DoScrollCalc( 0, event.GetOrientation() ); } void mpWindow::OnScrollBottom( wxScrollWinEvent& event ) { int scrollOrientation = event.GetOrientation(); // Get thumb size int thumbSize = GetScrollThumb( scrollOrientation ); // Get scroll range int scrollRange = GetScrollRange( scrollOrientation ); DoScrollCalc( scrollRange - thumbSize, scrollOrientation ); } // End patch ngpaton void mpWindow::SetScaleX( double scaleX ) { if( scaleX != 0 ) m_scaleX = scaleX; UpdateAll(); } // New methods implemented by Davide Rondini unsigned int mpWindow::CountLayers() const { unsigned int layerNo = 0; for( const mpLayer* layer : m_layers ) { if( layer->HasBBox() ) layerNo++; } return layerNo; } mpLayer* mpWindow::GetLayer( int position ) const { if( ( position >= (int) m_layers.size() ) || position < 0 ) return nullptr; return m_layers[position]; } const mpLayer* mpWindow::GetLayerByName( const wxString& name ) const { for( const mpLayer* layer : m_layers ) { if( !layer->GetName().Cmp( name ) ) return layer; } return nullptr; // Not found } void mpWindow::GetBoundingBox( double* bbox ) const { bbox[0] = m_minX; bbox[1] = m_maxX; bbox[2] = m_minY; bbox[3] = m_maxY; } bool mpWindow::SaveScreenshot( const wxString& filename, wxBitmapType type, wxSize imageSize, bool fit ) { int sizeX, sizeY; int bk_scrX, bk_scrY; if( imageSize == wxDefaultSize ) { sizeX = m_scrX; sizeY = m_scrY; } else { sizeX = imageSize.x; sizeY = imageSize.y; bk_scrX = m_scrX; bk_scrY = m_scrY; SetScr( sizeX, sizeY ); } wxBitmap screenBuffer( sizeX, sizeY ); wxMemoryDC screenDC; screenDC.SelectObject( screenBuffer ); screenDC.SetPen( *wxWHITE_PEN ); screenDC.SetTextForeground( m_fgColour ); wxBrush brush( GetBackgroundColour() ); screenDC.SetBrush( brush ); screenDC.DrawRectangle( 0, 0, sizeX, sizeY ); if( fit ) Fit( m_minX, m_maxX, m_minY, m_maxY, &sizeX, &sizeY ); else Fit( m_desiredXmin, m_desiredXmax, m_desiredYmin, m_desiredYmax, &sizeX, &sizeY ); // Draw all the layers: for( mpLayer* layer : m_layers ) layer->Plot( screenDC, *this ); if( imageSize != wxDefaultSize ) { // Restore dimensions SetScr( bk_scrX, bk_scrY ); Fit( m_desiredXmin, m_desiredXmax, m_desiredYmin, m_desiredYmax, &bk_scrX, &bk_scrY ); UpdateAll(); } // Once drawing is complete, actually save screen shot wxImage screenImage = screenBuffer.ConvertToImage(); return screenImage.SaveFile( filename, type ); } void mpWindow::SetMargins( int top, int right, int bottom, int left ) { m_marginTop = top; m_marginRight = right; m_marginBottom = bottom; m_marginLeft = left; } mpInfoLayer* mpWindow::IsInsideInfoLayer( wxPoint& point ) { for( mpLayer* layer : m_layers ) { if( layer->IsInfo() ) { mpInfoLayer* tmpLyr = static_cast( layer ); if( tmpLyr->Inside( point ) ) return tmpLyr; } } return nullptr; } void mpWindow::SetLayerVisible( const wxString& name, bool viewable ) { mpLayer* lx = GetLayerByName( name ); if( lx ) { lx->SetVisible( viewable ); UpdateAll(); } } bool mpWindow::IsLayerVisible( const wxString& name ) const { const mpLayer* lx = GetLayerByName( name ); return lx ? lx->IsVisible() : false; } void mpWindow::SetLayerVisible( const unsigned int position, bool viewable ) { mpLayer* lx = GetLayer( position ); if( lx ) { lx->SetVisible( viewable ); UpdateAll(); } } bool mpWindow::IsLayerVisible( unsigned int position ) const { mpLayer* lx = GetLayer( position ); return (lx) ? lx->IsVisible() : false; } void mpWindow::SetColourTheme( const wxColour& bgColour, const wxColour& drawColour, const wxColour& axesColour ) { SetBackgroundColour( bgColour ); SetForegroundColour( drawColour ); m_bgColour = bgColour; m_fgColour = drawColour; m_axColour = axesColour; // Cycle between layers to set colours and properties to them for( mpLayer* layer : m_layers ) { if( layer->GetLayerType() == mpLAYER_AXIS ) { wxPen axisPen = layer->GetPen(); // Get the old pen to modify only colour, not style or width axisPen.SetColour( axesColour ); layer->SetPen( axisPen ); } if( layer->GetLayerType() == mpLAYER_INFO ) { wxPen infoPen = layer->GetPen(); // Get the old pen to modify only colour, not style or width infoPen.SetColour( drawColour ); layer->SetPen( infoPen ); } } } // ----------------------------------------------------------------------------- // mpFXYVector implementation - by Jose Luis Blanco (AGO-2007) // ----------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS( mpFXYVector, mpFXY ) // Constructor mpFXYVector::mpFXYVector( const wxString& name, int flags ) : mpFXY( name, flags ) { m_index = 0; m_minX = -1; m_maxX = 1; m_minY = -1; m_maxY = 1; m_type = mpLAYER_PLOT; } double mpScaleX::TransformToPlot( double x ) const { return (x + m_offset) * m_scale; } double mpScaleX::TransformFromPlot( double xplot ) const { return xplot / m_scale - m_offset; } double mpScaleY::TransformToPlot( double x ) const { return (x + m_offset) * m_scale; } double mpScaleY::TransformFromPlot( double xplot ) const { return xplot / m_scale - m_offset; } double mpScaleXLog::TransformToPlot( double x ) const { double xlogmin = log10( m_minV ); double xlogmax = log10( m_maxV ); return ( log10( x ) - xlogmin) / (xlogmax - xlogmin); } double mpScaleXLog::TransformFromPlot( double xplot ) const { double xlogmin = log10( m_minV ); double xlogmax = log10( m_maxV ); return pow( 10.0, xplot * (xlogmax - xlogmin) + xlogmin ); } #if 0 mpFSemiLogXVector::mpFSemiLogXVector( wxString name, int flags ) : mpFXYVector( name, flags ) { } IMPLEMENT_DYNAMIC_CLASS( mpFSemiLogXVector, mpFXYVector ) #endif // 0 void mpFXYVector::Rewind() { m_index = 0; } size_t mpFXYVector::GetCount() const { return m_xs.size(); } bool mpFXYVector::GetNextXY( double& x, double& y ) { if( m_index >= m_xs.size() ) { return false; } else { x = m_xs[m_index]; y = m_ys[m_index++]; return m_index <= m_xs.size(); } } void mpFXYVector::Clear() { m_xs.clear(); m_ys.clear(); } void mpFXYVector::SetData( const std::vector& xs, const std::vector& ys ) { // Check if the data vectors are of the same size if( xs.size() != ys.size() ) return; // Copy the data: m_xs = xs; m_ys = ys; // Update internal variables for the bounding box. if( xs.size() > 0 ) { m_minX = xs[0]; m_maxX = xs[0]; m_minY = ys[0]; m_maxY = ys[0]; for( const double x : xs ) { if( x < m_minX ) m_minX = x; if( x > m_maxX ) m_maxX = x; } for( const double y : ys ) { if( y < m_minY ) m_minY = y; if( y > m_maxY ) m_maxY = y; } } else { m_minX = -1; m_maxX = 1; m_minY = -1; m_maxY = 1; } } // ----------------------------------------------------------------------------- // mpText - provided by Val Greene // ----------------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS( mpText, mpLayer ) /** @param name text to be displayed * @param offsetx x position in percentage (0-100) * @param offsetx y position in percentage (0-100) */ mpText::mpText( const wxString& name, int offsetx, int offsety ) { SetName( name ); if( offsetx >= 0 && offsetx <= 100 ) m_offsetx = offsetx; else m_offsetx = 5; if( offsety >= 0 && offsety <= 100 ) m_offsety = offsety; else m_offsety = 50; m_type = mpLAYER_INFO; } /** mpText Layer plot handler. * This implementation will plot the text adjusted to the visible area. */ void mpText::Plot( wxDC& dc, mpWindow& w ) { if( m_visible ) { dc.SetPen( m_pen ); dc.SetFont( m_font ); wxCoord tw = 0, th = 0; dc.GetTextExtent( GetName(), &tw, &th ); // int left = -dc.LogicalToDeviceX(0); // int width = dc.LogicalToDeviceX(0) - left; // int bottom = dc.LogicalToDeviceY(0); // int height = bottom - -dc.LogicalToDeviceY(0); /* dc.DrawText( GetName(), * (int)((((float)width/100.0) * m_offsety) + left - (tw/2)), * (int)((((float)height/100.0) * m_offsetx) - bottom) );*/ int px = m_offsetx * ( w.GetScrX() - w.GetMarginLeft() - w.GetMarginRight() ) / 100; int py = m_offsety * ( w.GetScrY() - w.GetMarginTop() - w.GetMarginBottom() ) / 100; dc.DrawText( GetName(), px, py ); } } // ----------------------------------------------------------------------------- // mpPrintout - provided by Davide Rondini // ----------------------------------------------------------------------------- mpPrintout::mpPrintout( mpWindow* drawWindow, const wxChar* title ) : wxPrintout( title ) { drawn = false; plotWindow = drawWindow; } bool mpPrintout::OnPrintPage( int page ) { wxDC* trgDc = GetDC(); if( (trgDc) && (page == 1) ) { wxCoord m_prnX, m_prnY; int marginX = 50; int marginY = 50; trgDc->GetSize( &m_prnX, &m_prnY ); m_prnX -= (2 * marginX); m_prnY -= (2 * marginY); trgDc->SetDeviceOrigin( marginX, marginY ); // Set the scale according to the page: plotWindow->Fit( plotWindow->GetDesiredXmin(), plotWindow->GetDesiredXmax(), plotWindow->GetDesiredYmin(), plotWindow->GetDesiredYmax(), &m_prnX, &m_prnY ); // Get the colours of the plotWindow to restore them ath the end wxColour oldBgColour = plotWindow->GetBackgroundColour(); wxColour oldFgColour = plotWindow->GetForegroundColour(); wxColour oldAxColour = plotWindow->GetAxesColour(); // Draw background, ensuring to use white background for printing. trgDc->SetPen( *wxTRANSPARENT_PEN ); // wxBrush brush( plotWindow->GetBackgroundColour() ); wxBrush brush = *wxWHITE_BRUSH; trgDc->SetBrush( brush ); trgDc->DrawRectangle( 0, 0, m_prnX, m_prnY ); // Draw all the layers: // trgDc->SetDeviceOrigin( m_prnX>>1, m_prnY>>1); // Origin at the center mpLayer* layer; for( unsigned int li = 0; li < plotWindow->CountAllLayers(); li++ ) { layer = plotWindow->GetLayer( li ); layer->Plot( *trgDc, *plotWindow ); } ; // Restore device origin // trgDc->SetDeviceOrigin(0, 0); // Restore colours plotWindow->SetColourTheme( oldBgColour, oldFgColour, oldAxColour ); // Restore drawing plotWindow->Fit( plotWindow->GetDesiredXmin(), plotWindow->GetDesiredXmax(), plotWindow->GetDesiredYmin(), plotWindow->GetDesiredYmax(), nullptr, nullptr ); plotWindow->UpdateAll(); } return true; } bool mpPrintout::HasPage( int page ) { return page == 1; } // ----------------------------------------------------------------------------- // mpMovableObject - provided by Jose Luis Blanco // ----------------------------------------------------------------------------- void mpMovableObject::TranslatePoint( double x, double y, double& out_x, double& out_y ) { double ccos = cos( m_reference_phi ); // Avoid computing cos/sin twice. double csin = sin( m_reference_phi ); out_x = m_reference_x + ccos * x - csin * y; out_y = m_reference_y + csin * x + ccos * y; } // This method updates the buffers m_trans_shape_xs/ys, and the precomputed bounding box. void mpMovableObject::ShapeUpdated() { // Just in case... if( m_shape_xs.size() != m_shape_ys.size() ) { } else { double ccos = cos( m_reference_phi ); // Avoid computing cos/sin twice. double csin = sin( m_reference_phi ); m_trans_shape_xs.resize( m_shape_xs.size() ); m_trans_shape_ys.resize( m_shape_xs.size() ); std::vector::iterator itXi, itXo; std::vector::iterator itYi, itYo; m_bbox_min_x = 1e300; m_bbox_max_x = -1e300; m_bbox_min_y = 1e300; m_bbox_max_y = -1e300; for( itXo = m_trans_shape_xs.begin(), itYo = m_trans_shape_ys.begin(), itXi = m_shape_xs.begin(), itYi = m_shape_ys.begin(); itXo!=m_trans_shape_xs.end(); itXo++, itYo++, itXi++, itYi++ ) { *itXo = m_reference_x + ccos * (*itXi) - csin * (*itYi); *itYo = m_reference_y + csin * (*itXi) + ccos * (*itYi); // Keep BBox: if( *itXo < m_bbox_min_x ) m_bbox_min_x = *itXo; if( *itXo > m_bbox_max_x ) m_bbox_max_x = *itXo; if( *itYo < m_bbox_min_y ) m_bbox_min_y = *itYo; if( *itYo > m_bbox_max_y ) m_bbox_max_y = *itYo; } } } void mpMovableObject::Plot( wxDC& dc, mpWindow& w ) { if( m_visible ) { dc.SetPen( m_pen ); std::vector::iterator itX = m_trans_shape_xs.begin(); std::vector::iterator itY = m_trans_shape_ys.begin(); if( !m_continuous ) { // for some reason DrawPoint does not use the current pen, // so we use DrawLine for fat pens if( m_pen.GetWidth() <= 1 ) { while( itX!=m_trans_shape_xs.end() ) { dc.DrawPoint( w.x2p( *(itX++) ), w.y2p( *(itY++) ) ); } } else { while( itX!=m_trans_shape_xs.end() ) { wxCoord cx = w.x2p( *(itX++) ); wxCoord cy = w.y2p( *(itY++) ); dc.DrawLine( cx, cy, cx, cy ); } } } else { wxCoord cx0 = 0, cy0 = 0; bool first = true; while( itX != m_trans_shape_xs.end() ) { wxCoord cx = w.x2p( *(itX++) ); wxCoord cy = w.y2p( *(itY++) ); if( first ) { first = false; cx0 = cx; cy0 = cy; } dc.DrawLine( cx0, cy0, cx, cy ); cx0 = cx; cy0 = cy; } } if( !m_name.IsEmpty() && m_showName ) { dc.SetFont( m_font ); wxCoord tx, ty; dc.GetTextExtent( m_name, &tx, &ty ); if( HasBBox() ) { wxCoord sx = (wxCoord) ( ( m_bbox_max_x - w.GetPosX() ) * w.GetScaleX() ); wxCoord sy = (wxCoord) ( (w.GetPosY() - m_bbox_max_y ) * w.GetScaleY() ); tx = sx - tx - 8; ty = sy - 8 - ty; } else { const int sx = w.GetScrX() >> 1; const int sy = w.GetScrY() >> 1; if( (m_flags & mpALIGNMASK) == mpALIGN_NE ) { tx = sx - tx - 8; ty = -sy + 8; } else if( (m_flags & mpALIGNMASK) == mpALIGN_NW ) { tx = -sx + 8; ty = -sy + 8; } else if( (m_flags & mpALIGNMASK) == mpALIGN_SW ) { tx = -sx + 8; ty = sy - 8 - ty; } else { tx = sx - tx - 8; ty = sy - 8 - ty; } } dc.DrawText( m_name, tx, ty ); } } } // ----------------------------------------------------------------------------- // mpCovarianceEllipse - provided by Jose Luis Blanco // ----------------------------------------------------------------------------- // Called to update the m_shape_xs, m_shape_ys vectors, whenever a parameter changes. void mpCovarianceEllipse::RecalculateShape() { m_shape_xs.clear(); m_shape_ys.clear(); // Preliminary checks: if( m_quantiles < 0 ) return; if( m_cov_00 < 0 ) return; if( m_cov_11 < 0 ) return; m_shape_xs.resize( m_segments, 0 ); m_shape_ys.resize( m_segments, 0 ); // Compute the two eigenvalues of the covariance: // ------------------------------------------------- double b = -m_cov_00 - m_cov_11; double c = m_cov_00 * m_cov_11 - m_cov_01 * m_cov_01; double D = b * b - 4 * c; if( D < 0 ) return; double eigenVal0 = 0.5 * ( -b + sqrt( D ) ); double eigenVal1 = 0.5 * ( -b - sqrt( D ) ); // Compute the two corresponding eigenvectors: // ------------------------------------------------- double eigenVec0_x, eigenVec0_y; double eigenVec1_x, eigenVec1_y; if( fabs( eigenVal0 - m_cov_00 ) > 1e-6 ) { double k1x = m_cov_01 / ( eigenVal0 - m_cov_00 ); eigenVec0_y = 1; eigenVec0_x = eigenVec0_y * k1x; } else { double k1y = m_cov_01 / ( eigenVal0 - m_cov_11 ); eigenVec0_x = 1; eigenVec0_y = eigenVec0_x * k1y; } if( fabs( eigenVal1 - m_cov_00 ) > 1e-6 ) { double k2x = m_cov_01 / ( eigenVal1 - m_cov_00 ); eigenVec1_y = 1; eigenVec1_x = eigenVec1_y * k2x; } else { double k2y = m_cov_01 / ( eigenVal1 - m_cov_11 ); eigenVec1_x = 1; eigenVec1_y = eigenVec1_x * k2y; } // Normalize the eigenvectors: double len = sqrt( eigenVec0_x * eigenVec0_x + eigenVec0_y * eigenVec0_y ); eigenVec0_x /= len; // It *CANNOT* be zero eigenVec0_y /= len; len = sqrt( eigenVec1_x * eigenVec1_x + eigenVec1_y * eigenVec1_y ); eigenVec1_x /= len; // It *CANNOT* be zero eigenVec1_y /= len; // Take the sqrt of the eigenvalues (required for the ellipse scale): eigenVal0 = sqrt( eigenVal0 ); eigenVal1 = sqrt( eigenVal1 ); // Compute the 2x2 matrix M = diag(eigVal) * (~eigVec) (each eigen vector is a row): double M_00 = eigenVec0_x * eigenVal0; double M_01 = eigenVec0_y * eigenVal0; double M_10 = eigenVec1_x * eigenVal1; double M_11 = eigenVec1_y * eigenVal1; // The points of the 2D ellipse: double ang; double Aang = 6.283185308 / (m_segments - 1); int i; for( i = 0, ang = 0; i < m_segments; i++, ang += Aang ) { double ccos = cos( ang ); double csin = sin( ang ); m_shape_xs[i] = m_quantiles * (ccos * M_00 + csin * M_10 ); m_shape_ys[i] = m_quantiles * (ccos * M_01 + csin * M_11 ); } // end for points on ellipse ShapeUpdated(); } // ----------------------------------------------------------------------------- // mpPolygon - provided by Jose Luis Blanco // ----------------------------------------------------------------------------- void mpPolygon::setPoints( const std::vector& points_xs, const std::vector& points_ys, bool closedShape ) { if( points_xs.size() == points_ys.size() ) { m_shape_xs = points_xs; m_shape_ys = points_ys; if( closedShape && !points_xs.empty() ) { m_shape_xs.push_back( points_xs[0] ); m_shape_ys.push_back( points_ys[0] ); } ShapeUpdated(); } } // ----------------------------------------------------------------------------- // mpBitmapLayer - provided by Jose Luis Blanco // ----------------------------------------------------------------------------- void mpBitmapLayer::GetBitmapCopy( wxImage& outBmp ) const { if( m_validImg ) outBmp = m_bitmap; } void mpBitmapLayer::SetBitmap( const wxImage& inBmp, double x, double y, double lx, double ly ) { if( inBmp.Ok() ) { m_bitmap = inBmp; // .GetSubBitmap( wxRect(0, 0, inBmp.GetWidth(), inBmp.GetHeight())); m_min_x = x; m_min_y = y; m_max_x = x + lx; m_max_y = y + ly; m_validImg = true; } } void mpBitmapLayer::Plot( wxDC& dc, mpWindow& w ) { if( m_visible && m_validImg ) { /* 1st: We compute (x0,y0)-(x1,y1), the pixel coordinates of the real outer limits * of the image rectangle within the (screen) mpWindow. Note that these coordinates * might fall well far away from the real view limits when the user zoom in. * * 2nd: We compute (dx0,dy0)-(dx1,dy1), the pixel coordinates the rectangle that will * be actually drawn into the mpWindow, i.e. the clipped real rectangle that * avoids the non-visible parts. (offset_x,offset_y) are the pixel coordinates * that correspond to the window point (dx0,dy0) within the image "m_bitmap", and * (b_width,b_height) is the size of the bitmap patch that will be drawn. * * (x0,y0) ................. (x1,y0) * . . * . . * (x0,y1) ................ (x1,y1) * (In pixels!!) */ // 1st step ------------------------------- wxCoord x0 = w.x2p( m_min_x ); wxCoord y0 = w.y2p( m_max_y ); wxCoord x1 = w.x2p( m_max_x ); wxCoord y1 = w.y2p( m_min_y ); // 2nd step ------------------------------- // Precompute the size of the actual bitmap pixel on the screen (e.g. will be >1 if zoomed in) double screenPixelX = ( x1 - x0 ) / (double) m_bitmap.GetWidth(); double screenPixelY = ( y1 - y0 ) / (double) m_bitmap.GetHeight(); // The minimum number of pixels that the stretched image will overpass the actual mpWindow borders: wxCoord borderMarginX = (wxCoord) (screenPixelX + 1); // ceil wxCoord borderMarginY = (wxCoord) (screenPixelY + 1); // ceil // The actual drawn rectangle (dx0,dy0)-(dx1,dy1) is (x0,y0)-(x1,y1) clipped: wxCoord dx0 = x0, dx1 = x1, dy0 = y0, dy1 = y1; if( dx0 < 0 ) dx0 = -borderMarginX; if( dy0 < 0 ) dy0 = -borderMarginY; if( dx1 > w.GetScrX() ) dx1 = w.GetScrX() + borderMarginX; if( dy1 > w.GetScrY() ) dy1 = w.GetScrY() + borderMarginY; // For convenience, compute the width/height of the rectangle to be actually drawn: wxCoord d_width = dx1 - dx0 + 1; wxCoord d_height = dy1 - dy0 + 1; // Compute the pixel offsets in the internally stored bitmap: wxCoord offset_x = (wxCoord) ( (dx0 - x0) / screenPixelX ); wxCoord offset_y = (wxCoord) ( (dy0 - y0) / screenPixelY ); // and the size in pixel of the area to be actually drawn from the internally stored bitmap: wxCoord b_width = (wxCoord) ( (dx1 - dx0 + 1) / screenPixelX ); wxCoord b_height = (wxCoord) ( (dy1 - dy0 + 1) / screenPixelY ); // Is there any visible region? if( d_width>0 && d_height>0 ) { // Build the scaled bitmap from the image, only if it has changed: if( m_scaledBitmap.GetWidth()!=d_width || m_scaledBitmap.GetHeight()!=d_height || m_scaledBitmap_offset_x != offset_x || m_scaledBitmap_offset_y != offset_y ) { wxRect r( wxRect( offset_x, offset_y, b_width, b_height ) ); // Just for the case.... if( r.x < 0 ) r.x = 0; if( r.y < 0 ) r.y = 0; if( r.width>m_bitmap.GetWidth() ) r.width = m_bitmap.GetWidth(); if( r.height>m_bitmap.GetHeight() ) r.height = m_bitmap.GetHeight(); m_scaledBitmap = wxBitmap( wxBitmap( m_bitmap ).GetSubBitmap( r ).ConvertToImage() .Scale( d_width, d_height ) ); m_scaledBitmap_offset_x = offset_x; m_scaledBitmap_offset_y = offset_y; } // Draw it: dc.DrawBitmap( m_scaledBitmap, dx0, dy0, true ); } } // Draw the name label if( !m_name.IsEmpty() && m_showName ) { dc.SetFont( m_font ); wxCoord tx, ty; dc.GetTextExtent( m_name, &tx, &ty ); if( HasBBox() ) { wxCoord sx = (wxCoord) ( ( m_max_x - w.GetPosX() ) * w.GetScaleX() ); wxCoord sy = (wxCoord) ( (w.GetPosY() - m_max_y ) * w.GetScaleY() ); tx = sx - tx - 8; ty = sy - 8 - ty; } else { const int sx = w.GetScrX() >> 1; const int sy = w.GetScrY() >> 1; if( (m_flags & mpALIGNMASK) == mpALIGN_NE ) { tx = sx - tx - 8; ty = -sy + 8; } else if( (m_flags & mpALIGNMASK) == mpALIGN_NW ) { tx = -sx + 8; ty = -sy + 8; } else if( (m_flags & mpALIGNMASK) == mpALIGN_SW ) { tx = -sx + 8; ty = sy - 8 - ty; } else { tx = sx - tx - 8; ty = sy - 8 - ty; } } dc.DrawText( m_name, tx, ty ); } } void mpFXY::SetScale( mpScaleBase* scaleX, mpScaleBase* scaleY ) { m_scaleX = scaleX; m_scaleY = scaleY; UpdateScales(); } void mpFXY::UpdateScales() { if( m_scaleX ) m_scaleX->ExtendDataRange( GetMinX(), GetMaxX() ); if( m_scaleY ) m_scaleY->ExtendDataRange( GetMinY(), GetMaxY() ); } double mpFXY::s2x( double plotCoordX ) const { return m_scaleX ? m_scaleX->TransformFromPlot( plotCoordX ) : plotCoordX; } double mpFXY::s2y( double plotCoordY ) const { return m_scaleY ? m_scaleY->TransformFromPlot( plotCoordY ) : plotCoordY; } double mpFXY::x2s( double x ) const { return m_scaleX ? m_scaleX->TransformToPlot( x ) : x; } double mpFXY::y2s( double y ) const { return m_scaleY ? m_scaleY->TransformToPlot( y ) : y; }