KiCad PCB EDA Suite
Loading...
Searching...
No Matches
sim_plot_tab.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2016-2023 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Tomasz Wlostowski <[email protected]>
8 * @author Maciej Suminski <[email protected]>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 3
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 */
23
24#include <wx/tokenzr.h>
25#include "sim_plot_colors.h"
26#include "sim_plot_tab.h"
27#include "simulator_frame.h"
28#include "core/kicad_algo.h"
29
30#include <algorithm>
31#include <cmath>
32#include <limits>
33
34
35static wxString formatFloat( double x, int nDigits )
36{
37 wxString rv, fmt;
38
39 if( nDigits )
40 fmt.Printf( "%%.0%df", nDigits );
41 else
42 fmt = wxT( "%.0f" );
43
44 rv.Printf( fmt, x );
45
46 return rv;
47}
48
49
50static void getSISuffix( double x, const wxString& unit, int& power, wxString& suffix )
51{
52 const int n_powers = 11;
53
54 const struct
55 {
56 int exponent;
57 char suffix;
58 } powers[] =
59 {
60 { -18, 'a' },
61 { -15, 'f' },
62 { -12, 'p' },
63 { -9, 'n' },
64 { -6, 'u' },
65 { -3, 'm' },
66 { 0, 0 },
67 { 3, 'k' },
68 { 6, 'M' },
69 { 9, 'G' },
70 { 12, 'T' },
71 { 14, 'P' }
72 };
73
74 power = 0;
75 suffix = unit;
76
77 if( x == 0.0 )
78 return;
79
80 for( int i = 0; i < n_powers - 1; i++ )
81 {
82 double r_cur = pow( 10, powers[i].exponent );
83
84 if( fabs( x ) >= r_cur && fabs( x ) < r_cur * 1000.0 )
85 {
86 power = powers[i].exponent;
87
88 if( powers[i].suffix )
89 suffix = wxString( powers[i].suffix ) + unit;
90 else
91 suffix = unit;
92
93 return;
94 }
95 }
96}
97
98
99static int countDecimalDigits( double x, int maxDigits )
100{
101 if( std::isnan( x ) )
102 return 0;
103
104 auto countSignificantDigits =
105 [&]( int64_t k )
106 {
107 while( k && ( k % 10LL ) == 0LL )
108 k /= 10LL;
109
110 int n = 0;
111
112 while( k != 0LL )
113 {
114 n++;
115 k /= 10LL;
116 }
117
118 return n;
119 };
120
121 int64_t k = (int)( ( x - floor( x ) ) * pow( 10.0, (double) maxDigits ) );
122 int n = countSignificantDigits( k );
123
124 // check for trailing 9's
125 n = std::min( n, countSignificantDigits( k + 1 ) );
126
127 return n;
128}
129
130
131template <typename T_PARENT>
132class LIN_SCALE : public T_PARENT
133{
134public:
135 LIN_SCALE( const wxString& name, const wxString& unit, int flags ) :
136 T_PARENT( name, flags, false ),
137 m_unit( unit )
138 {};
139
140 wxString GetUnits() const { return m_unit; }
141
142private:
143 void formatLabels() override
144 {
145 double maxVis = T_PARENT::AbsVisibleMaxValue();
146
147 wxString suffix;
148 int power = 0;
149 int digits = 0;
150 int constexpr MAX_DIGITS = 3;
151 int constexpr MAX_DISAMBIGUATION_DIGITS = 6;
152 bool duplicateLabels = false;
153
154 getSISuffix( maxVis, m_unit, power, suffix );
155
156 double sf = pow( 10.0, power );
157
158 for( mpScaleBase::TICK_LABEL& l : T_PARENT::m_tickLabels )
159 digits = std::max( digits, countDecimalDigits( l.pos / sf, MAX_DIGITS ) );
160
161 do
162 {
163 for( size_t ii = 0; ii < T_PARENT::m_tickLabels.size(); ++ii )
164 {
165 mpScaleBase::TICK_LABEL& l = T_PARENT::m_tickLabels[ii];
166
167 l.label = formatFloat( l.pos / sf, digits );
168 l.visible = true;
169
170 if( ii > 0 && l.label == T_PARENT::m_tickLabels[ii-1].label )
171 duplicateLabels = true;
172 }
173 }
174 while( duplicateLabels && ++digits <= MAX_DISAMBIGUATION_DIGITS );
175
176 if( m_base_axis_label.IsEmpty() )
177 m_base_axis_label = T_PARENT::GetName();
178
179 T_PARENT::SetName( wxString::Format( "%s (%s)", m_base_axis_label, suffix ) );
180 }
181
182private:
183 const wxString m_unit;
185};
186
187
188class TIME_SCALE : public LIN_SCALE<mpScaleX>
189{
190public:
191 TIME_SCALE( const wxString& name, const wxString& unit, int flags ) :
192 LIN_SCALE( name, unit, flags ),
193 m_startTime( 0.0 ),
194 m_endTime( 1.0 )
195 {};
196
197 void ExtendDataRange( double minV, double maxV ) override
198 {
199 LIN_SCALE::ExtendDataRange( minV, maxV );
200
201 // Time is never longer than the simulation itself
202 if( m_minV < m_startTime )
204
205 if( m_maxV > m_endTime )
207 };
208
209 void SetStartAndEnd( double aStartTime, double aEndTime )
210 {
211 m_startTime = aStartTime;
212 m_endTime = aEndTime;
214 }
215
216 void ResetDataRange() override
217 {
220 m_rangeSet = true;
221 }
222
223protected:
225 double m_endTime;
226};
227
228
229template <typename T_PARENT>
230class LOG_SCALE : public T_PARENT
231{
232public:
233 LOG_SCALE( const wxString& name, const wxString& unit, int flags ) :
234 T_PARENT( name, flags, false ),
235 m_unit( unit )
236 {};
237
238 wxString GetUnits() const { return m_unit; }
239
240private:
241 void formatLabels() override
242 {
243 wxString suffix;
244 int power;
245 int constexpr MAX_DIGITS = 3;
246
247 for( mpScaleBase::TICK_LABEL& l : T_PARENT::m_tickLabels )
248 {
249 getSISuffix( l.pos, m_unit, power, suffix );
250 double sf = pow( 10.0, power );
251 int k = countDecimalDigits( l.pos / sf, MAX_DIGITS );
252
253 l.label = formatFloat( l.pos / sf, k ) + suffix;
254 l.visible = true;
255 }
256 }
257
258private:
259 const wxString m_unit;
260};
261
262
263bool SMITH_GRID::GetChartView( mpWindow& aWindow, double aZoom, const wxRealPoint& aPan, SMITH_VIEW& aView )
264{
265 int mL = aWindow.GetMarginLeft(), mT = aWindow.GetMarginTop();
266 int plotW = aWindow.GetScrX() - mL - aWindow.GetMarginRight();
267 int plotH = aWindow.GetScrY() - mT - aWindow.GetMarginBottom();
268 int radius = std::min( plotW, plotH ) / 2 - 15;
269
270 if( radius <= 20 )
271 return false;
272
273 aView.center = wxPoint( mL + plotW / 2, mT + plotH / 2 );
274 aView.radius = radius * aZoom;
275 aView.zoom = aZoom;
276 aView.pan = aPan;
277 aView.plotRect = wxRect( mL, mT, plotW, plotH );
278
279 return true;
280}
281
282
283static bool smithView( mpWindow& aWindow, SMITH_VIEW& aView )
284{
285 SIM_PLOT_TAB* tab = dynamic_cast<SIM_PLOT_TAB*>( aWindow.GetParent() );
286 double zoom = tab ? tab->GetSmithZoom() : 1.0;
287 wxRealPoint pan = tab ? tab->GetSmithPan() : wxRealPoint( 0.0, 0.0 );
288
289 return SMITH_GRID::GetChartView( aWindow, zoom, pan, aView );
290}
291
292
293void SMITH_GRID::Plot( wxDC& aDC, mpWindow& aWindow )
294{
295 if( !m_visible )
296 return;
297
298 SMITH_VIEW view;
299
300 if( !smithView( aWindow, view ) )
301 return;
302
303 const wxRect& plotRect = view.plotRect;
304 int mL = plotRect.x, mT = plotRect.y, plotW = plotRect.width, plotH = plotRect.height;
305
306 aDC.SetClippingRegion( plotRect );
307
308 // enough segments to keep the chord error under a pixel at this circle's screen radius
309 auto segmentsFor = [&]( double aR ) -> int
310 {
311 return std::clamp( KiROUND( M_PI * std::sqrt( aR * view.radius ) ), 64, 4096 );
312 };
313
314 // draw a gamma-plane circle, keeping only the parts inside the unit circle
315 auto drawClippedCircle = [&]( double aCx, double aCy, double aR )
316 {
317 constexpr double LIMIT = 1.0002;
318
319 std::vector<wxPoint> run;
320 int segments = segmentsFor( aR );
321
322 for( int ii = 0; ii <= segments; ii++ )
323 {
324 double angle = 2.0 * M_PI * ii / segments;
325 double re = aCx + aR * cos( angle );
326 double im = aCy + aR * sin( angle );
327
328 if( re * re + im * im <= LIMIT )
329 {
330 run.push_back( view.ToScreen( re, im ) );
331 }
332 else
333 {
334 if( run.size() > 1 )
335 aDC.DrawLines( (int) run.size(), run.data() );
336
337 run.clear();
338 }
339 }
340
341 if( run.size() > 1 )
342 aDC.DrawLines( (int) run.size(), run.data() );
343 };
344
345 auto formatValue = []( double aValue ) -> wxString
346 {
347 return wxString::Format( wxS( "%g" ), aValue );
348 };
349
350 // ohm labels for a single reference impedance, normalized labels when the traces disagree
351 double labelScale = m_normalized ? 1.0 : m_z0;
352
353 static const std::vector<double> baseVals = { 0.2, 0.5, 1.0, 2.0, 5.0 };
354 std::vector<double> gridVals = baseVals;
355
356 if( view.zoom >= 2.0 )
357 gridVals.insert( gridVals.end(), { 0.1, 0.3, 0.4, 0.7, 1.5, 3.0, 10.0 } );
358
359 if( view.zoom >= 5.0 )
360 gridVals.insert( gridVals.end(), { 0.05, 0.15, 0.6, 0.8, 1.2, 1.7, 2.5, 4.0, 7.0, 20.0 } );
361
362 wxPen gridPen = m_pen;
363 gridPen.SetStyle( wxPENSTYLE_DOT );
364
365 aDC.SetBrush( *wxTRANSPARENT_BRUSH );
366 aDC.SetFont( m_font );
367 aDC.SetTextForeground( m_pen.GetColour() );
368 aDC.SetPen( gridPen );
369
370 // constant resistance circles, centered on the real axis, tangent at gamma = 1
371 for( double r : gridVals )
372 drawClippedCircle( r / ( 1.0 + r ), 0.0, 1.0 / ( 1.0 + r ) );
373
374 // constant reactance arcs, one per sign, clipped to the unit circle
375 for( double x : gridVals )
376 {
377 drawClippedCircle( 1.0, 1.0 / x, 1.0 / x );
378 drawClippedCircle( 1.0, -1.0 / x, 1.0 / x );
379 }
380
381 aDC.SetPen( m_pen );
382 aDC.DrawLine( view.ToScreen( -1.0, 0.0 ), view.ToScreen( 1.0, 0.0 ) );
383 aDC.DrawCircle( view.ToScreen( 0.0, 0.0 ), KiROUND( view.radius ) );
384
385 // Label each gridline at its axis/rim anchor, or at the plot edge when the anchor is
386 // panned/zoomed out of view.
387 constexpr double LABEL_LIMIT = 1.0002;
388
389 auto anchorPos = [&]( double aRe, double aIm, wxPoint& aOut ) -> bool
390 {
391 if( aRe * aRe + aIm * aIm > LABEL_LIMIT )
392 return false;
393
394 wxPoint p = view.ToScreen( aRe, aIm );
395
396 if( !plotRect.Contains( p ) )
397 return false;
398
399 aOut = p;
400 return true;
401 };
402
403 // anchor off-screen, put the label where the gridline meets the plot edge
404 auto edgePos = [&]( double aCx, double aCy, double aR, wxPoint& aOut ) -> bool
405 {
406 int bestMargin = std::numeric_limits<int>::max();
407 bool found = false;
408 int segments = segmentsFor( aR );
409
410 for( int ii = 0; ii <= segments; ii++ )
411 {
412 double angle = 2.0 * M_PI * ii / segments;
413 double re = aCx + aR * cos( angle );
414 double im = aCy + aR * sin( angle );
415
416 if( re * re + im * im > LABEL_LIMIT )
417 continue;
418
419 wxPoint p = view.ToScreen( re, im );
420
421 if( !plotRect.Contains( p ) )
422 continue;
423
424 int margin = std::min( { p.x - mL, mL + plotW - p.x, p.y - mT, mT + plotH - p.y } );
425
426 if( margin < bestMargin )
427 {
428 bestMargin = margin;
429 aOut = p;
430 found = true;
431 }
432 }
433
434 return found;
435 };
436
437 auto clampToPlot = [&]( const wxPoint& aPos, const wxSize& aExt ) -> wxPoint
438 {
439 return wxPoint( std::clamp( aPos.x, mL + 1, mL + plotW - aExt.x - 1 ),
440 std::clamp( aPos.y, mT + 1, mT + plotH - aExt.y - 1 ) );
441 };
442
443 auto drawEdgeLabel = [&]( const wxString& aLabel, const wxPoint& aAt )
444 {
445 wxSize ext = aDC.GetTextExtent( aLabel );
446
447 aDC.DrawText( aLabel, clampToPlot( wxPoint( aAt.x - ext.x / 2, aAt.y + 3 ), ext ) );
448 };
449
450 // push reactance labels just outside the rim, clamped so j50 and -j50 are not clipped
451 auto drawRimLabel = [&]( const wxString& aLabel, const wxPoint& aAt, double aRe, double aIm )
452 {
453 wxSize ext = aDC.GetTextExtent( aLabel );
454 wxPoint pos = aAt;
455
456 pos.x += KiROUND( aRe * 6 );
457 pos.y -= KiROUND( aIm * 6 );
458 pos.x -= KiROUND( ext.x * ( 1.0 - aRe ) / 2.0 );
459 pos.y -= KiROUND( ext.y * ( 1.0 + aIm ) / 2.0 );
460
461 aDC.DrawText( aLabel, clampToPlot( pos, ext ) );
462 };
463
464 wxPoint at;
465
466 // short (r = 0)
467 if( anchorPos( -1.0, 0.0, at ) )
468 drawRimLabel( wxS( "0" ), at, -1.0, 0.0 );
469 else if( edgePos( 0.0, 0.0, 1.0, at ) )
470 drawEdgeLabel( wxS( "0" ), at );
471
472 for( double r : gridVals )
473 {
474 if( anchorPos( ( r - 1.0 ) / ( r + 1.0 ), 0.0, at ) || edgePos( r / ( 1.0 + r ), 0.0, 1.0 / ( 1.0 + r ), at ) )
475 {
476 drawEdgeLabel( formatValue( r * labelScale ), at );
477 }
478 }
479
480 for( double x : gridVals )
481 {
482 double d = x * x + 1.0;
483 double re = ( x * x - 1.0 ) / d;
484 double im = 2.0 * x / d;
485 wxString posLabel = wxS( "j" ) + formatValue( x * labelScale );
486 wxString negLabel = wxS( "-j" ) + formatValue( x * labelScale );
487
488 if( anchorPos( re, im, at ) )
489 drawRimLabel( posLabel, at, re, im );
490 else if( edgePos( 1.0, 1.0 / x, 1.0 / x, at ) )
491 drawEdgeLabel( posLabel, at );
492
493 if( anchorPos( re, -im, at ) )
494 drawRimLabel( negLabel, at, re, -im );
495 else if( edgePos( 1.0, -1.0 / x, 1.0 / x, at ) )
496 drawEdgeLabel( negLabel, at );
497 }
498
499 if( m_normalized )
500 {
501 wxString note = _( "normalized" );
502 wxSize ext = aDC.GetTextExtent( note );
503
504 aDC.DrawText( note, mL + 4, mT + plotH - ext.y - 4 );
505 }
506
507 aDC.DestroyClippingRegion();
508}
509
510
511void SMITH_TRACE::Plot( wxDC& aDC, mpWindow& aWindow )
512{
513 if( !m_visible )
514 return;
515
516 SMITH_VIEW view;
517
518 if( !smithView( aWindow, view ) )
519 return;
520
521 const std::vector<double>& xs = GetDataX();
522 const std::vector<double>& ys = GetDataY();
523 size_t count = std::min( xs.size(), ys.size() );
524
525 if( count == 0 )
526 return;
527
528 aDC.SetPen( m_pen );
529 aDC.SetClippingRegion( view.plotRect );
530
531 size_t chunk = GetSweepSize();
532
533 if( GetSweepCount() <= 1 || chunk == std::numeric_limits<size_t>::max() || chunk == 0 )
534 chunk = count;
535
536 std::vector<wxPoint> pts;
537
538 auto flush = [&]()
539 {
540 if( pts.size() > 1 )
541 {
542 aDC.DrawLines( (int) pts.size(), pts.data() );
543 }
544 else if( pts.size() == 1 )
545 {
546 aDC.SetBrush( wxBrush( m_pen.GetColour() ) );
547 aDC.DrawCircle( pts[0], 2 );
548 }
549
550 pts.clear();
551 };
552
553 for( size_t start = 0; start < count; start += chunk )
554 {
555 size_t end = std::min( count, start + chunk );
556
557 for( size_t ii = start; ii < end; ii++ )
558 {
559 // a non-finite sample breaks the locus rather than drawing a bogus segment
560 if( !std::isfinite( xs[ii] ) || !std::isfinite( ys[ii] ) )
561 {
562 flush();
563 continue;
564 }
565
566 pts.emplace_back( view.ToScreen( xs[ii], ys[ii] ) );
567 }
568
569 flush();
570 }
571
572 aDC.DestroyClippingRegion();
573}
574
575
577{
578 const std::vector<double>& re = m_trace->GetDataX();
579 const std::vector<double>& im = m_trace->GetDataY();
580 const std::vector<double>& freqs = static_cast<SMITH_TRACE*>( m_trace )->GetFrequencies();
581
582 size_t count = std::min( re.size(), im.size() );
583
584 if( count == 0 )
585 return;
586
587 m_index = std::clamp( aIndex, 0, (int) count - 1 );
588 m_gamma = wxRealPoint( re[m_index], im[m_index] );
589
590 // no frequency data for this sample, keep the previous x so a saved position stays finite
591 double freq = m_index < (int) freqs.size() ? freqs[m_index] : m_coords.x;
592
593 m_coords = wxRealPoint( freq, std::hypot( m_gamma.x, m_gamma.y ) );
594}
595
596
598{
599 const std::vector<double>& freqs = static_cast<SMITH_TRACE*>( m_trace )->GetFrequencies();
600 const std::vector<double>& re = m_trace->GetDataX();
601 const std::vector<double>& im = m_trace->GetDataY();
602
603 // frequencies repeat identically per run, search only the run the cursor is on
604 // so a frequency-keyed move cannot silently hop to run 0
605 size_t begin = 0;
606 size_t end = freqs.size();
607 size_t chunk = m_trace->GetSweepSize();
608
609 if( m_trace->GetSweepCount() > 1 && chunk > 0 && chunk != std::numeric_limits<size_t>::max() && m_index >= 0
610 && (size_t) m_index < freqs.size() )
611 {
612 begin = ( (size_t) m_index / chunk ) * chunk;
613 end = std::min( freqs.size(), begin + chunk );
614 }
615
616 int best = -1;
617 double bestDist = std::numeric_limits<double>::max();
618
619 for( size_t ii = begin; ii < end; ii++ )
620 {
621 if( !std::isfinite( freqs[ii] ) )
622 continue;
623
624 if( ii < re.size() && ii < im.size() && ( !std::isfinite( re[ii] ) || !std::isfinite( im[ii] ) ) )
625 continue;
626
627 double dist = std::fabs( freqs[ii] - aFreq );
628
629 if( dist < bestDist )
630 {
631 bestDist = dist;
632 best = (int) ii;
633 }
634 }
635
636 if( best >= 0 )
637 snapToIndex( best );
638}
639
640
641void SMITH_CURSOR::SetCoordX( double aValue )
642{
643 if( static_cast<SMITH_TRACE*>( m_trace )->GetFrequencies().empty() )
644 {
645 // no data yet, remember the frequency and resolve it once the sim fills in
646 m_coords.x = aValue;
647 m_pendingFreq = true;
648 m_updateRequired = false;
649 return;
650 }
651
652 snapToFrequency( aValue );
653 m_pendingFreq = false;
654 m_updateRequired = false;
655 m_updateRef = true;
656
657 if( m_window )
658 m_window->Refresh();
659}
660
661
662void SMITH_CURSOR::Move( wxPoint aDelta )
663{
664 m_dragging = true;
665 Update();
666 mpInfoLayer::Move( aDelta );
667}
668
669
671{
672 // skip CURSOR's axis reference, the marker follows the locus
674}
675
676
677bool SMITH_CURSOR::Inside( const wxPoint& aPoint ) const
678{
679 if( !m_window || m_index < 0 )
680 return false;
681
682 SMITH_VIEW view;
683
684 if( !smithView( *m_window, view ) )
685 return false;
686
687 wxPoint marker = view.ToScreen( m_gamma.x, m_gamma.y );
688
689 return std::abs( aPoint.x - marker.x ) <= DRAG_MARGIN && std::abs( aPoint.y - marker.y ) <= DRAG_MARGIN;
690}
691
692
693void SMITH_CURSOR::Plot( wxDC& aDC, mpWindow& aWindow )
694{
695 if( !m_window )
696 m_window = &aWindow;
697
698 if( !m_visible )
699 return;
700
701 SMITH_VIEW view;
702
703 if( !smithView( aWindow, view ) )
704 return;
705
706 const std::vector<double>& re = m_trace->GetDataX();
707 const std::vector<double>& im = m_trace->GetDataY();
708 size_t count = std::min( re.size(), im.size() );
709
710 if( count == 0 )
711 return;
712
713 if( m_pendingFreq )
714 {
715 // sim data has arrived, restore the frequency saved from the workbook
717 m_pendingFreq = false;
718 m_updateRequired = false;
719 m_updateRef = true;
720 }
721 else if( m_updateRequired )
722 {
723 if( m_dragging )
724 {
725 // snap to the locus sample closest to the drag position
726 int best = -1;
727 double bestDist = std::numeric_limits<double>::max();
728
729 for( size_t ii = 0; ii < count; ii++ )
730 {
731 if( !std::isfinite( re[ii] ) || !std::isfinite( im[ii] ) )
732 continue;
733
734 wxPoint p = view.ToScreen( re[ii], im[ii] );
735 double dx = (double) p.x - m_dim.x;
736 double dy = (double) p.y - m_dim.y;
737 double dist = dx * dx + dy * dy;
738
739 if( dist < bestDist )
740 {
741 bestDist = dist;
742 best = (int) ii;
743 }
744 }
745
746 if( best >= 0 )
747 snapToIndex( best );
748
749 m_dragging = false;
750 }
751 else
752 {
753 // the trace data changed under the cursor, follow the frequency rather than
754 // the screen position so a re-run cannot hop to another point of the locus
756 m_updateRef = true;
757 }
758
759 m_updateRequired = false;
760
761 // Notify the parent window about the changes
762 wxQueueEvent( aWindow.GetParent(), new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
763 }
764 else
765 {
766 if( m_index < 0 )
767 snapToIndex( (int) count / 2 );
768
769 m_updateRef = true;
770 }
771
772 wxPoint marker = view.ToScreen( m_gamma.x, m_gamma.y );
773
774 m_dim.SetX( marker.x );
775 m_dim.SetY( marker.y );
776
777 if( m_updateRef )
778 {
780 m_updateRef = false;
781 }
782
783 wxPen pen = GetPen();
784 wxColour fg = aWindow.GetForegroundColour();
785 COLOR4D cursorColor = COLOR4D( m_trace->GetTraceColour() ).Mix( fg, 0.6 );
786
787 pen.SetColour( cursorColor.ToColour() );
788 pen.SetStyle( wxPENSTYLE_SOLID );
789 aDC.SetPen( pen );
790 aDC.SetBrush( *wxTRANSPARENT_BRUSH );
791
792 aDC.DrawCircle( marker, 4 );
793 aDC.DrawLine( marker.x - 8, marker.y, marker.x - 4, marker.y );
794 aDC.DrawLine( marker.x + 4, marker.y, marker.x + 8, marker.y );
795 aDC.DrawLine( marker.x, marker.y - 8, marker.x, marker.y - 4 );
796 aDC.DrawLine( marker.x, marker.y + 4, marker.x, marker.y + 8 );
797
798 double gm = std::hypot( m_gamma.x, m_gamma.y );
799 double z0 = static_cast<SMITH_TRACE*>( m_trace )->GetReferenceImpedance();
800 double freq = m_coords.x;
801 double zr, zi;
802
803 auto formatSI = []( double aValue, const wxString& aUnit ) -> wxString
804 {
805 if( std::isnan( aValue ) )
806 return wxS( "--" );
807
808 int power = 0;
809 wxString suffix;
810
811 getSISuffix( aValue, aUnit, power, suffix );
812
813 double sf = pow( 10.0, power );
814
815 return formatFloat( aValue / sf, 3 ) + wxS( " " ) + suffix;
816 };
817
818 std::vector<wxString> lines;
819
820 lines.push_back( getID() + wxS( ": f = " ) + formatSI( freq, wxS( "Hz" ) ) );
821
822 if( !SMITH_MATH::GammaToImpedance( m_gamma.x, m_gamma.y, z0, zr, zi ) )
823 {
824 lines.push_back( wxS( "Z = inf" ) );
825 }
826 else
827 {
828 lines.push_back( wxString::Format( wxS( "Z = %s %s j%s" ), formatSI( zr, wxS( "Ω" ) ),
829 zi < 0 ? wxS( "-" ) : wxS( "+" ),
830 formatSI( std::fabs( zi ), wxS( "Ω" ) ) ) );
831
832 // series equivalent of the reactance at the marker frequency
833 if( std::isfinite( freq ) && freq > 0.0 && zi != 0.0 )
834 {
835 if( zi > 0.0 )
836 lines.push_back( wxS( "L = " ) + formatSI( SMITH_MATH::SeriesInductance( zi, freq ), wxS( "H" ) ) );
837 else
838 lines.push_back( wxS( "C = " ) + formatSI( SMITH_MATH::SeriesCapacitance( zi, freq ), wxS( "F" ) ) );
839 }
840 }
841
842 double rl = SMITH_MATH::ReturnLoss( gm );
843 double vswr = SMITH_MATH::VSWR( gm );
844
845 if( std::isfinite( rl ) )
846 lines.push_back( wxString::Format( wxS( "RL = %s dB" ), formatFloat( rl, 1 ) ) );
847 else
848 lines.push_back( wxS( "RL = inf" ) );
849
850 if( std::isfinite( vswr ) )
851 lines.push_back( wxString::Format( wxS( "VSWR = %s" ), formatFloat( vswr, 2 ) ) );
852 else
853 lines.push_back( wxS( "VSWR = inf" ) );
854
855 aDC.SetFont( GetFont() );
856
857 int boxW = 0;
858 int boxH = 0;
859 int lineH = aDC.GetTextExtent( wxS( "M" ) ).y;
860
861 for( const wxString& line : lines )
862 boxW = std::max( boxW, aDC.GetTextExtent( line ).x );
863
864 boxW += 8;
865 boxH = (int) lines.size() * lineH + 6;
866
867 wxPoint boxPos( marker.x + ( marker.x < aWindow.GetScrX() / 2 ? 12 : -12 - boxW ),
868 marker.y + ( marker.y < aWindow.GetScrY() / 2 ? 12 : -12 - boxH ) );
869
870 boxPos.x = std::clamp( boxPos.x, 0, std::max( 0, aWindow.GetScrX() - boxW ) );
871 boxPos.y = std::clamp( boxPos.y, 0, std::max( 0, aWindow.GetScrY() - boxH ) );
872
873 wxBrush labelBrush( aWindow.GetBackgroundColour() );
874
875 aDC.SetBrush( labelBrush );
876 aDC.DrawRectangle( wxRect( boxPos, wxSize( boxW, boxH ) ) );
877 aDC.SetTextForeground( cursorColor.ToColour() );
878
879 for( size_t ii = 0; ii < lines.size(); ii++ )
880 aDC.DrawText( lines[ii], boxPos.x + 4, boxPos.y + 3 + (int) ii * lineH );
881}
882
883
884void CURSOR::SetCoordX( double aValue )
885{
886 wxRealPoint oldCoords = m_coords;
887
888 doSetCoordX( aValue );
889 m_updateRequired = false;
890 m_updateRef = true;
891
892 if( m_window )
893 {
894 wxRealPoint delta = m_coords - oldCoords;
895 mpInfoLayer::Move( wxPoint( m_window->x2p( m_trace->x2s( delta.x ) ),
896 m_window->y2p( m_trace->y2s( delta.y ) ) ) );
897
898 m_window->Refresh();
899 }
900}
901
902
903void CURSOR::Move( wxPoint aDelta )
904{
905 Update();
906
907 if( m_trace->IsMultiRun() && m_window && m_trace->GetSweepCount() > 1
908 && m_trace->GetSweepSize() != std::numeric_limits<size_t>::max() )
909 {
910 int newY = m_reference.y + aDelta.y;
911
912 double plotY = m_window->p2y( newY );
913 m_snapTargetY = m_trace->s2y( plotY );
914 m_snapToNearest = true;
915 }
916
917 mpInfoLayer::Move( aDelta );
918}
919
920
921bool CURSOR::OnDoubleClick( const wxPoint& aPoint, mpWindow& aWindow )
922{
923 if( !Inside( aPoint ) )
924 return false;
925
926 if( !m_trace->IsMultiRun() )
927 return false;
928
929 int sweepCount = m_trace->GetSweepCount();
930 size_t sweepSize = m_trace->GetSweepSize();
931
932 if( sweepCount <= 1 )
933 return false;
934
935 if( sweepSize == std::numeric_limits<size_t>::max() || sweepSize == 0 )
936 return false;
937
938 if( m_sweepIndex < 0 || m_sweepIndex >= sweepCount )
939 m_sweepIndex = 0;
940
941 m_sweepIndex = ( m_sweepIndex + 1 ) % sweepCount;
942
943 Update();
944 m_updateRef = true;
945 m_window = &aWindow;
946 aWindow.Refresh();
947
948 return true;
949}
950
951
952void CURSOR::doSetCoordX( double aValue )
953{
954 m_coords.x = aValue;
955
956 const std::vector<double>& dataX = m_trace->GetDataX();
957 const std::vector<double>& dataY = m_trace->GetDataY();
958
959 if( dataX.size() <= 1 )
960 return;
961
962 bool snapToNearest = m_snapToNearest;
963 double snapTargetY = m_snapTargetY;
964 m_snapToNearest = false;
965
966 size_t startIdx = 0;
967 size_t endIdx = dataX.size();
968 int sweepCount = m_trace->GetSweepCount();
969 size_t sweepSize = m_trace->GetSweepSize();
970
971 if( snapToNearest && m_trace->IsMultiRun() && sweepCount > 1
972 && sweepSize != std::numeric_limits<size_t>::max() && sweepSize > 0
973 && std::isfinite( snapTargetY ) )
974 {
975 double bestDistance = std::numeric_limits<double>::infinity();
976 int bestSweep = m_sweepIndex;
977 bool found = false;
978
979 for( int sweepIdx = 0; sweepIdx < sweepCount; ++sweepIdx )
980 {
981 size_t candidateStart = static_cast<size_t>( sweepIdx ) * sweepSize;
982 size_t candidateEnd = std::min( dataX.size(), candidateStart + sweepSize );
983
984 if( candidateStart >= candidateEnd )
985 continue;
986
987 auto candidateBegin = dataX.begin() + candidateStart;
988 auto candidateEndIt = dataX.begin() + candidateEnd;
989 auto candidateMaxIt = std::upper_bound( candidateBegin, candidateEndIt, m_coords.x );
990 int candidateMaxIdx = candidateMaxIt - dataX.begin();
991 int candidateMinIdx = candidateMaxIdx - 1;
992
993 if( candidateMinIdx < (int) candidateStart
994 || candidateMaxIdx >= (int) candidateEnd
995 || candidateMaxIdx >= (int) dataX.size() )
996 {
997 continue;
998 }
999
1000 double leftX = dataX[candidateMinIdx];
1001 double rightX = dataX[candidateMaxIdx];
1002
1003 if( leftX == rightX )
1004 continue;
1005
1006 double leftY = dataY[candidateMinIdx];
1007 double rightY = dataY[candidateMaxIdx];
1008 double value = leftY + ( rightY - leftY ) / ( rightX - leftX ) * ( m_coords.x - leftX );
1009 double distance = std::fabs( value - snapTargetY );
1010
1011 if( distance < bestDistance )
1012 {
1013 bestDistance = distance;
1014 bestSweep = sweepIdx;
1015 found = true;
1016 }
1017 }
1018
1019 if( found )
1020 m_sweepIndex = bestSweep;
1021 }
1022
1023 if( m_trace->IsMultiRun() && sweepCount > 1
1024 && sweepSize != std::numeric_limits<size_t>::max() && sweepSize > 0 )
1025 {
1026 size_t available = static_cast<size_t>( sweepCount ) * sweepSize;
1027
1028 if( available <= dataX.size() )
1029 {
1030 if( m_sweepIndex < 0 || m_sweepIndex >= sweepCount )
1031 m_sweepIndex = std::max( sweepCount - 1, 0 );
1032
1033 startIdx = static_cast<size_t>( m_sweepIndex ) * sweepSize;
1034 endIdx = std::min( dataX.size(), startIdx + sweepSize );
1035 }
1036 else
1037 {
1038 m_sweepIndex = 0;
1039 }
1040 }
1041 else
1042 {
1043 m_sweepIndex = 0;
1044 }
1045
1046 if( startIdx >= endIdx )
1047 {
1048 m_coords.y = NAN;
1049 return;
1050 }
1051
1052 auto beginIt = dataX.begin() + startIdx;
1053 auto endIt = dataX.begin() + endIdx;
1054
1055 // Find the closest point coordinates
1056 auto maxXIt = std::upper_bound( beginIt, endIt, m_coords.x );
1057 int maxIdx = maxXIt - dataX.begin();
1058 int minIdx = maxIdx - 1;
1059
1060 // Out of bounds checks
1061 if( minIdx < (int) startIdx || maxIdx >= (int) endIdx || maxIdx >= (int) dataX.size() )
1062 {
1063 // Simulation may not be complete yet, or we may have a cursor off the beginning or end
1064 // of the data. Either way, that's where the user put it. Don't second guess them; just
1065 // leave its y value undefined.
1066 m_coords.y = NAN;
1067 return;
1068 }
1069
1070 const double leftX = dataX[minIdx];
1071 const double rightX = dataX[maxIdx];
1072 const double leftY = dataY[minIdx];
1073 const double rightY = dataY[maxIdx];
1074
1075 // Linear interpolation
1076 m_coords.y = leftY + ( rightY - leftY ) / ( rightX - leftX ) * ( m_coords.x - leftX );
1077}
1078
1079
1081{
1082 for( const auto& [ id, cursor ] : m_trace->GetCursors() )
1083 {
1084 if( cursor == this )
1085 return wxString::Format( _( "%d" ), id );
1086 }
1087
1088 return wxEmptyString;
1089}
1090
1091
1092void CURSOR::Plot( wxDC& aDC, mpWindow& aWindow )
1093{
1094 if( !m_window )
1095 m_window = &aWindow;
1096
1097 if( !m_visible || m_trace->GetDataX().size() <= 1 )
1098 return;
1099
1100 if( m_updateRequired )
1101 {
1102 doSetCoordX( m_trace->s2x( aWindow.p2x( m_dim.x ) ) );
1103 m_updateRequired = false;
1104
1105 // Notify the parent window about the changes
1106 wxQueueEvent( aWindow.GetParent(), new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
1107 }
1108 else
1109 {
1110 m_updateRef = true;
1111 }
1112
1113 if( m_updateRef )
1114 {
1116 m_updateRef = false;
1117 }
1118
1119 if( !std::isfinite( m_coords.x ) )
1120 return;
1121
1122 // A silent trace interpolates to no y value at all, and converting that to a pixel is
1123 // undefined behaviour, so carry the x cursor on its own
1124 const bool hasY = std::isfinite( m_coords.y );
1125
1126 // Line length in horizontal and vertical dimensions
1127 const wxPoint cursorPos( aWindow.x2p( m_trace->x2s( m_coords.x ) ),
1128 hasY ? aWindow.y2p( m_trace->y2s( m_coords.y ) ) : 0 );
1129
1130 wxCoord leftPx = aWindow.GetMarginLeft();
1131 wxCoord rightPx = aWindow.GetScrX() - aWindow.GetMarginRight();
1132 wxCoord topPx = aWindow.GetMarginTop();
1133 wxCoord bottomPx = aWindow.GetScrY() - aWindow.GetMarginBottom();
1134
1135 wxPen pen = GetPen();
1136 wxColour fg = aWindow.GetForegroundColour();
1137 COLOR4D cursorColor = COLOR4D( m_trace->GetTraceColour() ).Mix( fg, 0.6 );
1138 COLOR4D textColor = fg;
1139
1140 if( cursorColor.Distance( textColor ) < 0.66 )
1141 textColor.Invert();
1142
1143 pen.SetColour( cursorColor.ToColour() );
1144 pen.SetStyle( m_continuous ? wxPENSTYLE_SOLID : wxPENSTYLE_LONG_DASH );
1145 aDC.SetPen( pen );
1146
1147 if( hasY && topPx < cursorPos.y && cursorPos.y < bottomPx )
1148 aDC.DrawLine( leftPx, cursorPos.y, rightPx, cursorPos.y );
1149
1150 if( leftPx < cursorPos.x && cursorPos.x < rightPx )
1151 {
1152 aDC.DrawLine( cursorPos.x, topPx, cursorPos.x, bottomPx );
1153
1154 wxString id = getID();
1155 wxSize size = aDC.GetTextExtent( wxS( "M" ) );
1156 wxRect textRect( wxPoint( cursorPos.x + 1 - size.x / 2, topPx - 4 - size.y ), size );
1157 wxBrush brush;
1158 wxPoint poly[3];
1159
1160 // Because a "1" looks off-center if it's actually centred.
1161 if( id == "1" )
1162 textRect.x -= 1;
1163
1164 // We want an equalateral triangle, so use size.y for both axes.
1165 size.y += 3;
1166 // Make sure it's an even number so the slopes of the sides will be identical.
1167 size.y = ( size.y / 2 ) * 2;
1168 poly[0] = { cursorPos.x - 1 - size.y / 2, topPx - size.y };
1169 poly[1] = { cursorPos.x + 1 + size.y / 2, topPx - size.y };
1170 poly[2] = { cursorPos.x, topPx };
1171
1172 brush.SetStyle( wxBRUSHSTYLE_SOLID );
1173 brush.SetColour( m_trace->GetTraceColour() );
1174 aDC.SetBrush( brush );
1175 aDC.DrawPolygon( 3, poly );
1176
1177 aDC.SetTextForeground( textColor.ToColour() );
1178 aDC.DrawLabel( id, textRect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL );
1179
1180 if( m_trace->IsMultiRun() && m_trace->GetSweepCount() > 1
1181 && m_trace->GetSweepSize() != std::numeric_limits<size_t>::max() )
1182 {
1183 wxString runLabel;
1184 const std::vector<wxString>& labels = m_trace->GetMultiRunLabels();
1185
1186 if( m_sweepIndex >= 0 && m_sweepIndex < (int) labels.size() )
1187 {
1188 runLabel = labels[m_sweepIndex];
1189 }
1190 else
1191 {
1192 runLabel = wxString::Format( _( "Run %d" ), m_sweepIndex + 1 );
1193 }
1194
1195 wxSize runSize = aDC.GetTextExtent( runLabel );
1196 int runX = textRect.GetRight() + 6;
1197 wxRect runRect( wxPoint( runX, textRect.y ), runSize );
1198
1199 runRect.Inflate( 3, 1 );
1200
1201 wxBrush labelBrush( aWindow.GetBackgroundColour() );
1202 wxPen labelPen( cursorColor.ToColour() );
1203
1204 aDC.SetPen( labelPen );
1205 aDC.SetBrush( labelBrush );
1206 aDC.DrawRectangle( runRect );
1207 aDC.SetTextForeground( cursorColor.ToColour() );
1208 aDC.DrawLabel( runLabel, runRect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL );
1209 }
1210 }
1211}
1212
1213
1214bool CURSOR::Inside( const wxPoint& aPoint ) const
1215{
1216 if( !m_window || !m_trace )
1217 return false;
1218
1219 // An undefined coordinate draws no line, so it offers nothing to grab
1220 bool nearX = std::isfinite( m_coords.x )
1221 && std::abs( (double) aPoint.x - m_window->x2p( m_trace->x2s( m_coords.x ) ) ) <= DRAG_MARGIN;
1222 bool nearY = std::isfinite( m_coords.y )
1223 && std::abs( (double) aPoint.y - m_window->y2p( m_trace->y2s( m_coords.y ) ) ) <= DRAG_MARGIN;
1224
1225 return nearX || nearY;
1226}
1227
1228
1230{
1231 if( !m_window )
1232 return;
1233
1234 // An undefined coordinate has no pixel, so keep the last good reference for a drag to
1235 // measure against
1236 if( std::isfinite( m_coords.x ) )
1237 m_reference.x = m_window->x2p( m_trace->x2s( m_coords.x ) );
1238
1239 if( std::isfinite( m_coords.y ) )
1240 m_reference.y = m_window->y2p( m_trace->y2s( m_coords.y ) );
1241}
1242
1243
1244SIM_PLOT_TAB::SIM_PLOT_TAB( const wxString& aSimCommand, wxWindow* parent ) :
1245 SIM_TAB( aSimCommand, parent ),
1246 m_axis_x( nullptr ),
1247 m_axis_y1( nullptr ),
1248 m_axis_y2( nullptr ),
1249 m_axis_y3( nullptr ),
1250 m_smithGrid( nullptr ),
1251 m_dotted_cp( false ),
1252 m_smithMode( false ),
1253 m_smithZoom( 1.0 ),
1254 m_smithPanning( false ),
1255 m_smithLeftSkipped( false )
1256{
1257 m_sizer = new wxBoxSizer( wxVERTICAL );
1258 m_plotWin = new mpWindow( this, wxID_ANY );
1259
1260 m_plotWin->LimitView( true );
1261 m_plotWin->SetMargins( 30, 70, 45, 70 );
1263
1264 // Smith-mode pan/zoom, these run before mpWindow's handlers and skip when not in Smith mode
1265 m_plotWin->Bind( wxEVT_MOUSEWHEEL, &SIM_PLOT_TAB::onSmithMouseWheel, this );
1266 m_plotWin->Bind( wxEVT_MAGNIFY, &SIM_PLOT_TAB::onSmithMagnify, this );
1267 m_plotWin->Bind( wxEVT_MIDDLE_DOWN, &SIM_PLOT_TAB::onSmithMiddleDown, this );
1268 m_plotWin->Bind( wxEVT_LEFT_DOWN, &SIM_PLOT_TAB::onSmithLeftDown, this );
1269 m_plotWin->Bind( wxEVT_MOTION, &SIM_PLOT_TAB::onSmithMotion, this );
1270 m_plotWin->Bind( wxEVT_LEFT_UP, &SIM_PLOT_TAB::onSmithLeftUp, this );
1271 m_plotWin->Bind( wxEVT_LEFT_DCLICK, &SIM_PLOT_TAB::onSmithDClick, this );
1272 m_plotWin->Bind( wxEVT_RIGHT_DOWN, &SIM_PLOT_TAB::onSmithRightDown, this );
1273 m_plotWin->Bind( wxEVT_RIGHT_UP, &SIM_PLOT_TAB::onSmithRightUp, this );
1274
1275 // route the context-menu zoom commands to the Smith view
1276 for( int id : { mpID_ZOOM_IN, mpID_ZOOM_OUT, mpID_FIT, mpID_CENTER } )
1277 m_plotWin->Bind( wxEVT_MENU, &SIM_PLOT_TAB::onSmithMenuCommand, this, id );
1278
1279 updateAxes();
1280
1281 // a mpInfoLegend displays le name of traces on the left top panel corner:
1282 m_legend = new mpInfoLegend( wxRect( 0, 0, 200, 40 ), wxTRANSPARENT_BRUSH );
1283 m_legend->SetVisible( false );
1284 m_plotWin->AddLayer( m_legend );
1285 m_LastLegendPosition = m_legend->GetPosition();
1286
1287 m_plotWin->EnableDoubleBuffer( true );
1288 m_plotWin->UpdateAll();
1289
1290 m_sizer->Add( m_plotWin, 1, wxALL | wxEXPAND, 1 );
1291 SetSizer( m_sizer );
1292}
1293
1294
1296{
1297 // ~mpWindow destroys all the added layers, so there is no need to destroy m_traces contents
1298}
1299
1300
1301void SIM_PLOT_TAB::SetY1Scale( bool aLock, double aMin, double aMax )
1302{
1303 wxCHECK( m_axis_y1, /* void */ );
1304 m_axis_y1->SetAxisMinMax( aLock, aMin, aMax );
1305}
1306
1307
1308void SIM_PLOT_TAB::SetY2Scale( bool aLock, double aMin, double aMax )
1309{
1310 wxCHECK( m_axis_y2, /* void */ );
1311 m_axis_y2->SetAxisMinMax( aLock, aMin, aMax );
1312}
1313
1314
1315void SIM_PLOT_TAB::SetY3Scale( bool aLock, double aMin, double aMax )
1316{
1317 wxCHECK( m_axis_y3, /* void */ );
1318 m_axis_y3->SetAxisMinMax( aLock, aMin, aMax );
1319}
1320
1321
1323{
1324 LOG_SCALE<mpScaleXLog>* logScale = dynamic_cast<LOG_SCALE<mpScaleXLog>*>( m_axis_x );
1325 LIN_SCALE<mpScaleX>* linScale = dynamic_cast<LIN_SCALE<mpScaleX>*>( m_axis_x );
1326
1327 if( logScale )
1328 return logScale->GetUnits();
1329 else if( linScale )
1330 return linScale->GetUnits();
1331 else
1332 return wxEmptyString;
1333}
1334
1335
1337{
1338 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y1 );
1339
1340 if( linScale )
1341 return linScale->GetUnits();
1342 else
1343 return wxEmptyString;
1344}
1345
1346
1348{
1349 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y2 );
1350
1351 if( linScale )
1352 return linScale->GetUnits();
1353 else
1354 return wxEmptyString;
1355}
1356
1357
1359{
1360 LIN_SCALE<mpScaleY>* linScale = dynamic_cast<LIN_SCALE<mpScaleY>*>( m_axis_y3 );
1361
1362 if( linScale )
1363 return linScale->GetUnits();
1364 else
1365 return wxEmptyString;
1366}
1367
1368
1369void SIM_PLOT_TAB::updateAxes( int aNewTraceType )
1370{
1371 switch( GetSimType() )
1372 {
1373 case ST_AC:
1374 if( !m_axis_x )
1375 {
1376 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
1377 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1378 m_plotWin->AddLayer( m_axis_x );
1379
1380 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "dB" ), mpALIGN_LEFT );
1381 m_axis_y1->SetNameAlign( mpALIGN_LEFT );
1382 m_plotWin->AddLayer( m_axis_y1 );
1383
1384 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "°" ), mpALIGN_RIGHT );
1385 m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
1386 m_axis_y2->SetMasterScale( m_axis_y1 );
1387 m_plotWin->AddLayer( m_axis_y2 );
1388 }
1389
1390 m_axis_x->SetName( _( "Frequency" ) );
1391 m_axis_y1->SetName( _( "Gain" ) );
1392 m_axis_y2->SetName( _( "Phase" ) );
1393 break;
1394
1395 case ST_SP:
1396 if( !m_axis_x )
1397 {
1398 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
1399 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1400 m_plotWin->AddLayer( m_axis_x );
1401
1402 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_LEFT );
1403 m_axis_y1->SetNameAlign( mpALIGN_LEFT );
1404 m_plotWin->AddLayer( m_axis_y1 );
1405
1406 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "°" ), mpALIGN_RIGHT );
1407 m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
1408 m_axis_y2->SetMasterScale( m_axis_y1 );
1409 m_plotWin->AddLayer( m_axis_y2 );
1410 }
1411
1412 m_axis_x->SetName( _( "Frequency" ) );
1413 m_axis_y1->SetName( _( "Amplitude" ) );
1414 m_axis_y2->SetName( _( "Phase" ) );
1415 break;
1416
1417 case ST_DC:
1418 prepareDCAxes( aNewTraceType );
1419 break;
1420
1421 case ST_NOISE:
1422 if( !m_axis_x )
1423 {
1424 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
1425 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1426 m_plotWin->AddLayer( m_axis_x );
1427
1428 if( ( aNewTraceType & SPT_CURRENT ) == 0 )
1429 {
1430 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_LEFT );
1431 m_axis_y1->SetNameAlign( mpALIGN_LEFT );
1432 m_plotWin->AddLayer( m_axis_y1 );
1433 }
1434 else
1435 {
1436 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "" ), mpALIGN_RIGHT );
1437 m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
1438 m_plotWin->AddLayer( m_axis_y2 );
1439 }
1440 }
1441
1442 m_axis_x->SetName( _( "Frequency" ) );
1443
1444 if( m_axis_y1 )
1445 m_axis_y1->SetName( _( "Noise (V/√Hz)" ) );
1446
1447 if( m_axis_y2 )
1448 m_axis_y2->SetName( _( "Noise (A/√Hz)" ) );
1449
1450 break;
1451
1452 case ST_FFT:
1453 if( !m_axis_x )
1454 {
1455 m_axis_x = new LOG_SCALE<mpScaleXLog>( wxEmptyString, wxT( "Hz" ), mpALIGN_BOTTOM );
1456 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1457 m_plotWin->AddLayer( m_axis_x );
1458
1459 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "dB" ), mpALIGN_LEFT );
1460 m_axis_y1->SetNameAlign( mpALIGN_LEFT );
1461 m_plotWin->AddLayer( m_axis_y1 );
1462 }
1463
1464 m_axis_x->SetName( _( "Frequency" ) );
1465 m_axis_y1->SetName( _( "Intensity" ) );
1466 break;
1467
1468 case ST_TRAN:
1469 if( !m_axis_x )
1470 {
1471 m_axis_x = new TIME_SCALE( wxEmptyString, wxT( "s" ), mpALIGN_BOTTOM );
1472 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1473 m_plotWin->AddLayer( m_axis_x );
1474
1475 m_axis_y1 = new LIN_SCALE<mpScaleY>(wxEmptyString, wxT( "V" ), mpALIGN_LEFT );
1476 m_axis_y1->SetNameAlign( mpALIGN_LEFT );
1477 m_plotWin->AddLayer( m_axis_y1 );
1478
1479 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "A" ), mpALIGN_RIGHT );
1480 m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
1481 m_axis_y2->SetMasterScale( m_axis_y1 );
1482 m_plotWin->AddLayer( m_axis_y2 );
1483 }
1484
1485 m_axis_x->SetName( _( "Time" ) );
1486 m_axis_y1->SetName( _( "Voltage" ) );
1487 m_axis_y2->SetName( _( "Current" ) );
1488
1489 if( aNewTraceType & SPT_POWER )
1491
1492 if( m_axis_y3 )
1493 m_axis_y3->SetName( _( "Power" ) );
1494
1495 break;
1496
1497 default:
1498 // suppress warnings
1499 break;
1500 }
1501
1502 if( GetSimType() == ST_TRAN || GetSimType() == ST_DC )
1503 {
1504 if( m_axis_y3 )
1505 {
1506 m_plotWin->SetMargins( 30, 160, 45, 70 );
1507
1508 if( m_axis_y2 )
1509 m_axis_y2->SetNameAlign( mpALIGN_BORDER_RIGHT );
1510
1511 m_axis_y3->SetAlign( mpALIGN_BORDER_RIGHT );
1512 m_axis_y3->SetNameAlign( mpALIGN_BORDER_RIGHT );
1513 }
1514 else
1515 {
1516 m_plotWin->SetMargins( 30, 70, 45, 70 );
1517
1518 if( m_axis_y2 )
1519 m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
1520 }
1521 }
1522
1523 if( m_axis_x )
1524 m_axis_x->SetFont( KIUI::GetStatusFont( m_plotWin ) );
1525
1526 if( m_axis_y1 )
1528
1529 if( m_axis_y2 )
1531
1532 if( m_axis_y3 )
1534
1536}
1537
1538
1539void SIM_PLOT_TAB::prepareDCAxes( int aNewTraceType )
1540{
1541 wxString sim_cmd = GetSimCommand().Lower();
1542 wxString rem;
1543
1544 if( sim_cmd.StartsWith( ".dc", &rem ) )
1545 {
1546 wxChar ch = 0;
1547
1548 rem.Trim( false );
1549
1550 try
1551 {
1552 ch = rem.GetChar( 0 );
1553 }
1554 catch( ... )
1555 {
1556 // Best efforts
1557 }
1558
1559 switch( ch )
1560 {
1561 // Make sure that we have a reliable default (even if incorrectly labeled)
1562 default:
1563 case 'v':
1564 if( !m_axis_x )
1565 {
1566 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "V" ), mpALIGN_BOTTOM );
1567 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1568 m_plotWin->AddLayer( m_axis_x );
1569 }
1570
1571 m_axis_x->SetName( _( "Voltage (swept)" ) );
1572 break;
1573
1574 case 'i':
1575 if( !m_axis_x )
1576 {
1577 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "A" ), mpALIGN_BOTTOM );
1578 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1579 m_plotWin->AddLayer( m_axis_x );
1580 }
1581
1582 m_axis_x->SetName( _( "Current (swept)" ) );
1583 break;
1584
1585 case 'r':
1586 if( !m_axis_x )
1587 {
1588 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "Ω" ), mpALIGN_BOTTOM );
1589 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1590 m_plotWin->AddLayer( m_axis_x );
1591 }
1592
1593 m_axis_x->SetName( _( "Resistance (swept)" ) );
1594 break;
1595
1596 case 't':
1597 if( !m_axis_x )
1598 {
1599 m_axis_x = new LIN_SCALE<mpScaleX>( wxEmptyString, wxT( "°C" ), mpALIGN_BOTTOM );
1600 m_axis_x->SetNameAlign( mpALIGN_BOTTOM );
1601 m_plotWin->AddLayer( m_axis_x );
1602 }
1603
1604 m_axis_x->SetName( _( "Temperature (swept)" ) );
1605 break;
1606 }
1607
1608 if( !m_axis_y1 )
1609 {
1610 m_axis_y1 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "V" ), mpALIGN_LEFT );
1611 m_axis_y1->SetNameAlign( mpALIGN_LEFT );
1612 m_plotWin->AddLayer( m_axis_y1 );
1613 }
1614
1615 if( !m_axis_y2 )
1616 {
1617 m_axis_y2 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "A" ), mpALIGN_RIGHT );
1618 m_axis_y2->SetNameAlign( mpALIGN_RIGHT );
1619 m_plotWin->AddLayer( m_axis_y2 );
1620 }
1621
1622 m_axis_y1->SetName( _( "Voltage (measured)" ) );
1623 m_axis_y2->SetName( _( "Current" ) );
1624
1625 if( ( aNewTraceType & SPT_POWER ) )
1627
1628 if( m_axis_y3 )
1629 m_axis_y3->SetName( _( "Power" ) );
1630 }
1631}
1632
1633
1635{
1636 if( !m_axis_y3 )
1637 {
1638 m_plotWin->SetMargins( 30, 160, 45, 70 );
1639 m_axis_y3 = new LIN_SCALE<mpScaleY>( wxEmptyString, wxT( "W" ), mpALIGN_BORDER_RIGHT );
1640 m_axis_y3->SetNameAlign( mpALIGN_BORDER_RIGHT );
1641 m_axis_y3->SetMasterScale( m_axis_y1 );
1642 m_plotWin->AddLayer( m_axis_y3 );
1643 }
1644
1645 if( m_axis_y3 )
1646 {
1647 m_axis_y3->SetAlign( mpALIGN_BORDER_RIGHT );
1648 m_axis_y3->SetNameAlign( mpALIGN_BORDER_RIGHT );
1649 }
1650
1651 if( m_axis_y2 )
1652 m_axis_y2->SetNameAlign( mpALIGN_BORDER_RIGHT );
1653}
1654
1655
1657{
1658 // Update bg and fg colors:
1659 m_plotWin->SetColourTheme( m_colors.GetPlotColor( SIM_PLOT_COLORS::COLOR_SET::BACKGROUND ),
1662
1663 if( m_smithGrid )
1664 m_smithGrid->SetPen( wxPen( m_colors.GetPlotColor( SIM_PLOT_COLORS::COLOR_SET::AXIS ), 1 ) );
1665
1666 m_plotWin->UpdateAll();
1667}
1668
1669
1671{
1672 updateAxes();
1673 m_plotWin->UpdateAll();
1674}
1675
1676
1678{
1679 int type = trace->GetType();
1680 wxPenStyle penStyle;
1681
1682 if( ( type & SPT_AC_GAIN ) > 0 )
1683 penStyle = wxPENSTYLE_SOLID;
1684 else if( ( type & SPT_AC_PHASE ) > 0 )
1685 penStyle = m_dotted_cp ? wxPENSTYLE_DOT : wxPENSTYLE_SOLID;
1686 else if( ( type & SPT_CURRENT ) > 0 )
1687 penStyle = m_dotted_cp ? wxPENSTYLE_DOT : wxPENSTYLE_SOLID;
1688 else
1689 penStyle = wxPENSTYLE_SOLID;
1690
1691 trace->SetPen( wxPen( trace->GetTraceColour(), 2, penStyle ) );
1692 m_sessionTraceColors[ trace->GetName() ] = trace->GetTraceColour();
1693}
1694
1695
1696TRACE* SIM_PLOT_TAB::GetOrAddTrace( const wxString& aVectorName, int aType )
1697{
1698 TRACE* trace = GetTrace( aVectorName, aType );
1699
1700 if( !trace )
1701 {
1702 updateAxes( aType );
1703
1704 if( GetSimType() == ST_TRAN || GetSimType() == ST_DC )
1705 {
1706 bool hasVoltageTraces = false;
1707
1708 for( const auto& [ id, candidate ] : m_traces )
1709 {
1710 if( candidate->GetType() & SPT_VOLTAGE )
1711 {
1712 hasVoltageTraces = true;
1713 break;
1714 }
1715 }
1716
1717 if( !hasVoltageTraces )
1718 {
1719 if( m_axis_y2 )
1720 m_axis_y2->SetMasterScale( nullptr );
1721
1722 if( m_axis_y3 )
1723 m_axis_y3->SetMasterScale( nullptr );
1724 }
1725 }
1726
1727 if( aType & SPT_SP_SMITH )
1728 trace = new SMITH_TRACE( aVectorName, (SIM_TRACE_TYPE) aType );
1729 else
1730 trace = new TRACE( aVectorName, (SIM_TRACE_TYPE) aType );
1731
1732 if( m_sessionTraceColors.count( aVectorName ) )
1733 trace->SetTraceColour( m_sessionTraceColors[ aVectorName ] );
1734 else
1735 trace->SetTraceColour( m_colors.GenerateColor( m_sessionTraceColors ) );
1736
1737 UpdateTraceStyle( trace );
1738 m_traces[ getTraceId( aVectorName, aType ) ] = trace;
1739
1740 m_plotWin->AddLayer( (mpLayer*) trace );
1741 }
1742
1743 return trace;
1744}
1745
1746
1747void SIM_PLOT_TAB::SetTraceData( TRACE* trace, std::vector<double>& aX, std::vector<double>& aY,
1748 int aSweepCount, size_t aSweepSize, bool aIsMultiRun,
1749 const std::vector<wxString>& aMultiRunLabels )
1750{
1751 // smith traces carry Re/Im of the reflection coefficient, not frequency
1752 bool smithTrace = ( trace->GetType() & SPT_SP_SMITH ) > 0;
1753
1754 if( dynamic_cast<LOG_SCALE<mpScaleXLog>*>( m_axis_x ) && !smithTrace )
1755 {
1756 // log( 0 ) is not valid.
1757 if( aX.size() > 0 && aX[0] == 0 )
1758 {
1759 aX.erase( aX.begin() );
1760 aY.erase( aY.begin() );
1761 }
1762 }
1763
1764 if( GetSimType() == ST_AC || GetSimType() == ST_FFT )
1765 {
1766 if( trace->GetType() & SPT_AC_PHASE )
1767 {
1768 for( double& pt : aY )
1769 pt = pt * 180.0 / M_PI; // convert to degrees
1770 }
1771 else
1772 {
1773 for( double& pt : aY )
1774 pt = MagnitudeToDb( pt ); // NaN where there is no signal
1775 }
1776 }
1777
1778 trace->SetData( aX, aY );
1779 trace->SetSweepCount( aSweepCount );
1780 trace->SetSweepSize( aSweepSize );
1781 trace->SetIsMultiRun( aIsMultiRun );
1782 trace->SetMultiRunLabels( aMultiRunLabels );
1783
1784 // Phase and currents on second Y axis, except for AC currents, those use the same axis as voltage
1785 if( smithTrace )
1786 {
1787 // drawn through the chart geometry, not the axis transforms
1788 trace->SetScale( nullptr, nullptr );
1789 }
1790 else if( ( trace->GetType() & SPT_AC_PHASE )
1791 || ( ( GetSimType() != ST_AC ) && ( trace->GetType() & SPT_CURRENT ) ) )
1792 {
1793 trace->SetScale( m_axis_x, m_axis_y2 );
1794 }
1795 else if( trace->GetType() & SPT_POWER )
1796 {
1797 trace->SetScale( m_axis_x, m_axis_y3 );
1798 }
1799 else
1800 {
1801 trace->SetScale( m_axis_x, m_axis_y1 );
1802 }
1803
1804 for( auto& [ cursorId, cursor ] : trace->GetCursors() )
1805 {
1806 if( cursor )
1807 cursor->SetCoordX( cursor->GetCoords().x );
1808 }
1809
1811}
1812
1813
1815{
1816 bool hasY1Traces = false;
1817 bool hasY2Traces = false;
1818 bool hasY3Traces = false;
1819
1820 if( !m_smithMode )
1821 {
1822 for( const auto& [name, trace] : m_traces )
1823 {
1824 if( !trace )
1825 continue;
1826
1827 if( trace->GetType() & SPT_POWER )
1828 {
1829 hasY3Traces = true;
1830 }
1831 else if( ( trace->GetType() & SPT_AC_PHASE )
1832 || ( ( GetSimType() != ST_AC ) && ( trace->GetType() & SPT_CURRENT ) ) )
1833 {
1834 hasY2Traces = true;
1835 }
1836 else
1837 {
1838 hasY1Traces = true;
1839 }
1840 }
1841 }
1842
1843 bool visibilityChanged = false;
1844
1845 if( m_axis_x && m_axis_x->IsVisible() != !m_smithMode )
1846 {
1847 m_axis_x->SetVisible( !m_smithMode );
1848 visibilityChanged = true;
1849 }
1850
1851 if( m_axis_y1 && m_axis_y1->IsVisible() != hasY1Traces )
1852 {
1853 m_axis_y1->SetVisible( hasY1Traces );
1854 visibilityChanged = true;
1855 }
1856
1857 if( m_axis_y2 && m_axis_y2->IsVisible() != hasY2Traces )
1858 {
1859 m_axis_y2->SetVisible( hasY2Traces );
1860 visibilityChanged = true;
1861 }
1862
1863 if( m_axis_y3 && m_axis_y3->IsVisible() != hasY3Traces )
1864 {
1865 m_axis_y3->SetVisible( hasY3Traces );
1866 visibilityChanged = true;
1867 }
1868
1869 if( visibilityChanged )
1870 m_plotWin->UpdateAll();
1871}
1872
1873
1875{
1876 for( const auto& [ name, trace ] : m_traces )
1877 {
1878 if( trace == aTrace )
1879 {
1880 m_traces.erase( name );
1881 break;
1882 }
1883 }
1884
1885 for( const auto& [ id, cursor ] : aTrace->GetCursors() )
1886 {
1887 if( cursor )
1888 m_plotWin->DelLayer( cursor, true );
1889 }
1890
1891 m_plotWin->DelLayer( aTrace, true, true );
1892 ResetScales( false );
1895}
1896
1897
1898bool SIM_PLOT_TAB::DeleteTrace( const wxString& aVectorName, int aTraceType )
1899{
1900 if( TRACE* trace = GetTrace( aVectorName, aTraceType ) )
1901 {
1902 DeleteTrace( trace );
1903 return true;
1904 }
1905
1906 return false;
1907}
1908
1909
1910void SIM_PLOT_TAB::SetSmithMode( bool aEnable )
1911{
1912 // only S-parameter tabs have a Smith view, a stale workbook cannot force one elsewhere
1913 if( aEnable && GetSimType() != ST_SP )
1914 return;
1915
1916 if( m_smithMode == aEnable )
1917 return;
1918
1919 m_smithMode = aEnable;
1920
1921 // a mode switch mid-gesture must not leave a pan or a skipped click behind
1922 m_smithPanning = false;
1923 m_smithLeftSkipped = false;
1924
1925 if( aEnable && !m_smithGrid )
1926 {
1927 m_smithGrid = new SMITH_GRID();
1929 m_smithGrid->SetPen( wxPen( m_colors.GetPlotColor( SIM_PLOT_COLORS::COLOR_SET::AXIS ), 1 ) );
1930 m_plotWin->AddLayer( m_smithGrid );
1931 }
1932
1933 if( m_smithGrid )
1934 m_smithGrid->SetVisible( aEnable );
1935
1936 if( aEnable )
1938
1940
1942 m_plotWin->UpdateAll();
1943}
1944
1945
1947{
1948 if( !m_smithGrid )
1949 return;
1950
1951 double z0 = 0.0;
1952 bool mixed = false;
1953
1954 for( const auto& [name, trace] : m_traces )
1955 {
1956 SMITH_TRACE* smithTrace = dynamic_cast<SMITH_TRACE*>( trace );
1957
1958 if( !smithTrace )
1959 continue;
1960
1961 if( z0 == 0.0 )
1962 z0 = smithTrace->GetReferenceImpedance();
1963 else if( smithTrace->GetReferenceImpedance() != z0 )
1964 mixed = true;
1965 }
1966
1967 // with no smith traces the grid keeps its last z0
1968 if( z0 > 0.0 )
1969 m_smithGrid->SetReferenceImpedance( z0 );
1970
1971 m_smithGrid->SetNormalizedLabels( mixed );
1972}
1973
1974
1976{
1978}
1979
1980
1981void SIM_PLOT_TAB::SmithZoomAt( const wxPoint& aPos, double aFactor )
1982{
1983 SMITH_VIEW view;
1984
1985 if( !getSmithView( view ) )
1986 return;
1987
1988 double newZoom = std::clamp( m_smithZoom * aFactor, 1.0, 50.0 );
1989
1990 if( newZoom <= 1.0 )
1991 {
1993 m_plotWin->Refresh();
1994 return;
1995 }
1996
1997 // keep the point under the cursor fixed while zooming
1998 m_smithPan = SMITH_MATH::ZoomAboutPoint( view, aPos, newZoom );
1999 m_smithZoom = newZoom;
2000
2001 m_plotWin->Refresh();
2002}
2003
2004
2005void SIM_PLOT_TAB::SmithPanBy( const wxPoint& aDelta )
2006{
2007 SMITH_VIEW view;
2008
2009 if( !getSmithView( view ) )
2010 return;
2011
2012 m_smithPan.x -= aDelta.x / view.radius;
2013 m_smithPan.y += aDelta.y / view.radius;
2014
2015 m_plotWin->Refresh();
2016}
2017
2018
2019void SIM_PLOT_TAB::onSmithMouseWheel( wxMouseEvent& aEvent )
2020{
2021 if( !m_smithMode )
2022 {
2023 aEvent.Skip();
2024 return;
2025 }
2026
2027 // swallow horizontal scroll too, mpWindow would pan its hidden axes with it
2028 if( aEvent.GetWheelAxis() != wxMOUSE_WHEEL_VERTICAL || aEvent.GetWheelRotation() == 0 )
2029 return;
2030
2031 // do not skip, or mpWindow would also zoom its hidden axes
2032 SmithZoomAt( aEvent.GetPosition(), aEvent.GetWheelRotation() > 0 ? 1.2 : 1.0 / 1.2 );
2033}
2034
2035
2036void SIM_PLOT_TAB::onSmithMagnify( wxMouseEvent& aEvent )
2037{
2038 if( !m_smithMode )
2039 {
2040 aEvent.Skip();
2041 return;
2042 }
2043
2044 double factor = aEvent.GetMagnification() + 1.0;
2045
2046 if( factor > 0.0 )
2047 SmithZoomAt( aEvent.GetPosition(), factor );
2048}
2049
2050
2051void SIM_PLOT_TAB::onSmithMiddleDown( wxMouseEvent& aEvent )
2052{
2053 // keep mpWindow's middle-button pan off the hidden axes
2054 if( !m_smithMode )
2055 aEvent.Skip();
2056}
2057
2058
2059void SIM_PLOT_TAB::onSmithLeftDown( wxMouseEvent& aEvent )
2060{
2061 // clear stale pan state so it cannot hijack a cursor grab
2062 m_smithPanning = false;
2063 m_smithLeftSkipped = false;
2064
2065 wxPoint pos = aEvent.GetPosition();
2066
2067 if( m_smithMode && !m_plotWin->IsInsideInfoLayer( pos ) )
2068 {
2069 m_smithPanning = true;
2070 m_smithPanLast = pos;
2071 return;
2072 }
2073
2074 // on a cursor or the legend, let mpWindow drag it, and remember that it saw the click
2075 // so the matching motions and release reach it too
2076 m_smithLeftSkipped = true;
2077 aEvent.Skip();
2078}
2079
2080
2081void SIM_PLOT_TAB::onSmithMotion( wxMouseEvent& aEvent )
2082{
2083 if( m_smithMode && aEvent.Dragging() )
2084 {
2085 if( aEvent.LeftIsDown() && !m_smithLeftSkipped )
2086 {
2087 // a left drag mpWindow did not see the start of, pan if one is active, and
2088 // swallow either way so mpWindow cannot rubber-band from a stale click point
2089 if( m_smithPanning )
2090 {
2091 wxPoint pos = aEvent.GetPosition();
2092
2093 SmithPanBy( pos - m_smithPanLast );
2094 m_smithPanLast = pos;
2095 }
2096
2097 return;
2098 }
2099
2100 // keep mpWindow's middle-button pan off the hidden axes
2101 if( aEvent.MiddleIsDown() )
2102 return;
2103 }
2104
2105 aEvent.Skip();
2106}
2107
2108
2109void SIM_PLOT_TAB::onSmithLeftUp( wxMouseEvent& aEvent )
2110{
2111 if( m_smithMode )
2112 {
2113 m_smithPanning = false;
2114
2115 // a release mpWindow saw no click for would zoom the hidden axes to the rect
2116 // between its stale click point and this position
2117 if( !m_smithLeftSkipped )
2118 return;
2119
2120 m_smithLeftSkipped = false;
2121 }
2122
2123 aEvent.Skip();
2124}
2125
2126
2127void SIM_PLOT_TAB::onSmithDClick( wxMouseEvent& aEvent )
2128{
2129 wxPoint pos = aEvent.GetPosition();
2130
2131 if( m_smithMode && !m_plotWin->IsInsideInfoLayer( pos ) )
2132 {
2134 m_plotWin->Refresh();
2135 return;
2136 }
2137
2138 aEvent.Skip();
2139}
2140
2141
2142void SIM_PLOT_TAB::onSmithRightDown( wxMouseEvent& aEvent )
2143{
2144 // remember where the menu opened, for its zoom commands
2145 if( m_smithMode )
2146 m_smithMenuPos = aEvent.GetPosition();
2147
2148 aEvent.Skip();
2149}
2150
2151
2152void SIM_PLOT_TAB::onSmithRightUp( wxMouseEvent& aEvent )
2153{
2154 if( !m_smithMode )
2155 {
2156 aEvent.Skip();
2157 return;
2158 }
2159
2160 // no zoom history here, so grey out Undo/Redo and show the menu ourselves
2161 wxMenu* menu = m_plotWin->GetPopupMenu();
2162
2163 menu->Enable( mpID_ZOOM_UNDO, false );
2164 menu->Enable( mpID_ZOOM_REDO, false );
2165
2166 m_plotWin->PopupMenu( menu, aEvent.GetPosition() );
2167}
2168
2169
2170void SIM_PLOT_TAB::onSmithMenuCommand( wxCommandEvent& aEvent )
2171{
2172 if( !m_smithMode )
2173 {
2174 aEvent.Skip();
2175 return;
2176 }
2177
2178 switch( aEvent.GetId() )
2179 {
2180 case mpID_ZOOM_IN: SmithZoomAt( m_smithMenuPos, 1.5 ); break;
2181 case mpID_ZOOM_OUT: SmithZoomAt( m_smithMenuPos, 1.0 / 1.5 ); break;
2182
2183 case mpID_FIT:
2185 m_plotWin->Refresh();
2186 break;
2187
2188 case mpID_CENTER:
2189 {
2190 SMITH_VIEW view;
2191
2192 if( getSmithView( view ) )
2193 {
2195 m_plotWin->Refresh();
2196 }
2197
2198 break;
2199 }
2200
2201 default: aEvent.Skip();
2202 }
2203}
2204
2205
2206void SIM_PLOT_TAB::EnableCursor( TRACE* aTrace, int aCursorId, const wxString& aSignalName )
2207{
2208 CURSOR* cursor;
2209
2210 if( aTrace->GetType() & SPT_SP_SMITH )
2211 {
2212 SMITH_TRACE* smithTrace = static_cast<SMITH_TRACE*>( aTrace );
2213
2214 cursor = new SMITH_CURSOR( smithTrace, this );
2215
2216 // start somewhere on the locus, biased per cursor id like the rectangular case
2217 const std::vector<double>& freqs = smithTrace->GetFrequencies();
2218
2219 if( !freqs.empty() )
2220 cursor->SetCoordX( freqs[freqs.size() * ( aCursorId == 1 ? 2 : 3 ) / 5] );
2221 }
2222 else
2223 {
2224 mpWindow* win = GetPlotWin();
2225 int width = win->GetXScreen() - win->GetMarginLeft() - win->GetMarginRight();
2226 int center = win->GetMarginLeft() + KiROUND( width * ( aCursorId == 1 ? 0.4 : 0.6 ) );
2227
2228 cursor = new CURSOR( aTrace, this );
2229
2230 cursor->SetX( center );
2231 }
2232
2233 cursor->SetName( aSignalName );
2234 aTrace->SetCursor( aCursorId, cursor );
2235 m_plotWin->AddLayer( cursor );
2236
2237 // Notify the parent window about the changes
2238 wxQueueEvent( this, new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
2239}
2240
2241
2242void SIM_PLOT_TAB::DisableCursor( TRACE* aTrace, int aCursorId )
2243{
2244 if( CURSOR* cursor = aTrace->GetCursor( aCursorId ) )
2245 {
2246 aTrace->SetCursor( aCursorId, nullptr );
2247 GetPlotWin()->DelLayer( cursor, true );
2248
2249 // Notify the parent window about the changes
2250 wxQueueEvent( this, new wxCommandEvent( EVT_SIM_CURSOR_UPDATE ) );
2251 }
2252}
2253
2254
2255void SIM_PLOT_TAB::ResetScales( bool aIncludeX )
2256{
2257 if( m_axis_x && aIncludeX )
2258 {
2259 m_axis_x->ResetDataRange();
2260
2261 if( GetSimType() == ST_TRAN )
2262 {
2263 wxStringTokenizer tokenizer( GetSimCommand(), " \t\r\n", wxTOKEN_STRTOK );
2264 wxString cmd = tokenizer.GetNextToken().Lower();
2265
2266 wxASSERT( cmd == wxS( ".tran" ) );
2267
2268 SPICE_VALUE step;
2269 SPICE_VALUE end( 1.0 );
2270 SPICE_VALUE start( 0.0 );
2271
2272 if( tokenizer.HasMoreTokens() )
2273 step = SPICE_VALUE( tokenizer.GetNextToken() );
2274
2275 if( tokenizer.HasMoreTokens() )
2276 end = SPICE_VALUE( tokenizer.GetNextToken() );
2277
2278 if( tokenizer.HasMoreTokens() )
2279 start = SPICE_VALUE( tokenizer.GetNextToken() );
2280
2281 static_cast<TIME_SCALE*>( m_axis_x )->SetStartAndEnd( start.ToDouble(), end.ToDouble() );
2282 }
2283 }
2284
2285 if( m_axis_y1 )
2286 m_axis_y1->ResetDataRange();
2287
2288 if( m_axis_y2 )
2289 m_axis_y2->ResetDataRange();
2290
2291 if( m_axis_y3 )
2292 m_axis_y3->ResetDataRange();
2293
2294 for( auto& [ name, trace ] : m_traces )
2295 trace->UpdateScales();
2296}
2297
2298
2299wxDEFINE_EVENT( EVT_SIM_CURSOR_UPDATE, wxCommandEvent );
const char * name
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
The SIMULATOR_FRAME holds the main user-interface for running simulations.
mpWindow * m_window
void Move(wxPoint aDelta) override
Moves the layer rectangle of given pixel deltas.
wxString getID()
wxRealPoint m_coords
bool m_updateRef
static constexpr int DRAG_MARGIN
bool Inside(const wxPoint &aPoint) const override
Checks whether a point is inside the info box rectangle.
void doSetCoordX(double aValue)
int m_sweepIndex
bool OnDoubleClick(const wxPoint &aPoint, mpWindow &aWindow) override
virtual void SetCoordX(double aValue)
void UpdateReference() override
Updates the rectangle reference point.
bool m_updateRequired
double m_snapTargetY
TRACE * m_trace
void Plot(wxDC &aDC, mpWindow &aWindow) override
Plot method.
bool m_snapToNearest
void Update()
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
COLOR4D & Invert()
Makes the color inverted, alpha remains the same.
Definition color4d.h:239
wxColour ToColour() const
Definition color4d.cpp:221
double Distance(const COLOR4D &other) const
Returns the distance (in RGB space) between two colors.
Definition color4d.cpp:549
COLOR4D Mix(const COLOR4D &aColor, double aFactor) const
Return a color that is mixed with the input by a factor.
Definition color4d.h:292
void formatLabels() override
wxString GetUnits() const
const wxString m_unit
LIN_SCALE(const wxString &name, const wxString &unit, int flags)
wxString m_base_axis_label
wxString GetUnits() const
const wxString m_unit
LOG_SCALE(const wxString &name, const wxString &unit, int flags)
void formatLabels() override
bool DeleteTrace(const wxString &aVectorName, int aTraceType)
wxPoint m_smithMenuPos
double GetSmithZoom() const
wxPoint m_smithPanLast
void UpdateSmithReferenceImpedance()
mpScaleXBase * m_axis_x
void onSmithMotion(wxMouseEvent &aEvent)
mpWindow * GetPlotWin() const
void prepareDCAxes(int aNewTraceType)
Create/Ensure axes are available for plotting.
void SetTraceData(TRACE *aTrace, std::vector< double > &aX, std::vector< double > &aY, int aSweepCount, size_t aSweepSize, bool aIsMultiRun=false, const std::vector< wxString > &aMultiRunLabels={})
wxString GetUnitsY2() const
SMITH_GRID * m_smithGrid
void SetY2Scale(bool aLock, double aMin, double aMax)
bool m_smithLeftSkipped
void SmithZoomAt(const wxPoint &aPos, double aFactor)
TRACE * GetTrace(const wxString &aVecName, int aType) const
void SetSmithMode(bool aEnable)
virtual ~SIM_PLOT_TAB()
void SetY1Scale(bool aLock, double aMin, double aMax)
mpInfoLegend * m_legend
void onSmithRightDown(wxMouseEvent &aEvent)
void UpdateAxisVisibility()
void SetY3Scale(bool aLock, double aMin, double aMax)
wxBoxSizer * m_sizer
std::map< wxString, TRACE * > m_traces
SIM_PLOT_COLORS m_colors
void UpdateTraceStyle(TRACE *trace)
Update plot colors.
void ResetScales(bool aIncludeX)
Update trace line style.
void onSmithMiddleDown(wxMouseEvent &aEvent)
void onSmithMenuCommand(wxCommandEvent &aEvent)
void UpdatePlotColors()
mpScaleY * m_axis_y2
SIM_PLOT_TAB(const wxString &aSimCommand, wxWindow *parent)
void EnableCursor(TRACE *aTrace, int aCursorId, const wxString &aSignalName)
wxString GetUnitsX() const
void OnLanguageChanged() override
Getter for math plot window.
wxRealPoint m_smithPan
void EnsureThirdYAxisExists()
wxString getTraceId(const wxString &aVectorName, int aType) const
Construct the plot axes for DC simulation plot.
TRACE * GetOrAddTrace(const wxString &aVectorName, int aType)
mpScaleY * m_axis_y1
void onSmithLeftUp(wxMouseEvent &aEvent)
double m_smithZoom
void SmithPanBy(const wxPoint &aDelta)
Traces and cursors set aside while in Smith mode, restored when leaving it.
mpScaleY * m_axis_y3
wxPoint m_LastLegendPosition
void onSmithDClick(wxMouseEvent &aEvent)
void onSmithRightUp(wxMouseEvent &aEvent)
void onSmithMouseWheel(wxMouseEvent &aEvent)
wxString GetUnitsY1() const
std::map< wxString, wxColour > m_sessionTraceColors
mpWindow * m_plotWin
void DisableCursor(TRACE *aTrace, int aCursorId)
Reset scale ranges to fit the current traces.
const wxRealPoint & GetSmithPan() const
void onSmithLeftDown(wxMouseEvent &aEvent)
void ResetSmithView()
Restore a saved view, values are validated and clamped.
bool getSmithView(SMITH_VIEW &aView) const
void updateAxes(int aNewTraceType=SIM_TRACE_TYPE::SPT_UNKNOWN)
void onSmithMagnify(wxMouseEvent &aEvent)
wxString GetUnitsY3() const
SIM_TAB()
Definition sim_tab.cpp:29
SIM_TYPE GetSimType() const
Definition sim_tab.cpp:71
const wxString & GetSimCommand() const
Definition sim_tab.h:48
Trace hidden while the tab is in Smith mode, kept so leaving the mode restores it.
void UpdateReference() override
Updates the rectangle reference point.
void Plot(wxDC &aDC, mpWindow &aWindow) override
Plot method.
bool Inside(const wxPoint &aPoint) const override
Checks whether a point is inside the info box rectangle.
void SetCoordX(double aValue) override
void snapToIndex(int aIndex)
void snapToFrequency(double aFreq)
wxRealPoint m_gamma
void Move(wxPoint aDelta) override
Moves the layer rectangle of given pixel deltas.
Reflection coefficient locus, Re in X and Im in Y, drawn on the Smith chart.
static bool GetChartView(mpWindow &aWindow, double aZoom, const wxRealPoint &aPan, SMITH_VIEW &aView)
void Plot(wxDC &aDC, mpWindow &aWindow) override
Plot given view of layer to the given device context.
bool m_normalized
Cursor that snaps along a Smith chart locus, keyed by frequency.
void Plot(wxDC &aDC, mpWindow &aWindow) override
Layer plot handler.
const std::vector< double > & GetFrequencies() const
double GetReferenceImpedance() const
Helper class to recognize Spice formatted values.
Definition spice_value.h:52
double ToDouble() const
void ResetDataRange() override
void ExtendDataRange(double minV, double maxV) override
void SetStartAndEnd(double aStartTime, double aEndTime)
double m_startTime
TIME_SCALE(const wxString &name, const wxString &unit, int flags)
Overlay layer drawing the Smith chart grid (constant resistance and reactance circles)
void SetIsMultiRun(bool aIsMultiRun)
void SetTraceColour(const wxColour &aColour)
void SetMultiRunLabels(const std::vector< wxString > &aLabels)
std::map< int, CURSOR * > & GetCursors()
SIM_TRACE_TYPE GetType() const
void SetData(const std::vector< double > &aX, const std::vector< double > &aY) override
Assigns new data set for the trace.
void SetCursor(int aCursorId, CURSOR *aCursor)
const std::vector< double > & GetDataY() const
wxColour GetTraceColour() const
const std::vector< double > & GetDataX() const
CURSOR * GetCursor(int aCursorId)
void SetSweepSize(size_t aSweepSize)
Definition mathplot.h:1438
size_t GetSweepSize() const
Definition mathplot.h:1439
void SetSweepCount(int aSweepCount)
Definition mathplot.h:1437
int GetSweepCount() const override
Definition mathplot.h:1463
virtual void SetScale(mpScaleBase *scaleX, mpScaleBase *scaleY)
wxPoint m_reference
Definition mathplot.h:393
wxRect m_dim
Definition mathplot.h:391
virtual void UpdateReference()
Updates the rectangle reference point.
Definition mathplot.cpp:153
virtual void Move(wxPoint delta)
Moves the layer rectangle of given pixel deltas.
Definition mathplot.cpp:146
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
wxFont m_font
Definition mathplot.h:317
const wxString & GetName() const
Get layer name.
Definition mathplot.h:249
bool m_continuous
Definition mathplot.h:322
bool m_visible
Definition mathplot.h:325
const wxFont & GetFont() const
Get font set for this layer.
Definition mathplot.h:259
const wxPen & GetPen() const
Get pen set for this layer.
Definition mathplot.h:264
void SetPen(const wxPen &pen)
Set layer pen.
Definition mathplot.h:293
wxPen m_pen
Definition mathplot.h:318
bool m_rangeSet
Definition mathplot.h:755
double m_maxV
Definition mathplot.h:754
double m_minV
Definition mathplot.h:754
Canvas for plotting mpLayer implementations.
Definition mathplot.h:920
int GetMarginLeft() const
Definition mathplot.h:1231
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
wxCoord x2p(double x)
Converts graph (floating point) coordinates into mpWindow (screen) pixel coordinates,...
Definition mathplot.h:1102
int GetXScreen() const
Definition mathplot.h:1042
int GetMarginTop() const
Definition mathplot.h:1225
bool DelLayer(mpLayer *layer, bool alsoDeleteObject=false, bool refreshDisplay=true)
Remove a plot layer from the canvas.
wxCoord y2p(double y)
Converts graph (floating point) coordinates into mpWindow (screen) pixel coordinates,...
Definition mathplot.h:1106
int GetMarginRight() const
Definition mathplot.h:1227
int GetMarginBottom() const
Definition mathplot.h:1229
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
#define mpALIGN_BORDER_RIGHT
Aligns Y axis to right border.
Definition mathplot.h:467
class WXDLLIMPEXP_MATHPLOT mpWindow
Definition mathplot.h:109
#define mpALIGN_RIGHT
Aligns label to the right.
Definition mathplot.h:439
#define mpALIGN_LEFT
Aligns label to the left.
Definition mathplot.h:443
@ 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_BOTTOM
Aligns label to the bottom.
Definition mathplot.h:447
KICOMMON_API wxFont GetStatusFont(wxWindow *aWindow)
double SeriesCapacitance(double aReactance, double aFreq)
Pan that keeps the gamma point under aPos fixed when the view zooms to aNewZoom.
Definition smith_math.h:101
double SeriesInductance(double aReactance, double aFreq)
Definition smith_math.h:96
double VSWR(double aGammaMag)
Definition smith_math.h:78
wxRealPoint ZoomAboutPoint(const SMITH_VIEW &aView, const wxPoint &aPos, double aNewZoom)
S-parameter vectors are named S_<responsePort>_<drivePort>.
Definition smith_math.h:107
bool GammaToImpedance(double aRe, double aIm, double aZ0, double &aResistance, double &aReactance)
< Impedance of a reflection coefficient, z = z0 ( 1 + gamma ) / ( 1 - gamma ), false at the gamma = 1...
Definition smith_math.h:65
double ReturnLoss(double aGammaMag)
Series equivalent element of a reactance at one frequency, henries for aReactance > 0,...
Definition smith_math.h:86
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
Class is responsible for providing colors for traces on simulation plot.
wxDEFINE_EVENT(EVT_SIM_CURSOR_UPDATE, wxCommandEvent)
static void getSISuffix(double x, const wxString &unit, int &power, wxString &suffix)
static int countDecimalDigits(double x, int maxDigits)
static bool smithView(mpWindow &aWindow, SMITH_VIEW &aView)
static wxString formatFloat(double x, int nDigits)
SIM_TRACE_TYPE
Definition sim_types.h:49
@ SPT_AC_PHASE
Definition sim_types.h:53
@ SPT_AC_GAIN
Definition sim_types.h:54
@ SPT_VOLTAGE
Definition sim_types.h:51
@ SPT_POWER
Definition sim_types.h:55
@ SPT_CURRENT
Definition sim_types.h:52
@ SPT_SP_SMITH
Definition sim_types.h:57
double MagnitudeToDb(double aMagnitude)
Convert a linear magnitude to decibels.
Definition sim_types.h:82
@ ST_SP
Definition sim_types.h:42
@ ST_TRAN
Definition sim_types.h:41
@ ST_NOISE
Definition sim_types.h:36
@ ST_AC
Definition sim_types.h:33
@ ST_DC
Definition sim_types.h:34
@ ST_FFT
Definition sim_types.h:43
< Smith chart placement plus pan/zoom, maps a gamma point to a screen pixel and back.
Definition smith_math.h:32
wxRealPoint pan
Definition smith_math.h:36
double zoom
Definition smith_math.h:35
wxRealPoint ToGamma(const wxPoint &aPt) const
Definition smith_math.h:53
wxPoint center
Definition smith_math.h:33
wxPoint ToScreen(double aRe, double aIm) const
Definition smith_math.h:39
double radius
Definition smith_math.h:34
wxRect plotRect
Definition smith_math.h:37
VECTOR2I center
int radius
VECTOR2I end
int delta
#define M_PI