Chart.php 11 KB

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