tor-commits
Threads by month
- ----- 2025 -----
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
May 2018
- 17 participants
- 1514 discussions

[metrics-web/release] Use helper functions for date breaks and labels.
by karsten@torproject.org 30 May '18
by karsten@torproject.org 30 May '18
30 May '18
commit 4b1dc786148111df4fee71297f6094671dc22565
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Tue Feb 13 13:02:07 2018 +0100
Use helper functions for date breaks and labels.
The main motivation behind this change is that we don't have to call a
helper function ourselves anymore on our data to determine useful
breaks and labels. Instead, we let ggplot2 call our functions to
determine breaks and labels for the displayed data.
Another motivation is that this removes a fair amount of code, and
that it's one step further towards piping everything using %>%.
Last but not least, it gives us even more control over date breaks and
labels.
---
src/main/R/rserver/graphs.R | 263 ++++++++++++++++----------------------------
1 file changed, 97 insertions(+), 166 deletions(-)
diff --git a/src/main/R/rserver/graphs.R b/src/main/R/rserver/graphs.R
index 9a37d88..a597746 100644
--- a/src/main/R/rserver/graphs.R
+++ b/src/main/R/rserver/graphs.R
@@ -258,17 +258,55 @@ countryname <- function(country) {
res
}
-date_breaks <- function(days) {
- length <- cut(days, c(-1, 7, 12, 56, 180, 600, 5000, Inf), labels=FALSE)
- major <- c("days", "2 days", "weeks", "months", "3 months", "years",
- "5 years")[length]
- minor <- c("days", "days", "days", "weeks", "months", "months",
- "years")[length]
- format <- c("%d-%b", "%d-%b", "%d-%b", "%b-%Y", "%b-%Y", "%Y",
- "%Y")[length]
- list(major = major, minor = minor, format = format)
+# Helper function that takes date limits as input and returns major breaks as
+# output. The main difference to the built-in major breaks is that we're trying
+# harder to align major breaks with first days of weeks (Sundays), months,
+# quarters, or years.
+custom_breaks <- function(input) {
+ scales_index <- cut(as.numeric(max(input) - min(input)),
+ c(-1, 7, 12, 56, 180, 600, 2000, Inf), labels = FALSE)
+ from_print_format <- c("%F", "%F", "%Y-W%U-7", "%Y-%m-01", "%Y-01-01",
+ "%Y-01-01", "%Y-01-01")[scales_index]
+ from_parse_format <- ifelse(scales_index == 3, "%Y-W%U-%u", "%F")
+ by <- c("1 day", "2 days", "1 week", "1 month", "3 months", "1 year",
+ "2 years")[scales_index]
+ seq(as.Date(as.character(min(input), from_print_format),
+ format = from_parse_format), max(input), by = by)
}
+# Helper function that takes date limits as input and returns minor breaks as
+# output. As opposed to the built-in minor breaks, we're not just adding one
+# minor break half way through between two major breaks. Instead, we're plotting
+# a minor break for every day, week, month, or quarter between two major breaks.
+custom_minor_breaks <- function(input) {
+ scales_index <- cut(as.numeric(max(input) - min(input)),
+ c(-1, 7, 12, 56, 180, 600, 2000, Inf), labels = FALSE)
+ from_print_format <- c("%F", "%F", "%F", "%Y-W%U-7", "%Y-%m-01", "%Y-01-01",
+ "%Y-01-01")[scales_index]
+ from_parse_format <- ifelse(scales_index == 4, "%Y-W%U-%u", "%F")
+ by <- c("1 day", "1 day", "1 day", "1 week", "1 month", "3 months",
+ "1 year")[scales_index]
+ seq(as.Date(as.character(min(input), from_print_format),
+ format = from_parse_format), max(input), by = by)
+}
+
+# Helper function that takes breaks as input and returns labels as output. We're
+# going all ISO-8601 here, though we're not just writing %Y-%m-%d everywhere,
+# but %Y-%m or %Y if all breaks are on the first of a month or even year.
+custom_labels <- function(breaks) {
+ if (all(format(breaks, format = "%m-%d") == "01-01", na.rm = TRUE)) {
+ format(breaks, format = "%Y")
+ } else {
+ if (all(format(breaks, format = "%d") == "01", na.rm = TRUE)) {
+ format(breaks, format = "%Y-%m")
+ } else {
+ format(breaks, format = "%F")
+ }
+ }
+}
+
+# Helper function to format numbers in non-scientific notation with spaces as
+# thousands separator.
formatter <- function(x, ...) {
format(x, ..., scientific = FALSE, big.mark = ' ')
}
@@ -313,15 +351,10 @@ plot_networksize <- function(start, end, path) {
data.frame(date = as.Date(missing, origin = "1970-01-01"),
relays = NA, bridges = NA))
networksize <- melt(s, id = "date")
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(networksize$date, "%Y-%m-%d")) -
- min(as.Date(networksize$date, "%Y-%m-%d"))))
ggplot(networksize, aes(x = as.Date(date, "%Y-%m-%d"), y = value,
colour = variable)) + geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_colour_hue("", breaks = c("relays", "bridges"),
labels = c("Relays", "Bridges")) +
@@ -347,16 +380,11 @@ plot_versions <- function(start, end, path) {
stringsAsFactors = FALSE)
versions <- s[s$version %in% known_versions, ]
visible_versions <- sort(unique(versions$version))
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(versions$date, "%Y-%m-%d")) -
- min(as.Date(versions$date, "%Y-%m-%d"))))
ggplot(versions, aes(x = as.Date(date, "%Y-%m-%d"), y = relays,
colour = version)) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_colour_manual(name = "Tor version",
values = colours[colours$breaks %in% visible_versions, 2],
@@ -373,16 +401,11 @@ plot_platforms <- function(start, end, path) {
s$ec2bridge == '', ]
platforms <- data.frame(date = as.Date(s$date, "%Y-%m-%d"),
variable = s$platform, value = s$relays)
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(platforms$date, "%Y-%m-%d")) -
- min(as.Date(platforms$date, "%Y-%m-%d"))))
ggplot(platforms, aes(x = as.Date(date, "%Y-%m-%d"), y = value,
colour = variable)) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_colour_manual(name = "Platform",
breaks = c("Linux", "Darwin", "BSD", "Windows", "Other"),
@@ -401,16 +424,11 @@ plot_bandwidth <- function(start, end, path) {
bwadv = b$advbw,
bwhist = (b$bwread + b$bwwrite) / 2)
bandwidth <- melt(b, id = "date")
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(bandwidth$date, "%Y-%m-%d")) -
- min(as.Date(bandwidth$date, "%Y-%m-%d"))))
ggplot(bandwidth, aes(x = as.Date(date, "%Y-%m-%d"),
y = value * 8 / 1e9, colour = variable)) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "Bandwidth (Gbit/s)", limits = c(0, NA)) +
scale_colour_hue(name = "", h.start = 90,
breaks = c("bwadv", "bwhist"),
@@ -445,16 +463,11 @@ plot_bwhist_flags <- function(start, end, path) {
ifelse(bw$isguard, "Guard & Exit", "Exit only"),
ifelse(bw$isguard, "Guard only", "Middle only")),
value = (bw$read + bw$written) / 2)
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(bw$date, "%Y-%m-%d")) -
- min(as.Date(bw$date, "%Y-%m-%d"))))
ggplot(bw, aes(x = as.Date(date, "%Y-%m-%d"), y = value * 8 / 1e9,
colour = variable)) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name="Bandwidth (Gbit/s)", limits = c(0, NA)) +
scale_colour_manual(name = "",
values = c("#E69F00", "#56B4E9", "#009E73", "#0072B2")) +
@@ -471,16 +484,11 @@ plot_dirbytes <- function(start, end, path) {
b <- data.frame(date = as.Date(b$date, "%Y-%m-%d"),
dirread = b$dirread, dirwrite = b$dirwrite)
dir <- melt(b, id = "date")
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(dir$date, "%Y-%m-%d")) -
- min(as.Date(dir$date, "%Y-%m-%d"))))
ggplot(dir, aes(x = as.Date(date, "%Y-%m-%d"), y = value * 8 / 1e9,
colour = variable)) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name="Bandwidth (Gbit/s)", limits = c(0, NA)) +
scale_colour_hue(name = "",
breaks = c("dirwrite", "dirread"),
@@ -511,15 +519,10 @@ plot_relayflags <- function(start, end, flags, path) {
date = as.Date(rep(missing, 6), origin = "1970-01-01"),
variable = c("Running", "Exit", "Guard", "Fast", "Stable", "HSDir"),
value = rep(NA, length(missing) * 6)), networksize)
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(end, "%Y-%m-%d")) -
- min(as.Date(networksize$date, "%Y-%m-%d"))))
ggplot(networksize, aes(x = as.Date(date, "%Y-%m-%d"), y = value,
colour = as.factor(variable))) + geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor, limits = as.Date(c(start, end))) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_colour_manual(name = "Relay flags", values = c("#E69F00",
"#56B4E9", "#009E73", "#EE6A50", "#000000", "#0072B2"),
@@ -553,18 +556,13 @@ plot_torperf <- function(start, end, source, server, filesize, path) {
filesizes <- data.frame(filesizes = c("5mb", "1mb", "50kb"),
label = c("5 MiB", "1 MiB", "50 KiB"), stringsAsFactors = FALSE)
filesizeStr <- filesizes[filesizes$filesize == filesize, "label"]
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(torperf$date, "%Y-%m-%d")) -
- min(as.Date(torperf$date, "%Y-%m-%d"))))
ggplot(torperf, aes(x = as.Date(date, "%Y-%m-%d"), y = md/1e3,
fill = "line")) +
geom_line(colour = colour, size = 0.75) +
geom_ribbon(data = torperf, aes(x = date, ymin = q1/1e3,
ymax = q3/1e3, fill = "ribbon")) +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_fill_manual(name = paste("Measured times on",
ifelse(source == "all", "all sources", source), "per day"),
@@ -606,16 +604,11 @@ plot_torperf_failures <- function(start, end, source, server, filesize, path) {
value = ifelse(torperf$requests > 0,
torperf$failures / torperf$requests, 0),
variable = "failures"))
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(torperf$date, "%Y-%m-%d")) -
- min(as.Date(torperf$date, "%Y-%m-%d"))))
ggplot(torperf, aes(x = as.Date(date, "%Y-%m-%d"), y = value,
colour = variable)) +
geom_point(size = 2) +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = percent) +
scale_colour_hue(name = paste("Problems encountered on",
ifelse(source == "all", "all sources", source)),
@@ -637,17 +630,12 @@ plot_connbidirect <- function(start, end, path) {
quantile = paste("X", c$quantile, sep = ""),
fraction = c$fraction / 100)
c <- cast(c, date + direction ~ quantile, value = "fraction")
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(c$date, "%Y-%m-%d")) -
- min(as.Date(c$date, "%Y-%m-%d"))))
ggplot(c, aes(x = date, y = X0.5, colour = direction)) +
geom_line(size = 0.75) +
geom_ribbon(aes(x = date, ymin = X0.25, ymax = X0.75,
fill = direction), alpha = 0.5, show_guide = FALSE) +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = percent) +
scale_colour_hue(name = "Medians and interquartile ranges",
breaks = c("both", "write", "read"),
@@ -685,9 +673,6 @@ plot_bandwidth_flags <- function(start, end, path) {
'bandwidth history'),
flag = b$flag, value = b$value)
bandwidth <- b[b$value > 0, ]
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(bandwidth$date, "%Y-%m-%d")) -
- min(as.Date(bandwidth$date, "%Y-%m-%d"))))
dates <- seq(from = as.Date(start, "%Y-%m-%d"),
to = as.Date(end, "%Y-%m-%d"), by = "1 day")
missing <- setdiff(dates, as.Date(bandwidth$date,
@@ -711,10 +696,8 @@ plot_bandwidth_flags <- function(start, end, path) {
ggplot(bandwidth, aes(x = as.Date(date, "%Y-%m-%d"),
y = value * 8 / 1e9, colour = variable)) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name="Bandwidth (Gbit/s)", limits = c(0, NA)) +
scale_colour_manual(name = "",
values = c("#E69F00", "#D6C827", "#009E73", "#00C34F")) +
@@ -808,8 +791,6 @@ plot_userstats <- function(start, end, node, variable, value, events,
date = seq(from = as.Date(start, "%Y-%m-%d"),
to = as.Date(end, "%Y-%m-%d"), by="1 day"),
value = ifelse(value == 'all', '', value))))
- date_breaks <- date_breaks(
- as.numeric(max(u$date) - min(u$date)))
if (length(value) > 1) {
plot <- ggplot(u, aes(x = date, y = users, colour = value))
} else {
@@ -835,10 +816,8 @@ plot_userstats <- function(start, end, node, variable, value, events,
}
plot <- plot +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
ggtitle(title)
if (length(value) > 1) {
@@ -888,16 +867,11 @@ plot_userstats_bridge_combined <- function(start, end, country, path) {
u <- u[u$transport %in% a$transport, ]
title <- paste("Bridge users by transport from ",
countryname(country), sep = "")
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(u$date, "%Y-%m-%d")) -
- min(as.Date(u$date, "%Y-%m-%d"))))
ggplot(u, aes(x = as.Date(date), ymin = low, ymax = high,
colour = transport, fill = transport)) +
geom_ribbon(alpha = 0.5, size = 0.5) +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA), labels = formatter) +
scale_colour_hue(paste("Top-", top, " transports", sep = "")) +
scale_fill_hue(paste("Top-", top, " transports", sep = "")) +
@@ -916,16 +890,11 @@ plot_advbwdist_perc <- function(start, end, p, path) {
variable = ifelse(t$isexit != "t", "All relays",
"Exits only"),
percentile = as.factor(t$percentile))
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(t$date, "%Y-%m-%d")) -
- min(as.Date(t$date, "%Y-%m-%d"))))
ggplot(t, aes(x = as.Date(date), y = advbw, colour = percentile)) +
facet_grid(variable ~ .) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "Advertised bandwidth in Gbit/s",
limits = c(0, NA)) +
scale_colour_hue(name = "Percentile",
@@ -942,16 +911,11 @@ plot_advbwdist_relay <- function(start, end, n, path) {
variable = ifelse(t$isexit != "t", "All relays",
"Exits only"),
relay = as.factor(t$relay))
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(t$date, "%Y-%m-%d")) -
- min(as.Date(t$date, "%Y-%m-%d"))))
ggplot(t, aes(x = as.Date(date), y = advbw, colour = relay)) +
facet_grid(variable ~ .) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "Advertised bandwidth in Gbit/s",
limits = c(0, NA)) +
scale_colour_hue(name = "n", breaks = levels(t$relay)) +
@@ -966,14 +930,10 @@ plot_hidserv_dir_onions_seen <- function(start, end, path) {
h <- rbind(data.frame(date = NA, wiqm = 0),
data.frame(date = as.Date(h$date, "%Y-%m-%d"),
wiqm = ifelse(h$frac >= 0.01, h$wiqm, NA)))
- date_breaks <- date_breaks(as.numeric(max(h$date, na.rm = TRUE)
- - min(h$date, na.rm = TRUE)))
ggplot(h, aes(x = as.Date(date, origin = "1970-01-01"), y = wiqm)) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "") +
ggtitle("Unique .onion addresses")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
@@ -987,15 +947,11 @@ plot_hidserv_rend_relayed_cells <- function(start, end, path) {
h <- rbind(data.frame(date = NA, wiqm = 0),
data.frame(date = as.Date(h$date, "%Y-%m-%d"),
wiqm = ifelse(h$frac >= 0.01, h$wiqm, NA)))
- date_breaks <- date_breaks(as.numeric(max(h$date, na.rm = TRUE)
- - min(h$date, na.rm = TRUE)))
ggplot(h, aes(x = as.Date(date, origin = "1970-01-01"),
y = wiqm * 8 * 512 / (86400 * 1e6))) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "") +
ggtitle("Onion-service traffic in Mbit/s")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
@@ -1010,16 +966,12 @@ plot_hidserv_frac_reporting <- function(start, end, path) {
"dir-onions-seen")),
data.frame(date = as.Date(h$date, "%Y-%m-%d"),
frac = h$frac, type = h$type))
- date_breaks <- date_breaks(as.numeric(max(h$date, na.rm = TRUE)
- - min(h$date, na.rm = TRUE)))
ggplot(h, aes(x = as.Date(date, origin = "1970-01-01"), y = frac,
colour = type)) +
geom_line() +
geom_hline(yintercept = 0.01, linetype = 2) +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = percent) +
scale_colour_hue(name = "",
breaks = c("rend-relayed-cells", "dir-onions-seen"),
@@ -1035,7 +987,6 @@ plot_webstats_tb <- function(start, end, path) {
load(paste(rdata_dir, "webstats-tb.RData", sep = ""))
d <- data
d <- d[d$log_date >= start & d$log_date <= end, ]
- date_breaks <- date_breaks(as.numeric(max(d$log_date) - min(d$log_date)))
d$request_type <- factor(d$request_type)
levels(d$request_type) <- list(
'Initial downloads' = 'tbid',
@@ -1046,10 +997,8 @@ plot_webstats_tb <- function(start, end, path) {
geom_point() +
geom_line() +
facet_grid(request_type ~ ., scales = "free_y") +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = 'Requests per day', labels = formatter,
limits = c(0, NA)) +
theme(strip.text.y = element_text(angle = 0, hjust = 0, size = rel(1.5)),
@@ -1064,14 +1013,11 @@ plot_webstats_tb_platform <- function(start, end, path) {
d <- d[d$log_date >= start & d$log_date <= end & d$request_type == 'tbid', ]
d <- aggregate(list(count = d$count), by = list(log_date = as.Date(d$log_date),
platform = d$platform), FUN = sum)
- date_breaks <- date_breaks(as.numeric(max(d$log_date) - min(d$log_date)))
ggplot(d, aes(x = log_date, y = count, colour = platform)) +
geom_point() +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = 'Requests per day', labels = formatter,
limits = c(0, NA)) +
scale_colour_hue(name = "Platform",
@@ -1093,14 +1039,11 @@ plot_webstats_tb_locale <- function(start, end, path) {
e <- e[1:5, ]
d <- aggregate(list(count = d$count), by = list(log_date = as.Date(d$log_date),
locale = ifelse(d$locale %in% e$locale, d$locale, '(other)')), FUN = sum)
- date_breaks <- date_breaks(as.numeric(max(d$log_date) - min(d$log_date)))
ggplot(d, aes(x = log_date, y = count, colour = locale)) +
geom_point() +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = 'Requests per day', labels = formatter,
limits = c(0, NA)) +
scale_colour_hue(name = "Locale",
@@ -1116,7 +1059,6 @@ plot_webstats_tm <- function(start, end, path) {
load(paste(rdata_dir, "webstats-tm.RData", sep = ""))
d <- data
d <- d[d$log_date >= start & d$log_date <= end, ]
- date_breaks <- date_breaks(as.numeric(max(d$log_date) - min(d$log_date)))
d$request_type <- factor(d$request_type)
levels(d$request_type) <- list(
'Initial downloads' = 'tmid',
@@ -1125,10 +1067,8 @@ plot_webstats_tm <- function(start, end, path) {
geom_point() +
geom_line() +
facet_grid(request_type ~ ., scales = "free_y") +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = 'Requests per day', labels = formatter,
limits = c(0, NA)) +
theme(strip.text.y = element_text(angle = 0, hjust = 0, size = rel(1.5)),
@@ -1143,7 +1083,6 @@ plot_relays_ipv6 <- function(start, end, path) {
filter(server == "relay")
start_date <- max(as.Date(start), min(all_relay_data$valid_after_date))
end_date <- min(as.Date(end), max(all_relay_data$valid_after_date))
- date_breaks <- date_breaks(as.numeric(end_date - start_date))
all_relay_data %>%
filter(valid_after_date >= start_date, valid_after_date <= end_date) %>%
group_by(valid_after_date) %>%
@@ -1157,10 +1096,8 @@ plot_relays_ipv6 <- function(start, end, path) {
value = "count") %>%
ggplot(aes(x = valid_after_date, y = count, colour = category)) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_colour_hue(name = "", h.start = 90,
breaks = c("total", "announced", "reachable", "exiting"),
@@ -1177,7 +1114,6 @@ plot_bridges_ipv6 <- function(start, end, path) {
filter(server == "bridge")
start_date <- max(as.Date(start), min(all_bridge_data$valid_after_date))
end_date <- min(as.Date(end), max(all_bridge_data$valid_after_date))
- date_breaks <- date_breaks(as.numeric(end_date - start_date))
all_bridge_data %>%
filter(valid_after_date >= start_date, valid_after_date <= end_date) %>%
group_by(valid_after_date) %>%
@@ -1188,10 +1124,8 @@ plot_bridges_ipv6 <- function(start, end, path) {
gather(total, announced, key = "category", value = "count") %>%
ggplot(aes(x = valid_after_date, y = count, colour = category)) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_colour_hue(name = "", h.start = 90,
breaks = c("total", "announced"),
@@ -1207,7 +1141,6 @@ plot_advbw_ipv6 <- function(start, end, path) {
filter(server == "relay")
start_date <- max(as.Date(start), min(all_relay_data$valid_after_date))
end_date <- min(as.Date(end), max(all_relay_data$valid_after_date))
- date_breaks <- date_breaks(as.numeric(end_date - start_date))
all_relay_data %>%
filter(valid_after_date >= start_date, valid_after_date <= end_date) %>%
group_by(valid_after_date) %>%
@@ -1227,10 +1160,8 @@ plot_advbw_ipv6 <- function(start, end, path) {
ggplot(aes(x = valid_after_date, y = (count * 8) / 1e9,
colour = category)) +
geom_line() +
- scale_x_date(name = copyright_notice,
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
+ scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "Bandwidth (Gbit/s)", limits = c(0, NA)) +
scale_colour_hue(name = "", h.start = 90,
breaks = c("total", "total_guard", "total_exit", "reachable_guard",
1
0

30 May '18
commit 8831ecebd4ac186a798c2cb844289ed94318a6ed
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Fri Feb 9 21:24:01 2018 +0100
Don't cut off any data from CSV files.
When graphing data from CSV files it's not our job to make sure the
data is stable enough to be graphed. That would mean that whoever uses
our CSV files directly would have to make that sure by themselves. If
data is too recent to be graphed, it shouldn't be included in the CSV
files. As a side effect this makes the graphing process a little
easier.
---
src/main/R/rserver/graphs.R | 31 +++----------------------------
1 file changed, 3 insertions(+), 28 deletions(-)
diff --git a/src/main/R/rserver/graphs.R b/src/main/R/rserver/graphs.R
index 440a691..768b776 100644
--- a/src/main/R/rserver/graphs.R
+++ b/src/main/R/rserver/graphs.R
@@ -294,7 +294,6 @@ update_geom_defaults("line", list(size = 1))
copyright_notice = "The Tor Project - https://metrics.torproject.org/"
plot_networksize <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 2))
s <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"servers.csv", sep = ""), stringsAsFactors = FALSE)
s <- s[s$date >= start & s$date <= end & s$flag == '' &
@@ -327,7 +326,6 @@ plot_networksize <- function(start, end, path) {
}
plot_versions <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 2))
s <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"servers.csv", sep = ""), stringsAsFactors = FALSE)
s <- s[s$date >= start & s$date <= end & s$flag == '' &
@@ -364,7 +362,6 @@ plot_versions <- function(start, end, path) {
}
plot_platforms <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 2))
s <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"servers.csv", sep = ""), stringsAsFactors = FALSE)
s <- s[s$date >= start & s$date <= end & s$flag == '' &
@@ -392,7 +389,6 @@ plot_platforms <- function(start, end, path) {
}
plot_bandwidth <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 4))
b <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"bandwidth.csv", sep = ""), stringsAsFactors = FALSE)
b <- b[b$date >= start & b$date <= end & b$isexit == '' &
@@ -421,7 +417,6 @@ plot_bandwidth <- function(start, end, path) {
}
plot_bwhist_flags <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 4))
b <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"bandwidth.csv", sep = ""), stringsAsFactors = FALSE)
b <- b[b$date >= start & b$date <= end & b$isexit != '' &
@@ -465,7 +460,6 @@ plot_bwhist_flags <- function(start, end, path) {
}
plot_dirbytes <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 4))
b <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"bandwidth.csv", sep = ""), stringsAsFactors = FALSE)
b <- b[b$date >= start & b$date <= end & b$isexit == '' &
@@ -493,7 +487,6 @@ plot_dirbytes <- function(start, end, path) {
}
plot_relayflags <- function(start, end, flags, path) {
- end <- min(end, as.character(Sys.Date() - 2))
s <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"servers.csv", sep = ""), stringsAsFactors = FALSE)
s <- s[s$date >= start & s$date <= end & s$country == '' &
@@ -532,7 +525,6 @@ plot_relayflags <- function(start, end, flags, path) {
}
plot_torperf <- function(start, end, source, server, filesize, path) {
- end <- min(end, as.character(Sys.Date() - 2))
filesizeVal <- ifelse(filesize == '50kb', 50 * 1024,
ifelse(filesize == '1mb', 1024 * 1024, 5 * 1024 * 1024))
t <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
@@ -582,7 +574,6 @@ plot_torperf <- function(start, end, source, server, filesize, path) {
}
plot_torperf_failures <- function(start, end, source, server, filesize, path) {
- end <- min(end, as.character(Sys.Date() - 2))
filesizeVal <- ifelse(filesize == '50kb', 50 * 1024,
ifelse(filesize == '1mb', 1024 * 1024, 5 * 1024 * 1024))
t <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
@@ -633,7 +624,6 @@ plot_torperf_failures <- function(start, end, source, server, filesize, path) {
}
plot_connbidirect <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 2))
c <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"connbidirect2.csv", sep = ""), stringsAsFactors = FALSE)
c <- c[c$date >= start & c$date <= end, ]
@@ -669,7 +659,6 @@ plot_connbidirect <- function(start, end, path) {
}
plot_bandwidth_flags <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 4))
b <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"bandwidth.csv", sep = ""), stringsAsFactors = FALSE)
b <- b[b$date >= start & b$date <= end & b$isexit != '' &
@@ -733,7 +722,6 @@ plot_bandwidth_flags <- function(start, end, path) {
plot_userstats <- function(start, end, node, variable, value, events,
path) {
- end <- min(end, as.character(Sys.Date() - 2))
load(paste("/srv/metrics.torproject.org/metrics/shared/RData/clients-",
node, ".RData", sep = ""))
c <- data
@@ -887,7 +875,6 @@ plot_userstats_bridge_combined <- function(start, end, country, path) {
} else {
top <- 3
country <- ifelse(country == "all", NA, country)
- end <- min(end, as.character(Sys.Date() - 2))
load(paste("/srv/metrics.torproject.org/metrics/shared/RData/",
"userstats-bridge-combined.RData", sep = ""))
u <- data
@@ -919,7 +906,6 @@ plot_userstats_bridge_combined <- function(start, end, country, path) {
}
plot_advbwdist_perc <- function(start, end, p, path) {
- end <- min(end, as.character(Sys.Date() - 2))
t <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"advbwdist.csv", sep = ""), stringsAsFactors = FALSE)
t <- t[t$date >= start & t$date <= end &
@@ -947,7 +933,6 @@ plot_advbwdist_perc <- function(start, end, p, path) {
}
plot_advbwdist_relay <- function(start, end, n, path) {
- end <- min(end, as.character(Sys.Date() - 2))
t <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"advbwdist.csv", sep = ""), stringsAsFactors = FALSE)
t <- t[t$date >= start & t$date <= end & t$relay %in% as.numeric(n), ]
@@ -973,7 +958,6 @@ plot_advbwdist_relay <- function(start, end, n, path) {
}
plot_hidserv_dir_onions_seen <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 2))
h <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"hidserv.csv", sep = ""), stringsAsFactors = FALSE)
h <- h[h$date >= start & h$date <= end & h$type == "dir-onions-seen", ]
@@ -994,7 +978,6 @@ plot_hidserv_dir_onions_seen <- function(start, end, path) {
}
plot_hidserv_rend_relayed_cells <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 2))
h <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"hidserv.csv", sep = ""), stringsAsFactors = FALSE)
h <- h[h$date >= start & h$date <= end &
@@ -1017,7 +1000,6 @@ plot_hidserv_rend_relayed_cells <- function(start, end, path) {
}
plot_hidserv_frac_reporting <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 2))
h <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"hidserv.csv", sep = ""), stringsAsFactors = FALSE)
h <- h[h$date >= start & h$date <= end, ]
@@ -1048,7 +1030,6 @@ plot_hidserv_frac_reporting <- function(start, end, path) {
}
plot_webstats_tb <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 2))
load("/srv/metrics.torproject.org/metrics/shared/RData/webstats-tb.RData")
d <- data
d <- d[d$log_date >= start & d$log_date <= end, ]
@@ -1076,7 +1057,6 @@ plot_webstats_tb <- function(start, end, path) {
}
plot_webstats_tb_platform <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 2))
d <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"webstats.csv", sep = ""), stringsAsFactors = FALSE)
d <- d[d$log_date >= start & d$log_date <= end & d$request_type == 'tbid', ]
@@ -1102,7 +1082,6 @@ plot_webstats_tb_platform <- function(start, end, path) {
}
plot_webstats_tb_locale <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 2))
d <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
"webstats.csv", sep = ""), stringsAsFactors = FALSE)
d <- d[d$log_date >= start & d$log_date <= end & d$request_type == 'tbid', ]
@@ -1132,7 +1111,6 @@ plot_webstats_tb_locale <- function(start, end, path) {
}
plot_webstats_tm <- function(start, end, path) {
- end <- min(end, as.character(Sys.Date() - 2))
load("/srv/metrics.torproject.org/metrics/shared/RData/webstats-tm.RData")
d <- data
d <- d[d$log_date >= start & d$log_date <= end, ]
@@ -1163,8 +1141,7 @@ plot_relays_ipv6 <- function(start, end, path) {
colClasses = c("valid_after_date" = "Date")) %>%
filter(server == "relay")
start_date <- max(as.Date(start), min(all_relay_data$valid_after_date))
- end_date <- min(as.Date(end), max(all_relay_data$valid_after_date),
- Sys.Date() - 2)
+ end_date <- min(as.Date(end), max(all_relay_data$valid_after_date))
date_breaks <- date_breaks(as.numeric(end_date - start_date))
all_relay_data %>%
filter(valid_after_date >= start_date, valid_after_date <= end_date) %>%
@@ -1199,8 +1176,7 @@ plot_bridges_ipv6 <- function(start, end, path) {
colClasses = c("valid_after_date" = "Date")) %>%
filter(server == "bridge")
start_date <- max(as.Date(start), min(all_bridge_data$valid_after_date))
- end_date <- min(as.Date(end), max(all_bridge_data$valid_after_date),
- Sys.Date() - 2)
+ end_date <- min(as.Date(end), max(all_bridge_data$valid_after_date))
date_breaks <- date_breaks(as.numeric(end_date - start_date))
all_bridge_data %>%
filter(valid_after_date >= start_date, valid_after_date <= end_date) %>%
@@ -1231,8 +1207,7 @@ plot_advbw_ipv6 <- function(start, end, path) {
colClasses = c("valid_after_date" = "Date")) %>%
filter(server == "relay")
start_date <- max(as.Date(start), min(all_relay_data$valid_after_date))
- end_date <- min(as.Date(end), max(all_relay_data$valid_after_date),
- Sys.Date() - 2)
+ end_date <- min(as.Date(end), max(all_relay_data$valid_after_date))
date_breaks <- date_breaks(as.numeric(end_date - start_date))
all_relay_data %>%
filter(valid_after_date >= start_date, valid_after_date <= end_date) %>%
1
0

[metrics-web/release] Move copyright notice from x axis label to caption.
by karsten@torproject.org 30 May '18
by karsten@torproject.org 30 May '18
30 May '18
commit bb596534c18d38b8054101b99066365e3d28e53c
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Tue Feb 13 14:53:54 2018 +0100
Move copyright notice from x axis label to caption.
---
src/main/R/rserver/graphs.R | 107 ++++++++++++++++++++++++++------------------
1 file changed, 64 insertions(+), 43 deletions(-)
diff --git a/src/main/R/rserver/graphs.R b/src/main/R/rserver/graphs.R
index fffe7c4..04f1b10 100644
--- a/src/main/R/rserver/graphs.R
+++ b/src/main/R/rserver/graphs.R
@@ -316,11 +316,7 @@ theme_update(
plot.title = element_text(hjust = 0.5, margin = margin(b = 11)),
# Leave a little more room to the right for long x axis labels.
- plot.margin = margin(5.5, 11, 5.5, 5.5),
-
- # Leave some room between plot and x axis label, which we use for the
- # copyright notice.
- axis.title.x = element_text(margin = margin(t = 11))
+ plot.margin = margin(5.5, 11, 5.5, 5.5)
)
# Set the default line size of geom_line() to 1.
@@ -350,12 +346,13 @@ plot_networksize <- function(start, end, path) {
networksize <- melt(s, id = "date")
ggplot(networksize, aes(x = as.Date(date, "%Y-%m-%d"), y = value,
colour = variable)) + geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_colour_hue("", breaks = c("relays", "bridges"),
labels = c("Relays", "Bridges")) +
- ggtitle("Number of relays")
+ ggtitle("Number of relays") +
+ labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -380,13 +377,14 @@ plot_versions <- function(start, end, path) {
ggplot(versions, aes(x = as.Date(date, "%Y-%m-%d"), y = relays,
colour = version)) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_colour_manual(name = "Tor version",
values = colours[colours$breaks %in% visible_versions, 2],
breaks = visible_versions) +
- ggtitle("Relay versions")
+ ggtitle("Relay versions") +
+ labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -401,14 +399,15 @@ plot_platforms <- function(start, end, path) {
ggplot(platforms, aes(x = as.Date(date, "%Y-%m-%d"), y = value,
colour = variable)) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_colour_manual(name = "Platform",
breaks = c("Linux", "Darwin", "BSD", "Windows", "Other"),
labels = c("Linux", "macOS", "BSD", "Windows", "Other"),
values = c("#E69F00", "#56B4E9", "#009E73", "#0072B2", "#333333")) +
- ggtitle("Relay platforms")
+ ggtitle("Relay platforms") +
+ labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -424,7 +423,7 @@ plot_bandwidth <- function(start, end, path) {
ggplot(bandwidth, aes(x = as.Date(date, "%Y-%m-%d"),
y = value * 8 / 1e9, colour = variable)) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
limits = c(0, NA)) +
@@ -432,6 +431,7 @@ plot_bandwidth <- function(start, end, path) {
breaks = c("bwadv", "bwhist"),
labels = c("Advertised bandwidth", "Bandwidth history")) +
ggtitle("Total relay bandwidth") +
+ labs(caption = copyright_notice) +
theme(legend.position = "top")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -464,13 +464,14 @@ plot_bwhist_flags <- function(start, end, path) {
ggplot(bw, aes(x = as.Date(date, "%Y-%m-%d"), y = value * 8 / 1e9,
colour = variable)) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
limits = c(0, NA)) +
scale_colour_manual(name = "",
values = c("#E69F00", "#56B4E9", "#009E73", "#0072B2")) +
ggtitle("Bandwidth history by relay flags") +
+ labs(caption = copyright_notice) +
theme(legend.position = "top")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -486,7 +487,7 @@ plot_dirbytes <- function(start, end, path) {
ggplot(dir, aes(x = as.Date(date, "%Y-%m-%d"), y = value * 8 / 1e9,
colour = variable)) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
limits = c(0, NA)) +
@@ -494,6 +495,7 @@ plot_dirbytes <- function(start, end, path) {
breaks = c("dirwrite", "dirread"),
labels = c("Written dir bytes", "Read dir bytes")) +
ggtitle("Number of bytes spent on answering directory requests") +
+ labs(caption = copyright_notice) +
theme(legend.position = "top")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -521,13 +523,14 @@ plot_relayflags <- function(start, end, flags, path) {
value = rep(NA, length(missing) * 6)), networksize)
ggplot(networksize, aes(x = as.Date(date, "%Y-%m-%d"), y = value,
colour = as.factor(variable))) + geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_colour_manual(name = "Relay flags", values = c("#E69F00",
"#56B4E9", "#009E73", "#EE6A50", "#000000", "#0072B2"),
breaks = flags, labels = flags) +
- ggtitle("Number of relays with relay flags assigned")
+ ggtitle("Number of relays with relay flags assigned") +
+ labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -561,7 +564,7 @@ plot_torperf <- function(start, end, source, server, filesize, path) {
geom_line(colour = colour, size = 0.75) +
geom_ribbon(data = torperf, aes(x = date, ymin = q1/1e3,
ymax = q3/1e3, fill = "ribbon")) +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_fill_manual(name = paste("Measured times on",
@@ -571,6 +574,7 @@ plot_torperf <- function(start, end, source, server, filesize, path) {
values = paste(colour, c("", "66"), sep = "")) +
ggtitle(paste("Time in seconds to complete", filesizeStr,
"request to", server, "server")) +
+ labs(caption = copyright_notice) +
theme(legend.position = "top")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -607,7 +611,7 @@ plot_torperf_failures <- function(start, end, source, server, filesize, path) {
ggplot(torperf, aes(x = as.Date(date, "%Y-%m-%d"), y = value,
colour = variable)) +
geom_point(size = 2) +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = percent) +
scale_colour_hue(name = paste("Problems encountered on",
@@ -616,6 +620,7 @@ plot_torperf_failures <- function(start, end, source, server, filesize, path) {
labels = c("Timeouts", "Failures")) +
ggtitle(paste("Timeouts and failures of", filesizeStr,
"requests to", server, "server")) +
+ labs(caption = copyright_notice) +
theme(legend.position = "top")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -634,7 +639,7 @@ plot_connbidirect <- function(start, end, path) {
geom_line(size = 0.75) +
geom_ribbon(aes(x = date, ymin = X0.25, ymax = X0.75,
fill = direction), alpha = 0.5, show_guide = FALSE) +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = percent) +
scale_colour_hue(name = "Medians and interquartile ranges",
@@ -646,6 +651,7 @@ plot_connbidirect <- function(start, end, path) {
labels = c("Both reading and writing", "Mostly writing",
"Mostly reading")) +
ggtitle("Fraction of connections used uni-/bidirectionally") +
+ labs(caption = copyright_notice) +
theme(legend.position = "top")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -696,7 +702,7 @@ plot_bandwidth_flags <- function(start, end, path) {
ggplot(bandwidth, aes(x = as.Date(date, "%Y-%m-%d"),
y = value * 8 / 1e9, colour = variable)) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
limits = c(0, NA)) +
@@ -704,6 +710,7 @@ plot_bandwidth_flags <- function(start, end, path) {
values = c("#E69F00", "#D6C827", "#009E73", "#00C34F")) +
ggtitle(paste("Advertised bandwidth and bandwidth history by",
"relay flags")) +
+ labs(caption = copyright_notice) +
theme(legend.position = "top")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -817,10 +824,11 @@ plot_userstats <- function(start, end, node, variable, value, events,
}
plot <- plot +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
- ggtitle(title)
+ ggtitle(title) +
+ labs(caption = copyright_notice)
if (length(value) > 1) {
plot <- plot +
scale_colour_hue(name = "", breaks = value,
@@ -871,12 +879,13 @@ plot_userstats_bridge_combined <- function(start, end, country, path) {
ggplot(u, aes(x = as.Date(date), ymin = low, ymax = high,
colour = transport, fill = transport)) +
geom_ribbon(alpha = 0.5, size = 0.5) +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA), labels = formatter) +
scale_colour_hue(paste("Top-", top, " transports", sep = "")) +
scale_fill_hue(paste("Top-", top, " transports", sep = "")) +
ggtitle(title) +
+ labs(caption = copyright_notice) +
theme(legend.position = "top")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -894,13 +903,14 @@ plot_advbwdist_perc <- function(start, end, p, path) {
ggplot(t, aes(x = as.Date(date), y = advbw, colour = percentile)) +
facet_grid(variable ~ .) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
limits = c(0, NA)) +
scale_colour_hue(name = "Percentile",
breaks = rev(levels(t$percentile))) +
- ggtitle("Advertised bandwidth distribution")
+ ggtitle("Advertised bandwidth distribution") +
+ labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -915,12 +925,13 @@ plot_advbwdist_relay <- function(start, end, n, path) {
ggplot(t, aes(x = as.Date(date), y = advbw, colour = relay)) +
facet_grid(variable ~ .) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
limits = c(0, NA)) +
scale_colour_hue(name = "n", breaks = levels(t$relay)) +
- ggtitle("Advertised bandwidth of n-th fastest relays")
+ ggtitle("Advertised bandwidth of n-th fastest relays") +
+ labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -933,10 +944,11 @@ plot_hidserv_dir_onions_seen <- function(start, end, path) {
wiqm = ifelse(h$frac >= 0.01, h$wiqm, NA)))
ggplot(h, aes(x = as.Date(date, origin = "1970-01-01"), y = wiqm)) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "") +
- ggtitle("Unique .onion addresses")
+ ggtitle("Unique .onion addresses") +
+ labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -951,10 +963,11 @@ plot_hidserv_rend_relayed_cells <- function(start, end, path) {
ggplot(h, aes(x = as.Date(date, origin = "1970-01-01"),
y = wiqm * 8 * 512 / (86400 * 1e6))) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "") +
- ggtitle("Onion-service traffic in Mbit/s")
+ ggtitle("Onion-service traffic in Mbit/s") +
+ labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -971,7 +984,7 @@ plot_hidserv_frac_reporting <- function(start, end, path) {
colour = type)) +
geom_line() +
geom_hline(yintercept = 0.01, linetype = 2) +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = percent) +
scale_colour_hue(name = "",
@@ -980,6 +993,7 @@ plot_hidserv_frac_reporting <- function(start, end, path) {
"Unique .onion addresses")) +
ggtitle(paste("Fraction of relays reporting onion-service",
"statistics")) +
+ labs(caption = copyright_notice) +
theme(legend.position = "top")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -998,12 +1012,13 @@ plot_webstats_tb <- function(start, end, path) {
geom_point() +
geom_line() +
facet_grid(request_type ~ ., scales = "free_y") +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
theme(strip.text.y = element_text(angle = 0, hjust = 0, size = rel(1.5)),
strip.background = element_rect(fill = NA)) +
- ggtitle("Tor Browser downloads and updates")
+ ggtitle("Tor Browser downloads and updates") +
+ labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -1016,7 +1031,7 @@ plot_webstats_tb_platform <- function(start, end, path) {
ggplot(d, aes(x = log_date, y = count, colour = platform)) +
geom_point() +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
scale_colour_hue(name = "Platform",
@@ -1024,7 +1039,8 @@ plot_webstats_tb_platform <- function(start, end, path) {
labels = c("Windows", "macOS", "Linux", "Other", "Unknown")) +
theme(strip.text.y = element_text(angle = 0, hjust = 0, size = rel(1.5)),
strip.background = element_rect(fill = NA)) +
- ggtitle("Tor Browser downloads by platform")
+ ggtitle("Tor Browser downloads by platform") +
+ labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -1041,7 +1057,7 @@ plot_webstats_tb_locale <- function(start, end, path) {
ggplot(d, aes(x = log_date, y = count, colour = locale)) +
geom_point() +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
scale_colour_hue(name = "Locale",
@@ -1049,7 +1065,8 @@ plot_webstats_tb_locale <- function(start, end, path) {
labels = c(e$locale, "Other")) +
theme(strip.text.y = element_text(angle = 0, hjust = 0, size = rel(1.5)),
strip.background = element_rect(fill = NA)) +
- ggtitle("Tor Browser downloads by locale")
+ ggtitle("Tor Browser downloads by locale") +
+ labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -1065,12 +1082,13 @@ plot_webstats_tm <- function(start, end, path) {
geom_point() +
geom_line() +
facet_grid(request_type ~ ., scales = "free_y") +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
theme(strip.text.y = element_text(angle = 0, hjust = 0, size = rel(1.5)),
strip.background = element_rect(fill = NA)) +
- ggtitle("Tor Messenger downloads and updates")
+ ggtitle("Tor Messenger downloads and updates") +
+ labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -1093,7 +1111,7 @@ plot_relays_ipv6 <- function(start, end, path) {
value = "count") %>%
ggplot(aes(x = valid_after_date, y = count, colour = category)) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_colour_hue(name = "", h.start = 90,
@@ -1101,6 +1119,7 @@ plot_relays_ipv6 <- function(start, end, path) {
labels = c("Total (IPv4) OR", "IPv6 announced OR", "IPv6 reachable OR",
"IPv6 exiting")) +
ggtitle("Relays by IP version") +
+ labs(caption = copyright_notice) +
theme(legend.position = "top")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -1121,13 +1140,14 @@ plot_bridges_ipv6 <- function(start, end, path) {
gather(total, announced, key = "category", value = "count") %>%
ggplot(aes(x = valid_after_date, y = count, colour = category)) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", limits = c(0, NA)) +
scale_colour_hue(name = "", h.start = 90,
breaks = c("total", "announced"),
labels = c("Total (IPv4) OR", "IPv6 announced OR")) +
ggtitle("Bridges by IP version") +
+ labs(caption = copyright_notice) +
theme(legend.position = "top")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -1157,7 +1177,7 @@ plot_advbw_ipv6 <- function(start, end, path) {
ggplot(aes(x = valid_after_date, y = (count * 8) / 1e9,
colour = category)) +
geom_line() +
- scale_x_date(name = copyright_notice, breaks = custom_breaks,
+ scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
limits = c(0, NA)) +
@@ -1167,6 +1187,7 @@ plot_advbw_ipv6 <- function(start, end, path) {
labels = c("Total (IPv4) OR", "Guard total (IPv4)", "Exit total (IPv4)",
"Reachable guard IPv6 OR", "Reachable exit IPv6 OR", "IPv6 exiting")) +
ggtitle("Advertised bandwidth by IP version") +
+ labs(caption = copyright_notice) +
theme(legend.position = "top") +
guides(colour = guide_legend(nrow = 2, byrow = TRUE))
ggsave(filename = path, width = 8, height = 5, dpi = 150)
1
0
commit 47427b9d4fecbdeb0b22e6c39a2aa2656e3d14c1
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Tue Feb 13 14:36:11 2018 +0100
Stop using y axis labels.
In most, if not all, cases it's obvious from the graph title what
we're plotting on the y axis. The main reason why we had a y axis name
was to include the unit. But we can as well add the unit to the
labels, similar to what we're already doing with percent. This saves
more room for the plot area, and it's easier to read.
---
src/main/R/rserver/graphs.R | 40 +++++++++++++++++++---------------------
1 file changed, 19 insertions(+), 21 deletions(-)
diff --git a/src/main/R/rserver/graphs.R b/src/main/R/rserver/graphs.R
index a597746..fffe7c4 100644
--- a/src/main/R/rserver/graphs.R
+++ b/src/main/R/rserver/graphs.R
@@ -320,10 +320,7 @@ theme_update(
# Leave some room between plot and x axis label, which we use for the
# copyright notice.
- axis.title.x = element_text(margin = margin(t = 11)),
-
- # Leave some room between plot and y axis label.
- axis.title.y = element_text(margin = margin(r = 11))
+ axis.title.x = element_text(margin = margin(t = 11))
)
# Set the default line size of geom_line() to 1.
@@ -429,7 +426,8 @@ plot_bandwidth <- function(start, end, path) {
geom_line() +
scale_x_date(name = copyright_notice, breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "Bandwidth (Gbit/s)", limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
+ limits = c(0, NA)) +
scale_colour_hue(name = "", h.start = 90,
breaks = c("bwadv", "bwhist"),
labels = c("Advertised bandwidth", "Bandwidth history")) +
@@ -468,7 +466,8 @@ plot_bwhist_flags <- function(start, end, path) {
geom_line() +
scale_x_date(name = copyright_notice, breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name="Bandwidth (Gbit/s)", limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
+ limits = c(0, NA)) +
scale_colour_manual(name = "",
values = c("#E69F00", "#56B4E9", "#009E73", "#0072B2")) +
ggtitle("Bandwidth history by relay flags") +
@@ -489,7 +488,8 @@ plot_dirbytes <- function(start, end, path) {
geom_line() +
scale_x_date(name = copyright_notice, breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name="Bandwidth (Gbit/s)", limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
+ limits = c(0, NA)) +
scale_colour_hue(name = "",
breaks = c("dirwrite", "dirread"),
labels = c("Written dir bytes", "Read dir bytes")) +
@@ -698,7 +698,8 @@ plot_bandwidth_flags <- function(start, end, path) {
geom_line() +
scale_x_date(name = copyright_notice, breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name="Bandwidth (Gbit/s)", limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
+ limits = c(0, NA)) +
scale_colour_manual(name = "",
values = c("#E69F00", "#D6C827", "#009E73", "#00C34F")) +
ggtitle(paste("Advertised bandwidth and bandwidth history by",
@@ -895,8 +896,8 @@ plot_advbwdist_perc <- function(start, end, p, path) {
geom_line() +
scale_x_date(name = copyright_notice, breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "Advertised bandwidth in Gbit/s",
- limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
+ limits = c(0, NA)) +
scale_colour_hue(name = "Percentile",
breaks = rev(levels(t$percentile))) +
ggtitle("Advertised bandwidth distribution")
@@ -916,8 +917,8 @@ plot_advbwdist_relay <- function(start, end, n, path) {
geom_line() +
scale_x_date(name = copyright_notice, breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "Advertised bandwidth in Gbit/s",
- limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
+ limits = c(0, NA)) +
scale_colour_hue(name = "n", breaks = levels(t$relay)) +
ggtitle("Advertised bandwidth of n-th fastest relays")
ggsave(filename = path, width = 8, height = 5, dpi = 150)
@@ -999,8 +1000,7 @@ plot_webstats_tb <- function(start, end, path) {
facet_grid(request_type ~ ., scales = "free_y") +
scale_x_date(name = copyright_notice, breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = 'Requests per day', labels = formatter,
- limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
theme(strip.text.y = element_text(angle = 0, hjust = 0, size = rel(1.5)),
strip.background = element_rect(fill = NA)) +
ggtitle("Tor Browser downloads and updates")
@@ -1018,8 +1018,7 @@ plot_webstats_tb_platform <- function(start, end, path) {
geom_line() +
scale_x_date(name = copyright_notice, breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = 'Requests per day', labels = formatter,
- limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
scale_colour_hue(name = "Platform",
breaks = c("w", "m", "l", "o", ""),
labels = c("Windows", "macOS", "Linux", "Other", "Unknown")) +
@@ -1044,8 +1043,7 @@ plot_webstats_tb_locale <- function(start, end, path) {
geom_line() +
scale_x_date(name = copyright_notice, breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = 'Requests per day', labels = formatter,
- limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
scale_colour_hue(name = "Locale",
breaks = c(e$locale, "(other)"),
labels = c(e$locale, "Other")) +
@@ -1069,8 +1067,7 @@ plot_webstats_tm <- function(start, end, path) {
facet_grid(request_type ~ ., scales = "free_y") +
scale_x_date(name = copyright_notice, breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = 'Requests per day', labels = formatter,
- limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
theme(strip.text.y = element_text(angle = 0, hjust = 0, size = rel(1.5)),
strip.background = element_rect(fill = NA)) +
ggtitle("Tor Messenger downloads and updates")
@@ -1162,7 +1159,8 @@ plot_advbw_ipv6 <- function(start, end, path) {
geom_line() +
scale_x_date(name = copyright_notice, breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "Bandwidth (Gbit/s)", limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
+ limits = c(0, NA)) +
scale_colour_hue(name = "", h.start = 90,
breaks = c("total", "total_guard", "total_exit", "reachable_guard",
"reachable_exit", "exiting"),
1
0

[metrics-web/release] Remove unused code for plotting relays by country.
by karsten@torproject.org 30 May '18
by karsten@torproject.org 30 May '18
30 May '18
commit e797903fb93b70c675d2b1655c9d333a713f6bfb
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Fri Feb 9 17:57:26 2018 +0100
Remove unused code for plotting relays by country.
---
src/main/R/rserver/graphs.R | 35 -----------------------------------
1 file changed, 35 deletions(-)
diff --git a/src/main/R/rserver/graphs.R b/src/main/R/rserver/graphs.R
index e24a617..2a055c2 100644
--- a/src/main/R/rserver/graphs.R
+++ b/src/main/R/rserver/graphs.R
@@ -311,41 +311,6 @@ plot_networksize <- function(start, end, path) {
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
-plot_relaycountries <- function(start, end, country, path) {
- end <- min(end, as.character(Sys.Date() - 2))
- s <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
- "servers.csv", sep = ""), stringsAsFactors = FALSE)
- s <- s[s$date >= start & s$date <= end & s$flag == '' &
- s$country == ifelse(country == "all", '', country) &
- s$version == '' & s$platform == '' & s$ec2bridge == '', ]
- s <- data.frame(date = as.Date(s$date, "%Y-%m-%d"), relays = s$relays)
- dates <- seq(from = as.Date(start, "%Y-%m-%d"),
- to = as.Date(end, "%Y-%m-%d"), by="1 day")
- missing <- setdiff(dates, s$date)
- if (length(missing) > 0)
- s <- rbind(s,
- data.frame(date = as.Date(missing, origin = "1970-01-01"),
- relays = NA))
- title <- ifelse(country == "all",
- "Number of relays in all countries\n",
- paste("Number of relays in ", countryname(country), "\n", sep = ""))
- formatter <- function(x, ...) { format(x, scientific = FALSE, ...) }
- date_breaks <- date_breaks(
- as.numeric(max(as.Date(s$date, "%Y-%m-%d")) -
- min(as.Date(s$date, "%Y-%m-%d"))))
- ggplot(s, aes(x = as.Date(date, "%Y-%m-%d"), y = relays)) +
- geom_line(size = 1) +
- scale_x_date(name = paste("\nThe Tor Project - ",
- "https://metrics.torproject.org/", sep = ""),
- labels = date_format(date_breaks$format),
- date_breaks = date_breaks$major,
- date_minor_breaks = date_breaks$minor) +
- scale_y_continuous(name = "", limits = c(0, max(s$relays,
- na.rm = TRUE)), formatter = formatter) +
- ggtitle(title)
- ggsave(filename = path, width = 8, height = 5, dpi = 150)
-}
-
plot_versions <- function(start, end, path) {
end <- min(end, as.character(Sys.Date() - 2))
s <- read.csv(paste("/srv/metrics.torproject.org/metrics/shared/stats/",
1
0
commit 0021881b15972010675be6176c40160b19fa7a91
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Tue Feb 13 15:04:52 2018 +0100
Tweak y scales some more.
---
src/main/R/rserver/graphs.R | 32 +++++++++++++++++---------------
1 file changed, 17 insertions(+), 15 deletions(-)
diff --git a/src/main/R/rserver/graphs.R b/src/main/R/rserver/graphs.R
index 04f1b10..c7ef2bd 100644
--- a/src/main/R/rserver/graphs.R
+++ b/src/main/R/rserver/graphs.R
@@ -348,7 +348,7 @@ plot_networksize <- function(start, end, path) {
colour = variable)) + geom_line() +
scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "", limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
scale_colour_hue("", breaks = c("relays", "bridges"),
labels = c("Relays", "Bridges")) +
ggtitle("Number of relays") +
@@ -379,7 +379,7 @@ plot_versions <- function(start, end, path) {
geom_line() +
scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "", limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
scale_colour_manual(name = "Tor version",
values = colours[colours$breaks %in% visible_versions, 2],
breaks = visible_versions) +
@@ -401,7 +401,7 @@ plot_platforms <- function(start, end, path) {
geom_line() +
scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "", limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
scale_colour_manual(name = "Platform",
breaks = c("Linux", "Darwin", "BSD", "Windows", "Other"),
labels = c("Linux", "macOS", "BSD", "Windows", "Other"),
@@ -525,7 +525,7 @@ plot_relayflags <- function(start, end, flags, path) {
colour = as.factor(variable))) + geom_line() +
scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "", limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
scale_colour_manual(name = "Relay flags", values = c("#E69F00",
"#56B4E9", "#009E73", "#EE6A50", "#000000", "#0072B2"),
breaks = flags, labels = flags) +
@@ -566,13 +566,14 @@ plot_torperf <- function(start, end, source, server, filesize, path) {
ymax = q3/1e3, fill = "ribbon")) +
scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "", limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = unit_format(unit = "s"),
+ limits = c(0, NA)) +
scale_fill_manual(name = paste("Measured times on",
ifelse(source == "all", "all sources", source), "per day"),
breaks = c("line", "ribbon"),
labels = c("Median", "1st to 3rd quartile"),
values = paste(colour, c("", "66"), sep = "")) +
- ggtitle(paste("Time in seconds to complete", filesizeStr,
+ ggtitle(paste("Time to complete", filesizeStr,
"request to", server, "server")) +
labs(caption = copyright_notice) +
theme(legend.position = "top")
@@ -613,7 +614,7 @@ plot_torperf_failures <- function(start, end, source, server, filesize, path) {
geom_point(size = 2) +
scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "", labels = percent) +
+ scale_y_continuous(name = "", labels = percent, limits = c(0, NA)) +
scale_colour_hue(name = paste("Problems encountered on",
ifelse(source == "all", "all sources", source)),
h.start = 45, breaks = c("timeouts", "failures"),
@@ -641,7 +642,7 @@ plot_connbidirect <- function(start, end, path) {
fill = direction), alpha = 0.5, show_guide = FALSE) +
scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "", labels = percent) +
+ scale_y_continuous(name = "", labels = percent, limits = c(0, NA)) +
scale_colour_hue(name = "Medians and interquartile ranges",
breaks = c("both", "write", "read"),
labels = c("Both reading and writing", "Mostly writing",
@@ -946,7 +947,7 @@ plot_hidserv_dir_onions_seen <- function(start, end, path) {
geom_line() +
scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "") +
+ scale_y_continuous(name = "", limits = c(0, NA), labels = formatter) +
ggtitle("Unique .onion addresses") +
labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
@@ -961,12 +962,13 @@ plot_hidserv_rend_relayed_cells <- function(start, end, path) {
data.frame(date = as.Date(h$date, "%Y-%m-%d"),
wiqm = ifelse(h$frac >= 0.01, h$wiqm, NA)))
ggplot(h, aes(x = as.Date(date, origin = "1970-01-01"),
- y = wiqm * 8 * 512 / (86400 * 1e6))) +
+ y = wiqm * 8 * 512 / (86400 * 1e9))) +
geom_line() +
scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "") +
- ggtitle("Onion-service traffic in Mbit/s") +
+ scale_y_continuous(name = "", labels = unit_format(unit = "Gbit/s"),
+ limits = c(0, NA)) +
+ ggtitle("Onion-service traffic") +
labs(caption = copyright_notice)
ggsave(filename = path, width = 8, height = 5, dpi = 150)
}
@@ -986,7 +988,7 @@ plot_hidserv_frac_reporting <- function(start, end, path) {
geom_hline(yintercept = 0.01, linetype = 2) +
scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "", labels = percent) +
+ scale_y_continuous(name = "", labels = percent, limits = c(0, NA)) +
scale_colour_hue(name = "",
breaks = c("rend-relayed-cells", "dir-onions-seen"),
labels = c("Onion-service traffic",
@@ -1113,7 +1115,7 @@ plot_relays_ipv6 <- function(start, end, path) {
geom_line() +
scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "", limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
scale_colour_hue(name = "", h.start = 90,
breaks = c("total", "announced", "reachable", "exiting"),
labels = c("Total (IPv4) OR", "IPv6 announced OR", "IPv6 reachable OR",
@@ -1142,7 +1144,7 @@ plot_bridges_ipv6 <- function(start, end, path) {
geom_line() +
scale_x_date(name = "", breaks = custom_breaks,
labels = custom_labels, minor_breaks = custom_minor_breaks) +
- scale_y_continuous(name = "", limits = c(0, NA)) +
+ scale_y_continuous(name = "", labels = formatter, limits = c(0, NA)) +
scale_colour_hue(name = "", h.start = 90,
breaks = c("total", "announced"),
labels = c("Total (IPv4) OR", "IPv6 announced OR")) +
1
0

[metrics-web/release] Add lost submodule and force its proper checkout.
by karsten@torproject.org 30 May '18
by karsten@torproject.org 30 May '18
30 May '18
commit 1fd732b4046f01cedbf9adeba0ded82a334421c2
Author: iwakeh <iwakeh(a)torproject.org>
Date: Fri Feb 9 21:01:00 2018 +0000
Add lost submodule and force its proper checkout.
---
src/main/resources/bootstrap-development.sh | 1 +
src/submods/metrics-lib | 1 +
2 files changed, 2 insertions(+)
diff --git a/src/main/resources/bootstrap-development.sh b/src/main/resources/bootstrap-development.sh
index d301e25..407bdea 100755
--- a/src/main/resources/bootstrap-development.sh
+++ b/src/main/resources/bootstrap-development.sh
@@ -7,4 +7,5 @@
# Only necessary after very first checkout without recursion.
#
git submodule update --init --remote
+cd src/submods/metrics-lib/ && ./src/main/resources/bootstrap-development.sh
diff --git a/src/submods/metrics-lib b/src/submods/metrics-lib
new file mode 160000
index 0000000..9f2db9a
--- /dev/null
+++ b/src/submods/metrics-lib
@@ -0,0 +1 @@
+Subproject commit 9f2db9a194d5e290b456765f83cc6eeaf745e619
1
0

[metrics-web/release] Fixes getPosition method in boostrap.min.js
by karsten@torproject.org 30 May '18
by karsten@torproject.org 30 May '18
30 May '18
commit a5c220aa3a341f8ac4acf24274d8e96d9b1c012e
Author: Iain R. Learmonth <irl(a)fsfe.org>
Date: Wed Feb 14 16:42:35 2018 +0000
Fixes getPosition method in boostrap.min.js
This method was broken between 3.3.5 and 3.3.7. As boostrap 3.x.x is EOL
this is never going to get fixed. As a workaround, replace the
getPosition method with the one from 3.3.5.
Fixes: #25254
---
src/main/resources/web/js/bootstrap.min.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/resources/web/js/bootstrap.min.js b/src/main/resources/web/js/bootstrap.min.js
index 9bcd2fc..9c4b730 100644
--- a/src/main/resources/web/js/bootstrap.min.js
+++ b/src/main/resources/web/js/bootstrap.min.js
@@ -3,5 +3,5 @@
* Copyright 2011-2016 Twitter, Inc.
* Licensed under the MIT license
*/
-if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b
.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c
,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeCla
ss("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"us
e strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();b
reak;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),thi
s.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offs
etWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);
b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&
&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.b
s.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expande
d",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}funct
ion d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){v
ar e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",c).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",".dropdown-menu",g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?
f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dial
og.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in"),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.m
odal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){document===a.target||this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})
},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a(document.createElement("div")).addClass("modal-backdrop "+e).appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransiti
onEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding
-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.tooltip",e=new c(this,f)),"
string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.inState=null,this.init("tooltip",a,b)};c.VERSION="3.3.7",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+
this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){v
ar c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.option
s.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0
].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-m<o.top?"bottom":"right"==h&&k.right+l>o.width?"left":"left"==h&&k.left-l<o.left?"right":h,f.removeClass(n).addClass(h)}var p=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(p,h);var q=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",q).emulateTransitionEnd(c.TRANSITION_DURATION):q()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top+=g,b.left+=h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewport
AdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element&&e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);if(this.$element.trigger(g),!g.isDefaultPrevented())return f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"s
tring"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=window.SVGElement&&c instanceof window.SVGElement,g=d?{top:0,left:0}:f?null:b.offset(),h={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},i=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,h,i,g)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)ret
urn e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){th
is.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}v
ar c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=
function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollH
eight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.
clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){
-this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.7",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("ta
rget");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&
&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkP
osition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e<c&&"top";if("bottom"==this.affixed)return null!=c?!(e+this.unpin<=f.top)&&"bottom":!(e+g<=a-d)&&"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&e<=c?"top":null!=d&&i+j>=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"o
bject"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
\ No newline at end of file
+if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b
.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c
,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeCla
ss("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"us
e strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();b
reak;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),thi
s.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offs
etWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);
b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&
&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.b
s.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expande
d",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}funct
ion d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){v
ar e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",c).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",".dropdown-menu",g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?
f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dial
og.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in"),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.m
odal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){document===a.target||this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})
},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a(document.createElement("div")).addClass("modal-backdrop "+e).appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransiti
onEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding
-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.tooltip",e=new c(this,f)),"
string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.inState=null,this.init("tooltip",a,b)};c.VERSION="3.3.7",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+
this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){v
ar c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.option
s.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0
].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-m<o.top?"bottom":"right"==h&&k.right+l>o.width?"left":"left"==h&&k.left-l<o.left?"right":h,f.removeClass(n).addClass(h)}var p=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(p,h);var q=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",q).emulateTransitionEnd(c.TRANSITION_DURATION):q()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top+=g,b.left+=h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewport
AdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element&&e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);if(this.$element.trigger(g),!g.isDefaultPrevented())return f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"s
tring"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.pa
dding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.en
abled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.toolt
ip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr
("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight
||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a
+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){
+this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.7",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("ta
rget");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&
&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkP
osition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e<c&&"top";if("bottom"==this.affixed)return null!=c?!(e+this.unpin<=f.top)&&"bottom":!(e+g<=a-d)&&"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&e<=c?"top":null!=d&&i+j>=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"o
bject"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
1
0

[metrics-web/release] Use complete() rather than merge() for missing dates.
by karsten@torproject.org 30 May '18
by karsten@torproject.org 30 May '18
30 May '18
commit 519512c85d814985caffbbda93db00c5dd32b41f
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Tue Feb 13 15:19:21 2018 +0100
Use complete() rather than merge() for missing dates.
The result is the same, but the code is much easier to read.
---
src/main/R/rserver/graphs.R | 36 ++++++++++++------------------------
1 file changed, 12 insertions(+), 24 deletions(-)
diff --git a/src/main/R/rserver/graphs.R b/src/main/R/rserver/graphs.R
index c7ef2bd..ab1a60d 100644
--- a/src/main/R/rserver/graphs.R
+++ b/src/main/R/rserver/graphs.R
@@ -1095,20 +1095,16 @@ plot_webstats_tm <- function(start, end, path) {
}
plot_relays_ipv6 <- function(start, end, path) {
- all_relay_data <- read.csv(paste(stats_dir, "ipv6servers.csv", sep = ""),
+ read.csv(paste(stats_dir, "ipv6servers.csv", sep = ""),
colClasses = c("valid_after_date" = "Date")) %>%
- filter(server == "relay")
- start_date <- max(as.Date(start), min(all_relay_data$valid_after_date))
- end_date <- min(as.Date(end), max(all_relay_data$valid_after_date))
- all_relay_data %>%
- filter(valid_after_date >= start_date, valid_after_date <= end_date) %>%
+ filter(valid_after_date >= as.Date(start),
+ valid_after_date <= as.Date(end), server == "relay") %>%
group_by(valid_after_date) %>%
summarize(total = sum(server_count_sum_avg),
announced = sum(server_count_sum_avg[announced_ipv6 == 't']),
reachable = sum(server_count_sum_avg[reachable_ipv6_relay == 't']),
exiting = sum(server_count_sum_avg[exiting_ipv6_relay == 't'])) %>%
- merge(data.frame(valid_after_date = seq(start_date, end_date,
- by = "1 day")), all = TRUE) %>%
+ complete(valid_after_date = full_seq(valid_after_date, period = 1)) %>%
gather(total, announced, reachable, exiting, key = "category",
value = "count") %>%
ggplot(aes(x = valid_after_date, y = count, colour = category)) +
@@ -1127,18 +1123,14 @@ plot_relays_ipv6 <- function(start, end, path) {
}
plot_bridges_ipv6 <- function(start, end, path) {
- all_bridge_data <- read.csv(paste(stats_dir, "ipv6servers.csv", sep = ""),
+ read.csv(paste(stats_dir, "ipv6servers.csv", sep = ""),
colClasses = c("valid_after_date" = "Date")) %>%
- filter(server == "bridge")
- start_date <- max(as.Date(start), min(all_bridge_data$valid_after_date))
- end_date <- min(as.Date(end), max(all_bridge_data$valid_after_date))
- all_bridge_data %>%
- filter(valid_after_date >= start_date, valid_after_date <= end_date) %>%
+ filter(valid_after_date >= as.Date(start),
+ valid_after_date <= as.Date(end), server == "bridge") %>%
group_by(valid_after_date) %>%
summarize(total = sum(server_count_sum_avg),
announced = sum(server_count_sum_avg[announced_ipv6 == 't'])) %>%
- merge(data.frame(valid_after_date = seq(start_date, end_date,
- by = "1 day")), all = TRUE) %>%
+ complete(valid_after_date = full_seq(valid_after_date, period = 1)) %>%
gather(total, announced, key = "category", value = "count") %>%
ggplot(aes(x = valid_after_date, y = count, colour = category)) +
geom_line() +
@@ -1155,13 +1147,10 @@ plot_bridges_ipv6 <- function(start, end, path) {
}
plot_advbw_ipv6 <- function(start, end, path) {
- all_relay_data <- read.csv(paste(stats_dir, "ipv6servers.csv", sep = ""),
+ read.csv(paste(stats_dir, "ipv6servers.csv", sep = ""),
colClasses = c("valid_after_date" = "Date")) %>%
- filter(server == "relay")
- start_date <- max(as.Date(start), min(all_relay_data$valid_after_date))
- end_date <- min(as.Date(end), max(all_relay_data$valid_after_date))
- all_relay_data %>%
- filter(valid_after_date >= start_date, valid_after_date <= end_date) %>%
+ filter(valid_after_date >= as.Date(start),
+ valid_after_date <= as.Date(end), server == "relay") %>%
group_by(valid_after_date) %>%
summarize(total = sum(advertised_bandwidth_bytes_sum_avg),
total_guard = sum(advertised_bandwidth_bytes_sum_avg[guard_relay != 'f']),
@@ -1172,8 +1161,7 @@ plot_advbw_ipv6 <- function(start, end, path) {
reachable_ipv6_relay != 'f' & exit_relay != 'f']),
exiting = sum(advertised_bandwidth_bytes_sum_avg[
exiting_ipv6_relay != 'f'])) %>%
- merge(data.frame(valid_after_date = seq(start_date, end_date,
- by = "1 day")), all = TRUE) %>%
+ complete(valid_after_date = full_seq(valid_after_date, period = 1)) %>%
gather(total, total_guard, total_exit, reachable_guard, reachable_exit,
exiting, key = "category", value = "count") %>%
ggplot(aes(x = valid_after_date, y = (count * 8) / 1e9,
1
0

30 May '18
commit ab7d546a9dae35efdfc2c1f8c4a09e473df72747
Author: iwakeh <iwakeh(a)torproject.org>
Date: Tue Feb 13 19:53:15 2018 +0000
Tune R processing in advbwdist module.
Processing advbwdist-validafter.csv (350M) took 150 seconds and used up to 7G.
Performing pre-processing separately, helping R by defining read types, and
avoiding multiple casting operations led to halving the processing time
(to 77 seconds) and reducing the necessary memory to about 25% (approx. 1.8G).
The resulting advbwdist.csv are identical.
Avoid casting to 'Date' and make implicit cast explicit. This saves reliably
10 seconds processing time and reduces used memory to less than 1.65G.
Total: processing time down to 44% and memory consumption down to 24%.
Also indent source code for readability.
---
src/main/R/advbwdist/aggregate.R | 35 ++++++++++++++++++++++-------------
1 file changed, 22 insertions(+), 13 deletions(-)
diff --git a/src/main/R/advbwdist/aggregate.R b/src/main/R/advbwdist/aggregate.R
index ee52a64..1c67dff 100644
--- a/src/main/R/advbwdist/aggregate.R
+++ b/src/main/R/advbwdist/aggregate.R
@@ -1,16 +1,25 @@
require(reshape)
-t <- read.csv("stats/advbwdist-validafter.csv", stringsAsFactors = FALSE)
-t <- t[t$valid_after < paste(Sys.Date() - 1, "23:59:59"), ]
-t <- aggregate(list(advbw = as.numeric(t$advbw)),
- by = list(date = as.Date(cut.Date(as.Date(t$valid_after), "day")),
- isexit = !is.na(t$isexit), relay = ifelse(is.na(t$relay), -1, t$relay),
- percentile = ifelse(is.na(t$percentile), -1, t$percentile)),
- FUN = median)
-t <- data.frame(date = t$date, isexit = ifelse(t$isexit, "t", ""),
- relay = ifelse(t$relay < 0, NA, t$relay),
- percentile = ifelse(t$percentile < 0, NA, t$percentile),
- advbw = floor(t$advbw))
+t <- read.csv("stats/advbwdist-validafter.csv",
+ colClasses = c("character", "logical", "integer", "integer", "integer"),
+ stringsAsFactors = FALSE)
+
+currSysDate <- paste(Sys.Date() - 1, "23:59:59")
+t <- t[t$valid_after < currSysDate, ]
+t$date <- as.factor(substr(t$valid_after, 1, 10))
+t$isexit <- !is.na(t$isexit)
+t$relay <- ifelse(is.na(t$relay), -1, t$relay)
+t$percentile <- ifelse(is.na(t$percentile), -1, t$percentile)
+
+t <- aggregate(list(advbw = t$advbw), by = list(date = t$date,
+ isexit = t$isexit, relay = t$relay, percentile = t$percentile),
+ FUN = median)
+
+t$isexit <- ifelse(t$isexit, "t", "")
+t$relay <- ifelse(t$relay < 0, NA, t$relay)
+t$percentile <- ifelse(t$percentile < 0, NA, t$percentile)
+t$advbw <- floor(t$advbw)
+
t <- t[order(t$date, t$isexit, t$relay, t$percentile), ]
-write.csv(t, "stats/advbwdist.csv", quote = FALSE, row.names = FALSE,
- na = "")
+
+write.csv(t, "stats/advbwdist.csv", quote = FALSE, row.names = FALSE, na = "")
1
0