zipkin-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From adrianc...@apache.org
Subject [incubator-zipkin] branch master updated: Deleting Zipkin UI (#2605)
Date Mon, 20 May 2019 09:52:47 GMT
This is an automated email from the ASF dual-hosted git repository.

adriancole pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-zipkin.git


The following commit(s) were added to refs/heads/master by this push:
     new 5e82715  Deleting Zipkin UI (#2605)
5e82715 is described below

commit 5e827151dc59e5c847bb5b7f245f89c0d48acf7a
Author: Raja Sundaram <4723376+zeagord@users.noreply.github.com>
AuthorDate: Mon May 20 17:52:38 2019 +0800

    Deleting Zipkin UI (#2605)
---
 LICENSE                                            |    10 -
 pom.xml                                            |    10 -
 {zipkin-ui => zipkin-lens}/testdata/README.md      |     0
 {zipkin-ui => zipkin-lens}/testdata/ascend.json    |     0
 .../testdata/messaging-kafka.json                  |     0
 {zipkin-ui => zipkin-lens}/testdata/messaging.json |     0
 .../testdata/messaging2.json                       |     0
 {zipkin-ui => zipkin-lens}/testdata/netflix.json   |     0
 .../testdata/simple-db-p6.json                     |     0
 {zipkin-ui => zipkin-lens}/testdata/skew.json      |     0
 .../testdata/smartthings-mobile-web-install.json   |     0
 .../testdata/smartthings-oauth-authorization.json  |     0
 zipkin-ui/.babelrc                                 |     4 -
 zipkin-ui/.bowerrc                                 |     3 -
 zipkin-ui/.eslintrc.js                             |    17 -
 zipkin-ui/.gitignore                               |     4 -
 zipkin-ui/README.md                                |   166 -
 zipkin-ui/bower.json                               |     8 -
 zipkin-ui/css/dependency.css                       |    79 -
 zipkin-ui/css/main.scss                            |    74 -
 zipkin-ui/css/style-loader.js                      |    26 -
 zipkin-ui/css/summary.css                          |   180 -
 zipkin-ui/css/trace.css                            |   232 -
 zipkin-ui/css/traces.css                           |   216 -
 zipkin-ui/index.ejs                                |    49 -
 zipkin-ui/js/component_data/default.js             |   127 -
 zipkin-ui/js/component_data/dependency.js          |    97 -
 zipkin-ui/js/component_data/remoteServiceNames.js  |    45 -
 zipkin-ui/js/component_data/serviceNames.js        |    36 -
 zipkin-ui/js/component_data/skew.js                |   178 -
 zipkin-ui/js/component_data/spanCleaner.js         |   269 -
 zipkin-ui/js/component_data/spanNames.js           |    41 -
 zipkin-ui/js/component_data/spanNode.js            |   293 -
 zipkin-ui/js/component_data/trace.js               |    50 -
 zipkin-ui/js/component_ui/backToTop.js             |    39 -
 zipkin-ui/js/component_ui/dependencyGraph.js       |   211 -
 zipkin-ui/js/component_ui/environment.js           |    23 -
 zipkin-ui/js/component_ui/error.js                 |    41 -
 zipkin-ui/js/component_ui/filterAllServices.js     |    66 -
 zipkin-ui/js/component_ui/fullPageSpinner.js       |    38 -
 zipkin-ui/js/component_ui/goToDependency.js        |    31 -
 zipkin-ui/js/component_ui/goToLens.js              |    30 -
 zipkin-ui/js/component_ui/goToTrace.js             |    30 -
 zipkin-ui/js/component_ui/i18n.js                  |    43 -
 zipkin-ui/js/component_ui/infoButton.js            |    27 -
 zipkin-ui/js/component_ui/infoPanel.js             |    30 -
 zipkin-ui/js/component_ui/jsonPanel.js             |    41 -
 zipkin-ui/js/component_ui/lookback.js              |    47 -
 zipkin-ui/js/component_ui/navbar.js                |    39 -
 zipkin-ui/js/component_ui/remoteServiceName.js     |    53 -
 zipkin-ui/js/component_ui/serviceDataModal.js      |   111 -
 zipkin-ui/js/component_ui/serviceFilterSearch.js   |    33 -
 zipkin-ui/js/component_ui/serviceName.js           |    76 -
 zipkin-ui/js/component_ui/spanName.js              |    53 -
 zipkin-ui/js/component_ui/spanPanel.js             |   156 -
 zipkin-ui/js/component_ui/spanRow.js               |   352 -
 zipkin-ui/js/component_ui/timeStamp.js             |    57 -
 zipkin-ui/js/component_ui/trace.js                 |   483 -
 zipkin-ui/js/component_ui/traceConstants.js        |    69 -
 zipkin-ui/js/component_ui/traceFilters.js          |    64 -
 zipkin-ui/js/component_ui/traceSummary.js          |   212 -
 zipkin-ui/js/component_ui/traceToMustache.js       |   148 -
 zipkin-ui/js/component_ui/traces.js                |    51 -
 zipkin-ui/js/component_ui/uploadTrace.js           |    78 -
 zipkin-ui/js/component_ui/zoomOutSpans.js          |    27 -
 zipkin-ui/js/config.js                             |    42 -
 zipkin-ui/js/main.js                               |    48 -
 zipkin-ui/js/page/common.js                        |    36 -
 zipkin-ui/js/page/default.js                       |   133 -
 zipkin-ui/js/page/dependency.js                    |    53 -
 zipkin-ui/js/page/trace.js                         |    73 -
 zipkin-ui/js/page/traceViewer.js                   |    85 -
 zipkin-ui/js/publicPath.js                         |    32 -
 zipkin-ui/js/templates.js                          |    22 -
 zipkin-ui/karma.conf.js                            |    84 -
 zipkin-ui/libs/chosen/chosen-sprite.png            |   Bin 538 -> 0 bytes
 zipkin-ui/libs/chosen/chosen-sprite@2x.png         |   Bin 738 -> 0 bytes
 zipkin-ui/libs/chosen/chosen.css                   |   450 -
 zipkin-ui/libs/dagre-d3/.bower.json                |    25 -
 zipkin-ui/libs/dagre-d3/bower.json                 |    14 -
 zipkin-ui/libs/dagre-d3/js/dagre-d3.js             |  5003 -------
 zipkin-ui/libs/dagre-d3/js/dagre-d3.min.js         |     2 -
 zipkin-ui/npm.sh                                   |    27 -
 zipkin-ui/package-lock.json                        | 13974 -------------------
 zipkin-ui/package.json                             |    72 -
 zipkin-ui/pom.xml                                  |   102 -
 zipkin-ui/static/dep.properties                    |    22 -
 zipkin-ui/static/dep_it_IT.properties              |    20 -
 zipkin-ui/static/dep_zh_CN.properties              |    22 -
 zipkin-ui/static/dep_zh_TW.properties              |    20 -
 zipkin-ui/static/favicon.ico                       |   Bin 25407 -> 0 bytes
 zipkin-ui/static/nav.properties                    |    23 -
 zipkin-ui/static/nav_it_IT.properties              |    21 -
 zipkin-ui/static/nav_zh_CN.properties              |    23 -
 zipkin-ui/static/nav_zh_TW.properties              |    21 -
 zipkin-ui/static/trace.properties                  |    24 -
 zipkin-ui/static/trace_it_IT.properties            |    24 -
 zipkin-ui/static/trace_zh_CN.properties            |    24 -
 zipkin-ui/static/trace_zh_TW.properties            |    24 -
 zipkin-ui/static/traces.properties                 |    57 -
 zipkin-ui/static/traces_it_IT.properties           |    46 -
 zipkin-ui/static/traces_zh_CN.properties           |    57 -
 zipkin-ui/static/traces_zh_TW.properties           |    46 -
 zipkin-ui/templates/dependency.mustache            |   108 -
 zipkin-ui/templates/index.mustache                 |   268 -
 zipkin-ui/templates/layout.mustache                |    63 -
 zipkin-ui/templates/searchDisabled.mustache        |    19 -
 zipkin-ui/templates/trace.mustache                 |   272 -
 zipkin-ui/templates/traceViewer.mustache           |   277 -
 zipkin-ui/test/.eslintrc                           |    10 -
 zipkin-ui/test/component_data/default.test.js      |   231 -
 zipkin-ui/test/component_data/skew.test.js         |   328 -
 zipkin-ui/test/component_data/spanCleaner.test.js  |  1014 --
 zipkin-ui/test/component_data/spanNode.test.js     |   242 -
 zipkin-ui/test/component_data/trace.test.js        |   520 -
 zipkin-ui/test/component_ui/serviceName.test.js    |    29 -
 zipkin-ui/test/component_ui/spanPanel.test.js      |   153 -
 zipkin-ui/test/component_ui/spanRow.test.js        |  1229 --
 zipkin-ui/test/component_ui/trace.test.js          |   268 -
 zipkin-ui/test/component_ui/traceSummary.test.js   |   274 -
 zipkin-ui/test/component_ui/traceTestHelpers.js    |   126 -
 .../test/component_ui/traceToMustache.test.js      |   326 -
 zipkin-ui/test/component_ui/uploadTrace.test.js    |    74 -
 zipkin-ui/test/config.test.js                      |    54 -
 zipkin-ui/test/page/common.test.js                 |    26 -
 zipkin-ui/webpack.config.js                        |    85 -
 126 files changed, 31720 deletions(-)

diff --git a/LICENSE b/LICENSE
index 930beea..0c1111b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -214,13 +214,3 @@ This product contains a modified part of Okio, distributed by Square:
 
   * License: Apache License v2.0
   * Homepage: https://github.com/square/okio
-
-This product contains a modified part of Chosen, distributed by Harvest:
-
-  * License: MIT
-  * Homepage: https://github.com/harvesthq/chosen
-
-This product contains a modified part of Dager-D3, distributed by Dagre:
-
-  * License: MIT
-  * Homepage: https://github.com/dagrejs/dagre-d3
diff --git a/pom.xml b/pom.xml
index 3dcf546..9e80a9e 100755
--- a/pom.xml
+++ b/pom.xml
@@ -631,11 +631,6 @@
             <exclude>**/.eslintrc</exclude>
             <exclude>**/.eslintrc</exclude>
             <exclude>**/.eslintrc.js</exclude>
-
-            <!-- remove when deleting zipkin-ui #2569 -->
-            <exclude>**/chosen.*</exclude>
-	          <exclude>**/dagre-d3.*</exclude>
-
             <exclude>**/testdata/**/*.json</exclude>
             <exclude>**/test/data/**/*.json</exclude>
             <exclude>LICENSE</exclude>
@@ -742,11 +737,6 @@
             <exclude>**/.editorconfig</exclude>
             <exclude>**/.eslintrc</exclude>
             <exclude>**/.eslintrc.js</exclude>
-
-            <!-- remove when deleting zipkin-ui #2569 -->
-            <exclude>**/chosen.*</exclude>
-            <exclude>**/dagre-d3.*</exclude>
-
             <!-- Maven Wrapper generated files -->
             <exclude>.mvn/wrapper/maven-wrapper.properties</exclude>
 
diff --git a/zipkin-ui/testdata/README.md b/zipkin-lens/testdata/README.md
similarity index 100%
rename from zipkin-ui/testdata/README.md
rename to zipkin-lens/testdata/README.md
diff --git a/zipkin-ui/testdata/ascend.json b/zipkin-lens/testdata/ascend.json
similarity index 100%
rename from zipkin-ui/testdata/ascend.json
rename to zipkin-lens/testdata/ascend.json
diff --git a/zipkin-ui/testdata/messaging-kafka.json b/zipkin-lens/testdata/messaging-kafka.json
similarity index 100%
rename from zipkin-ui/testdata/messaging-kafka.json
rename to zipkin-lens/testdata/messaging-kafka.json
diff --git a/zipkin-ui/testdata/messaging.json b/zipkin-lens/testdata/messaging.json
similarity index 100%
rename from zipkin-ui/testdata/messaging.json
rename to zipkin-lens/testdata/messaging.json
diff --git a/zipkin-ui/testdata/messaging2.json b/zipkin-lens/testdata/messaging2.json
similarity index 100%
rename from zipkin-ui/testdata/messaging2.json
rename to zipkin-lens/testdata/messaging2.json
diff --git a/zipkin-ui/testdata/netflix.json b/zipkin-lens/testdata/netflix.json
similarity index 100%
rename from zipkin-ui/testdata/netflix.json
rename to zipkin-lens/testdata/netflix.json
diff --git a/zipkin-ui/testdata/simple-db-p6.json b/zipkin-lens/testdata/simple-db-p6.json
similarity index 100%
rename from zipkin-ui/testdata/simple-db-p6.json
rename to zipkin-lens/testdata/simple-db-p6.json
diff --git a/zipkin-ui/testdata/skew.json b/zipkin-lens/testdata/skew.json
similarity index 100%
rename from zipkin-ui/testdata/skew.json
rename to zipkin-lens/testdata/skew.json
diff --git a/zipkin-ui/testdata/smartthings-mobile-web-install.json b/zipkin-lens/testdata/smartthings-mobile-web-install.json
similarity index 100%
rename from zipkin-ui/testdata/smartthings-mobile-web-install.json
rename to zipkin-lens/testdata/smartthings-mobile-web-install.json
diff --git a/zipkin-ui/testdata/smartthings-oauth-authorization.json b/zipkin-lens/testdata/smartthings-oauth-authorization.json
similarity index 100%
rename from zipkin-ui/testdata/smartthings-oauth-authorization.json
rename to zipkin-lens/testdata/smartthings-oauth-authorization.json
diff --git a/zipkin-ui/.babelrc b/zipkin-ui/.babelrc
deleted file mode 100644
index 831f20a..0000000
--- a/zipkin-ui/.babelrc
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "presets": ["es2015"],
-  "plugins": ["transform-object-rest-spread"]
-}
diff --git a/zipkin-ui/.bowerrc b/zipkin-ui/.bowerrc
deleted file mode 100644
index b4b9cc0..0000000
--- a/zipkin-ui/.bowerrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-  "directory": "app/libs"
-}
diff --git a/zipkin-ui/.eslintrc.js b/zipkin-ui/.eslintrc.js
deleted file mode 100644
index 13f4f29..0000000
--- a/zipkin-ui/.eslintrc.js
+++ /dev/null
@@ -1,17 +0,0 @@
-module.exports = {
-  extends: 'airbnb',
-  plugins: [],
-  parserOptions: {
-    ecmaFeatures: {
-      experimentalObjectRestSpread: true
-    }
-  },
-  rules: {
-    'comma-dangle': 'off',
-    'func-names': 'off',
-    'object-curly-spacing': [2, 'never'],
-    'space-before-function-paren': [2, 'never'],
-    eqeqeq: [2, 'allow-null'],
-    'no-else-return': 'off'
-  }
-};
diff --git a/zipkin-ui/.gitignore b/zipkin-ui/.gitignore
deleted file mode 100644
index 8f36902..0000000
--- a/zipkin-ui/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-# Ignores are placed here to allow node.js-idiomatic builds
-*.log
-node_modules
-dist
diff --git a/zipkin-ui/README.md b/zipkin-ui/README.md
deleted file mode 100644
index 337a6bd..0000000
--- a/zipkin-ui/README.md
+++ /dev/null
@@ -1,166 +0,0 @@
-# zipkin-ui
-
-Zipkin-UI is a single-page application mounted at /zipkin. For simplicity,
-assume paths mentioned below are relative to that. For example, the UI
-reads config.json, from the absolute path /zipkin/config.json
-
-When looking at a trace, the browser is sent to the path "/traces/{id}".
-For the single-page app to serve that route, the server needs to forward
-the request to "/index.html". The same forwarding applies to "/dependencies"
-and any other routes the UI controls.
-
-Under the scenes the JavaScript code looks at window.location to figure
-out what the UI should do. This is handled by a route api defined in the
-crossroads library.
-
-The suggested logic for serving the assets of Zipkin-UI is as follows:
-
- 1. If the browser is requesting a file with an extension (that is, the last path segment has a `.` in it), then
-    serve that file. If it doesn't exist, then return 404.
- 1. Otherwise, serve `index.html`.
-
-For an example implementation using Finatra, see [zipkin-query](https://github.com/apache/incubator-zipkin/blob/5dec252e4c562b21bac5ac2f9d0b437d90988f79/zipkin-query/src/main/scala/com/twitter/zipkin/query/ZipkinQueryController.scala).
-
-Note that in cases where a non-existent resource is requested, this logic returns the contents of `index.html`. When
-loaded as a web-page, the client application will handle the problem of telling the user about this. When not,
-it'll take an extra step to find where the problem is - you won't see a 404 in your network tab.
-
-## Why is it packaged as a `.jar` and not a `.zip`?
-
-Since many Zipkin servers are Java-based, it's convenient to distribute the UI as a jar, which can be imported by the
-Gradle build tool. A `.jar` file is really only a `.zip` file, and can be treated as such. It can be opened by any
-program that can extract zip files.
-
-## How do I run against a proxy zipkin-backend?
-
-By specifying the `proxy` environment variable, you can point the zipkin-ui to a different backend, allowing you to access real data while developing locally.
-An example to run with npm would be `proxy=http://myzipkininstance.com:9411/zipkin/ npm run dev`. (note that prefixing with http:// and suffixing the port is mandatory)
-
-## What's the easiest way to develop against this locally?
-
-There is one requirement not currently filled in our build. You need a recent
-version of Chrome (>= 59) installed. If the build doesn't detect this, you can
-explicitly assign a location with the variable [CHROME_BIN](https://github.com/karma-runner/karma-chrome-launcher).
-
-The maven install process downloads everything else needed to do development,
-so you don't need to install node/npm or whatever. Instead, you can use the
-`./npm.sh` shell script to perform npm operations. Here's how you launch zipkin
-server and webapp to work together:
-
-* In one terminal, go to the root of the zipkin repo and run this to build zipkin:
-
-```bash
-# In one terminal, build the server and also make its dependencies (run from the root of the zipkin repo)
-$ mvn -DskipTests --also-make -pl zipkin-server clean install
-# Run it!
-$ java -jar ./zipkin-server/target/zipkin-server-*exec.jar
-```
-
-* In another terminal, launch the zipkin UI server:
-
-```bash
-# Do this in another terminal!
-$ cd zipkin-ui
-$ proxy=http://localhost:9411/zipkin/ ./npm.sh run dev
-```
-
-This runs an NPM development server, which will automatically rebuild the webapp
-when you change the source code. You should now be able to access your local
-copy of zipkin-ui at http://localhost:9090.
-
-## What's an easy way to create new spans for testing?
-
-Using this setup, if you open the web UI and find a trace from zipkin-server,
-you can download a JSON blob by right clicking on the JSON button on the trace
-page (top right) and doing a "Save As". Modify the span to your heart's content,
-and then you can submit the span(s) via curl:
-
-```bash
-$ curl -H "Content-Type: application/json" --data @span.json http://localhost:9411/api/v1/spans
-```
-
-## How do I find logs associated with a particular trace
-
-Since zipkin provides a correlation id (the trace id), it's a good pattern to add it in your logs.
-If you use zipkin, it's very likely that you have distributed logging sytem also and a way to query your logs (e.g. ELK stack).
-
-One convenient way to switch from a trace to its logs is to get a button which directly links a trace to its logs.
-
-This feature can be activated by setting the property `zipkin.ui.logs-url` or its corresponding environment variable:
-
-* Kibana 3: `ZIPKIN_UI_LOGS_URL=http://kibana.company.com/query={traceId}`
-* Kibana 4 or newer: `ZIPKIN_UI_LOGS_URL=http://kibana.company.com/app/kibana#/discover?_a=(index:'filebeat-*',query:(language:lucene,query:'{traceId}'))`
-  This assumes that the log data is stored under the `filebeat-*` index pattern (replace this with the correct pattern if needed) and it uses the default timerange of 15 minutes (change it to 24 hours by adding `&_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))` to the URL for example).
-
-where `{traceId}` will be contextually replaced by the trace id.
-
-If this feature is activated, you'll see on the trace detail page an additional button named `logs`.
-
-![Logs Button](https://cloud.githubusercontent.com/assets/9842366/20482538/6e35ca66-afed-11e6-90e9-1e28f66d985e.png)
-
-## How do I make errors visible in yellow or red?
-The UI interprets an "error" tag as a failed span, coloring it red. It interprets an annotation containing the substring
-"error" as a transient failure. To ensure the UI displays errors, please use the [error key](https://zipkin.apache.org/public/thrift/v1/zipkinCore.html#Const_ERROR) appropriately.
-
-## How do I adjust the error rates in the dependency graph
-
-By default, the /dependency endpoint colors a link yellow when the error
-rate is 50% or higher, or red when it 75% or higher. You can control
-these rates via the `dependency.low-error-rate` and `dependency.high-error-rate`
-properties:
-
-Ex. To make lines yellow when there's a 10% error rate, set:
-`ZIPKIN_UI_DEPENDENCY_LOW_ERROR_RATE=0.1`
-
-To disable coloring of lines, set both rates to a number higher than 1.
-
-## Running behind a reverse proxy
-Starting with Zipkin `1.31.2`, Zipkin UI supports running under an arbitrary _context root_. As a result, it can be proxied
-under a different path than `/zipkin/` such as `/proxy/foo/bar/zipkin/`.
-
-> Note that Zipkin requires the last path segment to be `zipkin`.
-
-> Also note that due to `html-webpack-plugin` limitations, Zipkin UI relies on a
-[`base` tag](https://www.w3schools.com/TAgs/tag_base.asp) and its `href` attribute to be set in the `index.html` file.
-By default its value is `/zipkin/` and as such the reverse proxy must rewrite the value to an alternate _context root_.
-
-### Apache HTTP as a Zipkin reverse proxy
-To configure Apache HTTP as a reverse proxy, following minimal configuration is required.
-
-```
-LoadModule proxy_module libexec/apache2/mod_proxy.so
-LoadModule proxy_html_module libexec/apache2/mod_proxy_html.so
-LoadModule proxy_http_module libexec/apache2/mod_proxy_http.so
-
-ProxyPass /proxy/foo/bar/ http://localhost:9411/
-SetOutputFilter proxy-html
-ProxyHTMLURLMap /zipkin/ /proxy/foo/bar/zipkin/
-ProxyHTMLLinks  base        href
-```
-
-To access Zipkin UI behind the reverse proxy, execute:
-```bash
-$ curl http://localhost/proxy/foo/bar/zipkin/
-<html><head><!--
-      add 'base' tag to work around the fact that 'html-webpack-plugin' does not work
-      with '__webpack_public_path__' being set as reported at https://github.com/jantimon/html-webpack-plugin/issues/119
-    --><base href="/proxy/foo/bar/zipkin/"><link rel="icon" type="image/x-icon" href="favicon.ico"><meta charset="UTF-8"><title>Webpack App</title><link href="app-94a6ee84dc608c5f9e66.min.css" rel="stylesheet"></head><body>
-  <script type="text/javascript" src="app-94a6ee84dc608c5f9e66.min.js"></script></body></html>
-```
-As you would see, the attribute `href` of the `base` tag is rewritten which is the way to get around the
-`html-webpack-plugin` limitations.
-
-Uploading the span is easy as
-```bash
-$ curl -H "Content-Type: application/json" --data-binary "[$(cat ../benchmarks/src/main/resources/span-local.json)]" http://localhost/proxy/foo/bar/api/v1/spans
-```
-
-And then it's observable in the UI:
-```bash
-$ open http://localhost/proxy/foo/bar/zipkin/?serviceName=zipkin-server&startTs=1378193040000&endTs=1505463856013
-```
-### How do I configure security (authentication, authorization)? 
-
-Zipkin UI can be secured by running it behind an authenticating proxy like [Apache HTTPD](https://httpd.apache.org/docs/current/howto/auth.html), [Nginx](https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html) or similar. Make sure to also consult the [notes](#apache-http-as-a-zipkin-reverse-proxy) on running apache http as a reverse proxy for the UI, as it can be a bit tricky.
-
-Note that by default, a Zipkin server runs both the UI ('/zipkin') and the span collector ('/api') endpoint. Your configuration to secure the UI should only target the UI endpoint in order to not prevent clients from ingesting span data.
diff --git a/zipkin-ui/bower.json b/zipkin-ui/bower.json
deleted file mode 100644
index a87cb47..0000000
--- a/zipkin-ui/bower.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "name": "zipkin",
-  "version": "0.0.0",
-  "dependencies": {
-    "chosen": "https://github.com/harvesthq/chosen/releases/download/v1.1.0/chosen_v1.1.0.zip",
-    "dagre-d3": "~0.2.9"
-  }
-}
diff --git a/zipkin-ui/css/dependency.css b/zipkin-ui/css/dependency.css
deleted file mode 100644
index c1efac7..0000000
--- a/zipkin-ui/css/dependency.css
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-svg {
-  overflow: hidden;
-  font-size: 12px;
-}
-
-.node rect {
-  stroke: #333;
-  stroke-width: 1px;
-}
-
-.node text {
-  -webkit-user-select: none;
-  -moz-user-select: none;
-  -ms-user-select: none;
-  user-select: none;
-  cursor: default;
-}
-
-.edgeLabel rect {
-  fill: #fff;
-}
-
-.edgePath {
-  stroke: #333;
-  stroke-width: 1px;
-  fill: none;
-}
-
-.edgePath path {
-  padding: 20px;
-}
-
-svg {
-  transition: background-color 800ms;
-}
-
-svg.dark {
-  background-color: #7a7a7a;
-}
-
-svg.dark .node:not(.hover):not(.hover-light) rect {
-  opacity: 0.3;
-}
-
-svg.dark g.node {
-  fill: #fff;
-}
-
-g.hover {
-  fill: #fff;
-}
-
-g.hover rect {
-  fill: #4058ff;
-}
-
-g.hover-edge {
-  stroke: #4058ff;
-}
-
-g.hover-light rect {
-  fill: #8a9eff;
-}
diff --git a/zipkin-ui/css/main.scss b/zipkin-ui/css/main.scss
deleted file mode 100644
index 4f79ea0..0000000
--- a/zipkin-ui/css/main.scss
+++ /dev/null
@@ -1,74 +0,0 @@
-//
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-@import "~bootstrap/scss/bootstrap.scss";
-@import "~bootstrap4c-chosen/dist/css/component-chosen.min.css";
-@import "~bootstrap-datepicker/dist/css/bootstrap-datepicker3.css";
-$fa-font-path: "~@fortawesome/fontawesome-free/webfonts" !default;
-@import "~@fortawesome/fontawesome-free/scss/fontawesome.scss";    
-@import "~@fortawesome/fontawesome-free/scss/regular.scss";    
-@import "~@fortawesome/fontawesome-free/scss/solid.scss";    
-
-@import "dependency";
-@import "summary";
-@import "trace";
-@import "traces";
-
-.content {
-  padding: 2%;
-}
-
-.chosen-container .chosen-single {
-  height: 38px;
-}
-
-#errorPanel {
-  display: none;
-}
-
-#fullPageSpinner {
-  display: none;
-  position: absolute;
-  width: 100%;
-  height: 100%;
-  top: 0px;
-  padding-top: 10%;
-  background: rgba(256, 256, 256, 0.5);
-}
-
-#fullPageSpinner .progress {
-  margin-left: 25%;
-  width: 50%;
-}
-
-/*for page up button*/
-#backToTop {
-  background-color: #0084B4;
-}
-
-/*for zoomout button*/
-#zoomOutSpans {
-  background-color: #d9534f;
-}
-
-/*container for zoomout, pageup buttons*/
-#extraButtonsContainer {
-  position: fixed;
-  bottom: 0;
-  right: 0;
-  z-index: 100;
-}
diff --git a/zipkin-ui/css/style-loader.js b/zipkin-ui/css/style-loader.js
deleted file mode 100644
index 7b0a481..0000000
--- a/zipkin-ui/css/style-loader.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// We use this file to let webpack generate
-// a css bundle from our stylesheets.
-
-// The import of 'publicPath' module has to be the first statement in this entry point file
-// so that '__webpack_public_path__' (see https://webpack.github.io/docs/configuration.html#output-publicpath)
-// is set soon enough.
-// In the same time, 'contextRoot' is made available as the context root path reference.
-import {contextRoot} from '../js/publicPath';
-
-require('./main.scss');
diff --git a/zipkin-ui/css/summary.css b/zipkin-ui/css/summary.css
deleted file mode 100644
index 27b7f05..0000000
--- a/zipkin-ui/css/summary.css
+++ /dev/null
@@ -1,180 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#summary {
-  list-style: none;
-  margin: 10px 0 0 0;
-  padding: 0;
-}
-
-#summary a {
-  text-decoration: none;
-}
-
-#summary li {
-  display: block;
-  padding: 5px;
-  padding-bottom: 20px;
-  margin: 20px 0px 20px;
-  list-style: none;
-
-  -webkit-border-radius: 4px;
-  -moz-border-radius: 4px;
-  border-radius: 4px;
-
-  background-color: rgba(255,255,255,1);
-
-  -webkit-transition: background-color .3s;
-}
-
-#summary li:after {
-  content: '';
-  display: block;
-  width: 75%;
-  margin: 15px auto -35px;
-  border-bottom: 1px solid #EEE;
-}
-
-#summary li:first-child {
-  margin-top: 0px;
-}
-
-#summary li:hover {
-  background-color: rgba(221,238,246,1);
-}
-
-
-#summary li div {
-  display: block;
-  padding: 2px 0px;
-  margin-bottom: 0px;
-  color: #999;
-
-  -webkit-transition: .3s;
-}
-
-#summary li .bar-block {
-  position: relative;
-  line-height: 20px;
-  padding: 0px;
-  margin: 2px 0;
-  background-color: rgba(255, 255, 255, .75);
-
-  -webkit-border-radius: 4px;
-  -moz-border-radius: 4px;
-  border-radius: 4px;
-}
-
-#summary li .bar-graphic {
-  position: absolute;
-  top: 0px;
-  left: 0px;
-  display: block;
-  height: 20px;
-  z-index: 0;
-
-  -webkit-border-radius: 4px;
-  -moz-border-radius: 4px;
-  border-radius: 4px;
-
-  background-color: #c0deed;
-
-  -webkit-transition: .3s;
-}
-
-#summary li:hover .bar-graphic {
-  background-color: #0084B4;
-}
-
-#summary li .bar-label {
-  position: relative;
-  margin-left: 6px;
-  padding: 2px 4px;
-  color: #0084B4;
-  font-weight: bold;
-  z-index: 1;
-
-  -webkit-border-radius: 4px;
-  -moz-border-radius: 4px;
-  border-radius: 4px;
-
-  -webkit-transition: .3s;
-}
-
-#summary li:hover .bar-label {
-  color: #FFF;
-  background-color: rgba(0,132,180,.35);
-}
-
-#summary .trace-id {
-  font-size: 85%;
-  color: #0084B4;
-  background-color: rgba(0,132,180,.35);
-  border-radius: 4px;
-}
-
-#summary .trace-id:hover {
-  font-size: 85%;
-  color: #FFF;
-  background-color: #0084B4;
-}
-
-#summary li div span.label {
-  -webkit-transition: .3s;
-}
-
-#summary li div p {
-  margin-bottom: 0;
-  font-size: 11px;
-  font-weight: bold;
-  line-height: 11px;
-}
-
-#summary li .summary-details {
-  display: inline-block;
-}
-
-#summary li .durations {
-  width: 80%;
-}
-
-#summary li .durations span {
-  cursor: pointer;
-}
-
-#summary li .timestamp {
-  width: 19%;
-  text-align: right;
-}
-
-#summary li:hover .durations .label {
-  background-color: #F89406;
-}
-
-#summary li .durations .label:hover,
-#summary li .durations .label:hover span {
-  background-color: red;
-}
-
-#summary li .timestamp .label {
-  background-color: #FAFAFA;
-  color: #AAA;
-}
-
-#summary li:hover .timestamp .label {
-  background-color: #FFF;
-  color: #666;
-}
diff --git a/zipkin-ui/css/trace.css b/zipkin-ui/css/trace.css
deleted file mode 100644
index c1eed1a..0000000
--- a/zipkin-ui/css/trace.css
+++ /dev/null
@@ -1,232 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#trace-container {
-  font-size: 12px;
-  margin-bottom: 50px;
-}
-
-#trace-container .tag {
-  display: none;
-}
-
-#trace-container .rect-element {
-  border-style: dashed;
-  border-width: medium;
-  position: absolute;
-  z-index: 1;
-  border-color: #d9534f;
-}
-
-#trace-container .span {
-  cursor: pointer;
-  display: none;
-  position: relative;
-  border-bottom: solid 1px #fff;
-  border-top: solid 1px #fff;
-}
-
-#trace-container .service-span:hover {
-  background: #eee;
-  border-color: #ccc;
-}
-
-#trace-container .span:after {
-  content: ".";
-  display: block;
-  clear: both;
-  visibility: hidden;
-  line-height: 0;
-  height: 0;
-}
-
-#trace-container .span .handle {
-  overflow: hidden;
-  white-space: nowrap;
-  float: left;
-  opacity: 0.25;
-}
-
-/* ensure spans are shown even if the don't have a name */
-#trace-container .span .handle .service-name:after {
-  content: ".";
-  visibility: hidden;
-}
-
-#trace-container .span.highlight .handle,
-#trace-container .service-span:hover .handle {
-  opacity: 1;
-}
-
-#trace-container .span .handle .expander {
-  margin-left: -1.1em;
-  color: #000;
-  padding: 2px;
-  margin-right: 0.25em;
-  cursor: pointer;
-  font-family: monospace;
-}
-
-#trace-container .span .handle .service-name {
-  background: #0084b4;
-  color: #fff;
-  padding: 2px;
-  max-width: 12em;
-}
-
-#trace-container .span .duration-container {
-  position: relative;
-  margin-left: 155px;
-}
-
-#trace-container #timeLabel.span {
-  overflow: visible;
-  display: block;
-  white-space: nowrap;
-}
-
-#trace-container #timeLabel.span .handle {
-  opacity: 1;
-}
-
-#trace-container #timeLabel.span .time-marker {
-  margin-left: -1%;
-}
-
-#trace-container .span .time-marker {
-  position: absolute;
-  color: #333;
-}
-
-#trace-container .span .time-marker-0 { left: 0; width: 1px;}
-#trace-container .span .time-marker-1 { left: 20%; width: 1px;}
-#trace-container .span .time-marker-2 { left: 40%; width: 1px;}
-#trace-container .span .time-marker-3 { left: 60%; width: 1px;}
-#trace-container .span .time-marker-4 { left: 80%; width: 1px;}
-#trace-container .span .time-marker-5 { left: 99%; width: 1px;}
-
-#trace-container .span .duration {
-  position: absolute;
-  background: #ccc;
-  overflow: visible;
-  white-space: nowrap;
-  padding: 2px;
-}
-
-#trace-container .span .duration .annotation {
-  position: absolute;
-  top: 5px;
-  height: 10px;
-  width: 10px;
-  background: #fff;
-  border: solid 1px #000;
-  border-radius: 1em;
-  margin-left: -6px;
-}
-
-/*
- * Derived annotations like "Server Start" "Server Finish" are hidden to make
- * the user-defined ones more obvious.
- */
-#trace-container .span .duration .annotation.derived {
-  display: none;
-}
-
-#trace-container .span .duration .annotation[data-value="error"] {
-  border: solid 1px #ff0000;
-  background: #ff0000;
-}
-
-#trace-container .span .duration .tooltip {
-  white-space: normal;
-}
-
-#trace-container .span.depth-0 .duration { background: rgba(66, 146, 198, 0.1); }
-#trace-container .span.depth-1 .duration { background: rgba(66, 146, 198, 0.20); }
-#trace-container .span.depth-2 .duration { background: rgba(66, 146, 198, 0.40); }
-#trace-container .span.depth-3 .duration { background: rgba(66, 146, 198, 0.60); }
-#trace-container .span.depth-4 .duration { background: rgba(66, 146, 198, 0.80); }
-#trace-container .span.depth-5 .duration { background: rgba(66, 146, 198, 1); }
-
-#trace-container .span[data-error-type="transient"] .handle .service-name {
-  background: #8a6d3b;
-}
-
-#trace-container .span[data-error-type="transient"].depth-0 .duration { background: rgba(230, 215, 140, 0.1); }
-#trace-container .span[data-error-type="transient"].depth-1 .duration { background: rgba(230, 215, 140, 0.20); }
-#trace-container .span[data-error-type="transient"].depth-2 .duration { background: rgba(230, 215, 140, 0.40); }
-#trace-container .span[data-error-type="transient"].depth-3 .duration { background: rgba(230, 215, 140, 0.60); }
-#trace-container .span[data-error-type="transient"].depth-4 .duration { background: rgba(230, 215, 140, 0.80); }
-#trace-container .span[data-error-type="transient"].depth-5 .duration { background: rgba(230, 215, 140, 1); }
-
-#trace-container .span[data-error-type="critical"] .handle .service-name {
-  background: #a94442;
-}
-
-#trace-container .span[data-error-type="critical"].depth-0 .duration { background: rgba(230, 162, 161, 0.1); }
-#trace-container .span[data-error-type="critical"].depth-1 .duration { background: rgba(230, 162, 161, 0.20); }
-#trace-container .span[data-error-type="critical"].depth-2 .duration { background: rgba(230, 162, 161, 0.40); }
-#trace-container .span[data-error-type="critical"].depth-3 .duration { background: rgba(230, 162, 161, 0.60); }
-#trace-container .span[data-error-type="critical"].depth-4 .duration { background: rgba(230, 162, 161, 0.80); }
-#trace-container .span[data-error-type="critical"].depth-5 .duration { background: rgba(230, 162, 161, 1); }
-
-#trace-controls form {
-  margin-bottom: 10px;
-}
-
-/* hide the fact that these are links */
-#trace-controls .nav-pills a {
-  color: #000;
-  cursor: default;
-  background: rgba(0, 0, 0, 0);
-}
-
-#trace-controls .nav-pills .badge {
-  background: #428bca;
-}
-
-#spanPanel tr.anno-error-transient {
-  color: #8a6d3b;
-  background: #fcf8e3;
-}
-
-#spanPanel tr.anno-error-critical {
-  color: #a94442;
-  background: #f2dede;
-}
-
-#spanPanel #tags td.value {
-  word-break: break-all;
-  max-width: 75%;
-}
-#spanPanel #annotations td.value {
-  word-break: break-all;
-  max-width: 75%;
-}
-#spanPanel #debugInfo td.value {
-  word-break: break-all;
-  max-width: 75%;
-}
-
-.modal-header .save {
-  float: right;
-  margin-right: 5px;
-}
-
-/* .save {
-  opacity: 0.2;
-} */
-
diff --git a/zipkin-ui/css/traces.css b/zipkin-ui/css/traces.css
deleted file mode 100644
index f4ad956..0000000
--- a/zipkin-ui/css/traces.css
+++ /dev/null
@@ -1,216 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#trace-filters .label {
-  margin-right: 5px;
-}
-
-.date-input, .time-input {
-  width: 49%;
-  display: inline-block;
-}
-
-.service-filter-remove {
-  cursor: pointer;
-  border-radius: 0;
-  margin-left: 5px;
-}
-
-.service-filter-remove:hover {
-  background: red;
-}
-
-#traces {
-  list-style: none;
-  margin: 10px 0 0 0;
-  padding: 0;
-}
-
-#traces a {
-  text-decoration: none;
-}
-
-#traces li {
-  display: block;
-  padding: 5px 0;
-  padding-bottom: 20px;
-  margin: 20px 0px 20px;
-  list-style: none;
-
-  -webkit-border-radius: 4px;
-  -moz-border-radius: 4px;
-  border-radius: 4px;
-
-  background-color: rgba(255,255,255,1);
-
-  -webkit-transition: background-color .3s;
-}
-
-#traces li:after {
-  content: '';
-  display: block;
-  width: 75%;
-  margin: 15px auto -35px;
-  border-bottom: 1px solid #EEE;
-}
-
-#traces li:first-child {
-  margin-top: 0px;
-}
-
-#traces li:hover {
-  background-color: rgba(221,238,246,1);
-}
-
-
-#traces li div {
-  display: block;
-  padding: 2px 0px;
-  margin-bottom: 0px;
-  color: #999;
-
-  -webkit-transition: .3s;
-}
-
-#traces li .bar-block {
-  position: relative;
-  line-height: 20px;
-  padding: 0px;
-  margin: 2px 0;
-  background-color: rgba(255, 255, 255, .75);
-
-  -webkit-border-radius: 4px;
-  -moz-border-radius: 4px;
-  border-radius: 4px;
-}
-
-#traces li .bar-graphic {
-  position: absolute;
-  top: 0px;
-  left: 0px;
-  display: block;
-  height: 20px;
-  z-index: 0;
-
-  -webkit-border-radius: 4px;
-  -moz-border-radius: 4px;
-  border-radius: 4px;
-
-  background-color: #c0deed;
-
-  -webkit-transition: .3s;
-}
-
-#traces li:hover .bar-graphic {
-  background-color: #0084B4;
-}
-
-#traces li.trace-error-transient .bar-graphic {
-  background-color: #fcf8e3;;
-}
-
-#traces li.trace-error-transient:hover .bar-graphic {
-  background-color: #8a6d3b;
-}
-
-#traces li.trace-error-critical .bar-graphic {
-  background-color: #f2dede;
-}
-
-#traces li.trace-error-critical:hover .bar-graphic {
-  background-color: #a94442;
-}
-
-#traces li .bar-label {
-  position: relative;
-  margin-left: 6px;
-  padding: 2px 4px;
-  color: #0084B4;
-  font-weight: bold;
-  z-index: 1;
-
-  -webkit-border-radius: 4px;
-  -moz-border-radius: 4px;
-  border-radius: 4px;
-
-  -webkit-transition: .3s;
-}
-
-#traces li:hover .bar-label {
-  color: #FFF;
-  background-color: rgba(0,132,180,.35);
-}
-
-#traces li.trace-error-transient .bar-label {
-  color: #8a6d3b;
-}
-
-#traces li.trace-error-transient:hover .bar-label {
-  color: #FFF;
-  background-color: rgba(138,109,59,.35);
-}
-
-#traces li.trace-error-critical .bar-label {
-  color: #a94442;
-}
-
-#traces li.trace-error-critical:hover .bar-label {
-  color: #FFF;
-  background-color: rgba(169,68,66,.35);
-}
-
-#traces li div span.label {
-  -webkit-transition: .3s;
-}
-
-#traces li div p {
-  margin-bottom: 0;
-  font-size: 11px;
-  font-weight: bold;
-  line-height: 11px;
-}
-
-#traces li .trace-details {
-  display: inline-block;
-}
-
-#traces li .services {
-  width: 80%;
-}
-
-#traces li .timestamp {
-  width: 19%;
-  text-align: right;
-}
-
-#traces li:hover .services .label {
-  background-color: #F89406;
-}
-
-#traces li .services .label:hover,
-#traces li .services .label:hover span {
-  background-color: red;
-}
-
-#traces li .timestamp .label {
-  background-color: #FAFAFA;
-  color: #AAA;
-}
-
-#traces li:hover .timestamp .label {
-  background-color: #FFF;
-  color: #666;
-}
diff --git a/zipkin-ui/index.ejs b/zipkin-ui/index.ejs
deleted file mode 100644
index da4fa9d..0000000
--- a/zipkin-ui/index.ejs
+++ /dev/null
@@ -1,49 +0,0 @@
-<!--
-
-    Licensed to the Apache Software Foundation (ASF) under one or more
-    contributor license agreements.  See the NOTICE file distributed with
-    this work for additional information regarding copyright ownership.
-    The ASF licenses this file to You under the Apache License, Version 2.0
-    (the "License"); you may not use this file except in compliance with
-    the License.  You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
--->
-<!DOCTYPE html>
-<html>
-  <head>
-    <!--
-      add 'base' tag to work around the fact that 'html-webpack-plugin' does not work
-      with '__webpack_public_path__' being set as reported at https://github.com/jantimon/html-webpack-plugin/issues/119
-    -->
-    <base href="<%= htmlWebpackPlugin.options.contextRoot %>">
-
-    <link rel="icon" type="image/x-icon" href="favicon.ico">
-
-    <meta charset="UTF-8">
-    <title><%= htmlWebpackPlugin.options.title %></title>
-  </head>
-  <body>
-
-    <!-- we include a reduced header on page load, as this makes the page load feel much smoother -->
-
-    <div class='navbar navbar-inverse' role='navigation'>
-      <div class='container'>
-        <div class='navbar-header'>
-          <a class='navbar-brand' href='#'>
-            Zipkin<span class='muted' style='font-size: .75em; padding-left: 10px;' data-i18n="nav.inves">Investigate system behavior</span>
-          </a>
-        </div>
-        </div><!--/.nav-collapse -->
-      </div>
-    </div>
-
-  </body>
-</html>
diff --git a/zipkin-ui/js/component_data/default.js b/zipkin-ui/js/component_data/default.js
deleted file mode 100644
index 10bc2f8..0000000
--- a/zipkin-ui/js/component_data/default.js
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import {errToStr} from '../../js/component_ui/error';
-import $ from 'jquery';
-import queryString from 'query-string';
-import {traceSummary, traceSummariesToMustache} from '../component_ui/traceSummary';
-import {treeCorrectedForClockSkew} from '../component_data/skew';
-
-const debug = false;
-
-export function convertDurationToMicrosecond(duration) {
-  const match = duration.match(/^(\d+)(us|μs|ms|s)$/i);
-  if (match) {
-    const unit = match[2];
-    switch (unit) {
-      case 'us':
-      case 'μs':
-        return match[1];
-      case 'ms':
-        return String((Number(match[1]) * 1000));
-      case 's':
-        return String((Number(match[1]) * Math.pow(1000, 2)));
-      default:
-        // Do nothing
-    }
-  }
-  return duration;
-}
-
-export function convertToApiQuery(source) {
-  const query = Object.assign({}, source);
-
-  if (query.minDuration) {
-    query.minDuration = convertDurationToMicrosecond(query.minDuration);
-  }
-
-  // zipkin's api looks back from endTs
-  if (query.lookback !== 'custom') {
-    delete query.startTs;
-    delete query.endTs;
-  }
-  if (query.startTs) {
-    if (query.endTs > query.startTs) {
-      query.lookback = String(query.endTs - query.startTs);
-    }
-    delete query.startTs;
-  }
-  if (query.lookback === 'custom') {
-    delete query.lookback;
-  }
-  if (query.serviceName === 'all') {
-    delete query.serviceName;
-  }
-  if (query.remoteServiceName === 'all') {
-    delete query.remoteServiceName;
-  }
-  if (query.spanName === 'all') {
-    delete query.spanName;
-  }
-  // delete any parameters unused on the server
-  Object.keys(query).forEach(key => {
-    if (query[key] === '') {
-      delete query[key];
-    }
-  });
-  delete query.sortOrder;
-  return query;
-}
-
-// Converts the response into data for index.mustache. Traces missing required data are skipped.
-export function convertSuccessResponse(rawResponse, serviceName, apiURL, utc = false) {
-  const summaries = [];
-  rawResponse.forEach((raw) => {
-    if (raw.length === 0) return;
-
-    const corrected = treeCorrectedForClockSkew(raw);
-    try {
-      summaries.push(traceSummary(corrected));
-    } catch (e) {
-      /* eslint-disable no-console */
-      if (debug) console.log(e.toString());
-    }
-  });
-
-  // Take the summaries and convert them to template parameters for index.mustache
-  let traces = [];
-  if (summaries.length > 0) {
-    traces = traceSummariesToMustache(serviceName, summaries, utc);
-  }
-  return {traces, apiURL, rawResponse};
-}
-
-export default component(function DefaultData() {
-  this.after('initialize', function() {
-    const query = queryString.parse(window.location.search);
-    const serviceName = query.serviceName;
-    if (!serviceName) {
-      this.trigger('defaultPageModelView', {traces: []});
-      return;
-    }
-    const apiQuery = convertToApiQuery(query);
-    const apiURL = `api/v2/traces?${queryString.stringify(apiQuery)}`;
-    $.ajax(apiURL, {
-      type: 'GET',
-      dataType: 'json'
-    }).done(rawTraces => {
-      this.trigger('defaultPageModelView', convertSuccessResponse(rawTraces, serviceName, apiURL));
-    }).fail(e => {
-      this.trigger('defaultPageModelView', {traces: [], queryError: errToStr(e)});
-    });
-  });
-});
diff --git a/zipkin-ui/js/component_data/dependency.js b/zipkin-ui/js/component_data/dependency.js
deleted file mode 100644
index 930b8d6..0000000
--- a/zipkin-ui/js/component_data/dependency.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import moment from 'moment';
-import $ from 'jquery';
-
-export default component(function dependency() {
-  let services = {};
-  let dependencies = {};
-
-  this.getDependency = function(endTs, lookback) {
-    let url = `api/v2/dependencies?endTs=${endTs}`;
-    if (lookback) {
-      url += `&lookback=${lookback}`;
-    }
-    $.ajax(url, {
-      type: 'GET',
-      dataType: 'json',
-      success: links => {
-        this.links = links.sort((a, b) => a.parent - b.parent || a.child - b.child);
-        this.buildServiceData(links);
-        this.trigger('dependencyDataReceived', links);
-      },
-      failure: (jqXHR, status, err) => {
-        const error = {
-          message: `Couldn't get dependency data from backend: ${err}`
-        };
-        this.trigger('dependencyDataFailed', error);
-      }
-    });
-  };
-
-  this.buildServiceData = function(links) {
-    services = {};
-    dependencies = {};
-    links.forEach(link => {
-      const {parent, child} = link;
-
-      dependencies[parent] = dependencies[parent] || {};
-      dependencies[parent][child] = link;
-
-      services[parent] = services[parent] || {serviceName: parent, uses: [], usedBy: []};
-      services[child] = services[child] || {serviceName: child, uses: [], usedBy: []};
-
-      services[parent].uses.push(child);
-      services[child].usedBy.push(parent);
-    });
-  };
-
-  this.after('initialize', function() {
-    this.on(document, 'dependencyDataRequested', function(event, {endTs, lookback}) {
-      this.getDependency(endTs, lookback);
-    });
-
-    this.on(document, 'serviceDataRequested', function(event, {serviceName}) {
-      this.getServiceData(serviceName, data => {
-        this.trigger(document, 'serviceDataReceived', data);
-      });
-    });
-
-    this.on(document, 'parentChildDataRequested', function(event, {parent, child}) {
-      this.getDependencyData(parent, child, data => {
-        this.trigger(document, 'parentChildDataReceived', data);
-      });
-    });
-
-    const endTs = document.getElementById('endTs').value || moment().valueOf();
-    const startTs = document.getElementById('startTs').value;
-    let lookback;
-    if (startTs && endTs > startTs) {
-      lookback = endTs - startTs;
-    }
-    this.getDependency(endTs, lookback);
-  });
-
-  this.getServiceData = function(serviceName, callback) {
-    callback(services[serviceName]);
-  };
-
-  this.getDependencyData = function(parent, child, callback) {
-    callback(dependencies[parent][child]);
-  };
-});
diff --git a/zipkin-ui/js/component_data/remoteServiceNames.js b/zipkin-ui/js/component_data/remoteServiceNames.js
deleted file mode 100644
index 72d2489..0000000
--- a/zipkin-ui/js/component_data/remoteServiceNames.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import {getError} from '../../js/component_ui/error';
-import $ from 'jquery';
-
-export default component(function remoteServiceNames() {
-  this.updateRemoteServiceNames = function(ev, serviceName) {
-    if (!serviceName) {
-      this.trigger('dataRemoteServiceNames', {remoteServices: []});
-      return;
-    }
-    $.ajax(`api/v2/remoteServices?serviceName=${serviceName}`, {
-      type: 'GET',
-      dataType: 'json'
-    }).done(remoteServices => {
-      this.trigger('dataRemoteServiceNames', {remoteServices: remoteServices.sort()});
-    }).fail(e => {
-      if (e.status && e.status === 404) { // remote service names is a new endpoint
-        this.trigger('dataRemoteServiceNames', {remoteServices: []});
-        return;
-      }
-      this.trigger('uiServerError', getError('cannot load remote service names', e));
-    });
-  };
-
-  this.after('initialize', function() {
-    this.on('uiChangeServiceName', this.updateRemoteServiceNames);
-    this.on('uiFirstLoadRemoteServiceNames', this.updateRemoteServiceNames);
-  });
-});
diff --git a/zipkin-ui/js/component_data/serviceNames.js b/zipkin-ui/js/component_data/serviceNames.js
deleted file mode 100644
index 8d0a31b..0000000
--- a/zipkin-ui/js/component_data/serviceNames.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import {getError} from '../../js/component_ui/error';
-import $ from 'jquery';
-
-export default component(function serviceNames() {
-  this.updateServiceNames = function(ev, lastServiceName) {
-    $.ajax('api/v2/services', {
-      type: 'GET',
-      dataType: 'json'
-    }).done(names => {
-      this.trigger('dataServiceNames', {names: names.sort(), lastServiceName});
-    }).fail(e => {
-      this.trigger('uiServerError', getError('cannot load service names', e));
-    });
-  };
-
-  this.after('initialize', function() {
-    this.on('uiChangeServiceName', this.updateServiceNames);
-  });
-});
diff --git a/zipkin-ui/js/component_data/skew.js b/zipkin-ui/js/component_data/skew.js
deleted file mode 100644
index 8eae704..0000000
--- a/zipkin-ui/js/component_data/skew.js
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {SpanNode, SpanNodeBuilder} from './spanNode';
-
-class ClockSkew {
-  constructor(params) {
-    const {endpoint, skew} = params;
-    this._endpoint = endpoint;
-    this._skew = skew;
-  }
-
-  get endpoint() {
-    return this._endpoint;
-  }
-
-  get skew() {
-    return this._skew;
-  }
-}
-
-function ipsMatch(a, b) {
-  if (!a || !b) return false;
-  if (a.ipv6 && b.ipv6 && a.ipv6 === b.ipv6) {
-    return true;
-  }
-  if (!a.ipv4 && !b.ipv4) return false;
-  return a.ipv4 === b.ipv4;
-}
-
-// If any annotation has an IP with skew associated, adjust accordingly.
-function adjustTimestamps(span, skew) {
-  if (!ipsMatch(skew.endpoint, span.localEndpoint)) return span;
-
-  const result = Object.assign({}, span);
-  if (span.timestamp) result.timestamp = span.timestamp - skew.skew;
-  const annotationLength = span.annotations.length;
-  if (annotationLength > 0) result.annotations = [];
-  for (let i = 0; i < annotationLength; i++) {
-    const a = span.annotations[i];
-    result.annotations[i] = {timestamp: a.timestamp - skew.skew, value: a.value};
-  }
-  return result;
-}
-
-/* Uses span kind to determine if there's clock skew. */
-function getClockSkew(node) {
-  const parent = node.parent ? node.parent.span : undefined;
-  const child = node.span;
-  if (!parent) return undefined;
-
-  // skew is only detectable client to server
-  if (parent.kind !== 'CLIENT' || child.kind !== 'SERVER') return undefined;
-
-  let oneWay = false;
-  const clientTimestamp = parent.timestamp;
-  const serverTimestamp = child.timestamp;
-  if (!clientTimestamp || !serverTimestamp) return undefined;
-
-  // skew is when the server happens before the client
-  if (serverTimestamp > clientTimestamp) return undefined;
-
-  const clientDuration = parent.duration;
-  const serverDuration = child.duration;
-  if (!clientDuration || !serverDuration) oneWay = true;
-
-  const server = child.localEndpoint;
-  if (!server) return undefined;
-  const client = parent.localEndpoint;
-  if (!client) return undefined;
-
-  // There's no skew if the RPC is going to itself
-  if (ipsMatch(server, client)) return undefined;
-
-  if (oneWay) {
-    const latency = serverTimestamp - clientTimestamp;
-
-    // the only way there is skew is when the client appears to be after the server
-    if (latency > 0) return undefined;
-    // We can't currently do better than push the client and server apart by minimum duration (1)
-    return new ClockSkew({endpoint: server, skew: latency - 1});
-  } else {
-    // If the client finished before the server (async), we still know the server must have happened
-    // after the client. So, push 1us.
-    if (clientDuration < serverDuration) {
-      const skew = serverTimestamp - clientTimestamp - 1;
-      return new ClockSkew({endpoint: server, skew});
-    }
-
-    // We assume latency is half the difference between the client and server duration.
-    const latency = (clientDuration - serverDuration) / 2;
-
-    // We can't see skew when send happens before receive
-    if (latency < 0) return undefined;
-
-    const skew = serverTimestamp - latency - clientTimestamp;
-    if (skew !== 0) return new ClockSkew({endpoint: server, skew});
-  }
-  return undefined;
-}
-
-/*
- * Recursively adjust the timestamps on the span trace. Root span is the reference point, all
- * children's timestamps gets adjusted based on that span's timestamps.
- */
-function adjust(node, skewFromParent) {
-  // adjust skew for the endpoint brought over from the parent span
-  if (skewFromParent) {
-    node.setSpan(adjustTimestamps(node.span, skewFromParent));
-  }
-
-  // Is there any skew in the current span?
-  let skew = getClockSkew(node);
-  if (skew) {
-    // the current span's skew may be a different endpoint than its parent, so adjust again.
-    node.setSpan(adjustTimestamps(node.span, skew));
-  } else if (skewFromParent) {
-    // Assumes we are on the same host: propagate skew from our parent
-    skew = skewFromParent;
-  }
-  // propagate skew to any children
-  node.children.forEach(child => adjust(child, skew));
-}
-
-function treeCorrectedForClockSkew(spans, debug = false) {
-  if (spans.length === 0) return new SpanNode();
-
-  const trace = new SpanNodeBuilder({debug}).build(spans);
-
-  if (!trace.span) {
-    if (debug) {
-      /* eslint-disable no-console */
-      console.log(
-        `skipping clock skew adjustment due to missing root span: traceId=${spans[0].traceId}`
-      );
-    }
-    return trace;
-  }
-
-  const childrenOfRoot = trace.children;
-  for (let i = 0; i < childrenOfRoot.length; i++) {
-    const next = childrenOfRoot[i].span;
-    if (next.parentId || next.shared) continue;
-
-    const traceId = next.traceId;
-    const spanId = next.id;
-    const rootSpanId = trace.span.id;
-    if (debug) {
-      /* eslint-disable no-console */
-      const prefix = 'skipping redundant root span';
-      console.log(`${prefix}: traceId=${traceId}, rootSpanId=${rootSpanId}, spanId=${spanId}`);
-    }
-    return trace;
-  }
-
-  adjust(trace);
-  return trace;
-}
-
-
-module.exports = {
-  ipsMatch, // for testing
-  getClockSkew, // for testing
-  treeCorrectedForClockSkew
-};
diff --git a/zipkin-ui/js/component_data/spanCleaner.js b/zipkin-ui/js/component_data/spanCleaner.js
deleted file mode 100644
index c119dcc..0000000
--- a/zipkin-ui/js/component_data/spanCleaner.js
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import _ from 'lodash';
-
-export function normalizeTraceId(traceId) {
-  if (traceId.length > 16) {
-    const result = traceId.padStart(32, '0');
-    // undo prefix if it will result in a 64-bit trace ID
-    if (result.startsWith('0000000000000000')) return result.substring(16);
-    return result;
-  }
-  return traceId.padStart(16, '0');
-}
-
-function isEndpoint(endpoint) {
-  return endpoint && Object.keys(endpoint).length > 0;
-}
-
-// This cleans potential dirty v2 inputs, like normalizing IDs etc. It does not affect the input
-export function clean(span) {
-  const res = {
-    traceId: normalizeTraceId(span.traceId)
-  };
-
-  // take care not to create self-referencing spans even if the input data is incorrect
-  const id = span.id.padStart(16, '0');
-  if (span.parentId) {
-    const parentId = span.parentId.padStart(16, '0');
-    if (parentId !== id) res.parentId = parentId;
-  }
-  res.id = id;
-
-  if (span.name && span.name !== '' && span.name !== 'unknown') res.name = span.name;
-  if (span.kind) res.kind = span.kind;
-
-  if (span.timestamp) res.timestamp = span.timestamp;
-  if (span.duration) res.duration = span.duration;
-
-  if (isEndpoint(span.localEndpoint)) res.localEndpoint = Object.assign({}, span.localEndpoint);
-  if (isEndpoint(span.remoteEndpoint)) res.remoteEndpoint = Object.assign({}, span.remoteEndpoint);
-
-  res.annotations = span.annotations ? span.annotations.slice(0) : [];
-  if (res.annotations.length > 1) {
-    res.annotations = _(_.unionWith(res.annotations, _.isEqual))
-            .sortBy('timestamp', 'value').value();
-  }
-
-  res.tags = span.tags || {};
-
-  if (span.debug) res.debug = true;
-  // shared is for the server side, unset it if accidentally set on the client side
-  if (span.shared && span.kind !== 'CLIENT') res.shared = true;
-
-  return res;
-}
-
-// exposed for testing. assumes spans are already clean
-export function merge(left, right) {
-  const res = {
-    traceId: right.traceId.length > 16 ? right.traceId : left.traceId
-  };
-
-  const parentId = left.parentId || right.parentId;
-  if (parentId) res.parentId = parentId;
-
-  res.id = left.id;
-  const name = left.name || right.name;
-  if (name) res.name = name;
-
-  const kind = left.kind || right.kind;
-  if (kind) res.kind = kind;
-
-  const timestamp = left.timestamp || right.timestamp;
-  if (timestamp) res.timestamp = timestamp;
-  const duration = left.duration || right.duration;
-  if (duration) res.duration = duration;
-
-  const localEndpoint = Object.assign({}, left.localEndpoint, right.localEndpoint);
-  if (isEndpoint(localEndpoint)) res.localEndpoint = localEndpoint;
-  const remoteEndpoint = Object.assign({}, left.remoteEndpoint, right.remoteEndpoint);
-  if (isEndpoint(remoteEndpoint)) res.remoteEndpoint = remoteEndpoint;
-
-  if (left.annotations.length === 0) {
-    res.annotations = right.annotations;
-  } else if (right.annotations.length === 0) {
-    res.annotations = left.annotations;
-  } else {
-    res.annotations = _(_.unionWith(left.annotations, right.annotations, _.isEqual))
-      .sortBy('timestamp', 'value').value();
-  }
-
-  res.tags = Object.assign({}, left.tags, right.tags);
-
-  if (left.debug || right.debug) res.debug = true;
-  if (left.shared || right.shared) res.shared = true;
-  return res;
-}
-
-// compares potentially undefined input
-export function compare(a, b) {
-  if (!a && !b) return 0;
-  if (!a) return -1;
-  if (!b) return 1;
-  return (a > b) - (a < b);
-}
-
-function isUndefined(ref) {
-  return typeof(ref) === 'undefined';
-}
-
-/*
- * Put spans with null endpoints first, so that their data can be attached to the first span with
- * the same ID and endpoint. It is possible that a server can get the same request on a different
- * port. Not addressing this.
- */
-function compareEndpoint(left, right) {
-  // handle nulls first
-  if (isUndefined(left)) return -1;
-  if (isUndefined(right)) return 1;
-
-  const byService = compare(left.serviceName, right.serviceName);
-  if (byService !== 0) return byService;
-  const byIpV4 = compare(left.ipv4, right.ipv4);
-  if (byIpV4 !== 0) return byIpV4;
-  return compare(left.ipv6, right.ipv6);
-}
-
-// false or null first (client first)
-function compareShared(left, right) {
-  const leftNotShared = isUndefined(left.shared) || !left.shared;
-  const rightNotShared = isUndefined(right.shared) || !right.shared;
-
-  if (leftNotShared && rightNotShared) {
-    return left.kind === 'CLIENT' ? -1 : 1;
-  }
-  if (leftNotShared) return -1;
-  if (rightNotShared) return 1;
-  return 0;
-}
-
-export function cleanupComparator(left, right) { // exported for testing
-  const bySpanId = compare(left.id, right.id);
-  if (bySpanId !== 0) return bySpanId;
-  const byShared = compareShared(left, right);
-  if (byShared !== 0) return byShared;
-  return compareEndpoint(left.localEndpoint, right.localEndpoint);
-}
-
-function tryMerge(current, endpoint) {
-  if (!endpoint) return true;
-  if (current.serviceName && endpoint.serviceName && current.serviceName !== endpoint.serviceName) {
-    return false;
-  }
-  if (current.ipv4 && endpoint.ipv4 && current.ipv4 !== endpoint.ipv4) {
-    return false;
-  }
-  if (current.ipv6 && endpoint.ipv6 && current.ipv6 !== endpoint.ipv6) {
-    return false;
-  }
-  if (current.port && endpoint.port && current.port !== endpoint.port) {
-    return false;
-  }
-  if (!current.serviceName) {
-    current.serviceName = endpoint.serviceName; // eslint-disable-line no-param-reassign
-  }
-  if (!current.ipv4) current.ipv4 = endpoint.ipv4; // eslint-disable-line no-param-reassign
-  if (!current.ipv6) current.ipv6 = endpoint.ipv6; // eslint-disable-line no-param-reassign
-  if (!current.port) current.port = endpoint.portAsInt; // eslint-disable-line no-param-reassign
-  return true;
-}
-
-// sort by timestamp, then name, root/shared first in case of skew
-export function spanComparator(a, b) { // exported for testing
-  if (!a.parentId && b.parentId) { // a is root
-    return -1;
-  } else if (a.parentId && !b.parentId) { // b is root
-    return 1;
-  }
-
-  // order client first in case of shared spans (shared is always server)
-  if (a.id === b.id) return compareShared(a, b);
-
-  // Either a and b are root or neither are. sort by shared timestamp, then name
-  return compare(a.timestamp, b.timestamp) || compare(a.name, b.name);
-}
-
-/*
- * Spans can be sent in multiple parts. Also client and server spans can share the same ID. This
- * merges both scenarios.
- */
-export function mergeV2ById(spans) {
-  let length = spans.length;
-  if (length === 0) return spans;
-
-  const result = [];
-
-  // Let's cleanup any spans and pick the longest ID
-  let traceId;
-  spans.forEach(span => {
-    const cleaned = clean(span);
-    if (!traceId || traceId.length !== 32) traceId = cleaned.traceId;
-    result.push(cleaned);
-  });
-
-  if (length <= 1) return result;
-  result.sort(cleanupComparator);
-
-  // Now start any fixes or merging
-  let last;
-  for (let i = 0; i < length; i++) {
-    let span = result[i];
-
-    // Choose the longest trace ID
-    if (span.traceId.length !== traceId.length) {
-      span.traceId = traceId;
-    }
-
-    const localEndpoint = span.localEndpoint ? Object.assign({}, span.localEndpoint) : {};
-    while (i + 1 < length) {
-      const next = result[i + 1];
-      if (next.id !== span.id) break;
-
-      // This cautiously merges with the next span, if we think it was sent in multiple pieces.
-      if (span.shared === next.shared && tryMerge(localEndpoint, next.localEndpoint)) {
-        span = merge(span, next);
-
-        // remove the merged element
-        length--;
-        result.splice(i + 1, 1);
-        continue;
-      }
-      break;
-    }
-
-    // Zipkin and B3 originally used the same span ID between client and server. Some
-    // instrumentation are inconsistent about adding the shared flag on the server side. Since we
-    // have the entire trace, and it is ordered client-first, we can correct a missing shared flag.
-    if (last && last.id === span.id) {
-      // Backfill missing shared flag as some instrumentation doesn't add it
-      if (last.kind === 'CLIENT' && span.kind === 'SERVER' && !span.shared) {
-        span.shared = true;
-      }
-
-      // handle a shared RPC server span that wasn't propagated its parent span ID
-      if (span.shared && !span.parentId && last.parentId) {
-        span.parentId = last.parentId;
-      }
-    }
-
-    last = span;
-    result[i] = span;
-  }
-
-  return result.sort(spanComparator);
-}
diff --git a/zipkin-ui/js/component_data/spanNames.js b/zipkin-ui/js/component_data/spanNames.js
deleted file mode 100644
index b1c58fb..0000000
--- a/zipkin-ui/js/component_data/spanNames.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import {getError} from '../../js/component_ui/error';
-import $ from 'jquery';
-
-export default component(function spanNames() {
-  this.updateSpanNames = function(ev, serviceName) {
-    if (!serviceName) {
-      this.trigger('dataSpanNames', {spans: []});
-      return;
-    }
-    $.ajax(`api/v2/spans?serviceName=${serviceName}`, {
-      type: 'GET',
-      dataType: 'json'
-    }).done(spans => {
-      this.trigger('dataSpanNames', {spans: spans.sort()});
-    }).fail(e => {
-      this.trigger('uiServerError', getError('cannot load span names', e));
-    });
-  };
-
-  this.after('initialize', function() {
-    this.on('uiChangeServiceName', this.updateSpanNames);
-    this.on('uiFirstLoadSpanNames', this.updateSpanNames);
-  });
-});
diff --git a/zipkin-ui/js/component_data/spanNode.js b/zipkin-ui/js/component_data/spanNode.js
deleted file mode 100644
index 2cb1a58..0000000
--- a/zipkin-ui/js/component_data/spanNode.js
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {compare, mergeV2ById} from './spanCleaner';
-
-/*
- * Convenience type representing a trace tree. Multiple Zipkin features require a trace tree. For
- * example, looking at network boundaries to correct clock skew and aggregating requests paths imply
- * visiting the tree.
- */
-// originally zipkin2.internal.SpanNode.java
-class SpanNode {
-  constructor(span) {
-    this._parent = undefined; // no default
-    this._span = span; // undefined is possible when this is a synthetic root node
-    this._children = [];
-  }
-
-  // Returns the parent, or undefined if root.
-  get parent() {
-    return this._parent;
-  }
-
-  _setParent(newParent) {
-    this._parent = newParent;
-  }
-
-  // Returns the span, or undefined if a synthetic root node
-  get span() {
-    return this._span;
-  }
-
-  // Returns the children of this node
-  get children() {
-    return this._children;
-  }
-
-  // Mutable as some transformations, such as clock skew, adjust the current node in the tree.
-  setSpan(span) {
-    if (!span) throw new Error('span was undefined');
-    this._span = span;
-  }
-
-  // Adds the child IFF it isn't already a child.
-  addChild(child) {
-    if (!child) throw new Error('child was undefined');
-    if (child === this) throw new Error(`circular dependency on ${this.toString()}`);
-    child._setParent(this);
-    this._children.push(child);
-  }
-
-  // throws an error if the trace was empty
-  queueRootMostSpans() {
-    const queue = [];
-    // since the input data could be headless, we first push onto the queue the root-most spans
-    if (typeof(this.span) === 'undefined') { // synthetic root
-      this.children.forEach(child => queue.push(child));
-    } else {
-      queue.push(this);
-    }
-    if (queue.length === 0) throw new Error('Trace was empty');
-    return queue;
-  }
-
-  // Invokes the callback for each span resulting from a breadth-first traversal at this node
-  traverse(spanCallback) {
-    const queue = this.queueRootMostSpans();
-
-    while (queue.length > 0) {
-      const current = queue.shift();
-
-      spanCallback(current.span);
-
-      const children = current.children;
-      for (let i = 0; i < children.length; i++) {
-        queue.push(children[i]);
-      }
-    }
-  }
-
-  toString() {
-    if (this._span) return `SpanNode(${JSON.stringify(this._span)})`;
-    return 'SpanNode()';
-  }
-}
-
-// In javascript, dict keys can't be objects
-function keyString(id, shared = false, endpoint) {
-  if (!shared) return id;
-  const endpointString = endpoint ? JSON.stringify(endpoint) : 'x';
-  return `${id}-${endpointString}`;
-}
-
-function nodeByTimestamp(a, b) {
-  return compare(a.span.timestamp, b.span.timestamp);
-}
-
-function sortChildren(node) {
-  if (node.children.length > 0) {
-    node.children.sort(nodeByTimestamp);
-  }
-}
-
-function sortTreeByTimestamp(root) {
-  const queue = [];
-  queue.push(root);
-
-  while (queue.length > 0) {
-    const current = queue.shift();
-
-    sortChildren(current);
-
-    const {children} = current;
-    for (let i = 0; i < children.length; i += 1) {
-      queue.push(children[i]);
-    }
-  }
-}
-
-class SpanNodeBuilder {
-  constructor(params) {
-    const {debug = false} = params;
-    this._debug = debug;
-    this._rootSpan = undefined;
-    this._keyToNode = {};
-    this._spanToParent = {};
-  }
-
-  /*
-   * We index spans by (id, shared, localEndpoint) before processing them. This latter fields
-   * (shared, endpoint) are important because in zipkin (specifically B3), a server can share
-   * (re-use) the same ID as its client. This impacts processing quite a bit when multiple servers
-   * share one span ID.
-   *
-   * In a Zipkin trace, a parent (client) and child (server) can share the same ID if in an
-   * RPC. If two different servers respond to the same client, the only way for us to tell which
-   * is which is by endpoint. Our goal is to retain full paths across multiple endpoints. Even
-   * though instrumentation should be configured in such a way that a client never sends the same
-   * span ID to multiple servers, it can happen. Accordingly, we index defensively including any
-   * endpoint data that might be available.
-   */
-  _index(span) {
-    let idKey;
-    let parentKey;
-
-    if (span.shared) {
-      // we need to classify a shared span by its endpoint in case multiple servers respond to the
-      // same ID sent by the client.
-      idKey = keyString(span.id, true, span.localEndpoint);
-      // the parent of a server span is a client, which is not ambiguous for a given span ID.
-      parentKey = span.id;
-    } else {
-      idKey = span.id;
-      parentKey = span.parentId;
-    }
-
-    this._spanToParent[idKey] = parentKey;
-  }
-
-  /**
-   * Processing is taking a span and placing it at the most appropriate place in the trace tree.
-   * For example, if this is a server span, it would be a different node, and a child of its client
-   * even if they share the same span ID.
-   *
-   * Processing is defensive of typical problems in span reporting, such as depth-first. For
-   * example, depth-first reporting implies you can see spans missing their parent. Hence, the
-   * result of processing all spans can be a virtual root node.
-   */
-  _process(span) {
-    const endpoint = span.localEndpoint;
-    const key = keyString(span.id, span.shared, span.localEndpoint);
-    const noEndpointKey = endpoint ? keyString(span.id, span.shared) : key;
-
-    let parent;
-    if (span.shared) {
-      // Shared is a server span. It will very likely be on a different endpoint than the client.
-      // Clients are not ambiguous by ID, so we don't need to qualify by endpoint.
-      parent = span.id;
-    } else if (span.parentId) {
-      // We are not a root span, and not a shared server span. Proceed in most specific to least.
-
-      // We could be the child of a shared server span (ex a local (intermediate) span on the same
-      // endpoint). This is the most specific case, so we try this first.
-      parent = keyString(span.parentId, true, endpoint);
-      if (this._spanToParent[parent]) {
-        this._spanToParent[noEndpointKey] = parent;
-      } else {
-        // If there's no shared parent, fall back to normal case which is unqualified beyond ID.
-        parent = span.parentId;
-      }
-    } else { // we are root or don't know our parent
-      if (this._rootSpan) {
-        if (this._debug) {
-          const prefix = 'attributing span missing parent to root';
-          /* eslint-disable no-console */
-          console.log(
-            `${prefix}: traceId=${span.traceId}, rootId=${this._rootSpan.span.id}, id=${span.id}`
-          );
-        }
-      }
-    }
-
-    const node = new SpanNode(span);
-    // special-case root, and attribute missing parents to it. In
-    // other words, assume that the first root is the "real" root.
-    if (!parent && !this._rootSpan) {
-      this._rootSpan = node;
-      delete this._spanToParent[noEndpointKey];
-    } else if (span.shared) {
-      // In the case of shared server span, we need to address it both ways, in case intermediate
-      // spans are lacking endpoint information.
-      this._keyToNode[key] = node;
-      this._keyToNode[noEndpointKey] = node;
-    } else {
-      this._keyToNode[noEndpointKey] = node;
-    }
-  }
-
-  /*
-   * Builds a trace tree by merging and processing the input or returns an empty tree.
-   *
-   * While the input can be incomplete or redundant, they must all be a part of the same trace
-   * (e.g. all share the same trace ID).
-   */
-  build(spans) {
-    if (spans.length === 0) throw new Error('Trace was empty');
-
-    // In order to make a tree, we need clean data. This will merge any duplicates so that we
-    // don't have redundant leaves on the tree.
-    const cleaned = mergeV2ById(spans);
-    const length = cleaned.length;
-    const traceId = cleaned[0].traceId;
-
-    if (this._debug) {
-      /* eslint-disable no-console */
-      console.log(`building trace tree: traceId=${traceId}`);
-    }
-
-    // Next, index all the spans so that we can understand any relationships.
-    for (let i = 0; i < length; i++) {
-      this._index(cleaned[i]);
-    }
-
-    // Now that we've index references to all spans, we can revise any parent-child relationships.
-    // Notably, by now, we can tell which is the root-most.
-    for (let i = 0; i < length; i++) {
-      this._process(cleaned[i]);
-    }
-
-    if (!this._rootSpan) {
-      if (this._debug) {
-        /* eslint-disable no-console */
-        console.log(`substituting dummy node for missing root span: traceId=${traceId}`);
-      }
-      this._rootSpan = new SpanNode();
-    }
-
-    // At this point, we have the most reliable parent-child relationships and can allocate spans
-    // corresponding the the best place in the trace tree.
-    Object.keys(this._spanToParent).forEach(key => {
-      const child = this._keyToNode[key];
-      const parent = this._keyToNode[this._spanToParent[key]];
-
-      if (!parent) { // Handle headless by attaching spans missing parents to root
-        this._rootSpan.addChild(child);
-      } else {
-        parent.addChild(child);
-      }
-    });
-
-    sortTreeByTimestamp(this._rootSpan);
-
-    return this._rootSpan;
-  }
-}
-
-module.exports = {
-  SpanNode, // for testing
-  SpanNodeBuilder
-};
diff --git a/zipkin-ui/js/component_data/trace.js b/zipkin-ui/js/component_data/trace.js
deleted file mode 100644
index 724d7dc..0000000
--- a/zipkin-ui/js/component_data/trace.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import $ from 'jquery';
-import {getError} from '../component_ui/error';
-import {traceToMustache} from '../component_ui/traceToMustache';
-import {treeCorrectedForClockSkew} from '../component_data/skew';
-
-export function toContextualLogsUrl(logsUrl, traceId) {
-  if (logsUrl) {
-    return logsUrl.replace('{traceId}', traceId);
-  }
-  return logsUrl;
-}
-
-// Converts the response into data for trace.mustache. Missing required data will raise an error.
-export function convertSuccessResponse(rawResponse, logsUrl) {
-  const corrected = treeCorrectedForClockSkew(rawResponse);
-  const modelview = traceToMustache(corrected, logsUrl);
-  return {modelview, trace: rawResponse};
-}
-
-export default component(function TraceData() {
-  this.after('initialize', function() {
-    const traceId = this.attr.traceId;
-    const logsUrl = toContextualLogsUrl(this.attr.logsUrl, traceId);
-    $.ajax(`api/v2/trace/${traceId}`, {
-      type: 'GET',
-      dataType: 'json'
-    }).done(raw => {
-      this.trigger('tracePageModelView', convertSuccessResponse(raw, logsUrl));
-    }).fail(e => {
-      this.trigger('uiServerError', getError(`Cannot load trace ${traceId}`, e));
-    });
-  });
-});
diff --git a/zipkin-ui/js/component_ui/backToTop.js b/zipkin-ui/js/component_ui/backToTop.js
deleted file mode 100644
index 2fa53f8..0000000
--- a/zipkin-ui/js/component_ui/backToTop.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import $ from 'jquery';
-
-export default component(function backToTop() {
-  this.toTop = function() {
-    event.preventDefault();
-    $('html, body').animate({scrollTop: 0}, 300);
-    return false;
-  };
-
-  this.after('initialize', function() {
-    /* handle window scroll here*/
-    $(window).scroll(function() {
-      if ($(this).scrollTop() > 200) {
-        $('.back-to-top').fadeIn(300);
-      } else {
-        $('.back-to-top').fadeOut(300);
-      }
-    });
-
-    this.on('click', this.toTop);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/dependencyGraph.js b/zipkin-ui/js/component_ui/dependencyGraph.js
deleted file mode 100644
index 170e368..0000000
--- a/zipkin-ui/js/component_ui/dependencyGraph.js
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import d3 from 'd3';
-import $ from 'jquery';
-import dagreD3 from '../../libs/dagre-d3/js/dagre-d3'; // eslint-disable-line no-unused-vars
-
-const dagre = window.dagreD3;
-
-export default component(function dependencyGraph() {
-  this.after('initialize', function afterInitialize(container) {
-    const lowErrorRate = this.attr.config('dependency').lowErrorRate;
-    const highErrorRate = this.attr.config('dependency').highErrorRate;
-    this.on(document, 'dependencyDataReceived', function onDependencyDataReceived(ev, ...links) {
-      const _this = this;
-      const rootSvg = container.querySelector('svg');
-      rootSvg.textContent = null;
-      const svg = d3.select('svg');
-      const svgGroup = svg.append('g');
-      const g = new dagre.Digraph();
-      const renderer = new dagre.Renderer();
-
-      function arrayUnique(array) {
-        return array.filter((val, i, arr) => i <= arr.indexOf(val));
-      }
-
-      function flatten(arrayOfArrays) {
-        if (arrayOfArrays.length === 0) {
-          return [];
-        } else {
-          return arrayOfArrays.reduce((a, b) => a.concat(b));
-        }
-      }
-
-      function getIncidentEdgeElements(nodeName) {
-        const selectedElements = rootSvg
-            .querySelectorAll(`[data-from='${nodeName}'],[data-to='${nodeName}']`);
-        return [...selectedElements];
-      }
-
-      function getIncidentNodeElements(from, to) {
-        return [
-          rootSvg.querySelector(`[data-node='${from}']`),
-          rootSvg.querySelector(`[data-node='${to}']`)
-        ];
-      }
-
-      function getAdjacentNodeElements(centerNode) {
-        const edges = g.incidentEdges(centerNode);
-        const nodes = flatten(edges.map(edge => g.incidentNodes(edge)));
-        const otherNodes = arrayUnique(nodes.filter(node => node !== centerNode));
-        const elements = otherNodes.map(name => rootSvg.querySelector(`[data-node='${name}']`));
-        return elements;
-      }
-
-      function scale(i, startRange, endRange, minResult, maxResult) {
-        return minResult + (i - startRange) * (maxResult - minResult) / (endRange - startRange);
-      }
-
-      // Find min/max number of calls for all dependency links
-      // to render different arrow widths depending on number of calls
-      let minCallCount = 0;
-      let maxCallCount = 0;
-      links.filter(link => link.parent !== link.child).forEach(link => {
-        const numCalls = link.callCount;
-        if (minCallCount === 0 || numCalls < minCallCount) {
-          minCallCount = numCalls;
-        }
-        if (numCalls > maxCallCount) {
-          maxCallCount = numCalls;
-        }
-      });
-      const minLg = Math.log(minCallCount);
-      const maxLg = Math.log(maxCallCount);
-
-      function arrowWidth(callCount) {
-        const lg = Math.log(callCount);
-        return scale(lg, minLg, maxLg, 0.3, 3);
-      }
-
-      // Get the names of all nodes in the graph
-      const parentNames = links.map(link => link.parent);
-      const childNames = links.map(link => link.child);
-      const allNames = arrayUnique(parentNames.concat(childNames));
-
-      // Add nodes/service names to the graph
-      allNames.forEach(name => {
-        g.addNode(name, {label: name});
-      });
-
-      // Add edges/dependency links to the graph
-      links.filter(link => link.parent !== link.child)
-           .forEach(({parent, child, callCount, errorCount}) => {
-             g.addEdge(`${parent}->${child}`, parent, child, {
-               from: parent,
-               to: child,
-               callCount,
-               errorCount
-             });
-           });
-
-      const layout = dagre.layout()
-        .nodeSep(30)
-        .rankSep(200)
-        .rankDir('LR'); // LR = left-to-right, TB = top-to-bottom.
-
-      // Override drawNodes and drawEdgePaths, so we can add
-      // hover functionality on top of Dagre.
-      const innerDrawNodes = renderer.drawNodes();
-      const innerDrawEdgePaths = renderer.drawEdgePaths();
-
-      renderer.drawNodes((gInner, svgInner) => {
-        const svgNodes = innerDrawNodes(gInner, svgInner);
-        // Add mouse hover/click handlers
-        svgNodes.attr('data-node', d => d)
-          .each(function(d) {
-            const $this = $(this);
-            const nodeEl = $this[0];
-
-            $this.click(() => {
-              _this.trigger('showServiceDataModal', {
-                serviceName: d
-              });
-            });
-
-            $this.hover(() => {
-              nodeEl.classList.add('hover');
-              rootSvg.classList.add('dark');
-              getIncidentEdgeElements(d).forEach(el => {
-                el.classList.add('hover-edge');
-              });
-              getAdjacentNodeElements(d).forEach(el => {
-                el.classList.add('hover-light');
-              });
-            }, () => {
-              nodeEl.classList.remove('hover');
-              rootSvg.classList.remove('dark');
-              getIncidentEdgeElements(d).forEach(e => {
-                e.classList.remove('hover-edge');
-              });
-              getAdjacentNodeElements(d).forEach(e => {
-                e.classList.remove('hover-light');
-              });
-            });
-          });
-        return svgNodes;
-      });
-
-      renderer.drawEdgePaths((gInner, svgInner) => {
-        const svgNodes = innerDrawEdgePaths(gInner, svgInner);
-        svgNodes.each(function(edge) {
-          // Add mouse hover handlers
-          const edgeEl = this;
-          const $el = $(edgeEl);
-
-          const callCount = gInner.edge(edge).callCount;
-          const arrowWidthPx = `${arrowWidth(callCount)}px`;
-          $el.css('stroke-width', arrowWidthPx);
-
-          const errorCount = gInner.edge(edge).errorCount || 0;
-          const errorRate = errorCount / callCount;
-          if (errorRate >= highErrorRate) {
-            $el.css('stroke', 'rgb(230, 162, 161)');
-          } else if (errorRate >= lowErrorRate) {
-            $el.css('stroke', 'rgb(230, 215, 140)');
-          }
-
-          $el.hover(() => {
-            rootSvg.classList.add('dark');
-            const nodes = getIncidentNodeElements(
-                edgeEl.getAttribute('data-from'),
-                edgeEl.getAttribute('data-to'));
-            nodes.forEach(el => { el.classList.add('hover'); });
-            edgeEl.classList.add('hover-edge');
-          }, () => {
-            rootSvg.classList.remove('dark');
-            const nodes = getIncidentNodeElements(
-                edgeEl.getAttribute('data-from'),
-                edgeEl.getAttribute('data-to'));
-            nodes.forEach(el => {
-              el.classList.remove('hover');
-            });
-            edgeEl.classList.remove('hover-edge');
-          });
-        });
-
-        svgNodes.attr('data-from', d => gInner.edge(d).from);
-        svgNodes.attr('data-to', d => gInner.edge(d).to);
-        return svgNodes;
-      });
-
-      renderer
-        .layout(layout)
-        .run(g, svgGroup);
-    });
-  });
-});
diff --git a/zipkin-ui/js/component_ui/environment.js b/zipkin-ui/js/component_ui/environment.js
deleted file mode 100644
index 186d0c1..0000000
--- a/zipkin-ui/js/component_ui/environment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import flight from 'flightjs';
-
-export default flight.component(function environmentUI() {
-  this.after('initialize', function() {
-    this.$node.text(this.attr.config('environment'));
-  });
-});
diff --git a/zipkin-ui/js/component_ui/error.js b/zipkin-ui/js/component_ui/error.js
deleted file mode 100644
index d67f904..0000000
--- a/zipkin-ui/js/component_ui/error.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import $ from 'jquery';
-
-export default component(function ErrorUI() {
-  this.after('initialize', function() {
-    this.on(document, 'uiServerError', function(evt, e) {
-      this.$node.append($('<div></div>').text(`ERROR: ${e.desc}: ${e.message}`));
-      this.$node.show();
-    });
-  });
-});
-
-// converts an jqXhr error to a string
-export function errToStr(e) {
-  return e.responseJSON ? e.responseJSON.message : `server error (${e.statusText})`;
-}
-
-// transforms an ajax error into something that is passed to
-// trigger('uiServerError')
-export function getError(desc, e) {
-  return {
-    desc,
-    message: errToStr(e)
-  };
-}
diff --git a/zipkin-ui/js/component_ui/filterAllServices.js b/zipkin-ui/js/component_ui/filterAllServices.js
deleted file mode 100644
index 5f864fa..0000000
--- a/zipkin-ui/js/component_ui/filterAllServices.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import $ from 'jquery';
-
-export default component(function filterAllServices() {
-  this.$expandAll = $();
-  this.$collapseAll = $();
-  this.totalServices = 0;
-  this.filtered = {};
-  this.currentFilterCount = 0;
-
-  this.toggleFilter = function(e) {
-    this.trigger(document, $(e.target).val());
-  };
-
-  this.filterAdded = function(e, data) {
-    if (this.filtered[data.value]) return;
-
-    this.filtered[data.value] = true;
-    this.currentFilterCount += 1;
-
-    if (this.currentFilterCount === this.totalServices) {
-      this.$expandAll.addClass('active');
-    } else {
-      this.$collapseAll.removeClass('active');
-    }
-  };
-
-  this.filterRemoved = function(e, data) {
-    if (!this.filtered[data.value]) return;
-
-    this.filtered[data.value] = false;
-    this.currentFilterCount -= 1;
-
-    if (this.currentFilterCount === 0) {
-      this.$collapseAll.addClass('active');
-    } else {
-      this.$expandAll.removeClass('active');
-    }
-  };
-
-  this.after('initialize', function(node, data) {
-    this.totalServices = data.totalServices;
-    this.$expandAll = this.$node.find('[value="uiExpandAllSpans"]');
-    this.$collapseAll = this.$node.find('[value="uiCollapseAllSpans"]');
-
-    this.on('.btn', 'click', this.toggleFilter);
-    this.on(document, 'uiAddServiceNameFilter', this.filterAdded);
-    this.on(document, 'uiRemoveServiceNameFilter', this.filterRemoved);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/fullPageSpinner.js b/zipkin-ui/js/component_ui/fullPageSpinner.js
deleted file mode 100644
index c6e9bf0..0000000
--- a/zipkin-ui/js/component_ui/fullPageSpinner.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-
-export default component(function fullPageSpinner() {
-  this.requests = 0;
-
-  this.showSpinner = function() {
-    this.requests += 1;
-    this.$node.show();
-  };
-
-  this.hideSpinner = function() {
-    this.requests -= 1;
-    if (this.requests === 0) {
-      this.$node.hide();
-    }
-  };
-
-  this.after('initialize', function() {
-    this.on(document, 'uiShowFullPageSpinner', this.showSpinner);
-    this.on(document, 'uiHideFullPageSpinner', this.hideSpinner);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/goToDependency.js b/zipkin-ui/js/component_ui/goToDependency.js
deleted file mode 100644
index 8e436c7..0000000
--- a/zipkin-ui/js/component_ui/goToDependency.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {contextRoot} from '../publicPath';
-import {component} from 'flightjs';
-
-export default component(function goToDependency() {
-  this.navigateToDependency = function(evt) {
-    evt.preventDefault();
-    const endTs = document.getElementById('endTs').value;
-    const startTs = document.getElementById('startTs').value;
-    window.location.href = `${contextRoot}dependency?endTs=${endTs}&startTs=${startTs}`;
-  };
-
-  this.after('initialize', function() {
-    this.on('submit', this.navigateToDependency);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/goToLens.js b/zipkin-ui/js/component_ui/goToLens.js
deleted file mode 100644
index 804500a..0000000
--- a/zipkin-ui/js/component_ui/goToLens.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import Cookies from 'js-cookie';
-
-export default component(function goToLens() {
-  this.goToLens = function(evt) {
-    evt.preventDefault();
-    Cookies.set('lens', 'true');
-    window.location.reload(true);
-  };
-
-  this.after('initialize', function() {
-    this.on('submit', this.goToLens);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/goToTrace.js b/zipkin-ui/js/component_ui/goToTrace.js
deleted file mode 100644
index 9743665..0000000
--- a/zipkin-ui/js/component_ui/goToTrace.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import {contextRoot} from '../publicPath';
-
-export default component(function goToTrace() {
-  this.navigateToTrace = function(evt) {
-    evt.preventDefault();
-    const traceId = document.getElementById('traceIdQuery').value;
-    window.location.href = `${contextRoot}traces/${traceId}`;
-  };
-
-  this.after('initialize', function() {
-    this.on('submit', this.navigateToTrace);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/i18n.js b/zipkin-ui/js/component_ui/i18n.js
deleted file mode 100644
index 2935d9e..0000000
--- a/zipkin-ui/js/component_ui/i18n.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import 'jquery-i18n-properties';
-import $ from 'jquery';
-import {contextRoot} from '../publicPath';
-
-export function i18nInit(file) {
-  // https://github.com/jquery-i18n-properties/jquery-i18n-properties
-  $.i18n.properties({
-    name: file,
-    path: contextRoot,
-    mode: 'map',
-    // do not append a unix timestamp when requesting the language (.properties) files
-    // this allows them to be cached by the browser
-    cache: true,
-    // do not perform blocking XHR requests, as it blocks the entire browser, including
-    // rendering
-    async: true,
-    callback: () => {
-      $('[data-i18n]').each((index, item) => {
-        if (item.tagName === 'INPUT' || item.tagName === 'SELECT') {
-          $(item).attr('placeholder', $.i18n.prop($(item).attr('data-i18n')));
-        } else {
-          $(item).html($.i18n.prop($(item).attr('data-i18n')));
-        }
-      });
-    }
-  });
-}
diff --git a/zipkin-ui/js/component_ui/infoButton.js b/zipkin-ui/js/component_ui/infoButton.js
deleted file mode 100644
index e65964f..0000000
--- a/zipkin-ui/js/component_ui/infoButton.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-
-export default component(function infoButton() {
-  this.requestInfoPanel = function() {
-    this.trigger('uiRequestInfoPanel');
-  };
-
-  this.after('initialize', function() {
-    this.on('click', this.requestInfoPanel);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/infoPanel.js b/zipkin-ui/js/component_ui/infoPanel.js
deleted file mode 100644
index 67a009e..0000000
--- a/zipkin-ui/js/component_ui/infoPanel.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import bootstrap // eslint-disable-line no-unused-vars
-    from 'bootstrap/dist/js/bootstrap.bundle.min.js';
-
-export default component(function infoPanel() {
-  this.show = function() {
-    this.$node.modal('show');
-  };
-
-  this.after('initialize', function() {
-    this.$node.modal('hide');
-    this.on(document, 'uiRequestInfoPanel', this.show);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/jsonPanel.js b/zipkin-ui/js/component_ui/jsonPanel.js
deleted file mode 100644
index 663b55e..0000000
--- a/zipkin-ui/js/component_ui/jsonPanel.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-
-export default component(function jsonPanel() {
-  this.show = function(e, data) {
-    this.$node.find('.modal-title').text(data.title);
-    this.$node.find('.save').attr('href', data.link);
-    this.$node.find('.modal-body pre code').text(JSON.stringify(data.obj, null, 2));
-    this.$node.modal('show');
-  };
-  this.copyToClipboard = function() {
-    const traceJson = document.getElementById('trace-json-content');
-    function copyListener(e) {
-      e.clipboardData.setData('text/plain', traceJson.textContent);
-      e.preventDefault();
-    }
-    document.addEventListener('copy', copyListener);
-    document.execCommand('copy');
-    document.removeEventListener('copy', copyListener);
-  };
-  this.after('initialize', function() {
-    this.$node.modal('hide');
-    this.on(document, 'uiRequestJsonPanel', this.show);
-    this.on('#copy2clipboard', 'click', this.copyToClipboard);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/lookback.js b/zipkin-ui/js/component_ui/lookback.js
deleted file mode 100644
index 2ccbba0..0000000
--- a/zipkin-ui/js/component_ui/lookback.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/* eslint-disable prefer-template */
-import {component} from 'flightjs';
-import $ from 'jquery';
-import queryString from 'query-string';
-
-export default component(function lookback() {
-  this.refreshCustomFields = function() {
-    if (this.$node.val() === 'custom') {
-      $('#custom-lookback').show();
-    } else {
-      $('#custom-lookback').hide();
-    }
-  };
-
-  this.render = function() {
-    const selectedLookback = queryString.parse(window.location.search).lookback;
-    this.$node.find('option').each((i, option) => {
-      const $option = $(option);
-      if ($option.val() === selectedLookback) {
-        $option.prop('selected', true);
-      }
-    });
-  };
-
-  this.after('initialize', function() {
-    this.render();
-    this.refreshCustomFields();
-
-    this.on('change', this.refreshCustomFields);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/navbar.js b/zipkin-ui/js/component_ui/navbar.js
deleted file mode 100644
index dda59c9..0000000
--- a/zipkin-ui/js/component_ui/navbar.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import $ from 'jquery';
-import {i18nInit} from '../component_ui/i18n';
-
-const NavbarUI = component(function navbar() {
-  this.onNavigate = function(ev, {route}) {
-    this.$node.find('[data-route]').each((i, el) => {
-      const $el = $(el);
-      if ($el.data('route') === route) {
-        $el.addClass('active');
-      } else {
-        $el.removeClass('active');
-      }
-    });
-  };
-
-  this.after('initialize', function() {
-    i18nInit('nav');
-    this.on(document, 'navigate', this.onNavigate);
-  });
-});
-
-export default NavbarUI;
diff --git a/zipkin-ui/js/component_ui/remoteServiceName.js b/zipkin-ui/js/component_ui/remoteServiceName.js
deleted file mode 100644
index 6f3f5eb..0000000
--- a/zipkin-ui/js/component_ui/remoteServiceName.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/* eslint-disable prefer-template */
-import {component} from 'flightjs';
-import 'chosen-js';
-import $ from 'jquery';
-import queryString from 'query-string';
-
-export default component(function remoteServiceName() {
-  this.updateRemoteServices = function(ev, data) {
-    this.render(data.remoteServices);
-    this.trigger('chosen:updated');
-  };
-
-  this.render = function(remoteServices) {
-    this.$node.empty();
-    this.$node.append($($.parseHTML('<option value="all">all</option>')));
-
-    const selectedRemoteServiceName = queryString.parse(window.location.search).remoteServiceName;
-    $.each(remoteServices, (i, remoteService) => {
-      const option = $($.parseHTML('<option/>'));
-      option.val(remoteService);
-      option.text(remoteService);
-      if (remoteService === selectedRemoteServiceName) {
-        option.prop('selected', true);
-      }
-      this.$node.append(option);
-    });
-  };
-
-  this.after('initialize', function() {
-    this.$node.chosen({
-      search_contains: true
-    });
-    this.$node.next('.chosen-container');
-
-    this.on(document, 'dataRemoteServiceNames', this.updateRemoteServices);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/serviceDataModal.js b/zipkin-ui/js/component_ui/serviceDataModal.js
deleted file mode 100644
index f4f925e..0000000
--- a/zipkin-ui/js/component_ui/serviceDataModal.js
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import $ from 'jquery';
-import bootstrap // eslint-disable-line no-unused-vars
-  from 'bootstrap/dist/js/bootstrap.bundle.min.js';
-
-function renderDependencyModal(event, data) {
-  const $modal = $('#dependencyModal');
-  const $parentElement = $(`<a href="">${data.parent}</a>`);
-  $parentElement.click(ev => {
-    ev.preventDefault();
-    this.trigger(document, 'showServiceDataModal', {
-      serviceName: data.parent
-    });
-  });
-
-  const $childElement = $(`<a href="">${data.child}</a>`);
-  $childElement.click(ev => {
-    ev.preventDefault();
-    this.trigger(document, 'showServiceDataModal', {
-      serviceName: data.child
-    });
-  });
-
-  $modal.find('#dependencyModalParent').html($parentElement);
-  $modal.find('#dependencyModalChild').html($childElement);
-  $modal.find('#dependencyCallCount').text(data.callCount);
-  $modal.find('#dependencyErrorCount').text(data.errorCount || 0);
-
-  $('#serviceModal').modal('hide');
-  $modal.modal('show');
-}
-
-function renderServiceDataModal(event, data) {
-  const $modal = $('#serviceModal');
-  $modal.find('#serviceUsedByList').html('');
-  data.usedBy.sort((a, b) =>
-    a.toLowerCase().localeCompare(b.toLowerCase())
-  );
-  data.usedBy.forEach(usedBy => {
-    const $name = $(`<li><a href="">${usedBy}</a></li>`);
-    $name.find('a').click(ev => {
-      ev.preventDefault();
-      this.trigger(document, 'showDependencyModal', {
-        parent: usedBy,
-        child: data.serviceName
-      });
-    });
-    $modal.find('#serviceUsedByList').append($name);
-  });
-
-  $modal.find('#serviceUsesList').html('');
-  data.uses.sort((a, b) =>
-    a.toLowerCase().localeCompare(b.toLowerCase())
-  );
-
-  data.uses.forEach(uses => {
-    const $name = $(`<li><a href="">${uses}</a></li>`);
-    $name.find('a').click(ev => {
-      ev.preventDefault();
-      this.trigger(document, 'showDependencyModal', {
-        parent: data.serviceName,
-        child: uses
-      });
-    });
-    $modal.find('#serviceUsesList').append($name);
-  });
-
-  $modal.find('#serviceModalTitle').text(data.serviceName);
-
-  $modal.modal('show');
-  $('#dependencyModal').modal('hide');
-}
-
-export default component(function serviceDataModal() {
-  this.showServiceDataModal = function(event, data) {
-    this.trigger(document, 'serviceDataRequested', {
-      serviceName: data.serviceName
-    });
-  };
-
-  this.showDependencyModal = function(event, data) {
-    this.trigger(document, 'parentChildDataRequested', {
-      parent: data.parent,
-      child: data.child,
-      callCount: data.callCount
-    });
-  };
-
-  this.after('initialize', function() {
-    this.on(document, 'showServiceDataModal', this.showServiceDataModal);
-    this.on(document, 'showDependencyModal', this.showDependencyModal);
-    this.on(document, 'serviceDataReceived', renderServiceDataModal);
-    this.on(document, 'parentChildDataReceived', renderDependencyModal);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/serviceFilterSearch.js b/zipkin-ui/js/component_ui/serviceFilterSearch.js
deleted file mode 100644
index a06149c..0000000
--- a/zipkin-ui/js/component_ui/serviceFilterSearch.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import 'chosen-js';
-
-export default component(function serviceNameFilter() {
-  this.onChange = function(e, params) {
-    if (params.selected === '') return;
-
-    this.trigger(document, 'uiAddServiceNameFilter', {value: params.selected});
-    this.$node.val('');
-    this.$node.trigger('chosen:updated');
-  };
-
-  this.after('initialize', function() {
-    this.$node.chosen({search_contains: true});
-    this.on('change', this.onChange);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/serviceName.js b/zipkin-ui/js/component_ui/serviceName.js
deleted file mode 100644
index 935a9e6..0000000
--- a/zipkin-ui/js/component_ui/serviceName.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import Cookies from 'js-cookie';
-import $ from 'jquery';
-import queryString from 'query-string';
-
-import 'chosen-js';
-
-  // Sorting based on the localCompare so that sorting can be
-  // accomplished for non-ascii (non english) service names.
-export function sortServiceNames(serviceNames) {
-  if (serviceNames) {
-    serviceNames.sort((a, b) =>
-       a.localeCompare(b)
-    );
-  }
-  return serviceNames;
-}
-export default component(function serviceName() {
-  this.onChange = function() {
-    Cookies.set('last-serviceName', this.$node.val());
-    this.triggerChange(this.$node.val());
-  };
-
-  this.triggerChange = function(name) {
-    this.$node.trigger('uiChangeServiceName', name);
-  };
-
-  this.updateServiceNameDropdown = function(ev, data) {
-    $('#serviceName').empty();
-    this.$node.append($($.parseHTML('<option value="all">all</option>')));
-    const services = sortServiceNames(data.names);
-    $.each(services, (i, item) => {
-      $('<option>').val(item).text(item).appendTo('#serviceName');
-    });
-
-    this.$node.find(`[value="${data.lastServiceName}"]`).attr('selected', 'selected');
-
-    this.trigger('chosen:updated');
-
-    // On the first view there won't be a selected or "last" service
-    // name.  Instead the first service at the top of the list will be
-    // displayed, so load the span names for the top service too.
-    if (!data.lastServiceName && services && services.length > 1) {
-      this.$node.trigger('uiFirstLoadSpanNames', services[0]);
-      this.$node.trigger('uiFirstLoadRemoteServiceNames', services[0]);
-    }
-  };
-
-  this.after('initialize', function() {
-    const name = queryString.parse(window.location.search).serviceName
-        || Cookies.get('last-serviceName');
-    this.triggerChange(name);
-
-    this.$node.chosen({search_contains: true});
-    this.$node.next('.chosen-container');
-
-    this.on('change', this.onChange);
-    this.on(document, 'dataServiceNames', this.updateServiceNameDropdown);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/spanName.js b/zipkin-ui/js/component_ui/spanName.js
deleted file mode 100644
index 643169c..0000000
--- a/zipkin-ui/js/component_ui/spanName.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/* eslint-disable prefer-template */
-import {component} from 'flightjs';
-import 'chosen-js';
-import $ from 'jquery';
-import queryString from 'query-string';
-
-export default component(function spanName() {
-  this.updateSpans = function(ev, data) {
-    this.render(data.spans);
-    this.trigger('chosen:updated');
-  };
-
-  this.render = function(spans) {
-    this.$node.empty();
-    this.$node.append($($.parseHTML('<option value="all">all</option>')));
-
-    const selectedSpanName = queryString.parse(window.location.search).spanName;
-    $.each(spans, (i, span) => {
-      const option = $($.parseHTML('<option/>'));
-      option.val(span);
-      option.text(span);
-      if (span === selectedSpanName) {
-        option.prop('selected', true);
-      }
-      this.$node.append(option);
-    });
-  };
-
-  this.after('initialize', function() {
-    this.$node.chosen({
-      search_contains: true
-    });
-    this.$node.next('.chosen-container');
-
-    this.on(document, 'dataSpanNames', this.updateSpans);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/spanPanel.js b/zipkin-ui/js/component_ui/spanPanel.js
deleted file mode 100644
index 4833905..0000000
--- a/zipkin-ui/js/component_ui/spanPanel.js
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import $ from 'jquery';
-import {Constants} from './traceConstants';
-
-const entityMap = {
-  '&': '&amp;',
-  '<': '&lt;',
-  '>': '&gt;',
-  '"': '&quot;',
-  "'": '&#39;',
-  '/': '&#x2F;',
-  '`': '&#x60;',
-  '=': '&#x3D;'
-};
-
-function escapeHtml(string) {
-  return String(string).replace(/[&<>"'`=\/]/g, s => entityMap[s]);
-}
-
-export function isDupeTag(tagMap, anno) {
-  if (!tagMap[anno.key]) {
-    tagMap[anno.key] = anno.value; // eslint-disable-line no-param-reassign
-  } else if (tagMap[anno.key] === anno.value) {
-    return true;
-  }
-  return false;
-}
-
-// Annotation values that contain the word "error" hint of a transient error.
-// This adds a class when that's the case.
-export function maybeMarkTransientError(row, anno) {
-  if (/error/i.test(anno.value)) {
-    row.addClass('anno-error-transient');
-  }
-}
-
-// Normal values are formatted in traceToMustache. However, Quoted json values
-// end up becoming javascript objects later. For this reason, we have to guard
-// and stringify as necessary.
-
-// annotations are named events which shouldn't hold json. If someone passed
-// json, format as a single line. That way the rows corresponding to timestamps
-// aren't disrupted.
-export function formatAnnotationValue(value) {
-  const type = $.type(value);
-  if (type === 'object' || type === 'array' || value == null) {
-    return escapeHtml(JSON.stringify(value));
-  } else {
-    return escapeHtml(value.toString()); // prevents false from coercing to empty!
-  }
-}
-
-// Tags sometimes have large values, for example json representing a query or a
-// stack trace. Format these so that they don't scroll off the side of the screen.
-export function formatTagValue(value) {
-  const type = $.type(value);
-  if (type === 'object' || type === 'array' || value == null) {
-    return `<pre><code>${escapeHtml(JSON.stringify(value, null, 2))}</code></pre>`;
-  }
-  const result = value.toString();
-  // Preformat if the text includes newlines
-  return result.indexOf('\n') === -1 ? escapeHtml(result)
-    : `<pre><code>${escapeHtml(result)}</code></pre>`;
-}
-
-export default component(function spanPanel() {
-  this.$annotationTemplate = null;
-  this.$tagTemplate = null;
-  this.$showIdsTemplate = null;
-
-  this.show = function(e, span) {
-    const self = this;
-    const tagMap = {};
-
-    this.$node.find('.modal-title').text(
-      `${span.serviceName}.${span.spanName}: ${span.durationStr}`);
-
-    this.$node.find('.service-names').text(span.serviceNames);
-
-    const $annoBody = this.$node.find('#annotations tbody').text('');
-    $.each(span.annotations, (i, anno) => {
-      const $row = self.$annotationTemplate.clone();
-      maybeMarkTransientError($row, anno);
-      $row.find('td').each(function() {
-        const $this = $(this);
-        const propertyName = $this.data('key');
-        const text = propertyName === 'value'
-          ? formatAnnotationValue(anno.value)
-          : anno[propertyName];
-        $this.append(text);
-      });
-      $annoBody.append($row);
-    });
-
-    $annoBody.find('.local-datetime').each(function() {
-      const $this = $(this);
-      const timestamp = $this.text();
-      $this.text((new Date(parseInt(timestamp, 10) / 1000)).toLocaleString());
-    });
-
-    const $tagBody = this.$node.find('#tags tbody').text('');
-    $.each((span.tags || []), (i, anno) => {
-      if (isDupeTag(tagMap, anno)) return;
-      const $row = self.$tagTemplate.clone();
-      if (anno.key === Constants.ERROR) {
-        $row.addClass('anno-error-critical');
-      }
-      $row.find('td').each(function() {
-        const $this = $(this);
-        const propertyName = $this.data('key');
-        const text = propertyName === 'value'
-          ? formatTagValue(anno.value)
-          : escapeHtml(anno[propertyName]);
-        $this.append(text);
-      });
-      $tagBody.append($row);
-    });
-
-    const $showIdsBody = this.$node.find('#showIds tbody').text('');
-    const showIds = [['traceId', span.traceId],
-                      ['spanId', span.id],
-                      ['parentId', span.parentId]];
-    $.each(showIds, (i, pair) => {
-      const $row = self.$showIdsTemplate.clone();
-      $row.find('.key').text(pair[0]);
-      $row.find('.value').text(pair[1]);
-      $showIdsBody.append($row);
-    });
-
-    this.$node.modal('show');
-  };
-
-  this.after('initialize', function() {
-    this.$node.modal('hide');
-    this.$annotationTemplate = this.$node.find('#annotations tbody tr').remove();
-    this.$tagTemplate = this.$node.find('#tags tbody tr').remove();
-    this.$showIdsTemplate = this.$node.find('#showIds tbody tr').remove();
-    this.on(document, 'uiRequestSpanPanel', this.show);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/spanRow.js b/zipkin-ui/js/component_ui/spanRow.js
deleted file mode 100644
index 6c22ef6..0000000
--- a/zipkin-ui/js/component_ui/spanRow.js
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {ConstantNames} from './traceConstants';
-
-// returns 'critical' if one of the spans has an error tag or currentErrorType was already critical,
-// returns 'transient' if one of the spans has an ERROR annotation, else
-// returns currentErrorType
-export function getErrorType(span, currentErrorType) {
-  if (currentErrorType === 'critical') return currentErrorType;
-  if (span.tags.error !== undefined) { // empty error tag is ok
-    return 'critical';
-  } else if (span.annotations.findIndex(ann => ann.value === 'error') !== -1) {
-    return 'transient';
-  }
-  return currentErrorType;
-}
-
-export function formatEndpoint(endpoint) {
-  if (!endpoint) return undefined;
-  const {ipv4, ipv6, port, serviceName} = endpoint;
-  if (ipv4 || ipv6) {
-    const ip = ipv6 ? `[${ipv6}]` : ipv4; // arbitrarily prefer ipv6
-    const portString = port ? `:${port}` : '';
-    const serviceNameString = serviceName ? ` (${serviceName})` : '';
-    return ip + portString + serviceNameString;
-  } else {
-    return serviceName || '';
-  }
-}
-
-/*
- * Derived means not annotated directly. Ex 'Server Start' reflects the the timestamp of a
- * kind=SERVER span. 'Server Finish' is timestamp+duration of the same.
- */
-function toAnnotationRow(a, localFormatted, isDerived = false) {
-  const res = {
-    isDerived,
-    value: ConstantNames[a.value] || a.value,
-    timestamp: a.timestamp
-  };
-  if (localFormatted) res.endpoint = localFormatted;
-  return res;
-}
-
-function parseAnnotationRows(span) {
-  const localFormatted = formatEndpoint(span.localEndpoint) || undefined;
-
-  let startTs = span.timestamp || 0;
-  let endTs = startTs && span.duration ? startTs + span.duration : 0;
-  let msTs = 0;
-  let wsTs = 0;
-  let wrTs = 0;
-  let mrTs = 0;
-
-  let begin;
-  let end;
-
-  let kind = span.kind;
-
-  // scan annotations in case there are better timestamps, or inferred kind
-  const annotationsToAdd = [];
-  span.annotations.forEach(a => {
-    switch (a.value) {
-      case 'cs':
-        kind = 'CLIENT';
-        if (a.timestamp <= startTs) {
-          startTs = a.timestamp;
-        } else {
-          annotationsToAdd.push(a);
-        }
-        break;
-      case 'sr':
-        kind = 'SERVER';
-        if (a.timestamp <= startTs) {
-          startTs = a.timestamp;
-        } else {
-          annotationsToAdd.push(a);
-        }
-        break;
-      case 'ss':
-        kind = 'SERVER';
-        if (a.timestamp >= endTs) {
-          endTs = a.timestamp;
-        } else {
-          annotationsToAdd.push(a);
-        }
-        break;
-      case 'cr':
-        kind = 'CLIENT';
-        if (a.timestamp >= endTs) {
-          endTs = a.timestamp;
-        } else {
-          annotationsToAdd.push(a);
-        }
-        break;
-      case 'ms':
-        kind = 'PRODUCER';
-        msTs = a.timestamp;
-        break;
-      case 'mr':
-        kind = 'CONSUMER';
-        mrTs = a.timestamp;
-        break;
-      case 'ws':
-        wsTs = a.timestamp;
-        break;
-      case 'wr':
-        wrTs = a.timestamp;
-        break;
-      default:
-        annotationsToAdd.push(a);
-    }
-  });
-
-  switch (kind) {
-    case 'CLIENT':
-      begin = 'Client Start';
-      end = 'Client Finish';
-      break;
-    case 'SERVER':
-      begin = 'Server Start';
-      end = 'Server Finish';
-      break;
-    case 'PRODUCER':
-      begin = 'Producer Start';
-      end = 'Producer Finish';
-      if (startTs === 0 || (msTs !== 0 && msTs < startTs)) {
-        startTs = msTs;
-        msTs = 0;
-      }
-      if (endTs === 0 || (wsTs !== 0 && wsTs > endTs)) {
-        endTs = wsTs;
-        wsTs = 0;
-      }
-      break;
-    case 'CONSUMER':
-      if (startTs === 0 || (wrTs !== 0 && wrTs < startTs)) {
-        startTs = wrTs;
-        wrTs = 0;
-      }
-      if (endTs === 0 || (mrTs !== 0 && mrTs > endTs)) {
-        endTs = mrTs;
-        mrTs = 0;
-      }
-      if (endTs !== 0 || wrTs !== 0) {
-        begin = 'Consumer Start';
-        end = 'Consumer Finish';
-      } else {
-        begin = 'Consumer Start';
-      }
-      break;
-    default:
-  }
-
-  // restore sometimes special-cased annotations
-  if (msTs) annotationsToAdd.push({timestamp: msTs, value: 'ms'});
-  if (wsTs) annotationsToAdd.push({timestamp: wsTs, value: 'ws'});
-  if (wrTs) annotationsToAdd.push({timestamp: wrTs, value: 'wr'});
-  if (mrTs) annotationsToAdd.push({timestamp: mrTs, value: 'mr'});
-
-  const beginAnnotation = startTs && begin;
-  const endAnnotation = endTs && end;
-
-  const annotations = []; // prefer empty to undefined for arrays
-
-  let annotationCount = annotationsToAdd.length;
-  if (beginAnnotation) {
-    annotationCount++;
-    annotations.push(toAnnotationRow({
-      value: begin,
-      timestamp: startTs
-    }, localFormatted, true));
-  }
-
-  annotationsToAdd.forEach((a) => {
-    if (beginAnnotation && a.value === begin) return;
-    if (endAnnotation && a.value === end) return;
-    annotations.push(toAnnotationRow(a, localFormatted));
-  });
-
-  if (endAnnotation) {
-    annotationCount++;
-    annotations.push(toAnnotationRow({
-      value: end,
-      timestamp: endTs
-    }, localFormatted, true));
-  }
-  return annotations;
-}
-
-function parseTagRows(span) {
-  const localFormatted = formatEndpoint(span.localEndpoint) || undefined;
-
-  const tagRows = []; // prefer empty to undefined for arrays
-  const keys = Object.keys(span.tags);
-  if (keys.length > 0) {
-    keys.forEach(key => {
-      const tagRow = {
-        key: ConstantNames[key] || key,
-        value: span.tags[key]
-      };
-      if (localFormatted) tagRow.endpoints = [localFormatted];
-      tagRows.push(tagRow);
-    });
-  }
-
-  // Ensure there's at least some data that will display the local address
-  if (!span.kind && span.annotations.length === 0 && localFormatted && keys.length === 0) {
-    tagRows.push({
-      key: 'Local Address',
-      value: localFormatted
-    });
-  }
-
-  let addr;
-  switch (span.kind) {
-    case 'CLIENT':
-      addr = 'Server Address';
-      break;
-    case 'SERVER':
-      addr = 'Client Address';
-      break;
-    case 'PRODUCER':
-      addr = 'Broker Address';
-      break;
-    case 'CONSUMER':
-      addr = 'Broker Address';
-      break;
-    default:
-  }
-
-  if (span.remoteEndpoint) {
-    tagRows.push({
-      key: addr || 'Server Address', // default when we don't know the endpoint
-      value: formatEndpoint(span.remoteEndpoint)
-    });
-  }
-  return tagRows;
-}
-
-// This ensures we don't add duplicate annotations on merge
-function maybePushAnnotation(annotations, a) {
-  if (annotations.findIndex(b => a.timestamp === b.timestamp && a.value === b.value) === -1) {
-    annotations.push(a);
-  }
-}
-
-// This ensures we only add rows for tags that are unique on key and value on merge
-function maybePushTag(tags, a) {
-  const sameKeyAndValue = tags.filter(b => a.key === b.key && a.value === b.value);
-  if (sameKeyAndValue.length === 0) {
-    tags.push(a);
-    return;
-  }
-  if ((a.endpoints || []).length === 0) {
-    return; // no endpoints to merge
-  }
-  // Handle when tags are reported by different endpoints
-  sameKeyAndValue.forEach((t) => {
-    if (!t.endpoints) t.endpoints = []; // eslint-disable-line no-param-reassign
-    a.endpoints.forEach((endpoint) => {
-      if (t.endpoints.indexOf(endpoint) === -1) {
-        t.endpoints.push(endpoint);
-      }
-    });
-  });
-}
-
-// This guards to ensure we don't add duplicate service names on merge
-function maybePushServiceName(serviceNames, serviceName) {
-  if (!serviceName) return;
-  if (serviceNames.findIndex(s => s === serviceName) === -1) {
-    serviceNames.push(serviceName);
-  }
-}
-
-function getServiceName(endpoint) {
-  return endpoint ? endpoint.serviceName : undefined;
-}
-
-// Merges the data into a single span row, which is lacking presentation information
-export function newSpanRow(spansToMerge, isLeafSpan) {
-  const first = spansToMerge[0];
-  const res = {
-    spanId: first.id,
-    serviceNames: [],
-    annotations: [],
-    tags: [],
-    errorType: 'none'
-  };
-
-  let sharedTimestamp;
-  let sharedDuration;
-  spansToMerge.forEach(next => {
-    if (next.parentId) res.parentId = next.parentId;
-    if (next.name && (!res.spanName || next.kind === 'SERVER')) {
-      res.spanName = next.name; // prefer the server's span name
-    }
-
-    if (next.shared) { // save off any shared timestamp, it is our second choice
-      if (!sharedTimestamp) sharedTimestamp = next.timestamp;
-      if (!sharedDuration) sharedDuration = next.duration;
-    } else {
-      if (!res.timestamp && next.timestamp) res.timestamp = next.timestamp;
-      if (!res.duration && next.duration) res.duration = next.duration;
-    }
-
-    const nextLocalServiceName = getServiceName(next.localEndpoint);
-    const nextRemoteServiceName = getServiceName(next.remoteEndpoint);
-    if (nextLocalServiceName && next.kind === 'SERVER') {
-      res.serviceName = nextLocalServiceName; // prefer the server's service name
-    } else if (isLeafSpan && nextRemoteServiceName && next.kind === 'CLIENT' && !res.serviceName) {
-      // use the client's remote service name only on leaf spans
-      res.serviceName = nextRemoteServiceName;
-    } else if (nextLocalServiceName && !res.serviceName) {
-      res.serviceName = nextLocalServiceName;
-    }
-
-    maybePushServiceName(res.serviceNames, nextLocalServiceName);
-    maybePushServiceName(res.serviceNames, nextRemoteServiceName);
-
-    parseAnnotationRows(next).forEach((a) => maybePushAnnotation(res.annotations, a));
-    parseTagRows(next).forEach((t) => maybePushTag(res.tags, t));
-
-    res.errorType = getErrorType(next, res.errorType);
-
-    if (next.debug) res.debug = true;
-  });
-
-  // timestamp is used to derive positional data later
-  if (!res.timestamp && sharedTimestamp) res.timestamp = sharedTimestamp;
-  // duration is used for deriving data, and also for the zoom function
-  if (!res.duration && sharedDuration) res.duration = sharedDuration;
-
-  res.serviceNames.sort();
-  res.annotations.sort((a, b) => a.timestamp - b.timestamp);
-  return res;
-}
diff --git a/zipkin-ui/js/component_ui/timeStamp.js b/zipkin-ui/js/component_ui/timeStamp.js
deleted file mode 100644
index d27b7ee..0000000
--- a/zipkin-ui/js/component_ui/timeStamp.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import moment from 'moment';
-import bootstrapDatepicker from 'bootstrap-datepicker'; // eslint-disable-line no-unused-vars
-
-export default component(function timeStamp() {
-  this.init = function() {
-    this.$timestamp = this.$node.find('.timestamp-value');
-    this.$date = this.$node.find('.date-input');
-    this.$time = this.$node.find('.time-input');
-    const ts = this.$timestamp.val();
-    this.setDateTime((ts) ? moment(Number(ts)) : moment());
-  };
-
-  this.setDateTime = function(time) {
-    this.$date.val(time.format('YYYY-MM-DD'));
-    this.$time.val(time.format('HH:mm'));
-  };
-
-  this.setTimestamp = function(time) {
-    this.$timestamp.val(time.valueOf());
-  };
-
-  this.dateChanged = function(e) {
-    const time = moment(e.date);
-    time.add(moment.duration(this.$time.val()));
-    this.setTimestamp(moment.utc(time));
-  };
-
-  this.timeChanged = function() {
-    const time = moment(this.$date.val(), 'YYYY-MM-DD');
-    time.add(moment.duration(this.$time.val()));
-    this.setTimestamp(moment.utc(time));
-  };
-
-  this.after('initialize', function() {
-    this.init();
-    this.on(this.$time, 'change', this.timeChanged);
-    this.$date.datepicker({format: 'yyyy-mm-dd'});
-    this.on(this.$date, 'changeDate', this.dateChanged.bind(this));
-  });
-});
diff --git a/zipkin-ui/js/component_ui/trace.js b/zipkin-ui/js/component_ui/trace.js
deleted file mode 100644
index 01d4388..0000000
--- a/zipkin-ui/js/component_ui/trace.js
+++ /dev/null
@@ -1,483 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/* eslint-disable no-param-reassign, prefer-template */
-import {component} from 'flightjs';
-import queryString from 'query-string';
-import $ from 'jquery';
-import {i18nInit} from '../component_ui/i18n';
-
-// extracted for testing. this code mutates spans and selectedSpans
-export function showSpans(spans, parents, childIds, selectedSpans) {
-  const family = new Set();
-  $.each(selectedSpans, (i, $selected) => {
-    $selected.show();
-    $selected.addClass('highlight');
-    $selected.expanded = true;
-    $selected.$expander.html('<i class="far fa-minus-square"></i>');
-
-    $.each(childIds[$selected.id], (j, cId) => {
-      family.add(cId);
-      spans[cId].openParents += 1;
-    });
-    $.each(parents[$selected.id], (j, pId) => {
-      /* Parent may not be found for a number of reasons. For example, the
-      trace id may not be a span id. Also, it is possible to lose the root
-      span data (i.e. a headless trace) */
-      if (spans[pId]) {
-        family.add(pId);
-        spans[pId].openChildren += 1;
-      }
-    });
-  });
-  family.forEach(id => spans[id].show());
-}
-
-// extracted for testing. this code mutates spans and selectedSpans
-export function hideSpans(spans, parents, childIds, selectedSpans, childrenOnly) {
-  const family = new Set();
-  $.each(selectedSpans, (i, $selected) => {
-    if (!childrenOnly === 0) {
-      $selected.hide();
-    }
-    $selected.removeClass('highlight');
-    $selected.expanded = false;
-    $selected.$expander.html('<i class="far fa-plus-square"></i>');
-
-    $.each(childIds[$selected.id], (j, cId) => {
-      family.add(cId);
-      // Decrement only when there is an open parent
-      if (spans[cId].openParents >= 1) spans[cId].openParents -= 1;
-    });
-    if (!childrenOnly) {
-      $.each(parents[$selected.id], (j, pId) => {
-        /* Parent may not be found for a number of reasons. For example, the
-        trace id may not be a span id. Also, it is possible to lose the root
-        span data (i.e. a headless trace) */
-        if (spans[pId]) {
-          family.add(pId);
-          spans[pId].openChildren -= 1;
-        }
-      });
-    }
-  });
-  family.forEach(id => hideSpans(spans, parents, childIds, [spans[id]], true));
-  family.forEach(id => spans[id].hide());
-}
-
-function spanChildIds($span) {
-  const childIds = ($span.attr('data-child-ids') || '').toString().split(',');
-  if (childIds.length === 1 && childIds[0] === '') {
-    $span.find('.expander').hide();
-    return [];
-  } else {
-    return childIds;
-  }
-}
-
-function initSpan($span) {
-  const id = $span.data('id');
-  $span.id = id;
-  $span.expanded = false;
-  $span.$expander = $span.find('.expander');
-  $span.openChildren = 0;
-  $span.openParents = 0;
-
-  const parentId = $span.data('parentId');
-  $span.parentId = parentId;
-  $span.isRoot = !(parentId !== undefined && parentId !== '');
-  return $span;
-}
-
-export function initSpans($node) {
-  const spans = {};
-  const childIds = {};
-  const parents = {};
-  // this includes both local and remote service names
-  const spansByService = {};
-
-  $node.find('.span:not(#timeLabel)').each(function() {
-    const span = initSpan($(this));
-    const id = span.id;
-    const parentId = span.parentId;
-
-    spans[id] = span;
-    childIds[id] = spanChildIds(span);
-    parents[id] = !span.isRoot ? [parentId] : [];
-    $.merge(parents[id], parents[parentId] || []);
-
-    $.each((span.attr('data-service-names') || '').split(','), (i, sn) => {
-      const current = spansByService[sn] || [];
-      current.push(id);
-      spansByService[sn] = current;
-    });
-  });
-
-  return {
-    spans,
-    childIds,
-    parents,
-    spansByService
-  };
-}
-
-export default component(function trace() {
-  /*
-   * Next variables are setting up after initialization.
-   * see initSpans
-   *
-   * this.spans = {};
-   * this.parents = {};
-   * this.childIds = {};
-   * this.spansByService = {};
-   */
-  this.spansBackup = {};
-
-  /* this is for a temporary rectangle which is shown on
-   * user's mouse move over span view.*/
-  this.rectElement = $('<div>').addClass('rect-element');
-
-  /* This method stores original span details for later use.
-   * When span view is zoomed in and zoomed out these details help to
-   * get back to original span view*/
-  this.setupSpansBackup = function($span) {
-    const id = $span.data('id');
-    $span.id = id;
-    this.spansBackup[id] = $span;
-  };
-
-  /* Returns a jquery object representing the spans in svc */
-  this.getSpansByService = function(svc) {
-    let spans = this.spansByService[svc];
-    if (spans === undefined) {
-      this.spansByService[svc] = spans = $();
-    } else if (spans.jquery === undefined) {
-      this.spansByService[svc] = spans = $(`#${spans.join(',#')}`);
-    }
-    return spans;
-  };
-
-  this.expandSpans = function(spans) {
-    showSpans(this.spans, this.parents, this.childIds, spans);
-  };
-
-  this.collapseSpans = function(spans, childrenOnly) {
-    hideSpans(this.spans, this.parents, this.childIds, spans, childrenOnly);
-  };
-
-  this.handleClick = function(e) {
-    const $target = $(e.target);
-    const $span = this.spans[($target.is('.span') ? $target : $target.parents('.span')).data('id')];
-
-    const $expander = $target.is('.expander') ? $target : $target.parents('.expander');
-    if ($expander.length > 0) {
-      this.toggleExpander($span);
-      return;
-    }
-
-    /* incase mouse moved to another span after mousedown, $span is undefined*/
-    if ($span && $span.length > 0) {
-      this.showSpanDetails($span);
-    }
-  };
-
-  this.originalDuration = function() {
-    const markerText = $('#timeLabel-backup .time-marker-5').text();
-    return markerText ? parseFloat(markerText) : 0;
-  };
-
-  /* On mousedown and mousemove we need to show a selection area and zoomin
-   * spans according to width of selected area. During zoomin only the
-   * width i.e. x coordinates are considered.*/
-  this.handleMouseDown = function(e) {
-    const self = this;
-
-    const rectTop = e.pageY;
-    const rectLeft = e.pageX;
-    self.rectElement.appendTo(self.$node);
-
-    /* dont draw the rectangle until mouse is moved.
-     * this helps in getting the correct parent in case of click
-     * event.*/
-    self.rectElement.css({top: '0px', left: '0px', width: '0px', height: '0px'});
-
-    self.$node.bind('mousemove', ev => {
-      /* prevent selection and thus highlighting of spans after mousedown and mousemove*/
-      ev.preventDefault();
-
-      /* draw a rectangle out of mousedown and mousemove coordinates*/
-      const rectWidth = Math.abs(ev.pageX - rectLeft);
-      const rectHeight = Math.abs(ev.pageY - rectTop);
-
-      const newX = (ev.pageX < rectLeft) ? (rectLeft - rectWidth) : rectLeft;
-      const newY = (ev.pageY < rectTop) ? (rectTop - rectHeight) : rectTop;
-
-      self.rectElement.css({top: `${newY}px`, left: `${newX}px`,
-          width: `${rectWidth}px`, height: `${rectHeight}px`});
-    });
-
-    self.$node.bind('mouseup', ev => {
-      self.$node.unbind('mousemove');
-      self.$node.unbind('mouseup');
-      /* Add code to calculate mintime and max time from pixel value of
-       * mouse down and mouse move*/
-      const originalDuration = this.originalDuration();
-      const spanClickViewLeftOffsetPx = $($('#trace-container .time-marker-0')[1]).offset().left;
-      const spanClickViewWidthPx = $('#trace-container .time-marker-5').position().left;
-
-      const rectTopLeft = self.rectElement.position().left;
-      /* make sure that redraw mintime starts from 0.0 not less than 0.0.
-       * if user starts selecting from servicename adjust the left, width accordingly*/
-      const rectElementActualLeft =
-        (rectTopLeft < spanClickViewLeftOffsetPx) ? spanClickViewLeftOffsetPx : rectTopLeft;
-      const rectElementActualWidth =
-        (rectTopLeft < spanClickViewLeftOffsetPx) ?
-        (self.rectElement.width() - (spanClickViewLeftOffsetPx - rectTopLeft))
-            : self.rectElement.width();
-
-      const minTimeOffsetPx = rectElementActualLeft - spanClickViewLeftOffsetPx;
-      const maxTimeOffsetPx = (rectElementActualLeft + rectElementActualWidth)
-          - spanClickViewLeftOffsetPx;
-
-      const minTime = minTimeOffsetPx * (originalDuration / spanClickViewWidthPx);
-      const maxTime = maxTimeOffsetPx * (originalDuration / spanClickViewWidthPx);
-
-      /* when mousemove doesnt happen mintime is greater than maxtime. since rect-element
-       * is created at top left corner of screen, rectElementActualWidth will be negative.
-       *We need to invoke mouseclick functionality*/
-      if (minTime >= maxTime) {
-        /* pass on the target which got clicked. Since we do not draw
-         * rectangle on mousedown, we would never endup having
-         * rect-element as our target. Target would always be either
-         * handle, time-marker, duration which are all children of span class*/
-        self.handleClick(ev.target);
-      } else {
-        /* now that we have min and max time, trigger zoominspans*/
-        self.trigger(document, 'uiZoomInSpans', {mintime: minTime, maxtime: maxTime});
-      }
-      self.rectElement.remove();
-    });
-  };
-
-
-  this.toggleExpander = function($span) {
-    if ($span.expanded) {
-      this.collapseSpans([$span], true);
-    } else {
-      this.expandSpans([$span], true);
-    }
-  };
-
-  this.showSpanDetails = function($span) {
-    const spanData = {
-      annotations: [],
-      tags: []
-    };
-
-    $.each($span.data('keys').split(','), (i, n) => { spanData[n] = $span.data(n); });
-
-    $span.find('.annotation').each(function() {
-      const $this = $(this);
-      const anno = {};
-      $.each($this.data('keys').split(','), (e, n) => { anno[n] = $this.data(n); });
-      spanData.annotations.push(anno);
-    });
-
-    $span.find('.tag').each(function() {
-      const $this = $(this);
-      const anno = {};
-      $.each($this.data('keys').split(','), (e, n) => { anno[n] = $this.data(n); });
-      spanData.tags.push(anno);
-    });
-
-    this.trigger('uiRequestSpanPanel', spanData);
-  };
-
-  this.showSpinnerAround = function(cb, e, data) {
-    if (this.actingOnAll) {
-      cb(e, data);
-    } else {
-      this.trigger(document, 'uiShowFullPageSpinner');
-      setTimeout(() => {
-        cb(e, data);
-        this.trigger(document, 'uiHideFullPageSpinner');
-      }, 100);
-    }
-  };
-
-
-  this.expandAllSpans = function() {
-    const self = this;
-    self.actingOnAll = true;
-    this.showSpinnerAround(() => {
-      showSpans(self.spans, self.parents, self.childIds, self.spans);
-    });
-    self.actingOnAll = false;
-    $('#expandAll').addClass('active');
-    $('#collapseAll').removeClass('active');
-  };
-
-  this.collapseAllSpans = function() {
-    const self = this;
-    self.actingOnAll = true;
-    this.showSpinnerAround(() => {
-      $.each(self.spans, (id, $span) => {
-        $span.openParents = 0;
-        $span.openChildren = 0;
-        $span.removeClass('highlight');
-        $span.expanded = false;
-        $span.$expander.html('<i class="far fa-plus-square"></i>');
-        if (!$span.isRoot) $span.hide();
-      });
-    });
-    self.actingOnAll = false;
-    $('#expandAll').removeClass('active');
-    $('#collapseAll').addClass('active');
-  };
-
-  /* This method modifies the span container view. It zooms in the span view
-   * for selected time duration. Spans starting with in the selected time
-   * duration are highlighted with span name in red color.
-   * Also unhides zoomout button so that user can click to go
-   * back to original span view*/
-  this.zoomInSpans = function(node, data) {
-    const self = this;
-
-    const originalDuration = this.originalDuration();
-
-    const mintime = data.mintime;
-    const maxtime = data.maxtime;
-    const newDuration = maxtime - mintime;
-
-    self.$node.find('#timeLabel .time-marker').each(function(i) {
-      const v = (mintime + newDuration * (i / 5)).toFixed(2);
-      // TODO:show time units according to new duration
-      $(this).text(v);
-      $(this).css('color', '#d9534f');
-    });
-
-    const styles = {
-      left: '0.0%',
-      width: '100.0%',
-      color: '#000'
-    };
-    self.showSpinnerAround(() => {
-      $.each(self.spans, (id, $span) => {
-        /* corresponding to this id extract span from backupspans list*/
-        const origLeftVal = parseFloat((self.spansBackup[id]).find('.duration')[0].style.left);
-        const origWidthVal = parseFloat((self.spansBackup[id]).find('.duration')[0].style.width);
-        /* left and width are in %, so convert them as time values*/
-        const spanStart = (origLeftVal * originalDuration) / 100;
-        const spanEnd = spanStart + (origWidthVal * originalDuration) / 100;
-        /* reset the color to black. It gets set for inrange spans to red*/
-        styles.color = '#000';
-
-        /* change style left, width and color of new spans based on mintime and maxtime*/
-        if (spanStart < mintime && spanEnd < mintime) {
-          styles.left = '0.0%'; styles.width = '0.0%';
-        } else if (spanStart < mintime && spanEnd > mintime && spanEnd < maxtime) {
-          const w = (((spanEnd - mintime)) / newDuration) * 100 + '%';
-          styles.left = '0.0%'; styles.width = w;
-        } else if (spanStart < mintime && spanEnd > mintime && spanEnd > maxtime) {
-          styles.left = '0.0%'; styles.width = '100.0%';
-        } else if (spanStart >= mintime && spanStart < maxtime && spanEnd <= maxtime) {
-          const l = (((spanStart - mintime)) / newDuration) * 100 + '%';
-          const w = (((spanEnd - spanStart)) / newDuration) * 100 + '%';
-          styles.left = l; styles.width = w; styles.color = '#d9534f';
-        } else if (spanStart >= mintime && spanStart < maxtime && spanEnd > maxtime) {
-          const l = (((spanStart - mintime)) / newDuration) * 100 + '%';
-          const w = (((maxtime - spanStart)) / newDuration) * 100 + '%';
-          styles.left = l; styles.width = w; styles.color = '#d9534f';
-        } else if (spanStart > maxtime) {
-          styles.left = '100.0%'; styles.width = '0.0%';
-        } else if (spanStart === maxtime) {
-          styles.left = '100.0%'; styles.width = '0.0%'; styles.color = '#d9534f';
-        } else {
-          styles.left = '0.0%'; styles.width = '0.0%';
-        }
-
-        $span.find('.duration').css('color', styles.color);
-        $span.find('.duration').animate({left: styles.left, width: styles.width}, 'slow');
-      });
-    });
-
-    /* make sure that zoom-in on already zoomed spanview is not allowed*/
-    self.$node.unbind('mousedown');
-
-    /* show zoomOut button now*/
-    $('button[value=uiZoomOutSpans]').show('slow');
-  };
-
-
-  /* This method brings back the original span container in view*/
-  this.zoomOutSpans = function() {
-    /* re bind mousedown event to enable zoom-in after zoom-out*/
-    this.on('mousedown', this.handleMouseDown);
-
-    /* get values from the backup trace container*/
-    this.$node.find('#timeLabel .time-marker').each(function(i) {
-      $(this).css('color', $('#timeLabel-backup .time-marker-' + i).css('color'));
-      $(this).text($('#timeLabel-backup .time-marker-' + i).text());
-    });
-
-    const self = this;
-    this.showSpinnerAround(() => {
-      $.each(self.spans, (id, $span) => {
-        const originalStyleLeft = (self.spansBackup[id]).find('.duration')[0].style.left;
-        const originalStyleWidth = (self.spansBackup[id]).find('.duration')[0].style.width;
-
-        $span.find('.duration').animate({
-          left: originalStyleLeft,
-          width: originalStyleWidth
-        }, 'slow');
-        $span.find('.duration').css('color', '#000');
-      });
-    });
-
-    /* hide zoomOut button now*/
-    $('button[value=uiZoomOutSpans]').hide('slow');
-  };
-
-  this.after('initialize', function() {
-    this.on('click', this.handleClick);
-    this.on('mousedown', this.handleMouseDown);
-    this.on(document, 'uiExpandAllSpans', this.expandAllSpans);
-    this.on(document, 'uiCollapseAllSpans', this.collapseAllSpans);
-    this.on(document, 'uiZoomInSpans', this.zoomInSpans);
-    this.on(document, 'uiZoomOutSpans', this.zoomOutSpans);
-
-    const self = this;
-    const initData = initSpans(self.$node);
-    this.spans = initData.spans;
-    this.parents = initData.parents;
-    this.childIds = initData.childIds;
-    this.spansByService = initData.spansByService;
-
-    /* get spans from trace-container-backup*/
-    $('#trace-container-backup .span:not(#timeLabel-backup)').each(function() {
-      self.setupSpansBackup($(this));
-    });
-
-    const serviceName = queryString.parse(location.search).serviceName;
-    if (serviceName !== undefined) {
-      this.trigger(document, 'uiAddServiceNameFilter', {value: serviceName});
-    } else {
-      this.trigger(document, 'uiExpandAllSpans');
-    }
-
-    i18nInit('trace');
-  });
-});
diff --git a/zipkin-ui/js/component_ui/traceConstants.js b/zipkin-ui/js/component_ui/traceConstants.js
deleted file mode 100644
index b3c2dbd..0000000
--- a/zipkin-ui/js/component_ui/traceConstants.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-const CLIENT_SEND = 'cs';
-const CLIENT_SEND_FRAGMENT = 'csf';
-const CLIENT_RECEIVE = 'cr';
-const CLIENT_RECEIVE_FRAGMENT = 'crf';
-const MESSAGE_SEND = 'ms';
-const MESSAGE_RECEIVE = 'mr';
-const SERVER_SEND = 'ss';
-const SERVER_SEND_FRAGMENT = 'ssf';
-const SERVER_RECEIVE = 'sr';
-const SERVER_RECEIVE_FRAGMENT = 'srf';
-const SERVER_ADDR = 'sa';
-const CLIENT_ADDR = 'ca';
-const MESSAGE_ADDR = 'ma';
-const WIRE_SEND = 'ws';
-const WIRE_RECEIVE = 'wr';
-const ERROR = 'error';
-const LOCAL_COMPONENT = 'lc';
-export const Constants = {
-  CLIENT_SEND,
-  CLIENT_SEND_FRAGMENT,
-  CLIENT_RECEIVE,
-  CLIENT_RECEIVE_FRAGMENT,
-  MESSAGE_SEND,
-  MESSAGE_RECEIVE,
-  SERVER_SEND,
-  SERVER_SEND_FRAGMENT,
-  SERVER_RECEIVE,
-  SERVER_RECEIVE_FRAGMENT,
-  SERVER_ADDR,
-  CLIENT_ADDR,
-  MESSAGE_ADDR,
-  ERROR,
-  LOCAL_COMPONENT,
-};
-
-export const ConstantNames = {};
-ConstantNames[CLIENT_SEND] = 'Client Send';
-ConstantNames[CLIENT_SEND_FRAGMENT] = 'Client Send Fragment';
-ConstantNames[CLIENT_RECEIVE] = 'Client Receive';
-ConstantNames[CLIENT_RECEIVE_FRAGMENT] = 'Client Receive Fragment';
-ConstantNames[MESSAGE_SEND] = 'Producer Send';
-ConstantNames[MESSAGE_RECEIVE] = 'Consumer Receive';
-ConstantNames[SERVER_SEND] = 'Server Send';
-ConstantNames[SERVER_SEND_FRAGMENT] = 'Server Send Fragment';
-ConstantNames[SERVER_RECEIVE] = 'Server Receive';
-ConstantNames[SERVER_RECEIVE_FRAGMENT] = 'Server Receive Fragment';
-ConstantNames[CLIENT_ADDR] = 'Client Address';
-ConstantNames[MESSAGE_ADDR] = 'Broker Address';
-ConstantNames[SERVER_ADDR] = 'Server Address';
-ConstantNames[WIRE_SEND] = 'Wire Send';
-ConstantNames[WIRE_RECEIVE] = 'Wire Receive';
-ConstantNames[LOCAL_COMPONENT] = 'Local Component';
-// Don't add ERROR to ConstantNames -- css coloring depends on constant name 'error'
diff --git a/zipkin-ui/js/component_ui/traceFilters.js b/zipkin-ui/js/component_ui/traceFilters.js
deleted file mode 100644
index f7ede97..0000000
--- a/zipkin-ui/js/component_ui/traceFilters.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import $ from 'jquery';
-
-export default component(function traceFilters() {
-  this.idFromService = function(service) {
-    return `service-filter-${service.replace(/[^a-z0-9\-_]/gi, '-')}`;
-  };
-
-  this.addToFilter = function(ev, data) {
-    if (this.$node.find(`[data-service-name='${data.value}']`).length) return;
-
-    // TODO: should this be mustache instead?
-    const $remove =
-      $('<span>')
-        .attr('class', 'badge service-filter-remove')
-        .text('x')
-        .on('click', function() { $(this).trigger('uiRemoveServiceNameFilter', data); });
-
-    const $html =
-      $('<span>')
-        .attr('class', 'label service-filter-label')
-        .attr('id', this.idFromService(data.value))
-        .attr('data-serviceName', data.value)
-        .text(data.value)
-        .append($remove);
-
-    this.$node.find('.service-tags').append($html);
-  };
-
-  this.removeFromFilter = function(ev, data) {
-    this.$node.find(`#${this.idFromService(data.value)}`).remove();
-  };
-
-  this.updateTraces = function(ev, data) {
-    this.$node.find('.filter-current').text(data.traces.length);
-  };
-
-  this.updateSortOrder = function(ev) {
-    this.trigger(document, 'uiUpdateTraceSortOrder', {order: $(ev.target).val()});
-  };
-
-  this.after('initialize', function() {
-    this.on(document, 'uiAddServiceNameFilter', this.addToFilter);
-    this.on(document, 'uiRemoveServiceNameFilter', this.removeFromFilter);
-    this.on(document, 'uiUpdateTraces', this.updateTraces);
-    this.on('.sort-order', 'change', this.updateSortOrder);
-  });
-});
diff --git a/zipkin-ui/js/component_ui/traceSummary.js b/zipkin-ui/js/component_ui/traceSummary.js
deleted file mode 100644
index bc8a531..0000000
--- a/zipkin-ui/js/component_ui/traceSummary.js
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// eslint-disable no-nested-ternary
-import _ from 'lodash';
-import moment from 'moment';
-import {getErrorType} from './spanRow';
-
-// To ensure data doesn't scroll off the screen, we need all timestamps, not just
-// client/server ones.
-export function addTimestamps(span, timestamps) {
-  if (!span.timestamp) return;
-  timestamps.push(span.timestamp);
-  if (!span.duration) return;
-  timestamps.push(span.timestamp + span.duration);
-}
-
-export function getMaxDuration(timestamps) {
-  if (timestamps.length > 1) {
-    timestamps.sort();
-    return timestamps[timestamps.length - 1] - timestamps[0];
-  }
-  return 0;
-}
-
-function pushEntry(dict, key, value) {
-  if (dict[key]) {
-    dict[key].push(value);
-  } else {
-    dict[key] = [value]; // eslint-disable-line no-param-reassign
-  }
-}
-
-function addServiceNameTimestampDuration(span, groupedTimestamps) {
-  const value = {
-    timestamp: span.timestamp || 0, // only used by totalDuration
-    duration: span.duration || 0
-  };
-
-  if (span.localEndpoint && span.localEndpoint.serviceName) {
-    pushEntry(groupedTimestamps, span.localEndpoint.serviceName, value);
-  }
-  // TODO: only do this if it is a leaf span and a client or producer.
-  // If we are at the bottom of the tree, it can be helpful to count also against a remote
-  // uninstrumented service
-  if (span.remoteEndpoint && span.remoteEndpoint.serviceName) {
-    pushEntry(groupedTimestamps, span.remoteEndpoint.serviceName, value);
-  }
-}
-
-// Returns null on empty or when missing a timestamp
-export function traceSummary(root) {
-  const timestamps = [];
-  const groupedTimestamps = {};
-
-  let traceId;
-  let spanCount = 0;
-  let errorType = 'none';
-
-  root.traverse(span => {
-    spanCount++;
-    traceId = span.traceId;
-    errorType = getErrorType(span, errorType);
-    addTimestamps(span, timestamps);
-    addServiceNameTimestampDuration(span, groupedTimestamps);
-  });
-
-  if (timestamps.length === 0) throw new Error(`Trace ${traceId} is missing a timestamp`);
-
-  return {
-    traceId,
-    timestamp: timestamps[0],
-    duration: getMaxDuration(timestamps),
-    groupedTimestamps,
-    errorType,
-    spanCount
-  };
-}
-
-// This returns a total duration by merging all overlapping intervals found in the the input.
-//
-// This is used to create servicePercentage for index.mustache when a service is selected
-export function totalDuration(timestampAndDurations) {
-  const filtered = _(timestampAndDurations)
-    .filter((s) => s.duration) // filter out anything we can't make an interval out of
-    .sortBy('timestamp').value(); // to merge intervals, we need the input sorted
-
-  if (filtered.length === 0) {
-    return 0;
-  }
-  if (filtered.length === 1) {
-    return filtered[0].duration;
-  }
-
-  let result = filtered[0].duration;
-  let currentIntervalEnd = filtered[0].timestamp + filtered[0].duration;
-
-  for (let i = 1; i < filtered.length; i++) {
-    const next = filtered[i];
-    const nextIntervalEnd = next.timestamp + next.duration;
-
-    if (nextIntervalEnd <= currentIntervalEnd) { // we are still in the interval
-      continue;
-    } else if (next.timestamp <= currentIntervalEnd) { // we extending the interval
-      result += nextIntervalEnd - currentIntervalEnd;
-      currentIntervalEnd = nextIntervalEnd;
-    } else { // this is a new interval
-      result += next.duration;
-      currentIntervalEnd = nextIntervalEnd;
-    }
-  }
-
-  return result;
-}
-
-function formatDate(timestamp, utc) {
-  let m = moment(timestamp / 1000);
-  if (utc) {
-    m = m.utc();
-  }
-  return m.format('MM-DD-YYYYTHH:mm:ss.SSSZZ');
-}
-
-export function mkDurationStr(duration) {
-  if (duration === 0 || typeof duration === 'undefined') {
-    return '';
-  } else if (duration < 1000) {
-    return `${duration.toFixed(0)}μs`;
-  } else if (duration < 1000000) {
-    if (duration % 1000 === 0) { // Sometimes spans are in milliseconds resolution
-      return `${(duration / 1000).toFixed(0)}ms`;
-    }
-    return `${(duration / 1000).toFixed(3)}ms`;
-  } else {
-    return `${(duration / 1000000).toFixed(3)}s`;
-  }
-}
-
-// maxSpanDurationStr is only used in index.mustache
-export function getServiceSummaries(groupedTimestamps) {
-  return _(groupedTimestamps).toPairs()
-    .map(([serviceName, sts]) => ({
-      serviceName,
-      spanCount: sts.length,
-      maxSpanDuration: Math.max(...sts.map(t => t.duration))
-    }))
-    .orderBy(['maxSpanDuration', 'serviceName'], ['desc', 'asc'])
-    .map(summary => ({
-      serviceName: summary.serviceName,
-      spanCount: summary.spanCount,
-      maxSpanDurationStr: mkDurationStr(summary.maxSpanDuration)
-    })).value();
-}
-
-export function traceSummariesToMustache(serviceName, traceSummaries, utc = false) {
-  const maxDuration = Math.max(...traceSummaries.map((s) => s.duration));
-
-  return traceSummaries.map((t) => {
-    const timestamp = t.timestamp;
-
-    const res = {
-      traceId: t.traceId, // used to navigate to trace screen
-      timestamp, // used only for client-side sort
-      startTs: formatDate(timestamp, utc),
-      spanCount: t.spanCount
-    };
-
-    const duration = t.duration || 0;
-    if (duration) {
-      // used to show the relative duration this trace was compared to others
-      res.width = parseInt(parseFloat(duration) / parseFloat(maxDuration) * 100, 10);
-      res.duration = duration / 1000; // used only for client-side sort
-      res.durationStr = mkDurationStr(duration);
-    }
-
-    // groupedTimestamps is keyed by service name, if there are no service names in the trace,
-    // don't try to add data dependent on service names.
-    if (Object.keys(t.groupedTimestamps).length !== 0) {
-      res.serviceSummaries = getServiceSummaries(t.groupedTimestamps);
-
-      // Only add a service percentage when there is a duration for it
-      if (serviceName && duration && t.groupedTimestamps[serviceName]) {
-        const serviceTime = totalDuration(t.groupedTimestamps[serviceName]);
-        // used for display and also client-side sort by service percentage
-        res.servicePercentage = parseInt(parseFloat(serviceTime) / parseFloat(duration) * 100, 10);
-      }
-    }
-
-    if (t.errorType !== 'none') res.infoClass = `trace-error-${t.errorType}`;
-    return res;
-  }).sort((t1, t2) => {
-    const durationComparison = t2.duration - t1.duration;
-    if (durationComparison === 0) {
-      return t1.traceId.localeCompare(t2.traceId);
-    } else {
-      return durationComparison;
-    }
-  });
-}
diff --git a/zipkin-ui/js/component_ui/traceToMustache.js b/zipkin-ui/js/component_ui/traceToMustache.js
deleted file mode 100644
index c97f0ab..0000000
--- a/zipkin-ui/js/component_ui/traceToMustache.js
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {compare} from '../component_data/spanCleaner';
-import {addTimestamps, getMaxDuration, mkDurationStr} from './traceSummary';
-import {newSpanRow} from './spanRow';
-
-function incrementEntry(dict, key) {
-  if (dict[key]) {
-    dict[key]++; // eslint-disable-line no-param-reassign
-  } else {
-    dict[key] = 1; // eslint-disable-line no-param-reassign
-  }
-}
-
-// We need to do an initial traversal in order to get the timestamp and duration of the trace,
-// as that is used for positioning spans later.
-function getTraceTimestampAndDuration(root) {
-  const timestamps = [];
-  root.traverse(span => addTimestamps(span, timestamps));
-  return {
-    timestamp: timestamps[0] || 0,
-    duration: getMaxDuration(timestamps)
-  };
-}
-
-function addLayoutDetails(
-  spanRow, traceTimestamp, traceDuration, depth, childIds
-) { /* eslint-disable no-param-reassign */
-  spanRow.childIds = childIds;
-  spanRow.depth = (depth + 1) * 5;
-  spanRow.depthClass = (depth - 1) % 6;
-
-  // Add the correct width and duration string for the span
-  if (spanRow.duration) { // implies traceDuration, as trace duration is derived from spans
-    const width = traceDuration ? spanRow.duration / traceDuration * 100 : 0;
-    spanRow.width = width < 0.1 ? 0.1 : width;
-    spanRow.durationStr = mkDurationStr(spanRow.duration); // bubble over the span in trace view
-  } else {
-    spanRow.width = 0.1;
-  }
-
-  if (traceDuration) {
-    // position the span at the correct offset in the trace diagram.
-    spanRow.left = ((spanRow.timestamp - traceTimestamp) / traceDuration * 100);
-
-    // position each annotation at the offset in the trace diagram.
-    spanRow.annotations.forEach(a => { /* eslint-disable no-param-reassign */
-      // left offset here is from the span
-      a.left = spanRow.duration ? ((a.timestamp - spanRow.timestamp) / spanRow.duration * 100) : 0;
-      // relative time is for the trace itself
-      a.relativeTime = mkDurationStr(a.timestamp - traceTimestamp);
-      a.width = 8; // size of the dot
-    });
-  } else {
-    spanRow.left = 0;
-  }
-}
-
-function nodeByTimestamp(a, b) {
-  return compare(a.span.timestamp, b.span.timestamp);
-}
-
-export function traceToMustache(root, logsUrl) {
-  const serviceNameToCount = {};
-  let queue = root.queueRootMostSpans();
-  const modelview = {
-    traceId: queue[0].span.traceId,
-    depth: 0,
-    spans: []
-  };
-
-  const {timestamp, duration} = getTraceTimestampAndDuration(root);
-  if (!timestamp) throw new Error(`Trace ${modelview.traceId} is missing a timestamp`);
-
-  while (queue.length > 0) {
-    let current = queue.shift();
-
-    // This is more than a normal tree traversal, as we are merging any server spans that share the
-    // same ID. When that's the case, we pull up any of their children as if they are our own.
-    const spansToMerge = [current.span];
-    const children = [];
-    current.children.forEach(child => {
-      if (current.span.id === child.span.id) {
-        spansToMerge.push(child.span);
-        child.children.forEach(grandChild => children.push(grandChild));
-      } else {
-        children.push(child);
-      }
-    });
-
-    // Pulling up children may affect our sort order. We re-sort to ensure rows are added in
-    // timestamp order.
-    children.sort(nodeByTimestamp);
-    queue = children.concat(queue);
-    const childIds = children.map(child => child.span.id);
-
-    // The mustache template expects one row per span ID. To get the correct depth class, we need to
-    // count distinct span IDs above us.
-    let depth = 1;
-    while (current.parent && current.parent.span) {
-      if (current.parent.span.id !== current.span.id) depth++;
-      current = current.parent;
-    }
-    // If we are the deepest span, mark the trace accordingly
-    if (depth > modelview.depth) modelview.depth = depth;
-
-    const isLeafSpan = children.length === 0;
-    const spanRow = newSpanRow(spansToMerge, isLeafSpan);
-
-    addLayoutDetails(spanRow, timestamp, duration, depth, childIds);
-    // NOTE: This will increment both the local and remote service name
-    //
-    // TODO: We should only do this if it is a leaf span and a client or producer. If we are at the
-    // bottom of the tree, it can be helpful to count also against a remote uninstrumented service.
-    spanRow.serviceNames.forEach(serviceName => incrementEntry(serviceNameToCount, serviceName));
-
-    modelview.spans.push(spanRow);
-  }
-
-  modelview.serviceNameAndSpanCounts = Object.keys(serviceNameToCount).sort().map(serviceName =>
-    ({serviceName, spanCount: serviceNameToCount[serviceName]})
-  );
-
-  // the zoom feature needs backups and timeMarkers regardless of if there is a trace duration
-  modelview.spansBackup = modelview.spans;
-  modelview.timeMarkers = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
-    .map((p, index) => ({index, time: mkDurationStr(duration * p)}));
-  modelview.timeMarkersBackup = modelview.timeMarkers;
-
-  if (duration) modelview.durationStr = mkDurationStr(duration);
-  if (logsUrl) modelview.logsUrl = logsUrl;
-
-  return modelview;
-}
diff --git a/zipkin-ui/js/component_ui/traces.js b/zipkin-ui/js/component_ui/traces.js
deleted file mode 100644
index 0e3f043..0000000
--- a/zipkin-ui/js/component_ui/traces.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import $ from 'jquery';
-
-export default component(function traces() {
-  this.$traces = [];
-
-  this.sortFunctions = {
-    'service-percentage-desc': (a, b) => b.percentage - a.percentage,
-    'service-percentage-asc': (a, b) => a.percentage - b.percentage,
-    'duration-desc': (a, b) => b.duration - a.duration,
-    'duration-asc': (a, b) => a.duration - b.duration,
-    'timestamp-desc': (a, b) => b.timestamp - a.timestamp,
-    'timestamp-asc': (a, b) => a.timestamp - b.timestamp
-  };
-
-  this.updateSortOrder = function(ev, data) {
-    if (this.sortFunctions.hasOwnProperty(data.order)) {
-      this.$traces.sort(this.sortFunctions[data.order]);
-      this.$node.html(this.$traces);
-    }
-  };
-
-  this.after('initialize', function() {
-    this.$traces = this.$node.find('.trace');
-    this.$traces.each(function() {
-      const $this = $(this);
-      this.duration = parseInt($this.data('duration'), 10);
-      this.timestamp = parseInt($this.data('timestamp'), 10);
-      this.percentage = parseInt($this.data('servicePercentage'), 10);
-    });
-    this.on(document, 'uiUpdateTraceSortOrder', this.updateSortOrder);
-    const sortOrderSelect = $('.sort-order');
-    this.updateSortOrder(null, {order: sortOrderSelect.val()});
-  });
-});
diff --git a/zipkin-ui/js/component_ui/uploadTrace.js b/zipkin-ui/js/component_ui/uploadTrace.js
deleted file mode 100644
index 3edf722..0000000
--- a/zipkin-ui/js/component_ui/uploadTrace.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import FullPageSpinnerUI from '../component_ui/fullPageSpinner';
-import {traceToMustache} from '../component_ui/traceToMustache';
-import {treeCorrectedForClockSkew} from '../component_data/skew';
-
-export function ensureV2(trace) {
-  if (!Array.isArray(trace) || trace.length === 0) {
-    throw new Error('input is not a list');
-  }
-  const first = trace[0];
-  if (!first.traceId || !first.id) {
-    throw new Error('List<Span> implies at least traceId and id fields');
-  }
-  if (first.binaryAnnotations || (!first.localEndpoint && !first.remoteEndpoint && !first.tags)) {
-    throw new Error(
-      'v1 format is not supported. For help, contact https://gitter.im/openzipkin/zipkin');
-  }
-}
-
-export default component(function uploadTrace() {
-  this.doUpload = function() {
-    const files = this.node.files;
-    if (files.length === 0) {
-      return;
-    }
-
-    const reader = new FileReader();
-    reader.onload = evt => {
-      let model;
-      try {
-        const raw = JSON.parse(evt.target.result);
-        ensureV2(raw);
-        const corrected = treeCorrectedForClockSkew(raw);
-        const modelview = traceToMustache(corrected);
-        model = {modelview, trace: raw};
-      } catch (e) {
-        this.trigger('uiServerError',
-              {desc: 'Cannot parse file', message: e});
-        throw e;
-      } finally {
-        this.trigger(document, 'uiHideFullPageSpinner');
-      }
-
-      this.trigger(document, 'traceViewerPageModelView', model);
-    };
-
-    reader.onerror = evt => {
-      this.trigger(document, 'uiHideFullPageSpinner');
-      this.trigger('uiServerError',
-            {desc: 'Cannot load file', message: `${evt.target.error.name}`});
-    };
-
-    this.trigger(document, 'uiShowFullPageSpinner');
-    setTimeout(() => reader.readAsText(files[0]), 0);
-  };
-
-  this.after('initialize', function() {
-    this.on('change', this.doUpload);
-    FullPageSpinnerUI.teardownAll();
-    FullPageSpinnerUI.attachTo('#fullPageSpinner');
-  });
-});
diff --git a/zipkin-ui/js/component_ui/zoomOutSpans.js b/zipkin-ui/js/component_ui/zoomOutSpans.js
deleted file mode 100644
index a71c525..0000000
--- a/zipkin-ui/js/component_ui/zoomOutSpans.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-
-export default component(function zoomOutSpans() {
-  this.zoomOut = function() {
-    this.trigger('uiZoomOutSpans');
-  };
-
-  this.after('initialize', function() {
-    this.on('click', this.zoomOut);
-  });
-});
diff --git a/zipkin-ui/js/config.js b/zipkin-ui/js/config.js
deleted file mode 100644
index 6d1e8cf..0000000
--- a/zipkin-ui/js/config.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import $ from 'jquery';
-
-const defaults = {
-  environment: '',
-  suggestLens: false,
-  queryLimit: 10,
-  defaultLookback: 60 * 60 * 1000, // 1 hour
-  searchEnabled: true,
-  dependency: {
-    lowErrorRate: 0.5, // 50% of calls in error turns line yellow
-    highErrorRate: 0.75 // 75% of calls in error turns line red
-  }
-};
-
-export default function loadConfig() {
-  return $.ajax('config.json', {
-    type: 'GET',
-    dataType: 'json'
-  }).then(data => function config(key) {
-    if (data[key] === false) {
-      return false;
-    }
-
-    return data[key] || defaults[key];
-  });
-}
diff --git a/zipkin-ui/js/main.js b/zipkin-ui/js/main.js
deleted file mode 100644
index eacbb0b..0000000
--- a/zipkin-ui/js/main.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// The import of 'publicPath' module has to be the first statement in this entry point file
-// so that '__webpack_public_path__' (see https://webpack.github.io/docs/configuration.html#output-publicpath)
-// is set soon enough.
-// In the same time, 'contextRoot' is made available as the context root path reference.
-import {contextRoot} from './publicPath';
-
-import {compose, registry, advice, debug} from 'flightjs';
-import crossroads from 'crossroads';
-import initializeDefault from './page/default';
-import initializeTrace from './page/trace';
-import initializeTraceViewer from './page/traceViewer';
-import initializeDependency from './page/dependency';
-import CommonUI from './page/common';
-import loadConfig from './config';
-import {errToStr} from './component_ui/error';
-
-loadConfig().then(config => {
-  debug.enable(true);
-  compose.mixin(registry, [advice.withAdvice]);
-
-  CommonUI.attachTo(window.document.body, {config});
-
-  crossroads.addRoute(contextRoot, () => initializeDefault(config));
-  crossroads.addRoute(`${contextRoot}traces/{id}`, traceId => initializeTrace(traceId, config));
-  crossroads.addRoute(`${contextRoot}traceViewer`, () => initializeTraceViewer(config));
-  crossroads.addRoute(`${contextRoot}dependency`, () => initializeDependency(config));
-  crossroads.parse(window.location.pathname);
-}, e => {
-  // TODO: better error message, but this is better than a blank screen...
-  const err = errToStr(e);
-  document.write(`Error loading config.json: ${err}`);
-});
diff --git a/zipkin-ui/js/page/common.js b/zipkin-ui/js/page/common.js
deleted file mode 100644
index e377a59..0000000
--- a/zipkin-ui/js/page/common.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import EnvironmentUI from '../component_ui/environment';
-import ErrorUI from '../component_ui/error';
-import NavbarUI from '../component_ui/navbar';
-import {layoutTemplate} from '../templates';
-import GoToTraceUI from '../component_ui/goToTrace';
-import GoToLensUI from '../component_ui/goToLens';
-import {contextRoot} from '../publicPath';
-
-export default component(function CommonUI() {
-  this.after('initialize', function() {
-    const suggestLens = this.attr.config && this.attr.config('suggestLens');
-    this.$node.html(layoutTemplate({contextRoot, suggestLens}));
-    NavbarUI.attachTo('#navbar');
-    ErrorUI.attachTo('#errorPanel');
-    EnvironmentUI.attachTo('#environment', {config: this.attr.config});
-    GoToTraceUI.attachTo('#traceIdQueryForm');
-    GoToLensUI.attachTo('#lensForm');
-  });
-});
diff --git a/zipkin-ui/js/page/default.js b/zipkin-ui/js/page/default.js
deleted file mode 100644
index 08620c2..0000000
--- a/zipkin-ui/js/page/default.js
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import $ from 'jquery';
-import timeago from 'timeago'; // eslint-disable-line no-unused-vars
-import queryString from 'query-string';
-import DefaultData from '../component_data/default';
-import RemoteServiceNamesData from '../component_data/remoteServiceNames';
-import SpanNamesData from '../component_data/spanNames';
-import ServiceNamesData from '../component_data/serviceNames';
-import ServiceNameUI from '../component_ui/serviceName';
-import RemoteServiceNameUI from '../component_ui/remoteServiceName';
-import SpanNameUI from '../component_ui/spanName';
-import LookbackUI from '../component_ui/lookback';
-import InfoPanelUI from '../component_ui/infoPanel';
-import InfoButtonUI from '../component_ui/infoButton';
-import JsonPanelUI from '../component_ui/jsonPanel';
-import TraceFiltersUI from '../component_ui/traceFilters';
-import TracesUI from '../component_ui/traces';
-import TimeStampUI from '../component_ui/timeStamp';
-import BackToTop from '../component_ui/backToTop';
-import {defaultTemplate} from '../templates';
-import {searchDisabled} from '../templates';
-import {contextRoot} from '../publicPath';
-import {i18nInit} from '../component_ui/i18n';
-import bootstrap // eslint-disable-line no-unused-vars
-    from 'bootstrap/dist/js/bootstrap.bundle.min.js';
-
-const DefaultPageComponent = component(function DefaultPage() {
-  const sortOptions = [
-    {value: 'service-percentage-desc', text: 'Service Percentage: Longest First'},
-    {value: 'service-percentage-asc', text: 'Service Percentage: Shortest First'},
-    {value: 'duration-desc', text: 'Longest First'},
-    {value: 'duration-asc', text: 'Shortest First'},
-    {value: 'timestamp-desc', text: 'Newest First'},
-    {value: 'timestamp-asc', text: 'Oldest First'}
-  ];
-
-  const sortSelected = function getSelector(selectedSortValue) {
-    return function selected() {
-      if (this.value === selectedSortValue) {
-        return 'selected';
-      }
-      return '';
-    };
-  };
-
-  this.after('initialize', function() {
-    window.document.title = 'Zipkin - Index';
-    if (!this.attr.config('searchEnabled')) {
-      this.$node.html(searchDisabled());
-      return;
-    }
-
-    this.trigger(document, 'navigate', {route: 'zipkin/index'});
-
-    const query = queryString.parse(window.location.search);
-
-    this.on(document, 'defaultPageModelView', function(ev, modelView) {
-      const limit = query.limit || this.attr.config('queryLimit');
-      const minDuration = query.minDuration;
-      const endTs = query.endTs || new Date().getTime();
-      const startTs = query.startTs || (endTs - this.attr.config('defaultLookback'));
-      const serviceName = query.serviceName || '';
-      const annotationQuery = query.annotationQuery || '';
-      const sortOrder = query.sortOrder || 'duration-desc';
-      const queryWasPerformed = serviceName && serviceName.length > 0;
-      this.$node.html(defaultTemplate({
-        limit,
-        minDuration,
-        startTs,
-        endTs,
-        serviceName,
-        annotationQuery,
-        queryWasPerformed,
-        contextRoot,
-        traceCount: modelView.traces.length,
-        sortOrderOptions: sortOptions,
-        sortOrderSelected: sortSelected(sortOrder),
-        apiURL: modelView.apiURL,
-        ...modelView
-      }));
-
-      RemoteServiceNamesData.attachTo(document);
-      SpanNamesData.attachTo(document);
-      ServiceNamesData.attachTo(document);
-      ServiceNameUI.attachTo('#serviceName');
-      RemoteServiceNameUI.attachTo('#remoteServiceName');
-      SpanNameUI.attachTo('#spanName');
-      LookbackUI.attachTo('#lookback');
-      InfoPanelUI.attachTo('#infoPanel');
-      InfoButtonUI.attachTo('button.info-request');
-      JsonPanelUI.attachTo('#jsonPanel');
-      TraceFiltersUI.attachTo('#trace-filters');
-      TracesUI.attachTo('#traces');
-      TimeStampUI.attachTo('#end-ts');
-      TimeStampUI.attachTo('#start-ts');
-      BackToTop.attachTo('#backToTop');
-      i18nInit('traces');
-      $('.timeago').timeago();
-      // Need to initialize the datepicker when the UI refershes. Can be optimized
-      this.$date = this.$node.find('.date-input');
-      this.$date.datepicker({format: 'yyyy-mm-dd'});
-      this.$node.find('#rawResultsJsonLink').click(e => {
-        e.preventDefault();
-        this.trigger('uiRequestJsonPanel', {
-          title: 'Search Results',
-          obj: modelView.rawResponse,
-          link: modelView.apiURL
-        });
-      });
-    });
-    DefaultData.attachTo(document);
-  });
-});
-
-export default function initializeDefault(config) {
-  DefaultPageComponent.attachTo('.content', {config});
-}
diff --git a/zipkin-ui/js/page/dependency.js b/zipkin-ui/js/page/dependency.js
deleted file mode 100644
index 0994587..0000000
--- a/zipkin-ui/js/page/dependency.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import moment from 'moment';
-import {component} from 'flightjs';
-import $ from 'jquery';
-import queryString from 'query-string';
-import DependencyData from '../component_data/dependency';
-import DependencyGraphUI from '../component_ui/dependencyGraph';
-import ServiceDataModal from '../component_ui/serviceDataModal';
-import TimeStampUI from '../component_ui/timeStamp';
-import GoToDependencyUI from '../component_ui/goToDependency';
-import {dependenciesTemplate} from '../templates';
-import {i18nInit} from '../component_ui/i18n';
-
-const DependencyPageComponent = component(function DependencyPage() {
-  this.after('initialize', function() {
-    window.document.title = 'Zipkin - Dependency';
-    this.trigger(document, 'navigate', {route: 'zipkin/dependency'});
-
-    this.$node.html(dependenciesTemplate());
-
-    const {startTs, endTs} = queryString.parse(location.search);
-    $('#endTs').val(endTs || moment().valueOf());
-    // When #1185 is complete, the only visible granularity is day
-    $('#startTs').val(startTs || moment().valueOf() - 86400000);
-
-    DependencyData.attachTo('#dependency-container');
-    DependencyGraphUI.attachTo('#dependency-container', {config: this.attr.config});
-    ServiceDataModal.attachTo('#service-data-modal-container');
-    TimeStampUI.attachTo('#end-ts');
-    TimeStampUI.attachTo('#start-ts');
-    GoToDependencyUI.attachTo('#dependency-query-form');
-    i18nInit('dep');
-  });
-});
-
-export default function initializeDependencies(config) {
-  DependencyPageComponent.attachTo('.content', {config});
-}
diff --git a/zipkin-ui/js/page/trace.js b/zipkin-ui/js/page/trace.js
deleted file mode 100644
index 95cbef5..0000000
--- a/zipkin-ui/js/page/trace.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import $ from 'jquery';
-import TraceData from '../component_data/trace';
-import FilterAllServicesUI from '../component_ui/filterAllServices';
-import FullPageSpinnerUI from '../component_ui/fullPageSpinner';
-import JsonPanelUI from '../component_ui/jsonPanel';
-import SpanPanelUI from '../component_ui/spanPanel';
-import TraceUI from '../component_ui/trace';
-import ZoomOut from '../component_ui/zoomOutSpans';
-import {traceTemplate} from '../templates';
-import {contextRoot} from '../publicPath';
-
-const TracePageComponent = component(function TracePage() {
-  this.after('initialize', function() {
-    window.document.title = 'Zipkin - Traces';
-    $('body').tooltip({
-      selector: '[data-toggle="tooltip"]'
-    });
-    TraceData.attachTo(document, {
-      traceId: this.attr.traceId,
-      logsUrl: this.attr.config('logsUrl')
-    });
-    this.on(document, 'tracePageModelView', function(ev, data) {
-      this.$node.html(traceTemplate({
-        contextRoot,
-        ...data.modelview
-      }));
-
-      FilterAllServicesUI.attachTo('#filterAllServices', {
-        totalServices: $('.trace-details.services span').length
-      });
-      FullPageSpinnerUI.attachTo('#fullPageSpinner');
-      JsonPanelUI.attachTo('#jsonPanel');
-      SpanPanelUI.attachTo('#spanPanel');
-      TraceUI.attachTo('#trace-container');
-      ZoomOut.attachTo('#zoomOutSpans');
-
-      this.$node.find('#traceJsonLink').click(e => {
-        e.preventDefault();
-        this.trigger('uiRequestJsonPanel', {
-          title: `Trace ${this.attr.traceId}`,
-          obj: data.trace,
-          link: `${contextRoot}api/v2/trace/${this.attr.traceId}`
-        });
-      });
-
-      $('.annotation:not(.derived)').tooltip({placement: 'left'});
-    });
-  });
-});
-
-export default function initializeTrace(traceId, config) {
-  TracePageComponent.attachTo('.content', {
-    traceId,
-    config
-  });
-}
diff --git a/zipkin-ui/js/page/traceViewer.js b/zipkin-ui/js/page/traceViewer.js
deleted file mode 100644
index 311a071..0000000
--- a/zipkin-ui/js/page/traceViewer.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {component} from 'flightjs';
-import $ from 'jquery';
-import FilterAllServicesUI from '../component_ui/filterAllServices';
-import JsonPanelUI from '../component_ui/jsonPanel';
-import SpanPanelUI from '../component_ui/spanPanel';
-import TraceUI from '../component_ui/trace';
-import ZoomOut from '../component_ui/zoomOutSpans';
-import UploadTraceUI from '../component_ui/uploadTrace';
-import {traceViewerTemplate} from '../templates';
-import {contextRoot} from '../publicPath';
-
-const TraceViewerPageComponent = component(function TraceViewerPage() {
-  this.render = function(model) {
-    try {
-      this.$node.html(traceViewerTemplate({
-        contextRoot,
-        ...model
-      }));
-    } catch (e) {
-      this.trigger('uiServerError',
-                {desc: 'Failed to render template', message: e.message});
-    }
-
-    UploadTraceUI.attachTo('#traceFile');
-  };
-
-  this.attach = function() {
-    FilterAllServicesUI.attachTo('#filterAllServices', {
-      totalServices: $('.trace-details.services span').length
-    });
-    JsonPanelUI.attachTo('#jsonPanel');
-    SpanPanelUI.attachTo('#spanPanel');
-    TraceUI.attachTo('#trace-container');
-    ZoomOut.attachTo('#zoomOutSpans');
-  };
-
-  this.teardown = function() {
-    ZoomOut.teardownAll();
-    TraceUI.teardownAll();
-    SpanPanelUI.teardownAll();
-    JsonPanelUI.teardownAll();
-    FilterAllServicesUI.teardownAll();
-  };
-
-  this.after('initialize', function() {
-    window.document.title = 'Zipkin - Trace Viewer';
-
-    this.render({});
-
-    this.on(document, 'traceViewerPageModelView', function(ev, data) {
-      this.teardown();
-      this.render(data.modelview);
-      this.attach();
-
-      this.$node.find('#traceJsonLink').click(e => {
-        e.preventDefault();
-        this.trigger('uiRequestJsonPanel', {title: `Trace ${data.modelview.traceId}`,
-                                            obj: data.trace,
-                                            link: `${contextRoot}traceViewer`});
-      });
-
-      $('.annotation:not(.core)').tooltip({placement: 'left'});
-    });
-  });
-});
-
-export default function initializeTrace(config) {
-  TraceViewerPageComponent.attachTo('.content', {config});
-}
diff --git a/zipkin-ui/js/publicPath.js b/zipkin-ui/js/publicPath.js
deleted file mode 100644
index ca597d0..0000000
--- a/zipkin-ui/js/publicPath.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import $ from 'jquery';
-
-// read the public path from the <base> tag where it has to be set anyway because of
-// html-webpack-plugin limitations: https://github.com/jantimon/html-webpack-plugin/issues/119
-// otherwise it could be: window.location.pathname.replace(/(.*)\/zipkin\/.*/, '$1/zipkin/')
-let contextRoot = $('base').attr('href') || '/zipkin/';
-
-// explicit to avoid having to do a polyfill for String.endsWith
-if (contextRoot.substr(contextRoot.length - 1) !== '/') {
-  contextRoot += '/';
-}
-
-// set dynamically 'output.publicPath' as per https://webpack.github.io/docs/configuration.html#output-publicpath
-__webpack_public_path__ = contextRoot; // eslint-disable-line camelcase, no-undef
-
-export {contextRoot};
diff --git a/zipkin-ui/js/templates.js b/zipkin-ui/js/templates.js
deleted file mode 100644
index eec4424..0000000
--- a/zipkin-ui/js/templates.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-export const defaultTemplate = require('../templates/index.mustache');
-export const searchDisabled = require('../templates/searchDisabled.mustache');
-export const layoutTemplate = require('../templates/layout.mustache');
-export const dependenciesTemplate = require('../templates/dependency.mustache');
-export const traceTemplate = require('../templates/trace.mustache');
-export const traceViewerTemplate = require('../templates/traceViewer.mustache');
diff --git a/zipkin-ui/karma.conf.js b/zipkin-ui/karma.conf.js
deleted file mode 100644
index 4623e78..0000000
--- a/zipkin-ui/karma.conf.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-var webpack = require('webpack');
-
-module.exports = function(config) {
-  config.set({
-    frameworks: ['mocha', 'chai'],
-    files: [
-      'node_modules/babel-polyfill/dist/polyfill.js',
-      'test/*test.js',
-      'test/**/*test.js'
-    ],
-
-    preprocessors: {
-      '*test.js': ['babel', 'webpack', 'sourcemap'],
-      '**/*test.js': ['babel', 'webpack', 'sourcemap']
-    },
-
-    client: {
-      captureConsole: true
-    },
-
-    browsers: ['FirefoxHeadless'],
-
-    // see https://github.com/karma-runner/karma-firefox-launcher/issues/76
-    customLaunchers: {
-      FirefoxHeadless: {
-        base: 'Firefox',
-        flags: [ '-headless' ],
-      },
-    },
-
-    webpack: {
-      devtool: 'inline-source-map',
-      module: {
-        rules: [{
-          test: /\.js$/,
-          exclude: /node_modules/,
-          use: 'babel-loader'
-        }, {
-          test: /\.mustache$/,
-          use: 'mustache-loader'
-        }]
-      },
-      resolve: {
-        modules: ['node_modules']
-      },
-      plugins: [
-        new webpack.ProvidePlugin({
-          $: "jquery",
-          jQuery: "jquery"
-        })
-      ]
-    },
-
-    webpackServer: {
-      noInfo: true
-    },
-
-    plugins: [
-      require('karma-babel-preprocessor'),
-      require('karma-webpack'),
-      require('karma-mocha'),
-      require('karma-chai'),
-      require('karma-chrome-launcher'),
-      require('karma-firefox-launcher'),
-      require('karma-sourcemap-loader')
-    ]
-  });
-};
diff --git a/zipkin-ui/libs/chosen/chosen-sprite.png b/zipkin-ui/libs/chosen/chosen-sprite.png
deleted file mode 100644
index c57da70..0000000
Binary files a/zipkin-ui/libs/chosen/chosen-sprite.png and /dev/null differ
diff --git a/zipkin-ui/libs/chosen/chosen-sprite@2x.png b/zipkin-ui/libs/chosen/chosen-sprite@2x.png
deleted file mode 100644
index 6b50545..0000000
Binary files a/zipkin-ui/libs/chosen/chosen-sprite@2x.png and /dev/null differ
diff --git a/zipkin-ui/libs/chosen/chosen.css b/zipkin-ui/libs/chosen/chosen.css
deleted file mode 100644
index 4599aaa..0000000
--- a/zipkin-ui/libs/chosen/chosen.css
+++ /dev/null
@@ -1,450 +0,0 @@
-/*!
-Chosen, a Select Box Enhancer for jQuery and Prototype
-by Patrick Filler for Harvest, http://getharvest.com
-
-Version 1.4.2
-Full source at https://github.com/harvesthq/chosen
-Copyright (c) 2011-2015 Harvest http://getharvest.com
-
-MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
-This file is generated by `grunt build`, do not edit it by hand.
-*/
-
-/* @group Base */
-.chosen-container {
-    position: relative;
-    display: inline-block;
-    vertical-align: middle;
-    font-size: 13px;
-    zoom: 1;
-    *display: inline;
-    -webkit-user-select: none;
-    -moz-user-select: none;
-    user-select: none;
-}
-.chosen-container * {
-    -webkit-box-sizing: border-box;
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-}
-.chosen-container .chosen-drop {
-    position: absolute;
-    top: 100%;
-    left: -9999px;
-    z-index: 1010;
-    width: 100%;
-    border: 1px solid #aaa;
-    border-top: 0;
-    background: #fff;
-    box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15);
-}
-.chosen-container.chosen-with-drop .chosen-drop {
-    left: 0;
-}
-.chosen-container a {
-    cursor: pointer;
-}
-.chosen-container .search-choice .group-name, .chosen-container .chosen-single .group-name {
-    margin-right: 4px;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
-    font-weight: normal;
-    color: #999999;
-}
-.chosen-container .search-choice .group-name:after, .chosen-container .chosen-single .group-name:after {
-    content: ":";
-    padding-left: 2px;
-    vertical-align: top;
-}
-
-/* @end */
-/* @group Single Chosen */
-.chosen-container-single .chosen-single {
-    position: relative;
-    display: block;
-    overflow: hidden;
-    padding: 0 0 0 8px;
-    height: 25px;
-    border: 1px solid #aaa;
-    border-radius: 5px;
-    background-color: #fff;
-    background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4));
-    background: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
-    background: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
-    background: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
-    background: linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
-    background-clip: padding-box;
-    box-shadow: 0 0 3px white inset, 0 1px 1px rgba(0, 0, 0, 0.1);
-    color: #444;
-    text-decoration: none;
-    white-space: nowrap;
-    line-height: 24px;
-}
-.chosen-container-single .chosen-default {
-    color: #999;
-}
-.chosen-container-single .chosen-single span {
-    display: block;
-    overflow: hidden;
-    margin-right: 26px;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-}
-.chosen-container-single .chosen-single-with-deselect span {
-    margin-right: 38px;
-}
-.chosen-container-single .chosen-single abbr {
-    position: absolute;
-    top: 6px;
-    right: 26px;
-    display: block;
-    width: 12px;
-    height: 12px;
-    background: url('chosen-sprite.png') -42px 1px no-repeat;
-    font-size: 1px;
-}
-.chosen-container-single .chosen-single abbr:hover {
-    background-position: -42px -10px;
-}
-.chosen-container-single.chosen-disabled .chosen-single abbr:hover {
-    background-position: -42px -10px;
-}
-.chosen-container-single .chosen-single div {
-    position: absolute;
-    top: 0;
-    right: 0;
-    display: block;
-    width: 18px;
-    height: 100%;
-}
-.chosen-container-single .chosen-single div b {
-    display: block;
-    width: 100%;
-    height: 100%;
-    background: url('chosen-sprite.png') no-repeat 0px 2px;
-}
-.chosen-container-single .chosen-search {
-    position: relative;
-    z-index: 1010;
-    margin: 0;
-    padding: 3px 4px;
-    white-space: nowrap;
-}
-.chosen-container-single .chosen-search input[type="text"] {
-    margin: 1px 0;
-    padding: 4px 20px 4px 5px;
-    width: 100%;
-    height: auto;
-    outline: 0;
-    border: 1px solid #aaa;
-    background: white url('chosen-sprite.png') no-repeat 100% -20px;
-    background: url('chosen-sprite.png') no-repeat 100% -20px;
-    font-size: 1em;
-    font-family: sans-serif;
-    line-height: normal;
-    border-radius: 0;
-}
-.chosen-container-single .chosen-drop {
-    margin-top: -1px;
-    border-radius: 0 0 4px 4px;
-    background-clip: padding-box;
-}
-.chosen-container-single.chosen-container-single-nosearch .chosen-search {
-    position: absolute;
-    left: -9999px;
-}
-
-/* @end */
-/* @group Results */
-.chosen-container .chosen-results {
-    color: #444;
-    position: relative;
-    overflow-x: hidden;
-    overflow-y: auto;
-    margin: 0 4px 4px 0;
-    padding: 0 0 0 4px;
-    max-height: 240px;
-    -webkit-overflow-scrolling: touch;
-}
-.chosen-container .chosen-results li {
-    display: none;
-    margin: 0;
-    padding: 5px 6px;
-    list-style: none;
-    line-height: 15px;
-    word-wrap: break-word;
-    -webkit-touch-callout: none;
-}
-.chosen-container .chosen-results li.active-result {
-    display: list-item;
-    cursor: pointer;
-}
-.chosen-container .chosen-results li.disabled-result {
-    display: list-item;
-    color: #ccc;
-    cursor: default;
-}
-.chosen-container .chosen-results li.highlighted {
-    background-color: #3875d7;
-    background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc));
-    background-image: -webkit-linear-gradient(#3875d7 20%, #2a62bc 90%);
-    background-image: -moz-linear-gradient(#3875d7 20%, #2a62bc 90%);
-    background-image: -o-linear-gradient(#3875d7 20%, #2a62bc 90%);
-    background-image: linear-gradient(#3875d7 20%, #2a62bc 90%);
-    color: #fff;
-}
-.chosen-container .chosen-results li.no-results {
-    color: #777;
-    display: list-item;
-    background: #f4f4f4;
-}
-.chosen-container .chosen-results li.group-result {
-    display: list-item;
-    font-weight: bold;
-    cursor: default;
-}
-.chosen-container .chosen-results li.group-option {
-    padding-left: 15px;
-}
-.chosen-container .chosen-results li em {
-    font-style: normal;
-    text-decoration: underline;
-}
-
-/* @end */
-/* @group Multi Chosen */
-.chosen-container-multi .chosen-choices {
-    position: relative;
-    overflow: hidden;
-    margin: 0;
-    padding: 0 5px;
-    width: 100%;
-    height: auto !important;
-    height: 1%;
-    border: 1px solid #aaa;
-    background-color: #fff;
-    background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
-    background-image: -webkit-linear-gradient(#eeeeee 1%, #ffffff 15%);
-    background-image: -moz-linear-gradient(#eeeeee 1%, #ffffff 15%);
-    background-image: -o-linear-gradient(#eeeeee 1%, #ffffff 15%);
-    background-image: linear-gradient(#eeeeee 1%, #ffffff 15%);
-    cursor: text;
-}
-.chosen-container-multi .chosen-choices li {
-    float: left;
-    list-style: none;
-}
-.chosen-container-multi .chosen-choices li.search-field {
-    margin: 0;
-    padding: 0;
-    white-space: nowrap;
-}
-.chosen-container-multi .chosen-choices li.search-field input[type="text"] {
-    margin: 1px 0;
-    padding: 0;
-    height: 25px;
-    outline: 0;
-    border: 0 !important;
-    background: transparent !important;
-    box-shadow: none;
-    color: #999;
-    font-size: 100%;
-    font-family: sans-serif;
-    line-height: normal;
-    border-radius: 0;
-}
-.chosen-container-multi .chosen-choices li.search-choice {
-    position: relative;
-    margin: 3px 5px 3px 0;
-    padding: 3px 20px 3px 5px;
-    border: 1px solid #aaa;
-    max-width: 100%;
-    border-radius: 3px;
-    background-color: #eeeeee;
-    background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
-    background-image: -webkit-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
-    background-image: -moz-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
-    background-image: -o-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
-    background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
-    background-size: 100% 19px;
-    background-repeat: repeat-x;
-    background-clip: padding-box;
-    box-shadow: 0 0 2px white inset, 0 1px 0 rgba(0, 0, 0, 0.05);
-    color: #333;
-    line-height: 13px;
-    cursor: default;
-}
-.chosen-container-multi .chosen-choices li.search-choice span {
-    word-wrap: break-word;
-}
-.chosen-container-multi .chosen-choices li.search-choice .search-choice-close {
-    position: absolute;
-    top: 4px;
-    right: 3px;
-    display: block;
-    width: 12px;
-    height: 12px;
-    background: url('chosen-sprite.png') -42px 1px no-repeat;
-    font-size: 1px;
-}
-.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover {
-    background-position: -42px -10px;
-}
-.chosen-container-multi .chosen-choices li.search-choice-disabled {
-    padding-right: 5px;
-    border: 1px solid #ccc;
-    background-color: #e4e4e4;
-    background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
-    background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
-    background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
-    background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
-    background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
-    color: #666;
-}
-.chosen-container-multi .chosen-choices li.search-choice-focus {
-    background: #d4d4d4;
-}
-.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close {
-    background-position: -42px -10px;
-}
-.chosen-container-multi .chosen-results {
-    margin: 0;
-    padding: 0;
-}
-.chosen-container-multi .chosen-drop .result-selected {
-    display: list-item;
-    color: #ccc;
-    cursor: default;
-}
-
-/* @end */
-/* @group Active  */
-.chosen-container-active .chosen-single {
-    border: 1px solid #5897fb;
-    box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
-}
-.chosen-container-active.chosen-with-drop .chosen-single {
-    border: 1px solid #aaa;
-    -moz-border-radius-bottomright: 0;
-    border-bottom-right-radius: 0;
-    -moz-border-radius-bottomleft: 0;
-    border-bottom-left-radius: 0;
-    background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #eeeeee), color-stop(80%, #ffffff));
-    background-image: -webkit-linear-gradient(#eeeeee 20%, #ffffff 80%);
-    background-image: -moz-linear-gradient(#eeeeee 20%, #ffffff 80%);
-    background-image: -o-linear-gradient(#eeeeee 20%, #ffffff 80%);
-    background-image: linear-gradient(#eeeeee 20%, #ffffff 80%);
-    box-shadow: 0 1px 0 #fff inset;
-}
-.chosen-container-active.chosen-with-drop .chosen-single div {
-    border-left: none;
-    background: transparent;
-}
-.chosen-container-active.chosen-with-drop .chosen-single div b {
-    background-position: -18px 2px;
-}
-.chosen-container-active .chosen-choices {
-    border: 1px solid #5897fb;
-    box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
-}
-.chosen-container-active .chosen-choices li.search-field input[type="text"] {
-    color: #222 !important;
-}
-
-/* @end */
-/* @group Disabled Support */
-.chosen-disabled {
-    opacity: 0.5 !important;
-    cursor: default;
-}
-.chosen-disabled .chosen-single {
-    cursor: default;
-}
-.chosen-disabled .chosen-choices .search-choice .search-choice-close {
-    cursor: default;
-}
-
-/* @end */
-/* @group Right to Left */
-.chosen-rtl {
-    text-align: right;
-}
-.chosen-rtl .chosen-single {
-    overflow: visible;
-    padding: 0 8px 0 0;
-}
-.chosen-rtl .chosen-single span {
-    margin-right: 0;
-    margin-left: 26px;
-    direction: rtl;
-}
-.chosen-rtl .chosen-single-with-deselect span {
-    margin-left: 38px;
-}
-.chosen-rtl .chosen-single div {
-    right: auto;
-    left: 3px;
-}
-.chosen-rtl .chosen-single abbr {
-    right: auto;
-    left: 26px;
-}
-.chosen-rtl .chosen-choices li {
-    float: right;
-}
-.chosen-rtl .chosen-choices li.search-field input[type="text"] {
-    direction: rtl;
-}
-.chosen-rtl .chosen-choices li.search-choice {
-    margin: 3px 5px 3px 0;
-    padding: 3px 5px 3px 19px;
-}
-.chosen-rtl .chosen-choices li.search-choice .search-choice-close {
-    right: auto;
-    left: 4px;
-}
-.chosen-rtl.chosen-container-single-nosearch .chosen-search,
-.chosen-rtl .chosen-drop {
-    left: 9999px;
-}
-.chosen-rtl.chosen-container-single .chosen-results {
-    margin: 0 0 4px 4px;
-    padding: 0 4px 0 0;
-}
-.chosen-rtl .chosen-results li.group-option {
-    padding-right: 15px;
-    padding-left: 0;
-}
-.chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div {
-    border-right: none;
-}
-.chosen-rtl .chosen-search input[type="text"] {
-    padding: 4px 5px 4px 20px;
-    background: white url('chosen-sprite.png') no-repeat -30px -20px;
-    background: url('chosen-sprite.png') no-repeat -30px -20px;
-    direction: rtl;
-}
-.chosen-rtl.chosen-container-single .chosen-single div b {
-    background-position: 6px 2px;
-}
-.chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b {
-    background-position: -12px 2px;
-}
-
-/* @end */
-/* @group Retina compatibility */
-@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi), only screen and (min-resolution: 1.5dppx) {
-    .chosen-rtl .chosen-search input[type="text"],
-    .chosen-container-single .chosen-single abbr,
-    .chosen-container-single .chosen-single div b,
-    .chosen-container-single .chosen-search input[type="text"],
-    .chosen-container-multi .chosen-choices .search-choice .search-choice-close,
-    .chosen-container .chosen-results-scroll-down span,
-    .chosen-container .chosen-results-scroll-up span {
-        background-image: url('chosen-sprite@2x.png') !important;
-        background-size: 52px 37px !important;
-        background-repeat: no-repeat !important;
-    }
-}
-/* @end */
diff --git a/zipkin-ui/libs/dagre-d3/.bower.json b/zipkin-ui/libs/dagre-d3/.bower.json
deleted file mode 100644
index 2ba09f8..0000000
--- a/zipkin-ui/libs/dagre-d3/.bower.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
-  "name": "dagre-d3",
-  "version": "0.2.9",
-  "main": [
-    "js/dagre-d3.js",
-    "js/dagre-d3.min.js"
-  ],
-  "ignore": [
-    "README.md"
-  ],
-  "dependencies": {
-    "d3": "~3.3.8"
-  },
-  "homepage": "https://github.com/cpettitt/dagre-d3-bower",
-  "_release": "0.2.9",
-  "_resolution": {
-    "type": "version",
-    "tag": "v0.2.9",
-    "commit": "536bd5b212e4dd1f2d63a20b4daecb2987a0568f"
-  },
-  "_source": "git://github.com/cpettitt/dagre-d3-bower.git",
-  "_target": "~0.2.9",
-  "_originalSource": "dagre-d3",
-  "_direct": true
-}
\ No newline at end of file
diff --git a/zipkin-ui/libs/dagre-d3/bower.json b/zipkin-ui/libs/dagre-d3/bower.json
deleted file mode 100644
index 27507b0..0000000
--- a/zipkin-ui/libs/dagre-d3/bower.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "name": "dagre-d3",
-  "version": "0.2.9",
-  "main": [
-    "js/dagre-d3.js",
-    "js/dagre-d3.min.js"
-  ],
-  "ignore": [
-    "README.md"
-  ],
-  "dependencies": {
-    "d3": "~3.3.8"
-  }
-}
diff --git a/zipkin-ui/libs/dagre-d3/js/dagre-d3.js b/zipkin-ui/libs/dagre-d3/js/dagre-d3.js
deleted file mode 100644
index 8728227..0000000
--- a/zipkin-ui/libs/dagre-d3/js/dagre-d3.js
+++ /dev/null
@@ -1,5003 +0,0 @@
-;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-var global=self;/**
- * @license
- * Copyright (c) 2012-2013 Chris Pettitt
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-global.dagreD3 = require('./index');
-
-},{"./index":2}],2:[function(require,module,exports){
-/**
- * @license
- * Copyright (c) 2012-2013 Chris Pettitt
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-module.exports =  {
-  Digraph: require('graphlib').Digraph,
-  Graph: require('graphlib').Graph,
-  Renderer: require('./lib/Renderer'),
-  json: require('graphlib').converter.json,
-  layout: require('dagre').layout,
-  version: require('./lib/version'),
-  debug: require('dagre').debug
-};
-
-},{"./lib/Renderer":3,"./lib/version":4,"dagre":11,"graphlib":29}],3:[function(require,module,exports){
-var layout = require('dagre').layout;
-
-var d3;
-try { d3 = require('d3'); } catch (_) { d3 = window.d3; }
-
-module.exports = Renderer;
-
-function Renderer() {
-  // Set up defaults...
-  this._layout = layout();
-
-  this.drawNodes(defaultDrawNodes);
-  this.drawEdgeLabels(defaultDrawEdgeLabels);
-  this.drawEdgePaths(defaultDrawEdgePaths);
-  this.positionNodes(defaultPositionNodes);
-  this.positionEdgeLabels(defaultPositionEdgeLabels);
-  this.positionEdgePaths(defaultPositionEdgePaths);
-  this.zoomSetup(defaultZoomSetup);
-  this.zoom(defaultZoom);
-  this.transition(defaultTransition);
-  this.postLayout(defaultPostLayout);
-  this.postRender(defaultPostRender);
-
-  this.edgeInterpolate('bundle');
-  this.edgeTension(0.95);
-}
-
-Renderer.prototype.layout = function(layout) {
-  if (!arguments.length) { return this._layout; }
-  this._layout = layout;
-  return this;
-};
-
-Renderer.prototype.drawNodes = function(drawNodes) {
-  if (!arguments.length) { return this._drawNodes; }
-  this._drawNodes = bind(drawNodes, this);
-  return this;
-};
-
-Renderer.prototype.drawEdgeLabels = function(drawEdgeLabels) {
-  if (!arguments.length) { return this._drawEdgeLabels; }
-  this._drawEdgeLabels = bind(drawEdgeLabels, this);
-  return this;
-};
-
-Renderer.prototype.drawEdgePaths = function(drawEdgePaths) {
-  if (!arguments.length) { return this._drawEdgePaths; }
-  this._drawEdgePaths = bind(drawEdgePaths, this);
-  return this;
-};
-
-Renderer.prototype.positionNodes = function(positionNodes) {
-  if (!arguments.length) { return this._positionNodes; }
-  this._positionNodes = bind(positionNodes, this);
-  return this;
-};
-
-Renderer.prototype.positionEdgeLabels = function(positionEdgeLabels) {
-  if (!arguments.length) { return this._positionEdgeLabels; }
-  this._positionEdgeLabels = bind(positionEdgeLabels, this);
-  return this;
-};
-
-Renderer.prototype.positionEdgePaths = function(positionEdgePaths) {
-  if (!arguments.length) { return this._positionEdgePaths; }
-  this._positionEdgePaths = bind(positionEdgePaths, this);
-  return this;
-};
-
-Renderer.prototype.transition = function(transition) {
-  if (!arguments.length) { return this._transition; }
-  this._transition = bind(transition, this);
-  return this;
-};
-
-Renderer.prototype.zoomSetup = function(zoomSetup) {
-  if (!arguments.length) { return this._zoomSetup; }
-  this._zoomSetup = bind(zoomSetup, this);
-  return this;
-};
-
-Renderer.prototype.zoom = function(zoom) {
-  if (!arguments.length) { return this._zoom; }
-  if (zoom) {
-    this._zoom = bind(zoom, this);
-  } else {
-    delete this._zoom;
-  }
-  return this;
-};
-
-Renderer.prototype.postLayout = function(postLayout) {
-  if (!arguments.length) { return this._postLayout; }
-  this._postLayout = bind(postLayout, this);
-  return this;
-};
-
-Renderer.prototype.postRender = function(postRender) {
-  if (!arguments.length) { return this._postRender; }
-  this._postRender = bind(postRender, this);
-  return this;
-};
-
-Renderer.prototype.edgeInterpolate = function(edgeInterpolate) {
-  if (!arguments.length) { return this._edgeInterpolate; }
-  this._edgeInterpolate = edgeInterpolate;
-  return this;
-};
-
-Renderer.prototype.edgeTension = function(edgeTension) {
-  if (!arguments.length) { return this._edgeTension; }
-  this._edgeTension = edgeTension;
-  return this;
-};
-
-Renderer.prototype.run = function(graph, orgSvg) {
-  // First copy the input graph so that it is not changed by the rendering
-  // process.
-  graph = copyAndInitGraph(graph);
-
-  // Create zoom elements
-  var svg = this._zoomSetup(graph, orgSvg);
-
-  // Create layers
-  svg
-    .selectAll('g.edgePaths, g.edgeLabels, g.nodes')
-    .data(['edgePaths', 'edgeLabels', 'nodes'])
-    .enter()
-      .append('g')
-      .attr('class', function(d) { return d; });
-
-  // Create node and edge roots, attach labels, and capture dimension
-  // information for use with layout.
-  var svgNodes = this._drawNodes(graph, svg.select('g.nodes'));
-  var svgEdgeLabels = this._drawEdgeLabels(graph, svg.select('g.edgeLabels'));
-
-  svgNodes.each(function(u) { calculateDimensions(this, graph.node(u)); });
-  svgEdgeLabels.each(function(e) { calculateDimensions(this, graph.edge(e)); });
-
-  // Now apply the layout function
-  var result = runLayout(graph, this._layout);
-
-  // Copy useDef attribute from input graph to output graph
-  graph.eachNode(function(u, a) {
-    if (a.useDef) {
-      result.node(u).useDef = a.useDef;
-    }
-  });
-
-  // Run any user-specified post layout processing
-  this._postLayout(result, svg);
-
-  var svgEdgePaths = this._drawEdgePaths(graph, svg.select('g.edgePaths'));
-
-  // Apply the layout information to the graph
-  this._positionNodes(result, svgNodes);
-  this._positionEdgeLabels(result, svgEdgeLabels);
-  this._positionEdgePaths(result, svgEdgePaths, orgSvg);
-
-  this._postRender(result, svg);
-
-  return result;
-};
-
-function copyAndInitGraph(graph) {
-  var copy = graph.copy();
-
-  if (copy.graph() === undefined) {
-    copy.graph({});
-  }
-
-  if (!('arrowheadFix' in copy.graph())) {
-    copy.graph().arrowheadFix = true;
-  }
-
-  // Init labels if they were not present in the source graph
-  copy.nodes().forEach(function(u) {
-    var value = copyObject(copy.node(u));
-    copy.node(u, value);
-    if (!('label' in value)) { value.label = ''; }
-  });
-
-  copy.edges().forEach(function(e) {
-    var value = copyObject(copy.edge(e));
-    copy.edge(e, value);
-    if (!('label' in value)) { value.label = ''; }
-  });
-
-  return copy;
-}
-
-function copyObject(obj) {
-  var copy = {};
-  for (var k in obj) {
-    copy[k] = obj[k];
-  }
-  return copy;
-}
-
-function calculateDimensions(group, value) {
-  var bbox = group.getBBox();
-  value.width = bbox.width;
-  value.height = bbox.height;
-}
-
-function runLayout(graph, layout) {
-  var result = layout.run(graph);
-
-  // Copy labels to the result graph
-  graph.eachNode(function(u, value) { result.node(u).label = value.label; });
-  graph.eachEdge(function(e, u, v, value) { result.edge(e).label = value.label; });
-
-  return result;
-}
-
-function defaultDrawNodes(g, root) {
-  var nodes = g.nodes().filter(function(u) { return !isComposite(g, u); });
-
-  var svgNodes = root
-    .selectAll('g.node')
-    .classed('enter', false)
-    .data(nodes, function(u) { return u; });
-
-  svgNodes.selectAll('*').remove();
-
-  svgNodes
-    .enter()
-      .append('g')
-        .style('opacity', 0)
-        .attr('class', 'node enter');
-
-  svgNodes.each(function(u) {
-    var attrs = g.node(u),
-        domNode = d3.select(this);
-    addLabel(attrs, domNode, true, 10, 10);
-  });
-
-  this._transition(svgNodes.exit())
-      .style('opacity', 0)
-      .remove();
-
-  return svgNodes;
-}
-
-function defaultDrawEdgeLabels(g, root) {
-  var svgEdgeLabels = root
-    .selectAll('g.edgeLabel')
-    .classed('enter', false)
-    .data(g.edges(), function (e) { return e; });
-
-  svgEdgeLabels.selectAll('*').remove();
-
-  svgEdgeLabels
-    .enter()
-      .append('g')
-        .style('opacity', 0)
-        .attr('class', 'edgeLabel enter');
-
-  svgEdgeLabels.each(function(e) { addLabel(g.edge(e), d3.select(this), false, 0, 0); });
-
-  this._transition(svgEdgeLabels.exit())
-      .style('opacity', 0)
-      .remove();
-
-  return svgEdgeLabels;
-}
-
-var defaultDrawEdgePaths = function(g, root) {
-  var svgEdgePaths = root
-    .selectAll('g.edgePath')
-    .classed('enter', false)
-    .data(g.edges(), function(e) { return e; });
-
-  var DEFAULT_ARROWHEAD = 'url(#arrowhead)',
-      createArrowhead = DEFAULT_ARROWHEAD;
-  if (!g.isDirected()) {
-    createArrowhead = null;
-  } else if (g.graph().arrowheadFix !== 'false' && g.graph().arrowheadFix !== false) {
-    createArrowhead = function() {
-      var strokeColor = d3.select(this).style('stroke');
-      if (strokeColor) {
-        var id = 'arrowhead-' + strokeColor.replace(/[^a-zA-Z0-9]/g, '_');
-        getOrMakeArrowhead(root, id).style('fill', strokeColor);
-        return 'url(#' + id + ')';
-      }
-      return DEFAULT_ARROWHEAD;
-    };
-  }
-
-  svgEdgePaths
-    .enter()
-      .append('g')
-        .attr('class', 'edgePath enter')
-        .append('path')
-          .style('opacity', 0);
-
-  svgEdgePaths
-    .selectAll('path')
-    .each(function(e) { applyStyle(g.edge(e).style, d3.select(this)); })
-    .attr('marker-end', createArrowhead);
-
-  this._transition(svgEdgePaths.exit())
-      .style('opacity', 0)
-      .remove();
-
-  return svgEdgePaths;
-};
-
-function defaultPositionNodes(g, svgNodes) {
-  function transform(u) {
-    var value = g.node(u);
-    return 'translate(' + value.x + ',' + value.y + ')';
-  }
-
-  // For entering nodes, position immediately without transition
-  svgNodes.filter('.enter').attr('transform', transform);
-
-  this._transition(svgNodes)
-      .style('opacity', 1)
-      .attr('transform', transform);
-}
-
-function defaultPositionEdgeLabels(g, svgEdgeLabels) {
-  function transform(e) {
-    var value = g.edge(e);
-    var point = findMidPoint(value.points);
-    return 'translate(' + point.x + ',' + point.y + ')';
-  }
-
-  // For entering edge labels, position immediately without transition
-  svgEdgeLabels.filter('.enter').attr('transform', transform);
-
-  this._transition(svgEdgeLabels)
-    .style('opacity', 1)
-    .attr('transform', transform);
-}
-
-function isEllipse(obj) {
-  return Object.prototype.toString.call(obj) === '[object SVGEllipseElement]';
-}
-
-function isCircle(obj) {
-  return Object.prototype.toString.call(obj) === '[object SVGCircleElement]';
-}
-
-function isPolygon(obj) {
-  return Object.prototype.toString.call(obj) === '[object SVGPolygonElement]';
-}
-
-function intersectNode(nd, p1, root) {
-  if (nd.useDef) {
-    var definedFig = root.select('defs #' + nd.useDef).node();
-    if (definedFig) {
-      var outerFig = definedFig.childNodes[0];
-      if (isCircle(outerFig) || isEllipse(outerFig)) {
-        return intersectEllipse(nd, outerFig, p1);
-      } else if (isPolygon(outerFig)) {
-        return intersectPolygon(nd, outerFig, p1);
-      }
-    }
-  }
-  // TODO: use bpodgursky's shortening algorithm here
-  return intersectRect(nd, p1);
-}
-
-function defaultPositionEdgePaths(g, svgEdgePaths, root) {
-  var interpolate = this._edgeInterpolate,
-      tension = this._edgeTension;
-
-  function calcPoints(e) {
-    var value = g.edge(e);
-    var source = g.node(g.incidentNodes(e)[0]);
-    var target = g.node(g.incidentNodes(e)[1]);
-    var points = value.points.slice();
-
-    var p0 = points.length === 0 ? target : points[0];
-    var p1 = points.length === 0 ? source : points[points.length - 1];
-
-    points.unshift(intersectNode(source, p0, root));
-    points.push(intersectNode(target, p1, root));
-
-    return d3.svg.line()
-      .x(function(d) { return d.x; })
-      .y(function(d) { return d.y; })
-      .interpolate(interpolate)
-      .tension(tension)
-      (points);
-  }
-
-  svgEdgePaths.filter('.enter').selectAll('path')
-      .attr('d', calcPoints);
-
-  this._transition(svgEdgePaths.selectAll('path'))
-      .attr('d', calcPoints)
-      .style('opacity', 1);
-}
-
-// By default we do not use transitions
-function defaultTransition(selection) {
-  return selection;
-}
-
-// Setup dom for zooming
-function defaultZoomSetup(graph, svg) {
-  var root = svg.property('ownerSVGElement');
-  // If the svg node is the root, we get null, so set to svg.
-  if (!root) {
-    root = svg;
-  } else {
-    root = d3.select(root);
-  }
-
-  if (root.select('rect.overlay').empty()) {
-    // Create an overlay for capturing mouse events that don't touch foreground
-    root.insert('rect', ':first-child')
-      .attr('class', 'overlay')
-      .attr('width', '100%')
-      .attr('height', '100%')
-      .style('fill', 'none')
-      .style('pointer-events', 'all');
-
-    // Capture the zoom behaviour from the svg
-    svg = svg.append('g')
-      .attr('class', 'zoom');
-
-    if (this._zoom) {
-      root.call(this._zoom(graph, svg));
-    }
-  }
-
-  return svg;
-}
-
-// By default allow pan and zoom
-function defaultZoom(graph, svg) {
-  return d3.behavior.zoom().on('zoom', function() {
-    svg.attr('transform', 'translate(' + d3.event.translate + ')scale(' + d3.event.scale + ')');
-  });
-}
-
-function defaultPostLayout() {
-  // Do nothing
-}
-
-function defaultPostRender(graph, root) {
-  if (graph.isDirected()) {
-    // Fill = #333 is for backwards compatibility
-    getOrMakeArrowhead(root, 'arrowhead')
-      .attr('fill', '#333');
-  }
-}
-
-function getOrMakeArrowhead(root, id) {
-  var search = root.select('#' + id);
-  if (!search.empty()) { return search; }
-
-  var defs = root.select('defs');
-  if (defs.empty()) {
-    defs = root.append('svg:defs');
-  }
-
-  var marker =
-    defs
-      .append('svg:marker')
-        .attr('id', id)
-        .attr('viewBox', '0 0 10 10')
-        .attr('refX', 8)
-        .attr('refY', 5)
-        .attr('markerUnits', 'strokeWidth')
-        .attr('markerWidth', 8)
-        .attr('markerHeight', 5)
-        .attr('orient', 'auto');
-
-  marker
-    .append('svg:path')
-      .attr('d', 'M 0 0 L 10 5 L 0 10 z');
-
-  return marker;
-}
-
-function addLabel(node, root, addingNode, marginX, marginY) {
-  // If the node has 'useDef' meta data, we rely on that
-  if (node.useDef) {
-    root.append('use').attr('xlink:href', '#' + node.useDef);
-    return;
-  }
-  // Add the rect first so that it appears behind the label
-  var label = node.label;
-  var rect = root.append('rect');
-  if (node.width) {
-    rect.attr('width', node.width);
-  }
-  if (node.height) {
-    rect.attr('height', node.height);
-  }
-
-  var labelSvg = root.append('g'),
-      innerLabelSvg;
-
-  if (label[0] === '<') {
-    addForeignObjectLabel(label, labelSvg);
-    // No margin for HTML elements
-    marginX = marginY = 0;
-  } else {
-    innerLabelSvg = addTextLabel(label,
-                                 labelSvg,
-                                 Math.floor(node.labelCols),
-                                 node.labelCut);
-    applyStyle(node.labelStyle, innerLabelSvg);
-  }
-
-  var labelBBox = labelSvg.node().getBBox();
-  labelSvg.attr('transform',
-                'translate(' + (-labelBBox.width / 2) + ',' + (-labelBBox.height / 2) + ')');
-
-  var bbox = root.node().getBBox();
-
-  rect
-    .attr('rx', node.rx ? node.rx : 5)
-    .attr('ry', node.ry ? node.ry : 5)
-    .attr('x', -(bbox.width / 2 + marginX))
-    .attr('y', -(bbox.height / 2 + marginY))
-    .attr('width', bbox.width + 2 * marginX)
-    .attr('height', bbox.height + 2 * marginY)
-    .attr('fill', '#fff');
-
-  if (addingNode) {
-    applyStyle(node.style, rect);
-
-    if (node.fill) {
-      rect.style('fill', node.fill);
-    }
-
-    if (node.stroke) {
-      rect.style('stroke', node.stroke);
-    }
-
-    if (node['stroke-width']) {
-      rect.style('stroke-width', node['stroke-width'] + 'px');
-    }
-
-    if (node['stroke-dasharray']) {
-      rect.style('stroke-dasharray', node['stroke-dasharray']);
-    }
-
-    if (node.href) {
-      root
-        .attr('class', root.attr('class') + ' clickable')
-        .on('click', function() {
-          window.open(node.href);
-        });
-    }
-  }
-}
-
-function addForeignObjectLabel(label, root) {
-  var fo = root
-    .append('foreignObject')
-      .attr('width', '100000');
-
-  var w, h;
-  fo
-    .append('xhtml:div')
-      .style('float', 'left')
-      // TODO find a better way to get dimensions for foreignObjects...
-      .html(function() { return label; })
-      .each(function() {
-        w = this.clientWidth;
-        h = this.clientHeight;
-      });
-
-  fo
-    .attr('width', w)
-    .attr('height', h);
-}
-
-function addTextLabel(label, root, labelCols, labelCut) {
-  if (labelCut === undefined) { labelCut = 'false'; }
-  labelCut = (labelCut.toString().toLowerCase() === 'true');
-
-  var node = root
-    .append('text')
-    .attr('text-anchor', 'left');
-
-  label = label.replace(/\\n/g, '\n');
-
-  var arr = labelCols ? wordwrap(label, labelCols, labelCut) : label;
-  arr = arr.split('\n');
-  for (var i = 0; i < arr.length; i++) {
-    node
-      .append('tspan')
-        .attr('dy', '1em')
-        .attr('x', '1')
-        .text(arr[i]);
-  }
-
-  return node;
-}
-
-// Thanks to
-// http://james.padolsey.com/javascript/wordwrap-for-javascript/
-function wordwrap (str, width, cut, brk) {
-  brk = brk || '\n';
-  width = width || 75;
-  cut = cut || false;
-
-  if (!str) { return str; }
-
-  var regex = '.{1,' + width + '}(\\s|$)' + (cut ? '|.{' + width + '}|.+$' : '|\\S+?(\\s|$)');
-
-  return str.match(new RegExp(regex, 'g')).join(brk);
-}
-
-function findMidPoint(points) {
-  var midIdx = points.length / 2;
-  if (points.length % 2) {
-    return points[Math.floor(midIdx)];
-  } else {
-    var p0 = points[midIdx - 1];
-    var p1 = points[midIdx];
-    return {x: (p0.x + p1.x) / 2, y: (p0.y + p1.y) / 2};
-  }
-}
-
-function intersectRect(rect, point) {
-  var x = rect.x;
-  var y = rect.y;
-
-  // Rectangle intersection algorithm from:
-  // http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
-  var dx = point.x - x;
-  var dy = point.y - y;
-  var w = rect.width / 2;
-  var h = rect.height / 2;
-
-  var sx, sy;
-  if (Math.abs(dy) * w > Math.abs(dx) * h) {
-    // Intersection is top or bottom of rect.
-    if (dy < 0) {
-      h = -h;
-    }
-    sx = dy === 0 ? 0 : h * dx / dy;
-    sy = h;
-  } else {
-    // Intersection is left or right of rect.
-    if (dx < 0) {
-      w = -w;
-    }
-    sx = w;
-    sy = dx === 0 ? 0 : w * dy / dx;
-  }
-
-  return {x: x + sx, y: y + sy};
-}
-
-function intersectEllipse(node, ellipseOrCircle, point) {
-  // Formulae from: http://mathworld.wolfram.com/Ellipse-LineIntersection.html
-
-  var cx = node.x;
-  var cy = node.y;
-  var rx, ry;
-
-  if (isCircle(ellipseOrCircle)) {
-    rx = ry = ellipseOrCircle.r.baseVal.value;
-  } else {
-    rx = ellipseOrCircle.rx.baseVal.value;
-    ry = ellipseOrCircle.ry.baseVal.value;
-  }
-
-  var px = cx - point.x;
-  var py = cy - point.y;
-
-  var det = Math.sqrt(rx * rx * py * py + ry * ry * px * px);
-
-  var dx = Math.abs(rx * ry * px / det);
-  if (point.x < cx) {
-    dx = -dx;
-  }
-  var dy = Math.abs(rx * ry * py / det);
-  if (point.y < cy) {
-    dy = -dy;
-  }
-
-  return {x: cx + dx, y: cy + dy};
-}
-
-function sameSign(r1, r2) {
-  return r1 * r2 > 0;
-}
-
-// Add point to the found intersections, but check first that it is unique.
-function addPoint(x, y, intersections) {
-  if (!intersections.some(function (elm) { return elm[0] === x && elm[1] === y; })) {
-    intersections.push([x, y]);
-  }
-}
-
-function intersectLine(x1, y1, x2, y2, x3, y3, x4, y4, intersections) {
-  // Algorithm from J. Avro, (ed.) Graphics Gems, No 2, Morgan Kaufmann, 1994, p7 and p473.
-
-  var a1, a2, b1, b2, c1, c2;
-  var r1, r2 , r3, r4;
-  var denom, offset, num;
-  var x, y;
-
-  // Compute a1, b1, c1, where line joining points 1 and 2 is F(x,y) = a1 x + b1 y + c1 = 0.
-  a1 = y2 - y1;
-  b1 = x1 - x2;
-  c1 = (x2 * y1) - (x1 * y2);
-
-  // Compute r3 and r4.
-  r3 = ((a1 * x3) + (b1 * y3) + c1);
-  r4 = ((a1 * x4) + (b1 * y4) + c1);
-
-  // Check signs of r3 and r4. If both point 3 and point 4 lie on
-  // same side of line 1, the line segments do not intersect.
-  if ((r3 !== 0) && (r4 !== 0) && sameSign(r3, r4)) {
-    return /*DONT_INTERSECT*/;
-  }
-
-  // Compute a2, b2, c2 where line joining points 3 and 4 is G(x,y) = a2 x + b2 y + c2 = 0
-  a2 = y4 - y3;
-  b2 = x3 - x4;
-  c2 = (x4 * y3) - (x3 * y4);
-
-  // Compute r1 and r2
-  r1 = (a2 * x1) + (b2 * y1) + c2;
-  r2 = (a2 * x2) + (b2 * y2) + c2;
-
-  // Check signs of r1 and r2. If both point 1 and point 2 lie
-  // on same side of second line segment, the line segments do
-  // not intersect.
-  if ((r1 !== 0) && (r2 !== 0) && (sameSign(r1, r2))) {
-    return /*DONT_INTERSECT*/;
-  }
-
-  // Line segments intersect: compute intersection point.
-  denom = (a1 * b2) - (a2 * b1);
-  if (denom === 0) {
-    return /*COLLINEAR*/;
-  }
-
-  offset = Math.abs(denom / 2);
-
-  // The denom/2 is to get rounding instead of truncating. It
-  // is added or subtracted to the numerator, depending upon the
-  // sign of the numerator.
-  num = (b1 * c2) - (b2 * c1);
-  x = (num < 0) ? ((num - offset) / denom) : ((num + offset) / denom);
-
-  num = (a2 * c1) - (a1 * c2);
-  y = (num < 0) ? ((num - offset) / denom) : ((num + offset) / denom);
-
-  // lines_intersect
-  addPoint(x, y, intersections);
-}
-
-function intersectPolygon(node, polygon, point) {
-  var x1 = node.x;
-  var y1 = node.y;
-  var x2 = point.x;
-  var y2 = point.y;
-
-  var intersections = [];
-  var points = polygon.points;
-
-  var minx = 100000, miny = 100000;
-  for (var j = 0; j < points.numberOfItems; j++) {
-    var p = points.getItem(j);
-    minx = Math.min(minx, p.x);
-    miny = Math.min(miny, p.y);
-  }
-
-  var left = x1 - node.width / 2 - minx;
-  var top =  y1 - node.height / 2 - miny;
-
-  for (var i = 0; i < points.numberOfItems; i++) {
-    var p1 = points.getItem(i);
-    var p2 = points.getItem(i < points.numberOfItems - 1 ? i + 1 : 0);
-    intersectLine(x1, y1, x2, y2, left + p1.x, top + p1.y, left + p2.x, top + p2.y, intersections);
-  }
-
-  if (intersections.length === 1) {
-    return {x: intersections[0][0], y: intersections[0][1]};
-  }
-
-  if (intersections.length > 1) {
-    // More intersections, find the one nearest to edge end point
-    intersections.sort(function(p, q) {
-      var pdx = p[0] - point.x,
-         pdy = p[1] - point.y,
-         distp = Math.sqrt(pdx * pdx + pdy * pdy),
-
-         qdx = q[0] - point.x,
-         qdy = q[1] - point.y,
-         distq = Math.sqrt(qdx * qdx + qdy * qdy);
-
-      return (distp < distq) ? -1 : (distp === distq ? 0 : 1);
-    });
-    return {x: intersections[0][0], y: intersections[0][1]};
-  } else {
-    console.log('NO INTERSECTION FOUND, RETURN NODE CENTER', node);
-    return node;
-  }
-}
-
-function isComposite(g, u) {
-  return 'children' in g && g.children(u).length;
-}
-
-function bind(func, thisArg) {
-  // For some reason PhantomJS occassionally fails when using the builtin bind,
-  // so we check if it is available and if not, use a degenerate polyfill.
-  if (func.bind) {
-    return func.bind(thisArg);
-  }
-
-  return function() {
-    return func.apply(thisArg, arguments);
-  };
-}
-
-function applyStyle(style, domNode) {
-  if (style) {
-    var currStyle = domNode.attr('style') || '';
-    domNode.attr('style', currStyle + '; ' + style);
-  }
-}
-
-},{"d3":10,"dagre":11}],4:[function(require,module,exports){
-module.exports = '0.2.9';
-
-},{}],5:[function(require,module,exports){
-exports.Set = require('./lib/Set');
-exports.PriorityQueue = require('./lib/PriorityQueue');
-exports.version = require('./lib/version');
-
-},{"./lib/PriorityQueue":6,"./lib/Set":7,"./lib/version":9}],6:[function(require,module,exports){
-module.exports = PriorityQueue;
-
-/**
- * A min-priority queue data structure. This algorithm is derived from Cormen,
- * et al., "Introduction to Algorithms". The basic idea of a min-priority
- * queue is that you can efficiently (in O(1) time) get the smallest key in
- * the queue. Adding and removing elements takes O(log n) time. A key can
- * have its priority decreased in O(log n) time.
- */
-function PriorityQueue() {
-  this._arr = [];
-  this._keyIndices = {};
-}
-
-/**
- * Returns the number of elements in the queue. Takes `O(1)` time.
- */
-PriorityQueue.prototype.size = function() {
-  return this._arr.length;
-};
-
-/**
- * Returns the keys that are in the queue. Takes `O(n)` time.
- */
-PriorityQueue.prototype.keys = function() {
-  return this._arr.map(function(x) { return x.key; });
-};
-
-/**
- * Returns `true` if **key** is in the queue and `false` if not.
- */
-PriorityQueue.prototype.has = function(key) {
-  return key in this._keyIndices;
-};
-
-/**
- * Returns the priority for **key**. If **key** is not present in the queue
- * then this function returns `undefined`. Takes `O(1)` time.
- *
- * @param {Object} key
- */
-PriorityQueue.prototype.priority = function(key) {
-  var index = this._keyIndices[key];
-  if (index !== undefined) {
-    return this._arr[index].priority;
-  }
-};
-
-/**
- * Returns the key for the minimum element in this queue. If the queue is
- * empty this function throws an Error. Takes `O(1)` time.
- */
-PriorityQueue.prototype.min = function() {
-  if (this.size() === 0) {
-    throw new Error("Queue underflow");
-  }
-  return this._arr[0].key;
-};
-
-/**
- * Inserts a new key into the priority queue. If the key already exists in
- * the queue this function returns `false`; otherwise it will return `true`.
- * Takes `O(n)` time.
- *
- * @param {Object} key the key to add
- * @param {Number} priority the initial priority for the key
- */
-PriorityQueue.prototype.add = function(key, priority) {
-  var keyIndices = this._keyIndices;
-  if (!(key in keyIndices)) {
-    var arr = this._arr;
-    var index = arr.length;
-    keyIndices[key] = index;
-    arr.push({key: key, priority: priority});
-    this._decrease(index);
-    return true;
-  }
-  return false;
-};
-
-/**
- * Removes and returns the smallest key in the queue. Takes `O(log n)` time.
- */
-PriorityQueue.prototype.removeMin = function() {
-  this._swap(0, this._arr.length - 1);
-  var min = this._arr.pop();
-  delete this._keyIndices[min.key];
-  this._heapify(0);
-  return min.key;
-};
-
-/**
- * Decreases the priority for **key** to **priority**. If the new priority is
- * greater than the previous priority, this function will throw an Error.
- *
- * @param {Object} key the key for which to raise priority
- * @param {Number} priority the new priority for the key
- */
-PriorityQueue.prototype.decrease = function(key, priority) {
-  var index = this._keyIndices[key];
-  if (priority > this._arr[index].priority) {
-    throw new Error("New priority is greater than current priority. " +
-        "Key: " + key + " Old: " + this._arr[index].priority + " New: " + priority);
-  }
-  this._arr[index].priority = priority;
-  this._decrease(index);
-};
-
-PriorityQueue.prototype._heapify = function(i) {
-  var arr = this._arr;
-  var l = 2 * i,
-      r = l + 1,
-      largest = i;
-  if (l < arr.length) {
-    largest = arr[l].priority < arr[largest].priority ? l : largest;
-    if (r < arr.length) {
-      largest = arr[r].priority < arr[largest].priority ? r : largest;
-    }
-    if (largest !== i) {
-      this._swap(i, largest);
-      this._heapify(largest);
-    }
-  }
-};
-
-PriorityQueue.prototype._decrease = function(index) {
-  var arr = this._arr;
-  var priority = arr[index].priority;
-  var parent;
-  while (index !== 0) {
-    parent = index >> 1;
-    if (arr[parent].priority < priority) {
-      break;
-    }
-    this._swap(index, parent);
-    index = parent;
-  }
-};
-
-PriorityQueue.prototype._swap = function(i, j) {
-  var arr = this._arr;
-  var keyIndices = this._keyIndices;
-  var origArrI = arr[i];
-  var origArrJ = arr[j];
-  arr[i] = origArrJ;
-  arr[j] = origArrI;
-  keyIndices[origArrJ.key] = i;
-  keyIndices[origArrI.key] = j;
-};
-
-},{}],7:[function(require,module,exports){
-var util = require('./util');
-
-module.exports = Set;
-
-/**
- * Constructs a new Set with an optional set of `initialKeys`.
- *
- * It is important to note that keys are coerced to String for most purposes
- * with this object, similar to the behavior of JavaScript's Object. For
- * example, the following will add only one key:
- *
- *     var s = new Set();
- *     s.add(1);
- *     s.add("1");
- *
- * However, the type of the key is preserved internally so that `keys` returns
- * the original key set uncoerced. For the above example, `keys` would return
- * `[1]`.
- */
-function Set(initialKeys) {
-  this._size = 0;
-  this._keys = {};
-
-  if (initialKeys) {
-    for (var i = 0, il = initialKeys.length; i < il; ++i) {
-      this.add(initialKeys[i]);
-    }
-  }
-}
-
-/**
- * Returns a new Set that represents the set intersection of the array of given
- * sets.
- */
-Set.intersect = function(sets) {
-  if (sets.length === 0) {
-    return new Set();
-  }
-
-  var result = new Set(!util.isArray(sets[0]) ? sets[0].keys() : sets[0]);
-  for (var i = 1, il = sets.length; i < il; ++i) {
-    var resultKeys = result.keys(),
-        other = !util.isArray(sets[i]) ? sets[i] : new Set(sets[i]);
-    for (var j = 0, jl = resultKeys.length; j < jl; ++j) {
-      var key = resultKeys[j];
-      if (!other.has(key)) {
-        result.remove(key);
-      }
-    }
-  }
-
-  return result;
-};
-
-/**
- * Returns a new Set that represents the set union of the array of given sets.
- */
-Set.union = function(sets) {
-  var totalElems = util.reduce(sets, function(lhs, rhs) {
-    return lhs + (rhs.size ? rhs.size() : rhs.length);
-  }, 0);
-  var arr = new Array(totalElems);
-
-  var k = 0;
-  for (var i = 0, il = sets.length; i < il; ++i) {
-    var cur = sets[i],
-        keys = !util.isArray(cur) ? cur.keys() : cur;
-    for (var j = 0, jl = keys.length; j < jl; ++j) {
-      arr[k++] = keys[j];
-    }
-  }
-
-  return new Set(arr);
-};
-
-/**
- * Returns the size of this set in `O(1)` time.
- */
-Set.prototype.size = function() {
-  return this._size;
-};
-
-/**
- * Returns the keys in this set. Takes `O(n)` time.
- */
-Set.prototype.keys = function() {
-  return values(this._keys);
-};
-
-/**
- * Tests if a key is present in this Set. Returns `true` if it is and `false`
- * if not. Takes `O(1)` time.
- */
-Set.prototype.has = function(key) {
-  return key in this._keys;
-};
-
-/**
- * Adds a new key to this Set if it is not already present. Returns `true` if
- * the key was added and `false` if it was already present. Takes `O(1)` time.
- */
-Set.prototype.add = function(key) {
-  if (!(key in this._keys)) {
-    this._keys[key] = key;
-    ++this._size;
-    return true;
-  }
-  return false;
-};
-
-/**
- * Removes a key from this Set. If the key was removed this function returns
- * `true`. If not, it returns `false`. Takes `O(1)` time.
- */
-Set.prototype.remove = function(key) {
-  if (key in this._keys) {
-    delete this._keys[key];
-    --this._size;
-    return true;
-  }
-  return false;
-};
-
-/*
- * Returns an array of all values for properties of **o**.
- */
-function values(o) {
-  var ks = Object.keys(o),
-      len = ks.length,
-      result = new Array(len),
-      i;
-  for (i = 0; i < len; ++i) {
-    result[i] = o[ks[i]];
-  }
-  return result;
-}
-
-},{"./util":8}],8:[function(require,module,exports){
-/*
- * This polyfill comes from
- * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
- */
-if(!Array.isArray) {
-  exports.isArray = function (vArg) {
-    return Object.prototype.toString.call(vArg) === '[object Array]';
-  };
-} else {
-  exports.isArray = Array.isArray;
-}
-
-/*
- * Slightly adapted polyfill from
- * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
- */
-if ('function' !== typeof Array.prototype.reduce) {
-  exports.reduce = function(array, callback, opt_initialValue) {
-    'use strict';
-    if (null === array || 'undefined' === typeof array) {
-      // At the moment all modern browsers, that support strict mode, have
-      // native implementation of Array.prototype.reduce. For instance, IE8
-      // does not support strict mode, so this check is actually useless.
-      throw new TypeError(
-          'Array.prototype.reduce called on null or undefined');
-    }
-    if ('function' !== typeof callback) {
-      throw new TypeError(callback + ' is not a function');
-    }
-    var index, value,
-        length = array.length >>> 0,
-        isValueSet = false;
-    if (1 < arguments.length) {
-      value = opt_initialValue;
-      isValueSet = true;
-    }
-    for (index = 0; length > index; ++index) {
-      if (array.hasOwnProperty(index)) {
-        if (isValueSet) {
-          value = callback(value, array[index], index, array);
-        }
-        else {
-          value = array[index];
-          isValueSet = true;
-        }
-      }
-    }
-    if (!isValueSet) {
-      throw new TypeError('Reduce of empty array with no initial value');
-    }
-    return value;
-  };
-} else {
-  exports.reduce = function(array, callback, opt_initialValue) {
-    return array.reduce(callback, opt_initialValue);
-  };
-}
-
-},{}],9:[function(require,module,exports){
-module.exports = '1.1.3';
-
-},{}],10:[function(require,module,exports){
-require("./d3");
-module.exports = d3;
-(function () { delete this.d3; })(); // unset global
-
-},{}],11:[function(require,module,exports){
-/*
-Copyright (c) 2012-2013 Chris Pettitt
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-*/
-exports.Digraph = require("graphlib").Digraph;
-exports.Graph = require("graphlib").Graph;
-exports.layout = require("./lib/layout");
-exports.version = require("./lib/version");
-exports.debug = require("./lib/debug");
-
-},{"./lib/debug":12,"./lib/layout":13,"./lib/version":28,"graphlib":29}],12:[function(require,module,exports){
-'use strict';
-
-var util = require('./util');
-
-/**
- * Renders a graph in a stringified DOT format that indicates the ordering of
- * nodes by layer. Circles represent normal nodes. Diamons represent dummy
- * nodes. While we try to put nodes in clusters, it appears that graphviz
- * does not respect this because we're later using subgraphs for ordering nodes
- * in each layer.
- */
-exports.dotOrdering = function(g) {
-  var ordering = util.ordering(g.filterNodes(util.filterNonSubgraphs(g)));
-  var result = 'digraph {';
-
-  function dfs(u) {
-    var children = g.children(u);
-    if (children.length) {
-      result += 'subgraph cluster_' + u + ' {';
-      result += 'label="' + u + '";';
-      children.forEach(function(v) {
-        dfs(v);
-      });
-      result += '}';
-    } else {
-      result += u;
-      if (g.node(u).dummy) {
-        result += ' [shape=diamond]';
-      }
-      result += ';';
-    }
-  }
-
-  g.children(null).forEach(dfs);
-
-  ordering.forEach(function(layer) {
-    result += 'subgraph { rank=same; edge [style="invis"];';
-    result += layer.join('->');
-    result += '}';
-  });
-
-  g.eachEdge(function(e, u, v) {
-    result += u + '->' + v + ';';
-  });
-
-  result += '}';
-
-  return result;
-};
-
-},{"./util":27}],13:[function(require,module,exports){
-'use strict';
-
-var util = require('./util'),
-    rank = require('./rank'),
-    order = require('./order'),
-    CGraph = require('graphlib').CGraph,
-    CDigraph = require('graphlib').CDigraph;
-
-module.exports = function() {
-  // External configuration
-  var config = {
-    // How much debug information to include?
-    debugLevel: 0,
-    // Max number of sweeps to perform in order phase
-    orderMaxSweeps: order.DEFAULT_MAX_SWEEPS,
-    // Use network simplex algorithm in ranking
-    rankSimplex: false,
-    // Rank direction. Valid values are (TB, LR)
-    rankDir: 'TB'
-  };
-
-  // Phase functions
-  var position = require('./position')();
-
-  // This layout object
-  var self = {};
-
-  self.orderIters = util.propertyAccessor(self, config, 'orderMaxSweeps');
-
-  self.rankSimplex = util.propertyAccessor(self, config, 'rankSimplex');
-
-  self.nodeSep = delegateProperty(position.nodeSep);
-  self.edgeSep = delegateProperty(position.edgeSep);
-  self.universalSep = delegateProperty(position.universalSep);
-  self.rankSep = delegateProperty(position.rankSep);
-  self.rankDir = util.propertyAccessor(self, config, 'rankDir');
-  self.debugAlignment = delegateProperty(position.debugAlignment);
-
-  self.debugLevel = util.propertyAccessor(self, config, 'debugLevel', function(x) {
-    util.log.level = x;
-    position.debugLevel(x);
-  });
-
-  self.run = util.time('Total layout', run);
-
-  self._normalize = normalize;
-
-  return self;
-
-  /*
-   * Constructs an adjacency graph using the nodes and edges specified through
-   * config. For each node and edge we add a property `dagre` that contains an
-   * object that will hold intermediate and final layout information. Some of
-   * the contents include:
-   *
-   *  1) A generated ID that uniquely identifies the object.
-   *  2) Dimension information for nodes (copied from the source node).
-   *  3) Optional dimension information for edges.
-   *
-   * After the adjacency graph is constructed the code no longer needs to use
-   * the original nodes and edges passed in via config.
-   */
-  function initLayoutGraph(inputGraph) {
-    var g = new CDigraph();
-
-    inputGraph.eachNode(function(u, value) {
-      if (value === undefined) value = {};
-      g.addNode(u, {
-        width: value.width,
-        height: value.height
-      });
-      if (value.hasOwnProperty('rank')) {
-        g.node(u).prefRank = value.rank;
-      }
-    });
-
-    // Set up subgraphs
-    if (inputGraph.parent) {
-      inputGraph.nodes().forEach(function(u) {
-        g.parent(u, inputGraph.parent(u));
-      });
-    }
-
-    inputGraph.eachEdge(function(e, u, v, value) {
-      if (value === undefined) value = {};
-      var newValue = {
-        e: e,
-        minLen: value.minLen || 1,
-        width: value.width || 0,
-        height: value.height || 0,
-        points: []
-      };
-
-      g.addEdge(null, u, v, newValue);
-    });
-
-    // Initial graph attributes
-    var graphValue = inputGraph.graph() || {};
-    g.graph({
-      rankDir: graphValue.rankDir || config.rankDir,
-      orderRestarts: graphValue.orderRestarts
-    });
-
-    return g;
-  }
-
-  function run(inputGraph) {
-    var rankSep = self.rankSep();
-    var g;
-    try {
-      // Build internal graph
-      g = util.time('initLayoutGraph', initLayoutGraph)(inputGraph);
-
-      if (g.order() === 0) {
-        return g;
-      }
-
-      // Make space for edge labels
-      g.eachEdge(function(e, s, t, a) {
-        a.minLen *= 2;
-      });
-      self.rankSep(rankSep / 2);
-
-      // Determine the rank for each node. Nodes with a lower rank will appear
-      // above nodes of higher rank.
-      util.time('rank.run', rank.run)(g, config.rankSimplex);
-
-      // Normalize the graph by ensuring that every edge is proper (each edge has
-      // a length of 1). We achieve this by adding dummy nodes to long edges,
-      // thus shortening them.
-      util.time('normalize', normalize)(g);
-
-      // Order the nodes so that edge crossings are minimized.
-      util.time('order', order)(g, config.orderMaxSweeps);
-
-      // Find the x and y coordinates for every node in the graph.
-      util.time('position', position.run)(g);
-
-      // De-normalize the graph by removing dummy nodes and augmenting the
-      // original long edges with coordinate information.
-      util.time('undoNormalize', undoNormalize)(g);
-
-      // Reverses points for edges that are in a reversed state.
-      util.time('fixupEdgePoints', fixupEdgePoints)(g);
-
-      // Restore delete edges and reverse edges that were reversed in the rank
-      // phase.
-      util.time('rank.restoreEdges', rank.restoreEdges)(g);
-
-      // Construct final result graph and return it
-      return util.time('createFinalGraph', createFinalGraph)(g, inputGraph.isDirected());
-    } finally {
-      self.rankSep(rankSep);
-    }
-  }
-
-  /*
-   * This function is responsible for 'normalizing' the graph. The process of
-   * normalization ensures that no edge in the graph has spans more than one
-   * rank. To do this it inserts dummy nodes as needed and links them by adding
-   * dummy edges. This function keeps enough information in the dummy nodes and
-   * edges to ensure that the original graph can be reconstructed later.
-   *
-   * This method assumes that the input graph is cycle free.
-   */
-  function normalize(g) {
-    var dummyCount = 0;
-    g.eachEdge(function(e, s, t, a) {
-      var sourceRank = g.node(s).rank;
-      var targetRank = g.node(t).rank;
-      if (sourceRank + 1 < targetRank) {
-        for (var u = s, rank = sourceRank + 1, i = 0; rank < targetRank; ++rank, ++i) {
-          var v = '_D' + (++dummyCount);
-          var node = {
-            width: a.width,
-            height: a.height,
-            edge: { id: e, source: s, target: t, attrs: a },
-            rank: rank,
-            dummy: true
-          };
-
-          // If this node represents a bend then we will use it as a control
-          // point. For edges with 2 segments this will be the center dummy
-          // node. For edges with more than two segments, this will be the
-          // first and last dummy node.
-          if (i === 0) node.index = 0;
-          else if (rank + 1 === targetRank) node.index = 1;
-
-          g.addNode(v, node);
-          g.addEdge(null, u, v, {});
-          u = v;
-        }
-        g.addEdge(null, u, t, {});
-        g.delEdge(e);
-      }
-    });
-  }
-
-  /*
-   * Reconstructs the graph as it was before normalization. The positions of
-   * dummy nodes are used to build an array of points for the original 'long'
-   * edge. Dummy nodes and edges are removed.
-   */
-  function undoNormalize(g) {
-    g.eachNode(function(u, a) {
-      if (a.dummy) {
-        if ('index' in a) {
-          var edge = a.edge;
-          if (!g.hasEdge(edge.id)) {
-            g.addEdge(edge.id, edge.source, edge.target, edge.attrs);
-          }
-          var points = g.edge(edge.id).points;
-          points[a.index] = { x: a.x, y: a.y, ul: a.ul, ur: a.ur, dl: a.dl, dr: a.dr };
-        }
-        g.delNode(u);
-      }
-    });
-  }
-
-  /*
-   * For each edge that was reversed during the `acyclic` step, reverse its
-   * array of points.
-   */
-  function fixupEdgePoints(g) {
-    g.eachEdge(function(e, s, t, a) { if (a.reversed) a.points.reverse(); });
-  }
-
-  function createFinalGraph(g, isDirected) {
-    var out = isDirected ? new CDigraph() : new CGraph();
-    out.graph(g.graph());
-    g.eachNode(function(u, value) { out.addNode(u, value); });
-    g.eachNode(function(u) { out.parent(u, g.parent(u)); });
-    g.eachEdge(function(e, u, v, value) {
-      out.addEdge(value.e, u, v, value);
-    });
-
-    // Attach bounding box information
-    var maxX = 0, maxY = 0;
-    g.eachNode(function(u, value) {
-      if (!g.children(u).length) {
-        maxX = Math.max(maxX, value.x + value.width / 2);
-        maxY = Math.max(maxY, value.y + value.height / 2);
-      }
-    });
-    g.eachEdge(function(e, u, v, value) {
-      var maxXPoints = Math.max.apply(Math, value.points.map(function(p) { return p.x; }));
-      var maxYPoints = Math.max.apply(Math, value.points.map(function(p) { return p.y; }));
-      maxX = Math.max(maxX, maxXPoints + value.width / 2);
-      maxY = Math.max(maxY, maxYPoints + value.height / 2);
-    });
-    out.graph().width = maxX;
-    out.graph().height = maxY;
-
-    return out;
-  }
-
-  /*
-   * Given a function, a new function is returned that invokes the given
-   * function. The return value from the function is always the `self` object.
-   */
-  function delegateProperty(f) {
-    return function() {
-      if (!arguments.length) return f();
-      f.apply(null, arguments);
-      return self;
-    };
-  }
-};
-
-
-},{"./order":14,"./position":19,"./rank":20,"./util":27,"graphlib":29}],14:[function(require,module,exports){
-'use strict';
-
-var util = require('./util'),
-    crossCount = require('./order/crossCount'),
-    initLayerGraphs = require('./order/initLayerGraphs'),
-    initOrder = require('./order/initOrder'),
-    sortLayer = require('./order/sortLayer');
-
-module.exports = order;
-
-// The maximum number of sweeps to perform before finishing the order phase.
-var DEFAULT_MAX_SWEEPS = 24;
-order.DEFAULT_MAX_SWEEPS = DEFAULT_MAX_SWEEPS;
-
-/*
- * Runs the order phase with the specified `graph, `maxSweeps`, and
- * `debugLevel`. If `maxSweeps` is not specified we use `DEFAULT_MAX_SWEEPS`.
- * If `debugLevel` is not set we assume 0.
- */
-function order(g, maxSweeps) {
-  if (arguments.length < 2) {
-    maxSweeps = DEFAULT_MAX_SWEEPS;
-  }
-
-  var restarts = g.graph().orderRestarts || 0;
-
-  var layerGraphs = initLayerGraphs(g);
-  // TODO: remove this when we add back support for ordering clusters
-  layerGraphs.forEach(function(lg) {
-    lg = lg.filterNodes(function(u) { return !g.children(u).length; });
-  });
-
-  var iters = 0,
-      currentBestCC,
-      allTimeBestCC = Number.MAX_VALUE,
-      allTimeBest = {};
-
-  function saveAllTimeBest() {
-    g.eachNode(function(u, value) { allTimeBest[u] = value.order; });
-  }
-
-  for (var j = 0; j < Number(restarts) + 1 && allTimeBestCC !== 0; ++j) {
-    currentBestCC = Number.MAX_VALUE;
-    initOrder(g, restarts > 0);
-
-    util.log(2, 'Order phase start cross count: ' + g.graph().orderInitCC);
-
-    var i, lastBest, cc;
-    for (i = 0, lastBest = 0; lastBest < 4 && i < maxSweeps && currentBestCC > 0; ++i, ++lastBest, ++iters) {
-      sweep(g, layerGraphs, i);
-      cc = crossCount(g);
-      if (cc < currentBestCC) {
-        lastBest = 0;
-        currentBestCC = cc;
-        if (cc < allTimeBestCC) {
-          saveAllTimeBest();
-          allTimeBestCC = cc;
-        }
-      }
-      util.log(3, 'Order phase start ' + j + ' iter ' + i + ' cross count: ' + cc);
-    }
-  }
-
-  Object.keys(allTimeBest).forEach(function(u) {
-    if (!g.children || !g.children(u).length) {
-      g.node(u).order = allTimeBest[u];
-    }
-  });
-  g.graph().orderCC = allTimeBestCC;
-
-  util.log(2, 'Order iterations: ' + iters);
-  util.log(2, 'Order phase best cross count: ' + g.graph().orderCC);
-}
-
-function predecessorWeights(g, nodes) {
-  var weights = {};
-  nodes.forEach(function(u) {
-    weights[u] = g.inEdges(u).map(function(e) {
-      return g.node(g.source(e)).order;
-    });
-  });
-  return weights;
-}
-
-function successorWeights(g, nodes) {
-  var weights = {};
-  nodes.forEach(function(u) {
-    weights[u] = g.outEdges(u).map(function(e) {
-      return g.node(g.target(e)).order;
-    });
-  });
-  return weights;
-}
-
-function sweep(g, layerGraphs, iter) {
-  if (iter % 2 === 0) {
-    sweepDown(g, layerGraphs, iter);
-  } else {
-    sweepUp(g, layerGraphs, iter);
-  }
-}
-
-function sweepDown(g, layerGraphs) {
-  var cg;
-  for (var i = 1; i < layerGraphs.length; ++i) {
-    cg = sortLayer(layerGraphs[i], cg, predecessorWeights(g, layerGraphs[i].nodes()));
-  }
-}
-
-function sweepUp(g, layerGraphs) {
-  var cg;
-  for (var i = layerGraphs.length - 2; i >= 0; --i) {
-    sortLayer(layerGraphs[i], cg, successorWeights(g, layerGraphs[i].nodes()));
-  }
-}
-
-},{"./order/crossCount":15,"./order/initLayerGraphs":16,"./order/initOrder":17,"./order/sortLayer":18,"./util":27}],15:[function(require,module,exports){
-'use strict';
-
-var util = require('../util');
-
-module.exports = crossCount;
-
-/*
- * Returns the cross count for the given graph.
- */
-function crossCount(g) {
-  var cc = 0;
-  var ordering = util.ordering(g);
-  for (var i = 1; i < ordering.length; ++i) {
-    cc += twoLayerCrossCount(g, ordering[i-1], ordering[i]);
-  }
-  return cc;
-}
-
-/*
- * This function searches through a ranked and ordered graph and counts the
- * number of edges that cross. This algorithm is derived from:
- *
- *    W. Barth et al., Bilayer Cross Counting, JGAA, 8(2) 179–194 (2004)
- */
-function twoLayerCrossCount(g, layer1, layer2) {
-  var indices = [];
-  layer1.forEach(function(u) {
-    var nodeIndices = [];
-    g.outEdges(u).forEach(function(e) { nodeIndices.push(g.node(g.target(e)).order); });
-    nodeIndices.sort(function(x, y) { return x - y; });
-    indices = indices.concat(nodeIndices);
-  });
-
-  var firstIndex = 1;
-  while (firstIndex < layer2.length) firstIndex <<= 1;
-
-  var treeSize = 2 * firstIndex - 1;
-  firstIndex -= 1;
-
-  var tree = [];
-  for (var i = 0; i < treeSize; ++i) { tree[i] = 0; }
-
-  var cc = 0;
-  indices.forEach(function(i) {
-    var treeIndex = i + firstIndex;
-    ++tree[treeIndex];
-    while (treeIndex > 0) {
-      if (treeIndex % 2) {
-        cc += tree[treeIndex + 1];
-      }
-      treeIndex = (treeIndex - 1) >> 1;
-      ++tree[treeIndex];
-    }
-  });
-
-  return cc;
-}
-
-},{"../util":27}],16:[function(require,module,exports){
-'use strict';
-
-var nodesFromList = require('graphlib').filter.nodesFromList,
-    /* jshint -W079 */
-    Set = require('cp-data').Set;
-
-module.exports = initLayerGraphs;
-
-/*
- * This function takes a compound layered graph, g, and produces an array of
- * layer graphs. Each entry in the array represents a subgraph of nodes
- * relevant for performing crossing reduction on that layer.
- */
-function initLayerGraphs(g) {
-  var ranks = [];
-
-  function dfs(u) {
-    if (u === null) {
-      g.children(u).forEach(function(v) { dfs(v); });
-      return;
-    }
-
-    var value = g.node(u);
-    value.minRank = ('rank' in value) ? value.rank : Number.MAX_VALUE;
-    value.maxRank = ('rank' in value) ? value.rank : Number.MIN_VALUE;
-    var uRanks = new Set();
-    g.children(u).forEach(function(v) {
-      var rs = dfs(v);
-      uRanks = Set.union([uRanks, rs]);
-      value.minRank = Math.min(value.minRank, g.node(v).minRank);
-      value.maxRank = Math.max(value.maxRank, g.node(v).maxRank);
-    });
-
-    if ('rank' in value) uRanks.add(value.rank);
-
-    uRanks.keys().forEach(function(r) {
-      if (!(r in ranks)) ranks[r] = [];
-      ranks[r].push(u);
-    });
-
-    return uRanks;
-  }
-  dfs(null);
-
-  var layerGraphs = [];
-  ranks.forEach(function(us, rank) {
-    layerGraphs[rank] = g.filterNodes(nodesFromList(us));
-  });
-
-  return layerGraphs;
-}
-
-},{"cp-data":5,"graphlib":29}],17:[function(require,module,exports){
-'use strict';
-
-var crossCount = require('./crossCount'),
-    util = require('../util');
-
-module.exports = initOrder;
-
-/*
- * Given a graph with a set of layered nodes (i.e. nodes that have a `rank`
- * attribute) this function attaches an `order` attribute that uniquely
- * arranges each node of each rank. If no constraint graph is provided the
- * order of the nodes in each rank is entirely arbitrary.
- */
-function initOrder(g, random) {
-  var layers = [];
-
-  g.eachNode(function(u, value) {
-    var layer = layers[value.rank];
-    if (g.children && g.children(u).length > 0) return;
-    if (!layer) {
-      layer = layers[value.rank] = [];
-    }
-    layer.push(u);
-  });
-
-  layers.forEach(function(layer) {
-    if (random) {
-      util.shuffle(layer);
-    }
-    layer.forEach(function(u, i) {
-      g.node(u).order = i;
-    });
-  });
-
-  var cc = crossCount(g);
-  g.graph().orderInitCC = cc;
-  g.graph().orderCC = Number.MAX_VALUE;
-}
-
-},{"../util":27,"./crossCount":15}],18:[function(require,module,exports){
-'use strict';
-
-var util = require('../util'),
-    Digraph = require('graphlib').Digraph,
-    topsort = require('graphlib').alg.topsort,
-    nodesFromList = require('graphlib').filter.nodesFromList;
-
-module.exports = sortLayer;
-
-function sortLayer(g, cg, weights) {
-  weights = adjustWeights(g, weights);
-  var result = sortLayerSubgraph(g, null, cg, weights);
-
-  result.list.forEach(function(u, i) {
-    g.node(u).order = i;
-  });
-  return result.constraintGraph;
-}
-
-function sortLayerSubgraph(g, sg, cg, weights) {
-  cg = cg ? cg.filterNodes(nodesFromList(g.children(sg))) : new Digraph();
-
-  var nodeData = {};
-  g.children(sg).forEach(function(u) {
-    if (g.children(u).length) {
-      nodeData[u] = sortLayerSubgraph(g, u, cg, weights);
-      nodeData[u].firstSG = u;
-      nodeData[u].lastSG = u;
-    } else {
-      var ws = weights[u];
-      nodeData[u] = {
-        degree: ws.length,
-        barycenter: util.sum(ws) / ws.length,
-        order: g.node(u).order,
-        orderCount: 1,
-        list: [u]
-      };
-    }
-  });
-
-  resolveViolatedConstraints(g, cg, nodeData);
-
-  var keys = Object.keys(nodeData);
-  keys.sort(function(x, y) {
-    return nodeData[x].barycenter - nodeData[y].barycenter ||
-           nodeData[x].order - nodeData[y].order;
-  });
-
-  var result =  keys.map(function(u) { return nodeData[u]; })
-                    .reduce(function(lhs, rhs) { return mergeNodeData(g, lhs, rhs); });
-  return result;
-}
-
-function mergeNodeData(g, lhs, rhs) {
-  var cg = mergeDigraphs(lhs.constraintGraph, rhs.constraintGraph);
-
-  if (lhs.lastSG !== undefined && rhs.firstSG !== undefined) {
-    if (cg === undefined) {
-      cg = new Digraph();
-    }
-    if (!cg.hasNode(lhs.lastSG)) { cg.addNode(lhs.lastSG); }
-    cg.addNode(rhs.firstSG);
-    cg.addEdge(null, lhs.lastSG, rhs.firstSG);
-  }
-
-  return {
-    degree: lhs.degree + rhs.degree,
-    barycenter: (lhs.barycenter * lhs.degree + rhs.barycenter * rhs.degree) /
-                (lhs.degree + rhs.degree),
-    order: (lhs.order * lhs.orderCount + rhs.order * rhs.orderCount) /
-           (lhs.orderCount + rhs.orderCount),
-    orderCount: lhs.orderCount + rhs.orderCount,
-    list: lhs.list.concat(rhs.list),
-    firstSG: lhs.firstSG !== undefined ? lhs.firstSG : rhs.firstSG,
-    lastSG: rhs.lastSG !== undefined ? rhs.lastSG : lhs.lastSG,
-    constraintGraph: cg
-  };
-}
-
-function mergeDigraphs(lhs, rhs) {
-  if (lhs === undefined) return rhs;
-  if (rhs === undefined) return lhs;
-
-  lhs = lhs.copy();
-  rhs.nodes().forEach(function(u) { lhs.addNode(u); });
-  rhs.edges().forEach(function(e, u, v) { lhs.addEdge(null, u, v); });
-  return lhs;
-}
-
-function resolveViolatedConstraints(g, cg, nodeData) {
-  // Removes nodes `u` and `v` from `cg` and makes any edges incident on them
-  // incident on `w` instead.
-  function collapseNodes(u, v, w) {
-    // TODO original paper removes self loops, but it is not obvious when this would happen
-    cg.inEdges(u).forEach(function(e) {
-      cg.delEdge(e);
-      cg.addEdge(null, cg.source(e), w);
-    });
-
-    cg.outEdges(v).forEach(function(e) {
-      cg.delEdge(e);
-      cg.addEdge(null, w, cg.target(e));
-    });
-
-    cg.delNode(u);
-    cg.delNode(v);
-  }
-
-  var violated;
-  while ((violated = findViolatedConstraint(cg, nodeData)) !== undefined) {
-    var source = cg.source(violated),
-        target = cg.target(violated);
-
-    var v;
-    while ((v = cg.addNode(null)) && g.hasNode(v)) {
-      cg.delNode(v);
-    }
-
-    // Collapse barycenter and list
-    nodeData[v] = mergeNodeData(g, nodeData[source], nodeData[target]);
-    delete nodeData[source];
-    delete nodeData[target];
-
-    collapseNodes(source, target, v);
-    if (cg.incidentEdges(v).length === 0) { cg.delNode(v); }
-  }
-}
-
-function findViolatedConstraint(cg, nodeData) {
-  var us = topsort(cg);
-  for (var i = 0; i < us.length; ++i) {
-    var u = us[i];
-    var inEdges = cg.inEdges(u);
-    for (var j = 0; j < inEdges.length; ++j) {
-      var e = inEdges[j];
-      if (nodeData[cg.source(e)].barycenter >= nodeData[u].barycenter) {
-        return e;
-      }
-    }
-  }
-}
-
-// Adjust weights so that they fall in the range of 0..|N|-1. If a node has no
-// weight assigned then set its adjusted weight to its current position. This
-// allows us to better retain the origiinal position of nodes without neighbors.
-function adjustWeights(g, weights) {
-  var minW = Number.MAX_VALUE,
-      maxW = 0,
-      adjusted = {};
-  g.eachNode(function(u) {
-    if (g.children(u).length) return;
-
-    var ws = weights[u];
-    if (ws.length) {
-      minW = Math.min(minW, util.min(ws));
-      maxW = Math.max(maxW, util.max(ws));
-    }
-  });
-
-  var rangeW = (maxW - minW);
-  g.eachNode(function(u) {
-    if (g.children(u).length) return;
-
-    var ws = weights[u];
-    if (!ws.length) {
-      adjusted[u] = [g.node(u).order];
-    } else {
-      adjusted[u] = ws.map(function(w) {
-        if (rangeW) {
-          return (w - minW) * (g.order() - 1) / rangeW;
-        } else {
-          return g.order() - 1 / 2;
-        }
-      });
-    }
-  });
-
-  return adjusted;
-}
-
-},{"../util":27,"graphlib":29}],19:[function(require,module,exports){
-'use strict';
-
-var util = require('./util');
-
-/*
- * The algorithms here are based on Brandes and Köpf, "Fast and Simple
- * Horizontal Coordinate Assignment".
- */
-module.exports = function() {
-  // External configuration
-  var config = {
-    nodeSep: 50,
-    edgeSep: 10,
-    universalSep: null,
-    rankSep: 30
-  };
-
-  var self = {};
-
-  self.nodeSep = util.propertyAccessor(self, config, 'nodeSep');
-  self.edgeSep = util.propertyAccessor(self, config, 'edgeSep');
-  // If not null this separation value is used for all nodes and edges
-  // regardless of their widths. `nodeSep` and `edgeSep` are ignored with this
-  // option.
-  self.universalSep = util.propertyAccessor(self, config, 'universalSep');
-  self.rankSep = util.propertyAccessor(self, config, 'rankSep');
-  self.debugLevel = util.propertyAccessor(self, config, 'debugLevel');
-
-  self.run = run;
-
-  return self;
-
-  function run(g) {
-    g = g.filterNodes(util.filterNonSubgraphs(g));
-
-    var layering = util.ordering(g);
-
-    var conflicts = findConflicts(g, layering);
-
-    var xss = {};
-    ['u', 'd'].forEach(function(vertDir) {
-      if (vertDir === 'd') layering.reverse();
-
-      ['l', 'r'].forEach(function(horizDir) {
-        if (horizDir === 'r') reverseInnerOrder(layering);
-
-        var dir = vertDir + horizDir;
-        var align = verticalAlignment(g, layering, conflicts, vertDir === 'u' ? 'predecessors' : 'successors');
-        xss[dir]= horizontalCompaction(g, layering, align.pos, align.root, align.align);
-
-        if (config.debugLevel >= 3)
-          debugPositioning(vertDir + horizDir, g, layering, xss[dir]);
-
-        if (horizDir === 'r') flipHorizontally(xss[dir]);
-
-        if (horizDir === 'r') reverseInnerOrder(layering);
-      });
-
-      if (vertDir === 'd') layering.reverse();
-    });
-
-    balance(g, layering, xss);
-
-    g.eachNode(function(v) {
-      var xs = [];
-      for (var alignment in xss) {
-        var alignmentX = xss[alignment][v];
-        posXDebug(alignment, g, v, alignmentX);
-        xs.push(alignmentX);
-      }
-      xs.sort(function(x, y) { return x - y; });
-      posX(g, v, (xs[1] + xs[2]) / 2);
-    });
-
-    // Align y coordinates with ranks
-    var y = 0, reverseY = g.graph().rankDir === 'BT' || g.graph().rankDir === 'RL';
-    layering.forEach(function(layer) {
-      var maxHeight = util.max(layer.map(function(u) { return height(g, u); }));
-      y += maxHeight / 2;
-      layer.forEach(function(u) {
-        posY(g, u, reverseY ? -y : y);
-      });
-      y += maxHeight / 2 + config.rankSep;
-    });
-
-    // Translate layout so that top left corner of bounding rectangle has
-    // coordinate (0, 0).
-    var minX = util.min(g.nodes().map(function(u) { return posX(g, u) - width(g, u) / 2; }));
-    var minY = util.min(g.nodes().map(function(u) { return posY(g, u) - height(g, u) / 2; }));
-    g.eachNode(function(u) {
-      posX(g, u, posX(g, u) - minX);
-      posY(g, u, posY(g, u) - minY);
-    });
-  }
-
-  /*
-   * Generate an ID that can be used to represent any undirected edge that is
-   * incident on `u` and `v`.
-   */
-  function undirEdgeId(u, v) {
-    return u < v
-      ? u.toString().length + ':' + u + '-' + v
-      : v.toString().length + ':' + v + '-' + u;
-  }
-
-  function findConflicts(g, layering) {
-    var conflicts = {}, // Set of conflicting edge ids
-        pos = {},       // Position of node in its layer
-        prevLayer,
-        currLayer,
-        k0,     // Position of the last inner segment in the previous layer
-        l,      // Current position in the current layer (for iteration up to `l1`)
-        k1;     // Position of the next inner segment in the previous layer or
-                // the position of the last element in the previous layer
-
-    if (layering.length <= 2) return conflicts;
-
-    function updateConflicts(v) {
-      var k = pos[v];
-      if (k < k0 || k > k1) {
-        conflicts[undirEdgeId(currLayer[l], v)] = true;
-      }
-    }
-
-    layering[1].forEach(function(u, i) { pos[u] = i; });
-    for (var i = 1; i < layering.length - 1; ++i) {
-      prevLayer = layering[i];
-      currLayer = layering[i+1];
-      k0 = 0;
-      l = 0;
-
-      // Scan current layer for next node that is incident to an inner segement
-      // between layering[i+1] and layering[i].
-      for (var l1 = 0; l1 < currLayer.length; ++l1) {
-        var u = currLayer[l1]; // Next inner segment in the current layer or
-                               // last node in the current layer
-        pos[u] = l1;
-        k1 = undefined;
-
-        if (g.node(u).dummy) {
-          var uPred = g.predecessors(u)[0];
-          // Note: In the case of self loops and sideways edges it is possible
-          // for a dummy not to have a predecessor.
-          if (uPred !== undefined && g.node(uPred).dummy)
-            k1 = pos[uPred];
-        }
-        if (k1 === undefined && l1 === currLayer.length - 1)
-          k1 = prevLayer.length - 1;
-
-        if (k1 !== undefined) {
-          for (; l <= l1; ++l) {
-            g.predecessors(currLayer[l]).forEach(updateConflicts);
-          }
-          k0 = k1;
-        }
-      }
-    }
-
-    return conflicts;
-  }
-
-  function verticalAlignment(g, layering, conflicts, relationship) {
-    var pos = {},   // Position for a node in its layer
-        root = {},  // Root of the block that the node participates in
-        align = {}; // Points to the next node in the block or, if the last
-                    // element in the block, points to the first block's root
-
-    layering.forEach(function(layer) {
-      layer.forEach(function(u, i) {
-        root[u] = u;
-        align[u] = u;
-        pos[u] = i;
-      });
-    });
-
-    layering.forEach(function(layer) {
-      var prevIdx = -1;
-      layer.forEach(function(v) {
-        var related = g[relationship](v), // Adjacent nodes from the previous layer
-            mid;                          // The mid point in the related array
-
-        if (related.length > 0) {
-          related.sort(function(x, y) { return pos[x] - pos[y]; });
-          mid = (related.length - 1) / 2;
-          related.slice(Math.floor(mid), Math.ceil(mid) + 1).forEach(function(u) {
-            if (align[v] === v) {
-              if (!conflicts[undirEdgeId(u, v)] && prevIdx < pos[u]) {
-                align[u] = v;
-                align[v] = root[v] = root[u];
-                prevIdx = pos[u];
-              }
-            }
-          });
-        }
-      });
-    });
-
-    return { pos: pos, root: root, align: align };
-  }
-
-  // This function deviates from the standard BK algorithm in two ways. First
-  // it takes into account the size of the nodes. Second it includes a fix to
-  // the original algorithm that is described in Carstens, "Node and Label
-  // Placement in a Layered Layout Algorithm".
-  function horizontalCompaction(g, layering, pos, root, align) {
-    var sink = {},       // Mapping of node id -> sink node id for class
-        maybeShift = {}, // Mapping of sink node id -> { class node id, min shift }
-        shift = {},      // Mapping of sink node id -> shift
-        pred = {},       // Mapping of node id -> predecessor node (or null)
-        xs = {};         // Calculated X positions
-
-    layering.forEach(function(layer) {
-      layer.forEach(function(u, i) {
-        sink[u] = u;
-        maybeShift[u] = {};
-        if (i > 0)
-          pred[u] = layer[i - 1];
-      });
-    });
-
-    function updateShift(toShift, neighbor, delta) {
-      if (!(neighbor in maybeShift[toShift])) {
-        maybeShift[toShift][neighbor] = delta;
-      } else {
-        maybeShift[toShift][neighbor] = Math.min(maybeShift[toShift][neighbor], delta);
-      }
-    }
-
-    function placeBlock(v) {
-      if (!(v in xs)) {
-        xs[v] = 0;
-        var w = v;
-        do {
-          if (pos[w] > 0) {
-            var u = root[pred[w]];
-            placeBlock(u);
-            if (sink[v] === v) {
-              sink[v] = sink[u];
-            }
-            var delta = sep(g, pred[w]) + sep(g, w);
-            if (sink[v] !== sink[u]) {
-              updateShift(sink[u], sink[v], xs[v] - xs[u] - delta);
-            } else {
-              xs[v] = Math.max(xs[v], xs[u] + delta);
-            }
-          }
-          w = align[w];
-        } while (w !== v);
-      }
-    }
-
-    // Root coordinates relative to sink
-    util.values(root).forEach(function(v) {
-      placeBlock(v);
-    });
-
-    // Absolute coordinates
-    // There is an assumption here that we've resolved shifts for any classes
-    // that begin at an earlier layer. We guarantee this by visiting layers in
-    // order.
-    layering.forEach(function(layer) {
-      layer.forEach(function(v) {
-        xs[v] = xs[root[v]];
-        if (v === root[v] && v === sink[v]) {
-          var minShift = 0;
-          if (v in maybeShift && Object.keys(maybeShift[v]).length > 0) {
-            minShift = util.min(Object.keys(maybeShift[v])
-                                 .map(function(u) {
-                                      return maybeShift[v][u] + (u in shift ? shift[u] : 0);
-                                      }
-                                 ));
-          }
-          shift[v] = minShift;
-        }
-      });
-    });
-
-    layering.forEach(function(layer) {
-      layer.forEach(function(v) {
-        xs[v] += shift[sink[root[v]]] || 0;
-      });
-    });
-
-    return xs;
-  }
-
-  function findMinCoord(g, layering, xs) {
-    return util.min(layering.map(function(layer) {
-      var u = layer[0];
-      return xs[u];
-    }));
-  }
-
-  function findMaxCoord(g, layering, xs) {
-    return util.max(layering.map(function(layer) {
-      var u = layer[layer.length - 1];
-      return xs[u];
-    }));
-  }
-
-  function balance(g, layering, xss) {
-    var min = {},                            // Min coordinate for the alignment
-        max = {},                            // Max coordinate for the alginment
-        smallestAlignment,
-        shift = {};                          // Amount to shift a given alignment
-
-    function updateAlignment(v) {
-      xss[alignment][v] += shift[alignment];
-    }
-
-    var smallest = Number.POSITIVE_INFINITY;
-    for (var alignment in xss) {
-      var xs = xss[alignment];
-      min[alignment] = findMinCoord(g, layering, xs);
-      max[alignment] = findMaxCoord(g, layering, xs);
-      var w = max[alignment] - min[alignment];
-      if (w < smallest) {
-        smallest = w;
-        smallestAlignment = alignment;
-      }
-    }
-
-    // Determine how much to adjust positioning for each alignment
-    ['u', 'd'].forEach(function(vertDir) {
-      ['l', 'r'].forEach(function(horizDir) {
-        var alignment = vertDir + horizDir;
-        shift[alignment] = horizDir === 'l'
-            ? min[smallestAlignment] - min[alignment]
-            : max[smallestAlignment] - max[alignment];
-      });
-    });
-
-    // Find average of medians for xss array
-    for (alignment in xss) {
-      g.eachNode(updateAlignment);
-    }
-  }
-
-  function flipHorizontally(xs) {
-    for (var u in xs) {
-      xs[u] = -xs[u];
-    }
-  }
-
-  function reverseInnerOrder(layering) {
-    layering.forEach(function(layer) {
-      layer.reverse();
-    });
-  }
-
-  function width(g, u) {
-    switch (g.graph().rankDir) {
-      case 'LR': return g.node(u).height;
-      case 'RL': return g.node(u).height;
-      default:   return g.node(u).width;
-    }
-  }
-
-  function height(g, u) {
-    switch(g.graph().rankDir) {
-      case 'LR': return g.node(u).width;
-      case 'RL': return g.node(u).width;
-      default:   return g.node(u).height;
-    }
-  }
-
-  function sep(g, u) {
-    if (config.universalSep !== null) {
-      return config.universalSep;
-    }
-    var w = width(g, u);
-    var s = g.node(u).dummy ? config.edgeSep : config.nodeSep;
-    return (w + s) / 2;
-  }
-
-  function posX(g, u, x) {
-    if (g.graph().rankDir === 'LR' || g.graph().rankDir === 'RL') {
-      if (arguments.length < 3) {
-        return g.node(u).y;
-      } else {
-        g.node(u).y = x;
-      }
-    } else {
-      if (arguments.length < 3) {
-        return g.node(u).x;
-      } else {
-        g.node(u).x = x;
-      }
-    }
-  }
-
-  function posXDebug(name, g, u, x) {
-    if (g.graph().rankDir === 'LR' || g.graph().rankDir === 'RL') {
-      if (arguments.length < 3) {
-        return g.node(u)[name];
-      } else {
-        g.node(u)[name] = x;
-      }
-    } else {
-      if (arguments.length < 3) {
-        return g.node(u)[name];
-      } else {
-        g.node(u)[name] = x;
-      }
-    }
-  }
-
-  function posY(g, u, y) {
-    if (g.graph().rankDir === 'LR' || g.graph().rankDir === 'RL') {
-      if (arguments.length < 3) {
-        return g.node(u).x;
-      } else {
-        g.node(u).x = y;
-      }
-    } else {
-      if (arguments.length < 3) {
-        return g.node(u).y;
-      } else {
-        g.node(u).y = y;
-      }
-    }
-  }
-
-  function debugPositioning(align, g, layering, xs) {
-    layering.forEach(function(l, li) {
-      var u, xU;
-      l.forEach(function(v) {
-        var xV = xs[v];
-        if (u) {
-          var s = sep(g, u) + sep(g, v);
-          if (xV - xU < s)
-            console.log('Position phase: sep violation. Align: ' + align + '. Layer: ' + li + '. ' +
-              'U: ' + u + ' V: ' + v + '. Actual sep: ' + (xV - xU) + ' Expected sep: ' + s);
-        }
-        u = v;
-        xU = xV;
-      });
-    });
-  }
-};
-
-},{"./util":27}],20:[function(require,module,exports){
-'use strict';
-
-var util = require('./util'),
-    acyclic = require('./rank/acyclic'),
-    initRank = require('./rank/initRank'),
-    feasibleTree = require('./rank/feasibleTree'),
-    constraints = require('./rank/constraints'),
-    simplex = require('./rank/simplex'),
-    components = require('graphlib').alg.components,
-    filter = require('graphlib').filter;
-
-exports.run = run;
-exports.restoreEdges = restoreEdges;
-
-/*
- * Heuristic function that assigns a rank to each node of the input graph with
- * the intent of minimizing edge lengths, while respecting the `minLen`
- * attribute of incident edges.
- *
- * Prerequisites:
- *
- *  * Each edge in the input graph must have an assigned 'minLen' attribute
- */
-function run(g, useSimplex) {
-  expandSelfLoops(g);
-
-  // If there are rank constraints on nodes, then build a new graph that
-  // encodes the constraints.
-  util.time('constraints.apply', constraints.apply)(g);
-
-  expandSidewaysEdges(g);
-
-  // Reverse edges to get an acyclic graph, we keep the graph in an acyclic
-  // state until the very end.
-  util.time('acyclic', acyclic)(g);
-
-  // Convert the graph into a flat graph for ranking
-  var flatGraph = g.filterNodes(util.filterNonSubgraphs(g));
-
-  // Assign an initial ranking using DFS.
-  initRank(flatGraph);
-
-  // For each component improve the assigned ranks.
-  components(flatGraph).forEach(function(cmpt) {
-    var subgraph = flatGraph.filterNodes(filter.nodesFromList(cmpt));
-    rankComponent(subgraph, useSimplex);
-  });
-
-  // Relax original constraints
-  util.time('constraints.relax', constraints.relax(g));
-
-  // When handling nodes with constrained ranks it is possible to end up with
-  // edges that point to previous ranks. Most of the subsequent algorithms assume
-  // that edges are pointing to successive ranks only. Here we reverse any "back
-  // edges" and mark them as such. The acyclic algorithm will reverse them as a
-  // post processing step.
-  util.time('reorientEdges', reorientEdges)(g);
-}
-
-function restoreEdges(g) {
-  acyclic.undo(g);
-}
-
-/*
- * Expand self loops into three dummy nodes. One will sit above the incident
- * node, one will be at the same level, and one below. The result looks like:
- *
- *         /--<--x--->--\
- *     node              y
- *         \--<--z--->--/
- *
- * Dummy nodes x, y, z give us the shape of a loop and node y is where we place
- * the label.
- *
- * TODO: consolidate knowledge of dummy node construction.
- * TODO: support minLen = 2
- */
-function expandSelfLoops(g) {
-  g.eachEdge(function(e, u, v, a) {
-    if (u === v) {
-      var x = addDummyNode(g, e, u, v, a, 0, false),
-          y = addDummyNode(g, e, u, v, a, 1, true),
-          z = addDummyNode(g, e, u, v, a, 2, false);
-      g.addEdge(null, x, u, {minLen: 1, selfLoop: true});
-      g.addEdge(null, x, y, {minLen: 1, selfLoop: true});
-      g.addEdge(null, u, z, {minLen: 1, selfLoop: true});
-      g.addEdge(null, y, z, {minLen: 1, selfLoop: true});
-      g.delEdge(e);
-    }
-  });
-}
-
-function expandSidewaysEdges(g) {
-  g.eachEdge(function(e, u, v, a) {
-    if (u === v) {
-      var origEdge = a.originalEdge,
-          dummy = addDummyNode(g, origEdge.e, origEdge.u, origEdge.v, origEdge.value, 0, true);
-      g.addEdge(null, u, dummy, {minLen: 1});
-      g.addEdge(null, dummy, v, {minLen: 1});
-      g.delEdge(e);
-    }
-  });
-}
-
-function addDummyNode(g, e, u, v, a, index, isLabel) {
-  return g.addNode(null, {
-    width: isLabel ? a.width : 0,
-    height: isLabel ? a.height : 0,
-    edge: { id: e, source: u, target: v, attrs: a },
-    dummy: true,
-    index: index
-  });
-}
-
-function reorientEdges(g) {
-  g.eachEdge(function(e, u, v, value) {
-    if (g.node(u).rank > g.node(v).rank) {
-      g.delEdge(e);
-      value.reversed = true;
-      g.addEdge(e, v, u, value);
-    }
-  });
-}
-
-function rankComponent(subgraph, useSimplex) {
-  var spanningTree = feasibleTree(subgraph);
-
-  if (useSimplex) {
-    util.log(1, 'Using network simplex for ranking');
-    simplex(subgraph, spanningTree);
-  }
-  normalize(subgraph);
-}
-
-function normalize(g) {
-  var m = util.min(g.nodes().map(function(u) { return g.node(u).rank; }));
-  g.eachNode(function(u, node) { node.rank -= m; });
-}
-
-},{"./rank/acyclic":21,"./rank/constraints":22,"./rank/feasibleTree":23,"./rank/initRank":24,"./rank/simplex":26,"./util":27,"graphlib":29}],21:[function(require,module,exports){
-'use strict';
-
-var util = require('../util');
-
-module.exports = acyclic;
-module.exports.undo = undo;
-
-/*
- * This function takes a directed graph that may have cycles and reverses edges
- * as appropriate to break these cycles. Each reversed edge is assigned a
- * `reversed` attribute with the value `true`.
- *
- * There should be no self loops in the graph.
- */
-function acyclic(g) {
-  var onStack = {},
-      visited = {},
-      reverseCount = 0;
-  
-  function dfs(u) {
-    if (u in visited) return;
-    visited[u] = onStack[u] = true;
-    g.outEdges(u).forEach(function(e) {
-      var t = g.target(e),
-          value;
-
-      if (u === t) {
-        console.error('Warning: found self loop "' + e + '" for node "' + u + '"');
-      } else if (t in onStack) {
-        value = g.edge(e);
-        g.delEdge(e);
-        value.reversed = true;
-        ++reverseCount;
-        g.addEdge(e, t, u, value);
-      } else {
-        dfs(t);
-      }
-    });
-
-    delete onStack[u];
-  }
-
-  g.eachNode(function(u) { dfs(u); });
-
-  util.log(2, 'Acyclic Phase: reversed ' + reverseCount + ' edge(s)');
-
-  return reverseCount;
-}
-
-/*
- * Given a graph that has had the acyclic operation applied, this function
- * undoes that operation. More specifically, any edge with the `reversed`
- * attribute is again reversed to restore the original direction of the edge.
- */
-function undo(g) {
-  g.eachEdge(function(e, s, t, a) {
-    if (a.reversed) {
-      delete a.reversed;
-      g.delEdge(e);
-      g.addEdge(e, t, s, a);
-    }
-  });
-}
-
-},{"../util":27}],22:[function(require,module,exports){
-'use strict';
-
-exports.apply = function(g) {
-  function dfs(sg) {
-    var rankSets = {};
-    g.children(sg).forEach(function(u) {
-      if (g.children(u).length) {
-        dfs(u);
-        return;
-      }
-
-      var value = g.node(u),
-          prefRank = value.prefRank;
-      if (prefRank !== undefined) {
-        if (!checkSupportedPrefRank(prefRank)) { return; }
-
-        if (!(prefRank in rankSets)) {
-          rankSets.prefRank = [u];
-        } else {
-          rankSets.prefRank.push(u);
-        }
-
-        var newU = rankSets[prefRank];
-        if (newU === undefined) {
-          newU = rankSets[prefRank] = g.addNode(null, { originalNodes: [] });
-          g.parent(newU, sg);
-        }
-
-        redirectInEdges(g, u, newU, prefRank === 'min');
-        redirectOutEdges(g, u, newU, prefRank === 'max');
-
-        // Save original node and remove it from reduced graph
-        g.node(newU).originalNodes.push({ u: u, value: value, parent: sg });
-        g.delNode(u);
-      }
-    });
-
-    addLightEdgesFromMinNode(g, sg, rankSets.min);
-    addLightEdgesToMaxNode(g, sg, rankSets.max);
-  }
-
-  dfs(null);
-};
-
-function checkSupportedPrefRank(prefRank) {
-  if (prefRank !== 'min' && prefRank !== 'max' && prefRank.indexOf('same_') !== 0) {
-    console.error('Unsupported rank type: ' + prefRank);
-    return false;
-  }
-  return true;
-}
-
-function redirectInEdges(g, u, newU, reverse) {
-  g.inEdges(u).forEach(function(e) {
-    var origValue = g.edge(e),
-        value;
-    if (origValue.originalEdge) {
-      value = origValue;
-    } else {
-      value =  {
-        originalEdge: { e: e, u: g.source(e), v: g.target(e), value: origValue },
-        minLen: g.edge(e).minLen
-      };
-    }
-
-    // Do not reverse edges for self-loops.
-    if (origValue.selfLoop) {
-      reverse = false;
-    }
-
-    if (reverse) {
-      // Ensure that all edges to min are reversed
-      g.addEdge(null, newU, g.source(e), value);
-      value.reversed = true;
-    } else {
-      g.addEdge(null, g.source(e), newU, value);
-    }
-  });
-}
-
-function redirectOutEdges(g, u, newU, reverse) {
-  g.outEdges(u).forEach(function(e) {
-    var origValue = g.edge(e),
-        value;
-    if (origValue.originalEdge) {
-      value = origValue;
-    } else {
-      value =  {
-        originalEdge: { e: e, u: g.source(e), v: g.target(e), value: origValue },
-        minLen: g.edge(e).minLen
-      };
-    }
-
-    // Do not reverse edges for self-loops.
-    if (origValue.selfLoop) {
-      reverse = false;
-    }
-
-    if (reverse) {
-      // Ensure that all edges from max are reversed
-      g.addEdge(null, g.target(e), newU, value);
-      value.reversed = true;
-    } else {
-      g.addEdge(null, newU, g.target(e), value);
-    }
-  });
-}
-
-function addLightEdgesFromMinNode(g, sg, minNode) {
-  if (minNode !== undefined) {
-    g.children(sg).forEach(function(u) {
-      // The dummy check ensures we don't add an edge if the node is involved
-      // in a self loop or sideways edge.
-      if (u !== minNode && !g.outEdges(minNode, u).length && !g.node(u).dummy) {
-        g.addEdge(null, minNode, u, { minLen: 0 });
-      }
-    });
-  }
-}
-
-function addLightEdgesToMaxNode(g, sg, maxNode) {
-  if (maxNode !== undefined) {
-    g.children(sg).forEach(function(u) {
-      // The dummy check ensures we don't add an edge if the node is involved
-      // in a self loop or sideways edge.
-      if (u !== maxNode && !g.outEdges(u, maxNode).length && !g.node(u).dummy) {
-        g.addEdge(null, u, maxNode, { minLen: 0 });
-      }
-    });
-  }
-}
-
-/*
- * This function "relaxes" the constraints applied previously by the "apply"
- * function. It expands any nodes that were collapsed and assigns the rank of
- * the collapsed node to each of the expanded nodes. It also restores the
- * original edges and removes any dummy edges pointing at the collapsed nodes.
- *
- * Note that the process of removing collapsed nodes also removes dummy edges
- * automatically.
- */
-exports.relax = function(g) {
-  // Save original edges
-  var originalEdges = [];
-  g.eachEdge(function(e, u, v, value) {
-    var originalEdge = value.originalEdge;
-    if (originalEdge) {
-      originalEdges.push(originalEdge);
-    }
-  });
-
-  // Expand collapsed nodes
-  g.eachNode(function(u, value) {
-    var originalNodes = value.originalNodes;
-    if (originalNodes) {
-      originalNodes.forEach(function(originalNode) {
-        originalNode.value.rank = value.rank;
-        g.addNode(originalNode.u, originalNode.value);
-        g.parent(originalNode.u, originalNode.parent);
-      });
-      g.delNode(u);
-    }
-  });
-
-  // Restore original edges
-  originalEdges.forEach(function(edge) {
-    g.addEdge(edge.e, edge.u, edge.v, edge.value);
-  });
-};
-
-},{}],23:[function(require,module,exports){
-'use strict';
-
-/* jshint -W079 */
-var Set = require('cp-data').Set,
-/* jshint +W079 */
-    Digraph = require('graphlib').Digraph,
-    util = require('../util');
-
-module.exports = feasibleTree;
-
-/*
- * Given an acyclic graph with each node assigned a `rank` attribute, this
- * function constructs and returns a spanning tree. This function may reduce
- * the length of some edges from the initial rank assignment while maintaining
- * the `minLen` specified by each edge.
- *
- * Prerequisites:
- *
- * * The input graph is acyclic
- * * Each node in the input graph has an assigned `rank` attribute
- * * Each edge in the input graph has an assigned `minLen` attribute
- *
- * Outputs:
- *
- * A feasible spanning tree for the input graph (i.e. a spanning tree that
- * respects each graph edge's `minLen` attribute) represented as a Digraph with
- * a `root` attribute on graph.
- *
- * Nodes have the same id and value as that in the input graph.
- *
- * Edges in the tree have arbitrarily assigned ids. The attributes for edges
- * include `reversed`. `reversed` indicates that the edge is a
- * back edge in the input graph.
- */
-function feasibleTree(g) {
-  var remaining = new Set(g.nodes()),
-      tree = new Digraph();
-
-  if (remaining.size() === 1) {
-    var root = g.nodes()[0];
-    tree.addNode(root, {});
-    tree.graph({ root: root });
-    return tree;
-  }
-
-  function addTightEdges(v) {
-    var continueToScan = true;
-    g.predecessors(v).forEach(function(u) {
-      if (remaining.has(u) && !slack(g, u, v)) {
-        if (remaining.has(v)) {
-          tree.addNode(v, {});
-          remaining.remove(v);
-          tree.graph({ root: v });
-        }
-
-        tree.addNode(u, {});
-        tree.addEdge(null, u, v, { reversed: true });
-        remaining.remove(u);
-        addTightEdges(u);
-        continueToScan = false;
-      }
-    });
-
-    g.successors(v).forEach(function(w)  {
-      if (remaining.has(w) && !slack(g, v, w)) {
-        if (remaining.has(v)) {
-          tree.addNode(v, {});
-          remaining.remove(v);
-          tree.graph({ root: v });
-        }
-
-        tree.addNode(w, {});
-        tree.addEdge(null, v, w, {});
-        remaining.remove(w);
-        addTightEdges(w);
-        continueToScan = false;
-      }
-    });
-    return continueToScan;
-  }
-
-  function createTightEdge() {
-    var minSlack = Number.MAX_VALUE;
-    remaining.keys().forEach(function(v) {
-      g.predecessors(v).forEach(function(u) {
-        if (!remaining.has(u)) {
-          var edgeSlack = slack(g, u, v);
-          if (Math.abs(edgeSlack) < Math.abs(minSlack)) {
-            minSlack = -edgeSlack;
-          }
-        }
-      });
-
-      g.successors(v).forEach(function(w) {
-        if (!remaining.has(w)) {
-          var edgeSlack = slack(g, v, w);
-          if (Math.abs(edgeSlack) < Math.abs(minSlack)) {
-            minSlack = edgeSlack;
-          }
-        }
-      });
-    });
-
-    tree.eachNode(function(u) { g.node(u).rank -= minSlack; });
-  }
-
-  while (remaining.size()) {
-    var nodesToSearch = !tree.order() ? remaining.keys() : tree.nodes();
-    for (var i = 0, il = nodesToSearch.length;
-         i < il && addTightEdges(nodesToSearch[i]);
-         ++i);
-    if (remaining.size()) {
-      createTightEdge();
-    }
-  }
-
-  return tree;
-}
-
-function slack(g, u, v) {
-  var rankDiff = g.node(v).rank - g.node(u).rank;
-  var maxMinLen = util.max(g.outEdges(u, v)
-                            .map(function(e) { return g.edge(e).minLen; }));
-  return rankDiff - maxMinLen;
-}
-
-},{"../util":27,"cp-data":5,"graphlib":29}],24:[function(require,module,exports){
-'use strict';
-
-var util = require('../util'),
-    topsort = require('graphlib').alg.topsort;
-
-module.exports = initRank;
-
-/*
- * Assigns a `rank` attribute to each node in the input graph and ensures that
- * this rank respects the `minLen` attribute of incident edges.
- *
- * Prerequisites:
- *
- *  * The input graph must be acyclic
- *  * Each edge in the input graph must have an assigned 'minLen' attribute
- */
-function initRank(g) {
-  var sorted = topsort(g);
-
-  sorted.forEach(function(u) {
-    var inEdges = g.inEdges(u);
-    if (inEdges.length === 0) {
-      g.node(u).rank = 0;
-      return;
-    }
-
-    var minLens = inEdges.map(function(e) {
-      return g.node(g.source(e)).rank + g.edge(e).minLen;
-    });
-    g.node(u).rank = util.max(minLens);
-  });
-}
-
-},{"../util":27,"graphlib":29}],25:[function(require,module,exports){
-'use strict';
-
-module.exports = {
-  slack: slack
-};
-
-/*
- * A helper to calculate the slack between two nodes (`u` and `v`) given a
- * `minLen` constraint. The slack represents how much the distance between `u`
- * and `v` could shrink while maintaining the `minLen` constraint. If the value
- * is negative then the constraint is currently violated.
- *
-  This function requires that `u` and `v` are in `graph` and they both have a
-  `rank` attribute.
- */
-function slack(graph, u, v, minLen) {
-  return Math.abs(graph.node(u).rank - graph.node(v).rank) - minLen;
-}
-
-},{}],26:[function(require,module,exports){
-'use strict';
-
-var util = require('../util'),
-    rankUtil = require('./rankUtil');
-
-module.exports = simplex;
-
-function simplex(graph, spanningTree) {
-  // The network simplex algorithm repeatedly replaces edges of
-  // the spanning tree with negative cut values until no such
-  // edge exists.
-  initCutValues(graph, spanningTree);
-  while (true) {
-    var e = leaveEdge(spanningTree);
-    if (e === null) break;
-    var f = enterEdge(graph, spanningTree, e);
-    exchange(graph, spanningTree, e, f);
-  }
-}
-
-/*
- * Set the cut values of edges in the spanning tree by a depth-first
- * postorder traversal.  The cut value corresponds to the cost, in
- * terms of a ranking's edge length sum, of lengthening an edge.
- * Negative cut values typically indicate edges that would yield a
- * smaller edge length sum if they were lengthened.
- */
-function initCutValues(graph, spanningTree) {
-  computeLowLim(spanningTree);
-
-  spanningTree.eachEdge(function(id, u, v, treeValue) {
-    treeValue.cutValue = 0;
-  });
-
-  // Propagate cut values up the tree.
-  function dfs(n) {
-    var children = spanningTree.successors(n);
-    for (var c in children) {
-      var child = children[c];
-      dfs(child);
-    }
-    if (n !== spanningTree.graph().root) {
-      setCutValue(graph, spanningTree, n);
-    }
-  }
-  dfs(spanningTree.graph().root);
-}
-
-/*
- * Perform a DFS postorder traversal, labeling each node v with
- * its traversal order 'lim(v)' and the minimum traversal number
- * of any of its descendants 'low(v)'.  This provides an efficient
- * way to test whether u is an ancestor of v since
- * low(u) <= lim(v) <= lim(u) if and only if u is an ancestor.
- */
-function computeLowLim(tree) {
-  var postOrderNum = 0;
-  
-  function dfs(n) {
-    var children = tree.successors(n);
-    var low = postOrderNum;
-    for (var c in children) {
-      var child = children[c];
-      dfs(child);
-      low = Math.min(low, tree.node(child).low);
-    }
-    tree.node(n).low = low;
-    tree.node(n).lim = postOrderNum++;
-  }
-
-  dfs(tree.graph().root);
-}
-
-/*
- * To compute the cut value of the edge parent -> child, we consider
- * it and any other graph edges to or from the child.
- *          parent
- *             |
- *           child
- *          /      \
- *         u        v
- */
-function setCutValue(graph, tree, child) {
-  var parentEdge = tree.inEdges(child)[0];
-
-  // List of child's children in the spanning tree.
-  var grandchildren = [];
-  var grandchildEdges = tree.outEdges(child);
-  for (var gce in grandchildEdges) {
-    grandchildren.push(tree.target(grandchildEdges[gce]));
-  }
-
-  var cutValue = 0;
-
-  // TODO: Replace unit increment/decrement with edge weights.
-  var E = 0;    // Edges from child to grandchild's subtree.
-  var F = 0;    // Edges to child from grandchild's subtree.
-  var G = 0;    // Edges from child to nodes outside of child's subtree.
-  var H = 0;    // Edges from nodes outside of child's subtree to child.
-
-  // Consider all graph edges from child.
-  var outEdges = graph.outEdges(child);
-  var gc;
-  for (var oe in outEdges) {
-    var succ = graph.target(outEdges[oe]);
-    for (gc in grandchildren) {
-      if (inSubtree(tree, succ, grandchildren[gc])) {
-        E++;
-      }
-    }
-    if (!inSubtree(tree, succ, child)) {
-      G++;
-    }
-  }
-
-  // Consider all graph edges to child.
-  var inEdges = graph.inEdges(child);
-  for (var ie in inEdges) {
-    var pred = graph.source(inEdges[ie]);
-    for (gc in grandchildren) {
-      if (inSubtree(tree, pred, grandchildren[gc])) {
-        F++;
-      }
-    }
-    if (!inSubtree(tree, pred, child)) {
-      H++;
-    }
-  }
-
-  // Contributions depend on the alignment of the parent -> child edge
-  // and the child -> u or v edges.
-  var grandchildCutSum = 0;
-  for (gc in grandchildren) {
-    var cv = tree.edge(grandchildEdges[gc]).cutValue;
-    if (!tree.edge(grandchildEdges[gc]).reversed) {
-      grandchildCutSum += cv;
-    } else {
-      grandchildCutSum -= cv;
-    }
-  }
-
-  if (!tree.edge(parentEdge).reversed) {
-    cutValue += grandchildCutSum - E + F - G + H;
-  } else {
-    cutValue -= grandchildCutSum - E + F - G + H;
-  }
-
-  tree.edge(parentEdge).cutValue = cutValue;
-}
-
-/*
- * Return whether n is a node in the subtree with the given
- * root.
- */
-function inSubtree(tree, n, root) {
-  return (tree.node(root).low <= tree.node(n).lim &&
-          tree.node(n).lim <= tree.node(root).lim);
-}
-
-/*
- * Return an edge from the tree with a negative cut value, or null if there
- * is none.
- */
-function leaveEdge(tree) {
-  var edges = tree.edges();
-  for (var n in edges) {
-    var e = edges[n];
-    var treeValue = tree.edge(e);
-    if (treeValue.cutValue < 0) {
-      return e;
-    }
-  }
-  return null;
-}
-
-/*
- * The edge e should be an edge in the tree, with an underlying edge
- * in the graph, with a negative cut value.  Of the two nodes incident
- * on the edge, take the lower one.  enterEdge returns an edge with
- * minimum slack going from outside of that node's subtree to inside
- * of that node's subtree.
- */
-function enterEdge(graph, tree, e) {
-  var source = tree.source(e);
-  var target = tree.target(e);
-  var lower = tree.node(target).lim < tree.node(source).lim ? target : source;
-
-  // Is the tree edge aligned with the graph edge?
-  var aligned = !tree.edge(e).reversed;
-
-  var minSlack = Number.POSITIVE_INFINITY;
-  var minSlackEdge;
-  if (aligned) {
-    graph.eachEdge(function(id, u, v, value) {
-      if (id !== e && inSubtree(tree, u, lower) && !inSubtree(tree, v, lower)) {
-        var slack = rankUtil.slack(graph, u, v, value.minLen);
-        if (slack < minSlack) {
-          minSlack = slack;
-          minSlackEdge = id;
-        }
-      }
-    });
-  } else {
-    graph.eachEdge(function(id, u, v, value) {
-      if (id !== e && !inSubtree(tree, u, lower) && inSubtree(tree, v, lower)) {
-        var slack = rankUtil.slack(graph, u, v, value.minLen);
-        if (slack < minSlack) {
-          minSlack = slack;
-          minSlackEdge = id;
-        }
-      }
-    });
-  }
-
-  if (minSlackEdge === undefined) {
-    var outside = [];
-    var inside = [];
-    graph.eachNode(function(id) {
-      if (!inSubtree(tree, id, lower)) {
-        outside.push(id);
-      } else {
-        inside.push(id);
-      }
-    });
-    throw new Error('No edge found from outside of tree to inside');
-  }
-
-  return minSlackEdge;
-}
-
-/*
- * Replace edge e with edge f in the tree, recalculating the tree root,
- * the nodes' low and lim properties and the edges' cut values.
- */
-function exchange(graph, tree, e, f) {
-  tree.delEdge(e);
-  var source = graph.source(f);
-  var target = graph.target(f);
-
-  // Redirect edges so that target is the root of its subtree.
-  function redirect(v) {
-    var edges = tree.inEdges(v);
-    for (var i in edges) {
-      var e = edges[i];
-      var u = tree.source(e);
-      var value = tree.edge(e);
-      redirect(u);
-      tree.delEdge(e);
-      value.reversed = !value.reversed;
-      tree.addEdge(e, v, u, value);
-    }
-  }
-
-  redirect(target);
-
-  var root = source;
-  var edges = tree.inEdges(root);
-  while (edges.length > 0) {
-    root = tree.source(edges[0]);
-    edges = tree.inEdges(root);
-  }
-
-  tree.graph().root = root;
... 22605 lines suppressed ...


Mime
View raw message