WebReference.com - Drawing Charts with JavaScript (5/7) | WebReference

WebReference.com - Drawing Charts with JavaScript (5/7)

To page 1To page 2To page 3To page 4current pageTo page 6To page 7
[previous] [next]

Drawing Charts with JavaScript

Breaking down the code

We start with a little housekeeping: setting variables to determine the browser type, preloading our images and initializing a few global variables for use in our functions. We'll also initialize the four arrays:

IE4=(document.all)?1:0;
NS4=(document.layers)?1:0;
DOM=(document.getElementById)?1:0;
NS6=((DOM)&&(!IE4));
ver4=((IE4)||(NS6)||(NS4))?1:0;
nav=navigator.appVersion;
nav=nav.toLowerCase();
isMac=(nav.indexOf("mac")!=-1)?1:0;
IEmac=((document.all)&&(isMac))?1:0;
imRed = new Image();
imYellow = new Image();
imOrange = new Image();
imRed.src = "red.gif";
imYellow.src = "yellow.gif";
imOrange.src = "orange.gif";
strImage = "";
strToWrite = "";
barHeight = 10;
arScores = new Array("",90,100,78,86,52,99,99,100);
arStudents = new Array("","Alan", "Bob", "Joe", "Mary", "Dunce", "Sally", "Allison", "Jacob");
arScoresToChart = new Array();
arStudentsToChart = new Array();
chartWidth = 275;
maxWidth = 0;
for(i = 0 ; i<arScores.length; i++) {
   if(arScores[i] > maxWidth)
      maxWidth = arScores[i];
}

Nothing surprising here. One interesting note is that Netscape 6 and Internet Explorer 5 automatically select the first element in the options array of the select element (NS4.x seems not to do this), causing us to add an empty element to the front of our arrays. If we don't do this, we will never be able to add the first student in the Select element to our chart. Alternatively, we could add a 'GO' button to submit the selected value to our function instead of responding to the onChange event of the Select element, as we have chosen to do here. I felt this way was a bit more user friendly, if not more elegant. Notice too, that we have now stored the height of our bars in a variable (barHeight).

MakeChart() is the function that generates the HTML for the chart and then writes it to our positioned DIV. Adding a 3rd color to draw was a simple matter of preloading a third image (again, a 1-pixel gif) and alternating colors (by using the modulus operator) as we iterate through the array arScoresToChart. This is the only place in our script that risks generating errors in version 3 browsers, so we'll check at the top of this function, and return immediately if the user is running and older browser.

function makeChart() {
   if(!ver4) return;
   strToWrite = "<table bgcolor='#cccccc' cellpadding=0 cellspacing=2 width=" + (chartWidth+80) + "><tr><td>";
   strToWrite += "<table bgcolor='#cccccc' cellpadding=0 cellspacing=2 width=" + (chartWidth+80) + ">"
   for(i=0; i<arScoresToChart.length; i++) {
    if(i%3 == 0)
       strImage = "red.gif";
    if(i%3 == 1)
       strImage = "yellow.gif";
    if(i%3 == 2)
       strImage = "orange.gif";
    strToWrite += "<tr><td align=right>" + arStudentsToChart[i] + "</td>"
    strToWrite += "<td><img src='" + strImage + "' width=" + parseInt((arScoresToChart[i]/maxWidth) * chartWidth) + " height=" + barHeight + " hspace=2>" + arScoresToChart[i] + "</td></tr>";
   }
   strToWrite += "</table>";
   strToWrite += "</td></tr></table>";
   
   // now show it!
   if(IE4) {
      if((IEmac) && (DOM)) return;
      winInnerWidth = document.body.clientWidth;
      winInnerHeight = document.body.clientHeight;
      screenWidth = screen.availWidth;
      screenHeight = screen.availHeight;
      window.offscreenBuffering = true;
      theChart.innerHTML=strToWrite;
   }
   if(NS4) {
      with(document.theChart) {
      document.open();
      document.write(strToWrite);
      document.close();
   }
   }
   if(NS6) {
      document.getElementById("theChart").innerHTML=strToWrite;
   }
}

Occasionally, Internet Explorer 6 demonstrates an unusual behavior. By adding and removing elements from our chart (and updating the page with the innerHTML property of our DIV) we can cause the browser to lose track of the page formatting. When this happens, the layout of the page is destroyed. I could find this documented nowhere on Microsoft's web site, but was able to come up with a solution. The workaround is two-fold. You'll notice in the function above, that before we update the innerHTML property for IE, we first assign variables to the height and width of both the screen object and the window object. Even though we don't use these values in our routine, just referencing them seems to give IE6 the little "reminder" it needs to keep our page layout consistent when it updates the onscreen display. While I haven't seen this behavior in IE 4 or 5, we'll let all versions of IE execute this code just in case. The second part of the workaround is to set offscreenbuffering to true. This property has been available since IE4, but is seldom explicitly referenced in script, since IE has rarely had screen update problems (at least on Windows platforms).

Internet Explorer 5 for Macintosh platforms also exhibits many strange behaviors when we run this code. While it is fairly standards compliant (maybe too compliant!), if you are not going to position every element on the page, I've found that it is extremely difficult to maintain page layout for this browser. What I have chosen to do here is to assign our div (the one that will hold our chart) a style of position:absolute, but not explicitly set left or top values. This allows us to access the element in Netscape 4, while keeping it in the regular page flow. The unfortunate side effect of this is breaking the effect in IE5 for the Mac, forcing us to hide the screen update routine for IE5 Mac users. Another workaround would be to explicitly assign all CSS values (especially top and left) for our chart with DOM compliant code, but this would take the chart out of the normal page flow -- meaning that we must know exactly where on the page the chart will land, since we don't want it to cover up (or be covered up by) the other content on the page. Although this choice means IE5 Mac users can't build our charts (they can see the non-interactive version just fine), this way will work for the widest array of browsers. Again, if you position every element on your page, you won't have this problem. The routine works fine for Mac IE4.5.

Let's look at the following three functions together. These are the functions that will manipulate the arrays, and are at the heart of our technique for allowing the user to make changes.

function addItemToChart(whichItem) {
   arStudentsToChart[arStudentsToChart.length] = arStudents[whichItem];
   arScoresToChart[arScoresToChart.length] = arScores[whichItem];
   document.chartForm.chartItems.options[whichItem]=null;
   v1=pull(arStudents,whichItem);
   v2=pull(arScores,whichItem);
   makeChart();
   window.focus();
}
function undo() {
   if(arScoresToChart.length > 0) {
      tStudent = arStudentsToChart[arStudentsToChart.length-1];
      tValue = arScoresToChart[arScoresToChart.length-1];
	  
      arStudents[arStudents.length]=tStudent;
      arScores[arScores.length]=tValue;
	  
      document.chartForm.chartItems.options[document.chartForm.chartItems.options.length]=new Option(tStudent+" "+tValue);
	  
      arStudentsToChart.length=arStudentsToChart.length-1;
      arScoresToChart.length=arScoresToChart.length-1;
      makeChart();
   }
}
function pull(ar,whichEl) {
  tempEl=ar[whichEl];
  idx=whichEl;
  for(i=idx; i<ar.length-1; i++) {
     ar[i] = ar[i+1];
  }
  ar.length = ar.length-1;
  return tempEl;
}
AddItemToChart() accepts the index of the selected option as its lone argument. It uses that index, which we'll call whichItem, against the arrays arStudents and arScores, and assigns the elements found in those arrays, at that index, to the end of the arStudentsToChart and arScoresToChart arrays (say that 5 times fast). Remembering that arrays are zero based, we can always assign a new element to the end of an array by using the length of the array as the index: arStudentsToChart[arStudentsToChart.length]. Next, AddItemToChart() removes the selected Option from the Select element by assigning it null. We can add and remove Options in a Select element at will by either setting the option to null, or calling the built-in new Option() constructor function. Now it calls the helper function pull() to remove the element from the original arrays, then makes a call to makeChart() to update the display. Finally, since it makes me squeamish when form elements retain focus after an event, we'll call window.focus().

Undo() is quite similar to addItemsToChart(). We essentially do the same operation in reverse, only it gets a bit easier, because we already know the index position of the array elements that we're going to operate on -- they are always going to be the last element in our arrays. First we make sure that there is actually an action to undo, by checking that arScoresToChart.length is greater than zero. If it is not, we immediately return from the function. If it is, we take the last element in arScoresToChart and arStudentsToChart and put them at the end of the arScores and arStudents arrays, respectively. We'll add an Option to the Select element by using the built in new Option() constructor, which takes the option display text as an argument. We'll store the values of the elements just operated on, and pass them to the new Option() constructor. Finally we lop off the last element from arScoresToChart and arStudentsToChart by shortening those arrays by one: arScoresToChart.length=arScoresToChart.length-1;

Here's the generic syntax to add an option to the end of a Select list:

document.formName.selectName.options[document.formName.selectName.options.length]=new Option(optionText);

pull() is a little function I wrote for removing elements from the middle of an array. Excellent functions have been written for adding and removing elements to the front and back of arrays (See Peter Belesis' outstanding columns on advanced array manipulation) but none that I am aware of that pull an element from any location in an array, and then resize the array accordingly. This does just that. We pass it a variable assigned to the array that we want to manipulate, and the index of the element that we want to remove. The function starts at that index and moves all subsequent elements down one position in the array. It then returns that (removed) element. For our purposes we will discard the return value.

The in-page HTML (below) is trivial. A form, imaginatively named chartForm, contains a Select element and an undo button. We dynamically populate the Select element by iterating through arStudents(). When the user fires the onChange event by selecting an option, we pass the selectedIndex to addItemToChart() and kick off the process of drawing our chart. Our positioned DIV, named during another stroke of inspiration -- theChart -- will house our dynamically built table (our chart).

<form name=chartForm>
<select name=chartItems onchange=addItemToChart(this.selectedIndex)>
<script><!--
for(i=0; i<arStudents.length; i++) {
 document.write("<option>" + arStudents[i] + " " + arScores[i]);
}
// -->
</script>
</select>
<input type=button value="undo" onclick="undo()">
</form>
<div id=theChart style="position:absolute"></div>

Finally, we should allow the user to enter the data to chart.


To page 1To page 2To page 3To page 4current pageTo page 6To page 7
[previous] [next]

Created: March 6, 2002
Revised: March 6, 2002

URL: https://webreference.com/programming/javascript/charts/5.html