Business Central 2019 Wave 2 – How to extend the Business Charts with your own charts

We all have seen the business charts on the role centers that Microsoft added. It always would have been nice, if you would have been able to add your own charts. Unfortunately, there were no events available so that you had to basically rewrite the page part and replace the existing one. I requested the addition of events in that area and it seems that those requests were heard: This changed now with Business Central 2019 Wave 2 – we can add our own charts.

I am going to explain how to do this and also bring up some issues that should be changed, in my eyes.

What do we want to do?

We have the chart part shown below in the role center. We would like to add our own chart in there to enhance the experience for our users and provide additional KPIs.

Standard Chart in Business Central
Business Central – Business Charts

Implementation of our own charts

I have the full source code of the project below, so you can download it and customize it to add your own charts. The main process is that you need to create a new codeunit and subscribe to some events that allow you interacting with the chart part. Then you also need to provide the values for your chart.

To get started

Microsoft provides an event to add your own chart into the Chart Definition table, unfortunately, this event is only raised when the table is still empty. This only happens typically on initialization of a new company.

What is really needed here is that the entire “Chart Definition” table and the entire functionality around adding charts is refactored using the Discovery Event Pattern. Let’s hope that this will be done in the future. For now, you have to create your chart during the install or setup of your app – but this also means that the chart stays there, even if you remove your extension. When someone selects your chart after your extension is removed, the standard behavior kicks in and – based on the implementation in standard – the “Top Ten Customers” chart will be displayed.

Let’s create a new codeunit, I called it “Demo Chart Management”, since it will contain the full implementation for the Demo Chart. I then added a global procedure as follows (also am showing the needed global Label for this:

  1. var
  2.     ChartNameLbl: Label 'DEMO';
  3.  
  4. procedure InstallChart()
  5. var
  6.     ChartDefinition: Record "Chart Definition";
  7. begin
  8.     if not ChartDefinition.Get(Codeunit::"Demo Chart Management", ChartNameLbl) then begin
  9.         ChartDefinition."Code Unit ID" := Codeunit::"Demo Chart Management";
  10.         ChartDefinition."Chart Name" := ChartNameLbl;
  11.         ChartDefinition.Enabled := true;
  12.         ChartDefinition.Insert(true);
  13.     end;
  14. end;

I am calling this from outside of the codeunit, so it needed to be a global function. This function is called – in my demo code – from the Installation codeunit as shown here:

  1. trigger OnInstallAppPerCompany()
  2. var
  3.     ChartMgt: Codeunit "Demo Chart Management";
  4. begin
  5.     ChartMgt.InstallChart();
  6. end;

Now that we have the initial issue covered and get our chart into the list of chart definitions, you can actually now see it in the list of charts. However, when you select the chart, nothing happens – well, it shows another chart:

Business Central – Select Chart

The events needed

Microsoft added events to codeunit Chart Management and we will need to subscribe to those and implement our own logic. There is also an event to subscribe to in the page Help and Chart Wrapper to subscribe to.

Codeunit Chart Management – OnBeforeChartDescription

This event will show the description of the chart when you click on “Chart Information”. The implementation looks as follows:

  1. [EventSubscriber(ObjectType::Codeunit, Codeunit::"Chart Management"'OnBeforeChartDescription'''truetrue)]
  2. local procedure ChartManagementOnChartDescriptionSubscriber(ChartDefinition: Record "Chart Definition"; var ChartDescription: Text; var IsHandled: Boolean)
  3. begin
  4.     case ChartDefinition."Code Unit ID" of
  5.         Codeunit::"Demo Chart Management":
  6.             begin
  7.                 ChartDescription := ChartDescriptionMsg;
  8.                 IsHandled := true;
  9.             end;
  10.     end;
  11. end;

Codeunit Chart Management – OnAfterPopulateChartDefinitionTable

If the Chart Definition table is empty, this event will be raised when filling in the standard charts. The implementation should call the function from above InstallChart.

Codeunit Chart Management – OnBeforeSetPeriodLength

When the period length is changed in the chart part, this event is called. The implementation should look like this:

  1. [EventSubscriber(ObjectType::Codeunit, Codeunit::"Chart Management"'OnBeforeSetPeriodLength'''truetrue)]
  2. local procedure ChartManagementOnSetPeriodLengthSubscriber(ChartDefinition: Record "Chart Definition"; PeriodLength: Option; var IsHandled: Boolean)
  3. var
  4.     BusChartBuf: Record "Business Chart Buffer";
  5. begin
  6.     case ChartDefinition."Code Unit ID" of
  7.         Codeunit::"Demo Chart Management":
  8.             begin
  9.                 BusChartBuf."Period Length" := PeriodLength;
  10.                 SaveSettings(BusChartBuf);
  11.                 IsHandled := true;
  12.             end;
  13.     end;
  14. end;

Codeunit Chart Management – OnBeforeUpdateChart

This is the main event – it is called when the chart should be updated with data. It is also important that you update the Last Used Chart table to save the current settings and allow the chart to be shown again when the user goes back to the role center. This is also something that should be done in base code, but unfortunately, it does not get executed, if “IsHandled” is set in the event. So, an alternative would be to leave IsHandled = false, but this doesn’t follow the Handled pattern, so I am rather considering that an incomplete implementation of base functionality and hope that it will be fixed.

I will go into some details of the UpdateChart function later.

  1. [EventSubscriber(ObjectType::Codeunit, Codeunit::"Chart Management"'OnBeforeUpdateChart'''truetrue)]
  2. local procedure ChartManagementOnUpdateChartSubscriber(var ChartDefinition: Record "Chart Definition"; var BusinessChartBuffer: Record "Business Chart Buffer"; Period: Option; var IsHandled: Boolean)
  3. begin
  4.     case ChartDefinition."Code Unit ID" of
  5.         Codeunit::"Demo Chart Management":
  6.             begin
  7.                 UpdateChart(BusinessChartBuffer, 0);
  8.                 UpdateLastUsedChart(ChartDefinition);
  9.                 IsHandled := true;
  10.             end;
  11.     end;
  12. end;

Codeunit Chart Management – OnBeforeUpdateNextPrevious

This event does not necessarily need to be subscribed to. It is called when a user clicks on “Next” or “Previous” period and you can perform any actions that are necessary to be done before that.

Codeunit Chart Management – OnBeforeUpdateStatusText

The status text is the heading above the chart that defines the name of the chart and the current period length.

Page Help and Chart Wrapper – OnBeforeInitializeSelectedChart

When a user selects a chart, this event is raised and you should subscribe to it to get the settings for your chart.

Functions called in event handlers

The UpdateLastUsedChart function is called to store the last used chart per user so that it can be displayed again when the user goes back to the role center.

  1. local procedure UpdateLastUsedChart(ChartDefinition: Record "Chart Definition")
  2. var
  3.     LastUsedChart: Record "Last Used Chart";
  4. begin
  5.     with LastUsedChart do
  6.         if Get(UserId()) then begin
  7.             Validate("Code Unit ID", ChartDefinition."Code Unit ID");
  8.             Validate("Chart Name", ChartDefinition."Chart Name");
  9.             Modify();
  10.         end else begin
  11.             Validate(UID, UserId());
  12.             Validate("Code Unit ID", ChartDefinition."Code Unit ID");
  13.             Validate("Chart Name", ChartDefinition."Chart Name");
  14.             Insert();
  15.         end;
  16. end;

Now we are getting to the main function: UpdateChart. The function will add the proper columns and measurements and values to the chart and then make sure that they are displayed. You might have to adjust this based on the chart type that you are displaying.

It is also important that you should try to use a Query to calculate your values for the chart and do one read to the server instead of manually calculating the values and then performing multiple server roundtrips to get the information. This just improves the performance of the chart and it is important to not slow down the display of the role center.

You can see some documentation in the code. Some of the functions that are called are local functions and can be found in the source code at the bottom.

  1. local procedure UpdateChart(var BusChartBuf: Record "Business Chart Buffer"; Period: Option " ",Next,Previous)
  2. var
  3.     BusChartMapColumn: Record "Business Chart Map";
  4.     PeriodLength: Text[1];
  5.     NoOfPeriods: Integer;
  6.     PeriodCounter: Integer;
  7.     FromDate: Date;
  8.     ToDate: Date;
  9.     i: Integer;
  10. begin
  11.     // any settings stored for this chart - we are only storing the periods that are used.
  12.     GetSettings(BusChartBuf."Period Length");
  13.  
  14.      with BusChartBuf do begin
  15.         if Period = Period::" " then begin
  16.             FromDate := 0D;
  17.             ToDate := 0D;
  18.         end else
  19.             if FindMidColumn(BusChartMapColumn) then
  20.                 GetPeriodFromMapColumn(BusChartMapColumn.Index, FromDate, ToDate);
  21.  
  22.         // now we are initializing the chart and defining the X axis for it - here it is a period of time
  23.         Initialize();
  24.         SetPeriodXAxis();
  25.         InitParameters(BusChartBuf, PeriodLength, NoOfPeriods);
  26.         CalcAndInsertPeriodAxis(BusChartBuf, Period, NoOfPeriods, FromDate, ToDate);
  27.  
  28.         // since we are using a stacked column, we are adding 5 measures for each period. Those are just demo values
  29.         for i := 1 to 5 do
  30.             AddMeasure(StrSubstNo(MeasureNameTxt, Format(i))''"Data Type"::Decimal"Chart Type"::StackedColumn);
  31.  
  32.         // we are now defining the Y axis values for each of the measures for each of the periods. Since it's just a demo,
  33.         // we are randmizing some values.
  34.         FindFirstColumn(BusChartMapColumn);
  35.         for PeriodCounter := 1 to NoOfPeriods do
  36.             for i := 1 to 5 do begin
  37.                 GetPeriodFromMapColumn(PeriodCounter - 1, FromDate, ToDate);
  38.                 Randomize(CurrentDateTime() - CreateDateTime(Today(), 0T));
  39.                 SetValue(StrSubstNo(MeasureNameTxt, Format(i)), PeriodCounter - 1, Random(10000) / 100);
  40.             end;
  41.     end;
  42. end;

We are done

The full chart looks like this:

You can find the source code here for download.

I hope this helps a bit to make your life easier and not have to spend the same amount of time that I took to figure this out.

If you have any questions, feel free to hit me up in the comments.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.