$this->setLanguage(app()->getLocale());
$this->viewData = new Data(['fluidbook_id' => $fluidbook->id, 'hash' => $this->hash, 'title' => $fluidbook->title, 'page_count' => $fluidbook->getPagesNumber(), 'base_URL' => route('stats', ['fluidbook_id' => $this->fluidbook_id, 'hash' => $this->hash])]);
- parent::__construct('https://' . static::getMatomoServer($this->fluidbook->id) . '/', null, $this->fluidbook_id);
+ parent::__construct('https://' . static::getMatomoServer($this->fluidbook->id, $this->fluidbook->region) . '/', null, $this->fluidbook_id);
}
public function getToken(): string
{
- return $this->getMatomoToken(static::getMatomoServer($this->fluidbook->id));
+ return $this->getMatomoToken(static::getMatomoServer($this->fluidbook->id, $this->fluidbook->region));
}
* @param $id int
* @return string
*/
- public static function getMatomoServer($id): string
+ public static function getMatomoServer($id, $region = 'EU'): string
{
// Get the appropriate server based on the Fluidbook ID
// Stats are split across different servers depending on the ID:
// ID < 21210 = stats3.fluidbook.com
// ID >= 21210 (even numbers) = stats4.fluidbook.com
// ID >= 21211 (odd numbers) = stats5.fluidbook.com
+ if ($region === 'US') {
+ return 'stats6.fluidbook.com';
+ }
$id = intval($id);
if ($id < 21210) {
return 'stats3.fluidbook.com';
'data' => $this->_pagesByPeriod->pluck('nb_visits')->toArray(),
'order' => 1,
'bar_offset' => -5, // Negative offset shifts bar to left
+ 'yAxisID' => 'y2',
],
[
'label' => __('Pages vues'),
'data' => $this->_pagesByPeriod->pluck('nb_hits')->toArray(),
'order' => 2,
'bar_offset' => 5, // Positive offset shifts bar to right
+ 'yAxisID' => 'y',
],
];
'data' => $this->_pagesByPeriod->pluck('nb_uniq_visitors')->toArray(),
'order' => 1,
'bar_offset' => -8, // Negative offset shifts bar to left
+ 'yAxisID' => 'y2',
]
], $res);
}
-
protected function _getTableMap()
{
$res = [
// Older Fluidbooks can't show unique visitors (see notes above)
if (!$this->_supportUniqueVisitors()) {
unset($res['summary']['nb_uniq_visitors']);
- unset($res['per-page']['nb_uniq_visitors']);
}
return $res;
// Fetch stats for pages, separated by their URLs.
// Since we're interested in per-page not per-period stats, we fetch the report flattened for the full date range
$pageUrls = collect($this->getPageUrls(['period' => 'range', 'flat' => 1]))
- ->keyBy('label')
- ->reject(function ($value, $key) {
- // Remove the '/' URL because it's not a real page (it's created by the "Open Fluidbook"
- // event and a separate, "real" page view is recorded at the same time)
- return $key === '/';
- });
+ ->keyBy('label');
+
+ $this->viewData->openings = $pageUrls['/'];
+ unset($pageUrls['/']);
//=== Group and combine page stats with related data
$res = [];
- for ($page_number = 1; $page_number <= $this->fluidbook->getPagesNumber(); $page_number++) {
- // For first and last pages or independent pages, don't group pages into spreads
- // There's also a special case where a Fluidbook has spreads but has uneven pages
- // (ie. no back cover, so the last page shouldn't be excluded from grouping)
- if ($this->fluidbook->isOnePage() || $page_number === 1 || ($page_number === $this->fluidbook->getPagesNumber() && $this->fluidbook->getPagesNumber() % 2 === 0)) {
+ for ($page_number = 0; $page_number <= $this->fluidbook->getPagesNumber(); $page_number++) {
+ if ($this->fluidbook->isOnePage()) {
$page_group = $page_number;
-
- $res[$page_group] = [
- 'page_group' => $page_group,
- 'page_number' => $page_number, // Used by table column sorter
- 'nb_pageviews' => $pageUrls["/page/$page_number"]['nb_hits'] ?? 0,
- 'nb_zooms' => data_get($this->_eventsByPage, "zoom.subtable.$page_number.nb_events", 0),
- 'nb_bookmarks' => data_get($this->_eventsByPage, "bookmark.subtable.$page_number.nb_events", 0),
- 'nb_shares' => data_get($this->_eventsByPage, "share.subtable.$page_number.nb_events", 0),
- ];
-
- // Special case: the first page of double-page Fluidbooks is tracked as "page 0", meaning that
- // for our stats labelled as "page 1", we must get certain stats from the "page 0" data...
- if (!$this->fluidbook->isOnePage() && $page_number === 1) {
- $res[$page_group]['nb_visits'] = $pageUrls["/page/0"]['nb_visits'] ?? 0;
- $res[$page_group]['nb_uniq_visitors'] = $pageUrls["/page/0"]['sum_daily_nb_uniq_visitors'] ?? 0;
- $res[$page_group]['nb_pageviews'] = $pageUrls["/page/0"]['nb_hits'] ?? 0;
- $res[$page_group]['nb_zooms'] = data_get($this->_eventsByPage, "zoom.subtable.0.nb_events", 0);
-
- // Bookmarks and shares are already set above, using the page 1 data.
- // In this case, page 0 doesn't exist as a bookmarking or sharing option, so nothing else to add.
- }
-
} else {
-
- $start_page = $page_number;
- if ($page_number % 2 == 1) {
- $start_page--;
- }
-
- // Only stats for even pages are considered when grouped (except for bookmarks / shares)
- $following_page = $start_page + 1; // By logic, there should always be a following page
- $page_group = $start_page . '—' . $following_page;
-
- $single_page_data = [
- 'page_group' => $page_group,
- 'page_number' => $page_number, // Used by table column sorter
- 'nb_pageviews' => $pageUrls["/page/$page_number"]['nb_hits'] ?? 0,
- 'nb_zooms' => data_get($this->_eventsByPage, "zoom.subtable.$page_number.nb_events", 0),
- // Bookmarks and shares are counted and summed for both pages in the spread
- 'nb_bookmarks' => data_get($this->_eventsByPage, "bookmark.subtable.$page_number.nb_events", 0)
- + data_get($this->_eventsByPage, "bookmark.subtable.$following_page.nb_events", 0),
- 'nb_shares' => data_get($this->_eventsByPage, "share.subtable.$page_number.nb_events", 0)
- + data_get($this->_eventsByPage, "share.subtable.$following_page.nb_events", 0),
- ];
-
- if (isset($res[$page_group])) {
- $res[$page_group] = $this->_sumArrays($res[$page_group], $single_page_data);
+ if ($page_number <= 1) {
+ $page_group = 1;
+ } elseif ($page_number == $this->fluidbook->getPagesNumber() && $this->fluidbook->getPagesNumber() % 2 == 0) {
+ $page_group = $page_number;
} else {
- $res[$page_group] = $single_page_data;
+ $start_page = $page_number;
+ if ($page_number % 2 == 1) {
+ $start_page--;
+ }
+ $following_page = $start_page + 1;
+ $page_group = $start_page . '—' . $following_page;
}
}
+
+ $single_page_data = [
+ 'page_group' => $page_group,
+ 'page_number' => $page_number, // Used by table column sorter
+ 'nb_pageviews' => $pageUrls["/page/$page_number"]['nb_hits'] ?? 0,
+ 'nb_zooms' => data_get($this->_eventsByPage, "zoom.subtable.$page_number.nb_events", 0),
+ 'nb_bookmarks' => data_get($this->_eventsByPage, "bookmark.subtable.$page_number.nb_events", 0),
+ 'nb_shares' => data_get($this->_eventsByPage, "share.subtable.$page_number.nb_events", 0)
+ ];
+
+ if (isset($res[$page_group])) {
+ $res[$page_group] = $this->_sumArrays($res[$page_group], $single_page_data);
+ } else {
+ $res[$page_group] = $single_page_data;
+ }
}
return $res;
}
protected function _sumArrays()
{
+ $exclude = ['page_number', 'page_group'];
$res = [];
foreach (func_get_args() as $array) {
foreach ($array as $k => $v) {
if (!isset($res[$k])) {
$res[$k] = 0;
}
- if (is_numeric($v)) {
+ if (is_numeric($v) && !in_array($k, $exclude)) {
$res[$k] += $v;
} else {
$res[$k] = $v;
<dt>{{ __('Nombre de pages') }}</dt>
<dd>{{ $page_count }}</dd>
- @if(isset($visits_summary['nb_uniq_visitors']))
- <dt>{{ __('Visiteurs uniques') }}</dt>
- <dd>{{ $visits_summary['nb_uniq_visitors'] }}</dd>
- @endif
-
- @if(isset($visits_summary['nb_visits']))
- <dt>{{ __('Visites') }}</dt>
- <dd>{{ $visits_summary['nb_visits'] }}</dd>
- @endif
-
-
{{-- Summary of totals --}}
@if($period_details->isNotEmpty())
@foreach ($table_map['summary'] as $summary_key => $summary_heading)
</div>
{{-- Chart --}}
- <canvas id="stats_chart"></canvas>
+ <div style="height: 500px">
+ <canvas id="stats_chart"></canvas>
+ </div>
{{-- Stats for each period entry (year, month, week or day) --}}
<table class="sortable stats-details mt-5">
@can('fluidbook-publication:admin')
<div style="text-align: right;margin-top: 20px;">
<a target="_blank"
- href="{{route('statsmatomo',['fluidbook_id'=>$fluidbook->id,'hash'=>$fluidbook->hash])}}">{{__('Voir les données brutes sur Matomo')}}</a>
+ href="{{route('statsmatomo',['fluidbook_id'=>$fluidbook->id,'hash'=>$fluidbook->hash,'period'=>'range','date'=>$start_date.','.$end_date])}}">{{__('Voir les données brutes sur Matomo')}}</a>
@can('superadmin')
| <a target="_blank" href="/fluidbook-publication/stats/API/{{$fluidbook->id}}">{{__('API Matomo')}}</a>
@endcan
let arrow = document.createElement('span');
arrow.className = 'arrow-right';
rangeInputs.insertBefore(arrow, rangeInputs.children[1]); // Insert in 2nd position
-
- @can('fluidbook-publication:admin')
- <div style="text-align: right;margin-top: 20px;">
- <a target="_blank"
- href="{{route('statsmatomo',['fluidbook_id'=>$fluidbook->id,'hash'=>$fluidbook->hash])}}">{{__('Voir les données brutes sur Matomo')}}</a>
- @can('superadmin')
- | <a target="_blank" href="/fluidbook-publication/stats/API/{{$fluidbook->id}}">{{__('API Matomo')}}</a>
- @endcan
- </div>
- @endcan
-
-
</script>
{{--============================================================================================================--}}
datasets: {!! $chart_datasets !!}
};
+ let maxvisits = 0;
+ $.each(data.datasets[1].data, function (k, v) {
+ if (v === null) {
+ return;
+ }
+ maxvisits = Math.max(maxvisits, v);
+ });
+
//=== Plugins
//== Offset Bars Plugin: creates a small offset between stacked bars
const offsetBars = {
type: 'bar',
data: data,
options: {
- // The best way to make the chart responsive is to set the aspect ratio without setting any sizes on
- // the canvas element itself. This way, Chart JS will manage the size, and it will behave much like a
- // responsive image - it will take the available width, scaling the height proportionally.
- // The aspectRatio value is a measure of the width compared to the height. In other words:
- // aspectRatio: 3 = 3x the width compared to height
- // aspectRatio: 1 = square
- // and an aspectRatio less than 1 will result in a canvas that is taller than it is wide.
- aspectRatio: 3,
- responsive: true, // Default for Chart JS but defining it explicitly here for clarity
- maintainAspectRatio: true, // As above, also a library default
- maxBarThickness: 100, // Prevent bars being ridiculously wide when there isn't much data
+ responsive: true,
+ maintainAspectRatio: false,
+ maxBarThickness: 50, // Prevent bars being ridiculously wide when there isn't much data
scales: {
x: {stacked: true},
- y: {stacked: false}, // Don't stack y-axis: prevents datasets from adding
+ y: {
+ type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
+ position: 'left',
+ stacked: false,
+ },
+ y2: {
+ type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
+ position: 'right',
+ reverse: false,
+ ticks: {
+ color: '#9bc200',
+ },
+ grid: {
+ drawOnChartArea: false // only want the grid lines for one axis to show up
+ },
+ max: maxvisits * 3,
+ stacked: false,
+ },
},
plugins: {
tooltip: {