KiCad PCB EDA Suite
Loading...
Searching...
No Matches
mathplot.cpp
Go to the documentation of this file.
1
2// Name: mathplot.cpp
3// Purpose: Framework for plotting in wxWindows
4// Original Author: David Schalig
5// Maintainer: Davide Rondini
6// Contributors: Jose Luis Blanco, Val Greene, Maciej Suminski, Tomasz Wlostowski
7// Created: 21/07/2003
8// Last edit: 2024
9// Copyright: (c) David Schalig, Davide Rondini
10// Copyright (c) 2021-2024 KiCad Developers, see AUTHORS.txt for contributors.
11// Licence: wxWindows licence
13
14#include <wx/window.h>
15
16// Comment out for release operation:
17// (Added by J.L.Blanco, Aug 2007)
18//#define MATHPLOT_DO_LOGGING
19
20#ifdef __BORLANDC__
21#pragma hdrstop
22#endif
23
24#ifndef WX_PRECOMP
25#include "wx/object.h"
26#include "wx/font.h"
27#include "wx/colour.h"
28#include "wx/sizer.h"
29#include "wx/intl.h"
30#include "wx/dcclient.h"
31#include "wx/cursor.h"
32#include "gal/cursors.h"
33#endif
34
35#include <widgets/mathplot.h>
36#include <wx/graphics.h>
37#include <wx/image.h>
38
39#include <cmath>
40#include <cstdio> // used only for debug
41#include <ctime> // used for representation of x axes involving date
42#include <set>
43
44// Memory leak debugging
45#ifdef _DEBUG
46#define new DEBUG_NEW
47#endif
48
49// Legend margins
50#define mpLEGEND_MARGIN 5
51#define mpLEGEND_LINEWIDTH 10
52
53// See doxygen comments.
55
56// -----------------------------------------------------------------------------
57// mpLayer
58// -----------------------------------------------------------------------------
59
60IMPLEMENT_ABSTRACT_CLASS( mpLayer, wxObject )
61
64{
65 SetPen( (wxPen&) *wxBLACK_PEN );
66 SetFont( (wxFont&) *wxNORMAL_FONT );
67 m_continuous = false; // Default
68 m_showName = true; // Default
69 m_visible = true;
70}
71
72
73// -----------------------------------------------------------------------------
74// mpInfoLayer
75// -----------------------------------------------------------------------------
76IMPLEMENT_DYNAMIC_CLASS( mpInfoLayer, mpLayer )
77
79{
80 m_dim = wxRect( 0, 0, 1, 1 );
81 m_brush = *wxTRANSPARENT_BRUSH;
82 m_reference.x = 0; m_reference.y = 0;
83 m_winX = 1; // parent->GetScrX();
84 m_winY = 1; // parent->GetScrY();
86}
87
88
89mpInfoLayer::mpInfoLayer( wxRect rect, const wxBrush* brush ) :
90 m_dim( rect )
91{
92 m_brush = *brush;
93 m_reference.x = rect.x;
94 m_reference.y = rect.y;
95 m_winX = 1; // parent->GetScrX();
96 m_winY = 1; // parent->GetScrY();
98}
99
100
104
105
106bool mpInfoLayer::Inside( const wxPoint& point ) const
107{
108 return m_dim.Contains( point );
109}
110
111
112bool mpInfoLayer::OnDoubleClick( const wxPoint& point, mpWindow& w )
113{
114 return false;
115}
116
117
119{
120 m_dim.SetX( m_reference.x + delta.x );
121 m_dim.SetY( m_reference.y + delta.y );
122}
123
124
126{
127 m_reference.x = m_dim.x;
128 m_reference.y = m_dim.y;
129}
130
131
132void mpInfoLayer::Plot( wxDC& dc, mpWindow& w )
133{
134 if( m_visible )
135 {
136 // Adjust relative position inside the window
137 int scrx = w.GetScrX();
138 int scry = w.GetScrY();
139
140 // Avoid dividing by 0
141 if( scrx == 0 )
142 scrx = 1;
143
144 if( scry == 0 )
145 scry = 1;
146
147 if( ( m_winX != scrx ) || ( m_winY != scry ) )
148 {
149 if( m_winX > 1 )
150 m_dim.x = (int) floor( (double) ( m_dim.x * scrx / m_winX ) );
151
152 if( m_winY > 1 )
153 {
154 m_dim.y = (int) floor( (double) ( m_dim.y * scry / m_winY ) );
156 }
157
158 // Finally update window size
159 m_winX = scrx;
160 m_winY = scry;
161 }
162
163 dc.SetPen( m_pen );
164 dc.SetBrush( m_brush );
165 dc.DrawRectangle( m_dim.x, m_dim.y, m_dim.width, m_dim.height );
166 }
167}
168
169
171{
172 return m_dim.GetPosition();
173}
174
175
177{
178 return m_dim.GetSize();
179}
180
181
186
187
188mpInfoLegend::mpInfoLegend( wxRect rect, const wxBrush* brush ) :
189 mpInfoLayer( rect, brush )
190{
191}
192
193
197
198
199void mpInfoLegend::Plot( wxDC& dc, mpWindow& w )
200{
201 if( m_visible )
202 {
203 // Adjust relative position inside the window
204 int scrx = w.GetScrX();
205 int scry = w.GetScrY();
206
207 if( m_winX != scrx || m_winY != scry )
208 {
209 if( m_winX > 1 )
210 m_dim.x = (int) floor( (double) ( m_dim.x * scrx / m_winX ) );
211
212 if( m_winY > 1 )
213 {
214 m_dim.y = (int) floor( (double) ( m_dim.y * scry / m_winY ) );
216 }
217
218 // Finally update window size
219 m_winX = scrx;
220 m_winY = scry;
221 }
222
223 dc.SetBrush( m_brush );
224 dc.SetFont( m_font );
225
226 const int baseWidth = mpLEGEND_MARGIN * 2 + mpLEGEND_LINEWIDTH;
227 int textX = baseWidth, textY = mpLEGEND_MARGIN;
228 int plotCount = 0;
229 int posY = 0;
230 int tmpX = 0;
231 int tmpY = 0;
232 mpLayer* layer = nullptr;
233 wxPen lpen;
234 wxString label;
235
236 for( unsigned int p = 0; p < w.CountAllLayers(); p++ )
237 {
238 layer = w.GetLayer( p );
239
240 if( layer->GetLayerType() == mpLAYER_PLOT && layer->IsVisible() )
241 {
242 label = layer->GetDisplayName();
243 dc.GetTextExtent( label, &tmpX, &tmpY );
244 textX = ( textX > tmpX + baseWidth ) ? textX : tmpX + baseWidth + mpLEGEND_MARGIN;
245 textY += tmpY;
246 }
247 }
248
249 dc.SetPen( m_pen );
250 dc.SetBrush( m_brush );
251 m_dim.width = textX;
252
253 if( textY != mpLEGEND_MARGIN ) // Don't draw any thing if there are no visible layers
254 {
255 textY += mpLEGEND_MARGIN;
256 m_dim.height = textY;
257 dc.DrawRectangle( m_dim.x, m_dim.y, m_dim.width, m_dim.height );
258
259 for( unsigned int p2 = 0; p2 < w.CountAllLayers(); p2++ )
260 {
261 layer = w.GetLayer( p2 );
262
263 if( layer->GetLayerType() == mpLAYER_PLOT && layer->IsVisible() )
264 {
265 label = layer->GetDisplayName();
266 lpen = layer->GetPen();
267 dc.GetTextExtent( label, &tmpX, &tmpY );
268 dc.SetPen( lpen );
269 posY = m_dim.y + mpLEGEND_MARGIN + plotCount * tmpY + (tmpY >> 1);
270 dc.DrawLine( m_dim.x + mpLEGEND_MARGIN, // X start coord
271 posY, // Y start coord
272 m_dim.x + mpLEGEND_LINEWIDTH + mpLEGEND_MARGIN, // X end coord
273 posY );
274 dc.DrawText( label,
275 m_dim.x + baseWidth,
276 m_dim.y + mpLEGEND_MARGIN + plotCount * tmpY );
277 plotCount++;
278 }
279 }
280 }
281 }
282}
283
284
285// -----------------------------------------------------------------------------
286// mpLayer implementations - functions
287// -----------------------------------------------------------------------------
288
289IMPLEMENT_ABSTRACT_CLASS( mpFX, mpLayer )
290
291mpFX::mpFX( const wxString& name, int flags )
292{
293 SetName( name );
294 m_flags = flags;
296}
297
298
299void mpFX::Plot( wxDC& dc, mpWindow& w )
300{
301 if( m_visible )
302 {
303 dc.SetPen( m_pen );
304
305 wxCoord startPx = w.GetMarginLeft();
306 wxCoord endPx = w.GetScrX() - w.GetMarginRight();
307 wxCoord minYpx = w.GetMarginTop();
308 wxCoord maxYpx = w.GetScrY() - w.GetMarginBottom();
309
310 wxCoord iy = 0;
311
312 if( m_pen.GetWidth() <= 1 )
313 {
314 for( wxCoord i = startPx; i < endPx; ++i )
315 {
316 iy = w.y2p( GetY( w.p2x( i ) ) );
317
318 // Draw the point only if you can draw outside margins or if the point is
319 // inside margins
320 if( ( iy >= minYpx ) && ( iy <= maxYpx ) )
321 dc.DrawPoint( i, iy );
322 }
323 }
324 else
325 {
326 for( wxCoord i = startPx; i < endPx; ++i )
327 {
328 iy = w.y2p( GetY( w.p2x( i ) ) );
329
330 // Draw the point only if you can draw outside margins or if the point is
331 // inside margins
332 if( iy >= minYpx && iy <= maxYpx )
333 dc.DrawLine( i, iy, i, iy );
334 }
335 }
336
337 if( !m_name.IsEmpty() && m_showName )
338 {
339 dc.SetFont( m_font );
340
341 wxCoord tx, ty;
342 dc.GetTextExtent( m_name, &tx, &ty );
343
344 if( ( m_flags & mpALIGNMASK ) == mpALIGN_RIGHT )
345 tx = ( w.GetScrX() - tx ) - w.GetMarginRight() - 8;
346 else if( ( m_flags & mpALIGNMASK ) == mpALIGN_CENTER )
347 tx = ( ( w.GetScrX() - w.GetMarginRight() - w.GetMarginLeft() - tx ) / 2 )
348 + w.GetMarginLeft();
349 else
350 tx = w.GetMarginLeft() + 8;
351
352 dc.DrawText( m_name, tx, w.y2p( GetY( w.p2x( tx ) ) ) );
353 }
354 }
355}
356
357
358IMPLEMENT_ABSTRACT_CLASS( mpFY, mpLayer )
359
360
361mpFY::mpFY( const wxString& name, int flags )
362{
363 SetName( name );
364 m_flags = flags;
366}
367
368
369void mpFY::Plot( wxDC& dc, mpWindow& w )
370{
371 if( m_visible )
372 {
373 dc.SetPen( m_pen );
374
375 wxCoord i, ix;
376
377 wxCoord startPx = w.GetMarginLeft();
378 wxCoord endPx = w.GetScrX() - w.GetMarginRight();
379 wxCoord minYpx = w.GetMarginTop();
380 wxCoord maxYpx = w.GetScrY() - w.GetMarginBottom();
381
382 if( m_pen.GetWidth() <= 1 )
383 {
384 for( i = minYpx; i < maxYpx; ++i )
385 {
386 ix = w.x2p( GetX( w.p2y( i ) ) );
387
388 if( ( ix >= startPx ) && ( ix <= endPx ) )
389 dc.DrawPoint( ix, i );
390 }
391 }
392 else
393 {
394 for( i = 0; i< w.GetScrY(); ++i )
395 {
396 ix = w.x2p( GetX( w.p2y( i ) ) );
397
398 if( ( ix >= startPx ) && ( ix <= endPx ) )
399 dc.DrawLine( ix, i, ix, i );
400 }
401 }
402
403 if( !m_name.IsEmpty() && m_showName )
404 {
405 dc.SetFont( m_font );
406
407 wxCoord tx, ty;
408 dc.GetTextExtent( m_name, &tx, &ty );
409
410 if( ( m_flags & mpALIGNMASK ) == mpALIGN_TOP )
411 ty = w.GetMarginTop() + 8;
412 else if( ( m_flags & mpALIGNMASK ) == mpALIGN_CENTER )
413 ty = ( ( w.GetScrY() - w.GetMarginTop() - w.GetMarginBottom() - ty ) / 2 )
414 + w.GetMarginTop();
415 else
416 ty = w.GetScrY() - 8 - ty - w.GetMarginBottom();
417
418 dc.DrawText( m_name, w.x2p( GetX( w.p2y( ty ) ) ), ty );
419 }
420 }
421}
422
423
424IMPLEMENT_ABSTRACT_CLASS( mpFXY, mpLayer )
425
426
427mpFXY::mpFXY( const wxString& name, int flags )
428{
429 SetName( name );
430 m_flags = flags;
432 m_scaleX = nullptr;
433 m_scaleY = nullptr;
434
435 // Avoid not initialized members:
437}
438
439
440void mpFXY::UpdateViewBoundary( wxCoord xnew, wxCoord ynew )
441{
442 // Keep track of how many points have been drawn and the bounding box
443 maxDrawX = (xnew > maxDrawX) ? xnew : maxDrawX;
444 minDrawX = (xnew < minDrawX) ? xnew : minDrawX;
445 maxDrawY = (maxDrawY > ynew) ? maxDrawY : ynew;
446 minDrawY = (minDrawY < ynew) ? minDrawY : ynew;
447 // drawnPoints++;
448}
449
450
451void mpFXY::Plot( wxDC& dc, mpWindow& w )
452{
453 // If trace doesn't have any data yet then it won't have any scale set. In any case, there's
454 // nothing to plot.
455 if( !GetCount() )
456 return;
457
458 wxCHECK_RET( m_scaleX, wxS( "X scale was not set" ) );
459 wxCHECK_RET( m_scaleY, wxS( "Y scale was not set" ) );
460
461 if( !m_visible )
462 return;
463
464 wxCoord startPx = w.GetMarginLeft();
465 wxCoord endPx = w.GetScrX() - w.GetMarginRight();
466 wxCoord minYpx = w.GetMarginTop();
467 wxCoord maxYpx = w.GetScrY() - w.GetMarginBottom();
468
469 // Check for a collapsed window before we try to allocate a negative number of points
470 if( endPx <= startPx || minYpx >= maxYpx )
471 return;
472
473 dc.SetPen( m_pen );
474
475 double x, y;
476 // Do this to reset the counters to evaluate bounding box for label positioning
477 Rewind();
478 GetNextXY( x, y );
479 maxDrawX = x;
480 minDrawX = x;
481 maxDrawY = y;
482 minDrawY = y;
483 // drawnPoints = 0;
484 Rewind();
485
486 dc.SetClippingRegion( startPx, minYpx, endPx - startPx + 1, maxYpx - minYpx + 1 );
487
488 if( !m_continuous )
489 {
490 bool first = true;
491 wxCoord ix = 0;
492 std::set<wxCoord> ys;
493
494 while( GetNextXY( x, y ) )
495 {
496 double px = m_scaleX->TransformToPlot( x );
497 double py = m_scaleY->TransformToPlot( y );
498 wxCoord newX = w.x2p( px );
499
500 if( first )
501 {
502 ix = newX;
503 first = false;
504 }
505
506 if( newX == ix ) // continue until a new X coordinate is reached
507 {
508 // collect all unique points
509 ys.insert( w.y2p( py ) );
510 continue;
511 }
512
513 for( auto& iy: ys )
514 {
515 if( ( ix >= startPx ) && ( ix <= endPx ) && ( iy >= minYpx ) && ( iy <= maxYpx ) )
516 {
517 // for some reason DrawPoint does not use the current pen, so we use
518 // DrawLine for fat pens
519 if( m_pen.GetWidth() <= 1 )
520 dc.DrawPoint( ix, iy );
521 else
522 dc.DrawLine( ix, iy, ix, iy );
523
524 UpdateViewBoundary( ix, iy );
525 }
526 }
527
528 ys.clear();
529 ix = newX;
530 ys.insert( w.y2p( py ) );
531 }
532 }
533 else for( int sweep = 0; sweep < GetSweepCount(); ++sweep )
534 {
535 SetSweepWindow( sweep );
536
537 int count = 0;
538 int x0 = 0; // X position of merged current vertical line
539 int ymin0 = 0; // y min coord of merged current vertical line
540 int ymax0 = 0; // y max coord of merged current vertical line
541 int dupx0 = 0; // count of currently merged vertical lines
542 wxPoint line_start; // starting point of the current line to draw
543
544 // A buffer to store coordinates of lines to draw
545 std::vector<wxPoint>pointList;
546 pointList.reserve( ( endPx - startPx ) * 2 );
547
548 double nextX;
549 double nextY;
550 bool hasNext = GetNextXY( nextX, nextY );
551 bool offRight = false;
552
553 // Note: we can use dc.DrawLines() only for a reasonable number or points (<10,000),
554 // because at least on Windows dc.DrawLines() can hang for a lot of points. Note that
555 // this includes the intermediate points when drawing dotted lines.
556
557 // Our first-pass optimization is to exclude points outside the view, and aggregate all
558 // contiguous y values found at a single x value into a vertical line.
559 while( hasNext )
560 {
561 x = nextX;
562 y = nextY;
563 hasNext = GetNextXY( nextX, nextY );
564
565 double px = m_scaleX->TransformToPlot( x );
566 double py = m_scaleY->TransformToPlot( y );
567
568 wxCoord x1 = w.x2p( px );
569 wxCoord y1 = w.y2p( py );
570
571 // Note that we can't start *right* at the edge of the view because we need to
572 // interpolate between two points, one of which might be outside the view.
573 // Note: x1 is a value truncated from px by w.x2p(). So to be sure the first point
574 // is drawn, the x1 low limit is startPx-1 in plot coordinates
575 if( x1 < startPx-1 )
576 {
577 wxCoord nextX1 = w.x2p( m_scaleX->TransformToPlot( nextX ) );
578
579 if( nextX1 < startPx-1 )
580 continue;
581 }
582 else if( x1 > endPx )
583 {
584 if( offRight )
585 continue;
586 else
587 offRight = true;
588 }
589
590 if( !count || line_start.x != x1 )
591 {
592 // We've aggregated a bunch of y values with a shared x value, so we need to draw
593 // a vertical line. However, short vertical segments spoil anti-aliasing on
594 // Retina displays, so only draw them if they're "significant" (the user should
595 // zoom in if they need a more accurate picture).
596 if( count && dupx0 > 1 && abs( ymax0 - ymin0 ) > 2 )
597 dc.DrawLine( x0, ymin0, x0, ymax0 );
598
599 x0 = x1;
600 ymin0 = ymax0 = y1;
601 dupx0 = 0;
602
603 pointList.emplace_back( wxPoint( x1, y1 ) );
604
605 line_start.x = x1;
606 line_start.y = y1;
607 count++;
608 }
609 else
610 {
611 ymin0 = std::min( ymin0, y1 );
612 ymax0 = std::max( ymax0, y1 );
613 x0 = x1;
614 dupx0++;
615 }
616 }
617
618 if( pointList.size() > 1 )
619 {
620 // Second pass optimization is to merge horizontal segments. This improves the look
621 // of dotted lines, keeps the point count down, and it's easy.
622 //
623 // This pass also includes a final protection to keep MSW from hanging by chunking to
624 // a size it can handle.
625 std::vector<wxPoint> drawPoints;
626 drawPoints.reserve( ( endPx - startPx ) * 2 );
627
628#ifdef __WXMSW__
629 int chunkSize = 10000;
630#else
631 int chunkSize = 100000;
632#endif
633 if( dc.GetPen().GetStyle() == wxPENSTYLE_DOT )
634 chunkSize /= 500;
635
636 drawPoints.push_back( pointList[0] ); // push the first point in list
637
638 for( size_t ii = 1; ii < pointList.size()-1; ii++ )
639 {
640 // Skip intermediate points between the first point and the last point of the
641 // segment candidate
642 if( drawPoints.back().y == pointList[ii].y &&
643 drawPoints.back().y == pointList[ii+1].y )
644 {
645 continue;
646 }
647 else
648 {
649 drawPoints.push_back( pointList[ii] );
650
651 if( (int) drawPoints.size() > chunkSize )
652 {
653 dc.DrawLines( (int) drawPoints.size(), &drawPoints[0] );
654 drawPoints.clear();
655
656 // Restart the line with the current point
657 drawPoints.push_back( pointList[ii] );
658 }
659 }
660 }
661
662 // push the last point to draw in list
663 if( drawPoints.back() != pointList.back() )
664 drawPoints.push_back( pointList.back() );
665
666 dc.DrawLines( (int) drawPoints.size(), &drawPoints[0] );
667 }
668 }
669
670 if( !m_name.IsEmpty() && m_showName )
671 {
672 dc.SetFont( m_font );
673
674 wxCoord tx, ty;
675 dc.GetTextExtent( m_name, &tx, &ty );
676
677 if( ( m_flags & mpALIGNMASK ) == mpALIGN_NW )
678 {
679 tx = minDrawX + 8;
680 ty = maxDrawY + 8;
681 }
682 else if( ( m_flags & mpALIGNMASK ) == mpALIGN_NE )
683 {
684 tx = maxDrawX - tx - 8;
685 ty = maxDrawY + 8;
686 }
687 else if( ( m_flags & mpALIGNMASK ) == mpALIGN_SE )
688 {
689 tx = maxDrawX - tx - 8;
690 ty = minDrawY - ty - 8;
691 }
692 else
693 {
694 // mpALIGN_SW
695 tx = minDrawX + 8;
696 ty = minDrawY - ty - 8;
697 }
698
699 dc.DrawText( m_name, tx, ty );
700 }
701
702 dc.DestroyClippingRegion();
703}
704
705
706// -----------------------------------------------------------------------------
707// mpLayer implementations - furniture (scales, ...)
708// -----------------------------------------------------------------------------
709
710#define mpLN10 2.3025850929940456840179914546844
711
713{
714 double minV, maxV, minVvis, maxVvis;
715
716 GetDataRange( minV, maxV );
717 getVisibleDataRange( w, minVvis, maxVvis );
718
719 m_absVisibleMaxV = std::max( std::abs( minVvis ), std::abs( maxVvis ) );
720
721 m_tickValues.clear();
722 m_tickLabels.clear();
723
724 double minErr = 1000000000000.0;
725 double bestStep = 1.0;
726 int m_scrX = w.GetXScreen();
727
728 for( int i = 10; i <= 20; i += 2 )
729 {
730 double curr_step = fabs( maxVvis - minVvis ) / (double) i;
731 double base = pow( 10, floor( log10( curr_step ) ) );
732 double stepInt = floor( curr_step / base ) * base;
733 double err = fabs( curr_step - stepInt );
734
735 if( err < minErr )
736 {
737 minErr = err;
738 bestStep = stepInt;
739 }
740 }
741
742 double numberSteps = floor( ( maxVvis - minVvis ) / bestStep );
743
744 // Half the number of ticks according to window size.
745 // The value 96 is used to have only 4 ticks when m_scrX is 268.
746 // For each 96 device context units, is possible to add a new tick.
747 while( numberSteps - 2.0 >= m_scrX/96.0 )
748 {
749 bestStep *= 2;
750 numberSteps = floor( ( maxVvis - minVvis ) / bestStep );
751 }
752
753 double v = floor( minVvis / bestStep ) * bestStep;
754 double zeroOffset = 100000000.0;
755
756 while( v < maxVvis )
757 {
758 m_tickValues.push_back( v );
759
760 if( fabs( v ) < zeroOffset )
761 zeroOffset = fabs( v );
762
763 v += bestStep;
764 }
765
766 if( zeroOffset <= bestStep )
767 {
768 for( double& t : m_tickValues )
769 t -= zeroOffset;
770 }
771
772 for( double t : m_tickValues )
773 m_tickLabels.emplace_back( t );
774
775 updateTickLabels( dc, w );
776}
777
778
780{
781 m_rangeSet = false;
782 m_axisLocked = false;
783 m_axisMin = 0;
784 m_axisMax = 0;
786
787 // initialize these members mainly to avoid not initialized values
788 m_offset = 0.0;
789 m_scale = 1.0;
790 m_absVisibleMaxV = 0.0;
791 m_flags = 0; // Flag for axis alignment
792 m_ticks = true; // Flag to toggle between ticks or grid
793 m_minV = 0.0;
794 m_maxV = 0.0;
796 m_maxLabelWidth = 1;
797}
798
799
801{
803 m_maxLabelWidth = 0;
804
805 for( const TICK_LABEL& tickLabel : m_tickLabels )
806 {
807 int tx, ty;
808 const wxString s = tickLabel.label;
809
810 dc.GetTextExtent( s, &tx, &ty );
811 m_maxLabelHeight = std::max( ty, m_maxLabelHeight );
812 m_maxLabelWidth = std::max( tx, m_maxLabelWidth );
813 }
814}
815
816
818{
819 formatLabels();
820 computeLabelExtents( dc, w );
821}
822
823
824void mpScaleY::getVisibleDataRange( mpWindow& w, double& minV, double& maxV )
825{
826 wxCoord minYpx = w.GetMarginTop();
827 wxCoord maxYpx = w.GetScrY() - w.GetMarginBottom();
828
829 double pymin = w.p2y( minYpx );
830 double pymax = w.p2y( maxYpx );
831
832 minV = TransformFromPlot( pymax );
833 maxV = TransformFromPlot( pymin );
834}
835
836
838{
839 // No need for slave ticks when there aren't 2 main ticks for them to go between
840 if( m_masterScale->m_tickValues.size() < 2 )
841 return;
842
843 m_tickValues.clear();
844 m_tickLabels.clear();
845
846 double p0 = m_masterScale->TransformToPlot( m_masterScale->m_tickValues[0] );
847 double p1 = m_masterScale->TransformToPlot( m_masterScale->m_tickValues[1] );
848
849 m_scale = 1.0 / ( m_maxV - m_minV );
850 m_offset = -m_minV;
851
852 double y_slave0 = p0 / m_scale;
853 double y_slave1 = p1 / m_scale;
854
855 double dy_slave = ( y_slave1 - y_slave0 );
856 double exponent = floor( log10( dy_slave ) );
857 double base = dy_slave / pow( 10.0, exponent );
858
859 double dy_scaled = ceil( 2.0 * base ) / 2.0 * pow( 10.0, exponent );
860
861 double minvv, maxvv;
862
863 getVisibleDataRange( w, minvv, maxvv );
864
865 minvv = floor( minvv / dy_scaled ) * dy_scaled;
866
867 m_scale = 1.0 / ( m_maxV - m_minV );
868 m_scale *= dy_slave / dy_scaled;
869
870 m_offset = p0 / m_scale - minvv;
871
872 m_tickValues.clear();
873
875
876 for( double tickValue : m_masterScale->m_tickValues )
877 {
878 double m = TransformFromPlot( m_masterScale->TransformToPlot( tickValue ) );
879 m_tickValues.push_back( m );
880 m_tickLabels.emplace_back( m );
881 m_absVisibleMaxV = std::max( m_absVisibleMaxV, fabs( m ) );
882 }
883}
884
885
887{
888 double minVvis, maxVvis;
889
890 if( m_axisLocked )
891 {
892 minVvis = m_axisMin;
893 maxVvis = m_axisMax;
895 m_scale = 1.0 / ( m_axisMax - m_axisMin );
896 }
897 else if( m_masterScale )
898 {
900 updateTickLabels( dc, w );
901
902 return;
903 }
904 else
905 {
906 getVisibleDataRange( w, minVvis, maxVvis );
907 }
908
909 m_absVisibleMaxV = std::max( std::abs( minVvis ), std::abs( maxVvis ) );
910 m_tickValues.clear();
911 m_tickLabels.clear();
912
913 double minErr = 1000000000000.0;
914 double bestStep = 1.0;
915 int m_scrY = w.GetYScreen();
916
917 for( int i = 10; i <= 20; i += 2 )
918 {
919 double curr_step = fabs( maxVvis - minVvis ) / (double) i;
920 double base = pow( 10, floor( log10( curr_step ) ) );
921 double stepInt = floor( curr_step / base ) * base;
922 double err = fabs( curr_step - stepInt );
923
924 if( err< minErr )
925 {
926 minErr = err;
927 bestStep = stepInt;
928 }
929 }
930
931 double numberSteps = floor( ( maxVvis - minVvis ) / bestStep );
932
933 // Half the number of ticks according to window size.
934 // For each 32 device context units, is possible to add a new tick.
935 while( numberSteps >= m_scrY / 32.0 )
936 {
937 bestStep *= 2;
938 numberSteps = floor( ( maxVvis - minVvis ) / bestStep );
939 }
940
941 double v = floor( minVvis / bestStep ) * bestStep;
942 double zeroOffset = 100000000.0;
943 const int iterLimit = 1000;
944 int i = 0;
945
946 while( v <= maxVvis && i < iterLimit )
947 {
948 m_tickValues.push_back( v );
949
950 if( fabs( v ) < zeroOffset )
951 zeroOffset = fabs( v );
952
953 v += bestStep;
954 i++;
955 }
956
957 // something weird happened...
958 if( i == iterLimit )
959 m_tickValues.clear();
960
961 if( zeroOffset <= bestStep )
962 {
963 for( double& t : m_tickValues )
964 t -= zeroOffset;
965 }
966
967 for( double t : m_tickValues )
968 m_tickLabels.emplace_back( t );
969
970 updateTickLabels( dc, w );
971}
972
973
974void mpScaleXBase::getVisibleDataRange( mpWindow& w, double& minV, double& maxV )
975{
976 wxCoord startPx = w.GetMarginLeft();
977 wxCoord endPx = w.GetScrX() - w.GetMarginRight();
978 double pxmin = w.p2x( startPx );
979 double pxmax = w.p2x( endPx );
980
981 minV = TransformFromPlot( pxmin );
982 maxV = TransformFromPlot( pxmax );
983}
984
985
987{
988 double minV, maxV, minVvis, maxVvis;
989
990 GetDataRange( minV, maxV );
991 getVisibleDataRange( w, minVvis, maxVvis );
992
993 // double decades = log( maxV / minV ) / log(10);
994 double minDecade = pow( 10, floor( log10( minV ) ) );
995 double maxDecade = pow( 10, ceil( log10( maxV ) ) );
996 double visibleDecades = log( maxVvis / minVvis ) / log( 10 );
997 double step = 10.0;
998 int m_scrX = w.GetXScreen();
999
1000 double d;
1001
1002 m_tickValues.clear();
1003 m_tickLabels.clear();
1004
1005 if( minDecade == 0.0 )
1006 return;
1007
1008 // Half the number of ticks according to window size.
1009 // The value 96 is used to have only 4 ticks when m_scrX is 268.
1010 // For each 96 device context units, is possible to add a new tick.
1011 while( visibleDecades - 2 >= m_scrX / 96.0 )
1012 {
1013 step *= 10.0;
1014 visibleDecades = log( maxVvis / minVvis ) / log( step );
1015
1016 if( !std::isfinite( visibleDecades ) )
1017 break;
1018 }
1019
1020 for( d = minDecade; d<=maxDecade; d *= step )
1021 {
1022 m_tickLabels.emplace_back( d );
1023
1024 for( double dd = d; dd < d * step; dd += d )
1025 {
1026 if( visibleDecades < 2 )
1027 m_tickLabels.emplace_back( dd );
1028
1029 m_tickValues.push_back( dd );
1030 }
1031 }
1032
1033 updateTickLabels( dc, w );
1034}
1035
1036
1037IMPLEMENT_ABSTRACT_CLASS( mpScaleXBase, mpLayer )
1038IMPLEMENT_DYNAMIC_CLASS( mpScaleX, mpScaleXBase )
1039IMPLEMENT_DYNAMIC_CLASS( mpScaleXLog, mpScaleXBase )
1040
1041
1042mpScaleXBase::mpScaleXBase( const wxString& name, int flags, bool ticks, unsigned int type )
1043{
1044 SetName( name );
1045 SetFont( (wxFont&) *wxSMALL_FONT );
1046 SetPen( (wxPen&) *wxGREY_PEN );
1047 m_flags = flags;
1048 m_ticks = ticks;
1050}
1051
1052
1053mpScaleX::mpScaleX( const wxString& name, int flags, bool ticks, unsigned int type ) :
1054 mpScaleXBase( name, flags, ticks, type )
1055{
1056}
1057
1058
1059mpScaleXLog::mpScaleXLog( const wxString& name, int flags, bool ticks, unsigned int type ) :
1060 mpScaleXBase( name, flags, ticks, type )
1061{
1062}
1063
1064
1065void mpScaleXBase::Plot( wxDC& dc, mpWindow& w )
1066{
1067 int tx, ty;
1068
1069 m_offset = -m_minV;
1070 m_scale = 1.0 / ( m_maxV - m_minV );
1071
1072 recalculateTicks( dc, w );
1073
1074 if( m_visible )
1075 {
1076 dc.SetPen( m_pen );
1077 dc.SetFont( m_font );
1078 int orgy = 0;
1079
1080 const int extend = w.GetScrX();
1081
1082 if( m_flags == mpALIGN_CENTER )
1083 orgy = w.y2p( 0 );
1084
1085 if( m_flags == mpALIGN_TOP )
1086 orgy = w.GetMarginTop();
1087
1088 if( m_flags == mpALIGN_BOTTOM )
1089 orgy = w.GetScrY() - w.GetMarginBottom();
1090
1092 orgy = w.GetScrY() - 1;
1093
1095 orgy = 1;
1096
1097 wxCoord startPx = w.GetMarginLeft();
1098 wxCoord endPx = w.GetScrX() - w.GetMarginRight();
1099 wxCoord minYpx = w.GetMarginTop();
1100 wxCoord maxYpx = w.GetScrY() - w.GetMarginBottom();
1101
1102 // int tmp=-65535;
1103
1104 // Control labels height to decide where to put axis name (below labels or on top of axis).
1105 int labelH = m_maxLabelHeight;
1106
1107 // int maxExtent = tc.MaxLabelWidth();
1108 for( double tp : m_tickValues )
1109 {
1110 double px = TransformToPlot( tp );
1111 const int p = (int) ( ( px - w.GetPosX() ) * w.GetScaleX() );
1112
1113 if( p >= startPx && p <= endPx )
1114 {
1115 if( m_ticks ) // draw axis ticks
1116 {
1118 dc.DrawLine( p, orgy, p, orgy - 4 );
1119 else
1120 dc.DrawLine( p, orgy, p, orgy + 4 );
1121 }
1122 else // draw grid dotted lines
1123 {
1124 m_pen.SetStyle( wxPENSTYLE_DOT );
1125 dc.SetPen( m_pen );
1126
1127 if( m_flags == mpALIGN_BOTTOM )
1128 {
1129 m_pen.SetStyle( wxPENSTYLE_DOT );
1130 dc.SetPen( m_pen );
1131 dc.DrawLine( p, orgy + 4, p, minYpx );
1132 m_pen.SetStyle( wxPENSTYLE_SOLID );
1133 dc.SetPen( m_pen );
1134 dc.DrawLine( p, orgy + 4, p, orgy - 4 );
1135 }
1136 else
1137 {
1138 if( m_flags == mpALIGN_TOP )
1139 dc.DrawLine( p, orgy - 4, p, maxYpx );
1140 else
1141 dc.DrawLine( p, minYpx, p, maxYpx );
1142 }
1143
1144 m_pen.SetStyle( wxPENSTYLE_SOLID );
1145 dc.SetPen( m_pen );
1146 }
1147 }
1148 }
1149
1150 m_pen.SetStyle( wxPENSTYLE_SOLID );
1151 dc.SetPen( m_pen );
1152 dc.DrawLine( startPx, minYpx, endPx, minYpx );
1153 dc.DrawLine( startPx, maxYpx, endPx, maxYpx );
1154
1155 // Actually draw labels, taking care of not overlapping them, and distributing them
1156 // regularly
1157 for( const TICK_LABEL& tickLabel : m_tickLabels )
1158 {
1159 if( !tickLabel.visible )
1160 continue;
1161
1162 double px = TransformToPlot( tickLabel.pos );
1163 const int p = (int) ( ( px - w.GetPosX() ) * w.GetScaleX() );
1164
1165 if( ( p >= startPx ) && ( p <= endPx ) )
1166 {
1167 // Write ticks labels in s string
1168 wxString s = tickLabel.label;
1169
1170 dc.GetTextExtent( s, &tx, &ty );
1171
1172 if( ( m_flags == mpALIGN_BORDER_BOTTOM ) || ( m_flags == mpALIGN_TOP ) )
1173 dc.DrawText( s, p - tx / 2, orgy - 4 - ty );
1174 else
1175 dc.DrawText( s, p - tx / 2, orgy + 4 );
1176 }
1177 }
1178
1179 // Draw axis name
1180 dc.GetTextExtent( m_name, &tx, &ty );
1181
1182 switch( m_nameFlags )
1183 {
1185 dc.DrawText( m_name, extend - tx - 4, orgy - 8 - ty - labelH );
1186 break;
1187
1188 case mpALIGN_BOTTOM:
1189 dc.DrawText( m_name, ( endPx + startPx ) / 2 - tx / 2, orgy + 6 + labelH );
1190 break;
1191
1192 case mpALIGN_CENTER:
1193 dc.DrawText( m_name, extend - tx - 4, orgy - 4 - ty );
1194 break;
1195
1196 case mpALIGN_TOP:
1197 if( w.GetMarginTop() > (ty + labelH + 8) )
1198 dc.DrawText( m_name, ( endPx - startPx - tx ) >> 1, orgy - 6 - ty - labelH );
1199 else
1200 dc.DrawText( m_name, extend - tx - 4, orgy + 4 );
1201
1202 break;
1203
1204 case mpALIGN_BORDER_TOP:
1205 dc.DrawText( m_name, extend - tx - 4, orgy + 6 + labelH );
1206 break;
1207
1208 default:
1209 break;
1210 }
1211 }
1212}
1213
1214
1215IMPLEMENT_DYNAMIC_CLASS( mpScaleY, mpLayer )
1216
1217
1218mpScaleY::mpScaleY( const wxString& name, int flags, bool ticks )
1219{
1220 SetName( name );
1221 SetFont( (wxFont&) *wxSMALL_FONT );
1222 SetPen( (wxPen&) *wxGREY_PEN );
1223 m_flags = flags;
1224 m_ticks = ticks;
1226 m_masterScale = nullptr;
1228}
1229
1230
1231void mpScaleY::Plot( wxDC& dc, mpWindow& w )
1232{
1233 m_offset = -m_minV;
1234 m_scale = 1.0 / ( m_maxV - m_minV );
1235
1236 recalculateTicks( dc, w );
1237
1238 if( m_visible )
1239 {
1240 dc.SetPen( m_pen );
1241 dc.SetFont( m_font );
1242
1243 int orgx = 0;
1244
1245 if( m_flags == mpALIGN_CENTER )
1246 orgx = w.x2p( 0 );
1247
1248 if( m_flags == mpALIGN_LEFT )
1249 orgx = w.GetMarginLeft();
1250
1251 if( m_flags == mpALIGN_RIGHT )
1252 orgx = w.GetScrX() - w.GetMarginRight();
1253
1254 if( m_flags == mpALIGN_FAR_RIGHT )
1255 orgx = w.GetScrX() - ( w.GetMarginRight() / 2 );
1256
1258 orgx = w.GetScrX() - 1;
1259
1261 orgx = 1;
1262
1263 wxCoord endPx = w.GetScrX() - w.GetMarginRight();
1264 wxCoord minYpx = w.GetMarginTop();
1265 wxCoord maxYpx = w.GetScrY() - w.GetMarginBottom();
1266
1267 // Draw line
1268 dc.DrawLine( orgx, minYpx, orgx, maxYpx );
1269
1270 wxCoord tx, ty;
1271 wxString s;
1272 wxString fmt;
1273
1274 int labelW = 0;
1275
1276 // Before staring cycle, calculate label height
1277 int labelHeight = 0;
1278 s.Printf( fmt, 0 );
1279 dc.GetTextExtent( s, &tx, &labelHeight );
1280
1281 for( double tp : m_tickValues )
1282 {
1283 double py = TransformToPlot( tp );
1284 const int p = (int) ( ( w.GetPosY() - py ) * w.GetScaleY() );
1285
1286 if( p >= minYpx && p <= maxYpx )
1287 {
1288 if( m_ticks ) // Draw axis ticks
1289 {
1291 dc.DrawLine( orgx, p, orgx + 4, p );
1292 else
1293 dc.DrawLine( orgx - 4, p, orgx, p );
1294 }
1295 else
1296 {
1297 dc.DrawLine( orgx - 4, p, orgx + 4, p );
1298
1299 m_pen.SetStyle( wxPENSTYLE_DOT );
1300 dc.SetPen( m_pen );
1301
1302 dc.DrawLine( orgx - 4, p, endPx, p );
1303
1304 m_pen.SetStyle( wxPENSTYLE_SOLID );
1305 dc.SetPen( m_pen );
1306 }
1307
1308 // Print ticks labels
1309 }
1310 }
1311
1312 for( const TICK_LABEL& tickLabel : m_tickLabels )
1313 {
1314 double py = TransformToPlot( tickLabel.pos );
1315 const int p = (int) ( ( w.GetPosY() - py ) * w.GetScaleY() );
1316
1317 if( !tickLabel.visible )
1318 continue;
1319
1320 if( p >= minYpx && p <= maxYpx )
1321 {
1322 s = tickLabel.label;
1323 dc.GetTextExtent( s, &tx, &ty );
1324
1327 dc.DrawText( s, orgx + 4, p - ty / 2 );
1328 else
1329 dc.DrawText( s, orgx - 4 - tx, p - ty / 2 ); // ( s, orgx+4, p-ty/2);
1330 }
1331 }
1332
1333 // Draw axis name
1334 dc.GetTextExtent( m_name, &tx, &ty );
1335
1336 switch( m_nameFlags )
1337 {
1339 dc.DrawText( m_name, labelW + 8, 4 );
1340 break;
1341
1342 case mpALIGN_LEFT:
1343 dc.DrawText( m_name, orgx - ( tx / 2 ), minYpx - ty - 4 );
1344 break;
1345
1346 case mpALIGN_CENTER:
1347 dc.DrawText( m_name, orgx + 4, 4 );
1348 break;
1349
1350 case mpALIGN_RIGHT:
1351 case mpALIGN_FAR_RIGHT:
1352 dc.DrawText( m_name, orgx - ( tx / 2 ), minYpx - ty - 4 );
1353 break;
1354
1356 dc.DrawText( m_name, orgx - 6 - tx - labelW, 4 );
1357 break;
1358
1359 default:
1360 break;
1361 }
1362 }
1363}
1364
1365
1366// -----------------------------------------------------------------------------
1367// mpWindow
1368// -----------------------------------------------------------------------------
1369
1370IMPLEMENT_DYNAMIC_CLASS( mpWindow, wxWindow )
1371
1372BEGIN_EVENT_TABLE( mpWindow, wxWindow )
1373EVT_PAINT( mpWindow::OnPaint )
1374EVT_SIZE( mpWindow::OnSize )
1375
1376EVT_MIDDLE_DOWN( mpWindow::OnMouseMiddleDown ) // JLB
1377EVT_RIGHT_UP( mpWindow::OnShowPopupMenu )
1378EVT_MOUSEWHEEL( mpWindow::onMouseWheel ) // JLB
1379EVT_MAGNIFY( mpWindow::onMagnify )
1380EVT_MOTION( mpWindow::onMouseMove ) // JLB
1381EVT_LEFT_DOWN( mpWindow::onMouseLeftDown )
1382EVT_LEFT_DCLICK( mpWindow::onMouseLeftDClick )
1383EVT_LEFT_UP( mpWindow::onMouseLeftRelease )
1384
1386EVT_MENU( mpID_FIT, mpWindow::OnFit )
1391END_EVENT_TABLE()
1392
1393
1398
1399
1400mpWindow::mpWindow( wxWindow* parent, wxWindowID id ) :
1401 mpWindow( DelegatingContructorTag(), parent, id, wxDefaultPosition, wxDefaultSize, 0,
1402 wxT( "mathplot" ) )
1403{
1404 m_popmenu.Append( mpID_ZOOM_UNDO, _( "Undo Last Zoom" ),
1405 _( "Return zoom to level prior to last zoom action" ) );
1406 m_popmenu.Append( mpID_ZOOM_REDO, _( "Redo Last Zoom" ),
1407 _( "Return zoom to level prior to last zoom undo" ) );
1408 m_popmenu.AppendSeparator();
1409 m_popmenu.Append( mpID_ZOOM_IN, _( "Zoom In" ), _( "Zoom in plot view." ) );
1410 m_popmenu.Append( mpID_ZOOM_OUT, _( "Zoom Out" ), _( "Zoom out plot view." ) );
1411 m_popmenu.Append( mpID_CENTER, _( "Center on Cursor" ),
1412 _( "Center plot view to this position" ) );
1413 m_popmenu.Append( mpID_FIT, _( "Fit on Screen" ), _( "Set plot view to show all items" ) );
1414
1415 m_layers.clear();
1416 SetBackgroundColour( *wxWHITE );
1417 m_bgColour = *wxWHITE;
1418 m_fgColour = *wxBLACK;
1419
1420 SetSizeHints( 128, 128 );
1421
1422 // J.L.Blanco: Eliminates the "flick" with the double buffer.
1423 SetBackgroundStyle( wxBG_STYLE_CUSTOM );
1424
1426 UpdateAll();
1427}
1428
1429
1431{
1432 // Free all the layers:
1433 DelAllLayers( true, false );
1434
1435 delete m_buff_bmp;
1436 m_buff_bmp = nullptr;
1437}
1438
1439
1440// Mouse handler, for detecting when the user drag with the right button or just "clicks" for
1441// the menu.
1442// JLB
1443void mpWindow::OnMouseMiddleDown( wxMouseEvent& event )
1444{
1445 m_mouseMClick.x = event.GetX();
1446 m_mouseMClick.y = event.GetY();
1447}
1448
1449
1450void mpWindow::onMagnify( wxMouseEvent& event )
1451{
1453 {
1454 event.Skip();
1455 return;
1456 }
1457
1458 float zoom = event.GetMagnification() + 1.0f;
1459 wxPoint pos( event.GetX(), event.GetY() );
1460
1461 if( zoom > 1.0f )
1462 ZoomIn( pos, zoom );
1463 else if( zoom < 1.0f )
1464 ZoomOut( pos, 1.0f / zoom );
1465}
1466
1467
1468// Process mouse wheel events
1469// JLB
1470void mpWindow::onMouseWheel( wxMouseEvent& event )
1471{
1473 {
1474 event.Skip();
1475 return;
1476 }
1477
1478 const wxMouseWheelAxis axis = event.GetWheelAxis();
1479 const int modifiers = event.GetModifiers();
1481
1482 if( axis == wxMOUSE_WHEEL_HORIZONTAL )
1483 {
1484 action = m_mouseWheelActions.horizontal;
1485 }
1486 else if( modifiers == wxMOD_NONE )
1487 {
1488 action = m_mouseWheelActions.verticalUnmodified;
1489 }
1490 else if( modifiers == wxMOD_CONTROL )
1491 {
1492 action = m_mouseWheelActions.verticalWithCtrl;
1493 }
1494 else if( modifiers == wxMOD_SHIFT )
1495 {
1496 action = m_mouseWheelActions.verticalWithShift;
1497 }
1498 else if( modifiers == wxMOD_ALT )
1499 {
1500 action = m_mouseWheelActions.verticalWithAlt;
1501 }
1502 else
1503 {
1504 event.Skip();
1505 return;
1506 }
1507
1508 PerformMouseWheelAction( event, action );
1509}
1510
1511
1512// If the user "drags" with the right button pressed, do "pan"
1513// JLB
1514void mpWindow::onMouseMove( wxMouseEvent& event )
1515{
1517 {
1518 event.Skip();
1519 return;
1520 }
1521
1522 wxCursor cursor = wxCURSOR_MAGNIFIER;
1523
1524 if( event.m_middleDown )
1525 {
1526 cursor = wxCURSOR_ARROW;
1527
1528 // The change:
1529 int Ax = m_mouseMClick.x - event.GetX();
1530 int Ay = m_mouseMClick.y - event.GetY();
1531
1532 // For the next event, use relative to this coordinates.
1533 m_mouseMClick.x = event.GetX();
1534 m_mouseMClick.y = event.GetY();
1535
1536 if( Ax )
1537 {
1538 double Ax_units = Ax / m_scaleX;
1539 SetXView( m_posX + Ax_units, m_desiredXmax + Ax_units, m_desiredXmin + Ax_units );
1540 }
1541
1542 if( Ay )
1543 {
1544 double Ay_units = -Ay / m_scaleY;
1545 SetYView( m_posY + Ay_units, m_desiredYmax + Ay_units, m_desiredYmin + Ay_units );
1546 }
1547
1548 if( Ax || Ay )
1549 UpdateAll();
1550 }
1551 else if( event.m_leftDown )
1552 {
1553 if( m_movingInfoLayer )
1554 {
1555 if( dynamic_cast<mpInfoLegend*>( m_movingInfoLayer ) )
1556 cursor = wxCURSOR_SIZING;
1557 else
1558 cursor = wxCURSOR_SIZEWE;
1559
1560 wxPoint moveVector( event.GetX() - m_mouseLClick.x, event.GetY() - m_mouseLClick.y );
1561 m_movingInfoLayer->Move( moveVector );
1562 m_zooming = false;
1563 }
1564 else
1565 {
1566 cursor = wxCURSOR_MAGNIFIER;
1567
1568 wxClientDC dc( this );
1569 wxPen pen( m_fgColour, 1, wxPENSTYLE_DOT );
1570 dc.SetPen( pen );
1571 dc.SetBrush( *wxTRANSPARENT_BRUSH );
1572 dc.DrawRectangle( m_mouseLClick.x, m_mouseLClick.y,
1573 event.GetX() - m_mouseLClick.x, event.GetY() - m_mouseLClick.y );
1574 m_zooming = true;
1577 m_zoomRect.width = event.GetX() - m_mouseLClick.x;
1578 m_zoomRect.height = event.GetY() - m_mouseLClick.y;
1579 }
1580
1581 UpdateAll();
1582 }
1583 else
1584 {
1585 for( mpLayer* layer : m_layers)
1586 {
1587 if( layer->IsInfo() && layer->IsVisible() )
1588 {
1589 mpInfoLayer* infoLayer = (mpInfoLayer*) layer;
1590
1591 if( infoLayer->Inside( event.GetPosition() ) )
1592 {
1593 if( dynamic_cast<mpInfoLegend*>( infoLayer ) )
1594 cursor = wxCURSOR_SIZING;
1595 else
1596 cursor = wxCURSOR_SIZEWE;
1597 }
1598 }
1599 }
1600 }
1601
1602 SetCursor( cursor );
1603
1604 event.Skip();
1605}
1606
1607
1608void mpWindow::onMouseLeftDown( wxMouseEvent& event )
1609{
1610 m_mouseLClick.x = event.GetX();
1611 m_mouseLClick.y = event.GetY();
1612 m_zooming = true;
1613 wxPoint pointClicked = event.GetPosition();
1614 m_movingInfoLayer = IsInsideInfoLayer( pointClicked );
1615
1616 event.Skip();
1617}
1618
1619
1620void mpWindow::onMouseLeftDClick( wxMouseEvent& event )
1621{
1622 wxPoint pointClicked = event.GetPosition();
1623
1624 if( mpInfoLayer* infoLayer = IsInsideInfoLayer( pointClicked ) )
1625 {
1626 if( infoLayer->OnDoubleClick( pointClicked, *this ) )
1627 {
1628 UpdateAll();
1629 return;
1630 }
1631 }
1632
1633 event.Skip();
1634}
1635
1636
1637void mpWindow::onMouseLeftRelease( wxMouseEvent& event )
1638{
1639 wxPoint release( event.GetX(), event.GetY() );
1640 wxPoint press( m_mouseLClick.x, m_mouseLClick.y );
1641
1642 m_zooming = false;
1643
1644 if( m_movingInfoLayer != nullptr )
1645 {
1646 m_movingInfoLayer->UpdateReference();
1647 m_movingInfoLayer = nullptr;
1648 }
1649 else
1650 {
1651 if( release != press )
1652 ZoomRect( press, release );
1653 }
1654
1655 event.Skip();
1656}
1657
1658
1660{
1661 if( UpdateBBox() )
1663}
1664
1665
1666// JL
1667void mpWindow::Fit( double xMin, double xMax, double yMin, double yMax, const wxCoord* printSizeX,
1668 const wxCoord* printSizeY, wxOrientation directions )
1669{
1670 const bool isPrinting = printSizeX != nullptr && printSizeY != nullptr;
1671
1672 // Save desired borders:
1673 double newDesiredXmin = xMin;
1674 double newDesiredXmax = xMax;
1675 double newDesiredYmin = yMin;
1676 double newDesiredYmax = yMax;
1677
1678 // Provide a gap between the extrema of the curve and the top/bottom edges of the
1679 // plot area. Not to be confused with the left/right/top/bottom margins outside the plot area.
1680 const double xGap = fabs( xMax - xMin ) * m_leftRightPlotGapFactor;
1681 const double yGap = fabs( yMax - yMin ) * m_topBottomPlotGapFactor;
1682 xMin -= xGap;
1683 xMax += xGap;
1684 yMin -= yGap;
1685 yMax += yGap;
1686
1687 int newScrX = m_scrX;
1688 int newScrY = m_scrY;
1689
1690 if( isPrinting )
1691 {
1692 // Printer:
1693 newScrX = *printSizeX;
1694 newScrY = *printSizeY;
1695 }
1696 else
1697 {
1698 // Normal case (screen):
1699 GetClientSize( &newScrX, &newScrY );
1700 }
1701
1702 // Compute the width/height in pixels for the plot area.
1703 const int plotScreenWidth = newScrX - m_marginLeft - m_marginRight;
1704 const int plotScreenHeight = newScrY - m_marginTop - m_marginBottom;
1705
1706 // Adjust scale so that desired X/Y span plus extra gap fits in the plot area
1707 double desiredSpanX = xMax - xMin;
1708 double desiredSpanY = yMax - yMin;
1709 double newScaleX = ( desiredSpanX != 0 ) ? double( plotScreenWidth ) / desiredSpanX : 1;
1710 double newScaleY = ( desiredSpanY != 0 ) ? double( plotScreenHeight ) / desiredSpanY : 1;
1711
1712 // Adjust corner coordinates:
1713 // Upstream's aspect lock code has been removed, so no need to account for centering.
1714 double newPosX = xMin - ( m_marginLeft / newScaleX );
1715 double newPosY = yMax + ( m_marginTop / newScaleY );
1716
1717 // Commit above changes to member variables only if enabled for their respective dimension.
1718 if( ( ( directions & wxHORIZONTAL ) != 0 ) || isPrinting )
1719 {
1720 // Don't commit the passed desired bounds when printing
1721 if( !isPrinting )
1722 {
1723 m_desiredXmin = newDesiredXmin;
1724 m_desiredXmax = newDesiredXmax;
1725 }
1726
1727 m_scrX = newScrX;
1728 m_scaleX = newScaleX;
1729 m_posX = newPosX;
1730 }
1731
1732 if( ( ( directions & wxVERTICAL ) != 0 ) || isPrinting )
1733 {
1734 // Don't commit the passed desired bounds when printing
1735 if( !isPrinting )
1736 {
1737 m_desiredYmin = newDesiredYmin;
1738 m_desiredYmax = newDesiredYmax;
1739 }
1740
1741 m_scrY = newScrY;
1742 m_scaleY = newScaleY;
1743 m_posY = newPosY;
1744 }
1745
1746 // It is VERY IMPORTANT to NOT call Refresh if we are drawing to the printer!!
1747 // Otherwise, the DC dimensions will be those of the window instead of the printer device
1748 // The caller wanting to print should perform another Fit() afterwards to restore this
1749 // object's state.
1750 if( !isPrinting )
1751 UpdateAll();
1752}
1753
1754
1755void mpWindow::AdjustLimitedView( wxOrientation directions )
1756{
1757 if( !m_enableLimitedView )
1758 return;
1759
1760 // The m_desired* members are expressed in plot coordinates.
1761 // They should be clamped against their respective m_minX, m_maxX, m_minY, m_maxY limits.
1762
1763 if( ( directions & wxHORIZONTAL ) != 0 )
1764 {
1765 if( m_desiredXmin < m_minX )
1766 {
1767 double diff = m_minX - m_desiredXmin;
1768 m_posX += diff;
1769 m_desiredXmax += diff;
1771 }
1772
1773 if( m_desiredXmax > m_maxX )
1774 {
1775 double diff = m_desiredXmax - m_maxX;
1776 m_posX -= diff;
1777 m_desiredXmin -= diff;
1779 }
1780 }
1781
1782 if( ( directions & wxVERTICAL ) != 0 )
1783 {
1784 if( m_desiredYmin < m_minY )
1785 {
1786 double diff = m_minY - m_desiredYmin;
1787 m_posY += diff;
1788 m_desiredYmax += diff;
1790 }
1791
1792 if( m_desiredYmax > m_maxY )
1793 {
1794 double diff = m_desiredYmax - m_maxY;
1795 m_posY -= diff;
1796 m_desiredYmin -= diff;
1798 }
1799 }
1800}
1801
1802
1803bool mpWindow::SetXView( double pos, double desiredMax, double desiredMin )
1804{
1805 // TODO (ecorm): Investigate X scale flickering when panning at minimum zoom level
1806 // Possible cause: When AdjustLimitedView subtracts the out-of-bound delta, it does not
1807 // revert back to the exact same original coordinates due to floating point rounding errors.
1808 m_posX = pos;
1809 m_desiredXmax = desiredMax;
1810 m_desiredXmin = desiredMin;
1811 AdjustLimitedView( wxHORIZONTAL );
1812
1813 return true;
1814}
1815
1816
1817bool mpWindow::SetYView( double pos, double desiredMax, double desiredMin )
1818{
1819 m_posY = pos;
1820 m_desiredYmax = desiredMax;
1821 m_desiredYmin = desiredMin;
1822 AdjustLimitedView( wxVERTICAL );
1823
1824 return true;
1825}
1826
1827
1828void mpWindow::ZoomIn( const wxPoint& centerPoint )
1829{
1830 ZoomIn( centerPoint, zoomIncrementalFactor, wxBOTH );
1831}
1832
1833
1834void mpWindow::ZoomIn( const wxPoint& centerPoint, double zoomFactor, wxOrientation directions )
1835{
1836 DoZoom( centerPoint, zoomFactor, directions );
1837}
1838
1839
1840void mpWindow::ZoomOut( const wxPoint& centerPoint )
1841{
1842 ZoomOut( centerPoint, zoomIncrementalFactor, wxBOTH );
1843}
1844
1845
1846void mpWindow::ZoomOut( const wxPoint& centerPoint, double zoomFactor, wxOrientation directions )
1847{
1848 if( zoomFactor == 0 )
1849 zoomFactor = 1.0;
1850
1851 DoZoom( centerPoint, 1.0 / zoomFactor, directions );
1852}
1853
1854
1855void mpWindow::ZoomRect( wxPoint p0, wxPoint p1 )
1856{
1858
1859 // Constrain given rectangle to plot area
1860 const int pMinX = m_marginLeft;
1861 const int pMaxX = m_scrX - m_marginRight;
1862 const int pMinY = m_marginTop;
1863 const int pMaxY = m_scrY - m_marginBottom;
1864 p0.x = std::max( p0.x, pMinX );
1865 p0.x = std::min( p0.x, pMaxX );
1866 p0.y = std::max( p0.y, pMinY );
1867 p0.y = std::min( p0.y, pMaxY );
1868 p1.x = std::max( p1.x, pMinX );
1869 p1.x = std::min( p1.x, pMaxX );
1870 p1.y = std::max( p1.y, pMinY );
1871 p1.y = std::min( p1.y, pMaxY );
1872
1873 // Compute the 2 corners in graph coordinates:
1874 double p0x = p2x( p0.x );
1875 double p0y = p2y( p0.y );
1876 double p1x = p2x( p1.x );
1877 double p1y = p2y( p1.y );
1878
1879 // Order them:
1880 double zoom_x_min = p0x<p1x ? p0x : p1x;
1881 double zoom_x_max = p0x>p1x ? p0x : p1x;
1882 double zoom_y_min = p0y<p1y ? p0y : p1y;
1883 double zoom_y_max = p0y>p1y ? p0y : p1y;
1884
1885 if( m_yLocked )
1886 {
1887 zoom_y_min = m_desiredYmin;
1888 zoom_y_max = m_desiredYmax;
1889 }
1890
1891 Fit( zoom_x_min, zoom_x_max, zoom_y_min, zoom_y_max );
1892
1893 // Even with the input rectangle constrained to the plot area, it's still possible for the
1894 // resulting view to exceed limits when a portion of the gap is grabbed.
1896
1897 // These additional checks are needed because AdjustLimitedView only adjusts the position
1898 // and not the scale.
1899 wxOrientation directionsNeedingRefitting = ViewNeedsRefitting( wxBOTH );
1900
1901 if( directionsNeedingRefitting != 0 )
1902 Fit( m_minX, m_maxX, m_minY, m_maxY, nullptr, nullptr, directionsNeedingRefitting );
1903}
1904
1905
1906void mpWindow::pushZoomUndo( const std::array<double, 4>& aZoom )
1907{
1908 m_undoZoomStack.push( aZoom );
1909
1910 while( !m_redoZoomStack.empty() )
1911 m_redoZoomStack.pop();
1912}
1913
1914
1916{
1917 if( m_undoZoomStack.size() )
1918 {
1920
1921 std::array<double, 4> zoom = m_undoZoomStack.top();
1922 m_undoZoomStack.pop();
1923
1924 Fit( zoom[0], zoom[1], zoom[2], zoom[3] );
1926 }
1927}
1928
1929
1931{
1932 if( m_redoZoomStack.size() )
1933 {
1935
1936 std::array<double, 4> zoom = m_redoZoomStack.top();
1937 m_redoZoomStack.pop();
1938
1939 Fit( zoom[0], zoom[1], zoom[2], zoom[3] );
1941 }
1942}
1943
1944
1945void mpWindow::OnShowPopupMenu( wxMouseEvent& event )
1946{
1947 m_clickedX = event.GetX();
1948 m_clickedY = event.GetY();
1949
1950 m_popmenu.Enable( mpID_ZOOM_UNDO, !m_undoZoomStack.empty() );
1951 m_popmenu.Enable( mpID_ZOOM_REDO, !m_redoZoomStack.empty() );
1952
1953 PopupMenu( &m_popmenu, event.GetX(), event.GetY() );
1954}
1955
1956
1957void mpWindow::OnFit( wxCommandEvent& WXUNUSED( event ) )
1958{
1960
1961 Fit();
1962}
1963
1964
1965void mpWindow::OnCenter( wxCommandEvent& WXUNUSED( event ) )
1966{
1967 GetClientSize( &m_scrX, &m_scrY );
1968 int centerX = ( m_scrX - m_marginLeft - m_marginRight ) / 2;
1969 int centerY = ( m_scrY - m_marginTop - m_marginBottom ) / 2;
1970 SetPos( p2x( m_clickedX - centerX ), p2y( m_clickedY - centerY ) );
1971}
1972
1973
1984
1985
1986void mpWindow::onZoomIn( wxCommandEvent& WXUNUSED( event ) )
1987{
1988 ZoomIn( wxPoint( m_mouseMClick.x, m_mouseMClick.y ) );
1989}
1990
1991
1992void mpWindow::onZoomOut( wxCommandEvent& WXUNUSED( event ) )
1993{
1994 ZoomOut();
1995}
1996
1997
1998void mpWindow::onZoomUndo( wxCommandEvent& WXUNUSED( event ) )
1999{
2000 ZoomUndo();
2001}
2002
2003
2004void mpWindow::onZoomRedo( wxCommandEvent& WXUNUSED( event ) )
2005{
2006 ZoomRedo();
2007}
2008
2009
2010void mpWindow::OnSize( wxSizeEvent& WXUNUSED( event ) )
2011{
2012 // Try to fit again with the new window size:
2014}
2015
2016
2017bool mpWindow::AddLayer( mpLayer* layer, bool refreshDisplay )
2018{
2019 if( layer )
2020 {
2021 m_layers.push_back( layer );
2022
2023 if( refreshDisplay )
2024 UpdateAll();
2025
2026 return true;
2027 }
2028
2029 return false;
2030}
2031
2032
2033bool mpWindow::DelLayer( mpLayer* layer, bool alsoDeleteObject, bool refreshDisplay )
2034{
2035 wxLayerList::iterator layIt;
2036
2037 for( layIt = m_layers.begin(); layIt != m_layers.end(); layIt++ )
2038 {
2039 if( *layIt == layer )
2040 {
2041 // Also delete the object?
2042 if( alsoDeleteObject )
2043 delete *layIt;
2044
2045 m_layers.erase( layIt ); // this deleted the reference only
2046
2047 if( refreshDisplay )
2048 UpdateAll();
2049
2050 return true;
2051 }
2052 }
2053
2054 return false;
2055}
2056
2057
2058void mpWindow::DelAllLayers( bool alsoDeleteObject, bool refreshDisplay )
2059{
2060 while( m_layers.size()>0 )
2061 {
2062 // Also delete the object?
2063 if( alsoDeleteObject )
2064 delete m_layers[0];
2065
2066 m_layers.erase( m_layers.begin() ); // this deleted the reference only
2067 }
2068
2069 if( refreshDisplay )
2070 UpdateAll();
2071}
2072
2073
2074void mpWindow::OnPaint( wxPaintEvent& WXUNUSED( event ) )
2075{
2076 wxPaintDC paintDC( this );
2077
2078 paintDC.GetSize( &m_scrX, &m_scrY ); // This is the size of the visible area only!
2079
2080 // Selects direct or buffered draw:
2081 wxDC* targetDC = &paintDC;
2082
2083 // J.L.Blanco @ Aug 2007: Added double buffer support
2085 {
2086 if( m_last_lx != m_scrX || m_last_ly != m_scrY )
2087 {
2088 delete m_buff_bmp;
2089 m_buff_bmp = new wxBitmap( m_scrX, m_scrY );
2090 m_buff_dc.SelectObject( *m_buff_bmp );
2091 m_last_lx = m_scrX;
2092 m_last_ly = m_scrY;
2093 }
2094
2095 targetDC = &m_buff_dc;
2096 }
2097
2098 if( wxGraphicsContext* ctx = targetDC->GetGraphicsContext() )
2099 {
2100 if( !ctx->SetInterpolationQuality( wxINTERPOLATION_BEST ) )
2101 if( !ctx->SetInterpolationQuality( wxINTERPOLATION_GOOD ) )
2102 ctx->SetInterpolationQuality( wxINTERPOLATION_FAST );
2103
2104 ctx->SetAntialiasMode( wxANTIALIAS_DEFAULT );
2105 }
2106
2107 // Draw background:
2108 targetDC->SetPen( *wxTRANSPARENT_PEN );
2109 wxBrush brush( GetBackgroundColour() );
2110 targetDC->SetBrush( brush );
2111 targetDC->SetTextForeground( m_fgColour );
2112 targetDC->DrawRectangle( 0, 0, m_scrX, m_scrY );
2113
2114 // Draw all the layers:
2115 for( mpLayer* layer : m_layers )
2116 layer->Plot( *targetDC, *this );
2117
2118 if( m_zooming )
2119 {
2120 wxPen pen( m_fgColour, 1, wxPENSTYLE_DOT );
2121 targetDC->SetPen( pen );
2122 targetDC->SetBrush( *wxTRANSPARENT_BRUSH );
2123 targetDC->DrawRectangle( m_zoomRect );
2124 }
2125
2126 // If doublebuffer, draw now to the window:
2128 paintDC.Blit( 0, 0, m_scrX, m_scrY, targetDC, 0, 0 );
2129}
2130
2131
2132void mpWindow::DoZoom( const wxPoint& centerPoint, double zoomFactor, wxOrientation directions )
2133{
2134 if( m_yLocked )
2135 {
2136 if( directions == wxVERTICAL )
2137 return;
2138
2139 directions = wxHORIZONTAL;
2140 }
2141
2142 const bool horizontally = ( directions & wxHORIZONTAL ) != 0;
2143 const bool vertically = ( directions & wxVERTICAL ) != 0;
2144
2146
2147 // Preserve the position of the clicked point:
2148 wxPoint c( centerPoint );
2149 if( c == wxDefaultPosition )
2150 {
2151 GetClientSize( &m_scrX, &m_scrY );
2152 c.x = ( m_scrX - m_marginLeft - m_marginRight ) / 2 + m_marginLeft;
2153 c.y = ( m_scrY - m_marginTop - m_marginBottom ) / 2 + m_marginTop;
2154 }
2155 else
2156 {
2157 c.x = std::max( c.x, m_marginLeft );
2158 c.x = std::min( c.x, m_scrX - m_marginRight );
2159 c.y = std::max( c.y, m_marginTop );
2160 c.y = std::min( c.y, m_scrY - m_marginBottom );
2161 }
2162
2163 // Zoom in/out:
2164 const double MAX_SCALE = 1e6;
2165 const double newScaleX = horizontally ? ( m_scaleX * zoomFactor ) : m_scaleX;
2166 const double newScaleY = vertically ? ( m_scaleY * zoomFactor ) : m_scaleY;
2167
2168 // Baaaaad things happen when you zoom in too much..
2169 if( newScaleX > MAX_SCALE || newScaleY > MAX_SCALE )
2170 return;
2171
2172 if( horizontally )
2173 {
2174 // Transform the clicked X point to layer coordinates:
2175 const double prior_layer_x = p2x( c.x );
2176
2177 // Adjust the new X scale and plot X origin:
2178 m_scaleX = newScaleX;
2179 m_posX = prior_layer_x - c.x / newScaleX;
2180
2181 // Recompute the desired X view extents:
2183 }
2184
2185 if( vertically )
2186 {
2187 // Transform the clicked Y point to layer coordinates:
2188 const double prior_layer_y = p2y( c.y );
2189
2190 // Adjust the new Y scale and plot Y origin:
2191 m_scaleY = newScaleY;
2192 m_posY = prior_layer_y + c.y / newScaleY;
2193
2194 // Recompute the desired Y view extents:
2196 }
2197
2198 AdjustLimitedView( directions );
2199
2200 if( zoomFactor < 1.0 )
2201 {
2202 // These additional checks are needed because AdjustLimitedView only adjusts the position
2203 // and not the scale.
2204 wxOrientation directionsNeedingRefitting = ViewNeedsRefitting( directions );
2205
2206 // If the view is still out-of-limits after AdjustLimitedView is called, perform a Fit
2207 // along the offending dimension(s).
2208 if( directionsNeedingRefitting != 0 )
2209 Fit( m_minX, m_maxX, m_minY, m_maxY, nullptr, nullptr, directionsNeedingRefitting );
2210 }
2211
2212 UpdateAll();
2213}
2214
2215
2216void mpWindow::RecomputeDesiredX( double& min, double& max )
2217{
2218 const int plotScreenWidth = m_scrX - m_marginLeft - m_marginRight;
2219 const double plotSpanX = plotScreenWidth / m_scaleX;
2220 const double desiredSpanX = plotSpanX / ( 2 * m_leftRightPlotGapFactor + 1 );
2221 const double xGap = desiredSpanX * m_leftRightPlotGapFactor;
2222 min = m_posX + ( m_marginLeft / m_scaleX ) + xGap;
2223 max = m_desiredXmin + desiredSpanX;
2224}
2225
2226
2227void mpWindow::RecomputeDesiredY( double& min, double& max )
2228{
2229 const int plotScreenHeight = m_scrY - m_marginTop - m_marginBottom;
2230 const double plotSpanY = plotScreenHeight / m_scaleY;
2231 const double desiredSpanY = plotSpanY / ( 2 * m_topBottomPlotGapFactor + 1 );
2232 const double yGap = desiredSpanY * m_topBottomPlotGapFactor;
2233 max = m_posY - ( m_marginTop / m_scaleY ) - yGap;
2234 min = m_desiredYmax - desiredSpanY;
2235}
2236
2237
2238wxOrientation mpWindow::ViewNeedsRefitting( wxOrientation directions ) const
2239{
2240 if( !m_enableLimitedView )
2241 return static_cast<wxOrientation>( 0 );
2242
2243 // Allow a gap between the extrema of the curve and the edges of the plot area. Not to be
2244 // confused with the left/right/top/bottom margins outside the plot area.
2245 const double xGap = fabs( m_maxX - m_minX ) * m_leftRightPlotGapFactor;
2246 const double yGap = fabs( m_maxY - m_minY ) * m_topBottomPlotGapFactor;
2247
2248 wxOrientation result = {};
2249
2250 if( ( directions & wxHORIZONTAL ) != 0 )
2251 {
2252 if( ( m_desiredXmax > m_maxX + xGap ) || ( m_desiredXmin < m_minX - xGap ) )
2253 result = static_cast<wxOrientation>( result | wxHORIZONTAL );
2254 }
2255
2256 if( ( directions & wxVERTICAL ) != 0 )
2257 {
2258 if( ( m_desiredYmax > m_maxY + yGap ) || ( m_desiredYmin < m_minY - yGap ) )
2259 result = static_cast<wxOrientation>( result | wxVERTICAL );
2260 }
2261
2262 return result;
2263}
2264
2265
2266void mpWindow::PerformMouseWheelAction( wxMouseEvent& event, MouseWheelAction action )
2267{
2268 const int change = event.GetWheelRotation();
2269 const double changeUnitsX = change / m_scaleX;
2270 const double changeUnitsY = change / m_scaleY;
2271 const wxPoint clickPt( event.GetX(), event.GetY() );
2272
2273 switch( action )
2274 {
2275 case MouseWheelAction::NONE: break;
2276
2278 SetXView( m_posX + changeUnitsX, m_desiredXmax + changeUnitsX,
2279 m_desiredXmin + changeUnitsX );
2280 UpdateAll();
2281 break;
2282
2284 SetXView( m_posX - changeUnitsX, m_desiredXmax - changeUnitsX,
2285 m_desiredXmin - changeUnitsX );
2286 UpdateAll();
2287 break;
2288
2290 if( !m_yLocked )
2291 {
2292 SetYView( m_posY + changeUnitsY, m_desiredYmax + changeUnitsY,
2293 m_desiredYmin + changeUnitsY );
2294 UpdateAll();
2295 }
2296
2297 break;
2298
2300 if( event.GetWheelRotation() > 0 )
2301 ZoomIn( clickPt );
2302 else
2303 ZoomOut( clickPt );
2304
2305 break;
2306
2308 if( event.GetWheelRotation() > 0 )
2309 ZoomIn( clickPt, zoomIncrementalFactor, wxHORIZONTAL );
2310 else
2311 ZoomOut( clickPt, zoomIncrementalFactor, wxHORIZONTAL );
2312
2313 break;
2314
2316 if( event.GetWheelRotation() > 0 )
2317 ZoomIn( clickPt, zoomIncrementalFactor, wxVERTICAL );
2318 else
2319 ZoomOut( clickPt, zoomIncrementalFactor, wxVERTICAL );
2320
2321 break;
2322
2323 default:
2324 break;
2325 }
2326}
2327
2328
2330{
2331 m_minX = 0.0;
2332 m_maxX = 1.0;
2333 m_minY = 0.0;
2334 m_maxY = 1.0;
2335
2336 return true;
2337}
2338
2339
2341{
2342 UpdateBBox();
2343 Refresh( false );
2344}
2345
2346
2347void mpWindow::SetScaleX( double scaleX )
2348{
2349 if( scaleX != 0 )
2350 m_scaleX = scaleX;
2351
2352 UpdateAll();
2353}
2354
2355
2356// New methods implemented by Davide Rondini
2357
2358mpLayer* mpWindow::GetLayer( int position ) const
2359{
2360 if( ( position >= (int) m_layers.size() ) || position < 0 )
2361 return nullptr;
2362
2363 return m_layers[position];
2364}
2365
2366
2367const mpLayer* mpWindow::GetLayerByName( const wxString& name ) const
2368{
2369 for( const mpLayer* layer : m_layers )
2370 {
2371 if( !layer->GetName().Cmp( name ) )
2372 return layer;
2373 }
2374
2375 return nullptr; // Not found
2376}
2377
2378
2379void mpWindow::GetBoundingBox( double* bbox ) const
2380{
2381 bbox[0] = m_minX;
2382 bbox[1] = m_maxX;
2383 bbox[2] = m_minY;
2384 bbox[3] = m_maxY;
2385}
2386
2387
2388bool mpWindow::SaveScreenshot( wxImage& aImage, wxSize aImageSize, bool aFit )
2389{
2390 int sizeX, sizeY;
2391
2392 if( aImageSize == wxDefaultSize )
2393 {
2394 sizeX = m_scrX;
2395 sizeY = m_scrY;
2396 }
2397 else
2398 {
2399 sizeX = aImageSize.x;
2400 sizeY = aImageSize.y;
2401 SetScr( sizeX, sizeY );
2402 }
2403
2404 wxBitmap screenBuffer( sizeX, sizeY );
2405 wxMemoryDC screenDC;
2406 screenDC.SelectObject( screenBuffer );
2407 screenDC.SetPen( *wxWHITE_PEN );
2408 screenDC.SetTextForeground( m_fgColour );
2409 wxBrush brush( GetBackgroundColour() );
2410 screenDC.SetBrush( brush );
2411 screenDC.DrawRectangle( 0, 0, sizeX, sizeY );
2412
2413 if( aFit )
2414 Fit( m_minX, m_maxX, m_minY, m_maxY, &sizeX, &sizeY );
2415 else
2417
2418 // Draw all the layers:
2419 for( mpLayer* layer : m_layers )
2420 layer->Plot( screenDC, *this );
2421
2422 if( aImageSize != wxDefaultSize )
2423 {
2424 // Restore dimensions
2425 int bk_scrX = m_scrX;
2426 int bk_scrY = m_scrY;
2427 SetScr( bk_scrX, bk_scrY );
2428 Fit( m_desiredXmin, m_desiredXmax, m_desiredYmin, m_desiredYmax, &bk_scrX, &bk_scrY );
2429 UpdateAll();
2430 }
2431
2432 // Once drawing is complete, actually save screen shot
2433 aImage = screenBuffer.ConvertToImage();
2434
2435 return true;
2436}
2437
2438
2439void mpWindow::SetMargins( int top, int right, int bottom, int left )
2440{
2441 m_marginTop = top;
2443 m_marginBottom = bottom;
2445}
2446
2447
2449{
2450 for( mpLayer* layer : m_layers )
2451 {
2452 if( layer->IsInfo() )
2453 {
2454 mpInfoLayer* tmpLyr = static_cast<mpInfoLayer*>( layer );
2455
2456 if( tmpLyr->Inside( point ) )
2457 return tmpLyr;
2458 }
2459 }
2460
2461 return nullptr;
2462}
2463
2464
2465void mpWindow::SetLayerVisible( const wxString& name, bool viewable )
2466{
2467 if( mpLayer* lx = GetLayerByName( name ) )
2468 {
2469 lx->SetVisible( viewable );
2470 UpdateAll();
2471 }
2472}
2473
2474
2475bool mpWindow::IsLayerVisible( const wxString& name ) const
2476{
2477 if( const mpLayer* lx = GetLayerByName( name ) )
2478 return lx->IsVisible();
2479
2480 return false;
2481}
2482
2483
2484void mpWindow::SetLayerVisible( const unsigned int position, bool viewable )
2485{
2486 if( mpLayer* lx = GetLayer( position ) )
2487 {
2488 lx->SetVisible( viewable );
2489 UpdateAll();
2490 }
2491}
2492
2493
2494bool mpWindow::IsLayerVisible( unsigned int position ) const
2495{
2496 if( const mpLayer* lx = GetLayer( position ) )
2497 return lx->IsVisible();
2498
2499 return false;
2500}
2501
2502
2503void mpWindow::SetColourTheme( const wxColour& bgColour, const wxColour& drawColour,
2504 const wxColour& axesColour )
2505{
2506 SetBackgroundColour( bgColour );
2507 SetForegroundColour( drawColour );
2508 m_bgColour = bgColour;
2509 m_fgColour = drawColour;
2510 m_axColour = axesColour;
2511
2512 // Cycle between layers to set colours and properties to them
2513 for( mpLayer* layer : m_layers )
2514 {
2515 if( layer->GetLayerType() == mpLAYER_AXIS )
2516 {
2517 // Get the old pen to modify only colour, not style or width.
2518 wxPen axisPen = layer->GetPen();
2519 axisPen.SetColour( axesColour );
2520 layer->SetPen( axisPen );
2521 }
2522
2523 if( layer->GetLayerType() == mpLAYER_INFO )
2524 {
2525 // Get the old pen to modify only colour, not style or width.
2526 wxPen infoPen = layer->GetPen();
2527 infoPen.SetColour( drawColour );
2528 layer->SetPen( infoPen );
2529 }
2530 }
2531}
2532
2533
2534template <typename... Ts>
2536 wxWindow( std::forward<Ts>( windowArgs )... ),
2537 m_minX( 0.0 ),
2538 m_maxX( 0.0 ),
2539 m_minY( 0.0 ),
2540 m_maxY( 0.0 ),
2541 m_scaleX( 1.0 ),
2542 m_scaleY( 1.0 ),
2543 m_posX( 0.0 ),
2544 m_posY( 0.0 ),
2545 m_scrX( 64 ),
2546 m_scrY( 64 ),
2547 m_clickedX( 0 ),
2548 m_clickedY( 0 ),
2549 m_yLocked( false ),
2550 m_desiredXmin( 0.0 ),
2551 m_desiredXmax( 1.0 ),
2552 m_desiredYmin( 0.0 ),
2553 m_desiredYmax( 1.0 ),
2556 m_marginTop( 0 ),
2557 m_marginRight( 0 ),
2558 m_marginBottom( 0 ),
2559 m_marginLeft( 0 ),
2560 m_last_lx( 0 ),
2561 m_last_ly( 0 ),
2562 m_buff_bmp( nullptr ),
2563 m_enableDoubleBuffer( false ),
2565 m_enableLimitedView( false ),
2567 m_movingInfoLayer( nullptr ),
2568 m_zooming( false )
2569{}
2570
2571
2573{
2574 if( wxGraphicsContext* ctx = m_buff_dc.GetGraphicsContext() )
2575 {
2576 if( !ctx->SetInterpolationQuality( wxINTERPOLATION_BEST )
2577 || !ctx->SetInterpolationQuality( wxINTERPOLATION_GOOD ) )
2578 {
2579 ctx->SetInterpolationQuality( wxINTERPOLATION_FAST );
2580 }
2581
2582 ctx->SetAntialiasMode( wxANTIALIAS_DEFAULT );
2583 }
2584}
2585
2586
2587// -----------------------------------------------------------------------------
2588// mpFXYVector implementation - by Jose Luis Blanco (AGO-2007)
2589// -----------------------------------------------------------------------------
2590
2591IMPLEMENT_DYNAMIC_CLASS( mpFXYVector, mpFXY )
2592
2593
2594mpFXYVector::mpFXYVector( const wxString& name, int flags ) :
2595 mpFXY( name, flags )
2596{
2597 m_index = 0;
2598 m_sweepWindow = 0;
2599 m_minX = -1;
2600 m_maxX = 1;
2601 m_minY = -1;
2602 m_maxY = 1;
2604}
2605
2606
2607double mpScaleX::TransformToPlot( double x ) const
2608{
2609 return ( x + m_offset ) * m_scale;
2610}
2611
2612
2613double mpScaleX::TransformFromPlot( double xplot ) const
2614{
2615 return xplot / m_scale - m_offset;
2616}
2617
2618
2619double mpScaleY::TransformToPlot( double x ) const
2620{
2621 return ( x + m_offset ) * m_scale;
2622}
2623
2624
2625double mpScaleY::TransformFromPlot( double xplot ) const
2626{
2627 return xplot / m_scale - m_offset;
2628}
2629
2630
2631double mpScaleXLog::TransformToPlot( double x ) const
2632{
2633 double xlogmin = log10( m_minV );
2634 double xlogmax = log10( m_maxV );
2635
2636 return ( log10( x ) - xlogmin ) / ( xlogmax - xlogmin );
2637}
2638
2639
2640double mpScaleXLog::TransformFromPlot( double xplot ) const
2641{
2642 double xlogmin = log10( m_minV );
2643 double xlogmax = log10( m_maxV );
2644
2645 return pow( 10.0, xplot * ( xlogmax - xlogmin ) + xlogmin );
2646}
2647
2648
2650{
2651 m_index = 0;
2652 m_sweepWindow = std::numeric_limits<size_t>::max();
2653}
2654
2655
2656void mpFXYVector::SetSweepWindow( int aSweepIdx )
2657{
2658 m_index = aSweepIdx * m_sweepSize;
2659 m_sweepWindow = ( aSweepIdx + 1 ) * m_sweepSize;
2660}
2661
2662
2663bool mpFXYVector::GetNextXY( double& x, double& y )
2664{
2665 if( m_index >= m_xs.size() || m_index >= m_sweepWindow )
2666 {
2667 return false;
2668 }
2669 else
2670 {
2671 x = m_xs[m_index];
2672 y = m_ys[m_index++];
2673 return m_index <= m_xs.size() && m_index <= m_sweepWindow;
2674 }
2675}
2676
2677
2679{
2680 m_xs.clear();
2681 m_ys.clear();
2682}
2683
2684
2685void mpFXYVector::SetData( const std::vector<double>& xs, const std::vector<double>& ys )
2686{
2687 // Check if the data vectors are of the same size
2688 if( xs.size() != ys.size() )
2689 return;
2690
2691 // Copy the data:
2692 m_xs = xs;
2693 m_ys = ys;
2694
2695 // Update internal variables for the bounding box.
2696 if( xs.size() > 0 )
2697 {
2698 m_minX = xs[0];
2699 m_maxX = xs[0];
2700 m_minY = ys[0];
2701 m_maxY = ys[0];
2702
2703 for( const double x : xs )
2704 {
2705 if( x < m_minX )
2706 m_minX = x;
2707
2708 if( x > m_maxX )
2709 m_maxX = x;
2710 }
2711
2712 for( const double y : ys )
2713 {
2714 if( y < m_minY )
2715 m_minY = y;
2716
2717 if( y > m_maxY )
2718 m_maxY = y;
2719 }
2720 }
2721 else
2722 {
2723 m_minX = 0;
2724 m_maxX = 0;
2725 m_minY = 0;
2726 m_maxY = 0;
2727 }
2728}
2729
2730
2732{
2733 m_scaleX = scaleX;
2734 m_scaleY = scaleY;
2735
2736 UpdateScales();
2737}
2738
2739
2741{
2742 if( m_scaleX )
2743 m_scaleX->ExtendDataRange( GetMinX(), GetMaxX() );
2744
2745 if( m_scaleY )
2746 m_scaleY->ExtendDataRange( GetMinY(), GetMaxY() );
2747}
2748
2749
2750double mpFXY::s2x( double plotCoordX ) const
2751{
2752 return m_scaleX ? m_scaleX->TransformFromPlot( plotCoordX ) : plotCoordX;
2753}
2754
2755
2756double mpFXY::s2y( double plotCoordY ) const
2757{
2758 return m_scaleY ? m_scaleY->TransformFromPlot( plotCoordY ) : plotCoordY;
2759}
2760
2761
2762double mpFXY::x2s( double x ) const
2763{
2764 return m_scaleX ? m_scaleX->TransformToPlot( x ) : x;
2765}
2766
2767
2768double mpFXY::y2s( double y ) const
2769{
2770 return m_scaleY ? m_scaleY->TransformToPlot( y ) : y;
2771}
const char * name
A class providing graphs functionality for a 2D plot (either continuous or a set of points),...
Definition mathplot.h:1412
void SetSweepWindow(int aSweepIdx) override
bool GetNextXY(double &x, double &y) override
Get locus value for next N.
mpFXYVector(const wxString &name=wxEmptyString, int flags=mpALIGN_NE)
double m_maxY
Definition mathplot.h:1465
std::vector< double > m_ys
Definition mathplot.h:1458
double m_minX
Loaded at SetData.
Definition mathplot.h:1465
std::vector< double > m_xs
The internal copy of the set of data to draw.
Definition mathplot.h:1458
size_t m_sweepWindow
Definition mathplot.h:1461
virtual void SetData(const std::vector< double > &xs, const std::vector< double > &ys)
Changes the internal data: the set of points to draw.
double m_maxX
Definition mathplot.h:1465
void Clear()
Clears all the data, leaving the layer empty.
size_t m_sweepSize
Definition mathplot.h:1467
void Rewind() override
Rewind value enumeration with mpFXY::GetNextXY.
size_t m_index
Definition mathplot.h:1460
double m_minY
Definition mathplot.h:1465
Abstract base class providing plot and labeling functionality for a locus plot F:N->X,...
Definition mathplot.h:544
mpScaleBase * m_scaleX
Definition mathplot.h:589
wxCoord maxDrawY
Definition mathplot.h:587
double s2y(double plotCoordY) const
virtual void SetScale(mpScaleBase *scaleX, mpScaleBase *scaleY)
mpScaleBase * m_scaleY
Definition mathplot.h:589
wxCoord maxDrawX
Definition mathplot.h:587
virtual void Rewind()=0
Rewind value enumeration with mpFXY::GetNextXY.
void UpdateScales()
virtual void SetSweepWindow(int aSweepIdx)
Definition mathplot.h:555
virtual size_t GetCount() const =0
int m_flags
Definition mathplot.h:584
mpFXY(const wxString &name=wxEmptyString, int flags=mpALIGN_NE)
Definition mathplot.cpp:427
virtual void Plot(wxDC &dc, mpWindow &w) override
Layer plot handler.
Definition mathplot.cpp:451
double y2s(double y) const
void UpdateViewBoundary(wxCoord xnew, wxCoord ynew)
Update label positioning data.
Definition mathplot.cpp:440
double x2s(double x) const
virtual int GetSweepCount() const
Definition mathplot.h:565
wxCoord minDrawX
Definition mathplot.h:587
double s2x(double plotCoordX) const
wxCoord minDrawY
Definition mathplot.h:587
virtual bool GetNextXY(double &x, double &y)=0
Get locus value for next N.
Abstract base class providing plot and labeling functionality for functions F:X->Y.
Definition mathplot.h:478
virtual double GetY(double x) const =0
Get function value for argument.
mpFX(const wxString &name=wxEmptyString, int flags=mpALIGN_RIGHT)
Definition mathplot.cpp:291
virtual void Plot(wxDC &dc, mpWindow &w) override
Layer plot handler.
Definition mathplot.cpp:299
int m_flags
Definition mathplot.h:499
Abstract base class providing plot and labeling functionality for functions F:Y->X.
Definition mathplot.h:510
int m_flags
Definition mathplot.h:531
virtual double GetX(double y) const =0
Get function value for argument.
mpFY(const wxString &name=wxEmptyString, int flags=mpALIGN_TOP)
Definition mathplot.cpp:361
virtual void Plot(wxDC &dc, mpWindow &w) override
Layer plot handler.
Definition mathplot.cpp:369
Base class to create small rectangular info boxes mpInfoLayer is the base class to create a small rec...
Definition mathplot.h:330
virtual ~mpInfoLayer()
Destructor.
Definition mathplot.cpp:101
wxPoint m_reference
Definition mathplot.h:383
wxRect m_dim
Definition mathplot.h:381
mpInfoLayer()
Default constructor.
Definition mathplot.cpp:78
wxPoint GetPosition() const
Returns the position of the upper left corner of the box (in pixels)
Definition mathplot.cpp:170
virtual void Plot(wxDC &dc, mpWindow &w) override
Plot method.
Definition mathplot.cpp:132
virtual void UpdateReference()
Updates the rectangle reference point.
Definition mathplot.cpp:125
virtual bool Inside(const wxPoint &point) const
Checks whether a point is inside the info box rectangle.
Definition mathplot.cpp:106
virtual bool OnDoubleClick(const wxPoint &point, mpWindow &w)
Definition mathplot.cpp:112
wxBrush m_brush
Definition mathplot.h:384
virtual void Move(wxPoint delta)
Moves the layer rectangle of given pixel deltas.
Definition mathplot.cpp:118
wxSize GetSize() const
Returns the size of the box (in pixels)
Definition mathplot.cpp:176
Implements the legend to be added to the plot This layer allows you to add a legend to describe the p...
Definition mathplot.h:395
mpInfoLegend()
Default constructor.
Definition mathplot.cpp:182
virtual void Plot(wxDC &dc, mpWindow &w) override
Plot method.
Definition mathplot.cpp:199
~mpInfoLegend()
Default destructor.
Definition mathplot.cpp:194
const wxString & GetDisplayName() const
Definition mathplot.h:241
mpLayerType GetLayerType() const
Get layer type: a Layer can be of different types: plot lines, axis, info boxes, etc,...
Definition mathplot.h:287
wxFont m_font
Definition mathplot.h:307
void SetFont(const wxFont &font)
Set layer font.
Definition mathplot.h:278
bool IsVisible() const
Checks whether the layer is visible or not.
Definition mathplot.h:291
virtual double GetMinY() const
Get inclusive bottom border of bounding box.
Definition mathplot.h:186
bool m_continuous
Definition mathplot.h:312
bool m_showName
Definition mathplot.h:313
bool m_visible
Definition mathplot.h:315
virtual void SetName(const wxString &name)
Set layer name.
Definition mathplot.h:273
const wxPen & GetPen() const
Get pen set for this layer.
Definition mathplot.h:254
mpLayerType m_type
Definition mathplot.h:314
wxString m_name
Definition mathplot.h:310
void SetPen(const wxPen &pen)
Set layer pen.
Definition mathplot.h:283
wxPen m_pen
Definition mathplot.h:308
virtual double GetMaxX() const
Get inclusive right border of bounding box.
Definition mathplot.h:181
virtual double GetMinX() const
Get inclusive left border of bounding box.
Definition mathplot.h:176
virtual double GetMaxY() const
Get inclusive top border of bounding box.
Definition mathplot.h:191
Plot layer implementing a x-scale ruler.
Definition mathplot.h:616
bool m_axisLocked
Definition mathplot.h:746
double m_scale
Definition mathplot.h:739
virtual void recalculateTicks(wxDC &dc, mpWindow &w)
Definition mathplot.h:731
double m_offset
Definition mathplot.h:739
void GetDataRange(double &minV, double &maxV) const
Definition mathplot.h:643
std::vector< double > m_tickValues
Definition mathplot.h:736
bool m_rangeSet
Definition mathplot.h:745
virtual double TransformToPlot(double x) const
Definition mathplot.h:708
virtual void formatLabels()
Definition mathplot.h:733
int m_maxLabelHeight
Definition mathplot.h:749
double m_axisMin
Definition mathplot.h:747
double m_maxV
Definition mathplot.h:744
void computeLabelExtents(wxDC &dc, mpWindow &w)
Definition mathplot.cpp:800
int m_maxLabelWidth
Definition mathplot.h:750
std::vector< TICK_LABEL > m_tickLabels
Definition mathplot.h:737
double m_absVisibleMaxV
Definition mathplot.h:740
bool m_ticks
Definition mathplot.h:743
double m_minV
Definition mathplot.h:744
void updateTickLabels(wxDC &dc, mpWindow &w)
Definition mathplot.cpp:817
int m_nameFlags
Definition mathplot.h:742
virtual double TransformFromPlot(double xplot) const
Definition mathplot.h:709
double m_axisMax
Definition mathplot.h:748
virtual void getVisibleDataRange(mpWindow &w, double &minV, double &maxV) override
Definition mathplot.cpp:974
virtual void Plot(wxDC &dc, mpWindow &w) override
Plot given view of layer to the given device context.
mpScaleXBase(const wxString &name=wxT("X"), int flags=mpALIGN_CENTER, bool ticks=true, unsigned int type=mpX_NORMAL)
Full constructor.
mpScaleXLog(const wxString &name=wxT("log(X)"), int flags=mpALIGN_CENTER, bool ticks=true, unsigned int type=mpX_NORMAL)
Full constructor.
virtual double TransformFromPlot(double xplot) const override
virtual double TransformToPlot(double x) const override
void recalculateTicks(wxDC &dc, mpWindow &w) override
Definition mathplot.cpp:986
virtual double TransformToPlot(double x) const override
virtual double TransformFromPlot(double xplot) const override
virtual void recalculateTicks(wxDC &dc, mpWindow &w) override
Definition mathplot.cpp:712
mpScaleX(const wxString &name=wxT("X"), int flags=mpALIGN_CENTER, bool ticks=true, unsigned int type=mpX_NORMAL)
Full constructor.
Plot layer implementing a y-scale ruler.
Definition mathplot.h:830
int m_flags
Definition mathplot.h:864
mpScaleY * m_masterScale
Definition mathplot.h:863
virtual void getVisibleDataRange(mpWindow &w, double &minV, double &maxV) override
Definition mathplot.cpp:824
virtual double TransformFromPlot(double xplot) const override
void computeSlaveTicks(mpWindow &w)
Definition mathplot.cpp:837
virtual void Plot(wxDC &dc, mpWindow &w) override
Layer plot handler.
bool m_ticks
Definition mathplot.h:865
virtual void recalculateTicks(wxDC &dc, mpWindow &w) override
Definition mathplot.cpp:886
virtual double TransformToPlot(double x) const override
mpScaleY(const wxString &name=wxT("Y"), int flags=mpALIGN_CENTER, bool ticks=true)
Canvas for plotting mpLayer implementations.
Definition mathplot.h:910
double m_desiredYmin
Definition mathplot.h:1352
bool SaveScreenshot(wxImage &aImage, wxSize aImageSize=wxDefaultSize, bool aFit=false)
Draw the window on a wxBitmap, then save it to a file.
mpInfoLayer * m_movingInfoLayer
Definition mathplot.h:1370
void DelAllLayers(bool alsoDeleteObject, bool refreshDisplay=true)
Remove all layers from the plot.
bool m_zooming
Definition mathplot.h:1371
void SetColourTheme(const wxColour &bgColour, const wxColour &drawColour, const wxColour &axesColour)
Set Color theme.
virtual bool SetYView(double pos, double desiredMax, double desiredMin)
Applies new Y view coordinates depending on the settings.
void ZoomRect(wxPoint p0, wxPoint p1)
Zoom view fitting given coordinates to the window (p0 and p1 do not need to be in any specific order)
double m_maxY
Definition mathplot.h:1334
void onMouseLeftRelease(wxMouseEvent &event)
double m_posY
Definition mathplot.h:1338
int GetMarginLeft() const
Definition mathplot.h:1221
bool m_enableMouseNavigation
Definition mathplot.h:1365
int GetYScreen() const
Definition mathplot.h:1041
void RecomputeDesiredY(double &min, double &max)
wxOrientation ViewNeedsRefitting(wxOrientation directions) const
void OnPaint(wxPaintEvent &event)
void OnShowPopupMenu(wxMouseEvent &event)
double m_desiredXmax
Definition mathplot.h:1352
int m_last_lx
Definition mathplot.h:1361
void onMouseLeftDown(wxMouseEvent &event)
void onMouseWheel(wxMouseEvent &event)
static MouseWheelActionSet defaultMouseWheelActions()
void SetMargins(int top, int right, int bottom, int left)
Set window margins, creating a blank area where some kinds of layers cannot draw.
MouseWheelActionSet m_mouseWheelActions
Definition mathplot.h:1367
int m_marginLeft
Definition mathplot.h:1359
int m_marginTop
Definition mathplot.h:1359
double m_minY
Definition mathplot.h:1333
int GetScrX() const
Get current view's X dimension in device context units.
Definition mathplot.h:1031
int GetScrY() const
Get current view's Y dimension in device context units.
Definition mathplot.h:1040
double p2x(wxCoord pixelCoordX)
Converts mpWindow (screen) pixel coordinates into graph (floating point) coordinates,...
Definition mathplot.h:1084
double p2y(wxCoord pixelCoordY)
Converts mpWindow (screen) pixel coordinates into graph (floating point) coordinates,...
Definition mathplot.h:1088
wxMemoryDC m_buff_dc
Definition mathplot.h:1362
double m_maxX
Definition mathplot.h:1332
void initializeGraphicsContext()
int m_marginBottom
Definition mathplot.h:1359
void RecomputeDesiredX(double &min, double &max)
int m_clickedY
Definition mathplot.h:1342
wxCoord x2p(double x)
Converts graph (floating point) coordinates into mpWindow (screen) pixel coordinates,...
Definition mathplot.h:1092
wxColour m_bgColour
Definition mathplot.h:1327
MouseWheelAction
Enumerates the possible mouse wheel actions that can be performed on the plot.
Definition mathplot.h:916
double m_leftRightPlotGapFactor
Definition mathplot.h:1357
double m_posX
Definition mathplot.h:1337
wxPoint m_mouseLClick
Definition mathplot.h:1369
mpInfoLayer * IsInsideInfoLayer(wxPoint &point)
Check if a given point is inside the area of a mpInfoLayer and eventually returns its pointer.
void OnMouseMiddleDown(wxMouseEvent &event)
std::stack< std::array< double, 4 > > m_redoZoomStack
Definition mathplot.h:1374
void SetLayerVisible(const wxString &name, bool viewable)
Sets the visibility of a layer by its name.
int GetXScreen() const
Definition mathplot.h:1032
int GetMarginTop() const
Definition mathplot.h:1215
double m_scaleY
Definition mathplot.h:1336
void DoZoom(const wxPoint &centerPoint, double zoomFactor, wxOrientation directions)
int m_marginRight
Definition mathplot.h:1359
mpLayer * GetLayer(int position) const
void onZoomRedo(wxCommandEvent &event)
void ZoomIn(const wxPoint &centerPoint=wxDefaultPosition)
Zoom into current view and refresh display.
wxColour m_fgColour
Definition mathplot.h:1328
void ZoomOut(const wxPoint &centerPoint=wxDefaultPosition)
Zoom out current view and refresh display.
virtual bool UpdateBBox()
Recalculate global layer bounding box, and save it in m_minX,...
double m_minX
Definition mathplot.h:1331
wxRect m_zoomRect
Definition mathplot.h:1372
bool DelLayer(mpLayer *layer, bool alsoDeleteObject=false, bool refreshDisplay=true)
Remove a plot layer from the canvas.
void UpdateAll()
Refresh display.
wxLayerList m_layers
Definition mathplot.h:1325
void AdjustLimitedView(wxOrientation directions=wxBOTH)
Limits the zoomed or panned view to the area used by the plots.
wxCoord y2p(double y)
Converts graph (floating point) coordinates into mpWindow (screen) pixel coordinates,...
Definition mathplot.h:1096
wxMenu m_popmenu
Definition mathplot.h:1326
void OnCenter(wxCommandEvent &event)
virtual bool SetXView(double pos, double desiredMax, double desiredMin)
Applies new X view coordinates depending on the settings.
bool m_enableLimitedView
Definition mathplot.h:1366
int m_clickedX
Definition mathplot.h:1341
void SetScaleX(double scaleX)
Set current view's X scale and refresh display.
void onMagnify(wxMouseEvent &event)
wxBitmap * m_buff_bmp
Definition mathplot.h:1363
double m_topBottomPlotGapFactor
Definition mathplot.h:1356
void onZoomOut(wxCommandEvent &event)
wxPoint m_mouseMClick
Definition mathplot.h:1368
void pushZoomUndo(const std::array< double, 4 > &aZoom)
double m_desiredYmax
Definition mathplot.h:1352
std::stack< std::array< double, 4 > > m_undoZoomStack
Definition mathplot.h:1373
int GetMarginRight() const
Definition mathplot.h:1217
void GetBoundingBox(double *bbox) const
Returns the bounding box coordinates.
void SetScr(int scrX, int scrY)
Set current view's dimensions in device context units.
Definition mathplot.h:1080
int GetMarginBottom() const
Definition mathplot.h:1219
void PerformMouseWheelAction(wxMouseEvent &event, MouseWheelAction action)
wxColour m_axColour
Definition mathplot.h:1329
static double zoomIncrementalFactor
This value sets the zoom steps whenever the user clicks "Zoom in/out" or performs zoom with the mouse...
Definition mathplot.h:1195
bool m_yLocked
Definition mathplot.h:1344
void onMouseLeftDClick(wxMouseEvent &event)
double m_desiredXmin
These are updated in Fit, ZoomIn, ZoomOut, ZoomRect, SetXView, SetYView and may be different from the...
Definition mathplot.h:1352
double m_scaleX
Definition mathplot.h:1335
void ZoomRedo()
bool m_enableDoubleBuffer
Definition mathplot.h:1364
void ZoomUndo()
void OnFit(wxCommandEvent &event)
void OnSize(wxSizeEvent &event)
const mpLayer * GetLayerByName(const wxString &name) const
unsigned int CountAllLayers() const
Counts the number of plot layers, whether or not they have a bounding box.
Definition mathplot.h:1152
void SetPos(double posX, double posY)
Set current view's X and Y position and refresh display.
Definition mathplot.h:1073
void onZoomIn(wxCommandEvent &event)
double GetScaleY() const
Get current view's Y scale.
Definition mathplot.h:1011
int m_scrY
Definition mathplot.h:1340
double GetScaleX() const
Get current view's X scale.
Definition mathplot.h:1005
bool AddLayer(mpLayer *layer, bool refreshDisplay=true)
Add a plot layer to the canvas.
void Fit() override
Set view to fit global bounding box of all plot layers and refresh display.
void onZoomUndo(wxCommandEvent &event)
double GetPosY() const
Get current view's Y position.
Definition mathplot.h:1023
void onMouseMove(wxMouseEvent &event)
double GetPosX() const
Get current view's X position.
Definition mathplot.h:1017
int m_scrX
Definition mathplot.h:1339
bool IsLayerVisible(const wxString &name) const
Check whether a layer with given name is visible.
int m_last_ly
Definition mathplot.h:1361
#define _(s)
#define mpLEGEND_MARGIN
Definition mathplot.cpp:50
#define mpLEGEND_LINEWIDTH
Definition mathplot.cpp:51
#define mpALIGN_BORDER_RIGHT
Aligns Y axis to right border.
Definition mathplot.h:457
@ mpLAYER_INFO
Definition mathplot.h:132
@ mpLAYER_UNDEF
Definition mathplot.h:129
@ mpLAYER_AXIS
Definition mathplot.h:130
@ mpLAYER_PLOT
Definition mathplot.h:131
#define mpALIGN_RIGHT
Aligns label to the right.
Definition mathplot.h:429
#define mpALIGN_NW
Aligns label to north-west.
Definition mathplot.h:461
#define mpALIGN_FAR_RIGHT
Aligns label to the right of mpALIGN_RIGHT.
Definition mathplot.h:443
#define mpALIGN_BORDER_TOP
Aligns X axis to top border.
Definition mathplot.h:441
#define mpALIGN_CENTER
Aligns label to the center.
Definition mathplot.h:431
#define mpALIGN_LEFT
Aligns label to the left.
Definition mathplot.h:433
#define mpALIGN_TOP
Aligns label to the top.
Definition mathplot.h:435
@ mpID_ZOOM_REDO
Definition mathplot.h:117
@ mpID_FIT
Definition mathplot.h:115
@ mpID_ZOOM_IN
Definition mathplot.h:118
@ mpID_CENTER
Definition mathplot.h:120
@ mpID_ZOOM_UNDO
Definition mathplot.h:116
@ mpID_ZOOM_OUT
Definition mathplot.h:119
#define mpALIGN_BORDER_LEFT
Aligns Y axis to left border.
Definition mathplot.h:455
#define mpALIGNMASK
Definition mathplot.h:427
#define mpALIGN_SE
Aligns label to south-east.
Definition mathplot.h:465
#define mpALIGN_NE
Aligns label to north-east.
Definition mathplot.h:459
#define mpALIGN_BOTTOM
Aligns label to the bottom.
Definition mathplot.h:437
#define mpALIGN_BORDER_BOTTOM
Aligns X axis to bottom border.
Definition mathplot.h:439
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
#define MAX_SCALE
void Refresh()
Update the board display after modifying it by a python script (note: it is automatically called by a...
Contains the set of modified mouse wheel actions that can be performed on the plot.
Definition mathplot.h:931
MouseWheelAction horizontal
Definition mathplot.h:941
MouseWheelAction verticalWithShift
Definition mathplot.h:939
MouseWheelAction verticalWithCtrl
Definition mathplot.h:938
MouseWheelAction verticalWithAlt
Definition mathplot.h:940
MouseWheelAction verticalUnmodified
Definition mathplot.h:937
wxString result
Test unit parsing edge cases and error handling.
int delta
static thread_pool * tp