1464 lines
27 KiB
PHP
Executable File
1464 lines
27 KiB
PHP
Executable File
<?php
|
|
/*
|
|
* This work is hereby released into the Public Domain.
|
|
* To view a copy of the public domain dedication,
|
|
* visit http://creativecommons.org/licenses/publicdomain/ or send a letter to
|
|
* Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
|
|
*
|
|
*/
|
|
|
|
require_once dirname(__FILE__)."/Component.class.php";
|
|
|
|
|
|
/**
|
|
* Graph using X and Y axis
|
|
*
|
|
* @package Artichow
|
|
*/
|
|
abstract class awPlot extends awComponent {
|
|
|
|
/**
|
|
* Values for Y axis
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $datay;
|
|
|
|
/**
|
|
* Values for X axis
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $datax;
|
|
|
|
/**
|
|
* Grid properties
|
|
*
|
|
* @var Grid
|
|
*/
|
|
public $grid;
|
|
|
|
/**
|
|
* X axis
|
|
*
|
|
* @var Axis
|
|
*/
|
|
public $xAxis;
|
|
|
|
/**
|
|
* Y axis
|
|
*
|
|
* @var Axis
|
|
*/
|
|
public $yAxis;
|
|
|
|
/**
|
|
* Position of X axis
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $xAxisPosition = awPlot::BOTTOM;
|
|
|
|
/**
|
|
* Set X axis on zero ?
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $xAxisZero = TRUE;
|
|
|
|
/**
|
|
* Set Y axis on zero ?
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $yAxisZero = FALSE;
|
|
|
|
/**
|
|
* Position of Y axis
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $yAxisPosition = awPlot::LEFT;
|
|
|
|
/**
|
|
* Change min value for Y axis
|
|
*
|
|
* @var mixed
|
|
*/
|
|
private $yMin = NULL;
|
|
|
|
/**
|
|
* Change max value for Y axis
|
|
*
|
|
* @var mixed
|
|
*/
|
|
private $yMax = NULL;
|
|
|
|
/**
|
|
* Change min value for X axis
|
|
*
|
|
* @var mixed
|
|
*/
|
|
private $xMin = NULL;
|
|
|
|
/**
|
|
* Change max value for X axis
|
|
*
|
|
* @var mixed
|
|
*/
|
|
private $xMax = NULL;
|
|
|
|
/**
|
|
* Left axis
|
|
*
|
|
* @var int
|
|
*/
|
|
const LEFT = 'left';
|
|
|
|
/**
|
|
* Right axis
|
|
*
|
|
* @var int
|
|
*/
|
|
const RIGHT = 'right';
|
|
|
|
/**
|
|
* Top axis
|
|
*
|
|
* @var int
|
|
*/
|
|
const TOP = 'top';
|
|
|
|
/**
|
|
* Bottom axis
|
|
*
|
|
* @var int
|
|
*/
|
|
const BOTTOM = 'bottom';
|
|
|
|
/**
|
|
* Both left/right or top/bottom axis
|
|
*
|
|
* @var int
|
|
*/
|
|
const BOTH = 'both';
|
|
|
|
/**
|
|
* Build the plot
|
|
*
|
|
*/
|
|
public function __construct() {
|
|
|
|
parent::__construct();
|
|
|
|
$this->grid = new awGrid;
|
|
$this->grid->setBackgroundColor(new awWhite);
|
|
|
|
$this->padding->add(20, 0, 0, 20);
|
|
|
|
$this->xAxis = new awAxis;
|
|
$this->xAxis->addTick('major', new awTick(0, 5));
|
|
$this->xAxis->addTick('minor', new awTick(0, 3));
|
|
$this->xAxis->setTickStyle(awTick::OUT);
|
|
$this->xAxis->label->setFont(new awTuffy(7));
|
|
|
|
$this->yAxis = new awAxis;
|
|
$this->yAxis->auto(TRUE);
|
|
$this->yAxis->addTick('major', new awTick(0, 5));
|
|
$this->yAxis->addTick('minor', new awTick(0, 3));
|
|
$this->yAxis->setTickStyle(awTick::OUT);
|
|
$this->yAxis->setNumberByTick('minor', 'major', 3);
|
|
$this->yAxis->label->setFont(new awTuffy(7));
|
|
$this->yAxis->title->setAngle(90);
|
|
|
|
}
|
|
|
|
/**
|
|
* Get plot values
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getValues() {
|
|
return $this->datay;
|
|
}
|
|
|
|
/**
|
|
* Reduce number of values in the plot
|
|
*
|
|
* @param int $number Reduce number of values to $number
|
|
*/
|
|
public function reduce($number) {
|
|
|
|
$count = count($this->datay);
|
|
$ratio = ceil($count / $number);
|
|
|
|
if($ratio > 1) {
|
|
|
|
$tmpy = $this->datay;
|
|
$datay = array();
|
|
|
|
$datax = array();
|
|
$cbLabel = $this->xAxis->label->getCallbackFunction();
|
|
|
|
for($i = 0; $i < $count; $i += $ratio) {
|
|
|
|
$slice = array_slice($tmpy, $i, $ratio);
|
|
$datay[] = array_sum($slice) / count($slice);
|
|
|
|
// Reduce data on X axis if needed
|
|
if($cbLabel !== NULL) {
|
|
$datax[] = $cbLabel($i + round($ratio / 2));
|
|
}
|
|
|
|
}
|
|
|
|
$this->setValues($datay);
|
|
|
|
if($cbLabel !== NULL) {
|
|
$this->xAxis->setLabelText($datax);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Count values in the plot
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getXAxisNumber() {
|
|
list($min, $max) = $this->xAxis->getRange();
|
|
return ($max - $min + 1);
|
|
}
|
|
|
|
/**
|
|
* Change X axis
|
|
*
|
|
* @param int $axis
|
|
*/
|
|
public function setXAxis($axis) {
|
|
$this->xAxisPosition = $axis;
|
|
}
|
|
|
|
/**
|
|
* Get X axis
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getXAxis() {
|
|
return $this->xAxisPosition;
|
|
}
|
|
|
|
/**
|
|
* Set X axis on zero
|
|
*
|
|
* @param bool $zero
|
|
*/
|
|
public function setXAxisZero($zero) {
|
|
$this->xAxisZero = (bool)$zero;
|
|
}
|
|
|
|
/**
|
|
* Set Y axis on zero
|
|
*
|
|
* @param bool $zero
|
|
*/
|
|
public function setYAxisZero($zero) {
|
|
$this->yAxisZero = (bool)$zero;
|
|
}
|
|
|
|
/**
|
|
* Change Y axis
|
|
*
|
|
* @param int $axis
|
|
*/
|
|
public function setYAxis($axis) {
|
|
$this->yAxisPosition = $axis;
|
|
}
|
|
|
|
/**
|
|
* Get Y axis
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getYAxis() {
|
|
return $this->yAxisPosition;
|
|
}
|
|
|
|
/**
|
|
* Change min value for Y axis
|
|
* Set NULL for auto selection.
|
|
*
|
|
* @param float $value
|
|
*/
|
|
public function setYMin($value) {
|
|
$this->yMin = $value;
|
|
$this->yAxis->auto(FALSE);
|
|
$this->updateAxis();
|
|
}
|
|
|
|
/**
|
|
* Change max value for Y axis
|
|
* Set NULL for auto selection.
|
|
*
|
|
* @param float $value
|
|
*/
|
|
public function setYMax($value) {
|
|
$this->yMax = $value;
|
|
$this->yAxis->auto(FALSE);
|
|
$this->updateAxis();
|
|
}
|
|
|
|
/**
|
|
* Change min value for X axis
|
|
* Set NULL for auto selection.
|
|
*
|
|
* @param float $value
|
|
*/
|
|
public function setXMin($value) {
|
|
$this->xMin = $value;
|
|
$this->updateAxis();
|
|
}
|
|
|
|
/**
|
|
* Change max value for X axis
|
|
* Set NULL for auto selection.
|
|
*
|
|
* @param float $value
|
|
*/
|
|
public function setXMax($value) {
|
|
$this->xMax = $value;
|
|
$this->updateAxis();
|
|
}
|
|
|
|
/**
|
|
* Get min value for Y axis
|
|
*
|
|
* @return float $value
|
|
*/
|
|
public function getYMin() {
|
|
if($this->auto) {
|
|
if(is_null($this->yMin)) {
|
|
$min = array_min($this->datay);
|
|
if($min > 0) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return is_null($this->yMin) ? array_min($this->datay) : (float)$this->yMin;
|
|
}
|
|
|
|
/**
|
|
* Get max value for Y axis
|
|
*
|
|
* @return float $value
|
|
*/
|
|
public function getYMax() {
|
|
if($this->auto) {
|
|
if(is_null($this->yMax)) {
|
|
$max = array_max($this->datay);
|
|
if($max < 0) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return is_null($this->yMax) ? array_max($this->datay) : (float)$this->yMax;
|
|
}
|
|
|
|
/**
|
|
* Get min value for X axis
|
|
*
|
|
* @return float $value
|
|
*/
|
|
public function getXMin() {
|
|
return floor(is_null($this->xMin) ? array_min($this->datax) : $this->xMin);
|
|
}
|
|
|
|
/**
|
|
* Get max value for X axis
|
|
*
|
|
* @return float $value
|
|
*/
|
|
public function getXMax() {
|
|
return (ceil(is_null($this->xMax) ? array_max($this->datax) : (float)$this->xMax)) + ($this->getXCenter() ? 1 : 0);
|
|
}
|
|
|
|
/**
|
|
* Get min value with spaces for Y axis
|
|
*
|
|
* @return float $value
|
|
*/
|
|
public function getRealYMin() {
|
|
$min = $this->getYMin();
|
|
if($this->space->bottom !== NULL) {
|
|
$interval = ($this->getYMax() - $min) * $this->space->bottom / 100;
|
|
return $min - $interval;
|
|
} else {
|
|
return is_null($this->yMin) ? $min : (float)$this->yMin;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get max value with spaces for Y axis
|
|
*
|
|
* @return float $value
|
|
*/
|
|
public function getRealYMax() {
|
|
$max = $this->getYMax();
|
|
if($this->space->top !== NULL) {
|
|
$interval = ($max - $this->getYMin()) * $this->space->top / 100;
|
|
return $max + $interval;
|
|
} else {
|
|
return is_null($this->yMax) ? $max : (float)$this->yMax;
|
|
}
|
|
}
|
|
|
|
public function init(awDriver $driver) {
|
|
|
|
list($x1, $y1, $x2, $y2) = $this->getPosition();
|
|
|
|
// Get space informations
|
|
list($leftSpace, $rightSpace, $topSpace, $bottomSpace) = $this->getSpace($x2 - $x1, $y2 - $y1);
|
|
|
|
$this->xAxis->setPadding($leftSpace, $rightSpace);
|
|
|
|
if($this->space->bottom > 0 or $this->space->top > 0) {
|
|
|
|
list($min, $max) = $this->yAxis->getRange();
|
|
$interval = $max - $min;
|
|
|
|
$this->yAxis->setRange(
|
|
$min - $interval * $this->space->bottom / 100,
|
|
$max + $interval * $this->space->top / 100
|
|
);
|
|
|
|
}
|
|
|
|
// Auto-scaling mode
|
|
$this->yAxis->autoScale();
|
|
|
|
// Number of labels is not specified
|
|
if($this->yAxis->getLabelNumber() === NULL) {
|
|
$number = round(($y2 - $y1) / 75) + 2;
|
|
$this->yAxis->setLabelNumber($number);
|
|
}
|
|
|
|
$this->xAxis->line->setX($x1, $x2);
|
|
$this->yAxis->line->setY($y2, $y1);
|
|
|
|
// Set ticks
|
|
|
|
$this->xAxis->tick('major')->setNumber($this->getXAxisNumber());
|
|
$this->yAxis->tick('major')->setNumber($this->yAxis->getLabelNumber());
|
|
|
|
|
|
// Center X axis on zero
|
|
if($this->xAxisZero) {
|
|
$this->xAxis->setYCenter($this->yAxis, 0);
|
|
}
|
|
|
|
// Center Y axis on zero
|
|
if($this->yAxisZero) {
|
|
$this->yAxis->setXCenter($this->xAxis, 0);
|
|
}
|
|
|
|
// Set axis labels
|
|
$labels = array();
|
|
|
|
list($xMin, $xMax) = $this->xAxis->getRange();
|
|
for($i = $xMin; $i <= $xMax; $i++) {
|
|
$labels[] = $i;
|
|
}
|
|
$this->xAxis->label->set($labels);
|
|
|
|
parent::init($driver);
|
|
|
|
list($x1, $y1, $x2, $y2) = $this->getPosition();
|
|
|
|
list($leftSpace, $rightSpace) = $this->getSpace($x2 - $x1, $y2 - $y1);
|
|
|
|
// Create the grid
|
|
$this->createGrid();
|
|
|
|
// Draw the grid
|
|
$this->grid->setSpace($leftSpace, $rightSpace, 0, 0);
|
|
$this->grid->draw($driver, $x1, $y1, $x2, $y2);
|
|
|
|
}
|
|
|
|
public function drawEnvelope(awDriver $driver) {
|
|
|
|
list($x1, $y1, $x2, $y2) = $this->getPosition();
|
|
|
|
if($this->getXCenter()) {
|
|
$size = $this->xAxis->getDistance(0, 1);
|
|
$this->xAxis->label->move($size / 2, 0);
|
|
$this->xAxis->label->hideLast(TRUE);
|
|
}
|
|
|
|
// Draw top axis
|
|
if($this->xAxisPosition === awPlot::TOP or $this->xAxisPosition === awPlot::BOTH) {
|
|
$top = clone $this->xAxis;
|
|
if($this->xAxisZero === FALSE) {
|
|
$top->line->setY($y1, $y1);
|
|
}
|
|
$top->label->setAlign(NULL, awLabel::TOP);
|
|
$top->label->move(0, -3);
|
|
$top->title->move(0, -25);
|
|
$top->draw($driver);
|
|
}
|
|
|
|
// Draw bottom axis
|
|
if($this->xAxisPosition === awPlot::BOTTOM or $this->xAxisPosition === awPlot::BOTH) {
|
|
$bottom = clone $this->xAxis;
|
|
if($this->xAxisZero === FALSE) {
|
|
$bottom->line->setY($y2, $y2);
|
|
}
|
|
$bottom->label->setAlign(NULL, awLabel::BOTTOM);
|
|
$bottom->label->move(0, 3);
|
|
$bottom->reverseTickStyle();
|
|
$bottom->title->move(0, 25);
|
|
$bottom->draw($driver);
|
|
}
|
|
|
|
// Draw left axis
|
|
if($this->yAxisPosition === awPlot::LEFT or $this->yAxisPosition === awPlot::BOTH) {
|
|
$left = clone $this->yAxis;
|
|
if($this->yAxisZero === FALSE) {
|
|
$left->line->setX($x1, $x1);
|
|
}
|
|
$left->label->setAlign(awLabel::RIGHT);
|
|
$left->label->move(-6, 0);
|
|
$left->title->move(-25, 0);
|
|
$left->draw($driver);
|
|
}
|
|
|
|
// Draw right axis
|
|
if($this->yAxisPosition === awPlot::RIGHT or $this->yAxisPosition === awPlot::BOTH) {
|
|
$right = clone $this->yAxis;
|
|
if($this->yAxisZero === FALSE) {
|
|
$right->line->setX($x2, $x2);
|
|
}
|
|
$right->label->setAlign(awLabel::LEFT);
|
|
$right->label->move(6, 0);
|
|
$right->reverseTickStyle();
|
|
$right->title->move(25, 0);
|
|
$right->draw($driver);
|
|
}
|
|
|
|
}
|
|
|
|
protected function createGrid() {
|
|
|
|
$max = $this->getRealYMax();
|
|
$min = $this->getRealYMin();
|
|
|
|
$number = $this->yAxis->getLabelNumber() - 1;
|
|
|
|
if($number < 1) {
|
|
return;
|
|
}
|
|
|
|
// Horizontal lines of the grid
|
|
|
|
$h = array();
|
|
for($i = 0; $i <= $number; $i++) {
|
|
$h[] = $i / $number;
|
|
}
|
|
|
|
// Vertical lines
|
|
|
|
$major = $this->yAxis->tick('major');
|
|
$interval = $major->getInterval();
|
|
$number = $this->getXAxisNumber() - 1;
|
|
|
|
$w = array();
|
|
|
|
if($number > 0) {
|
|
|
|
for($i = 0; $i <= $number; $i++) {
|
|
if($i%$interval === 0) {
|
|
$w[] = $i / $number;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
$this->grid->setGrid($w, $h);
|
|
|
|
}
|
|
|
|
/**
|
|
* Change values of Y axis
|
|
* This method ignores not numeric values
|
|
*
|
|
* @param array $datay
|
|
* @param array $datax
|
|
*/
|
|
public function setValues($datay, $datax = NULL) {
|
|
|
|
$this->checkArray($datay);
|
|
|
|
foreach($datay as $key => $value) {
|
|
unset($datay[$key]);
|
|
$datay[(int)$key] = $value;
|
|
}
|
|
|
|
if($datax === NULL) {
|
|
$datax = array();
|
|
for($i = 0; $i < count($datay); $i++) {
|
|
$datax[] = $i;
|
|
}
|
|
} else {
|
|
foreach($datax as $key => $value) {
|
|
unset($datax[$key]);
|
|
$datax[(int)$key] = $value;
|
|
}
|
|
}
|
|
|
|
$this->checkArray($datax);
|
|
|
|
if(count($datay) === count($datax)) {
|
|
|
|
// Set values
|
|
$this->datay = $datay;
|
|
$this->datax = $datax;
|
|
// Update axis with the new awvalues
|
|
$this->updateAxis();
|
|
} else {
|
|
awImage::drawError("Class Plot: Plots must have the same number of X and Y points.");
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Return begin and end values
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function getLimit() {
|
|
|
|
$i = 0;
|
|
while(array_key_exists($i, $this->datay) and $this->datay[$i] === NULL) {
|
|
$i++;
|
|
}
|
|
$start = $i;
|
|
$i = count($this->datay) - 1;
|
|
while(array_key_exists($i, $this->datay) and $this->datay[$i] === NULL) {
|
|
$i--;
|
|
}
|
|
$stop = $i;
|
|
|
|
return array($start, $stop);
|
|
|
|
}
|
|
|
|
/**
|
|
* Return TRUE if labels must be centered on X axis, FALSE otherwise
|
|
*
|
|
* @return bool
|
|
*/
|
|
abstract public function getXCenter();
|
|
|
|
private function updateAxis() {
|
|
|
|
$this->xAxis->setRange(
|
|
$this->getXMin(),
|
|
$this->getXMax()
|
|
);
|
|
$this->yAxis->setRange(
|
|
$this->getRealYMin(),
|
|
$this->getRealYMax()
|
|
);
|
|
|
|
}
|
|
|
|
private function checkArray(&$array) {
|
|
|
|
if(is_array($array) === FALSE) {
|
|
awImage::drawError("Class Plot: You tried to set a value that is not an array.");
|
|
}
|
|
|
|
foreach($array as $key => $value) {
|
|
if(is_numeric($value) === FALSE and is_null($value) === FALSE) {
|
|
awImage::drawError("Class Plot: Expected numeric values for the plot.");
|
|
}
|
|
}
|
|
|
|
if(count($array) < 1) {
|
|
awImage::drawError("Class Plot: Your plot must have at least 1 value.");
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
registerClass('Plot', TRUE);
|
|
|
|
class awPlotAxis {
|
|
|
|
/**
|
|
* Left axis
|
|
*
|
|
* @var Axis
|
|
*/
|
|
public $left;
|
|
|
|
/**
|
|
* Right axis
|
|
*
|
|
* @var Axis
|
|
*/
|
|
public $right;
|
|
|
|
/**
|
|
* Top axis
|
|
*
|
|
* @var Axis
|
|
*/
|
|
public $top;
|
|
|
|
/**
|
|
* Bottom axis
|
|
*
|
|
* @var Axis
|
|
*/
|
|
public $bottom;
|
|
|
|
/**
|
|
* Build the group of axis
|
|
*/
|
|
public function __construct() {
|
|
|
|
$this->left = new awAxis;
|
|
$this->left->auto(TRUE);
|
|
$this->left->label->setAlign(awLabel::RIGHT);
|
|
$this->left->label->move(-6, 0);
|
|
$this->yAxis($this->left);
|
|
$this->left->setTickStyle(awTick::OUT);
|
|
$this->left->title->move(-25, 0);
|
|
|
|
$this->right = new awAxis;
|
|
$this->right->auto(TRUE);
|
|
$this->right->label->setAlign(awLabel::LEFT);
|
|
$this->right->label->move(6, 0);
|
|
$this->yAxis($this->right);
|
|
$this->right->setTickStyle(awTick::IN);
|
|
$this->right->title->move(25, 0);
|
|
|
|
$this->top = new awAxis;
|
|
$this->top->label->setAlign(NULL, awLabel::TOP);
|
|
$this->top->label->move(0, -3);
|
|
$this->xAxis($this->top);
|
|
$this->top->setTickStyle(awTick::OUT);
|
|
$this->top->title->move(0, -25);
|
|
|
|
$this->bottom = new awAxis;
|
|
$this->bottom->label->setAlign(NULL, awLabel::BOTTOM);
|
|
$this->bottom->label->move(0, 3);
|
|
$this->xAxis($this->bottom);
|
|
$this->bottom->setTickStyle(awTick::IN);
|
|
$this->bottom->title->move(0, 25);
|
|
|
|
}
|
|
|
|
protected function xAxis(awAxis $axis) {
|
|
|
|
$axis->addTick('major', new awTick(0, 5));
|
|
$axis->addTick('minor', new awTick(0, 3));
|
|
$axis->label->setFont(new awTuffy(7));
|
|
|
|
}
|
|
|
|
protected function yAxis(awAxis $axis) {
|
|
|
|
$axis->addTick('major', new awTick(0, 5));
|
|
$axis->addTick('minor', new awTick(0, 3));
|
|
$axis->setNumberByTick('minor', 'major', 3);
|
|
$axis->label->setFont(new awTuffy(7));
|
|
$axis->title->setAngle(90);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
registerClass('PlotAxis');
|
|
|
|
/**
|
|
* A graph with axis can contain some groups of components
|
|
*
|
|
* @package Artichow
|
|
*/
|
|
class awPlotGroup extends awComponentGroup {
|
|
|
|
/**
|
|
* Grid properties
|
|
*
|
|
* @var Grid
|
|
*/
|
|
public $grid;
|
|
|
|
/**
|
|
* Left, right, top and bottom axis
|
|
*
|
|
* @var PlotAxis
|
|
*/
|
|
public $axis;
|
|
|
|
/**
|
|
* Set the X axis on zero
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $xAxisZero = TRUE;
|
|
|
|
/**
|
|
* Set the Y axis on zero
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $yAxisZero = FALSE;
|
|
|
|
/**
|
|
* Real axis used for Y axis
|
|
*
|
|
* @var string
|
|
*/
|
|
private $yRealAxis = awPlot::LEFT;
|
|
|
|
/**
|
|
* Real axis used for X axis
|
|
*
|
|
* @var string
|
|
*/
|
|
private $xRealAxis = awPlot::BOTTOM;
|
|
|
|
/**
|
|
* Change min value for Y axis
|
|
*
|
|
* @var mixed
|
|
*/
|
|
private $yMin = NULL;
|
|
|
|
/**
|
|
* Change max value for Y axis
|
|
*
|
|
* @var mixed
|
|
*/
|
|
private $yMax = NULL;
|
|
|
|
/**
|
|
* Change min value for X axis
|
|
*
|
|
* @var mixed
|
|
*/
|
|
private $xMin = NULL;
|
|
|
|
/**
|
|
* Change max value for X axis
|
|
*
|
|
* @var mixed
|
|
*/
|
|
private $xMax = NULL;
|
|
|
|
/**
|
|
* Build the PlotGroup
|
|
*
|
|
*/
|
|
public function __construct() {
|
|
|
|
parent::__construct();
|
|
|
|
$this->grid = new awGrid;
|
|
$this->grid->setBackgroundColor(new awWhite);
|
|
|
|
$this->axis = new awPlotAxis;
|
|
|
|
}
|
|
|
|
/**
|
|
* Set the X axis on zero or not
|
|
*
|
|
* @param bool $zero
|
|
*/
|
|
public function setXAxisZero($zero) {
|
|
$this->xAxisZero = (bool)$zero;
|
|
}
|
|
|
|
/**
|
|
* Set the Y axis on zero or not
|
|
*
|
|
* @param bool $zero
|
|
*/
|
|
public function setYAxisZero($zero) {
|
|
$this->yAxisZero = (bool)$zero;
|
|
}
|
|
|
|
/**
|
|
* Change min value for Y axis
|
|
* Set NULL for auto selection.
|
|
*
|
|
* @param float $value
|
|
*/
|
|
public function setYMin($value) {
|
|
$this->axis->left->auto(FALSE);
|
|
$this->axis->right->auto(FALSE);
|
|
$this->yMin = $value;
|
|
}
|
|
|
|
/**
|
|
* Change max value for Y axis
|
|
* Set NULL for auto selection.
|
|
*
|
|
* @param float $value
|
|
*/
|
|
public function setYMax($value) {
|
|
$this->axis->left->auto(FALSE);
|
|
$this->axis->right->auto(FALSE);
|
|
$this->yMax = $value;
|
|
}
|
|
|
|
/**
|
|
* Change min value for X axis
|
|
* Set NULL for auto selection.
|
|
*
|
|
* @param float $value
|
|
*/
|
|
public function setXMin($value) {
|
|
$this->xMin = $value;
|
|
}
|
|
|
|
/**
|
|
* Change max value for X axis
|
|
* Set NULL for auto selection.
|
|
*
|
|
* @param float $value
|
|
*/
|
|
public function setXMax($value) {
|
|
$this->xMax = $value;
|
|
}
|
|
|
|
/**
|
|
* Get min value for X axis
|
|
*
|
|
* @return float $value
|
|
*/
|
|
public function getXMin() {
|
|
|
|
return $this->getX('min');
|
|
|
|
}
|
|
|
|
/**
|
|
* Get max value for X axis
|
|
*
|
|
* @return float $value
|
|
*/
|
|
public function getXMax() {
|
|
|
|
return $this->getX('max');
|
|
|
|
}
|
|
|
|
private function getX($type) {
|
|
|
|
switch($type) {
|
|
case 'max' :
|
|
if($this->xMax !== NULL) {
|
|
return $this->xMax;
|
|
}
|
|
break;
|
|
case 'min' :
|
|
if($this->xMin !== NULL) {
|
|
return $this->xMin;
|
|
}
|
|
break;
|
|
}
|
|
|
|
$value = NULL;
|
|
$get = 'getX'.ucfirst($type);
|
|
|
|
for($i = 0; $i < count($this->components); $i++) {
|
|
|
|
$component = $this->components[$i];
|
|
|
|
if($value === NULL) {
|
|
$value = $component->$get();
|
|
} else {
|
|
$value = $type($value, $component->$get());
|
|
}
|
|
|
|
}
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
/**
|
|
* Get min value with spaces for Y axis
|
|
*
|
|
* @param string $axis Axis name
|
|
* @return float $value
|
|
*/
|
|
public function getRealYMin($axis = NULL) {
|
|
|
|
if($axis === NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
$min = $this->getRealY('min', $axis);
|
|
$max = $this->getRealY('max', $axis);
|
|
|
|
if($this->space->bottom !== NULL) {
|
|
$interval = ($min - $max) * $this->space->bottom / 100;
|
|
return $min + $interval;
|
|
} else {
|
|
return $min;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Get max value with spaces for Y axis
|
|
*
|
|
* @param string $axis Axis name
|
|
* @return float $value
|
|
*/
|
|
public function getRealYMax($axis = NULL) {
|
|
|
|
if($axis === NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
$min = $this->getRealY('min', $axis);
|
|
$max = $this->getRealY('max', $axis);
|
|
|
|
if($this->space->top !== NULL) {
|
|
$interval = ($max - $min) * $this->space->top / 100;
|
|
return $max + $interval;
|
|
} else {
|
|
return $max;
|
|
}
|
|
|
|
}
|
|
|
|
private function getRealY($type, $axis) {
|
|
|
|
switch($type) {
|
|
case 'max' :
|
|
if($this->yMax !== NULL) {
|
|
return $this->yMax;
|
|
}
|
|
break;
|
|
case 'min' :
|
|
if($this->yMin !== NULL) {
|
|
return $this->yMin;
|
|
}
|
|
break;
|
|
}
|
|
|
|
$value = NULL;
|
|
$get = 'getY'.ucfirst($type);
|
|
|
|
for($i = 0; $i < count($this->components); $i++) {
|
|
|
|
$component = $this->components[$i];
|
|
|
|
switch($axis) {
|
|
|
|
case awPlot::LEFT :
|
|
case awPlot::RIGHT :
|
|
$test = ($component->getYAxis() === $axis);
|
|
break;
|
|
default :
|
|
$test = FALSE;
|
|
|
|
}
|
|
|
|
if($test) {
|
|
|
|
$auto = $component->yAxis->isAuto();
|
|
$this->axis->{$axis}->auto($auto);
|
|
|
|
if($value === NULL) {
|
|
$value = $component->$get();
|
|
} else {
|
|
$value = $type($value, $component->$get());
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
public function init(awDriver $driver) {
|
|
|
|
list($x1, $y1, $x2, $y2) = $this->getPosition();
|
|
|
|
// Get PlotGroup space
|
|
list($leftSpace, $rightSpace, $topSpace, $bottomSpace) = $this->getSpace($x2 - $x1, $y2 - $y1);
|
|
|
|
// Count values in the group
|
|
$values = $this->getXAxisNumber();
|
|
|
|
// Init the PlotGroup
|
|
$this->axis->top->line->setX($x1, $x2);
|
|
$this->axis->bottom->line->setX($x1, $x2);
|
|
$this->axis->left->line->setY($y2, $y1);
|
|
$this->axis->right->line->setY($y2, $y1);
|
|
|
|
$this->axis->top->setPadding($leftSpace, $rightSpace);
|
|
$this->axis->bottom->setPadding($leftSpace, $rightSpace);
|
|
|
|
$xMin = $this->getXMin();
|
|
$xMax = $this->getXMax();
|
|
|
|
$this->axis->top->setRange($xMin, $xMax);
|
|
$this->axis->bottom->setRange($xMin, $xMax);
|
|
|
|
for($i = 0; $i < count($this->components); $i++) {
|
|
|
|
|
|
$component = $this->components[$i];
|
|
|
|
$component->auto($this->auto);
|
|
|
|
// Copy space to the component
|
|
|
|
$component->setSpace($this->space->left, $this->space->right, $this->space->top, $this->space->bottom);
|
|
|
|
$component->xAxis->setPadding($leftSpace, $rightSpace);
|
|
$component->xAxis->line->setX($x1, $x2);
|
|
|
|
$component->yAxis->line->setY($y2, $y1);
|
|
|
|
}
|
|
|
|
// Set Y axis range
|
|
foreach(array('left', 'right') as $axis) {
|
|
|
|
if($this->isAxisUsed($axis)) {
|
|
|
|
$min = $this->getRealYMin($axis);
|
|
$max = $this->getRealYMax($axis);
|
|
|
|
$interval = $max - $min;
|
|
|
|
$this->axis->{$axis}->setRange(
|
|
$min - $interval * $this->space->bottom / 100,
|
|
$max + $interval * $this->space->top / 100
|
|
);
|
|
|
|
// Auto-scaling mode
|
|
if($this->axis->{$axis}->isAuto()) {
|
|
$this->axis->{$axis}->autoScale();
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if($this->axis->left->getLabelNumber() === NULL) {
|
|
$number = round(($y2 - $y1) / 75) + 2;
|
|
$this->axis->left->setLabelNumber($number);
|
|
}
|
|
|
|
if($this->axis->right->getLabelNumber() === NULL) {
|
|
$number = round(($y2 - $y1) / 75) + 2;
|
|
$this->axis->right->setLabelNumber($number);
|
|
}
|
|
|
|
// Center labels on X axis if needed
|
|
$test = array(awPlot::TOP => FALSE, awPlot::BOTTOM => FALSE);
|
|
|
|
for($i = 0; $i < count($this->components); $i++) {
|
|
|
|
|
|
$component = $this->components[$i];
|
|
|
|
|
|
if($component->getValues() !== NULL) {
|
|
|
|
$axis = $component->getXAxis();
|
|
|
|
if($test[$axis] === FALSE) {
|
|
|
|
// Center labels for bar plots
|
|
if($component->getXCenter()) {
|
|
$size = $this->axis->{$axis}->getDistance(0, 1);
|
|
$this->axis->{$axis}->label->move($size / 2, 0);
|
|
$this->axis->{$axis}->label->hideLast(TRUE);
|
|
$test[$axis] = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// Set axis labels
|
|
$labels = array();
|
|
for($i = $xMin; $i <= $xMax; $i++) {
|
|
$labels[] = $i;
|
|
}
|
|
if($this->axis->top->label->count() === 0) {
|
|
$this->axis->top->label->set($labels);
|
|
}
|
|
if($this->axis->bottom->label->count() === 0) {
|
|
$this->axis->bottom->label->set($labels);
|
|
}
|
|
|
|
// Set ticks
|
|
|
|
$this->axis->top->tick('major')->setNumber($values);
|
|
$this->axis->bottom->tick('major')->setNumber($values);
|
|
$this->axis->left->tick('major')->setNumber($this->axis->left->getLabelNumber());
|
|
$this->axis->right->tick('major')->setNumber($this->axis->right->getLabelNumber());
|
|
|
|
|
|
// Set X axis on zero
|
|
if($this->xAxisZero) {
|
|
$axis = $this->selectYAxis();
|
|
$this->axis->bottom->setYCenter($axis, 0);
|
|
$this->axis->top->setYCenter($axis, 0);
|
|
}
|
|
|
|
// Set Y axis on zero
|
|
if($this->yAxisZero) {
|
|
$axis = $this->selectXAxis();
|
|
$this->axis->left->setXCenter($axis, 1);
|
|
$this->axis->right->setXCenter($axis, 1);
|
|
}
|
|
|
|
parent::init($driver);
|
|
|
|
list($leftSpace, $rightSpace, $topSpace, $bottomSpace) = $this->getSpace($x2 - $x1, $y2 - $y1);
|
|
|
|
// Create the grid
|
|
$this->createGrid();
|
|
|
|
// Draw the grid
|
|
$this->grid->setSpace($leftSpace, $rightSpace, 0, 0);
|
|
$this->grid->draw($driver, $x1, $y1, $x2, $y2);
|
|
|
|
}
|
|
|
|
public function drawComponent(awDriver $driver, $x1, $y1, $x2, $y2, $aliasing) {
|
|
|
|
$xMin = $this->getXMin();
|
|
$xMax = $this->getXMax();
|
|
|
|
$maxLeft = $this->getRealYMax(awPlot::LEFT);
|
|
$maxRight = $this->getRealYMax(awPlot::RIGHT);
|
|
|
|
$minLeft = $this->getRealYMin(awPlot::LEFT);
|
|
$minRight = $this->getRealYMin(awPlot::RIGHT);
|
|
|
|
foreach($this->components as $component) {
|
|
|
|
$min = $component->getYMin();
|
|
$max = $component->getYMax();
|
|
|
|
// Set component minimum and maximum
|
|
if($component->getYAxis() === awPlot::LEFT) {
|
|
|
|
list($min, $max) = $this->axis->left->getRange();
|
|
|
|
$component->setYMin($min);
|
|
$component->setYMax($max);
|
|
|
|
} else {
|
|
|
|
list($min, $max) = $this->axis->right->getRange();
|
|
|
|
$component->setYMin($min);
|
|
$component->setYMax($max);
|
|
|
|
}
|
|
|
|
$component->setXAxisZero($this->xAxisZero);
|
|
$component->setYAxisZero($this->yAxisZero);
|
|
|
|
$component->xAxis->setRange($xMin, $xMax);
|
|
|
|
$component->drawComponent(
|
|
$driver,
|
|
$x1, $y1,
|
|
$x2, $y2,
|
|
$aliasing
|
|
);
|
|
|
|
$component->setYMin($min);
|
|
$component->setYMax($max);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public function drawEnvelope(awDriver $driver) {
|
|
|
|
list($x1, $y1, $x2, $y2) = $this->getPosition();
|
|
|
|
// Hide unused axis
|
|
foreach(array(awPlot::LEFT, awPlot::RIGHT, awPlot::TOP, awPlot::BOTTOM) as $axis) {
|
|
if($this->isAxisUsed($axis) === FALSE) {
|
|
$this->axis->{$axis}->hide(TRUE);
|
|
}
|
|
}
|
|
|
|
// Draw top axis
|
|
$top = $this->axis->top;
|
|
if($this->xAxisZero === FALSE) {
|
|
$top->line->setY($y1, $y1);
|
|
}
|
|
$top->draw($driver);
|
|
|
|
// Draw bottom axis
|
|
$bottom = $this->axis->bottom;
|
|
if($this->xAxisZero === FALSE) {
|
|
$bottom->line->setY($y2, $y2);
|
|
}
|
|
$bottom->draw($driver);
|
|
|
|
// Draw left axis
|
|
$left = $this->axis->left;
|
|
if($this->yAxisZero === FALSE) {
|
|
$left->line->setX($x1, $x1);
|
|
}
|
|
$left->draw($driver);
|
|
|
|
// Draw right axis
|
|
$right = $this->axis->right;
|
|
if($this->yAxisZero === FALSE) {
|
|
$right->line->setX($x2, $x2);
|
|
}
|
|
$right->draw($driver);
|
|
|
|
}
|
|
|
|
/**
|
|
* Is the specified axis used ?
|
|
*
|
|
* @param string $axis Axis name
|
|
* @return bool
|
|
*/
|
|
protected function isAxisUsed($axis) {
|
|
|
|
for($i = 0; $i < count($this->components); $i++) {
|
|
|
|
$component = $this->components[$i];
|
|
|
|
switch($axis) {
|
|
|
|
case awPlot::LEFT :
|
|
case awPlot::RIGHT :
|
|
if($component->getYAxis() === $axis) {
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case awPlot::TOP :
|
|
case awPlot::BOTTOM :
|
|
if($component->getXAxis() === $axis) {
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
protected function createGrid() {
|
|
|
|
$max = $this->getRealYMax(awPlot::LEFT);
|
|
$min = $this->getRealYMin(awPlot::RIGHT);
|
|
|
|
// Select axis (left if possible, right otherwise)
|
|
$axis = $this->selectYAxis();
|
|
|
|
$number = $axis->getLabelNumber() - 1;
|
|
|
|
if($number < 1) {
|
|
return;
|
|
}
|
|
|
|
// Horizontal lines of grid
|
|
|
|
$h = array();
|
|
for($i = 0; $i <= $number; $i++) {
|
|
$h[] = $i / $number;
|
|
}
|
|
|
|
// Vertical lines
|
|
|
|
$major = $axis->tick('major');
|
|
$interval = $major->getInterval();
|
|
$number = $this->getXAxisNumber() - 1;
|
|
|
|
$w = array();
|
|
|
|
if($number > 0) {
|
|
|
|
for($i = 0; $i <= $number; $i++) {
|
|
if($i%$interval === 0) {
|
|
$w[] = $i / $number;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
$this->grid->setGrid($w, $h);
|
|
|
|
}
|
|
|
|
protected function selectYAxis(){
|
|
|
|
// Select axis (left if possible, right otherwise)
|
|
if($this->isAxisUsed(awPlot::LEFT)) {
|
|
$axis = $this->axis->left;
|
|
} else {
|
|
$axis = $this->axis->right;
|
|
}
|
|
|
|
return $axis;
|
|
|
|
}
|
|
|
|
protected function selectXAxis(){
|
|
|
|
// Select axis (bottom if possible, top otherwise)
|
|
if($this->isAxisUsed(awPlot::BOTTOM)) {
|
|
$axis = $this->axis->bottom;
|
|
} else {
|
|
$axis = $this->axis->top;
|
|
}
|
|
|
|
return $axis;
|
|
|
|
}
|
|
|
|
protected function getXAxisNumber() {
|
|
$offset = $this->components[0];
|
|
$max = $offset->getXAxisNumber();
|
|
for($i = 1; $i < count($this->components); $i++) {
|
|
$offset = $this->components[$i];
|
|
$max = max($max, $offset->getXAxisNumber());
|
|
}
|
|
return $max;
|
|
}
|
|
|
|
}
|
|
|
|
registerClass('PlotGroup');
|