Przeglądaj źródła

Stats: replace flotr2 with chart.js (#3858)

* include Chart.js

* page: main statistics. Flotr.js replaced with Chart.js

* main stats + repartition

* Delete: repartition.js + stats.js

* delete flotr2

* add libs in README

* polish

* code polish

* fixed amount of week days and months

* added manget link for LibreJS

* added: @license-end

* phpcbf + jshint formatting

* delete old code

* fix stats

* fix Comments

* finally I found the issue and fixed its best

* fix the month stats

* Whitespace fixes

* Remove flotr2

* Rename to chart.min.js

* Remove console.log

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
maTh 4 lat temu
rodzic
commit
02641de32e

+ 1 - 2
.jshintignore

@@ -1,4 +1,3 @@
 node_modules
 p/scripts/bcrypt.min.js
-p/scripts/flotr2.min.js
-p/scripts/jquery.min.js
+p/scripts/vendor/

+ 1 - 2
README.fr.md

@@ -235,10 +235,9 @@ et [l’API Fever](https://freshrss.github.io/FreshRSS/fr/users/06_Fever_API.htm
 * [SimplePie](https://simplepie.org/)
 * [MINZ](https://github.com/marienfressinaud/MINZ)
 * [php-http-304](https://alexandre.alapetite.fr/doc-alex/php-http-304/)
-* [jQuery](https://jquery.com/)
 * [lib_opml](https://github.com/marienfressinaud/lib_opml)
-* [flotr2](http://www.humblesoftware.com/flotr2)
 * [PHPMailer](https://github.com/PHPMailer/PHPMailer)
+* [Chart.js](https://www.chartjs.org)
 
 ## Uniquement pour certaines options ou configurations
 

+ 1 - 2
README.md

@@ -135,10 +135,9 @@ and [Fever API](https://freshrss.github.io/FreshRSS/en/users/06_Fever_API.html)
 * [SimplePie](https://simplepie.org/)
 * [MINZ](https://github.com/marienfressinaud/MINZ)
 * [php-http-304](https://alexandre.alapetite.fr/doc-alex/php-http-304/)
-* [jQuery](https://jquery.com/)
 * [lib_opml](https://github.com/marienfressinaud/lib_opml)
-* [flotr2](http://www.humblesoftware.com/flotr2)
 * [PHPMailer](https://github.com/PHPMailer/PHPMailer)
+* [Chart.js](https://www.chartjs.org)
 
 ## Only for some options or configurations
 

+ 58 - 24
app/Controllers/statsController.php

@@ -49,22 +49,43 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
 	 *
 	 * It displays the statistic main page.
 	 * The values computed to display the page are:
-	 *   - repartition of read/unread/favorite/not favorite
-	 *   - number of article per day
-	 *   - number of feed by category
-	 *   - number of article by category
-	 *   - list of most prolific feed
+	 *   - repartition of read/unread/favorite/not favorite (repartition)
+	 *   - number of article per day (entryCount)
+	 *   - number of feed by category (feedByCategory)
+	 *   - number of article by category (entryByCategory)
+	 *   - list of most prolific feed (topFeed)
 	 */
 	public function indexAction() {
 		$statsDAO = FreshRSS_Factory::createStatsDAO();
-		Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
+		Minz_View::appendScript(Minz_Url::display('/scripts/vendor/chart.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/vendor/chart.min.js')));
+
 		$this->view->repartition = $statsDAO->calculateEntryRepartition();
+
 		$entryCount = $statsDAO->calculateEntryCount();
-		$this->view->count = $this->convertToSerie($entryCount);
+		$this->view->entryCount = $entryCount;
 		$this->view->average = round(array_sum(array_values($entryCount)) / count($entryCount), 2);
-		$this->view->feedByCategory = $this->convertToPieSerie($statsDAO->calculateFeedByCategory());
-		$this->view->entryByCategory = $this->convertToPieSerie($statsDAO->calculateEntryByCategory());
+
+		$feedByCategory_calculated = $statsDAO->calculateFeedByCategory();
+		for ($i = 0; $i < count($feedByCategory_calculated); $i++) {
+			$feedByCategory['label'][$i] 	= $feedByCategory_calculated[$i]['label'];
+			$feedByCategory['data'][$i] 	= $feedByCategory_calculated[$i]['data'];
+		}
+		$this->view->feedByCategory = $feedByCategory;
+
+		$entryByCategory_calculated = $statsDAO->calculateEntryByCategory();
+		for ($i = 0; $i < count($entryByCategory_calculated); $i++) {
+			$entryByCategory['label'][$i] 	= $entryByCategory_calculated[$i]['label'];
+			$entryByCategory['data'][$i] 	= $entryByCategory_calculated[$i]['data'];
+		}
+		$this->view->entryByCategory = $entryByCategory;
+
 		$this->view->topFeed = $statsDAO->calculateTopFeed();
+
+		for ($i = 0; $i < 30; $i++) {
+			$last30DaysLabels[$i] = date('d.m.Y', strtotime((-30 + $i) . ' days'));
+		}
+
+		$this->view->last30DaysLabels = $last30DaysLabels;
 	}
 
 	/**
@@ -153,21 +174,34 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
 	 *       for the average.
 	 */
 	public function repartitionAction() {
-		$statsDAO = FreshRSS_Factory::createStatsDAO();
-		$categoryDAO = FreshRSS_Factory::createCategoryDao();
-		$feedDAO = FreshRSS_Factory::createFeedDao();
-		Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
+		$statsDAO 		= FreshRSS_Factory::createStatsDAO();
+		$categoryDAO 	= FreshRSS_Factory::createCategoryDao();
+		$feedDAO 		= FreshRSS_Factory::createFeedDao();
+
+		Minz_View::appendScript(Minz_Url::display('/scripts/vendor/chart.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/vendor/chart.min.js')));
+
 		$id = Minz_Request::param('id', null);
-		$this->view->categories = $categoryDAO->listCategories();
-		$this->view->feed = $feedDAO->searchById($id);
-		$this->view->days = $statsDAO->getDays();
-		$this->view->months = $statsDAO->getMonths();
-		$this->view->repartition = $statsDAO->calculateEntryRepartitionPerFeed($id);
-		$this->view->repartitionHour = $this->convertToSerie($statsDAO->calculateEntryRepartitionPerFeedPerHour($id));
-		$this->view->averageHour = $statsDAO->calculateEntryAveragePerFeedPerHour($id);
-		$this->view->repartitionDayOfWeek = $this->convertToSerie($statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id));
-		$this->view->averageDayOfWeek = $statsDAO->calculateEntryAveragePerFeedPerDayOfWeek($id);
-		$this->view->repartitionMonth = $this->convertToSerie($statsDAO->calculateEntryRepartitionPerFeedPerMonth($id));
-		$this->view->averageMonth = $statsDAO->calculateEntryAveragePerFeedPerMonth($id);
+
+		$this->view->categories 	= $categoryDAO->listCategories();
+		$this->view->feed 			= $feedDAO->searchById($id);
+		$this->view->days 			= $statsDAO->getDays();
+		$this->view->months 		= $statsDAO->getMonths();
+
+		$this->view->repartition 			= $statsDAO->calculateEntryRepartitionPerFeed($id);
+
+		$this->view->repartitionHour 		= $statsDAO->calculateEntryRepartitionPerFeedPerHour($id);
+		$this->view->averageHour 			= $statsDAO->calculateEntryAveragePerFeedPerHour($id);
+
+		$this->view->repartitionDayOfWeek 	= $statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id);
+		$this->view->averageDayOfWeek 		= $statsDAO->calculateEntryAveragePerFeedPerDayOfWeek($id);
+
+		$this->view->repartitionMonth 		= $statsDAO->calculateEntryRepartitionPerFeedPerMonth($id);
+		$this->view->averageMonth 			= $statsDAO->calculateEntryAveragePerFeedPerMonth($id);
+
+		for ($i = 0; $i < 24; $i++) {
+			$hours24Labels[$i] = $i . ':xx';
+		}
+
+		$this->view->hours24Labels = $hours24Labels;
 	}
 }

+ 24 - 6
app/Models/StatsDAO.php

@@ -98,7 +98,7 @@ SQL;
 	 * Calculates the number of article per hour of the day per feed
 	 *
 	 * @param integer $feed id
-	 * @return string
+	 * @return array
 	 */
 	public function calculateEntryRepartitionPerFeedPerHour($feed = null) {
 		return $this->calculateEntryRepartitionPerFeedPerPeriod('%H', $feed);
@@ -108,7 +108,7 @@ SQL;
 	 * Calculates the number of article per day of week per feed
 	 *
 	 * @param integer $feed id
-	 * @return string
+	 * @return array
 	 */
 	public function calculateEntryRepartitionPerFeedPerDayOfWeek($feed = null) {
 		return $this->calculateEntryRepartitionPerFeedPerPeriod('%w', $feed);
@@ -118,18 +118,22 @@ SQL;
 	 * Calculates the number of article per month per feed
 	 *
 	 * @param integer $feed
-	 * @return string
+	 * @return array
 	 */
 	public function calculateEntryRepartitionPerFeedPerMonth($feed = null) {
-		return $this->calculateEntryRepartitionPerFeedPerPeriod('%m', $feed);
+		$monthRepartition = $this->calculateEntryRepartitionPerFeedPerPeriod('%m', $feed);
+		// cut out the 0th month (Jan=1, Dec=12)
+		\array_splice($monthRepartition, 0, 1);
+		return $monthRepartition;
 	}
 
+
 	/**
 	 * Calculates the number of article per period per feed
 	 *
 	 * @param string $period format string to use for grouping
 	 * @param integer $feed id
-	 * @return string
+	 * @return array
 	 */
 	protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
 		$restrict = '';
@@ -148,7 +152,21 @@ SQL;
 		$stm = $this->pdo->query($sql);
 		$res = $stm->fetchAll(PDO::FETCH_NAMED);
 
-		$repartition = array();
+		switch ($period) {
+			case '%H':
+				$periodMax = 24;
+				break;
+			case '%w':
+				$periodMax = 7;
+				break;
+			case '%m':
+				$periodMax = 12;
+				break;
+			default:
+			$periodMax = 30;
+		}
+
+		$repartition = array_fill(0, $periodMax, 0);
 		foreach ($res as $value) {
 			$repartition[(int) $value['period']] = (int) $value['count'];
 		}

+ 15 - 0
app/Models/StatsDAOPGSQL.php

@@ -56,6 +56,21 @@ SQL;
 		$stm = $this->pdo->query($sql);
 		$res = $stm->fetchAll(PDO::FETCH_NAMED);
 
+		switch ($period) {
+			case 'hour':
+				$periodMax = 24;
+				break;
+			case 'day':
+				$periodMax = 7;
+				break;
+			case 'month':
+				$periodMax = 12;
+				break;
+			default:
+			$periodMax = 30;
+		}
+
+		$repartition = array_fill(0, $periodMax, 0);
 		foreach ($res as $value) {
 			$repartition[(int) $value['period']] = (int) $value['count'];
 		}

+ 15 - 1
app/Models/StatsDAOSQLite.php

@@ -24,7 +24,21 @@ SQL;
 		$stm = $this->pdo->query($sql);
 		$res = $stm->fetchAll(PDO::FETCH_NAMED);
 
-		$repartition = array();
+		switch ($period) {
+			case '%H':
+				$periodMax = 24;
+				break;
+			case '%w':
+				$periodMax = 7;
+				break;
+			case '%m':
+				$periodMax = 12;
+				break;
+			default:
+			$periodMax = 30;
+		}
+
+		$repartition = array_fill(0, $periodMax, 0);
 		foreach ($res as $value) {
 			$repartition[(int) $value['period']] = (int) $value['count'];
 		}

+ 40 - 14
app/views/stats/index.phtml

@@ -69,29 +69,55 @@
 
 		<div class="stat">
 			<h2><?= _t('admin.stats.entry_per_day') ?></h2>
-			<div id="statsEntryPerDay" class="statGraph"></div>
+			<div>
+				<canvas id="statsEntriesPerDay"></canvas>
+				<script class="jsonData-stats" type="application/json">
+				<?php
+				echo json_encode(array(
+					'canvasID' 		=> 'statsEntriesPerDay',
+					'charttype' 	=> 'barWithAverage',
+					'labelBarChart' => _t('admin.stats.entry_count'),
+					'dataBarChart' 	=> $this->entryCount,
+					'labelAverage' 	=> 'Average ('.$this->average.')',
+					'dataAverage' 	=> $this->average,
+					'xAxisLabels' 	=> $this->last30DaysLabels,
+				), JSON_UNESCAPED_UNICODE);
+				?></script>
+			</div>
 		</div>
 
 		<div class="stat half">
 			<h2><?= _t('admin.stats.feed_per_category') ?></h2>
-			<div id="statsFeedPerCategory" class="statGraph"></div>
-			<div id="statsFeedPerCategoryLegend"></div>
+			<div>
+				<canvas id="statsFeedsPerCategory"></canvas>
+				<script class="jsonData-stats" type="application/json">
+				<?php
+				echo json_encode(array(
+					'canvasID' 		=> 'statsFeedsPerCategory',
+					'charttype' 	=> 'doughnut',
+					'data' 			=> $this->feedByCategory['data'],
+					'labels' 		=> $this->feedByCategory['label'],
+				), JSON_UNESCAPED_UNICODE);
+				?></script>
+			</div>
 		</div>
 
 		<div class="stat half">
 			<h2><?= _t('admin.stats.entry_per_category') ?></h2>
-			<div id="statsEntryPerCategory" class="statGraph"></div>
-			<div id="statsEntryPerCategoryLegend"></div>
+			<div>
+				<canvas id="statsEntriesPerCategory"></canvas>
+				<script class="jsonData-stats" type="application/json">
+				<?php
+				echo json_encode(array(
+					'canvasID' 		=> 'statsEntriesPerCategory',
+					'charttype' 	=> 'doughnut',
+					'data' 			=> $this->entryByCategory['data'],
+					'labels' 		=> $this->entryByCategory['label'],
+				), JSON_UNESCAPED_UNICODE);
+				?></script>
+			</div>
 		</div>
 	</div>
 </div>
 
-<script id="jsonStats" type="application/json"><?php
-echo json_encode(array(
-	'average' => $this->average,
-	'dataCount' => $this->count,
-	'feedByCategory' => $this->feedByCategory,
-	'entryByCategory' => $this->entryByCategory,
-), JSON_UNESCAPED_UNICODE);
-?></script>
-<script src="../scripts/stats.js?<?= @filemtime(PUBLIC_PATH . '/scripts/stats.js') ?>"></script>
+<script src="../scripts/statsWithChartjs.js?<?= @filemtime(PUBLIC_PATH . '/scripts/statsWithChartjs.js') ?>"></script>

+ 40 - 13
app/views/stats/repartition.phtml

@@ -53,28 +53,55 @@
 
 		<div class="stat">
 			<h2><?= _t('admin.stats.entry_per_hour', $this->averageHour) ?></h2>
-			<div id="statsEntryPerHour" class="statGraph"></div>
+			<div>
+				<canvas id="statsEntriesPerHour"></canvas>
+				<script class="jsonData-stats" type="application/json">
+				<?php
+				echo json_encode(array(
+					'canvasID' 		=> 'statsEntriesPerHour',
+					'charttype' 	=> 'bar',
+					'data' 			=> $this->repartitionHour,
+					'label' 		=> _t('admin.stats.entry_count'),
+					'xAxisLabels' 	=> $this->hours24Labels
+				), JSON_UNESCAPED_UNICODE);
+				?></script>
+			</div>
 		</div>
 
 		<div class="stat half">
 			<h2><?= _t('admin.stats.entry_per_day_of_week', $this->averageDayOfWeek) ?></h2>
-			<div id="statsEntryPerDayOfWeek" class="statGraph"></div>
+			<div>
+				<canvas id="statsEntriesPerDayOfWeek"></canvas>
+				<script class="jsonData-stats" type="application/json">
+				<?php
+				echo json_encode(array(
+					'canvasID' 		=> 'statsEntriesPerDayOfWeek',
+					'charttype' 	=> 'bar',
+					'data' 			=> $this->repartitionDayOfWeek,
+					'label' 		=> _t('admin.stats.entry_count'),
+					'xAxisLabels' 	=> $this->days,
+				), JSON_UNESCAPED_UNICODE);
+				?></script>
+			</div>
 		</div>
 
 		<div class="stat half">
 			<h2><?= _t('admin.stats.entry_per_month', $this->averageMonth) ?></h2>
-			<div id="statsEntryPerMonth" class="statGraph"></div>
+			<div>
+				<canvas id="statsEntriesPerMonth"></canvas>
+				<script class="jsonData-stats" type="application/json">
+				<?php
+				echo json_encode(array(
+					'canvasID' 		=> 'statsEntriesPerMonth',
+					'charttype' 	=> 'bar',
+					'data' 			=> $this->repartitionMonth,
+					'label' 		=> _t('admin.stats.entry_count'),
+					'xAxisLabels' 	=> $this->months,
+				), JSON_UNESCAPED_UNICODE);
+				?></script>
+			</div>
 		</div>
 	</div>
 </div>
 
-<script id="jsonRepartition" type="application/json"><?php
-echo htmlspecialchars(json_encode(array(
-	'repartitionHour' => $this->repartitionHour,
-	'repartitionDayOfWeek' => $this->repartitionDayOfWeek,
-	'days' => $this->days,
-	'repartitionMonth' => $this->repartitionMonth,
-	'months' => $this->months,
-), JSON_UNESCAPED_UNICODE), ENT_NOQUOTES, 'UTF-8');
-?></script>
-<script src="../scripts/repartition.js?<?= @filemtime(PUBLIC_PATH . '/scripts/repartition.js') ?>"></script>
+<script src="../scripts/statsWithChartjs.js?<?= @filemtime(PUBLIC_PATH . '/scripts/statsWithChartjs.js') ?>"></script>

Plik diff jest za duży
+ 0 - 10
p/scripts/flotr2.min.js


+ 0 - 75
p/scripts/repartition.js

@@ -1,75 +0,0 @@
-// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
-"use strict";
-/* globals Flotr, numberFormat */
-/* jshint esversion:6, strict:global */
-
-function initStats() {
-	if (!window.Flotr) {
-		if (window.console) {
-			console.log('FreshRSS waiting for Flotr…');
-		}
-		window.setTimeout(initStats, 50);
-		return;
-	}
-	const jsonRepartition = document.getElementById('jsonRepartition'),
-		stats = JSON.parse(jsonRepartition.innerHTML);
-	// Entry per hour
-	Flotr.draw(document.getElementById('statsEntryPerHour'),
-		[{
-			data: stats.repartitionHour,
-			bars: {horizontal: false, show: true}
-		}],
-		{
-			grid: {verticalLines: false},
-			xaxis: {noTicks: 23,
-				tickFormatter: function(x1) {
-					return 1 + parseInt(x1);
-				},
-				min: -0.9,
-				max: 23.9,
-				tickDecimals: 0},
-			yaxis: {min: 0},
-			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
-		});
-	// Entry per day of week
-	Flotr.draw(document.getElementById('statsEntryPerDayOfWeek'),
-		[{
-			data: stats.repartitionDayOfWeek,
-			bars: {horizontal: false, show: true}
-		}],
-		{
-			grid: {verticalLines: false},
-			xaxis: {noTicks: 6,
-				tickFormatter: function(x2) {
-					return stats.days[parseInt(x2)];
-				},
-				min: -0.9,
-				max: 6.9,
-				tickDecimals: 0},
-			yaxis: {min: 0},
-			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
-		});
-	// Entry per month
-	Flotr.draw(document.getElementById('statsEntryPerMonth'),
-		[{
-			data: stats.repartitionMonth,
-			bars: {horizontal: false, show: true}
-		}],
-		{
-			grid: {verticalLines: false},
-			xaxis: {noTicks: 12,
-				tickFormatter: function(x3) {
-					return stats.months[parseInt(x3) - 1];
-				},
-				min: 0.1,
-				max: 12.9,
-				tickDecimals: 0},
-			yaxis: {min: 0},
-			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
-		});
-
-}
-initStats();
-
-window.addEventListener('resize', initStats);
-// @license-end

+ 0 - 67
p/scripts/stats.js

@@ -1,67 +0,0 @@
-// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
-"use strict";
-/* globals Flotr, numberFormat */
-/* jshint esversion:6, strict:global */
-
-function initStats() {
-	if (!window.Flotr) {
-		if (window.console) {
-			console.log('FreshRSS waiting for Flotr…');
-		}
-		window.setTimeout(initStats, 50);
-		return;
-	}
-	const jsonStats = document.getElementById('jsonStats'),
-		stats = JSON.parse(jsonStats.innerHTML);
-	// Entry per day
-	const avg = [];
-	for (let i = -31; i <= 0; i++) {
-		avg.push([i, stats.average]);
-	}
-	Flotr.draw(document.getElementById('statsEntryPerDay'),
-		[{
-			data: stats.dataCount,
-			bars: {horizontal: false, show: true}
-		},{
-			data: avg,
-			lines: {show: true},
-			label: stats.average,
-		}],
-		{
-			grid: {verticalLines: false},
-			xaxis: {noTicks: 6, showLabels: false, tickDecimals: 0, min: -30.75, max: -0.25},
-			yaxis: {min: 0},
-			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function (obj) { return numberFormat(obj.y); }},
-		});
-	// Feed per category
-	Flotr.draw(document.getElementById('statsFeedPerCategory'),
-		stats.feedByCategory,
-		{
-			grid: {verticalLines: false, horizontalLines: false},
-			pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
-			xaxis: {showLabels: false},
-			yaxis: {showLabels: false},
-			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function (obj) {
-				return obj.series.label + ' - ' + numberFormat(obj.y) + ' (' + (obj.fraction * 100).toFixed(1) + '%)';
-			}},
-			legend: {container: document.getElementById('statsFeedPerCategoryLegend'), noColumns: 3}
-		});
-	// Entry per category
-	Flotr.draw(document.getElementById('statsEntryPerCategory'),
-		stats.entryByCategory,
-		{
-			grid: {verticalLines: false, horizontalLines: false},
-			pie: {explode: 10, show: true, labelFormatter: function () { return ''; }},
-			xaxis: {showLabels: false},
-			yaxis: {showLabels: false},
-			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function (obj) {
-				return obj.series.label + ' - ' + numberFormat(obj.y) + ' (' + (obj.fraction * 100).toFixed(1) + '%)';
-			}},
-			legend: {container: document.getElementById('statsEntryPerCategoryLegend'), noColumns: 3}
-		});
-}
-initStats();
-
-window.addEventListener('resize', initStats);
-
-// @license-end

+ 194 - 0
p/scripts/statsWithChartjs.js

@@ -0,0 +1,194 @@
+// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
+"use strict";
+/* globals Chart */
+/* jshint esversion:6, strict:global */
+
+function initCharts() {
+	if (!window.Chart) {
+		if (window.console) {
+			console.log('FreshRSS is waiting for Chart.js...');
+		}
+		window.setTimeout(initCharts, 25);
+		return;
+	}
+
+	const jsonData = document.getElementsByClassName('jsonData-stats');
+
+	var jsonDataParsed;
+	var chartConfig;
+
+	for (var i = 0; i < jsonData.length; i++) {
+		jsonDataParsed = JSON.parse(jsonData[i].innerHTML);
+
+		switch(jsonDataParsed.charttype) {
+			case 'bar':
+				chartConfig = jsonChartBar(jsonDataParsed.label, jsonDataParsed.data, jsonDataParsed.xAxisLabels);
+				break;
+			case 'doughnut':
+				chartConfig = jsonChartDoughnut(jsonDataParsed.labels, jsonDataParsed.data);
+				break;
+			case 'barWithAverage':
+				chartConfig = jsonChartBarWithAvarage(jsonDataParsed.labelBarChart, jsonDataParsed.dataBarChart, jsonDataParsed.labelAverage, jsonDataParsed.dataAverage, jsonDataParsed.xAxisLabels);
+		}
+
+		new Chart(
+			document.getElementById(jsonDataParsed.canvasID),
+			chartConfig
+		);
+	}
+
+	if (window.console) {
+		console.log('Chart.js finished');
+	}
+}
+
+function jsonChartBar(label, data, xAxisLabels = '') {
+	return {
+		type: 'bar',
+		data: {
+			labels: xAxisLabels,
+			datasets: [{
+				label: label,
+				backgroundColor: '#0062BD',
+				borderColor: '#0062BD',
+				data: data,
+				barPercentage: 1.0,
+				categoryPercentage: 1.0,
+				order: 2,
+			}]
+		},
+		options: {
+			scales: {
+				y: {
+					beginAtZero: true
+				},
+				x: {
+					grid: {
+						display: false,
+					}
+				}
+			},
+			plugins: {
+				legend: {
+					display: false,
+				}
+			}
+		}
+	};
+}
+
+function jsonChartDoughnut(labels, data) {
+	return {
+		type: 'doughnut',
+		data: {
+			labels: labels,
+			datasets: [{
+				backgroundColor: [
+					'#0b84a5',  //petrol
+					'#f6c85f', // sand
+					'#6f4e7c', //purple
+					'#9dd866', //green
+					'#ca472f', //red
+					'#ffa056', //orange
+					'#8dddd0', // turkis
+					'#f6c85f', // sand
+					'#6f4e7c', //purple
+					'#9dd866', //green
+					'#ca472f', //red
+					'#ffa056', //orange
+					'#8dddd0', // turkis
+				],
+				data: data,
+			}]
+		},
+		options: {
+			layout: {
+				padding: 20,
+			},
+			plugins: {
+				legend: {
+					position: 'bottom',
+					align: 'start',
+				}
+			}
+		}
+	};
+}
+
+function jsonChartBarWithAvarage(labelBarChart, dataBarChart, labelAverage, dataAverage, xAxisLabels = '') {
+	return {
+		type: 'bar',
+		data: {
+			datasets: [
+				{
+					// bar chart layout
+					label: labelBarChart,
+					backgroundColor: '#0062BD',
+					borderColor: '#0062BD',
+					data: dataBarChart,
+					barPercentage: 1.0,
+					categoryPercentage: 1.0,
+					order: 2,
+				},
+				{
+					// average line chart
+					type: 'line',
+					label: labelAverage,  // Todo: i18n
+					borderColor: 'rgb(192,216,0)',
+					data: {
+						'-30' : dataAverage,
+						'-1' : dataAverage,
+					},
+					order: 1,
+				}
+			]
+		},
+
+		options: {
+			scales: {
+				y: {
+					beginAtZero: true,
+				},
+				x: {
+					ticks: {
+						callback: function(val){
+							if (xAxisLabels.length > 0) {
+								return xAxisLabels[val];
+							} else {
+								return val;
+							}
+						}
+					},
+					grid: {
+						display: false,
+					}
+				}
+			},
+			elements: {
+				point: {
+					radius: 0,
+				}
+			},
+			plugins: {
+				tooltip: {
+					callbacks: {
+						title: function(tooltipitem) {
+							if (xAxisLabels.length > 0) {
+								return xAxisLabels[tooltipitem[0].dataIndex];
+							} else {
+								return tooltipitem[0].label;
+							}
+						}
+					}
+				},
+				legend: {
+					display: false,
+				}
+			}
+		}
+	};
+}
+
+initCharts();
+
+// @license-end

Plik diff jest za duży
+ 6 - 0
p/scripts/vendor/chart.min.js


+ 0 - 4
p/themes/base-theme/template.css

@@ -1057,10 +1057,6 @@ br {
 	height: 300px;
 }
 
-.stat .flotr-legend-label {
-	padding-left: 0;
-}
-
 /*=== LOGIN VIEW */
 /*================*/
 .formLogin .header > .item {

+ 0 - 4
p/themes/base-theme/template.rtl.css

@@ -1057,10 +1057,6 @@ br {
 	height: 300px;
 }
 
-.stat .flotr-legend-label {
-	padding-right: 0;
-}
-
 /*=== LOGIN VIEW */
 /*================*/
 .formLogin .header > .item {

+ 1 - 0
phpcs.xml

@@ -11,6 +11,7 @@
 	<exclude-pattern>./data/config.php</exclude-pattern>
 	<exclude-pattern>./data/users/*/config.php</exclude-pattern>
 	<exclude-pattern>./extensions/</exclude-pattern>
+	<exclude-pattern>./p/scripts/vendor/</exclude-pattern>
 	<exclude-pattern>*.min.js$</exclude-pattern>
 	<!-- Duplicate class names are not allowed -->
 	<rule ref="Generic.Classes.DuplicateClassName"/>

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików