Chart.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. <?php
  2. namespace Dcat\Admin\Widgets\Chart;
  3. use Dcat\Admin\Admin;
  4. use Dcat\Admin\Widgets\AjaxRequestBuilder;
  5. use Dcat\Admin\Widgets\Color;
  6. use Dcat\Admin\Widgets\Widget;
  7. use Illuminate\Support\Str;
  8. /**
  9. * @see https://www.chartjs.org/docs/latest/
  10. *
  11. * @method $this blue()
  12. * @method $this green()
  13. * @method $this orange()
  14. * @method $this purple()
  15. */
  16. abstract class Chart extends Widget
  17. {
  18. use AjaxRequestBuilder;
  19. public static $globalSettings = [
  20. 'defaultFontColor' => '#555',
  21. 'defaultFontFamily' => 'Nunito,system-ui,sans-serif',
  22. ];
  23. public $colors = [];
  24. protected $id = '';
  25. protected $type = 'line';
  26. protected $data = [
  27. 'labels' => [],
  28. 'datasets' => [],
  29. ];
  30. protected $options = [];
  31. protected $width;
  32. protected $height;
  33. protected $containerStyle = '';
  34. /**
  35. * Chart constructor.
  36. *
  37. * @param mixed ...$params
  38. */
  39. public function __construct(...$params)
  40. {
  41. if (count($params) == 2) {
  42. list($title, $labels) = $params;
  43. $title && $this->title($title);
  44. $labels && $this->labels($labels);
  45. } elseif (!empty($params[0])) {
  46. if (is_string($params[0])) {
  47. $this->title($params[0]);
  48. } elseif (is_array($params[0])) {
  49. $this->labels($params[0]);
  50. }
  51. }
  52. $this->setDefaultColors();
  53. }
  54. /**
  55. * Composite the chart.
  56. *
  57. * @param Chart $chart
  58. *
  59. * @return $this
  60. */
  61. public function composite(self $chart)
  62. {
  63. $this->data['datasets']
  64. = array_merge($this->data['datasets'], $chart->getDatasets());
  65. return $this;
  66. }
  67. /**
  68. * Set labels.
  69. *
  70. * @param $labels
  71. *
  72. * @return $this
  73. */
  74. public function labels(array $labels)
  75. {
  76. $this->data['labels'] = $labels;
  77. return $this;
  78. }
  79. /**
  80. * Add datasets.
  81. *
  82. * @example
  83. * $this->add('LiXin', [1, 23, 6, 10, 6]);
  84. * $this->add([
  85. * ['LiXin', [1, 23, 6, 10, 6]], ['阿翡', [4, 11, 8, 25, 19]]
  86. * ]);
  87. *
  88. * @param string|array $label
  89. * @param array $data
  90. * @param string|array $fillColor
  91. *
  92. * @return $this
  93. */
  94. public function add($label, $data = [], $fillColor = null)
  95. {
  96. if (is_array($label)) {
  97. foreach ($label as $item) {
  98. call_user_func_array([$this, 'add'], $item);
  99. }
  100. return $this;
  101. }
  102. $item = [
  103. 'label' => $label,
  104. 'data' => $data,
  105. 'backgroundColor' => $fillColor,
  106. ];
  107. if ($fillColor) {
  108. if (is_string($fillColor)) {
  109. $item['backgroundColor'] = $fillColor;
  110. } elseif (is_array($fillColor)) {
  111. $item = array_merge($fillColor, $item);
  112. }
  113. }
  114. $this->data['datasets'][] = &$item;
  115. return $this;
  116. }
  117. /**
  118. * @return array
  119. */
  120. public function getData()
  121. {
  122. return $this->data;
  123. }
  124. /**
  125. * @param bool $val
  126. *
  127. * @return $this
  128. */
  129. public function responsive(bool $val = true)
  130. {
  131. return $this->options(['responsive' => $val]);
  132. }
  133. /**
  134. * @see https://www.chartjs.org/docs/latest/configuration/legend.html
  135. *
  136. * @param array $opts
  137. *
  138. * @return $this
  139. */
  140. public function legend(array $opts)
  141. {
  142. if (!isset($this->options['legend'])) {
  143. $this->options['legend'] = [];
  144. }
  145. $this->options['legend'] = array_merge($this->options['legend'], $opts);
  146. return $this;
  147. }
  148. /**
  149. * @return $this
  150. */
  151. public function disableLegend()
  152. {
  153. return $this->legend(['display' => false]);
  154. }
  155. /**
  156. * @return $this
  157. */
  158. public function legendPosition(string $val)
  159. {
  160. return $this->legend(['position' => $val]);
  161. }
  162. /**
  163. * @see https://www.chartjs.org/docs/latest/configuration/tooltip.html
  164. *
  165. * @param array $opts
  166. *
  167. * @return $this
  168. */
  169. public function tooltips(array $opts)
  170. {
  171. if (!isset($this->options['tooltips'])) {
  172. $this->options['tooltips'] = [];
  173. }
  174. $this->options['tooltips'] = array_merge($this->options['tooltips'], $opts);
  175. return $this;
  176. }
  177. /**
  178. * Disable tooltip.
  179. *
  180. * @return $this
  181. */
  182. public function disableTooltip()
  183. {
  184. return $this->tooltips(['enabled' => false]);
  185. }
  186. /**
  187. * @see https://www.chartjs.org/docs/latest/configuration/title.html
  188. *
  189. * @param array $options
  190. *
  191. * @return $this
  192. */
  193. public function title($options)
  194. {
  195. if (is_array($options)) {
  196. $this->options['title'] = $options;
  197. } else {
  198. $this->options['title'] = ['text' => $options, 'display' => true, 'fontSize' => '14'];
  199. }
  200. return $this;
  201. }
  202. /**
  203. * @see https://www.chartjs.org/docs/latest/configuration/elements.html
  204. *
  205. * @param array $options
  206. *
  207. * @return $this
  208. */
  209. public function elements(array $options)
  210. {
  211. if (!isset($this->options['elements'])) {
  212. $this->options['elements'] = [];
  213. }
  214. $this->options['elements'] = array_merge($this->options['elements'], $options);
  215. return $this;
  216. }
  217. /**
  218. * @see https://www.chartjs.org/docs/latest/configuration/layout.html
  219. *
  220. * @param array $opts
  221. *
  222. * @return $this
  223. */
  224. public function layout(array $opts)
  225. {
  226. if (!isset($this->options['layout'])) {
  227. $this->options['layout'] = [];
  228. }
  229. $this->options['layout'] = array_merge($this->options['layout'], $opts);
  230. return $this;
  231. }
  232. /**
  233. * The padding to add inside the chart.
  234. *
  235. * @param array|int $opts
  236. *
  237. * @return Chart
  238. */
  239. public function padding($opts)
  240. {
  241. return $this->layout(['padding' => $opts]);
  242. }
  243. /**
  244. * @param array $opts
  245. *
  246. * @return $this
  247. */
  248. public function animation(array $opts)
  249. {
  250. if (!isset($this->options['animation'])) {
  251. $this->options['animation'] = [];
  252. }
  253. $this->options['animation'] = array_merge($this->options['animation'], $opts);
  254. return $this;
  255. }
  256. /**
  257. * Set width of container.
  258. *
  259. * @param string $width
  260. *
  261. * @return Chart
  262. */
  263. public function width($width)
  264. {
  265. return $this->setContainerStyle('width:'.$width);
  266. }
  267. /**
  268. * Set height of container.
  269. *
  270. * @param string $height
  271. *
  272. * @return Chart
  273. */
  274. public function height($height)
  275. {
  276. return $this->setContainerStyle('height:'.$height);
  277. }
  278. /**
  279. * @param string $style
  280. * @param bool $append
  281. *
  282. * @return $this
  283. */
  284. public function setContainerStyle(string $style, bool $append = true)
  285. {
  286. if ($append) {
  287. $this->containerStyle .= ';'.$style;
  288. } else {
  289. $this->containerStyle = $style;
  290. }
  291. return $this;
  292. }
  293. /**
  294. * Fill default color.
  295. *
  296. * @param array $colors
  297. *
  298. * @return void
  299. */
  300. protected function fillColor(array $colors = [])
  301. {
  302. $colors = $colors ?: $this->colors;
  303. foreach ($this->data['datasets'] as &$item) {
  304. if (empty($item['backgroundColor'])) {
  305. $item['backgroundColor'] = array_shift($colors);
  306. }
  307. }
  308. }
  309. /**
  310. * Make element id.
  311. *
  312. * @return void
  313. */
  314. protected function makeId()
  315. {
  316. if ($this->id) {
  317. return;
  318. }
  319. $this->id = 'chart_'.$this->type.Str::random(8);
  320. }
  321. public function getId()
  322. {
  323. $this->makeId();
  324. return $this->id;
  325. }
  326. /**
  327. * Setup script.
  328. *
  329. * @return string
  330. */
  331. protected function script()
  332. {
  333. $config = [
  334. 'type' => $this->type,
  335. 'data' => &$this->data,
  336. 'options' => &$this->options,
  337. ];
  338. $options = json_encode($config);
  339. // Global configure.
  340. $globalSettings = '';
  341. foreach (self::$globalSettings as $k => $v) {
  342. $globalSettings .= sprintf('Chart.defaults.global.%s="%s";', $k, $v);
  343. }
  344. if (!$this->allowBuildFetchingScript()) {
  345. return <<<JS
  346. {$globalSettings}
  347. setTimeout(function(){ new Chart($("#{$this->id}").get(0).getContext("2d"), $options) },60)
  348. JS;
  349. }
  350. $this->fetched(
  351. <<<JS
  352. if (!result.status) {
  353. return LA.error(result.message || 'Server internal error.');
  354. }
  355. var id = '{$this->id}', opt = $options, prev = window['obj'+id];
  356. opt.options = $.extend(opt.options, result.options || {});
  357. opt.data.datasets = result.datasets || opt.data.datasets;
  358. if (prev) prev.destroy();
  359. window['obj'+id] = new Chart($("#"+id).get(0).getContext("2d"), opt);
  360. JS
  361. );
  362. return $globalSettings.$this->buildFetchingScript();
  363. }
  364. /**
  365. * Get datasets.
  366. *
  367. * @return array
  368. */
  369. public function getDatasets()
  370. {
  371. $this->fillColor();
  372. return array_map(function ($v) {
  373. $v['type'] = $v['type'] ?? $this->type;
  374. return $v;
  375. }, $this->data['datasets']);
  376. }
  377. /**
  378. * @return string
  379. */
  380. public function render()
  381. {
  382. $this->makeId();
  383. $this->fillColor();
  384. $this->script = $this->script();
  385. $this->setHtmlAttribute([
  386. 'id' => $this->id,
  387. ]);
  388. $this->collectAssets();
  389. return <<<HTML
  390. <div class="chart" style="{$this->containerStyle}">
  391. <canvas {$this->formatHtmlAttributes()}>
  392. Your browser does not support the canvas element.
  393. </canvas>
  394. </div>
  395. HTML;
  396. }
  397. /**
  398. * @param string $method
  399. * @param array $parameters
  400. *
  401. * @return $this
  402. */
  403. public function __call($method, $parameters)
  404. {
  405. if (isset(Color::$chartTheme[$method])) {
  406. $this->colors = Color::$chartTheme[$method];
  407. return $this;
  408. }
  409. return parent::__call($method, $parameters); // TODO: Change the autogenerated stub
  410. }
  411. /**
  412. * Return JsonResponse instance.
  413. *
  414. * @param bool $returnOptions
  415. * @param array $data
  416. *
  417. * @return \Illuminate\Http\JsonResponse
  418. */
  419. public function toJsonResponse(bool $returnOptions = true, array $data = [])
  420. {
  421. return response()->json(array_merge(
  422. [
  423. 'status' => 1,
  424. 'datasets' => $this->getDatasets(),
  425. 'options' => $returnOptions ? $this->getOptions() : [],
  426. ],
  427. $data
  428. ));
  429. }
  430. /**
  431. * @return void
  432. */
  433. protected function setDefaultColors()
  434. {
  435. if (!$this->colors) {
  436. $this->colors = Color::$chartTheme['blue'];
  437. }
  438. }
  439. /**
  440. * Collect assets.
  441. *
  442. * @return void
  443. */
  444. public function collectAssets()
  445. {
  446. $this->script && Admin::script($this->script);
  447. Admin::collectComponentAssets('chartjs');
  448. }
  449. }