JpGraph.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
  3. use PhpOffice\PhpSpreadsheet\Chart\Chart;
  4. use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
  5. require_once __DIR__ . '/Polyfill.php';
  6. class JpGraph implements IRenderer
  7. {
  8. private static $width = 640;
  9. private static $height = 480;
  10. private static $colourSet = [
  11. 'mediumpurple1', 'palegreen3', 'gold1', 'cadetblue1',
  12. 'darkmagenta', 'coral', 'dodgerblue3', 'eggplant',
  13. 'mediumblue', 'magenta', 'sandybrown', 'cyan',
  14. 'firebrick1', 'forestgreen', 'deeppink4', 'darkolivegreen',
  15. 'goldenrod2',
  16. ];
  17. private static $markSet;
  18. private $chart;
  19. private $graph;
  20. private static $plotColour = 0;
  21. private static $plotMark = 0;
  22. /**
  23. * Create a new jpgraph.
  24. *
  25. * @param Chart $chart
  26. */
  27. public function __construct(Chart $chart)
  28. {
  29. self::init();
  30. $this->graph = null;
  31. $this->chart = $chart;
  32. }
  33. private static function init()
  34. {
  35. static $loaded = false;
  36. if ($loaded) {
  37. return;
  38. }
  39. \JpGraph\JpGraph::load();
  40. \JpGraph\JpGraph::module('bar');
  41. \JpGraph\JpGraph::module('contour');
  42. \JpGraph\JpGraph::module('line');
  43. \JpGraph\JpGraph::module('pie');
  44. \JpGraph\JpGraph::module('pie3d');
  45. \JpGraph\JpGraph::module('radar');
  46. \JpGraph\JpGraph::module('regstat');
  47. \JpGraph\JpGraph::module('scatter');
  48. \JpGraph\JpGraph::module('stock');
  49. self::$markSet = [
  50. 'diamond' => MARK_DIAMOND,
  51. 'square' => MARK_SQUARE,
  52. 'triangle' => MARK_UTRIANGLE,
  53. 'x' => MARK_X,
  54. 'star' => MARK_STAR,
  55. 'dot' => MARK_FILLEDCIRCLE,
  56. 'dash' => MARK_DTRIANGLE,
  57. 'circle' => MARK_CIRCLE,
  58. 'plus' => MARK_CROSS,
  59. ];
  60. $loaded = true;
  61. }
  62. private function formatPointMarker($seriesPlot, $markerID)
  63. {
  64. $plotMarkKeys = array_keys(self::$markSet);
  65. if ($markerID === null) {
  66. // Use default plot marker (next marker in the series)
  67. self::$plotMark %= count(self::$markSet);
  68. $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
  69. } elseif ($markerID !== 'none') {
  70. // Use specified plot marker (if it exists)
  71. if (isset(self::$markSet[$markerID])) {
  72. $seriesPlot->mark->SetType(self::$markSet[$markerID]);
  73. } else {
  74. // If the specified plot marker doesn't exist, use default plot marker (next marker in the series)
  75. self::$plotMark %= count(self::$markSet);
  76. $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
  77. }
  78. } else {
  79. // Hide plot marker
  80. $seriesPlot->mark->Hide();
  81. }
  82. $seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]);
  83. $seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]);
  84. $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
  85. return $seriesPlot;
  86. }
  87. private function formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation = '')
  88. {
  89. $datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode();
  90. if ($datasetLabelFormatCode !== null) {
  91. // Retrieve any label formatting code
  92. $datasetLabelFormatCode = stripslashes($datasetLabelFormatCode);
  93. }
  94. $testCurrentIndex = 0;
  95. foreach ($datasetLabels as $i => $datasetLabel) {
  96. if (is_array($datasetLabel)) {
  97. if ($rotation == 'bar') {
  98. $datasetLabels[$i] = implode(' ', $datasetLabel);
  99. } else {
  100. $datasetLabel = array_reverse($datasetLabel);
  101. $datasetLabels[$i] = implode("\n", $datasetLabel);
  102. }
  103. } else {
  104. // Format labels according to any formatting code
  105. if ($datasetLabelFormatCode !== null) {
  106. $datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode);
  107. }
  108. }
  109. ++$testCurrentIndex;
  110. }
  111. return $datasetLabels;
  112. }
  113. private function percentageSumCalculation($groupID, $seriesCount)
  114. {
  115. // Adjust our values to a percentage value across all series in the group
  116. for ($i = 0; $i < $seriesCount; ++$i) {
  117. if ($i == 0) {
  118. $sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
  119. } else {
  120. $nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
  121. foreach ($nextValues as $k => $value) {
  122. if (isset($sumValues[$k])) {
  123. $sumValues[$k] += $value;
  124. } else {
  125. $sumValues[$k] = $value;
  126. }
  127. }
  128. }
  129. }
  130. return $sumValues;
  131. }
  132. private function percentageAdjustValues($dataValues, $sumValues)
  133. {
  134. foreach ($dataValues as $k => $dataValue) {
  135. $dataValues[$k] = $dataValue / $sumValues[$k] * 100;
  136. }
  137. return $dataValues;
  138. }
  139. private function getCaption($captionElement)
  140. {
  141. // Read any caption
  142. $caption = ($captionElement !== null) ? $captionElement->getCaption() : null;
  143. // Test if we have a title caption to display
  144. if ($caption !== null) {
  145. // If we do, it could be a plain string or an array
  146. if (is_array($caption)) {
  147. // Implode an array to a plain string
  148. $caption = implode('', $caption);
  149. }
  150. }
  151. return $caption;
  152. }
  153. private function renderTitle()
  154. {
  155. $title = $this->getCaption($this->chart->getTitle());
  156. if ($title !== null) {
  157. $this->graph->title->Set($title);
  158. }
  159. }
  160. private function renderLegend()
  161. {
  162. $legend = $this->chart->getLegend();
  163. if ($legend !== null) {
  164. $legendPosition = $legend->getPosition();
  165. switch ($legendPosition) {
  166. case 'r':
  167. $this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); // right
  168. $this->graph->legend->SetColumns(1);
  169. break;
  170. case 'l':
  171. $this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); // left
  172. $this->graph->legend->SetColumns(1);
  173. break;
  174. case 't':
  175. $this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); // top
  176. break;
  177. case 'b':
  178. $this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); // bottom
  179. break;
  180. default:
  181. $this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); // top-right
  182. $this->graph->legend->SetColumns(1);
  183. break;
  184. }
  185. } else {
  186. $this->graph->legend->Hide();
  187. }
  188. }
  189. private function renderCartesianPlotArea($type = 'textlin')
  190. {
  191. $this->graph = new \Graph(self::$width, self::$height);
  192. $this->graph->SetScale($type);
  193. $this->renderTitle();
  194. // Rotate for bar rather than column chart
  195. $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection();
  196. $reverse = ($rotation == 'bar') ? true : false;
  197. $xAxisLabel = $this->chart->getXAxisLabel();
  198. if ($xAxisLabel !== null) {
  199. $title = $this->getCaption($xAxisLabel);
  200. if ($title !== null) {
  201. $this->graph->xaxis->SetTitle($title, 'center');
  202. $this->graph->xaxis->title->SetMargin(35);
  203. if ($reverse) {
  204. $this->graph->xaxis->title->SetAngle(90);
  205. $this->graph->xaxis->title->SetMargin(90);
  206. }
  207. }
  208. }
  209. $yAxisLabel = $this->chart->getYAxisLabel();
  210. if ($yAxisLabel !== null) {
  211. $title = $this->getCaption($yAxisLabel);
  212. if ($title !== null) {
  213. $this->graph->yaxis->SetTitle($title, 'center');
  214. if ($reverse) {
  215. $this->graph->yaxis->title->SetAngle(0);
  216. $this->graph->yaxis->title->SetMargin(-55);
  217. }
  218. }
  219. }
  220. }
  221. private function renderPiePlotArea()
  222. {
  223. $this->graph = new \PieGraph(self::$width, self::$height);
  224. $this->renderTitle();
  225. }
  226. private function renderRadarPlotArea()
  227. {
  228. $this->graph = new \RadarGraph(self::$width, self::$height);
  229. $this->graph->SetScale('lin');
  230. $this->renderTitle();
  231. }
  232. private function renderPlotLine($groupID, $filled = false, $combination = false, $dimensions = '2d')
  233. {
  234. $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
  235. $labelCount = count($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount());
  236. if ($labelCount > 0) {
  237. $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
  238. $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount);
  239. $this->graph->xaxis->SetTickLabels($datasetLabels);
  240. }
  241. $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
  242. $seriesPlots = [];
  243. if ($grouping == 'percentStacked') {
  244. $sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
  245. }
  246. // Loop through each data series in turn
  247. for ($i = 0; $i < $seriesCount; ++$i) {
  248. $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
  249. $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
  250. if ($grouping == 'percentStacked') {
  251. $dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
  252. }
  253. // Fill in any missing values in the $dataValues array
  254. $testCurrentIndex = 0;
  255. foreach ($dataValues as $k => $dataValue) {
  256. while ($k != $testCurrentIndex) {
  257. $dataValues[$testCurrentIndex] = null;
  258. ++$testCurrentIndex;
  259. }
  260. ++$testCurrentIndex;
  261. }
  262. $seriesPlot = new \LinePlot($dataValues);
  263. if ($combination) {
  264. $seriesPlot->SetBarCenter();
  265. }
  266. if ($filled) {
  267. $seriesPlot->SetFilled(true);
  268. $seriesPlot->SetColor('black');
  269. $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
  270. } else {
  271. // Set the appropriate plot marker
  272. $this->formatPointMarker($seriesPlot, $marker);
  273. }
  274. $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
  275. $seriesPlot->SetLegend($dataLabel);
  276. $seriesPlots[] = $seriesPlot;
  277. }
  278. if ($grouping == 'standard') {
  279. $groupPlot = $seriesPlots;
  280. } else {
  281. $groupPlot = new \AccLinePlot($seriesPlots);
  282. }
  283. $this->graph->Add($groupPlot);
  284. }
  285. private function renderPlotBar($groupID, $dimensions = '2d')
  286. {
  287. $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection();
  288. // Rotate for bar rather than column chart
  289. if (($groupID == 0) && ($rotation == 'bar')) {
  290. $this->graph->Set90AndMargin();
  291. }
  292. $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
  293. $labelCount = count($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount());
  294. if ($labelCount > 0) {
  295. $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
  296. $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation);
  297. // Rotate for bar rather than column chart
  298. if ($rotation == 'bar') {
  299. $datasetLabels = array_reverse($datasetLabels);
  300. $this->graph->yaxis->SetPos('max');
  301. $this->graph->yaxis->SetLabelAlign('center', 'top');
  302. $this->graph->yaxis->SetLabelSide(SIDE_RIGHT);
  303. }
  304. $this->graph->xaxis->SetTickLabels($datasetLabels);
  305. }
  306. $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
  307. $seriesPlots = [];
  308. if ($grouping == 'percentStacked') {
  309. $sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
  310. }
  311. // Loop through each data series in turn
  312. for ($j = 0; $j < $seriesCount; ++$j) {
  313. $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues();
  314. if ($grouping == 'percentStacked') {
  315. $dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
  316. }
  317. // Fill in any missing values in the $dataValues array
  318. $testCurrentIndex = 0;
  319. foreach ($dataValues as $k => $dataValue) {
  320. while ($k != $testCurrentIndex) {
  321. $dataValues[$testCurrentIndex] = null;
  322. ++$testCurrentIndex;
  323. }
  324. ++$testCurrentIndex;
  325. }
  326. // Reverse the $dataValues order for bar rather than column chart
  327. if ($rotation == 'bar') {
  328. $dataValues = array_reverse($dataValues);
  329. }
  330. $seriesPlot = new \BarPlot($dataValues);
  331. $seriesPlot->SetColor('black');
  332. $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
  333. if ($dimensions == '3d') {
  334. $seriesPlot->SetShadow();
  335. }
  336. if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) {
  337. $dataLabel = '';
  338. } else {
  339. $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue();
  340. }
  341. $seriesPlot->SetLegend($dataLabel);
  342. $seriesPlots[] = $seriesPlot;
  343. }
  344. // Reverse the plot order for bar rather than column chart
  345. if (($rotation == 'bar') && (!($grouping == 'percentStacked'))) {
  346. $seriesPlots = array_reverse($seriesPlots);
  347. }
  348. if ($grouping == 'clustered') {
  349. $groupPlot = new \GroupBarPlot($seriesPlots);
  350. } elseif ($grouping == 'standard') {
  351. $groupPlot = new \GroupBarPlot($seriesPlots);
  352. } else {
  353. $groupPlot = new \AccBarPlot($seriesPlots);
  354. if ($dimensions == '3d') {
  355. $groupPlot->SetShadow();
  356. }
  357. }
  358. $this->graph->Add($groupPlot);
  359. }
  360. private function renderPlotScatter($groupID, $bubble)
  361. {
  362. $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
  363. $scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
  364. $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
  365. $seriesPlots = [];
  366. // Loop through each data series in turn
  367. for ($i = 0; $i < $seriesCount; ++$i) {
  368. $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
  369. $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
  370. foreach ($dataValuesY as $k => $dataValueY) {
  371. $dataValuesY[$k] = $k;
  372. }
  373. $seriesPlot = new \ScatterPlot($dataValuesX, $dataValuesY);
  374. if ($scatterStyle == 'lineMarker') {
  375. $seriesPlot->SetLinkPoints();
  376. $seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]);
  377. } elseif ($scatterStyle == 'smoothMarker') {
  378. $spline = new \Spline($dataValuesY, $dataValuesX);
  379. list($splineDataY, $splineDataX) = $spline->Get(count($dataValuesX) * self::$width / 20);
  380. $lplot = new \LinePlot($splineDataX, $splineDataY);
  381. $lplot->SetColor(self::$colourSet[self::$plotColour]);
  382. $this->graph->Add($lplot);
  383. }
  384. if ($bubble) {
  385. $this->formatPointMarker($seriesPlot, 'dot');
  386. $seriesPlot->mark->SetColor('black');
  387. $seriesPlot->mark->SetSize($bubbleSize);
  388. } else {
  389. $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
  390. $this->formatPointMarker($seriesPlot, $marker);
  391. }
  392. $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
  393. $seriesPlot->SetLegend($dataLabel);
  394. $this->graph->Add($seriesPlot);
  395. }
  396. }
  397. private function renderPlotRadar($groupID)
  398. {
  399. $radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
  400. $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
  401. $seriesPlots = [];
  402. // Loop through each data series in turn
  403. for ($i = 0; $i < $seriesCount; ++$i) {
  404. $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
  405. $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
  406. $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
  407. $dataValues = [];
  408. foreach ($dataValuesY as $k => $dataValueY) {
  409. $dataValues[$k] = implode(' ', array_reverse($dataValueY));
  410. }
  411. $tmp = array_shift($dataValues);
  412. $dataValues[] = $tmp;
  413. $tmp = array_shift($dataValuesX);
  414. $dataValuesX[] = $tmp;
  415. $this->graph->SetTitles(array_reverse($dataValues));
  416. $seriesPlot = new \RadarPlot(array_reverse($dataValuesX));
  417. $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
  418. $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
  419. if ($radarStyle == 'filled') {
  420. $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]);
  421. }
  422. $this->formatPointMarker($seriesPlot, $marker);
  423. $seriesPlot->SetLegend($dataLabel);
  424. $this->graph->Add($seriesPlot);
  425. }
  426. }
  427. private function renderPlotContour($groupID)
  428. {
  429. $contourStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
  430. $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
  431. $seriesPlots = [];
  432. $dataValues = [];
  433. // Loop through each data series in turn
  434. for ($i = 0; $i < $seriesCount; ++$i) {
  435. $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
  436. $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
  437. $dataValues[$i] = $dataValuesX;
  438. }
  439. $seriesPlot = new \ContourPlot($dataValues);
  440. $this->graph->Add($seriesPlot);
  441. }
  442. private function renderPlotStock($groupID)
  443. {
  444. $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
  445. $plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder();
  446. $dataValues = [];
  447. // Loop through each data series in turn and build the plot arrays
  448. foreach ($plotOrder as $i => $v) {
  449. $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues();
  450. foreach ($dataValuesX as $j => $dataValueX) {
  451. $dataValues[$plotOrder[$i]][$j] = $dataValueX;
  452. }
  453. }
  454. if (empty($dataValues)) {
  455. return;
  456. }
  457. $dataValuesPlot = [];
  458. // Flatten the plot arrays to a single dimensional array to work with jpgraph
  459. $jMax = count($dataValues[0]);
  460. for ($j = 0; $j < $jMax; ++$j) {
  461. for ($i = 0; $i < $seriesCount; ++$i) {
  462. $dataValuesPlot[] = $dataValues[$i][$j];
  463. }
  464. }
  465. // Set the x-axis labels
  466. $labelCount = count($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount());
  467. if ($labelCount > 0) {
  468. $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
  469. $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount);
  470. $this->graph->xaxis->SetTickLabels($datasetLabels);
  471. }
  472. $seriesPlot = new \StockPlot($dataValuesPlot);
  473. $seriesPlot->SetWidth(20);
  474. $this->graph->Add($seriesPlot);
  475. }
  476. private function renderAreaChart($groupCount, $dimensions = '2d')
  477. {
  478. $this->renderCartesianPlotArea();
  479. for ($i = 0; $i < $groupCount; ++$i) {
  480. $this->renderPlotLine($i, true, false, $dimensions);
  481. }
  482. }
  483. private function renderLineChart($groupCount, $dimensions = '2d')
  484. {
  485. $this->renderCartesianPlotArea();
  486. for ($i = 0; $i < $groupCount; ++$i) {
  487. $this->renderPlotLine($i, false, false, $dimensions);
  488. }
  489. }
  490. private function renderBarChart($groupCount, $dimensions = '2d')
  491. {
  492. $this->renderCartesianPlotArea();
  493. for ($i = 0; $i < $groupCount; ++$i) {
  494. $this->renderPlotBar($i, $dimensions);
  495. }
  496. }
  497. private function renderScatterChart($groupCount)
  498. {
  499. $this->renderCartesianPlotArea('linlin');
  500. for ($i = 0; $i < $groupCount; ++$i) {
  501. $this->renderPlotScatter($i, false);
  502. }
  503. }
  504. private function renderBubbleChart($groupCount)
  505. {
  506. $this->renderCartesianPlotArea('linlin');
  507. for ($i = 0; $i < $groupCount; ++$i) {
  508. $this->renderPlotScatter($i, true);
  509. }
  510. }
  511. private function renderPieChart($groupCount, $dimensions = '2d', $doughnut = false, $multiplePlots = false)
  512. {
  513. $this->renderPiePlotArea();
  514. $iLimit = ($multiplePlots) ? $groupCount : 1;
  515. for ($groupID = 0; $groupID < $iLimit; ++$groupID) {
  516. $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
  517. $exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
  518. if ($groupID == 0) {
  519. $labelCount = count($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount());
  520. if ($labelCount > 0) {
  521. $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
  522. $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount);
  523. }
  524. }
  525. $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
  526. $seriesPlots = [];
  527. // For pie charts, we only display the first series: doughnut charts generally display all series
  528. $jLimit = ($multiplePlots) ? $seriesCount : 1;
  529. // Loop through each data series in turn
  530. for ($j = 0; $j < $jLimit; ++$j) {
  531. $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues();
  532. // Fill in any missing values in the $dataValues array
  533. $testCurrentIndex = 0;
  534. foreach ($dataValues as $k => $dataValue) {
  535. while ($k != $testCurrentIndex) {
  536. $dataValues[$testCurrentIndex] = null;
  537. ++$testCurrentIndex;
  538. }
  539. ++$testCurrentIndex;
  540. }
  541. if ($dimensions == '3d') {
  542. $seriesPlot = new \PiePlot3D($dataValues);
  543. } else {
  544. if ($doughnut) {
  545. $seriesPlot = new \PiePlotC($dataValues);
  546. } else {
  547. $seriesPlot = new \PiePlot($dataValues);
  548. }
  549. }
  550. if ($multiplePlots) {
  551. $seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4));
  552. }
  553. if ($doughnut) {
  554. $seriesPlot->SetMidColor('white');
  555. }
  556. $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
  557. if (count($datasetLabels) > 0) {
  558. $seriesPlot->SetLabels(array_fill(0, count($datasetLabels), ''));
  559. }
  560. if ($dimensions != '3d') {
  561. $seriesPlot->SetGuideLines(false);
  562. }
  563. if ($j == 0) {
  564. if ($exploded) {
  565. $seriesPlot->ExplodeAll();
  566. }
  567. $seriesPlot->SetLegends($datasetLabels);
  568. }
  569. $this->graph->Add($seriesPlot);
  570. }
  571. }
  572. }
  573. private function renderRadarChart($groupCount)
  574. {
  575. $this->renderRadarPlotArea();
  576. for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
  577. $this->renderPlotRadar($groupID);
  578. }
  579. }
  580. private function renderStockChart($groupCount)
  581. {
  582. $this->renderCartesianPlotArea('intint');
  583. for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
  584. $this->renderPlotStock($groupID);
  585. }
  586. }
  587. private function renderContourChart($groupCount, $dimensions)
  588. {
  589. $this->renderCartesianPlotArea('intint');
  590. for ($i = 0; $i < $groupCount; ++$i) {
  591. $this->renderPlotContour($i);
  592. }
  593. }
  594. private function renderCombinationChart($groupCount, $dimensions, $outputDestination)
  595. {
  596. $this->renderCartesianPlotArea();
  597. for ($i = 0; $i < $groupCount; ++$i) {
  598. $dimensions = null;
  599. $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
  600. switch ($chartType) {
  601. case 'area3DChart':
  602. $dimensions = '3d';
  603. // no break
  604. case 'areaChart':
  605. $this->renderPlotLine($i, true, true, $dimensions);
  606. break;
  607. case 'bar3DChart':
  608. $dimensions = '3d';
  609. // no break
  610. case 'barChart':
  611. $this->renderPlotBar($i, $dimensions);
  612. break;
  613. case 'line3DChart':
  614. $dimensions = '3d';
  615. // no break
  616. case 'lineChart':
  617. $this->renderPlotLine($i, false, true, $dimensions);
  618. break;
  619. case 'scatterChart':
  620. $this->renderPlotScatter($i, false);
  621. break;
  622. case 'bubbleChart':
  623. $this->renderPlotScatter($i, true);
  624. break;
  625. default:
  626. $this->graph = null;
  627. return false;
  628. }
  629. }
  630. $this->renderLegend();
  631. $this->graph->Stroke($outputDestination);
  632. return true;
  633. }
  634. public function render($outputDestination)
  635. {
  636. self::$plotColour = 0;
  637. $groupCount = $this->chart->getPlotArea()->getPlotGroupCount();
  638. $dimensions = null;
  639. if ($groupCount == 1) {
  640. $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType();
  641. } else {
  642. $chartTypes = [];
  643. for ($i = 0; $i < $groupCount; ++$i) {
  644. $chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
  645. }
  646. $chartTypes = array_unique($chartTypes);
  647. if (count($chartTypes) == 1) {
  648. $chartType = array_pop($chartTypes);
  649. } elseif (count($chartTypes) == 0) {
  650. echo 'Chart is not yet implemented<br />';
  651. return false;
  652. } else {
  653. return $this->renderCombinationChart($groupCount, $dimensions, $outputDestination);
  654. }
  655. }
  656. switch ($chartType) {
  657. case 'area3DChart':
  658. $dimensions = '3d';
  659. // no break
  660. case 'areaChart':
  661. $this->renderAreaChart($groupCount, $dimensions);
  662. break;
  663. case 'bar3DChart':
  664. $dimensions = '3d';
  665. // no break
  666. case 'barChart':
  667. $this->renderBarChart($groupCount, $dimensions);
  668. break;
  669. case 'line3DChart':
  670. $dimensions = '3d';
  671. // no break
  672. case 'lineChart':
  673. $this->renderLineChart($groupCount, $dimensions);
  674. break;
  675. case 'pie3DChart':
  676. $dimensions = '3d';
  677. // no break
  678. case 'pieChart':
  679. $this->renderPieChart($groupCount, $dimensions, false, false);
  680. break;
  681. case 'doughnut3DChart':
  682. $dimensions = '3d';
  683. // no break
  684. case 'doughnutChart':
  685. $this->renderPieChart($groupCount, $dimensions, true, true);
  686. break;
  687. case 'scatterChart':
  688. $this->renderScatterChart($groupCount);
  689. break;
  690. case 'bubbleChart':
  691. $this->renderBubbleChart($groupCount);
  692. break;
  693. case 'radarChart':
  694. $this->renderRadarChart($groupCount);
  695. break;
  696. case 'surface3DChart':
  697. $dimensions = '3d';
  698. // no break
  699. case 'surfaceChart':
  700. $this->renderContourChart($groupCount, $dimensions);
  701. break;
  702. case 'stockChart':
  703. $this->renderStockChart($groupCount);
  704. break;
  705. default:
  706. echo $chartType . ' is not yet implemented<br />';
  707. return false;
  708. }
  709. $this->renderLegend();
  710. $this->graph->Stroke($outputDestination);
  711. return true;
  712. }
  713. }