[tor-commits] [metrics-web/master] Apply new design to graph pages.

karsten at torproject.org karsten at torproject.org
Mon Jan 9 17:03:42 UTC 2017


commit 5f96e401fa847ae109191722562f8ea5ed341c14
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Wed Dec 21 09:27:07 2016 +0100

    Apply new design to graph pages.
---
 website/etc/metrics.json                           | 356 +--------------------
 .../org/torproject/metrics/web/DataServlet.java    |   1 -
 .../org/torproject/metrics/web/GraphServlet.java   |  23 +-
 .../org/torproject/metrics/web/LinkServlet.java    |  24 +-
 website/src/org/torproject/metrics/web/Metric.java |  18 --
 .../org/torproject/metrics/web/MetricServlet.java  |  28 +-
 .../org/torproject/metrics/web/TableServlet.java   |  18 +-
 website/web/WEB-INF/graph.jsp                      |  96 +++---
 website/web/js/script.js                           |  13 -
 9 files changed, 62 insertions(+), 515 deletions(-)

diff --git a/website/etc/metrics.json b/website/etc/metrics.json
index 1acc17b..6f26a86 100644
--- a/website/etc/metrics.json
+++ b/website/etc/metrics.json
@@ -2,12 +2,7 @@
   {
     "id": "networksize",
     "title": "Relays and bridges in the network",
-    "tags": [
-      "Relays",
-      "Bridges"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the number of running <a href=\"about.html#relay\">relays</a> and <a href=\"about.html#bridge\">bridges</a> in the network.</p>",
     "function": "plot_networksize",
     "parameters": [
@@ -16,21 +11,12 @@
     ],
     "data": [
       "servers-data"
-    ],
-    "related": [
-      "relayflags",
-      "versions",
-      "platforms"
     ]
   },
   {
     "id": "relayflags",
     "title": "Relays with Exit, Fast, Guard, Stable, and HSDir flags",
-    "tags": [
-      "Relays"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the number of running <a href=\"about.html#relay\">relays</a> that have had certain <a href=\"about.html#relay-flag\">flags</a> assigned by the <a href=\"about.html#directory-authority\">directory authorities</a>.  These flags indicate that a relay should be preferred for either guard (\"Guard\") or exit positions (\"Exit\"), that a relay is suitable for high-bandwidth (\"Fast\") or long-lived circuits (\"Stable\"), or that a relay is considered a hidden service directory (\"HSDir\").</p>",
     "function": "plot_relayflags",
     "parameters": [
@@ -40,22 +26,12 @@
     ],
     "data": [
       "servers-data"
-    ],
-    "related": [
-      "networksize",
-      "versions",
-      "platforms"
     ]
   },
   {
     "id": "versions",
     "title": "Relays by version",
-    "tags": [
-      "Relays",
-      "Diversity"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the number of running <a href=\"about.html#relay\">relays</a> by tor software version.  Relays report their tor software version when they announce themselves in the network.  More details on when these versions were declared stable or unstable can be found on the <a href=\"https://www.torproject.org/download/download.html\">download page</a> and in the <a href=\"https://gitweb.torproject.org/tor.git/tree/ChangeLog\">changes file</a>.</p>",
     "function": "plot_versions",
     "parameters": [
@@ -64,22 +40,12 @@
     ],
     "data": [
       "servers-data"
-    ],
-    "related": [
-      "networksize",
-      "relayflags",
-      "platforms"
     ]
   },
   {
     "id": "platforms",
     "title": "Relays by platform",
-    "tags": [
-      "Relays",
-      "Diversity"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the number of running <a href=\"about.html#relay\">relays</a> by operating system.  Relays report their operating system when they announce themselves in the network.</p>",
     "function": "plot_platforms",
     "parameters": [
@@ -88,23 +54,12 @@
     ],
     "data": [
       "servers-data"
-    ],
-    "related": [
-      "networksize",
-      "relayflags",
-      "versions"
     ]
   },
   {
     "id": "servers-data",
     "title": "Number of relays and bridges",
-    "tags": [
-      "Relays",
-      "Bridges",
-      "Diversity"
-    ],
     "type": "Data",
-    "level": "Advanced",
     "description": "<p>The following data file contains the number of running <a href=\"about.html#relay\">relays</a> and <a href=\"about.html#bridge\">bridges</a> in the network.  Statistics include subsets of relays or bridges by <a href=\"about.html#relay-flag\">relay flag</a> (relays only), country code (relays only, and only until February 2013), tor software version (relays only), operating system (relays only), and by whether or not they are running in the EC2 cloud (bridges only).  The data file contains daily (mean) averages of relay and bridge numbers.</p>",
     "data_file": "stats/servers.csv",
     "data_column_spec": [
@@ -116,23 +71,12 @@
       "<b>ec2bridge:</b> Whether bridges are running in the EC2 cloud or not.  More precisely, bridges in the EC2 cloud running an image provided by Tor by default set their nickname to <b>\"ec2bridger\"</b> plus 8 random hex characters.  This column either contains <b>\"t\"</b> for bridges matching this naming scheme, or the empty string for all bridges regardless of their nickname.  There are no statistics on the number of relays running in the EC2 cloud.",
       "<b>relays:</b> The average number of relays matching the criteria in the previous columns.  If the values in previous columns are specific to bridges only, this column contains the empty string.",
       "<b>bridges:</b> The average number of bridges matching the criteria in the previous columns.  If the values in previous columns are specific to relays only, this column contains the empty string."
-    ],
-    "related": [
-      "networksize",
-      "relayflags",
-      "versions",
-      "platforms"
     ]
   },
   {
     "id": "bandwidth",
     "title": "Total relay bandwidth in the network",
-    "tags": [
-      "Relays",
-      "Bandwidth"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the total <a href=\"about.html#advertised-bandwidth\">advertised</a> and <a href=\"about.html#bandwidth-history\">consumed bandwidth</a> of all <a href=\"about.html#relay\">relays</a> in the network.</p>",
     "function": "plot_bandwidth",
     "parameters": [
@@ -141,22 +85,12 @@
     ],
     "data": [
       "bandwidth-data"
-    ],
-    "related": [
-      "bwhist-flags",
-      "bandwidth-flags",
-      "dirbytes"
     ]
   },
   {
     "id": "bwhist-flags",
     "title": "Relay bandwidth by Exit and/or Guard flags",
-    "tags": [
-      "Relays",
-      "Bandwidth"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the <a href=\"about.html#bandwidth-history\">consumed bandwidth</a> reported by relays, subdivided into four distinct subsets by assigned \"Exit\" and/or \"Guard\" <a href=\"about.html#relay-flag\">flags</a>.</p>",
     "function": "plot_bwhist_flags",
     "parameters": [
@@ -165,23 +99,12 @@
     ],
     "data": [
       "bandwidth-data"
-    ],
-    "related": [
-      "relayflags",
-      "bandwidth",
-      "bandwidth-flags",
-      "dirbytes"
     ]
   },
   {
     "id": "bandwidth-flags",
     "title": "Advertised bandwidth and bandwidth history by relay flags",
-    "tags": [
-      "Relays",
-      "Bandwidth"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows <a href=\"about.html#advertised-bandwidth\">advertised</a> and <a href=\"about.html#bandwidth-history\">consumed bandwidth</a> of relays with either \"Exit\" or \"Guard\" <a href=\"about.html#relay-flag\">flags</a> assigned by the directory authorities.  These sets are not distinct, because a relay that has both the \"Exit\" and \"Guard\" flags assigned will be included in both sets.</p>",
     "function": "plot_bandwidth_flags",
     "parameters": [
@@ -190,23 +113,12 @@
     ],
     "data": [
       "bandwidth-data"
-    ],
-    "related": [
-      "relayflags",
-      "bandwidth",
-      "bwhist-flags",
-      "dirbytes"
     ]
   },
   {
     "id": "dirbytes",
     "title": "Number of bytes spent on answering directory requests",
-    "tags": [
-      "Relays",
-      "Bandwidth"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the portion of <a href=\"about.html#bandwidth-history\">consumed bandwidth</a> that <a href=\"about.html#directory-authority\">directory authorities</a> and <a href=\"about.html#directory-mirror\">mirrors</a> have spent on answering directory requests.  Not all directories report these statistics, so the graph shows an estimation of total consumed bandwidth as it would be observed if all directories reported these statistics.</p>",
     "function": "plot_dirbytes",
     "parameters": [
@@ -215,22 +127,12 @@
     ],
     "data": [
       "bandwidth-data"
-    ],
-    "related": [
-      "bandwidth",
-      "bwhist-flags",
-      "bandwidth-flags"
     ]
   },
   {
     "id": "advbwdist-perc",
     "title": "Advertised bandwidth distribution",
-    "tags": [
-      "Relays",
-      "Bandwidth"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the distribution of the <a href=\"about.html#advertised-bandwidth\">advertised bandwidth</a> of relays in the network.  Each percentile represents the advertised bandwidth that a given percentage of relays does not exceed (and that in turn the remaining relays either match or exceed).  For example, 99% of relays advertise at most the bandwidth value shown in the 99th percentile line (and the remaining 1% advertise at least that amount).</p>",
     "function": "plot_advbwdist_perc",
     "parameters": [
@@ -240,20 +142,12 @@
     ],
     "data": [
       "advbwdist-data"
-    ],
-    "related": [
-      "advbwdist-relay"
     ]
   },
   {
     "id": "advbwdist-relay",
     "title": "Advertised bandwidth of n-th fastest relays",
-    "tags": [
-      "Relays",
-      "Bandwidth"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the <a href=\"about.html#advertised-bandwidth\">advertised bandwidth</a> of the n-th fastest relays in the network for different values of n.</p>",
     "function": "plot_advbwdist_relay",
     "parameters": [
@@ -263,20 +157,12 @@
     ],
     "data": [
       "advbwdist-data"
-    ],
-    "related": [
-      "advbwdist-perc"
     ]
   },
   {
     "id": "bandwidth-data",
     "title": "Bandwidth provided and consumed by relays",
-    "tags": [
-      "Relays",
-      "Bandwidth"
-    ],
     "type": "Data",
-    "level": "Advanced",
     "description": "<p>The following data file contains statistics on <a href=\"about.html#advertised-bandwidth\">advertised</a> and <a href=\"about.html#bandwidth-history\">consumed bandwidth</a> of <a href=\"about.html#relay\">relays</a> in the network.  Statistics on advertised bandwidth include any kind of traffic handled by a relay, whereas statistics on consumed bandwidth are available either for all traffic combined, or specifically for directory traffic.  Some of the statistics are available for subsets of relays that have the \"Exit\" and/or the \"Guard\" <a href=\"about.html#relay-flag\">flag</a>.  The data file contains daily (mean) averages of bandwidth numbers.</p>",
     "data_file": "stats/bandwidth.csv",
     "data_column_spec": [
@@ -288,24 +174,12 @@
       "<b>bwwrite:</b> Similar to <b>bwread</b>, but for traffic written by relays.",
       "<b>dirread:</b> Bandwidth in bytes per second that relays have read when serving directory data.  Not all relays report how many bytes they read when serving directory data which is why this value is an estimate from the available data.  This metric is not available for subsets of relays with certain relay flags, so that this column will contain the empty string if either <b>isexit</b> or <b>isguard</b> is non-empty.",
       "<b>dirwrite:</b> Similar to <b>dirread</b>, but for traffic written by relays when serving directory data."
-    ],
-    "related": [
-      "bandwidth",
-      "bwhist-flags",
-      "bandwidth-flags",
-      "dirbytes",
-      "bandwidth-data"
     ]
   },
   {
     "id": "advbwdist-data",
     "title": "Advertised bandwidth distribution and n-th fastest relays",
-    "tags": [
-      "Relays",
-      "Bandwidth"
-    ],
     "type": "Data",
-    "level": "Advanced",
     "description": "<p>The following data file contains statistics on the distribution of <a href=\"about.html#advertised-bandwidth\">advertised bandwidth</a> of relays in the network.  These statistics include advertised bandwidth percentiles and advertised bandwidth values of the n-th fastest relays.  All values are obtained from advertised bandwidths of running relays in a <a href=\"about.html#consensus\">network status consensus</a>.  The data file contains daily (median) averages of percentiles and n-th largest values.</p>",
     "data_file": "stats/advbwdist.csv",
     "data_column_spec": [
@@ -314,30 +188,17 @@
       "<b>relay:</b> Position of the relay in an ordered list of all advertised bandwidths, starting at 1 for the fastest relay in the network.  May be the empty string if this line contains advertised bandwidth by percentile.",
       "<b>percentile:</b> Advertised bandwidth percentile given in this line.  May be the empty string if this line contains advertised bandwidth by fastest relays.",
       "<b>advbw:</b> Advertised bandwidth in B/s."
-    ],
-    "related": [
-      "advbwdist-perc",
-      "advbwdist-relay"
     ]
   },
   {
     "id": "bubbles",
     "title": "Network bubble graphs",
-    "tags": [
-      "Relays",
-      "Diversity"
-    ],
-    "type": "Graph",
-    "level": "Basic"
+    "type": "Graph"
   },
   {
     "id": "userstats-relay-country",
     "title": "Direct users by country",
-    "tags": [
-      "Clients"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the estimated number of directly-connecting <a href=\"about.html#client\">clients</a>; that is, it excludes clients connecting via <a href=\"about.html#bridge\">bridges</a>.  These estimates are derived from the number of directory requests counted on <a href=\"about.html#directory-authority\">directory authorities</a> and <a href=\"about.html#directory-mirror\">mirrors</a>.  Relays resolve client IP addresses to country codes, so that graphs are available for most countries.  Furthermore, it is possible to display indications of censorship events as obtained from an anomaly-based censorship-detection system (for more details, see this <a href=\"https://research.torproject.org/techreports/detector-2011-09-09.pdf\">technical report</a>).  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
     "function": "plot_userstats_relay_country",
     "parameters": [
@@ -348,24 +209,12 @@
     ],
     "data": [
       "clients-data"
-    ],
-    "related": [
-      "userstats-relay-table",
-      "userstats-censorship-events",
-      "userstats-bridge-country",
-      "userstats-bridge-table",
-      "userstats-bridge-combined",
-      "oxford-anonymous-internet"
     ]
   },
   {
     "id": "userstats-relay-table",
     "title": "Top-10 countries by directly connecting users",
-    "tags": [
-      "Clients"
-    ],
     "type": "Table",
-    "level": "Basic",
     "description": "<p>The following table shows the top-10 countries by estimated number of directly-connecting <a href=\"about.html#client\">clients</a>.  These numbers are derived from directory requests counted on <a href=\"about.html#directory-authority\">directory authorities</a> and <a href=\"about.html#directory-mirror\">mirrors</a>.  Relays resolve client IP addresses to country codes, so that numbers are available for most countries.  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
     "function": "write_userstats_relay",
     "parameters": [
@@ -382,24 +231,12 @@
     ],
     "data": [
       "clients-data"
-    ],
-    "related": [
-      "userstats-relay-country",
-      "userstats-censorship-events",
-      "userstats-bridge-country",
-      "userstats-bridge-table",
-      "userstats-bridge-combined",
-      "oxford-anonymous-internet"
     ]
   },
   {
     "id": "userstats-censorship-events",
     "title": "Top-10 countries by possible censorship events",
-    "tags": [
-      "Clients"
-    ],
     "type": "Table",
-    "level": "Basic",
     "description": "<p>The following table shows the top-10 countries by possible censorship events, as obtained from an anomaly-based censorship-detection system (for more details, see this <a href=\"https://research.torproject.org/techreports/detector-2011-09-09.pdf\">technical report</a>).  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
     "function": "write_userstats_censorship_events",
     "parameters": [
@@ -418,21 +255,12 @@
     ],
     "data": [
       "clients-data"
-    ],
-    "related": [
-      "userstats-relay-country",
-      "userstats-relay-table",
-      "userstats-censorship-events"
     ]
   },
   {
     "id": "userstats-bridge-country",
     "title": "Bridge users by country",
-    "tags": [
-      "Clients"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the estimated number of <a href=\"about.html#client\">clients</a> connecting via <a href=\"about.html#bridge\">bridges</a>.  These numbers are derived from directory requests counted on bridges.  Bridges resolve client IP addresses of incoming directory requests to country codes, so that graphs are available for most countries.  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
     "function": "plot_userstats_bridge_country",
     "parameters": [
@@ -442,22 +270,12 @@
     ],
     "data": [
       "clients-data"
-    ],
-    "related": [
-      "userstats-relay-country",
-      "userstats-relay-table",
-      "userstats-bridge-table",
-      "userstats-bridge-combined"
     ]
   },
   {
     "id": "userstats-bridge-table",
     "title": "Top-10 countries by bridge users",
-    "tags": [
-      "Clients"
-    ],
     "type": "Table",
-    "level": "Basic",
     "description": "<p>The following table shows the top-10 countries by estimated number of <a href=\"about.html#client\">clients</a> connecting via <a href=\"about.html#bridge\">bridges</a>.  These numbers are derived from directory requests counted on bridges.  Bridges resolve client IP addresses of incoming directory requests to country codes, so that numbers are available for most countries.  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
     "function": "write_userstats_bridge",
     "parameters": [
@@ -474,25 +292,12 @@
     ],
     "data": [
       "clients-data"
-    ],
-    "related": [
-      "userstats-relay-country",
-      "userstats-relay-table",
-      "userstats-bridge-country",
-      "userstats-bridge-transport",
-      "userstats-bridge-combined",
-      "userstats-bridge-version"
     ]
   },
   {
     "id": "userstats-bridge-transport",
     "title": "Bridge users by transport",
-    "tags": [
-      "Clients",
-      "Pluggable transports"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the estimated number of <a href=\"about.html#client\">clients</a> connecting via <a href=\"about.html#bridge\">bridges</a>.  These numbers are derived from directory requests counted on bridges.  Bridges distinguish connecting clients by transport protocol, which may include <a href=\"about.html#pluggable-transport\">pluggable transports</a>, so that graphs are available for different transports.  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
     "function": "plot_userstats_bridge_transport",
     "parameters": [
@@ -502,23 +307,12 @@
     ],
     "data": [
       "clients-data"
-    ],
-    "related": [
-      "userstats-bridge-country",
-      "userstats-bridge-table",
-      "userstats-bridge-combined",
-      "userstats-bridge-version"
     ]
   },
   {
     "id": "userstats-bridge-combined",
     "title": "Bridge users by country and transport",
-    "tags": [
-      "Clients",
-      "Pluggable transports"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the estimated number of <a href=\"about.html#client\">clients</a> connecting via <a href=\"about.html#bridge\">bridges</a>.  These numbers are derived from directory requests counted on bridges.  Bridges resolve client IP addresses of incoming directory requests to country codes, and they distinguish connecting clients by transport protocol, which may include <a href=\"about.html#pluggable-transport\">pluggable transports</a>.  Even though bridges don't report a combination of clients by country and transport, it's possible to derive and graph lower and upper bounds from existing usage statistics.  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
     "function": "plot_userstats_bridge_combined",
     "parameters": [
@@ -528,20 +322,12 @@
     ],
     "data": [
       "userstats-combined-data"
-    ],
-    "related": [
-      "userstats-bridge-country",
-      "userstats-bridge-transport"
     ]
   },
   {
     "id": "userstats-bridge-version",
     "title": "Bridge users by IP version",
-    "tags": [
-      "Clients"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the estimated number of <a href=\"about.html#client\">clients</a> connecting via <a href=\"about.html#bridge\">bridges</a>.  These numbers are derived from directory requests counted on bridges.  Bridges distinguish connecting clients by IP version, so that graphs are available for both IP versions 4 and 6.  For further details see these <a href=\"https://gitweb.torproject.org/metrics-web.git/tree/doc/users-q-and-a.txt\">questions and answers about user statistics</a>.</p>",
     "function": "plot_userstats_bridge_version",
     "parameters": [
@@ -551,38 +337,18 @@
     ],
     "data": [
       "clients-data"
-    ],
-    "related": [
-      "userstats-bridge-country",
-      "userstats-bridge-table",
-      "userstats-bridge-transport",
-      "userstats-bridge-combined"
     ]
   },
   {
     "id": "oxford-anonymous-internet",
     "title": "Tor users as percentage of larger Internet population",
-    "tags": [
-      "Clients"
-    ],
     "type": "Link",
-    "level": "Basic",
-    "description": "<p>The Oxford Internet Institute made a cartogram visualization of Tor users as compared to the overall Internet population.  They used the average number of Tor <a href=\"about.html#client\">users</a> per country from August 2012 to August 2013 and put it in relation to total Internet users per country.  More details and conclusions can be found on the <a href=\"http://geography.oii.ox.ac.uk/?page=tor\">Information Geographies website at the Oxford Internet Institute</a>.</p><p><a href=\"http://geography.oii.ox.ac.uk/?page=tor\"><img src=\"images/oxford-anonymous-internet.png\" alt=\"The anonymous Internet\"></a></p>",
-    "related": [
-      "userstats-relay-country",
-      "userstats-relay-table",
-      "clients-data"
-    ]
+    "description": "<p>The Oxford Internet Institute made a cartogram visualization of Tor users as compared to the overall Internet population.  They used the average number of Tor <a href=\"about.html#client\">users</a> per country from August 2012 to August 2013 and put it in relation to total Internet users per country.  More details and conclusions can be found on the <a href=\"http://geography.oii.ox.ac.uk/?page=tor\">Information Geographies website at the Oxford Internet Institute</a>.</p><p><a href=\"http://geography.oii.ox.ac.uk/?page=tor\"><img src=\"images/oxford-anonymous-internet.png\" alt=\"The anonymous Internet\"></a></p>"
   },
   {
     "id": "clients-data",
     "title": "Estimated number of clients in the Tor network",
-    "tags": [
-      "Clients",
-      "Pluggable transports"
-    ],
     "type": "Data",
-    "level": "Advanced",
     "description": "<p>The following data file contains estimates on the number of <a href=\"about.html#client\">clients</a> in the network.  These numbers are derived from directory requests counted on <a href=\"about.html#directory-authority\">directory authorities</a>, <a href=\"about.html#directory-mirror\">directory mirrors</a>, and <a href=\"about.html#bridge\">bridges</a>.  Statistics are available for clients connecting directly relays and clients connecting via bridges.  There are statistics available by country (for both directly-connecting clients and clients connecting via bridges), by transport protocol (only for clients connecting via bridges), and by IP version (only for clients connecting via bridges).  Statistics also include predicted client numbers from past observations, which can be used to detect censorship events.</p>",
     "data_file": "stats/clients.csv",
     "data_column_spec": [
@@ -595,27 +361,12 @@
       "<b>upper:</b> Upper number of expected clients under the assumption that there has been no release of censorship.  If this column contains the empty string, there are no expectations on the number of clients.",
       "<b>clients:</b> Estimated number of clients.",
       "<b>frac:</b> Fraction of relays or bridges in percent that the estimate is based on.  The higher this value, the more reliable is the estimate.  Values above 50 can be considered reliable enough for most purposes, lower values should be handled with more care."
-    ],
-    "related": [
-      "userstats-relay-country",
-      "userstats-relay-table",
-      "userstats-censorship-events",
-      "userstats-bridge-country",
-      "userstats-bridge-table",
-      "userstats-bridge-transport",
-      "userstats-combined-data",
-      "oxford-anonymous-internet"
     ]
   },
   {
     "id": "userstats-combined-data",
     "title": "Estimated number of clients by country and transport",
-    "tags": [
-      "Clients",
-      "Pluggable transports"
-    ],
     "type": "Data",
-    "level": "Advanced",
     "description": "<p>The following data file contains additional statistics on the number of <a href=\"about.html#client\">clients</a> in the network.  This data file is related to the <a href=\"clients-data.html\">clients-data file</a> that contains estimates on the number of clients by country and by transport protocol.  This data file enhances these statistics by containing estimates of clients connecting to <a href=\"about.html#bridge\">bridges</a> by a given country and using a given <a href=\"about.html#pluggable-transport\">transport protocol</a>.  Even though bridges don't report a combination of clients by country and transport, it's possible to derive lower and upper bounds from existing usage statistics.</p>",
     "data_file": "stats/userstats-combined.csv",
     "data_column_spec": [
@@ -627,20 +378,12 @@
       "<b>frac:</b> Fraction of relays or bridges in percent that the estimate is based on.  The higher this value, the more reliable is the estimate.  Values above 50 can be considered reliable enough for most purposes, lower values should be handled with more care.",
       "<b>low:</b> Lower bound of users by country and transport, calculated as sum over all bridges having reports for the given country and transport, that is, the sum of <b>M(b)</b>, where for each bridge <b>b</b> define <b>M(b) := max(0, C(b) + T(b) - S(b))</b> using the following definitions: <b>C(b)</b> is the number of users from a given country reported by <b>b</b>; <b>T(b)</b> is the number of users using a given transport reported by <b>b</b>; and <b>S(b)</b> is the total numbers of users reported by <b>b</b>.  Reasoning: If the sum <b>C(b) + T(b)</b> exceeds the total number of users from all countries and transports <b>S(b)</b>, there must be users from that country and transport.  And if that is not the case, <b>0</b> is the lower limit.",
       "<b>high:</b> Upper bound of users by country and transport, calculated as sum over all bridges having reports for the given country and transport, that is, the sum of <b>m(b)</b>, where for each bridge <b>b</b> define <b>m(b):=min(C(b), T(b))</b> where we use the definitions from <b>low</b> (above).  Reasoning: there cannot be more users by country and transport than there are users by either of the two numbers."
-    ],
-    "related": [
-      "userstats-bridge-combined",
-      "clients-data"
     ]
   },
   {
     "id": "torperf",
     "title": "Time to download files over Tor",
-    "tags": [
-      "Performance"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows overall performance when downloading static files of different sizes over Tor.  The graph shows the range of measurements from first to third quartile, and highlights the median.  The slowest and fastest quarter of measurements are omitted from the graph.</p>",
     "function": "plot_torperf",
     "parameters": [
@@ -651,19 +394,12 @@
     ],
     "data": [
       "torperf-data"
-    ],
-    "related": [
-      "torperf-failures"
     ]
   },
   {
     "id": "torperf-failures",
     "title": "Timeouts and failures of downloading files over Tor",
-    "tags": [
-      "Performance"
-    ],
     "type": "Graph",
-    "level": "Advanced",
     "description": "<p>The following graph shows the fraction of timeouts and failures when downloading static files of different sizes over Tor.  A timeout occurs when a download does not complete within the scheduled time, in which case it is aborted in order not to overlap with the next scheduled download.  A failure occurs when the download completes, but the response is smaller than expected.</p>",
     "function": "plot_torperf_failures",
     "parameters": [
@@ -674,19 +410,12 @@
     ],
     "data": [
       "torperf-data"
-    ],
-    "related": [
-      "torperf"
     ]
   },
   {
     "id": "connbidirect",
     "title": "Fraction of connections used uni-/bidirectionally",
-    "tags": [
-      "Performance"
-    ],
     "type": "Graph",
-    "level": "Advanced",
     "description": "<p>The following graph shows the fraction of direct connections between a <a href=\"about.html#relay\">relay</a> and other nodes in the network that are used uni- or bi-directionally.  Every 10 seconds, relays determine for every direct connection whether they read and wrote less than a threshold of 20 KiB.  Connections below this threshold are excluded from the graph.  For the remaining connections, relays determine whether they read/wrote at least 10 times as many bytes as they wrote/read.  If so, they classify a connection as \"Mostly reading\" or \"Mostly writing\", respectively.  All other connections are classified as \"Both reading and writing\".  After classifying connections, read and write counters are reset for the next 10-second interval.  The graph shows daily medians and inter-quartile ranges of reported fractions.</p>",
     "function": "plot_connbidirect",
     "parameters": [
@@ -700,11 +429,7 @@
   {
     "id": "torperf-data",
     "title": "Performance of downloading static files over Tor",
-    "tags": [
-      "Performance"
-    ],
     "type": "Data",
-    "level": "Advanced",
     "description": "<p>The following data file contains aggregate statistics on performance when downloading static files of different sizes over Tor.  These statistics are generated by the <a href=\"https://gitweb.torproject.org/torperf.git\">Torperf</a> tool, which periodically fetches static files over Tor and records several timestamps in the process.  The data file contains daily medians and quartiles as well as total numbers of requests, timeouts, and failures.  Raw Torperf measurement data are available on the <a href=\"https://collector.torproject.org/formats.html#torperf\">CollecTor</a> website.</p>",
     "data_file": "stats/torperf.csv",
     "data_column_spec": [
@@ -717,20 +442,12 @@
       "<b>timeouts:</b> Number of timeouts that occurred when attempting to download the static file over Tor.",
       "<b>failures:</b> Number of failures that occurred when attempting to download the static file over Tor.",
       "<b>requests:</b> Total number of requests made to download the static file over Tor."
-    ],
-    "related": [
-      "torperf",
-      "torperf-failures"
     ]
   },
   {
     "id": "connbidirect2-data",
     "title": "Fraction of connections used uni-/bidirectionally",
-    "tags": [
-      "Performance"
-    ],
     "type": "Data",
-    "level": "Advanced",
     "description": "<p>The following data file contains statistics on the fraction of direct connections between a <a href=\"about.html#relay\">relay</a> and other nodes in the network that are used uni- or bidirectionally.  Every 10 seconds, relays determine for every direct connection whether they read and wrote less than a threshold of 20 KiB.  Connections below this threshold are excluded from the statistics file.  For the remaining connections, relays determine whether they read/wrote at least 10 times as many bytes as they wrote/read.  If so, they classify a connection as \"mostly reading\" or \"mostly writing\", respectively.  All other connections are classified as \"both reading and writing\".  After classifying connections, read and write counters are reset for the next 10-second interval.  The data file contains daily medians and quartiles of reported fractions.</p>",
     "data_file": "stats/connbidirect2.csv",
     "data_column_spec": [
@@ -738,19 +455,12 @@
       "<b>direction:</b> Direction of reported fraction, which can be <b>\"read\"</b>, <b>\"write\"</b>, or <b>\"both\"</b> for connections classified as \"mostly reading\", \"mostly writing\", or \"both reading as writing\".  Connections below the threshold have been removed from this statistics file entirely.",
       "<b>quantile:</b> Quantile of the reported fraction when considering all statistics reported for this date.  Examples are <b>\"0.5\"</b> for the median and <b>\"0.25\"</b> and <b>\"0.75\"</b> for the lower and upper quartile.",
       "<b>fraction:</b> Fraction of connections in percent for the given date, direction, and quantile.  For each daily statistic reported by a relay, fractions for the three directions \"read\", \"write\", and \"both\" sum up to exactly 100."
-    ],
-    "related": [
-      "connbidirect"
     ]
   },
   {
     "id": "hidserv-dir-onions-seen",
     "title": "Unique .onion addresses",
-    "tags": [
-      "Hidden services"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the number of unique .onion addresses in the network per day.  These numbers are extrapolated from aggregated statistics on unique .onion addresses reported by single <a href=\"about.html#relay\">relays</a> acting as <a href=\"about.html#hidden-service\">hidden-service</a> directories, if at least 1% of relays reported these statistics.  For more details on the extrapolation algorithm, see <a href=\"https://blog.torproject.org/blog/some-statistics-about-onions\">this blog post</a> and <a href=\"https://research.torproject.org/techreports/extrapolating-hidserv-stats-2015-01-31.pdf\">this technical report</a>.</p>",
     "function": "plot_hidserv_dir_onions_seen",
     "parameters": [
@@ -759,20 +469,12 @@
     ],
     "data": [
       "hidserv-data"
-    ],
-    "related": [
-      "hidserv-rend-relayed-cells",
-      "hidserv-frac-reporting"
     ]
   },
   {
     "id": "hidserv-rend-relayed-cells",
     "title": "Hidden-service traffic",
-    "tags": [
-      "Hidden services"
-    ],
     "type": "Graph",
-    "level": "Basic",
     "description": "<p>The following graph shows the amount of hidden-service traffic in the network per day.  This number is extrapolated from aggregated statistics on hidden-service traffic reported by single <a href=\"about.html#relay\">relays</a> acting as rendezvous points for <a href=\"about.html#hidden-service\">hidden services</a>, if at least 1% of relays reported these statistics.  For more details on the extrapolation algorithm, see <a href=\"https://blog.torproject.org/blog/some-statistics-about-onions\">this blog post</a> and <a href=\"https://research.torproject.org/techreports/extrapolating-hidserv-stats-2015-01-31.pdf\">this technical report</a>.</p>",
     "function": "plot_hidserv_rend_relayed_cells",
     "parameters": [
@@ -781,20 +483,12 @@
     ],
     "data": [
       "hidserv-data"
-    ],
-    "related": [
-      "hidserv-dir-onions-seen",
-      "hidserv-frac-reporting"
     ]
   },
   {
     "id": "hidserv-frac-reporting",
     "title": "Fraction of relays reporting hidden-service statistics",
-    "tags": [
-      "Hidden services"
-    ],
     "type": "Graph",
-    "level": "Advanced",
     "description": "<p>The following graph shows the fraction of <a href=\"about.html#relay\">relays</a> that report statistics on <a href=\"about.html#hidden-service\">hidden service</a> usage.  If at least 1% of relays report a statistic, it gets extrapolated towards a network total, where higher fractions are produce more accurate results.  For more details on the extrapolation algorithm, see <a href=\"https://blog.torproject.org/blog/some-statistics-about-onions\">this blog post</a> and <a href=\"https://research.torproject.org/techreports/extrapolating-hidserv-stats-2015-01-31.pdf\">this technical report</a>.</p>",
     "function": "plot_hidserv_frac_reporting",
     "parameters": [
@@ -803,20 +497,12 @@
     ],
     "data": [
       "hidserv-data"
-    ],
-    "related": [
-      "hidserv-dir-onions-seen",
-      "hidserv-rend-relayed-cells"
     ]
   },
   {
     "id": "hidserv-data",
     "title": "Hidden-service statistics",
-    "tags": [
-      "Hidden services"
-    ],
     "type": "Data",
-    "level": "Advanced",
     "description": "<p>The following data file contains <a href=\"about.html#hidden-service\">hidden-service</a> statistics gathered by a small subset of <a href=\"about.html#relay\">relays</a> and extrapolated to network totals.  Statistics include the amount of hidden-service traffic and the number of hidden-service addresses in the network per day.  For more details on the extrapolation algorithm, see <a href=\"https://blog.torproject.org/blog/some-statistics-about-onions\">this blog post</a> and <a href=\"https://research.torproject.org/techreports/extrapolating-hidserv-stats-2015-01-31.pdf\">this technical report</a>.</p>",
     "data_file": "stats/hidserv.csv",
     "data_column_spec": [
@@ -827,33 +513,18 @@
       "<b>wiqm:</b> Weighted interquartile mean of extrapolated network totals.",
       "<b>frac:</b> Total network fraction of reported statistics.",
       "<b>stats:</b> Number of reported statistics with non-zero computed network fraction."
-    ],
-    "related": [
-      "hidserv-dir-onions-seen",
-      "hidserv-rend-relayed-cells",
-      "hidserv-frac-reporting"
     ]
   },
   {
     "id": "uncharted-data-flow",
     "title": "Data flow in the Tor network",
-    "tags": [
-      "Relays",
-      "Hidden services",
-      "Bandwidth"
-    ],
     "type": "Link",
-    "level": "Basic",
     "description": "<p>Uncharted made a visualization of data flow in the Tor network where they place each <a href=\"about.html#relay\">relay</a> on a world map and illustrate traffic exchanged between relays as animated dots. More details can be found on the <a href=\"https://torflow.uncharted.software/\">Uncharted website</a>.</p><p><a href=\"https://torflow.uncharted.software/\"><img src=\"images/uncharted-data-flow.png\" alt=\"Data flow in the Tor network\"></a></p>"
   },
   {
     "id": "disagreement-data",
     "title": "Disagreement among the directory authorities (deprecated)",
-    "tags": [
-      "Relays"
-    ],
     "type": "Data",
-    "level": "Advanced",
     "description": "<p><font color=\"red\">As of September 29, 2016, the linked data file is not updated anymore.  This page and the linked data file will be removed in the future.</font></p><p>The following data file contains statistics about agreement or disagreement among the <a href=\"about.html#directory-authority\">directory authorities</a>.  Once per hour the directory authorities exchange votes with their view of the <a href=\"about.html#relay\">relays</a> in the network including attributes like <a href=\"about.html#relay-flag\">relay flags</a> or bandwidth measurements.  This data file includes counts of relays by number of directory authorities assigning them a given attribute.</p>",
     "data_file": "stats/disagreement.csv",
     "data_column_spec": [
@@ -863,37 +534,18 @@
       "<b>required:</b> Required number of votes for the attribute to be assigned to a relay for being included in the consensus.",
       "<b>max:</b> Maximum number of possible votes assigning the attribute to relays.",
       "<b>relays:</b> Number of relays that got the given number of votes for the given attribute."
-    ],
-    "related": [
-      "relayflags"
     ]
   },
   {
     "id": "uptimes",
     "title": "Monthly uptime of relays",
-    "tags": [
-      "Relays"
-    ],
     "type": "Link",
-    "level": "Advanced",
-    "description": "<p>The following image illustrates the uptime of all known <a href=\"about.html#relay\">relays</a> in a given month.  Each row of pixels denotes one <a href=\"about.html#consensus\">consensus</a> (that is, one hour), and each column denotes one relay.  Each pixel denotes if a given relay was online or offline at a given hour: black means online, white means offline, and red highlights relays with identical uptime patterns.  The complete image gallery can be found on <a href=\"https://nymity.ch/sybilhunting/uptime-visualisation/\">Philipp Winter's homepage</a>.</p><p><a href=\"https://nymity.ch/sybilhunting/uptime-visualisation/\"><img src=\"images/uptimes.jpg\" alt=\"Monthly uptime of relays\"></a></p>",
-    "related": [
-      "networkchurn"
-    ]
+    "description": "<p>The following image illustrates the uptime of all known <a href=\"about.html#relay\">relays</a> in a given month.  Each row of pixels denotes one <a href=\"about.html#consensus\">consensus</a> (that is, one hour), and each column denotes one relay.  Each pixel denotes if a given relay was online or offline at a given hour: black means online, white means offline, and red highlights relays with identical uptime patterns.  The complete image gallery can be found on <a href=\"https://nymity.ch/sybilhunting/uptime-visualisation/\">Philipp Winter's homepage</a>.</p><p><a href=\"https://nymity.ch/sybilhunting/uptime-visualisation/\"><img src=\"images/uptimes.jpg\" alt=\"Monthly uptime of relays\"></a></p>"
   },
   {
     "id": "networkchurn",
     "title": "Network churn rate by relay flag",
-    "tags": [
-      "Relays"
-    ],
     "type": "Link",
-    "level": "Advanced",
-    "description": "<p>The following image shows the churn rate of the Tor network by <a href=\"about.html#relay-flag\">relay flag</a> in a given month.  The churn rate, a value in the interval <b>[0,1]</b>, captures the rate of <a href=\"about.html#relay\">relays</a> joining and leaving the network from one <a href=\"about.html#consensus\">consensus</a> to the next (that is, within one hour).  The complete image gallery can be found on <a href=\"https://nymity.ch/sybilhunting/churn-values/\">Philipp Winter's homepage</a>.</p><p><a href=\"https://nymity.ch/sybilhunting/churn-values/\"><img src=\"images/networkchurn.png\" alt=\"Network churn rate by relay flag\"></a></p>",
-    "related": [
-      "uptimes",
-      "networksize",
-      "relayflags"
-    ]
+    "description": "<p>The following image shows the churn rate of the Tor network by <a href=\"about.html#relay-flag\">relay flag</a> in a given month.  The churn rate, a value in the interval <b>[0,1]</b>, captures the rate of <a href=\"about.html#relay\">relays</a> joining and leaving the network from one <a href=\"about.html#consensus\">consensus</a> to the next (that is, within one hour).  The complete image gallery can be found on <a href=\"https://nymity.ch/sybilhunting/churn-values/\">Philipp Winter's homepage</a>.</p><p><a href=\"https://nymity.ch/sybilhunting/churn-values/\"><img src=\"images/networkchurn.png\" alt=\"Network churn rate by relay flag\"></a></p>"
   }
 ]
diff --git a/website/src/org/torproject/metrics/web/DataServlet.java b/website/src/org/torproject/metrics/web/DataServlet.java
index ac7cb2a..2aa7ccd 100644
--- a/website/src/org/torproject/metrics/web/DataServlet.java
+++ b/website/src/org/torproject/metrics/web/DataServlet.java
@@ -36,7 +36,6 @@ public class DataServlet extends MetricServlet {
     request.setAttribute("data_column_spec",
         this.dataColumnSpecs.get(requestedId));
     request.setAttribute("data", this.data.get(requestedId));
-    request.setAttribute("related", this.related.get(requestedId));
     request.getRequestDispatcher("WEB-INF/data.jsp").forward(request,
         response);
   }
diff --git a/website/src/org/torproject/metrics/web/GraphServlet.java b/website/src/org/torproject/metrics/web/GraphServlet.java
index 917980b..9f3a7e3 100644
--- a/website/src/org/torproject/metrics/web/GraphServlet.java
+++ b/website/src/org/torproject/metrics/web/GraphServlet.java
@@ -134,37 +134,22 @@ public class GraphServlet extends MetricServlet {
       response.sendError(HttpServletResponse.SC_BAD_REQUEST);
       return;
     }
-    List<String[]> categories = new ArrayList<String[]>();
-    /* TODO Some of the following code should move to init(). */
-    for (Category category :
-        ContentProvider.getInstance().getCategoriesList()) {
-      if (category.getMetrics().isEmpty()
-          || this.categories.get(requestedId).equals(category)) {
-        categories.add(new String[] { "", category.getHeader() });
-      } else {
-        categories.add(new String[] { category.getMetrics().get(0),
-            category.getHeader() });
-      }
-    }
-    request.setAttribute("categories", categories);
+    request.setAttribute("categories", this.categories);
     request.setAttribute("id", requestedId);
     request.setAttribute("title", this.titles.get(requestedId));
-    if (this.categories.containsKey(requestedId)) {
-      Category category = this.categories.get(requestedId);
+    if (this.categoriesById.containsKey(requestedId)) {
+      Category category = this.categoriesById.get(requestedId);
       request.setAttribute("categoryHeader", category.getHeader());
       request.setAttribute("categoryDescription", category.getDescription());
       List<String[]> categoryTabs = new ArrayList<String[]>();
       for (String metricId : category.getMetrics()) {
-        categoryTabs.add(new String[] {
-            this.titles.get(metricId),
-            requestedId.equals(metricId) ? null : metricId });
+        categoryTabs.add(new String[] { this.titles.get(metricId), metricId });
       }
       request.setAttribute("categoryTabs", categoryTabs);
     }
     request.setAttribute("description",
         this.descriptions.get(requestedId));
     request.setAttribute("data", this.data.get(requestedId));
-    request.setAttribute("related", this.related.get(requestedId));
     SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
     dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
     Date defaultEndDate = new Date();
diff --git a/website/src/org/torproject/metrics/web/LinkServlet.java b/website/src/org/torproject/metrics/web/LinkServlet.java
index c8f4d46..7caeca2 100644
--- a/website/src/org/torproject/metrics/web/LinkServlet.java
+++ b/website/src/org/torproject/metrics/web/LinkServlet.java
@@ -15,6 +15,11 @@ import javax.servlet.http.HttpServletResponse;
 public class LinkServlet extends MetricServlet {
 
   @Override
+  public void init() throws ServletException {
+    super.init();
+  }
+
+  @Override
   public void doGet(HttpServletRequest request,
       HttpServletResponse response) throws IOException, ServletException {
     String requestUri = request.getRequestURI();
@@ -30,23 +35,11 @@ public class LinkServlet extends MetricServlet {
       response.sendError(HttpServletResponse.SC_BAD_REQUEST);
       return;
     }
-    List<String[]> categories = new ArrayList<String[]>();
-    /* TODO Some of the following code should move to init(). */
-    for (Category category :
-        ContentProvider.getInstance().getCategoriesList()) {
-      if (category.getMetrics().isEmpty()
-          || this.categories.get(requestedId).equals(category)) {
-        categories.add(new String[] { "", category.getHeader() });
-      } else {
-        categories.add(new String[] { category.getMetrics().get(0),
-            category.getHeader() });
-      }
-    }
-    request.setAttribute("categories", categories);
+    request.setAttribute("categories", this.categories);
     request.setAttribute("id", requestedId);
     request.setAttribute("title", this.titles.get(requestedId));
-    if (this.categories.containsKey(requestedId)) {
-      Category category = this.categories.get(requestedId);
+    if (this.categoriesById.containsKey(requestedId)) {
+      Category category = this.categoriesById.get(requestedId);
       request.setAttribute("categoryHeader", category.getHeader());
       request.setAttribute("categoryDescription", category.getDescription());
       List<String[]> categoryTabs = new ArrayList<String[]>();
@@ -60,7 +53,6 @@ public class LinkServlet extends MetricServlet {
     request.setAttribute("description",
         this.descriptions.get(requestedId));
     request.setAttribute("data", this.data.get(requestedId));
-    request.setAttribute("related", this.related.get(requestedId));
     request.getRequestDispatcher("WEB-INF/link.jsp").forward(request,
         response);
   }
diff --git a/website/src/org/torproject/metrics/web/Metric.java b/website/src/org/torproject/metrics/web/Metric.java
index 31dcbd7..b60f7ec 100644
--- a/website/src/org/torproject/metrics/web/Metric.java
+++ b/website/src/org/torproject/metrics/web/Metric.java
@@ -10,12 +10,8 @@ public class Metric {
 
   private String title;
 
-  private String[] tags;
-
   private String type;
 
-  private String level;
-
   private String description;
 
   private String function;
@@ -24,8 +20,6 @@ public class Metric {
 
   private String[] data;
 
-  private String[] related;
-
   private String[] table_headers;
 
   private String[] table_cell_formats;
@@ -42,18 +36,10 @@ public class Metric {
     return this.title;
   }
 
-  public String[] getTags() {
-    return this.tags;
-  }
-
   public String getType() {
     return this.type;
   }
 
-  public String getLevel() {
-    return this.level;
-  }
-
   public String getDescription() {
     return this.description;
   }
@@ -85,9 +71,5 @@ public class Metric {
   public String[] getData() {
     return this.data;
   }
-
-  public String[] getRelated() {
-    return this.related;
-  }
 }
 
diff --git a/website/src/org/torproject/metrics/web/MetricServlet.java b/website/src/org/torproject/metrics/web/MetricServlet.java
index a280ee4..901f4e2 100644
--- a/website/src/org/torproject/metrics/web/MetricServlet.java
+++ b/website/src/org/torproject/metrics/web/MetricServlet.java
@@ -12,10 +12,9 @@ import java.util.Map;
 import java.util.Set;
 
 import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
 
 @SuppressWarnings("serial")
-public abstract class MetricServlet extends HttpServlet {
+public abstract class MetricServlet extends AnyServlet {
 
   protected List<Metric> metrics;
 
@@ -44,17 +43,15 @@ public abstract class MetricServlet extends HttpServlet {
   protected Map<String, List<String[]>> data =
       new HashMap<String, List<String[]>>();
 
-  protected Map<String, List<String[]>> related =
-      new HashMap<String, List<String[]>>();
-
-  protected Map<String, Category> categories = new HashMap<String, Category>();
+  protected Map<String, Category> categoriesById =
+      new HashMap<String, Category>();
 
   @Override
   public void init() throws ServletException {
+    super.init();
     this.metrics = ContentProvider.getInstance().getMetricsList();
     Map<String, String> allTypesAndTitles = new HashMap<String, String>();
     Map<String, String[]> dataIds = new HashMap<String, String[]>();
-    Map<String, String[]> relatedIds = new HashMap<String, String[]>();
     for (Metric metric : this.metrics) {
       String id = metric.getId();
       String title = metric.getTitle();
@@ -84,9 +81,6 @@ public abstract class MetricServlet extends HttpServlet {
       if (metric.getData() != null) {
         dataIds.put(id, metric.getData());
       }
-      if (metric.getRelated() != null) {
-        relatedIds.put(id, metric.getRelated());
-      }
     }
     for (Set<String> ids : idsByType.values()) {
       for (String id : ids) {
@@ -101,24 +95,12 @@ public abstract class MetricServlet extends HttpServlet {
           }
           this.data.put(id, dataLinksTypesAndTitles);
         }
-        if (relatedIds.containsKey(id)) {
-          List<String[]> relatedLinksTypesAndTitles =
-              new ArrayList<String[]>();
-          for (String relatedId : relatedIds.get(id)) {
-            if (allTypesAndTitles.containsKey(relatedId)) {
-              relatedLinksTypesAndTitles.add(new String[] {
-                  relatedId + ".html",
-                  allTypesAndTitles.get(relatedId) } );
-            }
-          }
-          this.related.put(id, relatedLinksTypesAndTitles);
-        }
       }
     }
     for (Category category :
         ContentProvider.getInstance().getCategoriesList()) {
       for (String id : category.getMetrics()) {
-        categories.put(id, category);
+        this.categoriesById.put(id, category);
       }
     }
   }
diff --git a/website/src/org/torproject/metrics/web/TableServlet.java b/website/src/org/torproject/metrics/web/TableServlet.java
index a47a720..0236777 100644
--- a/website/src/org/torproject/metrics/web/TableServlet.java
+++ b/website/src/org/torproject/metrics/web/TableServlet.java
@@ -48,23 +48,10 @@ public class TableServlet extends MetricServlet {
       response.sendError(HttpServletResponse.SC_BAD_REQUEST);
       return;
     }
-    List<String[]> categories = new ArrayList<String[]>();
-    /* TODO Some of the following code should move to init(). */
-    for (Category category :
-        ContentProvider.getInstance().getCategoriesList()) {
-      if (category.getMetrics().isEmpty()
-          || this.categories.get(requestedId).equals(category)) {
-        categories.add(new String[] { "", category.getHeader() });
-      } else {
-        categories.add(new String[] { category.getMetrics().get(0),
-            category.getHeader() });
-      }
-    }
-    request.setAttribute("categories", categories);
     request.setAttribute("id", requestedId);
     request.setAttribute("title", this.titles.get(requestedId));
-    if (this.categories.containsKey(requestedId)) {
-      Category category = this.categories.get(requestedId);
+    if (this.categoriesById.containsKey(requestedId)) {
+      Category category = this.categoriesById.get(requestedId);
       request.setAttribute("categoryHeader", category.getHeader());
       request.setAttribute("categoryDescription", category.getDescription());
       List<String[]> categoryTabs = new ArrayList<String[]>();
@@ -80,7 +67,6 @@ public class TableServlet extends MetricServlet {
     request.setAttribute("tableheader",
         this.tableHeaders.get(requestedId));
     request.setAttribute("data", this.data.get(requestedId));
-    request.setAttribute("related", this.related.get(requestedId));
     SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
     dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
     Date defaultEndDate = new Date();
diff --git a/website/web/WEB-INF/graph.jsp b/website/web/WEB-INF/graph.jsp
index 133fd80..8d5c5db 100644
--- a/website/web/WEB-INF/graph.jsp
+++ b/website/web/WEB-INF/graph.jsp
@@ -1,54 +1,46 @@
 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-<head>
-  <title>Tor Metrics — ${title}</title>
-  <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
-  <link href="css/stylesheet-ltr.css" type="text/css" rel="stylesheet">
-  <link href="css/bootstrap.min.css" type="text/css" rel="stylesheet">
-  <link href="images/favicon.ico" type="image/x-icon" rel="shortcut icon">
-</head>
-<body>
-  <div class="center">
-    <div class="main-column">
-        <h2><a href="/"><img src="images/metrics-logo.png" width="153" height="200" alt="Metrics logo"><img src="images/metrics-wordmark.png" width="384" height="50" alt="Metrics wordmark"></a></h2>
-        <br>
+<jsp:include page="top.jsp">
+  <jsp:param name="pageTitle" value="${categoryHeader} – Tor Metrics"/>
+  <jsp:param name="navActive" value="${categoryHeader}"/>
+</jsp:include>
 
-<p>"Tor metrics are the ammunition that lets Tor and other security
-advocates argue for a more private and secure Internet from a position
-of data, rather than just dogma or perspective."
-<i>- Bruce Schneier (June 1, 2016)</i></p>
+    <div class="container">
+      <ul class="breadcrumb">
+        <li><a href="index.html">Home</a></li>
+        <li class="active">${categoryHeader}</li>
+      </ul>
+    </div>
 
-        <!-- Navigation start -->
-        Metrics |
-        <a href="about.html">About</a> |
-        <a href="news.html">News</a> |
-        <a href="tools.html">Tools</a> |
-        <a href="research.html">Research</a>
-        <br>
-        <br>
-        <!-- Navigation end -->
+    <div class="container">
+      <h1>${categoryHeader}</h1>
+      <p>${categoryDescription}</p>
+    </div>
 
-<c:forEach var="category" items="${categories}"><c:if test="${fn:length(category[0]) > 0}"><a href="${category[0]}.html"></c:if>${category[1]}<c:if test="${fn:length(category[0]) > 0}"></a></c:if> |
-</c:forEach>
-<br>
+    <div class="container">
 
-<h2>${categoryHeader}</h2>
+      <!-- tabs -->
+      <ul class="nav nav-tabs">
+        <c:forEach var="tab" items="${categoryTabs}">
+        <li role="presentation"<c:if test="${id.equals(tab[1])}"> class="active"</c:if>><a href="${tab[1]}.html" data-tab="${tab[1]}">${tab[0]}</a></li>
+        </c:forEach>
+      </ul>
 
-<p>${categoryDescription}</p>
+      <!-- tab-content -->
+      <div class="tab-content">
+        <div class="tab-pane active" id="tab-${tab[1]}">
 
-<c:forEach var="tab" items="${categoryTabs}">
-<c:if test="${fn:length(tab[1]) > 0}"><a href="${tab[1]}.html"></c:if>${tab[0]}<c:if test="${fn:length(tab[1]) > 0}"></a></c:if> |
-</c:forEach>
-<br>
+          <div class="row">
+            <div class="col-md-8">
+
+              <img src="${id}.png${parameters}" width="576" height="360" alt="${title} graph">
+
+              ${description}
+
+            </div>
+            <div class="col-md-4">
 
-<br>
-${description}
-<img src="${id}.png${parameters}"
-     width="576" height="360" alt="${title} graph">
 <form action="${id}.html">
-  <div class="formrow">
     <c:if test="${fn:length(start) > 0}">
       <p>
         <label>Start date (yyyy-mm-dd):
@@ -153,7 +145,6 @@ ${description}
     <p>
     <input class="submit" type="submit" value="Update graph">
     </p>
-  </div>
 </form>
 
 <p>Download graph as
@@ -170,20 +161,11 @@ ${description}
 </ul>
 </c:if>
 
-<c:if test="${fn:length(related) > 0}">
-<h4>Related metrics</h4>
-<ul>
-<c:forEach var="row" items="${related}">
-<li><a href="${row[0]}">${row[1]}</a></li>
-</c:forEach>
-</ul>
-</c:if>
+            </div><!-- col-md-4 -->
+          </div><!-- row -->
+        </div><!-- tab-pane -->
+      </div><!-- tab-content -->
+    </div><!-- container -->
 
-    </div>
-  </div>
-  <div class="bottom" id="bottom">
-    <%@ include file="footer.jsp"%>
-  </div>
-</body>
-</html>
+<jsp:include page="bottom.jsp"/>
 
diff --git a/website/web/js/script.js b/website/web/js/script.js
index e199cb4..8c7e4d3 100644
--- a/website/web/js/script.js
+++ b/website/web/js/script.js
@@ -34,19 +34,6 @@ jQuery(function() {
   });
   
   
-  // toggle tabs by JS:
-  jQuery('.nav-tabs a').each(function(){
-    jQuery(this).click(function(e){
-      e.preventDefault();
-      // toggle data-tab:
-      jQuery('.nav-tabs li').removeClass('active');
-      jQuery(this).parent().addClass('active');
-      jQuery( ".tab-pane" ).hide();
-      jQuery( "#tab-" + jQuery(this).data('tab') ).show();
-    });
-  });
-  
-  
   // make main menu items with dropdowns clickable again:
   jQuery('.dropdown-toggle').click(function(){
       location.href = jQuery(this).attr('href');





More information about the tor-commits mailing list