* @package chart * @version $Id: model.php 5086 2013-07-10 02:25:22Z wyd621@gmail.com $ * @link http://www.zentao.net */ class chartModel extends model { /** * Get first chart group id. * * @param int $dimensionID * @access public * @return int */ public function getFirstChartGroupID($dimensionID) { return $this->dao->select('id')->from(TABLE_MODULE) ->where('deleted')->eq('0') ->andWhere('type')->eq('chart') ->andWhere('root')->eq($dimensionID) ->andWhere('grade')->eq(1) ->orderBy('`order`') ->limit(1) ->fetch('id'); } /** * Get chart. * * @param int $chartID * @access public * @return object */ public function getByID($chartID) { $chart = $this->dao->select('*')->from(TABLE_CHART)->where('id')->eq($chartID)->fetch(); if(!$chart) return false; if(!empty($chart->fields) and $chart->fields != 'null') { $chart->fieldSettings = json_decode($chart->fields); $chart->fields = array(); foreach($chart->fieldSettings as $field => $settings) $chart->fields[] = $field; } else { $chart->fieldSettings = array(); } if($chart->sql == null) $chart->sql = ''; if(!empty($chart->filters)) $chart->filters = json_decode($chart->filters, true); return $this->processChart($chart); } /** * Process sql and correct type. * * @param object $chart * @access public * @return object */ public function processChart($chart) { if(!empty($chart->sql)) $chart->sql = trim(str_replace(';', '', $chart->sql)); if(!empty($chart->settings)) $chart->settings = json_decode($chart->settings, true); if(!empty($chart->settings) and is_array($chart->settings)) { $firstSetting = current($chart->settings); if(isset($firstSetting['type'])) $chart->type = $firstSetting['type']; } return $chart; } /** * Get tree of charts and groups. * * @param int $groupID * @param string $orderBy * @access public * @return array */ public function getPreviewCharts($groupID, $orderBy = 'id_desc') { if(!$groupID) return array('', array()); $currentGroup = $this->loadModel('tree')->getByID($groupID); if(empty($currentGroup) || $currentGroup->grade != 1) return array('', array()); $groups = $this->dao->select('id, grade, name')->from(TABLE_MODULE)->where('deleted')->eq('0')->andWhere('path')->like("{$currentGroup->path}%")->orderBy('`order`')->fetchAll(); if(!$groups) return array('', array()); $chartGroups = array(); foreach($groups as $group) { $chartGroups[$group->id] = $this->dao->select('*')->from(TABLE_CHART) ->where('deleted')->eq(0) ->andWhere("FIND_IN_SET($group->id, `group`)") ->andWhere('stage')->ne('draft') ->orderBy($orderBy) ->fetchAll(); } if(!$chartGroups) return array('', array()); $readyCharts = array(); foreach($chartGroups as $groupID => $chartGroup) { $charts = array(); foreach($chartGroup as $chart) { if($chart->stage != 'published') continue; $charts[] = $chart; } if(!empty($charts)) $readyCharts[$groupID] = $charts; } $index = 1; $chartTree = ''; $firstChart = null; foreach($groups as $group) { if(!isset($readyCharts[$group->id])) continue; if($group->grade == 2) { $class = $index == 1 ? 'open' : 'closed'; $chartTree .= "
  • '; $index++; } if($chartTree) $chartTree = "'; $charts = array(); if($firstChart) { if(!empty($firstChart->settings)) $firstChart->settings = json_decode($firstChart->settings, true); if(!empty($firstChart->filters)) $firstChart->filters = json_decode($firstChart->filters, true); if(!empty($firstChart->fields) and $firstChart->fields != 'null') { $firstChart->fieldSettings = json_decode($firstChart->fields); $firstChart->fields = array_keys(json_decode($firstChart->fields, true)); } $charts[] = $firstChart; } return array($chartTree, $charts); } /** * Gen radar. * * @param int $fields * @param int $settings * @param int $defaultSql * @param int $filters * @param array $langs * @access public * @return void */ public function genRadar($fields, $settings, $defaultSql, $filters, $langs = array()) { list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters); $yDatas = array(); $max = 0; foreach($yStats as $yStat) { $data = array(); foreach($xLabels as $xLabel) { $yStatXLabel = isset($yStat[$xLabel]) ? $yStat[$xLabel] : 0; $data[] = $yStatXLabel; } if(max($yStat) > $max) $max = max($yStat); $yDatas[] = $data; } $series = array(); $legend = new stdclass(); $series['type'] = 'radar'; $clientLang = $this->app->getClientLang(); foreach($yDatas as $index => $yData) { $fieldName = $fields[$metrics[$index]]['name']; if(!empty($fields[$metrics[$index]]['object']) and !empty($fields[$metrics[$index]]['field'])) { $relatedObject = $fields[$metrics[$index]]['object']; $relatedField = $fields[$metrics[$index]]['field']; $this->app->loadLang($relatedObject); $fieldName = isset($this->lang->$relatedObject->$relatedField) ? $this->lang->$relatedObject->$relatedField : $fieldName; } if(isset($langs[$metrics[$index]]) and !empty($langs[$metrics[$index]][$clientLang])) $fieldName = $langs[$metrics[$index]][$clientLang]; $seriesName = $fieldName . '(' . $this->lang->chart->aggList[$aggs[$index]] . ')'; $series['data'][] = array('name' => $seriesName, 'value' => $yData); } $indicator = array(); $optionList = $this->getSysOptions($fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']); foreach($xLabels as $xLabel) { $labelName = isset($optionList[$xLabel]) ? $optionList[$xLabel] : $xLabel; $indicator[] = array('name' => $labelName, 'max' => $max); } $options = array('series' => $series, 'legend' => $legend, 'radar' => array('indicator' => $indicator), 'tooltip' => array('trigger' => 'item')); return $options; } /** * Gen pie. * * @param string $schema * @param object $settings * @param string $sql * @param array $filters * @access public * @return array */ public function genPie($fields, $settings, $sql, $filters) { $group = isset($settings['group'][0]['field']) ? $settings['group'][0]['field'] : ''; $date = isset($settings['group'][0]['group']) ? zget($this->config->chart->dateConvert, $settings['group'][0]['group']) : ''; $metric = isset($settings['metric'][0]['field']) ? $settings['metric'][0]['field'] : ''; $agg = isset($settings['metric'][0]['valOrAgg']) ? $settings['metric'][0]['valOrAgg'] : ''; $defaultSql = str_replace(';', '', $sql); $groupSql = $groupBySql = "tt.`$group`"; if(!empty($date)) { $groupSql = $date == 'MONTH' ? "YEAR(tt.`$group`) as ttyear, $date(tt.`$group`) as ttgroup" : "$date(tt.`$group`) as $group"; $groupBySql = $date == 'MONTH' ? "YEAR(tt.`$group`), $date(tt.`$group`)" : "$date(tt.`$group`)"; } if($agg == 'distinct') { $aggSQL = "count($agg tt.`$metric`) as `$metric`"; } else { $aggSQL = "$agg(tt.`$metric`) as `$metric`"; } $sql = "select $groupSql,$aggSQL from ($defaultSql) tt"; if(!empty($filters)) { $wheres = array(); foreach($filters as $field => $filter) { $wheres[] = "$field {$filter['operator']} {$filter['value']}"; } $whereStr = implode(' and ', $wheres); $sql .= " where $whereStr"; } $sql .= " group by $groupBySql"; $rows = $this->dao->query($sql)->fetchAll(); $stat = $this->processRows($rows, $date, $group, $metric); $maxCount = 50; if(empty($date)) arsort($stat); $copyStat = $stat; $other = array_sum(array_splice($copyStat, $maxCount)); $stat[$this->lang->chart->other] = $other; if(empty($date)) arsort($stat); $optionList = $this->getSysOptions($fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']); $data = array(); foreach($stat as $name => $value) { if($value == 0) continue; $value = round($value, 2); $labelName = isset($optionList[$name]) ? $optionList[$name] : $name; $data[] = array('name' => $labelName, 'value' => $value); } $label = array('show' => true, 'position' => 'outside', 'formatter' => '{b} {d}%'); $legend = new stdclass(); $legend->type = 'scroll'; $legend->orient = 'vertical'; $legend->right = 0; $series[] = array('data' => $data, 'type' => 'pie', 'label' => $label); $options = array('series' => $series, 'legend' => $legend, 'tooltip' => array('trigger' => 'item', 'formatter' => "{b}
    {c} ({d}%)")); return $options; } /** * Process rows. * * @param array $rows * @param string $date * @param string $group * @param string $metric * @access public * @return array */ public function processRows($rows, $date, $group, $metric) { $stat = array(); foreach($rows as $row) { if(!empty($date) and $date == 'MONTH') { $stat[sprintf("%04d", $row->ttyear) . '-' . sprintf("%02d", $row->ttgroup)] = $row->$metric; } elseif(!empty($date) and $date == 'YEARWEEK') { $yearweek = sprintf("%06d", $row->$group); $year = substr($yearweek, 0, strlen($yearweek) - 2); $week = substr($yearweek, -2); $weekIndex = in_array($this->app->getClientLang(), array('zh-cn', 'zh-tw')) ? sprintf($this->lang->chart->groupWeek, $year, $week) : sprintf($this->lang->chart->groupWeek, $week, $year); $stat[$weekIndex] = $row->$metric; } elseif(!empty($date) and $date == 'YEAR') { $stat[sprintf("%04d", $row->$group)] = $row->$metric; } else { $stat[$row->$group] = $row->$metric; } } return $stat; } /** * Gen line. * * @param string $schema * @param object $settings * @param string $sql * @param array $filters * @param array $langs * @access public * @return array */ public function genLineChart($fields, $settings, $defaultSql, $filters, $langs = array()) { list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters); $fieldType = $fields[$settings['xaxis'][0]['field']]['type']; if($fieldType == 'date') sort($xLabels); $yDatas = array(); foreach($xLabels as $xLabel) { foreach($yStats as $index => $yStat) { if(!isset($yDatas[$index])) $yDatas[$index] = array(); $yDatas[$index][] = isset($yStat[$xLabel]) ? $yStat[$xLabel] : 0; } } $optionList = $this->getSysOptions($fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']); foreach($xLabels as $index => $xLabel) $xLabels[$index] = isset($optionList[$xLabel]) ? $optionList[$xLabel] : $xLabel; $xaxis = array('type' => 'category', 'data' => $xLabels); $yaxis = array('type' => 'value'); $legend = new stdclass(); $series = array(); $clientLang = $this->app->getClientLang(); foreach($yDatas as $index => $yData) { $fieldName = $fields[$metrics[$index]]['name']; if(!empty($fields[$metrics[$index]]['object']) and !empty($fields[$metrics[$index]]['field'])) { $relatedObject = $fields[$metrics[$index]]['object']; $relatedField = $fields[$metrics[$index]]['field']; $this->app->loadLang($relatedObject); $fieldName = isset($this->lang->$relatedObject->$relatedField) ? $this->lang->$relatedObject->$relatedField : $fieldName; } if(isset($langs[$metrics[$index]]) and !empty($langs[$metrics[$index]][$clientLang])) $fieldName = $langs[$metrics[$index]][$clientLang]; $seriesName = $fieldName . '(' . $this->lang->chart->aggList[$aggs[$index]] . ')'; $series[] = array('name' => $seriesName, 'data' => $yData, 'type' => 'line'); } $options = array('series' => $series, 'legend' => $legend, 'xAxis' => $xaxis, 'yAxis' => $yaxis, 'tooltip' => array('trigger' => 'axis')); return $options; } /** * Gen cluBar. * * @param int $fields * @param int $settings * @param int $defaultSql * @param int $filters * @param int $stack * @param array $langs * @access public * @return void */ public function genCluBar($fields, $settings, $defaultSql, $filters, $stack = '', $langs = array()) { list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters); $yDatas = array(); foreach($yStats as $yStat) { $data = array(); foreach($xLabels as $xLabel) { $data[] = isset($yStat[$xLabel]) ? $yStat[$xLabel] : 0; } $yDatas[] = $data; } $optionList = $this->getSysOptions($fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']); foreach($xLabels as $index => $xLabel) $xLabels[$index] = isset($optionList[$xLabel]) ? $optionList[$xLabel] : $xLabel; $xaxis = array('type' => 'category', 'data' => $xLabels, 'axisLabel' => array('interval' => 0)); $yaxis = array('type' => 'value'); $legend = new stdclass(); /* Cluster bar X graphs and cluster bar Y graphs are really just x and y axes switched, so cluster bar Y $xaixs and $yaxis are swapped so that the method can be reused. */ /* 簇状柱形图和簇状条形图其实只是x轴和y轴换了换,所以交换一下簇状条形图 xAxis和yAxis即可,这样方法就可以复用了。*/ if(in_array($settings['type'], array('cluBarY', 'stackedBarY'))) list($xaxis, $yaxis) = array($yaxis, $xaxis); $series = array(); $clientLang = $this->app->getClientLang(); foreach($yDatas as $index => $yData) { $fieldName = $fields[$metrics[$index]]['name']; if(!empty($fields[$metrics[$index]]['object']) and !empty($fields[$metrics[$index]]['field'])) { $relatedObject = $fields[$metrics[$index]]['object']; $relatedField = $fields[$metrics[$index]]['field']; $this->app->loadLang($relatedObject); $fieldName = isset($this->lang->$relatedObject->$relatedField) ? $this->lang->$relatedObject->$relatedField : $fieldName; } if(isset($langs[$metrics[$index]]) and !empty($langs[$metrics[$index]][$clientLang])) $fieldName = $langs[$metrics[$index]][$clientLang]; $seriesName = $fieldName . '(' . $this->lang->chart->aggList[$aggs[$index]] . ')'; $series[] = array('name' => $seriesName, 'data' => $yData, 'type' => 'bar', 'stack' => $stack); } $dataZoomX = '[{"type":"inside","startValue":0,"endValue":5,"minValueSpan":10,"maxValueSpan":10,"xAxisIndex":[0],"zoomOnMouseWheel":false,"moveOnMouseWheel":true,"moveOnMouseMove":true},{"type":"slider","realtime":true,"startValue":0,"endValue":5,"zoomLock":true,"brushSelect":false,"width":"80%","height":"5","xAxisIndex":[0],"fillerColor":"#ccc","borderColor":"#33aaff00","backgroundColor":"#cfcfcf00","handleSize":0,"showDataShadow":false,"showDetail":false,"bottom":"0","left":"10%"}]'; $dataZoomY = '[{"type":"inside","startValue":0,"endValue":5,"minValueSpan":10,"maxValueSpan":10,"yAxisIndex":[0],"zoomOnMouseWheel":false,"moveOnMouseWheel":true,"moveOnMouseMove":true},{"type":"slider","realtime":true,"startValue":0,"endValue":5,"zoomLock":true,"brushSelect":false,"width":5,"height":"80%","yAxisIndex":[0],"fillerColor":"#ccc","borderColor":"#33aaff00","backgroundColor":"#cfcfcf00","handleSize":0,"showDataShadow":false,"showDetail":false,"top":"10%","right":0}]'; $isY = in_array($settings['type'], array('cluBarY', 'stackedBarY')); $dataZoom = $isY ? json_decode($dataZoomY, true) : json_decode($dataZoomX, true); $options = array('series' => $series, 'legend' => $legend, 'xAxis' => $xaxis, 'yAxis' => $yaxis, 'tooltip' => array('trigger' => 'axis'), 'dataZoom' => $dataZoom); return $options; } /** * Get multi data. * * @param int $settings * @param int $defaultSql * @param int $filters * @access public * @return void */ public function getMultiData($settings, $defaultSql, $filters, $sort = false) { $group = isset($settings['xaxis'][0]['field']) ? $settings['xaxis'][0]['field'] : ''; $date = isset($settings['xaxis'][0]['group']) ? zget($this->config->chart->dateConvert, $settings['xaxis'][0]['group']) : ''; $metrics = array(); $aggs = array(); foreach($settings['yaxis'] as $yaxis) { $metrics[] = $yaxis['field']; $aggs[] = $yaxis['valOrAgg']; } $yCount = count($metrics); $xLabels = array(); $yStats = array(); for($i = 0; $i < $yCount; $i ++) { $metric = $metrics[$i]; $agg = $aggs[$i]; $groupSql = $groupBySql = "tt.`$group`"; if(!empty($date)) { $groupSql = $date == 'MONTH' ? "YEAR(tt.`$group`) as ttyear, $date(tt.`$group`) as ttgroup" : "$date(tt.`$group`) as $group"; $groupBySql = $date == 'MONTH' ? "YEAR(tt.`$group`), $date(tt.`$group`)" : "$date(tt.`$group`)"; } if($agg == 'distinct') { $aggSQL = "count($agg tt.`$metric`) as `$metric`"; } else { $aggSQL = "$agg(tt.`$metric`) as `$metric`"; } $sql = "select $groupSql,$aggSQL from ($defaultSql) tt"; if(!empty($filters)) { $wheres = array(); foreach($filters as $field => $filter) { $wheres[] = "`$field` {$filter['operator']} {$filter['value']}"; } $whereStr = implode(' and ', $wheres); $sql .= " where $whereStr"; } $sql .= " group by $groupBySql"; $rows = $this->dao->query($sql)->fetchAll(); $stat = $this->processRows($rows, $date, $group, $metric); $maxCount = 50; if($sort) arsort($stat); $yStats[] = $stat; $xLabels = array_merge($xLabels, array_keys($stat)); $xLabels = array_unique($xLabels); } return array($group, $metrics, $aggs, $xLabels, $yStats); } /** * Adjust the action is clickable. * * @param object $chart * @param string $action * @static * @access public * @return bool */ public static function isClickable($chart, $action) { return true; } /** * Get sys options. * * @param string $type * @access public * @return array */ public function getSysOptions($type, $object = '', $field = '', $sql = '') { $options = array('' => ''); switch($type) { case 'user': $options = $this->loadModel('user')->getPairs(); break; case 'product': $options = $this->loadModel('product')->getPairs(); break; case 'project': $options = $this->loadModel('project')->getPairsByProgram(); break; case 'execution': $options = $this->loadModel('execution')->getPairs(); break; case 'dept': $options = $this->loadModel('dept')->getOptionMenu(0); break; case 'option': if($field) { $path = $this->app->getModuleRoot() . 'dataview' . DS . 'table' . DS . "$object.php"; if(!is_file($path)) return; include $path; $options = $schema->fields[$field]['options']; } break; case 'object': if($field) { $table = zget($this->config->objectTables, $object); $options = $this->dao->select("id, {$field}")->from($table)->fetchPairs(); } break; case 'string': if($field and $sql) { $cols = $this->dbh->query($sql)->fetchAll(); foreach($cols as $col) { $data = $col->$field; $options[$data] = $data; } } break; } return $options; } public function getFilterFormat($filters) { $filterFormat = array(); foreach($filters as $filter) { $field = $filter['field']; if(!isset($filter['default'])) continue; $default = $filter['default']; switch($filter['type']) { case 'select': if(empty($default)) break; if(!is_array($default)) $default = array($default); $default = array_filter($default, function($val){return !empty($val);}); $value = "('" . implode("', '", $default) . "')"; $filterFormat[$field] = array('operator' => 'IN', 'value' => $value); break; case 'input': $filterFormat[$field] = array('operator' => 'like', 'value' => "'%$default%'"); break; case 'date': case 'datetime': $begin = $default['begin']; $end = $default['end']; if(empty($begin) or empty($end)) break; $value = "'$begin' and '$end'"; $filterFormat[$field] = array('operator' => 'BETWEEN', 'value' => $value); break; case 'condition': $operator = $filter['operator']; $value = $filter['value']; if(in_array($operator, array('IN', 'NOT IN'))) { $valueArr = explode(',', $value); foreach($valueArr as $key => $val) $valueArr[$key] = '"' . $val . '"'; $value = '(' . implode(',', $valueArr) . ')'; } elseif(in_array($operator, array('IS NOT NULL', 'IS NULL'))) { $value = ''; } $filterFormat[$field] = array('operator' => $operator, 'value' => $value); break; } } return $filterFormat; } }