Increase Maximum Chart Panes Per Window

I’m use a 4K monitor, containing the same amount of pixels as four 1920 x 1080 monitors.
Is there a way to increase the maximum number of Amibroker chart panes per window?
I understand I can have multiple windows, stacked in a layout, and that I synchronize the time interval.

For my purposes, I want to increase the number of panes beyond the apparent limit of 15 panes.
How can I do this?

Just as a test, I added 30 charts to a single pane without any issue. What is the limit of 15 panes you are referring to?
In Preferences -> Charting at the bottom there is an option to increase the Number of Chart Sheets over 15 (requires a restart).

There is a limit of 16 panes per window in the CSplitterWnd class of Microsoft Foundation Classes library that AB uses for chart window, so you can’t change it.

To avoid any confusion, I want to point out that in my test I actually added 30 graphs to a single graphic sheet (I mistakenly used the term “pane”) and I was also able to change the number of these sheets to 20 (such, as said, after an application restart and I suppose there is a limit here too).

The panes (with a maximum of 16 in a window as stated above by TJ) are the horizontal ones that are used to separate the different component of a chart like the plots of the price, the volume, the various indicators/oscillators etc. I apologize for the inaccuracy.

@Tomasz I'm not actually in need of that many windows (I honestly think it is not a good idea in terms of focus) but hey, "to each his own". Is it possible using Gfx functionality. This is an example of such functionality originally listed on the old forum back in 2007 by a James Hutchison (though that link no longer has an active attachment).
image

@Tomasz And is it OK for me to post the code if anyone is interested? I think it will be hard to find on old forum.

1 Like

These horizontal rows within single chart window are called "chart panes" in AmiBroker:

image

The limit is 16 rows (panes) per SINGLE chart. But you can have unlimited number of CHARTS.

As you can see on typical HD screen (~1000px vertical pixels) they get pretty tiny and there is hardly any reason to get more. On 4K screen (2160px vertical pixels) they would be less squeezed but if you really want more you can open say FOUR charts (File->New->Chart) and each can have 16 panes and choose Window->Tile and you will get 4x16 panes in a grid like fashion without ANY special coding as shown below:

image

The number of charts is UNLIMITED, so you can have hundreds of charts each having upto 16 subpanes. And really there is no need for Gfx coding to achieve enormous number of panes displayed on your monitor.

Gfx-based solution to draw mini charts can be seen as interesting exercise and demonstration of possibilities - so @portfoliobuilder please feel free to post the code, but while it is interesting I think for practical reasons outlined below multiple chart/pane is recommended solution.

Multiple charts/panes are less resource hungry than single chart using dozens of Foreign() calls. Multiple charts/panes offer smoother experience and higher refresh rates than any Foreign-based code, because multiple panes use: a) multithreading b) quickdata tech.

2 Likes

Yes, I don't see a reason for cluttering up a chart with too many panes or too many charts. But as an exercise in in code writing here it is (though written in 2007 and perhaps much has changed)

// refers to some code from a 2007 post in the forum by a James Hutchison
// graphics for multiple ticker windows simultaneously

function GetMonth( MonthNumber )
{
         switch (MonthNumber) {
            case 2 :
              result = "Feb";
               break ;
            case 3 :
              result = "Mar";
              break ;
            case 4 :
              result = "Apr";
            break ;
          case 5 :
            result = "May";
            break ;
          case 6 :
            result = "Jun";
            break ;
          case 7 :
            result = "Jul";
            break ;
          case 8 :
            result = "Aug";
            break ;
          case 9 :
            result = "Sep";
            break ;
          case 10 :
            result = "Oct";
            break ;
          case 11 :
            result = "Nov";
            break ;
          case 12 :
            result = "Dec";
      break ;
   }

   return result;
}

GfxSetOverlayMode( 2 );

String = "" ;
for ( x = 0 ; x < 200 ; x++ )
{
        WList = CategoryGetName( categoryWatchlist, x );
         if (WList != "" )
        {
                 String = String + WList +","   ;
        }
}
WatchList = ParamList ( "Watch List", String );
for ( x = 0 ; x < 200 ; x++ )
{
        sym = StrExtract ( String, x );
         if (sym == WatchList)
        {
                 listNum = x  ;
        }
}

Page = Param ( "Page #", 0 , 0 , 12 , 1 );
ChartsWiNum = Param ( "# Charts Wide", 3 , 1 , 10 , 1 ); // I've bumped up the max to 10 from original setting of 4
ChartsHiNum = Param ( "# Charts High", 3 , 1 , 10 , 1 ); // I've bumped up the max to 10 from original setting of 4
Bars =Param ( "Number Of Bars", 195 , 50 , 250 , 1 ) ;
lDays =Param ( "Long MA", 200 , 50 , 250 , 1 ) ;
sDays =Param ( "Short MA", 50 , 5 , 100 , 1 ) ;
vDays =Param ( "Volume MA", 50 , 5 , 100 , 1 ) ;
ChartMargin = 5 ;
DateMargin = 10 ;
DataMargin = 20 ;
NumCharts = ChartsWiNum * ChartsHiNum ;
ScreenHeight = Status ( "pxheight" )  ;
SceernWidth = Status ( "pxwidth" ) ;
ChartHeight = ScreenHeight / ChartsHiNum -2   ;
Width = SceernWidth / ChartsWiNum - 2 ;
BarChartHeight=  (0.75 ) * ChartHeight - DataMargin;
VolChartHeight = ChartHeight - BarChartHeight - DataMargin*2 ;
NumberPriceLevels = BarChartHeight / 25 ;
BarChartWidth=  Width - 45 ;
BarWidth = (BarChartWidth - ChartMargin * 3 ) / Bars  ;
LastBar =  BarCount ;
FirstBar =  LastBar - Bars;
_N (list = CategoryGetSymbols( categoryWatchlist, listnum ));
printf ( "Watch List Name\n");
WL = CategoryGetName( categoryWatchlist, listnum );
SymbolNum = Page * NumCharts;

for ( x = 0 ; x < ChartsHiNum ; x++ )
{
         for ( i = 0 ; i < ChartsWiNum ; i++ )
        {
                 notDone = True;
                 sym = StrExtract ( list, SymbolNum );
                  if (sym != "" )
                 {
                 x1 = Width * i + 5 ;
                 y1 = ChartHeight * x + 5 ;
                 x2 = Width * (i + 1 );
                 y2 = ChartHeight * (x + 1 )-DateMargin;
                  GfxSelectPen ( colorBlack );
                  GfxRectangle ( x1, y1, x2, y2 );
                 SymbolNum++;

                  SetForeign (sym);

                 EMAShort= EMA (C,sDays);
                 EMALong= EMA (C,lDays);
                 EMAVol= EMA (V,vDays);

                 D = Day ();
                 M = Month ();
                 Y = Year ();

                 priceHigh = 0 ;
                 VolHigh = 0 ;
                 priceMin = 1000000 ;
                  for ( z = FirstBar; z < LastBar ; z++ )
                 {
                          Vol = V[z];
                          BarH = H[z];
                          BarL = L[z];
                           if ( Vol > VolHigh )
                          {
                                   VolHigh = Vol;
                          }
                           if ( BarH > priceHigh )
                          {
                                   priceHigh = BarH;
                          }
                           if ( BarL < priceMin )
                          {
                                   priceMin = BarL;
                          }
                 }

                 LOpen = O[LastBar- 1];
                 LHigh = H[LastBar- 1];
                 LLow = L[LastBar- 1];
                 LClose = C[LastBar- 1];
                 LVol = V[LastBar- 1];
                  GfxSelectFont ( "Tahoma" , 9 );
                  GfxSetTextColor (colorBlack);
                  GfxTextOut (sym + "   O: " +LOpen + "   H: " +LHigh + "   L: " +LLow + "   C: " +LClose + "   Vol: " +LVol/ 1000000 + " M", x1+5 , y1+2 );

                 VolRatio = VolChartHeight /  (VolHigh+0.00001) ;
                 Range = priceHigh - priceMin;
                 Ratio = BarChartHeight / (Range+0.00001) ;
                 PriceLineLevel = Range / NumberPriceLevels;
                 yHi=(((priceHigh - priceMin )* Ratio) - BarChartHeight) * -1 + y1 + DataMargin;
                  GfxTextOut ( WriteVal (priceHigh, 1.2 ),   x1 + BarChartWidth, yHi -8 );
                  GfxSelectPen ( colorLightGrey );
                  GfxMoveTo ( x1 , yHi );
                  GfxLineTo ( x1 + BarChartWidth -5 , yHi );
                  for ( z = 0 ; z < NumberPriceLevels- 1   ; z++ )
                 {
                          PriceLevel = PriceLineLevel*z + priceMin;

                          yHi=(((PriceLevel - priceMin )* Ratio) - BarChartHeight) * -1 + y1 + DataMargin;
                           GfxTextOut ( WriteVal (PriceLevel, 1.2 ),   x1 + BarChartWidth, yHi -8 );
                           GfxSelectPen ( colorLightGrey );
                           GfxMoveTo ( x1 , yHi );
                           GfxLineTo ( x1 + BarChartWidth -5 , yHi );
                 }

                 HighestLast = 0 ;
                 w = 1 ;
                 sEMAlast = EMAShort[FirstBar];
                 lEMAlast = EMALong[FirstBar];
                 vEMAlast = EMAVol[FirstBar];
                  for ( z = FirstBar; z < LastBar ; z++ )
                 {
                          BarH = H[z];
                          BarL = L[z];
                          BarO = O[z];
                          BarC = C[z];
                          Vol = V[z];
                          sEMA = EMAShort[z];
                          lEMA = EMALong[z];
                          vEMA = EMAVol[z];
                          BarDay = D[z];
                          BarMonth = M[z];
                          BarYear = Y[z];

                          yO = (((BarO - priceMin )* Ratio)- BarChartHeight ) * -1 + y1 + DataMargin;
                          yC = (((BarC - priceMin )* Ratio)- BarChartHeight ) * -1 + y1 + DataMargin;
                          yHi = (((BarH - priceMin )* Ratio)- BarChartHeight ) * -1 + y1 + DataMargin;
                          yLo = (((BarL - priceMin )* Ratio) - BarChartHeight) * -1 + y1 + DataMargin ;
                          ysEMAlast = (((sEMAlast - priceMin )* Ratio) - BarChartHeight) * -1 + y1 + DataMargin;
                          ysEMA = (((sEMA - priceMin )* Ratio) - BarChartHeight) * -1 + y1 + DataMargin;
                          ylEMAlast = (((lEMAlast - priceMin )* Ratio) - BarChartHeight) * -1 + y1 + DataMargin;
                          ylEMA = (((lEMA - priceMin )* Ratio) - BarChartHeight) * -1 + y1 + DataMargin;
                          vHi = y2 - (Vol * VolRatio)  ;
                          yvEMAlast = y2 - (vEMAlast  * VolRatio) ;
                          yvEMA = y2 - (vEMA * VolRatio)  ;

                           if ( BarH > HighestLast )
                          {
                                   HighestLast = BarH;
                                    GfxSelectPen ( colorBlue );
                          }

                           else
                          {
                                    GfxSelectPen ( colorRed );
                          }

                           GfxMoveTo ( BarWidth * w + x1 + ChartMargin , yHi );
                           GfxLineTo ( BarWidth * w + x1 + ChartMargin, yLo );
                           GfxMoveTo ( BarWidth * w + x1 + ChartMargin -1   , yO );
                           GfxLineTo ( BarWidth * w + x1 + ChartMargin, yO );
                           GfxMoveTo ( BarWidth * w + x1 + ChartMargin +1   , yC );
                           GfxLineTo ( BarWidth * w + x1 + ChartMargin, yC );
                           if ( BarO > BarC)
                          {
                                    GfxSelectPen ( colorRed );
                          }
                           else
                          {
                                    GfxSelectPen ( colorLime );
                          }
                           GfxMoveTo ( BarWidth * w + x1 + ChartMargin , vHi );
                           GfxLineTo ( BarWidth * w + x1 + ChartMargin, y2 );

                           GfxSelectPen ( colorLime );
                           GfxMoveTo ( BarWidth * (w -1 ) + x1  + ChartMargin , ysEMAlast );
                           GfxLineTo ( BarWidth * w + x1 + ChartMargin, ysEMA );
                           GfxSelectPen ( colorDarkRed );
                           GfxMoveTo ( BarWidth * (w -1 ) + x1  + ChartMargin , ylEMAlast );
                           GfxLineTo ( BarWidth * w + x1 + ChartMargin, ylEMA );
                           GfxSelectPen ( colorBlack );
                           GfxMoveTo ( BarWidth * (w -1 ) + x1  + ChartMargin , yvEMAlast );
                           GfxLineTo ( BarWidth * w + x1 + ChartMargin, yvEMA );
                          w++;

                          sEMAlast = sEMA ;
                          lEMAlast = lEMA ;
                          vEMAlast = vEMA ;

                           GfxSelectFont ( "Tahoma" , 7 );
                           GfxSelectPen ( colorLightGrey );

                           if (BarDay== 1 & notDone )
                          {
                                    if (BarMonth == 1 ) myLabel =WriteVal (BarYear, 1.0 );
                                    else myLabel =GetMonth(BarMonth);
                                    GfxTextOut (myLabel,   BarWidth * w + x1 + ChartMargin - 5 , y2 +DateMargin/3 );
                                    GfxMoveTo ( BarWidth * w + x1 + ChartMargin , y2  );
                                    GfxLineTo ( BarWidth * w + x1 + ChartMargin, y1 + DataMargin  );
                                   notDone = False;
                          }

                           if (BarDay== 2 & notDone )
                          {
                                    if (BarMonth == 1 ) myLabel =WriteVal (BarYear, 1.0 );
                                    else myLabel =GetMonth(BarMonth);
                                    GfxTextOut (myLabel,   BarWidth * w + x1 + ChartMargin - 5 , y2 +DateMargin/3 );
                                    GfxMoveTo ( BarWidth * w + x1 + ChartMargin , y2  );
                                    GfxLineTo ( BarWidth * w + x1 + ChartMargin, y1 + DataMargin  );
                                   notDone = False;
                          }

                           if (BarDay== 3 & notDone )
                          {
                                    if (BarMonth == 1 ) myLabel =WriteVal (BarYear, 1.0 );
                                    else myLabel =GetMonth(BarMonth);
                                    GfxTextOut (myLabel,   BarWidth * w + x1 + ChartMargin - 5 , y2 +DateMargin/3 );
                                    GfxMoveTo ( BarWidth * w + x1 + ChartMargin , y2  );
                                    GfxLineTo ( BarWidth * w + x1 + ChartMargin, y1 + DataMargin  );
                                   notDone = False;
                          }

                           if (BarDay== 4 & notDone )
                          {
                                    if (BarMonth == 1 ) myLabel =WriteVal (BarYear, 1.0 );
                                    else myLabel =GetMonth(BarMonth);
                                    GfxTextOut (myLabel,   BarWidth * w + x1 + ChartMargin - 5 , y2 +DateMargin/3 );
                                    GfxMoveTo ( BarWidth * w + x1 + ChartMargin , y2  );
                                    GfxLineTo ( BarWidth * w + x1 + ChartMargin, y1 + DataMargin  );
                                   notDone = False;
                          }

                           if (BarDay== 5 & notDone )
                          {
                                    if (BarMonth == 1 ) myLabel =WriteVal (BarYear, 1.0 );
                                    else myLabel =GetMonth(BarMonth);
                                    GfxTextOut (myLabel,   BarWidth * w + x1 + ChartMargin - 5 , y2 +DateMargin/3 );
                                    GfxMoveTo ( BarWidth * w + x1 + ChartMargin , y2 - VolChartHeight );
                                    GfxLineTo ( BarWidth * w + x1 + ChartMargin, y1 + DataMargin );
                                   notDone = False;
                          }
                           if (BarDay== 6 |BarDay== 7 |BarDay== 8 |BarDay== 9 |BarDay== 10 )
                          {
                                   notDone = True;
                          }

                 }
                  RestorePriceArrays ();
                 }
        }
}

And the whole NDX100 in one go.
image

11 Likes

Wow! THANK YOU everyone for this response.

@Tomasz offers a great alternative for Amibroker overcoming the 16 panes per chart limitation.

Perhaps, in the future (if not already possible):

  1. Multiple charts can be synchronized to zoom and scroll together (similar to Interval Link).
  2. Chart Window Borders can be adjusted to be very thin, and the Title Bar & controls can toggle off and back on.
  3. Chart Window Layouts (size and position) can be optionally locked in place to prevent accidental movement.

As for the need and benefit for so many different panes, some of my back testing uses more than 16 indicators (I know it may be seem excessive to some), and the ability to visually see each independent decision point is a powerful decision support tool!

Thank you again!

I have discovered that multiple charts in a layout can synchronize both the time interval and the symbol by clicking the icons located at the bottom of the chart.
amibroker-sync-Symbol and Interval

1 Like

Hi @portfoliobuilder,
I try to use your code but there are some problems with Last OHLC price as picture below, it shows only Open Price and no data in Volume. Can you help me to solve this problem?
Thank you in advance!
Capture2

@Albert88 sorry but the code works fine with my data. Perhaps check your data and make sure you have Volume?

BTW it is not my code, I just tinkered with it and posted it with proper credit to the author, as an example of code writing. Overall I think it is not useful as @Tomasz has pointed out repeatedly you can open as many charts as you might need without low level Gfx code.

Copy&paste is not equal to code writing. Please read carefully about actual creator.

As for "problem"... you apparently have symbols with data not being up-to-date to selected symbol's data of chart.


I beg to differ,
AmiBroker does not have native synced scrolling/zooming feature (not blaming anyone). So it is not quite the same thing creating multiple single UI charts. So creating an AFL chart matrix is reasonable option making synced scroll/zoom of price data of multiple symbols possible and user can refrain from using OLE which is worse than that. Another thing is that via chart matrix one can quickly change between watchlists to be displayed.

But the code itself can be written better optimized.

E.g. this one

function GetMonth( MonthNumber )
{
         switch (MonthNumber) {
            case 2 :
              result = "Feb";
               break ;
            case 3 :
              result = "Mar";
              break ;
            case 4 :
              result = "Apr";
            break ;
          case 5 :
            result = "May";
            break ;
          case 6 :
            result = "Jun";
            break ;
          case 7 :
            result = "Jul";
            break ;
          case 8 :
            result = "Aug";
            break ;
          case 9 :
            result = "Sep";
            break ;
          case 10 :
            result = "Oct";
            break ;
          case 11 :
            result = "Nov";
            break ;
          case 12 :
            result = "Dec";
      break ;
   }

   return result;
}

can be written via single line without any switch statement but just by using StrExtract instead.

function GetMonth( MonthNumber ) {
	return StrExtract(",Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", MonthNumber-1);
}

Etc. related to other things...

Note also, that code does not have scrolling yet (but can be added easily -> attaching to native scroll bar via AFL). And also you do not need Param to zoom (instead you can attach to native AB UI zoom via AFL). So it is not optimally handled.

No blaming... but just giving info that there are more optimal and faster ways.

1 Like

Many thanks @portfoliobuilder and @fxshrat for your reply and valuable advice,
Im a new one and wanna study some code to improve myself.
I found out and solved the problem when I change from "End" to "START time of interval" in Intraday tab of Preferences.
The code works perfectly fine now.

Capture5

2 Likes