2 /*=======================================================================
3 // File: JPGRAPH_LINE.PHP
4 // Description: Line plot extension for JpGraph
6 // Author: Johan Persson (johanp@aditus.nu)
7 // Ver: $Id: jpgraph_line.php,v 1.1 2006/07/07 13:37:14 powles Exp $
9 // Copyright (c) Aditus Consulting. All rights reserved.
10 //========================================================================
13 require_once ('jpgraph_plotmark.inc');
15 // constants for the (filled) area
16 DEFINE("LP_AREA_FILLED", true);
17 DEFINE("LP_AREA_NOT_FILLED", false);
18 DEFINE("LP_AREA_BORDER",false);
19 DEFINE("LP_AREA_NO_BORDER",true);
21 //===================================================
24 //===================================================
25 class LinePlot
extends Plot
{
27 var $fill_color='blue';
29 var $step_style=false, $center=false;
30 var $line_style=1; // Default to solid
31 var $filledAreas = array(); // array of arrays(with min,max,col,filled in them)
32 var $barcenter=false; // When we mix line and bar. Should we center the line in the bar.
33 var $fillFromMin = false ;
34 var $fillgrad=false,$fillgrad_fromcolor='navy',$fillgrad_tocolor='silver',$fillgrad_numcolors=100;
35 var $iFastStroke=false;
39 function __construct(&$datay,$datax=false) {
40 parent
::__construct($datay,$datax);
41 $this->mark
= new PlotMark();
46 // Set style, filled or open
47 function SetFilled($aFlag=true) {
48 JpGraphError
::RaiseL(10001);//('LinePlot::SetFilled() is deprecated. Use SetFillColor()');
51 function SetBarCenter($aFlag=true) {
52 $this->barcenter
=$aFlag;
55 function SetStyle($aStyle) {
56 $this->line_style
=$aStyle;
59 function SetStepStyle($aFlag=true) {
60 $this->step_style
= $aFlag;
63 function SetColor($aColor) {
64 parent
::SetColor($aColor);
67 function SetFillFromYMin($f=true) {
68 $this->fillFromMin
= $f ;
71 function SetFillColor($aColor,$aFilled=true) {
72 $this->fill_color
=$aColor;
73 $this->filled
=$aFilled;
76 function SetFillGradient($aFromColor,$aToColor,$aNumColors=100,$aFilled=true) {
77 $this->fillgrad_fromcolor
= $aFromColor;
78 $this->fillgrad_tocolor
= $aToColor;
79 $this->fillgrad_numcolors
= $aNumColors;
80 $this->filled
= $aFilled;
81 $this->fillgrad
= true;
84 function Legend(&$graph) {
85 if( $this->legend
!="" ) {
86 if( $this->filled
&& !$this->fillgrad
) {
87 $graph->legend
->Add($this->legend
,
88 $this->fill_color
,$this->mark
,0,
89 $this->legendcsimtarget
,$this->legendcsimalt
);
91 elseif( $this->fillgrad
) {
92 $color=array($this->fillgrad_fromcolor
,$this->fillgrad_tocolor
);
93 // In order to differentiate between gradients and cooors specified as an RGB triple
94 $graph->legend
->Add($this->legend
,$color,"",-2 /* -GRAD_HOR */,
95 $this->legendcsimtarget
,$this->legendcsimalt
);
98 $graph->legend
->Add($this->legend
,
99 $this->color
,$this->mark
,$this->line_style
,
100 $this->legendcsimtarget
,$this->legendcsimalt
);
105 function AddArea($aMin=0,$aMax=0,$aFilled=LP_AREA_NOT_FILLED
,$aColor="gray9",$aBorder=LP_AREA_BORDER
) {
112 $this->filledAreas
[] = array($aMin,$aMax,$aColor,$aFilled,$aBorder);
115 // Gets called before any axis are stroked
116 function PreStrokeAdjust(&$graph) {
118 // If another plot type have already adjusted the
119 // offset we don't touch it.
120 // (We check for empty in case the scale is a log scale
121 // and hence doesn't contain any xlabel_offset)
122 if( empty($graph->xaxis
->scale
->ticks
->xlabel_offset
) ||
123 $graph->xaxis
->scale
->ticks
->xlabel_offset
== 0 ) {
124 if( $this->center
) {
130 $graph->xaxis
->scale
->ticks
->SetXLabelOffset($a);
131 $graph->SetTextScaleOff($b);
132 //$graph->xaxis->scale->ticks->SupressMinorTickMarks();
136 function SetFastStroke($aFlg=true) {
137 $this->iFastStroke
= $aFlg;
140 function FastStroke(&$img,&$xscale,&$yscale,$aStartPoint=0,$exist_x=true) {
141 // An optimized stroke for many data points with no extra
142 // features but 60% faster. You can't have values or line styles, or null
144 $numpoints=count($this->coords
[0]);
145 if( $this->barcenter
)
146 $textadj = 0.5-$xscale->text_scale_off
;
150 $img->SetColor($this->color
);
151 $img->SetLineWeight($this->weight
);
153 while( $pnts < $numpoints ) {
154 if( $exist_x ) $x=$this->coords
[1][$pnts];
155 else $x=$pnts+
$textadj;
156 $xt = $xscale->Translate($x);
157 $y=$this->coords
[0][$pnts];
158 $yt = $yscale->Translate($y);
159 if( is_numeric($y) ) {
163 elseif( $y == '-' && $pnts > 0 ) {
167 JpGraphError
::RaiseL(10002);//('Plot too complicated for fast line Stroke. Use standard Stroke()');
173 $img->Polygon($cord,false,true);
177 function Stroke(&$img,&$xscale,&$yscale) {
179 $numpoints=count($this->coords
[0]);
180 if( isset($this->coords
[1]) ) {
181 if( count($this->coords
[1])!=$numpoints )
182 JpGraphError
::RaiseL(2003,count($this->coords
[1]),$numpoints);
183 //("Number of X and Y points are not equal. Number of X-points:".count($this->coords[1])." Number of Y-points:$numpoints");
190 if( $this->barcenter
)
191 $textadj = 0.5-$xscale->text_scale_off
;
195 // Find the first numeric data point
197 while( $startpoint < $numpoints && !is_numeric($this->coords
[0][$startpoint]) )
200 // Bail out if no data points
201 if( $startpoint == $numpoints )
204 if( $this->iFastStroke
) {
205 $this->FastStroke($img,$xscale,$yscale,$startpoint,$exist_x);
210 $xs=$this->coords
[1][$startpoint];
212 $xs= $textadj+
$startpoint;
214 $img->SetStartPoint($xscale->Translate($xs),
215 $yscale->Translate($this->coords
[0][$startpoint]));
217 if( $this->filled
) {
218 $min = $yscale->GetMinVal();
219 if( $min > 0 ||
$this->fillFromMin
)
220 $fillmin = $yscale->scale_abs
[0];//Translate($min);
222 $fillmin = $yscale->Translate(0);
224 $cord[$idx++
] = $xscale->Translate($xs);
225 $cord[$idx++
] = $fillmin;
227 $xt = $xscale->Translate($xs);
228 $yt = $yscale->Translate($this->coords
[0][$startpoint]);
233 $y_old = $this->coords
[0][$startpoint];
235 $this->value
->Stroke($img,$this->coords
[0][$startpoint],$xt,$yt);
237 $img->SetColor($this->color
);
238 $img->SetLineWeight($this->weight
);
239 $img->SetLineStyle($this->line_style
);
241 $firstnonumeric = false;
242 while( $pnts < $numpoints ) {
244 if( $exist_x ) $x=$this->coords
[1][$pnts];
245 else $x=$pnts+
$textadj;
246 $xt = $xscale->Translate($x);
247 $yt = $yscale->Translate($this->coords
[0][$pnts]);
249 $y=$this->coords
[0][$pnts];
250 if( $this->step_style
) {
251 // To handle null values within step style we need to record the
252 // first non numeric value so we know from where to start if the
254 if( is_numeric($y) ) {
255 $firstnonumeric = false;
256 if( is_numeric($y_old) ) {
257 $img->StyleLine($xt_old,$yt_old,$xt,$yt_old);
258 $img->StyleLine($xt,$yt_old,$xt,$yt);
260 elseif( $y_old == '-' ) {
261 $img->StyleLine($xt_first,$yt_first,$xt,$yt_first);
262 $img->StyleLine($xt,$yt_first,$xt,$yt);
269 $cord[$idx++
] = $yt_old;
273 elseif( $firstnonumeric==false ) {
274 $firstnonumeric = true;
281 $prev=$this->coords
[0][$pnts-1];
282 if( $tmp1==='' ||
$tmp1===NULL ||
$tmp1==='X' ) $tmp1 = 'x';
283 if( $prev==='' ||
$prev===null ||
$prev==='X' ) $prev = 'x';
285 if( is_numeric($y) ||
(is_string($y) && $y != '-') ) {
286 if( is_numeric($y) && (is_numeric($prev) ||
$prev === '-' ) ) {
287 $img->StyleLineTo($xt,$yt);
290 $img->SetStartPoint($xt,$yt);
293 if( $this->filled
&& $tmp1 !== '-' ) {
294 if( $tmp1 === 'x' ) {
295 $cord[$idx++
] = $cord[$idx-3];
296 $cord[$idx++
] = $fillmin;
298 elseif( $prev === 'x' ) {
300 $cord[$idx++
] = $fillmin;
310 if( is_numeric($tmp1) && (is_numeric($prev) ||
$prev === '-' ) ) {
320 $this->StrokeDataValue($img,$this->coords
[0][$pnts],$xt,$yt);
325 if( $this->filled
) {
327 if( $min > 0 ||
$this->fillFromMin
)
328 $cord[$idx++
] = $yscale->Translate($min);
330 $cord[$idx++
] = $yscale->Translate(0);
331 if( $this->fillgrad
) {
332 $img->SetLineWeight(1);
333 $grad = new Gradient($img);
334 $grad->SetNumColors($this->fillgrad_numcolors
);
335 $grad->FilledFlatPolygon($cord,$this->fillgrad_fromcolor
,$this->fillgrad_tocolor
);
336 $img->SetLineWeight($this->weight
);
339 $img->SetColor($this->fill_color
);
340 $img->FilledPolygon($cord);
342 if( $this->line_weight
> 0 ) {
343 $img->SetColor($this->color
);
344 $img->Polygon($cord);
348 if(!empty($this->filledAreas
)) {
350 $minY = $yscale->Translate($yscale->GetMinVal());
351 $factor = ($this->step_style ?
4 : 2);
353 for($i = 0; $i < sizeof($this->filledAreas
); ++
$i) {
354 // go through all filled area elements ordered by insertion
355 // fill polygon array
356 $areaCoords[] = $cord[$this->filledAreas
[$i][0] * $factor];
357 $areaCoords[] = $minY;
360 array_merge($areaCoords,
362 $this->filledAreas
[$i][0] * $factor,
363 ($this->filledAreas
[$i][1] - $this->filledAreas
[$i][0] +
($this->step_style ?
0 : 1)) * $factor));
364 $areaCoords[] = $areaCoords[sizeof($areaCoords)-2]; // last x
365 $areaCoords[] = $minY; // last y
367 if($this->filledAreas
[$i][3]) {
368 $img->SetColor($this->filledAreas
[$i][2]);
369 $img->FilledPolygon($areaCoords);
370 $img->SetColor($this->color
);
372 // Check if we should draw the frame.
373 // If not we still re-draw the line since it might have been
374 // partially overwritten by the filled area and it doesn't look
376 // TODO: The behaviour is undefined if the line does not have
377 // any line at the position of the area.
378 if( $this->filledAreas
[$i][4] )
379 $img->Polygon($areaCoords);
381 $img->Polygon($cord);
383 $areaCoords = array();
387 if( $this->mark
->type
== -1 ||
$this->mark
->show
== false )
390 for( $pnts=0; $pnts<$numpoints; ++
$pnts) {
392 if( $exist_x ) $x=$this->coords
[1][$pnts];
393 else $x=$pnts+
$textadj;
394 $xt = $xscale->Translate($x);
395 $yt = $yscale->Translate($this->coords
[0][$pnts]);
397 if( is_numeric($this->coords
[0][$pnts]) ) {
398 if( !empty($this->csimtargets
[$pnts]) ) {
399 $this->mark
->SetCSIMTarget($this->csimtargets
[$pnts]);
400 $this->mark
->SetCSIMAlt($this->csimalts
[$pnts]);
403 $x=$this->coords
[1][$pnts];
406 $this->mark
->SetCSIMAltVal($this->coords
[0][$pnts],$x);
407 $this->mark
->Stroke($img,$xt,$yt);
408 $this->csimareas
.= $this->mark
->GetCSIMAreas();
409 $this->StrokeDataValue($img,$this->coords
[0][$pnts],$xt,$yt);
418 //===================================================
421 //===================================================
422 class AccLinePlot
extends Plot
{
423 var $plots=null,$nbrplots=0,$numpoints=0;
424 var $iStartEndZero=true;
427 function __construct($plots) {
428 $this->plots
= $plots;
429 $this->nbrplots
= count($plots);
430 $this->numpoints
= $plots[0]->numpoints
;
432 for($i=0; $i < $this->nbrplots
; ++
$i ) {
433 $this->LineInterpolate($this->plots
[$i]->coords
[0]);
439 function Legend(&$graph) {
440 $n=count($this->plots
);
441 for($i=0; $i < $n; ++
$i )
442 $this->plots
[$i]->DoLegend($graph);
446 list($xmax) = $this->plots
[0]->Max();
448 $n = count($this->plots
);
449 for($i=0; $i < $n; ++
$i) {
450 $nc = count($this->plots
[$i]->coords
[0]);
451 $nmax = max($nmax,$nc);
452 list($x) = $this->plots
[$i]->Max();
453 $xmax = Max($xmax,$x);
455 for( $i = 0; $i < $nmax; $i++
) {
456 // Get y-value for line $i by adding the
457 // individual bars from all the plots added.
458 // It would be wrong to just add the
459 // individual plots max y-value since that
460 // would in most cases give to large y-value.
461 $y=$this->plots
[0]->coords
[0][$i];
462 for( $j = 1; $j < $this->nbrplots
; $j++
) {
463 $y +
= $this->plots
[ $j ]->coords
[0][$i];
468 return array($xmax,$ymax);
473 list($xmin,$ysetmin) = $this->plots
[0]->Min();
474 $n = count($this->plots
);
475 for($i=0; $i < $n; ++
$i) {
476 $nc = count($this->plots
[$i]->coords
[0]);
477 $nmax = max($nmax,$nc);
478 list($x,$y) = $this->plots
[$i]->Min();
479 $xmin = Min($xmin,$x);
480 $ysetmin = Min($y,$ysetmin);
482 for( $i = 0; $i < $nmax; $i++
) {
483 // Get y-value for line $i by adding the
484 // individual bars from all the plots added.
485 // It would be wrong to just add the
486 // individual plots min y-value since that
487 // would in most cases give to small y-value.
488 $y=$this->plots
[0]->coords
[0][$i];
489 for( $j = 1; $j < $this->nbrplots
; $j++
) {
490 $y +
= $this->plots
[ $j ]->coords
[0][$i];
494 $ymin = Min($ysetmin,Min($ymin));
495 return array($xmin,$ymin);
498 // Gets called before any axis are stroked
499 function PreStrokeAdjust(&$graph) {
501 // If another plot type have already adjusted the
502 // offset we don't touch it.
503 // (We check for empty in case the scale is a log scale
504 // and hence doesn't contain any xlabel_offset)
506 if( empty($graph->xaxis
->scale
->ticks
->xlabel_offset
) ||
507 $graph->xaxis
->scale
->ticks
->xlabel_offset
== 0 ) {
508 if( $this->center
) {
514 $graph->xaxis
->scale
->ticks
->SetXLabelOffset($a);
515 $graph->SetTextScaleOff($b);
516 $graph->xaxis
->scale
->ticks
->SupressMinorTickMarks();
521 function SetInterpolateMode($aIntMode) {
522 $this->iStartEndZero
=$aIntMode;
525 // Replace all '-' with an interpolated value. We use straightforward
526 // linear interpolation. If the data starts with one or several '-' they
527 // will be replaced by the the first valid data point
528 function LineInterpolate(&$aData) {
533 // If first point is undefined we will set it to the same as the first
535 if( $aData[$i]==='-' ) {
536 // Find the first valid data
537 while( $i < $n && $aData[$i]==='-' ) {
541 for($j=0; $j < $i; ++
$j ) {
542 if( $this->iStartEndZero
)
545 $aData[$j] = $aData[$i];
555 while( $i < $n && $aData[$i] !== '-' ) {
561 // Now see how long this segment of '-' are
562 while( $i < $n && $aData[$i] === '-' )
567 $k=($aData[$pend]-$aData[$pstart])/$size;
568 // Replace the segment of '-' with a linear interpolated value.
569 for($j=1; $j < $size; ++
$j ) {
570 $aData[$pstart+
$j] = $aData[$pstart] +
$j*$k ;
574 // There are no valid end point. The '-' goes all the way to the end
575 // In that case we just set all the remaining values the the same as the
576 // last valid data point.
577 for( $j=$pstart+
1; $j < $n; ++
$j )
578 if( $this->iStartEndZero
)
581 $aData[$j] = $aData[$pstart] ;
590 // To avoid duplicate of line drawing code here we just
591 // change the y-values for each plot and then restore it
592 // after we have made the stroke. We must do this copy since
593 // it wouldn't be possible to create an acc line plot
594 // with the same graphs, i.e AccLinePlot(array($pl,$pl,$pl));
595 // since this method would have a side effect.
596 function Stroke(&$img,&$xscale,&$yscale) {
597 $img->SetLineWeight($this->weight
);
598 $this->numpoints
= count($this->plots
[0]->coords
[0]);
600 $coords[$this->nbrplots
][$this->numpoints
]=0;
601 for($i=0; $i<$this->numpoints
; $i++
) {
602 $coords[0][$i]=$this->plots
[0]->coords
[0][$i];
603 $accy=$coords[0][$i];
604 for($j=1; $j<$this->nbrplots
; ++
$j ) {
605 $coords[$j][$i] = $this->plots
[$j]->coords
[0][$i]+
$accy;
606 $accy = $coords[$j][$i];
609 for($j=$this->nbrplots
-1; $j>=0; --$j) {
611 for( $i=0; $i<$this->numpoints
; ++
$i) {
612 $tmp[$i]=$p->coords
[0][$i];
613 $p->coords
[0][$i]=$coords[$j][$i];
615 $p->Stroke($img,$xscale,$yscale);
616 for( $i=0; $i<$this->numpoints
; ++
$i)
617 $p->coords
[0][$i]=$tmp[$i];
618 $p->coords
[0][]=$tmp;