Add site analytics (close #8)
parent
88d7b539b9
commit
a3391f5ab6
Binary file not shown.
@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An array of two-letter to three-letter country codes, from
|
||||
* http://country.io/iso3.json
|
||||
*/
|
||||
$COUNTRY_CODES = array (
|
||||
'BD' => 'BGD',
|
||||
'BE' => 'BEL',
|
||||
'BF' => 'BFA',
|
||||
'BG' => 'BGR',
|
||||
'BA' => 'BIH',
|
||||
'BB' => 'BRB',
|
||||
'WF' => 'WLF',
|
||||
'BL' => 'BLM',
|
||||
'BM' => 'BMU',
|
||||
'BN' => 'BRN',
|
||||
'BO' => 'BOL',
|
||||
'BH' => 'BHR',
|
||||
'BI' => 'BDI',
|
||||
'BJ' => 'BEN',
|
||||
'BT' => 'BTN',
|
||||
'JM' => 'JAM',
|
||||
'BV' => 'BVT',
|
||||
'BW' => 'BWA',
|
||||
'WS' => 'WSM',
|
||||
'BQ' => 'BES',
|
||||
'BR' => 'BRA',
|
||||
'BS' => 'BHS',
|
||||
'JE' => 'JEY',
|
||||
'BY' => 'BLR',
|
||||
'BZ' => 'BLZ',
|
||||
'RU' => 'RUS',
|
||||
'RW' => 'RWA',
|
||||
'RS' => 'SRB',
|
||||
'TL' => 'TLS',
|
||||
'RE' => 'REU',
|
||||
'TM' => 'TKM',
|
||||
'TJ' => 'TJK',
|
||||
'RO' => 'ROU',
|
||||
'TK' => 'TKL',
|
||||
'GW' => 'GNB',
|
||||
'GU' => 'GUM',
|
||||
'GT' => 'GTM',
|
||||
'GS' => 'SGS',
|
||||
'GR' => 'GRC',
|
||||
'GQ' => 'GNQ',
|
||||
'GP' => 'GLP',
|
||||
'JP' => 'JPN',
|
||||
'GY' => 'GUY',
|
||||
'GG' => 'GGY',
|
||||
'GF' => 'GUF',
|
||||
'GE' => 'GEO',
|
||||
'GD' => 'GRD',
|
||||
'GB' => 'GBR',
|
||||
'GA' => 'GAB',
|
||||
'SV' => 'SLV',
|
||||
'GN' => 'GIN',
|
||||
'GM' => 'GMB',
|
||||
'GL' => 'GRL',
|
||||
'GI' => 'GIB',
|
||||
'GH' => 'GHA',
|
||||
'OM' => 'OMN',
|
||||
'TN' => 'TUN',
|
||||
'JO' => 'JOR',
|
||||
'HR' => 'HRV',
|
||||
'HT' => 'HTI',
|
||||
'HU' => 'HUN',
|
||||
'HK' => 'HKG',
|
||||
'HN' => 'HND',
|
||||
'HM' => 'HMD',
|
||||
'VE' => 'VEN',
|
||||
'PR' => 'PRI',
|
||||
'PS' => 'PSE',
|
||||
'PW' => 'PLW',
|
||||
'PT' => 'PRT',
|
||||
'SJ' => 'SJM',
|
||||
'PY' => 'PRY',
|
||||
'IQ' => 'IRQ',
|
||||
'PA' => 'PAN',
|
||||
'PF' => 'PYF',
|
||||
'PG' => 'PNG',
|
||||
'PE' => 'PER',
|
||||
'PK' => 'PAK',
|
||||
'PH' => 'PHL',
|
||||
'PN' => 'PCN',
|
||||
'PL' => 'POL',
|
||||
'PM' => 'SPM',
|
||||
'ZM' => 'ZMB',
|
||||
'EH' => 'ESH',
|
||||
'EE' => 'EST',
|
||||
'EG' => 'EGY',
|
||||
'ZA' => 'ZAF',
|
||||
'EC' => 'ECU',
|
||||
'IT' => 'ITA',
|
||||
'VN' => 'VNM',
|
||||
'SB' => 'SLB',
|
||||
'ET' => 'ETH',
|
||||
'SO' => 'SOM',
|
||||
'ZW' => 'ZWE',
|
||||
'SA' => 'SAU',
|
||||
'ES' => 'ESP',
|
||||
'ER' => 'ERI',
|
||||
'ME' => 'MNE',
|
||||
'MD' => 'MDA',
|
||||
'MG' => 'MDG',
|
||||
'MF' => 'MAF',
|
||||
'MA' => 'MAR',
|
||||
'MC' => 'MCO',
|
||||
'UZ' => 'UZB',
|
||||
'MM' => 'MMR',
|
||||
'ML' => 'MLI',
|
||||
'MO' => 'MAC',
|
||||
'MN' => 'MNG',
|
||||
'MH' => 'MHL',
|
||||
'MK' => 'MKD',
|
||||
'MU' => 'MUS',
|
||||
'MT' => 'MLT',
|
||||
'MW' => 'MWI',
|
||||
'MV' => 'MDV',
|
||||
'MQ' => 'MTQ',
|
||||
'MP' => 'MNP',
|
||||
'MS' => 'MSR',
|
||||
'MR' => 'MRT',
|
||||
'IM' => 'IMN',
|
||||
'UG' => 'UGA',
|
||||
'TZ' => 'TZA',
|
||||
'MY' => 'MYS',
|
||||
'MX' => 'MEX',
|
||||
'IL' => 'ISR',
|
||||
'FR' => 'FRA',
|
||||
'IO' => 'IOT',
|
||||
'SH' => 'SHN',
|
||||
'FI' => 'FIN',
|
||||
'FJ' => 'FJI',
|
||||
'FK' => 'FLK',
|
||||
'FM' => 'FSM',
|
||||
'FO' => 'FRO',
|
||||
'NI' => 'NIC',
|
||||
'NL' => 'NLD',
|
||||
'NO' => 'NOR',
|
||||
'NA' => 'NAM',
|
||||
'VU' => 'VUT',
|
||||
'NC' => 'NCL',
|
||||
'NE' => 'NER',
|
||||
'NF' => 'NFK',
|
||||
'NG' => 'NGA',
|
||||
'NZ' => 'NZL',
|
||||
'NP' => 'NPL',
|
||||
'NR' => 'NRU',
|
||||
'NU' => 'NIU',
|
||||
'CK' => 'COK',
|
||||
'XK' => 'XKX',
|
||||
'CI' => 'CIV',
|
||||
'CH' => 'CHE',
|
||||
'CO' => 'COL',
|
||||
'CN' => 'CHN',
|
||||
'CM' => 'CMR',
|
||||
'CL' => 'CHL',
|
||||
'CC' => 'CCK',
|
||||
'CA' => 'CAN',
|
||||
'CG' => 'COG',
|
||||
'CF' => 'CAF',
|
||||
'CD' => 'COD',
|
||||
'CZ' => 'CZE',
|
||||
'CY' => 'CYP',
|
||||
'CX' => 'CXR',
|
||||
'CR' => 'CRI',
|
||||
'CW' => 'CUW',
|
||||
'CV' => 'CPV',
|
||||
'CU' => 'CUB',
|
||||
'SZ' => 'SWZ',
|
||||
'SY' => 'SYR',
|
||||
'SX' => 'SXM',
|
||||
'KG' => 'KGZ',
|
||||
'KE' => 'KEN',
|
||||
'SS' => 'SSD',
|
||||
'SR' => 'SUR',
|
||||
'KI' => 'KIR',
|
||||
'KH' => 'KHM',
|
||||
'KN' => 'KNA',
|
||||
'KM' => 'COM',
|
||||
'ST' => 'STP',
|
||||
'SK' => 'SVK',
|
||||
'KR' => 'KOR',
|
||||
'SI' => 'SVN',
|
||||
'KP' => 'PRK',
|
||||
'KW' => 'KWT',
|
||||
'SN' => 'SEN',
|
||||
'SM' => 'SMR',
|
||||
'SL' => 'SLE',
|
||||
'SC' => 'SYC',
|
||||
'KZ' => 'KAZ',
|
||||
'KY' => 'CYM',
|
||||
'SG' => 'SGP',
|
||||
'SE' => 'SWE',
|
||||
'SD' => 'SDN',
|
||||
'DO' => 'DOM',
|
||||
'DM' => 'DMA',
|
||||
'DJ' => 'DJI',
|
||||
'DK' => 'DNK',
|
||||
'VG' => 'VGB',
|
||||
'DE' => 'DEU',
|
||||
'YE' => 'YEM',
|
||||
'DZ' => 'DZA',
|
||||
'US' => 'USA',
|
||||
'UY' => 'URY',
|
||||
'YT' => 'MYT',
|
||||
'UM' => 'UMI',
|
||||
'LB' => 'LBN',
|
||||
'LC' => 'LCA',
|
||||
'LA' => 'LAO',
|
||||
'TV' => 'TUV',
|
||||
'TW' => 'TWN',
|
||||
'TT' => 'TTO',
|
||||
'TR' => 'TUR',
|
||||
'LK' => 'LKA',
|
||||
'LI' => 'LIE',
|
||||
'LV' => 'LVA',
|
||||
'TO' => 'TON',
|
||||
'LT' => 'LTU',
|
||||
'LU' => 'LUX',
|
||||
'LR' => 'LBR',
|
||||
'LS' => 'LSO',
|
||||
'TH' => 'THA',
|
||||
'TF' => 'ATF',
|
||||
'TG' => 'TGO',
|
||||
'TD' => 'TCD',
|
||||
'TC' => 'TCA',
|
||||
'LY' => 'LBY',
|
||||
'VA' => 'VAT',
|
||||
'VC' => 'VCT',
|
||||
'AE' => 'ARE',
|
||||
'AD' => 'AND',
|
||||
'AG' => 'ATG',
|
||||
'AF' => 'AFG',
|
||||
'AI' => 'AIA',
|
||||
'VI' => 'VIR',
|
||||
'IS' => 'ISL',
|
||||
'IR' => 'IRN',
|
||||
'AM' => 'ARM',
|
||||
'AL' => 'ALB',
|
||||
'AO' => 'AGO',
|
||||
'AQ' => 'ATA',
|
||||
'AS' => 'ASM',
|
||||
'AR' => 'ARG',
|
||||
'AU' => 'AUS',
|
||||
'AT' => 'AUT',
|
||||
'AW' => 'ABW',
|
||||
'IN' => 'IND',
|
||||
'AX' => 'ALA',
|
||||
'AZ' => 'AZE',
|
||||
'IE' => 'IRL',
|
||||
'ID' => 'IDN',
|
||||
'UA' => 'UKR',
|
||||
'QA' => 'QAT',
|
||||
'MZ' => 'MOZ',
|
||||
);
|
@ -0,0 +1,269 @@
|
||||
<?php
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
require_once __DIR__ . '/../required.php';
|
||||
|
||||
redirectifnotloggedin();
|
||||
|
||||
$select_filter = [];
|
||||
|
||||
if (!is_empty($VARS['siteid'])) {
|
||||
if ($database->has('sites', ['siteid' => $VARS['siteid']])) {
|
||||
$select_filter["siteid"] = $VARS['siteid'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_empty($VARS['after'])) {
|
||||
if (strtotime($VARS['after']) !== FALSE) {
|
||||
$select_filter["time[>]"] = date("Y-m-d H:i:s", strtotime($VARS['after']));
|
||||
}
|
||||
}
|
||||
if (!is_empty($VARS['before'])) {
|
||||
if (strtotime($VARS['before']) !== FALSE) {
|
||||
$select_filter["time[<]"] = date("Y-m-d H:i:s", strtotime($VARS['before']));
|
||||
}
|
||||
}
|
||||
|
||||
$where = [];
|
||||
if (count($select_filter) == 1) {
|
||||
$where = $select_filter;
|
||||
} else if (count($select_filter) > 1) {
|
||||
$where = ["AND" => $select_filter];
|
||||
}
|
||||
|
||||
$where["LIMIT"] = 1000;
|
||||
$where["ORDER"] = ["time" => "DESC"];
|
||||
|
||||
$records = $database->select("analytics", [
|
||||
"[>]sites" => ["siteid" => "siteid"],
|
||||
"[>]pages" => ["pageid" => "pageid"],
|
||||
], [
|
||||
"analytics.siteid", "analytics.pageid", "uuid", "country", "region", "city",
|
||||
"countrycode", "regioncode",
|
||||
"lat", "lon", "time", "pages.title (pagetitle)", "pages.slug (pageslug)",
|
||||
"sites.sitename"
|
||||
], $where);
|
||||
?>
|
||||
|
||||
<!-- Filter bar -->
|
||||
<div class="card p-2">
|
||||
<form class="form-inline" action="app.php" method="GET">
|
||||
<button type="submit" class="btn btn-primary"><i class="fas fa-sync"></i></button>
|
||||
<label for="siteid_select" class="sr-only"><?php lang("filter by site") ?></label>
|
||||
<div class="input-group mx-2">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-sitemap"></i></span>
|
||||
</div>
|
||||
<select name="siteid" class="form-control pr-4" id="siteid_select">
|
||||
<option value=""><?php lang("all sites"); ?></option>
|
||||
<?php
|
||||
$sites = $database->select("sites", ["siteid", "sitename"]);
|
||||
foreach ($sites as $s) {
|
||||
$selected = "";
|
||||
if (!empty($select_filter["siteid"]) && $select_filter["siteid"] == $s['siteid']) {
|
||||
$selected = "selected";
|
||||
}
|
||||
?>
|
||||
<option value="<?php echo $s['siteid']; ?>" <?php echo $selected; ?>><?php echo $s['sitename']; ?></option>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<span class="vertline d-none d-lg-inline"></span>
|
||||
|
||||
<label for="date_after" class="sr-only"><?php lang("filter after date") ?></label>
|
||||
<label for="date_before" class="sr-only"><?php lang("filter before date") ?></label>
|
||||
<div class="input-group mx-2">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fas fa-calendar"></i></span>
|
||||
</div>
|
||||
<input type="text" id="date_after" name="after" value="<?php echo htmlspecialchars($VARS['after']); ?>" class="form-control" placeholder="<?php lang("start date"); ?>" data-toggle="datetimepicker" data-target="#date_after" />
|
||||
<div class="input-group-prepend input-group-append">
|
||||
<span class="input-group-text"><i class="fas fa-caret-right"></i></span>
|
||||
</div>
|
||||
<input type="text" id="date_before" name="before" value="<?php echo htmlspecialchars($VARS['before']); ?>" class="form-control" placeholder="<?php lang("end date"); ?>" data-toggle="datetimepicker" data-target="#date_before" />
|
||||
</div>
|
||||
|
||||
|
||||
<input type="hidden" name="page" value="analytics" />
|
||||
<button type="submit" class="btn btn-secondary"><i class="fas fa-filter"></i> <?php lang("filter"); ?></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Data views -->
|
||||
<?php
|
||||
if (count($records) > 0) {
|
||||
?>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 col-sm-6 col-md-6 col-lg-4 mb-4">
|
||||
<!-- Overview -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title"><?php lang("overview"); ?></h4>
|
||||
<?php
|
||||
$uuids = [];
|
||||
foreach ($records as $r) {
|
||||
if (!in_array($r["uuid"], $uuids)) {
|
||||
$uuids[] = $r["uuid"];
|
||||
}
|
||||
}
|
||||
$visits = count($uuids);
|
||||
$views = count($records);
|
||||
$ratio = round($views / $visits, 1);
|
||||
?>
|
||||
<h5>
|
||||
<i class="fas fa-users fa-fw"></i> <?php echo $visits; ?> <?php lang("visits") ?> <br />
|
||||
<i class="fas fa-eye fa-fw"></i> <?php echo $views; ?> <?php lang("page views") ?> <br />
|
||||
<i class="fas fa-percent fa-fw"></i> <?php echo $ratio; ?> <?php lang("views per visit") ?>
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Visits Over Time -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title"><?php lang("visits over time"); ?></h4>
|
||||
<?php
|
||||
$format = "Y-m-00 00:00:00";
|
||||
$max = $records[0];
|
||||
$min = $records[count($records) - 1];
|
||||
$diff = strtotime($max['time']) - strtotime($min['time']);
|
||||
if ($diff < 60 * 60) { // 1 hour
|
||||
$format = "Y-m-d H:i:00";
|
||||
} else if ($diff < 60 * 60 * 24 * 3) { // 3 days
|
||||
$format = "Y-m-d H:00:00";
|
||||
} else if ($diff < 60 * 60 * 24 * 60) { // 30 days
|
||||
$format = "Y-m-d 00:00:00";
|
||||
}
|
||||
|
||||
$counted = [];
|
||||
foreach ($records as $r) {
|
||||
$rf = date($format, strtotime($r['time']));
|
||||
if (array_key_exists($rf, $counted)) {
|
||||
$counted[$rf] ++;
|
||||
} else {
|
||||
$counted[$rf] = 1;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<script nonce="<?php echo $SECURE_NONCE; ?>">
|
||||
var visitsOverTimeData = [
|
||||
<?php foreach ($counted as $d => $c) { ?>
|
||||
{
|
||||
x: "<?php echo $d; ?>",
|
||||
y: <?php echo $c; ?>
|
||||
},
|
||||
<?php } ?>
|
||||
];
|
||||
</script>
|
||||
<div class="w-100 position-relative">
|
||||
<canvas id="visitsOverTime"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Actions -->
|
||||
<div class="col-12 col-sm-6 col-md-6 col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title"><?php lang("recent actions"); ?></h4>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<?php
|
||||
$max = 10;
|
||||
$i = 0;
|
||||
foreach ($records as $r) {
|
||||
$i++;
|
||||
if ($i > $max) {
|
||||
break;
|
||||
}
|
||||
?>
|
||||
<div class="list-group-item">
|
||||
<div>
|
||||
<div><i class="fas fa-user fa-fw"></i> <?php echo substr($r["uuid"], 0, 8); ?></div>
|
||||
</div>
|
||||
<div class="row justify-content-between">
|
||||
<div class="col-12 col-sm-6 d-flex flex-column">
|
||||
<span><i class="fas fa-clock fa-fw"></i> <?php echo date("g:i A", strtotime($r["time"])); ?></span>
|
||||
<span><i class="fas fa-file fa-fw"></i> <?php echo $r["pagetitle"]; ?></span>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 d-flex flex-column">
|
||||
<span><i class="fas fa-calendar fa-fw"></i> <?php echo date("M j Y", strtotime($r["time"])); ?></span>
|
||||
<span><i class="fas fa-sitemap fa-fw"></i> <?php echo $r["sitename"]; ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div><i class="fas fa-globe fa-fw"></i> <?php echo $r["country"]; ?></div>
|
||||
<div><i class="fas fa-map-marker fa-fw"></i> <?php echo $r["city"] . ", " . $r["region"]; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Visitor Map -->
|
||||
<div class="col-12 col-sm-6 col-md-6 col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title"><?php lang("visitor map"); ?></h4>
|
||||
<?php
|
||||
require_once __DIR__ . "/../lib/countries_2_3.php";
|
||||
$countries = [];
|
||||
$states = [];
|
||||
foreach ($records as $r) {
|
||||
if (array_key_exists($COUNTRY_CODES[$r['countrycode']], $countries)) {
|
||||
$countries[$COUNTRY_CODES[$r['countrycode']]] ++;
|
||||
} else {
|
||||
$countries[$COUNTRY_CODES[$r['countrycode']]] = 1;
|
||||
}
|
||||
if ($r['countrycode'] === "US") {
|
||||
if (array_key_exists($r['regioncode'], $states)) {
|
||||
$states[$r['regioncode']] ++;
|
||||
} else {
|
||||
$states[$r['regioncode']] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
$countrymapdata = [];
|
||||
foreach ($countries as $id => $count) {
|
||||
$countrymapdata[] = [$id, $count];
|
||||
}
|
||||
$statemapdata = [];
|
||||
foreach ($states as $id => $count) {
|
||||
$statemapdata[] = [$id, $count];
|
||||
}
|
||||
?>
|
||||
<script nonce="<?php echo $SECURE_NONCE; ?>">
|
||||
visitorMap_Countries = <?php echo json_encode($countrymapdata); ?>;
|
||||
visitorMap_States = <?php echo json_encode($statemapdata); ?>;
|
||||
</script>
|
||||
<div class="w-100" id="visitorMapWorld"></div>
|
||||
<div class="w-100" id="visitorMapUSA"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<div class="row mt-3 justify-content-center">
|
||||
<div class="col-12 col-sm-10 col-md-8 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-info-circle"></i> <?php lang("no data"); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
?>
|
@ -0,0 +1,204 @@
|
||||
/*@preserve
|
||||
* Tempus Dominus Bootstrap4 v5.0.0-alpha16 (https://tempusdominus.github.io/bootstrap-4/)
|
||||
* Copyright 2016-2018 Jonathan Peterson
|
||||
* Licensed under MIT (https://github.com/tempusdominus/bootstrap-3/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
.sr-only, .bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after, .bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after, .bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after, .bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after, .bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after, .bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after, .bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after, .bootstrap-datetimepicker-widget .btn[data-action="clear"]::after, .bootstrap-datetimepicker-widget .btn[data-action="today"]::after, .bootstrap-datetimepicker-widget .picker-switch::after, .bootstrap-datetimepicker-widget table th.prev::after, .bootstrap-datetimepicker-widget table th.next::after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0; }
|
||||
|
||||
.bootstrap-datetimepicker-widget {
|
||||
list-style: none; }
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu {
|
||||
display: block;
|
||||
margin: 2px 0;
|
||||
padding: 4px;
|
||||
width: 14rem; }
|
||||
@media (min-width: 576px) {
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
|
||||
width: 38em; } }
|
||||
@media (min-width: 768px) {
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
|
||||
width: 38em; } }
|
||||
@media (min-width: 992px) {
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
|
||||
width: 38em; } }
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu:before, .bootstrap-datetimepicker-widget.dropdown-menu:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
position: absolute; }
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before {
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid #ccc;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||
top: -7px;
|
||||
left: 7px; }
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid white;
|
||||
top: -6px;
|
||||
left: 8px; }
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.top:before {
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-top: 7px solid #ccc;
|
||||
border-top-color: rgba(0, 0, 0, 0.2);
|
||||
bottom: -7px;
|
||||
left: 6px; }
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.top:after {
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 6px solid white;
|
||||
bottom: -6px;
|
||||
left: 7px; }
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.float-right:before {
|
||||
left: auto;
|
||||
right: 6px; }
|
||||
.bootstrap-datetimepicker-widget.dropdown-menu.float-right:after {
|
||||
left: auto;
|
||||
right: 7px; }
|
||||
.bootstrap-datetimepicker-widget .list-unstyled {
|
||||
margin: 0; }
|
||||
.bootstrap-datetimepicker-widget a[data-action] {
|
||||
padding: 6px 0; }
|
||||
.bootstrap-datetimepicker-widget a[data-action]:active {
|
||||
box-shadow: none; }
|
||||
.bootstrap-datetimepicker-widget .timepicker-hour, .bootstrap-datetimepicker-widget .timepicker-minute, .bootstrap-datetimepicker-widget .timepicker-second {
|
||||
width: 54px;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
margin: 0; }
|
||||
.bootstrap-datetimepicker-widget button[data-action] {
|
||||
padding: 6px; }
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after {
|
||||
content: "Increment Hours"; }
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after {
|
||||
content: "Increment Minutes"; }
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after {
|
||||
content: "Decrement Hours"; }
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after {
|
||||
content: "Decrement Minutes"; }
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after {
|
||||
content: "Show Hours"; }
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after {
|
||||
content: "Show Minutes"; }
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after {
|
||||
content: "Toggle AM/PM"; }
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after {
|
||||
content: "Clear the picker"; }
|
||||
.bootstrap-datetimepicker-widget .btn[data-action="today"]::after {
|
||||
content: "Set the date to today"; }
|
||||
.bootstrap-datetimepicker-widget .picker-switch {
|
||||
text-align: center; }
|
||||
.bootstrap-datetimepicker-widget .picker-switch::after {
|
||||
content: "Toggle Date and Time Screens"; }
|
||||
.bootstrap-datetimepicker-widget .picker-switch td {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: auto;
|
||||
width: auto;
|
||||
line-height: inherit; }
|
||||
.bootstrap-datetimepicker-widget .picker-switch td span {
|
||||
line-height: 2.5;
|
||||
height: 2.5em;
|
||||
width: 100%; }
|
||||
.bootstrap-datetimepicker-widget table {
|
||||
width: 100%;
|
||||
margin: 0; }
|
||||
.bootstrap-datetimepicker-widget table td,
|
||||
.bootstrap-datetimepicker-widget table th {
|
||||
text-align: center;
|
||||
border-radius: 0.25rem; }
|
||||
.bootstrap-datetimepicker-widget table th {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
width: 20px; }
|
||||
.bootstrap-datetimepicker-widget table th.picker-switch {
|
||||
width: 145px; }
|
||||
.bootstrap-datetimepicker-widget table th.disabled, .bootstrap-datetimepicker-widget table th.disabled:hover {
|
||||
background: none;
|
||||
color: #868e96;
|
||||
cursor: not-allowed; }
|
||||
.bootstrap-datetimepicker-widget table th.prev::after {
|
||||
content: "Previous Month"; }
|
||||
.bootstrap-datetimepicker-widget table th.next::after {
|
||||
content: "Next Month"; }
|
||||
.bootstrap-datetimepicker-widget table thead tr:first-child th {
|
||||
cursor: pointer; }
|
||||
.bootstrap-datetimepicker-widget table thead tr:first-child th:hover {
|
||||
background: #e9ecef; }
|
||||
.bootstrap-datetimepicker-widget table td {
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
width: 54px; }
|
||||
.bootstrap-datetimepicker-widget table td.cw {
|
||||
font-size: .8em;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
color: #868e96; }
|
||||
.bootstrap-datetimepicker-widget table td.day {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
width: 20px; }
|
||||
.bootstrap-datetimepicker-widget table td.day:hover, .bootstrap-datetimepicker-widget table td.hour:hover, .bootstrap-datetimepicker-widget table td.minute:hover, .bootstrap-datetimepicker-widget table td.second:hover {
|
||||
background: #e9ecef;
|
||||
cursor: pointer; }
|
||||
.bootstrap-datetimepicker-widget table td.old, .bootstrap-datetimepicker-widget table td.new {
|
||||
color: #868e96; }
|
||||
.bootstrap-datetimepicker-widget table td.today {
|
||||
position: relative; }
|
||||
.bootstrap-datetimepicker-widget table td.today:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border: solid transparent;
|
||||
border-width: 0 0 7px 7px;
|
||||
border-bottom-color: #007bff;
|
||||
border-top-color: rgba(0, 0, 0, 0.2);
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 4px; }
|
||||
.bootstrap-datetimepicker-widget table td.active, .bootstrap-datetimepicker-widget table td.active:hover {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); }
|
||||
.bootstrap-datetimepicker-widget table td.active.today:before {
|
||||
border-bottom-color: #fff; }
|
||||
.bootstrap-datetimepicker-widget table td.disabled, .bootstrap-datetimepicker-widget table td.disabled:hover {
|
||||
background: none;
|
||||
color: #868e96;
|
||||
cursor: not-allowed; }
|
||||
.bootstrap-datetimepicker-widget table td span {
|
||||
display: inline-block;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
margin: 2px 1.5px;
|
||||
cursor: pointer;
|
||||
border-radius: 0.25rem; }
|
||||
.bootstrap-datetimepicker-widget table td span:hover {
|
||||
background: #e9ecef; }
|
||||
.bootstrap-datetimepicker-widget table td span.active {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); }
|
||||
.bootstrap-datetimepicker-widget table td span.old {
|
||||
color: #868e96; }
|
||||
.bootstrap-datetimepicker-widget table td span.disabled, .bootstrap-datetimepicker-widget table td span.disabled:hover {
|
||||
background: none;
|
||||
color: #868e96;
|
||||
cursor: not-allowed; }
|
||||
.bootstrap-datetimepicker-widget.usetwentyfour td.hour {
|
||||
height: 27px;
|
||||
line-height: 27px; }
|
||||
|
||||
.input-group [data-toggle="datetimepicker"] {
|
||||
cursor: pointer; }
|
@ -0,0 +1,13 @@
|
||||
/*
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.vertline {
|
||||
margin: 1px 3px;
|
||||
height: 100%;
|
||||
max-height: 50px;
|
||||
width: 1px;
|
||||
background-color: rgba(0,0,0,.20);
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Note: This script is *not* called "analytics.js" because adblockers.
|
||||
*/
|
||||
|
||||
|
||||
$(function () {
|
||||
$('#date_after').datetimepicker({
|
||||
format: "MMM D YYYY h:mm A",
|
||||
useCurrent: false,
|
||||
icons: {
|
||||
time: "fas fa-clock",
|
||||
date: "fas fa-calendar",
|
||||
up: "fas fa-arrow-up",
|
||||
down: "fas fa-arrow-down"
|
||||
}
|
||||
});
|
||||
$('#date_before').datetimepicker({
|
||||
format: "MMM D YYYY h:mm A",
|
||||
useCurrent: true,
|
||||
icons: {
|
||||
time: "fas fa-clock",
|
||||
date: "fas fa-calendar",
|
||||
up: "fas fa-arrow-up",
|
||||
down: "fas fa-arrow-down"
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var visitsOverTime = new Chart($("#visitsOverTime"), {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [{
|
||||
data: visitsOverTimeData
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
}],
|
||||
yAxes: [{
|
||||
type: 'linear',
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
callback: function (value) {
|
||||
if (Number.isInteger(value)) {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
}
|
||||
}]
|
||||
},
|
||||
tooltips: {
|
||||
displayColors: false,
|
||||
callbacks: {
|
||||
title: function (item) {
|
||||
var lbl = item[0].xLabel;
|
||||
if (lbl.endsWith("-00 00:00:00")) {
|
||||
return moment(lbl).format("MMM YYYY");
|
||||
} else if (lbl.endsWith(" 00:00:00")) {
|
||||
return moment(lbl).format("MMM D YYYY");
|
||||
} else if (lbl.endsWith(":00:00")) {
|
||||
return moment(lbl).format("MMM D YYYY ha");
|
||||
} else if (lbl.endsWith(":00")) {
|
||||
return moment(lbl).format("MMM D YYYY h:mma");
|
||||
}
|
||||
return item[0].xLabel;
|
||||
},
|
||||
label: function (item) {
|
||||
return item.yLabel + " visits";
|
||||
}
|
||||
}
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
borderWidth: 2,
|
||||
borderColor: "#ff0000",
|
||||
backgroundColor: "#ffffff00",
|
||||
tension: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
function getVisitorMapData(source) {
|
||||
var visitorMapDataset = {};
|
||||
|
||||
var onlyValues = source.map(function (obj) {
|
||||
return obj[1];
|
||||
});
|
||||
var minValue = Math.min.apply(null, onlyValues);
|
||||
var maxValue = Math.max.apply(null, onlyValues);
|
||||
|
||||
var paletteScale = d3.scale.linear()
|
||||
.domain([minValue, maxValue])
|
||||
.range(["#A5D6A7", "#124016"]); // blue color
|
||||
|
||||
source.forEach(function (item) { //
|
||||
// item example value ["USA", 70]
|
||||
var iso = item[0],
|
||||
value = item[1];
|
||||
visitorMapDataset[iso] = {numberOfThings: value, fillColor: paletteScale(value)};
|
||||
});
|
||||
|
||||
return visitorMapDataset;
|
||||
}
|
||||
|
||||
var visitorMap;
|
||||
|
||||
function showVisitorMap(data, scope, containerid) {
|
||||
$("visitorMap").html("");
|
||||
visitorMap = new Datamap({
|
||||
element: document.getElementById(containerid),
|
||||
scope: scope,
|
||||
responsive: true,
|
||||
fills: {defaultFill: '#F5F5F5'},
|
||||
data: data,
|
||||
geographyConfig: {
|
||||
borderColor: '#DEDEDE',
|
||||
highlightBorderWidth: 2,
|
||||
// don't change color on mouse hover
|
||||
highlightFillColor: function (geo) {
|
||||
return geo['fillColor'] || '#E8F5E9';
|
||||
},
|
||||
// only change border
|
||||
highlightBorderColor: '#00C853',
|
||||
// show desired information in tooltip
|
||||
popupTemplate: function (geo, data) {
|
||||
// don't show tooltip if country don't present in dataset
|
||||
if (!data) {
|
||||
//return;
|
||||
}
|
||||
// tooltip content
|
||||
return ['<div class="hoverinfo">',
|
||||
'<strong>', geo.properties.name, '</strong>',
|
||||
'<br>Visits: <strong>', data.numberOfThings, '</strong>',
|
||||
'</div>'].join('');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showVisitorMap(getVisitorMapData(visitorMap_Countries), 'world', 'visitorMapWorld');
|
||||
showVisitorMap(getVisitorMapData(visitorMap_States), 'usa', 'visitorMapUSA');
|
||||
|
||||
$(window).on('resize', function () {
|
||||
visitorMap.resize();
|
||||
});
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue