Files
2024-04-07 06:12:02 +00:00

3124 lines
119 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* The model file of screen module of ZenTaoPMS.
*
* @copyright Copyright 2009-2022 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Mengyi Liu <liumengyi@cnezsoft.com>
* @package task
* @version $Id: control.php 5106 2022-02-08 17:15:54Z $
* @link https://www.zentao.net
*/
class screenModel extends model
{
/**
* Filters
*
* @var object
* @access public
*/
public $filter;
/**
* Construct function.
*
* @access public
* @return void
*/
public function __construct()
{
parent::__construct();
$this->loadBIDAO();
$this->filter = new stdclass();
$this->filter->screen = '';
$this->filter->year = '';
$this->filter->month = '';
$this->filter->dept = '';
$this->filter->account = '';
$this->filter->charts = array();
}
/**
* Get screen list.
*
* @param int $dimensionID
* @access public
* @return array
*/
public function getList($dimensionID)
{
$hasUsageReport = $this->config->edition !== 'open';
$screens = $this->dao->select('*')->from(TABLE_SCREEN)
->where('dimension')->eq($dimensionID)
->andWhere('deleted')->eq('0')
->beginIF(!$hasUsageReport)->andWhere('id')->ne(1001)->fi()
->fetchAll('id');
return $screens;
}
/**
* Get screen thumbnail.
*
* @param array $screens
* @access public
* @return array
*/
public function getThumbnail($screens)
{
foreach($screens as $screen)
{
$images = $this->loadModel('file')->getByObject('screen', $screen->id);
if(empty($images)) continue;
$image = end($images);
$screen->cover = helper::createLink('file', 'read', "fileID={$image->id}", 'png');
}
return $screens;
}
/**
* Remove scheme field of screen.
*
* @param array $screens
* @access public
* @return array
*/
public function removeScheme($screens)
{
foreach($screens as $screen) unset($screen->scheme);
return $screens;
}
/**
* Get screen by id.
*
* @param int $screenID
* @param int $year
* @param int $dept
* @param string $account
* @access public
* @return object
*/
public function getByID($screenID, $year = '', $month = '', $dept = '', $account = '', $withChartData = true)
{
$screen = $this->dao->select('*')->from(TABLE_SCREEN)->where('id')->eq($screenID)->fetch();
if(!isset($screen->scheme) or empty($screen->scheme)) $screen->scheme = file_get_contents(__DIR__ . '/json/screen.json');
if($withChartData) $screen->chartData = $this->genChartData($screen, $year, $month, $dept, $account);
return $screen;
}
/**
* Generate chartData of screen.
*
* @param object $screen
* @param string $year
* @param string $dept
* @param string $account
* @access public
* @return object
*/
public function genChartData($screen, $year, $month, $dept, $account)
{
$this->filter = new stdclass();
$this->filter->screen = $screen->id;
$this->filter->year = $year;
$this->filter->month = $month;
$this->filter->dept = $dept;
$this->filter->account = $account;
$this->filter->charts = array();
if(!$screen->builtin or in_array($screen->id, $this->config->screen->builtinScreen))
{
$scheme = json_decode($screen->scheme);
foreach($scheme->componentList as $component)
{
if(!empty($component->isGroup))
{
foreach($component->groupList as $key => $groupComponent)
{
if(isset($groupComponent->key) and $groupComponent->key === 'Select') $groupComponent = $this->buildSelect($groupComponent);
}
}
else
{
if(isset($component->key) and $component->key === 'Select') $component = $this->buildSelect($component);
}
}
return $scheme;
}
$editCanvasConfig = $this->config->screen->editCanvasConfig;
$componentList = json_decode($screen->scheme);
if(empty($componentList)) $componentList = array();
/* Reset height of canvas. */
foreach($componentList as $component)
{
if(!isset($component->attr)) continue;
$height = $component->attr->y + $component->attr->h;
if($height > $editCanvasConfig->height) $editCanvasConfig->height = $height;
}
$editCanvasConfig->height += 50;
$chartData = new stdclass();
$chartData->editCanvasConfig = $editCanvasConfig;
$chartData->componentList = $this->buildComponentList($componentList);
return $chartData;
}
/**
* Generate chartData of new screen.
*
* @param object $screen
* @param string $year
* @param string $dept
* @param string $account
* @access public
* @return object
*/
public function genNewChartData($screen, $year, $month, $dept, $account)
{
$this->loadModel('pivot');
$scheme = json_decode($screen->scheme);
foreach($scheme->componentList as $component)
{
if(!empty($component->isGroup))
{
foreach($component->groupList as $key => $groupComponent)
{
if(isset($groupComponent->key) and $groupComponent->key === 'Select') $groupComponent = $this->buildSelect($groupComponent);
}
}
else
{
if(isset($component->key) and $component->key === 'Select') $component = $this->buildSelect($component);
}
}
return $scheme;
}
/**
* Get the latest chart.
*
* @param object $component
* @access public
* @return void
*/
public function getLatestChart($component)
{
if(isset($component->key) and $component->key === 'Select') return $component;
$chartID = zget($component->chartConfig, 'sourceID', '');
if(!$chartID) return $component;
$type = $component->chartConfig->package;
$type = $this->getChartType($type);
$table = $this->config->objectTables[$type];
if($type == 'metric')
{
$chart = $this->loadModel('metric')->getByID($chartID);
}
else
{
$chart = $this->dao->select('*')->from($table)->where('id')->eq($chartID)->fetch();
}
if($type == 'metric') return $this->genMetricComponent($chart);
return $this->genComponentData($chart, $type, $component);
}
/**
* mergeChartAndPivotFilters
*
* @param int $type
* @param int $chartOrPivot
* @param int $sourceID
* @param int $filters
* @access public
* @return void
*/
public function mergeChartAndPivotFilters($type, $chartOrPivot, $sourceID, $filters)
{
$filterFormat = '';
$chartOrPivotFilters = json_decode($chartOrPivot->filters, true);
$mergeFilters = array();
foreach($chartOrPivotFilters as $index => $chartOrPivotFilter)
{
if(!isset($filters[$index]['default'])) continue;
$filterDefault = $filters[$index]['default'];
if($filterDefault === null) continue;
$filterType = $chartOrPivotFilter['type'];
$filterFrom = zget($chartOrPivotFilter, 'from', '');
if($filterType == 'date' or $filterType == 'datetime')
{
if($filterFrom == 'query')
{
if(is_numeric($filterDefault)) $filterDefault = date('Y-m-d H:i:s', $filterDefault / 1000);
}
else
{
if(is_array($filterDefault))
{
$begin = $filterDefault[0];
$end = $filterDefault[1];
$begin = date('Y-m-d H:i:s', $begin / 1000);
$end = date('Y-m-d H:i:s', $end / 1000);
$filterDefault = array('begin' => $begin, 'end' => $end);
}
else
{
$filterDefault = array('begin' => '', 'end' => '');
}
}
}
$chartOrPivotFilter['default'] = $filterDefault;
$mergeFilters[] = $chartOrPivotFilter;
}
if($type == 'pivot')
{
list($sql, $filterFormat) = $this->loadModel($type)->getFilterFormat($chartOrPivot->sql, $mergeFilters);
$chartOrPivot->sql = $sql;
}
else
{
$filterFormat = $this->loadModel($type)->getFilterFormat($mergeFilters);
}
return array($chartOrPivot, $filterFormat);
}
/**
* Generate a component of screen.
*
* @param object $chart
* @param string $type
* @param object $component
* @access public
* @return object
*/
public function genComponentData($chart, $type = 'chart', $component = null, $filters = '')
{
if(empty($chart) || ($chart->stage == 'draft' || $chart->deleted == '1'))
{
return $this->genNotFoundOrDraftComponentOption($component, $chart, $type);
}
$component = $this->unsetComponentDraftMarker($component);
$chart = clone($chart);
if($type == 'pivot' and $chart)
{
$chart = $this->loadModel('pivot')->processPivot($chart);
$chart->settings = json_encode($chart->settings);
}
if(empty($filters) and !empty($chart->filters))
{
if($type == 'pivot')
{
list($sql, $filters) = $this->loadModel($type)->getFilterFormat($chart->sql, json_decode($chart->filters, true));
$chart->sql = $sql;
}
else
{
$filters = $this->loadModel($type)->getFilterFormat(json_decode($chart->filters, true));
}
}
list($component, $typeChanged) = $this->initComponent($chart, $type, $component);
$component = $this->getChartOption($chart, $component, $filters);
if($type == 'chart') $component = $this->getAxisRotateOption($chart, $component);
$latestFilters = $this->getChartFilters($chart);
$component = $this->updateComponentFilters($component, $latestFilters);
if($type == 'chart' && (!$chart->builtin or in_array($chart->id, $this->config->screen->builtinChart)))
{
if(!empty($component->option->series))
{
$defaultSeries = $component->option->series;
if($component->type == 'radar')
{
$component->option->radar->indicator = $component->option->dataset->radarIndicator;
$defaultSeries[0]->data = $component->option->dataset->seriesData;
$legends = array();
foreach($component->option->dataset->seriesData as $seriesData) $legends[] = $seriesData->name;
$component->option->legend->data = $legends;
}
elseif($component->type != 'waterpolo')
{
$series = array();
for($i = 1; $i < count($component->option->dataset->dimensions); $i ++) $series[] = $defaultSeries[0];
$component->option->series = $series;
}
}
}
return $component;
}
/**
* Update piovt or chart component chartConfig filters.
*
* @param object $component
* @param array $latestFilters
* @access public
* @return object
*/
public function updateComponentFilters($component, $latestFilters)
{
// 如果传过来的component->chartConfig中有filters判断filters是否发生了改变改变则重置filters其中单个filter的linkedGlobalFilter属性就不存在了
if(!isset($component->chartConfig->filters))
{
$component->chartConfig->filters = $latestFilters;
$component->chartConfig->noSetupGlobalFilterList = array();
}
else
{
$oldFilters = $component->chartConfig->filters;
$filterChanged = $this->isFilterChange($oldFilters, $latestFilters);
if($filterChanged)
{
$component->chartConfig->filters = $latestFilters;
$component->chartConfig->noSetupGlobalFilterList = array();
}
else
{
if(!isset($_POST['filters'])) // 如果不是前端进行筛选操作,则说明是第一次打开页面,此时看看后台的默认值有没有发生变更
{
foreach($oldFilters as $index => $filter)
{
$newFilter = $latestFilters[$index];
$oldDefault = isset($filter->default) ? $filter->default : '';
$newDefault = isset($newFilter->default) ? $newFilter->default : '';
if($oldDefault !== $newDefault) $component->chartConfig->filters[$index]->default = $newDefault;
}
}
foreach($oldFilters as $index => $filter)
{
$isSelect = $filter->type == 'select' && $latestFilters[$index]->type == 'select';
$oldSaveAs = zget($filter, 'saveAs', '');
$newSaveAs = zget($latestFilters[$index], 'saveAs', '');
if($oldSaveAs !== $newSaveAs) $component->chartConfig->filters[$index]->default = $newSaveAs;
if($isSelect and $oldSaveAs !== $newSaveAs) $component->chartConfig->filters[$index]->saveAs = $newSaveAs;
}
}
}
return $component;
}
/**
* 判断图表/透视表的筛选器是否发生了变化。
* Determine if the filter for the chart/pivot has changed.
*
* @param array $oldFilters
* @param array $latestFilters
* @access public
* @return void
*/
public function isFilterChange($oldFilters, $latestFilters)
{
$filterChanged = false;
if(count($oldFilters) != count($latestFilters)) $filterChanged = true;
foreach($oldFilters as $index => $oldFilter)
{
$newFilter = $latestFilters[$index];
// 结果筛选器和查询筛选器都有的三个字段
if($oldFilter->field != $newFilter->field || $oldFilter->name != $newFilter->name || $oldFilter->type != $newFilter->type) $filterChanged = true;
// 如果一个是查询筛选器一个不是,也判定为更改
$oldHaveQuery = isset($oldFilter->from) and $oldFilter->from = 'query';
$newHaveQuery = isset($newFilter->from) and $newFilter->from = 'query';
if($oldHaveQuery != $newHaveQuery) $filterChanged = true;
// 对于查询筛选器可以再做进一步判断如果下拉选择器的typeOption发生了变化也应该判定为更改
if($oldHaveQuery and $newHaveQuery)
{
if($oldFilter->type == 'select' && $newFilter->type == 'select' && $oldFilter->typeOption != $newFilter->typeOption) $filterChanged = true;
}
// 如果发生了变化,不必再去判断后续
if($filterChanged) break;
}
return $filterChanged;
}
/**
* 生成不存在的图表或者草稿图表的参数。
* Generate not found or draft chart option.
*
* @param object $component
* @param object $chart
* @access public
* @return void
*/
public function genNotFoundOrDraftComponentOption($component, $chart, $type)
{
if(empty($component)) $component = new stdclass();
$noDataLang = $type == 'chart' ? 'noChartData' : 'noPivotData';
if(!isset($component->option)) $component->option = new stdclass();
if(!isset($component->option->title)) $component->option->title = new stdclass();
$component->option->title->notFoundText = sprintf($this->lang->screen->$noDataLang, $chart->name);
$component->option->isDeleted = true;
return $component;
}
/**
* 生成已下架或者已删除度量项的参数。
* Generate delist or deleted metric options.
*
* @param object $component
* @access public
* @return object
*/
public function genDelistOrDeletedMetricOption($component)
{
if(empty($component)) $component = new stdclass();
if(!isset($component->option)) $component->option = new stdclass();
if(!isset($component->option->title)) $component->option->title = new stdclass();
$component->option->title->notFoundText = $this->lang->screen->noMetricData;
$component->option->isDeleted = true;
return $component;
}
/**
* 删除component的已删除标记。
* Unset component option isDeleted.
*
* @param object $component
* @access public
* @return object
*/
public function unsetComponentDraftMarker($component)
{
if(isset($component->option->isDeleted)) unset($component->option->isDeleted);
if(isset($component->option->title)) unset($component->option->title->notFoundText);
return $component;
}
/**
* 生成全局筛选器的组件。
* Generate component of global filters.
*
* @param string $filterType
* @access public
* @return object
*/
public function genFilterComponent($filterType)
{
$this->loadModel('metric');
$type = ucfirst($filterType);
$component = new stdclass();
$component->chartConfig = new stdclass();
$component->chartConfig->id = $type;
$component->chartConfig->key = "{$type}Filter";
$component->chartConfig->chartKey = "V{$type}Filter";
$component->chartConfig->conKey = "VC{$type}Filter";
$component->chartConfig->category = 'Filters';
$component->chartConfig->categoryName = $this->lang->screen->globalFilter;
$component->chartConfig->package = 'Decorates';
if(in_array($filterType, $this->config->metric->scopeList))
{
$objectPairs = $this->metric->getPairsByScope($filterType, true);
$component->chartConfig->objectList = array_map(function($objectID, $objectTitle)
{
$object = new stdclass();
$object->label = $objectTitle;
$object->value = (string)$objectID;
return $object;
}, array_keys($objectPairs), array_values($objectPairs));
}
$firstAction = $this->dao->select('YEAR(date) as year')->from(TABLE_ACTION)->orderBy('id_asc')->limit(1)->fetch();
$yearRange = range((int)date('Y'), $firstAction->year);
$component->chartConfig->yearList = array_map(function($year)
{
$yearObject = new stdclass();
$yearObject->label = $year;
$yearObject->value = $year;
return $yearObject;
}, $yearRange);
return $component;
}
/**
* Generate metric component.
*
* @param object $metric
* @param object|null $component
* @param array $filterParams
* @access public
* @return object
*/
public function genMetricComponent($metric, $component = null, $filterParams = array())
{
$this->loadModel('metric');
if($metric->deleted == '1' || $metric->stage == 'wait') return $this->genDelistOrDeletedMetricOption($component);
$pagination = $this->getMetricPagination($component);
$filters = $this->processMetricFilter($filterParams, $metric->dateType);
list($pager, $pagination) = $this->preparePaginationBeforeFetchRecords($pagination);
$result = $this->metric->getResultByCode($metric->code, $filters, 'cron', $pager);
$pagination['total'] = $pager->recTotal;
$pagination['pageTotal'] = $pager->pageTotal;
$resultHeader = $this->metric->getViewTableHeader($metric);
$resultData = $this->metric->getViewTableData($metric, $result);
$isObjectMetric = $metric->scope != 'system';
$isDateMetric = $metric->dateType != 'nodate';
$tableOption = $this->getMetricTableOption($metric, $resultHeader, $resultData, $component);
$chartOption = $this->getMetricChartOption($metric, $resultHeader, $resultData, $component);
$card = $this->getMetricCardOption($metric, $resultData, $component);
$tableOption->pagination = $pagination;
$card->pagination = $pagination;
list($component, $typeChanged) = $this->initMetricComponent($metric, $component);
$component->chartConfig->title = $metric->name;
$component->chartConfig->sourceID = $metric->id;
$component->chartConfig->scope = $metric->scope;
$component->chartConfig->dateType = $metric->dateType;
$latestFilters = $this->buildMetricFilters($metric, $isObjectMetric, $isDateMetric);
$component = $this->updateMetricFilters($component, $latestFilters);
$component->option->chartOption = $chartOption;
$component->option->tableOption = $tableOption;
$component->option->card = $card;
$component->option->card->isDateMetric = $isDateMetric;
$component->option->card->isObjectMetric = $isObjectMetric;
$component->option->card->cardDateDefault = $component->option->card->filterValue ? $component->option->card->filterValue->dateString : '';
$component->option->noDataTip = $this->metric->getNoDataTip($metric->code);
return $component;
}
public function getMetricPagination($component)
{
$tablePagination = array('index' => 1, 'size' => 5, 'total' => 0, 'pageTotal' => 1);
$cardPagination = array('index' => 1, 'size' => 2 * 6, 'total' => 0, 'pageTotal' => 1);
if(empty($component)) return $tablePagination;
$option = $component->option;
$displayType = $option->displayType;
if(isset($option->card->pagination)) $cardPagination = array_merge($cardPagination, (array)$option->card->pagination);
if(isset($option->tableOption->pagination)) $tablePagination = array_merge($tablePagination, (array)$option->tableOption->pagination);
$tableOption = $option->tableOption;
$cardOption = $option->card;
if(isset($tableOption->rowNum)) $tablePagination['size'] = $tableOption->rowNum;
$cardRow = isset($cardOption->countEachRow) ? $cardOption->countEachRow : 2;
$cardColumn = isset($cardOption->countEachColumn) ? $cardOption->countEachColumn : 6;
$cardPagination['size'] = $cardRow * $cardColumn;
return $displayType == 'normal' ? $tablePagination : $cardPagination;
}
/**
* Prepare pagination before fetch records.
*
* @param object $pagination
* @access public
* @return array
*/
public function preparePaginationBeforeFetchRecords($pagination)
{
$defaultPagination = array('index' => 1, 'size' => 2 * 6, 'total' => 0);
if(is_string($pagination)) $pagination = json_decode($pagination, true);
if(empty($pagination)) return $pagination;
$pagination = array_merge($defaultPagination, (array)$pagination);
extract($pagination);
$this->app->loadClass('pager', true);
$pager = new pager($total, $size, $index);
return array($pager, $pagination);
}
/**
* Update metric component chartConfig filters.
*
* @param object $component
* @param array $latestFilters
* @access public
* @return object
*/
public function updateMetricFilters($component, $latestFilters)
{
if(!isset($component->chartConfig->filters))
{
$component->chartConfig->filters = $latestFilters;
}
return $component;
}
/**
* Build filters for metric.
*
* @param object $metric
* @access public
* @return array
*/
public function buildMetricFilters($metric, $isObjectMetric, $isDateMetric)
{
$this->loadModel('metric');
$scope = $metric->scope;
$filters = array();
if($isObjectMetric)
{
$scopeFilter = new stdclass();
$scopeFilter->belong = 'metric';
$scopeFilter->field = $scope;
$scopeFilter->name = $this->lang->screen->belong . $this->lang->$scope->common;
$scopeFilter->type = 'select';
$scopeFilter->typeOption = $scope;
$scopeFilter->default = null;
$filters[] = $scopeFilter;
}
if($isDateMetric)
{
$dateFilter = new stdclass();
$dateFilter->belong = 'metric';
$dateFilter->field = 'date';
$dateFilter->name = $this->lang->screen->dateRange;
$dateFilter->type = 'dateRange';
$dateFilter->typeOption = null;
$dateFilter->default = null;
$filters[] = $dateFilter;
}
return $filters;
}
/**
* Get chart option.
*
* @param object $chart
* @param object $component
* @param array $filters
* @access public
* @return object
*/
public function getChartOption($chart, $component, $filters = '')
{
switch($component->type)
{
case 'line':
return $this->getLineChartOption($component, $chart, $filters);
case 'cluBarY':
case 'stackedBarY':
case 'cluBarX':
case 'stackedBar':
case 'bar':
return $this->getBarChartOption($component, $chart, $filters);
case 'piecircle':
return $this->buildPieCircleChart($component, $chart);
case 'pie':
return $this->getPieChartOption($component, $chart, $filters);
case 'table':
return $this->getTableChartOption($component, $chart, $filters);
case 'radar':
return $this->getRadarChartOption($component, $chart, $filters);
case 'card':
return $this->buildCardChart($component, $chart);
case 'waterpolo':
if(strpos($chart->settings, 'waterpolo') === false) return $this->buildWaterPolo($component, $chart);
return $this->getWaterPoloOption($component, $chart, $filters);
case 'metric':
return $this->getMetricOption($component, $chart, $filters);
default:
return '';
}
}
/**
* Get chart option about rotate.
*
* @param object $chart
* @param object $component
* @access public
* @return object
*/
public function getAxisRotateOption($chart, $component)
{
$settings = json_decode($chart->settings, true);
$setting = $settings[0];
$component->chartConfig->xAxis = new stdclass();
$component->chartConfig->yAxis = new stdclass();
$component->chartConfig->xAxis->axisLabel = new stdclass();
$component->chartConfig->yAxis->axisLabel = new stdclass();
$component->chartConfig->xAxis->axisLabel->rotate = 0;
$component->chartConfig->yAxis->axisLabel->rotate = 0;
if(isset($setting['rotateX']) && $setting['rotateX'] == 'use') $component->chartConfig->xAxis->axisLabel->rotate = 30;
if(isset($setting['rotateY']) && $setting['rotateY'] == 'use') $component->chartConfig->yAxis->axisLabel->rotate = 30;
return $component;
}
/**
* Get bar chart option.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function getBarChartOption($component, $chart, $filters = '')
{
if($chart->sql)
{
$settings = json_decode($chart->settings, true);
$langs = json_decode($chart->langs, true);
$settings = $settings[0];
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->loadModel('chart')->getMultiData($settings, $chart->sql, $filters);
$fields = json_decode($chart->fields, true);
$dimensions = array($settings['xaxis'][0]['field']);
$sourceData = array();
$clientLang = $this->app->getClientLang();
$xLabelValues = $this->processXLabel($xLabels, $fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
foreach($yStats as $index => $dataList)
{
$field = zget($fields, $metrics[$index]);
$fieldName = $field['name'];
if(isset($langs[$field['field']]) and !empty($langs[$field['field']][$clientLang])) $fieldName = $langs[$field['field']][$clientLang];
$field = $fieldName . '(' . zget($this->lang->chart->aggList, $aggs[$index]) . ')';
$dimensions[] = $field;
foreach($dataList as $valueField => $value)
{
$valueField = $xLabelValues[$valueField];
if(empty($sourceData[$valueField]))
{
$sourceData[$valueField] = new stdclass();
$sourceData[$valueField]->{$settings['xaxis'][0]['field']} = $valueField;
}
$sourceData[$valueField]->{$field} = $value;
}
}
$component->option->dataset->dimensions = $dimensions;
$component->option->dataset->source = array_values($sourceData);
}
$component = $this->setComponentDefaults($component);
return $component;
}
/**
* Get line chart option.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function getLineChartOption($component, $chart, $filters = '')
{
if($chart->sql)
{
$settings = json_decode($chart->settings, true);
$langs = json_decode($chart->langs, true);
$settings = $settings[0];
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->loadModel('chart')->getMultiData($settings, $chart->sql, $filters);
$fields = json_decode($chart->fields, true);
$dimensions = array($settings['xaxis'][0]['field']);
$sourceData = array();
$clientLang = $this->app->getClientLang();
$xLabelValues = $this->processXLabel($xLabels, $fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
foreach($yStats as $index => $dataList)
{
$field = zget($fields, $metrics[$index]);
$fieldName = $field['name'];
if(isset($langs[$field['field']]) and !empty($langs[$field['field']][$clientLang])) $fieldName = $langs[$field['field']][$clientLang];
$field = $fieldName . '(' . zget($this->lang->chart->aggList, $aggs[$index]) . ')';
$dimensions[] = $field;
foreach($dataList as $valueField => $value)
{
$valueField = $xLabelValues[$valueField];
if(empty($sourceData[$valueField]))
{
$sourceData[$valueField] = new stdclass();
$sourceData[$valueField]->{$settings['xaxis'][0]['field']} = $valueField;
}
$sourceData[$valueField]->{$field} = $value;
}
}
/* Completing empty values. */
foreach($sourceData as $lineData)
{
foreach($dimensions as $dimension)
{
if(empty($lineData->{$dimension})) $lineData->{$dimension} = 0;
}
}
$component->option->dataset->dimensions = $dimensions;
$component->option->dataset->source = array_values($sourceData);
}
return $this->setComponentDefaults($component);
}
/**
* Get pie chart option.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function getPieChartOption($component, $chart, $filters = '')
{
if($chart->sql)
{
$settings = json_decode($chart->settings, true);
$settings = $settings[0];
$options = $this->loadModel('chart')->genPie(json_decode($chart->fields, true), $settings, $chart->sql, $filters);
if($settings['group'][0]['field'] == $settings['metric'][0]['field']) $settings['group'][0]['field'] = $settings['group'][0]['field'] . '1';
$dimensions = array($settings['group'][0]['field'], $settings['metric'][0]['field']);
$sourceData = array();
foreach($options['series'] as $dataList)
{
$field = $settings['metric'][0]['field'];
foreach($dataList['data'] as $data)
{
$fieldValue = $data['name'];
if(empty($sourceData[$fieldValue]))
{
$sourceData[$fieldValue] = new stdclass();
$sourceData[$fieldValue]->{$settings['group'][0]['field']} = (string)$fieldValue;
}
$sourceData[$fieldValue]->{$field} = $data['value'];
}
}
if(empty($sourceData)) $dimensions = array();
$component->option->dataset->dimensions = $dimensions;
$component->option->dataset->source = array_values($sourceData);
}
return $this->setComponentDefaults($component);
}
/**
* Get radar chart option.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function getRadarChartOption($component, $chart, $filters = '')
{
$indicator = array();
$seriesData = array();
if($chart->sql)
{
$settings = json_decode($chart->settings, true);
$langs = json_decode($chart->langs, true);
$settings = $settings[0];
list($group, $metrics, $aggs, $xLabels, $yStats) = $this->loadModel('chart')->getMultiData($settings, $chart->sql, $filters);
$fields = json_decode($chart->fields, true);
$radarIndicator = array();
$seriesData = array();
$max = 0;
$clientLang = $this->app->getClientLang();
$xLabelValues = $this->processXLabel($xLabels, $fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
foreach($yStats as $index => $dataList)
{
$fieldObj = zget($fields, $metrics[$index]);
$fieldName = $fieldObj['name'];
$field = $fieldObj['field'];
if(isset($langs[$field])) $fieldName = zget($langs[$field], $clientLang, $fieldName);
$seriesData[$index] = new stdclass();
$seriesData[$index]->name = $fieldName . '(' . zget($this->lang->chart->aggList, $aggs[$index]) . ')';
$values = array();
foreach($dataList as $valueField => $value)
{
$values[] = (float)$value;
$max = $max < $value ? (float)$value : (float)$max;
}
$seriesData[$index]->value = $values;
}
if(!empty($dataList))
{
foreach($dataList as $valueField => $value)
{
$indicator = new stdclass();
$indicator->name = $xLabelValues[$valueField];
$indicator->max = $max;
$radarIndicator[] = $indicator;;
}
}
$component->option->dataset->radarIndicator = $radarIndicator;
$component->option->dataset->seriesData = $seriesData;
}
return $this->setComponentDefaults($component);
}
/**
* Get table chart option.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function getTableChartOption($component, $chart, $filters = '')
{
if($chart->sql)
{
$settings = json_decode($chart->settings, true);
$fields = json_decode($chart->fields, true);
$langs = json_decode($chart->langs, true);
if(isset($settings['summary']) and $settings['summary'] == 'notuse')
{
list($options, $config) = $this->loadModel('pivot')->genOriginSheet($fields, $settings, $chart->sql, $filters, $langs);
}
else
{
list($options, $config) = $this->loadModel('pivot')->genSheet($fields, $settings, $chart->sql, $filters, $langs);
}
$colspan = array();
if(isset($options->columnTotal) and $options->columnTotal == 'sum' and !empty($options->array))
{
$optionsData = $options->array;
$count = count($optionsData);
foreach($optionsData as $index => $data)
{
if($index == ($count - 1))
{
$newData = array('total' => $this->lang->pivot->step2->total);
foreach($options->groups as $field) unset($data[$field]);
$newData += $data;
$optionsData[$index] = $newData;
}
}
$options->array = $optionsData;
$colspan[$count - 1][0] = count($options->groups);
}
$dataset = array();
foreach($options->array as $data) $dataset[] = array_values($data);
foreach($config as $i => $data)
{
foreach($data as $j => $rowspan)
{
for($k = 1; $k < $rowspan; $k ++)
{
unset($dataset[$i + $k][$j]);
}
}
}
$align = array();
$headers = array();
foreach($options->cols as $cols)
{
$count = 1;
$header = array();
foreach($cols as $data)
{
$header[] = $data;
if($count == 1) $align[] = 'center';
}
$headers[] = $header;
$count ++;
}
if(!isset($component->chartConfig->tableInfo)) $component->chartConfig->tableInfo = new stdclass();
$component->option->header = $headers;
$component->option->align = $align;
$component->option->columnWidth = array();
$component->option->rowspan = $config;
$component->option->colspan = $colspan;
$component->option->dataset = $dataset;
}
return $this->setComponentDefaults($component);
}
/**
* Get chart filters
*
* @param object $chart
* @access public
* @return void
*/
public function getChartFilters($chart)
{
$filters = json_decode($chart->filters, true);
$fields = json_decode($chart->fields, true);
if(empty($filters)) return array();
$this->loadModel('pivot');
$screenFilters = array();
foreach($filters as $filter)
{
$isQuery = (isset($filter['from']) and $filter['from'] == 'query');
if($isQuery)
{
if($filter['type'] == 'date' or $filter['type'] == 'datetime')
{
if(isset($filter['default']))
{
$default = $this->pivot->processDateVar($filter['default']);
$filter['default'] = empty($default) ? null : strtotime($default) * 1000;
}
}
$screenFilters[] = (object)$filter;
continue;
}
if($filter['type'] == 'date' or $filter['type'] == 'datetime')
{
if(isset($filter['default']))
{
$default = $filter['default'];
$begin = $default['begin'];
$end = $default['end'];
if(empty($begin) and empty($end))
{
$filter['default'] = null;
}
else if(empty($begin) or empty($end))
{
$filter['default'] = empty($begin) ? strtotime($end) * 1000 : strtotime($begin) * 1000;
}
else
{
$filter['default'] = array(strtotime($begin) * 1000, strtotime($end) * 1000);
}
}
else
{
$filter['default'] = null;
}
}
$screenFilters[] = (object)$filter;
}
return $screenFilters;
}
/**
* Process xLabel with lang
*
* @param array $xLabel
* @param string $type
* @param string $object
* @param string $field
* @access public
* @return array
*/
public function processXLabel($xLabels, $type, $object, $field)
{
$options = $this->getSysOptions($type, $object, $field);
$xLabelValues = array();
foreach($xLabels as $index => $label)
{
$xLabelValues[$label] = isset($options[$label]) ? $options[$label] : $label;
}
return $xLabelValues;
}
/**
* Process metric filter.
*
* @param array $filterParams
* @param string $dateType
* @access public
* @return array
*/
public function processMetricFilter($filterParams, $dateType)
{
$filters = array();
foreach($filterParams as $filterParam)
{
$field = $filterParam['field'];
$value = $filterParam['default'];
if(empty($value)) continue;
if($field == 'date')
{
$beginValue = $value[0];
$endValue = $value[1];
$beginFilter = $this->formatMetricDateByType($beginValue, $dateType);
$endFilter = $this->formatMetricDateByType($endValue, $dateType);
$beginFilter->value = $beginValue;
$endFilter->value = $endValue;
$filters['dateBegin'] = $beginFilter->$dateType;
$filters['dateEnd'] = $endFilter->$dateType;
}
else
{
$scopeFilter = new stdclass();
$scopeFilter->value = $value;
$scopeFilter->type = $field;
$filters['scope'] = implode(',', $value);
}
}
return $filters;
}
/**
* Format date of metric's filter by type.
*
* @param string $stamp
* @param string $dateType
* @access public
* @return array
*/
public function formatMetricDateByType($stamp, $dateType)
{
$formatedDate = new stdclass();
$formatedDate->year = date('Y', $stamp/1000);
if($dateType == 'month') $formatedDate->month = date('Y-m', $stamp/1000);
if($dateType == 'week') $formatedDate->week = date('Y-W', $stamp/1000);
if($dateType == 'day') $formatedDate->day = date('Y-m-d', $stamp/1000);
return $formatedDate;
}
/**
* Get system options.
*
* @param string $type
* @param string $object
* @param string $field
* @param string $sql
* @param string $saveAs
* @access public
* @return array
*/
public function getSysOptions($type, $object = '', $field = '', $sql = '', $saveAs = '')
{
$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 'project.status':
$this->app->loadLang('project');
$options = $this->lang->project->statusList;
break;
case 'option':
if($field)
{
$path = $this->app->getModuleRoot() . 'dataview' . DS . 'table' . DS . "$object.php";
if(is_file($path))
{
include $path;
$options = $schema->fields[$field]['options'];
}
}
break;
case 'object':
if($field)
{
$useField = $field;
$useTable = $object;
$path = $this->app->getModuleRoot() . 'dataview' . DS . 'table' . DS . "$object.php";
if(is_file($path))
{
include $path;
if(isset($schema->fields[$field]['object']))
{
$fieldObject = $schema->fields[$field]['object'];
$fieldShow = explode('.', $schema->fields[$field]['show']);
if($fieldObject) $useTable = $fieldObject;
if(count($fieldShow) == 2) $useField = $fieldShow[1];
}
}
$table = isset($this->config->objectTables[$useTable]) ? $this->config->objectTables[$useTable] : zget($this->config->objectTables, $object, '');
if($table)
{
$columns = $this->dbh->query("SHOW COLUMNS FROM $table")->fetchAll();
foreach($columns as $id => $column) $columns[$id] = (array)$column;
$fieldList = array_column($columns, 'Field');
$useField = in_array($useField, $fieldList) ? $useField : 'id';
$options = $this->dao->select("id, {$useField}")->from($table)->fetchPairs();
}
}
break;
default:
if($field and $sql)
{
$keyField = $field;
$valueField = $saveAs ? $saveAs : $field;
$options = $this->getOptionsFromSql($sql, $keyField, $valueField);
}
break;
}
if($sql and $field and $saveAs and in_array($type, array('user', 'product', 'project', 'execution', 'dept', 'project.status', 'option', 'object')))
{
$options = $this->getOptionsFromSql($sql, $field, $saveAs);
}
return array_filter($options);
}
/**
* Get pairs from column by keyField and valueField.
*
* @param string $sql
* @param string $keyField
* @param string $valueField
* @access public
* @return array
*/
public function getOptionsFromSql($sql, $keyField, $valueField)
{
$options = array();
$cols = $this->dbh->query($sql)->fetchAll();
$sample = current($cols);
if(!isset($sample->$keyField) or !isset($sample->$valueField)) return $options;
foreach($cols as $col)
{
$key = $col->$keyField;
$value = $col->$valueField;
$options[$key] = $value;
}
return $options;
}
/**
* Build component list.
*
* @param array $componentList
* @access public
* @return array
*/
public function buildComponentList($componentList)
{
$components = array();
foreach($componentList as $component)
{
if($component) $components[] = $this->buildComponent($component);
}
return $components;
}
/**
* Build component.
*
* @param object $component
* @access public
* @return object
*/
public function buildComponent($component)
{
/* If chart is builtin, build it. */
if(isset($component->sourceID) and $component->sourceID) return $this->buildChart($component);
if(isset($component->key) and $component->key === 'Select') return $this->buildSelect($component);
if(empty($component->isGroup)) return $this->setComponentDefaults($component);
$component->groupList = $this->buildComponentList($component->groupList);
return $this->buildGroup($component);
}
/**
* Build chart group.
*
* @param object $component
* @access public
* @return object
*/
public function buildGroup($component)
{
return $this->setComponentDefaults($component);
}
/**
* Set component defaults.
*
* @param object $component
* @access public
* @return object
*/
public function setComponentDefaults($component)
{
if(!isset($component->styles)) $component->styles = json_decode('{"filterShow": false, "hueRotate": 0, "saturate": 1, "contrast": 1, "brightness": 1, "opacity": 1, "rotateZ": 0, "rotateX": 0, "rotateY": 0, "skewX": 0, "skewY": 0, "blendMode": "normal", "animations": []}');
if(!isset($component->status)) $component->status = json_decode('{"lock": false, "hide": false}');
if(!isset($component->request)) $component->request = json_decode('{ "requestDataType": 0, "requestHttpType": "get", "requestUrl": "", "requestIntervalUnit": "second", "requestContentType": 0, "requestParamsBodyType": "none", "requestSQLContent": { "sql": "select * from where" }, "requestParams": { "Body": { "form-data": {}, "x-www-form-urlencoded": {}, "json": "", "xml": "" }, "Header": {}, "Params": {} } }');
return $component;
}
/**
* Build select.
*
* @param object $component
* @access public
* @return object
*/
public function buildSelect($component)
{
switch($component->type)
{
case 'year':
$component->option->value = $this->filter->year;
$beginYear = $this->dao->select('YEAR(MIN(date)) year')->from(TABLE_ACTION)->where('date')->notZeroDate()->fetch('year');
if($beginYear < 2009) $beginYear = 2009;
$options = array();
for($year = date('Y'); $year >= $beginYear; $year--) $options[] = array('label' => $year, 'value' => $year);
$component->option->dataset = $options;
$url = "createLink('screen', 'view', 'screenID=" . $this->filter->screen. "&year=' + value + '&month=" . $this->filter->month . "&dept=" . $this->filter->dept . "&account=" . $this->filter->account . "')";
$component->option->onChange = "window.location.href = $url";
break;
case 'month':
$component->option->value = $this->filter->month;
$beginYear = $this->dao->select('YEAR(MIN(date)) year')->from(TABLE_ACTION)->where('date')->notZeroDate()->fetch('year');
$beginMonth = $this->dao->select('MONTH(MIN(date)) month')->from(TABLE_ACTION)->where('date')->notZeroDate()->fetch('month');
$currentYear = date('Y');
$currentMonth = date('n');
$options = array();
for($month = 12; $month >= 1; $month--)
{
if($currentYear == $this->filter->year && $month > $currentMonth) continue;
if($currentYear == $beginYear && $month < $beginMonth) continue;
$options[] = array('label' => $month, 'value' => $month);
}
$component->option->dataset = $options;
$url = "createLink('screen', 'view', 'screenID=" . $this->filter->screen. "&year=" . $this->filter->year . "&month=' + value + '&dept=" . $this->filter->dept . "&account=" . $this->filter->account . "')";
$component->option->onChange = "window.location.href = $url";
break;
case 'dept':
$component->option->value = (string)$this->filter->dept;
$options = array(array('label' => $this->lang->screen->allDepts, 'value' => '0'));
$depts = $this->dao->select('id,name')->from(TABLE_DEPT)->where('grade')->eq(1)->fetchAll();
foreach($depts as $dept)
{
$options[] = array('label' => $dept->name, 'value' => $dept->id);
}
$component->option->dataset = $options;
$url = "createLink('screen', 'view', 'screenID=" . $this->filter->screen . "&year=" . $this->filter->year . "&dept=' + value + '&account=')";
$component->option->onChange = "window.location.href = $url";
break;
case 'account':
$component->option->value = $this->filter->account;
$options = array(array('label' => $this->lang->screen->allUsers, 'value' => ''));
$depts = array();
if($this->filter->dept) $depts = $this->dao->select('id')->from(TABLE_DEPT)->where('path')->like(',' . $this->filter->dept . ',%')->fetchPairs();
$users = $this->dao->select('account,realname')->from(TABLE_USER)
->where('deleted')->eq(0)
->beginIF($this->filter->dept)->andWhere('dept')->in($depts)->fi()
->fetchAll();
foreach($users as $user)
{
$options[] = array('label' => $user->realname, 'value' => $user->account);
}
$component->option->dataset = $options;
$url = "createLink('screen', 'view', 'screenID=" . $this->filter->screen . "&year=" . $this->filter->year . "&dept=" . $this->filter->dept . "&account=' + value)";
$component->option->onChange = "window.location.href = $url";
break;
}
foreach($component->filterCharts as $chart)
{
if(!isset($this->filter->charts[$chart->chart])) $this->filter->charts[$chart->chart] = array();
$this->filter->charts[$chart->chart][$component->type] = $chart->field;
}
return $this->setComponentDefaults($component);
}
/**
* Build chart.
*
* @param object $component
* @access public
* @return object
*/
public function buildChart($component)
{
$chart = $this->dao->select('*')->from(TABLE_CHART)->where('id')->eq($component->sourceID)->fetch();
switch($chart->type)
{
case 'card':
return $this->buildCardChart($component, $chart);
break;
case 'line':
if($chart->builtin == '0')
{
$chart->sql = $this->setFilterSQL($chart);
return $this->getLineChartOption($component, $chart, array());
}
return $this->buildLineChart($component, $chart);
break;
case 'bar':
return $this->buildBarChart($component, $chart);
break;
case 'piecircle':
return $this->buildPieCircleChart($component, $chart);
break;
case 'pie':
if($chart->builtin == '0') return $this->getPieChartOption($component, $chart, array());
return $this->buildPieChart($component, $chart);
break;
case 'radar':
return $this->buildRadarChart($component, $chart);
break;
case 'org':
return $this->buildOrgChart($component, $chart);
break;
case 'funnel':
return $this->buildFunnelChart($component, $chart);
break;
case 'table':
return $this->buildTableChart($component, $chart);
break;
case 'cluBarY':
case 'stackedBarY':
case 'cluBarX':
case 'stackedBar':
$chart->sql = $this->setFilterSQL($chart);
return $this->getBarChartOption($component, $chart);
break;
case 'waterpolo':
return $this->getWaterPoloOption($component, $chart, array());
}
}
/**
* Set SQL filter
*
* @param object $chart
* @access public
* @return string
*/
public function setFilterSQL($chart)
{
if(isset($this->filter->charts[$chart->id]))
{
$conditions = array();
foreach($this->filter->charts[$chart->id] as $key => $field)
{
switch($key)
{
case 'year':
$conditions[] = $field . " = '" . $this->filter->$key . "'";
break;
case 'month':
$conditions[] = $field . " = '" . $this->filter->$key . "'";
break;
case 'dept':
if($this->filter->dept and !$this->filter->account)
{
$accountField = $this->filter->charts[$chart->id]['account'];
$users = $this->dao->select('account')->from(TABLE_USER)->alias('t1')
->leftJoin(TABLE_DEPT)->alias('t2')
->on('t1.dept = t2.id')
->where('t2.path')->like(',' . $this->filter->dept . ',%')
->fetchPairs('account');
$accounts = array();
foreach($users as $account) $accounts[] = "'" . $account . "'";
$conditions[] = $accountField . ' IN (' . implode(',', $accounts) . ')';
}
break;
case 'account':
if($this->filter->account) $conditions[] = $field . " = '" . $this->filter->$key . "'";
break;
}
}
if($conditions) return 'SELECT * FROM (' . str_replace(';', '', $chart->sql) . ') AS t1 WHERE ' . implode(' AND ', $conditions);
}
return $chart->sql;
}
/**
* Build card chart.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function buildCardChart($component, $chart)
{
if(!$chart->settings)
{
$component->option->dataset = '?';
}
else
{
$value = 0;
if($chart->sql)
{
$settings = json_decode($chart->settings);
if($settings and isset($settings->value))
{
$field = $settings->value->field;
$sql = $this->setFilterSQL($chart);
$results = $this->buildDataset($chart->id, $sql);
if($settings->value->type === 'text')
{
$value = empty($results[0]) ? '' : $results[0]->$field;
}
if($settings->value->type === 'value')
{
$value = empty($results[0]) ? 0 : $results[0]->$field;
}
if($settings->value->agg === 'count')
{
$value = count($results);
}
else if($settings->value->agg === 'sum')
{
foreach($results as $result)
{
$value += intval($result->$field);
}
$value = round($value);
}
}
else
{
$value = '?';
}
}
$component->option->dataset = (string)$value;
}
return $this->setComponentDefaults($component);
}
/**
* Build line chart.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function buildLineChart($component, $chart)
{
if(!$chart->settings)
{
$component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
$component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
$component->key = "LineCommon";
$component->chartConfig = json_decode('{"key":"LineCommon","chartKey":"VLineCommon","conKey":"VCLineCommon","title":"折线图","category":"Lines","categoryName":"折线图","package":"Charts","chartFrame":"echarts","image":"/static/png/line-e714bc74.png"}');
$component->option = json_decode('{"legend":{"show":true,"top":"5%","textStyle":{"color":"#B9B8CE"}},"xAxis":{"type":"category"},"yAxis":{"show":true,"axisLine":{"show":true},"type":"value"},"backgroundColor":"rgba(0,0,0,0)"}');
return $this->setComponentDefaults($component);
}
else
{
if($chart->sql)
{
$settings = json_decode($chart->settings);
if($settings and isset($settings->xaxis))
{
$dimensions = array($settings->xaxis[0]->name);
foreach($settings->yaxis as $yaxis) $dimensions[] = $yaxis->name;
$sourceData = array();
$sql = $this->setFilterSQL($chart);
$results = $this->dao->query($sql)->fetchAll();
foreach($results as $result)
{
$key = $settings->xaxis[0]->name;
$field = $settings->xaxis[0]->field;
$row = array($key => $result->$field);
foreach($settings->yaxis as $yaxis)
{
$field = $yaxis->field;
$row[$yaxis->name] = $result->$field;
}
$sourceData[] = $row;
}
$component->option->dataset->dimensions = $dimensions;
$component->option->dataset->source = $sourceData;
}
}
return $this->setComponentDefaults($component);
}
}
/**
* Build table chart
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function buildTableChart($component, $chart)
{
if(!$chart->settings)
{
$component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
$component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
$component->key = "TableScrollBoard";
$component->chartConfig = json_decode('{"key":"TableScrollBoard","chartKey":"VTableScrollBoard","conKey":"VCTableScrollBoard","title":"轮播列表","category":"Tables","categoryName":"表格","package":"Tables","chartFrame":"common","image":"/static/png/table_scrollboard-fb642e78.png"}');
$component->option = json_decode('{"header":["列1","列2","列3"],"dataset":[["行1列1","行1列2","行1列3"],["行2列1","行2列2","行2列3"],["行3列1","行3列2","行3列3"]],"rowNum":2,"waitTime":2,"headerHeight":35,"carousel":"single","headerBGC":"#00BAFF","oddRowBGC":"#003B51","evenRowBGC":"#0A2732"}');
return $this->setComponentDefaults($component);
}
else
{
if($chart->sql)
{
$settings = json_decode($chart->settings);
if($settings and isset($settings->column))
{
$header = array();
$dataset = array();
foreach($settings->column as $column)
{
$header[$column->field] = $column->name;
}
$sql = $this->setFilterSQL($chart);
$results = $this->buildDataset($chart->id, $sql);
foreach($results as $result)
{
$row = array();
foreach($header as $field => $name)
{
$row[] = $result->$field;
}
$dataset[] = $row;
}
$component->option->header = array_values($header);
$component->option->dataset = $dataset;
}
}
return $this->setComponentDefaults($component);
}
}
/**
* Build bar chart.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function buildBarChart($component, $chart)
{
if(!$chart->settings)
{
$component->request = json_decode('{"requestDataType": 0, "requestHttpType": "get", "requestUrl": "", "requestIntervalUnit": "second", "requestContentType": 0, "requestParamsBodyType": "none", "requestSQLContent": { "sql": "select * from where" }, "requestParams": { "Body": { "form-data": {}, "x-www-form-urlencoded": {}, "json": "", "xml": "" }, "Header": {}, "Params": {}}}');
$component->events = json_decode('{"baseEvent": {}, "advancedEvents": {}}');
$component->key = "BarCrossrange";
$component->chartConfig = json_decode('{"key": "BarCrossrange", "chartKey": "VBarCrossrange", "conKey": "VCBarCrossrange", "title": "横向柱状图", "category": "Bars", "categoryName": "柱状图", "package": "Charts", "chartFrame": "echarts", "image": "/static/png/bar_y-05067169.png" }');
$component->option = json_decode('{"xAxis": { "show": true, "type": "category" }, "yAxis": { "show": true, "axisLine": { "show": true }, "type": "value" }, "series": [], "backgroundColor": "rgba(0,0,0,0)"}');
return $this->setComponentDefaults($component);
}
else
{
if($chart->sql)
{
$settings = json_decode($chart->settings);
if($settings and isset($settings->xaxis))
{
$dimensions = array($settings->xaxis[0]->name);
foreach($settings->yaxis as $yaxis) $dimensions[] = $yaxis->name;
$sourceData = array();
$sql = $this->setFilterSQL($chart);
$results = $this->dao->query($sql)->fetchAll();
foreach($results as $result)
{
$key = $settings->xaxis[0]->name;
$field = $settings->xaxis[0]->field;
if($settings->yaxis[0]->agg == 'sum')
{
if(!isset($sourceData[$result->$field])) $sourceData[$result->$field] = array($key => $result->$field);
foreach($settings->yaxis as $yaxis)
{
$valueField = $yaxis->field;
if(!isset($sourceData[$result->$field][$yaxis->name])) $sourceData[$result->$field][$yaxis->name] = 0;
$sourceData[$result->$field][$yaxis->name] += $result->$valueField;
}
}
else
{
$row = array($key => $result->$field);
foreach($settings->yaxis as $yaxis)
{
$field = $yaxis->field;
$row[$yaxis->name] = $result->$field;
}
$sourceData[] = $row;
}
}
$component->option->dataset->dimensions = $dimensions;
$component->option->dataset->source = array_values($sourceData);
}
}
return $this->setComponentDefaults($component);
}
}
/**
* Build pie chart.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function buildPieChart($component, $chart)
{
if(!$chart->settings)
{
$component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
$component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
$component->key = "PieCommon";
$component->chartConfig = json_decode('{"key":"PieCommon","chartKey":"VPieCommon","conKey":"VCPieCommon","title":"饼图","category":"Pies","categoryName":"饼图","package":"Charts","chartFrame":"echarts","image":"/static/png/pie-9620f191.png"}');
$component->option = json_decode('{"type":"nomal","series":[{"type":"pie","radius":"70%","roseType":false}],"backgroundColor":"rgba(0,0,0,0)"}');
return $this->setComponentDefaults($component);
}
else
{
if($chart->sql)
{
$settings = json_decode($chart->settings);
if($settings and isset($settings->metric))
{
$dimensions = array($settings->group[0]->name, $settings->metric[0]->field);
$sourceData = array();
$sql = $this->setFilterSQL($chart);
$results = $this->dao->query($sql)->fetchAll();
$group = $settings->group[0]->field;
$groupCount = array();
foreach($results as $result)
{
if($settings->metric[0]->agg == 'count')
{
if(!isset($groupCount[$result->$group])) $groupCount[$result->$group] = 0;
$groupCount[$result->$group]++;
}
}
arsort($groupCount);
foreach($groupCount as $groupValue => $groupCount)
{
$sourceData[] = array($settings->group[0]->name => $groupValue, $settings->metric[0]->field => $groupCount);
}
}
if(empty($sourceData)) $dimensions = array();
$component->option->dataset->dimensions = $dimensions;
$component->option->dataset->source = $sourceData;
}
return $this->setComponentDefaults($component);
}
}
/**
* Build piecircle chart.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function buildPieCircleChart($component, $chart)
{
$option = new stdclass();
$option->type = 'nomal';
$option->series = array();
$option->series[0] = new stdclass();
$option->series[0]->type = 'pie';
$option->series[0]->radius = '70%';
$option->series[0]->roseType = false;
$option->backgroundColor = 'rgba(0,0,0,0)';
$option->series[0]->data = array();
$option->series[0]->data[0] = new stdclass();
$option->series[0]->data[0]->value = array();
$option->series[0]->data[0]->value[0] = 0;
$option->series[0]->data[1] = new stdclass();
$option->series[0]->data[1]->value = array();
$option->series[0]->data[1]->value[0] = 0;
if(!$chart->settings)
{
$component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
$component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
$component->key = "PieCircle";
$component->chartConfig = json_decode('{"key":"PieCircle","chartKey":"VPieCircle","conKey":"VCPieCircle","title":"饼图","category":"Pies","categoryName":"饼图","package":"Charts","chartFrame":"echarts","image":"/static/png/pie-circle-258fcce7.png"}');
$component->option = $option;
return $this->setComponentDefaults($component);
}
else
{
if($chart->sql)
{
$settings = json_decode($chart->settings);
if($settings and isset($settings->metric))
{
$sourceData = array();
$sql = $this->setFilterSQL($chart);
$results = $this->dao->query($sql)->fetchAll();
$group = $settings->group[0]->field;
$groupCount = array();
foreach($results as $result)
{
if($settings->metric[0]->agg == 'count')
{
if(!isset($groupCount[$result->$group])) $groupCount[$result->$group] = 0;
$groupCount[$result->$group]++;
}
}
foreach($groupCount as $groupValue => $groupCount) $sourceData[$groupValue] = $groupCount;
}
$doneData = round((array_sum($sourceData) != 0 and !empty($sourceData['done'])) ? $sourceData['done'] / array_sum($sourceData) : 0, 4);
$component->option->dataset = $doneData;
if(!isset($component->option->series) || !is_array($component->option->series)) $component->option->series = $option->series;
if(!isset($component->option)) $component->option = $option;
$component->option->series[0]->data[0]->value = array($doneData);
$component->option->series[0]->data[1]->value = array(1 - $doneData);
}
return $this->setComponentDefaults($component);
}
}
/**
* Build water polo chart.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function buildWaterPolo($component, $chart)
{
if(!$chart->settings)
{
$component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
$component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
$component->key = "PieCircle";
$component->chartConfig = json_decode('{"key":"WaterPolo","chartKey":"VWaterPolo","conKey":"VCWaterPolo","title":"水球图","category":"Mores","categoryName":"更多","package":"Charts","chartFrame":"common","image":"water_WaterPolo.png"}');
$component->option = json_decode('{"type":"nomal","series":[{"type":"liquidFill","radius":"90%","roseType":false}],"backgroundColor":"rgba(0,0,0,0)"}');
return $this->setComponentDefaults($component);
}
else
{
if($chart->sql)
{
$settings = json_decode($chart->settings);
$sourceData = 0;
if($settings and isset($settings->metric))
{
$sql = $this->setFilterSQL($chart);
$result = $this->dao->query($sql)->fetch();
$group = $settings->group[0]->field;
$sourceData = zget($result, $group, 0);
if(empty($sourceData)) $sourceData = 0;
}
$component->option->dataset = $sourceData;
}
return $this->setComponentDefaults($component);
}
}
/**
* Get waterpolo option.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function getWaterPoloOption($component, $chart, $filters)
{
if(!$chart->settings)
{
$component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
$component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
$component->key = "PieCircle";
$component->chartConfig = json_decode('{"key":"WaterPolo","chartKey":"VWaterPolo","conKey":"VCWaterPolo","title":"水球图","category":"Mores","categoryName":"更多","package":"Charts","chartFrame":"common","image":"water_WaterPolo.png"}');
$component->option = json_decode('{"type":"nomal","series":[{"type":"liquidFill","radius":"90%","roseType":false}],"backgroundColor":"rgba(0,0,0,0)"}');
return $this->setComponentDefaults($component);
}
else
{
$setting = json_decode($chart->settings, true)[0];
$options = $this->loadModel('chart')->genWaterPolo(json_decode($chart->fields, true), $setting, $chart->sql, $filters);
$component->option->dataset = $options['series'][0]['data'][0];
return $this->setComponentDefaults($component);
}
}
/**
* Get option of metric chart.
*
* @param object $metric
* @param array $resultHeader
* @param array $resultData
* @param object $component
* @access public
* @return object
*/
public function getMetricChartOption($metric, $resultHeader, $resultData, $component = null)
{
$chartOption = $this->metric->getEchartsOptions($resultHeader, $resultData);
if(isset($component) && isset($component->option->chartOption))
{
$preChartOption = $component->option->chartOption;
$preChartOption->series = $chartOption['series'];
if(!isset($preChartOption->xAxis)) $preChartOption->xAxis = $chartOption['xAxis'];
$preChartOption->xAxis->data = $chartOption['xAxis']['data'];
return $preChartOption;
}
if(!isset($chartOption['title']))
{
$chartOption['title'] = array();
$chartOption['title']['show'] = false;
$chartOption['title']['titleShow'] = true;
$chartOption['title']['textStyle'] = array('color' => '#BFBFBF');
}
$chartOption['title']['text'] = $metric->name;
$chartOption['backgroundColor'] = "#0B1727FF";
if(!isset($chartOption['legend'])) $chartOption['legend'] = array();
$chartOption['legend']['textStyle']['color'] = 'white';
$chartOption['legend']['inactiveColor'] = 'gray';
return $chartOption;
}
/**
* Get option of metric table.
*
* @param array $resultHeader
* @param array $resultData
* @param array $filterParams
* @param object $component
* @access public
* @return object
*/
public function getMetricTableOption($metric, $resultHeader, $resultData, $component = null)
{
$this->loadModel('metric');
$isObjectMetric = $this->metric->isObjectMetric($resultHeader);
$dateType = $metric->dateType;
list($groupHeader, $groupData) = $this->metric->getGroupTable($resultHeader, $resultData, $metric->dateType, false);
$tableOption = new stdclass();
if(!empty($component) && isset($component->option->tableOption)) $tableOption = $component->option->tableOption;
$tableOption->headers = $isObjectMetric ? $this->getMetricHeaders($groupHeader, $dateType) : array($groupHeader);
$tableOption->data = $groupData;
$tableOption->scope = $metric->scope;
return $tableOption;
}
/**
* Filter metric data.
*
* @param array $data
* @param string $dateType
* @param bool $isObjectMetric
* @param array $filters
* @access public
* @return array
*/
public function filterMetricData($data, $dateType, $isObjectMetric, $filters = array())
{
if(empty($filters)) return $data;
if($isObjectMetric)
{
if(isset($filters['scope']))
{
$scopeFilter = $filters['scope'];
$objectPairs = $this->loadModel('metric')->getPairsByScope($scopeFilter->type);
$selectedObjects = array_intersect_key($objectPairs, array_flip($scopeFilter->value));
foreach($data as $index => $row)
{
if(!in_array($row['scope'], $selectedObjects)) unset($data[$index]);
}
}
$filteredData = array();
if(isset($filters['begin'], $filters['end']))
{
$beginFilter = $filters['begin']->$dateType;
$endFilter = $filters['end']->$dateType;
foreach($data as $index => $row)
{
$filteredData[$index] = array_filter($row, function($value, $key) use ($beginFilter, $endFilter)
{
if($key == 'scope') return true;
if($key >= $beginFilter && $key <= $endFilter) return true;
return false;
}, ARRAY_FILTER_USE_BOTH);
}
}
else
{
$filteredData = $data;
}
}
else
{
$beginFilter = $filters['begin']->$dateType;
$endFilter = $filters['end']->$dateType;
$filteredData = array_filter($data, function($row) use ($beginFilter, $endFilter)
{
if($row['date'] >= $beginFilter && $row['date'] <= $endFilter) return true;
});
}
return array_values($filteredData);
}
/**
* 获取度量项卡片参数。
* Get card option of metric.
*
* @param object $metric
* @access public
* @return object
*/
public function getMetricCardOption(object $metric, $resultData, $component = null): object
{
$this->loadModel('metric');
$option = new stdclass();
if(empty($component))
{
$option->displayType = 'normal';
$option->cardType = 'A';
$option->dateType = $metric->dateType;
$option->bgColor = '#26292EFF';
$option->border = array('color' => '#515458FF', 'width' => 1, 'radius' => 2);
$option->scope = $metric->scope;
$option->objectPairs = array();
}
else
{
$option = $component->option->card;
}
$option->data = $resultData;
$option->filterValue = (is_array($option->data) && !empty($option->data)) ? current($option->data) : array();
return $option;
}
/**
* Get table headers of metric in screen designer.
*
* @param array $resultHeaders
* @param string $dateType
* @param object $filters
* @access public
* @return object
*/
public function getMetricHeaders($resultHeader, $dateType)
{
$headers = array_fill(0, 2, array());
foreach($resultHeader as $head)
{
if($head['name'] == 'scope')
{
if($dateType != 'year') $head['rowspan'] = 2;
$headers[0][] = $head;
}
else
{
$row = $dateType == 'year' ? 0 : 1;
$head['type'] = $dateType;
$head['value'] = $head['name'];
$headers[$row][] = $head;
}
}
$dateGroups = array_count_values(array_column($resultHeader, 'headerGroup'));
foreach($dateGroups as $date => $count)
{
$dateGroup = array();
$dateGroup['colspan'] = $count;
$dateGroup['title'] = $date;
$dateGroup['name'] = $date;
$dateGroup['value'] = substr($date, 0, 4);
$dateGroup['type'] = 'year';
$headers[0][] = $dateGroup;
}
if($dateType == 'year') unset($headers[1]);
return $headers;
}
/**
* Build radar chart.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function buildRadarChart($component, $chart)
{
if(!$chart->settings)
{
$component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
$component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
$component->key = "Radar";
$component->chartConfig = json_decode('{"key":"Radar","chartKey":"VRadar","conKey":"VCRadar","title":"雷达图","category":"Mores","categoryName":"更多","package":"Charts","chartFrame":"common","image":"/static/png/radar-91567f95.png"}');
$component->option = json_decode('{"radar":{"indicator":[{"name":"数据1","max":6500},{"name":"数据2","max":16000},{"name":"数据3","max":30000},{"name":"数据4","max":38000},{"name":"数据5","max":52000}]},"series":[{"name":"radar","type":"radar","areaStyle":{"opacity":0.1},"data":[{"name":"data1","value":[4200,3000,20000,35000,50000]}]}],"backgroundColor":"rgba(0,0,0,0)"}');
return $this->setComponentDefaults($component);
}
else
{
$indicator = array();
$seriesData = array();
if($chart->sql)
{
$settings = json_decode($chart->settings);
if($settings and isset($settings->metric))
{
$sql = $this->setFilterSQL($chart);
$results = $this->dao->query($sql)->fetchAll();
$group = $settings->group[0]->field;
$metrics = array();
foreach($settings->metric as $metric)
{
$metrics[$metric->key] = array('field' => $metric->field, 'name' => $metric->name, 'value' => 0);
}
foreach($results as $result)
{
if(isset($metrics[$result->$group]))
{
$field = $metrics[$result->$group]['field'];
$metrics[$result->$group]['value'] += $result->$field;
}
}
$max = 0;
foreach($metrics as $data)
{
if($data['value'] > $max) $max = $data['value'];
}
$data = array('name' => '', 'value' => array());
$value = array();
foreach($metrics as $key => $metric)
{
$indicator[] = array('name' => $metric['name'], 'max' => $max);
$data['value'][] = $metric['value'];
$value[] = $metric['value'];
}
$seriesData[] = $data;
}
$component->option->dataset->radarIndicator = $indicator;
$component->option->radar->indicator = $indicator;
$component->option->dataset->seriesData = $seriesData;
$component->option->series[0]->data[0]->value = $value;
}
return $this->setComponentDefaults($component);
}
}
/**
* Build org chart.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function buildOrgChart($component, $chart)
{
//TODO
}
/**
* Build funnel chart.
*
* @param object $component
* @param object $chart
* @access public
* @return object
*/
public function buildFunnelChart($component, $chart)
{
if(!$chart->settings)
{
$component->request = json_decode('{"requestDataType":0,"requestHttpType":"get","requestUrl":"","requestIntervalUnit":"second","requestContentType":0,"requestParamsBodyType":"none","requestSQLContent":{"sql":"select * from where"},"requestParams":{"Body":{"form-data":{},"x-www-form-urlencoded":{},"json":"","xml":""},"Header":{},"Params":{}}}');
$component->events = json_decode('{"baseEvent":{},"advancedEvents":{}}');
$component->key = "Funnel";
$component->chartConfig = json_decode('{"key":"Funnel","chartKey":"VFunnel","conKey":"VCFunnel","title":"漏斗图","category":"Mores","categoryName":"更多","package":"Charts","chartFrame":"echarts","image":"/static/png/funnel-d032fdf6.png"}');
$component->option = json_decode('{"dataset":{"dimensions":["product","dataOne"],"source":[{"product":"data1","dataOne":20},{"product":"data2","dataOne":40},{"product":"data3","dataOne":60},{"product":"data4","dataOne":80},{"product":"data5","dataOne":100}]},"series":[{"name":"Funnel","type":"funnel","gap":5,"label":{"show":true,"position":"inside"}}],"backgroundColor":"rgba(0,0,0,0)"}');
return $this->setComponentDefaults($component);
}
}
/**
* Get burn data.
*
* @access public
* @return array
*/
public function getBurnData()
{
$type = 'withdelay';
$this->loadModel('execution');
$executions = $this->execution->getList(0, 'sprint', 'doing') + $this->execution->getList(0, 'stage', 'doing');
$executionData = array();
foreach($executions as $executionID => $execution)
{
$execution = $this->execution->getByID($executionID);
/* Splice project name for the execution name. */
$execution->name = $this->loadModel('project')->getByID($execution->project)->name . '--' . $execution->name;
/* Get date list. */
if(((strpos('closed,suspended', $execution->status) === false and helper::today() > $execution->end)
or ($execution->status == 'closed' and substr($execution->closedDate, 0, 10) > $execution->end)
or ($execution->status == 'suspended' and $execution->suspendedDate > $execution->end))
and strpos($type, 'delay') === false)
$type .= ',withdelay';
$deadline = $execution->status == 'closed' ? substr($execution->closedDate, 0, 10) : $execution->suspendedDate;
$deadline = strpos('closed,suspended', $execution->status) === false ? helper::today() : $deadline;
$endDate = (strpos($type, 'withdelay') !== false and $deadline > $execution->end) ? $deadline : $execution->end;
list($dateList, $interval) = $this->execution->getDateList($execution->begin, $endDate, $type, 0, 'Y-m-d', $deadline);
$executionEnd = strpos($type, 'withdelay') !== false ? $execution->end : '';
$chartData = $this->execution->buildBurnData($executionID, $dateList, $type, 'left', $executionEnd);
$execution->chartData = $chartData;
$executionData[$executionID] = $execution;
}
return $executionData;
}
/**
* Init component.
*
* @param object $chart
* @param string $type
* @param object $component
* @access public
* @return void
*/
public function initComponent($chart, $type, $component = null)
{
if($type == 'metric') return $this->initMetricComponent($chart, $component);
if($type == 'chart' || $type == 'pivot') return $this->initChartAndPivotComponent($chart, $type, $component);
return array($component, false);
}
/**
* Init metric component.
*
* @param object $metric
* @param object $component
* @access public
* @return array
*/
public function initMetricComponent($metric, $component = null)
{
if(!$component) $component = new stdclass();
if(!isset($component->id)) $component->id = $metric->id;
if(!isset($component->sourceID)) $component->sourceID = $metric->id;
if(!isset($component->title)) $component->title = $metric->name;
if(!isset($component->type)) $component->type = 'metric';
if(!isset($component->chartConfig)) $component->chartConfig = json_decode($this->config->screen->chartConfig['metric']);
if(!isset($component->option)) $component->option = new stdclass();
return array($component, false);
}
/**
* Init chart or pivot component.
*
* @param object $metric
* @param string $type
* @param object $component
* @access public
* @return array
*/
public function initChartAndPivotComponent($chart, $type, $component = null)
{
if(!$component) $component = new stdclass();
if(!$chart) return array($component, false);
$chartID = $chart->id;
$chartName = $chart->name;
$settings = is_string($chart->settings) ? json_decode($chart->settings) : $chart->settings;
$builtin = $chart->builtin;
$isBuiltin = ($builtin and !in_array($chartID, $this->config->screen->builtinChart));
if(!isset($component->id)) $component->id = $chartID;
if(!isset($component->sourceID)) $component->sourceID = $chartID;
if(!isset($component->title)) $component->title = $chartName;
if($type == 'chart') $chartType = ($chart->builtin and !in_array($chart->id, $this->config->screen->builtinChart)) ? $chart->type : $settings[0]->type;
if($type == 'pivot') $chartType = 'table';
if($type == 'metric') $chartType = 'metric';
$component->type = $chartType;
$typeChanged = false;
// Get type is changed or not.
if(isset($component->chartConfig))
{
foreach($this->config->screen->chartConfig as $type => $chartConfig)
{
$chartConfig = json_decode($chartConfig, true);
if($chartConfig['key'] == $component->chartConfig->key) $componentType = $type;
}
$typeChanged = $chartType != $componentType;
}
// New component type or change component type.
if(!isset($component->chartConfig) or $typeChanged)
{
$chartConfig = json_decode(zget($this->config->screen->chartConfig, $chartType));
if(empty($chartConfig)) return null;
$component->chartConfig = $chartConfig;
$component->key = $chartConfig->key;
}
if(!isset($component->option) or $typeChanged)
{
$component->option = new stdclass();
$component->option->dataset = new stdclass();
}
$component = $this->initOptionTitle($component, $type, $chartName);
if(!isset($component->option->dataset)) $component->option->dataset = new stdclass();
$component->chartConfig->title = $chartName;
$component->chartConfig->sourceID = $component->sourceID;
return array($component, $typeChanged);
}
public function initOptionTitle($component, $type, $chartName)
{
if($type == 'pivot')
{
if(!isset($component->option->caption)) $component->option->caption = $chartName;
}
elseif($type == 'chart')
{
if(!isset($component->option->title))
{
$component->option->title = new stdclass();
$component->option->title->text = $chartName;
$component->option->title->show = false;
$component->option->title->titleShow = true;
}
}
return $component;
}
/**
* Check if the Chart is in use.
*
* @param int $chartID
* @param string $type
* @access public
* @return void
*/
public function checkIFChartInUse($chartID, $type = 'chart')
{
static $screenList = array();
if(empty($screenList)) $screenList = $this->dao->select('scheme')->from(TABLE_SCREEN)->where('deleted')->eq(0)->andWhere('status')->eq('published')->fetchAll();
foreach($screenList as $screen)
{
$scheme = json_decode($screen->scheme);
if(empty($scheme->componentList)) continue;
foreach($scheme->componentList as $component)
{
if(!empty($component->isGroup))
{
foreach($component->groupList as $key => $groupComponent)
{
if(!isset($groupComponent->chartConfig)) continue;
$sourceID = zget($groupComponent->chartConfig, 'sourceID', '');
$package = zget($groupComponent->chartConfig, 'package', '');
$sourceType = $this->getChartType($package);
if($chartID == $sourceID and $type == $sourceType) return true;
}
}
else
{
if(!isset($component->chartConfig)) continue;
$sourceID = zget($component->chartConfig, 'sourceID', '');
$package = zget($component->chartConfig, 'package', '');
$sourceType = $this->getChartType($package);
if($chartID == $sourceID and $type == $sourceType) return true;
}
}
}
return false;
}
/**
* Get chart type.
*
* @param string $type
* @access public
* @return string
*/
public function getChartType($type)
{
if($type == 'Tables' || $type == 'pivot') return 'pivot';
if($type == 'Metrics') return 'metric';
return 'chart';
}
/**
* 构建大屏图表的数据源。
* Build dataset of chart.
*
* @param int $chartID
* @param string $sql
* @access public
* @return array
*/
public function buildDataset($chartID, $sql = '')
{
if(!in_array($chartID, $this->config->screen->phpChart)) return $this->dao->query($sql)->fetchAll();
$year = $this->filter->year;
$month = $this->filter->month;
$projectList = $this->getUsageReportProjects($year, $month);
$productList = $this->getUsageReportProducts($year, $month);
if($chartID == 20002) return $this->getActiveUserTable($year, $month, $projectList);
if($chartID == 20012) return $this->getProductStoryTable($year, $month, $productList);
if($chartID == 20011) return $this->getProductTestTable($year, $month, $productList);
if($chartID == 20004) return $this->getActiveProductCard($year, $month);
if($chartID == 20007) return $this->getActiveProjectCard($year, $month);
if($chartID == 20013) return $this->getProjectStoryTable($year, $month, $projectList);
if($chartID == 20010) return $this->getProjectTaskTable($year, $month, $projectList);
}
/**
* 获取应用健康度体检报告的活跃账号数项目间对比表格。
* Get table of active account per project in usage report.
*
* @param string $year
* @param string $month
* @param array $projectList
* @access public
* @return array
*/
public function getActiveUserTable($year, $month, $projectList)
{
$date = date("Y-m-t", strtotime("$year-$month"));
$loginUserList = $this->dao->select('distinct actor')->from(TABLE_ACTION)
->where('objectType')->eq('user')
->andWhere('action')->eq('login')
->andWhere('year(date)')->eq($year)
->andWhere('month(date)')->eq($month)
->fetchPairs();
$dataset = array();
foreach($projectList as $projectID => $projectName)
{
$teamMemberList = $this->dao->select('t2.id, t2.account')->from(TABLE_TEAM)->alias('t1')
->leftJoin(TABLE_USER)->alias('t2')->on('t1.account=t2.account')
->where('t1.root')->eq($projectID)
->andWhere('t1.type')->eq('project')
->andWhere('date(t1.join)')->le($date)
->andWhere('t2.deleted')->eq('0')
->fetchPairs();
$activeUser = array_filter($teamMemberList, function($item) use ($loginUserList)
{
return in_array($item, $loginUserList);
});
$row = new stdclass();
$row->id = $projectID;
$row->name = $projectName;
$row->year = $year;
$row->month = $month;
$row->totalAccount = count($teamMemberList);
$row->activeAccount = count($activeUser);
$row->ratio = $row->totalAccount == 0 ? '0.00%' : number_format(($row->activeAccount/$row->totalAccount) * 100, 2) . '%';
$dataset[] = $row;
}
return $dataset;
}
/**
* 获取应用健康度体检报告的活跃项目数卡片。
* Get card of active project in usage report.
*
* @param string $year
* @param string $month
* @access public
* @return array
*/
public function getActiveProjectCard($year, $month)
{
return $this->dao->select('count(distinct t1.project) as count')->from(TABLE_ACTION)->alias('t1')
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
->where('t1.project')->ne(0)
->andWhere('year(t1.date)')->eq($year)
->andWhere('month(t1.date)')->eq($month)
->andWhere('t2.type')->eq('project')
->andWhere('t2.deleted')->eq('0')
->andWhere("NOT FIND_IN_SET('or', t2.vision)")
->fetchAll();
}
/**
* 获取应用健康度体检报告的活跃产品数卡片。
* Get card of active product in usage report.
*
* @param string $year
* @param string $month
* @access public
* @return array
*/
public function getActiveProductCard($year, $month)
{
$noDeletedProductList = $this->dao->select('id')->from(TABLE_PRODUCT)
->where('deleted')->eq('0')
->andWhere('shadow')->eq(0)
->fetchPairs();
$activeProductList = $this->dao->select('distinct product')->from(TABLE_ACTION)
->where('product')->ne(',0,')
->andWhere('product')->ne(',,')
->andWhere('product')->ne(',,0,,')
->andWhere('objectType')->notin('project,execution,task')
->andWhere('year(date)')->eq($year)
->andWhere('month(date)')->eq($month)
->fetchPairs();
$activeProductCount = 0;
foreach($activeProductList as $product)
{
$productID = trim($product, ',');
if(in_array($productID, $noDeletedProductList)) $activeProductCount ++;
}
$activeProductCard = new stdclass();
$activeProductCard->count = $activeProductCount;
$activeProductCard->year = $year;
$activeProductCard->month = $month;
return array($activeProductCard);
}
/**
* 获取应用健康度体检报告的 产品测试表。
* Get table of product test summary in usage report.
*
* @param string $year
* @param string $month
* @param array $productList
* @access public
* @return array
*/
public function getProductTestTable($year, $month, $productList)
{
foreach($productList as $productID => $productName)
{
$createdCaseCount = $this->dao->select('count(t2.id) as count')->from(TABLE_PRODUCT)->alias('t1')
->leftJoin(TABLE_CASE)->alias('t2')->on('t1.id=t2.product')
->where('t1.deleted')->eq('0')
->andWhere('t2.deleted')->eq('0')
->andWhere('year(t2.openedDate)')->eq($year)
->andWhere('month(t2.openedDate)')->eq($month)
->andWhere('t1.id')->eq($productID)
->fetch();
$linkedBugCount = $this->dao->select('count(t3.id) as count')->from(TABLE_PRODUCT)->alias('t1')
->leftJoin(TABLE_CASE)->alias('t2')->on('t1.id=t2.product')
->leftJoin(TABLE_BUG)->alias('t3')->on('t2.id=t3.case')
->where('t1.deleted')->eq('0')
->andWhere('t2.deleted')->eq('0')
->andWhere('t3.deleted')->eq('0')
->andWhere('year(t2.openedDate)')->eq($year)
->andWhere('month(t2.openedDate)')->eq($month)
->andWhere('t1.id')->eq($productID)
->fetch();
$createdBugCount = $this->dao->select('count(t2.id) as count')->from(TABLE_PRODUCT)->alias('t1')
->leftJoin(TABLE_BUG)->alias('t2')->on('t1.id=t2.product')
->where('t1.deleted')->eq('0')
->andWhere('t2.deleted')->eq('0')
->andWhere('year(t2.openedDate)')->eq($year)
->andWhere('month(t2.openedDate)')->eq($month)
->andWhere('t1.id')->eq($productID)
->fetch();
$fixedBugList = $this->dao->select('t2.id,datediff(t2.closedDate, t2.openedDate) as fixedCycle')->from(TABLE_PRODUCT)->alias('t1')
->leftJoin(TABLE_BUG)->alias('t2')->on('t1.id=t2.product')
->where('t1.deleted')->eq('0')
->andWhere('t2.deleted')->eq('0')
->andWhere('t2.status')->eq('closed')
->andWhere('t2.resolution')->eq('fixed')
->andWhere('year(t2.closedDate)')->eq($year)
->andWhere('month(t2.closedDate)')->eq($month)
->andWhere('t1.id')->eq($productID)
->fetchPairs();
$row = new stdclass();
$row->id = $productID;
$row->name = $productName;
$row->year = $year;
$row->month = $month;
$row->createdCases = $createdCaseCount->count;
$row->avgBugsOfCase = $createdCaseCount->count == 0 ? 0 : round($linkedBugCount->count/$createdCaseCount->count, 2);
$row->createdBugs = $createdBugCount->count;
$row->fixedBugs = count($fixedBugList);
$row->avgFixedCycle = count($fixedBugList) == 0 ? 0 : round(array_sum($fixedBugList)/count($fixedBugList), 2);
if($row->createdCases === 0 && $row->avgBugsOfCase === 0 && $row->createdBugs === 0 && $row->fixedBugs === 0 && $row->avgFixedCycle === 0) continue;
$dataset[] = $row;
}
return $dataset;
}
/**
* 获取应用健康度体检报告的项目任务概况表。
* Get table of project task summary in usage report.
*
* @param string $year
* @param string $month
* @param array $projectList
* @access public
* @return array
*/
public function getProjectTaskTable($year, $month, $projectList)
{
$deletedExecutionList = $this->dao->select('id')->from(TABLE_EXECUTION)
->where('deleted')->eq('1')
->andWhere('type')->in('sprint,stage,kanban')
->fetchPairs();
$dataset = array();
foreach($projectList as $projectID => $projectName)
{
$createdTaskList = $this->dao->select('id,openedBy')->from(TABLE_TASK)
->where('project')->eq($projectID)
->andWhere('year(openedDate)')->eq($year)
->andWhere('month(openedDate)')->eq($month)
->andWhere('deleted')->eq('0')
->andWhere("NOT FIND_IN_SET('or', vision)")
->andWhere('execution')->notin($deletedExecutionList)
->fetchPairs();
$finishedTaskList = $this->dao->select('id')->from(TABLE_TASK)
->where('project')->eq($projectID)
->andWhere('year(finishedDate)')->eq($year)
->andWhere('month(finishedDate)')->eq($month)
->andWhere('deleted')->eq('0')
->andWhere("NOT FIND_IN_SET('or', vision)")
->andWhere('execution')->notin($deletedExecutionList)
->fetchPairs();
$row = new stdclass();
$row->name = $projectName;
$row->year = $year;
$row->month = $month;
$row->createdTasks = count(array_unique(array_keys($createdTaskList)));
$row->finishedTasks = count(array_unique($finishedTaskList));
$row->contributors = count(array_unique(array_values($createdTaskList)));
if($row->createdTasks === 0 && $row->finishedTasks === 0 && $row->contributors === 0) continue;
$dataset[] = $row;
}
return $dataset;
}
/**
* 获取应用健康度体检报告的产品需求概况表。
* Get table of product story summary in usage report.
*
* @param string $year
* @param string $month
* @param array $productList
* @access public
* @return array
*/
public function getProductStoryTable($year, $month, $productList)
{
$releasedStories = $this->dao->select('t2.id, t1.id as product')->from(TABLE_PRODUCT)->alias('t1')
->leftJoin(TABLE_STORY)->alias('t2')->on('t1.id=t2.product')
->leftJoin(TABLE_ACTION)->alias('t3')->on('t2.id=t3.objectID')
->where('t1.deleted')->eq('0')
->andWhere('t2.deleted')->eq('0')
->andWhere("NOT FIND_IN_SET('or', t2.vision)")
->andWhere('t2.stage')->eq('released')
->andWhere('t3.objectType')->eq('story')
->andWhere('t3.action')->eq('linked2release')
->andWhere('year(t3.date)')->eq($year)
->andWhere('month(t3.date)')->eq($month)
->fetchPairs();
$releasedStoryGroups = array();
foreach($releasedStories as $storyID => $productID)
{
if(!isset($releasedStoryGroups[$productID])) $releasedStoryGroups[$productID] = array();
$releasedStoryGroups[$productID][] = $storyID;
}
$dataset = array();
foreach($productList as $productID => $productName)
{
$createdStoryCount = $this->dao->select('count(t2.id) as count')->from(TABLE_PRODUCT)->alias('t1')
->leftJoin(TABLE_STORY)->alias('t2')->on('t1.id=t2.product')
->where('t1.deleted')->eq('0')
->andWhere('t2.deleted')->eq('0')
->andWhere("NOT FIND_IN_SET('or', t2.vision)")
->andWhere('t1.id')->eq($productID)
->andWhere('year(t2.openedDate)')->eq($year)
->andWhere('month(t2.openedDate)')->eq($month)
->fetch();
$finishedStoryList = $this->dao->select('t2.id')->from(TABLE_PRODUCT)->alias('t1')
->leftJoin(TABLE_STORY)->alias('t2')->on('t1.id=t2.product')
->where('t1.deleted')->eq('0')
->andWhere('t2.deleted')->eq('0')
->andWhere("NOT FIND_IN_SET('or', t2.vision)")
->andWhere('t1.id')->eq($productID)
->andWhere('t2.closedReason')->eq('done')
->andWhere('year(t2.closedDate)')->eq($year)
->andWhere('month(t2.closedDate)')->eq($month)
->fetchPairs();
$releasedStoryList = isset($releasedStoryGroups[$productID]) ? $releasedStoryGroups[$productID] : array();
$deliveredStoryCount = count(array_merge($finishedStoryList, (array)$releasedStoryList));
$row = new stdclass();
$row->id = $productID;
$row->name = $productName;
$row->createdStories = $createdStoryCount->count;
$row->deliveredStories = $deliveredStoryCount;
if($row->createdStories === 0 && $row->deliveredStories === 0) continue;
$dataset[] = $row;
}
return $dataset;
}
/**
* 获取应用健康度体检报告的项目需求概况表。
* Get table of project story summary in usage report.
*
* @param string $year
* @param string $month
* @param array $projectList
* @access public
* @return array
*/
public function getProjectStoryTable($year, $month, $projectList)
{
$releasedStories = $this->dao->select('t3.id,t1.id as projectID')->from(TABLE_PROJECT)->alias('t1')
->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.project')
->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story=t3.id')
->leftJoin(TABLE_ACTION)->alias('t4')->on('t3.id=t4.objectID')
->where('t1.deleted')->eq('0')
->andWhere('t3.deleted')->eq('0')
->andWhere("NOT FIND_IN_SET('or', t1.vision)")
->andWhere("NOT FIND_IN_SET('or', t3.vision)")
->andWhere('t3.stage')->eq('released')
->andWhere('t4.objectType')->eq('story')
->andWhere('t4.action')->eq('linked2release')
->andWhere('year(t4.date)')->eq($year)
->andWhere('month(t4.date)')->eq($month)
->fetchPairs();
$releasedStoryGroups = array();
foreach($releasedStories as $storyID => $projectID)
{
if(!isset($releasedStoryGroups[$projectID])) $releasedStoryGroups[$projectID] = array();
$releasedStoryGroups[$projectID][] = $storyID;
}
$dataset = array();
foreach($projectList as $projectID => $projectName)
{
$createdStoryCount = $this->dao->select('count(t3.id) as count')->from(TABLE_PROJECT)->alias('t1')
->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.project')
->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story=t3.id')
->where('t1.deleted')->eq('0')
->andWhere('t3.deleted')->eq('0')
->andWhere("NOT FIND_IN_SET('or', t1.vision)")
->andWhere("NOT FIND_IN_SET('or', t3.vision)")
->andWhere('t1.type')->eq('project')
->andWhere('t3.type')->eq('story')
->andWhere('t1.id')->eq($projectID)
->andWhere('year(t3.openedDate)')->eq($year)
->andWhere('month(t3.openedDate)')->eq($month)
->fetch();
$finishedStoryList = $this->dao->select('t3.id')->from(TABLE_PROJECT)->alias('t1')
->leftJoin(TABLE_PROJECTSTORY)->alias('t2')->on('t1.id=t2.project')
->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story=t3.id')
->where('t1.deleted')->eq('0')
->andWhere('t3.deleted')->eq('0')
->andWhere("NOT FIND_IN_SET('or', t1.vision)")
->andWhere("NOT FIND_IN_SET('or', t3.vision)")
->andWhere('t1.id')->eq($projectID)
->andWhere('t3.closedReason')->eq('done')
->andWhere('year(t3.closedDate)')->eq($year)
->andWhere('month(t3.closedDate)')->eq($month)
->fetchPairs();
$releasedStoryList = isset($releasedStoryGroups[$projectID]) ? $releasedStoryGroups[$projectID] : array();
$deliveredStoryCount = count(array_merge($finishedStoryList, (array)$releasedStoryList));
$row = new stdclass();
$row->id = $projectID;
$row->name = $projectName;
$row->createdStories = $createdStoryCount->count;
$row->deliveredStories = $deliveredStoryCount;
if($row->createdStories === 0 && $row->deliveredStories === 0) continue;
$dataset[] = $row;
}
return $dataset;
}
/**
* 获取应用健康度体检报告的项目列表。
* Get project list for usage report.
*
* @param string $year
* @param string $month
* @access public
* @return array
*/
public function getUsageReportProjects($year, $month)
{
$date = date("Y-m-t", strtotime("$year-$month"));
return $this->dao->select('id,name')->from(TABLE_PROJECT)
->where('type')->eq('project')
->andWhere('deleted')->eq('0')
->andWhere('date(openedDate)')->le($date)
->andWhere("NOT FIND_IN_SET('or', vision)")
->andWhere('date(closedDate)', true)->gt($date)
->orWhere('date(closedDate)')->eq('0000-00-00')
->orWhere('closedDate')->in(NULL)
->markRight(true)
->fetchPairs();
}
/**
* 获取应用健康度体检报告的产品列表。
* Get product list for usage report.
*
* @param string $year
* @param string $month
* @access public
* @return array
*/
public function getUsageReportProducts($year, $month)
{
$date = date("Y-m-t", strtotime("$year-$month"));
return $this->dao->select('id,name')->from(TABLE_PRODUCT)
->where('deleted')->eq('0')
->andWhere('shadow')->eq(0)
->andWhere('date(createdDate)')->le($date)
->fetchPairs();
}
}