mirror of
https://github.com/cosmocode/dokuwiki-plugin-statdisplay.git
synced 2025-08-16 16:17:47 +00:00
3141 lines
95 KiB
PHP
3141 lines
95 KiB
PHP
<?php
|
|
/**
|
|
* pChart - a PHP class to build charts!
|
|
* Copyright (C) 2008 Jean-Damien POGOLOTTI
|
|
* Version 2.0
|
|
*
|
|
* http://pchart.sourceforge.net
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 1,2,3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
require_once(dirname(__FILE__).'/ConversionHelpers.php');
|
|
require_once(dirname(__FILE__).'/ShadowProperties.php');
|
|
require_once(dirname(__FILE__).'/Color.php');
|
|
require_once(dirname(__FILE__).'/Palette.php');
|
|
require_once(dirname(__FILE__).'/ICanvas.php');
|
|
require_once(dirname(__FILE__).'/GridStyle.php');
|
|
require_once(dirname(__FILE__).'/ScaleStyle.php');
|
|
|
|
/* Declare some script wide constants */
|
|
define ( "SCALE_NORMAL", 1 );
|
|
define ( "SCALE_ADDALL", 2 );
|
|
define ( "SCALE_START0", 3 );
|
|
define ( "SCALE_ADDALLSTART0", 4 );
|
|
define ( "TARGET_GRAPHAREA", 1 );
|
|
define ( "TARGET_BACKGROUND", 2 );
|
|
define ( "ALIGN_TOP_LEFT", 1 );
|
|
define ( "ALIGN_TOP_CENTER", 2 );
|
|
define ( "ALIGN_TOP_RIGHT", 3 );
|
|
define ( "ALIGN_LEFT", 4 );
|
|
define ( "ALIGN_CENTER", 5 );
|
|
define ( "ALIGN_RIGHT", 6 );
|
|
define ( "ALIGN_BOTTOM_LEFT", 7 );
|
|
define ( "ALIGN_BOTTOM_CENTER", 8 );
|
|
define ( "ALIGN_BOTTOM_RIGHT", 9 );
|
|
|
|
/**
|
|
* pChart class definition
|
|
*/
|
|
class pChart {
|
|
protected $palette;
|
|
|
|
/* Some static vars used in the class */
|
|
protected $XSize = NULL;
|
|
protected $YSize = NULL;
|
|
protected $Picture = NULL;
|
|
protected $ImageMap = NULL;
|
|
|
|
/* Error management */
|
|
protected $ErrorReporting = FALSE;
|
|
protected $ErrorInterface = "CLI";
|
|
protected $Errors = NULL;
|
|
protected $ErrorFontName = "Fonts/pf_arma_five.ttf";
|
|
protected $ErrorFontSize = 6;
|
|
|
|
/* vars related to the graphing area */
|
|
protected $GArea_X1 = NULL;
|
|
protected $GArea_Y1 = NULL;
|
|
protected $GArea_X2 = NULL;
|
|
protected $GArea_Y2 = NULL;
|
|
protected $GAreaXOffset = NULL;
|
|
protected $VMax = NULL;
|
|
protected $VMin = NULL;
|
|
protected $VXMax = NULL;
|
|
protected $VXMin = NULL;
|
|
protected $Divisions = NULL;
|
|
protected $XDivisions = NULL;
|
|
protected $DivisionHeight = NULL;
|
|
protected $XDivisionHeight = NULL;
|
|
protected $DivisionCount = NULL;
|
|
protected $XDivisionCount = NULL;
|
|
protected $DivisionRatio = NULL;
|
|
protected $XDivisionRatio = NULL;
|
|
protected $DivisionWidth = NULL;
|
|
protected $DataCount = NULL;
|
|
|
|
/* Text format related vars */
|
|
protected $FontName = NULL;
|
|
protected $FontSize = NULL;
|
|
protected $DateFormat = "d/m/Y";
|
|
|
|
/* Lines format related vars */
|
|
protected $LineWidth = 1;
|
|
protected $LineDotSize = 0;
|
|
|
|
/* Shadow settings */
|
|
private $shadowProperties;
|
|
|
|
/* Image Map settings */
|
|
protected $BuildMap = FALSE;
|
|
protected $MapFunction = NULL;
|
|
protected $tmpFolder = "tmp/";
|
|
protected $MapID = NULL;
|
|
|
|
/**
|
|
* @brief An abstract ICanvas onto which we draw the chart
|
|
*
|
|
* @todo This probably shouldn't be protected, I'm still working
|
|
* on how the modules are going to break down between the various
|
|
* chart types.
|
|
*/
|
|
protected $canvas = null;
|
|
|
|
/**
|
|
* This function create the background picture
|
|
*/
|
|
function __construct($XSize, $YSize, ICanvas $canvas) {
|
|
$this->palette = Palette::defaultPalette();
|
|
|
|
$this->XSize = $XSize;
|
|
$this->YSize = $YSize;
|
|
|
|
$this->setFontProperties ( "tahoma.ttf", 8 );
|
|
|
|
$this->shadowProperties = ShadowProperties::FromDefaults();
|
|
|
|
$this->canvas = $canvas;
|
|
}
|
|
|
|
/**
|
|
* Set if warnings should be reported
|
|
*/
|
|
function reportWarnings($Interface = "CLI") {
|
|
$this->ErrorReporting = TRUE;
|
|
$this->ErrorInterface = $Interface;
|
|
}
|
|
|
|
/**
|
|
* Set the font properties
|
|
*/
|
|
function setFontProperties($FontName, $FontSize) {
|
|
$this->FontName = $FontName;
|
|
$this->FontSize = $FontSize;
|
|
}
|
|
|
|
public function setPalette(Palette $newPalette) {
|
|
$this->palette = $newPalette;
|
|
}
|
|
|
|
/**
|
|
* Set the shadow properties
|
|
*/
|
|
function setShadowProperties($XDistance = 1, $YDistance = 1, Color $color = null, $Alpha = 50, $Blur = 0) {
|
|
if ($color == null) {
|
|
$color = new Color(60, 60, 60);
|
|
}
|
|
|
|
$this->shadowProperties = ShadowProperties::FromSettings($XDistance,
|
|
$YDistance,
|
|
$color,
|
|
$Alpha,
|
|
$Blur);
|
|
}
|
|
|
|
/**
|
|
* Remove shadow option
|
|
*/
|
|
function clearShadow() {
|
|
$this->shadowProperties = ShadowProperties::FromDefaults();
|
|
}
|
|
|
|
/**
|
|
* Load Color Palette from file
|
|
*/
|
|
function loadColorPalette($FileName, $Delimiter = ",") {
|
|
$handle = @fopen ( $FileName, "r" );
|
|
|
|
if ($handle == null) {
|
|
throw new Exception("Failed to open file in loadColorPalette");
|
|
}
|
|
|
|
$ColorID = 0;
|
|
if ($handle) {
|
|
while ( ! feof ( $handle ) ) {
|
|
$buffer = fgets ( $handle, 4096 );
|
|
$buffer = str_replace ( chr ( 10 ), "", $buffer );
|
|
$buffer = str_replace ( chr ( 13 ), "", $buffer );
|
|
$Values = explode ( $Delimiter, $buffer );
|
|
if (count ( $Values ) == 3) {
|
|
$this->palette->colors[$ColorID] = new Color($Values[0],
|
|
$Values[1],
|
|
$Values[2]);
|
|
$ColorID ++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set line style
|
|
*/
|
|
function setLineStyle($Width = 1, $DotSize = 0) {
|
|
$this->LineWidth = $Width;
|
|
$this->LineDotSize = $DotSize;
|
|
}
|
|
|
|
/**
|
|
* Set the graph area location
|
|
*/
|
|
function setGraphArea($X1, $Y1, $X2, $Y2) {
|
|
$this->GArea_X1 = $X1;
|
|
$this->GArea_Y1 = $Y1;
|
|
$this->GArea_X2 = $X2;
|
|
$this->GArea_Y2 = $Y2;
|
|
}
|
|
|
|
/**
|
|
* Prepare the graph area
|
|
*/
|
|
private function drawGraphArea(BackgroundStyle $style) {
|
|
$this->canvas->drawFilledRectangle(new Point($this->GArea_X1, $this->GArea_Y1),
|
|
new Point($this->GArea_X2, $this->GArea_Y2),
|
|
$style->getBackgroundColor(),
|
|
$this->shadowProperties, FALSE );
|
|
$this->canvas->drawRectangle(new Point($this->GArea_X1, $this->GArea_Y1),
|
|
new Point($this->GArea_X2, $this->GArea_Y2),
|
|
$style->getBackgroundColor()->addRGBIncrement(-40),
|
|
$style->getBorderWidth(),
|
|
$style->getBorderDotSize(),
|
|
$this->shadowProperties);
|
|
|
|
if ($style->useStripe()) {
|
|
$color2 = $style->getBackgroundColor()->addRGBIncrement(-15);
|
|
|
|
$SkewWidth = $this->GArea_Y2 - $this->GArea_Y1 - 1;
|
|
|
|
for($i = $this->GArea_X1 - $SkewWidth; $i <= $this->GArea_X2; $i = $i + 4) {
|
|
$X1 = $i;
|
|
$Y1 = $this->GArea_Y2;
|
|
$X2 = $i + $SkewWidth;
|
|
$Y2 = $this->GArea_Y1;
|
|
|
|
if ($X1 < $this->GArea_X1) {
|
|
$X1 = $this->GArea_X1;
|
|
$Y1 = $this->GArea_Y1 + $X2 - $this->GArea_X1 + 1;
|
|
}
|
|
|
|
if ($X2 >= $this->GArea_X2) {
|
|
$Y2 = $this->GArea_Y1 + $X2 - $this->GArea_X2 + 1;
|
|
$X2 = $this->GArea_X2 - 1;
|
|
}
|
|
|
|
$this->canvas->drawLine(new Point($X1, $Y1),
|
|
new Point($X2, $Y2 + 1),
|
|
$color2,
|
|
1,
|
|
0,
|
|
ShadowProperties::NoShadow());
|
|
}
|
|
}
|
|
}
|
|
|
|
public function drawGraphBackground(BackgroundStyle $style) {
|
|
$this->drawGraphArea($style);
|
|
$this->drawGraphAreaGradient($style);
|
|
}
|
|
|
|
/**
|
|
* Allow you to clear the scale : used if drawing multiple charts
|
|
*/
|
|
function clearScale() {
|
|
$this->VMin = NULL;
|
|
$this->VMax = NULL;
|
|
$this->VXMin = NULL;
|
|
$this->VXMax = NULL;
|
|
$this->Divisions = NULL;
|
|
$this->XDivisions = NULL;
|
|
}
|
|
|
|
/**
|
|
* Allow you to fix the scale, use this to bypass the automatic scaling
|
|
*/
|
|
function setFixedScale($VMin, $VMax, $Divisions = 5, $VXMin = 0, $VXMax = 0, $XDivisions = 5) {
|
|
$this->VMin = $VMin;
|
|
$this->VMax = $VMax;
|
|
$this->Divisions = $Divisions;
|
|
|
|
if (! $VXMin == 0) {
|
|
$this->VXMin = $VXMin;
|
|
$this->VXMax = $VXMax;
|
|
$this->XDivisions = $XDivisions;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrapper to the drawScale() function allowing a second scale to
|
|
* be drawn
|
|
*/
|
|
function drawRightScale(pData $data, ScaleStyle $style, $Angle = 0, $Decimals = 1, $WithMargin = FALSE, $SkipLabels = 1) {
|
|
$this->drawScale($data, $style, $Angle, $Decimals, $WithMargin, $SkipLabels, TRUE );
|
|
}
|
|
|
|
/**
|
|
* Compute and draw the scale
|
|
*/
|
|
function drawScale(pData $Data, ScaleStyle $style, $Angle = 0, $Decimals = 1, $WithMargin = FALSE, $SkipLabels = 1, $RightScale = FALSE) {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateData ( "drawScale", $Data->getData() );
|
|
|
|
$this->canvas->drawLine(new Point($this->GArea_X1, $this->GArea_Y1),
|
|
new Point($this->GArea_X1, $this->GArea_Y2),
|
|
$style->getColor(),
|
|
$style->getLineWidth(),
|
|
$style->getLineDotSize(),
|
|
$this->shadowProperties);
|
|
$this->canvas->drawLine(new Point($this->GArea_X1, $this->GArea_Y2),
|
|
new Point($this->GArea_X2, $this->GArea_Y2),
|
|
$style->getColor(),
|
|
$style->getLineWidth(),
|
|
$style->getLineDotSize(),
|
|
$this->shadowProperties);
|
|
|
|
if ($this->VMin == NULL && $this->VMax == NULL) {
|
|
$Divisions = $this->calculateDivisions($Data, $style);
|
|
} else
|
|
$Divisions = $this->Divisions;
|
|
|
|
$this->DivisionCount = $Divisions;
|
|
|
|
$DataRange = $this->VMax - $this->VMin;
|
|
if ($DataRange == 0) {
|
|
$DataRange = .1;
|
|
}
|
|
|
|
$this->DivisionHeight = ($this->GArea_Y2 - $this->GArea_Y1) / $Divisions;
|
|
$this->DivisionRatio = ($this->GArea_Y2 - $this->GArea_Y1) / $DataRange;
|
|
|
|
$this->GAreaXOffset = 0;
|
|
if (count ( $Data->getData() ) > 1) {
|
|
if ($WithMargin == FALSE)
|
|
$this->DivisionWidth = ($this->GArea_X2 - $this->GArea_X1) / (count ( $Data->getData() ) - 1);
|
|
else {
|
|
$this->DivisionWidth = ($this->GArea_X2 - $this->GArea_X1) / (count ( $Data->getData() ));
|
|
$this->GAreaXOffset = $this->DivisionWidth / 2;
|
|
}
|
|
} else {
|
|
$this->DivisionWidth = $this->GArea_X2 - $this->GArea_X1;
|
|
$this->GAreaXOffset = $this->DivisionWidth / 2;
|
|
}
|
|
|
|
$this->DataCount = count ( $Data->getData() );
|
|
|
|
if ($style->getDrawTicks() == FALSE)
|
|
return (0);
|
|
|
|
$YPos = $this->GArea_Y2;
|
|
$XMin = NULL;
|
|
for($i = 1; $i <= $Divisions + 1; $i ++) {
|
|
if ($RightScale)
|
|
$this->canvas->drawLine(new Point($this->GArea_X2, $YPos),
|
|
new Point($this->GArea_X2 + 5, $YPos),
|
|
$style->getColor(),
|
|
$style->getLineWidth(),
|
|
$style->getLineDotSize(),
|
|
$this->shadowProperties);
|
|
else
|
|
$this->canvas->drawLine(new Point($this->GArea_X1, $YPos),
|
|
new Point($this->GArea_X1 - 5, $YPos),
|
|
$style->getColor(),
|
|
$style->getLineWidth(),
|
|
$style->getLineDotSize(),
|
|
$this->shadowProperties);
|
|
|
|
$Value = $this->VMin + ($i - 1) * (($this->VMax - $this->VMin) / $Divisions);
|
|
$Value = round ( $Value * pow ( 10, $Decimals ) ) / pow ( 10, $Decimals );
|
|
$Value = $this->convertValueForDisplay($Value,
|
|
$Data->getDataDescription()->getYFormat(),
|
|
$Data->getDataDescription()->getYUnit());
|
|
|
|
$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
|
|
$TextWidth = $Position [2] - $Position [0];
|
|
|
|
if ($RightScale) {
|
|
$this->canvas->drawText($this->FontSize, 0,
|
|
new Point($this->GArea_X2 + 10,
|
|
$YPos + ($this->FontSize / 2)),
|
|
$style->getColor(),
|
|
$this->FontName,
|
|
$Value,
|
|
ShadowProperties::NoShadow());
|
|
if ($XMin < $this->GArea_X2 + 15 + $TextWidth || $XMin == NULL) {
|
|
$XMin = $this->GArea_X2 + 15 + $TextWidth;
|
|
}
|
|
} else {
|
|
$this->canvas->drawText($this->FontSize,
|
|
0,
|
|
new Point($this->GArea_X1 - 10 - $TextWidth,
|
|
$YPos + ($this->FontSize / 2)),
|
|
$style->getColor(),
|
|
$this->FontName,
|
|
$Value,
|
|
ShadowProperties::NoShadow());
|
|
if ($XMin > $this->GArea_X1 - 10 - $TextWidth || $XMin == NULL) {
|
|
$XMin = $this->GArea_X1 - 10 - $TextWidth;
|
|
}
|
|
}
|
|
|
|
$YPos = $YPos - $this->DivisionHeight;
|
|
}
|
|
|
|
/* Write the Y Axis caption if set */
|
|
if ($Data->getDataDescription()->getYAxisName() != '') {
|
|
$Position = imageftbbox ( $this->FontSize, 90, $this->FontName, $Data->getDataDescription()->getYAxisName() );
|
|
$TextHeight = abs ( $Position [1] ) + abs ( $Position [3] );
|
|
$TextTop = (($this->GArea_Y2 - $this->GArea_Y1) / 2) + $this->GArea_Y1 + ($TextHeight / 2);
|
|
|
|
if ($RightScale) {
|
|
$this->canvas->drawText($this->FontSize, 90,
|
|
new Point($XMin + $this->FontSize,
|
|
$TextTop),
|
|
$style->getColor(), $this->FontName,
|
|
$Data->getDataDescription()->getYAxisName(),
|
|
ShadowProperties::NoShadow());
|
|
}
|
|
else {
|
|
$this->canvas->drawText($this->FontSize, 90,
|
|
new Point($XMin - $this->FontSize,
|
|
$TextTop),
|
|
$style->getColor(), $this->FontName,
|
|
$Data->getDataDescription()->getYAxisName(),
|
|
ShadowProperties::NoShadow());
|
|
}
|
|
}
|
|
|
|
/* Horizontal Axis */
|
|
$XPos = $this->GArea_X1 + $this->GAreaXOffset;
|
|
$ID = 1;
|
|
$YMax = NULL;
|
|
foreach ( $Data->getData() as $Values ) {
|
|
if ($ID % $SkipLabels == 0) {
|
|
$this->canvas->drawLine(new Point(floor($XPos), $this->GArea_Y2),
|
|
new Point(floor($XPos), $this->GArea_Y2 + 5),
|
|
$style->getColor(),
|
|
$style->getLineWidth(),
|
|
$style->getLineDotSize(),
|
|
$this->shadowProperties);
|
|
$Value = $Values[$Data->getDataDescription()->getPosition()];
|
|
$Value = $this->convertValueForDisplay($Value,
|
|
$Data->getDataDescription()->getXFormat(),
|
|
$Data->getDataDescription()->getXUnit());
|
|
|
|
$Position = imageftbbox ( $this->FontSize, $Angle, $this->FontName, $Value );
|
|
$TextWidth = abs ( $Position [2] ) + abs ( $Position [0] );
|
|
$TextHeight = abs ( $Position [1] ) + abs ( $Position [3] );
|
|
|
|
if ($Angle == 0) {
|
|
$YPos = $this->GArea_Y2 + 18;
|
|
$this->canvas->drawText($this->FontSize,
|
|
$Angle,
|
|
new Point(floor ( $XPos ) - floor ( $TextWidth / 2 ),
|
|
$YPos),
|
|
$style->getColor(),
|
|
$this->FontName,
|
|
$Value,
|
|
ShadowProperties::NoShadow());
|
|
} else {
|
|
$YPos = $this->GArea_Y2 + 10 + $TextHeight;
|
|
if ($Angle <= 90) {
|
|
$this->canvas->drawText($this->FontSize,
|
|
$Angle,
|
|
new Point(floor($XPos) - $TextWidth + 5,
|
|
$YPos),
|
|
$style->getColor(),
|
|
$this->FontName,
|
|
$Value,
|
|
ShadowProperties::NoShadow());
|
|
}
|
|
else {
|
|
$this->canvas->drawText($this->FontSize,
|
|
$Angle,
|
|
new Point(floor ( $XPos ) + $TextWidth + 5,
|
|
$YPos),
|
|
$style->getColor(),
|
|
$this->FontName,
|
|
$Value,
|
|
ShadowProperties::NoShadow());
|
|
}
|
|
}
|
|
if ($YMax < $YPos || $YMax == NULL) {
|
|
$YMax = $YPos;
|
|
}
|
|
}
|
|
|
|
$XPos = $XPos + $this->DivisionWidth;
|
|
$ID ++;
|
|
}
|
|
|
|
/* Write the X Axis caption if set */
|
|
if ($Data->getDataDescription()->getXAxisName() != '') {
|
|
$Position = imageftbbox ( $this->FontSize, 90,
|
|
$this->FontName,
|
|
$Data->getDataDescription()->getXAxisName());
|
|
$TextWidth = abs ( $Position [2] ) + abs ( $Position [0] );
|
|
$TextLeft = (($this->GArea_X2 - $this->GArea_X1) / 2) + $this->GArea_X1 + ($TextWidth / 2);
|
|
$this->canvas->drawText($this->FontSize, 0,
|
|
new Point($TextLeft,
|
|
$YMax + $this->FontSize + 5),
|
|
$style->getColor(), $this->FontName,
|
|
$Data->getDataDescription()->getXAxisName(),
|
|
ShadowProperties::NoShadow());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate the number of divisions that the Y axis will be
|
|
* divided into. This is a function of the range of Y values the
|
|
* data covers, as well as the scale style. Divisions should have
|
|
* some minimum size in screen coordinates in order that the
|
|
* divisions are clearly visible, so this is also a function of
|
|
* the graph size in screen coordinates.
|
|
*
|
|
* This method returns the number of divisions, but it also has
|
|
* side-effects on some class data members. This needs to be
|
|
* refactored to make it clearer what is and isn't affected.
|
|
*/
|
|
private function calculateDivisions(pData $Data, ScaleStyle $style) {
|
|
if (isset ( $Data->getDataDescription()->values[0] )) {
|
|
/* Pointless temporary is necessary because you can't
|
|
* directly apply an array index to the return value
|
|
* of a function in PHP */
|
|
$dataArray = $Data->getData();
|
|
$this->VMin = $dataArray[0] [$Data->getDataDescription()->values[0]];
|
|
$this->VMax = $dataArray[0] [$Data->getDataDescription()->values[0]];
|
|
} else {
|
|
$this->VMin = 2147483647;
|
|
$this->VMax = - 2147483647;
|
|
}
|
|
|
|
/* Compute Min and Max values */
|
|
if ($style->getScaleMode() == SCALE_NORMAL
|
|
|| $style->getScaleMode() == SCALE_START0) {
|
|
if ($style->getScaleMode() == SCALE_START0) {
|
|
$this->VMin = 0;
|
|
}
|
|
|
|
foreach ( $Data->getData() as $Values ) {
|
|
foreach ( $Data->getDataDescription()->values as $ColName ) {
|
|
if (isset ( $Values[$ColName] )) {
|
|
$Value = $Values[$ColName];
|
|
|
|
if (is_numeric ( $Value )) {
|
|
if ($this->VMax < $Value) {
|
|
$this->VMax = $Value;
|
|
}
|
|
if ($this->VMin > $Value) {
|
|
$this->VMin = $Value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} elseif ($style->getScaleMode() == SCALE_ADDALL || $style->getScaleMode() == SCALE_ADDALLSTART0 ) /* Experimental */ {
|
|
if ($style->getScaleMode() == SCALE_ADDALLSTART0) {
|
|
$this->VMin = 0;
|
|
}
|
|
|
|
foreach ( $Data->getData() as $Values ) {
|
|
$Sum = 0;
|
|
foreach ( $Data->getDataDescription()->values as $ColName ) {
|
|
$dataArray = $Data->getData();
|
|
if (isset ( $Values[$ColName] )) {
|
|
$Value = $Values[$ColName];
|
|
if (is_numeric ( $Value ))
|
|
$Sum += $Value;
|
|
}
|
|
}
|
|
if ($this->VMax < $Sum) {
|
|
$this->VMax = $Sum;
|
|
}
|
|
if ($this->VMin > $Sum) {
|
|
$this->VMin = $Sum;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->VMax = ceil($this->VMax);
|
|
|
|
/* If all values are the same */
|
|
if ($this->VMax == $this->VMin) {
|
|
if ($this->VMax >= 0) {
|
|
$this->VMax ++;
|
|
} else {
|
|
$this->VMin --;
|
|
}
|
|
}
|
|
|
|
$DataRange = $this->VMax - $this->VMin;
|
|
if ($DataRange == 0) {
|
|
$DataRange = .1;
|
|
}
|
|
|
|
$this->calculateScales($Scale, $Divisions);
|
|
|
|
if (! isset ( $Divisions ))
|
|
$Divisions = 2;
|
|
|
|
if ($Scale == 1 && $Divisions % 2 == 1)
|
|
$Divisions --;
|
|
|
|
return $Divisions;
|
|
}
|
|
|
|
/**
|
|
* Compute and draw the scale for X/Y charts
|
|
*/
|
|
function drawXYScale(pData $Data, ScaleStyle $style, $YSerieName, $XSerieName, $Angle = 0, $Decimals = 1) {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateData ( "drawScale", $Data->getData());
|
|
|
|
$this->canvas->drawLine(new Point($this->GArea_X1, $this->GArea_Y1),
|
|
new Point($this->GArea_X1, $this->GArea_Y2),
|
|
$style->getColor(),
|
|
$style->getLineWidth(),
|
|
$style->getLineDotSize(),
|
|
$this->shadowProperties);
|
|
$this->canvas->drawLine(new Point($this->GArea_X1, $this->GArea_Y2),
|
|
new Point($this->GArea_X2, $this->GArea_Y2),
|
|
$style->getColor(),
|
|
$style->getLineWidth(),
|
|
$style->getLineDotSize(),
|
|
$this->shadowProperties);
|
|
|
|
/* Process Y scale */
|
|
if ($this->VMin == NULL && $this->VMax == NULL) {
|
|
$this->VMin = $Data->getSeriesMin($YSerieName);
|
|
$this->VMax = $Data->getSeriesMax($YSerieName);
|
|
|
|
/** @todo The use of ceil() here is questionable if all
|
|
* the values are much less than 1, AIUI */
|
|
$this->VMax = ceil($this->VMax);
|
|
|
|
$DataRange = $this->VMax - $this->VMin;
|
|
if ($DataRange == 0) {
|
|
$DataRange = .1;
|
|
}
|
|
|
|
self::computeAutomaticScaling($this->GArea_Y1,
|
|
$this->GArea_Y2,
|
|
$this->VMin,
|
|
$this->VMax,
|
|
$Divisions);
|
|
} else
|
|
$Divisions = $this->Divisions;
|
|
|
|
$this->DivisionCount = $Divisions;
|
|
|
|
$DataRange = $this->VMax - $this->VMin;
|
|
if ($DataRange == 0) {
|
|
$DataRange = .1;
|
|
}
|
|
|
|
$this->DivisionHeight = ($this->GArea_Y2 - $this->GArea_Y1) / $Divisions;
|
|
$this->DivisionRatio = ($this->GArea_Y2 - $this->GArea_Y1) / $DataRange;
|
|
|
|
$YPos = $this->GArea_Y2;
|
|
$XMin = NULL;
|
|
for($i = 1; $i <= $Divisions + 1; $i ++) {
|
|
$this->canvas->drawLine(new Point($this->GArea_X1, $YPos),
|
|
new Point($this->GArea_X1 - 5, $YPos),
|
|
$style->getColor(),
|
|
$style->getLineWidth(),
|
|
$style->getLineDotSize(),
|
|
$this->shadowProperties);
|
|
$Value = $this->VMin + ($i - 1) * (($this->VMax - $this->VMin) / $Divisions);
|
|
$Value = round ( $Value * pow ( 10, $Decimals ) ) / pow ( 10, $Decimals );
|
|
$Value = $this->convertValueForDisplay($Value,
|
|
$Data->getDataDescription()->getYFormat(),
|
|
$Data->getDataDescription()->getYUnit());
|
|
|
|
$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
|
|
$TextWidth = $Position [2] - $Position [0];
|
|
$this->canvas->drawText($this->FontSize,
|
|
0,
|
|
new Point($this->GArea_X1 - 10 - $TextWidth,
|
|
$YPos + ($this->FontSize / 2)),
|
|
$style->getColor(),
|
|
$this->FontName,
|
|
$Value,
|
|
$this->shadowProperties);
|
|
|
|
if ($XMin > $this->GArea_X1 - 10 - $TextWidth || $XMin == NULL) {
|
|
$XMin = $this->GArea_X1 - 10 - $TextWidth;
|
|
}
|
|
|
|
$YPos = $YPos - $this->DivisionHeight;
|
|
}
|
|
|
|
/* Process X scale */
|
|
if ($this->VXMin == NULL && $this->VXMax == NULL) {
|
|
$this->VXMax = $Data->getSeriesMax($XSerieName);
|
|
$this->VXMin = $Data->getSeriesMin($XSerieName);
|
|
|
|
$this->VXMax = ceil($this->VXMax);
|
|
|
|
$DataRange = $this->VMax - $this->VMin;
|
|
if ($DataRange == 0) {
|
|
$DataRange = .1;
|
|
}
|
|
|
|
/* Compute automatic scaling */
|
|
self::computeAutomaticScaling($this->GArea_X1, $this->GArea_X2,
|
|
$this->VXMin, $this->VXMax,
|
|
$XDivisions);
|
|
} else
|
|
$XDivisions = $this->XDivisions;
|
|
|
|
$this->XDivisionCount = $Divisions;
|
|
$this->DataCount = $Divisions + 2;
|
|
|
|
$XDataRange = $this->VXMax - $this->VXMin;
|
|
if ($XDataRange == 0) {
|
|
$XDataRange = .1;
|
|
}
|
|
|
|
$this->DivisionWidth = ($this->GArea_X2 - $this->GArea_X1) / $XDivisions;
|
|
$this->XDivisionRatio = ($this->GArea_X2 - $this->GArea_X1) / $XDataRange;
|
|
|
|
$XPos = $this->GArea_X1;
|
|
$YMax = NULL;
|
|
for($i = 1; $i <= $XDivisions + 1; $i ++) {
|
|
$this->canvas->drawLine(new Point($XPos, $this->GArea_Y2),
|
|
new Point($XPos, $this->GArea_Y2 + 5),
|
|
$style->getColor(),
|
|
$style->getLineWidth(),
|
|
$style->getLineDotSize(),
|
|
$this->shadowProperties);
|
|
|
|
$Value = $this->VXMin + ($i - 1) * (($this->VXMax - $this->VXMin) / $XDivisions);
|
|
$Value = round ( $Value * pow ( 10, $Decimals ) ) / pow ( 10, $Decimals );
|
|
|
|
$Value = $this->convertValueForDisplay($Value,
|
|
$Data->getDataDescription()->getYFormat(),
|
|
$Data->getDataDescription()->getYUnit());
|
|
|
|
$Position = imageftbbox ( $this->FontSize, $Angle, $this->FontName, $Value );
|
|
$TextWidth = abs ( $Position [2] ) + abs ( $Position [0] );
|
|
$TextHeight = abs ( $Position [1] ) + abs ( $Position [3] );
|
|
|
|
if ($Angle == 0) {
|
|
$YPos = $this->GArea_Y2 + 18;
|
|
$this->canvas->drawText($this->FontSize,
|
|
$Angle,
|
|
new Point(floor ( $XPos ) - floor ( $TextWidth / 2 ),
|
|
$YPos),
|
|
$style->getColor(),
|
|
$this->FontName,
|
|
$Value,
|
|
$this->shadowProperties);
|
|
} else {
|
|
$YPos = $this->GArea_Y2 + 10 + $TextHeight;
|
|
if ($Angle <= 90) {
|
|
$this->canvas->drawText($this->FontSize,
|
|
$Angle,
|
|
new Point(floor ( $XPos ) - $TextWidth + 5,
|
|
$YPos),
|
|
$style->getColor(),
|
|
$this->FontName,
|
|
$Value,
|
|
$this->shadowProperties);
|
|
}
|
|
else {
|
|
$this->canvas->drawText($this->FontSize,
|
|
$Angle,
|
|
new Point(floor ( $XPos ) + $TextWidth + 5,
|
|
$YPos),
|
|
$style->getColor(),
|
|
$this->FontName,
|
|
$Value,
|
|
$this->shadowProperties);
|
|
}
|
|
}
|
|
|
|
if ($YMax < $YPos || $YMax == NULL) {
|
|
$YMax = $YPos;
|
|
}
|
|
|
|
$XPos = $XPos + $this->DivisionWidth;
|
|
}
|
|
|
|
/* Write the Y Axis caption if set */
|
|
if ($Data->getDataDescription()->getYAxisName() != '') {
|
|
$Position = imageftbbox ( $this->FontSize, 90, $this->FontName,
|
|
$Data->getDataDescription()->getYAxisName());
|
|
$TextHeight = abs ( $Position [1] ) + abs ( $Position [3] );
|
|
$TextTop = (($this->GArea_Y2 - $this->GArea_Y1) / 2) + $this->GArea_Y1 + ($TextHeight / 2);
|
|
$this->canvas->drawText($this->FontSize,
|
|
90,
|
|
new Point($XMin - $this->FontSize,
|
|
$TextTop),
|
|
$style->getColor(),
|
|
$this->FontName,
|
|
$Data->getDataDescription()->getYAxisName(),
|
|
$this->shadowProperties);
|
|
}
|
|
|
|
/* Write the X Axis caption if set */
|
|
$this->writeScaleXAxisCaption($Data, $style, $YMax);
|
|
}
|
|
|
|
private function drawGridMosaic(GridStyle $style, $divisionCount, $divisionHeight) {
|
|
$LayerHeight = $this->GArea_Y2 - $this->GArea_Y1;
|
|
|
|
$YPos = $LayerHeight - 1; //$this->GArea_Y2-1;
|
|
$LastY = $YPos;
|
|
|
|
for($i = 0; $i < $divisionCount; $i ++) {
|
|
$LastY = $YPos;
|
|
$YPos = $YPos - $divisionHeight;
|
|
|
|
if ($YPos <= 0) {
|
|
$YPos = 1;
|
|
}
|
|
|
|
if ($i % 2 == 0) {
|
|
$this->canvas->drawFilledRectangle(new Point($this->GArea_X1 + 1,
|
|
$this->GArea_Y1 + $YPos),
|
|
new Point($this->GArea_X2 - 1,
|
|
$this->GArea_Y1 + $LastY),
|
|
new Color(250, 250, 250),
|
|
ShadowProperties::NoShadow(),
|
|
false,
|
|
$style->getAlpha());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write the X Axis caption on the scale, if set
|
|
*/
|
|
private function writeScaleXAxisCaption(pData $data, ScaleStyle $style, $YMax) {
|
|
if ($data->getDataDescription()->getXAxisName() != '') {
|
|
$Position = imageftbbox ( $this->FontSize, 90, $this->FontName, $data->getDataDescription()->getXAxisName());
|
|
$TextWidth = abs ( $Position [2] ) + abs ( $Position [0] );
|
|
$TextLeft = (($this->GArea_X2 - $this->GArea_X1) / 2) + $this->GArea_X1 + ($TextWidth / 2);
|
|
$this->canvas->drawText($this->FontSize,
|
|
0,
|
|
new Point($TextLeft,
|
|
$YMax + $this->FontSize + 5),
|
|
$style->getColor(),
|
|
$this->FontName,
|
|
$data->getDataDescription()->getXAxisName(),
|
|
$this->shadowProperties);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Compute and draw the scale
|
|
*/
|
|
function drawGrid(GridStyle $style) {
|
|
/* Draw mosaic */
|
|
if ($style->getMosaic()) {
|
|
$this->drawGridMosaic($style, $this->DivisionCount, $this->DivisionHeight);
|
|
}
|
|
|
|
/* Horizontal lines */
|
|
$YPos = $this->GArea_Y2 - $this->DivisionHeight;
|
|
for($i = 1; $i <= $this->DivisionCount; $i ++) {
|
|
if ($YPos > $this->GArea_Y1 && $YPos < $this->GArea_Y2)
|
|
$this->canvas->drawDottedLine(new Point($this->GArea_X1, $YPos),
|
|
new Point($this->GArea_X2, $YPos),
|
|
$style->getLineWidth(),
|
|
$this->LineWidth,
|
|
$style->getColor(),
|
|
ShadowProperties::NoShadow());
|
|
/** @todo There's some inconsistency here. The parameter
|
|
* $lineWidth appears to be used to control the dot size,
|
|
* not the line width? This is the same way it's always
|
|
* been done, although now it's more obvious that there's
|
|
* a problem. */
|
|
|
|
$YPos = $YPos - $this->DivisionHeight;
|
|
}
|
|
|
|
/* Vertical lines */
|
|
if ($this->GAreaXOffset == 0) {
|
|
$XPos = $this->GArea_X1 + $this->DivisionWidth + $this->GAreaXOffset;
|
|
$ColCount = $this->DataCount - 2;
|
|
} else {
|
|
$XPos = $this->GArea_X1 + $this->GAreaXOffset;
|
|
$ColCount = floor ( ($this->GArea_X2 - $this->GArea_X1) / $this->DivisionWidth );
|
|
}
|
|
|
|
for($i = 1; $i <= $ColCount; $i ++) {
|
|
if ($XPos > $this->GArea_X1 && $XPos < $this->GArea_X2)
|
|
$this->canvas->drawDottedLine(new Point(floor($XPos), $this->GArea_Y1),
|
|
new Point(floor($XPos), $this->GArea_Y2),
|
|
$style->getLineWidth(),
|
|
$this->LineWidth,
|
|
$style->getcolor(),
|
|
$this->shadowProperties);
|
|
$XPos = $XPos + $this->DivisionWidth;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* retrieve the legends size
|
|
*/
|
|
public function getLegendBoxSize($DataDescription) {
|
|
if (! isset ( $DataDescription->description))
|
|
return (- 1);
|
|
|
|
/* <-10->[8]<-4->Text<-10-> */
|
|
$MaxWidth = 0;
|
|
$MaxHeight = 8;
|
|
foreach ( $DataDescription->description as $Value ) {
|
|
$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
|
|
$TextWidth = $Position [2] - $Position [0];
|
|
$TextHeight = $Position [1] - $Position [7];
|
|
if ($TextWidth > $MaxWidth) {
|
|
$MaxWidth = $TextWidth;
|
|
}
|
|
$MaxHeight = $MaxHeight + $TextHeight + 4;
|
|
}
|
|
$MaxHeight = $MaxHeight - 3;
|
|
$MaxWidth = $MaxWidth + 32;
|
|
|
|
return (array ($MaxWidth, $MaxHeight ));
|
|
}
|
|
|
|
/**
|
|
* Draw the data legends
|
|
*/
|
|
public function drawLegend($XPos, $YPos, $DataDescription, Color $color, Color $color2 = null, Color $color3 = null, $Border = TRUE) {
|
|
if ($color2 == null) {
|
|
$color2 = $color->addRGBIncrement(-30);
|
|
}
|
|
|
|
if ($color3 == null) {
|
|
$color3 = new Color(0, 0, 0);
|
|
}
|
|
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription("drawLegend", $DataDescription);
|
|
|
|
if (! isset ( $DataDescription->description))
|
|
return (- 1);
|
|
|
|
/* <-10->[8]<-4->Text<-10-> */
|
|
$MaxWidth = 0;
|
|
$MaxHeight = 8;
|
|
foreach ( $DataDescription->description as $Key => $Value ) {
|
|
$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
|
|
$TextWidth = $Position [2] - $Position [0];
|
|
$TextHeight = $Position [1] - $Position [7];
|
|
if ($TextWidth > $MaxWidth) {
|
|
$MaxWidth = $TextWidth;
|
|
}
|
|
$MaxHeight = $MaxHeight + $TextHeight + 4;
|
|
}
|
|
$MaxHeight = $MaxHeight - 5;
|
|
$MaxWidth = $MaxWidth + 32;
|
|
|
|
if ($Border) {
|
|
$this->canvas->drawFilledRoundedRectangle(new Point($XPos + 1, $YPos + 1),
|
|
new Point($XPos + $MaxWidth + 1,
|
|
$YPos + $MaxHeight + 1),
|
|
5, $color2,
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
|
|
$this->canvas->drawFilledRoundedRectangle(new Point($XPos, $YPos),
|
|
new Point($XPos + $MaxWidth,
|
|
$YPos + $MaxHeight),
|
|
5, $color,
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
}
|
|
|
|
$YOffset = 4 + $this->FontSize;
|
|
$ID = 0;
|
|
foreach ( $DataDescription->description as $Key => $Value ) {
|
|
$this->canvas->drawFilledRoundedRectangle(new Point($XPos + 10,
|
|
$YPos + $YOffset - 4),
|
|
new Point($XPos + 14,
|
|
$YPos + $YOffset - 4),
|
|
2,
|
|
$this->palette->colors[$ID],
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
$this->canvas->drawText($this->FontSize,
|
|
0,
|
|
new Point($XPos + 22,
|
|
$YPos + $YOffset),
|
|
$color3,
|
|
$this->FontName,
|
|
$Value,
|
|
$this->shadowProperties);
|
|
|
|
$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
|
|
$TextHeight = $Position [1] - $Position [7];
|
|
|
|
$YOffset = $YOffset + $TextHeight + 4;
|
|
$ID ++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draw the graph title
|
|
*
|
|
* @todo Should we pass in a ShadowProperties object here? Or is
|
|
* this a public function?
|
|
*/
|
|
public function drawTitle($XPos, $YPos, $Value, Color $color, $XPos2 = -1, $YPos2 = -1, ShadowProperties $shadowProperties = null) {
|
|
if ($shadowProperties == null) {
|
|
$shadowProperties = ShadowProperties::NoShadow();
|
|
}
|
|
|
|
if ($XPos2 != - 1) {
|
|
$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
|
|
$TextWidth = $Position [2] - $Position [0];
|
|
$XPos = floor ( ($XPos2 - $XPos - $TextWidth) / 2 ) + $XPos;
|
|
}
|
|
|
|
if ($YPos2 != - 1) {
|
|
$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Value );
|
|
$TextHeight = $Position [5] - $Position [3];
|
|
$YPos = floor ( ($YPos2 - $YPos - $TextHeight) / 2 ) + $YPos;
|
|
}
|
|
|
|
$this->canvas->drawText($this->FontSize,
|
|
0,
|
|
new Point($XPos, $YPos),
|
|
$color,
|
|
$this->FontName,
|
|
$Value,
|
|
$shadowProperties);
|
|
}
|
|
|
|
/**
|
|
* Draw a text box with text align & alpha properties
|
|
*
|
|
* @param $point1 Minimum corner of the box
|
|
* @param $point2 Maximum corner of the box
|
|
*
|
|
* @todo This should probably be a method on the ICanvas
|
|
* interface, since it doesn't have anything specifically to do
|
|
* with graphs
|
|
*/
|
|
public function drawTextBox(Point $point1, Point $point2, $Text, $Angle = 0, Color $color = null, $Align = ALIGN_LEFT, ShadowProperties $shadowProperties = null, Color $backgroundColor = null, $Alpha = 100) {
|
|
if ($color == null) {
|
|
$color = new Color(255, 255, 255);
|
|
}
|
|
|
|
if ($shadowProperties == null) {
|
|
$shadowProperties = ShadowProperties::NoShadow();
|
|
}
|
|
|
|
$Position = imageftbbox ( $this->FontSize, $Angle, $this->FontName, $Text );
|
|
$TextWidth = $Position [2] - $Position [0];
|
|
$TextHeight = $Position [5] - $Position [3];
|
|
$AreaWidth = $point2->getX() - $point1->getX();
|
|
$AreaHeight = $point2->getY() - $point1->getY();
|
|
|
|
if ($backgroundColor != null)
|
|
$this->canvas->drawFilledRectangle($point1,
|
|
$point2,
|
|
$backgroundColor,
|
|
$shadowProperties, FALSE, $Alpha );
|
|
|
|
if ($Align == ALIGN_TOP_LEFT) {
|
|
$newPosition = $point1->addIncrement(1, $this->FontSize + 1);
|
|
}
|
|
if ($Align == ALIGN_TOP_CENTER) {
|
|
$newPosition = $point1->addIncrement(($AreaWidth / 2) - ($TextWidth / 2),
|
|
$this->FontSize + 1);
|
|
}
|
|
if ($Align == ALIGN_TOP_RIGHT) {
|
|
$newPosition = new Point($point2->getX() - $TextWidth - 1,
|
|
$point1->getY() + $this->FontSize + 1);
|
|
}
|
|
if ($Align == ALIGN_LEFT) {
|
|
$newPosition = $point1->addIncrement(1,
|
|
($AreaHeight / 2) - ($TextHeight / 2));
|
|
}
|
|
if ($Align == ALIGN_CENTER) {
|
|
$newPosition = $point1->addIncrement(($AreaWidth / 2) - ($TextWidth / 2),
|
|
($AreaHeight / 2) - ($TextHeight / 2));
|
|
}
|
|
if ($Align == ALIGN_RIGHT) {
|
|
$newPosition = new Point($point2->getX() - $TextWidth - 1,
|
|
$point1->getY() + ($AreaHeight / 2) - ($TextHeight / 2));
|
|
}
|
|
if ($Align == ALIGN_BOTTOM_LEFT) {
|
|
$newPosition = new Point($point1->getX() + 1,
|
|
$point2->getY() - 1);
|
|
}
|
|
if ($Align == ALIGN_BOTTOM_CENTER) {
|
|
$newPosition = new Point($point1->getX() + ($AreaWidth / 2) - ($TextWidth / 2),
|
|
$point2->getY() - 1);
|
|
}
|
|
if ($Align == ALIGN_BOTTOM_RIGHT) {
|
|
$newPosition = $point2->addIncrement(- $TextWidth - 1,
|
|
-1);
|
|
}
|
|
|
|
$this->canvas->drawText($this->FontSize, $Angle, $newPosition, $color, $this->FontName, $Text, $shadowProperties);
|
|
}
|
|
|
|
/**
|
|
* Compute and draw the scale
|
|
*
|
|
* @todo What is the method name a typo for? Threshold?
|
|
*/
|
|
function drawTreshold($Value, Color $color, $ShowLabel = FALSE, $ShowOnRight = FALSE, $TickWidth = 4, $FreeText = NULL) {
|
|
$Y = $this->GArea_Y2 - ($Value - $this->VMin) * $this->DivisionRatio;
|
|
|
|
if ($Y <= $this->GArea_Y1 || $Y >= $this->GArea_Y2)
|
|
return (- 1);
|
|
|
|
if ($TickWidth == 0)
|
|
$this->canvas->drawLine(new Point($this->GArea_X1, $Y),
|
|
new Point($this->GArea_X2, $Y),
|
|
$color,
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
else
|
|
$this->canvas->drawDottedLine(new Point($this->GArea_X1, $Y),
|
|
new Point($this->GArea_X2, $Y),
|
|
$TickWidth,
|
|
$this->LineWidth,
|
|
$color,
|
|
$this->shadowProperties);
|
|
|
|
if ($ShowLabel) {
|
|
if ($FreeText == NULL) {
|
|
$Label = $Value;
|
|
} else {
|
|
$Label = $FreeText;
|
|
}
|
|
|
|
if ($ShowOnRight) {
|
|
$position = new Point($this->GArea_X2 + 2,
|
|
$Y + ($this->FontSize/ 2));
|
|
}
|
|
else {
|
|
$position = new Point($this->GArea_X1 + 2,
|
|
$Y - ($this->FontSize / 2));
|
|
}
|
|
|
|
$this->canvas->drawText($this->FontSize, 0,
|
|
$position,
|
|
$color,
|
|
$this->FontName,
|
|
$Label,
|
|
ShadowProperties::NoShadow());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function put a label on a specific point
|
|
*/
|
|
function setLabel($Data, $DataDescription, $SerieName, $ValueName, $Caption, Color $color = null) {
|
|
if ($color == null) {
|
|
$color = new Color(210, 210, 210);
|
|
}
|
|
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription ( "setLabel", $DataDescription );
|
|
$this->validateData ( "setLabel", $Data );
|
|
$ShadowFactor = 100;
|
|
|
|
$Cp = 0;
|
|
$Found = FALSE;
|
|
foreach ( $Data as $Value ) {
|
|
if ($Value[$DataDescription->getPosition()] == $ValueName) {
|
|
$NumericalValue = $Value[$SerieName];
|
|
$Found = TRUE;
|
|
}
|
|
if (! $Found)
|
|
$Cp ++;
|
|
}
|
|
|
|
$XPos = $this->GArea_X1 + $this->GAreaXOffset + ($this->DivisionWidth * $Cp) + 2;
|
|
$YPos = $this->GArea_Y2 - ($NumericalValue - $this->VMin) * $this->DivisionRatio;
|
|
|
|
$Position = imageftbbox ( $this->FontSize, 0, $this->FontName, $Caption );
|
|
$TextHeight = $Position [3] - $Position [5];
|
|
$TextWidth = $Position [2] - $Position [0] + 2;
|
|
$TextOffset = floor ( $TextHeight / 2 );
|
|
|
|
// Shadow
|
|
$Poly = array ($XPos + 1, $YPos + 1, $XPos + 9, $YPos - $TextOffset, $XPos + 8, $YPos + $TextOffset + 2 );
|
|
|
|
$this->canvas->drawFilledPolygon($Poly,
|
|
3,
|
|
$color->addRGBIncrement(-$ShadowFactor));
|
|
|
|
$this->canvas->drawLine(new Point($XPos, $YPos + 1),
|
|
new Point($XPos + 9, $YPos - $TextOffset - .2),
|
|
$color->addRGBIncrement(-$ShadowFactor),
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
|
|
$this->canvas->drawLine(new Point($XPos, $YPos + 1),
|
|
new Point($XPos + 9, $YPos + $TextOffset + 2.2),
|
|
$color->addRGBIncrement(-$ShadowFactor),
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
|
|
$this->canvas->drawFilledRectangle(new Point($XPos + 9,
|
|
$YPos - $TextOffset - .2),
|
|
new Point($XPos + 13 + $TextWidth,
|
|
$YPos + $TextOffset + 2.2),
|
|
$color->addRGBIncrement(-$ShadowFactor),
|
|
$this->shadowProperties);
|
|
|
|
// Label background
|
|
$Poly = array ($XPos, $YPos, $XPos + 8, $YPos - $TextOffset - 1, $XPos + 8, $YPos + $TextOffset + 1 );
|
|
|
|
$this->canvas->drawFilledPolygon($Poly, 3, $color);
|
|
|
|
/** @todo We draw exactly the same line twice, with the same settings.
|
|
* Surely this is pointless? */
|
|
$this->canvas->drawLine(new Point($XPos - 1, $YPos),
|
|
new Point($XPos + 8, $YPos - $TextOffset - 1.2),
|
|
$color,
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
|
|
$this->canvas->drawLine(new Point($XPos - 1, $YPos),
|
|
new Point($XPos + 8, $YPos + $TextOffset + 1.2),
|
|
$color,
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
$this->canvas->drawFilledRectangle(new Point($XPos + 8,
|
|
$YPos - $TextOffset - 1.2),
|
|
new Point($XPos + 12 + $TextWidth,
|
|
$YPos + $TextOffset + 1.2),
|
|
$color,
|
|
$this->shadowProperties);
|
|
|
|
$this->canvas->drawText($this->FontSize,
|
|
0,
|
|
new Point($XPos + 10, $YPos + $TextOffset),
|
|
new Color(0, 0, 0),
|
|
$this->FontName,
|
|
$Caption,
|
|
ShadowProperties::NoShadow());
|
|
}
|
|
|
|
/**
|
|
* This function draw a plot graph
|
|
*/
|
|
function drawPlotGraph($Data, $DataDescription, $BigRadius = 5, $SmallRadius = 2, Color $color2 = null, $Shadow = FALSE) {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription ( "drawPlotGraph", $DataDescription );
|
|
$this->validateData ( "drawPlotGraph", $Data );
|
|
|
|
$GraphID = 0;
|
|
$colorO = $color2;
|
|
|
|
foreach ( $DataDescription->values as $ColName ) {
|
|
$ColorID = $DataDescription->getColumnIndex($ColName);
|
|
|
|
$color = $this->palette->colors[$ColorID];
|
|
$color2 = $colorO;
|
|
|
|
if (isset ( $DataDescription->seriesSymbols[$ColName] )) {
|
|
$Infos = getimagesize ( $DataDescription->seriesSymbols[$ColName] );
|
|
$ImageWidth = $Infos [0];
|
|
$ImageHeight = $Infos [1];
|
|
$Symbol = imagecreatefromgif ( $DataDescription->seriesSymbols[$ColName] );
|
|
}
|
|
|
|
$XPos = $this->GArea_X1 + $this->GAreaXOffset;
|
|
$Hsize = round ( $BigRadius / 2 );
|
|
|
|
$color3 = null;
|
|
foreach ( $Data as $Values ) {
|
|
$Value = $Values[$ColName];
|
|
$YPos = $this->GArea_Y2 - (($Value - $this->VMin) * $this->DivisionRatio);
|
|
|
|
/* Save point into the image map if option activated */
|
|
if ($this->BuildMap)
|
|
$this->addToImageMap ( $XPos - $Hsize, $YPos - $Hsize, $XPos + 1 + $Hsize, $YPos + $Hsize + 1, $DataDescription->description[$ColName], $Values[$ColName] . $DataDescription->getYUnit(), "Plot" );
|
|
|
|
if (is_numeric ( $Value )) {
|
|
if (! isset ( $DataDescription->seriesSymbols[$ColName] )) {
|
|
|
|
if ($Shadow) {
|
|
if ($color3 != null) {
|
|
$this->canvas->drawFilledCircle(new Point($XPos + 2,
|
|
$YPos + 2),
|
|
$BigRadius,
|
|
$color3,
|
|
$this->shadowProperties);
|
|
}
|
|
else {
|
|
$color3 = $this->palette->colors[$ColorID]->addRGBIncrement(-20);
|
|
|
|
$this->canvas->drawFilledCircle(new Point($XPos + 2,
|
|
$YPos + 2),
|
|
$BigRadius,
|
|
$color3,
|
|
$this->shadowProperties);
|
|
}
|
|
}
|
|
|
|
$this->canvas->drawFilledCircle(new Point($XPos + 1,
|
|
$YPos + 1),
|
|
$BigRadius,
|
|
$color,
|
|
$this->shadowProperties);
|
|
|
|
if ($SmallRadius != 0) {
|
|
if ($color2 != null) {
|
|
$this->canvas->drawFilledCircle(new Point($XPos + 1,
|
|
$YPos + 1),
|
|
$SmallRadius,
|
|
$color2,
|
|
$this->shadowProperties);
|
|
}
|
|
else {
|
|
$color2 = $this->palette->colors[$ColorID]->addRGBIncrement(-15);
|
|
|
|
$this->canvas->drawFilledCircle(new Point($XPos + 1,
|
|
$YPos + 1),
|
|
$SmallRadius,
|
|
$color2,
|
|
$this->shadowProperties);
|
|
}
|
|
}
|
|
} else {
|
|
imagecopymerge ( $this->canvas->getPicture(), $Symbol, $XPos + 1 - $ImageWidth / 2, $YPos + 1 - $ImageHeight / 2, 0, 0, $ImageWidth, $ImageHeight, 100 );
|
|
}
|
|
}
|
|
|
|
$XPos = $XPos + $this->DivisionWidth;
|
|
}
|
|
$GraphID ++;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Linearly Scale a given value
|
|
*
|
|
* using it's own minima/maxima and the desired output minima/maxima
|
|
*/
|
|
function linearScale($value, $istart, $istop, $ostart, $ostop){
|
|
$div = ($istop - $istart);
|
|
if($div == 0.0) $div = 1;
|
|
return $ostart + ($ostop - $ostart) * (($value - $istart) / $div);
|
|
}
|
|
|
|
/**
|
|
* @brief This function draw a plot graph in an X/Y space
|
|
*/
|
|
function drawXYPlotGraph(pData $DataSet, $YSerieName, $XSerieName, $PaletteID = 0, $BigRadius = 5, $SmallRadius = 2, Color $color2 = null, $Shadow = TRUE, $SizeSerieName = '') {
|
|
$color = $this->palette->colors[$PaletteID];
|
|
|
|
$color3 = null;
|
|
|
|
$Data = $DataSet->getData();
|
|
foreach ( $Data as $Values ) {
|
|
if (isset($Values[$YSerieName]) && isset ($Values[$XSerieName])) {
|
|
$X = $Values[$XSerieName];
|
|
$Y = $Values[$YSerieName];
|
|
|
|
$Y = $this->GArea_Y2 - (($Y - $this->VMin) * $this->DivisionRatio);
|
|
$X = $this->GArea_X1 + (($X - $this->VXMin) * $this->XDivisionRatio);
|
|
|
|
if(isset($Values[$SizeSerieName])){
|
|
$br = $this->linearScale(
|
|
$Values[$SizeSerieName],
|
|
$DataSet->getSeriesMin($SizeSerieName),
|
|
$DataSet->getSeriesMax($SizeSerieName),
|
|
$SmallRadius,
|
|
$BigRadius
|
|
);
|
|
$sr = $br;
|
|
}else{
|
|
$br = $BigRadius;
|
|
$sr = $SmallRadius;
|
|
}
|
|
|
|
if ($Shadow) {
|
|
if ($color3 != null) {
|
|
$this->canvas->drawFilledCircle(new Point($X + 2, $Y + 2),
|
|
$br,
|
|
$color3,
|
|
$this->shadowProperties);
|
|
}
|
|
else {
|
|
$color3 = $this->palette->colors[$PaletteID]->addRGBIncrement(-20);
|
|
$this->canvas->drawFilledCircle(new Point($X + 2, $Y + 2),
|
|
$br,
|
|
$color3,
|
|
$this->shadowProperties);
|
|
}
|
|
}
|
|
|
|
$this->canvas->drawFilledCircle(new Point($X + 1, $Y + 1),
|
|
$br,
|
|
$color,
|
|
$this->shadowProperties);
|
|
|
|
if ($color2 != null) {
|
|
$this->canvas->drawFilledCircle(new Point($X + 1, $Y + 1),
|
|
$sr,
|
|
$color2,
|
|
$this->shadowProperties);
|
|
}
|
|
else {
|
|
$color2 = $this->palette->colors[$PaletteID]->addRGBIncrement(20);
|
|
|
|
$this->canvas->drawFilledCircle(new Point($X + 1, $Y + 1),
|
|
$sr,
|
|
$color2,
|
|
$this->shadowProperties);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* This function draw an area between two series
|
|
*/
|
|
function drawArea($Data, $Serie1, $Serie2, Color $color, $Alpha = 50) {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateData ( "drawArea", $Data );
|
|
|
|
$LayerHeight = $this->GArea_Y2 - $this->GArea_Y1;
|
|
|
|
$XPos = $this->GAreaXOffset;
|
|
$LastXPos = - 1;
|
|
foreach ( $Data as $Values ) {
|
|
$Value1 = $Values[$Serie1];
|
|
$Value2 = $Values[$Serie2];
|
|
$YPos1 = $LayerHeight - (($Value1 - $this->VMin) * $this->DivisionRatio);
|
|
$YPos2 = $LayerHeight - (($Value2 - $this->VMin) * $this->DivisionRatio);
|
|
|
|
if ($LastXPos != - 1) {
|
|
$Points = array();
|
|
$Points [] = $LastXPos + $this->GArea_X1;
|
|
$Points [] = $LastYPos1 + $this->GArea_Y1;
|
|
$Points [] = $LastXPos + $this->GArea_X1;
|
|
$Points [] = $LastYPos2 + $this->GArea_Y1;
|
|
$Points [] = $XPos + $this->GArea_X1;
|
|
$Points [] = $YPos2 + $this->GArea_Y1;
|
|
$Points [] = $XPos + $this->GArea_X1;
|
|
$Points [] = $YPos1 + $this->GArea_Y1;
|
|
|
|
$this->canvas->drawFilledPolygon($Points,
|
|
4,
|
|
$color,
|
|
$Alpha);
|
|
}
|
|
|
|
$LastYPos1 = $YPos1;
|
|
$LastYPos2 = $YPos2;
|
|
$LastXPos = $XPos;
|
|
|
|
$XPos = $XPos + $this->DivisionWidth;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function write the values of the specified series
|
|
*/
|
|
function writeValues($Data, $DataDescription, $Series) {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription ( "writeValues", $DataDescription );
|
|
$this->validateData ( "writeValues", $Data );
|
|
|
|
if (! is_array ( $Series )) {
|
|
$Series = array ($Series );
|
|
}
|
|
|
|
foreach ( $Series as $Serie ) {
|
|
$ColorID = $DataDescription->getColumnIndex($Serie);
|
|
|
|
$XPos = $this->GArea_X1 + $this->GAreaXOffset;
|
|
|
|
foreach ( $Data as $Values ) {
|
|
if (isset ( $Values[$Serie] ) && is_numeric ( $Values[$Serie] )) {
|
|
$Value = $Values[$Serie];
|
|
$YPos = $this->GArea_Y2 - (($Value - $this->VMin) * $this->DivisionRatio);
|
|
|
|
$Positions = imagettfbbox ( $this->FontSize, 0, $this->FontName, $Value );
|
|
$Width = $Positions [2] - $Positions [6];
|
|
$XOffset = $XPos - ($Width / 2);
|
|
$YOffset = $YPos - 4;
|
|
|
|
$this->canvas->drawText($this->FontSize,
|
|
0,
|
|
new Point($XOffset, $YOffset),
|
|
$this->palette->colors[$ColorID],
|
|
$this->FontName,
|
|
$Value,
|
|
ShadowProperties::NoShadow());
|
|
}
|
|
$XPos = $XPos + $this->DivisionWidth;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Draws a line graph where the data gives Y values for a
|
|
* series of regular positions along the X axis
|
|
*/
|
|
function drawLineGraph($Data, $DataDescription, $SerieName = "") {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription ( "drawLineGraph", $DataDescription );
|
|
$this->validateData ( "drawLineGraph", $Data );
|
|
|
|
$GraphID = 0;
|
|
foreach ( $DataDescription->values as $ColName ) {
|
|
$ColorID = $DataDescription->getColumnIndex($ColName);
|
|
|
|
if ($SerieName == "" || $SerieName == $ColName) {
|
|
$XPos = $this->GArea_X1 + $this->GAreaXOffset;
|
|
$XLast = - 1;
|
|
foreach ( $Data as $Values ) {
|
|
if (isset ( $Values[$ColName] )) {
|
|
$Value = $Values[$ColName];
|
|
$YPos = $this->GArea_Y2 - (($Value - $this->VMin) * $this->DivisionRatio);
|
|
|
|
/* Save point into the image map if option activated */
|
|
if ($this->BuildMap)
|
|
$this->addToImageMap ( $XPos - 3, $YPos - 3, $XPos + 3, $YPos + 3, $DataDescription->description[$ColName], $Values[$ColName] . $DataDescription->getYUnit(), "Line" );
|
|
|
|
if (! is_numeric ( $Value )) {
|
|
$XLast = - 1;
|
|
}
|
|
if ($XLast != - 1)
|
|
$this->canvas->drawLine(new Point($XLast, $YLast),
|
|
new Point($XPos, $YPos),
|
|
$this->palette->colors[$ColorID],
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties,
|
|
new Point($this->GArea_X1,
|
|
$this->GArea_Y1),
|
|
new Point($this->GArea_X2,
|
|
$this->GArea_Y2));
|
|
|
|
$XLast = $XPos;
|
|
$YLast = $YPos;
|
|
if (! is_numeric ( $Value )) {
|
|
$XLast = - 1;
|
|
}
|
|
}
|
|
$XPos = $XPos + $this->DivisionWidth;
|
|
}
|
|
$GraphID ++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Draws a line graph where one series of data defines the
|
|
* X position and another the Y position
|
|
*/
|
|
function drawXYGraph($Data, $YSerieName, $XSerieName, $PaletteID = 0) {
|
|
$graphAreaMin = new Point($this->GArea_X1, $this->GArea_Y1);
|
|
$graphAreaMax = new Point($this->GArea_X2, $this->GArea_Y2);
|
|
|
|
$lastPoint = null;
|
|
|
|
foreach ($Data as $Values) {
|
|
if (isset ( $Values[$YSerieName] ) && isset ( $Values[$XSerieName] )) {
|
|
$X = $Values[$XSerieName];
|
|
$Y = $Values[$YSerieName];
|
|
|
|
$Y = $this->GArea_Y2 - (($Y - $this->VMin) * $this->DivisionRatio);
|
|
$X = $this->GArea_X1 + (($X - $this->VXMin) * $this->XDivisionRatio);
|
|
|
|
$currentPoint = new Point($X, $Y);
|
|
|
|
if ($lastPoint != null) {
|
|
$this->canvas->drawLine($lastPoint,
|
|
$currentPoint,
|
|
$this->palette->colors[$PaletteID],
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties,
|
|
$graphAreaMin,
|
|
$graphAreaMax);
|
|
}
|
|
|
|
$lastPoint = $currentPoint;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function draw a cubic curve
|
|
*/
|
|
function drawCubicCurve(pData $data, $Accuracy = .1, $SerieName = "") {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription("drawCubicCurve",
|
|
$data->getDataDescription());
|
|
$this->validateData ( "drawCubicCurve", $data->getData() );
|
|
|
|
$graphAreaMin = new Point($this->GArea_X1, $this->GArea_Y1);
|
|
$graphAreaMax = new Point($this->GArea_X2, $this->GArea_Y2);
|
|
|
|
$GraphID = 0;
|
|
foreach ( $data->getDataDescription()->values as $ColName ) {
|
|
if ($SerieName == "" || $SerieName == $ColName) {
|
|
/** @todo The next section of code has been duplicated by
|
|
* copy & paste */
|
|
$XIn = array();
|
|
$YIn = array();
|
|
$Yt = "";
|
|
$U = "";
|
|
|
|
$ColorID = $data->getDataDescription()->getColumnIndex($ColName);
|
|
|
|
$Index = 1;
|
|
$XLast = - 1;
|
|
$Missing = array();
|
|
|
|
$data->getXYMap($ColName, $XIn, $YIn, $Missing, $Index);
|
|
|
|
assert(count($XIn) == count($YIn));
|
|
assert($Index + 1 >= count($XIn));
|
|
|
|
$Yt [0] = 0;
|
|
$Yt [1] = 0;
|
|
$U [1] = 0;
|
|
|
|
$this->calculateCubicCurve($Yt, $XIn, $YIn, $U, $Index);
|
|
|
|
$Yt [$Index] = 0;
|
|
|
|
for($k = $Index - 1; $k >= 1; $k --)
|
|
$Yt [$k] = $Yt [$k] * $Yt [$k + 1] + $U [$k];
|
|
|
|
$XPos = $this->GArea_X1 + $this->GAreaXOffset;
|
|
for($X = 1; $X <= $Index; $X = $X + $Accuracy) {
|
|
|
|
/* I believe here we're searching for the integral
|
|
* value k such that $X lies between $XIn[k] and
|
|
* $XIn[k-1] */
|
|
$klo = 1;
|
|
$khi = $Index;
|
|
$k = $khi - $klo;
|
|
while ( $k > 1 ) {
|
|
$k = $khi - $klo;
|
|
If ($XIn [$k] >= $X)
|
|
$khi = $k;
|
|
else
|
|
$klo = $k;
|
|
}
|
|
$klo = $khi - 1;
|
|
|
|
/* These assertions are to check my understanding
|
|
* of the code. If they fail, it is my fault and
|
|
* not a bug */
|
|
assert($khi = $klo + 1);
|
|
assert($XIn[$klo] < $X);
|
|
assert($X <= $XIn[$khi]);
|
|
|
|
$h = $XIn [$khi] - $XIn [$klo];
|
|
$a = ($XIn [$khi] - $X) / $h;
|
|
$b = ($X - $XIn [$klo]) / $h;
|
|
|
|
/**
|
|
* I believe this is the actual cubic Bezier
|
|
* calculation. In parametric form:
|
|
*
|
|
* B(t) = (1-t)^3 * B0
|
|
* + 3 (1-t)^2 * t * B1
|
|
* + 3 (1-t) * t^2 * B2
|
|
* + t^3 B3
|
|
*/
|
|
$Value = $a * $YIn [$klo] + $b * $YIn [$khi] + (($a * $a * $a - $a) * $Yt [$klo] + ($b * $b * $b - $b) * $Yt [$khi]) * ($h * $h) / 6;
|
|
|
|
$YPos = $this->GArea_Y2 - (($Value - $this->VMin) * $this->DivisionRatio);
|
|
|
|
if ($XLast != - 1 && ! isset ( $Missing [floor ( $X )] ) && ! isset ( $Missing [floor ( $X + 1 )] ))
|
|
$this->canvas->drawLine(new Point($XLast,
|
|
$YLast),
|
|
new Point($XPos,
|
|
$YPos),
|
|
$this->palette->colors[$ColorID],
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties,
|
|
$graphAreaMin,
|
|
$graphAreaMax);
|
|
|
|
$XLast = $XPos;
|
|
$YLast = $YPos;
|
|
$XPos = $XPos + $this->DivisionWidth * $Accuracy;
|
|
}
|
|
|
|
// Add potentialy missing values
|
|
$XPos = $XPos - $this->DivisionWidth * $Accuracy;
|
|
if ($XPos < ($this->GArea_X2 - $this->GAreaXOffset)) {
|
|
$YPos = $this->GArea_Y2 - (($YIn [$Index] - $this->VMin) * $this->DivisionRatio);
|
|
$this->canvas->drawLine(new Point($XLast,
|
|
$YLast),
|
|
new Point($this->GArea_X2 - $this->GAreaXOffset,
|
|
$YPos),
|
|
$this->palette->colors[$ColorID],
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties,
|
|
$graphAreaMin,
|
|
$graphAreaMax);
|
|
}
|
|
|
|
$GraphID ++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @todo I haven't figured out exactly what this bit of code does,
|
|
* it's just an attempt to reduce code duplication
|
|
*/
|
|
private function calculateCubicCurve(array & $Yt, array $XIn, array $YIn, array & $U, $Index) {
|
|
for($i = 2; $i <= $Index - 1; $i ++) {
|
|
/* Typically $Sig will be 0.5, since each X value will be
|
|
* one unit past the last. If there is missing data then
|
|
* this ratio will change */
|
|
$Sig = ($XIn [$i] - $XIn [$i - 1]) / ($XIn [$i + 1] - $XIn [$i - 1]);
|
|
|
|
$p = $Sig * $Yt [$i - 1] + 2;
|
|
|
|
/* This Y value will nearly always be negative, thanks to
|
|
* $Sig being 0.5 */
|
|
$Yt [$i] = ($Sig - 1) / $p;
|
|
|
|
/** @todo No idea what the following code is doing */
|
|
$U [$i] = ($YIn [$i + 1] - $YIn [$i]) / ($XIn [$i + 1] - $XIn [$i])
|
|
- ($YIn [$i] - $YIn [$i - 1]) / ($XIn [$i] - $XIn [$i - 1]);
|
|
$U [$i] = (6 * $U [$i] / ($XIn [$i + 1] - $XIn [$i - 1]) - $Sig * $U [$i - 1]) / $p;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function draw a filled cubic curve
|
|
*/
|
|
function drawFilledCubicCurve(pData $data, $Accuracy = .1, $Alpha = 100, $AroundZero = FALSE) {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription("drawFilledCubicCurve",
|
|
$data->getDataDescription());
|
|
$this->validateData ( "drawFilledCubicCurve", $data->getData() );
|
|
|
|
$LayerWidth = $this->GArea_X2 - $this->GArea_X1;
|
|
$LayerHeight = $this->GArea_Y2 - $this->GArea_Y1;
|
|
$YZero = $LayerHeight - ((0 - $this->VMin) * $this->DivisionRatio);
|
|
if ($YZero > $LayerHeight) {
|
|
$YZero = $LayerHeight;
|
|
}
|
|
|
|
$GraphID = 0;
|
|
foreach ($data->getDataDescription()->values as $ColName) {
|
|
$XIn = array();
|
|
$YIn = array();
|
|
$Yt = array();
|
|
$U = array();
|
|
|
|
$ColorID = $data->getDataDescription()->getColumnIndex($ColName);
|
|
|
|
$numElements = 1;
|
|
$XLast = - 1;
|
|
$Missing = array();
|
|
|
|
$data->getXYMap($ColName, $XIn, $YIn, $Missing, $numElements);
|
|
|
|
$Yt [0] = 0;
|
|
$Yt [1] = 0;
|
|
$U [1] = 0;
|
|
|
|
$this->calculateCubicCurve($Yt, $XIn, $YIn, $U, $numElements);
|
|
|
|
$Yt [$numElements] = 0;
|
|
|
|
for($k = $numElements - 1; $k >= 1; $k --)
|
|
$Yt [$k] = $Yt [$k] * $Yt [$k + 1] + $U [$k];
|
|
|
|
$Points = "";
|
|
$Points [] = $this->GAreaXOffset + $this->GArea_X1;
|
|
$Points [] = $LayerHeight + $this->GArea_Y1;
|
|
|
|
$YLast = NULL;
|
|
$XPos = $this->GAreaXOffset;
|
|
$PointsCount = 2;
|
|
for($X = 1; $X <= $numElements; $X = $X + $Accuracy) {
|
|
$klo = 1;
|
|
$khi = $numElements;
|
|
$k = $khi - $klo;
|
|
while ( $k > 1 ) {
|
|
$k = $khi - $klo;
|
|
If ($XIn [$k] >= $X)
|
|
$khi = $k;
|
|
else
|
|
$klo = $k;
|
|
}
|
|
$klo = $khi - 1;
|
|
|
|
$h = $XIn [$khi] - $XIn [$klo];
|
|
$a = ($XIn [$khi] - $X) / $h;
|
|
$b = ($X - $XIn [$klo]) / $h;
|
|
$Value = $a * $YIn [$klo] + $b * $YIn [$khi] + (($a * $a * $a - $a) * $Yt [$klo] + ($b * $b * $b - $b) * $Yt [$khi]) * ($h * $h) / 6;
|
|
|
|
$YPos = $LayerHeight - (($Value - $this->VMin) * $this->DivisionRatio);
|
|
|
|
if ($YLast != NULL && $AroundZero && ! isset ( $Missing [floor ( $X )] ) && ! isset ( $Missing [floor ( $X + 1 )] )) {
|
|
$aPoints = "";
|
|
|
|
$aPoints [] = $XLast + $this->GArea_X1;
|
|
$aPoints [] = min($YLast + $this->GArea_Y1, $this->GArea_Y2);
|
|
$aPoints [] = $XPos + $this->GArea_X1;
|
|
$aPoints [] = min($YPos + $this->GArea_Y1, $this->GArea_Y2);
|
|
$aPoints [] = $XPos + $this->GArea_X1;
|
|
$aPoints [] = $YZero + $this->GArea_Y1;
|
|
$aPoints [] = $XLast + $this->GArea_X1;
|
|
$aPoints [] = $YZero + $this->GArea_Y1;
|
|
|
|
$this->canvas->drawFilledPolygon($aPoints,
|
|
4,
|
|
$this->palette->colors[$ColorID],
|
|
$alpha);
|
|
}
|
|
|
|
if (! isset ( $Missing [floor ( $X )] ) || $YLast == NULL) {
|
|
$PointsCount ++;
|
|
$Points [] = $XPos + $this->GArea_X1;
|
|
$Points [] = min($YPos + $this->GArea_Y1, $this->GArea_Y2);
|
|
} else {
|
|
$PointsCount ++;
|
|
$Points [] = $XLast + $this->GArea_X1;
|
|
$Points [] = min($LayerHeight + $this->GArea_Y1,
|
|
$this->GArea_Y2);;
|
|
}
|
|
|
|
$YLast = $YPos;
|
|
$XLast = $XPos;
|
|
$XPos = $XPos + $this->DivisionWidth * $Accuracy;
|
|
}
|
|
|
|
// Add potentialy missing values
|
|
$XPos = $XPos - $this->DivisionWidth * $Accuracy;
|
|
if ($XPos < ($LayerWidth - $this->GAreaXOffset)) {
|
|
$YPos = $LayerHeight - (($YIn [$numElements] - $this->VMin) * $this->DivisionRatio);
|
|
if ($YLast != NULL && $AroundZero) {
|
|
$aPoints = "";
|
|
$aPoints [] = $XLast + $this->GArea_X1;
|
|
$aPoints [] = max($YLast + $this->GArea_Y1, $this->GArea_Y1);
|
|
$aPoints [] = $LayerWidth - $this->GAreaXOffset + $this->GArea_X1;
|
|
$aPoints [] = max($YPos + $this->GArea_Y1, $this->GArea_Y1);
|
|
$aPoints [] = $LayerWidth - $this->GAreaXOffset + $this->GArea_X1;
|
|
$aPoints [] = max($YZero + $this->GArea_Y1, $this->GArea_Y1);
|
|
$aPoints [] = $XLast + $this->GArea_X1;
|
|
$aPoints [] = max($YZero + $this->GArea_Y1, $this->GArea_Y1);
|
|
|
|
$this->canvas->drawFilledPolygon($aPoints,
|
|
4,
|
|
$this->palette->colors[$ColorID],
|
|
$alpha);
|
|
}
|
|
|
|
if ($YIn [$klo] != "" && $YIn [$khi] != "" || $YLast == NULL) {
|
|
$PointsCount ++;
|
|
$Points [] = $LayerWidth
|
|
- $this->GAreaXOffset
|
|
+ $this->GArea_X1;
|
|
$Points [] = $YPos + $this->GArea_Y1;
|
|
}
|
|
}
|
|
|
|
$Points [] = $LayerWidth - $this->GAreaXOffset + $this->GArea_X1;
|
|
$Points [] = $LayerHeight + $this->GArea_Y1;
|
|
|
|
if (! $AroundZero) {
|
|
$this->canvas->drawFilledPolygon($Points,
|
|
$PointsCount,
|
|
$this->palette->colors[$ColorID],
|
|
$Alpha);
|
|
}
|
|
|
|
$this->drawCubicCurve($data, $Accuracy, $ColName );
|
|
|
|
$GraphID ++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function draw a filled line graph
|
|
*/
|
|
function drawFilledLineGraph($Data, $DataDescription, $Alpha = 100, $AroundZero = FALSE) {
|
|
$Empty = - 2147483647;
|
|
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription ( "drawFilledLineGraph", $DataDescription );
|
|
$this->validateData ( "drawFilledLineGraph", $Data );
|
|
|
|
$LayerWidth = $this->GArea_X2 - $this->GArea_X1;
|
|
$LayerHeight = $this->GArea_Y2 - $this->GArea_Y1;
|
|
|
|
$GraphID = 0;
|
|
foreach ( $DataDescription->values as $ColName ) {
|
|
$ColorID = $DataDescription->getColumnIndex($ColName);
|
|
|
|
$aPoints = array();
|
|
$aPoints [] = $this->GAreaXOffset + $this->GArea_X1;
|
|
$aPoints [] = $LayerHeight + $this->GArea_Y1;
|
|
|
|
$XPos = $this->GAreaXOffset;
|
|
$XLast = - 1;
|
|
$PointsCount = 2;
|
|
$YZero = $LayerHeight - ((0 - $this->VMin) * $this->DivisionRatio);
|
|
if ($YZero > $LayerHeight) {
|
|
$YZero = $LayerHeight;
|
|
}
|
|
|
|
$YLast = $Empty;
|
|
foreach (array_keys($Data) as $Key) {
|
|
$Value = $Data [$Key] [$ColName];
|
|
$YPos = $LayerHeight - (($Value - $this->VMin) * $this->DivisionRatio);
|
|
|
|
/* Save point into the image map if option activated */
|
|
if ($this->BuildMap)
|
|
$this->addToImageMap ( $XPos - 3, $YPos - 3, $XPos + 3, $YPos + 3, $DataDescription->description[$ColName], $Data [$Key] [$ColName] . $DataDescription->getYUnit(), "FLine" );
|
|
|
|
if (! is_numeric ( $Value )) {
|
|
$PointsCount ++;
|
|
$aPoints [] = $XLast + $this->GArea_X1;
|
|
$aPoints [] = $LayerHeight + $this->GArea_Y1;
|
|
|
|
$YLast = $Empty;
|
|
} else {
|
|
$PointsCount ++;
|
|
if ($YLast != $Empty) {
|
|
$aPoints [] = $XPos + $this->GArea_X1;
|
|
$aPoints [] = $YPos + $this->GArea_Y1;
|
|
} else {
|
|
$PointsCount ++;
|
|
|
|
$aPoints [] = $XPos + $this->GArea_X1;
|
|
$aPoints [] = $LayerHeight + $this->GArea_Y1;
|
|
$aPoints [] = $XPos + $this->GArea_X1;
|
|
$aPoints [] = $YPos + $this->GArea_Y1;
|
|
}
|
|
|
|
if ($YLast != $Empty && $AroundZero) {
|
|
$Points = "";
|
|
$Points [] = $XLast + $this->GArea_X1;
|
|
$Points [] = $YLast + $this->GArea_Y1;
|
|
$Points [] = $XPos + $this->GArea_X1;
|
|
$Points [] = $YPos + $this->GArea_Y1;
|
|
$Points [] = $XPos + $this->GArea_X1;
|
|
$Points [] = $YZero + $this->GArea_Y1;
|
|
$Points [] = $XLast + $this->GArea_X1;
|
|
$Points [] = $YZero + $this->GArea_Y1;
|
|
|
|
$this->canvas->drawFilledPolygon($Points,
|
|
4,
|
|
$this->palette->colors[$ColorID],
|
|
$Alpha);
|
|
}
|
|
$YLast = $YPos;
|
|
}
|
|
|
|
$XLast = $XPos;
|
|
$XPos = $XPos + $this->DivisionWidth;
|
|
}
|
|
$aPoints [] = $LayerWidth - $this->GAreaXOffset + $this->GArea_X1;
|
|
$aPoints [] = $LayerHeight + $this->GArea_Y1;
|
|
|
|
if ($AroundZero == FALSE) {
|
|
$this->canvas->drawFilledPolygon($aPoints,
|
|
$PointsCount,
|
|
$this->palette->colors[$ColorID],
|
|
$Alpha);
|
|
}
|
|
|
|
$GraphID ++;
|
|
$this->drawLineGraph ( $Data, $DataDescription, $ColName );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function draws a bar graph
|
|
*/
|
|
function drawOverlayBarGraph($Data, $DataDescription, $Alpha = 50) {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription ( "drawOverlayBarGraph", $DataDescription );
|
|
$this->validateData ( "drawOverlayBarGraph", $Data );
|
|
|
|
$LayerHeight = $this->GArea_Y2 - $this->GArea_Y1;
|
|
|
|
$GraphID = 0;
|
|
foreach ( $DataDescription->values as $ColName ) {
|
|
$ColorID = $DataDescription->getColumnIndex($ColName);
|
|
|
|
$XWidth = $this->DivisionWidth / 4;
|
|
$XPos = $this->GAreaXOffset;
|
|
$YZero = $LayerHeight - ((0 - $this->VMin) * $this->DivisionRatio);
|
|
foreach (array_keys($Data) as $Key) {
|
|
if (isset ( $Data [$Key] [$ColName] )) {
|
|
$Value = $Data [$Key] [$ColName];
|
|
if (is_numeric ( $Value )) {
|
|
$YPos = $LayerHeight - (($Value - $this->VMin) * $this->DivisionRatio);
|
|
|
|
$this->canvas->drawFilledRectangle(new Point(floor($XPos - $XWidth + $this->GArea_X1),
|
|
floor($YPos + $this->GArea_Y1)),
|
|
new Point(floor($XPos + $XWidth + $this->GArea_X1),
|
|
floor($YZero + $this->GArea_Y1)),
|
|
$this->palette->colors[$GraphID],
|
|
ShadowProperties::NoShadow(),
|
|
false,
|
|
$Alpha);
|
|
|
|
$X1 = floor ( $XPos - $XWidth + $this->GArea_X1 );
|
|
$Y1 = floor ( $YPos + $this->GArea_Y1 ) + .2;
|
|
$X2 = floor ( $XPos + $XWidth + $this->GArea_X1 );
|
|
$Y2 = $this->GArea_Y2 - ((0 - $this->VMin) * $this->DivisionRatio);
|
|
if ($X1 <= $this->GArea_X1) {
|
|
$X1 = $this->GArea_X1 + 1;
|
|
}
|
|
if ($X2 >= $this->GArea_X2) {
|
|
$X2 = $this->GArea_X2 - 1;
|
|
}
|
|
|
|
/* Save point into the image map if option activated */
|
|
if ($this->BuildMap)
|
|
$this->addToImageMap ( $X1, min ( $Y1, $Y2 ), $X2, max ( $Y1, $Y2 ), $DataDescription->description[$ColName], $Data [$Key] [$ColName] . $DataDescription->getYUnit(), "oBar" );
|
|
|
|
$this->canvas->drawLine(new Point($X1,
|
|
$Y1),
|
|
new Point($X2,
|
|
$Y1),
|
|
$this->palette->colors[$ColorID],
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties,
|
|
new Point($this->GArea_X1,
|
|
$this->GArea_Y1),
|
|
new Point($this->GArea_X2,
|
|
$this->GArea_Y2));
|
|
}
|
|
}
|
|
$XPos = $XPos + $this->DivisionWidth;
|
|
}
|
|
|
|
$GraphID ++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function draw a bar graph
|
|
*/
|
|
function drawBarGraph($Data, $DataDescription, $Alpha = 100) {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription ( "drawBarGraph", $DataDescription );
|
|
$this->validateData ( "drawBarGraph", $Data );
|
|
|
|
$Series = count ( $DataDescription->values);
|
|
$SeriesWidth = $this->DivisionWidth / ($Series + 1);
|
|
$SerieXOffset = $this->DivisionWidth / 2 - $SeriesWidth / 2;
|
|
|
|
$YZero = $this->GArea_Y2 - ((0 - $this->VMin) * $this->DivisionRatio);
|
|
if ($YZero > $this->GArea_Y2) {
|
|
$YZero = $this->GArea_Y2;
|
|
}
|
|
|
|
$SerieID = 0;
|
|
|
|
foreach ( $DataDescription->values as $ColName ) {
|
|
$ColorID = $DataDescription->getColumnIndex($ColName);
|
|
|
|
$XPos = $this->GArea_X1 + $this->GAreaXOffset - $SerieXOffset + $SeriesWidth * $SerieID;
|
|
foreach (array_keys($Data) as $Key) {
|
|
if (isset ( $Data [$Key] [$ColName] )) {
|
|
if (is_numeric ( $Data [$Key] [$ColName] )) {
|
|
$Value = $Data [$Key] [$ColName];
|
|
$YPos = $this->GArea_Y2 - (($Value - $this->VMin) * $this->DivisionRatio);
|
|
|
|
/* Save point into the image map if option activated */
|
|
if ($this->BuildMap) {
|
|
$this->addToImageMap ( $XPos + 1, min ( $YZero, $YPos ), $XPos + $SeriesWidth - 1, max ( $YZero, $YPos ), $DataDescription->description[$ColName], $Data [$Key] [$ColName] . $DataDescription->getYUnit(), "Bar" );
|
|
}
|
|
|
|
if ($Alpha == 100) {
|
|
$this->canvas->drawRectangle(new Point($XPos + 1, $YZero),
|
|
new Point($XPos + $SeriesWidth - 1,
|
|
$YPos),
|
|
new Color(25, 25, 25),
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
}
|
|
|
|
$this->canvas->drawFilledRectangle(new Point($XPos + 1,
|
|
$YZero),
|
|
new Point($XPos + $SeriesWidth - 1,
|
|
$YPos),
|
|
$this->palette->colors[$ColorID],
|
|
$this->shadowProperties,
|
|
TRUE, $Alpha);
|
|
}
|
|
}
|
|
$XPos = $XPos + $this->DivisionWidth;
|
|
}
|
|
$SerieID ++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function draw a stacked bar graph
|
|
*/
|
|
function drawStackedBarGraph($Data, $DataDescription, $Alpha = 50, $Contiguous = FALSE) {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription ( "drawBarGraph", $DataDescription );
|
|
$this->validateData ( "drawBarGraph", $Data );
|
|
|
|
if ($Contiguous)
|
|
$SeriesWidth = $this->DivisionWidth;
|
|
else
|
|
$SeriesWidth = $this->DivisionWidth * .8;
|
|
|
|
$YZero = $this->GArea_Y2 - ((0 - $this->VMin) * $this->DivisionRatio);
|
|
if ($YZero > $this->GArea_Y2) {
|
|
$YZero = $this->GArea_Y2;
|
|
}
|
|
|
|
$SerieID = 0;
|
|
$LastValue = "";
|
|
foreach ( $DataDescription->values as $ColName ) {
|
|
$ColorID = $DataDescription->getColumnIndex($ColName);
|
|
|
|
$XPos = $this->GArea_X1 + $this->GAreaXOffset - $SeriesWidth / 2;
|
|
foreach (array_keys($Data) as $Key) {
|
|
if (isset ( $Data [$Key] [$ColName] )) {
|
|
if (is_numeric ( $Data [$Key] [$ColName] )) {
|
|
$Value = $Data [$Key] [$ColName];
|
|
|
|
if (isset ( $LastValue [$Key] )) {
|
|
$YPos = $this->GArea_Y2 - ((($Value + $LastValue [$Key]) - $this->VMin) * $this->DivisionRatio);
|
|
$YBottom = $this->GArea_Y2 - (($LastValue [$Key] - $this->VMin) * $this->DivisionRatio);
|
|
$LastValue [$Key] += $Value;
|
|
} else {
|
|
$YPos = $this->GArea_Y2 - (($Value - $this->VMin) * $this->DivisionRatio);
|
|
$YBottom = $YZero;
|
|
$LastValue [$Key] = $Value;
|
|
}
|
|
|
|
/* Save point into the image map if option activated */
|
|
if ($this->BuildMap)
|
|
$this->addToImageMap ( $XPos + 1, min ( $YBottom, $YPos ), $XPos + $SeriesWidth - 1, max ( $YBottom, $YPos ), $DataDescription->description[$ColName], $Data [$Key] [$ColName] . $DataDescription->getYUnit(), "sBar" );
|
|
|
|
$this->canvas->drawFilledRectangle(new Point($XPos + 1,
|
|
$YBottom),
|
|
new Point($XPos + $SeriesWidth - 1,
|
|
$YPos),
|
|
$this->palette->colors[$ColorID],
|
|
$this->shadowProperties,
|
|
TRUE,
|
|
$Alpha);
|
|
}
|
|
}
|
|
$XPos = $XPos + $this->DivisionWidth;
|
|
}
|
|
$SerieID ++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function draw a limits bar graphs
|
|
*/
|
|
function drawLimitsGraph($Data, $DataDescription, Color $color = null) {
|
|
if ($color == null) {
|
|
$color = new Color(0, 0, 0);
|
|
}
|
|
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription ( "drawLimitsGraph", $DataDescription );
|
|
$this->validateData ( "drawLimitsGraph", $Data );
|
|
|
|
$XWidth = $this->DivisionWidth / 4;
|
|
$XPos = $this->GArea_X1 + $this->GAreaXOffset;
|
|
|
|
$graphAreaMin = new Point($this->GArea_X1, $this->GArea_Y1);
|
|
$graphAreaMax = new Point($this->GArea_X2, $this->GArea_Y2);
|
|
|
|
foreach (array_keys($Data) as $Key) {
|
|
$Min = $Data [$Key] [$DataDescription->values[0]];
|
|
$Max = $Data [$Key] [$DataDescription->values[0]];
|
|
$GraphID = 0;
|
|
$MaxID = 0;
|
|
$MinID = 0;
|
|
foreach ( $DataDescription->values as $ColName ) {
|
|
if (isset ( $Data [$Key] [$ColName] )) {
|
|
if ($Data [$Key] [$ColName] > $Max && is_numeric ( $Data [$Key] [$ColName] )) {
|
|
$Max = $Data [$Key] [$ColName];
|
|
$MaxID = $GraphID;
|
|
}
|
|
}
|
|
if (isset ( $Data [$Key] [$ColName] ) && is_numeric ( $Data [$Key] [$ColName] )) {
|
|
if ($Data [$Key] [$ColName] < $Min) {
|
|
$Min = $Data [$Key] [$ColName];
|
|
$MinID = $GraphID;
|
|
}
|
|
$GraphID ++;
|
|
}
|
|
}
|
|
|
|
$YPos = $this->GArea_Y2 - (($Max - $this->VMin) * $this->DivisionRatio);
|
|
$X1 = floor ( $XPos - $XWidth );
|
|
$Y1 = floor ( $YPos ) - .2;
|
|
$X2 = floor ( $XPos + $XWidth );
|
|
if ($X1 <= $this->GArea_X1) {
|
|
$X1 = $this->GArea_X1 + 1;
|
|
}
|
|
if ($X2 >= $this->GArea_X2) {
|
|
$X2 = $this->GArea_X2 - 1;
|
|
}
|
|
|
|
$YPos = $this->GArea_Y2 - (($Min - $this->VMin) * $this->DivisionRatio);
|
|
$Y2 = floor ( $YPos ) + .2;
|
|
|
|
$this->canvas->drawLine(new Point(floor ( $XPos ) - .2, $Y1 + 1),
|
|
new Point(floor ( $XPos ) - .2, $Y2 - 1),
|
|
$color,
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties,
|
|
$graphAreaMin,
|
|
$graphAreaMax);
|
|
$this->canvas->drawLine(new Point(floor ( $XPos ) + .2, $Y1 + 1),
|
|
new Point(floor ( $XPos ) + .2, $Y2 - 1),
|
|
$color,
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties,
|
|
$graphAreaMin,
|
|
$graphAreaMax);
|
|
$this->canvas->drawLine(new Point($X1,
|
|
$Y1),
|
|
new Point($X2,
|
|
$Y1),
|
|
$this->palette->colors[$MaxID],
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
$this->canvas->drawLine(new Point($X1,
|
|
$Y2),
|
|
new Point($X2,
|
|
$Y2),
|
|
$this->palette->colors[$MinID],
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
|
|
$XPos = $XPos + $this->DivisionWidth;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function draw radar axis centered on the graph area
|
|
*/
|
|
function drawRadarAxis($Data, $DataDescription, $Mosaic = TRUE, $BorderOffset = 10, Color $colorA = null, Color $colorS = null, $MaxValue = -1) {
|
|
if ($colorA == null) {
|
|
$colorA = new Color(60, 60, 60);
|
|
}
|
|
|
|
if ($colorS == null) {
|
|
$colorS = new Color(200, 200, 200);
|
|
}
|
|
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription ( "drawRadarAxis", $DataDescription );
|
|
$this->validateData ( "drawRadarAxis", $Data );
|
|
|
|
/* Draw radar axis */
|
|
$Points = count ( $Data );
|
|
$Radius = ($this->GArea_Y2 - $this->GArea_Y1) / 2 - $BorderOffset;
|
|
$XCenter = ($this->GArea_X2 - $this->GArea_X1) / 2 + $this->GArea_X1;
|
|
$YCenter = ($this->GArea_Y2 - $this->GArea_Y1) / 2 + $this->GArea_Y1;
|
|
|
|
/* Search for the max value */
|
|
if ($MaxValue == - 1) {
|
|
foreach ( $DataDescription->values as $ColName ) {
|
|
foreach (array_keys($Data) as $Key) {
|
|
if (isset ( $Data [$Key] [$ColName] ))
|
|
if ($Data [$Key] [$ColName] > $MaxValue) {
|
|
$MaxValue = $Data [$Key] [$ColName];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Draw the mosaic */
|
|
if ($Mosaic) {
|
|
$RadiusScale = $Radius / $MaxValue;
|
|
for($t = 1; $t <= $MaxValue - 1; $t ++) {
|
|
$TRadius = $RadiusScale * $t;
|
|
$LastX1 = - 1;
|
|
|
|
for($i = 0; $i <= $Points; $i ++) {
|
|
$Angle = - 90 + $i * 360 / $Points;
|
|
$X1 = cos ( $Angle * M_PI / 180 ) * $TRadius + $XCenter;
|
|
$Y1 = sin ( $Angle * M_PI / 180 ) * $TRadius + $YCenter;
|
|
$X2 = cos ( $Angle * M_PI / 180 ) * ($TRadius + $RadiusScale) + $XCenter;
|
|
$Y2 = sin ( $Angle * M_PI / 180 ) * ($TRadius + $RadiusScale) + $YCenter;
|
|
|
|
if ($t % 2 == 1 && $LastX1 != - 1) {
|
|
$Plots = "";
|
|
$Plots [] = $X1;
|
|
$Plots [] = $Y1;
|
|
$Plots [] = $X2;
|
|
$Plots [] = $Y2;
|
|
$Plots [] = $LastX2;
|
|
$Plots [] = $LastY2;
|
|
$Plots [] = $LastX1;
|
|
$Plots [] = $LastY1;
|
|
|
|
$this->canvas->drawFilledPolygon($Plots,
|
|
(count ( $Plots ) + 1) / 2,
|
|
new Color(250, 250, 250));
|
|
}
|
|
|
|
$LastX1 = $X1;
|
|
$LastY1 = $Y1;
|
|
$LastX2 = $X2;
|
|
$LastY2 = $Y2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Draw the spider web */
|
|
for($t = 1; $t <= $MaxValue; $t ++) {
|
|
$TRadius = ($Radius / $MaxValue) * $t;
|
|
$LastX = - 1;
|
|
|
|
for($i = 0; $i <= $Points; $i ++) {
|
|
$Angle = - 90 + $i * 360 / $Points;
|
|
$X = cos ( $Angle * M_PI / 180 ) * $TRadius + $XCenter;
|
|
$Y = sin ( $Angle * M_PI / 180 ) * $TRadius + $YCenter;
|
|
|
|
if ($LastX != - 1)
|
|
$this->canvas->drawDottedLine(new Point($LastX, $LastY),
|
|
new Point($X, $Y),
|
|
4, 1, $colorS,
|
|
$this->shadowProperties);
|
|
|
|
$LastX = $X;
|
|
$LastY = $Y;
|
|
}
|
|
}
|
|
|
|
/* Draw the axis */
|
|
for($i = 0; $i <= $Points; $i ++) {
|
|
$Angle = - 90 + $i * 360 / $Points;
|
|
$X = cos ( $Angle * M_PI / 180 ) * $Radius + $XCenter;
|
|
$Y = sin ( $Angle * M_PI / 180 ) * $Radius + $YCenter;
|
|
|
|
$this->canvas->drawLine(new Point($XCenter, $YCenter),
|
|
new Point($X, $Y),
|
|
$colorA,
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
|
|
$XOffset = 0;
|
|
$YOffset = 0;
|
|
if (isset ( $Data [$i] [$DataDescription->getPosition()] )) {
|
|
$Label = $Data [$i] [$DataDescription->getPosition()];
|
|
|
|
$Positions = imagettfbbox ( $this->FontSize, 0, $this->FontName, $Label );
|
|
$Width = $Positions [2] - $Positions [6];
|
|
$Height = $Positions [3] - $Positions [7];
|
|
|
|
if ($Angle >= 0 && $Angle <= 90)
|
|
$YOffset = $Height;
|
|
|
|
if ($Angle > 90 && $Angle <= 180) {
|
|
$YOffset = $Height;
|
|
$XOffset = - $Width;
|
|
}
|
|
|
|
if ($Angle > 180 && $Angle <= 270) {
|
|
$XOffset = - $Width;
|
|
}
|
|
|
|
$this->canvas->drawText($this->FontSize,
|
|
0,
|
|
new Point($X + $XOffset, $Y + $YOffset),
|
|
$colorA,
|
|
$this->FontName,
|
|
$Label,
|
|
ShadowProperties::NoShadow());
|
|
}
|
|
}
|
|
|
|
/* Write the values */
|
|
for($t = 1; $t <= $MaxValue; $t ++) {
|
|
$TRadius = ($Radius / $MaxValue) * $t;
|
|
|
|
$Angle = - 90 + 360 / $Points;
|
|
$X1 = $XCenter;
|
|
$Y1 = $YCenter - $TRadius;
|
|
$X2 = cos ( $Angle * M_PI / 180 ) * $TRadius + $XCenter;
|
|
$Y2 = sin ( $Angle * M_PI / 180 ) * $TRadius + $YCenter;
|
|
|
|
$XPos = floor ( ($X2 - $X1) / 2 ) + $X1;
|
|
$YPos = floor ( ($Y2 - $Y1) / 2 ) + $Y1;
|
|
|
|
$Positions = imagettfbbox ( $this->FontSize, 0, $this->FontName, $t );
|
|
$X = $XPos - ($X + $Positions [2] - $X + $Positions [6]) / 2;
|
|
$Y = $YPos + $this->FontSize;
|
|
|
|
$this->canvas->drawFilledRoundedRectangle(new Point($X + $Positions [6] - 2,
|
|
$Y + $Positions [7] - 1),
|
|
new Point($X + $Positions [2] + 4,
|
|
$Y + $Positions [3] + 1),
|
|
2,
|
|
new Color(240, 240, 240),
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
|
|
$this->canvas->drawRoundedRectangle(new Point($X + $Positions [6] - 2,
|
|
$Y + $Positions [7] - 1),
|
|
new Point($X + $Positions [2] + 4,
|
|
$Y + $Positions [3] + 1),
|
|
2,
|
|
new Color(220, 220, 220),
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
$this->canvas->drawText($this->FontSize,
|
|
0,
|
|
new Point($X, $Y),
|
|
$colorA,
|
|
$this->FontName,
|
|
$t,
|
|
ShadowProperties::NoShadow());
|
|
}
|
|
}
|
|
|
|
private function calculateMaxValue($Data, $DataDescription) {
|
|
$MaxValue = -1;
|
|
foreach ( $DataDescription->values as $ColName ) {
|
|
foreach (array_keys($Data) as $Key) {
|
|
if (isset ( $Data [$Key] [$ColName] ))
|
|
if ($Data [$Key] [$ColName] > $MaxValue && is_numeric($Data[$Key][$ColName])) {
|
|
$MaxValue = $Data [$Key] [$ColName];
|
|
}
|
|
}
|
|
}
|
|
return $MaxValue;
|
|
}
|
|
|
|
/**
|
|
* This function draw a radar graph centered on the graph area
|
|
*/
|
|
function drawRadar($Data, $DataDescription, $BorderOffset = 10, $MaxValue = -1) {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription ( "drawRadar", $DataDescription );
|
|
$this->validateData ( "drawRadar", $Data );
|
|
|
|
$Points = count ( $Data );
|
|
$Radius = ($this->GArea_Y2 - $this->GArea_Y1) / 2 - $BorderOffset;
|
|
$XCenter = ($this->GArea_X2 - $this->GArea_X1) / 2 + $this->GArea_X1;
|
|
$YCenter = ($this->GArea_Y2 - $this->GArea_Y1) / 2 + $this->GArea_Y1;
|
|
|
|
/* Search for the max value */
|
|
if ($MaxValue == - 1) {
|
|
$MaxValue = $this->calculateMaxValue($Data, $DataDescription);
|
|
}
|
|
|
|
$GraphID = 0;
|
|
foreach ( $DataDescription->values as $ColName ) {
|
|
$ColorID = $DataDescription->getColumnIndex($ColName);
|
|
|
|
$Angle = - 90;
|
|
$XLast = - 1;
|
|
foreach (array_keys($Data) as $Key) {
|
|
if (isset ( $Data [$Key] [$ColName] )) {
|
|
$Value = $Data [$Key] [$ColName];
|
|
$Strength = ($Radius / $MaxValue) * $Value;
|
|
|
|
$XPos = cos ( $Angle * M_PI / 180 ) * $Strength + $XCenter;
|
|
$YPos = sin ( $Angle * M_PI / 180 ) * $Strength + $YCenter;
|
|
|
|
if ($XLast != - 1)
|
|
$this->canvas->drawLine(new Point($XLast,
|
|
$YLast),
|
|
new Point($XPos,
|
|
$YPos),
|
|
$this->palette->colors[$ColorID],
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
|
|
if ($XLast == - 1) {
|
|
$FirstX = $XPos;
|
|
$FirstY = $YPos;
|
|
}
|
|
|
|
$Angle = $Angle + (360 / $Points);
|
|
$XLast = $XPos;
|
|
$YLast = $YPos;
|
|
}
|
|
}
|
|
$this->canvas->drawLine(new Point($XPos,
|
|
$YPos),
|
|
new Point($FirstX,
|
|
$FirstY),
|
|
$this->palette->colors[$ColorID],
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
$GraphID ++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function draw a radar graph centered on the graph area
|
|
*/
|
|
function drawFilledRadar($Data, $DataDescription, $Alpha = 50, $BorderOffset = 10, $MaxValue = -1) {
|
|
/* Validate the Data and DataDescription array */
|
|
$this->validateDataDescription ( "drawFilledRadar", $DataDescription );
|
|
$this->validateData ( "drawFilledRadar", $Data );
|
|
|
|
$Points = count ( $Data );
|
|
$Radius = ($this->GArea_Y2 - $this->GArea_Y1) / 2 - $BorderOffset;
|
|
$XCenter = ($this->GArea_X2 - $this->GArea_X1) / 2;
|
|
$YCenter = ($this->GArea_Y2 - $this->GArea_Y1) / 2;
|
|
|
|
/* Search for the max value */
|
|
if ($MaxValue == - 1) {
|
|
$MaxValue = $this->calculateMaxValue($Data, $DataDescription);
|
|
}
|
|
|
|
$GraphID = 0;
|
|
foreach ( $DataDescription->values as $ColName ) {
|
|
$ColorID = $DataDescription->getColumnIndex($ColName);
|
|
|
|
$Angle = - 90;
|
|
$XLast = - 1;
|
|
$Plots = array();
|
|
foreach (array_keys($Data) as $Key) {
|
|
if (isset ( $Data [$Key] [$ColName] )) {
|
|
$Value = $Data [$Key] [$ColName];
|
|
if (! is_numeric ( $Value )) {
|
|
$Value = 0;
|
|
}
|
|
$Strength = ($Radius / $MaxValue) * $Value;
|
|
|
|
$XPos = cos ( $Angle * M_PI / 180 ) * $Strength + $XCenter;
|
|
$YPos = sin ( $Angle * M_PI / 180 ) * $Strength + $YCenter;
|
|
|
|
$Plots [] = $XPos + $this->GArea_X1;
|
|
$Plots [] = $YPos + $this->GArea_Y1;
|
|
|
|
$Angle = $Angle + (360 / $Points);
|
|
$XLast = $XPos;
|
|
}
|
|
}
|
|
|
|
if (isset ( $Plots [0] )) {
|
|
$Plots [] = $Plots [0];
|
|
$Plots [] = $Plots [1];
|
|
|
|
$this->canvas->drawFilledPolygon($Plots,
|
|
(count ( $Plots ) + 1) / 2,
|
|
$this->palette->colors[$ColorID],
|
|
$Alpha);
|
|
|
|
for($i = 0; $i <= count ( $Plots ) - 4; $i = $i + 2)
|
|
$this->canvas->drawLine(new Point($Plots [$i],
|
|
$Plots [$i + 1]),
|
|
new Point($Plots [$i + 2],
|
|
$Plots [$i + 3]),
|
|
$this->palette->colors[$ColorID],
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
}
|
|
|
|
$GraphID ++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function can be used to set the background color
|
|
*/
|
|
function drawBackground(Color $color) {
|
|
$C_Background = $this->canvas->allocateColor($color);
|
|
imagefilledrectangle ( $this->canvas->getPicture(), 0, 0, $this->XSize, $this->YSize, $C_Background );
|
|
}
|
|
|
|
private function drawGradient(Point $point1, Point $point2, Color $color, $decay) {
|
|
/* Positive gradient */
|
|
if ($decay > 0) {
|
|
$YStep = ($point2->getY() - $point1->getY() - 2) / $decay;
|
|
for($i = 0; $i <= $decay; $i ++) {
|
|
$color = $color->addRGBIncrement(-1);
|
|
$Yi1 = $point1->getY() + ($i * $YStep);
|
|
$Yi2 = ceil ( $Yi1 + ($i * $YStep) + $YStep );
|
|
if ($Yi2 >= $Yi2) {
|
|
$Yi2 = $point2->getY() - 1;
|
|
}
|
|
|
|
$this->canvas->drawFilledRectangle(new Point($point1->getX(), $Yi1),
|
|
new Point($point2->getX(), $Yi2),
|
|
$color,
|
|
ShadowProperties::NoShadow());
|
|
}
|
|
}
|
|
|
|
/* Negative gradient */
|
|
if ($decay < 0) {
|
|
$YStep = ($point2->getY() - $point1->getY() - 2) / - $decay;
|
|
$Yi1 = $point1->getY();
|
|
$Yi2 = $point1->getY() + $YStep;
|
|
for($i = - $decay; $i >= 0; $i --) {
|
|
$color = $color->addRGBIncrement(1);
|
|
|
|
$this->canvas->drawFilledRectangle(new Point($point1->getX(), $Yi1),
|
|
new Point($point2->getX(), $Yi2),
|
|
$color,
|
|
ShadowProperties::NoShadow());
|
|
|
|
$Yi1 += $YStep;
|
|
$Yi2 += $YStep;
|
|
if ($Yi2 >= $Yi2) {
|
|
$Yi2 = $point2->getY() - 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function can be used to set the background color
|
|
*/
|
|
private function drawGraphAreaGradient(BackgroundStyle $style) {
|
|
if (!$style->useGradient()) {
|
|
return;
|
|
}
|
|
|
|
$this->drawGradient(new Point($this->GArea_X1 + 1, $this->GArea_Y1 + 1),
|
|
new Point($this->GArea_X2 - 1, $this->GArea_Y2),
|
|
$style->getGradientStartColor(),
|
|
$style->getGradientDecay());
|
|
}
|
|
|
|
public function drawBackgroundGradient(Color $color, $decay) {
|
|
$this->drawGradient(new Point(0, 0),
|
|
new Point($this->XSize, $this->YSize),
|
|
$color,
|
|
$decay);
|
|
}
|
|
|
|
/**
|
|
* This function will draw an ellipse
|
|
*/
|
|
function drawEllipse($Xc, $Yc, $Height, $Width, Color $color) {
|
|
$this->canvas->drawCircle(new Point($Xc, $Yc), $Height, $color, $this->shadowProperties, $Width );
|
|
}
|
|
|
|
/**
|
|
* This function will draw a filled ellipse
|
|
*/
|
|
function drawFilledEllipse($Xc, $Yc, $Height, $Width, Color $color) {
|
|
$this->canvas->drawFilledCircle(new Point($Xc, $Yc), $Height, $color, $this->shadowProperties, $Width );
|
|
}
|
|
|
|
/**
|
|
* Load a PNG file and draw it over the chart
|
|
*/
|
|
function drawFromPNG($FileName, $X, $Y, $Alpha = 100) {
|
|
$this->drawFromPicture ( 1, $FileName, $X, $Y, $Alpha );
|
|
}
|
|
|
|
/**
|
|
* Load a GIF file and draw it over the chart
|
|
*/
|
|
function drawFromGIF($FileName, $X, $Y, $Alpha = 100) {
|
|
$this->drawFromPicture ( 2, $FileName, $X, $Y, $Alpha );
|
|
}
|
|
|
|
/**
|
|
* Load a JPEG file and draw it over the chart
|
|
*/
|
|
function drawFromJPG($FileName, $X, $Y, $Alpha = 100) {
|
|
$this->drawFromPicture ( 3, $FileName, $X, $Y, $Alpha );
|
|
}
|
|
|
|
/**
|
|
* Generic loader function for external pictures
|
|
*/
|
|
function drawFromPicture($PicType, $FileName, $X, $Y, $Alpha = 100) {
|
|
if (file_exists ( $FileName )) {
|
|
$Infos = getimagesize ( $FileName );
|
|
$Width = $Infos [0];
|
|
$Height = $Infos [1];
|
|
if ($PicType == 1) {
|
|
$Raster = imagecreatefrompng ( $FileName );
|
|
}
|
|
if ($PicType == 2) {
|
|
$Raster = imagecreatefromgif ( $FileName );
|
|
}
|
|
if ($PicType == 3) {
|
|
$Raster = imagecreatefromjpeg ( $FileName );
|
|
}
|
|
|
|
imagecopymerge ( $this->canvas->getPicture(), $Raster, $X, $Y, 0, 0, $Width, $Height, $Alpha );
|
|
imagedestroy ( $Raster );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a border to the picture
|
|
*
|
|
* @todo This hasn't been updated to the new API yet
|
|
*/
|
|
function addBorder($Size = 3, $R = 0, $G = 0, $B = 0) {
|
|
$Width = $this->XSize + 2 * $Size;
|
|
$Height = $this->YSize + 2 * $Size;
|
|
|
|
$Resampled = imagecreatetruecolor ( $Width, $Height );
|
|
$C_Background = imagecolorallocate($Resampled, $R, $G, $B);
|
|
imagefilledrectangle ( $Resampled, 0, 0, $Width, $Height, $C_Background );
|
|
|
|
imagecopy ( $Resampled, $this->canvas->getPicture(), $Size, $Size, 0, 0, $this->XSize, $this->YSize );
|
|
imagedestroy ( $this->canvas->getPicture() );
|
|
|
|
$this->XSize = $Width;
|
|
$this->YSize = $Height;
|
|
|
|
$this->canvas->setPicture(imagecreatetruecolor ( $this->XSize, $this->YSize ));
|
|
$C_White = $this->canvas->allocate(new Color(255, 255, 255));
|
|
imagefilledrectangle ( $this->canvas->getPicture(), 0, 0, $this->XSize, $this->YSize, $C_White );
|
|
imagecolortransparent ( $this->canvas->getPicture(), $C_White );
|
|
imagecopy ( $this->canvas->getPicture(), $Resampled, 0, 0, 0, 0, $this->XSize, $this->YSize );
|
|
}
|
|
|
|
/**
|
|
* Render the current picture to a file
|
|
*/
|
|
function Render($FileName) {
|
|
if ($this->ErrorReporting)
|
|
$this->printErrors ( $this->ErrorInterface );
|
|
|
|
/* Save image map if requested */
|
|
if ($this->BuildMap)
|
|
$this->SaveImageMap ();
|
|
|
|
imagepng ( $this->canvas->getPicture(), $FileName );
|
|
}
|
|
|
|
/**
|
|
* Render the current picture to STDOUT
|
|
*/
|
|
function Stroke() {
|
|
if ($this->ErrorReporting)
|
|
$this->printErrors ( "GD" );
|
|
|
|
/* Save image map if requested */
|
|
if ($this->BuildMap)
|
|
$this->SaveImageMap ();
|
|
|
|
header ( 'Content-type: image/png' );
|
|
imagepng ( $this->canvas->getPicture() );
|
|
}
|
|
|
|
/**
|
|
* Validate data contained in the description array
|
|
*
|
|
* @todo Should this be a method on DataDescription?
|
|
*/
|
|
protected function validateDataDescription($FunctionName, DataDescription &$DataDescription, $DescriptionRequired = TRUE) {
|
|
if ($DataDescription->getPosition() == '') {
|
|
$this->Errors [] = "[Warning] " . $FunctionName . " - Y Labels are not set.";
|
|
$DataDescription->setPosition("Name");
|
|
}
|
|
|
|
if ($DescriptionRequired) {
|
|
if (! isset ( $DataDescription->description)) {
|
|
$this->Errors [] = "[Warning] " . $FunctionName . " - Series descriptions are not set.";
|
|
foreach ( $DataDescription->values as $key => $Value ) {
|
|
$DataDescription->description[$Value] = $Value;
|
|
}
|
|
}
|
|
|
|
if (count ( $DataDescription->description) < count ( $DataDescription->values )) {
|
|
$this->Errors [] = "[Warning] " . $FunctionName . " - Some series descriptions are not set.";
|
|
foreach ( $DataDescription->values as $key => $Value ) {
|
|
if (! isset ( $DataDescription->description[$Value] ))
|
|
$DataDescription->description[$Value] = $Value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate data contained in the data array
|
|
*/
|
|
protected function validateData($FunctionName, &$Data) {
|
|
$DataSummary = array ();
|
|
|
|
foreach ( $Data as $key => $Values ) {
|
|
foreach ( $Values as $key2 => $Value ) {
|
|
if (! isset ( $DataSummary [$key2] ))
|
|
$DataSummary [$key2] = 1;
|
|
else
|
|
$DataSummary [$key2] ++;
|
|
}
|
|
}
|
|
|
|
if (empty($DataSummary))
|
|
$this->Errors [] = "[Warning] " . $FunctionName . " - No data set.";
|
|
|
|
foreach ( $DataSummary as $key => $Value ) {
|
|
if ($Value < max ( $DataSummary )) {
|
|
$this->Errors [] = "[Warning] " . $FunctionName . " - Missing data in serie " . $key . ".";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Print all error messages on the CLI or graphically
|
|
*/
|
|
function printErrors($Mode = "CLI") {
|
|
if (count ( $this->Errors ) == 0)
|
|
return (0);
|
|
|
|
if ($Mode == "CLI") {
|
|
foreach ( $this->Errors as $key => $Value )
|
|
echo $Value . "\r\n";
|
|
} elseif ($Mode == "GD") {
|
|
$MaxWidth = 0;
|
|
foreach ( $this->Errors as $key => $Value ) {
|
|
$Position = imageftbbox ( $this->ErrorFontSize, 0, $this->ErrorFontName, $Value );
|
|
$TextWidth = $Position [2] - $Position [0];
|
|
if ($TextWidth > $MaxWidth) {
|
|
$MaxWidth = $TextWidth;
|
|
}
|
|
}
|
|
$this->canvas->drawFilledRoundedRectangle(new Point($this->XSize - ($MaxWidth + 20),
|
|
$this->YSize - (20 + (($this->ErrorFontSize + 4) * count ( $this->Errors )))),
|
|
new Point($this->XSize - 10,
|
|
$this->YSize - 10),
|
|
6,
|
|
new Color(233, 185, 185),
|
|
$this->lineWidth,
|
|
$this->lineDotSize,
|
|
$this->shadowProperties);
|
|
|
|
$this->canvas->drawRoundedRectangle(new Point($this->XSize - ($MaxWidth + 20),
|
|
$this->YSize - (20 + (($this->ErrorFontSize + 4) * count ( $this->Errors )))),
|
|
new Point($this->XSize - 10,
|
|
$this->YSize - 10),
|
|
6,
|
|
new Color(193, 145, 145),
|
|
$this->LineWidth,
|
|
$this->LineDotSize,
|
|
$this->shadowProperties);
|
|
|
|
$YPos = $this->YSize - (18 + (count ( $this->Errors ) - 1) * ($this->ErrorFontSize + 4));
|
|
foreach ( $this->Errors as $key => $Value ) {
|
|
$this->canvas->drawText($this->ErrorFontSize,
|
|
0,
|
|
new Point($this->XSize - ($MaxWidth + 15),
|
|
$YPos),
|
|
new Color(133, 85, 85),
|
|
$this->ErrorFontName,
|
|
$Value,
|
|
ShadowProperties::NoShadow());
|
|
$YPos = $YPos + ($this->ErrorFontSize + 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Activate the image map creation process
|
|
*/
|
|
function setImageMap($Mode = TRUE, $GraphID = "MyGraph") {
|
|
$this->BuildMap = $Mode;
|
|
$this->MapID = $GraphID;
|
|
}
|
|
|
|
/**
|
|
* Add a box into the image map
|
|
*/
|
|
function addToImageMap($X1, $Y1, $X2, $Y2, $SerieName, $Value, $CallerFunction) {
|
|
if ($this->MapFunction == NULL || $this->MapFunction == $CallerFunction) {
|
|
$this->ImageMap [] = round ( $X1 ) . "," . round ( $Y1 ) . "," . round ( $X2 ) . "," . round ( $Y2 ) . "," . $SerieName . "," . $Value;
|
|
$this->MapFunction = $CallerFunction;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load and cleanup the image map from disk
|
|
*/
|
|
function getImageMap($MapName, $Flush = TRUE) {
|
|
/* Strip HTML query strings */
|
|
$Values = $this->tmpFolder . $MapName;
|
|
$Value = explode ( "\?", $Values );
|
|
$FileName = $Value [0];
|
|
|
|
if (file_exists ( $FileName )) {
|
|
$Handle = fopen ( $FileName, "r" );
|
|
$MapContent = fread ( $Handle, filesize ( $FileName ) );
|
|
fclose ( $Handle );
|
|
echo $MapContent;
|
|
|
|
if ($Flush)
|
|
unlink ( $FileName );
|
|
|
|
exit ();
|
|
} else {
|
|
header ( "HTTP/1.0 404 Not Found" );
|
|
exit ();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save the image map to the disk
|
|
*/
|
|
function SaveImageMap() {
|
|
if (! $this->BuildMap) {
|
|
return (- 1);
|
|
}
|
|
|
|
if ($this->ImageMap == NULL) {
|
|
$this->Errors [] = "[Warning] SaveImageMap - Image map is empty.";
|
|
return (- 1);
|
|
}
|
|
|
|
$Handle = fopen ( $this->tmpFolder . $this->MapID, 'w' );
|
|
if (! $Handle) {
|
|
$this->Errors [] = "[Warning] SaveImageMap - Cannot save the image map.";
|
|
return (- 1);
|
|
} else {
|
|
foreach ( $this->ImageMap as $Value )
|
|
fwrite ( $Handle, htmlentities ( $Value ) . "\r" );
|
|
}
|
|
fclose ( $Handle );
|
|
}
|
|
|
|
/**
|
|
* Set date format for axis labels
|
|
*/
|
|
function setDateFormat($Format) {
|
|
$this->DateFormat = $Format;
|
|
}
|
|
|
|
/**
|
|
* Convert TS to a date format string
|
|
*/
|
|
function ToDate($Value) {
|
|
return (date ( $this->DateFormat, $Value ));
|
|
}
|
|
|
|
/**
|
|
* Check if a number is a full integer (for scaling)
|
|
*/
|
|
static private function isRealInt($Value) {
|
|
if ($Value == floor ( $Value ))
|
|
return (TRUE);
|
|
return (FALSE);
|
|
}
|
|
|
|
/**
|
|
* @todo I don't know what this does yet, I'm refactoring...
|
|
*/
|
|
public function calculateScales(& $Scale, & $Divisions) {
|
|
/* Compute automatic scaling */
|
|
$ScaleOk = FALSE;
|
|
$Factor = 1;
|
|
$MinDivHeight = 25;
|
|
$MaxDivs = ($this->GArea_Y2 - $this->GArea_Y1) / $MinDivHeight;
|
|
|
|
if ($this->VMax <= $this->VMin) {
|
|
throw new Exception("Impossible to calculate scales when VMax <= VMin");
|
|
}
|
|
|
|
if ($this->VMin == 0 && $this->VMax == 0) {
|
|
$this->VMin = 0;
|
|
$this->VMax = 2;
|
|
$Scale = 1;
|
|
$Divisions = 2;
|
|
} elseif ($MaxDivs > 1) {
|
|
while ( ! $ScaleOk ) {
|
|
$Scale1 = ($this->VMax - $this->VMin) / $Factor;
|
|
$Scale2 = ($this->VMax - $this->VMin) / $Factor / 2;
|
|
|
|
if ($Scale1 > 1 && $Scale1 <= $MaxDivs && ! $ScaleOk) {
|
|
$ScaleOk = TRUE;
|
|
$Divisions = floor ( $Scale1 );
|
|
$Scale = 1;
|
|
}
|
|
if ($Scale2 > 1 && $Scale2 <= $MaxDivs && ! $ScaleOk) {
|
|
$ScaleOk = TRUE;
|
|
$Divisions = floor ( $Scale2 );
|
|
$Scale = 2;
|
|
}
|
|
if (! $ScaleOk) {
|
|
if ($Scale2 > 1) {
|
|
$Factor = $Factor * 10;
|
|
}
|
|
if ($Scale2 < 1) {
|
|
$Factor = $Factor / 10;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (floor ( $this->VMax / $Scale / $Factor ) != $this->VMax / $Scale / $Factor) {
|
|
$GridID = floor ( $this->VMax / $Scale / $Factor ) + 1;
|
|
$this->VMax = $GridID * $Scale * $Factor;
|
|
$Divisions ++;
|
|
}
|
|
|
|
if (floor ( $this->VMin / $Scale / $Factor ) != $this->VMin / $Scale / $Factor) {
|
|
$GridID = floor ( $this->VMin / $Scale / $Factor );
|
|
$this->VMin = $GridID * $Scale * $Factor;
|
|
$Divisions ++;
|
|
}
|
|
} else /* Can occur for small graphs */
|
|
$Scale = 1;
|
|
}
|
|
|
|
/*
|
|
* Returns the resource
|
|
*/
|
|
public function getPicture() {
|
|
return $this->canvas->getPicture();
|
|
}
|
|
|
|
static private function computeAutomaticScaling($minCoord, $maxCoord, &$minVal, &$maxVal, &$Divisions) {
|
|
$ScaleOk = FALSE;
|
|
$Factor = 1.0;
|
|
$MinDivHeight = 25;
|
|
$MaxDivs = ($maxCoord - $minCoord) / $MinDivHeight;
|
|
|
|
if ($minVal == 0 && $maxVal == 0){
|
|
$minVal = 0;
|
|
$maxVal = 2;
|
|
$Scale = 1;
|
|
$Divisions = 2;
|
|
}elseif ($minVal == $maxVal){
|
|
$minVal = 0;
|
|
$maxVal = $maxVal*2;
|
|
$Scale = 1;
|
|
$Divisions = 2;
|
|
} elseif ($MaxDivs > 1) {
|
|
while ( ! $ScaleOk ) {
|
|
if($Factor == 0) { die(); }
|
|
$Scale1 = ($maxVal - $minVal) / $Factor;
|
|
$Scale2 = ($maxVal - $minVal) / $Factor / 2;
|
|
|
|
if ($Scale1 > 1 && $Scale1 <= $MaxDivs && ! $ScaleOk) {
|
|
$ScaleOk = TRUE;
|
|
$Divisions = floor ( $Scale1 );
|
|
$Scale = 1;
|
|
}
|
|
if ($Scale2 > 1 && $Scale2 <= $MaxDivs && ! $ScaleOk) {
|
|
$ScaleOk = TRUE;
|
|
$Divisions = floor ( $Scale2 );
|
|
$Scale = 2;
|
|
}
|
|
if (! $ScaleOk) {
|
|
if ($Scale2 > 1) {
|
|
$Factor = $Factor * 10;
|
|
}
|
|
if ($Scale2 < 1) {
|
|
$Factor = $Factor / 10;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (floor ( $maxVal / $Scale / $Factor ) != $maxVal / $Scale / $Factor) {
|
|
$GridID = floor ( $maxVal / $Scale / $Factor ) + 1;
|
|
$maxVal = $GridID * $Scale * $Factor;
|
|
$Divisions ++;
|
|
}
|
|
|
|
if (floor ( $minVal / $Scale / $Factor ) != $minVal / $Scale / $Factor) {
|
|
$GridID = floor ( $minVal / $Scale / $Factor );
|
|
$minVal = $GridID * $Scale * $Factor;
|
|
$Divisions ++;
|
|
}
|
|
} else /* Can occurs for small graphs */
|
|
$Scale = 1;
|
|
|
|
if (! isset ( $Divisions ))
|
|
$Divisions = 2;
|
|
|
|
if (self::isRealInt ( ($maxVal - $minVal) / ($Divisions - 1) ))
|
|
$Divisions --;
|
|
elseif (self::isRealInt ( ($maxVal - $minVal) / ($Divisions + 1) ))
|
|
$Divisions ++;
|
|
}
|
|
|
|
static private function convertValueForDisplay($value, $format, $unit) {
|
|
if ($format == "number")
|
|
return $value . $unit;
|
|
if ($format == "time")
|
|
return ConversionHelpers::ToTime ( $value );
|
|
if ($format == "date")
|
|
return $this->ToDate ( $value );
|
|
if ($format == "metric")
|
|
return ConversionHelpers::ToMetric ( $value );
|
|
if ($format == "currency")
|
|
return ConversionHelpers::ToCurrency ( $value );
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param $Message
|
|
*/
|
|
function RaiseFatal($Message) {
|
|
echo "[FATAL] " . $Message . "\r\n";
|
|
exit ();
|
|
}
|
|
?>
|