kicad/common/widgets/mathplot.cpp

3631 lines
102 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// 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 <wx/window.h>
// 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 <widgets/mathplot.h>
#include <wx/graphics.h>
#include <wx/image.h>
#include <cmath>
#include <cstdio> // used only for debug
#include <ctime> // used for representation of x axes involving date
#include <set>
// 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<wxCoord> 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::vector<wxPoint>pointList;
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<wxPoint> 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 )
{
if( dynamic_cast<mpInfoLegend*>( m_movingInfoLayer ) )
cursor = wxCURSOR_SIZING;
else
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() ) )
{
if( dynamic_cast<mpInfoLegend*>( infoLayer ) )
cursor = wxCURSOR_SIZING;
else
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 = p0x<p1x ? p0x : p1x;
double zoom_x_max = p0x>p1x ? p0x : p1x;
double zoom_y_min = p0y<p1y ? p0y : p1y;
double zoom_y_max = p0y>p1y ? 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<mpInfoLayer*>( 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<double>& xs, const std::vector<double>& 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<double>::iterator itXi, itXo;
std::vector<double>::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<double>::iterator itX = m_trans_shape_xs.begin();
std::vector<double>::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<double>& points_xs,
const std::vector<double>& 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;
}