source: trunk/xgraph/jpgraph/jpgraph_line.php @ 42

Last change on this file since 42 was 42, checked in by marrucho, 10 years ago
File size: 23.9 KB
Line 
1<?php
2/*=======================================================================
3 // File:                JPGRAPH_LINE.PHP
4 // Description: Line plot extension for JpGraph
5 // Created:     2001-01-08
6 // Ver:                 $Id: jpgraph_line.php 1921 2009-12-11 11:46:39Z ljp $
7 //
8 // Copyright (c) Asial Corporation. All rights reserved.
9 //========================================================================
10 */
11
12require_once ('jpgraph_plotmark.inc.php');
13
14// constants for the (filled) area
15DEFINE("LP_AREA_FILLED", true);
16DEFINE("LP_AREA_NOT_FILLED", false);
17DEFINE("LP_AREA_BORDER",false);
18DEFINE("LP_AREA_NO_BORDER",true);
19
20//===================================================
21// CLASS LinePlot
22// Description:
23//===================================================
24class LinePlot extends Plot{
25    public $mark=null;
26    protected $filled=false;
27    protected $fill_color='blue';
28    protected $step_style=false, $center=false;
29    protected $line_style=1; // Default to solid
30    protected $filledAreas = array(); // array of arrays(with min,max,col,filled in them)
31    public $barcenter=false;  // When we mix line and bar. Should we center the line in the bar.
32    protected $fillFromMin = false, $fillFromMax = false;
33    protected $fillgrad=false,$fillgrad_fromcolor='navy',$fillgrad_tocolor='silver',$fillgrad_numcolors=100;
34    protected $iFastStroke=false;
35
36    //---------------
37    // CONSTRUCTOR
38    function LinePlot($datay,$datax=false) {
39        parent::__construct($datay,$datax);
40        $this->mark = new PlotMark() ;
41        $this->color = ColorFactory::getColor();
42        $this->fill_color = $this->color;
43    }
44    //---------------
45    // PUBLIC METHODS
46
47    function SetFilled($aFlg=true) {
48                $this->filled = $aFlg;
49    }
50
51    function SetBarCenter($aFlag=true) {
52        $this->barcenter=$aFlag;
53    }
54
55    function SetStyle($aStyle) {
56        $this->line_style=$aStyle;
57    }
58
59    function SetStepStyle($aFlag=true) {
60        $this->step_style = $aFlag;
61    }
62
63    function SetColor($aColor) {
64        parent::SetColor($aColor);
65    }
66
67    function SetFillFromYMin($f=true) {
68        $this->fillFromMin = $f ;
69    }
70
71    function SetFillFromYMax($f=true) {
72        $this->fillFromMax = $f ;
73    }
74
75    function SetFillColor($aColor,$aFilled=true) {
76        //$this->color = $aColor;
77        $this->fill_color=$aColor;
78        $this->filled=$aFilled;
79    }
80
81    function SetFillGradient($aFromColor,$aToColor,$aNumColors=100,$aFilled=true) {
82        $this->fillgrad_fromcolor = $aFromColor;
83        $this->fillgrad_tocolor   = $aToColor;
84        $this->fillgrad_numcolors = $aNumColors;
85        $this->filled = $aFilled;
86        $this->fillgrad = true;
87    }
88
89    function Legend($graph) {
90        if( $this->legend!="" ) {
91            if( $this->filled && !$this->fillgrad ) {
92                $graph->legend->Add($this->legend,
93                $this->fill_color,$this->mark,0,
94                $this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget);
95            }
96            elseif( $this->fillgrad ) {
97                $color=array($this->fillgrad_fromcolor,$this->fillgrad_tocolor);
98                // In order to differentiate between gradients and cooors specified as an RGB triple
99                $graph->legend->Add($this->legend,$color,"",-2 /* -GRAD_HOR */,
100                $this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget);
101            } else {
102                $graph->legend->Add($this->legend,
103                $this->color,$this->mark,$this->line_style,
104                $this->legendcsimtarget,$this->legendcsimalt,$this->legendcsimwintarget);
105            }
106        }
107    }
108
109    function AddArea($aMin=0,$aMax=0,$aFilled=LP_AREA_NOT_FILLED,$aColor="gray9",$aBorder=LP_AREA_BORDER) {
110        if($aMin > $aMax) {
111            // swap
112            $tmp = $aMin;
113            $aMin = $aMax;
114            $aMax = $tmp;
115        }
116        $this->filledAreas[] = array($aMin,$aMax,$aColor,$aFilled,$aBorder);
117    }
118
119    // Gets called before any axis are stroked
120    function PreStrokeAdjust($graph) {
121
122        // If another plot type have already adjusted the
123        // offset we don't touch it.
124        // (We check for empty in case the scale is  a log scale
125        // and hence doesn't contain any xlabel_offset)
126        if( empty($graph->xaxis->scale->ticks->xlabel_offset) || $graph->xaxis->scale->ticks->xlabel_offset == 0 ) {
127            if( $this->center ) {
128                ++$this->numpoints;
129                $a=0.5; $b=0.5;
130            } else {
131                $a=0; $b=0;
132            }
133            $graph->xaxis->scale->ticks->SetXLabelOffset($a);
134            $graph->SetTextScaleOff($b);
135            //$graph->xaxis->scale->ticks->SupressMinorTickMarks();
136        }
137    }
138
139    function SetFastStroke($aFlg=true) {
140        $this->iFastStroke = $aFlg;
141    }
142
143    function FastStroke($img,$xscale,$yscale,$aStartPoint=0,$exist_x=true) {
144        // An optimized stroke for many data points with no extra
145        // features but 60% faster. You can't have values or line styles, or null
146        // values in plots.
147        $numpoints=count($this->coords[0]);
148        if( $this->barcenter ) {
149            $textadj = 0.5-$xscale->text_scale_off;
150        }
151        else {
152            $textadj = 0;
153        }
154
155        $img->SetColor($this->color);
156        $img->SetLineWeight($this->weight);
157        $pnts=$aStartPoint;
158        while( $pnts < $numpoints ) {
159            if( $exist_x ) {
160                $x=$this->coords[1][$pnts];
161            }
162            else {
163                $x=$pnts+$textadj;
164            }
165            $xt = $xscale->Translate($x);
166            $y=$this->coords[0][$pnts];
167            $yt = $yscale->Translate($y);
168            if( is_numeric($y) ) {
169                $cord[] = $xt;
170                $cord[] = $yt;
171            }
172            elseif( $y == '-' && $pnts > 0 ) {
173                // Just ignore
174            }
175            else {
176                JpGraphError::RaiseL(10002);//('Plot too complicated for fast line Stroke. Use standard Stroke()');
177            }
178            ++$pnts;
179        } // WHILE
180
181        $img->Polygon($cord,false,true);
182    }
183
184    function Stroke($img,$xscale,$yscale) {
185        $idx=0;
186        $numpoints=count($this->coords[0]);
187        if( isset($this->coords[1]) ) {
188            if( count($this->coords[1])!=$numpoints ) {
189                JpGraphError::RaiseL(2003,count($this->coords[1]),$numpoints);
190            //("Number of X and Y points are not equal. Number of X-points:".count($this->coords[1])." Number of Y-points:$numpoints");
191            }
192            else {
193                $exist_x = true;
194            }
195        }
196        else {
197            $exist_x = false;
198        }
199
200        if( $this->barcenter ) {
201            $textadj = 0.5-$xscale->text_scale_off;
202        }
203        else {
204            $textadj = 0;
205        }
206
207        // Find the first numeric data point
208        $startpoint=0;
209        while( $startpoint < $numpoints && !is_numeric($this->coords[0][$startpoint]) ) {
210            ++$startpoint;
211        }
212
213        // Bail out if no data points
214        if( $startpoint == $numpoints ) return;
215
216        if( $this->iFastStroke ) {
217            $this->FastStroke($img,$xscale,$yscale,$startpoint,$exist_x);
218            return;
219        }
220
221        if( $exist_x ) {
222            $xs=$this->coords[1][$startpoint];
223        }
224        else {
225            $xs= $textadj+$startpoint;
226        }
227
228        $img->SetStartPoint($xscale->Translate($xs),
229        $yscale->Translate($this->coords[0][$startpoint]));
230
231        if( $this->filled ) {
232            if( $this->fillFromMax ) {
233                //$max = $yscale->GetMaxVal();
234                $cord[$idx++] = $xscale->Translate($xs);
235                $cord[$idx++] = $yscale->scale_abs[1];
236            }
237            else {
238                $min = $yscale->GetMinVal();
239                if( $min > 0 || $this->fillFromMin ) {
240                    $fillmin = $yscale->scale_abs[0];//Translate($min);
241                }
242                else {
243                    $fillmin = $yscale->Translate(0);
244                }
245
246                $cord[$idx++] = $xscale->Translate($xs);
247                $cord[$idx++] = $fillmin;
248            }
249        }
250        $xt = $xscale->Translate($xs);
251        $yt = $yscale->Translate($this->coords[0][$startpoint]);
252        $cord[$idx++] = $xt;
253        $cord[$idx++] = $yt;
254        $yt_old = $yt;
255        $xt_old = $xt;
256        $y_old = $this->coords[0][$startpoint];
257
258        $this->value->Stroke($img,$this->coords[0][$startpoint],$xt,$yt);
259
260        $img->SetColor($this->color);
261        $img->SetLineWeight($this->weight);
262        $img->SetLineStyle($this->line_style);
263        $pnts=$startpoint+1;
264        $firstnonumeric = false;
265
266
267        while( $pnts < $numpoints ) {
268
269            if( $exist_x ) {
270                $x=$this->coords[1][$pnts];
271            }
272            else {
273                $x=$pnts+$textadj;
274            }
275            $xt = $xscale->Translate($x);
276            $yt = $yscale->Translate($this->coords[0][$pnts]);
277
278            $y=$this->coords[0][$pnts];
279            if( $this->step_style ) {
280                // To handle null values within step style we need to record the
281                // first non numeric value so we know from where to start if the
282                // non value is '-'.
283                if( is_numeric($y) ) {
284                    $firstnonumeric = false;
285                    if( is_numeric($y_old) ) {
286                        $img->StyleLine($xt_old,$yt_old,$xt,$yt_old);
287                        $img->StyleLine($xt,$yt_old,$xt,$yt);
288                    }
289                    elseif( $y_old == '-' ) {
290                        $img->StyleLine($xt_first,$yt_first,$xt,$yt_first);
291                        $img->StyleLine($xt,$yt_first,$xt,$yt);
292                    }
293                    else {
294                        $yt_old = $yt;
295                        $xt_old = $xt;
296                    }
297                    $cord[$idx++] = $xt;
298                    $cord[$idx++] = $yt_old;
299                    $cord[$idx++] = $xt;
300                    $cord[$idx++] = $yt;
301                }
302                elseif( $firstnonumeric==false ) {
303                    $firstnonumeric = true;
304                    $yt_first = $yt_old;
305                    $xt_first = $xt_old;
306                }
307            }
308            else {
309                $tmp1=$y;
310                $prev=$this->coords[0][$pnts-1];
311                if( $tmp1==='' || $tmp1===NULL || $tmp1==='X' ) $tmp1 = 'x';
312                if( $prev==='' || $prev===null || $prev==='X' ) $prev = 'x';
313
314                if( is_numeric($y) || (is_string($y) && $y != '-') ) {
315                    if( is_numeric($y) && (is_numeric($prev) || $prev === '-' ) ) {
316                        $img->StyleLineTo($xt,$yt);
317                    }
318                    else {
319                        $img->SetStartPoint($xt,$yt);
320                    }
321                }
322                if( $this->filled && $tmp1 !== '-' ) {
323                    if( $tmp1 === 'x' ) {
324                        $cord[$idx++] = $cord[$idx-3];
325                        $cord[$idx++] = $fillmin;
326                    }
327                    elseif( $prev === 'x' ) {
328                        $cord[$idx++] = $xt;
329                        $cord[$idx++] = $fillmin;
330                        $cord[$idx++] = $xt;
331                        $cord[$idx++] = $yt;
332                    }
333                    else {
334                        $cord[$idx++] = $xt;
335                        $cord[$idx++] = $yt;
336                    }
337                }
338                else {
339                    if( is_numeric($tmp1)  && (is_numeric($prev) || $prev === '-' ) ) {
340                        $cord[$idx++] = $xt;
341                        $cord[$idx++] = $yt;
342                    }
343                }
344            }
345            $yt_old = $yt;
346            $xt_old = $xt;
347            $y_old = $y;
348
349            $this->StrokeDataValue($img,$this->coords[0][$pnts],$xt,$yt);
350
351            ++$pnts;
352        }
353
354        if( $this->filled  ) {
355            $cord[$idx++] = $xt;
356            if( $this->fillFromMax ) {
357                $cord[$idx++] = $yscale->scale_abs[1];
358            }
359            else {
360                if( $min > 0 || $this->fillFromMin ) {
361                    $cord[$idx++] = $yscale->Translate($min);
362                }
363                else {
364                    $cord[$idx++] = $yscale->Translate(0);
365                }
366            }
367            if( $this->fillgrad ) {
368                $img->SetLineWeight(1);
369                $grad = new Gradient($img);
370                $grad->SetNumColors($this->fillgrad_numcolors);
371                $grad->FilledFlatPolygon($cord,$this->fillgrad_fromcolor,$this->fillgrad_tocolor);
372                $img->SetLineWeight($this->weight);
373            }
374            else {
375                $img->SetColor($this->fill_color);
376                $img->FilledPolygon($cord);
377            }
378            if( $this->weight > 0 ) { 
379                $img->SetLineWeight($this->weight);
380                $img->SetColor($this->color);
381                // Remove first and last coordinate before drawing the line
382                // sine we otherwise get the vertical start and end lines which
383                // doesn't look appropriate
384                $img->Polygon(array_slice($cord,2,count($cord)-4));
385            }
386        }
387
388        if(!empty($this->filledAreas)) {
389
390            $minY = $yscale->Translate($yscale->GetMinVal());
391            $factor = ($this->step_style ? 4 : 2);
392
393            for($i = 0; $i < sizeof($this->filledAreas); ++$i) {
394                // go through all filled area elements ordered by insertion
395                // fill polygon array
396                $areaCoords[] = $cord[$this->filledAreas[$i][0] * $factor];
397                $areaCoords[] = $minY;
398
399                $areaCoords =
400                array_merge($areaCoords,
401                array_slice($cord,
402                $this->filledAreas[$i][0] * $factor,
403                ($this->filledAreas[$i][1] - $this->filledAreas[$i][0] + ($this->step_style ? 0 : 1))  * $factor));
404                $areaCoords[] = $areaCoords[sizeof($areaCoords)-2]; // last x
405                $areaCoords[] = $minY; // last y
406
407                if($this->filledAreas[$i][3]) {
408                    $img->SetColor($this->filledAreas[$i][2]);
409                    $img->FilledPolygon($areaCoords);
410                    $img->SetColor($this->color);
411                }
412                // Check if we should draw the frame.
413                // If not we still re-draw the line since it might have been
414                // partially overwritten by the filled area and it doesn't look
415                // very good.
416                if( $this->filledAreas[$i][4] ) {
417                    $img->Polygon($areaCoords);
418                }
419                else {
420                    $img->Polygon($cord);
421                }
422
423                $areaCoords = array();
424            }
425        }
426
427        if( $this->mark->type == -1 || $this->mark->show == false )
428        return;
429
430        for( $pnts=0; $pnts<$numpoints; ++$pnts) {
431
432            if( $exist_x ) {
433                $x=$this->coords[1][$pnts];
434            }
435            else {
436                $x=$pnts+$textadj;
437            }
438            $xt = $xscale->Translate($x);
439            $yt = $yscale->Translate($this->coords[0][$pnts]);
440
441            if( is_numeric($this->coords[0][$pnts]) ) {
442                if( !empty($this->csimtargets[$pnts]) ) {
443                    if( !empty($this->csimwintargets[$pnts]) ) {
444                        $this->mark->SetCSIMTarget($this->csimtargets[$pnts],$this->csimwintargets[$pnts]);
445                    }
446                    else {
447                        $this->mark->SetCSIMTarget($this->csimtargets[$pnts]);
448                    }
449                    $this->mark->SetCSIMAlt($this->csimalts[$pnts]);
450                }
451                if( $exist_x ) {
452                    $x=$this->coords[1][$pnts];
453                }
454                else {
455                    $x=$pnts;
456                }
457                $this->mark->SetCSIMAltVal($this->coords[0][$pnts],$x);
458                $this->mark->Stroke($img,$xt,$yt);
459                $this->csimareas .= $this->mark->GetCSIMAreas();
460            }
461        }
462    }
463} // Class
464
465
466//===================================================
467// CLASS AccLinePlot
468// Description:
469//===================================================
470class AccLinePlot extends Plot {
471    protected $plots=null,$nbrplots=0;
472    private $iStartEndZero=true;
473    //---------------
474    // CONSTRUCTOR
475    function __construct($plots) {
476        $this->plots = $plots;
477        $this->nbrplots = count($plots);
478        $this->numpoints = $plots[0]->numpoints;
479
480        // Verify that all plots have the same number of data points
481        for( $i=1; $i < $this->nbrplots; ++$i ) {
482            if( $plots[$i]->numpoints != $this->numpoints ) {
483                JpGraphError::RaiseL(10003);//('Each plot in an accumulated lineplot must have the same number of data points',0)
484            }
485        }
486
487        for($i=0; $i < $this->nbrplots; ++$i ) {
488            $this->LineInterpolate($this->plots[$i]->coords[0]);
489        }
490    }
491
492    //---------------
493    // PUBLIC METHODS
494    function Legend($graph) {
495        foreach( $this->plots as $p ) {
496            $p->DoLegend($graph);
497        }
498    }
499
500    function Max() {
501        list($xmax) = $this->plots[0]->Max();
502        $nmax=0;
503        $n = count($this->plots);
504        for($i=0; $i < $n; ++$i) {
505            $nc = count($this->plots[$i]->coords[0]);
506            $nmax = max($nmax,$nc);
507            list($x) = $this->plots[$i]->Max();
508            $xmax = Max($xmax,$x);
509        }
510        for( $i = 0; $i < $nmax; $i++ ) {
511            // Get y-value for line $i by adding the
512            // individual bars from all the plots added.
513            // It would be wrong to just add the
514            // individual plots max y-value since that
515            // would in most cases give to large y-value.
516            $y=$this->plots[0]->coords[0][$i];
517            for( $j = 1; $j < $this->nbrplots; $j++ ) {
518                $y += $this->plots[ $j ]->coords[0][$i];
519            }
520            $ymax[$i] = $y;
521        }
522        $ymax = max($ymax);
523        return array($xmax,$ymax);
524    }
525
526    function Min() {
527        $nmax=0;
528        list($xmin,$ysetmin) = $this->plots[0]->Min();
529        $n = count($this->plots);
530        for($i=0; $i < $n; ++$i) {
531            $nc = count($this->plots[$i]->coords[0]);
532            $nmax = max($nmax,$nc);
533            list($x,$y) = $this->plots[$i]->Min();
534            $xmin = Min($xmin,$x);
535            $ysetmin = Min($y,$ysetmin);
536        }
537        for( $i = 0; $i < $nmax; $i++ ) {
538            // Get y-value for line $i by adding the
539            // individual bars from all the plots added.
540            // It would be wrong to just add the
541            // individual plots min y-value since that
542            // would in most cases give to small y-value.
543            $y=$this->plots[0]->coords[0][$i];
544            for( $j = 1; $j < $this->nbrplots; $j++ ) {
545                $y += $this->plots[ $j ]->coords[0][$i];
546            }
547            $ymin[$i] = $y;
548        }
549        $ymin = Min($ysetmin,Min($ymin));
550        return array($xmin,$ymin);
551    }
552
553    // Gets called before any axis are stroked
554    function PreStrokeAdjust($graph) {
555
556        // If another plot type have already adjusted the
557        // offset we don't touch it.
558        // (We check for empty in case the scale is  a log scale
559        // and hence doesn't contain any xlabel_offset)
560
561        if( empty($graph->xaxis->scale->ticks->xlabel_offset) ||
562        $graph->xaxis->scale->ticks->xlabel_offset == 0 ) {
563            if( $this->center ) {
564                ++$this->numpoints;
565                $a=0.5; $b=0.5;
566            } else {
567                $a=0; $b=0;
568            }
569            $graph->xaxis->scale->ticks->SetXLabelOffset($a);
570            $graph->SetTextScaleOff($b);
571            $graph->xaxis->scale->ticks->SupressMinorTickMarks();
572        }
573
574    }
575
576    function SetInterpolateMode($aIntMode) {
577        $this->iStartEndZero=$aIntMode;
578    }
579
580    // Replace all '-' with an interpolated value. We use straightforward
581    // linear interpolation. If the data starts with one or several '-' they
582    // will be replaced by the the first valid data point
583    function LineInterpolate(&$aData) {
584
585        $n=count($aData);
586        $i=0;
587
588        // If first point is undefined we will set it to the same as the first
589        // valid data
590        if( $aData[$i]==='-' ) {
591            // Find the first valid data
592            while( $i < $n && $aData[$i]==='-' ) {
593                ++$i;
594            }
595            if( $i < $n ) {
596                for($j=0; $j < $i; ++$j ) {
597                    if( $this->iStartEndZero )
598                    $aData[$i] = 0;
599                    else
600                    $aData[$j] = $aData[$i];
601                }
602            }
603            else {
604                // All '-' => Error
605                return false;
606            }
607        }
608
609        while($i < $n) {
610            while( $i < $n && $aData[$i] !== '-' ) {
611                ++$i;
612            }
613            if( $i < $n ) {
614                $pstart=$i-1;
615
616                // Now see how long this segment of '-' are
617                while( $i < $n && $aData[$i] === '-' ) {
618                    ++$i;
619                }
620                if( $i < $n ) {
621                    $pend=$i;
622                    $size=$pend-$pstart;
623                    $k=($aData[$pend]-$aData[$pstart])/$size;
624                    // Replace the segment of '-' with a linear interpolated value.
625                    for($j=1; $j < $size; ++$j ) {
626                        $aData[$pstart+$j] = $aData[$pstart] + $j*$k ;
627                    }
628                }
629                else {
630                    // There are no valid end point. The '-' goes all the way to the end
631                    // In that case we just set all the remaining values the the same as the
632                    // last valid data point.
633                    for( $j=$pstart+1; $j < $n; ++$j )
634                    if( $this->iStartEndZero ) {
635                        $aData[$j] = 0;
636                    }
637                    else {
638                        $aData[$j] = $aData[$pstart] ;
639                    }
640                }
641            }
642        }
643        return true;
644    }
645
646    // To avoid duplicate of line drawing code here we just
647    // change the y-values for each plot and then restore it
648    // after we have made the stroke. We must do this copy since
649    // it wouldn't be possible to create an acc line plot
650    // with the same graphs, i.e AccLinePlot(array($pl,$pl,$pl));
651    // since this method would have a side effect.
652    function Stroke($img,$xscale,$yscale) {
653        $img->SetLineWeight($this->weight);
654        $this->numpoints = count($this->plots[0]->coords[0]);
655        // Allocate array
656        $coords[$this->nbrplots][$this->numpoints]=0;
657        for($i=0; $i<$this->numpoints; $i++) {
658            $coords[0][$i]=$this->plots[0]->coords[0][$i];
659            $accy=$coords[0][$i];
660            for($j=1; $j<$this->nbrplots; ++$j ) {
661                $coords[$j][$i] = $this->plots[$j]->coords[0][$i]+$accy;
662                $accy = $coords[$j][$i];
663            }
664        }
665        for($j=$this->nbrplots-1; $j>=0; --$j) {
666            $p=$this->plots[$j];
667            for( $i=0; $i<$this->numpoints; ++$i) {
668                $tmp[$i]=$p->coords[0][$i];
669                $p->coords[0][$i]=$coords[$j][$i];
670            }
671            $p->Stroke($img,$xscale,$yscale);
672            for( $i=0; $i<$this->numpoints; ++$i) {
673                $p->coords[0][$i]=$tmp[$i];
674            }
675            $p->coords[0][]=$tmp;
676        }
677    }
678} // Class
679
680
681/* EOF */
682?>
Note: See TracBrowser for help on using the repository browser.