Multiple Low Level Graphics Charts

Thanks to @fxshrat I've now worked out how to create a bar chart based upon multiple series using the low level graphics library functions within my own custom 'Report Charts' file which renders as required in back test reports. My approach is based upon...

https://www.amibroker.com/kb/2007/10/11/low-level-gfx-example-yearlymonthly-profit-chart/

My problem now is that when the back test is over multiple years there is just too much data for the one chart, so my question is, is it possible within the one custom 'Report Charts' file to generate multiple charts, one for each year? I've tried stepping through the years and offsetting the y axis values but so far I only get the first year's chart?

After reading this...

Use Status("pxwidth") and Status("pxheight") to find pixel dimensions of drawing surface

at https://www.amibroker.com/guide/h_lowlevelgfx.html

I'm guessing the available drawing surface is a fixed size that cannot be resized in height, even though for a backtest report it would just result in a longer page?

You assumption is incorrect. I have already told you that everything is possible.
http://www.fastswf.com/-cObV_c

pxwidth and pxheight are just values returning pixel numbers telling you where chart pane ends. Nothing more nothing less. Things you draw within that chart pane area can be of any size and any repetition and at any location. Think of pxwidth and pxheight like fences of your garden that you want to design in front of your house.

2 Likes

Thank you for your reply @fxshrat.

That link does indeed illustrate anything can be drawn, but instead of everything being drawn within the 'sheet' height can more graphics be drawn resulting in a vertical scroll bar? - Not that that is what I am after as I'm trying to draw multiple charts on the generated back test report from within the single file.

The height returned by...

Status( "pxheight" )

is 480, and if I move to and draw lines at coordinates such as ( 25, 505 ), ( 125, 505 ), ( 25, 745 ), ( 125, 745 ) etc. the lines are either not drawn or are beyond the 'garden fence' and therefore not displayed.

You should calculate your drawings by means of existing "fences". Simply divide the drawing ranges by number of graphs you want to draw and then iterate. On the other hand if you want to manually draw a line "till the end of our galaxy" of course AmiBroker will capitulate at pxheight/pxwidth since it is located on small spot on earth. (As aside if you draw a line it is defined by equation y = a*x+b. Think about it.)

As for vertical scroll bar to adjust number of graphs you may use Param function. A scroll bar that moves drawings and shows invisible graphs from beneath would be doable too. But why the hassle? You might also create "pages".

1 Like

Within my 'Report Charts' folder, I've just created a new file named...

  1. Test Boxes.afl

the code in the file draws 7 boxes in the back test report using the following code...

GfxSetOverlayMode( 2 );

function DrawBox( index, color )
{
    x1 = 20;
    x2 = 100;
    y1 = 100 * index;
    y2 = 80 + (100 * index);
    
    GfxFillSolidRect( x1, y1, x2 , y2, color ); 
    
    text = "" + ( index + 1 );
 
    GfxTextOut( text, x1 + ( ( x2 - x1 ) / 2 ), y1 + ( ( y2 - y1 ) / 2 ) );        
}    

function DrawBoxes()
{
    DrawBox( 0, colorRed );
    DrawBox( 1, colorOrange );
    DrawBox( 2, colorYellow );
    DrawBox( 3, colorGreen );
    DrawBox( 4, colorBlue );
    DrawBox( 5, colorIndigo );
    DrawBox( 6, colorViolet );
}

DrawBoxes();

but when I run a backtest and view the report, only the first 5 rectangles are drawn...

Custom%20Graphics%20In%20Backtest%20Report

I can quite easily scale the boxes to fit within the 'fences' but if I try to do it with the charts I wish to display, they are just too small.

1 Like

In your formula you should create a relation between pxheight/pxwidth and your number of sub graphs. That is not the case in your function. They are fixed sized (independent of pxheight and pxwidth).

Secondly if you need more height and width of your report charts then you can increase sizes by going to Backtest settings - Report tab - Chart dimensions.

If you still need more space then just make two separate AFLs.

2 Likes

Thank you for your reply.

But I do not want to scale the 'boxes' in this case.

Secondly if you need more height and width of your report charts then you can increase sizes by going to Backtest settings - Report tab - Chart dimensions.

The problem with this is it affects all charts. I would like to change these values for just this one item.

If you still need more space then just make two separate AFLs.

The problem is that the number of charts that will be drawn is dynamic.

The problem is you can't have your cake and eat it too.

You said you want to make sub charts per year or something.

Then make it so that each global custom report chart represents a year range.

If there are too less years that fit into just the first one then the rest of the charts below simply keep empty.

What's the big deal?

Or you have to make subcharts smaller and/or increase dimensions of all global report charts if you want all in one.

Those are the three possiblities.

Report charts are fixed. You can't scroll them in backtest report when backtest is finished. You can scroll in chart panes.

2 Likes

I was hoping there would be some way of changing the chart dimensions on a per report element basis.

I've just tried to split the graphs over multiple files but VarSet and VarGet do not seem to be working across files even though the help says they are global?

@SippDealer,

No, chart dimensions can't be changed on per report element basis, AFAICS.


Varset and Varget are global within same AFL. Static Variables are global within AB.

You would just need to get the end year of analysis range via Status("LastBarInRange") and number of years (your set date range). Again the fact that date range is dynamic is not an issue at all. You know beforehand how many years you want to analyze. And beforehand you know the number of mini graphs you want to have placed in each AFL (n-rows and m-columns. So it is a chart matrix you iterate through to draw each sub-graph).

Now.... you have a master AFL and several slave AFLs being connected to the master one via static variables. From the master AFL you subtract the first number of sub-charts (see above n * m) -> each one plotting extracted year<- to get start year moving forward from. From the slaves ones you get n/m via static vars and there you subtract k * n * m (k being number of slave chart) to get start year.

If date range is small one and fits into one single one of your custom charts AFLs then you have two possibilities... either keep the other ones (resulting in empty charts) in the final report or (since you know the date range and as such you know the number of analyzed years beforehand - so it is elementary school math we are talking about here not requiring any computer but just small brain power. Well, or just subtract year of Status("FirstBarInRange") from year of Status("LastBarInRange") to get number of years) you temporarily move those charts (resulting in emptiness) into an archive folder within Report charts folder. AFLs being placed within such archive subfolder of Reports charts folder are then excluded from report charts output of analysis report but they are kept within reach. So once you will have a larger date range again it is dead simple to just move required number of additional slave AFLs back to main Report Charts folder from within Charts window of AmiBroker so that they get included in final report again.

Now below is short animation (I only added an additional pane to visualize that it is indeed two separate sub-graphs drawing AFLs) showing that it is quite simple to do and only requires some 30 lines of code to draw multiple mini charts and connect AFLs only divided by one (the master) sending number of rows and columns and x/y-borders to its slave charts. One AFL controls the other ones.

Dynamic year range is not an issue. Use your imagination or rather let it loose instead of caging it.

test


Of course instead of from left to right you may alternatively go from top to bottom iterating through the years (subcharts).

718


And here is proof that master and slave(s) do work fine during Backtest Report generation too.

010

5 Likes

Wow, thank you very much for taking the time to write such a detailed reply @fxshrat, it's most appreciated.

I shall give it a go with static variables.:grinning:

first of all apologies for reviving an old thread,I didn't want to create a new thread, but the previews solution by @fxshrat in this thread is what i would like to achieve. so if you don't mind @fxshrat i would like ask few question as how to achieve multiple complete sub charts on a single pane.

referring to this topic / thread:
https://forum.amibroker.com/t/low-level-graphics-vs-plot-function/5962
Plot() function is always prefered when plotting graphs.

I would like to achieve what is referred by this url
Multiple Complete SubChart FlashAnimation by @fxshrat
where there are multiple complete sub charts , complete as in having:

  1. x axis
  2. x axis lables , time
  3. y axis
  4. y axis labels, price levels
  5. x grids , y grids
  6. title

here are pictures of objects
Full Pane:
Capture

Single Subchart:
singleWindow

what i got so far is the very basic pane divisions what i need is
guidance on how to create a single complete subchart
was the subcharts created using a modifiedPlot() function?

i reference above:

procedure PlotSubCharts(rows,columns,symbols)
{

	pxheight = Status("pxheight");
	pxwidth = Status("pxwidth");
	
	xnum = columns;  //columns
	ynum = rows; //rows
	
	boxHeight = pxHeight / ynum;
	boxWidth = pxwidth / xnum;
	
	for( i = 0 ; i < ynum ; i++){
		for(j=0 ; j< xnum ; j++){
			//draw collumn first
			x1 = j*boxWidth;
			y1 = i*boxHeight;
			x2 = x1 + boxWidth - 5;
			y2 = y1 + boxHeight - 5;
			
			symIdx = i*xnum + j;
			text =  StrExtract( symbols, symIdx );
			GfxFillSolidRect( x1, y1, x2 , y2, colorGrey40 );
			GfxTextOut(text , x1 + ( ( x2 - x1 ) / 2 ), y1 + ( ( y2 - y1 ) / 2 ) );  
		}
	}

}

symbols = "sym1,sym2,sym3,sym4,sym5,sym6,sym7,sym8";
Row = Param("Rows",1,1,10);
Col = Param("Column",1,1,10);
PlotSubCharts(Row,Col,symbols);

thanks in advance.

1 Like

In most cases it is true, but if you want to display multiple custom charts in one chart pane - similarily to the screenshots above, you need to use Gfx functions, not Plot().

You can find examples of using Plot() function in several panes (up to 16) in one chart window and an example AFL code displaying multiple graphs in one pane in this thread:

1 Like

@Milosz , wow thank you very much digging into it now. appreciate it very much.

1 Like

@Milosz, just a quick question , just the top of your head, is the multiple
subcharting possilbe/compatilbe with my previous post
Anchoring Chart on the left side (its currently locked , unfrotunalely since i wanted to follow up on the post)

I.e , multiple complete subcharts with "anchoring on the left side" also with zooming .

thanks again.

@fourier when using Gfx functions (instead of Plot) you create the graph from scratch, so everything is possible. In AmiBroker you can control every single pixel of a chart window/pane. For this reason - only your imagination (and of course your skills) set the limit ...

1 Like

You do not need OLE to anchor Gfx plot.
To anchor partial array e.g. anchoring intraday data to last day you just need

bi = BarIndex();
dn = Datenum();
start_of_day = dn != Ref(dn,-1);
first_bar_of_day = LastValue(ValueWhen( start_of_day, bi));
last_bar_of_day = LastValue( bi );

It is a lot of work for sure. For example the Gfx datetime axis handles all time frames (so actually getting those axes right on all timeframes is the most work and getting axes text auto-adjusting on zoom level). Potting price is simple. And the program does not use param slider for chart scrolling&zoom but it's attached to AmiBroker's horizontal UI scroll bar and its zoom in/out buttons. So no, is not the basic code by J. Hutchinson. It's just another plot program. But also that one of the video is outdated as it is 5 years old and much has changed since then.

Anyway basically all being required to be said is you need to convert bar&price to pixel. And since you iterate through symbol list you need SetForeign(), ... etc.. "That's all".

2 Likes

Alright @Milosz , well and true I will dig into the code example from your reference

ah okay, so each elements are drawn separately using gfx , thank you for correcting

my assumption , since the subcharts looks excelent , just like a miniaturized version of a
regular chart. i thought it was a modified Plot()

is it safe to assume with the current version of AmiBroker since 5 years ago the tasks you mentioned above are / is made easier with built in function?

thanks again

quick question/ request to @fxshrat refering back to the topic/thread.
Anchoring Chart on the left side ( referring here since thread is locked)

how would i go about having
multiple chart windows to be anchored per the code written by @fxshrat before in
the above topic/thread.

I am thinking iterating through the visible chart windows ?
but I have no idea how to do that. if you be so kind as to guide me
thank you

original code:

dt = DateTime();
bi = BarIndex();
dn = DateNum();

SetBarsRequired( 86400 / Max(1, Interval()) );

procedure ZoomToBarIndex( fbi, lbi ) {
    /// posted to discussion thread here   
    /// @link https://forum.amibroker.com/t/anchoring-chart-on-the-left-side/7988/7
    gid = StrFormat( "%g_%s", GetChartID(), Name() );
    static_fbi = Nz( StaticVarGet( "ZOOM_FIRST_BI_" + gid ) );
    static_lbi = Nz( StaticVarGet( "ZOOM_LAST_BI_" + gid ) );

    if ( static_fbi != fbi || static_lbi != lbi ) {
        StaticVarSet( "ZOOM_FIRST_BI_" + gid, fbi );
        StaticVarSet( "ZOOM_LAST_BI_" + gid, lbi );
        
        tmfrm = Max(1, Interval());

        fdt = LastValue( ValueWhen( bi == fbi, DateTimeAdd( dt, 0, tmfrm ) ) );
        fdt_str = DateTimeToStr( fdt, 3 );
        
        ldt = LastValue( ValueWhen( bi == lbi, DateTimeAdd( dt, 1, tmfrm ) ) );
        ldt_str = DateTimeToStr( ldt, 3 );                

        AB = CreateObject( "Broker.Application" );
        AW = AB.ActiveWindow;
        AW.ZoomToRange( fdt_str, ldt_str );      
    }
}

newday = dn != Ref( dn, -1 );
fbi = LastValue( ValueWhen( newday, bi ) ); 
lbi = LastValue( bi );

if ( ParamToggle( "Chart Anchoring", "OFF|ON", 1 ) )
	ZoomToBarIndex( fbi, lbi );

SetChartOptions( 0, chartShowArrows | chartShowDates );
Plot( C, "Price", colorDefault, styleBar );
  • modified code
//cnahor
// original code by fxshrat amibroker forum
/// posted to discussion thread here   
/// @link https://forum.amibroker.com/t/anchoring-chart-on-the-left-side/7988/7

tInterval = Interval();

dt = DateTime();
bi = BarIndex();
dn = DateNum();

if( tInterval < 86401){
	SetBarsRequired( 86400 / Max(1, tInterval) ); // seconds in a day / interval  gives the number of bars required
}

procedure ZoomToBarIndex( fbi, lbi , blankBars ) {

    gid = StrFormat( "%g_%s", GetChartID(), Name() );
    static_fbi = Nz( StaticVarGet( "ZOOM_FIRST_BI_" + gid ) ); // check if the static var exists else null
    static_lbi = Nz( StaticVarGet( "ZOOM_LAST_BI_" + gid ) );  // check if the static var exists else null


	//refresh / rezoom if the fbi is moved before
	//start of day and each time new data is updated
    if ( static_fbi != fbi || static_lbi != lbi ) {     // check if the staticvars values which are bar index 
 
        StaticVarSet( "ZOOM_FIRST_BI_" + gid, fbi );  // assign the staticvar to anchor bar
        StaticVarSet( "ZOOM_LAST_BI_" + gid, lbi );   // asssgin the var to the current last barindex
        
        tmfrm = Max(1, Interval());

        fdt = LastValue( ValueWhen( bi == fbi, DateTimeAdd( dt, 0, tmfrm ) ) );
        fdt_str = DateTimeToStr( fdt, 3 );
        
        ldt = LastValue( ValueWhen( bi == lbi, DateTimeAdd( dt, blankBars , tmfrm ) ) );
        ldt_str = DateTimeToStr( ldt, 3 );                


		// create ole object and zoom on the active
		// active window objet. user guide "AmiBroker's OLE Automation Object Model"
        AB = CreateObject( "Broker.Application" );
        AW = AB.ActiveWindow;
        AW.ZoomToRange( fdt_str, ldt_str );      
    }
}

swZoomOn = ParamToggle( "Chart Anchoring", "OFF|ON", 0 );
blankBars = 5;
newday = dn != Ref( dn, -1 );
fbi = LastValue( ValueWhen( newday, bi ) ); 
lbi = LastValue( bi );


if( tInterval < 86401){
	if ( swZoomOn ){
		ZoomToBarIndex( fbi, lbi , blankBars );
	}
}

-- only the top left , active window is anchoring.
multiChartAnchored