1<?php
2/**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9namespace Piwik\Plugins\ImageGraph\StaticGraph;
10use Piwik\Piwik;
11
12/**
13 *
14 */
15class HorizontalBar extends GridGraph
16{
17    const INTERLEAVE = 0.30;
18    const PADDING_CHARS = ' ';
19    const LEGEND_SQUARE_WIDTH = 11;
20    const MIN_SPACE_BETWEEN_HORIZONTAL_VALUES = 5;
21    const LOGO_MIN_RIGHT_MARGIN = 3;
22
23    public function renderGraph()
24    {
25        $verticalLegend = false;
26
27        // create resized copies of logo to match maximum width / height
28        foreach ($this->abscissaLogos as &$logoPath) {
29            $logoPath = $this->createResizedImageCopyIfNeeded($logoPath);
30            list($logoWidth, $logoHeight) = self::getLogoSize($logoPath);
31            $logoPathToHeight[$logoPath] = $logoHeight;
32        }
33
34        // determine the maximum logo width & height
35        list($maxLogoWidth, $maxLogoHeight) = self::getMaxLogoSize($this->abscissaLogos);
36
37        // truncate report
38        $graphHeight = $this->getGraphBottom($horizontalGraph = true) - $this->getGridTopMargin($horizontalGraph = true, $verticalLegend);
39
40        list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
41        list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries);
42
43        $numberOfSeries = count($this->ordinateSeries);
44        $ordinateMaxHeight = $ordinateMaxHeight * $numberOfSeries;
45
46        $textMaxHeight = $abscissaMaxHeight > $ordinateMaxHeight ? $abscissaMaxHeight : $ordinateMaxHeight;
47
48        $minLineWidth = ($textMaxHeight > $maxLogoHeight ? $textMaxHeight : $maxLogoHeight) + (self::MIN_SPACE_BETWEEN_HORIZONTAL_VALUES * $numberOfSeries);
49        $maxNumOfValues = floor($graphHeight / $minLineWidth);
50        $abscissaSeriesCount = count($this->abscissaSeries);
51
52        if ($maxNumOfValues < $abscissaSeriesCount - 1) {
53            $sumOfOthers = array();
54            $truncatedOrdinateSeries = array();
55            $truncatedAbscissaLogos = array();
56            $truncatedAbscissaSeries = array();
57            foreach ($this->ordinateSeries as $column => $data) {
58                $truncatedOrdinateSeries[$column] = array();
59                $sumOfOthers[$column] = 0;
60            }
61
62            $i = 0;
63            for (; $i < $maxNumOfValues; $i++) {
64                foreach ($this->ordinateSeries as $column => $data) {
65                    $truncatedOrdinateSeries[$column][] = $data[$i];
66                }
67
68                $truncatedAbscissaLogos[] = isset($this->abscissaLogos[$i]) ? $this->abscissaLogos[$i] : null;
69                $truncatedAbscissaSeries[] = $this->abscissaSeries[$i];
70            }
71
72            for (; $i < $abscissaSeriesCount; $i++) {
73                foreach ($this->ordinateSeries as $column => $data) {
74                    $sumOfOthers[$column] += $data[$i];
75                }
76            }
77
78            foreach ($this->ordinateSeries as $column => $data) {
79                $truncatedOrdinateSeries[$column][] = $sumOfOthers[$column];
80            }
81
82            $truncatedAbscissaSeries[] = Piwik::translate('General_Others');
83            $this->abscissaSeries = $truncatedAbscissaSeries;
84            $this->ordinateSeries = $truncatedOrdinateSeries;
85            $this->abscissaLogos = $truncatedAbscissaLogos;
86        }
87
88        // blank characters are used to pad labels so the logo can be displayed
89        $paddingText = '';
90        $paddingWidth = 0;
91        if ($maxLogoWidth > 0) {
92            while ($paddingWidth < $maxLogoWidth + self::LOGO_MIN_RIGHT_MARGIN) {
93                $paddingText .= self::PADDING_CHARS;
94                list($paddingWidth, $paddingHeight) = $this->getTextWidthHeight($paddingText);
95            }
96        }
97
98        // determine the maximum label width according to the minimum comfortable graph size
99        $gridRightMargin = $this->getGridRightMargin($horizontalGraph = true);
100        $minGraphSize = ($this->width - $gridRightMargin) / 2;
101
102        $metricLegendWidth = 0;
103        foreach ($this->ordinateLabels as $column => $label) {
104            list($textWidth, $textHeight) = $this->getTextWidthHeight($label);
105            $metricLegendWidth += $textWidth;
106        }
107
108        $legendWidth = $metricLegendWidth + ((self::HORIZONTAL_LEGEND_LEFT_MARGIN + self::LEGEND_SQUARE_WIDTH) * $numberOfSeries);
109        if ($this->showLegend) {
110            if ($legendWidth > $minGraphSize) {
111                $minGraphSize = $legendWidth;
112            }
113        }
114
115        $gridLeftMarginWithoutLabels = $this->getGridLeftMargin($horizontalGraph = true, $withLabel = false);
116        $labelWidthLimit =
117            $this->width
118            - $gridLeftMarginWithoutLabels
119            - $gridRightMargin
120            - $paddingWidth
121            - $minGraphSize;
122
123        // truncate labels if needed
124        foreach ($this->abscissaSeries as &$label) {
125            $label = $this->truncateLabel($label, $labelWidthLimit);
126        }
127
128        $gridLeftMarginBeforePadding = $this->getGridLeftMargin($horizontalGraph = true, $withLabel = true);
129
130        // pad labels for logo space
131        foreach ($this->abscissaSeries as &$label) {
132            $label .= $paddingText;
133        }
134
135        $this->initGridChart(
136            $displayVerticalGridLines = false,
137            $bulletType = LEGEND_FAMILY_BOX,
138            $horizontalGraph = true,
139            $showTicks = false,
140            $verticalLegend
141        );
142
143        $valueColor = $this->textColor;
144        $this->pImage->drawBarChart(
145            array(
146                 'DisplayValues' => true,
147                 'Interleave'    => self::INTERLEAVE,
148                 'DisplayR'      => $valueColor['R'],
149                 'DisplayG'      => $valueColor['G'],
150                 'DisplayB'      => $valueColor['B'],
151            )
152        );
153
154//		// display icons
155        $graphData = $this->pData->getData();
156        $numberOfRows = count($this->abscissaSeries);
157        $logoInterleave = $this->getGraphHeight(true, $verticalLegend) / $numberOfRows;
158        for ($i = 0; $i < $numberOfRows; $i++) {
159            if (isset($this->abscissaLogos[$i])) {
160                $currentLogoPath = $this->abscissaLogos[$i];
161
162                if (isset($logoPathToHeight[$currentLogoPath])) {
163                    $logoHeight = $logoPathToHeight[$currentLogoPath];
164
165                    $pathInfo = pathinfo($currentLogoPath);
166                    $logoExtension = strtoupper($pathInfo['extension']);
167                    $drawingFunction = 'drawFrom' . $logoExtension;
168
169                    $logoYPosition =
170                        ($logoInterleave * $i)
171                        + $this->getGridTopMargin(true, $verticalLegend)
172                        + $graphData['Axis'][1]['Margin']
173                        - $logoHeight / 2
174                        + 1;
175
176                    if (method_exists($this->pImage, $drawingFunction)) {
177                        $this->pImage->$drawingFunction(
178                            $gridLeftMarginBeforePadding,
179                            $logoYPosition,
180                            $currentLogoPath
181                        );
182                    }
183                }
184            }
185        }
186    }
187}
188