mirror of
https://gitea.phreedom.club/tolstoevsky/fedi.git
synced 2024-11-23 12:01:26 +00:00
4389 lines
745 KiB
HTML
4389 lines
745 KiB
HTML
|
<!DOCTYPE html><html><head><meta charset='UTF-8' /><meta http-equiv='X-UA-Compatible' content='IE=edge'><meta name='viewport' content='width=device-width, initial-scale=1'><meta name='robots' content='noindex, nofollow' /><title>Server Statistics</title><style>/*!* Font Awesome Free 5.0.10 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license(Icons:CC BY 4.0,Fonts:SIL OFL 1.1,Code:MIT License) */ .fa,.fas,.far,.fal,.fab{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:solid .08em #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fas.fa-pull-left,.far.fa-pull-left,.fal.fa-pull-left,.fab.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fas.fa-pull-right,.far.fa-pull-right,.fal.fa-pull-right,.fab.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0,mirror=1)";-webkit-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2,mirror=1)";-webkit-transform:scale(1,-1);transform:scale(1,-1)}.fa-flip-horizontal.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2,mirror=1)";-webkit-transform:scale(-1,-1);transform:scale(-1,-1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before,.fa-wheelchair-alt:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-address-book:before,.fa-address-book-o:before{content:"\f2b9"}.fa-address-card:before,.fa-vcard:before,.fa-address-card-o:before,.fa-vcard-o:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-america
|
|||
|
html,
|
|||
|
body {
|
|||
|
overflow-x: hidden;
|
|||
|
background: #f0f0f0;
|
|||
|
}
|
|||
|
h1 {
|
|||
|
font-weight: bold;
|
|||
|
letter-spacing: -3px;
|
|||
|
}
|
|||
|
h3 {
|
|||
|
font-size: 21px;
|
|||
|
letter-spacing: -1px;
|
|||
|
}
|
|||
|
.page-header {
|
|||
|
position: relative;
|
|||
|
margin: 25px 0 20px;
|
|||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.15);
|
|||
|
}
|
|||
|
.page-header h1 {
|
|||
|
margin: 0;
|
|||
|
}
|
|||
|
.pagination {
|
|||
|
margin: 5px 0;
|
|||
|
}
|
|||
|
.clickable,
|
|||
|
.expandable>td {
|
|||
|
cursor: pointer;
|
|||
|
}
|
|||
|
.spinner {
|
|||
|
color: #999;
|
|||
|
left: 50%;
|
|||
|
top: 50%;
|
|||
|
position: absolute;
|
|||
|
}
|
|||
|
.powered {
|
|||
|
bottom: 170px;
|
|||
|
color: #9E9E9E;
|
|||
|
font-size: smaller;
|
|||
|
position: absolute;
|
|||
|
right: 20px;
|
|||
|
transform-origin: 100% 0;
|
|||
|
transform: rotate(-90deg);
|
|||
|
}
|
|||
|
.powered a {
|
|||
|
color: #636363;
|
|||
|
}
|
|||
|
.dropdown-header {
|
|||
|
color: #007bc3;
|
|||
|
padding: 3px 25px;
|
|||
|
text-transform: uppercase;
|
|||
|
}
|
|||
|
.gheader {
|
|||
|
letter-spacing: -1px;
|
|||
|
text-transform: uppercase;
|
|||
|
}
|
|||
|
h5.gheader {
|
|||
|
letter-spacing: 0;
|
|||
|
}
|
|||
|
.panel-header h4.gheader {
|
|||
|
margin-top: 20px;
|
|||
|
}
|
|||
|
.panel-header .gheader small {
|
|||
|
font-size: 69%;
|
|||
|
}
|
|||
|
|
|||
|
/* NAVIGATION */
|
|||
|
nav {
|
|||
|
-webkit-transition: left .7s;
|
|||
|
background: #1C1C1C;
|
|||
|
border-right: 3px solid #5bc0de;
|
|||
|
height: 100%;
|
|||
|
left: -236px;
|
|||
|
position: fixed;
|
|||
|
top: 0;
|
|||
|
transition: left .7s;
|
|||
|
width: 300px;
|
|||
|
z-index: 2;
|
|||
|
overflow: hidden;
|
|||
|
}
|
|||
|
nav .nav-list {
|
|||
|
height: 100%;
|
|||
|
overflow-y: auto;
|
|||
|
width: 350px;
|
|||
|
}
|
|||
|
nav header {
|
|||
|
margin: 40px 20px 30px;
|
|||
|
}
|
|||
|
nav header a {
|
|||
|
font-size: 2.7em;
|
|||
|
font-weight: 300;
|
|||
|
text-transform: uppercase;
|
|||
|
color: rgba(240,240,240,.7);
|
|||
|
}
|
|||
|
nav header a:hover {
|
|||
|
color: #eee;
|
|||
|
}
|
|||
|
nav.active {
|
|||
|
display: block !important;
|
|||
|
left: 0;
|
|||
|
}
|
|||
|
nav:hover ~ #content {
|
|||
|
opacity: .3;
|
|||
|
}
|
|||
|
nav.active .nav-bars,
|
|||
|
nav.active .nav-gears,
|
|||
|
nav.active .nav-ws-status {
|
|||
|
opacity: 0;
|
|||
|
}
|
|||
|
nav .nav-bars,
|
|||
|
nav .nav-gears,
|
|||
|
nav .nav-ws-status {
|
|||
|
-webkit-transition: opacity .7s;
|
|||
|
color: #9E9E9E;
|
|||
|
cursor: pointer;
|
|||
|
float: right;
|
|||
|
font-size: 36px;
|
|||
|
height: 32px;
|
|||
|
left: 13px;
|
|||
|
line-height: 32px;
|
|||
|
position: fixed;
|
|||
|
text-align: center;
|
|||
|
top: 30px;
|
|||
|
transition: opacity .7s;
|
|||
|
width: 32px;
|
|||
|
}
|
|||
|
nav .nav-gears {
|
|||
|
top: 100px;
|
|||
|
opacity: 0.6;
|
|||
|
}
|
|||
|
nav .nav-ws-status,
|
|||
|
.nav-ws-status.mini {
|
|||
|
color: #6A6A6A;
|
|||
|
cursor: help;
|
|||
|
display: none;
|
|||
|
font-size: 12px;
|
|||
|
}
|
|||
|
nav .nav-ws-status {
|
|||
|
left: 25px;
|
|||
|
top: 125px;
|
|||
|
}
|
|||
|
.nav-ws-status.mini {
|
|||
|
top: 14px;
|
|||
|
left: 50px;
|
|||
|
position: absolute;
|
|||
|
}
|
|||
|
.nav-ws-status.connected {
|
|||
|
color: #5DB56A;
|
|||
|
}
|
|||
|
nav li a {
|
|||
|
border-left: 3px solid transparent;
|
|||
|
color: rgba(200,200,200,.5);
|
|||
|
display: block;
|
|||
|
font-size: smaller;
|
|||
|
max-width: 235px;
|
|||
|
opacity: 0;
|
|||
|
overflow: hidden;
|
|||
|
padding: 9px 20px;
|
|||
|
text-overflow: ellipsis;
|
|||
|
text-transform: uppercase;
|
|||
|
transition: opacity .7s;
|
|||
|
white-space: nowrap;
|
|||
|
}
|
|||
|
nav.active li a {
|
|||
|
max-width: 100%;
|
|||
|
opacity: 1;
|
|||
|
}
|
|||
|
nav li a:hover,
|
|||
|
nav li.active a {
|
|||
|
background: rgba(0,0,0,.1);
|
|||
|
border-color: #5BC0DE;
|
|||
|
color: #eee;
|
|||
|
}
|
|||
|
nav ul {
|
|||
|
padding-left: 0;
|
|||
|
list-style: none;
|
|||
|
}
|
|||
|
/* Navigation -- Icon */
|
|||
|
nav a,
|
|||
|
nav a:hover {
|
|||
|
text-decoration: none;
|
|||
|
}
|
|||
|
nav h3 {
|
|||
|
color: #FFF !important;
|
|||
|
font-size: medium;
|
|||
|
font-weight: bold;
|
|||
|
margin: 20px 25px 10px;
|
|||
|
text-transform: uppercase;
|
|||
|
}
|
|||
|
|
|||
|
/* CONTAINER */
|
|||
|
@media screen and (max-width: 767px) {
|
|||
|
.row-offcanvas {
|
|||
|
-webkit-transition: all .25s ease-out;
|
|||
|
-o-transition: all .25s ease-out;
|
|||
|
position: relative;
|
|||
|
transition: all .25s ease-out;
|
|||
|
}
|
|||
|
|
|||
|
.row-offcanvas-right {
|
|||
|
right: 0;
|
|||
|
}
|
|||
|
|
|||
|
.row-offcanvas-left {
|
|||
|
left: 0;
|
|||
|
}
|
|||
|
|
|||
|
.row-offcanvas-right
|
|||
|
.sidebar-offcanvas {
|
|||
|
right: -50%;
|
|||
|
}
|
|||
|
|
|||
|
.row-offcanvas-left
|
|||
|
.sidebar-offcanvas {
|
|||
|
left: -50%;
|
|||
|
}
|
|||
|
|
|||
|
.row-offcanvas-right.active {
|
|||
|
right: 50%;
|
|||
|
}
|
|||
|
|
|||
|
.row-offcanvas-left.active {
|
|||
|
left: 50%;
|
|||
|
}
|
|||
|
|
|||
|
.sidebar-offcanvas {
|
|||
|
position: absolute;
|
|||
|
top: 0;
|
|||
|
width: 50%;
|
|||
|
};
|
|||
|
}
|
|||
|
@media (min-width: 768px) {
|
|||
|
.container {
|
|||
|
width: 750px;
|
|||
|
};
|
|||
|
}
|
|||
|
@media (max-width: 480px) {
|
|||
|
.wrap-general h5,
|
|||
|
.wrap-panel h5 {
|
|||
|
white-space: nowrap;
|
|||
|
overflow: hidden;
|
|||
|
text-overflow: ellipsis;
|
|||
|
}
|
|||
|
.wrap-general h5 {
|
|||
|
width: 100%
|
|||
|
}
|
|||
|
.wrap-panel h5 {
|
|||
|
width: 70%
|
|||
|
}
|
|||
|
}
|
|||
|
.container-fluid {
|
|||
|
margin-left: 75px;
|
|||
|
}
|
|||
|
@media (min-width: 1120px) {
|
|||
|
.container {
|
|||
|
width: 970px;
|
|||
|
};
|
|||
|
}
|
|||
|
@media (min-width: 1320px) {
|
|||
|
.container {
|
|||
|
width: 1170px;
|
|||
|
};
|
|||
|
}
|
|||
|
@media (max-width: 992px) {
|
|||
|
.container-fluid {
|
|||
|
margin-left: auto;
|
|||
|
};
|
|||
|
}
|
|||
|
@media (max-width: 768px) {
|
|||
|
.container-fluid {
|
|||
|
padding-left: 5px;
|
|||
|
padding-right: 5px;
|
|||
|
}
|
|||
|
.page-header {
|
|||
|
padding: 0 10px;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* PANEL STYLES */
|
|||
|
.wrap-panel .panel-header {
|
|||
|
position: relative;
|
|||
|
}
|
|||
|
div.wrap-panel > div {
|
|||
|
background: #FFF;
|
|||
|
margin-top: 10px;
|
|||
|
padding: 0 10px;
|
|||
|
border-top: 1px solid rgba(0, 0, 0, 0.15);
|
|||
|
}
|
|||
|
|
|||
|
/* PANEL TABLES */
|
|||
|
.wrap-panel table.table-borderless tbody tr td,
|
|||
|
.wrap-panel table.table-borderless tbody tr th,
|
|||
|
.wrap-panel table.table-borderless thead tr th {
|
|||
|
border: none;
|
|||
|
}
|
|||
|
.wrap-panel table thead tr th {
|
|||
|
text-align: right;
|
|||
|
border-bottom-width: 1px;
|
|||
|
}
|
|||
|
.wrap-panel table .string,
|
|||
|
.wrap-panel table .date {
|
|||
|
text-align: left;
|
|||
|
}
|
|||
|
.wrap-panel table .percent {
|
|||
|
color: #898989;
|
|||
|
}
|
|||
|
.wrap-panel table td,
|
|||
|
.wrap-panel table th {
|
|||
|
white-space: nowrap;
|
|||
|
overflow: hidden;
|
|||
|
}
|
|||
|
.wrap-panel table th.sortable {
|
|||
|
cursor: pointer;
|
|||
|
}
|
|||
|
/* thead meta */
|
|||
|
.wrap-panel table tbody.tbody-meta {
|
|||
|
border-top: 1px solid #C7C7C7;
|
|||
|
border-bottom: 1px solid #C7C7C7;
|
|||
|
}
|
|||
|
.wrap-panel table tbody.tbody-meta tr {
|
|||
|
background-color: #F1F1F1;
|
|||
|
color: #222;
|
|||
|
}
|
|||
|
.wrap-panel table tbody.tbody-meta small {
|
|||
|
font-size: 65%;
|
|||
|
}
|
|||
|
/* thead data */
|
|||
|
.wrap-panel table tbody.tbody-data tr td {
|
|||
|
border-right: 1px solid #F1F1F1;
|
|||
|
font-size: smaller;
|
|||
|
}
|
|||
|
.wrap-panel table tbody.tbody-data td:last-child {
|
|||
|
border-right: none;
|
|||
|
}
|
|||
|
.wrap-panel table tbody.tbody-data td.row-idx {
|
|||
|
color: #898989;
|
|||
|
}
|
|||
|
.wrap-panel table>tbody+tbody {
|
|||
|
border-top-width: 1px;
|
|||
|
}
|
|||
|
.wrap-panel table tbody.tbody-data tr.shaded {
|
|||
|
background-color: #F7F7F7;
|
|||
|
}
|
|||
|
.wrap-panel table tbody.tbody-data tr. {
|
|||
|
background-color: #F7F7F7;
|
|||
|
}
|
|||
|
.wrap-panel table tbody.tbody-data tr.child td:nth-child(1),
|
|||
|
.wrap-panel table tbody.tbody-data tr.child td:nth-child(2) {
|
|||
|
border-right: none;
|
|||
|
}
|
|||
|
.wrap-panel table.table-hover>tbody>tr:hover {
|
|||
|
background-color: #EEE;
|
|||
|
}
|
|||
|
|
|||
|
/* GENERAL */
|
|||
|
.wrap-general {
|
|||
|
position: relative;
|
|||
|
}
|
|||
|
.report-title {
|
|||
|
background: #FFF;
|
|||
|
border-radius: 4px;
|
|||
|
bottom: -10px;
|
|||
|
color: #9E9E9E;
|
|||
|
font-size: small;
|
|||
|
padding: 0 10px;
|
|||
|
position: absolute;
|
|||
|
right: 0;
|
|||
|
z-index: 1;
|
|||
|
}
|
|||
|
.panel-plot-wrap {
|
|||
|
position: absolute;
|
|||
|
right: 0;
|
|||
|
top: 18px;
|
|||
|
}
|
|||
|
.col-title {
|
|||
|
font-size: 85%;
|
|||
|
overflow: hidden;
|
|||
|
text-overflow: ellipsis;
|
|||
|
text-shadow: 1px 1px 0 #FFF;
|
|||
|
white-space: nowrap;
|
|||
|
width: 100%;
|
|||
|
}
|
|||
|
.grid-module {
|
|||
|
background: #FFF;
|
|||
|
color: rgb(36, 36, 36);
|
|||
|
font-weight: normal;
|
|||
|
margin-top: 5px;
|
|||
|
padding: 7px;
|
|||
|
}
|
|||
|
.grid-module h3 {
|
|||
|
font-size: 25px;
|
|||
|
margin: 0;
|
|||
|
overflow: hidden;
|
|||
|
text-overflow: ellipsis;
|
|||
|
white-space: nowrap;
|
|||
|
width: 100%;
|
|||
|
}
|
|||
|
.grid-module.black {
|
|||
|
border-top: 4px solid #0F1214;
|
|||
|
}
|
|||
|
.grid-module.gray {
|
|||
|
border-top: 4px solid #9E9E9E;
|
|||
|
}
|
|||
|
.grid-module.red {
|
|||
|
border-top: 4px solid #FF303E;
|
|||
|
}
|
|||
|
.grid-module.blue{
|
|||
|
border-top: 4px solid #00D4E1;
|
|||
|
}
|
|||
|
.grid-module.green {
|
|||
|
border-top: 4px solid #5DB56A;
|
|||
|
}
|
|||
|
@media (max-width: 767px) {
|
|||
|
.panel-plot-wrap {
|
|||
|
top: 10px;
|
|||
|
}
|
|||
|
.powered {
|
|||
|
bottom: 10px;
|
|||
|
left: 25px;
|
|||
|
transform: initial;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* CHARTS */
|
|||
|
.chart-wrap {
|
|||
|
margin-bottom: 15px;
|
|||
|
position: relative;
|
|||
|
}
|
|||
|
svg {
|
|||
|
background-color: #fff;
|
|||
|
display: block;
|
|||
|
}
|
|||
|
.axis path {
|
|||
|
fill: transparent;
|
|||
|
stroke: black;
|
|||
|
shape-rendering: crispEdges;
|
|||
|
stroke-width: 1;
|
|||
|
}
|
|||
|
.grid.y .tick line,
|
|||
|
.grid.x .tick line {
|
|||
|
shape-rendering: crispEdges;
|
|||
|
stroke: #999;
|
|||
|
stroke-dasharray: 3 3;
|
|||
|
stroke-width: 1;
|
|||
|
}
|
|||
|
.axis.x .tick line,
|
|||
|
.axis.y0 .tick line,
|
|||
|
.axis.y1 .tick line,
|
|||
|
.grid.y .tick:first-child line {
|
|||
|
stroke: black;
|
|||
|
stroke-width: 1;
|
|||
|
shape-rendering: crispEdges;
|
|||
|
}
|
|||
|
.bars rect.bar {
|
|||
|
shape-rendering: crispEdges;
|
|||
|
}
|
|||
|
.rects rect {
|
|||
|
fill: transparent;
|
|||
|
}
|
|||
|
.area {
|
|||
|
opacity: 0.2;
|
|||
|
}
|
|||
|
.points {
|
|||
|
stroke: transparent;
|
|||
|
}
|
|||
|
line.indicator {
|
|||
|
fill: transparent;
|
|||
|
pointer-events: none;
|
|||
|
shape-rendering: crispEdges;
|
|||
|
stroke: #999;
|
|||
|
stroke-width: 1;
|
|||
|
}
|
|||
|
.area0,
|
|||
|
.bars.y0 .bar,
|
|||
|
.points.y0,
|
|||
|
rect.legend.y0 {
|
|||
|
fill: #447FB3;
|
|||
|
}
|
|||
|
.area1,
|
|||
|
.bars.y1 .bar,
|
|||
|
.points.y1,
|
|||
|
rect.legend.y1 {
|
|||
|
fill: #FF6854;
|
|||
|
}
|
|||
|
.line0,
|
|||
|
.line1 {
|
|||
|
fill: transparent;
|
|||
|
stroke-width: 1;
|
|||
|
}
|
|||
|
.line0 {
|
|||
|
stroke: #007BC3;
|
|||
|
}
|
|||
|
.line1 {
|
|||
|
stroke: #FF303E;
|
|||
|
}
|
|||
|
.axis text,
|
|||
|
.axis-label,
|
|||
|
text.legend {
|
|||
|
font: 10px sans-serif;
|
|||
|
}
|
|||
|
.axis-label.y0,
|
|||
|
.axis-label.y1 {
|
|||
|
text-anchor: end;
|
|||
|
}
|
|||
|
rect.legend {
|
|||
|
height: 10px;
|
|||
|
width: 10px;
|
|||
|
}
|
|||
|
.legend {
|
|||
|
cursor: pointer;
|
|||
|
}
|
|||
|
.wrap-text text {
|
|||
|
text-anchor: start!important;
|
|||
|
}
|
|||
|
|
|||
|
/* CHART TOOLTIP */
|
|||
|
.chart-tooltip-wrap {
|
|||
|
left: 0;
|
|||
|
pointer-events: none;
|
|||
|
position: absolute;
|
|||
|
top: 10px;
|
|||
|
z-index: 10;
|
|||
|
}
|
|||
|
.chart-tooltip {
|
|||
|
-moz-box-shadow: 7px 7px 12px -9px #777777;
|
|||
|
-webkit-box-shadow: 7px 7px 12px -9px #777777;
|
|||
|
background-color: #fff;
|
|||
|
border-collapse: collapse;
|
|||
|
border-spacing: 0;
|
|||
|
box-shadow: 7px 7px 12px -9px #777777;
|
|||
|
empty-cells: show;
|
|||
|
opacity: 0.9;
|
|||
|
}
|
|||
|
.chart-tooltip tr {
|
|||
|
border: 1px solid #CCC;
|
|||
|
}
|
|||
|
.chart-tooltip th {
|
|||
|
background-color: #aaa;
|
|||
|
color: #FFF;
|
|||
|
font-size: 14px;
|
|||
|
max-width: 380px;
|
|||
|
overflow: hidden;
|
|||
|
padding: 2px 5px;
|
|||
|
text-align: left;
|
|||
|
text-overflow: ellipsis;
|
|||
|
white-space: nowrap;
|
|||
|
}
|
|||
|
.chart-tooltip td {
|
|||
|
border-left: 1px dotted #999;
|
|||
|
font-size: 13px;
|
|||
|
padding: 3px 6px;
|
|||
|
}
|
|||
|
.chart-tooltip td > span {
|
|||
|
display: inline-block;
|
|||
|
height: 10px;
|
|||
|
margin-right: 6px;
|
|||
|
width: 10px;
|
|||
|
}
|
|||
|
.chart-tooltip td.value {
|
|||
|
text-align: right;
|
|||
|
}
|
|||
|
.chart-tooltip .blue {
|
|||
|
background-color: #007BC3;
|
|||
|
}
|
|||
|
.chart-tooltip .red {
|
|||
|
background-color: #FF303E;
|
|||
|
}
|
|||
|
|
|||
|
/* DARK THEME */
|
|||
|
.dark h1 {
|
|||
|
color: rgba(255, 255, 255, 0.6);
|
|||
|
}
|
|||
|
.dark h3,
|
|||
|
.dark h4,
|
|||
|
.dark h5 {
|
|||
|
color: rgba(255,255,255,0.4);
|
|||
|
}
|
|||
|
.dark .table-responsive {
|
|||
|
border: none;
|
|||
|
}
|
|||
|
.dark .wrap-panel > div > table {
|
|||
|
color: #D2D2D2;
|
|||
|
}
|
|||
|
.dark .wrap-panel table tbody.tbody-meta tr {
|
|||
|
background-color: transparent;
|
|||
|
color: #F7F7F7;
|
|||
|
}
|
|||
|
.dark .wrap-panel table tbody.tbody-data tr td {
|
|||
|
border-right: none;
|
|||
|
}
|
|||
|
.dark .wrap-panel table.table-hover>tbody.tbody-data>tr:hover {
|
|||
|
background-color: rgba(255, 255, 255, 0.08) !important;
|
|||
|
}
|
|||
|
.dark .col-title {
|
|||
|
color: #9e9e9e;
|
|||
|
text-shadow:none;
|
|||
|
}
|
|||
|
.dark .grid-module h3 {
|
|||
|
color: #FFF;
|
|||
|
}
|
|||
|
.dark .dropdown-menu>li>a {
|
|||
|
color: #FFF;
|
|||
|
}
|
|||
|
.dark div.wrap-panel > div {
|
|||
|
color: #EEE;
|
|||
|
margin-top: 10px;
|
|||
|
padding: 0 10px;
|
|||
|
border-top: 1px solid rgba(255, 255, 255, 0.15);
|
|||
|
}
|
|||
|
|
|||
|
/* DARK BLUE THEME */
|
|||
|
html.dark.blue,
|
|||
|
.dark.blue body {
|
|||
|
background: #252B30;
|
|||
|
}
|
|||
|
.dark.blue .container {
|
|||
|
background: #252B30;
|
|||
|
}
|
|||
|
.dark.blue .page-header {
|
|||
|
border-bottom: 1px solid #3B444C;
|
|||
|
}
|
|||
|
.dark.blue .label-info {
|
|||
|
background-color: #252B30;
|
|||
|
}
|
|||
|
.dark.blue nav {
|
|||
|
border-right: 1px solid #181B1F;
|
|||
|
background: #1F2328;
|
|||
|
}
|
|||
|
.dark.blue div.wrap-panel > div {
|
|||
|
background: #1F2328;
|
|||
|
}
|
|||
|
.dark.blue .wrap-panel table tbody.tbody-meta {
|
|||
|
border-top: 1px solid #3B444C;
|
|||
|
border-bottom: 1px solid #3B444C;
|
|||
|
}
|
|||
|
.dark.blue .wrap-panel table tbody.tbody-data tr.shaded {
|
|||
|
background-color: #181B1F;
|
|||
|
}
|
|||
|
.dark.blue .gray {
|
|||
|
border-top: 4px solid #3B444C;
|
|||
|
}
|
|||
|
.dark.blue .grid-module {
|
|||
|
background: #1F2328;
|
|||
|
}
|
|||
|
.dark.blue .btn-default {
|
|||
|
color: #9E9E9E;
|
|||
|
background-color: #1F2328;
|
|||
|
border-color: #3B444C;
|
|||
|
}
|
|||
|
.dark.blue .btn-default:active,
|
|||
|
.dark.blue .btn-default:hover,
|
|||
|
.dark.blue .btn-default.active,
|
|||
|
.dark.blue .open>.dropdown-toggle.btn-default {
|
|||
|
color: #3B444C;
|
|||
|
background-color: #1F2328;
|
|||
|
border-color: #0F1214;
|
|||
|
}
|
|||
|
.dark.blue .pagination>.disabled>a,
|
|||
|
.dark.blue .pagination>.disabled>a:hover,
|
|||
|
.dark.blue .pagination>.disabled>a:focus {
|
|||
|
color: #777;
|
|||
|
}
|
|||
|
.dark.blue .pagination>li>a {
|
|||
|
background-color: #1F2328;
|
|||
|
border: 1px solid #3B444C;
|
|||
|
}
|
|||
|
.dark.blue .pagination>li>a:hover,
|
|||
|
.dark.blue .pagination>li>a:active,
|
|||
|
.dark.blue .pagination>li>a:focus {
|
|||
|
color: #0370B0;
|
|||
|
background-color: #1F2328;
|
|||
|
border-color: #3B444C;
|
|||
|
}
|
|||
|
.dark.blue .dropdown-menu>li>a:hover,
|
|||
|
.dark.blue .dropdown-menu>li>a:focus {
|
|||
|
color: #FFF;
|
|||
|
background-color: #3B444C;
|
|||
|
}
|
|||
|
.dark.blue .dropdown-menu {
|
|||
|
background-color: #252B30;
|
|||
|
}
|
|||
|
.dark.blue::-webkit-scrollbar-track,
|
|||
|
.dark.blue .table-responsive::-webkit-scrollbar-track {
|
|||
|
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
|
|||
|
background-color: #9E9E9E;
|
|||
|
}
|
|||
|
.dark.blue::-webkit-scrollbar,
|
|||
|
.dark.blue .table-responsive::-webkit-scrollbar {
|
|||
|
width: 10px;
|
|||
|
height: 10px;
|
|||
|
background-color: #9E9E9E;
|
|||
|
}
|
|||
|
.dark.blue::-webkit-scrollbar-thumb,
|
|||
|
.dark.blue .table-responsive::-webkit-scrollbar-thumb {
|
|||
|
background-color: #3B444C;
|
|||
|
}
|
|||
|
.dark.blue .chart-tooltip {
|
|||
|
background-color: #252B30;
|
|||
|
}
|
|||
|
.dark.blue .report-title {
|
|||
|
background: #1F2328;
|
|||
|
}
|
|||
|
|
|||
|
/* DARK GREY THEME */
|
|||
|
html.dark.gray,
|
|||
|
.dark.gray body {
|
|||
|
background: #212121;
|
|||
|
}
|
|||
|
.dark.gray .container {
|
|||
|
background: #212121;
|
|||
|
}
|
|||
|
.dark.gray .page-header {
|
|||
|
border-bottom: 1px solid #303030;
|
|||
|
}
|
|||
|
.dark.gray .label-info {
|
|||
|
background-color: #303030;
|
|||
|
}
|
|||
|
.dark.gray nav {
|
|||
|
border-right: 1px solid #363737;
|
|||
|
background: #1C1C1C;
|
|||
|
}
|
|||
|
.dark.gray div.wrap-panel > div {
|
|||
|
background: #1C1C1C;
|
|||
|
}
|
|||
|
.dark.gray .wrap-panel table tbody.tbody-meta {
|
|||
|
border-top: 1px solid #363737;
|
|||
|
border-bottom: 1px solid #363737;
|
|||
|
}
|
|||
|
.dark.gray .wrap-panel table tbody.tbody-data tr.shaded {
|
|||
|
background-color: rgba(48, 48, 48, 0.48);
|
|||
|
}
|
|||
|
.dark.gray .gray {
|
|||
|
border-top: 4px solid #303030;
|
|||
|
}
|
|||
|
.dark.gray .grid-module {
|
|||
|
background: #1C1C1C;
|
|||
|
}
|
|||
|
.dark.gray .btn-default {
|
|||
|
color: #9E9E9E;
|
|||
|
background-color: #212121;
|
|||
|
border-color: #303030;
|
|||
|
}
|
|||
|
.dark.gray .btn-default:active,
|
|||
|
.dark.gray .btn-default:hover,
|
|||
|
.dark.gray .btn-default.active,
|
|||
|
.dark.gray .open>.dropdown-toggle.btn-default {
|
|||
|
color: #363737;
|
|||
|
background-color: #1C1C1C;
|
|||
|
border-color: #0F1214;
|
|||
|
}
|
|||
|
.dark.gray .pagination>.disabled>a,
|
|||
|
.dark.gray .pagination>.disabled>a:hover,
|
|||
|
.dark.gray .pagination>.disabled>a:focus {
|
|||
|
color: #777;
|
|||
|
}
|
|||
|
.dark.gray .pagination>li>a {
|
|||
|
background-color: #212121;
|
|||
|
border: 1px solid #303030;
|
|||
|
}
|
|||
|
.dark.gray .pagination>li>a:hover,
|
|||
|
.dark.gray .pagination>li>a:active,
|
|||
|
.dark.gray .pagination>li>a:focus {
|
|||
|
color: #0370B0;
|
|||
|
background-color: #212121;
|
|||
|
border-color: #303030;
|
|||
|
}
|
|||
|
.dark.gray .dropdown-menu>li>a {
|
|||
|
color: #FFF;
|
|||
|
}
|
|||
|
.dark.gray .dropdown-menu>li>a:hover,
|
|||
|
.dark.gray .dropdown-menu>li>a:focus {
|
|||
|
color: #FFF;
|
|||
|
background-color: #303030;
|
|||
|
}
|
|||
|
.dark.gray .dropdown-menu {
|
|||
|
background-color: #212121;
|
|||
|
}
|
|||
|
.dark.gray::-webkit-scrollbar-track,
|
|||
|
.dark.gray .table-responsive::-webkit-scrollbar-track {
|
|||
|
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
|
|||
|
background-color: #9E9E9E;
|
|||
|
}
|
|||
|
.dark.gray::-webkit-scrollbar,
|
|||
|
.dark.gray .table-responsive::-webkit-scrollbar {
|
|||
|
width: 10px;
|
|||
|
height: 10px;
|
|||
|
background-color: #9E9E9E;
|
|||
|
}
|
|||
|
.dark.gray::-webkit-scrollbar-thumb,
|
|||
|
.dark.gray .table-responsive::-webkit-scrollbar-thumb {
|
|||
|
background-color: #303030;
|
|||
|
}
|
|||
|
.dark.gray .chart-tooltip {
|
|||
|
background-color: #303030;
|
|||
|
}
|
|||
|
.dark.gray .report-title {
|
|||
|
background: #303030;
|
|||
|
}
|
|||
|
|
|||
|
/* DARK CHARTS */
|
|||
|
.dark svg {
|
|||
|
background-color: transparent;
|
|||
|
}
|
|||
|
.dark .area {
|
|||
|
opacity: 0.1;
|
|||
|
}
|
|||
|
.dark .line0,
|
|||
|
.dark .line1 {
|
|||
|
stroke-width: 2;
|
|||
|
}
|
|||
|
.dark .area0,
|
|||
|
.dark .bars.y0 .bar,
|
|||
|
.dark rect.legend.y0 {
|
|||
|
fill: #007BC3;
|
|||
|
}
|
|||
|
.dark .area1,
|
|||
|
.dark .bars.y1 .bar,
|
|||
|
.dark .points.y1,
|
|||
|
.dark rect.legend.y1 {
|
|||
|
fill: #FF303E;
|
|||
|
}
|
|||
|
.dark .points.y0 {
|
|||
|
fill: #00D4E1;
|
|||
|
}
|
|||
|
.dark .line0 {
|
|||
|
stroke: #007BC3;
|
|||
|
}
|
|||
|
.dark .line1 {
|
|||
|
stroke: #FF303E;
|
|||
|
}
|
|||
|
.dark .grid.y .tick line,
|
|||
|
.dark .grid.x .tick line {
|
|||
|
stroke: #44474B;
|
|||
|
stroke-dasharray: 1 1;
|
|||
|
}
|
|||
|
.dark .axis text,
|
|||
|
.dark .axis-label,
|
|||
|
.dark text.legend {
|
|||
|
fill: #9E9E9E;
|
|||
|
}
|
|||
|
.dark .axis path {
|
|||
|
stroke: #999999;
|
|||
|
}
|
|||
|
.dark .axis.x .tick line,
|
|||
|
.dark .axis.y0 .tick line,
|
|||
|
.dark .axis.y1 .tick line,
|
|||
|
.dark .grid.y .tick:first-child line {
|
|||
|
stroke: #3B444C;
|
|||
|
}
|
|||
|
.dark .chart-tooltip th {
|
|||
|
background-color: #1c1c1c;
|
|||
|
}
|
|||
|
.dark .chart-tooltip tr {
|
|||
|
border: 1px solid #363737;
|
|||
|
}
|
|||
|
</style></head><body><nav class='hidden-xs hidden-sm hide'></nav><i class='spinner fa fa-circle-o-notch fa-spin fa-3x fa-fw'></i><div class='container hide'><div class='wrap-header'><div class='row row-offcanvas row-offcanvas-right'><div class='col-md-12'><div class='page-header clearfix'><div class='pull-right'><h4><span class='label label-info' style='display:block'><span class='hidden-xs'>Last Updated: </span><span class='last-updated'>2020-09-16 15:46:22 +0200</span></span></h4></div><h1><span class='hidden-xs hidden-sm'><i class='fa fa-tachometer'></i> Dashboard</span><span class='visible-xs visible-sm'><i class='fa fa-bars nav-minibars'></i><i class='fa fa-circle nav-ws-status mini'></i></span></h1><div class='report-title'></div></div><div class='wrap-general'></div></div></div></div><div class='wrap-panels'></div></div><!-- TPL General -->
|
|||
|
<script id="tpl-general" type="text/template">
|
|||
|
<h4 class="hidden-xs gheader">{{head}}<span class="pull-right"><span class="from"></span> — <span class="to"></span></span></h4>
|
|||
|
<h5 class="visible-xs hidden-sm hidden-md hidden-lg gheader">{{head}} <span class="from"></span> — <span class="to"></span></span></h5>
|
|||
|
<div class="wrap-general-items"></div>
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- TPL General Items -->
|
|||
|
<script id="tpl-general-items" type="text/template">
|
|||
|
<div class="col-md-2">
|
|||
|
<div class="grid-module {{#className}}{{className}}{{/className}}{{^className}}gray{{/className}}">
|
|||
|
<div class="col-title">
|
|||
|
<i class="fa fa-bar-chart"></i> {{#label}}{{label}}{{/label}}
|
|||
|
</div>
|
|||
|
<h3 id="{{id}}" style="padding-top: 0;">{{value}}</h3>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- TPL Panel Table -->
|
|||
|
<script id="tpl-table-row" type="text/template">
|
|||
|
{{#rows}}
|
|||
|
<tr class="{{#className}}{{className}}{{/className}} {{#hasSubItems}}{{#items}}expandable{{/items}}{{/hasSubItems}}" {{#idx}}data-pid="{{idx}}"{{/idx}} data-panel="{{panel}}" {{#key}}data-key="{{key}}"{{/key}}>
|
|||
|
{{#hasSubItems}}
|
|||
|
<td class="row-expandable text-center {{#items}}clickable{{/items}}">
|
|||
|
{{#items}}<i class="fa {{#expanded}}fa-caret-down{{/expanded}}{{^expanded}}fa fa-caret-right{{/expanded}}"></i>{{/items}}
|
|||
|
{{^items}}<i></i>{{/items}}
|
|||
|
</td>
|
|||
|
{{/hasSubItems}}
|
|||
|
<td class="row-idx text-right">
|
|||
|
{{#idx}}{{idx}}{{/idx}}
|
|||
|
</td>
|
|||
|
{{#cells}}
|
|||
|
<td class="{{className}}" {{#colspan}}colspan="{{colspan}}"{{/colspan}}>
|
|||
|
<span class="value">{{{value}}}</span>{{#percent}}<span class="percent"> ({{percent}})</span>{{/percent}}
|
|||
|
</td>
|
|||
|
{{/cells}}
|
|||
|
</tr>
|
|||
|
{{/rows}}
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- TPL Panel Table Meta -->
|
|||
|
<script id="tpl-table-row-meta" type="text/template">
|
|||
|
{{#row}}
|
|||
|
<tr>
|
|||
|
{{#hasSubItems}}
|
|||
|
<td class=""></td>
|
|||
|
{{/hasSubItems}}
|
|||
|
<td class=""></td>
|
|||
|
{{#cells}}
|
|||
|
<td class="{{className}}" {{#colspan}}colspan="{{colspan}}"{{/colspan}}>
|
|||
|
{{#value}}
|
|||
|
<h4 class="value"><span title="{{title}}">{{value}}</span>{{#label}}<small> {{label}}</small>{{/label}}{{#max}}<br><small>Max: {{max}}</small>{{/max}}{{#min}}<br><small>Min: {{min}}</small>{{/min}}</h4>
|
|||
|
{{/value}}
|
|||
|
</td>
|
|||
|
{{/cells}}
|
|||
|
</tr>
|
|||
|
{{/row}}
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- TPL Table thead -->
|
|||
|
<script id="tpl-table-thead" type="text/template">
|
|||
|
<tr>
|
|||
|
{{#hasSubItems}}
|
|||
|
<th></th>
|
|||
|
{{/hasSubItems}}
|
|||
|
<th>#</th>
|
|||
|
{{#items}}
|
|||
|
<th class="{{dataType}} {{#key}}sortable{{/key}}" data-key="{{key}}" {{#sort}}data-order="{{#asc}}asc{{/asc}}{{^asc}}desc{{/asc}}"{{/sort}}>
|
|||
|
{{label}} <i class="fa fa-{{^sort}}sort{{/sort}}{{#sort}}{{#asc}}caret-up{{/asc}}{{^asc}}caret-down{{/asc}}{{/sort}}"></i>
|
|||
|
</th>
|
|||
|
{{/items}}
|
|||
|
</tr>
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- TPL Panel Options DropDown -->
|
|||
|
<script id="tpl-panel-opts" type="text/template">
|
|||
|
{{#plot.length}}
|
|||
|
<li class="dropdown-header">Chart Type</li>
|
|||
|
<li><a href="javascript:void(0);" data-panel="{{id}}" data-chart-type="area-spline"><i class="fa fa-circle{{^area-spline}}-o{{/area-spline}}"></i> Area Spline</a></li>
|
|||
|
<li><a href="javascript:void(0);" data-panel="{{id}}" data-chart-type="bar"><i class="fa fa-circle{{^bar}}-o{{/bar}}"></i> Bar</a></li>
|
|||
|
<li class="dropdown-header">Plot Metric</li>
|
|||
|
{{#plot}}
|
|||
|
<li><a href="javascript:void(0);" data-panel="{{id}}" data-plot="{{className}}" class="panel-plot-{{className}}"><i class="fa fa-circle{{^selected}}-o{{/selected}}"></i> {{label}}</a></li>
|
|||
|
{{/plot}}
|
|||
|
{{/plot.length}}
|
|||
|
|
|||
|
<li class="dropdown-header">Table Columns</li>
|
|||
|
{{#items}}
|
|||
|
<li><a href="javascript:void(0);" data-panel="{{id}}" data-metric="{{key}}"><i class="fa fa-{{^hide}}check-{{/hide}}square-o"></i> {{label}}</a></li>
|
|||
|
{{/items}}
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- TPL Table colgroup -->
|
|||
|
<script id="tpl-table-colgroup" type="text/template">
|
|||
|
{{#hasSubItems}}
|
|||
|
<col style="width: 2%;"> <!-- right-caret -->
|
|||
|
{{/hasSubItems}}
|
|||
|
<col style="width: 3%;"> <!-- row # -->
|
|||
|
{{#items}}
|
|||
|
<col style="width:{{colWidth}}">
|
|||
|
{{/items}}
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- TPL Panel -->
|
|||
|
<script id="tpl-panel" type="text/template">
|
|||
|
<div class="row">
|
|||
|
<div class="col-md-12">
|
|||
|
<div class="form-group clearfix panel-header">
|
|||
|
<h4 class="pull-left hidden-xs gheader" id="{{id}}">{{head}}<br><small>{{desc}}</small></h4>
|
|||
|
<h5 class="pull-left visible-xs hidden-sm hidden-md hidden-lg gheader" id="{{id}}">{{head}}<br><small>{{desc}}</small></h5>
|
|||
|
<div class="panel-plot-wrap">
|
|||
|
<div class="dropdown">
|
|||
|
<button class="btn btn-default btn-sm dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" data-panel="{{id}}">
|
|||
|
<i class="fa fa-gear"></i> Panel Options <span class="fa fa-caret-down"></span>
|
|||
|
</button>
|
|||
|
<ul class="dropdown-menu dropdown-menu-right panel-opts-{{id}}">
|
|||
|
</ul>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
{{#plot.length}}
|
|||
|
<div class="row">
|
|||
|
<div class="col-md-12">
|
|||
|
<div id="chart-{{id}}" class="chart-wrap"></div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
{{/plot.length}}
|
|||
|
{{#table}}
|
|||
|
<div class="row clearfix table-wrapper {{#autoHideTables}}hidden-xs{{/autoHideTables}}">
|
|||
|
<div class="col-md-12">
|
|||
|
<div class="table-responsive">
|
|||
|
<table data-panel="{{id}}" class="table table-borderless table-hover table-{{id}}">
|
|||
|
<colgroup>
|
|||
|
</colgroup>
|
|||
|
<thead>
|
|||
|
</thead>
|
|||
|
<tbody class="tbody-meta">
|
|||
|
</tbody>
|
|||
|
<tbody class="tbody-data">
|
|||
|
</tbody>
|
|||
|
</table>
|
|||
|
</div>
|
|||
|
|
|||
|
<ul class="pagination pagination-sm pull-left">
|
|||
|
<li class="disabled">
|
|||
|
<a class="panel-prev" href="javascript:void(0);" aria-label="Previous" data-panel="{{id}}">
|
|||
|
<i class="fa fa-chevron-left"></i>
|
|||
|
</a>
|
|||
|
</li>
|
|||
|
<li>
|
|||
|
<a class="panel-next" href="javascript:void(0);" aria-label="Next" data-panel="{{id}}">
|
|||
|
<i class="fa fa-chevron-right"></i>
|
|||
|
</a>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
{{/table}}
|
|||
|
</script>
|
|||
|
|
|||
|
<script id="tpl-nav-wrap" type="text/template">
|
|||
|
<div class="nav-bars fa fa-bars"></div>
|
|||
|
<div class="nav-gears fa fa-cog"></div>
|
|||
|
<div class="nav-ws-status fa fa-circle"></div>
|
|||
|
<div class="nav-list"></div>
|
|||
|
<div class="powered hidden-xs hidden-sm">by <a href="https://goaccess.io/">GoAccess</a> and <a href="http://gwsocket.io/">GWSocket</a></div>
|
|||
|
</script>
|
|||
|
<script id="tpl-nav-menu" type="text/template">
|
|||
|
<h3>Panels</h3>
|
|||
|
<ul>
|
|||
|
<li {{#overall}}class="active"{{/overall}}><a href="#"><i class="fa fa-bar-chart"></i> Overall Analyzed Requests</a></li>
|
|||
|
{{#nav}}
|
|||
|
<li {{#current}}class="active"{{/current}}><a href="#{{key}}"><i class="fa fa-{{icon}}"></i> {{head}}</a></li>
|
|||
|
{{/nav}}
|
|||
|
</ul>
|
|||
|
</script>
|
|||
|
<script id="tpl-nav-opts" type="text/template">
|
|||
|
<h3><i class="fa fa-hashtag"></i> Theme</h3>
|
|||
|
<ul>
|
|||
|
<li {{#darkGray}}class="active"{{/darkGray}}>
|
|||
|
<a href="javascript:void(0);" class="theme-dark-gray"><i class="fa fa-circle{{^darkGray}}-o{{/darkGray}}"></i> Dark Gray</a>
|
|||
|
</li>
|
|||
|
<li {{#bright}}class="active"{{/bright}}>
|
|||
|
<a href="javascript:void(0);" class="theme-bright"><i class="fa fa-circle{{^bright}}-o{{/bright}}"></i> Bright</a>
|
|||
|
</li>
|
|||
|
<li {{#darkBlue}}class="active"{{/darkBlue}}>
|
|||
|
<a href="javascript:void(0);" class="theme-dark-blue"><i class="fa fa-circle{{^darkBlue}}-o{{/darkBlue}}"></i> Dark Blue</a>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
<h3><i class="fa fa-list-alt"></i> Panels</h3>
|
|||
|
<ul class="perpage-wrap">
|
|||
|
<li class="dropdown-header"><i class="fa fa-list"></i> Items Per Page</li>
|
|||
|
<li {{#perPage5}}class="active"{{/perPage5}}>
|
|||
|
<a href="javascript:void(0);" data-perpage="5"><i class="fa fa-circle{{^perPage5}}-o{{/perPage5}}"></i> 5</a>
|
|||
|
</li>
|
|||
|
<li {{#perPage7}}class="active"{{/perPage7}}>
|
|||
|
<a href="javascript:void(0);" data-perpage="7"><i class="fa fa-circle{{^perPage7}}-o{{/perPage7}}"></i> 7</a>
|
|||
|
</li>
|
|||
|
<li {{#perPage10}}class="active"{{/perPage10}}>
|
|||
|
<a href="javascript:void(0);" data-perpage="10"><i class="fa fa-circle{{^perPage10}}-o{{/perPage10}}"></i> 10</a>
|
|||
|
</li>
|
|||
|
<li {{#perPage15}}class="active"{{/perPage15}}>
|
|||
|
<a href="javascript:void(0);" data-perpage="15"><i class="fa fa-circle{{^perPage15}}-o{{/perPage15}}"></i> 15</a>
|
|||
|
</li>
|
|||
|
<li {{#perPage20}}class="active"{{/perPage20}}>
|
|||
|
<a href="javascript:void(0);" data-perpage="20"><i class="fa fa-circle{{^perPage20}}-o{{/perPage20}}"></i> 20</a>
|
|||
|
</li>
|
|||
|
<li class="dropdown-header"><i class="fa fa-table"></i> Tables</li>
|
|||
|
<li {{#showTables}}class="active"{{/showTables}}>
|
|||
|
<a href="javascript:void(0);" data-show-tables="1"><i class="fa fa-{{#showTables}}check-{{/showTables}}square-o"></i> Display Tables</a>
|
|||
|
</li>
|
|||
|
<li {{#autoHideTables}}class="active"{{/autoHideTables}}>
|
|||
|
<a href="javascript:void(0);" data-autohide-tables="1" title="Automatically hide tables on small screen devices">
|
|||
|
<i class="fa fa-{{#autoHideTables}}check-{{/autoHideTables}}square-o"></i> Auto-hide on small devices
|
|||
|
</a>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
<h3><i class="fa fa-th-large"></i> Layout</h3>
|
|||
|
<ul>
|
|||
|
<li {{#horizontal}}class="active"{{/horizontal}}>
|
|||
|
<a href="javascript:void(0);" class="layout-horizontal"><i class="fa fa-circle{{^horizontal}}-o{{/horizontal}}"></i> Horizontal</a>
|
|||
|
</li>
|
|||
|
<li {{#vertical}}class="active"{{/vertical}}>
|
|||
|
<a href="javascript:void(0);" class="layout-vertical"><i class="fa fa-circle{{^vertical}}-o{{/vertical}}"></i> Vertical</a>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
<h3><i class="fa fa-cog"></i> File Options</h3>
|
|||
|
<ul>
|
|||
|
<li><a href="javascript:void(0);" class="export-json"><i class="fa fa-code"></i> Export as JSON</a></li>
|
|||
|
</ul>
|
|||
|
</script>
|
|||
|
|
|||
|
<script id="tpl-chart-tooltip" type="text/template">
|
|||
|
<table class="chart-tooltip">
|
|||
|
<tbody>
|
|||
|
<tr>
|
|||
|
<th colspan="2">{{data.0}}</th>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td class="name"><span class="blue"></span>hits</td>
|
|||
|
<td class="value">{{data.1}}</td>
|
|||
|
</tr>
|
|||
|
{{#data.2}}
|
|||
|
<tr>
|
|||
|
<td class="name"><span class="red"></span>visitors</td>
|
|||
|
<td class="value">{{data.2}}</td>
|
|||
|
</tr>
|
|||
|
{{/data.2}}
|
|||
|
</tbody>
|
|||
|
</table>
|
|||
|
</script>
|
|||
|
<script type='text/javascript'>var html_prefs={};var user_interface={"general": {"head": "Overall Analyzed Requests","desc": "","items": {"total_requests": {"className": "black","dataType": "numeric","label": "Total Requests"},"valid_requests": {"className": "green","dataType": "numeric","label": "Valid Requests"},"failed_requests": {"className": "red","dataType": "numeric","label": "Failed Requests"},"generation_time": {"className": "gray","dataType": "secs","label": "Init. Proc. Time"},"unique_visitors": {"className": "blue","dataType": "numeric","label": "Unique Visitors"},"unique_files": {"dataType": "numeric","label": "Unique Files"},"excluded_hits": {"dataType": "numeric","label": "Excl. IP Hits"},"unique_referrers": {"dataType": "numeric","label": "Referrers"},"unique_not_found": {"dataType": "numeric","label": "Unique 404"},"unique_static_files": {"dataType": "numeric","label": "Static Files"},"log_size": {"dataType": "bytes","label": "Log Size"},"bandwidth": {"dataType": "bytes","label": "Bandwidth"}}},"visitors": {"head": "Unique visitors per day - Including spiders","desc": "Hits having the same IP, date and agent are a unique visit.","id": "visitors","table": 1,"sort": {"field": "data","order": "DESC"},"plot": [{"className": "hits-visitors","label": "Hits/Visitors","chartType": "area-spline","chartReverse": 1,"redrawOnExpand": 1,"d3": {"y0": {"key": "hits","label": "Hits"},"y1": {"key": "visitors","label": "Visitors"}}},{"className": "bandwidth","label": "Bandwidth","chartType": "area-spline","chartReverse": 1,"redrawOnExpand": 1,"d3": {"y0": {"key": "bytes","label": "Bandwidth","format": "bytes"}}}],"items": [{"colWidth": "12%","meta": "count","dataType": "numeric","key": "hits","label": "Hits"},{"colWidth": "12%","meta": "count","dataType": "numeric","key": "visitors","label": "Visitors"},{"colWidth": "12%","meta": "count","dataType": "bytes","key": "bytes","label": "Bandwidth"},{"className": "trunc","colWidth": "100%","meta": "unique","metaType": "numeric","metaLabel": "Total","dataType": "date","key": "data","label": "Data"}]},"requests": {"head": "Requested Files (URLs)","desc": "Top requests sorted by hits [, avgts, cumts, maxts, mthd, proto]","id": "requests","table": 1,"sort": {"field": "hits","order": "DESC"},"plot": [{"className": "hits-visitors","label": "Hits/Visitors","chartType": "bar","chartReverse": 0,"redrawOnExpand": 0,"d3": {"x": {"key": ["method", "data", "protocol"]},"y0": {"key": "hits","label": "Hits"},"y1": {"key": "visitors","label": "Visitors"}}},{"className": "bandwidth","label": "Bandwidth","chartType": "bar","chartReverse": 0,"redrawOnExpand": 0,"d3": {"x": {"key": ["method", "protocol", "data"]},"y0": {"key": "bytes","label": "Bandwidth","format": "bytes"}}}],"items": [{"colWidth": "12%","meta": "count","dataType": "numeric","key": "hits","label": "Hits"},{"colWidth": "12%","meta": "count","dataType": "numeric","key": "visitors","label": "Visitors"},{"colWidth": "12%","meta": "count","dataType": "bytes","key": "bytes","label": "Bandwidth"},{"colWidth": "6%","dataType": "string","key": "method","label": "Method"},{"colWidth": "7%","dataType": "string","key": "protocol","label": "Protocol"},{"className": "trunc","colWidth": "100%","meta": "unique","metaType": "numeric","metaLabel": "Total","dataType": "string","key": "data","label": "Data"}]},"static_requests": {"head": "Static Requests","desc": "Top static requests sorted by hits [, avgts, cumts, maxts, mthd, proto]","id": "static_requests","table": 1,"sort": {"field": "hits","order": "DESC"},"plot": [{"className": "hits-visitors","label": "Hits/Visitors","chartType": "bar","chartReverse": 0,"redrawOnExpand": 0,"d3": {"x": {"key": ["method", "data", "protocol"]},"y0": {"key": "hits","label": "Hits"},"y1": {"key": "visitors","label": "Visitors"}}},{"className": "bandwidth","label": "Bandwidth","chartType": "bar","chartReverse": 0,"redrawOnExpand": 0,"d3": {"x": {"key": ["method", "protocol", "data"]},"y0": {"key": "bytes","label": "Bandwidth","format": "bytes"}}}],"items": [{"colWidth": "12%","meta": "count","dataType": "numeric",
|
|||
|
S=(r1-r0)/ρ;i=function(t){var s=t*S,coshr0=d3_cosh(r0),u=w0/(ρ2*d1)*(coshr0*d3_tanh(ρ*s+r0)-d3_sinh(r0));return[ux0+u*dx,uy0+u*dy,w0*coshr0/d3_cosh(ρ*s+r0)]}}i.duration=S*1e3;return i};d3.behavior.zoom=function(){var view={x:0,y:0,k:1},translate0,center0,center,size=[960,500],scaleExtent=d3_behavior_zoomInfinity,duration=250,zooming=0,mousedown="mousedown.zoom",mousemove="mousemove.zoom",mouseup="mouseup.zoom",mousewheelTimer,touchstart="touchstart.zoom",touchtime,event=d3_eventDispatch(zoom,"zoomstart","zoom","zoomend"),x0,x1,y0,y1;if(!d3_behavior_zoomWheel){d3_behavior_zoomWheel="onwheel"in d3_document?(d3_behavior_zoomDelta=function(){return-d3.event.deltaY*(d3.event.deltaMode?120:1)},"wheel"):"onmousewheel"in d3_document?(d3_behavior_zoomDelta=function(){return d3.event.wheelDelta},"mousewheel"):(d3_behavior_zoomDelta=function(){return-d3.event.detail},"MozMousePixelScroll")}function zoom(g){g.on(mousedown,mousedowned).on(d3_behavior_zoomWheel+".zoom",mousewheeled).on("dblclick.zoom",dblclicked).on(touchstart,touchstarted)}zoom.event=function(g){g.each(function(){var dispatch=event.of(this,arguments),view1=view;if(d3_transitionInheritId){d3.select(this).transition().each("start.zoom",function(){view=this.__chart__||{x:0,y:0,k:1};zoomstarted(dispatch)}).tween("zoom:zoom",function(){var dx=size[0],dy=size[1],cx=center0?center0[0]:dx/2,cy=center0?center0[1]:dy/2,i=d3.interpolateZoom([(cx-view.x)/view.k,(cy-view.y)/view.k,dx/view.k],[(cx-view1.x)/view1.k,(cy-view1.y)/view1.k,dx/view1.k]);return function(t){var l=i(t),k=dx/l[2];this.__chart__=view={x:cx-l[0]*k,y:cy-l[1]*k,k:k};zoomed(dispatch)}}).each("interrupt.zoom",function(){zoomended(dispatch)}).each("end.zoom",function(){zoomended(dispatch)})}else{this.__chart__=view;zoomstarted(dispatch);zoomed(dispatch);zoomended(dispatch)}})};zoom.translate=function(_){if(!arguments.length)return[view.x,view.y];view={x:+_[0],y:+_[1],k:view.k};rescale();return zoom};zoom.scale=function(_){if(!arguments.length)return view.k;view={x:view.x,y:view.y,k:null};scaleTo(+_);rescale();return zoom};zoom.scaleExtent=function(_){if(!arguments.length)return scaleExtent;scaleExtent=_==null?d3_behavior_zoomInfinity:[+_[0],+_[1]];return zoom};zoom.center=function(_){if(!arguments.length)return center;center=_&&[+_[0],+_[1]];return zoom};zoom.size=function(_){if(!arguments.length)return size;size=_&&[+_[0],+_[1]];return zoom};zoom.duration=function(_){if(!arguments.length)return duration;duration=+_;return zoom};zoom.x=function(z){if(!arguments.length)return x1;x1=z;x0=z.copy();view={x:0,y:0,k:1};return zoom};zoom.y=function(z){if(!arguments.length)return y1;y1=z;y0=z.copy();view={x:0,y:0,k:1};return zoom};function location(p){return[(p[0]-view.x)/view.k,(p[1]-view.y)/view.k]}function point(l){return[l[0]*view.k+view.x,l[1]*view.k+view.y]}function scaleTo(s){view.k=Math.max(scaleExtent[0],Math.min(scaleExtent[1],s))}function translateTo(p,l){l=point(l);view.x+=p[0]-l[0];view.y+=p[1]-l[1]}function zoomTo(that,p,l,k){that.__chart__={x:view.x,y:view.y,k:view.k};scaleTo(Math.pow(2,k));translateTo(center0=p,l);that=d3.select(that);if(duration>0)that=that.transition().duration(duration);that.call(zoom.event)}function rescale(){if(x1)x1.domain(x0.range().map(function(x){return(x-view.x)/view.k}).map(x0.invert));if(y1)y1.domain(y0.range().map(function(y){return(y-view.y)/view.k}).map(y0.invert))}function zoomstarted(dispatch){if(!zooming++)dispatch({type:"zoomstart"})}function zoomed(dispatch){rescale();dispatch({type:"zoom",scale:view.k,translate:[view.x,view.y]})}function zoomended(dispatch){if(!--zooming)dispatch({type:"zoomend"}),center0=null}function mousedowned(){var that=this,dispatch=event.of(that,arguments),dragged=0,subject=d3.select(d3_window(that)).on(mousemove,moved).on(mouseup,ended),location0=location(d3.mouse(that)),dragRestore=d3_event_dragSuppress(that);d3_selection_interrupt.call(that);zoomstarted(dispatch);function moved(){dragged=1;translateTo(d3.mouse(that),location0);zoomed(dispatch)}function ended(){subject.on(mousemove,null).on(mouseup,null);dragRestore(dragged);zoomended(dis
|
|||
|
locale_periods.forEach(function(p,i){d3_time_periodLookup.set(p.toLowerCase(),i)});var d3_time_formats={a:function(d){return locale_shortDays[d.getDay()]},A:function(d){return locale_days[d.getDay()]},b:function(d){return locale_shortMonths[d.getMonth()]},B:function(d){return locale_months[d.getMonth()]},c:d3_time_format(locale_dateTime),d:function(d,p){return d3_time_formatPad(d.getDate(),p,2)},e:function(d,p){return d3_time_formatPad(d.getDate(),p,2)},H:function(d,p){return d3_time_formatPad(d.getHours(),p,2)},I:function(d,p){return d3_time_formatPad(d.getHours()%12||12,p,2)},j:function(d,p){return d3_time_formatPad(1+d3_time.dayOfYear(d),p,3)},L:function(d,p){return d3_time_formatPad(d.getMilliseconds(),p,3)},m:function(d,p){return d3_time_formatPad(d.getMonth()+1,p,2)},M:function(d,p){return d3_time_formatPad(d.getMinutes(),p,2)},p:function(d){return locale_periods[+(d.getHours()>=12)]},S:function(d,p){return d3_time_formatPad(d.getSeconds(),p,2)},U:function(d,p){return d3_time_formatPad(d3_time.sundayOfYear(d),p,2)},w:function(d){return d.getDay()},W:function(d,p){return d3_time_formatPad(d3_time.mondayOfYear(d),p,2)},x:d3_time_format(locale_date),X:d3_time_format(locale_time),y:function(d,p){return d3_time_formatPad(d.getFullYear()%100,p,2)},Y:function(d,p){return d3_time_formatPad(d.getFullYear()%1e4,p,4)},Z:d3_time_zone,"%":function(){return"%"}};var d3_time_parsers={a:d3_time_parseWeekdayAbbrev,A:d3_time_parseWeekday,b:d3_time_parseMonthAbbrev,B:d3_time_parseMonth,c:d3_time_parseLocaleFull,d:d3_time_parseDay,e:d3_time_parseDay,H:d3_time_parseHour24,I:d3_time_parseHour24,j:d3_time_parseDayOfYear,L:d3_time_parseMilliseconds,m:d3_time_parseMonthNumber,M:d3_time_parseMinutes,p:d3_time_parseAmPm,S:d3_time_parseSeconds,U:d3_time_parseWeekNumberSunday,w:d3_time_parseWeekdayNumber,W:d3_time_parseWeekNumberMonday,x:d3_time_parseLocaleDate,X:d3_time_parseLocaleTime,y:d3_time_parseYear,Y:d3_time_parseFullYear,Z:d3_time_parseZone,"%":d3_time_parseLiteralPercent};function d3_time_parseWeekdayAbbrev(date,string,i){d3_time_dayAbbrevRe.lastIndex=0;var n=d3_time_dayAbbrevRe.exec(string.slice(i));return n?(date.w=d3_time_dayAbbrevLookup.get(n[0].toLowerCase()),i+n[0].length):-1}function d3_time_parseWeekday(date,string,i){d3_time_dayRe.lastIndex=0;var n=d3_time_dayRe.exec(string.slice(i));return n?(date.w=d3_time_dayLookup.get(n[0].toLowerCase()),i+n[0].length):-1}function d3_time_parseMonthAbbrev(date,string,i){d3_time_monthAbbrevRe.lastIndex=0;var n=d3_time_monthAbbrevRe.exec(string.slice(i));return n?(date.m=d3_time_monthAbbrevLookup.get(n[0].toLowerCase()),i+n[0].length):-1}function d3_time_parseMonth(date,string,i){d3_time_monthRe.lastIndex=0;var n=d3_time_monthRe.exec(string.slice(i));return n?(date.m=d3_time_monthLookup.get(n[0].toLowerCase()),i+n[0].length):-1}function d3_time_parseLocaleFull(date,string,i){return d3_time_parse(date,d3_time_formats.c.toString(),string,i)}function d3_time_parseLocaleDate(date,string,i){return d3_time_parse(date,d3_time_formats.x.toString(),string,i)}function d3_time_parseLocaleTime(date,string,i){return d3_time_parse(date,d3_time_formats.X.toString(),string,i)}function d3_time_parseAmPm(date,string,i){var n=d3_time_periodLookup.get(string.slice(i,i+=2).toLowerCase());return n==null?-1:(date.p=n,i)}return d3_time_format}var d3_time_formatPads={"-":"",_:" ",0:"0"},d3_time_numberRe=/^\s*\d+/,d3_time_percentRe=/^%/;function d3_time_formatPad(value,fill,width){var sign=value<0?"-":"",string=(sign?-value:value)+"",length=string.length;return sign+(length<width?new Array(width-length+1).join(fill)+string:string)}function d3_time_formatRe(names){return new RegExp("^(?:"+names.map(d3.requote).join("|")+")","i")}function d3_time_formatLookup(names){var map=new d3_Map,i=-1,n=names.length;while(++i<n)map.set(names[i].toLowerCase(),i);return map}function d3_time_parseWeekdayNumber(date,string,i){d3_time_numberRe.lastIndex=0;var n=d3_time_numberRe.exec(string.slice(i,i+1));return n?(date.w=+n[0],i+n[0].length):-1}function d3_time_parseWeekNumberSunday(date,string,i){d3_time_numberRe.lastIndex=0;var n=d
|
|||
|
};d3.geo.albersUsa=function(){var lower48=d3.geo.albers();var alaska=d3.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]);var hawaii=d3.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]);var point,pointStream={point:function(x,y){point=[x,y]}},lower48Point,alaskaPoint,hawaiiPoint;function albersUsa(coordinates){var x=coordinates[0],y=coordinates[1];point=null;(lower48Point(x,y),point)||(alaskaPoint(x,y),point)||hawaiiPoint(x,y);return point}albersUsa.invert=function(coordinates){var k=lower48.scale(),t=lower48.translate(),x=(coordinates[0]-t[0])/k,y=(coordinates[1]-t[1])/k;return(y>=.12&&y<.234&&x>=-.425&&x<-.214?alaska:y>=.166&&y<.234&&x>=-.214&&x<-.115?hawaii:lower48).invert(coordinates)};albersUsa.stream=function(stream){var lower48Stream=lower48.stream(stream),alaskaStream=alaska.stream(stream),hawaiiStream=hawaii.stream(stream);return{point:function(x,y){lower48Stream.point(x,y);alaskaStream.point(x,y);hawaiiStream.point(x,y)},sphere:function(){lower48Stream.sphere();alaskaStream.sphere();hawaiiStream.sphere()},lineStart:function(){lower48Stream.lineStart();alaskaStream.lineStart();hawaiiStream.lineStart()},lineEnd:function(){lower48Stream.lineEnd();alaskaStream.lineEnd();hawaiiStream.lineEnd()},polygonStart:function(){lower48Stream.polygonStart();alaskaStream.polygonStart();hawaiiStream.polygonStart()},polygonEnd:function(){lower48Stream.polygonEnd();alaskaStream.polygonEnd();hawaiiStream.polygonEnd()}}};albersUsa.precision=function(_){if(!arguments.length)return lower48.precision();lower48.precision(_);alaska.precision(_);hawaii.precision(_);return albersUsa};albersUsa.scale=function(_){if(!arguments.length)return lower48.scale();lower48.scale(_);alaska.scale(_*.35);hawaii.scale(_);return albersUsa.translate(lower48.translate())};albersUsa.translate=function(_){if(!arguments.length)return lower48.translate();var k=lower48.scale(),x=+_[0],y=+_[1];lower48Point=lower48.translate(_).clipExtent([[x-.455*k,y-.238*k],[x+.455*k,y+.238*k]]).stream(pointStream).point;alaskaPoint=alaska.translate([x-.307*k,y+.201*k]).clipExtent([[x-.425*k+ε,y+.12*k+ε],[x-.214*k-ε,y+.234*k-ε]]).stream(pointStream).point;hawaiiPoint=hawaii.translate([x-.205*k,y+.212*k]).clipExtent([[x-.214*k+ε,y+.166*k+ε],[x-.115*k-ε,y+.234*k-ε]]).stream(pointStream).point;return albersUsa};return albersUsa.scale(1070)};var d3_geo_pathAreaSum,d3_geo_pathAreaPolygon,d3_geo_pathArea={point:d3_noop,lineStart:d3_noop,lineEnd:d3_noop,polygonStart:function(){d3_geo_pathAreaPolygon=0;d3_geo_pathArea.lineStart=d3_geo_pathAreaRingStart},polygonEnd:function(){d3_geo_pathArea.lineStart=d3_geo_pathArea.lineEnd=d3_geo_pathArea.point=d3_noop;d3_geo_pathAreaSum+=abs(d3_geo_pathAreaPolygon/2)}};function d3_geo_pathAreaRingStart(){var x00,y00,x0,y0;d3_geo_pathArea.point=function(x,y){d3_geo_pathArea.point=nextPoint;x00=x0=x,y00=y0=y};function nextPoint(x,y){d3_geo_pathAreaPolygon+=y0*x-x0*y;x0=x,y0=y}d3_geo_pathArea.lineEnd=function(){nextPoint(x00,y00)}}var d3_geo_pathBoundsX0,d3_geo_pathBoundsY0,d3_geo_pathBoundsX1,d3_geo_pathBoundsY1;var d3_geo_pathBounds={point:d3_geo_pathBoundsPoint,lineStart:d3_noop,lineEnd:d3_noop,polygonStart:d3_noop,polygonEnd:d3_noop};function d3_geo_pathBoundsPoint(x,y){if(x<d3_geo_pathBoundsX0)d3_geo_pathBoundsX0=x;if(x>d3_geo_pathBoundsX1)d3_geo_pathBoundsX1=x;if(y<d3_geo_pathBoundsY0)d3_geo_pathBoundsY0=y;if(y>d3_geo_pathBoundsY1)d3_geo_pathBoundsY1=y}function d3_geo_pathBuffer(){var pointCircle=d3_geo_pathBufferCircle(4.5),buffer=[];var stream={point:point,lineStart:function(){stream.point=pointLineStart},lineEnd:lineEnd,polygonStart:function(){stream.lineEnd=lineEndPolygon},polygonEnd:function(){stream.lineEnd=lineEnd;stream.point=point},pointRadius:function(_){pointCircle=d3_geo_pathBufferCircle(_);return stream},result:function(){if(buffer.length){var result=buffer.join("");buffer=[];return result}}};function point(x,y){buffer.push("M",x,",",y,pointCircle)}function pointLineStart(x,y){buffer.push("M",x,",",y);stream.point=pointLine}function pointLine(x,y){buffer.push("L",x,",",y)}function
|
|||
|
return(rfocx+lfocx)/2}function d3_geom_voronoiRightBreakPoint(arc,directrix){var rArc=arc.N;if(rArc)return d3_geom_voronoiLeftBreakPoint(rArc,directrix);var site=arc.site;return site.y===directrix?site.x:Infinity}function d3_geom_voronoiCell(site){this.site=site;this.edges=[]}d3_geom_voronoiCell.prototype.prepare=function(){var halfEdges=this.edges,iHalfEdge=halfEdges.length,edge;while(iHalfEdge--){edge=halfEdges[iHalfEdge].edge;if(!edge.b||!edge.a)halfEdges.splice(iHalfEdge,1)}halfEdges.sort(d3_geom_voronoiHalfEdgeOrder);return halfEdges.length};function d3_geom_voronoiCloseCells(extent){var x0=extent[0][0],x1=extent[1][0],y0=extent[0][1],y1=extent[1][1],x2,y2,x3,y3,cells=d3_geom_voronoiCells,iCell=cells.length,cell,iHalfEdge,halfEdges,nHalfEdges,start,end;while(iCell--){cell=cells[iCell];if(!cell||!cell.prepare())continue;halfEdges=cell.edges;nHalfEdges=halfEdges.length;iHalfEdge=0;while(iHalfEdge<nHalfEdges){end=halfEdges[iHalfEdge].end(),x3=end.x,y3=end.y;start=halfEdges[++iHalfEdge%nHalfEdges].start(),x2=start.x,y2=start.y;if(abs(x3-x2)>ε||abs(y3-y2)>ε){halfEdges.splice(iHalfEdge,0,new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site,end,abs(x3-x0)<ε&&y1-y3>ε?{x:x0,y:abs(x2-x0)<ε?y2:y1}:abs(y3-y1)<ε&&x1-x3>ε?{x:abs(y2-y1)<ε?x2:x1,y:y1}:abs(x3-x1)<ε&&y3-y0>ε?{x:x1,y:abs(x2-x1)<ε?y2:y0}:abs(y3-y0)<ε&&x3-x0>ε?{x:abs(y2-y0)<ε?x2:x0,y:y0}:null),cell.site,null));++nHalfEdges}}}}function d3_geom_voronoiHalfEdgeOrder(a,b){return b.angle-a.angle}function d3_geom_voronoiCircle(){d3_geom_voronoiRedBlackNode(this);this.x=this.y=this.arc=this.site=this.cy=null}function d3_geom_voronoiAttachCircle(arc){var lArc=arc.P,rArc=arc.N;if(!lArc||!rArc)return;var lSite=lArc.site,cSite=arc.site,rSite=rArc.site;if(lSite===rSite)return;var bx=cSite.x,by=cSite.y,ax=lSite.x-bx,ay=lSite.y-by,cx=rSite.x-bx,cy=rSite.y-by;var d=2*(ax*cy-ay*cx);if(d>=-ε2)return;var ha=ax*ax+ay*ay,hc=cx*cx+cy*cy,x=(cy*ha-ay*hc)/d,y=(ax*hc-cx*ha)/d,cy=y+by;var circle=d3_geom_voronoiCirclePool.pop()||new d3_geom_voronoiCircle;circle.arc=arc;circle.site=cSite;circle.x=x+bx;circle.y=cy+Math.sqrt(x*x+y*y);circle.cy=cy;arc.circle=circle;var before=null,node=d3_geom_voronoiCircles._;while(node){if(circle.y<node.y||circle.y===node.y&&circle.x<=node.x){if(node.L)node=node.L;else{before=node.P;break}}else{if(node.R)node=node.R;else{before=node;break}}}d3_geom_voronoiCircles.insert(before,circle);if(!before)d3_geom_voronoiFirstCircle=circle}function d3_geom_voronoiDetachCircle(arc){var circle=arc.circle;if(circle){if(!circle.P)d3_geom_voronoiFirstCircle=circle.N;d3_geom_voronoiCircles.remove(circle);d3_geom_voronoiCirclePool.push(circle);d3_geom_voronoiRedBlackNode(circle);arc.circle=null}}function d3_geom_voronoiClipEdges(extent){var edges=d3_geom_voronoiEdges,clip=d3_geom_clipLine(extent[0][0],extent[0][1],extent[1][0],extent[1][1]),i=edges.length,e;while(i--){e=edges[i];if(!d3_geom_voronoiConnectEdge(e,extent)||!clip(e)||abs(e.a.x-e.b.x)<ε&&abs(e.a.y-e.b.y)<ε){e.a=e.b=null;edges.splice(i,1)}}}function d3_geom_voronoiConnectEdge(edge,extent){var vb=edge.b;if(vb)return true;var va=edge.a,x0=extent[0][0],x1=extent[1][0],y0=extent[0][1],y1=extent[1][1],lSite=edge.l,rSite=edge.r,lx=lSite.x,ly=lSite.y,rx=rSite.x,ry=rSite.y,fx=(lx+rx)/2,fy=(ly+ry)/2,fm,fb;if(ry===ly){if(fx<x0||fx>=x1)return;if(lx>rx){if(!va)va={x:fx,y:y0};else if(va.y>=y1)return;vb={x:fx,y:y1}}else{if(!va)va={x:fx,y:y1};else if(va.y<y0)return;vb={x:fx,y:y0}}}else{fm=(lx-rx)/(ry-ly);fb=fy-fm*fx;if(fm<-1||fm>1){if(lx>rx){if(!va)va={x:(y0-fb)/fm,y:y0};else if(va.y>=y1)return;vb={x:(y1-fb)/fm,y:y1}}else{if(!va)va={x:(y1-fb)/fm,y:y1};else if(va.y<y0)return;vb={x:(y0-fb)/fm,y:y0}}}else{if(ly<ry){if(!va)va={x:x0,y:fm*x0+fb};else if(va.x>=x1)return;vb={x:x1,y:fm*x1+fb}}else{if(!va)va={x:x1,y:fm*x1+fb};else if(va.x<x0)return;vb={x:x0,y:fm*x0+fb}}}}edge.a=va;edge.b=vb;return true}function d3_geom_voronoiEdge(lSite,rSite){this.l=lSite;this.r=rSite;this.a=this.b=null}function d3_geom_voronoiCreateEdge(lSite,rSite,va,vb){var edge=new d3_geom_voronoiEdge(lSite,rSite);d3_geom_voronoiEdges.push(
|
|||
|
delete node.children}}d3_layout_hierarchyVisitAfter(root,function(node){var childs,parent;if(sort&&(childs=node.children))childs.sort(sort);if(value&&(parent=node.parent))parent.value+=node.value});return nodes}hierarchy.sort=function(x){if(!arguments.length)return sort;sort=x;return hierarchy};hierarchy.children=function(x){if(!arguments.length)return children;children=x;return hierarchy};hierarchy.value=function(x){if(!arguments.length)return value;value=x;return hierarchy};hierarchy.revalue=function(root){if(value){d3_layout_hierarchyVisitBefore(root,function(node){if(node.children)node.value=0});d3_layout_hierarchyVisitAfter(root,function(node){var parent;if(!node.children)node.value=+value.call(hierarchy,node,node.depth)||0;if(parent=node.parent)parent.value+=node.value})}return root};return hierarchy};function d3_layout_hierarchyRebind(object,hierarchy){d3.rebind(object,hierarchy,"sort","children","value");object.nodes=object;object.links=d3_layout_hierarchyLinks;return object}function d3_layout_hierarchyVisitBefore(node,callback){var nodes=[node];while((node=nodes.pop())!=null){callback(node);if((children=node.children)&&(n=children.length)){var n,children;while(--n>=0)nodes.push(children[n])}}}function d3_layout_hierarchyVisitAfter(node,callback){var nodes=[node],nodes2=[];while((node=nodes.pop())!=null){nodes2.push(node);if((children=node.children)&&(n=children.length)){var i=-1,n,children;while(++i<n)nodes.push(children[i])}}while((node=nodes2.pop())!=null){callback(node)}}function d3_layout_hierarchyChildren(d){return d.children}function d3_layout_hierarchyValue(d){return d.value}function d3_layout_hierarchySort(a,b){return b.value-a.value}function d3_layout_hierarchyLinks(nodes){return d3.merge(nodes.map(function(parent){return(parent.children||[]).map(function(child){return{source:parent,target:child}})}))}d3.layout.partition=function(){var hierarchy=d3.layout.hierarchy(),size=[1,1];function position(node,x,dx,dy){var children=node.children;node.x=x;node.y=node.depth*dy;node.dx=dx;node.dy=dy;if(children&&(n=children.length)){var i=-1,n,c,d;dx=node.value?dx/node.value:0;while(++i<n){position(c=children[i],x,d=c.value*dx,dy);x+=d}}}function depth(node){var children=node.children,d=0;if(children&&(n=children.length)){var i=-1,n;while(++i<n)d=Math.max(d,depth(children[i]))}return 1+d}function partition(d,i){var nodes=hierarchy.call(this,d,i);position(nodes[0],0,size[0],size[1]/depth(nodes[0]));return nodes}partition.size=function(x){if(!arguments.length)return size;size=x;return partition};return d3_layout_hierarchyRebind(partition,hierarchy)};d3.layout.pie=function(){var value=Number,sort=d3_layout_pieSortByValue,startAngle=0,endAngle=τ,padAngle=0;function pie(data){var n=data.length,values=data.map(function(d,i){return+value.call(pie,d,i)}),a=+(typeof startAngle==="function"?startAngle.apply(this,arguments):startAngle),da=(typeof endAngle==="function"?endAngle.apply(this,arguments):endAngle)-a,p=Math.min(Math.abs(da)/n,+(typeof padAngle==="function"?padAngle.apply(this,arguments):padAngle)),pa=p*(da<0?-1:1),sum=d3.sum(values),k=sum?(da-n*pa)/sum:0,index=d3.range(n),arcs=[],v;if(sort!=null)index.sort(sort===d3_layout_pieSortByValue?function(i,j){return values[j]-values[i]}:function(i,j){return sort(data[i],data[j])});index.forEach(function(i){arcs[i]={data:data[i],value:v=values[i],startAngle:a,endAngle:a+=v*k+pa,padAngle:p}});return arcs}pie.value=function(_){if(!arguments.length)return value;value=_;return pie};pie.sort=function(_){if(!arguments.length)return sort;sort=_;return pie};pie.startAngle=function(_){if(!arguments.length)return startAngle;startAngle=_;return pie};pie.endAngle=function(_){if(!arguments.length)return endAngle;endAngle=_;return pie};pie.padAngle=function(_){if(!arguments.length)return padAngle;padAngle=_;return pie};return pie};var d3_layout_pieSortByValue={};d3.layout.stack=function(){var values=d3_identity,order=d3_layout_stackOrderDefault,offset=d3_layout_stackOffsetZero,out=d3_layout_stackOut,x=d3_layout_stackX,y=d3_layout_stackY;function stack(data,index){if(!(n=data.length))retu
|
|||
|
d3.scale.quantile=function(){return d3_scale_quantile([],[])};function d3_scale_quantile(domain,range){var thresholds;function rescale(){var k=0,q=range.length;thresholds=[];while(++k<q)thresholds[k-1]=d3.quantile(domain,k/q);return scale}function scale(x){if(!isNaN(x=+x))return range[d3.bisect(thresholds,x)]}scale.domain=function(x){if(!arguments.length)return domain;domain=x.map(d3_number).filter(d3_numeric).sort(d3_ascending);return rescale()};scale.range=function(x){if(!arguments.length)return range;range=x;return rescale()};scale.quantiles=function(){return thresholds};scale.invertExtent=function(y){y=range.indexOf(y);return y<0?[NaN,NaN]:[y>0?thresholds[y-1]:domain[0],y<thresholds.length?thresholds[y]:domain[domain.length-1]]};scale.copy=function(){return d3_scale_quantile(domain,range)};return rescale()}d3.scale.quantize=function(){return d3_scale_quantize(0,1,[0,1])};function d3_scale_quantize(x0,x1,range){var kx,i;function scale(x){return range[Math.max(0,Math.min(i,Math.floor(kx*(x-x0))))]}function rescale(){kx=range.length/(x1-x0);i=range.length-1;return scale}scale.domain=function(x){if(!arguments.length)return[x0,x1];x0=+x[0];x1=+x[x.length-1];return rescale()};scale.range=function(x){if(!arguments.length)return range;range=x;return rescale()};scale.invertExtent=function(y){y=range.indexOf(y);y=y<0?NaN:y/kx+x0;return[y,y+1/kx]};scale.copy=function(){return d3_scale_quantize(x0,x1,range)};return rescale()}d3.scale.threshold=function(){return d3_scale_threshold([.5],[0,1])};function d3_scale_threshold(domain,range){function scale(x){if(x<=x)return range[d3.bisect(domain,x)]}scale.domain=function(_){if(!arguments.length)return domain;domain=_;return scale};scale.range=function(_){if(!arguments.length)return range;range=_;return scale};scale.invertExtent=function(y){y=range.indexOf(y);return[domain[y-1],domain[y]]};scale.copy=function(){return d3_scale_threshold(domain,range)};return scale}d3.scale.identity=function(){return d3_scale_identity([0,1])};function d3_scale_identity(domain){function identity(x){return+x}identity.invert=identity;identity.domain=identity.range=function(x){if(!arguments.length)return domain;domain=x.map(identity);return identity};identity.ticks=function(m){return d3_scale_linearTicks(domain,m)};identity.tickFormat=function(m,format){return d3_scale_linearTickFormat(domain,m,format)};identity.copy=function(){return d3_scale_identity(domain)};return identity}d3.svg={};function d3_zero(){return 0}d3.svg.arc=function(){var innerRadius=d3_svg_arcInnerRadius,outerRadius=d3_svg_arcOuterRadius,cornerRadius=d3_zero,padRadius=d3_svg_arcAuto,startAngle=d3_svg_arcStartAngle,endAngle=d3_svg_arcEndAngle,padAngle=d3_svg_arcPadAngle;function arc(){var r0=Math.max(0,+innerRadius.apply(this,arguments)),r1=Math.max(0,+outerRadius.apply(this,arguments)),a0=startAngle.apply(this,arguments)-halfπ,a1=endAngle.apply(this,arguments)-halfπ,da=Math.abs(a1-a0),cw=a0>a1?0:1;if(r1<r0)rc=r1,r1=r0,r0=rc;if(da>=τε)return circleSegment(r1,cw)+(r0?circleSegment(r0,1-cw):"")+"Z";var rc,cr,rp,ap,p0=0,p1=0,x0,y0,x1,y1,x2,y2,x3,y3,path=[];if(ap=(+padAngle.apply(this,arguments)||0)/2){rp=padRadius===d3_svg_arcAuto?Math.sqrt(r0*r0+r1*r1):+padRadius.apply(this,arguments);if(!cw)p1*=-1;if(r1)p1=d3_asin(rp/r1*Math.sin(ap));if(r0)p0=d3_asin(rp/r0*Math.sin(ap))}if(r1){x0=r1*Math.cos(a0+p1);y0=r1*Math.sin(a0+p1);x1=r1*Math.cos(a1-p1);y1=r1*Math.sin(a1-p1);var l1=Math.abs(a1-a0-2*p1)<=π?0:1;if(p1&&d3_svg_arcSweep(x0,y0,x1,y1)===cw^l1){var h1=(a0+a1)/2;x0=r1*Math.cos(h1);y0=r1*Math.sin(h1);x1=y1=null}}else{x0=y0=0}if(r0){x2=r0*Math.cos(a1-p0);y2=r0*Math.sin(a1-p0);x3=r0*Math.cos(a0+p0);y3=r0*Math.sin(a0+p0);var l0=Math.abs(a0-a1+2*p0)<=π?0:1;if(p0&&d3_svg_arcSweep(x2,y2,x3,y3)===1-cw^l0){var h0=(a0+a1)/2;x2=r0*Math.cos(h0);y2=r0*Math.sin(h0);x3=y3=null}}else{x2=y2=0}if(da>ε&&(rc=Math.min(Math.abs(r1-r0)/2,+cornerRadius.apply(this,arguments)))>.001){cr=r0<r1^cw?0:1;var rc1=rc,rc0=rc;if(da<π){var oc=x3==null?[x2,y2]:x1==null?[x0,y0]:d3_geom_polygonIntersect([x0,y0],[x3,y3],[x1,y1],[x2,y2]),ax=x0-oc[0],ay=y0-oc[1],bx=x1-oc[0],by=
|
|||
|
var lineEnter=tickEnter.select("line"),lineUpdate=tickUpdate.select("line"),text=tick.select("text").text(tickFormat),textEnter=tickEnter.select("text"),textUpdate=tickUpdate.select("text"),sign=orient==="top"||orient==="left"?-1:1,x1,x2,y1,y2;if(orient==="bottom"||orient==="top"){tickTransform=d3_svg_axisX,x1="x",y1="y",x2="x2",y2="y2";text.attr("dy",sign<0?"0em":".71em").style("text-anchor","middle");pathUpdate.attr("d","M"+range[0]+","+sign*outerTickSize+"V0H"+range[1]+"V"+sign*outerTickSize)}else{tickTransform=d3_svg_axisY,x1="y",y1="x",x2="y2",y2="x2";text.attr("dy",".32em").style("text-anchor",sign<0?"end":"start");pathUpdate.attr("d","M"+sign*outerTickSize+","+range[0]+"H0V"+range[1]+"H"+sign*outerTickSize)}lineEnter.attr(y2,sign*innerTickSize);textEnter.attr(y1,sign*tickSpacing);lineUpdate.attr(x2,0).attr(y2,sign*innerTickSize);textUpdate.attr(x1,0).attr(y1,sign*tickSpacing);if(scale1.rangeBand){var x=scale1,dx=x.rangeBand()/2;scale0=scale1=function(d){return x(d)+dx}}else if(scale0.rangeBand){scale0=scale1}else{tickExit.call(tickTransform,scale1,scale0)}tickEnter.call(tickTransform,scale0,scale1);tickUpdate.call(tickTransform,scale1,scale1)})}axis.scale=function(x){if(!arguments.length)return scale;scale=x;return axis};axis.orient=function(x){if(!arguments.length)return orient;orient=x in d3_svg_axisOrients?x+"":d3_svg_axisDefaultOrient;return axis};axis.ticks=function(){if(!arguments.length)return tickArguments_;tickArguments_=d3_array(arguments);return axis};axis.tickValues=function(x){if(!arguments.length)return tickValues;tickValues=x;return axis};axis.tickFormat=function(x){if(!arguments.length)return tickFormat_;tickFormat_=x;return axis};axis.tickSize=function(x){var n=arguments.length;if(!n)return innerTickSize;innerTickSize=+x;outerTickSize=+arguments[n-1];return axis};axis.innerTickSize=function(x){if(!arguments.length)return innerTickSize;innerTickSize=+x;return axis};axis.outerTickSize=function(x){if(!arguments.length)return outerTickSize;outerTickSize=+x;return axis};axis.tickPadding=function(x){if(!arguments.length)return tickPadding;tickPadding=+x;return axis};axis.tickSubdivide=function(){return arguments.length&&axis};return axis};var d3_svg_axisDefaultOrient="bottom",d3_svg_axisOrients={top:1,right:1,bottom:1,left:1};function d3_svg_axisX(selection,x0,x1){selection.attr("transform",function(d){var v0=x0(d);return"translate("+(isFinite(v0)?v0:x1(d))+",0)"})}function d3_svg_axisY(selection,y0,y1){selection.attr("transform",function(d){var v0=y0(d);return"translate(0,"+(isFinite(v0)?v0:y1(d))+")"})}d3.svg.brush=function(){var event=d3_eventDispatch(brush,"brushstart","brush","brushend"),x=null,y=null,xExtent=[0,0],yExtent=[0,0],xExtentDomain,yExtentDomain,xClamp=true,yClamp=true,resizes=d3_svg_brushResizes[0];function brush(g){g.each(function(){var g=d3.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",brushstart).on("touchstart.brush",brushstart);var background=g.selectAll(".background").data([0]);background.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair");g.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var resize=g.selectAll(".resize").data(resizes,d3_identity);resize.exit().remove();resize.enter().append("g").attr("class",function(d){return"resize "+d}).style("cursor",function(d){return d3_svg_brushCursor[d]}).append("rect").attr("x",function(d){return/[ew]$/.test(d)?-3:null}).attr("y",function(d){return/^[ns]/.test(d)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden");resize.style("display",brush.empty()?"none":null);var gUpdate=d3.transition(g),backgroundUpdate=d3.transition(background),range;if(x){range=d3_scaleRange(x);backgroundUpdate.attr("x",range[0]).attr("width",range[1]-range[0]);redrawX(gUpdate)}if(y){range=d3_scaleRange(y);backgroundUpdate.attr("y",range[0]).attr("height",range[1]-range[0]);redrawY(gUpdate)}redraw(gUpdate)})}brush.event=function(g){g.each(function(){var event_=event.of
|
|||
|
</script><script>/*
|
|||
|
* Copyright 2011 Twitter, Inc.
|
|||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|||
|
* you may not use this file except in compliance with the License.
|
|||
|
* You may obtain a copy of the License at
|
|||
|
*
|
|||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
|
*
|
|||
|
* Unless required by applicable law or agreed to in writing, software
|
|||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
|
* See the License for the specific language governing permissions and
|
|||
|
* limitations under the License.
|
|||
|
*/
|
|||
|
|
|||
|
|
|||
|
|
|||
|
var Hogan = {};
|
|||
|
|
|||
|
(function (Hogan, useArrayBuffer) {
|
|||
|
Hogan.Template = function (renderFunc, text, compiler, options) {
|
|||
|
this.r = renderFunc || this.r;
|
|||
|
this.c = compiler;
|
|||
|
this.options = options;
|
|||
|
this.text = text || '';
|
|||
|
this.buf = (useArrayBuffer) ? [] : '';
|
|||
|
}
|
|||
|
|
|||
|
Hogan.Template.prototype = {
|
|||
|
// render: replaced by generated code.
|
|||
|
r: function (context, partials, indent) { return ''; },
|
|||
|
|
|||
|
// variable escaping
|
|||
|
v: hoganEscape,
|
|||
|
|
|||
|
// triple stache
|
|||
|
t: coerceToString,
|
|||
|
|
|||
|
render: function render(context, partials, indent) {
|
|||
|
return this.ri([context], partials || {}, indent);
|
|||
|
},
|
|||
|
|
|||
|
// render internal -- a hook for overrides that catches partials too
|
|||
|
ri: function (context, partials, indent) {
|
|||
|
return this.r(context, partials, indent);
|
|||
|
},
|
|||
|
|
|||
|
// tries to find a partial in the curent scope and render it
|
|||
|
rp: function(name, context, partials, indent) {
|
|||
|
var partial = partials[name];
|
|||
|
|
|||
|
if (!partial) {
|
|||
|
return '';
|
|||
|
}
|
|||
|
|
|||
|
if (this.c && typeof partial == 'string') {
|
|||
|
partial = this.c.compile(partial, this.options);
|
|||
|
}
|
|||
|
|
|||
|
return partial.ri(context, partials, indent);
|
|||
|
},
|
|||
|
|
|||
|
// render a section
|
|||
|
rs: function(context, partials, section) {
|
|||
|
var tail = context[context.length - 1];
|
|||
|
|
|||
|
if (!isArray(tail)) {
|
|||
|
section(context, partials, this);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
for (var i = 0; i < tail.length; i++) {
|
|||
|
context.push(tail[i]);
|
|||
|
section(context, partials, this);
|
|||
|
context.pop();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// maybe start a section
|
|||
|
s: function(val, ctx, partials, inverted, start, end, tags) {
|
|||
|
var pass;
|
|||
|
|
|||
|
if (isArray(val) && val.length === 0) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
if (typeof val == 'function') {
|
|||
|
val = this.ls(val, ctx, partials, inverted, start, end, tags);
|
|||
|
}
|
|||
|
|
|||
|
pass = (val === '') || !!val;
|
|||
|
|
|||
|
if (!inverted && pass && ctx) {
|
|||
|
ctx.push((typeof val == 'object') ? val : ctx[ctx.length - 1]);
|
|||
|
}
|
|||
|
|
|||
|
return pass;
|
|||
|
},
|
|||
|
|
|||
|
// find values with dotted names
|
|||
|
d: function(key, ctx, partials, returnFound) {
|
|||
|
var names = key.split('.'),
|
|||
|
val = this.f(names[0], ctx, partials, returnFound),
|
|||
|
cx = null;
|
|||
|
|
|||
|
if (key === '.' && isArray(ctx[ctx.length - 2])) {
|
|||
|
return ctx[ctx.length - 1];
|
|||
|
}
|
|||
|
|
|||
|
for (var i = 1; i < names.length; i++) {
|
|||
|
if (val && typeof val == 'object' && names[i] in val) {
|
|||
|
cx = val;
|
|||
|
val = val[names[i]];
|
|||
|
} else {
|
|||
|
val = '';
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (returnFound && !val) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
if (!returnFound && typeof val == 'function') {
|
|||
|
ctx.push(cx);
|
|||
|
val = this.lv(val, ctx, partials);
|
|||
|
ctx.pop();
|
|||
|
}
|
|||
|
|
|||
|
return val;
|
|||
|
},
|
|||
|
|
|||
|
// find values with normal names
|
|||
|
f: function(key, ctx, partials, returnFound) {
|
|||
|
var val = false,
|
|||
|
v = null,
|
|||
|
found = false;
|
|||
|
|
|||
|
for (var i = ctx.length - 1; i >= 0; i--) {
|
|||
|
v = ctx[i];
|
|||
|
if (v && typeof v == 'object' && key in v) {
|
|||
|
val = v[key];
|
|||
|
found = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!found) {
|
|||
|
return (returnFound) ? false : "";
|
|||
|
}
|
|||
|
|
|||
|
if (!returnFound && typeof val == 'function') {
|
|||
|
val = this.lv(val, ctx, partials);
|
|||
|
}
|
|||
|
|
|||
|
return val;
|
|||
|
},
|
|||
|
|
|||
|
// higher order templates
|
|||
|
ho: function(val, cx, partials, text, tags) {
|
|||
|
var compiler = this.c;
|
|||
|
var options = this.options;
|
|||
|
options.delimiters = tags;
|
|||
|
var text = val.call(cx, text);
|
|||
|
text = (text == null) ? String(text) : text.toString();
|
|||
|
this.b(compiler.compile(text, options).render(cx, partials));
|
|||
|
return false;
|
|||
|
},
|
|||
|
|
|||
|
// template result buffering
|
|||
|
b: (useArrayBuffer) ? function(s) { this.buf.push(s); } :
|
|||
|
function(s) { this.buf += s; },
|
|||
|
fl: (useArrayBuffer) ? function() { var r = this.buf.join(''); this.buf = []; return r; } :
|
|||
|
function() { var r = this.buf; this.buf = ''; return r; },
|
|||
|
|
|||
|
// lambda replace section
|
|||
|
ls: function(val, ctx, partials, inverted, start, end, tags) {
|
|||
|
var cx = ctx[ctx.length - 1],
|
|||
|
t = null;
|
|||
|
|
|||
|
if (!inverted && this.c && val.length > 0) {
|
|||
|
return this.ho(val, cx, partials, this.text.substring(start, end), tags);
|
|||
|
}
|
|||
|
|
|||
|
t = val.call(cx);
|
|||
|
|
|||
|
if (typeof t == 'function') {
|
|||
|
if (inverted) {
|
|||
|
return true;
|
|||
|
} else if (this.c) {
|
|||
|
return this.ho(t, cx, partials, this.text.substring(start, end), tags);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return t;
|
|||
|
},
|
|||
|
|
|||
|
// lambda replace variable
|
|||
|
lv: function(val, ctx, partials) {
|
|||
|
var cx = ctx[ctx.length - 1];
|
|||
|
var result = val.call(cx);
|
|||
|
|
|||
|
if (typeof result == 'function') {
|
|||
|
result = coerceToString(result.call(cx));
|
|||
|
if (this.c && ~result.indexOf("{\u007B")) {
|
|||
|
return this.c.compile(result, this.options).render(cx, partials);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return coerceToString(result);
|
|||
|
}
|
|||
|
|
|||
|
};
|
|||
|
|
|||
|
var rAmp = /&/g,
|
|||
|
rLt = /</g,
|
|||
|
rGt = />/g,
|
|||
|
rApos =/\'/g,
|
|||
|
rQuot = /\"/g,
|
|||
|
hChars =/[&<>\"\']/;
|
|||
|
|
|||
|
|
|||
|
function coerceToString(val) {
|
|||
|
return String((val === null || val === undefined) ? '' : val);
|
|||
|
}
|
|||
|
|
|||
|
function hoganEscape(str) {
|
|||
|
str = coerceToString(str);
|
|||
|
return hChars.test(str) ?
|
|||
|
str
|
|||
|
.replace(rAmp,'&')
|
|||
|
.replace(rLt,'<')
|
|||
|
.replace(rGt,'>')
|
|||
|
.replace(rApos,''')
|
|||
|
.replace(rQuot, '"') :
|
|||
|
str;
|
|||
|
}
|
|||
|
|
|||
|
var isArray = Array.isArray || function(a) {
|
|||
|
return Object.prototype.toString.call(a) === '[object Array]';
|
|||
|
};
|
|||
|
|
|||
|
})(typeof exports !== 'undefined' ? exports : Hogan);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
(function (Hogan) {
|
|||
|
// Setup regex assignments
|
|||
|
// remove whitespace according to Mustache spec
|
|||
|
var rIsWhitespace = /\S/,
|
|||
|
rQuot = /\"/g,
|
|||
|
rNewline = /\n/g,
|
|||
|
rCr = /\r/g,
|
|||
|
rSlash = /\\/g,
|
|||
|
tagTypes = {
|
|||
|
'#': 1, '^': 2, '/': 3, '!': 4, '>': 5,
|
|||
|
'<': 6, '=': 7, '_v': 8, '{': 9, '&': 10
|
|||
|
};
|
|||
|
|
|||
|
Hogan.scan = function scan(text, delimiters) {
|
|||
|
var len = text.length,
|
|||
|
IN_TEXT = 0,
|
|||
|
IN_TAG_TYPE = 1,
|
|||
|
IN_TAG = 2,
|
|||
|
state = IN_TEXT,
|
|||
|
tagType = null,
|
|||
|
tag = null,
|
|||
|
buf = '',
|
|||
|
tokens = [],
|
|||
|
seenTag = false,
|
|||
|
i = 0,
|
|||
|
lineStart = 0,
|
|||
|
otag = '{{',
|
|||
|
ctag = '}}';
|
|||
|
|
|||
|
function addBuf() {
|
|||
|
if (buf.length > 0) {
|
|||
|
tokens.push(new String(buf));
|
|||
|
buf = '';
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function lineIsWhitespace() {
|
|||
|
var isAllWhitespace = true;
|
|||
|
for (var j = lineStart; j < tokens.length; j++) {
|
|||
|
isAllWhitespace =
|
|||
|
(tokens[j].tag && tagTypes[tokens[j].tag] < tagTypes['_v']) ||
|
|||
|
(!tokens[j].tag && tokens[j].match(rIsWhitespace) === null);
|
|||
|
if (!isAllWhitespace) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return isAllWhitespace;
|
|||
|
}
|
|||
|
|
|||
|
function filterLine(haveSeenTag, noNewLine) {
|
|||
|
addBuf();
|
|||
|
|
|||
|
if (haveSeenTag && lineIsWhitespace()) {
|
|||
|
for (var j = lineStart, next; j < tokens.length; j++) {
|
|||
|
if (!tokens[j].tag) {
|
|||
|
if ((next = tokens[j+1]) && next.tag == '>') {
|
|||
|
// set indent to token value
|
|||
|
next.indent = tokens[j].toString()
|
|||
|
}
|
|||
|
tokens.splice(j, 1);
|
|||
|
}
|
|||
|
}
|
|||
|
} else if (!noNewLine) {
|
|||
|
tokens.push({tag:'\n'});
|
|||
|
}
|
|||
|
|
|||
|
seenTag = false;
|
|||
|
lineStart = tokens.length;
|
|||
|
}
|
|||
|
|
|||
|
function changeDelimiters(text, index) {
|
|||
|
var close = '=' + ctag,
|
|||
|
closeIndex = text.indexOf(close, index),
|
|||
|
delimiters = trim(
|
|||
|
text.substring(text.indexOf('=', index) + 1, closeIndex)
|
|||
|
).split(' ');
|
|||
|
|
|||
|
otag = delimiters[0];
|
|||
|
ctag = delimiters[1];
|
|||
|
|
|||
|
return closeIndex + close.length - 1;
|
|||
|
}
|
|||
|
|
|||
|
if (delimiters) {
|
|||
|
delimiters = delimiters.split(' ');
|
|||
|
otag = delimiters[0];
|
|||
|
ctag = delimiters[1];
|
|||
|
}
|
|||
|
|
|||
|
for (i = 0; i < len; i++) {
|
|||
|
if (state == IN_TEXT) {
|
|||
|
if (tagChange(otag, text, i)) {
|
|||
|
--i;
|
|||
|
addBuf();
|
|||
|
state = IN_TAG_TYPE;
|
|||
|
} else {
|
|||
|
if (text.charAt(i) == '\n') {
|
|||
|
filterLine(seenTag);
|
|||
|
} else {
|
|||
|
buf += text.charAt(i);
|
|||
|
}
|
|||
|
}
|
|||
|
} else if (state == IN_TAG_TYPE) {
|
|||
|
i += otag.length - 1;
|
|||
|
tag = tagTypes[text.charAt(i + 1)];
|
|||
|
tagType = tag ? text.charAt(i + 1) : '_v';
|
|||
|
if (tagType == '=') {
|
|||
|
i = changeDelimiters(text, i);
|
|||
|
state = IN_TEXT;
|
|||
|
} else {
|
|||
|
if (tag) {
|
|||
|
i++;
|
|||
|
}
|
|||
|
state = IN_TAG;
|
|||
|
}
|
|||
|
seenTag = i;
|
|||
|
} else {
|
|||
|
if (tagChange(ctag, text, i)) {
|
|||
|
tokens.push({tag: tagType, n: trim(buf), otag: otag, ctag: ctag,
|
|||
|
i: (tagType == '/') ? seenTag - ctag.length : i + otag.length});
|
|||
|
buf = '';
|
|||
|
i += ctag.length - 1;
|
|||
|
state = IN_TEXT;
|
|||
|
if (tagType == '{') {
|
|||
|
if (ctag == '}}') {
|
|||
|
i++;
|
|||
|
} else {
|
|||
|
cleanTripleStache(tokens[tokens.length - 1]);
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
buf += text.charAt(i);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
filterLine(seenTag, true);
|
|||
|
|
|||
|
return tokens;
|
|||
|
}
|
|||
|
|
|||
|
function cleanTripleStache(token) {
|
|||
|
if (token.n.substr(token.n.length - 1) === '}') {
|
|||
|
token.n = token.n.substring(0, token.n.length - 1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function trim(s) {
|
|||
|
if (s.trim) {
|
|||
|
return s.trim();
|
|||
|
}
|
|||
|
|
|||
|
return s.replace(/^\s*|\s*$/g, '');
|
|||
|
}
|
|||
|
|
|||
|
function tagChange(tag, text, index) {
|
|||
|
if (text.charAt(index) != tag.charAt(0)) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
for (var i = 1, l = tag.length; i < l; i++) {
|
|||
|
if (text.charAt(index + i) != tag.charAt(i)) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
function buildTree(tokens, kind, stack, customTags) {
|
|||
|
var instructions = [],
|
|||
|
opener = null,
|
|||
|
token = null;
|
|||
|
|
|||
|
while (tokens.length > 0) {
|
|||
|
token = tokens.shift();
|
|||
|
if (token.tag == '#' || token.tag == '^' || isOpener(token, customTags)) {
|
|||
|
stack.push(token);
|
|||
|
token.nodes = buildTree(tokens, token.tag, stack, customTags);
|
|||
|
instructions.push(token);
|
|||
|
} else if (token.tag == '/') {
|
|||
|
if (stack.length === 0) {
|
|||
|
throw new Error('Closing tag without opener: /' + token.n);
|
|||
|
}
|
|||
|
opener = stack.pop();
|
|||
|
if (token.n != opener.n && !isCloser(token.n, opener.n, customTags)) {
|
|||
|
throw new Error('Nesting error: ' + opener.n + ' vs. ' + token.n);
|
|||
|
}
|
|||
|
opener.end = token.i;
|
|||
|
return instructions;
|
|||
|
} else {
|
|||
|
instructions.push(token);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (stack.length > 0) {
|
|||
|
throw new Error('missing closing tag: ' + stack.pop().n);
|
|||
|
}
|
|||
|
|
|||
|
return instructions;
|
|||
|
}
|
|||
|
|
|||
|
function isOpener(token, tags) {
|
|||
|
for (var i = 0, l = tags.length; i < l; i++) {
|
|||
|
if (tags[i].o == token.n) {
|
|||
|
token.tag = '#';
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isCloser(close, open, tags) {
|
|||
|
for (var i = 0, l = tags.length; i < l; i++) {
|
|||
|
if (tags[i].c == close && tags[i].o == open) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Hogan.generate = function (tree, text, options) {
|
|||
|
var code = 'var _=this;_.b(i=i||"");' + walk(tree) + 'return _.fl();';
|
|||
|
if (options.asString) {
|
|||
|
return 'function(c,p,i){' + code + ';}';
|
|||
|
}
|
|||
|
|
|||
|
return new Hogan.Template(new Function('c', 'p', 'i', code), text, Hogan, options);
|
|||
|
}
|
|||
|
|
|||
|
function esc(s) {
|
|||
|
return s.replace(rSlash, '\\\\')
|
|||
|
.replace(rQuot, '\\\"')
|
|||
|
.replace(rNewline, '\\n')
|
|||
|
.replace(rCr, '\\r');
|
|||
|
}
|
|||
|
|
|||
|
function chooseMethod(s) {
|
|||
|
return (~s.indexOf('.')) ? 'd' : 'f';
|
|||
|
}
|
|||
|
|
|||
|
function walk(tree) {
|
|||
|
var code = '';
|
|||
|
for (var i = 0, l = tree.length; i < l; i++) {
|
|||
|
var tag = tree[i].tag;
|
|||
|
if (tag == '#') {
|
|||
|
code += section(tree[i].nodes, tree[i].n, chooseMethod(tree[i].n),
|
|||
|
tree[i].i, tree[i].end, tree[i].otag + " " + tree[i].ctag);
|
|||
|
} else if (tag == '^') {
|
|||
|
code += invertedSection(tree[i].nodes, tree[i].n,
|
|||
|
chooseMethod(tree[i].n));
|
|||
|
} else if (tag == '<' || tag == '>') {
|
|||
|
code += partial(tree[i]);
|
|||
|
} else if (tag == '{' || tag == '&') {
|
|||
|
code += tripleStache(tree[i].n, chooseMethod(tree[i].n));
|
|||
|
} else if (tag == '\n') {
|
|||
|
code += text('"\\n"' + (tree.length-1 == i ? '' : ' + i'));
|
|||
|
} else if (tag == '_v') {
|
|||
|
code += variable(tree[i].n, chooseMethod(tree[i].n));
|
|||
|
} else if (tag === undefined) {
|
|||
|
code += text('"' + esc(tree[i]) + '"');
|
|||
|
}
|
|||
|
}
|
|||
|
return code;
|
|||
|
}
|
|||
|
|
|||
|
function section(nodes, id, method, start, end, tags) {
|
|||
|
return 'if(_.s(_.' + method + '("' + esc(id) + '",c,p,1),' +
|
|||
|
'c,p,0,' + start + ',' + end + ',"' + tags + '")){' +
|
|||
|
'_.rs(c,p,' +
|
|||
|
'function(c,p,_){' +
|
|||
|
walk(nodes) +
|
|||
|
'});c.pop();}';
|
|||
|
}
|
|||
|
|
|||
|
function invertedSection(nodes, id, method) {
|
|||
|
return 'if(!_.s(_.' + method + '("' + esc(id) + '",c,p,1),c,p,1,0,0,"")){' +
|
|||
|
walk(nodes) +
|
|||
|
'};';
|
|||
|
}
|
|||
|
|
|||
|
function partial(tok) {
|
|||
|
return '_.b(_.rp("' + esc(tok.n) + '",c,p,"' + (tok.indent || '') + '"));';
|
|||
|
}
|
|||
|
|
|||
|
function tripleStache(id, method) {
|
|||
|
return '_.b(_.t(_.' + method + '("' + esc(id) + '",c,p,0)));';
|
|||
|
}
|
|||
|
|
|||
|
function variable(id, method) {
|
|||
|
return '_.b(_.v(_.' + method + '("' + esc(id) + '",c,p,0)));';
|
|||
|
}
|
|||
|
|
|||
|
function text(id) {
|
|||
|
return '_.b(' + id + ');';
|
|||
|
}
|
|||
|
|
|||
|
Hogan.parse = function(tokens, text, options) {
|
|||
|
options = options || {};
|
|||
|
return buildTree(tokens, '', [], options.sectionTags || []);
|
|||
|
},
|
|||
|
|
|||
|
Hogan.cache = {};
|
|||
|
|
|||
|
Hogan.compile = function(text, options) {
|
|||
|
// options
|
|||
|
//
|
|||
|
// asString: false (default)
|
|||
|
//
|
|||
|
// sectionTags: [{o: '_foo', c: 'foo'}]
|
|||
|
// An array of object with o and c fields that indicate names for custom
|
|||
|
// section tags. The example above allows parsing of {{_foo}}{{/foo}}.
|
|||
|
//
|
|||
|
// delimiters: A string that overrides the default delimiters.
|
|||
|
// Example: "<% %>"
|
|||
|
//
|
|||
|
options = options || {};
|
|||
|
|
|||
|
var key = text + '||' + !!options.asString;
|
|||
|
|
|||
|
var t = this.cache[key];
|
|||
|
|
|||
|
if (t) {
|
|||
|
return t;
|
|||
|
}
|
|||
|
|
|||
|
t = this.generate(this.parse(this.scan(text, options.delimiters), text, options), text, options);
|
|||
|
return this.cache[key] = t;
|
|||
|
};
|
|||
|
})(typeof exports !== 'undefined' ? exports : Hogan);
|
|||
|
|
|||
|
</script><script>'use strict';
|
|||
|
|
|||
|
// Syntactic sugar
|
|||
|
function $(selector) {
|
|||
|
return document.querySelector(selector);
|
|||
|
}
|
|||
|
|
|||
|
// Syntactic sugar & execute callback
|
|||
|
function $$(selector, callback) {
|
|||
|
var elems = document.querySelectorAll(selector);
|
|||
|
for (var i = 0; i < elems.length; ++i) {
|
|||
|
if (callback && typeof callback == 'function')
|
|||
|
callback.call(this, elems[i]);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var debounce = function (func, wait, now) {
|
|||
|
var timeout;
|
|||
|
return function debounced () {
|
|||
|
var that = this, args = arguments;
|
|||
|
function delayed() {
|
|||
|
if (!now)
|
|||
|
func.apply(that, args);
|
|||
|
timeout = null;
|
|||
|
};
|
|||
|
if (timeout)
|
|||
|
clearTimeout(timeout);
|
|||
|
else if (now)
|
|||
|
func.apply(obj, args);
|
|||
|
timeout = setTimeout(delayed, wait || 250);
|
|||
|
};
|
|||
|
};
|
|||
|
|
|||
|
// global namespace
|
|||
|
window.GoAccess = window.GoAccess || {
|
|||
|
initialize: function (options) {
|
|||
|
this.opts = options;
|
|||
|
|
|||
|
this.AppState = {}; // current state app key-value store
|
|||
|
this.AppTpls = {}; // precompiled templates
|
|||
|
this.AppCharts = {}; // holds all rendered charts
|
|||
|
this.AppUIData = (this.opts || {}).uiData || {}; // holds panel definitions
|
|||
|
this.AppData = (this.opts || {}).panelData || {}; // hold raw data
|
|||
|
this.AppWSConn = (this.opts || {}).wsConnection || {}; // WebSocket connection
|
|||
|
this.AppPrefs = {
|
|||
|
'theme': 'darkBlue',
|
|||
|
'perPage': 7,
|
|||
|
'layout': 'horizontal',
|
|||
|
'autoHideTables': true,
|
|||
|
};
|
|||
|
this.AppPrefs = GoAccess.Util.merge(this.AppPrefs, this.opts.prefs);
|
|||
|
|
|||
|
if (GoAccess.Util.hasLocalStorage()) {
|
|||
|
var ls = JSON.parse(localStorage.getItem('AppPrefs'));
|
|||
|
this.AppPrefs = GoAccess.Util.merge(this.AppPrefs, ls);
|
|||
|
}
|
|||
|
if (Object.keys(this.AppWSConn).length)
|
|||
|
this.setWebSocket(this.AppWSConn);
|
|||
|
|
|||
|
},
|
|||
|
|
|||
|
getPanelUI: function (panel) {
|
|||
|
return panel ? this.AppUIData[panel] : this.AppUIData;
|
|||
|
},
|
|||
|
|
|||
|
getPrefs: function (panel) {
|
|||
|
return panel ? this.AppPrefs[panel] : this.AppPrefs;
|
|||
|
},
|
|||
|
|
|||
|
setPrefs: function () {
|
|||
|
if (GoAccess.Util.hasLocalStorage()) {
|
|||
|
localStorage.setItem('AppPrefs', JSON.stringify(GoAccess.getPrefs()));
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
getPanelData: function (panel) {
|
|||
|
return panel ? this.AppData[panel] : this.AppData;
|
|||
|
},
|
|||
|
|
|||
|
setWebSocket: function (wsConn) {
|
|||
|
var host = null, host = wsConn.url ? wsConn.url : window.location.hostname ? window.location.hostname : "localhost";
|
|||
|
var str = /^(wss?:\/\/)?[^\/]+:[0-9]{1,5}\//.test(host + "/") ? host : String(host + ':' + wsConn.port);
|
|||
|
str = !/^wss?:\/\//i.test(str) ? 'ws://' + str : str;
|
|||
|
|
|||
|
var socket = new WebSocket(str);
|
|||
|
socket.onopen = function (event) {
|
|||
|
GoAccess.Nav.WSOpen();
|
|||
|
}.bind(this);
|
|||
|
|
|||
|
socket.onmessage = function (event) {
|
|||
|
this.AppState['updated'] = true;
|
|||
|
this.AppData = JSON.parse(event.data);
|
|||
|
this.App.renderData();
|
|||
|
}.bind(this);
|
|||
|
|
|||
|
socket.onclose = function (event) {
|
|||
|
GoAccess.Nav.WSClose();
|
|||
|
}.bind(this);
|
|||
|
},
|
|||
|
};
|
|||
|
|
|||
|
// HELPERS
|
|||
|
GoAccess.Util = {
|
|||
|
months: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul","Aug", "Sep", "Oct", "Nov", "Dec"],
|
|||
|
|
|||
|
// Add all attributes of n to o
|
|||
|
merge: function (o, n) {
|
|||
|
var obj = {}, i = 0, il = arguments.length, key;
|
|||
|
for (; i < il; i++) {
|
|||
|
for (key in arguments[i]) {
|
|||
|
if (arguments[i].hasOwnProperty(key)) {
|
|||
|
obj[key] = arguments[i][key];
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return obj;
|
|||
|
},
|
|||
|
|
|||
|
// hash a string
|
|||
|
hashCode: function (s) {
|
|||
|
return (s.split('').reduce(function (a, b) {
|
|||
|
a = ((a << 5) - a) + b.charCodeAt(0);
|
|||
|
return a&a;
|
|||
|
}, 0) >>> 0).toString(16);
|
|||
|
},
|
|||
|
|
|||
|
// Format bytes to human readable
|
|||
|
formatBytes: function (bytes, decimals, numOnly) {
|
|||
|
if (bytes == 0)
|
|||
|
return numOnly ? 0 : '0 Byte';
|
|||
|
var k = 1024;
|
|||
|
var dm = decimals + 1 || 2;
|
|||
|
var sizes = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
|
|||
|
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
|||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + (numOnly ? '' : (' ' + sizes[i]));
|
|||
|
},
|
|||
|
|
|||
|
// Validate number
|
|||
|
isNumeric: function (n) {
|
|||
|
return !isNaN(parseFloat(n)) && isFinite(n);
|
|||
|
},
|
|||
|
|
|||
|
// Format microseconds to human readable
|
|||
|
utime2str: function (usec) {
|
|||
|
if (usec >= 864E8)
|
|||
|
return ((usec) / 864E8).toFixed(2) + ' d';
|
|||
|
else if (usec >= 36E8)
|
|||
|
return ((usec) / 36E8).toFixed(2) + ' h';
|
|||
|
else if (usec >= 6E7)
|
|||
|
return ((usec) / 6E7).toFixed(2) + ' m';
|
|||
|
else if (usec >= 1E6)
|
|||
|
return ((usec) / 1E6).toFixed(2) + ' s';
|
|||
|
else if (usec >= 1E3)
|
|||
|
return ((usec) / 1E3).toFixed(2) + ' ms';
|
|||
|
return (usec).toFixed(2) + ' us';
|
|||
|
},
|
|||
|
|
|||
|
// Format date from 20120124 to 24/Jan/2012
|
|||
|
formatDate: function (str) {
|
|||
|
var y = str.substr(0,4), m = str.substr(4,2) - 1, d = str.substr(6,2),
|
|||
|
h = str.substr(8,2) || 0, i = str.substr(10, 2) || 0, s = str.substr(12, 2) || 0;
|
|||
|
var date = new Date(y,m,d,h,i,s);
|
|||
|
|
|||
|
var out = ('0' + date.getDate()).slice(-2) + '/' + this.months[date.getMonth()] + '/' + date.getFullYear();
|
|||
|
10 <= str.length && (out += ":" + h);
|
|||
|
12 <= str.length && (out += ":" + i);
|
|||
|
14 <= str.length && (out += ":" + s);
|
|||
|
return out;
|
|||
|
},
|
|||
|
|
|||
|
// Format field value to human readable
|
|||
|
fmtValue: function (value, dataType) {
|
|||
|
var val = 0;
|
|||
|
if (!dataType)
|
|||
|
val = value;
|
|||
|
|
|||
|
switch (dataType) {
|
|||
|
case 'utime':
|
|||
|
val = this.utime2str(value);
|
|||
|
break;
|
|||
|
case 'date':
|
|||
|
val = this.formatDate(value);
|
|||
|
break;
|
|||
|
case 'numeric':
|
|||
|
if (this.isNumeric(value))
|
|||
|
val = value.toLocaleString();
|
|||
|
break;
|
|||
|
case 'bytes':
|
|||
|
val = this.formatBytes(value);
|
|||
|
break;
|
|||
|
case 'percent':
|
|||
|
val = value.toFixed(2) + '%';
|
|||
|
break;
|
|||
|
case 'time':
|
|||
|
if (this.isNumeric(value))
|
|||
|
val = value.toLocaleString();
|
|||
|
break;
|
|||
|
case 'secs':
|
|||
|
val = value + ' secs';
|
|||
|
break;
|
|||
|
default:
|
|||
|
val = value;
|
|||
|
};
|
|||
|
|
|||
|
return value == 0 ? String(val) : val;
|
|||
|
},
|
|||
|
|
|||
|
isPanelValid: function (panel) {
|
|||
|
var data = GoAccess.getPanelData(), ui = GoAccess.getPanelUI();
|
|||
|
return (!ui.hasOwnProperty(panel) || !data.hasOwnProperty(panel) || !ui[panel].id);
|
|||
|
},
|
|||
|
|
|||
|
// Attempts to extract the count from either an object or a scalar.
|
|||
|
// e.g., item = Object {count: 14351, percent: 5.79} OR item = 4824825140
|
|||
|
getCount: function (item) {
|
|||
|
if (this.isObject(item) && 'count' in item)
|
|||
|
return item.count;
|
|||
|
return item;
|
|||
|
},
|
|||
|
|
|||
|
getPercent: function (item) {
|
|||
|
if (this.isObject(item) && 'percent' in item)
|
|||
|
return this.fmtValue(item.percent, 'percent');
|
|||
|
return null;
|
|||
|
},
|
|||
|
|
|||
|
isObject: function (o) {
|
|||
|
return o === Object(o);
|
|||
|
},
|
|||
|
|
|||
|
setProp: function (o, s, v) {
|
|||
|
var schema = o;
|
|||
|
var a = s.split('.');
|
|||
|
for (var i = 0, n = a.length; i < n-1; ++i) {
|
|||
|
var k = a[i];
|
|||
|
if (!schema[k]) schema[k] = {}
|
|||
|
schema = schema[k];
|
|||
|
}
|
|||
|
schema[a[n-1]] = v;
|
|||
|
},
|
|||
|
|
|||
|
getProp: function (o, s) {
|
|||
|
s = s.replace(/\[(\w+)\]/g, '.$1');
|
|||
|
s = s.replace(/^\./, '');
|
|||
|
var a = s.split('.');
|
|||
|
for (var i = 0, n = a.length; i < n; ++i) {
|
|||
|
var k = a[i];
|
|||
|
if (this.isObject(o) && k in o) {
|
|||
|
o = o[k];
|
|||
|
} else {
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
return o;
|
|||
|
},
|
|||
|
|
|||
|
hasLocalStorage: function () {
|
|||
|
try {
|
|||
|
localStorage.setItem('test', 'test');
|
|||
|
localStorage.removeItem('test');
|
|||
|
return true;
|
|||
|
} catch(e) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
isWithinViewPort: function (el) {
|
|||
|
var elemTop = el.getBoundingClientRect().top;
|
|||
|
var elemBottom = el.getBoundingClientRect().bottom;
|
|||
|
return elemTop < window.innerHeight && elemBottom >= 0;
|
|||
|
},
|
|||
|
};
|
|||
|
|
|||
|
// OVERALL STATS
|
|||
|
GoAccess.OverallStats = {
|
|||
|
// Render general section wrapper
|
|||
|
renderWrapper: function (ui) {
|
|||
|
$('.wrap-general').innerHTML = GoAccess.AppTpls.General.wrap.render(ui);
|
|||
|
},
|
|||
|
|
|||
|
// Render each overall stats box
|
|||
|
renderBox: function (data, ui, row, x, idx) {
|
|||
|
var wrap = $('.wrap-general-items');
|
|||
|
|
|||
|
// create a new bootstrap row every 6 elements
|
|||
|
if (idx % 6 == 0) {
|
|||
|
row = document.createElement('div');
|
|||
|
row.setAttribute('class', 'row');
|
|||
|
wrap.appendChild(row);
|
|||
|
}
|
|||
|
|
|||
|
var box = document.createElement('div');
|
|||
|
box.innerHTML = GoAccess.AppTpls.General.items.render({
|
|||
|
'id': x,
|
|||
|
'className': ui.items[x].className,
|
|||
|
'label': ui.items[x].label,
|
|||
|
'value': GoAccess.Util.fmtValue(data[x], ui.items[x].dataType),
|
|||
|
});
|
|||
|
row.appendChild(box);
|
|||
|
|
|||
|
return row;
|
|||
|
},
|
|||
|
|
|||
|
// Render overall stats
|
|||
|
renderData: function (data, ui) {
|
|||
|
var idx = 0, row = null;
|
|||
|
|
|||
|
$('.last-updated').innerHTML = data.date_time;
|
|||
|
$$('span.from', function (item) {
|
|||
|
item.innerHTML = data.start_date
|
|||
|
});
|
|||
|
$$('span.to', function (item) {
|
|||
|
item.innerHTML = data.end_date
|
|||
|
});
|
|||
|
// Iterate over general data object
|
|||
|
for (var x in data) {
|
|||
|
if (!data.hasOwnProperty(x) || !ui.items.hasOwnProperty(x))
|
|||
|
continue;
|
|||
|
row = this.renderBox(data, ui, row, x, idx);
|
|||
|
idx++;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// Render general/overall analyzed requests.
|
|||
|
initialize: function () {
|
|||
|
var ui = GoAccess.getPanelUI('general');
|
|||
|
var data = GoAccess.getPanelData('general'), i = 0;
|
|||
|
|
|||
|
this.renderWrapper(ui);
|
|||
|
this.renderData(data, ui);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
// RENDER PANELS
|
|||
|
GoAccess.Nav = {
|
|||
|
events: function () {
|
|||
|
$('.nav-bars').onclick = function (e) {
|
|||
|
e.stopPropagation();
|
|||
|
this.renderMenu(e);
|
|||
|
}.bind(this);
|
|||
|
|
|||
|
$('.nav-gears').onclick = function (e) {
|
|||
|
e.stopPropagation();
|
|||
|
this.renderOpts(e);
|
|||
|
}.bind(this);
|
|||
|
|
|||
|
$('.nav-minibars').onclick = function (e) {
|
|||
|
e.stopPropagation();
|
|||
|
this.renderOpts(e);
|
|||
|
}.bind(this);
|
|||
|
|
|||
|
$('body').onclick = function (e) {
|
|||
|
$('nav').classList.remove('active');
|
|||
|
}.bind(this);
|
|||
|
|
|||
|
$$('.export-json', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
this.downloadJSON(e);
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('.theme-bright', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
this.setTheme('bright');
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('.theme-dark-blue', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
this.setTheme('darkBlue');
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('.theme-dark-gray', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
this.setTheme('darkGray');
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('.layout-horizontal', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
this.setLayout('horizontal');
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('.layout-vertical', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
this.setLayout('vertical');
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('[data-perpage]', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
this.setPerPage(e);
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('[data-show-tables]', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
this.toggleTables();
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('[data-autohide-tables]', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
this.toggleAutoHideTables();
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
},
|
|||
|
|
|||
|
downloadJSON: function (e) {
|
|||
|
var targ = e.currentTarget;
|
|||
|
var data = "text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(GoAccess.getPanelData()));
|
|||
|
targ.href = 'data:' + data;
|
|||
|
targ.download = 'goaccess-' + +new Date() + '.json';
|
|||
|
},
|
|||
|
|
|||
|
setLayout: function (layout) {
|
|||
|
if ('horizontal' == layout) {
|
|||
|
$('.container').classList.add('container-fluid');
|
|||
|
$('.container').classList.remove('container');
|
|||
|
} else if ('vertical' == layout) {
|
|||
|
$('.container-fluid').classList.add('container');
|
|||
|
$('.container').classList.remove('container-fluid');
|
|||
|
}
|
|||
|
|
|||
|
GoAccess.AppPrefs['layout'] = layout;
|
|||
|
GoAccess.setPrefs();
|
|||
|
|
|||
|
GoAccess.Panels.initialize();
|
|||
|
GoAccess.Charts.initialize();
|
|||
|
GoAccess.Tables.initialize();
|
|||
|
},
|
|||
|
|
|||
|
toggleAutoHideTables: function (e) {
|
|||
|
var autoHideTables = GoAccess.Tables.autoHideTables();
|
|||
|
$$('.table-wrapper', function (item) {
|
|||
|
if (autoHideTables)
|
|||
|
item.classList.remove('hidden-xs');
|
|||
|
else
|
|||
|
item.classList.add('hidden-xs');
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
GoAccess.AppPrefs['autoHideTables'] = !autoHideTables;
|
|||
|
GoAccess.setPrefs();
|
|||
|
},
|
|||
|
|
|||
|
toggleTables: function () {
|
|||
|
var ui = GoAccess.getPanelUI();
|
|||
|
var showTables = GoAccess.Tables.showTables();
|
|||
|
Object.keys(ui).forEach(function (panel, idx) {
|
|||
|
if (!GoAccess.Util.isPanelValid(panel))
|
|||
|
ui[panel]['table'] = !showTables;
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
GoAccess.AppPrefs['showTables'] = !showTables;
|
|||
|
GoAccess.setPrefs();
|
|||
|
|
|||
|
GoAccess.Panels.initialize();
|
|||
|
GoAccess.Charts.initialize();
|
|||
|
GoAccess.Tables.initialize();
|
|||
|
},
|
|||
|
|
|||
|
setTheme: function (theme) {
|
|||
|
if (!theme)
|
|||
|
return;
|
|||
|
|
|||
|
$('html').className = '';
|
|||
|
switch(theme) {
|
|||
|
case 'darkGray':
|
|||
|
$('html').classList.add('dark');
|
|||
|
$('html').classList.add('gray');
|
|||
|
break;
|
|||
|
case 'darkBlue':
|
|||
|
$('html').classList.add('dark');
|
|||
|
$('html').classList.add('blue');
|
|||
|
break;
|
|||
|
}
|
|||
|
GoAccess.AppPrefs['theme'] = theme;
|
|||
|
GoAccess.setPrefs();
|
|||
|
},
|
|||
|
|
|||
|
getIcon: function (key) {
|
|||
|
switch(key) {
|
|||
|
case 'visitors' : return 'users';
|
|||
|
case 'requests' : return 'file';
|
|||
|
case 'static_requests' : return 'file-text';
|
|||
|
case 'not_found' : return 'file-o';
|
|||
|
case 'hosts' : return 'user';
|
|||
|
case 'os' : return 'desktop';
|
|||
|
case 'browsers' : return 'chrome';
|
|||
|
case 'visit_time' : return 'clock-o';
|
|||
|
case 'vhosts' : return 'th-list';
|
|||
|
case 'referrers' : return 'external-link';
|
|||
|
case 'referring_sites' : return 'external-link';
|
|||
|
case 'keyphrases' : return 'google';
|
|||
|
case 'status_codes' : return 'warning';
|
|||
|
case 'remote_user' : return 'users';
|
|||
|
case 'geolocation' : return 'map-marker';
|
|||
|
default : return 'pie-chart';
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
getItems: function () {
|
|||
|
var ui = GoAccess.getPanelUI(), menu = [];
|
|||
|
for (var panel in ui) {
|
|||
|
if (GoAccess.Util.isPanelValid(panel))
|
|||
|
continue;
|
|||
|
// Push valid panels to our navigation array
|
|||
|
menu.push({
|
|||
|
'current': window.location.hash.substr(1) == panel,
|
|||
|
'head': ui[panel].head,
|
|||
|
'key': panel,
|
|||
|
'icon': this.getIcon(panel),
|
|||
|
});
|
|||
|
}
|
|||
|
return menu;
|
|||
|
},
|
|||
|
|
|||
|
setPerPage: function (e) {
|
|||
|
GoAccess.AppPrefs['perPage'] = +e.currentTarget.getAttribute('data-perpage');
|
|||
|
GoAccess.App.renderData();
|
|||
|
GoAccess.setPrefs();
|
|||
|
},
|
|||
|
|
|||
|
getTheme: function () {
|
|||
|
return GoAccess.AppPrefs.theme || 'darkGray';
|
|||
|
},
|
|||
|
|
|||
|
getLayout: function () {
|
|||
|
return GoAccess.AppPrefs.layout || 'horizontal';
|
|||
|
},
|
|||
|
|
|||
|
getPerPage: function () {
|
|||
|
return GoAccess.AppPrefs.perPage || 7;
|
|||
|
},
|
|||
|
|
|||
|
// Render left-hand side navigation options.
|
|||
|
renderOpts: function () {
|
|||
|
var o = {};
|
|||
|
o[this.getLayout()] = true;
|
|||
|
o[this.getTheme()] = true;
|
|||
|
o['perPage' + this.getPerPage()] = true;
|
|||
|
o['autoHideTables'] = GoAccess.Tables.autoHideTables();
|
|||
|
o['showTables'] = GoAccess.Tables.showTables();
|
|||
|
|
|||
|
$('.nav-list').innerHTML = GoAccess.AppTpls.Nav.opts.render(o);
|
|||
|
$('nav').classList.toggle('active');
|
|||
|
this.events();
|
|||
|
},
|
|||
|
|
|||
|
// Render left-hand side navigation given the available panels.
|
|||
|
renderMenu: function (e) {
|
|||
|
$('.nav-list').innerHTML = GoAccess.AppTpls.Nav.menu.render({
|
|||
|
'nav': this.getItems(),
|
|||
|
'overall': window.location.hash.substr(1) == '',
|
|||
|
});
|
|||
|
$('nav').classList.toggle('active');
|
|||
|
this.events();
|
|||
|
},
|
|||
|
|
|||
|
WSStatus: function () {
|
|||
|
if (Object.keys(GoAccess.AppWSConn).length)
|
|||
|
$$('.nav-ws-status', function (item) { item.style.display = 'block' });
|
|||
|
},
|
|||
|
|
|||
|
WSClose: function () {
|
|||
|
$$('.nav-ws-status', function (item) {
|
|||
|
item.classList.remove('connected');
|
|||
|
item.setAttribute('title', 'Disconnected');
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
WSOpen: function () {
|
|||
|
$$('.nav-ws-status', function (item) {
|
|||
|
item.classList.add('connected');
|
|||
|
item.setAttribute('title', 'Connected to ' + GoAccess.AppWSConn.url);
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
// Render left-hand side navigation given the available panels.
|
|||
|
renderWrap: function (nav) {
|
|||
|
$('nav').innerHTML = GoAccess.AppTpls.Nav.wrap.render({});
|
|||
|
},
|
|||
|
|
|||
|
// Iterate over all available panels and render each.
|
|||
|
initialize: function () {
|
|||
|
this.setTheme(GoAccess.AppPrefs.theme);
|
|||
|
this.renderWrap();
|
|||
|
this.WSStatus();
|
|||
|
this.events();
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
// RENDER PANELS
|
|||
|
GoAccess.Panels = {
|
|||
|
events: function () {
|
|||
|
$$('[data-toggle=dropdown]', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
this.openOpts(e.currentTarget);
|
|||
|
}.bind(this);
|
|||
|
item.onblur = function (e) {
|
|||
|
this.closeOpts(e);
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('[data-plot]', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
GoAccess.Charts.redrawChart(e.currentTarget);
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('[data-chart-type]', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
GoAccess.Charts.setChartType(e.currentTarget);
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('[data-metric]', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
GoAccess.Tables.toggleColumn(e.currentTarget);
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
},
|
|||
|
|
|||
|
openOpts: function (targ) {
|
|||
|
var panel = targ.getAttribute('data-panel');
|
|||
|
targ.parentElement.classList.toggle('open');
|
|||
|
this.renderOpts(panel);
|
|||
|
},
|
|||
|
|
|||
|
closeOpts: function (e) {
|
|||
|
e.currentTarget.parentElement.classList.remove('open');
|
|||
|
// Trigger the click event on the target if not opening another menu
|
|||
|
if (e.relatedTarget && e.relatedTarget.getAttribute('data-toggle') !== 'dropdown')
|
|||
|
e.relatedTarget.click();
|
|||
|
},
|
|||
|
|
|||
|
setPlotSelection: function (ui, prefs) {
|
|||
|
var chartType = ((prefs || {}).plot || {}).chartType || ui.plot[0].chartType;
|
|||
|
var metric = ((prefs || {}).plot || {}).metric || ui.plot[0].className;
|
|||
|
|
|||
|
ui[chartType] = true;
|
|||
|
for (var i = 0, len = ui.plot.length; i < len; ++i)
|
|||
|
if (ui.plot[i].className == metric)
|
|||
|
ui.plot[i]['selected'] = true;
|
|||
|
},
|
|||
|
|
|||
|
setColSelection: function (items, prefs) {
|
|||
|
var columns = (prefs || {}).columns || {};
|
|||
|
for (var i = 0, len = items.length; i < len; ++i)
|
|||
|
if ((items[i].key in columns) && columns[items[i].key]['hide'])
|
|||
|
items[i]['hide'] = true;
|
|||
|
},
|
|||
|
|
|||
|
setOpts: function (panel) {
|
|||
|
var ui = JSON.parse(JSON.stringify(GoAccess.getPanelUI(panel))), prefs = GoAccess.getPrefs(panel);
|
|||
|
this.setPlotSelection(ui, prefs);
|
|||
|
this.setColSelection(ui.items, prefs);
|
|||
|
return ui;
|
|||
|
},
|
|||
|
|
|||
|
renderOpts: function (panel) {
|
|||
|
$('.panel-opts-' + panel).innerHTML = GoAccess.AppTpls.Panels.opts.render(this.setOpts(panel));
|
|||
|
this.events();
|
|||
|
},
|
|||
|
|
|||
|
enablePrev: function (panel) {
|
|||
|
var $pagination = $('#panel-' + panel + ' .pagination a.panel-prev');
|
|||
|
if ($pagination)
|
|||
|
$pagination.parentNode.classList.remove('disabled');
|
|||
|
},
|
|||
|
|
|||
|
disablePrev: function (panel) {
|
|||
|
var $pagination = $('#panel-' + panel + ' .pagination a.panel-prev');
|
|||
|
if ($pagination)
|
|||
|
$pagination.parentNode.classList.add('disabled');
|
|||
|
},
|
|||
|
|
|||
|
enableNext: function (panel) {
|
|||
|
var $pagination = $('#panel-' + panel + ' .pagination a.panel-next');
|
|||
|
if ($pagination)
|
|||
|
$pagination.parentNode.classList.remove('disabled');
|
|||
|
},
|
|||
|
|
|||
|
disableNext: function (panel) {
|
|||
|
var $pagination = $('#panel-' + panel + ' .pagination a.panel-next');
|
|||
|
if ($pagination)
|
|||
|
$pagination.parentNode.classList.add('disabled');
|
|||
|
},
|
|||
|
|
|||
|
enablePagination: function (panel) {
|
|||
|
this.enablePrev(panel);
|
|||
|
this.enableNext(panel);
|
|||
|
},
|
|||
|
|
|||
|
disablePagination: function (panel) {
|
|||
|
this.disablePrev(panel);
|
|||
|
this.disableNext(panel);
|
|||
|
},
|
|||
|
|
|||
|
hasSubItems: function (ui, data) {
|
|||
|
for (var i = 0, len = data.length; i < len; ++i) {
|
|||
|
if (!data[i].items)
|
|||
|
return (ui['hasSubItems'] = false);
|
|||
|
if (data[i].items.length) {
|
|||
|
return (ui['hasSubItems'] = true);
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
},
|
|||
|
|
|||
|
// Render the given panel given a user interface definition.
|
|||
|
renderPanel: function (panel, ui, row, idx) {
|
|||
|
var wrap = $('.wrap-panels');
|
|||
|
var every = GoAccess.AppPrefs['layout'] == 'horizontal' ? 2 : 1;
|
|||
|
var perRow = GoAccess.AppPrefs['layout'] == 'horizontal' ? 6 : 12;
|
|||
|
|
|||
|
// create a new bootstrap row every one or two elements depending on
|
|||
|
// the layout
|
|||
|
if (idx % every == 0) {
|
|||
|
row = document.createElement('div');
|
|||
|
row.setAttribute('class', 'row' + (every == 2 ? ' equal' : ''));
|
|||
|
wrap.appendChild(row);
|
|||
|
}
|
|||
|
|
|||
|
var data = GoAccess.getPanelData(panel);
|
|||
|
this.hasSubItems(ui, data.data);
|
|||
|
GoAccess.Tables.hasTables(ui);
|
|||
|
|
|||
|
// set the number of columns based on current layout
|
|||
|
var col = document.createElement('div');
|
|||
|
col.setAttribute('class', 'col-md-' + perRow + ' wrap-panel');
|
|||
|
row.appendChild(col);
|
|||
|
|
|||
|
// per panel wrapper
|
|||
|
var box = document.createElement('div');
|
|||
|
box.id = 'panel-' + panel;
|
|||
|
box.innerHTML = GoAccess.AppTpls.Panels.wrap.render(ui);
|
|||
|
col.appendChild(box);
|
|||
|
|
|||
|
// Remove pagination if not enough data for the given panel
|
|||
|
if (data.data.length <= GoAccess.getPrefs().perPage)
|
|||
|
this.disablePagination(panel);
|
|||
|
GoAccess.Tables.renderThead(panel, ui);
|
|||
|
|
|||
|
return row;
|
|||
|
},
|
|||
|
|
|||
|
// Iterate over all available panels and render each panel
|
|||
|
// structure.
|
|||
|
renderPanels: function () {
|
|||
|
var ui = GoAccess.getPanelUI(), idx = 0, row = null;
|
|||
|
|
|||
|
$('.wrap-panels').innerHTML = '';
|
|||
|
for (var panel in ui) {
|
|||
|
if (GoAccess.Util.isPanelValid(panel))
|
|||
|
continue;
|
|||
|
// Render panel given a user interface definition
|
|||
|
row = this.renderPanel(panel, ui[panel], row, idx++);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
initialize: function () {
|
|||
|
this.renderPanels();
|
|||
|
this.events();
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
// RENDER CHARTS
|
|||
|
GoAccess.Charts = {
|
|||
|
iter: function (callback) {
|
|||
|
Object.keys(GoAccess.AppCharts).forEach(function (panel) {
|
|||
|
// redraw chart only if it's within the viewport
|
|||
|
if (!GoAccess.Util.isWithinViewPort($('#panel-' + panel)))
|
|||
|
return;
|
|||
|
if (callback && typeof callback === 'function')
|
|||
|
callback.call(this, GoAccess.AppCharts[panel], panel);
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
getMetricKeys: function (panel, key) {
|
|||
|
return GoAccess.getPanelUI(panel)['items'].map(function (a) { return a[key]; });
|
|||
|
},
|
|||
|
|
|||
|
getPanelData: function (panel, data) {
|
|||
|
// Grab ui plot data for the selected panel
|
|||
|
var plot = GoAccess.Util.getProp(GoAccess.AppState, panel + '.plot');
|
|||
|
|
|||
|
// Grab the data for the selected panel
|
|||
|
data = data || this.processChartData(GoAccess.getPanelData(panel).data);
|
|||
|
return plot.chartReverse ? data.reverse() : data;
|
|||
|
},
|
|||
|
|
|||
|
renderChart: function (panel, chart, data) {
|
|||
|
// remove popup
|
|||
|
d3.select('#chart-' + panel + '>.chart-tooltip-wrap')
|
|||
|
.remove();
|
|||
|
// remove svg
|
|||
|
d3.select('#chart-' + panel).select('svg')
|
|||
|
.remove();
|
|||
|
// add chart to the document
|
|||
|
d3.select("#chart-" + panel)
|
|||
|
.datum(data)
|
|||
|
.call(chart)
|
|||
|
.append("div").attr("class", "chart-tooltip-wrap");
|
|||
|
},
|
|||
|
|
|||
|
drawPlot: function (panel, plotUI, data) {
|
|||
|
var chart = this.getChart(panel, plotUI, data);
|
|||
|
if (!chart)
|
|||
|
return;
|
|||
|
|
|||
|
this.renderChart(panel, chart, data);
|
|||
|
GoAccess.AppCharts[panel] = null;
|
|||
|
GoAccess.AppCharts[panel] = chart;
|
|||
|
},
|
|||
|
|
|||
|
setChartType: function (targ) {
|
|||
|
var panel = targ.getAttribute('data-panel');
|
|||
|
var type = targ.getAttribute('data-chart-type');
|
|||
|
|
|||
|
GoAccess.Util.setProp(GoAccess.AppPrefs, panel + '.plot.chartType', type);
|
|||
|
GoAccess.setPrefs();
|
|||
|
|
|||
|
var plotUI = GoAccess.Util.getProp(GoAccess.AppState, panel + '.plot');
|
|||
|
// Extract data for the selected panel and process it
|
|||
|
this.drawPlot(panel, plotUI, this.getPanelData(panel));
|
|||
|
},
|
|||
|
|
|||
|
// Redraw a chart upon selecting a metric.
|
|||
|
redrawChart: function (targ) {
|
|||
|
var plot = targ.getAttribute('data-plot');
|
|||
|
var panel = targ.getAttribute('data-panel');
|
|||
|
var ui = GoAccess.getPanelUI(panel);
|
|||
|
var plotUI = ui.plot;
|
|||
|
|
|||
|
GoAccess.Util.setProp(GoAccess.AppPrefs, panel + '.plot.metric', plot);
|
|||
|
GoAccess.setPrefs();
|
|||
|
|
|||
|
// Iterate over plot user interface definition
|
|||
|
for (var x in plotUI) {
|
|||
|
if (!plotUI.hasOwnProperty(x) || plotUI[x].className != plot)
|
|||
|
continue;
|
|||
|
|
|||
|
GoAccess.Util.setProp(GoAccess.AppState, panel + '.plot', plotUI[x]);
|
|||
|
// Extract data for the selected panel and process it
|
|||
|
this.drawPlot(panel, plotUI[x], this.getPanelData(panel));
|
|||
|
break;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// Iterate over the item properties and and extract the count value.
|
|||
|
extractCount: function (item) {
|
|||
|
var o = {};
|
|||
|
for (var prop in item)
|
|||
|
o[prop] = GoAccess.Util.getCount(item[prop]);
|
|||
|
return o;
|
|||
|
},
|
|||
|
|
|||
|
// Extract an array of objects that D3 can consume to process the chart.
|
|||
|
// e.g., o = Object {hits: 37402, visitors: 6949, bytes:
|
|||
|
// 505881789, avgts: 118609, cumts: 4436224010…}
|
|||
|
processChartData: function (data) {
|
|||
|
var out = [];
|
|||
|
for (var i = 0; i < data.length; ++i)
|
|||
|
out.push(this.extractCount(data[i]));
|
|||
|
return out;
|
|||
|
},
|
|||
|
|
|||
|
findUIItem: function (panel, key) {
|
|||
|
var items = GoAccess.getPanelUI(panel).items, o = {};
|
|||
|
for (var i = 0; i < items.length; ++i) {
|
|||
|
if (items[i].key == key)
|
|||
|
return items[i];
|
|||
|
}
|
|||
|
return null;
|
|||
|
},
|
|||
|
|
|||
|
getXKey: function (datum, key) {
|
|||
|
var arr = [];
|
|||
|
if (typeof key === 'string')
|
|||
|
return datum[key];
|
|||
|
for (var prop in key)
|
|||
|
arr.push(datum[key[prop]]);
|
|||
|
return arr.join(' ');
|
|||
|
},
|
|||
|
|
|||
|
getAreaSpline: function (panel, plotUI, data) {
|
|||
|
var dualYaxis = plotUI['d3']['y1'];
|
|||
|
|
|||
|
var chart = AreaChart(dualYaxis)
|
|||
|
.labels({
|
|||
|
y0: plotUI['d3']['y0'].label,
|
|||
|
y1: dualYaxis ? plotUI['d3']['y1'].label : ''
|
|||
|
})
|
|||
|
.x(function (d) {
|
|||
|
if ((((plotUI || {}).d3 || {}).x || {}).key)
|
|||
|
return this.getXKey(d, plotUI['d3']['x']['key']);
|
|||
|
return d.data;;
|
|||
|
}.bind(this))
|
|||
|
.y0(function (d) {
|
|||
|
return +d[plotUI['d3']['y0']['key']];
|
|||
|
})
|
|||
|
.width($("#chart-" + panel).getBoundingClientRect().width)
|
|||
|
.height(175)
|
|||
|
.format({
|
|||
|
x: (this.findUIItem(panel, 'data') || {}).dataType || null,
|
|||
|
y0: ((plotUI.d3 || {}).y0 || {}).format,
|
|||
|
y1: ((plotUI.d3 || {}).y1 || {}).format,
|
|||
|
})
|
|||
|
.opts(plotUI);
|
|||
|
|
|||
|
dualYaxis && chart.y1(function (d) {
|
|||
|
return +d[plotUI['d3']['y1']['key']];
|
|||
|
});
|
|||
|
|
|||
|
return chart;
|
|||
|
},
|
|||
|
|
|||
|
getVBar: function (panel, plotUI, data) {
|
|||
|
var dualYaxis = plotUI['d3']['y1'];
|
|||
|
|
|||
|
var chart = BarChart(dualYaxis)
|
|||
|
.labels({
|
|||
|
y0: plotUI['d3']['y0'].label,
|
|||
|
y1: dualYaxis ? plotUI['d3']['y1'].label : ''
|
|||
|
})
|
|||
|
.x(function (d) {
|
|||
|
if ((((plotUI || {}).d3 || {}).x || {}).key)
|
|||
|
return this.getXKey(d, plotUI['d3']['x']['key']);
|
|||
|
return d.data;;
|
|||
|
}.bind(this))
|
|||
|
.y0(function (d) {
|
|||
|
return +d[plotUI['d3']['y0']['key']];
|
|||
|
})
|
|||
|
.width($("#chart-" + panel).getBoundingClientRect().width)
|
|||
|
.height(175)
|
|||
|
.format({
|
|||
|
x: (this.findUIItem(panel, 'data') || {}).dataType || null,
|
|||
|
y0: ((plotUI.d3 || {}).y0 || {}).format,
|
|||
|
y1: ((plotUI.d3 || {}).y1 || {}).format,
|
|||
|
})
|
|||
|
.opts(plotUI);
|
|||
|
|
|||
|
dualYaxis && chart.y1(function (d) {
|
|||
|
return +d[plotUI['d3']['y1']['key']];
|
|||
|
});
|
|||
|
|
|||
|
return chart;
|
|||
|
},
|
|||
|
|
|||
|
getChartType: function (panel) {
|
|||
|
var ui = GoAccess.getPanelUI(panel);
|
|||
|
if (!ui.plot.length)
|
|||
|
return '';
|
|||
|
|
|||
|
return GoAccess.Util.getProp(GoAccess.getPrefs(), panel + '.plot.chartType') || ui.plot[0].chartType;
|
|||
|
},
|
|||
|
|
|||
|
getPlotUI: function (panel, ui) {
|
|||
|
var metric = GoAccess.Util.getProp(GoAccess.getPrefs(), panel + '.plot.metric');
|
|||
|
if (!metric)
|
|||
|
return ui.plot[0];
|
|||
|
return ui.plot.filter(function (v) {
|
|||
|
return v.className == metric;
|
|||
|
})[0];
|
|||
|
},
|
|||
|
|
|||
|
getChart: function (panel, plotUI, data) {
|
|||
|
var chart = null;
|
|||
|
|
|||
|
// Render given its type
|
|||
|
switch (this.getChartType(panel)) {
|
|||
|
case 'area-spline':
|
|||
|
chart = this.getAreaSpline(panel, plotUI, data);
|
|||
|
break;
|
|||
|
case 'bar':
|
|||
|
chart = this.getVBar(panel, plotUI, data);
|
|||
|
break;
|
|||
|
};
|
|||
|
|
|||
|
return chart;
|
|||
|
},
|
|||
|
|
|||
|
// Render all charts for the applicable panels.
|
|||
|
renderCharts: function (ui) {
|
|||
|
var plotUI = null, chart = null;
|
|||
|
for (var panel in ui) {
|
|||
|
if (!ui.hasOwnProperty(panel))
|
|||
|
continue;
|
|||
|
// Ensure it has a plot definitions
|
|||
|
if (!ui[panel].plot || !ui[panel].plot.length)
|
|||
|
continue;
|
|||
|
|
|||
|
plotUI = this.getPlotUI(panel, ui[panel]);
|
|||
|
// set ui plot data
|
|||
|
GoAccess.Util.setProp(GoAccess.AppState, panel + '.plot', plotUI);
|
|||
|
|
|||
|
// Grab the data for the selected panel
|
|||
|
var data = this.getPanelData(panel);
|
|||
|
if (!(chart = this.getChart(panel, plotUI, data)))
|
|||
|
continue;
|
|||
|
|
|||
|
this.renderChart(panel, chart, data);
|
|||
|
GoAccess.AppCharts[panel] = chart;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// Reload the given chart data
|
|||
|
reloadChart: function (chart, panel) {
|
|||
|
var subItems = GoAccess.Tables.getSubItemsData(panel);
|
|||
|
var data = (subItems.length ? subItems : GoAccess.getPanelData(panel).data).slice(0);
|
|||
|
|
|||
|
d3.select("#chart-" + panel)
|
|||
|
.datum(this.processChartData(this.getPanelData(panel, data)))
|
|||
|
.call(chart.width($("#chart-" + panel).offsetWidth));
|
|||
|
},
|
|||
|
|
|||
|
reloadCharts: function () {
|
|||
|
this.iter(function (chart, panel) {
|
|||
|
this.reloadChart(chart, panel);
|
|||
|
}.bind(this));
|
|||
|
GoAccess.AppState.updated = false;
|
|||
|
},
|
|||
|
|
|||
|
// Only redraw charts with current data
|
|||
|
redrawCharts: function () {
|
|||
|
this.iter(function (chart, panel) {
|
|||
|
d3.select("#chart-" + panel).call(chart.width($("#chart-" + panel).offsetWidth));
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
initialize: function () {
|
|||
|
this.renderCharts(GoAccess.getPanelUI());
|
|||
|
|
|||
|
// reload on scroll & redraw on resize
|
|||
|
d3.select(window).on('scroll.charts', debounce(function () {
|
|||
|
this.reloadCharts();
|
|||
|
}, 250, false).bind(this)).on('resize.charts', function () {
|
|||
|
this.redrawCharts();
|
|||
|
}.bind(this));
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
// RENDER TABLES
|
|||
|
GoAccess.Tables = {
|
|||
|
chartData: {}, // holds all panel sub items data that feeds the chart
|
|||
|
|
|||
|
events: function () {
|
|||
|
$$('.panel-next', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
var panel = e.currentTarget.getAttribute('data-panel');
|
|||
|
this.renderTable(panel, this.nextPage(panel))
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('.panel-prev', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
var panel = e.currentTarget.getAttribute('data-panel');
|
|||
|
this.renderTable(panel, this.prevPage(panel))
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('.expandable>td', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
if (!window.getSelection().toString())
|
|||
|
this.toggleRow(e.currentTarget);
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('.row-expandable.clickable', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
this.toggleRow(e.currentTarget);
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
$$('.sortable', function (item) {
|
|||
|
item.onclick = function (e) {
|
|||
|
this.sortColumn(e.currentTarget);
|
|||
|
}.bind(this);
|
|||
|
}.bind(this));
|
|||
|
},
|
|||
|
|
|||
|
toggleColumn: function (targ) {
|
|||
|
var panel = targ.getAttribute('data-panel');
|
|||
|
var metric = targ.getAttribute('data-metric');
|
|||
|
|
|||
|
var columns = (GoAccess.getPrefs(panel) || {}).columns || {};
|
|||
|
if (metric in columns)
|
|||
|
delete columns[metric];
|
|||
|
else
|
|||
|
GoAccess.Util.setProp(columns, metric + '.hide', true);
|
|||
|
|
|||
|
GoAccess.Util.setProp(GoAccess.AppPrefs, panel + '.columns', columns);
|
|||
|
GoAccess.setPrefs();
|
|||
|
|
|||
|
GoAccess.Tables.renderThead(panel, GoAccess.getPanelUI(panel));
|
|||
|
GoAccess.Tables.renderFullTable(panel);
|
|||
|
},
|
|||
|
|
|||
|
sortColumn: function (ele) {
|
|||
|
var field = ele.getAttribute('data-key');
|
|||
|
var order = ele.getAttribute('data-order');
|
|||
|
var panel = ele.parentElement.parentElement.parentElement.getAttribute('data-panel');
|
|||
|
|
|||
|
order = order ? 'asc' == order ? 'desc' : 'asc' : 'asc';
|
|||
|
GoAccess.App.sortData(panel, field, order)
|
|||
|
GoAccess.Util.setProp(GoAccess.AppState, panel + '.sort', {
|
|||
|
'field': field,
|
|||
|
'order': order,
|
|||
|
});
|
|||
|
this.renderThead(panel, GoAccess.getPanelUI(panel));
|
|||
|
this.renderTable(panel, this.getCurPage(panel));
|
|||
|
|
|||
|
GoAccess.Charts.reloadChart(GoAccess.AppCharts[panel], panel);
|
|||
|
},
|
|||
|
|
|||
|
getDataByKey: function (panel, key) {
|
|||
|
var data = GoAccess.getPanelData(panel).data;
|
|||
|
for (var i = 0, n = data.length; i < n; ++i) {
|
|||
|
if (GoAccess.Util.hashCode(data[i].data) == key)
|
|||
|
return data[i];
|
|||
|
}
|
|||
|
return null;
|
|||
|
},
|
|||
|
|
|||
|
getSubItemsData: function (panel) {
|
|||
|
var out = [], items = this.chartData[panel];
|
|||
|
for (var x in items) {
|
|||
|
if (!items.hasOwnProperty(x))
|
|||
|
continue;
|
|||
|
out = out.concat(items[x]);
|
|||
|
}
|
|||
|
return out;
|
|||
|
},
|
|||
|
|
|||
|
addChartData: function (panel, key) {
|
|||
|
var data = this.getDataByKey(panel, key);
|
|||
|
var path = panel + '.' + key;
|
|||
|
|
|||
|
if (!data || !data.items)
|
|||
|
return [];
|
|||
|
GoAccess.Util.setProp(this.chartData, path, data.items);
|
|||
|
|
|||
|
return this.getSubItemsData(panel);
|
|||
|
},
|
|||
|
|
|||
|
removeChartData: function (panel, key) {
|
|||
|
if (GoAccess.Util.getProp(this.chartData, panel + '.' + key))
|
|||
|
delete this.chartData[panel][key];
|
|||
|
|
|||
|
if (!this.chartData[panel] || Object.keys(this.chartData[panel]).length == 0)
|
|||
|
return GoAccess.getPanelData(panel).data;
|
|||
|
|
|||
|
return this.getSubItemsData(panel);
|
|||
|
},
|
|||
|
|
|||
|
isExpanded: function (panel, key) {
|
|||
|
var path = panel + '.expanded.' + key;
|
|||
|
return GoAccess.Util.getProp(GoAccess.AppState, path);
|
|||
|
},
|
|||
|
|
|||
|
toggleExpanded: function (panel, key) {
|
|||
|
var path = panel + '.expanded.' + key, ret = true;
|
|||
|
|
|||
|
if (this.isExpanded(panel, key))
|
|||
|
delete GoAccess.AppState[panel]['expanded'][key];
|
|||
|
else
|
|||
|
GoAccess.Util.setProp(GoAccess.AppState, path, true), ret = false;
|
|||
|
|
|||
|
return ret;
|
|||
|
},
|
|||
|
|
|||
|
// Toggle children rows
|
|||
|
toggleRow: function (ele) {
|
|||
|
var hide = false, data = [];
|
|||
|
var row = ele.parentNode;
|
|||
|
var panel = row.getAttribute('data-panel'), key = row.getAttribute('data-key');
|
|||
|
var plotUI = GoAccess.AppCharts[panel].opts();
|
|||
|
|
|||
|
hide = this.toggleExpanded(panel, key);
|
|||
|
this.renderTable(panel, this.getCurPage(panel));
|
|||
|
if (!plotUI.redrawOnExpand)
|
|||
|
return;
|
|||
|
|
|||
|
if (!hide)
|
|||
|
data = GoAccess.Charts.processChartData(this.addChartData(panel, key));
|
|||
|
else
|
|||
|
data = GoAccess.Charts.processChartData(this.removeChartData(panel, key));
|
|||
|
GoAccess.Charts.drawPlot(panel, plotUI, data);
|
|||
|
},
|
|||
|
|
|||
|
// Get current panel page
|
|||
|
getCurPage: function (panel) {
|
|||
|
return GoAccess.Util.getProp(GoAccess.AppState, panel + '.curPage') || 0;
|
|||
|
},
|
|||
|
|
|||
|
// Page offset.
|
|||
|
// e.g., Return Value: 11, curPage: 2
|
|||
|
pageOffSet: function (panel) {
|
|||
|
return ((this.getCurPage(panel) - 1) * GoAccess.getPrefs().perPage);
|
|||
|
},
|
|||
|
|
|||
|
// Get total number of pages given the number of items on array
|
|||
|
getTotalPages: function (dataItems) {
|
|||
|
return Math.ceil(dataItems.length / GoAccess.getPrefs().perPage);
|
|||
|
},
|
|||
|
|
|||
|
// Get a shallow copy of a portion of the given data array and the
|
|||
|
// current page.
|
|||
|
getPage: function (panel, dataItems, page) {
|
|||
|
var totalPages = this.getTotalPages(dataItems);
|
|||
|
if (page < 1)
|
|||
|
page = 1;
|
|||
|
if (page > totalPages)
|
|||
|
page = totalPages;
|
|||
|
|
|||
|
GoAccess.Util.setProp(GoAccess.AppState, panel + '.curPage', page);
|
|||
|
var start = this.pageOffSet(panel);
|
|||
|
var end = start + GoAccess.getPrefs().perPage;
|
|||
|
|
|||
|
return dataItems.slice(start, end);
|
|||
|
},
|
|||
|
|
|||
|
// Get previous page
|
|||
|
prevPage: function (panel) {
|
|||
|
return this.getCurPage(panel) - 1;
|
|||
|
},
|
|||
|
|
|||
|
// Get next page
|
|||
|
nextPage: function (panel) {
|
|||
|
return this.getCurPage(panel) + 1;
|
|||
|
},
|
|||
|
|
|||
|
getMetaValue: function (ui, value) {
|
|||
|
if ('meta' in ui)
|
|||
|
return value[ui.meta];
|
|||
|
return null;
|
|||
|
},
|
|||
|
|
|||
|
getMetaCell: function (ui, value) {
|
|||
|
var val = this.getMetaValue(ui, value);
|
|||
|
var max = (value || {}).max;
|
|||
|
var min = (value || {}).min;
|
|||
|
|
|||
|
// use metaType if exist else fallback to dataType
|
|||
|
var vtype = ui.metaType || ui.dataType;
|
|||
|
var className = ui.className || '';
|
|||
|
className += ui.dataType != 'string' ? 'text-right' : '';
|
|||
|
return {
|
|||
|
'className': className,
|
|||
|
'max' : max != undefined ? GoAccess.Util.fmtValue(max, vtype) : null,
|
|||
|
'min' : min != undefined ? GoAccess.Util.fmtValue(min, vtype) : null,
|
|||
|
'value' : val != undefined ? GoAccess.Util.fmtValue(val, vtype) : null,
|
|||
|
'title' : ui.meta,
|
|||
|
'label' : ui.metaLabel || null,
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
hideColumn: function (panel, col) {
|
|||
|
var columns = (GoAccess.getPrefs(panel) || {}).columns || {};
|
|||
|
return ((col in columns) && columns[col]['hide']);
|
|||
|
},
|
|||
|
|
|||
|
showTables: function () {
|
|||
|
return ('showTables' in GoAccess.getPrefs()) ? GoAccess.getPrefs().showTables : true;
|
|||
|
},
|
|||
|
|
|||
|
autoHideTables: function () {
|
|||
|
return ('autoHideTables' in GoAccess.getPrefs()) ? GoAccess.getPrefs().autoHideTables : true;
|
|||
|
},
|
|||
|
|
|||
|
hasTables: function (ui) {
|
|||
|
ui['table'] = this.showTables();
|
|||
|
ui['autoHideTables'] = this.autoHideTables();
|
|||
|
},
|
|||
|
|
|||
|
renderMetaRow: function (panel, ui) {
|
|||
|
// find the table to set
|
|||
|
var table = $('.table-' + panel + ' tbody.tbody-meta');
|
|||
|
if (!table)
|
|||
|
return;
|
|||
|
|
|||
|
var cells = [], uiItems = ui.items;
|
|||
|
var data = GoAccess.getPanelData(panel).metadata;
|
|||
|
for (var i = 0; i < uiItems.length; ++i) {
|
|||
|
var item = uiItems[i];
|
|||
|
if (this.hideColumn(panel, item.key))
|
|||
|
continue;
|
|||
|
var value = data[item.key];
|
|||
|
cells.push(this.getMetaCell(item, value))
|
|||
|
}
|
|||
|
|
|||
|
table.innerHTML = GoAccess.AppTpls.Tables.meta.render({
|
|||
|
row: [{
|
|||
|
'hasSubItems': ui.hasSubItems,
|
|||
|
'cells': cells
|
|||
|
}]
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
// Iterate over user interface definition properties
|
|||
|
iterUIItems: function (panel, uiItems, dataItems, callback) {
|
|||
|
var out = [];
|
|||
|
for (var i = 0; i < uiItems.length; ++i) {
|
|||
|
var uiItem = uiItems[i];
|
|||
|
if (this.hideColumn(panel, uiItem.key))
|
|||
|
continue;
|
|||
|
// Data for the current user interface property.
|
|||
|
// e.g., dataItem = Object {count: 13949, percent: 5.63}
|
|||
|
var dataItem = dataItems[uiItem.key];
|
|||
|
// Apply the callback and push return data to output array
|
|||
|
if (callback && typeof callback == 'function') {
|
|||
|
var ret = callback.call(this, panel, uiItem, dataItem);
|
|||
|
if (ret) out.push(ret);
|
|||
|
}
|
|||
|
}
|
|||
|
return out;
|
|||
|
},
|
|||
|
|
|||
|
// Return an object that can be consumed by the table template given a user
|
|||
|
// interface definition and a cell value object.
|
|||
|
// e.g., value = Object {count: 14351, percent: 5.79}
|
|||
|
getObjectCell: function (panel, ui, value) {
|
|||
|
var className = ui.className || '';
|
|||
|
className += ui.dataType != 'string' ? 'text-right' : '';
|
|||
|
return {
|
|||
|
'className': className,
|
|||
|
'percent': GoAccess.Util.getPercent(value),
|
|||
|
'value': GoAccess.Util.fmtValue(GoAccess.Util.getCount(value), ui.dataType)
|
|||
|
};
|
|||
|
},
|
|||
|
|
|||
|
// Given a data item object, set all the row cells and return a
|
|||
|
// table row that the template can consume.
|
|||
|
renderRow: function (panel, callback, ui, dataItem, idx, subItem, parentId, expanded) {
|
|||
|
var shadeParent = ((!subItem && idx % 2 != 0) ? 'shaded' : '');
|
|||
|
var shadeChild = ((parentId % 2 != 0) ? 'shaded' : '');
|
|||
|
return {
|
|||
|
'panel' : panel,
|
|||
|
'idx' : !subItem && (String((idx + 1) + this.pageOffSet(panel))),
|
|||
|
'key' : !subItem ? GoAccess.Util.hashCode(dataItem.data) : '',
|
|||
|
'expanded' : !subItem && expanded,
|
|||
|
'parentId' : subItem ? String(parentId) : '',
|
|||
|
'className' : subItem ? 'child ' + shadeChild : 'parent ' + shadeParent,
|
|||
|
'hasSubItems' : ui.hasSubItems,
|
|||
|
'items' : dataItem.items ? dataItem.items.length : 0,
|
|||
|
'cells' : callback.call(this),
|
|||
|
};
|
|||
|
},
|
|||
|
|
|||
|
renderRows: function (rows, panel, ui, dataItems, subItem, parentId) {
|
|||
|
subItem = subItem || false;
|
|||
|
// no data rows
|
|||
|
if (dataItems.length == 0 && ui.items.length) {
|
|||
|
rows.push({
|
|||
|
cells: [{
|
|||
|
className: 'text-center',
|
|||
|
colspan: ui.items.length + 1,
|
|||
|
value: 'No data on this panel.'
|
|||
|
}]
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
// Iterate over all data items for the given panel and
|
|||
|
// generate a table row per date item.
|
|||
|
var cellcb = null;
|
|||
|
for (var i = 0; i < dataItems.length; ++i) {
|
|||
|
var dataItem = dataItems[i], data = null, expanded = false;
|
|||
|
switch(typeof dataItem) {
|
|||
|
case 'string':
|
|||
|
data = dataItem;
|
|||
|
cellcb = function () {
|
|||
|
return {
|
|||
|
'colspan': ui.items.length,
|
|||
|
'value': data
|
|||
|
}
|
|||
|
};
|
|||
|
break;
|
|||
|
default:
|
|||
|
data = dataItem.data;
|
|||
|
cellcb = this.iterUIItems.bind(this, panel, ui.items, dataItem, this.getObjectCell.bind(this));
|
|||
|
}
|
|||
|
|
|||
|
expanded = this.isExpanded(panel, GoAccess.Util.hashCode(data));
|
|||
|
rows.push(this.renderRow(panel, cellcb, ui, dataItem, i, subItem, parentId, expanded));
|
|||
|
if (dataItem.items && dataItem.items.length && expanded) {
|
|||
|
this.renderRows(rows, panel, ui, dataItem.items, true, i, expanded);
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// Entry point to render all data rows into the table
|
|||
|
renderDataRows: function (panel, ui, dataItems, page) {
|
|||
|
// find the table to set
|
|||
|
var table = $('.table-' + panel + ' tbody.tbody-data');
|
|||
|
if (!table)
|
|||
|
return;
|
|||
|
|
|||
|
var dataItems = this.getPage(panel, dataItems, page);
|
|||
|
var rows = [];
|
|||
|
this.renderRows(rows, panel, ui, dataItems);
|
|||
|
if (rows.length == 0)
|
|||
|
return;
|
|||
|
|
|||
|
table.innerHTML = GoAccess.AppTpls.Tables.data.render({
|
|||
|
rows: rows
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
togglePagination: function (panel, page, dataItems) {
|
|||
|
GoAccess.Panels.enablePagination(panel);
|
|||
|
// Diable pagination next button if last page is reached
|
|||
|
if (page >= this.getTotalPages(dataItems))
|
|||
|
GoAccess.Panels.disableNext(panel);
|
|||
|
if (page <= 1)
|
|||
|
GoAccess.Panels.disablePrev(panel);
|
|||
|
},
|
|||
|
|
|||
|
renderTable: function (panel, page) {
|
|||
|
var dataItems = GoAccess.getPanelData(panel).data;
|
|||
|
var ui = GoAccess.getPanelUI(panel);
|
|||
|
|
|||
|
this.togglePagination(panel, page, dataItems);
|
|||
|
// Render data rows
|
|||
|
this.renderDataRows(panel, ui, dataItems, page);
|
|||
|
this.events();
|
|||
|
},
|
|||
|
|
|||
|
renderFullTable: function (panel) {
|
|||
|
var ui = GoAccess.getPanelUI(panel), page = 0;
|
|||
|
// panel's data
|
|||
|
var data = GoAccess.getPanelData(panel);
|
|||
|
// render meta data
|
|||
|
if (data.hasOwnProperty('metadata'))
|
|||
|
this.renderMetaRow(panel, ui);
|
|||
|
|
|||
|
// render actual data
|
|||
|
if (data.hasOwnProperty('data')) {
|
|||
|
page = this.getCurPage(panel);
|
|||
|
this.togglePagination(panel, page, data.data);
|
|||
|
this.renderDataRows(panel, ui, data.data, page);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// Iterate over all panels and determine which ones should contain
|
|||
|
// a data table.
|
|||
|
renderTables: function (force) {
|
|||
|
var ui = GoAccess.getPanelUI();
|
|||
|
for (var panel in ui) {
|
|||
|
if (GoAccess.Util.isPanelValid(panel) || !this.showTables())
|
|||
|
continue;
|
|||
|
if (force || GoAccess.Util.isWithinViewPort($('#panel-' + panel)))
|
|||
|
this.renderFullTable(panel);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// Given a UI panel definition, make a copy of it and assign the sort
|
|||
|
// fields to the template object to render
|
|||
|
sort2Tpl: function (panel, ui) {
|
|||
|
var uiClone = JSON.parse(JSON.stringify(ui)), out = [];
|
|||
|
var sort = GoAccess.Util.getProp(GoAccess.AppState, panel + '.sort');
|
|||
|
|
|||
|
for (var i = 0, len = uiClone.items.length; i < len; ++i) {
|
|||
|
var item = uiClone.items[i];
|
|||
|
if (this.hideColumn(panel, item.key))
|
|||
|
continue;
|
|||
|
|
|||
|
item['sort'] = false;
|
|||
|
if (item.key == sort.field && sort.order) {
|
|||
|
item['sort'] = true;
|
|||
|
item[sort.order.toLowerCase()] = true;
|
|||
|
}
|
|||
|
out.push(item);
|
|||
|
}
|
|||
|
uiClone.items = out;
|
|||
|
|
|||
|
return uiClone;
|
|||
|
},
|
|||
|
|
|||
|
renderThead: function (panel, ui) {
|
|||
|
var $thead = $('.table-' + panel + '>thead'), $colgroup = $('.table-' + panel + '>colgroup');
|
|||
|
if ($thead && $colgroup && this.showTables()) {
|
|||
|
ui = this.sort2Tpl(panel, ui);
|
|||
|
|
|||
|
$thead.innerHTML = GoAccess.AppTpls.Tables.head.render(ui);
|
|||
|
$colgroup.innerHTML = GoAccess.AppTpls.Tables.colgroup.render(ui);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
reloadTables: function () {
|
|||
|
this.renderTables(false);
|
|||
|
this.events();
|
|||
|
},
|
|||
|
|
|||
|
initialize: function () {
|
|||
|
this.renderTables(true);
|
|||
|
this.events();
|
|||
|
|
|||
|
// redraw on scroll
|
|||
|
d3.select(window).on('scroll.tables', debounce(function () {
|
|||
|
this.reloadTables();
|
|||
|
}, 250, false).bind(this));
|
|||
|
},
|
|||
|
};
|
|||
|
|
|||
|
// Main App
|
|||
|
GoAccess.App = {
|
|||
|
tpl: function (tpl) {
|
|||
|
return Hogan.compile(tpl);
|
|||
|
},
|
|||
|
|
|||
|
setTpls: function () {
|
|||
|
GoAccess.AppTpls = {
|
|||
|
'Nav': {
|
|||
|
'wrap': this.tpl($('#tpl-nav-wrap').innerHTML),
|
|||
|
'menu': this.tpl($('#tpl-nav-menu').innerHTML),
|
|||
|
'opts': this.tpl($('#tpl-nav-opts').innerHTML),
|
|||
|
},
|
|||
|
'Panels': {
|
|||
|
'wrap': this.tpl($('#tpl-panel').innerHTML),
|
|||
|
'opts': this.tpl($('#tpl-panel-opts').innerHTML),
|
|||
|
},
|
|||
|
'General': {
|
|||
|
'wrap': this.tpl($('#tpl-general').innerHTML),
|
|||
|
'items': this.tpl($('#tpl-general-items').innerHTML),
|
|||
|
},
|
|||
|
'Tables': {
|
|||
|
'colgroup': this.tpl($('#tpl-table-colgroup').innerHTML),
|
|||
|
'head': this.tpl($('#tpl-table-thead').innerHTML),
|
|||
|
'meta': this.tpl($('#tpl-table-row-meta').innerHTML),
|
|||
|
'data': this.tpl($('#tpl-table-row').innerHTML),
|
|||
|
},
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
sortField: function (o, field) {
|
|||
|
var f = o[field];
|
|||
|
if (GoAccess.Util.isObject(f) && (f !== null))
|
|||
|
f = o[field].count;
|
|||
|
return f;
|
|||
|
},
|
|||
|
|
|||
|
sortData: function (panel, field, order) {
|
|||
|
// panel's data
|
|||
|
var panelData = GoAccess.getPanelData(panel).data;
|
|||
|
panelData.sort(function (a, b) {
|
|||
|
var a = this.sortField(a, field);
|
|||
|
var b = this.sortField(b, field);
|
|||
|
|
|||
|
if (typeof a === 'string' && typeof b === 'string')
|
|||
|
return 'asc' == order ? a.localeCompare(b) : b.localeCompare(a);
|
|||
|
return 'asc' == order ? a - b : b - a;
|
|||
|
}.bind(this));
|
|||
|
},
|
|||
|
|
|||
|
setInitSort: function () {
|
|||
|
var ui = GoAccess.getPanelUI();
|
|||
|
for (var panel in ui) {
|
|||
|
if (GoAccess.Util.isPanelValid(panel))
|
|||
|
continue;
|
|||
|
GoAccess.Util.setProp(GoAccess.AppState, panel + '.sort', ui[panel].sort);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// Verify if we need to sort panels upon data re-entry
|
|||
|
verifySort: function () {
|
|||
|
var ui = GoAccess.getPanelUI();
|
|||
|
for (var panel in ui) {
|
|||
|
if (GoAccess.Util.isPanelValid(panel))
|
|||
|
continue;
|
|||
|
var sort = GoAccess.Util.getProp(GoAccess.AppState, panel + '.sort');
|
|||
|
// do not sort panels if they still hold the same sort properties
|
|||
|
if (JSON.stringify(sort) === JSON.stringify(ui[panel].sort))
|
|||
|
continue;
|
|||
|
this.sortData(panel, sort.field, sort.order);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
initDom: function () {
|
|||
|
$('nav').classList.remove('hide');
|
|||
|
$('.container').classList.remove('hide');
|
|||
|
$('.spinner').classList.add('hide');
|
|||
|
|
|||
|
if (GoAccess.AppPrefs['layout'] == 'horizontal') {
|
|||
|
$('.container').classList.add('container-fluid');
|
|||
|
$('.container-fluid').classList.remove('container');
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
renderData: function () {
|
|||
|
this.verifySort();
|
|||
|
GoAccess.OverallStats.initialize();
|
|||
|
|
|||
|
// do not rerender tables/charts if data hasn't changed
|
|||
|
if (!GoAccess.AppState.updated)
|
|||
|
return;
|
|||
|
|
|||
|
GoAccess.Charts.reloadCharts();
|
|||
|
GoAccess.Tables.reloadTables();
|
|||
|
},
|
|||
|
|
|||
|
initialize: function () {
|
|||
|
this.setInitSort();
|
|||
|
this.setTpls();
|
|||
|
GoAccess.Nav.initialize();
|
|||
|
this.initDom();
|
|||
|
GoAccess.OverallStats.initialize();
|
|||
|
GoAccess.Panels.initialize();
|
|||
|
GoAccess.Charts.initialize();
|
|||
|
GoAccess.Tables.initialize();
|
|||
|
},
|
|||
|
};
|
|||
|
|
|||
|
// Init app
|
|||
|
window.onload = function () {
|
|||
|
GoAccess.initialize({
|
|||
|
'uiData': window.user_interface,
|
|||
|
'panelData': window.json_data,
|
|||
|
'wsConnection': window.connection || null,
|
|||
|
'prefs': window.html_prefs || {},
|
|||
|
});
|
|||
|
GoAccess.App.initialize();
|
|||
|
};
|
|||
|
</script><script>/**
|
|||
|
* ______ ___
|
|||
|
* / ____/___ / | _____________ __________
|
|||
|
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
|||
|
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
|||
|
* \____/\____/_/ |_\___/\___/\___/____/____/
|
|||
|
*
|
|||
|
* The MIT License (MIT)
|
|||
|
* Copyright (c) 2009-2017 Gerardo Orellana <hello @ goaccess.io>
|
|||
|
*/
|
|||
|
|
|||
|
'use strict';
|
|||
|
|
|||
|
// This is faster than calculating the exact length of each label.
|
|||
|
// e.g., getComputedTextLength(), slice()...
|
|||
|
function truncate(text, width) {
|
|||
|
text.each(function () {
|
|||
|
var parent = this.parentNode, $d3parent = d3.select(parent);
|
|||
|
var gw = $d3parent.node().getBBox();
|
|||
|
var x = (Math.min(gw.width, width) / 2) * -1;
|
|||
|
// adjust wrapper <svg> width
|
|||
|
if ('svg' == parent.nodeName)
|
|||
|
$d3parent.attr('width', width).attr('x', x);
|
|||
|
// wrap <text> within an svg
|
|||
|
else {
|
|||
|
$d3parent.insert('svg', function () {
|
|||
|
return this;
|
|||
|
}.bind(this))
|
|||
|
.attr('class', 'wrap-text')
|
|||
|
.attr('width', width)
|
|||
|
.attr('x', x)
|
|||
|
.append(function () {
|
|||
|
return this;
|
|||
|
}.bind(this));
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
function AreaChart(dualYaxis) {
|
|||
|
var opts = {};
|
|||
|
var margin = {
|
|||
|
top : 20,
|
|||
|
right : 50,
|
|||
|
bottom : 40,
|
|||
|
left : 50
|
|||
|
},
|
|||
|
height = 170,
|
|||
|
nTicks = 10,
|
|||
|
padding = 10,
|
|||
|
width = 760;
|
|||
|
var labels = { x: 'Unnamed', y0: 'Unnamed', y1: 'Unnamed' };
|
|||
|
var format = { x: null, y0: null, y1: null};
|
|||
|
|
|||
|
var xValue = function (d) {
|
|||
|
return d[0];
|
|||
|
},
|
|||
|
yValue0 = function (d) {
|
|||
|
return d[1];
|
|||
|
},
|
|||
|
yValue1 = function (d) {
|
|||
|
return d[2];
|
|||
|
};
|
|||
|
|
|||
|
var xScale = d3.scale.ordinal();
|
|||
|
var yScale0 = d3.scale.linear().nice();
|
|||
|
var yScale1 = d3.scale.linear().nice();
|
|||
|
|
|||
|
var xAxis = d3.svg.axis()
|
|||
|
.scale(xScale)
|
|||
|
.orient('bottom')
|
|||
|
.tickFormat(function (d) {
|
|||
|
if (format.x)
|
|||
|
return GoAccess.Util.fmtValue(d, format.x);
|
|||
|
return d;
|
|||
|
});
|
|||
|
|
|||
|
var yAxis0 = d3.svg.axis()
|
|||
|
.scale(yScale0)
|
|||
|
.orient('left')
|
|||
|
.tickFormat(function (d) {
|
|||
|
if (format.y0)
|
|||
|
return GoAccess.Util.fmtValue(d, format.y0);
|
|||
|
return d3.format('.2s')(d);
|
|||
|
});
|
|||
|
|
|||
|
var yAxis1 = d3.svg.axis()
|
|||
|
.scale(yScale1)
|
|||
|
.orient('right')
|
|||
|
.tickFormat(function (d) {
|
|||
|
if (format.y1)
|
|||
|
return GoAccess.Util.fmtValue(d, format.y1);
|
|||
|
return d3.format('.2s')(d);
|
|||
|
});
|
|||
|
|
|||
|
var xGrid = d3.svg.axis()
|
|||
|
.scale(xScale)
|
|||
|
.orient('bottom');
|
|||
|
|
|||
|
var yGrid = d3.svg.axis()
|
|||
|
.scale(yScale0)
|
|||
|
.orient('left');
|
|||
|
|
|||
|
var area0 = d3.svg.area()
|
|||
|
.interpolate('cardinal')
|
|||
|
.x(X)
|
|||
|
.y(Y0);
|
|||
|
var area1 = d3.svg.area()
|
|||
|
.interpolate('cardinal')
|
|||
|
.x(X)
|
|||
|
.y(Y1);
|
|||
|
|
|||
|
var line0 = d3.svg.line()
|
|||
|
.interpolate('cardinal')
|
|||
|
.x(X)
|
|||
|
.y(Y0);
|
|||
|
var line1 = d3.svg.line()
|
|||
|
.interpolate('cardinal')
|
|||
|
.x(X)
|
|||
|
.y(Y1);
|
|||
|
|
|||
|
// The x-accessor for the path generator; xScale ∘ xValue.
|
|||
|
function X(d) {
|
|||
|
return xScale(d[0]);
|
|||
|
}
|
|||
|
|
|||
|
// The x-accessor for the path generator; yScale0 yValue0.
|
|||
|
function Y0(d) {
|
|||
|
return yScale0(d[1]);
|
|||
|
}
|
|||
|
|
|||
|
// The x-accessor for the path generator; yScale0 yValue0.
|
|||
|
function Y1(d) {
|
|||
|
return yScale1(d[2]);
|
|||
|
}
|
|||
|
|
|||
|
function innerW() {
|
|||
|
return width - margin.left - margin.right;
|
|||
|
}
|
|||
|
|
|||
|
function innerH() {
|
|||
|
return height - margin.top - margin.bottom;
|
|||
|
}
|
|||
|
|
|||
|
function getXTicks(data) {
|
|||
|
if (data.length < nTicks)
|
|||
|
return xScale.domain();
|
|||
|
|
|||
|
return d3.range(0, data.length, Math.ceil(data.length / nTicks)).map(function (d) {
|
|||
|
return xScale.domain()[d];
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
function getYTicks(scale) {
|
|||
|
var domain = scale.domain();
|
|||
|
return d3.range(domain[0], domain[1], Math.ceil(domain[1] / nTicks));
|
|||
|
}
|
|||
|
|
|||
|
// Convert data to standard representation greedily;
|
|||
|
// this is needed for nondeterministic accessors.
|
|||
|
function mapData(data) {
|
|||
|
var _datum = function (d, i) {
|
|||
|
var datum = [xValue.call(data, d, i), yValue0.call(data, d, i)];
|
|||
|
dualYaxis && datum.push(yValue1.call(data, d, i));
|
|||
|
return datum;
|
|||
|
};
|
|||
|
return data.map(function (d, i) {
|
|||
|
return _datum(d, i);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
function updateScales(data) {
|
|||
|
// Update the x-scale.
|
|||
|
xScale.domain(data.map(function (d) {
|
|||
|
return d[0];
|
|||
|
}))
|
|||
|
.rangePoints([0, innerW()], 1);
|
|||
|
|
|||
|
// Update the y-scale.
|
|||
|
yScale0.domain([0, d3.max(data, function (d) {
|
|||
|
return d[1];
|
|||
|
})])
|
|||
|
.range([innerH(), 0]);
|
|||
|
|
|||
|
// Update the y-scale.
|
|||
|
dualYaxis && yScale1.domain([0, d3.max(data, function (d) {
|
|||
|
return d[2];
|
|||
|
})])
|
|||
|
.range([innerH(), 0]);
|
|||
|
}
|
|||
|
|
|||
|
function toggleOpacity(ele, op) {
|
|||
|
d3.select(ele.parentNode).selectAll('.' + (ele.getAttribute('data-yaxis') == 'y0' ? 'y1' : 'y0')).attr('style', op);
|
|||
|
}
|
|||
|
|
|||
|
function setLegendLabels(svg) {
|
|||
|
// Legend Color
|
|||
|
var rect = svg.selectAll('rect.legend.y0').data([null]);
|
|||
|
rect.enter().append('rect')
|
|||
|
.attr('class', 'legend y0')
|
|||
|
.attr('data-yaxis', 'y0')
|
|||
|
.on('mousemove', function (d, i) { toggleOpacity(this, 'opacity:0.1'); })
|
|||
|
.on('mouseleave', function (d, i) { toggleOpacity(this, null); })
|
|||
|
.attr('y', (height - 15));
|
|||
|
rect
|
|||
|
.attr('x', (width / 2) - 100);
|
|||
|
|
|||
|
// Legend Labels
|
|||
|
var text = svg.selectAll('text.legend.y0').data([null]);
|
|||
|
text.enter().append('text')
|
|||
|
.attr('class', 'legend y0')
|
|||
|
.attr('data-yaxis', 'y0')
|
|||
|
.on('mousemove', function (d, i) { toggleOpacity(this, 'opacity:0.1'); })
|
|||
|
.on('mouseleave', function (d, i) { toggleOpacity(this, null); })
|
|||
|
.attr('y', (height - 6));
|
|||
|
text
|
|||
|
.attr('x', (width / 2) - 85)
|
|||
|
.text(labels.y0);
|
|||
|
|
|||
|
if (!dualYaxis)
|
|||
|
return;
|
|||
|
|
|||
|
// Legend Labels
|
|||
|
rect = svg.selectAll('rect.legend.y1').data([null]);
|
|||
|
rect.enter().append('rect')
|
|||
|
.attr('class', 'legend y1')
|
|||
|
.attr('data-yaxis', 'y1')
|
|||
|
.on('mousemove', function (d, i) { toggleOpacity(this, 'opacity:0.1'); })
|
|||
|
.on('mouseleave', function (d, i) { toggleOpacity(this, null); })
|
|||
|
.attr('y', (height - 15));
|
|||
|
rect
|
|||
|
.attr('x', (width / 2));
|
|||
|
|
|||
|
// Legend Labels
|
|||
|
text = svg.selectAll('text.legend.y1').data([null]);
|
|||
|
text.enter().append('text')
|
|||
|
.attr('class', 'legend y1')
|
|||
|
.attr('data-yaxis', 'y1')
|
|||
|
.on('mousemove', function (d, i) { toggleOpacity(this, 'opacity:0.1'); })
|
|||
|
.on('mouseleave', function (d, i) { toggleOpacity(this, null); })
|
|||
|
.attr('y', (height - 6));
|
|||
|
text
|
|||
|
.attr('x', (width / 2) + 15)
|
|||
|
.text(labels.y1);
|
|||
|
}
|
|||
|
|
|||
|
function setAxisLabels(svg) {
|
|||
|
// Labels
|
|||
|
svg.selectAll('text.axis-label.y0').data([null])
|
|||
|
.enter().append('text')
|
|||
|
.attr('class', 'axis-label y0')
|
|||
|
.attr('y', 10)
|
|||
|
.attr('x', 50)
|
|||
|
.text(labels.y0);
|
|||
|
|
|||
|
if (!dualYaxis)
|
|||
|
return;
|
|||
|
|
|||
|
// Labels
|
|||
|
var tEnter = svg.selectAll('text.axis-label.y1').data([null]);
|
|||
|
tEnter.enter().append('text')
|
|||
|
.attr('class', 'axis-label y1')
|
|||
|
.attr('y', 10)
|
|||
|
.text(labels.y1);
|
|||
|
dualYaxis && tEnter
|
|||
|
.attr('x', width - 25)
|
|||
|
}
|
|||
|
|
|||
|
function createSkeleton(svg) {
|
|||
|
// Otherwise, create the skeletal chart.
|
|||
|
var gEnter = svg.enter().append('svg').append('g');
|
|||
|
|
|||
|
// Lines
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'line line0 y0');
|
|||
|
dualYaxis && gEnter.append('g')
|
|||
|
.attr('class', 'line line1 y1');
|
|||
|
|
|||
|
// Areas
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'area area0 y0');
|
|||
|
dualYaxis && gEnter.append('g')
|
|||
|
.attr('class', 'area area1 y1');
|
|||
|
|
|||
|
// Points
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'points y0');
|
|||
|
dualYaxis && gEnter.append('g')
|
|||
|
.attr('class', 'points y1');
|
|||
|
|
|||
|
// Grid
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'x grid');
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'y grid');
|
|||
|
|
|||
|
// Axis
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'x axis');
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'y0 axis');
|
|||
|
dualYaxis && gEnter.append('g')
|
|||
|
.attr('class', 'y1 axis');
|
|||
|
|
|||
|
// Rects
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'rects');
|
|||
|
|
|||
|
setAxisLabels(svg);
|
|||
|
setLegendLabels(svg);
|
|||
|
|
|||
|
// Mouseover line
|
|||
|
gEnter.append('line')
|
|||
|
.attr('y2', innerH())
|
|||
|
.attr('y1', 0)
|
|||
|
.attr('class', 'indicator');
|
|||
|
}
|
|||
|
|
|||
|
function pathLen(d) {
|
|||
|
return d.node().getTotalLength();
|
|||
|
}
|
|||
|
|
|||
|
function addLine(g, data, line, cName) {
|
|||
|
// Update the line path.
|
|||
|
var path = g.select('g.' + cName).selectAll('path.' + cName)
|
|||
|
.data([data]);
|
|||
|
// enter
|
|||
|
path
|
|||
|
.enter()
|
|||
|
.append('svg:path')
|
|||
|
.attr('d', line)
|
|||
|
.attr('class', cName)
|
|||
|
.attr('stroke-dasharray', function (d) {
|
|||
|
var pl = pathLen(d3.select(this));
|
|||
|
return pl + ' ' + pl;
|
|||
|
})
|
|||
|
.attr('stroke-dashoffset', function (d) {
|
|||
|
return pathLen(d3.select(this))
|
|||
|
});
|
|||
|
// update
|
|||
|
path
|
|||
|
.attr('d', line)
|
|||
|
.transition()
|
|||
|
.attr('stroke-dasharray', function (d) {
|
|||
|
var pl = pathLen(d3.select(this));
|
|||
|
return pl + ' ' + pl;
|
|||
|
})
|
|||
|
.duration(2000)
|
|||
|
.attr('stroke-dashoffset', 0);
|
|||
|
// remove elements
|
|||
|
path.exit().remove();
|
|||
|
}
|
|||
|
|
|||
|
function addArea(g, data, cb, cName) {
|
|||
|
// Update the area path.
|
|||
|
var area = g.select('g.' + cName).selectAll('path.' + cName)
|
|||
|
.data([data]);
|
|||
|
area
|
|||
|
.enter()
|
|||
|
.append('svg:path')
|
|||
|
.attr('class', cName);
|
|||
|
area
|
|||
|
.attr('d', cb);
|
|||
|
// remove elements
|
|||
|
area.exit().remove();
|
|||
|
}
|
|||
|
|
|||
|
// Update the area path and lines.
|
|||
|
function addAreaLines(g, data) {
|
|||
|
// Update the area path.
|
|||
|
addArea(g, data, area0.y0(yScale0.range()[0]), 'area0');
|
|||
|
// Update the line path.
|
|||
|
addLine(g, data, line0, 'line0');
|
|||
|
// Update the area path.
|
|||
|
addArea(g, data, area1.y1(yScale1.range()[0]), 'area1');
|
|||
|
// Update the line path.
|
|||
|
addLine(g, data, line1, 'line1');
|
|||
|
}
|
|||
|
|
|||
|
// Update chart points
|
|||
|
function addPoints(g, data) {
|
|||
|
var radius = data.length > 100 ? 1 : 2.5;
|
|||
|
|
|||
|
var points = g.select('g.points.y0').selectAll('circle.point')
|
|||
|
.data(data);
|
|||
|
points
|
|||
|
.enter()
|
|||
|
.append('svg:circle')
|
|||
|
.attr('r', radius)
|
|||
|
.attr('class', 'point');
|
|||
|
points
|
|||
|
.attr('cx', function (d) { return xScale(d[0]) })
|
|||
|
.attr('cy', function (d) { return yScale0(d[1]) })
|
|||
|
// remove elements
|
|||
|
points.exit().remove();
|
|||
|
|
|||
|
if (!dualYaxis)
|
|||
|
return;
|
|||
|
|
|||
|
points = g.select('g.points.y1').selectAll('circle.point')
|
|||
|
.data(data);
|
|||
|
points
|
|||
|
.enter()
|
|||
|
.append('svg:circle')
|
|||
|
.attr('r', radius)
|
|||
|
.attr('class', 'point');
|
|||
|
points
|
|||
|
.attr('cx', function (d) { return xScale(d[0]) })
|
|||
|
.attr('cy', function (d) { return yScale1(d[2]) })
|
|||
|
// remove elements
|
|||
|
points.exit().remove();
|
|||
|
}
|
|||
|
|
|||
|
function addAxis(g, data) {
|
|||
|
var xTicks = getXTicks(data);
|
|||
|
var tickDistance = xTicks.length > 1 ? (xScale(xTicks[1]) - xScale(xTicks[0])) : innerW();
|
|||
|
var labelW = tickDistance - padding;
|
|||
|
|
|||
|
// Update the x-axis.
|
|||
|
g.select('.x.axis')
|
|||
|
.attr('transform', 'translate(0,' + yScale0.range()[0] + ')')
|
|||
|
.call(xAxis.tickValues(xTicks))
|
|||
|
.selectAll(".tick text")
|
|||
|
.call(truncate, labelW > 0 ? labelW : innerW());
|
|||
|
|
|||
|
// Update the y0-axis.
|
|||
|
g.select('.y0.axis')
|
|||
|
.call(yAxis0.tickValues(getYTicks(yScale0)) );
|
|||
|
|
|||
|
if (!dualYaxis)
|
|||
|
return;
|
|||
|
|
|||
|
// Update the y1-axis.
|
|||
|
g.select('.y1.axis')
|
|||
|
.attr('transform', 'translate(' + innerW() + ', 0)')
|
|||
|
.call(yAxis1.tickValues(getYTicks(yScale1)));
|
|||
|
}
|
|||
|
|
|||
|
// Update the X-Y grid.
|
|||
|
function addGrid(g, data) {
|
|||
|
g.select('.x.grid')
|
|||
|
.attr('transform', 'translate(0,' + yScale0.range()[0] + ')')
|
|||
|
.call(xGrid
|
|||
|
.tickValues(getXTicks(data))
|
|||
|
.tickSize(-innerH(), 0, 0)
|
|||
|
.tickFormat('')
|
|||
|
);
|
|||
|
|
|||
|
g.select('.y.grid')
|
|||
|
.call(yGrid
|
|||
|
.tickValues(getYTicks(yScale0))
|
|||
|
.tickSize(-innerW(), 0, 0)
|
|||
|
.tickFormat('')
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
function formatTooltip(data, i) {
|
|||
|
var d = data.slice(0);
|
|||
|
|
|||
|
d[0] = (format.x) ? GoAccess.Util.fmtValue(d[0], format.x) : d[0];
|
|||
|
d[1] = (format.y0) ? GoAccess.Util.fmtValue(d[1], format.y0) : d3.format(',')(d[1]);
|
|||
|
dualYaxis && (d[2] = (format.y1) ? GoAccess.Util.fmtValue(d[2], format.y1) : d3.format(',')(d[2]));
|
|||
|
|
|||
|
var template = d3.select('#tpl-chart-tooltip').html();
|
|||
|
return Hogan.compile(template).render({
|
|||
|
'data': d
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
function mouseover(_self, selection, data, idx) {
|
|||
|
var tooltip = selection.select('.chart-tooltip-wrap');
|
|||
|
tooltip.html(formatTooltip(data, idx))
|
|||
|
.style('left', (xScale(data[0])) + 'px')
|
|||
|
.style('top', (d3.mouse(_self)[1] + 10) + 'px')
|
|||
|
.style('display', 'block');
|
|||
|
|
|||
|
selection.select('line.indicator')
|
|||
|
.style('display', 'block')
|
|||
|
.attr('transform', 'translate(' + xScale(data[0]) + ',' + 0 + ')');
|
|||
|
}
|
|||
|
|
|||
|
function mouseout(selection, g) {
|
|||
|
var tooltip = selection.select('.chart-tooltip-wrap');
|
|||
|
tooltip.style('display', 'none');
|
|||
|
|
|||
|
g.select('line.indicator').style('display', 'none');
|
|||
|
}
|
|||
|
|
|||
|
function addRects(selection, g, data) {
|
|||
|
var w = (innerW() / data.length);
|
|||
|
|
|||
|
var rects = g.select('g.rects').selectAll('rect')
|
|||
|
.data(data);
|
|||
|
rects
|
|||
|
.enter()
|
|||
|
.append('svg:rect')
|
|||
|
.attr('height', innerH())
|
|||
|
.attr('class', 'point');
|
|||
|
rects
|
|||
|
.attr('width', d3.functor(w))
|
|||
|
.attr('x', function (d, i) { return (w * i); })
|
|||
|
.attr('y', 0)
|
|||
|
.on('mousemove', function (d, i) {
|
|||
|
mouseover(this, selection, d, i);
|
|||
|
})
|
|||
|
.on('mouseleave', function (d, i) {
|
|||
|
mouseout(selection, g);
|
|||
|
});
|
|||
|
// remove elements
|
|||
|
rects.exit().remove();
|
|||
|
}
|
|||
|
|
|||
|
function chart(selection) {
|
|||
|
selection.each(function (data) {
|
|||
|
// normalize data
|
|||
|
data = mapData(data);
|
|||
|
// updates X-Y scales
|
|||
|
updateScales(data);
|
|||
|
|
|||
|
// Select the svg element, if it exists.
|
|||
|
var svg = d3.select(this).selectAll('svg').data([data]);
|
|||
|
createSkeleton(svg);
|
|||
|
|
|||
|
// Update the outer dimensions.
|
|||
|
svg.attr({
|
|||
|
'width': width,
|
|||
|
'height': height
|
|||
|
});
|
|||
|
|
|||
|
// Update the inner dimensions.
|
|||
|
var g = svg.select('g')
|
|||
|
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
|||
|
|
|||
|
// Add grid
|
|||
|
addGrid(g, data);
|
|||
|
// Add chart lines and areas
|
|||
|
addAreaLines(g, data);
|
|||
|
// Add chart points
|
|||
|
addPoints(g, data);
|
|||
|
// Add axis
|
|||
|
addAxis(g, data);
|
|||
|
// Add rects
|
|||
|
addRects(selection, g, data);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
chart.opts = function (_) {
|
|||
|
if (!arguments.length) return opts;
|
|||
|
opts = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.format = function (_) {
|
|||
|
if (!arguments.length) return format;
|
|||
|
format = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.labels = function (_) {
|
|||
|
if (!arguments.length) return labels;
|
|||
|
labels = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.margin = function (_) {
|
|||
|
if (!arguments.length) return margin;
|
|||
|
margin = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.width = function (_) {
|
|||
|
if (!arguments.length) return width;
|
|||
|
width = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.height = function (_) {
|
|||
|
if (!arguments.length) return height;
|
|||
|
height = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.x = function (_) {
|
|||
|
if (!arguments.length) return xValue;
|
|||
|
xValue = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.y0 = function (_) {
|
|||
|
if (!arguments.length) return yValue0;
|
|||
|
yValue0 = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.y1 = function (_) {
|
|||
|
if (!arguments.length) return yValue1;
|
|||
|
yValue1 = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
return chart;
|
|||
|
}
|
|||
|
|
|||
|
function BarChart(dualYaxis) {
|
|||
|
var opts = {};
|
|||
|
var margin = {
|
|||
|
top : 20,
|
|||
|
right : 50,
|
|||
|
bottom : 40,
|
|||
|
left : 50
|
|||
|
},
|
|||
|
height = 170,
|
|||
|
nTicks = 10,
|
|||
|
padding = 10,
|
|||
|
width = 760;
|
|||
|
var labels = { x: 'Unnamed', y0: 'Unnamed', y1: 'Unnamed' };
|
|||
|
var format = { x: null, y0: null, y1: null};
|
|||
|
|
|||
|
var xValue = function (d) {
|
|||
|
return d[0];
|
|||
|
},
|
|||
|
yValue0 = function (d) {
|
|||
|
return d[1];
|
|||
|
},
|
|||
|
yValue1 = function (d) {
|
|||
|
return d[2];
|
|||
|
};
|
|||
|
|
|||
|
var xScale = d3.scale.ordinal();
|
|||
|
var yScale0 = d3.scale.linear().nice();
|
|||
|
var yScale1 = d3.scale.linear().nice();
|
|||
|
|
|||
|
var xAxis = d3.svg.axis()
|
|||
|
.scale(xScale)
|
|||
|
.orient('bottom')
|
|||
|
.tickFormat(function (d) {
|
|||
|
if (format.x)
|
|||
|
return GoAccess.Util.fmtValue(d, format.x);
|
|||
|
return d;
|
|||
|
});
|
|||
|
|
|||
|
var yAxis0 = d3.svg.axis()
|
|||
|
.scale(yScale0)
|
|||
|
.orient('left')
|
|||
|
.tickFormat(function (d) {
|
|||
|
if (format.y1)
|
|||
|
return GoAccess.Util.fmtValue(d, format.y1);
|
|||
|
return d3.format('.2s')(d);
|
|||
|
});
|
|||
|
|
|||
|
var yAxis1 = d3.svg.axis()
|
|||
|
.scale(yScale1)
|
|||
|
.orient('right')
|
|||
|
.tickFormat(function (d) {
|
|||
|
if (format.y1)
|
|||
|
return GoAccess.Util.fmtValue(d, format.y1);
|
|||
|
return d3.format('.2s')(d);
|
|||
|
});
|
|||
|
|
|||
|
var xGrid = d3.svg.axis()
|
|||
|
.scale(xScale)
|
|||
|
.orient('bottom');
|
|||
|
|
|||
|
var yGrid = d3.svg.axis()
|
|||
|
.scale(yScale0)
|
|||
|
.orient('left');
|
|||
|
|
|||
|
function innerW() {
|
|||
|
return width - margin.left - margin.right;
|
|||
|
}
|
|||
|
|
|||
|
function innerH() {
|
|||
|
return height - margin.top - margin.bottom;
|
|||
|
}
|
|||
|
|
|||
|
function getXTicks(data) {
|
|||
|
if (data.length < nTicks)
|
|||
|
return xScale.domain();
|
|||
|
|
|||
|
return d3.range(0, data.length, Math.ceil(data.length / nTicks)).map(function (d) {
|
|||
|
return xScale.domain()[d];
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
function getYTicks(scale) {
|
|||
|
var domain = scale.domain();
|
|||
|
return d3.range(domain[0], domain[1], Math.ceil(domain[1] / nTicks));
|
|||
|
}
|
|||
|
|
|||
|
// Convert data to standard representation greedily;
|
|||
|
// this is needed for nondeterministic accessors.
|
|||
|
function mapData(data) {
|
|||
|
var _datum = function (d, i) {
|
|||
|
var datum = [xValue.call(data, d, i), yValue0.call(data, d, i)];
|
|||
|
dualYaxis && datum.push(yValue1.call(data, d, i));
|
|||
|
return datum;
|
|||
|
};
|
|||
|
return data.map(function (d, i) {
|
|||
|
return _datum(d, i);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
function updateScales(data) {
|
|||
|
// Update the x-scale.
|
|||
|
xScale.domain(data.map(function (d) {
|
|||
|
return d[0];
|
|||
|
}))
|
|||
|
.rangeBands([0, innerW()], .1);
|
|||
|
|
|||
|
// Update the y-scale.
|
|||
|
yScale0.domain([0, d3.max(data, function (d) {
|
|||
|
return d[1];
|
|||
|
})])
|
|||
|
.range([innerH(), 0]);
|
|||
|
|
|||
|
// Update the y-scale.
|
|||
|
dualYaxis && yScale1.domain([0, d3.max(data, function (d) {
|
|||
|
return d[2];
|
|||
|
})])
|
|||
|
.range([innerH(), 0]);
|
|||
|
}
|
|||
|
|
|||
|
function toggleOpacity(ele, op) {
|
|||
|
d3.select(ele.parentNode).selectAll('.' + (ele.getAttribute('data-yaxis') == 'y0' ? 'y1' : 'y0')).attr('style', op);
|
|||
|
}
|
|||
|
|
|||
|
function setLegendLabels(svg) {
|
|||
|
// Legend Color
|
|||
|
var rect = svg.selectAll('rect.legend.y0').data([null]);
|
|||
|
rect.enter().append('rect')
|
|||
|
.attr('class', 'legend y0')
|
|||
|
.attr('data-yaxis', 'y0')
|
|||
|
.on('mousemove', function (d, i) { toggleOpacity(this, 'opacity:0.1'); })
|
|||
|
.on('mouseleave', function (d, i) { toggleOpacity(this, null); })
|
|||
|
.attr('y', (height - 15));
|
|||
|
rect
|
|||
|
.attr('x', (width / 2) - 100);
|
|||
|
|
|||
|
// Legend Labels
|
|||
|
var text = svg.selectAll('text.legend.y0').data([null]);
|
|||
|
text.enter().append('text')
|
|||
|
.attr('class', 'legend y0')
|
|||
|
.attr('data-yaxis', 'y0')
|
|||
|
.on('mousemove', function (d, i) { toggleOpacity(this, 'opacity:0.1'); })
|
|||
|
.on('mouseleave', function (d, i) { toggleOpacity(this, null); })
|
|||
|
.attr('y', (height - 6));
|
|||
|
text
|
|||
|
.attr('x', (width / 2) - 85)
|
|||
|
.text(labels.y0);
|
|||
|
|
|||
|
if (!dualYaxis)
|
|||
|
return;
|
|||
|
|
|||
|
// Legend Labels
|
|||
|
rect = svg.selectAll('rect.legend.y1').data([null]);
|
|||
|
rect.enter().append('rect')
|
|||
|
.attr('class', 'legend y1')
|
|||
|
.attr('data-yaxis', 'y1')
|
|||
|
.on('mousemove', function (d, i) { toggleOpacity(this, 'opacity:0.1'); })
|
|||
|
.on('mouseleave', function (d, i) { toggleOpacity(this, null); })
|
|||
|
.attr('y', (height - 15));
|
|||
|
rect
|
|||
|
.attr('x', (width / 2));
|
|||
|
|
|||
|
// Legend Labels
|
|||
|
text = svg.selectAll('text.legend.y1').data([null]);
|
|||
|
text.enter().append('text')
|
|||
|
.attr('class', 'legend y1')
|
|||
|
.attr('data-yaxis', 'y1')
|
|||
|
.on('mousemove', function (d, i) { toggleOpacity(this, 'opacity:0.1'); })
|
|||
|
.on('mouseleave', function (d, i) { toggleOpacity(this, null); })
|
|||
|
.attr('y', (height - 6));
|
|||
|
text
|
|||
|
.attr('x', (width / 2) + 15)
|
|||
|
.text(labels.y1);
|
|||
|
}
|
|||
|
|
|||
|
function setAxisLabels(svg) {
|
|||
|
// Labels
|
|||
|
svg.selectAll('text.axis-label.y0').data([null])
|
|||
|
.enter().append('text')
|
|||
|
.attr('class', 'axis-label y0')
|
|||
|
.attr('y', 10)
|
|||
|
.attr('x', 50)
|
|||
|
.text(labels.y0);
|
|||
|
|
|||
|
if (!dualYaxis)
|
|||
|
return;
|
|||
|
|
|||
|
// Labels
|
|||
|
var tEnter = svg.selectAll('text.axis-label.y1').data([null]);
|
|||
|
tEnter.enter().append('text')
|
|||
|
.attr('class', 'axis-label y1')
|
|||
|
.attr('y', 10)
|
|||
|
.text(labels.y1);
|
|||
|
dualYaxis && tEnter
|
|||
|
.attr('x', width - 25)
|
|||
|
}
|
|||
|
|
|||
|
function createSkeleton(svg) {
|
|||
|
// Otherwise, create the skeletal chart.
|
|||
|
var gEnter = svg.enter().append('svg').append('g');
|
|||
|
|
|||
|
// Grid
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'x grid');
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'y grid');
|
|||
|
|
|||
|
// Axis
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'x axis');
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'y0 axis');
|
|||
|
dualYaxis && gEnter.append('g')
|
|||
|
.attr('class', 'y1 axis');
|
|||
|
|
|||
|
// Bars
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'bars y0');
|
|||
|
dualYaxis && gEnter.append('g')
|
|||
|
.attr('class', 'bars y1');
|
|||
|
|
|||
|
// Rects
|
|||
|
gEnter.append('g')
|
|||
|
.attr('class', 'rects');
|
|||
|
|
|||
|
setAxisLabels(svg);
|
|||
|
setLegendLabels(svg);
|
|||
|
|
|||
|
// Mouseover line
|
|||
|
gEnter.append('line')
|
|||
|
.attr('y2', innerH())
|
|||
|
.attr('y1', 0)
|
|||
|
.attr('class', 'indicator');
|
|||
|
}
|
|||
|
|
|||
|
// Update the area path and lines.
|
|||
|
function addBars(g, data) {
|
|||
|
var bars = g.select('g.bars.y0').selectAll('rect.bar')
|
|||
|
.data(data);
|
|||
|
// enter
|
|||
|
bars
|
|||
|
.enter()
|
|||
|
.append('svg:rect')
|
|||
|
.attr('class', 'bar')
|
|||
|
.attr('height', 0)
|
|||
|
.attr('width', function (d, i) { return xScale.rangeBand() / 2; })
|
|||
|
.attr('x', function (d, i) { return xScale(d[0]); })
|
|||
|
.attr('y', function (d, i) { return innerH(); });
|
|||
|
// update
|
|||
|
bars
|
|||
|
.attr('width', xScale.rangeBand() / 2)
|
|||
|
.attr('x', function (d) { return xScale(d[0]) })
|
|||
|
.transition()
|
|||
|
.delay(function (d, i) { return i / data.length * 1000; })
|
|||
|
.duration(500)
|
|||
|
.attr('height', function (d, i) { return innerH() - yScale0(d[1]); })
|
|||
|
.attr('y', function (d, i) { return yScale0(d[1]); });
|
|||
|
// remove elements
|
|||
|
bars.exit().remove();
|
|||
|
|
|||
|
if (!dualYaxis)
|
|||
|
return;
|
|||
|
|
|||
|
var bars = g.select('g.bars.y1').selectAll('rect.bar')
|
|||
|
.data(data);
|
|||
|
// enter
|
|||
|
bars
|
|||
|
.enter()
|
|||
|
.append('svg:rect')
|
|||
|
.attr('class', 'bar')
|
|||
|
.attr('height', 0)
|
|||
|
.attr('width', function (d, i) { return xScale.rangeBand() / 2; })
|
|||
|
.attr('x', function (d) { return (xScale(d[0]) + xScale.rangeBand() / 2) })
|
|||
|
.attr('y', function (d, i) { return innerH(); });
|
|||
|
// update
|
|||
|
bars
|
|||
|
.attr('width', xScale.rangeBand() / 2)
|
|||
|
.attr('x', function (d) { return (xScale(d[0]) + xScale.rangeBand() / 2) })
|
|||
|
.transition()
|
|||
|
.delay(function (d, i) { return i / data.length * 1000; })
|
|||
|
.duration(500)
|
|||
|
.attr('height', function (d, i) { return innerH() - yScale1(d[2]); })
|
|||
|
.attr('y', function (d, i) { return yScale1(d[2]); });
|
|||
|
// remove elements
|
|||
|
bars.exit().remove();
|
|||
|
}
|
|||
|
|
|||
|
function addAxis(g, data) {
|
|||
|
var xTicks = getXTicks(data);
|
|||
|
var tickDistance = xTicks.length > 1 ? (xScale(xTicks[1]) - xScale(xTicks[0])) : innerW();
|
|||
|
var labelW = tickDistance - padding;
|
|||
|
|
|||
|
// Update the x-axis.
|
|||
|
g.select('.x.axis')
|
|||
|
.attr('transform', 'translate(0,' + yScale0.range()[0] + ')')
|
|||
|
.call(xAxis.tickValues(xTicks))
|
|||
|
.selectAll(".tick text")
|
|||
|
.call(truncate, labelW > 0 ? labelW : innerW());
|
|||
|
|
|||
|
// Update the y0-axis.
|
|||
|
g.select('.y0.axis')
|
|||
|
.call(yAxis0.tickValues(getYTicks(yScale0)));
|
|||
|
|
|||
|
if (!dualYaxis)
|
|||
|
return;
|
|||
|
|
|||
|
// Update the y1-axis.
|
|||
|
g.select('.y1.axis')
|
|||
|
.attr('transform', 'translate(' + innerW() + ', 0)')
|
|||
|
.call(yAxis1.tickValues(getYTicks(yScale1)));
|
|||
|
}
|
|||
|
|
|||
|
// Update the X-Y grid.
|
|||
|
function addGrid(g, data) {
|
|||
|
g.select('.x.grid')
|
|||
|
.attr('transform', 'translate(0,' + yScale0.range()[0] + ')')
|
|||
|
.call(xGrid
|
|||
|
.tickValues(getXTicks(data))
|
|||
|
.tickSize(-innerH(), 0, 0)
|
|||
|
.tickFormat('')
|
|||
|
);
|
|||
|
|
|||
|
g.select('.y.grid')
|
|||
|
.call(yGrid
|
|||
|
.tickValues(getYTicks(yScale0))
|
|||
|
.tickSize(-innerW(), 0, 0)
|
|||
|
.tickFormat('')
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
function formatTooltip(data, i) {
|
|||
|
var d = data.slice(0);
|
|||
|
|
|||
|
d[0] = (format.x) ? GoAccess.Util.fmtValue(d[0], format.x) : d[0];
|
|||
|
d[1] = (format.y0) ? GoAccess.Util.fmtValue(d[1], format.y0) : d3.format(',')(d[1]);
|
|||
|
dualYaxis && (d[2] = (format.y1) ? GoAccess.Util.fmtValue(d[2], format.y1) : d3.format(',')(d[2]));
|
|||
|
|
|||
|
var template = d3.select('#tpl-chart-tooltip').html();
|
|||
|
return Hogan.compile(template).render({
|
|||
|
'data': d
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
function mouseover(_self, selection, data, idx) {
|
|||
|
var left = xScale(data[0]) + (xScale.rangeBand() / 2);
|
|||
|
var tooltip = selection.select('.chart-tooltip-wrap');
|
|||
|
tooltip.html(formatTooltip(data, idx))
|
|||
|
.style('left', left + 'px')
|
|||
|
.style('top', (d3.mouse(_self)[1] + 10) + 'px')
|
|||
|
.style('display', 'block');
|
|||
|
|
|||
|
selection.select('line.indicator')
|
|||
|
.style('display', 'block')
|
|||
|
.attr('transform', 'translate(' + left + ',' + 0 + ')');
|
|||
|
}
|
|||
|
|
|||
|
function mouseout(selection, g) {
|
|||
|
var tooltip = selection.select('.chart-tooltip-wrap');
|
|||
|
tooltip.style('display', 'none');
|
|||
|
|
|||
|
g.select('line.indicator').style('display', 'none');
|
|||
|
}
|
|||
|
|
|||
|
function addRects(selection, g, data) {
|
|||
|
var w = (innerW() / data.length);
|
|||
|
|
|||
|
var rects = g.select('g.rects').selectAll('rect')
|
|||
|
.data(data);
|
|||
|
rects
|
|||
|
.enter()
|
|||
|
.append('svg:rect')
|
|||
|
.attr('height', innerH())
|
|||
|
.attr('class', 'point');
|
|||
|
rects
|
|||
|
.attr('width', d3.functor(w))
|
|||
|
.attr('x', function (d, i) { return (w * i); })
|
|||
|
.attr('y', 0)
|
|||
|
.on('mousemove', function (d, i) {
|
|||
|
mouseover(this, selection, d, i);
|
|||
|
})
|
|||
|
.on('mouseleave', function (d, i) {
|
|||
|
mouseout(selection, g);
|
|||
|
});
|
|||
|
// remove elements
|
|||
|
rects.exit().remove();
|
|||
|
}
|
|||
|
|
|||
|
function chart(selection) {
|
|||
|
selection.each(function (data) {
|
|||
|
// normalize data
|
|||
|
data = mapData(data);
|
|||
|
// updates X-Y scales
|
|||
|
updateScales(data);
|
|||
|
|
|||
|
// Select the svg element, if it exists.
|
|||
|
var svg = d3.select(this).selectAll('svg').data([data]);
|
|||
|
createSkeleton(svg);
|
|||
|
|
|||
|
// Update the outer dimensions.
|
|||
|
svg.attr({
|
|||
|
'width': width,
|
|||
|
'height': height
|
|||
|
});
|
|||
|
|
|||
|
// Update the inner dimensions.
|
|||
|
var g = svg.select('g')
|
|||
|
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
|||
|
|
|||
|
// Add grid
|
|||
|
addGrid(g, data);
|
|||
|
// Add axis
|
|||
|
addAxis(g, data);
|
|||
|
// Add chart lines and areas
|
|||
|
addBars(g, data);
|
|||
|
// Add rects
|
|||
|
addRects(selection, g, data);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
chart.opts = function (_) {
|
|||
|
if (!arguments.length) return opts;
|
|||
|
opts = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.format = function (_) {
|
|||
|
if (!arguments.length) return format;
|
|||
|
format = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.labels = function (_) {
|
|||
|
if (!arguments.length) return labels;
|
|||
|
labels = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.width = function (_) {
|
|||
|
if (!arguments.length) return width;
|
|||
|
width = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.height = function (_) {
|
|||
|
if (!arguments.length) return height;
|
|||
|
height = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.x = function (_) {
|
|||
|
if (!arguments.length) return xValue;
|
|||
|
xValue = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.y0 = function (_) {
|
|||
|
if (!arguments.length) return yValue0;
|
|||
|
yValue0 = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
chart.y1 = function (_) {
|
|||
|
if (!arguments.length) return yValue1;
|
|||
|
yValue1 = _;
|
|||
|
return chart;
|
|||
|
};
|
|||
|
|
|||
|
return chart;
|
|||
|
}
|
|||
|
</script></body></html>
|