This section covers the common programming tasks associated with the RAS SDK. Although the SDK provides many capabilities, some of the following tasks are common to most programming exercises and are central to the SDK.
Initiating a session with the RAS is the first step in programming with the RAS SDK. In this step, a specific RAS can be specified for use; otherwise, the system selects one from the RASs listed in the clientSDKOptions.xml file using a round-robin method. Initializing a RAS session by specifying a machine name at runtime is shown in the following code:
//Create a new Report Application Session ReportAppSession reportAppSession = new ReportAppSession(); //Create a Report Application Server Service reportAppSession.createService("com.crystaldecisions.sdk.occa.report.application .ReportClientDocument"); //Set the RAS server to be used for the service. You can also use "localhost" if the RAS server is running on your local machine. reportAppSession.setReportAppServer("MACHINE_NAME"); //Initialize RAS reportAppSession.initialize(); //Create the report client document object ReportClientDocument clientDoc = new ReportClientDocument(); //Set the RAS Server to be used for the Client Document clientDoc.setReportAppServer(reportAppSession.getReportAppServer() );
All ReportClientDocument objects created from the same ReportAppSession communicate with the same RAS.
A report can be opened first by creating a new ReportClientDocument object and specifying the ReportAppServer. Then the open method can be used to open a report. This method takes two parameters:
See the OpenReportOptions class for valid report options.
NOTE
Reports are loaded from the report folder found at Program FilesCrystal DecisionsReport Application Server 10Reportsby default.
The following code opens a report:
try { reportClientDocument.open("C:MyReportsGlobalSales.rpt", 0); } catch (ReportSDKException e) { // Handle the case where the report does not open properly.; }
The previous chapter explained how to view reports using RAS. Creating and modifying those reports using the RAS SDK will be the focus of the remainder of this chapter.
A report can be modified after creating and opening a ReportClientDocument by using the reports controllers. The only way to modify reports and ensure that the changes are synchronized with the server is to use controllers. Although the reports fields can be accessed directly through the DataDefinition property, any changes made will not be committed. This section explains how to add a field to a report.
A field is usually selected by name. The DatabaseController can be used to retrieve the object that represents this field given a database fields name or its tables name. Another method of accessing a tables fields is using the ReportClientDocuments Database property. Here you use the DatabaseController.
The DatabaseController contains a collection of database tables that are available to the report and might be accessed using the getDatabaseController method of ReportClientDocument. Each table contains a collection of DBField objects.
NOTE
All tables and fields that are listed by DatabaseController.getDatabase() are not retrieved when the report is refreshed; that is, they are available for report design but might not actually be part of the reports data definition.
A method called findFieldByName is shown in the following sample code snippet. This method returns a field given its fully qualified field name in the form:
IField findFieldByName(String nameOfFieldToFind, ReportClientDocument reportClientDocument) { //Extracts the field name and the table name. int dotPosition = nameOfFieldToFind.indexOf("."); String tablePartName = nameOfFieldToFind.substring(0, dotPosition); String fieldPartName = nameOfFieldToFind.substring(dotPosition + 1, nameOfFieldToFind.length()); ITable table = null; // Uses the DatabaseController to search for the field. try { Tables retreivedTables = reportClientDocument.getDatabaseController().getDatabase().getTables(); int tableIndex = retreivedTables.findByAlias(tablePartName); table = retreivedTables.getTable(tableIndex); } catch (ReportSDKException e) { return null; } // Finds the field in the table. int fieldIndex = table.getDataFields().find(fieldPartName, FieldDisplayNameType.fieldName, Locale .ENGLISH); if (fieldIndex == -1) { return null; } IField field = table.getDataFields().getField(fieldIndex); return field; }
This method uses the following key methods:
After you obtain the Field object that you want to add, the field can be added to the report so that it is processed and displayed when the report is run. This is done via the DataDefController, which is used to modify the reports data definition and contains a subcontroller called the ResultFieldController. This subcontroller is used for modifying fields that have been placed on the report and that are processed at runtime. The fields that are shown on the report belong to the ResultFields collection. A new database field will be added to the ResultFields collection in this step.
NOTE
The ResultFields collection can contain other types of field objects such as parameter fields, formula fields, and summary fields in addition to DBField objects. Like DBFields, the ResultFieldController can add these fields to a report. Unlike DBFields, only the DatabaseDefControllers DataDefinition property, and not the DatabaseDefControllers Database property, can retrieve these fields.
A field being added to the ResultFields collection is shown by the following code:
/* * Because all modifications to a report must be made with a controller, * the resulting field controller is used to add and remove each field. */ ResultFieldController resultFieldController = reportClientDocument.getDataDefController().getResultFieldController(); // Adds fieldToAdd. -1 indicates the end of the collection. resultFieldController.add(-1, fieldToAdd);
The parameter -1 indicates that the field is to be placed at the end of the collection. As a result of this code, the new field displays on the report and is processed when the report is refreshed.
The fields that have been added to a report are stored in the ResultFields collection and can be retrieved using the following sample method:
Fields getUsedDatabaseFields(ReportClientDocument reportClientDocument) { Fields usedFields = new Fields(); /* * The DataDefinitions ResultFields collection * contains all the fields that have been placed * on the report and which will be processed * when the report is refreshed. */ Fields resultFields = null; try { resultFields = reportClientDocument.getDataDefinition().getResultFields(); } catch (ReportSDKException e) { return null; } /* * Because the ResultFields collection contains * many different kinds of fields, all fields except * for database fields are filtered out. */ for (int i = 0; i < resultFields.size() - 1; i++) { if (resultFields.getField(i).getKind() == FieldKind.DBField) { // Adds the database field to the collection. usedFields.addElement(resultFields.getField(i)); } } return usedFields; }
With the full name of the field, you can use a DatabaseController to retrieve the DBField object.
When youve found the field you want to remove, use the ResultFieldController to remove it as follows:
// Removes fieldToDelete. resultFieldController.remove(fieldToDelete);
In this code, fieldToDelete is a DBField object. After the field is removed from the result fields using this method, the report ceases to display the field.
A new report can be created by first creating an empty ReportClientDocument as shown:
ReportClientDocument reportClientDocument = reportAppFactory.newDocument(Locale.ENGLISH);
NOTE
Because the newDocument method of ReportClientDocument is provided for deployments that use an unmanaged RAS to access report (.rpt) files, it should not be deployed when using a Crystal Enterprise RAS. Instead, when deploying with Crystal Enterprise, the IReportAppFactory.newDocument method should be used as in the previous code.
Because the report is not actually created until tables are added, after creating an empty ReportClientDocument, details such as the new reports tables and the fields used to link them should be added.
However, before adding the tables to the new report, the table objects must first be retrieved from the source report. This can be accomplished in two ways: using the DatabaseController object and using the Database object, both of which are available from the ReportClientDocument object. The ensuing code iterates through all the tables in an open report and prints the tables aliases:
Tables tables = reportClientDocument.getDatabase().getTables() for (int i = 0; i < tables.size(); i++) { ITable table = tables.getTable(i); out.println(table.getAlias()); }
Because controllers are the only objects that can modify the reports object model, a controller must be used to add tables to a report. The following code retrieves the reports DatabaseController and adds a table.
DatabaseController databaseController; try { databaseController = reportClientDocument.getDatabaseController(); databaseController.addTable(sourceTable, new TableLinks()); databaseController.addTable(targetTable, new TableLinks()); } catch(ReportSDKException e) { throw new Exception("Error while adding tables."); }
The addTable method of the DatabaseController adds a table to the report. The addTable method takes two parameters:
Tables must be linked after they have been added to the report. To link two tables, first create a new TableLink object, set the properties of the TableLink, and then add the TableLink to the report definition.
Linking two tables using an equal join is illustrated by the following code:
// Create the new link that will connect the two tables. TableLink tableLink = new TableLink(); /* * Add the source field name and the target field name to the SourceFieldNames * and TargetFieldNames collection of the TableLink object. */ tableLink.getSourceFieldNames().add(sourceFieldName); tableLink.getTargetFieldNames().add(targetFieldName); /* * Specify which tables are to be linked by setting table aliases * for the TableLink object. */ tableLink.setSourceTableAlias(sourceTable.getAlias()); tableLink.setTargetTableAlias(targetTable.getAlias()); // Add the link to the report. Doing so effectively links the two tables. try { databaseController.addTableLink(tableLink); } catch(ReportSDKException e) { throw new TutorialException("Error while linking tables."); }
These newly linked tables can be used as the reports data source. However there have been no visible objects added to the report, so when the report is refreshed, it will be blank.
To add a group, you must know which field is being grouped on. For information on working with fields, see the "Adding a Field to the Report Document" section earlier in this chapter. Because not all fields can be used to define a group, use the canGroupOn method of GroupController to check whether a field can be used for grouping. If canGroupOn returns true, the field is an acceptable field to use for grouping. The next example demonstrates a function that adds a new group to a report:
// Uses the sort controller to remove all the reports sorts. Sorts sorts = dataDefController.getDataDefinition().getSorts(); SortController sortController = dataDefController.getSortController(); for (int i = 0; i < sorts.size(); i++) { sortController.remove(0); }
Here the group was added to the end of the Groups collection by setting the index to -1, which means that the new group becomes the innermost group. When a new group is added, a new sorting definition is also added which will sort the records according to the groups condition field and group options. An additional reflection of adding the new group is the group name field appearing on the groups header. Fields added to the group header are not added to the ResultFields collection. When the group is removed, the group name field is also removed.
Using the SortController adds a new sorting definition to a report. The SortController can add any kind of sorting definition, including a Top N sort. Adding a Top N sort requires that a summary has first been added.
Next you demonstrate how to add a sort to the report by taking a Fields collection and adding a sorting definition based on each field in the collection:
void addNewGroup(ReportClientDocument reportClientDocument, Fields newGroupFields) throws ExampleException { try { // Create a new, empty group. IGroup group = new Group(); // Iterate through every field in the given Fields collection. for (int i = 0; i < newGroupFields.size(); i++) { IField field = newGroupFields.getField(i); // Set the field that will define how data is grouped. group.setConditionField(field); GroupController groupController = reportClientDocument.getDataDefController().getGroupController(); groupController.add(-1, group); } } // If any part of the above procedure failed, redirect the user to an error page. catch (ReportSDKException e) { throw new ExampleException("Error while adding new groups."); } }
When the new Sort object is added, it is added to the end of the collection, indicated by the -1 argument, which designates that the records will be sorted on this field after all other sorting definitions. The SortDirection class indicates the direction of the sort. The static objects SortDirection.ascendingOrder and SortDirection.descendingOrder are the only values that can be used for a normal sort. The other values are used for a Top N or Bottom N sort. See Adding a Top N sorting definition in the SDK documentation for additional details.
The SummaryFieldController adds a new summary field. To determine if a field can produce a summary, the SummaryFieldControllers method canSummarizeOn is called. Here you add a summary to a group:
void setSorting(ReportClientDocument reportClientDocument, Fields fieldsToSortOn) throws ExampleException { try { DataDefController dataDefController = reportClientDocument.getDataDefController(); // Create a new Sort object ISort sort = new Sort(); // Iterate through the fields for (int i = 0; i < fieldsToSortOn.size(); i++) { IField field = fieldsToSortOn.getField(i); // Add the current field to the result fields. dataDefController.getResultFieldController().add(-1, field); // Set the field to sort on. sort.setSortField(field); // Define the type of sorting. Ascending here. sort.setDirection(SortDirection.ascendingOrder); //Get Sort Controller. SortController sortController = dataDefController.getSortController(); sortController.add(-1, sort); } } // If any part of the above procedure failed, redirect the user to an error page. catch (ReportSDKException e) { throw new TutorialException("Error while setting sort."); } }
After creating a summary field, set the following properties before adding it:
Filters are used in record selection and group selection. The filter is initially a string written in Crystal formula syntax. The record selection formula is then parsed into an array of FilterItems stored in the Filter objects FilterItems property. The string is broken up into data components and operator components that act on the data. These components are stored as FieldRangeFilterItem and OperatorFilterItem objects respectively, which are stored in the FilterItems collection in the same order that they appear in the formula string. Re-ordering the objects in the array changes the functionality of the formula. In summary, the FieldRangeFilterItem is an expression that is joined with other expressions using an OperatorFilterItem.
For instance, consider a simple record selection formula such as
{Customer.Name} = "Bashka Futbol" and {Customer.Country} = "USA".
This results in only the records that have a name equal to "Bashka Futbol" and a country of the USA. The result is stored in the FreeEditingText property. After this string is parsed, the FieldRangeItems collection contains two FieldRangeFilterItem objects because there are two data items used to filter the records. The OperatorFilterItem is used to indicate how two primitive expressions are combined, so it is now equal to and.
The FieldRangeFilterItem contains three properties:
After the file is opened, and the filters parsed, the FreeEditingText property that stores these strings is cleared and the FilterItems populated. Conversely, if the formula is too complex, the FilterItems collection property remains empty and the FreeEditingText property populated. When altering a filter, you have two options: modify the FreeEditingText property or the FilterItems property.
If you use only one property to modify the filter, the other will not be automatically updated, however. For instance, you would modify the FreeEditingText property, but this will not necessarily be parsed again to repopulate the FilterItems. You should use only one of these properties per session.
Use a controller to ensure that modifications are saved. The GroupFilterController and the RecordFilterController modify the group formula and record formula respectively.
A FieldRangeFilterItem contains a primitive comparison expression. Its most relevant properties are
The Operation and RangeField properties usually contain a constant. However, the Values property stores either ConstantValue objects, which don need evaluation (such as 1, 5, or "Stringiethingie"), and ExpressionValue objects, which do need evaluation (such as "WeekToDateSinceSun," 4/2, and so on).
The following section of code defines the expression {Customer.ID > 2}. Note how it creates a new ConstantValue object for the number 2 and adds it to the Values collection:
// Create a new range filter item. FieldRangeFilterItem fieldRangeFilterItem = new FieldRangeFilterItem(); // Assume the customerDBField has been retrieved from a table fieldRangeFilterItem.setRangeField(customerDBField); // Set the operation to > fieldRangeFilterItem.setOperation(SelectionOperation.greaterThan); fieldRangeFilterItem.setInclusive(false); // Create a constant value and add it to the range filter item ConstantValue constantValue = new ConstantValue(); constantValue.setValue(2); fieldRangeFilterItem.getValues().addElement(constantValue); // Create a filter and add the field range filter item IFilter filter = new Filter(); filter.getFilterItems().addElement(fieldRangeFilterItem);
All fields cannot be used in a filter formula (for example, you can use BLOB fields). Use the canFilterOn method, which is located in either the RecordFilterController or the GroupFilterController, to verify that a field can be filtered on. You must also verify that the constant data type is the same as the field. In the previous example, constantValue must not be a variant and corresponds to the data type used in the comparison.
The following example assumes the same expression as defined in the preceding example, but concatenates to the filter using the OR operator. Assume the filter would look like this: {Customer.ID} > 2 OR {Customer.name} = "Arsel". To the code above you would add:
OperatorFilterItem operatorFilterItem = new OperatorFilterItem(); operatorFilterItem.setOperator("OR"); filter.getFilterItems().addElement(operatorFilterItem); filter.getFilterItems().addElement(fieldRangeFilterItem);
The filterItems parameter is a FilterItems collection. It stores FilterItem objects. In the two examples, both a FieldRangeFilterItem object and an OperatorFilterItem object were added to this collection. Both of these objects inherit from FilterItem, making this possible.
After defining the filter, you add it to the report. Filters can be used in two places: group selection and record selection. The GroupFilterController and RecordFilterController, which can be accessed via the DataDefController object, modify their respective filters.
You can also obtain the filters from the GroupFilter and RecordFilter properties in the DataDefinition, although they can only be modified with a controller.
FilterController provides these methods for modifying a filter:
In the following code, the modify method is used because a new filter has already been defined. Assume that there is a ReportClientDocument object and that you have opened a report already:
FilterController groupFilterController = reportClientDocument.getDataDefController.getGroupFilterController(); groupFilterController.modify(filter) ;
Parameters enable end users to enter information to define the report behavior. Parameters have specific data types just like any other field: string, number, date, and so on. Parameters also are divided into two basic types: discrete and ranged. A discrete parameter value is one that represents a singular value such as 9, "Nur", 1863, True, and so on. Ranged values represent a particular span of values from one point to another such as [9..95], [4..6], ["Alpha","Omega"]. The lower bound value of the range must be smaller than the upper bound. Some parameters support more than one value: They effectively contain an array containing many values.
Parameters have default values and the user can be forced to select from them. You can also provide default parameters but allow users to enter their own values. Default values are stored in the ParameterField.DefaultValues property. Selected values are stored in the ParameterField.CurrentValues property.
Parameters support many more features than those covered here. For a complete list of features, see the ParameterField class in the SDK documentation.
The parameters are exposed in the SDK by the DataDefinitions ParameterFields class. The ParameterFields class inherits from the Fields class. For example the name of a parameter is obtained using this method:
Fields parameterFields = reportClientDocument.getDataDefinition().getParameterFields(); ParameterField parameterField = (ParameterField)parameterFields.getField(0); parameterField.getDisplayName(FieldDisplayNameType.fieldName, Locale.ENGLISH);
The getDisplayName method is used for UI purposes and so is not a unique identifier. The getFormulaForm method can be used to retrieve a unique identifier. getDisplayName and getFormulaForm are not documented under the ParameterField class because they are inherited from Field.
Because parameter values might be either discrete or ranged, and default values might only be discrete, there are two different objects to represent these: ParameterFieldDiscreteValue and ParameterFieldRangeValue. Both of these objects inherit from ParameterField. You must understand the type of the parameter to know what kind of parameter values it contains. For example, the following code determines if the parameter is of a ranged or discrete type:
// Check to see if the value is range or discrete IValue firstValue = parameterField.getCurrentValues().getValue(0); if (firstValue instanceof IParameterFieldRangeValue) { IParameterFieldRangeValue rangeValue = (IParameterFieldRangeValue)firstValue; toValueText = rangeValue.getEndValue().toString(); fromValueText = rangeValue.getEndValue().toString(); } else { IParameterFieldDiscreteValue discreteValue = (IParameterFieldDiscreteValue)firstValue; discreteValueText = discreteValue.getValue().toString(); }
Check the parameters type before you try to print the parameters values. You must determine the type so you can retrieve the correct field. Trying to access the EndValue of a discrete value will cause a runtime error because no EndValue exists. The previous code example determines whether the parameter value is an instance of IParameterFieldRangeValue to determine what kind of values it will have. For parameters that support both discrete and ranged values, however, you must verify the type of the parameter by using getValueRangeKind method. The following code checks the parameter type and calls a secondary function to handle the correct type and build a table of parameters:
ParameterValueRangeKind kinda = parameterField.getValueRangeKind(); if (kinda == ParameterValueRangeKind.discrete) { table += createDiscreteParameterTableData(parameterField, key); key += 1; } else if (kinda == ParameterValueRangeKind.range) { table += createRangeParameterTableData(parameterField, key); key += 2; } else if (kinda == ParameterValueRangeKind.discreteAndRange) { table += createDiscreteRangeParameterTableData(parameterField, key); key += 3; } else { table += "Parameter kind not known "; }
The ParameterFieldController, which can be found in the DataDefController, enables you to change parameters. To modify a parameter field in the report, you copy the field, modify the copy, and then have the controller modify the original based on changes made to the copy. For instance here you demonstrate this by changing a default discrete value:
ParameterField newParamField = new ParameterField(); parameterField.copyTo(newParamField, true); newParamField.getCurrentValues().removeAllElements(); // Check the type of the parameter ParameterValueRangeKind kinda = parameterField.getValueRangeKind(); // If it is discrete if (kinda == ParameterValueRangeKind.discrete) { // Get the parameters value String textFieldText = request.getParameter("textField" + key); // Convert this value to the right format String discreteValueText = (String)convertToValidValue(newParamField, textFieldText); // Modify the copy of the parameter field with the value above. ParameterFieldDiscreteValue discreteValue = new ParameterFieldDiscreteValue(); discreteValue.setValue(discreteValueText); newParamField.getCurrentValues().add(discreteValue); key += 1; }
Use the ParameterFieldController to add new parameters. You do this the same way as adding any other fields to the report: A new field is created, its fields are set, and it is added using a controller. Here you define a new, discrete, string parameter and add it using the controller:
IParameterField paramField = new ParameterField(); paramField.setAllowCustomCurrentValues(false); paramField.setAllowMultiValue(false); paramField.setAllowNullValue(false); paramField.setDescription("Here we go dude!"); paramField.setParameterType(ParameterFieldType.queryParameter); paramField.setValueRangeKind(ParameterValueRangeKind.discrete); paramField.setType(FieldValueType.numberField); paramField.setName("YourNewParameter"); reportClientDoc.getDataDefController().getParameterFieldController().add(parameterField);
Adding a parameter using the Parameter field controller does not place the parameter on the report, so the user is not prompted for the parameter when the report is refreshed. To prompt the user, either use it in a filter, or add it by using the ResultFieldController.
Handling parameters involves many important details. When using parameters keep the following points in mind:
Failing these tests results in a runtime error.
The ChartObject, which represents a chart in the RAS SDK, inherits variables and methods from the ReportObject. Remember that the report that you open is represented by the ReportClientDocument, not the ReportObject.
The ChartObjects properties determine the charts appearance and where it shows on the report.
Here you focus on three ChartObject properties:
The following two sections show how you can use these ChartObject properties to create a chart. You must first specify the fields on which you want your chart to be based on. To do this, create a ChartDefinition object, which will then be added to the ChartObject with the ChartDefinition property.
The ChartDefinition object determines the type of chart that appears in the report and sets the fields to be displayed. A simple, two-dimensional chart displays two types of fields:
Below the chart added is a Group type (see the ChartType class), so the ConditionFields and DataFields that are being charted on are group fields and summary fields respectively.
Add the first group field in the Groups collection to a Fields collection. This field is retrieved with the ChartDefinitions getConditionFields method.
ReportClientDocuments DataDefinition: // Create a new ChartDefinition and set its type to Group ChartDefinition chartDef = new ChartDefinition(); chartDef.setChartType(ChartType.group); Fields conditionFields = new Fields(); if (!dataDefinition.getGroups().isEmpty()) { IField field = dataDefinition.getGroups().getGroup(0).getConditionField(); conditionFields.addElement(field); } chartDef.setConditionFields(conditionFields);
NOTE
Adding two groups as ConditionFields enables you to create a 3D chart. Because one value is required for the x values, the next value drives the z-axis.
After you have added ConditionFields to the ChartDefinition, add the DataFields. In a Group type chart, the DataFields are summaries for the group fields that you added as ConditionFields.
Adding DataFields is similar to how you added ConditionFields. For example, you use the name of the summary field that the user has selected to locate the desired field in the SummaryFields collection and add this field to a Fields collection. You then accessed the summary field with the ChartDefinitions DataFields property:
Fields dataFields = new Fields(); for (int i = 0; i < dataDefinition.getSummaryFields().size(); i++) { IField summaryField = dataDefinition.getSummaryFields().getField(i); if (summaryField.getLongName(Locale.ENGLISH).equals(summaryFieldName)) { dataFields.addElement(summaryField); } } chartDef.setDataFields(dataFields);
Here you use the LongName of the summary field. The LongName contains the type of summary, for example, a sum or a count, and the group field that it applies to. For example
Sum of (Customer.Last Years Sales, Customer.Country)
In general you will want to use a fields LongName instead of its ShortName or Name to avoid confusion as the ShortName or Name might be the same for several fields.
After the fields are defined, they are added to the ChartObject with the ChartDefinition property. The following code uses the ChartObjects ChartStyle property and ChartReportArea to specify the chart style type, the chart title, and the location of the chart:
ChartObject chartObject = new ChartObject(); chartObject.setChartDefinition(chartDefinition); String chartTypeString = request.getParameter("type"); String chartPlacementString = request.getParameter("placement"); String chartTitle = request.getParameter("title"); if (chartTitle.equals("")) { chartTitle = "no title at all!"; } ChartStyleType chartStyleType = ChartStyleType.from_string(chartTypeString); AreaSectionKind chartPlacement = AreaSectionKind.from_string(chartPlacementString); // Set the chart type, chart placement, and chart title chartObject.getChartStyle().setType(chartStyleType); chartObject.setChartReportArea(chartPlacement); chartObject.getChartStyle().getTextOptions().setTitle(chartTitle); // Set the width, height, and top chartObject.setHeight(5000); chartObject.setWidth(5000); chartObject.setTop(1000);
In this example, the first chart that you add will appear 50 points below the top of the report area in which the chart is located. (These fields are measured in twips, and 20 twips = 1 font point, so 1000/20 = 50 points.) Adding another chart to the same report area places it over the first chart because the formatting for report objects is absolute. The first chart remains hidden until the second chart is removed.
Now add the chart using the ReportObjectControllers add method. This method takes three parameters: the ChartObject, the section to place it in, and the position in the ReportObjectController collection where you want to add the chart. An option of 1 for the index adds the chart to the end of the array. Return the ReportObjectController by the ReportDefControllers getReportObjectController method:
reportDefController.getReportObjectController().add(chartObject, chartSection, 1);
NOTE
If you want to modify an existing chart, you can use the clone method to copy the chart, make the desired changes, and then call the modifyObject method using the original chart and the newly modified chart as parameters.
Part I. Crystal Reports Design
Creating and Designing Basic Reports
Selecting and Grouping Data
Filtering, Sorting, and Summarizing Data
Understanding and Implementing Formulas
Implementing Parameters for Dynamic Reporting
Part II. Formatting Crystal Reports
Fundamentals of Report Formatting
Working with Report Sections
Visualizing Your Data with Charts and Maps
Custom Formatting Techniques
Part III. Advanced Crystal Reports Design
Using Cross-Tabs for Summarized Reporting
Using Record Selections and Alerts for Interactive Reporting
Using Subreports and Multi-Pass Reporting
Using Formulas and Custom Functions
Designing Effective Report Templates
Additional Data Sources for Crystal Reports
Multidimensional Reporting Against OLAP Data with Crystal Reports
Part IV. Enterprise Report Design Analytic, Web-based, and Excel Report Design
Introduction to Crystal Repository
Crystal Reports Semantic Layer Business Views
Creating Crystal Analysis Reports
Advanced Crystal Analysis Report Design
Ad-Hoc Application and Excel Plug-in for Ad-Hoc and Analytic Reporting
Part V. Web Report Distribution Using Crystal Enterprise
Introduction to Crystal Enterprise
Using Crystal Enterprise with Web Desktop
Crystal Enterprise Architecture
Planning Considerations When Deploying Crystal Enterprise
Deploying Crystal Enterprise in a Complex Network Environment
Administering and Configuring Crystal Enterprise
Part VI. Customized Report Distribution Using Crystal Reports Components
Java Reporting Components
Crystal Reports .NET Components
COM Reporting Components
Part VII. Customized Report Distribution Using Crystal Enterprise Embedded Edition
Introduction to Crystal Enterprise Embedded Edition
Crystal Enterprise Viewing Reports
Crystal Enterprise Embedded Report Modification and Creation
Part VIII. Customized Report Distribution Using Crystal Enterprise Professional
Introduction to the Crystal Enterprise Professional Object Model
Creating Enterprise Reports Applications with Crystal Enterprise Part I
Creating Enterprise Reporting Applications with Crystal Enterprise Part II
Appendix A. Using Sql Queries In Crystal Reports
Creating Enterprise Reporting Applications with Crystal Enterprise Part II