Chapter 6. Reports and Charts

Many applications need the ability to display information to the end user in the form of a report or a chart. Fortunately, APEX makes this extremely easy, allowing you to create reports and charts out of the box. APEX provides an assortment of ready-made report and chart types, and you can customize them in many ways. APEX 4 includes a new charting engine, AnyCharts 5.1, which improves rendering time, generates better graphics, and allows for greater customization.

Reports

You might think of reports as a simple way to present record information in a tabular format. You would certainly be correct, but reports are much more flexible than that. In APEX, you have complete control over how a report is processed and presented, which means that you can create some really advanced reports.

Looking at the home page of the Buglist application, you can see the report that was automatically created by the application creation wizard, as shown in Figure 6-1. This report has the same look and feel as the rest of our application because it is using a particular template. We will cover how to customize templates in general in Chapter 10. In this chapter, we will look at how you can modify the layout and look of reports specifically.

Buglist report

Figure 6.1. Buglist report

The code used as the region source for the report is quite a simple query, as shown in Listing 6-1.

Example 6-1. Buglist Report Region Source Query

select

  "ID", "BUGID",
  "REPORTED","STATUS",
  "PRIORITY","DESCRIPTION",
  "REPORTED_BY", "ASSIGNED_TO"
 from   "BUGLIST"
where
(
 instr(upper("STATUS"),upper(nvl(:P1_REPORT_SEARCH,"STATUS"))) > 0  or
 instr(upper("PRIORITY"),upper(nvl(:P1_REPORT_SEARCH,"PRIORITY"))) > 0  or
 instr(upper("DESCRIPTION"),upper(nvl(:P1_REPORT_SEARCH,"DESCRIPTION"))) > 0  or
 instr(upper("REPORTED_BY"),upper(nvl(:P1_REPORT_SEARCH,"REPORTED_BY"))) > 0
)

The code in Listing 6-1 was generated automatically by the application creation wizard. Note that the query uses quotation marks (quotes) around the column names and the table name. Generally, it's a bad idea to use quotes like this, as it makes Oracle check the names in a case-sensitive manner. This means you must use quotes (and the correct case) when you later refer to that table or column. Without quotes, Oracle checks in uppercase so if you search for an unquoted lowercase "t," it is the same as searching for a quoted uppercase "T." Listing 6-2 demonstrates the effect of using quotes.

Example 6-2. Effect of Using Quotation Marks

-- Create a table without quotes

apexdemo@DBTEST> create table upperCase(
  2  id number,
  3  data varchar2(20)
  4  );

Table created.

apexdemo@DBTEST> insert into upperCase
  2  (id, data)
  3  values
  4  (1, 'data 1'),

1 row created.

-- Create a table with quotes
apexdemo@DBTEST> create table "MixedCase"(
  2  "Id" number,
  3  "Data" varchar2(20)
  4  );

Table created.

-- Can't insert into the table; we need to use quotes
apexdemo@DBTEST> insert into MixedCase
  2  (id, data)
  3  values
  4  (1, 'data 1'),
insert into MixedCase
            *
ERROR at line 1:
ORA-00942: table or view does not exist

-- Using quotes, the insert work
apexdemo@DBTEST> insert into "MixedCase"
  2  ("Id", "Data")
  3  values
  4  (1, 'Data 1'),

1 row created.

apexdemo@DBTEST> select * from upperCase;

        ID DAT
---------- --------------------
         1 data 1

apexdemo@DBTEST> select * from mixedcase
select * from mixedcase
              *
ERROR at line 1:
ORA-00942: table or view does not exist

Creating tables with quoted names can lead to confusion. You'll find yourself trying to query a table that you know is there, but getting this error every time:

ORA-00942: table or view does not exist

When you get such an error, check carefully to be sure that the table name in your query matches the one used when the table was created. If the name is mixed-case or contains spaces or other unusual characters, you'll need to put that table name in quotes in your queries.

It's best practice not to use quotes in naming database tables, unless there's a very compelling reason to do so. Without them, you can write your SQL statements in whichever case you like, and they will work (where work is a relative term, of course).

Referring back to Listing 6-1, you can see that the query is selecting eight columns from the table, and uses the P1_REPORT_SEARCH page item to compare against the STATUS, PRIORITY, DESCRIPTION, and REPORTED_BY columns.

If you edit the Report region, you'll see that three tabs are available, as shown in Figure 6-2:

  • The Region Definition tab allows you to modify the query itself, as well as adjust the positioning of the region and the other attributes that affect a page region, such as the conditional logic used to display it.

  • The Report Attributes tab allows you to modify the attributes of the individual columns of the query used in the region definition, as shown in Figure 6-3.

  • The Print Attributes tab allows you to control attributes relating to printing of the report, which we will cover in Chapter 9.

Editing a Report region

Figure 6.2. Editing a Report region

The Report Attributes tab

Figure 6.3. The Report Attributes tab

The Column Attributes section of the Report Attributes tab (Figure 6-3) lists each column selected in the query. Each column has a number of attributes, such as Column Alignment, Heading Alignment, Sort Sequence, and so on. You can also drill down even further; clicking the link next to each column name provides access to more of the column's attributes, as you will see later in this chapter.

From the Column Attributes section you can control a wide variety of report aspects, ranging from the report headers to how a column should be formatted and whether that column will be enabled as a link to some other page or external web site.

Report Headers

From Figures 6-1 and 6-3, you can see that the report uses the names of the columns as the column headings. Of course, as with most things in APEX, if you don't like using the column names this way, you can change it. The choices at the top of the Column Attributes section allow you to initial cap the column names, use custom headers, use a PL/SQL function, or not to display any headers (None).

Initial Capping

The Column Names (InitCap) choice uses the column names and also applies the InitCap function to them. InitCap is a simple function that changes the first letter of each word into uppercase and lowercases the subsequent letters of the word. For example, REPORTED becomes Reported. You might think that REPORTED_BY would become Reported_By, but APEX is a bit more sensible than that; it changes it to Reported By, substituting a space for the underscore character so that it looks nicer on the page.

Note that you can use the InitCap function in your own code, like this:

apexdemo@DBTEST> select

  2  initcap('REPORTED_BY') as colname
  3  from dual;

COLNAM
-----------
Reported_By

However, as you can see, the underscore remains. The translation of the underscore into a space is performed by some extra code in the APEX interface (presumably by using the Translate or Replace function), rather than being part of the InitCap code.

Custom Headings

You may want the heading names to be something else entirely, rather than based on the column names from the query itself. In that case, you would choose the Custom option at the top of the Column Attributes section. Then you can type anything you like into the Heading attribute for each column,

Custom Headings via a PL/SQL Function

One of the more interesting choices is to use the PL/SQL option to define the report's column headings. When you choose this option, you get an extra field in the Column Attributes section, where you can specify a function that will return the headings, as shown in Figure 6-4.

Using PL/SQL to return heading names

Figure 6.4. Using PL/SQL to return heading names

The PL/SQL function that you enter here needs to return a string containing the headings delimited by colons. As a simple example, you can enter the following code:

return 'ID:Bug Id:Reported:Status:Priority:Description:Reported By:Assigned To'

This function just returns a colon-delimited string, with each entry in the string corresponding to each column sequentially.

You can make this much more useful by returning a more generic list of headings. For example, let's say you want to store all the headings for your reports in a table so you can use them in various places throughout the application (for example, you might want to reuse them in an LOV). Listing 6-3 demonstrates creating such a table.

Example 6-3. Creating and Populating the report_headings Table

apexdemo@DBTEST> create table report_headings(

  2  id number primary_key,
  3  table_name varchar2(200),
  4  heading_id number,
  5  heading_name varchar2(200)
  6  );

Table created.

apexdemo@DBTEST> insert into report_headings
  2  (id, table_name, heading_id, heading_name)
  3  values
  4  (1, 'BUGLIST', 1, 'Id'),

1 row created.

apexdemo@DBTEST> insert into report_headings
  2  (id, table_name, heading_id, heading_name)
  3  values
  4  (2, 'BUGLIST', 2, 'Bug Id'),

1 row created.

-- Extra rows added here

apexdemo@DBTEST> select * from report_headings;

        ID TABLE_NAME HEADING_ID HEADING_NAME
---------- ---------- ---------- --------------------
         1 BUGLIST             1 Id
         2 BUGLIST             2 Bug Id
         3 BUGLIST             3 Reported
         4 BUGLIST             4 Status
         5 BUGLIST             5 Priority
         6 BUGLIST             6 Description
         7 BUGLIST             7 Reported By
         8 BUGLIST             8 Assigned To

8 rows selected.

Listing 6-3 creates a table called report_headings to store the headings that should be used for each table. It then inserts a record for each heading for the buglist table. (Note that we've omitted some of the insert statements for brevity, but the final select statement shows the records that have been inserted.) The heading_id column allows you to order the way headings are listed for a particular table. You could use the id column, but using a separate column makes it easier to rearrange the heading order later.

Now you can create a packaged procedure that will, when a table name is passed into it as a parameter, return the colon-delimited list of headings that should be used, as shown in Listing 6-4.

Example 6-4. Creating a PL/SQL Function to Return Headings

apexdemo@DBTEST> create or replace package pkg_report_headers as

  2  function get_headers(p_table in varchar2) return varchar2;
  3  end pkg_report_headers;
  4  /

Package created.

apexdemo@DBTEST> create or replace
2  package body pkg_report_headers as
3  function get_headers(p_table in varchar2) return varchar2 is
4    v_temp apex_application_global.vc_arr2;
5    begin
6      select heading_name bulk collect into v_temp
7      from   report_headings
8      where  table_name=p_table
9      order by heading_id;
10     return apex_util.table_to_string(v_temp, ':'),
11   end;
12 end pkg_report_headers;

Package body created.

In the function definition, we declare a variable called v_temp as the type vc_arr2, which is defined in the wwv_flow_global package. The public synonym, apex_application_global, can be used to access this package:

v_temp apex_application_global.vc_arr2;

The vc_arr2 type is a PL/SQL associative array type that maps a binary integer to a varchar2. It is capable of holding an array of strings (you can look in the wwv_flow_global package to see the definition). We used a bulk collect statement instead of a cursor for loop to reduce network traffic. In a cursor for loop, a SQL statement is executed for each iteration of the loop. The bulk collect statement transfers groups of data in one round trip, which can significantly improve performance.

The rows in the report_headings table where the heading_name column matches the p_table input parameter are placed in the v_temp array:

select heading_name bulk collect into v_temp
from   report_headings
where  table_name=p_table
order by heading_id;

Once you've added all the heading names to the array, you use the apex_util.table_to_string function to return the array type as a string:

return apex_util.table_to_string(v_temp, ':'),

Here, the second parameter to the table_to_string function is the character you wish to use as a delimiter in the returned string. The default character is actually the colon, so you don't need to specify it explicitly (we specified it for clarity here).

You can now test to check that the code works by calling the function via some SQL, as shown in Listing 6-5.

Example 6-5. Calling the Function via SQL

apexdemo@DBTEST> select

  2  pkg_report_headers.get_headers('BUGLIST') as headings
  3  from dual;

HEADINGS
----------------------------------------------------------------------
Bug Id:Reported:Status:Priority:Description:Reported By:Assigned To:

So, with just a few lines of code, you are able to return a dynamic list of report headings. Using a bind variable in the call to pkg_report_headers.get_headers allows you to programmatically set or change the input parameter's value. Because the report we're dealing with is called BUGLIST, that's the value that will be sent unless you change it. You can plug this into your report by entering the following code in the "Function returning colon delimited headings" text box (Figure 6-4):

return pkg_report_headers.get_headers(:REPORT_NAME)

You can now dynamically update the report headings and have the changes immediately reflected in the report, as shown in Listing 6-6 and Figure 6-5.

Example 6-6. Updating the report_headings Table

apexdemo@DBTEST> update report_headings

  2  set heading_name = 'Bug Status'
  3  where id = 4;

1 row updated.

apexdemo@DBTEST> commit;
Commit complete.
Updated headings reflected in the report

Figure 6.5. Updated headings reflected in the report

You might be thinking that instead of using an array in the get_headers function, you could have just concatenated the headings together in a string. That's true. However, there are quite a few places in APEX where you will work with arrays and delimited strings (for example, when working with LOVs). If you're not already familiar with the table_to_string function and the corresponding string_to_table function, it's worth exploring them, because you'll find you use them more often as you become more experienced with APEX.

You might also have wondered about passing in a hard-coded value ('BUGLIST') for the p_table parameter of the function. You could instead pass in the value of a page item (for example, &P1_TABLE_NAME.) that represents the table you're using, which would allow the columns to be retrieved even more generically, if that's something that you'd like to try (sometimes there is such a thing as trying to make your code too generic).

As you can see, choosing the PL/SQL option for Headings Type is a powerful way to generate the heading names at runtime.

Named Columns vs. Generic Columns

You might have already noticed that when you define the region source query for your report, you have the option to use column names based on the query or to use generic column names, as shown in Figure 6-6.

Specifying the column names type

Figure 6.6. Specifying the column names type

The default is the Use Query-Specific Column Names and Validate Query option. As the name implies, this means that when you enter your query and click the Apply button in the Application Builder, your query will be validated (checked to ensure it is syntactically correct and so on). In contrast, if you select the Use Generic Column Names (parse query at runtime only) option, the query will not be validated within the Application Builder when you hit the Apply button. Instead, it will be parsed at runtime, and you will not be forewarned if you've made a mistake in the query (such as incorrectly naming a column or table).

However, the difference between using named columns (as we'll refer to query-specific column names) and generic columns runs much deeper than just whether they are parsed and validated at design time. Both types serve a specific purpose, so one type may be more applicable to a particular situation than the other.

Generic Columns

When you choose the Use Generic Column Names option, you automatically lose the names of the columns in the Column Attributes section of the report, as shown in Figure 6-7. You can see that where the column names were defined by the query before, the columns are now named COL01, COL02 . . . all the way to COL60 (only the first six columns are shown in Figure 6-7).

Using generic columns

Figure 6.7. Using generic columns

You can actually control the number of generic columns by modifying the Maximum Number of Generic Report Columns value in the region source definition. The default value is 60. You can increase or decrease this number, depending on the number of columns you expect to return in your query.

So, why are generic columns useful and when would you choose to use them over named columns? Generic columns are handy when you have a report where each column will look more or less the same; that is, the structure of the report is fairly uniform, and you want to define that layout in a simple way that will be repeated across each of the columns defined for the report.

You can see the layout definitions in the templates. The Layout and Pagination section for the report definition shows that the report is using the Standard Report template, as shown in Figure 6-8.

Layout and Pagination section for the report

Figure 6.8. Layout and Pagination section for the report

You can examine the Standard Report template by clicking the link in the Templates section of the Page Definition section, which will list each type of template being used by the components on the page. The template defines different sections for particular parts of the report. The Column Heading template contains the following code:

<th class="t15header" #ALIGNMENT#>#COLUMN_HEADER#</th>

This code uses the standard HTML th element to create a table header tag and specifies a class attribute of t15header, which is defined within one of the CSS files included for the standard APEX templates. The substitution values of #ALIGNMENT# and #COLUMN_HEADER# pick up the value for the alignment assigned to each column header and the column header value itself.

The primary difference between generic columns and named columns is in the Column Templates section. For generic columns, the template contains this code:

<td class="t15data" #ALIGNMENT#>#COLUMN_VALUE#</td>

This looks similar to the Column Heading template, except that it uses the td element tag, a different CSS class, and the #COLUMN_VALUE# substitution variable. For every column of the report, this template substitutes the value of each column where the #COLUMN_VALUE# is specified. This is what we were referring to earlier when we said that generic columns are useful when you are not particularly concerned with the look of individual columns, but just want them all to be formatted in more or less the same way (using the same HTML markup and CSS class information).

You might also notice that you can control the template by assigning conditional logic, as shown in Figure 6-9. This example uses two different column templates. The first one encloses the #COLUMN_VALUE# in bold tags (<b> and </b>) if the PL/SQL expression:

'#PRIORITY#' = 'High'

evaluates to true. The other template displays the column value without the bold tags if the converse PL/SQL expression is true (that is, if the priority is not High).

Conditional logic in a generic column template

Figure 6.9. Conditional logic in a generic column template

Now you might be surprised to see that even though you're using generic columns (COL01, COL02, and so on), you can use substitution strings such as #PRIORITY# to access the value of a particular column. Even though the template is being applied to each column, you are still able to evaluate particular columns for each row to which the template is being applied.

The end result of customizing the template in this way, if you hadn't guessed already, is that all the records that have a High priority are now displayed in bold, as shown in Figure 6-10.

Conditional template logic applied to the report

Figure 6.10. Conditional template logic applied to the report

Three options are available for the conditions applied to the column templates:

  • Use for Even Numbered Rows

  • Use for Odd Numbered Rows

  • Use Based on PL/SQL Expression

You can use the even and odd row options to make it easier to read the report by perhaps using a different CSS class to make alternate rows have a different background color. The PL/SQL expression option allows a great amount of flexibility to apply a different column template depending on specific criteria. Currently, the Application Builder allows you to specify up to four different column templates and associated conditions.

Named Columns

Using named columns with your reports gives you much more flexibility than generic columns afford. You can customize the layout of your report template to a much greater degree since, as the name implies, you can reference each column by name in the report template.

You can change the report query back to using the default Use Query-Specific Column Names and Validate Query option. However, the report will still be using a column-based template. To take advantage of named columns, you can create a new report template, as shown in Figure 6-11.

Creating a new report template

Figure 6.11. Creating a new report template

When you create a new report template from scratch, you are given the option of making it either a column template or a row template. This determines whether the report template can use generic columns or named columns; a report that uses named columns can use a row-based template.

When you create a row-based template, the definition of the template is slightly different from the definition for a column template, in that the template refers to the row rather than the column. By default, the row template contains a minimal amount of code, as shown in Figure 6-12.

Definition of a newly created row template

Figure 6.12. Definition of a newly created row template

If you changed the report to use this new template and ran the page, you wouldn't see anything just yet, since the row template defines table rows and columns (using the tr and td elements), but there is no enclosing table element defined in the template yet (in this example anyway; yours may vary).

In Figure 6-12, you can also see that the template is using the substitution strings #1#, #2#, and so on. This is known as using positional notation; for example, #1# refers to the first column in the query.

To make this template a bit more visually pleasing, you can copy some of the CSS styling information that the Standard Report template uses. In the Row Template 1 box, use the following code:

<tr #HIGHLIGHT_ROW#>

<td class="t15data">#1#</td>
<td class="t15data">#2#</td>
<td class="t15data">#3#</td>
<td class="t15data">#4#</td>
<td class="t15data">#5#</td>
</tr>

Here, you're using the positional notation to show each of the first five columns and the same t15data CSS class that the Standard Report template uses for the columns.

Next, in the Before Rows section, add the following code:

<table class="t15standard" summary="Report">

<th class="t15header" #ALIGNMENT#>#1#</th>
<th class="t15header" #ALIGNMENT#>#2#</th>
<th class="t15header" #ALIGNMENT#>#3#</th>
<th class="t15header" #ALIGNMENT#>#4#</th>
<th class="t15header" #ALIGNMENT#>#5#</th>

Again, this is using the same sort of code that is used in the Standard Report template to create a table and then list each of the first five headings using positional notation.

Finally, in the After Rows section, use the following code:

<tr>

    <td colspan="99" class="t15afterrows">
        <span class="left">#EXTERNAL_LINK##CSV_LINK#</span>
        <table style="float:right;text-align:right;" summary="pagination">
            #PAGINATION#</table>

    </td>
</tr></table>

This was again taken verbatim from the Standard Report template. It simply inserts an extra area below the report that adds the ability to paginate the report and export it to a comma-separated values (CSV) format file.

If you save the template and run the report now, it will look exactly as it did before. The report template was changed, but the report is still using the Standard Reports template. The template setting needs to be changed to Buglist Report as shown in Figure 6-13.

Selecting the Buglist Report template

Figure 6.13. Selecting the Buglist Report template

If you now run the page, you should see the report looks pretty much as it did with the Standard Report region, although it is displaying only the first five columns of the query, as shown in Figure 6-14.

Using the row-based template

Figure 6.14. Using the row-based template

As this point, you are probably wondering why you should go to the extra effort of using row-based templates when they end up looking the same as column-based templates. But in this template, you're still using positional-based notation. You have not yet taken advantage of the extra flexibility attained by referring to the columns by name.

In the original report, all the columns are in the same row. If a lot of data has been entered in the Description column, that could make the report difficult to read (perhaps making the report overly wide or limiting the amount of text that can be shown in the Description column). Wouldn't it be nice if you could make the Description column appear in a separate row below the main detail row of the record? Well, as you've no doubt guessed, you can accomplish this sort of layout by using row-based templates combined with named columns.

First, you need to convert the template to use the named columns rather than the positional notation. Change the Row Template 1 section to use the code shown in Listing 6-7.

Example 6-7. Row Template Using Named Column Notation

<tr #HIGHLIGHT_ROW#>

  <td class="t15data">#ID#</td>
  <td class="t15data">#BUGID#</td>
  <td class="t15data">#REPORTED#</td>
  <td class="t15data">#STATUS#</td>
  <td class="t15data">#PRIORITY#</td>
  <td class="t15data">#DESCRIPTION#</td>
  <td class="t15data">#REPORTED_BY#</td>
  <td class="t15data">#ASSIGNED_TO#</td>
</tr>

This code adds the extra columns to the code. Note that in the query, you use the name of the actual column, rather than the heading name.

Also modify the Before Rows section to include the extra heading names, as shown in Listing 6-8.

Example 6-8. Extra Heading Names in the Before Rows Template

<table class="t15standard" summary="Report">

  <th class="t15header" #ALIGNMENT#>#1#</th>
  <th class="t15header" #ALIGNMENT#>#2#</th>
  <th class="t15header" #ALIGNMENT#>#3#</th>
  <th class="t15header" #ALIGNMENT#>#4#</th>
  <th class="t15header" #ALIGNMENT#>#5#</th>
  <th class="t15header" #ALIGNMENT#>#6#</th>
  <th class="t15header" #ALIGNMENT#>#7#</th>
  <th class="t15header" #ALIGNMENT#>#8#</th>

Note that you could hard-code the names of the headings in the template. However, using positional notation gives you the ability to modify the headings using the options covered earlier in this chapter (such as via a PL/SQL function), without needing to modify the template again.

Now you should have a template that makes the report look exactly as it did before. Since you're now using named columns, you can modify the row template a bit more to move the Description column into a separate row, as shown in Listing 6-9.

Example 6-9. Moving the Description Column into a Separate Row

<tr #HIGHLIGHT_ROW#>

  <td class="t15data">#ID#</td>
  <td class="t15data">#BUGID#</td>
  <td class="t15data">#REPORTED#</td>
  <td class="t15data">#STATUS#</td>
  <td class="t15data">#PRIORITY#</td>
  <td class="t15data">#REPORTED_BY#</td>
  <td class="t15data">#ASSIGNED_TO#</td>
</tr>
<tr>
<td class="t15data" colspan="7">#DESCRIPTION#</td>
</tr>

This code creates another row below the original one and makes the Description column span the entire width of the row (by using the colspan attribute to make it span seven columns, which is the number of columns in the row above it). Since you have changed the order of the columns in the row, you also need to modify the Before Rows template, as shown in Listing 6-10.

Example 6-10. Modified Before Rows Template

<table class="t15standard" summary="Report">

  <th class="t15header" #ALIGNMENT#>#1#</th>
  <th class="t15header" #ALIGNMENT#>#2#</th>
  <th class="t15header" #ALIGNMENT#>#3#</th>
  <th class="t15header" #ALIGNMENT#>#4#</th>
  <th class="t15header" #ALIGNMENT#>#5#</th>
  <th class="t15header" #ALIGNMENT#>#7#</th>
  <th class="t15header" #ALIGNMENT#>#8#</th>

The only change in Listing 6-10 is that you have removed the line that represented the heading for the Description column (which was column 6 in positional notation). When you mix positional notation with named notation, things can become confusing. It's not immediately apparent which positional element represents which column. If you probably won't want to later change the column headings in the report definition (or dynamically), you could hard-code them to make it easier to read and modify the template.

After these changes, the report looks like the one shown in Figure 6-15.

Using named columns to affect the layout

Figure 6.15. Using named columns to affect the layout

Note

It is debatable whether the report in Figure 6-15 is actually any easier to read than it was before (in fact, you could argue that it's slightly more difficult to read, since the eye does not scan quite so easily across a nonuniform order like this). However, the point of this exercise is just to demonstrate how easily you can modify the layout of the report using named columns and templates. You could make this report easier to read by using a different CSS class for the Description column, so that the background of that column is a slightly different color.

We can extend this example a bit further, making it a little less cluttered and perhaps more useful to the end users. Imagine that the users want to see only the description from bugs that are still classified as Open. You can do this quite easily by using conditional logic with the row template, much as you saw with the earlier generic columns example. First, modify the row template to include an extra column before the Description, just to add some space and make it easier to read, as shown in Listing 6-11.

Example 6-11. Inserting a Column Prior to Description

<tr #HIGHLIGHT_ROW#>

  <td class="t15data">#ID#</td>
  <td class="t15data">#BUGID#</td>
  <td class="t15data">#REPORTED#</td>
  <td class="t15data">#STATUS#</td>
  <td class="t15data">#PRIORITY#</td>
  <td class="t15data">#REPORTED_BY#</td>
  <td class="t15data">#ASSIGNED_TO#</td>
</tr>
<tr>
  <td class="t15data"></td>
  <td class="t15data" colspan="6">#DESCRIPTION#</td>
</tr>

Next, add a condition to this row template, which is a PL/SQL expression:

'#STATUS#' = 'Open'

This template will be used for records with an Open status. Listing 6-12 shows a second template to be used when the status is not Open (using the reverse PL/SQL expression logic). This second template does not display the Description column at all.

Example 6-12. Row Template for Closed Bugs

<tr #HIGHLIGHT_ROW#>

  <td class="t15data">#ID#</td>
  <td class="t15data">#BUGID#</td>
  <td class="t15data">#REPORTED#</td>
  <td class="t15data">#STATUS#</td>
  <td class="t15data">#PRIORITY#</td>
  <td class="t15data">#REPORTED_BY#</td>
<td class="t15data">#ASSIGNED_TO#</td>
</tr>

If you now run the report, it should look similar to Figure 6-16. Notice that where the bug is classified as Closed (Bug ID 2 in Figure 6-16), no description is shown underneath.

Using conditional logic in a row template

Figure 6.16. Using conditional logic in a row template

Row-based templates also allow you to use a number of conditional templates, based on even rows, odd rows, or PL/SQL expressions.

Named column templates are probably one of the most underused features of APEX. The amount of control and flexibility they give you is amazing. You can build some really complex-looking reports—ones that don't even look like standard tabular reports—by applying logic to vary how each individual row is represented in the report.

Report Pagination

When you define your report template, you can also specify a pagination scheme for the report (look back at Figure 6-8). APEX 3.0 currently supports the following pagination schemes:

  • Row Ranges 1-15 16-30 (with set pagination)

  • Row Ranges 1-15 16-30 in select list (with pagination)

  • Row Ranges X to Y (no pagination)

  • Row Ranges X to Y of Z (no pagination)

  • Row Ranges X to Y of Z (with pagination)

  • Search Engine 1,2,3,4 (set based pagination)

  • Use Externally Created Pagination Buttons

  • Row Ranges X to Y (with next and previous links)

Even though some of the schemes say no pagination, they still allow you to move through your report resultset in different ways.

Enabling pagination in your report and the type of pagination style you use can have an impact on the performance and usability of your report. To demonstrate the effect of the different pagination schemes, let's consider a report that returns a large number of rows. The report is based on a new table called big_emp. This table contains repeated records from the familiar emp table, as well as the same indexes, for a total of more than 114,000 records, as shown in Listing 6-13. You can use any large table to test pagination yourself, since the point is to see the differences when using the various pagination schemes with the same data.

Example 6-13. Definition of the big_emp Table

apexdemo@DBTEST> desc big_emp;

 Name          Null?    Type
 ------------- -------- ---------------
 EMPNO         NOT NULL NUMBER(4)
 ENAME                  VARCHAR2(10)
 JOB                    VARCHAR2(9)
 MGR                    NUMBER(4)
 HIREDATE               DATE
 SAL                    NUMBER(7,2)
 COMM                   NUMBER(7,2)
 DEPTNO                 NUMBER(2)
apexdemo@DBTEST> select count(*) from big_emp;

  COUNT(*
----------
    114688

For a quick test, let's just create a new page in the application (to be removed later), with a new SQL Report region, using the following SQL in the region source:

select

  empno, ename,
  job, mgr,
  hiredate, sal,
  comm, deptno
from
  big_emp

Now we need some way of measuring the relative performance of each of the pagination types. This could be done in various ways, such as by running the page in debug mode and examining the timings, or by generating a trace file for the page and examining it with TKProf. However, a much easier way (though not as accurate in granularity) is to use the substitution string #TIMING# within the footer of the Report region. Then APEX will substitute the string for the time (in seconds) that it took to render that particular region; in other words, how long it took to run the query and generate the report for those particular rows retrieved. Figure 6-17 shows the timing information being displayed below the report when using the Row Ranges 1-15 16-30 (with set pagination) scheme. Notice the information above the report that's new with APEX 4.0. The report is telling you that it's returning too many rows to be practical and asks that you apply some type of filter. This can easily be suppressed via the the Pagination section in the Report Attributes page.

Tip

You can use the #TIMING# substitution string in any region type. It can be a great way to track down performance issues in your application, or even just to provide some visual feedback to your end users to let them know how quickly your pages are being generated.

Using the #TIMING# substitution string in a region

Figure 6.17. Using the #TIMING# substitution string in a region

To benchmark the pagination types, we timed how long it took to retrieve the first set of results (records 1–15), and then how long it took to page to other results (records 61–75 and records 136–150). This process simulates a user paging through the resultset (although, typically, users might not move that many pages into the results). This test was with the report's Max Row Count set to 120,000, to allow paging to the end of the resultset (in practice, you wouldn't want to return this many rows, but this test is to illustrate the effect that the different pagination schemes have on performance). Table 6-1 shows the results.

Table 6.1. Pagination Style Benchmarks with a Max Row Count Setting of 120,000

Pagination Style

Rows 1–15

Rows 61–75

Rows 136–150

Row Ranges 1–15 16–30 (with set pagination)

13.21

13.36

15.99

Row Ranges X to Y of Z (with pagination)

13.20

14.90

15.20

Search Engine 1,2,3,4 (set-based pagination)

13.30

14.20

14.87

Row Ranges X to Y (with next and previous links)

0.21

0.26

0.28

Note

We ran our test on relatively modest hardware. You might get much faster times for your tests. The point is to illustrate the relative differences between timings, not to see how fast you can make each pagination type.

You can see from Table 6-1 that there's a huge difference in performance between the pagination styles that had to keep track of the number of overall results (such as Row Ranges X to Y of Z) compared with the simple Row Ranges X to Y pagination type. Also, remember that for some of the pagination types, there is no quick way for the user to jump to a particular set of results, which means that the performance effect is cumulative (that is, if it takes 5 seconds per page, then it might take 25 seconds to reach the fifth page if the user can't skip ahead). These benchmarks illustrate that if you have a very large resultset, the type of pagination you select can significantly affect how usable your report is for the end users.

You can greatly improve the performance of your reports by keeping the Max Row Count setting to a sensible value (or even allowing the user to define it). We are all very familiar with the typical search engines available on the Internet today. When you search for something with these search engines, you usually get many hits, often running to many pages of results. Typically, you'll use only the first page or so of results, rather than going to the twentieth or fiftieth page (since the first results must be most relevant, right?). You can apply the same logic to your reports, showing, say, just the first 500 results. Table 6-2 shows the results of rerunning the same benchmark, but this time with the report's Max Row Count set to 500 (which is the default value if you don't specify one).

Table 6.2. Pagination Style Benchmarks with a Max Row Count Setting of 500

Pagination Style

Rows 1–15

Rows 61–75

Rows 136–150

Row Ranges 1–15 16–30 (with set pagination)

0.30

0.33

0.32

Row Ranges X to Y of Z (with pagination)

0.19

0.29

0.34

Search Engine 1,2,3,4 (set-based pagination)

0.28

0.31

0.31

Row Ranges X to Y (with next and previous links)

0.23

0.24

0.28

The results in Table 6-2 clearly show a dramatic improvement for those pagination types that provide feedback about the maximum number of rows returned. So, if your users can live without knowing that XXX number of results were returned, you might use this approach. Note that this might not be preferable in all cases, so you should use your judgment about where reducing the Max Row Count setting for the report might be appropriate.

Break Formatting

You can use break formatting to specify whether a particular column will repeat values across rows. For example, if the current record contains the same value for a particular column that the preceding row contained, you can suppress outputting the value to make the report slightly easier to read.

An example of a column for which you might not want to repeat values is one that shows the department number in an employee report. Using the employee report from the previous examples, rearrange the order to display the department number first. You can do this quite easily via the Column Attributes section, as shown in Figure 6-18.

Rearranging columns in the Column Attributes section

Figure 6.18. Rearranging columns in the Column Attributes section

Here, you can use the small up and down arrows to the right of the Sort Sequence select list to move the DEPTNO column so that it is the first column in the list of columns. When you use a column-based template, as discussed earlier in the chapter, you don't need to also modify the query to rearrange the column order in the report. When the template uses named columns, though, you do need to modify the template to rearrange the order.

Note

In previous versions of APEX, each time you moved a column, you had to resubmit the page to the server to effect the change. Now, that work is performed via Ajax, and no full page refresh is required. This makes your application much more responsive, and it's quicker for users to make changes to their data.

Next, go to the Break Formatting section of the report, and you will see the options for breaks are First Column; First and Second Columns; and First, Second and Third columns. For this example, choose First Column. The resulting report is shown in Figure 6-19. It no longer shows repeated data for the DEPTNO column, so that it becomes much easier to visually group the data in the report.

Using a report break to avoid repeated columns

Figure 6.19. Using a report break to avoid repeated columns

As you can see in the Break Formatting section, some other interesting options are available for report breaks. One is to repeat the report headings whenever a break is performed, which makes it even easier to visually comprehend the data in the report. (Note that you'll need to make sure your returned data is ordered sensibly using order by so that the breaks can be performed.) You can also add some extra text before break columns or after. Figure 6-20 shows some extra tweaks to repeat the report heading whenever the DEPTNO value changes and to display the sum of the salary for that DEPTNO. Figure 6-21 shows how different the report looks with just a bit of work.

Of course, you should take advantage of break formatting only when it makes sense to do so with regard to both your business rules and the data you are displaying. Otherwise, the report can actually become more confusing to read.

Customizing break formatting to display sums and headings

Figure 6.20. Customizing break formatting to display sums and headings

Report showing department breaks and sums

Figure 6.21. Report showing department breaks and sums

It is important to note that you could have achieved almost the same results had the report been built as an Interactive Report rather than in the Classic style. Using the Action button on an Interactive report, you can create control breaks on the fly. If you wanted specific text displayed on each break, though, you'd still have to do some work.

Column Formatting

You might think that formatting a report column refers only to things like controlling the currency symbol or perhaps setting the number of decimal places displayed in a numeric value. However, APEX allows a great deal of control over the column formats, so you can think of formatting as referring to the onscreen display of any type of data represented by your columns.

Number and Date Formatting

The Column Formatting section of the Column Attributes section for a report includes a Number/Date Format setting. As an example, the Buglist report currently displays the date that the bug was reported in the format DD-MON-YY. You can modify that by changing the formatting of the REPORTED column, as shown in Figure 6-22.

Modifying the date formatting for the REPORTED column

Figure 6.22. Modifying the date formatting for the REPORTED column

Figure 6-22 shows the Number/Date Format value for the REPORTED column changed to use DD-MON-YYYY HH:MIPM. This value was selected from the pop-up list for this field. The text field is a free-format field, so you can type anything into it, as long as the value makes sense for the particular field and is valid. For example, you will get strange results if you try to apply a date-format mask to a numeric column, or if you specify invalid characters in your date-format mask. Note that no validation of the input text takes place at design time; it is used only at runtime.

If you ran the report after making this change to the date format, you'd find that the REPORTED column now displays values such as 27-JAN-2006 12:00AM. In our example, all the times display as 12:00AM, since only a date (not a time component) was used when each record was created. Thus, the time defaults to 12:00AM.

Another interesting format mask for dates uses the keyword SINCE, which displays a text description of how long ago that date occurred. For example, you might see 6 months ago, 2 days ago, 8 minutes ago, and so on. This makes it much easier for the end users to immediately see how long ago an event occurred, rather than having to mentally calculate it themselves. Also, when you use the SINCE mask, the value is calculated each time it is used, so you'll see the age of the record increasing each time you view the report—for example, from 8 minutes ago to 9 minutes ago and so on.

CSS Classes

You can also assign particular CSS classes and styles to the column, For example, if you wanted to display the REPORTED field in bold, you could take advantage of the predefined CSS class called fielddatabold, which is defined in the standard APEX CSS files. All you would need to do is to enter the text fielddatabold into the CSS Class field in the Column Formatting section, and APEX would enclose your column data in an HTML span element and assign the class to it, as shown here:

<span class="fielddatabold">27-JAN-06</span>

Highlighted Words

You can also enter a comma-delimited list of words into the Highlight Words text field in the Column Formatting section, and APEX will automatically highlight any words that match the column data when you run the report. It does this by wrapping any matching words in an HTML span element and applying a CSS style to that span. For example, entering JAN in the Highlight Words field generates the following HTML (manually broken here):

<td class="t15data">27-
  <span style="font-weight: bold; color: red;">JAN</span>−06</td>

Although this built-in highlighting is great, considering you get it for free, you have no control over how the word is highlighted—you can't modify the CSS styling that is used. So unless you always want your words highlighted in bold red text, you might find this feature of limited use.

HTML Expressions

Perhaps the most interesting, yet often overlooked, part of the Column Formatting section is the HTML Expression text field. This is extremely powerful, since it allows you to essentially apply another template to the column.

As an example, let's imagine that you'd like the Buglist report to give the end users a quick way to e-mail the person who reported the bug. To do this, you could turn the REPORTED_BY column into a link that, when clicked, launches the default e-mail client and automatically fills in the e-mail address of the person who reported the bug. This is not quite as difficult as it might seem at first, but it does require changing the report a bit.

First, you need to modify the query used for the report so you can extract the e-mail address of the person who reported the bug. Recall that the query was originally as follows:

select

  "ID","BUGID",
  "REPORTED","STATUS",
  "PRIORITY","DESCRIPTION",
  "REPORTED_BY", "ASSIGNED_TO"
 from
  "BUGLIST"
where
(
   ... where clause omitted
)

Currently, the users who reported bugs are not maintained in the user_repository table, so you wouldn't be able to get the e-mail address from there. (The reported_by field is actually a free-format field, so any name could be typed in it.) For this example, we have inserted records into the user_repository table to represent each person who has reported a bug, and also modified the bug-editing screens to allow only the reporting person to be selected from a list of people in the user_repository table. We will not show all the steps we performed, since they are not directly relevant to this example, but you can see the changes in the application export included with the downloadable code provided for this chapter.

You can now change the report query to extract the e-mail address from the user_repository table by performing a subquery, as shown in Listing 6-14.

Example 6-14. Subquery to Extract E-Mail Addresses

SELECT

  bl.id,
  bl.bugid,
  bl.reported,
  bl.status,
  bl.priority,
  bl.description,
  bl.reported_by,
  bl.assigned_to,
  (select ur.email from user_repository ur
     where ur.username = bl.reported_by)
    as reported_email,
  (select initcap(ur2.forename) || ' ' || initcap(ur2.surname) from
     user_repository ur2 where ur2.username = bl.reported_by)
    as reported_full_name
FROM buglist bl
WHERE(
  ... where clause omitted
)

This query is essentially the same as before, except now the subquery looks up the e-mail address from the user_repository table and also generates the full name of the user by using the InitCap function to uppercase the forename and surname of the person who reported the bug, This is necessary because the reported_by field now contains the username of the user, rather than the free-format text it contained previously. Note also in Listing 6-14 that table aliases have been added to make it easier to remove any ambiguity about which table the columns reference.

If you now looked at the Report Attributes section, you would see the REPORTED_EMAIL and REPORTED_FULL_NAME columns added to the list. You could just modify your report template to display this new column, but it would be nice to use it in a link from the user's full name. To do that, enter the following code in the HTML Expression field for the REPORTED_BY column:

<a href="mailto:#REPORTED_EMAIL#">#REPORTED_FULL_NAME#</a>

Even though you place this code into the HTML Expression field for the REPORTED_BY column, you are actually referencing the two other columns you haven't directly used in the report. The code will generate an HTML a (anchor) element, with the special href attribute of mailto:, which most modern browsers recognize as meaning that the default mail client should be launched when the user clicks the link. You pass the value of the #REPORTED_EMAIL# column into the href, so that when the default mail client launches, it generates a new e-mail message and uses the value of the REPORTED_EMAIL column as the e-mail address to which to send the message. By using the REPORTED_FULL_NAME column value inside the HTML anchor, the text that is displayed for the link is the user's full name, rather than the login username, which you would have gotten if you had used #REPORTED_BY# instead.

But why use the REPORTED_BY column here, when it isn't actually displayed in the report? You could have just as easily shown the REPORTED_EMAIL column in the report, and used the same HTML expression to format it slightly differently. Well, that's a good question. Doing that would make it slightly less confusing when you return to this code in six months or so. However, doing it this way demonstrates that in the HTML expression, you can reference columns other than just the current one. This is what gives HTML expressions their power. Using them, you can completely transform the way a particular column is represented on the screen.

You can use HTML expressions in many different ways. Typical uses include making custom links (as in this example), generating the correct HTML for an image to be displayed, and linking in some custom JavaScript for that item.

One place you should definitely consider using HTML expressions is if you find yourself including HTML markup inside your queries. For example, rather than write code like this:

select

  name,
  '<img src="apexdemo.generate_image?p_file=' ||
    filename || '"</img"' as custom_image
from
custom_files

use code like this:

select

  name,
  filename as custom_image
from
  custom_files

then use an HTML expression to transform the custom_image column like this:

<img src="/apexdemo.generate_image?p_file=#CUSTOM_IMAGE#"></img>

We suggest doing it this way for two reasons:

Readability:

Your code will be much more readable. It will be much easier to modify the second query than the first, since the extra text, and particularly the quotation marks, make the first query more difficult to read.

Performance:

By minimizing (or better still, eliminating) the HTML markup in your queries, you make it possible for Oracle to reuse the same cached query used in different places in your application, even though the HTML expression might format the results differently. When you use the first query, you might need to have multiple versions of it throughout your application, if you want the resulting HTML to be displayed slightly differently.

It is definitely best practice to try to separate your queries from the display markup as much as you can, and using HTML expressions makes that task very easy indeed.

Columns as Links

APEX makes it easy to use the columns in your report as links, either to pages in the same application or to external URLs. In the previous example, in rewriting the query, the original column link for editing a bug (which was created by the application creation wizard) was lost. However, it is very easy to re-create that link.

All you need to do is decide which column you want to turn into a link and choose that column in the Column Attributes section of the Application Builder. Then you can access the Column Link section for that particular column, as shown in Figure 6-23.

Column link attributes

Figure 6.23. Column link attributes

As you can see from Figure 6-23, you can use substitution values in the link text. This example has #ID# as the link text, which means that you'll still be able to see the data in that column, but APEX will turn that text into a link for you. The target of this link is set to be another page in the application. You could also make the link an external URL, and then you'll be able to enter a URL.

When the target is another page in your application, you can set page items to certain values via the link that will be generated. This example sets the value of the P3_ID page item to the value represented by the substitution string #ID#. In other words, the value of that page item is set so that the page being linked to (page 3) is then able to retrieve the details about that particular record in a page process.

Note

You might wonder what happens if you want to pass across more than three items in your link; for example, if you have a four-part primary key. There are all sorts of work-arounds for that, and two main schools of thought. One school of thought (the one we belong to) is that when you get to the stage where your primary keys become that complex, it is perhaps time to think about using surrogate primary keys (that is, a single value that uniquely identifies the record). If you use surrogate keys, you will be able to pass across the primary key, which the other page can then use to retrieve all of the details for that particular record. The other school of thought maintains that having three, four, five, or more components of the primary key is the correct way to go.

Charts

Like reports, charts are a great way to visually present information to the user. APEX provides built-in charting functionality and a wizard to help you create charts. However, unlike reports, charts usually make sense only for certain types of data (or, rather, to represent the relationships between certain types of data).

To add a chart, you can either create a chart on an existing page or on a new page. If you want to add a chart to an existing page, first create a new region and select a region type of chart. This will give you access to the chart creation wizard, where you can choose which type of chart you wish to create, as shown in Figure 6-24. To create a new page for your chart, choose Create Page from the Application Builder, and then select a page type of chart. This will also take you to the chart creation wizard.

Using the chart creation wizard

Figure 6.24. Using the chart creation wizard

As you can see in Figure 6-24, two different types of charts are currently available in APEX:

HTML chart:

This is the most basic, although still highly effective, charting method in APEX. It relies on nothing more than standard HTML to produce static charts. This type of chart does not require the end user to have anything other than a standard web browser, and should therefore work in the vast majority of cases (even on mobile devices).

Flash chart:

This chart type was introduced in APEX 3.0. It uses the AnyChart Flash chart component, which is shipped as part of the APEX product, to produce animated, interactive Adobe Flash charts. To view the charts, the end user needs a web browser with Flash Player version 8 or higher installed (the installation of Flash Player can be easily automated as part of the Flash display itself).

In the previous version of APEX, a third chart type, Scalable Vector Graphics (SVG), was available. Although the SVG format is still supported in APEX 4.0, you can't create new SVG charts. If you upgrade an application that contains SVG charts, you can leave them alone or choose to upgrade them. The Region Definition for SVG charts contains a task named "Upgrade to New Flash Chart."

If you have an application that contains several SVG reports, you can upgrade the application to APEX 4.0 and convert all of the SVG charts at once as part of the upgrade.

The type of chart you use in your applications should be driven by two factors:

  • How the end users will access the application. For example, if they will be using mobile devices (smart phones, PDAs, and so on), they may not be able to view SVG or Flash charts.

  • The degree of interactivity with the data your users need. For example, the Flash chart allows much more interactivity than the standard HTML chart.

When you created the Buglist application, the application creation wizard automatically created charts (and reports) for you. Here, we will demonstrate how you can create your own charts, although obviously you can just modify the existing ones, too.

Chart Query Types

The basic format of the query to define the chart data is essentially the same for many chart types and styles. The standard format for the query is:

select

  <link>,
  <label>,
  <value>
from table

where link represents the link to use if the user clicks that particular data item in the chart, label represents the text to use as the label in the chart, and value represents the numeric value to use for the data point.

There are some exceptions to this general format. The following sections describe various chart types that require slight modifications of the general query format. Most of those are really subtypes of the Flash chart. We don't illustrate each subtype of chart (dial, range, candlestick, and so on) in this chapter, but we do list the query variations because that information may come in very handy down the road.

Dial Chart Syntax

When you use a dial chart, you use the following general syntax:

select

  <value>
  <maximum_value>
  [, <low_value [, <high_value> ]
from
  table

where value is the initial value for the data point, maximum value is the highest possible value allowed for the data point, and low_value and high_value are the historical low and high values (optional).

Multiple Series Syntax (Flash Charts Only)

Flash charts allow you to define multiple series to display in the chart. You can do this by entering additional series in the chart attributes, or you can list each series in a single query (if the data lends itself to being queried in that way). The following is the syntax for querying multiple series:

select

  link,
  label,
  series_1_value,
  series_2_value,
  [, ...]
from
  table

where the values for the different series are determined from the column aliases you use.

Range Chart Syntax (Flash Charts Only)

If you use a range chart type in a Flash chart, you need to provide two different values for each bar:

select

  link,
  label,
  low_value,
  high_value
from
  table

Candlestick Chart Syntax (Flash Charts Only)

Candlestick charts require four different values for each data point: a value for open, low, high, and close.

select

  link,
  label,
open,
  low,
  high,
  close
from
  table

Gantt Charts

Two types of Gantt charts are supported in APEX 4—Project and Resource. Both of these charts are used for project management and, to be used effectively, require a specific set of data. The query syntax describes what is necessary and is shown below:

Project Gantt chart syntax:

SELECT link, task_name, task_id, parent_task_id, actual_start_date, actual_end_date, progress
FROM   ...

Resource Gantt chart syntax

SELECT link, resource_id, resource_name, resource_parent_id, actual_start_date,
actual_end_date
FROM   ...

As you can see, these are fairly specialized reports, but if you are making use of the Team Development features of APEX 4.0, the data to support these reports will likely be available.

Flash Maps

Flash Maps are created using map files, which are included with APEX in conjunction with a query. The syntax of the query is basically the same for any map page, but what you include in the query has to match with the type of map you select. If you plan to use your own data to reference geographical information in a map, it must correspond to the region information associated with the map. If, for example, you want to show the population of Dallas County on a map of Texas, you need to have 'dallas' in your data as that is the lowest level of detail associated with state maps of the United States. The syntax used with maps is very simple, as shown here:

SELECT null link, region label, value Populations
FROM (
SELECT 'Florida' region, 18328340 value FROM dual
UNION ALL
SELECT 'Alaska' region, 686293 value FROM dual)

On a map of the United States, this query would result in Florida and Alaska being highlighted, with each displaying the label and value when the mouse is hovered over the region.

HTML Charts

Since we've already touched on the existence of HTML charts and Flash charts, it is important to define the distinctions between the two charting methods.

  • HTML Charts

    • Support only bar charts

    • Limited interactivity, no animations

    • Do not require a browser plugin to execute

  • Flash Charts

    • Support multiple chart types—bar, dial, pie, map, etc.

    • High levels of interactivity—animations, region highlighting, etc

    • Require the Flash browser plug-in

The charting examples here use a new page in the Buglist application to house the chart. Also, using the page creation wizard, we added a new tab called Charts to the tab set, for easy navigation to the new page (see Chapter 5 for details on adding tabs).

Suppose you want to see a chart of how many bugs were reported by each user. You can create a new HTML Chart region on the page and give it a title of Reported By. Now you need to provide the query to generate the chart. If you examine the definition of the buglist table, you can see that you can use a group by query against the REPORTED_BY field and perform a count on the returned records, as shown in Listing 6-15.

Example 6-15. Using a Group By Query

apexdemo@DBTEST> desc buglist;

 Name            Null?    Type
 --------------- -------- ------------
 ID              NOT NULL NUMBER
 BUGID                    NUMBER
 REPORTED                 DATE
 STATUS                   VARCHAR2(30)
 PRIORITY                 VARCHAR2(30)
 DESCRIPTION              VARCHAR2(255)
 REPORTED_BY              VARCHAR2(30)
 ASSIGNED_TO              VARCHAR2(30)

apexdemo@DBTEST> selec
  2  reported_by, count(*)
  3  from buglist
  4  group by reported_by;

REPORTED_BY            COUNT(*)
-------------------- ----------
LScott                        1
SGreen                        1
PMathews                      2
LBarnes                       2
JStevens                      1
CWatson                       3
MLawson                       2
CDonaldson                    1
RHudson                       1
RHuson                        1
CWhite                        2

However, rather than reporting the username, you want to show the forename and surname. To do this, you can adapt the query to perform a subquery against the user_repository table (or you could use a join if you prefer), as shown in Listing 6-16.

Example 6-16. Returning the Nicely Formatted Name

apexdemo@DBTEST>

REPORTED BY            BUGCOUNT
-------------------- ----------
Lucy Scott                    1
Steven Green                  1
Paul Mathews                  2
Laura Barnes                  2
John Stevens                  1
Carl Watson                   3
Mark Lawson                   2
Chris Donaldson               1
Rachel Hudson                 1
Rachel Huson                  1
Caroline White                2

You can now enter this query as the source for the chart. However, don't forget that you need to modify the query, since you must return a link, label, and value for each data point. At this stage, you do not want the data point to link to anything, so you can simply use NULL as the link, as shown in Listing 6-17.

Example 6-17. Modified Query to Use NULL as the Link

apexdemo@DBTEST>  select

  2    null as link,
  3  (select ur.forename || ' ' || ur.surname
  4     from user_repository ur where
  5     ur.username = bl.reported_by) as label,
  6  count(*) as value
  7  from buglist bl
  8* group by bl.reported_by;
apexdemo@DBTEST> /

LINK         LABEL                     VALU
------------ -------------------- ----------
             Lucy  Scott                   1
             Steven  Green                 1
             Paul  Mathews                 2
             Laura  Barnes                 2
John  Stevens                 1
             Carl  Watson                  3
             Mark  Lawson                  2
             Chris  Donaldson              1
             Rachel  Hudson                1
             Rachel  Huson                 1
             Caroline  White               2

In Listing 6-17, the columns aliases are modified to link, label, and value. You don't have to use these column aliases, strictly speaking, since it is the order of the columns that is important. However, using them makes debugging much easier—you can just run the query in SQL*Plus or SQL Workshop, and you'll be able to see immediately which columns are which.

If you run the page, you'll see a chart like the one shown in Figure 6-25. Note that the default is to assign random colors to each entry (which may not show up that well in the figure). The method used to assign the colors does show some intelligence, however. Rather than being completely random for each value, it uses the same color for repeated values (such as the entry for Carl Watson, since it reported three bugs) and also the same color for values that are near each other statistically (which may or may not be appropriate to your situation).

HTML chart showing number of bugs per reporter

Figure 6.25. HTML chart showing number of bugs per reporter

Now, what if you wanted to make this chart a bit more useful and enable the user to quickly see the bugs reported by a particular person. You can do this by providing a link that goes from the chart back to the same page but sets the value of a hidden page item with the value of the person who was clicked.

First, create a hidden item on the page, which will be used to store the name. Let's call it P14_REPORTED_BY. Then create a Report region below the Chart region. This performs a query against the buglist table and displays any rows where the REPORTED_BY columns match the value of the P14_REPORTED_BY page item. Also add some conditional display logic so that the report does not display if the value of the P14_REPORTED_BY page item is NULL (for example, the first time the user views the page without having clicked an item in the chart).

In the report, the query would look like this:

select

  bugid, reported,
  status, priority,
  description, assigned_to
from
  buglist
where
  reported_by = :P14_REPORTED_BY

Note the use of the P14_REPORTED_BY page item in the query. You also need to modify the query used for the chart so that it includes a link. Recall the format of the standard APEX URL (discussed in Chapter 5). You can create a URL that links back to this same page (in the same application) and automatically sets the value of the P14_REPORTED_BY page item. To achieve that, you want a URL that looks similar to this (divided over two lines for readability):

http://server:port/DAD/f?p=APP:PAGE:SESSION::::
P14_REPORTED_BY:cwatson

Fortunately, rather than needing to worry about getting the correct values for the server name, port number (if not on the default port value of 80), and DAD, you can use a relative URL that will automatically use the values for the current page. You can also use the substitution strings APP_ID, APP_PAGE_ID, and APP_SESSION in the URL. So your URL now looks like this:

f?p=&APP_ID.:&APP_PAGE_ID.:&APP_SESSION.::::P14_REPORTED_BY:

Notice how you can just use f?p to indicate the relative URL, rather than needing to specify everything from the http:// onwards. You can now replace the null as link part in the query with the code shown in Listing 6-18.

Example 6-18. Querying Using a Link Back to the Same Page

select

  'f?p=&APP_ID.:&APP_PAGE_ID.:&APP_SESSION.::::P14_REPORTED_BY:'
  || bl.reported_by as link,
  (select ur.forename || ' ' || ur.surname from user_repository ur
     where ur.username = bl.reported_by) as label,
  count(*) as value
from buglist bl
group by bl.reported_by

Running the page again and clicking one of the data points will show additional detail in the report section, as shown in Figure 6-26.

Chart with report detail

Figure 6.26. Chart with report detail

In Figure 6-26, you can also see some summary details displayed in the chart, including the number of rows shown (1-11) and the sum of all the values displayed (17). If you look at the Chart Attributes section for the HTML chart, you'll see that you can include the following summary items:

  • Number of data points

  • Minimum value

  • Average value

  • First value

  • Axis

  • Maximum value

  • Sum of all values

  • Last value

In Figure 6-26, the Axis summary value is 0, which indicates that the base axis for this chart is 0. You can alter this by changing the value for the Axis setting in the Chart Attributes section. The valid Axis values are as follows:

  • Average Value in Series

  • First Value in Series

  • Last Value in Series

  • Maximum Value in Series

  • Minimum Value in Series

  • Zero

Changing the Axis setting allows you to create some interesting-looking charts. For example, if you wish to compare the values against some baseline, you can use the first value or last value. This makes it easy to see the values that are greater or lower than this baseline, since the values that are lower than the baseline will be seen extending in one direction, while the values that exceed the baseline will extend in the other direction, Note that we said "direction," rather than "left" or "right" in the previous sentence, because you can also modify the orientation of the chart between horizontal and vertical, which makes the baseline view just described perhaps even more visually useful.

HTML charts are quite basic when compared with the other two types of charts. However, they are very functional and are the most supported type of chart for your users (almost every browser type is able to display HTML charts). You may find that the lack of additional features is more than made up for by the fact that the chart will work out of the box for almost every user.

Flash Charts

Both APEX 3.x and 4.x support Flash-based charts, but version 4.x has the advantage of using a newer release the AnyChart product, version 5.1. The range of options presented by AnyChart is tremendous, and though we will touch on certain features, covering all the possibilities is out of the scope of this book. To fully understand what you can do with charts in APEX 4, you have to experiment. You can access the AnyChart user guide at the http://anychart.com/products/anychart/docs/users-guide/. One thing to note is that not every type of AnyChart object is available in APEX. Only the Scatter (Marker) chart, for example, is available in APEX, though there are several other scatter chart types available in the full product.

Where Flash charts really shine is in the creation of dashboards. As every company becomes more data-hungry, the need for high-quality, usable reports grows. Given the amazing breadth of capabilities within AnyChart, you can easily create very high-quality visual representations of data with very little effort. Be careful in how you use some of the more obscure chart types (scatter, candlestick, etc.) as your users may not know how to read the chart. Just make sure you know your audience so you don't create overly complex, incomprehensible reports. One more thing to remember is to limit the use of animations in Flash Charts. While this feature does have a "wow factor," be conservative in where and how much you use it. If you build a dashboard, for example, you should probably limit animations as the motion can become annoying, especially if the page is refreshed frequently.

We built the dashboard shown in Figure 6-27 in about 30 minutes. It could have taken even less time, but it wouldn't have fit on the page as well as it does.

Buglist dashboard built with Flash charts

Figure 6.27. Buglist dashboard built with Flash charts

Some of the things we did to make this dashboard look the way it does are as follows:

  1. The chart regions were all built without a template so that the line and title above the region wouldn't take up space.

  2. The heights of all regions on the same row were set to make them appear to line up. I say "appear" because the height of a 3D Pie Chart and a Bar Chart don't look equal when they are next to each other (the pie chart looks shorter).

  3. The x-axis labels on the 3D Column chart were rotated 45 degrees so that all the lables would be visible. If left horizontal, the label values would overlap and some of the text would not show up.

  4. Two of the pie pieces were clicked to highlight them before this screen was captured.

In general, the process of creating Flash charts has not changed, but the number of options that can be applied to a report has increased in APEX 4.x. Table 6-3 lists the available chart types.

Table 6.3. Supported Chart Types

Type

Description

Column

Allows a single series, vertically oriented as a bar chart

Horizontal Bar

Allows a single series, horizontally oriented as a bar chart

Pie and Doughnut

Allows a single series, with each data point displayed as a slice in the pie

Scatter

Allows multiple series, vertically oriented and clustered by a common variable

Line

Allows multiple series, shows data over time compared to a single scale

Candlestick

Allows multiple series, shows the movement in the value of a data point over a specific time interval

Gauge

Allows multiple series, shows the value of datapoints in horizontal, vertical, or circular formats

Gantt

Allows single series, shows task duration and completion percentage

Figure 6-28 shows a Flash 3D Bar Chart based on the same query used for the HTML chart created in the previous section. This chart was no more difficult to create than the HTML version, but it is much more visually pleasing (although it's not as impressive in gray-scale). Adding one of the many animation features makes viewing reports almost fun (if that's possible).

The main consideration for using Flash reports is the requirement to install (or have) the Flash browser plug-in. Many corporations are becoming less open to installing any new software at the request of an application—especially automated installs as is the case with the Flash plug-in.

Flash 3D horizontal bar chart

Figure 6.28. Flash 3D horizontal bar chart

Flash Maps

Another interesting new addition to Flash Charts is the Mapping feature, which allows you to use maps in your reports to display location-based information. You can display only those maps that are delivered with APEX 4.0 (there are more than 100). The lowest level of detail on each map is considered a "region." In the case of a map of Texas, available regions are counties and 3-digit zip codes. You can't drill down to a specific city with this level of detail. The page shown in Figure 6-29 contains a map of Texas with two counties, Tarrant and Dallas, highlighted. To make this work, the county names had to be known as they are stored in the map as the regions.

Flash Map using the state of Texas

Figure 6.29. Flash Map using the state of Texas

Notice that when the mouse is hovered over Dallas County, the population value is shown. The query used to add this functionality is below:

select null as link, region Label, value Population
from
(
select 'Dallas' region, 1500000 as value from dual
UNION ALL
select 'Tarrant' region, 600000 as value from dual
)

As stated previously, the name of the county is synonymous with the region. When the county name is selected along with a value (population was used in this example), both are displayed upon hovering the mouse over the region.

Chart Localization

You can take advantage of CSS styling to display Flash charts in other languages. For example, the Application Express User's Guide suggests that to display the text in your chart in the Korean language, you can modify the CSS definitions for the text and tspan classes as follows:

text{font-family:Batang;fill:#000000;}
tspan{font-family:Batang;fill:#000000;}

Note that the charts are not automatically translated. You are simply modifying the font-family directive to use the correct font type, which can display the text in the correct format. It is still your responsibility to make sure the actual text is correct.

Asynchronous Updates

Flash charts provide a very handy feature called Asynchronous Updates, which automatically refreshes the chart at a specified interval and displays any changes in the underlying data. The Asynchronous Updates feature can be very useful for dashboard-type applications, where end users want to see constantly updated feedback about particular statistics without having to click a refresh button or resubmit the page.

To enable Asynchronous Updates, change the Asynchronous Updates setting to Yes and specify an Update Interval in seconds in the Refresh section of the Chart Attributes section.

If you use the Asynchronous Updates feature, be aware that any animation features you've used will also be executed as part of the refresh process.

Multiple Series

Using multiple series in your charts is no more difficult than using single series, although you need to take a bit of care that the data values you represent on the chart work well with each other. In other words, if you try to display multiple series in the same chart when there is no obvious correlation between the data in each series, you may end up making the chart more difficult to read.

Suppose you wish to modify the sample chart so that instead of just showing the number of bugs reported by people, it also shows the number of bugs assigned to people. You can do this by using a chart type that supports multiple series. Remember that you can't just change the chart type, so you need to delete the chart and create a new one (of another type).

For example, create a new Chart region type and select the Cluster Bar, Horizontal option, which enables you to specify multiple series. However, because you need to have a common link between the two series, you need to rewrite the previous SQL as shown in Listing 6-19, so that the chart shows all the users and the bugs reported by them, rather than just listing the users that have bugs assigned.

Example 6-19. Modified Query to Show All Users, As Well As Bugs

select

  null as link,
  ur.username as label,
  (select count(*) from buglist bl
     where bl.reported_by = ur.username) as value
from user_repository ur

Note that this query is simplified a little by using NULL for the link, as well as by just using the username rather than concatenating the forename and surname. As before, you could provide a link to the same page, which generates a report showing the detail for a selected chart item.

If you look at the report attributes, you will see that this chart type has a Chart Series section, which allows you to add an extra series, as shown in Figure 6-30.

Adding an extra series to a chart

Figure 6.30. Adding an extra series to a chart

Add another series, but this time modify the query so that it returns the number of bugs assigned to people, as shown in Listing 6-20.

Example 6-20. Query to Group Bugs by the assigned_to Column

select

  null as link,
  assigned_to as label,
  count(assigned_to)
from buglist bl
group by assigned_to

You can now see both series displayed in the same chart, as shown in Figure 6-31 (the color difference between the series may be difficult to make out in the screenshot).

Using multiple series in a chart

Figure 6.31. Using multiple series in a chart

Flash Chart XML Customization

You can further customize the look of Flash charts by modifying the XML used to generate those charts. The Chart XML section of the Chart Attributes section contains code similar to Listing 6-21.

Example 6-21. XML Used by the Flash Chart

<?xml version = "1.0" encoding="utf-8" standalone = "yes"?>
<anychart>
  <settings>
    <animation enabled="false"/>
    <no_data show_waiting_animation="False">
      <label>
        <text></text>
        <font family="Verdana" bold="yes" size="10"/>
      </label>
    </no_data>
  </settings>
  <margin left="0" top="0" right="0" bottom="0" />
<charts>
    <chart plot_type="CategorizedBySeriesVertical" name="chart_5928617224245708">
      <chart_settings>
        <title text_align="Center" position="Top" >
          <text>Reported by</text>
          <font family="Tahoma" size="14" color="0x000000" />
        </title>
        <chart_background>
          <fill type="Solid" color="0xffffff" opacity="0" />
          <border enabled="false"/>
          <corners type="Square"/>
        </chart_background>
        <data_plot_background>

        </data_plot_background>
        <axes>
         <y_axis >
            <scale   mode="Stacked"    />
            <title enabled="false" />
            <labels enabled="true" position="Outside">
              <font family="Tahoma" size="10" color="0x000000" />
              <format><![CDATA[{%Value}
               {numDecimals:0,decimalSeparator:.,thousandsSeparator:,}]]>
             </format>
           </labels>
           <major_grid enabled="False"/>
           <minor_grid enabled="False"/>
          </y_axis>
          <x_axis>
            <scale  mode="Stacked"    />
            <title enabled="false"/>
            <labels enabled="true" position="Outside">
              <font family="Tahoma" size="10" color="0x000000" />
            <format>
              <![CDATA[{%Value}
              {numDecimals:0,decimalSeparator:.,thousandsSeparator:,}]]>
              </format>
</labels>
            <major_grid enabled="True" interlaced="false">
                <line color="Black" />
              </major_grid>
               <minor_grid enabled="True">
              </minor_grid>
          </x_axis>
        </axes>
      </chart_settings>
      <data_plot_settings enable_3d_mode="true" >
        <bar_series style="Default">
          <tooltip_settings enabled="true">
            <format><![CDATA[{%Name}{enabled:False} –
              {%Value}
              {numDecimals:0,decimalSeparator:.,thousandsSeparator:,}]]>
            </format>
            <font family="Tahoma" size="10" color="0x000000" />
              <position anchor="Float" valign="Top" padding="10" />
          </tooltip_settings>
          <label_settings enabled="true" mode="Outside" multi_line_align="Center">
            <format>
              <![CDATA[{%Value}
              {numDecimals:0,decimalSeparator:.,thousandsSeparator:,}]]>
           </format>
           <background enabled="false"/>
           <position anchor="Center" valign="Center" halign="Center"/>
           <font family="Arial" size="10" color="0x000000" />
          </label_settings>
          <bar_style>
          </bar_style>
          <marker_settings enabled="True" >
            <marker type="None" />
          </marker_settings>
        </bar_series>
      </data_plot_settings>
#DATA#
    </chart>
  </charts>

</anychart>

As you can see in Listing 6-21, you can modify many options in the XML to affect how the chart will look and operate. Before doing so, read the documentation about the various options (and definitely make a backup of the current XML). As noted earlier, the Flash charts are produced with a third-party component, the AnyChart Flash chart component, which Oracle has licensed to ship with the APEX product. For more information and documentation about the various options, you can consult the original component documentation at www.anychart.com.

Generic Charting

You may want to have your chart use different SQL depending on certain criteria. For example, you might want to modify the where clause in the query, depending on what the user has selected from a list. You can do this in a couple of ways.

Function to Return the SQL

Generic charting is quite simple. Rather than defining the SQL in the Series Query section for the chart, you can change the Query Source Type setting from SQL Query to Function Returning SQL Query, and then write a query that returns the text to use for the SQL query, as shown in Listing 6-22.

Example 6-22. Using a Function to Return the SQL

CREATE OR REPLACE FUNCTION generatequery(p_type IN VARCHAR2) RETURN VARCHAR2 IS v_sql
VARCHAR2(2000);

BEGIN
  v_sql := 'select id, name, salary from payroll where ';
  IF p_type IS NOT NULL THEN

    IF(p_type = 'DEPT') THE
      v_sql := v_sql || ' and dept_name = v(''P1_SEARCH'')';
      ELSIF(p_type = 'MANAGER') THEN
        v_sql := v_sql || ' and manager = v(''P1_SEARCH'')';
      END IF;

    END IF;

    RETURN v_sql;
  END;

This example passes in a parameter, p_type, to the function. This parameter is then used to determine whether to append an extra part to the where clause restriction, which compares the value of the P1_SEARCH page item against the dept_name column or the manager column.

Note that the v('ITEM') syntax is used in the function, rather than passing in the value of the search text and then concatenating it to the SQL like this:

v_sql := v_sql || ' and dept_name = ' || p_query_string;

Using this type of concatenation is extremely dangerous and makes your application susceptible to SQL-injection attacks. A malicious user could manipulate the value of the p_query_string parameter so that the string returned from the function contains some code you didn't anticipate (such as deleting from a table).

For performance reasons, you should use bind variable notation (:ITEM), rather than using the v('') function. We will come back to this issue of using bind variables in more detail in Chapter 14.

Pipelined Functions

Rather than using the Function Returning SQL Query option, an alternative way to achieve generic charting relies on a feature called pipelined functions. You know that the chart is expecting certain columns to be returned by the query, namely link, label, and value, like this:

select

  null as link, ename as label,  sal as value
from
  scott.emp
where
  deptno = :P101_DEPTNO

So you need a way of returning a link column, a label column, and a value column dynamically for each row of data. First, you need to create a type that will be used as the return type of your pipelined function; that is, this type will represent a single point on the chart:

create or replace type ty_chart_entry as object (

  link varchar2(60),
  label varchar2(60),
  value number
);

Next, create another type that is a collection of the ty_chart_entry type. This will effectively hold the table representing all the data points on the chart:

create or replace type tbl_chart_entry as table of ty_chart_entry;

Now you need to create the function itself, as shown in Listing 6-23.

Example 6-23. Pipelined Function Definition

create or replace package chart_pkg

as
    function generate(p_type in varchar2)
      return tbl_chart_entry PIPELINED;
end;
create or replace package body chart_pkg as
  function generate(p_type in varchar2)
    return tbl_chart_entry PIPELINED is
  begin
    if (p_type = 'E') then
      for rec in (select ename, sal from emp) loop
        pipe row (ty_chart_entry(null, rec.ename, rec.sal));
      end loop;
    end if;
    if (p_type = 'D') then
      for rec in (select d.dname as name,
                  (select sum(e.sal) from emp e
                     where e.deptno = d.deptno) as sal
                  from dept d) loop
        pipe row (ty_chart_entry(null, rec.name, rec.sal));
      end loop;
    end if;
    return;
  end;
end;

Essentially, this function performs two entirely different queries depending on whether you pass in a 'D' (to query the dept table) or an 'E' (to query the emp table).

SQL> select count(*) from table(chart_pkg.generate('E'));

COUNT(*)
--------
14
SQL> select count(*) from table(chart_pkg.generate('D'));
COUNT(*)
--------
4

You can now create a new chart region on a page and add a select list (with submit) that returns 'E' or 'D', and use the value of this page item in the query, as shown here:

select

  link, label, value
from
  table(chart_pkg.generate(:P14_CHARTTYPE))

If you now run the page, you should see the different data, depending on what was chosen from the select list, as shown in Figures 6-32 and 6-33.

Querying the emp table with generic charting

Figure 6.32. Querying the emp table with generic charting

Querying the dept table with generic charting

Figure 6.33. Querying the dept table with generic charting

You might find that you prefer the approach of using pipelined functions so that you don't risk producing dynamic SQL that could potentially be exploited via SQL injection. If there is a restricted set of queries that you want to enable the user to perform, using a pipelined function is an ideal way of providing that functionality.

Summary

This has been a long chapter, and it's long because APEX offers incredible power and flexibility in reporting and charting. Reporting gives you many options to use to effectively present data. You have control over page breaks, column formatting, and so on. You can even create reports in which each item links to pages on the Internet, or to other reports, forms, and so forth defined within APEX. For example, you could create a Buglist report in which clicking on a developer's name took you to a chart showing that developer's success rate in closing bugs on the first try.

Charting in APEX is a substantial topic in its own right, and we have only scratched the surface of what is available. While you can create basic, HTML-based charts, APEX also supports key industry standards such as SVG and Flash. Using those standards, you can provide elegant and highly effective charts and graphs to key decision-makers in your business. APEX's ability to link from reports to charts, combined with its support for industry standards makes APEX a highly effective reporting too

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset