Thursday, April 20, 2017

Save Visualforce Chart as Image in Attachment

The issue that brought me here is a user wants to render chart as PDF as we all know that Visualforce Chart is not renderable into PDF. Visualforce Chart is renderd by Javascript that is why it is not PDF renderable.

Please refer here for limitations:



In order to bypass those limitation, we need to save chart as image and after that use the image in PDF.Currently there a lot chart library that offers saving the chart to user machine.In my scenario, user want to be able to get chart along with Opportunity information in PDF.So saving chart into the local machine and upload it back as Attachment is not ideal solution.

In order to achieve this, we must include canvg library that you can get it here.This library will help to convert SVG to PNG[also please checkout other format].I get this information from here . Note that Visualforce Chart only render in browsers that support SVG, which mean this is a good sign.


1
2
3
<script type="text/javascript" src="http://canvg.github.io/canvg/rgbcolor.js"></script> 
<script type="text/javascript" src="http://canvg.github.io/canvg/StackBlur.js"></script>
<script type="text/javascript" src="http://canvg.github.io/canvg/canvg.js"></script> 

In order Visualforce page , add canvas element in page.In this example, I use demo from Visualforce Chart for pie chart.

1
2
3
4
5
6
7
<input type="button" id="save_img_att" value="Save as Attachment"/>
<div id="container" style="min-width: 400px; max-width: 600px; height: 400px; margin: 0 auto"></div>
    <apex:chart height="350" width="450" data="{!pieData}" id="pieChart" renderTo="container">
        <apex:pieSeries dataField="data" labelField="name" id="pieSeries"/>
        <apex:legend position="right"/>
    </apex:chart>
    <canvas id="canvas" style="display:none;"></canvas>

In Javascript,note 'container' is the div element where chart is render to.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$("#save_img_att").click(function(){
        var mainDiv=document.getElementById('container');
        var svg = mainDiv.children[0].children[0].innerHTML;
        console.log('@@svg .... ' + svg);
        canvg(document.getElementById('canvas'),svg);
        var img = canvas.toDataURL("image/png"); //img is data:image/png;base64
        img = img.replace('data:image/png;base64,', '');
        var data = "bin_data=" + img;
        console.log('@@oppId =' +     '{!Opportunity.Id}');
        Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.VFChart_Server.saveChartAsAttachment}',
       '{!Opportunity.Id}',img,
          function(result,event){
             if(event.status){
             
               console.log('@@@ success');
               
             }
             else {
               console.log('@@@ fail');
             
             }
          
          
          } );
        
      
    });

We use var svg = mainDiv.children[0].children[0].innerHTML; in order to get the div that generated by Salesforce.You can right click at the chart and search for something like below.We don't want to hardcode so we need to find it from 'container' div.


Create @RemoteAction in controller class.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RemoteAction
    global static void saveChartAsAttachment(String oppId,String attachmentBody){
       system.debug('@@@saveChartAsAttachment  oppId ='+ oppId);
       List<Attachment> listAttachment =[Select Name,Id,Body from Attachment Where ParentId=:oppId];
       Attachment att;
       if(listAttachment.size()>0){
          att =listAttachment[0];
          att.Body = EncodingUtil.base64Decode(attachmentBody);
          update att;
       
       }
       else{
       
           att = new Attachment();
           att.Name='spiderChart_VF';
           att.ParentId=oppId;
           att.ContentType='image/png';
           att.Body = EncodingUtil.base64Decode(attachmentBody);
           insert att;
    
       }
  
    }

I include this visualforce page in Opportunity page layout  like below.This is nothing to do with saving it as attachment, it just because I am lazy to create my own custom page.


Click on Save as Attachment. Refresh it, you should see the image is save in Attachment.Here the sample of image that generated from Visualforce Chart.


Once it save as Attachment, it easy for us to render as PDF which I will update in other entry.

For complete source code please check it out here.



10 comments:

  1. Hi,

    I followed the steps mentioned this blog but i am getting error : Uncaught ReferenceError: canvg is not defined.

    how to solve this issue ?

    Thanks,
    Nitish

    ReplyDelete
    Replies
    1. Hi Nitish,
      Have you include the canvg library in your code?Please try compare your code with my source code that I shared.

      Delete
    2. Yes i have added canvg library in my code still i am facing this issue.
      Please suggest.

      Thanks,
      Nitish

      Delete
  2. Hello, Great example..
    I was looking for graphs to be attached while sending emails or how can I include graphs in email templates?

    ReplyDelete
    Replies
    1. Hi Saurabh,
      I never try to attach the graph before.I believe it should be doable after you save it into attachment.
      Here the example https://developer.salesforce.com/docs/atlas.en-us.pages.meta/pages/pages_email_sending_attachments.htm and also there an idea on this https://success.salesforce.com/ideaView?id=08730000000BpSJAA0. If you don't mind, please describe it in detail so I can try look into it.

      Delete
  3. Hello Unid,
    Thanks for reply,
    I am having scenario as below,
    1.user will click on monthly report button(Custom Button).
    2. One method will get called from apex.
    3. This method will fetch records based on query and will pass to email template.
    4. Email template having html table where this query result will get replace.
    5.this email will sent to user.

    ------
    Here I want to add PDF attachment by creating graph in it based on query result instead of HTML table.

    How can I do that?

    ReplyDelete
  4. Hello Unid,
    Currently I am sending HTML table in email based on query result in apex class and using Email HTML template. Now I want to replace all text part(Table having record result) in my email with graphical representation using apex code. Is it possible? please suggest how can I achieve that.

    thanks,
    Saurabh

    ReplyDelete
    Replies
    1. I never try to put the image email content before, I believe you need to do some encode image encode64 first before you do that.

      Delete
  5. Hi Unid,
    I am getting :

    # ) 11:26:27.912 rgbcolor.js:1 Uncaught SyntaxError: Unexpected token <
    # ) 11:26:28.070 StackBlur.js:1 Uncaught SyntaxError: Unexpected token <
    # ) 11:26:28.070 canvg.js:1 Uncaught SyntaxError: Unexpected token <

    # ) Uncaught ReferenceError: canvg is not defined
    at window.onload

    I am trying to capture the the chart as attachment , as soon the vfp loads i.e. Window.OnLoad()

    thanks
    SFDC Dev








    ReplyDelete
    Replies
    1. How you call the library?Is it using CDN or using static resource?

      Delete