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:
maTh
2021-10-16 12:00:07 +02:00
committed by GitHub
parent ebf9c70ebd
commit 02641de32e
17 changed files with 403 additions and 243 deletions
+1 -2
View File
@@ -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
View File
@@ -235,10 +235,9 @@ et [lAPI 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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>
-29
View File
File diff suppressed because one or more lines are too long
-75
View File
@@ -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
-67
View File
@@ -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
View File
@@ -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
+13
View File
File diff suppressed because one or more lines are too long
-4
View File
@@ -1057,10 +1057,6 @@ br {
height: 300px;
}
.stat .flotr-legend-label {
padding-left: 0;
}
/*=== LOGIN VIEW */
/*================*/
.formLogin .header > .item {
-4
View File
@@ -1057,10 +1057,6 @@ br {
height: 300px;
}
.stat .flotr-legend-label {
padding-right: 0;
}
/*=== LOGIN VIEW */
/*================*/
.formLogin .header > .item {
+1
View File
@@ -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"/>