Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

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