2 /*=======================================================================
3 // File: JPGRAPH_BAR.PHP
4 // Description: Bar plot extension for JpGraph
6 // Author: Johan Persson (johanp@aditus.nu)
7 // Ver: $Id: jpgraph_bar.php,v 1.54.2.5 2003/08/15 19:06:15 aditus Exp $
9 // License: This code is released under QPL
10 // Copyright (C) 2001,2002 Johan Persson
11 //========================================================================
14 //===================================================
16 // Description: Main code to produce a bar plot
17 //===================================================
18 class BarPlot extends Plot {
19 var $width=0.4; // in percent of major ticks
20 var $abswidth=-1; // Width in absolute pixels
21 var $fill=false,$fill_color="lightblue"; // Default is to fill with light blue
22 var $ybase=0; // Bars start at 0
24 var $grad=false,$grad_style=1;
25 var $grad_fromcolor=array(50,50,200),$grad_tocolor=array(255,255,255);
26 var $bar_shadow=false;
27 var $bar_shadow_color="black";
28 var $bar_shadow_hsize=3,$bar_shadow_vsize=3;
33 function BarPlot(&$datay,$datax=false) {
34 $this->Plot($datay,$datax);
41 // Set a drop shadow for the bar (or rather an "up-right" shadow)
42 function SetShadow($color="black",$hsize=3,$vsize=3) {
43 $this->bar_shadow=true;
44 $this->bar_shadow_color=$color;
45 $this->bar_shadow_vsize=$vsize;
46 $this->bar_shadow_hsize=$hsize;
48 // Adjust the value margin to compensate for shadow
49 $this->value->margin += $vsize;
52 // DEPRECATED use SetYBase instead
53 function SetYMin($aYStartValue) {
54 //die("JpGraph Error: Deprecated function SetYMin. Use SetYBase() instead.");
55 $this->ybase=$aYStartValue;
58 // Specify the base value for the bars
59 function SetYBase($aYStartValue) {
60 $this->ybase=$aYStartValue;
63 function Legend(&$graph) {
64 if( $this->grad && $this->legend!="" && !$this->fill ) {
65 $color=array($this->grad_fromcolor,$this->grad_tocolor,$this->grad_style);
66 $graph->legend->Add($this->legend,$color,"",0,
67 $this->legendcsimtarget,$this->legendcsimalt);
69 elseif( $this->fill_color && $this->legend!="" ) {
70 if( is_array($this->fill_color) )
71 $graph->legend->Add($this->legend,$this->fill_color[0],"",0,
72 $this->legendcsimtarget,$this->legendcsimalt);
74 $graph->legend->Add($this->legend,$this->fill_color,"",0,
75 $this->legendcsimtarget,$this->legendcsimalt);
79 // Gets called before any axis are stroked
80 function PreStrokeAdjust(&$graph) {
81 parent::PreStrokeAdjust($graph);
83 // If we are using a log Y-scale we want the base to be at the
84 // minimum Y-value unless the user have specifically set some other
85 // value than the default.
86 if( substr($graph->axtype,-3,3)=="log" && $this->ybase==0 )
87 $this->ybase = $graph->yaxis->scale->GetMinVal();
89 // For a "text" X-axis scale we will adjust the
90 // display of the bars a little bit.
91 if( substr($graph->axtype,0,3)=="tex" ) {
92 // Position the ticks between the bars
93 $graph->xaxis->scale->ticks->SetXLabelOffset(0.5,0);
96 if( $this->align == "center" )
97 $graph->SetTextScaleOff(0.5-$this->width/2);
98 elseif( $this->align == "right" )
99 $graph->SetTextScaleOff(1-$this->width);
103 // We only set an absolute width for linear and int scale
104 // for text scale the width will be set to a fraction of
105 // the majstep width.
106 if( $this->abswidth == -1 ) {
108 // set width to a visuable sensible default
109 $this->abswidth = $graph->img->plotwidth/(2*count($this->coords[0]));
116 if( $m[1] >= $this->ybase )
117 $m[1] = $this->ybase;
123 if( $m[1] <= $this->ybase )
124 $m[1] = $this->ybase;
128 // Specify width as fractions of the major stepo size
129 function SetWidth($aFractionWidth) {
130 $this->width=$aFractionWidth;
133 // Specify width in absolute pixels. If specified this
134 // overrides SetWidth()
135 function SetAbsWidth($aWidth) {
136 $this->abswidth=$aWidth;
139 function SetAlign($aAlign) {
140 $this->align=$aAlign;
143 function SetNoFill() {
145 $this->fill_color=false;
149 function SetFillColor($aColor) {
151 $this->fill_color=$aColor;
154 function SetFillGradient($from_color,$to_color,$style) {
156 $this->grad_fromcolor=$from_color;
157 $this->grad_tocolor=$to_color;
158 $this->grad_style=$style;
161 function SetValuePos($aPos) {
162 $this->valuepos = $aPos;
165 function Stroke(&$img,&$xscale,&$yscale) {
167 $numpoints = count($this->coords[0]);
168 if( isset($this->coords[1]) ) {
169 if( count($this->coords[1])!=$numpoints )
170 die("JpGraph Error: Number of X and Y points are not equal.<br>
171 Number of X-points:".count($this->coords[1])."<br>
172 Number of Y-points:$numpoints");
180 $numbars=count($this->coords[0]);
182 // Use GetMinVal() instead of scale[0] directly since in the case
183 // of log scale we get a correct value. Log scales will have negative
184 // values for values < 1 while still not representing negative numbers.
185 if( $yscale->GetMinVal() >= 0 )
186 $zp=$yscale->scale_abs[0];
188 $zp=$yscale->Translate(0);
191 if( $this->abswidth > -1 ) {
192 $abswidth=$this->abswidth;
195 $abswidth=round($this->width*$xscale->scale_factor,0);
197 for($i=0; $i<$numbars; $i++) {
199 // If value is NULL, or 0 then don't draw a bar at all
200 if ($this->coords[0][$i] === null ||
201 $this->coords[0][$i] === '' ||
202 $this->coords[0][$i] === 0 ) continue;
204 if( $exist_x ) $x=$this->coords[1][$i];
207 $x=$xscale->Translate($x);
209 if( !$xscale->textscale ) {
210 if($this->align=="center")
212 elseif($this->align=="right")
218 $x,$yscale->Translate($this->coords[0][$i]),
219 $x+$abswidth,$yscale->Translate($this->coords[0][$i]),
222 $grad = new Gradient($img);
223 $grad->FilledRectangle($pts[2],$pts[3],
225 $this->grad_fromcolor,$this->grad_tocolor,$this->grad_style);
227 elseif( !empty($this->fill_color) ) {
228 if(is_array($this->fill_color)) {
229 $img->PushColor($this->fill_color[$i % count($this->fill_color)]);
231 $img->PushColor($this->fill_color);
233 $img->FilledPolygon($pts);
237 // Remember value of this bar
238 $val=$this->coords[0][$i];
240 if( $this->bar_shadow && $val !== 0 && $val !== 0.0 ) {
241 $ssh = $this->bar_shadow_hsize;
242 $ssv = $this->bar_shadow_vsize;
243 // Create points to create a "upper-right" shadow
245 $sp[0]=$pts[6]; $sp[1]=$pts[7];
246 $sp[2]=$pts[4]; $sp[3]=$pts[5];
247 $sp[4]=$pts[2]; $sp[5]=$pts[3];
248 $sp[6]=$pts[2]+$ssh; $sp[7]=$pts[3]-$ssv;
249 $sp[8]=$pts[4]+$ssh; $sp[9]=$pts[5]-$ssv;
250 $sp[10]=$pts[6]+$ssh; $sp[11]=$pts[7]-$ssv;
253 $sp[0]=$pts[4]; $sp[1]=$pts[5];
254 $sp[2]=$pts[6]; $sp[3]=$pts[7];
255 $sp[4]=$pts[0]; $sp[5]=$pts[1];
256 $sp[6]=$pts[0]+$ssh; $sp[7]=$pts[1]-$ssv;
257 $sp[8]=$pts[6]+$ssh; $sp[9]=$pts[7]-$ssv;
258 $sp[10]=$pts[4]+$ssh; $sp[11]=$pts[5]-$ssv;
261 $img->PushColor($this->bar_shadow_color);
262 $img->FilledPolygon($sp);
266 // Stroke the outline of the bar
267 if( is_array($this->color) )
268 $img->SetColor($this->color[$i % count($this->color)]);
270 $img->SetColor($this->color);
275 if( $this->weight > 0 ) {
276 $img->SetLineWeight($this->weight);
280 $x=$pts[2]+($pts[4]-$pts[2])/2;
281 if( $this->valuepos=='top' ) {
283 $this->value->Stroke($img,$val,$x,$y);
285 elseif( $this->valuepos=='max' ) {
287 if( $img->a === 90 ) {
288 $this->value->SetAlign('right','center');
291 $this->value->SetAlign('center','top');
293 $this->value->SetMargin(-2);
294 $this->value->Stroke($img,$val,$x,$y);
296 elseif( $this->valuepos=='center' ) {
297 $y = ($pts[3] + $pts[1])/2;
298 $this->value->SetAlign('center','center');
299 $this->value->SetMargin(0);
300 $this->value->Stroke($img,$val,$x,$y);
302 elseif( $this->valuepos=='bottom' || $this->valuepos=='min' ) {
304 $this->value->SetMargin(0);
305 $this->value->Stroke($img,$val,$x,$y);
308 JpGraphError::Raise('Unknown position for values on bars :'.$this->valuepos);
311 // Create the client side image map
312 $rpts = $img->ArrRotate($pts);
313 $csimcoord=round($rpts[0]).", ".round($rpts[1]);
314 for( $j=1; $j < 4; ++$j){
315 $csimcoord .= ", ".round($rpts[2*$j]).", ".round($rpts[2*$j+1]);
317 if( !empty($this->csimtargets[$i]) ) {
318 $this->csimareas .= '<area shape="poly" coords="'.$csimcoord.'" ';
319 $this->csimareas .= " href=\"".$this->csimtargets[$i]."\"";
320 if( !empty($this->csimalts[$i]) ) {
321 $sval=sprintf($this->csimalts[$i],$this->coords[0][$i]);
322 $this->csimareas .= " alt=\"$sval\" title=\"$sval\" ";
324 $this->csimareas .= ">\n";
331 //===================================================
332 // CLASS GroupBarPlot
333 // Description: Produce grouped bar plots
334 //===================================================
335 class GroupBarPlot extends BarPlot {
342 function GroupBarPlot($plots) {
343 $this->plots = $plots;
344 $this->nbrplots = count($plots);
345 $this->numpoints = $plots[0]->numpoints;
350 function Legend(&$graph) {
351 $n = count($this->plots);
352 for($i=0; $i<$n; ++$i)
353 $this->plots[$i]->DoLegend($graph);
357 list($xmin,$ymin) = $this->plots[0]->Min();
358 $n = count($this->plots);
359 for($i=0; $i<$n; ++$i) {
360 list($xm,$ym) = $this->plots[$i]->Min();
361 $xmin = max($xmin,$xm);
362 $ymin = min($ymin,$ym);
364 return array($xmin,$ymin);
368 list($xmax,$ymax) = $this->plots[0]->Max();
369 $n = count($this->plots);
370 for($i=0; $i<$n; ++$i) {
371 list($xm,$ym) = $this->plots[$i]->Max();
372 $xmax = max($xmax,$xm);
373 $ymax = max($ymax,$ym);
375 return array($xmax,$ymax);
378 function GetCSIMareas() {
379 $n = count($this->plots);
381 for($i=0; $i < $n; ++$i) {
382 $csimareas .= $this->plots[$i]->csimareas;
387 // Stroke all the bars next to each other
388 function Stroke(&$img,&$xscale,&$yscale) {
390 $n = count($this->plots);
391 $subwidth = $this->width/$this->nbrplots ;
392 for( $i=0; $i < $n; ++$i ) {
393 $this->plots[$i]->ymin=$this->ybase;
394 $this->plots[$i]->SetWidth($subwidth);
396 // If the client have used SetTextTickInterval() then
397 // major_step will be > 1 and the positioning will fail.
398 // If we assume it is always one the positioning will work
399 // fine with a text scale but this will not work with
400 // arbitrary linear scale
401 $xscale->off = $tmp+$i*round(/*$xscale->ticks->major_step* */
402 $xscale->scale_factor*$subwidth);
403 $this->plots[$i]->Stroke($img,$xscale,$yscale);
409 //===================================================
411 // Description: Produce accumulated bar plots
412 //===================================================
413 class AccBarPlot extends BarPlot {
414 var $plots=null,$nbrplots=0,$numpoints=0;
417 function AccBarPlot($plots) {
418 $this->plots = $plots;
419 $this->nbrplots = count($plots);
420 $this->numpoints = $plots[0]->numpoints;
421 $this->value = new DisplayValue();
426 function Legend(&$graph) {
427 $n = count($this->plots);
428 for( $i=$n-1; $i>=0; --$i )
429 $this->plots[$i]->DoLegend($graph);
433 list($xmax) = $this->plots[0]->Max();
435 for($i=0; $i<count($this->plots); ++$i) {
436 $n = count($this->plots[$i]->coords[0]);
437 $nmax = max($nmax,$n);
438 list($x) = $this->plots[$i]->Max();
439 $xmax = max($xmax,$x);
441 for( $i = 0; $i < $nmax; $i++ ) {
442 // Get y-value for bar $i by adding the
443 // individual bars from all the plots added.
444 // It would be wrong to just add the
445 // individual plots max y-value since that
446 // would in most cases give to large y-value.
448 if( $this->plots[0]->coords[0][$i] > 0 )
449 $y=$this->plots[0]->coords[0][$i];
450 for( $j = 1; $j < $this->nbrplots; $j++ ) {
451 if( $this->plots[$j]->coords[0][$i] > 0 )
452 $y += $this->plots[$j]->coords[0][$i];
458 // Bar always start at baseline
459 if( $ymax <= $this->ybase )
460 $ymax = $this->ybase;
461 return array($xmax,$ymax);
466 list($xmin,$ysetmin) = $this->plots[0]->Min();
467 for($i=0; $i<count($this->plots); ++$i) {
468 $n = count($this->plots[$i]->coords[0]);
469 $nmax = max($nmax,$n);
470 list($x,$y) = $this->plots[$i]->Min();
471 $xmin = Min($xmin,$x);
472 $ysetmin = Min($y,$ysetmin);
474 for( $i = 0; $i < $nmax; $i++ ) {
475 // Get y-value for bar $i by adding the
476 // individual bars from all the plots added.
477 // It would be wrong to just add the
478 // individual plots max y-value since that
479 // would in most cases give to large y-value.
480 $y=$this->plots[0]->coords[0][$i];
481 for( $j = 1; $j < $this->nbrplots; $j++ ) {
482 $y += $this->plots[ $j ]->coords[0][$i];
486 $ymin = Min($ysetmin,Min($ymin));
487 // Bar always start at baseline
488 if( $ymin >= $this->ybase )
489 $ymin = $this->ybase;
490 return array($xmin,$ymin);
493 // Stroke acc bar plot
494 function Stroke(&$img,&$xscale,&$yscale) {
495 $img->SetLineWeight($this->weight);
496 for($i=0; $i<$this->numpoints-1; $i++) {
499 for($j=0; $j < $this->nbrplots; ++$j ) {
501 $img->SetColor($this->plots[$j]->color);
503 if ( $this->plots[$j]->coords[0][$i] >= 0) {
504 $yt=$yscale->Translate($this->plots[$j]->coords[0][$i]+$accy);
505 $accyt=$yscale->Translate($accy);
506 $accy+=$this->plots[$j]->coords[0][$i];
509 //if ( $this->plots[$j]->coords[0][$i] < 0 || $accy_neg < 0 ) {
510 $yt=$yscale->Translate($this->plots[$j]->coords[0][$i]+$accy_neg);
511 $accyt=$yscale->Translate($accy_neg);
512 $accy_neg+=$this->plots[$j]->coords[0][$i];
515 $xt=$xscale->Translate($i);
517 if( $this->abswidth > -1 )
518 $abswidth=$this->abswidth;
520 $abswidth=round($this->width*$xscale->scale_factor,0);
522 $pts=array($xt,$accyt,$xt,$yt,$xt+$abswidth,$yt,$xt+$abswidth,$accyt);
524 if( $this->bar_shadow ) {
525 $ssh = $this->bar_shadow_hsize;
526 $ssv = $this->bar_shadow_vsize;
528 // We must also differ if we are a positive or negative bar.
530 // This gets extra complicated since we have to
531 // see all plots to see if we are negative. It could
532 // for example be that all plots are 0 until the very
533 // last one. We therefore need to save the initial setup
534 // for both the negative and positive case
536 // In case the final bar is positive
537 $sp[0]=$pts[6]+1; $sp[1]=$pts[7];
538 $sp[2]=$pts[6]+$ssh; $sp[3]=$pts[7]-$ssv;
540 // In case the final bar is negative
541 $nsp[0]=$pts[0]; $nsp[1]=$pts[1];
542 $nsp[2]=$pts[0]+$ssh; $nsp[3]=$pts[1]-$ssv;
543 $nsp[4]=$pts[6]+$ssh; $nsp[5]=$pts[7]-$ssv;
544 $nsp[10]=$pts[6]+1; $nsp[11]=$pts[7];
547 if( $j === $this->nbrplots-1 ) {
548 // If this is the last plot of the bar and
549 // the total value is larger than 0 then we
552 $sp[4]=$pts[4]+$ssh; $sp[5]=$pts[5]-$ssv;
553 $sp[6]=$pts[2]+$ssh; $sp[7]=$pts[3]-$ssv;
554 $sp[8]=$pts[2]; $sp[9]=$pts[3]-1;
555 $sp[10]=$pts[4]+1; $sp[11]=$pts[5];
556 $img->PushColor($this->bar_shadow_color);
557 $img->FilledPolygon($sp,4);
560 elseif( $accy_neg < 0 ) {
561 $nsp[6]=$pts[4]+$ssh; $nsp[7]=$pts[5]-$ssv;
562 $nsp[8]=$pts[4]+1; $nsp[9]=$pts[5];
563 $img->PushColor($this->bar_shadow_color);
564 $img->FilledPolygon($nsp,4);
570 // If value is NULL or 0, then don't draw a bar at all
571 if ($this->plots[$j]->coords[0][$i] == 0 ) continue;
573 if( $this->plots[$j]->grad ) {
574 $grad = new Gradient($img);
575 $grad->FilledRectangle(
578 $this->plots[$j]->grad_fromcolor,
579 $this->plots[$j]->grad_tocolor,
580 $this->plots[$j]->grad_style);
582 if (is_array($this->plots[$j]->fill_color) ) {
583 $numcolors = count($this->plots[$j]->fill_color);
584 $img->SetColor($this->plots[$j]->fill_color[$i % $numcolors]);
587 $img->SetColor($this->plots[$j]->fill_color);
589 $img->FilledPolygon($pts);
590 $img->SetColor($this->plots[$j]->color);
596 if( $i < count($this->plots[$j]->csimtargets) ) {
597 // Create the client side image map
598 $rpts = $img->ArrRotate($pts);
599 $csimcoord=round($rpts[0]).", ".round($rpts[1]);
600 for( $k=1; $k < 4; ++$k){
601 $csimcoord .= ", ".round($rpts[2*$k]).", ".round($rpts[2*$k+1]);
603 if( ! empty($this->plots[$j]->csimtargets[$i]) ) {
604 $this->csimareas.= '<area shape="poly" coords="'.$csimcoord.'" ';
605 $this->csimareas.= " href=\"".$this->plots[$j]->csimtargets[$i]."\"";
606 if( !empty($this->plots[$j]->csimalts[$i]) ) {
607 $sval=sprintf($this->plots[$j]->csimalts[$i],$this->plots[$j]->coords[0][$i]);
608 $this->csimareas .= " alt=\"$sval\" title=\"$sval\" ";
610 $this->csimareas .= ">\n";
619 // Draw labels for each acc.bar
621 $x=$pts[2]+($pts[4]-$pts[2])/2;
622 $y=$yscale->Translate($accy);
623 if($this->bar_shadow) $x += $ssh;
624 $this->value->Stroke($img,$accy,$x,$y);
628 for($j=0; $j<$this->nbrplots; ++$j ) {
629 if ($this->plots[$j]->coords[0][$i] > 0) {
630 $yt=$yscale->Translate($this->plots[$j]->coords[0][$i]+$accy);
631 $accyt=$yscale->Translate($accy);
632 $y = $accyt-($accyt-$yt)/2;
633 $accy+=$this->plots[$j]->coords[0][$i];
635 $yt=$yscale->Translate($this->plots[$j]->coords[0][$i]+$accy_neg);
636 $accyt=$yscale->Translate($accy_neg);
638 $accy_neg+=$this->plots[$j]->coords[0][$i];
639 $y = $accyt-($accyt-$yt)/2; // TODO : Check this fix
641 $this->plots[$j]->value->SetAlign("center","center");
642 $this->plots[$j]->value->SetMargin(0);
643 $this->plots[$j]->value->Stroke($img,$this->plots[$j]->coords[0][$i],$x,$y);