Contour::getIsobars()   F
last analyzed

Complexity

Conditions 20
Paths 198

Size

Total Lines 83
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 51
c 0
b 0
f 0
nc 198
nop 0
dl 0
loc 83
rs 3.35

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * JPGraph v4.0.3
5
 */
6
7
namespace Amenadiel\JpGraph\Plot;
8
9
use Amenadiel\JpGraph\Image;
10
use Amenadiel\JpGraph\Util;
11
12
/*
13
 * File:        JPGRAPH_CONTOUR.PHP
14
 * // Description: Contour plot
15
 * // Created:     2009-03-08
16
 * // Ver:         $Id: jpgraph_contour.php 1870 2009-09-29 04:24:18Z ljp $
17
 * //
18
 * // Copyright (c) Asial Corporation. All rights reserved.
19
 */
20
define('HORIZ_EDGE', 0);
21
define('VERT_EDGE', 1);
22
23
/**
24
 * This class encapsulates the core contour plot algorithm. It will find the path
25
 * of the specified isobars in the data matrix specified. It is assumed that the
26
 * data matrix models an equspaced X-Y mesh of datavalues corresponding to the Z
27
 * values.
28
 */
29
class Contour
30
{
31
    private $dataPoints   = [];
32
    private $nbrCols      = 0;
33
    private $nbrRows      = 0;
34
    private $horizEdges   = [];
0 ignored issues
show
introduced by
The private property $horizEdges is not used, and could be removed.
Loading history...
35
    private $vertEdges    = [];
0 ignored issues
show
introduced by
The private property $vertEdges is not used, and could be removed.
Loading history...
36
    private $isobarValues = [];
37
    private $stack;
0 ignored issues
show
introduced by
The private property $stack is not used, and could be removed.
Loading history...
38
    private $isobarCoord    = [];
39
    private $nbrIsobars     = 10;
40
    private $isobarColors   = [];
41
    private $invert         = true;
42
    private $highcontrast   = false;
43
    private $highcontrastbw = false;
44
45
    /**
46
     * Create a new contour level "algorithm machine".
47
     *
48
     * @param $aMatrix    The values to find the contour from
49
     * @param $aIsobars Mixed. If integer it determines the number of isobars to be used. The levels are determined
0 ignored issues
show
Documentation Bug introduced by
The doc comment Mixed. at position 0 could not be parsed: Unknown type name 'Mixed.' at position 0 in Mixed..
Loading history...
50
     * automatically as equdistance between the min and max value of the matrice.
51
     * If $aIsobars is an array then this is interpretated as an array of values to be used as isobars in the
52
     * contour plot.
53
     * @param null|mixed $aColors
54
     *
55
     * @return an instance of the contour algorithm
56
     */
57
    public function __construct($aMatrix, $aIsobars = 10, $aColors = null)
58
    {
59
        $this->nbrRows    = safe_count($aMatrix);
60
        $this->nbrCols    = safe_count($aMatrix[0]);
61
        $this->dataPoints = $aMatrix;
62
63
        if (is_array($aIsobars)) {
64
            // use the isobar values supplied
65
            $this->nbrIsobars   = safe_count($aIsobars);
66
            $this->isobarValues = $aIsobars;
67
        } else {
68
            // Determine the isobar values automatically
69
            $this->nbrIsobars = $aIsobars;
70
            list($min, $max)  = $this->getMinMaxVal();
71
            $stepSize         = ($max - $min) / $aIsobars;
72
            $isobar           = $min + $stepSize / 2;
73
            for ($i = 0; $i < $aIsobars; ++$i) {
74
                $this->isobarValues[$i] = $isobar;
75
                $isobar += $stepSize;
76
            }
77
        }
78
79
        if ($aColors !== null && safe_count($aColors) > 0) {
80
            if (!is_array($aColors)) {
81
                Util\JpGraphError::RaiseL(28001);
82
                //'Third argument to Contour must be an array of colors.'
83
            }
84
85
            if (safe_count($aColors) != safe_count($this->isobarValues)) {
86
                Util\JpGraphError::RaiseL(28002);
87
                //'Number of colors must equal the number of isobar lines specified';
88
            }
89
90
            $this->isobarColors = $aColors;
91
        }
92
    }
93
94
    /**
95
     * Flip the plot around the Y-coordinate. This has the same affect as flipping the input
96
     * data matrice.
97
     *
98
     * @param $aFlg If true the the vertice in input data matrice position (0,0) corresponds to the top left
0 ignored issues
show
Bug introduced by
The type Amenadiel\JpGraph\Plot\If was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
99
     * corner of teh plot otherwise it will correspond to the bottom left corner (a horizontal flip)
100
     */
101
    public function SetInvert($aFlg = true)
102
    {
103
        $this->invert = $aFlg;
104
    }
105
106
    /**
107
     * Find the min and max values in the data matrice.
108
     *
109
     * @return array(min_value,max_value)
110
     */
111
    public function getMinMaxVal()
112
    {
113
        $min = $this->dataPoints[0][0];
114
        $max = $this->dataPoints[0][0];
115
        for ($i = 0; $i < $this->nbrRows; ++$i) {
116
            if (($mi = min($this->dataPoints[$i])) < $min) {
117
                $min = $mi;
118
            }
119
120
            if (($ma = max($this->dataPoints[$i])) > $max) {
121
                $max = $ma;
122
            }
123
        }
124
125
        return [$min, $max];
126
    }
127
128
    /**
129
     * Reset the two matrices that keeps track on where the isobars crosses the
130
     * horizontal and vertical edges.
131
     */
132
    public function resetEdgeMatrices()
133
    {
134
        for ($k = 0; $k < 2; ++$k) {
135
            for ($i = 0; $i <= $this->nbrRows; ++$i) {
136
                for ($j = 0; $j <= $this->nbrCols; ++$j) {
137
                    $this->edges[$k][$i][$j] = false;
0 ignored issues
show
Bug Best Practice introduced by
The property edges does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
138
                }
139
            }
140
        }
141
    }
142
143
    /**
144
     * Determine if the specified isobar crosses the horizontal edge specified by its row and column.
145
     *
146
     * @param $aRow Row index of edge to be checked
0 ignored issues
show
Bug introduced by
The type Amenadiel\JpGraph\Plot\Row was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
147
     * @param $aCol Col index of edge to be checked
0 ignored issues
show
Bug introduced by
The type Amenadiel\JpGraph\Plot\Col was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
148
     * @param $aIsobar Isobar value
0 ignored issues
show
Bug introduced by
The type Amenadiel\JpGraph\Plot\Isobar was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
149
     *
150
     * @return true if the isobar is crossing this edge
151
     */
152
    public function isobarHCrossing($aRow, $aCol, $aIsobar)
153
    {
154
        if ($aCol >= $this->nbrCols - 1) {
155
            Util\JpGraphError::RaiseL(28003, $aCol);
156
            //'ContourPlot Internal Error: isobarHCrossing: Coloumn index too large (%d)'
157
        }
158
        if ($aRow >= $this->nbrRows) {
159
            Util\JpGraphError::RaiseL(28004, $aRow);
160
            //'ContourPlot Internal Error: isobarHCrossing: Row index too large (%d)'
161
        }
162
163
        $v1 = $this->dataPoints[$aRow][$aCol];
164
        $v2 = $this->dataPoints[$aRow][$aCol + 1];
165
166
        return ($aIsobar - $v1) * ($aIsobar - $v2) < 0;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $aIsobar - $v1 * $aIsobar - $v2 < 0 returns the type boolean which is incompatible with the documented return type true.
Loading history...
167
    }
168
169
    /**
170
     * Determine if the specified isobar crosses the vertical edge specified by its row and column.
171
     *
172
     * @param $aRow Row index of edge to be checked
173
     * @param $aCol Col index of edge to be checked
174
     * @param $aIsobar Isobar value
175
     *
176
     * @return true if the isobar is crossing this edge
177
     */
178
    public function isobarVCrossing($aRow, $aCol, $aIsobar)
179
    {
180
        if ($aRow >= $this->nbrRows - 1) {
181
            Util\JpGraphError::RaiseL(28005, $aRow);
182
            //'isobarVCrossing: Row index too large
183
        }
184
        if ($aCol >= $this->nbrCols) {
185
            Util\JpGraphError::RaiseL(28006, $aCol);
186
            //'isobarVCrossing: Col index too large
187
        }
188
189
        $v1 = $this->dataPoints[$aRow][$aCol];
190
        $v2 = $this->dataPoints[$aRow + 1][$aCol];
191
192
        return ($aIsobar - $v1) * ($aIsobar - $v2) < 0;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $aIsobar - $v1 * $aIsobar - $v2 < 0 returns the type boolean which is incompatible with the documented return type true.
Loading history...
193
    }
194
195
    /**
196
     * Determine all edges, horizontal and vertical that the specified isobar crosses. The crossings
197
     * are recorded in the two edge matrices.
198
     *
199
     * @param $aIsobar The value of the isobar to be checked
0 ignored issues
show
Bug introduced by
The type Amenadiel\JpGraph\Plot\The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
200
     */
201
    public function determineIsobarEdgeCrossings($aIsobar)
202
    {
203
        $ib = $this->isobarValues[$aIsobar];
204
205
        for ($i = 0; $i < $this->nbrRows - 1; ++$i) {
206
            for ($j = 0; $j < $this->nbrCols - 1; ++$j) {
207
                $this->edges[HORIZ_EDGE][$i][$j] = $this->isobarHCrossing($i, $j, $ib);
0 ignored issues
show
Bug Best Practice introduced by
The property edges does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
208
                $this->edges[VERT_EDGE][$i][$j]  = $this->isobarVCrossing($i, $j, $ib);
209
            }
210
        }
211
212
        // We now have the bottom and rightmost edges unsearched
213
        for ($i = 0; $i < $this->nbrRows - 1; ++$i) {
214
            $this->edges[VERT_EDGE][$i][$j] = $this->isobarVCrossing($i, $this->nbrCols - 1, $ib);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $j does not seem to be defined for all execution paths leading up to this point.
Loading history...
215
        }
216
        for ($j = 0; $j < $this->nbrCols - 1; ++$j) {
217
            $this->edges[HORIZ_EDGE][$i][$j] = $this->isobarHCrossing($this->nbrRows - 1, $j, $ib);
218
        }
219
    }
220
221
    /**
222
     * Return the normalized coordinates for the crossing of the specified edge with the specified
223
     * isobar- The crossing is simpy detrmined with a linear interpolation between the two vertices
224
     * on each side of the edge and the value of the isobar.
225
     *
226
     * @param $aRow Row of edge
227
     * @param $aCol Column of edge
0 ignored issues
show
Bug introduced by
The type Amenadiel\JpGraph\Plot\Column was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
228
     * @param $aEdgeDir Determine if this is a horizontal or vertical edge
0 ignored issues
show
Bug introduced by
The type Amenadiel\JpGraph\Plot\Determine was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
229
     * @param $ib The isobar value
230
     * @param mixed $aIsobarVal
231
     *
232
     * @return unknown_type
0 ignored issues
show
Bug introduced by
The type Amenadiel\JpGraph\Plot\unknown_type was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
233
     */
234
    public function getCrossingCoord($aRow, $aCol, $aEdgeDir, $aIsobarVal)
235
    {
236
        // In order to avoid numerical problem when two vertices are very close
237
        // we have to check and avoid dividing by close to zero denumerator.
238
        if ($aEdgeDir == HORIZ_EDGE) {
239
            $d = abs($this->dataPoints[$aRow][$aCol] - $this->dataPoints[$aRow][$aCol + 1]);
240
            if ($d > 0.001) {
241
                $xcoord = $aCol + abs($aIsobarVal - $this->dataPoints[$aRow][$aCol]) / $d;
242
            } else {
243
                $xcoord = $aCol;
244
            }
245
            $ycoord = $aRow;
246
        } else {
247
            $d = abs($this->dataPoints[$aRow][$aCol] - $this->dataPoints[$aRow + 1][$aCol]);
248
            if ($d > 0.001) {
249
                $ycoord = $aRow + abs($aIsobarVal - $this->dataPoints[$aRow][$aCol]) / $d;
250
            } else {
251
                $ycoord = $aRow;
252
            }
253
            $xcoord = $aCol;
254
        }
255
        if ($this->invert) {
256
            $ycoord = $this->nbrRows - 1 - $ycoord;
257
        }
258
259
        return [$xcoord, $ycoord];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($xcoord, $ycoord) returns the type array which is incompatible with the documented return type Amenadiel\JpGraph\Plot\unknown_type.
Loading history...
260
    }
261
262
    /**
263
     * In order to avoid all kinds of unpleasent extra checks and complex boundary
264
     * controls for the degenerated case where the contour levels exactly crosses
265
     * one of the vertices we add a very small delta (0.1%) to the data point value.
266
     * This has no visible affect but it makes the code sooooo much cleaner.
267
     */
268
    public function adjustDataPointValues()
269
    {
270
        $ni = safe_count($this->isobarValues);
271
        for ($k = 0; $k < $ni; ++$k) {
272
            $ib = $this->isobarValues[$k];
273
            for ($row = 0; $row < $this->nbrRows - 1; ++$row) {
274
                for ($col = 0; $col < $this->nbrCols - 1; ++$col) {
275
                    if (abs($this->dataPoints[$row][$col] - $ib) < 0.0001) {
276
                        $this->dataPoints[$row][$col] += $this->dataPoints[$row][$col] * 0.001;
277
                    }
278
                }
279
            }
280
        }
281
    }
282
283
    /**
284
     * @param $aFlg
285
     * @param $aBW
286
     *
287
     * @return unknown_type
288
     */
289
    public function UseHighContrastColor($aFlg = true, $aBW = false)
290
    {
291
        $this->highcontrast   = $aFlg;
292
        $this->highcontrastbw = $aBW;
293
    }
294
295
    /**
296
     * Calculate suitable colors for each defined isobar.
297
     */
298
    public function CalculateColors()
299
    {
300
        if ($this->highcontrast) {
301
            if ($this->highcontrastbw) {
302
                for ($ib = 0; $ib < $this->nbrIsobars; ++$ib) {
303
                    $this->isobarColors[$ib] = 'black';
304
                }
305
            } else {
306
                // Use only blue/red scale
307
                $step = round(255 / ($this->nbrIsobars - 1));
308
                for ($ib = 0; $ib < $this->nbrIsobars; ++$ib) {
309
                    $this->isobarColors[$ib] = [$ib * $step, 50, 255 - $ib * $step];
310
                }
311
            }
312
        } else {
313
            $n    = $this->nbrIsobars;
0 ignored issues
show
Unused Code introduced by
The assignment to $n is dead and can be removed.
Loading history...
314
            $v    = 0;
315
            $step = 1 / ($this->nbrIsobars - 1);
316
            for ($ib = 0; $ib < $this->nbrIsobars; ++$ib) {
317
                $this->isobarColors[$ib] = Image\RGB::GetSpectrum($v);
318
                $v += $step;
319
            }
320
        }
321
    }
322
323
    /**
324
     * This is where the main work is done. For each isobar the crossing of the edges are determined
325
     * and then each cell is analyzed to find the 0, 2 or 4 crossings. Then the normalized coordinate
326
     * for the crossings are determined and pushed on to the isobar stack. When the method is finished
327
     * the $isobarCoord will hold one arrayfor each isobar where all the line segments that makes
328
     * up the contour plot are stored.
329
     *
330
     * @return array( $isobarCoord, $isobarValues, $isobarColors )
331
     */
332
    public function getIsobars()
333
    {
334
        $this->adjustDataPointValues();
335
336
        for ($isobar = 0; $isobar < $this->nbrIsobars; ++$isobar) {
337
            $ib = $this->isobarValues[$isobar];
338
            $this->resetEdgeMatrices();
339
            $this->determineIsobarEdgeCrossings($isobar);
340
            $this->isobarCoord[$isobar] = [];
341
342
            $ncoord = 0;
343
344
            for ($row = 0; $row < $this->nbrRows - 1; ++$row) {
345
                for ($col = 0; $col < $this->nbrCols - 1; ++$col) {
346
                    // Find out how many crossings around the edges
347
                    $n = 0;
348
                    if ($this->edges[HORIZ_EDGE][$row][$col]) {
349
                        $neigh[$n++] = [$row, $col, HORIZ_EDGE];
350
                    }
351
352
                    if ($this->edges[HORIZ_EDGE][$row + 1][$col]) {
353
                        $neigh[$n++] = [$row + 1, $col, HORIZ_EDGE];
354
                    }
355
356
                    if ($this->edges[VERT_EDGE][$row][$col]) {
357
                        $neigh[$n++] = [$row, $col, VERT_EDGE];
358
                    }
359
360
                    if ($this->edges[VERT_EDGE][$row][$col + 1]) {
361
                        $neigh[$n++] = [$row, $col + 1, VERT_EDGE];
362
                    }
363
364
                    if ($n == 2) {
365
                        $n1                                    = 0;
366
                        $n2                                    = 1;
367
                        $this->isobarCoord[$isobar][$ncoord++] = [
368
                            $this->getCrossingCoord($neigh[$n1][0], $neigh[$n1][1], $neigh[$n1][2], $ib),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $neigh does not seem to be defined for all execution paths leading up to this point.
Loading history...
369
                            $this->getCrossingCoord($neigh[$n2][0], $neigh[$n2][1], $neigh[$n2][2], $ib), ];
370
                    } elseif ($n == 4) {
371
                        // We must determine how to connect the edges either northwest->southeast or
372
                        // northeast->southwest. We do that by calculating the imaginary middle value of
373
                        // the cell by averaging the for corners. This will compared with the value of the
374
                        // top left corner will help determine the orientation of the ridge/creek
375
                        $midval = ($this->dataPoints[$row][$col] + $this->dataPoints[$row][$col + 1] + $this->dataPoints[$row + 1][$col] + $this->dataPoints[$row + 1][$col + 1]) / 4;
376
                        $v      = $this->dataPoints[$row][$col];
377
                        if ($midval == $ib) {
378
                            // Orientation "+"
379
                            $n1 = 0;
380
                            $n2 = 1;
381
                            $n3 = 2;
382
                            $n4 = 3;
383
                        } elseif (($midval > $ib && $v > $ib) || ($midval < $ib && $v < $ib)) {
384
                            // Orientation of ridge/valley = "\"
385
                            $n1 = 0;
386
                            $n2 = 3;
387
                            $n3 = 2;
388
                            $n4 = 1;
389
                        } elseif (($midval > $ib && $v < $ib) || ($midval < $ib && $v > $ib)) {
390
                            // Orientation of ridge/valley = "/"
391
                            $n1 = 0;
392
                            $n2 = 2;
393
                            $n3 = 3;
394
                            $n4 = 1;
395
                        }
396
397
                        $this->isobarCoord[$isobar][$ncoord++] = [
398
                            $this->getCrossingCoord($neigh[$n1][0], $neigh[$n1][1], $neigh[$n1][2], $ib),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $n1 does not seem to be defined for all execution paths leading up to this point.
Loading history...
399
                            $this->getCrossingCoord($neigh[$n2][0], $neigh[$n2][1], $neigh[$n2][2], $ib), ];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $n2 does not seem to be defined for all execution paths leading up to this point.
Loading history...
400
401
                        $this->isobarCoord[$isobar][$ncoord++] = [
402
                            $this->getCrossingCoord($neigh[$n3][0], $neigh[$n3][1], $neigh[$n3][2], $ib),
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $n3 does not seem to be defined for all execution paths leading up to this point.
Loading history...
403
                            $this->getCrossingCoord($neigh[$n4][0], $neigh[$n4][1], $neigh[$n4][2], $ib), ];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $n4 does not seem to be defined for all execution paths leading up to this point.
Loading history...
404
                    }
405
                }
406
            }
407
        }
408
409
        if (safe_count($this->isobarColors) == 0) {
410
            // No manually specified colors. Calculate them automatically.
411
            $this->CalculateColors();
412
        }
413
414
        return [$this->isobarCoord, $this->isobarValues, $this->isobarColors];
415
    }
416
}
417
418
// EOF
419