source: trunk/client/modules/Elezioni/grafici/jpgraph_contour.php@ 429

Last change on this file since 429 was 266, checked in by roby, 6 years ago
File size: 20.6 KB
Line 
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*/
11require_once('jpgraph_meshinterpolate.inc.php');
12define('HORIZ_EDGE',0);
13define('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 */
22class 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 */
387class 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?>
Note: See TracBrowser for help on using the repository browser.