PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/jpgraph/jpgraph_pie3d.php

https://github.com/TICanalyste/netOffice--remix-
PHP | 903 lines | 592 code | 144 blank | 167 comment | 162 complexity | fe1fcaaacaaa131f598fdae7d161fe11 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /*=======================================================================
  3. // File: JPGRAPH_PIE3D.PHP
  4. // Description: 3D Pie plot extension for JpGraph
  5. // Created: 2001-03-24
  6. // Author: Johan Persson (johanp@aditus.nu)
  7. // Ver: $Id: jpgraph_pie3d.php,v 1.5 2005/06/10 01:07:35 madbear Exp $
  8. //
  9. // Copyright (c) Aditus Consulting. All rights reserved.
  10. //========================================================================
  11. */
  12. //===================================================
  13. // CLASS PiePlot3D
  14. // Description: Plots a 3D pie with a specified projection
  15. // angle between 20 and 70 degrees.
  16. //===================================================
  17. class PiePlot3D extends PiePlot {
  18. var $labelhintcolor="red",$showlabelhint=true;
  19. var $angle=50;
  20. var $edgecolor="", $edgeweight=1;
  21. var $iThickness=false;
  22. //---------------
  23. // CONSTRUCTOR
  24. function PiePlot3d(&$data) {
  25. $this->radius = 0.5;
  26. $this->data = $data;
  27. $this->title = new Text("");
  28. $this->title->SetFont(FF_FONT1,FS_BOLD);
  29. $this->value = new DisplayValue();
  30. $this->value->Show();
  31. $this->value->SetFormat('%.0f%%');
  32. }
  33. //---------------
  34. // PUBLIC METHODS
  35. // Set label arrays
  36. function SetLegends($aLegend) {
  37. $this->legends = array_reverse($aLegend);
  38. }
  39. function SetSliceColors($aColors) {
  40. $this->setslicecolors = $aColors;
  41. }
  42. function Legend(&$aGraph) {
  43. parent::Legend($aGraph);
  44. $aGraph->legend->txtcol = array_reverse($aGraph->legend->txtcol);
  45. }
  46. function SetCSIMTargets($targets,$alts=null) {
  47. $this->csimtargets = $targets;
  48. $this->csimalts = $alts;
  49. }
  50. // Should the slices be separated by a line? If color is specified as "" no line
  51. // will be used to separate pie slices.
  52. function SetEdge($aColor,$aWeight=1) {
  53. $this->edgecolor = $aColor;
  54. $this->edgeweight = $aWeight;
  55. }
  56. // Specify projection angle for 3D in degrees
  57. // Must be between 20 and 70 degrees
  58. function SetAngle($a) {
  59. if( $a<5 || $a>90 )
  60. JpGraphError::Raise("PiePlot3D::SetAngle() 3D Pie projection angle must be between 5 and 85 degrees.");
  61. else
  62. $this->angle = $a;
  63. }
  64. function AddSliceToCSIM($i,$xc,$yc,$height,$width,$thick,$sa,$ea) { //Slice number, ellipse centre (x,y), height, width, start angle, end angle
  65. $sa *= M_PI/180;
  66. $ea *= M_PI/180;
  67. //add coordinates of the centre to the map
  68. $coords = "$xc, $yc";
  69. //add coordinates of the first point on the arc to the map
  70. $xp = floor($width*cos($sa)/2+$xc);
  71. $yp = floor($yc-$height*sin($sa)/2);
  72. $coords.= ", $xp, $yp";
  73. //If on the front half, add the thickness offset
  74. if ($sa >= M_PI && $sa <= 2*M_PI*1.01) {
  75. $yp = floor($yp+$thick);
  76. $coords.= ", $xp, $yp";
  77. }
  78. //add coordinates every 0.2 radians
  79. $a=$sa+0.2;
  80. while ($a<$ea) {
  81. $xp = floor($width*cos($a)/2+$xc);
  82. if ($a >= M_PI && $a <= 2*M_PI*1.01) {
  83. $yp = floor($yc-($height*sin($a)/2)+$thick);
  84. } else {
  85. $yp = floor($yc-$height*sin($a)/2);
  86. }
  87. $coords.= ", $xp, $yp";
  88. $a += 0.2;
  89. }
  90. //Add the last point on the arc
  91. $xp = floor($width*cos($ea)/2+$xc);
  92. $yp = floor($yc-$height*sin($ea)/2);
  93. if ($ea >= M_PI && $ea <= 2*M_PI*1.01) {
  94. $coords.= ", $xp, ".floor($yp+$thick);
  95. }
  96. $coords.= ", $xp, $yp";
  97. $alt='';
  98. if( !empty($this->csimalts[$i]) ) {
  99. $tmp=sprintf($this->csimalts[$i],$this->data[$i]);
  100. $alt="alt=\"$tmp\" title=\"$tmp\"";
  101. }
  102. if( !empty($this->csimtargets[$i]) )
  103. $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->csimtargets[$i]."\" $alt>\n";
  104. }
  105. function SetLabels($aLabels,$aLblPosAdj="auto") {
  106. $this->labels = $aLabels;
  107. $this->ilabelposadj=$aLblPosAdj;
  108. }
  109. // Distance from the pie to the labels
  110. function SetLabelMargin($m) {
  111. $this->value->SetMargin($m);
  112. }
  113. // Show a thin line from the pie to the label for a specific slice
  114. function ShowLabelHint($f=true) {
  115. $this->showlabelhint=$f;
  116. }
  117. // Set color of hint line to label for each slice
  118. function SetLabelHintColor($c) {
  119. $this->labelhintcolor=$c;
  120. }
  121. function SetHeight($aHeight) {
  122. $this->iThickness = $aHeight;
  123. }
  124. // Normalize Angle between 0-360
  125. function NormAngle($a) {
  126. // Normalize anle to 0 to 2M_PI
  127. //
  128. if( $a > 0 ) {
  129. while($a > 360) $a -= 360;
  130. }
  131. else {
  132. while($a < 0) $a += 360;
  133. }
  134. if( $a < 0 )
  135. $a = 360 + $a;
  136. if( $a == 360 ) $a=0;
  137. return $a;
  138. }
  139. // Draw one 3D pie slice at position ($xc,$yc) with height $z
  140. function Pie3DSlice($img,$xc,$yc,$w,$h,$sa,$ea,$z,$fillcolor,$shadow=0.65) {
  141. // Due to the way the 3D Pie algorithm works we are
  142. // guaranteed that any slice we get into this method
  143. // belongs to either the left or right side of the
  144. // pie ellipse. Hence, no slice will cross 90 or 270
  145. // point.
  146. if( ($sa < 90 && $ea > 90) || ( ($sa > 90 && $sa < 270) && $ea > 270) ) {
  147. JpGraphError::Raise('Internal assertion failed. Pie3D::Pie3DSlice');
  148. exit(1);
  149. }
  150. $p[] = array();
  151. // Setup pre-calculated values
  152. $rsa = $sa/180*M_PI; // to Rad
  153. $rea = $ea/180*M_PI; // to Rad
  154. $sinsa = sin($rsa);
  155. $cossa = cos($rsa);
  156. $sinea = sin($rea);
  157. $cosea = cos($rea);
  158. // p[] is the points for the overall slice and
  159. // pt[] is the points for the top pie
  160. // Angular step when approximating the arc with a polygon train.
  161. $step = 0.05;
  162. if( $sa >= 270 ) {
  163. if( $ea > 360 || ($ea > 0 && $ea <= 90) ) {
  164. if( $ea > 0 && $ea <= 90 ) {
  165. // Adjust angle to simplify conditions in loops
  166. $rea += 2*M_PI;
  167. }
  168. $p = array($xc,$yc,$xc,$yc+$z,
  169. $xc+$w*$cossa,$z+$yc-$h*$sinsa);
  170. $pt = array($xc,$yc,$xc+$w*$cossa,$yc-$h*$sinsa);
  171. for( $a=$rsa; $a < 2*M_PI; $a += $step ) {
  172. $tca = cos($a);
  173. $tsa = sin($a);
  174. $p[] = $xc+$w*$tca;
  175. $p[] = $z+$yc-$h*$tsa;
  176. $pt[] = $xc+$w*$tca;
  177. $pt[] = $yc-$h*$tsa;
  178. }
  179. $pt[] = $xc+$w;
  180. $pt[] = $yc;
  181. $p[] = $xc+$w;
  182. $p[] = $z+$yc;
  183. $p[] = $xc+$w;
  184. $p[] = $yc;
  185. $p[] = $xc;
  186. $p[] = $yc;
  187. for( $a=2*M_PI+$step; $a < $rea; $a += $step ) {
  188. $pt[] = $xc + $w*cos($a);
  189. $pt[] = $yc - $h*sin($a);
  190. }
  191. $pt[] = $xc+$w*$cosea;
  192. $pt[] = $yc-$h*$sinea;
  193. $pt[] = $xc;
  194. $pt[] = $yc;
  195. }
  196. else {
  197. $p = array($xc,$yc,$xc,$yc+$z,
  198. $xc+$w*$cossa,$z+$yc-$h*$sinsa);
  199. $pt = array($xc,$yc,$xc+$w*$cossa,$yc-$h*$sinsa);
  200. $rea = $rea == 0.0 ? 2*M_PI : $rea;
  201. for( $a=$rsa; $a < $rea; $a += $step ) {
  202. $tca = cos($a);
  203. $tsa = sin($a);
  204. $p[] = $xc+$w*$tca;
  205. $p[] = $z+$yc-$h*$tsa;
  206. $pt[] = $xc+$w*$tca;
  207. $pt[] = $yc-$h*$tsa;
  208. }
  209. $pt[] = $xc+$w*$cosea;
  210. $pt[] = $yc-$h*$sinea;
  211. $pt[] = $xc;
  212. $pt[] = $yc;
  213. $p[] = $xc+$w*$cosea;
  214. $p[] = $z+$yc-$h*$sinea;
  215. $p[] = $xc+$w*$cosea;
  216. $p[] = $yc-$h*$sinea;
  217. $p[] = $xc;
  218. $p[] = $yc;
  219. }
  220. }
  221. elseif( $sa >= 180 ) {
  222. $p = array($xc,$yc,$xc,$yc+$z,$xc+$w*$cosea,$z+$yc-$h*$sinea);
  223. $pt = array($xc,$yc,$xc+$w*$cosea,$yc-$h*$sinea);
  224. for( $a=$rea; $a>$rsa; $a -= $step ) {
  225. $tca = cos($a);
  226. $tsa = sin($a);
  227. $p[] = $xc+$w*$tca;
  228. $p[] = $z+$yc-$h*$tsa;
  229. $pt[] = $xc+$w*$tca;
  230. $pt[] = $yc-$h*$tsa;
  231. }
  232. $pt[] = $xc+$w*$cossa;
  233. $pt[] = $yc-$h*$sinsa;
  234. $pt[] = $xc;
  235. $pt[] = $yc;
  236. $p[] = $xc+$w*$cossa;
  237. $p[] = $z+$yc-$h*$sinsa;
  238. $p[] = $xc+$w*$cossa;
  239. $p[] = $yc-$h*$sinsa;
  240. $p[] = $xc;
  241. $p[] = $yc;
  242. }
  243. elseif( $sa >= 90 ) {
  244. if( $ea > 180 ) {
  245. $p = array($xc,$yc,$xc,$yc+$z,$xc+$w*$cosea,$z+$yc-$h*$sinea);
  246. $pt = array($xc,$yc,$xc+$w*$cosea,$yc-$h*$sinea);
  247. for( $a=$rea; $a > M_PI; $a -= $step ) {
  248. $tca = cos($a);
  249. $tsa = sin($a);
  250. $p[] = $xc+$w*$tca;
  251. $p[] = $z + $yc - $h*$tsa;
  252. $pt[] = $xc+$w*$tca;
  253. $pt[] = $yc-$h*$tsa;
  254. }
  255. $p[] = $xc-$w;
  256. $p[] = $z+$yc;
  257. $p[] = $xc-$w;
  258. $p[] = $yc;
  259. $p[] = $xc;
  260. $p[] = $yc;
  261. $pt[] = $xc-$w;
  262. $pt[] = $z+$yc;
  263. $pt[] = $xc-$w;
  264. $pt[] = $yc;
  265. for( $a=M_PI-$step; $a > $rsa; $a -= $step ) {
  266. $pt[] = $xc + $w*cos($a);
  267. $pt[] = $yc - $h*sin($a);
  268. }
  269. $pt[] = $xc+$w*$cossa;
  270. $pt[] = $yc-$h*$sinsa;
  271. $pt[] = $xc;
  272. $pt[] = $yc;
  273. }
  274. else { // $sa >= 90 && $ea <= 180
  275. $p = array($xc,$yc,$xc,$yc+$z,
  276. $xc+$w*$cosea,$z+$yc-$h*$sinea,
  277. $xc+$w*$cosea,$yc-$h*$sinea,
  278. $xc,$yc);
  279. $pt = array($xc,$yc,$xc+$w*$cosea,$yc-$h*$sinea);
  280. for( $a=$rea; $a>$rsa; $a -= $step ) {
  281. $pt[] = $xc + $w*cos($a);
  282. $pt[] = $yc - $h*sin($a);
  283. }
  284. $pt[] = $xc+$w*$cossa;
  285. $pt[] = $yc-$h*$sinsa;
  286. $pt[] = $xc;
  287. $pt[] = $yc;
  288. }
  289. }
  290. else { // sa > 0 && ea < 90
  291. $p = array($xc,$yc,$xc,$yc+$z,
  292. $xc+$w*$cossa,$z+$yc-$h*$sinsa,
  293. $xc+$w*$cossa,$yc-$h*$sinsa,
  294. $xc,$yc);
  295. $pt = array($xc,$yc,$xc+$w*$cossa,$yc-$h*$sinsa);
  296. for( $a=$rsa; $a < $rea; $a += $step ) {
  297. $pt[] = $xc + $w*cos($a);
  298. $pt[] = $yc - $h*sin($a);
  299. }
  300. $pt[] = $xc+$w*$cosea;
  301. $pt[] = $yc-$h*$sinea;
  302. $pt[] = $xc;
  303. $pt[] = $yc;
  304. }
  305. $img->PushColor($fillcolor.":".$shadow);
  306. $img->FilledPolygon($p);
  307. $img->PopColor();
  308. $img->PushColor($fillcolor);
  309. $img->FilledPolygon($pt);
  310. $img->PopColor();
  311. }
  312. function SetStartAngle($aStart) {
  313. if( $aStart < 0 || $aStart > 360 ) {
  314. JpGraphError::Raise('Slice start angle must be between 0 and 360 degrees.');
  315. }
  316. $this->startangle = $aStart;
  317. }
  318. // Draw a 3D Pie
  319. function Pie3D($aaoption,$img,$data,$colors,$xc,$yc,$d,$angle,$z,
  320. $shadow=0.65,$startangle=0,$edgecolor="",$edgeweight=1) {
  321. //---------------------------------------------------------------------------
  322. // As usual the algorithm get more complicated than I originally
  323. // envisioned. I believe that this is as simple as it is possible
  324. // to do it with the features I want. It's a good exercise to start
  325. // thinking on how to do this to convince your self that all this
  326. // is really needed for the general case.
  327. //
  328. // The algorithm two draw 3D pies without "real 3D" is done in
  329. // two steps.
  330. // First imagine the pie cut in half through a thought line between
  331. // 12'a clock and 6'a clock. It now easy to imagine that we can plot
  332. // the individual slices for each half by starting with the topmost
  333. // pie slice and continue down to 6'a clock.
  334. //
  335. // In the algortithm this is done in three principal steps
  336. // Step 1. Do the knife cut to ensure by splitting slices that extends
  337. // over the cut line. This is done by splitting the original slices into
  338. // upto 3 subslices.
  339. // Step 2. Find the top slice for each half
  340. // Step 3. Draw the slices from top to bottom
  341. //
  342. // The thing that slightly complicates this scheme with all the
  343. // angle comparisons below is that we can have an arbitrary start
  344. // angle so we must take into account the different equivalence classes.
  345. // For the same reason we must walk through the angle array in a
  346. // modulo fashion.
  347. //
  348. // Limitations of algorithm:
  349. // * A small exploded slice which crosses the 270 degree point
  350. // will get slightly nagged close to the center due to the fact that
  351. // we print the slices in Z-order and that the slice left part
  352. // get printed first and might get slightly nagged by a larger
  353. // slice on the right side just before the right part of the small
  354. // slice. Not a major problem though.
  355. //---------------------------------------------------------------------------
  356. // Determine the height of the ellippse which gives an
  357. // indication of the inclination angle
  358. $h = ($angle/90.0)*$d;
  359. $sum = 0;
  360. for($i=0; $i<count($data); ++$i ) {
  361. $sum += $data[$i];
  362. }
  363. // Special optimization
  364. if( $sum==0 ) return;
  365. if( $this->labeltype == 2 ) {
  366. $this->adjusted_data = $this->AdjPercentage($data);
  367. }
  368. // Setup the start
  369. $accsum = 0;
  370. $a = $startangle;
  371. $a = $this->NormAngle($a);
  372. //
  373. // Step 1 . Split all slices that crosses 90 or 270
  374. //
  375. $idx=0;
  376. $adjexplode=array();
  377. $numcolors = count($colors);
  378. for($i=0; $i<count($data); ++$i, ++$idx ) {
  379. $da = $data[$i]/$sum * 360;
  380. if( empty($this->explode_radius[$i]) )
  381. $this->explode_radius[$i]=0;
  382. $expscale=1;
  383. if( $aaoption == 1 )
  384. $expscale=2;
  385. $la = $a + $da/2;
  386. $explode = array( $xc + $this->explode_radius[$i]*cos($la*M_PI/180)*$expscale,
  387. $yc - $this->explode_radius[$i]*sin($la*M_PI/180) * ($h/$d) *$expscale );
  388. $adjexplode[$idx] = $explode;
  389. $labeldata[$i] = array($la,$explode[0],$explode[1]);
  390. $originalangles[$i] = array($a,$a+$da);
  391. $ne = $this->NormAngle($a+$da);
  392. if( $da <= 180 ) {
  393. // If the slice size is <= 90 it can at maximum cut across
  394. // one boundary (either 90 or 270) where it needs to be split
  395. $split=-1; // no split
  396. if( ($da<=90 && ($a <= 90 && $ne > 90)) ||
  397. (($da <= 180 && $da >90) && (($a < 90 || $a >= 270) && $ne > 90)) ) {
  398. $split = 90;
  399. }
  400. elseif( ($da<=90 && ($a <= 270 && $ne > 270)) ||
  401. (($da<=180 && $da>90) && ($a >= 90 && $a < 270 && ($a+$da) > 270 )) ) {
  402. $split = 270;
  403. }
  404. if( $split > 0 ) { // split in two
  405. $angles[$idx] = array($a,$split);
  406. $adjcolors[$idx] = $colors[$i % $numcolors];
  407. $adjexplode[$idx] = $explode;
  408. $angles[++$idx] = array($split,$ne);
  409. $adjcolors[$idx] = $colors[$i % $numcolors];
  410. $adjexplode[$idx] = $explode;
  411. }
  412. else { // no split
  413. $angles[$idx] = array($a,$ne);
  414. $adjcolors[$idx] = $colors[$i % $numcolors];
  415. $adjexplode[$idx] = $explode;
  416. }
  417. }
  418. else {
  419. // da>180
  420. // Slice may, depending on position, cross one or two
  421. // bonudaries
  422. if( $a < 90 )
  423. $split = 90;
  424. elseif( $a <= 270 )
  425. $split = 270;
  426. else
  427. $split = 90;
  428. $angles[$idx] = array($a,$split);
  429. $adjcolors[$idx] = $colors[$i % $numcolors];
  430. $adjexplode[$idx] = $explode;
  431. //if( $a+$da > 360-$split ) {
  432. // For slices larger than 270 degrees we might cross
  433. // another boundary as well. This means that we must
  434. // split the slice further. The comparison gets a little
  435. // bit complicated since we must take into accound that
  436. // a pie might have a startangle >0 and hence a slice might
  437. // wrap around the 0 angle.
  438. // Three cases:
  439. // a) Slice starts before 90 and hence gets a split=90, but
  440. // we must also check if we need to split at 270
  441. // b) Slice starts after 90 but before 270 and slices
  442. // crosses 90 (after a wrap around of 0)
  443. // c) If start is > 270 (hence the firstr split is at 90)
  444. // and the slice is so large that it goes all the way
  445. // around 270.
  446. if( ($a < 90 && ($a+$da > 270)) ||
  447. ($a > 90 && $a<=270 && ($a+$da>360+90) ) ||
  448. ($a > 270 && $this->NormAngle($a+$da)>270) ) {
  449. $angles[++$idx] = array($split,360-$split);
  450. $adjcolors[$idx] = $colors[$i % $numcolors];
  451. $adjexplode[$idx] = $explode;
  452. $angles[++$idx] = array(360-$split,$ne);
  453. $adjcolors[$idx] = $colors[$i % $numcolors];
  454. $adjexplode[$idx] = $explode;
  455. }
  456. else {
  457. // Just a simple split to the previous decided
  458. // angle.
  459. $angles[++$idx] = array($split,$ne);
  460. $adjcolors[$idx] = $colors[$i % $numcolors];
  461. $adjexplode[$idx] = $explode;
  462. }
  463. }
  464. $a += $da;
  465. $a = $this->NormAngle($a);
  466. }
  467. // Total number of slices
  468. $n = count($angles);
  469. for($i=0; $i<$n; ++$i) {
  470. list($dbgs,$dbge) = $angles[$i];
  471. }
  472. //
  473. // Step 2. Find start index (first pie that starts in upper left quadrant)
  474. //
  475. $minval = $angles[0][0];
  476. $min = 0;
  477. for( $i=0; $i<$n; ++$i ) {
  478. if( $angles[$i][0] < $minval ) {
  479. $minval = $angles[$i][0];
  480. $min = $i;
  481. }
  482. }
  483. $j = $min;
  484. $cnt = 0;
  485. while( $angles[$j][1] <= 90 ) {
  486. $j++;
  487. if( $j>=$n) {
  488. $j=0;
  489. }
  490. if( $cnt > $n ) {
  491. JpGraphError::Raise("Pie3D Internal error (#1). Trying to wrap twice when looking for start index");
  492. }
  493. ++$cnt;
  494. }
  495. $start = $j;
  496. //
  497. // Step 3. Print slices in z-order
  498. //
  499. $cnt = 0;
  500. // First stroke all the slices between 90 and 270 (left half circle)
  501. // counterclockwise
  502. while( $angles[$j][0] < 270 && $aaoption !== 2 ) {
  503. list($x,$y) = $adjexplode[$j];
  504. $this->Pie3DSlice($img,$x,$y,$d,$h,$angles[$j][0],$angles[$j][1],
  505. $z,$adjcolors[$j],$shadow);
  506. $last = array($x,$y,$j);
  507. $j++;
  508. if( $j >= $n ) $j=0;
  509. if( $cnt > $n ) {
  510. JpGraphError::Raise("Pie3D Internal Error: Z-Sorting algorithm for 3D Pies is not working properly (2). Trying to wrap twice while stroking.");
  511. }
  512. ++$cnt;
  513. }
  514. $slice_left = $n-$cnt;
  515. $j=$start-1;
  516. if($j<0) $j=$n-1;
  517. $cnt = 0;
  518. // The stroke all slices from 90 to -90 (right half circle)
  519. // clockwise
  520. while( $cnt < $slice_left && $aaoption !== 2 ) {
  521. list($x,$y) = $adjexplode[$j];
  522. $this->Pie3DSlice($img,$x,$y,$d,$h,$angles[$j][0],$angles[$j][1],
  523. $z,$adjcolors[$j],$shadow);
  524. $j--;
  525. if( $cnt > $n ) {
  526. JpGraphError::Raise("Pie3D Internal Error: Z-Sorting algorithm for 3D Pies is not working properly (2). Trying to wrap twice while stroking.");
  527. }
  528. if($j<0) $j=$n-1;
  529. $cnt++;
  530. }
  531. // Now do a special thing. Stroke the last slice on the left
  532. // halfcircle one more time. This is needed in the case where
  533. // the slice close to 270 have been exploded. In that case the
  534. // part of the slice close to the center of the pie might be
  535. // slightly nagged.
  536. if( $aaoption !== 2 )
  537. $this->Pie3DSlice($img,$last[0],$last[1],$d,$h,$angles[$last[2]][0],
  538. $angles[$last[2]][1],$z,$adjcolors[$last[2]],$shadow);
  539. if( $aaoption !== 1 ) {
  540. // Now print possible labels and add csim
  541. $img->SetFont($this->value->ff,$this->value->fs);
  542. $margin = $img->GetFontHeight()/2 + $this->value->margin ;
  543. for($i=0; $i < count($data); ++$i ) {
  544. $la = $labeldata[$i][0];
  545. $x = $labeldata[$i][1] + cos($la*M_PI/180)*($d+$margin);
  546. $y = $labeldata[$i][2] - sin($la*M_PI/180)*($h+$margin);
  547. if( $la > 180 && $la < 360 ) $y += $z;
  548. if( $this->labeltype == 0 ) {
  549. if( $sum > 0 )
  550. $l = 100*$data[$i]/$sum;
  551. else
  552. $l = 0;
  553. }
  554. elseif( $this->labeltype == 1 ) {
  555. $l = $data[$i];
  556. }
  557. else {
  558. $l = $this->adjusted_data[$i];
  559. }
  560. if( isset($this->labels[$i]) && is_string($this->labels[$i]) )
  561. $l=sprintf($this->labels[$i],$l);
  562. $this->StrokeLabels($l,$img,$labeldata[$i][0]*M_PI/180,$x,$y,$z);
  563. $this->AddSliceToCSIM($i,$labeldata[$i][1],$labeldata[$i][2],$h*2,$d*2,$z,
  564. $originalangles[$i][0],$originalangles[$i][1]);
  565. }
  566. }
  567. //
  568. // Finally add potential lines in pie
  569. //
  570. if( $edgecolor=="" || $aaoption !== 0 ) return;
  571. $accsum = 0;
  572. $a = $startangle;
  573. $a = $this->NormAngle($a);
  574. $a *= M_PI/180.0;
  575. $idx=0;
  576. $img->PushColor($edgecolor);
  577. $img->SetLineWeight($edgeweight);
  578. $fulledge = true;
  579. for($i=0; $i < count($data) && $fulledge; ++$i ) {
  580. if( empty($this->explode_radius[$i]) )
  581. $this->explode_radius[$i]=0;
  582. if( $this->explode_radius[$i] > 0 ) {
  583. $fulledge = false;
  584. }
  585. }
  586. for($i=0; $i < count($data); ++$i, ++$idx ) {
  587. $da = $data[$i]/$sum * 2*M_PI;
  588. $this->StrokeFullSliceFrame($img,$xc,$yc,$a,$a+$da,$d,$h,$z,$edgecolor,
  589. $this->explode_radius[$i],$fulledge);
  590. $a += $da;
  591. }
  592. $img->PopColor();
  593. }
  594. function StrokeFullSliceFrame($img,$xc,$yc,$sa,$ea,$w,$h,$z,$edgecolor,$exploderadius,$fulledge) {
  595. $step = 0.02;
  596. if( $exploderadius > 0 ) {
  597. $la = ($sa+$ea)/2;
  598. $xc += $exploderadius*cos($la);
  599. $yc -= $exploderadius*sin($la) * ($h/$w) ;
  600. }
  601. $p = array($xc,$yc,$xc+$w*cos($sa),$yc-$h*sin($sa));
  602. for($a=$sa; $a < $ea; $a += $step ) {
  603. $p[] = $xc + $w*cos($a);
  604. $p[] = $yc - $h*sin($a);
  605. }
  606. $p[] = $xc+$w*cos($ea);
  607. $p[] = $yc-$h*sin($ea);
  608. $p[] = $xc;
  609. $p[] = $yc;
  610. $img->SetColor($edgecolor);
  611. $img->Polygon($p);
  612. // Unfortunately we can't really draw the full edge around the whole of
  613. // of the slice if any of the slices are exploded. The reason is that
  614. // this algorithm is to simply. There are cases where the edges will
  615. // "overwrite" other slices when they have been exploded.
  616. // Doing the full, proper 3D hidden lines stiff is actually quite
  617. // tricky. So for exploded pies we only draw the top edge. Not perfect
  618. // but the "real" solution is much more complicated.
  619. if( $fulledge && !( $sa > 0 && $sa < M_PI && $ea < M_PI) ) {
  620. if($sa < M_PI && $ea > M_PI)
  621. $sa = M_PI;
  622. if($sa < 2*M_PI && (($ea >= 2*M_PI) || ($ea > 0 && $ea < $sa ) ) )
  623. $ea = 2*M_PI;
  624. if( $sa >= M_PI && $ea <= 2*M_PI ) {
  625. $p = array($xc + $w*cos($sa),$yc - $h*sin($sa),
  626. $xc + $w*cos($sa),$z + $yc - $h*sin($sa));
  627. for($a=$sa+$step; $a < $ea; $a += $step ) {
  628. $p[] = $xc + $w*cos($a);
  629. $p[] = $z + $yc - $h*sin($a);
  630. }
  631. $p[] = $xc + $w*cos($ea);
  632. $p[] = $z + $yc - $h*sin($ea);
  633. $p[] = $xc + $w*cos($ea);
  634. $p[] = $yc - $h*sin($ea);
  635. $img->SetColor($edgecolor);
  636. $img->Polygon($p);
  637. }
  638. }
  639. }
  640. function Stroke($img,$aaoption=0) {
  641. $n = count($this->data);
  642. // If user hasn't set the colors use the theme array
  643. if( $this->setslicecolors==null ) {
  644. $colors = array_keys($img->rgb->rgb_table);
  645. sort($colors);
  646. $idx_a=$this->themearr[$this->theme];
  647. $ca = array();
  648. $m = count($idx_a);
  649. for($i=0; $i < $m; ++$i)
  650. $ca[$i] = $colors[$idx_a[$i]];
  651. $ca = array_reverse(array_slice($ca,0,$n));
  652. }
  653. else {
  654. $ca = $this->setslicecolors;
  655. }
  656. if( $this->posx <= 1 && $this->posx > 0 )
  657. $xc = round($this->posx*$img->width);
  658. else
  659. $xc = $this->posx ;
  660. if( $this->posy <= 1 && $this->posy > 0 )
  661. $yc = round($this->posy*$img->height);
  662. else
  663. $yc = $this->posy ;
  664. if( $this->radius <= 1 ) {
  665. $width = floor($this->radius*min($img->width,$img->height));
  666. // Make sure that the pie doesn't overflow the image border
  667. // The 0.9 factor is simply an extra margin to leave some space
  668. // between the pie an the border of the image.
  669. $width = min($width,min($xc*0.9,($yc*90/$this->angle-$width/4)*0.9));
  670. }
  671. else {
  672. $width = $this->radius * ($aaoption === 1 ? 2 : 1 ) ;
  673. }
  674. // Add a sanity check for width
  675. if( $width < 1 ) {
  676. JpGraphError::Raise("Width for 3D Pie is 0. Specify a size > 0");
  677. exit();
  678. }
  679. // Establish a thickness. By default the thickness is a fifth of the
  680. // pie slice width (=pie radius) but since the perspective depends
  681. // on the inclination angle we use some heuristics to make the edge
  682. // slightly thicker the less the angle.
  683. // Has user specified an absolute thickness? In that case use
  684. // that instead
  685. if( $this->iThickness ) {
  686. $thick = $this->iThickness;
  687. $thick *= ($aaoption === 1 ? 2 : 1 );
  688. }
  689. else
  690. $thick = $width/12;
  691. $a = $this->angle;
  692. if( $a <= 30 ) $thick *= 1.6;
  693. elseif( $a <= 40 ) $thick *= 1.4;
  694. elseif( $a <= 50 ) $thick *= 1.2;
  695. elseif( $a <= 60 ) $thick *= 1.0;
  696. elseif( $a <= 70 ) $thick *= 0.8;
  697. elseif( $a <= 80 ) $thick *= 0.7;
  698. else $thick *= 0.6;
  699. $thick = floor($thick);
  700. if( $this->explode_all )
  701. for($i=0; $i < $n; ++$i)
  702. $this->explode_radius[$i]=$this->explode_r;
  703. $this->Pie3D($aaoption,$img,$this->data, $ca, $xc, $yc, $width, $this->angle,
  704. $thick, 0.65, $this->startangle, $this->edgecolor, $this->edgeweight);
  705. // Adjust title position
  706. if( $aaoption != 1 ) {
  707. $this->title->Pos($xc,$yc-$this->title->GetFontHeight($img)-$width/2-$this->title->margin, "center","bottom");
  708. $this->title->Stroke($img);
  709. }
  710. }
  711. //---------------
  712. // PRIVATE METHODS
  713. // Position the labels of each slice
  714. function StrokeLabels($label,$img,$a,$xp,$yp,$z) {
  715. $this->value->halign="left";
  716. $this->value->valign="top";
  717. // Position the axis title.
  718. // dx, dy is the offset from the top left corner of the bounding box that sorrounds the text
  719. // that intersects with the extension of the corresponding axis. The code looks a little
  720. // bit messy but this is really the only way of having a reasonable position of the
  721. // axis titles.
  722. $img->SetFont($this->value->ff,$this->value->fs,$this->value->fsize);
  723. $h=$img->GetTextHeight($label);
  724. // For numeric values the format of the display value
  725. // must be taken into account
  726. if( is_numeric($label) ) {
  727. if( $label >= 0 )
  728. $w=$img->GetTextWidth(sprintf($this->value->format,$label));
  729. else
  730. $w=$img->GetTextWidth(sprintf($this->value->negformat,$label));
  731. }
  732. else
  733. $w=$img->GetTextWidth($label);
  734. while( $a > 2*M_PI ) $a -= 2*M_PI;
  735. if( $a>=7*M_PI/4 || $a <= M_PI/4 ) $dx=0;
  736. if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dx=($a-M_PI/4)*2/M_PI;
  737. if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dx=1;
  738. if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dx=(1-($a-M_PI*5/4)*2/M_PI);
  739. if( $a>=7*M_PI/4 ) $dy=(($a-M_PI)-3*M_PI/4)*2/M_PI;
  740. if( $a<=M_PI/4 ) $dy=(1-$a*2/M_PI);
  741. if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dy=1;
  742. if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dy=(1-($a-3*M_PI/4)*2/M_PI);
  743. if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dy=0;
  744. $x = round($xp-$dx*$w);
  745. $y = round($yp-$dy*$h);
  746. // Mark anchor point for debugging
  747. /*
  748. $img->SetColor('red');
  749. $img->Line($xp-10,$yp,$xp+10,$yp);
  750. $img->Line($xp,$yp-10,$xp,$yp+10);
  751. */
  752. $oldmargin = $this->value->margin;
  753. $this->value->margin=0;
  754. $this->value->Stroke($img,$label,$x,$y);
  755. $this->value->margin=$oldmargin;
  756. }
  757. } // Class
  758. /* EOF */
  759. ?>