I believe you were referring to this:

This is what I have re-written:
fvb = Status( "firstvisiblebar" );
lvb = Status( "lastvisiblebar" );
pxchartwidth = Status( "pxchartwidth" );
pxchartheight = Status( "pxchartheight" );
pxchartleft = Status( "pxchartleft" );
pxchartbottom = Status( "pxchartbottom" );
MaxY = Status( "axismaxy" );
MinY = Status( "axisminy" );
// Following functions BarToPxX and ValueToPxY are depicted from @fxshrat's elegant method of pixel conversion
// https://forum.amibroker.com/t/circle-through-three-points/1165/6
function BarToPxX( bar )
{
local bardiff, barnum, relbar, px;
// based on http://www.amibroker.com/kb/2009/03/30/how-to-convert-from-bar-value-to-pixel-co-ordinates/
barnum = lvb - fvb + 1;
bardiff = bar - fvb;
relbar = bardiff / barnum;
px = relbar * pxchartwidth + pxchartleft;
return Nz( px );
}
function ValueToPxY( Value, logscale )
{
local logMiny, logMinMax, pxy;
//based on http://www.amibroker.com/kb/2009/03/30/how-to-convert-from-bar-value-to-pixel-co-ordinates/
if( logscale )
{
logMiny = log( Miny );
logMinMax = log( Maxy ) - logMiny;
pxy = pxchartbottom - floor( 0.5 + ( log( Value ) - logMiny ) * pxchartheight / logMinMax );
}
else
{
pxy = pxchartbottom - floor( 0.5 + ( Value - Miny ) * pxchartheight / ( Maxy - Miny + 1e-9 ) );
}
return Nz( pxy );
}
_SECTION_BEGIN( "Al Brook's Measure Moves" ); // Using GetAsyncKeyState()
// https://forum.amibroker.com/t/drawing-multiple-lines-using-getasynckeystate-or-any-better-method/12007/17
nMM = Param( "Max no. of MM lines", 3, 1, 9, 1 );
LnColor = ParamColor( "Pick a color for the Lines", colorLightOrange );
SetChartOptions( 0, chartLogarithmic | chartShowDates );
bi = BarIndex();
MouseBtn = GetCursorMouseButtons();
LeftJustClkd = MouseBtn & 8;
LeftClkDownRlsd = MouseBtn & 9;
MiddleClkd = MouseBtn & 4;
chartId = Name() + Interval() + GetChartID();
BarX = GetCursorXPosition( 0 );
ValY = GetCursorYPosition( 0 );
PxCrsrX = GetCursorXPosition( 1 );
PxCrsrY = GetCursorYPosition( 1 );
if( ( GetAsyncKeyState( 48 ) || GetAsyncKeyState( 96 ) < 0 ) AND MiddleClkd )
{
StaticVarRemove( "x1" + chartId + "*" );
StaticVarRemove( "y1" + chartId + "*" );
StaticVarRemove( "x2" + chartId + "*" );
StaticVarRemove( "y2" + chartId + "*" );
StaticVarRemove( "PullBkX" + chartId + "*" );
StaticVarRemove( "PullBkY" + chartId + "*" );
}
for( i = 49; i <= 48 + nMM; i++ )
{
MMid = i - 48;
DigitPressed = GetAsyncKeyState( i ) || GetAsyncKeyState( i + 48 ) < 0;
if( DigitPressed )
{
// Shared by Milosz
if( LeftJustClkd )
{
StaticVarSet( "x1" + chartId + MMid, BarX, 0 );
StaticVarSet( "y1" + chartId + MMid, ValY, 0 );
}
if( LeftClkDownRlsd )
{
StaticVarSet( "x2" + chartId + MMid, BarX, 0 );
StaticVarSet( "y2" + chartId + MMid, ValY, 0 );
}
if( MiddleClkd )
{
StaticVarRemove( "x1" + chartId + MMid );
StaticVarRemove( "y1" + chartId + MMid );
StaticVarRemove( "x2" + chartId + MMid );
StaticVarRemove( "y2" + chartId + MMid );
StaticVarRemove( "PullBkX" + chartId + MMid );
StaticVarRemove( "PullBkY" + chartId + MMid );
}
RequestMouseMoveRefresh();
}
}
GfxSetOverlayMode( 1 ); // Well I was unable to handle Mode 2 (need to study). So, to get things done going by orthodox conversion functions.
GfxSetCoordsMode( 0 );
GfxSetBkMode( 1 );
// This bit is PanoS's contribution
for( i = 1; i <= nMM; i++ )
{
MMx1 = Lookup( bi, StaticVarGet( "x1" + chartId + i ) );
MMy1 = StaticVarGet( "y1" + chartId + i );
MMx2 = Lookup( bi, StaticVarGet( "x2" + chartId + i ) );
MMy2 = StaticVarGet( "y2" + chartId + i );
aPxMMx1 = BarToPxX( MMx1 - 2 );
PxMMx1 = BarToPxX( MMx1 );
PxMMy1 = ValueToPxY( MMy1, 1 );
PxMMx2 = BarToPxX( MMx2 );
PxMMy2 = ValueToPxY( MMy2, 1 );
MidMMx = ( PxMMx1 + PxMMx2 ) / 2;
MidMMy = ( PxMMy1 + PxMMy2 ) / 2;
OneFourthX = ( PxMMx1 + MidMMx ) / 2;
OneFourthY = ( PxMMy1 + MidMMy ) / 2;
ThreeFourthX = ( MidMMx + PxMMx2 ) / 2;
ThreeFourthY = ( MidMMy + PxMMy2 ) / 2;
GuiCheckBox( "", 10 + i, OneFourthX, OneFourthY, 9, 9, notifyClicked );
GuiCheckBox( "", 20 + i, ThreeFourthX, ThreeFourthY, 9, 9, notifyClicked );
GuiSetVisible( 10 + i, 0 );
GuiSetVisible( 20 + i, 0 );
MoveCheck = MMx2 >= MMx1;
if( MMx1 && MMx2 != 0 AND MoveCheck )
{
GfxSelectPen( LnColor, 1, 1 );
GfxMoveTo( PxMMx1, PxMMy1 );
GfxLineTo( PxMMx2, PxMMy2 );
GfxSetTextAlign( 6 | 24 );
GfxSetTextColor( colorWhite );
GfxSelectFont( "Courier New", 9, 800 );
GfxTextOut( NumToStr( i, 1.01 ), MidMMx, MidMMy );
GuiSetVisible( 10 + i, 1 );
GuiSetVisible( 20 + i, 1 );
GfxSetTextColor( LnColor );
GfxSelectFont( "Courier New", 10, 700 );
GfxTextOut( "D", OneFourthX, OneFourthY ); // Measure "D"ouble the move
GfxTextOut( "S", ThreeFourthX, ThreeFourthY ); // Measure the move to a parallel "S"wing
// Measure "D"ouble the move
if( GuiGetCheck( 10 + i ) )
{
PxMMy3 = 2 * PxMMy2 - PxMMy1;
GfxSetTextAlign( 2 );
if( PxMMy2 != PxMMy1 )
{
// Start Line
GfxSelectPen( LnColor, 1 );
GfxMoveTo( PxMMx1, PxMMy1 );
GfxLineTo( PxMMx2, PxMMy1 );
GfxTextOut( "Start:" + NumToStr( MMy1, 1.2 ), PxMMx1, PxMMy1 );
// Mid Line
GfxMoveTo( aPxMMx1, PxMMy2 );
GfxLineTo( PxMMx2, PxMMy2 );
GfxTextOut( NumToStr( MMy2, 1.2 ), aPxMMx1, PxMMy2 );
// Target Line
GfxMoveTo( PxMMx1, PxMMy3 );
GfxLineTo( PxMMx2, PxMMy3 );
GfxTextOut( "Target:" + NumToStr( 2 * MMy2 - MMy1, 1.2 ), PxMMx1, PxMMy3 );
// Joining Line
GfxMoveTo( PxMMx1, PxMMy1 );
GfxLineTo( PxMMx1, PxMMy3 );
}
else
{
GfxSetTextAlign( 0 );
GfxSetTextColor( colorRed );
GfxSelectFont( "Courier New", 10, 700 );
GfxTextOut( "The 'D'ouble the move checkbox is 'selected' and you have drawn a lateral line.", PxMMx1, PxMMy1 + 14 );
GfxTextOut( "For a move, price either has to go 'Up' or 'Down'.", PxMMx1, PxMMy1 + 28 );
GfxTextOut( "If price remained constant, how can you draw a pullback and what is there to measure?", PxMMx1, PxMMy1 + 42 );
GfxTextOut( "Un-select and try again !", PxMMx1, PxMMy1 + 56 );
}
}
// Measure the move to a parallel "S"wing
if( GuiGetCheck( 20 + i ) AND ( PxMMx2 - PxMMx1 ) != 0 )
{
PullBkX = IIf( IsNull( StaticVarGet( "PullBkX" + chartId + i ) ), MMx2 + 5, Lookup( bi, StaticVarGet( "PullBkX" + chartId + i ) ) );
PxPullBkX = BarToPxX( PullBkX );
if( PxMMy2 < PxMMy1 ) // Up-move
{
PullBkY = Nz( StaticVarGet( "PullBkY" + chartId + i ), MMy2 - SelectedValue( ATR( 1050 ) ) * 3 );
PxPullBkY = ValueToPxY( PullBkY, 1 );
}
else if( PxMMy2 > PxMMy1 ) // Down move
{
PullBkY = Nz( StaticVarGet( "PullBkY" + chartId + i ), MMy2 + SelectedValue( ATR( 1050 ) ) * 3 );
PxPullBkY = ValueToPxY( PullBkY, 1 );
}
else
{
GfxSetTextAlign( 0 );
GfxSetTextColor( colorRed );
GfxSelectFont( "Courier New", 10, 700 );
GfxTextOut( "The 'S'wing checkbox is 'selected' and you have drawn a lateral line.", PxMMx1, PxMMy1 + 84 );
GfxTextOut( "For a Swing it either has to move on the Upside or Downside.", PxMMx1, PxMMy1 + 98 );
GfxTextOut( "If price remained constant, how can you draw a pullback and what is there to measure?", PxMMx1, PxMMy1 + 112 );
GfxTextOut( "Un-select and try again !", PxMMx1, PxMMy1 + 126 );
}
if( PxMMy2 != PxMMy1 AND PxPullBkX >= PxMMx2 )
{
// Pullback Line
GfxSelectPen( LnColor, 1 );
GfxMoveTo( PxMMx2, PxMMy2 );
GfxLineTo( PxPullBkX, PxPullBkY );
PxdX = PxMMx2 - PxMMx1;
PxSlopeMM = ( PxMMy2 - PxMMy1 ) / PxdX;
// Expected Swing
GfxSelectPen( LnColor, 2 );
GfxMoveTo( PxPullBkX, PxPullBkY );
GfxLineTo( PxPullBkX + PxdX, PxSlopeMM * PxdX + PxPullBkY );
GuiCheckBox( "", 30 + i, PxPullBkX, PxPullBkY, 10, 10, notifyClicked );
if( GuiGetCheck( 30 + i ) AND GetAsyncKeyState( 17 ) < 0 AND LeftJustClkd )
{
StaticVarSet( "PullBkX" + ChartId + i, BarX, 0 );
StaticVarSet( "PullBkY" + ChartId + i, ValY, 0 );
}
}
}
if( GuiGetCheck( 20 + i ) AND ( PxMMx2 - PxMMx1 ) == 0 )
{
GfxSetTextAlign( 0 );
GfxSetTextColor( colorRed );
GfxSelectFont( "Courier New", 10, 700 );
GfxTextOut( "The 'S'wing checkbox is 'selected' and you have drawn a vertical line.", PxMMx1, PxMMy1 );
GfxTextOut( "To measure such move, use the other Checkbox ('D'ouble the move).", PxMMx1, PxMMy1 + 14 );
GfxTextOut( "Un-select and try again !", PxMMx1, PxMMy1 + 28 );
}
if( NOT GuiGetCheck( 20 + i ) )
{
StaticVarRemove( "PullBkX" + chartId + i );
StaticVarRemove( "PullBkY" + chartId + i );
StaticVarRemove( "PullBkCirColor" + chartId + i );
}
}
if( MMx1 && MMx2 != 0 AND NOT MoveCheck )
{
GfxSetTextAlign( 0 );
GfxSetTextColor( colorRed );
GfxSelectFont( "Courier New", 10, 700 );
GfxTextOut( "This is incorrect to draw like this. You are dragging backwards.", PxMMx2, MidMMy );
GfxTextOut( "For an Up Move : Draw from a Low to High towards the Right of the chart.", PxMMx2, MidMMy + 14 );
GfxTextOut( "For a Down Move: Draw from a High to Low towards the Right of the chart.", PxMMx2, MidMMy + 28 );
GfxTextOut( "To remove this irritating text. Hold the Digit '" + NumToStr( i, 1.0 ) + "' and", PxMMx2, MidMMy + 42 );
GfxTextOut( "click the Mouse middle button. Try again !", PxMMx2, MidMMy + 56 );
}
}
RequestMouseMoveRefresh();
Plot( C, "", colorDefault, styleBar, Null, Null, 0, 0, 2 );
_SECTION_END();
You will notice 2 Check Boxes on every line that gets drawn.
- The Check Box 'D' is to measure Price at "Double the move".
- The Check Box 'S' is to measure Price to "equate the previous Swing from a pullback".
How to use:
- Press and hold any digit from 1 to 9 (including numpad keys) while Mouse left click down and draw multiple lines (releasing the mouse left button will store the line).
- Max number of lines parameter set to 3 as default - increase if more lines are needed.
- To remove a specific line click the mouse middle button while holding that specific digit for which the line is required to be removed.
- To remove all lines at one go, press and hold the digit "0" while clicking the mouse middle button.
- While using multiple lines with 'S' checkbox checked, please ensure to uncheck the particular Pullback checkbox after picking the Pullback price of that line in order to any avoid clutter of lines.
- At any given point in time Digit '0' and Mouse middle button can be pressed together to reset the co-ordinates for a re-trial.
Do share some more ideas of measuring Swings (or Price moves)!
Thank you for reading! Hope you like it!
P.S. Tried to use GfxSetCoordsMode( 2 )
however failed to implement it successfully. Need to study further.