<?php
//=======================================================================
// File:	JPGRAPH_PLOTBAND.PHP
// Description:	PHP4 Graph Plotting library. Extension module.
// Created: 	2004-02-18
// Author:	Johan Persson (johanp@aditus.nu)
// Ver:		$Id: jpgraph_plotband.php,v 1.1 2006/10/31 13:15:29 macphisto Exp $
//
// Copyright (c) Aditus Consulting. All rights reserved.
//========================================================================

// Constants for types of static bands in plot area
DEFINE("BAND_RDIAG",1);	// Right diagonal lines
DEFINE("BAND_LDIAG",2); // Left diagonal lines
DEFINE("BAND_SOLID",3); // Solid one color
DEFINE("BAND_VLINE",4); // Vertical lines
DEFINE("BAND_HLINE",5);  // Horizontal lines
DEFINE("BAND_3DPLANE",6);  // "3D" Plane
DEFINE("BAND_HVCROSS",7);  // Vertical/Hor crosses
DEFINE("BAND_DIAGCROSS",8); // Diagonal crosses


// Utility class to hold coordinates for a rectangle
class Rectangle {
    var $x,$y,$w,$h;
    var $xe, $ye;
    function Rectangle($aX,$aY,$aWidth,$aHeight) {
	$this->x=$aX;
	$this->y=$aY;
	$this->w=$aWidth;
	$this->h=$aHeight;
	$this->xe=$aX+$aWidth-1;
	$this->ye=$aY+$aHeight-1;
    }
}

//=====================================================================
// Class RectPattern
// Base class for pattern hierarchi that is used to display patterned
// bands on the graph. Any subclass that doesn't override Stroke()
// must at least implement method DoPattern(&$aImg) which is responsible
// for drawing the pattern onto the graph.
//=====================================================================
class RectPattern {
    var $color;
    var $weight;
    var $rect=null;
    var $doframe=true;
    var $linespacing;	// Line spacing in pixels
    var $iBackgroundColor=-1;  // Default is no background fill
	
    function RectPattern($aColor,$aWeight=1) {
	$this->color = $aColor;
	$this->weight = $aWeight;		
    }

    function SetBackground($aBackgroundColor) {
	$this->iBackgroundColor=$aBackgroundColor;
    }

    function SetPos(&$aRect) {
	$this->rect = $aRect;
    }
	
    function ShowFrame($aShow=true) {
	$this->doframe=$aShow;
    }

    function SetDensity($aDens) {
	if( $aDens < 1 || $aDens > 100 )
	    JpGraphError::Raise(" Desity for pattern must be between 1 and 100. (You tried $aDens)");
	// 1% corresponds to linespacing=50
	// 100 % corresponds to linespacing 1
	$this->linespacing = floor(((100-$aDens)/100.0)*50)+1;

    }

    function Stroke(&$aImg) {
	if( $this->rect == null )
	    JpGraphError::Raise(" No positions specified for pattern.");

	if( !(is_numeric($this->iBackgroundColor) && $this->iBackgroundColor==-1) ) {
	    $aImg->SetColor($this->iBackgroundColor);
	    $aImg->FilledRectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye); 
	}

	$aImg->SetColor($this->color);
	$aImg->SetLineWeight($this->weight);

	// Virtual function implemented by subclass
	$this->DoPattern($aImg);

	// Frame around the pattern area
	if( $this->doframe ) 
	    $aImg->Rectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye);
    }

}


//=====================================================================
// Class RectPatternSolid
// Implements a solid band
//=====================================================================
class RectPatternSolid extends RectPattern {

    function RectPatternSolid($aColor="black",$aWeight=1) {
	parent::RectPattern($aColor,$aWeight);
    }

    function DoPattern(&$aImg) {
	$aImg->SetColor($this->color);
	$aImg->FilledRectangle($this->rect->x,$this->rect->y,
			       $this->rect->xe,$this->rect->ye);
    }
}

//=====================================================================
// Class RectPatternHor
// Implements horizontal line pattern
//=====================================================================
class RectPatternHor extends RectPattern {
		
    function RectPatternHor($aColor="black",$aWeight=1,$aLineSpacing=7) {
	parent::RectPattern($aColor,$aWeight);
	$this->linespacing = $aLineSpacing;
    }
		
    function DoPattern(&$aImg) {
	$x0 = $this->rect->x;		
	$x1 = $this->rect->xe;
	$y = $this->rect->y;
	while( $y < $this->rect->ye ) {
	    $aImg->Line($x0,$y,$x1,$y);
	    $y += $this->linespacing;
	}
    }
}

//=====================================================================
// Class RectPatternVert
// Implements vertical line pattern
//=====================================================================
class RectPatternVert extends RectPattern {
    var $linespacing=10;	// Line spacing in pixels
		
    function RectPatternVert($aColor="black",$aWeight=1,$aLineSpacing=7) {
	parent::RectPattern($aColor,$aWeight);
	$this->linespacing = $aLineSpacing;
    }

    //--------------------
    // Private methods
    //
    function DoPattern(&$aImg) {
	$x = $this->rect->x;		
	$y0 = $this->rect->y;
	$y1 = $this->rect->ye;
	while( $x < $this->rect->xe ) {
	    $aImg->Line($x,$y0,$x,$y1);
	    $x += $this->linespacing;
	}
    }
}


//=====================================================================
// Class RectPatternRDiag
// Implements right diagonal pattern
//=====================================================================
class RectPatternRDiag extends RectPattern {
    var $linespacing;	// Line spacing in pixels
		
    function RectPatternRDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
	parent::RectPattern($aColor,$aWeight);
	$this->linespacing = $aLineSpacing;
    }

    function DoPattern(&$aImg) {
	//  --------------------
	//  | /   /   /   /   /|
	//  |/   /   /   /   / |
	//  |   /   /   /   /  |
	//  --------------------
	$xe = $this->rect->xe;
	$ye = $this->rect->ye;
	$x0 = $this->rect->x + round($this->linespacing/2); 
	$y0 = $this->rect->y;
	$x1 = $this->rect->x; 
	$y1 = $this->rect->y + round($this->linespacing/2);

	while($x0<=$xe && $y1<=$ye) {
	    $aImg->Line($x0,$y0,$x1,$y1);
	    $x0 += $this->linespacing;
	    $y1 += $this->linespacing;
	}

	if( $xe-$x1 > $ye-$y0 ) { 
	    // Width larger than height
	    $x1 = $this->rect->x + ($y1-$ye);
	    $y1 = $ye; 
	    $y0 = $this->rect->y; 
	    while( $x0 <= $xe ) {
		$aImg->Line($x0,$y0,$x1,$y1);
		$x0 += $this->linespacing;
		$x1 += $this->linespacing;
	    }
	    
	    $y0=$this->rect->y + ($x0-$xe);
	    $x0=$xe;
	}
	else {
	    // Height larger than width
	    $diff = $x0-$xe;
	    $y0 = $diff+$this->rect->y;
	    $x0 = $xe;
	    $x1 = $this->rect->x;
	    while( $y1 <= $ye ) {
		$aImg->Line($x0,$y0,$x1,$y1);
		$y1 += $this->linespacing;
		$y0 += $this->linespacing;
	    }
	    
	    $diff = $y1-$ye;
	    $y1 = $ye;
	    $x1 = $diff + $this->rect->x;
	}

	while( $y0 <= $ye ) {
	    $aImg->Line($x0,$y0,$x1,$y1);
	    $y0 += $this->linespacing;		
	    $x1 += $this->linespacing;
	}
    }
}
 
//=====================================================================
// Class RectPatternLDiag
// Implements left diagonal pattern
//=====================================================================
class RectPatternLDiag extends RectPattern {
    var $linespacing;	// Line spacing in pixels
		
    function RectPatternLDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
	$this->linespacing = $aLineSpacing;
	parent::RectPattern($aColor,$aWeight);
    }

    function DoPattern(&$aImg) {
	//  --------------------
	//  |\   \   \   \   \ |
	//  | \   \   \   \   \|
	//  |  \   \   \   \   |
	//  |------------------|
	$xe = $this->rect->xe;
	$ye = $this->rect->ye;
	$x0 = $this->rect->x + round($this->linespacing/2); 
	$y0 = $this->rect->ye;
	$x1 = $this->rect->x; 
	$y1 = $this->rect->ye - round($this->linespacing/2);

	while($x0<=$xe && $y1>=$this->rect->y) {
	    $aImg->Line($x0,$y0,$x1,$y1);
	    $x0 += $this->linespacing;
	    $y1 -= $this->linespacing;
	}
	if( $xe-$x1 > $ye-$this->rect->y ) { 
	    // Width larger than height
	    $x1 = $this->rect->x + ($this->rect->y-$y1);
	    $y0=$ye; $y1=$this->rect->y; 
	    while( $x0 <= $xe ) {
		$aImg->Line($x0,$y0,$x1,$y1);
		$x0 += $this->linespacing;
		$x1 += $this->linespacing;
	    }
	    
	    $y0=$this->rect->ye - ($x0-$xe);
	    $x0=$xe;
	}
	else {
	    // Height larger than width
	    $diff = $x0-$xe;
	    $y0 = $ye-$diff;
	    $x0 = $xe;
	    while( $y1 >= $this->rect->y ) {
		$aImg->Line($x0,$y0,$x1,$y1);
		$y0 -= $this->linespacing;
		$y1 -= $this->linespacing;
	    }	    
	    $diff = $this->rect->y - $y1;
	    $x1 = $this->rect->x + $diff;
	    $y1 = $this->rect->y;
	}
	while( $y0 >= $this->rect->y ) {
	    $aImg->Line($x0,$y0,$x1,$y1);
	    $y0 -= $this->linespacing;
	    $x1 += $this->linespacing;
	}
    }
}

//=====================================================================
// Class RectPattern3DPlane
// Implements "3D" plane pattern
//=====================================================================
class RectPattern3DPlane extends RectPattern {
    var $alpha=50;  // Parameter that specifies the distance
    // to "simulated" horizon in pixel from the
    // top of the band. Specifies how fast the lines
    // converge.

    function RectPattern3DPlane($aColor="black",$aWeight=1) {
	parent::RectPattern($aColor,$aWeight);
	$this->SetDensity(10);  // Slightly larger default
    }

    function SetHorizon($aHorizon) {
	$this->alpha=$aHorizon;
    }
	
    function DoPattern(&$aImg) {
	// "Fake" a nice 3D grid-effect. 
	$x0 = $this->rect->x + $this->rect->w/2;
	$y0 = $this->rect->y;
	$x1 = $x0;
	$y1 = $this->rect->ye;
	$x0_right = $x0;
	$x1_right = $x1;

	// BTW "apa" means monkey in Swedish but is really a shortform for
	// "alpha+a" which was the labels I used on paper when I derived the
	// geometric to get the 3D perspective right. 
	// $apa is the height of the bounding rectangle plus the distance to the
	// artifical horizon (alpha)
	$apa = $this->rect->h + $this->alpha;

	// Three cases and three loops
	// 1) The endpoint of the line ends on the bottom line
	// 2) The endpoint ends on the side
	// 3) Horizontal lines

	// Endpoint falls on bottom line
	$middle=$this->rect->x + $this->rect->w/2;
	$dist=$this->linespacing;
	$factor=$this->alpha /($apa);
	while($x1>$this->rect->x) {
	    $aImg->Line($x0,$y0,$x1,$y1);
	    $aImg->Line($x0_right,$y0,$x1_right,$y1);
	    $x1 = $middle - $dist;
	    $x0 = $middle - $dist * $factor;
	    $x1_right = $middle + $dist;
	    $x0_right =  $middle + $dist * $factor;
	    $dist += $this->linespacing;
	}

	// Endpoint falls on sides
	$dist -= $this->linespacing;
	$d=$this->rect->w/2;
	$c = $apa - $d*$apa/$dist;
	while( $x0>$this->rect->x ) {
	    $aImg->Line($x0,$y0,$this->rect->x,$this->rect->ye-$c);
	    $aImg->Line($x0_right,$y0,$this->rect->xe,$this->rect->ye-$c);
	    $dist += $this->linespacing;			
	    $x0 = $middle - $dist * $factor;
	    $x1 = $middle - $dist;
	    $x0_right =  $middle + $dist * $factor;			
	    $c = $apa - $d*$apa/$dist;
	}		
		
	// Horizontal lines
	// They need some serious consideration since they are a function
	// of perspective depth (alpha) and density (linespacing)
	$x0=$this->rect->x;
	$x1=$this->rect->xe;
	$y=$this->rect->ye;
		
	// The first line is drawn directly. Makes the loop below slightly
	// more readable.
	$aImg->Line($x0,$y,$x1,$y);
	$hls = $this->linespacing;
		
	// A correction factor for vertical "brick" line spacing to account for
	// a) the difference in number of pixels hor vs vert
	// b) visual apperance to make the first layer of "bricks" look more
	// square.
	$vls = $this->linespacing*0.6;
		
	$ds = $hls*($apa-$vls)/$apa;
	// Get the slope for the "perspective line" going from bottom right
	// corner to top left corner of the "first" brick.
		
	// Uncomment the following lines if you want to get a visual understanding
	// of what this helpline does. BTW this mimics the way you would get the
	// perspective right when drawing on paper.
	/*
	  $x0 = $middle;
	  $y0 = $this->rect->ye;
	  $len=floor(($this->rect->ye-$this->rect->y)/$vls);
	  $x1 = $middle+round($len*$ds);
	  $y1 = $this->rect->ye-$len*$vls;
	  $aImg->PushColor("red");
	  $aImg->Line($x0,$y0,$x1,$y1);
	  $aImg->PopColor();
	*/
		
	$y -= $vls;		
	$k=($this->rect->ye-($this->rect->ye-$vls))/($middle-($middle-$ds));
	$dist = $hls;
	while( $y>$this->rect->y ) {
	    $aImg->Line($this->rect->x,$y,$this->rect->xe,$y);
	    $adj = $k*$dist/(1+$dist*$k/$apa);
	    if( $adj < 2 ) $adj=1;
	    $y = $this->rect->ye - round($adj);
	    $dist += $hls;
	}
    }
}

//=====================================================================
// Class RectPatternCross
// Vert/Hor crosses
//=====================================================================
class RectPatternCross extends RectPattern {
    var $vert=null;
    var $hor=null;
    function RectPatternCross($aColor="black",$aWeight=1) {
	parent::RectPattern($aColor,$aWeight);
	$this->vert = new RectPatternVert($aColor,$aWeight);
	$this->hor  = new RectPatternHor($aColor,$aWeight);
    }

    function SetOrder($aDepth) {
	$this->vert->SetOrder($aDepth);
	$this->hor->SetOrder($aDepth);
    }

    function SetPos(&$aRect) {
	parent::SetPos($aRect);
	$this->vert->SetPos($aRect);
	$this->hor->SetPos($aRect);
    }

    function SetDensity($aDens) {
	$this->vert->SetDensity($aDens);
	$this->hor->SetDensity($aDens);
    }

    function DoPattern(&$aImg) {
	$this->vert->DoPattern($aImg);
	$this->hor->DoPattern($aImg);
    }
}

//=====================================================================
// Class RectPatternDiagCross
// Vert/Hor crosses
//=====================================================================

class RectPatternDiagCross extends RectPattern {
    var $left=null;
    var $right=null;
    function RectPatternDiagCross($aColor="black",$aWeight=1) {
	parent::RectPattern($aColor,$aWeight);
	$this->right = new RectPatternRDiag($aColor,$aWeight);
	$this->left  = new RectPatternLDiag($aColor,$aWeight);
    }

    function SetOrder($aDepth) {
	$this->left->SetOrder($aDepth);
	$this->right->SetOrder($aDepth);
    }

    function SetPos(&$aRect) {
	parent::SetPos($aRect);
	$this->left->SetPos($aRect);
	$this->right->SetPos($aRect);
    }

    function SetDensity($aDens) {
	$this->left->SetDensity($aDens);
	$this->right->SetDensity($aDens);
    }

    function DoPattern(&$aImg) {
	$this->left->DoPattern($aImg);
	$this->right->DoPattern($aImg);
    }

}

//=====================================================================
// Class RectPatternFactory
// Factory class for rectangular pattern 
//=====================================================================
class RectPatternFactory {
    function RectPatternFactory() {
	// Empty
    }
    function Create($aPattern,$aColor,$aWeight=1) {
	switch($aPattern) {
	    case BAND_RDIAG:
		$obj =  new RectPatternRDiag($aColor,$aWeight);
		break;
	    case BAND_LDIAG:
		$obj =  new RectPatternLDiag($aColor,$aWeight);
		break;
	    case BAND_SOLID:
		$obj =  new RectPatternSolid($aColor,$aWeight);
		break;
	    case BAND_VLINE:
		$obj =  new RectPatternVert($aColor,$aWeight);
		break;
	    case BAND_HLINE:
		$obj =  new RectPatternHor($aColor,$aWeight);
		break;
	    case BAND_3DPLANE:
		$obj =  new RectPattern3DPlane($aColor,$aWeight);
		break;
	    case BAND_HVCROSS:
		$obj =  new RectPatternCross($aColor,$aWeight);
		break;
	    case BAND_DIAGCROSS:
		$obj =  new RectPatternDiagCross($aColor,$aWeight);
		break;
	    default:
		JpGraphError::Raise(" Unknown pattern specification ($aPattern)");
	}
	return $obj;
    }
}


//=====================================================================
// Class PlotBand
// Factory class which is used by the client.
// It is responsible for factoring the corresponding pattern
// concrete class.
//=====================================================================
class PlotBand {
    var $prect=null;
    var $depth;
    var $dir, $min, $max;

    function PlotBand($aDir,$aPattern,$aMin,$aMax,$aColor="black",$aWeight=1,$aDepth=DEPTH_BACK) {
	$f =  new RectPatternFactory();
	$this->prect = $f->Create($aPattern,$aColor,$aWeight);
	if( is_numeric($aMin) && is_numeric($aMax) && ($aMin > $aMax) ) 
	    JpGraphError::Raise('Min value for plotband is larger than specified max value. Please correct.');
	$this->dir = $aDir;
	$this->min = $aMin;
	$this->max = $aMax;
	$this->depth=$aDepth;
    }
	
    // Set position. aRect contains absolute image coordinates
    function SetPos(&$aRect) {
	assert( $this->prect != null ) ;
	$this->prect->SetPos($aRect);
    }
	
    function ShowFrame($aFlag=true) {
	$this->prect->ShowFrame($aFlag);
    }

    // Set z-order. In front of pplot or in the back
    function SetOrder($aDepth) {
	$this->depth=$aDepth;
    }
	
    function SetDensity($aDens) {
	$this->prect->SetDensity($aDens);
    }
	
    function GetDir() {
	return $this->dir;
    }
	
    function GetMin() {
	return $this->min;
    }
	
    function GetMax() {
	return $this->max;
    }
	
    // Display band
    function Stroke(&$aImg,&$aXScale,&$aYScale) {
	assert( $this->prect != null ) ;
	if( $this->dir == HORIZONTAL ) {
	    if( $this->min === 'min' ) $this->min = $aYScale->GetMinVal();
	    if( $this->max === 'max' ) $this->max = $aYScale->GetMaxVal();

            // Only draw the bar if it actually appears in the range
            if ($this->min < $aYScale->GetMaxVal() && $this->max > $aYScale->GetMinVal()) {
	    
	    // Trucate to limit of axis
	    $this->min = max($this->min, $aYScale->GetMinVal());
	    $this->max = min($this->max, $aYScale->GetMaxVal());

	    $x=$aXScale->scale_abs[0];
	    $y=$aYScale->Translate($this->max);
	    $width=$aXScale->scale_abs[1]-$aXScale->scale_abs[0]+1;
	    $height=abs($y-$aYScale->Translate($this->min))+1;
	    $this->prect->SetPos(new Rectangle($x,$y,$width,$height));
	    $this->prect->Stroke($aImg);
            }
	}
	else {	// VERTICAL
	    if( $this->min === 'min' ) $this->min = $aXScale->GetMinVal();
	    if( $this->max === 'max' ) $this->max = $aXScale->GetMaxVal();
            
            // Only draw the bar if it actually appears in the range
	    if ($this->min < $aXScale->GetMaxVal() && $this->max > $aXScale->GetMinVal()) {
	    
	    // Trucate to limit of axis
	    $this->min = max($this->min, $aXScale->GetMinVal());
	    $this->max = min($this->max, $aXScale->GetMaxVal());

	    $y=$aYScale->scale_abs[1];
	    $x=$aXScale->Translate($this->min);
	    $height=abs($aYScale->scale_abs[1]-$aYScale->scale_abs[0]);
	    $width=abs($x-$aXScale->Translate($this->max));
	    $this->prect->SetPos(new Rectangle($x,$y,$width,$height));
	    $this->prect->Stroke($aImg);
            }
	}
    }
}


?>
