| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\Plugins\ImageGraph\StaticGraph; |
|
|
| use Piwik\Exception\InvalidDimensionException; |
| use Piwik\Plugins\ImageGraph\StaticGraph; |
|
|
| abstract class GridGraph extends StaticGraph |
| { |
| public const GRAPHIC_COLOR_KEY = 'GRAPHIC_COLOR'; |
|
|
| public const TRUNCATION_TEXT = '...'; |
|
|
| public const DEFAULT_TICK_ALPHA = 20; |
| public const DEFAULT_SERIE_WEIGHT = 0.5; |
| public const LEFT_GRID_MARGIN = 20; |
| public const BOTTOM_GRID_MARGIN = 10; |
| public const TOP_GRID_MARGIN_HORIZONTAL_GRAPH = 10; |
| public const RIGHT_GRID_MARGIN_HORIZONTAL_GRAPH = 20; |
| public const OUTER_TICK_WIDTH = 5; |
| public const INNER_TICK_WIDTH = 0; |
| public const LABEL_SPACE_VERTICAL_GRAPH = 7; |
|
|
| public const HORIZONTAL_LEGEND_TOP_MARGIN = 5; |
| public const HORIZONTAL_LEGEND_LEFT_MARGIN = 10; |
| public const HORIZONTAL_LEGEND_BOTTOM_MARGIN = 10; |
| public const VERTICAL_LEGEND_TOP_MARGIN = 10; |
| public const VERTICAL_LEGEND_LEFT_MARGIN = 6; |
| public const VERTICAL_LEGEND_MAX_WIDTH_PCT = 0.70; |
| public const LEGEND_LINE_BULLET_WIDTH = 14; |
| public const LEGEND_BOX_BULLET_WIDTH = 5; |
| public const LEGEND_BULLET_RIGHT_PADDING = 5; |
| public const LEGEND_ITEM_HORIZONTAL_INTERSTICE = 6; |
| public const LEGEND_ITEM_VERTICAL_INTERSTICE_OFFSET = 4; |
| public const LEGEND_SHADOW_OPACITY = 25; |
| public const LEGEND_VERTICAL_SHADOW_PADDING = 3; |
| public const LEGEND_HORIZONTAL_SHADOW_PADDING = 2; |
| public const PCHART_HARD_CODED_VERTICAL_LEGEND_INTERSTICE = 5; |
|
|
| protected function getDefaultColors() |
| { |
| return array( |
| self::GRAPHIC_COLOR_KEY . '1' => '5170AE', |
| self::GRAPHIC_COLOR_KEY . '2' => 'F29007', |
| self::GRAPHIC_COLOR_KEY . '3' => 'CC3399', |
| self::GRAPHIC_COLOR_KEY . '4' => '9933CC', |
| self::GRAPHIC_COLOR_KEY . '5' => '80A033', |
| self::GRAPHIC_COLOR_KEY . '6' => '246AD2', |
| ); |
| } |
|
|
| protected function initGridChart( |
| $displayVerticalGridLines, |
| $bulletType, |
| $horizontalGraph, |
| $showTicks, |
| $verticalLegend |
| ) { |
| $this->initpData(); |
|
|
| $colorIndex = 1; |
| foreach ($this->ordinateSeries as $column => $data) { |
| $this->pData->setSerieWeight($column, self::DEFAULT_SERIE_WEIGHT); |
| $graphicColor = $this->colors[self::GRAPHIC_COLOR_KEY . $colorIndex++]; |
| $this->pData->setPalette($column, $graphicColor); |
| } |
|
|
| $this->initpImage(); |
|
|
| |
| $topLeftXValue = $this->getGridLeftMargin($horizontalGraph, $withLabel = true); |
| $topLeftYValue = $this->getGridTopMargin($horizontalGraph, $verticalLegend); |
| $bottomRightXValue = $this->width - $this->getGridRightMargin($horizontalGraph); |
| $bottomRightYValue = $this->getGraphBottom($horizontalGraph); |
|
|
| $this->drawBackground(); |
|
|
| $this->pImage->setGraphArea( |
| $topLeftXValue, |
| $topLeftYValue, |
| $bottomRightXValue, |
| $bottomRightYValue |
| ); |
|
|
| |
| $skippedLabels = 0; |
| if (!$horizontalGraph) { |
| list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries); |
| $graphWidth = $bottomRightXValue - $topLeftXValue; |
| $maxNumOfLabels = floor($graphWidth / ($abscissaMaxWidth + self::LABEL_SPACE_VERTICAL_GRAPH)); |
|
|
| $abscissaSeriesCount = count($this->abscissaSeries); |
| if ($maxNumOfLabels < $abscissaSeriesCount) { |
| for ($candidateSkippedLabels = 1; $candidateSkippedLabels < $abscissaSeriesCount; $candidateSkippedLabels++) { |
| $numberOfSegments = $abscissaSeriesCount / ($candidateSkippedLabels + 1); |
| $numberOfCompleteSegments = floor($numberOfSegments); |
|
|
| $numberOfLabels = $numberOfCompleteSegments; |
| if ($numberOfSegments > $numberOfCompleteSegments) { |
| $numberOfLabels++; |
| } |
|
|
| if ($numberOfLabels <= $maxNumOfLabels) { |
| $skippedLabels = $candidateSkippedLabels; |
| break; |
| } |
| } |
| } |
|
|
| if ( |
| $this->forceSkippedLabels |
| && $skippedLabels |
| && $skippedLabels < $this->forceSkippedLabels |
| && $abscissaSeriesCount > $this->forceSkippedLabels + 1 |
| ) { |
| $skippedLabels = $this->forceSkippedLabels; |
| } |
| } |
|
|
| $ordinateAxisLength = |
| $horizontalGraph ? $bottomRightXValue - $topLeftXValue : $this->getGraphHeight($horizontalGraph, $verticalLegend); |
|
|
| $maxOrdinateValue = 0; |
| foreach ($this->ordinateSeries as $column => $data) { |
| $currentMax = $this->pData->getMax($column); |
|
|
| if ($currentMax > $maxOrdinateValue) { |
| $maxOrdinateValue = ceil($currentMax); |
| } |
| } |
|
|
| |
| if ($maxOrdinateValue > 10) { |
| $modTen = $maxOrdinateValue % 10; |
| if ($modTen) { |
| $maxOrdinateValue += 10 - $modTen; |
| } |
| } |
|
|
| if ($ordinateAxisLength <= 0 || $bottomRightYValue - $topLeftYValue <= 0) { |
| throw new InvalidDimensionException('Error: the graph dimension is not valid. Please try larger width and height values or use 0 for default values.'); |
| } |
|
|
| $gridColor = $this->gridColor; |
| $this->pImage->drawScale( |
| array( |
| 'Mode' => SCALE_MODE_MANUAL, |
| 'GridTicks' => 0, |
| 'LabelSkip' => $skippedLabels, |
| 'DrawXLines' => $displayVerticalGridLines, |
| 'Factors' => array(ceil($maxOrdinateValue / 2)), |
| 'MinDivHeight' => $ordinateAxisLength / 2, |
| 'AxisAlpha' => 0, |
| 'SkippedAxisAlpha' => 0, |
| 'TickAlpha' => $showTicks ? self::DEFAULT_TICK_ALPHA : 0, |
| 'InnerTickWidth' => self::INNER_TICK_WIDTH, |
| 'OuterTickWidth' => self::OUTER_TICK_WIDTH, |
| 'GridR' => $gridColor['R'], |
| 'GridG' => $gridColor['G'], |
| 'GridB' => $gridColor['B'], |
| 'GridAlpha' => 100, |
| 'ManualScale' => array( |
| 0 => array( |
| 'Min' => 0, |
| 'Max' => $maxOrdinateValue, |
| ), |
| ), |
| 'Pos' => $horizontalGraph ? SCALE_POS_TOPBOTTOM : SCALE_POS_LEFTRIGHT, |
| ) |
| ); |
|
|
| if ($this->showLegend) { |
| switch ($bulletType) { |
| case LEGEND_FAMILY_LINE: |
| $bulletWidth = self::LEGEND_LINE_BULLET_WIDTH; |
|
|
| |
| $iconOffsetAboveLabelSymmetryAxis = -2; |
| break; |
|
|
| case LEGEND_FAMILY_BOX: |
| $bulletWidth = self::LEGEND_BOX_BULLET_WIDTH; |
|
|
| |
| $iconOffsetAboveLabelSymmetryAxis = 3; |
| break; |
| } |
|
|
| |
| |
| $legendTopLeftXValue = $topLeftXValue + ($verticalLegend ? self::VERTICAL_LEGEND_LEFT_MARGIN : self::HORIZONTAL_LEGEND_LEFT_MARGIN); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| list($maxLogoWidth, $maxLogoHeight) = self::getMaxLogoSize(array_values($this->ordinateLogos)); |
| if ($maxLogoHeight >= $this->legendFontSize) { |
| $heightOfTextAboveBulletTop = 0; |
| $paddingCreatedByLogo = $maxLogoHeight - $this->legendFontSize; |
| $effectiveShadowPadding = $paddingCreatedByLogo < self::LEGEND_VERTICAL_SHADOW_PADDING * 2 ? self::LEGEND_VERTICAL_SHADOW_PADDING - ($paddingCreatedByLogo / 2) : 0; |
| } else { |
| if ($maxLogoHeight) { |
| |
| $iconOffsetAboveLabelSymmetryAxis = 5; |
| } |
| $heightOfTextAboveBulletTop = $this->legendFontSize / 2 - $iconOffsetAboveLabelSymmetryAxis; |
| $effectiveShadowPadding = self::LEGEND_VERTICAL_SHADOW_PADDING; |
| } |
|
|
| $effectiveLegendItemVerticalInterstice = $this->legendFontSize + self::LEGEND_ITEM_VERTICAL_INTERSTICE_OFFSET; |
| $effectiveLegendItemHorizontalInterstice = self::LEGEND_ITEM_HORIZONTAL_INTERSTICE + self::LEGEND_HORIZONTAL_SHADOW_PADDING; |
|
|
| $legendTopMargin = $verticalLegend ? self::VERTICAL_LEGEND_TOP_MARGIN : self::HORIZONTAL_LEGEND_TOP_MARGIN; |
| $requiredPaddingAboveItemBullet = $legendTopMargin + $heightOfTextAboveBulletTop + $effectiveShadowPadding; |
|
|
| $paddingAddedByPChart = 0; |
| if ($verticalLegend) { |
| if ($maxLogoHeight) { |
| |
| if ($maxLogoHeight < $effectiveLegendItemVerticalInterstice) { |
| $paddingAddedByPChart = ($effectiveLegendItemVerticalInterstice / 2) - ($maxLogoHeight / 2); |
| } |
| } else { |
| |
| $paddingAddedByPChart = $effectiveLegendItemVerticalInterstice / 2; |
| } |
| } |
|
|
| $legendTopLeftYValue = $paddingAddedByPChart < $requiredPaddingAboveItemBullet ? $requiredPaddingAboveItemBullet - $paddingAddedByPChart : 0; |
|
|
| |
| if (count($this->ordinateLabels) > 1) { |
| $currentPosition = $verticalLegend ? $legendTopMargin : $legendTopLeftXValue; |
| $colorIndex = 1; |
| foreach ($this->ordinateLabels as $metricCode => &$label) { |
| $color = $this->colors[self::GRAPHIC_COLOR_KEY . $colorIndex++]; |
|
|
| $paddedBulletWidth = $bulletWidth; |
| if (isset($this->ordinateLogos[$metricCode])) { |
| $paddedBulletWidth = $maxLogoWidth; |
| } |
| $paddedBulletWidth += self::LEGEND_BULLET_RIGHT_PADDING; |
|
|
| |
| if ($verticalLegend) { |
| $label = $this->truncateLabel($label, ($this->width * self::VERTICAL_LEGEND_MAX_WIDTH_PCT) - $legendTopLeftXValue - $paddedBulletWidth, $this->legendFontSize); |
| $this->pData->setSerieDescription($metricCode, $label); |
| } |
|
|
| $rectangleTopLeftXValue = ($verticalLegend ? $legendTopLeftXValue : $currentPosition) + $paddedBulletWidth - self::LEGEND_HORIZONTAL_SHADOW_PADDING; |
| $rectangleTopLeftYValue = $verticalLegend ? $currentPosition : $legendTopMargin; |
|
|
| list($labelWidth, $labelHeight) = $this->getTextWidthHeight($label, $this->legendFontSize); |
| $legendItemWidth = $paddedBulletWidth + $labelWidth + $effectiveLegendItemHorizontalInterstice; |
| $rectangleBottomRightXValue = $rectangleTopLeftXValue + $labelWidth + (self::LEGEND_HORIZONTAL_SHADOW_PADDING * 2); |
|
|
| $legendItemHeight = max($maxLogoHeight, $this->legendFontSize) + ($effectiveShadowPadding * 2); |
| $rectangleBottomRightYValue = $rectangleTopLeftYValue + $legendItemHeight; |
|
|
| $this->pImage->drawFilledRectangle( |
| $rectangleTopLeftXValue, |
| $rectangleTopLeftYValue, |
| $rectangleBottomRightXValue, |
| $rectangleBottomRightYValue, |
| array( |
| 'Alpha' => self::LEGEND_SHADOW_OPACITY, |
| 'R' => $color['R'], |
| 'G' => $color['G'], |
| 'B' => $color['B'], |
| ) |
| ); |
|
|
| if ($verticalLegend) { |
| $currentPositionIncrement = max($maxLogoHeight, $effectiveLegendItemVerticalInterstice, $this->legendFontSize) + self::PCHART_HARD_CODED_VERTICAL_LEGEND_INTERSTICE; |
| } else { |
| $currentPositionIncrement = $legendItemWidth; |
| } |
|
|
| $currentPosition += $currentPositionIncrement; |
| } |
| } |
|
|
| |
| $legendColor = $this->textColor; |
| $this->pImage->drawLegend( |
| $legendTopLeftXValue, |
| $legendTopLeftYValue, |
| array( |
| 'Style' => LEGEND_NOBORDER, |
| 'FontSize' => $this->legendFontSize, |
| 'BoxWidth' => $bulletWidth, |
| 'XSpacing' => $effectiveLegendItemHorizontalInterstice, // not effective when vertical |
| 'Mode' => $verticalLegend ? LEGEND_VERTICAL : LEGEND_HORIZONTAL, |
| 'BoxHeight' => $verticalLegend ? $effectiveLegendItemVerticalInterstice : null, |
| 'Family' => $bulletType, |
| 'FontR' => $legendColor['R'], |
| 'FontG' => $legendColor['G'], |
| 'FontB' => $legendColor['B'], |
| ) |
| ); |
| } |
| } |
|
|
| protected static function getMaxLogoSize($logoPaths) |
| { |
| $maxLogoWidth = 0; |
| $maxLogoHeight = 0; |
| foreach ($logoPaths as $logoPath) { |
| list($logoWidth, $logoHeight) = self::getLogoSize($logoPath); |
|
|
| if ($logoWidth > $maxLogoWidth) { |
| $maxLogoWidth = $logoWidth; |
| } |
| if ($logoHeight > $maxLogoHeight) { |
| $maxLogoHeight = $logoHeight; |
| } |
| } |
|
|
| return array($maxLogoWidth, $maxLogoHeight); |
| } |
|
|
| protected static function getLogoSize($logoPath) |
| { |
| $pathInfo = getimagesize($logoPath); |
| return array($pathInfo[0], $pathInfo[1]); |
| } |
|
|
| protected function getGridLeftMargin($horizontalGraph, $withLabel) |
| { |
| $gridLeftMargin = self::LEFT_GRID_MARGIN + self::OUTER_TICK_WIDTH; |
|
|
| if ($withLabel) { |
| list($maxTextWidth, $maxTextHeight) = $this->getMaximumTextWidthHeight($horizontalGraph ? $this->abscissaSeries : $this->ordinateSeries); |
| $gridLeftMargin += $maxTextWidth; |
| } |
|
|
| return $gridLeftMargin; |
| } |
|
|
| protected function getGridTopMargin($horizontalGraph, $verticalLegend) |
| { |
| list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries); |
|
|
| if ($horizontalGraph) { |
| $topMargin = $ordinateMaxHeight + self::TOP_GRID_MARGIN_HORIZONTAL_GRAPH + self::OUTER_TICK_WIDTH; |
| } else { |
| $topMargin = $ordinateMaxHeight / 2 + self::TOP_GRID_MARGIN_HORIZONTAL_GRAPH; |
| } |
|
|
| if ($this->showLegend && !$verticalLegend) { |
| $topMargin += $this->getHorizontalLegendHeight(); |
| } |
|
|
| return $topMargin; |
| } |
|
|
| private function getHorizontalLegendHeight() |
| { |
| list($maxMetricLegendWidth, $maxMetricLegendHeight) = |
| $this->getMaximumTextWidthHeight(array_values($this->ordinateLabels)); |
|
|
| return $maxMetricLegendHeight + self::HORIZONTAL_LEGEND_BOTTOM_MARGIN + self::HORIZONTAL_LEGEND_TOP_MARGIN; |
| } |
|
|
| protected function getGraphHeight($horizontalGraph, $verticalLegend) |
| { |
| return $this->getGraphBottom($horizontalGraph) - $this->getGridTopMargin($horizontalGraph, $verticalLegend); |
| } |
|
|
| private function getGridBottomMargin($horizontalGraph) |
| { |
| $gridBottomMargin = self::BOTTOM_GRID_MARGIN; |
| if (!$horizontalGraph) { |
| list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries); |
| $gridBottomMargin += $abscissaMaxHeight; |
| } |
| return $gridBottomMargin; |
| } |
|
|
| protected function getGridRightMargin($horizontalGraph) |
| { |
| if ($horizontalGraph) { |
| |
| list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries); |
| return self::RIGHT_GRID_MARGIN_HORIZONTAL_GRAPH + $ordinateMaxWidth; |
| } else { |
| return self::RIGHT_GRID_MARGIN_HORIZONTAL_GRAPH; |
| } |
| } |
|
|
| protected function getGraphBottom($horizontalGraph) |
| { |
| return $this->height - $this->getGridBottomMargin($horizontalGraph); |
| } |
|
|
| protected function truncateLabel($label, $labelWidthLimit, $fontSize = false) |
| { |
| list($truncationTextWidth, $truncationTextHeight) = $this->getTextWidthHeight(self::TRUNCATION_TEXT, $fontSize); |
| list($labelWidth, $labelHeight) = $this->getTextWidthHeight($label, $fontSize); |
|
|
| if ($labelWidth > $labelWidthLimit) { |
| $averageCharWidth = $labelWidth / strlen($label); |
| $charsToKeep = floor(($labelWidthLimit - $truncationTextWidth) / $averageCharWidth); |
| $label = substr($label, 0, $charsToKeep) . self::TRUNCATION_TEXT; |
| } |
| return $label; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
|
|