* http://sparkline.org * * Dual-licensed under the BSD (LICENSE-BSD.txt) and GPL (LICENSE-GPL.txt) * licenses. * * $Id: Sparkline.php,v 1.1 2008-09-05 22:13:12 chris Exp $ * */ define('TEXT_TOP', 1); define('TEXT_RIGHT', 2); define('TEXT_BOTTOM', 3); define('TEXT_LEFT', 4); define('FONT_1', 1); define('FONT_2', 2); define('FONT_3', 3); define('FONT_4', 4); define('FONT_5', 5); require_once('Object.php'); class Sparkline extends Object { var $imageX; var $imageY; var $imageHandle; var $graphAreaPx; var $graphAreaPt; var $colorList; var $colorBackground; var $lineSize; //////////////////////////////////////////////////////////////////////////// // constructor // function Sparkline($catch_errors = true) { parent::Object($catch_errors); $this->colorList = array(); $this->colorBackground = 'white'; $this->lineSize = 1; $this->graphAreaPx = array(array(0, 0), array(0, 0)); // px(L, B), px(R, T) } //////////////////////////////////////////////////////////////////////////// // init // function Init($x, $y) { $this->Debug("Sparkline :: Init($x, $y)", DEBUG_CALLS); $this->imageX = $x; $this->imageY = $y; // Set functions may have already set graphAreaPx offsets; add image dimensions // $this->graphAreaPx = array(array($this->graphAreaPx[0][0], $this->graphAreaPx[0][1]), array($this->graphAreaPx[1][0] + $x - 1, $this->graphAreaPx[1][1] + $y - 1)); $this->imageHandle = $this->CreateImageHandle($x, $y); // load default colors; set all color handles // $this->SetColorDefaults(); while (list($k, $v) = each($this->colorList)) { $this->SetColorHandle($k, $this->DrawColorAllocate($k, $this->imageHandle)); } reset($this->colorList); if ($this->IsError()) { return false; } else { return true; } } //////////////////////////////////////////////////////////////////////////// // color, drawing setup functions // function SetColor($name, $r, $g, $b) { $this->Debug("Sparkline :: SetColor('$name', $r, $g, $b)", DEBUG_SET); $name = strtolower($name); $this->colorList[$name] = array('rgb' => array($r, $g, $b)); } function SetColorHandle($name, $handle) { $this->Debug("Sparkline :: SetColorHandle('$name', $handle)", DEBUG_SET); $name = strtolower($name); if (array_key_exists($name, $this->colorList)) { $this->colorList[$name]['handle'] = $handle; return true; } else { return false; } } function SetColorHex($name, $r, $g, $b) { $this->Debug("Sparkline :: SetColorHex('$name', $r, $g, $b)", DEBUG_SET); $this->SetColor($name, hexdec($r), hexdec($g), hexdec($b)); } function SetColorHtml($name, $rgb) { $this->Debug("Sparkline :: SetColorHtml('$name', '$rgb')", DEBUG_SET); $rgb = trim($rgb, '#'); $this->SetColor($name, hexdec(substr($rgb, 0, 2)), hexdec(substr($rgb, 2, 2)), hexdec(substr($rgb, 4, 2))); } function SetColorBackground($name) { $this->Debug("Sparkline :: SetColorBackground('$name')", DEBUG_SET); $this->colorBackground = $name; } function GetColor($name) { if (array_key_exists($name, $this->colorList)) { return $this->colorList[$name]['rgb']; } else { return false; } } function GetColorHandle($name) { $name = strtolower($name); if (array_key_exists($name, $this->colorList)) { return $this->colorList[$name]['handle']; } else { $this->Debug("Sparkline :: GetColorHandle color '$name' not set", DEBUG_WARNING); return false; } } function SetColorDefaults() { $this->Debug("Sparkline :: SetColorDefaults()", DEBUG_SET); $colorDefaults = array(array('aqua', '#00FFFF'), array('black', '#010101'), // TODO failure if 000000? array('blue', '#0000FF'), array('fuscia', '#FF00FF'), array('gray', '#808080'), array('grey', '#808080'), array('green', '#008000'), array('lime', '#00FF00'), array('maroon', '#800000'), array('navy', '#000080'), array('olive', '#808000'), array('purple', '#800080'), array('red', '#FF0000'), array('silver', '#C0C0C0'), array('teal', '#008080'), array('white', '#FFFFFF'), array('yellow', '#FFFF00')); while (list(, $v) = each($colorDefaults)) { if (!array_key_exists($v[0], $this->colorList)) { $this->SetColorHtml($v[0], $v[1]); } } } function SetLineSize($size) { $this->Debug("Sparkline :: SetLineSize($size)", DEBUG_CALLS); $this->lineSize = $size; } function GetLineSize() { return($this->lineSize); } function SetPadding($T, $R = null, $B = null, $L = null) { $this->Debug("Sparkline :: SetPadding($T, $R, $B, $L)", DEBUG_CALLS); if (null == $R && null == $B && null == $L) { $this->graphAreaPx = array(array($this->graphAreaPx[0][0] + $T, $this->graphAreaPx[0][1] + $T), array($this->graphAreaPx[1][0] - $T, $this->graphAreaPx[1][1] - $T)); } else { $this->graphAreaPx = array(array($this->graphAreaPx[0][0] + $L, $this->graphAreaPx[0][1] + $B), array($this->graphAreaPx[1][0] - $R, $this->graphAreaPx[1][1] - $T)); } } //////////////////////////////////////////////////////////////////////////// // canvas setup // function CreateImageHandle($x, $y) { $this->Debug("Sparkline :: CreateImageHandle($x, $y)", DEBUG_CALLS); $handle = @imagecreatetruecolor($x, $y); if (!is_resource($handle)) { $handle = imagecreate($x, $y); $this->Debug('imagecreatetruecolor unavailable', DEBUG_WARNING); } if (!is_resource($handle)) { $this->Debug('imagecreate unavailable', DEBUG_WARNING); $this->Error('could not create image; GD imagecreate functions unavailable'); } return $handle; } //////////////////////////////////////////////////////////////////////////// // drawing primitives // // NB: all drawing primitives use the coordinate system where (0,0) // corresponds to the bottom left of the image, unlike y-inverted // PHP gd functions // function DrawBackground($handle = false) { $this->Debug("Sparkline :: DrawBackground()", DEBUG_DRAW); if (!$this->IsError()) { if ($handle === false) $handle = $this->imageHandle; return $this->DrawRectangleFilled(0, 0, imagesx($handle) - 1, imagesy($handle) - 1, $this->colorBackground, $handle); } } function DrawColorAllocate($color, $handle = false) { $this->Debug("Sparkline :: DrawColorAllocate('$color')", DEBUG_DRAW); if (!$this->IsError() && $colorRGB = $this->GetColor($color)) { if ($handle === false) $handle = $this->imageHandle; return imagecolorallocate($handle, $colorRGB[0], $colorRGB[1], $colorRGB[2]); } } function DrawFill($x, $y, $color, $handle = false) { $this->Debug("Sparkline :: DrawFill($x, $y, '$color')", DEBUG_DRAW); if (!$this->IsError() && $colorHandle = $this->GetColorHandle($color)) { if ($handle === false) $handle = $this->imageHandle; return imagefill($handle, $x, $this->TxGDYToSLY($y, $handle), $colorHandle); } } function DrawLine($x1, $y1, $x2, $y2, $color, $thickness = 1, $handle = false) { $this->Debug("Sparkline :: DrawLine($x1, $y1, $x2, $y2, '$color', $thickness)", DEBUG_DRAW); if (!$this->IsError() && $colorHandle = $this->GetColorHandle($color)) { if ($handle === false) $handle = $this->imageHandle; imagesetthickness($handle, $thickness); $result = imageline($handle, $x1, $this->TxGDYToSLY($y1, $handle), $x2, $this->TxGDYToSLY($y2, $handle), $colorHandle); imagesetthickness($handle, 1); return $result; } } function DrawPoint($x, $y, $color, $handle = false) { $this->Debug("Sparkline :: DrawPoint($x, $y, '$color')", DEBUG_DRAW); if (!$this->IsError() && $colorHandle = $this->GetColorHandle($color)) { if ($handle === false) $handle = $this->imageHandle; return imagesetpixel($handle, $x, $this->TxGDYToSLY($y, $handle), $colorHandle); } } function DrawRectangle($x1, $y1, $x2, $y2, $color, $handle = false) { $this->Debug("Sparkline :: DrawRectangle($x1, $y1, $x2, $y2 '$color')", DEBUG_DRAW); if (!$this->IsError() && $colorHandle = $this->GetColorHandle($color)) { if ($handle === false) $handle = $this->imageHandle; return imagerectangle($handle, $x1, $this->TxGDYToSLY($y1, $handle), $x2, $this->TxGDYToSLY($y2, $handle), $colorHandle); } } function DrawRectangleFilled($x1, $y1, $x2, $y2, $color, $handle = false) { $this->Debug("Sparkline :: DrawRectangleFilled($x1, $y1, $x2, $y2 '$color')", DEBUG_DRAW); if (!$this->IsError() && $colorHandle = $this->GetColorHandle($color)) { // NB: switch y1, y2 post conversion // if ($y1 < $y2) { $yt = $y1; $y1 = $y2; $y2 = $yt; } if ($handle === false) $handle = $this->imageHandle; return imagefilledrectangle($handle, $x1, $this->TxGDYToSLY($y1, $handle), $x2, $this->TxGDYToSLY($y2, $handle), $colorHandle); } } function DrawCircleFilled($x, $y, $diameter, $color, $handle = false) { $this->Debug("Sparkline :: DrawCircleFilled($x, $y, $diameter, '$color')", DEBUG_DRAW); if (!$this->IsError() && $colorHandle = $this->GetColorHandle($color)) { if ($handle === false) $handle = $this->imageHandle; return imagefilledellipse($handle, $x, $this->TxGDYToSLY($y, $handle), $diameter, $diameter, $colorHandle); } } function DrawText($string, $x, $y, $color, $font = FONT_1, $handle = false) { $this->Debug("Sparkline :: DrawText('$string', $x, $y, '$color', $font)", DEBUG_DRAW); if (!$this->IsError() && $colorHandle = $this->GetColorHandle($color)) { // adjust for font height so x,y corresponds to bottom left of font // if ($handle === false) $handle = $this->imageHandle; return imagestring($handle, $font, $x, $this->TxGDYToSLY($y + imagefontheight($font), $handle), $string, $colorHandle); } } function DrawTextRelative($string, $x, $y, $color, $position, $padding = 2, $font = FONT_1, $handle = false) { $this->Debug("Sparkline :: DrawTextRelative('$string', $x, $y, '$color', $position, $font, $padding)", DEBUG_DRAW); if (!$this->IsError() && $colorHandle = $this->GetColorHandle($color)) { if ($handle === false) $handle = $this->imageHandle; // rendered text width, height // $textHeight = imagefontheight($font); $textWidth = imagefontwidth($font) * strlen($string); // set (pxX, pxY) based on position and point // switch($position) { case TEXT_TOP: $x = $x - round($textWidth / 2); $y = $y + $padding; break; case TEXT_RIGHT: $x = $x + $padding; $y = $y - round($textHeight / 2); break; case TEXT_BOTTOM: $x = $x - round($textWidth / 2); $y = $y - $padding - $textHeight; break; case TEXT_LEFT: default: $x = $x - $padding - $textWidth; $y = $y - round($textHeight / 2); break; } // truncate bounds based on string size in pixels, image bounds // order: TRBL // $y = min($y, $this->GetImageHeight() - $textHeight); $x = min($x, $this->GetImageWidth() - $textWidth); $y = max($y, 0); $x = max($x, 0); return $this->DrawText($string, $x, $y, $color, $font, $handle); } } function DrawImageCopyResampled($dhandle, $shandle, $dx, $dy, $sx, $sy, $dw, $dh, $sw, $sh) { $this->Debug("Sparkline :: DrawImageCopyResampled($dhhandle, $shandle, $dx, $dy, $sx, $sy, $dw, $dh, $sw, $sh)", DEBUG_DRAW); if (!$this->IsError()) { return imagecopyresampled($dhandle, // dest handle $shandle, // src handle $dx, $dy, // dest x, y $sx, $sy, // src x, y $dw, $dh, // dest w, h $sw, $sh); // src w, h } } //////////////////////////////////////////////////////////////////////////// // coordinate system functions // world coordinates are referenced as points or pt // graph coordinates are referenced as pixels or px // sparkline inverts GD Y pixel coordinates; the bottom left of the // image rendering area is px(0,0) // all coordinate transformation functions are prefixed with Tx // all coordinate transformation functions depend on a valid image handle // and will only return valid results after all Set* calls are performed // function TxGDYToSLY($gdY, $handle) { return imagesy($handle) - 1 - $gdY; } function TxPxToPt($pxX, $pxY, $handle) { // TODO; must occur after data series conversion } function TxPtToPx($ptX, $ptY, $handle) { // TODO; must occur after data series conversion } function GetGraphWidth() { return $this->graphAreaPx[1][0] - $this->graphAreaPx[0][0]; } function GetGraphHeight() { return $this->graphAreaPx[1][1] - $this->graphAreaPx[0][1]; } function GetImageWidth() { return $this->imageX; } function GetImageHeight() { return $this->imageY; } //////////////////////////////////////////////////////////////////////////// // image output // function Output($file = '') { $this->Debug("Sparkline :: Output($file)", DEBUG_CALLS); if ($this->IsError()) { $colorError = imagecolorallocate($this->imageHandle, 0xFF, 0x00, 0x00); imagestring($this->imageHandle, 1, ($this->imageX / 2) - (5 * imagefontwidth(1) / 2), ($this->imageY / 2) - (imagefontheight(1) / 2), "ERROR", $colorError); } if ($file == '') { header('Content-type: image/png'); imagepng($this->imageHandle); } else { imagepng($this->imageHandle, $file); } $this->Debug('Sparkline :: Output - total execution time: ' . round($this->microTimer() - $this->startTime, 4) . ' seconds', DEBUG_STATS); } function OutputToFile($file) { $this->Output($file); } function OutputToDataURI() { ob_start(null, 4096); $this->Output(); return "data:image/png;base64,".base64_encode(ob_get_clean()); } } ?>