Skip to main content

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.



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
  6. Hi ,

    I am trying to implement the same code as provided in the github link but it says the static resource high_5 missing.Can you please share the same.

    ReplyDelete

Post a Comment

Popular posts from this blog

Search Solution Basics

When is it a good time to create a customized search solution? You're developing an external knowledge base for user support. You're in the mood for a fun Friday night. The sales reps just started using the Sales Cloud in Lightning Experience. You want to put your company branding in the search bar. What differentiates SOSL from SOQL? Syntax SOSL searches the search index instead of the org database. SOSL searches more efficiently when you don't know in which object the data resides. All of the above. SOSL works with: REST only SOAP only REST, SOAP, and Apex SOQL only What does a search for a single object look like in SOSL? FIND {cloud} RETURNING Account FIND in ACCOUNT RETURNING "cloud" FIND "cloud" in ACCOUNT FIND (cloud) RIGHT NOW! What does a search for multiple objects look like in SOSL? FIND {sneakers} RETURNING ALL ARTICLES FIND {sneakers} in ALL OBJECTS FIND {sneakers} RETURNING Product2, Content

Process Builder is not fired when field update is called from Approval Process

Scenario In Final Approvals section ; in Approval Process we have field update to update Status field. In Process Builder , we have some action that need to be done when Status field is updated in Approval Process.However this process builder is not fired. Solution To handle this, in Field Update in Approval Process , check Re-evaluated Workflow Rules after Field Change as picture below. What happen if field updated from Approval Process. Workflow - does not fires untill Re-evaluate workflow checkbox is ticked on your field update Process Builder - does not fires untill Re-evaluate workflow checkbox is ticked on your field update Trigger - will fire if conditions are matched This is explained in article here  . Field Updates That Re-evaluate Workflow Rules If  Re-evaluate Workflow Rules After Field Change  is enabled for a field update action, Salesforce  re-evaluates all workflow rules on the object if the field update results in a change to the value of the fi

Tips and Tricks : Test class for Invocable method

Issue : I got 100% coverage in my sandbox but when run validation for deployment it returns 0% coverage It turn out that in my sandbox, I am depending on Process Builder to Invocable Apex class, as long I manipulate test data that fire Process Builder it will call Invocable class. This is not useful when deploying it to Production although it gets deployed together with Process Builder. The correct way is to direct call Invocable method inside test class itself. Example of class : 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 28 29 30 31 32 33 global class MyCustomObject_StatusUpdate_Util { @InvocableMethod ( label = ' Update Quote Status ' ) public static void updateQuote ( Request [] requests ) { Set < Id > setOppId = new Set < Id >(); List < SBQQ__Quote__c > listQuoteToUpdate = new List < SBQQ__Quote__c >(); for ( Request request : requests ) {