Contour::getIsobars()   F
last analyzed

Complexity

Conditions 20
Paths 198

Size

Total Lines 83
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 48
CRAP Score 20.1819

Importance

Changes 0
Metric Value
cc 20
eloc 51
nc 198
nop 0
dl 0
loc 83
ccs 48
cts 52
cp 0.9231
crap 20.1819
rs 3.35
c 0
b 0
f 0

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 1
define('HORIZ_EDGE', 0);
21 1
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 1
    public function __construct($aMatrix, $aIsobars = 10, $aColors = null)
58
    {
59 1
        $this->nbrRows    = safe_count($aMatrix);
60 1
        $this->nbrCols    = safe_count($aMatrix[0]);
61 1
        $this->dataPoints = $aMatrix;
62
63 1
        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 1
            $this->nbrIsobars = $aIsobars;
70 1
            list($min, $max)  = $this->getMinMaxVal();
71 1
            $stepSize         = ($max - $min) / $aIsobars;
72 1
            $isobar           = $min + $stepSize / 2;
73 1
            for ($i = 0; $i < $aIsobars; ++$i) {
74 1
                $this->isobarValues[$i] = $isobar;
75 1
                $isobar += $stepSize;
76
            }
77
        }
78
79 1
        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 1
    }
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
99
     * corner of teh plot otherwise it will correspond to the bottom left corner (a horizontal flip)
100
     */
101 1
    public function SetInvert($aFlg = true)
102
    {
103 1
        $this->invert = $aFlg;
104 1
    }
105
106
    /**
107
     * Find the min and max values in the data matrice.
108
     *
109
     * @return array(min_value,max_value)
110
     */
111 1
    public function getMinMaxVal()
112
    {
113 1
        $min = $this->dataPoints[0][0];
114 1
        $max = $this->dataPoints[0][0];
115 1
        for ($i = 0; $i < $this->nbrRows; ++$i) {
116 1
            if (($mi = min($this->dataPoints[$i])) < $min) {
117 1
                $min = $mi;
118
            }
119
120 1
            if (($ma = max($this->dataPoints[$i])) > $max) {
121 1
                $max = $ma;
122
            }
123
        }
124
125 1
        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 1
    public function resetEdgeMatrices()
133
    {
134 1
        for ($k = 0; $k < 2; ++$k) {
135 1
            for ($i = 0; $i <= $this->nbrRows; ++$i) {
136 1
                for ($j = 0; $j <= $this->nbrCols; ++$j) {
137 1
                    $this->edges[$k][$i][$j] = false;
138
                }
139
            }
140
        }
141 1
    }
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
147
     * @param $aCol Col index of edge to be checked
148
     * @param $aIsobar Isobar value
149
     *
150
     * @return true if the isobar is crossing this edge
151
     */
152 1
    public function isobarHCrossing($aRow, $aCol, $aIsobar)
153
    {
154 1
        if ($aCol >= $this->nbrCols - 1) {
155
            Util\JpGraphError::RaiseL(28003, $aCol);
156
            //'ContourPlot Internal Error: isobarHCrossing: Coloumn index too large (%d)'
157
        }
158 1
        if ($aRow >= $this->nbrRows) {
159
            Util\JpGraphError::RaiseL(28004, $aRow);
160
            //'ContourPlot Internal Error: isobarHCrossing: Row index too large (%d)'
161
        }
162
163 1
        $v1 = $this->dataPoints[$aRow][$aCol];
164 1
        $v2 = $this->dataPoints[$aRow][$aCol + 1];
165
166 1
        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 1
    public function isobarVCrossing($aRow, $aCol, $aIsobar)
179
    {
180 1
        if ($aRow >= $this->nbrRows - 1) {
181
            Util\JpGraphError::RaiseL(28005, $aRow);
182
            //'isobarVCrossing: Row index too large
183
        }
184 1
        if ($aCol >= $this->nbrCols) {
185
            Util\JpGraphError::RaiseL(28006, $aCol);
186
            //'isobarVCrossing: Col index too large
187
        }
188
189 1
        $v1 = $this->dataPoints[$aRow][$aCol];
190 1
        $v2 = $this->dataPoints[$aRow + 1][$aCol];
191
192 1
        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
200
     */
201 1
    public function determineIsobarEdgeCrossings($aIsobar)
202
    {
203 1
        $ib = $this->isobarValues[$aIsobar];
204
205 1
        for ($i = 0; $i < $this->nbrRows - 1; ++$i) {
206 1
            for ($j = 0; $j < $this->nbrCols - 1; ++$j) {
207 1
                $this->edges[HORIZ_EDGE][$i][$j] = $this->isobarHCrossing($i, $j, $ib);
208 1
                $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 1
        for ($i = 0; $i < $this->nbrRows - 1; ++$i) {
214 1
            $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 1
        for ($j = 0; $j < $this->nbrCols - 1; ++$j) {
217 1
            $this->edges[HORIZ_EDGE][$i][$j] = $this->isobarHCrossing($this->nbrRows - 1, $j, $ib);
218
        }
219 1
    }
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
228
     * @param $aEdgeDir Determine if this is a horizontal or vertical edge
229
     * @param $ib The isobar value
230
     * @param mixed $aIsobarVal
231
     *
232
     * @return unknown_type
233
     */
234 1
    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 1
        if ($aEdgeDir == HORIZ_EDGE) {
239 1
            $d = abs($this->dataPoints[$aRow][$aCol] - $this->dataPoints[$aRow][$aCol + 1]);
240 1
            if ($d > 0.001) {
241 1
                $xcoord = $aCol + abs($aIsobarVal - $this->dataPoints[$aRow][$aCol]) / $d;
242
            } else {
243
                $xcoord = $aCol;
244
            }
245 1
            $ycoord = $aRow;
246
        } else {
247 1
            $d = abs($this->dataPoints[$aRow][$aCol] - $this->dataPoints[$aRow + 1][$aCol]);
248 1
            if ($d > 0.001) {
249 1
                $ycoord = $aRow + abs($aIsobarVal - $this->dataPoints[$aRow][$aCol]) / $d;
250
            } else {
251
                $ycoord = $aRow;
252
            }
253 1
            $xcoord = $aCol;
254
        }
255 1
        if ($this->invert) {
256 1
            $ycoord = $this->nbrRows - 1 - $ycoord;
257
        }
258
259 1
        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 1
    public function adjustDataPointValues()
269
    {
270 1
        $ni = safe_count($this->isobarValues);
271 1
        for ($k = 0; $k < $ni; ++$k) {
272 1
            $ib = $this->isobarValues[$k];
273 1
            for ($row = 0; $row < $this->nbrRows - 1; ++$row) {
274 1
                for ($col = 0; $col < $this->nbrCols - 1; ++$col) {
275 1
                    if (abs($this->dataPoints[$row][$col] - $ib) < 0.0001) {
276 1
                        $this->dataPoints[$row][$col] += $this->dataPoints[$row][$col] * 0.001;
277
                    }
278
                }
279
            }
280
        }
281 1
    }
282
283
    /**
284
     * @param $aFlg
285
     * @param $aBW
286
     *
287
     * @return unknown_type
288
     */
289 1
    public function UseHighContrastColor($aFlg = true, $aBW = false)
290
    {
291 1
        $this->highcontrast   = $aFlg;
292 1
        $this->highcontrastbw = $aBW;
293 1
    }
294
295
    /**
296
     * Calculate suitable colors for each defined isobar.
297
     */
298 1
    public function CalculateColors()
299
    {
300 1
        if ($this->highcontrast) {
301 1
            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 1
                $step = round(255 / ($this->nbrIsobars - 1));
308 1
                for ($ib = 0; $ib < $this->nbrIsobars; ++$ib) {
309 1
                    $this->isobarColors[$ib] = [$ib * $step, 50, 255 - $ib * $step];
310
                }
311
            }
312
        } else {
313 1
            $n    = $this->nbrIsobars;
0 ignored issues
show
Unused Code introduced by
The assignment to $n is dead and can be removed.
Loading history...
314 1
            $v    = 0;
315 1
            $step = 1 / ($this->nbrIsobars - 1);
316 1
            for ($ib = 0; $ib < $this->nbrIsobars; ++$ib) {
317 1
                $this->isobarColors[$ib] = Image\RGB::GetSpectrum($v);
318 1
                $v += $step;
319
            }
320
        }
321 1
    }
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 1
    public function getIsobars()
333
    {
334 1
        $this->adjustDataPointValues();
335
336 1
        for ($isobar = 0; $isobar < $this->nbrIsobars; ++$isobar) {
337 1
            $ib = $this->isobarValues[$isobar];
338 1
            $this->resetEdgeMatrices();
339 1
            $this->determineIsobarEdgeCrossings($isobar);
340 1
            $this->isobarCoord[$isobar] = [];
341
342 1
            $ncoord = 0;
343
344 1
            for ($row = 0; $row < $this->nbrRows - 1; ++$row) {
345 1
                for ($col = 0; $col < $this->nbrCols - 1; ++$col) {
346
                    // Find out how many crossings around the edges
347 1
                    $n = 0;
348 1
                    if ($this->edges[HORIZ_EDGE][$row][$col]) {
349 1
                        $neigh[$n++] = [$row, $col, HORIZ_EDGE];
350
                    }
351
352 1
                    if ($this->edges[HORIZ_EDGE][$row + 1][$col]) {
353 1
                        $neigh[$n++] = [$row + 1, $col, HORIZ_EDGE];
354
                    }
355
356 1
                    if ($this->edges[VERT_EDGE][$row][$col]) {
357 1
                        $neigh[$n++] = [$row, $col, VERT_EDGE];
358
                    }
359
360 1
                    if ($this->edges[VERT_EDGE][$row][$col + 1]) {
361 1
                        $neigh[$n++] = [$row, $col + 1, VERT_EDGE];
362
                    }
363
364 1
                    if ($n == 2) {
365 1
                        $n1                                    = 0;
366 1
                        $n2                                    = 1;
367 1
                        $this->isobarCoord[$isobar][$ncoord++] = [
368 1
                            $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 1
                            $this->getCrossingCoord($neigh[$n2][0], $neigh[$n2][1], $neigh[$n2][2], $ib), ];
370 1
                    } 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 1
                        $midval = ($this->dataPoints[$row][$col] + $this->dataPoints[$row][$col + 1] + $this->dataPoints[$row + 1][$col] + $this->dataPoints[$row + 1][$col + 1]) / 4;
376 1
                        $v      = $this->dataPoints[$row][$col];
377 1
                        if ($midval == $ib) {
378
                            // Orientation "+"
379
                            $n1 = 0;
380
                            $n2 = 1;
381
                            $n3 = 2;
382
                            $n4 = 3;
383 1
                        } elseif (($midval > $ib && $v > $ib) || ($midval < $ib && $v < $ib)) {
384
                            // Orientation of ridge/valley = "\"
385 1
                            $n1 = 0;
386 1
                            $n2 = 3;
387 1
                            $n3 = 2;
388 1
                            $n4 = 1;
389 1
                        } elseif (($midval > $ib && $v < $ib) || ($midval < $ib && $v > $ib)) {
390
                            // Orientation of ridge/valley = "/"
391 1
                            $n1 = 0;
392 1
                            $n2 = 2;
393 1
                            $n3 = 3;
394 1
                            $n4 = 1;
395
                        }
396
397 1
                        $this->isobarCoord[$isobar][$ncoord++] = [
398 1
                            $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 1
                            $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 1
                        $this->isobarCoord[$isobar][$ncoord++] = [
402 1
                            $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 1
                            $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 1
        if (safe_count($this->isobarColors) == 0) {
410
            // No manually specified colors. Calculate them automatically.
411 1
            $this->CalculateColors();
412
        }
413
414 1
        return [$this->isobarCoord, $this->isobarValues, $this->isobarColors];
415
    }
416
}
417
418
// EOF
419