diff --git a/.gitignore b/.gitignore index b4576d7b5b..07081b1b5b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ stochss/dist/stochss-domain-editor.html stochss/dist/stochss-loading-page.html stochss/dist/stochss-quick-start.html stochss/dist/stochss-user-home.html +stochss/dist/stochss-example-library.html jupyterhub/templates/page.html jupyterhub/templates/stochss-home.html jupyterhub/templates/stochss-job-presentation.html diff --git a/Makefile b/Makefile index ee4955a7aa..a4f706ddc8 100644 --- a/Makefile +++ b/Makefile @@ -83,32 +83,32 @@ cert: @echo "Generating certificate..." openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout $(SSL_KEY) -out $(SSL_CERT) -build_home_page: - npm run build-home +create_dep_dirs: + mkdir -p "./userlist" -build_hub: build_home_page check-files network volumes +build_hub: create_dep_dirs check-files network volumes export AUTH_CLASS='' && export OAUTH_FILE='.oauth.dummy.env' && \ - cd ./jupyterhub && docker-compose build + docker-compose --env-file jupyterhub/.env -f jupyterhub/docker-compose.yml build -build_hub_clean: build_home_page check-files network volumes +build_hub_clean: create_dep_dirs check-files network volumes export AUTH_CLASS='' && export OAUTH_FILE='.oauth.dummy.env' && \ - cd ./jupyterhub && docker-compose build --no-cache + docker-compose --env-file jupyterhub/.env -f jupyterhub/docker-compose.yml build --no-cache run_hub_dev: export AUTH_CLASS='jupyterhub.auth.DummyAuthenticator' && \ export OAUTH_FILE='.oauth.dummy.env' && \ - cd ./jupyterhub && docker-compose up + cd jupyterhub && docker-compose up & run_hub_staging: build_hub_clean build_clean check_files_staging kill_hub export AUTH_CLASS=oauthenticator.GoogleOAuthenticator && \ export OAUTH_FILE='.oauth.staging.env' && \ - cd ./jupyterhub && docker-compose up & + cd jupyterhub && docker-compose up & run_hub_prod: build_hub_clean build_clean check_files_prod kill_hub export AUTH_CLASS=oauthenticator.GoogleOAuthenticator && \ export OAUTH_FILE='.oauth.prod.env' && \ - cd ./jupyterhub && docker-compose up & + cd jupyterhub && docker-compose up & kill_hub: export AUTH_CLASS='' && \ diff --git a/README.md b/README.md index 758a0fdaf2..ceec68c164 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,36 @@ StochSS uses [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/#) as the - In addition to the single-user requirements, you will need [Docker Compose](https://docs.docker.com/compose/install/). -- [Optional] To set admins for JupyterHub, make a file called `userlist` in the `jupyterhub/` directory. On each line of this file place a username followed by the word 'admin'. For example: `myuser admin`. If using Google OAuth, the uesername will be a Gmail address. Navigate to `/hub/admin` to use the JupyterHub admin interface. +- [Optional] To set admins for JupyterHub, make a file called `userlist` in the `userlist/` directory. On each line of this file place a username followed by the word 'admin'. For example: `myuser admin`. If using Google OAuth, the username will be a Gmail address. Navigate to `/hub/admin` to use the JupyterHub admin interface. - [Optional] By default multi-user StochSS is set up to allocate 2 logical cpus per user, reserving 2 logical cpus for the hub container and underlying OS. You can define "power users" that are excluded from resource limitations using the same method as above for adding an admin, but instead of following the username with 'admin', use the keyword 'power' instead. +- [Optional] To disseminate messages to all users, make a JSON decodable file called `messages.json` in the `userlist/` directory. The contents of the `messages` file should be formatted as a list of dictionaries defining each message. Accepted keys are `message`, string containing the message to be display with tags for dates and time i.e. `StochSS Live! will be down for scheduled maintenance on __DATE__ from __START__ to __END__` an additional `__DATE__` tag can be added if the start and end dates differ, `start`, string representing the starting date and time i.e. `Sep 26, 2022 14:00 EST`, `end`, string representing the ending date and time i.e. `Sep 26, 2022 18:00 EST`, and `style`, a string containing a background color keyword i.e. `warning` or css i.e. `background-color: rgba(160, 32, 240, 0.5) !important;`. + +```python +[ + { + "message": "StochSS Live! will be down for scheduled maintenance on __DATE__ from __START__ to __END__", + "start": "Sep 26, 2022 14:00 EST", + "end": "Sep 26, 2022 18:00 EST", + "style": "info" + }, + { + "message": "StochSS Live! will be down for scheduled maintenance from" + }, + { + "message": "__DATE__ __START__ to __DATE__ __END__", + "start": "Sep 26, 2022 14:00 EST", + "end": "Sep 27, 2022 14:00 EST", + "style": "warning" + }, + { + "message": "StochSS Live! is down for scheduled maintenance", + "style": "background-color: rgba(160, 32, 240, 0.5) !important;" + } +] +``` + ### Run Locally To run JupyterHub locally run `make hub` and go to `http://127.0.0.1:8000/` . diff --git a/__version__.py b/__version__.py index 24f6d5b956..c3d296bd19 100644 --- a/__version__.py +++ b/__version__.py @@ -5,7 +5,7 @@ # @website https://github.com/stochss/stochss # ============================================================================= -__version__ = '2.4.10' +__version__ = '2.4.12' __title__ = 'StochSS' __description__ = 'StochSS is an integrated development environment (IDE) \ for simulation of biochemical networks.' diff --git a/client/app.js b/client/app.js index 786121e86f..65275f7076 100644 --- a/client/app.js +++ b/client/app.js @@ -167,21 +167,26 @@ let newWorkflow = (parent, mdlPath, isSpatial, type) => { if(document.querySelector('#newWorkflowModal')) { document.querySelector('#newWorkflowModal').remove() } + let typeCodes = { + "Ensemble Simulation": "_ES", + "Spatial Ensemble Simulation": "_SES", + "Parameter Sweep": "_PS" + } let self = parent; let ext = isSpatial ? /.smdl/g : /.mdl/g - let typeCode = type === "Ensemble Simulation" ? "_ES" : "_PS"; + let typeCode = typeCodes[type]; let name = mdlPath.split('/').pop().replace(ext, typeCode) let modal = $(modals.createWorkflowHtml(name, type)).modal(); let okBtn = document.querySelector('#newWorkflowModal .ok-model-btn'); let input = document.querySelector('#newWorkflowModal #workflowNameInput'); okBtn.disabled = false; - input.addEventListener("keyup", function (event) { + input.addEventListener("keyup", (event) => { if(event.keyCode === 13){ event.preventDefault(); okBtn.click(); } }); - input.addEventListener("input", function (e) { + input.addEventListener("input", (e) => { let endErrMsg = document.querySelector('#newWorkflowModal #workflowNameInputEndCharError') let charErrMsg = document.querySelector('#newWorkflowModal #workflowNameInputSpecCharError') let error = validateName(input.value) @@ -189,19 +194,19 @@ let newWorkflow = (parent, mdlPath, isSpatial, type) => { charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" }); - okBtn.addEventListener('click', function (e) { + okBtn.addEventListener('click', (e) => { modal.modal("hide"); - let wkflFile = input.value.trim() + ".wkfl"; + let wkflFile = `${input.value.trim()}.wkfl`; if(mdlPath.includes(".proj") && !mdlPath.includes(".wkgp")){ var wkflPath = path.join(path.dirname(mdlPath), "WorkflowGroup1.wkgp", wkflFile); }else{ var wkflPath = path.join(path.dirname(mdlPath), wkflFile); } - let queryString = "?path=" + wkflPath + "&model=" + mdlPath + "&type=" + type; + let queryString = `?path=${wkflPath}&model=${mdlPath}&type=${type}`; let endpoint = path.join(getApiPath(), "workflow/new") + queryString; getXHR(endpoint, { - success: function (err, response, body) { - window.location.href = path.join(getBasePath(), "stochss/workflow/edit") + "?path=" + body.path; + success: (err, response, body) => { + window.location.href = `${path.join(getBasePath(), "stochss/workflow/edit")}?path=${body.path}`; } }); }); @@ -246,6 +251,60 @@ let switchToEditTab = (view, section) => { } } +let maintenance = (view) => { + getXHR("stochss/api/message", { + always: (err, response, body) => { + if(body.messages.length === 0) { console.log(null) } + var html = ``; + body.messages.forEach((data) => { + let styles = { + "primary": "background-color: rgba(0, 123, 255, 0.5) !important;", + "secondary": "background-color: rgba(108, 117, 125, 0.5) !important;", + "light": "background-color: rgba(248, 249, 250, 0.5) !important;", + "dark": "background-color: rgba(52, 58, 64, 0.5) !important;", + "success": "background-color: rgba(40, 167, 69, 0.5) !important;", + "info": "background-color: rgba(23, 162, 184, 0.5) !important;", + "warning": "background-color: rgba(255, 193, 7, 0.5) !important;", + "danger": "background-color: rgba(220, 53, 69, 0.5) !important;" + } + if(data.start) { + let s_date = new Date(data.start); + let day = new Intl.DateTimeFormat('en-US', {weekday: 'short'}).format(s_date); + let mon = new Intl.DateTimeFormat('en-US', {month: 'short'}).format(s_date); + let s_day = `${day} ${mon} ${s_date.getDate()} ${s_date.getFullYear()}`; + data.message = data.message.replace("__DATE__", s_day); + + let tz = s_date.toString().split('(').pop().split(')')[0]; + var minutes = s_date.getMinutes() < 10 ? `0${s_date.getMinutes()}` : s_date.getMinutes(); + let m_start = `${s_date.getHours()}:${minutes} ${tz}`; + data.message = data.message.replace("__START__", m_start); + } + if(data.end) { + let e_date = new Date(data.end); + let day = new Intl.DateTimeFormat('en-US', {weekday: 'short'}).format(e_date); + let mon = new Intl.DateTimeFormat('en-US', {month: 'short'}).format(e_date); + let e_day = `${day} ${mon} ${e_date.getDate()} ${e_date.getFullYear()}`; + data.message = data.message.replace("__DATE__", e_day); + + let tz = e_date.toString().split('(').pop().split(')')[0]; + var minutes = e_date.getMinutes() < 10 ? `0${e_date.getMinutes()}` : e_date.getMinutes(); + let m_end = `${e_date.getHours()}:${minutes} ${tz}`; + data.message = data.message.replace("__END__", m_end); + } + if(!data.style) { + var style = styles.warning; + }else if(Object.keys(styles).includes(data.style)) { + var style = styles[data.style]; + }else { + var style = data.style; + } + html += `
" + body.Message + "
Please re-run this job to get this plot
"; - $(self.queryByHook(type + "-plot")).html(message); + error: (err, response, body) => { + if(type === "spatial") { + $(this.queryByHook("spatial-plot-loading-msg")).css("display", "none"); + } + $(this.queryByHook(`${type}-plot-spinner`)).css("display", "none"); + let message = `${body.Message}
Please re-run this job to get this plot
`; + $(this.queryByHook(`${type}-plot`)).html(message); } }); } @@ -200,6 +215,12 @@ module.exports = View.extend({ data['sim_type'] = "GillesPy2_PS"; data['data_keys'] = this.getDataKeys(true); data['plt_key'] = type === "ts-psweep-mp" ? "mltplplt" : this.tsPlotData.type; + }else if(type === "spatial") { + data['sim_type'] = "SpatialPy"; + data['data_keys'] = { + target: this.spatialTarget, index: this.targetIndex, mode: this.targetMode + }; + data['plt_key'] = type; }else { data['sim_type'] = "GillesPy2"; data['data_keys'] = {}; @@ -228,6 +249,27 @@ module.exports = View.extend({ this.model.settings.parameterSweepSettings.speciesOfInterest = species; this.getPlot('psweep') }, + getPlotForTarget: function (e) { + let value = e.target.value; + if(["0", "1", "2"].includes(value)) { + this.spatialTarget = "v"; + this.targetIndex = number(value); + }else{ + this.spatialTarget = value; + this.targetIndex = null; + } + if(!["type", "v", "nu", "rho", "mass"].includes(this.spatialTarget)) { + $(this.queryByHook('job-results-mode')).css('display', 'inline-block'); + this.renderTargetModeView(); + }else{ + $(this.queryByHook('job-results-mode')).css('display', 'none'); + } + this.getPlot("spatial"); + }, + getPlotForTargetMode: function (e) { + this.targetMode = e.target.value; + this.getPlot("spatial"); + }, getPlotKey: function (type) { if(type === "psweep") { let realizations = this.model.settings.simulationSettings.realizations; @@ -274,18 +316,17 @@ module.exports = View.extend({ this.getPlot(type); }, handleConvertToNotebookClick: function (e) { - let self = this; - if(this.titleType === "Ensemble Simulation") { - var type = "gillespy"; - }else if(this.titleType === "Parameter Sweep" && this.model.settings.parameterSweepSettings.parameters.length > 1) { - var type = "2d_parameter_sweep"; - }else{ - var type = "1d_parameter-sweep"; + let is2D = this.model.settings.parameterSweepSettings.parameters.length > 1; + let types = { + "Ensemble Simulation": "gillespy", + "Spatial Ensemble Simulation": "spatialpy", + "Parameter Sweep": is2D ? "2d_parameter_sweep" : "1d_parameter-sweep" } - let queryStr = "?path=" + this.model.directory + "&type=" + type; - let endpoint = path.join(app.getApiPath(), "workflow/notebook") + queryStr; + let type = types[this.titleType]; + let queryStr = `?path=${this.model.directory}&type=${type}`; + let endpoint = `${path.join(app.getApiPath(), "workflow/notebook")}${queryStr}`; app.getXHR(endpoint, { - success: function (err, response, body) { + success: (err, response, body) => { window.open(path.join(app.getBasePath(), "notebooks", body.FilePath)); } }); @@ -369,13 +410,15 @@ module.exports = View.extend({ }); }, plotFigure: function (figure, type) { - let self = this; - let hook = type + "-plot"; + let hook = `${type}-plot`; let el = this.queryByHook(hook); Plotly.newPlot(el, figure); - $(this.queryByHook(type + "-plot-spinner")).css("display", "none"); - $(this.queryByHook(type + "-edit-plot")).prop("disabled", false); - $(this.queryByHook(type + "-download")).prop("disabled", false); + if(type === "spatial") { + $(this.queryByHook("spatial-plot-loading-msg")).css("display", "none"); + } + $(this.queryByHook(`${type}-plot-spinner`)).css("display", "none"); + $(this.queryByHook(`${type}-edit-plot`)).prop("disabled", false); + $(this.queryByHook(`${type}-download`)).prop("disabled", false); if(type === "trajectories" || (this.tsPlotData && this.tsPlotData.type === "trajectories")) { $(this.queryByHook("multiple-plots")).prop("disabled", false); } @@ -464,6 +507,44 @@ module.exports = View.extend({ ); } }, + renderTargetOfInterestView: function () { + let species = this.model.model.species.map((specie) => { + return [specie.name, specie.name]; + }); + let header = Boolean(species) ? "Variables" : "Variables (empty)"; + let properties = [ + ["type", "Type"], ["0", "X Velocity"], ["1", "Y Velocity"], ["2", "Z Velocity"], + ["rho", "Density"], ["mass", "Mass"], ["nu", "Viscosity"] + ]; + let options = [ + {groupName: "Properties", options: properties}, + {groupName: header, options: species} + ]; + let targetOfInterestView = new SelectView({ + name: 'target', + required: true, + eagerValidate: true, + groupOptions: options, + value: this.spatialTarget + }); + app.registerRenderSubview(this, targetOfInterestView, "target-of-interest-list"); + }, + renderTargetModeView: function () { + if(this.targetModeView) { return; } + let options = [ + ["discrete", "Population (outputs: absolute value)"], + ["discrete-concentration", "Population (outputs: scaled by volume)"], + ["continuous", "Concentration"] + ]; + this.targetModeView = new SelectView({ + name: 'target-mode', + required: true, + eagerValidate: true, + options: options, + value: this.targetMode + }); + app.registerRenderSubview(this, this.targetModeView, "target-mode-list"); + }, setTitle: function (e) { this.plotArgs['title'] = e.target.value for (var storageKey in this.plots) { diff --git a/client/lib/plotly.js b/client/lib/plotly.js deleted file mode 100644 index 97596bac67..0000000000 --- a/client/lib/plotly.js +++ /dev/null @@ -1,7 +0,0 @@ -/** -* plotly.js v1.47.1 -* Copyright 2012-2019, Plotly, Inc. -* All rights reserved. -* Licensed under the MIT license -*/ -!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Plotly=t()}}(function(){return function(){return function t(e,r,n){function i(o,s){if(!r[o]){if(!e[o]){var l="function"==typeof require&&require;if(!s&&l)return l(o,!0);if(a)return a(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var u=r[o]={exports:{}};e[o][0].call(u.exports,function(t){return i(e[o][1][t]||t)},u,u.exports,t,e,r,n)}return r[o].exports}for(var a="function"==typeof require&&require,o=0;o=p0)&&!(p1>=hi)",["p0","p1"]),g=u("lo===p0",["p0"]),v=u("lo =r)for(n=r;++an&&(n=r)}else for(;++a=r)for(n=r;++an&&(n=r);return n},t.mean=function(t,e){var r,n=t.length,i=n,a=-1,o=0;if(null==e)for(;++a l.length)return r;var i,a=c[n-1];return null!=e&&n>=l.length?i=r.entries():(i=[],r.each(function(e,r){i.push({key:r,values:t(e,n)})})),null!=a?i.sort(function(t,e){return a(t.key,e.key)}):i}(u(t,0,a,o),0)},key:function(t){return l.push(t),s},sortKeys:function(t){return c[l.length-1]=t,s},sortValues:function(e){return t=e,s},rollup:function(t){return e=t,s}}},t.set=c,t.map=r,t.keys=function(t){var e=[];for(var r in t)e.push(r);return e},t.values=function(t){var e=[];for(var r in t)e.push(t[r]);return e},t.entries=function(t){var e=[];for(var r in t)e.push({key:r,value:t[r]});return e},Object.defineProperty(t,"__esModule",{value:!0})}("object"==typeof r&&"undefined"!=typeof e?r:n.d3=n.d3||{})},{}],145:[function(t,e,r){var n;n=this,function(t){"use strict";function e(t,e,r){t.prototype=e.prototype=r,r.constructor=t}function r(t,e){var r=Object.create(t.prototype);for(var n in e)r[n]=e[n];return r}function n(){}var i="\\s*([+-]?\\d+)\\s*",a="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*",o="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*",s=/^#([0-9a-f]{3})$/,l=/^#([0-9a-f]{6})$/,c=new RegExp("^rgb\\("+[i,i,i]+"\\)$"),u=new RegExp("^rgb\\("+[o,o,o]+"\\)$"),h=new RegExp("^rgba\\("+[i,i,i,a]+"\\)$"),f=new RegExp("^rgba\\("+[o,o,o,a]+"\\)$"),p=new RegExp("^hsl\\("+[a,o,o]+"\\)$"),d=new RegExp("^hsla\\("+[a,o,o,a]+"\\)$"),g={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};function v(t){var e;return t=(t+"").trim().toLowerCase(),(e=s.exec(t))?new _((e=parseInt(e[1],16))>>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):(e=l.exec(t))?m(parseInt(e[1],16)):(e=c.exec(t))?new _(e[1],e[2],e[3],1):(e=u.exec(t))?new _(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=h.exec(t))?y(e[1],e[2],e[3],e[4]):(e=f.exec(t))?y(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=p.exec(t))?k(e[1],e[2]/100,e[3]/100,1):(e=d.exec(t))?k(e[1],e[2]/100,e[3]/100,e[4]):g.hasOwnProperty(t)?m(g[t]):"transparent"===t?new _(NaN,NaN,NaN,0):null}function m(t){return new _(t>>16&255,t>>8&255,255&t,1)}function y(t,e,r,n){return n<=0&&(t=e=r=NaN),new _(t,e,r,n)}function x(t){return t instanceof n||(t=v(t)),t?new _((t=t.rgb()).r,t.g,t.b,t.opacity):new _}function b(t,e,r,n){return 1===arguments.length?x(t):new _(t,e,r,null==n?1:n)}function _(t,e,r,n){this.r=+t,this.g=+e,this.b=+r,this.opacity=+n}function w(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function k(t,e,r,n){return n<=0?t=e=r=NaN:r<=0||r>=1?t=e=NaN:e<=0&&(t=NaN),new T(t,e,r,n)}function A(t,e,r,i){return 1===arguments.length?function(t){if(t instanceof T)return new T(t.h,t.s,t.l,t.opacity);if(t instanceof n||(t=v(t)),!t)return new T;if(t instanceof T)return t;var e=(t=t.rgb()).r/255,r=t.g/255,i=t.b/255,a=Math.min(e,r,i),o=Math.max(e,r,i),s=NaN,l=o-a,c=(o+a)/2;return l?(s=e===o?(r-i)/l+6*(r0&&c<1?0:s,new T(s,l,c,t.opacity)}(t):new T(t,e,r,null==i?1:i)}function T(t,e,r,n){this.h=+t,this.s=+e,this.l=+r,this.opacity=+n}function M(t,e,r){return 255*(t<60?e+(r-e)*t/60:t<180?r:t<240?e+(r-e)*(240-t)/60:e)}e(n,v,{displayable:function(){return this.rgb().displayable()},hex:function(){return this.rgb().hex()},toString:function(){return this.rgb()+""}}),e(_,b,r(n,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new _(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new _(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return 0<=this.r&&this.r<=255&&0<=this.g&&this.g<=255&&0<=this.b&&this.b<=255&&0<=this.opacity&&this.opacity<=1},hex:function(){return"#"+w(this.r)+w(this.g)+w(this.b)},toString:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}})),e(T,A,r(n,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new T(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new T(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,r=this.l,n=r+(r<.5?r:1-r)*e,i=2*r-n;return new _(M(t>=240?t-240:t+120,i,n),M(t,i,n),M(t<120?t+240:t-120,i,n),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1}}));var S=Math.PI/180,E=180/Math.PI,C=.96422,L=1,z=.82521,O=4/29,I=6/29,D=3*I*I,P=I*I*I;function R(t){if(t instanceof B)return new B(t.l,t.a,t.b,t.opacity);if(t instanceof G){if(isNaN(t.h))return new B(t.l,0,0,t.opacity);var e=t.h*S;return new B(t.l,Math.cos(e)*t.c,Math.sin(e)*t.c,t.opacity)}t instanceof _||(t=x(t));var r,n,i=U(t.r),a=U(t.g),o=U(t.b),s=N((.2225045*i+.7168786*a+.0606169*o)/L);return i===a&&a===o?r=n=s:(r=N((.4360747*i+.3850649*a+.1430804*o)/C),n=N((.0139322*i+.0971045*a+.7141733*o)/z)),new B(116*s-16,500*(r-s),200*(s-n),t.opacity)}function F(t,e,r,n){return 1===arguments.length?R(t):new B(t,e,r,null==n?1:n)}function B(t,e,r,n){this.l=+t,this.a=+e,this.b=+r,this.opacity=+n}function N(t){return t>P?Math.pow(t,1/3):t/D+O}function j(t){return t>I?t*t*t:D*(t-O)}function V(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function U(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function q(t){if(t instanceof G)return new G(t.h,t.c,t.l,t.opacity);if(t instanceof B||(t=R(t)),0===t.a&&0===t.b)return new G(NaN,0,t.l,t.opacity);var e=Math.atan2(t.b,t.a)*E;return new G(e<0?e+360:e,Math.sqrt(t.a*t.a+t.b*t.b),t.l,t.opacity)}function H(t,e,r,n){return 1===arguments.length?q(t):new G(t,e,r,null==n?1:n)}function G(t,e,r,n){this.h=+t,this.c=+e,this.l=+r,this.opacity=+n}e(B,F,r(n,{brighter:function(t){return new B(this.l+18*(null==t?1:t),this.a,this.b,this.opacity)},darker:function(t){return new B(this.l-18*(null==t?1:t),this.a,this.b,this.opacity)},rgb:function(){var t=(this.l+16)/116,e=isNaN(this.a)?t:t+this.a/500,r=isNaN(this.b)?t:t-this.b/200;return new _(V(3.1338561*(e=C*j(e))-1.6168667*(t=L*j(t))-.4906146*(r=z*j(r))),V(-.9787684*e+1.9161415*t+.033454*r),V(.0719453*e-.2289914*t+1.4052427*r),this.opacity)}})),e(G,H,r(n,{brighter:function(t){return new G(this.h,this.c,this.l+18*(null==t?1:t),this.opacity)},darker:function(t){return new G(this.h,this.c,this.l-18*(null==t?1:t),this.opacity)},rgb:function(){return R(this).rgb()}}));var Y=-.14861,W=1.78277,X=-.29227,Z=-.90649,$=1.97294,J=$*Z,K=$*W,Q=W*X-Z*Y;function tt(t,e,r,n){return 1===arguments.length?function(t){if(t instanceof et)return new et(t.h,t.s,t.l,t.opacity);t instanceof _||(t=x(t));var e=t.r/255,r=t.g/255,n=t.b/255,i=(Q*n+J*e-K*r)/(Q+J-K),a=n-i,o=($*(r-i)-X*a)/Z,s=Math.sqrt(o*o+a*a)/($*i*(1-i)),l=s?Math.atan2(o,a)*E-120:NaN;return new et(l<0?l+360:l,s,i,t.opacity)}(t):new et(t,e,r,null==n?1:n)}function et(t,e,r,n){this.h=+t,this.s=+e,this.l=+r,this.opacity=+n}e(et,tt,r(n,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new et(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new et(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=isNaN(this.h)?0:(this.h+120)*S,e=+this.l,r=isNaN(this.s)?0:this.s*e*(1-e),n=Math.cos(t),i=Math.sin(t);return new _(255*(e+r*(Y*n+W*i)),255*(e+r*(X*n+Z*i)),255*(e+r*($*n)),this.opacity)}})),t.color=v,t.rgb=b,t.hsl=A,t.lab=F,t.hcl=H,t.lch=function(t,e,r,n){return 1===arguments.length?q(t):new G(r,e,t,null==n?1:n)},t.gray=function(t,e){return new B(t,0,0,null==e?1:e)},t.cubehelix=tt,Object.defineProperty(t,"__esModule",{value:!0})}("object"==typeof r&&"undefined"!=typeof e?r:n.d3=n.d3||{})},{}],146:[function(t,e,r){var n;n=this,function(t){"use strict";var e={value:function(){}};function r(){for(var t,e=0,r=arguments.length,i={};e =i.length)return e;var n=[],o=a[r++];return e.forEach(function(e,i){n.push({key:e,values:t(i,r)})}),o?n.sort(function(t,e){return o(t.key,e.key)}):n}(o(t.map,e,0),0)},n.key=function(t){return i.push(t),n},n.sortKeys=function(t){return a[i.length-1]=t,n},n.sortValues=function(t){return e=t,n},n.rollup=function(t){return r=t,n},n},t.set=function(t){var e=new L;if(t)for(var r=0,n=t.length;r 360?t-=360:t<0&&(t+=360),t<60?n+(i-n)*t/60:t<180?i:t<240?n+(i-n)*(240-t)/60:n}(t))}return t=isNaN(t)?0:(t%=360)<0?t+360:t,e=isNaN(e)?0:e<0?0:e>1?1:e,n=2*(r=r<0?0:r>1?1:r)-(i=r<=.5?r*(1+e):r+e-r*e),new ae(a(t+120),a(t),a(t-120))}function Gt(e,r,n){return this instanceof Gt?(this.h=+e,this.c=+r,void(this.l=+n)):arguments.length<2?e instanceof Gt?new Gt(e.h,e.c,e.l):ee(e instanceof Xt?e.l:(e=fe((e=t.rgb(e)).r,e.g,e.b)).l,e.a,e.b):new Gt(e,r,n)}qt.brighter=function(t){return t=Math.pow(.7,arguments.length?t:1),new Ut(this.h,this.s,this.l/t)},qt.darker=function(t){return t=Math.pow(.7,arguments.length?t:1),new Ut(this.h,this.s,t*this.l)},qt.rgb=function(){return Ht(this.h,this.s,this.l)},t.hcl=Gt;var Yt=Gt.prototype=new Vt;function Wt(t,e,r){return isNaN(t)&&(t=0),isNaN(e)&&(e=0),new Xt(r,Math.cos(t*=Ct)*e,Math.sin(t)*e)}function Xt(t,e,r){return this instanceof Xt?(this.l=+t,this.a=+e,void(this.b=+r)):arguments.length<2?t instanceof Xt?new Xt(t.l,t.a,t.b):t instanceof Gt?Wt(t.h,t.c,t.l):fe((t=ae(t)).r,t.g,t.b):new Xt(t,e,r)}Yt.brighter=function(t){return new Gt(this.h,this.c,Math.min(100,this.l+Zt*(arguments.length?t:1)))},Yt.darker=function(t){return new Gt(this.h,this.c,Math.max(0,this.l-Zt*(arguments.length?t:1)))},Yt.rgb=function(){return Wt(this.h,this.c,this.l).rgb()},t.lab=Xt;var Zt=18,$t=.95047,Jt=1,Kt=1.08883,Qt=Xt.prototype=new Vt;function te(t,e,r){var n=(t+16)/116,i=n+e/500,a=n-r/200;return new ae(ie(3.2404542*(i=re(i)*$t)-1.5371385*(n=re(n)*Jt)-.4985314*(a=re(a)*Kt)),ie(-.969266*i+1.8760108*n+.041556*a),ie(.0556434*i-.2040259*n+1.0572252*a))}function ee(t,e,r){return t>0?new Gt(Math.atan2(r,e)*Lt,Math.sqrt(e*e+r*r),t):new Gt(NaN,NaN,t)}function re(t){return t>.206893034?t*t*t:(t-4/29)/7.787037}function ne(t){return t>.008856?Math.pow(t,1/3):7.787037*t+4/29}function ie(t){return Math.round(255*(t<=.00304?12.92*t:1.055*Math.pow(t,1/2.4)-.055))}function ae(t,e,r){return this instanceof ae?(this.r=~~t,this.g=~~e,void(this.b=~~r)):arguments.length<2?t instanceof ae?new ae(t.r,t.g,t.b):ue(""+t,ae,Ht):new ae(t,e,r)}function oe(t){return new ae(t>>16,t>>8&255,255&t)}function se(t){return oe(t)+""}Qt.brighter=function(t){return new Xt(Math.min(100,this.l+Zt*(arguments.length?t:1)),this.a,this.b)},Qt.darker=function(t){return new Xt(Math.max(0,this.l-Zt*(arguments.length?t:1)),this.a,this.b)},Qt.rgb=function(){return te(this.l,this.a,this.b)},t.rgb=ae;var le=ae.prototype=new Vt;function ce(t){return t<16?"0"+Math.max(0,t).toString(16):Math.min(255,t).toString(16)}function ue(t,e,r){var n,i,a,o=0,s=0,l=0;if(n=/([a-z]+)\((.*)\)/.exec(t=t.toLowerCase()))switch(i=n[2].split(","),n[1]){case"hsl":return r(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return e(de(i[0]),de(i[1]),de(i[2]))}return(a=ge.get(t))?e(a.r,a.g,a.b):(null==t||"#"!==t.charAt(0)||isNaN(a=parseInt(t.slice(1),16))||(4===t.length?(o=(3840&a)>>4,o|=o>>4,s=240&a,s|=s>>4,l=15&a,l|=l<<4):7===t.length&&(o=(16711680&a)>>16,s=(65280&a)>>8,l=255&a)),e(o,s,l))}function he(t,e,r){var n,i,a=Math.min(t/=255,e/=255,r/=255),o=Math.max(t,e,r),s=o-a,l=(o+a)/2;return s?(i=l<.5?s/(o+a):s/(2-o-a),n=t==o?(e-r)/s+(e=o)d(c,u,C--,L=L-o|0);else if(L>=0)d(s,l,E--,L);else if(L<=-o){L=-L-o|0;for(var z=0;zs&&(r=s-l),a=r;a>=0;a--){for(var h=!0,f=0;f0&&l.push(["index[",h,"]-=s",h].join("")),l.push(["++index[",u,"]"].join(""))),l.push("}")}return l.join("\n")}function a(t,e,r){for(var n=t.body,i=[],a=[],o=0;oh;)f.pop(),--p;var d,g=new Array(p+1);for(a=0;a<=p;++a)(d=g[a]=[]).x0=a>0?f[a-1]:u,d.x1=a=1?f:t<=-1?-f:Math.asin(t)}function g(t){return t.innerRadius}function v(t){return t.outerRadius}function m(t){return t.startAngle}function y(t){return t.endAngle}function x(t){return t&&t.padAngle}function b(t,e,r,n,i,a,s){var l=t-r,u=e-n,h=(s?a:-a)/c(l*l+u*u),f=h*u,p=-h*l,d=t+f,g=e+p,v=r+f,m=n+p,y=(d+v)/2,x=(g+m)/2,b=v-d,_=m-g,w=b*b+_*_,k=i-a,A=d*m-v*g,T=(_<0?-1:1)*c(o(0,k*k*w-A*A)),M=(A*_-b*T)/w,S=(-A*b-_*T)/w,E=(A*_+b*T)/w,C=(-A*b+_*T)/w,L=M-y,z=S-x,O=E-y,I=C-x;return L*L+z*z>O*O+I*I&&(M=E,S=C),{cx:M,cy:S,x01:-f,y01:-p,x11:M*(i/k-1),y11:S*(i/k-1)}}function _(t){this._context=t}function w(t){return new _(t)}function k(t){return t[0]}function A(t){return t[1]}function T(){var t=k,n=A,i=r(!0),a=null,o=w,s=null;function l(r){var l,c,u,h=r.length,f=!1;for(null==a&&(s=o(u=e.path())),l=0;l<=h;++l)!(l=0&&"xmlns"!==(r=t.slice(0,e))&&(t=t.slice(e+1)),J.hasOwnProperty(r)?{space:J[r],local:t}:t}},W.attr=function(e,r){if(arguments.length<2){if("string"==typeof e){var n=this.node();return(e=t.ns.qualify(e)).local?n.getAttributeNS(e.space,e.local):n.getAttribute(e)}for(r in e)this.each(K(r,e[r]));return this}return this.each(K(e,r))},W.classed=function(t,e){if(arguments.length<2){if("string"==typeof t){var r=this.node(),n=(t=et(t)).length,i=-1;if(e=r.classList){for(;++i