WebReference.com - Drawing Charts with JavaScript (5/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.
[previous] [next] |
Created: March 6, 2002
Revised: March 6, 2002
URL: https://webreference.com/programming/javascript/charts/5.html