387 lines
9.9 KiB
PHP
387 lines
9.9 KiB
PHP
<?php
|
|
/**
|
|
* Piwik - free/libre analytics platform
|
|
*
|
|
* @link http://piwik.org
|
|
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
|
*
|
|
*/
|
|
|
|
namespace Piwik\Plugins\ImageGraph;
|
|
|
|
use CpChart\Chart\Data;
|
|
use CpChart\Chart\Image;
|
|
use Piwik\Container\StaticContainer;
|
|
use Piwik\NumberFormatter;
|
|
use Piwik\Piwik;
|
|
use Piwik\BaseFactory;
|
|
|
|
/**
|
|
* The StaticGraph abstract class is used as a base class for different types of static graphs.
|
|
*
|
|
*/
|
|
abstract class StaticGraph extends BaseFactory
|
|
{
|
|
const GRAPH_TYPE_BASIC_LINE = "evolution";
|
|
const GRAPH_TYPE_VERTICAL_BAR = "verticalBar";
|
|
const GRAPH_TYPE_HORIZONTAL_BAR = "horizontalBar";
|
|
const GRAPH_TYPE_3D_PIE = "3dPie";
|
|
const GRAPH_TYPE_BASIC_PIE = "pie";
|
|
|
|
private static $availableStaticGraphTypes = array(
|
|
self::GRAPH_TYPE_BASIC_LINE => 'Evolution',
|
|
self::GRAPH_TYPE_VERTICAL_BAR => 'VerticalBar',
|
|
self::GRAPH_TYPE_HORIZONTAL_BAR => 'HorizontalBar',
|
|
self::GRAPH_TYPE_BASIC_PIE => 'Pie',
|
|
self::GRAPH_TYPE_3D_PIE => 'Pie3D',
|
|
);
|
|
|
|
const ABSCISSA_SERIE_NAME = 'ABSCISSA';
|
|
|
|
private $aliasedGraph;
|
|
|
|
/**
|
|
* @var Image
|
|
*/
|
|
protected $pImage;
|
|
/**
|
|
* @var Data
|
|
*/
|
|
protected $pData;
|
|
protected $ordinateLabels;
|
|
protected $showLegend;
|
|
protected $abscissaSeries;
|
|
protected $abscissaLogos;
|
|
protected $ordinateSeries;
|
|
protected $ordinateLogos;
|
|
protected $colors;
|
|
protected $font;
|
|
protected $fontSize;
|
|
protected $textColor = [];
|
|
protected $backgroundColor = [];
|
|
protected $gridColor = [];
|
|
protected $legendFontSize;
|
|
protected $width;
|
|
protected $height;
|
|
protected $forceSkippedLabels = false;
|
|
|
|
abstract protected function getDefaultColors();
|
|
|
|
abstract public function renderGraph();
|
|
|
|
protected static function getClassNameFromClassId($graphType)
|
|
{
|
|
$className = self::$availableStaticGraphTypes[$graphType];
|
|
$className = __NAMESPACE__ . "\\StaticGraph\\" . $className;
|
|
return $className;
|
|
}
|
|
|
|
protected static function getInvalidClassIdExceptionMessage($graphType)
|
|
{
|
|
return Piwik::translate(
|
|
'General_ExceptionInvalidStaticGraphType',
|
|
array($graphType, implode(', ', self::getAvailableStaticGraphTypes()))
|
|
);
|
|
}
|
|
|
|
public static function getAvailableStaticGraphTypes()
|
|
{
|
|
return array_keys(self::$availableStaticGraphTypes);
|
|
}
|
|
|
|
/**
|
|
* Save rendering to disk
|
|
*
|
|
* @param string $filename without path
|
|
* @return string path of file
|
|
*/
|
|
public function sendToDisk($filename)
|
|
{
|
|
$filePath = self::getOutputPath($filename);
|
|
$this->pImage->render($filePath);
|
|
return $filePath;
|
|
}
|
|
|
|
/**
|
|
* @return resource rendered static graph
|
|
*/
|
|
public function getRenderedImage()
|
|
{
|
|
return $this->pImage->Picture;
|
|
}
|
|
|
|
/**
|
|
* Output rendering to browser
|
|
*/
|
|
public function sendToBrowser()
|
|
{
|
|
$this->pImage->stroke();
|
|
}
|
|
|
|
public function setWidth($width)
|
|
{
|
|
$this->width = $width;
|
|
}
|
|
|
|
public function setHeight($height)
|
|
{
|
|
$this->height = $height;
|
|
}
|
|
|
|
public function setFontSize($fontSize)
|
|
{
|
|
if (!is_numeric($fontSize)) {
|
|
$fontSize = API::DEFAULT_FONT_SIZE;
|
|
}
|
|
$this->fontSize = $fontSize;
|
|
}
|
|
|
|
public function setLegendFontSize($legendFontSize)
|
|
{
|
|
$this->legendFontSize = $legendFontSize;
|
|
}
|
|
|
|
public function setFont($font)
|
|
{
|
|
$this->font = $font;
|
|
}
|
|
|
|
public function setTextColor($textColor)
|
|
{
|
|
$this->textColor = self::hex2rgb($textColor);
|
|
}
|
|
|
|
public function setBackgroundColor($backgroundColor)
|
|
{
|
|
$this->backgroundColor = self::hex2rgb($backgroundColor);
|
|
}
|
|
|
|
public function setGridColor($gridColor)
|
|
{
|
|
$this->gridColor = self::hex2rgb($gridColor);
|
|
}
|
|
|
|
public function setOrdinateSeries($ordinateSeries)
|
|
{
|
|
$this->ordinateSeries = $ordinateSeries;
|
|
}
|
|
|
|
public function setOrdinateLogos($ordinateLogos)
|
|
{
|
|
$this->ordinateLogos = $ordinateLogos;
|
|
}
|
|
|
|
public function setAbscissaLogos($abscissaLogos)
|
|
{
|
|
$this->abscissaLogos = $abscissaLogos;
|
|
}
|
|
|
|
public function setAbscissaSeries($abscissaSeries)
|
|
{
|
|
$this->abscissaSeries = $abscissaSeries;
|
|
}
|
|
|
|
public function setShowLegend($showLegend)
|
|
{
|
|
$this->showLegend = $showLegend;
|
|
}
|
|
|
|
public function setForceSkippedLabels($forceSkippedLabels)
|
|
{
|
|
$this->forceSkippedLabels = $forceSkippedLabels;
|
|
}
|
|
|
|
public function setOrdinateLabels($ordinateLabels)
|
|
{
|
|
$this->ordinateLabels = $ordinateLabels;
|
|
}
|
|
|
|
public function setAliasedGraph($aliasedGraph)
|
|
{
|
|
$this->aliasedGraph = $aliasedGraph;
|
|
}
|
|
|
|
public function setColors($colors)
|
|
{
|
|
$i = 0;
|
|
foreach ($this->getDefaultColors() as $colorKey => $defaultColor) {
|
|
if (isset($colors[$i]) && $this->hex2rgb($colors[$i])) {
|
|
$hexColor = $colors[$i];
|
|
} else {
|
|
$hexColor = $defaultColor;
|
|
}
|
|
|
|
$this->colors[$colorKey] = $this->hex2rgb($hexColor);
|
|
$i++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return $filename with temp directory and delete file
|
|
*
|
|
* @static
|
|
* @param $filename
|
|
* @return string path of file in temp directory
|
|
*/
|
|
protected static function getOutputPath($filename)
|
|
{
|
|
$outputFilename = StaticContainer::get('path.tmp') . '/assets/' . $filename;
|
|
|
|
@chmod($outputFilename, 0600);
|
|
@unlink($outputFilename);
|
|
return $outputFilename;
|
|
}
|
|
|
|
protected function initpData()
|
|
{
|
|
$this->pData = new Data();
|
|
|
|
foreach ($this->ordinateSeries as $column => $data) {
|
|
$this->pData->addPoints($data, $column);
|
|
$this->pData->setSerieDescription($column, $this->ordinateLabels[$column]);
|
|
|
|
if (isset($this->ordinateLogos[$column])) {
|
|
$ordinateLogo = $this->createResizedImageCopyIfNeeded($this->ordinateLogos[$column]);
|
|
$this->pData->setSeriePicture($column, $ordinateLogo);
|
|
}
|
|
}
|
|
|
|
$this->pData->setAxisDisplay(0, AXIS_FORMAT_CUSTOM, '\\Piwik\\Plugins\\ImageGraph\\formatYAxis');
|
|
|
|
$this->pData->addPoints($this->abscissaSeries, self::ABSCISSA_SERIE_NAME);
|
|
$this->pData->setAbscissa(self::ABSCISSA_SERIE_NAME);
|
|
}
|
|
|
|
protected function createResizedImageCopyIfNeeded($image)
|
|
{
|
|
$size = getimagesize($image);
|
|
|
|
if ($size[0] <= 16 && $size[1] <= 16) {
|
|
return $image; // use original image if size fits
|
|
}
|
|
|
|
$ratio = $size[0] / $size[1];
|
|
if ($ratio > 1) {
|
|
$width = 16;
|
|
$height = 16 / $ratio;
|
|
} else {
|
|
$width = 16 * $ratio;
|
|
$height = 16;
|
|
}
|
|
|
|
$newImage = self::getOutputPath(md5($image) . '.png');
|
|
|
|
$src = imagecreatefromstring(file_get_contents($image));
|
|
$dst = imagecreatetruecolor($width, $height);
|
|
imagesavealpha($dst, true);
|
|
$color = imagecolorallocatealpha($dst, 0, 0, 0, 127);
|
|
imagefill($dst, 0, 0, $color);
|
|
imagecopyresampled($dst, $src, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
|
|
imagedestroy($src);
|
|
imagepng($dst, $newImage);
|
|
imagedestroy($dst);
|
|
|
|
return $newImage;
|
|
}
|
|
|
|
protected function initpImage()
|
|
{
|
|
$this->pImage = new Image($this->width, $this->height, $this->pData);
|
|
$this->pImage->Antialias = $this->aliasedGraph;
|
|
|
|
$this->pImage->setFontProperties(
|
|
array_merge(
|
|
array(
|
|
'FontName' => $this->font,
|
|
'FontSize' => $this->fontSize,
|
|
),
|
|
$this->textColor
|
|
)
|
|
);
|
|
}
|
|
|
|
protected function getTextWidthHeight($text, $fontSize = false)
|
|
{
|
|
if (!$fontSize) {
|
|
$fontSize = $this->fontSize;
|
|
}
|
|
|
|
if (!$this->pImage) {
|
|
$this->initpImage();
|
|
}
|
|
|
|
// could not find a way to get pixel perfect width & height info using imageftbbox
|
|
$textInfo = $this->pImage->drawText(
|
|
0, 0, $text,
|
|
array(
|
|
'Alpha' => 0,
|
|
'FontSize' => $fontSize,
|
|
'FontName' => $this->font
|
|
)
|
|
);
|
|
|
|
return array($textInfo[1]['X'] + 1, $textInfo[0]['Y'] - $textInfo[2]['Y']);
|
|
}
|
|
|
|
protected function getMaximumTextWidthHeight($values)
|
|
{
|
|
if (array_values($values) === $values) {
|
|
$values = array('' => $values);
|
|
}
|
|
|
|
$maxWidth = 0;
|
|
$maxHeight = 0;
|
|
foreach ($values as $data) {
|
|
foreach ($data as $value) {
|
|
list($valueWidth, $valueHeight) = $this->getTextWidthHeight($value);
|
|
|
|
if ($valueWidth > $maxWidth) {
|
|
$maxWidth = $valueWidth;
|
|
}
|
|
|
|
if ($valueHeight > $maxHeight) {
|
|
$maxHeight = $valueHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
return array($maxWidth, $maxHeight);
|
|
}
|
|
|
|
protected function drawBackground()
|
|
{
|
|
$this->pImage->drawFilledRectangle(
|
|
0,
|
|
0,
|
|
$this->width,
|
|
$this->height,
|
|
array_merge(array('Alpha' => 100), $this->backgroundColor)
|
|
);
|
|
}
|
|
|
|
private static function hex2rgb($hexColor)
|
|
{
|
|
if (preg_match('/([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/', $hexColor, $matches)) {
|
|
return array(
|
|
'R' => hexdec($matches[1]),
|
|
'G' => hexdec($matches[2]),
|
|
'B' => hexdec($matches[3])
|
|
);
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Global format method
|
|
*
|
|
* required to format y axis values using CpChart internal format callbacks
|
|
* @param $value
|
|
* @return mixed
|
|
*/
|
|
function formatYAxis($value)
|
|
{
|
|
return NumberFormatter::getInstance()->format($value);
|
|
}
|