Chart.php 11 KB

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