Commit 565d6278 authored by setop's avatar setop 🚀
Browse files

integrate v0.3.1 from hex repo content

parent c9ba4894
......@@ -24,3 +24,5 @@ incendium-*.tar
# Temporary files, for example, from tests.
/tmp/
.vscode
mix.lock
{
"cSpell.words": [
"clearfix"
]
}
\ No newline at end of file
......@@ -2,6 +2,25 @@
<!-- %% CHANGELOG_ENTRIES %% -->
### 0.3.1 - 2021-07-20 19:21:12
* Fixed javascript
* Added instructions for incendium-enhanced benchmarks
### 0.3.0 - 2021-07-14 01:07:08
Integrate with Benchee.
We're shifting the focus of Incendium from something you integrate with your Phoenix app for instant feedback after small code changes to something more reproducible that can be integrate with a benchmark suite.
We do this by providing the function `Incendium.run/2`, which takes the same arguments as `Benchee.run/2` plus some extra incendium-specific arguments.
### 0.2.1 - 2021-07-05 00:20:48
Improved docs.
### 0.2.0 - 2021-07-05 00:00:02
First public version.
......
Copyright 2021 by Tiago Barroso <tmbb@campus.ul.pt>
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.
\ No newline at end of file
......@@ -3,6 +3,13 @@
Easy profiling for your Phoenix controller actions (and other functions) using [flamegraphs](http://www.brendangregg.com/flamegraphs.html).
#### Example flamegraph
<link rel="stylesheet" href="doc_extra/assets/incendium.css" />
<script src="doc_extra/assets/incendium.js" charset="utf-8"></script>
<script type="text/javascript" src="doc_extra/assets/incendium_flamegraph_hkctthqlqhcubcsgrazymmvaldzllxbq.js"></script>
## Installation
The package can be installed
......@@ -11,7 +18,7 @@ by adding `incendium` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:incendium, "~> 0.1.0"}
{:incendium, "~> x.y.z"}
]
end
```
......@@ -34,7 +41,40 @@ Fortunately there is a very good javascrtip library to generate flamegraphs: [d3
By reading `:eflame` stacktrace samples and converting them into a format that `d3-flame-graph` can understand, we can render the flamegraph in a webpage.
That way, instead of the manual steps above you can just visit an URL in your web application.
## Usage
## Batch usage (usage with benchmarks)
Incendium can be used to run benchmarks with integrated profiling data (in the form of flamegraphs).
It uses [Benchee]() under the hood, and the API is actually quite similar to Benchee's.
It provides a single function, namely `Incendium.run/2`, which takes the same arguments as `Benchee.run/2`, plus some incendium-specific ones. The main difference is that the suite title is a required keyword argument instead of an optional one.
An example:
```elixir
defmodule Incendium.Benchmarks.Example do
defp map_fun(i) do
[i, i * i]
end
def run() do
list = Enum.to_list(1..10_000)
Incendium.run(%{
"flat_map" => fn -> Enum.flat_map(list, &map_fun/1) end,
"map.flatten" => fn -> list |> Enum.map(&map_fun/1) |> List.flatten() end
},
title: "Example",
incendium_flamegraph_widths_to_scale: true
)
end
end
Incendium.Benchmarks.Example.run()
```
The output of the script above can be found [here](https://hexdocs.pm/incendium/0.3.0/assets/Example.html).
## Interactive Usage (intgrated with a Phoenix web application)
To use `incendium` in your web application, you need to follow these steps:
......@@ -43,7 +83,7 @@ To use `incendium` in your web application, you need to follow these steps:
```elixir
def deps do
[
{:incendium, "~> 0.1.0"}
{:incendium, "~> 0.2.0"}
]
end
```
......@@ -112,3 +152,5 @@ In the future we may support better options such as sampling profilers.
Each time you run a profiled function, a new stacktrace will be generated.
Stacktraces are not currently saves, you can only access the latest one.
In the future we might add a persistence layer that stores a number of stacktraces instead of keeping just the last one.
[Here](https://hexdocs.pm/incendium/example-flamegraph.html) you can find an example flamegraph with explanations about how to interact with it .
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
# Example flamegraph
This is an example flamegraph generated by Incendium.
Flamegraphs are highly dynamic thanks to the `d3` library.
Ýou can click on a horizontal rectangle (which represents a function call)
to zoom in on the rectangle and its descendants.
You can use the search form on the top right to search for a specific function name.
<link rel="stylesheet" href="assets/incendium.css" />
<script src="assets/incendium.js" charset="utf-8"></script>
<script type="text/javascript" src="assets/incendium_flamegraph_hkctthqlqhcubcsgrazymmvaldzllxbq.js"></script>
The *Download* link allows you to download the flamegraph as a javascript file.
To draw the flamegraph in a webpage you just have to add an inline `<script>` that references the javascript file.
You don't even need to call any javascript.
The script automatically inserts the flamgeraph just before the script tag.
For the flamegraph to work properly, you must include the `incendium.js` and `incendium.css` in the webpage (the generated javascript code dependes on the javascript in `incendium.js`). While incendium could be changed in order to generate completely standalone javascript, if you want to include several flamegrpahs in the same webpage, including the entire contents of `incendium.js` would be very wasteful.
For example, the flamegraph above was generated by the following HTML:
```html
<link rel="stylesheet" href="assets/incendium.css" />
<script src="assets/incendium.js" charset="utf-8"></script>
<script type="text/javascript" src="assets/incendium_flamegraph_hkctthqlqhcubcsgrazymmvaldzllxbq.js"></script>
```
\ No newline at end of file
defmodule Incendium do
@external_resource "README.md"
readme_part =
"README.md"
|> File.read!()
|> String.split("<!-- ex_doc -->")
|> Enum.at(1)
@moduledoc readme_part
@moduledoc """
Easy flamegraphs to profile for your web applications.
#{readme_part}
"""
@doc """
Runs benchmarks using `Benchee` and render the scenarios as flamegraphs.
Takes the same parameters as `Benchee.run/2`.
Takes the following `Incendium`-specific options:
- `:incendium_flamegraph_widths_to_scale` (default `true`) -
sets the width of the flamegraphs according to the scenario runtime.
Scenarios that take a longer time to run will generate wider flamegraphs.
- `:incendium_file` (default `"incendium/\#{suite.title}`) -
the path to the HTML file were Incendium saves the benchmark results
(in Benchee, this is specified by a Formatter, bue Incendium unfortunatelly
can't reuse the normal Benchee formatting infrastructure)
All other options are passed into `Benchee.run/2`
"""
def run(benchmarks, options) do
Incendium.Benchee.run(benchmarks, options)
end
alias Incendium.Storage
......
defmodule Incendium.Application do
use Application
@moduledoc false
require Logger
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
{Registry, keys: :unique, name: Incendium.BencheeServer.Registry}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
end
......@@ -4,8 +4,11 @@ defmodule Incendium.Assets do
@d3_js_url "https://d3js.org/d3.v4.min.js"
@d3_tip_js_url "https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.min.js"
@d3_flamegraph_js_url "https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@2.0.3/dist/d3-flamegraph.min.js"
@jquery_js_url "https://code.jquery.com/jquery-3.4.1.slim.min.js"
@bootstrap_js_url "https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
@d3_flamegraph_css_url "https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@2.0.3/dist/d3-flamegraph.css"
@bootstrap_css_url "https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
extra_css_path = "lib/incendium/assets/extra.css"
extra_js_path = "lib/incendium/assets/extra.js"
......@@ -20,14 +23,45 @@ defmodule Incendium.Assets do
@extra_css File.read!(extra_css_path)
@extra_js File.read!(extra_js_path)
def css_path(), do: Path.join([:code.priv_dir(:incendium), "assets", "incendium.css"])
def css_path() do
Path.join([:code.priv_dir(:incendium), "assets", "incendium.css"])
end
def js_path() do
Path.join([:code.priv_dir(:incendium), "assets", "incendium.js"])
end
def benchee_css_path() do
Path.join([:code.priv_dir(:incendium), "assets", "benchee-incendium.css"])
end
def js_path(), do: Path.join([:code.priv_dir(:incendium), "assets", "incendium.js"])
def benchee_js_path() do
Path.join([:code.priv_dir(:incendium), "assets", "benchee-incendium.js"])
end
def extra_css() do
@extra_css
end
def all_js() do
File.read!(js_path())
end
def all_css() do
File.read!(css_path())
end
def all_benchee_js() do
File.read!(benchee_js_path())
end
def all_benchee_css() do
File.read!(benchee_css_path())
end
# Build online
# ------------
def build_js() do
{:ok, {_, _, d3_js}} = :httpc.request(@d3_js_url)
{:ok, {_, _, d3_tip_js}} = :httpc.request(@d3_tip_js_url)
......@@ -39,18 +73,163 @@ defmodule Incendium.Assets do
d3_flamegraph_js,
@extra_js
]
|> Enum.intersperse("\n")
|> remove_js_source_map()
save_to_local_cache("d3.js", d3_js)
save_to_local_cache("d3-tip.js", d3_tip_js)
save_to_local_cache("d3-flame-graph", d3_flamegraph_js)
File.write(js_path(), all_js)
end
def build_benchee_js() do
{:ok, {_, _, d3_js}} = :httpc.request(@d3_js_url)
{:ok, {_, _, d3_tip_js}} = :httpc.request(@d3_tip_js_url)
{:ok, {_, _, d3_flamegraph_js}} = :httpc.request(@d3_flamegraph_js_url)
{:ok, {_, _, jquery_js}} = :httpc.request(@jquery_js_url)
{:ok, {_, _, bootstrap_js}} = :httpc.request(@bootstrap_js_url)
all_js = [
d3_js,
d3_tip_js,
d3_flamegraph_js,
jquery_js,
bootstrap_js,
@extra_js
]
|> Enum.intersperse("\n")
|> remove_js_source_map()
save_to_local_cache("d3.js", d3_js)
save_to_local_cache("d3-tip.js", d3_tip_js)
save_to_local_cache("d3-flame-graph.js", d3_flamegraph_js)
save_to_local_cache("jquery.js", jquery_js)
save_to_local_cache("bootstrap.js", bootstrap_js)
File.write(benchee_js_path(), all_js)
end
def build_css() do
{:ok, {_, _, d3_flamegraph_css}} = :httpc.request(@d3_flamegraph_css_url)
File.write!(css_path(), d3_flamegraph_css)
save_to_local_cache("flamegraph.css", d3_flamegraph_css)
File.write!(css_path(), remove_css_source_map(d3_flamegraph_css))
end
def build_benchee_css() do
{:ok, {_, _, d3_flamegraph_css}} = :httpc.request(@d3_flamegraph_css_url)
{:ok, {_, _, bootstrap_css}} = :httpc.request(@bootstrap_css_url)
save_to_local_cache("flamegraph.css", d3_flamegraph_css)
save_to_local_cache("bootstrap.css", bootstrap_css)
all_css = [
d3_flamegraph_css,
bootstrap_css
]
|> Enum.intersperse("\n")
|> remove_css_source_map()
File.write!(benchee_css_path(), all_css)
end
def build_assets() do
build_css()
build_js()
build_benchee_css()
build_benchee_js()
end
# Build offline:
# --------------
def build_js_offline() do
d3_js = read_from_local_cache("d3.js")
d3_tip_js = read_from_local_cache("d3-tip.js")
d3_flamegraph_js = read_from_local_cache("d3-flame-graph.js")
jquery_js = read_from_local_cache("jquery.js")
bootstrap_js = read_from_local_cache("bootstrap.js")
all_js = [
d3_js,
d3_tip_js,
d3_flamegraph_js,
jquery_js,
bootstrap_js,
@extra_js
]
|> Enum.intersperse("\n")
|> remove_js_source_map()
File.write(js_path(), all_js)
end
def build_benchee_js_offline() do
d3_js = read_from_local_cache("d3.js")
d3_tip_js = read_from_local_cache("d3-tip.js")
d3_flamegraph_js = read_from_local_cache("d3-flame-graph.js")
all_js = [
d3_js,
d3_tip_js,
d3_flamegraph_js,
@extra_js
]
|> Enum.intersperse("\n")
|> remove_js_source_map()
File.write(benchee_js_path(), all_js)
end
def build_css_offline() do
d3_flamegraph_css = read_from_local_cache("flamegraph.css")
File.write!(css_path(), d3_flamegraph_css |> remove_css_source_map)
end
def build_benchee_css_offline() do
d3_flamegraph_css = read_from_local_cache("flamegraph.css")
bootstrap_css = read_from_local_cache("bootstrap.css")
all_css = [
d3_flamegraph_css,
bootstrap_css
]
|> Enum.intersperse("\n")
|> remove_css_source_map()
File.write!(benchee_css_path(), all_css)
end
def build_assets_offline() do
build_css_offline()
build_js_offline()
build_benchee_css_offline()
build_benchee_js_offline()
end
# Helpers
defp save_to_local_cache(filename, data) do
path = Path.join("lib/incendium/assets/vendor", filename)
File.write!(path, data)
end
defp read_from_local_cache(filename) do
path = Path.join("lib/incendium/assets/vendor", filename)
File.read!(path)
end
defp remove_js_source_map(iolist) do
binary = to_string(iolist)
String.replace(binary, ~r'^\s*//#\s*sourceMappingURL=[^\n]+', "\n")
end
defp remove_css_source_map(iolist) do
binary = to_string(iolist)
# Not semantically correct but the correct regex is way too complex
String.replace(binary, ~r'^\s*/\*#\s*sourceMappingURL=[^\n]+', "\n")
end
end
File mode changed from 100644 to 100755
function incendiumSearch(id) {
console.log("search")
var term = document.getElementById("term-" + id).value;
window['incendiumFlamegraph_' + id].search(term);
window['incendiumFlameGraph_' + id].search(term);
}
function incendiumClear(id) {
console.log("clear")
document.getElementById('term-' + id).value = '';
window['incendiumFlamegraph_' + id].clear();
window['incendiumFlameGraph_' + id].clear();
}
function incendiumResetZoom(id) {
window['incendiumFlamegraph_' + id].resetZoom();
window['incendiumFlameGraph_' + id].resetZoom();
}
// Saves the javascript as a file
function incendiumDownload(id) {
console.log("download")
var text = window['incendiumFlamegraphScript_' + id].innerHTML
var text = window['incendiumFlameGraphScript_' + id].innerHTML
var name = "incendium_flamegraph_" + id + ".js";
var type = "text/javascript";
var a = document.getElementById("downloader-" + id);
......@@ -34,7 +31,7 @@ function incendiumDownload(id) {
// Having embedded flamegraphs that play well with markdown
// is important because it makes it possible to embed them
// in places like ExDoc pages
function incendiumFlamegraph(id, data) {
function incendiumFlameGraph(id, data) {
// Convenience function to make it easier to generate HTML elements from javascript
function h(type, attributes, children) {
var el = document.createElement(type);
......@@ -65,54 +62,53 @@ function incendiumFlamegraph(id, data) {
var detailsId = "details-" + id;
var downloaderId = "downloader-" + id;
var styleSeparate = "margin-right: 1.25em;";
var newElement =
h("div", { class: "incendium" }, [
h("div", { id: chartId, style: "" }, []),
h("div", { id: detailsId, style: "height: 2.15em;" }, []),
h("nav", {}, [
h("div", {}, [
h("div", { style: "float: left" }, [
h("a", { class: "incendium", href: downloadAction, id: downloaderId, style: styleSeparate }, ["Download"]),
h("a", { class: "incendium", href: resetZoomAction, style: styleSeparate }, ["Reset zoom"]),
h("a", { class: "incendium", href: clearAction, style: styleSeparate }, ["Clear"])
]),
h("div", { style: "float: right" }, [
h("form", { class: "incendium", id: formId, style: "display: inline-block important!;" }, [
h("input", { class: "incendium", id: termId }, []),
h("a", { class: "incendium", style: "margin-left: 5px", href: searchAction }, ["Search"])
])
])
// h("a", { class: "incendium", href: downloadAction, id: downloaderId, style: styleSeparate }, ["Download"]),
h("input", { class: "incendium", id: termId }, []),
h("a", { class: "incendium", href: searchAction, style: "margin-left: 0.75em;" }, ["Search"]),
h("a", { class: "incendium", href: clearAction, style: "margin-left: 2em;" }, ["Clear search"]),
h("a", { class: "incendium", href: resetZoomAction, style: "margin-left: 1.25em;" }, ["Reset zoom"])
])
]),
h("div", { id: chartId }, []),
h("div", { id: detailsId }, [])
]);
// Insert the HTML just before the current script
document.currentScript.parentNode.insertBefore(newElement, document.currentScript);
var script = document.currentScript;
script.parentNode.insertBefore(newElement, script);
document.getElementById(termId).addEventListener("keyup", function (event) {
if (event.key === "Enter") {
incendiumSearch(id);
}
})
var flameGraphWidthMultiplier = script.dataset.flameGraphWidthMultiplier || 1.0;
var flameGraphWidth = (script.parentElement.clientWidth || 960) * flameGraphWidthMultiplier;
var flameGraph = d3.flamegraph()
.cellHeight(18)
.transitionDuration(750)
.minFrameSize(5)
.transitionDuration(500)
.width(flameGraphWidth)
.minFrameSize(0)
.transitionEase(d3.easeCubic)
.sort(true)
.title("")
.differential(false)
.selfValue(false);
.selfValue(false)
.tooltip(false)
var details = document.getElementById(detailsId);
flameGraph.setDetailsElement(details);
window["incendiumFlamegraph_" + id] = flameGraph;
window["incendiumFlamegraphScript_" + id] = document.currentScript;
window["incendiumFlameGraph_" + id] = flameGraph;
window["incendiumFlameGraphScript_" + id] = document.currentScript;
d3.select("#" + chartId)
.datum(data)
.call(flameGraph);
document.getElementById(formId).addEventListener("submit", function (event) {
event.preventDefault();
incendiumSearch(id);
});
}
\ No newline at end of file
*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#606c76;font-family:'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#9b4dca;border:0.1rem solid #9b4dca;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#9b4dca;border-color:#9b4dca}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#9b4dca}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#9b4dca}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#9b4dca}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#9b4dca}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #9b4dca;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='color'],input[type='date'],input[type='datetime'],input[type='datetime-local'],input[type='email'],input[type='month'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],input[type='week'],input:not([type]),textarea,select{-webkit-appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem .7rem;width:100%}input[type='color']:focus,input[type='date']:focus,input[type='datetime']:focus,input[type='datetime-local']:focus,input[type='email']:focus,input[type='month']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,input[type='week']:focus,input:not([type]):focus,textarea:focus,select:focus{border-color:#9b4dca;outline:0}select{background:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 8" width="30"><path fill="%23d1d1d1" d="M0,0l6,8l6-8"/></svg>') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 8" width="30"><path fill="%239b4dca" d="M0,0l6,8l6-8"/></svg>')}select[multiple]{background:none;height:auto}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.container{margin:0 auto;max-width:112.0rem;padding:0 2.0rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-40{margin-left:40%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-60{margin-left:60%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#9b4dca;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;display:block;overflow-x:auto;text-align:left;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}@media (min-width: 40rem){table{display:table;overflow-x:initial}}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("d3-collection"),require("d3-selection")):"function"==typeof define&&define.amd?define(["d3-collection","d3-selection"],e):(t.d3=t.d3||{},t.d3.tip=e(t.d3,t.d3))}(this,function(l,i){"use strict";return function(){var u=function(){return"n"},c=function(){return[0,0]},a=function(){return" "},p=document.body,n=t(),r=null,y=null,d=null;function h(t){var e;e=t.node(),(r=e?"svg"===e.tagName.toLowerCase()?e:e.ownerSVGElement:null)&&(y=r.createSVGPoint(),p.appendChild(n))}h.show=function(){var t=Array.prototype.slice.call(arguments);t[t.length-1]instanceof SVGElement&&(d=t.pop());var e,n=a.apply(this,t),r=c.apply(this,t),o=u.