2020-11-22 21:38:52 +08:00
# include " tracesmithchart.h "
2021-10-21 19:00:34 +08:00
2021-06-19 21:33:43 +08:00
# include "Marker/marker.h"
2020-10-23 03:12:33 +08:00
# include "preferences.h"
2020-11-06 20:05:09 +08:00
# include "ui_smithchartdialog.h"
2020-11-23 05:24:49 +08:00
# include "unit.h"
2021-04-13 02:15:38 +08:00
# include "QFileDialog"
2020-08-31 04:03:41 +08:00
2021-10-21 19:00:34 +08:00
# include <QPainter>
# include <array>
# include <math.h>
# include <QDebug>
2020-08-31 04:03:41 +08:00
using namespace std ;
TraceSmithChart : : TraceSmithChart ( TraceModel & model , QWidget * parent )
2020-11-06 20:05:09 +08:00
: TracePlot ( model , parent )
2020-08-31 04:03:41 +08:00
{
2020-11-06 20:05:09 +08:00
limitToSpan = true ;
2022-01-02 03:04:43 +08:00
edgeReflection = 1.0 ;
2020-11-06 20:05:09 +08:00
initializeTraceInfo ( ) ;
}
2020-12-05 06:49:52 +08:00
nlohmann : : json TraceSmithChart : : toJSON ( )
{
nlohmann : : json j ;
j [ " limit_to_span " ] = limitToSpan ;
2022-01-02 03:04:43 +08:00
j [ " limit_to_edge " ] = limitToEdge ;
j [ " edge_reflection " ] = edgeReflection ;
2020-12-05 06:49:52 +08:00
nlohmann : : json jtraces ;
for ( auto t : traces ) {
if ( t . second ) {
jtraces . push_back ( t . first - > toHash ( ) ) ;
}
}
j [ " traces " ] = jtraces ;
return j ;
}
void TraceSmithChart : : fromJSON ( nlohmann : : json j )
{
limitToSpan = j . value ( " limit_to_span " , true ) ;
2022-01-02 03:04:43 +08:00
limitToEdge = j . value ( " limit_to_edge " , false ) ;
edgeReflection = j . value ( " edge_reflection " , 1.0 ) ;
2020-12-05 06:49:52 +08:00
for ( unsigned int hash : j [ " traces " ] ) {
// attempt to find the traces with this hash
bool found = false ;
for ( auto t : model . getTraces ( ) ) {
if ( t - > toHash ( ) = = hash ) {
enableTrace ( t , true ) ;
found = true ;
break ;
}
}
if ( ! found ) {
qWarning ( ) < < " Unable to find trace with hash " < < hash ;
}
}
}
2022-01-02 03:04:43 +08:00
void TraceSmithChart : : wheelEvent ( QWheelEvent * event )
{
// most mousewheel have 15 degree increments, the reported delta is in 1/8th degree -> 120
auto increment = event - > angleDelta ( ) . y ( ) / 120.0 ;
// round toward bigger step in case of special higher resolution mousewheel
int steps = increment > 0 ? ceil ( increment ) : floor ( increment ) ;
constexpr double zoomfactor = 1.1 ;
auto zoom = pow ( zoomfactor , steps ) ;
edgeReflection / = zoom ;
triggerReplot ( ) ;
}
2020-11-06 20:05:09 +08:00
void TraceSmithChart : : axisSetupDialog ( )
{
auto dialog = new QDialog ( ) ;
auto ui = new Ui : : SmithChartDialog ( ) ;
ui - > setupUi ( dialog ) ;
if ( limitToSpan ) {
2022-01-02 03:04:43 +08:00
ui - > displayModeFreq - > setCurrentIndex ( 1 ) ;
} else {
ui - > displayModeFreq - > setCurrentIndex ( 0 ) ;
}
if ( limitToEdge ) {
ui - > displayModeImp - > setCurrentIndex ( 1 ) ;
2020-11-06 20:05:09 +08:00
} else {
2022-01-02 03:04:43 +08:00
ui - > displayModeImp - > setCurrentIndex ( 0 ) ;
2020-11-06 20:05:09 +08:00
}
2022-01-02 03:04:43 +08:00
ui - > zoomReflection - > setPrecision ( 3 ) ;
ui - > zoomFactor - > setPrecision ( 3 ) ;
ui - > zoomReflection - > setValue ( edgeReflection ) ;
ui - > zoomFactor - > setValue ( 1.0 / edgeReflection ) ;
2020-11-06 20:05:09 +08:00
connect ( ui - > buttonBox , & QDialogButtonBox : : accepted , [ = ] ( ) {
2022-01-02 03:04:43 +08:00
limitToSpan = ui - > displayModeFreq - > currentIndex ( ) = = 1 ;
limitToEdge = ui - > displayModeImp - > currentIndex ( ) = = 1 ;
2020-11-06 20:05:09 +08:00
triggerReplot ( ) ;
} ) ;
2022-01-02 03:04:43 +08:00
connect ( ui - > zoomFactor , & SIUnitEdit : : valueChanged , [ = ] ( ) {
edgeReflection = 1.0 / ui - > zoomFactor - > value ( ) ;
ui - > zoomReflection - > setValueQuiet ( edgeReflection ) ;
} ) ;
connect ( ui - > zoomReflection , & SIUnitEdit : : valueChanged , [ = ] ( ) {
edgeReflection = ui - > zoomReflection - > value ( ) ;
ui - > zoomFactor - > setValueQuiet ( 1.0 / edgeReflection ) ;
} ) ;
2020-11-06 20:05:09 +08:00
dialog - > show ( ) ;
2020-08-31 04:03:41 +08:00
}
2022-01-02 03:04:43 +08:00
QPoint TraceSmithChart : : dataToPixel ( std : : complex < double > d )
{
return transform . map ( QPoint ( d . real ( ) * smithCoordMax * ( 1.0 / edgeReflection ) , - d . imag ( ) * smithCoordMax * ( 1.0 / edgeReflection ) ) ) ;
}
2020-11-22 21:38:52 +08:00
QPoint TraceSmithChart : : dataToPixel ( Trace : : Data d )
2020-08-31 04:03:41 +08:00
{
2020-11-25 23:47:29 +08:00
if ( d . x < sweep_fmin | | d . x > sweep_fmax ) {
2020-11-22 21:38:52 +08:00
return QPoint ( ) ;
2020-08-31 04:03:41 +08:00
}
2022-01-02 03:04:43 +08:00
return dataToPixel ( d . y ) ;
2020-08-31 04:03:41 +08:00
}
2020-11-23 05:24:49 +08:00
std : : complex < double > TraceSmithChart : : pixelToData ( QPoint p )
{
auto data = transform . inverted ( ) . map ( QPointF ( p ) ) ;
2022-01-02 03:04:43 +08:00
return complex < double > ( data . x ( ) / smithCoordMax * edgeReflection , - data . y ( ) / smithCoordMax * edgeReflection ) ;
2020-11-23 05:24:49 +08:00
}
2021-06-19 21:33:43 +08:00
QPoint TraceSmithChart : : markerToPixel ( Marker * m )
2020-11-23 04:25:41 +08:00
{
QPoint ret = QPoint ( ) ;
2020-11-29 05:34:40 +08:00
// if(!m->isTimeDomain()) {
2020-11-23 04:25:41 +08:00
if ( m - > getPosition ( ) > = sweep_fmin & & m - > getPosition ( ) < = sweep_fmax ) {
auto d = m - > getData ( ) ;
2022-01-02 03:04:43 +08:00
ret = dataToPixel ( d ) ;
2020-11-23 04:25:41 +08:00
}
2020-11-29 05:34:40 +08:00
// }
2020-11-23 04:25:41 +08:00
return ret ;
}
2021-05-15 02:34:23 +08:00
double TraceSmithChart : : nearestTracePoint ( Trace * t , QPoint pixel , double * distance )
2020-11-23 04:25:41 +08:00
{
double closestDistance = numeric_limits < double > : : max ( ) ;
2021-10-16 21:42:31 +08:00
double closestXpos = 0 ;
auto samples = t - > size ( ) ;
for ( unsigned int i = 0 ; i < samples ; i + + ) {
2020-11-23 04:25:41 +08:00
auto data = t - > sample ( i ) ;
auto plotPoint = dataToPixel ( data ) ;
if ( plotPoint . isNull ( ) ) {
// destination point outside of currently displayed range
continue ;
}
auto diff = plotPoint - pixel ;
unsigned int distance = diff . x ( ) * diff . x ( ) + diff . y ( ) * diff . y ( ) ;
if ( distance < closestDistance ) {
closestDistance = distance ;
2021-10-16 21:42:31 +08:00
closestXpos = t - > sample ( i ) . x ;
2020-11-23 04:25:41 +08:00
}
}
2021-05-15 02:34:23 +08:00
if ( distance ) {
* distance = closestDistance ;
}
2021-10-16 21:42:31 +08:00
return closestXpos ;
2020-11-23 04:25:41 +08:00
}
2021-05-14 22:18:43 +08:00
bool TraceSmithChart : : xCoordinateVisible ( double x )
{
if ( limitToSpan ) {
if ( x > = sweep_fmin & & x < = sweep_fmax ) {
return true ;
} else {
return false ;
}
} else {
// complete traces visible
return true ;
}
}
2020-11-22 07:41:42 +08:00
void TraceSmithChart : : draw ( QPainter & p ) {
auto pref = Preferences : : getInstance ( ) ;
2020-08-31 04:03:41 +08:00
2022-01-02 03:04:43 +08:00
// translate coordinate system so that the smith chart sits in the origin and has a size of 1
2020-11-22 07:41:42 +08:00
auto w = p . window ( ) ;
2020-11-22 21:38:52 +08:00
p . save ( ) ;
2020-11-22 07:41:42 +08:00
p . translate ( w . width ( ) / 2 , w . height ( ) / 2 ) ;
auto scale = qMin ( w . height ( ) , w . width ( ) ) / ( 2.0 * smithCoordMax ) ;
p . scale ( scale , scale ) ;
2020-08-31 04:03:41 +08:00
2020-11-22 07:41:42 +08:00
transform = p . transform ( ) ;
2022-01-02 03:04:43 +08:00
p . restore ( ) ;
auto drawArc = [ & ] ( Arc a ) {
a . constrainToCircle ( QPointF ( 0 , 0 ) , edgeReflection ) ;
auto topleft = dataToPixel ( complex < double > ( a . center . x ( ) - a . radius , a . center . y ( ) - a . radius ) ) ;
auto bottomright = dataToPixel ( complex < double > ( a . center . x ( ) + a . radius , a . center . y ( ) + a . radius ) ) ;
a . startAngle * = 5760 / ( 2 * M_PI ) ;
a . spanAngle * = 5760 / ( 2 * M_PI ) ;
p . drawArc ( QRect ( topleft , bottomright ) , a . startAngle , a . spanAngle ) ;
} ;
2020-10-23 03:12:33 +08:00
2020-08-31 04:03:41 +08:00
// Outer circle
2021-07-10 19:12:30 +08:00
auto pen = QPen ( pref . Graphs . Color . axis ) ;
2020-11-22 07:41:42 +08:00
pen . setCosmetic ( true ) ;
p . setPen ( pen ) ;
2022-01-02 03:04:43 +08:00
drawArc ( Arc ( QPointF ( 0 , 0 ) , edgeReflection , 0 , 2 * M_PI ) ) ;
2020-08-31 04:03:41 +08:00
constexpr int Circles = 6 ;
2021-10-18 06:37:40 +08:00
pen = QPen ( pref . Graphs . Color . Ticks . divisions , 0.5 , Qt : : DashLine ) ;
2020-11-22 07:41:42 +08:00
pen . setCosmetic ( true ) ;
p . setPen ( pen ) ;
2022-01-02 03:04:43 +08:00
for ( int i = 1 ; i < Circles * 2 ; i + + ) {
auto radius = ( double ) i / Circles ;
drawArc ( Arc ( QPointF ( 1.0 - radius , 0.0 ) , radius , 0 , 2 * M_PI ) ) ;
drawArc ( Arc ( QPointF ( 1.0 + radius , 0.0 ) , radius , 0 , 2 * M_PI ) ) ;
2020-08-31 04:03:41 +08:00
}
2022-01-02 03:04:43 +08:00
p . drawLine ( dataToPixel ( complex < double > ( edgeReflection , 0 ) ) , dataToPixel ( complex < double > ( - edgeReflection , 0 ) ) ) ;
2020-08-31 04:03:41 +08:00
constexpr std : : array < double , 5 > impedanceLines = { 10 , 25 , 50 , 100 , 250 } ;
for ( auto z : impedanceLines ) {
z / = ReferenceImpedance ;
2022-01-02 03:04:43 +08:00
auto radius = 1.0 / z ;
drawArc ( Arc ( QPointF ( 1.0 , radius ) , radius , 0 , 2 * M_PI ) ) ;
drawArc ( Arc ( QPointF ( 1.0 , - radius ) , radius , 0 , 2 * M_PI ) ) ;
2020-08-31 04:03:41 +08:00
}
for ( auto t : traces ) {
if ( ! t . second ) {
// trace not enabled in plot
continue ;
}
auto trace = t . first ;
if ( ! trace - > isVisible ( ) ) {
// trace marked invisible
continue ;
}
2021-12-06 01:26:32 +08:00
pen = QPen ( trace - > color ( ) , pref . Graphs . lineWidth ) ;
2020-11-22 07:41:42 +08:00
pen . setCosmetic ( true ) ;
p . setPen ( pen ) ;
2020-08-31 04:03:41 +08:00
int nPoints = trace - > size ( ) ;
for ( int i = 1 ; i < nPoints ; i + + ) {
2020-11-06 20:05:09 +08:00
auto last = trace - > sample ( i - 1 ) ;
auto now = trace - > sample ( i ) ;
2021-07-10 04:26:44 +08:00
if ( limitToSpan & & ( trace - > getDataType ( ) = = Trace : : DataType : : Frequency ) & & ( last . x < sweep_fmin | | now . x > sweep_fmax ) ) {
2020-11-06 20:05:09 +08:00
continue ;
}
2020-11-25 23:47:29 +08:00
if ( isnan ( now . y . real ( ) ) ) {
2020-08-31 04:03:41 +08:00
break ;
}
2022-01-02 03:04:43 +08:00
if ( limitToEdge & & ( abs ( last . y ) > edgeReflection | | abs ( now . y ) > edgeReflection ) ) {
// outside of visible area
continue ;
}
2020-08-31 04:03:41 +08:00
// scale to size of smith diagram
2022-01-02 03:04:43 +08:00
auto p1 = dataToPixel ( last ) ;
auto p2 = dataToPixel ( now ) ;
2020-08-31 04:03:41 +08:00
// draw line
2022-01-02 03:04:43 +08:00
p . drawLine ( p1 , p2 ) ;
2020-08-31 04:03:41 +08:00
}
2020-09-12 05:07:15 +08:00
if ( trace - > size ( ) > 0 ) {
// only draw markers if the trace has at least one point
auto markers = t . first - > getMarkers ( ) ;
for ( auto m : markers ) {
2020-11-29 05:34:40 +08:00
// if (m->isTimeDomain()) {
// continue;
// }
2020-11-23 04:25:41 +08:00
if ( limitToSpan & & ( m - > getPosition ( ) < sweep_fmin | | m - > getPosition ( ) > sweep_fmax ) ) {
2020-11-06 20:05:09 +08:00
continue ;
}
2020-12-05 19:59:23 +08:00
if ( m - > getPosition ( ) < trace - > minX ( ) | | m - > getPosition ( ) > trace - > maxX ( ) ) {
// marker not in trace range
continue ;
}
2020-09-12 05:07:15 +08:00
auto coords = m - > getData ( ) ;
2022-01-02 03:04:43 +08:00
if ( limitToEdge & & abs ( coords ) > edgeReflection ) {
// outside of visible area
continue ;
}
auto point = dataToPixel ( coords ) ;
2020-09-12 05:07:15 +08:00
auto symbol = m - > getSymbol ( ) ;
2022-01-02 03:04:43 +08:00
p . drawPixmap ( point . x ( ) - symbol . width ( ) / 2 , point . y ( ) - symbol . height ( ) , symbol ) ;
2020-09-12 05:07:15 +08:00
}
2020-08-31 04:03:41 +08:00
}
}
2020-11-22 21:38:52 +08:00
if ( dropPending ) {
2022-01-02 03:04:43 +08:00
// TODO adjust coords due to shifted restore
2020-11-22 21:38:52 +08:00
p . setOpacity ( 0.5 ) ;
p . setBrush ( Qt : : white ) ;
p . setPen ( Qt : : white ) ;
p . drawEllipse ( - smithCoordMax , - smithCoordMax , 2 * smithCoordMax , 2 * smithCoordMax ) ;
auto font = p . font ( ) ;
font . setPixelSize ( 20 ) ;
p . setFont ( font ) ;
p . setOpacity ( 1.0 ) ;
p . setPen ( Qt : : white ) ;
auto text = " Drop here to add \n " + dropTrace - > name ( ) + " \n to Smith chart " ;
p . drawText ( p . window ( ) , Qt : : AlignCenter , text ) ;
} else {
}
}
void TraceSmithChart : : traceDropped ( Trace * t , QPoint position )
{
Q_UNUSED ( t )
Q_UNUSED ( position ) ;
if ( supported ( t ) ) {
enableTrace ( t , true ) ;
}
2020-08-31 04:03:41 +08:00
}
2020-11-23 05:24:49 +08:00
QString TraceSmithChart : : mouseText ( QPoint pos )
{
auto data = pixelToData ( pos ) ;
2022-01-02 03:04:43 +08:00
if ( abs ( data ) < = edgeReflection ) {
data = 50.0 * ( 1.0 + data ) / ( 1.0 - data ) ;
2020-11-23 05:24:49 +08:00
auto ret = Unit : : ToString ( data . real ( ) , " " , " " , 3 ) ;
if ( data . imag ( ) > = 0 ) {
ret + = " + " ;
}
ret + = Unit : : ToString ( data . imag ( ) , " j " , " " , 3 ) ;
return ret ;
} else {
return QString ( ) ;
}
}
2020-11-06 20:05:09 +08:00
void TraceSmithChart : : updateContextMenu ( )
{
contextmenu - > clear ( ) ;
auto setup = new QAction ( " Setup... " , contextmenu ) ;
connect ( setup , & QAction : : triggered , this , & TraceSmithChart : : axisSetupDialog ) ;
contextmenu - > addAction ( setup ) ;
2021-05-15 02:34:23 +08:00
2020-11-06 20:05:09 +08:00
contextmenu - > addSeparator ( ) ;
2021-04-13 02:15:38 +08:00
auto image = new QAction ( " Save image... " , contextmenu ) ;
contextmenu - > addAction ( image ) ;
connect ( image , & QAction : : triggered , [ = ] ( ) {
auto filename = QFileDialog : : getSaveFileName ( nullptr , " Save plot image " , " " , " PNG image files (*.png) " , nullptr , QFileDialog : : DontUseNativeDialog ) ;
if ( filename . isEmpty ( ) ) {
// aborted selection
return ;
}
if ( filename . endsWith ( " .png " ) ) {
filename . chop ( 4 ) ;
}
filename + = " .png " ;
grab ( ) . save ( filename ) ;
} ) ;
2021-05-15 02:34:23 +08:00
auto createMarker = contextmenu - > addAction ( " Add marker here " ) ;
bool activeTraces = false ;
for ( auto t : traces ) {
if ( t . second ) {
activeTraces = true ;
break ;
}
}
if ( ! activeTraces ) {
createMarker - > setEnabled ( false ) ;
}
connect ( createMarker , & QAction : : triggered , [ = ] ( ) {
createMarkerAtPosition ( contextmenuClickpoint ) ;
} ) ;
contextmenu - > addSection ( " Traces " ) ;
// Populate context menu
for ( auto t : traces ) {
if ( ! supported ( t . first ) ) {
continue ;
}
auto action = new QAction ( t . first - > name ( ) , contextmenu ) ;
action - > setCheckable ( true ) ;
if ( t . second ) {
action - > setChecked ( true ) ;
}
connect ( action , & QAction : : toggled , [ = ] ( bool active ) {
enableTrace ( t . first , active ) ;
} ) ;
contextmenu - > addAction ( action ) ;
}
2021-04-13 02:15:38 +08:00
contextmenu - > addSeparator ( ) ;
2020-11-06 20:05:09 +08:00
auto close = new QAction ( " Close " , contextmenu ) ;
contextmenu - > addAction ( close ) ;
connect ( close , & QAction : : triggered , [ = ] ( ) {
markedForDeletion = true ;
} ) ;
}
2020-08-31 04:03:41 +08:00
bool TraceSmithChart : : supported ( Trace * t )
2021-02-22 20:39:47 +08:00
{
return dropSupported ( t ) ;
}
bool TraceSmithChart : : dropSupported ( Trace * t )
2020-08-31 04:03:41 +08:00
{
2021-07-10 00:42:22 +08:00
if ( ! t - > isReflection ( ) ) {
return false ;
}
switch ( t - > outputType ( ) ) {
case Trace : : DataType : : Frequency :
case Trace : : DataType : : Power :
2020-08-31 04:03:41 +08:00
return true ;
2021-07-10 00:42:22 +08:00
default :
2020-08-31 04:03:41 +08:00
return false ;
}
}
2022-01-02 03:04:43 +08:00
TraceSmithChart : : Arc : : Arc ( QPointF center , double radius , double startAngle , double spanAngle )
: center ( center ) ,
radius ( radius ) ,
startAngle ( startAngle ) ,
spanAngle ( spanAngle )
{
}
void TraceSmithChart : : Arc : : constrainToCircle ( QPointF center , double radius )
{
// check if arc/circle intersect
auto centerDiff = this - > center - center ;
auto centerDistSquared = centerDiff . x ( ) * centerDiff . x ( ) + centerDiff . y ( ) * centerDiff . y ( ) ;
if ( centerDistSquared > = ( radius + this - > radius ) * ( radius + this - > radius ) ) {
// arc completely outside of constraining circle
spanAngle = 0.0 ;
return ;
} else if ( centerDistSquared < = ( radius - this - > radius ) * ( radius - this - > radius ) ) {
if ( radius > = this - > radius ) {
// arc completely in constraining circle, do nothing
return ;
} else {
// arc completely outside of circle
spanAngle = 0.0 ;
return ;
}
} else {
// there are intersections between the arc and the circle. Calculate points according to https://stackoverflow.com/questions/3349125/circle-circle-intersection-points
auto distance = sqrt ( centerDistSquared ) ;
auto a = ( this - > radius * this - > radius - radius * radius + distance * distance ) / ( 2 * distance ) ;
auto h = sqrt ( this - > radius * this - > radius - a * a ) ;
auto intersectMiddle = this - > center + a * ( center - this - > center ) / distance ;
auto rotatedCenterDiff = center - this - > center ;
swap ( rotatedCenterDiff . rx ( ) , rotatedCenterDiff . ry ( ) ) ;
rotatedCenterDiff . setY ( - rotatedCenterDiff . y ( ) ) ;
auto intersect1 = intersectMiddle + h * rotatedCenterDiff / distance ;
auto intersect2 = intersectMiddle - h * rotatedCenterDiff / distance ;
// got intersection points, convert into angles from arc center
auto wrapAngle = [ ] ( double angle ) - > double {
double ret = fmod ( angle , 2 * M_PI ) ;
if ( ret < 0 ) {
ret + = 2 * M_PI ;
}
return ret ;
} ;
auto angle1 = wrapAngle ( atan2 ( ( intersect1 - this - > center ) . y ( ) , ( intersect1 - this - > center ) . x ( ) ) ) ;
auto angle2 = wrapAngle ( atan2 ( ( intersect2 - this - > center ) . y ( ) , ( intersect2 - this - > center ) . x ( ) ) ) ;
auto angleDiff = wrapAngle ( angle2 - angle1 ) ;
if ( ( angleDiff > = M_PI ) ^ ( a > 0.0 ) ) {
// allowed angles go from intersect1 to intersect2
if ( startAngle < angle1 ) {
startAngle = angle1 ;
}
auto maxSpan = wrapAngle ( angle2 - startAngle ) ;
if ( spanAngle > maxSpan ) {
spanAngle = maxSpan ;
}
} else {
// allowed angles go from intersect2 to intersect1
if ( startAngle < angle2 ) {
startAngle = angle2 ;
}
auto maxSpan = wrapAngle ( angle1 - startAngle ) ;
if ( spanAngle > maxSpan ) {
spanAngle = maxSpan ;
}
}
}
}