api.chartmancer.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. <?php
  2. /**
  3. * The ChartMancer class automates the generation of graphs based on data provided in an array.
  4. * It utilizes the GD library to create visually appealing charts, allowing developers to easily
  5. * visualize and display data trends. With ChartMancer, you can customize various aspects of the
  6. * graph, such as colors, labels, and other chart properties, providing a versatile tool
  7. * for data representation in PHP applications.
  8. *
  9. * @package ChartMancer
  10. * @author Rostyslav Haitkulov <info@ubilling.net.ua>
  11. * @see https://github.com/nightflyza/ChartMancer
  12. * @license MIT
  13. */
  14. class ChartMancer {
  15. /**
  16. * Custom chart chart title
  17. *
  18. * @var string
  19. */
  20. protected $chartTitle = '';
  21. /**
  22. * Custom colors palette modifier
  23. *
  24. * @var string
  25. */
  26. protected $palette = 'O-M-G';
  27. /**
  28. * Image dimensions, width in px
  29. *
  30. * @var int
  31. */
  32. protected $imageWidth = 1540;
  33. /**
  34. * Image dimensions, height in px
  35. *
  36. * @var int
  37. */
  38. protected $imageHeight = 400;
  39. /**
  40. * Grid dimensions and placement within chart, top side, px
  41. *
  42. * @var int
  43. */
  44. protected $gridTop = 40;
  45. /**
  46. * Grid dimensions and placement within chart, left side, px
  47. * @var int
  48. */
  49. protected $gridLeft = 50;
  50. /**
  51. * Grid line width in px
  52. *
  53. * @var int
  54. */
  55. protected $lineWidth = 1;
  56. /**
  57. * Bar default width in px
  58. *
  59. * @var int
  60. */
  61. protected $barWidth = 5;
  62. /**
  63. * Margin between label and axis in px
  64. *
  65. * @var int
  66. */
  67. protected $labelMargin = 8;
  68. /**
  69. * Contains optional chart legend
  70. *
  71. * @var array
  72. */
  73. protected $chartLegend = array();
  74. /**
  75. * Contains default base color RGB decimal values
  76. *
  77. * @var array
  78. */
  79. protected $baseColor = array(
  80. 'r' => 47,
  81. 'g' => 133,
  82. 'b' => 217
  83. );
  84. /**
  85. * Contains default background color RGB decimal values. White by default.
  86. *
  87. * @var array
  88. */
  89. protected $backGroundColor = array(
  90. 'r' => 255,
  91. 'g' => 255,
  92. 'b' => 255
  93. );
  94. /**
  95. * Contains default grid color RGB decimal values.
  96. *
  97. * @var array
  98. */
  99. protected $gridColor = array(
  100. 'r' => 212,
  101. 'g' => 212,
  102. 'b' => 212
  103. );
  104. /**
  105. * Contains default axis color RGB decimal values.
  106. *
  107. * @var array
  108. */
  109. protected $axisColor = array(
  110. 'r' => 85,
  111. 'g' => 85,
  112. 'b' => 85
  113. );
  114. /**
  115. * Contains default text color RGB decimal values.
  116. *
  117. * @var array
  118. */
  119. protected $textColor = array(
  120. 'r' => 85,
  121. 'g' => 85,
  122. 'b' => 85
  123. );
  124. /**
  125. * Dynamic palette overrides
  126. *
  127. * @var array
  128. */
  129. protected $overrideColors = array();
  130. /**
  131. * Transparent background transparency flag
  132. *
  133. * @var bool
  134. */
  135. protected $backgroundTransparent = false;
  136. /**
  137. * TTF font path
  138. *
  139. * @var string
  140. */
  141. protected $font = 'skins/OpenSans-Regular.ttf';
  142. /**
  143. * Font size in pt.
  144. *
  145. * @var int
  146. */
  147. protected $fontSize = 10;
  148. /**
  149. * Maximum length of x-Axis label text
  150. *
  151. * @var int
  152. */
  153. protected $xLabelLen = 5;
  154. /**
  155. * Contains X-axis labels count
  156. *
  157. * @var int
  158. */
  159. protected $xAxisLabelCount = 20;
  160. /**
  161. * Contains cutted string suffix
  162. *
  163. * @var string
  164. */
  165. protected $cutSuffix = '...';
  166. /**
  167. * Render maximum dataset value on chart?
  168. *
  169. * @var bool
  170. */
  171. protected $displayPeakValue = false;
  172. /**
  173. * Contains custom Y-axis label
  174. *
  175. * @var string
  176. */
  177. protected $yAxisName = '';
  178. /**
  179. * Rendering debug flag
  180. *
  181. * @var bool
  182. */
  183. protected $debug = false;
  184. /**
  185. * Bar automatic width modifier depend on data set size
  186. *
  187. * @var bool
  188. */
  189. protected $barAutoWidth = true;
  190. /**
  191. * Contains y-max ratio offset from max dataset value to upper grid limit
  192. *
  193. * @var float
  194. */
  195. protected $yMaxValueRatio = 0.1;
  196. /**
  197. * X-Axis text labels rendering flag
  198. *
  199. * @var bool
  200. */
  201. protected $xLabelRender = true;
  202. /**
  203. * Y-Axis text labels rendering flag
  204. *
  205. * @var bool
  206. */
  207. protected $yLabelRender = true;
  208. /**
  209. * Prevents first (totals) data column from marking as already drawn
  210. *
  211. * @var bool
  212. */
  213. protected $drawFirstColumnAlways = true;
  214. public function __construct() {
  215. //what are you expecting to see here?
  216. }
  217. /**
  218. * Returns a decimal RGB color based on text string as array(r/g/b)
  219. *
  220. * @param $text Some string of text
  221. * @param $palette palette string
  222. *
  223. * @return array
  224. */
  225. protected function getColorFromText($text) {
  226. $result = array();
  227. $hash = md5($this->palette . $text);
  228. $result['r'] = hexdec(substr($hash, 0, 2));
  229. $result['g'] = hexdec(substr($hash, 2, 2));
  230. $result['b'] = hexdec(substr($hash, 4, 2));
  231. return ($result);
  232. }
  233. /**
  234. * Checks is array contains valid RGB values or not?
  235. *
  236. * @return bool
  237. */
  238. protected function checkColor($colorArray) {
  239. $result = false;
  240. if (isset($colorArray['r']) and isset($colorArray['g']) and isset($colorArray['b'])) {
  241. if (is_numeric($colorArray['r']) and is_numeric($colorArray['g']) and is_numeric($colorArray['b'])) {
  242. $result = true;
  243. }
  244. }
  245. return ($result);
  246. }
  247. /**
  248. * Set bar automatic width modifier depend on data set size
  249. *
  250. * @param bool $barAutoWidth Bar automatic width modifier depend on data set size
  251. *
  252. * @return void
  253. */
  254. public function setBarAutoWidth($barAutoWidth) {
  255. $this->barAutoWidth = $barAutoWidth;
  256. }
  257. /**
  258. * Set custom colors palette modifier
  259. *
  260. * @param string $palette Custom colors palette modifier
  261. *
  262. * @return void
  263. */
  264. public function setPalette($palette) {
  265. $this->palette = $palette;
  266. }
  267. /**
  268. * Set image dimensions, width in px
  269. *
  270. * @param int $imageWidth Image dimensions, width in px
  271. *
  272. * @return void
  273. */
  274. public function setImageWidth($imageWidth) {
  275. $this->imageWidth = $imageWidth;
  276. }
  277. /**
  278. * Set image dimensions, height in px
  279. *
  280. * @param int $imageHeight Image dimensions, height in px
  281. *
  282. * @return void
  283. */
  284. public function setImageHeight($imageHeight) {
  285. $this->imageHeight = $imageHeight;
  286. }
  287. /**
  288. * Set bar default width in px
  289. *
  290. * @param int $barWidth Bar default width in px
  291. *
  292. * @return void
  293. */
  294. public function setBarWidth($barWidth) {
  295. $this->barWidth = $barWidth;
  296. }
  297. /**
  298. * Set default base color RGB decimal values
  299. *
  300. * @param array $baseColor Contains default base color RGB decimal values
  301. *
  302. * @return void
  303. */
  304. public function setBaseColor($baseColor) {
  305. if ($this->checkColor($baseColor)) {
  306. $this->baseColor = $baseColor;
  307. }
  308. }
  309. /**
  310. * Set default background color RGB decimal values. White by default.
  311. *
  312. * @param array $backGroundColor Contains default background color RGB decimal values.
  313. *
  314. * @return void
  315. */
  316. public function setBackGroundColor($backGroundColor) {
  317. if ($this->checkColor($backGroundColor)) {
  318. $this->backGroundColor = $backGroundColor;
  319. }
  320. }
  321. /**
  322. * Set default grid color RGB decimal values.
  323. *
  324. * @param array $gridColor Contains default grid color RGB decimal values.
  325. *
  326. * @return void
  327. */
  328. public function setGridColor($gridColor) {
  329. if ($this->checkColor($gridColor)) {
  330. $this->gridColor = $gridColor;
  331. }
  332. }
  333. /**
  334. * Set default axis color RGB decimal values.
  335. *
  336. * @param array $axisColor Contains default axis color RGB decimal values.
  337. *
  338. * @return void
  339. */
  340. public function setAxisColor($axisColor) {
  341. if ($this->checkColor($axisColor)) {
  342. $this->axisColor = $axisColor;
  343. }
  344. }
  345. /**
  346. * Sets X-axis labels length in bytes
  347. *
  348. * @param int $len
  349. *
  350. * @return void
  351. */
  352. public function setXLabelLen($len = 5) {
  353. $this->xLabelLen = $len;
  354. }
  355. /**
  356. * Sets X-axis labels count
  357. *
  358. * @param int $count
  359. *
  360. * @return void
  361. */
  362. public function setXLabelsCount($count = 20) {
  363. $this->xAxisLabelCount = $count;
  364. }
  365. /**
  366. * Sets cutted labels suffix value
  367. *
  368. * @param string $value
  369. *
  370. * @return void
  371. */
  372. public function setCutSuffix($value = '...') {
  373. $this->cutSuffix = $value;
  374. }
  375. /**
  376. * Set transparent background transparency flag
  377. *
  378. * @param bool $backgroundTransparent Transparent background transparency flag
  379. *
  380. * @return void
  381. */
  382. public function setBackgroundTransparent($backgroundTransparent) {
  383. $this->backgroundTransparent = $backgroundTransparent;
  384. }
  385. /**
  386. * Set TTF font path
  387. *
  388. * @param string $font TTF font path
  389. *
  390. * @return void
  391. */
  392. public function setFont($font) {
  393. $this->font = $font;
  394. }
  395. /**
  396. * Set font size in pt.
  397. *
  398. * @param int $fontSize Font size in pt.
  399. *
  400. * @return void
  401. */
  402. public function setFontSize($fontSize) {
  403. $this->fontSize = $fontSize;
  404. }
  405. /**
  406. * Sets rendering debug flag
  407. *
  408. * @param bool $debug Rendering debug flag
  409. *
  410. * @return void
  411. */
  412. public function setDebug($debug) {
  413. $this->debug = $debug;
  414. }
  415. /**
  416. * Set default text color RGB decimal values.
  417. *
  418. * @param array $textColor default text color RGB decimal values.
  419. *
  420. * @return self
  421. */
  422. public function setTextColor(array $textColor) {
  423. if ($this->checkColor($textColor)) {
  424. $this->textColor = $textColor;
  425. }
  426. }
  427. /**
  428. * Sets chart chartTitle
  429. *
  430. * @param string $chartTitle Chart chartTitle
  431. *
  432. * @return void
  433. */
  434. public function setChartTitle($chartTitle) {
  435. $this->chartTitle = $chartTitle;
  436. }
  437. /**
  438. * Sets maximum dataset value rendering on chart flag
  439. *
  440. * @return void
  441. */
  442. public function setDisplayPeakValue($displayPeakValue) {
  443. $this->displayPeakValue = $displayPeakValue;
  444. }
  445. /**
  446. * Sets optional chart legend
  447. *
  448. * @param array $chartLegend
  449. *
  450. * @return void
  451. */
  452. public function setChartLegend($chartLegend) {
  453. if (is_array($chartLegend)) {
  454. $this->chartLegend = $chartLegend;
  455. }
  456. }
  457. /**
  458. * Sets custom Y-axis name
  459. *
  460. * @param string $yAxisName
  461. *
  462. * @return void
  463. */
  464. public function setChartYaxisName($yAxisName) {
  465. $this->yAxisName = $yAxisName;
  466. }
  467. /**
  468. * Sets custom colors overrides as array of RGB decimal values.
  469. *
  470. * @return void
  471. */
  472. public function setOverrideColors($customColors) {
  473. $this->overrideColors = $customColors;
  474. }
  475. /**
  476. * Sets the maximum value ratio for the y-axis.
  477. *
  478. * This method allows you to set the maximum value ratio for the y-axis in the chart.
  479. * The ratio determines the maximum value displayed on the y-axis as a additional percentage of the actual maximum dataset value.
  480. *
  481. * @param float $ratio The maximum value ratio for the y-axis like 0.1 for 10% or 0.2 for 20%.
  482. *
  483. * @return void
  484. */
  485. public function setYMaxValueRatio($ratio) {
  486. $this->yMaxValueRatio = $ratio;
  487. }
  488. /**
  489. * Sets the state of drawing the explict first (totals) column flag.
  490. *
  491. * @param bool $state The state to set for drawing the first column.
  492. * @return void
  493. */
  494. public function setDrawFirstColumn($state) {
  495. $this->drawFirstColumnAlways = $state;
  496. }
  497. /**
  498. * Sets X-Axis text labels rendering flag
  499. *
  500. * @param bool $state X-Axis text labels rendering state
  501. *
  502. * @return void
  503. */
  504. public function setXLabelRender($state) {
  505. $this->xLabelRender = $state;
  506. }
  507. /**
  508. * Sets Y-Axis text labels rendering flag
  509. *
  510. * @param bool $state Y-Axis text labels rendering state
  511. *
  512. * @return void
  513. */
  514. public function setYLabelRender($state) {
  515. $this->yLabelRender = $state;
  516. }
  517. /**
  518. * Renders chart as PNG image into browser or into specified file
  519. *
  520. * @param array $data chart dataset
  521. * @param string $filename filename to export chart. May be empty for rendering to browser
  522. * may contain name of .png file to save as file on FS, or be like
  523. * base64 or base64html to return chart as base64 encoded string
  524. *
  525. * @return bool|string
  526. */
  527. public function renderChart($data, $fileName = '') {
  528. if ($this->debug) {
  529. //chart generation start timing
  530. $starttime = explode(' ', microtime());
  531. $starttime = $starttime[1] + $starttime[0];
  532. }
  533. $dataMax = 0;
  534. $nestedData = false;
  535. $nestedDepth = 0;
  536. $dataSize = sizeof($data);
  537. $dataSize = ($dataSize == 0) ? 1 : $dataSize;
  538. $result = false;
  539. $xAxisLabelCount = $this->xAxisLabelCount;
  540. if ($dataSize < 10) {
  541. $xAxisLabelCount = 10;
  542. }
  543. // Avoid non array input data usage
  544. if (!is_array($data)) {
  545. $data = array();
  546. }
  547. // Basic data preprocessing
  548. if (!empty($data)) {
  549. foreach ($data as $key => $value) {
  550. if (is_array($value)) {
  551. $nestedData = true;
  552. $nestedDepth = sizeof($value);
  553. foreach ($value as $io => $subVal) {
  554. if ($subVal > $dataMax) {
  555. $dataMax = $subVal;
  556. }
  557. }
  558. } else {
  559. if ($value > $dataMax) {
  560. $dataMax = $value;
  561. }
  562. }
  563. }
  564. }
  565. // Calculating grid dimensions and placement within image
  566. $gridBottom = $this->imageHeight - $this->gridTop;
  567. $gridRight = $this->imageWidth - $this->gridLeft;
  568. $gridHeight = $gridBottom - $this->gridTop;
  569. $gridWidth = $gridRight - $this->gridLeft;
  570. // Max value on y-axis
  571. $yMaxValue = 200;
  572. // Setting yMaxValue depend of data values
  573. if ($dataMax) {
  574. $yMaxValue = round($dataMax + ($dataMax * $this->yMaxValueRatio));
  575. $yMaxValue = ($yMaxValue != 0) ? $yMaxValue : 2; //preventing division by zero
  576. }
  577. // Distance between grid lines on y-axis
  578. $yLabelSpan = 20;
  579. if ($dataMax) {
  580. if ($dataMax <= 20) {
  581. $yLabelSpan = 1;
  582. } else {
  583. if ($dataMax <= 100) {
  584. $yLabelSpan = 5;
  585. } else {
  586. $yLabelSpan = 10;
  587. }
  588. }
  589. if ($dataMax >= 200) {
  590. $yLabelSpan = round($dataMax / 10);
  591. }
  592. }
  593. // Bar width based on data set size?
  594. if ($this->barAutoWidth) {
  595. if ($dataSize <= 50) {
  596. $this->barWidth = round(($this->imageWidth - $this->gridLeft) / ($dataSize * 1.2));
  597. }
  598. }
  599. // Init image
  600. $chart = imagecreate($this->imageWidth, $this->imageHeight);
  601. // Chart backgroun color setup
  602. if ($this->backgroundTransparent) {
  603. imagealphablending($chart, false);
  604. $backgroundColor = imagecolorallocatealpha($chart, 255, 255, 255, 127);
  605. imagesavealpha($chart, true);
  606. } else {
  607. $backgroundColor = imagecolorallocate($chart, $this->backGroundColor['r'], $this->backGroundColor['g'], $this->backGroundColor['b']);
  608. }
  609. // Chart base, axis, labels and grid colors setup
  610. $baseColor = imagecolorallocate($chart, $this->baseColor['r'], $this->baseColor['g'], $this->baseColor['b']);
  611. $axisColor = imagecolorallocate($chart, $this->axisColor['r'], $this->axisColor['g'], $this->axisColor['b']);
  612. $labelColor = imagecolorallocate($chart, $this->textColor['r'], $this->textColor['g'], $this->textColor['b']);
  613. $gridColor = imagecolorallocate($chart, $this->gridColor['r'], $this->gridColor['g'], $this->gridColor['b']);
  614. $customColors = array();
  615. $customColors[0] = $baseColor;
  616. // Nested colors palette generation here
  617. if ($nestedData) {
  618. for ($i = 1; $i <= $nestedDepth; $i++) {
  619. if (isset($this->overrideColors[$i])) {
  620. //use color override
  621. $randomColor = $this->overrideColors[$i];
  622. } else {
  623. //or generating new
  624. $randomColor = $this->getColorFromText($i);
  625. }
  626. $customColors[$i] = imagecolorallocate($chart, $randomColor['r'], $randomColor['g'], $randomColor['b']);
  627. }
  628. }
  629. imagefill($chart, 0, 0, $backgroundColor);
  630. imagesetthickness($chart, $this->lineWidth);
  631. /*
  632. * Print grid lines bottom up
  633. */
  634. for ($i = 0; $i <= $yMaxValue; $i += $yLabelSpan) {
  635. $y = $gridBottom - $i * $gridHeight / $yMaxValue;
  636. // Draw the line
  637. imageline($chart, $this->gridLeft, (int) $y, $gridRight, (int) $y, $gridColor);
  638. // Draw right aligned label
  639. $labelBox = imagettfbbox($this->fontSize, 0, $this->font, strval($i));
  640. $labelWidth = $labelBox[4] - $labelBox[0];
  641. //drawing Y axis labels
  642. if ($this->yLabelRender) {
  643. $labelX = $this->gridLeft - $labelWidth - $this->labelMargin;
  644. $labelY = $y + $this->fontSize / 2;
  645. $labelX = (int) $labelX;
  646. $labelY = (int) $labelY;
  647. imagettftext($chart, $this->fontSize, 0, $labelX, $labelY, $labelColor, $this->font, strval($i));
  648. }
  649. }
  650. /*
  651. * Draw x- and y-axis
  652. */
  653. imageline($chart, $this->gridLeft, $this->gridTop, $this->gridLeft, $gridBottom, $axisColor);
  654. imageline($chart, $this->gridLeft, $gridBottom, $gridRight, $gridBottom, $axisColor);
  655. /*
  656. * Draw the bars with labels
  657. */
  658. $barSpacing = $gridWidth / $dataSize;
  659. //that 4px avoids round overflow issues with grid on large datasets
  660. $itemX = $this->gridLeft + $barSpacing / 2 + 4;
  661. $index = 0;
  662. //invisible bars control
  663. $renderedBars = array();
  664. $drawCalls = 0;
  665. $drawSkip = 0;
  666. foreach ($data as $key => $value) {
  667. /**
  668. * Draw the bars
  669. */
  670. if (is_array($value)) {
  671. //nested data rendering here
  672. $i = sizeof($value) - 1;
  673. $zBuffer = array();
  674. foreach (array_reverse($value, true) as $io => $subVal) {
  675. $x1 = $itemX - $this->barWidth / 2;
  676. $y1 = $gridBottom - $subVal / $yMaxValue * $gridHeight;
  677. $x2 = $itemX + $this->barWidth / 2;
  678. $y2 = $gridBottom - 1;
  679. $x1 = (int) $x1;
  680. $y1 = (int) $y1;
  681. $x2 = (int) $x2;
  682. $y2 = (int) $y2;
  683. if (!isset($renderedBars[$x1 . '|' . $y1 . '|' . $x2 . '|' . $y2])) {
  684. @$rValue = (isset($zBuffer[$subVal])) ? $subVal . '_' : $subVal;
  685. @$zBuffer[$rValue] = array(
  686. 'value' => $subVal,
  687. 'x1' => $x1,
  688. 'y1' => $y1,
  689. 'x2' => $x2,
  690. 'y2' => $y2,
  691. 'colorIdx' => $i
  692. );
  693. //thats prevents overdraw folloving values with first (totals) column
  694. if ($this->drawFirstColumnAlways) {
  695. if ($i != 0) {
  696. $renderedBars[$x1 . '|' . $y1 . '|' . $x2 . '|' . $y2] = '1';
  697. }
  698. } else {
  699. //just set bar as rendered in some area
  700. $renderedBars[$x1 . '|' . $y1 . '|' . $x2 . '|' . $y2] = '1';
  701. }
  702. } else {
  703. $drawSkip++;
  704. }
  705. $i--; //color index changes anyway
  706. }
  707. if (!empty($zBuffer)) {
  708. krsort($zBuffer);
  709. foreach ($zBuffer as $subValue => $rParams) {
  710. if ($rParams['value'] > 0) {
  711. imagefilledrectangle($chart, $rParams['x1'], $rParams['y1'], $rParams['x2'], $rParams['y2'], $customColors[$rParams['colorIdx']]);
  712. $drawCalls++;
  713. }
  714. }
  715. }
  716. } else {
  717. // raw key=>val dataset
  718. $x1 = $itemX - $this->barWidth / 2;
  719. $y1 = $gridBottom - $value / $yMaxValue * $gridHeight;
  720. $x2 = $itemX + $this->barWidth / 2;
  721. $y2 = $gridBottom - 1;
  722. //explict conversion to avoid implict precision warnings
  723. $x1 = (int) $x1;
  724. $y1 = (int) $y1;
  725. $x2 = (int) $x2;
  726. $y2 = (int) $y2;
  727. if ($value > 0) {
  728. if (!isset($renderedBars[$x1 . '|' . $y1 . '|' . $x2 . '|' . $y2])) {
  729. imagefilledrectangle($chart, $x1, $y1, $x2, $y2, $customColors[0]);
  730. $renderedBars[$x1 . '|' . $y1 . '|' . $x2 . '|' . $y2] = '1';
  731. $drawCalls++;
  732. } else {
  733. $drawSkip++;
  734. }
  735. }
  736. }
  737. $index++;
  738. if ($this->xLabelRender) {
  739. // Skipping some labels display?
  740. if ($dataSize > 10) {
  741. $labelIterator = (int) ($dataSize / $xAxisLabelCount);
  742. $labelIterator = ($labelIterator == 0) ? 1 : $labelIterator; //prevents mod by zero
  743. if (($index) % $labelIterator == 0) {
  744. // Draw the label if its renderable
  745. $labelBox = imagettfbbox($this->fontSize, 0, $this->font, $key);
  746. $labelWidth = $labelBox[4] - $labelBox[0];
  747. $labelX = $itemX - $labelWidth / 2;
  748. $labelY = $gridBottom + $this->labelMargin + $this->fontSize;
  749. $labelX = (int) $labelX;
  750. $labelY = (int) $labelY;
  751. $labelText = (((mb_strlen($key, 'UTF-8') > $this->xLabelLen))) ? mb_substr($key, 0, $this->xLabelLen, 'utf-8') . $this->cutSuffix : $key;
  752. imagettftext($chart, $this->fontSize, 0, $labelX, $labelY, $labelColor, $this->font, $labelText);
  753. }
  754. } else {
  755. // or just draw each column label
  756. $labelBox = imagettfbbox($this->fontSize, 0, $this->font, $key);
  757. $labelWidth = $labelBox[4] - $labelBox[0];
  758. $labelX = $itemX - $labelWidth / 2;
  759. $labelY = $gridBottom + $this->labelMargin + $this->fontSize;
  760. $labelX = (int) $labelX;
  761. $labelY = (int) $labelY;
  762. $labelText = (((mb_strlen($key, 'UTF-8') > $this->xLabelLen))) ? mb_substr($key, 0, $this->xLabelLen, 'utf-8') . $this->cutSuffix : $key;
  763. imagettftext($chart, $this->fontSize, 0, $labelX, $labelY, $labelColor, $this->font, $labelText);
  764. }
  765. }
  766. $itemX += $barSpacing;
  767. }
  768. // Optional chart chartTitle?
  769. if ($this->chartTitle) {
  770. $titleX = ($this->imageWidth - $this->gridLeft) / 2.3;
  771. imagettftext($chart, $this->fontSize + 8, 0, (int) $titleX, 24, $labelColor, $this->font, $this->chartTitle);
  772. }
  773. // Rendering custom Y-axis label
  774. if ($this->yAxisName) {
  775. $yAxisX = $this->gridLeft - 40;
  776. $yAxisY = (int) $this->gridTop - 10;
  777. imagettftext($chart, $this->fontSize, 0, $yAxisX, $yAxisY, $labelColor, $this->font, $this->yAxisName);
  778. }
  779. // Rendering of data set peak value?
  780. if ($this->displayPeakValue) {
  781. $peakX = (int) ($this->imageWidth - $this->gridLeft) - 150;
  782. $peakY = (int) $this->imageHeight - ($this->fontSize * 0.5);
  783. $peakLabel = ($this->yAxisName) ? round($dataMax, 3) . ' ' . $this->yAxisName : round($dataMax, 3);
  784. imagettftext($chart, $this->fontSize, 0, $peakX, $peakY, $labelColor, $this->font, 'Max: ' . $peakLabel);
  785. }
  786. // Rendering chart legend
  787. if (!empty($this->chartLegend)) {
  788. $lWidth = 20;
  789. foreach ($customColors as $colorIndex => $customColor) {
  790. if (isset($this->chartLegend[$colorIndex])) {
  791. $rawLabel = $this->chartLegend[$colorIndex];
  792. $legendText = (((mb_strlen($rawLabel, 'UTF-8') > $this->xLabelLen + 3))) ? mb_substr($rawLabel, 0, $this->xLabelLen + 3, 'utf-8') . '...' : $rawLabel;
  793. $offset = $colorIndex * 10;
  794. $y1 = $this->imageHeight - 5;
  795. $y2 = $this->imageHeight - 20;
  796. $x1 = $offset * 10 + $lWidth;
  797. $x2 = $x1 + $lWidth;
  798. $x1 = (int) $x1;
  799. $y1 = (int) $y1;
  800. $x2 = (int) $x2;
  801. $y2 = (int) $y2;
  802. $labelX = $x2 + 5;
  803. imagefilledrectangle($chart, $x1, $y1, $x2, $y2, $customColor);
  804. imagettftext($chart, $this->fontSize, 0, $labelX, $y1 - 2, $labelColor, $this->font, $legendText);
  805. }
  806. }
  807. }
  808. if ($this->debug) {
  809. //chart generation end timing
  810. $mtime = explode(' ', microtime());
  811. $totaltime = $mtime[0] + $mtime[1] - $starttime;
  812. $debugX = $this->imageWidth - 150;
  813. $totalBars = $drawCalls + $drawSkip;
  814. $totalBars = ($totalBars != 0) ? $totalBars : 1;
  815. $skipPercent = round((($drawCalls / $totalBars) * 100), 2);
  816. imagettftext($chart, 8, 0, $debugX, 10, $labelColor, $this->font, 'DS: ' . $dataSize . ' items');
  817. imagettftext($chart, 8, 0, $debugX, 22, $labelColor, $this->font, 'DC: ' . $drawCalls . ' bars (' . $skipPercent . '%)');
  818. imagettftext($chart, 8, 0, $debugX, 34, $labelColor, $this->font, 'GT: ' . round($totaltime, 5) . ' sec.');
  819. }
  820. if (empty($fileName)) {
  821. //browser output
  822. header('Content-Type: image/png');
  823. $result = imagepng($chart);
  824. imagedestroy($chart);
  825. die();
  826. } else {
  827. if (strpos($fileName, 'base64') !== false) {
  828. //encode image as base64 data
  829. $htmlOutput = (strpos($fileName, 'base64html') !== false) ? true : false;
  830. $result = $this->getChartBase($chart, $htmlOutput);
  831. } else {
  832. //just save as PNG file
  833. $result = imagepng($chart, $fileName);
  834. imagedestroy($chart);
  835. }
  836. }
  837. return ($result);
  838. }
  839. /**
  840. * Returns current chart as base64 encoded text
  841. *
  842. * @param GdImage $image chart image instance to export
  843. * @param bool $htmlData data ready to embed as img src HTML base64 data (data URI scheme)
  844. *
  845. * @return void
  846. */
  847. public function getChartBase($image, $htmlData = false) {
  848. $result = '';
  849. $type = 'png';
  850. $quality = -1;
  851. if ($image) {
  852. ob_start();
  853. imagesavealpha($image, true);
  854. $result = imagepng($image, null, $quality);
  855. imagedestroy($image);
  856. $imageBody = ob_get_contents();
  857. ob_end_clean();
  858. if (!empty($imageBody)) {
  859. $result = base64_encode($imageBody);
  860. }
  861. //optional html embed data
  862. if ($htmlData) {
  863. $result = 'data:image/' . $type . ';charset=utf-8;base64,' . $result;
  864. }
  865. } else {
  866. throw new Exception('EX_VOID_CHART');
  867. }
  868. return ($result);
  869. }
  870. }