Graph.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Backend\Block\Dashboard;
  7. /**
  8. * Adminhtml dashboard google chart block
  9. *
  10. * @author Magento Core Team <core@magentocommerce.com>
  11. */
  12. class Graph extends \Magento\Backend\Block\Dashboard\AbstractDashboard
  13. {
  14. /**
  15. * Api URL
  16. */
  17. const API_URL = 'http://chart.apis.google.com/chart';
  18. /**
  19. * All series
  20. *
  21. * @var array
  22. */
  23. protected $_allSeries = [];
  24. /**
  25. * Axis labels
  26. *
  27. * @var array
  28. */
  29. protected $_axisLabels = [];
  30. /**
  31. * Axis maps
  32. *
  33. * @var array
  34. */
  35. protected $_axisMaps = [];
  36. /**
  37. * Data rows
  38. *
  39. * @var array
  40. */
  41. protected $_dataRows = [];
  42. /**
  43. * Simple encoding chars
  44. *
  45. * @var string
  46. */
  47. protected $_simpleEncoding = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  48. /**
  49. * Extended encoding chars
  50. *
  51. * @var string
  52. */
  53. protected $_extendedEncoding = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.';
  54. /**
  55. * Chart width
  56. *
  57. * @var string
  58. */
  59. protected $_width = '780';
  60. /**
  61. * Chart height
  62. *
  63. * @var string
  64. */
  65. protected $_height = '384';
  66. /**
  67. * Google chart api data encoding
  68. *
  69. * @var string
  70. */
  71. protected $_encoding = 'e';
  72. /**
  73. * Html identifier
  74. *
  75. * @var string
  76. */
  77. protected $_htmlId = '';
  78. /**
  79. * @var string
  80. */
  81. protected $_template = 'Magento_Backend::dashboard/graph.phtml';
  82. /**
  83. * Adminhtml dashboard data
  84. *
  85. * @var \Magento\Backend\Helper\Dashboard\Data
  86. */
  87. protected $_dashboardData = null;
  88. /**
  89. * @param \Magento\Backend\Block\Template\Context $context
  90. * @param \Magento\Reports\Model\ResourceModel\Order\CollectionFactory $collectionFactory
  91. * @param \Magento\Backend\Helper\Dashboard\Data $dashboardData
  92. * @param array $data
  93. */
  94. public function __construct(
  95. \Magento\Backend\Block\Template\Context $context,
  96. \Magento\Reports\Model\ResourceModel\Order\CollectionFactory $collectionFactory,
  97. \Magento\Backend\Helper\Dashboard\Data $dashboardData,
  98. array $data = []
  99. ) {
  100. $this->_dashboardData = $dashboardData;
  101. parent::__construct($context, $collectionFactory, $data);
  102. }
  103. /**
  104. * Get tab template
  105. *
  106. * @return string
  107. */
  108. protected function _getTabTemplate()
  109. {
  110. return 'dashboard/graph.phtml';
  111. }
  112. /**
  113. * Set data rows
  114. *
  115. * @param array $rows
  116. * @return void
  117. */
  118. public function setDataRows($rows)
  119. {
  120. $this->_dataRows = (array)$rows;
  121. }
  122. /**
  123. * Add series
  124. *
  125. * @param string $seriesId
  126. * @param array $options
  127. * @return void
  128. */
  129. public function addSeries($seriesId, array $options)
  130. {
  131. $this->_allSeries[$seriesId] = $options;
  132. }
  133. /**
  134. * Get series
  135. *
  136. * @param string $seriesId
  137. * @return array|false
  138. */
  139. public function getSeries($seriesId)
  140. {
  141. if (isset($this->_allSeries[$seriesId])) {
  142. return $this->_allSeries[$seriesId];
  143. } else {
  144. return false;
  145. }
  146. }
  147. /**
  148. * Get all series
  149. *
  150. * @return array
  151. */
  152. public function getAllSeries()
  153. {
  154. return $this->_allSeries;
  155. }
  156. /**
  157. * Get chart url
  158. *
  159. * @param bool $directUrl
  160. * @return string
  161. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  162. * @SuppressWarnings(PHPMD.NPathComplexity)
  163. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  164. * @SuppressWarnings(PHPMD.UnusedLocalVariable)
  165. */
  166. public function getChartUrl($directUrl = true)
  167. {
  168. $params = [
  169. 'cht' => 'lc',
  170. 'chf' => 'bg,s,ffffff',
  171. 'chco' => 'ef672f',
  172. 'chls' => '7',
  173. 'chxs' => '0,676056,15,0,l,676056|1,676056,15,0,l,676056',
  174. 'chm' => 'h,f2ebde,0,0:1:.1,1,-1',
  175. ];
  176. $this->_allSeries = $this->getRowsData($this->_dataRows);
  177. foreach ($this->_axisMaps as $axis => $attr) {
  178. $this->setAxisLabels($axis, $this->getRowsData($attr, true));
  179. }
  180. $timezoneLocal = $this->_localeDate->getConfigTimezone();
  181. /** @var \DateTime $dateStart */
  182. /** @var \DateTime $dateEnd */
  183. list($dateStart, $dateEnd) = $this->_collectionFactory->create()->getDateRange(
  184. $this->getDataHelper()->getParam('period'),
  185. '',
  186. '',
  187. true
  188. );
  189. $dateStart->setTimezone(new \DateTimeZone($timezoneLocal));
  190. $dateEnd->setTimezone(new \DateTimeZone($timezoneLocal));
  191. if ($this->getDataHelper()->getParam('period') == '24h') {
  192. $dateEnd->modify('-1 hour');
  193. } else {
  194. $dateEnd->setTime(23, 59, 59);
  195. $dateStart->setTime(0, 0, 0);
  196. }
  197. $dates = [];
  198. $datas = [];
  199. while ($dateStart <= $dateEnd) {
  200. switch ($this->getDataHelper()->getParam('period')) {
  201. case '7d':
  202. case '1m':
  203. $d = $dateStart->format('Y-m-d');
  204. $dateStart->modify('+1 day');
  205. break;
  206. case '1y':
  207. case '2y':
  208. $d = $dateStart->format('Y-m');
  209. $dateStart->modify('+1 month');
  210. break;
  211. default:
  212. $d = $dateStart->format('Y-m-d H:00');
  213. $dateStart->modify('+1 hour');
  214. }
  215. foreach ($this->getAllSeries() as $index => $serie) {
  216. if (in_array($d, $this->_axisLabels['x'])) {
  217. $datas[$index][] = (double)array_shift($this->_allSeries[$index]);
  218. } else {
  219. $datas[$index][] = 0;
  220. }
  221. }
  222. $dates[] = $d;
  223. }
  224. /**
  225. * setting skip step
  226. */
  227. if (count($dates) > 8 && count($dates) < 15) {
  228. $c = 1;
  229. } else {
  230. if (count($dates) >= 15) {
  231. $c = 2;
  232. } else {
  233. $c = 0;
  234. }
  235. }
  236. /**
  237. * skipping some x labels for good reading
  238. */
  239. $i = 0;
  240. foreach ($dates as $k => $d) {
  241. if ($i == $c) {
  242. $dates[$k] = $d;
  243. $i = 0;
  244. } else {
  245. $dates[$k] = '';
  246. $i++;
  247. }
  248. }
  249. $this->_axisLabels['x'] = $dates;
  250. $this->_allSeries = $datas;
  251. //Google encoding values
  252. if ($this->_encoding == "s") {
  253. // simple encoding
  254. $params['chd'] = "s:";
  255. $dataDelimiter = "";
  256. $dataSetdelimiter = ",";
  257. $dataMissing = "_";
  258. } else {
  259. // extended encoding
  260. $params['chd'] = "e:";
  261. $dataDelimiter = "";
  262. $dataSetdelimiter = ",";
  263. $dataMissing = "__";
  264. }
  265. // process each string in the array, and find the max length
  266. $localmaxvalue = [0];
  267. $localminvalue = [0];
  268. foreach ($this->getAllSeries() as $index => $serie) {
  269. $localmaxvalue[$index] = max($serie);
  270. $localminvalue[$index] = min($serie);
  271. }
  272. $maxvalue = max($localmaxvalue);
  273. $minvalue = min($localminvalue);
  274. // default values
  275. $yrange = 0;
  276. $yLabels = [];
  277. $miny = 0;
  278. $maxy = 0;
  279. $yorigin = 0;
  280. if ($minvalue >= 0 && $maxvalue >= 0) {
  281. if ($maxvalue > 10) {
  282. $p = pow(10, $this->_getPow($maxvalue));
  283. $maxy = ceil($maxvalue / $p) * $p;
  284. $yLabels = range($miny, $maxy, $p);
  285. } else {
  286. $maxy = ceil($maxvalue + 1);
  287. $yLabels = range($miny, $maxy, 1);
  288. }
  289. $yrange = $maxy;
  290. $yorigin = 0;
  291. }
  292. $chartdata = [];
  293. foreach ($this->getAllSeries() as $index => $serie) {
  294. $thisdataarray = $serie;
  295. if ($this->_encoding == "s") {
  296. // SIMPLE ENCODING
  297. for ($j = 0; $j < sizeof($thisdataarray); $j++) {
  298. $currentvalue = $thisdataarray[$j];
  299. if (is_numeric($currentvalue)) {
  300. $ylocation = round(
  301. (strlen($this->_simpleEncoding) - 1) * ($yorigin + $currentvalue) / $yrange
  302. );
  303. $chartdata[] = substr($this->_simpleEncoding, $ylocation, 1) . $dataDelimiter;
  304. } else {
  305. $chartdata[] = $dataMissing . $dataDelimiter;
  306. }
  307. }
  308. } else {
  309. // EXTENDED ENCODING
  310. for ($j = 0; $j < sizeof($thisdataarray); $j++) {
  311. $currentvalue = $thisdataarray[$j];
  312. if (is_numeric($currentvalue)) {
  313. if ($yrange) {
  314. $ylocation = 4095 * ($yorigin + $currentvalue) / $yrange;
  315. } else {
  316. $ylocation = 0;
  317. }
  318. $firstchar = floor($ylocation / 64);
  319. $secondchar = $ylocation % 64;
  320. $mappedchar = substr(
  321. $this->_extendedEncoding,
  322. $firstchar,
  323. 1
  324. ) . substr(
  325. $this->_extendedEncoding,
  326. $secondchar,
  327. 1
  328. );
  329. $chartdata[] = $mappedchar . $dataDelimiter;
  330. } else {
  331. $chartdata[] = $dataMissing . $dataDelimiter;
  332. }
  333. }
  334. }
  335. $chartdata[] = $dataSetdelimiter;
  336. }
  337. $buffer = implode('', $chartdata);
  338. $buffer = rtrim($buffer, $dataSetdelimiter);
  339. $buffer = rtrim($buffer, $dataDelimiter);
  340. $buffer = str_replace($dataDelimiter . $dataSetdelimiter, $dataSetdelimiter, $buffer);
  341. $params['chd'] .= $buffer;
  342. $valueBuffer = [];
  343. if (sizeof($this->_axisLabels) > 0) {
  344. $params['chxt'] = implode(',', array_keys($this->_axisLabels));
  345. $indexid = 0;
  346. foreach ($this->_axisLabels as $idx => $labels) {
  347. if ($idx == 'x') {
  348. /**
  349. * Format date
  350. */
  351. foreach ($this->_axisLabels[$idx] as $_index => $_label) {
  352. if ($_label != '') {
  353. $period = new \DateTime($_label, new \DateTimeZone($timezoneLocal));
  354. switch ($this->getDataHelper()->getParam('period')) {
  355. case '24h':
  356. $this->_axisLabels[$idx][$_index] = $this->_localeDate->formatDateTime(
  357. $period->setTime($period->format('H'), 0, 0),
  358. \IntlDateFormatter::NONE,
  359. \IntlDateFormatter::SHORT
  360. );
  361. break;
  362. case '7d':
  363. case '1m':
  364. $this->_axisLabels[$idx][$_index] = $this->_localeDate->formatDateTime(
  365. $period,
  366. \IntlDateFormatter::SHORT,
  367. \IntlDateFormatter::NONE
  368. );
  369. break;
  370. case '1y':
  371. case '2y':
  372. $this->_axisLabels[$idx][$_index] = date('m/Y', strtotime($_label));
  373. break;
  374. }
  375. } else {
  376. $this->_axisLabels[$idx][$_index] = '';
  377. }
  378. }
  379. $tmpstring = implode('|', $this->_axisLabels[$idx]);
  380. $valueBuffer[] = $indexid . ":|" . $tmpstring;
  381. } elseif ($idx == 'y') {
  382. $valueBuffer[] = $indexid . ":|" . implode('|', $yLabels);
  383. }
  384. $indexid++;
  385. }
  386. $params['chxl'] = implode('|', $valueBuffer);
  387. }
  388. // chart size
  389. $params['chs'] = $this->getWidth() . 'x' . $this->getHeight();
  390. // return the encoded data
  391. if ($directUrl) {
  392. $p = [];
  393. foreach ($params as $name => $value) {
  394. $p[] = $name . '=' . urlencode($value);
  395. }
  396. return self::API_URL . '?' . implode('&', $p);
  397. } else {
  398. $gaData = urlencode(base64_encode(json_encode($params)));
  399. $gaHash = $this->_dashboardData->getChartDataHash($gaData);
  400. $params = ['ga' => $gaData, 'h' => $gaHash];
  401. return $this->getUrl('adminhtml/*/tunnel', ['_query' => $params]);
  402. }
  403. }
  404. /**
  405. * Get rows data
  406. *
  407. * @param array $attributes
  408. * @param bool $single
  409. * @return array
  410. */
  411. protected function getRowsData($attributes, $single = false)
  412. {
  413. $items = $this->getCollection()->getItems();
  414. $options = [];
  415. foreach ($items as $item) {
  416. if ($single) {
  417. $options[] = max(0, $item->getData($attributes));
  418. } else {
  419. foreach ((array)$attributes as $attr) {
  420. $options[$attr][] = max(0, $item->getData($attr));
  421. }
  422. }
  423. }
  424. return $options;
  425. }
  426. /**
  427. * Set axis labels
  428. *
  429. * @param string $axis
  430. * @param array $labels
  431. * @return void
  432. */
  433. public function setAxisLabels($axis, $labels)
  434. {
  435. $this->_axisLabels[$axis] = $labels;
  436. }
  437. /**
  438. * Set html id
  439. *
  440. * @param string $htmlId
  441. * @return void
  442. */
  443. public function setHtmlId($htmlId)
  444. {
  445. $this->_htmlId = $htmlId;
  446. }
  447. /**
  448. * Get html id
  449. *
  450. * @return string
  451. */
  452. public function getHtmlId()
  453. {
  454. return $this->_htmlId;
  455. }
  456. /**
  457. * Return pow
  458. *
  459. * @param int $number
  460. * @return int
  461. */
  462. protected function _getPow($number)
  463. {
  464. $pow = 0;
  465. while ($number >= 10) {
  466. $number = $number / 10;
  467. $pow++;
  468. }
  469. return $pow;
  470. }
  471. /**
  472. * Return chart width
  473. *
  474. * @return string
  475. */
  476. protected function getWidth()
  477. {
  478. return $this->_width;
  479. }
  480. /**
  481. * Return chart height
  482. *
  483. * @return string
  484. */
  485. protected function getHeight()
  486. {
  487. return $this->_height;
  488. }
  489. /**
  490. * @param \Magento\Backend\Helper\Dashboard\AbstractDashboard $dataHelper
  491. * @return void
  492. */
  493. public function setDataHelper(\Magento\Backend\Helper\Dashboard\AbstractDashboard $dataHelper)
  494. {
  495. $this->_dataHelper = $dataHelper;
  496. }
  497. /**
  498. * Prepare chart data
  499. *
  500. * @return void
  501. */
  502. protected function _prepareData()
  503. {
  504. if ($this->_dataHelper !== null) {
  505. $availablePeriods = array_keys($this->_dashboardData->getDatePeriods());
  506. $period = $this->getRequest()->getParam('period');
  507. $this->getDataHelper()->setParam(
  508. 'period',
  509. $period && in_array($period, $availablePeriods) ? $period : '24h'
  510. );
  511. }
  512. }
  513. }