// $Id: bfograph.js,v 1.1 2008-07-23 14:23:51 mike Exp $

// This file contains JavaScript functions that can be used to create
// interactive graphs. The JavaScript is used for both interactive
// bitmap graphs (image rollovers) and SVG.
//
// The first three functions are required for correct display of values on
// Line series. They should not be altered.
//
// The remaining functions can be used to create "tooltip" type windows
// in rollovers, but are not required for proper functioning and may be
// modified or deleted as required.
//
// Methods in this file have been tested with Firefox, Safari 2.0 and IE6.
// The SVG code doesn't yet work on Safari, which appears to have trouble
// loading externally references JavaScript files from an SVG.
//
// All code in this file is under the Public Domain, and end users may do
// anything they like with it.
//

/**
 * Given a date as a numeric value as used inside the Graph library, and an
 * optional format in the same format as the java.util.SimpleDateFormat class,
 * return the date formatted as a String
 * 
 * date   - the date as a numeric. Usually the value of "seriesx"
 * format - (optional) the format to use to format it - defaults to "dd-MMM-yyyy"
 */
function bfgToDate(date,format) {
    if (date!=date) return null;
    date = new Date((date+631152000)*1000);
    var MONTH_NAMES=new Array('January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
    var DAY_NAMES=new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat');

    if (!format) format="dd-MMM-yyyy";
    var y=date.getYear()+"";
    var M=date.getMonth()+1;
    var d=date.getDate();
    var E=date.getDay();
    var H=date.getHours();
    var m=date.getMinutes();
    var s=date.getSeconds();

    var value=new Object();
    if (y.length < 4) y=""+(y-0+1900);
    value["y"]=""+y;
    value["yyyy"]=y;
    value["yy"]=y.substring(2,4);
    value["M"]=M;
    value["MM"]=M<10 ? "0"+M : M;
    value["MMMM"]=MONTH_NAMES[M-1];
    value["MMM"]=MONTH_NAMES[M+11];
    value["d"]=d;
    value["dd"]=d<10 ? "0"+d : d;
    value["E"]=DAY_NAMES[E+7];
    value["EE"]=DAY_NAMES[E];
    value["H"]=H;
    value["HH"]=H<10 ? "0"+H : H;
    value["h"] = H>=12 ? H-12 : H;
    value["hh"]=value["h"]<10 ? "0"+value["h"] : value["h"];
    value["K"]=H>11 ? H-12 : H;
    value["k"]=H+1;
    value["KK"]=value["K"]<10 ? "0"+value["K"] : value["K"];
    value["kk"]=value["k"]<10 ? "0"+value["k"] : value["k"];
    value["a"]=H>11 ? "PM" : "AM";
    value["m"]=m;
    value["mm"]=m<10 ? "0"+m : m;
    value["s"]=s;
    value["ss"]=s<10 ? "0"+s : s;

    var i=0;
    var result="";
    while (i<format.length) {
	var c=format.charAt(i);
	var token="";
	while (i<format.length && c==format.charAt(i)) {
	    token += format.charAt(i++);
	}
	result += value[token]==null ? token : value[token];
    }
    return result;
}

/**
 * Given an X and an array of 4-tuple values of the form (x1,y1,x2,y2),
 * use x to find the matching tuple and return a value which depends on
 * the mode. Called internally, no need for users to call it.
 *
 * x - the X co-ordinate
 * a - the array of co-ordinates
 * mode - given the appropriate tuple, 0 to return the interpolated value y'
 *        between y1 and y2, 1 to return y1 and 2 to return x1.
 */
function bfgFunc(x, a, mode) {
    for (var i=0;i<a.length;i+=4) {
        if (x>=a[i] && x<=a[i+2]) {
            if (mode==1) {
                return (x-a[i])/(a[i+2]-a[i]) < 0.5 ? a[i+1] : a[i+3];
            } else if (mode==2) {
                return (x-a[i])/(a[i+2]-a[i]) < 0.5 ? a[i] : a[i+2];
                return a[i];
            } else {
                return (((x-a[i])/(a[i+2]-a[i])) * (a[i+3]-a[i+1])) + a[i+1];
            }
	}
    }
    return NaN;
}

/**
 * Round val to the specified number of decimal places
 * val the value to round
 * dp the number if decimal places to use
 */
function bfgRound(val, dp) {
    if (dp<=0) {
        return Math.round(val);
    } else {
        var z = ""+val;
        var ix = z.indexOf(".");
        return z.substring(0, Math.min(ix+dp+1, z.length));
    }
}


// ----------- Remaining functions are convenient ways to use -------------
// ----------- popup windows with SVG and HTML images.        -------------
// ----------- They can be altered or removed as necessary.   -------------


var bfgHider;

function bfgIsSVG() {
    return this.SVGDocument && document instanceof SVGDocument;
}

function bfgShowPopup(msg, event) {
    if (bfgIsSVG()) {
        if (!document.getElementById("bfgPopup.rect")) {
            var popupText = document.createElementNS("http://www.w3.org/2000/svg", "text");
            popupText.setAttribute("id", "bfgPopup.text");
            popupText.setAttribute("visibility", "hidden");
            popupText.setAttribute("fill", "black");
            popupText.setAttribute("font-family", "sans-serif");
            popupText.setAttribute("font-size", 10);
            popupText.appendChild(document.createTextNode("label"));

            var popupRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
            popupRect.setAttribute("id", "bfgPopup.rect");
            popupRect.setAttribute("visibility", "hidden");
            popupRect.setAttribute("fill", "lightyellow");
            popupRect.setAttribute("height", "15");
            popupRect.setAttribute("stroke", "black");
            popupRect.setAttribute("stroke-width", "0.5");
            
            // Add them to the first group
            //
            var firstGroup = document.getElementsByTagName("g").item(0);
            firstGroup.appendChild(popupRect);
            firstGroup.appendChild(popupText);
        }
        var popupRect = document.getElementById("bfgPopup.rect");
        var popupText = document.getElementById("bfgPopup.text");
        
        // Get the title attribute from the firt node of the class
        //
        var child = popupText.firstChild;
        if (child.data != msg) {
            popupText.replaceChild(document.createTextNode(msg), child);
        }

        popupRect.setAttribute("x", event.clientX+10);
        popupRect.setAttribute("y", event.clientY);
        popupRect.setAttribute("width", msg.length*6);      // TODO Could be more accurate?
        popupText.setAttribute("x", event.clientX+15);
        popupText.setAttribute("y", event.clientY+10);
        popupText.setAttribute("visibility", "visible");
        popupRect.setAttribute("visibility", "visible");
    } else {
        var popup = document.getElementById("bfgPopup");
        if (popup==null) {
            popup = document.createElement("div");
            popup.style.position = "absolute";
            popup.id = "bfgPopup";
            var body = document.getElementsByTagName("body")[0];
            if (body==null) {               // No body, try appending to document
                body = document;
            }
            body.appendChild(popup);
        }


        // Here are the values we can expect to be set in the four main browsers:
        // 				pageY	clientY		documentElement.scrollTop	body.scrollTop
        // Safari (quirks/strict)	page	window		set				set
        // Mozilla quirks		page	window		set				set
        // Opera (quirks/strict)	page	window		set				set
        // Mozilla strict		page	window		set				0
        // IE6 quirks			undef	window		0				set
        // IE6 strict			undef	window		set				0
        //
        var x = event.pageX ? event.pageX : event.clientX + document.documentElement.scrollLeft + document.body.scrollLeft;
        var y = event.pageY ? event.pageY : event.clientY + document.documentElement.scrollTop  + document.body.scrollTop;
        popup.innerHTML="<div style='padding:2px; border:1px solid #000; background-color:#ffd; white-space:nowrap; font:8pt sans-serif'>"+msg+"</div>";
        popup.style.left = (x+15)+"px";
        popup.style.top = (y-5)+"px";
        popup.style.visibility = "visible";
    }
    if (bfgHider) clearTimeout(bfgHider);
}

function bfgHidePopup() {
    bfgHider = setTimeout("_bfgHidePopup()", 500);
}

function _bfgHidePopup() {
    if (bfgIsSVG()) {
        document.getElementById("bfgPopup.rect").setAttribute("visibility", "hidden");
        document.getElementById("bfgPopup.text").setAttribute("visibility", "hidden");
    } else {
        document.getElementById("bfgPopup").style.visibility = "hidden";
    }
}

//-------------------------------------------------------------------------
// Following methods apply to AJAX graphs

function _bfoGraphImportNode(node) {
    if (node.nodeType==1) {
        var out = document.createElement(node.nodeName);
        for (var i=0;node.attributes && i<node.attributes.length;i++) {
            var a = node.attributes[i];
            out.setAttribute(a.nodeName, node.getAttribute(a.nodeName));
        }
        for (var i=0;node.childNodes && i<node.childNodes.length;i++) {
            out.appendChild(_bfoGraphImportNode(node.childNodes[i]));
        }
        return out;
    } else if (node.nodeType==3) {
        return document.createTextNode(node.nodeValue);
    } else if (node.nodeType==8) {
        return document.createComment(node.nodeValue);
    }
}

function _bfoGraphConnect(node) {
    var request = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("MSXML2.XMLHTTP");
    if (node.getAttribute("url")!=null) {
        var url = node.getAttribute("url")+"/xml-html";
        request.open("POST", url, true);
        request.setRequestHeader("Content-Type", "text/xml");
        node.removeAttribute("url");
        node.setAttribute("xmlns:bfg", "http://bfo.co.uk/ns/graph?version=2");
        var text = "<?xml version=\"1.0\"?>\n<"+node.tagName.toLowerCase();
        for (var i=0;i<node.attributes.length;i++) {
            var a = node.attributes[i];
            text += " "+a.nodeName+"=\""+a.nodeValue+"\"";
        }
        text += ">"+node.innerHTML+"</"+node.tagName.toLowerCase()+">\n";
        request.send(text);

        request.onreadystatechange = function() {
            if (request.readyState == 4) {
                if (request.responseText) {
                    var doc;
                    if (window.ActiveXObject) {
                        doc = new ActiveXObject("Microsoft.XMLDOM");
                        doc.async = "false";
                        doc.loadXML(request.responseText);
                    } else {
                        doc = (new DOMParser()).parseFromString(request.responseText, "text/xml");
                    }
                    node.parentNode.replaceChild(_bfoGraphImportNode(doc.documentElement), node);
                }
            }
        };
    }
}

/**
 * If this method is called after the document is loaded, any <bfg:piegraph>
 * or <bfg:areagraph> tags in the XHTML will be parsed and converted to images.
 * No local server side processing is required, although the "url" attribute of
 * the tag must be set to the URL of the GraphServlet class - eg.
 * <html>
 *  <body>
 *   <bfg:piegraph style="width:400px; height;400px" url="http://bfo.co.uk/servlet/GraphServlet">
 *    <bfg:data>...
 *   </bfg:piegraph>
 *  </body>
 * </html>
 */
function bfoGraphLoader() {
    try {
//        netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
    } catch (e) { }
    var kids = document.getElementsByTagName("bfg:piegraph");
    for (var i=0;i<kids.length;i++) _bfoGraphConnect(kids[i]);
    kids = document.getElementsByTagName("bfg:axesgraph");
    for (var i=0;i<kids.length;i++) _bfoGraphConnect(kids[i]);
}

if (window && window.addEventListener) {
    window.addEventListener("load", bfoGraphLoader, false);
} else if (window && window.attachEvent) {
    window.attachEvent("onload", bfoGraphLoader); 
}
