Saturday 29 October 2011

Creating Attachments in Apex Code

Capturing notes and attachments against records is standard Salesforce functionality.  Recently I had a requirement to capture some notes and a subset of the record details at various points of the record's life.  Effectively the notes were specific to the record's current state and viewing either in isolation wouldn't give the desired outcome. While it would be possible to handle this with a combination of notes/attachments and field history tracking, that puts far too much emphasis on the user to pull the information from disparate places.

To satisfy this requirement I came up with a Visualforce page that is embedded in a record view and has a notes field to capture the free text plus a checkbox to indicate if the opportunity detail should be captured.  The page embedded into an Opportunity record is shown below:



The fields that are included into the attachment are coded into the page and controller in this case, but it would be straightforward to use Visualforce field sets to reduce the maintenance overhead.  The Visualforce markup is shown below:


<apex:page standardController="Opportunity" extensions="OpportunityAttachmentController">
  <apex:outputText value="{!Opportunity.Account.Name}" rendered="false"/>
  <apex:outputText value="{!Opportunity.CloseDate}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.Amount}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.StageName}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.Probability}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.ExpectedRevenue}" rendered="false"/>
   
  <c:RefreshEmbeddedComponent id="refresher" location="/{!Opportunity.id}" active="{!refreshPage}"/>
  <apex:form >
   <apex:pageBlock title="Add Attachment" mode="mainDetail" id="pb">
      <apex:pageBlockSection columns="2">
         <apex:pageBlockSectionItem >
            <apex:outputLabel for="title" value="Title"/>
            <apex:inputText id="title" value="{!title}"/>
         </apex:pageBlockSectionItem>
         <apex:pageBlockSectionItem >
            <apex:outputLabel for="incsum" value="Include Opportunity Detail?"/>
            <apex:inputCheckbox id="incsum" value="{!includeOppDetail}"/>
         </apex:pageBlockSectionItem>
      </apex:pageBlockSection>
      <apex:pageBlockSection columns="1">
         <apex:pageBlockSectionItem >
            <apex:outputLabel for="notes" value="Notes"/>
            <apex:inputTextArea rows="15" style="width:80%" id="notes" value="{!notes}"/>
         </apex:pageBlockSectionItem>
      </apex:pageBlockSection>
      <apex:commandButton value="Add As Attachment" action="{!addAttachment}" rerender="refresher"/>
   </apex:pageBlock>
  </apex:form>
</apex:page>

The first part of the page simply pulls in the fields that are required by the controller. Note that these aren't rendered or used in the page, but without these components my controller would have to query the details from the database:
<apex:outputText value="{!Opportunity.Account.Name}" rendered="false"/>
  <apex:outputText value="{!Opportunity.CloseDate}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.Amount}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.StageName}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.Probability}" rendered="false"/> 
  <apex:outputText value="{!Opportunity.ExpectedRevenue}" rendered="false"/>

The only other aspect that isn't vanilla Visualforce forms is the following custom component:

<c:RefreshEmbeddedComponent id="refresher" location="/{!Opportunity.id}" active="{!refreshPage}"/>

This allows the entire record view page to be refreshed once a Boolean value from the controller is set to true - its the functionality from an earlier post embedded into a component to promote reuse.

Once the user has filled in the form, the following Apex action method is invoked:

public void addAttachment()
{
 String bodyStr=title + '\n' + 
                notes;
  
 if (includeOppDetail)
 {
  bodyStr+='\n\nOpportunity Summary\n' +
           'Account      : ' + opp.Account.Name + '\n' + 
           'Close Date   : ' + opp.CloseDate + '\n' + 
           'Amount       : ' + opp.Amount + '\n' + 
           'Stage        : ' + opp.StageName + '\n' + 
           'Probability  : ' + opp.Probability + '\n' + 
           'Expected Rev : ' + opp.ExpectedRevenue;
 }
 bodyStr+='\n\n\n' + 
           System.now();
  
 Attachment att=new Attachment();
 att.Body=Blob.valueOf(bodyStr);
 att.Name='Note_' + System.now().format('yyyy_MM_dd_hh_mm_ss') + '.txt';
 att.parentId=opp.id;
 insert att;
  
 refreshPage=true;
}

The first part of the method simply builds up the text body of the attachment in a String, appending the user's notes and, if requested, a snapshot of the opportunity fields.

The interesting part is where the attachment is created:
Attachment att=new Attachment();
att.Body=Blob.valueOf(bodyStr);
att.Name='Note_' + System.now().format('yyyy_MM_dd_hh_mm_ss') + '.txt';
att.parentId=opp.id;
insert att;


As you can see there's not much to do - simply create a new attachment, convert the String containing the body to a Blob and store that in the attachment's body field, set up the name and which record its attached to and then insert. The final line sets the flag to refresh the record view page.

Sunday 23 October 2011

London Force.com Developer Meetup #3

This week saw the third London Force.com Developer meetup take place, at the Skills Matter eXchange in Clerkenwell.  A slightly different format this time, with five lightning talks.  Click on the links at the start of each section to view the podcasts.

First up was Amjad Khan on Schema Spy.  Schema Spy is a Java tool that analyses databases and produces a graphical representation of the schema.  A plugin links this to your Salesforce instance.  This is a tool that I've used once or twice in the past, but not that frequently as one of the BrightGen deliverables from the scoping stage of a project is a diagram of the data model.  Where it has come in very handy is when we've been engaged to extend an existing solution or provide a managed service - it saves a lot of analysis time in these cases.

The new Schema Builder functionality looks likely to replace this for us in the fullness of time, mainly as it is in the cloud, and anything that gets software off of my laptop gets my vote.

Next up was Simon Goodyear on interfaces.  For some reason I'd convinced myself that this was going to be around user interfaces, rather than the programming construct as it turned out to be! Interfaces are something that I tend to use when developing products rather than carrying out a Salesforce implementation, on the assumption that I'm likely to want to swap out implementations as the platform moves on, but customers are unlikely to want (or to want to pay for!, even though the cost is usually pretty low) this level of flexibility. Usually Salesforce implementations are seen as a single-shot roll out with some additional maintenance work as time goes on.

A real-world example of interfaces for us was a custom calendar implementation.  The standard Salesforce activity functionality didn't give the flexibility that was required, Initially there were three sobject types that could feature in calendars, which were people, rooms and equipment.  It seemed highly likely that there would be other sobject types required to work in this way, and indeed this turned out to be the case.  Thus the calendar functionality was implemented to work with a Calendarable interface, and wrapper classes to encapsulate the sobjects along with an implementation of the Calendarable interface were written.   Supporting a new sobject type in the calendar was reduced to a small custom class with 10s of lines of code, rather than tweaking code all over the calendar implementation.

Next up was yours truly on the subject of testing.  I'm planning to use this as the basis for a blog post dedicated to testing, so won't say anything more here.

Stony Grunow then spoke on effective B2C management.  I found this a particularly useful talk, as I'm often having to field questions about why person accounts aren't suitable for an implementation, usually because additional, secondary contacts are required to be associated, for example spouse and children.  I'll definitely be taking a look at the Contacts and Organizations package.

Finally, Bruce Durling spoke on using Selenium to automate deployments.  Deploying configuration has always been a challenge, as there are some things that can be deployed via metadata - approval processes for example.  Re-keying these is error-prone, even when using the four eyes principle.  Using Selenium for this is an intriguing notion.  A key factor in deciding whether to go this route would be the amount of times that the deployment will take place.  If its a single shot, then I can't see there would be much benefit, as you would be effectively re-keying it through code that drives Selenium.    Where it becomes much more useful is where changes have to be deployed through various developer sandboxes, QA and pre-production environments before ending up on production.

I can also see this being a useful technique for creating custom setting values, which aren't copied over for anything other than full copy sandboxes, but supply vital configuration information to allow the system to function correctly.  It may even provide a solution to the perennial problem of determining the HTML element ids of fields to populate via URL parameters.  We use custom settings to capture this information, but there's still manual intervention required to scrape the id out of the page. Something I'll be investigating in the near future.

With all this, excellent before and after networking, and free pizza and beer, its easy to see why these meetups are growing in popularity.  Once again, thanks go to Wes Nolte (@wesnolte) and Bruce Durling (@otfrom) for organizing everything.  If you are interested in coming along to the next one, follow any (or all!) of these twitter users and the details will be (re)tweeted as soon as they are confirmed: @bob_buzzard, @otfrom, @wesnolte.  Look forward to seeing you there!

Saturday 15 October 2011

Dojo Charts Part 4 - Line Charts

This week the Dojo chart under the microscope is the line chart, where a series of points are plotted on the graph and joined by lines.  In my example there are two series allowing comparison of made up information about the English counties of Suffolk and Norfolk.

Following the now familiar pattern is a page with associated controller that pulls in the cross domain Dojo build, manages the data and delegates the chart production to a custom component:


<apex:page controller="DojoLineChartController">
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true,
      modulePaths: { 
          'dojo': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo', 
          'dijit': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit', 
         'dojox': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox' 
             }
    ">
</script>

<apex:form >
  <table>
    <tr>
      <td>Labels:</td>
      <td>
         <apex:inputText value="{!labels}" size="80"/>
      </td>
    </tr>
    <tr>
      <td>Series 1:</td>
      <td>
        <apex:inputText value="{!series1}"/>
      </td>
    </tr>
    <tr>
      <td>Series 2:</td>
      <td>
        <apex:inputText value="{!series2}"/>
      </td>
    </tr>
  </table>
  <apex:commandButton value="Update"/>
</apex:form>
<c:DojoLineChart id="linechart" miny="0" maxy="12" stepy="3" 
        labelsx="{!xAxisLabels}"   
        title1="Essex" 
        series1="{!series1}"
                 color1="#000"
        title2="Norfolk" 
        series2="{!series2}"
                 color2="#C00"
        />
</apex:page>

The controller manages three String properties:

  • A comma separated list of labels, used to produce the x axis labels
  • A comma separated list of values for the first series to plot
  • A comma separated list of values for the second series to plot

<apex:component >
  <apex:attribute name="miny" description="Minimum Y Axis value" type="Integer"/>
  <apex:attribute name="maxy" description="Maximum Y Axis value" type="Integer"/>
  <apex:attribute name="stepy" description="Maximum Y Axis value" type="Integer"/>
  <apex:attribute name="labelsx" description="X Axis Labels" type="String" />
  <apex:attribute name="title1" description="Plot series 1 title - displayed in legend" type="String" />
  <apex:attribute name="series1" description="Plot series 1, comma separated values" type="String" />
  <apex:attribute name="color1" description="Plot series 1 colour" type="String" />
  <apex:attribute name="title2" description="Plot series 2 title - displayed in legend" type="String" />
  <apex:attribute name="series2" description="Plot series 2, comma separated values" type="String" />
  <apex:attribute name="color2" description="Plot series 2 colour" type="String" />
  <apex:attribute name="title3" description="Plot series 3 title - displayed in legend" type="String" />
  <apex:attribute name="series3" description="Plot series 3, comma separated values" type="String" />
  <apex:attribute name="color3" description="Plot series 3colour" type="String" />
  <apex:attribute name="height" description="Chart Height" type="Integer" default="300"/>
  <apex:attribute name="width" description="Chart Width" type="Integer" default="800"/>
  <apex:attribute name="showLegend" description="Show Legend" type="Boolean" default="true"/>
  
  
<script type="text/javascript">

  dojo.require("dojox.charting.Chart2D");
  dojo.require("dojox.charting.widget.Legend");

  makeCharts = function(){

        var chart1 = new dojox.charting.Chart2D("simplechart", {fill:"transparent"});
        chart1.addPlot("default", {type: "Lines", markers: "true", hAxis: "x", vAxis: "y"});
        chart1.addAxis("x", {
       <apex:outputText rendered="{!NOT(ISBLANK(labelsx))}">
            labels:
               [
                 {!labelsx}
               ]
            </apex:outputText>
  });
  
        chart1.addAxis("y", 
                      {
                       <apex:outputText value="min:{!miny}," rendered="{!NOT(ISBLANK(miny))}"/> 
                       <apex:outputText value="max:{!maxy}," rendered="{!NOT(ISBLANK(maxy))}"/> 
                       <apex:outputText value="majorTickStep:{!stepy}," rendered="{!NOT(ISBLANK(stepy))}"/> 
                       vertical: true, 
                       natural: false});
        <apex:outputPanel layout="none" rendered="{!NOT(ISBLANK(title1))}">
           chart1.addSeries("{!title1}", [{!series1}],
                {plot: "other", stroke: {color:"{!color1}"}});
        </apex:outputPanel>
        
        <apex:outputPanel layout="none" rendered="{!NOT(ISBLANK(title2))}">
           chart1.addSeries("{!title2}", [{!series2}],
                {plot: "other", stroke: {color:"{!color2}"}});
        </apex:outputPanel>
        
        <apex:outputPanel layout="none" rendered="{!NOT(ISBLANK(title3))}">
           chart1.addSeries("{!title3}", [{!series3}],
                {plot: "other", stroke: {color:"{!color3}"}});
        </apex:outputPanel>
        chart1.render();
        <apex:outputText rendered="{!showLegend}">
  var legend1 = new dojox.charting.widget.Legend({chart: chart1, horizontal: false}, "legend1");
  </apex:outputText>        
};

dojo.addOnLoad(makeCharts);

</script>

<table>
   <tr>
     <td>
    <div id="simplechart" style="width: 800px; height: 300px;"></div>
  </td>
     <td style="vertical-align:middle">
    <div id="legend1"></div>
  </td>
   </tr>
</table> 
 
</apex:component>

The chart is created with a plot and x and y axes. The component can take up to
3 series - these are conditionally added to to chart via apex:outputPanel
components, along with the line colour. The component can also display a legend
for the chart - if the showLegend attribute is set to true. When first opened,
the page appears as:




Updating the values in the chart and clicking the Update button causes the graph to be redrawn with the  new values:


This example, like the others in this series, is available in the Google Code project

Sunday 9 October 2011

Dojo Charts Part 3 - Stacked Bar Charts

Continuing the series of posts on Dojo charting, this week I'm looking at stacked bar charts.  This is nboStacked bar charts allow a single bar to display more than one category of data.  My example shows the SLA status for a set of records.

As usual, the Visualforce page pulls in the cross domain Dojo build from Google, and delegates responsibility for the production of the chart to a custom component:


<apex:page >
	 <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js"
    	djConfig="parseOnLoad: true,
	  	modulePaths: { 
    		'dojo': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo', 
    		'dijit': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit', 
    		'dojox': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox' 
  				}
    	">
    </script>
    
  <apex:sectionheader title="Dojo Stacked Bar Chart"/>    	
	<c:DojoStackedBarChart name1="Inside SLA"
		series1="1,3,1" 
		colour1="green"
		click1="http://www.google.com"
		name2="Escalated"
		series2="3,8,5" 
		colour2="yellow"
		click2="http://www.google.com"
		name3="Breached SLA"
		series3="4,13,8" 
		colour3="red"
		click3="http://www.google.com" 
		divId="stackedBars"
		title="SLA Status"
		/>
</apex:page>
each stacked bar in the chart is set up via the series/colour/click/name indexed parameters. I haven't made this a live page, but it would be straightforward to store the series1-3 values in a custom controller and add apex:inputField components that allowed the user to update them. Where this differs from previous posts is the click parameters - this allows the user to click on one of the stacked bars and be taken to another page. I've simply sent the user to www.google.com, but when I've used this technique in a real project the user is taken to a drill down page or report that shows the detail behind that particular stacked bar. The component markup is shown below:
<apex:component >
<apex:attribute name="name1" type="String" description="Name of first series (first stack)"/>
<apex:attribute name="series1" type="String" description="Comma seperated values for first series"/>
<apex:attribute name="colour1" type="String" description="Colour of first series"/>
<apex:attribute name="click1" type="String" description="The destination if the user clicks on the first series"/>
<apex:attribute name="name2" type="String" description="Name of second series (second stack)"/>
<apex:attribute name="series2" type="String" description="Comma seperated values for second series"/>
<apex:attribute name="colour2" type="String" description="Colour of second series"/>
<apex:attribute name="click2" type="String" description="The destination if the user clicks on the first series"/>
<apex:attribute name="name3" type="String" description="Name of third series (third stack)"/>
<apex:attribute name="series3" type="String" description="Comma seperated values for first series"/>
<apex:attribute name="colour3" type="String" description="Colour of first series"/>
<apex:attribute name="click3" type="String" description="The destination if the user clicks on the first series"/>
<apex:attribute name="divId" type="String" description="ID of the div to store the chart in"/>
<apex:attribute name="title" type="String" description="Title of the chart"/>

<script type="text/javascript">
dojo.require("dojox.charting.Chart2D");
dojo.require("dojo.colors");
dojo.require("dojox.charting.themes.Tufte");
dojo.require("dojox.charting.widget.Legend");
	
makeCharts = function(){
		var chart2=new dojox.charting.Chart2D("{!divId}");
		// use the Tufte theme as this gives transparent background
        chart2.setTheme(dojox.charting.themes.Tufte);
        chart2.addPlot("default", {type: "StackedBars"});
        
        chart2.addSeries("{!name1}", [{!series1}], {fill: "{!colour1}"});
        chart2.addSeries("{!name2}", [{!series2}], {fill: "{!colour2}"});
        chart2.addSeries("{!name3}", [{!series3}], {fill: "{!colour3}"});
        chart2.addAxis("x", {includeZero: true});
        
        chart2.connectToPlot("default", this, function(evt)
           {
				if(!evt.shape||(evt.type != 'onclick')){
					return;
				}
				var link="";
				if (evt.run.name=="{!name1}")
				{
				   link="{!click1}";
				}
				else if (evt.run.name=="{!name2}")
				{
				   link="{!click2}";
				}
				else if (evt.run.name=="{!name3}")
				{
				   link="{!click3}";
				}
                if (""!=link)
                {
    				window.open(link, "_blank");
                }
           }
        );
        chart2.render(); 
		var legend1 = new dojox.charting.widget.Legend({chart: chart2}, "legend1");
};

dojo.addOnLoad(makeCharts);

</script>

<div id="{!divId}" style="width: 600px; height: 200px;"></div>
<div id="legend1"></div>
<div style="margin-top: 40px; font-size:14px; font-weight:bold; width: 400px; text-align:center">{!title}</div>
</apex:component>
Most of this will be familiar to regular readers - create the chart, define the series, add plots and axes etc. The interesting part (for this week at least) is the function that handles the event when the user clicks on a stacked bar.
   chart2.connectToPlot("default", this, function(evt)
           {
				if(!evt.shape||(evt.type != 'onclick')){
					return;
				}
				var link="";
				if (evt.run.name=="{!name1}")
				{
				   link="{!click1}";
				}
				else if (evt.run.name=="{!name2}")
				{
				   link="{!click2}";
				}
				else if (evt.run.name=="{!name3}")
				{
				   link="{!click3}";
				}
                if (""!=link)
                {
    				window.open(link, "_blank");
                }
           }
        );
The bar can be identified based on the run.name attribute of the event and is simply matched up with the associated click parameter. The generated chart is shown below:


Sunday 2 October 2011

Dojo Charts Part 2 - Bar Charts

Following on from last weeks Pie Chart post, this week I'm presenting an example of a Bar Chart generated via the Dojo JavaScript library.

As in the previous example, the Visualforce page pulls in the cross domain Dojo build from Google, has a couple of input fields (so that the data can be updated live) and responsibility for the production of the chart is delegated to a custom component:


<apex:page controller="DojoBarChartController">
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true,
      modulePaths: { 
          'dojo': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo', 
          'dijit': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit', 
         'dojox': 'https://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox' 
             }
    ">
</script>

<apex:sectionheader title="Dojo Bar Chart"/>
<apex:form >
 <apex:pageBlock title="Chart Data">
   <table>
    <tr>
      <td>Labels:</td>
      <td>
         <apex:inputText value="{!labels}" size="80"/>
      </td>
    </tr>
    <tr>
      <td>Values:</td>
      <td>
        <apex:inputText value="{!series1}"/>
      </td>
    </tr>
  </table>
  <apex:commandButton value="Update"/>
 </apex:pageBlock>
</apex:form>
<c:DojoBarChart id="barchart" minx="0" stepx="2"
			     labelsy="{!yAxisLabels}"   
			     title1="Data" 
			     series1="{!series1}"
                             color1="#000"
                             fill1="blue"
			     />
</apex:page>

The labels and series1 properties are simply comma separated lists of values, where the first entry in the series1 is associated with the first entry in labels and so on.  The controller converts the labels into a suitable format for use in the JavaScript that sets up the bar chart.

The component takes a number of attributes, not all of which are supplied from the page.  As an aside, this is by no means the full functionality available from a Dojo bar chart - there are click throughs, animations and many other features, some of which will be explored in later posts in this series.


<apex:component >
  <apex:attribute name="minx" description="Minimum X Axis value" type="Integer"/>
  <apex:attribute name="maxx" description="Maximum X Axis value" type="Integer"/>
  <apex:attribute name="stepx" description="Step value for X axis" type="Integer"/>
  <apex:attribute name="labelsy" description="Y Axis Labels" type="String" />
  <apex:attribute name="title1" description="Plot series 1 title - displayed in legend" type="String" />
  <apex:attribute name="series1" description="Plotext>
                       vertical: true, 
                       natural: false});
        <apex:outputPanel layout="none" rendered="{!NOT(ISBLANK(title1))}">
           chart1.addSeries("{!title1}", [{!series1}],
                {stroke: {color:"{!color1}"}, fill: "{!fill1}"});
        </apex:outputPanel>
        chart1.render();
        <apex:outputText rendered="{!showLegend}">
		var legend1 = new dojox.charting.widget.Legend({chart: chart1, horizontal: false}, "legend1");
		</apex:outputText>        
};

dojo.addOnLoadavascript">

  dojo.require("dojox.charting.Chart2D");
  dojo.require("dojox.charting.widget.Legend");

  makeCharts = function(){

        var chart1 = new dojox.charting.Chart2D("simplechart", {fill:"transparent"});
        chart1.addPlot("default", {type: "Bars", gap:10});
        chart1.addAxis("x", {
                       <apex:outputText value="min:{!minx}," rendered="{!NOT(ISBLANK(minx))}"/> 
                       <apex:outputText value="max:{!maxx}," rendered="{!NOT(ISBLANK(maxx))}"/> 
                       <apex:outputText value="majorTickStep:{!stepx}," rendered="{!NOT(ISBLANK(stepx))}"/> 
		});
		
        chart1.addAxis("y", 
                      {
  		   <apex:outputText rendered="{!NOT(ISBLANK(labelsy))}">
            labels:
               [
                 {!labelsy}
               ],
            </apex:outputText>
                       vertical: true, 
                       natural: false});
        <apex:outputPanel layout="none" rendered="{!NOT(ISBLANK(title1))}">
           chart1.addSeries("{!title1}", [{!series1}],
                {stroke: {color:"{!color1}"}, fill: "{!fill1}"});
        </apex:outputPanel>
        chart1.render();
        <apex:outputText rendered="{!showLegend}">
		var legend1 = new dojox.charting.widget.Legend({chart: chart1, horizontal: false}, "legend1");
		</apex:outputText>        
};

dojo.addOnLoad(makeCharts);

</script>

<table>
   <tr>
     <td>
	   <div id="simplechart" style="width: 800px; height: 300px;"></div>
	 </td>
     <td style="vertical-align:middle">
	   <div id="legend1"></div>
	 </td>
   </tr>
</table> 
 
</apex:component>

Some of the values (minx, maxx) are optional - Dojo will calculate these if you don't provide them.   The creation of the chart follows a simple path - create the chart, add a plot, add x and y axis, add the data series and render the chart.  In this case the chart is rendered into the div with the id of "simplechart".  If multiple charts were required on the same page, this should be passed as an attribute to the component.

The page with the generated chart is shown below:


As before, the chart is live, so updating the values in the Chart Data section and clicking "Update" regenerates the chart with the latest data:


The page, controller and component are available in the Google Code project, along with a number of additional examples.  The next post will cover how to generate a stacked chart.