Figure 2    The CSS Style Sheet


 <!--NoAd-->
 #backgroundDIV {
     position: absolute;
     width:  280;
     height: 290;
     background-color: black;
     cursor: arrow;
 }
 
 #chartAppletDIV {
     position: absolute;
     left: 4;
     top:  24;
     width:  270;
     height: 140;
     background-color: black;
     z-index: 1;
 }
 
 #indexesDIV {
     position: absolute;
     left: 4;
     top: 172;
     width: 270;
     height: 10;
     padding: 2;
     background-color: #FF7200;
     font-size: 7pt;
     font-family: Verdana;
     font-weight: bold; 
     text-align: center; 
     cursor: hand;
     z-index:2;
 }
 
 #adDIV {
     position: absolute;
     left: 20;
     top: 224;
     height: 62;
     width: 236;
     background-color: black;
 }
 
 .customize {
     font-size: 7pt;
     font-family: Verdana;
     font-weight: bold;
     background-color: black;
     color: #FF7200;
     border: 1 solid #FF7200;
 }
 
 .menu {
     position: absolute;
     height: 10;
     padding-left: 2;
     padding-right: 2;
     border: 1 solid #FF7200;
     color: #FF7200;
     font-size: 7pt;
     font-family: Verdana;
     text-align: left; 
     cursor: hand;
     z-index:3;
 }
 
 #customizeDIV {
     position: absolute;
     left: 4;
     top: 169;
     width: 270;
     height: 15;
     color: #FF7200;
     background-color: black;
     font-size: 7pt;
     font-family: Verdana;
     font-weight: bold; 
     text-align: center;
     z-index: 2;
     visibility: hidden;
 }
 

Figure 3   Fortune Personal Stock Chart HTML


 <!-- Title: Fortune Personal Stock Chart        -->
 <!-- Type: ActiveDesktop component            -->
 <!-- Author: Josh Hochman (jhochman@pathfinder.com)    -->
 <!-- based on original code by Stefan Winz    -->
 <!-- Date: 9-31-97                    -->
 
 <HTML> 
 <HEAD>
 
 <!-- Always expire this page because the content will always be fresh -->
 <META HTTP-EQUIV="Pragma" CONTENT="no-cache">
 
 <TITLE>Fortune Desktop Component</TITLE>
 
 <!-- to save space here, link in a stylesheet -->
 <LINK REL=STYLESHEET TYPE="text/css" HREF="styles.html" TITLE="Styles">
 
 <!-- chart.js contains all JavaScript that drives this page -->
 <SCRIPT LANGUAGE="JavaScript" SRC="chart.js"></SCRIPT>
 
 <!-- a few wrapper functions to take care of the ADO stuff -->
 <!-- ADO is "ActiveX Data Object" - just a fancy name for -->
 <!-- OLE DB routines. They manipulate a cursor in the TDC's recordset-->
 <SCRIPT LANGUAGE="VBScript">
 
 sub moveFirst (theTDC)
     theTDC.Recordset.MoveFirst
 end sub     
 
 function getValue (theTDC,theName)
     getValue = theTDC.Recordset(theName)
 end function
 
 function getRecordCount (theTDC)
     getRecordCount = theTDC.Recordset.RecordCount
 end function
 
 function moveNext (theTDC)
     theTDC.Recordset.MoveNext
 end function
 
 </SCRIPT>
 
 </HEAD>
 
 <BODY BGCOLOR="#000000" LEFTMARGIN=0 TOPMARGIN=0 onload=init()>
 
 <!-- TDC is a "Tabular Data Control", just an ADO with a few web-related-->
 <!-- functions added on. The important thing here is "DataURL" which is -->
 <!-- where the data is coming from                    -->
 <OBJECT ID=TDC CLASSID="CLSID:333C7BC4-460F-11D0-BC04-0080C7055A83">
   <PARAM NAME="TextQualifier" VALUE="&quot;">
   <PARAM NAME="FieldDelim" VALUE="|">
   <PARAM NAME="UseHeader" VALUE="False">
   <PARAM NAME="DataURL" VALUE="">
   <PARAM NAME="SortColumn" VALUE="Column1">
 </OBJECT>
 
 <!-- we have to stick this here because of the way that browsers load    -->
 <!-- code. Now that TDC has been declared, we tie a function to it's    -->
 <!-- notify routine. *BUG*: you have to attatch the TDC to another    -->
 <!-- object, or the notify event will never fire. That is why there is    -->
 <!-- a hidden DIV that displays TDC                    -->
 <SCRIPT LANGUAGE="JavaScript1.2">
     TDC.ondatasetcomplete = TDCcomplete;
 </SCRIPT>
 
 <DIV id=backgroundDIV>
 
     <DIV STYLE="width: 280;font-family: Verdana;font-size:13;font-weight: bold;color:
                #FF7200;text-align:center;padding:4">
     PERSONAL FORTUNE STOCK CHART
     </DIV>
     
     <!-- chartAppletDIV is the Java applet that does all the drawing-->
     <DIV id=chartAppletDIV>
     <applet code=draw.class
         id=chart
         width=270
         height=140>
 
         <param name=colorBGR value=0>
         <param name=colorBGG value=0>
         <param name=colorBGB value=0>
 
     </applet>
 
     </DIV>
 
     <!-- indexesDIV displays all the stock symbols, and lets you    -->
     <!-- switch between them. handleMousedown takes care of the that-->
     <DIV id=indexesDIV>
     <SPAN id=DJIASPAN
     onmouseover="style.color='white'" 
     onmouseout="if (0 != theChoice) style.color='black'"
     onmousedown="if (0 != theChoice) handleMouseDown(this)">DJIA</SPAN>
     <SPAN id=SP500SPAN
     onmouseover="style.color='white'" 
     onmouseout="if (1 != theChoice) style.color='black'"
     onmousedown="if (1 != theChoice) handleMouseDown(this)">S&P500</SPAN>
     <SPAN id=NASDAQSPAN
     onmouseover="style.color='white'" 
     onmouseout="if (2 != theChoice) style.color='black'"
     onmousedown="if (2 != theChoice) handleMouseDown(this)">NASDAQ</SPAN>
     <SPAN id=NYSESPAN
     onmouseover="style.color='white'" 
     onmouseout="if (3 != theChoice) style.color='black'"
     onmousedown="if (3 != theChoice) handleMouseDown(this)">NYSE</SPAN>
     <SPAN id=AMEXSPAN
     onmouseover="style.color='white'" 
     onmouseout="if (4 != theChoice) style.color='black'"
     onmousedown="if (4 != theChoice) handleMouseDown(this)">AMEX</SPAN> -
     <SPAN id=custom1SPAN
     onmouseover="style.color='white'" 
     onmouseout="if (5 != theChoice) style.color='black'"
     onmousedown="if (5 != theChoice) handleMouseDown(this)"></SPAN>
     <SPAN id=custom2SPAN
     onmouseover="style.color='white'" 
     onmouseout="if (6 != theChoice) style.color='black'"
     onmousedown="if (6 != theChoice) handleMouseDown(this)"></SPAN>
     </DIV>
 
     <!-- statsDIV will get filled in with the current value of the stock -->
     <DIV id=statsDIV class=menu style="left: 4;top: 192;width: 138;">
     current value: <SPAN id=currentPrice>0</SPAN></SPAN>
     </DIV>
 
     <!-- refreshMenuDIV is either the refresh button, wich calls refresh,    -->
     <!-- or it is the cancel button when customizing. If we are customizing -->
     <!-- then clicking this button (which says "cancel") will simulate    -->
     <!-- the user pressing the ESC key and call keydown, effectively    -->
     <!-- cancelling the customization                    -->
     <DIV id=refreshMenuDIV class=menu style="left: 144;top: 192;width: 55;"
     onmouseover="style.color='white'" 
     onmouseout="style.color='#FF7200'"
     onmousedown="style.color='#FF7200';if (theState == 'customize') 
         {event.keyCode =27;keydown()} else {refresh()}">
     REFRESH 
     </DIV>
 
     <!-- customizeMenuDIV is either the customize button, which calls the    -->
     <!-- customize function, or it is the save button, which will simulate    -->
     <!-- the user pressing "return" and will call keydown, effectively    -->
     <!-- saving the customization                        -->
     <DIV id=customizeMenuDIV class=menu style="left: 201;top: 192;width: 60;"
     onmouseover="style.color='white'" 
     onmouseout="style.color='#FF7200'"
     onmousedown="style.color='#FF7200';if (theState == 'customize') 
         {event.keyCode = 13;keydown()} else {customize()}">
     CUSTOMIZE
     </DIV>
 
     <DIV id=link class=menu style="border:0 none;:top:210;text
              align:center;width:270"
     onmouseover="style.color='white'" 
     onmouseout="style.color='#FF7200'"
     onmousedown="window.open('http://www.fortune.com')">
     Go to the Fortune website...
     </DIV>
     
     <!-- customizeDIV is a hidden DIV that gets shown when the user clicks    -->
     <!-- the customize button. It is a form that lets you change the last    -->
     <!-- two symbols                            -->
     <DIV id=customizeDIV>
     <FORM id=theForm>
     DJIA S&P500 NASDAQ NYSE AMEX -
     <INPUT id=custom1 class=customize SIZE=4>
     <INPUT id=custom2 class=customize SIZE=4>
     </FORM>
     </DIV>
 
     <DIV id=adDIV><!--#exec cgi="/pfinclude@pfadspace"--></DIV>
 
     <!-- this hidden layer holds the cursor's value in the first column of    -->
     <!-- the current record in the TDC's recordset. If this isn't here, then-->
     <!-- ondatasetcomplete events never get fired. Oh well. I guess it    -->
     <!-- doesn't hurt that much.                        -->
     <DIV id=poop DATASRC=#TDC DATAFLD="Column1"
          style="layout:fill;position:absolute;
          top:0;visibility:hidden"> 
          </DIV>
     
 </DIV>
 
 </BODY>
 </HTML>
 

Figure 5    JavaScript for Databinding


 <!-- NoAd -->
 var symbols = new Array(7);     // the seven symbol objects
 var theState = 'init';          // internal state
 var theChoice = 0;              // the currently chosen symbol
 var startTime = (9*60) + 30;    // 9:30 a.m., in minutes since midnight
 var endTime = (16*60) + 30;     // 4:30 p.m., in minutes since midnight
 var leftOffset = 60;            // offsets for the chart section 
 var topOffset = 20;
 var bottomOffset = 20;
 var rightOffset = 20;
 var top = 0;                    // values to help with offset calculations
 var left = 0;
 var bottom = 140;
 var right = 270;
 var loadDataTimeout = 0;        // timeout for loading data
 var drawChartTimeout = 0;       // timeout for drawing the chart
 var blink = 0;                  // for blinking "loading"
 var blinker;                    // blinker intervalID
 
 document.onkeydown = keydown;    // capture keydown events with my own function
 
 // Init - this function loads in the two personal stocks (if there is a cookie
 // that contains them) and creates five symbol objects. Next it calls three
 // asynchronous functions to load the data, convert the data, and draw the chart
 
 function init(){
     // either get the cookie, or bake some ourselves
     var cookie1 = getCookie("custom1");
     var cookie2 = getCookie("custom2");
     if (!cookie1) cookie1 = "TWI";
     if (!cookie2) cookie2 = "MRV";
     // preload the customize form with our values
     theForm.custom1.value = cookie1;
     theForm.custom2.value = cookie2;
     // create a bunch of symbol objects
     symbols[0] = new Symbol (TDC,DJIASPAN,"http://quote.pathfinder.com/money/quote/
         qclie.cgi?dukebert=INDU");
     symbols[1] = new Symbol (TDC,SP500SPAN,"http://quote.pathfinder.com/money/quote/
         qclie.cgi?dukebert=INX");
     symbols[2] = new Symbol (TDC,NASDAQSPAN,"http://quote.pathfinder.com/money/quote/
         qclie.cgi?dukebert=COMPX");
     symbols[3] = new Symbol (TDC,NYSESPAN,"http://quote.pathfinder.com/money/quote/
         qclie.cgi?dukebert=NYA.X");
     symbols[4] = new Symbol (TDC,AMEXSPAN,"http://quote.pathfinder.com/money/quote/
         qclie.cgi?dukebert=XAX.X");
     symbols[5] = new Symbol (TDC,custom1SPAN,"http://quote.pathfinder.com/money/
         quote/qclie.cgi?dukebert=" + cookie1);
     symbols[6] = new Symbol (TDC,custom2SPAN,"http://quote.pathfinder.com/money/
         quote/qclie.cgi?dukebert=" + cookie2);
     // fill in the customizable symbols on the interface
     custom1SPAN.innerText = theForm.custom1.value + " ";
     custom2SPAN.innerText = theForm.custom2.value;
     loadTDC(symbols[0].TDC,symbols[0].dataURL);    // load the first TDC
     theChoice = 0;
     symbols[theChoice].SPAN.style.color = "white";    // make it white
     drawTable();                    // draw the table (sans data)
     loadData(theChoice);            // load in the data (asynchronous)
     drawChart(theChoice);           // draw the chart (asynchronous)
 }
 
 // handleMouseDown - loops through all the symbol objects and sets the color
 // of their label SPAN to black. when it matches the SPAN passed in to the
 // SPAN of one of the symbols, it makes it white, erases the chart, loads the
 // data (if needed), and redraws the chart.
 //    
 //    theObject - the SPAN that was clicked (should match one of the 
 //            symbol.SPAN objects)
 
 function handleMouseDown(theObject){
     if (theState != "idle") return;
     chart.DrawRect(left,top,right,bottom,0,0,0);    // erase the chart
     for (var j=0;j < symbols.length;j++){
         // why is everything in a timeout? because stuff doesn't work
         // correctly if it isn't. Oh well.
         if (symbols[j].SPAN == theObject) {    // matching symbol
             theChoice = j;
             symbols[j].SPAN.style.color = 'white';
             if (!symbols[j].loaded) {    // needs to get data
                 loadTDC(symbols[j].TDC,symbols[j].dataURL);
                 loadData(j);
             } else {            // just draw new y labels
                 setTimeout("drawYLabels(" + j + ")",5);
             }
             drawTable();
             setTimeout("drawChart(" + j + ")",4);
         } else {                // if not the symbol,
                             // make sure it's black
             symbols[j].SPAN.style.color = 'black';
         }
     }
 }
 
 // refresh - called when the refresh button is pressed. This function will set
 // the "loaded" state of all the symbol objects to 0, effectively clearing
 // them. It also reloads the current symbol.
 
 function refresh(){
     if (theState != "idle") return;
     for (var j=0;j < symbols.length;j++){
         symbols[j].loaded = 0;
     }
     // clear the chart and load and draw the current symbol
     chart.DrawRect(left,top,right,bottom,0,0,0);
     loadTDC(symbols[theChoice].TDC,symbols[theChoice].dataURL);
     loadData(theChoice);
     drawTable();
     setTimeout("drawChart(" + theChoice + ")",4);
 }
 
 // getCookie - a simple function to return the value of a particular
 // "chip" in the cookie for this page
 //
 //    theName - the name part of the name-value pair
 
 function getCookie(theName){
     var theValue = document.cookie;
     if (theValue.indexOf(theName) == -1) return 0;
     theValue = theValue.substring(theValue.indexOf(theName));
     theValue = theValue.substring(0,theValue.indexOf(";"));
     return theValue.substring(theValue.indexOf("=")+1);
 }
 
 // loadTDC - sets the data source for a TDC and starts the process
 // of getting the data.
 //
 //    theTDC - the tabular data control object
 //    theURL - the URL containing the tabular data
 
 function loadTDC(theTDC, theURL){
     theState = "loading";
     theTDC.DataURL = theURL;
     theTDC.Reset();
     blinker = setInterval("blinkme()",500);
 }
 
 // customize - called when the "customize" button is pressed. Reveals
 // the DIV that contains the customize boxes, and changes some of the
 // values in the buttons (REFRESH -> CANCEL) (CUSTOMIZE -> SAVE).
 
 function customize(){
     theState = "customize";
     customizeMenuDIV.innerText = "SAVE";
     refreshMenuDIV.innerText = "CANCEL";
     customizeDIV.style.visibility = 'visible';
 }
 
 // keydown - traps keydown events. If in customize state, then do
 // something depending upon what key is pressed - "return" will save
 // any changes, while ESC will cancel.
 
 function keydown(){
     if (theState == 'customize' && event.keyCode == 13){  // return pressed
         theForm.custom1.value = theForm.custom1.value.toUpperCase();
         theForm.custom2.value = theForm.custom2.value.toUpperCase();
         // set the cookie for this document with 2 values
         var expiration = "Saturday, 01-Jan-2000 00:00:00 GMT"
         document.cookie = "custom1=" + theForm.custom1.value + ";" + "expires=" +
             expiration + ";" + "domain: pathfinder.com;" + "path: /";
         document.cookie = "custom2=" + theForm.custom2.value + ";" + "expires=" + 
             expiration + ";" + "domain: pathfinder.com;" + "path: /";
         // change everything back to the way it was (SAVE -> CUSTOMIZE) (CANCEL -> 
         //REFRESH)
         customizeDIV.style.visibility = 'hidden';
         customizeMenuDIV.innerText = "CUSTOMIZE";
         refreshMenuDIV.innerText = "REFRESH";
         refreshMenuDIV.style.visibility = 'visible';
         custom1SPAN.innerText = theForm.custom1.value + " ";
         custom2SPAN.innerText = theForm.custom2.value;
         // actually create the symbol objects
         symbols[5] = new Symbol (TDC,custom1SPAN,"http://quote.pathfinder.com/money/
             quote/qclie.cgi?dukebert=" + theForm.custom1.value);
         symbols[6] = new Symbol (TDC,custom2SPAN,"http://quote.pathfinder.com/money/
             quote/qclie.cgi?dukebert=" + theForm.custom2.value);
         if (!navigator.cookieEnabled) alert("Your changes will not be saved unless
             you enable your browser to accept cookies.");
         theState = "idle";
     } else if (theState == 'customize' && event.keyCode == 27){    // ESC pressed
         theForm.custom1.value = custom1SPAN.innerText;    // reset the form values
         theForm.custom2.value = custom2SPAN.innerText;
         // change everything back to the way it was (SAVE -> CUSTOMIZE) (CANCEL -> 
         //REFRESH)
         customizeDIV.style.visibility = 'hidden';
         customizeMenuDIV.innerText = "CUSTOMIZE";
         refreshMenuDIV.innerText = "REFRESH";
         refreshMenuDIV.style.visibility = 'visible';
         theState = "idle";
     }
 }
 
 // drawTable - draws the chart that the stocks are charted on
 
 function drawTable(){
     var x = 0;
     var theTime = 9;
     var fixedTime = 0;
     var labelOffset = 0;
     chart.init();    // initialize the java applet
     // draw the left and bottom lines.
     // for some reason, sometimes the first draw call never executes,
     // therefore, call it twice to make sure it does execute
     chart.DrawLine(leftOffset,topOffset,leftOffset,bottom - bottomOffset,85,85,85);
     chart.DrawLine(leftOffset,topOffset,leftOffset,bottom - bottomOffset,85,85,85);
     chart.DrawLine(leftOffset,bottom - bottomOffset,right - rightOffset + 7,bottom -
                    bottomOffset,85,85,85);
     var chunkSize = Math.round((right - leftOffset - rightOffset)/14);   
     for (i=1;i<8;i++){
         x = leftOffset + chunkSize * i * 2;
         // draw a light line for half-hour segments
         chart.DrawLine(x,topOffset,x,bottom - bottomOffset - 1,40,40,40);
         // draw a darker line for hour segments
         chart.DrawLine(x - chunkSize,topOffset,x - chunkSize,bottom - bottomOffset -
                        1,85,85,85);
     }
     x = leftOffset + chunkSize;
     for (i=9;i<16;i++){    // slap some labels on there, also, do some math to 
                            // convert the time
         if (i != 9) x += chunkSize * 2;
         fixedTime = ++theTime;
         labelOffset = 13;
         if (theTime > 12) { 
             fixedTime = theTime - 12;
             labelOffset = 7;
         }
         chart.DrawText(fixedTime + ":00",x  - labelOffset,bottom - bottomOffset/2 +
                        5,255,102,0);
     }
 }
 
 
 // loadData - an asynchronous function (uses setTimeout to keep calling itself
 // until a certain global state is reached) that uses the ADO (Active Data Object)
 // part of the TDC object to convert market data into an array of points to plot on
 // the chart.
 //
 //    theSymbol - the symbol object that contains the TDC and array of points
 
 function loadData(theSymbol){
     var x = 0;
     var y = 0;
     if (theSymbol > 7) return;          // obviously something is wrong!
     if (theState != "dataWaiting") {    // haven't gotten all the data yet, so use
                                         // settimeout
                                         // to call this function again
         loadDataTimeout = setTimeout("loadData("+theSymbol+")",500);
     } else {                // have data - convert it to points on chart
         theState = "fixing";
         clearTimeout(loadDataTimeout);
         moveFirst(symbols[theSymbol].TDC);        // use VBScript wrapper function
         symbols[theSymbol].numRecords = getRecordCount(symbols[theSymbol].TDC);
         if (symbols[theSymbol].numRecords == 1) {    // no data, return
             theState = "idle";
             clearInterval(blinker);
             chart.DrawText("",100,100,255,102,0);        // nudge the java applet
             chart.DrawRect(10,60,50,30,0,0,0);    // erase the text
             chart.DrawText("No Data",20,75,255,102,0);
             return 0;
         }
         var j = 0;
         for (i=0;i<symbols[theSymbol].numRecords - 1;i++){
             var minutes = getValue(symbols[theSymbol].TDC,"Column1");
             if ((minutes > 570) && (minutes < 990)){
                 // if last record, then save the last time
                 if (i == (symbols[theSymbol].numRecords - 1))
                     symbols[theSymbol].lastTime = x;
                 x = translateX(getValue(symbols[theSymbol].TDC,"Column1"));
                 // don't translate y values yet
                 y = getValue(symbols[theSymbol].TDC,"Column2");
                 symbols[theSymbol].data[j] = new Point(x,y)  // add a point object
                 // do some comparisons to keep the min Y and max Y values
                 if (!j) symbols[theSymbol].maxY = y;        
                 if (y > symbols[theSymbol].maxY) symbols[theSymbol].maxY = y;
                 if (!j) symbols[theSymbol].minY = symbols[theSymbol].maxY;
                 if (y < symbols[theSymbol].minY) symbols[theSymbol].minY = y;
                 j++;
             }
             moveNext(symbols[theSymbol].TDC);
         }
         // last name/value pair is stock ticker/current value, just get the value
         symbols[theSymbol].currentPrice = 
             getValue(symbols[theSymbol].TDC,"Column2");
         drawYLabels(theSymbol);        // draw Y values with min and max Y
         symbols[theSymbol].data[0].y =
             translateY(symbols[theSymbol].data[0].y,theSymbol);
         // translate all Y values
         for (i=0;i < symbols[theSymbol].data.length;i++){
             symbols[theSymbol].data[i].y =
                 translateY(symbols[theSymbol].data[i].y,theSymbol);
         }
         theState = "idle";
         symbols[theSymbol].loaded = 1;
     }
 }
 
 
 // drawChart - draws the activity of the symbol object. An asynchronous function
 // that uses settimeout to keep calling itself until an idle state is achieved.
 //
 //    theSymbol - the symbol object that contains the TDC and array of points
 // 
 
 function drawChart(theSymbol){
     if (theState != "idle"){   // not idle, so call settimeout on this function
                                // to call it again 1/2 second later
         drawChartTimeout = setTimeout("drawChart("+theSymbol+")",500);
     } else {                   // loop throught the points and draw 'em
         theState = "drawing";
         clearTimeout(drawChartTimeout);
         var numEntries= getRecordCount(symbols[theSymbol].TDC);
         for (i=3;i < symbols[theSymbol].data.length - 1;i++) {
             chart.DrawLine(leftOffset + symbols[theSymbol].data[i-1].x,topOffset +
             symbols[theSymbol].data[i-1].y,leftOffset + symbols[theSymbol].data[i-
             2].x,topOffset + symbols[theSymbol].data[i-2].y,0,255,0);
         }
         theState = "idle";
         currentPrice.innerText = pround(symbols[theSymbol].currentPrice,1);
     }
 }
 
 
 // drawYlabels - draws the label for the minimum and maximum stock values, 
 // and 2 values in between. 
 //
 //    theSymbol - the symbol object that contains the max and min Y values
 
 function drawYLabels(theSymbol){
     var theValue = 0.0;
     clearInterval(blinker);
     chart.DrawText("",100,100,255,102,0);    // nudge the java applet
     chart.DrawRect(10,60,50,30,0,0,0);       // erase the blinking text
     chart.DrawText(symbols[theSymbol].minY,leftOffset - 45,bottom - 
         bottomOffset + 5,255,102,0);
     var chunkSize = 0.0;
     chunkSize = (symbols[theSymbol].maxY - symbols[theSymbol].minY)/4;
     for (var i=1;i<4;i++){
         theValue = chunkSize * i;
         theValue = theValue + parseFloat(symbols[theSymbol].minY);
         theValue = pround(theValue,2);
         chart.DrawText("",100,100,255,102,0);
         chart.DrawText(theValue,leftOffset - 45,bottom - bottomOffset - 25 * i +
             5,255,102,0);
         chart.DrawLine(leftOffset,bottom - bottomOffset - i * 25,right - 
             rightOffset + 5,bottom - bottomOffset - i * 25,40,40,40);
     }
     chart.DrawLine(leftOffset,topOffset,right - rightOffset + 5,topOffset,40,40,40);
     chart.DrawText(symbols[theSymbol].maxY,leftOffset - 45,top + topOffset +
         5,255,102,0);
 }
 
 
 // translateX - converts a minutes after midnight value into an X coordinate
 function translateX(theValue){
     var value = Math.round((theValue - startTime) * (( right - leftOffset -
         rightOffset +20)/420));
     return (value <= 0) ? (( right - leftOffset - rightOffset +20)/420) : value;
 }
 
 // translateY - converts a stock value into a Y coordinate
 
 function translateY(theValue,theSymbol){
     var theDiff = symbols[theSymbol].maxY - symbols[theSymbol].minY;
     return parseInt((100 * (1.0 - (theValue - symbols[theSymbol].minY) / theDiff)));
 }
 
 // TDCcomplete - gets called when TDC is done loading data - setting the
 // state to dataWaiting 
 // should hold off any asynchronous functions from firing
 
 function TDCcomplete(){
     theState = "dataWaiting";
 }
 
 // Symbol - This is the constructor for a symbol object.
 // 
 //    aTDC -  a Tabular Data Control (TDC) object that will contain the
 //        data for this symbol
 //    aSPAN -    the SPAN tag that contains the link that makes the symbol active
 //    aDataURL- the URL for the data that gets loaded into The TDC
 
 function Symbol (aTDC,aSPAN,aDataURL){
     this.loaded = 0;
     this.TDC = aTDC;
     this.SPAN = aSPAN;
     this.dataURL = aDataURL;
     this.data = new Array();
     this.maxY = 0;
     this.minY = 0;
     this.numRecords = 0;
     this.lastTime = 0;
     this.currentPrice = 0;
     return this;
 }
 
 
 // Point - a simple object that holds and X,Y coordinate
 //
 //    aX - the X coordinate
 //    aY - the Y coordinate
 
 function Point (aX,aY){
     this.x = aX;
     this.y = aY;
 }
 
 
 function blinkme(){
     if (!blink) {
         chart.DrawText("",100,100,255,102,0);        // nudge the java applet
         chart.DrawText("Loading",20,75,255,102,0);
         blink = 1;
     } else {
         chart.DrawText("",100,100,255,102,0);        // nudge the java applet
         chart.DrawRect(10,60,50,30,0,0,0);    // erase the text
         blink = 0;
     }
 }
 
 
 // pround - precision round function (written by Bob Hartlaub)
 // returns the rounded number
 //    
 //    num - the number to round
 //    precision - how many decimal places to round to
 
 function pround(num, precision){
     num = parseFloat(num);                         // turn num into a number
     rndprec = Math.pow(10,(precision));
     num = ((Math.round(num * rndprec)) / rndprec); // round to precision amount
 
     //Fix code to avert javascript 1.2 internal precision problem
     p_dot   = /\./;      // determine length of return string before doing addition
     numstr = num.toString(); 
     numstr_parts = numstr.split(p_dot);
     pnum_prec_length = numstr_parts[0].length + precision + 1;// pre-decimal +
         precision + decimal point
     // end of fix
 
     var pr = Math.pow( 10,(0 - precision - 1) ); // create precision # + xtra tenth
     var pn = num + pr;  
     var pnum = pn.toString();                // turn value to a string
     var pnumtrunc = pnum.substring(0, pnum_prec_length);    // truncate xtra tenth
     return  pnumtrunc; 
 }