PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.12.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.12.1
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 2 weeks ago
Sparkline.php
237 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\Container\StaticContainer;
13 use Piwik\Piwik;
14 use Piwik\Plugins\CoreVisualizations\FeatureFlags\SparklinesRedesign;
15 use Piwik\Plugins\FeatureFlags\FeatureFlagManager;
16 use Piwik\View\ViewInterface;
17 /**
18 * Renders a sparkline image given a PHP data array.
19 * Using the Sparkline PHP Graphing Library sparkline.org
20 */
21 class Sparkline implements ViewInterface
22 {
23 public const DEFAULT_WIDTH = 200;
24 public const DEFAULT_HEIGHT = 50;
25 public const DEFAULT_LINE_THICKNESS = 1;
26 public const REDESIGN_LINE_THICKNESS = 4;
27 public const DEFAULT_POINT_SIZE = 5;
28 public const REDESIGN_POINT_SIZE = 6;
29 // We now create different sized width for Sparklines based on the card designs
30 // This max width will still be adjusted as we create new Sparkline modes.
31 public const MAX_WIDTH = 1000;
32 public const MAX_HEIGHT = 250;
33 /**
34 * Width of the sparkline
35 * @var int
36 */
37 protected $width = self::DEFAULT_WIDTH;
38 /**
39 * Height of sparkline
40 * @var int
41 */
42 protected $height = self::DEFAULT_HEIGHT;
43 private $serieses = [];
44 /**
45 * @var \Davaxi\Sparkline
46 */
47 private $sparkline;
48 /**
49 * Array with format: array( x, y, z, ... )
50 * @param array ...$data
51 */
52 public function setValues(...$data)
53 {
54 $this->serieses = $data;
55 }
56 public function addSeries(array $values)
57 {
58 $this->serieses[] = $values;
59 }
60 public function main()
61 {
62 try {
63 $sparkline = new \Davaxi\Sparkline();
64 } catch (\Exception $exception) {
65 // Ignore GD not installed exception
66 return;
67 }
68 $thousandSeparator = Piwik::translate('Intl_NumberSymbolGroup');
69 $decimalSeparator = Piwik::translate('Intl_NumberSymbolDecimal');
70 $sparkline->setData();
71 // remove default series
72 foreach ($this->serieses as $seriesIndex => $series) {
73 $values = [];
74 $hasFloat = \false;
75 foreach ($series as $value) {
76 // replace localized decimal separator
77 $value = str_replace($thousandSeparator, '', $value);
78 $value = str_replace($decimalSeparator, '.', $value);
79 // sanitize value
80 $value = filter_var($value, \FILTER_SANITIZE_NUMBER_FLOAT, \FILTER_FLAG_ALLOW_FRACTION | \FILTER_FLAG_ALLOW_SCIENTIFIC);
81 if (empty($value) || !is_numeric($value)) {
82 $value = 0;
83 }
84 $values[] = $value;
85 if (is_float($value + 0)) {
86 // coerce to int/float type before checking
87 $hasFloat = \true;
88 }
89 }
90 // the sparkline lib used converts everything to integers (see the FormatTrait.php file) which means float
91 // numbers that are close to 1.0 or 0.0 will get floored. this can happen in the average page generation time
92 // report, and cause some values which are, eg, around ~.9 to appear as 0 in the sparkline. to workaround this, we
93 // scale the values.
94 if ($hasFloat) {
95 $values = array_map(function ($x) {
96 return $x * 1000.0;
97 }, $values);
98 }
99 $sparkline->addSeries($values);
100 $this->setSparklineColors($sparkline, $seriesIndex);
101 }
102 $sparkline->setWidth($this->getWidth());
103 $sparkline->setHeight($this->getHeight());
104 $sparkline->setLineThickness($this->getLineThickness());
105 // Pad by at least the point radius so edge dots (first/last, and min/max at the
106 // top/bottom) aren't clipped by the image boundary. Legacy used a fixed 5, which
107 // happened to equal the legacy point size; keep them coupled as the point size grows.
108 $sparkline->setPadding((string) $this->getPointSize());
109 $this->sparkline = $sparkline;
110 }
111 /**
112 * Returns the width of the sparkline
113 * @return int
114 */
115 public function getWidth()
116 {
117 return $this->width;
118 }
119 /**
120 * Sets the width of the sparkline
121 * @param int $width
122 */
123 public function setWidth($width)
124 {
125 if (!is_numeric($width) || $width <= 0) {
126 return;
127 }
128 if ($width > self::MAX_WIDTH) {
129 $this->width = self::MAX_WIDTH;
130 } else {
131 $this->width = (int) $width;
132 }
133 }
134 /**
135 * Returns the height of the sparkline
136 * @return int
137 */
138 public function getHeight()
139 {
140 return $this->height;
141 }
142 /**
143 * Sets the height of the sparkline
144 * @param int $height
145 */
146 public function setHeight($height)
147 {
148 if (!is_numeric($height) || $height <= 0) {
149 return;
150 }
151 if ($height > self::MAX_HEIGHT) {
152 $this->height = self::MAX_HEIGHT;
153 } else {
154 $this->height = (int) $height;
155 }
156 }
157 /**
158 * Sets the sparkline colors
159 *
160 * @param \Davaxi\Sparkline $sparkline
161 */
162 private function setSparklineColors($sparkline, $seriesIndex)
163 {
164 $colors = Common::getRequestVar('colors', \false, 'json');
165 $defaultColors = array('backgroundColor' => '#ffffff', 'lineColor' => '#162C4A', 'minPointColor' => '#ff7f7f', 'maxPointColor' => '#75BF7C', 'lastPointColor' => '#55AAFF', 'fillColor' => '#ffffff');
166 if (empty($colors) || !is_array($colors)) {
167 $colors = $defaultColors;
168 //set default color, if no color passed
169 } else {
170 $colors = array_merge($defaultColors, $colors);
171 //set default color key, if no key set.
172 }
173 if ($this->shouldApplyColor($colors['backgroundColor'])) {
174 $sparkline->setBackgroundColorHex($colors['backgroundColor']);
175 } else {
176 $sparkline->deactivateBackgroundColor();
177 }
178 if (is_array($colors['lineColor'])) {
179 $sparkline->setLineColorHex($colors['lineColor'][$seriesIndex] ?? $defaultColors['lineColor'], $seriesIndex);
180 // set point colors to same as line colors so they can be better differentiated
181 $colors['minPointColor'] = $colors['maxPointColor'] = $colors['lastPointColor'] = $colors['lineColor'][$seriesIndex] ?? $defaultColors['lineColor'];
182 } else {
183 $sparkline->setLineColorHex($colors['lineColor']);
184 }
185 if ($this->shouldApplyColor($colors['fillColor'])) {
186 $sparkline->setFillColorHex($colors['fillColor']);
187 } else {
188 $sparkline->deactivateFillColor();
189 }
190 $pointSize = $this->getPointSize();
191 if ($this->shouldApplyColor($colors['minPointColor'])) {
192 $sparkline->addPoint("minimum", $pointSize, $colors['minPointColor'], $seriesIndex);
193 }
194 if ($this->shouldApplyColor($colors['maxPointColor'])) {
195 $sparkline->addPoint("maximum", $pointSize, $colors['maxPointColor'], $seriesIndex);
196 }
197 if ($this->shouldApplyColor($colors['lastPointColor'])) {
198 $sparkline->addPoint("last", $pointSize, $colors['lastPointColor'], $seriesIndex);
199 }
200 }
201 private function getLineThickness() : int
202 {
203 if ($this->isSparklinesRedesignEnabled()) {
204 return self::REDESIGN_LINE_THICKNESS;
205 }
206 return self::DEFAULT_LINE_THICKNESS;
207 }
208 private function getPointSize() : int
209 {
210 if ($this->isSparklinesRedesignEnabled()) {
211 return self::REDESIGN_POINT_SIZE;
212 }
213 return self::DEFAULT_POINT_SIZE;
214 }
215 private function isSparklinesRedesignEnabled() : bool
216 {
217 return StaticContainer::get(FeatureFlagManager::class)->isFeatureActive(SparklinesRedesign::class);
218 }
219 private function shouldApplyColor($color) : bool
220 {
221 return is_string($color) && strtolower($color) !== '#ffffff';
222 }
223 public function render()
224 {
225 if (!$this->sparkline instanceof \Davaxi\Sparkline) {
226 return;
227 }
228 if (0 === $this->sparkline->getSeriesCount()) {
229 // ensure to have at least one series & point in sparkline to avoid possible php notices/errors
230 // a sparkline will then be displayed with a zero line
231 $this->sparkline->addSeries([0]);
232 }
233 $this->sparkline->display();
234 $this->sparkline->destroy();
235 }
236 }
237