JpGraph.php 31 KB

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