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