<?php namespace App\Utils; use App\Entity\AccountComptoir; use App\Entity\AccountGroupe; use App\Entity\Adherent; use App\Entity\Flux; use App\Entity\Groupe; use App\Entity\Prestataire; use App\Enum\ChartEnum; use App\Enum\CurrencyEnum; use App\Enum\MoyenEnum; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Security\Core\Security; use Symfony\UX\Chartjs\Builder\ChartBuilderInterface; use Symfony\UX\Chartjs\Model\Chart; /** * ChartUtils : Utils for stats and charts on admin dashboard */ class ChartUtils { private $em; private $session; private $security; private $chartBuilder; public function __construct(SessionInterface $session, CustomEntityManager $em, Security $security, ChartBuilderInterface $chartBuilder) { $this->em = $em; $this->session = $session; $this->security = $security; $this->chartBuilder = $chartBuilder; } /** * Get Chart given the type and the params * @param string $type Type of chart, one of ChartEnum * @param array $params array of params depending on the chart you want * @return ?Chart return the Chart object with datas and labels */ public function getMlcChart(string $type, array $params = []): ?Chart { switch ($type) { case 'fluxbytype': return $this->getChartForStatsType($params);//['type' => $type, 'byTypes' => $byTypes, 'byVolume' => $byVolume, 'options' => $options]); case 'groupeMlc': return $this->getChartForBalancesGroupe(); case 'comptoirMlc': return $this->getChartForBalancesComptoir(); case 'nbuser': // check params $grouByType = isset($params['grouByType']) ? $params['grouByType'] : 'month'; $withadherent = isset($params['withadherent']) ? $params['withadherent'] : true; $withpresta = isset($params['withpresta']) ? $params['withpresta'] : true; return $this->getChartForStats($grouByType, $withpresta, $withadherent); case 'nbpresta': return $this->getChartForStats($grouByType, true, false); case 'nbadherent': return $this->getChartForStats(false, true); case 'nombretransac': return $this->getChartForStatsMouvements(); case 'volumetransac': return $this->getChartForStatsMouvements(false); default: return null; } } /** * Get chart for flux by specific Flux::TYPE_*** by months * * @param array|null $byFluxType Flux::TYPE_*** or null for all * @param bool|boolean $byVolume if true, show volume, if false show amount * @return ?Chart return the Chart object with datas and labels */ public function getChartForStatsType(?array $params): ?Chart { // check params $type = isset($params['type']) ? $params['type'] : Chart::TYPE_LINE; $byFluxType = isset($params['byTypes']) ? $params['byTypes'] : []; $byVolume = isset($params['byVolume']) ? $params['byVolume'] : true; $options = isset($params['options']) ? $params['options'] : []; $groupBy = isset($params['groupBy'])?$params['groupBy']:'month'; if ($byVolume) { $functionRepository = 'findCountByParams'; } else { $functionRepository = 'findSumByParams'; } $datasArray = []; $mL = []; $colors = ['red', 'orange', 'yellow', 'green', 'blue', 'violet', 'grey', 'pink']; $colorCount = 0; if (!empty($byFluxType)) { // Chart of certain type of flux with same options array if ('all' === $byFluxType) { $byFluxType = [ Flux::TYPE_ACHAT, Flux::TYPE_COTISATION, Flux::TYPE_RECONVERSION, Flux::TYPE_RETRAIT, Flux::TYPE_TRANSACTION, Flux::TYPE_TRANSFERT, Flux::TYPE_VENTE, Flux::TYPE_VENTE_EMLC, ]; } foreach($byFluxType as $type) { $options = array_unique(array_merge($options, ['parenttype' => $type])); $datas = $this->em->getRepository(Flux::class)->$functionRepository($options); list($monthsLabelNew, $dataSet) = $this->getDatasetAndLabelsFromData($datas, $groupBy); $mL = array_unique(array_merge($monthsLabelNew, $mL)); list($bgColor, $bdColor) = $this->getColorsFromText($colors[$colorCount]); $colorCount++; $colorCount = $colorCount>7?0:$colorCount; $datasArray[] = [ 'label' => ucwords(str_replace('_', ' ', $type)), 'data' => $dataSet, 'bgColor' => $bgColor, 'bdColor' => $bdColor ]; } } elseif (!empty($options)) { // Charts for specific options foreach($options as $option) { $byVolume = isset($option['byVolume']) ? $option['byVolume'] : true; if ($byVolume) { $functionRepository = 'findCountByParams'; } else { $functionRepository = 'findSumByParams'; } $datas = $this->em->getRepository(Flux::class)->$functionRepository($option['filter']); list($monthsLabelNew, $dataSet) = $this->getDatasetAndLabelsFromData($datas, $groupBy); $mL = array_unique(array_merge($monthsLabelNew, $mL)); list($bgColor, $bdColor) = $this->getColorsFromText(isset($option['color'])?$option['color']:$colors[$colorCount], isset($option['opacity'])?$option['opacity']:1, isset($option['samecolor'])?$option['samecolor']:true); $colorCount++; $colorCount = $colorCount>7?0:$colorCount; $datasArray[] = [ 'label' => isset($option['label'])?$option['label']:'', 'data' => $dataSet, 'bgColor' => $bgColor, 'bdColor' => $bdColor ]; } } else { return null; } if ($type == Chart::TYPE_LINE || $type == Chart::TYPE_BAR) { $chart = $this->getMyChartForStats($mL, $datasArray, $type); } else { $chart = $this->getMyChartForStats($mL, $datasArray, Chart::TYPE_LINE); } return $chart; } /** * Get chart for all movement (transfert, transaction, CB and reconversion) * @param boolean $byVolume if true, show volume, if false show amount * @return ?Chart return the Chart object with datas and labels */ public function getChartForStatsMouvements($byVolume = true): ?Chart { if ($byVolume) { $functionRepository = 'findSumByParams'; } else { $functionRepository = 'findCountByParams'; } $datas = $this->em->getRepository(Flux::class)->$functionRepository(['parenttype' => Flux::TYPE_TRANSFERT]); list($monthsLabel, $dataSet) = $this->getDatasetAndLabelsFromData($datas); list($bgColor, $bdColor) = $this->getColorsFromText('green'); $datasTransac = $this->em->getRepository(Flux::class)->$functionRepository(['parenttype' => Flux::TYPE_TRANSACTION]); list($monthsLabelTransac, $dataSetTransac) = $this->getDatasetAndLabelsFromData($datasTransac); list($bgColorTransac, $bdColorTransac) = $this->getColorsFromText('orange'); $datasCB = $this->em->getRepository(Flux::class)->$functionRepository(['moyen' => MoyenEnum::MOYEN_CB]); list($monthsLabelCB, $dataSetCB) = $this->getDatasetAndLabelsFromData($datasCB); list($bgColorCB, $bdColorCB) = $this->getColorsFromText('violet'); $dataReconv = $this->em->getRepository(Flux::class)->$functionRepository(['parenttype' => Flux::TYPE_RECONVERSION]); list($monthsLabelReconv, $dataSetReconv) = $this->getDatasetAndLabelsFromData($dataReconv); list($bgColorReconv, $bdColorReconv) = $this->getColorsFromText('red'); $mL = array_unique(array_merge($monthsLabelTransac, $monthsLabel, $monthsLabelCB, $monthsLabelReconv)); $chart = $this->getMyChartForStats($mL, [ [ 'label' => 'Virements', 'data' => $dataSet, 'bgColor' => $bgColor, 'bdColor' => $bdColor ], [ 'label' => 'Transactions', 'data' => $dataSetTransac, 'bgColor' => $bgColorTransac, 'bdColor' => $bdColorTransac ], [ 'label' => 'Carte Bancaire', 'data' => $dataSetCB, 'bgColor' => $bgColorCB, 'bdColor' => $bdColorCB ], [ 'label' => 'Reconversion', 'data' => $dataSetReconv, 'bgColor' => $bgColorReconv, 'bdColor' => $bdColorReconv ], ]); return $chart; } /** * Get chart for balance of all groupes * @return ?Chart return the Chart object with datas and labels */ public function getChartForBalancesGroupe(): ?Chart { $datas = $this->em->getRepository(AccountGroupe::class)->findAllBalancesByName(CurrencyEnum::CURRENCY_MLC); $datasNew = []; $labelsNew = []; $colorStartBlue = 255; foreach ($datas as $data) { if ($data['balance'] > 0) { $labelsNew[] = $data['name']; $datasNew[] = [ 'label' => $data['name'], 'data' => $data['balance'], 'bgColor' => 'rgba(54, 162, '.$colorStartBlue.', 0.2)', 'bdColor' => 'rgba(54, 162, '.$colorStartBlue.')', ]; $colorStartBlue = $colorStartBlue-30; } } return $this->getMyChartForStats($labelsNew, $datasNew, Chart::TYPE_PIE); } /** * Get chart for balance of all "comptoir" (restricted if you are a groupe manager to your "comptoir") * @return ?Chart return the Chart object with datas and labels */ public function getChartForBalancesComptoir(): ?Chart { if (!empty($this->session->get('_groupegere'))) { $datas = $this->em->getRepository(AccountComptoir::class)->findAllBalancesByName(CurrencyEnum::CURRENCY_MLC, $this->session->get('_groupegere')); } else { $datas = $this->em->getRepository(AccountComptoir::class)->findAllBalancesByName(CurrencyEnum::CURRENCY_MLC); } $datasNew = []; $labelsNew = []; $colorStartBlue = 255; foreach ($datas as $data) { if ($data['balance'] > 0) { $labelsNew[] = $data['name']; $datasNew[] = [ 'label' => $data['name'], 'data' => $data['balance'], 'bgColor' => 'rgba(124, 122, '.$colorStartBlue.', 0.2)', 'bdColor' => 'rgba(124, 122, '.$colorStartBlue.')', ]; $colorStartBlue = $colorStartBlue-15; } } return $this->getMyChartForStats($labelsNew, $datasNew, Chart::TYPE_PIE); } /** * Get chart for stats for number of new adherent and/or new prestataire by months * @param string $groupByType By 'year', 'month', 'day' * @param bool|boolean $withPresta With prestataires * @param bool|boolean $withAdh With adherents * @return ?Chart return the Chart object with datas and labels */ public function getChartForStats(?string $groupByType = 'month', ?bool $withPresta = true, ?bool $withAdh = true): ?Chart { $groupe = null; $datasArray = []; $mL = []; // if (!empty($this->session->get('_groupegere'))) { // $groupe = $this->session->get('_groupegere'); // $groupe = $this->em->getRepository(Groupe::class)->findOneById($groupe->getId()); // } if ($withPresta) { $datas = $this->em->getRepository(Prestataire::class)->findCountBy($groupByType, $groupe); list($monthsLabel, $dataSet) = $this->getDatasetAndLabelsFromData($datas, $groupByType); list($bgColor, $bdColor) = $this->getColorsFromText('red'); $mL = $monthsLabel; $datasArray[] = [ 'label' => 'Prestataires', 'data' => $dataSet, 'bgColor' => $bgColor, 'bdColor' => $bdColor ]; } if ($withAdh) { $datasAdh = $this->em->getRepository(Adherent::class)->findCountBy($groupByType, $groupe); list($monthsLabelAdh, $dataSetAdh) = $this->getDatasetAndLabelsFromData($datasAdh, $groupByType); list($bgColorAdh, $bdColorAdh) = $this->getColorsFromText('blue'); if (!$withPresta) { $mL = $monthsLabelAdh; } else { $mL = array_unique(array_merge($monthsLabel, $monthsLabelAdh)); } $datasArray[] = [ 'label' => 'Adhérents', 'data' => $dataSetAdh, 'bgColor' => $bgColorAdh, 'bdColor' => $bdColorAdh ]; } return $this->getMyChartForStats($mL, $datasArray); } /** * Get dataset and labels for chart by months * @param array $datas array of datas * @param string $groupByType 'year' 'month' or 'day' * @param bool|boolean $withAddition with addition of value or no, default false * @return array (label, dataset) */ private function getDatasetAndLabelsFromData(array $datas, string $groupByType = 'month', bool $withAddition = false): array { $monthsLabel = array_values(array_column($datas, $groupByType)); if ($groupByType == 'month') { $monthsLabel = array_map(function($v){ $monthFr = ['Jan', 'Fév', 'Mars', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sep', 'Oct', 'Nov', 'Déc']; return $monthFr[substr($v, 5, strlen($v)-5)-1] . ' ' . substr($v, 0, 4); } , $monthsLabel); } $months = array_column($datas, 'total'); $dataSet = []; if ($withAddition) { $currentValue = 0; foreach ($months as $month) { $currentValue += $month; $dataSet[] = $currentValue; } } else { $dataSet = array_values($months); } return [$monthsLabel, $dataSet]; } /** * Get colors from text for chart * @param string $txt Can be 'red', 'orange', 'yellow', 'green', 'blue', 'violet', 'grey' * @param float $opacity Opacity of the color * @param boolean $sameColor If true, same color apply for background and border, if false, background opacity is 0.2 * * @return array [color background, color border] */ private function getColorsFromText(string $txt, float $opacity = 1, $sameColor = true): array { switch($txt) { case 'red': return ['rgba(255, 99, 132, '.($sameColor?$opacity:'0.2').')', 'rgba(255, 99, 132, '.$opacity.')']; case 'orange': return ['rgba(255, 159, 64, '.($sameColor?$opacity:'0.2').')', 'rgba(255, 159, 64, '.$opacity.')']; case 'yellow': return ['rgba(255, 205, 86, '.($sameColor?$opacity:'0.2').')', 'rgba(255, 205, 86, '.$opacity.')']; case 'green': return ['rgba(75, 192, 192, '.($sameColor?$opacity:'0.2').')', 'rgba(75, 192, 192, '.$opacity.')']; case 'blue': return ['rgba(54, 162, 235, '.($sameColor?$opacity:'0.2').')', 'rgba(54, 162, 235, '.$opacity.')']; case 'violet': return ['rgba(153, 102, 255, '.($sameColor?$opacity:'0.2').')', 'rgba(153, 102, 255, '.$opacity.')']; case 'pink': return ['rgba(232, 62, 140, '.($sameColor?$opacity:'0.2').')', 'rgba(232, 62, 140, '.$opacity.')']; case 'grey': default: return ['rgba(201, 203, 207, '.($sameColor?$opacity:'0.2').')', 'rgba(201, 203, 207, '.$opacity.')']; } } /** * @param array labels * @param array datas * @param string Type of chart from Chart::TYPE_*** * @return ?Chart Chart object */ private function getMyChartForStats(array $labels, array $datas, string $type = Chart::TYPE_BAR): ?Chart { $datasets = []; if ($type == Chart::TYPE_PIE) { $datasets = [[ 'label' => '', 'data' => array_map(function($val) { return $val['data']; }, $datas), 'backgroundColor' => array_map(function($val) { return $val['bgColor']; }, $datas), 'borderColor' => array_map(function($val) { return $val['bdColor']; }, $datas), 'hoverOffset' => 4 ]]; } foreach ($datas as $data) { if ($type == Chart::TYPE_BAR) { $datasets[] = [ 'label' => isset($data['label'])?$data['label']:'', 'barPercentage' => 0.5, 'barThickness' => 6, 'maxBarThickness' => 8, 'minBarLength' => 2, 'data' => isset($data['data'])?$data['data']:[], 'backgroundColor' => [ isset($data['bgColor'])?$data['bgColor']:'rgba(201, 203, 207, 0.2)', ], 'borderColor' => [ isset($data['bdColor'])?$data['bdColor']:'rgb(201, 203, 207)' ], 'borderWidth' => 1 ]; } else if ($type == Chart::TYPE_LINE) { $datasets[] = [ 'label' => isset($data['label'])?$data['label']:'', 'data' => isset($data['data'])?$data['data']:[], 'backgroundColor' => [ isset($data['bgColor'])?$data['bgColor']:'rgba(201, 203, 207, 0.2)', ], 'borderColor' => [ isset($data['bdColor'])?$data['bdColor']:'rgb(201, 203, 207)' ], 'fill' => false, 'tension' => 0.4, ]; } } $chart = $this->chartBuilder->createChart($type); $chart->setData([ 'labels' => array_values($labels), 'datasets' => $datasets, ]); if ($type == Chart::TYPE_PIE) { $chart->setOptions([ 'aspectRatio' => 1.5, ]); } else if ($type == Chart::TYPE_BAR) { $chart->setOptions([ // 'scales' => [ // 'x' => [ // ['grid' => ['lineWidth' => 2, 'offset' => true]], // ], // ], 'aspectRatio' => 1.5, ]); } else if ($type == Chart::TYPE_LINE) { $chart->setOptions([ 'aspectRatio' => 1.5, ]); } return $chart; } }