Files
TZU/php/phpgraphlib.php
2018-04-11 22:17:21 +02:00

1624 lines
54 KiB
PHP

<?php
/*
PHPGraphLib Graphing Library
The first version PHPGraphLib was written in 2007 by Elliott Brueggeman to
deliver PHP generated graphs quickly and easily. It has grown in both features
and maturity since its inception, but remains PHP 4.04+ compatible. Originally
available only for paid commerial use, PHPGraphLib was open-sourced in 2013
under the MIT License. Please visit http://www.ebrueggeman.com/phpgraphlib
for more information.
---
The MIT License (MIT)
Copyright (c) 2013 Elliott Brueggeman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
class PHPGraphLib {
//set to actual font heights and widths used
const TITLE_CHAR_WIDTH = 6;
const TITLE_CHAR_HEIGHT = 12;
const TEXT_WIDTH = 6;
const TEXT_HEIGHT = 12;
const LEGEND_TEXT_WIDTH = 6;
const LEGEND_TEXT_HEIGHT = 12;
const DATA_VALUE_TEXT_WIDTH = 6;
const DATA_VALUE_TEXT_HEIGHT = 12;
//padding between axis and value displayed
const AXIS_VALUE_PADDING = 5;
//space b/t top of bar or center of point and data value
const DATA_VALUE_PADDING = 5;
//default margin % of width / height
const X_AXIS_MARGIN_PERCENT = 12;
const Y_AXIS_MARGIN_PERCENT = 8;
//controls auto-adjusting grid interval
const RANGE_DIVISOR_FACTOR = 25;
//limit colors allocated for gradient
const GRADIENT_MAX_COLORS = 200;
//multiple dataset offset percents
const MULTI_OFFSET_TWO = 24;
const MULTI_OFFSET_THREE = 15;
const MULTI_OFFSET_FOUR = 10;
const MULTI_OFFSET_FIVE = 9;
//padding inside legend box
const LEGEND_PADDING = 4;
const LEGEND_MAX_CHARS = 15;
//display default
protected $height = 300;
protected $width = 400;
protected $bool_bars = true;
protected $bool_bar_outline = true;
protected $bool_x_axis = true;
protected $bool_y_axis = true;
protected $bool_x_axis_values = true;
protected $bool_y_axis_values = true;
protected $bool_grid = true;
protected $bool_line = false;
protected $bool_data_values = false;
protected $bool_x_axis_values_vert = true;
protected $bool_data_points = false;
protected $bool_title_left = false;
protected $bool_title_right = false;
protected $bool_title_center = true;
protected $bool_background = false;
protected $bool_title = false;
protected $bool_ignore_data_fit_errors = false;
protected $data_point_width = 6;
protected $x_axis_value_interval = false;
//internal status vars
protected $data_set_count = 0;
protected $data_min = 0;
protected $data_max = 0;
protected $data_count = 0;
protected $bool_data = false;
protected $bool_bars_generate = true;
protected $bool_all_negative = false;
protected $bool_all_positive = false;
protected $bool_gradient = false;
protected $bool_user_data_range = false;
protected $all_zero_data = false;
protected $bool_gradient_colors_found = array();
protected $bool_y_axis_setup = false;
protected $bool_x_axis_setup = false;
//color vars
protected $background_color;
protected $grid_color;
protected $bar_color;
protected $outline_color;
protected $x_axis_text_color;
protected $y_axis_text_color;
protected $title_color;
protected $x_axis_color;
protected $y_axis_color;
protected $data_point_color;
protected $data_value_color;
protected $line_color = array();
protected $line_color_default;
protected $goal_line_color;
protected $gradient_color_array;
protected $gradient_handicap = array();
protected $image;
protected $output_file;
protected $error;
protected $data_array;
protected $actual_displayed_max_value;
protected $actual_displayed_min_value;
protected $data_currency;
protected $data_format_array = array();
protected $data_additional_length = 0;
protected $data_format_generic;
//bar vars / scale
protected $bar_width;
protected $space_width;
protected $unit_scale;
protected $goal_line_array = array();
protected $horiz_grid_lines = array();
protected $vert_grid_lines = array();
protected $horiz_grid_values = array();
protected $vert_grid_values = array();
//axis points
protected $x_axis_x1;
protected $x_axis_y1;
protected $x_axis_x2;
protected $x_axis_y2;
protected $y_axis_x1;
protected $y_axis_y1;
protected $y_axis_x2;
protected $y_axis_y2;
protected $lowest_x;
protected $highest_x;
//aka bottom margin
protected $x_axis_margin;
//aka left margin
protected $y_axis_margin;
protected $data_range_max;
protected $data_range_min;
protected $top_margin = 0;
protected $right_margin = 0;
protected $data_point_array = array();
protected $multi_gradient_colors_1 = array();
protected $multi_gradient_colors_2 = array();
protected $multi_bar_colors = array();
//percent color decrease for multiple datasets
protected $color_darken_factor = 30;
//legend variables
protected $bool_legend = false;
protected $legend_total_chars = array();
protected $legend_width;
protected $legend_height;
protected $legend_x;
protected $legend_y;
protected $legend_color;
protected $legend_text_color;
protected $legend_outline_color;
protected $legend_swatch_outline_color;
protected $legend_titles = array();
public function __construct($width, $height, $output_file = null)
{
$this->width = $width;
$this->height = $height;
$this->output_file = $output_file;
$this->initialize();
$this->allocateColors();
}
protected function initialize()
{
//header must be sent before any html or blank space output
if (!$this->output_file) {
header("Content-type: image/png");
}
$this->image = @imagecreate($this->width, $this->height)
or die("Cannot Initialize new GD image stream - Check your PHP setup");
}
public function createGraph()
{
//main class method - called last
//setup axis if not already setup by user
if ($this->bool_data){
$this->analyzeData();
if (!$this->bool_x_axis_setup) {
$this->setupXAxis();
}
if (!$this->bool_y_axis_setup){
$this->setupYAxis();
}
//calculations
$this->calcTopMargin();
$this->calcRightMargin();
$this->calcCoords();
$this->setupData();
//start creating actual image elements
if ($this->bool_background) {
$this->generateBackgound();
}
//always gen grid values, even if not displayed
$this->setupGrid();
if ($this->bool_bars_generate) {
$this->generateBars();
}
if ($this->bool_data_points) {
$this->generateDataPoints();
}
if ($this->bool_legend) {
$this->generateLegend();
}
if ($this->bool_title) {
$this->generateTitle();
}
if ($this->bool_x_axis) {
$this->generateXAxis();
}
if ($this->bool_y_axis) {
$this->generateYAxis();
}
} else {
$this->error[] = "No valid data added to graph. Add data with the addData() function.";
}
//display errors
$this->displayErrors();
//output to browser
if ($this->output_file) {
imagepng($this->image, $this->output_file);
} else {
imagepng($this->image);
}
imagedestroy($this->image);
}
protected function setupData()
{
$unit_width = ($this->width - $this->y_axis_margin - $this->right_margin) / (($this->data_count * 2) + $this->data_count);
if ($unit_width < 1 && !$this->bool_ignore_data_fit_errors) {
//error units too small, too many data points or not large enough graph
$this->bool_bars_generate = false;
$this->error[] = "Graph too small or too many data points.";
} else {
//default space between bars is 1/2 the width of the bar
//find bar and space widths. bar = 2 units, space = 1 unit
$this->bar_width = 2 * $unit_width;
$this->space_width = $unit_width;
//now calculate height (scale) units
$availVertSpace = $this->height - $this->x_axis_margin - $this->top_margin;
if ($availVertSpace < 1) {
$this->bool_bars_generate = false;
$this->error[] = "Graph height not tall enough.";
//error scale units too small, x axis margin too big or graph height not tall enough
} else {
if ($this->bool_user_data_range) {
//if all zero data, set fake max and min to range boundaries
if ($this->all_zero_data) {
if ($this->data_range_min > $this->data_min) {
$this->data_min = $this->data_range_min;
}
if ($this->data_range_max < $this->data_max) {
$this->data_max = $this->data_range_max;
}
}
$graphTopScale = $this->data_range_max;
$graphBottomScale = $this->data_range_min;
$graphScaleRange = $graphTopScale - $graphBottomScale;
$this->unit_scale = $availVertSpace / $graphScaleRange;
$this->data_max = $this->data_range_max;
$this->data_min = $this->data_range_min;
if ($this->data_min < 0) {
$this->x_axis_y1 -= round($this->unit_scale * abs($this->data_min));
$this->x_axis_y2 -= round($this->unit_scale * abs($this->data_min));
}
//all data identical
if ($graphScaleRange == 0) {
$graphScaleRange = 100;
}
} else {
//start at y value 0 or data min, whichever is less
$graphBottomScale = ($this->data_min<0) ? $this->data_min : 0;
$graphTopScale = ($this->data_max<0) ? 0 : $this->data_max ;
$graphScaleRange = $graphTopScale - $graphBottomScale;
//all data identical
if ($graphScaleRange == 0) {
$graphScaleRange = 100;
}
$this->unit_scale = $availVertSpace / $graphScaleRange;
//now adjust x axis in y value if negative values
if ($this->data_min < 0) {
$this->x_axis_y1 -= round($this->unit_scale * abs($this->data_min));
$this->x_axis_y2 -= round($this->unit_scale * abs($this->data_min));
}
}
}
}
}
//always port changes to generatebars() to extensions
protected function generateBars()
{
$this->finalizeColors();
$barCount = 0;
$adjustment = 0;
if ($this->bool_user_data_range && $this->data_min >= 0) {
$adjustment = $this->data_min * $this->unit_scale;
}
//reverse array to order data sets in order of priority
$this->data_array = array_reverse($this->data_array);
//set different offsets based on number of data sets
$dataset_offset = 0;
switch ($this->data_set_count) {
case 2:
$dataset_offset = $this->bar_width * (self::MULTI_OFFSET_TWO / 100);
break;
case 3:
$dataset_offset = $this->bar_width * (self::MULTI_OFFSET_THREE / 100);
break;
case 4:
$dataset_offset = $this->bar_width * (self::MULTI_OFFSET_FOUR / 100);
break;
case 5:
$dataset_offset = $this->bar_width * (self::MULTI_OFFSET_FIVE / 100);
break;
}
foreach ($this->data_array as $data_set_num => $data_set) {
$lineX2 = null;
reset($data_set);
$xStart = $this->y_axis_x1 + ($this->space_width / 2) + ((key($data_set) - $this->lowest_x) * ($this->bar_width + $this->space_width));
foreach ($data_set as $key => $item) {
$hideBarOutline = false;
$x1 = round($xStart + ($dataset_offset * $data_set_num));
$x2 = round(($xStart + $this->bar_width) + ($dataset_offset * $data_set_num));
$y1 = round($this->x_axis_y1 - ($item * $this->unit_scale) + $adjustment);
$y2 = round($this->x_axis_y1);
//if we are using a user specified data range, need to limit what's displayed
if ($this->bool_user_data_range) {
if ($item <= $this->data_range_min) {
//don't display, we are out of our allowed display range!
$y1 = $y2;
$hideBarOutline = true;
} elseif ($item >= $this->data_range_max) {
//display, but cut off display above range max
$y1 = $this->x_axis_y1 - ($this->actual_displayed_max_value * $this->unit_scale) + $adjustment;
}
}
//draw bar
if ($this->bool_bars) {
if ($this->bool_gradient) {
//draw gradient if desired
$this->drawGradientBar($x1, $y1, $x2, $y2, $this->multi_gradient_colors_1[$data_set_num], $this->multi_gradient_colors_2[$data_set_num], $data_set_num);
} else {
imagefilledrectangle($this->image, $x1, $y1,$x2, $y2, $this->multi_bar_colors[$data_set_num]);
}
//draw bar outline
if ($this->bool_bar_outline && !$hideBarOutline) {
imagerectangle($this->image, $x1, $y2, $x2, $y1, $this->outline_color);
}
}
// draw line
if ($this->bool_line) {
$lineX1 = $x1 + ($this->bar_width / 2); //MIDPOINT OF BARS, IF SHOWN
$lineY1 = $y1;
if (isset($lineX2)) {
imageline($this->image, $lineX2, $lineY2, $lineX1, $lineY1, $this->line_color[$data_set_num]);
$lineX2 = $lineX1;
$lineY2 = $lineY1;
} else {
$lineX2 = $lineX1;
$lineY2 = $lineY1;
}
}
// display data points
if ($this->bool_data_points) {
//dont draw datapoints here or will overlap poorly with line
//instead collect coordinates
$pointX = $x1 + ($this->bar_width / 2); //MIDPOINT OF BARS, IF SHOWN
$this->data_point_array[] = array($pointX, $y1);
}
// display data values
if ($this->bool_data_values) {
$dataX = ($x1 + ($this->bar_width / 2)) - ((strlen($item) * self::DATA_VALUE_TEXT_WIDTH) / 2);
//value to be graphed is equal/over 0
if ($item >= 0) {
$dataY = $y1 - self::DATA_VALUE_PADDING - self::DATA_VALUE_TEXT_HEIGHT;
} else {
//check for item values below user spec'd range
if ($this->bool_user_data_range && $item <= $this->data_range_min) {
$dataY = $y1 - self::DATA_VALUE_PADDING - self::DATA_VALUE_TEXT_HEIGHT;
} else {
$dataY = $y1 + self::DATA_VALUE_PADDING;
}
}
//add currency sign, formatting etc
if ($this->data_format_array) {
$item = $this->applyDataFormats($item);
}
if ($this->data_currency) {
$item = $this->applyDataCurrency($item);
}
//recenter data position if necessary
$dataX -= ($this->data_additional_length * self::DATA_VALUE_TEXT_WIDTH) / 2;
imagestring($this->image, 2, $dataX, $dataY, $item, $this->data_value_color);
}
//write x axis value
if ($this->bool_x_axis_values) {
{
if ($this->bool_x_axis_values_vert) {
if ($this->bool_all_negative) {
//we must put values above 0 line
$textVertPos = round($this->y_axis_y2 - self::AXIS_VALUE_PADDING);
} else {
//mix of both pos and neg numbers
//write value y axis bottom value (will be under bottom of grid even if x axis is floating due to
$textVertPos = round($this->y_axis_y1 + (strlen($key) * self::TEXT_WIDTH) + self::AXIS_VALUE_PADDING);
}
$textHorizPos = round($xStart + ($this->bar_width / 2) - (self::TEXT_HEIGHT / 2));
//skip and dispplay every x intervals
if ($this->x_axis_value_interval) {
if ($key % $this->x_axis_value_interval) {
} else {
imagestringup($this->image, 2, $textHorizPos, $textVertPos, $key, $this->x_axis_text_color);
}
}
else {
imagestringup($this->image, 2, $textHorizPos, $textVertPos, $key, $this->x_axis_text_color);
}
}
else {
if ($this->bool_all_negative) {
//we must put values above 0 line
$textVertPos = round($this->y_axis_y2 - self::TEXT_HEIGHT - self::AXIS_VALUE_PADDING);
} else {
//mix of both pos and neg numbers
//write value y axis bottom value (will be under bottom of grid even if x axis is floating due to
$textVertPos = round($this->y_axis_y1 + (self::TEXT_HEIGHT * 2 / 3) - self::AXIS_VALUE_PADDING);
}
//horizontal data keys
$textHorizPos = round($xStart + ($this->bar_width / 2) - ((strlen($key) * self::TEXT_WIDTH) / 2));
//skip and dispplay every x intervals
if ($this->x_axis_value_interval) {
if ($key % $this->x_axis_value_interval) {
} else {
imagestring($this->image, 2, $textHorizPos, $textVertPos, $key, $this->x_axis_text_color);
}
} else {
imagestring($this->image, 2, $textHorizPos, $textVertPos, $key, $this->x_axis_text_color);
}
}
}
}
$xStart += $this->bar_width + $this->space_width;
}
}
}
protected function finalizeColors()
{
if ($this->bool_gradient) {
$num_set=count($this->multi_gradient_colors_1);
//loop through set colors and add backing colors if necessary
if ($num_set != $this->data_set_count) {
$color_darken_decimal = (100 - $this->color_darken_factor) / 100;
while ($num_set < $this->data_set_count) {
$color_ref_1 = $this->multi_gradient_colors_1[$num_set - 1];
$color_ref_2 = $this->multi_gradient_colors_2[$num_set - 1];
$this->multi_gradient_colors_1[] = array(
round($color_ref_1[0] * $color_darken_decimal),
round($color_ref_1[1] * $color_darken_decimal),
round($color_ref_1[2] * $color_darken_decimal));
$this->multi_gradient_colors_2[]=array(
round($color_ref_2[0] * $color_darken_decimal),
round($color_ref_2[1] * $color_darken_decimal),
round($color_ref_2[2] * $color_darken_decimal));
$num_set++;
}
}
while (count($this->multi_gradient_colors_1) > $this->data_set_count) {
$temp = array_pop($this->multi_gradient_colors_1);
}
while (count($this->multi_gradient_colors_2) > $this->data_set_count) {
$temp = array_pop($this->multi_gradient_colors_2);
}
$this->multi_gradient_colors_1 = array_reverse($this->multi_gradient_colors_1);
$this->multi_gradient_colors_2 = array_reverse($this->multi_gradient_colors_2);
} elseif (!$this->bool_gradient) {
$num_set = count($this->multi_bar_colors);
if ($num_set == 0) {
$this->multi_bar_colors[0] = $this->bar_color;
$num_set = 1;
}
//loop through set colors and add backing colors if necessary
while ($num_set < $this->data_set_count) {
$color_ref = $this->multi_bar_colors[$num_set - 1];
$color_parts = imagecolorsforindex($this->image, $color_ref);
$color_darken_decimal = (100 - $this->color_darken_factor) / 100;
$this->multi_bar_colors[$num_set] = imagecolorallocate($this->image,
round($color_parts['red'] * $color_darken_decimal),
round($color_parts['green'] * $color_darken_decimal),
round($color_parts['blue'] * $color_darken_decimal));
$num_set++;
}
while (count($this->multi_bar_colors) > $this->data_set_count) {
$temp = array_pop($this->multi_bar_colors);
}
$this->multi_bar_colors = array_reverse($this->multi_bar_colors);
}
if ($this->bool_line) {
if (!$this->bool_bars) {
$num_set = count($this->line_color);
if ($num_set == 0) {
$this->line_color[0] = $this->line_color_default;
$num_set = 1;
}
//only darken each data set's lines when no bars present
while ($num_set < $this->data_set_count) {
$color_ref = $this->line_color[$num_set - 1];
$color_parts = imagecolorsforindex($this->image, $color_ref);
$color_darken_decimal = (100 - $this->color_darken_factor) / 100;
$this->line_color[$num_set] = imagecolorallocate($this->image,
round($color_parts['red'] * $color_darken_decimal),
round($color_parts['green'] * $color_darken_decimal),
round($color_parts['blue'] * $color_darken_decimal));
$num_set++;
}
} else {
$num_set = count($this->line_color);
while ($num_set < $this->data_set_count) {
$this->line_color[$num_set] = $this->line_color_default;
$num_set++;
}
}
while (count($this->line_color) > $this->data_set_count) {
$temp = array_pop($this->line_color);
}
$this->line_color = array_reverse($this->line_color);
}
}
protected function drawGradientBar($x1, $y1, $x2, $y2, $colorArr1, $colorArr2, $data_set_num)
{
if (!isset($this->bool_gradient_colors_found[$data_set_num]) || $this->bool_gradient_colors_found[$data_set_num] == false) {
$this->gradient_handicap[$data_set_num] = 0;
$numLines = abs($x1 - $x2) + 1;
while ($numLines * $this->data_set_count > self::GRADIENT_MAX_COLORS) {
//we have more lines than allowable colors
//use handicap to record this
$numLines /= 2;
$this->gradient_handicap[$data_set_num]++;
}
$color1R = $colorArr1[0];
$color1G = $colorArr1[1];
$color1B = $colorArr1[2];
$color2R = $colorArr2[0];
$color2G = $colorArr2[1];
$color2B = $colorArr2[2];
$rScale = ($color1R-$color2R) / $numLines;
$gScale = ($color1G-$color2G) / $numLines;
$bScale = ($color1B-$color2B) / $numLines;
$this->allocateGradientColors($color1R, $color1G, $color1B, $rScale, $gScale, $bScale, $numLines, $data_set_num);
}
$numLines = abs($x1 - $x2) + 1;
if ($this->gradient_handicap[$data_set_num] > 0) {
//if handicap is used, it will allow us to move through the array more slowly, depending on the set value
$interval = $this->gradient_handicap[$data_set_num];
for($i = 0; $i < $numLines; $i++) {
$adjusted_index = ceil($i / pow(2, $interval)) - 1;
if ($adjusted_index < 0) {
$adjusted_index = 0;
}
imageline($this->image, $x1+$i, $y1, $x1 + $i, $y2, $this->gradient_color_array[$data_set_num][$adjusted_index]);
}
} else {
//normal gradients with colors < self::GRADIENT_MAX_COLORS
for ($i = 0; $i < $numLines; $i++) {
imageline($this->image, $x1+$i, $y1, $x1+$i, $y2, $this->gradient_color_array[$data_set_num][$i]);
}
}
}
protected function setupGrid()
{
//adjustment for when user sets their data display range
$adjustment = 0;
if ($this->bool_user_data_range && $this->data_min >= 0) {
$adjustment = $this->data_min * $this->unit_scale;
}
$this->calculateGridHoriz($adjustment);
$this->calculateGridVert();
$this->generateGrids();
$this->generateGoalLines($adjustment);
}
protected function calculateGridHoriz($adjustment = 0)
{
//determine horizontal grid lines
$horizGridArray = array();
$min = $this->bool_user_data_range ? $this->data_min : 0;
$horizGridArray[] = $min;
//use our function to determine ideal y axis scale interval
$intervalFromZero = $this->determineAxisMarkerScale($this->data_max, $this->data_min);
//if we have positive values, add grid values to array
//until we reach the max needed (we will go 1 over)
$cur = $min;
while ($cur < $this->data_max) {
$cur += $intervalFromZero;
$horizGridArray[] = $cur;
}
//if we have negative values, add grid values to array
//until we reach the min needed (we will go 1 over)
$cur = $min;
while ($cur > $this->data_min) {
$cur -= $intervalFromZero;
$horizGridArray[] = $cur;
}
//sort needed b/c we will use last value later (max)
sort($horizGridArray);
$this->actual_displayed_max_value = $horizGridArray[count($horizGridArray) - 1];
$this->actual_displayed_min_value = $horizGridArray[0];
//loop through each horizontal line
foreach ($horizGridArray as $value) {
$yValue = round($this->x_axis_y1 - ($value * $this->unit_scale) + $adjustment);
if ($this->bool_grid) {
//imageline($this->image, $this->y_axis_x1, $yValue, $this->x_axis_x2 , $yValue, $this->grid_color);
$this->horiz_grid_lines[] = array('x1' => $this->y_axis_x1, 'y1' => $yValue,
'x2' => $this->x_axis_x2, 'y2' => $yValue, 'color' => $this->grid_color);
}
//display value on y axis if desired using calc'd grid values
if ($this->bool_y_axis_values) {
$adjustedYValue = $yValue - (self::TEXT_HEIGHT / 2);
$adjustedXValue = $this->y_axis_x1 - ((strlen($value) + $this->data_additional_length) * self::TEXT_WIDTH) - self::AXIS_VALUE_PADDING;
//add currency sign, formatting etc
if ($this->data_format_array) {
$value = $this->applyDataFormats($value);
}
if ($this->data_currency) {
$value = $this->applyDataCurrency($value);
}
//imagestring($this->image, 2, $adjustedXValue, $adjustedYValue, $value, $this->y_axis_text_color);
$this->horiz_grid_values[] = array('size' => 2, 'x' => $adjustedXValue, 'y' => $adjustedYValue,
'value' => $value, 'color' => $this->y_axis_text_color);
}
}
if (!$this->bool_all_positive && !$this->bool_user_data_range) {
//reset with better value based on grid min value calculations, not data min
$this->y_axis_y1 = $this->x_axis_y1 - ($horizGridArray[0] * $this->unit_scale);
}
//reset with better value based on grid value calculations, not data min
$this->y_axis_y2 = $yValue;
}
protected function calculateGridVert()
{
//determine vertical grid lines
$vertGridArray = array();
$vertGrids = $this->data_count + 1;
$interval = $this->bar_width + $this->space_width;
//assemble vert gridline array
for ($i = 1; $i < $vertGrids; $i++) {
$vertGridArray[] = $this->y_axis_x1 + ($interval * $i);
}
//loop through each vertical line
if ($this->bool_grid) {
$xValue = $this->y_axis_y1;
foreach ($vertGridArray as $value) {
//imageline($this->image, $value, $this->y_axis_y2, $value, $xValue , $this->grid_color);
$this->vert_grid_lines[] = array('x1' => $value, 'y1' => $this->y_axis_y2,
'x2' => $value, 'y2' => $xValue, 'color' => $this->grid_color);
}
}
}
protected function imagelinedashed(&$image_handle, $x_axis_x1, $yLocation, $x_axis_x2, $color)
{
$step = 3;
for ($i = $x_axis_x1; $i < $x_axis_x2 -1; $i += ($step*2)) {
imageline($this->image, $i, $yLocation, $i + $step - 1, $yLocation, $color);
}
}
protected function generateGrids()
{
//loop through and display values
foreach ($this->horiz_grid_lines as $line) {
imageline($this->image, $line['x1'], $line['y1'], $line['x2'], $line['y2'] , $line['color']);
}
foreach ($this->vert_grid_lines as $line) {
imageline($this->image, $line['x1'], $line['y1'], $line['x2'], $line['y2'] , $line['color']);
}
foreach ($this->horiz_grid_values as $value) {
imagestring($this->image, $value['size'], $value['x'], $value['y'], $value['value'], $value['color']);
}
//not implemented in the base library, but used in extensions
foreach ($this->vert_grid_values as $value) {
imagestring($this->image, $value['size'], $value['x'], $value['y'], $value['value'], $value['color']);
}
}
protected function generateGoalLines($adjustment = 0)
{
//draw goal lines if present (after grid) - doesn't get executed if array empty
foreach ($this->goal_line_array as $goal_line_data) {
$yLocation = $goal_line_data['yValue'];
$style = $goal_line_data['style'];
$color = $goal_line_data['color'] ? $goal_line_data['color'] : $this->goal_line_color;
$yLocation = round(($this->x_axis_y1 - ($yLocation * $this->unit_scale) + $adjustment));
if ($style == 'dashed') {
$this->imagelinedashed($this->image, $this->x_axis_x1, $yLocation, $this->x_axis_x2, $color);
} else {
//a solid line is the default if a style condition is not matched
imageline($this->image, $this->x_axis_x1, $yLocation, $this->x_axis_x2 , $yLocation, $color);
}
}
}
protected function generateDataPoints()
{
foreach ($this->data_point_array as $pointArray) {
imagefilledellipse($this->image, $pointArray[0], $pointArray[1], $this->data_point_width, $this->data_point_width, $this->data_point_color);
}
}
protected function generateXAxis()
{
imageline($this->image, $this->x_axis_x1, $this->x_axis_y1, $this->x_axis_x2, $this->x_axis_y2, $this->x_axis_color);
}
protected function generateYAxis()
{
imageline($this->image, $this->y_axis_x1, $this->y_axis_y1, $this->y_axis_x2, $this->y_axis_y2, $this->y_axis_color);
}
protected function generateBackgound()
{
imagefilledrectangle($this->image, 0, 0, $this->width, $this->height, $this->background_color);
}
protected function generateTitle()
{
//spacing may have changed since earlier
//use top margin or grid top y, whichever less
$highestElement = ($this->top_margin < $this->y_axis_y2) ? $this->top_margin : $this->y_axis_y2;
$textVertPos = ($highestElement / 2) - (self::TITLE_CHAR_HEIGHT / 2); //centered
$titleLength = strlen($this->title_text);
if ($this->bool_title_center) {
$title_x = ($this->width / 2) - (($titleLength * self::TITLE_CHAR_WIDTH) / 2);
$title_y = $textVertPos;
} elseif ($this->bool_title_left) {
$title_x = $this->y_axis_x1;
$title_y = $textVertPos;
} elseif ($this->bool_title_right) {
$this->title_x = $this->x_axis_x2 - ($titleLength * self::TITLE_CHAR_WIDTH);
$this->title_y = $textVertPos;
}
imagestring($this->image, 2, $title_x , $title_y , $this->title_text, $this->title_color);
}
protected function calcTopMargin()
{
if ($this->bool_title) {
//include space for title, approx margin + 3*title height
$this->top_margin = ($this->height * (self::X_AXIS_MARGIN_PERCENT / 100)) + self::TITLE_CHAR_HEIGHT;
} else {
//just use default spacing
$this->top_margin = $this->height * (self::X_AXIS_MARGIN_PERCENT / 100);
}
}
protected function calcRightMargin()
{
//just use default spacing
$this->right_margin = $this->width * (self::Y_AXIS_MARGIN_PERCENT / 100);
}
protected function calcCoords()
{
//calculate axis points, also used for other calculations
$this->x_axis_x1 = $this->y_axis_margin;
$this->x_axis_y1 = $this->height - $this->x_axis_margin;
$this->x_axis_x2 = $this->width - $this->right_margin;
$this->x_axis_y2 = $this->height - $this->x_axis_margin;
$this->y_axis_x1 = $this->y_axis_margin;
$this->y_axis_y1 = $this->height - $this->x_axis_margin;
$this->y_axis_x2 = $this->y_axis_margin;
$this->y_axis_y2 = $this->top_margin;
}
protected function determineAxisMarkerScale($max, $min, $axis = 'y')
{
switch ($axis) {
case 'y' :
$space = $this->height;
break;
case 'x' :
$space = $this->width;
break;
}
//for calclation, take range or max-0
if ($this->bool_user_data_range) {
$range = abs($max - $min);
} else {
$range = (abs($max-$min) > abs($max - 0)) ? abs($max - $min) : abs($max - 0);
}
//handle all zero data
if ($range == 0) {
$range = 10;
}
//multiply up to over 100, to better figure interval
$count = 0;
while (abs($range) < 100) {
$range *= 10;
$count++;
}
//divide into intervals based on height / preset constant - after rounding will be approx
$divisor = round($space / self::RANGE_DIVISOR_FACTOR);
$divided = round($range / $divisor);
$result = $this->roundUpOneExtraDigit($divided);
//if rounded up w/ extra digit is more than 200% of divided value,
//round up to next sig number with same num of digits
if ($result / $divided >= 2) {
$result = $this->roundUpSameDigits($divided);
}
//divide back down, if needed
for ($i = 0; $i < $count; $i++) {
$result /= 10;
}
return $result;
}
protected function roundUpSameDigits($num)
{
$len = strlen($num);
if (round($num, -1 * ($len - 1)) == $num) {
//we already have a sig number
return $num;
} else {
$firstDig = substr($num, 0,1);
$secondDig = substr($num, 1,1);
$rest = substr($num, 2);
$secondDig = 5;
$altered = $firstDig.$secondDig.$rest;
//after reassembly, round up to next sig number, same # of digits
return round((int)$altered, -1 * ($len - 1));
}
}
protected function roundUpOneExtraDigit($num)
{
$len = strlen($num);
$firstDig = substr($num, 0, 1);
$rest = substr($num, 1);
$firstDig = 5;
$altered = $firstDig . $rest;
//after reassembly, round up to next sig number, one extra # of digits
return round((int)$altered, -1 * ($len));
}
protected function displayErrors()
{
if (count($this->error) > 0) {
$lineHeight = 12;
$errorColor = imagecolorallocate($this->image, 0, 0, 0);
$errorBackColor = imagecolorallocate($this->image, 255, 204, 0);
imagefilledrectangle($this->image, 0, 0, $this->width - 1, 2 * $lineHeight, $errorBackColor);
imagestring($this->image, 3, 2, 0, "!!----- PHPGraphLib Error -----!!", $errorColor);
foreach($this->error as $key => $errorText) {
imagefilledrectangle($this->image, 0, ($key * $lineHeight) + $lineHeight, $this->width - 1, ($key * $lineHeight) + 2 * $lineHeight, $errorBackColor);
imagestring($this->image, 2, 2, ($key * $lineHeight) + $lineHeight, "[". ($key + 1) . "] ". $errorText, $errorColor);
}
$errorOutlineColor = imagecolorallocate($this->image, 255, 0, 0);
imagerectangle($this->image, 0, 0, $this->width-1,($key * $lineHeight) + 2 * $lineHeight, $errorOutlineColor);
}
}
public function addData($data, $data2 = '', $data3 = '', $data4 = '', $data5 = '')
{
$data_sets = array($data, $data2, $data3, $data4, $data5);
foreach ($data_sets as $set) {
if (is_array($set)) {
$this->data_array[] = $set;
}
}
//get rid of bad data, find max, min
$low_x = 0;
$high_x = 0;
$force_set_x = 1;
foreach ($this->data_array as $data_set_num => $data_set) {
foreach ($data_set as $key => $item) {
if ($force_set_x) {
$low_x = $key;
$high_x = $key;
$force_set_x = 0;
}
if ($key < $low_x) {
$low_x = $key;
}
if ($key > $high_x) {
$high_x = $key;
}
if (!is_numeric($item)) {
unset($this->data_array[$data_set_num][$key]);
continue;
}
if ($item < $this->data_min) {
$this->data_min = $item;
}
if ($item > $this->data_max) {
$this->data_max = $item;
}
}
//find the count of the dataset with the most data points
$count = count($this->data_array[$data_set_num]);
if ($count > $this->data_count) {
$this->data_count = $count;
}
}
$this->lowest_x = $low_x;
$this->highest_x = $high_x;
$raw_size = $high_x - $low_x +1;
if ($raw_size > $this->data_count) {
$this->data_count = $raw_size;
}
//number of valid data sets
$this->data_set_count = count($this->data_array);
if ($this->data_set_count == 0) {
$this->error[] = "No valid datasets added in adddata() function.";
return;
}
$this->bool_data = true;
}
protected function analyzeData()
{
if ($this->data_min >= 0) {
$this->bool_all_positive = true;
} elseif ($this->data_max <= 0) {
$this->bool_all_negative = true;
}
//setup static max and min for all zero data
if ($this->data_min >= 0 && $this->data_max == 0 ) {
$this->data_min = 0;
$this->data_max = 10;
$this->all_zero_data = true;
}
}
public function setupXAxis($percent = '', $color = '')
{
$this->bool_x_axis_setup = true;
if ($percent === false) {
$this->bool_x_axis = false;
} else {
$this->bool_x_axis = true;
}
if (!empty($color) && $arr = $this->returnColorArray($color)) {
$this->x_axis_color = imagecolorallocate($this->image, $arr[0], $arr[1], $arr[2]);
}
if (is_numeric($percent) && $percent > 0) {
$percent = $percent / 100;
$this->x_axis_margin = round($this->height * $percent);
} else {
$percent = self::X_AXIS_MARGIN_PERCENT / 100;
$this->x_axis_margin = round($this->height * $percent);
}
}
public function setupYAxis($percent = '', $color = '')
{
$this->bool_y_axis_setup = true;
if ($percent === false) {
$this->bool_y_axis = false;
} else {
$this->bool_y_axis = true;
}
if (!empty($color) && $arr = $this->returnColorArray($color)) {
$this->y_axis_color = imagecolorallocate($this->image, $arr[0], $arr[1], $arr[2]);
}
if (is_numeric($percent) && $percent > 0) {
$this->y_axis_margin = round($this->width * ($percent / 100));
} else {
$percent = self::Y_AXIS_MARGIN_PERCENT / 100;
$this->y_axis_margin = round($this->width * $percent);
}
}
public function setRange($min, $max)
{
//because of deprecated use of this function as($max, $min)
if ($min > $max) {
$this->data_range_max = $min;
$this->data_range_min = $max;
} else {
$this->data_range_max = $max;
$this->data_range_min = $min;
}
$this->bool_user_data_range = true;
}
public function setTitle($title)
{
if (!empty($title)) {
$this->title_text = $title;
$this->bool_title = true;
} else {
$this->error[] = "String arg for setTitle() not specified properly.";
}
}
public function setTitleLocation($location)
{
$this->bool_title_left = false;
$this->bool_title_right = false;
$this->bool_title_center = false;
switch (strtolower($location)) {
case 'left':
$this->bool_title_left = true;
break;
case 'right':
$this->bool_title_right = true;
break;
case 'center':
$this->bool_title_center = true;
break;
default:
$this->error[] = "String arg for setTitleLocation() not specified properly.";
}
}
public function setBars($bool)
{
if (is_bool($bool)) {
$this->bool_bars = $bool;
} else {
$this->error[] = "Boolean arg for setBars() not specified properly.";
}
}
public function setGrid($bool)
{
if (is_bool($bool)) {
$this->bool_grid = $bool;
} else {
$this->error[] = "Boolean arg for setGrid() not specified properly.";
}
}
public function setXValues($bool)
{
if (is_bool($bool)) {
$this->bool_x_axis_values = $bool;
} else {
$this->error[] = "Boolean arg for setXValues() not specified properly.";
}
}
public function setYValues($bool)
{
if (is_bool($bool)) {
$this->bool_y_axis_values = $bool;
} else {
$this->error[] = "Boolean arg for setYValues() not specified properly.";
}
}
public function setXValuesHorizontal($bool)
{
if (is_bool($bool)) {
$this->bool_x_axis_values_vert = !$bool;
} else {
$this->error[] = "Boolean arg for setXValuesHorizontal() not specified properly.";
}
}
public function setXValuesVertical($bool)
{
if (is_bool($bool)) {
$this->bool_x_axis_values_vert = $bool;
} else {
$this->error[] = "Boolean arg for setXValuesVertical() not specified properly.";
}
}
public function setXValuesInterval($value)
{
if (is_int($value) && $value > 0) {
$this->x_axis_value_interval = $value;
} else {
$this->error[] = "Value arg for setXValuesInterval() not specified properly.";
}
}
public function setBarOutline($bool)
{
if (is_bool($bool)) {
$this->bool_bar_outline = $bool;
} else {
$this->error[] = "Boolean arg for setBarOutline() not specified properly.";
}
}
public function setDataPoints($bool)
{
if (is_bool($bool)) {
$this->bool_data_points = $bool;
} else {
$this->error[] = "Boolean arg for setDataPoints() not specified properly.";
}
}
public function setDataPointSize($size)
{
if (is_numeric($size)) {
$this->data_point_width = $size;
} else {
$this->error[] = "Data point size in setDataPointSize() not specified properly.";
}
}
public function setDataValues($bool)
{
if (is_bool($bool)) {
$this->bool_data_values = $bool;
}
else {
$this->error[] = "Boolean arg for setDataValues() not specified properly.";
}
}
public function setDataCurrency($currency_type = 'dollar')
{
switch (strtolower($currency_type)) {
case 'dollar': $this->data_currency = '$'; break;
case 'yen': $this->data_currency = '¥'; break;
case 'pound': $this->data_currency = '£'; break;
case 'lira': $this->data_currency = '£'; break;
// Euro doesn't display properly
// Franc doesn't display properly
default: $this->data_currency = $currency_type; break;
}
$this->data_additional_length += strlen($this->data_currency);
}
protected function applyDataCurrency($input)
{
return $this->data_currency . $input;
}
public function setDataFormat($format)
{
//setup structure for future additional data formats - specify callback functions
switch ($format) {
case 'comma':
$this->data_format_array[] = 'formatDataAsComma';
$this->data_additional_length += floor(strlen($this->data_max) / 3);
break;
case 'percent':
$this->data_format_array[] = 'formatDataAsPercent';
$this->data_additional_length++;
break;
case 'degrees':
$this->data_format_array[] = 'formatDataAsDegrees';
$this->data_additional_length++;
break;
default:
$this->data_format_array[] = 'formatDataAsGeneric';
$this->data_format_generic = $format;
$this->data_additional_length += strlen($format);
break;
}
}
protected function applyDataFormats($input)
{
//comma formatting must be done first
if ($pos = array_search('formatDataAsComma', $this->data_format_array)) {
unset($this->data_format_array[$pos]);
array_unshift($this->data_format_array, 'formatDataAsComma');
}
//loop through each formatting function
foreach ($this->data_format_array as $format_type_callback) {
eval('$input=$this->' . $format_type_callback . '($input);');
}
return $input;
}
protected function formatDataAsComma($input)
{
//check for negative sign
$sign_part = '';
if (substr($input, 0, 1) == '-') {
$input = substr($input, 1);
$sign_part = '-';
}
//handle decimals
$decimal_part = '';
if (($pos = strpos($input, '.')) !== false) {
$decimal_part = substr($input, $pos);
$input = substr($input, 0, $pos);
}
//turn data into format 12,234...
$parts = '';
while (strlen($input)>3) {
$parts = ',' . substr($input, -3) . $parts;
$input = substr($input, 0, strlen($input) - 3);
}
return $sign_part . $currency_part . $input . $parts . $decimal_part;
}
protected function formatDataAsPercent($input)
{
return $input . '%';
}
protected function formatDataAsDegrees($input)
{
return $input . '°';
}
protected function formatDataAsGeneric($input)
{
return $input . $this->data_format_generic;
}
public function setLine($bool)
{
if (is_bool($bool)) {
$this->bool_line = $bool;
} else {
$this->error[] = "Boolean arg for setLine() not specified properly.";
}
}
public function setGoalLine($yValue, $color = null, $style = 'solid')
{
if (is_numeric($yValue)) {
if ($color) {
$this->setGenericColor($color, '$this->goal_line_custom_color', "Goal line color not specified properly.");
$color = $this->goal_line_custom_color;
}
$this->goal_line_array[] = array(
'yValue' => $yValue,
'color' => $color,
'style' => $style
);
if($yValue > $this->data_max) {
$this->data_range_max = $yValue;
$this->bool_user_data_range = true;
}
} else {
$this->error[] = "Goal line Y axis value not specified properly.";
}
}
protected function allocateColors()
{
$this->background_color = imagecolorallocate($this->image, 255, 255, 255);
$this->grid_color = imagecolorallocate($this->image, 220, 220, 220);
$this->bar_color = imagecolorallocate($this->image, 200, 200, 200);
$this->line_color_default = imagecolorallocate($this->image, 100, 100, 100);
$this->x_axis_text_color = $this->line_color_default;
$this->y_axis_text_color = $this->line_color_default;
$this->data_value_color = $this->line_color_default;
$this->title_color = imagecolorallocate($this->image, 0, 0, 0);
$this->outline_color = $this->title_color;
$this->data_point_color = $this->title_color;
$this->x_axis_color = $this->title_color;
$this->y_axis_color = $this->title_color;
$this->goal_line_color = $this->title_color;
$this->legend_outline_color = $this->grid_color;
$this->legend_color = $this->background_color;
$this->legend_text_color = $this->line_color_default;
$this->legend_swatch_outline_color = $this->line_color_default;
}
protected function returnColorArray($color)
{
//check to see if color passed through in form '128,128,128' or hex format
if (strpos($color,',') !== false) {
return explode(',', $color);
} elseif (substr($color, 0, 1) == '#') {
if (strlen($color) == 7) {
$hex1 = hexdec(substr($color, 1, 2));
$hex2 = hexdec(substr($color, 3, 2));
$hex3 = hexdec(substr($color, 5, 2));
return array($hex1, $hex2, $hex3);
} elseif (strlen($color) == 4) {
$hex1 = hexdec(substr($color, 1, 1) . substr($color, 1, 1));
$hex2 = hexdec(substr($color, 2, 1) . substr($color, 2, 1));
$hex3 = hexdec(substr($color, 3, 1) . substr($color, 3, 1));
return array($hex1, $hex2, $hex3);
}
}
switch (strtolower($color)) {
//named colors based on W3C's recd html colors
case 'black': return array(0,0,0); break;
case 'silver': return array(192,192,192); break;
case 'gray': return array(128,128,128); break;
case 'white': return array(255,255,255); break;
case 'maroon': return array(128,0,0); break;
case 'red': return array(255,0,0); break;
case 'purple': return array(128,0,128); break;
case 'fuscia': return array(255,0,255); break;
case 'green': return array(0,128,0); break;
case 'lime': return array(0,255,0); break;
case 'olive': return array(128,128,0); break;
case 'yellow': return array(255,255,0); break;
case 'navy': return array(0,0,128); break;
case 'blue': return array(0,0,255); break;
case 'teal': return array(0,128,128); break;
case 'aqua': return array(0,255,255); break;
}
$this->error[] = "Color name \"$color\" not recogized.";
return false;
}
protected function allocateGradientColors($color1R, $color1G, $color1B, $rScale, $gScale, $bScale, $num, $data_set_num)
{
//caluclate the colors used in our gradient and store them in array
$this->gradient_color_array[$data_set_num] = array();
for ($i = 0; $i <= $num + 1; $i++) {
$this->gradient_color_array[$data_set_num][$i] = imagecolorallocate($this->image, $color1R - ($rScale * $i), $color1G - ($gScale * $i), $color1B - ($bScale * $i));
}
$this->bool_gradient_colors_found[$data_set_num] = true;
}
protected function setGenericColor($inputColor, $var, $errorMsg)
{
//can be used for most color setting options
if (!empty($inputColor) && ($arr = $this->returnColorArray($inputColor))) {
eval($var . ' = imagecolorallocate($this->image, $arr[0], $arr[1], $arr[2]);');
return true;
}
else {
$this->error[] = $errorMsg;
return false;
}
}
public function setBackgroundColor($color)
{
if ($this->setGenericColor($color, '$this->background_color', "Background color not specified properly.")) {
$this->bool_background = true;
}
}
public function setTitleColor($color)
{
$this->setGenericColor($color, '$this->title_color', "Title color not specified properly.");
}
public function setTextColor($color)
{
$this->setGenericColor($color, '$this->x_axis_text_color', "X axis text color not specified properly.");
$this->setGenericColor($color, '$this->y_axis_text_color', "Y axis Text color not specified properly.");
}
public function setXAxisTextColor($color)
{
$this->setGenericColor($color, '$this->x_axis_text_color', "X axis text color not specified properly.");
}
public function setYAxisTextColor($color)
{
$this->setGenericColor($color, '$this->y_axis_text_color', "Y axis Text color not specified properly.");
}
public function setBarColor($color1, $color2 = '', $color3 = '', $color4 = '', $color5 = '')
{
$bar_colors = array($color1, $color2, $color3, $color4, $color5);
foreach ($bar_colors as $key => $color) {
if ($color) {
$this->setGenericColor($color, '$this->multi_bar_colors[]', "Bar color " . ($key + 1) . " not specified properly.");
}
}
}
public function setGridColor($color)
{
$this->setGenericColor($color, '$this->grid_color', "Grid color not specified properly.");
}
public function setBarOutlineColor($color)
{
$this->setGenericColor($color, '$this->outline_color', "Bar outline color not specified properly.");
}
public function setDataPointColor($color)
{
$this->setGenericColor($color, '$this->data_point_color', "Data point color not specified properly.");
}
public function setDataValueColor($color)
{
$this->setGenericColor($color, '$this->data_value_color', "Data value color not specified properly.");
}
public function setLineColor($color1, $color2 = '', $color3 = '', $color4 = '', $color5 = '')
{
$line_colors = array($color1, $color2, $color3, $color4, $color5);
foreach ($line_colors as $key => $color) {
if ($color) {
$this->setGenericColor($color, '$this->line_color[]', "Line color " . ($key + 1) . " not specified properly.");
}
}
}
/* @deprecated - setGoalLineColor() replaced with 2nd parameter of setGoalLine() */
public function setGoalLineColor($color) {
$this->setGenericColor($color, '$this->goal_line_color', "Goal line color not specified properly.");
}
public function setGradient($color1, $color2, $color3 = '', $color4 = '', $color5 = '', $color6 = '', $color7 = '', $color8 = '', $color9 = '', $color10 = '')
{
if (!empty($color1) && !empty($color2) && ($arr1 = $this->returnColorArray($color1)) && ($arr2 = $this->returnColorArray($color2))) {
$this->bool_gradient = true;
$this->multi_gradient_colors_1[] = $arr1;
$this->multi_gradient_colors_2[] = $arr2;
}
else {
$this->error[] = "Gradient color(s) not specified properly.";
}
//Optional gradients
if (!empty($color3) && !empty($color4) && ($arr1 = $this->returnColorArray($color3)) && ($arr2 = $this->returnColorArray($color4))) {
$this->bool_gradient = true;
$this->multi_gradient_colors_1[]=$arr1;
$this->multi_gradient_colors_2[]=$arr2;
}
if (!empty($color5) && !empty($color6) && ($arr1 = $this->returnColorArray($color5)) && ($arr2 = $this->returnColorArray($color6))) {
$this->bool_gradient = true;
$this->multi_gradient_colors_1[] = $arr1;
$this->multi_gradient_colors_2[] = $arr2;
}
if (!empty($color7) && !empty($color8) && ($arr1 = $this->returnColorArray($color7)) && ($arr2 = $this->returnColorArray($color8))) {
$this->bool_gradient = true;
$this->multi_gradient_colors_1[] = $arr1;
$this->multi_gradient_colors_2[] = $arr2;
}
if (!empty($color9) && !empty($color10) && ($arr1 = $this->returnColorArray($color9)) && ($arr2 = $this->returnColorArray($color10))) {
$this->bool_gradient = true;
$this->multi_gradient_colors_1[] = $arr1;
$this->multi_gradient_colors_2[] = $arr2;
}
}
//Legend Related Functions
public function setLegend($bool)
{
if (is_bool($bool)) {
$this->bool_legend = $bool;
} else {
$this->error[] = "Boolean arg for setLegend() not specified properly.";
}
}
public function setLegendColor($color)
{
$this->setGenericColor($color, '$this->legend_color', "Legend color not specified properly.");
}
public function setLegendTextColor($color)
{
$this->setGenericColor($color, '$this->legend_text_color', "Legend text color not specified properly.");
}
public function setLegendOutlineColor($color)
{
$this->setGenericColor($color, '$this->legend_outline_color', "Legend outline color not specified properly.");
}
public function setSwatchOutlineColor($color)
{
$this->setGenericColor($color, '$this->legend_swatch_outline_color', "Swatch outline color not specified properly.");
}
public function setLegendTitle($title1, $title2 = '', $title3 = '', $title4 = '', $title5 = '')
{
$title_array = array($title1, $title2, $title3, $title4, $title5);
foreach ($title_array as $title) {
if ($len = strlen($title)) {
if ($len > self::LEGEND_MAX_CHARS) {
$title = substr($title, 0, self::LEGEND_MAX_CHARS);
$this->legend_total_chars[] = self::LEGEND_MAX_CHARS;
} else {
$this->legend_total_chars[] = $len;
}
$this->legend_titles[] = $title;
}
}
}
protected function generateLegend()
{
$swatchToTextOffset = (self::LEGEND_TEXT_HEIGHT - 6) / 2;
$swatchSize = self::LEGEND_TEXT_HEIGHT - (2 * $swatchToTextOffset);
//calc height / width based on # of data sets
$this->legend_height = self::LEGEND_TEXT_HEIGHT + (2 * self::LEGEND_PADDING);
$totalChars = 0;
for ($i = 0; $i < $this->data_set_count; $i++) {
//could have more titles than data sets - check for this
if (isset($this->legend_total_chars[$i])){ $totalChars += $this->legend_total_chars[$i]; }
}
$this->legend_width = $totalChars * self::LEGEND_TEXT_WIDTH + (self::LEGEND_PADDING * 2.2) +
($this->data_set_count * ($swatchSize + (self::LEGEND_PADDING * 2)));
$this->legend_x = $this->x_axis_x2 - $this->legend_width;
$highestElement = ($this->top_margin < $this->y_axis_y2) ? $this->top_margin : $this->y_axis_y2;
$this->legend_y = ($highestElement / 2) - ($this->legend_height / 2); //centered
//draw background
imagefilledrectangle($this->image, $this->legend_x, $this->legend_y, $this->legend_x + $this->legend_width,
$this->legend_y + $this->legend_height, $this->legend_color);
//draw border
imagerectangle($this->image, $this->legend_x, $this->legend_y, $this->legend_x + $this->legend_width,
$this->legend_y + $this->legend_height, $this->legend_outline_color);
$length_covered = 0;
for ($i = 0; $i < $this->data_set_count; $i++) {
$data_label = '';
if (isset($this->legend_titles[$i])) {
$data_label = $this->legend_titles[$i];
}
$yValue = $this->legend_y + self::LEGEND_PADDING;
$xValue = $this->legend_x + self::LEGEND_PADDING + ($length_covered * self::LEGEND_TEXT_WIDTH) + ($i * 4 * self::LEGEND_PADDING);
$length_covered += strlen($data_label);
//draw color boxes
if ($this->bool_bars) {
if ($this->bool_gradient) {
$color = $this->gradient_color_array[$this->data_set_count - $i - 1][0];
} else {
$color = $this->multi_bar_colors[$this->data_set_count - $i - 1];
}
} elseif ($this->bool_line && !$this->bool_bars) {
$color = $this->line_color[$this->data_set_count - $i - 1];
}
imagefilledrectangle($this->image, $xValue, $yValue + $swatchToTextOffset, $xValue + $swatchSize, $yValue + $swatchToTextOffset + $swatchSize, $color);
imagerectangle($this->image, $xValue, $yValue + $swatchToTextOffset, $xValue + $swatchSize, $yValue + $swatchToTextOffset + $swatchSize, $this->legend_swatch_outline_color);
imagestring($this->image, 2, $xValue + (2 * self::LEGEND_PADDING + 2), $yValue, $data_label, $this->legend_text_color);
}
}
public function setIgnoreDataFitErrors($bool)
{
if (is_bool($bool)) {
$this->bool_ignore_data_fit_errors = $bool;
} else {
$this->error[] = "Boolean arg for setIgnoreDataFitErrors() not specified properly.";
}
}
}