blob: 5d636c003a65416e15e25d6d1d5f07e814daa6a5 [file] [log] [blame]
<?php
//=======================================================================
// File: JPGRAPH_PLOTBAND.PHP
// Description: PHP4 Graph Plotting library. Extension module.
// Created: 2004-02-18
// Author: Johan Persson (johanp@aditus.nu)
// Ver: $Id$
//
// 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::RaiseL(16001,$aDens);
//(" 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::RaiseL(16002);
//(" 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::RaiseL(16003,$aPattern);
//(" 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::RaiseL(16004);
//('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);
}
}
}
}
?>