mirror of
https://github.com/FreshRSS/FreshRSS.git
synced 2026-05-20 09:30:36 +00:00
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>
This commit is contained in:
+1
-2
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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'];
|
||||
}
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Vendored
-29
File diff suppressed because one or more lines are too long
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Vendored
+13
File diff suppressed because one or more lines are too long
@@ -1057,10 +1057,6 @@ br {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.stat .flotr-legend-label {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/*=== LOGIN VIEW */
|
||||
/*================*/
|
||||
.formLogin .header > .item {
|
||||
|
||||
@@ -1057,10 +1057,6 @@ br {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.stat .flotr-legend-label {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
/*=== LOGIN VIEW */
|
||||
/*================*/
|
||||
.formLogin .header > .item {
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user