model.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. <?php
  2. /**
  3. * The model file of chart module of ZenTaoPMS.
  4. *
  5. * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
  6. * @license ZPL (http://zpl.pub/page/zplv12.html)
  7. * @author Chunsheng Wang <chunsheng@cnezsoft.com>
  8. * @package chart
  9. * @version $Id: model.php 5086 2013-07-10 02:25:22Z wyd621@gmail.com $
  10. * @link https://www.zentao.net
  11. */
  12. class chartModel extends model
  13. {
  14. /**
  15. * Construct.
  16. *
  17. * @param string $appName
  18. * @access public
  19. * @return void
  20. */
  21. public function __construct($appName = '')
  22. {
  23. parent::__construct($appName);
  24. $this->loadBIDAO();
  25. $this->loadModel('bi');
  26. }
  27. /**
  28. * 判断是否有权限访问。
  29. * Check chart access.
  30. *
  31. * @param int $chartID
  32. * @access public
  33. * @return array
  34. */
  35. public function checkAccess($chartID, $method = 'preview')
  36. {
  37. $viewableObjects = $this->bi->getViewableObject('chart');
  38. if(!in_array($chartID, $viewableObjects))
  39. {
  40. return $this->app->control->sendError($this->lang->chart->accessDenied, helper::createLink('chart', $method));
  41. }
  42. }
  43. /**
  44. * 获取指定维度下的第一个分组 id。
  45. * Get the first group id under the specified dimension.
  46. *
  47. * @param int $dimensionID
  48. * @access public
  49. * @return int|string
  50. */
  51. public function getFirstGroup($dimensionID)
  52. {
  53. return $this->dao->select('id')->from(TABLE_MODULE)
  54. ->where('deleted')->eq('0')
  55. ->andWhere('type')->eq('chart')
  56. ->andWhere('root')->eq($dimensionID)
  57. ->andWhere('grade')->eq(1)
  58. ->orderBy('`order`')
  59. ->limit(1)
  60. ->fetch('id');
  61. }
  62. /**
  63. * 获取指定分组下默认显示的图表。
  64. * Get the charts displayed by default under the specified group.
  65. *
  66. * @param int $groupID
  67. * @access public
  68. * @return array
  69. */
  70. public function getDefaultCharts($groupID)
  71. {
  72. $group = $this->loadModel('tree')->getByID($groupID);
  73. if(empty($group) || $group->grade != 1) return array();
  74. $groups = $this->dao->select('id')->from(TABLE_MODULE)->where('deleted')->eq('0')->andWhere('path')->like(",{$groupID},%")->orderBy('`order`')->fetchPairs();
  75. if(!$groups) return array();
  76. $this->app->loadModuleConfig('screen');
  77. $viewableObjects = $this->bi->getViewableObject('chart');
  78. /* 获取分组下的第一个图表。*/
  79. /* Get the first chart under the group. */
  80. foreach($groups as $groupID)
  81. {
  82. $chart = $this->dao->select('*')->from(TABLE_CHART)
  83. ->where('deleted')->eq('0')
  84. ->andWhere('builtin', true)->eq('0')
  85. ->orWhere('id')->in($this->config->screen->builtinChart)
  86. ->markRight(1)
  87. ->andWhere("FIND_IN_SET({$groupID}, `group`)")
  88. ->andWhere('stage')->eq('published')
  89. ->andWhere('id')->in($viewableObjects)
  90. ->beginIF(!helper::hasFeature('program'))->andWhere('code')->notLike("%program%")->fi()
  91. ->beginIF(!helper::hasFeature('devops'))->andWhere('code')->notLike("%devops%")->fi()
  92. ->orderBy('id_desc')
  93. ->limit(1)
  94. ->fetch();
  95. if($chart)
  96. {
  97. $chart = $this->processChart($chart);
  98. $chart->currentGroup = $groupID;
  99. return array($chart);
  100. }
  101. }
  102. return array();
  103. }
  104. /**
  105. * 根据 id 获取一个图表。
  106. * Get a chart by id.
  107. *
  108. * @param int $chartID
  109. * @access public
  110. * @return object
  111. */
  112. public function getByID($chartID)
  113. {
  114. $chart = $this->dao->select('*')->from(TABLE_CHART)->where('id')->eq($chartID)->fetch();
  115. if(!$chart) return false;
  116. return $this->processChart($chart);
  117. }
  118. /**
  119. * 处理图表的数据以供后续使用。
  120. * Process the data of the chart for subsequent use.
  121. *
  122. * @param object $chart
  123. * @access public
  124. * @return object
  125. */
  126. public function processChart($chart)
  127. {
  128. if($chart->sql == null) $chart->sql = '';
  129. if($chart->sql) $chart->sql = trim(str_replace(';', '', $chart->sql));
  130. $chart->langs = json_decode($chart->langs, true);
  131. if($chart->langs === null) $chart->langs = array();
  132. $chart->filters = json_decode($chart->filters, true);
  133. if($chart->filters === null) $chart->filters = array();
  134. $chart->settings = json_decode($chart->settings, true);
  135. if($chart->settings === null) $chart->settings = array();
  136. if(!empty($chart->settings[0]['type']) && empty($chart->type)) $chart->type = $chart->settings[0]['type'];
  137. $chart->fieldSettings = json_decode($chart->fields, true);
  138. if($chart->fieldSettings === null) $chart->fieldSettings = array();
  139. $chart->fields = array_keys($chart->fieldSettings);
  140. return $chart;
  141. }
  142. /**
  143. * 生成分组和图表混排的菜单树。
  144. * Generate a menu tree that mixes groups and charts.
  145. *
  146. * @param int $groupID
  147. * @param string $orderBy
  148. * @access public
  149. * @return array
  150. */
  151. public function getTreeMenu($groupID, $orderBy = 'id_desc')
  152. {
  153. if(!$groupID) return array();
  154. $group = $this->loadModel('tree')->getByID($groupID);
  155. if(empty($group) || $group->grade != 1) return array();
  156. $groups = $this->dao->select('id, grade, name')->from(TABLE_MODULE)->where('deleted')->eq('0')->andWhere('path')->like("{$group->path}%")->orderBy('`order`')->fetchAll();
  157. if(!$groups) return array();
  158. $this->app->loadModuleConfig('screen');
  159. $viewableObjects = $this->bi->getViewableObject('chart');
  160. /* 获取每个分组下的图表以供生成菜单树。*/
  161. /* Get the charts under each group for generating the menu tree. */
  162. $chartGroups = array();
  163. foreach($groups as $group)
  164. {
  165. $chartGroups[$group->id] = $this->dao->select('id, name, builtin, code')->from(TABLE_CHART)
  166. ->where('deleted')->eq('0')
  167. ->andWhere('builtin', true)->eq('0')
  168. ->orWhere('id')->in($this->config->screen->builtinChart)
  169. ->markRight(1)
  170. ->andWhere("FIND_IN_SET({$group->id}, `group`)")
  171. ->andWhere('stage')->eq('published')
  172. ->andWhere('id')->in($viewableObjects)
  173. ->beginIF(!helper::hasFeature('program'))->andWhere('code')->notLike("%program%")->fi()
  174. ->beginIF(!helper::hasFeature('devops'))->andWhere('code')->notLike("%devops%")->fi()
  175. ->orderBy($orderBy)
  176. ->fetchAll();
  177. }
  178. if(!$chartGroups) return array();
  179. $treeMenu = array();
  180. foreach($groups as $group)
  181. {
  182. if(empty($chartGroups[$group->id])) continue;
  183. if(!helper::hasFeature('program') && strpos($group->name, $this->lang->program->common) !== false) continue;
  184. if(!helper::hasFeature('devops') && strpos($group->name, $this->lang->devops->common) !== false) continue;
  185. /* 菜单树中只显示二级分组名称。*/
  186. /* Only the name of the second-level group is displayed in the menu tree. */
  187. if($group->grade == 2) $treeMenu[] = (object)array('id' => $group->id, 'parent' => 0, 'name' => $group->name);
  188. foreach($chartGroups[$group->id] as $chart)
  189. {
  190. $treeMenu[] = (object)array('id' => $group->id . '_' . $chart->id, 'parent' => $group->id, 'name' => $chart->name);
  191. }
  192. }
  193. return $treeMenu;
  194. }
  195. /**
  196. * 获取图表的 echarts 配置。
  197. * Get the echarts configuration of the chart.
  198. *
  199. * @param object $chart
  200. * @access public
  201. * @return array
  202. */
  203. public function getEchartOptions($chart)
  204. {
  205. $settings = current($chart->settings);
  206. $type = $settings['type'];
  207. $driver = $chart->driver;
  208. $options = array();
  209. $filterFormat = $this->getFilterFormat($chart->filters);
  210. /* 过滤掉模板数据。 */
  211. if(strpos($chart->sql, 'zt_project') !== false) $chart->sql = preg_replace('/\bzt_project\b(?!\w)/', 'ztv_projectnotpl', $chart->sql);
  212. if(strpos($chart->sql, 'zt_task') !== false) $chart->sql = preg_replace('/\bzt_task\b(?!\w)/', 'ztv_tasknotpl', $chart->sql);
  213. if($type == 'pie') $options = $this->genPie($chart->fieldSettings, $settings, $chart->sql, $filterFormat, $driver);
  214. if($type == 'radar') $options = $this->genRadar($chart->fieldSettings, $settings, $chart->sql, $filterFormat, $chart->langs, $driver);
  215. if($type == 'line') $options = $this->genLineChart($chart->fieldSettings, $settings, $chart->sql, $filterFormat, $chart->langs, $driver);
  216. if($type == 'cluBarX' || $type == 'cluBarY') $options = $this->genCluBar($chart->fieldSettings, $settings, $chart->sql, $filterFormat, '', $chart->langs, $driver);
  217. if($type == 'stackedBar' || $type == 'stackedBarY') $options = $this->genCluBar($chart->fieldSettings, $settings, $chart->sql, $filterFormat, 'total', $chart->langs, $driver);
  218. if($type == 'waterpolo') $options = $this->bi->genWaterpolo($chart->fieldSettings, $settings, $chart->sql, $filterFormat, $driver);
  219. if(empty($options)) return array();
  220. $options = $this->addFormatter4Echart($options, $type);
  221. $options = $this->addRotate4Echart($options, $settings, $type);
  222. return $options;
  223. }
  224. /**
  225. * 为 echart options 添加 formatter。
  226. *
  227. * @param array $options
  228. * @param string $type
  229. * @access public
  230. * @return array
  231. */
  232. public function addFormatter4Echart($options, $type)
  233. {
  234. if($type == 'waterpolo')
  235. {
  236. $formatter = "RAWJS<(params) => (params.value * 100).toFixed(2) + '%'>RAWJS";
  237. $options['series'][0]['label']['formatter'] = $formatter;
  238. $options['tooltip']['formatter'] = $formatter;
  239. }
  240. elseif(in_array($type, $this->config->chart->canLabelRotate))
  241. {
  242. $labelMaxLength = $this->config->chart->labelMaxLength;
  243. $labelFormatter = "RAWJS<(value) => {value = value.toString(); return value.length <= $labelMaxLength ? value : value.substring(0, $labelMaxLength) + '...'}>RAWJS";
  244. if(!isset($options['xAxis']['axisLabel'])) $options['xAxis']['axisLabel'] = array();
  245. if(!isset($options['yAxis']['axisLabel'])) $options['yAxis']['axisLabel'] = array();
  246. $options['xAxis']['axisLabel']['formatter'] = $labelFormatter;
  247. $options['yAxis']['axisLabel']['formatter'] = $labelFormatter;
  248. }
  249. return $options;
  250. }
  251. /**
  252. * 为 echart options 添加 rotate。
  253. *
  254. * @param array $options
  255. * @param string $type
  256. * @access public
  257. * @return array
  258. * @param mixed[] $settings
  259. */
  260. public function addRotate4Echart($options, $settings, $type)
  261. {
  262. if(in_array($type, $this->config->chart->canLabelRotate))
  263. {
  264. if(isset($settings['rotateX']) and $settings['rotateX'] == 'use') $options['xAxis']['axisLabel']['rotate'] = 30;
  265. if(isset($settings['rotateY']) and $settings['rotateY'] == 'use') $options['yAxis']['axisLabel']['rotate'] = 30;
  266. }
  267. return $options;
  268. }
  269. /**
  270. * 雷达图。
  271. * Gen radar.
  272. *
  273. * @param array $fields
  274. * @param array $settings
  275. * @param string $defaultSql
  276. * @param array $filters
  277. * @param array $langs
  278. * @access public
  279. * @return array
  280. */
  281. public function genRadar($fields, $settings, $defaultSql, $filters, $langs = array(), $driver = 'mysql')
  282. {
  283. list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters, $driver);
  284. $yDatas = array();
  285. $max = 0;
  286. foreach($yStats as $yStat)
  287. {
  288. if(empty($yStat)) continue;
  289. $data = array();
  290. foreach($xLabels as $xLabel)
  291. {
  292. $yStatXLabel = isset($yStat[$xLabel]) ? $yStat[$xLabel] : 0;
  293. $data[] = $yStatXLabel;
  294. }
  295. if(max($yStat) > $max) $max = max($yStat);
  296. $yDatas[] = $data;
  297. }
  298. $series = array();
  299. $series['type'] = 'radar';
  300. foreach($yDatas as $index => $yData)
  301. {
  302. $fieldName = $this->chartTao->switchFieldName($fields, $langs, $metrics, $index);
  303. $seriesName = $fieldName . '(' . $this->lang->chart->aggList[$aggs[$index]] . ')';
  304. $series['data'][] = array('name' => $seriesName, 'value' => $yData);
  305. }
  306. $indicator = array();
  307. $optionList = $this->getSysOptions($fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
  308. foreach($xLabels as $xLabel)
  309. {
  310. $labelName = isset($optionList[$xLabel]) ? $optionList[$xLabel] : $xLabel;
  311. $indicator[] = array('name' => $labelName, 'max' => $max);
  312. }
  313. return array('series' => $series, 'radar' => array('indicator' => $indicator, 'center' => array('50%', '55%')), 'tooltip' => array('trigger' => 'item'));
  314. }
  315. /**
  316. * 饼图。
  317. * Gen pie.
  318. *
  319. * @param array $fields
  320. * @param array $settings
  321. * @param string $sql
  322. * @param array $filters
  323. * @access public
  324. * @return array
  325. * @param string $driver
  326. */
  327. public function genPie($fields, $settings, $sql, $filters, $driver = 'mysql')
  328. {
  329. $group = isset($settings['group'][0]['field']) ? $settings['group'][0]['field'] : '';
  330. $date = isset($settings['group'][0]['group']) ? zget($this->config->chart->dateConvert, $settings['group'][0]['group']) : '';
  331. $metric = isset($settings['metric'][0]['field']) ? $settings['metric'][0]['field'] : '';
  332. $agg = isset($settings['metric'][0]['valOrAgg']) ? $settings['metric'][0]['valOrAgg'] : '';
  333. $rows = $this->chartTao->getRows(str_replace(';', '', $sql), $filters, $date, $group, $metric, $agg, $driver);
  334. $stat = $this->chartTao->processRows($rows, $date, $group, $metric);
  335. if(empty($date)) arsort($stat);
  336. /* 若查询结果大于50条,将50条之后的结果归于其他。*/
  337. /* If the query results are greater than 50, the results after 50 will be classified as other. */
  338. $maxCount = 50;
  339. if(count($stat) > $maxCount)
  340. {
  341. $other = array_sum(array_slice($stat, $maxCount));
  342. $stat = array_slice($stat, 0, $maxCount);
  343. $stat[$this->lang->chart->other] = $other;
  344. }
  345. $seriesData = array();
  346. $optionList = $this->getSysOptions($fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
  347. foreach($stat as $name => $value)
  348. {
  349. if(empty($value)) continue;
  350. $labelName = isset($optionList[$name]) ? $optionList[$name] : $name;
  351. $value = round($value, 2);
  352. $seriesData[] = array('name' => $labelName, 'value' => $value);
  353. }
  354. $label = array('show' => true, 'position' => 'outside', 'formatter' => '{b} {d}%');
  355. $series[] = array('data' => $seriesData, 'center' => array('50%', '55%'), 'type' => 'pie', 'label' => $label);
  356. $legend = new stdclass();
  357. $legend->type = 'scroll';
  358. $legend->orient = 'horizontal';
  359. $legend->left = 'center';
  360. $legend->top = 'top';
  361. return array('series' => $series, 'legend' => $legend, 'tooltip' => array('trigger' => 'item', 'formatter' => "{b}<br/> {c} ({d}%)"));
  362. }
  363. /**
  364. * 折线图。
  365. * Gen line.
  366. *
  367. * @param array $fields
  368. * @param array $settings
  369. * @param string $defaultSql
  370. * @param array $filters
  371. * @param array $langs
  372. * @access public
  373. * @return array
  374. * @param string $driver
  375. */
  376. public function genLineChart($fields, $settings, $defaultSql, $filters, $langs = array(), $driver = 'mysql')
  377. {
  378. list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters, $driver);
  379. $fieldType = $fields[$settings['xaxis'][0]['field']]['type'];
  380. if($fieldType == 'date') sort($xLabels);
  381. $yDatas = array();
  382. foreach($xLabels as $xLabel)
  383. {
  384. foreach($yStats as $index => $yStat)
  385. {
  386. if(!isset($yDatas[$index])) $yDatas[$index] = array();
  387. $yDatas[$index][] = isset($yStat[$xLabel]) ? $yStat[$xLabel] : 0;
  388. }
  389. }
  390. $optionList = $this->getSysOptions($fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
  391. foreach($xLabels as $index => $xLabel) $xLabels[$index] = isset($optionList[$xLabel]) ? $optionList[$xLabel] : $xLabel;
  392. $series = array();
  393. foreach($yDatas as $index => $yData)
  394. {
  395. $fieldName = $this->chartTao->switchFieldName($fields, $langs, $metrics, $index);
  396. $seriesName = $fieldName . '(' . $this->lang->chart->aggList[$aggs[$index]] . ')';
  397. $series[] = array('name' => $seriesName, 'data' => $yData, 'type' => 'line');
  398. }
  399. $grid = array('left' => '3%', 'right' => '4%', 'bottom' => '3%', 'containLabel' => true);
  400. $xaxis = array('type' => 'category', 'data' => $xLabels, 'axisTick' => array('alignWithLabel' => true));
  401. $yaxis = array('type' => 'value');
  402. return array('series' => $series, 'grid' => $grid, 'xAxis' => $xaxis, 'yAxis' => $yaxis, 'tooltip' => array('trigger' => 'axis'));
  403. }
  404. /**
  405. * 簇状条形图、堆积条形图。
  406. * Gen cluBar.
  407. *
  408. * @param array $fields
  409. * @param array $settings
  410. * @param string $defaultSql
  411. * @param array $filters
  412. * @param string $stack
  413. * @param array $langs
  414. * @access public
  415. * @return array
  416. */
  417. public function genCluBar($fields, $settings, $defaultSql, $filters, $stack = '', $langs = array(), $driver = 'mysql')
  418. {
  419. list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters, $driver);
  420. $yDatas = array();
  421. foreach($yStats as $yStat)
  422. {
  423. $data = array();
  424. foreach($xLabels as $xLabel) $data[] = isset($yStat[$xLabel]) ? $yStat[$xLabel] : 0;
  425. $yDatas[] = $data;
  426. }
  427. $optionList = $this->getSysOptions($fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
  428. foreach($xLabels as $index => $xLabel) $xLabels[$index] = isset($optionList[$xLabel]) ? $optionList[$xLabel] : $xLabel;
  429. $position = 'top';
  430. if($settings['type'] == 'stackedBar' or $settings['type'] == 'stackedBarY') $position = 'inside';
  431. if($settings['type'] == 'cluBarY') $position = 'right';
  432. $label = array('show' => true, 'position' => $position, 'formatter' => '{c}');
  433. $series = array();
  434. foreach($yDatas as $index => $yData)
  435. {
  436. $fieldName = $this->chartTao->switchFieldName($fields, $langs, $metrics, $index);
  437. $seriesName = $fieldName . '(' . $this->lang->chart->aggList[$aggs[$index]] . ')';
  438. $series[] = array('name' => $seriesName, 'data' => $yData, 'type' => 'bar', 'stack' => $stack, 'label' => $label);
  439. }
  440. $grid = array('left' => '3%', 'right' => '4%', 'bottom' => '3%', 'containLabel' => true);
  441. $xaxis = array('type' => 'category', 'data' => $xLabels, 'axisLabel' => array('interval' => 0), 'axisTick' => array('alignWithLabel' => true));
  442. $yaxis = array('type' => 'value');
  443. /* 簇状柱形图和簇状条形图其实只是x轴和y轴换了换,所以交换一下簇状条形图 xAxis和yAxis即可,这样方法就可以复用了。*/
  444. $isY = in_array($settings['type'], array('cluBarY', 'stackedBarY'));
  445. if($isY) list($xaxis, $yaxis) = array($yaxis, $xaxis);
  446. $options = array('series' => $series, 'grid' => $grid, 'xAxis' => $xaxis, 'yAxis' => $yaxis, 'tooltip' => array('trigger' => 'axis'));
  447. if(is_array($xLabels) and count($xLabels) > 10)
  448. {
  449. $sliderConfig = $this->config->chart->dataZoom->slider;
  450. $axisIndex = $isY ? 'yAxisIndex' : 'xAxisIndex';
  451. $dataZoomCommon = $this->config->chart->dataZoom->common;
  452. $dataZoomCommon->inside->$axisIndex = array(0);
  453. $dataZoomCommon->slider->$axisIndex = array(0);
  454. $dataZoomCommon->slider->width = $sliderConfig->{$isY ? 'width' : 'height'};
  455. $dataZoomCommon->slider->height = $sliderConfig->{$isY ? 'height' : 'width'};
  456. $dataZoomCommon->slider->{$isY ? 'top' : 'bottom'} = $sliderConfig->{$isY ? 'top' : 'bottom'};
  457. $dataZoomCommon->slider->{$isY ? 'right' : 'left'} = $sliderConfig->{$isY ? 'right' : 'left'};
  458. $dataZoom = array($dataZoomCommon->inside, $dataZoomCommon->slider);
  459. $options['dataZoom'] = $dataZoom;
  460. }
  461. return $options;
  462. }
  463. /**
  464. * 获取图表所需的数据:X轴、Y轴、计数方式
  465. * Get multi data.
  466. *
  467. * @param array $settings
  468. * @param string $defaultSql
  469. * @param array $filters
  470. * @param bool $sort
  471. * @access public
  472. * @return array
  473. * @param string $driver
  474. */
  475. public function getMultiData($settings, $defaultSql, $filters, $driver, $sort = false)
  476. {
  477. $group = isset($settings['xaxis'][0]['field']) ? $settings['xaxis'][0]['field'] : '';
  478. $date = isset($settings['xaxis'][0]['group']) ? zget($this->config->chart->dateConvert, $settings['xaxis'][0]['group']) : '';
  479. $metrics = array();
  480. $aggs = array();
  481. foreach($settings['yaxis'] as $yaxis)
  482. {
  483. $metrics[] = $yaxis['field'];
  484. $aggs[] = $yaxis['valOrAgg'];
  485. }
  486. $yCount = count($metrics);
  487. $xLabels = array();
  488. $yStats = array();
  489. for($i = 0; $i < $yCount; $i ++)
  490. {
  491. $metric = $metrics[$i];
  492. $agg = $aggs[$i];
  493. $rows = $this->chartTao->getRows($defaultSql, $filters, $date, $group, $metric, $agg, $driver);
  494. $stat = $this->chartTao->processRows($rows, $date, $group, $metric);
  495. if($sort) arsort($stat);
  496. $yStats[] = $stat;
  497. $xLabels = array_merge($xLabels, array_keys($stat));
  498. $xLabels = array_unique($xLabels);
  499. }
  500. return array($group, $metrics, $aggs, $xLabels, $yStats);
  501. }
  502. /**
  503. * 使用设置的内容,在sql结果中计算百分比
  504. * Get water polo option.
  505. *
  506. * @param array $settings
  507. * @param string $sql
  508. * @param array $filters
  509. * @access public
  510. * @return array
  511. * @param string $driver
  512. */
  513. public function genWaterpolo($settings, $sql, $filters, $driver = 'mysql')
  514. {
  515. $operate = "{$settings['calc']}({$settings['goal']})";
  516. $sql = "select $operate count from ($sql) tt ";
  517. $moleculeSQL = $sql;
  518. $denominatorSQL = $sql;
  519. $moleculeWheres = array();
  520. $denominatorWheres = array();
  521. foreach($settings['conditions'] as $condition)
  522. {
  523. $condition = (array)$condition;
  524. $where = "{$condition['field']} {$this->config->chart->conditionList[$condition['condition']]} '{$condition['value']}'";
  525. $moleculeWheres[] = $where;
  526. }
  527. if(!empty($filters))
  528. {
  529. $wheres = array();
  530. foreach($filters as $field => $filter)
  531. {
  532. $wheres[] = "$field {$filter['operator']} {$filter['value']}";
  533. }
  534. $moleculeWheres = array_merge($moleculeWheres, $wheres);
  535. $denominatorWheres = $wheres;
  536. }
  537. if($moleculeWheres) $moleculeSQL .= 'where ' . implode(' and ', $moleculeWheres);
  538. if($denominatorWheres) $denominatorSQL .= 'where ' . implode(' and ', $denominatorWheres);
  539. $molecule = $this->bi->queryWithDriver($driver, $moleculeSQL, false);
  540. $denominator = $this->bi->queryWithDriver($driver, $denominatorSQL, false);
  541. $percent = $denominator->count ? round($molecule->count / $denominator->count, 4) : 0;
  542. $series = array(array('type' => 'liquidFill', 'data' => array($percent), 'color' => array('#2e7fff'), 'outline' => array('show' => false), 'label' => array('fontSize' => 26)));
  543. $tooltip = array('show' => true);
  544. $options = array('series' => $series, 'tooltip' => $tooltip);
  545. return $options;
  546. }
  547. /**
  548. * 根据用户设置的字段展示对应的下拉菜单。
  549. * Get field options.
  550. *
  551. * @param string $type user|product|project|execution|dept|option|object|string
  552. * @param string $object
  553. * @param string $field
  554. * @param string $sql
  555. * @param string $saveAs
  556. * @access public
  557. * @return array
  558. */
  559. public function getSysOptions($type, $object = '', $field = '', $sql = '', $saveAs = '', $driver = 'mysql')
  560. {
  561. if(in_array($type, array('user', 'product', 'project', 'execution', 'dept'))) return $this->bi->getScopeOptions($type);
  562. if(!$field) return array();
  563. $options = array();
  564. switch($type)
  565. {
  566. case 'option':
  567. $options = $this->bi->getDataviewOptions($object, $field);
  568. break;
  569. case 'object':
  570. $options = $this->bi->getObjectOptions($object, $field);
  571. break;
  572. case 'string':
  573. case 'number':
  574. if($sql)
  575. {
  576. $keyField = $field;
  577. $valueField = $saveAs ? $saveAs : $field;
  578. $options = $this->bi->getOptionsFromSql($sql, $driver, $keyField, $valueField);
  579. }
  580. break;
  581. }
  582. if($sql and $saveAs and in_array($type, array('user', 'product', 'project', 'execution', 'dept', 'option', 'object')))
  583. {
  584. $options = $this->bi->getOptionsFromSql($sql, $driver, $field, $saveAs);
  585. }
  586. return array_filter($options);
  587. }
  588. /**
  589. * 判断操作按钮是否可点击。
  590. * Adjust the action is clickable.
  591. *
  592. * @param object $chart
  593. * @param string $action
  594. * @access public
  595. * @return bool
  596. */
  597. public static function isClickable($chart, $action)
  598. {
  599. $builtinCharts = array();
  600. $builtinCharts[] = array(1001, 1110);
  601. $builtinCharts[] = array(10000, 10119);
  602. $builtinCharts[] = array(10201, 10220);
  603. $builtinCharts[] = array(20002, 20015);
  604. $builtinCharts[] = array(30000, 30001);
  605. $found = false; // 标记ID是否在范围内
  606. foreach ($builtinCharts as $range) {
  607. $minId = $range[0];
  608. $maxId = $range[1];
  609. if ($chart->id >= $minId && $chart->id <= $maxId) {
  610. $found = true;
  611. break;
  612. }
  613. }
  614. return !$found && !$chart->builtin;
  615. }
  616. /**
  617. * 格式化筛选器。
  618. * Format filter.
  619. *
  620. * @param array $filters
  621. * @access public
  622. * @return array
  623. */
  624. public function getFilterFormat($filters)
  625. {
  626. $filterFormat = array();
  627. foreach($filters as $filter)
  628. {
  629. $field = $filter['field'];
  630. $type = $filter['type'];
  631. if($type != 'condition' && !isset($filter['default'])) continue;
  632. if($type != 'condition') $default = $filter['default'];
  633. switch($type)
  634. {
  635. case 'select':
  636. if(empty($default)) break;
  637. if(!is_array($default)) $default = array($default);
  638. $default = array_filter($default, function($val){return !empty($val);});
  639. $value = "('" . implode("', '", $default) . "')";
  640. $filterFormat[$field] = array('operator' => 'IN', 'value' => $value);
  641. break;
  642. case 'input':
  643. $filterFormat[$field] = array('operator' => 'like', 'value' => "'%$default%'");
  644. break;
  645. case 'date':
  646. case 'datetime':
  647. $begin = $default['begin'];
  648. $end = $default['end'];
  649. if(empty($begin) or empty($end)) break;
  650. $begin = date('Y-m-d 00:00:00', strtotime($begin));
  651. $end = date('Y-m-d 23:59:59', strtotime($end));
  652. $value = "'$begin' and '$end'";
  653. $filterFormat[$field] = array('operator' => 'BETWEEN', 'value' => $value);
  654. break;
  655. case 'condition':
  656. $operator = $filter['operator'];
  657. $value = $filter['value'];
  658. if(in_array($operator, array('IN', 'NOT IN')))
  659. {
  660. $valueArr = explode(',', $value);
  661. foreach($valueArr as $key => $val) $valueArr[$key] = '"' . $val . '"';
  662. $value = '(' . implode(',', $valueArr) . ')';
  663. }
  664. elseif(in_array($operator, array('IS NOT NULL', 'IS NULL')))
  665. {
  666. $value = '';
  667. }
  668. $filterFormat[$field] = array('operator' => $operator, 'value' => $value);
  669. break;
  670. }
  671. }
  672. return $filterFormat;
  673. }
  674. /**
  675. * 在sql中将变量解析为空字符串。
  676. *
  677. * @param array $options
  678. * @param string $type
  679. * @access public
  680. * @return bool
  681. */
  682. public function isChartHaveData($options, $type)
  683. {
  684. if($type == 'waterpolo') return true;
  685. $data = array();
  686. if($type == 'pie') $data = $options['series'][0]['data'];
  687. if($type == 'line') $data = $options['xAxis']['data'];
  688. if($type == 'radar') $data = $options['radar']['indicator'];
  689. if($type == 'cluBarY' or $type == 'stackedBarY') $data = $options['yAxis']['data'];
  690. if($type == 'cluBarX' or $type == 'stackedBar') $data = $options['xAxis']['data'];
  691. return count($data) ? true : false;
  692. }
  693. }