feat: add map analytics, add episodes analytics, clean analytics page layout, translate countries

This commit is contained in:
Benjamin Bellamy 2020-10-14 10:38:48 +00:00 committed by Yassine Doghri
parent 196920d62f
commit 07eae83a00
27 changed files with 726 additions and 213 deletions

View File

@ -123,10 +123,46 @@ $routes->group(
'as' => 'podcast-delete',
'filter' => 'permission:podcasts-delete',
]);
$routes->get('analytics', 'Podcast::analytics/$1', [
'as' => 'podcast-analytics',
'filter' => 'permission:podcasts-view,podcast-view',
]);
$routes->group('analytics', function ($routes) {
$routes->get('/', 'Podcast::viewAnalytics/$1', [
'as' => 'podcast-analytics',
'filter' => 'permission:podcasts-view,podcast-view',
]);
$routes->get(
'webpages',
'Podcast::viewAnalyticsWebpages/$1',
[
'as' => 'podcast-analytics-webpages',
'filter' => 'permission:podcasts-view,podcast-view',
]
);
$routes->get(
'locations',
'Podcast::viewAnalyticsLocations/$1',
[
'as' => 'podcast-analytics-locations',
'filter' => 'permission:podcasts-view,podcast-view',
]
);
$routes->get(
'unique-listeners',
'Podcast::viewAnalyticsUniqueListeners/$1',
[
'as' => 'podcast-analytics-unique-listeners',
'filter' => 'permission:podcasts-view,podcast-view',
]
);
$routes->get(
'players',
'Podcast::viewAnalyticsPlayers/$1',
[
'as' => 'podcast-analytics-players',
'filter' => 'permission:podcasts-view,podcast-view',
]
);
});
$routes->get(
'analytics-data/(:segment)',
'AnalyticsData::getData/$1/$2',

View File

@ -58,12 +58,44 @@ class Podcast extends BaseController
return view('admin/podcast/view', $data);
}
public function analytics()
public function viewAnalytics()
{
$data = ['podcast' => $this->podcast];
replace_breadcrumb_params([0 => $this->podcast->title]);
return view('admin/podcast/analytics', $data);
return view('admin/podcast/analytics/index', $data);
}
public function viewAnalyticsWebpages()
{
$data = ['podcast' => $this->podcast];
replace_breadcrumb_params([0 => $this->podcast->title]);
return view('admin/podcast/analytics/webpages', $data);
}
public function viewAnalyticsLocations()
{
$data = ['podcast' => $this->podcast];
replace_breadcrumb_params([0 => $this->podcast->title]);
return view('admin/podcast/analytics/locations', $data);
}
public function viewAnalyticsUniqueListeners()
{
$data = ['podcast' => $this->podcast];
replace_breadcrumb_params([0 => $this->podcast->title]);
return view('admin/podcast/analytics/unique_listeners', $data);
}
public function viewAnalyticsPlayers()
{
$data = ['podcast' => $this->podcast];
replace_breadcrumb_params([0 => $this->podcast->title]);
return view('admin/podcast/analytics/players', $data);
}
public function create()

View File

@ -20,4 +20,9 @@ class AnalyticsPodcastsByCountry extends Entity
'date' => 'datetime',
'hits' => 'integer',
];
public function getLabels()
{
return lang('Countries.' . $this->attributes['labels']);
}
}

View File

@ -23,4 +23,9 @@ class AnalyticsPodcastsByRegion extends Entity
'date' => 'datetime',
'hits' => 'integer',
];
public function getCountryCode()
{
return lang('Countries.' . $this->attributes['country_code']);
}
}

View File

@ -20,4 +20,10 @@ class AnalyticsWebsiteByEntryPage extends Entity
'date' => 'datetime',
'hits' => 'integer',
];
public function getLabels()
{
$split = explode('/', $this->attributes['labels']);
return $split[count($split) - 1];
}
}

View File

@ -23,4 +23,8 @@ return [
'settings' => 'settings',
'platforms' => 'platforms',
'analytics' => 'Analytics',
'locations' => 'Locations',
'website' => 'Website',
'unique-listeners' => 'Unique listeners',
'players' => 'Players',
];

View File

@ -7,14 +7,24 @@
*/
return [
'by_player' => 'Podcast downloads by player (for the past week)',
'by_player_weekly' => 'Podcast downloads by player (for the past week)',
'by_player_yearly' => 'Podcast downloads by player (for the past year)',
'by_device_weekly' => 'Podcast downloads by device (for the past week)',
'by_os_weekly' => 'Podcast downloads by O.S. (for the past week)',
'podcast_by_region' => 'Podcast downloads by region (for the past week)',
'unique_daily_listeners' => 'Daily unique listeners',
'unique_monthly_listeners' => 'Monthly unique listeners',
'by_browser' => 'Website usage by browser (for the past week)',
'by_browser' => 'Web pages usage by browser (for the past week)',
'podcast_by_day' => 'Podcast daily downloads',
'podcast_by_month' => 'Podcast monthly downloads',
'episode_by_day' => 'Episode daily downloads (first 60 days)',
'episode_by_month' => 'Episode monthly downloads',
'episodes_by_day' =>
'5 latest episodes downloads (during their first 60 days)',
'by_country' => 'Podcast downloads by country (for the past week)',
'by_domain' => 'Website visits by origin (for the past week)',
'by_country_weekly' => 'Podcast downloads by country (for the past week)',
'by_country_yearly' => 'Podcast downloads by country (for the past year)',
'by_domain_weekly' => 'Web pages visits by source (for the past week)',
'by_domain_yearly' => 'Web pages visits by source (for the past year)',
'by_entry_page' => 'Web pages visits by landing page (for the past week)',
'podcast_bots' => 'Bots (crawlers)',
];

View File

@ -20,5 +20,9 @@ return [
'contributor-add' => 'Add contributor',
'settings' => 'Settings',
'platforms' => 'Podcast platforms',
'podcast-analytics' => 'Audiences Overview',
'podcast-analytics' => 'Audience overview',
'podcast-analytics-webpages' => 'Web pages visits',
'podcast-analytics-locations' => 'Locations',
'podcast-analytics-unique-listeners' => 'Unique listeners',
'podcast-analytics-players' => 'Players',
];

View File

@ -20,7 +20,7 @@ return [
'unique_daily_listeners' => 'Auditeurs uniques quotidiens',
'unique_monthly_listeners' => 'Auditeurs uniques mensuels',
'by_browser' =>
'Fréquentation du site par navigateur (sur la dernière semaine)',
'Fréquentation des pages web par navigateur (sur la dernière semaine)',
'podcast_by_day' => 'Téléchargements quotidiens de podcasts',
'podcast_by_month' => 'Téléchargements mensuels de podcasts',
'episode_by_day' =>
@ -33,10 +33,10 @@ return [
'by_country_yearly' =>
'Téléchargement de podcasts par pays (sur la dernière année)',
'by_domain_weekly' =>
'Fréquentation du site par origine (sur la dernière semaine)',
'Fréquentation des pages web par origine (sur la dernière semaine)',
'by_domain_yearly' =>
'Fréquentation du site par origine (sur la dernière année)',
'Fréquentation des pages web par origine (sur la dernière année)',
'by_entry_page' =>
'Fréquentation du site par page dentrée (sur la dernière semaine)',
'Fréquentation des pages web par page dentrée (sur la dernière semaine)',
'podcast_bots' => 'Robots (bots)',
];

View File

@ -20,8 +20,8 @@ return [
'contributor-add' => 'Ajouter un contributeur',
'settings' => 'Paramètres',
'platforms' => 'Plateformes du podcast',
'podcast-analytics' => 'Mesures daudience',
'podcast-analytics-website' => 'Visites du site web',
'podcast-analytics' => 'Vue densemble',
'podcast-analytics-webpages' => 'Visites des pages web',
'podcast-analytics-locations' => 'Localisations',
'podcast-analytics-unique-listeners' => 'Auditeurs uniques',
'podcast-analytics-players' => 'Lecteurs',

View File

@ -30,9 +30,14 @@ class AnalyticsPodcastByCountryModel extends Model
*
* @return array
*/
public function getData(int $podcastId): array
public function getDataWeekly(int $podcastId): array
{
if (!($found = cache("{$podcastId}_analytics_podcast_by_country"))) {
$locale = service('request')->getLocale();
if (
!($found = cache(
"{$podcastId}_analytics_podcast_by_country_weekly_{$locale}"
))
) {
$found = $this->select('`country_code` as `labels`')
->selectSum('`hits`', '`values`')
->where([
@ -44,7 +49,41 @@ class AnalyticsPodcastByCountryModel extends Model
->findAll(10);
cache()->save(
"{$podcastId}_analytics_podcast_by_country",
"{$podcastId}_analytics_podcast_by_country_weekly_{$locale}",
$found,
600
);
}
return $found;
}
/**
* Gets country data for a podcast
*
* @param int $podcastId
*
* @return array
*/
public function getDataYearly(int $podcastId): array
{
$locale = service('request')->getLocale();
if (
!($found = cache(
"{$podcastId}_analytics_podcast_by_country_yearly_{$locale}"
))
) {
$found = $this->select('`country_code` as `labels`')
->selectSum('`hits`', '`values`')
->where([
'`podcast_id`' => $podcastId,
'`date` >' => date('Y-m-d', strtotime('-1 year')),
])
->groupBy('`labels`')
->orderBy('`values`', 'DESC')
->findAll(10);
cache()->save(
"{$podcastId}_analytics_podcast_by_country_yearly_{$locale}",
$found,
600
);

View File

@ -37,12 +37,12 @@ class AnalyticsPodcastByEpisodeModel extends Model
))
) {
$lastEpisodes = (new EpisodeModel())
->select('id, season_number, number, title')
->orderBy('id', 'DESC')
->where(['podcast_id' => $podcastId])
->select('`id`, `season_number`, `number`, `title`')
->orderBy('`id`', 'DESC')
->where(['`podcast_id`' => $podcastId])
->findAll(5);
$found = $this->select('age AS X');
$found = $this->select('`age` AS `X`');
$letter = 97;
foreach ($lastEpisodes as $episode) {
@ -51,7 +51,7 @@ class AnalyticsPodcastByEpisodeModel extends Model
'(CASE WHEN `episode_id`=' .
$episode->id .
' THEN `hits` END)',
chr($letter) . 'Y'
'`' . chr($letter) . 'Y`'
)
->select(
'"' .
@ -62,20 +62,20 @@ class AnalyticsPodcastByEpisodeModel extends Model
? ''
: '-' . $episode->number . '/ ') .
$episode->title .
'" AS ' .
'" AS `' .
chr($letter) .
'Value'
'Value`'
);
$letter++;
}
$found = $found
->where([
'podcast_id' => $podcastId,
'age <' => 60,
'`podcast_id`' => $podcastId,
'`age` <' => 60,
])
->groupBy('X')
->orderBy('X', 'ASC')
->groupBy('`X`')
->orderBy('`X`', 'ASC')
->findAll();
cache()->save(
@ -91,14 +91,15 @@ class AnalyticsPodcastByEpisodeModel extends Model
"{$podcastId}_{$episodeId}_analytics_podcast_by_episode_by_day"
))
) {
$found = $this->select('date as labels')
->selectSum('hits', 'values')
$found = $this->select('`date as `labels`')
->selectSum('`hits`', '`values`')
->where([
'episode_id' => $episodeId,
'podcast_id' => $podcastId,
'`episode_id`' => $episodeId,
'`podcast_id`' => $podcastId,
'`age` <' => 60,
])
->groupBy('labels')
->orderBy('labels', 'ASC')
->groupBy('`labels`')
->orderBy('`labels`', 'ASC')
->findAll();
cache()->save(
@ -110,4 +111,35 @@ class AnalyticsPodcastByEpisodeModel extends Model
return $found;
}
}
/**
* @param int $podcastId, $episodeId
*
* @return array
*/
public function getDataByMonth(int $podcastId, int $episodeId = null): array
{
if (
!($found = cache(
"{$podcastId}_{$episodeId}_analytics_podcast_by_episode_by_month"
))
) {
$found = $this->select('DATE_FORMAT(`date`,"%Y-%m-01") as `labels`')
->selectSum('`hits`', '`values`')
->where([
'episode_id' => $episodeId,
'podcast_id' => $podcastId,
])
->groupBy('`labels`')
->orderBy('`labels`', 'ASC')
->findAll();
cache()->save(
"{$podcastId}_{$episodeId}_analytics_podcast_by_episode_by_month",
$found,
600
);
}
return $found;
}
}

View File

@ -30,11 +30,11 @@ class AnalyticsPodcastByPlayerModel extends Model
*
* @return array
*/
public function getDataByApp(int $podcastId): array
public function getDataByAppWeekly(int $podcastId): array
{
if (
!($found = cache(
"{$podcastId}_analytics_podcasts_by_player_by_app"
"{$podcastId}_analytics_podcasts_by_player_by_app_weekly"
))
) {
$found = $this->select('`app` as `labels`')
@ -50,92 +50,148 @@ class AnalyticsPodcastByPlayerModel extends Model
->findAll(10);
cache()->save(
"{$podcastId}_analytics_podcasts_by_player_by_app",
"{$podcastId}_analytics_podcasts_by_player_by_app_weekly",
$found,
600
);
}
return $found;
}
/**
* Gets device data for a podcast
* Gets player data for a podcast
*
* @param int $podcastId
*
* @return array
*/
public function getDataByDevice(int $podcastId): array
public function getDataByAppYearly(int $podcastId): array
{
if (
!($found = cache(
"{$podcastId}_analytics_podcasts_by_player_by_device"
"{$podcastId}_analytics_podcasts_by_player_by_app_yearly"
))
) {
$foundApp = $this->select(
'CONCAT_WS("/", `device`, `os`, `app`) as `ids`, `app` as `labels`, CONCAT_WS("/", `device`, `os`) as `parents`'
)
$found = $this->select('`app` as `labels`')
->selectSum('`hits`', '`values`')
->where([
'`podcast_id`' => $podcastId,
'`app` !=' => null,
'`app` !=' => '',
'`bot`' => 0,
'`date` >' => date('Y-m-d', strtotime('-1 week')),
'`date` >' => date('Y-m-d', strtotime('-1 year')),
])
->groupBy('`ids`')
->groupBy('`labels`')
->orderBy('`values`', 'DESC')
->findAll();
->findAll(10);
$foundOs = $this->select(
'CONCAT_WS("/", `device`, `os`) as `ids`, `os` as `labels`, `device` as `parents`'
)
->selectSum('`hits`', '`values`')
->where([
'`podcast_id`' => $podcastId,
'`os` !=' => null,
'`bot`' => 0,
'`date` >' => date('Y-m-d', strtotime('-1 week')),
])
->groupBy('`ids`')
->orderBy('`values`', 'DESC')
->findAll();
$foundDevice = $this->select(
'`device` as `ids`, `device` as `labels`, "" as `parents`'
)
->selectSum('`hits`', '`values`')
->where([
'`podcast_id`' => $podcastId,
'`device` !=' => null,
'`bot`' => 0,
'`date` >' => date('Y-m-d', strtotime('-1 week')),
])
->groupBy('`ids`')
->orderBy('`values`', 'DESC')
->findAll();
$foundBot = $this->select(
'"bots" as `ids`, "Bots" as `labels`, "" as `parents`'
)
->selectSum('`hits`', '`values`')
->where([
'`podcast_id`' => $podcastId,
'`bot`' => 1,
'`date` >' => date('Y-m-d', strtotime('-1 week')),
])
->groupBy('`ids`')
->orderBy('`values`', 'DESC')
->findAll();
$found = array_merge($foundApp, $foundOs, $foundDevice, $foundBot);
cache()->save(
"{$podcastId}_analytics_podcasts_by_player_by_device",
"{$podcastId}_analytics_podcasts_by_player_by_app_yearly",
$found,
600
);
}
return $found;
}
/**
* Gets os data for a podcast
*
* @param int $podcastId
*
* @return array
*/
public function getDataByOsWeekly(int $podcastId): array
{
if (
!($found = cache(
"{$podcastId}_analytics_podcasts_by_player_by_os_weekly"
))
) {
$found = $this->select('`os` as `labels`')
->selectSum('`hits`', '`values`')
->where([
'`podcast_id`' => $podcastId,
'`app` !=' => '',
'`bot`' => 0,
'`date` >' => date('Y-m-d', strtotime('-1 week')),
])
->groupBy('`labels`')
->orderBy('`values`', 'DESC')
->findAll(10);
cache()->save(
"{$podcastId}_analytics_podcasts_by_player_by_os_weekly",
$found,
600
);
}
return $found;
}
/**
* Gets player data for a podcast
*
* @param int $podcastId
*
* @return array
*/
public function getDataByDeviceWeekly(int $podcastId): array
{
if (
!($found = cache(
"{$podcastId}_analytics_podcasts_by_player_by_device_weekly"
))
) {
$found = $this->select('`device` as `labels`')
->selectSum('`hits`', '`values`')
->where([
'`podcast_id`' => $podcastId,
'`device` !=' => '',
'`bot`' => 0,
'`date` >' => date('Y-m-d', strtotime('-1 week')),
])
->groupBy('`labels`')
->orderBy('`values`', 'DESC')
->findAll(10);
cache()->save(
"{$podcastId}_analytics_podcasts_by_player_by_device_weekly",
$found,
600
);
}
return $found;
}
/**
* Gets bots data for a podcast
*
* @param int $podcastId
*
* @return array
*/
public function getDataBots(int $podcastId): array
{
if (
!($found = cache("{$podcastId}_analytics_podcasts_by_player_bots"))
) {
$found = $this->select('DATE_FORMAT(`date`,"%Y-%m-01") as `labels`')
->selectSum('`hits`', '`values`')
->where([
'`podcast_id`' => $podcastId,
'`bot`' => 1,
'`date` >' => date('Y-m-d', strtotime('-1 year')),
])
->groupBy('`labels`')
->orderBy('`labels`', 'ASC')
->findAll(10);
cache()->save(
"{$podcastId}_analytics_podcasts_by_player_bots",
$found,
600
);
}
return $found;
}
}

View File

@ -32,11 +32,16 @@ class AnalyticsPodcastByRegionModel extends Model
*/
public function getData(int $podcastId): array
{
if (!($found = cache("{$podcastId}_analytics_podcast_by_region"))) {
$locale = service('request')->getLocale();
if (
!($found = cache(
"{$podcastId}_analytics_podcast_by_region_{$locale}"
))
) {
$found = $this->select(
'`country_code`, `region_code`, `latitude`, `longitude`'
)
->selectSum('`hits`', '`values`')
->selectSum('`hits`', '`value`')
->groupBy(
'`country_code`, `region_code`, `latitude`, `longitude`'
)
@ -44,11 +49,11 @@ class AnalyticsPodcastByRegionModel extends Model
'`podcast_id`' => $podcastId,
'`date` >' => date('Y-m-d', strtotime('-1 week')),
])
->orderBy('`values`', 'DESC')
->orderBy('`value`', 'DESC')
->findAll();
cache()->save(
"{$podcastId}_analytics_podcast_by_region",
"{$podcastId}_analytics_podcast_by_region_{$locale}",
$found,
600
);

View File

@ -36,7 +36,7 @@ class AnalyticsPodcastModel extends Model
$found = $this->select('`date` as `labels`, `hits` as `values`')
->where([
'`podcast_id`' => $podcastId,
'`date` >' => date('Y-m-d', strtotime('-1 year')),
'`date` >' => date('Y-m-d', strtotime('-60 days')),
])
->orderBy('`labels`', 'ASC')
->findAll();
@ -60,7 +60,6 @@ class AnalyticsPodcastModel extends Model
->selectSum('`hits`', '`values`')
->where([
'`podcast_id`' => $podcastId,
'`date` >' => date('Y-m-d', strtotime('-1 year')),
])
->groupBy('`labels`')
->orderBy('`labels`', 'ASC')
@ -94,7 +93,7 @@ class AnalyticsPodcastModel extends Model
)
->where([
'`podcast_id`' => $podcastId,
'`date` >' => date('Y-m-d', strtotime('-1 year')),
'`date` >' => date('Y-m-d', strtotime('-60 days')),
])
->orderBy('`labels`', 'ASC')
->findAll();

View File

@ -59,9 +59,11 @@ class AnalyticsWebsiteByRefererModel extends Model
*
* @return array
*/
public function getDataByDomain(int $podcastId): array
public function getDataByDomainWeekly(int $podcastId): array
{
if (!($found = cache("{$podcastId}_analytics_website_by_domain"))) {
if (
!($found = cache("{$podcastId}_analytics_website_by_domain_weekly"))
) {
$found = $this->select('`domain` as `labels`')
->selectSum('`hits`', '`values`')
->where([
@ -73,7 +75,38 @@ class AnalyticsWebsiteByRefererModel extends Model
->findAll(10);
cache()->save(
"{$podcastId}_analytics_website_by_domain",
"{$podcastId}_analytics_website_by_domain_weekly",
$found,
600
);
}
return $found;
}
/**
* Gets domain data for a podcast
*
* @param int $podcastId
*
* @return array
*/
public function getDataByDomainYearly(int $podcastId): array
{
if (
!($found = cache("{$podcastId}_analytics_website_by_domain_yearly"))
) {
$found = $this->select('`domain` as `labels`')
->selectSum('`hits`', '`values`')
->where([
'`podcast_id`' => $podcastId,
'`date` >' => date('Y-m-d', strtotime('-1 year')),
])
->groupBy('`labels`')
->orderBy('`values`', 'DESC')
->findAll(10);
cache()->save(
"{$podcastId}_analytics_website_by_domain_yearly",
$found,
600
);

View File

@ -1,33 +1,29 @@
// Import modules
import am4geodata_worldLow from "@amcharts/amcharts4-geodata/worldLow";
import * as am4charts from "@amcharts/amcharts4/charts";
import * as am4core from "@amcharts/amcharts4/core";
import * as am4maps from "@amcharts/amcharts4/maps";
import am4themes_material from "@amcharts/amcharts4/themes/material";
const drawPieChart = (chartDivId: string, dataUrl: string | null): void => {
// Create chart instance
const chart = am4core.create(chartDivId, am4charts.PieChart);
am4core.percent(100);
// Set theme
am4core.useTheme(am4themes_material);
chart.innerRadius = am4core.percent(10);
// Add data
chart.dataSource.url = dataUrl || "";
chart.dataSource.parser.options.emptyAs = 0;
// Add and configure Series
const pieSeries = chart.series.push(new am4charts.PieSeries());
pieSeries.dataFields.value = "values";
pieSeries.dataFields.category = "labels";
pieSeries.slices.template.stroke = am4core.color("#ffffff");
pieSeries.slices.template.strokeWidth = 1;
pieSeries.slices.template.strokeOpacity = 1;
pieSeries.labels.template.disabled = true;
pieSeries.ticks.template.disabled = true;
chart.legend = new am4charts.Legend();
chart.legend.position = "right";
chart.legend.scrollable = true;
@ -37,32 +33,32 @@ const drawXYChart = (chartDivId: string, dataUrl: string | null): void => {
// Create chart instance
const chart = am4core.create(chartDivId, am4charts.XYChart);
am4core.percent(100);
// Set theme
am4core.useTheme(am4themes_material);
// Create axes
const dateAxis = chart.xAxes.push(new am4charts.DateAxis());
dateAxis.renderer.minGridDistance = 60;
chart.yAxes.push(new am4charts.ValueAxis());
// Add data
chart.dataSource.url = dataUrl || "";
chart.dataSource.parser.options.emptyAs = 0;
// Create series
const series = chart.series.push(new am4charts.LineSeries());
series.dataFields.valueY = "values";
series.dataFields.dateX = "labels";
series.tooltipText = "{valueY} downloads";
series.tooltipText = "{valueY} hits";
series.strokeWidth = 2;
// Make bullets grow on hover
const bullet = series.bullets.push(new am4charts.CircleBullet());
bullet.circle.strokeWidth = 2;
bullet.circle.radius = 4;
bullet.circle.fill = am4core.color("#fff");
const bullethover = bullet.states.create("hover");
bullethover.properties.scale = 1.3;
series.tooltip.pointerOrientation = "vertical";
chart.cursor = new am4charts.XYCursor();
chart.cursor.snapToSeries = series;
chart.cursor.xAxis = dateAxis;
chart.scrollbarX = new am4core.Scrollbar();
};
@ -73,40 +69,74 @@ const drawXYSeriesChart = (
// Create chart instance
const chart = am4core.create(chartDivId, am4charts.XYChart);
am4core.percent(100);
// Set theme
am4core.useTheme(am4themes_material);
// Create axes
chart.xAxes.push(new am4charts.ValueAxis());
chart.yAxes.push(new am4charts.ValueAxis());
// Add data
chart.dataSource.url = dataUrl || "";
chart.dataSource.parser.options.emptyAs = 0;
// Create series
const series1 = chart.series.push(new am4charts.LineSeries());
series1.dataFields.valueX = "X";
series1.dataFields.valueY = "aY";
const series2 = chart.series.push(new am4charts.LineSeries());
series2.dataFields.valueX = "X";
series2.dataFields.valueY = "bY";
const series3 = chart.series.push(new am4charts.LineSeries());
series3.dataFields.valueX = "X";
series3.dataFields.valueY = "cY";
const series4 = chart.series.push(new am4charts.LineSeries());
series4.dataFields.valueX = "X";
series4.dataFields.valueY = "dY";
const series5 = chart.series.push(new am4charts.LineSeries());
series5.dataFields.valueX = "X";
series5.dataFields.valueY = "eY";
};
const drawMapChart = (chartDivId: string, dataUrl: string | null): void => {
// Create map instance
const chart = am4core.create(chartDivId, am4maps.MapChart);
am4core.percent(100);
// Set theme
am4core.useTheme(am4themes_material);
// Set map definition
chart.geodata = am4geodata_worldLow;
// Set projection
chart.projection = new am4maps.projections.Miller();
// Create map polygon series
const polygonSeries = chart.series.push(new am4maps.MapPolygonSeries());
// Exclude Antartica
polygonSeries.exclude = ["AQ"];
// Make map load polygon (like country names) data from GeoJSON
polygonSeries.useGeodata = true;
// Configure series
const polygonTemplate = polygonSeries.mapPolygons.template;
polygonTemplate.tooltipText = "{name}";
polygonTemplate.polygon.fillOpacity = 0.6;
// Create hover state and set alternative fill color
const hs = polygonTemplate.states.create("hover");
hs.properties.fill = chart.colors.getIndex(0);
// Add image series
const imageSeries = chart.series.push(new am4maps.MapImageSeries());
imageSeries.dataSource.url = dataUrl || "";
imageSeries.mapImages.template.propertyFields.longitude = "longitude";
imageSeries.mapImages.template.propertyFields.latitude = "latitude";
imageSeries.mapImages.template.tooltipText =
"{country_code}, {region_code}:\n[bold]{value}[/] hits";
const circle = imageSeries.mapImages.template.createChild(am4core.Circle);
circle.radius = 1;
circle.fill = am4core.color("#60f");
imageSeries.heatRules.push({
target: circle,
property: "radius",
min: 0.5,
max: 3,
dataField: "value",
});
};
const DrawCharts = (): void => {
const chartDivs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
"div[data-chart-type]"
@ -125,6 +155,9 @@ const DrawCharts = (): void => {
case "xy-series-chart":
drawXYSeriesChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
break;
case "map-chart":
drawMapChart(chartDiv.id, chartDiv.getAttribute("data-chart-url"));
break;
default:
console.error("Unknown chart type:" + chartType);
}

View File

@ -0,0 +1,15 @@
.chart-map {
height: 800px;
border: solid 10px #eee;
}
.chart-pie {
height: 400px;
width: 100%;
border: solid 1px #eee;
}
.chart-xy {
height: 500px;
width: 100%;
border: solid 1px #eee;
border: solid 3px #eee;
}

View File

@ -5,3 +5,4 @@
@import "./radioBtn.css";
@import "./switch.css";
@import "./enclosureInput.css";
@import "./charts.css";

View File

@ -9,7 +9,7 @@
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="flex flex-wrap">
<div class="w-full max-w-sm mb-6 md:mr-4">
<img
@ -46,4 +46,29 @@
</section>
</div>
<div class="mb-12 text-center">
<h2><?= lang('Charts.episode_by_day') ?></h2>
<div class="chart-xy" id="by-day-graph" data-chart-type="xy-chart" data-chart-url="<?= route_to(
'analytics-filtered-data',
$podcast->id,
'PodcastByEpisode',
'ByDay',
$episode->id
) ?>"></div>
</div>
<div class="mb-12 text-center">
<h2><?= lang('Charts.episode_by_month') ?></h2>
<div class="chart-xy" id="by-month-graph" data-chart-type="xy-chart" data-chart-url="<?= route_to(
'analytics-filtered-data',
$podcast->id,
'PodcastByEpisode',
'ByMonth',
$episode->id
) ?>"></div>
</div>
<script src="/assets/charts.js" type="module"></script>
<?= $this->endSection() ?>

View File

@ -10,7 +10,13 @@ $podcastNavigation = [
],
'analytics' => [
'icon' => 'line-chart',
'items' => ['podcast-analytics'],
'items' => [
'podcast-analytics',
'podcast-analytics-unique-listeners',
'podcast-analytics-players',
'podcast-analytics-locations',
'podcast-analytics-webpages',
],
],
'contributors' => [
'icon' => 'group',

View File

@ -1,84 +0,0 @@
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= $podcast->title ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= $podcast->title ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<h2><?= lang('Charts.podcast_by_day') ?></h2>
<div class="h-64" id="by-day-graph" data-chart-type="xy-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'Podcast',
'ByDay'
) ?>"></div>
<h2><?= lang('Charts.podcast_by_month') ?></h2>
<div class="h-64" id="by-month-graph" data-chart-type="xy-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'Podcast',
'ByMonth'
) ?>"></div>
<h2><?= lang('Charts.unique_daily_listeners') ?></h2>
<div class="h-64" id="by-day-listeners-graph" data-chart-type="xy-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'Podcast',
'UniqueListenersByDay'
) ?>"></div>
<h2><?= lang('Charts.unique_monthly_listeners') ?></h2>
<div class="h-64" id="by-month-listeners-graph" data-chart-type="xy-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'Podcast',
'UniqueListenersByMonth'
) ?>"></div>
<h2><?= lang('Charts.episodes_by_day') ?></h2>
<div class="h-64" id="by-age-graph" data-chart-type="xy-series-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'PodcastByEpisode',
'ByDay'
) ?>"></div>
<h2><?= lang('Charts.by_player') ?></h2>
<div class="h-64" id="by-app-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'PodcastByPlayer',
'ByApp'
) ?>"></div>
<h2><?= lang('Charts.by_browser') ?></h2>
<div class="h-64" id="by-browser-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-full-data',
$podcast->id,
'WebsiteByBrowser'
) ?>"></div>
<h2><?= lang('Charts.by_country') ?></h2>
<div class="h-64" id="by-country-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-full-data',
$podcast->id,
'PodcastByCountry'
) ?>"></div>
<h2><?= lang('Charts.by_domain') ?></h2>
<div class="h-64" id="by-domain-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'WebsiteByReferer',
'ByDomain'
) ?>"></div>
<script src="/assets/charts.js" type="module"></script>
<?= $this->endSection() ?>

View File

@ -0,0 +1,43 @@
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= $podcast->title ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= $podcast->title ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="mb-12 text-center">
<h2><?= lang('Charts.podcast_by_day') ?></h2>
<div class="chart-xy" id="by-day-graph" data-chart-type="xy-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'Podcast',
'ByDay'
) ?>"></div>
</div>
<div class="mb-12 text-center">
<h2><?= lang('Charts.podcast_by_month') ?></h2>
<div class="chart-xy" id="by-month-graph" data-chart-type="xy-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'Podcast',
'ByMonth'
) ?>"></div>
</div>
<div class="mb-12 text-center">
<h2><?= lang('Charts.episodes_by_day') ?></h2>
<div class="chart-xy" id="by-age-graph" data-chart-type="xy-series-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'PodcastByEpisode',
'ByDay'
) ?>"></div>
</div>
<script src="/assets/charts.js" type="module"></script>
<?= $this->endSection() ?>

View File

@ -0,0 +1,46 @@
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= $podcast->title ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= $podcast->title ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="grid grid-cols-2 divide-x">
<div class="mb-12 mr-6 text-center">
<h2><?= lang('Charts.by_country_weekly') ?></h2>
<div class="chart-pie" id="by-country-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'PodcastByCountry',
'Weekly'
) ?>"></div>
</div>
<div class="mb-12 mr-6 text-center">
<h2><?= lang('Charts.by_country_yearly') ?></h2>
<div class="chart-pie" id="by-country-by-year-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'PodcastByCountry',
'Yearly'
) ?>"></div>
</div>
</div>
<div class="mb-12 mr-6 text-center">
<h2><?= lang('Charts.podcast_by_region') ?></h2>
<div class="chart-map" id="by-region-map" data-chart-type="map-chart" data-chart-url="<?= route_to(
'analytics-full-data',
$podcast->id,
'PodcastByRegion'
) ?>"></div>
</div>
<script src="/assets/charts.js" type="module"></script>
<?= $this->endSection() ?>

View File

@ -0,0 +1,67 @@
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= $podcast->title ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= $podcast->title ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="grid grid-cols-2 divide-x">
<div class="mb-12 mr-6 text-center">
<h2><?= lang('Charts.by_player_weekly') ?></h2>
<div class="chart-pie" id="by-app-weekly-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'PodcastByPlayer',
'ByAppWeekly'
) ?>"></div>
</div>
<div class="mb-12 mr-6 text-center">
<h2><?= lang('Charts.by_player_yearly') ?></h2>
<div class="chart-pie" id="by-app-yearly-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'PodcastByPlayer',
'ByAppYearly'
) ?>"></div>
</div>
<div class="mb-12 mr-6 text-center">
<h2><?= lang('Charts.by_device_weekly') ?></h2>
<div class="chart-pie" id="by-device-weekly-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'PodcastByPlayer',
'ByDeviceWeekly'
) ?>"></div>
</div>
<div class="mb-12 mr-6 text-center">
<h2><?= lang('Charts.by_os_weekly') ?></h2>
<div class="chart-pie" id="by-os-yearly-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'PodcastByPlayer',
'ByOsWeekly'
) ?>"></div>
</div>
</div>
<div class="mb-12 mr-6 text-center">
<h2><?= lang('Charts.podcast_bots') ?></h2>
<div class="chart-xy" id="bots-graph" data-chart-type="xy-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'PodcastByPlayer',
'Bots'
) ?>"></div>
</div>
<script src="/assets/charts.js" type="module"></script>
<?= $this->endSection() ?>

View File

@ -0,0 +1,34 @@
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= $podcast->title ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= $podcast->title ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="mb-12 text-center">
<h2><?= lang('Charts.unique_daily_listeners') ?></h2>
<div class="chart-xy" id="by-day-listeners-graph" data-chart-type="xy-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'Podcast',
'UniqueListenersByDay'
) ?>"></div>
</div>
<div class="mb-12 text-center">
<h2><?= lang('Charts.unique_monthly_listeners') ?></h2>
<div class="chart-xy" id="by-month-listeners-graph" data-chart-type="xy-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'Podcast',
'UniqueListenersByMonth'
) ?>"></div>
</div>
<script src="/assets/charts.js" type="module"></script>
<?= $this->endSection() ?>

View File

@ -0,0 +1,61 @@
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= $podcast->title ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= $podcast->title ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="grid grid-cols-2 divide-x">
<div class="mb-12 mr-6 text-center">
<h2><?= lang('Charts.by_domain_weekly') ?></h2>
<div class="chart-pie" id="by-domain-weekly-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'WebsiteByReferer',
'ByDomainWeekly'
) ?>"></div>
</div>
<div class="mb-12 mr-6 text-center">
<h2><?= lang('Charts.by_domain_yearly') ?></h2>
<div class="chart-pie" id="by-domain-yearly-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-data',
$podcast->id,
'WebsiteByReferer',
'ByDomainYearly'
) ?>"></div>
</div>
<div class="mb-12 mr-6 text-center">
<h2><?= lang('Charts.by_entry_page') ?></h2>
<div class="chart-pie" id="by-entry-page-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-full-data',
$podcast->id,
'WebsiteByEntryPage'
) ?>"></div>
</div>
<div class="mb-12 mr-6 text-center">
<h2><?= lang('Charts.by_browser') ?></h2>
<div class="chart-pie" id="by-browser-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to(
'analytics-full-data',
$podcast->id,
'WebsiteByBrowser'
) ?>"></div>
</div>
</div>
<script src="/assets/charts.js" type="module"></script>
<?= $this->endSection() ?>