Completed
Push — master ( 6dc7d8...407c40 )
by Karsten
15:45
created

formats/jqplot/SRF_jqPlotChart.php (3 issues)

Upgrade to new PHP Analysis Engine

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 bar, line, pie and donut chart on aggregated values
5
 * using the jqPlot JavaScript library.
6
 *
7
 * @since 1.8
8
 *
9
 * @file SRF_jqPlotChart.php
10
 * @ingroup SemanticResultFormats
11
 * @licence GNU GPL v2 or later
12
 *
13
 * @author mwjames
14
 * @author Jeroen De Dauw < [email protected] >
15
 * @author Yaron Koren
16
 * @author Sanyam Goyal
17
 */
18
class SRFjqPlotChart extends SRFjqPlot {
19
20
	/**
21
	 * Corresponding message name
22
	 *
23
	 */
24
	public function getName() {
25
		return wfMessage( 'srf-printername-jqplotchart' )->text();
26
	}
27
28
	/**
29
	 * Prepare data output
30
	 *
31
	 * @since 1.8
32
	 *
33
	 * @param array $data label => value
34
	 */
35
	protected function getFormatOutput( array $data ) {
36
37
		static $statNr = 0;
38
		$chartID = 'jqplot-' . $this->params['charttype'] . '-' . ++$statNr;
39
40
		$this->isHTML = true;
41
42
		// Prepare data objects
43
		if ( in_array( $this->params['charttype'], [ 'bar', 'line' ] ) ) {
44
			// Parse bar relevant data
45
			$dataObject = $this->prepareBarData( $data );
46
		} elseif ( in_array( $this->params['charttype'], [ 'pie', 'donut' ] ) ) {
47
			//Parse pie/donut relevant data
48
			$dataObject = $this->preparePieData( $data );
49
		} else {
50
			// Return with an error
51
			return Html::rawElement(
52
				'span',
53
				[
54
					'class' => "error"
55
				],
56
				wfMessage( 'srf-error-missing-layout' )->inContentLanguage()->text()
57
			);
58
		}
59
60
		// Encode data objects
61
		$requireHeadItem = [ $chartID => FormatJson::encode( $dataObject ) ];
62
		SMWOutputs::requireHeadItem( $chartID, Skin::makeVariablesScript( $requireHeadItem ) );
63
64
		// Processing placeholder
65
		$processing = SRFUtils::htmlProcessingElement( $this->isHTML );
66
67
		// Ensure right conversion
68
		$width = strstr( $this->params['width'], "%" ) ? $this->params['width'] : $this->params['width'] . 'px';
69
70
		// Chart/graph placeholder
71
		$chart = Html::rawElement(
72
			'div',
73
			[
74
				'id' => $chartID,
75
				'class' => 'container',
76
				'style' => "display:none; width: {$width}; height: {$this->params['height']}px;"
77
			],
78
			null
79
		);
80
81
		// Beautify class selector
82
		$class = $this->params['charttype'] ? '-' . $this->params['charttype'] : '';
83
		$class = $this->params['class'] ? $class . ' ' . $this->params['class'] : $class . ' jqplot-common';
84
85
		// Chart/graph wrappper
86
		return Html::rawElement(
87
			'div',
88
			[
89
				'class' => 'srf-jqplot' . $class,
90
			],
91
			$processing . $chart
92
		);
93
	}
94
95
	/**
96
	 * Prepare pie/donut chart specific data and parameters
97
	 *
98
	 * @since 1.8
99
	 *
100
	 * @param array $rawdata label => value
101
	 *
102
	 * @return array
103
	 */
104
	private function preparePieData( $rawdata ) {
105
106
		// Init
107
		$mode = 'single';
108
109
		// Reorganize the data in accordance with the pie chart req.
110
		foreach ( $rawdata as $name => $value ) {
111
			if ( $value >= $this->params['min'] ) {
112
				$data[] = [ $name, $value ];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = 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 $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar 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.

Loading history...
113
			}
114
		}
115
116
		if ( $this->params['charttype'] === 'donut' ) {
117
			SMWOutputs::requireResource( 'ext.srf.jqplot.donut' );
118
		} else {
119
			SMWOutputs::requireResource( 'ext.srf.jqplot.pie' );
120
		}
121
122
		return [
123
			'data' => [ $data ],
0 ignored issues
show
The variable $data 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

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
124
			'renderer' => $this->params['charttype'],
125
			'mode' => $mode,
126
			'parameters' => $this->addCommonOptions()
127
		];
128
	}
129
130
	/**
131
	 * Prepare bar/line chart specific data and parameters
132
	 *
133
	 * Data can be an array of y values, or an array of [label, value] pairs;
134
	 * While labels are used only on the first series with labels on
135
	 * subsequent series being ignored
136
	 *
137
	 * @since 1.8
138
	 *
139
	 * @param array $rawdata label => value
140
	 *
141
	 * @return array
142
	 */
143
	private function prepareBarData( $rawdata ) {
144
145
		// Init
146
		$total = 0;
147
		$mode = 'single';
148
149
		// Find min and max values to determine the graphs axis parameter
150
		$maxValue = count( $rawdata ) == 0 ? 0 : max( $rawdata );
151
152
		if ( $this->params['min'] === false ) {
153
			$minValue = count( $rawdata ) == 0 ? 0 : min( $rawdata );
154
		} else {
155
			$minValue = $this->params['min'];
156
		}
157
158
		// Get number ticks
159
		$data['numbersticks'] = SRFjqPlot::getNumbersTicks( $minValue, $maxValue );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$data was never initialized. Although not strictly required by PHP, it is generally a good practice to add $data = 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 $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar 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.

Loading history...
160
161
		// Reorganize the data in accordance with the bar/line chart req.
162
		foreach ( $rawdata as $key => $value ) {
163
			if ( $value >= $this->params['min'] ) {
164
				$data['series'][] = [ $key, $value ];
165
				$total = $total + $value;
166
			}
167
		}
168
169
		// Bar/line module
170
		SMWOutputs::requireResource( 'ext.srf.jqplot.bar' );
171
172
		// Highlighter plugin
173
		if ( $this->params['highlighter'] ) {
174
			SMWOutputs::requireResource( 'ext.srf.jqplot.highlighter' );
175
		}
176
177
		// Pointlabels plugin
178
		if ( in_array( $this->params['datalabels'], [ 'value', 'label', 'percent' ] ) ) {
179
			SMWOutputs::requireResource( 'ext.srf.jqplot.pointlabels' );
180
		}
181
182
		return [
183
			'data' => [ $data['series'] ],
184
			'ticks' => $data['numbersticks'],
185
			'labels' => array_keys( $data['series'] ),
186
			'numbers' => array_values( $data['series'] ),
187
			'max' => $maxValue,
188
			'total' => $total,
189
			'mode' => $mode,
190
			'series' => [],
191
			'renderer' => $this->params['charttype'],
192
			'parameters' => $this->addCommonOptions()
193
		];
194
	}
195
196
	/**
197
	 * jqPlot common parameters
198
	 *
199
	 * @since 1.8
200
	 *
201
	 */
202
	private function addCommonOptions() {
203
204
		// Series colour
205
		$seriescolors = $this->params['chartcolor'] !== '' ? array_filter(
206
			explode( ",", $this->params['chartcolor'] )
207
		) : null;
208
209
		return [
210
			'numbersaxislabel' => $this->params['numbersaxislabel'],
211
			'labelaxislabel' => $this->params['labelaxislabel'],
212
			'charttitle' => $this->params['charttitle'],
213
			'charttext' => $this->params['charttext'],
214
			'theme' => $this->params['theme'] ? $this->params['theme'] : null,
215
			'ticklabels' => $this->params['ticklabels'],
216
			'highlighter' => $this->params['highlighter'],
217
			'direction' => $this->params['direction'],
218
			'smoothlines' => $this->params['smoothlines'],
219
			'filling' => $this->params['filling'],
220
			'datalabels' => $this->params['datalabels'],
221
			'valueformat' => $this->params['valueformat'],
222
			'chartlegend' => $this->params['chartlegend'] !== '' ? $this->params['chartlegend'] : 'none',
223
			'colorscheme' => $this->params['colorscheme'] !== '' ? $this->params['colorscheme'] : null,
224
			'pointlabels' => $this->params['datalabels'] === 'none' ? false : $this->params['datalabels'],
225
			'grid' => $this->params['theme'] === 'vector' ? [ 'borderColor' => '#a7d7f9' ] : ( $this->params['theme'] === 'simple' ? [ 'borderColor' => '#ddd' ] : null ),
226
			'seriescolors' => $seriescolors
227
		];
228
	}
229
230
	/**
231
	 * @see SMWResultPrinter::getParamDefinitions
232
	 *
233
	 * @since 1.8
234
	 *
235
	 * @param $definitions array of IParamDefinition
236
	 *
237
	 * @return array of IParamDefinition|array
238
	 */
239
	public function getParamDefinitions( array $definitions ) {
240
		$params = self::getCommonParams();
241
242
		$params['charttype'] = [
243
			'message' => 'srf-paramdesc-charttype',
244
			'default' => 'bar',
245
			'values' => [ 'bar', 'line', 'pie', 'donut' ],
246
		];
247
248
		return array_merge( parent::getParamDefinitions( $definitions ), $params );
249
	}
250
}
251