diff --git a/README.md b/README.md index 8b866e7..074c4a7 100644 --- a/README.md +++ b/README.md @@ -2,314 +2,14 @@ [![Deploy Status](http://delphi.midas.cs.cmu.edu/~automation/public/github_deploy_repo/badge.php?repo=cmu-delphi/www-epivis)](#) # About -An interactive tool for visualizing epidemiological time-series data. - -The site is live at http://delphi.midas.cs.cmu.edu/epivis/ - -# Legacy Changelog -```` -=== v39: 2017-12-04 === - ./epivis.html - + source `quidel` - js/epidata.js - + source `quidel` - (by Lisheng) - -=== v38: 2017-02-15 === - ./epivis.html - + 'run regression','reset' features - js/epivis.js - + regression related member functions in Dataset class - js/treeview.js - * return full path for dataset in `getDatasets...()` functions - (by Lisheng) - -=== v37: 2017-02-07 === - ./epivis.html - + source `flusurv` - js/epidata.js - + source `flusurv` - -=== v36: 2016-11-15 === - ./epivis.html - * API update for `stateili` param `version` - js/epidata.js - * API update for `stateili` param `version` - -=== v35: 2016-11-14 === - ./epivis.html - * link to 3rd party libs instead of hosting them - -=== v34: 2016-08-05 === - ./epivis.html - + added description for authorization token under ILINet - (by Paul) - -=== v33: 2016-06-16 === - js/epivis.js - + don't request focus if in an iframe - -=== v32: 2016-04-20 === - js/epivis.js - + don't interpolate missing values (unless enabled) - -=== v31: 2016-04-16 === - ./epivis.html - + location for source `cdc` - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + location for source `cdc` - -=== v30: 2016-04-15 === - ./epivis.html - + YYYYWW epiweek date column - js/csv.js - + YYYYWW epiweek date column - -=== v29: 2016-04-09 === - ./epivis.html - + all locations for `nowcast` - js/epidata.js - * new `nowcast` start week - -=== v28: 2016-04-07 === - ./epivis.html - + sources `cdc` and `sensors` - * createDataset handles different dates - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + sources `cdc` and `sensors` - -=== v27: 2016-04-06 === - ./epivis.html - + source `stateili` - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + source `stateili` - * fixed first `ght` epiweek - -=== v26: 2016-04-01 === - ./epivis.html - + census regions for fluview, ilinet, and twitter - -=== v25: 2016-03-22 === - ./epivis.html - + added screenshot button and function - -=== v24: 2016-02-18 === - ./epivis.html - + param `auth` for source `ilinet` - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + param `auth` for source `ilinet` - -=== v23: 2016-01-29 === - ./epivis.html - + signal `ght` -=== v22: 2015-12-15 === - ./epivis.html - + source `nowcast` - * fixed wording and default value for `fluview` lag parameter - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + source `nowcast` - -=== v21: 2015-12-11 === - ./epivis.html - + source `signals` - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + source `signals` - -=== v20: 2015-12-03 === - ./epivis.html - + source `ght` - css/epivis.css - * increased option_label width - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + source `ght` - -=== v19: 2015-09-18 === - js/epidata.js - + added `value` column for wiki - -=== v18: 2015-09-15 === - ./epivis.html - + ILINet support - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + ILINet support - -=== v17: 2015-08-20 === - ./epivis.html - + NIDSS dengue support - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + NIDSS dengue support - * NIDSS flu update - -=== v16: 2015-08-11 === - ./epivis.html - + NIDSS support - js/delphi_epidata.js - + from https://github.com/undefx/delphi-epidata - js/epidata.js - + NIDSS support - + New wiki field - * wrapper for delphi_epidata.js - -=== v15: 2015-07-31 === - ./epivis.html - + auth token for twitter - js/epidata.js - + auth token for twitter - -=== v14: 2015-07-24 === - ./epivis.html - + parse CSVOptions from CSV comment - + auto load file when CSVOptions present - + bulk add/remove grouped datasets - js/csv.js - + ignore lines starting with '#' - -=== v13: 2015-07-23 === - ./epivis.html - * createDataset requires kernel to be non-null - * fixed preview box wrapping - js/epivis.js - + range check for week in Date.fromEpiweek - * fix (hack) for week 53 in years with 52 weeks - -=== v12: 2015-07-09 === - ./epivis.html - + draw custom line - js/epivis.js - * fixed autoscale with multiple datasets - -=== v11: 2015-07-03 === - ./epivis.html - + meta viewport tag - + meta mobile-web-app-capable tag - + link to manifest.json - + link to icon.png - + new buttons for CSV, API, kernels - + fullscreen support - + android homescreen support - * improved dialog support - * better tooltip positioning - * hide tooltip after button click - * adjustments to top bar - * import via dialog - - "flag" buttons for UI layout - - sample CSV files - ./icon.png - + basic icon for android homescreen - ./manifest.json - + basic manifest for android homescreen - css/epivis.css - * increased size of top bar - * adjusted button look and feel - * changed import color scheme - - bottom section now inside dialog - js/epivis.js - * default dataset lineWidth 1 to 2 - * various linting - -=== v10: 2015-07-02 === - ./epivis.html - + 97 GFT cities - + UI tooltips for top-bar buttons - css/epivis.css - + tooltip class - -=== v9: 2015-06-30 === - ./epivis.html - + experimental overlay/dialog - css/epivis.css - + overlay/dialog classes - -=== v8: 2015-06-24 === - ./epivis.html - + twitter from Epidata API - + wiki from Epidata API - * major improvements to Epidata API - * tentatively enabled multiscale button - css/epivis.css - * some padding for option labels - * tree view width from 200px to 15% - js/epidata.js - + Delphi Epidata API (supporting all data sources) - js/epivis.js - + scale and vertical offset per dataset - + dataset scale by mean - * small tweaks for jsHint - -=== v7: 2015-06-23 === - ./epivis.html - + page title - + more documentation for file loading - + fetch from Epidata API (FluView and GFT) - * better mime type detection (excel types) - * converted double quotes to single quotes - css/epivis.css - + extra classes for documentation - js/csv.js - + export CSV.DataGroup - * small syntax adjustments - -=== v6: 2015-03-24 === - ./epivis.html - + automatic file preview - -=== v5: 2015-03-17 === - ./epivis.html - + kernel: product - js/csv.js - * fixed loading without date column - * fixed loading 1-column CSV - js/epivis.js - + .showPoints - + .isShowingPoints - js/treeview.js - + .getAllDatasets - -=== v4: 2015-01-21 === - ./epivis.html - + kernels: sum and iliplus +An interactive tool for visualizing epidemiological time-series data. -=== v3: 2015-01-20 === - ./changelog.txt - + separate changelog file - ./epivis.html - + link to this changelog - + createDataset (very experimental) - + Kernels (kernel generators) - * fixed randomize button's id - css/epivis.css - + reasonable anchor colors - js/csv.js - * Use Date.fromEpiweek - js/epivis.js - + Date.fromEpiweek - + .toString - js/treeview.js - + .getSelectedDatasets +The site is live at https://delphi.cmu.edu/epivis/epivis.html -=== v2: 2014-12-22 === - + randomize color button - + autoscale button +For development, see the +[EpiVis development guide](docs/epivis_development.md). -=== v1: 2014-12-22 === - * original version -```` +Little piece of trivia: the first version of EpiVis was written on 2014-12-22. +See the git history of this file for a legacy changelog which describes changes +made prior to the migration to git around 2017. diff --git a/deploy.json b/deploy.json index 0943fb7..ae119e2 100644 --- a/deploy.json +++ b/deploy.json @@ -3,19 +3,12 @@ "version": 1, "actions": [ - "// make changelog", - { - "type": "move", - "src": "README.md", - "dst": "site/changelog.txt" - }, - "// web sources", { "type": "move", "src": "site/", "dst": "/var/www/html/epivis/", - "match": "^.*\\.(html|php|txt)$", + "match": "^.*\\.(html|php)$", "add-header-comment": true }, { diff --git a/dev/docker/web/epivis/Dockerfile b/dev/docker/web/epivis/Dockerfile new file mode 100644 index 0000000..1095a42 --- /dev/null +++ b/dev/docker/web/epivis/Dockerfile @@ -0,0 +1,11 @@ +# start with the `delphi_web` image +FROM delphi_web + +# deploy the EpiVis website (see `www-epivis/deploy.json`) +COPY repos/delphi/www-epivis/site /var/www/html/epivis + +# grab the javascript client library for the Epidata API (it's not included in +# this repo) +COPY repos/delphi/delphi-epidata/src/client/delphi_epidata.js /var/www/html/epidata/lib/delphi_epidata.min.js + +RUN chmod o+r /var/www/html/epivis/* /var/www/html/epidata/* diff --git a/dev/docker/web/epivis/README.md b/dev/docker/web/epivis/README.md new file mode 100644 index 0000000..bedc5ed --- /dev/null +++ b/dev/docker/web/epivis/README.md @@ -0,0 +1,13 @@ +# `delphi_web_epivis` + +This image starts with Delphi's web server and adds the sources necessary for +hosting EpiVis, Delphi's epidata visualization website. + +To start a container from this image, run: + +```bash +docker run --rm -p 10080:80 --name delphi_web_epivis delphi_web_epivis +``` + +You should be able to view the site by visiting (or `curl`ing) +http://localhost:10080/epivis/epivis.html. diff --git a/docs/epivis_development.md b/docs/epivis_development.md new file mode 100644 index 0000000..d988b8e --- /dev/null +++ b/docs/epivis_development.md @@ -0,0 +1,82 @@ +# EpiVis Development Guide + +**Prerequisite:** this guide assumes that you have read and followed the +[frontend development guide](https://github.com/cmu-delphi/operations/blob/master/docs/frontend_development.md). + +This guide describes how to run a local instance of the EpiVis website. See +[the Epidata API guide](https://github.com/cmu-delphi/delphi-epidata/blob/main/docs/epidata_development.md) +for much more detail. + +# setup + +For working on EpiVis, you'll need the following Delphi repositories: + +- [operations](https://github.com/cmu-delphi/operations) +- [delphi-epidata](https://github.com/cmu-delphi/delphi-epidata) +- [www-epivis](https://github.com/cmu-delphi/www-epivis) + +Your workspace should look like this after cloning: + +```bash +tree -L 3 . +``` + +``` +. +└── repos + └── delphi + ├── delphi-epidata + ├── operations + └── www-epivis +``` + +# build images + +We now need an image for the EpiVis web server. This will be based on core +Delphi images which are defined in the +[`operations` repo](https://github.com/cmu-delphi/operations) which you cloned +above. The base image is built first, followed by the derived EpiVis image. + +- The [`delphi_web_epivis` image](../dev/docker/web/epivis/README.md) adds + the EpiVis website to the `delphi_web` image. + +From the root of your workspace, the images can be built as follows: + +```bash +docker build -t delphi_web \ + -f repos/delphi/operations/dev/docker/web/Dockerfile . + +docker build -t delphi_web_epivis \ + -f repos/delphi/www-epivis/dev/docker/web/epivis/Dockerfile . +``` + +# run + +At this point you're ready to bring the server online. + +```bash +docker run --rm -p 127.0.0.1:10080:80 \ + --name delphi_web_epivis \ + delphi_web_epivis +``` + +You can view the website and manually test your changes by visiting the +following URL: http://localhost:10080/epivis/epivis.html + +Alternatively, the server sources can be bind-mounted by passing the `--mount` +flag to `docker run`. Note however that this is not without drawbacks. See the +Epicast and Epidata API development guides for discussion of bind-mounting. + +``` +--mount type=bind,source="$(pwd)"/repos/delphi/www-epivis/site,target=/var/www/html/epivis,readonly +``` + +Note that by default the local site will pull live data from the Epidata API. +It's possible to swap out the live API with a local development instance, +however this requires some additional work. At a high level: + +1. start containers `delphi_database_epidata` and `delphi_web_epidata` as + described in the Epidata API development guide +2. edit the Epidata API javascript client, changing the base URL from the + production value (like `delphi.cmu.edu`) to a local value (like `localhost`) +3. rebuild and restart the EpiVis container using the `delphi-net` network diff --git a/site/backup/css/epivis.css b/site/backup/css/epivis.css deleted file mode 100644 index 4d6f03c..0000000 --- a/site/backup/css/epivis.css +++ /dev/null @@ -1,173 +0,0 @@ -html { - height: 100%; - box-sizing: border-box; - font-size: 100%; -} -*, *:before, *:after { - box-sizing: inherit; -} -body { - height: 100%; - margin: 0px; - padding: 0px; - background-color: #222; - color: #ddd; - font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif; - font-size: 0.9em; -} -a:link { - color: #16a; -} -a:visited { - color: #16a; -} -a:hover { - color: #61a; -} -a:active { - color: #61a; -} -h1 { - text-align: center; -} -#top_bar { - text-align: center; - height: 32px; - overflow: hidden; - background-color: #222; - color: #444; -} -#chart_container { - width: 100%; - padding: 0px; - margin: 0px; - height: calc(100% - 32px); -} -#chart_tree { - border-right: 2px solid #000; - padding: 4px; - margin: 0px; - background-color: #fff; - color: #000; - width: 15%; - height: 100%; - float: left; - overflow: auto; -} -#chart_canvas { - padding: 0px; - margin: 0px; - height: 100%; - float: left; -} -#chart_canvas.show_left { - width: 85%; -} -#chart_canvas.hide_left { - width: 100%; -} -#box_overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: #000; - opacity: 0; - cursor: not-allowed; - display: none; -} -div.box_dialog { - position: absolute; - top: 5%; - left: 15%; - color: #222; - background-color: #fff; - width: 70%; - height: 90%; - overflow: auto; - opacity: 0; - border: 1px solid #000; - padding: 16px; - box-shadow: 0px 0px 16px #000; - display: none; -} -div.csv_options { - border-left: 1px solid #666; - padding: 8px; - margin: 16px; -} -div.box_tooltip { - position: absolute; - background-color: #222; - color: #ddd; - display: inline-block; - border: 1px solid #000; - padding: 4px 8px; - box-shadow: 0 0 16px #000; -} -div.ui_flag { - margin-top: 2px; - width: 28px; - height: 28px; - display: inline-block; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: pointer; -} -div.ui_flag_icon { - border-radius: 4px; - padding-top: 4px; - vertical-align: top; - font-size: 1.25em; - color: #000; - background-color: #fff; - transition: background-color 0.2s ease 0s; -} -div.ui_flag_icon:hover { - background-color: #aaa; - transition: background-color 0.2s ease 0s; -} -div.ui_flag_icon_selected { - box-shadow: 0px 0px 8px #000 inset; -} -div.ui_flag_icon_disabled { - color: #300; - background-color: #666; - cursor: not-allowed; - display: none; -} -p.info_text { - display: inline-block; - position: absolute; - top: 0px; - right: 0px; - color: #888; - font-size: 0.8em; - margin: 2px; -} -div.spacer { - line-height: 9px; - margin-top: 2px; - width: 28px; - height: 28px; - padding-top: 10px; - display: inline-block; - font-weight: bold; - vertical-align: top; -} -div.spacer:before { - content: '|'; -} -div.option_label { - display: inline-block; - min-width: 170px; - padding-right: 10px; -} -span.explanation { - font-style: italic; - font-size: 0.85em; - color: #666; -} diff --git a/site/backup/css/treeview.css b/site/backup/css/treeview.css deleted file mode 100644 index 6aa21e4..0000000 --- a/site/backup/css/treeview.css +++ /dev/null @@ -1,18 +0,0 @@ -div.tv_node { - padding: 2px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: pointer; - min-width: 512px; -} -div.tv_node div.tv_node { - margin-left: 16px; -} -div.tv_branch { - font-weight: bold; -} -div.tv_leaf { - font-weight: normal; -} diff --git a/site/backup/epivis.html b/site/backup/epivis.html deleted file mode 100644 index 337850c..0000000 --- a/site/backup/epivis.html +++ /dev/null @@ -1,1795 +0,0 @@ - - - - - EpiVis - - - - - - - - - - - - - - - - - - -

[v38]

-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
- - - -
-
-
- -
- -
-
-

Load from Epidata API

-
(source: cdc.gov) -
-
(source: cdc.gov) -
-
(source: google.com) -
-
(source: private Google API) -
-
(source: healthtweets.org) -
-
(source: dumps.wikimedia.org) -
-
(source: private CDC dataset) -
-
(source: private Quidel dataset) -
-
(source: nidss.cdc.gov.tw) -
-
(source: nidss.cdc.gov.tw) -
-
(source: delphi.midas.cs.cmu.edu) -
-
(source: delphi.midas.cs.cmu.edu) -
-
- -
(region to filter data by) -
-
(fetch the most up-to-date/stable data) -
-
(fetch data "as-of" a specific week) -
-
(fetch data lagged by a number of weeks) -
-
- (this option has no additional parameters) -
-
- - -
- -
(authorization token; required for imputed data for non-participating states; in any case, the API will return data for regions and participating states) -
-
- - - - - -
- -
- -
- - -
- -
- -
- -
-
-

Load from CSV File

- -
- -
-
(treat rows as columns and vice versa) -
-
(extract column names, then discard first row) -
-
(otherwise assume daily starting on 2000-01-01) -
-
(format: YYYY-MM-DD; ex: 2015-05-02) -
-
(format: YYYYWW; ex: 201522) -
-
(format: YYYY, WW; ex: 2015, 22) -
-
(format: YYYY.YYY; ex: 2015.456) -
-
(format: YYYY, MM; ex: 2015, 05) -
(all columns are zero-indexed) -
- -
- - - - -
-
-
(for when multiple overlapping time series are in the same file; ex: FluView Regional data) - -
-
- -
-
-

Add Custom Line

-
(short string) -
-
-
(format is YYYY-MM-DD or YYYYWW) -
-
(any floating point number) -
-
-
(format is YYYY-MM-DD or YYYYWW) -
-
(any floating point number) -
-
Tip: you can make a vertical line by using the same date at both endpoints!
-
- -
-
-

Create a new Dataset

- Select Inputs
- In the tree to the left, select (by checking) the datasets to start with. How many you select depends on the function you want to apply.
-
- Open the developer console
- In chrome, the keyboard shortcut is Ctrl+Shift+J
-
- Call the function createDataset
- The function accepts two parameters: a name for the output dataset, and a function to apply to the selected dataset(s).
-
- Available Functions ("Kernels")
- Kernels.sum, Kernels.product, Kernels.average
- Selected Datasets: 1 or more
- Kernel Parameters: none
- Action: Calculates the sum, product, or average of the selected datasets.
-
- Kernels.iliplus
- Selected Datasets: 2
- Kernel Parameters: none
- Action: Calculates the product, divided by 100, of the selected datasets. (Intended for 'wILI' and '%positive' datasets.)
-
- Kernels.scale
- Selected Datasets: 1
- Kernel Parameters: scale
- Action: Scales the selected dataset by the provided scale parameter.
-
- Kernels.ratio
- Selected Datasets: 2
- Kernel Parameters: inverse (optional)
- Action: Calculates the quotient of the selected datasets. (Flips numerator and denominator if inverse is true.)
-
- Examples
- Compute ILI+: createDataset('ILI+', Kernels.iliplus())
- Scale GFT by 1/1000: createDataset('GFT/1000', Kernels.scale(0.001))
- Take the average: createDataset('avg', Kernels.average()) -
-
-
-

Run regression

- Regression target selection: -
- -
- -
- - - diff --git a/site/backup/icon.png b/site/backup/icon.png deleted file mode 100644 index aa23aaa..0000000 Binary files a/site/backup/icon.png and /dev/null differ diff --git a/site/backup/index.php b/site/backup/index.php deleted file mode 100644 index 6e865e3..0000000 --- a/site/backup/index.php +++ /dev/null @@ -1,5 +0,0 @@ - -Nothing to see here. diff --git a/site/backup/js/animate.js b/site/backup/js/animate.js deleted file mode 100644 index 24cda6e..0000000 --- a/site/backup/js/animate.js +++ /dev/null @@ -1,82 +0,0 @@ -// dfarrow -var Animate = (function() { - var self = {}; - var Task = function(target, fps, duration, ease) { - // setup - if(typeof duration === 'undefined' || duration === null) { - duration = 0; - } - if(typeof ease === 'undefined' || ease === null) { - ease = Animate.Ease.linear; - } - // private variables - var timerID = null; - var startTime = 0; - var lastUpdateTime = 0; - // public methods - var start = function() { - if(this.isRunning()) { - return; - } - lastUpdateTime = startTime = getTime(); - timerID = setTimeout(update, 1000 / fps); - }; - var finish = function(callTarget) { - if(!this.isRunning()) { - return; - } - clearTimeout(timerID); - timerID = null; - if(typeof callTarget === 'undefined' || callTarget !== false) { - target(getTime() - lastUpdateTime, 1); - } - }; - var isRunning = function() { - return timerID !== null; - }; - this.start = start; - this.finish = finish; - this.isRunning = isRunning; - // private methods - function getTime() { - return Date.now() / 1000; - } - function update() { - var time = getTime(); - var dt = time - lastUpdateTime; - if(duration > 0) { - var position = Math.min(1, Math.max(0, (time - startTime) / duration)); - target(dt, ease(position)); - if(isRunning() && position < 1) { - timerID = setTimeout(update, 1000 / fps); - } else { - timerID = null; - } - } else { - target(dt, time - startTime); - if(isRunning()) { - timerID = setTimeout(update, 1000 / fps); - } - } - lastUpdateTime = time; - } - }; - var Ease = { - linear: function(x) { - return x; - }, - sine: function(x) { - return (1 - Math.cos(x * Math.PI)) / 2; - }, - double_sine: function(x) { - return Ease.sine(Ease.sine(x)); - }, - }; - var blend = function(before, after, position) { - return ((1 - position) * before) + (position * after); - }; - self.Task = Task; - self.Ease = Ease; - self.blend = blend; - return self; -})(); diff --git a/site/backup/js/csv.js b/site/backup/js/csv.js deleted file mode 100644 index 81da50f..0000000 --- a/site/backup/js/csv.js +++ /dev/null @@ -1,373 +0,0 @@ -// dfarrow -var CSV = (function() { - var self = {}; - var DateType = { - Simple: 0, - Epi: 1, - Decimal: 2, - Monthly: 3, - Epiweek: 4, - }; - var Options = function() { - var self = {}; - self.transpose = false; - self.hasHeader = false; - self.dateType = DateType.Simple; - self.dateCol1 = 0; - self.dateCol2 = 0; - self.dateFormat = "YYYY-MM-DD"; - self.hasGroup = false; - self.groupColumn = 0; - self.print = function() { - console.log("transpose: " + self.transpose); - console.log("hasHeader: " + self.hasHeader); - console.log("(hasDate): " + (self.dateType !== null)); - console.log("dateType: " + self.dateType); - console.log("dateCol1: " + self.dateCol1); - console.log("dateCol2: " + self.dateCol2); - console.log("dateFormat: " + self.dateFormat); - console.log("hasGroup: " + self.hasGroup); - console.log("groupColumn: " + self.groupColumn); - }; - return self; - }; - Options.DateType = DateType; - var Info = function() { - var self = {}; - self.data = []; - self.numLines = 0; - self.numRows = 0; - self.numCols = 0; - self.numComments = 0; - self.numCommentsAfterHeader = 0; - self.lastComment = 0; - self.print = function() { - console.log("Num lines: " + self.numLines); - console.log("Data size: " + self.numRows + "r x " + self.numCols + "c"); - console.log("Total comments: " + self.numComments); - console.log("Comments after header: " + self.numCommentsAfterHeader); - console.log("Last comment: line #" + self.lastComment); - }; - return self; - }; - var DataGroup = function(title, data) { - var self = {}; - self.getTitle = function() { return title; }; - self.getData = function() { return data; }; - self.group = true; - return self; - }; - var read = function(title, file, options) { - var info = new Info(); - var lines = []; - var numColumns = 0; - file = file.split("\n"); - for(var i = 0; i < file.length; i++) { - var line = file[i].trim(); - if(line[0] !== '#') { - lines.push(line); - numColumns = Math.max(numColumns, split(line).length); - } - } - if(options.transpose) { - lines = transpose(lines, lines.length, numColumns); - numColumns = 0; - for(var i = 0; i < lines.length; i++) { - var line = lines[i]; - numColumns = Math.max(numColumns, split(line).length); - } - } - info.numLines = lines.length; - var numRows = 0; - var foundHeader = false; - var labels = new Array(numColumns); - for(var i = 0, lineNum = 1; i < lines.length; i++, lineNum++) { - var line = lines[i]; - var values = split(line); - if(values.length == numColumns) { - if(options.hasHeader && !foundHeader) { - //Header - var j = 0; - for(var col = 0; col < values.length; col++) { - if(isDateColumn(options, col)) { - continue; - } - labels[j++] = values[col]; - } - foundHeader = true; - lines.splice(i--, 1); - } else { - ++numRows; - } - } else if(values.length == 1 && values[0][0] == "+") { - //group - } else { - lines.splice(i--, 1); - info.numComments++; - if(foundHeader) { - info.numCommentsAfterHeader++; - } - info.lastComment = lineNum; - } - } - if(options.hasGroup) { - lines = groupBy(options, lines); - } - var data = parseAll(options, labels, lines, 0); - if(data.length === 0) { - throw "Nothing loaded"; - } - info.numCols = numColumns; - info.numRows = numRows; - info.data = new DataGroup(title, data); - info.print(); - return info; - }; - var transpose = function(input, numRows, numCols) { - var data = new Array(numRows); - for(var i = 0; i < data.length; i++) { - data[i] = new Array(numCols); - } - for(var row = 0; row < numRows; row++) { - var values = split(input[row]); - for(var col = 0; col < values.length; col++) { - data[row][col] = values[col]; - } - } - var output = []; - for(var col = 0; col < numCols; col++) { - var line = ""; - for(var row = 0; row < numRows; row++) { - line += ","; - if(data[row][col] !== null) { - line += "\"" + data[row][col] + "\""; - } - } - output.push(line.substring(1)); - } - return output; - }; - var groupBy = function(options, input) { - var output = []; - var group = null; - while(input.length > 0) { - var values = split(input[0]); - group = values[options.groupColumn]; - output.push("\"+" + group + "\""); - for(var i = 0; i < input.length; i++) { - values = split(input[i]); - if(group == values[options.groupColumn]) { - output.push(input.splice(i--, 1)[0]); - } - } - } - return output; - }; - var parseAll = function(options, columns, lines, level) { - var datasets = []; - var subLines = getDataLines(lines); - if(subLines.length > 0) { - var temp = parseGroup(options, columns, subLines); - for(var i = 0; i < temp.length; i++) { - datasets.push(temp[i]); - } - } - while(lines.length > 0) { - var values = split(lines[0]); - if(values.length == 1 && values[0][0] == "+") { - var title = values[0].replace("+", ""); - var newLevel = values[0].lastIndexOf("+") + 1; - if(newLevel > level) { - lines.splice(0, 1); - var subsets = parseAll(options, columns, lines, newLevel); - var data = new DataGroup(title, subsets); - datasets.push(data); - } else { - break; - } - } else { - throw "Expected a group"; - } - } - return datasets; - }; - var getDataLines = function(lines) { - var dataLines = []; - for(var i = 0; i < lines.length; i++) { - var values = split(lines[i]); - if(values.length == 1 && values[0][0] == "+") { - break; - } - dataLines.push(lines.splice(i--, 1)[0]); - } - return dataLines; - }; - var parseGroup = function(options, columns, lines) { - var numColumns = columns.length; - if(options.dateType !== null) { - switch(options.dateType) { - case Options.DateType.Simple: - numColumns -= 1; - break; - case Options.DateType.Decimal: - case Options.DateType.Epiweek: - numColumns -= 1; - break; - case Options.DateType.Epi: - case Options.DateType.Monthly: - numColumns -= 2; - break; - } - } - var datas = new Array(numColumns); - for(var i = 0; i < datas.length; i++) { - datas[i] = new Array(lines.length); - } - var index = 0; - for(var lineIndex = 0; lineIndex < lines.length; lineIndex++) { - var line = lines[lineIndex]; - var values = split(line); - //Data - var j = 0; - for(var i = 0; i < values.length; i++) { - if(isDateColumn(options, i)) { - continue; - } - //Date - var date; - var value; - try { - if(options.dateType !== null) { - switch(options.dateType) { - case Options.DateType.Simple: { - var m = moment(values[options.dateCol1], options.dateFormat); - date = new EpiVis.Date(m.year(), m.month() + 1, m.date()); - } - break; - case Options.DateType.Epiweek: { - var ew = parseInt(values[options.dateCol1], 10); - var year = Math.floor(ew / 100); - var week = ew % 100; - date = EpiVis.Date.fromEpiweek(year, week); - } - break; - case Options.DateType.Epi: { - var year = parseInt(values[options.dateCol1], 10); - var week = parseInt(values[options.dateCol2], 10); - date = EpiVis.Date.fromEpiweek(year, week); - } - break; - case Options.DateType.Decimal: { - var y = parseFloat(values[options.dateCol1]); - var year = Math.floor(y); - var days = Math.floor((y - year) * 365.25); - date = new EpiVis.Date(year, 1, 1).addDays(days); - } - break; - case Options.DateType.Monthly: { - var year = parseInt(values[options.dateCol1], 10); - var month = parseInt(values[options.dateCol2], 10); - date = new EpiVis.Date(year, month, 15); - } - break; - } - } else { - date = new EpiVis.Date(2000, 1, 1).addDays(lineIndex); - } - } catch(e) { - date = null; - } - //Value - value = parseFloat(values[i]); - if(isNaN(value)) { - value = 0; - } - datas[j][index] = new EpiVis.Point(date, value); - j++; - } - index++; - } - var datasets = []; - for(var i = 0; i < datas.length; i++) { - var title = (typeof columns[i] !== "undefined") ? (columns[i] + " (col " + (i + 1) + ")") : ("Column " + i); - var ds = new EpiVis.Dataset(datas[i], title); - datasets.push(ds); - } - return datasets; - }; - var isDateColumn = function(options, column) { - if(options.dateType !== null) { - switch(options.dateType) { - case Options.DateType.Simple: { - return column == options.dateCol1; - } - case Options.DateType.Epiweek: { - return column == options.dateCol1; - } - case Options.DateType.Epi: { - return column == options.dateCol1 || column == options.dateCol2; - } - case Options.DateType.Decimal: { - return column == options.dateCol1; - } - case Options.DateType.Monthly: { - return column == options.dateCol1 || column == options.dateCol2; - } - } - } - return false; - }; - var split = function(line) { - var tokenizer = new Tokenizer(line); - var fields = []; - var value; - while((value = tokenizer.next()) !== null) { - fields.push(value); - } - return fields; - }; - var Tokenizer = function(line) { - var str = line; - var i = 0; - var self = {}; - self.next = function() { - if(str === null) { - return null; - } - if(i >= str.length) { - str = null; - return ""; - } - var first = str[i]; - var quote = (first === "\""); - if(quote) { - var end = str.indexOf("\"", i + 1); - var token = str.substring(i + 1, end); - i = end + 2; - if(str.indexOf(",", end + 1) < 0) { - str = null; - } - return token; - } else { - var done = false; - var end = str.indexOf(",", i); - if(end < 0) { - end = str.length; - done = true; - } - var token = str.substring(i, end); - i = end + 1; - if(done) { - str = null; - } - return token; - } - }; - return self; - }; - self.Options = Options; - self.Info = Info; - self.DataGroup = DataGroup; - self.read = read; - return self; -})(); diff --git a/site/backup/js/epidata.js b/site/backup/js/epidata.js deleted file mode 100644 index 9fb88c3..0000000 --- a/site/backup/js/epidata.js +++ /dev/null @@ -1,134 +0,0 @@ -// Wrapper for delphi_epidata.js -var Epidata = (function() { - // The API - var api = Epidata; - // first available epiweek for each data source - var first_epiweek = { - 'fluview': 199740, - 'flusurv': 200340, - 'gft': 200340, - 'ght': 200401, - 'twitter': 201148, - 'wiki': 200750, - 'nidss_flu': 200814, - 'nidss_dengue': 200301, - 'cdc': 201301, - 'quidel': 201535, - 'sensors': 201030, - 'nowcast': 200901, - }; - // first available date for each data source - var first_date = { - 'twitter': 20111201, - 'wiki': 20071209, - }; - // find the current epiweek and date - var date = new Date(); - var epidate = new EpiVis.Date(date.getYear() + 1900, date.getMonth() + 1, date.getDate()); - var current_epiweek = (epidate.getEpiYear() * 100) + epidate.getEpiWeek(); - var current_date = (epidate.getYear() * 10000) + (epidate.getMonth() * 100) + epidate.getDay(); - // generic epidata loader - function loadEpidata(epidata, columns) { - var datasets, points, row, col, year, week, date; - datasets = []; - for(col = 0; col < columns.length; col++) { - points = []; - for(row = 0; row < epidata.length; row++) { - if(epidata[row].hasOwnProperty('date')) { - date = EpiVis.Date.parse(epidata[row].date); - } else if(epidata[row].hasOwnProperty('epiweek')) { - year = Math.floor(epidata[row].epiweek / 100); - week = epidata[row].epiweek % 100; - date = EpiVis.Date.fromEpiweek(year, week); - } else { - throw {'msg': 'missing column "date" and "epiweek"'}; - } - points.push(new EpiVis.Point(date, epidata[row][columns[col]])); - } - datasets.push(new EpiVis.Dataset(points, columns[col])); - } - return datasets; - } - // generic API access - function handleData(onSuccess, onFailure, columns, result, message, epidata) { - if(result <= 0) { - // hard failure (can't continue) - onFailure('There was an error fetching the data. [result=' + result + ']'); - } - if(result === 2) { - // soft failure (can continue) - onFailure('Too many results, data truncated.'); - } - if(result >= 1) { - // success - onSuccess(loadEpidata(epidata, columns)); - } - } - // generic callback generator - function getCallback(onSuccess, onFailure, columns) { - return function(result, message, epidata) { - handleData(onSuccess, onFailure, columns, result, message, epidata); - }; - } - // public API - return { - fetchFluView: function(onSuccess, onFailure, region, issue, lag, auth) { - var columns = ['wili', 'ili', 'num_ili', 'num_patients', 'num_providers', 'num_age_0', 'num_age_1', 'num_age_2', 'num_age_3', 'num_age_4', 'num_age_5']; - api.fluview(getCallback(onSuccess, onFailure, columns), region, [api.range(first_epiweek.fluview, current_epiweek)], issue, lag, auth); - }, - fetchFluSurv: function(onSuccess, onFailure, location, issue, lag) { - var columns = ['rate_age_0', 'rate_age_1', 'rate_age_2', 'rate_age_3', 'rate_age_4', 'rate_overall']; - api.flusurv(getCallback(onSuccess, onFailure, columns), location, [api.range(first_epiweek.flusurv, current_epiweek)], issue, lag); - }, - fetchGFT: function(onSuccess, onFailure, location) { - var columns = ['num']; - api.gft(getCallback(onSuccess, onFailure, columns), location, [api.range(first_epiweek.gft, current_epiweek)]); - }, - fetchGHT: function(onSuccess, onFailure, auth, location, query) { - var columns = ['value']; - api.ght(getCallback(onSuccess, onFailure, columns), auth, location, [api.range(first_epiweek.ght, current_epiweek)], query); - }, - fetchTwitter: function(onSuccess, onFailure, auth, location, resolution) { - var columns = ['num', 'total', 'percent']; - var callback = getCallback(onSuccess, onFailure, columns); - if(resolution === 'daily') { - api.twitter(callback, auth, location, [api.range(first_date.twitter, current_date)], null); - } else { - api.twitter(callback, auth, location, null, [api.range(first_epiweek.twitter, current_epiweek)]); - } - }, - fetchWiki: function(onSuccess, onFailure, article, resolution, hour) { - var columns = ['count', 'total', 'value']; - var callback = getCallback(onSuccess, onFailure, columns); - if(resolution === 'daily') { - api.wiki(callback, article, [api.range(first_date.wiki, current_date)], null, hour); - } else { - api.wiki(callback, article, null, [api.range(first_epiweek.wiki, current_epiweek)], hour); - } - }, - fetchCDC: function(onSuccess, onFailure, auth, location) { - var columns = ['total', 'num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7', 'num8']; - api.cdc(getCallback(onSuccess, onFailure, columns), auth, [api.range(first_epiweek.cdc, current_epiweek)], location); - }, - fetchQuidel: function(onSuccess, onFailure, auth, location) { - var columns = ['value']; - api.quidel(getCallback(onSuccess, onFailure, columns), auth, [api.range(first_epiweek.quidel, current_epiweek)], location); - }, - fetchNIDSS_flu: function(onSuccess, onFailure, region, issue, lag) { - var columns = ['visits', 'ili']; - api.nidss_flu(getCallback(onSuccess, onFailure, columns), region, [api.range(first_epiweek.nidss_flu, current_epiweek)], issue, lag); - }, - fetchNIDSS_dengue: function(onSuccess, onFailure, location) { - var columns = ['count']; - api.nidss_dengue(getCallback(onSuccess, onFailure, columns), location, [api.range(first_epiweek.nidss_dengue, current_epiweek)]); - }, - fetchSensors: function(onSuccess, onFailure, auth, name, location) { - var columns = ['value']; - api.sensors(getCallback(onSuccess, onFailure, columns), auth, name, location, [api.range(first_epiweek.sensors, current_epiweek)]); - }, - fetchNowcast: function(onSuccess, onFailure, location) { - var columns = ['value', 'std']; - api.nowcast(getCallback(onSuccess, onFailure, columns), location, [api.range(first_epiweek.nowcast, current_epiweek)]); - }, - }; -}()); diff --git a/site/backup/js/epivis.js b/site/backup/js/epivis.js deleted file mode 100644 index 43d21ac..0000000 --- a/site/backup/js/epivis.js +++ /dev/null @@ -1,1040 +0,0 @@ -// dfarrow -var EpiVis = (function() { - var self = {}; - var Date = function(year, month, day) { - // private static constants - var DAYS_PER_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - var CUMULATIVE_DAYS_PER_MONTH = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; - var DAY_OF_WEEK_TABLE = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]; - // private static functions - var getIndex = function(year, month, day) { - var y = (year + 399) % 400; - var yearOffset = (Math.floor((year - 1) / 400) * 146097) + (y * 365) + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400); - var monthOffset = CUMULATIVE_DAYS_PER_MONTH[month - 1] + (((month > 2) && Date.isLeapYear(year)) ? 1 : 0); - var dayOffset = day - 1; - return yearOffset + monthOffset + dayOffset; - }; - var getDayOfWeek = function(year, month, day) { - var y = year - (month < 3 ? 1 : 0); - return (y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) + DAY_OF_WEEK_TABLE[month - 1] + day) % 7; - }; - // constructor logic - if(year < 1 || month < 1 || month > 12 || day < 1 || day > DAYS_PER_MONTH[month - 1] + ((month == 2 && Date.isLeapYear(year)) ? 1 : 0)) { - throw {message: "invalid date", year: year, month: month, day: day}; - } - // public methods - this.toString = function() { return year + '/' + month + '/' + day; }; - this.getYear = function() { return year; }; - this.getMonth = function() { return month; }; - this.getDay = function() { return day; }; - this.isLeapYear = function() { return Date.isLeapYear(year); }; - this.getIndex = function() { return getIndex(year, month, day); }; - this.getDayOfWeek = function() { return getDayOfWeek(year, month, day); }; - this.getEpiYear = function() { - var thisDate = getIndex(year, month, day); - var firstDate1 = getIndex(year, 1, 4) - getDayOfWeek(year, 1, 4); - var y; - if(thisDate < firstDate1) { - y = -1; - } else { - var firstDate2 = getIndex(year + 1, 1, 4) - getDayOfWeek(year + 1, 1, 4); - y = ((thisDate < firstDate2) ? 0 : 1); - } - return year + y; - }; - this.getEpiWeek = function() { - var thisDate = getIndex(year, month, day); - var firstDate1 = getIndex(year, 1, 4) - getDayOfWeek(year, 1, 4); - var firstDate; - if (thisDate < firstDate1) { - firstDate = getIndex(year - 1, 1, 4) - getDayOfWeek(year - 1, 1, 4); - } else { - var firstDate2 = getIndex(year + 1, 1, 4) - getDayOfWeek(year + 1, 1, 4); - firstDate = (thisDate < firstDate2) ? firstDate1 : firstDate2; - } - return Math.floor((thisDate - firstDate) / 7) + 1; - }; - this.addDays = function(num) { - return Date.fromIndex(this.getIndex() + num); - }; - this.addWeeks = function(num) { - return Date.fromIndex(this.getIndex() + 7 * num); - }; - this.addMonths = function(num) { - num = Math.floor(num); - var y = this.getYear(); - var m = this.getMonth(); - var sign = num >= 0 ? +1 : -1; - num *= sign; - var yy = Math.floor(num / 12); - var mm = num % 12; - y += sign * yy; - m += sign * mm; - if(m > 12) { - m -= 12; - y++; - } - if(m < 1) { - m += 12; - y--; - } - return new Date(y, m, 1); - }; - this.addYears = function(num) { - return new Date(this.getYear() + num, 1, 1); - }; - }; - Date.isLeapYear = function(year) { - return (((year % 4) === 0) && ((year % 100) !== 0)) || ((year % 400) === 0); - }; - Date.parse = function(str) { - var y, m, d; - if(str.length === 8) { - y = parseInt(str.substring(0, 4), 10); - m = parseInt(str.substring(4, 6), 10); - d = parseInt(str.substring(6, 8), 10); - } else if(str.length === 10) { - y = parseInt(str.substring(0, 4), 10); - m = parseInt(str.substring(5, 7), 10); - d = parseInt(str.substring(8, 10), 10); - } else { - throw {message: "unknown date format (try YYYYMMDD)", str: str}; - } - return new Date(y, m, d); - }; - Date.fromIndex = function(index) { - var CUMULATIVE_DAYS_PER_MONTH = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; - var x = index; - var year = Math.floor(index / 365); - index = index % 365; - var leaps = Math.floor(year / 4) - Math.floor(year / 100) + Math.floor(year / 400); - index = index - leaps + year * 365; - year = Math.floor(index / 365) + 1; - var y = (year + 399) % 400; - var yearOffset = (Math.floor((year - 1) / 400) * 146097) + (y * 365) + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400); - if((x - yearOffset) == 365 && Date.isLeapYear(year + 1)) { - ++year; - yearOffset += 365; - } - index = x - yearOffset; - var m = 1; - var leap = Date.isLeapYear(year) ? 1 : 0; - while(m < 12 && (CUMULATIVE_DAYS_PER_MONTH[m] + ((m >= 2) ? leap : 0)) <= index) { - ++m; - } - index -= CUMULATIVE_DAYS_PER_MONTH[m - 1] + (((m > 2) && Date.isLeapYear(year)) ? 1 : 0); - return new Date(year, m, index + 1); - }; - Date.fromEpiweek = function(year, week) { - if(week < 1 || week > 54) { - throw {message: "invalid epiweek", year: year, week: week}; - } - var date = new EpiVis.Date(year, 6, 1); - while(date.getEpiYear() < year) { date = date.addYears(+1); } - while(date.getEpiYear() > year) { date = date.addYears(-1); } - while(date.getEpiWeek() < week) { date = date.addWeeks(+1); } - while(date.getEpiWeek() > week) { date = date.addWeeks(-1); } - while(date.getDayOfWeek() < 3) { date = date.addDays(+1); } - while(date.getDayOfWeek() > 3) { date = date.addDays(-1); } - if(date.getEpiYear() !== year || date.getEpiWeek() !== week) { - // fix for week 53 in years with only 52 weeks - console.log('warning: invalid epiweek', year, week); - date = new EpiVis.Date(year, 12, 31); - } - return date; - }; - Date.getDayName = function(dayOfWeek, longName) { - var DAY_NAMES_LONG = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; - var DAY_NAMES_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - if(typeof longName === 'undefined' || longName) { - return DAY_NAMES_LONG[dayOfWeek]; - } else { - return DAY_NAMES_SHORT[dayOfWeek]; - } - }; - Date.getMonthName = function(month, longName) { - var MONTH_NAMES_LONG = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; - var MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - if(typeof longName === 'undefined' || longName) { - return MONTH_NAMES_LONG[month - 1]; - } else { - return MONTH_NAMES_SHORT[month - 1]; - } - }; - var Point = function(date, value) { - this.getDate = function() { return date; }; - this.getValue = function() { return value; }; - }; - var Dataset = function(data, title) { - function getComponent() { - var x = 0x20 + Math.floor(Math.random() * 0xC0); - return ("0" + x.toString(16)).slice(-2); - } - function getRandomColor() { - return "#" + getComponent() + getComponent() + getComponent(); - } - var self = {}; - self.data = data; - self.title = (typeof title === 'undefined') ? '' : title; - self.color = getRandomColor(); - self.lineWidth = 2; - self.scale = 1; - self.verticalOffset = 0; - self.horizontalOffset = 0; - var a, b, i, gaps = []; - for(i = 1; i < data.length; i++) { - a = data[i - 1].getDate().getIndex(); - b = data[i].getDate().getIndex(); - gaps.push(b - a); - } - gaps.sort(function(p, q){return p - q;}); - self.gap = gaps.length > 0 ? gaps[Math.floor(gaps.length / 2)] : 1; - self.randomize = function() { - self.color = getRandomColor(); - }; - self.reset = function() { - self.scale = 1; - self.verticalOffset = 0; - self.horizontalOffset = 0; - }; - self.scaleMean = function() { - var i, value, num = 0, sum = 0; - for(i = 0; i < self.data.length; i++) { - value = self.data[i].getValue(); - if(!isNaN(value)) { - num++; - sum += value; - } - } - if(num > 0) { - self.scaleAndShift(1 / (sum / num), 0); - } - }; - - self.scaleAndShift = function(scaler, shift){ - self.scale = scaler; - self.verticalOffset = shift; - }; - - self.getDataHash = function(){ - var dataHash = {}; - for(i = 0; i < self.data.length; i++) { - value = self.data[i].getValue(); - key = self.data[i].getDate(); - if(!isNaN(value)) { - dataHash[key]=value; - } - } - return dataHash; - }; - - self.regress = function(dataset){ - var datahash = dataset.getDataHash(); - var x = []; - var y = []; - for(i = 0; i < self.data.length; i++) { - value = self.data[i].getValue(); - key = self.data[i].getDate(); - if(!isNaN(value) && key in datahash) { - x.push(value); - y.push(datahash[key]); - } - } - // Run regression - var x_sum = x.reduce(function(a, b) { return a + b; }); - var y_sum = y.reduce(function(a, b) { return a + b; }); - var x_avg = x_sum / x.length; - var y_avg = y_sum / y.length; - var cov_sum = 0; - var x_var_sum = 0; - for(i = 0; i < x.length; i++) { - cov_sum += x[i]*y[i]-x_avg*y_avg; - x_var_sum += Math.pow(x[i], 2) - Math.pow(x_avg, 2); - } - var beta = cov_sum/x_var_sum; - var alpha = y_avg - beta*x_avg; - self.scaleAndShift(beta*dataset.scale,alpha*dataset.scale); - }; - return self; - }; - var NavMode = { - pan: 0, - crop: 1, - zoom: 2, - }; - var Chart = function(canvasID, listener) { - listener = (typeof listener === 'undefined') ? null : listener; - var Align = { - left: 0, - right: 1, - bottom: 2, - top: 3, - center: 4 - }; - var canvas = $("#" + canvasID); - var chart = canvas[0]; - var self = this; - var buttons = []; - var ctx = chart.getContext("2d"); - var datasets = []; - var demoAnimation = null; - var xMin = new Date(2014, 1, 1).getIndex(), xMax = new Date(2016, 1, 1).getIndex(), yMin = -1, yMax = 1; - var highlightedDate = null; - var animation = null; - var navMode = NavMode.pan; - var navBox = null; - var forceZoom = false; - var forceCrop = false; - var showPoints = false; - var interpolate = false; - var mouse = { - pressed: false, - dragging: false, - hovering: false, - position: null, - down: null, - }; - function consumeEvent(e, handler) { - e.preventDefault(); - handler(e); - } - function mousePosition(e) { - if(e.type.toLowerCase().indexOf('touch') === 0) { - e = e.originalEvent.changedTouches[0]; - } - return { - x: e.pageX - canvas.offset().left, - y: e.pageY - canvas.offset().top - }; - } - function mouseDown(e) { - var m = mousePosition(e); - mouse.pressed = true; - mouse.dragging = false; - mouse.hovering = false; - mouse.position = m; - mouse.down = m; - if(navMode == NavMode.crop) { - navBox = {x: m.x, y: m.y, w: 0, h: 0}; - } - } - function mouseMove(e) { - var m = mousePosition(e); - mouse.dragging = mouse.pressed; - mouse.hovering = !mouse.pressed; - if(mouse.dragging) { - var deltaX = -m.x + mouse.position.x; - var deltaY = +m.y - mouse.position.y; - if(navMode == NavMode.pan) { - var dx = deltaX; - var dy = deltaY; - var x = (dx / chart.width) * (xMax - xMin); - var y = (dy / chart.height) * (yMax - yMin); - self.setViewport(xMin + x, yMin + y, xMax + x, yMax + y); - } else if(navMode == NavMode.zoom) { - var scale = 4 / ((chart.width + chart.height) / 2); - var dx = deltaX * scale; - var dy = deltaY * scale; - var zx = (dx >= 0) ? (1 / (1 + dx)) : (1 + Math.abs(dx)); - var zy = (dy >= 0) ? (1 / (1 + dy)) : (1 + Math.abs(dy)); - self.zoom(zx, zy); - } else if(navMode == NavMode.crop) { - navBox.w += -deltaX; - navBox.h += +deltaY; - } - } - if(mouse.hovering) { - self.highlight(Date.fromIndex(Math.floor(x2date(m.x) + 0.5))); - } - mouse.position = m; - self.render(); - } - function mouseUp(e) { - var m = mousePosition(e); - var m1 = mouse.down; - var m2 = m; - mouse.pressed = false; - mouse.dragging = false; - mouse.hovering = true; - mouse.position = m; - mouse.down = null; - if(m1 !== null) { - mouseClick(m1, m2); - } - if(navBox !== null) { - var box = navBox; - navBox = null; - if(navMode == NavMode.crop) { - var x = Math.min(box.x, box.x + box.w); - var y = Math.max(box.y, box.y + box.h); - var w = +Math.abs(box.w); - var h = -Math.abs(box.h); - if(Math.abs(w) * Math.abs(h) >= 50) { - self.setViewport(x2date(x), y2value(y), x2date(x + w), y2value(y + h)); - } else { - console.log("Crop box was very small, this was probably unintended. If you really want to apply the crop, use the following command:"); - console.log(" chart.setViewport(" + x2date(x) + ", " + y2value(y) + ", " + x2date(x + w) + ", " + y2value(y + h) + ");"); - } - } - } - self.setNavMode(NavMode.pan); - } - function mouseOver(e) { - canvas.focus(); - } - function mouseOut(e) { - navBox = null; - var m = mousePosition(e.originalEvent); - mouseUp(e.originalEvent); - mouse.hovering = false; - self.highlight(null); - canvas.blur(); - } - function mouseClick(m1, m2) { - var hit = false; - for(var i = 0; i < buttons.length; i++) { - if(contains(buttons[i].box, m1) && contains(buttons[i].box, m2)) { - hit = true; - buttons[i].handler(); - } - } - if(hit) { - self.render(); - } - } - function keyDown(e) { - if(e.keyCode == 16) { - forceCrop = true; - self.setNavMode(NavMode.crop); - } else if(e.keyCode == 17) { - forceZoom = true; - self.setNavMode(NavMode.zoom); - } - } - function keyUp(e) { - if(e.keyCode == 16) { - forceCrop = false; - } else if(e.keyCode == 17) { - forceZoom = false; - } - self.setNavMode(NavMode.pan); - } - function mouseWheel(e) { - // scroll info - var mode = e.originalEvent.deltaMode; - var delta = e.originalEvent.deltaY; - var ticks = Math.abs(delta); - var amount; - // zoom calculation - if(mode === 0) { - // DOM_DELTA_PIXEL - amount = 1 + ticks / 500; - } else if(mode == 1) { - // DOM_DELTA_LINE - amount = 1 + ticks / 10; - } else if(mode == 2) { - // DOM_DELTA_PAGE - amount = 1 + ticks / 2; - } else { - // unknown mode - amount = 1.5; - } - if(delta > 0) { - amount = 1 / amount; - } - // pan so that the zoom is centered under the mouse - var m = mousePosition(e.originalEvent); - var dx, dy; - var cx = (xMin + xMax) / 2; - var cy = (yMin + yMax) / 2; - var fx = +(m.x - chart.width / 2) / (chart.width / 2); - var fy = -(m.y - chart.height / 2) / (chart.height / 2); - dx = ((xMax - xMin) / 2) * fx; - dy = ((yMax - yMin) / 2) * fy; - self.setViewport(xMin + dx, yMin + dy, xMax + dx, yMax + dy); - // zoom - self.zoom(amount, amount); - // pan back to the original location - dx = ((xMax - xMin) / 2) * fx; - dy = ((yMax - yMin) / 2) * fy; - self.setViewport(xMin - dx, yMin - dy, xMax - dx, yMax - dy); - self.render(); - } - function addButton(box, handler) { - buttons.push({box: box, handler: handler}); - } - function contains(box, point) { - return box.x <= point.x && point.x <= (box.x + box.w) && box.y < point.y && point.y < (box.y + box.h); - } - function date2x(date) { - return ((date - xMin) / (xMax - xMin)) * chart.width; - } - function x2date(x) { - return xMin + ((x / chart.width) * (xMax - xMin)); - } - function value2y(value) { - return (1 - ((value - yMin) / (yMax - yMin))) * chart.height; - } - function y2value(y) { - return yMin - (((y / chart.height) - 1) * (yMax - yMin)); - } - function drawText(str, x, y, angle, alignX, alignY, scale, font) { - scale = typeof scale !== 'undefined' ? scale : 1; - font = typeof font !== 'undefined' ? font : 'Calibri'; - var size = Math.round(12 * scale); - ctx.font = size + 'px ' + font; - var w = ctx.measureText(str).width; - var h = size; - var dx = 0; - var dy = 0; - if(alignX == Align.left) { - dx = 0; - } else if(alignX == Align.right) { - dx = -w; - } else if(alignX == Align.center) { - dx = -w / 2; - } else { - ctx.fillStyle = '#f00'; - } - if(alignY == Align.bottom) { - dy = 0; - } else if(alignY == Align.top) { - dy = h; - } else if(alignY == Align.center) { - dy = h / 2; - } else { - ctx.fillStyle = '#f00'; - } - ctx.save(); - ctx.translate(x, y); - ctx.rotate(angle); - ctx.fillText(str, dx, dy); - ctx.restore(); - return {x: x + dx, y: y + dy - h, w: w, h: h}; - } - function zeroPad(num) { - return (num < 10) ? ("0" + num) : ("" + num); - } - function prepareAnimation() { - if(animation !== null && animation.isRunning()) { - animation.finish(false); - } - } - function animateDateRange(x1, x2) { - var d1 = x2date(x1); - var d2 = x2date(x2); - var cx = (d1 + d2) / 2; - var dx = (d2 - d1) * 1.25; - return self.animatePan(cx - (dx / 2), yMin, cx + (dx / 2), yMax); - } - function getPointValue(dataset, index) { - var temp = dataset.data[index].getValue(); - if(isNaN(temp)) { - return temp; - } - return dataset.verticalOffset + (temp * dataset.scale); - } - this.animatePan = function(x1, y1, x2, y2) { - var before = { - x1: xMin, - y1: yMin, - x2: xMax, - y2: yMax, - }; - var after = { - x1: x1, - y1: y1, - x2: x2, - y2: y2, - }; - return function() { - function update(dt, a) { - var b = 1 - a; - self.setViewport( - b * before.x1 + a * after.x1, - b * before.y1 + a * after.y1, - b * before.x2 + a * after.x2, - b * before.y2 + a * after.y2 - ); - } - prepareAnimation(); - animation = new Animate.Task(update, 60, 0.75, Animate.Ease.double_sine); - animation.start(); - }; - }; - this.render = function() { - // reset buttons - buttons = []; - // clear the chart - ctx.lineWidth = 1; - ctx.fillStyle = "#fff"; - ctx.fillRect(0, 0, chart.width, chart.height); - if(datasets.length === 0) { - // no data - return; - } - var daysPerPixel = (xMax - xMin) / chart.width; - //console.log(daysPerPixel); - var labels = { - day_long: (daysPerPixel < 0.010), - day_short: (daysPerPixel < 0.02), - day_num: (daysPerPixel < 0.04), - day_blank: (daysPerPixel < 0.08), - epiweek_long: (daysPerPixel < 0.12), - epiweek_short: (daysPerPixel < 0.20), - epiweek_blank: (daysPerPixel < 0.50), - month_long: (daysPerPixel < 0.38), - month_short: (daysPerPixel < 1.20), - month_blank: (daysPerPixel < 3.00), - year_long: (daysPerPixel < 8.00), - year_short: (daysPerPixel < 15.00), - year_blank: (daysPerPixel < 25.00), - }; - // x-axis - var interval = Math.pow(2, Math.floor(Math.log((yMax - yMin) / 4) / Math.log(2))); - ctx.fillStyle = "#888"; - ctx.strokeStyle = "#ddd"; - ctx.beginPath(); - for(var i = Math.ceil(yMin / interval); i <= Math.floor(yMax / interval); i++) { - var y = value2y(i * interval); - ctx.moveTo(0, y); - ctx.lineTo(chart.width, y); - drawText("" + (i * interval), 0, y - 2, 0, Align.left, Align.bottom); - } - if(mouse.position !== null) { - var y = mouse.position.y; - var value = y2value(mouse.position.y); - ctx.moveTo(0, y); - ctx.lineTo(chart.width, y); - drawText("" + value, chart.width - 2, y - 2, 0, Align.right, Align.bottom); - } - ctx.stroke(); - // y-axis - var firstDay = Date.fromIndex(Math.floor(xMin)); - var lastDay = Date.fromIndex(Math.ceil(xMax)); - var day; - function drawLabel(label, x1, x2, y, highlight) { - // box for mouse click - var box = { - x: x1, - y: y, - w: x2 - x1, - h: 15, - }; - // background - if(highlight) { - ctx.fillStyle = "#eee"; - ctx.fillRect(x1, y, x2 - x1, 15); - // frame - ctx.strokeStyle = "#888"; - ctx.beginPath(); - ctx.moveTo(x1, y + 15); - ctx.lineTo(x1, y); - ctx.moveTo(x2, y); - ctx.lineTo(x2, y + 15); - ctx.stroke(); - ctx.fillStyle = "#000"; - } else { - ctx.fillStyle = "#888"; - } - // label - var width = drawText(label, 0, -100, 0, Align.center, Align.top).w + 10; - var left = x1; - var right = x2; - if(left < 0) { - left = Math.min(0, x2 - width); - } - if(right >= chart.width) { - right = Math.max(chart.width - 1, x1 + width); - } - drawText(label, (left + right) / 2, y, 0, Align.center, Align.top); - return box; - } - // highlighted day - if(highlightedDate !== null) { - var idx = highlightedDate.getIndex(); - var x1 = date2x(idx - 0.5); - var x2 = date2x(idx + 0.5); - ctx.fillStyle = "#eee"; - ctx.strokeStyle = "#888"; - ctx.fillRect(x1, 0, Math.max(1, x2 - x1), chart.height); - } - // value at cursor - if(mouse.position !== null) { - ctx.fillStyle = "#eee"; - ctx.strokeStyle = "#888"; - ctx.fillRect(0, mouse.position.y - 0.5, chart.width, 1); - } - // day labels - if(labels.day_long || labels.day_short || labels.day_num || labels.day_blank) { - day = firstDay; - while(day.getIndex() <= lastDay.getIndex()) { - var idx = day.getIndex(); - var x1 = date2x(idx - 0.5); - var x2 = date2x(idx + 0.5); - var x = (x1 + x2) / 2; - var labelNum = zeroPad(day.getDay()); - var label; - if(labels.day_long || labels.day_short) { - var labelName = Date.getDayName(day.getDayOfWeek(), labels.day_long); - if(labels.day_long) { - label = labelName + ", " + labelNum; - } else { - label = labelName + "-" + labelNum; - } - } else if(labels.day_num) { - label = labelNum; - } else { - label = "'"; - } - var highlight = (highlightedDate !== null) && (highlightedDate.getIndex() == day.getIndex()); - var box = drawLabel(label, x1, x2, 45, highlight); - addButton(box, animateDateRange(x1, x2)); - day = day.addDays(1); - } - } - // week labels - if(labels.epiweek_long || labels.epiweek_short || labels.epiweek_blank) { - day = Date.fromIndex(firstDay.getIndex() - firstDay.getDayOfWeek()); - while(day.getIndex() <= lastDay.getIndex()) { - var idx = day.getIndex(); - var dow = day.getDayOfWeek(); - var x1 = date2x(idx - dow - 0.5); - var x2 = date2x(idx + (6 - dow) + 0.5); - var labelWeek = zeroPad(day.getEpiWeek()); - var label; - if(labels.epiweek_long) { - var labelYear = day.getEpiYear() + ""; - label = labelYear + "w" + labelWeek; - } else if(labels.epiweek_short) { - label = "w" + labelWeek; - } else { - label = "'"; - } - var highlight = (highlightedDate !== null) && (highlightedDate.getEpiYear() == day.getEpiYear() && highlightedDate.getEpiWeek() == day.getEpiWeek()); - var box = drawLabel(label, x1, x2, 30, highlight); - addButton(box, animateDateRange(x1, x2)); - day = day.addWeeks(1); - } - } - // month labels - if(labels.month_long || labels.month_short || labels.month_blank) { - day = firstDay; - while(day.getIndex() <= lastDay.getIndex()) { - var thisMonth = new Date(day.getYear(), day.getMonth(), 1); - var nextMonth = thisMonth.addMonths(1); - var x1 = date2x(thisMonth.getIndex() - 0.5); - var x2 = date2x(nextMonth.getIndex() - 0.5); - var label; - if(labels.month_long || labels.month_short) { - label = Date.getMonthName(day.getMonth(), labels.month_long); - } else { - label = "'"; - } - var highlight = (highlightedDate !== null) && (highlightedDate.getYear() == day.getYear() && highlightedDate.getMonth() == day.getMonth()); - var box = drawLabel(label, x1, x2, 15, highlight); - addButton(box, animateDateRange(x1, x2)); - day = nextMonth; - } - } - // year labels - if(labels.year_long || labels.year_short || labels.year_blank) { - day = firstDay; - while(day.getIndex() <= lastDay.getIndex()) { - var thisYear = new Date(day.getYear(), 1, 1); - var nextYear = thisYear.addYears(1); - var x1 = date2x(thisYear.getIndex() - 0.5); - var x2 = date2x(nextYear.getIndex() - 0.5); - var label; - if(labels.year_long) { - label = "" + thisYear.getYear(); - } else if(labels.year_short) { - label = "'" + zeroPad(thisYear.getYear() % 100); - } else { - label = "'"; - } - var highlight = (highlightedDate !== null) && (highlightedDate.getYear() == day.getYear()); - var box = drawLabel(label, x1, x2, 0, highlight); - addButton(box, animateDateRange(x1, x2)); - day = nextYear; - } - } - // highlighted day - if(highlightedDate !== null) { - var dayName = Date.getDayName(highlightedDate.getDayOfWeek(), false); - var monthName = Date.getMonthName(highlightedDate.getMonth(), false); - var epiweek = zeroPad(highlightedDate.getEpiWeek()); - var label = dayName + ", " + monthName + " " + highlightedDate.getDay() + ", " + highlightedDate.getYear() + " [" + highlightedDate.getEpiYear() + "w" + epiweek + "]"; - ctx.fillStyle = "#000"; - var x = Math.max(chart.width * 0.1, Math.min(chart.width * 0.9, date2x(highlightedDate.getIndex()))); - drawText(label, x, chart.height - 10, 0, Align.center, Align.center); - } - /*function isSegmentVisible(src, dst) { - var x1 = src.getDate().getIndex(); - var x2 = dst.getDate().getIndex(); - var y1 = src.getValue(); - var y2 = dst.getValue(); - // src is inside the viewport - if(xMin < x1 && x1 < xMax && yMin < y1 && y1 < yMax) { - return true; - } - // dst is inside the viewport - if(xMin < x2 && x2 < xMax && yMin < y2 && y2 < yMax) { - return true; - } - // neither endpoint is inside the viewpoint - var dx = x2 - x1; - var dy = y2 - y1; - var corners = [[xMin, yMin], [xMin, yMax], [xMax, yMax], [xMax, yMin]]; - var sum = 0; - for(var i = 0; i < 4; i++) { - if(dx * (corners[i][1] - y1) - dy * (corners[i][0] - x1) >= 0) { - sum++; - } else { - sum--; - } - } - // if the corners are on different sides of the line, there is an intersection - return Math.abs(sum) != 4; - }*/ - // data - for(var ds = 0; ds < datasets.length; ds++) { - var data = datasets[ds].data; - var scale = datasets[ds].scale; - var verticalOffset = datasets[ds].verticalOffset; - var first = true; - var date1 = 0, date2 = 0; - ctx.fillStyle = datasets[ds].color; - ctx.beginPath(); - for(var i = 0; i < data.length; i++) { - var y = value2y(getPointValue(datasets[ds], i)); - if(isNaN(y)) { - continue; - } - date1 = date2; - date2 = data[i].getDate().getIndex(); - if(date2 - date1 !== datasets[ds].gap) { - first = !interpolate; - } - var x = date2x(date2); - /*if(!isSegmentVisible(data[Math.max(0, i - 1)], data[i])) { - console.log("skipped " + i, data[i].getDate().getYear(), data[i].getDate().getMonth(), data[i].getDate().getDay(), data[i].getValue()); - continue; - }*/ - if(first) { - first = false; - ctx.moveTo(x, y); - } else { - ctx.lineTo(x, y); - } - if(showPoints) { - var w = datasets[ds].lineWidth * 2; - ctx.fillRect(x - w, y - w, 2 * w, 2 * w); - } - } - ctx.strokeStyle = datasets[ds].color; - ctx.lineWidth = datasets[ds].lineWidth; - ctx.stroke(); - } - ctx.lineWidth = 1; - // legend - var labelOffset = 0; - for(var ds = 0; ds < datasets.length; ds++) { - ctx.fillStyle = datasets[ds].color; - var label = "-" + datasets[ds].title; - drawText(label, chart.width - 10, chart.height - 10 - labelOffset, 0, Align.right, Align.bottom); - labelOffset += 12; - } - // nav box - if(navMode == NavMode.crop && navBox !== null) { - ctx.strokeStyle = "#000"; - ctx.strokeRect(navBox.x, navBox.y, navBox.w, navBox.h); - } - }; - this.addDataset = function(ds) { - if(demoAnimation !== null) { - demoAnimation.finish(); - demoAnimation = null; - datasets = []; - } - for(var i = 0; i < datasets.length; i++) { - if(datasets[i] === ds) { - // don't add it twice - return; - } - } - datasets.push(ds); - this.render(); - }; - this.removeDataset = function(ds) { - for(var i = 0; i < datasets.length; i++) { - if(datasets[i] === ds) { - // remove and stop looking - datasets.splice(i, 1); - break; - } - } - this.render(); - }; - this.autoScale = function(shouldAnimate) { - if(datasets.length === 0) { return; } - shouldAnimate = (typeof shouldAnimate !== 'undefined' && shouldAnimate === true); - var temp = datasets[0].data; - var _xMin = temp[0].getDate().getIndex() - 0.5; - var _xMax = temp[temp.length - 1].getDate().getIndex() + 0.5; - var _yMin = getPointValue(datasets[0], 0); - var _yMax = _yMin; - for(var ds = 0; ds < datasets.length; ds++) { - var data = datasets[ds].data; - _xMin = Math.min(_xMin, data[0].getDate().getIndex() - 0.5); - _xMax = Math.max(_xMax, data[data.length - 1].getDate().getIndex() + 0.5); - for(var i = 0; i < data.length; i++) { - var v = getPointValue(datasets[ds], i); - if(isNaN(_yMin) || v < _yMin) { _yMin = v; } - if(isNaN(_yMax) || v > _yMax) { _yMax = v; } - } - } - var dy = (_yMax - _yMin) / 0.75, cy = (_yMin + _yMax) / 2; - _yMin = cy - (dy / 2); - _yMax = cy + (dy / 2); - if(shouldAnimate) { - var playAnimation = this.animatePan(_xMin, _yMin, _xMax, _yMax); - playAnimation(); - } else { - this.setViewport(_xMin, _yMin, _xMax, _yMax); - } - }; - this.randomizeColors = function() { - for(var ds = 0; ds < datasets.length; ds++) { - datasets[ds].randomize(); - } - this.render(); - }; - this.showPoints = function(show) { - showPoints = typeof show !== 'undefined' ? show === true : !showPoints; - this.render(); - }; - this.interpolate = function(interp) { - interpolate = typeof interp !== 'undefined' ? interp === true : !interpolate; - this.render(); - }; - this.isShowingPoints = function() { - return showPoints; - }; - this.zoom = function(x, y) { - // x-axis - var xMid = (xMin + xMax) / 2; - var xRange = (xMax - xMin) / x; - xRange = Math.max(1, xRange); - xRange = Math.min(365.25 * 100, xRange); - xMin = xMid - xRange / 2; - xMax = xMid + xRange / 2; - // y-axis - var yMid = (yMin + yMax) / 2; - var yRange = (yMax - yMin) / y; - yMin = yMid - yRange / 2; - yMax = yMid + yRange / 2; - // render - this.render(); - }; - this.setViewport = function(x1, y1, x2, y2) { - xMin = x1; - xMax = x2; - yMin = y1; - yMax = y2; - this.render(); - }; - this.highlight = function(date) { - var update = false; - if(highlightedDate === null) { - if(date !== null) { - update = true; - } - } else { - if(date === null || highlightedDate.getIndex() != date.getIndex()) { - update = true; - } - } - if(update) { - highlightedDate = date; - this.render(); - } - }; - this.setNavMode = function(mode) { - if(forceCrop) { - mode = NavMode.crop; - } - if(forceZoom) { - mode = NavMode.zoom; - } - if(mode !== navMode) { - navMode = mode; - switch(navMode) { - case NavMode.pan: { - canvas.css("cursor", "default"); - } break; - case NavMode.crop: { - canvas.css("cursor", "crosshair"); - } break; - case NavMode.zoom: { - canvas.css("cursor", "nesw-resize"); - } break; - } - if(navMode !== NavMode.crop) { - navBox = null; - } - listener.onNavModeChanged(navMode); - } - this.render(); - }; - this.getNavMode = function() { - return navMode; - }; - // initial dataset, just for fun - var data = Array(365); - var now = moment(); - var baseIndex = new EpiVis.Date(now.year(), now.month() + 1, now.date()).getIndex() - 182; - for(var i = 0; i < data.length; i++) { - var x = 6 - (12 * i) / (data.length - 1); - var xp = x * Math.PI; - var v = (x === 0) ? 1 : (Math.sin(xp) / xp); - data[i] = new EpiVis.Point(new EpiVis.Date.fromIndex(baseIndex + i), v); - } - var sampleDataset = new EpiVis.Dataset(data, 'EpiVis Sample'); - datasets.push(sampleDataset); - this.autoScale(); - // animate it for even more fun - demoAnimation = new Animate.Task(function(dx, x) { - var two_pi = Math.PI * 2; - function sin(a, b) { return (Math.sin(x * a * two_pi + b) + 1) / 2; } - function hex(a) { a = Math.floor(a * 255); return (a < 16) ? ("0" + a.toString(16)) : a.toString(16); } - var r = 0.5 * sin(0.05, (1 / 3) * two_pi); - var g = 0.5 * sin(0.05, (2 / 3) * two_pi); - var b = 0.5 * sin(0.05, (0 / 3) * two_pi); - sampleDataset.color = "#" + hex(r) + hex(g) + hex(b); - self.render(); - }, 1, 0, Animate.Ease.sine); - demoAnimation.start(); - //sampleDataset.lineWidth = 5; - //sampleDataset.color = '#dd3311'; - // user interaction - if(window.self === window.top) { - // only focus if this is the top-level window (e.g. not in an iframe) - canvas.focus(); - } - canvas.on("mousedown touchstart", function(e) { consumeEvent(e, mouseDown); }); - canvas.on("mouseup touchend", function(e) { consumeEvent(e, mouseUp); }); - canvas.on("mouseover touchenter", function(e) { consumeEvent(e, mouseOver); }); - canvas.on("mouseout touchleave", function(e) { consumeEvent(e, mouseOut); }); - canvas.on("mousemove touchmove", function(e) { consumeEvent(e, mouseMove); }); - canvas.on("wheel", function(e) { consumeEvent(e, mouseWheel); }); - canvas.on("keydown", function(e) { consumeEvent(e, keyDown); }); - canvas.on("keyup", function(e) { consumeEvent(e, keyUp); }); - }; - self.Date = Date; - self.Point = Point; - self.Dataset = Dataset; - self.Chart = Chart; - self.Chart.NavMode = NavMode; - return self; -})(); diff --git a/site/backup/js/treeview.js b/site/backup/js/treeview.js deleted file mode 100644 index 1ca443f..0000000 --- a/site/backup/js/treeview.js +++ /dev/null @@ -1,171 +0,0 @@ -// dfarrow -var TreeView = (function() { - var self = {}; - var nextNodeID = 0; - var Node = function(name,parent) { - var self = {}; - // private variables - var nodes = null; - var dataset = null; - var callback = null; - var id = nextNodeID++; - var selected = false; - // public methods - self.parent = parent; - self.getID = function() { return id; }; - self.getName = function() { return name; }; - self.getNodes = function() { return nodes; }; - self.getDataset = function() { return dataset; }; - self.setNodes = function(ns) { nodes = ns; }; - self.setDataset = function(ds, cb) { dataset = ds; callback = cb; }; - self.isSelected = function() { return selected; }; - self.isLeaf = function() { return dataset !== null; }; - self.isBranch = function() { return nodes !== null; }; - self.toggleSelected = function() { - selected = !selected; - if(typeof callback !== "undefined" && callback !== null) { - callback(self); - } - }; - self.getParent = function(){ return self.parent }; - self.getRoot = function(){ - var par = self.getParent(); - if (typeof par === "undefined") - return self; - else - return par.getRoot(); - }; - return self; - }; - var TreeView = function(containerID) { - var self = {}; - // private variables - var container = $("#" + containerID); - var rootNodes = []; - // public methods - var append = function(node, parent) { - parent = (typeof parent !== "undefined") ? parent : container; - parent.append('
' + node.getName() + '
'); - $("#" + getNodeLabelID(node)).click(function() { - node.toggleSelected(); - updateNodeCSS(node); - }); - var newParent = $("#" + getNodeID(node)); - if(node.isBranch()) { - for(var i = 0; i < node.getNodes().length; i++) { - append(node.getNodes()[i], newParent); - } - } - updateNodeCSS(node); - if(parent === container) { - rootNodes.push(node); - } - }; - // toggles (show=undefined), shows (show=true), or hides (show=false) all tree nodes with name containing the given string (name) - var toggleLike = function(name, show) { - for(var i = 0; i < rootNodes.length; i++) { - toggleNodeLike(rootNodes[i], name, (typeof show === "undefined"), (show === true)); - } - }; - // return all datasets - var getAllDatasets = function() { - var datasets = []; - for(var i = 0; i < rootNodes.length; i++) { - getDatasetsRecursive(datasets, rootNodes[i], false); - } - return datasets; - }; - // return all selected datasets - var getSelectedDatasets = function() { - var selected = []; - var nodenames = []; - for(var i = 0; i < rootNodes.length; i++) { - getDatasetsRecursive(selected, nodenames, rootNodes[i].getName(),rootNodes[i], true); - } - return [selected,nodenames]; - }; - // private methods - function updateNodeCSS(node) { - var nodeLabel = $("#" + getNodeID(node)); - var nodeIcon = $("#" + getNodeIconID(node)); - nodeLabel.removeClass(); - nodeLabel.addClass(getNodeClass(node)); - nodeIcon.removeClass(); - nodeIcon.addClass(getNodeIconClass(node)); - if(node.isBranch()) { - for(var i = 0; i < node.getNodes().length; i++) { - var child = $("#" + getNodeID(node.getNodes()[i])); - if(node.isSelected()) { - child.show(200); - } else { - child.hide(200); - } - } - } - } - function toggleNodeLike(node, name, toggle, show) { - if(node.getName().toLowerCase().indexOf(name.toLowerCase()) >= 0) { - if(toggle || show !== node.isSelected()) { - node.toggleSelected(); - updateNodeCSS(node); - } - } - if(node.isBranch()) { - for(var i = 0; i < node.getNodes().length; i++) { - toggleNodeLike(node.getNodes()[i], name, toggle, show); - } - } - } - function getDatasetsRecursive(datasets, nodenames, currpath, node, onlySelected) { - if(node.isBranch()) { - for(var i = 0; i < node.getNodes().length; i++) { - getDatasetsRecursive(datasets, nodenames, currpath+'/'+node.getName(), node.getNodes()[i], onlySelected); - } - } - if(node.isLeaf() && (node.isSelected() || !onlySelected)) { - datasets.push(node.getDataset()); - nodenames.push(currpath+'/'+node.getName()); - } - } - function getNodeID(node) { - return "tv_node_" + node.getID(); - } - function getNodeLabelID(node) { - return "tv_node_label_" + node.getID(); - } - function getNodeIconID(node) { - return "tv_node_icon_" + node.getID(); - } - function getNodeClass(node) { - if(node.isLeaf()) { - return "tv_node tv_leaf"; - } else if(node.isBranch()) { - return "tv_node tv_branch"; - } - } - function getNodeIconClass(node) { - if(node.isLeaf()) { - if(node.isSelected()) { - return "fa fa-check-square-o"; - } else { - return "fa fa-square-o"; - } - } else { - if(node.isSelected()) { - return "fa fa-minus-square-o"; - } else { - return "fa fa-plus-square-o"; - } - } - } - // export public methods - self.append = append; - self.toggleLike = toggleLike; - self.getAllDatasets = getAllDatasets; - self.getSelectedDatasets = getSelectedDatasets; - return self; - }; - self.TreeView = TreeView; - self.Node = Node; - return self; -})(); diff --git a/site/backup/manifest.json b/site/backup/manifest.json deleted file mode 100644 index cec1083..0000000 --- a/site/backup/manifest.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "EpiVis", - "icons": [ - { - "src": "icon.png", - "sizes": "192x192", - "type": "image/png", - "density": "4.0" - } - ], - "start_url": "epivis.html", - "display": "standalone", - "orientation": "landscape" -} diff --git a/site/changelog.txt b/site/changelog.txt deleted file mode 100644 index 8b866e7..0000000 --- a/site/changelog.txt +++ /dev/null @@ -1,315 +0,0 @@ -# Status -[![Deploy Status](http://delphi.midas.cs.cmu.edu/~automation/public/github_deploy_repo/badge.php?repo=cmu-delphi/www-epivis)](#) - -# About -An interactive tool for visualizing epidemiological time-series data. - -The site is live at http://delphi.midas.cs.cmu.edu/epivis/ - -# Legacy Changelog -```` -=== v39: 2017-12-04 === - ./epivis.html - + source `quidel` - js/epidata.js - + source `quidel` - (by Lisheng) - -=== v38: 2017-02-15 === - ./epivis.html - + 'run regression','reset' features - js/epivis.js - + regression related member functions in Dataset class - js/treeview.js - * return full path for dataset in `getDatasets...()` functions - (by Lisheng) - -=== v37: 2017-02-07 === - ./epivis.html - + source `flusurv` - js/epidata.js - + source `flusurv` - -=== v36: 2016-11-15 === - ./epivis.html - * API update for `stateili` param `version` - js/epidata.js - * API update for `stateili` param `version` - -=== v35: 2016-11-14 === - ./epivis.html - * link to 3rd party libs instead of hosting them - -=== v34: 2016-08-05 === - ./epivis.html - + added description for authorization token under ILINet - (by Paul) - -=== v33: 2016-06-16 === - js/epivis.js - + don't request focus if in an iframe - -=== v32: 2016-04-20 === - js/epivis.js - + don't interpolate missing values (unless enabled) - -=== v31: 2016-04-16 === - ./epivis.html - + location for source `cdc` - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + location for source `cdc` - -=== v30: 2016-04-15 === - ./epivis.html - + YYYYWW epiweek date column - js/csv.js - + YYYYWW epiweek date column - -=== v29: 2016-04-09 === - ./epivis.html - + all locations for `nowcast` - js/epidata.js - * new `nowcast` start week - -=== v28: 2016-04-07 === - ./epivis.html - + sources `cdc` and `sensors` - * createDataset handles different dates - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + sources `cdc` and `sensors` - -=== v27: 2016-04-06 === - ./epivis.html - + source `stateili` - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + source `stateili` - * fixed first `ght` epiweek - -=== v26: 2016-04-01 === - ./epivis.html - + census regions for fluview, ilinet, and twitter - -=== v25: 2016-03-22 === - ./epivis.html - + added screenshot button and function - -=== v24: 2016-02-18 === - ./epivis.html - + param `auth` for source `ilinet` - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + param `auth` for source `ilinet` - -=== v23: 2016-01-29 === - ./epivis.html - + signal `ght` - -=== v22: 2015-12-15 === - ./epivis.html - + source `nowcast` - * fixed wording and default value for `fluview` lag parameter - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + source `nowcast` - -=== v21: 2015-12-11 === - ./epivis.html - + source `signals` - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + source `signals` - -=== v20: 2015-12-03 === - ./epivis.html - + source `ght` - css/epivis.css - * increased option_label width - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + source `ght` - -=== v19: 2015-09-18 === - js/epidata.js - + added `value` column for wiki - -=== v18: 2015-09-15 === - ./epivis.html - + ILINet support - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + ILINet support - -=== v17: 2015-08-20 === - ./epivis.html - + NIDSS dengue support - js/delphi_epidata.js - * from https://github.com/undefx/delphi-epidata - js/epidata.js - + NIDSS dengue support - * NIDSS flu update - -=== v16: 2015-08-11 === - ./epivis.html - + NIDSS support - js/delphi_epidata.js - + from https://github.com/undefx/delphi-epidata - js/epidata.js - + NIDSS support - + New wiki field - * wrapper for delphi_epidata.js - -=== v15: 2015-07-31 === - ./epivis.html - + auth token for twitter - js/epidata.js - + auth token for twitter - -=== v14: 2015-07-24 === - ./epivis.html - + parse CSVOptions from CSV comment - + auto load file when CSVOptions present - + bulk add/remove grouped datasets - js/csv.js - + ignore lines starting with '#' - -=== v13: 2015-07-23 === - ./epivis.html - * createDataset requires kernel to be non-null - * fixed preview box wrapping - js/epivis.js - + range check for week in Date.fromEpiweek - * fix (hack) for week 53 in years with 52 weeks - -=== v12: 2015-07-09 === - ./epivis.html - + draw custom line - js/epivis.js - * fixed autoscale with multiple datasets - -=== v11: 2015-07-03 === - ./epivis.html - + meta viewport tag - + meta mobile-web-app-capable tag - + link to manifest.json - + link to icon.png - + new buttons for CSV, API, kernels - + fullscreen support - + android homescreen support - * improved dialog support - * better tooltip positioning - * hide tooltip after button click - * adjustments to top bar - * import via dialog - - "flag" buttons for UI layout - - sample CSV files - ./icon.png - + basic icon for android homescreen - ./manifest.json - + basic manifest for android homescreen - css/epivis.css - * increased size of top bar - * adjusted button look and feel - * changed import color scheme - - bottom section now inside dialog - js/epivis.js - * default dataset lineWidth 1 to 2 - * various linting - -=== v10: 2015-07-02 === - ./epivis.html - + 97 GFT cities - + UI tooltips for top-bar buttons - css/epivis.css - + tooltip class - -=== v9: 2015-06-30 === - ./epivis.html - + experimental overlay/dialog - css/epivis.css - + overlay/dialog classes - -=== v8: 2015-06-24 === - ./epivis.html - + twitter from Epidata API - + wiki from Epidata API - * major improvements to Epidata API - * tentatively enabled multiscale button - css/epivis.css - * some padding for option labels - * tree view width from 200px to 15% - js/epidata.js - + Delphi Epidata API (supporting all data sources) - js/epivis.js - + scale and vertical offset per dataset - + dataset scale by mean - * small tweaks for jsHint - -=== v7: 2015-06-23 === - ./epivis.html - + page title - + more documentation for file loading - + fetch from Epidata API (FluView and GFT) - * better mime type detection (excel types) - * converted double quotes to single quotes - css/epivis.css - + extra classes for documentation - js/csv.js - + export CSV.DataGroup - * small syntax adjustments - -=== v6: 2015-03-24 === - ./epivis.html - + automatic file preview - -=== v5: 2015-03-17 === - ./epivis.html - + kernel: product - js/csv.js - * fixed loading without date column - * fixed loading 1-column CSV - js/epivis.js - + .showPoints - + .isShowingPoints - js/treeview.js - + .getAllDatasets - -=== v4: 2015-01-21 === - ./epivis.html - + kernels: sum and iliplus - -=== v3: 2015-01-20 === - ./changelog.txt - + separate changelog file - ./epivis.html - + link to this changelog - + createDataset (very experimental) - + Kernels (kernel generators) - * fixed randomize button's id - css/epivis.css - + reasonable anchor colors - js/csv.js - * Use Date.fromEpiweek - js/epivis.js - + Date.fromEpiweek - + .toString - js/treeview.js - + .getSelectedDatasets - -=== v2: 2014-12-22 === - + randomize color button - + autoscale button - -=== v1: 2014-12-22 === - * original version -```` diff --git a/site/css/epivis.css b/site/css/epivis.css index 392f3cd..3ee8588 100644 --- a/site/css/epivis.css +++ b/site/css/epivis.css @@ -1,189 +1,184 @@ -/* - ____ ___ _ _ ___ _____ _____ ____ ___ _____ -| _ \ / _ \ | \ | |/ _ \_ _| | ____| _ \_ _|_ _| -| | | | | | | | \| | | | || | | _| | | | | | | | -| |_| | |_| | | |\ | |_| || | | |___| |_| | | | | -|____/ \___/ |_| \_|\___/ |_| |_____|____/___| |_| - - Automatically generated from sources at: - https://github.com/cmu-delphi/www-epivis - - Commit hash: 6c66fa51b7bbf0e7be8b99a4cb6f9bf059d1fd7a - Deployed at: 2018-06-26 10:15:52 (1530022552) -*/ - - - -html { - height: 100%; - box-sizing: border-box; - font-size: 100%; -} -*, *:before, *:after { - box-sizing: inherit; -} -body { - height: 100%; - margin: 0px; - padding: 0px; - background-color: #222; - color: #ddd; - font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif; - font-size: 0.9em; -} -a:link { - color: #16a; -} -a:visited { - color: #16a; -} -a:hover { - color: #61a; -} -a:active { - color: #61a; -} -h1 { - text-align: center; -} -#top_bar { - text-align: center; - height: 32px; - overflow: hidden; - background-color: #222; - color: #444; -} -#chart_container { - width: 100%; - padding: 0px; - margin: 0px; - height: calc(100% - 32px); -} -#chart_tree { - border-right: 2px solid #000; - padding: 4px; - margin: 0px; - background-color: #fff; - color: #000; - width: 15%; - height: 100%; - float: left; - overflow: auto; -} -#chart_canvas { - padding: 0px; - margin: 0px; - height: 100%; - float: left; -} -#chart_canvas.show_left { - width: 85%; -} -#chart_canvas.hide_left { - width: 100%; -} -#box_overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: #000; - opacity: 0; - cursor: not-allowed; - display: none; -} -div.box_dialog { - position: absolute; - top: 5%; - left: 15%; - color: #222; - background-color: #fff; - width: 70%; - height: 90%; - overflow: auto; - opacity: 0; - border: 1px solid #000; - padding: 16px; - box-shadow: 0px 0px 16px #000; - display: none; -} -div.csv_options { - border-left: 1px solid #666; - padding: 8px; - margin: 16px; -} -div.box_tooltip { - position: absolute; - background-color: #222; - color: #ddd; - display: inline-block; - border: 1px solid #000; - padding: 4px 8px; - box-shadow: 0 0 16px #000; -} -div.ui_flag { - margin-top: 2px; - width: 28px; - height: 28px; - display: inline-block; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: pointer; -} -div.ui_flag_icon { - border-radius: 4px; - padding-top: 4px; - vertical-align: top; - font-size: 1.25em; - color: #000; - background-color: #fff; - transition: background-color 0.2s ease 0s; -} -div.ui_flag_icon:hover { - background-color: #aaa; - transition: background-color 0.2s ease 0s; -} -div.ui_flag_icon_selected { - box-shadow: 0px 0px 8px #000 inset; -} -div.ui_flag_icon_disabled { - color: #300; - background-color: #666; - cursor: not-allowed; - display: none; -} -p.info_text { - display: inline-block; - position: absolute; - top: 0px; - right: 0px; - color: #888; - font-size: 0.8em; - margin: 2px; -} -div.spacer { - line-height: 9px; - margin-top: 2px; - width: 28px; - height: 28px; - padding-top: 10px; - display: inline-block; - font-weight: bold; - vertical-align: top; -} -div.spacer:before { - content: '|'; -} -div.option_label { - display: inline-block; - min-width: 170px; - padding-right: 10px; -} -span.explanation { - font-style: italic; - font-size: 0.85em; - color: #666; -} +html { + height: 100%; + box-sizing: border-box; + font-size: 100%; +} +*, *:before, *:after { + box-sizing: inherit; +} +body { + height: 100%; + margin: 0px; + padding: 0px; + background-color: #222; + color: #ddd; + font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif; + font-size: 0.9em; +} +a:link { + color: #16a; +} +a:visited { + color: #16a; +} +a:hover { + color: #61a; +} +a:active { + color: #61a; +} +h1 { + text-align: center; +} +#top_bar { + text-align: center; + height: 32px; + overflow: hidden; + background-color: #222; + color: #444; +} +#chart_container { + width: 100%; + padding: 0px; + margin: 0px; + height: calc(100% - 32px); +} +#chart_tree { + border-right: 2px solid #000; + padding: 4px; + margin: 0px; + background-color: #fff; + color: #000; + width: 15%; + height: 100%; + float: left; + overflow: auto; +} +#chart_canvas { + padding: 0px; + margin: 0px; + height: 100%; + float: left; +} +#chart_canvas.show_left { + width: 85%; +} +#chart_canvas.hide_left { + width: 100%; +} +#box_overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #000; + opacity: 0; + cursor: not-allowed; + display: none; +} +div.box_dialog { + position: absolute; + top: 5%; + left: 15%; + color: #222; + background-color: #fff; + width: 70%; + height: 90%; + overflow: auto; + opacity: 0; + border: 1px solid #000; + padding: 16px; + box-shadow: 0px 0px 16px #000; + display: none; +} +div.csv_options { + border-left: 1px solid #666; + padding: 8px; + margin: 16px; +} +div.box_tooltip { + position: absolute; + background-color: #222; + color: #ddd; + display: inline-block; + border: 1px solid #000; + padding: 4px 8px; + box-shadow: 0 0 16px #000; +} +div.ui_flag { + margin-top: 2px; + width: 28px; + height: 28px; + display: inline-block; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; +} +div.ui_flag_icon { + border-radius: 4px; + padding-top: 4px; + vertical-align: top; + font-size: 1.25em; + color: #000; + background-color: #fff; + transition: background-color 0.2s ease 0s; +} +div.ui_flag_icon:hover { + background-color: #aaa; + transition: background-color 0.2s ease 0s; +} +div.ui_flag_icon_selected { + box-shadow: 0px 0px 8px #000 inset; +} +div.ui_flag_icon_disabled { + color: #300; + background-color: #666; + cursor: not-allowed; + display: none; +} +p.info_text { + display: inline-block; + position: absolute; + top: 0px; + right: 0px; + color: #888; + font-size: 0.8em; + margin: 2px; +} +div.spacer { + line-height: 9px; + margin-top: 2px; + width: 28px; + height: 28px; + padding-top: 10px; + display: inline-block; + font-weight: bold; + vertical-align: top; +} +div.spacer:before { + content: '|'; +} +div.option_label { + display: inline-block; + min-width: 170px; + padding-right: 10px; +} +span.explanation { + font-style: italic; + font-size: 0.85em; + color: #666; +} +.directlink_text { + color: #444; + font-family: monospace; + font-size: 0.9em; + overflow-wrap: anywhere; + padding: 8px; +} +.warning_text { + color: #620; + padding: 8px; +} diff --git a/site/css/treeview.css b/site/css/treeview.css index 38e091e..351fe25 100644 --- a/site/css/treeview.css +++ b/site/css/treeview.css @@ -1,34 +1,18 @@ -/* - ____ ___ _ _ ___ _____ _____ ____ ___ _____ -| _ \ / _ \ | \ | |/ _ \_ _| | ____| _ \_ _|_ _| -| | | | | | | | \| | | | || | | _| | | | | | | | -| |_| | |_| | | |\ | |_| || | | |___| |_| | | | | -|____/ \___/ |_| \_|\___/ |_| |_____|____/___| |_| - - Automatically generated from sources at: - https://github.com/cmu-delphi/www-epivis - - Commit hash: 6c66fa51b7bbf0e7be8b99a4cb6f9bf059d1fd7a - Deployed at: 2018-06-26 10:15:52 (1530022552) -*/ - - - -div.tv_node { - padding: 2px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: pointer; - min-width: 512px; -} -div.tv_node div.tv_node { - margin-left: 16px; -} -div.tv_branch { - font-weight: bold; -} -div.tv_leaf { - font-weight: normal; -} +div.tv_node { + padding: 2px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + min-width: 512px; +} +div.tv_node div.tv_node { + margin-left: 16px; +} +div.tv_branch { + font-weight: bold; +} +div.tv_leaf { + font-weight: normal; +} diff --git a/site/epivis.html b/site/epivis.html index 5282998..cc0ba10 100644 --- a/site/epivis.html +++ b/site/epivis.html @@ -22,7 +22,7 @@ -

[v38]

+

[source]

@@ -34,10 +34,11 @@
-
-
-
+
+
+
+
@@ -73,9 +74,11 @@

Load from Epidata API


(source: nidss.cdc.gov.tw)
-
(source: delphi.midas.cs.cmu.edu) +
(source: delphi.cmu.edu)
-
(source: delphi.midas.cs.cmu.edu) +
(source: delphi.cmu.edu) +
+
(source: delphi.cmu.edu)
@@ -711,7 +713,6 @@

Load from Epidata API

(location to filter data by)
-
-
-
-
-
+
@@ -1112,695 +1133,14 @@

Run regression

- + - diff --git a/site/index.php b/site/index.php index 6e865e3..be64d99 100644 --- a/site/index.php +++ b/site/index.php @@ -1,5 +1,5 @@ Nothing to see here. diff --git a/site/js/animate.js b/site/js/animate.js index cef6ffd..24cda6e 100644 --- a/site/js/animate.js +++ b/site/js/animate.js @@ -1,98 +1,82 @@ -/* - ____ ___ _ _ ___ _____ _____ ____ ___ _____ -| _ \ / _ \ | \ | |/ _ \_ _| | ____| _ \_ _|_ _| -| | | | | | | | \| | | | || | | _| | | | | | | | -| |_| | |_| | | |\ | |_| || | | |___| |_| | | | | -|____/ \___/ |_| \_|\___/ |_| |_____|____/___| |_| - - Automatically generated from sources at: - https://github.com/cmu-delphi/www-epivis - - Commit hash: 6c66fa51b7bbf0e7be8b99a4cb6f9bf059d1fd7a - Deployed at: 2018-06-26 10:15:51 (1530022551) -*/ - - - -// dfarrow -var Animate = (function() { - var self = {}; - var Task = function(target, fps, duration, ease) { - // setup - if(typeof duration === 'undefined' || duration === null) { - duration = 0; - } - if(typeof ease === 'undefined' || ease === null) { - ease = Animate.Ease.linear; - } - // private variables - var timerID = null; - var startTime = 0; - var lastUpdateTime = 0; - // public methods - var start = function() { - if(this.isRunning()) { - return; - } - lastUpdateTime = startTime = getTime(); - timerID = setTimeout(update, 1000 / fps); - }; - var finish = function(callTarget) { - if(!this.isRunning()) { - return; - } - clearTimeout(timerID); - timerID = null; - if(typeof callTarget === 'undefined' || callTarget !== false) { - target(getTime() - lastUpdateTime, 1); - } - }; - var isRunning = function() { - return timerID !== null; - }; - this.start = start; - this.finish = finish; - this.isRunning = isRunning; - // private methods - function getTime() { - return Date.now() / 1000; - } - function update() { - var time = getTime(); - var dt = time - lastUpdateTime; - if(duration > 0) { - var position = Math.min(1, Math.max(0, (time - startTime) / duration)); - target(dt, ease(position)); - if(isRunning() && position < 1) { - timerID = setTimeout(update, 1000 / fps); - } else { - timerID = null; - } - } else { - target(dt, time - startTime); - if(isRunning()) { - timerID = setTimeout(update, 1000 / fps); - } - } - lastUpdateTime = time; - } - }; - var Ease = { - linear: function(x) { - return x; - }, - sine: function(x) { - return (1 - Math.cos(x * Math.PI)) / 2; - }, - double_sine: function(x) { - return Ease.sine(Ease.sine(x)); - }, - }; - var blend = function(before, after, position) { - return ((1 - position) * before) + (position * after); - }; - self.Task = Task; - self.Ease = Ease; - self.blend = blend; - return self; -})(); +// dfarrow +var Animate = (function() { + var self = {}; + var Task = function(target, fps, duration, ease) { + // setup + if(typeof duration === 'undefined' || duration === null) { + duration = 0; + } + if(typeof ease === 'undefined' || ease === null) { + ease = Animate.Ease.linear; + } + // private variables + var timerID = null; + var startTime = 0; + var lastUpdateTime = 0; + // public methods + var start = function() { + if(this.isRunning()) { + return; + } + lastUpdateTime = startTime = getTime(); + timerID = setTimeout(update, 1000 / fps); + }; + var finish = function(callTarget) { + if(!this.isRunning()) { + return; + } + clearTimeout(timerID); + timerID = null; + if(typeof callTarget === 'undefined' || callTarget !== false) { + target(getTime() - lastUpdateTime, 1); + } + }; + var isRunning = function() { + return timerID !== null; + }; + this.start = start; + this.finish = finish; + this.isRunning = isRunning; + // private methods + function getTime() { + return Date.now() / 1000; + } + function update() { + var time = getTime(); + var dt = time - lastUpdateTime; + if(duration > 0) { + var position = Math.min(1, Math.max(0, (time - startTime) / duration)); + target(dt, ease(position)); + if(isRunning() && position < 1) { + timerID = setTimeout(update, 1000 / fps); + } else { + timerID = null; + } + } else { + target(dt, time - startTime); + if(isRunning()) { + timerID = setTimeout(update, 1000 / fps); + } + } + lastUpdateTime = time; + } + }; + var Ease = { + linear: function(x) { + return x; + }, + sine: function(x) { + return (1 - Math.cos(x * Math.PI)) / 2; + }, + double_sine: function(x) { + return Ease.sine(Ease.sine(x)); + }, + }; + var blend = function(before, after, position) { + return ((1 - position) * before) + (position * after); + }; + self.Task = Task; + self.Ease = Ease; + self.blend = blend; + return self; +})(); diff --git a/site/js/app.js b/site/js/app.js index 9244685..c9d1b5e 100644 --- a/site/js/app.js +++ b/site/js/app.js @@ -1,46 +1,5 @@ "use strict"; -function _slicedToArray(arr, i) { - return ( - _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest() - ); -} - -function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance"); -} - -function _iterableToArrayLimit(arr, i) { - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; - try { - for ( - var _i = arr[Symbol.iterator](), _s; - !(_n = (_s = _i.next()).done); - _n = true - ) { - _arr.push(_s.value); - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i["return"] != null) _i["return"](); - } finally { - if (_d) throw _e; - } - } - return _arr; -} - -function _arrayWithHoles(arr) { - if (Array.isArray(arr)) return arr; -} - var chart; var tree; var currentDialog = null; @@ -48,7 +7,9 @@ var currentDialog = null; function run() { // chart setup chart = new EpiVis.Chart("chart_canvas", chartListener); - tree = new TreeView.TreeView("chart_tree"); // interface setup + + // interface setup + tree = new TreeView.TreeView("chart_tree"); $("#file_local").change(function() { loadFile(previewFile); @@ -87,7 +48,8 @@ function run() { "radio_cdc", "radio_quidel", "radio_sensors", - "radio_nowcast" + "radio_nowcast", + "radio_covidcast" ]); connectSubOptions([ "radio_fluview_recent", @@ -106,8 +68,9 @@ function run() { ]); connectSubOptions(["check_wiki_hour"]); connectSubOptions(["check_date"]); - connectSubOptions(["check_group"]); // top button bar + connectSubOptions(["check_group"]); + // top button bar setActionTooltip( "file_csv", function() { @@ -162,15 +125,16 @@ function run() { ); setActionTooltip("action_reset", resetChart, "reset scaling and shifting"); setActionTooltip("action_screenshot", screenshot, "take a screenshot"); + setActionTooltip("action_directlink", showDirectLink, "link to this view"); setActionTooltip( "action_undo", chartUndo, - 'undo //not implemented" + 'undo //not implemented' ); setActionTooltip( "action_redo", chartRedo, - 'redo //not implemented" + 'redo //not implemented' ); setActionTooltip( "navmode_pan", @@ -192,16 +156,65 @@ function run() { setNavMode(EpiVis.Chart.NavMode.zoom); }, "zoom mode" - ); // escape key closes dialog + ); + // escape key closes dialog $(document).keyup(function(e) { if (e.keyCode == 27) { closeDialog(); } - }); // dynamic resizing + }); + // dynamic resizing $(window).resize(resize); resize(); + + // populate covidcast options from live metadata + initializeCovidcastOptions(); + + // maybe load config encoded in path fragment + loadDirectLinkFragment(); +} + +const initializeCovidcastOptions = () => { + + const validNameRegex = /^[-_\w\d]+$/; + + const getSortedUnique = (rows, key) => { + const set = {}; + rows.forEach(row => { + set[row[key]] = 1; + }); + const items = []; + Object.entries(set).forEach(keyval => items.push(keyval[0])); + return items.sort(); + } + + const populateDropdown = (element, names) => { + names.forEach(name => { + if (name.match(validNameRegex)) { + element.append(``); + } else { + console.log('invalid name:', name); + } + }); + }; + + Epidata.api.covidcast_meta((result, message, epidata) => { + if (result !== 1) { + console.log('failed to fetch covidcast metadata:', message); + return; + } + populateDropdown( + $('#select_covidcast_data_source'), + getSortedUnique(epidata, 'data_source')); + populateDropdown( + $('#select_covidcast_signal'), + getSortedUnique(epidata, 'signal')); + populateDropdown( + $('#select_covidcast_geo_type'), + getSortedUnique(epidata, 'geo_type')); + }); } function setActionTooltip(id, action, tooltip) { @@ -230,25 +243,28 @@ function hideTooltip(event) { $("#tooltip").hide(0); } -function loadEpidata() { - function successFunction(title) { - return function(datasets) { - var info = new CSV.Info(); - info.numCols = datasets.length; - info.numRows = datasets[0].data.length; - info.data = new CSV.DataGroup("[API] " + title, datasets); - info.print(); - var node = new TreeView.Node(info.data.getTitle()); - loadDataGroup(node, info.data.getData()); - tree.append(node); - closeDialog(); - }; - } +const successFunction = (title) => { + return function(datasets) { + datasets.forEach(data => { + data.parentTitle = title; + }); + var info = new CSV.Info(); + info.numCols = datasets.length; + info.numRows = datasets[0].data.length; + info.data = new CSV.DataGroup("[API] " + title, datasets); + info.print(); + var node = new TreeView.Node(info.data.getTitle()); + loadDataGroup(node, info.data.getData()); + tree.append(node); + closeDialog(); + }; +}; - function onFailure(message) { - alert(message); - } +const onFailure = (message) => { + alert(message); +}; +function loadEpidata() { if ($("#radio_fluview").is(":checked")) { (function() { var region = $("#select_fluview_region :selected").val(); @@ -434,6 +450,23 @@ function loadEpidata() { var title = "Delphi Nowcast: " + location_t; Epidata.fetchNowcast(successFunction(title), onFailure, location_v); })(); + } else if ($("#radio_covidcast").is(":checked")) { + (() => { + const dataSource = $("#select_covidcast_data_source :selected").val(); + const signal = $("#select_covidcast_signal :selected").val(); + const timeType = 'day'; + const geoType = $("#select_covidcast_geo_type :selected").val(); + const geoValue = $("#text_covidcast_geo_value").val(); + const title = `Delphi COVIDcast: ${geoValue} ${signal} (${dataSource})`; + Epidata.fetchCovidcast( + successFunction(title), + onFailure, + dataSource, + signal, + timeType, + geoType, + geoValue); + })(); } else { alert("invalid api"); } @@ -481,51 +514,35 @@ function showLeftSection(show) { } function multiScale() { - var _tree$getSelectedData = tree.getSelectedDatasets(), - _tree$getSelectedData2 = _slicedToArray(_tree$getSelectedData, 2), - selected = _tree$getSelectedData2[0], - nodenames = _tree$getSelectedData2[1]; - - for (i = 0; i < selected.length; i++) { - selected[i].scaleMean(); - } - + tree.getSelectedDatasets()[0].forEach(data => data.scaleMean()); chart.render(); } function runRegression(index) { - var _tree$getSelectedData3 = tree.getSelectedDatasets(), - _tree$getSelectedData4 = _slicedToArray(_tree$getSelectedData3, 2), - selected = _tree$getSelectedData4[0], - nodenames = _tree$getSelectedData4[1]; - - if (selected.length < 2) multiScale(); - else { - for (i = 0; i < selected.length; i++) { - if (i == index) continue; - else selected[i].regress(selected[index]); + const selected = tree.getSelectedDatasets()[0]; + if (selected.length < 2) { + multiScale(); + } else { + for (let i = 0; i < selected.length; i++) { + if (i != index) { + selected[i].regress(selected[index]); + } } - chart.render(); } closeDialog(); } function resetChart() { - var _tree$getSelectedData5 = tree.getSelectedDatasets(), - _tree$getSelectedData6 = _slicedToArray(_tree$getSelectedData5, 2), - selected = _tree$getSelectedData6[0], - nodenames = _tree$getSelectedData6[1]; - - for (i = 0; i < selected.length; i++) { - selected[i].scaleAndShift(1, 0); - } - + tree.getSelectedDatasets()[0].forEach(data => data.scaleAndShift(1, 0)); chart.render(); } function screenshot() { - window.open($("#chart_canvas")[0].toDataURL()); + const image = new Image(); + image.src = $('#chart_canvas')[0].toDataURL(); + const newWindow = window.open(''); + newWindow.document.write(image.outerHTML); } function chartAutoScale() { @@ -851,8 +868,9 @@ function createDataset(title, kernel) { for (var i = 0; i < selected.length; i++) { console.log(" " + selected[i].title); - } // find the union of dates + } + // find the union of dates var values = {}; for (var ds = 0; ds < selected.length; ds++) { @@ -1034,13 +1052,8 @@ function closeDialog() { } function fillRegressionDialog() { - var _tree$getSelectedData7 = tree.getSelectedDatasets(), - _tree$getSelectedData8 = _slicedToArray(_tree$getSelectedData7, 2), - selected = _tree$getSelectedData8[0], - nodenames = _tree$getSelectedData8[1]; //var dropdown = $("#regress_dropdown"); - $("#regress_dropdown").empty(); - $.each(nodenames, function(i, name) { + $.each(tree.getSelectedDatasets()[1], (i, name) => { $("#regress_dropdown").append( $("