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