These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * A query printer for charts series using the jqPlot JavaScript library. |
||
5 | * |
||
6 | * @since 1.8 |
||
7 | * @licence GNU GPL v2 or later |
||
8 | * |
||
9 | * @author mwjames |
||
10 | */ |
||
11 | class SRFjqPlotSeries extends SMWResultPrinter { |
||
12 | |||
13 | /** |
||
14 | * @see SMWResultPrinter::getName |
||
15 | */ |
||
16 | public function getName() { |
||
17 | return wfMessage( 'srf-printername-jqplotseries' )->text(); |
||
18 | } |
||
19 | |||
20 | /** |
||
21 | * Returns an array with the numerical data in the query result. |
||
22 | * |
||
23 | * |
||
24 | * @param SMWQueryResult $result |
||
25 | * @param $outputMode |
||
26 | * |
||
27 | * @return string |
||
28 | */ |
||
29 | protected function getResultText( SMWQueryResult $result, $outputMode ) { |
||
30 | |||
31 | // Get data set |
||
32 | $data = $this->getResultData( $result, $outputMode ); |
||
33 | |||
34 | // Check data availability |
||
35 | if ( $data['series'] === [] ) { |
||
36 | return $result->addErrors( |
||
37 | [ |
||
38 | wfMessage( 'srf-warn-empy-chart' ) |
||
39 | ->inContentLanguage()->text() ] |
||
40 | ); |
||
41 | } else { |
||
42 | $options['sask'] = SRFUtils::htmlQueryResultLink( $this->getLink( $result, SMW_OUTPUT_HTML ) ); |
||
0 ignored issues
–
show
|
|||
43 | return $this->getFormatOutput( $this->getFormatSettings( $this->getNumbersTicks( $data ), $options ) ); |
||
44 | } |
||
45 | } |
||
46 | |||
47 | /** |
||
48 | * Returns an array with the numerical data |
||
49 | * |
||
50 | * @since 1.8 |
||
51 | * |
||
52 | * @param SMWQueryResult $result |
||
0 ignored issues
–
show
There is no parameter named
$result . Was it maybe removed?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. Consider the following example. The parameter /**
* @param array $germany
* @param array $island
* @param array $italy
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was removed, but the annotation was not.
Loading history...
|
|||
53 | * @param $outputMode |
||
54 | * |
||
55 | * @return array |
||
56 | */ |
||
57 | protected function getResultData( SMWQueryResult $res, $outputMode ) { |
||
58 | $data = []; |
||
59 | $data['series'] = []; |
||
60 | |||
61 | while ( $row = $res->getNext() ) { |
||
62 | // Loop over their fields (properties) |
||
63 | $label = ''; |
||
64 | $i = 0; |
||
65 | |||
66 | foreach ( $row as /* SMWResultArray */ |
||
67 | $field ) { |
||
68 | $i++; |
||
69 | $rowNumbers = []; |
||
70 | |||
71 | // Grouping by subject (page object) or property |
||
72 | if ( $this->params['group'] === 'subject' ) { |
||
73 | $groupedBy = $field->getResultSubject()->getTitle()->getText(); |
||
74 | } else { |
||
75 | $groupedBy = $field->getPrintRequest()->getLabel(); |
||
76 | } |
||
77 | |||
78 | // Property label |
||
79 | $property = $field->getPrintRequest()->getLabel(); |
||
80 | |||
81 | // First column property typeid |
||
82 | $i == 1 ? $data['fcolumntypeid'] = $field->getPrintRequest()->getTypeID() : ''; |
||
83 | |||
84 | // Loop over all values for the property. |
||
85 | while ( ( /* SMWDataValue */ |
||
86 | $object = $field->getNextDataValue() ) !== false ) { |
||
87 | |||
88 | if ( $object->getDataItem()->getDIType() == SMWDataItem::TYPE_NUMBER ) { |
||
89 | $number = $object->getNumber(); |
||
90 | |||
91 | // Checking against the row and in case the first column is a numeric |
||
92 | // value it is handled as label with the remaining steps continue to work |
||
93 | // as it were a text label |
||
94 | // The first column container will not be part of the series container |
||
95 | if ( $i == 1 ) { |
||
96 | $label = $number; |
||
97 | continue; |
||
98 | } |
||
99 | |||
100 | if ( $label !== '' && $number >= $this->params['min'] ) { |
||
101 | |||
102 | // Reference array summarize all items per row |
||
103 | $rowNumbers += [ 'subject' => $label, 'value' => $number, 'property' => $property ]; |
||
104 | |||
105 | // Store plain numbers for simpler handling |
||
106 | $data['series'][$groupedBy][] = $number; |
||
107 | } |
||
108 | } elseif ( $object->getDataItem()->getDIType() == SMWDataItem::TYPE_TIME ) { |
||
109 | $label = $object->getShortWikiText(); |
||
110 | } else { |
||
111 | $label = $object->getWikiValue(); |
||
112 | } |
||
113 | } |
||
114 | // Only for array's with numbers |
||
115 | if ( count( $rowNumbers ) > 0 ) { |
||
116 | |||
117 | // For cases where mainlabel=- we assume that the subject should not be |
||
118 | // used as identifier and therefore we try to match the groupby |
||
119 | // with the first available text label |
||
120 | if ( $this->params['mainlabel'] == '-' && $this->params['group'] === 'subject' ) { |
||
121 | $data[$this->params['group']][$label][] = $rowNumbers; |
||
122 | } else { |
||
123 | $data[$this->params['group']][$groupedBy][] = $rowNumbers; |
||
124 | } |
||
125 | } |
||
126 | } |
||
127 | } |
||
128 | return $data; |
||
129 | } |
||
130 | |||
131 | /** |
||
132 | * Data set sorting |
||
133 | * |
||
134 | * @since 1.8 |
||
135 | * |
||
136 | * @param array $data label => value |
||
137 | * |
||
138 | * @return array |
||
139 | */ |
||
140 | private function getFormatSettings( $data, $options ) { |
||
141 | |||
142 | // Init |
||
143 | $dataSet = []; |
||
144 | $options['mode'] = 'series'; |
||
145 | $options['autoscale'] = false; |
||
146 | |||
147 | // Available markers |
||
148 | $marker = [ 'circle', 'diamond', 'square', 'filledCircle', 'filledDiamond', 'filledSquare' ]; |
||
149 | |||
150 | // Series colour(has to be null otherwise jqplot runs with a type error) |
||
151 | $seriescolors = $this->params['chartcolor'] !== '' ? array_filter( |
||
152 | explode( ",", $this->params['chartcolor'] ) |
||
153 | ) : null; |
||
154 | |||
155 | // Re-grouping |
||
156 | foreach ( $data[$this->params['group']] as $rowKey => $row ) { |
||
157 | $values = []; |
||
158 | |||
159 | foreach ( $row as $key => $value ) { |
||
160 | // Switch labels according to the group parameter |
||
161 | $label = $this->params['grouplabel'] === 'property' ? $value['property'] : $value['subject']; |
||
162 | $values[] = [ $label, $value['value'] ]; |
||
163 | } |
||
164 | $dataSet[] = $values; |
||
165 | } |
||
166 | |||
167 | // Series plotting parameters |
||
168 | foreach ( $data[$this->params['group']] as $key => $row ) { |
||
169 | $series[] = [ |
||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
$series was never initialized. Although not strictly required by PHP, it is generally a good practice to add $series = array(); before regardless.
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code. Let’s take a look at an example: foreach ($collection as $item) {
$myArray['foo'] = $item->getFoo();
if ($item->hasBar()) {
$myArray['bar'] = $item->getBar();
}
// do something with $myArray
}
As you can see in this example, the array This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.
Loading history...
|
|||
170 | 'label' => $key, |
||
171 | 'xaxis' => 'xaxis', // xaxis could also be xaxis2 or ... |
||
172 | 'yaxis' => 'yaxis', |
||
173 | 'fill' => $this->params['stackseries'], |
||
174 | 'showLine' => $this->params['charttype'] !== 'scatter', |
||
175 | 'showMarker' => true, |
||
176 | 'trendline' => [ |
||
177 | 'show' => in_array( $this->params['trendline'], [ 'exp', 'linear' ] ), |
||
178 | 'shadow' => $this->params['theme'] !== 'simple', |
||
179 | 'type' => $this->params['trendline'], |
||
180 | ], |
||
181 | 'markerOptions' => [ |
||
182 | 'style' => $marker[array_rand( $marker )], |
||
183 | 'shadow' => $this->params['theme'] !== 'simple' |
||
184 | ], |
||
185 | 'rendererOptions' => [ 'barDirection' => $this->params['direction'] ] |
||
186 | ]; |
||
187 | }; |
||
188 | |||
189 | // Basic parameters |
||
190 | $parameters = [ |
||
191 | 'numbersaxislabel' => $this->params['numbersaxislabel'], |
||
192 | 'labelaxislabel' => $this->params['labelaxislabel'], |
||
193 | 'charttitle' => $this->params['charttitle'], |
||
194 | 'charttext' => $this->params['charttext'], |
||
195 | 'infotext' => $this->params['infotext'], |
||
196 | 'theme' => $this->params['theme'] ? $this->params['theme'] : null, |
||
197 | 'valueformat' => $this->params['datalabels'] === 'label' ? '' : $this->params['valueformat'], |
||
198 | 'ticklabels' => $this->params['ticklabels'], |
||
199 | 'highlighter' => $this->params['highlighter'], |
||
200 | 'autoscale' => $options['autoscale'], |
||
201 | 'gridview' => $this->params['gridview'], |
||
202 | 'direction' => $this->params['direction'], |
||
203 | 'smoothlines' => $this->params['smoothlines'], |
||
204 | 'cursor' => $this->params['cursor'], |
||
205 | 'chartlegend' => $this->params['chartlegend'] !== '' ? $this->params['chartlegend'] : 'none', |
||
206 | 'colorscheme' => $this->params['colorscheme'] !== '' ? $this->params['colorscheme'] : null, |
||
207 | 'pointlabels' => $this->params['datalabels'] === 'none' ? false : $this->params['datalabels'], |
||
208 | 'datalabels' => $this->params['datalabels'], |
||
209 | 'stackseries' => $this->params['stackseries'], |
||
210 | 'grid' => $this->params['theme'] === 'vector' ? [ 'borderColor' => '#a7d7f9' ] : ( $this->params['theme'] === 'simple' ? [ 'borderColor' => '#ddd' ] : null ), |
||
211 | 'seriescolors' => $seriescolors |
||
212 | ]; |
||
213 | |||
214 | return [ |
||
215 | 'data' => $dataSet, |
||
216 | //'rawdata' => $data , // control array |
||
217 | 'series' => $series, |
||
0 ignored issues
–
show
The variable
$series does not seem to be defined for all execution paths leading up to this point.
If you define a variable conditionally, it can happen that it is not defined for all execution paths. Let’s take a look at an example: function myFunction($a) {
switch ($a) {
case 'foo':
$x = 1;
break;
case 'bar':
$x = 2;
break;
}
// $x is potentially undefined here.
echo $x;
}
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined. Available Fixes
Loading history...
|
|||
218 | 'ticks' => $data['numbersticks'], |
||
219 | 'total' => $data['total'], |
||
220 | 'fcolumntypeid' => $data['fcolumntypeid'], |
||
221 | 'sask' => $options['sask'], |
||
222 | 'mode' => $options['mode'], |
||
223 | 'renderer' => $this->params['charttype'], |
||
224 | 'parameters' => $parameters |
||
225 | ]; |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * Fetch numbers ticks |
||
230 | * |
||
231 | * @since 1.8 |
||
232 | * |
||
233 | * @param array $data |
||
234 | */ |
||
235 | protected function getNumbersTicks( array $data ) { |
||
236 | |||
237 | // Only look for numeric values that have been stored |
||
238 | $numerics = array_values( $data['series'] ); |
||
239 | |||
240 | // Find min and max values to determine the graphs axis parameter |
||
241 | $maxValue = count( $numerics ) == 0 ? 0 : max( array_map( "max", $numerics ) ); |
||
242 | |||
243 | if ( $this->params['min'] === false ) { |
||
244 | $minValue = count( $numerics ) == 0 ? 0 : min( array_map( "min", $numerics ) ); |
||
245 | } else { |
||
246 | $minValue = $this->params['min']; |
||
247 | } |
||
248 | |||
249 | // Get ticks info |
||
250 | $data['numbersticks'] = SRFjqPlot::getNumbersTicks( $minValue, $maxValue ); |
||
251 | $data['total'] = array_sum( array_map( "array_sum", $numerics ) ); |
||
252 | |||
253 | return $data; |
||
254 | } |
||
255 | |||
256 | /** |
||
257 | * Add resource definitions |
||
258 | * |
||
259 | * @since 1.8 |
||
260 | * |
||
261 | * @param array $data |
||
0 ignored issues
–
show
There is no parameter named
$data . Was it maybe removed?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. Consider the following example. The parameter /**
* @param array $germany
* @param array $island
* @param array $italy
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was removed, but the annotation was not.
Loading history...
|
|||
262 | * |
||
263 | * @return string |
||
264 | */ |
||
265 | protected function addResources() { |
||
266 | // RL module |
||
267 | switch ( $this->params['charttype'] ) { |
||
268 | case 'bubble': |
||
269 | SMWOutputs::requireResource( 'ext.srf.jqplot.bubble' ); |
||
270 | break; |
||
271 | case 'donut': |
||
272 | SMWOutputs::requireResource( 'ext.srf.jqplot.donut' ); |
||
273 | break; |
||
274 | case 'scatter': |
||
275 | case 'line': |
||
276 | case 'bar': |
||
277 | SMWOutputs::requireResource( 'ext.srf.jqplot.bar' ); |
||
278 | break; |
||
279 | } |
||
280 | |||
281 | // Trendline plugin |
||
282 | if ( in_array( $this->params['trendline'], [ 'exp', 'linear' ] ) ) { |
||
283 | SMWOutputs::requireResource( 'ext.srf.jqplot.trendline' ); |
||
284 | } |
||
285 | |||
286 | // Cursor plugin |
||
287 | if ( in_array( $this->params['cursor'], [ 'zoom', 'tooltip' ] ) ) { |
||
288 | SMWOutputs::requireResource( 'ext.srf.jqplot.cursor' ); |
||
289 | } |
||
290 | |||
291 | // Highlighter plugin |
||
292 | if ( $this->params['highlighter'] ) { |
||
293 | SMWOutputs::requireResource( 'ext.srf.jqplot.highlighter' ); |
||
294 | } |
||
295 | |||
296 | // Enhancedlegend plugin |
||
297 | if ( $this->params['chartlegend'] ) { |
||
298 | SMWOutputs::requireResource( 'ext.srf.jqplot.enhancedlegend' ); |
||
299 | } |
||
300 | |||
301 | // gridview plugin |
||
302 | if ( in_array( $this->params['gridview'], [ 'tabs' ] ) ) { |
||
303 | SMWOutputs::requireResource( 'ext.srf.util.grid' ); |
||
304 | } |
||
305 | |||
306 | // Pointlabels plugin |
||
307 | if ( in_array( $this->params['datalabels'], [ 'value', 'label', 'percent' ] ) ) { |
||
308 | SMWOutputs::requireResource( 'ext.srf.jqplot.pointlabels' ); |
||
309 | } |
||
310 | } |
||
311 | |||
312 | /** |
||
313 | * Prepare data for the output |
||
314 | * |
||
315 | * @since 1.8 |
||
316 | * |
||
317 | * @param array $data |
||
318 | * |
||
319 | * @return string |
||
320 | */ |
||
321 | protected function getFormatOutput( array $data ) { |
||
322 | |||
323 | $this->isHTML = true; |
||
324 | |||
325 | static $statNr = 0; |
||
326 | $chartID = 'jqplot-series-' . ++$statNr; |
||
327 | |||
328 | // Encoding |
||
329 | $requireHeadItem = [ $chartID => FormatJson::encode( $data ) ]; |
||
330 | SMWOutputs::requireHeadItem( $chartID, Skin::makeVariablesScript( $requireHeadItem ) ); |
||
331 | |||
332 | // Add RL resources |
||
333 | $this->addResources(); |
||
334 | |||
335 | // Processing placeholder |
||
336 | $processing = SRFUtils::htmlProcessingElement( $this->isHTML ); |
||
337 | |||
338 | // Conversion due to a string as value that can contain % |
||
339 | $width = strstr( $this->params['width'], "%" ) ? $this->params['width'] : $this->params['width'] . 'px'; |
||
340 | |||
341 | // Chart/graph placeholder |
||
342 | $chart = Html::rawElement( |
||
343 | 'div', |
||
344 | [ |
||
345 | 'id' => $chartID, |
||
346 | 'class' => 'container', |
||
347 | 'style' => "display:none; width: {$width}; height: {$this->params['height']}px;" |
||
348 | ], |
||
349 | null |
||
350 | ); |
||
351 | |||
352 | // Beautify class selector |
||
353 | $class = $this->params['charttype'] ? '-' . $this->params['charttype'] : ''; |
||
354 | $class = $this->params['class'] ? $class . ' ' . $this->params['class'] : $class . ' jqplot-common'; |
||
355 | |||
356 | // Chart/graph wrappper |
||
357 | return Html::rawElement( |
||
358 | 'div', |
||
359 | [ |
||
360 | 'class' => 'srf-jqplot' . $class, |
||
361 | ], |
||
362 | $processing . $chart |
||
363 | ); |
||
364 | } |
||
365 | |||
366 | /** |
||
367 | * @see SMWResultPrinter::getParamDefinitions |
||
368 | * |
||
369 | * @since 1.8 |
||
370 | * |
||
371 | * @param $definitions array of IParamDefinition |
||
372 | * |
||
373 | * @return array of IParamDefinition|array |
||
374 | */ |
||
375 | public function getParamDefinitions( array $definitions ) { |
||
376 | $params = array_merge( parent::getParamDefinitions( $definitions ), SRFjqPlot::getCommonParams() ); |
||
377 | |||
378 | $params['infotext'] = [ |
||
379 | 'message' => 'srf-paramdesc-infotext', |
||
380 | 'default' => '', |
||
381 | ]; |
||
382 | |||
383 | $params['stackseries'] = [ |
||
384 | 'type' => 'boolean', |
||
385 | 'message' => 'srf-paramdesc-stackseries', |
||
386 | 'default' => false, |
||
387 | ]; |
||
388 | |||
389 | $params['group'] = [ |
||
390 | 'message' => 'srf-paramdesc-group', |
||
391 | 'default' => 'subject', |
||
392 | 'values' => [ 'property', 'subject' ], |
||
393 | ]; |
||
394 | |||
395 | $params['grouplabel'] = [ |
||
396 | 'message' => 'srf-paramdesc-grouplabel', |
||
397 | 'default' => 'subject', |
||
398 | 'values' => [ 'property', 'subject' ], |
||
399 | ]; |
||
400 | |||
401 | $params['charttype'] = [ |
||
402 | 'message' => 'srf-paramdesc-charttype', |
||
403 | 'default' => 'bar', |
||
404 | 'values' => [ 'bar', 'line', 'donut', 'bubble', 'scatter' ], |
||
405 | ]; |
||
406 | |||
407 | $params['trendline'] = [ |
||
408 | 'message' => 'srf-paramdesc-trendline', |
||
409 | 'default' => 'none', |
||
410 | 'values' => [ 'none', 'exp', 'linear' ], |
||
411 | ]; |
||
412 | |||
413 | $params['cursor'] = [ |
||
414 | 'message' => 'srf-paramdesc-chartcursor', |
||
415 | 'default' => 'none', |
||
416 | 'values' => [ 'none', 'zoom', 'tooltip' ], |
||
417 | ]; |
||
418 | |||
419 | $params['gridview'] = [ |
||
420 | 'message' => 'srf-paramdesc-gridview', |
||
421 | 'default' => 'none', |
||
422 | 'values' => [ 'none', 'tabs' ], |
||
423 | ]; |
||
424 | |||
425 | return $params; |
||
426 | } |
||
427 | } |
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.
Let’s take a look at an example:
As you can see in this example, the array
$myArray
is initialized the first time when the foreach loop is entered. You can also see that the value of thebar
key is only written conditionally; thus, its value might result from a previous iteration.This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.