# Drawing Multiple Lines using GetAsyncKeyState (or any better method)

Hello All,

Due to some personal reasons, I went out of touch from AFL coding for several months and have forgotten some techniques - pardon me for that! Trying to get back on my feet as fast as possible.

Recently struggling to find a better logic in order to solve this problem and thus seek your kind help as usual. A friend has gifted a price action book authored by Al Brooks where he uses a tool to measure price movement.

Last year Milosz shared a method to draw stuffs using GetAsyncKeyState. Wondering whether it would be possible to draw multiple lines by using digits 1, 2 or 3 and left-click keeping the previous line(s) intact.

Below is my failed attempt where things are not stable and pressing the next digit with a click removes the previous line(s) too.

``````_SECTION_BEGIN( "Drawing Multiple Measure Moves" );
NumMM = Param( "Max no. of MMs", 3, 1, 5, 1 );

GfxSetZOrder( 5 );
GfxSetCoordsMode( 0 );
bi = BarIndex();

MouseButtonPressed = GetCursorMouseButtons();
x = GetCursorXPosition( 1 );
y = GetCursorYPosition( 1 );
Price = GetCursorYPosition( 0 );

GfxSelectFont( "Times New Roman", 16, 700, True );

for( i = 49; i <= 48 + NumMM; i++ )
{
DigitPressed = GetAsyncKeyState( i ) < 0 || GetAsyncKeyState( i + 48 ) < 0;

if( DigitPressed )
{
StaticVarSet( "DigitPressed" + Name(), 0, False );

if( MouseButtonPressed & 8 )
{
StaticVarSet( "X1" + Name(), x, False );
StaticVarSet( "Y1" + Name(), y, False );
StaticVarSet( "PrY1" + Name(), Price, False );
}

if( MouseButtonPressed & 9 )
{
StaticVarSet( "X2" + Name(), x, False );
StaticVarSet( "Y2" + Name(), y, False );
StaticVarSet( "PrY2" + Name(), Price, False );
GfxTextOut( "Digit " + i + " pressed", 10, 10 );
}

if( MouseButtonPressed & 4 )
{
StaticVarSet( "X1" + Name(), 0, False );
StaticVarSet( "Y1" + Name(), 0, False );
StaticVarSet( "X2" + Name(), 0, False );
StaticVarSet( "Y2" + Name(), 0, False );
}

RequestMouseMoveRefresh();
}
}

pX1 = StaticVarGet( "X1" + Name() );
pY1 = StaticVarGet( "Y1" + Name() );
pX2 = StaticVarGet( "X2" + Name() );
pY2 = StaticVarGet( "Y2" + Name() );

GfxSelectPen( colorLightOrange, 2 );
GfxMoveTo( pX1, pY1 );
GfxLineTo( pX2, pY2 );

Plot( C, "", colorWhite, styleCandle );
Title = "";
_SECTION_END();
``````

Another solution that comes to mind is by using TrendLines as shared here - no offence to Panos (you are always helpful) but I intend to have something sleek and kind-of on the go instead of changing properties everytime I draw a TrendLine.

Thank you

hi

are you sure if this line is correct ?
not sure what you are trying here with "`i + 48` "

``````DigitPressed = GetAsyncKeyState( i ) < 0 || GetAsyncKeyState( i + 48 ) < 0;
``````

did you check a similar link to plot horizontal lines using GetAsyncKeyState

1 Like

Hmm... Well I used i + 48 to capture numpad digits too...

hello again

i found an idea that you may like to draw the lines like this video. Can you tell us about how you imagine the final job?

1. how many lines will be for every group?
2. Are you planing the second line "leg2" to appear in the chart automatically and to be exact like the mother line "Leg1". So with your mouse to drag it -move it in a different position in the chart ?
3. in your code above you measure pixels is this what you need. or you need to measure any ratio or bars or % is behind the line?

many questions rise behind this << how i like it >>

Lets cover little bit your main question here.
what i am thinking you need is :

``````Attention for new users this is NOT a code for copy- paste

//   instead of this line that look ok
//   StaticVarSet( "X0" + Name(), x, False );

// your AFL code need to have
ClickCounter = to increase the mouse click
RangeIndex  = is to make a group of
x = GetCursorXPosition( 0 ); //  X-coordinate in DateTime format. y - value gives PRICE
StaticVarSet( "X0" +RangeIndex + ClickCounter, x,1 );

// and after you store your  Mouse Click you need to call

x0 = LastValue( ValueWhen( x == DateTime(), bi ) );

// or a limited RangeIndex  loop

for( i = 1; i < nIndex=5 ; i++ )
x0 = Lookup(bi,StaticVarGet( "X0" + i + "1" ));	// return the bar index of the specific datetime

``````

maybe i am thinking too much complicated things you need. but maybe not.. tell us

2 Likes

Hi David
@Lennon This is a code from another Project so i didn't spent lot of time.
NOTE: if your Amibroker Version is less that 6.28 then maybe you have problem with GuiGetCheck() function but is easy to adapt it with GuiGetEvent()
For now this code is working ok. But it is with guibutton
if you reply to the forum more about your plan maybe we can add more things in your code

``````
/** @link https://forum.amibroker.com/t/drawing-multiple-lines-using-getasynckeystate-or-any-better-method/12007/5
By Panos 26-03-2019
how to use the mouse clicks:
first press the MM guibutton then click once to start the line and one click to end of the line
By pressing the Ctlr on keyboard you can delete a single line

to understand how the staticVars are working i wrote like a debugger, please Open interpretation window to read
*/
_SECTION_BEGIN( "Drawing Multiple Measure Moves" );
Title = " Drawing Multiple Lines version 1 Template ";
Version(6.28);
// #include <AFLRevisionInclude3.afl>

Plot( C, "Price", colorDefault, stylebar );
bi = BarIndex();
symbol = Name();
tframe = Interval();
chartID = symbol + tframe + GetChartID();
b = GetCursorMouseButtons();

// Delete specific range index, press Ctrl and then DoubleClick ON the Circle
if( GetAsyncKeyState( 17 ) < 0 ) { RequestMouseMoveRefresh(); GfxCircle( GetCursorXPosition(1), GetCursorYPosition(1), 5 );  }
function RemoveOneRangeIndex( ButtonID, CheckID , SVx, SVy )
{
global y0, x0;
if( GetAsyncKeyState( 17 ) < 0 )
if( GuiGetCheck( ButtonID ) == 0  AND GuiGetCheck( CheckID ) == 1 ) // Allowed to Delete Only if range button is OFF
{
y= GetCursorYPosition(0);
x= GetCursorXPosition(0);
RequestMouseMoveRefresh();
GfxSelectSolidBrush( colorred ); GfxSelectPen( colorYellow );
GfxCircle( x0, y0, 0.5 );   // place a Circle - Dot in the chart
pnt1 = y0 + ( y0 * 0.001 );  pnt2 = y0 - ( y0 * 0.001 ); // +- Yaxis

if( y < pnt1 AND Y >pnt2 AND x0 == Lookup(bi,x) )
{
GfxSelectFont( "Arial", 12, 500 );
GfxTextOut( "Remove", X0+5, y0 );  //point to select to Remove

if( b & 9 )
{
StaticVarRemove( SVx );
StaticVarRemove( SVy );
}
}
}
}

/// initialize  the staticVars
prefix = "OnlyClicksMM" + Name();    // This version includes prefix for ALL statickvars
nIndex = 6;  		//nIndex ( is the Num of the loop )

ClickCounter = Nz(StaticVarGet("ClickCounter"+chartID),1);  // a Click Counter start from ONE
RangeIndex = Nz( StaticVarGet( "RangeIndex" ), 1);		// a RangeIndex Counter start from ONE
// reset the RangeIndex back to 1, if used range are more than nIndex
if (RangeIndex > nIndex-1 )  StaticVarSet( "RangeIndex", 1 );

SVMMx = prefix+"MMx"; // Multiple Measure Moves
SVMMy = prefix+"MMy";
riMM = Nz( StaticVarGet( "riMM" ), 1);  if (riMM > nIndex-1 )  StaticVarSet( "riMM", 1 ); // Reset the RangeIndex Counter back to 1, if used range are more than nIndex = 5
MMx= Nz(StaticVarGet( SVMMx+riMM+ClickCounter ),0); //
MMy= Nz(StaticVarGet( SVMMy+riMM+ClickCounter ),0); // 2 Clicks
MMyc = Nz(StaticVarGet( SVMMy+"C"+riMM+ClickCounter ),0); // this line is a spare you mey need it in the future

id = GuiGetEvent( 0, 0 );  event = GuiGetEvent( 0, 1 );

GuiToggle( "MM", 11,5, 50, 30, 18, 1|64|128); 	if( id == 11 AND event == 64 ) GfxTextOut( "Measure Line", 100, 50);
//GuiToggle( "Spaire", 12, 5, 75, 30, 18, 1);
GuiCheckBox( "show  MM", 21, 10, 35, 13, 13, 1 ); 	// SELECT MM
GuiSetColors( 10, 21, 0 , colorWhite,  colorViolet, 0, colorBlack,  colorAqua);

//  IF switcher is ON do this amound of clicks
totalClick = 0;
if(  GuiGetCheck(11) )  totalClick = 2; // just 2 clicks allowed

///////////////////////////////////////////////////////////////
///// Part 2  Mouse Clicking Control /////////////////////////
////////////  if Left button is Pressed   /////////////////
if( b & 8 ) // flag = 8 is set when window just received mouse click
{
x = GetCursorXPosition( 0 ); //  X-coordinate in DateTime format. y - value gives PRICE
y = GetCursorYPosition( 0 );
barNo = Lookup( bi, x, 0);

if( b & 1 AND barNo >0 )
{
///////////////////////////////////////////////////////////////////
////////////////// Measure Moves - just 2 clicks allowed  /////////
if( GuiGetCheck(11) AND ClickCounter <= totalClick )
{
StaticVarSet( SVMMx+riMM+ClickCounter, x,1 );  //  DateTime of the bar ...
StaticVarSet( SVMMy+riMM+ClickCounter, y,1 );
if( ClickCounter == 1 ) StaticVarSet( SVMMy+"C" +riMM + ClickCounter, SelectedValue(C),1 );   // close of the bar
// _TRACE("#, Message ClickCounter= " + ClickCounter + " SVMMyc"+ StaticVarGet( SVMMy+"C" +riMM + ClickCounter) );

StaticVarset( "ClickCounter" + chartID, ClickCounter + 1 );  // Add +1 to ClickCounter
Say( "" + ClickCounter ); Say( "Range " + riMM,0 );
GuiSetCheck( 21,1 ); // swich ON to show the FTE

if( ClickCounter == 2 ) // Once the last mouse Click has occured
{
StaticVarset( "ClickCounter" + chartID, 1 ); // restart the ClickCounter
StaticVarSet( "riMM", riMM+1 );  // add plus +1 to RangeIndex Counter
GuisetCheck( 11, 0 ) ; // Reset the button toggle
}
}
}
}

////////////  TextOut the Next Click or the range /////////////////////////////
if(   ( GuiGetCheck(11) ) AND ClickCounter<= totalClick)
{
GfxsetBkMode( 1 ); // 1 set transparent mode
GfxSetTextColor( colorYellow );
GfxSelectFont("Times New Roman", 14, 700, True );
GfxTextOut( " Place " + ClickCounter + " of ( " + totalClick + " Clicks ) ", 20 ,status( "pxchartbottom" )-20);
RequestMouseMoveRefresh();
}

/////////////////////////////////////////////////////////////
// Part 3	 DO your calculation here  for Drawing Multiple Measure Lines
/////////////////////////////////////////////////////////////
GfxSetOverlayMode( 1 );
GfxSetCoordsMode( 1 ); // bar/price mode (instead of pixel)

for( i = 1; i < nIndex ; i++ )
{
x0 = Lookup( bi, StaticVarGet( SVMMx + i + "1" ) );
x1 = Lookup( bi, StaticVarGet( SVMMx + i + "2" ) );
y0 = StaticVarGet( SVMMy + i + "1" );
y1 = StaticVarGet( SVMMy + i + "2" );

if( GuiGetCheck( 21 ) ) // show/ hide the MM lines
{
if( x0 != 0 AND x1 != 0 ) // draw on chart if range is selected
{
GfxSelectPen( colorGreen, 2 );
GfxSelectSolidBrush( colorYellow );
GfxMoveTo( X0, y0 );
GfxLineTo( x1, y1 );

}
}

// Delete specific range index, press Ctrl and then Click ON the Circle
RemoveOneRangeIndex( 11, 21 , SVMMy + i + "*", SVMMx + i + "*" );
}
// END
//////////////////////////////////////////////////////////////////////

//////// for debugger  = interpretation window - show me what  static vars i have until now
"<b> \nSVMMx11= " +StaticVarGet( SVMMx+"11" ) + ",\t SVMMy11= " +StaticVarGet( SVMMy+"11" ) +
"\nSVMMx12= " +StaticVarGet( SVMMx+"12" ) + ",\t SVMMy12= " +StaticVarGet( SVMMy+"12" ) ;
"\nSVMMx21= " +StaticVarGet( SVMMx+"21" ) + ",\t SVMMy21= " +StaticVarGet( SVMMy+"21" ) +
"\nSVMMx22= " +StaticVarGet( SVMMx+"22" ) + ",\t SVMMy22= " +StaticVarGet( SVMMy+"22" ) ;
"\nSVMMx31= " +StaticVarGet( SVMMx+"31" ) + ",\t SVMMy31= " +StaticVarGet( SVMMy+"31" ) +
"\nSVMMx32= " +StaticVarGet( SVMMx+"32" ) + ",\t SVMMy32= " +StaticVarGet( SVMMy+"32" ) ;

_SECTION_END();
``````
1 Like

You solved it! I take this as a homework and another wonderful learning to form the final desired result. May be by tomorrow I will understand the logic - sorry I am slow! So far I was unable to go beyond this.

What I intended is to use any digit (for e.g. 1 or 2 or 3) and keep painting straight lines while the left mouse button is down from bar-to-bar (live data). That line must remain intact after the digit key and left mouse button is released until the same digit is pressed along with the mouse middle button to remove that particular line.

Let's say:
Key pressed 1 (Ascii 49) & Left Mouse Button down (GetCursorMouseButtons 8): Records the x1, y1 of the first line.
Key released 1 + Left Mouse Button released: Records the x2, y2 of the first line. And stores all the co-ordinates of the first line. Draws a straight line to form the first line.

Then,
Key pressed 2 (Ascii 50) & Left Mouse Button down (GetCursorMouseButtons 8): Records the x1, y1 of the second line. No change to the first line (it remains intact).
Key released 2 + Left Mouse Button released: Records the x2, y2 of the second line. And stores the co-ordinates of the second line, upon connecting forms the second line.

Now if,
Key pressed 1 (Ascii 49) & Middle Mouse Button pressed (GetCursorMouseButtons 4): Shall remove the first line.

Likewise if,
Key pressed 2 (Ascii 50) & Middle Mouse Button pressed (GetCursorMouseButtons 4): Shall remove the second line.

Panos thanks again for looking into it. I have just read the book once, so, you can imagine my head is boiling with all sorts of ideas most of which, I will be able to counter on my own (thanks to the AFL lessons taught by you and others from this beautiful community of helpful programmers).

P.S. Just updated to 6.30

2 Likes

Hi

I have 2 more comments for the code in post 5
If you like to draw more than 5 lines go to line 55 and increase the number `nIndex = 6;`

Also at Line 97 and 98 persistent are set to TRUE, and I donâ€™t think you need that

``````// we can turn persistent to false
StaticVarSet( SVMMx+riMM+ClickCounter, x,0 );
StaticVarSet( SVMMy+riMM+ClickCounter, y,0 );
``````
``````// by pressing the Middle Îśouse Button we remove ALL lines
if( GetCursorMouseButtons() == 12 )
{
StaticVarRemove( Prefix + "*" );
}
``````
1 Like

Panos done with the homework. Thank you for sharing the code and your comments. A lesson well learnt - steal refreshes and get anything done using counters and StaticVars. However, in my case just to reduce the number of clicks, instead of using GUIs, I have used numeric digits and mouse clicks to store and/or remove multiple lines.

The logic of part 3 of your code did not come to my thick skull initially. Below is what I have done (when you get time please check for inefficiencies):

``````/** @link https://forum.amibroker.com/t/drawing-multiple-lines-using-getasynckeystate-or-any-better-method/12007/5
By Panos 26-03-2019

How to use:

1. 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).

2. Max number of lines parameter set to 3 as default.

3. To remove a specific line click the mouse middle button while holding the specific digit for which the line is required to be removed.

4. To remove all lines at one go press and hold the digit "0" while clicking the mouse middle button.
*/

_SECTION_BEGIN( "Drawing Multiple Measure Moves" ); // Using GetAsyncKeyState()
nMM = Param( "Max no. of MM lines", 3, 1, 9, 1 );

chartId = Name() + Interval() + GetChartID();

GfxSetZOrder( 0 );
GfxSetCoordsMode( 1 );

bi = BarIndex();
MouseBtn = GetCursorMouseButtons();
LeftJustClkd = MouseBtn & 8;
LeftClkDownRlsd = MouseBtn & 9;
MiddleClkd = MouseBtn & 4;

x = GetCursorXPosition( 0 );
y = GetCursorYPosition( 0 );

if( GetAsyncKeyState( 48 ) < 0 OR GetAsyncKeyState( 96 ) < 0 AND MiddleClkd )
{
StaticVarRemove( "x1*" );
StaticVarRemove( "y1*" );
StaticVarRemove( "x2*" );
StaticVarRemove( "y2*" );
}

for( i = 49; i <= 48 + nMM; i++ )
{
riMM = i - 48;
DigitPressed = GetAsyncKeyState( i ) < 0 || GetAsyncKeyState( i + 48 ) < 0;
if( DigitPressed )
{
if( LeftJustClkd )
{
StaticVarSet( "x1" + chartId + riMM, x, 0 );
StaticVarSet( "y1" + chartId + riMM, y, 0 );
}
if( LeftClkDownRlsd )
{
StaticVarSet( "x2" + chartId + riMM, x, 0 );
StaticVarSet( "y2" + chartId + riMM, y, 0 );
}
if( MiddleClkd )
{
StaticVarRemove( "x1" + chartId + riMM );
StaticVarRemove( "y1" + chartId + riMM );
StaticVarRemove( "x2" + chartId + riMM );
StaticVarRemove( "y2" + chartId + riMM );
}
RequestMouseMoveRefresh();
}
}

GfxSetBkMode( 1 );
GfxSelectFont( "Courier New", 10, 700 );
GfxSetTextColor( colorLightOrange );
GfxSelectPen( colorLightOrange, 2 );

for( i = 1; i <= nMM; i++ )
{
riMMx1 = Lookup( bi, StaticVarGet( "x1" + chartId + i ) );
riMMy1 = StaticVarGet( "y1" + chartId + i );
riMMx2 = Lookup( bi, StaticVarGet( "x2" + chartId + i ) );
riMMy2 = StaticVarGet( "y2" + chartId + i );
if( riMMx1 != 0 AND riMMx2 != 0 )
{
GfxMoveTo( riMMx1, riMMy1 );
GfxLineTo( riMMx2, riMMy2 );

GfxTextOut( "MM " + i, riMMx1, riMMy1 );

/*
For additional drawings here
*/
}
}

Plot( C, "", colorDefault, styleBar, Null, Null, 0, 0, 2 );
_SECTION_END();
``````

2 Likes

Good job David - good job
You are not good only in coding Alf but also you very good on drawing also. LOL

According to the measurement systemâ€¦. Now the next step (just for fun or practice) is to clone the mother M1 line to M1b and then to drag the M1b line to a different position on the chart. Lets say, Like two parallel lines with the same distance and same angle

1 Like

He he...
The very little that I know about AFL; without the forum's help my skill is a big Null. I shall ever remain grateful to you and others.

Are you referring to this?

``````/** @link https://forum.amibroker.com/t/drawing-multiple-lines-using-getasynckeystate-or-any-better-method/12007/5
By Panos 26-03-2019

How to use:

1. 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).

2. Max number of lines parameter set to 3 as default.

3. To remove a specific line click the mouse middle button while holding the specific digit for which the line is required to be removed.

4. To remove all lines at one go press and hold the digit "0" while clicking the mouse middle button.
*/

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( "Drawing Multiple Measure Moves" ); // Using GetAsyncKeyState()
nMM = Param( "Max no. of MM lines", 3, 1, 9, 1 );

SetChartOptions( 0, chartLogarithmic | chartShowDates );

bi = BarIndex();
MouseBtn = GetCursorMouseButtons();
LeftJustClkd = MouseBtn & 8;
LeftClkDownRlsd = MouseBtn & 9;
MiddleClkd = MouseBtn & 4;
chartId = Name() + Interval() + GetChartID();

x = GetCursorXPosition( 0 );
y = GetCursorYPosition( 0 );

if( GetAsyncKeyState( 48 ) < 0 OR GetAsyncKeyState( 96 ) < 0 AND MiddleClkd )
{
StaticVarRemove( "x1*" );
StaticVarRemove( "y1*" );
StaticVarRemove( "x2*" );
StaticVarRemove( "y2*" );
}

for( i = 49; i <= 48 + nMM; i++ )
{
riMM = i - 48;
DigitPressed = GetAsyncKeyState( i ) < 0 || GetAsyncKeyState( i + 48 ) < 0;
if( DigitPressed )
{
// This elegant technique shared by @Milosz
if( LeftJustClkd )
{
StaticVarSet( "x1" + chartId + riMM, x, 0 );
StaticVarSet( "y1" + chartId + riMM, y, 0 );
}
if( LeftClkDownRlsd )
{
StaticVarSet( "x2" + chartId + riMM, x, 0 );
StaticVarSet( "y2" + chartId + riMM, y, 0 );
}
if( MiddleClkd )
{
StaticVarRemove( "x1" + chartId + riMM );
StaticVarRemove( "y1" + chartId + riMM );
StaticVarRemove( "x2" + chartId + riMM );
StaticVarRemove( "y2" + chartId + riMM );
}
RequestMouseMoveRefresh();
}
}

GfxSetZOrder( 5 );
GfxSetBkMode( 1 );

// This bit is @PanoS's contribution
for( i = 1; i <= nMM; i++ )
{
riMMx1 = Lookup( bi, StaticVarGet( "x1" + chartId + i ) );
riMMy1 = StaticVarGet( "y1" + chartId + i );
riMMx2 = Lookup( bi, StaticVarGet( "x2" + chartId + i ) );
riMMy2 = StaticVarGet( "y2" + chartId + i );
if( riMMx1 != 0 AND riMMx2 != 0 )
{
GfxSetCoordsMode( 1 );
riMMy3 = 2 * riMMy2 - riMMy1;

GfxSelectPen( colorLightOrange, 1, 1 );
GfxMoveTo( riMMx1, riMMy1 );
GfxLineTo( riMMx2, riMMy2 );

GfxSelectPen( colorLightOrange, 1 );
GfxMoveTo( riMMx1, riMMy1 );
GfxLineTo( riMMx2, riMMy1 );

GfxMoveTo( riMMx1 - 2, riMMy2 );
GfxLineTo( riMMx2, riMMy2 );

GfxMoveTo( riMMx1, riMMy3 );
GfxLineTo( riMMx2, riMMy3 );

GfxMoveTo( riMMx1, riMMy1 );
GfxLineTo( riMMx1, riMMy3 );

PXriMMx1 = BarToPxX( riMMx1 );
PXriMMy1 = ValueToPxY( riMMy1, 1 );

PXMidriMMx1 = BarToPxX( riMMx1 - 2 );

PXriMMx2 = BarToPxX( riMMx2 );
PXriMMy2 = ValueToPxY( riMMy2, 1 );

PXriMMy3 = ValueToPxY( riMMy3, 1 );

PxMMidX = ( PXriMMx1 + PXriMMx2 ) / 2;
PxMMidY = ( PXriMMy1 + PXriMMy2 ) / 2;

GfxSetCoordsMode( 0 );
GfxSetTextColor( colorLightOrange );
GfxSelectFont( "Courier New", 8.5, 700 );
text = "Start: " + NumToStr( riMMy1, 1.2 );
GfxTextOut( text, PXriMMx1 - GfxGetTextWidth( text ), PXriMMy1 );

text = "Mid: " + NumToStr( riMMy2, 1.2 );
GfxTextOut( text, PXMidriMMx1 - GfxGetTextWidth( text ), PXriMMy2 );

text = "Target: " + NumToStr( riMMy3, 1.2 );
GfxTextOut( text, PXriMMx1 - GfxGetTextWidth( text ), PXriMMy3 );

GfxSetTextColor( colorWhite );
GfxSelectFont( "Courier New", 11, 800 );
GfxTextOut( NumToStr( i, 1.0 ), PxMMidX, PxMMidY );
}
}

Plot( C, "", colorDefault, styleBar, Null, Null, 0, 0, 2 );
_SECTION_END();
``````

Price Action Trading is an ocean; I am yet to learn the techniques and the nuances. Please share your ideas to improve this tool.

1 Like

Hi

Tip of the day:
in AB formula editor if you right click on any function and then choose "function reference" on the drop menu, the User Guide will open on this specific function

So, we can use multi times the GfxSetCoordsMode() function and you DO NOT really need to convert it by the custom
`function ValueToPxY( Value, logscale )` GfxSetCoordsMode function is doing for us

GfxSetCoordsMode() function allows to switch co-ordinate system for low-level gfx functions:
// mode = 2 - X coordinate is pixel, Y coordinate is price (new in 6.20)

``````

// use this mode 2 instead
GfxSetCoordsMode( 2 );
GfxSetTextColor( colorLightOrange );
GfxSelectFont( "Courier New", 8.5, 700 );
text = "Start: " + NumToStr( riMMy1, 1.2 );
GfxTextOut( text, riMMx1 - GfxGetTextWidth( text ), riMMy1 );
GfxTextOut( text, riMMx1 - GfxGetTextWidth( text ), riMMy1 );
``````
3 Likes

Ah! Thanks PanoS.... That's the new update.. eh! Good one! PanoS I was gone for sometime, things changes so fast...

1 Like

@Lennon opps sorry.... an update here `GfxSetCoordsMode( 1 );`

and `GfxGetTextWidth()` you donâ€™t really need it .
If you like to move the text left or right then use the barindex.
And in your case to shift the text to the right ( +5 bars) use `riMMx1 + 5`

``````		GfxSetCoordsMode(1 );
GfxSetTextColor( colorLightOrange );
GfxSelectFont( "Courier New", 8.5, 700 );
text = NumToStr( riMMy1, 1.2 );
GfxTextOut( "Start: " +text , riMMx1 , riMMy1 );
``````

or

``````StartTextFromMiddle =(riMMx2-riMMx1)/2+riMMx1;
GfxTextOut( "Start: " +text , StartTextFromMiddle , riMMy1 );
``````
1 Like

Or just press F1 key. When cursor is on function it will display reference.

3 Likes

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.

1. The Check Box 'D' is to measure Price at "Double the move".
2. The Check Box 'S' is to measure Price to "equate the previous Swing from a pullback".

How to use:

1. 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).
2. Max number of lines parameter set to 3 as default - increase if more lines are needed.
3. To remove a specific line click the mouse middle button while holding that specific digit for which the line is required to be removed.
4. To remove all lines at one go, press and hold the digit "0" while clicking the mouse middle button.
5. 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.
6. 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.

3 Likes

Hello
David. Sorry. I did send, one more message and maybe you miss it.
Sorry for that i did make a correction to my previous post.
and i said
opps sorry.... an update here `GfxSetCoordsMode( 1 );`

So below version is WITHOUT functions BarToPxX and ValueToPxY we use onlyGfxSetCoordsMode( 1 )

NOTE: In your post 17 few Functions are missing. Now i am thinking (if you keep the link from the foto) o you may delete post 17 and repost it later?

by the way i am thinking Tomasz will Not agree to use guibuttons in such places.

"It's nice to look for beauty and it's nice to create it"
Also what a nice thing is, to see that each one of us is the style of writing the code. I love it.

``````/**  @link https://forum.amibroker.com/t/drawing-multiple-lines-using-getasynckeystate-or-any-better-method/12007/18
By Panos 26-03-2019
Modify By Lennon 27-03-2019 Using GetAsyncKeyState()

this Version 30-03-2019 is WITHOUT functions BarToPxX and ValueToPxY we use ONLY GfxSetCoordsMode( 1 );
mode = 1 - bar / price mode where X is expressed in bar index and Y is expressed in price.
This new mode allows way easier overlays on top of existing charts without need to do conversion between bars/price pixels and without any extra refresh normally required in old versions when Y scale changed.

*/

_SECTION_BEGIN( "Drawing Multiple Measure Moves" ); // Using GetAsyncKeyState()
nMM = Param( "Max no. of MM lines", 3, 1, 9, 1 );

SetChartOptions( 0, chartLogarithmic | chartShowDates );
//SetChartOptions( 0,  chartShowDates );

bi = BarIndex();
MouseBtn = GetCursorMouseButtons();
LeftJustClkd = MouseBtn & 8;
LeftClkDownRlsd = MouseBtn & 9;
MiddleClkd = MouseBtn & 4;
chartId = Name() + Interval();// + GetChartID();

x = GetCursorXPosition( 0 );
y = GetCursorYPosition( 0 );

if( GetAsyncKeyState( 48 ) < 0 OR GetAsyncKeyState( 96 ) < 0 AND MiddleClkd )
{
StaticVarRemove( "x1*" );
StaticVarRemove( "y1*" );
StaticVarRemove( "x2*" );
StaticVarRemove( "y2*" );
}

for( i = 49; i <= 48 + nMM; i++ )
{
riMM = i - 48;
DigitPressed = GetAsyncKeyState( i ) < 0 || GetAsyncKeyState( i + 48 ) < 0;
if( DigitPressed )
{
// This elegant technique shared by @Milosz
if( LeftJustClkd )
{
StaticVarSet( "x1" + chartId + riMM, x, 0 );
StaticVarSet( "y1" + chartId + riMM, y, 0 );
}
if( LeftClkDownRlsd )
{
StaticVarSet( "x2" + chartId + riMM, x, 0 );
StaticVarSet( "y2" + chartId + riMM, y, 0 );
}
if( MiddleClkd )
{
StaticVarRemove( "x1" + chartId + riMM );
StaticVarRemove( "y1" + chartId + riMM );
StaticVarRemove( "x2" + chartId + riMM );
StaticVarRemove( "y2" + chartId + riMM );
}
RequestMouseMoveRefresh();
}
}

GfxSetZOrder( 5 );
GfxSetBkMode( 1 );
GfxSetCoordsMode( 1 ); // mode = 1 - bar / price mode where X is expressed in bar index and Y is expressed in price.
// This bit is @PanoS's contribution
for( i = 1; i <= nMM; i++ )
{
x1 = Lookup( bi, StaticVarGet( "x1" + chartId + i ) );
y1 = StaticVarGet( "y1" + chartId + i );
x2 = Lookup( bi, StaticVarGet( "x2" + chartId + i ) );
y2 = StaticVarGet( "y2" + chartId + i );
if( x1 != 0 AND x2 != 0 )
{

y3 = 2 * y2 - y1;
MidX = (x1+x2)/2;		MidY = (y1+y2)/2;

GfxSelectPen( colorLightOrange, 1, 1 );
GfxMoveTo( x1, y1 );		GfxLineTo( x2, y2 ); 	// Diagonal line

GfxSelectPen( colorLightOrange, 1 );
GfxMoveTo( x1, y1 );		GfxLineTo( x2, y1 );	// Start Horizontal

GfxMoveTo( x1 - 2, y2 );	GfxLineTo( x2, y2 );	// midle Horizontal

GfxMoveTo( x1, y3 );		GfxLineTo( x2, y3 );	// target

GfxMoveTo( x1, y1 );		GfxLineTo( x1, y3 );	// Vertical

GfxSetTextColor( colorLightOrange );
GfxSelectFont( "Courier New", 8.5, 700 );
text = NumToStr( y1, 1.2 );
GfxTextOut( "Start: " +text , x1 , y1 );

text = "Mid: " + NumToStr( y2, 1.2 );
GfxTextOut( text, x1, y2 );

text = "Target: " + NumToStr( y3, 1.2 );
GfxTextOut( text, x1 , y3 );

GfxSetTextColor( colorWhite );
GfxSelectFont( "Courier New", 11, 800 );
GfxTextOut( NumToStr( i, 1.0 ), MidX, MidY );

}
}

Plot( C, "", colorDefault, styleBar, Null, Null, 0, 0, 2 );
_SECTION_END();

``````
5 Likes

There is NOT function missing, if you use AB 6.30+ version

1 Like

Okay...I get it now! ha ha

Your Post # 18 code is doable using `GfxSetCoordsMode( 1 )`, however when it comes to the other measurement type:

I was unable to code the below (excerpt from Post # 17) using `GfxSetCoordsMode( 1 )`. Since we are using anisotropic chart, it became necessary to convert to pixels in order to calculate the slope of Leg 1 and the Y-intercept of its parallel line Leg 2.

X3 = PxPullBkX
Y3 = PxPullBkY
X3, Y3 Initially set to a default on the basis of X2, Y2
Then on the basis of the co-ordinates of chosen price.

// 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 ); // Line To X4, Y4

Hence a thought came, in any case when I am required to convert the co-ordinates to Pixels then why not use the Pixelated co-ordinates for all drawings using `GfxSetCoordsMode( 0 )` instead of switching back and forth between `GfxSetCoordsModes`.

To understand GUIs better would like to know the reason, too?

Meanwhile will think on how to achieve the same result, instead of using GUIs. The best one that comes to mind is this article.

Thank you for reading!

Enjoy the weekend....

1 Like

I am at work now... Maybe you right about the slope I have to think about this... but to move or adjust a line we can use matrix also. There is an example in member area from Elliot Wave code . But since you are not going to use alot of Gui you can stay on this style of code.

Mayybe another user can recommend a better or easier how to move the lines ..

2 Likes

hello
I downloaded the code
how can i do