/**
 * :::ZOOLTIPS:::
 * ##############
 * A tool that adds zooming and tooltip information to charts.
 * ##############
 * This JavaScript and associated zooltips.css Stylesheet
 * plus the example custom zooltips_custom.css and
 * zooltips_custom.js were created during 2005 and 2006 by 
 * Philip Jack Hanna.
 * Do Not Remove This.
 * The code has been donated for use to Hemscott.
 */
/**
 * Update: Firefox 1.5+ flicking tooltips fix added by Hemscott.
 */


/*****************************create required holding objects**********************************/

//Object to hold info about the browser
function BrowserObj(){
  this.agt = navigator.userAgent.toLowerCase();
  this.mac=(this.agt.indexOf('mac')!=-1)?true:false;
  verReal = navigator.appVersion;
  this.verFloat = parseFloat(verReal);
  this.app = navigator.appName.toLowerCase();
  this.ie = (this.agt.indexOf('msie') != -1 || this.agt.indexOf('microsoft') != -1)?true:false;
  //Since version 5.5 of IE there's been a miss calculation of offsetTop(offsetLeft)/offsetParent resulting in a doubling of the true figure.
  //Below we assume the bug continues in ie7+.  If it doesn't some zoom lines will be miss placed so an extra condition should be added to the below.
  this.containerDivOffsetBuggy =  (this.ie && this.verFloat >= 5.5)?true:false;
  this.documentOffsetFix = 0
  if(this.ie && !this.mac){
    this.documentOffsetFix = 2
  }
}

//This object is the only one that should be accessed to customise behaviour and features.
function CustomSettingsObj(){
  this.msgUsingWriteToDiv = true;

  this.trackingDivsMouseOffsetX = 12;
  this.trackingDivsMouseOffsetY = 18;
  this.trackingDivsMouseOffsetReverseX = 10;
  this.trackingDivsMouseOffsetReverseY = 10;

  this.showTooltipsHorizontalLine = true;
  this.tooltipsHorizontalLineLeftExtension = 0;

  this.showZoomInfoDiv = true;//the dynamicSettingsObj version is the one used in code.
  this.nameZoomIdentifiersByPosition = false;//when false use identifier name

  this.showCorrectDateTimesArea0Date1 = false;
  this.showArea0Date1Comparators = true;//all of them.  if true some may also be set false individually.
  this.showCorrectDateTimesArea1Date0 = false;
  this.showArea1Date0Comparators = true;//i.e show volumes.

  this.postCurrency = 'p';
  this.preCurrency = '';

  this.rebasedText = ' (rebased) ';
  this.disableRebasedText = function(){
    this.rebasedText = ' ';
  }

  this.renameDayClose = true;
  this.dayCloseTime = '16:35';
  
  this.boundedMethod = false;
  this.boundedMethodAlternative = false;//See bottom for info
  this.boundedMethodExtendX = 0;
  this.boundedMethodExtendY = 0;
  
  this.zoomInfoOutMoveMethod = true;
  this.mouseOutCancelZoomMethod = false;

  this.bannedIdentifiers = new Array();
  this.addBannedIdentifier = function(bi){
    this.bannedIdentifiers[this.bannedIdentifiers.length] = bi;
  }
  this.removeBannedIdentifier = function(bi){
    for(lookI=0;lookI<this.bannedIdentifiers.length;lookI++){
      if(bi == this.bannedIdentifiers[lookI]){
        this.bannedIdentifiers[lookI] = '_REMOVE_FROM_ARRAY_';
      }
    }
  }
  this.addBannedIdentifier('*@IT');
  this.changeTooltipIdentifierNames = new Array(); 
  this.changeTooltipIdentifierName = function(symbolMarket,newName){
    this.changeTooltipIdentifierNames[this.changeTooltipIdentifierNames.length] = new Array(symbolMarket,newName);
  }
}
//For IE we use a different boundedMethod.  This is only called if boundedMethod is true
function setBoundedMethodAlternative(){
  //basically if boundedMethod is true which it is if we are called
  //the boundedMethodAlternative should be true if we are using ie.
  customSettingsObj.boundedMethodAlternative = browserObj.ie;
}

//This function is called onload so that the CustomSettingsObj can have it's values changed.
function loadCustomSettingsObj(){
  //overwritable function.
  //use e.g customSettingsObj.showCorrectDateTimesArea1Date0 = false;
}
function ServiceObj(){
  //overwritten by zooltips.xsl
  //These are declared here because they do not need to be declared in zooltips.xsl
  //If there is not area1 (second area) then they are not declared.
  this.area1XLeft   = -1;
  this.area1XRight  = -1; 
  this.area1YTop    = -1;
  this.area1YBottom = -1;
}

function DynamicSettingsObj(){
  //these settings are set at loadtime
  this.intraday = serviceObj.intraday;
  this.zoomAllowed = !this.intraday;//don't allow zooming if intraday.
  this.numberOfZoombarsObjs = serviceObj.zoombarsHoldingObjs.length;
  this.numberOfTooltipsLineObjs = serviceObj.tooltipsLineHoldingObjs.length;
  this.showTooltipsHorizontalLineObj = customSettingsObj.showTooltipsHorizontalLine && (this.numberOfTooltipsLineObjs > 0);
  this.noTooltipsEntities = toolTipsEntitiesObj.toolTipsEntities.length;
  //assume that if there are any tooltips there will be exactly 1 Area0Date0Objs.
  //note, similar assumptions are made in ToolTipsEntitiesObj construction.
  //AreaXDateXObjs are the divs that show the datetime on the tooltips.
  //there are several of these because many x-coordinates correspond to different datetimes for particular lines.
  this.noTooltipsChartArea0Date0Objs = (this.noTooltipsEntities > 0) ? 1 : 0;
  if(this.noTooltipsEntities-1 >= 0){
    //if other than main line there is another tooltip and the last ones id is volume then we have 1 Area1Date0.
    //(PHIL potential problem if Area1 (area one) is ever used for non-volumes, the id isn't volume or we need more than 1 date on Area1)
    this.noTooltipsChartArea1Date0Objs = (toolTipsEntitiesObj.toolTipsIdentifiers[this.noTooltipsEntities-1][1] == 'volume') ? 1:0;//currently max 1. volume.
  }
  this.noTooltipsChartArea0Date1Objs = this.noTooltipsEntities - this.noTooltipsChartArea1Date0Objs - this.noTooltipsChartArea0Date0Objs;
  this.showZoomInfoDiv = customSettingsObj.showZoomInfoDiv && this.numberOfZoombarsObjs > 0;
  this.existsAndShowDateTimesArea0Date1 = customSettingsObj.showCorrectDateTimesArea0Date1 && this.intraday && this.noTooltipsChartArea0Date1Objs > 0;
  this.existsAndShowDateTimesArea1Date0 = customSettingsObj.showCorrectDateTimesArea1Date0 && this.noTooltipsChartArea1Date0Objs > 0;
}

function ZoombarsObj(i){
  this.startZoomDiv = getObject('startZoomDiv'+i);
  this.dragZoomDiv = getObject('dragZoomDiv'+i);
  this.endZoomDiv = getObject('endZoomDiv'+i);
  setObjectStyleProperty(this.startZoomDiv,'top',serviceObj.zoombarsHoldingObjs[i].top);
  setObjectStyleProperty(this.dragZoomDiv,'top',serviceObj.zoombarsHoldingObjs[i].top);
  setObjectStyleProperty(this.endZoomDiv,'top',serviceObj.zoombarsHoldingObjs[i].top);
  setObjectStyleProperty(this.startZoomDiv,'height',serviceObj.zoombarsHoldingObjs[i].height);
  setObjectStyleProperty(this.dragZoomDiv,'height',serviceObj.zoombarsHoldingObjs[i].height);
  setObjectStyleProperty(this.endZoomDiv,'height',serviceObj.zoombarsHoldingObjs[i].height);
}
function TooltipsLineObj(i){
  this.tooltipsLineDiv = getObject('tooltipsLineDiv'+i);
  setObjectStyleProperty(this.tooltipsLineDiv,'top',serviceObj.tooltipsLineHoldingObjs[i].top);
  setObjectStyleProperty(this.tooltipsLineDiv,'height',serviceObj.tooltipsLineHoldingObjs[i].height);
}
function TooltipsHorizontalLineObj(){
  this.tooltipsHorizontalLineDiv = getObject('tooltipsHorizontalLineDiv');
  setObjectStyleProperty(this.tooltipsHorizontalLineDiv,'left',(parseInt(serviceObj.tooltipsHorizontalLineHoldingObj.left) - customSettingsObj.tooltipsHorizontalLineLeftExtension));
  setObjectStyleProperty(this.tooltipsHorizontalLineDiv,'width',(parseInt(serviceObj.tooltipsHorizontalLineHoldingObj.width) + customSettingsObj.tooltipsHorizontalLineLeftExtension));
}
function TooltipsObj(identifier){
  this.tooltipsDiv = getObject('tooltipsDiv_'+identifier);
  this.tooltipsContent = null;
}
function ElementsRefObj(){
  this.msgDiv = getObject('msgDiv');//Useful to keep though not neccessarily used.
  this.bodyTag = getObjectByTagName('body',0);
  this.graphDiv = getObject('graphDiv');
  this.containOverGraphElementsDiv = getObject('containOverGraphElementsDiv');
  this.zoombarsObjs = new Array();
  this.tooltipsLineObjs = new Array();
  this.tooltipsObjs = new Array();
  for(i=0;i<dynamicSettingsObj.numberOfZoombarsObjs;i++){
    this.zoombarsObjs[i] = new ZoombarsObj(i);
  }
  if(dynamicSettingsObj.showZoomInfoDiv){
    this.containZoomInfoDiv = getObject('containZoomInfoDiv');
    this.zoomInfoContainerDiv = getObject('zoomInfoContainerDiv');
    this.zoomInfoDiv = getObject('zoomInfoDiv');
    //I don't know why but i seem to need to move zoomInfoContainerDiv.  containZoomInfoDiv doesn't go past 
    //the edge of graphDiv.  Only in Firefox.  I can't see why but this works. containZoomInfoDiv has the more accurate width.
    this.zoomInfoMoveThisDiv = this.zoomInfoContainerDiv;  //getObject('zoomInfoMoveThisDiv');
    if(customSettingsObj.boundedMethodAlternative){
      this.zoomInfoWidthFinderDiv = getObject('zoomInfoWidthFinderDiv');
    }
  }
  for(i=0;i<dynamicSettingsObj.numberOfTooltipsLineObjs;i++){
    this.tooltipsLineObjs[i] = new TooltipsLineObj(i);
  }
  if(dynamicSettingsObj.showTooltipsHorizontalLineObj){
    this.tooltipsHorizontalLineObj = new TooltipsHorizontalLineObj();
  }
  //Note: toolTipsEntitiesObj.toolTipsIdentifiers.length and toolTipsEntitiesObj.toolTipsEntities.length must (and as long as the xml is correct will) be the same.
  //Note: above is wrong! Do'h.  If there is no tooltips data the main identifier line still written so one is 1 and the other is 0.  Entities, 0 is correct.
  this.containTooltipsDiv = getObject('containTooltipsDiv');
  this.tooltipsMoveThisDiv = getObject('tooltipsMoveThisDiv');
  if(customSettingsObj.boundedMethodAlternative){
    this.tooltipsWidthFinderDiv = getObject('tooltipsWidthFinderDiv');
  }
  //the first toolTipsEntitiesObj.toolTipsIdentifiers.length-1 tooltipsObjs's are the ones for prices/volumes etc..
  for(i=0;i<dynamicSettingsObj.noTooltipsEntities;i++){
    this.tooltipsObjs[i] = new TooltipsObj(toolTipsEntitiesObj.toolTipsIdentifiers[i][1]);
  }
  //later tooltipsObjs are for dates
  //the currently accurate assumption is that there can be different dates attached to each line at the same x pixel.
  //e.g. at pixel x=100 the main line could be showing a price for 10:32, a comparison could be showing a price for 10:30 
  //and the volumes could be showing a value for 10:25-10:30.  However all comparison (or if ever applicable all volumes)
  //will be showing the same dates. i.e all comparisons are for dates 10:30. Thus the graph is split into sub areas 
  //0 (e.g. main and comparison prices) and 1 (volumes) and also area-sub-areas (0-0 is main line, 0-1 is comparisons).
  if(dynamicSettingsObj.noTooltipsChartArea0Date0Objs > 0){
    this.tooltipsObjs["ChartArea0Date0"] = new TooltipsObj("ChartArea0Date0");
  }
  if(dynamicSettingsObj.existsAndShowDateTimesArea0Date1){
    this.tooltipsObjs["ChartArea0Date1"] = new TooltipsObj("ChartArea0Date1");
  }
  if(dynamicSettingsObj.existsAndShowDateTimesArea1Date0){
    this.tooltipsObjs["ChartArea1Date0"] = new TooltipsObj("ChartArea1Date0");
  } 
  this.chartForm = getObject('chartForm');
}
function ZoombarsHoldingObj(){
  this.top = 0;
  this.height = 0;
}
function TooltipsLineHoldingObj(){
  this.top = 0;
  this.height = 0;
}
function TooltipsHorizontalLineHoldingObj(){
  this.left = 0;
  this.width = 0;
}
//i've put this function here even though it's not an object as it mirrors ElementsRefObj closely
function createObjs(){
  //Note this is a not the ideal solution and it's here because of an IE Bug.  
  //In IE if you remove an image from the DOM before it's finished loading 
  // - even if you add it back later - the page will appear to never finish loading.
  //Here I would like to clone graphImg then replace it with the clone wrapped in the extra divs.
  //However what I do is create a copy based on it's properties (only the one's I want so less than the clone 
  //which would have got them all) and then display none the existing graph but add an onload event to it
  //which when it loads removes it from the DOM so there is only one of them.  This is done by the hideGraphImg()
  //function which is called near the end of this function.
  //Also using insertBefore instead of replaceChild
  
  oldGraphImg = getObject('graphImg');
  //newGraphImg = oldGraphImg.cloneNode(true);
  newGraphImg = createElementAndAttributes('img',new Array(new Array('id','newGraphImg'),
                                                        new Array('src',oldGraphImg.src),
                                                        new Array('title',oldGraphImg.title),
                                                        new Array('border',oldGraphImg.border),
                                                        new Array('height',oldGraphImg.height),
                                                        new Array('width',oldGraphImg.width),
                                                        new Array('longDesc',oldGraphImg.longDesc)));
  
  graphDiv = createElementAndAttributes('div',new Array(new Array('id','graphDiv'),new Array('onmousedown','return false;'),new Array('onmousemove','return false;')));
  graphDiv.style.cssText = 'width: ' + graphDetailsObj.chartWidth + 'px;'
  mainContainerDiv = createElementAndAttributes('div',new Array(new Array('id','containOverGraphElementsDiv')));
  //add zoom elements
  for(i=0;i<dynamicSettingsObj.numberOfZoombarsObjs;i++){
    mainContainerDiv.appendChild(createElementAndAttributes('div',new Array(new Array('id','startZoomDiv' + i))));
    mainContainerDiv.appendChild(createElementAndAttributes('div',new Array(new Array('id','dragZoomDiv' + i))));
    mainContainerDiv.appendChild(createElementAndAttributes('div',new Array(new Array('id','endZoomDiv' + i))));
  }
  if(dynamicSettingsObj.showZoomInfoDiv){
    /*
    It zoomInfoDiv should only require 1 container but to get the proper styling a second is needed and to have 
    a seperate one for the boundedMethod2 problem we have another.
    Structure
    zoomInfoMoveThisDiv
      containZoomInfoDiv
        zoomInfoContainerDiv
          zoomInfoDiv
      zoomInfoWidthFinderDiv
    */
    moveThisZoomInfoDiv = createElementAndAttributes('div',new Array(new Array('id','zoomInfoMoveThisDiv')));
    mainZoomInfoDiv = createElementAndAttributes('div',new Array(new Array('id','containZoomInfoDiv')));
    secondZoomInfoDiv = createElementAndAttributes('div',new Array(new Array('id','zoomInfoContainerDiv')));
    secondZoomInfoDiv.appendChild(createElementAndAttributes('div',new Array(new Array('id','zoomInfoDiv'))));
    mainZoomInfoDiv.appendChild(secondZoomInfoDiv);
    moveThisZoomInfoDiv.appendChild(mainZoomInfoDiv);
    if(customSettingsObj.boundedMethodAlternative){
      widthFinderZoomInfoDiv = createElementAndAttributes('div',new Array(new Array('id','zoomInfoWidthFinderDiv')));
      widthFinderZoomInfoDiv.style.cssText = 'position:absolute; right:0px; bottom:0px;';
      widthFinderZoomInfoDiv.appendChild(createElementAndAttributes('img',new Array(new Array('height','1'),new Array('width','1'))));
      moveThisZoomInfoDiv.appendChild(widthFinderZoomInfoDiv);
    }
    mainContainerDiv.appendChild(moveThisZoomInfoDiv);
  }
  //add tooltip line elements
  for(i=0;i<dynamicSettingsObj.numberOfTooltipsLineObjs;i++){
    mainContainerDiv.appendChild(createElementAndAttributes('div',new Array(new Array('id','tooltipsLineDiv' + i))));
  }
  if(dynamicSettingsObj.showTooltipsHorizontalLineObj){
    tooltipsHorizontalLineDiv = createElementAndAttributes('div',new Array(new Array('id','tooltipsHorizontalLineDiv')));
    tooltipsHorizontalLineDiv.appendChild(createElementAndAttributes('img',new Array(new Array('height','1'),new Array('width','1'))));
    mainContainerDiv.appendChild(tooltipsHorizontalLineDiv);
  }
  //add tooltip elements
  /*
    Structure
    tooltipsMoveThisDiv
      containTooltipsDiv
        tooltipsContainerDiv
          tooltipsDiv_*
      tooltipsWidthFinderDiv
    */
  //OLD:::: mainTooltipsDiv (id containTooltipsDiv) is used for moving, tracking, hidding, showing, finding it's width etc
  //NEW:::: mainTooltipsDiv (id containTooltipsDiv) nolonger used for moving, tracking or showing.
  //NEW:::: tooltipsMoveThisDiv is now used for  moving, tracking and showing.
  //secondTooltipsDiv (tooltipsContainerDiv) is what styles are applied to.  It's just to hold all the different line and date divs.
  //We keep them seperate as styles mess up things like width calculations.
  //containTooltipsDiv Does not contain tooltip line Divs.
  moveThisTooltipsDiv = createElementAndAttributes('div',new Array(new Array('id','tooltipsMoveThisDiv')));
  mainTooltipsDiv = createElementAndAttributes('div',new Array(new Array('id','containTooltipsDiv')));
  secondTooltipsDiv = createElementAndAttributes('div',new Array(new Array('id','tooltipsContainerDiv')));
  if(dynamicSettingsObj.noTooltipsChartArea0Date0Objs > 0){
    secondTooltipsDiv.appendChild(createElementAndAttributes('div',new Array(new Array('id','tooltipsDiv_ChartArea0Date0'))));
  }
  if(dynamicSettingsObj.noTooltipsEntities > 0){
    secondTooltipsDiv.appendChild(createElementAndAttributes('div',new Array(new Array('id','tooltipsDiv_' + toolTipsEntitiesObj.toolTipsIdentifiers[0][1]),new Array('class','tooltipsInfo'))));
  }
  if(dynamicSettingsObj.existsAndShowDateTimesArea0Date1){
    secondTooltipsDiv.appendChild(createElementAndAttributes('div',new Array(new Array('id','tooltipsDiv_ChartArea0Date1'))));
  }
  for(i=1;i<=dynamicSettingsObj.noTooltipsChartArea0Date1Objs;i++){
    secondTooltipsDiv.appendChild(createElementAndAttributes('div',new Array(new Array('id','tooltipsDiv_' + toolTipsEntitiesObj.toolTipsIdentifiers[i][1]),new Array('class','tooltipsInfo'))));
  }
  if(dynamicSettingsObj.existsAndShowDateTimesArea1Date0){
    secondTooltipsDiv.appendChild(createElementAndAttributes('div',new Array(new Array('id','tooltipsDiv_ChartArea1Date0'))));
  }
  for(i=dynamicSettingsObj.noTooltipsChartArea0Date1Objs+1;i<=(dynamicSettingsObj.noTooltipsChartArea0Date1Objs+dynamicSettingsObj.noTooltipsChartArea1Date0Objs);i++){
    secondTooltipsDiv.appendChild(createElementAndAttributes('div',new Array(new Array('id','tooltipsDiv_' + toolTipsEntitiesObj.toolTipsIdentifiers[i][1]),new Array('class','tooltipsInfo'))));
  }
  mainTooltipsDiv.appendChild(secondTooltipsDiv);
  moveThisTooltipsDiv.appendChild(mainTooltipsDiv);
  if(customSettingsObj.boundedMethodAlternative){
    widthFinderTooltipsDiv = createElementAndAttributes('div',new Array(new Array('id','tooltipsWidthFinderDiv')));
    widthFinderTooltipsDiv.style.cssText = 'position:absolute; right:0px; bottom:0px;';
    widthFinderTooltipsDiv.appendChild(createElementAndAttributes('img',new Array(new Array('height','1'),new Array('width','1'))));
    moveThisTooltipsDiv.appendChild(widthFinderTooltipsDiv);
  }
  mainContainerDiv.appendChild(moveThisTooltipsDiv);
  graphDiv.appendChild(mainContainerDiv);
  graphDiv.appendChild(newGraphImg);
  //We want a msgDiv for messages and debugging but it cannot go inside graphDiv as this needs to keep it's size.
  //So we put graphDiv in a dummy div and create msgDiv inside this as a sibling of graphDiv.
  rootDiv = createElementAndAttributes('div',new Array(new Array('id','graphRootDiv')));
  rootDiv.appendChild(graphDiv);
  rootDiv.appendChild(createElementAndAttributes('div',new Array(new Array('id','msgDiv'))));
  oldGraphImgParent = oldGraphImg.parentNode
  graphImgNextSibling = oldGraphImg.nextSibling; //can be null but is null after hideGraphImg hence variable.
  hideGraphImg();
  oldGraphImgParent.insertBefore(rootDiv,graphImgNextSibling);
}
//have this here because it's part of the IE bug fix.  We don't replace the old graphImg, 
//we hide it then remove it when it has loaded.
function hideGraphImg(){
  oldGraphImg = getObject('graphImg').style.display = 'none';
  //and onload event is added to this to remove it from the DOM once loaded.
}
//opera fails to set the style's on creation so we do it here. 
//This again shouldn't be at this position but is because it's relevant to createObjs
function operaFix(){ //for opera
  elementsRefObj.graphDiv.style.width = graphDetailsObj.chartWidth + 'px';
}
//again not a holding obj but relevant to createObjs
function createElementAndAttributes(tagName,attributes){
  newElement = document.createElement(tagName);
  if(attributes != null){
    for(attI=0;attI<attributes.length;attI++){
      newElement.setAttribute(attributes[attI][0],attributes[attI][1]);
    }
  }
  return newElement;
}

function DynamicBrowserObj(){
  this.containerDivOffsetToDocument = getContainerDivOffsetToDocument();//find out the offset position of the tooltips container relative to the document.
  this.mouseToDocument = [0,0];
  //containerDiv is positioned at the same place as graphDiv.  mouseToGraph is updated with a figure that is actually mouse to containerDiv but the name mouseToGraph is easier to understand.
  //containerDiv (containOverGraphElementsDiv) is a special div that noone should ever edit which is placed directly inside graphDiv.
  this.mouseToGraph = [0,0];
  this.mouseToGraphLtd = [0,0];//limited to chart area.  cannot be before or after the chart part of the image.
  this.insideChartXArea = false;
  this.insideChartYArea = false;
  this.update = function(e){
    this.containerDivOffsetToDocument = getContainerDivOffsetToDocument()
    this.mouseToDocument = mouseCoordsToDocument(e);
    //These originally included  + 1 - this.lineThicknessOffsetX and Y to allow for different width's lines.
    //However this flexibility is probably over the top so I have removed it.
    this.mouseToGraph = [this.mouseToDocument[0] - this.containerDivOffsetToDocument[0],
                         this.mouseToDocument[1] - this.containerDivOffsetToDocument[1]];
    if(this.mouseToGraph[0] < graphDetailsObj.startX){
       this.mouseToGraphLtd[0] = graphDetailsObj.startX;
       this.insideChartXArea = false;
    } else if(this.mouseToGraph[0] > graphDetailsObj.endX){
       this.mouseToGraphLtd[0] = graphDetailsObj.endX;
       this.insideChartXArea = false;
    } else {
       this.mouseToGraphLtd[0] = this.mouseToGraph[0];
       this.insideChartXArea = true;
    }
    //This sets the mouseToGraphLtd Y value and determies the boolean insideChartYArea
    //This could be made smaller by combining the first if else blocks but it's simpler
    //to understand if they are seperate.
    if(!graphDetailsObj.area1Exists){
      //if above graph area
      if(this.mouseToGraph[1] < graphDetailsObj.area0YTop){
         this.mouseToGraphLtd[1] = graphDetailsObj.area0YTop;
         this.insideChartYArea = false;
      //if below graph area
      } else if(this.mouseToGraph[1] > graphDetailsObj.area0YBottom){
         this.mouseToGraphLtd[1] = graphDetailsObj.area0YBottom;
         this.insideChartYArea = false;
      //else inside graph area.
      } else {
         this.mouseToGraphLtd[1] = this.mouseToGraph[1];
         this.insideChartYArea = true;
      }
    } else {
      //if above first graph area
      if(this.mouseToGraph[1] < graphDetailsObj.area0YTop){
         this.mouseToGraphLtd[1] = graphDetailsObj.area0YTop;
         this.insideChartYArea = false;
      //if below second graph area
      } else if(this.mouseToGraph[1] > graphDetailsObj.area1YBottom){
         this.mouseToGraphLtd[1] = graphDetailsObj.area1YBottom;
         this.insideChartYArea = false;
      //if in between bottom of first graph area and top of second graph area
      } else if(this.mouseToGraph[1] > graphDetailsObj.area0YBottom && this.mouseToGraph[1] < graphDetailsObj.area1YTop){
         this.mouseToGraphLtd[1] = graphDetailsObj.area1YTop;
         this.insideChartYArea = false;
      //else inside one of the graph area's
      } else {
         this.mouseToGraphLtd[1] = this.mouseToGraph[1];
         this.insideChartYArea = true;
      }
    }
  }
}

function ZoomTrackingObj(){
  this.startX = 0;
  this.endX = 0;
  this.zoomWidth = 1;
  this.inZoomProcess = false;
  this.doneZoom = false;
  this.startDate = null;
  this.endDate = null;
  this.dateChangeAmount = '0 Days';
  //x,y position, offset from mouse and might be bounded
  this.xOffsetBounded = 0;
  this.yOffsetBounded = 0;
  this.setStartDate = function(newDate){
    newDate.setHours(0);
    newDate.setMinutes(0);
    newDate.setMilliseconds(0);
    this.startDate = newDate;
  }
  this.setEndDate = function(newDate){
    newDate.setHours(23);
    newDate.setMinutes(59);
    newDate.setMilliseconds(999);
    this.endDate = newDate;
    this.dateChange();
  }
  this.dateChange = function(){
    if(zoomTrackingObj.startDate != null && zoomTrackingObj.endDate != null){
      this.dateChangeAmount = Math.abs(Math.floor((this.endDate - this.startDate)/86400000)) + ' Days';
    }
  }
  this.update = function(){
    if(!customSettingsObj.boundedMethod){
      //offset from mouse and standard below and right
      this.xOffsetBounded = dynamicBrowserObj.mouseToGraph[0] + customSettingsObj.trackingDivsMouseOffsetX;
      this.yOffsetBounded = dynamicBrowserObj.mouseToGraph[1] + customSettingsObj.trackingDivsMouseOffsetY;
    } else {
      zoomBoundedMethodObj.update();
      //reverse mode above and/or to the left
      if(zoomBoundedMethodObj.objOutsideGraphDivX){
        this.xOffsetBounded = zoomBoundedMethodObj.reverseX;
      } else {
        this.xOffsetBounded = dynamicBrowserObj.mouseToGraph[0] + customSettingsObj.trackingDivsMouseOffsetX;
      }
      if(zoomBoundedMethodObj.objOutsideGraphDivY){
        this.yOffsetBounded = zoomBoundedMethodObj.reverseY;
      } else {
        this.yOffsetBounded = dynamicBrowserObj.mouseToGraph[1] + customSettingsObj.trackingDivsMouseOffsetY;
      }
    }
  }
}
function TooltipsTrackingObj(){
  this.x = 0;
  this.y = 0;
  //x,y position, offset from mouse and might be bounded
  this.xOffsetBounded = 0;
  this.yOffsetBounded = 0;
  this.update = function(){
    this.x = dynamicBrowserObj.mouseToGraphLtd[0];
    this.y = dynamicBrowserObj.mouseToGraphLtd[1];
    //if not boundedMethod (most likely) or the boundedMethod would result in a negative co-ordinate
    if(!customSettingsObj.boundedMethod){
      //offset from mouse and standard below and right
      this.xOffsetBounded = dynamicBrowserObj.mouseToGraph[0] + customSettingsObj.trackingDivsMouseOffsetX;
      this.yOffsetBounded = dynamicBrowserObj.mouseToGraph[1] + customSettingsObj.trackingDivsMouseOffsetY;
    } else {
      tooltipsBoundedMethodObj.update();
      //reverse mode above and/or to the left
      if(tooltipsBoundedMethodObj.objOutsideGraphDivX){
        this.xOffsetBounded = tooltipsBoundedMethodObj.reverseX;
      } else {
        this.xOffsetBounded = dynamicBrowserObj.mouseToGraph[0] + customSettingsObj.trackingDivsMouseOffsetX;
      }
      if(tooltipsBoundedMethodObj.objOutsideGraphDivY){
        this.yOffsetBounded = tooltipsBoundedMethodObj.reverseY;
      } else {
        this.yOffsetBounded = dynamicBrowserObj.mouseToGraph[1] + customSettingsObj.trackingDivsMouseOffsetY;
      }
    }
  }
  this.TooltipsChartArea0Date0ObjsAllNull = true;
  this.TooltipsChartArea0Date1ObjsAllNull = true;
  this.TooltipsChartArea1Date0ObjsAllNull = true;
  this.TooltipsChartAllAreaObjsAllNull = true;
}

//This is possibly not the best way to integrate the bounded method functionality as there is a little
//bit of repeated code.  However this is neater keeping it in one place and also the bounded method is
//rarely used so it's better kept as seperate as possible so that there is bounded method code being 
//checked all the time for the majority of cases.
//This Object is only created and used if we are in boundedMethod mode.
function BoundedMethodObj(obj,alternateObj){
  //for alternateObj info see boundedMethodAlternate comment at the bottom.
  //using external to this object;
  //graphDetailsObj.chartWidth and graphDetailsObj.chartHeight
  //customSettingsObj.trackingDivsMouseOffsetX and customSettingsObj.trackingDivsMouseOffsetY
  //customSettingsObj.trackingDivsMouseOffsetReverseX and customSettingsObj.trackingDivsMouseOffsetReverseY
  //dynamicBrowserObj.mouseToGraph[x,y];
  this.reverseX = 0;
  this.reverseY = 0;
  this.objWidth  = 0;
  this.objHeight = 0;
  this.objOutsideGraphDivX = false;
  this.objOutsideGraphDivY = false;
  this.objMaxOffsetX = 0;
  this.objMaxOffsetY = 0;
  this.update = function(){
    if(customSettingsObj.boundedMethodAlternative){
      //this is semi-evil.  To find the width of the obj we have alternateObj stuck to it's bottom right corner
      //and we find it offsetLeft or Top +1. see boundedMethodAlternate comment at the bottom.
      //Infact the +1 is not really needed as without it the visible part would still be ok but it is necessary to
      //ensure none of the div even the invisible widthFinder goes over the edge.
      //It still seems to not be the correct number and i don't have the time to figure out why so i'm adding a bit more.
      //Oh i hate this 'fix'.
      this.objWidth  = alternateObj.offsetLeft + 3; //( + 1 + 2); //yuck i'm even putting browser code in here now.  
      this.objHeight = alternateObj.offsetTop + 3; //(+ 1 + 2); 
    } else {
      this.objWidth  = getIntObjectStyleProperty(obj,'width' , true);
      this.objHeight = getIntObjectStyleProperty(obj,'height', true);
    }
    this.maxAllowedOffsetX = graphDetailsObj.chartWidth - this.objWidth + customSettingsObj.boundedMethodExtendX;
    this.maxAllowedOffsetY = graphDetailsObj.chartHeight - this.objHeight + customSettingsObj.boundedMethodExtendY;
    //if far right edge of the tooltips is past the graph edge
    //or
    //if the lower edge of the tooltips is past the graph edge 
    this.objOutsideGraphDivX = dynamicBrowserObj.mouseToGraph[0] + customSettingsObj.trackingDivsMouseOffsetX > this.maxAllowedOffsetX;
    this.objOutsideGraphDivY = dynamicBrowserObj.mouseToGraph[1] + customSettingsObj.trackingDivsMouseOffsetY > this.maxAllowedOffsetY;
    this.reverseX = dynamicBrowserObj.mouseToGraph[0] - (this.objWidth + customSettingsObj.trackingDivsMouseOffsetReverseX);
    this.reverseY = dynamicBrowserObj.mouseToGraph[1] - (this.objHeight + customSettingsObj.trackingDivsMouseOffsetReverseY);
    //PHIL idea is this should stop with the tooltips stuck to the right side.
    if(this.reverseX < 0 - customSettingsObj.boundedMethodExtendX){
      this.reverseX = this.maxAllowedOffsetX;
    }
    if(this.reverseY < 0 - customSettingsObj.boundedMethodExtendY){
      this.reverseY = this.maxAllowedOffsetY;
    }
  }
}

function GraphDetailsObj(){
  this.startX = parseInt(serviceObj.startX);
  this.endX = parseInt(serviceObj.endX);
  this.area0XLeft = parseInt(serviceObj.area0XLeft);
  this.area0YTop = parseInt(serviceObj.area0YTop);
  this.area0YBottom = parseInt(serviceObj.area0YBottom);
  this.area1YTop = parseInt(serviceObj.area1YTop);
  this.area1YBottom = parseInt(serviceObj.area1YBottom);
  this.startDateMs = parseInt(serviceObj.startDateMs);
  this.endDateMs = parseInt(serviceObj.endDateMs);
  this.duration = parseInt(this.endDateMs - this.startDateMs);
  this.msPerPixel = this.duration / (this.endX - this.startX);
  this.chartSrc = serviceObj.chartSrc;
  this.chartWidth = parseInt(serviceObj.chartWidth);
  this.chartHeight = parseInt(serviceObj.chartHeight);
  this.area1Exists = true;
  //if either area1YTop or area1YBottom are -1 then it's because they don't exist meaning area1 doesn't exist.
  //e.g. tsr when they are not declared in the xsl.
  //if these are 0 then it is because we are on a graph that could have them so the declaration is there but 
  //area1 is turned off so they become 0. check for '' just in case too.
  //Note: 0 could be a valid value for area0YTop (if it started right at the top of the image) but its not
  //valid for area1YTop as there must be something above it.
  //We check Y only as these are values that are used in calculation X isn't used.
  if(this.area1YTop == -1 || this.area1YBottom == -1 ||
     this.area1YTop == 0  || this.area1YBottom == 0  ||
     this.area1YTop == '' || this.area1YBottom == '')
  {
    this.area1Exists = false;
  }
}

function IsMouseOverGraphObj(){
  //can and or should this also auto fill in the same number in DynamicBrowserObj.
  //It may not then be the correct figure for what it's doing.
  this.currentMouseToDocumentPosition = [null,null];
  this.graphDivOffsetToDocument = getGraphDivOffsetToDocument();
  this.mouseOverGraph = false;
  this.update = function(e){
    this.currentMouseToDocumentPosition = mouseCoordsToDocument(e);
    this.graphDivOffsetToDocument = getGraphDivOffsetToDocument();
    if(this.currentMouseToDocumentPosition[0] < this.graphDivOffsetToDocument[0]){
      //mouse is to the left of the graph
      this.mouseOverGraph = false;
    } else if(this.currentMouseToDocumentPosition[0] > this.graphDivOffsetToDocument[0] + graphDetailsObj.chartWidth){
      //mouse is to the right of the graph
      this.mouseOverGraph = false;
    } else if(this.currentMouseToDocumentPosition[1] < this.graphDivOffsetToDocument[1]){
      //mouse is above of the graph
      this.mouseOverGraph = false;
    } else if(this.currentMouseToDocumentPosition[1] > this.graphDivOffsetToDocument[1] + graphDetailsObj.chartHeight){
      //mouse is below the graph
      this.mouseOverGraph = false;
    } else {
      //must be over it then
      this.mouseOverGraph = true;
    }
  }
}

function ToolTipsEntitiesStrObj(){
  //overwritten in zooltips.xsl
}

//tooltips object create
function ToolTipsEntitiesObj(){
  this.alteredIdentifier = 0;//used  by alterIdentifier()
  //ToolTipsEntitiesObj is an intermediate obj.  The tooltips info is output in the source in strings to reduce the
  //effort on the xslt and to create as small a file size as possible.  We want the info in a completely
  //different format to how it is in the xml so we convert these strings into arrays which are then converted again
  //into the easy lookup object found later.
  //items with a 'comment t' at the end are the ones we want to create for use later.
  //Everything else is just used for calculations
  this.t = new ToolTipsEntitiesStrObj()
  
  //this.toolTipsIdentifiers[company or market tracker number][epic or market or index,name,initial value]
  this.toolTipsIdentifiers = new Array();//t
  //this.identifiers is array of strings like 'ETR,Enterprise PLC'.
  this.identifiers = this.t.toolTipsIdentifiersStr.split('~');
  //3rd identifier might not be 3rd displayed as some tooltips are hidden therefore i iterates over all
  //identifiers and m just over those shown.
  m=0;
  this.displayIdentifier = new Array();
  for(i=0; i < this.identifiers.length-1; i++){//-1 due to ; on the end
    //note: this if assumes 0 is main line and volumes is last and only one in Area1
    //show tooltip if main line or if customSetting set to true.
    if(i == 0 ||
       ((i > 0 && i < this.identifiers.length-2) && customSettingsObj.showArea0Date1Comparators) ||
       (i == this.identifiers.length-2 && customSettingsObj.showArea1Date0Comparators)
    ){
      this.displayIdentifier[i] = true;
      this.identifierDetails = this.identifiers[i].split('#');
      if(testAllowedIdentifier(this.identifierDetails[0])){
        this.alteredIdentifier = alterIdentifier(this.identifierDetails[0],this.alteredIdentifier);
        this.toolTipsIdentifiers[m] = new Array(this.identifierDetails[0],this.alteredIdentifier,checkForNameChangeIdentifier(this.identifierDetails[0],this.identifierDetails[1]));//t
        m++;
      } else {
        this.displayIdentifier[i] = false;
      }
    } else {
      this.displayIdentifier[i] = false;
    }
  }
  //this.toolTipsEntities[chartEntity(line number)][point number][[startx][endx][date][price and change or vol or point details]]
  this.toolTipsEntities = new Array();//t
  //this.t.toolTipsEntitiesStr is an array of strings like '50,51,23-Feb-2005 : 380;51,53,24-Feb-2005 : 380;53,58,25-Feb-2005 : 378.5;' etc...
  //Note: this.t.toolTipsIdentifiersStr.split('~').length is 1 more than 
  //      this.t.toolTipsEntitiesStr.length.  They would be identical if not for the first ones ; at the end.
  //if they are not like this the xml is wrong or we have a problem with inconsistent numbers of identifiers and entities.
  n=0;//see comment for m above.
  for(j=0;j<this.t.toolTipsEntitiesStr.length;j++){
    //see comment before similar if above.
    if(this.displayIdentifier[j]){
      this.toolTipsEntities[n] = new Array;//t
      //this.doOrNot is used to decide if we should get point change details.  currently this is always true except for volumes which happen to be the last line.
      this.doOrNot = (j != this.t.toolTipsEntitiesStr.length - 1);
      //this.entities is an array of strings like '50,51,23-Feb-2005 : 380;' for a particular entity/line
      this.entities = this.t.toolTipsEntitiesStr[j].split(';');
      this.entityInitialValue = null;
      for(k=0;k<this.entities.length-1;k++){//-1 due to ; on the end
        //this.entity is an array of startx endx, text.      
        this.entity = this.entities[k].split('#');
        this.entityText = this.entity[2].split(' : ');
        if(k==0){this.entityInitialValue=this.entityText[1]}
        if(this.doOrNot){
          this.toolTipsEntities[n][k] = new Array(this.entity[0],this.entity[1],this.entityText[0],calcPercentages(this.entityText[1],this.entityInitialValue));//t
        } else {
          this.toolTipsEntities[n][k] = new Array(this.entity[0],this.entity[1],this.entityText[0],this.entityText[1]);//t
        }
      }
      n++;
    }
  }
  //because this is an itermediate obj i want to clear out as many of the large unneeded strings as possible.
  this.t = null;
  this.identifiers = null;
  this.identifierDetails = null;
  this.entities = null;
  this.entityInitialValue = null;
  this.entity = null;
  this.entityText = null;
}
//these four functions are used in the creation of ToolTipsEntitiesObj.
function calcPercentages(numberNow,numberStart,currency){
  numberNow = parseFloat(numberNow);
  numberStart = parseFloat(numberStart);
  ret = customSettingsObj.preCurrency;
  ret += numberNow;
  ret += customSettingsObj.postCurrency;
  ret += (numberNow > numberStart) ? ' (+' : ' (';
  ret += twoDP(((numberNow/numberStart)*100) - 100) + '%)';
  return ret;
}
function alterIdentifier(identifier,previousIdentifier){
  if(customSettingsObj.nameZoomIdentifiersByPosition){
    identifier = ++previousIdentifier;
  } else {
    identifier = identifier.replace(/@/,'_AT_');
    identifier = identifier.replace(/\./,'_DOT_');
  }
  return identifier;
}
function testAllowedIdentifier(identifier){
  allowed = true;
  for(bi=0;bi<customSettingsObj.bannedIdentifiers.length;bi++){
    //if there any * in the bannedIdentifier the just check for indexOf rather than equals.
    if(customSettingsObj.bannedIdentifiers[bi].indexOf('*') != -1 && 
      identifier.indexOf(customSettingsObj.bannedIdentifiers[bi].replace('*','')) > -1){
      allowed = false;
    } else if(identifier == customSettingsObj.bannedIdentifiers[bi]){
      allowed = false;
    }
  }
  return allowed;
}
function checkForNameChangeIdentifier(identifier,currentName){
  newName = currentName;
  for(ident=0;ident<customSettingsObj.changeTooltipIdentifierNames.length;ident++){
    if(customSettingsObj.changeTooltipIdentifierNames[ident][0] == identifier){
      newName = customSettingsObj.changeTooltipIdentifierNames[ident][1];
    }
  }
  return newName;
}
function twoDP(num){
  return (Math.round(num*100))/100
}

function TooltipsLookupObj(){
  this.lastPixel = graphDetailsObj.chartWidth;
  //entities array description:
  //first array represents the x coord over the image. (from 1 to chartWidth).
  //second array represents the line no. (from 0 to no of lines-1).
  //third array holds all data relevant to that line at that point, i.e. data, price/volume.
  //create first and second array structure.
  this.entities = new Array();
  for(i=0;i<=this.lastPixel;i++){//loops around chart width times
    this.entities[i] = new Array();
    for(j=0;j<toolTipsEntitiesObj.toolTipsEntities.length;j++){//loops around line no times
      this.entities[i][j] = new Array();
      this.entities[i][j][0] = null;//date
      this.entities[i][j][1] = null;//text (price, vol, tsr-points)
    }
  }
  //populate with data from toolTipsEntitiesObj
  for(i=0;i<toolTipsEntitiesObj.toolTipsEntities.length;i++){//loops around line no times
    for(j=0;j<toolTipsEntitiesObj.toolTipsEntities[i].length;j++){//loops around no points times 
      x1 = parseInt(toolTipsEntitiesObj.toolTipsEntities[i][j][0]);//[0] being x1
      x2 = parseInt(toolTipsEntitiesObj.toolTipsEntities[i][j][1]);//[0] being x2
      //duplicate date and text for all pixels from x1 to x2 inclusive of x1.
      for(k=x1;k<x2;k++){
        this.entities[k][i] = new Array(toolTipsEntitiesObj.toolTipsEntities[i][j][2],toolTipsEntitiesObj.toolTipsEntities[i][j][3]);//[2] is time, [3] is text(price or vol)
      }
    }
  }
  if(dynamicSettingsObj.intraday && customSettingsObj.renameDayClose){
    setDayClose(this);
  }
}
//used in TooltipsLookupObj
function setDayClose(tooltipsLookupObjReference){
  for(i=0;i<tooltipsLookupObjReference.entities.length;i++){
    if(tooltipsLookupObjReference.entities[i][0][0] == customSettingsObj.dayCloseTime){
      tooltipsLookupObjReference.entities[i][0][0] = 'Day Close';
    }
  }
}

//not to be used on live pages.
function DebugObj(){
  this.stacktrace = '';
  this.initialTime = (new Date);
  this.currentTime = (new Date);
  this.newTime = (new Date);
  this.debug = function(){
    doMsg(this.stacktrace);
  }
  this.add = function(whatText){
    this.stacktrace += '<br />' + whatText;
  }
  this.addDebug = function(whatText){
    this.add(whatText);
    this.debug();
  }
  this.show = function(whatText){
    this.add(whatText);
    this.debug();
    this.endDebug();
  }
  this.endDebug = function(){
    this.stacktrace = ''  
  }
  this.timeTrack = function(pointName){
    this.newTime = (new Date);
    this.add(stringPad(pointName,30) + ' ' + stringPadRev((this.newTime - this.currentTime),4) + ' ' + stringPadRev((this.newTime - this.initialTime),6));
    this.currentTime = this.newTime;
  }
}
//used in DebugObj
function stringPad(str,len){
  str = str.toString();
  while(str.length<len){
    str += ' ';
  }
  str = str.replace(/\s/gi,'&nbsp;');
  return str;
}
//used in DebugObj
function stringPadRev(str,len){
  str = str.toString();
  while(str.length<len){
    str = ' ' + str;
  }
  str = str.replace(/\s/gi,'&nbsp;');
  return str;
}

function DateLookupObj(){
  this.monthArray = new Array();
  this.monthArray[this.monthArray.length] = "Jan";
  this.monthArray[this.monthArray.length] = "Feb";
  this.monthArray[this.monthArray.length] = "Mar";
  this.monthArray[this.monthArray.length] = "Apr";
  this.monthArray[this.monthArray.length] = "May";
  this.monthArray[this.monthArray.length] = "Jun";
  this.monthArray[this.monthArray.length] = "Jul";
  this.monthArray[this.monthArray.length] = "Aug";
  this.monthArray[this.monthArray.length] = "Sep";
  this.monthArray[this.monthArray.length] = "Oct";
  this.monthArray[this.monthArray.length] = "Nov";
  this.monthArray[this.monthArray.length] = "Dec";

  this.dayArray = new Array();
  this.dayArray[this.dayArray.length] = "Sun";
  this.dayArray[this.dayArray.length] = "Mon";
  this.dayArray[this.dayArray.length] = "Tue";
  this.dayArray[this.dayArray.length] = "Wed";
  this.dayArray[this.dayArray.length] = "Thu";
  this.dayArray[this.dayArray.length] = "Fri";
  this.dayArray[this.dayArray.length] = "Sat";
}
/*****************************end of required holding objects**********************************/
/********************************event triggering functions************************************/
//add events functions
function addOverGraphEvents(){
  addEvent(elementsRefObj.graphDiv,'dblclick',graphDblClick);
  addEvent(elementsRefObj.graphDiv,'mousedown',graphMouseDown);
  addEvent(elementsRefObj.graphDiv,'mousemove',graphMouseMove);
  addEvent(elementsRefObj.graphDiv,'mouseup',graphMouseUp);
  //addEvent(elementsRefObj.graphImg,'load',graphImgLoad);PHIL FIX THIS
  //I don't think these are needed.  If no errors show up remove all of these.
/*  for(i=0;i<dynamicSettingsObj.numberOfZoombarsObjs;i++){
    addEvent(elementsRefObj.zoombarsObjs[i].startZoomDiv,'dblclick',graphDblClick);
    addEvent(elementsRefObj.zoombarsObjs[i].dragZoomDiv,'dblclick',graphDblClick);
    addEvent(elementsRefObj.zoombarsObjs[i].endZoomDiv,'dblclick',graphDblClick);
    addEvent(elementsRefObj.zoombarsObjs[i].startZoomDiv,'mousedown',graphMouseDown);
    addEvent(elementsRefObj.zoombarsObjs[i].dragZoomDiv,'mousedown',graphMouseDown);
    addEvent(elementsRefObj.zoombarsObjs[i].endZoomDiv,'mousedown',graphMouseDown);
    addEvent(elementsRefObj.zoombarsObjs[i].startZoomDiv,'mousemove',graphMouseMove);
    addEvent(elementsRefObj.zoombarsObjs[i].dragZoomDiv,'mousemove',graphMouseMove);
    addEvent(elementsRefObj.zoombarsObjs[i].endZoomDiv,'mousemove',graphMouseMove);
    addEvent(elementsRefObj.zoombarsObjs[i].startZoomDiv,'mouseup',graphMouseUp);
    addEvent(elementsRefObj.zoombarsObjs[i].dragZoomDiv,'mouseup',graphMouseUp);
    addEvent(elementsRefObj.zoombarsObjs[i].endZoomDiv,'mouseup',graphMouseUp);
  }
  for(i=0;i<dynamicSettingsObj.noTooltipsLineObjs;i++){
    addEvent(elementsRefObj.tooltipsLineObjs[i].tooltipsLineDiv,'mousedown',graphMouseDown);
    addEvent(elementsRefObj.tooltipsLineObjs[i].tooltipsLineDiv,'mousemove',graphMouseMove);
    addEvent(elementsRefObj.tooltipsLineObjs[i].tooltipsLineDiv,'mouseup',graphMouseUp);
  }*/
}
function graphImgLoad(){
  //remove graphImg from DOM as it has been copied but we need to wait for it to load before it can be removed.
}
//MouseOutWatcher is the name given to the mini-process that runs when you are over the graph and determines is you move
//out of the graph area.  i.e. it replicated graphDiv mouseout.
function addMouseOutWatcherEvents(){
  addEvent(elementsRefObj.graphDiv,'mouseover',graphMouseOver);
}
function loadMouseOutWatcher(){
  addEvent(elementsRefObj.bodyTag,'mousemove',bodyMouseMove);
}
function unloadMouseOutWatcher(){
  removeEvent(elementsRefObj.bodyTag,'mousemove',bodyMouseMove);
}
function addEvent(obj,eventType,functionReference){
  if(obj.addEventListener){
    obj.addEventListener(eventType,functionReference,true);
  } else if(obj.attachEvent){
    obj.attachEvent('on'+eventType,functionReference);
  } 
}
function removeEvent(obj,eventType,functionReference){
  if(obj.removeEventListener){
    obj.removeEventListener(eventType,functionReference,true);
  } else if(obj.detachEvent){
    obj.detachEvent('on'+eventType,functionReference);
  } 
}
//add event to stop zoom when esc is pressed.
function addKeyDownEvent(){
    document.onkeydown = escapeToQuitZoom;
}
function escapeToQuitZoom(e) {
  key = null;
  if (window.event) key = window.event.keyCode;
  else if (e) key = e.which;
  if(key){
    if (key==27) {
      cancelZoom();
    }
  }
}
/****************************end of event triggering functions*********************************/
/*********************************Get Set and Do Functions*************************************/

//get set and do functions
function writeToDiv(obj,whatText){
    obj.innerHTML = whatText;
}
function doMsg(whatText){
  if(customSettingsObj.msgUsingWriteToDiv){
    setObjectStyleProperty(elementsRefObj.msgDiv,'visibility','visible');
    writeToDiv(elementsRefObj.msgDiv,whatText);
  } else if(whatText != ''){
    alert(whatText);
  }
}
function clearMsg(){
  if(customSettingsObj.msgUsingWriteToDiv){
    writeToDiv(elementsRefObj.msgDiv,'');
    setObjectStyleProperty(elementsRefObj.msgDiv,'visibility','hidden');
  }
}

function simpleDateStr(newDate){
  this.d = new Date(newDate);
  this.newDateStr = dateLookupObj.dayArray[this.d.getDay()];
  this.newDateStr += ' ' + this.d.getDate();
  this.newDateStr += ' ' + dateLookupObj.monthArray[this.d.getMonth()];
  this.newDateStr += ' ' + this.d.getFullYear();
  return this.newDateStr;
}
//get set property functions
function getObject(objId){
  return document.getElementById(objId);
}
function getObjectByTagName(tagName,index){
  return document.getElementsByTagName(tagName)[index];
}
function getObjectStyleProperty(obj,propertyName,currentStyleTrueFalse){
  if (document.defaultView && document.defaultView.getComputedStyle){
    return document.defaultView.getComputedStyle(obj,'')[propertyName];
  } else if (currentStyleTrueFalse && obj.currentStyle){
    return obj.currentStyle[propertyName];//NOTE IE and BORDERS:: IE will often give 'medium' etc for border sizes.  luckily we should never need to ask for borders in IE.
  } else {
    return '';
  }
}
//getIntDivPropertyByObj is often used to find positions or sizes.  
//In many calculations we want to return 0 for IE even if the value is not 0 hence what currentStyleTrueFale is used for.
//currentStyleTrueFalse is therefore set to false for IE as currentStyle is what IE uses.
//If it ever starts using getComputedStyle I suspect it'll also be wanting a value too.
function getIntObjectStyleProperty(obj,propertyName,currentStyleTrueFalse){//phil what's currentStyleTrueFalse??
  intDivPropertyByObj = parseInt(getObjectStyleProperty(obj,propertyName,currentStyleTrueFalse))
  if (isNaN(intDivPropertyByObj)){
    return 0;
  } else {
    return intDivPropertyByObj;
  }
}
function setObjectStyleProperty(obj,propertyName,valueSetTo){//IE5/6 IE4  NS6/7 NS4
  valueSetTo += addSizePx(propertyName);
  obj.style[propertyName] = valueSetTo;
}
function addSizePx(propertyName){
  if(propertyName == 'left' || propertyName == 'top' || propertyName == 'width' || propertyName == 'height'){
    return "px";
  } else {
    return "";
  }
}
function setMultiObjectStyleProperty(objHolder,objName,propertyName,valueSetTo){
  for(i=0; i< objHolder.length; i++){
    setObjectStyleProperty(objHolder[i][objName],propertyName,valueSetTo);
  }
}
function setObjectText(obj,valueSetTo){//IE5/6 IE4  NS6/7 NS4
  obj.innerHTML = valueSetTo;   
}
/*******************************End Get Set and Do Functions***********************************/
/**********************************Positioning Functions***************************************/

function mouseCoordsToDocument(e) {//works in everything.
  if( !e ) { e = window.event; }
  if( !e || ( typeof( e.pageX ) != 'number' && typeof( e.clientX ) != 'number' ) ) { var posX = 0; var posY = 0; }
  if( typeof( e.pageX ) == 'number' ) {
    var posX = e.pageX; var posY = e.pageY;
  } else {
    var posX = e.clientX; var posY = e.clientY;
    if( !( ( window.navigator.userAgent.indexOf( 'Opera' ) + 1 ) || ( window.ScriptEngine && ScriptEngine().indexOf( 'InScript' ) + 1 ) || window.navigator.vendor == 'KDE' ) ) {
       if( document.documentElement && ( document.documentElement.scrollTop || document.documentElement.scrollLeft ) ) {
         posX += document.documentElement.scrollLeft; posY += document.documentElement.scrollTop;
       } else if( document.body && ( document.body.scrollTop || document.body.scrollLeft ) ) {
         posX += document.body.scrollLeft; posY += document.body.scrollTop;
       }
    }
  }
  return [posX - browserObj.documentOffsetFix,posY - browserObj.documentOffsetFix];//I added documentOffsetFix to this standardfunction as IE is 2 out.
}
//Phil, can you cache this?  maybe for a little while.
function getContainerDivOffsetToDocument(){
  return getObjOffsetToDocument(elementsRefObj.containOverGraphElementsDiv);
}
function getGraphDivOffsetToDocument(){
  return getObjOffsetToDocument(elementsRefObj.graphDiv);
}
function getObjOffsetToDocument(obj){
  var curleft = 0;
  var curtop = 0;
  if (obj.offsetParent){
    while (obj.offsetParent){
      curleft += obj.offsetLeft;
      curtop += obj.offsetTop;
      curleft += getIntObjectStyleProperty(obj,'borderLeftWidth',!browserObj.ie);//don't do for IE as it takes borders into account in offsetLeft
      curtop += getIntObjectStyleProperty(obj,'borderTopWidth',!browserObj.ie);
      obj = obj.offsetParent;
    }
  } else if (obj.x && obj.y) {
    curleft += obj.x;
    curtop += obj.y;
  }
  if(browserObj.containerDivOffsetBuggy){
    curleft = curleft/2;  
    curtop = curtop/2;  
  }
  return [curleft,curtop];
}

/********************************End Positioning Functions*************************************/
/********************************Event Processing Functions************************************/

function graphMouseMove(e){
  //oddly with xhtml 1.1 sometimes you get mouseMove called even when you're not over it.
  if(isMouseOverGraphObj.mouseOverGraph){
    try{dynamicBrowserObj} catch (e){/*alert(e);*/return;}//PHIL - caused by moving away from action very swiftly.  shows js error in console. or would if try catch not here.
    dynamicBrowserObj.update(e);
    if(dynamicSettingsObj.zoomAllowed && zoomTrackingObj.inZoomProcess){
      if(!zoomTrackingObj.doneZoom){//started zooming and not already zoomed
        zoomMoving();
      }
    } else if(!zoomTrackingObj.doneZoom){//don't show tooltips if zooming has been done. no need to check dynamicSettingsObj.zoomAllowed
      tooltipsGeneralMoving();
    }
  }
}
function graphMouseDown(e){
  try{dynamicBrowserObj} catch (e){/*alert(e);*/return;}//PHIL - caused by moving away from action very swiftly.  shows js error in console.
  dynamicBrowserObj.update(e);
  if(dynamicSettingsObj.zoomAllowed && zoomTrackingObj.inZoomProcess && dynamicBrowserObj.mouseToGraphLtd[0] != zoomTrackingObj.startX){//not moused click and reclicked in same place
    zoomEnd();
  } else if(!zoomTrackingObj.doneZoom){//start zoom if zooming not already done also hide tooltips
    if(dynamicSettingsObj.zoomAllowed){
      tooltipsGeneralHide();
      zoomStart();
    }
  }
}
function graphMouseUp(e){
  try{dynamicBrowserObj} catch (e){/*alert(e);*/return;}//PHIL - caused by moving away from action very swiftly.  shows js error in console.
  dynamicBrowserObj.update(e);
  if(dynamicSettingsObj.zoomAllowed && !zoomTrackingObj.doneZoom && dynamicBrowserObj.mouseToGraphLtd[0] != zoomTrackingObj.startX){//not moused up before moved and not already done zooming
    zoomEnd();
  }
}
//Note, this is what would normally be called graphMouseOut() but we don't use the mouseout event because it's not accurate.
//Ask Me (P Hanna) why if you want to know but it isn't and even the Quirksmode attempt at fixing it is rubbish as it misses out many possible problems.
//So I work out mouseout a different way hence the odd name of this function.
function graphMouseOutCalculated(){
  tooltipsGeneralHide();
  if(customSettingsObj.mouseOutCancelZoomMethod){
    cancelZoom();
  } else {
    zoomInfoOut();
  }
}
function graphMouseOver(){
  loadMouseOutWatcher();
}
function graphDblClick(){
  goToReturnPeriod();
}
//An unfortunate side effect of this method is that
//It doesn't work if the mouse stays still but the page moves beneath.
function bodyMouseMove(e){
  isMouseOverGraphObj.update(e);
  if(!isMouseOverGraphObj.mouseOverGraph){//out of graph region
    unloadMouseOutWatcher();
    //Effectively we have just worked out that the mouse has moved out of the graphDiv area.
    graphMouseOutCalculated();//effectively graphMouseOut()
  }
}

/******************************End Event Processing Functions**********************************/
/*********************************Zoom Processing Functions************************************/

//enable zooming functions
function zoomStart(){
  zoomTrackingObj.inZoomProcess = true;
  zoomTrackingObj.startX = dynamicBrowserObj.mouseToGraphLtd[0];
  setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'startZoomDiv','left',zoomTrackingObj.startX);
  setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'startZoomDiv','visibility','visible');
  zoomTrackingObj.setStartDate(new Date(((zoomTrackingObj.startX - graphDetailsObj.startX) * graphDetailsObj.msPerPixel) + graphDetailsObj.startDateMs));
  setDropdown('period','-');
  setDateDropdown('from',zoomTrackingObj.startDate);
  zoomInfoStart();
}
function zoomEnd(){
  if(zoomTrackingObj.inZoomProcess){//cancelZoom() may have been called.
    zoomTrackingObj.endX = dynamicBrowserObj.mouseToGraphLtd[0];
    setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'endZoomDiv','left',zoomTrackingObj.endX);
    zoomTrackingObj.inZoomProcess = false;
    zoomTrackingObj.doneZoom = true;
    zoomTrackingObj.setEndDate(new Date(((zoomTrackingObj.endX - graphDetailsObj.startX) * graphDetailsObj.msPerPixel) + graphDetailsObj.startDateMs));
    if(zoomTrackingObj.endDate > zoomTrackingObj.startDate){
      setDateDropdown('to',zoomTrackingObj.endDate);
    } else {
      setDateDropdown('from',zoomTrackingObj.endDate);
      setDateDropdown('to',zoomTrackingObj.startDate);
    }
    submitForm();
  }
}
function zoomMoving(){
  dragZoomPositionAndResize(zoomTrackingObj.startX,dynamicBrowserObj.mouseToGraphLtd[0])
  setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'endZoomDiv','left',dynamicBrowserObj.mouseToGraphLtd[0]);
  setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'endZoomDiv','visibility','visible');
  zoomTrackingObj.setEndDate(new Date(((dynamicBrowserObj.mouseToGraphLtd[0] - graphDetailsObj.startX) * graphDetailsObj.msPerPixel) + graphDetailsObj.startDateMs));
  zoomInfoMoving()
  //too slow in IE. maybe turn on for firefox.
  //setDateDropdown('to',zoomTrackingObj.endDate);
}
function dragZoomPositionAndResize(startX,endX){
  dynamicBrowserObj.zoomWidth = Math.abs(endX-startX)+1
  if(parseInt(endX) > parseInt(startX)){
    setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'dragZoomDiv','left',startX);
  } else {
    setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'dragZoomDiv','left',endX);
  }
  setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'dragZoomDiv','width',dynamicBrowserObj.zoomWidth);
  setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'dragZoomDiv','visibility','visible');
}
function cancelZoom(){
  setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'startZoomDiv','visibility','hidden');
  setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'dragZoomDiv','visibility','hidden');
  setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'endZoomDiv','visibility','hidden');
  setObjectStyleProperty(elementsRefObj.zoomInfoMoveThisDiv,'visibility','hidden');
  zoomTrackingObj.inZoomProcess = false;
  zoomTrackingObj.doneZoom = false
}

//info part of zoom
function zoomInfoStart(){
  zoomTrackingObj.update();//would do this in main zoom function but its only needed for zoomInfo.
  setObjectStyleProperty(elementsRefObj.zoomInfoMoveThisDiv,'left',zoomTrackingObj.xOffsetBounded);
  setObjectStyleProperty(elementsRefObj.zoomInfoMoveThisDiv,'top',zoomTrackingObj.yOffsetBounded);
  setObjectStyleProperty(elementsRefObj.zoomInfoMoveThisDiv,'visibility','visible');
  zoomInfoUpdateText(false);
}
function zoomInfoMoving(){
  zoomTrackingObj.update();
  setObjectStyleProperty(elementsRefObj.zoomInfoMoveThisDiv,'left',zoomTrackingObj.xOffsetBounded);
  setObjectStyleProperty(elementsRefObj.zoomInfoMoveThisDiv,'top',zoomTrackingObj.yOffsetBounded);
  setObjectStyleProperty(elementsRefObj.zoomInfoMoveThisDiv,'visibility','visible');
  zoomInfoUpdateText(false);
}
function zoomInfoOut(){
  zoomInfoOutMove();
  zoomInfoUpdateText(true);
}
//when zoomInfoOut move zoomInfo.
function zoomInfoOutMove(){
  if(customSettingsObj.zoomInfoOutMoveMethod){
    setObjectStyleProperty(elementsRefObj.zoomInfoMoveThisDiv,'left',graphDetailsObj.area0XLeft);
    setObjectStyleProperty(elementsRefObj.zoomInfoMoveThisDiv,'top',graphDetailsObj.area0YTop);
  }
}

function zoomInfoUpdateText(escapeMsg){
  zoomInfoText = zoomTrackingObj.dateChangeAmount + '<br />';
  if(zoomTrackingObj.startDate != null){zoomInfoText += 'From ' + simpleDateStr(zoomTrackingObj.startDate) + '<br />';}
  if(zoomTrackingObj.endDate != null){zoomInfoText += 'To ' + simpleDateStr(zoomTrackingObj.endDate);}
  if(escapeMsg){zoomInfoText += '<br />Press "Esc" to clear zoom'}
  setObjectText(elementsRefObj.zoomInfoDiv,zoomInfoText);
}

/******************************End Zoom Processing Functions************************************/
/******************************Tooltips Processing Functions************************************/

//enable tooltips functions
function tooltipsGeneralMoving(){
  tooltipsTrackingObj.update();
  tooltipsUpdateText();
  tooltipsLineMoving();
  tooltipsHorizontalLineMoving();
  tooltipsMoving();
}
function tooltipsGeneralHide(){
  //these are called when you leave the graph as a whole or when you enter zooming mode.
  //these are also the ones affected by Firefox 1.5's over zelous main graph out event.
  tooltipsLineHide();
  tooltipsHorizontalLineHide();
  tooltipsHide();
}
function tooltipsLineMoving(){
  if(dynamicBrowserObj.insideChartXArea){
    setMultiObjectStyleProperty(elementsRefObj.tooltipsLineObjs,'tooltipsLineDiv','left',tooltipsTrackingObj.x);
    setMultiObjectStyleProperty(elementsRefObj.tooltipsLineObjs,'tooltipsLineDiv','visibility','visible');
  } else {
    //this hide and the similar two below are called when you are not in zoom but are also not in the relevant graph area
    //i.e. over the y axis this and the tooltips info are hidden.
    tooltipsLineHide();  
  }
}
function tooltipsHorizontalLineMoving(){
  if(dynamicSettingsObj.showTooltipsHorizontalLineObj){
    if(dynamicBrowserObj.insideChartYArea){
      setObjectStyleProperty(elementsRefObj.tooltipsHorizontalLineObj.tooltipsHorizontalLineDiv,'top',tooltipsTrackingObj.y);
      setObjectStyleProperty(elementsRefObj.tooltipsHorizontalLineObj.tooltipsHorizontalLineDiv,'visibility','visible');
    } else {
      tooltipsHorizontalLineHide();  
    }
  }
}
function tooltipsMoving(){
  if(!tooltipsLookupObj.TooltipsChartAllAreaObjsAllNull && 
      dynamicBrowserObj.insideChartXArea && 
      dynamicSettingsObj.noTooltipsEntities > 0)
  {
    setObjectStyleProperty(elementsRefObj.tooltipsMoveThisDiv,'left',tooltipsTrackingObj.xOffsetBounded);
    setObjectStyleProperty(elementsRefObj.tooltipsMoveThisDiv,'top',tooltipsTrackingObj.yOffsetBounded);
    setObjectStyleProperty(elementsRefObj.tooltipsMoveThisDiv,'visibility','visible');
  } else {
    tooltipsHide();  
  }
}

function tooltipsLineHide(){
    setMultiObjectStyleProperty(elementsRefObj.tooltipsLineObjs,'tooltipsLineDiv','visibility','hidden');
}
function tooltipsHorizontalLineHide(){
  if(dynamicSettingsObj.showTooltipsHorizontalLineObj){
    setObjectStyleProperty(elementsRefObj.tooltipsHorizontalLineObj.tooltipsHorizontalLineDiv,'visibility','hidden');
  }
}
function tooltipsHide(){
  setObjectStyleProperty(elementsRefObj.tooltipsMoveThisDiv,'visibility','hidden');
}
function changeTooltipIfChanged(tooltipObj,newVal){
  if(tooltipObj.tooltipsContent != newVal){
    setObjectText(tooltipObj['tooltipsDiv'],newVal);
    tooltipObj.tooltipsContent = newVal;
  }
}
function tooltipsUpdateText(){
  //Note we do the lines before the dates incase the lines are null and then they are both hidden.
  tooltipsLookupObj.TooltipsChartArea0Date0ObjsAllNull = true;
  tooltipsLookupObj.TooltipsChartArea0Date1ObjsAllNull = true;
  tooltipsLookupObj.TooltipsChartArea1Date0ObjsAllNull = true;
  tooltipsLookupObj.TooltipsChartAllAreaObjsAllNull = true;
  //update first area main line. Probably just the one main line.
  for(i=0;i<dynamicSettingsObj.noTooltipsChartArea0Date0Objs;i++){
    if(tooltipsLookupObj.entities[tooltipsTrackingObj.x][i][1] == null){
      showValues = ' - ';
      setObjectStyleProperty(elementsRefObj.tooltipsObjs[i]['tooltipsDiv'],'display','none');
    } else {
      showValues = tooltipsLookupObj.entities[tooltipsTrackingObj.x][i][1];
      setObjectStyleProperty(elementsRefObj.tooltipsObjs[i]['tooltipsDiv'],'display','block');
      tooltipsLookupObj.TooltipsChartArea0Date0ObjsAllNull = false;
    }
    changeTooltipIfChanged(elementsRefObj.tooltipsObjs[i],toolTipsEntitiesObj.toolTipsIdentifiers[i][2] + ' ' + showValues);
  }
  //update main date value
  if(dynamicSettingsObj.noTooltipsChartArea0Date0Objs > 0){
    if(tooltipsLookupObj.TooltipsChartArea0Date0ObjsAllNull){
      setObjectStyleProperty(elementsRefObj.tooltipsObjs['ChartArea0Date0']['tooltipsDiv'],'display','none');
    } else {
      changeTooltipIfChanged(elementsRefObj.tooltipsObjs['ChartArea0Date0'],tooltipsLookupObj.entities[tooltipsTrackingObj.x][0][0]);
      setObjectStyleProperty(elementsRefObj.tooltipsObjs['ChartArea0Date0']['tooltipsDiv'],'display','block');
    }
  }
  
  //update the second set of lines (probably comparators) in the first area 
  for(i=dynamicSettingsObj.noTooltipsChartArea0Date0Objs;i<=dynamicSettingsObj.noTooltipsChartArea0Date1Objs;i++){
    if(tooltipsLookupObj.entities[tooltipsTrackingObj.x][i][1] == null){
      showValues = ' - ';
      setObjectStyleProperty(elementsRefObj.tooltipsObjs[i]['tooltipsDiv'],'display','none');
    } else {
      showValues = customSettingsObj.rebasedText + tooltipsLookupObj.entities[tooltipsTrackingObj.x][i][1];
      setObjectStyleProperty(elementsRefObj.tooltipsObjs[i]['tooltipsDiv'],'display','block');
      tooltipsLookupObj.TooltipsChartArea0Date1ObjsAllNull = false;
    }
    changeTooltipIfChanged(elementsRefObj.tooltipsObjs[i],toolTipsEntitiesObj.toolTipsIdentifiers[i][2] + showValues);
  }
  //not always shown but update the second date display for items in the first area
  if(dynamicSettingsObj.existsAndShowDateTimesArea0Date1){
    if(tooltipsLookupObj.TooltipsChartArea0Date1ObjsAllNull){
      setObjectStyleProperty(elementsRefObj.tooltipsObjs['ChartArea0Date1']['tooltipsDiv'],'display','none');
    } else {
      changeTooltipIfChanged(elementsRefObj.tooltipsObjs['ChartArea0Date1'],tooltipsLookupObj.entities[tooltipsTrackingObj.x][1][0]);
      setObjectStyleProperty(elementsRefObj.tooltipsObjs['ChartArea0Date1']['tooltipsDiv'],'display','block');
    }
  }

  //update the details for the lines in the second area
  for(i=dynamicSettingsObj.noTooltipsChartArea0Date1Objs+1;i<(dynamicSettingsObj.noTooltipsChartArea0Date0Objs+dynamicSettingsObj.noTooltipsChartArea0Date1Objs+dynamicSettingsObj.noTooltipsChartArea1Date0Objs);i++){
    if(tooltipsLookupObj.entities[tooltipsTrackingObj.x][i][1] == null){
      showValues = ' - ';
      setObjectStyleProperty(elementsRefObj.tooltipsObjs[i]['tooltipsDiv'],'display','none');
    } else {
      showValues = ' ' + tooltipsLookupObj.entities[tooltipsTrackingObj.x][i][1];
      setObjectStyleProperty(elementsRefObj.tooltipsObjs[i]['tooltipsDiv'],'display','block');
      tooltipsLookupObj.TooltipsChartArea1Date0ObjsAllNull = false;
    }
    changeTooltipIfChanged(elementsRefObj.tooltipsObjs[i],toolTipsEntitiesObj.toolTipsIdentifiers[i][2] + showValues);
  }
  //update the date details for the second area
  if(dynamicSettingsObj.existsAndShowDateTimesArea1Date0){
    if(tooltipsLookupObj.TooltipsChartArea1Date0ObjsAllNull){
      setObjectStyleProperty(elementsRefObj.tooltipsObjs['ChartArea1Date0']['tooltipsDiv'],'display','none');
    } else {
      changeTooltipIfChanged(elementsRefObj.tooltipsObjs['ChartArea1Date0'],tooltipsLookupObj.entities[tooltipsTrackingObj.x][dynamicSettingsObj.noTooltipsChartArea0Date1Objs+1][0]);
      setObjectStyleProperty(elementsRefObj.tooltipsObjs['ChartArea1Date0']['tooltipsDiv'],'display','block');
    }
  }
  if(!tooltipsLookupObj.TooltipsChartArea0Date0ObjsAllNull ||
     !tooltipsLookupObj.TooltipsChartArea0Date1ObjsAllNull ||
     !tooltipsLookupObj.TooltipsChartArea1Date0ObjsAllNull)
  {
    tooltipsLookupObj.TooltipsChartAllAreaObjsAllNull = false;
  }
}

/******************************Form Manipulation Functions************************************/

//process form submittion
function submitForm(){
  elementsRefObj.chartForm.submit();
}
function goToReturnPeriod(){
  setDropdown('period',elementsRefObj.chartForm.returnPeriod.value);
  submitForm();
}

//form manipulation functions
function setDropdown(selectName,selectValue){
  for(i=0;i<elementsRefObj.chartForm[selectName].length;i++){
    if(elementsRefObj.chartForm[selectName].options[i].value == selectValue){
      elementsRefObj.chartForm[selectName].options.selectedIndex = i;
    }
  }
}

function setDateDropdown(whichRange,theDate){
  setDropdown(whichRange+'Day',theDate.getDate());
  setDropdown(whichRange+'Month',theDate.getMonth() + 1);
  setDropdown(whichRange+'Year',theDate.getFullYear());
}
function setDateToToday(whichRange){
  theDate = new Date();
  setDateDropdown(whichRange,theDate);
}

/*******************************End Form Manipulation Functions********************************/
/***********************************Unused -TODO- Functions************************************/

//Fix for browsers that don't support transparency PHIL try to fix this.
//Should be called after operaFix() in onloadZooltips.
function dragZoomChangeFix(){
  if(false){//PHIL - transparent
    setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'dragZoomDiv','backgroundColor','transparent');
    setMultiObjectStyleProperty(elementsRefObj.zoombarsObjs,'dragZoomDiv','backgroundImage','url(zoombg.png)');
  }
}

/*******************************End Unused -TODO- Functions********************************/
/************************************Onload Functions**************************************/

function onloadZooltips(){
  if(document.getElementById 
     && ((document.defaultView && document.defaultView.getComputedStyle) || getObject('graphImg').currentStyle)
     && (getObject('graphImg').addEventListener || getObject('graphImg').attachEvent)){
    dateLookupObj = new DateLookupObj();
    serviceObj = new ServiceObj();
    graphDetailsObj = new GraphDetailsObj();
    browserObj = new BrowserObj();
    customSettingsObj = new CustomSettingsObj();
    loadCustomSettingsObj();
    if(customSettingsObj.boundedMethod){
      setBoundedMethodAlternative();
    }
    toolTipsEntitiesObj = new ToolTipsEntitiesObj();
    dynamicSettingsObj = new DynamicSettingsObj();
    createObjs();
    elementsRefObj = new ElementsRefObj();
    operaFix();
    dynamicBrowserObj = new DynamicBrowserObj();
    debugObj = new DebugObj();
    zoomTrackingObj = new ZoomTrackingObj();
    isMouseOverGraphObj = new IsMouseOverGraphObj();
    tooltipsTrackingObj = new TooltipsTrackingObj();
    tooltipsLookupObj = new TooltipsLookupObj();
    if(customSettingsObj.boundedMethod){
      //note if boundedMethodAlternative then the WidthFinderDiv's are not created hence the nulls.
      tooltipsBoundedMethodObj = new BoundedMethodObj(elementsRefObj.containTooltipsDiv, customSettingsObj.boundedMethodAlternative ? elementsRefObj.tooltipsWidthFinderDiv : null);
      if(dynamicSettingsObj.showZoomInfoDiv){//note no zoom function called based on zoomAllowed.
        zoomBoundedMethodObj = new BoundedMethodObj(elementsRefObj.containZoomInfoDiv, customSettingsObj.boundedMethodAlternative ? elementsRefObj.zoomInfoWidthFinderDiv : null);
      }
    }
    addOverGraphEvents();
    addKeyDownEvent();
    addMouseOutWatcherEvents();
  }
}

/*******************************End Onload Functions********************************/
/********************************DEBBUGGING ONLY************************************/
//Debugging - simply prints out the tooltips info
function debugMap(){
  debugObj.endDebug();
  for(i=0;i<tooltipsLookupObj.entities.length;i++){
    for(m=0;m<tooltipsLookupObj.entities[i].length;m++){
      debugObj.add(i + ' ' + m + ' ' + tooltipsLookupObj.entities[i][m][0] + ' ' + tooltipsLookupObj.entities[i][m][1]);
    }
  }
  debugObj.debug();
}
function stackTrace(){
  debugObj.debug();//print current stacktrace
  debugObj.endDebug();//clear stacktrace
}
inc = 0;
function increment(){
  return inc++;
}
/*
'Hacks' and other unpleasantry's

boundedMethod2 is neccessary because I can't find a way to accurately obtain the width of a div that changes size in IE.  It only returns 'auto' or the start size.
As such I wrap the div (a) in another div (b), then place yet another div (c) in the outer one (b) but positioned right and bottom.  I can then obtain the offset of this.
This is not nice but i works.  It does have the unfortunate affect of making the overall (yet invisible) div (b) larger than it needs to be.
*/

/*ToDo
should lastpixel be chartwidth. no, make sure atleast last point (internal calc)
are imagemaps in right place? startx 51 but first imgmap 50.
if opacity doesn't exist have dragzoomdiv height 1 or 4pixel img bg//problem removing bg color 
check showZoomInfoDiv and zoomAllowed are linked.
*/
