diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 9561fb10..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include README.rst diff --git a/Pipfile b/Pipfile index 08586d9e..b0169ae9 100644 --- a/Pipfile +++ b/Pipfile @@ -15,26 +15,34 @@ pyyaml = "*" nltk = "*" redis = "*" ipython = "*" -"pybind11" = "*" +pybind11 = "*" python-dotenv = "*" click = "*" flask = "*" -misc = {git = "https://bitbucket.org/bmmalone/misc.git"} -autosklearn = {ref = "development",git = "https://github.com/automl/auto-sklearn.git"} flask-redis = "*" sklearn = "*" seaborn = "*" matplotlib = "*" -"jinja2" = "*" +jinja2 = "*" flask-login = "*" passlib = "*" +plotly = "*" pytest = "*" apscheduler = "*" waitress = "*" ipdb = "*" +sqlalchemy = "*" + [dev-packages] pylint = "*" [requires] python_version = "3.7" + +[packages.misc] +git = "https://bitbucket.org/bmmalone/misc.git" + +[packages.autosklearn] +ref = "development" +git = "https://github.com/automl/auto-sklearn.git" diff --git a/Pipfile.lock b/Pipfile.lock index 41f7aea1..dce26dcd 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "44afde0a263f4e70f13826133f3d9488a48995ff659ec1ea9b7cf1de8079f186" + "sha256": "a237915d15e1207680b344cb8c0547d82c1a6db9f685c3fd229487ae5d0dd074" }, "pipfile-spec": 6, "requires": { @@ -16,39 +16,24 @@ ] }, "default": { - "appnope": { - "hashes": [ - "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", - "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" - ], - "markers": "sys_platform == 'darwin'", - "version": "==0.1.0" - }, "apscheduler": { "hashes": [ - "sha256:529afb7909e08416132891188cbfea5351eb35e4a684b67e983d819e8d01a6b0", - "sha256:cde18f6dbffa1b75aff67fd7fe423a3020cb0363f6c67bd45f24306d90898231" + "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244", + "sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526" ], "index": "pypi", - "version": "==3.6.1" - }, - "atomicwrites": { - "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" - ], - "version": "==1.3.0" + "version": "==3.6.3" }, "attrs": { "hashes": [ - "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", - "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", + "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" ], - "version": "==19.1.0" + "version": "==19.3.0" }, "autosklearn": { "git": "https://github.com/automl/auto-sklearn.git", - "ref": "a54192c672192a67dc62129aac981cda0e30aeba" + "ref": "development" }, "backcall": { "hashes": [ @@ -59,11 +44,11 @@ }, "click": { "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc", + "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a" ], "index": "pypi", - "version": "==7.0" + "version": "==7.1.1" }, "cycler": { "hashes": [ @@ -74,44 +59,48 @@ }, "cython": { "hashes": [ - "sha256:07efba7b32c082c519b75e3b03821c2f32848e2b3e9986c784bbd8ffaf0666d7", - "sha256:08db41daf18fabf7b7a85e39aa26954f6246994540043194af026c0df65a4942", - "sha256:19bbe3caf885a1d2e2c30eacc10d1e45dbbefb156493fe1d5d1adc1668cc1269", - "sha256:1c574f2f2ba760b82b2bcf6262e77e75589247dc5ef796a3ff1b2213e50ee452", - "sha256:1dfe672c686e34598bdbaa93c3b30acb3720ae9258232a4f68ba04ee9969063d", - "sha256:283faea84e6c4e54c3f5c8ff89aa2b6c1c3a813aad4f6d48ed3b9cc9043ef9f9", - "sha256:2a145888d0942e7c36e86a7b7c7e2923cb9f7055805a3b72dcb137e3efdb0979", - "sha256:3f75065936e16569d6e13dfd76de988f5eabeae460aa54770c9b961ab6f747fc", - "sha256:4d78124f5f281f1d5d5b7919cbbc65a7073ff93562def81ee78a8307e6e72494", - "sha256:5ba4d088b8e5d59b8a5911ca9c72952acf3c83296b57daf75af92fb2af1e8423", - "sha256:6b19daeda1d5d1dfc973b291246f6a63a663b20c33980724d6d073c562719536", - "sha256:790c7dc80fd1c3e38acefe06027e2f5a8466c128c7e47c6e140fd5316132574d", - "sha256:7f8c4e648881454ba3ba0bcf3b21a9e1878a67d20ea2b8d9ec1c4c628592ab6b", - "sha256:8bcd3f597290f9902548d6355898d7e376e7f3762f89db9cd50b2b58429df9e8", - "sha256:8ffb18f71972a5c718a8600d9f52e3507f0d6fb72a978e03270d34a7035c98fb", - "sha256:92f025df1cb391e09f65775598c7dfb7efad72d74713775db54e267f62ca94a1", - "sha256:93cf1c72472a2fd0ef4c52f6074dab08fc28d475b9c824ba73a52701f7a48ae1", - "sha256:9a7fa692cdc967fdbf6a053c1975137d01f6935dede2ef222c71840b290caf79", - "sha256:a68eb0c1375f2401de881692b30370a51e550052b8e346b2f71bbdbdc74a214f", - "sha256:ac3b7a12ddd52ea910ee3a041e6bc65df7a52f0ba7bd10fb7123502af482c152", - "sha256:b402b700edaf571a0bae18ec35d5b71c266873a6616412b672435c10b6d8f041", - "sha256:c29d069a4a30f472482343c866f7486731ad638ef9af92bfe5fca9c7323d638e", - "sha256:d822311498f185db449b687336b4e5db7638c8d8b03bdf10ae91d74e23c7cc0c", - "sha256:dccc8df9e1ac158b06777bbaaeb4516f245f9b147701ae25e6023960e4a0c2a3", - "sha256:e31f4b946c2765b2f35440fdb4b00c496dfc5babc53c7ae61966b41171d1d59f", - "sha256:eb43f9e582cc221ee2832e25ea6fe5c06f2acc9da6353c562e922f107db12af8", - "sha256:f07822248110fd6213db8bc2745fdbbccef6f2b3d18ac91a7fba29c6bc575da5", - "sha256:ff69854f123b959d4ae14bd5330714bb9ee4360052992dc0fbd0a3dee4261f95" + "sha256:01d566750e7c08e5f094419f8d1ee90e7fa286d8d77c4569748263ed5f05280a", + "sha256:072cb90e2fe4b5cc27d56de12ec5a00311eee781c2d2e3f7c98a82319103c7ed", + "sha256:0e078e793a9882bf48194b8b5c9b40c75769db1859cd90b210a4d7bf33cda2b1", + "sha256:1a3842be21d1e25b7f3440a0c881ef44161937273ea386c30c0e253e30c63740", + "sha256:1dc973bdea03c65f03f41517e4f0fc2b717d71cfbcf4ec34adac7e5bee71303e", + "sha256:214a53257c100e93e7673e95ab448d287a37626a3902e498025993cc633647ae", + "sha256:30462d61e7e290229a64e1c3682b4cc758ffc441e59cc6ce6fae059a05df305b", + "sha256:34004f60b1e79033b0ca29b9ab53a86c12bcaab56648b82fbe21c007cd73d867", + "sha256:34c888a57f419c63bef63bc0911c5bb407b93ed5d6bdeb1587dca2cd1dd56ad1", + "sha256:3dd0cba13b36ff969232930bd6db08d3da0798f1fac376bd1fa4458f4b55d802", + "sha256:4e5acf3b856a50d0aaf385f06a7b56a128a296322a9740f5f279c96619244308", + "sha256:60d859e1efa5cc80436d58aecd3718ff2e74b987db0518376046adedba97ac30", + "sha256:61e505379497b624d6316dd67ef8100aaadca0451f48f8c6fff8d622281cd121", + "sha256:6f6de0bee19c70cb01e519634f0c35770de623006e4876e649ee4a960a147fec", + "sha256:77ac051b7caf02938a32ea0925f558534ab2a99c0c98c681cc905e3e8cba506e", + "sha256:7e4d74515d92c4e2be7201aaef7a51705bd3d5957df4994ddfe1b252195b5e27", + "sha256:7ea18a5c87cacdd6e4feacf8badf13643775b6f69c3aa8b50417834b9ce0e627", + "sha256:993837bbf0849e3b176e1ef6a50e9b8c2225e895501b85d56f4bb65a67f5ea25", + "sha256:9a5f0cf8b95c0c058e413679a650f70dcc97764ccb2a6d5ccc6b08d44c9b334c", + "sha256:9f2839396d21d5537bc9ff53772d44db39b0efb6bf8b6cac709170483df53a5b", + "sha256:b8ba4b4ee3addc26bc595a51b6240b05a80e254b946d624fff6506439bc323d1", + "sha256:bb6d90180eff72fc5a30099c442b8b0b5a620e84bf03ef32a55e3f7bd543f32e", + "sha256:c3d778304209cc39f8287da22f2180f34d2c2ee46cd55abd82e48178841b37b1", + "sha256:c562bc316040097e21357e783286e5eca056a5b2750e89d9d75f9541c156b6dc", + "sha256:d114f9c0164df8fcd2880e4ba96986d7b0e7218f6984acc4989ff384c5d3d512", + "sha256:d282b030ed5c736e4cdb1713a0c4fad7027f4e3959dc4b8fdb7c75042d83ed1b", + "sha256:d8c73fe0ec57a0e4fdf5d2728b5e18b63980f55f1baf51b6bac6a73e8cbb7186", + "sha256:e5c8f4198e25bc4b0e4a884377e0c0e46ca273993679e3bcc212ef96d4211b83", + "sha256:e7f1dcc0e8c3e18fa2fddca4aecdf71c5651555a8dc9a0cd3a1d164cbce6cb35", + "sha256:ea3b61bff995de49b07331d1081e0056ea29901d3e995aa989073fe2b1be0cb7", + "sha256:ea5f987b4da530822fa797cf2f010193be77ea9e232d07454e3194531edd8e58", + "sha256:f91b16e73eca996f86d1943be3b2c2b679b03e068ed8c82a5506c1e65766e4a6" ], "index": "pypi", - "version": "==0.29.13" + "version": "==0.29.15" }, "decorator": { "hashes": [ - "sha256:86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", - "sha256:f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6" + "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", + "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" ], - "version": "==4.4.0" + "version": "==4.4.2" }, "docopt": { "hashes": [ @@ -130,10 +119,11 @@ }, "flask-login": { "hashes": [ - "sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec" + "sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b", + "sha256:7451b5001e17837ba58945aead261ba425fdf7b4f0448777e597ddab39f4fba0" ], "index": "pypi", - "version": "==0.4.1" + "version": "==0.5.0" }, "flask-redis": { "hashes": [ @@ -145,18 +135,26 @@ }, "importlib-metadata": { "hashes": [ - "sha256:23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", - "sha256:80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3" + "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", + "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" ], - "version": "==0.19" + "markers": "python_version < '3.8'", + "version": "==1.5.0" + }, + "ipdb": { + "hashes": [ + "sha256:77fb1c2a6fccdfee0136078c9ed6fe547ab00db00bebff181f1e8c9e13418d49" + ], + "index": "pypi", + "version": "==0.13.2" }, "ipython": { "hashes": [ - "sha256:1d3a1692921e932751bc1a1f7bb96dc38671eeefdc66ed33ee4cbc57e92a410e", - "sha256:537cd0176ff6abd06ef3e23f2d0c4c2c8a4d9277b7451544c6cbf56d1c79a83d" + "sha256:ca478e52ae1f88da0102360e57e528b92f3ae4316aabac80a2cd7f7ab2efb48a", + "sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333" ], "index": "pypi", - "version": "==7.7.0" + "version": "==7.13.0" }, "ipython-genutils": { "hashes": [ @@ -174,31 +172,33 @@ }, "jedi": { "hashes": [ - "sha256:53c850f1a7d3cfcd306cc513e2450a54bdf5cacd7604b74e42dd1f0758eaaf36", - "sha256:e07457174ef7cb2342ff94fa56484fe41cec7ef69b0059f01d3f812379cb6f7c" + "sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2", + "sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5" ], - "version": "==0.14.1" + "version": "==0.16.0" }, "jinja2": { "hashes": [ - "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", - "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" + "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", + "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" ], "index": "pypi", - "version": "==2.10.1" + "version": "==2.11.1" }, "joblib": { "hashes": [ - "sha256:21e0c34a69ad7fde4f2b1f3402290e9ec46f545f15f1541c582edfe05d87b63a", - "sha256:315d6b19643ec4afd4c41c671f9f2d65ea9d787da093487a81ead7b0bac94524" + "sha256:0630eea4f5664c463f23fbf5dcfc54a2bc6168902719fa8e19daf033022786c8", + "sha256:bdb4fd9b72915ffb49fde2229ce482dd7ae79d842ed8c2b4c932441495af1403" ], "index": "pypi", - "version": "==0.13.2" + "version": "==0.14.1" }, "kiwisolver": { "hashes": [ "sha256:05b5b061e09f60f56244adc885c4a7867da25ca387376b02c1efc29cc16bcd0f", + "sha256:210d8c39d01758d76c2b9a693567e1657ec661229bc32eac30761fa79b2474b0", "sha256:26f4fbd6f5e1dabff70a9ba0d2c4bd30761086454aa30dddc5b52764ee4852b7", + "sha256:3b15d56a9cd40c52d7ab763ff0bc700edbb4e1a298dc43715ecccd605002cf11", "sha256:3b2378ad387f49cbb328205bda569b9f87288d6bc1bf4cd683c34523a2341efe", "sha256:400599c0fe58d21522cae0e8b22318e09d9729451b17ee61ba8e1e7c0346565c", "sha256:47b8cb81a7d18dbaf4fed6a61c3cecdb5adec7b4ac292bddb0d016d57e8507d5", @@ -207,16 +207,22 @@ "sha256:5a52e1b006bfa5be04fe4debbcdd2688432a9af4b207a3f429c74ad625022641", "sha256:5c7ca4e449ac9f99b3b9d4693debb1d6d237d1542dd6a56b3305fe8a9620f883", "sha256:682e54f0ce8f45981878756d7203fd01e188cc6c8b2c5e2cf03675390b4534d5", + "sha256:76275ee077772c8dde04fb6c5bc24b91af1bb3e7f4816fd1852f1495a64dad93", "sha256:79bfb2f0bd7cbf9ea256612c9523367e5ec51d7cd616ae20ca2c90f575d839a2", "sha256:7f4dd50874177d2bb060d74769210f3bce1af87a8c7cf5b37d032ebf94f0aca3", "sha256:8944a16020c07b682df861207b7e0efcd2f46c7488619cb55f65882279119389", "sha256:8aa7009437640beb2768bfd06da049bad0df85f47ff18426261acecd1cf00897", + "sha256:9105ce82dcc32c73eb53a04c869b6a4bc756b43e4385f76ea7943e827f529e4d", + "sha256:933df612c453928f1c6faa9236161a1d999a26cd40abf1dc5d7ebbc6dbfb8fca", "sha256:939f36f21a8c571686eb491acfffa9c7f1ac345087281b412d63ea39ca14ec4a", + "sha256:9491578147849b93e70d7c1d23cb1229458f71fc79c51d52dce0809b2ca44eea", "sha256:9733b7f64bd9f807832d673355f79703f81f0b3e52bfce420fc00d8cb28c6a6c", "sha256:a02f6c3e229d0b7220bd74600e9351e18bc0c361b05f29adae0d10599ae0e326", "sha256:a0c0a9f06872330d0dd31b45607197caab3c22777600e88031bfe66799e70bb0", + "sha256:aa716b9122307c50686356cfb47bfbc66541868078d0c801341df31dca1232a9", "sha256:acc4df99308111585121db217681f1ce0eecb48d3a828a2f9bbf9773f4937e9e", "sha256:b64916959e4ae0ac78af7c3e8cef4becee0c0e9694ad477b4c6b3a536de6a544", + "sha256:d22702cadb86b6fcba0e6b907d9f84a312db9cd6934ee728144ce3018e715ee1", "sha256:d3fcf0819dc3fea58be1fd1ca390851bdb719a549850e708ed858503ff25d995", "sha256:d52e3b1868a4e8fd18b5cb15055c76820df514e26aa84cc02f593d99fef6707f", "sha256:db1a5d3cc4ae943d674718d6c47d2d82488ddd94b93b9e12d24aabdbfe48caee", @@ -224,7 +230,8 @@ "sha256:e8bf074363ce2babeb4764d94f8e65efd22e6a7c74860a4f05a6947afc020ff2", "sha256:f16814a4a96dc04bf1da7d53ee8d5b1d6decfc1a92a63349bb15d37b6a263dd9", "sha256:f2b22153870ca5cf2ab9c940d7bc38e8e9089fa0f7e5856ea195e1cf4ff43d5a", - "sha256:f790f8b3dff3d53453de6a7b7ddd173d2e020fb160baff578d578065b108a05f" + "sha256:f790f8b3dff3d53453de6a7b7ddd173d2e020fb160baff578d578065b108a05f", + "sha256:fe51b79da0062f8e9d49ed0182a626a7dc7a0cbca0328f612c6ee5e4711c81e4" ], "version": "==1.1.0" }, @@ -234,13 +241,16 @@ "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", @@ -257,38 +267,45 @@ "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" ], "version": "==1.1.1" }, "matplotlib": { "hashes": [ - "sha256:1febd22afe1489b13c6749ea059d392c03261b2950d1d45c17e3aed812080c93", - "sha256:31a30d03f39528c79f3a592857be62a08595dec4ac034978ecd0f814fa0eec2d", - "sha256:4442ce720907f67a79d45de9ada47be81ce17e6c2f448b3c64765af93f6829c9", - "sha256:796edbd1182cbffa7e1e7a97f1e141f875a8501ba8dd834269ae3cd45a8c976f", - "sha256:934e6243df7165aad097572abf5b6003c77c9b6c480c3c4de6f2ef1b5fdd4ec0", - "sha256:bab9d848dbf1517bc58d1f486772e99919b19efef5dd8596d4b26f9f5ee08b6b", - "sha256:c1fe1e6cdaa53f11f088b7470c2056c0df7d80ee4858dadf6cbe433fcba4323b", - "sha256:e5b8aeca9276a3a988caebe9f08366ed519fff98f77c6df5b64d7603d0e42e36", - "sha256:ec6bd0a6a58df3628ff269978f4a4b924a0d371ad8ce1f8e2b635b99e482877a" + "sha256:0711b07920919951b2c508a773c433cbe07bdad952ea84ed9d18ca7853ccbe8b", + "sha256:0ab307e610302971012dc2387c97fc68e58c8eb00045a2c735da1b16353a3e3f", + "sha256:651d76daf9168250370d4befb09f79875daa2224a9096d97dfc3ed764c842be4", + "sha256:8e931015769322ee6860cabb8f975f628788e851092fd5edbdb065b5a516e3af", + "sha256:97a03e73f9ab71db8e4084894550c3af420c8ab1989b5e1306261b17576bf61b", + "sha256:9d174cc9681184023a7d520079eb0c085208761c6562710c1de7263d08217ab6", + "sha256:b21479a4478070c1c0f460e1bf1b65341e6a70ae0da905fcee836651450c66bb", + "sha256:b93377c6720e7db9cbba57e856a21aae2ff707677a6ee6b3b9d485f22ed82697", + "sha256:be937f34047bc09ed22d6a19d970fdc61d5d3191aa62f3262fc7f308e6d2e7f9", + "sha256:d281862a68b0bfce8f9e02a8e5acaa5cfbec37f37320f59b52eaf54b6423ec13", + "sha256:d5287cfcabad6f0f71a2627c1bbb6fb0cddacb9844f6c91f210604faa508f562", + "sha256:d75f5e952562f5e494ae92c1f917fc96c2ce09305a7c1bdc2e6502d3c61fbdc3", + "sha256:ee8acb1d4ee204e5cfe361d8f00d7e52c68f81c099b6c6048a3c76bf2c6b46e6", + "sha256:fc84f7c7cf1c5a9dbceadb7546818228f019d3b113ce5e362120c895fbba2944" ], "index": "pypi", - "version": "==3.1.1" + "version": "==3.2.0" }, "misc": { - "git": "https://bitbucket.org/bmmalone/misc.git", - "ref": "87c53ef87d4fd5bfac631643465175f241afd43f" + "git": "https://bitbucket.org/bmmalone/misc.git" }, "more-itertools": { "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c", + "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507" ], - "version": "==7.2.0" + "version": "==8.2.0" }, "nltk": { "hashes": [ + "sha256:a08bdb4b8a1c13de16743068d9eb61c8c71c2e5d642e8e08205c528035843f82", "sha256:bed45551259aa2101381bbdd5df37d44ca2669c5c3dad72439fa459b29137d94" ], "index": "pypi", @@ -296,75 +313,80 @@ }, "numpy": { "hashes": [ - "sha256:03e311b0a4c9f5755da7d52161280c6a78406c7be5c5cc7facfbcebb641efb7e", - "sha256:0cdd229a53d2720d21175012ab0599665f8c9588b3b8ffa6095dd7b90f0691dd", - "sha256:312bb18e95218bedc3563f26fcc9c1c6bfaaf9d453d15942c0839acdd7e4c473", - "sha256:464b1c48baf49e8505b1bb754c47a013d2c305c5b14269b5c85ea0625b6a988a", - "sha256:5adfde7bd3ee4864536e230bcab1c673f866736698724d5d28c11a4d63672658", - "sha256:7724e9e31ee72389d522b88c0d4201f24edc34277999701ccd4a5392e7d8af61", - "sha256:8d36f7c53ae741e23f54793ffefb2912340b800476eb0a831c6eb602e204c5c4", - "sha256:910d2272403c2ea8a52d9159827dc9f7c27fb4b263749dca884e2e4a8af3b302", - "sha256:951fefe2fb73f84c620bec4e001e80a80ddaa1b84dce244ded7f1e0cbe0ed34a", - "sha256:9588c6b4157f493edeb9378788dcd02cb9e6a6aeaa518b511a1c79d06cbd8094", - "sha256:9ce8300950f2f1d29d0e49c28ebfff0d2f1e2a7444830fbb0b913c7c08f31511", - "sha256:be39cca66cc6806652da97103605c7b65ee4442c638f04ff064a7efd9a81d50a", - "sha256:c3ab2d835b95ccb59d11dfcd56eb0480daea57cdf95d686d22eff35584bc4554", - "sha256:eb0fc4a492cb896346c9e2c7a22eae3e766d407df3eb20f4ce027f23f76e4c54", - "sha256:ec0c56eae6cee6299f41e780a0280318a93db519bbb2906103c43f3e2be1206c", - "sha256:f4e4612de60a4f1c4d06c8c2857cdcb2b8b5289189a12053f37d3f41f06c60d0" + "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", + "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e", + "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc", + "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc", + "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a", + "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa", + "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3", + "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121", + "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971", + "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26", + "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd", + "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480", + "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec", + "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77", + "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57", + "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07", + "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572", + "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73", + "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca", + "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474", + "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5" ], "index": "pypi", - "version": "==1.17.0" + "version": "==1.18.1" }, "packaging": { "hashes": [ - "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", - "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" + "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", + "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" ], - "version": "==19.1" + "version": "==20.3" }, "pandas": { "hashes": [ - "sha256:074a032f99bb55d178b93bd98999c971542f19317829af08c99504febd9e9b8b", - "sha256:20f1728182b49575c2f6f681b3e2af5fac9e84abdf29488e76d569a7969b362e", - "sha256:2745ba6e16c34d13d765c3657bb64fa20a0e2daf503e6216a36ed61770066179", - "sha256:32c44e5b628c48ba17703f734d59f369d4cdcb4239ef26047d6c8a8bfda29a6b", - "sha256:3b9f7dcee6744d9dcdd53bce19b91d20b4311bf904303fa00ef58e7df398e901", - "sha256:544f2033250980fb6f069ce4a960e5f64d99b8165d01dc39afd0b244eeeef7d7", - "sha256:58f9ef68975b9f00ba96755d5702afdf039dea9acef6a0cfd8ddcde32918a79c", - "sha256:9023972a92073a495eba1380824b197ad1737550fe1c4ef8322e65fe58662888", - "sha256:914341ad2d5b1ea522798efa4016430b66107d05781dbfe7cf05eba8f37df995", - "sha256:9d151bfb0e751e2c987f931c57792871c8d7ff292bcdfcaa7233012c367940ee", - "sha256:b932b127da810fef57d427260dde1ad54542c136c44b227a1e367551bb1a684b", - "sha256:cfb862aa37f4dd5be0730731fdb8185ac935aba8b51bf3bd035658111c9ee1c9", - "sha256:de7ecb4b120e98b91e8a2a21f186571266a8d1faa31d92421e979c7ca67d8e5c", - "sha256:df7e1933a0b83920769611c5d6b9a1bf301e3fa6a544641c6678c67621fe9843" + "sha256:23e177d43e4bf68950b0f8788b6a2fef2f478f4ec94883acb627b9264522a98a", + "sha256:2530aea4fe46e8df7829c3f05e0a0f821c893885d53cb8ac9b89cc67c143448c", + "sha256:303827f0bb40ff610fbada5b12d50014811efcc37aaf6ef03202dc3054bfdda1", + "sha256:3b019e3ea9f5d0cfee0efabae2cfd3976874e90bcc3e97b29600e5a9b345ae3d", + "sha256:3c07765308f091d81b6735d4f2242bb43c332cc3461cae60543df6b10967fe27", + "sha256:5036d4009012a44aa3e50173e482b664c1fae36decd277c49e453463798eca4e", + "sha256:6f38969e2325056f9959efbe06c27aa2e94dd35382265ad0703681d993036052", + "sha256:74a470d349d52b9d00a2ba192ae1ee22155bb0a300fd1ccb2961006c3fa98ed3", + "sha256:7d77034e402165b947f43050a8a415aa3205abfed38d127ea66e57a2b7b5a9e0", + "sha256:7f9a509f6f11fa8b9313002ebdf6f690a7aa1dd91efd95d90185371a0d68220e", + "sha256:942b5d04762feb0e55b2ad97ce2b254a0ffdd344b56493b04a627266e24f2d82", + "sha256:a9fbe41663416bb70ed05f4e16c5f377519c0dc292ba9aa45f5356e37df03a38", + "sha256:d10e83866b48c0cdb83281f786564e2a2b51a7ae7b8a950c3442ad3c9e36b48c", + "sha256:e2140e1bbf9c46db9936ee70f4be6584d15ff8dc3dfff1da022d71227d53bad3" ], "index": "pypi", - "version": "==0.25.0" + "version": "==1.0.1" }, "parso": { "hashes": [ - "sha256:63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", - "sha256:666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c" + "sha256:0c5659e0c6eba20636f99a04f469798dca8da279645ce5c387315b2c23912157", + "sha256:8515fc12cfca6ee3aa59138741fc5624d62340c97e401c74875769948d4f2995" ], - "version": "==0.5.1" + "version": "==0.6.2" }, "passlib": { "hashes": [ - "sha256:3d948f64138c25633613f303bcc471126eae67c04d5e3f6b7b8ce6242f8653e0", - "sha256:43526aea08fa32c6b6dbbbe9963c4c767285b78147b7437597f992812f69d280" + "sha256:68c35c98a7968850e17f1b6892720764cc7eed0ef2b7cb3116a89a28e43fe177", + "sha256:8d666cef936198bc2ab47ee9b0410c94adf2ba798e5a84bf220be079ae7ab6a8" ], "index": "pypi", - "version": "==1.7.1" + "version": "==1.7.2" }, "pexpect": { "hashes": [ - "sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", - "sha256:9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb" + "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", + "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" ], "markers": "sys_platform != 'win32'", - "version": "==4.7.0" + "version": "==4.8.0" }, "pickleshare": { "hashes": [ @@ -373,20 +395,27 @@ ], "version": "==0.7.5" }, + "plotly": { + "hashes": [ + "sha256:b6b1e13e7f04dc9a9f714ab8ecc63d00a4207a736c9835912c96b6884002be1e", + "sha256:e6eab2b6010921f5fb998860125b90748e581de66ebc6107b686829417d98fc4" + ], + "index": "pypi", + "version": "==4.5.4" + }, "pluggy": { "hashes": [ - "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", - "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], - "version": "==0.12.0" + "version": "==0.13.1" }, "prompt-toolkit": { "hashes": [ - "sha256:11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780", - "sha256:2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1", - "sha256:977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55" + "sha256:859e1b205b6cf6a51fa57fa34202e45365cf58f8338f0ee9f4e84a4165b37d5b", + "sha256:ebe6b1b08c888b84c50d7f93dee21a09af39860144ff6130aadbd61ae8d29783" ], - "version": "==2.0.9" + "version": "==3.0.4" }, "ptyprocess": { "hashes": [ @@ -397,145 +426,161 @@ }, "py": { "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", + "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" ], - "version": "==1.8.0" + "version": "==1.8.1" }, "pybind11": { "hashes": [ - "sha256:199a915e0f81b5a593d1a13a18f137f59a6111f0049543211d936d26dab34324", - "sha256:5531dee811310ff02ff69fe4f45feb56d845154ba692b8e4660ae2c478ee313a" + "sha256:06398d054acd33d3b89d4b12000fadc36e946001438425a96c9e30048655ab96", + "sha256:72e6def53fb491f7f4e92692029d2e7bb5a0783314f20d80222735ff10a75758" ], "index": "pypi", - "version": "==2.3.0" + "version": "==2.4.3" }, "pygments": { "hashes": [ - "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", - "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" + "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", + "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" ], - "version": "==2.4.2" + "version": "==2.6.1" }, "pyparsing": { "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", + "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" ], - "version": "==2.4.2" + "version": "==2.4.6" }, "pytest": { "hashes": [ - "sha256:6ef6d06de77ce2961156013e9dff62f1b2688aa04d0dc244299fe7d67e09370d", - "sha256:a736fed91c12681a7b34617c8fcefe39ea04599ca72c608751c31d89579a3f77" + "sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d", + "sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6" ], "index": "pypi", - "version": "==5.0.1" + "version": "==5.3.5" }, "python-dateutil": { "hashes": [ - "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", - "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" ], - "version": "==2.8.0" + "version": "==2.8.1" }, "python-dotenv": { "hashes": [ - "sha256:debd928b49dbc2bf68040566f55cdb3252458036464806f4094487244e2a4093", - "sha256:f157d71d5fec9d4bd5f51c82746b6344dffa680ee85217c123f4a0c8117c4544" + "sha256:81822227f771e0cab235a2939f0f265954ac4763cafd806d845801c863bf372f", + "sha256:92b3123fb2d58a284f76cc92bfe4ee6c502c32ded73e8b051c4f6afc8b6751ed" ], "index": "pypi", - "version": "==0.10.3" + "version": "==0.12.0" }, "pytz": { "hashes": [ - "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", - "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", + "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" ], - "version": "==2019.1" + "version": "==2019.3" }, "pyyaml": { "hashes": [ - "sha256:57acc1d8533cbe51f6662a55434f0dbecfa2b9eaf115bede8f6fd00115a0c0d3", - "sha256:588c94b3d16b76cfed8e0be54932e5729cc185caffaa5a451e7ad2f7ed8b4043", - "sha256:68c8dd247f29f9a0d09375c9c6b8fdc64b60810ebf07ba4cdd64ceee3a58c7b7", - "sha256:70d9818f1c9cd5c48bb87804f2efc8692f1023dac7f1a1a5c61d454043c1d265", - "sha256:86a93cccd50f8c125286e637328ff4eef108400dd7089b46a7be3445eecfa391", - "sha256:a0f329125a926876f647c9fa0ef32801587a12328b4a3c741270464e3e4fa778", - "sha256:a3c252ab0fa1bb0d5a3f6449a4826732f3eb6c0270925548cac342bc9b22c225", - "sha256:b4bb4d3f5e232425e25dda21c070ce05168a786ac9eda43768ab7f3ac2770955", - "sha256:cd0618c5ba5bda5f4039b9398bb7fb6a317bb8298218c3de25c47c4740e4b95e", - "sha256:ceacb9e5f8474dcf45b940578591c7f3d960e82f926c707788a570b51ba59190", - "sha256:fe6a88094b64132c4bb3b631412e90032e8cfe9745a58370462240b8cb7553cd" + "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6", + "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf", + "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5", + "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e", + "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811", + "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e", + "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d", + "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20", + "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689", + "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994", + "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615" ], "index": "pypi", - "version": "==5.1.1" + "version": "==5.3" }, "redis": { "hashes": [ - "sha256:067fd58d00c62145258bce0ef807ad605c3bc81795e8c883d5ed74688abb4f3d", - "sha256:9d9daf304c2ad7ca9c82e8868a1ac09382b6b3a7d905ba497d2151157df8378c" + "sha256:0dcfb335921b88a850d461dc255ff4708294943322bd55de6cfd68972490ca1f", + "sha256:b205cffd05ebfd0a468db74f0eedbff8df1a7bfc47521516ade4692991bb0833" ], "index": "pypi", - "version": "==3.3.2" + "version": "==3.4.1" + }, + "retrying": { + "hashes": [ + "sha256:08c039560a6da2fe4f2c426d0766e284d3b736e355f8dd24b37367b0bb41973b" + ], + "version": "==1.3.3" }, "scikit-learn": { "hashes": [ - "sha256:1ac81293d261747c25ea5a0ee8cd2bb1f3b5ba9ec05421a7f9f0feb4eb7c4116", - "sha256:289361cf003d90b007f5066b27fcddc2d71324c82f1c88e316fedacb0dfdd516", - "sha256:3a14d0abd4281fc3fd2149c486c3ec7cedad848b8d5f7b6f61522029d65a29f8", - "sha256:5083a5e50d9d54548e4ada829598ae63a05651dd2bb319f821ffd9e8388384a6", - "sha256:777cdd5c077b7ca9cb381396c81990cf41d2fa8350760d3cad3b4c460a7db644", - "sha256:8bf2ff63da820d09b96b18e88f9625228457bff8df4618f6b087e12442ef9e15", - "sha256:8d319b71c449627d178f21c57614e21747e54bb3fc9602b6f42906c3931aa320", - "sha256:928050b65781fea9542dfe9bfe02d8c4f5530baa8472ec60782ea77347d2c836", - "sha256:92c903613ff50e22aa95d589f9fff5deb6f34e79f7f21f609680087f137bb524", - "sha256:ae322235def5ce8fae645b439e332e6f25d34bb90d6a6c8e261f17eb476457b7", - "sha256:c1cd6b29eb1fd1cc672ac5e4a8be5f6ea936d094a3dc659ada0746d6fac750b1", - "sha256:c41a6e2685d06bcdb0d26533af2540f54884d40db7e48baed6a5bcbf1a7cc642", - "sha256:d07fcb0c0acbc043faa0e7cf4d2037f71193de3fb04fb8ed5c259b089af1cf5c", - "sha256:d146d5443cda0a41f74276e42faf8c7f283fef49e8a853b832885239ef544e05", - "sha256:eb2b7bed0a26ba5ce3700e15938b28a4f4513578d3e54a2156c29df19ac5fd01", - "sha256:eb9b8ebf59eddd8b96366428238ab27d05a19e89c5516ce294abc35cea75d003" - ], - "version": "==0.21.3" + "sha256:1bf45e62799b6938357cfce19f72e3751448c4b27010e4f98553da669b5bbd86", + "sha256:267ad874b54c67b479c3b45eb132ef4a56ab2b27963410624a413a4e2a3fc388", + "sha256:2d1bb83d6c51a81193d8a6b5f31930e2959c0e1019d49bdd03f54163735dae4b", + "sha256:349ba3d837fb3f7cb2b91486c43713e4b7de17f9e852f165049b1b7ac2f81478", + "sha256:3f4d8eea3531d3eaf613fa33f711113dfff6021d57a49c9d319af4afb46f72f0", + "sha256:4990f0e166292d2a0f0ee528233723bcfd238bfdb3ec2512a9e27f5695362f35", + "sha256:57538d138ba54407d21e27c306735cbd42a6aae0df6a5a30c7a6edde46b0017d", + "sha256:5b722e8bb708f254af028dc2da86d23df5371cba57e24f889b672e7b15423caa", + "sha256:6043e2c4ccfc68328c331b0fc19691be8fb02bd76d694704843a23ad651de902", + "sha256:672ea38eb59b739a8907ec063642b486bcb5a2073dda5b72b7983eeaf1fd67c1", + "sha256:73207dca6e70f8f611f28add185cf3a793c8232a1722f21d82259560dc35cd50", + "sha256:83fc104a799cb340054e485c25dfeee712b36f5638fb374eba45a9db490f16ff", + "sha256:8416150ab505f1813da02cdbdd9f367b05bfc75cf251235015bb09f8674358a0", + "sha256:84e759a766c315deb5c85139ff879edbb0aabcddb9358acf499564ed1c21e337", + "sha256:8ed66ab27b3d68e57bb1f315fc35e595a5c4a1f108c3420943de4d18fc40e615", + "sha256:a7f8aa93f61aaad080b29a9018db93ded0586692c03ddf2122e47dd1d3a14e1b", + "sha256:ddd3bf82977908ff69303115dd5697606e669d8a7eafd7d83bb153ef9e11bd5e", + "sha256:de9933297f8659ee3bb330eafdd80d74cd73d5dab39a9026b65a4156bc479063", + "sha256:ea91a70a992ada395efc3d510cf011dc2d99dc9037bb38cd1cb00e14745005f5", + "sha256:eb4c9f0019abb374a2e55150f070a333c8f990b850d1eb4dfc2765fc317ffc7c", + "sha256:ffce8abfdcd459e72e5b91727b247b401b22253cbd18d251f842a60e26262d6f" + ], + "version": "==0.22.2.post1" }, "scipy": { "hashes": [ - "sha256:03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", - "sha256:09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", - "sha256:10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", - "sha256:1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", - "sha256:409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", - "sha256:4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", - "sha256:6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", - "sha256:826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", - "sha256:a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", - "sha256:a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", - "sha256:adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", - "sha256:b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", - "sha256:c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", - "sha256:c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", - "sha256:c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", - "sha256:db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee" + "sha256:00af72998a46c25bdb5824d2b729e7dabec0c765f9deb0b504f928591f5ff9d4", + "sha256:0902a620a381f101e184a958459b36d3ee50f5effd186db76e131cbefcbb96f7", + "sha256:1e3190466d669d658233e8a583b854f6386dd62d655539b77b3fa25bfb2abb70", + "sha256:2cce3f9847a1a51019e8c5b47620da93950e58ebc611f13e0d11f4980ca5fecb", + "sha256:3092857f36b690a321a662fe5496cb816a7f4eecd875e1d36793d92d3f884073", + "sha256:386086e2972ed2db17cebf88610aab7d7f6e2c0ca30042dc9a89cf18dcc363fa", + "sha256:71eb180f22c49066f25d6df16f8709f215723317cc951d99e54dc88020ea57be", + "sha256:770254a280d741dd3436919d47e35712fb081a6ff8bafc0f319382b954b77802", + "sha256:787cc50cab3020a865640aba3485e9fbd161d4d3b0d03a967df1a2881320512d", + "sha256:8a07760d5c7f3a92e440ad3aedcc98891e915ce857664282ae3c0220f3301eb6", + "sha256:8d3bc3993b8e4be7eade6dcc6fd59a412d96d3a33fa42b0fa45dc9e24495ede9", + "sha256:9508a7c628a165c2c835f2497837bf6ac80eb25291055f56c129df3c943cbaf8", + "sha256:a144811318853a23d32a07bc7fd5561ff0cac5da643d96ed94a4ffe967d89672", + "sha256:a1aae70d52d0b074d8121333bc807a485f9f1e6a69742010b33780df2e60cfe0", + "sha256:a2d6df9eb074af7f08866598e4ef068a2b310d98f87dc23bd1b90ec7bdcec802", + "sha256:bb517872058a1f087c4528e7429b4a44533a902644987e7b2fe35ecc223bc408", + "sha256:c5cac0c0387272ee0e789e94a570ac51deb01c796b37fb2aad1fb13f85e2f97d", + "sha256:cc971a82ea1170e677443108703a2ec9ff0f70752258d0e9f5433d00dda01f59", + "sha256:dba8306f6da99e37ea08c08fef6e274b5bf8567bb094d1dbe86a20e532aca088", + "sha256:dc60bb302f48acf6da8ca4444cfa17d52c63c5415302a9ee77b3b21618090521", + "sha256:dee1bbf3a6c8f73b6b218cb28eed8dd13347ea2f87d572ce19b289d6fd3fbc59" ], "index": "pypi", - "version": "==1.3.0" + "version": "==1.4.1" }, "seaborn": { "hashes": [ - "sha256:42e627b24e849c2d3bbfd059e00005f6afbc4a76e4895baf44ae23fe8a4b09a5", - "sha256:76c83f794ca320fb6b23a7c6192d5e185a5fcf4758966a0c0a54baee46d41e2f" + "sha256:59fe414e138d7d5ea08b0feb01b86caf4682e36fa748e3987730523a89aecbb9", + "sha256:bdf7714ef7d4603e6325d3902e80a46d6149561e1cc237ac08a1c05c3f55a996" ], "index": "pypi", - "version": "==0.9.0" + "version": "==0.10.0" }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.12.0" + "version": "==1.14.0" }, "sklearn": { "hashes": [ @@ -544,20 +589,27 @@ "index": "pypi", "version": "==0.0" }, + "sqlalchemy": { + "hashes": [ + "sha256:c4cca4aed606297afbe90d4306b49ad3a4cd36feb3f87e4bfd655c57fd9ef445" + ], + "index": "pypi", + "version": "==1.3.15" + }, "tqdm": { "hashes": [ - "sha256:14a285392c32b6f8222ecfbcd217838f88e11630affe9006cd0e94c7eff3cb61", - "sha256:25d4c0ea02a305a688e7e9c2cdc8f862f989ef2a4701ab28ee963295f5b109ab" + "sha256:0d8b5afb66e23d80433102e9bd8b5c8b65d34c2a2255b2de58d97bd2ea8170fd", + "sha256:f35fb121bafa030bd94e74fcfd44f3c2830039a2ddef7fc87ef1c2d205237b24" ], "index": "pypi", - "version": "==4.32.2" + "version": "==4.43.0" }, "traitlets": { "hashes": [ - "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", - "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" + "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44", + "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7" ], - "version": "==4.3.2" + "version": "==4.3.3" }, "tzlocal": { "hashes": [ @@ -568,33 +620,129 @@ }, "waitress": { "hashes": [ - "sha256:4e2a6e6fca56d6d3c279f68a2b2cc9b4798d834ea3c3a9db3e2b76b6d66f4526", - "sha256:90fe750cd40b282fae877d3c866255d485de18e8a232e93de42ebd9fb750eebb" + "sha256:045b3efc3d97c93362173ab1dfc159b52cfa22b46c3334ffc805dbdbf0e4309e", + "sha256:77ff3f3226931a1d7d8624c5371de07c8e90c7e5d80c5cc660d72659aaf23f38" ], "index": "pypi", - "version": "==1.3.0" + "version": "==1.4.3" }, "wcwidth": { "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603", + "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8" ], - "version": "==0.1.7" + "version": "==0.1.8" }, "werkzeug": { "hashes": [ - "sha256:87ae4e5b5366da2347eb3116c0e6c681a0e939a33b2805e2c0cbd282664932c4", - "sha256:a13b74dd3c45f758d4ebdb224be8f1ab8ef58b3c0ffc1783a8c7d9f4f50227e6" + "sha256:169ba8a33788476292d04186ab33b01d6add475033dfc07215e6d219cc077096", + "sha256:6dc65cf9091cf750012f56f2cad759fa9e879f511b5ff8685e456b4e3bf90d16" ], - "version": "==0.15.5" + "version": "==1.0.0" }, "zipp": { "hashes": [ - "sha256:4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a", - "sha256:8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec" + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], - "version": "==0.5.2" + "version": "==3.1.0" } }, - "develop": {} + "develop": { + "astroid": { + "hashes": [ + "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", + "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42" + ], + "version": "==2.3.3" + }, + "isort": { + "hashes": [ + "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", + "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" + ], + "version": "==4.3.21" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", + "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", + "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", + "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", + "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", + "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", + "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", + "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", + "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", + "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", + "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", + "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", + "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", + "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", + "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", + "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", + "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", + "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", + "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", + "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", + "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" + ], + "version": "==1.4.3" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "pylint": { + "hashes": [ + "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", + "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4" + ], + "index": "pypi", + "version": "==2.4.4" + }, + "six": { + "hashes": [ + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + ], + "version": "==1.14.0" + }, + "typed-ast": { + "hashes": [ + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" + ], + "markers": "implementation_name == 'cpython' and python_version < '3.8'", + "version": "==1.4.1" + }, + "wrapt": { + "hashes": [ + "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" + ], + "version": "==1.11.2" + } + } } diff --git a/data_warehouse/create_redis_load_script.py b/data_warehouse/create_redis_load_script.py deleted file mode 100644 index 3c8b9681..00000000 --- a/data_warehouse/create_redis_load_script.py +++ /dev/null @@ -1,65 +0,0 @@ -#! /usr/bin/env python3 - -import argparse -import csv -import datetime -import time -import pandas as pd - -import tqdm - -import logging -import misc.logging_utils as logging_utils -logger = logging.getLogger(__name__) - -def print_db_statement(line, entities, out): - (patient_id, quarter_id, date_stamp, time_stamp, key, value) = line # line.strip().split(",") - entity_type = entities.get(key) - if entity_type is None: - return None - - if entity_type == 'String': - kv = "{}.{}".format(key, value) - #r.sadd(kv, patient_id) - out.write("SADD {} {}\n".format(kv, patient_id)) - elif entity_type in ['Integer', 'Double']: - #r.zadd(key, value=patient_id) - out.write("ZADD {} {} {}\n".format(key, value, patient_id)) - elif entity_type == 'null': - value = time.mktime(datetime.datetime.strptime(date_stamp, "%Y-%m-%d").timetuple()) - - value = max(value, 0) - #r.zadd(key, patient_id=value) - out.write("ZADD {} {} {}\n".format(key, value, patient_id)) - -def main(): - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description="") - - parser.add_argument('entities') - parser.add_argument('data') - parser.add_argument('out') - - logging_utils.add_logging_options(parser) - args = parser.parse_args() - logging_utils.update_logging(args) - entities_df = pd.read_csv(args.entities, names=['name', 'value_type']) - - entities = { - row['name']: row['value_type'] - for (index, row) in entities_df.iterrows() - } - # total = 64946577 # is it the number of lines? - # if total is defined as (len(list(csv_data)), then for some reason data_iter becomes empty. - with open(args.data) as data, open(args.out, 'w') as out: - csv_data = csv.reader(data) - data_iter = tqdm.tqdm(csv_data)#, total=total) - for line in data_iter: - try: - print_db_statement(line, entities, out) - except: - msg = "Problem: {}".format(line) - logger.warning(msg) - -if __name__ == '__main__': - main() diff --git a/data_warehouse/data_warehouse_utils.py b/data_warehouse/data_warehouse_utils.py index ce931ef3..8fc80727 100755 --- a/data_warehouse/data_warehouse_utils.py +++ b/data_warehouse/data_warehouse_utils.py @@ -1,111 +1,24 @@ import collections -import pickle - import numpy as np import pandas as pd import sklearn.cluster import sklearn.metrics.pairwise import sklearn.mixture import sklearn.preprocessing -import tqdm - -import data_warehouse.redis_rwh as rwh - import misc.parallel as parallel import misc.utils as utils -import logging -logger = logging.getLogger(__name__) - -def get_entity_maps(entity_file): - - entity_df = pd.read_csv(entity_file) - - entity_index_map = { - entity['name']: i for i, entity in entity_df.iterrows() - } - - rev_entity_index_map = { - i: entity['name'] for i, entity in entity_df.iterrows() - } - - entity_type_map = { - entity['name']: entity['value_type'] for i, entity in entity_df.iterrows() - } - - return entity_index_map, rev_entity_index_map, entity_type_map - -def unpickle_entity_info(entity_info_file): - - with open(entity_info_file, "rb") as f: - entity_info = pickle.load(f) - - return entity_info - - -def get_itemset_names(fim_result, rev_entity_index_map): - - itemset_indices = fim_result[0] - count = fim_result[1] - - itemset_names = [] - - for item in itemset_indices: - entity_name = rev_entity_index_map[item] - itemset_names.append(entity_name) - - return itemset_names - -def get_co_occurrence_matrix(sparse_patient_info): - - num_patients = sparse_patient_info.shape[0] - num_entities = sparse_patient_info.shape[1] - patient_observed_values = np.zeros(num_patients, dtype=object) - - co_occurrence_matrix = np.zeros((num_entities, num_entities), dtype=int) - - for i in tqdm.tqdm(range(num_patients)): - patient_observed_values_i = list(sparse_patient_info[i].nonzero()[1]) - num_observed_entities = len(patient_observed_values_i) - - for j in range(num_observed_entities): - entity_j = patient_observed_values_i[j] - - for k in range(i+1, num_observed_entities): - entity_k = patient_observed_values_i[k] - - co_occurrence_matrix[entity_j, entity_k] += 1 - - #msg = "Setting [{}, {}]: {}".format(entity_j, entity_k, co_occurrence_matrix[entity_j, entity_k]) - #print(msg) - - patient_observed_values[i] = patient_observed_values_i - - return patient_observed_values, co_occurrence_matrix - -def get_co_occurrence_graph(co_occurrence_matrix): - - num_entities = co_occurrence_matrix.shape[0] - co_occurrence_graph = DimacsGraph(num_entities, weighted=True, directed=False) - - for i in tqdm.tqdm(range(num_entities)): - for j in range(i+1, num_entities): - - edge_weight = co_occurrence_matrix[i, j] - - if edge_weight != 0: - co_occurrence_graph.addEdge(i, j, edge_weight) +import data_warehouse.redis_rwh as rwh - return co_occurrence_graph def clean_entity_names(entity_names): """ Remove prefixes and similar from the entity names - + Parameters ---------- entity_names: pd.Series The raw entity names - + Returns ------- cleaned_entity_names: pd.Series @@ -114,6 +27,7 @@ def clean_entity_names(entity_names): entity_names = entity_names.str.replace("diagnostik.labor.", "") return entity_names + ALLOWED_MISSING = { "drop", "mean", @@ -121,6 +35,7 @@ def clean_entity_names(entity_names): "most_frequent" } + def _retrieve_numeric_fieds_df(entities, r, standardize=True, missing='drop', min_max_filter=None): """ Retrieve patient records from the db and preprocess as specified. @@ -148,16 +63,15 @@ def _retrieve_numeric_fieds_df(entities, r, standardize=True, missing='drop', mi The data frame containing entity values for each patient scaler: sklearn.preprocessing.StandardScaler, or None - The scaler to standardize the data, or None if the data is not + The scaler to standardize the data, or None if the data is not standardized. The idea is that standardized values can be transformed back, and raw values can be transformed, using this. - """ + """ if missing not in ALLOWED_MISSING: msg = "Invalid \"missing\" argument: {}".format(missing) raise ValueError(msg) - df, error = rwh.get_joined_numeric_values(entities, r, min_max_filter) if error: # df, scaler @@ -180,6 +94,7 @@ def _retrieve_numeric_fieds_df(entities, r, standardize=True, missing='drop', mi return df, scaler, None +# use in clustering def cluster_numeric_fields(entities, r, standardize=True, missing='drop', min_max_filter=None): """ Cluster the given numeric entities using a DP-GMM. @@ -207,7 +122,7 @@ def cluster_numeric_fields(entities, r, standardize=True, missing='drop', min_ma The fit mixture model label_uses : dict - A dictionary mapping from each label to the number of items with that + A dictionary mapping from each label to the number of items with that label. patient_df: pd.DataFrame @@ -216,28 +131,28 @@ def cluster_numeric_fields(entities, r, standardize=True, missing='drop', min_ma import misc.math_utils as math_utils df, scaler, error = _retrieve_numeric_fieds_df( - entities, - r, - standardize=standardize, + entities, + r, + standardize=standardize, missing=missing, min_max_filter=min_max_filter ) if error: # cluster_data, cluster_labels, df - return None, None, None, error + return None, None, None, error X = df[entities].values - + # make sure we do not use too many components # otherwise, sklearn will complain - n_components = min(100, X.shape[0]-1) + n_components = min(100, X.shape[0] - 1) cluster_data = math_utils.fit_bayesian_gaussian_mixture( - X, + X, n_components=n_components, max_iter=10000, reg_covar=0, mean_precision_prior=0.8, - weight_concentration_prior_type="dirichlet_process", + weight_concentration_prior_type="dirichlet_process", init_params='kmeans', seed=8675309 ) @@ -256,24 +171,26 @@ def cluster_numeric_fields(entities, r, standardize=True, missing='drop', min_ma return cluster_data, cluster_labels, df, None + def _get_patient_categorical_rep(row, categorical_entities): ret = {} - + for categorical_entity in categorical_entities: vals = row[categorical_entity] - + # make sure this is a list of values, which is what # we expect from rwh.get_joined_categorical_values if not utils.is_sequence(vals): continue - + for val in vals: key = "{}.{}".format(categorical_entity, val) ret[key] = 1 return ret -def cluster_categorical_entities(entities, r, eps=0.1, min_samples=5, - seed=8675309): + +# use in clustering +def cluster_categorical_entities(entities, r, eps=0.1, min_samples=5, seed=8675309): """ Cluster patients based on the given categorical entities using DBSCAN. The Jaccard distance is used for clustering. @@ -308,25 +225,25 @@ def cluster_categorical_entities(entities, r, eps=0.1, min_samples=5, All observed (category,value) pairs label_uses : dict - A dictionary mapping from each label to the number of items with that + A dictionary mapping from each label to the number of items with that label. patient_df: pd.DataFrame The data frame containing entity values for each patient """ # pull the values from the database - cat_df, error = rwh.get_joined_categorical_values(entities, r) #.dropna() + cat_df, error = rwh.get_joined_categorical_values(entities, r) # .dropna() if error: # cluster_category_values, cat_rep_np, category_values, label_uses, cat_df return None, None, None, None, None, error # the error is here. cat_rep returns dicts with empty values # get the binary representation cat_rep = parallel.apply_df_simple( - cat_df, - _get_patient_categorical_rep, + cat_df, + _get_patient_categorical_rep, entities ) - + # convert it to a numpy 2-d array; each row is a patient cat_rep_df = pd.DataFrame(cat_rep) category_values = sorted(cat_rep_df.columns) @@ -334,19 +251,19 @@ def cluster_categorical_entities(entities, r, eps=0.1, min_samples=5, # calculate the jaccard distances between all patients distance = sklearn.metrics.pairwise.pairwise_distances( - cat_rep_np, + cat_rep_np, metric='jaccard' ) # cluster the patients based on the jaccard distances db = sklearn.cluster.DBSCAN( - eps=eps, - min_samples=min_samples, - metric='precomputed'\ - ) + eps=eps, + min_samples=min_samples, + metric='precomputed' \ + ) db.fit(distance) - # add the clustering information to the + # add the clustering information to the cat_df['cluster'] = db.labels_ # collect the usage information @@ -360,6 +277,3 @@ def cluster_categorical_entities(entities, r, eps=0.1, min_samples=5, cluster_category_values[l] = cat_rep_df[m_class].count() return cluster_category_values, cat_rep_np, category_values, label_uses, cat_df, None - - - diff --git a/data_warehouse/entity_types.py b/data_warehouse/entity_types.py deleted file mode 100644 index 1678caf8..00000000 --- a/data_warehouse/entity_types.py +++ /dev/null @@ -1,40 +0,0 @@ -import click -import pandas as pd - -@click.command() -@click.option('--host', default="127.0.0.1") -@click.option('--port', default='6379') -@click.argument('input_file') -def import_dataset(host, port, input_file): - entities_df = pd.read_csv(input_file) - numeric_entities = [] - categorical_entities = [] - unique_entities = set(entities_df['entity'].tolist()) - for entity in unique_entities: - df = entities_df.loc[entities_df['entity'] == entity] - values = set(df['value'].tolist()) - if all([type(value) in [int, float] for value in values]): - numeric_entities.append(entity) - else: - categorical_entities.append(entity) - print('entities.csv') - with open('entities.csv', 'w+') as csv_file: - for entity in numeric_entities: - csv_file.write('{},Integer\n'.format(entity)) - for entity in categorical_entities: - csv_file.write('{},String\n'.format(entity)) - - - -if __name__ == "__main__": - import_dataset() - - ## to fix the dataset: - # head -n 12775350 rwh_condensed.txt > rwh_head.txt - # tail -n 1116 > rwh_tail.txt - # head - n 1091 rwh_tail.txt > rwh_clean.txt - # cat rwh_head.txt >> rwh_clean.txt - # cat rwh_tail.txt >> rwh_clean.txt - # iconv -t ASCII//TRANSLIT -f UTF-8 rwh_clean.txt > rwh_acsii.txt - # python data_warehouse/entity_types.py rwh_ascii.txt - # python data_warehouse/load_redis.py entities.csv rwh_ascii.txt \ No newline at end of file diff --git a/data_warehouse/find_frequent_itemsets.py b/data_warehouse/find_frequent_itemsets.py deleted file mode 100755 index ee587ad0..00000000 --- a/data_warehouse/find_frequent_itemsets.py +++ /dev/null @@ -1,174 +0,0 @@ -#! /usr/bin/env python3 - -import argparse -import logging - -import numpy as np -import pandas as pd -import scipy.io -import tqdm - -import fim - -from misc.graph.DimacsGraph import DimacsGraph -import misc.logging_utils as logging_utils -import misc.parallel as parallel -import misc.utils as utils - -import artzebrief.data_warehouse_utils as data_warehouse_utils - -logger = logging.getLogger(__name__) - -default_max_partition_size = 50 -default_minimum_itemset_size = 5 -default_maximum_itemset_size = 10 -default_support = 10 - -target_choices = ['all', 'maximal', 'closed', 'rules'] -default_target = 'maximal' - -pruning_choices = ['none', 'weak', 'strong'] -default_pruning = 'strong' - -def get_itemset_indices(index_fim_result): - - itemset_index, fim_result = index_fim_result - - entity_indices = fim_result[0] - count = fim_result[1] - - ret = [ - { - "frequent_itemset": itemset_index, - "entity_index": entity_index - } for entity_index in entity_indices - ] - - return ret - -def main(): - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description="This script finds frequently co-occuring itemsets from a sparse " - "indicator matrix. It first constructs a weighted, undirected graph " - "based on the number of times a pair of entities appear together. It then " - "partitions this graph so that the size of the largest partitions is not " - "\"too big.\" Finally, it looks for frequent itemsets within the partitions.") - - parser.add_argument('sparse_indicators', help="A sparse matrix in which rows " - "correspond to patients, etc., and columns correspond to entities") - parser.add_argument('out', help="The output csv file") - - parser.add_argument('--max-partition-size', help="The expected maximum size " - "of the the partitions. The partitioning algorithm does not guarantee to " - "exactly balance the partitions, so the actual maximum may be somewhat " - "larger. The complexity of the frequent itemset-finding algorithm is " - "related to this value.", type=int, default=default_max_partition_size) - - parser.add_argument('--min-itemset-size', help="The minimum size to consider " - "for frequent itemsets", type=int, default=default_minimum_itemset_size) - - parser.add_argument('--max-itemset-size', help="The maximum size to consider " - "for frequent itemsets", type=int, default=default_maximum_itemset_size) - - parser.add_argument('--target', help="The type of frequent item sets to find. " - "Please see the pyfim documentation for more details.", choices=target_choices, - default=default_target) - - parser.add_argument('--support', help="The minimum support to consider an " - "itemset as frequenty. Positive numbers are treated as percentages, while " - "negative numbers are treated as absolute counts.", type=int, - default=default_support) - - parser.add_argument('--pruning', help="The pruning strategy during the " - "frequent itemset search", choices=pruning_choices, - default=default_pruning) - - logging_utils.add_logging_options(parser) - args = parser.parse_args() - logging_utils.update_logging(args) - - msg = "Reading indicator file" - logger.info(msg) - - sparse_patient_info = scipy.io.mmread(args.sparse_indicators).tocsr() - num_patients = sparse_patient_info.shape[0] - num_entities = sparse_patient_info.shape[1] - - msg = "Extracting co-occurrence matrix" - logger.info(msg) - - vals = data_warehouse_utils.get_co_occurrence_matrix(sparse_patient_info) - patient_observed_values, co_occurrence_matrix = vals - - msg = "Creating co-occurrence graph" - logger.info(msg) - - co_occurrence_graph = data_warehouse_utils.get_co_occurrence_graph(co_occurrence_matrix) - - msg = "Partitioning graph" - logger.info(msg) - num_partitions = np.ceil(num_entities / args.max_partition_size) - num_partitions = int(num_partitions) - cut_weight, partitions = co_occurrence_graph.get_partitions(num_partitions) - - msg = "Finding frequent itemsets" - logger.info(msg) - - # change the selected pruning into the int required by fim - if args.pruning == 'none': - prune = 0 - elif args.pruning == 'weak': - prune = -1 - elif args.pruning == 'strong': - prune = 1 - else: - msg = "Invalid pruning choice: {}".format(args.pruning) - raise ValueError(msg) - - # check the selected target to the one-letter code for fim - target = args.target[0] - - all_frequent_itemsets = [] - - for partition in range(num_partitions): - msg = "partition: {} of {}".format(partition, num_partitions) - logger.info(msg) - - partition_entities = np.where(partitions == partition)[0] - - partition_observations = parallel.apply_iter_simple(patient_observed_values, - np.intersect1d, partition_entities) - - partition_observations_l = [list(p) for p in partition_observations] - frequent_itemsets = fim.eclat(partition_observations_l, - prune=prune, - zmin=args.min_itemset_size, - zmax=args.max_itemset_size, - target=target, - supp=args.support) - - all_frequent_itemsets.extend(frequent_itemsets) - - msg = "Number of frequent itemsets: {}".format(len(frequent_itemsets)) - logger.info(msg) - - msg = "Combining frequent itemsets into data frame" - logger.info(msg) - - - num_frequent_itemsets = len(all_frequent_itemsets) - afi = zip(range(num_frequent_itemsets), all_frequent_itemsets) - all_frequent_itemset_indices = parallel.apply_parallel_iter(afi, num_cpus, - get_itemset_indices, - progress_bar=True) - - all_frequent_itemset_indices = utils.flatten_lists(all_frequent_itemset_indices) - afi_df = pd.DataFrame(all_frequent_itemset_indices) - - msg = "Writing frequent itemsets to disk" - logger.info(msg) - utils.write_df(afi_df, args.out, index=False) - - -if __name__ == '__main__': - main() diff --git a/data_warehouse/load_redis.py b/data_warehouse/load_redis.py deleted file mode 100644 index 9597879e..00000000 --- a/data_warehouse/load_redis.py +++ /dev/null @@ -1,235 +0,0 @@ -#! /usr/bin/env python3 - -import argparse -import collections -import datetime -import itertools -import time -import shlex - -import pandas as pd -import redis -import tqdm - - -# helpers for the nlp stuff -import nltk.corpus -import nltk.stem.snowball -import nltk.tag.stanford - -import misc.utils as utils - -# connect to redis -import data_warehouse.redis_rwh as rwh - -import logging -import misc.logging_utils as logging_utils -logger = logging.getLogger(__name__) - -# default_host = "129.206.148.189" -default_host = "127.0.0.1" -default_port = 6379 -default_entities_to_keep = [] - - -# example of the command: python load_redis.py dataset_examples/sample_diagnostic_data.csv dataset_examples/entities.csv --host 127.0.0.1 --port 6379 -def update_db( - line, - r, - entities, - number_keys, - category_keys, - category_values, - date_keys, - stopwords, - stemmer, - entities_to_keep - ): - - lexer = shlex.shlex(line.strip(), posix=True) - lexer.whitespace_split = True - lexer.whitespace = "," - (patient_id, quarter_id, date_stamp, time_stamp, key, value) = lexer - - if entities_to_keep is not None: - if key not in entities_to_keep: - return None - - #patient_id = patient_id.decode() - #quarter_id = quarter_id.decode() - #key = key.decode() - #value = value.decode() - - - # first, just check if the value is a number - float_value = utils.try_parse_float(value) - entity_type = entities.get(key) - - s = None - if float_value is not None: - r.zadd(key, float_value, patient_id) - s = ("ZADD {} {} {}".format(key, float_value, patient_id)) - - number_keys.add(key) - - elif entity_type == 'String': - value = [stemmer.stem(word) for word in value if word not in stopwords] - value = ' '.join(value) - - - kv = "{}.{}".format(key, value) - r.sadd(kv, patient_id) - s = ("SADD {} {}".format(kv, patient_id)) - - category_keys.add(key) - category_values[key].add(value) - - elif entity_type in ['Integer', 'Double']: - # i don't think we will ever be here - float_value = utils.try_parse_float(value) - r.zadd(key, float_value, patient_id) - s = ("ZADD {} {} {}".format(key, float_value, patient_id)) - - number_keys.add(key) - - elif entity_type == 'null': - value = time.mktime(datetime.datetime.strptime(date_stamp, "%Y-%m-%d").timetuple()) - - value = max(value, 0) - r.zadd(key, value, patient_id) - s = ("ZADD {} {} {}".format(key, value, patient_id)) - - date_keys.add(key) - - return s - -def main(): - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description="This script parses the rwh_export file and load the values " - "into the specified redis database.") - - parser.add_argument('entities', help="The pldm.entities file") - parser.add_argument('data', help="The rwh_export file") - - parser.add_argument('-n', '--num-entries', help="If this number is given, " - "then only this many entries will be loaded to the database.", type=int, - default=None) - - parser.add_argument('-f', '--flush', help="If this flag is given, then " - "the existing entries will be flushed from the database", - action='store_true') - - parser.add_argument('--host', help="The hostname of the redis database", - default=default_host) - - parser.add_argument('-p', '--port', help="The port on which the database " - "engine is listening", default=default_port) - - parser.add_argument('--entities-to-keep', help="If this list is given, " - "then only these entities will be added to the database", nargs='*', - default=default_entities_to_keep) - - - logging_utils.add_logging_options(parser) - args = parser.parse_args() - logging_utils.update_logging(args) - - msg = "Reading the entities information" - logger.info(msg) - - entities_df = pd.read_csv(args.entities, names=['name', 'value_type']) - entities_map = utils.dataframe_to_dict(entities_df, 'name', 'value_type') - - msg = "Opening connection to redis database, host:port {}:{}".format(args.host, args.port) - logger.info(msg) - - r = rwh.get_connection(host=args.host, port=args.port) - - if args.flush: - msg = "Flushing the database" - logger.info(msg) - r.flushall() - - msg = "Extracting stop words from nltk german list" - logger.info(msg) - - nltk.download('stopwords') - stopwords = { - w for w in nltk.corpus.stopwords.words('german') - } - - msg = "Creating stemmer" - logger.info(msg) - stemmer = nltk.stem.SnowballStemmer('german') - - msg = "Adding entries to the database" - logger.info(msg) - - if args.num_entries is None: - msg = "Counting the number of entries in the file" - logger.info(msg) - - args.num_entries = utils.count_lines(args.data) - - msg = "Found {} entries".format(args.num_entries) - logger.info(msg) - - num_problems = 0 - problem_lines = [] - - number_keys = set() - category_keys = set() - category_values = collections.defaultdict(set) - date_keys = set() - - entities_to_keep = None - if len(args.entities_to_keep) > 0: - entities_to_keep = set(args.entities_to_keep) - - with open(args.data) as f: - f = itertools.islice(f, args.num_entries) - for line in tqdm.tqdm(f, total=args.num_entries): - try: - update_db(line, r, entities_map, number_keys, category_keys, - category_values, date_keys, stopwords, stemmer, entities_to_keep) - except Exception as e: - msg = "Problem: {}".format(line) - logger.debug(msg) - logger.debug(str(e)) - #problem_lines.append(line) - num_problems += 1 - - msg = "Encountered {} problems while loading database".format(num_problems) - logger.warning(msg) - - msg = "Adding the list of numeric entities" - logger.info(msg) - - for number_key in number_keys: - r.sadd("number_keys", number_key) - - msg = "Adding the list of date entities" - logger.info(msg) - - for date_key in date_keys: - r.sadd("date_keys", date_key) - - msg = "Adding the list of categorical entities" - logger.info(msg) - - for category_key in category_keys: - r.sadd("category_keys", category_key) - - msg = "Adding the list of values for each categorical entitiy" - logger.info(msg) - - for category, cv in category_values.items(): - category_key = "{}_values".format(category) - for category_value in cv: - r.sadd(category_key, category_value) - - # no need to "close" the redis connection. - # See: http://stackoverflow.com/questions/24875806 - -if __name__ == '__main__': - main() diff --git a/data_warehouse/redis_rwh.py b/data_warehouse/redis_rwh.py index 353eccc1..99730297 100644 --- a/data_warehouse/redis_rwh.py +++ b/data_warehouse/redis_rwh.py @@ -7,9 +7,10 @@ import numpy as np import pandas as pd + def get_connection(host="localhost", port=6379, url=None): - """ Get a connection to the redis database server. - + """ Get a connection to the redis database server. + After opening the connection, a test ping is sent to ensure the connection is valid. @@ -61,7 +62,7 @@ def _get_sorted_values(key, r): res_np = np.sort(res_np) return res_np - +# use in basic def get_categorical_entities(r): """ Retrieve the names of all categorical entities in the database @@ -79,23 +80,8 @@ def get_categorical_entities(r): return categorical_entities -def get_date_entities(r): - """ Retrieve the names of all date entities in the database - - Parameters - ---------- - r : redis.StrictRedis - The redis database connection - - Returns - ------- - date_entities : np.array of strings - The (sorted) names of all the date entities - """ - date_entities = _get_sorted_values("date_keys", r) - return date_entities - +# use in basic def get_numeric_entities(r): """ Retrieve the names of all numeric entities in the database @@ -113,6 +99,7 @@ def get_numeric_entities(r): return numeric_entities +# use in basic def get_category_values(entity, r): """ Find all of the values for the given categorical entity @@ -132,45 +119,18 @@ def get_category_values(entity, r): """ key = "{}_values".format(entity) entity_values = r.smembers(key) - entity_values = { v.decode() for v in entity_values } + entity_values = {v.decode() for v in entity_values} return entity_values def get_categorical_value_set(categorical_entity, value): - """ Get the name of the set for patients with the given value for the + """ Get the name of the set for patients with the given value for the categorical entity. """ return "{}.{}".format(categorical_entity, value) - -def get_entity_mean_var(entity, r): - """ Find the mean and variance of a numeric entity - - Parameters - ---------- - entity : string - The name of the numeric entity - - r : redis.StrictRedis - The redis database connection - - Returns - ------- - mean, variance : floats - The mean and variance of the values for the entity - """ - from misc.incremental_gaussian_estimator import IncrementalGaussianEstimator - - ige = IncrementalGaussianEstimator() - it = r.zscan_iter(entity) - - for (patient_id, val) in it: - ige.add_observation(val) - - return ige.get_mean(), ige.get_var() - - +# use in plots def get_entity_values(field, r): """ Retrieve all of the unique values (scores) of a numeric entity @@ -196,37 +156,8 @@ def get_entity_values(field, r): return np.array(vals) -def get_patients_with_category_value(entity, value, r): - """ Retrieve all patients which have the specified value for a categorical - entity. - - N.B. A patient can have more than one value for a particular entity. For - example, "diagnostik.nebendiagnose.icd.kode" is an entity, and a patient - can have multiple values (i.e., associated ICD codes). - - Parameters - ---------- - entity : string - The name of the categorical entity - - value : string - The value for the entity - - r : redis.StrictRedis - The redis database connection - - Returns - ------- - patient_ids : set of strings - All patient IDs which have the entity and value - - """ - key = "{}.{}".format(entity, value) - res = r.smembers(key) - res = { r.decode() for r in res } - return res - +# use in data_warehouse,basic def get_joined_numeric_values(entities, r, min_max_filter=None): """ Retrieve the values of the given numeric entities for all patients @@ -255,56 +186,21 @@ def get_joined_numeric_values(entities, r, min_max_filter=None): merged_df = None for entity in entities: - min_value, max_value = min_max_filter[entity] if min_max_filter and entity in min_max_filter else ('-inf', '+inf') + min_value, max_value = min_max_filter[entity] if min_max_filter else ('-inf', '+inf') values = r.zrangebyscore(entity, min_value, max_value, withscores=True) df = pd.DataFrame(values) - if len(df) == 0: - # stop the join if atleast one entity has no values - return None, entity + " has no values" - df.columns = ['patient_id', entity] - merged_df = df.copy() if merged_df is None else pd.merge(merged_df, df, how='outer', on='patient_id') + if len(df) > 0: + df.columns = ['patient_id', entity] + merged_df = df.copy() if merged_df is None else pd.merge(merged_df, df, how='outer', on='patient_id') + if merged_df is None: + return None, "Values are Empty" merged_df['patient_id'] = merged_df['patient_id'].apply(lambda x: x.decode()) return merged_df, None -def get_all_patients_category_value(entity, r): - """ Find the value for a categorical entity for all patients. - - Parameters - ---------- - entity : string - The name of the categorical entity - - r : redis.StrictRedis - The redis database connection - - Returns - ------- - patient_values_df : pd.DataFrame - A data frame which contains the value of the given entity - for all patients which have a value for it in the database - """ - - all_dfs = [] - - # first, we need to get all of the values for this category - category_values = rwh.get_category_values(entity, r) - - # now, find all the patients with each value - for category_value in category_values: - patient_ids = rwh.get_patients_with_category_value(entity, category_value, r) - - df = pd.DataFrame() - df['patient_id'] = list(patient_ids) - df[entity] = category_value - - all_dfs.append(df) - - all_df = pd.concat(all_dfs) - return all_df - +# use in data_warehouse,basic def get_joined_categorical_values(entities, r): """ Retrieve the values of the given categorical entities for all patients @@ -331,12 +227,10 @@ def get_joined_categorical_values(entities, r): The "dropna" method of the data frame could be used to remove patients which do not have values for all entities. """ - values_dict = { } + values_dict = {} for entity in entities: # first, we need to get all of the values for this category category_values = get_category_values(entity, r) - if len(category_values) == 0: - return None, entity + " has no values" # now, find all the patients with each value for category_value in category_values: # for some reason the value has spaces between charachters @@ -344,7 +238,7 @@ def get_joined_categorical_values(entities, r): it = r.sscan_iter(key) for patient_id in it: if patient_id not in values_dict: - values_dict[patient_id] = { } + values_dict[patient_id] = {} if entity not in values_dict[patient_id]: values_dict[patient_id][entity] = category_value.replace(' ', '') rows = [] @@ -358,101 +252,10 @@ def get_joined_categorical_values(entities, r): values_df = pd.DataFrame(rows) # values_df['patient_id'] = values_df['patient_id'].apply(lambda x: x.decode()) # return values_df - values_df['patient_id'] = values_df['patient_id'].apply(lambda x: x.decode()) - return values_df, None - - -def get_patient_info( - patient_id, - r, - all_numeric_entities=None, - all_categorical_entities=None, - all_date_entities=None): - """ Extract all information from all entities about the given patient. - - Optionally, a set of entities can be passed, and only those will be - included in the results. - - Parameters - ---------- - patient_id: string - The anonymized patient id - - r: redis.StrictRedis - The redis database connection + if len(values_df) > 0 and 'patient_id' in values_df: + values_df['patient_id'] = values_df['patient_id'].apply(lambda x: x.decode()) + return values_df, None + return None, "No data" - all_{numeric, categorical, date}_entities: list-likes - If provided, then only these entities will be included in the results. - To skip an entity type, an empty list (*not* None) can be given. - Returns - ------- - patient_info: dict - A mapping from each entity to the value for this patient. Missing - entities are not included in the results. - """ - - if all_numeric_entities is None: - all_numeric_entities = rwh.get_numeric_entities(r) - - if all_categorical_entities is None: - all_categorical_entities = rwh.get_categorical_entities(r) - - if all_date_entities is None: - all_date_entities = rwh.get_date_entities(r) - - patient_info = { "patient_id": patient_id } - - for numeric_entity in all_numeric_entities: - score = r.zscore(numeric_entity, patient_id) - if score is not None: - patient_info[numeric_entity] = score - - for categorical_entity in all_categorical_entities: - category_values = get_category_values(categorical_entity, r) - - for category_value in category_values: - key = get_categorical_value_set(categorical_entity, category_value) - - if r.sismember(key, patient_id): - patient_info[categorical_entity] = category_value - - return patient_info - -def get_all_patient_info(patient_ids, r): - """ Extract all information about all of the given patients. - - This is mostly a wrapper around get_patient_info, so please see the - documentation for that function for more information. - - Parameters - ---------- - patient_ids: list-like - The patient identifiers - - r: redis.StrictRedis - The redis database connection - - Returns - ------- - patient_info_df: pandas.DataFrame - A data frame containing the values for each patient. Missing values - are represented with NaNs (*not* Nones). - - """ - all_numeric_entities = get_numeric_entities(r) - all_categorical_entities = get_categorical_entities(r) - all_date_entities = get_date_entities(r) - - all_patient_info = [ - get_patient_info( - patient_id, - r, - all_numeric_entities=all_numeric_entities, - all_categorical_entities=all_categorical_entities, - all_date_entities=all_date_entities - ) for patient_id in patient_ids - ] - - return pd.DataFrame(all_patient_info) diff --git a/data_warehouse/user_management.py b/data_warehouse/user_management.py deleted file mode 100644 index 81292c9c..00000000 --- a/data_warehouse/user_management.py +++ /dev/null @@ -1,54 +0,0 @@ -import click -from passlib.hash import sha256_crypt - -import data_warehouse.redis_rwh as rwh - -@click.group() -@click.option('--host', default='127.0.0.1') -@click.option('--port', default='6379') -@click.pass_context -def cli(ctx, host, port): - ctx.ensure_object(dict) - ctx.obj['host'] = host - ctx.obj['port'] = port - -@cli.command() -@click.pass_context -@click.argument('username') -def delete_user(ctx, username): - print('delete user') - host = ctx.obj.get('host') - port = ctx.obj.get('port') - rdb = rwh.get_connection(host, port) - user_exists = rdb.hexists('users', username) - if not user_exists: - click.echo('User "{}" does not exist, so cannot be deleted'.format(username)) - exit(1) - # else - rdb.hdel('users', username) - click.echo('User "{}" successfully deleted'.format(username)) - exit(0) - -@cli.command() -@click.pass_context -@click.argument('username') -@click.argument('password') -def create_user(ctx, username, password): - host = ctx.obj.get('host') - port = ctx.obj.get('port') - encrypted_password = sha256_crypt.hash(password) - rdb = rwh.get_connection(host, port) - user_exists = rdb.hexists('users', username) - if user_exists: - click.echo('User "{}" already exists'.format(username)) - exit(1) - # hash set = 'users', key1 = username, key2 = 'password', value2 = password - rdb.hmset('users', {username: encrypted_password}) - click.echo('User "{}" successfully created'.format(username)) - exit(0) - -if __name__ == '__main__': - cli(obj={}) - -# todo: reset password -# todo: create users from file \ No newline at end of file diff --git a/dataset_examples/entities.csv b/dataset_examples/entities.csv index af5ff4c9..60c8d80e 100644 --- a/dataset_examples/entities.csv +++ b/dataset_examples/entities.csv @@ -45,4 +45,4 @@ Delta8,Double Delta9,Double Delta10,Double Delta11,Double -Delta12,Double +Delta12,Double \ No newline at end of file diff --git a/modules/import_dataset.py b/modules/import_dataset.py index 7c358371..bf63670f 100644 --- a/modules/import_dataset.py +++ b/modules/import_dataset.py @@ -24,6 +24,18 @@ def import_entity_types(entity_file): + """ Divide the key into categories and numerical values + + Parameters + ---------- + entities : value from "entities file" + + Returns + ------- + numeric_enities: set of keys with a numeric values + categorical_entities: set of keys with a categorical values + + """ entities = pd.read_csv(entity_file) # Name is first, Type is second column numeric_entities = set(entities[(entities["datatype"] == "Double") | (entities["datatype"] == "Integer")]["entity"]) @@ -33,62 +45,49 @@ def import_entity_types(entity_file): return numeric_entities, categorical_entities -def estimate_entity_types_from_dataset(input_file): - numeric_entities = set() - categorical_entities = set() - # Open file connection - print("Starting readin of the data types") - with open(input_file) as infile: - # loop over input file, getting the data type at the same time - for n, line in enumerate(infile): - # print(line) - # we assume that the file is always Patient_ID, Billing_ID, Date, Time, Key, Value - linelist = line.replace("\n", "").split(",") - # last column 6 might be having commas - Patient_ID, Billing_ID, rwh_Date, rwh_Time, rwh_Key, rwh_Value = linelist[0:5] + [ - ",".join([str(x) for x in linelist[5:]])] +def clear_database(rdb): + """Delete all the keys of all the existing databases, + not just the currently selected one.""" + rdb.flushall() - # Test if Value is numeric or string - try: - print("adding numeric entity") - # Use scored set for float and int - value = float(rwh_Value) - numeric_entities.add(rwh_Key) - except ValueError: - print("adding category entity") - # Use regular set for string - # Add to set of categorical entities (duplicates are only once in a set) - categorical_entities.add(rwh_Key) - return numeric_entities, categorical_entities +def import_dataset(url, input_file, numeric_entities, categorical_entities): + """ Connection wit Redis database and input values from file to Redis database + divided into numerical and categorical values + Parameters + ---------- + url : we adress to connect to the Redis database (in our case localhost) + input_file : dataset file with values and keys + numeric_enities: set of keys with a numeric values + categorical_entities: set of keys with a categorical values -def clear_database(rdb): - rdb.flushall() + Returns + ------- + """ -def import_dataset(url, input_file, numeric_entities, categorical_entities): num_imp, cat_imp, num_err, not_imp = [0, 0, 0, 0] numeric_value_errors = { } categorical_values = { } + rdb = rwh.get_connection(url=url) clear_database(rdb) + print("Starting putting data into redis") # run again to have the categories and numerics now ready and write the data with open(input_file) as infile: # loop over input file, getting the data type at the same time for n, line in enumerate(infile): - # if "demografie.gewicht" in line: - # print(line) - if (n % 1000000 == 0): - print("Line: ", n) # we assume that the file is always Patient_ID, Billing_ID, Date, Time, Key, Value linelist = line.replace("\n", "").split(",") # last column 6 might be having commas Patient_ID, Billing_ID, rwh_Date, rwh_Time, rwh_Key, rwh_Value = linelist[0:5] + [ ",".join([str(x) for x in linelist[5:]])] + + # Test if Value is numeric or string if rwh_Key in categorical_entities: rdb.sadd('{}.{}'.format(rwh_Key, rwh_Value), Patient_ID) @@ -103,8 +102,6 @@ def import_dataset(url, input_file, numeric_entities, categorical_entities): value = float(rwh_Value) rdb.zadd(rwh_Key, { Patient_ID: rwh_Value }) num_imp += 1 - # if "gewicht" in rwh_Key: - # print(line) except ValueError: numeric_value_errors.setdefault(rwh_Key, set()) numeric_value_errors[rwh_Key].add(rwh_Value) @@ -113,14 +110,9 @@ def import_dataset(url, input_file, numeric_entities, categorical_entities): not_imp += 1 pass # we assume that we can add everything that was not cleaned (not in entity file) as string, but mark it "unclean" - # print(rwh_Value, "was not added, no entity type defined. Line: ", line) - ##rwh_Key = "uncleaned_" + rwh_Key - ##rdb.sadd('{}.{}'.format(rwh_Key, rwh_Value), Patient_ID) - # Add value to the dictionary of possible values (setdefault avoids duplicates) - ##categorical_values.setdefault(rwh_Key, []) - ##categorical_values[rwh_Key].append(rwh_Value) - # the same as in load_redis.py script + + # Add the specified members to the set stored at key. for entity in numeric_entities: rdb.sadd("number_keys", entity) @@ -131,25 +123,9 @@ def import_dataset(url, input_file, numeric_entities, categorical_entities): entity = "{}_values".format(category) for value in values: rdb.sadd(entity, value) + print("The following numerics were not valid and therefore not added:\n", numeric_value_errors) - print( - "\nNumeric values imported: %i\nCategorical values imported: %i\nErrors in numeric values: %i\nOther errors: %i\n" % ( + print("\nNumeric values imported: %i\nCategorical values imported: %i\nErrors in numeric values: %i\nOther errors: %i\n" % ( num_imp, cat_imp, num_err, not_imp)) -@click.command() -@click.option('--host', default='127.0.0.1') -@click.option('--port', default='6379') -@click.argument('input_file') -@click.argument('entity_file') -def main(host, port, input_file, entity_file): - if entity_file == "None": - numeric_entities, categorical_entities = estimate_entity_types_from_dataset(input_file) - else: - numeric_entities, categorical_entities = import_entity_types(entity_file) - url = f"redis://{host}:{port}/0" - import_dataset(url, input_file, numeric_entities, categorical_entities) - - -if __name__ == '__main__': - main() diff --git a/modules/import_scheduler.py b/modules/import_scheduler.py index f3a54db3..6dfb3385 100644 --- a/modules/import_scheduler.py +++ b/modules/import_scheduler.py @@ -6,12 +6,20 @@ from apscheduler.schedulers.background import BackgroundScheduler + class ImportSettings(): + """ + Class which create file dev_import necessary to import data. + Inside the file we have two unique cods for files dataset and entities. + The codes change every time we change anything in the files dataset and entites. + If the codes has been changed the program loads new data. + More about th code : https://www.computerhope.com/unix/sha512sum.htm + """ def __init__(self): if os.environ['FLASK_ENV'] == 'production': self.path = "./import/import.ini" else: - self.path = './dev_import.ini' + self.path = './import/dev_import.ini' self.config = ConfigParser() self.config.read(self.path) if 'hashes' not in self.config.sections(): @@ -36,6 +44,7 @@ def get_hash(self, path): return os.popen(f"sha512sum {path}").read() \ .split(' ')[0] + # Check that data in enities and dataset was changed def is_entity_changed(self, path): hash = self.get_hash(path) return self.config['hashes']['entity'] != hash @@ -51,16 +60,19 @@ def is_empty(self): def start_import(): + """ Import data from entities and dataset files""" + settings = ImportSettings() print('starting import', datetime.now().strftime('%H:%M:%S')) dataset = './import/dataset.csv' entities = './import/entities.csv' + if not os.path.isfile(dataset) or not os.path.isfile(entities): return print("Could not import to database either or both entities and dataset is missing", file=sys.stderr) if not settings.is_dataset_changed(dataset) and not settings.is_entity_changed(entities): - return - print(settings.config['hashes']['entity']) + return print("Data set not changed", file=sys.stderr) + numeric_entities, categorical_entities = id.import_entity_types(entities) id.import_dataset(os.environ['REDIS_URL'], dataset, numeric_entities, categorical_entities) @@ -69,6 +81,10 @@ def start_import(): class Scheduler(): + """ + BackgroundScheduler runs in a thread inside existing application. + Importing data check the data. Import data every day at 05.05 if the program see any changes. + """ def __init__(self, day_of_week, hour, minute): self.bgs = BackgroundScheduler() start_import() diff --git a/scripts/start.sh b/scripts/start.sh index b5d4c73e..75447d6d 100755 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -2,6 +2,8 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -FLASK_ENV=development -FLASK_APP=webserver.py +export FLASK_ENV=development +export FLASK_APP=webserver.py + +export REDIS_URL=redis://localhost:6379/0 flask run --host=0.0.0.0 diff --git a/static/choices_cross.svg b/static/choices_cross.svg deleted file mode 100644 index d8a60ebd..00000000 --- a/static/choices_cross.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/heatmap.js b/static/heatmap.js deleted file mode 100644 index 843d4240..00000000 --- a/static/heatmap.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - Highcharts JS v6.1.3 (2018-09-12) - - (c) 2009-2017 Torstein Honsi - - License: www.highcharts.com/license -*/ -(function(k){"object"===typeof module&&module.exports?module.exports=k:"function"===typeof define&&define.amd?define(function(){return k}):k(Highcharts)})(function(k){(function(b){var t=b.addEvent,g=b.Axis,k=b.Chart,m=b.color,q,h=b.each,r=b.extend,p=b.isNumber,e=b.Legend,c=b.LegendSymbolMixin,x=b.noop,w=b.merge,u=b.pick;b.ColorAxis||(q=b.ColorAxis=function(){this.init.apply(this,arguments)},r(q.prototype,g.prototype),r(q.prototype,{defaultColorAxisOptions:{lineWidth:0,minPadding:0,maxPadding:0,gridLineWidth:1, -tickPixelInterval:72,startOnTick:!0,endOnTick:!0,offset:0,marker:{animation:{duration:50},width:.01,color:"#999999"},labels:{overflow:"justify",rotation:0},minColor:"#e6ebf5",maxColor:"#003399",tickLength:5,showInLegend:!0},keepProps:["legendGroup","legendItemHeight","legendItemWidth","legendItem","legendSymbol"].concat(g.prototype.keepProps),init:function(a,d){var f="vertical"!==a.options.legend.layout,n;this.coll="colorAxis";n=w(this.defaultColorAxisOptions,{side:f?2:1,reversed:!f},d,{opposite:!f, -showEmpty:!1,title:null,visible:a.options.legend.enabled});g.prototype.init.call(this,a,n);d.dataClasses&&this.initDataClasses(d);this.initStops();this.horiz=f;this.zoomEnabled=!1;this.defaultLegendLength=200},initDataClasses:function(a){var d=this.chart,f,n=0,l=d.options.chart.colorCount,b=this.options,e=a.dataClasses.length;this.dataClasses=f=[];this.legendItems=[];h(a.dataClasses,function(a,c){a=w(a);f.push(a);a.color||("category"===b.dataClassColor?(c=d.options.colors,l=c.length,a.color=c[n], -a.colorIndex=n,n++,n===l&&(n=0)):a.color=m(b.minColor).tweenTo(m(b.maxColor),2>e?.5:c/(e-1)))})},setTickPositions:function(){if(!this.dataClasses)return g.prototype.setTickPositions.call(this)},initStops:function(){this.stops=this.options.stops||[[0,this.options.minColor],[1,this.options.maxColor]];h(this.stops,function(a){a.color=m(a[1])})},setOptions:function(a){g.prototype.setOptions.call(this,a);this.options.crosshair=this.options.marker},setAxisSize:function(){var a=this.legendSymbol,d=this.chart, -f=d.options.legend||{},n,l;a?(this.left=f=a.attr("x"),this.top=n=a.attr("y"),this.width=l=a.attr("width"),this.height=a=a.attr("height"),this.right=d.chartWidth-f-l,this.bottom=d.chartHeight-n-a,this.len=this.horiz?l:a,this.pos=this.horiz?f:n):this.len=(this.horiz?f.symbolWidth:f.symbolHeight)||this.defaultLegendLength},normalizedValue:function(a){this.isLog&&(a=this.val2lin(a));return 1-(this.max-a)/(this.max-this.min||1)},toColor:function(a,d){var f=this.stops,n,l,b=this.dataClasses,e,c;if(b)for(c= -b.length;c--;){if(e=b[c],n=e.from,f=e.to,(void 0===n||a>=n)&&(void 0===f||a<=f)){l=e.color;d&&(d.dataClass=c,d.colorIndex=e.colorIndex);break}}else{a=this.normalizedValue(a);for(c=f.length;c--&&!(a>f[c][0]););n=f[c]||f[c+1];f=f[c+1]||n;a=1-(f[0]-a)/(f[0]-n[0]||1);l=n.color.tweenTo(f.color,a)}return l},getOffset:function(){var a=this.legendGroup,d=this.chart.axisOffset[this.side];a&&(this.axisParent=a,g.prototype.getOffset.call(this),this.added||(this.added=!0,this.labelLeft=0,this.labelRight=this.width), -this.chart.axisOffset[this.side]=d)},setLegendColor:function(){var a,d=this.reversed;a=d?1:0;d=d?0:1;a=this.horiz?[a,0,d,0]:[0,d,0,a];this.legendColor={linearGradient:{x1:a[0],y1:a[1],x2:a[2],y2:a[3]},stops:this.stops}},drawLegendSymbol:function(a,d){var f=a.padding,b=a.options,l=this.horiz,c=u(b.symbolWidth,l?this.defaultLegendLength:12),e=u(b.symbolHeight,l?12:this.defaultLegendLength),h=u(b.labelPadding,l?16:30),b=u(b.itemDistance,10);this.setLegendColor();d.legendSymbol=this.chart.renderer.rect(0, -a.baseline-11,c,e).attr({zIndex:1}).add(d.legendGroup);this.legendItemWidth=c+f+(l?b:h);this.legendItemHeight=e+f+(l?h:0)},setState:function(a){h(this.series,function(d){d.setState(a)})},visible:!0,setVisible:x,getSeriesExtremes:function(){var a=this.series,d=a.length;this.dataMin=Infinity;for(this.dataMax=-Infinity;d--;)a[d].getExtremes(),void 0!==a[d].valueMin&&(this.dataMin=Math.min(this.dataMin,a[d].valueMin),this.dataMax=Math.max(this.dataMax,a[d].valueMax))},drawCrosshair:function(a,d){var f= -d&&d.plotX,b=d&&d.plotY,c,e=this.pos,h=this.len;d&&(c=this.toPixels(d[d.series.colorKey]),ce+h&&(c=e+h+2),d.plotX=c,d.plotY=this.len-c,g.prototype.drawCrosshair.call(this,a,d),d.plotX=f,d.plotY=b,this.cross&&!this.cross.addedToColorAxis&&this.legendGroup&&(this.cross.addClass("highcharts-coloraxis-marker").add(this.legendGroup),this.cross.addedToColorAxis=!0,this.cross.attr({fill:this.crosshair.color})))},getPlotLinePath:function(a,d,f,b,c){return p(c)?this.horiz?["M",c-4,this.top-6,"L", -c+4,this.top-6,c,this.top,"Z"]:["M",this.left,c,"L",this.left-6,c+6,this.left-6,c-6,"Z"]:g.prototype.getPlotLinePath.call(this,a,d,f,b)},update:function(a,d){var c=this.chart,b=c.legend;h(this.series,function(a){a.isDirtyData=!0});a.dataClasses&&b.allItems&&(h(b.allItems,function(a){a.isDataClass&&a.legendGroup&&a.legendGroup.destroy()}),c.isDirtyLegend=!0);c.options[this.coll]=w(this.userOptions,a);g.prototype.update.call(this,a,d);this.legendItem&&(this.setLegendColor(),b.colorizeItem(this,!0))}, -remove:function(){this.legendItem&&this.chart.legend.destroyItem(this);g.prototype.remove.call(this)},getDataClassLegendSymbols:function(){var a=this,d=this.chart,f=this.legendItems,e=d.options.legend,l=e.valueDecimals,q=e.valueSuffix||"",g;f.length||h(this.dataClasses,function(e,n){var m=!0,p=e.from,k=e.to;g="";void 0===p?g="\x3c ":void 0===k&&(g="\x3e ");void 0!==p&&(g+=b.numberFormat(p,l)+q);void 0!==p&&void 0!==k&&(g+=" - ");void 0!==k&&(g+=b.numberFormat(k,l)+q);f.push(r({chart:d,name:g,options:{}, -drawLegendSymbol:c.drawRectangle,visible:!0,setState:x,isDataClass:!0,setVisible:function(){m=this.visible=!m;h(a.series,function(a){h(a.points,function(a){a.dataClass===n&&a.setVisible(m)})});d.legend.colorizeItem(this,m)}},e))});return f},name:""}),h(["fill","stroke"],function(a){b.Fx.prototype[a+"Setter"]=function(){this.elem.attr(a,m(this.start).tweenTo(m(this.end),this.pos),null,!0)}}),t(k,"afterGetAxes",function(){var a=this.options.colorAxis;this.colorAxis=[];a&&new q(this,a)}),t(e,"afterGetAllItems", -function(a){var d=[],c=this.chart.colorAxis[0];c&&c.options&&c.options.showInLegend&&(c.options.dataClasses?d=c.getDataClassLegendSymbols():d.push(c),h(c.series,function(c){b.erase(a.allItems,c)}));for(c=d.length;c--;)a.allItems.unshift(d[c])}),t(e,"afterColorizeItem",function(a){a.visible&&a.item.legendColor&&a.item.legendSymbol.attr({fill:a.item.legendColor})}),t(e,"afterUpdate",function(a,c,b){this.chart.colorAxis[0]&&this.chart.colorAxis[0].update({},b)}))})(k);(function(b){var k=b.defined,g= -b.each,v=b.noop,m=b.seriesTypes;b.colorPointMixin={isValid:function(){return null!==this.value&&Infinity!==this.value&&-Infinity!==this.value},setVisible:function(b){var h=this,q=b?"show":"hide";g(["graphic","dataLabel"],function(b){if(h[b])h[b][q]()})},setState:function(g){b.Point.prototype.setState.call(this,g);this.graphic&&this.graphic.attr({zIndex:"hover"===g?1:0})}};b.colorSeriesMixin={pointArrayMap:["value"],axisTypes:["xAxis","yAxis","colorAxis"],optionalAxis:"colorAxis",trackerGroups:["group", -"markerGroup","dataLabelsGroup"],getSymbol:v,parallelArrays:["x","y","value"],colorKey:"value",pointAttribs:m.column.prototype.pointAttribs,translateColors:function(){var b=this,h=this.options.nullColor,k=this.colorAxis,m=this.colorKey;g(this.data,function(e){var c=e[m];if(c=e.options.color||(e.isNull?h:k&&void 0!==c?k.toColor(c,e):e.color||b.color))e.color=c})},colorAttribs:function(b){var g={};k(b.color)&&(g[this.colorProp||"fill"]=b.color);return g}}})(k);(function(b){var k=b.colorPointMixin,g= -b.each,v=b.merge,m=b.noop,q=b.pick,h=b.Series,r=b.seriesType,p=b.seriesTypes;r("heatmap","scatter",{animation:!1,borderWidth:0,nullColor:"#f7f7f7",dataLabels:{formatter:function(){return this.point.value},inside:!0,verticalAlign:"middle",crop:!1,overflow:!1,padding:0},marker:null,pointRange:null,tooltip:{pointFormat:"{point.x}, {point.y}: {point.value}\x3cbr/\x3e"},states:{hover:{halo:!1,brightness:.2}}},v(b.colorSeriesMixin,{pointArrayMap:["y","value"],hasPointSpecificOptions:!0,getExtremesFromAll:!0, -directTouch:!0,init:function(){var b;p.scatter.prototype.init.apply(this,arguments);b=this.options;b.pointRange=q(b.pointRange,b.colsize||1);this.yAxis.axisPointRange=b.rowsize||1},translate:function(){var b=this.options,c=this.xAxis,h=this.yAxis,k=b.pointPadding||0,m=function(a,b,c){return Math.min(Math.max(b,a),c)};this.generatePoints();g(this.points,function(a){var d=(b.colsize||1)/2,e=(b.rowsize||1)/2,g=m(Math.round(c.len-c.translate(a.x-d,0,1,0,1)),-c.len,2*c.len),d=m(Math.round(c.len-c.translate(a.x+ -d,0,1,0,1)),-c.len,2*c.len),l=m(Math.round(h.translate(a.y-e,0,1,0,1)),-h.len,2*h.len),e=m(Math.round(h.translate(a.y+e,0,1,0,1)),-h.len,2*h.len),p=q(a.pointPadding,k);a.plotX=a.clientX=(g+d)/2;a.plotY=(l+e)/2;a.shapeType="rect";a.shapeArgs={x:Math.min(g,d)+p,y:Math.min(l,e)+p,width:Math.abs(d-g)-2*p,height:Math.abs(e-l)-2*p}});this.translateColors()},drawPoints:function(){p.column.prototype.drawPoints.call(this);g(this.points,function(b){b.graphic.attr(this.colorAttribs(b))},this)},animate:m,getBox:m, -drawLegendSymbol:b.LegendSymbolMixin.drawRectangle,alignDataLabel:p.column.prototype.alignDataLabel,getExtremes:function(){h.prototype.getExtremes.call(this,this.valueData);this.valueMin=this.dataMin;this.valueMax=this.dataMax;h.prototype.getExtremes.call(this)}}),b.extend({haloPath:function(b){if(!b)return[];var c=this.shapeArgs;return["M",c.x-b,c.y-b,"L",c.x-b,c.y+c.height+b,c.x+c.width+b,c.y+c.height+b,c.x+c.width+b,c.y-b,"Z"]}},k))})(k)}); -//# sourceMappingURL=heatmap.js.map diff --git a/static/highcharts-regression.js b/static/highcharts-regression.js deleted file mode 100644 index f9f3a156..00000000 --- a/static/highcharts-regression.js +++ /dev/null @@ -1,574 +0,0 @@ -(function (factory) { - "use strict"; - - if (typeof module === "object" && module.exports) { - module.exports = factory; - } else { - factory(Highcharts); - } -}(function (H) { - var processSerie = function (s, method, chart) { - if (s.regression && !s.rendered) { - s.regressionSettings = s.regressionSettings || {}; - s.regressionSettings.tooltip = s.regressionSettings.tooltip || {}; - s.regressionSettings.dashStyle = s.regressionSettings.dashStyle || 'solid'; - s.regressionSettings.decimalPlaces = s.regressionSettings.decimalPlaces || 2; - s.regressionSettings.useAllSeries = s.regressionSettings.useAllSeries || false; - - var regressionType = s.regressionSettings.type || "linear"; - var regression; - var extraSerie = { - data: [], - color: s.regressionSettings.color || '', - yAxis: s.yAxis, - lineWidth: s.regressionSettings.lineWidth || 2, - marker: {enabled: false}, - isRegressionLine: true, - visible: s.regressionSettings.visible, - type: s.regressionSettings.linetype || 'spline', - name: s.regressionSettings.name || "Equation: %eq", - id: s.regressionSettings.id, - dashStyle: s.regressionSettings.dashStyle || 'solid', - showInLegend: !s.regressionSettings.hideInLegend, - tooltip: { - valueSuffix: s.regressionSettings.tooltip.valueSuffix || ' ' - } - }; - - var mergedData = s.data; - if (s.regressionSettings.useAllSeries) { - mergedData = []; - for (di = 0; di < series.length; di++) { - var seriesToMerge = series[di]; - mergedData = mergedData.concat(seriesToMerge.data); - } - } - - if (regressionType == "linear") { - regression = _linear(mergedData, s.regressionSettings.decimalPlaces); - extraSerie.type = "line"; - } else if (regressionType == "exponential") { - regression = _exponential(mergedData); - } - else if (regressionType == "polynomial") { - var order = s.regressionSettings.order || 2; - var extrapolate = s.regressionSettings.extrapolate || 0; - regression = _polynomial(mergedData, order, extrapolate); - } else if (regressionType == "power") { - regression = _power(mergedData); - } else if (regressionType == "logarithmic") { - regression = _logarithmic(mergedData); - } else if (regressionType == "loess") { - var loessSmooth = s.regressionSettings.loessSmooth || 25; - regression = _loess(mergedData, loessSmooth / 100); - } else { - console.error("Invalid regression type: ", regressionType); - return; - } - - regression.rSquared = coefficientOfDetermination(mergedData, regression.points); - regression.rValue = Math.sqrt(regression.rSquared).toFixed(s.regressionSettings.decimalPlaces); - regression.rSquared = regression.rSquared.toFixed(s.regressionSettings.decimalPlaces); - regression.standardError = standardError(mergedData, regression.points).toFixed(s.regressionSettings.decimalPlaces); - extraSerie.data = regression.points; - extraSerie.name = extraSerie.name.replace("%r2", regression.rSquared); - extraSerie.name = extraSerie.name.replace("%r", regression.rValue); - extraSerie.name = extraSerie.name.replace("%eq", regression.string); - extraSerie.name = extraSerie.name.replace("%se", regression.standardError); - - if (extraSerie.visible === false) { - extraSerie.visible = false; - } - extraSerie.regressionOutputs = regression; - return extraSerie; - - } - } - - H.wrap(H.Chart.prototype, 'init', function (proceed) { - var series = arguments[1].series; - var extraSeries = []; - var i = 0; - for (i = 0; i < series.length; i++) { - var s = series[i]; - if (s.regression) { - var extraSerie = processSerie(s, 'init', this); - extraSeries.push(extraSerie); - arguments[1].series[i].rendered = true; - } - } - - if (extraSerie) { - arguments[1].series = series.concat(extraSeries); - } - - proceed.apply(this, Array.prototype.slice.call(arguments, 1)); - - - }); - - H.wrap(H.Chart.prototype, 'addSeries', function (proceed) { - var s = arguments[1]; - var extraSerie = processSerie(s, 'addSeries', this); - arguments[1].rendered = true; - if (extraSerie) { - this.addSeries(extraSerie); - } - return proceed.apply(this, Array.prototype.slice.call(arguments, 1)); - }); - - - /** - * Code extracted from https://github.com/Tom-Alexander/regression-js/ - */ - function _exponential(data) { - var sum = [0, 0, 0, 0, 0, 0], n = 0, results = []; - - for (len = data.length; n < len; n++) { - if (data[n].x != null) { - data[n][0] = data[n].x; - data[n][1] = data[n].y; - } - if (data[n][1] != null) { - sum[0] += data[n][0]; // X - sum[1] += data[n][1]; // Y - sum[2] += data[n][0] * data[n][0] * data[n][1]; // XXY - sum[3] += data[n][1] * Math.log(data[n][1]); // Y Log Y - sum[4] += data[n][0] * data[n][1] * Math.log(data[n][1]); //YY Log Y - sum[5] += data[n][0] * data[n][1]; //XY - } - } - - var denominator = (sum[1] * sum[2] - sum[5] * sum[5]); - var A = Math.pow(Math.E, (sum[2] * sum[3] - sum[5] * sum[4]) / denominator); - var B = (sum[1] * sum[4] - sum[5] * sum[3]) / denominator; - - for (var i = 0, len = data.length; i < len; i++) { - var coordinate = [data[i][0], A * Math.pow(Math.E, B * data[i][0])]; - results.push(coordinate); - } - - results.sort(function (a, b) { - if (a[0] > b[0]) { - return 1; - } - if (a[0] < b[0]) { - return -1; - } - return 0; - }); - - var string = 'y = ' + Math.round(A * 100) / 100 + 'e^(' + Math.round(B * 100) / 100 + 'x)'; - - return {equation: [A, B], points: results, string: string}; - } - - - /** - * Code extracted from https://github.com/Tom-Alexander/regression-js/ - * Human readable formulas: - * - * N * Σ(XY) - Σ(X) - * intercept = --------------------- - * N * Σ(X^2) - Σ(X)^2 - * - * correlation = N * Σ(XY) - Σ(X) * Σ (Y) / √ ( N * Σ(X^2) - Σ(X) ) * ( N * Σ(Y^2) - Σ(Y)^2 ) ) ) - * - */ - function _linear(data, decimalPlaces) { - var sum = [0, 0, 0, 0, 0], n = 0, results = [], N = data.length; - - for (; n < data.length; n++) { - if (data[n]['x'] != null) { - data[n][0] = data[n].x; - data[n][1] = data[n].y; - } - if (data[n][1] != null) { - sum[0] += data[n][0]; //Σ(X) - sum[1] += data[n][1]; //Σ(Y) - sum[2] += data[n][0] * data[n][0]; //Σ(X^2) - sum[3] += data[n][0] * data[n][1]; //Σ(XY) - sum[4] += data[n][1] * data[n][1]; //Σ(Y^2) - } else { - N -= 1; - } - } - - var gradient = (N * sum[3] - sum[0] * sum[1]) / (N * sum[2] - sum[0] * sum[0]); - var intercept = (sum[1] / N) - (gradient * sum[0]) / N; - // var correlation = (N * sum[3] - sum[0] * sum[1]) / Math.sqrt((N * sum[2] - sum[0] * sum[0]) * (N * sum[4] - sum[1] * sum[1])); - - for (var i = 0, len = data.length; i < len; i++) { - var coorY = data[i][0] * gradient + intercept; - if (decimalPlaces) - coorY = parseFloat(coorY.toFixed(decimalPlaces)); - var coordinate = [data[i][0], coorY]; - results.push(coordinate); - } - - results.sort(function (a, b) { - if (a[0] > b[0]) { - return 1; - } - if (a[0] < b[0]) { - return -1; - } - return 0; - }); - - var string = 'y = ' + Math.round(gradient * 100) / 100 + 'x + ' + Math.round(intercept * 100) / 100; - return {equation: [gradient, intercept], points: results, string: string}; - } - - /** - * Code extracted from https://github.com/Tom-Alexander/regression-js/ - */ - function _logarithmic(data) { - var sum = [0, 0, 0, 0], n = 0, results = [], mean = 0; - - - for (len = data.length; n < len; n++) { - if (data[n].x != null) { - data[n][0] = data[n].x; - data[n][1] = data[n].y; - } - if (data[n][1] != null) { - sum[0] += Math.log(data[n][0]); - sum[1] += data[n][1] * Math.log(data[n][0]); - sum[2] += data[n][1]; - sum[3] += Math.pow(Math.log(data[n][0]), 2); - } - } - - var B = (n * sum[1] - sum[2] * sum[0]) / (n * sum[3] - sum[0] * sum[0]); - var A = (sum[2] - B * sum[0]) / n; - - for (var i = 0, len = data.length; i < len; i++) { - var coordinate = [data[i][0], A + B * Math.log(data[i][0])]; - results.push(coordinate); - } - - results.sort(function (a, b) { - if (a[0] > b[0]) { - return 1; - } - if (a[0] < b[0]) { - return -1; - } - return 0; - }); - - var string = 'y = ' + Math.round(A * 100) / 100 + ' + ' + Math.round(B * 100) / 100 + ' ln(x)'; - - return {equation: [A, B], points: results, string: string}; - } - - /** - * Code extracted from https://github.com/Tom-Alexander/regression-js/ - */ - function _power(data) { - var sum = [0, 0, 0, 0], n = 0, results = []; - - for (len = data.length; n < len; n++) { - if (data[n].x != null) { - data[n][0] = data[n].x; - data[n][1] = data[n].y; - } - if (data[n][1] != null) { - sum[0] += Math.log(data[n][0]); - sum[1] += Math.log(data[n][1]) * Math.log(data[n][0]); - sum[2] += Math.log(data[n][1]); - sum[3] += Math.pow(Math.log(data[n][0]), 2); - } - } - - var B = (n * sum[1] - sum[2] * sum[0]) / (n * sum[3] - sum[0] * sum[0]); - var A = Math.pow(Math.E, (sum[2] - B * sum[0]) / n); - - for (var i = 0, len = data.length; i < len; i++) { - var coordinate = [data[i][0], A * Math.pow(data[i][0], B)]; - results.push(coordinate); - } - - results.sort(function (a, b) { - if (a[0] > b[0]) { - return 1; - } - if (a[0] < b[0]) { - return -1; - } - return 0; - }); - - var string = 'y = ' + Math.round(A * 100) / 100 + 'x^' + Math.round(B * 100) / 100; - - return {equation: [A, B], points: results, string: string}; - } - - /** - * Code extracted from https://github.com/Tom-Alexander/regression-js/ - */ - function _polynomial(data, order, extrapolate) { - if (typeof order == 'undefined') { - order = 2; - } - var lhs = [], rhs = [], results = [], a = 0, b = 0, i = 0, k = order + 1; - - for (; i < k; i++) { - for (var l = 0, len = data.length; l < len; l++) { - if (data[l].x != null) { - data[l][0] = data[l].x; - data[l][1] = data[l].y; - } - if (data[l][1] != null) { - a += Math.pow(data[l][0], i) * data[l][1]; - } - } - lhs.push(a); - a = 0; - var c = []; - for (var j = 0; j < k; j++) { - for (var l = 0, len = data.length; l < len; l++) { - if (data[l][1]) { - b += Math.pow(data[l][0], i + j); - } - } - c.push(b); - b = 0; - } - rhs.push(c); - } - rhs.push(lhs); - - var equation = gaussianElimination(rhs, k); - - var resultLength = data.length + extrapolate; - var step = data[data.length - 1][0] - data[data.length - 2][0]; - for (var i = 0, len = resultLength; i < len; i++) { - var answer = 0; - var x = 0; - if (typeof data[i] !== 'undefined') { - x = data[i][0]; - } else { - x = data[data.length - 1][0] + (i - data.length) * step; - } - - for (var w = 0; w < equation.length; w++) { - answer += equation[w] * Math.pow(x, w); - } - results.push([x, answer]); - } - - results.sort(function (a, b) { - if (a[0] > b[0]) { - return 1; - } - if (a[0] < b[0]) { - return -1; - } - return 0; - }); - - var string = 'y = '; - - for (var i = equation.length - 1; i >= 0; i--) { - if (i > 1) string += Math.round(equation[i] * 100) / 100 + 'x^' + i + ' + '; - else if (i == 1) string += Math.round(equation[i] * 100) / 100 + 'x' + ' + '; - else string += Math.round(equation[i] * 100) / 100; - } - - return {equation: equation, points: results, string: string}; - } - - /** - * @author: Ignacio Vazquez - * Based on - * - http://commons.apache.org/proper/commons-math/download_math.cgi LoesInterpolator.java - * - https://gist.github.com/avibryant/1151823 - */ - function _loess(data, bandwidth) { - bandwidth = bandwidth || 0.25; - - var xval = data.map(function (pair) { - return pair[0]; - }); - var distinctX = array_unique(xval); - if (2 / distinctX.length > bandwidth) { - bandwidth = Math.min(2 / distinctX.length, 1); - console.warn("updated bandwith to " + bandwidth); - } - - var yval = data.map(function (pair) { - return pair[1]; - }); - - function array_unique(values) { - var o = {}, i, l = values.length, r = []; - for (i = 0; i < l; i += 1) o[values[i]] = values[i]; - for (i in o) r.push(o[i]); - return r; - } - - function tricube(x) { - var tmp = 1 - x * x * x; - return tmp * tmp * tmp; - } - - var res = []; - - var left = 0; - var right = Math.floor(bandwidth * xval.length) - 1; - - for (var i in xval) { - var x = xval[i]; - - if (i > 0) { - if (right < xval.length - 1 && - xval[right + 1] - xval[i] < xval[i] - xval[left]) { - left++; - right++; - } - } - //console.debug("left: "+left + " right: " + right ); - var edge; - if (xval[i] - xval[left] > xval[right] - xval[i]) - edge = left; - else - edge = right; - var denom = Math.abs(1.0 / (xval[edge] - x)); - var sumWeights = 0; - var sumX = 0, sumXSquared = 0, sumY = 0, sumXY = 0; - - var k = left; - while (k <= right) { - var xk = xval[k]; - var yk = yval[k]; - var dist; - if (k < i) { - dist = (x - xk); - } else { - dist = (xk - x); - } - var w = tricube(dist * denom); - var xkw = xk * w; - sumWeights += w; - sumX += xkw; - sumXSquared += xk * xkw; - sumY += yk * w; - sumXY += yk * xkw; - k++; - } - - var meanX = sumX / sumWeights; - //console.debug(meanX); - var meanY = sumY / sumWeights; - var meanXY = sumXY / sumWeights; - var meanXSquared = sumXSquared / sumWeights; - - var beta; - if (meanXSquared == meanX * meanX) - beta = 0; - else - beta = (meanXY - meanX * meanY) / (meanXSquared - meanX * meanX); - - var alpha = meanY - beta * meanX; - res[i] = beta * x + alpha; - } - //console.debug(res); - return { - equation: "", - points: xval.map(function (x, i) { - return [x, res[i]]; - }), - string: "" - }; - } - - - /** - * Code extracted from https://github.com/Tom-Alexander/regression-js/ - */ - function gaussianElimination(a, o) { - var i = 0, j = 0, k = 0, maxrow = 0, tmp = 0, n = a.length - 1, x = new Array(o); - for (i = 0; i < n; i++) { - maxrow = i; - for (j = i + 1; j < n; j++) { - if (Math.abs(a[i][j]) > Math.abs(a[i][maxrow])) - maxrow = j; - } - for (k = i; k < n + 1; k++) { - tmp = a[k][i]; - a[k][i] = a[k][maxrow]; - a[k][maxrow] = tmp; - } - for (j = i + 1; j < n; j++) { - for (k = n; k >= i; k--) { - a[k][j] -= a[k][i] * a[i][j] / a[i][i]; - } - } - } - for (j = n - 1; j >= 0; j--) { - tmp = 0; - for (k = j + 1; k < n; k++) - tmp += a[k][j] * x[k]; - x[j] = (a[n][j] - tmp) / a[j][j]; - } - return (x); - } - - /** - * @author Ignacio Vazquez - * See http://en.wikipedia.org/wiki/Coefficient_of_determination for theaorical details - */ - function coefficientOfDetermination(data, pred) { - // Sort the initial data { pred array (model's predictions) is sorted } - // The initial data must be sorted in the same way in order to calculate the coefficients - data.sort(function (a, b) { - if (a[0] > b[0]) { - return 1; - } - if (a[0] < b[0]) { - return -1; - } - return 0; - }); - - // Calc the mean - var mean = 0; - var N = data.length; - for (var i = 0; i < data.length; i++) { - if (data[i][1] != null) { - mean += data[i][1]; - } else { - N--; - } - } - mean /= N; - - // Calc the coefficent of determination - var SSE = 0; - var SSYY = 0; - for (var i = 0; i < data.length; i++) { - if (data[i][1] != null) { - SSYY += Math.pow(data[i][1] - pred[i][1], 2); - SSE += Math.pow(data[i][1] - mean, 2); - } - } - return 1 - ( SSYY / SSE); - } - - function standardError(data, pred) { - var SE = 0, N = data.length; - - for (var i = 0; i < data.length; i++) { - if (data[i][1] != null) { - SE += Math.pow(data[i][1] - pred[i][1], 2); - } else { - N--; - } - } - SE = Math.sqrt(SE / (N - 2)); - - return SE; - } -})); diff --git a/static/highcharts.js b/static/highcharts.js deleted file mode 100644 index 7a3ffff7..00000000 --- a/static/highcharts.js +++ /dev/null @@ -1,412 +0,0 @@ -/* - Highcharts JS v6.1.1 (2018-06-27) - - (c) 2009-2016 Torstein Honsi - - License: www.highcharts.com/license -*/ -(function(T,K){"object"===typeof module&&module.exports?module.exports=T.document?K(T):K:T.Highcharts=K(T)})("undefined"!==typeof window?window:this,function(T){var K=function(){var a="undefined"===typeof T?window:T,C=a.document,E=a.navigator&&a.navigator.userAgent||"",F=C&&C.createElementNS&&!!C.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect,n=/(edge|msie|trident)/i.test(E)&&!a.opera,h=-1!==E.indexOf("Firefox"),e=-1!==E.indexOf("Chrome"),u=h&&4>parseInt(E.split("Firefox/")[1], -10);return a.Highcharts?a.Highcharts.error(16,!0):{product:"Highcharts",version:"6.1.1",deg2rad:2*Math.PI/360,doc:C,hasBidiBug:u,hasTouch:C&&void 0!==C.documentElement.ontouchstart,isMS:n,isWebKit:-1!==E.indexOf("AppleWebKit"),isFirefox:h,isChrome:e,isSafari:!e&&-1!==E.indexOf("Safari"),isTouchDevice:/(Mobile|Android|Windows Phone)/.test(E),SVG_NS:"http://www.w3.org/2000/svg",chartCount:0,seriesTypes:{},symbolSizes:{},svg:F,win:a,marginNames:["plotTop","marginRight","marginBottom","plotLeft"],noop:function(){}, -charts:[]}}();(function(a){a.timers=[];var C=a.charts,E=a.doc,F=a.win;a.error=function(n,h){n=a.isNumber(n)?"Highcharts error #"+n+": www.highcharts.com/errors/"+n:n;if(h)throw Error(n);F.console&&console.log(n)};a.Fx=function(a,h,e){this.options=h;this.elem=a;this.prop=e};a.Fx.prototype={dSetter:function(){var a=this.paths[0],h=this.paths[1],e=[],u=this.now,y=a.length,q;if(1===u)e=this.toD;else if(y===h.length&&1>u)for(;y--;)q=parseFloat(a[y]),e[y]=isNaN(q)?h[y]:u*parseFloat(h[y]-q)+q;else e=h;this.elem.attr("d", -e,null,!0)},update:function(){var a=this.elem,h=this.prop,e=this.now,u=this.options.step;if(this[h+"Setter"])this[h+"Setter"]();else a.attr?a.element&&a.attr(h,e,null,!0):a.style[h]=e+this.unit;u&&u.call(a,e,this)},run:function(n,h,e){var u=this,y=u.options,q=function(a){return q.stopped?!1:u.step(a)},x=F.requestAnimationFrame||function(a){setTimeout(a,13)},f=function(){for(var c=0;c=x+this.startTime?(this.now=this.end,this.pos=1,this.update(),e=f[this.prop]=!0,a.objectEach(f,function(a){!0!== -a&&(e=!1)}),e&&q&&q.call(y),n=!1):(this.pos=u.easing((h-this.startTime)/x),this.now=this.start+(this.end-this.start)*this.pos,this.update(),n=!0);return n},initPath:function(n,h,e){function u(a){var d,l;for(b=a.length;b--;)d="M"===a[b]||"L"===a[b],l=/[a-zA-Z]/.test(a[b+3]),d&&l&&a.splice(b+1,0,a[b+1],a[b+2],a[b+1],a[b+2])}function y(a,d){for(;a.lengtha&&-Infinity=e&&(h=[1/e])));for(u=0;u=n|| -!y&&q<=(h[u]+(h[u+1]||h[u]))/2);u++);return x=a.correctFloat(x*e,-Math.round(Math.log(.001)/Math.LN10))};a.stableSort=function(a,h){var e=a.length,n,y;for(y=0;ye&&(e=a[h]);return e};a.destroyObjectProperties=function(n,h){a.objectEach(n,function(a, -u){a&&a!==h&&a.destroy&&a.destroy();delete n[u]})};a.discardElement=function(n){var h=a.garbageBin;h||(h=a.createElement("div"));n&&h.appendChild(n);h.innerHTML=""};a.correctFloat=function(a,h){return parseFloat(a.toPrecision(h||14))};a.setAnimation=function(n,h){h.renderer.globalAnimation=a.pick(n,h.options.chart.animation,!0)};a.animObject=function(n){return a.isObject(n)?a.merge(n):{duration:n?500:0}};a.timeUnits={millisecond:1,second:1E3,minute:6E4,hour:36E5,day:864E5,week:6048E5,month:24192E5, -year:314496E5};a.numberFormat=function(n,h,e,u){n=+n||0;h=+h;var y=a.defaultOptions.lang,q=(n.toString().split(".")[1]||"").split("e")[0].length,x,f,c=n.toString().split("e");-1===h?h=Math.min(q,20):a.isNumber(h)?h&&c[1]&&0>c[1]&&(x=h+ +c[1],0<=x?(c[0]=(+c[0]).toExponential(x).split("e")[0],h=x):(c[0]=c[0].split(".")[0]||0,n=20>h?(c[0]*Math.pow(10,c[1])).toFixed(h):0,c[1]=0)):h=2;f=(Math.abs(c[1]?c[0]:n)+Math.pow(10,-Math.max(h,q)-1)).toFixed(h);q=String(a.pInt(f));x=3n?"-":"")+(x?q.substr(0,x)+u:"");n+=q.substr(x).replace(/(\d{3})(?=\d)/g,"$1"+u);h&&(n+=e+f.slice(-h));c[1]&&0!==+n&&(n+="e"+c[1]);return n};Math.easeInOutSine=function(a){return-.5*(Math.cos(Math.PI*a)-1)};a.getStyle=function(n,h,e){if("width"===h)return Math.max(0,Math.min(n.offsetWidth,n.scrollWidth)-a.getStyle(n,"padding-left")-a.getStyle(n,"padding-right"));if("height"===h)return Math.max(0,Math.min(n.offsetHeight,n.scrollHeight)-a.getStyle(n,"padding-top")- -a.getStyle(n,"padding-bottom"));F.getComputedStyle||a.error(27,!0);if(n=F.getComputedStyle(n,void 0))n=n.getPropertyValue(h),a.pick(e,"opacity"!==h)&&(n=a.pInt(n));return n};a.inArray=function(n,h,e){return(a.indexOfPolyfill||Array.prototype.indexOf).call(h,n,e)};a.grep=function(n,h){return(a.filterPolyfill||Array.prototype.filter).call(n,h)};a.find=Array.prototype.find?function(a,h){return a.find(h)}:function(a,h){var e,u=a.length;for(e=0;e>16,(e&65280)>>8,e&255,1]:4===h&&(y=[(e&3840)>>4|(e&3840)>>8,(e&240)>>4|e&240,(e&15)<<4|e&15,1])),!y)for(q=this.parsers.length;q--&&!y;)x=this.parsers[q],(h=x.regex.exec(e))&&(y=x.parse(h));this.rgba=y||[]},get:function(a){var e=this.input,h=this.rgba,q;this.stops?(q=n(e),q.stops=[].concat(q.stops),C(this.stops,function(e,f){q.stops[f]=[q.stops[f][0],e.get(a)]})):q=h&&E(h[0])?"rgb"===a||!a&&1===h[3]?"rgb("+h[0]+","+h[1]+","+h[2]+")":"a"===a?h[3]:"rgba("+h.join(",")+")":e; -return q},brighten:function(a){var e,y=this.rgba;if(this.stops)C(this.stops,function(e){e.brighten(a)});else if(E(a)&&0!==a)for(e=0;3>e;e++)y[e]+=h(255*a),0>y[e]&&(y[e]=0),255z.width)z={width:0,height:0}}else z=this.htmlGetBBox();b.isSVG&&(a=z.width,b=z.height,c&&"11px"===c.fontSize&&17===Math.round(b)&&(z.height=b=14),g&&(z.width=Math.abs(b*Math.sin(d))+Math.abs(a*Math.cos(d)),z.height=Math.abs(b*Math.cos(d))+Math.abs(a*Math.sin(d))));if(p&&0]*>/g,"").replace(/</g,"\x3c").replace(/>/g,"\x3e")))},textSetter:function(a){a!==this.textStr&&(delete this.bBox,this.textStr=a,this.added&&this.renderer.buildText(this))},fillSetter:function(a,g,b){"string"===typeof a?b.setAttribute(g,a):a&&this.complexColor(a,g,b)},visibilitySetter:function(a,g,b){"inherit"===a?b.removeAttribute(g):this[g]!==a&&b.setAttribute(g, -a);this[g]=a},zIndexSetter:function(a,b){var d=this.renderer,w=this.parentGroup,z=(w||d).element||d.box,c,l=this.element,m,f,d=z===d.box;c=this.added;var J;x(a)?(l.setAttribute("data-z-index",a),a=+a,this[b]===a&&(c=!1)):x(this[b])&&l.removeAttribute("data-z-index");this[b]=a;if(c){(a=this.zIndex)&&w&&(w.handleZ=!0);b=z.childNodes;for(J=b.length-1;0<=J&&!m;J--)if(w=b[J],c=w.getAttribute("data-z-index"),f=!x(c),w!==l)if(0>a&&f&&!d&&!J)z.insertBefore(l,b[J]),m=!0;else if(g(c)<=a||f&&(!x(a)||0<=a))z.insertBefore(l, -b[J+1]||null),m=!0;m||(z.insertBefore(l,b[d?3:0]||null),m=!0)}return m},_defaultSetter:function(a,g,b){b.setAttribute(g,a)}});C.prototype.yGetter=C.prototype.xGetter;C.prototype.translateXSetter=C.prototype.translateYSetter=C.prototype.rotationSetter=C.prototype.verticalAlignSetter=C.prototype.rotationOriginXSetter=C.prototype.rotationOriginYSetter=C.prototype.scaleXSetter=C.prototype.scaleYSetter=C.prototype.matrixSetter=function(a,g){this[g]=a;this.doTransform=!0};C.prototype["stroke-widthSetter"]= -C.prototype.strokeSetter=function(a,g,b){this[g]=a;this.stroke&&this["stroke-width"]?(C.prototype.fillSetter.call(this,this.stroke,"stroke",b),b.setAttribute("stroke-width",this["stroke-width"]),this.hasStroke=!0):"stroke-width"===g&&0===a&&this.hasStroke&&(b.removeAttribute("stroke"),this.hasStroke=!1)};E=a.SVGRenderer=function(){this.init.apply(this,arguments)};l(E.prototype,{Element:C,SVG_NS:J,init:function(a,g,b,d,w,c){var z;d=this.createElement("svg").attr({version:"1.1","class":"highcharts-root"}).css(this.getStyle(d)); -z=d.element;a.appendChild(z);h(a,"dir","ltr");-1===a.innerHTML.indexOf("xmlns")&&h(z,"xmlns",this.SVG_NS);this.isSVG=!0;this.box=z;this.boxWrapper=d;this.alignedObjects=[];this.url=(t||m)&&k.getElementsByTagName("base").length?N.location.href.replace(/#.*?$/,"").replace(/<[^>]*>/g,"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(k.createTextNode("Created with Highcharts 6.1.1"));this.defs=this.createElement("defs").add();this.allowHTML=c; -this.forExport=w;this.gradients={};this.cache={};this.cacheKeys=[];this.imgCount=0;this.setSize(g,b,!1);var l;t&&a.getBoundingClientRect&&(g=function(){y(a,{left:0,top:0});l=a.getBoundingClientRect();y(a,{left:Math.ceil(l.left)-l.left+"px",top:Math.ceil(l.top)-l.top+"px"})},g(),this.unSubPixelFix=F(N,"resize",g))},getStyle:function(a){return this.style=l({fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif',fontSize:"12px"},a)},setStyle:function(a){this.boxWrapper.css(this.getStyle(a))}, -isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();c(this.gradients||{});this.gradients=null;a&&(this.defs=a.destroy());this.unSubPixelFix&&this.unSubPixelFix();return this.alignedObjects=null},createElement:function(a){var g=new this.Element;g.init(this,a);return g},draw:B,getRadialAttr:function(a,g){return{cx:a[0]-a[2]/2+g.cx*a[2],cy:a[1]-a[2]/2+g.cy*a[2],r:g.r*a[2]}},getSpanWidth:function(a){return a.getBBox(!0).width}, -applyEllipsis:function(a,g,b,d){var w=a.rotation,c=b,z,l=0,m=b.length,J=function(a){g.removeChild(g.firstChild);a&&g.appendChild(k.createTextNode(a))},f;a.rotation=0;c=this.getSpanWidth(a,g);if(f=c>d){for(;l<=m;)z=Math.ceil((l+m)/2),c=b.substring(0,z)+"\u2026",J(c),c=this.getSpanWidth(a,g),l===m?l=m+1:c>d?m=z-1:l=z;0===m&&J("")}a.rotation=w;return f},escapes:{"\x26":"\x26amp;","\x3c":"\x26lt;","\x3e":"\x26gt;","'":"\x26#39;",'"':"\x26quot;"},buildText:function(a){var d=a.element,w=this,c=w.forExport, -l=G(a.textStr,"").toString(),z=-1!==l.indexOf("\x3c"),m=d.childNodes,f,B=h(d,"x"),v=a.styles,D=a.textWidth,t=v&&v.lineHeight,A=v&&v.textOutline,e=v&&"ellipsis"===v.textOverflow,P=v&&"nowrap"===v.whiteSpace,O=v&&v.fontSize,q,x,I=m.length,v=D&&!a.added&&this.box,H=function(a){var b;b=/(px|em)$/.test(a&&a.style.fontSize)?a.style.fontSize:O||w.style.fontSize||12;return t?g(t):w.fontMetrics(b,a.getAttribute("style")?a:d).h},N=function(a,g){M(w.escapes,function(b,d){g&&-1!==p(b,g)||(a=a.toString().replace(new RegExp(b, -"g"),d))});return a},u=function(a,g){var b;b=a.indexOf("\x3c");a=a.substring(b,a.indexOf("\x3e")-b);b=a.indexOf(g+"\x3d");if(-1!==b&&(b=b+g.length+1,g=a.charAt(b),'"'===g||"'"===g))return a=a.substring(b+1),a.substring(0,a.indexOf(g))};q=[l,e,P,t,A,O,D].join();if(q!==a.textCache){for(a.textCache=q;I--;)d.removeChild(m[I]);z||A||e||D||-1!==l.indexOf(" ")?(v&&v.appendChild(d),l=z?l.replace(/<(b|strong)>/g,'\x3cspan style\x3d"font-weight:bold"\x3e').replace(/<(i|em)>/g,'\x3cspan style\x3d"font-style:italic"\x3e').replace(//g,"\x3c/span\x3e").split(//g):[l],l=b(l,function(a){return""!==a}),r(l,function(g,b){var l,z=0;g=g.replace(/^\s+|\s+$/g,"").replace(//g,"\x3c/span\x3e|||");l=g.split("|||");r(l,function(g){if(""!==g||1===l.length){var m={},v=k.createElementNS(w.SVG_NS,"tspan"),p,r;(p=u(g,"class"))&&h(v,"class",p);if(p=u(g,"style"))p=p.replace(/(;| |^)color([ :])/,"$1fill$2"),h(v,"style",p);(r=u(g,"href"))&&!c&&(h(v,"onclick", -'location.href\x3d"'+r+'"'),h(v,"class","highcharts-anchor"),y(v,{cursor:"pointer"}));g=N(g.replace(/<[a-zA-Z\/](.|\n)*?>/g,"")||" ");if(" "!==g){v.appendChild(k.createTextNode(g));z?m.dx=0:b&&null!==B&&(m.x=B);h(v,m);d.appendChild(v);!z&&x&&(!Q&&c&&y(v,{display:"block"}),h(v,"dy",H(v)));if(D){m=g.replace(/([^\^])-/g,"$1- ").split(" ");r=1D,void 0===f&&(f=g),g&&1!==m.length?(v.removeChild(v.firstChild),t.unshift(m.pop())):(m=t,t=[],m.length&&!P&&(v=k.createElementNS(J,"tspan"),h(v,{dy:O,x:B}),p&&h(v,"style",p),d.appendChild(v)),A>D&&(D=A+1)),m.length&&v.appendChild(k.createTextNode(m.join(" ").replace(/- /g,"-")));a.rotation=q}z++}}});x=x||d.childNodes.length}),e&&f&&a.attr("title",N(a.textStr,["\x26lt;","\x26gt;"])),v&&v.removeChild(d),A&&a.applyTextOutline&&a.applyTextOutline(A)):d.appendChild(k.createTextNode(N(l)))}},getContrast:function(a){a= -u(a).rgba;return 510Math.abs(w.end-w.start-2*Math.PI));var J=Math.cos(c),v=Math.sin(c),z=Math.cos(f),f=Math.sin(f);w=.001>w.end-c-Math.PI?0:1;l=["M",a+l*J,g+m*v,"A",l,m,0,w,1,a+l*z,g+m*f];x(b)&&l.push(d?"M":"L",a+b*z,g+b*f,"A",b,b,0,w,0,a+b*J,g+b*v);l.push(d?"":"Z");return l},callout:function(a, -g,b,d,w){var c=Math.min(w&&w.r||0,b,d),l=c+6,m=w&&w.anchorX;w=w&&w.anchorY;var f;f=["M",a+c,g,"L",a+b-c,g,"C",a+b,g,a+b,g,a+b,g+c,"L",a+b,g+d-c,"C",a+b,g+d,a+b,g+d,a+b-c,g+d,"L",a+c,g+d,"C",a,g+d,a,g+d,a,g+d-c,"L",a,g+c,"C",a,g,a,g,a+c,g];m&&m>b?w>g+l&&wm?w>g+l&&wd&&m>a+l&&mw&&m>a+l&&ma?a+3:Math.round(1.2*a);return{h:b,b:Math.round(.8*b),f:a}},rotCorr:function(a,g,b){var d=a;g&&b&&(d=Math.max(d*Math.cos(g*f), -4));return{x:-a/3*Math.sin(g*f),y:d}},label:function(g,b,d,c,m,f,J,v,B){var k=this,p=k.g("button"!==B&&"label"),t=p.text=k.text("",0,0,J).attr({zIndex:1}),z,A,Q=0,e=3,P=0,h,O,q,G,I,H={},N,y,M=/^url\((.*?)\)$/.test(c),u=M,n,L,R,U;B&&p.addClass("highcharts-"+B);u=M;n=function(){return(N||0)%2/2};L=function(){var a=t.element.style,g={};A=(void 0===h||void 0===O||I)&&x(t.textStr)&&t.getBBox();p.width=(h||A.width||0)+2*e+P;p.height=(O||A.height||0)+2*e;y=e+k.fontMetrics(a&&a.fontSize,t).b;u&&(z||(p.box= -z=k.symbols[c]||M?k.symbol(c):k.rect(),z.addClass(("button"===B?"":"highcharts-label-box")+(B?" highcharts-"+B+"-box":"")),z.add(p),a=n(),g.x=a,g.y=(v?-y:0)+a),g.width=Math.round(p.width),g.height=Math.round(p.height),z.attr(l(g,H)),H={})};R=function(){var a=P+e,g;g=v?0:y;x(h)&&A&&("center"===I||"right"===I)&&(a+={center:.5,right:1}[I]*(h-A.width));if(a!==t.x||g!==t.y)t.attr("x",a),t.hasBoxWidthChanged&&(A=t.getBBox(!0),L()),void 0!==g&&t.attr("y",g);t.x=a;t.y=g};U=function(a,g){z?z.attr(a,g):H[a]= -g};p.onAdd=function(){t.add(p);p.attr({text:g||0===g?g:"",x:b,y:d});z&&x(m)&&p.attr({anchorX:m,anchorY:f})};p.widthSetter=function(g){h=a.isNumber(g)?g:null};p.heightSetter=function(a){O=a};p["text-alignSetter"]=function(a){I=a};p.paddingSetter=function(a){x(a)&&a!==e&&(e=p.padding=a,R())};p.paddingLeftSetter=function(a){x(a)&&a!==P&&(P=a,R())};p.alignSetter=function(a){a={left:0,center:.5,right:1}[a];a!==Q&&(Q=a,A&&p.attr({x:q}))};p.textSetter=function(a){void 0!==a&&t.textSetter(a);L();R()};p["stroke-widthSetter"]= -function(a,g){a&&(u=!0);N=this["stroke-width"]=a;U(g,a)};p.strokeSetter=p.fillSetter=p.rSetter=function(a,g){"r"!==g&&("fill"===g&&a&&(u=!0),p[g]=a);U(g,a)};p.anchorXSetter=function(a,g){m=p.anchorX=a;U(g,Math.round(a)-n()-q)};p.anchorYSetter=function(a,g){f=p.anchorY=a;U(g,a-G)};p.xSetter=function(a){p.x=a;Q&&(a-=Q*((h||A.width)+2*e),p["forceAnimate:x"]=!0);q=Math.round(a);p.attr("translateX",q)};p.ySetter=function(a){G=p.y=Math.round(a);p.attr("translateY",G)};var S=p.css;return l(p,{css:function(a){if(a){var g= -{};a=D(a);r(p.textProps,function(b){void 0!==a[b]&&(g[b]=a[b],delete a[b])});t.css(g);"width"in g&&L()}return S.call(p,a)},getBBox:function(){return{width:A.width+2*e,height:A.height+2*e,x:A.x-e,y:A.y-e}},shadow:function(a){a&&(L(),z&&z.shadow(a));return p},destroy:function(){w(p.element,"mouseenter");w(p.element,"mouseleave");t&&(t=t.destroy());z&&(z=z.destroy());C.prototype.destroy.call(p);p=k=L=R=U=null}})}});a.Renderer=E})(K);(function(a){var C=a.attr,E=a.createElement,F=a.css,n=a.defined,h=a.each, -e=a.extend,u=a.isFirefox,y=a.isMS,q=a.isWebKit,x=a.pick,f=a.pInt,c=a.SVGRenderer,k=a.win,r=a.wrap;e(a.SVGElement.prototype,{htmlCss:function(a){var d=this.element;if(d=a&&"SPAN"===d.tagName&&a.width)delete a.width,this.textWidth=d,this.htmlUpdateTransform();a&&"ellipsis"===a.textOverflow&&(a.whiteSpace="nowrap",a.overflow="hidden");this.styles=e(this.styles,a);F(this.element,a);return this},htmlGetBBox:function(){var a=this.element;return{x:a.offsetLeft,y:a.offsetTop,width:a.offsetWidth,height:a.offsetHeight}}, -htmlUpdateTransform:function(){if(this.added){var a=this.renderer,d=this.element,b=this.translateX||0,c=this.translateY||0,p=this.x||0,k=this.y||0,t=this.textAlign||"left",r={left:0,center:.5,right:1}[t],A=this.styles,e=A&&A.whiteSpace;F(d,{marginLeft:b,marginTop:c});this.shadows&&h(this.shadows,function(a){F(a,{marginLeft:b+1,marginTop:c+1})});this.inverted&&h(d.childNodes,function(b){a.invertChild(b,d)});if("SPAN"===d.tagName){var A=this.rotation,m=this.textWidth&&f(this.textWidth),D=[A,t,d.innerHTML, -this.textWidth,this.textAlign].join(),B;(B=m!==this.oldTextWidth)&&!(B=m>this.oldTextWidth)&&((B=this.textPxLength)||(F(d,{width:"",whiteSpace:e||"nowrap"}),B=d.offsetWidth),B=B>m);B&&/[ \-]/.test(d.textContent||d.innerText)?(F(d,{width:m+"px",display:"block",whiteSpace:e||"normal"}),this.oldTextWidth=m,this.hasBoxWidthChanged=!0):this.hasBoxWidthChanged=!1;D!==this.cTT&&(e=a.fontMetrics(d.style.fontSize).b,n(A)&&A!==(this.oldRotation||0)&&this.setSpanRotation(A,r,e),this.getSpanCorrection(!n(A)&& -this.textPxLength||d.offsetWidth,e,r,A,t));F(d,{left:p+(this.xCorr||0)+"px",top:k+(this.yCorr||0)+"px"});this.cTT=D;this.oldRotation=A}}else this.alignOnAdd=!0},setSpanRotation:function(a,d,b){var c={},l=this.renderer.getTransformKey();c[l]=c.transform="rotate("+a+"deg)";c[l+(u?"Origin":"-origin")]=c.transformOrigin=100*d+"% "+b+"px";F(this.element,c)},getSpanCorrection:function(a,d,b){this.xCorr=-a*b;this.yCorr=-d}});e(c.prototype,{getTransformKey:function(){return y&&!/Edge/.test(k.navigator.userAgent)? -"-ms-transform":q?"-webkit-transform":u?"MozTransform":k.opera?"-o-transform":""},html:function(a,d,b){var c=this.createElement("span"),l=c.element,f=c.renderer,k=f.isSVG,q=function(a,b){h(["opacity","visibility"],function(d){r(a,d+"Setter",function(a,d,c,m){a.call(this,d,c,m);b[c]=d})});a.addedSetters=!0};c.textSetter=function(a){a!==l.innerHTML&&delete this.bBox;this.textStr=a;l.innerHTML=x(a,"");c.doTransform=!0};k&&q(c,c.element.style);c.xSetter=c.ySetter=c.alignSetter=c.rotationSetter=function(a, -b){"align"===b&&(b="textAlign");c[b]=a;c.doTransform=!0};c.afterSetters=function(){this.doTransform&&(this.htmlUpdateTransform(),this.doTransform=!1)};c.attr({text:a,x:Math.round(d),y:Math.round(b)}).css({fontFamily:this.style.fontFamily,fontSize:this.style.fontSize,position:"absolute"});l.style.whiteSpace="nowrap";c.css=c.htmlCss;k&&(c.add=function(a){var b,d=f.box.parentNode,p=[];if(this.parentGroup=a){if(b=a.div,!b){for(;a;)p.push(a),a=a.parentGroup;h(p.reverse(),function(a){function m(g,b){a[b]= -g;"translateX"===b?l.left=g+"px":l.top=g+"px";a.doTransform=!0}var l,g=C(a.element,"class");g&&(g={className:g});b=a.div=a.div||E("div",g,{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px",display:a.display,opacity:a.opacity,pointerEvents:a.styles&&a.styles.pointerEvents},b||d);l=b.style;e(a,{classSetter:function(a){return function(g){this.element.setAttribute("class",g);a.className=g}}(b),on:function(){p[0].div&&c.on.apply({element:p[0].div},arguments);return a},translateXSetter:m, -translateYSetter:m});a.addedSetters||q(a,l)})}}else b=d;b.appendChild(l);c.added=!0;c.alignOnAdd&&c.htmlUpdateTransform();return c});return c}})})(K);(function(a){var C=a.defined,E=a.each,F=a.extend,n=a.merge,h=a.pick,e=a.timeUnits,u=a.win;a.Time=function(a){this.update(a,!1)};a.Time.prototype={defaultOptions:{},update:function(e){var q=h(e&&e.useUTC,!0),x=this;this.options=e=n(!0,this.options||{},e);this.Date=e.Date||u.Date;this.timezoneOffset=(this.useUTC=q)&&e.timezoneOffset;this.getTimezoneOffset= -this.timezoneOffsetFunction();(this.variableTimezone=!(q&&!e.getTimezoneOffset&&!e.timezone))||this.timezoneOffset?(this.get=function(a,c){var f=c.getTime(),r=f-x.getTimezoneOffset(c);c.setTime(r);a=c["getUTC"+a]();c.setTime(f);return a},this.set=function(f,c,k){var r;if(-1!==a.inArray(f,["Milliseconds","Seconds","Minutes"]))c["set"+f](k);else r=x.getTimezoneOffset(c),r=c.getTime()-r,c.setTime(r),c["setUTC"+f](k),f=x.getTimezoneOffset(c),r=c.getTime()+f,c.setTime(r)}):q?(this.get=function(a,c){return c["getUTC"+ -a]()},this.set=function(a,c,k){return c["setUTC"+a](k)}):(this.get=function(a,c){return c["get"+a]()},this.set=function(a,c,k){return c["set"+a](k)})},makeTime:function(e,q,x,f,c,k){var r,l,d;this.useUTC?(r=this.Date.UTC.apply(0,arguments),l=this.getTimezoneOffset(r),r+=l,d=this.getTimezoneOffset(r),l!==d?r+=d-l:l-36E5!==this.getTimezoneOffset(r-36E5)||a.isSafari||(r-=36E5)):r=(new this.Date(e,q,h(x,1),h(f,0),h(c,0),h(k,0))).getTime();return r},timezoneOffsetFunction:function(){var e=this,h=this.options, -x=u.moment;if(!this.useUTC)return function(a){return 6E4*(new Date(a)).getTimezoneOffset()};if(h.timezone){if(x)return function(a){return 6E4*-x.tz(a,h.timezone).utcOffset()};a.error(25)}return this.useUTC&&h.getTimezoneOffset?function(a){return 6E4*h.getTimezoneOffset(a)}:function(){return 6E4*(e.timezoneOffset||0)}},dateFormat:function(e,h,x){if(!a.defined(h)||isNaN(h))return a.defaultOptions.lang.invalidDate||"";e=a.pick(e,"%Y-%m-%d %H:%M:%S");var f=this,c=new this.Date(h),k=this.get("Hours",c), -r=this.get("Day",c),l=this.get("Date",c),d=this.get("Month",c),b=this.get("FullYear",c),v=a.defaultOptions.lang,p=v.weekdays,q=v.shortWeekdays,t=a.pad,c=a.extend({a:q?q[r]:p[r].substr(0,3),A:p[r],d:t(l),e:t(l,2," "),w:r,b:v.shortMonths[d],B:v.months[d],m:t(d+1),o:d+1,y:b.toString().substr(2,2),Y:b,H:t(k),k:k,I:t(k%12||12),l:k%12||12,M:t(f.get("Minutes",c)),p:12>k?"AM":"PM",P:12>k?"am":"pm",S:t(c.getSeconds()),L:t(Math.round(h%1E3),3)},a.dateFormats);a.objectEach(c,function(a,b){for(;-1!==e.indexOf("%"+ -b);)e=e.replace("%"+b,"function"===typeof a?a.call(f,h):a)});return x?e.substr(0,1).toUpperCase()+e.substr(1):e},getTimeTicks:function(a,q,x,f){var c=this,k=[],r={},l,d=new c.Date(q),b=a.unitRange,v=a.count||1,p;if(C(q)){c.set("Milliseconds",d,b>=e.second?0:v*Math.floor(c.get("Milliseconds",d)/v));b>=e.second&&c.set("Seconds",d,b>=e.minute?0:v*Math.floor(c.get("Seconds",d)/v));b>=e.minute&&c.set("Minutes",d,b>=e.hour?0:v*Math.floor(c.get("Minutes",d)/v));b>=e.hour&&c.set("Hours",d,b>=e.day?0:v*Math.floor(c.get("Hours", -d)/v));b>=e.day&&c.set("Date",d,b>=e.month?1:v*Math.floor(c.get("Date",d)/v));b>=e.month&&(c.set("Month",d,b>=e.year?0:v*Math.floor(c.get("Month",d)/v)),l=c.get("FullYear",d));b>=e.year&&c.set("FullYear",d,l-l%v);b===e.week&&c.set("Date",d,c.get("Date",d)-c.get("Day",d)+h(f,1));l=c.get("FullYear",d);f=c.get("Month",d);var I=c.get("Date",d),t=c.get("Hours",d);q=d.getTime();c.variableTimezone&&(p=x-q>4*e.month||c.getTimezoneOffset(q)!==c.getTimezoneOffset(x));d=d.getTime();for(q=1;dk.length&&E(k,function(a){0===a%18E5&&"000000000"===c.dateFormat("%H%M%S%L",a)&&(r[a]="day")})}k.info=F(a,{higherRanks:r,totalRange:b*v});return k}}})(K);(function(a){var C=a.color,E=a.merge;a.defaultOptions={colors:"#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1".split(" "), -symbols:["circle","diamond","square","triangle","triangle-down"],lang:{loading:"Loading...",months:"January February March April May June July August September October November December".split(" "),shortMonths:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),weekdays:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),decimalPoint:".",numericSymbols:"kMGTPE".split(""),resetZoom:"Reset zoom",resetZoomTitle:"Reset zoom level 1:1",thousandsSep:" "},global:{},time:a.Time.prototype.defaultOptions, -chart:{borderRadius:0,defaultSeriesType:"line",ignoreHiddenSeries:!0,spacing:[10,10,15,10],resetZoomButton:{theme:{zIndex:6},position:{align:"right",x:-10,y:10}},width:null,height:null,borderColor:"#335cad",backgroundColor:"#ffffff",plotBorderColor:"#cccccc"},title:{text:"Chart title",align:"center",margin:15,widthAdjust:-44},subtitle:{text:"",align:"center",widthAdjust:-44},plotOptions:{},labels:{style:{position:"absolute",color:"#333333"}},legend:{enabled:!0,align:"center",alignColumns:!0,layout:"horizontal", -labelFormatter:function(){return this.name},borderColor:"#999999",borderRadius:0,navigation:{activeColor:"#003399",inactiveColor:"#cccccc"},itemStyle:{color:"#333333",fontSize:"12px",fontWeight:"bold",textOverflow:"ellipsis"},itemHoverStyle:{color:"#000000"},itemHiddenStyle:{color:"#cccccc"},shadow:!1,itemCheckboxStyle:{position:"absolute",width:"13px",height:"13px"},squareSymbol:!0,symbolPadding:5,verticalAlign:"bottom",x:0,y:0,title:{style:{fontWeight:"bold"}}},loading:{labelStyle:{fontWeight:"bold", -position:"relative",top:"45%"},style:{position:"absolute",backgroundColor:"#ffffff",opacity:.5,textAlign:"center"}},tooltip:{enabled:!0,animation:a.svg,borderRadius:3,dateTimeLabelFormats:{millisecond:"%A, %b %e, %H:%M:%S.%L",second:"%A, %b %e, %H:%M:%S",minute:"%A, %b %e, %H:%M",hour:"%A, %b %e, %H:%M",day:"%A, %b %e, %Y",week:"Week from %A, %b %e, %Y",month:"%B %Y",year:"%Y"},footerFormat:"",padding:8,snap:a.isTouchDevice?25:10,backgroundColor:C("#f7f7f7").setOpacity(.85).get(),borderWidth:1,headerFormat:'\x3cspan style\x3d"font-size: 10px"\x3e{point.key}\x3c/span\x3e\x3cbr/\x3e', -pointFormat:'\x3cspan style\x3d"color:{point.color}"\x3e\u25cf\x3c/span\x3e {series.name}: \x3cb\x3e{point.y}\x3c/b\x3e\x3cbr/\x3e',shadow:!0,style:{color:"#333333",cursor:"default",fontSize:"12px",pointerEvents:"none",whiteSpace:"nowrap"}},credits:{enabled:!0,href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#999999",fontSize:"9px"},text:"Highcharts.com"}};a.setOptions=function(C){a.defaultOptions=E(!0,a.defaultOptions,C);a.time.update(E(a.defaultOptions.global, -a.defaultOptions.time),!1);return a.defaultOptions};a.getOptions=function(){return a.defaultOptions};a.defaultPlotOptions=a.defaultOptions.plotOptions;a.time=new a.Time(E(a.defaultOptions.global,a.defaultOptions.time));a.dateFormat=function(C,n,h){return a.time.dateFormat(C,n,h)}})(K);(function(a){var C=a.correctFloat,E=a.defined,F=a.destroyObjectProperties,n=a.fireEvent,h=a.isNumber,e=a.merge,u=a.pick,y=a.deg2rad;a.Tick=function(a,e,f,c){this.axis=a;this.pos=e;this.type=f||"";this.isNewLabel=this.isNew= -!0;f||c||this.addLabel()};a.Tick.prototype={addLabel:function(){var a=this.axis,h=a.options,f=a.chart,c=a.categories,k=a.names,r=this.pos,l=h.labels,d=a.tickPositions,b=r===d[0],v=r===d[d.length-1],k=c?u(c[r],k[r],r):r,c=this.label,d=d.info,p;a.isDatetimeAxis&&d&&(p=h.dateTimeLabelFormats[d.higherRanks[r]||d.unitName]);this.isFirst=b;this.isLast=v;h=a.labelFormatter.call({axis:a,chart:f,isFirst:b,isLast:v,dateTimeLabelFormat:p,value:a.isLog?C(a.lin2log(k)):k,pos:r});if(E(c))c&&c.attr({text:h});else{if(this.label= -c=E(h)&&l.enabled?f.renderer.text(h,0,0,l.useHTML).css(e(l.style)).add(a.labelGroup):null)c.textPxLength=c.getBBox().width;this.rotation=0}},getLabelSize:function(){return this.label?this.label.getBBox()[this.axis.horiz?"height":"width"]:0},handleOverflow:function(a){var e=this.axis,f=e.options.labels,c=a.x,k=e.chart.chartWidth,r=e.chart.spacing,l=u(e.labelLeft,Math.min(e.pos,r[3])),r=u(e.labelRight,Math.max(e.isRadial?0:e.pos+e.len,k-r[1])),d=this.label,b=this.rotation,v={left:0,center:.5,right:1}[e.labelAlign|| -d.attr("align")],p=d.getBBox().width,h=e.getSlotWidth(this),t=h,q=1,A,H={};if(b||!1===f.overflow)0>b&&c-v*pr&&(A=Math.round((k-c)/Math.cos(b*y)));else if(k=c+(1-v)*p,c-v*pr&&(t=r-a.x+t*v,q=-1),t=Math.min(h,t),tt||e.autoRotation&&(d.styles||{}).width)A=t;A&&(H.width=A,(f.style||{}).textOverflow||(H.textOverflow="ellipsis"),d.css(H))},getPosition:function(e,h,f,c){var k= -this.axis,r=k.chart,l=c&&r.oldChartHeight||r.chartHeight;e={x:e?a.correctFloat(k.translate(h+f,null,null,c)+k.transB):k.left+k.offset+(k.opposite?(c&&r.oldChartWidth||r.chartWidth)-k.right-k.left:0),y:e?l-k.bottom+k.offset-(k.opposite?k.height:0):a.correctFloat(l-k.translate(h+f,null,null,c)-k.transB)};n(this,"afterGetPosition",{pos:e});return e},getLabelPosition:function(a,e,f,c,k,r,l,d){var b=this.axis,v=b.transA,p=b.reversed,h=b.staggerLines,t=b.tickRotCorr||{x:0,y:0},q=k.y,A=c||b.reserveSpaceDefault? -0:-b.labelOffset*("center"===b.labelAlign?.5:1),x={};E(q)||(q=0===b.side?f.rotation?-8:-f.getBBox().height:2===b.side?t.y+8:Math.cos(f.rotation*y)*(t.y-f.getBBox(!1,0).height/2));a=a+k.x+A+t.x-(r&&c?r*v*(p?-1:1):0);e=e+q-(r&&!c?r*v*(p?1:-1):0);h&&(f=l/(d||1)%h,b.opposite&&(f=h-f-1),e+=b.labelOffset/h*f);x.x=a;x.y=Math.round(e);n(this,"afterGetLabelPosition",{pos:x});return x},getMarkPath:function(a,e,f,c,k,r){return r.crispLine(["M",a,e,"L",a+(k?0:-f),e+(k?f:0)],c)},renderGridLine:function(a,e,f){var c= -this.axis,k=c.options,r=this.gridLine,l={},d=this.pos,b=this.type,v=c.tickmarkOffset,p=c.chart.renderer,h=b?b+"Grid":"grid",t=k[h+"LineWidth"],q=k[h+"LineColor"],k=k[h+"LineDashStyle"];r||(l.stroke=q,l["stroke-width"]=t,k&&(l.dashstyle=k),b||(l.zIndex=1),a&&(l.opacity=0),this.gridLine=r=p.path().attr(l).addClass("highcharts-"+(b?b+"-":"")+"grid-line").add(c.gridGroup));if(!a&&r&&(a=c.getPlotLinePath(d+v,r.strokeWidth()*f,a,!0)))r[this.isNew?"attr":"animate"]({d:a,opacity:e})},renderMark:function(a, -e,f){var c=this.axis,k=c.options,r=c.chart.renderer,l=this.type,d=l?l+"Tick":"tick",b=c.tickSize(d),v=this.mark,p=!v,h=a.x;a=a.y;var t=u(k[d+"Width"],!l&&c.isXAxis?1:0),k=k[d+"Color"];b&&(c.opposite&&(b[0]=-b[0]),p&&(this.mark=v=r.path().addClass("highcharts-"+(l?l+"-":"")+"tick").add(c.axisGroup),v.attr({stroke:k,"stroke-width":t})),v[p?"attr":"animate"]({d:this.getMarkPath(h,a,b[0],v.strokeWidth()*f,c.horiz,r),opacity:e}))},renderLabel:function(a,e,f,c){var k=this.axis,r=k.horiz,l=k.options,d=this.label, -b=l.labels,v=b.step,k=k.tickmarkOffset,p=!0,I=a.x;a=a.y;d&&h(I)&&(d.xy=a=this.getLabelPosition(I,a,d,r,b,k,c,v),this.isFirst&&!this.isLast&&!u(l.showFirstLabel,1)||this.isLast&&!this.isFirst&&!u(l.showLastLabel,1)?p=!1:!r||b.step||b.rotation||e||0===f||this.handleOverflow(a),v&&c%v&&(p=!1),p&&h(a.y)?(a.opacity=f,d[this.isNewLabel?"attr":"animate"](a),this.isNewLabel=!1):(d.attr("y",-9999),this.isNewLabel=!0))},render:function(e,h,f){var c=this.axis,k=c.horiz,r=this.getPosition(k,this.pos,c.tickmarkOffset, -h),l=r.x,d=r.y,c=k&&l===c.pos+c.len||!k&&d===c.pos?-1:1;f=u(f,1);this.isActive=!0;this.renderGridLine(h,f,c);this.renderMark(r,f,c);this.renderLabel(r,h,f,e);this.isNew=!1;a.fireEvent(this,"afterRender")},destroy:function(){F(this,this.axis)}}})(K);var V=function(a){var C=a.addEvent,E=a.animObject,F=a.arrayMax,n=a.arrayMin,h=a.color,e=a.correctFloat,u=a.defaultOptions,y=a.defined,q=a.deg2rad,x=a.destroyObjectProperties,f=a.each,c=a.extend,k=a.fireEvent,r=a.format,l=a.getMagnitude,d=a.grep,b=a.inArray, -v=a.isArray,p=a.isNumber,I=a.isString,t=a.merge,L=a.normalizeTickInterval,A=a.objectEach,H=a.pick,m=a.removeEvent,D=a.splat,B=a.syncTimeout,M=a.Tick,G=function(){this.init.apply(this,arguments)};a.extend(G.prototype,{defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,labels:{enabled:!0,style:{color:"#666666",cursor:"default",fontSize:"11px"},x:0},maxPadding:.01,minorTickLength:2, -minorTickPosition:"outside",minPadding:.01,startOfWeek:1,startOnTick:!1,tickLength:10,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",title:{align:"middle",style:{color:"#666666"}},type:"linear",minorGridLineColor:"#f2f2f2",minorGridLineWidth:1,minorTickColor:"#999999",lineColor:"#ccd6eb",lineWidth:1,gridLineColor:"#e6e6e6",tickColor:"#ccd6eb"},defaultYAxisOptions:{endOnTick:!0,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8},maxPadding:.05,minPadding:.05,startOnTick:!0, -title:{rotation:270,text:"Values"},stackLabels:{allowOverlap:!1,enabled:!1,formatter:function(){return a.numberFormat(this.total,-1)},style:{fontSize:"11px",fontWeight:"bold",color:"#000000",textOutline:"1px contrast"}},gridLineWidth:1,lineWidth:0},defaultLeftAxisOptions:{labels:{x:-15},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15},title:{rotation:90}},defaultBottomAxisOptions:{labels:{autoRotation:[-45],x:0},title:{rotation:0}},defaultTopAxisOptions:{labels:{autoRotation:[-45],x:0}, -title:{rotation:0}},init:function(a,d){var g=d.isX,w=this;w.chart=a;w.horiz=a.inverted&&!w.isZAxis?!g:g;w.isXAxis=g;w.coll=w.coll||(g?"xAxis":"yAxis");k(this,"init",{userOptions:d});w.opposite=d.opposite;w.side=d.side||(w.horiz?w.opposite?0:2:w.opposite?1:3);w.setOptions(d);var c=this.options,m=c.type;w.labelFormatter=c.labels.formatter||w.defaultLabelFormatter;w.userOptions=d;w.minPixelPadding=0;w.reversed=c.reversed;w.visible=!1!==c.visible;w.zoomEnabled=!1!==c.zoomEnabled;w.hasNames="category"=== -m||!0===c.categories;w.categories=c.categories||w.hasNames;w.names||(w.names=[],w.names.keys={});w.plotLinesAndBandsGroups={};w.isLog="logarithmic"===m;w.isDatetimeAxis="datetime"===m;w.positiveValuesOnly=w.isLog&&!w.allowNegativeLog;w.isLinked=y(c.linkedTo);w.ticks={};w.labelEdge=[];w.minorTicks={};w.plotLinesAndBands=[];w.alternateBands={};w.len=0;w.minRange=w.userMinRange=c.minRange||c.maxZoom;w.range=c.range;w.offset=c.offset||0;w.stacks={};w.oldStacks={};w.stacksTouched=0;w.max=null;w.min=null; -w.crosshair=H(c.crosshair,D(a.options.tooltip.crosshairs)[g?0:1],!1);d=w.options.events;-1===b(w,a.axes)&&(g?a.axes.splice(a.xAxis.length,0,w):a.axes.push(w),a[w.coll].push(w));w.series=w.series||[];a.inverted&&!w.isZAxis&&g&&void 0===w.reversed&&(w.reversed=!0);A(d,function(a,g){C(w,g,a)});w.lin2log=c.linearToLogConverter||w.lin2log;w.isLog&&(w.val2lin=w.log2lin,w.lin2val=w.lin2log);k(this,"afterInit")},setOptions:function(a){this.options=t(this.defaultOptions,"yAxis"===this.coll&&this.defaultYAxisOptions, -[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],t(u[this.coll],a));k(this,"afterSetOptions",{userOptions:a})},defaultLabelFormatter:function(){var g=this.axis,b=this.value,d=g.chart.time,c=g.categories,m=this.dateTimeLabelFormat,l=u.lang,f=l.numericSymbols,l=l.numericSymbolMagnitude||1E3,p=f&&f.length,k,v=g.options.labels.format,g=g.isLog?Math.abs(b):g.tickInterval;if(v)k=r(v,this,d);else if(c)k=b;else if(m)k=d.dateFormat(m, -b);else if(p&&1E3<=g)for(;p--&&void 0===k;)d=Math.pow(l,p+1),g>=d&&0===10*b%d&&null!==f[p]&&0!==b&&(k=a.numberFormat(b/d,-1)+f[p]);void 0===k&&(k=1E4<=Math.abs(b)?a.numberFormat(b,-1):a.numberFormat(b,-1,void 0,""));return k},getSeriesExtremes:function(){var a=this,b=a.chart;k(this,"getSeriesExtremes",null,function(){a.hasVisibleSeries=!1;a.dataMin=a.dataMax=a.threshold=null;a.softThreshold=!a.isXAxis;a.buildStacks&&a.buildStacks();f(a.series,function(g){if(g.visible||!b.options.chart.ignoreHiddenSeries){var w= -g.options,c=w.threshold,m;a.hasVisibleSeries=!0;a.positiveValuesOnly&&0>=c&&(c=null);if(a.isXAxis)w=g.xData,w.length&&(g=n(w),m=F(w),p(g)||g instanceof Date||(w=d(w,p),g=n(w),m=F(w)),w.length&&(a.dataMin=Math.min(H(a.dataMin,w[0],g),g),a.dataMax=Math.max(H(a.dataMax,w[0],m),m)));else if(g.getExtremes(),m=g.dataMax,g=g.dataMin,y(g)&&y(m)&&(a.dataMin=Math.min(H(a.dataMin,g),g),a.dataMax=Math.max(H(a.dataMax,m),m)),y(c)&&(a.threshold=c),!w.softThreshold||a.positiveValuesOnly)a.softThreshold=!1}})}); -k(this,"afterGetSeriesExtremes")},translate:function(a,b,d,c,m,l){var g=this.linkedParent||this,w=1,f=0,J=c?g.oldTransA:g.transA;c=c?g.oldMin:g.min;var k=g.minPixelPadding;m=(g.isOrdinal||g.isBroken||g.isLog&&m)&&g.lin2val;J||(J=g.transA);d&&(w*=-1,f=g.len);g.reversed&&(w*=-1,f-=w*(g.sector||g.len));b?(a=(a*w+f-k)/J+c,m&&(a=g.lin2val(a))):(m&&(a=g.val2lin(a)),a=p(c)?w*(a-c)*J+f+w*k+(p(l)?J*l:0):void 0);return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)}, -toValue:function(a,b){return this.translate(a-(b?0:this.pos),!0,!this.horiz,null,!0)},getPlotLinePath:function(a,b,d,c,m){var g=this.chart,w=this.left,l=this.top,f,J,k=d&&g.oldChartHeight||g.chartHeight,v=d&&g.oldChartWidth||g.chartWidth,e;f=this.transB;var B=function(a,g,b){if(ab)c?a=Math.min(Math.max(g,a),b):e=!0;return a};m=H(m,this.translate(a,null,null,d));m=Math.min(Math.max(-1E5,m),1E5);a=d=Math.round(m+f);f=J=Math.round(k-m-f);p(m)?this.horiz?(f=l,J=k-this.bottom,a=d=B(a,w,w+this.width)): -(a=w,d=v-this.right,f=J=B(f,l,l+this.height)):(e=!0,c=!1);return e&&!c?null:g.renderer.crispLine(["M",a,f,"L",d,J],b||1)},getLinearTickPositions:function(a,b,d){var g,w=e(Math.floor(b/a)*a);d=e(Math.ceil(d/a)*a);var c=[],m;e(w+a)===w&&(m=20);if(this.single)return[b];for(b=w;b<=d;){c.push(b);b=e(b+a,m);if(b===g)break;g=b}return c},getMinorTickInterval:function(){var a=this.options;return!0===a.minorTicks?H(a.minorTickInterval,"auto"):!1===a.minorTicks?null:a.minorTickInterval},getMinorTickPositions:function(){var a= -this,b=a.options,d=a.tickPositions,c=a.minorTickInterval,m=[],l=a.pointRangePadding||0,p=a.min-l,l=a.max+l,k=l-p;if(k&&k/c=this.minRange,B=this.minRange,c=(B-d+b)/2,c=[b-c,H(a.min,b-c)],m&&(c[2]=this.isLog?this.log2lin(this.dataMin):this.dataMin),b=F(c),d=[b+B, -H(a.max,b+B)],m&&(d[2]=this.isLog?this.log2lin(this.dataMax):this.dataMax),d=n(d),d-b=I?(q=I,D=0):g.dataMax<=I&&(u=I,r=0)),g.min=H(M,q,g.dataMin),g.max=H(n,u,g.dataMax));m&&(g.positiveValuesOnly&&!b&&0>=Math.min(g.min,H(g.dataMin,g.min))&&a.error(10,1),g.min= -e(g.log2lin(g.min),15),g.max=e(g.log2lin(g.max),15));g.range&&y(g.max)&&(g.userMin=g.min=M=Math.max(g.dataMin,g.minFromRange()),g.userMax=n=g.max,g.range=null);k(g,"foundExtremes");g.beforePadding&&g.beforePadding();g.adjustForMinRange();!(G||g.axisPointRange||g.usePercentage||t)&&y(g.min)&&y(g.max)&&(d=g.max-g.min)&&(!y(M)&&D&&(g.min-=d*D),!y(n)&&r&&(g.max+=d*r));p(c.softMin)&&!p(g.userMin)&&(g.min=Math.min(g.min,c.softMin));p(c.softMax)&&!p(g.userMax)&&(g.max=Math.max(g.max,c.softMax));p(c.floor)&& -(g.min=Math.max(g.min,c.floor));p(c.ceiling)&&(g.max=Math.min(g.max,c.ceiling));x&&y(g.dataMin)&&(I=I||0,!y(M)&&g.min=I?g.min=I:!y(n)&&g.max>I&&g.dataMax<=I&&(g.max=I));g.tickInterval=g.min===g.max||void 0===g.min||void 0===g.max?1:t&&!h&&A===g.linkedParent.options.tickPixelInterval?h=g.linkedParent.tickInterval:H(h,this.tickAmount?(g.max-g.min)/Math.max(this.tickAmount-1,1):void 0,G?1:(g.max-g.min)*A/Math.max(g.len,A));B&&!b&&f(g.series,function(a){a.processData(g.min!==g.oldMin||g.max!== -g.oldMax)});g.setAxisTranslation(!0);g.beforeSetTickPositions&&g.beforeSetTickPositions();g.postProcessTickInterval&&(g.tickInterval=g.postProcessTickInterval(g.tickInterval));g.pointRange&&!h&&(g.tickInterval=Math.max(g.pointRange,g.tickInterval));b=H(c.minTickInterval,g.isDatetimeAxis&&g.closestPointRange);!h&&g.tickIntervalg.tickInterval&&1E3g.max)),!!this.tickAmount)); -this.tickAmount||(g.tickInterval=g.unsquish());this.setTickPositions()},setTickPositions:function(){var a=this.options,b,d=a.tickPositions;b=this.getMinorTickInterval();var c=a.tickPositioner,m=a.startOnTick,l=a.endOnTick;this.tickmarkOffset=this.categories&&"between"===a.tickmarkPlacement&&1===this.tickInterval?.5:0;this.minorTickInterval="auto"===b&&this.tickInterval?this.tickInterval/5:b;this.single=this.min===this.max&&y(this.min)&&!this.tickAmount&&(parseInt(this.min,10)===this.min||!1!==a.allowDecimals); -this.tickPositions=b=d&&d.slice();!b&&(b=this.isDatetimeAxis?this.getTimeTicks(this.normalizeTimeTickInterval(this.tickInterval,a.units),this.min,this.max,a.startOfWeek,this.ordinalPositions,this.closestPointRange,!0):this.isLog?this.getLogTickPositions(this.tickInterval,this.min,this.max):this.getLinearTickPositions(this.tickInterval,this.min,this.max),b.length>this.len&&(b=[b[0],b.pop()],b[0]===b[1]&&(b.length=1)),this.tickPositions=b,c&&(c=c.apply(this,[this.min,this.max])))&&(this.tickPositions= -b=c);this.paddedTicks=b.slice(0);this.trimTicks(b,m,l);this.isLinked||(this.single&&2>b.length&&(this.min-=.5,this.max+=.5),d||c||this.adjustTickAmount());k(this,"afterSetTickPositions")},trimTicks:function(a,b,d){var g=a[0],c=a[a.length-1],m=this.minPointOffset||0;if(!this.isLinked){if(b&&-Infinity!==g)this.min=g;else for(;this.min-m>a[0];)a.shift();if(d)this.max=c;else for(;this.max+mb&&(this.finalTickAmt=b,b=5);this.tickAmount=b},adjustTickAmount:function(){var a=this.tickInterval,b=this.tickPositions,d=this.tickAmount,c=this.finalTickAmt,m=b&&b.length,l=H(this.threshold,this.softThreshold?0:null);if(this.hasData()){if(md&&(this.tickInterval*=2,this.setTickPositions());if(y(c)){for(a=d=b.length;a--;)(3===c&& -1===a%2||2>=c&&0c&&(a=c)),y(d)&&(bc&&(b=c))),this.displayBtn=void 0!==a||void 0!==b,this.setExtremes(a,b,!1,void 0,{trigger:"zoom"});return!0},setAxisSize:function(){var b=this.chart,d=this.options,c=d.offsets||[0,0,0,0],m=this.horiz,l=this.width=Math.round(a.relativeLength(H(d.width,b.plotWidth-c[3]+c[1]),b.plotWidth)),f=this.height=Math.round(a.relativeLength(H(d.height,b.plotHeight- -c[0]+c[2]),b.plotHeight)),p=this.top=Math.round(a.relativeLength(H(d.top,b.plotTop+c[0]),b.plotHeight,b.plotTop)),d=this.left=Math.round(a.relativeLength(H(d.left,b.plotLeft+c[3]),b.plotWidth,b.plotLeft));this.bottom=b.chartHeight-f-p;this.right=b.chartWidth-l-d;this.len=Math.max(m?l:f,0);this.pos=m?d:p},getExtremes:function(){var a=this.isLog;return{min:a?e(this.lin2log(this.min)):this.min,max:a?e(this.lin2log(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}}, -getThreshold:function(a){var b=this.isLog,g=b?this.lin2log(this.min):this.min,b=b?this.lin2log(this.max):this.max;null===a||-Infinity===a?a=g:Infinity===a?a=b:g>a?a=g:ba?"right":195a?"left":"center"},tickSize:function(a){var b=this.options,g=b[a+"Length"],d=H(b[a+"Width"],"tick"===a&&this.isXAxis?1:0);if(d&&g)return"inside"===b[a+"Position"]&&(g=-g),[g,d]},labelMetrics:function(){var a= -this.tickPositions&&this.tickPositions[0]||0;return this.chart.renderer.fontMetrics(this.options.labels.style&&this.options.labels.style.fontSize,this.ticks[a]&&this.ticks[a].label)},unsquish:function(){var a=this.options.labels,b=this.horiz,d=this.tickInterval,c=d,m=this.len/(((this.categories?1:0)+this.max-this.min)/d),l,p=a.rotation,k=this.labelMetrics(),v,B=Number.MAX_VALUE,t,r=function(a){a/=m||1;a=1=a)v=r(Math.abs(k.h/Math.sin(q*a))),b=v+Math.abs(a/360),b(d.step||0)&&!d.rotation&&(this.staggerLines||1)*this.len/c||!b&&(d.style&&parseInt(d.style.width,10)||m&&m-a.spacing[3]|| -.33*a.chartWidth)},renderUnsquish:function(){var a=this.chart,b=a.renderer,d=this.tickPositions,c=this.ticks,m=this.options.labels,l=m&&m.style||{},p=this.horiz,k=this.getSlotWidth(),v=Math.max(1,Math.round(k-2*(m.padding||5))),B={},e=this.labelMetrics(),t=m.style&&m.style.textOverflow,r,D,h=0,A;I(m.rotation)||(B.rotation=m.rotation||0);f(d,function(a){(a=c[a])&&a.label&&a.label.textPxLength>h&&(h=a.label.textPxLength)});this.maxLabelLength=h;if(this.autoRotation)h>v&&h>e.h?B.rotation=this.labelRotation: -this.labelRotation=0;else if(k&&(r=v,!t))for(D="clip",v=d.length;!p&&v--;)if(A=d[v],A=c[A].label)A.styles&&"ellipsis"===A.styles.textOverflow?A.css({textOverflow:"clip"}):A.textPxLength>k&&A.css({width:k+"px"}),A.getBBox().height>this.len/d.length-(e.h-e.f)&&(A.specificTextOverflow="ellipsis");B.rotation&&(r=h>.5*a.chartHeight?.33*a.chartHeight:a.chartHeight,t||(D="ellipsis"));if(this.labelAlign=m.align||this.autoLabelAlign(this.labelRotation))B.align=this.labelAlign;f(d,function(a){var b=(a=c[a])&& -a.label,g=l.width,d={};b&&(b.attr(B),r&&!g&&"nowrap"!==l.whiteSpace&&(r=this.min&&a<=this.max)g[a]||(g[a]=new M(this,a)),c&&g[a].isNew&&g[a].render(b,!0,.1),g[a].render(b)},render:function(){var b=this,d=b.chart,c=b.options,m=b.isLog,l=b.isLinked,v=b.tickPositions,e=b.axisTitle,t=b.ticks,r=b.minorTicks,D=b.alternateBands,h=c.stackLabels,G=c.alternateGridColor,I=b.tickmarkOffset,x=b.axisLine,q=b.showAxis,H=E(d.renderer.globalAnimation),u,n;b.labelEdge.length=0;b.overlap=!1;f([t,r,D],function(a){A(a,function(a){a.isActive= -!1})});if(b.hasData()||l)b.minorTickInterval&&!b.categories&&f(b.getMinorTickPositions(),function(a){b.renderMinorTick(a)}),v.length&&(f(v,function(a,d){b.renderTick(a,d)}),I&&(0===b.min||b.single)&&(t[-1]||(t[-1]=new M(b,-1,null,!0)),t[-1].render(-1))),G&&f(v,function(c,g){n=void 0!==v[g+1]?v[g+1]+I:b.max-I;0===g%2&&cu&&(!q||d<=y)&&void 0!==d&&c.push(d),d>y&&(b=!0),d=l;else u=this.lin2log(u),y=this.lin2log(y),a=q?this.getMinorTickInterval():e.tickInterval,a=h("auto"===a?null:a,this._minorAutoInterval,e.tickPixelInterval/(q?5:1)*(y-u)/((q?f/this.tickPositions.length:f)||1)),a=n(a,null,E(a)),c=F(this.getLinearTickPositions(a,u,y),this.log2lin),q||(this._minorAutoInterval=a/5);q||(this.tickInterval=a);return c};C.prototype.log2lin=function(a){return Math.log(a)/ -Math.LN10};C.prototype.lin2log=function(a){return Math.pow(10,a)}})(K);(function(a,C){var E=a.arrayMax,F=a.arrayMin,n=a.defined,h=a.destroyObjectProperties,e=a.each,u=a.erase,y=a.merge,q=a.pick;a.PlotLineOrBand=function(a,f){this.axis=a;f&&(this.options=f,this.id=f.id)};a.PlotLineOrBand.prototype={render:function(){var e=this,f=e.axis,c=f.horiz,k=e.options,r=k.label,l=e.label,d=k.to,b=k.from,v=k.value,p=n(b)&&n(d),h=n(v),t=e.svgElem,u=!t,A=[],H=k.color,m=q(k.zIndex,0),D=k.events,A={"class":"highcharts-plot-"+ -(p?"band ":"line ")+(k.className||"")},B={},M=f.chart.renderer,G=p?"bands":"lines";f.isLog&&(b=f.log2lin(b),d=f.log2lin(d),v=f.log2lin(v));h?(A.stroke=H,A["stroke-width"]=k.width,k.dashStyle&&(A.dashstyle=k.dashStyle)):p&&(H&&(A.fill=H),k.borderWidth&&(A.stroke=k.borderColor,A["stroke-width"]=k.borderWidth));B.zIndex=m;G+="-"+m;(H=f.plotLinesAndBandsGroups[G])||(f.plotLinesAndBandsGroups[G]=H=M.g("plot-"+G).attr(B).add());u&&(e.svgElem=t=M.path().attr(A).add(H));if(h)A=f.getPlotLinePath(v,t.strokeWidth()); -else if(p)A=f.getPlotBandPath(b,d,k);else return;u&&A&&A.length?(t.attr({d:A}),D&&a.objectEach(D,function(a,b){t.on(b,function(a){D[b].apply(e,[a])})})):t&&(A?(t.show(),t.animate({d:A})):(t.hide(),l&&(e.label=l=l.destroy())));r&&n(r.text)&&A&&A.length&&0this.max&&f>this.max;if(k&&c)for(a&&(b=k.toString()===c.toString(),d=0),a=0;aB-v?B:B-v);else if(e)b[a]=Math.max(l,m+v+g>c?m:m+v);else return!1},q=function(a,c,g,m){var l;mc-d?l=!1:b[a]=mc-g/2?c-g-2:m-g/2;return l},G=function(a){var b=r;r=m;m=b;p=a},g=function(){!1!==B.apply(0,r)?!1!==q.apply(0,m)||p||(G(!0),g()):p?b.x=b.y=0:(G(!0),g())};(c.inverted||1D&&(v=!1);a=(c.series&&c.series.yAxis&&c.series.yAxis.pos)+(c.plotY||0);a-=d.plotTop;l.push({target:c.isHeader?d.plotHeight+k:a,rank:c.isHeader?1:0,size:h.tt.getBBox().height+1,point:c,x:D,tt:m})}});this.cleanSplit(); -a.distribute(l,d.plotHeight+k);E(l,function(a){var b=a.point,c=b.series;a.tt.attr({visibility:void 0===a.pos?"hidden":"inherit",x:v||b.isHeader?a.x:b.plotX+d.plotLeft+y(p.distance,16),y:a.pos+d.plotTop,anchorX:b.isHeader?b.plotX+d.plotLeft:b.plotX+c.xAxis.pos,anchorY:b.isHeader?a.pos+d.plotTop-15:b.plotY+c.yAxis.pos})})},updatePosition:function(a){var c=this.chart,f=this.getLabel(),l=(this.options.positioner||this.getPosition).call(this,f.width,f.height,a),d=a.plotX+c.plotLeft;a=a.plotY+c.plotTop; -var b;this.outside&&(b=(this.options.borderWidth||0)+2*this.distance,this.renderer.setSize(f.width+b,f.height+b,!1),d+=c.pointer.chartPosition.left-l.x,a+=c.pointer.chartPosition.top-l.y);this.move(Math.round(l.x),Math.round(l.y||0),d,a)},getDateFormat:function(a,e,h,l){var d=this.chart.time,b=d.dateFormat("%m-%d %H:%M:%S.%L",e),c,p,k={millisecond:15,second:12,minute:9,hour:6,day:3},t="millisecond";for(p in f){if(a===f.week&&+d.dateFormat("%w",e)===h&&"00:00:00.000"===b.substr(6)){p="week";break}if(f[p]> -a){p=t;break}if(k[p]&&b.substr(k[p])!=="01-01 00:00:00.000".substr(k[p]))break;"week"!==p&&(t=p)}p&&(c=l[p]);return c},getXDateFormat:function(a,f,e){f=f.dateTimeLabelFormats;var c=e&&e.closestPointRange;return(c?this.getDateFormat(c,a.x,e.options.startOfWeek,f):f.day)||f.year},tooltipFooterHeaderFormatter:function(a,f){f=f?"footer":"header";var c=a.series,l=c.tooltipOptions,d=l.xDateFormat,b=c.xAxis,e=b&&"datetime"===b.options.type&&h(a.key),p=l[f+"Format"];e&&!d&&(d=this.getXDateFormat(a,l,b)); -e&&d&&E(a.point&&a.point.tooltipDateKeys||["key"],function(a){p=p.replace("{point."+a+"}","{point."+a+":"+d+"}")});return n(p,{point:a,series:c},this.chart.time)},bodyFormatter:function(a){return e(a,function(a){var c=a.series.tooltipOptions;return(c[(a.point.formatPrefix||"point")+"Formatter"]||a.point.tooltipFormatter).call(a.point,c[(a.point.formatPrefix||"point")+"Format"])})}}})(K);(function(a){var C=a.addEvent,E=a.attr,F=a.charts,n=a.color,h=a.css,e=a.defined,u=a.each,y=a.extend,q=a.find,x= -a.fireEvent,f=a.isNumber,c=a.isObject,k=a.offset,r=a.pick,l=a.splat,d=a.Tooltip;a.Pointer=function(a,d){this.init(a,d)};a.Pointer.prototype={init:function(a,c){this.options=c;this.chart=a;this.runChartClick=c.chart.events&&!!c.chart.events.click;this.pinchDown=[];this.lastValidTouch={};d&&(a.tooltip=new d(a,c.tooltip),this.followTouchMove=r(c.tooltip.followTouchMove,!0));this.setDOMEvents()},zoomOption:function(a){var b=this.chart,d=b.options.chart,c=d.zoomType||"",b=b.inverted;/touch/.test(a.type)&& -(c=r(d.pinchType,c));this.zoomX=a=/x/.test(c);this.zoomY=c=/y/.test(c);this.zoomHor=a&&!b||c&&b;this.zoomVert=c&&!b||a&&b;this.hasZoom=a||c},normalize:function(a,c){var b;b=a.touches?a.touches.length?a.touches.item(0):a.changedTouches[0]:a;c||(this.chartPosition=c=k(this.chart.container));return y(a,{chartX:Math.round(b.pageX-c.left),chartY:Math.round(b.pageY-c.top)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};u(this.chart.axes,function(c){b[c.isXAxis?"xAxis":"yAxis"].push({axis:c,value:c.toValue(a[c.horiz? -"chartX":"chartY"])})});return b},findNearestKDPoint:function(a,d,f){var b;u(a,function(a){var l=!(a.noSharedTooltip&&d)&&0>a.options.findNearestPointBy.indexOf("y");a=a.searchPoint(f,l);if((l=c(a,!0))&&!(l=!c(b,!0)))var l=b.distX-a.distX,e=b.dist-a.dist,p=(a.series.group&&a.series.group.zIndex)-(b.series.group&&b.series.group.zIndex),l=0<(0!==l&&d?l:0!==e?e:0!==p?p:b.series.index>a.series.index?-1:1);l&&(b=a)});return b},getPointFromEvent:function(a){a=a.target;for(var b;a&&!b;)b=a.point,a=a.parentNode; -return b},getChartCoordinatesFromPoint:function(a,c){var b=a.series,d=b.xAxis,b=b.yAxis,f=r(a.clientX,a.plotX),l=a.shapeArgs;if(d&&b)return c?{chartX:d.len+d.pos-f,chartY:b.len+b.pos-a.plotY}:{chartX:f+d.pos,chartY:a.plotY+b.pos};if(l&&l.x&&l.y)return{chartX:l.x,chartY:l.y}},getHoverData:function(b,d,f,l,e,k,h){var p,m=[],v=h&&h.isBoosting;l=!(!l||!b);h=d&&!d.stickyTracking?[d]:a.grep(f,function(a){return a.visible&&!(!e&&a.directTouch)&&r(a.options.enableMouseTracking,!0)&&a.stickyTracking});d=(p= -l?b:this.findNearestKDPoint(h,e,k))&&p.series;p&&(e&&!d.noSharedTooltip?(h=a.grep(f,function(a){return a.visible&&!(!e&&a.directTouch)&&r(a.options.enableMouseTracking,!0)&&!a.noSharedTooltip}),u(h,function(a){var b=q(a.points,function(a){return a.x===p.x&&!a.isNull});c(b)&&(v&&(b=a.getPoint(b)),m.push(b))})):m.push(p));return{hoverPoint:p,hoverSeries:d,hoverPoints:m}},runPointActions:function(b,d){var c=this.chart,f=c.tooltip&&c.tooltip.options.enabled?c.tooltip:void 0,l=f?f.shared:!1,e=d||c.hoverPoint, -k=e&&e.series||c.hoverSeries,k=this.getHoverData(e,k,c.series,!!d||k&&k.directTouch&&this.isDirectTouch,l,b,{isBoosting:c.isBoosting}),h,e=k.hoverPoint;h=k.hoverPoints;d=(k=k.hoverSeries)&&k.tooltipOptions.followPointer;l=l&&k&&!k.noSharedTooltip;if(e&&(e!==c.hoverPoint||f&&f.isHidden)){u(c.hoverPoints||[],function(b){-1===a.inArray(b,h)&&b.setState()});u(h||[],function(a){a.setState("hover")});if(c.hoverSeries!==k)k.onMouseOver();c.hoverPoint&&c.hoverPoint.firePointEvent("mouseOut");if(!e.series)return; -e.firePointEvent("mouseOver");c.hoverPoints=h;c.hoverPoint=e;f&&f.refresh(l?h:e,b)}else d&&f&&!f.isHidden&&(e=f.getAnchor([{}],b),f.updatePosition({plotX:e[0],plotY:e[1]}));this.unDocMouseMove||(this.unDocMouseMove=C(c.container.ownerDocument,"mousemove",function(b){var c=F[a.hoverChartIndex];if(c)c.pointer.onDocumentMouseMove(b)}));u(c.axes,function(c){var d=r(c.crosshair.snap,!0),m=d?a.find(h,function(a){return a.series[c.coll]===c}):void 0;m||!d?c.drawCrosshair(b,m):c.hideCrosshair()})},reset:function(a, -c){var b=this.chart,d=b.hoverSeries,f=b.hoverPoint,e=b.hoverPoints,k=b.tooltip,h=k&&k.shared?e:f;a&&h&&u(l(h),function(b){b.series.isCartesian&&void 0===b.plotX&&(a=!1)});if(a)k&&h&&(k.refresh(h),f&&(f.setState(f.state,!0),u(b.axes,function(a){a.crosshair&&a.drawCrosshair(null,f)})));else{if(f)f.onMouseOut();e&&u(e,function(a){a.setState()});if(d)d.onMouseOut();k&&k.hide(c);this.unDocMouseMove&&(this.unDocMouseMove=this.unDocMouseMove());u(b.axes,function(a){a.hideCrosshair()});this.hoverX=b.hoverPoints= -b.hoverPoint=null}},scaleGroups:function(a,c){var b=this.chart,d;u(b.series,function(f){d=a||f.getPlotBox();f.xAxis&&f.xAxis.zoomEnabled&&f.group&&(f.group.attr(d),f.markerGroup&&(f.markerGroup.attr(d),f.markerGroup.clip(c?b.clipRect:null)),f.dataLabelsGroup&&f.dataLabelsGroup.attr(d))});b.clipRect.attr(c||b.clipBox)},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=this.mouseDownY=a.chartY},drag:function(a){var b=this.chart, -c=b.options.chart,d=a.chartX,f=a.chartY,l=this.zoomHor,e=this.zoomVert,k=b.plotLeft,m=b.plotTop,h=b.plotWidth,B=b.plotHeight,r,G=this.selectionMarker,g=this.mouseDownX,w=this.mouseDownY,q=c.panKey&&a[c.panKey+"Key"];G&&G.touch||(dk+h&&(d=k+h),fm+B&&(f=m+B),this.hasDragged=Math.sqrt(Math.pow(g-d,2)+Math.pow(w-f,2)),10m.max&&(e=m.max-t,w=!0);w?(M-=.8*(M-k[d][0]),D||(g-=.8*(g-k[d][1])),h()):k[d]=[M,g];y||(c[d]=u-q,c[p]=t);c=y?1/A:A;f[p]= -t;f[d]=e;n[y?a?"scaleY":"scaleX":"scale"+b]=A;n["translate"+b]=c*q+(M-c*B)},pinch:function(a){var u=this,q=u.chart,x=u.pinchDown,f=a.touches,c=f.length,k=u.lastValidTouch,r=u.hasZoom,l=u.selectionMarker,d={},b=1===c&&(u.inClass(a.target,"highcharts-tracker")&&q.runTrackerClick||u.runChartClick),v={};1c-6&&e -q?this.maxItemWidth:a.itemWidth;f&&this.itemX-b+c>q&&(this.itemX=b,this.itemY+=k+this.lastLineHeight+l,this.lastLineHeight=0);this.lastItemY=k+this.itemY+l;this.lastLineHeight=Math.max(e,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];f?this.itemX+=c:(this.itemY+=k+e+l,this.lastLineHeight=e);this.offsetWidth=r||Math.max((f?this.itemX-b-(a.checkbox?0:h):c)+b,this.offsetWidth)},getAllItems:function(){var a=[];h(this.chart.series,function(c){var b=c&&c.options;c&&x(b.showInLegend,n(b.linkedTo)? -!1:void 0,!0)&&(a=a.concat(c.legendItems||("point"===b.legendType?c.data:c)))});e(this,"afterGetAllItems",{allItems:a});return a},getAlignment:function(){var a=this.options;return this.proximate?a.align.charAt(0)+"tv":a.floating?"":a.align.charAt(0)+a.verticalAlign.charAt(0)+a.layout.charAt(0)},adjustMargins:function(a,c){var b=this.chart,d=this.options,f=this.getAlignment();f&&h([/(lth|ct|rth)/,/(rtv|rm|rbv)/,/(rbh|cb|lbh)/,/(lbv|lm|ltv)/],function(e,l){e.test(f)&&!n(a[l])&&(b[y[l]]=Math.max(b[y[l]], -b.legend[(l+1)%2?"legendHeight":"legendWidth"]+[1,-1,-1,1][l]*d[l%2?"x":"y"]+x(d.margin,12)+c[l]+(0===l&&void 0!==b.options.title.margin?b.titleOffset+b.options.title.margin:0)))})},proximatePositions:function(){var c=this.chart,d=[],b="left"===this.options.align;h(this.allItems,function(f){var e,l;e=b;f.xAxis&&f.points&&(f.xAxis.options.reversed&&(e=!e),e=a.find(e?f.points:f.points.slice(0).reverse(),function(b){return a.isNumber(b.plotY)}),l=f.legendGroup.getBBox().height,d.push({target:f.visible? -e.plotY-.3*l:c.plotHeight,size:l,item:f}))},this);a.distribute(d,c.plotHeight);h(d,function(a){a.item._legendItemPos[1]=c.plotTop-c.spacing[0]+a.pos})},render:function(){var a=this.chart,d=a.renderer,b=this.group,f,e,k,r=this.box,n=this.options,A=this.padding;this.itemX=A;this.itemY=this.initialItemY;this.lastItemY=this.offsetWidth=0;b||(this.group=b=d.g("legend").attr({zIndex:7}).add(),this.contentGroup=d.g().attr({zIndex:1}).add(b),this.scrollGroup=d.g().add(this.contentGroup));this.renderTitle(); -f=this.getAllItems();c(f,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});n.reversed&&f.reverse();this.allItems=f;this.display=e=!!f.length;this.itemHeight=this.totalItemWidth=this.maxItemWidth=this.lastLineHeight=0;h(f,this.renderItem,this);h(f,this.layoutItem,this);f=(n.width||this.offsetWidth)+A;k=this.lastItemY+this.lastLineHeight+this.titleHeight;k=this.handleOverflow(k);k+=A;r||(this.box=r=d.rect().addClass("highcharts-legend-box").attr({r:n.borderRadius}).add(b), -r.isNew=!0);r.attr({stroke:n.borderColor,"stroke-width":n.borderWidth||0,fill:n.backgroundColor||"none"}).shadow(n.shadow);0b&&!1!==q.enabled?(this.clipHeight=r=Math.max(b-20-this.titleHeight-k,0),this.currentPage=x(this.currentPage,1),this.fullHeight=a,h(g,function(a,b){var c=a._legendItemPos[1],d=Math.round(a.legendItem.getBBox().height),f=n.length;if(!f||c-n[f-1]>r&&(G||c)!==n[f-1])n.push(G||c),f++;a.pageIx=f-1;G&&(g[b-1].pageIx=f-1);b===g.length-1&&c+d-n[f-1]>r&&(n.push(c),a.pageIx=f);c!==G&&(G=c)}),A||(A=c.clipRect= -f.clipRect(0,k,9999,0),c.contentGroup.clip(A)),w(r),B||(this.nav=B=f.g().attr({zIndex:1}).add(this.group),this.up=f.symbol("triangle",0,0,D,D).on("click",function(){c.scroll(-1,m)}).add(B),this.pager=f.text("",15,10).addClass("highcharts-legend-navigation").css(q.style).add(B),this.down=f.symbol("triangle-down",0,0,D,D).on("click",function(){c.scroll(1,m)}).add(B)),c.scroll(0),a=b):B&&(w(),this.nav=B.destroy(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0);return a},scroll:function(a,c){var b= -this.pages,d=b.length;a=this.currentPage+a;var e=this.clipHeight,l=this.options.navigation,k=this.pager,h=this.padding;a>d&&(a=d);0d&&(e=typeof f[0],"string"===e?c.name=f[0]:"number"===e&&(c.x=f[0]),b++);n=h.value;)h=c[++e];this.nonZonedColor||(this.nonZonedColor=this.color);this.color=h&&h.color&&!this.options.color?h.color:this.nonZonedColor;return h},destroy:function(){var a=this.series.chart,c=a.hoverPoints,e;a.pointCount--;c&&(this.setState(),n(c,this),c.length||(a.hoverPoints=null));if(this===a.hoverPoint)this.onMouseOut();if(this.graphic||this.dataLabel)x(this),this.destroyElements();this.legendItem&& -a.legend.destroyItem(this);for(e in this)this[e]=null},destroyElements:function(){for(var a=["graphic","dataLabel","dataLabelUpper","connector","shadowGroup"],c,e=6;e--;)c=a[e],this[c]&&(this[c]=this[c].destroy())},getLabelConfig:function(){return{x:this.category,y:this.y,color:this.color,colorIndex:this.colorIndex,key:this.name||this.category,series:this.series,point:this,percentage:this.percentage,total:this.total||this.stackTotal}},tooltipFormatter:function(a){var c=this.series,f=c.tooltipOptions, -h=q(f.valueDecimals,""),l=f.valuePrefix||"",d=f.valueSuffix||"";E(c.pointArrayMap||["y"],function(b){b="{point."+b;if(l||d)a=a.replace(RegExp(b+"}","g"),l+b+"}"+d);a=a.replace(RegExp(b+"}","g"),b+":,."+h+"f}")});return e(a,{point:this,series:this.series},c.chart.time)},firePointEvent:function(a,c,e){var f=this,l=this.series.options;(l.point.events[a]||f.options&&f.options.events&&f.options.events[a])&&this.importEvents();"click"===a&&l.allowPointSelect&&(e=function(a){f.select&&f.select(null,a.ctrlKey|| -a.metaKey||a.shiftKey)});h(this,a,c,e)},visible:!0}})(K);(function(a){var C=a.addEvent,E=a.animObject,F=a.arrayMax,n=a.arrayMin,h=a.correctFloat,e=a.defaultOptions,u=a.defaultPlotOptions,y=a.defined,q=a.each,x=a.erase,f=a.extend,c=a.fireEvent,k=a.grep,r=a.isArray,l=a.isNumber,d=a.isString,b=a.merge,v=a.objectEach,p=a.pick,I=a.removeEvent,t=a.splat,L=a.SVGElement,A=a.syncTimeout,H=a.win;a.Series=a.seriesType("line",null,{lineWidth:2,allowPointSelect:!1,showCheckbox:!1,animation:{duration:1E3},events:{}, -marker:{lineWidth:0,lineColor:"#ffffff",enabledThreshold:2,radius:4,states:{normal:{animation:!0},hover:{animation:{duration:50},enabled:!0,radiusPlus:2,lineWidthPlus:1},select:{fillColor:"#cccccc",lineColor:"#000000",lineWidth:2}}},point:{events:{}},dataLabels:{align:"center",formatter:function(){return null===this.y?"":a.numberFormat(this.y,-1)},style:{fontSize:"11px",fontWeight:"bold",color:"contrast",textOutline:"1px contrast"},verticalAlign:"bottom",x:0,y:0,padding:5},cropThreshold:300,pointRange:0, -softThreshold:!0,states:{normal:{animation:!0},hover:{animation:{duration:50},lineWidthPlus:1,marker:{},halo:{size:10,opacity:.25}},select:{marker:{}}},stickyTracking:!0,turboThreshold:1E3,findNearestPointBy:"x"},{isCartesian:!0,pointClass:a.Point,sorted:!0,requireSorting:!0,directTouch:!1,axisTypes:["xAxis","yAxis"],colorCounter:0,parallelArrays:["x","y"],coll:"series",init:function(a,b){var d=this,m,e=a.series,g;d.chart=a;d.options=b=d.setOptions(b);d.linkedSeries=[];d.bindAxes();f(d,{name:b.name, -state:"",visible:!1!==b.visible,selected:!0===b.selected});m=b.events;v(m,function(a,b){C(d,b,a)});if(m&&m.click||b.point&&b.point.events&&b.point.events.click||b.allowPointSelect)a.runTrackerClick=!0;d.getColor();d.getSymbol();q(d.parallelArrays,function(a){d[a+"Data"]=[]});d.setData(b.data,!1);d.isCartesian&&(a.hasCartesianSeries=!0);e.length&&(g=e[e.length-1]);d._i=p(g&&g._i,-1)+1;a.orderSeries(this.insert(e));c(this,"afterInit")},insert:function(a){var b=this.options.index,c;if(l(b)){for(c=a.length;c--;)if(b>= -p(a[c].options.index,a[c]._i)){a.splice(c+1,0,this);break}-1===c&&a.unshift(this);c+=1}else a.push(this);return p(c,a.length-1)},bindAxes:function(){var b=this,c=b.options,d=b.chart,f;q(b.axisTypes||[],function(m){q(d[m],function(a){f=a.options;if(c[m]===f.index||void 0!==c[m]&&c[m]===f.id||void 0===c[m]&&0===f.index)b.insert(a.series),b[m]=a,a.isDirty=!0});b[m]||b.optionalAxis===m||a.error(18,!0)})},updateParallelArrays:function(a,b){var c=a.series,d=arguments,f=l(b)?function(d){var g="y"===d&&c.toYData? -c.toYData(a):a[d];c[d+"Data"][b]=g}:function(a){Array.prototype[b].apply(c[a+"Data"],Array.prototype.slice.call(d,2))};q(c.parallelArrays,f)},autoIncrement:function(){var a=this.options,b=this.xIncrement,c,d=a.pointIntervalUnit,f=this.chart.time,b=p(b,a.pointStart,0);this.pointInterval=c=p(this.pointInterval,a.pointInterval,1);d&&(a=new f.Date(b),"day"===d?f.set("Date",a,f.get("Date",a)+c):"month"===d?f.set("Month",a,f.get("Month",a)+c):"year"===d&&f.set("FullYear",a,f.get("FullYear",a)+c),c=a.getTime()- -b);this.xIncrement=b+c;return b},setOptions:function(a){var d=this.chart,f=d.options,m=f.plotOptions,h=(d.userOptions||{}).plotOptions||{},g=m[this.type];this.userOptions=a;d=b(g,m.series,a);this.tooltipOptions=b(e.tooltip,e.plotOptions.series&&e.plotOptions.series.tooltip,e.plotOptions[this.type].tooltip,f.tooltip.userOptions,m.series&&m.series.tooltip,m[this.type].tooltip,a.tooltip);this.stickyTracking=p(a.stickyTracking,h[this.type]&&h[this.type].stickyTracking,h.series&&h.series.stickyTracking, -this.tooltipOptions.shared&&!this.noSharedTooltip?!0:d.stickyTracking);null===g.marker&&delete d.marker;this.zoneAxis=d.zoneAxis;a=this.zones=(d.zones||[]).slice();!d.negativeColor&&!d.negativeFillColor||d.zones||a.push({value:d[this.zoneAxis+"Threshold"]||d.threshold||0,className:"highcharts-negative",color:d.negativeColor,fillColor:d.negativeFillColor});a.length&&y(a[a.length-1].value)&&a.push({color:this.color,fillColor:this.fillColor});c(this,"afterSetOptions",{options:d});return d},getName:function(){return this.name|| -"Series "+(this.index+1)},getCyclic:function(a,b,c){var d,f=this.chart,g=this.userOptions,m=a+"Index",e=a+"Counter",h=c?c.length:p(f.options.chart[a+"Count"],f[a+"Count"]);b||(d=p(g[m],g["_"+m]),y(d)||(f.series.length||(f[e]=0),g["_"+m]=d=f[e]%h,f[e]+=1),c&&(b=c[d]));void 0!==d&&(this[m]=d);this[a]=b},getColor:function(){this.options.colorByPoint?this.options.color=null:this.getCyclic("color",this.options.color||u[this.type].color,this.chart.options.colors)},getSymbol:function(){this.getCyclic("symbol", -this.options.marker.symbol,this.chart.options.symbols)},drawLegendSymbol:a.LegendSymbolMixin.drawLineMarker,updateData:function(b){var c=this.options,d=this.points,f=[],m,g,e,h=this.requireSorting;q(b,function(b){var g;g=a.defined(b)&&this.pointClass.prototype.optionsToObject.call({series:this},b).x;l(g)&&(g=a.inArray(g,this.xData,e),-1===g?f.push(b):b!==c.data[g]?(d[g].update(b,!1,null,!1),d[g].touched=!0,h&&(e=g)):d[g]&&(d[g].touched=!0),m=!0)},this);if(m)for(b=d.length;b--;)g=d[b],g.touched||g.remove(!1), -g.touched=!1;else if(b.length===d.length)q(b,function(a,b){d[b].update&&a!==c.data[b]&&d[b].update(a,!1,null,!1)});else return!1;q(f,function(a){this.addPoint(a,!1)},this);return!0},setData:function(b,c,f,e){var m=this,g=m.points,h=g&&g.length||0,k,B=m.options,D=m.chart,t=null,A=m.xAxis,n=B.turboThreshold,v=this.xData,u=this.yData,y=(k=m.pointArrayMap)&&k.length,H;b=b||[];k=b.length;c=p(c,!0);!1!==e&&k&&h&&!m.cropped&&!m.hasGroupedData&&m.visible&&(H=this.updateData(b));if(!H){m.xIncrement=null;m.colorCounter= -0;q(this.parallelArrays,function(a){m[a+"Data"].length=0});if(n&&k>n){for(f=0;null===t&&fl||this.forceCrop))if(c[f-1]q)c=[],d=[];else if(c[0]q)m=this.cropData(this.xData,this.yData,n,q),c=m.xData,d=m.yData,m=m.start,g=!0;for(l=c.length||1;--l;)f=t?k(c[l])-k(c[l-1]):c[l]-c[l-1],0f&&A&&(a.error(15),A=!1);this.cropped=g;this.cropStart=m;this.processedXData=c;this.processedYData=d;this.closestPointRange=e},cropData:function(a,b,c,d,f){var g=a.length,m=0,e=g,h;f= -p(f,this.cropShoulder,1);for(h=0;h=c){m=Math.max(0,h-f);break}for(c=h;cd){e=c+f;break}return{xData:a.slice(m,e),yData:b.slice(m,e),start:m,end:e}},generatePoints:function(){var a=this.options,b=a.data,c=this.data,d,f=this.processedXData,g=this.processedYData,e=this.pointClass,h=f.length,l=this.cropStart||0,k,p=this.hasGroupedData,a=a.keys,r,n=[],A;c||p||(c=[],c.length=b.length,c=this.data=c);a&&p&&(this.options.keys=!1);for(A=0;A=m&&(c[A-p]||k)<=e,h&&k)if(h=t.length)for(;h--;)"number"===typeof t[h]&&(f[g++]=t[h]);else f[g++]=t;this.dataMin=n(f);this.dataMax=F(f)},translate:function(){this.processedXData||this.processData();this.generatePoints();var a=this.options,b=a.stacking,d=this.xAxis,f=d.categories,e=this.yAxis,g=this.points,k=g.length,r=!!this.modifyValue,t=a.pointPlacement, -A="between"===t||l(t),n=a.threshold,q=a.startFromThreshold?n:0,v,u,H,x,I=Number.MAX_VALUE;"between"===t&&(t=.5);l(t)&&(t*=p(a.pointRange||d.pointRange));for(a=0;a=E&&(L.isNull=!0);L.plotX=v=h(Math.min(Math.max(-1E5,d.translate(C,0,0,0,1,t,"flags"===this.type)),1E5));b&&this.visible&&!L.isNull&&F&&F[C]&&(x=this.getStackIndicator(x,C,this.index),K=F[C],E=K.points[x.key], -u=E[0],E=E[1],u===q&&x.key===F[C].base&&(u=p(l(n)&&n,e.min)),e.positiveValuesOnly&&0>=u&&(u=null),L.total=L.stackTotal=K.total,L.percentage=K.total&&L.y/K.total*100,L.stackY=E,K.setOffset(this.pointXOffset||0,this.barW||0));L.yBottom=y(u)?Math.min(Math.max(-1E5,e.translate(u,0,1,0,1)),1E5):null;r&&(E=this.modifyValue(E,L));L.plotY=u="number"===typeof E&&Infinity!==E?Math.min(Math.max(-1E5,e.translate(E,0,1,0,1)),1E5):void 0;L.isInside=void 0!==u&&0<=u&&u<=e.len&&0<=v&&v<=d.len;L.clientX=A?h(d.translate(C, -0,0,0,1,t)):v;L.negative=L.y<(n||0);L.category=f&&void 0!==f[L.x]?f[L.x]:L.x;L.isNull||(void 0!==H&&(I=Math.min(I,Math.abs(v-H))),H=v);L.zone=this.zones.length&&L.getZone()}this.closestPointRangePx=I;c(this,"afterTranslate")},getValidPoints:function(a,b){var c=this.chart;return k(a||this.points||[],function(a){return b&&!c.isInsidePlot(a.plotX,a.plotY,c.inverted)?!1:!a.isNull})},setClip:function(a){var b=this.chart,c=this.options,d=b.renderer,f=b.inverted,g=this.clipBox,e=g||b.clipBox,m=this.sharedClipKey|| -["_sharedClip",a&&a.duration,a&&a.easing,e.height,c.xAxis,c.yAxis].join(),h=b[m],l=b[m+"m"];h||(a&&(e.width=0,f&&(e.x=b.plotSizeX),b[m+"m"]=l=d.clipRect(f?b.plotSizeX+99:-99,f?-b.plotLeft:-b.plotTop,99,f?b.chartWidth:b.chartHeight)),b[m]=h=d.clipRect(e),h.count={length:0});a&&!h.count[this.index]&&(h.count[this.index]=!0,h.count.length+=1);!1!==c.clip&&(this.group.clip(a||g?h:b.clipRect),this.markerGroup.clip(l),this.sharedClipKey=m);a||(h.count[this.index]&&(delete h.count[this.index],--h.count.length), -0===h.count.length&&m&&b[m]&&(g||(b[m]=b[m].destroy()),b[m+"m"]&&(b[m+"m"]=b[m+"m"].destroy())))},animate:function(a){var b=this.chart,c=E(this.options.animation),d;a?this.setClip(c):(d=this.sharedClipKey,(a=b[d])&&a.animate({width:b.plotSizeX,x:0},c),b[d+"m"]&&b[d+"m"].animate({width:b.plotSizeX+99,x:0},c),this.animate=null)},afterAnimate:function(){this.setClip();c(this,"afterAnimate");this.finishedAnimating=!0},drawPoints:function(){var a=this.points,b=this.chart,c,d,f,g,e=this.options.marker, -h,l,k,r=this[this.specialGroup]||this.markerGroup,t,A=p(e.enabled,this.xAxis.isRadial?!0:null,this.closestPointRangePx>=e.enabledThreshold*e.radius);if(!1!==e.enabled||this._hasPointMarkers)for(c=0;cf&&b.shadow));e&&(e.startX=c.xMap,e.isArea=c.isArea)})},getZonesGraphs:function(a){q(this.zones,function(b,c){a.push(["zone-graph-"+c,"highcharts-graph highcharts-zone-graph-"+c+" "+(b.className||""),b.color||this.color,b.dashStyle||this.options.dashStyle])},this);return a},applyZones:function(){var a=this, -b=this.chart,c=b.renderer,d=this.zones,f,g,e=this.clips||[],h,l=this.graph,k=this.area,r=Math.max(b.chartWidth,b.chartHeight),t=this[(this.zoneAxis||"y")+"Axis"],A,n,v=b.inverted,u,H,y,x,I=!1;d.length&&(l||k)&&t&&void 0!==t.min&&(n=t.reversed,u=t.horiz,l&&!this.showLine&&l.hide(),k&&k.hide(),A=t.getExtremes(),q(d,function(d,m){f=n?u?b.plotWidth:0:u?0:t.toPixels(A.min);f=Math.min(Math.max(p(g,f),0),r);g=Math.min(Math.max(Math.round(t.toPixels(p(d.value,A.max),!0)),0),r);I&&(f=g=t.toPixels(A.max)); -H=Math.abs(f-g);y=Math.min(f,g);x=Math.max(f,g);t.isXAxis?(h={x:v?x:y,y:0,width:H,height:r},u||(h.x=b.plotHeight-h.x)):(h={x:0,y:v?x:y,width:r,height:H},u&&(h.y=b.plotWidth-h.y));v&&c.isVML&&(h=t.isXAxis?{x:0,y:n?y:x,height:h.width,width:b.chartWidth}:{x:h.y-b.plotLeft-b.spacingBox.x,y:0,width:h.height,height:b.chartHeight});e[m]?e[m].animate(h):(e[m]=c.clipRect(h),l&&a["zone-graph-"+m].clip(e[m]),k&&a["zone-area-"+m].clip(e[m]));I=d.value>A.max;a.resetZones&&0===g&&(g=void 0)}),this.clips=e)},invertGroups:function(a){function b(){q(["group", -"markerGroup"],function(b){c[b]&&(d.renderer.isVML&&c[b].attr({width:c.yAxis.len,height:c.xAxis.len}),c[b].width=c.yAxis.len,c[b].height=c.xAxis.len,c[b].invert(a))})}var c=this,d=c.chart,f;c.xAxis&&(f=C(d,"resize",b),C(c,"destroy",f),b(a),c.invertGroups=b)},plotGroup:function(a,b,c,d,f){var g=this[a],e=!g;e&&(this[a]=g=this.chart.renderer.g().attr({zIndex:d||.1}).add(f));g.addClass("highcharts-"+b+" highcharts-series-"+this.index+" highcharts-"+this.type+"-series "+(y(this.colorIndex)?"highcharts-color-"+ -this.colorIndex+" ":"")+(this.options.className||"")+(g.hasClass("highcharts-tracker")?" highcharts-tracker":""),!0);g.attr({visibility:c})[e?"attr":"animate"](this.getPlotBox());return g},getPlotBox:function(){var a=this.chart,b=this.xAxis,c=this.yAxis;a.inverted&&(b=c,c=this.xAxis);return{translateX:b?b.left:a.plotLeft,translateY:c?c.top:a.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this,b=a.chart,d,f=a.options,e=!!a.animate&&b.renderer.isSVG&&E(f.animation).duration,g=a.visible?"inherit": -"hidden",h=f.zIndex,l=a.hasRendered,k=b.seriesGroup,r=b.inverted;d=a.plotGroup("group","series",g,h,k);a.markerGroup=a.plotGroup("markerGroup","markers",g,h,k);e&&a.animate(!0);d.inverted=a.isCartesian?r:!1;a.drawGraph&&(a.drawGraph(),a.applyZones());a.drawDataLabels&&a.drawDataLabels();a.visible&&a.drawPoints();a.drawTracker&&!1!==a.options.enableMouseTracking&&a.drawTracker();a.invertGroups(r);!1===f.clip||a.sharedClipKey||l||d.clip(b.clipRect);e&&a.animate();l||(a.animationTimeout=A(function(){a.afterAnimate()}, -e));a.isDirty=!1;a.hasRendered=!0;c(a,"afterRender")},redraw:function(){var a=this.chart,b=this.isDirty||this.isDirtyData,c=this.group,d=this.xAxis,f=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:p(d&&d.left,a.plotLeft),translateY:p(f&&f.top,a.plotTop)}));this.translate();this.render();b&&delete this.kdTree},kdAxisArray:["clientX","plotY"],searchPoint:function(a,b){var c=this.xAxis,d=this.yAxis,f=this.chart.inverted;return this.searchKDTree({clientX:f? -c.len-a.chartY+c.pos:a.chartX-c.pos,plotY:f?d.len-a.chartX+d.pos:a.chartY-d.pos},b)},buildKDTree:function(){function a(c,d,f){var g,e;if(e=c&&c.length)return g=b.kdAxisArray[d%f],c.sort(function(a,b){return a[g]-b[g]}),e=Math.floor(e/2),{point:c[e],left:a(c.slice(0,e),d+1,f),right:a(c.slice(e+1),d+1,f)}}this.buildingKdTree=!0;var b=this,c=-1k?"left":"right";p=0>k?"right":"left";b[r]&&(r=c(a,b[r],h+1,m),t=r[e]n;)r--;this.updateParallelArrays(k,"splice",r,0,0);this.updateParallelArrays(k,r);g&&k.name&&(g[n]=k.name);l.splice(r,0,a);t&&(this.data.splice(r,0,null),this.processData());"point"===e.legendType&&this.generatePoints();d&&(h[0]&&h[0].remove?h[0].remove(!1):(h.shift(),this.updateParallelArrays(k,"shift"),l.shift()));this.isDirtyData=this.isDirty=!0;c&&m.redraw(f)},removePoint:function(a,c,d){var f=this,e=f.data,h=e[a],m=f.points, -g=f.chart,l=function(){m&&m.length===e.length&&m.splice(a,1);e.splice(a,1);f.options.data.splice(a,1);f.updateParallelArrays(h||{series:f},"splice",a,1);h&&h.destroy();f.isDirty=!0;f.isDirtyData=!0;c&&g.redraw()};t(d,g);c=b(c,!0);h?h.firePointEvent("remove",null,l):l()},remove:function(a,c,d){function f(){e.destroy();h.isDirtyLegend=h.isDirtyBox=!0;h.linkSeries();b(a,!0)&&h.redraw(c)}var e=this,h=e.chart;!1!==d?x(e,"remove",null,f):f()},update:function(c,d){var e=this,h=e.chart,k=e.userOptions,t= -e.oldType||e.type,p=c.type||k.type||h.options.chart.type,g=I[t].prototype,r,n=["group","markerGroup","dataLabelsGroup"],v=["navigatorSeries","baseSeries"],A=e.finishedAnimating&&{animation:!1},y=["data","name","turboThreshold"],H=a.keys(c),z=0a&&q>e?(q=Math.max(a,e),f=2*e-q):qn&&f>e?(f=Math.max(n,e),q=2*e-f):f=Math.abs(c)&&.5a.closestPointRange*a.xAxis.transA,h=a.borderWidth=u(e.borderWidth,h?0:1),l=a.yAxis,d=e.threshold,b=a.translatedThreshold=l.getThreshold(d),n=u(e.minPointLength,5),p=a.getColumnMetrics(),q=p.width,t=a.barW=Math.max(q,1+2*h),x=a.pointXOffset=p.offset;c.inverted&&(b-=.5);e.pointPadding&&(t=Math.ceil(t));y.prototype.translate.apply(a);F(a.points,function(f){var e=u(f.yBottom,b),h=999+Math.abs(e),h=Math.min(Math.max(-h,f.plotY),l.len+h), -k=f.plotX+x,p=t,r=Math.min(h,e),v,g=Math.max(h,e)-r;n&&Math.abs(g)n?e-n:b-(v?n:0));f.barX=k;f.pointWidth=q;f.tooltipPos=c.inverted?[l.len+l.pos-c.plotLeft-h,a.xAxis.len-k-p/2,g]:[k+p/2,h+l.pos-c.plotTop,g];f.shapeType="rect";f.shapeArgs=a.crispCol.apply(a,f.isNull?[k,b,p,0]:[k,r,p,g])})},getSymbol:a.noop,drawLegendSymbol:a.LegendSymbolMixin.drawRectangle,drawGraph:function(){this.group[this.dense? -"addClass":"removeClass"]("highcharts-dense-data")},pointAttribs:function(a,c){var f=this.options,h,l=this.pointAttrToOptions||{};h=l.stroke||"borderColor";var d=l["stroke-width"]||"borderWidth",b=a&&a.color||this.color,n=a&&a[h]||f[h]||this.color||b,p=a&&a[d]||f[d]||this[d]||0,l=f.dashStyle;a&&this.zones.length&&(b=a.getZone(),b=a.options.color||b&&b.color||this.color);c&&(a=e(f.states[c],a.options.states&&a.options.states[c]||{}),c=a.brightness,b=a.color||void 0!==c&&E(b).brighten(a.brightness).get()|| -b,n=a[h]||n,p=a[d]||p,l=a.dashStyle||l);h={fill:b,stroke:n,"stroke-width":p};l&&(h.dashstyle=l);return h},drawPoints:function(){var a=this,c=this.chart,k=a.options,r=c.renderer,l=k.animationLimit||250,d;F(a.points,function(b){var f=b.graphic,p=f&&c.pointCountf;++f)c=q[f],a=2>f||2===f&&/%$/.test(c),q[f]=n(c,[y,e,x,q[2]][f])+(a?u:0);q[3]>q[2]&&(q[3]=q[2]);return q},getStartAndEndRadians:function(a,e){a=E(a)?a:0;e=E(e)&&e>a&&360>e-a?e:a+360;return{start:C*(a+-90),end:C*(e+-90)}}}})(K);(function(a){var C=a.addEvent,E=a.CenteredSeriesMixin,F=a.defined,n=a.each,h=a.extend,e=E.getStartAndEndRadians,u=a.inArray,y=a.noop,q=a.pick,x=a.Point, -f=a.Series,c=a.seriesType,k=a.setAnimation;c("pie","line",{center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{allowOverlap:!0,distance:30,enabled:!0,formatter:function(){return this.point.isNull?void 0:this.point.name},x:0},ignoreHiddenPoint:!0,legendType:"point",marker:null,size:null,showInLegend:!1,slicedOffset:10,stickyTracking:!1,tooltip:{followPointer:!0},borderColor:"#ffffff",borderWidth:1,states:{hover:{brightness:.1}}},{isCartesian:!1,requireSorting:!1,directTouch:!0,noSharedTooltip:!0, -trackerGroups:["group","dataLabelsGroup"],axisTypes:[],pointAttribs:a.seriesTypes.column.prototype.pointAttribs,animate:function(a){var c=this,d=c.points,b=c.startAngleRad;a||(n(d,function(a){var d=a.graphic,f=a.shapeArgs;d&&(d.attr({r:a.startR||c.center[3]/2,start:b,end:b}),d.animate({r:f.r,start:f.start,end:f.end},c.options.animation))}),c.animate=null)},updateTotals:function(){var a,c=0,d=this.points,b=d.length,f,e=this.options.ignoreHiddenPoint;for(a=0;a1.5*Math.PI? -t-=2*Math.PI:t<-Math.PI/2&&(t+=2*Math.PI);G.slicedTranslation={translateX:Math.round(Math.cos(t)*b),translateY:Math.round(Math.sin(t)*b)};k=Math.cos(t)*a[2]/2;m=Math.sin(t)*a[2]/2;G.tooltipPos=[a[0]+.7*k,a[1]+.7*m];G.half=t<-Math.PI/2||t>Math.PI/2?1:0;G.angle=t;h=Math.min(f,G.labelDistance/5);G.labelPos=[a[0]+k+Math.cos(t)*G.labelDistance,a[1]+m+Math.sin(t)*G.labelDistance,a[0]+k+Math.cos(t)*h,a[1]+m+Math.sin(t)*h,a[0]+k,a[1]+m,0>G.labelDistance?"center":G.half?"right":"left",t]}},drawGraph:null, -drawPoints:function(){var a=this,c=a.chart.renderer,d,b,f,e,k=a.options.shadow;k&&!a.shadowGroup&&(a.shadowGroup=c.g("shadow").add(a.group));n(a.points,function(l){b=l.graphic;if(l.isNull)b&&(l.graphic=b.destroy());else{e=l.shapeArgs;d=l.getTranslate();var t=l.shadowGroup;k&&!t&&(t=l.shadowGroup=c.g("shadow").add(a.shadowGroup));t&&t.attr(d);f=a.pointAttribs(l,l.selected&&"select");b?b.setRadialReference(a.center).attr(f).animate(h(e,d)):(l.graphic=b=c[l.shapeType](e).setRadialReference(a.center).attr(d).add(a.group), -b.attr(f).attr({"stroke-linejoin":"round"}).shadow(k,t));b.attr({visibility:l.visible?"inherit":"hidden"});b.addClass(l.getClassName())}})},searchPoint:y,sortByAngle:function(a,c){a.sort(function(a,b){return void 0!==a.angle&&(b.angle-a.angle)*c})},drawLegendSymbol:a.LegendSymbolMixin.drawRectangle,getCenter:E.getCenter,getSymbol:y},{init:function(){x.prototype.init.apply(this,arguments);var a=this,c;a.name=q(a.name,"Slice");c=function(c){a.slice("select"===c.type)};C(a,"select",c);C(a,"unselect", -c);return a},isValid:function(){return a.isNumber(this.y,!0)&&0<=this.y},setVisible:function(a,c){var d=this,b=d.series,f=b.chart,e=b.options.ignoreHiddenPoint;c=q(c,e);a!==d.visible&&(d.visible=d.options.visible=a=void 0===a?!d.visible:a,b.options.data[u(d,b.data)]=d.options,n(["graphic","dataLabel","connector","shadowGroup"],function(b){if(d[b])d[b][a?"show":"hide"](!0)}),d.legendItem&&f.legend.colorizeItem(d,a),a||"hover"!==d.state||d.setState(""),e&&(b.isDirty=!0),c&&f.redraw())},slice:function(a, -c,d){var b=this.series;k(d,b.chart);q(c,!0);this.sliced=this.options.sliced=F(a)?a:!this.sliced;b.options.data[u(this,b.data)]=this.options;this.graphic.animate(this.getTranslate());this.shadowGroup&&this.shadowGroup.animate(this.getTranslate())},getTranslate:function(){return this.sliced?this.slicedTranslation:{translateX:0,translateY:0}},haloPath:function(a){var c=this.shapeArgs;return this.sliced||!this.visible?[]:this.series.chart.renderer.symbols.arc(c.x,c.y,c.r+a,c.r+a,{innerR:this.shapeArgs.r- -1,start:c.start,end:c.end})}})})(K);(function(a){var C=a.addEvent,E=a.arrayMax,F=a.defined,n=a.each,h=a.extend,e=a.format,u=a.map,y=a.merge,q=a.noop,x=a.pick,f=a.relativeLength,c=a.Series,k=a.seriesTypes,r=a.some,l=a.stableSort;a.distribute=function(c,b,f){function d(a,b){return a.target-b.target}var e,h=!0,k=c,q=[],v;v=0;var m=k.reducedLen||b;for(e=c.length;e--;)v+=c[e].size;if(v>m){l(c,function(a,b){return(b.rank||0)-(a.rank||0)});for(v=e=0;v<=m;)v+=c[e].size,e++;q=c.splice(e-1,c.length)}l(c,d); -for(c=u(c,function(a){return{size:a.size,targets:[a.target],align:x(a.align,.5)}});h;){for(e=c.length;e--;)h=c[e],v=(Math.min.apply(0,h.targets)+Math.max.apply(0,h.targets))/2,h.pos=Math.min(Math.max(0,v-h.size*h.align),b-h.size);e=c.length;for(h=!1;e--;)0c[e].pos&&(c[e-1].size+=c[e].size,c[e-1].targets=c[e-1].targets.concat(c[e].targets),c[e-1].align=.5,c[e-1].pos+c[e-1].size>b&&(c[e-1].pos=b-c[e-1].size),c.splice(e,1),h=!0)}k.push.apply(k,q);e=0;r(c,function(c){var d= -0;if(r(c.targets,function(){k[e].pos=c.pos+d;if(Math.abs(k[e].pos-k[e].target)>f)return n(k.slice(0,e+1),function(a){delete a.pos}),k.reducedLen=(k.reducedLen||b)-.1*b,k.reducedLen>.1*b&&a.distribute(k,b,f),!0;d+=k[e].size;e++}))return!0});l(k,d)};c.prototype.drawDataLabels=function(){function c(a,b){var c=b.filter;return c?(b=c.operator,a=a[c.property],c=c.value,"\x3e"===b&&a>c||"\x3c"===b&&a=c||"\x3c\x3d"===b&&a<=c||"\x3d\x3d"===b&&a==c||"\x3d\x3d\x3d"===b&&a===c?!0:!1):!0} -var b=this,f=b.chart,h=b.options,l=h.dataLabels,k=b.points,r,q,u=b.hasRendered||0,m,D,B=x(l.defer,!!h.animation),E=f.renderer;if(l.enabled||b._hasPointLabels)b.dlProcessOptions&&b.dlProcessOptions(l),D=b.plotGroup("dataLabelsGroup","data-labels",B&&!u?"hidden":"visible",l.zIndex||6),B&&(D.attr({opacity:+u}),u||C(b,"afterAnimate",function(){b.visible&&D.show(!0);D[h.animation?"animate":"attr"]({opacity:1},{duration:200})})),q=l,n(k,function(d){var g,k=d.dataLabel,t,p,n=d.connector,u=!k,v;r=d.dlOptions|| -d.options&&d.options.dataLabels;(g=x(r&&r.enabled,q.enabled)&&!d.isNull)&&(g=!0===c(d,r||l));g&&(l=y(q,r),t=d.getLabelConfig(),v=l[d.formatPrefix+"Format"]||l.format,m=F(v)?e(v,t,f.time):(l[d.formatPrefix+"Formatter"]||l.formatter).call(t,l),v=l.style,t=l.rotation,v.color=x(l.color,v.color,b.color,"#000000"),"contrast"===v.color&&(d.contrastColor=E.getContrast(d.color||b.color),v.color=l.inside||0>x(d.labelDistance,l.distance)||h.stacking?d.contrastColor:"#000000"),h.cursor&&(v.cursor=h.cursor),p= -{fill:l.backgroundColor,stroke:l.borderColor,"stroke-width":l.borderWidth,r:l.borderRadius||0,rotation:t,padding:l.padding,zIndex:1},a.objectEach(p,function(a,b){void 0===a&&delete p[b]}));!k||g&&F(m)?g&&F(m)&&(k?p.text=m:(k=d.dataLabel=t?E.text(m,0,-9999).addClass("highcharts-data-label"):E.label(m,0,-9999,l.shape,null,null,l.useHTML,null,"data-label"),k.addClass(" highcharts-data-label-color-"+d.colorIndex+" "+(l.className||"")+(l.useHTML?" highcharts-tracker":""))),k.attr(p),k.css(v).shadow(l.shadow), -k.added||k.add(D),b.alignDataLabel(d,k,l,null,u)):(d.dataLabel=k=k.destroy(),n&&(d.connector=n.destroy()))});a.fireEvent(this,"afterDrawDataLabels")};c.prototype.alignDataLabel=function(a,b,c,e,f){var d=this.chart,l=d.inverted,k=x(a.dlBox&&a.dlBox.centerX,a.plotX,-9999),n=x(a.plotY,-9999),m=b.getBBox(),p,r=c.rotation,q=c.align,u=this.visible&&(a.series.forceDL||d.isInsidePlot(k,Math.round(n),l)||e&&d.isInsidePlot(k,l?e.x+1:e.y+e.height-1,l)),g="justify"===x(c.overflow,"justify");if(u&&(p=c.style.fontSize, -p=d.renderer.fontMetrics(p,b).b,e=h({x:l?this.yAxis.len-n:k,y:Math.round(l?this.xAxis.len-k:n),width:0,height:0},e),h(c,{width:m.width,height:m.height}),r?(g=!1,k=d.renderer.rotCorr(p,r),k={x:e.x+c.x+e.width/2+k.x,y:e.y+c.y+{top:0,middle:.5,bottom:1}[c.verticalAlign]*e.height},b[f?"attr":"animate"](k).attr({align:q}),n=(r+720)%360,n=180n,"left"===q?k.y-=n?m.height:0:"center"===q?(k.x-=m.width/2,k.y-=m.height/2):"right"===q&&(k.x-=m.width,k.y-=n?0:m.height),b.placed=!0,b.alignAttr=k):(b.align(c, -null,e),k=b.alignAttr),g?a.isLabelJustified=this.justifyDataLabel(b,c,k,m,e,f):x(c.crop,!0)&&(u=d.isInsidePlot(k.x,k.y)&&d.isInsidePlot(k.x+m.width,k.y+m.height)),c.shape&&!r))b[f?"attr":"animate"]({anchorX:l?d.plotWidth-a.plotY:a.plotX,anchorY:l?d.plotHeight-a.plotX:a.plotY});u||(b.attr({y:-9999}),b.placed=!1)};c.prototype.justifyDataLabel=function(a,b,c,e,f,h){var d=this.chart,l=b.align,k=b.verticalAlign,m,t,n=a.box?0:a.padding||0;m=c.x+n;0>m&&("right"===l?b.align="left":b.x=-m,t=!0);m=c.x+e.width- -n;m>d.plotWidth&&("left"===l?b.align="right":b.x=d.plotWidth-m,t=!0);m=c.y+n;0>m&&("bottom"===k?b.verticalAlign="top":b.y=-m,t=!0);m=c.y+e.height-n;m>d.plotHeight&&("top"===k?b.verticalAlign="bottom":b.y=d.plotHeight-m,t=!0);t&&(a.placed=!h,a.align(b,null,f));return t};k.pie&&(k.pie.prototype.drawDataLabels=function(){var d=this,b=d.data,e,f=d.chart,h=d.options.dataLabels,l=x(h.connectorPadding,10),k=x(h.connectorWidth,1),r=f.plotWidth,q=f.plotHeight,m=Math.round(f.chartWidth/3),u,y=d.center,C=y[2]/ -2,G=y[1],g,w,K,Q,J=[[],[]],O,N,z,R,S=[0,0,0,0];d.visible&&(h.enabled||d._hasPointLabels)&&(n(b,function(a){a.dataLabel&&a.visible&&a.dataLabel.shortened&&(a.dataLabel.attr({width:"auto"}).css({width:"auto",textOverflow:"clip"}),a.dataLabel.shortened=!1)}),c.prototype.drawDataLabels.apply(d),n(b,function(a){a.dataLabel&&(a.visible?(J[a.half].push(a),a.dataLabel._pos=null,!F(h.style.width)&&!F(a.options.dataLabels&&a.options.dataLabels.style&&a.options.dataLabels.style.width)&&a.dataLabel.getBBox().width> -m&&(a.dataLabel.css({width:.7*m}),a.dataLabel.shortened=!0)):a.dataLabel=a.dataLabel.destroy())}),n(J,function(b,c){var k,m,t=b.length,p=[],u;if(t)for(d.sortByAngle(b,c-.5),0e.bottom-2?k:N,c,e),g._attr={visibility:z,align:K[6]},g._pos={x:O+h.x+({left:l,right:-l}[K[6]]||0),y:N+h.y-10},K.x=O,K.y=N,x(h.crop, -!0)&&(w=g.getBBox().width,k=null,O-wr-l&&0===c&&(k=Math.round(O+w-r+l),S[1]=Math.max(k,S[1])),0>N-Q/2?S[0]=Math.max(Math.round(-N+Q/2),S[0]):N+Q/2>q&&(S[2]=Math.max(Math.round(N+Q/2-q),S[2])),g.sideOverflow=k)}),0===E(S)||this.verifyDataLabelOverflow(S))&&(this.placeDataLabels(),k&&n(this.points,function(a){var b;u=a.connector;if((g=a.dataLabel)&&g._pos&&a.visible&&0x(this.translatedThreshold,l.yAxis.len)),m=x(e.inside,!!this.options.stacking);k&&(f=y(k),0>f.y&&(f.height+=f.y,f.y=0),k=f.y+f.height-l.yAxis.len,0a+d||e+hc+b||f+kthis.pointCount))},pan:function(a,b){var c=this,d=c.hoverPoints,e;d&&u(d,function(a){a.setState()});u("xy"===b?[1,0]: -[1],function(b){b=c[b?"xAxis":"yAxis"][0];var d=b.horiz,f=a[d?"chartX":"chartY"],d=d?"mouseDownX":"mouseDownY",h=c[d],g=(b.pointRange||0)/2,k=b.reversed&&!c.inverted||!b.reversed&&c.inverted?-1:1,l=b.getExtremes(),m=b.toValue(h-f,!0)+g*k,k=b.toValue(h+b.len-f,!0)-g*k,n=k=e(n.minWidth,0)&&this.chartHeight>=e(n.minHeight,0)}).call(this)&&h.push(a._id)};C.prototype.currentOptions= -function(e){function q(e,c,k,r){var f;a.objectEach(e,function(a,b){if(!r&&-1' + x_axis + ' and ' + y_axis + '', + xaxis: { title: { - text: 'Comparison between ' + x_axis + ' and ' + y_axis + '' - }, - xAxis: { - title: { - enabled: true, text: x_axis, + } }, - startOnTick: true, - endOnTick: true, - showLastLabel: true - }, - yAxis: { - title: { - text: y_axis, - } - }, - legend: { - layout: 'vertical', - align: 'left', - verticalAlign: 'top', - x: 100, - y: 70, - floating: true, - backgroundColor: (Highcharts.theme && Highcharts.theme.legendBackgroundColor) || '#FFFFFF', - borderWidth: 1 - }, - plotOptions: { - scatter: { - marker: { - radius: 5, - states: { - hover: { - enabled: true, - lineColor: 'rgb(100,100,100)' - } - } - }, - states: { - hover: { - marker: { - enabled: false - } - } - }, - tooltip: { - headerFormat: '{series.name} ({series.data.length} patients)
', - pointFormat: x_axis + ': {point.x}
' + y_axis +': {point.y}' + yaxis: { + title: { + text: y_axis, } } - }, - series: plot_series, - }); + },); + } // slider for dynamically added elements diff --git a/static/js/coplots.js b/static/js/coplots.js deleted file mode 100644 index af2b7566..00000000 --- a/static/js/coplots.js +++ /dev/null @@ -1,147 +0,0 @@ -$(document).ready(function() { - - // close error message - $(document).on('click', 'span.close', function() { - $(this).closest('div.alert').addClass('d-none'); - }); - - // apply filters - $(document).on('change', '#select_scale', function() { - if(this.checked) { - $('#scale_values').removeClass('d-none'); - } else { - $('#scale_values').addClass('d-none'); - } - }); - - // they all have to be of the same length - var chart_containers = []; - var titles = []; - var y_axis_text = []; - var x_axis_text = []; - var plot_series = []; - var how_to_plot = $('#coplot').attr('data-how-to-plot'); - $('#coplot').removeAttr('data-how-to-plot'); - // for single_plot - if (how_to_plot == "single_plot") { - // initializing constants and removing attributes from html elements - var PLOT_SERIES = $('#coplot').attr('data-plot-series').replace(/'/g, '"'); //"); - if (PLOT_SERIES.length != 0) { - PLOT_SERIES = JSON.parse(PLOT_SERIES); - // removing attributes - $('#coplot').removeAttr('data-plot-series'); - - var x_axis = $('#x_axis').val(); - var y_axis = $('#y_axis').val(); - var cat1 = $('#category1').val(); - var cat2 = $('#category2').val(); - - chart_containers.push('coplot'); - titles.push('Comparison of:' + x_axis + ' and ' + y_axis + ''); - x_axis_text.push(x_axis); - y_axis_text.push(y_axis); - plot_series.push(PLOT_SERIES); - } - } else { // for multiple plots - var how_to_plot = $('input:radio:checked').val(); - if (how_to_plot == 'multiple_plots') { - var x_axis = $('#x_axis').val(); - var y_axis = $('#y_axis').val(); - var cat1 = $('#category1').val(); - var cat2 = $('#category2').val(); - var cat1_values = $('#coplot').attr('data-cat1-values').replace(/'/g, '"'); //"); - var cat2_values = $('#coplot').attr('data-cat2-values').replace(/'/g, '"'); //"); - cat1_values = JSON.parse(cat1_values); - cat2_values = JSON.parse(cat2_values); - var PLOT_SERIES = $('#coplot').attr('data-plot-series').replace(/'/g, '"'); //"); - PLOT_SERIES = JSON.parse(PLOT_SERIES); - for (i=0; i" + val1 + " and " + val2 + ""); - x_axis_text.push(x_axis); - y_axis_text.push(y_axis); - var key = val1 + "_" + val2; - var series = PLOT_SERIES[key]; - plot_series.push([series]); - } - } - var chart_width = $('.coplot').width(); - chart_width = chart_width / cat2_values.length; - } - } - var x_min = parseFloat($('#coplot').attr('data-x-min')); - var x_max = parseFloat($('#coplot').attr('data-x-max')); - var y_min = parseFloat($('#coplot').attr('data-y-min')); - var y_max = parseFloat($('#coplot').attr('data-y-max')); - - for(i=0; i" + $('#category1').val() + ": " + this.options.cat1 + - '
' + $('#category2').val() + ': ' + this.options.cat2; - } - }, - plotOptions: { - scatter: { - marker: { - radius: 5, - states: { - hover: { - enabled: true, - lineColor: 'rgb(100,100,100)' - } - } - }, - states: { - hover: { - marker: { - enabled: false - } - } - }, - tooltip: { - pointFormat: '' + x_axis + ' : {point.x}
' + - '
' + y_axis + ' : {point.y}' + - '
' + cat1 + ' {point.cat1}' + - '
' + cat2 + ' {point.cat2}' + - '
patient_id: {point.patient_id}' + - '
group size: {point.series.options.series_length}', - } - } - }, - series: plot_series[i] - }); - if (how_to_plot == "multiple_plots") { - chart.setSize(chart_width, 400, doAnimation=false); - } - } - - -}); \ No newline at end of file diff --git a/static/js/coplots_pl.js b/static/js/coplots_pl.js new file mode 100644 index 00000000..de55fec6 --- /dev/null +++ b/static/js/coplots_pl.js @@ -0,0 +1,56 @@ +$(document).ready(function() { + + // close error message + $(document).on('click', 'span.close', function() { + $(this).closest('div.alert').addClass('d-none'); + }); + + // apply filters + $(document).on('change', '#select_scale', function() { + if(this.checked) { + $('#scale_values').removeClass('d-none'); + } else { + $('#scale_values').addClass('d-none'); + } + }); + + + var how_to_plot = $('#coplot').attr('data-how-to-plot'); + $('#coplot').removeAttr('data-how-to-plot'); + // for single_plot + + if (how_to_plot == "single_plot") { + // initializing constants and removing attributes from html elements + var x_axis = $('#coplot').attr('data-plot-x'); + var y_axis = $('#coplot').attr('data-plot-y'); + var PLOT_SERIES = $('#coplot').attr('data-plot-series2').replace(/'/g, '"'); //"); + if (PLOT_SERIES.length != 0) { + PLOT_SERIES = JSON.parse(PLOT_SERIES); + // removing attributes + Plotly.newPlot('coplot', + PLOT_SERIES, + { + title: 'Compare values', + 'xaxis': { + 'title': { + 'text': x_axis, + } + }, + 'yaxis': { + 'title': { + 'text': y_axis, + } + }, + },); + } + } else { // for multiple plots + var PLOT_SERIES = $('#coplot').attr('data-plot-series').replace(/'/g, '"'); //"); + var layout = $('#coplot').attr('data-layout').replace(/'/g, '"'); //"); + if (PLOT_SERIES.length != 0) { + PLOT_SERIES = JSON.parse(PLOT_SERIES); + layout = JSON.parse(layout); + Plotly.newPlot('coplot', + PLOT_SERIES,layout); + } + } +}); \ No newline at end of file diff --git a/static/js/heatmap.js b/static/js/heatmap.js new file mode 100644 index 00000000..5d6ffa25 --- /dev/null +++ b/static/js/heatmap.js @@ -0,0 +1,28 @@ +$(function () { + // close error message + $(document).on('click', 'span.close', function() { + $(this).closest('div.alert').addClass('d-none'); + }); + + +// pretify the select input for categorical entities + var c_choices = new Choices('#categorical_entities', { + allowSearch: true, + removeItemButton: true, + }); + + // pretify the select input for numeric entities + var n_choices = new Choices('#numeric_entities', { + allowSearch: true, + removeItemButton: true, + shouldSort: false, + }); + + var heatmap_data = JSON.parse($('#heatmap').attr('data-plot-series').replace(/'/g, '"')); //")); + if (heatmap_data.length != 0) { + // delete attribute + Plotly.newPlot('heatmap', + heatmap_data, + {title: 'Heatmap'},); + } +}); \ No newline at end of file diff --git a/static/js/histogram.js b/static/js/histogram.js index 62e88ded..9e2a1ed3 100644 --- a/static/js/histogram.js +++ b/static/js/histogram.js @@ -30,8 +30,7 @@ $(function () { text: "Number of Patients (Total count: " + count + ")", } } - }, - ); + },); } }); diff --git a/static/js/login.js b/static/js/login.js deleted file mode 100644 index d4aee26b..00000000 --- a/static/js/login.js +++ /dev/null @@ -1,4 +0,0 @@ -// close error message -$(document).on('click', 'span.close', function() { - $(this).parent().remove(); -}); \ No newline at end of file diff --git a/static/js/logout.js b/static/js/logout.js new file mode 100644 index 00000000..66f18dcf --- /dev/null +++ b/static/js/logout.js @@ -0,0 +1,4 @@ +// close error message + $(document).on('click', 'span.close', function() { + $(this).closest('div.alert').addClass('d-none'); + }); \ No newline at end of file diff --git a/static/js/plots.js b/static/js/plots.js index 1343edf4..c2f3a99b 100644 --- a/static/js/plots.js +++ b/static/js/plots.js @@ -4,82 +4,12 @@ $(function () { $(document).on('click', 'span.close', function() { $(this).closest('div.alert').addClass('d-none'); }); - + // scatter plot if ($('#scatter_plot_n').length != 0) { var x_axis = $('#scatter_plot_n').attr('data-plot-x'); var y_axis = $('#scatter_plot_n').attr('data-plot-y'); var plot_data = $('#scatter_plot_n').attr('data-plot-series').replace(/'/g, '"'); //"); - - if ($('#add_group_by:checkbox:checked').length > 0) { - - var plot_series = JSON.parse(plot_data); - var cat = $('#category').val(); - - var add_separate_regression_is_selected = $('#add_separate_regression:checkbox:checked').length > 0 - - add_separate_regression_is_selected ? applySeparateLinearRegressions(plot_series) : applySingleLinearRegression(plot_series) - - // initializing plot(s) - Highcharts.chart('scatter_plot_n', { - chart: { - type: 'scatter', - zoomType: 'xy', - height: 400, - }, - title: { - text: 'Compare values of ' + x_axis + ' and ' + y_axis + '', - }, - xAxis: { - title: { - text: x_axis, - }, - - }, - yAxis: { - title: { - text: y_axis, - }, - - }, - legend: { - labelFormatter: function () { - return this.options.cat ? "" + cat + ": " + this.options.cat : this.options.name; - } - }, - plotOptions: { - scatter: { - marker: { - radius: 5, - states: { - hover: { - enabled: true, - lineColor: 'rgb(100,100,100)' - } - } - }, - states: { - hover: { - marker: { - enabled: false - } - } - }, - tooltip: { - pointFormat: '' + x_axis + ' : {point.x}
' + - '
' + y_axis + ' : {point.y}' + - '
' + cat + ' {point.cat}' + - '
patient_id: {point.patient_id}' + - '
group size: {point.series.options.series_length}', - } - } - }, - series: plot_series, - }); - - - } else { - Highcharts.chart('scatter_plot_n', { chart: { type: 'scatter', @@ -124,56 +54,6 @@ $(function () { turboThreshold: 0, }], }); - } - - } - - /** - * Adds regression properties to each series - * @param {Array} plot_series - */ - function applySeparateLinearRegressions(plot_series) { - plot_series.forEach((series, index) => { - series.regression = true; - series.colorIndex = index; - series.regressionSettings = { - type: 'linear', - color: series.color, - name: 'Linear regression: r: %r (%eq)', // show equation and r - }; - }); - } - - /** - * It takes a plot_series with multiple categories - * and adds a regression_line series by joining them - * @param {Array} plot_series - */ - function applySingleLinearRegression(plot_series) { - regression_line = { - regression: true, - regressionSettings: { - type: 'linear', - color: 'black', - name: 'Linear regression: r: %r (%eq)', // show equation and r - }, - data: [], - visible: false, - showInLegend: false, - // disable max length of data-series - turboThreshold: 0, - } - - if (plot_series && plot_series.length > 0) { - // Collect data from each series - multiple_data = plot_series.map(series => series.data) - // Concatenate the multiple data to a single data - concatenated = [].concat.apply([], multiple_data) - // assign it to the 'data' property of the regression line - regression_line.data = concatenated - plot_series.push(regression_line) - } - } // pretify the select input for categorical entities @@ -264,23 +144,6 @@ $(function () { } }); - // // apply filters for separate regression - // $(document).on('change', '#add_separate_regression', function() { - // if(this.checked) { - // $('#add_separate_regression').removeClass('d-none'); - // } else { - // $('#add_separate_regression').addClass('d-none'); - // } - // }); - - // apply filter - $(document).on('change', '#add_group_by', function() { - if(this.checked) { - $('#add_group').removeClass('d-none'); - } else { - $('#add_group').addClass('d-none'); - } - }); // add entity to the list when selected in select input $('#numeric_entities').on('addItem', function(event) { diff --git a/static/js/plots_plotly.js b/static/js/plots_plotly.js new file mode 100644 index 00000000..4952b870 --- /dev/null +++ b/static/js/plots_plotly.js @@ -0,0 +1,70 @@ +$(function () { + + // close error message + $(document).on('click', 'span.close', function() { + $(this).closest('div.alert').addClass('d-none'); + }); + + // apply filter + $(document).on('change', '#add_group_by', function() { + if(this.checked == false) { + $('#add_group').addClass('d-none'); + } else { + $('#add_group').removeClass('d-none'); + } + }); + + + // scatter plot + + if ($('#add_group_by:checkbox:checked').length > 0) { + + var x_axis = $('#scatter_plot').attr('data-plot-x'); + var y_axis = $('#scatter_plot').attr('data-plot-y'); + var plot_data = $('#scatter_plot').attr('data-plot-series').replace(/'/g, '"'); //"); + if ($('#scatter_plot').length != 0) { + plot_data = JSON.parse(plot_data); + Plotly.newPlot('scatter_plot', + plot_data, + { + title: 'Compare values of ' + x_axis + ' and ' + y_axis + '', + xaxis: { + title: { + text: x_axis, + } + }, + yaxis: { + title: { + text: y_axis, + } + } + },); + } + }else{ + var x_axis = $('#scatter_plot').attr('data-plot-x'); + var y_axis = $('#scatter_plot').attr('data-plot-y'); + var plot_data = $('#scatter_plot').attr('data-plot-series').replace(/'/g, '"'); //"); + if ($('#scatter_plot').length != 0) { + plot_data = JSON.parse(plot_data); + Plotly.newPlot('scatter_plot', + plot_data, + { + title: 'Compare values of ' + x_axis + ' and ' + y_axis + '', + xaxis: { + title: { + text: x_axis, + } + }, + yaxis: { + title: { + text: y_axis, + } + } + },); + } + } + + + +}); + diff --git a/templates/barchart.html b/templates/barchart.html new file mode 100644 index 00000000..73d3e477 --- /dev/null +++ b/templates/barchart.html @@ -0,0 +1,98 @@ +{% extends "layout.html" %} +{% block body %} + +{# imports #} + {# our own js #} + + + +{# pretty multiselect inputs #} + + + + {# export tables #} + + + + +{# end of imports #} + + +{% if error %} + +{% endif %} + + +
+ {# plot type #} +
+ + +
+
+ +
+
+ + + + +
+ {% if plot_series %} +
+ +
+ {% endif %} + + {% if table_data %} + + + + + + + + + + + + + {% for entity in entity_values %} + {% for value in entity_values[entity] %} + + + + + + {% endfor %} + {% endfor %} + +
EntityValueNumber of patients
{{ entity }}{{ value }}{{ entity_values[entity][value] }}
+ {% endif %} +
+ + + + +{% endblock %} diff --git a/templates/basic_stats/categorical_tab.html b/templates/basic_stats/categorical_tab.html index 9810cabd..749972a0 100644 --- a/templates/basic_stats/categorical_tab.html +++ b/templates/basic_stats/categorical_tab.html @@ -24,14 +24,14 @@
{% endif %}
- - -
- + + +
+
diff --git a/templates/boxplot.html b/templates/boxplot.html index be9aaaed..896b545a 100644 --- a/templates/boxplot.html +++ b/templates/boxplot.html @@ -35,10 +35,15 @@ {% endfor %} - + {% if plot_series %} -
+
+ +
{% endif %} {% endblock %} diff --git a/templates/cluster_numeric_entities.html b/templates/cluster_numeric_entities.html deleted file mode 100644 index 6987b1db..00000000 --- a/templates/cluster_numeric_entities.html +++ /dev/null @@ -1,146 +0,0 @@ -{% extends "layout.html" %} -{% block body %} - - - - - - - - - - - - - - - - - -
-
- -
-
- -
- - Yes - No - -
- - - -
- - - - - -
- - {% if numeric_label_uses is not none %} - - - - - - {% for e in numeric_entities %} - - {% endfor %} - - - {% for cluster in numeric_label_uses.keys()|sort %} - - - - - {% for i, e in enumerate(numeric_entities) %} - - {% endfor %} - - {% endfor %} - -
ClusterPatient countPatient percent{{e}}
{{cluster}}{{numeric_label_uses[cluster]}}{{ "{0:.2f}".format(numeric_m.weights_[cluster]) }}{{ "{0:.2f}".format(numeric_m.means_[cluster][i]) }}
- - - {% if numeric_cluster_image is not none %} - Numeric clusters - {% endif %} - - {% endif %} - - {% if categorical_label_uses is not none %} - - - - - - {% for e in category_values %} - - {% endfor %} - - - {% for cluster, uses in categorical_label_uses.items() %} - - - - - {% for v in category_values %} - - {% endfor %} - - {% endfor %} - -
ClusterPatient countPatient percent{{e}}
{{cluster}}{{uses}}{{ "{0:.2f}".format(uses / any_present) }}{{ "{0:d}".format(cluster_category_values[cluster][v]) }}
- - {% if categorical_cluster_image is not none %} - Categorical clusters - {% endif %} - - {% endif %} - - - {% if counts is not none %} - - - - - - - - - - - - {% for f in counts.index %} - - - - - {% endfor %} - - - - - -
EntityCount
Any entity {{ any_present }}
{{ f }} {{ counts[f] }}
All entities {{ all_present }}
- {%endif%} - -{% endblock %} diff --git a/templates/clustering/categorical_tab.html b/templates/clustering/categorical_tab.html deleted file mode 100644 index 67003fa0..00000000 --- a/templates/clustering/categorical_tab.html +++ /dev/null @@ -1,102 +0,0 @@ -{# Categorical tab. Using bootstrap classes. The tab container (div#statistics) and navigation links (ul#statistics_tab) - are defined in statistics.html #} -
-
-
-
-
- -
-
-
-
- {% if error %} - - {% endif %} - - - -
-
- - -
-
- - -
-
-
- -
- -
-
-
-
-
-
- -
-
-
-
- {% if heat_map_data %} -
- {% endif %} - {% if ccv %} - - - - - - - - - - - {% for cluster, ccv_dict in ccv.items() %} - {% for entity, value in ccv_dict.items() %} - - - - - - {% endfor %} - {% endfor %} - -
ClusterValueNumer of patients
{{ cluster }}{{ entity }}{{ value }}
- {% endif %} -
-
-
-
-
\ No newline at end of file diff --git a/templates/clustering/clustering.html b/templates/clustering/clustering.html deleted file mode 100644 index 8484c53b..00000000 --- a/templates/clustering/clustering.html +++ /dev/null @@ -1,42 +0,0 @@ -{% extends "layout.html" %} -{% block body %} - -{# imports #} - {# our own js #} - - - {# pretty multiselect inputs #} - - - - {# requirements for highcharts #} - - - - {# export tables #} - - - - {# range slider #} - - -{# end of imports #} - -{# navigation controls #} - -{# tabs content #} -
- {# each tab in a single file, for readability #} -{# {% include 'clustering/categorical_tab.html' %}#} - {% include 'clustering/numeric_tab.html' %} -
-{% endblock %} \ No newline at end of file diff --git a/templates/clustering/numeric_tab.html b/templates/clustering/numeric_tab.html deleted file mode 100644 index cf3bf116..00000000 --- a/templates/clustering/numeric_tab.html +++ /dev/null @@ -1,155 +0,0 @@ -
-
- {# SETTINGS #} -
-
-
- -
-
-
-
-
- {% if error %} - - {% endif %} -
- - -
    - {% if selected_n_entities and selected_min_max %} - {% for entity in selected_n_entities %} - {% if entity in selected_min_max %} - {% set min_value, max_value = selected_min_max[entity] %} -
  • -
    -
    - -
    -
    - - - -
    -
    - × -
    -
    -
  • - {% endif %} - {% endfor %} - {% endif %} -
-
-
- - -
-
- -
-
- - -
-
- - -
-
-
-
- -
- -
-
-
-
-
-
- {# RESULTS #} -
-
-
- -
-
-
-
- {% if plot_data %} -
- {% endif %} - {% if table_data %} - - - - - - - - - - {% for entity in selected_n_entities %} - - {% endfor %} - - - - - {% for cluster in table_data.keys() | sort %} - - - - - {% for entity in selected_n_entities %} - - {% endfor %} - - {% endfor %} - -
ClusterPatient countPatient percent{{ entity }} (mean value)
{{ cluster }}{{ table_data[cluster]['patient_count'] }}{{ table_data[cluster]['patient_percent'] }}{{ table_data[cluster][entity] }}
- {% endif %} -
-
-
-
-
\ No newline at end of file diff --git a/templates/clustering_pl.html b/templates/clustering_pl.html new file mode 100644 index 00000000..d43c9482 --- /dev/null +++ b/templates/clustering_pl.html @@ -0,0 +1,143 @@ +{% extends "layout.html" %} +{% block body %} + +{# imports #} + {# our own js #} + + + +{# pretty multiselect inputs #} + + + + + {# range slider #} + + +{# end of imports #} + + +{% if error %} + +{% endif %} + + + + + +
+ + +
    + {% if selected_n_entities and selected_min_max %} + {% for entity in selected_n_entities %} + {% set min_value, max_value = selected_min_max[entity] %} +
  • +
    +
    + +
    +
    + + + +
    +
    + × +
    +
    +
  • + {% endfor %} + {% endif %} +
+
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+
+ +
+ +
+
+ + +
+ {% if plot_data %} +
+ {% endif %} + + {% if table_data %} + + + + + + + + + + {% for entity in selected_n_entities %} + + {% endfor %} + + + + + {% for cluster in table_data.keys() | sort %} + + + + + {% for entity in selected_n_entities %} + + {% endfor %} + + {% endfor %} + +
ClusterPatient countPatient percent{{ entity }} (mean value)
{{ cluster }}{{ table_data[cluster]['patient_count'] }}{{ table_data[cluster]['patient_percent'] }}{{ table_data[cluster][entity] }}
+ {% endif %} +
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/coplots.html b/templates/coplots.html deleted file mode 100644 index 25ff6029..00000000 --- a/templates/coplots.html +++ /dev/null @@ -1,128 +0,0 @@ -{% extends "layout.html" %} -{% block body %} - - -
-
- - {% if error %} - - {% endif %} - - - - - - - - - - - {# select scale #} -
- - -
- -
-
-
-

X Axis Scale

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

Y Axis Scale

-
-
-
-
- -
-
- - -
-
-
-
-
-
- - -
- - -
-
- - -
- -
- - - {# container for single plot #} -
-
- - - {# containers for multiple plots #} -
- {% for val1 in cat1_values %} -
- {% for val2 in cat2_values %} -
-
- {% endfor %} -
- {% endfor %} -
-
-{% endblock %} \ No newline at end of file diff --git a/templates/coplots_pl.html b/templates/coplots_pl.html new file mode 100644 index 00000000..18632327 --- /dev/null +++ b/templates/coplots_pl.html @@ -0,0 +1,97 @@ +{% extends "layout.html" %} +{% block body %} + +{# imports #} + {# our own js #} + + + + +{# pretty multiselect inputs #} + + + + + {# range slider #} + + +{# end of imports #} + + +{% if error %} + +{% endif %} + + +
+
+ {% if error %} + + {% endif %} + + + + + + + + + + + + + + +
+ + +
+
+ + +
+ +
+
+ +{% if plot_series %} +
+{% endif %} + + + +{% endblock %} \ No newline at end of file diff --git a/templates/data_management.html b/templates/data_management.html deleted file mode 100644 index 4754d8cb..00000000 --- a/templates/data_management.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "layout.html" %} -{% block body %} - -
- {% if update_message %} -

{{ update_message }}

- {% endif %} -

Select CSV data file: - -

-

Select entities data file (category: type): - -

- -
- -{% endblock %} \ No newline at end of file diff --git a/templates/heatmap.html b/templates/heatmap.html new file mode 100644 index 00000000..dafdfad0 --- /dev/null +++ b/templates/heatmap.html @@ -0,0 +1,48 @@ +{% extends "layout.html" %} +{% block body %} + +{# imports #} + {# our own js #} + + + +{# pretty multiselect inputs #} + + + + + {# range slider #} + + +{# end of imports #} + + +{% if error %} + +{% endif %} + + +
+
+ + +
+
+ +
+
+ +{% if plot_series %} +
+{% endif %} +{% endblock %} + diff --git a/templates/histogram.html b/templates/histogram.html index 9c9bff91..6dfb573d 100644 --- a/templates/histogram.html +++ b/templates/histogram.html @@ -7,17 +7,21 @@ {# end of imports #} + {% if error %} {% endif %} + + +
-
+
+
+ + + {% if plot_series %} -
+
+
{% endif %} + + + {% endblock %} \ No newline at end of file diff --git a/templates/image_test.html b/templates/image_test.html deleted file mode 100644 index ba1d085b..00000000 --- a/templates/image_test.html +++ /dev/null @@ -1,8 +0,0 @@ - - - {{ title }} - image - - - Image Placeholder - - diff --git a/templates/layout.html b/templates/layout.html index 752bee2b..407a1983 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -20,11 +20,6 @@ {# end of requirements for bootstrap #} - {# show login form (toggle dropdown) if error message -- not working?? #} - {% if login_error %} - - {% endif %} -
{% block body %}{% endblock %}
diff --git a/templates/login_page.html b/templates/login_page.html deleted file mode 100644 index 9d4ee914..00000000 --- a/templates/login_page.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends "layout.html" %} -{% block body %} - - - -
-
-
- {% if error %} - - {% endif %} -
-

Data Warehouse

-
- - -
-
- - -
-
- - -
- -
- - Not registered? - Forgot password? - -
-
-
-{% endblock %} diff --git a/templates/logout.html b/templates/logout.html new file mode 100644 index 00000000..f0d3b238 --- /dev/null +++ b/templates/logout.html @@ -0,0 +1,8 @@ +{% extends "layout.html" %} +{% block body %} + +
+

Data Warehouse

+
+ +{% endblock %} \ No newline at end of file diff --git a/templates/plots/categorical_tab.html b/templates/plots/categorical_tab.html deleted file mode 100644 index 9e789469..00000000 --- a/templates/plots/categorical_tab.html +++ /dev/null @@ -1,114 +0,0 @@ -{# Categorical tab. Using bootstrap classes. The tab container (div#statistics) and navigation links (ul#statistics_tab) - are defined in statistics.html #} -
-
-
-
-
- -
-
-
-
- {% if error %} - - {% endif %} -
- {# plot type #} -
- - -
- - -
-
- - -
-
-
- -
-
-
-
-
-
-
-
- -
-
-
-
- {% if entity_values %} - {% if plot_type == 'simple' %} -
- {% elif plot_type == 'stacked' %} -
- {% endif %} - - - - - - - - - - - - - {% for entity in entity_values %} - {% for value in entity_values[entity] %} - - - - - - {% endfor %} - {% endfor %} - -
EntityValueNumber of patients
{{ entity }}{{ value }}{{ entity_values[entity][value] }}
- {% endif %} -
-
-
-
-
\ No newline at end of file diff --git a/templates/plots/numeric_tab.html b/templates/plots/numeric_tab.html index b33ceeff..51cac14d 100644 --- a/templates/plots/numeric_tab.html +++ b/templates/plots/numeric_tab.html @@ -1,146 +1,124 @@ {# Numeric tab. Using bootstrap classes. The tab container (div.#statistics) and navigation links (ul#statistics_tab) - are defined in statistics.html. #} + are defined in statistics.html. #}
-
-
-
-
- -
-
-
-
- {% if error %} - - {% endif %} -
- {# plot type #} -
- - -
- {# scatter plot controls #} -
- - - - -
- - -
-
-
-
- - -
- - + id="numeric_tab" role="tabpanel" aria-labelledby="n_settings_header"> +
+
+
+
+ +
+
+
+
+ {% if error %} + -
- {# heat map controls #} -
- -
    - {% if selected_n_entities %} - {% for entity in selected_n_entities %} - {% set min_value, max_value = selected_min_max[entity] %} -
  • -
    -
    - -
    -
    - - - -
    -
    - × -
    -
    -
  • - {% endfor %} +
{% endif %} - -
-
- + + {# plot type #} +
+ + +
+ {# scatter plot controls #} +
+ + + + +
+ {# heat map controls #} +
+ +
    + {% if selected_n_entities %} + {% for entity in selected_n_entities %} + {% set min_value, max_value = selected_min_max[entity] %} +
  • +
    +
    + +
    +
    + + + +
    +
    + × +
    +
    +
  • + {% endfor %} + {% endif %} +
+
+
+ +
+
- +
-
-
-
-
-
- -
-
-
-
- {% if plot_type == 'heat_map_n' %} -
- {% elif plot_type == 'scatter_plot_n' %} -
- {% endif %} +
+
+
+ +
+
+
+
+ {% if plot_type == 'heat_map_n' %} +
+ {% elif plot_type == 'scatter_plot_n' %} +
+ {% endif %} +
+
-
-
diff --git a/templates/plots/plots.html b/templates/plots/plots.html deleted file mode 100644 index bf04f935..00000000 --- a/templates/plots/plots.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends "layout.html" %} -{% block body %} - - -{# imports #} - {# our own js #} - - - {# pretty multiselect inputs #} - - - - {# requirements for highcharts #} - - - - - - {# export tables #} - - - - {# slider #} - - - -{# end of imports #} - -{# navigation controls #} - -{# tabs content #} -
- {# each tab in a single file, for readability #} - {% include 'plots/numeric_tab.html' %} - {% include 'plots/categorical_tab.html' %} -
-{% endblock %} \ No newline at end of file diff --git a/templates/scatter_plot.html b/templates/scatter_plot.html new file mode 100644 index 00000000..92e1e26d --- /dev/null +++ b/templates/scatter_plot.html @@ -0,0 +1,73 @@ +{% extends "layout.html" %} +{% block body %} + +{# imports #} + {# our own js #} + + + + + {# pretty multiselect inputs #} + + + + {# export tables #} + + + + {# slider #} + + +{# end of imports #} + +{% if error %} + +{% endif %} + +
+
+ + + + +
+ + +
+
+
+
+ + +
+
+ +
+ +{% if plot_series %} +
+{% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/show_entities.html b/templates/show_entities.html deleted file mode 100644 index 06211224..00000000 --- a/templates/show_entities.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends "layout.html" %} -{% block body %} - - - - - - {% if categories is not none %} - - - - - {% endif %} - - -
- - - - -
- - - - -
- - {{ num_patients }} - -{% endblock %} diff --git a/test.txt b/test.txt deleted file mode 100644 index 7fbc42f8..00000000 --- a/test.txt +++ /dev/null @@ -1 +0,0 @@ -abc.abc.abc diff --git a/url_handlers/barchart.py b/url_handlers/barchart.py new file mode 100644 index 00000000..4af3d4b5 --- /dev/null +++ b/url_handlers/barchart.py @@ -0,0 +1,97 @@ +from flask import Blueprint, render_template, request +import collections +import pandas as pd +import json +import plotly +import plotly.graph_objs as go + +import data_warehouse.redis_rwh as rwh + +barchart_page = Blueprint('barchart', __name__, + template_folder='templates') + + +@barchart_page.route('/barchart', methods=['GET']) +def get_statistics(): + # this import has to be here!! + from webserver import get_db + rdb = get_db() + all_numeric_entities = rwh.get_numeric_entities(rdb) + all_categorical_entities = rwh.get_categorical_entities(rdb) + all_categorical_only_entities = sorted(set(all_categorical_entities) - set(all_numeric_entities)) + + return render_template('barchart.html', + numeric_tab=True, + all_categorical_entities=all_categorical_only_entities) + + +@barchart_page.route('/barchart', methods=['POST']) +def post_statistics(): + # this import has to be here!! + from webserver import get_db + rdb = get_db() + all_categorical_entities = rwh.get_categorical_entities(rdb) + all_numeric_entities = rwh.get_numeric_entities(rdb) + all_categorical_only_entities = sorted(set(all_categorical_entities) - set(all_numeric_entities)) + + + selected_entities = request.form.getlist('categorical_entities') + + + error = None + if not selected_entities: + error = "Please select entities" + elif selected_entities: + categorical_df, error = rwh.get_joined_categorical_values(selected_entities, rdb) + error = "No data based on the selected entities ( " + ", ".join(categorical_df) + " ) " if error else None + + + + if error: + return render_template('barchart.html', + categorical_tab=True, + all_categorical_entities=all_categorical_only_entities, + selected_c_entities=selected_entities, + error=error + ) + + entity_values = {} + key =[] + plot_series = [] + data = [] + for entity in selected_entities: + counter = collections.Counter(categorical_df[entity]) + values_c = list(counter.values()) + key_c = list(counter.keys()) + key.append(list(counter.keys())) + data.append(go.Bar(x=key_c, y=values_c, name=entity, width=0.3)) + plot_series.append({ + 'x': key_c, + 'y': values_c, + 'name': entity, + 'type': "bar", + 'width': 0.3 + }) + entity_df = pd.DataFrame(columns=[entity], data=categorical_df[entity].dropna()) + list_of_values = set(entity_df[entity].unique()) + entity_values[entity] = {} + for value in list_of_values: + entity_values[entity][value] = len(entity_df.loc[entity_df[entity] == value]) + + + layout = go.Layout( + barmode='stack', + template = 'plotly_white' + ) + + data = go.Figure(data=data, layout=layout) + graphJSON = json.dumps(data, cls=plotly.utils.PlotlyJSONEncoder) + + return render_template('barchart.html', + categorical_tab=True, + all_categorical_entities=all_categorical_only_entities, + plot = graphJSON, + entity_values=entity_values, + selected_c_entities=selected_entities, + plot_series=plot_series + ) diff --git a/url_handlers/basic_stats.py b/url_handlers/basic_stats.py index b790137b..a44b06ca 100644 --- a/url_handlers/basic_stats.py +++ b/url_handlers/basic_stats.py @@ -1,14 +1,19 @@ from flask import Blueprint, render_template, request - import data_warehouse.redis_rwh as rwh basic_stats_page = Blueprint('basic_stats', __name__, template_folder='basic_stats') + @basic_stats_page.route('/basic_stats', methods=['GET']) def get_statistics(): - # this import has to be here!! + """ + + Returns + ------- + + """ from webserver import get_db rdb = get_db() all_numeric_entities = rwh.get_numeric_entities(rdb) @@ -23,6 +28,13 @@ def get_statistics(): @basic_stats_page.route('/basic_stats', methods=['POST']) def get_basic_stats(): + """ + + Returns + ------- + + """ + from webserver import get_db rdb = get_db() all_numeric_entities = rwh.get_numeric_entities(rdb) @@ -31,8 +43,12 @@ def get_basic_stats(): if 'basic_stats' in request.form: numeric_entities = request.form.getlist('numeric_entities') - numeric_df, error = rwh.get_joined_numeric_values(numeric_entities, rdb) if numeric_entities else (None,"Please select numeric entities") - + error = None + if numeric_entities: + numeric_df, error = rwh.get_joined_numeric_values(numeric_entities, rdb) + error = "The selected entities (" + ", ".join(numeric_entities) + ") do not contain any values. " if error else None + else: + error = "Please select numeric entities" if error: return render_template('basic_stats/basic_stats.html', numeric_tab=True, @@ -90,8 +106,12 @@ def get_basic_stats(): if 'basic_stats_c' in request.form: categorical_entities = request.form.getlist('categorical_entities') # if not categorical_entities: - categorical_df, error = rwh.get_joined_categorical_values(categorical_entities, rdb) if categorical_entities else (None, "Please select entities") - + error = None + if categorical_entities: + categorical_df, error = rwh.get_joined_categorical_values(categorical_entities, rdb) + error = "No data based on the selected entities ( " + ", ".join(categorical_entities) + " ) " if error else None + else: + error = "Please select entities" if error: return render_template('basic_stats/basic_stats.html', categorical_tab=True, @@ -105,8 +125,6 @@ def get_basic_stats(): basic_stats_c[entity] = { } # if entity in categorical_df.columns: count = categorical_df[categorical_df.columns.intersection([entity])].count()[entity] - # else: - # count = 0 basic_stats_c[entity]['count'] = count return render_template('basic_stats/basic_stats.html', @@ -115,3 +133,4 @@ def get_basic_stats(): all_numeric_entities=all_numeric_entities, selected_c_entities=categorical_entities, basic_stats_c=basic_stats_c) + diff --git a/url_handlers/boxplot.py b/url_handlers/boxplot.py index 068cf656..a52a9ee5 100644 --- a/url_handlers/boxplot.py +++ b/url_handlers/boxplot.py @@ -1,6 +1,8 @@ from flask import Blueprint, render_template, request import pandas as pd - +import json +import plotly +import plotly.graph_objs as go import data_warehouse.redis_rwh as rwh boxplot_page = Blueprint('boxplot', __name__, @@ -31,7 +33,7 @@ def post_boxplots(): error = None if not entity or not group_by or entity == "Choose entity" or group_by == "Choose entity": - error = "Please select entity and group_by" + error = "Please select entity and group by" # get joined numerical and categorical values if not error: @@ -46,10 +48,12 @@ def post_boxplots(): selected_entity=entity, group_by=group_by, ) + + merged_df = pd.merge(numeric_df, categorical_df, how='inner', on='patient_id') min_val = numeric_df[entity].min() max_val = numeric_df[entity].max() - + data =[] groups = set(categorical_df[group_by].values.tolist()) plot_series = [] for group in sorted(groups): @@ -58,19 +62,21 @@ def post_boxplots(): values = df[entity].values.tolist() # print(entity, group, values[:10]) if (values): + data.append(go.Box(y=values, name =group)) plot_series.append({ - 'y' : values, + 'y': values, 'type': "box", # 'opacity': 0.5, 'name': group, }) - + graphJSON = json.dumps(data, cls=plotly.utils.PlotlyJSONEncoder) return render_template('boxplot.html', categorical_entities=all_categorical_entities, numeric_entities=all_numeric_entities, selected_entity=entity, group_by=group_by, plot_series=plot_series, + plot = graphJSON, min_val=min_val, max_val=max_val, ) diff --git a/url_handlers/clustering.py b/url_handlers/clustering.py deleted file mode 100644 index 57ce245c..00000000 --- a/url_handlers/clustering.py +++ /dev/null @@ -1,185 +0,0 @@ -from flask import Blueprint, render_template, request -import numpy as np - -import data_warehouse.redis_rwh as rwh -import data_warehouse.data_warehouse_utils as dwu - -clustering_page = Blueprint('clustering', __name__, - template_folder='clustering') - - -@clustering_page.route('/clustering', methods=['GET']) -def cluster(): - # this import has to be here!! - from webserver import get_db - rdb = get_db() - all_numeric_entities = rwh.get_numeric_entities(rdb) - all_categorical_entities = rwh.get_categorical_entities(rdb) - all_categorical_only_entities = sorted(set(all_categorical_entities) - set(all_numeric_entities)) - - # numeric_df = rwh.get_joined_numeric_values(all_numeric_entities, rdb) - # - # min_values = numeric_df.min() - # max_values = numeric_df.max() - - min_max_values = { } - # for entity in all_numeric_entities: - # min_max_values[entity] = { - # 'min': min_values[entity], - # 'max': max_values[entity], - # 'step': (max_values[entity] - min_values[entity]) / 100.0 - # } - return render_template('clustering/clustering.html', - numeric_tab=True, - all_numeric_entities=all_numeric_entities, - all_categorical_entities=all_categorical_only_entities, - min_max_values=min_max_values, - ) - - -@clustering_page.route('/clustering', methods=['POST']) -def post_clustering(): - # this import has to be here!! - from webserver import get_db - rdb = get_db() - all_numeric_entities = rwh.get_numeric_entities(rdb) - all_categorical_entities = rwh.get_categorical_entities(rdb) - all_categorical_only_entities = sorted(set(all_categorical_entities) - set(all_numeric_entities)) - min_max_values = { } - - if 'cluster_numeric' in request.form: - # transforming back underscores to dots - numeric_entities_with_underscore = request.form.getlist('numeric_entities') - numeric_entities = [entity.replace('__', '.') for entity in numeric_entities_with_underscore] - if not numeric_entities: - error = "Please select entities" - return render_template('clustering/clustering.html', - numeric_tab=True, - all_numeric_entities=all_numeric_entities, - all_categorical_entities=all_categorical_only_entities, - min_max_values=min_max_values, - error=error, - ) - - numeric_standardize = request.form['n_standardize'] == "yes" - numeric_missing = request.form['n_missing'] - min_max_filter = { } - for entity in numeric_entities: - min_max_entity = 'min_max_{}'.format(entity.replace('.', '__')) - if min_max_entity in request.form: - min_value, max_value = list(eval(request.form.get(min_max_entity))) - min_max_filter[entity] = min_value, max_value - - min_max_values[entity] = { - 'min' : min_value, - 'max' : max_value, - 'step': (max_value - min_value) / float(100), - } - - if any([entity for entity in numeric_entities]): - np.random.seed(8675309) # what is this number? - cluster_data, cluster_labels, df, error = dwu.cluster_numeric_fields( - numeric_entities, - rdb, - standardize=numeric_standardize, - missing=numeric_missing, - min_max_filter=min_max_filter, - ) - if error: - return render_template('clustering/clustering.html', - numeric_tab=True, - selected_n_entities=numeric_entities, - all_numeric_entities=all_numeric_entities, - all_categorical_entities=all_categorical_only_entities, - min_max_values=min_max_values, - selected_min_max=min_max_filter, - error=error, - ) - table_data = { } - plot_data = [] - for cluster in sorted(cluster_labels.keys()): - patient_count = cluster_labels[cluster] - patient_percent = "{:.0%}".format(cluster_data.weights_[cluster]) - - table_data[cluster] = { } - table_data[cluster]['patient_count'] = patient_count - table_data[cluster]['patient_percent'] = patient_percent - for i, entity in enumerate(numeric_entities): - mean_value = "{0:.2f}".format(cluster_data.means_[cluster][i]) - table_data[cluster][entity] = mean_value - # filter by cluster - entity_series = df[df.cluster == cluster][numeric_entities].dropna().values.round(2).tolist() - plot_data.append({ "name": "Cluster {}".format(cluster), "data": entity_series }) - any_present = df.shape[0] - all_present = df.dropna().shape[0] - return render_template('clustering/clustering.html', - numeric_tab=True, - selected_n_entities=numeric_entities, - all_numeric_entities=all_numeric_entities, - all_categorical_entities=all_categorical_only_entities, - any_present=any_present, - all_present=all_present, - table_data=table_data, - plot_data=plot_data, - min_max_values=min_max_values, - selected_min_max=min_max_filter, - ) - - elif 'cluster_categorical' in request.form: - eps = float(request.form['eps']) - min_samples = int(request.form['min_samples']) - categorical_entities = request.form.getlist('categorical_entities') - if not categorical_entities: - error = "Please select entities" - return render_template('clustering/clustering.html', - numeric_tab=True, - all_numeric_entities=all_numeric_entities, - all_categorical_entities=all_categorical_only_entities, - min_max_values=min_max_values, - error=error, - ) - - if any([entity for entity in categorical_entities]): - eps = eps - min_samples = min_samples - np.random.seed(8675309) - cluster_info = dwu.cluster_categorical_entities( - categorical_entities, - rdb, - eps=eps, - min_samples=min_samples - ) - - ccv, cat_rep_np, category_values, categorical_label_uses, cat_df, error = cluster_info - if error: - return render_template('clustering/clustering.html', - categorical_tab=True, - all_numeric_entities=all_numeric_entities, - all_categorical_entities=all_categorical_only_entities, - selected_c_entities=categorical_entities, - c_cluster_info=cluster_info, - ccv=cvv_dict, - min_max_values=min_max_values, - error=error - ) - any_present = cat_df.shape[0] - all_present = cat_df.dropna().shape[0] - - # df to dict - cvv_dict = { } - for key, value in ccv.items(): - normal_value = value.to_dict() - cvv_dict[key] = normal_value - - return render_template('clustering/clustering.html', - categorical_tab=True, - all_numeric_entities=all_numeric_entities, - all_categorical_entities=all_categorical_only_entities, - selected_c_entities=categorical_entities, - c_cluster_info=cluster_info, - all_present=all_present, - any_present=any_present, - ccv=cvv_dict, - min_max_values=min_max_values, - ) - # heat_map_data=data) diff --git a/url_handlers/clustering_pl.py b/url_handlers/clustering_pl.py new file mode 100644 index 00000000..ac299156 --- /dev/null +++ b/url_handlers/clustering_pl.py @@ -0,0 +1,122 @@ +from flask import Blueprint, render_template, request +import numpy as np + +import data_warehouse.redis_rwh as rwh +import data_warehouse.data_warehouse_utils as dwu + +clustering_plot_page = Blueprint('clustering_pl', __name__, + template_folder='clustering_pl') + + +@clustering_plot_page.route('/clustering_pl', methods=['GET']) +def cluster(): + # this import has to be here!! + from webserver import get_db + rdb = get_db() + all_numeric_entities = rwh.get_numeric_entities(rdb) + + + + min_max_values = { } + + return render_template('clustering_pl.html', + numeric_tab=True, + all_numeric_entities=all_numeric_entities, + min_max_values=min_max_values, + ) + + +@clustering_plot_page.route('/clustering_pl', methods=['POST']) +def post_clustering(): + # this import has to be here!! + from webserver import get_db + rdb = get_db() + all_numeric_entities = rwh.get_numeric_entities(rdb) + min_max_values = { } + + + # transforming back underscores to dots + numeric_entities_with_underscore = request.form.getlist('numeric_entities') + numeric_entities = [entity.replace('__', '.') for entity in numeric_entities_with_underscore] + if not numeric_entities: + error = "Please select entities" + return render_template('clustering_pl.html', + numeric_tab=True, + all_numeric_entities=all_numeric_entities, + min_max_values=min_max_values, + error=error, + ) + + numeric_standardize = request.form['n_standardize'] == "yes" + numeric_missing = request.form['n_missing'] + min_max_filter = { } + for entity in numeric_entities: + min_max_entity = 'min_max_{}'.format(entity.replace('.', '__')) + if min_max_entity in request.form: + min_value, max_value = list(eval(request.form.get(min_max_entity))) + min_max_filter[entity] = min_value, max_value + + min_max_values[entity] = { + 'min' : min_value, + 'max' : max_value, + 'step': (max_value - min_value) / float(100), + } + + if any([entity for entity in numeric_entities]): + np.random.seed(8675309) # what is this number? + cluster_data, cluster_labels, df, error = dwu.cluster_numeric_fields( + numeric_entities, + rdb, + standardize=numeric_standardize, + missing=numeric_missing, + min_max_filter=min_max_filter, + ) + if error: + return render_template('clustering_pl.html', + numeric_tab=True, + selected_n_entities=numeric_entities, + all_numeric_entities=all_numeric_entities, + min_max_values=min_max_values, + selected_min_max=min_max_filter, + error=error, + ) + table_data = { } + plot_data = [] + + for cluster in sorted(cluster_labels.keys()): + patient_count = cluster_labels[cluster] + patient_percent = "{:.0%}".format(cluster_data.weights_[cluster]) + + table_data[cluster] = { } + table_data[cluster]['patient_count'] = patient_count + table_data[cluster]['patient_percent'] = patient_percent + for i, entity in enumerate(numeric_entities): + mean_value = "{0:.2f}".format(cluster_data.means_[cluster][i]) + table_data[cluster][entity] = mean_value + # filter by cluster + entity_series = df[df.cluster == cluster][numeric_entities].dropna().values.round(2).tolist() + plot_data.append({"name": "Cluster {}".format(cluster), "data": entity_series}) + plot_data.append({ + 'x': list(df[df.cluster == cluster][numeric_entities[0]]), + 'y': list(df[df.cluster == cluster][numeric_entities[1]]), + 'mode': 'markers', + 'type': 'scatter', + "name": "Cluster {}".format(cluster), + }) + + any_present = df.shape[0] + all_present = df.dropna().shape[0] + + return render_template('clustering_pl.html', + numeric_tab=True, + selected_n_entities=numeric_entities, + all_numeric_entities=all_numeric_entities, + any_present=any_present, + all_present=all_present, + table_data=table_data, + plot_data=plot_data, + min_max_values=min_max_values, + selected_min_max=min_max_filter, + ) + + diff --git a/url_handlers/coplots.py b/url_handlers/coplots_pl.py similarity index 69% rename from url_handlers/coplots.py rename to url_handlers/coplots_pl.py index 65d872a2..e7cc9983 100644 --- a/url_handlers/coplots.py +++ b/url_handlers/coplots_pl.py @@ -3,11 +3,11 @@ import data_warehouse.redis_rwh as rwh -coplots_page = Blueprint('coplots', __name__, +coplots_plot_page = Blueprint('coplots_pl', __name__, template_folder='templates') -@coplots_page.route('/coplots', methods=['GET']) +@coplots_plot_page.route('/coplots_pl', methods=['GET']) def get_coplots(): # this import has to be here!! from webserver import get_db @@ -16,12 +16,12 @@ def get_coplots(): all_categorical_entities = rwh.get_categorical_entities(rdb) all_categorical_only_entities = sorted(set(all_categorical_entities) - set(all_numeric_entities)) - return render_template('coplots.html', + return render_template('coplots_pl.html', all_numeric_entities=all_numeric_entities, categorical_entities=all_categorical_only_entities) -@coplots_page.route('/coplots', methods=['POST']) +@coplots_plot_page.route('/coplots_pl', methods=['POST']) def post_coplots(): # this import has to be here!! from webserver import get_db @@ -30,7 +30,6 @@ def post_coplots(): all_categorical_entities = rwh.get_categorical_entities(rdb) all_categorical_only_entities = sorted(set(all_categorical_entities) - set(all_numeric_entities)) - plot_series = [] category1 = request.form.get('category1') category2 = request.form.get('category2') x_axis = request.form.get('x_axis') @@ -60,11 +59,11 @@ def post_coplots(): # get joined categorical values if not error_message: categorical_df, error_message = rwh.get_joined_categorical_values([category1, category2], rdb) - numeric_df, error_message = rwh.get_joined_numeric_values([x_axis, y_axis], rdb) if not error_message else (None, error_message) + numeric_df, error = rwh.get_joined_numeric_values([x_axis, y_axis], rdb) if not error_message else (None, error_message) error_message = "No data based on the selected options" if error_message else None if error_message: - return render_template('coplots.html', + return render_template('coplots_pl.html', all_numeric_entities=all_numeric_entities, categorical_entities=all_categorical_only_entities, error=error_message, @@ -83,35 +82,64 @@ def post_coplots(): numeric_df = numeric_df.dropna() merged_df = pd.merge(numeric_df, categorical_df, how='inner', on='patient_id') + + x_min = merged_df[x_axis].min() if not select_scale else selected_x_min x_max = merged_df[x_axis].max() if not select_scale else selected_x_max y_min = merged_df[y_axis].min() if not select_scale else selected_y_min y_max = merged_df[y_axis].max() if not select_scale else selected_y_max + category1_values = merged_df[category1].unique() category2_values = merged_df[category2].unique() - if how_to_plot == 'single_plot': - plot_series = [] - elif how_to_plot == 'multiple_plots': - plot_series = { } - for cat1_value in category1_values: - for cat2_value in category2_values: + + + count=0 + plot_series=[] + plot_series2 = [] + layout ={} + for i,cat1_value in enumerate(category1_values): + for j,cat2_value in enumerate(category2_values): + count += 1 df = merged_df.loc[(merged_df[category1] == cat1_value) & (merged_df[category2] == cat2_value)].dropna() df.columns = ['patient_id', 'x', 'y', 'cat1', 'cat2'] - series = { - 'name' : '{}_{}'.format(cat1_value, cat2_value), - 'turboThreshold': len(df), - 'data' : list(df.T.to_dict().values()), - 'cat1' : cat1_value, - 'cat2' : cat2_value, - 'series_length' : len(df), - } - if how_to_plot == 'single_plot': - plot_series.append(series) - elif how_to_plot == 'multiple_plots': - plot_series['{}_{}'.format(cat1_value, cat2_value)] = series - - return render_template('coplots.html', + plot_series.append({ + 'x': list(df['x']), + 'y': list(df['y']), + 'mode': 'markers', + 'type': 'scatter', + 'xaxis': 'x{}'.format(count), + 'yaxis': 'y{}'.format(count), + 'name': '{}: {}
{}: {}'.format(category1,cat1_value,category2 ,cat2_value), + 'text': list(df['patient_id']) + }) + plot_series2.append({ + 'x': list(df['x']), + 'y': list(df['y']), + 'mode': 'markers', + 'type': 'scatter', + 'name': '{}: {}
{}: {}'.format(category1, cat1_value, category2, cat2_value), + 'text': list(df['patient_id']) + } + ) + layout.update({ + 'xaxis{}'.format(count): { + 'title': { + 'text': x_axis, + } + }, + 'yaxis{}'.format(count): { + 'title': { + 'text': y_axis, + } + },}) + + + layout.update(title='Compare values of ' + x_axis + ' and ' + y_axis + '') + layout['grid'] = {'rows': len(category1_values), 'columns': len(category2_values), 'pattern': 'independent'} + + + return render_template('coplots_pl.html', all_numeric_entities=all_numeric_entities, categorical_entities=all_categorical_entities, category1=category1, @@ -120,8 +148,10 @@ def post_coplots(): cat2_values=list(category2_values), x_axis=x_axis, y_axis=y_axis, + layout =layout, how_to_plot=how_to_plot, plot_series=plot_series, + plot_series2=plot_series2, select_scale=select_scale, x_min=x_min, x_max=x_max, diff --git a/url_handlers/data_management.py b/url_handlers/data_management.py deleted file mode 100644 index a55076bc..00000000 --- a/url_handlers/data_management.py +++ /dev/null @@ -1,103 +0,0 @@ -import collections -import time -import datetime - -from flask import Blueprint, render_template, request -import csv -import misc.utils as utils - -from data_warehouse.redis_rwh import get_connection - -data_management_page = Blueprint('data_management_page', __name__, - template_folder='flaskr/templates') - - -# entities = { -# 'diagnostik.hauptdiagnose.tumortyp': 'String', -# 'diagnostik.labor.bare_nuclei': 'String', -# 'diagnostik.labor.bland_chromatin': 'String', -# 'diagnostik.labor.clump_thickness': 'String', -# 'diagnostik.labor.marginal_adhesion': 'String', -# 'diagnostik.labor.mitosis': 'String', -# 'diagnostik.labor.normal_nucleoli': 'String', -# 'diagnostik.labor.single_epithelial_cell_size': 'Integer', -# 'diagnostik.labor.uniformity_of_cell_shape': 'String', -# 'diagnostik.labor.uniformity_of_cell_size': 'String', -# } - -@data_management_page.route('/data_management', methods=['POST']) -def manage_data(): - # example of 1 line in csv file: - # fe189ec785f674f8d19f3af063c68413, 7dd8eb5937291aedc698e65581be76ca, 2018-05-22, 10:05:00, diagnostik.labor.uniformity_of_cell_size, 10 - csv_file = request.files['csv_file'] - csv_lines = csv_file.stream.read().decode("utf-8").split('\n') - csv_data = list(csv.reader(csv_lines, delimiter=',')) - - # example of entities file (also in csv format): - # diagnostik.hauptdiagnose.tumortyp, String - # diagnostik.labor.single_epithelial_cell_size, Integer - entities_file = request.files['entities_file'] - entities_lines = entities_file.stream.read().decode("utf-8").split('\n') - entities = dict(csv.DictReader(entities_lines, delimiter=',')) - - # I don't like to hardcode it, we should create a config file - redis_connection = get_connection('localhost', '6379') - pong = redis_connection.ping() - - update_message = pong - number_keys = set() - category_keys = set() - date_keys = set() - category_values = collections.defaultdict(set) - - redis_commands = [] - - for line in csv_data: - (patient_id, quarter_id, date_stamp, time_stamp, key, value) = line - float_value = utils.try_parse_float(value) - entity_type = entities.get(key) - - if float_value is not None: - redis_connection.zadd(key, float_value, patient_id) - redis_commands.append("ZADD {} {} {}".format(key, float_value, patient_id)) - number_keys.add(key) - - elif entity_type == 'String': - kv = "{}.{}".format(key, value) - redis_connection.sadd(kv, patient_id) - redis_commands.append("SADD {} {}".format(kv, patient_id)) - - category_keys.add(key) - category_values[key].add(value) - - elif entity_type == 'null': - value = time.mktime(datetime.datetime.strptime(date_stamp, "%Y-%m-%d").timetuple()) - - value = max(value, 0) - redis_connection.zadd(key, value, patient_id) - redis_commands.append("ZADD {} {} {}".format(key, value, patient_id)) - - date_keys.add(key) - - for number_key in number_keys: - redis_connection.sadd("number_keys", number_key) - - for date_key in date_keys: - redis_connection.sadd("date_keys", date_key) - - for category_key in category_keys: - redis_connection.sadd("category_keys", category_key) - - for category, cv in category_values.items(): - category_key = "{}_values".format(category) - for category_value in cv: - redis_connection.sadd(category_key, category_value) - - # if we got to this point, we did not get any exceptions - update_message = 'Successfully updated' - return render_template('data_management.html', update_message=update_message) - - -@data_management_page.route('/data_management', methods=['GET']) -def get_manage_data(): - return render_template('data_management.html') diff --git a/url_handlers/heatmap.py b/url_handlers/heatmap.py new file mode 100644 index 00000000..b041eaad --- /dev/null +++ b/url_handlers/heatmap.py @@ -0,0 +1,96 @@ +from flask import Blueprint, render_template, request, jsonify +import pandas as pd +from scipy.stats import pearsonr + +import data_warehouse.redis_rwh as rwh + +heatmap_plot_page = Blueprint('heatmap', __name__, + template_folder='tepmlates') + + +@heatmap_plot_page.route('/heatmap', methods=['GET']) +def get_plots(): + # this import has to be here!! + from webserver import get_db + rdb = get_db() + all_numeric_entities = rwh.get_numeric_entities(rdb) + all_categorical_entities = rwh.get_categorical_entities(rdb) + all_categorical_only_entities = sorted(set(all_categorical_entities) - set(all_numeric_entities)) + + return render_template('heatmap.html', + numeric_tab=True, + all_numeric_entities=all_numeric_entities, + all_categorical_entities=all_categorical_only_entities) + + +@heatmap_plot_page.route('/heatmap', methods=['POST']) +# @login_required +def post_plots(): + # this import has to be here!! + from webserver import get_db + rdb = get_db() + all_numeric_entities = rwh.get_numeric_entities(rdb) + all_categorical_entities = rwh.get_categorical_entities(rdb) + all_categorical_only_entities = sorted(set(all_categorical_entities) - set(all_numeric_entities)) + + + + selected_entities = request.form.getlist('numeric_entities') + + if not selected_entities: + error = "Please select entities" + return render_template('heatmap.html', + numeric_tab=True, + all_numeric_entities=all_numeric_entities, + all_categorical_entities=all_categorical_only_entities, + error=error) + + + numeric_df, err = rwh.get_joined_numeric_values(selected_entities, rdb) + if err: + return render_template('heatmap.html', + error=err, + numeric_tab=True, + all_numeric_entities=all_numeric_entities, + all_categorical_entities=all_categorical_only_entities) + + # remove patient id and drop NaN values (this will show only the patients with both values) + numeric_df = numeric_df[selected_entities] + # numeric_df = numeric_df.dropna()[selected_entities] + dfcols = pd.DataFrame(columns=numeric_df.columns) + pvalues = dfcols.transpose().join(dfcols, how='outer') + corr_values = dfcols.transpose().join(dfcols, how='outer') + for r in numeric_df.columns: + for c in numeric_df.columns: + if c == r: + df_corr = numeric_df[[r]].dropna() + else: + df_corr = numeric_df[[r, c]].dropna() + corr_values[r][c], pvalues[r][c] = pearsonr(df_corr[r], df_corr[c]) + + pvalues = pvalues.astype(float) + pvalues = pvalues.round(decimals=3) + pvalues = pvalues.T.values.tolist() + + corr_values = corr_values.astype(float) + corr_values = corr_values.round(decimals=2) + corr_values = corr_values.T.values.tolist() + + + plot_series = [] + plot_series.append({'z': corr_values, + 'x' : selected_entities, + 'y' : selected_entities, + 'type': "heatmap" + }) + + + + return render_template('heatmap.html', + numeric_tab=True, + all_numeric_entities=all_numeric_entities, + all_categorical_entities=all_categorical_only_entities, + selected_n_entities=selected_entities, + plot_series=plot_series + ) + diff --git a/url_handlers/histogram.py b/url_handlers/histogram.py index fabe4e8f..ec15fc73 100644 --- a/url_handlers/histogram.py +++ b/url_handlers/histogram.py @@ -1,6 +1,6 @@ from flask import Blueprint, render_template, request import pandas as pd - +import numpy as np import data_warehouse.redis_rwh as rwh histogram_page = Blueprint('histogram', __name__, @@ -34,37 +34,58 @@ def post_statistics(): if not entity or not group_by or entity == "Choose entity" or group_by == "Choose entity": error = "Please select entity and group_by" + if number_of_bins.isdigit(): + int_number_of_bins = int(number_of_bins) + if int_number_of_bins < 2: + error = "Nuber of bins need to be bigger then 1" + # get joined numerical and categorical values if not error: numeric_df, error = rwh.get_joined_numeric_values([entity], rdb) categorical_df, error = rwh.get_joined_categorical_values([group_by], rdb) if not error else (None, error) if error: - return render_template('histogram.html', categorical_entities=all_categorical_entities, - numeric_entities=all_numeric_entities, selected_entity=entity, group_by=group_by, + return render_template('histogram.html', + categorical_entities=all_categorical_entities, + numeric_entities=all_numeric_entities, + selected_entity=entity, + group_by=group_by, error=error, ) + merged_df = pd.merge(numeric_df, categorical_df, how='inner', on='patient_id') min_val = numeric_df[entity].min() max_val = numeric_df[entity].max() count = categorical_df[group_by].count() adjusted_bins = (max_val - min_val) + + if number_of_bins.isdigit(): int_number_of_bins = int(number_of_bins) - if int_number_of_bins > 0: + if int_number_of_bins > 1: int_number_of_bins = int(number_of_bins) - bin_numbers = (adjusted_bins / int_number_of_bins) + bin_numbers = (adjusted_bins / int_number_of_bins) elif number_of_bins == "": bin_numbers = (adjusted_bins / 20) else: error = "You have entered non-integer or negetive value. Please use positive integer" return render_template('histogram.html', categorical_entities=all_categorical_entities, numeric_entities=all_numeric_entities, - error=error, ) + error=error, ) + + groups = set(categorical_df[group_by].values.tolist()) plot_series = [] + table_data={} for group in groups: df = merged_df.loc[merged_df[group_by] == group] values = df[entity].values.tolist() + if number_of_bins.isdigit(): + hist=np.histogram(values, bins=int(number_of_bins),range = (min_val,max_val)) + else: + hist = np.histogram(values, bins=20, range=(min_val, max_val)) + table_data[group]={} + table_data[group]['count'] =hist[0] + table_data[group]['bin'] = hist[1] if (values): plot_series.append({ 'x' : values, @@ -77,12 +98,13 @@ def post_statistics(): 'start': min_val } }) - return render_template('histogram.html', categorical_entities=all_categorical_entities, numeric_entities=all_numeric_entities, selected_entity=entity, group_by=group_by, + group =groups, + table_data=table_data, plot_series=plot_series, min_val=min_val, max_val=max_val, diff --git a/url_handlers/login.py b/url_handlers/login.py deleted file mode 100644 index a566c473..00000000 --- a/url_handlers/login.py +++ /dev/null @@ -1,59 +0,0 @@ -from flask import Blueprint, render_template, request, redirect -from flask_login import login_user, UserMixin, logout_user - -from passlib.hash import sha256_crypt - - -login_page = Blueprint('login_page', __name__) - - -# creating a custom User class -class User(UserMixin): - def __init__(self, id, email, password): - self.id = id - self.email = email - self.password = password - - @classmethod - def get_user(self, email, password): - from webserver import get_db - rdb = get_db() - encrypted_password = rdb.hget('users', email) or '' - if sha256_crypt.verify(password, encrypted_password): - # todo: how to store ids?? - # for now we use email as an id - return User(email, email, password) - return None - - @classmethod - def get_by_id(self, id): - # todo: implement user by id - # for now get user by email and use email as an id - from webserver import get_db - rdb = get_db() - password = rdb.hget('users', id) or '' - if password: - return User(id, id, password) - return None - - -@login_page.route('/login', methods=['GET', 'POST']) -def login(): - error = None - if request.method == 'POST': - email = request.form['email'] - password = request.form['password'] - user = User.get_user(email, password) - if user is not None: - login_user(user) - return redirect('/basic_stats') - else: - error = "Incorrect username or password" - return render_template('login_page.html', - error=error) - - -@login_page.route('/logout', methods=['GET', 'POST']) -def logout(): - logout_user() - return redirect('/login') diff --git a/url_handlers/logout.py b/url_handlers/logout.py new file mode 100644 index 00000000..20596416 --- /dev/null +++ b/url_handlers/logout.py @@ -0,0 +1,8 @@ +from flask import Blueprint, render_template + +logout_page = Blueprint('logout', __name__, + template_folder='logout') + +@logout_page.route('/logout', methods=['GET', 'POST']) +def logout(): + return render_template('logout.html') diff --git a/url_handlers/plots.py b/url_handlers/plots.py index 313a5a86..cb3a91e1 100644 --- a/url_handlers/plots.py +++ b/url_handlers/plots.py @@ -34,8 +34,6 @@ def post_plots(): all_categorical_entities = rwh.get_categorical_entities(rdb) all_categorical_only_entities = sorted(set(all_categorical_entities) - set(all_numeric_entities)) - plot_series = [] - if 'plot_numeric' in request.form: plot_type = request.form['plot_type'] error = None @@ -49,19 +47,17 @@ def post_plots(): elif plot_type == 'scatter_plot_n': x_axis = request.form.get('x_axis') y_axis = request.form.get('y_axis') - category = request.form.get('category') - add_group_by = request.form.get('add_group_by') is not None - add_separate_regression = request.form.get('add_separate_regression') is not None - - # Check for input errors if not x_axis or not y_axis or x_axis == "Choose entity" or y_axis == "Choose entity": error = "Please select x_axis and y_axis" - elif x_axis == y_axis: + + if x_axis == y_axis: error = "You can't compare the same entity" - elif add_group_by and category == "Choose entity": - error = "Please select a categorical value to group by" + return render_template('plots/plots.html', + error=error, + numeric_tab=True, + all_numeric_entities=all_numeric_entities, + all_categorical_entities=all_categorical_only_entities) - categorical_df, err = rwh.get_joined_categorical_values([category], rdb) numeric_df, err = rwh.get_joined_numeric_values([x_axis, y_axis], rdb) if not error else (None, error) error = err if not error else error @@ -71,56 +67,22 @@ def post_plots(): numeric_tab=True, x_axis=x_axis, y_axis=y_axis, - category=category, plot_type=plot_type, all_numeric_entities=all_numeric_entities, - all_categorical_entities=all_categorical_only_entities, - add_group_by=add_group_by, - add_separate_regression=add_separate_regression) - - if not add_group_by: - plot_series = [] - category_values = [] - # change columns order and drop NaN values (this will show only the patients with both values) - numeric_df = numeric_df.dropna()[[x_axis, y_axis, 'patient_id']] - # rename columns - numeric_df.columns = ['x', 'y', 'patient_id'] - # data_to_plot = list(numeric_df.T.to_dict().values()) - plot_series = list(numeric_df.T.to_dict().values()) - # data_to_plot = numeric_df.values.tolist() - else: - numeric_df = numeric_df.dropna() - categorical_df = categorical_df.dropna() - merged_df = pd.merge(numeric_df, categorical_df, how='inner', on='patient_id') - - # category = merged_df[category] if not add_group_by else category - - category_values = merged_df[category].unique() - - plot_series = [] - for cat_value in category_values: - df = merged_df.loc[(merged_df[category] == cat_value)].dropna() - df.columns = ['patient_id', 'x', 'y', 'cat'] - series = { - 'name' : cat_value, - 'turboThreshold': len(df), - 'data' : list(df.T.to_dict().values()), - 'cat' : cat_value, - 'series_length' : len(df), - } - plot_series.append(series) + all_categorical_entities=all_categorical_only_entities) + # change columns order and drop NaN values (this will show only the patients with both values) + numeric_df = numeric_df.dropna()[[x_axis, y_axis, 'patient_id']] + # rename columns + numeric_df.columns = ['x', 'y', 'patient_id'] + data_to_plot = list(numeric_df.T.to_dict().values()) + # data_to_plot = numeric_df.values.tolist() return render_template('plots/plots.html', numeric_tab=True, all_numeric_entities=all_numeric_entities, all_categorical_entities=all_categorical_only_entities, x_axis=x_axis, y_axis=y_axis, - category=category, - cat_values=list(category_values), - add_group_by=add_group_by, - add_separate_regression=add_separate_regression, - plot_series=plot_series, - # plot_data=data_to_plot, + plot_data=data_to_plot, plot_type=plot_type) elif plot_type == 'heat_map_n': selected_entities = request.form.getlist('numeric_entities') @@ -202,7 +164,6 @@ def post_plots(): if 'plot_categorical' in request.form: plot_type = request.form.get('plot_type') selected_entities = request.form.getlist('categorical_entities') - categorical_df, error = rwh.get_joined_categorical_values(selected_entities, rdb) if selected_entities else (None, "Please select entities") @@ -292,4 +253,4 @@ def get_min_max(entity): 'max' : max_val, 'step': float(max_val - min_val) / 100.0 } - return jsonify(min_max_values) + return jsonify(min_max_values) \ No newline at end of file diff --git a/url_handlers/scatter_plot.py b/url_handlers/scatter_plot.py new file mode 100644 index 00000000..796d5b0a --- /dev/null +++ b/url_handlers/scatter_plot.py @@ -0,0 +1,172 @@ +from flask import Blueprint, render_template, request, jsonify +import numpy as np +import pandas as pd +import json + + +import data_warehouse.redis_rwh as rwh + +scatter_plot_page = Blueprint('scatter_plot', __name__, + template_folder='tepmlates') + + +@scatter_plot_page.route('/scatter_plot', methods=['GET']) +def get_plots(): + # this import has to be here!! + from webserver import get_db + rdb = get_db() + all_numeric_entities = rwh.get_numeric_entities(rdb) + all_categorical_entities = rwh.get_categorical_entities(rdb) + all_categorical_only_entities = sorted(set(all_categorical_entities) - set(all_numeric_entities)) + + return render_template('scatter_plot.html', + numeric_tab=True, + all_numeric_entities=all_numeric_entities, + all_categorical_entities=all_categorical_only_entities) + + +@scatter_plot_page.route('/scatter_plot', methods=['POST']) +def post_plots(): + # this import has to be here!! + from webserver import get_db + rdb = get_db() + all_numeric_entities = rwh.get_numeric_entities(rdb) + all_categorical_entities = rwh.get_categorical_entities(rdb) + all_categorical_only_entities = sorted(set(all_categorical_entities) - set(all_numeric_entities)) + + + y_axis = request.form.get('y_axis') + x_axis = request.form.get('x_axis') + category = request.form.get('category') + add_group_by = request.form.get('add_group_by') is not None + add_separate_regression = request.form.get('add_separate_regression') is not None + + + error = None + if not x_axis or not y_axis or x_axis == "Choose entity" or y_axis == "Choose entity": + error = "Please select x_axis and y_axis" + elif x_axis == y_axis: + error = "You can't compare the same entity" + elif add_group_by and category == "Choose entity": + error = "Please select a categorical value to group by" + elif add_group_by and category: + categorical_df, error = rwh.get_joined_categorical_values([category], rdb) + error = "No data based on the selected entities ( " + ", ".join([category]) + " ) " if error else None + + numeric_df, error = rwh.get_joined_numeric_values([x_axis, y_axis], rdb) if not error else (None, error) + + if error: + return render_template('scatter_plot.html', + error=error, + numeric_tab=True, + x_axis=x_axis, + y_axis=y_axis, + category=category, + all_numeric_entities=all_numeric_entities, + all_categorical_entities=all_categorical_only_entities, + add_group_by=add_group_by, + add_separate_regression=add_separate_regression) + + + i=0 + if not add_group_by: + i+=1 + plot_series = [] + # change columns order and drop NaN values (this will show only the patients with both values) + numeric_df = numeric_df.dropna()[[x_axis, y_axis, 'patient_id']] + # rename columns + numeric_df.columns = ['x', 'y', 'patient_id'] + # data_to_plot = list(numeric_df.T.to_dict().values()) + # fit lin to data to plot + m, b = np.polyfit(np.array(numeric_df['x']), np.array(numeric_df['y']), 1) + bestfit_y = (np.array(numeric_df['x']) * m + b) + + plot_series.append({ + 'x': list(numeric_df['x']), + 'y': list(numeric_df['y']), + 'mode': 'markers', + 'type': 'scatter', + 'name' : 'Patients', + 'text': list(numeric_df['patient_id']), + }) + + + + plot_series.append({ + 'x': list(numeric_df['x']), + 'y': list(bestfit_y), + 'type': 'scatter', + 'name' : 'Linear regression:
(y={0:.2f}x + {1:.2f})'.format(m, b) + }) + else: + numeric_df = numeric_df.dropna() + categorical_df = categorical_df.dropna() + merged_df = pd.merge(numeric_df, categorical_df, how='inner', on='patient_id') + + category_values = merged_df[category].unique() + + plot_series = [] + for cat_value in category_values: + + colorGen = [ 'rgb(31, 119, 180)', 'rgb(255, 127, 14)', + 'rgb(44, 160, 44)', 'rgb(214, 39, 40)', + 'rgb(148, 103, 189)', 'rgb(140, 86, 75)', + 'rgb(227, 119, 194)', 'rgb(127, 127, 127)', + 'rgb(188, 189, 34)', 'rgb(23, 190, 207)'] + + df = merged_df.loc[(merged_df[category] == cat_value)].dropna() + df.columns = ['patient_id', 'x', 'y', 'cat'] + # fit lin to data to plot + m, b = np.polyfit(np.array(df['x']), np.array(df['y']), 1) + bestfit_y = (np.array(df['x']) * m + b) + i += 1 + + plot_series.append({ + 'x': list(df['x']), + 'y': list(df['y']), + 'mode': 'markers', + 'type': 'scatter', + 'name': cat_value, + 'text': list(df['patient_id']), + 'marker' : {'color': colorGen[i]} + }) + + + plot_series.append({ + 'x': list(df['x']), + 'y': list(bestfit_y), + 'type': 'scatter', + 'name' : 'Linear regression {0}:
(y={1:.2f}x + {2:.2f})'.format(cat_value, m, b), + 'mode' : 'lines', + 'line' : {'color' : colorGen[i]} + }) + + + + return render_template('scatter_plot.html', + numeric_tab=True, + all_numeric_entities=all_numeric_entities, + all_categorical_entities=all_categorical_only_entities, + x_axis=x_axis, + y_axis=y_axis, + add_group_by=add_group_by, + add_separate_regression=add_separate_regression, + plot_series=plot_series) + + + + + + + + + + + + + + + + + + diff --git a/webserver.py b/webserver.py index a7405fb6..0b34dce5 100644 --- a/webserver.py +++ b/webserver.py @@ -1,94 +1,46 @@ -import misc.mpl_utils as mpl_utils -import data_warehouse.data_warehouse_utils as dwu -import misc.utils as utils - -from flask import Flask, session, g, redirect, url_for, render_template, flash -from flask_login import LoginManager +# import the Flask class from the flask module +from flask import Flask, session, g, redirect, flash from flask_redis import FlaskRedis -# I don't like the idea of handling all the urls in the same file -# I'll put all the new urls in the 'url_handlers' directory (one file for each new url) -# then can import it here and register (below) as a Blueprint: http://flask.pocoo.org/docs/1.0/blueprints/ -from url_handlers.data_management import data_management_page +# Urls in the 'url_handlers' directory (one file for each new url) +# import a Blueprint + from url_handlers.basic_stats import basic_stats_page -from url_handlers.plots import plots_page -from url_handlers.clustering import clustering_page -from url_handlers.login import login_page from url_handlers.histogram import histogram_page from url_handlers.boxplot import boxplot_page -from url_handlers.coplots import coplots_page - -from url_handlers.login import User +from url_handlers.scatter_plot import scatter_plot_page +from url_handlers.barchart import barchart_page +from url_handlers.heatmap import heatmap_plot_page +from url_handlers.clustering_pl import clustering_plot_page +from url_handlers.coplots_pl import coplots_plot_page +from url_handlers.logout import logout_page import os - -### -# Images -### -from flask import make_response, request -from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas - -import matplotlib.pyplot as plt - -import flask -import io -import matplotlib.backends.backend_agg - -import seaborn as sns - -sns.set(style='whitegrid') - -# from: https://arusahni.net/blog/2014/03/flask-nocache.html -from functools import wraps, update_wrapper -from datetime import datetime - from modules.import_scheduler import Scheduler -import atexit - +# create the application object app = Flask(__name__) -app.config.from_object(__name__) - -app.config.update(dict( - SECRET_KEY='development key', - USERNAME='admin', - PASSWORD='default' - )) -app.config.from_envvar('FLASKR_SETTINGS', silent=True) # register blueprints here: -app.register_blueprint(data_management_page) + +app.register_blueprint(logout_page) app.register_blueprint(basic_stats_page) -app.register_blueprint(plots_page) -app.register_blueprint(clustering_page) -app.register_blueprint(login_page) app.register_blueprint(histogram_page) app.register_blueprint(boxplot_page) -app.register_blueprint(coplots_page) - -# login manager -login_manager = LoginManager() -login_manager.init_app(app) - +app.register_blueprint(scatter_plot_page) +app.register_blueprint(barchart_page) +app.register_blueprint(heatmap_plot_page) +app.register_blueprint(clustering_plot_page) +app.register_blueprint(coplots_plot_page) -# don't understand why we need this -@login_manager.user_loader -def load_user(user_id): - return User.get_by_id(user_id) - - -# @app.context_processor -# def inject_enumerate(): -# return dict(enumerate=enumerate) - -### -# Database stuff -### +# Connection with database def connect_db(): """ connects to our redis database """ - app.config["REDIS_URL"] = os.environ["REDIS_URL"] # || "redis://docker.for.mac.localhost:6379/0" - redis_store = FlaskRedis() - redis_store.init_app(app) + # Set this connection URL in an environment variable, and then load it into your application configuration using + # os.environ, like this + app.config["REDIS_URL"] = os.environ["REDIS_URL"] + # To add a Redis client to your application + redis_store = FlaskRedis(app) return redis_store @@ -103,486 +55,20 @@ def get_db(): @app.teardown_appcontext def close_db(error): - """ close the database, or whatever, on exit """ - pass - - -def init_db(): - db = get_db() - return - - # we can use app.open_resource to grab something from the main - # folder (flaskr, here) - with app.open_resource('schema.sql', mode='r') as f: - pass - + """Closes the database again at the end of the request.""" + if hasattr(g, 'redis_db'): + g.redis_db.close() -@app.cli.command('initdb') -def initdb_command(): - """ this function is associated with the "initdb" command of the - "flask" script - """ - init_db() +""" Direct to Basic Stats website during opening the program.""" @app.route('/', methods=['GET']) -def root_route(): - return redirect('/basic_stats') - - -### -# Security -### -@app.route('/login', methods=['GET', 'POST']) def login(): - error = None - if request.method == 'POST': - if request.form['username'] != app.config['USERNAME']: - error = "Invalid username" - elif request.form['password'] != app.config['PASSWORD']: - error = "Invalid password" - else: - session['logged_in'] = True - flash("You were logged in") - return redirect(url_for('show_entities')) - return render_template('login_page.html', error=error) - - -# @app.route('/login', methods=['POST']) -# def login(): -# error = None -# if request.method == 'POST': -# if request.form['email'] != app.config['USERNAME']: -# error = "Login error: Invalid username" -# elif request.form['password'] != app.config['PASSWORD']: -# error = "Login error: Invalid password" -# else: -# session['logged_in'] = True -# flash("You were logged in") -# # todo: error message -# return redirect(request.referrer) - - -@app.route('/logout') -def logout(): - session.pop('logged_in', None) - flash("You were logged out") - - -# return redirect(request.referrer) - -# @app.route('/a', methods=['GET','POST']) -# def show_cluster_numeric_entities(): -# r = get_db() -# all_numeric_entities = rwh.get_numeric_entities(r) -# all_categorical_entities = rwh.get_categorical_entities(r) -# -# all_categorical_only_entities = set(all_categorical_entities) - set(all_numeric_entities) -# all_categorical_only_entities = sorted(all_categorical_only_entities) -# -# # numeric clustering -# numeric_m = None -# numeric_label_uses = None -# numeric_entities = None -# numeric_cluster_image = None -# numeric_standardize=None -# numeric_missing=None -# -# # categorical clustering -# cluster_category_values = None -# categorical_label_uses = None -# categorical_entities = None -# category_values = None -# categorical_cluster_image = None -# -# # counting -# counts = None -# all_present = None -# any_present = None -# -# if 'cluster_numeric' in request.form: -# numeric_entities = request.form.getlist('numeric_entities') -# numeric_standardize = request.form['standardize'] == "yes" -# numeric_missing = request.form['missing'] -# if any([entitiy for entitiy in numeric_entities]): -# np.random.seed(8675309) -# cluster_info = dwu.cluster_numeric_fields( -# numeric_entities, -# r, -# standardize=numeric_standardize, -# missing=numeric_missing -# ) -# -# numeric_m, numeric_label_uses, df = cluster_info -# any_present = df.shape[0] -# all_present = df.dropna().shape[0] -# numeric_cluster_image = True -# -# elif 'count_numeric' in request.form: -# numeric_entities = request.form.getlist('numeric_entities') -# if any([numeric_entitiy for numeric_entitiy in numeric_entities]): -# numeric_df = rwh.get_joined_numeric_values(numeric_entities, r) -# numeric_df = numeric_df[numeric_entities] -# counts = numeric_df.count() -# any_present = numeric_df.shape[0] -# all_present = numeric_df.dropna().shape[0] -# -# elif 'count_categorical' in request.form: -# categorical_entities = request.form.getlist('categorical_entities') -# -# if any([entitiy for entitiy in categorical_entities]): -# categorical_df = rwh.get_joined_categorical_values(categorical_entities, r) -# # -# categorical_df = categorical_df[categorical_entities] -# -# counts = categorical_df.count() -# any_present = categorical_df.shape[0] -# all_present = categorical_df.dropna().shape[0] -# -# elif 'cluster_categorical' in request.form: -# categorical_entities = request.form.getlist('categorical_entities') -# if any([entitiy for entitiy in categorical_entities]): -# eps = 0.15 -# min_samples = 10 -# -# np.random.seed(8675309) -# -# cluster_info = dwu.cluster_categorical_entities( -# categorical_entities, -# r, -# eps=eps, -# min_samples=min_samples -# ) -# -# ccv, cat_rep_np, category_values, categorical_label_uses, cat_df = cluster_info -# cluster_category_values = ccv -# any_present = cat_df.shape[0] -# all_present = cat_df.dropna().shape[0] -# -# categorical_cluster_image = True -# -# return render_template('statistics.html', -# all_numeric_entities=all_numeric_entities, -# all_categorical_entities=all_categorical_only_entities, -# numeric_m=numeric_m, -# numeric_label_uses=numeric_label_uses, -# numeric_entities=numeric_entities, -# numeric_cluster_image=numeric_cluster_image, -# numeric_standardize=numeric_standardize, -# numeric_missing=numeric_missing, -# cluster_category_values=cluster_category_values, -# categorical_label_uses=categorical_label_uses, -# categorical_entities=categorical_entities, -# category_values=category_values, -# categorical_cluster_image=categorical_cluster_image, -# counts=counts, -# any_present=any_present, -# all_present=all_present -# ) - - -def send_image(fig): - fig.tight_layout() - canvas = matplotlib.backends.backend_agg.FigureCanvas(fig) - img = io.BytesIO() - fig.savefig(img) - img.seek(0) - return flask.send_file(img, mimetype='image/png') - - -def nocache(view): - @wraps(view) - def no_cache(*args, **kwargs): - response = make_response(view(*args, **kwargs)) - response.headers['Last-Modified'] = datetime.now() - response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0' - response.headers['Pragma'] = 'no-cache' - response.headers['Expires'] = '-1' - return response - - return update_wrapper(no_cache, view) - - -@app.route('/categorical_cluster_image/') -@nocache -def categorical_cluster_image(entities): - import numpy as np - import pandas as pd - import sklearn.preprocessing - - r = get_db() - - np.random.seed(8675309) - - categorical_entities = entities.split(",") - - cluster_info = dwu.cluster_categorical_entities( - categorical_entities, - r - ) - - ccv, cat_rep_np, category_values, categorical_label_uses, cat_df, error = cluster_info - if error: - # TODO: check if this affects anything - return None - ccv_df = pd.DataFrame(ccv) - - # this should all be pulled into a function - patient_count_field = "Scaled patient count" - - ccv_np = ccv_df.values - scaled_ccv_np = sklearn.preprocessing.normalize(ccv_np, norm="l1") - scaled_ccv_pivot_np = scaled_ccv_np.transpose() - - ccv_pivot_df = pd.DataFrame(scaled_ccv_pivot_np) - ccv_pivot_df.columns = ccv_df.index - ccv_pivot_df.index = ccv_df.columns - - cols_to_visualize = list(ccv_pivot_df.columns) + [patient_count_field] - - label_uses_df = utils.dict_to_dataframe(categorical_label_uses, key_name="cluster", value_name="count") - - label_counts = label_uses_df['count'] - label_counts = label_counts.values.reshape(1, -1) - label_uses_df[patient_count_field] = sklearn.preprocessing.normalize(label_counts, norm="l1")[0] - - ccv_pivot_merge_df = ccv_pivot_df.merge(label_uses_df, left_index=True, right_on='cluster') - ccv_pivot_merge_df.index = ccv_pivot_merge_df['cluster'] - - df = ccv_pivot_merge_df[cols_to_visualize] - - new_df = pd.DataFrame(df.values.transpose()) - new_df.columns = df.index - new_df.index = df.columns - m_zero = new_df == 0 - - # create the image - - fig, ax = plt.subplots(figsize=(25, 10)) - fontsize = 30 - - vmax = 1 - sns.heatmap( - new_df, - cmap="Blues", - ax=ax, - vmin=0, - vmax=vmax, - mask=m_zero - ) - - mpl_utils.set_ticklabel_rotation(ax, 0, axis='y') - mpl_utils.set_ticklabels_fontsize(ax, fontsize) - mpl_utils.set_title_fontsize(ax, fontsize) - mpl_utils.set_label_fontsize(ax, fontsize) - - ax.xaxis.tick_top() - ax.xaxis.set_label_position('top') - - # get the colorbar, as well - cax = plt.gcf().axes[-1] - mpl_utils.set_ticklabels_fontsize(cax, fontsize) - - return send_image(fig) - - -@app.route('/patient_categorical_cluster_image/') -@nocache -def patient_categorical_cluster_image(entities): - import numpy as np - import sklearn.manifold - - r = get_db() - - eps = 0.15 - min_samples = 10 - - np.random.seed(8675309) - - categorical_entities = entities.split(",") - - cluster_info = dwu.cluster_categorical_entities( - categorical_entities, - r, - eps=eps, - min_samples=min_samples - ) - - ccv, cat_rep_np, category_values, categorical_label_uses, cat_df, error = cluster_info - if error: - # TODO: check if this affects anything - return None - - np.random.seed(8675309) - tsne = sklearn.manifold.TSNE(n_components=2) - np.set_printoptions(suppress=True) - projection = tsne.fit_transform(cat_rep_np) - - fig, ax = plt.subplots() # figsize=(15,15)) - - # Black removed and is used for noise instead. - unique_labels = sorted(cat_df['cluster'].unique()) - colors = plt.cm.Spectral(np.linspace(0, 1, len(unique_labels))) - for k, col in zip(unique_labels, colors): - if k == -1: - # Black used for noise. - col = 'k' - - m_cluster = np.array(cat_df['cluster'] == k) - - xy = projection[m_cluster] - ax.plot( - xy[:, 0], - xy[:, 1], - 'o', - markerfacecolor=col, - markeredgecolor='k', - markersize=8, - markeredgewidth=1, - label=str(k) - ) - - ax.legend(loc='best') - - return send_image(fig) - - -@app.route('/numeric_cluster_image/', methods=['GET']) -@nocache -def numeric_cluster_image(entities): - import numpy as np - import pandas as pd - import sklearn.preprocessing - - r = get_db() - - numeric_entities = entities.split(",") - standardize = request.args.get('standardize') - missing = request.args.get('missing') - - np.random.seed(8675309) - cluster_info = dwu.cluster_numeric_fields( - numeric_entities, - r, - standardize=standardize, - missing=missing - ) - - numeric_m, numeric_label_uses, patient_df, error = cluster_info - if error: - # TODO: check if this affects anything - return None - - - # this should be a function - X = patient_df[numeric_entities].values - X = sklearn.preprocessing.scale(X) - - scaled_df = pd.DataFrame(X) - scaled_df.columns = numeric_entities - scaled_df.index = patient_df.index - scaled_df['cluster'] = patient_df['cluster'] - - tidy_scaled_df = pd.melt(scaled_df, id_vars=['cluster'], value_name="Scaled value") - tidy_scaled_df['variable'] = dwu.clean_entity_names(tidy_scaled_df['variable']) - - # m_outlier = tidy_scaled_df['cluster'] == 2 - # tidy_scaled_no_outlier = tidy_scaled_df[~m_outlier] - - # create the plot - viz_df = tidy_scaled_df - fontsize = 20 - - num_clusters = len(viz_df['cluster'].unique()) - - g = sns.factorplot( - x="variable", - y="Scaled value", - col="cluster", - col_wrap=3, - # row="variable", - data=viz_df, - kind='violin', - sharey=False, - # size=5, - aspect=num_clusters / 5 - ) - - g.set(ylim=(-10, 10)) - - for ax in g.axes.flat: - mpl_utils.set_title_fontsize(ax, fontsize) - mpl_utils.set_ticklabels_fontsize(ax, fontsize) - - # g.set_xticklabels(fontsize=fontsize) - # g.set_yticklabels(fontsize=fontsize) - g.set_xlabels(fontsize=fontsize) - g.set_ylabels(fontsize=fontsize) - - g.fig.tight_layout() - - return send_image(g.fig) - - -@app.route('/patient_numeric_cluster_image/', methods=['GET']) -@nocache -def patient_numeric_cluster_image(entities): - import numpy as np - import sklearn.manifold - - r = get_db() - - numeric_entities = entities.split(",") - standardize = request.args.get('standardize') - missing = request.args.get('missing') - - np.random.seed(8675309) - cluster_info = dwu.cluster_numeric_fields( - numeric_entities, - r, - standardize=standardize, - missing=missing - ) - - numeric_m, numeric_label_uses, patient_df, error = cluster_info - if error: - # TODO: check if this affects anything - return None - - - np.random.seed(8675309) - tsne = sklearn.manifold.TSNE(n_components=2) - np.set_printoptions(suppress=True) - projection = tsne.fit_transform(patient_df[numeric_entities]) - - fig, ax = plt.subplots() # figsize=(15,15)) - - # Black removed and is used for noise instead. - unique_labels = sorted(patient_df['cluster'].unique()) - colors = plt.cm.Spectral(np.linspace(0, 1, len(unique_labels))) - for k, col in zip(unique_labels, colors): - if k == -1: - # Black used for noise. - col = 'k' - - m_cluster = np.array(patient_df['cluster'] == k) - xy = projection[m_cluster] - ax.plot( - xy[:, 0], - xy[:, 1], - 'o', - markerfacecolor=col, - markeredgecolor='k', - markersize=8, - markeredgewidth=1, - label=str(k) - ) - - ax.legend(loc='best') + return redirect('/basic_stats') - return send_image(fig) +# Import data to redis def check_for_env(key: str, default=None, cast=None): if key in os.environ: if cast: @@ -590,20 +76,17 @@ def check_for_env(key: str, default=None, cast=None): return os.environ.get(key) return default - +# date and hours to import data day_of_week = check_for_env('IMPORT_DAY_OF_WEEK', default='mon-sun') hour = check_for_env('IMPORT_HOUR', default=5) minute = check_for_env('IMPORT_MINUTE', default=5) + +# Import data using function scheduler from package modules if os.environ.get('IMPORT_DISABLED') is None: scheduler = Scheduler(day_of_week=day_of_week, hour=hour, minute=minute) scheduler.start() - - -@atexit.register -def exit(): scheduler.stop() - def main(): - return app + return app \ No newline at end of file