ADF Applications – Download DVT Charts in ADF 12c (and other technologies)

Hi all and welcome to a new article on Red Mavericks!

As we mentioned in past articles ADF 12.2.1 comes with several corrections of customer-reported and internal (unpublished) errors, along with new features that can be useful when developing applications. Recently we worked with ADF Visualization Components and we faced a big problem: there is no easy way to export a rendered DVT chart!
After doing some research in Oracle community and blogs we found that for ADF 11 there was one way: link

Unfortunately for ADF 12c the DVT class hierarchy was changed, and the previous method could not be used anymore. We did not find a way of using ADF classes to export the charts. The only suggestion was to use the <af:printablePageBehavior> to print the page. However, this will not fulfill the client’s needs so we had to try other ways.

With all these facts in consideration, we chose using JavaScript in order to export the charts generated by ADF. To clarify any misleading concepts, ADF charts are rendered as Scalable Vector Graphics (SVG).

This article will present a generic way to export and download the Scalable Vector Graphics as an image using JavaScript. In other words, the method presented can be used with ADF but also with other Application Development.

JavaScript libraries

In order to implement this solution the following JS libraries are required:

We tried some JS libraries (dom-to-image and html2canvas) to help us export the ADF charts and faced several problems mainly because of browsers incompatibilities. The canvg.js was the one that was used to put the pieces together in order to generate the chart image exportation (the RGBColor.js and StackBlur.js are dependencies of the canvg.js). You can find more information about these libraries here.

Briefly the steps to export the DVT chart are:

  1. Get the SVG component
  2. Convert the component into a canvas element
  3. Then export as an image

Importing JavaScript libraries into JSF

To include the JS scripts into your ADF application, copy the JS files to your application, for example:

Then insert the following lines into your main .JSF file:

<af:resource type="javascript" source="/resources/js/exportCharts/RGBColor.js"/>
<af:resource type="javascript" source="/resources/js/exportCharts/StackBlur.js"/>
<af:resource type="javascript" source="/resources/js/exportCharts/canvg.js"/>

Change the directory where you inserted the JS scripts. In this case was in /resources/js/exportCharts.
Be careful that the JS import order is important! The RGBColor and StackBlur must be imported before canvg!

Drawing the ADF DVT chart

For this article we will focus on dvt:barChart example. However, the solution here presented is tested with dvt:barChar, dvt:pieChart and dvt:lineChart (with “polar” and “Cartesian” coordinateSystem).

<af:panelGroupLayout id="pgGroupChart" 
                        clientComponent="true"
                        layout="vertical">
     <dvt:barChart orientation="vertical" 
                      id="barChart1"
                      var="row" 
                      value="#{bindings.EmployeesVO.collectionModel}
                      styleClass="AFStretchWidth">
                        <dvt:chartLegend rendered="true" id="cl1"/>
                         <f:facet name="dataStamp">
                            <dvt:chartDataItem id="di1" 
                                                  series="#{row. series}" 
                                                  group="#{row.group}" 
                                                  value="#{row.value}" />
                        </f:facet>
     </dvt:barChart>
</af:panelGroupLayout>

Note that we surrounded the ADF DVT with a panelGroupLayout in order to get this element for the exportation. Getting this element via JavaScript is important to set the clientComponent property to true.

Obtaining ADF Component ID for JavaScript

In run time, ADF generates a unique ID for every html component based on html hierarchy and the given component id. It is difficult to know the component id in runtime (also known as client component id), because sometimes, for example, when we reuse page fragments in the same ADF page, it auto generates the ids to make them distinguishable across different instances of the page fragments.

With this in mind, we can not simply get the element with the fixed ID in JavaScript. Instead, we need to provide the component client ID as an input parameter to the JavaScript.
For this you need to:

    1. Add the following methods to your Manage bean:
RichPanelGroupLayout chartGroup;

public RichPanelGroupLayout getChartGroup(){      
  if(chartGroup == null)
          chartGroup = (RichPanelGroupLayout)AdfUtils.findComponent("pgGroupChart");

  return chartGroup;
}
    
public String getClientChartGroupId(){
      FacesContext ctx = FacesContext.getCurrentInstance();
      return this.getChartGroup().getClientId(ctx);
}

This code allows us to get the client id from the panelGroupLayout that surrounds the ADF DVT we want to export.

    1. In jsf/jsff page
<af:button text="EXPORT_LABEL"
                  partialSubmit="true" 
     clientComponent="true"
     icon="/resources/images/submit.png" 
     id="btExport">
   <af:clientListener method="exportRadialChart" type="action"/>
   <af:clientAttribute name="componentClientId"value="#{attrs.manageBean.clientChartGroupId}"/>
</af:button>

Then we add a button that we will be used to export the chart in the jsf/jsff page. Notice that the <af:clientListener> contains the JavaScript method name, which will be called when the user activates the export button. Another important property to take into consideration is the <af:clientAttribute> that allows passing the panelGroupLayout client id to the JavaScript code. In the next session, we will explain how to access this attribute in JS.

    1. Javascript Code:
<af:resource type="javascript">
function exportChart(actionEvent){
  var actionComp = actionEvent.getSource();
  var componentClientId = actionComp.getProperty("componentClientId"); 
  var b1ComponentRef = document.getElementById(componentClientId);

Next, add the JS script to your page. Here you can see the function body named exportChart that will receive the actionEvent. The actionEvent contains the <af:clientAttibute> previously stated on the HTML code and the line 3 and 4 shows how to get its value. Also, take into consideration that the String componentClientId that is passed as a parameter to the getProperty function must be the same string as the name attribute in the <af:clientAttribute>.

The last line of code is used to get the element in the HTML page using the given client id parameter.

For more information about this read the “Pattern For Obtaining ADF Component ID for JavaScript” – link.

Using convg library

In order to use convg we need to:

    1. Get the SVG we want to export
var svgElements = b1ComponentRef.getElementsByTagName("svg");

//In our case we only have one SVG inside this panelGroupLayout      
if(svgElements != null & svgElements.length > 0) {
       var currentSVG = svgElements[0];                    
  1. Convert the SVG HTML element into an XML string
    1. Replace the SVG namespace if it exists – some browsers show an error when using canvg with this namespace
    2. IMPORTANT: The SVG width and height cannot be in percentage values. If this occurs you need to remove it from your HTML or replace them with the fixed values. Without this change, there will be problems with IE and some dvt charts like < dvt:lineChart&gt; with “coordinateSystem=polar”
           //convert SVG into a XML string
           var xml = (new XMLSerializer()).serializeToString(currentSVG);
                         
           // Removing the name space as IE throws an error
           xml = xml.replace(/xmlns=\"http:\/\/www\.w3\.org\/2000\/svg\"/, '');
                        
           //Replace width and height %
           xml = xml.replace(width="100%", '1350');
           xml = xml.replace(height="100%", '300');         
    
  2. Create a temporary variable with a new canvas element
  3.        var canvas = document.createElement("canvas");         
    
  4. Call canvg function with your temporary canvas and the XML containing your SVG element. Note that you can see any error throw in canvg library if you remove the comment of the alert messages in the catch block. This helped us find and debug errors that occurred before. In alternative, you can write the errors to the console
  5.        //draw the SVG onto a canvas
           try {
                canvg(canvas, xml);
           }
           catch(err) {
                //debug
                //alert(err.message);
                //alert(err.stack);
           }  
    

Download chart image

The last step is to download the generated image. For this, the code for IE is different from the Chrome and Firefox browsers. Also, Firefox requires some parameters that in Chrome are set by default.

The code here presented was tested for these 3 browsers.

     if (canvas.msToBlob) { //for IE
         var blob = canvas.msToBlob();
         window.navigator.msSaveBlob(blob, 'content.png');
                        
      } else {
         var dataUrl = canvas.toDataURL("image/png");
         var donwloadImage = dataUrl.replace(/^data:image\/[^;]*/, 'data:application/octet-stream');
                        
         var link = document.createElement('a');
         document.body.appendChild(link); //required in FF, optional for Chrome
         link.setAttribute('download', 'content.png');
         link.setAttribute('href', donwloadImage);
         link.target="_self" ; //required in FF, optional for Chrome
         link.click(); 
      }
};

Conclusion

The solution here presented is based on JS code and ADF compatibility. There is no easy way to export a feature on ADF 12c and we showed our solution that fulfilled our client’s needs. In order to export ADF dvt charts we used a JavaScript library called canvg because, after several tests with JS libraries, we found that canvg provided the best compatibility between browsers.

Take into consideration that the JS code can be used for other web technologies, not only for ADF applications. I hope this post helps you and speeds up any problematic situation you could face in this process.

Keep checking out Red Mavericks for additional tips on Oracle Middleware technology.

Cheers,
Pedro Curto

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *