PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.13.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.13.0
5.13.0 5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Visualization / Sparkline.php
matomo / app / core / Visualization Last commit date
Sparkline.php 1 week ago
Sparkline.php
214 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\Visualization;
10
11 use Piwik\Common;
12 use Piwik\Piwik;
13 use Piwik\View\ViewInterface;
14 /**
15 * Renders a sparkline image given a PHP data array.
16 * Using the Sparkline PHP Graphing Library sparkline.org
17 */
18 class Sparkline implements ViewInterface
19 {
20 public const DEFAULT_WIDTH = 200;
21 public const DEFAULT_HEIGHT = 50;
22 public const LINE_THICKNESS = 4;
23 public const POINT_SIZE = 6;
24 // We now create different sized width for Sparklines based on the card designs
25 // This max width will still be adjusted as we create new Sparkline modes.
26 public const MAX_WIDTH = 1000;
27 public const MAX_HEIGHT = 250;
28 /**
29 * Width of the sparkline
30 * @var int
31 */
32 protected $width = self::DEFAULT_WIDTH;
33 /**
34 * Height of sparkline
35 * @var int
36 */
37 protected $height = self::DEFAULT_HEIGHT;
38 private $serieses = [];
39 /**
40 * @var \Davaxi\Sparkline
41 */
42 private $sparkline;
43 /**
44 * Array with format: array( x, y, z, ... )
45 * @param array ...$data
46 */
47 public function setValues(...$data)
48 {
49 $this->serieses = $data;
50 }
51 public function addSeries(array $values)
52 {
53 $this->serieses[] = $values;
54 }
55 public function main()
56 {
57 try {
58 $sparkline = new \Davaxi\Sparkline();
59 } catch (\Exception $exception) {
60 // Ignore GD not installed exception
61 return;
62 }
63 $thousandSeparator = Piwik::translate('Intl_NumberSymbolGroup');
64 $decimalSeparator = Piwik::translate('Intl_NumberSymbolDecimal');
65 $sparkline->setData();
66 // remove default series
67 foreach ($this->serieses as $seriesIndex => $series) {
68 $values = [];
69 $hasFloat = \false;
70 foreach ($series as $value) {
71 // replace localized decimal separator
72 $value = str_replace($thousandSeparator, '', $value);
73 $value = str_replace($decimalSeparator, '.', $value);
74 // sanitize value
75 $value = filter_var($value, \FILTER_SANITIZE_NUMBER_FLOAT, \FILTER_FLAG_ALLOW_FRACTION | \FILTER_FLAG_ALLOW_SCIENTIFIC);
76 if (empty($value) || !is_numeric($value)) {
77 $value = 0;
78 }
79 $values[] = $value;
80 if (is_float($value + 0)) {
81 // coerce to int/float type before checking
82 $hasFloat = \true;
83 }
84 }
85 // the sparkline lib used converts everything to integers (see the FormatTrait.php file) which means float
86 // numbers that are close to 1.0 or 0.0 will get floored. this can happen in the average page generation time
87 // report, and cause some values which are, eg, around ~.9 to appear as 0 in the sparkline. to workaround this, we
88 // scale the values.
89 if ($hasFloat) {
90 $values = array_map(function ($x) {
91 return $x * 1000.0;
92 }, $values);
93 }
94 $sparkline->addSeries($values);
95 $this->setSparklineColors($sparkline, $seriesIndex);
96 }
97 $sparkline->setWidth($this->getWidth());
98 $sparkline->setHeight($this->getHeight());
99 $sparkline->setLineThickness(self::LINE_THICKNESS);
100 // Pad by at least the point radius so edge dots (first/last, and min/max at the
101 // top/bottom) aren't clipped by the image boundary.
102 $sparkline->setPadding((string) self::POINT_SIZE);
103 $this->sparkline = $sparkline;
104 }
105 /**
106 * Returns the width of the sparkline
107 * @return int
108 */
109 public function getWidth()
110 {
111 return $this->width;
112 }
113 /**
114 * Sets the width of the sparkline
115 * @param int $width
116 */
117 public function setWidth($width)
118 {
119 if (!is_numeric($width) || $width <= 0) {
120 return;
121 }
122 if ($width > self::MAX_WIDTH) {
123 $this->width = self::MAX_WIDTH;
124 } else {
125 $this->width = (int) $width;
126 }
127 }
128 /**
129 * Returns the height of the sparkline
130 * @return int
131 */
132 public function getHeight()
133 {
134 return $this->height;
135 }
136 /**
137 * Sets the height of the sparkline
138 * @param int $height
139 */
140 public function setHeight($height)
141 {
142 if (!is_numeric($height) || $height <= 0) {
143 return;
144 }
145 if ($height > self::MAX_HEIGHT) {
146 $this->height = self::MAX_HEIGHT;
147 } else {
148 $this->height = (int) $height;
149 }
150 }
151 /**
152 * Sets the sparkline colors
153 *
154 * @param \Davaxi\Sparkline $sparkline
155 */
156 private function setSparklineColors($sparkline, $seriesIndex)
157 {
158 $colors = Common::getRequestVar('colors', \false, 'json');
159 $defaultColors = array('backgroundColor' => '#ffffff', 'lineColor' => '#162C4A', 'minPointColor' => '#ff7f7f', 'maxPointColor' => '#75BF7C', 'lastPointColor' => '#55AAFF', 'fillColor' => '#ffffff');
160 if (empty($colors) || !is_array($colors)) {
161 $colors = $defaultColors;
162 //set default color, if no color passed
163 } else {
164 $colors = array_merge($defaultColors, $colors);
165 //set default color key, if no key set.
166 }
167 if ($this->shouldApplyColor($colors['backgroundColor'])) {
168 $sparkline->setBackgroundColorHex($colors['backgroundColor']);
169 } else {
170 $sparkline->deactivateBackgroundColor();
171 }
172 if (is_array($colors['lineColor'])) {
173 $sparkline->setLineColorHex($colors['lineColor'][$seriesIndex] ?? $defaultColors['lineColor'], $seriesIndex);
174 // set point colors to same as line colors so they can be better differentiated
175 $colors['minPointColor'] = $colors['maxPointColor'] = $colors['lastPointColor'] = $colors['lineColor'][$seriesIndex] ?? $defaultColors['lineColor'];
176 } else {
177 $sparkline->setLineColorHex($colors['lineColor']);
178 }
179 if ($this->shouldApplyColor($colors['fillColor'])) {
180 $sparkline->setFillColorHex($colors['fillColor']);
181 } else {
182 $sparkline->deactivateFillColor();
183 }
184 $pointSize = self::POINT_SIZE;
185 if ($this->shouldApplyColor($colors['minPointColor'])) {
186 $sparkline->addPoint("minimum", $pointSize, $colors['minPointColor'], $seriesIndex);
187 }
188 if ($this->shouldApplyColor($colors['maxPointColor'])) {
189 $sparkline->addPoint("maximum", $pointSize, $colors['maxPointColor'], $seriesIndex);
190 }
191 if ($this->shouldApplyColor($colors['lastPointColor'])) {
192 $sparkline->addPoint("last", $pointSize, $colors['lastPointColor'], $seriesIndex);
193 }
194 }
195 private function shouldApplyColor($color) : bool
196 {
197 return is_string($color) && strtolower($color) !== '#ffffff';
198 }
199 public function render()
200 {
201 if (!$this->sparkline instanceof \Davaxi\Sparkline) {
202 return null;
203 }
204 if (0 === $this->sparkline->getSeriesCount()) {
205 // ensure to have at least one series & point in sparkline to avoid possible php notices/errors
206 // a sparkline will then be displayed with a zero line
207 $this->sparkline->addSeries([0]);
208 }
209 $this->sparkline->display();
210 $this->sparkline->destroy();
211 return null;
212 }
213 }
214