| 1 | <?php
 | 
|---|
| 2 | /*=======================================================================
 | 
|---|
| 3 | // File:        JPGRAPH_CONTOUR.PHP
 | 
|---|
| 4 | // Description: Contour plot
 | 
|---|
| 5 | // Created:     2009-03-08
 | 
|---|
| 6 | // Ver:         $Id: jpgraph_contour.php 1870 2009-09-29 04:24:18Z ljp $
 | 
|---|
| 7 | //
 | 
|---|
| 8 | // Copyright (c) Asial Corporation. All rights reserved.
 | 
|---|
| 9 | //========================================================================
 | 
|---|
| 10 | */
 | 
|---|
| 11 | require_once('jpgraph_meshinterpolate.inc.php');
 | 
|---|
| 12 | define('HORIZ_EDGE',0);
 | 
|---|
| 13 | define('VERT_EDGE',1);
 | 
|---|
| 14 | 
 | 
|---|
| 15 | /**
 | 
|---|
| 16 |  * This class encapsulates the core contour plot algorithm. It will find the path
 | 
|---|
| 17 |  * of the specified isobars in the data matrix specified. It is assumed that the
 | 
|---|
| 18 |  * data matrix models an equspaced X-Y mesh of datavalues corresponding to the Z
 | 
|---|
| 19 |  * values.
 | 
|---|
| 20 |  *
 | 
|---|
| 21 |  */
 | 
|---|
| 22 | class Contour {
 | 
|---|
| 23 | 
 | 
|---|
| 24 |     private $dataPoints = array();
 | 
|---|
| 25 |     private $nbrCols=0,$nbrRows=0;
 | 
|---|
| 26 |     private $horizEdges = array(), $vertEdges=array();
 | 
|---|
| 27 |     private $isobarValues = array();
 | 
|---|
| 28 |     private $stack = null;
 | 
|---|
| 29 |     private $isobarCoord = array();
 | 
|---|
| 30 |     private $nbrIsobars = 10, $isobarColors = array();
 | 
|---|
| 31 |     private $invert = true;
 | 
|---|
| 32 |     private $highcontrast = false, $highcontrastbw = false;
 | 
|---|
| 33 | 
 | 
|---|
| 34 |     /**
 | 
|---|
| 35 |      * Create a new contour level "algorithm machine".
 | 
|---|
| 36 |      * @param $aMatrix    The values to find the contour from
 | 
|---|
| 37 |      * @param $aIsobars Mixed. If integer it determines the number of isobars to be used. The levels are determined
 | 
|---|
| 38 |      * automatically as equdistance between the min and max value of the matrice.
 | 
|---|
| 39 |      * If $aIsobars is an array then this is interpretated as an array of values to be used as isobars in the
 | 
|---|
| 40 |      * contour plot.
 | 
|---|
| 41 |      * @return an instance of the contour algorithm
 | 
|---|
| 42 |      */
 | 
|---|
| 43 |     function __construct($aMatrix,$aIsobars=10, $aColors=null) {
 | 
|---|
| 44 | 
 | 
|---|
| 45 |         $this->nbrRows = count($aMatrix);
 | 
|---|
| 46 |         $this->nbrCols = count($aMatrix[0]);
 | 
|---|
| 47 |         $this->dataPoints = $aMatrix;
 | 
|---|
| 48 | 
 | 
|---|
| 49 |         if( is_array($aIsobars) ) {
 | 
|---|
| 50 |             // use the isobar values supplied
 | 
|---|
| 51 |             $this->nbrIsobars = count($aIsobars);
 | 
|---|
| 52 |             $this->isobarValues = $aIsobars;
 | 
|---|
| 53 |         }
 | 
|---|
| 54 |         else {
 | 
|---|
| 55 |             // Determine the isobar values automatically
 | 
|---|
| 56 |             $this->nbrIsobars = $aIsobars;
 | 
|---|
| 57 |             list($min,$max) = $this->getMinMaxVal();
 | 
|---|
| 58 |             $stepSize = ($max-$min) / $aIsobars ;
 | 
|---|
| 59 |             $isobar = $min+$stepSize/2;
 | 
|---|
| 60 |             for ($i = 0; $i < $aIsobars; $i++) {
 | 
|---|
| 61 |                 $this->isobarValues[$i] = $isobar;
 | 
|---|
| 62 |                 $isobar += $stepSize;
 | 
|---|
| 63 |             }
 | 
|---|
| 64 |         }
 | 
|---|
| 65 | 
 | 
|---|
| 66 |         if( $aColors !== null && count($aColors) > 0 ) {
 | 
|---|
| 67 | 
 | 
|---|
| 68 |             if( !is_array($aColors) ) {
 | 
|---|
| 69 |                 JpGraphError::RaiseL(28001);
 | 
|---|
| 70 |                 //'Third argument to Contour must be an array of colors.'
 | 
|---|
| 71 |             }
 | 
|---|
| 72 | 
 | 
|---|
| 73 |             if( count($aColors) != count($this->isobarValues) ) {
 | 
|---|
| 74 |                 JpGraphError::RaiseL(28002);
 | 
|---|
| 75 |                 //'Number of colors must equal the number of isobar lines specified';
 | 
|---|
| 76 |             }
 | 
|---|
| 77 | 
 | 
|---|
| 78 |             $this->isobarColors = $aColors;
 | 
|---|
| 79 |         }
 | 
|---|
| 80 |     }
 | 
|---|
| 81 | 
 | 
|---|
| 82 |     /**
 | 
|---|
| 83 |      * Flip the plot around the Y-coordinate. This has the same affect as flipping the input
 | 
|---|
| 84 |      * data matrice
 | 
|---|
| 85 |      *
 | 
|---|
| 86 |      * @param $aFlg If true the the vertice in input data matrice position (0,0) corresponds to the top left
 | 
|---|
| 87 |      * corner of teh plot otherwise it will correspond to the bottom left corner (a horizontal flip)
 | 
|---|
| 88 |      */
 | 
|---|
| 89 |     function SetInvert($aFlg=true) {
 | 
|---|
| 90 |         $this->invert = $aFlg;
 | 
|---|
| 91 |     }
 | 
|---|
| 92 | 
 | 
|---|
| 93 |     /**
 | 
|---|
| 94 |      * Find the min and max values in the data matrice
 | 
|---|
| 95 |      *
 | 
|---|
| 96 |      * @return array(min_value,max_value)
 | 
|---|
| 97 |      */
 | 
|---|
| 98 |     function getMinMaxVal() {
 | 
|---|
| 99 |         $min = $this->dataPoints[0][0];
 | 
|---|
| 100 |         $max = $this->dataPoints[0][0];
 | 
|---|
| 101 |         for ($i = 0; $i < $this->nbrRows; $i++) {
 | 
|---|
| 102 |             if( ($mi=min($this->dataPoints[$i])) < $min )  $min = $mi;
 | 
|---|
| 103 |             if( ($ma=max($this->dataPoints[$i])) > $max )  $max = $ma;
 | 
|---|
| 104 |         }
 | 
|---|
| 105 |         return array($min,$max);
 | 
|---|
| 106 |     }
 | 
|---|
| 107 | 
 | 
|---|
| 108 |     /**
 | 
|---|
| 109 |      * Reset the two matrices that keeps track on where the isobars crosses the
 | 
|---|
| 110 |      * horizontal and vertical edges
 | 
|---|
| 111 |      */
 | 
|---|
| 112 |     function resetEdgeMatrices() {
 | 
|---|
| 113 |         for ($k = 0; $k < 2; $k++) {
 | 
|---|
| 114 |             for ($i = 0; $i <= $this->nbrRows; $i++) {
 | 
|---|
| 115 |                 for ($j = 0; $j <= $this->nbrCols; $j++) {
 | 
|---|
| 116 |                     $this->edges[$k][$i][$j] = false;
 | 
|---|
| 117 |                 }
 | 
|---|
| 118 |             }
 | 
|---|
| 119 |         }
 | 
|---|
| 120 |     }
 | 
|---|
| 121 | 
 | 
|---|
| 122 |     /**
 | 
|---|
| 123 |      * Determine if the specified isobar crosses the horizontal edge specified by its row and column
 | 
|---|
| 124 |      *
 | 
|---|
| 125 |      * @param $aRow Row index of edge to be checked
 | 
|---|
| 126 |      * @param $aCol Col index of edge to be checked
 | 
|---|
| 127 |      * @param $aIsobar Isobar value
 | 
|---|
| 128 |      * @return true if the isobar is crossing this edge
 | 
|---|
| 129 |      */
 | 
|---|
| 130 |     function isobarHCrossing($aRow,$aCol,$aIsobar) {
 | 
|---|
| 131 | 
 | 
|---|
| 132 |         if( $aCol >= $this->nbrCols-1 ) {
 | 
|---|
| 133 |             JpGraphError::RaiseL(28003,$aCol);
 | 
|---|
| 134 |             //'ContourPlot Internal Error: isobarHCrossing: Coloumn index too large (%d)'
 | 
|---|
| 135 |         }
 | 
|---|
| 136 |         if( $aRow >= $this->nbrRows ) {
 | 
|---|
| 137 |             JpGraphError::RaiseL(28004,$aRow);
 | 
|---|
| 138 |             //'ContourPlot Internal Error: isobarHCrossing: Row index too large (%d)'
 | 
|---|
| 139 |         }
 | 
|---|
| 140 | 
 | 
|---|
| 141 |         $v1 = $this->dataPoints[$aRow][$aCol];
 | 
|---|
| 142 |         $v2 = $this->dataPoints[$aRow][$aCol+1];
 | 
|---|
| 143 | 
 | 
|---|
| 144 |         return ($aIsobar-$v1)*($aIsobar-$v2) < 0 ;
 | 
|---|
| 145 | 
 | 
|---|
| 146 |     }
 | 
|---|
| 147 | 
 | 
|---|
| 148 |     /**
 | 
|---|
| 149 |      * Determine if the specified isobar crosses the vertical edge specified by its row and column
 | 
|---|
| 150 |      *
 | 
|---|
| 151 |      * @param $aRow Row index of edge to be checked
 | 
|---|
| 152 |      * @param $aCol Col index of edge to be checked
 | 
|---|
| 153 |      * @param $aIsobar Isobar value
 | 
|---|
| 154 |      * @return true if the isobar is crossing this edge
 | 
|---|
| 155 |      */
 | 
|---|
| 156 |     function isobarVCrossing($aRow,$aCol,$aIsobar) {
 | 
|---|
| 157 | 
 | 
|---|
| 158 |         if( $aRow >= $this->nbrRows-1) {
 | 
|---|
| 159 |             JpGraphError::RaiseL(28005,$aRow);
 | 
|---|
| 160 |             //'isobarVCrossing: Row index too large
 | 
|---|
| 161 |         }
 | 
|---|
| 162 |         if( $aCol >= $this->nbrCols ) {
 | 
|---|
| 163 |             JpGraphError::RaiseL(28006,$aCol);
 | 
|---|
| 164 |             //'isobarVCrossing: Col index too large
 | 
|---|
| 165 |         }
 | 
|---|
| 166 | 
 | 
|---|
| 167 |         $v1 = $this->dataPoints[$aRow][$aCol];
 | 
|---|
| 168 |         $v2 = $this->dataPoints[$aRow+1][$aCol];
 | 
|---|
| 169 | 
 | 
|---|
| 170 |         return ($aIsobar-$v1)*($aIsobar-$v2) < 0 ;
 | 
|---|
| 171 | 
 | 
|---|
| 172 |     }
 | 
|---|
| 173 | 
 | 
|---|
| 174 |     /**
 | 
|---|
| 175 |      * Determine all edges, horizontal and vertical that the specified isobar crosses. The crossings
 | 
|---|
| 176 |      * are recorded in the two edge matrices.
 | 
|---|
| 177 |      *
 | 
|---|
| 178 |      * @param $aIsobar The value of the isobar to be checked
 | 
|---|
| 179 |      */
 | 
|---|
| 180 |     function determineIsobarEdgeCrossings($aIsobar) {
 | 
|---|
| 181 | 
 | 
|---|
| 182 |         $ib = $this->isobarValues[$aIsobar];
 | 
|---|
| 183 | 
 | 
|---|
| 184 |         for ($i = 0; $i < $this->nbrRows-1; $i++) {
 | 
|---|
| 185 |             for ($j = 0; $j < $this->nbrCols-1; $j++) {
 | 
|---|
| 186 |                 $this->edges[HORIZ_EDGE][$i][$j] = $this->isobarHCrossing($i,$j,$ib);
 | 
|---|
| 187 |                 $this->edges[VERT_EDGE][$i][$j] = $this->isobarVCrossing($i,$j,$ib);
 | 
|---|
| 188 |             }
 | 
|---|
| 189 |         }
 | 
|---|
| 190 | 
 | 
|---|
| 191 |         // We now have the bottom and rightmost edges unsearched
 | 
|---|
| 192 |         for ($i = 0; $i < $this->nbrRows-1; $i++) {
 | 
|---|
| 193 |             $this->edges[VERT_EDGE][$i][$j] = $this->isobarVCrossing($i,$this->nbrCols-1,$ib);
 | 
|---|
| 194 |         }
 | 
|---|
| 195 |         for ($j = 0; $j < $this->nbrCols-1; $j++) {
 | 
|---|
| 196 |             $this->edges[HORIZ_EDGE][$i][$j] = $this->isobarHCrossing($this->nbrRows-1,$j,$ib);
 | 
|---|
| 197 |         }
 | 
|---|
| 198 | 
 | 
|---|
| 199 |     }
 | 
|---|
| 200 | 
 | 
|---|
| 201 |     /**
 | 
|---|
| 202 |      * Return the normalized coordinates for the crossing of the specified edge with the specified
 | 
|---|
| 203 |      * isobar- The crossing is simpy detrmined with a linear interpolation between the two vertices
 | 
|---|
| 204 |      * on each side of the edge and the value of the isobar
 | 
|---|
| 205 |      *
 | 
|---|
| 206 |      * @param $aRow Row of edge
 | 
|---|
| 207 |      * @param $aCol Column of edge
 | 
|---|
| 208 |      * @param $aEdgeDir Determine if this is a horizontal or vertical edge
 | 
|---|
| 209 |      * @param $ib The isobar value
 | 
|---|
| 210 |      * @return unknown_type
 | 
|---|
| 211 |      */
 | 
|---|
| 212 |     function getCrossingCoord($aRow,$aCol,$aEdgeDir,$aIsobarVal) {
 | 
|---|
| 213 | 
 | 
|---|
| 214 |         // In order to avoid numerical problem when two vertices are very close
 | 
|---|
| 215 |         // we have to check and avoid dividing by close to zero denumerator.
 | 
|---|
| 216 |         if( $aEdgeDir == HORIZ_EDGE ) {
 | 
|---|
| 217 |             $d = abs($this->dataPoints[$aRow][$aCol] - $this->dataPoints[$aRow][$aCol+1]);
 | 
|---|
| 218 |             if( $d > 0.001 ) {
 | 
|---|
| 219 |                 $xcoord = $aCol + abs($aIsobarVal - $this->dataPoints[$aRow][$aCol]) / $d;
 | 
|---|
| 220 |             }
 | 
|---|
| 221 |             else {
 | 
|---|
| 222 |                 $xcoord = $aCol;
 | 
|---|
| 223 |             }
 | 
|---|
| 224 |             $ycoord = $aRow;
 | 
|---|
| 225 |         }
 | 
|---|
| 226 |         else {
 | 
|---|
| 227 |             $d = abs($this->dataPoints[$aRow][$aCol] - $this->dataPoints[$aRow+1][$aCol]);
 | 
|---|
| 228 |             if( $d > 0.001 ) {
 | 
|---|
| 229 |                 $ycoord = $aRow + abs($aIsobarVal - $this->dataPoints[$aRow][$aCol]) / $d;
 | 
|---|
| 230 |             }
 | 
|---|
| 231 |             else {
 | 
|---|
| 232 |                 $ycoord = $aRow;
 | 
|---|
| 233 |             }
 | 
|---|
| 234 |             $xcoord = $aCol;
 | 
|---|
| 235 |         }
 | 
|---|
| 236 |         if( $this->invert ) {
 | 
|---|
| 237 |             $ycoord = $this->nbrRows-1 - $ycoord;
 | 
|---|
| 238 |         }
 | 
|---|
| 239 |         return array($xcoord,$ycoord);
 | 
|---|
| 240 | 
 | 
|---|
| 241 |     }
 | 
|---|
| 242 | 
 | 
|---|
| 243 |     /**
 | 
|---|
| 244 |      * In order to avoid all kinds of unpleasent extra checks and complex boundary
 | 
|---|
| 245 |      * controls for the degenerated case where the contour levels exactly crosses
 | 
|---|
| 246 |      * one of the vertices we add a very small delta (0.1%) to the data point value.
 | 
|---|
| 247 |      * This has no visible affect but it makes the code sooooo much cleaner.
 | 
|---|
| 248 |      *
 | 
|---|
| 249 |      */
 | 
|---|
| 250 |     function adjustDataPointValues() {
 | 
|---|
| 251 | 
 | 
|---|
| 252 |         $ni = count($this->isobarValues);
 | 
|---|
| 253 |         for ($k = 0; $k < $ni; $k++) {
 | 
|---|
| 254 |             $ib = $this->isobarValues[$k];
 | 
|---|
| 255 |             for ($row = 0 ; $row < $this->nbrRows-1; ++$row) {
 | 
|---|
| 256 |                 for ($col = 0 ; $col < $this->nbrCols-1; ++$col ) {
 | 
|---|
| 257 |                     if( abs($this->dataPoints[$row][$col] - $ib) < 0.0001 ) {
 | 
|---|
| 258 |                         $this->dataPoints[$row][$col] += $this->dataPoints[$row][$col]*0.001;
 | 
|---|
| 259 |                     }
 | 
|---|
| 260 |                 }
 | 
|---|
| 261 |             }
 | 
|---|
| 262 |         }
 | 
|---|
| 263 | 
 | 
|---|
| 264 |     }
 | 
|---|
| 265 | 
 | 
|---|
| 266 |     /**
 | 
|---|
| 267 |      * @param $aFlg
 | 
|---|
| 268 |      * @param $aBW
 | 
|---|
| 269 |      * @return unknown_type
 | 
|---|
| 270 |      */
 | 
|---|
| 271 |     function UseHighContrastColor($aFlg=true,$aBW=false) {
 | 
|---|
| 272 |         $this->highcontrast = $aFlg;
 | 
|---|
| 273 |         $this->highcontrastbw = $aBW;
 | 
|---|
| 274 |     }
 | 
|---|
| 275 | 
 | 
|---|
| 276 |     /**
 | 
|---|
| 277 |      * Calculate suitable colors for each defined isobar
 | 
|---|
| 278 |      *
 | 
|---|
| 279 |      */
 | 
|---|
| 280 |     function CalculateColors() {
 | 
|---|
| 281 |         if ( $this->highcontrast ) {
 | 
|---|
| 282 |             if ( $this->highcontrastbw ) {
 | 
|---|
| 283 |                 for ($ib = 0; $ib < $this->nbrIsobars; $ib++) {
 | 
|---|
| 284 |                     $this->isobarColors[$ib] = 'black';
 | 
|---|
| 285 |                 }
 | 
|---|
| 286 |             }
 | 
|---|
| 287 |             else {
 | 
|---|
| 288 |                 // Use only blue/red scale
 | 
|---|
| 289 |                 $step = round(255/($this->nbrIsobars-1));
 | 
|---|
| 290 |                 for ($ib = 0; $ib < $this->nbrIsobars; $ib++) {
 | 
|---|
| 291 |                     $this->isobarColors[$ib] = array($ib*$step, 50, 255-$ib*$step);
 | 
|---|
| 292 |                 }
 | 
|---|
| 293 |             }
 | 
|---|
| 294 |         }
 | 
|---|
| 295 |         else {
 | 
|---|
| 296 |             $n = $this->nbrIsobars;
 | 
|---|
| 297 |             $v = 0; $step = 1 / ($this->nbrIsobars-1);
 | 
|---|
| 298 |             for ($ib = 0; $ib < $this->nbrIsobars; $ib++) {
 | 
|---|
| 299 |                 $this->isobarColors[$ib] = RGB::GetSpectrum($v);
 | 
|---|
| 300 |                 $v += $step;
 | 
|---|
| 301 |             }
 | 
|---|
| 302 |         }
 | 
|---|
| 303 |     }
 | 
|---|
| 304 | 
 | 
|---|
| 305 |     /**
 | 
|---|
| 306 |      * This is where the main work is done. For each isobar the crossing of the edges are determined
 | 
|---|
| 307 |      * and then each cell is analyzed to find the 0, 2 or 4 crossings. Then the normalized coordinate
 | 
|---|
| 308 |      * for the crossings are determined and pushed on to the isobar stack. When the method is finished
 | 
|---|
| 309 |      * the $isobarCoord will hold one arrayfor each isobar where all the line segments that makes
 | 
|---|
| 310 |      * up the contour plot are stored.
 | 
|---|
| 311 |      *
 | 
|---|
| 312 |      * @return array( $isobarCoord, $isobarValues, $isobarColors )
 | 
|---|
| 313 |      */
 | 
|---|
| 314 |     function getIsobars() {
 | 
|---|
| 315 | 
 | 
|---|
| 316 |         $this->adjustDataPointValues();
 | 
|---|
| 317 | 
 | 
|---|
| 318 |         for ($isobar = 0; $isobar < $this->nbrIsobars; $isobar++) {
 | 
|---|
| 319 | 
 | 
|---|
| 320 |             $ib = $this->isobarValues[$isobar];
 | 
|---|
| 321 |             $this->resetEdgeMatrices();
 | 
|---|
| 322 |             $this->determineIsobarEdgeCrossings($isobar);
 | 
|---|
| 323 |             $this->isobarCoord[$isobar] = array();
 | 
|---|
| 324 | 
 | 
|---|
| 325 |             $ncoord = 0;
 | 
|---|
| 326 | 
 | 
|---|
| 327 |             for ($row = 0 ; $row < $this->nbrRows-1; ++$row) {
 | 
|---|
| 328 |                 for ($col = 0 ; $col < $this->nbrCols-1; ++$col ) {
 | 
|---|
| 329 | 
 | 
|---|
| 330 |                     // Find out how many crossings around the edges
 | 
|---|
| 331 |                     $n = 0;
 | 
|---|
| 332 |                     if ( $this->edges[HORIZ_EDGE][$row][$col] )   $neigh[$n++] = array($row,  $col,  HORIZ_EDGE);
 | 
|---|
| 333 |                     if ( $this->edges[HORIZ_EDGE][$row+1][$col] ) $neigh[$n++] = array($row+1,$col,  HORIZ_EDGE);
 | 
|---|
| 334 |                     if ( $this->edges[VERT_EDGE][$row][$col] )    $neigh[$n++] = array($row,  $col,  VERT_EDGE);
 | 
|---|
| 335 |                     if ( $this->edges[VERT_EDGE][$row][$col+1] )  $neigh[$n++] = array($row,  $col+1,VERT_EDGE);
 | 
|---|
| 336 | 
 | 
|---|
| 337 |                     if ( $n == 2 ) {
 | 
|---|
| 338 |                         $n1=0; $n2=1;
 | 
|---|
| 339 |                         $this->isobarCoord[$isobar][$ncoord++] = array(
 | 
|---|
| 340 |                         $this->getCrossingCoord($neigh[$n1][0],$neigh[$n1][1],$neigh[$n1][2],$ib),
 | 
|---|
| 341 |                         $this->getCrossingCoord($neigh[$n2][0],$neigh[$n2][1],$neigh[$n2][2],$ib) );
 | 
|---|
| 342 |                     }
 | 
|---|
| 343 |                     elseif ( $n == 4 ) {
 | 
|---|
| 344 |                         // We must determine how to connect the edges either northwest->southeast or
 | 
|---|
| 345 |                         // northeast->southwest. We do that by calculating the imaginary middle value of
 | 
|---|
| 346 |                         // the cell by averaging the for corners. This will compared with the value of the
 | 
|---|
| 347 |                         // top left corner will help determine the orientation of the ridge/creek
 | 
|---|
| 348 |                         $midval = ($this->dataPoints[$row][$col]+$this->dataPoints[$row][$col+1]+$this->dataPoints[$row+1][$col]+$this->dataPoints[$row+1][$col+1])/4;
 | 
|---|
| 349 |                         $v = $this->dataPoints[$row][$col];
 | 
|---|
| 350 |                         if( $midval == $ib ) {
 | 
|---|
| 351 |                             // Orientation "+"
 | 
|---|
| 352 |                             $n1=0; $n2=1; $n3=2; $n4=3;
 | 
|---|
| 353 |                         } elseif ( ($midval > $ib && $v > $ib) ||  ($midval < $ib && $v < $ib) ) {
 | 
|---|
| 354 |                             // Orientation of ridge/valley = "\"
 | 
|---|
| 355 |                             $n1=0; $n2=3; $n3=2; $n4=1;
 | 
|---|
| 356 |                         } elseif ( ($midval > $ib && $v < $ib) ||  ($midval < $ib && $v > $ib) ) {
 | 
|---|
| 357 |                             // Orientation of ridge/valley = "/"
 | 
|---|
| 358 |                             $n1=0; $n2=2; $n3=3; $n4=1;
 | 
|---|
| 359 |                         }
 | 
|---|
| 360 | 
 | 
|---|
| 361 |                         $this->isobarCoord[$isobar][$ncoord++] = array(
 | 
|---|
| 362 |                         $this->getCrossingCoord($neigh[$n1][0],$neigh[$n1][1],$neigh[$n1][2],$ib),
 | 
|---|
| 363 |                         $this->getCrossingCoord($neigh[$n2][0],$neigh[$n2][1],$neigh[$n2][2],$ib) );
 | 
|---|
| 364 | 
 | 
|---|
| 365 |                         $this->isobarCoord[$isobar][$ncoord++] = array(
 | 
|---|
| 366 |                         $this->getCrossingCoord($neigh[$n3][0],$neigh[$n3][1],$neigh[$n3][2],$ib),
 | 
|---|
| 367 |                         $this->getCrossingCoord($neigh[$n4][0],$neigh[$n4][1],$neigh[$n4][2],$ib) );
 | 
|---|
| 368 | 
 | 
|---|
| 369 |                     }
 | 
|---|
| 370 |                 }
 | 
|---|
| 371 |             }
 | 
|---|
| 372 |         }
 | 
|---|
| 373 | 
 | 
|---|
| 374 |         if( count($this->isobarColors) == 0 ) {
 | 
|---|
| 375 |             // No manually specified colors. Calculate them automatically.
 | 
|---|
| 376 |             $this->CalculateColors();
 | 
|---|
| 377 |         }
 | 
|---|
| 378 |         return array( $this->isobarCoord, $this->isobarValues, $this->isobarColors );
 | 
|---|
| 379 |     }
 | 
|---|
| 380 | }
 | 
|---|
| 381 | 
 | 
|---|
| 382 | 
 | 
|---|
| 383 | /**
 | 
|---|
| 384 |  * This class represent a plotting of a contour outline of data given as a X-Y matrice
 | 
|---|
| 385 |  *
 | 
|---|
| 386 |  */
 | 
|---|
| 387 | class ContourPlot extends Plot {
 | 
|---|
| 388 | 
 | 
|---|
| 389 |     private $contour, $contourCoord, $contourVal, $contourColor;
 | 
|---|
| 390 |     private $nbrCountours = 0 ;
 | 
|---|
| 391 |     private $dataMatrix = array();
 | 
|---|
| 392 |     private $invertLegend = false;
 | 
|---|
| 393 |     private $interpFactor = 1;
 | 
|---|
| 394 |     private $flipData = false;
 | 
|---|
| 395 |     private $isobar = 10;
 | 
|---|
| 396 |     private $showLegend = false;
 | 
|---|
| 397 |     private $highcontrast = false, $highcontrastbw = false;
 | 
|---|
| 398 |     private $manualIsobarColors = array();
 | 
|---|
| 399 | 
 | 
|---|
| 400 |     /**
 | 
|---|
| 401 |      * Construct a contour plotting algorithm. The end result of the algorithm is a sequence of
 | 
|---|
| 402 |      * line segments for each isobar given as two vertices.
 | 
|---|
| 403 |      *
 | 
|---|
| 404 |      * @param $aDataMatrix    The Z-data to be used
 | 
|---|
| 405 |      * @param $aIsobar A mixed variable, if it is an integer then this specified the number of isobars to use.
 | 
|---|
| 406 |      * The values of the isobars are automatically detrmined to be equ-spaced between the min/max value of the
 | 
|---|
| 407 |      * data. If it is an array then it explicetely gives the isobar values
 | 
|---|
| 408 |      * @param $aInvert By default the matrice with row index 0 corresponds to Y-value 0, i.e. in the bottom of
 | 
|---|
| 409 |      * the plot. If this argument is true then the row with the highest index in the matrice corresponds  to
 | 
|---|
| 410 |      * Y-value 0. In affect flipping the matrice around an imaginary horizontal axis.
 | 
|---|
| 411 |      * @param $aHighContrast Use high contrast colors (blue/red:ish)
 | 
|---|
| 412 |      * @param $aHighContrastBW Use only black colors for contours
 | 
|---|
| 413 |      * @return an instance of the contour plot algorithm
 | 
|---|
| 414 |      */
 | 
|---|
| 415 |     function __construct($aDataMatrix, $aIsobar=10, $aFactor=1, $aInvert=false, $aIsobarColors=array()) {
 | 
|---|
| 416 | 
 | 
|---|
| 417 |         $this->dataMatrix = $aDataMatrix;
 | 
|---|
| 418 |         $this->flipData = $aInvert;
 | 
|---|
| 419 |         $this->isobar = $aIsobar;
 | 
|---|
| 420 |         $this->interpFactor = $aFactor;
 | 
|---|
| 421 | 
 | 
|---|
| 422 |         if ( $this->interpFactor > 1 ) {
 | 
|---|
| 423 | 
 | 
|---|
| 424 |             if( $this->interpFactor > 5 ) {
 | 
|---|
| 425 |                 JpGraphError::RaiseL(28007);// ContourPlot interpolation factor is too large (>5)
 | 
|---|
| 426 |             }
 | 
|---|
| 427 | 
 | 
|---|
| 428 |             $ip = new MeshInterpolate();
 | 
|---|
| 429 |             $this->dataMatrix = $ip->Linear($this->dataMatrix, $this->interpFactor);
 | 
|---|
| 430 |         }
 | 
|---|
| 431 | 
 | 
|---|
| 432 |         $this->contour = new Contour($this->dataMatrix,$this->isobar,$aIsobarColors);
 | 
|---|
| 433 | 
 | 
|---|
| 434 |         if( is_array($aIsobar) )
 | 
|---|
| 435 |             $this->nbrContours = count($aIsobar);
 | 
|---|
| 436 |         else
 | 
|---|
| 437 |             $this->nbrContours = $aIsobar;
 | 
|---|
| 438 |     }
 | 
|---|
| 439 | 
 | 
|---|
| 440 | 
 | 
|---|
| 441 |     /**
 | 
|---|
| 442 |      * Flipe the data around the center
 | 
|---|
| 443 |      *
 | 
|---|
| 444 |      * @param $aFlg
 | 
|---|
| 445 |      *
 | 
|---|
| 446 |      */
 | 
|---|
| 447 |     function SetInvert($aFlg=true) {
 | 
|---|
| 448 |         $this->flipData = $aFlg;
 | 
|---|
| 449 |     }
 | 
|---|
| 450 | 
 | 
|---|
| 451 |     /**
 | 
|---|
| 452 |      * Set the colors for the isobar lines
 | 
|---|
| 453 |      *
 | 
|---|
| 454 |      * @param $aColorArray
 | 
|---|
| 455 |      *
 | 
|---|
| 456 |      */
 | 
|---|
| 457 |     function SetIsobarColors($aColorArray) {
 | 
|---|
| 458 |         $this->manualIsobarColors = $aColorArray;
 | 
|---|
| 459 |     }
 | 
|---|
| 460 | 
 | 
|---|
| 461 |     /**
 | 
|---|
| 462 |      * Show the legend
 | 
|---|
| 463 |      *
 | 
|---|
| 464 |      * @param $aFlg true if the legend should be shown
 | 
|---|
| 465 |      *
 | 
|---|
| 466 |      */
 | 
|---|
| 467 |     function ShowLegend($aFlg=true) {
 | 
|---|
| 468 |         $this->showLegend = $aFlg;
 | 
|---|
| 469 |     }
 | 
|---|
| 470 | 
 | 
|---|
| 471 | 
 | 
|---|
| 472 |     /**
 | 
|---|
| 473 |      * @param $aFlg true if the legend should start with the lowest isobar on top
 | 
|---|
| 474 |      * @return unknown_type
 | 
|---|
| 475 |      */
 | 
|---|
| 476 |     function Invertlegend($aFlg=true) {
 | 
|---|
| 477 |         $this->invertLegend = $aFlg;
 | 
|---|
| 478 |     }
 | 
|---|
| 479 | 
 | 
|---|
| 480 |     /* Internal method. Give the min value to be used for the scaling
 | 
|---|
| 481 |      *
 | 
|---|
| 482 |      */
 | 
|---|
| 483 |     function Min() {
 | 
|---|
| 484 |         return array(0,0);
 | 
|---|
| 485 |     }
 | 
|---|
| 486 | 
 | 
|---|
| 487 |     /* Internal method. Give the max value to be used for the scaling
 | 
|---|
| 488 |      *
 | 
|---|
| 489 |      */
 | 
|---|
| 490 |     function Max() {
 | 
|---|
| 491 |         return array(count($this->dataMatrix[0])-1,count($this->dataMatrix)-1);
 | 
|---|
| 492 |     }
 | 
|---|
| 493 | 
 | 
|---|
| 494 |     /**
 | 
|---|
| 495 |      * Internal ramewrok method to setup the legend to be used for this plot.
 | 
|---|
| 496 |      * @param $aGraph The parent graph class
 | 
|---|
| 497 |      */
 | 
|---|
| 498 |     function Legend($aGraph) {
 | 
|---|
| 499 | 
 | 
|---|
| 500 |         if( ! $this->showLegend )
 | 
|---|
| 501 |             return;
 | 
|---|
| 502 | 
 | 
|---|
| 503 |         if( $this->invertLegend ) {
 | 
|---|
| 504 |             for ($i = 0; $i < $this->nbrContours; $i++) {
 | 
|---|
| 505 |                 $aGraph->legend->Add(sprintf('%.1f',$this->contourVal[$i]), $this->contourColor[$i]);
 | 
|---|
| 506 |             }
 | 
|---|
| 507 |         }
 | 
|---|
| 508 |         else {
 | 
|---|
| 509 |             for ($i = $this->nbrContours-1; $i >= 0 ; $i--) {
 | 
|---|
| 510 |                 $aGraph->legend->Add(sprintf('%.1f',$this->contourVal[$i]), $this->contourColor[$i]);
 | 
|---|
| 511 |             }
 | 
|---|
| 512 |         }
 | 
|---|
| 513 |     }
 | 
|---|
| 514 | 
 | 
|---|
| 515 | 
 | 
|---|
| 516 |     /**
 | 
|---|
| 517 |      *  Framework function which gets called before the Stroke() method is called
 | 
|---|
| 518 |      *
 | 
|---|
| 519 |      *  @see Plot#PreScaleSetup($aGraph)
 | 
|---|
| 520 |      *
 | 
|---|
| 521 |      */
 | 
|---|
| 522 |     function PreScaleSetup($aGraph) {
 | 
|---|
| 523 |         $xn = count($this->dataMatrix[0])-1;
 | 
|---|
| 524 |         $yn = count($this->dataMatrix)-1;
 | 
|---|
| 525 | 
 | 
|---|
| 526 |         $aGraph->xaxis->scale->Update($aGraph->img,0,$xn);
 | 
|---|
| 527 |         $aGraph->yaxis->scale->Update($aGraph->img,0,$yn);
 | 
|---|
| 528 | 
 | 
|---|
| 529 |         $this->contour->SetInvert($this->flipData);
 | 
|---|
| 530 |         list($this->contourCoord,$this->contourVal,$this->contourColor) = $this->contour->getIsobars();
 | 
|---|
| 531 |     }
 | 
|---|
| 532 | 
 | 
|---|
| 533 |     /**
 | 
|---|
| 534 |      * Use high contrast color schema
 | 
|---|
| 535 |      *
 | 
|---|
| 536 |      * @param $aFlg True, to use high contrast color
 | 
|---|
| 537 |      * @param $aBW True, Use only black and white color schema
 | 
|---|
| 538 |      */
 | 
|---|
| 539 |     function UseHighContrastColor($aFlg=true,$aBW=false) {
 | 
|---|
| 540 |         $this->highcontrast = $aFlg;
 | 
|---|
| 541 |         $this->highcontrastbw = $aBW;
 | 
|---|
| 542 |         $this->contour->UseHighContrastColor($this->highcontrast,$this->highcontrastbw);
 | 
|---|
| 543 |     }
 | 
|---|
| 544 | 
 | 
|---|
| 545 |     /**
 | 
|---|
| 546 |      * Internal method. Stroke the contour plot to the graph
 | 
|---|
| 547 |      *
 | 
|---|
| 548 |      * @param $img Image handler
 | 
|---|
| 549 |      * @param $xscale Instance of the xscale to use
 | 
|---|
| 550 |      * @param $yscale Instance of the yscale to use
 | 
|---|
| 551 |      */
 | 
|---|
| 552 |     function Stroke($img,$xscale,$yscale) {
 | 
|---|
| 553 | 
 | 
|---|
| 554 |         if( count($this->manualIsobarColors) > 0 ) {
 | 
|---|
| 555 |             $this->contourColor = $this->manualIsobarColors;
 | 
|---|
| 556 |             if( count($this->manualIsobarColors) != $this->nbrContours ) {
 | 
|---|
| 557 |                 JpGraphError::RaiseL(28002);
 | 
|---|
| 558 |             }
 | 
|---|
| 559 |         }
 | 
|---|
| 560 | 
 | 
|---|
| 561 |         $img->SetLineWeight($this->line_weight);
 | 
|---|
| 562 | 
 | 
|---|
| 563 |         for ($c = 0; $c < $this->nbrContours; $c++) {
 | 
|---|
| 564 | 
 | 
|---|
| 565 |             $img->SetColor( $this->contourColor[$c] );
 | 
|---|
| 566 | 
 | 
|---|
| 567 |             $n = count($this->contourCoord[$c]);
 | 
|---|
| 568 |             $i = 0;
 | 
|---|
| 569 |             while ( $i < $n ) {
 | 
|---|
| 570 |                 list($x1,$y1) = $this->contourCoord[$c][$i][0];
 | 
|---|
| 571 |                 $x1t = $xscale->Translate($x1);
 | 
|---|
| 572 |                 $y1t = $yscale->Translate($y1);
 | 
|---|
| 573 | 
 | 
|---|
| 574 |                 list($x2,$y2) = $this->contourCoord[$c][$i++][1];
 | 
|---|
| 575 |                 $x2t = $xscale->Translate($x2);
 | 
|---|
| 576 |                 $y2t = $yscale->Translate($y2);
 | 
|---|
| 577 | 
 | 
|---|
| 578 |                 $img->Line($x1t,$y1t,$x2t,$y2t);
 | 
|---|
| 579 |             }
 | 
|---|
| 580 | 
 | 
|---|
| 581 |         }
 | 
|---|
| 582 |     }
 | 
|---|
| 583 | 
 | 
|---|
| 584 | }
 | 
|---|
| 585 | 
 | 
|---|
| 586 | // EOF
 | 
|---|
| 587 | ?>
 | 
|---|