/*====================================================================================
Copyright (c) 1998-2007 Fractal Edge Limited. All Rights Reserved.
This source code is protected in the United States of America by patent US6,775,659, in
Belgium, France, Ireland, Italy, the Netherlands, Spain and the United Kingdom by patent EP1105817,
in Germany by patent GE698 08 152.8-08, in Australia by patent AU775871 and by further patents
pending in these and other territories.
 
You are not authorized to use, modify, reproduce, store or distribute the Source Code or any
portion of it in any way. Unauthorized use, modification, reproduction, storage or distribution
of the Source Code or any portion of it is strictly prohibited and may result in civil
and/or criminal penalties. 
 
Notwithstanding the above, FRACTAL EDGE LIMITED PROVIDES THE SOURCE CODE "AS IS"
WITHOUT WARRANTY OF ANY KIND. FRACTAL EDGE LIMITED DISCLAIMS ALL WARRANTIES
WITH REGARD TO THE SOURCE CODE, EXPRESS, STATUTORY OR IMPLIED, INCLUDING,
WITHOUT LIMITATION, ALL WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, OR NON-INFRINGEMENT OF THIRD PARTY RIGHTS. FURTHER, FRACTAL EDGE LIMITED
REJECTS LIABILITY FOR ANY DIRECT, INDIRECT, SPECIAL, COVER, RELIANCE, PUNITIVE OR
CONSEQUENTIAL DAMAGES (INCLUDING BUT NOT LIMITED TO LOSS OF ANTICIPATED PROFIT)
ARISING FROM ANY CAUSE RELATED TO USE OF THE SOURCE CODE.
==================================================================================*/
var fractalmap;

function fractalMapRect( l, t, r, b )
{
    this.left = l;
    this.right = r;
    this.top = t;
    this.bottom = b;
    
    this.copyRect = function( rect )
    {
        this.left = rect.left;
        this.right = rect.right;
        this.top = rect.top;
        this.bottom = rect.bottom;
    };
    
    this.unionRect = function( rect )
    {
        this.left = Math.min( this.left, rect.left );
        this.right = Math.max( this.right, rect.right );
        this.top = Math.min( this.top, rect.top );
        this.bottom = Math.max( this.bottom, rect.bottom );
    };
    
    this.intersectRect = function( rect )
    {
        if ( rect.bottom > this.top )
        {
            if ( rect.top < this.bottom )
            {
                if ( rect.right > this.left )
                {
                    if ( rect.left < this.right )
                    {
                        return true;
                    }
                }
            }
        }
        return false;
    };
};

function fractalMapPoint( x, y )
{
    this.X = x;
    this.Y = y;
    this.div = null;
};

function fractalMapDisplayObj( node )
{
	this.rect = new fractalMapRect( 0, 0, 0, 0 );
	this.angle = 0.0;
	this.node = node;
	this.label = new fractalMapRect( 0, 0, 0, 0 );
	this.hotspot = 0;
	this.naturalWidth = 100;
	this.div = null;
	this.points = new Array();
};

function fractalMapTransformParams()
{
	this.children = 0;
	this.radius = 0;
	this.parentRadius = 0;
	this.X = 0;
	this.Y = 0;
	this.ratio = 0.0;
	this.perAng = 0.0;
	this.startAng = 0.0;
	this.length = 0.0;
	this.labelRect = new fractalMapRect( 0, 0, 0, 0 );
	this.internalFlags = 0;
	this.flags = 0;
	this.boundSpacing = 0;
	this.labelHeight = 0;
	this.labelSpace = 0;
	this.labelHSpace = 0;
	this.leftWidth = 0;
	this.rightWidth = 0;
	this.colSP = 0;
	this.colMX = 0;
	this.colMN = 0;
	this.colPL = 0;
	this.colZ1 = 0;
	this.colZ2 = 0;
	this.iconWidth = 0;
	this.iconHeight = 0;
};

function fractalMapDHTML( data, img, imgsrc, mapid, text, fmdiv )
{
    this.X                      =   0;
    this.Y                      =   0;
    this.base                   =   new fractalMapRect( img.offsetLeft, img.offsetTop, img.offsetLeft + img.offsetWidth, img.offsetTop + img.offsetHeight );
	this.extraRotate			=	true;
	this.percentSqueeze		    =  	true;
	this.squeeze				=	-26;
	this.pctSqueeze			    =	40;
	this.maxSqueeze			    =  	12;
	this.minimumVisible		    =	6;
	this.verticalLabelSpacing	=	2;
	this.fractalMapStub		    =	5;
	this.labelStub				=	5;
    this.FMTF_PROXIMATE         =	0x00000002;
    this.FMTF_LOCKED            =	0x00000004;
    this.FMTF_DRAWLINES		    =   0x00000008;
    this.FMTF_DISPLAYLEAF		=	0x00000001;
    this.FMTF_OVERLAPPING		=	0x00000002;
    this.BoxX                   =   new Array( 0x00008000, 0x00010000, 0x00010000, 0x00010000, 0x00008000, 0x00000000, 0x00000000, 0x00000000 );
    this.BoxY                   =   new Array( 0x00000000, 0x00000000, 0x00008000, 0x00010000, 0x00010000, 0x00010000, 0x00008000, 0x00000000 );
    this.nodes                  =   data;
    this.labels                 =   null;
    this.highlight              =   null;
    this.pendingHighlight       =   null;
    this.timer                  =   setInterval( function(){ fractalmap.onTick(); }, 100 );
    this.tick                   =   0;
    this.exitTimer              =   0;
    this.isIE                   =   ( navigator.appName.toLowerCase().indexOf( 'internet explorer' ) >= 0 ) ? true : false;
    this.textDiv                =   text;
    this.fmDiv                  =   fmdiv;

    this.safePng = function( img, imgsrc )
    {
        if ( this.isIE )
        {
            img.src = "image-lib/blank.gif";
    	    img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+imgsrc+"',sizingMethod='scale')";
        }
        else
        {
            img.src = imgsrc;
        }
    };
        
    this.fractalMapMouseOver = function( event )
    {
	    var area;

	    if ( event.srcElement )
	    {
		    area = event.srcElement;
	    }
	    else
	    {
		    area = event.target;
	    }
	    
        if ( fractalmap.highlight == null )
        {
            fractalmap.setHighlight( area.fmNode );
        }
        else
        {
            fractalmap.pendingHighlight = area.fmNode;
        }
    };

    this.fractalMapMouseMove = function( event )
    {
	    fractalmap.onMouseMove();
    };
    
    this.getNodeTarget = function( node )
    {
        return ( ( node.href.indexOf( "http://" ) == 0 ) && ( node.href.indexOf( ".asx" ) < 0 ) ) ? "_blank" : "_self";
    };
    
    this.initialiseArea = function( node, map )
    {
        var area;

        area = map.appendChild( document.createElement( 'AREA' ) );
        
        area.href = node.href;
        area.target = this.getNodeTarget( node );
        area.fmNode = node;
        
		if ( area.attachEvent )
		{
			area.attachEvent( "onmouseover", this.fractalMapMouseOver );
			area.attachEvent( "onmousemove", this.fractalMapMouseMove );
		}
		else
		{
			area.addEventListener( "mouseover", this.fractalMapMouseOver, false );
			area.addEventListener( "mousemove", this.fractalMapMouseMove, false );
		}

        return area;
    };
    
    this.initialiseData = function( node, parent, index, map )
    {
        var i;
        var area;
        
        node.parent = parent;
        node.index = index;
        
        if ( node.nodes )
        {
            for ( i = 0; i < node.nodes.length; i++ )
            {
                this.initialiseData( node.nodes[ i ], node, i, map );
            }
        }
        
        if ( node.coords )
        {
            if ( node.coords.length == 3 )
            {
                area = this.initialiseArea( node, map );
            
                area.shape = "circle";
                area.coords = node.coords.join( "," );
            }
            else for ( i = 0; i < node.coords.length; i += 4 )
            {
                area = this.initialiseArea( node, map );
            
                area.shape = "rect";
                area.coords = node.coords[ i ] + "," + node.coords[ i + 1 ] + "," + node.coords[ i + 2 ] + "," + node.coords[ i + 3 ];
            }
        }
    };
    
    this.searchFractalMapNode = function( node, href )
    {
        var i;
        var search;
        
        if ( node.href == href )
        {
            search = node;
        }
        else if ( node.nodes )
        {
            for ( i = 0; i < node.nodes.length; i++ )
            {
                if ( null != ( search = this.searchFractalMapNode( node.nodes[ i ], href ) ) )
                {
                    break;
                }
            }
        }
        else search = null;

        return search;
    };
    
    this.idFractalMapNode = function( node )
    {
        var id;
        var index;
        
        if ( node.parent != null )
        {
            id = this.idFractalMapNode( node.parent );
            index = ( new Number( node.index ) ).toString( 10 );
            
            while ( index.length < 4 )
            {
                index = "0" + index;
            }
            
            return id + index;
        }
        return "";
    };
    
    var map;
    var href;
    var node;
    
    map = document.createElement( "MAP" );
    
    map.id = mapid;
    map.name = mapid;
        
    this.initialiseData( data, null, 0, map );
    
    document.body.appendChild( map );
    
    href = ( document.URL.indexOf( "file://" ) == 0 ) ? document.URL.lastIndexOf( "\\" ) : document.URL.lastIndexOf( "/" );
    href = document.URL.substring( href + 1 );

	if ( href.length == 0 )
	{
	    node = data;
	}
	else
	{
	    node = this.searchFractalMapNode( data, href );
	}
	
	if ( node != null )
	{
    	imgsrc = imgsrc.replace( ".png", this.idFractalMapNode( node ) + ".png" );
    }
    
    this.safePng( img, imgsrc );

    this.onTick = function()
    {
        if ( ++this.tick == 2 )
        {
            if ( this.pendingHighlight != null )
            {
                this.setHighlight( this.pendingHighlight );
                
                this.pendingHighlight = null;
            }
        }
    };
    
    this.onMouseMove = function()
    {
        this.tick = 0;
        
        if ( this.exitTimer != 0 )
        {
            window.clearTimeout( this.exitTimer );
            
            this.exitTimer = 0;
        }
    };
    
    this.onMouseExit = function()
    {
        if ( !this.mouseOverLabel )
        {
            this.pendingHighlight = null;
            
            if ( this.exitTimer == 0 )
            {
                this.exitTimer = window.setTimeout( "fractalmap.clearLabels();", 1000 );
            }
        }
    };
    
    this.clearLabels = function()
    {
        this.exitTimer = 0;
        this.highlight = null;
        
        this.drawLabels( null );
    };
    
    this.onClickLabel = function( event )
    {
	    var div;

	    if ( event.srcElement )
	    {
		    div = event.srcElement;
	    }
	    else
	    {
		    div = event.target;
	    }
	    
	    window.open( div.fmNode.href, fractalmap.getNodeTarget( div.fmNode ) );
    };
    
    this.setLabelHotObject = function( event )
    {
	    var div;

	    if ( event.srcElement )
	    {
		    div = event.srcElement;
	    }
	    else
	    {
		    div = event.target;
	    }
	    
	    fractalmap.highlight = div.fmNode;
	    fractalmap.pendingHighlight = null;
	    
	    fractalmap.drawLabels( fractalmap.labels );
	    fractalmap.onMouseMove();
    };
    
    this.setHighlight = function( node )
    {
	    var i;
	    var labels = new Array();
	    var params = new fractalMapTransformParams();
	    var text = this.textDiv;
	    
	    this.highlight = node;
	    
	    if ( ( !node.nodes ) && ( node.parent != null ) )
	    {
	        node = node.parent;
	    }

	    labels.push( new fractalMapDisplayObj( node ) );
	    
	    if ( node.nodes )
	    {
	        for ( i = 0; i < node.nodes.length; i++ )
	        {
		        labels.push( new fractalMapDisplayObj( node.nodes[ i ] ) );
	        }
	    }
	    
	    for ( i = 0; i < labels.length; i++ )
	    {
	        text.innerHTML = labels[ i ].node.title;
	        
	        labels[ i ].naturalWidth = text.offsetWidth;
	    }
	    
	    this.setRect( labels[ 0 ] );
	    this.beginTransform( labels[ 0 ], params );
	    this.beginLabelTransform( null, labels, params );
	    this.drawLabels( labels );
    };
    
    this.setRect = function( obj )
    {
 	    var params;
 	    var node;
        
        if ( obj.node.parent != null )
        {
            node        =   obj.node;
            obj.node    =   obj.node.parent;
            
            this.setRect( obj );
            
            params = new fractalMapTransformParams();

	        this.beginTransform( obj, params );
	        
            obj.node = node;

	        this.transformChild( obj, node.index, params );
        }
        else
        {
            obj.rect.copyRect( this.base );
        }
    };
    
    this.findNode = function( node, id )
    {
        if ( ( id != null ) && ( id.length > 0 ) )
        {
            node = node.nodes[ parseInt( id.substring( 0, 4 ) ) ];
            
            node = this.findNode( node, id.substring( 4 ) );
        }
        return node;
    };
    
    this.beginTransform = function( obj, params )
    {
	    var parentR;
	    var minR;
	    var children;
    	
	    children			=	( obj.node.nodes ) ? obj.node.nodes.length : 0;
	    parentR			    =	obj.rect.right - obj.rect.left;
	    params.startAng	    =	Math.PI;
	    params.children	    =	children;
	    params.parentRadius	=	parentR;

	    if ( children <= 1 )
	    {
		    params.perAng	=	0.0;
		    params.radius	=	parentR / 2;
	    }
	    else
	    {
		    params.ratio	=	( this.squeeze < 0 ) ? Math.sin( Math.PI / Math.min( children, -this.squeeze ) ) : Math.sin( Math.PI / children );
		    params.radius	=	Math.floor( ( parentR * params.ratio ) / ( 1.0 + params.ratio ) );
		    params.perAng	=	( 2.0 * Math.PI ) / children;

		    if ( ( this.squeeze < 0 ) && ( children > -this.squeeze ) )
		    {
		        //*pdwFlags |= FMTF_OVERLAPPING;
		    }

		    if ( children == 2 )
		    {
			    params.startAng += Math.PI / 4.0;
		    }		
		    else if ( ( ( children & 1 ) == 0 ) && ( children > 2 ) && ( this.extraRotate ) )
		    {
			    params.startAng += params.perAng / 2.0;
		    }
    		
		    if ( params.radius < this.squeeze )
		    {
			    minR			=	Math.floor( ( parentR - 10 ) / 2 );
			    params.radius	=	( this.squeeze > minR ) ? minR : this.squeeze;
			    //*pdwFlags		|=	FMTF_OVERLAPPING;
		    }

		    if ( this.percentSqueeze )
		    {
			    if ( children > 3 )
			    {
				    minR = Math.floor( Math.min( this.maxSqueeze, ( parentR * this.pctSqueeze ) / 100 ) );

				    minR = Math.floor( ( parentR - minR ) / 2 );

				    if ( params.radius > minR )
				    {
					    params.radius = minR;
					    //*pdwFlags |= FMTF_OVERLAPPING;
				    }
			    }
		    }
		    else
		    {
			    if ( ( children < 5 ) && ( ( parentR - ( params.radius * 2 ) ) < 3 ) )
			    {
				    params.radius = Math.floor( ( children == 2 ) ? ( parentR / 2 ) - 1 : ( parentR - 3 ) / 2 );
			    }
		    }
	    }

	    params.X = obj.rect.left + Math.floor( parentR / 2 );
	    params.Y = obj.rect.top + Math.floor( parentR / 2 );

	    if ( ( params.radius < this.minimumVisible ) || ( params.radius > ( parentR / 2 ) ) )
	    {
		    params.length	=	parentR;
		    params.radius	=	params.parentRadius;
		    params.length	=	0.0;
		    //*pdwFlags		|=	FMTF_DISPLAYLEAF;

		    return false;
	    }

	    params.length = ( children == 1 ) ? 0.0 : ( parentR - params.radius ) / 2;
	    
	    return true;
    };
    
    this.transformChild = function( label, index, params )
    {
	    var d;
    	
	    d = label.angle	    =	( params.startAng + ( index * params.perAng ) ) % 6.2831853;
	    label.rect.left		=	params.X + this.toInt( params.length * Math.sin( d ) ) - Math.floor( params.radius / 2 );
	    label.rect.top		=	params.Y - this.toInt( params.length * Math.cos( d ) ) - Math.floor( params.radius / 2 );
	    label.rect.right	=	label.rect.left + params.radius;
	    label.rect.bottom	=	label.rect.top + params.radius;
    };
    
    this.beginLabelTransform = function( bounding, labels, params )
    {
        var	label;
        var label2;
        var tempLabel;
        var column;
        var i, j;
        var height;
        var base;
        var bounds;

        height                  =   16;

        params.colSP			=	Math.floor( params.children / 2 );
        params.colMX			=	params.children;
        params.colMN			=	-1;
        params.colPL			=	-1;
        params.colZ1			=	-1;
        params.colZ2			=	-1;
        params.labelHeight		=	height;
        params.labelSpace		=	this.verticalLabelSpacing;
        params.labelHSpace		=	/*pInfo->iExtraWidth +*/ 10;
        params.internalFlags	=	this.FMTF_PROXIMATE | this.FMTF_DRAWLINES;
        params.boundSpacing		=	this.fractalMapStub + this.labelStub;
        params.leftWidth		=	0;
        params.rightWidth		=	0;
        //params.flags			=	pInfo->dwFlags;
        column					=	Math.floor( ( ( ( params.children & 1 ) == 0 ) && ( params.children != 2 ) ) ? params.children / 2 : ( params.children / 2 ) + 1 );

        for ( i = 0; i <= params.children; i++ )
        {
            label = labels[ i ];

            label.naturalWidth += params.labelHSpace;

            if ( i > 0 )
            {
	            this.transformChild( label, i - 1, params );
    			
	            if ( label.naturalWidth > params.leftWidth )
	            {
		            params.leftWidth = label.naturalWidth;
	            }
            }
        }

        params.rightWidth = params.leftWidth;

        if ( params.children > 1 )
        {
            for ( i = params.children - 1; i > 0; i-- )
            {
	            for ( j = 0; j < i; j++ )
	            {
		            label	=	labels[ j + 1 ];
		            label2	=	labels[ j + 2 ];

		            if ( label.angle > label2.angle )
		            {
		                labels[ j + 1 ] = label2;
		                labels[ j + 2 ] = label;
		            }
	            }
            }
        }

        if ( params.children == 2 )
        {
            params.colSP = 1;
        }
        else if ( ( params.children & 1 ) == 0 )
        {
            if ( this.extraRotate )
            {
	            params.colMX--;
    			
	            params.colPL = params.colSP - 1;
	            params.colMN = params.colSP;
            }
            else
            {
	            params.colZ1 = 0;
	            params.colZ2 = params.colSP;
            }
        }
        else
        {
            if ( this.extraRotate )
            {
	            params.colMX--;
    			
	            params.colZ1 = params.colSP;
            }
            else
            {
	            params.colMX--;
    			
	            params.colPL = 0;
	            params.colMN = params.colMX;
	            params.colZ1 = params.colSP;
            }
        }

        label	    =	labels[ 0 ];
        i		    =	Math.floor( params.parentRadius / 2 );
        label.rect  =   new fractalMapRect( params.X - i, params.Y - i, params.X + i, params.Y + i );

        this.transformLabelProximate( params, label, -1 );

        for ( i = 0; i < params.children; i++ )
        {
            this.transformLabelProximate( params, labels[ i + 1 ], i + 1 );
        }
        
        bounds = new fractalMapRect( 0, 0, 0, 0 );

        if ( this.checkIntersect( params, labels, bounds ) == false )
        {
            params.internalFlags |= this.FMTF_LOCKED;

            //CheckBoundingRect( pDef, pLabels, dwLabelStructSize, &rBounds, prBounding, pInfo );
            return;
        }
        else
        {
           label				=	labels[ 0 ];
           label.label.bottom	=	bounds.top - Math.min( this.verticalLabelSpacing, params.labelHeight );
           label.label.top		=	label.label.bottom - params.labelHeight;

            if ( label.label.top >= /*prBounding->top*/0 )
            {
	            this.getHotSpot( label );//pInfo->pLabelUIPart->GetHotspot( &pLabel->rLabel, ( DWORD )label.hotspot, &label.points[ 1 ] );

	            if ( this.checkIntersect( params, labels, bounds ) == false )
	            {
		            params.internalFlags |= this.FMTF_LOCKED;

		            //CheckBoundingRect( pDef, pLabels, dwLabelStructSize, &rBounds, prBounding, pInfo );
		            return;
	            }
            }

            this.transformLabelProximate( params, label, -1 );
        }

        /*params.labelSpace = ( INT ) ( ( ( double ) ( ( rBounds.bottom - rBounds.top ) - ( params.labelHeight * ( INT ) column ) ) / ( double ) ( column - 1 ) ) + 1.0 );

        if ( params.labelSpace >= miVericalLabelSpacing )
        {
            for ( i = 0; i < params.children; i++ )
            {
	            pLabel = ( LPFMTRANSFORMEDLABEL ) ( pLabels + ( dwLabelStructSize * ( i + 1 ) ) );

	            TransformLabel( pLabel, i, &rBounds, pvData );
            }

            if ( CheckIntersect( pDef, pLabels, dwLabelStructSize, &rBounds ) == FALSE )
            {
	            params.internalFlags |= FMTF_LOCKED;

	            CheckBoundingRect( pDef, pLabels, dwLabelStructSize, &rBounds, prBounding, pInfo );
	            return;
            }

            if ( ( params.internalFlags & FMTF_DISPLAYBASE ) != 0 )
            {
	            pLabel					=	( LPFMTRANSFORMEDLABEL ) pLabels;
	           label.label.bottom	=	rBounds.top - min( miVericalLabelSpacing, params.labelHeight );
	           label.label.top		=	pLabel->rLabel.bottom - params.labelHeight;

	            if (label.label.top >= prBounding->top )
	            {
		            pInfo->pLabelUIPart->GetHotspot( &pLabel->rLabel, ( DWORD )label.hotspot, &label.points[ 1 ] );

		            params.internalFlags |= FMTF_LOCKED;

		            CheckBoundingRect( pDef, pLabels, dwLabelStructSize, &rBounds, prBounding, pInfo );
		            return;
	            }
            }
        }

        params.internalFlags	&=	~FMTF_PROXIMATE;
        params.rLabel.left		=	params.iX - ( params.iParentRadius / 2 ) - params.boundSpacing - params.leftWidth;

        if ( params.rLabel.left < prBounding->left )
        {
            params.leftWidth	-=	prBounding->left - params.rLabel.left;
            params.rLabel.left	=	prBounding->left;
        }

        params.rLabel.right = params.rLabel.left + params.iParentRadius + ( params.boundSpacing * 2 ) + params.leftWidth + params.rightWidth;

        if ( params.rLabel.right > prBounding->right )
        {
            params.rightWidth	-=	params.rLabel.right - prBounding->right;
            params.rLabel.right	=	prBounding->right;
        }

        params.rLabel.top	=	params.iY - ( params.iParentRadius / 2 ) - params.boundSpacing - params.labelHeight;
        params.rLabel.bottom	=	params.rLabel.top + params.iParentRadius + ( params.boundSpacing * 2 ) + ( params.labelHeight * 2 );

        if ( params.children != 0 )
        {
            iColumn	=	( INT ) column;
            iBase	=	( pInfo->bDisplayBase ) ? params.labelHeight + miVericalLabelSpacing : 0;
            iHeight	=	( iColumn * params.labelHeight ) + ( ( iColumn - 1 ) * miVericalLabelSpacing ) + iBase;

            if ( ( params.children > 2 ) && ( iHeight < ( params.rLabel.bottom - params.rLabel.top ) ) )
            {
	            params.labelSpace = ( ( ( params.rLabel.bottom - params.rLabel.top ) - ( iColumn * params.labelHeight ) - iBase ) / ( iColumn - 1 ) ) + 1;
            }
            else
            {
	            params.labelSpace = miVericalLabelSpacing;
            }

            iHeight				=	( iColumn * params.labelHeight ) + ( ( iColumn - 1 ) * params.labelSpace ) + iBase;
            params.rLabel.top	=	min( params.rLabel.top, params.iY - ( iHeight / 2 ) );

            if ( params.rLabel.top < prBounding->top )
            {
	            params.rLabel.top = prBounding->top;
            }

            params.rLabel.bottom = params.rLabel.top + iHeight;

            if ( params.rLabel.bottom > prBounding->bottom )
            {
	            OffsetRect( &params.rLabel, 0, -min( params.rLabel.top - prBounding->top, params.rLabel.bottom - prBounding->bottom ) );
            }

            i = ( DWORD ) ( ( sin( params.dPerAng ) * ( double ) params.iParentRadius ) / sin( ( 180.0 - params.dPerAng ) / 2.0 ) );

            if ( i < 10 )
            {
	            params.internalFlags &= ~FMTF_DRAWLINES;
            }
            else if ( ( ( params.flags & FMTF_DISPLAYLEAF ) != 0 ) || ( params.rLabel.bottom > prBounding->bottom ) )
            {
	            params.internalFlags &= ~FMTF_DRAWLINES;
            }
            else
            {
	            for ( i = 0; i < params.children; i++ )
	            {
		            pLabel = ( LPFMTRANSFORMEDLABEL ) ( pLabels + ( dwLabelStructSize * ( i + 1 ) ) );

		            TransformLabel( pLabel, i, &params.rLabel, pvData );

		            if ( pLabel->iValidPoints > 0 )
		            {
			            p.y = label.rect.top + ( ( label.rect.bottom - label.rect.top ) >> 1 );

			            if ( p.y > params.iY )
			            {
				            if ( label.points[ 3 ].y < params.iY )
				            {
					            params.internalFlags &= ~FMTF_DRAWLINES;
					            break;
				            }
			            }
			            else if ( p.y < params.iY )
			            {
				            if ( label.points[ 3 ].y > params.iY )
				            {
					            params.internalFlags &= ~FMTF_DRAWLINES;
					            break;
				            }
			            }
		            }
	            }
            }
        }

        pInfo->iLabelLeftColumn		=	params.leftWidth;
        pInfo->iLabelRightColumn	=	params.rightWidth;
    	
        CopyRect( prBounding, &params.rLabel );*/
    };
    
    this.checkIntersect = function( params, labels, bounds )
    {
	    var i, j;
	    var intersect;

	    for ( i = 0, intersect = false; i < labels.length; i++ )
	    {
		    if ( i == 1 )
		    {
			    bounds.copyRect( labels[ i ].label );
		    }
		    else if ( i > 1 )
		    {
			    bounds.unionRect( labels[ i ].label );
		    }

		    for ( j = i + 1; ( intersect == false ) && ( j < labels.length ); j++ )
		    {
			    intersect = labels[ i ].label.intersectRect( labels[ j ].label );
		    }
	    }
	    return intersect;
    }
    
    this.transformLabelProximate = function( params, label, index )
    {
	    var x;
	    var y;
	    var width	=	( index < 0 ) ? label.naturalWidth : params.leftWidth;
	    var	angle	=	this.transformAngle( params, label, index );

	    x = label.rect.left + Math.floor( ( label.rect.right - label.rect.left ) / 2 );
	    y = label.rect.top + Math.floor( ( label.rect.bottom - label.rect.top ) / 2 );
	    
	    label.points.push( new fractalMapPoint( 0, 0 ) );
	    label.points.push( new fractalMapPoint( 0, 0 ) );
    	
	    this.transformPoint( params, label, angle, 0, label.points[ 0 ] );
	    this.transformPoint( params, label, angle, this.fractalMapStub, label.points[ 1 ] );

	    if ( label.points[ 0 ].X == x )
	    {
		   label.hotspot = ( label.points[ 0 ].Y < y ) ? 4 : 0;
	    }
	    else
	    {
		    if ( label.points[ 0 ].Y < y )
		    {
			   label.hotspot = ( label.points[ 0 ].X < x ) ? 3 : 5;
		    }
		    else if ( label.points[ 0 ].Y == y )
		    {
			   label.hotspot = ( label.points[ 0 ].X < x ) ? 2 : 6;
		    }
		    else
		    {
			   label.hotspot = ( label.points[ 0 ].X < x ) ? 1 : 7;
		    }
	    }

	    //pLabel->iValidPoints	=	( ( iIndex < 0 ) || ( ( pDef->dwFlags & FMTF_DISPLAYLEAF ) == 0 ) ) ? 2 : 0;
	   label.label.left		=	label.points[ 1 ].X - this.BOXX( width, label.hotspot );
	   label.label.top		=	label.points[ 1 ].Y - this.BOXY( params.labelHeight, label.hotspot );
	   label.label.right	=	label.label.left + width;
	   label.label.bottom	=	label.label.top + params.labelHeight;

	    //pDef->pLabelUIPart->GetHotspot( &pLabel->rLabel, ( DWORD )label.hotspot, &label.points[ 1 ] );
    };
    
    this.transformPoint = function( params, label, angle, length, pt )
    {
	    var rx, ry;

	    if ( ( ( params.flags & this.FMTF_DISPLAYLEAF ) != 0 ) || ( ( length == 0 ) && ( params.children > 2 ) ) )
	    {
		    rx		=	Math.floor( params.parentRadius / 2 );
		    ry		=	rx;
		    pt.X	=	params.X;
		    pt.Y	=	params.Y;
	    }
	    else
	    {
		    rx		=	Math.floor( ( label.rect.right - label.rect.left ) / 2 );
		    ry		=	Math.floor( ( label.rect.bottom - label.rect.top ) / 2 );
		    pt.X	=	label.rect.left + rx;
		    pt.Y	=	label.rect.top + ry;
	    }

	    pt.X += this.toInt( ( rx + length ) * Math.sin( angle ) );
	    pt.Y -= this.toInt( ( ry + length ) * Math.cos( angle ) );
    };

    this.transformAngle = function( params, label, index )
    {
	    if ( ( index < 0 ) || ( params.children == 0 ) )
	    {
		    return Math.PI * 2;
	    }
	    else if ( ( params.children == 2 ) || ( ( params.internalFlags & this.FMTF_DISPLAYBASE ) != 0 ) )
	    {
		    if ( label.angle == ( Math.PI * 2 ) )
		    {
			    return label.angle + ( Math.PI / 2 );
		    }
	    }
	    return label.angle;
    };
    
    this.getHotSpot = function( label )
    {
	   label.points[ 1 ].X = label.label.left + this.BOXX( label.label.right - label.label.left, label.hotspot );
	   label.points[ 1 ].Y = label.label.top + this.BOXY( label.label.bottom - label.label.top, label.hotspot );
    };
    
    this.BOXX = function( rw, h )
    {
        return ( rw * this.BoxX[ h ] ) >> 16;
    };
    
    this.BOXY = function( rh, h )
    {
        return ( rh * this.BoxY[ h ] ) >> 16;
    };
    
    this.drawLabels = function( labels )
    {
        var i;
        var cls;
        
        while ( this.fmDiv.firstChild != null )
        {
            this.fmDiv.removeChild( this.fmDiv.firstChild );
        }
        
        if ( labels != null )
        {
            for ( i = 0; i < labels.length; i++ )
            {
                cls = ( labels[ i ].node == this.highlight ) ? " fractalMapHighlight" : "";
                
                this.createLabel( labels[ i ], cls ).innerHTML = labels[ i ].node.title;
                this.drawLine( "fractalMapLine" + cls, labels[ i ].points[ 0 ].X, labels[ i ].points[ 0 ].Y, labels[ i ].points[ 1 ].X, labels[ i ].points[ 1 ].Y );
            }
        }
            
        this.labels = labels;
    };
    
    this.createLabel = function( label, cls )
    {
        var div;
        
        //div = this.createDiv( "fractalMapLabel" + cls, label.label.left, label.label.top, label.label.right - label.label.left - 2, label.label.bottom - label.label.top - 2 );
        
        if ( cls != "" )
        {
            cls = "_hl";
        }
        
        this.createPngImg( "fractalMapBitmapLabel", label.label.left, label.label.top, 6, label.label.bottom - label.label.top, "label_left" + cls );
        this.createPngImg( "fractalMapBitmapLabel", label.label.right - 6, label.label.top, 6, label.label.bottom - label.label.top, "label_right" + cls );
        this.createPngImg( "fractalMapBitmapLabel", label.label.left + 6, label.label.top, label.label.right - label.label.left - 12, label.label.bottom - label.label.top, "label_mid" + cls );
        
        return this.setLabelEvent( label, this.createDiv( "fractalMapBitmapLabel", label.label.left, label.label.top, label.label.right - label.label.left, label.label.bottom - label.label.top ) );
        
        return div;
    };
    
    this.setLabelEvent = function( label, div )
    {
        div.fmNode = label.node;
        
        if ( div.attachEvent )
        {
            div.attachEvent( "onmouseover", this.setLabelHotObject );
            div.attachEvent( "onmousedown", this.onClickLabel );
        }
        else
        {
            div.addEventListener( "mouseover", this.setLabelHotObject, false ); 
            div.addEventListener( "mousedown", this.onClickLabel, false ); 
        }
        
        return div;
    };

    this.drawLine = function( cls, x1, y1, x2, y2 )
    {
	    var dx, dy;
	    var sx, sy;
	    var flush;

	    dx = ( x2 - x1 );
	    dy = ( y2 - y1 );
	    
	    if ( ( dx != 0 ) || ( dy != 0 ) )
	    {
		    if ( dx == 0 )
		    {
			    this.createDiv( cls, x1, Math.min( y1, y2 ), 1, Math.abs( dy ) + 1 );
		    }
		    else if ( dy == 0 )
		    {
			    this.createDiv( cls, Math.min( x1, x2 ), y1, Math.abs( dx ) + 1, 1 );
		    }
		    else if ( Math.abs( dx ) >= Math.abs( dy ) )
		    {
			    dy /= Math.abs( dx );
			    sx = x1;
			    sy = y1;
			    flush = false;

			    while ( x1 != x2 )
			    {
				    y1 += dy;
				    flush = ( Math.floor( y1 ) != sy );

				    if ( flush )
				    {
					    this.createDiv( cls, Math.min( x1, sx ), sy, Math.abs( x1 - sx ) + 1, 1 );
				    }

				    x1 += ( dx < 0 ) ? -1 : 1;

				    if ( flush )
				    {
					    sx = x1;
					    sy = Math.floor( y1 );
				    }
			    }

			    if ( !flush )
			    {
				    this.createDiv( cls, Math.min( x1, sx ), sy, Math.abs( x1 - sx ) + 1, 1 );
			    }
		    }
		    else
		    {
			    dx /= Math.abs( dy );
			    sx = x1;
			    sy = y1;
			    flush = false;

			    while ( y1 != y2 )
			    {
				    x1 += dx;
				    flush = ( Math.floor( x1 ) != sx );

				    if ( flush )
				    {
					    this.createDiv( cls, sx, Math.min( y1, sy ), 1, Math.abs( y1 - sy ) + 1 );
				    }

				    y1 += ( dy < 0 ) ? -1 : 1;

				    if ( flush )
				    {
					    sx = Math.floor( x1 );
					    sy = y1;
				    }
			    }

			    if ( !flush )
			    {
				    this.createDiv( cls, sx, Math.min( y1, sy ), 1, Math.abs( y1 - sy ) + 1 );
			    }
		    }
	    }
    };
    
    this.createPngImg = function( className, x, y, w, h, png )
    {
        var img;

	    img = document.createElement( 'IMG' );

	    img.className = className;
	    img.style.left = x + "px";
	    img.style.top = y + "px";
	    img.style.width = w + "px";
	    img.style.height = h + "px";
	
	    if ( this.isIE )
	    {
    		img.src = "image-lib/blank.gif";
    		img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='image-lib/"+png+".png',sizingMethod='scale')";
	    }
	    else
	    {
	        img.src = "image-lib/" + png + ".png";
	    }
	    
	    return this.fmDiv.appendChild( img );
    };

    this.createDiv = function( className, x, y, w, h )
    {
	    var div;

	    div = document.createElement( 'DIV' );

	    div.className = className;
	    div.style.left = x + "px";
	    div.style.top = y + "px";
	    div.style.width = w + "px";
	    div.style.height = h + "px";
    			
	    return this.fmDiv.appendChild( div );
    };
    
    this.toInt = function( number )
    {
        return ( number < 0 ) ? Math.ceil( number ) : Math.floor( number );
    };
}

function initFractalMap()
{
	fractalmap = new fractalMapDHTML( fmData, document.getElementById( "fractalMapImage" ), "image-lib/sitemap.png", "fractalMapShapes", document.getElementById( "fractalMapText" ), document.getElementById( "fractalMapDivs" ) );

	if ( ( fractalmap.isIE ) && ( parseInt( navigator.appVersion.substring( navigator.appVersion.indexOf( "MSIE" ) + 5 ) ) < 7 ) )
	{
	    document.getElementById( "contentwrapper" ).style.height = "100%";
	}
	
    fractalmap.safePng( document.getElementById( "helpimg" ), "image-lib/help.png" );
}