diff --git a/.dockerignore b/.dockerignore index bd234b75..10525905 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,25 +1,31 @@ -results -scr -old* -keys -logs -data -backup* -test_run -htmlcov -docs -bulma -scratch +!bin/*.tar.bz2 *.override.yml -*__pycache__* -*.swp +*.pem *.swo -frontend/tests/keys -frontend/tests/scr -frontend/tests/results +*.swp +*__pycache__* .git +backup* bin/* -!bin/*.tar.bz2 -gcloud.sh +bulma +data +docs frontend/cloud_config.py -*.pem +frontend/tests/keys +frontend/tests/results +frontend/tests/scr +gcloud.sh +htmlcov +keys +logs +npm_modules/node_modules +npm_packages/css +npm_packages/css-dist +npm_packages/js-dist +npm_packages/node_modules +old* +results +scr +scratch +test_run +firebase diff --git a/.gitignore b/.gitignore index 2b411bdf..0a7acc78 100755 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,34 @@ +!.github +!bin/*.tar.bz2 +*.override.yml +*.pem *.pyc -*__pycache__ +*.sqlite3 *.swp -calcus/*settings* +*__pycache__ *keys/ -*scr/ *results/ -static/CACHE/ -frontend/migrations/ -*.sqlite3 +*scr/ +scratch/ .* -!.github -*.override.yml -htmlcov/ -old/ -data/ -logs/ backup* -test_run -docs/build/ -celerybeat-schedule bin/* -!bin/*.tar.bz2 -gcloud.sh -frontend/cloud_config.py +calcus/*settings* +celerybeat-schedule +data/ +docs/build/ frontend/cloud_config.json -*.pem +frontend/cloud_config.py +frontend/migrations/ +gcloud.sh +htmlcov/ +logs/ +npm_packages/css +npm_packages/css-dist +npm_packages/js-dist +npm_packages/node_modules +old/ +static/CACHE/ +static/testimonials +test_run +firebase diff --git a/Cloud_dockerfile b/Cloud_dockerfile index 6171ad0f..ec0853e6 100644 --- a/Cloud_dockerfile +++ b/Cloud_dockerfile @@ -8,7 +8,7 @@ ENV CALCUS_CLOUD True ADD ./cloud_requirements.txt /calcus/cloud_requirements.txt RUN pip install -r /calcus/cloud_requirements.txt -RUN apt update && apt install openbabel postgresql-client dos2unix -y +RUN apt update && apt install openbabel postgresql-client dos2unix nwchem -y COPY calcus /calcus/calcus COPY frontend /calcus/frontend diff --git a/Dockerfile b/Dockerfile index 531c035a..53c4308b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9 AS calcus_user +FROM python:3.9-bullseye AS calcus_user ARG CALCUS_VERSION_HASH ENV CALCUS_VERSION_HASH=${CALCUS_VERSION_HASH} @@ -20,7 +20,10 @@ ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"/binaries/orca" ENV PATH=$PATH:$XTB4STDAHOME/xtb/bin:$XTB4STDAHOME:$EBROOTORCA:$GAUSS_EXEDIR ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/binaries/orca:/usr/lib/openmpi/ -RUN apt update && apt install openbabel sshpass postgresql-client dos2unix openmpi-bin -y +RUN apt update && apt install openbabel sshpass postgresql-client dos2unix python3-dev gfortran mpi-default-bin mpi-default-dev -y +RUN curl -LJO https://github.com/nwchemgit/nwchem/releases/download/v7.2.0-release/nwchem-data_7.2.0-2_all.debian_bullseye.deb +RUN curl -LJO https://github.com/nwchemgit/nwchem/releases/download/v7.2.0-release/nwchem_7.2.0-2_amd64.debian_bullseye.deb +RUN dpkg -i nwchem*7.2.0*.deb ADD ./requirements.txt /calcus/requirements.txt RUN pip install -r /calcus/requirements.txt diff --git a/README.md b/README.md index 5f0b52ea..0173a042 100644 --- a/README.md +++ b/README.md @@ -72,12 +72,13 @@ If you use CalcUS in your research, please cite it: **Contributor of significant features**: -+ Divyam Agarwal (automated multi-step calculations) ++ Divyam Agarwal (@div-yam): automated multi-step calculations (partially completed) **Contributors to the web frontend**: -+ Glen Forcha -+ Dhairya Khanna ++ Glen Forcha (@ForchaGlen) ++ Dhairya Khanna (@Dhairya3124) ++ Omkar (@Omkar0803) **Beta-testers**: diff --git a/bin/xtb_suite.tar.bz2 b/bin/xtb_suite.tar.bz2 index 848effbd..126c4681 100644 Binary files a/bin/xtb_suite.tar.bz2 and b/bin/xtb_suite.tar.bz2 differ diff --git a/calcus/settings.py b/calcus/settings.py index 732407c2..79f135ae 100755 --- a/calcus/settings.py +++ b/calcus/settings.py @@ -67,8 +67,6 @@ stripe.api_key = STRIPE_SECRET_KEY -SUBSCRIPTION_ACADEMIC_MONTHLY = os.getenv("SUBSCRIPTION_ACADEMIC_MONTHLY", "") - # On Google Cloud, the URI of the secret containing the PostgreSQL password (for compute instances) POSTGRES_SECRET_URI = os.getenv("POSTGRES_SECRET_URI", "") @@ -76,21 +74,24 @@ POSTGRES_HOST = os.environ.get("POSTGRES_HOST", "postgres") POSTGRES_USER = os.environ.get("POSTGRES_USER", "calcus") +""" GMAIL_API_CLIENT_ID = os.getenv("CALCUS_EMAIL_ID", "") GMAIL_API_CLIENT_SECRET = os.getenv("CALCUS_EMAIL_SECRET", "") GMAIL_API_REFRESH_TOKEN = os.getenv("CALCUS_EMAIL_TOKEN", "") +""" ALLOWED_HOSTS = [ "0.0.0.0", "0.0.0.0:*", "https://calcus.cloud", + "https://static.calcus.cloud", "calcus.cloud", - "localhost", + "static.calcus.cloud" "localhost", "cloud-compute", "cloud-compute:*", ] -CSRF_TRUSTED_ORIGINS = ["calcus.cloud"] +CSRF_TRUSTED_ORIGINS = ["calcus.cloud", "static.calcus.cloud"] if "CALCUS_CLOUD_INTERNAL" in os.environ: ALLOWED_HOSTS.append("*") @@ -116,14 +117,15 @@ "django.contrib.sitemaps", "axes", "bulma", - "gmailapi_backend", + # "gmailapi_backend", "corsheaders", #'debug_toolbar', ] -if IS_CLOUD: +if IS_CLOUD or IS_TEST: INSTALLED_APPS.append("captcha") +if IS_CLOUD: if not DEBUG: SECURE_SSL_REDIRECT = True SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") @@ -164,6 +166,8 @@ "https://optimize.google.com", "https://fonts.googleapis.com", "https://storage.googleapis.com", + "https://static.calcus.cloud", + "https://www.static.calcus.cloud", ] HASHID_FIELD_SALT = os.getenv("CALCUS_HASHID_SALT", "test_salt") @@ -259,7 +263,15 @@ EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend" EMAIL_FILE_PATH = os.path.join(BASE_DIR, "scratch", "sent_emails") else: - EMAIL_BACKEND = "gmailapi_backend.mail.GmailBackend" + # EMAIL_BACKEND = "gmailapi_backend.mail.GmailBackend" + EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" + EMAIL_HOST = "smtp.gmail.com" + EMAIL_PORT = 587 + EMAIL_USE_TLS = True + EMAIL_USE_SSL = False + EMAIL_HOST_USER = os.getenv("CALCUS_EMAIL_USER") + EMAIL_HOST_PASSWORD = os.getenv("CALCUS_EMAIL_PASSWORD") + MEDIA_URL = "/media/" @@ -300,7 +312,7 @@ }, "subscriber": { "nproc": 8, - "time": 120, + "time": 6 * 60, }, } @@ -308,6 +320,11 @@ TRIAL_DEFAULT_COMP_SECONDS = 600 FREE_DEFAULT_COMP_SECONDS = 3600 +# These allocation amounts shouldn't be used as official reference anymore. +# The real amounts are parsed from Stripe. These are for testing +SUBSCRIBER_COMP_SECONDS = 3600 * 60 +SUBSCRIBER_TEAM_COMP_SECONDS = 3600 * 500 + if IS_CLOUD: PING_SATELLITE = False @@ -325,14 +342,14 @@ ALLOW_TRIAL = True - LOCAL_MAX_ATOMS = 200 + LOCAL_MAX_ATOMS = 50000 LOCAL_ALLOWED_THEORY_LEVELS = [ "xtb", # "semiempirical", - # "hf", + "hf", # "special", # hf3c, pbeh3c, r2scan3c, b973c - # "dft", + "dft", # "mp2", # "cc", ] @@ -340,15 +357,17 @@ LOCAL_ALLOWED_STEPS = [ "Geometrical Optimisation", "Conformational Search", + "Fast Conformational Search", "Constrained Optimisation", "Frequency Calculation", "TS Optimisation", "UV-Vis Calculation", "Single-Point Energy", - # "Minimum Energy Path", + "Minimum Energy Path", "Constrained Conformational Search", # "NMR Prediction", - # "MO Calculation", + "MO Calculation", + "ESP Calculation", ] else: diff --git a/calcus/sitemaps.py b/calcus/sitemaps.py index f7a3439a..37e7bba3 100644 --- a/calcus/sitemaps.py +++ b/calcus/sitemaps.py @@ -7,7 +7,16 @@ class CalcUSSitemap(sitemaps.Sitemap): changefreq = "monthly" def items(self): - return ["frontend:home", "frontend:pricing"] + return [ + ("frontend:home", {}), + ("frontend:pricing", {}), + ("frontend:register", {}), + ("frontend:login", {}), + ("frontend:start_trial", {}), + ("frontend:learn", {}), + ("frontend:learn_keyword", {"keyword": "conformational_search"}), + ] - def location(self, item): - return reverse(item) + def location(self, item_data): + item, kwargs = item_data + return reverse(item, kwargs=kwargs) diff --git a/calcus/urls.py b/calcus/urls.py index 757fd1ac..9abdd123 100755 --- a/calcus/urls.py +++ b/calcus/urls.py @@ -27,6 +27,7 @@ from django.contrib.sitemaps.views import sitemap from .sitemaps import CalcUSSitemap +from .views import privacy sitemaps = { "static": CalcUSSitemap, @@ -34,6 +35,7 @@ urlpatterns = [ path("", include("frontend.urls", namespace="frontend")), + path("privacy/", privacy), path("accounts/", include("django.contrib.auth.urls")), path( "sitemap.xml", diff --git a/calcus/views.py b/calcus/views.py index b727e50b..b09440c4 100755 --- a/calcus/views.py +++ b/calcus/views.py @@ -45,3 +45,7 @@ class LogoutView(TemplateView): def get(self, request, **kwargs): logout(request) return render(request, self.template_name) + + +def privacy(request): + return render(request, "registration/privacy.html") diff --git a/cloud_requirements.txt b/cloud_requirements.txt index 04e0eedf..ac4a6089 100644 --- a/cloud_requirements.txt +++ b/cloud_requirements.txt @@ -1,6 +1,6 @@ basis-set-exchange bleach -ccinput>=1.7.1 +ccinput>=1.9.1 cryptography Django>=3.2.18,<4 django-appconf==1.0.4 @@ -23,8 +23,11 @@ periodictable psutil==5.7.2 psycopg2 pysisyphus +rdkit requests scipy +scikit-learn +spyrmsd stripe wheel xkcdpass diff --git a/dev-compose.yml b/dev-compose.yml index 2c0054f3..cb376063 100644 --- a/dev-compose.yml +++ b/dev-compose.yml @@ -45,7 +45,7 @@ services: context: . dockerfile: Dockerfile target: calcus_user - user: calcus + user: "${UID}:${GID}" command: ./scripts/run_celery_main.sh volumes: - ./scr:/calcus/scr @@ -61,7 +61,7 @@ services: context: . dockerfile: Dockerfile target: calcus_user - user: calcus + user: "${UID}:${GID}" shm_size: 1gb command: ./scripts/run_celery_comp.sh volumes: @@ -78,7 +78,7 @@ services: context: . dockerfile: Dockerfile target: calcus_user - user: calcus + user: "${UID}:${GID}" command: ./scripts/run_cluster_daemon.sh env_file: - ./.env diff --git a/docker-compose.yml b/docker-compose.yml index 71177eb2..49a417e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,7 +40,7 @@ services: - web celery_main: image: raphaelrobidas/calcus2:latest - user: calcus + user: "${UID}:${GID}" command: ./scripts/run_celery_main.sh volumes: - ./scr:/calcus/scr @@ -54,7 +54,7 @@ services: - redis celery_comp: image: raphaelrobidas/calcus2:latest - user: calcus + user: "${UID}:${GID}" command: ./scripts/run_celery_comp.sh volumes: - ./scr:/calcus/scr diff --git a/docs/source/software-packages.rst b/docs/source/software-packages.rst index 50e7ef35..218b6849 100644 --- a/docs/source/software-packages.rst +++ b/docs/source/software-packages.rst @@ -70,7 +70,7 @@ The Hamiltonian can be chosen with ``--gfn 2`` (default), ``--gfn 1``, ``--gfn 0 The convergence criteria of geometrical optimisations can be chosen with ``--opt level``, where level is ``crude``, ``sloppy``, ``loose``, ``lax``, ``normal``, ``tight`` (default), ``vtight`` or ``extreme``. -Conformational searches (constrained or not) have several particular options. Faster/cruder sampling procedures can be requested with ``--quick``, ``--squick`` and ``--mquick``. The RMSD threshold for considering conformers as different can be set with ``--rthr X``, where ``X`` is the threshold in Ångström (default of 0.6 in CalcUS). Furthermore, the energy window to consider can be set with ``--ewin X``, where ``X`` is the treshold in kcal/mol (default of 6 in CalcUS). +Conformational searches (constrained or not) have several particular options. Faster/cruder sampling procedures can be requested with ``--quick``, ``--squick`` and ``--mquick``. The RMSD threshold for considering conformers as different can be set with ``--rthr X``, where ``X`` is the threshold in Ångström (default of 2.0 in CalcUS). Furthermore, the energy window to consider can be set with ``--ewin X``, where ``X`` is the treshold in kcal/mol (default of 6 in CalcUS). Constrained geometrical optimisations and constrained conformational searches employ a harmonic potential to constrain coordinates (distances, angles, dihedral angles). The force constant of this potential can be chosen with ``--forceconstant X``, where ``X`` is the force constant in Hartree/Bohr². By default, CalcUS uses a force constant of 1.0, which corresponds to a very stiff potential well. In most cases, this high value forces the use of small timesteps in meta-dynamic and molecular dynamic simulations (for constrained conformational searches). If the constrained coordinates do not need to remain exactly constant, the simulations can be accelerated by a smaller force constant. diff --git a/frontend/calcusliveserver.py b/frontend/calcusliveserver.py index 9b0a1e13..f8ecab00 100644 --- a/frontend/calcusliveserver.py +++ b/frontend/calcusliveserver.py @@ -37,7 +37,6 @@ from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import Select from selenium.webdriver.chrome.options import Options -from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.common.action_chains import ActionChains from celery.contrib.testing.worker import start_worker @@ -78,18 +77,19 @@ def setUpClass(cls): cls.host = socket.gethostbyname(socket.gethostname()) + chrome_options = Options() if "CI" in os.environ: # Github Actions - chrome_options = Options() - from pyvirtualdisplay import Display + chrome_options.add_argument("--headless") + # from pyvirtualdisplay import Display - cls.display = Display(visible=0, size=(ZOOM * 1920, ZOOM * 1080)) - cls.display.start() + # cls.display = Display(visible=0, size=(ZOOM * 1920, ZOOM * 1080)) + # cls.display.start() - cls.driver = webdriver.Chrome(chrome_options=chrome_options) + cls.driver = webdriver.Chrome(options=chrome_options) else: cls.driver = webdriver.Remote( command_executor="http://selenium:4444/wd/hub", - desired_capabilities=DesiredCapabilities.CHROME, + options=chrome_options, ) cls.driver.set_window_size(ZOOM * 1920, ZOOM * 1080) @@ -151,7 +151,7 @@ def _test_wrapper(self): num += 1 print(f"Test failed, trying again (attempt {num}/{MAX_ATTEMPTS})") self.cleanupCalculations() - self.lget("/home/") + self.lget("/") time.sleep(3) else: break @@ -163,7 +163,14 @@ def setUp(self): self.email = "Selenium@test.com" self.password = "test1234" - self.user = User.objects.create_user(email=self.email, password=self.password) + self.user = User.objects.create_user( + email=self.email, + password=self.password, + advanced_interface=True, + calc_type_property=False, + calc_method_suggestions=False, + ) + self.login(self.email, self.password) time.sleep(0.1) # Reduces glitches (I think?) @@ -174,8 +181,9 @@ def setUp(self): def cleanupCalculations(self): for c in Calculation.objects.all(): - res = AbortableAsyncResult(c.task_id) - res.abort() + if c.task_id != "": + res = AbortableAsyncResult(c.task_id) + res.abort() def login(self, email, password): self.lget("/accounts/login/") @@ -234,6 +242,10 @@ def lget(self, url): def calc_input_params(self, params): self.wait_for_ajax() + simple_interface = False + if "interface" in params and params["interface"].lower() == "simple": + simple_interface = True + if "mol_name" in params.keys(): element = WebDriverWait(self.driver, 6).until( EC.presence_of_element_located((By.NAME, "calc_mol_name")) @@ -302,13 +314,14 @@ def calc_input_params(self, params): else: driver = "xtb" - select = self.driver.find_element(By.NAME, "calc_driver") - self.driver.execute_script( - "showDropdown = function (element) {var event; event = document.createEvent('MouseEvents'); event.initMouseEvent('mousedown', true, true, window); element.dispatchEvent(event); }; showDropdown(arguments[0]);", - select, - ) - time.sleep(0.1) - select.find_element(By.XPATH, f"option[text()='{driver}']").click() + if not simple_interface: + select = self.driver.find_element(By.NAME, "calc_driver") + self.driver.execute_script( + "showDropdown = function (element) {var event; event = document.createEvent('MouseEvents'); event.initMouseEvent('mousedown', true, true, window); element.dispatchEvent(event); }; showDropdown(arguments[0]);", + select, + ) + time.sleep(0.1) + select.find_element(By.XPATH, f"option[text()='{driver}']").click() self.wait_for_ajax() @@ -962,11 +975,11 @@ def is_on_page_order_details(self): def is_on_page_projects(self): for i in range(3): url = self.get_split_url() - if (url[0] == "projects" or url[0] == "home") and ( - url[1] == "" or self.is_user(url[1]) + if url[0] == "projects" and ( + len(url) < 2 or self.is_user(url[1]) or url[1] == "" ): return True - time.sleep(1) + time.sleep(0.5) return False @@ -1519,6 +1532,10 @@ def launch_frame_next_step(self): else: raise Exception("Could not get the button to launch calculation from frame") + button = WebDriverWait(self.driver, 1).until( + EC.element_to_be_clickable((By.ID, "launch_from_frame")) + ) + button.click() # Less flaky than "send_keys(Keys.RETURN)" in headless mode? def accept_alert(self): @@ -1679,7 +1696,12 @@ def setup_test_group(self): g = ResearchGroup.objects.create(name="Test group", PI=self.user) self.student = User.objects.create_user( - email="Student@test.com", password=self.password, member_of=g + email="Student@test.com", + password=self.password, + member_of=g, + advanced_interface=True, + calc_type_property=False, + calc_method_suggestions=False, ) def is_loaded_frequencies(self): @@ -1932,11 +1954,22 @@ def clean_all_completed(self): btn = self.driver.find_element(By.ID, "clean_all_completed_btn") btn.click() + def load_mol_in_sketcher(self, mol): + # The mol text should not have any excess whitespace (e.g., due to indentation in the Python code) + self.driver.execute_script( + "sketcher.loadMolecule(ChemDoodle.readMOL(`" + mol + "`))" + ) + + def preview_molecule(self): + self.driver.find_element(By.ID, "gen_3D_btn").click() + self.wait_for_ajax() + class CalcusCloudLiveServer(CalcusLiveServer): def setUp(self): super().setUp() self.user.allocated_seconds = 100 + self.user.calc_type_property = False self.user.save() settings.IS_CLOUD = True @@ -1966,6 +1999,45 @@ def tearDown(self): settings.LOCAL_ALLOWED_STEPS = ["ALL"] + def register(self, acc_type, email, password, opt_in_emails): + self.lget("/register/") + + # Researcher only for now + tab = WebDriverWait(self.driver, 2).until( + EC.presence_of_element_located((By.ID, "tab_" + acc_type)) + ) + + tab.click() + if acc_type == "researcher": + self.driver.find_element(By.ID, "id_email").send_keys(email) + self.driver.find_element(By.ID, "id_password1").send_keys(password) + self.driver.find_element(By.ID, "id_password2").send_keys(password) + if opt_in_emails: + self.driver.find_element(By.ID, "id_opted_in_emails").click() + + try: + WebDriverWait(self.driver, 3).until( + EC.frame_to_be_available_and_switch_to_it( + (By.XPATH, "//*[@title='reCAPTCHA']") + ) + ) + time.sleep(0.3) + WebDriverWait(self.driver, 1).until( + EC.element_to_be_clickable( + (By.XPATH, "//div[@class='recaptcha-checkbox-border']") + ) + ).click() + self.wait_for_ajax() + self.driver.switch_to.default_content() + time.sleep(0.3) + except selenium.common.exceptions.NoSuchElementException: + pass + + submit = self.driver.find_element( + By.CSS_SELECTOR, "#form_" + acc_type + " > button" + ) + submit.click() + def redeem_code(self, code): self.lget("/profile/") @@ -2097,20 +2169,39 @@ def dissolve_class(self, name): else: raise Exception(f"Could not find a class named {name} to dissolve") - def subscribe(self, email, card_number): + def subscribe(self, plan, email, card_number, length="month"): + """ + Plan is either `researcher` or `team` + """ self.lget("/pricing/") + if length == "year": + switch = WebDriverWait(self.driver, 5).until( + EC.presence_of_element_located( + (By.CSS_SELECTOR, f"[for=sub_length_switch]") + ) + ) + switch.click() + btn = WebDriverWait(self.driver, 2).until( - EC.element_to_be_clickable((By.ID, "subscribe_btn")) + EC.element_to_be_clickable((By.ID, f"subscribe_{plan}_btn_{length}")) ) btn.click() - email_inp = WebDriverWait(self.driver, 4).until( - EC.presence_of_element_located((By.ID, "email")) + cardNumber = WebDriverWait(self.driver, 4).until( + EC.presence_of_element_located((By.ID, "cardNumber")) ) - email_inp.send_keys(email) + cardNumber.send_keys(card_number) + + # The email is autofilled if the user is logged in + try: + email_field = self.driver.find_element(By.ID, "email") + if not email_field.get_property("readonly"): + email_field.send_keys(email) + except selenium.common.exceptions.NoSuchElementException: + # It seems like the filled field does not have the same ID + pass - self.driver.find_element(By.ID, "cardNumber").send_keys(card_number) self.driver.find_element(By.ID, "cardExpiry").send_keys("0140") self.driver.find_element(By.ID, "cardCvc").send_keys("123") self.driver.find_element(By.ID, "billingName").send_keys("Selenium Robot") diff --git a/frontend/cloud_job.py b/frontend/cloud_job.py index 9967bc46..431e7d32 100644 --- a/frontend/cloud_job.py +++ b/frontend/cloud_job.py @@ -1,10 +1,12 @@ import os import json +import uuid from django.conf import settings from .models import Calculation from .environment_variables import * +from .helpers import job_triage if settings.IS_CLOUD: from google.cloud import batch_v1 @@ -31,18 +33,16 @@ def record_event_analytics(request, event_name, **extra_params): ): return - from google.cloud import tasks_v2 - - client = tasks_v2.CloudTasksClient() - if "_ga" not in request.COOKIES: logger.warning( f"The Google Analytics cookie does not appear to be set for {request.user.id}" ) return + client_id = ".".join(request.COOKIES["_ga"].split(".")[-2:]) + payload = { - "client_id": request.COOKIES["_ga"], + "client_id": client_id, "events": [ { "name": event_name, @@ -54,10 +54,10 @@ def record_event_analytics(request, event_name, **extra_params): ], } - if "CALCUS_SESSION_COOKIE" in request.COOKIES: - payload["events"][0]["params"]["session_id"] = request.COOKIES[ - "CALCUS_SESSION_COOKIE" - ] + # Session ID + for cookie, val in request.COOKIES.items(): + if "_ga_" in cookie and len(cookie) > 4: + payload["events"][0]["params"]["session_id"] = val.split(".")[2] if not request.user.is_anonymous: payload["user_id"] = str(request.user.id) @@ -72,6 +72,13 @@ def record_event_analytics(request, event_name, **extra_params): url = f"https://www.google-analytics.com/mp/collect?measurement_id={settings.ANALYTICS_MEASUREMENT_ID}&api_secret={settings.ANALYTICS_API_SECRET}" + if "gclid" in request.COOKIES: + url += f'&gclid={request.COOKIES["gclid"]}' + + from google.cloud import tasks_v2 + + client = tasks_v2.CloudTasksClient() + parent = client.queue_path( settings.GCP_PROJECT_ID, settings.GCP_LOCATION, "analytics" ) @@ -113,8 +120,17 @@ def create_container_job(calc, nproc, timeout): "CALCUS_CLOUD": "True", "NUM_CPU": str(nproc), "OMP_NUM_THREADS": f"{nproc},1", - "OMP_STACKSIZE": "1536M", # for the c3 machines only + "OMP_STACKSIZE": "900M", "CALCUS_TIMEOUT": str(timeout), + # Variables for rescheduling when a VM in preempted + "CALC_ID": str(calc.id), + "COMPUTE_IMAGE": settings.COMPUTE_IMAGE, + "COMPUTE_SERVICE_ACCOUNT": settings.COMPUTE_SERVICE_ACCOUNT, + "GCP_PROJECT_ID": settings.GCP_PROJECT_ID, + "GCP_LOCATION": settings.GCP_LOCATION, + # NWChem variables + "OMPI_ALLOW_RUN_AS_ROOT": "1", + "OMPI_ALLOW_RUN_AS_ROOT_CONFIRM": "1", } runnable.environment = env @@ -124,7 +140,7 @@ def create_container_job(calc, nproc, timeout): resources = batch_v1.ComputeResource() resources.cpu_milli = nproc * 1000 # in milliseconds per cpu-second - resources.memory_mib = nproc * 1024 * 2 + resources.memory_mib = nproc * 1024 task.compute_resource = resources task.max_retry_count = 1 @@ -138,10 +154,21 @@ def create_container_job(calc, nproc, timeout): group.parallelism = 1 policy = batch_v1.AllocationPolicy.InstancePolicy() - policy.machine_type = f"c3-highcpu-{nproc}" + policy.machine_type = f"n2d-highcpu-{nproc}" + # policy.boot_disk = ... + # https://cloud.google.com/batch/docs/reference/rest/v1/projects.locations.jobs#Disk + + metadata = [] + + # Trying spot VMs + policy.provisioning_model = batch_v1.types.AllocationPolicy.ProvisioningModel.SPOT + metadata.append( + ("shutdown-script-url", "gs://calcus-cloud-config/shutdown_script.py") + ) instances = batch_v1.AllocationPolicy.InstancePolicyOrTemplate() instances.policy = policy + acc = batch_v1.ServiceAccount() acc.email = settings.COMPUTE_SERVICE_ACCOUNT @@ -158,15 +185,16 @@ def create_container_job(calc, nproc, timeout): job.logs_policy = batch_v1.LogsPolicy() job.logs_policy.destination = batch_v1.LogsPolicy.Destination.CLOUD_LOGGING + rand = str(uuid.uuid4())[:4] create_request = batch_v1.CreateJobRequest() create_request.job = job - create_request.job_id = f"j{str(calc.id).lower()}" + create_request.job_id = f"j{str(calc.id).lower()}{rand}" create_request.parent = ( f"projects/{settings.GCP_PROJECT_ID}/locations/{settings.GCP_LOCATION}" ) - return client.create_job(create_request) + return client.create_job(create_request, metadata=metadata) def send_gcloud_task(url, payload, compute=True): @@ -213,20 +241,6 @@ def send_gcloud_task(url, payload, compute=True): client.create_task(parent=parent, task=task) -def job_triage(calc): - if calc.step.name in ["Conformational Search", "Constrained Conformational Search"]: - nproc = 8 - else: - nproc = 1 - - user_type = calc.order.author.user_type - - return ( - min(settings.RESOURCE_LIMITS[user_type]["nproc"], nproc), - settings.RESOURCE_LIMITS[user_type]["time"] * 60, - ) - - def submit_cloud_job(calc): nproc, timeout = job_triage(calc) if nproc == 1: diff --git a/frontend/constants.py b/frontend/constants.py index 010ac157..a854f10e 100644 --- a/frontend/constants.py +++ b/frontend/constants.py @@ -47,6 +47,7 @@ R_CONSTANT = decimal.Decimal(8.31446261815324) R_CONSTANT_HARTREE = decimal.Decimal(3.166811565240536e-06) TEMP = decimal.Decimal(298) +BOHR_VAL = 0.529177 MAX_SFTP_ATTEMPT_COUNT = 3 MAX_COMMAND_ATTEMPT_COUNT = 5 diff --git a/frontend/context.py b/frontend/context.py index ec63a6f6..4b0b7005 100644 --- a/frontend/context.py +++ b/frontend/context.py @@ -1,18 +1,29 @@ from django.conf import settings +from frontend import constants def default(request): - return { - k: getattr(settings, k) - for k in [ - "IS_TEST", - "IS_CLOUD", - "ALLOW_LOCAL_CALC", - "ALLOW_REMOTE_CALC", - "ALLOW_TRIAL", - "LOCAL_MAX_ATOMS", - "LOCAL_ALLOWED_THEORY_LEVELS", - "LOCAL_ALLOWED_STEPS", - "SUBSCRIPTION_ACADEMIC_MONTHLY", - ] - } + d = {} + for k in [ + "IS_TEST", + "IS_CLOUD", + "ALLOW_LOCAL_CALC", + "ALLOW_REMOTE_CALC", + "ALLOW_TRIAL", + "LOCAL_MAX_ATOMS", + "LOCAL_ALLOWED_THEORY_LEVELS", + "LOCAL_ALLOWED_STEPS", + ]: + d[k] = getattr(settings, k) + + if settings.IS_CLOUD: + if settings.IS_TEST: + version = "testing" + else: + version = "production" + try: + d["SUBSCRIPTION_DATA"] = constants.SUBSCRIPTION_DATA[version] + except (NameError, AttributeError): + logger.error(f"Could not get subscription data") + + return d diff --git a/frontend/decorators.py b/frontend/decorators.py index 89cc7375..a0a84090 100644 --- a/frontend/decorators.py +++ b/frontend/decorators.py @@ -26,6 +26,6 @@ def wrapper(request, *args, **kwargs): if request.user.is_superuser: return func(request, *args, **kwargs) else: - return HttpResponseRedirect("/home/") + return HttpResponseRedirect("/") return wrapper diff --git a/frontend/forms.py b/frontend/forms.py index d51b7755..84e5abc5 100644 --- a/frontend/forms.py +++ b/frontend/forms.py @@ -30,10 +30,10 @@ from django.contrib.auth.forms import SetPasswordForm from django.utils import timezone -if settings.IS_CLOUD: +if settings.IS_CLOUD or settings.IS_TEST: from captcha.fields import ReCaptchaField -from frontend.models import User, ClassGroup, ResourceAllocation +from frontend.models import User, ClassGroup, ResourceAllocation, Project from frontend.helpers import get_random_string logging.basicConfig( @@ -49,7 +49,11 @@ class ResearcherCreateForm(UserCreationForm): error_messages={"exists": "This email has already been used"}, ) - if settings.IS_CLOUD: + if settings.IS_CLOUD or settings.IS_TEST: + opted_in_emails = forms.BooleanField( + required=False, + label="Receive occasional emails about updates and promotions", + ) captcha = ReCaptchaField() class Meta: @@ -59,6 +63,10 @@ class Meta: def save(self, commit=True): user = super(ResearcherCreateForm, self).save(commit=False) user.email = self.cleaned_data["email"] + + if "opted_in_emails" in self.cleaned_data: + user.opted_in_emails = self.cleaned_data["opted_in_emails"] + user.allocated_seconds = settings.FREE_DEFAULT_COMP_SECONDS user.last_free_refill = timezone.now() @@ -70,6 +78,7 @@ def save(self, commit=True): allocation_seconds=settings.FREE_DEFAULT_COMP_SECONDS, note=ResourceAllocation.NEW_ACCOUNT, ) + proj = Project.objects.create(name="My Main Project", author=user) return user @@ -138,13 +147,16 @@ def save(self, commit=True): if commit: user.save() + proj = Project.objects.create(name="My Main Project", author=user) return user class TrialUserCreateForm(forms.ModelForm): + """ accepts_tos = forms.BooleanField( required=True, label="I accept the Terms of Service" ) + """ if settings.IS_CLOUD: captcha = ReCaptchaField() @@ -153,12 +165,14 @@ class Meta: model = User fields = () + """ def clean_accepts_tos(self): accepts = self.cleaned_data["accepts_tos"] if not accepts: raise ValidationError( f"You must accept the Terms of Service in order to use the platform" ) + """ def save(self, commit=True): user = super().save(commit=False) @@ -181,6 +195,7 @@ def save(self, commit=True): allocation_seconds=settings.TRIAL_DEFAULT_COMP_SECONDS, note=ResourceAllocation.TRIAL, ) + proj = Project.objects.create(name="My Main Project", author=user) return user diff --git a/frontend/helpers.py b/frontend/helpers.py index e82756ca..d91a31ae 100644 --- a/frontend/helpers.py +++ b/frontend/helpers.py @@ -20,6 +20,7 @@ from .constants import * import string import secrets +from django.conf import settings from xkcdpass import xkcd_password as xp @@ -47,6 +48,25 @@ def get_xyz_from_Gaussian_input(txt): return xyz +def get_xyz_from_cube(cube): + lines = cube.split("\n") + ind = 1 + sline = lines[ind].strip().split() + xyz = "" + while len(sline) != 5: + ind += 1 + sline = lines[ind].strip().split() + start_ind = ind + + while len(sline) == 5: + xyz += f"{ATOMIC_SYMBOL[int(sline[0])]} {float(sline[2])*BOHR_VAL} {float(sline[3])*BOHR_VAL} {float(sline[4])*BOHR_VAL}\n" + ind += 1 + sline = lines[ind].strip().split() + + xyz = f"{ind-start_ind}\n\n{xyz}" + return xyz + + def get_random_string(n=16): return "".join(secrets.choice(full_alphabet) for i in range(n)) @@ -55,3 +75,62 @@ def get_random_readable_code(n=5): wordfile = xp.locate_wordfile() words = xp.generate_wordlist(wordfile=wordfile, min_length=n, max_length=n + 2) return xp.generate_xkcdpassword(words) + + +def job_triage(calc): + if calc.step.name in ["Conformational Search", "Constrained Conformational Search"]: + nproc = 8 + elif calc.step.name == "Minimum Energy Path": + nproc = 4 + else: + natoms = calc.structure.xyz_structure.count("\n") - 2 + if calc.parameters.software == "xtb": + if natoms > 100: + nproc = 4 + else: + nproc = 1 + else: + if natoms < 20: + nproc = 4 + else: + nproc = 8 + + user_type = calc.order.author.user_type + + return ( + min(settings.RESOURCE_LIMITS[user_type]["nproc"], nproc), + settings.RESOURCE_LIMITS[user_type]["time"] * 60, + ) + + +def guess_missing_parameters(params): + """ + Guesses which software and driver to use based on the parameters. + Mostly for CalcUS Cloud at the moment. + """ + if "calc_theory_level" in params: + t = params["calc_theory_level"] + if t == "xtb": + params["calc_software"] = "xtb" + params["calc_driver"] = "xtb" + if "calc_type" in params: + t = params["calc_type"] + if t == "TS Optimisation": + params["calc_driver"] = "Pysisyphus" + else: + params["calc_software"] = "NWChem" + params["calc_driver"] = "NWChem" + else: + params["calc_software"] = "xtb" + params["calc_driver"] = "xtb" + + if "calc_solvent" in params: + solv = params["calc_solvent"] + if solv.lower() not in ["", "vacuum"]: + # There is a solvent + if params["calc_software"] == "xtb": + params["calc_solvation_model"] = "ALPB" + params["calc_solvation_radii"] = "default" + elif params["calc_software"] == "NWChem": + params["calc_solvation_model"] = "SMD" + params["calc_solvation_radii"] = "default" diff --git a/frontend/management/commands/init_static_obj.py b/frontend/management/commands/init_static_obj.py index deb59144..b4ef483a 100755 --- a/frontend/management/commands/init_static_obj.py +++ b/frontend/management/commands/init_static_obj.py @@ -58,17 +58,20 @@ def add_step( self, name, short_name, + prop_name, **kwargs, ): if self.is_absent(BasicStep, name): BasicStep.objects.create( name=name, short_name=short_name, + prop_name=prop_name, **kwargs, ) else: BasicStep.objects.filter(name=name).update( short_name=short_name, + prop_name=prop_name, **kwargs, ) @@ -78,100 +81,92 @@ def handle(self, *args, **options): self.add_step( "Geometrical Optimisation", "opt", + "Geometry", creates_ensemble=True, - avail_xtb=True, - avail_Gaussian=True, - avail_ORCA=True, ) self.add_step( "Conformational Search", "conf_search", + "Conformer ensemble", creates_ensemble=True, - avail_xtb=True, - avail_Gaussian=False, - avail_ORCA=False, ) self.add_step( "Constrained Optimisation", "constr_opt", + "Geometry with constraint", creates_ensemble=True, - avail_xtb=True, - avail_Gaussian=True, - avail_ORCA=True, ) self.add_step( "Frequency Calculation", "freq", + "Vibrational modes, IR spectrum and thermochemistry", creates_ensemble=False, - avail_xtb=True, - avail_Gaussian=True, - avail_ORCA=True, ) self.add_step( "TS Optimisation", "optts", + "Transition state geometry", creates_ensemble=True, - avail_xtb=True, - avail_Gaussian=True, - avail_ORCA=True, ) self.add_step( "UV-Vis Calculation", "uvvis", + "UV-Vis spectrum", creates_ensemble=False, - avail_xtb=True, - avail_Gaussian=True, - avail_ORCA=False, ) self.add_step( "NMR Prediction", "nmr", + "NMR spectrum", creates_ensemble=False, - avail_xtb=False, - avail_Gaussian=True, - avail_ORCA=True, ) self.add_step( "Single-Point Energy", "sp", + "Electronic energy", creates_ensemble=False, - avail_xtb=True, - avail_Gaussian=True, - avail_ORCA=True, ) self.add_step( "MO Calculation", "mo", + "Molecular Orbitals", creates_ensemble=False, - avail_xtb=False, - avail_Gaussian=False, - avail_ORCA=True, ) self.add_step( "Minimum Energy Path", "mep", + "Minimum Energy Path", creates_ensemble=True, - avail_xtb=True, - avail_Gaussian=False, - avail_ORCA=True, ) self.add_step( "Constrained Conformational Search", "constr_conf_search", + "Conformer ensemble with constraint", + creates_ensemble=True, + ) + + self.add_step( + "ESP Calculation", + "esp", + "Electrostatic potential map", + creates_ensemble=False, + ) + + self.add_step( + "Fast Conformational Search", + "fast_conf_search", + "Preliminary conformer ensemble", creates_ensemble=True, - avail_xtb=True, - avail_Gaussian=False, - avail_ORCA=False, ) title = "NHC-Catalysed Condensation" diff --git a/frontend/management/commands/reschedule_calc.py b/frontend/management/commands/reschedule_calc.py new file mode 100755 index 00000000..b0117dae --- /dev/null +++ b/frontend/management/commands/reschedule_calc.py @@ -0,0 +1,34 @@ +import glob +import os +from django.core.management.base import BaseCommand + +from frontend.models import * +from frontend.environment_variables import * +from frontend.cloud_job import submit_cloud_job + + +class Command(BaseCommand): + help = "Runs a calculation" + + def add_arguments(self, parser): + parser.add_argument("calc_id", type=str) + + def handle(self, *args, **options): + calc_id = options["calc_id"] + try: + calc = Calculation.objects.get(pk=calc_id) + except Calculation.DoesNotExist: + raise Exception(f"Could not find calculation number {calc_id}") + + if calc.status == 2: # If successful + print( + f"Calculation {calc_id} has already completed successfully! This shouldn't happen" + ) + return + + submit_cloud_job(calc) + + calc.status = 0 + calc.save() + + print(f"Calculation {calc_id} has been rescheduled") diff --git a/frontend/models.py b/frontend/models.py index 02514f97..d5023341 100644 --- a/frontend/models.py +++ b/frontend/models.py @@ -40,13 +40,14 @@ from hashid_field import HashidAutoField, BigHashidAutoField from .constants import * -from .helpers import get_random_readable_code +from .helpers import get_random_readable_code, job_triage +from .environment_variables import PAL import ccinput register = template.Library() -STATUS_COLORS = {0: "#AAAAAA", 1: "#FFE515", 2: "#1EE000", 3: "#FD1425"} +STATUS_COLORS = {0: "#202f26", 1: "#e2e100", 2: "#02b200", 3: "#b21b00"} class UserManager(BaseUserManager): @@ -119,6 +120,13 @@ class User(AbstractUser): default_gaussian = models.CharField(max_length=1000, default="") default_orca = models.CharField(max_length=1000, default="") + tour_done = models.BooleanField(default=False) + opted_in_emails = models.BooleanField(default=False) + + advanced_interface = models.BooleanField(default=False) + calc_type_property = models.BooleanField(default=True) + calc_method_suggestions = models.BooleanField(default=True) + code = models.CharField(max_length=16) ### ? pref_units = models.PositiveIntegerField(default=0) @@ -130,6 +138,9 @@ class User(AbstractUser): INV_UNITS = {v: k for k, v in UNITS.items()} + stripe_cus_id = models.CharField(max_length=256, default="") + stripe_will_renew = models.BooleanField(default=True) + # Total/allocated computation time and time consumed by user or class/group # These numbers should be accurate, but are not the official references # Instead, allocated_seconds should be recalculated from ResourceAllocation objects @@ -149,6 +160,8 @@ def user_type(self): elif self.is_subscriber: return "subscriber" else: + if self.member_of is not None and self.member_of.PI.is_subscriber: + return "subscriber" return "free" @property @@ -268,14 +281,11 @@ class Subscription(models.Model): "ResourceAllocation", on_delete=models.SET_NULL, null=True ) - # Duration in months (1, 4, 12) - # Mostly to identify which subscription option the user has chosen - duration = models.PositiveSmallIntegerField() - for_academia = models.BooleanField(default=True) - start_date = models.DateTimeField("date") end_date = models.DateTimeField("date") + stripe_sub_id = models.CharField(max_length=256, default="") + class ResourceAllocation(models.Model): id = BigHashidAutoField( @@ -516,10 +526,12 @@ def connected(self): class BasicStep(models.Model): name = models.CharField(max_length=100) short_name = models.CharField(max_length=100, default="") + prop_name = models.CharField(max_length=100, default="") avail_xtb = models.BooleanField(default=False) avail_Gaussian = models.BooleanField(default=False) avail_ORCA = models.BooleanField(default=False) + avail_NWChem = models.BooleanField(default=False) creates_ensemble = models.BooleanField(default=False) @@ -881,6 +893,7 @@ class Property(models.Model): uvvis = models.TextField(default="") nmr = models.TextField(default="") mo = models.TextField(default="") + esp = models.TextField(default="") freq_list = ArrayField(models.FloatField(), default=list) freq_animations = ArrayField(models.TextField(), default=list) ir_spectrum = models.TextField(default="") @@ -906,6 +919,10 @@ def has_nmr(self): def has_mo(self): return len(self.mo) > 0 + @property + def has_esp(self): + return len(self.esp) > 0 + class Structure(models.Model): id = BigHashidAutoField( @@ -1081,7 +1098,7 @@ class Molecule(models.Model): primary_key=True, salt="Molecule_hashid_" + settings.HASHID_FIELD_SALT ) name = models.CharField(max_length=100) - inchi = models.CharField(max_length=1000, default="", blank=True, null=True) + inchi = models.CharField(max_length=10000, default="", blank=True, null=True) project = models.ForeignKey( Project, on_delete=models.CASCADE, blank=True, null=True ) @@ -1249,6 +1266,12 @@ def see(self): self.hidden = True self.save() + @property + def total_cpu_time(self): + return sum( + [c.execution_time for c in self.calculation_set.filter(status__gt=0).all()] + ) + @property def color(self): return STATUS_COLORS[self.status] @@ -1502,17 +1525,30 @@ def delete(self, *args, **kwargs): @property def execution_time(self): + if self.date_started is None: + return 0 + if self.date_finished is None: + end_date = timezone.now() + else: + end_date = self.date_finished + + if settings.IS_CLOUD: + nproc, limit = job_triage(self) + delta = end_date - self.date_started + return round(delta.total_seconds() * nproc) + if self.local: + delta = end_date - self.date_started + return round(delta.total_seconds() * PAL) + if self.order.resource is None: - return "-" - if self.date_started is not None and self.date_finished is not None: - if self.local: - pal = os.getenv("OMP_NUM_THREADS")[0] - else: - pal = self.order.resource.pal - return int((self.date_finished - self.date_started).seconds * int(pal)) + # Shouldn't happen + return 0 + if self.local: + pal = os.getenv("OMP_NUM_THREADS")[0] else: - return "-" + pal = self.order.resource.pal + return int((end_date - self.date_started).seconds * int(pal)) def __repr__(self): return str(self.id) @@ -1531,6 +1567,40 @@ def set_as_cancelled(self): calc.status = 3 calc.save() + +class BatchCalcOrder(models.Model): + id = BigHashidAutoField( + primary_key=True, salt="BatchCalc_hashid_" + settings.HASHID_FIELD_SALT + ) + step = models.ForeignKey( + BasicStep, on_delete=models.SET_NULL, blank=True, null=True + ) + author = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True) + + project = models.ForeignKey( + "Project", on_delete=models.CASCADE, blank=True, null=True + ) + date = models.DateTimeField("date", null=True, blank=True) + + + +class BatchCalculation(models.Model): + id = BigHashidAutoField( + primary_key=True, salt="BatchCalc_hashid_" + settings.HASHID_FIELD_SALT + ) + batch_name = models.CharField(max_length=100) + parametersets = models.JSONField(default=list) + parameters = models.ForeignKey( + Parameters, on_delete=models.SET_NULL, blank=True, null=True + ) + structure = models.ForeignKey( + Structure, on_delete=models.SET_NULL, blank=True, null=True + ) + calculationorder = models.ForeignKey( + BatchCalcOrder, n_delete=models.SET_NULL, blank=True, null=True) + + + class Filter(models.Model): id = BigHashidAutoField( diff --git a/frontend/tasks.py b/frontend/tasks.py index 1a00ba77..18ab9c22 100644 --- a/frontend/tasks.py +++ b/frontend/tasks.py @@ -36,6 +36,7 @@ import shutil import tempfile import json +import gzip, base64 from django.conf import settings import threading @@ -44,6 +45,13 @@ from shutil import copyfile, rmtree import time +from rdkit import Chem +from rdkit.Chem import AllChem +import spyrmsd +from spyrmsd.optional.rdkit import to_molecule +from spyrmsd.rmsd import rmsdwrapper +from sklearn.cluster import DBSCAN + if not settings.IS_CLOUD: from calcus.celery import app from celery.signals import task_prerun, task_postrun @@ -75,6 +83,7 @@ def wrapper(*args, **kwargs): from django.utils import timezone from django.core import management +from django.core.mail import send_mail from django.db.utils import IntegrityError from ccinput.wrapper import generate_calculation @@ -92,10 +101,10 @@ def wrapper(*args, **kwargs): import logging +logger = logging.getLogger() logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(module)s: %(message)s" ) -logger = logging.getLogger(__name__) REMOTE = False @@ -335,7 +344,14 @@ def testing_delay_cloud(calc_id): return ErrorCodes.SUCCESS -def system(command, log_file="", force_local=False, software="xtb", calc_id=-1): +def system( + command, + log_file="", + force_local=False, + software="xtb", + calc_id=-1, + time_cumul=False, +): if REMOTE and not force_local: assert calc_id != -1 @@ -449,16 +465,20 @@ def system(command, log_file="", force_local=False, software="xtb", calc_id=-1): return ret else: # Local + # Assume the calculation will be run in the same directory as the output file + exe_dir = os.path.dirname(log_file) + logger.info(f"exe_dir: {exe_dir} (calc {calc_id})") if calc_id != -1 and settings.IS_CLOUD: try: - timeout = int(os.getenv("CALCUS_TIMEOUT", "300")) + timeout = int(os.getenv("CALCUS_TIMEOUT", "580")) except ValueError: logger.warning(f"Could not parse the timeout for calc {calc_id}") - timeout = 300 + timeout = 580 if calc_id != -1: calc = Calculation.objects.get(pk=calc_id) calc.status = 1 - calc.date_started = timezone.now() + if calc.date_started is None or not time_cumul: + calc.date_started = timezone.now() calc.save() res = AbortableAsyncResult(calc.task_id) @@ -471,7 +491,13 @@ def system(command, log_file="", force_local=False, software="xtb", calc_id=-1): return testing_delay_local(res) try: - t = subprocess.Popen(shlex.split(command), stdout=stream, stderr=stream) + if exe_dir.strip() != "": + t = subprocess.Popen( + shlex.split(command), stdout=stream, stderr=stream, cwd=exe_dir + ) + else: + t = subprocess.Popen(shlex.split(command), stdout=stream, stderr=stream) + except FileNotFoundError: logger.error(f'Could not run command "{command}" - executable not found') calc.error_message = f"{command.split()[0]} is not found" @@ -507,6 +533,10 @@ def kill_task(): return ErrorCodes.SUCCESS else: logger.info(f"Got returncode {t.returncode}") + with open(log_file) as f: + last_lines = "".join(f.readlines()[-100:]) + logger.info("Last lines of the log file: " + last_lines) + return ErrorCodes.UNKNOWN_TERMINATION if calc_id != -1: @@ -602,7 +632,7 @@ def setup_cached_calc(calc): return True -def generate_xyz_structure(drawing, inp, ext): +def generate_xyz_structure(drawing, inp, ext, scale=1): if ext == "xyz": return inp elif ext == "mol": @@ -621,15 +651,19 @@ def generate_xyz_structure(drawing, inp, ext): to_print = [] for line in inp.split("\n")[4:]: sline = line.split() + if len(sline) < 4: + # Not sure what input causes this error + logger.warning(f"Strange .mol input; processing line: '{line}'") + break try: a = int(sline[3]) except ValueError: to_print.append( "{} {} {} {}\n".format( sline[3], - float(sline[0]), - float(sline[1]), - float(sline[2]), + float(sline[0]) * scale, + float(sline[1]) * scale, + float(sline[2]) * scale, ) ) else: @@ -677,9 +711,8 @@ def launch_pysis_calc(in_file, calc, files): with open(os.path.join(local_folder, "calc.inp"), "w") as out: out.write(calc.input_file) - os.chdir(local_folder) - if not calc.local: + log_path = os.path.join(folder, "calc.out") pid = int(threading.get_ident()) conn = connections[pid] lock = locks[pid] @@ -700,9 +733,9 @@ def launch_pysis_calc(in_file, calc, files): lock, ) else: - os.chdir(local_folder) + log_path = os.path.join(local_folder, "calc.out") - ret = system(calc.command, "calc.out", software="pysis", calc_id=calc.id) + ret = system(calc.command, log_path, software="pysis", calc_id=calc.id) cancelled = False if ret != ErrorCodes.SUCCESS: @@ -795,6 +828,7 @@ def launch_xtb_calc(in_file, calc, files): os.chdir(local_folder) if not calc.local: + log_path = os.path.join(folder, local_folder) pid = int(threading.get_ident()) conn = connections[pid] lock = locks[pid] @@ -815,9 +849,9 @@ def launch_xtb_calc(in_file, calc, files): lock, ) else: - os.chdir(local_folder) + log_path = os.path.join(local_folder, "calc.out") - ret = system(calc.command, "calc.out", software="xtb", calc_id=calc.id) + ret = system(calc.command, log_path, software="xtb", calc_id=calc.id) cancelled = False if ret != ErrorCodes.SUCCESS: @@ -1373,7 +1407,6 @@ def xtb_freq(in_file, calc): lines = [i + "\n" for i in calc.structure.xyz_structure.split("\n")] num_atoms = int(lines[0].strip()) lines = lines[2:] - hess = [] struct = [] for line in lines: @@ -1507,6 +1540,135 @@ def crest(in_file, calc): return ErrorCodes.SUCCESS +def fast_conf(in_file, calc): + local_folder = os.path.join(CALCUS_SCR_HOME, str(calc.id)) + folder = f"scratch/calcus/{calc.id}" # Remote + + calc_start = timezone.now() + + subprocess.call(shlex.split("obabel in.xyz -O in.mol"), cwd=local_folder) + + mol = Chem.MolFromMolFile(os.path.join(local_folder, "in.mol")) + + mol.UpdatePropertyCache() + mol = Chem.AddHs(mol) + + confs = list( + AllChem.EmbedMultipleConfs( + mol, numConfs=50, maxAttempts=1000, pruneRmsThresh=0.05 + ) + ) + mols = [] + props = [] + + for num in confs: + cpath = os.path.join(local_folder, f"conf{num+1}") + os.makedirs(cpath, exist_ok=True) + + Chem.rdmolfiles.MolToXYZFile(mol, os.path.join(cpath, "in.xyz"), confId=num) + + os.chdir(cpath) + + # We want to add up all the time required for the calculations. + # We will overwrite this with a more accurate time afterwards, but this makes the time preview + # a bit less strange. + ret = system( + calc.command, + os.path.join(cpath, "calc.out"), + software="xtb", + calc_id=calc.id, + time_cumul=True, + ) + + # duplicate code + if ret != ErrorCodes.SUCCESS: + logger.error(f"Failed to optimize conformer {num} of calc {calc.id}") + continue + + with open(f"{cpath}/xtbopt.xyz") as f: + xyz = clean_xyz("".join(f.readlines())) + + with open(f"{cpath}/calc.out") as f: + lines = f.readlines() + ind = len(lines) - 1 + + try: + while lines[ind].find("TOTAL ENERGY") == -1: + ind -= 1 + E = float(lines[ind].split()[3]) + except IndexError: + logger.error( + f"Could not parse the output of calculation {calc.id}: invalid format" + ) + continue + + rdmol = Chem.MolFromMolFile(os.path.join(cpath, "xtbtopo.mol")) + + smol = to_molecule(rdmol) + + mols.append(smol) + props.append((E, xyz)) + + def rmsd_cluster(mols, props, threshold=0.8): + # Could be too slow if there are many structures + dist = np.zeros((len(mols), len(mols))) + for i, (mol1, prop1) in enumerate(zip(mols, props)): + for j, (mol2, prop2) in enumerate(zip(mols, props)): + if j <= i: + continue + try: + d = rmsdwrapper( + mol1, mol2, symmetry=True, center=True, minimize=True + )[0] + except spyrmsd.exceptions.NonIsomorphicGraphs: + d = 10 + logger.error(f"Non-isomorphic graphs: {i} and {j} (calc {calc.id})") + + dist[i, j] = d + dist[j, i] = d + + clustering = DBSCAN(eps=threshold, min_samples=1, metric="precomputed").fit( + dist + ) + + unique_mol = [] + unique_prop = [] + known = [] # Known cluster labels + for ind, pos in enumerate(clustering.labels_): + if pos in known: + continue + known.append(pos) + unique_mol.append(mols[ind]) + unique_prop.append(props[ind]) + + return unique_mol, unique_prop + + umols, uprops = rmsd_cluster(mols, props, threshold=0.5) + for ind, (mol, (E, xyz)) in enumerate( + sorted(zip(umols, uprops), key=lambda i: i[1]) + ): + s = Structure.objects.get_or_create( + parent_ensemble=calc.result_ensemble, + number=ind + 1, + )[0] + s.degeneracy = 1 + s.xyz_structure = xyz + prop = get_or_create(calc.parameters, s) + prop.energy = E + prop.geom = True + s.save() + prop.save() + + calc_end = timezone.now() + + # Set the true start and end dates, including the conformational sampling and parsing + calc.date_started = calc_start + calc.date_finished = calc_end + calc.save() + + return ErrorCodes.SUCCESS + + def clean_struct_line(line): a, x, y, z = line.split() return f"{LOWERCASE_ATOMIC_SYMBOLS[a.lower()]} {x} {y} {z}\n" @@ -1546,10 +1708,9 @@ def launch_orca_calc(in_file, calc, files): "$EBROOTORCA/orca calc.inp", "calc.out", software="ORCA", calc_id=calc.id ) else: - os.chdir(local_folder) ret = system( f"{EBROOTORCA}/orca calc.inp", - "calc.out", + os.path.join(local_folder, "calc.out"), software="ORCA", calc_id=calc.id, ) @@ -1627,7 +1788,10 @@ def orca_mo_gen(in_file, calc): return ErrorCodes.MISSING_FILE with open(path) as f: cube = "".join(f.readlines()) - cubes[mo] = cube + + comp_cube = gzip.compress(bytes(cube, "utf-8")) + str_cube = base64.b64encode(comp_cube).decode("utf-8") + cubes[mo] = str_cube prop.energy = E prop.mo = json.dumps(cubes) @@ -2204,6 +2368,492 @@ def xtb_stda(in_file, calc): # TO OPTIMIZE return ErrorCodes.SUCCESS +def launch_nwchem_calc(in_file, calc, files): + local_folder = os.path.join(CALCUS_SCR_HOME, str(calc.id)) + folder = f"scratch/calcus/{calc.id}" + + if not os.path.isdir(local_folder): + os.makedirs(local_folder, exist_ok=True) + + # Debugging code for now + logger.info(f"The input is {calc.input_file}") + + with open(os.path.join(local_folder, "calc.inp"), "w") as out: + out.write(calc.input_file) + + if not calc.local: + pid = int(threading.get_ident()) + conn = connections[pid] + lock = locks[pid] + remote_dir = remote_dirs[pid] + if calc.remote_id == 0: + sftp_put( + f"{local_folder}/calc.inp", + os.path.join(folder, "calc.inp"), + conn, + lock, + ) + + ret = system( + "mpirun -n {PAL} nwchem calc.inp", + "calc.out", + software="NWChem", + calc_id=calc.id, + ) + else: + if settings.IS_CLOUD: + cmd = f"mpirun -n {PAL} --use-hwthread-cpus nwchem calc.inp" + else: + cmd = f"mpirun -n {PAL} nwchem calc.inp" + + ret = system( + cmd, + os.path.join(local_folder, "calc.out"), + software="NWChem", + calc_id=calc.id, + ) + + cancelled = False + if ret != ErrorCodes.SUCCESS: + if ret == ErrorCodes.JOB_CANCELLED: + cancelled = True + else: + return ret + + if not calc.local: + for f in files: + a = sftp_get( + f"{folder}/{f}", + os.path.join(CALCUS_SCR_HOME, str(calc.id), f), + conn, + lock, + ) + if not cancelled and a != ErrorCodes.SUCCESS: + return a + + if not cancelled: + for f in files: + if not os.path.isfile(f"{local_folder}/{f}"): + return ErrorCodes.COULD_NOT_GET_REMOTE_FILE + + return ErrorCodes.SUCCESS + else: + return ErrorCodes.JOB_CANCELLED + + +def nwchem_sp(in_file, calc): + local_folder = os.path.join(CALCUS_SCR_HOME, str(calc.id)) + + ret = launch_nwchem_calc(in_file, calc, ["calc.out"]) + + if ret != ErrorCodes.SUCCESS: + return ret + + with open(f"{local_folder}/calc.out") as f: + lines = f.readlines() + ind = len(lines) - 1 + + try: + while ( + lines[ind].find("Total SCF energy") == -1 + and lines[ind].find("Total DFT energy") == -1 + ): + ind -= 1 + E = float(lines[ind].split()[-1]) + except IndexError: + logger.error( + f"Could not parse the output of calculation {calc.id}: invalid format" + ) + return ErrorCodes.INVALID_OUTPUT + + prop = get_or_create(calc.parameters, calc.structure) + prop.energy = E + prop.save() + + # parse_nwchem_charges(calc, calc.structure) + + return ErrorCodes.SUCCESS + + +def nwchem_mo_gen(in_file, calc): + local_folder = os.path.join(CALCUS_SCR_HOME, str(calc.id)) + + ret = launch_nwchem_calc( + in_file, + calc, + ["calc.out", "in-HOMO.cube", "in-LUMO.cube", "in-LUMOA.cube", "in-LUMOB.cube"], + ) + + if ret != ErrorCodes.SUCCESS: + return ret + + with open(f"{local_folder}/calc.out") as f: + lines = f.readlines() + ind = len(lines) - 1 + + try: + while ( + lines[ind].find("Total SCF energy") == -1 + and lines[ind].find("Total DFT energy") == -1 + ): + ind -= 1 + E = float(lines[ind].split()[-1]) + except IndexError: + logger.error( + f"Could not parse the output of calculation {calc.id}: invalid format" + ) + return ErrorCodes.INVALID_OUTPUT + + prop = get_or_create(calc.parameters, calc.structure) + + cubes = {} + for mo in ["HOMO", "LUMO", "LUMOA", "LUMOB"]: + path = os.path.join(local_folder, f"in-{mo}.cube") + if not os.path.isfile(path): + logger.error(f"Cube file {path} does not exist!") + return ErrorCodes.MISSING_FILE + with open(path) as f: + cube = "".join(f.readlines()) + + comp_cube = gzip.compress(bytes(cube, "utf-8")) + str_cube = base64.b64encode(comp_cube).decode("utf-8") + cubes[mo] = str_cube + + prop.energy = E + prop.mo = json.dumps(cubes) + prop.save() + + # parse_nwchem_charges(calc, calc.structure) + + return ErrorCodes.SUCCESS + + +def nwchem_esp_gen(in_file, calc): + local_folder = os.path.join(CALCUS_SCR_HOME, str(calc.id)) + + ret = launch_nwchem_calc( + in_file, + calc, + ["calc.out", "esp.cube", "in.eld.total.cube"], + ) + + if ret != ErrorCodes.SUCCESS: + return ret + + # TODO: refactor this? + with open(f"{local_folder}/calc.out") as f: + lines = f.readlines() + ind = len(lines) - 1 + + try: + while ( + lines[ind].find("Total SCF energy") == -1 + and lines[ind].find("Total DFT energy") == -1 + ): + ind -= 1 + E = float(lines[ind].split()[-1]) + except IndexError: + logger.error( + f"Could not parse the output of calculation {calc.id}: invalid format" + ) + return ErrorCodes.INVALID_OUTPUT + + prop = get_or_create(calc.parameters, calc.structure) + + cubes = {} + MO_LABELS = { + "esp": "esp", + "in.eld.total": "density", + } + for mo in ["esp", "in.eld.total"]: + path = os.path.join(local_folder, f"{mo}.cube") + if not os.path.isfile(path): + logger.error(f"Cube file {path} does not exist!") + return ErrorCodes.MISSING_FILE + with open(path) as f: + cube = "".join(f.readlines()) + + # both could be compressed together, I suppose + comp_cube = gzip.compress(bytes(cube, "utf-8")) + str_cube = base64.b64encode(comp_cube).decode("utf-8") + cubes[MO_LABELS[mo]] = str_cube + + prop.energy = E + prop.esp = json.dumps(cubes) + prop.save() + + # parse_nwchem_charges(calc, calc.structure) + + return ErrorCodes.SUCCESS + + +def nwchem_opt(in_file, calc): + local_folder = os.path.join(CALCUS_SCR_HOME, str(calc.id)) + + ret = launch_nwchem_calc(in_file, calc, ["calc.out"]) + + if ret != ErrorCodes.SUCCESS: + return ret + + converged = True + structures = [] + rmsds = [] + with open(f"{local_folder}/calc.out") as f: + lines = f.readlines() + ind = 0 + while True: + while ( + ind < len(lines) - 1 + and lines[ind].find('Geometry "geometry" -> "geometry"') == -1 + ): + ind += 1 + if ind >= len(lines) - 1: + break + ind += 7 + xyz = [] + + while lines[ind].strip() != "": + sline = lines[ind].split() + ind += 1 + xyz.append([sline[1], [float(i) for i in sline[3:]]]) + + structures.append(xyz) + while ( + lines[ind].find( + "Step Energy Delta E Gmax Grms Xrms Xmax Walltime" + ) + == -1 + and ind < len(lines) - 1 + ): + ind += 1 + + if ind >= len(lines) - 1: + break + + ind += 2 + RMSD = float(lines[ind].split()[6]) + rmsds.append(RMSD) + + try: + while lines[ind].find("Optimization converged") == -1: + ind -= 1 + except IndexError: + logger.warning(f"Optimization {calc.id} did not converge") + else: + converged = True + try: + while ( + lines[ind].find("Total SCF energy") == -1 + and lines[ind].find("Total DFT energy") == -1 + ): + ind -= 1 + E = float(lines[ind].split()[-1]) + except IndexError: + return ErrorCodes.INVALID_OUTPUT + + if converged: + s = Structure.objects.get_or_create( + parent_ensemble=calc.result_ensemble, + number=calc.structure.number, + )[0] + s.degeneracy = calc.structure.degeneracy + s.xyz_structure = format_xyz(structures[-1]) + + prop = get_or_create(calc.parameters, s) + prop.energy = E + prop.geom = True + s.save() + prop.save() + + frames = structures[:-1] + else: + frames = structures + + new_frames = [] + update_frames = [] + + for ind, (s, rmsd) in enumerate(zip(frames, rmsds)): + xyz = format_xyz(s) + try: + f = calc.calculationframe_set.get(number=ind + 1) + except CalculationFrame.DoesNotExist: + new_frames.append( + CalculationFrame( + number=ind + 1, + xyz_structure=xyz, + parent_calculation=calc, + RMSD=rmsd, + ) + ) + else: + f.xyz_structure = xyz + f.RMSD = rmsd + update_frames.append(f) + + CalculationFrame.objects.bulk_create(new_frames) + CalculationFrame.objects.bulk_update(update_frames, ["xyz_structure", "RMSD"]) + + # parse_nwchem_charges(calc, s) + + return ErrorCodes.SUCCESS + + +def nwchem_freq(in_file, calc): + local_folder = os.path.join(CALCUS_SCR_HOME, str(calc.id)) + + ret = launch_nwchem_calc(in_file, calc, ["calc.out"]) + + if ret != ErrorCodes.SUCCESS: + return ret + + base_xyz = [i + "\n" for i in calc.structure.xyz_structure.split("\n")] + num_atoms = int(base_xyz[0].strip()) + base_xyz = base_xyz[2:] + struct = [] + prop = get_or_create(calc.parameters, calc.structure) + + for line in base_xyz: + if line.strip() != "": + a, x, y, z = line.strip().split() + struct.append([a, float(x), float(y), float(z)]) + + with open(f"{local_folder}/calc.out") as f: + lines = f.readlines() + ind = len(lines) - 1 + + try: + while lines[ind].find("Zero-Point correction to Energy") == -1: + ind -= 1 + # Temperature = 298.15K + T = float(lines[ind - 3].split()[-1][:1]) # K + + ZPE = float(lines[ind].split()[-2]) # Eh + Hcorr = float(lines[ind + 2].split()[-2]) # Eh + S = float(lines[ind + 4].split()[-2]) # cal/(mol*K) + + while ( + lines[ind].find("Total SCF energy") == -1 + and lines[ind].find("Total DFT energy") == -1 + ): + ind -= 1 + E = float(lines[ind].split()[-1]) # Eh + except IndexError: + raise + logger.error( + f"Could not parse the output of calculation {calc.id}: invalid format" + ) + return ErrorCodes.INVALID_OUTPUT + + G = E + Hcorr - T * S / (1000 * HARTREE_TO_KCAL_F) + + prop.energy = E + prop.free_energy = G + + try: + while ( + lines[ind].find("NORMAL MODE EIGENVECTORS IN CARTESIAN COORDINATES") + == -1 + ): + ind += 1 + + ind += 4 + + vib_freqs = [] + freqs = [] # each entry is the animation for one vib mode + while lines[ind].strip() != "": + num_line = len(lines[ind].strip().split()) + end_num = int(lines[ind].split()[-1]) + ind += 2 + + vib_freqs += [float(i) for i in lines[ind].split()[1:]] + + ind += 2 + + vib = [] + for i in range(num_line): + vib.append([]) + + for j in range(num_atoms * 3): + sline = lines[ind].split() + for i in range(num_line): + coord = float(sline[1 + i]) + vib[i].append(coord) + ind += 1 + + #### + def is_all_null(arr): + for el in arr: + if float(el) != 0: + return False + return True + + freqs += vib + ind += 1 + except IndexError: + raise + logger.error( + f"Could not parse the frequency modes of calculation {calc.id}: invalid format" + ) + return ErrorCodes.INVALID_OUTPUT + + freq_animations = [] + for ind in range(len(freqs)): + anim = f"{num_atoms}\nCalcUS\n" + assert len(struct) == num_atoms + for ind2, (a, x, y, z) in enumerate(struct): + anim += "{} {:.4f} {:.4f} {:.4f} {} {} {}\n".format( + a, x, y, z, *freqs[ind][3 * ind2 : 3 * ind2 + 3] + ) + freq_animations.append(anim) + + # Remove translation and rotation + # TODO: test for linear molecules + prop.freq_animations = freq_animations[6:] + prop.freq_list = vib_freqs[6:] + prop.save() + + # vib_file = os.path.join(local_folder, "vibspectrum") + + """ + if os.path.isfile(vib_file): + with open(vib_file) as f: + lines = f.readlines() + + vibs = [] + intensities = [] + for line in lines: + if len(line.split()) > 4 and line[0] != "#": + sline = line.split() + try: + a = float(sline[1]) + if a == 0.0: + continue + except ValueError: + pass + vib = float(line[20:33].strip()) + vibs.append(vib) + try: + intensity = float(sline[3]) + except ValueError: + continue + else: + intensities.append(intensity) + if len(vibs) == len(intensities) and len(intensities) > 0: + x = np.arange(500, 4000, 1) # Wave number in cm^-1 + spectrum = plot_vibs(x, zip(vibs, intensities)) + data = "Wavenumber,Intensity\n" + intensities = 1000 * np.array(intensities) / max(intensities) + for _x, i in sorted((zip(list(x), spectrum)), reverse=True): + data += f"-{_x:.1f},{i:.5f}\n" + + prop.ir_spectrum = data + prop.freq_list = vibs + + """ + prop.save() + return ErrorCodes.SUCCESS + + def calc_to_ccinput(calc): if calc.parameters.method != "": _method = calc.parameters.method @@ -2215,10 +2865,11 @@ def calc_to_ccinput(calc): raise Exception( f"No method specified; theory level is {calc.parameters.theory_level}" ) + _specifications = calc.parameters.specifications software = calc.parameters.software.lower() - if software in ["gaussian", "orca"]: + if software in ["gaussian", "orca"]: # TODO: NWChem? _specifications += " " + getattr(calc.order.author, "default_" + software) _solvation_radii = calc.parameters.solvation_radii else: @@ -2240,9 +2891,30 @@ def calc_to_ccinput(calc): _nproc = calc.order.resource.pal _mem = calc.order.resource.memory + _step_name = calc.step.name + + if calc.parameters.software.lower() == "xtb" and ( + calc.step.name == "Fast Conformational Search" + ): + _step_name = "OPT" + + if ( + calc.parameters.software.lower() == "xtb" + and (calc.parameters.driver.lower() == "pysis") + and (calc.step.name == "TS Optimization") + ): + if "tsopt(thresh=" not in _specifications: + _specifications += "tsopt(thresh=gau_vtight)" + + # patch + if calc.parameters.software.lower() == "nwchem" and ( + calc.step.name == "MO Calculation" or calc.step.name == "ESP Calculation" + ): + _step_name = "SP" + params = { "software": calc.parameters.software, - "type": calc.step.name, + "type": _step_name, "method": _method, "basis_set": calc.parameters.basis_set, "solvent": calc.parameters.solvent, @@ -2261,9 +2933,13 @@ def calc_to_ccinput(calc): "name": "in", "driver": calc.parameters.driver, } + try: inp = generate_calculation(**params) - except CCInputException as e: + except Exception as e: + logger.error( + f"Could not generate the input file: got exception {str(e)} from ccinput" + ) return e return inp @@ -2295,6 +2971,7 @@ def launch_gaussian_calc(in_file, calc, files): ret = system("g16 calc.com", software="Gaussian", calc_id=calc.id) else: os.chdir(local_folder) + # There could be a race condition here ret = system("g16 calc.com", software="Gaussian", calc_id=calc.id) cancelled = False @@ -3236,12 +3913,14 @@ def gen_fingerprint(structure): stderr=stream, ).decode("utf-8") - inchi = out.split("\n")[-2] - if inchi[:6] != "InChI=": + stext = out.split("\n") + + for line in stext: + if line[:6] == "InChI=": + return line[6:] + else: logger.warning(f"Invalid InChI key obtained for structure {structure.id}") return "" - else: - return inchi[6:] def analyse_opt(calc_id): @@ -3251,6 +3930,7 @@ def analyse_opt(calc_id): "xtb": analyse_opt_xtb, "pysis": analyse_opt_pysis, "pysisyphus": analyse_opt_pysis, + "nwchem": analyse_opt_nwchem, } calc = Calculation.objects.get(pk=calc_id) @@ -3266,7 +3946,16 @@ def analyse_opt(calc_id): return funcs[calc.parameters.software.lower()](calc) +def analyse_opt_nwchem(calc): + # TODO + return + + def analyse_opt_pysis(calc): + logger.warning( + "Parsing the Pysisyphus optimisation trajectory is currently not implemented" + ) + return # TODO prepath = os.path.join(CALCUS_SCR_HOME, str(calc.id)) @@ -3565,6 +4254,7 @@ def get_Gaussian_xyz(text): SPECIAL_FUNCTIONALS = ["HF-3c", "PBEh-3c"] + BASICSTEP_TABLE = { "xtb": { "Geometrical Optimisation": xtb_opt, @@ -3576,6 +4266,7 @@ def get_Gaussian_xyz(text): "Single-Point Energy": xtb_sp, "Minimum Energy Path": mep, "Constrained Conformational Search": crest, + "Fast Conformational Search": fast_conf, }, "ORCA": { "NMR Prediction": orca_nmr, @@ -3596,6 +4287,17 @@ def get_Gaussian_xyz(text): "Single-Point Energy": gaussian_sp, "UV-Vis Calculation": gaussian_td, }, + "NWChem": { + # "NMR Prediction": nwchem_nmr, + "Geometrical Optimisation": nwchem_opt, + # "TS Optimisation": nwchem_ts, ############## + "MO Calculation": nwchem_mo_gen, + "ESP Calculation": nwchem_esp_gen, + "Frequency Calculation": nwchem_freq, + # "Constrained Optimisation": nwchem_scan, + "Single-Point Energy": nwchem_sp, + # "Minimum Energy Path": mep, + }, } time_dict = {} @@ -3733,6 +4435,10 @@ def dispatcher(order_id, drawing=None, is_flowchart=False, flowchartStepObjectId ) except Molecule.DoesNotExist: """ + + if len(fingerprint) > 9900: + fingerprint = fingerprint[:9900] + ensemble.parent_molecule.inchi = fingerprint ensemble.parent_molecule.save() molecule = ensemble.parent_molecule @@ -3913,9 +4619,79 @@ def dispatcher(order_id, drawing=None, is_flowchart=False, flowchartStepObjectId c.save() +NWCHEM_MO_FIX = """ +dplot + TITLE HOMO + vectors in.movecs + LimitXYZ + -5.0 5.0 40 + -5.0 5.0 40 + -5.0 5.0 40 + spin total + ORBITALS view; 1; {} + gaussian + output in-HOMO.cube +end +task dplot + +dplot + TITLE LUMO + vectors in.movecs + LimitXYZ + -5.0 5.0 40 + -5.0 5.0 40 + -5.0 5.0 40 + spin total + ORBITALS view; 1; {} + gaussian + output in-LUMO.cube +end +task dplot + +dplot + TITLE LUMOA + vectors in.movecs + LimitXYZ + -5.0 5.0 40 + -5.0 5.0 40 + -5.0 5.0 40 + spin total + ORBITALS view; 1; {} + gaussian + output in-LUMOA.cube +end +task dplot + +dplot + TITLE LUMOB + vectors in.movecs + LimitXYZ + -5.0 5.0 40 + -5.0 5.0 40 + -5.0 5.0 40 + spin total + ORBITALS view; 1; {} + gaussian + output in-LUMOB.cube +end +task dplot + +""" + +NWCHEM_ESP_FIX = """ +property + ESP + ELECTRONDENSITY + grid pad 2.0 step 0.2 output esp.cube +end + +task dft property +""" + + def add_input_to_calc(calc): inp = calc_to_ccinput(calc) - if isinstance(inp, CCInputException): + if isinstance(inp, Exception): msg = f"CCInput error: {str(inp)}" if IS_TEST or settings.DEBUG: print(msg) @@ -3924,10 +4700,35 @@ def add_input_to_calc(calc): calc.save() return ErrorCodes.FAILED_TO_CREATE_INPUT - calc.input_file = inp.input_file + input_file = inp.input_file + + # Quick fix + if ( + calc.parameters.software.lower() == "nwchem" + and calc.step.name == "MO Calculation" + ): + electrons = 0 + for line in calc.structure.xyz_structure.split("\n")[2:]: + if line.strip() == "": + continue + el = line.split()[0] + electrons += ATOMIC_NUMBER[el] + + electrons -= calc.parameters.charge + n_homo = electrons // 2 + + input_file += NWCHEM_MO_FIX.format(n_homo, n_homo + 1, n_homo + 2, n_homo + 3) + elif ( + calc.parameters.software.lower() == "nwchem" + and calc.step.name == "ESP Calculation" + ): + input_file += NWCHEM_ESP_FIX + + calc.input_file = input_file - if hasattr(inp, "confirmed_specifications"): - calc.parameters.specifications = inp.confirmed_specifications + # TODO: make ccinput support keywords in a more general way + # if hasattr(inp, "confirmed_specifications"): + # calc.parameters.specifications = inp.confirmed_specifications if hasattr(inp, "command"): calc.command = inp.command @@ -3937,7 +4738,16 @@ def add_input_to_calc(calc): def load_output_files(calc): - output_files = {} + if not os.path.isdir(os.path.join(CALCUS_SCR_HOME, str(calc.id))): + logger.warning( + f"Cannot load the logs of calculation {calc.id}: no scratch directory" + ) + return + + if calc.output_files.strip() in ["{}", ""]: + output_files = {} + else: + output_files = json.loads(calc.output_files) for log_name in glob.glob( os.path.join(CALCUS_SCR_HOME, str(calc.id), "*.log") ) + glob.glob(os.path.join(CALCUS_SCR_HOME, str(calc.id), "*.out")): @@ -3946,6 +4756,10 @@ def load_output_files(calc): log = "".join(f.readlines()) output_files[fname] = log + logger.info( + f"Loaded {len(output_files)} output file(s) for calculation {str(calc.id)} (scr: {CALCUS_SCR_HOME})" + ) + calc.output_files = json.dumps(output_files) calc.save() @@ -3990,6 +4804,7 @@ def get_calc(calc_id): ret = None else: ret = add_input_to_calc(calc) + calc.refresh_from_db() if isinstance(ret, ErrorCodes): return ret @@ -4049,8 +4864,8 @@ def get_calc(calc_id): calc.error_message = "Failed to execute the relevant command" else: logger.warning(f"Calculation {calc.id} finished with return code {ret}") + calc.status = 3 if calc.error_message == "": - calc.status = 3 calc.error_message = "Unknown termination" calc.date_finished = timezone.now() @@ -4065,10 +4880,14 @@ def get_calc(calc_id): 1, round((calc.date_finished - calc.date_started).total_seconds()) ) + calc.save() + if calc.step.creates_ensemble: analyse_opt(calc.id) + calc.refresh_from_db() load_output_files(calc) + calc.refresh_from_db() global cache_ind @@ -4244,6 +5063,7 @@ def kill_calc(calc): if calc.status not in [2, 3]: calc.set_as_cancelled() return + logger.info(f"Trying to kill calc {calc.id}") if calc.local: if calc.task_id != "": @@ -4282,17 +5102,61 @@ def ping_satellite(): ) -def create_subscription(user, duration=1): - alloc = ResourceAllocation.objects.create( - code=get_random_string(32), - allocation_seconds=1000 * 60, - note=ResourceAllocation.SUBSCRIPTION, - ) # TODO: make dynamic - alloc.redeem(user) - sub = Subscription.objects.create( - subscriber=user, - associated_allocation=alloc, - duration=duration, - start_date=timezone.now(), - end_date=timezone.now() + timezone.timedelta(days=duration * 31), +def create_account_sub(email): + # The email must not be used by any other user already + default_pass = get_random_string() + user = User.objects.create_user(email, password=default_pass) + user.is_temporary = False + user.is_trial = False + user.save() + + send_mail( + "Your CalcUS Cloud account", + 'Your CalcUS Cloud account has been created. To log in, enter this email ({email}) and the temporary password "{default_pass}" on the login page (https://calcus.cloud/accounts/login/). Make sure to change the account password immediately after logging in by going on the profile page and clicking on the red "Change password" button.', + "bot.calcus.cloud@gmail.com", + recipient_list=[email], ) + logger.info( + f"Created the account with email {email} and sent instructions to that email." + ) + + return user + + +def create_subscription( + user, stripe_sub_id, end_date, allocation, stripe_cus_id, will_renew +): + time_left = max(0, user.allocated_seconds - user.billed_seconds) + sub_amount = max(allocation - time_left, 0) + + # Make sure we don't create a duplicate subscription and allocation + try: + sub = Subscription.objects.get(subscriber=user, end_date=end_date) + except Subscription.DoesNotExist: + alloc = ResourceAllocation.objects.create( + code=get_random_string(32), + allocation_seconds=sub_amount, + note=ResourceAllocation.SUBSCRIPTION, + ) + alloc.redeem(user) + sub = Subscription.objects.create( + subscriber=user, + associated_allocation=alloc, + stripe_sub_id=stripe_sub_id, + start_date=timezone.now(), + end_date=end_date, + ) + else: + logger.warning( + f"Received duplicate subscription creation request for user {user.id} ({stripe_sub_id})" + ) + return + + user.refresh_from_db() + if user.stripe_cus_id != "" and user.stripe_cus_id != stripe_cus_id: + logger.warning( + f"Changing the Stripe customer id of user {user.id} from {user.stripe_cus_id} to {stripe_cus_id}" + ) + user.stripe_cus_id = stripe_cus_id + user.stripe_will_renew = will_renew + user.save() diff --git a/frontend/templates/best_practices/conformational_search.html b/frontend/templates/best_practices/conformational_search.html new file mode 100755 index 00000000..250eec41 --- /dev/null +++ b/frontend/templates/best_practices/conformational_search.html @@ -0,0 +1,626 @@ +{% extends 'frontend/base.html' %} {% load static %} {% block extrahead %} + + + + + + + + + + + + + + + +Conformational Sampling + + +{% endblock %} +{% block content %} + +
+

Conformational Sampling

+

What conformational sampling is and why it is important

+

+ In computational chemistry, conformational sampling plays a crucial role in understanding the properties and reactivity of molecules. Conformational sampling refers to the exploration of different three-dimensional arrangements, or conformations, that a molecule can adopt. This spacial arrangement can greatly influence the properties of interest because of differences in the distances between atoms, in orbital interactions and in steric constraints, among other factors. +

+

+Molecules are exist in dynamic environments, constantly undergoing thermal motion and fluctuating between a range of conformations. What we mean by "conformation" is a local minimum in terms of all molecular coordinates, the bottom of a potential well on the potential energy surface (PES). In reality, molecules do not adopt strictly this conformation, but oscillates around this local minimum until it crosses over to another local minimum. +

+

+In the context of computational chemistry, we often use conformations (the minima) in order to study the properties of the molecule in its state of flux. However, determining the most energetically favorable or relevant conformations for a given molecule is a complex task due to the high dimensionality of the conformational space, namely $3N - 6$ dimensions (or $3N - 5$ for linear molecules). Conformational sampling methods aim to address this challenge by efficiently exploring this vast space and identifying all the significant local minima. +

+
+
+ Example: the conformations of butane +
+
+

+ Butane has only two unique stable conformations. The most stable conformation is obtained when the carbon backbone forms a zig-zag, putting the terminal methyl groups anti to each other (State A below). In this conformation, all the hydrogens are staggered to each other and the methyl groups are as far from each other as they can be. +

+

+ The second stable conformation also has every group staggered to each other, but the methyl groups are gauche to each other (State C). To go from one conformation to the other, butane must pass over an energy barrier (State B). The maximum point of the barrier (the transition state) occurs when the groups of the internal carbons are eclipsed and thus repulse each other the most. An even greater barrier occurs at State D due to the eclipsed methyl groups. +

+

+ Only one molecular coordinate is considered in this example, but larger flexible molecules have many molecular coordinates that must be explored. For example, if we consider longer linear alkane chains, the number of conformations featuring only staggered groups increases exponentially. Moreover, functional groups that can interact favorably with each other can force chains to fold due to a stability gain greater than the energy cost of bringing groups gauche to each other. The most notable example is the case of proteins, which fold into very specific structures due to internal hydrogen bonds. +

+
+
+
+
+
+
+
+
+
+
Speed
+
+ +
+
+ +
+ Manual Animation +
+
+
+ +

How conformations are found

+

+Various algorithms and techniques have been developed to explore the conformational space effectively. These include systematic search methods, such as the systematic torsion angle sampling or grid-based techniques, as well as stochastic search methods, like Monte Carlo methods and molecular dynamics simulations. Each approach has its strengths and limitations, and the choice of method depends on the specific objectives and computational resources available. +

+

+ One relatively recent solution to this problem has been developed and made freely available by the group of Stefan Grimme in a software called CREST. This approach makes extensive use of the extremely fast GFNn-xTB methods (available through the xtb package), which enable dynamics simulations for all the supported elements (hydrogen to radon). These simulations could be done using standard forcefields instead of GFNn-xTB, but this is very restrictive in terms of the supported molecules. Indeed, most forcefields have been developed with peptides and proteins in mind. Other molecules can be simulated using forcefields if proper parameters can be developed for these specific molecules. The GFNn-xTB methods thus provide a very convenient way to simulate the dynamics of almost any molecule. Moreover, the Grimme group have developed a forcefield method inspired from the GFNn-xTB methods and developed an automated parameterization procedure in order to keep the same generality. Among the three GFNn-xTB methods (n from 0 to 2), GFN2-xTB is the most accurate, and thus we will only consider this method out of the three here. +

+

+Conformational sampling is achieved by using molecular dynamics simulations to find new conformers. Starting from the initial conformation of the molecule, multiple other conformations will be explored throughout the simulations, purely through the random atomic movements within the molecule. With long enough simulations, all conformations will be visited by the molecule. +

+

+ However, depending on the flexibility of the molecule and the conformational change barriers, the required simulation time might be unreasonable in these conditions. In order to accelerate the exploration of new conformations, an artificial potential is periodically added around the current configuration. This potential increases the potential energy of the geometries that have been explored and thus "lowers" the barriers to new conformations. This can be seen as pouring a bucket of sand in the potential well and reducing its depth. After this has been done enough times, the potential well will be very shallow and the molecule will exit it towards new conformations. This type of dynamics is called meta-dynamics and is extensively used in the CREST package. Meta-dynamics are discussed in more detail in S. Grimme, J. Chem. Theory Comput., 2019, 155, 2847-2862. +

+

+ The full conformational sampling procedure is explained in details on the CREST documentation. In brief, meta-dynamics simulations are used to iteratively explore more and more possible conformations until no lower energy conformation is found. The structures that are found are reoptimized and filtered down to unique conformations. Normal molecular dynamics are launched starting from the most stable conformers in order to explore nearby conformations. Finally, the new orientations of parts of the molecule are applied to other conformers in order to generate a hybrid conformation, which is likely of similar energy as its parents. This is called "Genetic Z-matrix crossing", as it uses an approach related to genetic algorithms to combined conformations into new ones without relying on dynamics simulation. +

+

The computational cost of conformational sampling

+

+ The time required to perform the conformational sampling increases rapidly with the number of atoms in the molecule and its flexibility. To give you an idea of the resources required for your molecules, we have benchmarked the conformational search of very flexible molecules (linear alkanes) and very rigid molecules (insaturated polycycles). The GFN2-xTB calculations also used the analytical linearized Poisson-Boltzmann (ALPB) implicit solvation model to represent a typical use case (see S. Ehlert, M. Stahn, S. Spicher, S. Grimme, J. Chem. Theory Comput., 2021, 17, 4250-4261). The calculations were performed using 8 vCPU cores on CalcUS Cloud. The time reported is the computing time used, which is 8 times greater than the real elapsed time. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumber
of atoms
CPU Time
(GFN-FF)
CPU Time
(GFN2-xTB/ALPB(H2O))
Number of
conformers*
Butane14804002
Heptane23192200816-17
Decane32552804033-48
Hexadecane5010304101488143-197
Eicosane6280264-999
Benzene12644001
Biphenyl229611361-2
Pyrene2612821201
Coronene3633642001
Bicoronene70688191121-2
+
+

*This number can vary greatly depending on the parameters used, notably the maximal relative energy and the threshold RMSD for unique conformers (see the text for more details).

+ +

+ While the conformational sampling of small molecules like butane and benzene required similar durations, the different quickly increases. In the extreme case, we can compare eicosane ($C_{20}H_{42}$), which took 22 CPU-hours with GFN-FF, to bicoronene ($C_{48}H_{22}$), which took under 12 CPU-minutes, despite having slightly more atoms. +

+

+ The conformational sampling compute time grows exponentially with the number of atoms, and the flexibility of the molecules dictates how fast the time will grow. The two figures below illustrate the enormous difference between the rigid insaturated hydrocarbons in blue and the flexible alkane chains in red. The two series follow quite closely an exponential curve for molecules between 12 and 70 atoms. We can thus use the trendlines as guides to predict the minimal and maximal compute times to expect from a molecule with a given number of atoms. We can hardly predict exactly how much time the conformational sampling will take, but we can be confident that it will be between these two extremes. +

+ +
+
+
+ +
Scaling of GFN-FF (vacuum)
+
+
+ +
Scaling of GFN2-xTB with ALPB(H2O)
+
+
+
+ +
+
+ Cheatsheet: expected CPU seconds for conformational sampling on CalcUS Cloud +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Number of atomsPredicted CPU Time
(GFN-FF)
Predicted CPU Time
(GFN2-xTB/ALPB(H2O))
2090 — 1401000 — 1200
30140 — 5902000 — 5200
40210 — 25003700 — 24,000
50320 — 11,0006900 — 110,000
60480 — 47,00013,000 — 500,000
70730 — 200,00024,000 — 2,300,000
+
+
+
+ +

How unique conformations are identified

+

+ The conformational search process generates a large number of structures which may or may not correspond to known conformers. Even if two structures are visually indistinguishable to the human eye, its numerical representation (i.e., the 3D coordinates of the atoms) will not be identical. This is because structures remain chemically identical after translations or rotations, while the numerical representation is completely different. Moreover, tiny variations in the coordinates would make the coordinates different, while humans would classify them as essentially identical. +

+

+ In order to compare structures mathematically more rigorously, we can use their root mean squared deviation (RMSD) as difference metric. In words, this criteria expresses the average magnitude of the deviation between the position of the atoms in one structure compared to the other. It is usually implicitly assumed that both structures have been aligned (through translations and rotations) in order to obtain the smallest RMSD possible. This means that two structures whose internal coordinates are identical will have a RMSD of 0 with respect to each other. If the structures have slightly different bond distances or different angles within the molecule, the RMSD will quantify to what extent the structures differ. +

+ +
+
+ RMSD = $\sqrt{\frac{\sum_{i=1}^N \left((x_{Ai} - x_{Bi})^2 + (y_{Ai} - y_{Bi})^2 + (z_{Ai} - z_{Bi})^2\right)}{N}}$ +
+ +
+
+ Example: the structure of $NO_2$ +
+
+ +
+ +
RMSD between the GFN-FF structure (red) and GFN2-xTB structure (blue)
+
+

+ Both structures were aligned in order to minimize the RMSD. Due to the different N-O bond distances and O-N-O angles, the atoms are slightly offset. In this case, the RMSD is 0.125 Å, which is quite small. +

+
+
+
+ +

+ In the context of conformational sampling, we use a certain threshold RMSD above which two structures are considered to be different conformations. By default, the RMSD threshold of conformational searches on CalcUS Cloud is 2.0 Å, which is generally suitable for molecules with average flexibility and moderate size. However, if you wish to change this threshold, you can use the calculation specification --rthr NEWVALUE when launching the calculation. Additional available options are explained in the CalcUS documentation. +

+

+ There are however some caveats with respect to the RMSD. It requires all the atoms to be distinguishable, meaning that we know with which atom in structure B we should compare each atom in structure A. While this is generally not a problem, it can cause duplicate conformers if there are equivalent atoms in the molecule. For example, a methyl group has three equivalent hydrogen atoms. If the methyl group rotates by 120 degrees, the structure is chemically identical, since there is still three hydrogen atoms at the same positions. However, the indices of these atoms are different, meaning that the RMSD will compare the positions of the "wrong" hydrogen atoms and will not be null. It is thus possible that these two structures will be considered to be two unique conformations, although this is chemically false. +

+ +

General Recipe

+ +

+

    +
  1. + Select your input structure on the launch page, either through the online sketcher or by uploading the structure in a suitable format. +
  2. +
  3. + Specify the molecule name, the solvent (if desired), the project and pick the xtb software. The calculation type should be "Conformational Search". +
  4. +
  5. + Decide on the best method to use. Refer to the table above to estimate the approximate CPU time required with each method. If your system contains transition metals or exotic valence, it would be wise to optimise the structure using GFN-FF to verify if its structure changes significantly. If it does, GFN-FF might not be suitable. If the computing time is reasonable, using GFN2-xTB is a safe choice. +
  6. +
  7. + If desired, add specifications. +
  8. +
  9. + Submit the calculation and wait for it to finish. Conformational searches do not start immediately like other xtb calculations, since they are executed on an entire virtual machine, which takes some time to start up. +
  10. +
  11. + Once the calculation is done, click on the calculation order to view the ensemble page. You will see a table with all the unique conformers ranked by energy. +
  12. +
  13. + Use the ensemble (or a subset of it) for further calculations (e.g., frequency calculation to calculate the free energy). The Boltzmann-weighted ensemble averages will be calculated for energetic properties (energy and free energy). These values are more representative of the true value of the molecule than any individual conformation, since they take into account all (unique) significant conformations. +
  14. + +
+

+ +

Tips and guidelines

+

+

+

+
+{% endblock %} diff --git a/frontend/templates/frontend/analyse.html b/frontend/templates/frontend/analyse.html index d50da552..e35e4adc 100755 --- a/frontend/templates/frontend/analyse.html +++ b/frontend/templates/frontend/analyse.html @@ -1,5 +1,5 @@ {% extends 'frontend/base.html' %} {% load static %} {% block extrahead %} - + + + {% block extrahead %} {% endblock %} + + {% if IS_CLOUD %} + {% autoescape off %} + {% get_extra_body_code %} + {% endautoescape %} + {% endif %}
+ + diff --git a/frontend/templates/frontend/form/ensemble_name_body.html b/frontend/templates/frontend/form/ensemble_name_body.html new file mode 100644 index 00000000..89a89ffe --- /dev/null +++ b/frontend/templates/frontend/form/ensemble_name_body.html @@ -0,0 +1,6 @@ +
+ +
+ +
+
diff --git a/frontend/templates/frontend/form/file_upload_body.html b/frontend/templates/frontend/form/file_upload_body.html new file mode 100644 index 00000000..b79044c7 --- /dev/null +++ b/frontend/templates/frontend/form/file_upload_body.html @@ -0,0 +1,34 @@ +
+
+ +
+ +
+
+
+ + + diff --git a/frontend/templates/frontend/form/file_upload_head.js b/frontend/templates/frontend/form/file_upload_head.js new file mode 100644 index 00000000..eaccfa33 --- /dev/null +++ b/frontend/templates/frontend/form/file_upload_head.js @@ -0,0 +1,54 @@ +function file_upload_changed() { + file_list = this.files; + add_file_name(); + preview_upload(file_list); + + ff = file_list[0].name; + fname = ff.replace('C:\\fakepath\\', ''); + if(file_list.length > 1) { + fname += ', ...'; + } + + $("#file_upload_name").html(fname); + + combine = document.getElementById("calc_combine_files_column"); + parse = document.getElementById("calc_parse_filenames_column"); + if (file_list.length > 1) { + combine.style.display = "block"; + parse.style.display = "block"; + } + else { + combine.style.display = "none"; + parse.style.display = "none"; + } +} +function aux_file_upload_changed() { + file_list = this.files; + ff = file_list[0].name; + fname = ff.replace('C:\\fakepath\\', ''); + if(file_list.length > 1) { + fname += ', ...'; + } + + $("#aux_file_upload_name").html(fname); + if(fname.length > 1) + $("#calc_aux_structure").hide() + else + $("#calc_aux_structure").show() +} +function toggle_visibility_molname(check) { + if(check.checked) { + $("#calc_mol_name_field").hide(); + } + else{ + $("#calc_mol_name_field").show(); + } +} +function add_file_name() { + path = $("#file_structure").val(); + file_name = path.replace('C:\\fakepath\\', '').split('.')[0]; + curr_name = document.getElementById("calc_mol_name").value; + if(curr_name == "") { + $("#calc_mol_name").val(file_name); + } +} diff --git a/frontend/templates/frontend/form/filter_body.html b/frontend/templates/frontend/form/filter_body.html new file mode 100644 index 00000000..acd5647a --- /dev/null +++ b/frontend/templates/frontend/form/filter_body.html @@ -0,0 +1,38 @@ +
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
diff --git a/frontend/templates/frontend/form/filter_head.js b/frontend/templates/frontend/form/filter_head.js new file mode 100644 index 00000000..bf69ec5f --- /dev/null +++ b/frontend/templates/frontend/form/filter_head.js @@ -0,0 +1,21 @@ +function filter_changed() { + choice = document.getElementById("calc_filter"); + value_input = document.getElementById("calc_filter_value"); + param_input = document.getElementById("calc_filter_params"); + label = document.getElementById("filter_label"); + if(choice.value == "None") { + value_input.style.display = "none"; + param_input.style.display = "none"; + } + else if(choice.value == "By Boltzmann Weight") { + value_input.style.display = "block"; + param_input.style.display = "block"; + label.innerHTML = "Higher than"; + } + else if(choice.value == "By Relative Energy") { + value_input.style.display = "block"; + param_input.style.display = "block"; + label.innerHTML = "Lower than (" + "{{ profile.pref_units_name }}" + ")"; + } +} + diff --git a/frontend/templates/frontend/form/input_widgets_body.html b/frontend/templates/frontend/form/input_widgets_body.html new file mode 100644 index 00000000..a2d972dc --- /dev/null +++ b/frontend/templates/frontend/form/input_widgets_body.html @@ -0,0 +1,55 @@ +{% if not ensemble and not structure and not calc %} +
+

Load structure from name

+
+ +
+ +
+
+{% endif %} + +
+

3D Viewer

+ + +
+ +
+ +{% if not ensemble and not structure and not calc %} +
+

Sketcher

+ +
+ + +
+ + +{% endif %} +
diff --git a/frontend/templates/frontend/form/input_widgets_head.js b/frontend/templates/frontend/form/input_widgets_head.js new file mode 100644 index 00000000..5239d240 --- /dev/null +++ b/frontend/templates/frontend/form/input_widgets_head.js @@ -0,0 +1,54 @@ +function load_nih(btn) { + $(btn).addClass("is-loading"); + let name = document.getElementById("nih_name").value; + $.ajax({ + method: "POST", + url: "https://cactus.nci.nih.gov/chemical/structure/" + name + "//file?format=mol" + }).success(function(data) { + let mol_read = ChemDoodle.readMOL(data, multiplier=1); + ChemDoodle.relabel(mol_read); + viewer.clear(); + viewer.loadMolecule(mol_read); + sketcher.clear(); + sketcher.loadMolecule(ChemDoodle.readMOL(data)); + sketcher.center(); + Jmol.script(editor, 'load inline "' + data + '"'); + $("#calc_mol_name").val(name); + $("#nih_msg").text(""); + }).fail(function(data) { + $("#nih_msg").text("Sorry, could not find " + name); + }).always(function() { + $(btn).removeClass("is-loading"); + }); +} + +function add_3D() { + let mol = ChemDoodle.writeMOL(sketcher.getMolecule()); + document.getElementById("add_3D_btn").classList.add("is-loading"); + $.ajax({ + method: "POST", + url: "/gen_3D/", + headers: { + "X-CSRFToken": '{{ csrf_token }}', + }, + + data: {'mol': mol, 'randomize': true}, + statusCode: { + 404: function(data) { + $("#3d_msg").html("Error while generating structure"); + }, + 200: function(data) { + $("#3d_msg").html(""); + let mol_read = ChemDoodle.readXYZ(data); + ChemDoodle.relabel(mol_read); + viewer.addMolecule(mol_read); + document.getElementById("add_3D_btn").classList.remove("is-loading"); + + Jmol.script(editor, 'load APPEND inline "' + data + '"'); + } + } + }).always(function() { + document.getElementById("gen_3D_btn").classList.remove("is-loading"); + }); + +} diff --git a/frontend/templates/frontend/form/mol_name_body.html b/frontend/templates/frontend/form/mol_name_body.html new file mode 100644 index 00000000..ec13b3e1 --- /dev/null +++ b/frontend/templates/frontend/form/mol_name_body.html @@ -0,0 +1,7 @@ +
+ +
+ +
+
+ diff --git a/frontend/templates/frontend/form/multiplicity_body.html b/frontend/templates/frontend/form/multiplicity_body.html new file mode 100644 index 00000000..79598087 --- /dev/null +++ b/frontend/templates/frontend/form/multiplicity_body.html @@ -0,0 +1,6 @@ +
+ +
+ +
+
diff --git a/frontend/templates/frontend/form/preset_body.html b/frontend/templates/frontend/form/preset_body.html new file mode 100644 index 00000000..fb2165ee --- /dev/null +++ b/frontend/templates/frontend/form/preset_body.html @@ -0,0 +1,17 @@ +
+
+
+ +
+
+ Load Preset + Save Preset + Delete Preset +
+ Set current parameters as project defaults +
+ +
+
+
diff --git a/frontend/templates/frontend/form/preset_head.js b/frontend/templates/frontend/form/preset_head.js new file mode 100644 index 00000000..9e451b43 --- /dev/null +++ b/frontend/templates/frontend/form/preset_head.js @@ -0,0 +1,67 @@ +function save_preset() { + preset_name = prompt("Name of the preset?", "") + if(preset_name != null) { + data = $("#calcform").serializeArray(); + data.push({ name: 'preset_name', value: preset_name}); + $.ajax({ + data: data, + headers: { + "X-CSRFToken": '{{ csrf_token }}', + }, + + type: "POST", + url: "/save_preset/", + success: function (response) { + $("#preset_msg").html(response); + refresh_presets(); + } + }); + } + +} + +function load_selected_preset() { + preset = document.getElementById("presets").value; + load_preset(preset); +} +function delete_preset() { + preset = document.getElementById("presets").value; + $.ajax({ + headers: { + "X-CSRFToken": '{{ csrf_token }}', + }, + + type: "POST", + url: "/delete_preset/" + preset, + success: function (response) { + $("#preset_msg").html(response); + refresh_presets(); + } + }); + +} + +function refresh_presets() { + $("#presets").load("/presets/"); +} + +function load_preset(id) { + if(id == undefined) { + return; + } + $.ajax({ + method: "POST", + url: "/load_preset/" + id, + headers: { + "X-CSRFToken": '{{ csrf_token }}', + }, + success: function(data) { + eval(data); + refresh_availabilities(); + check("functional"); + check("basis_set"); + check("solvent"); + refresh_availabilities(); + }, + }); +} diff --git a/frontend/templates/frontend/form/project_body.html b/frontend/templates/frontend/form/project_body.html new file mode 100644 index 00000000..d70e1a72 --- /dev/null +++ b/frontend/templates/frontend/form/project_body.html @@ -0,0 +1,26 @@ +
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
diff --git a/frontend/templates/frontend/form/project_head.js b/frontend/templates/frontend/form/project_head.js new file mode 100644 index 00000000..6a71d166 --- /dev/null +++ b/frontend/templates/frontend/form/project_head.js @@ -0,0 +1,42 @@ +function project_selection_changed(choice) { + field = document.getElementById("new_project_name_field"); + if(choice.value == "New Project") { + field.style.display = "block"; + } + else { + field.style.display = "none"; + if(project_presets[choice.value] != -1) { + load_preset(project_presets[choice.value]); + } + type = document.getElementById("calc_type"); + if(type.value == "Minimum Energy Path") { + refresh_aux_mol(); + } + + } +} + +function preselect_last_project() { + $("#calc_project").val(localStorage.getItem("lastSelectedProject")); +} + +function set_project_default() { + $.ajax({ + data: $("#calcform").serialize(), + headers: { + "X-CSRFToken": '{{ csrf_token }}', + }, + + type: "POST", + url: "/set_project_default/", + success: function (response) { + $("#preset_msg").html(response); + refresh_presets(); + } + }); +} +var project_presets = { + {% for proj in profile.project_set.all %} + '{{ proj.name }}': {% if proj.preset %}"{{ proj.preset.id }}"{% else %} -1 {% endif %}, + {% endfor %} +} diff --git a/frontend/templates/frontend/form/resource_body.html b/frontend/templates/frontend/form/resource_body.html new file mode 100644 index 00000000..be369b90 --- /dev/null +++ b/frontend/templates/frontend/form/resource_body.html @@ -0,0 +1,20 @@ +
+ +
+
+ +
+
+
diff --git a/frontend/templates/frontend/form/resource_head.js b/frontend/templates/frontend/form/resource_head.js new file mode 100644 index 00000000..164d39d9 --- /dev/null +++ b/frontend/templates/frontend/form/resource_head.js @@ -0,0 +1,4 @@ +function preselect_last_resource() { + $("#calc_resource").val(localStorage.getItem("lastSelectedResource")); +} + diff --git a/frontend/templates/frontend/form/se_method_body.html b/frontend/templates/frontend/form/se_method_body.html new file mode 100644 index 00000000..005489c0 --- /dev/null +++ b/frontend/templates/frontend/form/se_method_body.html @@ -0,0 +1,13 @@ +
+ +
+
+ +
+
+
diff --git a/frontend/templates/frontend/form/software_body.html b/frontend/templates/frontend/form/software_body.html new file mode 100644 index 00000000..927813e0 --- /dev/null +++ b/frontend/templates/frontend/form/software_body.html @@ -0,0 +1,13 @@ +
+ +
+
+ +
+
+
diff --git a/frontend/templates/frontend/form/solvation_model_body.html b/frontend/templates/frontend/form/solvation_model_body.html new file mode 100644 index 00000000..72c217a6 --- /dev/null +++ b/frontend/templates/frontend/form/solvation_model_body.html @@ -0,0 +1,15 @@ +
+ +
+
+ +
+
+
diff --git a/frontend/templates/frontend/form/solvation_radii_body.html b/frontend/templates/frontend/form/solvation_radii_body.html new file mode 100644 index 00000000..1cd7017c --- /dev/null +++ b/frontend/templates/frontend/form/solvation_radii_body.html @@ -0,0 +1,17 @@ +
+ +
+
+ +
+
+
diff --git a/frontend/templates/frontend/form/solvent_body.html b/frontend/templates/frontend/form/solvent_body.html new file mode 100644 index 00000000..e884129e --- /dev/null +++ b/frontend/templates/frontend/form/solvent_body.html @@ -0,0 +1,7 @@ +
+ +
+ +
+

+
diff --git a/frontend/templates/frontend/form/special_body.html b/frontend/templates/frontend/form/special_body.html new file mode 100644 index 00000000..73b4b9c2 --- /dev/null +++ b/frontend/templates/frontend/form/special_body.html @@ -0,0 +1,22 @@ +
+
+
+ + +
+
+
+
+
+
+ + +
+
+
diff --git a/frontend/templates/frontend/form/specifications_body.html b/frontend/templates/frontend/form/specifications_body.html new file mode 100644 index 00000000..25c51f31 --- /dev/null +++ b/frontend/templates/frontend/form/specifications_body.html @@ -0,0 +1,6 @@ +
+ +
+ +
+
diff --git a/frontend/templates/frontend/form/suggestions_body.html b/frontend/templates/frontend/form/suggestions_body.html new file mode 100644 index 00000000..be933604 --- /dev/null +++ b/frontend/templates/frontend/form/suggestions_body.html @@ -0,0 +1,158 @@ +
+ +
+
+
+ GFN2-xTB + Details +
+ +
+
+
+ GFN-FF + Details +
+ +
+
+
+
+
+ M062X/Def2-SVP + Details +
+ +
+
+
+ M062X/Def2-TZVP + Details +
+ +
+
+
+
+
+ diff --git a/frontend/templates/frontend/form/suggestions_head.js b/frontend/templates/frontend/form/suggestions_head.js new file mode 100644 index 00000000..b144afce --- /dev/null +++ b/frontend/templates/frontend/form/suggestions_head.js @@ -0,0 +1,71 @@ +function select_gfn2() { + software = document.getElementsByName("calc_software")[0]; + if(software) + software.value = "xtb"; + + theory_level = document.getElementsByName("calc_theory_level")[0]; + if(theory_level) + theory_level.value = "xtb"; + + refresh_availabilities(); + + method = document.getElementsByName("calc_xtb_method")[0]; + if(method) + method.value = "gfn2-xtb"; +} + +function select_gfnff() { + software = document.getElementsByName("calc_software")[0]; + if(software) + software.value = "xtb"; + + theory_level = document.getElementsByName("calc_theory_level")[0]; + if(theory_level) + theory_level.value = "xtb"; + + refresh_availabilities(); + + method = document.getElementsByName("calc_xtb_method")[0]; + if(method) + method.value = "gfn-ff"; +} + +function select_m062x_svp() { + software = document.getElementsByName("calc_software")[0]; + if(software) + software.value = "NWChem"; + + theory_level = document.getElementsByName("calc_theory_level")[0]; + if(theory_level) + theory_level.value = "dft"; + + refresh_availabilities(); + + method = document.getElementsByName("calc_functional")[0]; + if(method) + method.value = "M062X"; + + bs = document.getElementsByName("calc_basis_set")[0]; + if(bs) + bs.value = "Def2-SVP"; +} + +function select_m062x_tzvp() { + software = document.getElementsByName("calc_software")[0]; + if(software) + software.value = "NWChem"; + + theory_level = document.getElementsByName("calc_theory_level")[0]; + if(theory_level) + theory_level.value = "dft"; + + refresh_availabilities(); + + method = document.getElementsByName("calc_functional")[0]; + if(method) + method.value = "M062X"; + + bs = document.getElementsByName("calc_basis_set")[0]; + if(bs) + bs.value = "Def2-TZVP"; +} diff --git a/frontend/templates/frontend/form/theory_level_body.html b/frontend/templates/frontend/form/theory_level_body.html new file mode 100644 index 00000000..32bba718 --- /dev/null +++ b/frontend/templates/frontend/form/theory_level_body.html @@ -0,0 +1,14 @@ +
+ +
+
+ +
+
+
diff --git a/frontend/templates/frontend/form/type_body.html b/frontend/templates/frontend/form/type_body.html new file mode 100644 index 00000000..0eb38885 --- /dev/null +++ b/frontend/templates/frontend/form/type_body.html @@ -0,0 +1,14 @@ +
+ +
+
+ + new +
+
+
+ diff --git a/frontend/templates/frontend/form/type_head.js b/frontend/templates/frontend/form/type_head.js new file mode 100644 index 00000000..4737617b --- /dev/null +++ b/frontend/templates/frontend/form/type_head.js @@ -0,0 +1,49 @@ +function set_visibility_name() { + choice = document.getElementById("calc_type"); + field = document.getElementById("calc_name_field"); + if(!field) { + console.log("Did not find an ensemble name (calc_name_field)") + return; + } + if(ensemble == true) { + if(choice.value == "Geometrical Optimisation" || choice.value == "Constrained Optimisation" || choice.value == "Conformational Search" || choice.value == "TS Optimisation" || choice.value == "Constrained Conformational Search" || choice.value == "Minimum Energy Path") { + field.style.display = "block"; + } + else { + field.style.display = "none"; + } + } + else { + field.style.display = "block"; + } +} +function calc_selection_changed() { + {% if is_flowchart is None %} + choice = document.getElementById("calc_type"); + if(typeof viewer !== 'undefined') { + if(choice.value == "Constrained Optimisation" || choice.value == "Constrained Conformational Search") { + viewer.styles.atoms_displayLabels_3D = true; + } + else if(choice.value == "Minimum Energy Path") { + viewer.styles.atoms_displayLabels_3D = false; + refresh_aux_mol(); + } + else { + viewer.styles.atoms_displayLabels_3D = false; + } + } + msg = document.getElementById("cloud_num_cores"); + if(msg) { + if(choice.value == "Conformational Search" || choice.value == "Constrained Conformational Search") { + // Hardcoded values for now... + $("#cloud_num_cores").html({% if request.user.is_trial %}"4"{% else %}"8"{% endif %}); + } + else { + $("#cloud_num_cores").html("1 to 4"); + } + } + {% endif %} + set_visibility_name(); + refresh_availabilities(); +} + diff --git a/frontend/templates/frontend/form/xtb_method_body.html b/frontend/templates/frontend/form/xtb_method_body.html new file mode 100644 index 00000000..4337e6c6 --- /dev/null +++ b/frontend/templates/frontend/form/xtb_method_body.html @@ -0,0 +1,13 @@ +
+ +
+
+ +
+
+
diff --git a/frontend/templates/frontend/home.html b/frontend/templates/frontend/home.html index eba36ae7..d0457c3d 100755 --- a/frontend/templates/frontend/home.html +++ b/frontend/templates/frontend/home.html @@ -1,6 +1,9 @@ -{% extends 'frontend/base.html' %} {% load static %} {% block extrahead %} +{% extends 'frontend/base.html' %} -CalcUS {% if IS_CLOUD %}Cloud {% endif %}- Quantum Chemistry Web Platform +{% load static %} +{% block extrahead %} +Computational Chemistry Online Platform + +{% if IS_CLOUD %} + + + + +{% endif %} {% endblock %} {% block content %} @@ -131,23 +196,33 @@
{% if IS_CLOUD %} - +
{% else %} - +
{% endif %}
-

Quantum Chemistry Web Platform

+

Making Quantum Chemistry
Accessible and Efficient

- View Project - View Publication - {% if IS_CLOUD and ALLOW_TRIAL and not request.user.is_authenticated %} -
- Try for free now + {% if not request.user.is_authenticated %} + {% if ALLOW_TRIAL %} + {% endif %} +
+
+ Create a free account and accelerate your research immediately with 3600 free seconds of compute time per month +
+ Create account + +
+
+ {% endif %} +

@@ -156,49 +231,231 @@
-

Launch state-of-the-art calculations

-

Run ridiculously fast semi-empirical xTB calculations on real computing resources, no configuration required

+

Skip the installation and configuration

+

Launch Density Functional Theory (DFT) calculations in your browser in under 30 seconds

- + Molecular Orbital generated with CalcUS
+
- + Conformers of pentane displayed in a table +
+
+
+

Use state-of-the-art software and algorithms

+

Run ridiculously fast semi-empirical xTB calculations on real computing resources, no configuration required

+
+
+
-

Focus on concepts, not minutae

+

Focus on concepts, not minutae

The user-friendly interface handles input file creation and management so you can concentrate on the bigger picture

+
+ Calculation launch page of CalcUS +
+ {% endif %} -
-
-

Get 3600 free seconds of compute time per month

+ + {% if IS_CLOUD %} +
+

What users say

+
+
+
+ Photo of Nassim Maarouf, a CalcUS Cloud user +
+ Nassim Maarouf
PhD student in Medicinal Chemistry
Université de Montréal
+
+
+
+ + Throughout my research journey, I have come to rely on the exceptional capabilities of CalcUS Cloud for computational modeling of molecules.
- Create a free account and accelerate your research immediately
+ CalcUS Cloud, with its powerful semi-empirical xTB calculations, has become an invaluable tool in my work. The platform's user-friendly interface and comprehensive features have greatly enhanced my ability to simulate 3D conformers and analyze bond angles, critical aspects in the field of medicinal chemistry. This has allowed me to simulate 3D structures before investing time in their laboratory synthesis, providing a solid starting point for my experimental work. + +
+
+
+
+ + CalcUS Cloud is a very important tool for undergraduate and postgraduate courses in computational chemistry and computer science in chemistry due to its easy handling, friendly interface, fast results and visualization of results. +
+
+ CalcUS Cloud has served chemistry students to learn about the different types of computational calculations at a theory level, the interpretation and management of cloud computing applied to chemistry. In addition, it is very important in investigations of large molecules to explain surface phenomena between chemical systems. + +
+
+
+ Photo of Hector Fabio Cortes Hernandez, a CalcUS Cloud user +
+ Hector Fabio Cortes Hernandez
Doctor in Chemical Sciences and Research Professor in Chemistry
Universidad Tecnológica de Pereira
+
+
+
+
+
+

- Create account - + Create account
{% endif %} + {% if False %} +
+

Practical guides

+ Learn about conformational sampling +
+ {% endif %} +
-

You have questions?

+

You have questions?


- Feel free to contact us for any inquiry at contact@calcus.cloud + Feel free to contact us for any inquiry at contact@calcus.cloud

- Contact us + Contact us
+ {% if IS_CLOUD %} +
+
+
+
+ + + + + Your subscription could not be saved. Please try again. + +
+
+
+
+
+ + + + + Your subscription has been successful. + +
+
+
+
+
+
+
+

Stay updated

+
+
+
+
+
+

Subscribe to our mailing list in order to get exclusive discounts and be notified when new features are available.

+
+
+
+
+
+
+
+ +
+ +
+
+ + +
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+
+
+
+ + + + + + + + + + +
+

+ We use Brevo as our marketing platform. By Clicking below to submit this form, you acknowledge that the information you provided will be transferred to Brevo for processing in accordance with their terms of use +

+
+
+
+
+ +
+
+ + + +
+
+
+
+ {% endif %}
+{% if IS_CLOUD %} + + +{% endif %} + {% endblock %} diff --git a/frontend/templates/frontend/includables/pricing_head.html b/frontend/templates/frontend/includables/pricing_head.html deleted file mode 100644 index 7e515ed7..00000000 --- a/frontend/templates/frontend/includables/pricing_head.html +++ /dev/null @@ -1,41 +0,0 @@ -{% load static %} - - - - - diff --git a/frontend/templates/frontend/includables/pricing_table.html b/frontend/templates/frontend/includables/pricing_table.html deleted file mode 100644 index 9182f26e..00000000 --- a/frontend/templates/frontend/includables/pricing_table.html +++ /dev/null @@ -1,67 +0,0 @@ -
-
-
-
Features
-
 
-
-
xTB calculations
-
Monthly calculation time
-
Maximum calculation runtime
-
Create groups and classes
-
Commercial Use
-
- -
-
-
Free Account
-
$0/month
-
-
Yes
-
60 CPU-minutes
-
15 minutes
-
No
-
No
-
- -
-
-
Academic Subscription
-
$19/month
-
-
Yes
-
1000 CPU-minutes*
-
2 hours
-
Yes
-
No
-
- -
-
-
Commercial Subscription
-
Contact us
-
-
Yes
-
According to needs
-
According to needs
-
Yes
-
Yes
-
- -
- -
-
diff --git a/frontend/templates/frontend/launch.html b/frontend/templates/frontend/launch.html index 0e82bc2a..f7605f15 100755 --- a/frontend/templates/frontend/launch.html +++ b/frontend/templates/frontend/launch.html @@ -1,118 +1,204 @@ -{% extends 'frontend/base.html' %} {% load i18n %} {% load static %} {% block extrahead %} -CalcUS - Launch - - - - - - - -{% include 'frontend/calc_form_head.html' %} {% endblock %} {% block content %} {% csrf_token %} +{% extends 'frontend/launch_template.html' %} + +{% block launchhead %} + +{% endblock %} + +{% block formblock %} + +{% include "frontend/form/edit_structure_body.html" %}
- {% if not ensemble and not structure and not calc %} - -
- - {% endif %} + {% include "frontend/form/input_widgets_body.html" %} +
+
+
+ {% if not ensemble and not structure %} +
+ {% if not is_batch %} + {% include "frontend/form/file_upload_body.html" %} + {% endif %} +
+ + {% include "frontend/form/aux_file_upload_body.html" %} + + {% endif %} + {% csrf_token %} + +
+
+ {% if not calc and not ensemble and not structure %} + {% include "frontend/form/mol_name_body.html" %} + {% endif %} +
+ +
+ {% include "frontend/form/charge_body.html" %} +
+
+ {% include "frontend/form/multiplicity_body.html" %} +
+
+ + + + {% if not is_batch %} + {% include "frontend/form/project_body.html" %} + {% endif %} + +
+
+ {% include "frontend/form/type_body.html" %} +
+
+ {% include "frontend/form/solvent_body.html" %} +
+
- -
+ {% include "frontend/form/aux_structure_body.html" %} + + {% include "frontend/form/constraints_body.html" %} + + +
+
+
+ {% include "frontend/form/theory_level_body.html" %} +
+
+ {% include "frontend/form/se_method_body.html" %} +
+
+ {% include "frontend/form/xtb_method_body.html" %} +
+ +
+ {% include "frontend/form/dft_method_body.html" %} +
+ +
+ {% include "frontend/form/basis_set_body.html" %} +
+
+
+
+ {% if IS_CLOUD %} + + {% else %} + {% include "frontend/form/resource_body.html" %} + {% endif %} + + {% if ensemble and not structures %} + {% include "frontend/form/filter_body.html" %} + {% endif %} + + {% if request.user.calc_method_suggestions %} +
+ {% include "frontend/form/suggestions_body.html" %} +
+ {% endif %} +
+
+ +
+ Submit +
+ {% if IS_CLOUD %} +
+ 1 to 4 core will be used (maximum runtime: {% if request.user.is_trial %}5 minutes{% elif request.user.is_subscriber %}6 hours{% else %}15 minutes{% endif %}) + {% if request.user.is_trial %} +
+ You are using a trial account. Convert to a free full account to unlock more computing power and longer runtimes + {% elif not request.user.is_subscriber %} +
+ Become a subscriber to increase your maximum runtime + {% endif %} +
+ {% endif %} +
+
+ +
- {% include 'frontend/calc_form_body.html' %}
+{% endblock %} + +{% block appendix %} -{% endblock content %} +{% endblock %} diff --git a/frontend/templates/frontend/launch_advanced.html b/frontend/templates/frontend/launch_advanced.html new file mode 100755 index 00000000..aa3ce778 --- /dev/null +++ b/frontend/templates/frontend/launch_advanced.html @@ -0,0 +1,255 @@ +{% extends 'frontend/launch_template.html' %} + +{% block launchhead %} + +{% endblock %} + +{% block formblock %} + +{% include "frontend/form/edit_structure_body.html" %} + +
+
+ {% include "frontend/form/input_widgets_body.html" %} +
+
+
+ {% if not IS_CLOUD %} + {% include "frontend/form/preset_body.html" %} + {% endif %} +
+ +
+ {% if not ensemble and not structure %} +
+ {% if not is_batch %} + {% include "frontend/form/file_upload_body.html" %} + {% endif %} +
+ + {% if not is_batch %} + {% include "frontend/form/derivatives_body.html" %} + {% endif %} + + {% include "frontend/form/aux_file_upload_body.html" %} + + {% endif %} + {% csrf_token %} + +
+
+ {% if not calc and not ensemble and not structure %} + {% include "frontend/form/mol_name_body.html" %} + {% endif %} + {% include "frontend/form/ensemble_name_body.html" %} +
+ +
+ {% include "frontend/form/charge_body.html" %} +
+
+ {% include "frontend/form/multiplicity_body.html" %} +
+
+ +
+
+ {% include "frontend/form/solvent_body.html" %} +
+
+ {% include "frontend/form/solvation_model_body.html" %} +
+
+ {% include "frontend/form/solvation_radii_body.html" %} +
+
+ + {% if not is_batch %} + {% include "frontend/form/project_body.html" %} + {% endif %} + +
+
+ {% include "frontend/form/software_body.html" %} +
+ +
+ {% include "frontend/form/driver_body.html" %} +
+ + +
+ {% include "frontend/form/type_body.html" %} +
+ +
+ + {% include "frontend/form/aux_structure_body.html" %} + + {% include "frontend/form/constraints_body.html" %} + + +
+
+
+ {% include "frontend/form/theory_level_body.html" %} +
+
+ {% include "frontend/form/special_body.html" %} +
+ +
+ {% include "frontend/form/se_method_body.html" %} +
+
+ {% include "frontend/form/xtb_method_body.html" %} +
+ +
+ {% include "frontend/form/dft_method_body.html" %} +
+ +
+ {% include "frontend/form/basis_set_body.html" %} +
+
+
+
+ {% include "frontend/form/specifications_body.html" %} + + {% include "frontend/form/df_body.html" %} + + {% include "frontend/form/custom_bs_body.html" %} +
+
+ {% if IS_CLOUD %} + + {% else %} + {% include "frontend/form/resource_body.html" %} + {% endif %} + + {% if ensemble and not structures %} + {% include "frontend/form/filter_body.html" %} + {% endif %} + +
+
+ +
+ Submit +
+ {% if IS_CLOUD %} +
+ 1 to 4 core will be used (maximum runtime: {% if request.user.is_trial %}5 minutes{% elif request.user.is_subscriber %}6 hours{% else %}15 minutes{% endif %}) + {% if request.user.is_trial %} +
+ You are using a trial account. Convert to a free full account to unlock more computing power and longer runtimes + {% elif not request.user.is_subscriber %} +
+ Become a subscriber to increase your maximum runtime + {% endif %} +
+ {% endif %} +
+
+ +
+{% endblock %} + +{% block appendix %} + +{% endblock %} diff --git a/frontend/templates/frontend/launch_template.html b/frontend/templates/frontend/launch_template.html new file mode 100644 index 00000000..dda1045a --- /dev/null +++ b/frontend/templates/frontend/launch_template.html @@ -0,0 +1,685 @@ +{% extends 'frontend/base.html' %} +{% load i18n %} +{% load static %} + +{% block extrahead %} +Launch a calculation + + + + + + + + + + {% if start_tour %} + + + + + + + {% endif %} + + +{% block launchhead %}{% endblock %} +{% endblock %} + +{% block content %} +{% block formblock %}{% endblock %} + +{% block appendix %} +{% endblock %} +{% endblock %} diff --git a/frontend/templates/frontend/learn.html b/frontend/templates/frontend/learn.html index 4e3519fd..4751425b 100755 --- a/frontend/templates/frontend/learn.html +++ b/frontend/templates/frontend/learn.html @@ -1,7 +1,9 @@ {% extends 'frontend/base.html' %} {% load static %} {% block extrahead %} -CalcUS - Learn +Learn quantum chemistry + + - + {% endblock %} {% block content %}
- {% include "frontend/includables/pricing_table.html" %} +

Unlock the Potential of Quantum Chemistry

+
+
+ + + +
+
+
+
Features
+
 
+
+
xTB and DFT calculations
+
Monthly calculation time
+
Maximum calculation runtime
+
Create groups and classes
+
+ +
+
+
Free
+
$0/month
+
+
+
1 CPU-hour
+
15 minutes
+
+
+ +
+
+
Researcher
+
$2919/month
+
+
+
60 CPU-hours*
+
6 hours
+
+
+ +
+ + +
+
Team
+
$199149/month
+
+
+
500 CPU-hours*
+
12 hours
+
+
+ +
+ +
+
Organisation
+ +
+
+
According to needs
+
According to needs
+
+
+ +
+ +
+
+

Try Risk-free

+
+ If you are not entirely satisfied with your CalcUS Cloud subscription, we will refund you the full price up to 14 days after your purchase, simply send us an email. After that, you can still cancel anytime you want. +
+ +

Advantageous yearly plans

+
+ Get 2 months free per year when you subscribe for a yearly subscription instead of a monthly subscription. +
+ +

Organisational subscriptions

+
+ Want to get access for your entire organisation with a custom plan satisfying your specific needs? Get in touch with us! +
{% endblock %} diff --git a/frontend/templates/frontend/profile.html b/frontend/templates/frontend/profile.html index 5b0f2961..8e6857eb 100755 --- a/frontend/templates/frontend/profile.html +++ b/frontend/templates/frontend/profile.html @@ -4,7 +4,7 @@ {% load static %} {% block extrahead %} - CalcUS - Profile + CalcUS Profile @@ -245,10 +245,10 @@ margin-top: 1em; } .class_code { - background-color: #eeffee; + background-color: #80d9ab; } .class_code:hover { - background-color: #ddffdd; + background-color: #33c179; cursor: pointer; } #load_password_box { @@ -292,38 +292,61 @@
Preferences
{% csrf_token %} -
-
- -
-
- -
-
-
- {% if not IS_CLOUD %} -
- - -
-
- - -
- {% endif %} -
-
- -
-
+
+
+
+
+ +
+
+ +
+
+
+ {% if not IS_CLOUD %} +
+ + +
+
+ + +
+ {% endif %} +
+
+
+
+ + +
+
+ + +
+
+ + +
+ Restart interface tour +
+
+
+ +
+
+
+ +
+
+
+
-
-

@@ -428,6 +451,7 @@
Account
{% endif %} + {% if IS_CLOUD %} {% if request.user.is_temporary %}

You are logged in as temporary user {{ request.user.email }}.

{% if request.user.is_trial %} @@ -436,15 +460,20 @@
Account
{% else %}

You are logged in as {{ request.user.email }}.

{% if request.user.is_subscriber %} - You have an active subscription which will end on {{ request.user.active_subscription.end_date|date }}. + {% if request.user.stripe_will_renew %} + You have an active subscription which will automatically renew (unless cancelled) on {{ request.user.active_subscription.end_date|date }}. + {% else %} + You have an active subscription which will end {{ request.user.active_subscription.end_date|date }} and not be renewed. + {% endif %}
- Manage subscription + Manage subscription {% else %} You do not have an active subscription.
Pricing {% endif %} {% endif %} + {% endif %}
diff --git a/frontend/templates/frontend/project_details.html b/frontend/templates/frontend/project_details.html index ac8ae8d5..9cbf931e 100755 --- a/frontend/templates/frontend/project_details.html +++ b/frontend/templates/frontend/project_details.html @@ -1,5 +1,5 @@ {% extends 'frontend/base.html' %} {% load static %} {% block extrahead %} -CalcUS - {{ project.name }} +{{ project.name }} @@ -99,7 +99,7 @@
{% for mol in molecules %} -
+
{% if mol.project.author == request.user %}

diff --git a/frontend/templates/frontend/project_folders.html b/frontend/templates/frontend/project_folders.html index fe914152..82c62c6d 100755 --- a/frontend/templates/frontend/project_folders.html +++ b/frontend/templates/frontend/project_folders.html @@ -4,7 +4,7 @@ {% load folder_tags %} {% block extrahead %} -CalcUS - {{ folder.name }} +{{ folder.name }} diff --git a/frontend/templates/frontend/projects.html b/frontend/templates/frontend/projects.html index ecfdd752..bcbe17f0 100755 --- a/frontend/templates/frontend/projects.html +++ b/frontend/templates/frontend/projects.html @@ -1,5 +1,5 @@ {% extends 'frontend/base.html' %} {% load static %} {% block extrahead %} -CalcUS - Projects +CalcUS Projects @@ -120,10 +120,12 @@ private_attr = ''; } project_template = ` -

+

+ {% if not IS_CLOUD %} + {% endif %} @@ -134,7 +136,7 @@

{} Molecule(s)

` - $("#projects_list").append(project_template.format(id, author, name, id, id, id, private_attr, id, id, id, id, id, author, name, id, name, mol_count)); + $("#projects_list").append(project_template.format(id, {% if not IS_CLOUD %}author, name, id, id, id, private_attr,{% endif %} id, id, id, id, id, author, name, id, name, mol_count)); } function toggle_private(id) { icon = document.getElementById("private_icon_" + id); diff --git a/frontend/templates/frontend/robots.txt b/frontend/templates/frontend/robots.txt new file mode 100644 index 00000000..0089db85 --- /dev/null +++ b/frontend/templates/frontend/robots.txt @@ -0,0 +1,7 @@ +User-agent: * +Allow: / + +User-agent: * +Disallow: /stripe_config/ + +Sitemap: https://calcus.cloud/sitemap.xml diff --git a/frontend/templates/frontend/server_summary.html b/frontend/templates/frontend/server_summary.html index 3b69255f..fb461d84 100755 --- a/frontend/templates/frontend/server_summary.html +++ b/frontend/templates/frontend/server_summary.html @@ -1,5 +1,5 @@ {% extends 'frontend/base.html' %} {% load i18n %} {% load static %} {% block extrahead %} -CalcUS - Server Summary +CalcUS Server Summary +{% endblock %} +{% block content %} +
+
+

Our use of cookies

+

+ Like almost all websites, we use cookies. By continuing to browse the website, you agree to our use of cookies, which is explained below. +

+

+ The cookies we use are divided into two groups: feature cookies and analytics cookies. The feature cookies are used to keep you logged in for long periods of time (unless you choose to log off) and to improve the security of the website. Some cookies store your last selected project on the platform in order to preselect it when you launch new calculations. We also need to create a cookie to indicate that you have seen the cookie banner and that we shouldn't bother you with it anymore. The analytics cookies help us run and improve the platform. For example, they allow us to see how many new users visit our website, how many returning users start a trial or create an account, and also how many people launch calculations. +

+

+ We do not use cookies to track you and send you ads on other websites. We also do not collect sensitive information about you through cookies. +

+ {% if not IS_CLOUD %} +

+ Note: the platform is not running in Cloud mode and does not use analytics cookies. +

+ {% endif %} +
+
+{% endblock %} diff --git a/frontend/templates/registration/register.html b/frontend/templates/registration/register.html index abfe1af5..0e055012 100755 --- a/frontend/templates/registration/register.html +++ b/frontend/templates/registration/register.html @@ -2,6 +2,10 @@ {% load i18n bulma_tags static %} {% block extrahead %} +Register for free + + + - - The CSV file is of the form - - Date,SeriesA,SeriesB,SeriesC - YYYYMMDD,A1,B1,C1 - YYYYMMDD,A2,B2,C2 - - If the 'errorBars' option is set in the constructor, the input should be of - the form - Date,SeriesA,SeriesB,... - YYYYMMDD,A1,sigmaA1,B1,sigmaB1,... - YYYYMMDD,A2,sigmaA2,B2,sigmaB2,... - - If the 'fractions' option is set, the input should be of the form: - - Date,SeriesA,SeriesB,... - YYYYMMDD,A1/B1,A2/B2,... - YYYYMMDD,A1/B1,A2/B2,... - - And error bars will be calculated automatically using a binomial distribution. - - For further documentation and examples, see http://dygraphs.com/ - */'use strict';Object.defineProperty(exports,'__esModule',{value:true});var _slicedToArray=(function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[Symbol.iterator](),_s;!(_n = (_s = _i.next()).done);_n = true) {_arr.push(_s.value);if(i && _arr.length === i)break;}}catch(err) {_d = true;_e = err;}finally {try{if(!_n && _i['return'])_i['return']();}finally {if(_d)throw _e;}}return _arr;}return function(arr,i){if(Array.isArray(arr)){return arr;}else if(Symbol.iterator in Object(arr)){return sliceIterator(arr,i);}else {throw new TypeError('Invalid attempt to destructure non-iterable instance');}};})();function _interopRequireWildcard(obj){if(obj && obj.__esModule){return obj;}else {var newObj={};if(obj != null){for(var key in obj) {if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key] = obj[key];}}newObj['default'] = obj;return newObj;}}function _interopRequireDefault(obj){return obj && obj.__esModule?obj:{'default':obj};}var _dygraphLayout=require('./dygraph-layout');var _dygraphLayout2=_interopRequireDefault(_dygraphLayout);var _dygraphCanvas=require('./dygraph-canvas');var _dygraphCanvas2=_interopRequireDefault(_dygraphCanvas);var _dygraphOptions=require('./dygraph-options');var _dygraphOptions2=_interopRequireDefault(_dygraphOptions);var _dygraphInteractionModel=require('./dygraph-interaction-model');var _dygraphInteractionModel2=_interopRequireDefault(_dygraphInteractionModel);var _dygraphTickers=require('./dygraph-tickers');var DygraphTickers=_interopRequireWildcard(_dygraphTickers);var _dygraphUtils=require('./dygraph-utils');var utils=_interopRequireWildcard(_dygraphUtils);var _dygraphDefaultAttrs=require('./dygraph-default-attrs');var _dygraphDefaultAttrs2=_interopRequireDefault(_dygraphDefaultAttrs);var _dygraphOptionsReference=require('./dygraph-options-reference');var _dygraphOptionsReference2=_interopRequireDefault(_dygraphOptionsReference);var _iframeTarp=require('./iframe-tarp');var _iframeTarp2=_interopRequireDefault(_iframeTarp);var _datahandlerDefault=require('./datahandler/default');var _datahandlerDefault2=_interopRequireDefault(_datahandlerDefault);var _datahandlerBarsError=require('./datahandler/bars-error');var _datahandlerBarsError2=_interopRequireDefault(_datahandlerBarsError);var _datahandlerBarsCustom=require('./datahandler/bars-custom');var _datahandlerBarsCustom2=_interopRequireDefault(_datahandlerBarsCustom);var _datahandlerDefaultFractions=require('./datahandler/default-fractions');var _datahandlerDefaultFractions2=_interopRequireDefault(_datahandlerDefaultFractions);var _datahandlerBarsFractions=require('./datahandler/bars-fractions');var _datahandlerBarsFractions2=_interopRequireDefault(_datahandlerBarsFractions);var _datahandlerBars=require('./datahandler/bars');var _datahandlerBars2=_interopRequireDefault(_datahandlerBars);var _pluginsAnnotations=require('./plugins/annotations');var _pluginsAnnotations2=_interopRequireDefault(_pluginsAnnotations);var _pluginsAxes=require('./plugins/axes');var _pluginsAxes2=_interopRequireDefault(_pluginsAxes);var _pluginsChartLabels=require('./plugins/chart-labels');var _pluginsChartLabels2=_interopRequireDefault(_pluginsChartLabels);var _pluginsGrid=require('./plugins/grid');var _pluginsGrid2=_interopRequireDefault(_pluginsGrid);var _pluginsLegend=require('./plugins/legend');var _pluginsLegend2=_interopRequireDefault(_pluginsLegend);var _pluginsRangeSelector=require('./plugins/range-selector');var _pluginsRangeSelector2=_interopRequireDefault(_pluginsRangeSelector);var _dygraphGviz=require('./dygraph-gviz');var _dygraphGviz2=_interopRequireDefault(_dygraphGviz);"use strict"; /** - * Creates an interactive, zoomable chart. - * - * @constructor - * @param {div | String} div A div or the id of a div into which to construct - * the chart. - * @param {String | Function} file A file containing CSV data or a function - * that returns this data. The most basic expected format for each line is - * "YYYY/MM/DD,val1,val2,...". For more information, see - * http://dygraphs.com/data.html. - * @param {Object} attrs Various other attributes, e.g. errorBars determines - * whether the input data contains error ranges. For a complete list of - * options, see http://dygraphs.com/options.html. - */var Dygraph=function Dygraph(div,data,opts){this.__init__(div,data,opts);};Dygraph.NAME = "Dygraph";Dygraph.VERSION = "2.1.0"; // Various default values -Dygraph.DEFAULT_ROLL_PERIOD = 1;Dygraph.DEFAULT_WIDTH = 480;Dygraph.DEFAULT_HEIGHT = 320; // For max 60 Hz. animation: -Dygraph.ANIMATION_STEPS = 12;Dygraph.ANIMATION_DURATION = 200; /** - * Standard plotters. These may be used by clients. - * Available plotters are: - * - Dygraph.Plotters.linePlotter: draws central lines (most common) - * - Dygraph.Plotters.errorPlotter: draws error bars - * - Dygraph.Plotters.fillPlotter: draws fills under lines (used with fillGraph) - * - * By default, the plotter is [fillPlotter, errorPlotter, linePlotter]. - * This causes all the lines to be drawn over all the fills/error bars. - */Dygraph.Plotters = _dygraphCanvas2['default']._Plotters; // Used for initializing annotation CSS rules only once. -Dygraph.addedAnnotationCSS = false; /** - * Initializes the Dygraph. This creates a new DIV and constructs the PlotKit - * and context <canvas> inside of it. See the constructor for details. - * on the parameters. - * @param {Element} div the Element to render the graph into. - * @param {string | Function} file Source data - * @param {Object} attrs Miscellaneous other options - * @private - */Dygraph.prototype.__init__ = function(div,file,attrs){this.is_initial_draw_ = true;this.readyFns_ = []; // Support two-argument constructor -if(attrs === null || attrs === undefined){attrs = {};}attrs = Dygraph.copyUserAttrs_(attrs);if(typeof div == 'string'){div = document.getElementById(div);}if(!div){throw new Error('Constructing dygraph with a non-existent div!');} // Copy the important bits into the object -// TODO(danvk): most of these should just stay in the attrs_ dictionary. -this.maindiv_ = div;this.file_ = file;this.rollPeriod_ = attrs.rollPeriod || Dygraph.DEFAULT_ROLL_PERIOD;this.previousVerticalX_ = -1;this.fractions_ = attrs.fractions || false;this.dateWindow_ = attrs.dateWindow || null;this.annotations_ = []; // Clear the div. This ensure that, if multiple dygraphs are passed the same -// div, then only one will be drawn. -div.innerHTML = ""; // For historical reasons, the 'width' and 'height' options trump all CSS -// rules _except_ for an explicit 'width' or 'height' on the div. -// As an added convenience, if the div has zero height (like
does -// without any styles), then we use a default height/width. -if(div.style.width === '' && attrs.width){div.style.width = attrs.width + "px";}if(div.style.height === '' && attrs.height){div.style.height = attrs.height + "px";}if(div.style.height === '' && div.clientHeight === 0){div.style.height = Dygraph.DEFAULT_HEIGHT + "px";if(div.style.width === ''){div.style.width = Dygraph.DEFAULT_WIDTH + "px";}} // These will be zero if the dygraph's div is hidden. In that case, -// use the user-specified attributes if present. If not, use zero -// and assume the user will call resize to fix things later. -this.width_ = div.clientWidth || attrs.width || 0;this.height_ = div.clientHeight || attrs.height || 0; // TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_. -if(attrs.stackedGraph){attrs.fillGraph = true; // TODO(nikhilk): Add any other stackedGraph checks here. -} // DEPRECATION WARNING: All option processing should be moved from -// attrs_ and user_attrs_ to options_, which holds all this information. -// -// Dygraphs has many options, some of which interact with one another. -// To keep track of everything, we maintain two sets of options: -// -// this.user_attrs_ only options explicitly set by the user. -// this.attrs_ defaults, options derived from user_attrs_, data. -// -// Options are then accessed this.attr_('attr'), which first looks at -// user_attrs_ and then computed attrs_. This way Dygraphs can set intelligent -// defaults without overriding behavior that the user specifically asks for. -this.user_attrs_ = {};utils.update(this.user_attrs_,attrs); // This sequence ensures that Dygraph.DEFAULT_ATTRS is never modified. -this.attrs_ = {};utils.updateDeep(this.attrs_,_dygraphDefaultAttrs2['default']);this.boundaryIds_ = [];this.setIndexByName_ = {};this.datasetIndex_ = [];this.registeredEvents_ = [];this.eventListeners_ = {};this.attributes_ = new _dygraphOptions2['default'](this); // Create the containing DIV and other interactive elements -this.createInterface_(); // Activate plugins. -this.plugins_ = [];var plugins=Dygraph.PLUGINS.concat(this.getOption('plugins'));for(var i=0;i < plugins.length;i++) { // the plugins option may contain either plugin classes or instances. -// Plugin instances contain an activate method. -var Plugin=plugins[i]; // either a constructor or an instance. -var pluginInstance;if(typeof Plugin.activate !== 'undefined'){pluginInstance = Plugin;}else {pluginInstance = new Plugin();}var pluginDict={plugin:pluginInstance,events:{},options:{},pluginOptions:{}};var handlers=pluginInstance.activate(this);for(var eventName in handlers) {if(!handlers.hasOwnProperty(eventName))continue; // TODO(danvk): validate eventName. -pluginDict.events[eventName] = handlers[eventName];}this.plugins_.push(pluginDict);} // At this point, plugins can no longer register event handlers. -// Construct a map from event -> ordered list of [callback, plugin]. -for(var i=0;i < this.plugins_.length;i++) {var plugin_dict=this.plugins_[i];for(var eventName in plugin_dict.events) {if(!plugin_dict.events.hasOwnProperty(eventName))continue;var callback=plugin_dict.events[eventName];var pair=[plugin_dict.plugin,callback];if(!(eventName in this.eventListeners_)){this.eventListeners_[eventName] = [pair];}else {this.eventListeners_[eventName].push(pair);}}}this.createDragInterface_();this.start_();}; /** - * Triggers a cascade of events to the various plugins which are interested in them. - * Returns true if the "default behavior" should be prevented, i.e. if one - * of the event listeners called event.preventDefault(). - * @private - */Dygraph.prototype.cascadeEvents_ = function(name,extra_props){if(!(name in this.eventListeners_))return false; // QUESTION: can we use objects & prototypes to speed this up? -var e={dygraph:this,cancelable:false,defaultPrevented:false,preventDefault:function preventDefault(){if(!e.cancelable)throw "Cannot call preventDefault on non-cancelable event.";e.defaultPrevented = true;},propagationStopped:false,stopPropagation:function stopPropagation(){e.propagationStopped = true;}};utils.update(e,extra_props);var callback_plugin_pairs=this.eventListeners_[name];if(callback_plugin_pairs){for(var i=callback_plugin_pairs.length - 1;i >= 0;i--) {var plugin=callback_plugin_pairs[i][0];var callback=callback_plugin_pairs[i][1];callback.call(plugin,e);if(e.propagationStopped)break;}}return e.defaultPrevented;}; /** - * Fetch a plugin instance of a particular class. Only for testing. - * @private - * @param {!Class} type The type of the plugin. - * @return {Object} Instance of the plugin, or null if there is none. - */Dygraph.prototype.getPluginInstance_ = function(type){for(var i=0;i < this.plugins_.length;i++) {var p=this.plugins_[i];if(p.plugin instanceof type){return p.plugin;}}return null;}; /** - * Returns the zoomed status of the chart for one or both axes. - * - * Axis is an optional parameter. Can be set to 'x' or 'y'. - * - * The zoomed status for an axis is set whenever a user zooms using the mouse - * or when the dateWindow or valueRange are updated. Double-clicking or calling - * resetZoom() resets the zoom status for the chart. - */Dygraph.prototype.isZoomed = function(axis){var isZoomedX=!!this.dateWindow_;if(axis === 'x')return isZoomedX;var isZoomedY=this.axes_.map(function(axis){return !!axis.valueRange;}).indexOf(true) >= 0;if(axis === null || axis === undefined){return isZoomedX || isZoomedY;}if(axis === 'y')return isZoomedY;throw new Error('axis parameter is [' + axis + '] must be null, \'x\' or \'y\'.');}; /** - * Returns information about the Dygraph object, including its containing ID. - */Dygraph.prototype.toString = function(){var maindiv=this.maindiv_;var id=maindiv && maindiv.id?maindiv.id:maindiv;return "[Dygraph " + id + "]";}; /** - * @private - * Returns the value of an option. This may be set by the user (either in the - * constructor or by calling updateOptions) or by dygraphs, and may be set to a - * per-series value. - * @param {string} name The name of the option, e.g. 'rollPeriod'. - * @param {string} [seriesName] The name of the series to which the option - * will be applied. If no per-series value of this option is available, then - * the global value is returned. This is optional. - * @return { ... } The value of the option. - */Dygraph.prototype.attr_ = function(name,seriesName){ // For "production" code, this gets removed by uglifyjs. -if(typeof process !== 'undefined'){if("development" != 'production'){if(typeof _dygraphOptionsReference2['default'] === 'undefined'){console.error('Must include options reference JS for testing');}else if(!_dygraphOptionsReference2['default'].hasOwnProperty(name)){console.error('Dygraphs is using property ' + name + ', which has no ' + 'entry in the Dygraphs.OPTIONS_REFERENCE listing.'); // Only log this error once. -_dygraphOptionsReference2['default'][name] = true;}}}return seriesName?this.attributes_.getForSeries(name,seriesName):this.attributes_.get(name);}; /** - * Returns the current value for an option, as set in the constructor or via - * updateOptions. You may pass in an (optional) series name to get per-series - * values for the option. - * - * All values returned by this method should be considered immutable. If you - * modify them, there is no guarantee that the changes will be honored or that - * dygraphs will remain in a consistent state. If you want to modify an option, - * use updateOptions() instead. - * - * @param {string} name The name of the option (e.g. 'strokeWidth') - * @param {string=} opt_seriesName Series name to get per-series values. - * @return {*} The value of the option. - */Dygraph.prototype.getOption = function(name,opt_seriesName){return this.attr_(name,opt_seriesName);}; /** - * Like getOption(), but specifically returns a number. - * This is a convenience function for working with the Closure Compiler. - * @param {string} name The name of the option (e.g. 'strokeWidth') - * @param {string=} opt_seriesName Series name to get per-series values. - * @return {number} The value of the option. - * @private - */Dygraph.prototype.getNumericOption = function(name,opt_seriesName){return (/** @type{number} */this.getOption(name,opt_seriesName));}; /** - * Like getOption(), but specifically returns a string. - * This is a convenience function for working with the Closure Compiler. - * @param {string} name The name of the option (e.g. 'strokeWidth') - * @param {string=} opt_seriesName Series name to get per-series values. - * @return {string} The value of the option. - * @private - */Dygraph.prototype.getStringOption = function(name,opt_seriesName){return (/** @type{string} */this.getOption(name,opt_seriesName));}; /** - * Like getOption(), but specifically returns a boolean. - * This is a convenience function for working with the Closure Compiler. - * @param {string} name The name of the option (e.g. 'strokeWidth') - * @param {string=} opt_seriesName Series name to get per-series values. - * @return {boolean} The value of the option. - * @private - */Dygraph.prototype.getBooleanOption = function(name,opt_seriesName){return (/** @type{boolean} */this.getOption(name,opt_seriesName));}; /** - * Like getOption(), but specifically returns a function. - * This is a convenience function for working with the Closure Compiler. - * @param {string} name The name of the option (e.g. 'strokeWidth') - * @param {string=} opt_seriesName Series name to get per-series values. - * @return {function(...)} The value of the option. - * @private - */Dygraph.prototype.getFunctionOption = function(name,opt_seriesName){return (/** @type{function(...)} */this.getOption(name,opt_seriesName));};Dygraph.prototype.getOptionForAxis = function(name,axis){return this.attributes_.getForAxis(name,axis);}; /** - * @private - * @param {string} axis The name of the axis (i.e. 'x', 'y' or 'y2') - * @return { ... } A function mapping string -> option value - */Dygraph.prototype.optionsViewForAxis_ = function(axis){var self=this;return function(opt){var axis_opts=self.user_attrs_.axes;if(axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)){return axis_opts[axis][opt];} // I don't like that this is in a second spot. -if(axis === 'x' && opt === 'logscale'){ // return the default value. -// TODO(konigsberg): pull the default from a global default. -return false;} // user-specified attributes always trump defaults, even if they're less -// specific. -if(typeof self.user_attrs_[opt] != 'undefined'){return self.user_attrs_[opt];}axis_opts = self.attrs_.axes;if(axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)){return axis_opts[axis][opt];} // check old-style axis options -// TODO(danvk): add a deprecation warning if either of these match. -if(axis == 'y' && self.axes_[0].hasOwnProperty(opt)){return self.axes_[0][opt];}else if(axis == 'y2' && self.axes_[1].hasOwnProperty(opt)){return self.axes_[1][opt];}return self.attr_(opt);};}; /** - * Returns the current rolling period, as set by the user or an option. - * @return {number} The number of points in the rolling window - */Dygraph.prototype.rollPeriod = function(){return this.rollPeriod_;}; /** - * Returns the currently-visible x-range. This can be affected by zooming, - * panning or a call to updateOptions. - * Returns a two-element array: [left, right]. - * If the Dygraph has dates on the x-axis, these will be millis since epoch. - */Dygraph.prototype.xAxisRange = function(){return this.dateWindow_?this.dateWindow_:this.xAxisExtremes();}; /** - * Returns the lower- and upper-bound x-axis values of the data set. - */Dygraph.prototype.xAxisExtremes = function(){var pad=this.getNumericOption('xRangePad') / this.plotter_.area.w;if(this.numRows() === 0){return [0 - pad,1 + pad];}var left=this.rawData_[0][0];var right=this.rawData_[this.rawData_.length - 1][0];if(pad){ // Must keep this in sync with dygraph-layout _evaluateLimits() -var range=right - left;left -= range * pad;right += range * pad;}return [left,right];}; /** - * Returns the lower- and upper-bound y-axis values for each axis. These are - * the ranges you'll get if you double-click to zoom out or call resetZoom(). - * The return value is an array of [low, high] tuples, one for each y-axis. - */Dygraph.prototype.yAxisExtremes = function(){ // TODO(danvk): this is pretty inefficient -var packed=this.gatherDatasets_(this.rolledSeries_,null);var extremes=packed.extremes;var saveAxes=this.axes_;this.computeYAxisRanges_(extremes);var newAxes=this.axes_;this.axes_ = saveAxes;return newAxes.map(function(axis){return axis.extremeRange;});}; /** - * Returns the currently-visible y-range for an axis. This can be affected by - * zooming, panning or a call to updateOptions. Axis indices are zero-based. If - * called with no arguments, returns the range of the first axis. - * Returns a two-element array: [bottom, top]. - */Dygraph.prototype.yAxisRange = function(idx){if(typeof idx == "undefined")idx = 0;if(idx < 0 || idx >= this.axes_.length){return null;}var axis=this.axes_[idx];return [axis.computedValueRange[0],axis.computedValueRange[1]];}; /** - * Returns the currently-visible y-ranges for each axis. This can be affected by - * zooming, panning, calls to updateOptions, etc. - * Returns an array of [bottom, top] pairs, one for each y-axis. - */Dygraph.prototype.yAxisRanges = function(){var ret=[];for(var i=0;i < this.axes_.length;i++) {ret.push(this.yAxisRange(i));}return ret;}; // TODO(danvk): use these functions throughout dygraphs. -/** - * Convert from data coordinates to canvas/div X/Y coordinates. - * If specified, do this conversion for the coordinate system of a particular - * axis. Uses the first axis by default. - * Returns a two-element array: [X, Y] - * - * Note: use toDomXCoord instead of toDomCoords(x, null) and use toDomYCoord - * instead of toDomCoords(null, y, axis). - */Dygraph.prototype.toDomCoords = function(x,y,axis){return [this.toDomXCoord(x),this.toDomYCoord(y,axis)];}; /** - * Convert from data x coordinates to canvas/div X coordinate. - * If specified, do this conversion for the coordinate system of a particular - * axis. - * Returns a single value or null if x is null. - */Dygraph.prototype.toDomXCoord = function(x){if(x === null){return null;}var area=this.plotter_.area;var xRange=this.xAxisRange();return area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w;}; /** - * Convert from data x coordinates to canvas/div Y coordinate and optional - * axis. Uses the first axis by default. - * - * returns a single value or null if y is null. - */Dygraph.prototype.toDomYCoord = function(y,axis){var pct=this.toPercentYCoord(y,axis);if(pct === null){return null;}var area=this.plotter_.area;return area.y + pct * area.h;}; /** - * Convert from canvas/div coords to data coordinates. - * If specified, do this conversion for the coordinate system of a particular - * axis. Uses the first axis by default. - * Returns a two-element array: [X, Y]. - * - * Note: use toDataXCoord instead of toDataCoords(x, null) and use toDataYCoord - * instead of toDataCoords(null, y, axis). - */Dygraph.prototype.toDataCoords = function(x,y,axis){return [this.toDataXCoord(x),this.toDataYCoord(y,axis)];}; /** - * Convert from canvas/div x coordinate to data coordinate. - * - * If x is null, this returns null. - */Dygraph.prototype.toDataXCoord = function(x){if(x === null){return null;}var area=this.plotter_.area;var xRange=this.xAxisRange();if(!this.attributes_.getForAxis("logscale",'x')){return xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]);}else {var pct=(x - area.x) / area.w;return utils.logRangeFraction(xRange[0],xRange[1],pct);}}; /** - * Convert from canvas/div y coord to value. - * - * If y is null, this returns null. - * if axis is null, this uses the first axis. - */Dygraph.prototype.toDataYCoord = function(y,axis){if(y === null){return null;}var area=this.plotter_.area;var yRange=this.yAxisRange(axis);if(typeof axis == "undefined")axis = 0;if(!this.attributes_.getForAxis("logscale",axis)){return yRange[0] + (area.y + area.h - y) / area.h * (yRange[1] - yRange[0]);}else { // Computing the inverse of toDomCoord. -var pct=(y - area.y) / area.h; // Note reversed yRange, y1 is on top with pct==0. -return utils.logRangeFraction(yRange[1],yRange[0],pct);}}; /** - * Converts a y for an axis to a percentage from the top to the - * bottom of the drawing area. - * - * If the coordinate represents a value visible on the canvas, then - * the value will be between 0 and 1, where 0 is the top of the canvas. - * However, this method will return values outside the range, as - * values can fall outside the canvas. - * - * If y is null, this returns null. - * if axis is null, this uses the first axis. - * - * @param {number} y The data y-coordinate. - * @param {number} [axis] The axis number on which the data coordinate lives. - * @return {number} A fraction in [0, 1] where 0 = the top edge. - */Dygraph.prototype.toPercentYCoord = function(y,axis){if(y === null){return null;}if(typeof axis == "undefined")axis = 0;var yRange=this.yAxisRange(axis);var pct;var logscale=this.attributes_.getForAxis("logscale",axis);if(logscale){var logr0=utils.log10(yRange[0]);var logr1=utils.log10(yRange[1]);pct = (logr1 - utils.log10(y)) / (logr1 - logr0);}else { // yRange[1] - y is unit distance from the bottom. -// yRange[1] - yRange[0] is the scale of the range. -// (yRange[1] - y) / (yRange[1] - yRange[0]) is the % from the bottom. -pct = (yRange[1] - y) / (yRange[1] - yRange[0]);}return pct;}; /** - * Converts an x value to a percentage from the left to the right of - * the drawing area. - * - * If the coordinate represents a value visible on the canvas, then - * the value will be between 0 and 1, where 0 is the left of the canvas. - * However, this method will return values outside the range, as - * values can fall outside the canvas. - * - * If x is null, this returns null. - * @param {number} x The data x-coordinate. - * @return {number} A fraction in [0, 1] where 0 = the left edge. - */Dygraph.prototype.toPercentXCoord = function(x){if(x === null){return null;}var xRange=this.xAxisRange();var pct;var logscale=this.attributes_.getForAxis("logscale",'x');if(logscale === true){ // logscale can be null so we test for true explicitly. -var logr0=utils.log10(xRange[0]);var logr1=utils.log10(xRange[1]);pct = (utils.log10(x) - logr0) / (logr1 - logr0);}else { // x - xRange[0] is unit distance from the left. -// xRange[1] - xRange[0] is the scale of the range. -// The full expression below is the % from the left. -pct = (x - xRange[0]) / (xRange[1] - xRange[0]);}return pct;}; /** - * Returns the number of columns (including the independent variable). - * @return {number} The number of columns. - */Dygraph.prototype.numColumns = function(){if(!this.rawData_)return 0;return this.rawData_[0]?this.rawData_[0].length:this.attr_("labels").length;}; /** - * Returns the number of rows (excluding any header/label row). - * @return {number} The number of rows, less any header. - */Dygraph.prototype.numRows = function(){if(!this.rawData_)return 0;return this.rawData_.length;}; /** - * Returns the value in the given row and column. If the row and column exceed - * the bounds on the data, returns null. Also returns null if the value is - * missing. - * @param {number} row The row number of the data (0-based). Row 0 is the - * first row of data, not a header row. - * @param {number} col The column number of the data (0-based) - * @return {number} The value in the specified cell or null if the row/col - * were out of range. - */Dygraph.prototype.getValue = function(row,col){if(row < 0 || row > this.rawData_.length)return null;if(col < 0 || col > this.rawData_[row].length)return null;return this.rawData_[row][col];}; /** - * Generates interface elements for the Dygraph: a containing div, a div to - * display the current point, and a textbox to adjust the rolling average - * period. Also creates the Renderer/Layout elements. - * @private - */Dygraph.prototype.createInterface_ = function(){ // Create the all-enclosing graph div -var enclosing=this.maindiv_;this.graphDiv = document.createElement("div"); // TODO(danvk): any other styles that are useful to set here? -this.graphDiv.style.textAlign = 'left'; // This is a CSS "reset" -this.graphDiv.style.position = 'relative';enclosing.appendChild(this.graphDiv); // Create the canvas for interactive parts of the chart. -this.canvas_ = utils.createCanvas();this.canvas_.style.position = "absolute"; // ... and for static parts of the chart. -this.hidden_ = this.createPlotKitCanvas_(this.canvas_);this.canvas_ctx_ = utils.getContext(this.canvas_);this.hidden_ctx_ = utils.getContext(this.hidden_);this.resizeElements_(); // The interactive parts of the graph are drawn on top of the chart. -this.graphDiv.appendChild(this.hidden_);this.graphDiv.appendChild(this.canvas_);this.mouseEventElement_ = this.createMouseEventElement_(); // Create the grapher -this.layout_ = new _dygraphLayout2['default'](this);var dygraph=this;this.mouseMoveHandler_ = function(e){dygraph.mouseMove_(e);};this.mouseOutHandler_ = function(e){ // The mouse has left the chart if: -// 1. e.target is inside the chart -// 2. e.relatedTarget is outside the chart -var target=e.target || e.fromElement;var relatedTarget=e.relatedTarget || e.toElement;if(utils.isNodeContainedBy(target,dygraph.graphDiv) && !utils.isNodeContainedBy(relatedTarget,dygraph.graphDiv)){dygraph.mouseOut_(e);}};this.addAndTrackEvent(window,'mouseout',this.mouseOutHandler_);this.addAndTrackEvent(this.mouseEventElement_,'mousemove',this.mouseMoveHandler_); // Don't recreate and register the resize handler on subsequent calls. -// This happens when the graph is resized. -if(!this.resizeHandler_){this.resizeHandler_ = function(e){dygraph.resize();}; // Update when the window is resized. -// TODO(danvk): drop frames depending on complexity of the chart. -this.addAndTrackEvent(window,'resize',this.resizeHandler_);}};Dygraph.prototype.resizeElements_ = function(){this.graphDiv.style.width = this.width_ + "px";this.graphDiv.style.height = this.height_ + "px";var pixelRatioOption=this.getNumericOption('pixelRatio');var canvasScale=pixelRatioOption || utils.getContextPixelRatio(this.canvas_ctx_);this.canvas_.width = this.width_ * canvasScale;this.canvas_.height = this.height_ * canvasScale;this.canvas_.style.width = this.width_ + "px"; // for IE -this.canvas_.style.height = this.height_ + "px"; // for IE -if(canvasScale !== 1){this.canvas_ctx_.scale(canvasScale,canvasScale);}var hiddenScale=pixelRatioOption || utils.getContextPixelRatio(this.hidden_ctx_);this.hidden_.width = this.width_ * hiddenScale;this.hidden_.height = this.height_ * hiddenScale;this.hidden_.style.width = this.width_ + "px"; // for IE -this.hidden_.style.height = this.height_ + "px"; // for IE -if(hiddenScale !== 1){this.hidden_ctx_.scale(hiddenScale,hiddenScale);}}; /** - * Detach DOM elements in the dygraph and null out all data references. - * Calling this when you're done with a dygraph can dramatically reduce memory - * usage. See, e.g., the tests/perf.html example. - */Dygraph.prototype.destroy = function(){this.canvas_ctx_.restore();this.hidden_ctx_.restore(); // Destroy any plugins, in the reverse order that they were registered. -for(var i=this.plugins_.length - 1;i >= 0;i--) {var p=this.plugins_.pop();if(p.plugin.destroy)p.plugin.destroy();}var removeRecursive=function removeRecursive(node){while(node.hasChildNodes()) {removeRecursive(node.firstChild);node.removeChild(node.firstChild);}};this.removeTrackedEvents_(); // remove mouse event handlers (This may not be necessary anymore) -utils.removeEvent(window,'mouseout',this.mouseOutHandler_);utils.removeEvent(this.mouseEventElement_,'mousemove',this.mouseMoveHandler_); // remove window handlers -utils.removeEvent(window,'resize',this.resizeHandler_);this.resizeHandler_ = null;removeRecursive(this.maindiv_);var nullOut=function nullOut(obj){for(var n in obj) {if(typeof obj[n] === 'object'){obj[n] = null;}}}; // These may not all be necessary, but it can't hurt... -nullOut(this.layout_);nullOut(this.plotter_);nullOut(this);}; /** - * Creates the canvas on which the chart will be drawn. Only the Renderer ever - * draws on this particular canvas. All Dygraph work (i.e. drawing hover dots - * or the zoom rectangles) is done on this.canvas_. - * @param {Object} canvas The Dygraph canvas over which to overlay the plot - * @return {Object} The newly-created canvas - * @private - */Dygraph.prototype.createPlotKitCanvas_ = function(canvas){var h=utils.createCanvas();h.style.position = "absolute"; // TODO(danvk): h should be offset from canvas. canvas needs to include -// some extra area to make it easier to zoom in on the far left and far -// right. h needs to be precisely the plot area, so that clipping occurs. -h.style.top = canvas.style.top;h.style.left = canvas.style.left;h.width = this.width_;h.height = this.height_;h.style.width = this.width_ + "px"; // for IE -h.style.height = this.height_ + "px"; // for IE -return h;}; /** - * Creates an overlay element used to handle mouse events. - * @return {Object} The mouse event element. - * @private - */Dygraph.prototype.createMouseEventElement_ = function(){return this.canvas_;}; /** - * Generate a set of distinct colors for the data series. This is done with a - * color wheel. Saturation/Value are customizable, and the hue is - * equally-spaced around the color wheel. If a custom set of colors is - * specified, that is used instead. - * @private - */Dygraph.prototype.setColors_ = function(){var labels=this.getLabels();var num=labels.length - 1;this.colors_ = [];this.colorsMap_ = {}; // These are used for when no custom colors are specified. -var sat=this.getNumericOption('colorSaturation') || 1.0;var val=this.getNumericOption('colorValue') || 0.5;var half=Math.ceil(num / 2);var colors=this.getOption('colors');var visibility=this.visibility();for(var i=0;i < num;i++) {if(!visibility[i]){continue;}var label=labels[i + 1];var colorStr=this.attributes_.getForSeries('color',label);if(!colorStr){if(colors){colorStr = colors[i % colors.length];}else { // alternate colors for high contrast. -var idx=i % 2?half + (i + 1) / 2:Math.ceil((i + 1) / 2);var hue=1.0 * idx / (1 + num);colorStr = utils.hsvToRGB(hue,sat,val);}}this.colors_.push(colorStr);this.colorsMap_[label] = colorStr;}}; /** - * Return the list of colors. This is either the list of colors passed in the - * attributes or the autogenerated list of rgb(r,g,b) strings. - * This does not return colors for invisible series. - * @return {Array.} The list of colors. - */Dygraph.prototype.getColors = function(){return this.colors_;}; /** - * Returns a few attributes of a series, i.e. its color, its visibility, which - * axis it's assigned to, and its column in the original data. - * Returns null if the series does not exist. - * Otherwise, returns an object with column, visibility, color and axis properties. - * The "axis" property will be set to 1 for y1 and 2 for y2. - * The "column" property can be fed back into getValue(row, column) to get - * values for this series. - */Dygraph.prototype.getPropertiesForSeries = function(series_name){var idx=-1;var labels=this.getLabels();for(var i=1;i < labels.length;i++) {if(labels[i] == series_name){idx = i;break;}}if(idx == -1)return null;return {name:series_name,column:idx,visible:this.visibility()[idx - 1],color:this.colorsMap_[series_name],axis:1 + this.attributes_.axisForSeries(series_name)};}; /** - * Create the text box to adjust the averaging period - * @private - */Dygraph.prototype.createRollInterface_ = function(){var _this=this; // Create a roller if one doesn't exist already. -var roller=this.roller_;if(!roller){this.roller_ = roller = document.createElement("input");roller.type = "text";roller.style.display = "none";roller.className = 'dygraph-roller';this.graphDiv.appendChild(roller);}var display=this.getBooleanOption('showRoller')?'block':'none';var area=this.getArea();var textAttr={"top":area.y + area.h - 25 + "px","left":area.x + 1 + "px","display":display};roller.size = "2";roller.value = this.rollPeriod_;utils.update(roller.style,textAttr);roller.onchange = function(){return _this.adjustRoll(roller.value);};}; /** - * Set up all the mouse handlers needed to capture dragging behavior for zoom - * events. - * @private - */Dygraph.prototype.createDragInterface_ = function(){var context={ // Tracks whether the mouse is down right now -isZooming:false,isPanning:false, // is this drag part of a pan? -is2DPan:false, // if so, is that pan 1- or 2-dimensional? -dragStartX:null, // pixel coordinates -dragStartY:null, // pixel coordinates -dragEndX:null, // pixel coordinates -dragEndY:null, // pixel coordinates -dragDirection:null,prevEndX:null, // pixel coordinates -prevEndY:null, // pixel coordinates -prevDragDirection:null,cancelNextDblclick:false, // see comment in dygraph-interaction-model.js -// The value on the left side of the graph when a pan operation starts. -initialLeftmostDate:null, // The number of units each pixel spans. (This won't be valid for log -// scales) -xUnitsPerPixel:null, // TODO(danvk): update this comment -// The range in second/value units that the viewport encompasses during a -// panning operation. -dateRange:null, // Top-left corner of the canvas, in DOM coords -// TODO(konigsberg): Rename topLeftCanvasX, topLeftCanvasY. -px:0,py:0, // Values for use with panEdgeFraction, which limit how far outside the -// graph's data boundaries it can be panned. -boundedDates:null, // [minDate, maxDate] -boundedValues:null, // [[minValue, maxValue] ...] -// We cover iframes during mouse interactions. See comments in -// dygraph-utils.js for more info on why this is a good idea. -tarp:new _iframeTarp2['default'](), // contextB is the same thing as this context object but renamed. -initializeMouseDown:function initializeMouseDown(event,g,contextB){ // prevents mouse drags from selecting page text. -if(event.preventDefault){event.preventDefault(); // Firefox, Chrome, etc. -}else {event.returnValue = false; // IE -event.cancelBubble = true;}var canvasPos=utils.findPos(g.canvas_);contextB.px = canvasPos.x;contextB.py = canvasPos.y;contextB.dragStartX = utils.dragGetX_(event,contextB);contextB.dragStartY = utils.dragGetY_(event,contextB);contextB.cancelNextDblclick = false;contextB.tarp.cover();},destroy:function destroy(){var context=this;if(context.isZooming || context.isPanning){context.isZooming = false;context.dragStartX = null;context.dragStartY = null;}if(context.isPanning){context.isPanning = false;context.draggingDate = null;context.dateRange = null;for(var i=0;i < self.axes_.length;i++) {delete self.axes_[i].draggingValue;delete self.axes_[i].dragValueRange;}}context.tarp.uncover();}};var interactionModel=this.getOption("interactionModel"); // Self is the graph. -var self=this; // Function that binds the graph and context to the handler. -var bindHandler=function bindHandler(handler){return function(event){handler(event,self,context);};};for(var eventName in interactionModel) {if(!interactionModel.hasOwnProperty(eventName))continue;this.addAndTrackEvent(this.mouseEventElement_,eventName,bindHandler(interactionModel[eventName]));} // If the user releases the mouse button during a drag, but not over the -// canvas, then it doesn't count as a zooming action. -if(!interactionModel.willDestroyContextMyself){var mouseUpHandler=function mouseUpHandler(event){context.destroy();};this.addAndTrackEvent(document,'mouseup',mouseUpHandler);}}; /** - * Draw a gray zoom rectangle over the desired area of the canvas. Also clears - * up any previous zoom rectangles that were drawn. This could be optimized to - * avoid extra redrawing, but it's tricky to avoid interactions with the status - * dots. - * - * @param {number} direction the direction of the zoom rectangle. Acceptable - * values are utils.HORIZONTAL and utils.VERTICAL. - * @param {number} startX The X position where the drag started, in canvas - * coordinates. - * @param {number} endX The current X position of the drag, in canvas coords. - * @param {number} startY The Y position where the drag started, in canvas - * coordinates. - * @param {number} endY The current Y position of the drag, in canvas coords. - * @param {number} prevDirection the value of direction on the previous call to - * this function. Used to avoid excess redrawing - * @param {number} prevEndX The value of endX on the previous call to this - * function. Used to avoid excess redrawing - * @param {number} prevEndY The value of endY on the previous call to this - * function. Used to avoid excess redrawing - * @private - */Dygraph.prototype.drawZoomRect_ = function(direction,startX,endX,startY,endY,prevDirection,prevEndX,prevEndY){var ctx=this.canvas_ctx_; // Clean up from the previous rect if necessary -if(prevDirection == utils.HORIZONTAL){ctx.clearRect(Math.min(startX,prevEndX),this.layout_.getPlotArea().y,Math.abs(startX - prevEndX),this.layout_.getPlotArea().h);}else if(prevDirection == utils.VERTICAL){ctx.clearRect(this.layout_.getPlotArea().x,Math.min(startY,prevEndY),this.layout_.getPlotArea().w,Math.abs(startY - prevEndY));} // Draw a light-grey rectangle to show the new viewing area -if(direction == utils.HORIZONTAL){if(endX && startX){ctx.fillStyle = "rgba(128,128,128,0.33)";ctx.fillRect(Math.min(startX,endX),this.layout_.getPlotArea().y,Math.abs(endX - startX),this.layout_.getPlotArea().h);}}else if(direction == utils.VERTICAL){if(endY && startY){ctx.fillStyle = "rgba(128,128,128,0.33)";ctx.fillRect(this.layout_.getPlotArea().x,Math.min(startY,endY),this.layout_.getPlotArea().w,Math.abs(endY - startY));}}}; /** - * Clear the zoom rectangle (and perform no zoom). - * @private - */Dygraph.prototype.clearZoomRect_ = function(){this.currentZoomRectArgs_ = null;this.canvas_ctx_.clearRect(0,0,this.width_,this.height_);}; /** - * Zoom to something containing [lowX, highX]. These are pixel coordinates in - * the canvas. The exact zoom window may be slightly larger if there are no data - * points near lowX or highX. Don't confuse this function with doZoomXDates, - * which accepts dates that match the raw data. This function redraws the graph. - * - * @param {number} lowX The leftmost pixel value that should be visible. - * @param {number} highX The rightmost pixel value that should be visible. - * @private - */Dygraph.prototype.doZoomX_ = function(lowX,highX){this.currentZoomRectArgs_ = null; // Find the earliest and latest dates contained in this canvasx range. -// Convert the call to date ranges of the raw data. -var minDate=this.toDataXCoord(lowX);var maxDate=this.toDataXCoord(highX);this.doZoomXDates_(minDate,maxDate);}; /** - * Zoom to something containing [minDate, maxDate] values. Don't confuse this - * method with doZoomX which accepts pixel coordinates. This function redraws - * the graph. - * - * @param {number} minDate The minimum date that should be visible. - * @param {number} maxDate The maximum date that should be visible. - * @private - */Dygraph.prototype.doZoomXDates_ = function(minDate,maxDate){var _this2=this; // TODO(danvk): when xAxisRange is null (i.e. "fit to data", the animation -// can produce strange effects. Rather than the x-axis transitioning slowly -// between values, it can jerk around.) -var old_window=this.xAxisRange();var new_window=[minDate,maxDate];var zoomCallback=this.getFunctionOption('zoomCallback');this.doAnimatedZoom(old_window,new_window,null,null,function(){if(zoomCallback){zoomCallback.call(_this2,minDate,maxDate,_this2.yAxisRanges());}});}; /** - * Zoom to something containing [lowY, highY]. These are pixel coordinates in - * the canvas. This function redraws the graph. - * - * @param {number} lowY The topmost pixel value that should be visible. - * @param {number} highY The lowest pixel value that should be visible. - * @private - */Dygraph.prototype.doZoomY_ = function(lowY,highY){var _this3=this;this.currentZoomRectArgs_ = null; // Find the highest and lowest values in pixel range for each axis. -// Note that lowY (in pixels) corresponds to the max Value (in data coords). -// This is because pixels increase as you go down on the screen, whereas data -// coordinates increase as you go up the screen. -var oldValueRanges=this.yAxisRanges();var newValueRanges=[];for(var i=0;i < this.axes_.length;i++) {var hi=this.toDataYCoord(lowY,i);var low=this.toDataYCoord(highY,i);newValueRanges.push([low,hi]);}var zoomCallback=this.getFunctionOption('zoomCallback');this.doAnimatedZoom(null,null,oldValueRanges,newValueRanges,function(){if(zoomCallback){var _xAxisRange=_this3.xAxisRange();var _xAxisRange2=_slicedToArray(_xAxisRange,2);var minX=_xAxisRange2[0];var maxX=_xAxisRange2[1];zoomCallback.call(_this3,minX,maxX,_this3.yAxisRanges());}});}; /** - * Transition function to use in animations. Returns values between 0.0 - * (totally old values) and 1.0 (totally new values) for each frame. - * @private - */Dygraph.zoomAnimationFunction = function(frame,numFrames){var k=1.5;return (1.0 - Math.pow(k,-frame)) / (1.0 - Math.pow(k,-numFrames));}; /** - * Reset the zoom to the original view coordinates. This is the same as - * double-clicking on the graph. - */Dygraph.prototype.resetZoom = function(){var _this4=this;var dirtyX=this.isZoomed('x');var dirtyY=this.isZoomed('y');var dirty=dirtyX || dirtyY; // Clear any selection, since it's likely to be drawn in the wrong place. -this.clearSelection();if(!dirty)return; // Calculate extremes to avoid lack of padding on reset. -var _xAxisExtremes=this.xAxisExtremes();var _xAxisExtremes2=_slicedToArray(_xAxisExtremes,2);var minDate=_xAxisExtremes2[0];var maxDate=_xAxisExtremes2[1];var animatedZooms=this.getBooleanOption('animatedZooms');var zoomCallback=this.getFunctionOption('zoomCallback'); // TODO(danvk): merge this block w/ the code below. -// TODO(danvk): factor out a generic, public zoomTo method. -if(!animatedZooms){this.dateWindow_ = null;this.axes_.forEach(function(axis){if(axis.valueRange)delete axis.valueRange;});this.drawGraph_();if(zoomCallback){zoomCallback.call(this,minDate,maxDate,this.yAxisRanges());}return;}var oldWindow=null,newWindow=null,oldValueRanges=null,newValueRanges=null;if(dirtyX){oldWindow = this.xAxisRange();newWindow = [minDate,maxDate];}if(dirtyY){oldValueRanges = this.yAxisRanges();newValueRanges = this.yAxisExtremes();}this.doAnimatedZoom(oldWindow,newWindow,oldValueRanges,newValueRanges,function(){_this4.dateWindow_ = null;_this4.axes_.forEach(function(axis){if(axis.valueRange)delete axis.valueRange;});if(zoomCallback){zoomCallback.call(_this4,minDate,maxDate,_this4.yAxisRanges());}});}; /** - * Combined animation logic for all zoom functions. - * either the x parameters or y parameters may be null. - * @private - */Dygraph.prototype.doAnimatedZoom = function(oldXRange,newXRange,oldYRanges,newYRanges,callback){var _this5=this;var steps=this.getBooleanOption("animatedZooms")?Dygraph.ANIMATION_STEPS:1;var windows=[];var valueRanges=[];var step,frac;if(oldXRange !== null && newXRange !== null){for(step = 1;step <= steps;step++) {frac = Dygraph.zoomAnimationFunction(step,steps);windows[step - 1] = [oldXRange[0] * (1 - frac) + frac * newXRange[0],oldXRange[1] * (1 - frac) + frac * newXRange[1]];}}if(oldYRanges !== null && newYRanges !== null){for(step = 1;step <= steps;step++) {frac = Dygraph.zoomAnimationFunction(step,steps);var thisRange=[];for(var j=0;j < this.axes_.length;j++) {thisRange.push([oldYRanges[j][0] * (1 - frac) + frac * newYRanges[j][0],oldYRanges[j][1] * (1 - frac) + frac * newYRanges[j][1]]);}valueRanges[step - 1] = thisRange;}}utils.repeatAndCleanup(function(step){if(valueRanges.length){for(var i=0;i < _this5.axes_.length;i++) {var w=valueRanges[step][i];_this5.axes_[i].valueRange = [w[0],w[1]];}}if(windows.length){_this5.dateWindow_ = windows[step];}_this5.drawGraph_();},steps,Dygraph.ANIMATION_DURATION / steps,callback);}; /** - * Get the current graph's area object. - * - * Returns: {x, y, w, h} - */Dygraph.prototype.getArea = function(){return this.plotter_.area;}; /** - * Convert a mouse event to DOM coordinates relative to the graph origin. - * - * Returns a two-element array: [X, Y]. - */Dygraph.prototype.eventToDomCoords = function(event){if(event.offsetX && event.offsetY){return [event.offsetX,event.offsetY];}else {var eventElementPos=utils.findPos(this.mouseEventElement_);var canvasx=utils.pageX(event) - eventElementPos.x;var canvasy=utils.pageY(event) - eventElementPos.y;return [canvasx,canvasy];}}; /** - * Given a canvas X coordinate, find the closest row. - * @param {number} domX graph-relative DOM X coordinate - * Returns {number} row number. - * @private - */Dygraph.prototype.findClosestRow = function(domX){var minDistX=Infinity;var closestRow=-1;var sets=this.layout_.points;for(var i=0;i < sets.length;i++) {var points=sets[i];var len=points.length;for(var j=0;j < len;j++) {var point=points[j];if(!utils.isValidPoint(point,true))continue;var dist=Math.abs(point.canvasx - domX);if(dist < minDistX){minDistX = dist;closestRow = point.idx;}}}return closestRow;}; /** - * Given canvas X,Y coordinates, find the closest point. - * - * This finds the individual data point across all visible series - * that's closest to the supplied DOM coordinates using the standard - * Euclidean X,Y distance. - * - * @param {number} domX graph-relative DOM X coordinate - * @param {number} domY graph-relative DOM Y coordinate - * Returns: {row, seriesName, point} - * @private - */Dygraph.prototype.findClosestPoint = function(domX,domY){var minDist=Infinity;var dist,dx,dy,point,closestPoint,closestSeries,closestRow;for(var setIdx=this.layout_.points.length - 1;setIdx >= 0;--setIdx) {var points=this.layout_.points[setIdx];for(var i=0;i < points.length;++i) {point = points[i];if(!utils.isValidPoint(point))continue;dx = point.canvasx - domX;dy = point.canvasy - domY;dist = dx * dx + dy * dy;if(dist < minDist){minDist = dist;closestPoint = point;closestSeries = setIdx;closestRow = point.idx;}}}var name=this.layout_.setNames[closestSeries];return {row:closestRow,seriesName:name,point:closestPoint};}; /** - * Given canvas X,Y coordinates, find the touched area in a stacked graph. - * - * This first finds the X data point closest to the supplied DOM X coordinate, - * then finds the series which puts the Y coordinate on top of its filled area, - * using linear interpolation between adjacent point pairs. - * - * @param {number} domX graph-relative DOM X coordinate - * @param {number} domY graph-relative DOM Y coordinate - * Returns: {row, seriesName, point} - * @private - */Dygraph.prototype.findStackedPoint = function(domX,domY){var row=this.findClosestRow(domX);var closestPoint,closestSeries;for(var setIdx=0;setIdx < this.layout_.points.length;++setIdx) {var boundary=this.getLeftBoundary_(setIdx);var rowIdx=row - boundary;var points=this.layout_.points[setIdx];if(rowIdx >= points.length)continue;var p1=points[rowIdx];if(!utils.isValidPoint(p1))continue;var py=p1.canvasy;if(domX > p1.canvasx && rowIdx + 1 < points.length){ // interpolate series Y value using next point -var p2=points[rowIdx + 1];if(utils.isValidPoint(p2)){var dx=p2.canvasx - p1.canvasx;if(dx > 0){var r=(domX - p1.canvasx) / dx;py += r * (p2.canvasy - p1.canvasy);}}}else if(domX < p1.canvasx && rowIdx > 0){ // interpolate series Y value using previous point -var p0=points[rowIdx - 1];if(utils.isValidPoint(p0)){var dx=p1.canvasx - p0.canvasx;if(dx > 0){var r=(p1.canvasx - domX) / dx;py += r * (p0.canvasy - p1.canvasy);}}} // Stop if the point (domX, py) is above this series' upper edge -if(setIdx === 0 || py < domY){closestPoint = p1;closestSeries = setIdx;}}var name=this.layout_.setNames[closestSeries];return {row:row,seriesName:name,point:closestPoint};}; /** - * When the mouse moves in the canvas, display information about a nearby data - * point and draw dots over those points in the data series. This function - * takes care of cleanup of previously-drawn dots. - * @param {Object} event The mousemove event from the browser. - * @private - */Dygraph.prototype.mouseMove_ = function(event){ // This prevents JS errors when mousing over the canvas before data loads. -var points=this.layout_.points;if(points === undefined || points === null)return;var canvasCoords=this.eventToDomCoords(event);var canvasx=canvasCoords[0];var canvasy=canvasCoords[1];var highlightSeriesOpts=this.getOption("highlightSeriesOpts");var selectionChanged=false;if(highlightSeriesOpts && !this.isSeriesLocked()){var closest;if(this.getBooleanOption("stackedGraph")){closest = this.findStackedPoint(canvasx,canvasy);}else {closest = this.findClosestPoint(canvasx,canvasy);}selectionChanged = this.setSelection(closest.row,closest.seriesName);}else {var idx=this.findClosestRow(canvasx);selectionChanged = this.setSelection(idx);}var callback=this.getFunctionOption("highlightCallback");if(callback && selectionChanged){callback.call(this,event,this.lastx_,this.selPoints_,this.lastRow_,this.highlightSet_);}}; /** - * Fetch left offset from the specified set index or if not passed, the - * first defined boundaryIds record (see bug #236). - * @private - */Dygraph.prototype.getLeftBoundary_ = function(setIdx){if(this.boundaryIds_[setIdx]){return this.boundaryIds_[setIdx][0];}else {for(var i=0;i < this.boundaryIds_.length;i++) {if(this.boundaryIds_[i] !== undefined){return this.boundaryIds_[i][0];}}return 0;}};Dygraph.prototype.animateSelection_ = function(direction){var totalSteps=10;var millis=30;if(this.fadeLevel === undefined)this.fadeLevel = 0;if(this.animateId === undefined)this.animateId = 0;var start=this.fadeLevel;var steps=direction < 0?start:totalSteps - start;if(steps <= 0){if(this.fadeLevel){this.updateSelection_(1.0);}return;}var thisId=++this.animateId;var that=this;var cleanupIfClearing=function cleanupIfClearing(){ // if we haven't reached fadeLevel 0 in the max frame time, -// ensure that the clear happens and just go to 0 -if(that.fadeLevel !== 0 && direction < 0){that.fadeLevel = 0;that.clearSelection();}};utils.repeatAndCleanup(function(n){ // ignore simultaneous animations -if(that.animateId != thisId)return;that.fadeLevel += direction;if(that.fadeLevel === 0){that.clearSelection();}else {that.updateSelection_(that.fadeLevel / totalSteps);}},steps,millis,cleanupIfClearing);}; /** - * Draw dots over the selectied points in the data series. This function - * takes care of cleanup of previously-drawn dots. - * @private - */Dygraph.prototype.updateSelection_ = function(opt_animFraction){ /*var defaultPrevented = */this.cascadeEvents_('select',{selectedRow:this.lastRow_ === -1?undefined:this.lastRow_,selectedX:this.lastx_ === -1?undefined:this.lastx_,selectedPoints:this.selPoints_}); // TODO(danvk): use defaultPrevented here? -// Clear the previously drawn vertical, if there is one -var i;var ctx=this.canvas_ctx_;if(this.getOption('highlightSeriesOpts')){ctx.clearRect(0,0,this.width_,this.height_);var alpha=1.0 - this.getNumericOption('highlightSeriesBackgroundAlpha');var backgroundColor=utils.toRGB_(this.getOption('highlightSeriesBackgroundColor'));if(alpha){ // Activating background fade includes an animation effect for a gradual -// fade. TODO(klausw): make this independently configurable if it causes -// issues? Use a shared preference to control animations? -var animateBackgroundFade=true;if(animateBackgroundFade){if(opt_animFraction === undefined){ // start a new animation -this.animateSelection_(1);return;}alpha *= opt_animFraction;}ctx.fillStyle = 'rgba(' + backgroundColor.r + ',' + backgroundColor.g + ',' + backgroundColor.b + ',' + alpha + ')';ctx.fillRect(0,0,this.width_,this.height_);} // Redraw only the highlighted series in the interactive canvas (not the -// static plot canvas, which is where series are usually drawn). -this.plotter_._renderLineChart(this.highlightSet_,ctx);}else if(this.previousVerticalX_ >= 0){ // Determine the maximum highlight circle size. -var maxCircleSize=0;var labels=this.attr_('labels');for(i = 1;i < labels.length;i++) {var r=this.getNumericOption('highlightCircleSize',labels[i]);if(r > maxCircleSize)maxCircleSize = r;}var px=this.previousVerticalX_;ctx.clearRect(px - maxCircleSize - 1,0,2 * maxCircleSize + 2,this.height_);}if(this.selPoints_.length > 0){ // Draw colored circles over the center of each selected point -var canvasx=this.selPoints_[0].canvasx;ctx.save();for(i = 0;i < this.selPoints_.length;i++) {var pt=this.selPoints_[i];if(isNaN(pt.canvasy))continue;var circleSize=this.getNumericOption('highlightCircleSize',pt.name);var callback=this.getFunctionOption("drawHighlightPointCallback",pt.name);var color=this.plotter_.colors[pt.name];if(!callback){callback = utils.Circles.DEFAULT;}ctx.lineWidth = this.getNumericOption('strokeWidth',pt.name);ctx.strokeStyle = color;ctx.fillStyle = color;callback.call(this,this,pt.name,ctx,canvasx,pt.canvasy,color,circleSize,pt.idx);}ctx.restore();this.previousVerticalX_ = canvasx;}}; /** - * Manually set the selected points and display information about them in the - * legend. The selection can be cleared using clearSelection() and queried - * using getSelection(). - * - * To set a selected series but not a selected point, call setSelection with - * row=false and the selected series name. - * - * @param {number} row Row number that should be highlighted (i.e. appear with - * hover dots on the chart). - * @param {seriesName} optional series name to highlight that series with the - * the highlightSeriesOpts setting. - * @param { locked } optional If true, keep seriesName selected when mousing - * over the graph, disabling closest-series highlighting. Call clearSelection() - * to unlock it. - */Dygraph.prototype.setSelection = function(row,opt_seriesName,opt_locked){ // Extract the points we've selected -this.selPoints_ = [];var changed=false;if(row !== false && row >= 0){if(row != this.lastRow_)changed = true;this.lastRow_ = row;for(var setIdx=0;setIdx < this.layout_.points.length;++setIdx) {var points=this.layout_.points[setIdx]; // Check if the point at the appropriate index is the point we're looking -// for. If it is, just use it, otherwise search the array for a point -// in the proper place. -var setRow=row - this.getLeftBoundary_(setIdx);if(setRow >= 0 && setRow < points.length && points[setRow].idx == row){var point=points[setRow];if(point.yval !== null)this.selPoints_.push(point);}else {for(var pointIdx=0;pointIdx < points.length;++pointIdx) {var point=points[pointIdx];if(point.idx == row){if(point.yval !== null){this.selPoints_.push(point);}break;}}}}}else {if(this.lastRow_ >= 0)changed = true;this.lastRow_ = -1;}if(this.selPoints_.length){this.lastx_ = this.selPoints_[0].xval;}else {this.lastx_ = -1;}if(opt_seriesName !== undefined){if(this.highlightSet_ !== opt_seriesName)changed = true;this.highlightSet_ = opt_seriesName;}if(opt_locked !== undefined){this.lockedSet_ = opt_locked;}if(changed){this.updateSelection_(undefined);}return changed;}; /** - * The mouse has left the canvas. Clear out whatever artifacts remain - * @param {Object} event the mouseout event from the browser. - * @private - */Dygraph.prototype.mouseOut_ = function(event){if(this.getFunctionOption("unhighlightCallback")){this.getFunctionOption("unhighlightCallback").call(this,event);}if(this.getBooleanOption("hideOverlayOnMouseOut") && !this.lockedSet_){this.clearSelection();}}; /** - * Clears the current selection (i.e. points that were highlighted by moving - * the mouse over the chart). - */Dygraph.prototype.clearSelection = function(){this.cascadeEvents_('deselect',{});this.lockedSet_ = false; // Get rid of the overlay data -if(this.fadeLevel){this.animateSelection_(-1);return;}this.canvas_ctx_.clearRect(0,0,this.width_,this.height_);this.fadeLevel = 0;this.selPoints_ = [];this.lastx_ = -1;this.lastRow_ = -1;this.highlightSet_ = null;}; /** - * Returns the number of the currently selected row. To get data for this row, - * you can use the getValue method. - * @return {number} row number, or -1 if nothing is selected - */Dygraph.prototype.getSelection = function(){if(!this.selPoints_ || this.selPoints_.length < 1){return -1;}for(var setIdx=0;setIdx < this.layout_.points.length;setIdx++) {var points=this.layout_.points[setIdx];for(var row=0;row < points.length;row++) {if(points[row].x == this.selPoints_[0].x){return points[row].idx;}}}return -1;}; /** - * Returns the name of the currently-highlighted series. - * Only available when the highlightSeriesOpts option is in use. - */Dygraph.prototype.getHighlightSeries = function(){return this.highlightSet_;}; /** - * Returns true if the currently-highlighted series was locked - * via setSelection(..., seriesName, true). - */Dygraph.prototype.isSeriesLocked = function(){return this.lockedSet_;}; /** - * Fires when there's data available to be graphed. - * @param {string} data Raw CSV data to be plotted - * @private - */Dygraph.prototype.loadedEvent_ = function(data){this.rawData_ = this.parseCSV_(data);this.cascadeDataDidUpdateEvent_();this.predraw_();}; /** - * Add ticks on the x-axis representing years, months, quarters, weeks, or days - * @private - */Dygraph.prototype.addXTicks_ = function(){ // Determine the correct ticks scale on the x-axis: quarterly, monthly, ... -var range;if(this.dateWindow_){range = [this.dateWindow_[0],this.dateWindow_[1]];}else {range = this.xAxisExtremes();}var xAxisOptionsView=this.optionsViewForAxis_('x');var xTicks=xAxisOptionsView('ticker')(range[0],range[1],this.plotter_.area.w, // TODO(danvk): should be area.width -xAxisOptionsView,this); // var msg = 'ticker(' + range[0] + ', ' + range[1] + ', ' + this.width_ + ', ' + this.attr_('pixelsPerXLabel') + ') -> ' + JSON.stringify(xTicks); -// console.log(msg); -this.layout_.setXTicks(xTicks);}; /** - * Returns the correct handler class for the currently set options. - * @private - */Dygraph.prototype.getHandlerClass_ = function(){var handlerClass;if(this.attr_('dataHandler')){handlerClass = this.attr_('dataHandler');}else if(this.fractions_){if(this.getBooleanOption('errorBars')){handlerClass = _datahandlerBarsFractions2['default'];}else {handlerClass = _datahandlerDefaultFractions2['default'];}}else if(this.getBooleanOption('customBars')){handlerClass = _datahandlerBarsCustom2['default'];}else if(this.getBooleanOption('errorBars')){handlerClass = _datahandlerBarsError2['default'];}else {handlerClass = _datahandlerDefault2['default'];}return handlerClass;}; /** - * @private - * This function is called once when the chart's data is changed or the options - * dictionary is updated. It is _not_ called when the user pans or zooms. The - * idea is that values derived from the chart's data can be computed here, - * rather than every time the chart is drawn. This includes things like the - * number of axes, rolling averages, etc. - */Dygraph.prototype.predraw_ = function(){var start=new Date(); // Create the correct dataHandler -this.dataHandler_ = new (this.getHandlerClass_())();this.layout_.computePlotArea(); // TODO(danvk): move more computations out of drawGraph_ and into here. -this.computeYAxes_();if(!this.is_initial_draw_){this.canvas_ctx_.restore();this.hidden_ctx_.restore();}this.canvas_ctx_.save();this.hidden_ctx_.save(); // Create a new plotter. -this.plotter_ = new _dygraphCanvas2['default'](this,this.hidden_,this.hidden_ctx_,this.layout_); // The roller sits in the bottom left corner of the chart. We don't know where -// this will be until the options are available, so it's positioned here. -this.createRollInterface_();this.cascadeEvents_('predraw'); // Convert the raw data (a 2D array) into the internal format and compute -// rolling averages. -this.rolledSeries_ = [null]; // x-axis is the first series and it's special -for(var i=1;i < this.numColumns();i++) { // var logScale = this.attr_('logscale', i); // TODO(klausw): this looks wrong // konigsberg thinks so too. -var series=this.dataHandler_.extractSeries(this.rawData_,i,this.attributes_);if(this.rollPeriod_ > 1){series = this.dataHandler_.rollingAverage(series,this.rollPeriod_,this.attributes_);}this.rolledSeries_.push(series);} // If the data or options have changed, then we'd better redraw. -this.drawGraph_(); // This is used to determine whether to do various animations. -var end=new Date();this.drawingTimeMs_ = end - start;}; /** - * Point structure. - * - * xval_* and yval_* are the original unscaled data values, - * while x_* and y_* are scaled to the range (0.0-1.0) for plotting. - * yval_stacked is the cumulative Y value used for stacking graphs, - * and bottom/top/minus/plus are used for error bar graphs. - * - * @typedef {{ - * idx: number, - * name: string, - * x: ?number, - * xval: ?number, - * y_bottom: ?number, - * y: ?number, - * y_stacked: ?number, - * y_top: ?number, - * yval_minus: ?number, - * yval: ?number, - * yval_plus: ?number, - * yval_stacked - * }} - */Dygraph.PointType = undefined; /** - * Calculates point stacking for stackedGraph=true. - * - * For stacking purposes, interpolate or extend neighboring data across - * NaN values based on stackedGraphNaNFill settings. This is for display - * only, the underlying data value as shown in the legend remains NaN. - * - * @param {Array.} points Point array for a single series. - * Updates each Point's yval_stacked property. - * @param {Array.} cumulativeYval Accumulated top-of-graph stacked Y - * values for the series seen so far. Index is the row number. Updated - * based on the current series's values. - * @param {Array.} seriesExtremes Min and max values, updated - * to reflect the stacked values. - * @param {string} fillMethod Interpolation method, one of 'all', 'inside', or - * 'none'. - * @private - */Dygraph.stackPoints_ = function(points,cumulativeYval,seriesExtremes,fillMethod){var lastXval=null;var prevPoint=null;var nextPoint=null;var nextPointIdx=-1; // Find the next stackable point starting from the given index. -var updateNextPoint=function updateNextPoint(idx){ // If we've previously found a non-NaN point and haven't gone past it yet, -// just use that. -if(nextPointIdx >= idx)return; // We haven't found a non-NaN point yet or have moved past it, -// look towards the right to find a non-NaN point. -for(var j=idx;j < points.length;++j) { // Clear out a previously-found point (if any) since it's no longer -// valid, we shouldn't use it for interpolation anymore. -nextPoint = null;if(!isNaN(points[j].yval) && points[j].yval !== null){nextPointIdx = j;nextPoint = points[j];break;}}};for(var i=0;i < points.length;++i) {var point=points[i];var xval=point.xval;if(cumulativeYval[xval] === undefined){cumulativeYval[xval] = 0;}var actualYval=point.yval;if(isNaN(actualYval) || actualYval === null){if(fillMethod == 'none'){actualYval = 0;}else { // Interpolate/extend for stacking purposes if possible. -updateNextPoint(i);if(prevPoint && nextPoint && fillMethod != 'none'){ // Use linear interpolation between prevPoint and nextPoint. -actualYval = prevPoint.yval + (nextPoint.yval - prevPoint.yval) * ((xval - prevPoint.xval) / (nextPoint.xval - prevPoint.xval));}else if(prevPoint && fillMethod == 'all'){actualYval = prevPoint.yval;}else if(nextPoint && fillMethod == 'all'){actualYval = nextPoint.yval;}else {actualYval = 0;}}}else {prevPoint = point;}var stackedYval=cumulativeYval[xval];if(lastXval != xval){ // If an x-value is repeated, we ignore the duplicates. -stackedYval += actualYval;cumulativeYval[xval] = stackedYval;}lastXval = xval;point.yval_stacked = stackedYval;if(stackedYval > seriesExtremes[1]){seriesExtremes[1] = stackedYval;}if(stackedYval < seriesExtremes[0]){seriesExtremes[0] = stackedYval;}}}; /** - * Loop over all fields and create datasets, calculating extreme y-values for - * each series and extreme x-indices as we go. - * - * dateWindow is passed in as an explicit parameter so that we can compute - * extreme values "speculatively", i.e. without actually setting state on the - * dygraph. - * - * @param {Array.)>>} rolledSeries, where - * rolledSeries[seriesIndex][row] = raw point, where - * seriesIndex is the column number starting with 1, and - * rawPoint is [x,y] or [x, [y, err]] or [x, [y, yminus, yplus]]. - * @param {?Array.} dateWindow [xmin, xmax] pair, or null. - * @return {{ - * points: Array.>, - * seriesExtremes: Array.>, - * boundaryIds: Array.}} - * @private - */Dygraph.prototype.gatherDatasets_ = function(rolledSeries,dateWindow){var boundaryIds=[];var points=[];var cumulativeYval=[]; // For stacked series. -var extremes={}; // series name -> [low, high] -var seriesIdx,sampleIdx;var firstIdx,lastIdx;var axisIdx; // Loop over the fields (series). Go from the last to the first, -// because if they're stacked that's how we accumulate the values. -var num_series=rolledSeries.length - 1;var series;for(seriesIdx = num_series;seriesIdx >= 1;seriesIdx--) {if(!this.visibility()[seriesIdx - 1])continue; // Prune down to the desired range, if necessary (for zooming) -// Because there can be lines going to points outside of the visible area, -// we actually prune to visible points, plus one on either side. -if(dateWindow){series = rolledSeries[seriesIdx];var low=dateWindow[0];var high=dateWindow[1]; // TODO(danvk): do binary search instead of linear search. -// TODO(danvk): pass firstIdx and lastIdx directly to the renderer. -firstIdx = null;lastIdx = null;for(sampleIdx = 0;sampleIdx < series.length;sampleIdx++) {if(series[sampleIdx][0] >= low && firstIdx === null){firstIdx = sampleIdx;}if(series[sampleIdx][0] <= high){lastIdx = sampleIdx;}}if(firstIdx === null)firstIdx = 0;var correctedFirstIdx=firstIdx;var isInvalidValue=true;while(isInvalidValue && correctedFirstIdx > 0) {correctedFirstIdx--; // check if the y value is null. -isInvalidValue = series[correctedFirstIdx][1] === null;}if(lastIdx === null)lastIdx = series.length - 1;var correctedLastIdx=lastIdx;isInvalidValue = true;while(isInvalidValue && correctedLastIdx < series.length - 1) {correctedLastIdx++;isInvalidValue = series[correctedLastIdx][1] === null;}if(correctedFirstIdx !== firstIdx){firstIdx = correctedFirstIdx;}if(correctedLastIdx !== lastIdx){lastIdx = correctedLastIdx;}boundaryIds[seriesIdx - 1] = [firstIdx,lastIdx]; // .slice's end is exclusive, we want to include lastIdx. -series = series.slice(firstIdx,lastIdx + 1);}else {series = rolledSeries[seriesIdx];boundaryIds[seriesIdx - 1] = [0,series.length - 1];}var seriesName=this.attr_("labels")[seriesIdx];var seriesExtremes=this.dataHandler_.getExtremeYValues(series,dateWindow,this.getBooleanOption("stepPlot",seriesName));var seriesPoints=this.dataHandler_.seriesToPoints(series,seriesName,boundaryIds[seriesIdx - 1][0]);if(this.getBooleanOption("stackedGraph")){axisIdx = this.attributes_.axisForSeries(seriesName);if(cumulativeYval[axisIdx] === undefined){cumulativeYval[axisIdx] = [];}Dygraph.stackPoints_(seriesPoints,cumulativeYval[axisIdx],seriesExtremes,this.getBooleanOption("stackedGraphNaNFill"));}extremes[seriesName] = seriesExtremes;points[seriesIdx] = seriesPoints;}return {points:points,extremes:extremes,boundaryIds:boundaryIds};}; /** - * Update the graph with new data. This method is called when the viewing area - * has changed. If the underlying data or options have changed, predraw_ will - * be called before drawGraph_ is called. - * - * @private - */Dygraph.prototype.drawGraph_ = function(){var start=new Date(); // This is used to set the second parameter to drawCallback, below. -var is_initial_draw=this.is_initial_draw_;this.is_initial_draw_ = false;this.layout_.removeAllDatasets();this.setColors_();this.attrs_.pointSize = 0.5 * this.getNumericOption('highlightCircleSize');var packed=this.gatherDatasets_(this.rolledSeries_,this.dateWindow_);var points=packed.points;var extremes=packed.extremes;this.boundaryIds_ = packed.boundaryIds;this.setIndexByName_ = {};var labels=this.attr_("labels");var dataIdx=0;for(var i=1;i < points.length;i++) {if(!this.visibility()[i - 1])continue;this.layout_.addDataset(labels[i],points[i]);this.datasetIndex_[i] = dataIdx++;}for(var i=0;i < labels.length;i++) {this.setIndexByName_[labels[i]] = i;}this.computeYAxisRanges_(extremes);this.layout_.setYAxes(this.axes_);this.addXTicks_(); // Tell PlotKit to use this new data and render itself -this.layout_.evaluate();this.renderGraph_(is_initial_draw);if(this.getStringOption("timingName")){var end=new Date();console.log(this.getStringOption("timingName") + " - drawGraph: " + (end - start) + "ms");}}; /** - * This does the work of drawing the chart. It assumes that the layout and axis - * scales have already been set (e.g. by predraw_). - * - * @private - */Dygraph.prototype.renderGraph_ = function(is_initial_draw){this.cascadeEvents_('clearChart');this.plotter_.clear();var underlayCallback=this.getFunctionOption('underlayCallback');if(underlayCallback){ // NOTE: we pass the dygraph object to this callback twice to avoid breaking -// users who expect a deprecated form of this callback. -underlayCallback.call(this,this.hidden_ctx_,this.layout_.getPlotArea(),this,this);}var e={canvas:this.hidden_,drawingContext:this.hidden_ctx_};this.cascadeEvents_('willDrawChart',e);this.plotter_.render();this.cascadeEvents_('didDrawChart',e);this.lastRow_ = -1; // because plugins/legend.js clears the legend -// TODO(danvk): is this a performance bottleneck when panning? -// The interaction canvas should already be empty in that situation. -this.canvas_.getContext('2d').clearRect(0,0,this.width_,this.height_);var drawCallback=this.getFunctionOption("drawCallback");if(drawCallback !== null){drawCallback.call(this,this,is_initial_draw);}if(is_initial_draw){this.readyFired_ = true;while(this.readyFns_.length > 0) {var fn=this.readyFns_.pop();fn(this);}}}; /** - * @private - * Determine properties of the y-axes which are independent of the data - * currently being displayed. This includes things like the number of axes and - * the style of the axes. It does not include the range of each axis and its - * tick marks. - * This fills in this.axes_. - * axes_ = [ { options } ] - * indices are into the axes_ array. - */Dygraph.prototype.computeYAxes_ = function(){var axis,index,opts,v; // this.axes_ doesn't match this.attributes_.axes_.options. It's used for -// data computation as well as options storage. -// Go through once and add all the axes. -this.axes_ = [];for(axis = 0;axis < this.attributes_.numAxes();axis++) { // Add a new axis, making a copy of its per-axis options. -opts = {g:this};utils.update(opts,this.attributes_.axisOptions(axis));this.axes_[axis] = opts;}for(axis = 0;axis < this.axes_.length;axis++) {if(axis === 0){opts = this.optionsViewForAxis_('y' + (axis?'2':''));v = opts("valueRange");if(v)this.axes_[axis].valueRange = v;}else { // To keep old behavior -var axes=this.user_attrs_.axes;if(axes && axes.y2){v = axes.y2.valueRange;if(v)this.axes_[axis].valueRange = v;}}}}; /** - * Returns the number of y-axes on the chart. - * @return {number} the number of axes. - */Dygraph.prototype.numAxes = function(){return this.attributes_.numAxes();}; /** - * @private - * Returns axis properties for the given series. - * @param {string} setName The name of the series for which to get axis - * properties, e.g. 'Y1'. - * @return {Object} The axis properties. - */Dygraph.prototype.axisPropertiesForSeries = function(series){ // TODO(danvk): handle errors. -return this.axes_[this.attributes_.axisForSeries(series)];}; /** - * @private - * Determine the value range and tick marks for each axis. - * @param {Object} extremes A mapping from seriesName -> [low, high] - * This fills in the valueRange and ticks fields in each entry of this.axes_. - */Dygraph.prototype.computeYAxisRanges_ = function(extremes){var isNullUndefinedOrNaN=function isNullUndefinedOrNaN(num){return isNaN(parseFloat(num));};var numAxes=this.attributes_.numAxes();var ypadCompat,span,series,ypad;var p_axis; // Compute extreme values, a span and tick marks for each axis. -for(var i=0;i < numAxes;i++) {var axis=this.axes_[i];var logscale=this.attributes_.getForAxis("logscale",i);var includeZero=this.attributes_.getForAxis("includeZero",i);var independentTicks=this.attributes_.getForAxis("independentTicks",i);series = this.attributes_.seriesForAxis(i); // Add some padding. This supports two Y padding operation modes: -// -// - backwards compatible (yRangePad not set): -// 10% padding for automatic Y ranges, but not for user-supplied -// ranges, and move a close-to-zero edge to zero, since drawing at the edge -// results in invisible lines. Unfortunately lines drawn at the edge of a -// user-supplied range will still be invisible. If logscale is -// set, add a variable amount of padding at the top but -// none at the bottom. -// -// - new-style (yRangePad set by the user): -// always add the specified Y padding. -// -ypadCompat = true;ypad = 0.1; // add 10% -var yRangePad=this.getNumericOption('yRangePad');if(yRangePad !== null){ypadCompat = false; // Convert pixel padding to ratio -ypad = yRangePad / this.plotter_.area.h;}if(series.length === 0){ // If no series are defined or visible then use a reasonable default -axis.extremeRange = [0,1];}else { // Calculate the extremes of extremes. -var minY=Infinity; // extremes[series[0]][0]; -var maxY=-Infinity; // extremes[series[0]][1]; -var extremeMinY,extremeMaxY;for(var j=0;j < series.length;j++) { // this skips invisible series -if(!extremes.hasOwnProperty(series[j]))continue; // Only use valid extremes to stop null data series' from corrupting the scale. -extremeMinY = extremes[series[j]][0];if(extremeMinY !== null){minY = Math.min(extremeMinY,minY);}extremeMaxY = extremes[series[j]][1];if(extremeMaxY !== null){maxY = Math.max(extremeMaxY,maxY);}} // Include zero if requested by the user. -if(includeZero && !logscale){if(minY > 0)minY = 0;if(maxY < 0)maxY = 0;} // Ensure we have a valid scale, otherwise default to [0, 1] for safety. -if(minY == Infinity)minY = 0;if(maxY == -Infinity)maxY = 1;span = maxY - minY; // special case: if we have no sense of scale, center on the sole value. -if(span === 0){if(maxY !== 0){span = Math.abs(maxY);}else { // ... and if the sole value is zero, use range 0-1. -maxY = 1;span = 1;}}var maxAxisY=maxY,minAxisY=minY;if(ypadCompat){if(logscale){maxAxisY = maxY + ypad * span;minAxisY = minY;}else {maxAxisY = maxY + ypad * span;minAxisY = minY - ypad * span; // Backwards-compatible behavior: Move the span to start or end at zero if it's -// close to zero. -if(minAxisY < 0 && minY >= 0)minAxisY = 0;if(maxAxisY > 0 && maxY <= 0)maxAxisY = 0;}}axis.extremeRange = [minAxisY,maxAxisY];}if(axis.valueRange){ // This is a user-set value range for this axis. -var y0=isNullUndefinedOrNaN(axis.valueRange[0])?axis.extremeRange[0]:axis.valueRange[0];var y1=isNullUndefinedOrNaN(axis.valueRange[1])?axis.extremeRange[1]:axis.valueRange[1];axis.computedValueRange = [y0,y1];}else {axis.computedValueRange = axis.extremeRange;}if(!ypadCompat){ // When using yRangePad, adjust the upper/lower bounds to add -// padding unless the user has zoomed/panned the Y axis range. -if(logscale){y0 = axis.computedValueRange[0];y1 = axis.computedValueRange[1];var y0pct=ypad / (2 * ypad - 1);var y1pct=(ypad - 1) / (2 * ypad - 1);axis.computedValueRange[0] = utils.logRangeFraction(y0,y1,y0pct);axis.computedValueRange[1] = utils.logRangeFraction(y0,y1,y1pct);}else {y0 = axis.computedValueRange[0];y1 = axis.computedValueRange[1];span = y1 - y0;axis.computedValueRange[0] = y0 - span * ypad;axis.computedValueRange[1] = y1 + span * ypad;}}if(independentTicks){axis.independentTicks = independentTicks;var opts=this.optionsViewForAxis_('y' + (i?'2':''));var ticker=opts('ticker');axis.ticks = ticker(axis.computedValueRange[0],axis.computedValueRange[1],this.plotter_.area.h,opts,this); // Define the first independent axis as primary axis. -if(!p_axis)p_axis = axis;}}if(p_axis === undefined){throw "Configuration Error: At least one axis has to have the \"independentTicks\" option activated.";} // Add ticks. By default, all axes inherit the tick positions of the -// primary axis. However, if an axis is specifically marked as having -// independent ticks, then that is permissible as well. -for(var i=0;i < numAxes;i++) {var axis=this.axes_[i];if(!axis.independentTicks){var opts=this.optionsViewForAxis_('y' + (i?'2':''));var ticker=opts('ticker');var p_ticks=p_axis.ticks;var p_scale=p_axis.computedValueRange[1] - p_axis.computedValueRange[0];var scale=axis.computedValueRange[1] - axis.computedValueRange[0];var tick_values=[];for(var k=0;k < p_ticks.length;k++) {var y_frac=(p_ticks[k].v - p_axis.computedValueRange[0]) / p_scale;var y_val=axis.computedValueRange[0] + y_frac * scale;tick_values.push(y_val);}axis.ticks = ticker(axis.computedValueRange[0],axis.computedValueRange[1],this.plotter_.area.h,opts,this,tick_values);}}}; /** - * Detects the type of the str (date or numeric) and sets the various - * formatting attributes in this.attrs_ based on this type. - * @param {string} str An x value. - * @private - */Dygraph.prototype.detectTypeFromString_ = function(str){var isDate=false;var dashPos=str.indexOf('-'); // could be 2006-01-01 _or_ 1.0e-2 -if(dashPos > 0 && str[dashPos - 1] != 'e' && str[dashPos - 1] != 'E' || str.indexOf('/') >= 0 || isNaN(parseFloat(str))){isDate = true;}else if(str.length == 8 && str > '19700101' && str < '20371231'){ // TODO(danvk): remove support for this format. -isDate = true;}this.setXAxisOptions_(isDate);};Dygraph.prototype.setXAxisOptions_ = function(isDate){if(isDate){this.attrs_.xValueParser = utils.dateParser;this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter;this.attrs_.axes.x.ticker = DygraphTickers.dateTicker;this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter;}else { /** @private (shut up, jsdoc!) */this.attrs_.xValueParser = function(x){return parseFloat(x);}; // TODO(danvk): use Dygraph.numberValueFormatter here? -/** @private (shut up, jsdoc!) */this.attrs_.axes.x.valueFormatter = function(x){return x;};this.attrs_.axes.x.ticker = DygraphTickers.numericTicks;this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter;}}; /** - * @private - * Parses a string in a special csv format. We expect a csv file where each - * line is a date point, and the first field in each line is the date string. - * We also expect that all remaining fields represent series. - * if the errorBars attribute is set, then interpret the fields as: - * date, series1, stddev1, series2, stddev2, ... - * @param {[Object]} data See above. - * - * @return [Object] An array with one entry for each row. These entries - * are an array of cells in that row. The first entry is the parsed x-value for - * the row. The second, third, etc. are the y-values. These can take on one of - * three forms, depending on the CSV and constructor parameters: - * 1. numeric value - * 2. [ value, stddev ] - * 3. [ low value, center value, high value ] - */Dygraph.prototype.parseCSV_ = function(data){var ret=[];var line_delimiter=utils.detectLineDelimiter(data);var lines=data.split(line_delimiter || "\n");var vals,j; // Use the default delimiter or fall back to a tab if that makes sense. -var delim=this.getStringOption('delimiter');if(lines[0].indexOf(delim) == -1 && lines[0].indexOf('\t') >= 0){delim = '\t';}var start=0;if(!('labels' in this.user_attrs_)){ // User hasn't explicitly set labels, so they're (presumably) in the CSV. -start = 1;this.attrs_.labels = lines[0].split(delim); // NOTE: _not_ user_attrs_. -this.attributes_.reparseSeries();}var line_no=0;var xParser;var defaultParserSet=false; // attempt to auto-detect x value type -var expectedCols=this.attr_("labels").length;var outOfOrder=false;for(var i=start;i < lines.length;i++) {var line=lines[i];line_no = i;if(line.length === 0)continue; // skip blank lines -if(line[0] == '#')continue; // skip comment lines -var inFields=line.split(delim);if(inFields.length < 2)continue;var fields=[];if(!defaultParserSet){this.detectTypeFromString_(inFields[0]);xParser = this.getFunctionOption("xValueParser");defaultParserSet = true;}fields[0] = xParser(inFields[0],this); // If fractions are expected, parse the numbers as "A/B" -if(this.fractions_){for(j = 1;j < inFields.length;j++) { // TODO(danvk): figure out an appropriate way to flag parse errors. -vals = inFields[j].split("/");if(vals.length != 2){console.error('Expected fractional "num/den" values in CSV data ' + "but found a value '" + inFields[j] + "' on line " + (1 + i) + " ('" + line + "') which is not of this form.");fields[j] = [0,0];}else {fields[j] = [utils.parseFloat_(vals[0],i,line),utils.parseFloat_(vals[1],i,line)];}}}else if(this.getBooleanOption("errorBars")){ // If there are error bars, values are (value, stddev) pairs -if(inFields.length % 2 != 1){console.error('Expected alternating (value, stdev.) pairs in CSV data ' + 'but line ' + (1 + i) + ' has an odd number of values (' + (inFields.length - 1) + "): '" + line + "'");}for(j = 1;j < inFields.length;j += 2) {fields[(j + 1) / 2] = [utils.parseFloat_(inFields[j],i,line),utils.parseFloat_(inFields[j + 1],i,line)];}}else if(this.getBooleanOption("customBars")){ // Bars are a low;center;high tuple -for(j = 1;j < inFields.length;j++) {var val=inFields[j];if(/^ *$/.test(val)){fields[j] = [null,null,null];}else {vals = val.split(";");if(vals.length == 3){fields[j] = [utils.parseFloat_(vals[0],i,line),utils.parseFloat_(vals[1],i,line),utils.parseFloat_(vals[2],i,line)];}else {console.warn('When using customBars, values must be either blank ' + 'or "low;center;high" tuples (got "' + val + '" on line ' + (1 + i));}}}}else { // Values are just numbers -for(j = 1;j < inFields.length;j++) {fields[j] = utils.parseFloat_(inFields[j],i,line);}}if(ret.length > 0 && fields[0] < ret[ret.length - 1][0]){outOfOrder = true;}if(fields.length != expectedCols){console.error("Number of columns in line " + i + " (" + fields.length + ") does not agree with number of labels (" + expectedCols + ") " + line);} // If the user specified the 'labels' option and none of the cells of the -// first row parsed correctly, then they probably double-specified the -// labels. We go with the values set in the option, discard this row and -// log a warning to the JS console. -if(i === 0 && this.attr_('labels')){var all_null=true;for(j = 0;all_null && j < fields.length;j++) {if(fields[j])all_null = false;}if(all_null){console.warn("The dygraphs 'labels' option is set, but the first row " + "of CSV data ('" + line + "') appears to also contain " + "labels. Will drop the CSV labels and use the option " + "labels.");continue;}}ret.push(fields);}if(outOfOrder){console.warn("CSV is out of order; order it correctly to speed loading.");ret.sort(function(a,b){return a[0] - b[0];});}return ret;}; // In native format, all values must be dates or numbers. -// This check isn't perfect but will catch most mistaken uses of strings. -function validateNativeFormat(data){var firstRow=data[0];var firstX=firstRow[0];if(typeof firstX !== 'number' && !utils.isDateLike(firstX)){throw new Error('Expected number or date but got ' + typeof firstX + ': ' + firstX + '.');}for(var i=1;i < firstRow.length;i++) {var val=firstRow[i];if(val === null || val === undefined)continue;if(typeof val === 'number')continue;if(utils.isArrayLike(val))continue; // e.g. error bars or custom bars. -throw new Error('Expected number or array but got ' + typeof val + ': ' + val + '.');}} /** - * The user has provided their data as a pre-packaged JS array. If the x values - * are numeric, this is the same as dygraphs' internal format. If the x values - * are dates, we need to convert them from Date objects to ms since epoch. - * @param {!Array} data - * @return {Object} data with numeric x values. - * @private - */Dygraph.prototype.parseArray_ = function(data){ // Peek at the first x value to see if it's numeric. -if(data.length === 0){console.error("Can't plot empty data set");return null;}if(data[0].length === 0){console.error("Data set cannot contain an empty row");return null;}validateNativeFormat(data);var i;if(this.attr_("labels") === null){console.warn("Using default labels. Set labels explicitly via 'labels' " + "in the options parameter");this.attrs_.labels = ["X"];for(i = 1;i < data[0].length;i++) {this.attrs_.labels.push("Y" + i); // Not user_attrs_. -}this.attributes_.reparseSeries();}else {var num_labels=this.attr_("labels");if(num_labels.length != data[0].length){console.error("Mismatch between number of labels (" + num_labels + ")" + " and number of columns in array (" + data[0].length + ")");return null;}}if(utils.isDateLike(data[0][0])){ // Some intelligent defaults for a date x-axis. -this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter;this.attrs_.axes.x.ticker = DygraphTickers.dateTicker;this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter; // Assume they're all dates. -var parsedData=utils.clone(data);for(i = 0;i < data.length;i++) {if(parsedData[i].length === 0){console.error("Row " + (1 + i) + " of data is empty");return null;}if(parsedData[i][0] === null || typeof parsedData[i][0].getTime != 'function' || isNaN(parsedData[i][0].getTime())){console.error("x value in row " + (1 + i) + " is not a Date");return null;}parsedData[i][0] = parsedData[i][0].getTime();}return parsedData;}else { // Some intelligent defaults for a numeric x-axis. -/** @private (shut up, jsdoc!) */this.attrs_.axes.x.valueFormatter = function(x){return x;};this.attrs_.axes.x.ticker = DygraphTickers.numericTicks;this.attrs_.axes.x.axisLabelFormatter = utils.numberAxisLabelFormatter;return data;}}; /** - * Parses a DataTable object from gviz. - * The data is expected to have a first column that is either a date or a - * number. All subsequent columns must be numbers. If there is a clear mismatch - * between this.xValueParser_ and the type of the first column, it will be - * fixed. Fills out rawData_. - * @param {!google.visualization.DataTable} data See above. - * @private - */Dygraph.prototype.parseDataTable_ = function(data){var shortTextForAnnotationNum=function shortTextForAnnotationNum(num){ // converts [0-9]+ [A-Z][a-z]* -// example: 0=A, 1=B, 25=Z, 26=Aa, 27=Ab -// and continues like.. Ba Bb .. Za .. Zz..Aaa...Zzz Aaaa Zzzz -var shortText=String.fromCharCode(65 /* A */ + num % 26);num = Math.floor(num / 26);while(num > 0) {shortText = String.fromCharCode(65 /* A */ + (num - 1) % 26) + shortText.toLowerCase();num = Math.floor((num - 1) / 26);}return shortText;};var cols=data.getNumberOfColumns();var rows=data.getNumberOfRows();var indepType=data.getColumnType(0);if(indepType == 'date' || indepType == 'datetime'){this.attrs_.xValueParser = utils.dateParser;this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter;this.attrs_.axes.x.ticker = DygraphTickers.dateTicker;this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter;}else if(indepType == 'number'){this.attrs_.xValueParser = function(x){return parseFloat(x);};this.attrs_.axes.x.valueFormatter = function(x){return x;};this.attrs_.axes.x.ticker = DygraphTickers.numericTicks;this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter;}else {throw new Error("only 'date', 'datetime' and 'number' types are supported " + "for column 1 of DataTable input (Got '" + indepType + "')");} // Array of the column indices which contain data (and not annotations). -var colIdx=[];var annotationCols={}; // data index -> [annotation cols] -var hasAnnotations=false;var i,j;for(i = 1;i < cols;i++) {var type=data.getColumnType(i);if(type == 'number'){colIdx.push(i);}else if(type == 'string' && this.getBooleanOption('displayAnnotations')){ // This is OK -- it's an annotation column. -var dataIdx=colIdx[colIdx.length - 1];if(!annotationCols.hasOwnProperty(dataIdx)){annotationCols[dataIdx] = [i];}else {annotationCols[dataIdx].push(i);}hasAnnotations = true;}else {throw new Error("Only 'number' is supported as a dependent type with Gviz." + " 'string' is only supported if displayAnnotations is true");}} // Read column labels -// TODO(danvk): add support back for errorBars -var labels=[data.getColumnLabel(0)];for(i = 0;i < colIdx.length;i++) {labels.push(data.getColumnLabel(colIdx[i]));if(this.getBooleanOption("errorBars"))i += 1;}this.attrs_.labels = labels;cols = labels.length;var ret=[];var outOfOrder=false;var annotations=[];for(i = 0;i < rows;i++) {var row=[];if(typeof data.getValue(i,0) === 'undefined' || data.getValue(i,0) === null){console.warn("Ignoring row " + i + " of DataTable because of undefined or null first column.");continue;}if(indepType == 'date' || indepType == 'datetime'){row.push(data.getValue(i,0).getTime());}else {row.push(data.getValue(i,0));}if(!this.getBooleanOption("errorBars")){for(j = 0;j < colIdx.length;j++) {var col=colIdx[j];row.push(data.getValue(i,col));if(hasAnnotations && annotationCols.hasOwnProperty(col) && data.getValue(i,annotationCols[col][0]) !== null){var ann={};ann.series = data.getColumnLabel(col);ann.xval = row[0];ann.shortText = shortTextForAnnotationNum(annotations.length);ann.text = '';for(var k=0;k < annotationCols[col].length;k++) {if(k)ann.text += "\n";ann.text += data.getValue(i,annotationCols[col][k]);}annotations.push(ann);}} // Strip out infinities, which give dygraphs problems later on. -for(j = 0;j < row.length;j++) {if(!isFinite(row[j]))row[j] = null;}}else {for(j = 0;j < cols - 1;j++) {row.push([data.getValue(i,1 + 2 * j),data.getValue(i,2 + 2 * j)]);}}if(ret.length > 0 && row[0] < ret[ret.length - 1][0]){outOfOrder = true;}ret.push(row);}if(outOfOrder){console.warn("DataTable is out of order; order it correctly to speed loading.");ret.sort(function(a,b){return a[0] - b[0];});}this.rawData_ = ret;if(annotations.length > 0){this.setAnnotations(annotations,true);}this.attributes_.reparseSeries();}; /** - * Signals to plugins that the chart data has updated. - * This happens after the data has updated but before the chart has redrawn. - * @private - */Dygraph.prototype.cascadeDataDidUpdateEvent_ = function(){ // TODO(danvk): there are some issues checking xAxisRange() and using -// toDomCoords from handlers of this event. The visible range should be set -// when the chart is drawn, not derived from the data. -this.cascadeEvents_('dataDidUpdate',{});}; /** - * Get the CSV data. If it's in a function, call that function. If it's in a - * file, do an XMLHttpRequest to get it. - * @private - */Dygraph.prototype.start_ = function(){var data=this.file_; // Functions can return references of all other types. -if(typeof data == 'function'){data = data();}if(utils.isArrayLike(data)){this.rawData_ = this.parseArray_(data);this.cascadeDataDidUpdateEvent_();this.predraw_();}else if(typeof data == 'object' && typeof data.getColumnRange == 'function'){ // must be a DataTable from gviz. -this.parseDataTable_(data);this.cascadeDataDidUpdateEvent_();this.predraw_();}else if(typeof data == 'string'){ // Heuristic: a newline means it's CSV data. Otherwise it's an URL. -var line_delimiter=utils.detectLineDelimiter(data);if(line_delimiter){this.loadedEvent_(data);}else { // REMOVE_FOR_IE -var req;if(window.XMLHttpRequest){ // Firefox, Opera, IE7, and other browsers will use the native object -req = new XMLHttpRequest();}else { // IE 5 and 6 will use the ActiveX control -req = new ActiveXObject("Microsoft.XMLHTTP");}var caller=this;req.onreadystatechange = function(){if(req.readyState == 4){if(req.status === 200 || // Normal http -req.status === 0){ // Chrome w/ --allow-file-access-from-files -caller.loadedEvent_(req.responseText);}}};req.open("GET",data,true);req.send(null);}}else {console.error("Unknown data format: " + typeof data);}}; /** - * Changes various properties of the graph. These can include: - *
    - *
  • file: changes the source data for the graph
  • - *
  • errorBars: changes whether the data contains stddev
  • - *
- * - * There's a huge variety of options that can be passed to this method. For a - * full list, see http://dygraphs.com/options.html. - * - * @param {Object} input_attrs The new properties and values - * @param {boolean} block_redraw Usually the chart is redrawn after every - * call to updateOptions(). If you know better, you can pass true to - * explicitly block the redraw. This can be useful for chaining - * updateOptions() calls, avoiding the occasional infinite loop and - * preventing redraws when it's not necessary (e.g. when updating a - * callback). - */Dygraph.prototype.updateOptions = function(input_attrs,block_redraw){if(typeof block_redraw == 'undefined')block_redraw = false; // copyUserAttrs_ drops the "file" parameter as a convenience to us. -var file=input_attrs.file;var attrs=Dygraph.copyUserAttrs_(input_attrs); // TODO(danvk): this is a mess. Move these options into attr_. -if('rollPeriod' in attrs){this.rollPeriod_ = attrs.rollPeriod;}if('dateWindow' in attrs){this.dateWindow_ = attrs.dateWindow;} // TODO(danvk): validate per-series options. -// Supported: -// strokeWidth -// pointSize -// drawPoints -// highlightCircleSize -// Check if this set options will require new points. -var requiresNewPoints=utils.isPixelChangingOptionList(this.attr_("labels"),attrs);utils.updateDeep(this.user_attrs_,attrs);this.attributes_.reparseSeries();if(file){ // This event indicates that the data is about to change, but hasn't yet. -// TODO(danvk): support cancellation of the update via this event. -this.cascadeEvents_('dataWillUpdate',{});this.file_ = file;if(!block_redraw)this.start_();}else {if(!block_redraw){if(requiresNewPoints){this.predraw_();}else {this.renderGraph_(false);}}}}; /** - * Make a copy of input attributes, removing file as a convenience. - * @private - */Dygraph.copyUserAttrs_ = function(attrs){var my_attrs={};for(var k in attrs) {if(!attrs.hasOwnProperty(k))continue;if(k == 'file')continue;if(attrs.hasOwnProperty(k))my_attrs[k] = attrs[k];}return my_attrs;}; /** - * Resizes the dygraph. If no parameters are specified, resizes to fill the - * containing div (which has presumably changed size since the dygraph was - * instantiated. If the width/height are specified, the div will be resized. - * - * This is far more efficient than destroying and re-instantiating a - * Dygraph, since it doesn't have to reparse the underlying data. - * - * @param {number} width Width (in pixels) - * @param {number} height Height (in pixels) - */Dygraph.prototype.resize = function(width,height){if(this.resize_lock){return;}this.resize_lock = true;if(width === null != (height === null)){console.warn("Dygraph.resize() should be called with zero parameters or " + "two non-NULL parameters. Pretending it was zero.");width = height = null;}var old_width=this.width_;var old_height=this.height_;if(width){this.maindiv_.style.width = width + "px";this.maindiv_.style.height = height + "px";this.width_ = width;this.height_ = height;}else {this.width_ = this.maindiv_.clientWidth;this.height_ = this.maindiv_.clientHeight;}if(old_width != this.width_ || old_height != this.height_){ // Resizing a canvas erases it, even when the size doesn't change, so -// any resize needs to be followed by a redraw. -this.resizeElements_();this.predraw_();}this.resize_lock = false;}; /** - * Adjusts the number of points in the rolling average. Updates the graph to - * reflect the new averaging period. - * @param {number} length Number of points over which to average the data. - */Dygraph.prototype.adjustRoll = function(length){this.rollPeriod_ = length;this.predraw_();}; /** - * Returns a boolean array of visibility statuses. - */Dygraph.prototype.visibility = function(){ // Do lazy-initialization, so that this happens after we know the number of -// data series. -if(!this.getOption("visibility")){this.attrs_.visibility = [];} // TODO(danvk): it looks like this could go into an infinite loop w/ user_attrs. -while(this.getOption("visibility").length < this.numColumns() - 1) {this.attrs_.visibility.push(true);}return this.getOption("visibility");}; /** - * Changes the visibility of one or more series. - * - * @param {number|number[]|object} num the series index or an array of series indices - * or a boolean array of visibility states by index - * or an object mapping series numbers, as keys, to - * visibility state (boolean values) - * @param {boolean} value the visibility state expressed as a boolean - */Dygraph.prototype.setVisibility = function(num,value){var x=this.visibility();var numIsObject=false;if(!Array.isArray(num)){if(num !== null && typeof num === 'object'){numIsObject = true;}else {num = [num];}}if(numIsObject){for(var i in num) {if(num.hasOwnProperty(i)){if(i < 0 || i >= x.length){console.warn("Invalid series number in setVisibility: " + i);}else {x[i] = num[i];}}}}else {for(var i=0;i < num.length;i++) {if(typeof num[i] === 'boolean'){if(i >= x.length){console.warn("Invalid series number in setVisibility: " + i);}else {x[i] = num[i];}}else {if(num[i] < 0 || num[i] >= x.length){console.warn("Invalid series number in setVisibility: " + num[i]);}else {x[num[i]] = value;}}}}this.predraw_();}; /** - * How large of an area will the dygraph render itself in? - * This is used for testing. - * @return A {width: w, height: h} object. - * @private - */Dygraph.prototype.size = function(){return {width:this.width_,height:this.height_};}; /** - * Update the list of annotations and redraw the chart. - * See dygraphs.com/annotations.html for more info on how to use annotations. - * @param ann {Array} An array of annotation objects. - * @param suppressDraw {Boolean} Set to "true" to block chart redraw (optional). - */Dygraph.prototype.setAnnotations = function(ann,suppressDraw){ // Only add the annotation CSS rule once we know it will be used. -this.annotations_ = ann;if(!this.layout_){console.warn("Tried to setAnnotations before dygraph was ready. " + "Try setting them in a ready() block. See " + "dygraphs.com/tests/annotation.html");return;}this.layout_.setAnnotations(this.annotations_);if(!suppressDraw){this.predraw_();}}; /** - * Return the list of annotations. - */Dygraph.prototype.annotations = function(){return this.annotations_;}; /** - * Get the list of label names for this graph. The first column is the - * x-axis, so the data series names start at index 1. - * - * Returns null when labels have not yet been defined. - */Dygraph.prototype.getLabels = function(){var labels=this.attr_("labels");return labels?labels.slice():null;}; /** - * Get the index of a series (column) given its name. The first column is the - * x-axis, so the data series start with index 1. - */Dygraph.prototype.indexFromSetName = function(name){return this.setIndexByName_[name];}; /** - * Find the row number corresponding to the given x-value. - * Returns null if there is no such x-value in the data. - * If there are multiple rows with the same x-value, this will return the - * first one. - * @param {number} xVal The x-value to look for (e.g. millis since epoch). - * @return {?number} The row number, which you can pass to getValue(), or null. - */Dygraph.prototype.getRowForX = function(xVal){var low=0,high=this.numRows() - 1;while(low <= high) {var idx=high + low >> 1;var x=this.getValue(idx,0);if(x < xVal){low = idx + 1;}else if(x > xVal){high = idx - 1;}else if(low != idx){ // equal, but there may be an earlier match. -high = idx;}else {return idx;}}return null;}; /** - * Trigger a callback when the dygraph has drawn itself and is ready to be - * manipulated. This is primarily useful when dygraphs has to do an XHR for the - * data (i.e. a URL is passed as the data source) and the chart is drawn - * asynchronously. If the chart has already drawn, the callback will fire - * immediately. - * - * This is a good place to call setAnnotation(). - * - * @param {function(!Dygraph)} callback The callback to trigger when the chart - * is ready. - */Dygraph.prototype.ready = function(callback){if(this.is_initial_draw_){this.readyFns_.push(callback);}else {callback.call(this,this);}}; /** - * Add an event handler. This event handler is kept until the graph is - * destroyed with a call to graph.destroy(). - * - * @param {!Node} elem The element to add the event to. - * @param {string} type The type of the event, e.g. 'click' or 'mousemove'. - * @param {function(Event):(boolean|undefined)} fn The function to call - * on the event. The function takes one parameter: the event object. - * @private - */Dygraph.prototype.addAndTrackEvent = function(elem,type,fn){utils.addEvent(elem,type,fn);this.registeredEvents_.push({elem:elem,type:type,fn:fn});};Dygraph.prototype.removeTrackedEvents_ = function(){if(this.registeredEvents_){for(var idx=0;idx < this.registeredEvents_.length;idx++) {var reg=this.registeredEvents_[idx];utils.removeEvent(reg.elem,reg.type,reg.fn);}}this.registeredEvents_ = [];}; // Installed plugins, in order of precedence (most-general to most-specific). -Dygraph.PLUGINS = [_pluginsLegend2['default'],_pluginsAxes2['default'],_pluginsRangeSelector2['default'], // Has to be before ChartLabels so that its callbacks are called after ChartLabels' callbacks. -_pluginsChartLabels2['default'],_pluginsAnnotations2['default'],_pluginsGrid2['default']]; // There are many symbols which have historically been available through the -// Dygraph class. These are exported here for backwards compatibility. -Dygraph.GVizChart = _dygraphGviz2['default'];Dygraph.DASHED_LINE = utils.DASHED_LINE;Dygraph.DOT_DASH_LINE = utils.DOT_DASH_LINE;Dygraph.dateAxisLabelFormatter = utils.dateAxisLabelFormatter;Dygraph.toRGB_ = utils.toRGB_;Dygraph.findPos = utils.findPos;Dygraph.pageX = utils.pageX;Dygraph.pageY = utils.pageY;Dygraph.dateString_ = utils.dateString_;Dygraph.defaultInteractionModel = _dygraphInteractionModel2['default'].defaultModel;Dygraph.nonInteractiveModel = Dygraph.nonInteractiveModel_ = _dygraphInteractionModel2['default'].nonInteractiveModel_;Dygraph.Circles = utils.Circles;Dygraph.Plugins = {Legend:_pluginsLegend2['default'],Axes:_pluginsAxes2['default'],Annotations:_pluginsAnnotations2['default'],ChartLabels:_pluginsChartLabels2['default'],Grid:_pluginsGrid2['default'],RangeSelector:_pluginsRangeSelector2['default']};Dygraph.DataHandlers = {DefaultHandler:_datahandlerDefault2['default'],BarsHandler:_datahandlerBars2['default'],CustomBarsHandler:_datahandlerBarsCustom2['default'],DefaultFractionHandler:_datahandlerDefaultFractions2['default'],ErrorBarsHandler:_datahandlerBarsError2['default'],FractionsBarsHandler:_datahandlerBarsFractions2['default']};Dygraph.startPan = _dygraphInteractionModel2['default'].startPan;Dygraph.startZoom = _dygraphInteractionModel2['default'].startZoom;Dygraph.movePan = _dygraphInteractionModel2['default'].movePan;Dygraph.moveZoom = _dygraphInteractionModel2['default'].moveZoom;Dygraph.endPan = _dygraphInteractionModel2['default'].endPan;Dygraph.endZoom = _dygraphInteractionModel2['default'].endZoom;Dygraph.numericLinearTicks = DygraphTickers.numericLinearTicks;Dygraph.numericTicks = DygraphTickers.numericTicks;Dygraph.dateTicker = DygraphTickers.dateTicker;Dygraph.Granularity = DygraphTickers.Granularity;Dygraph.getDateAxis = DygraphTickers.getDateAxis;Dygraph.floatFormat = utils.floatFormat;exports['default'] = Dygraph;module.exports = exports['default']; - -}).call(this,require('_process')) - -},{"./datahandler/bars":5,"./datahandler/bars-custom":2,"./datahandler/bars-error":3,"./datahandler/bars-fractions":4,"./datahandler/default":8,"./datahandler/default-fractions":7,"./dygraph-canvas":9,"./dygraph-default-attrs":10,"./dygraph-gviz":11,"./dygraph-interaction-model":12,"./dygraph-layout":13,"./dygraph-options":15,"./dygraph-options-reference":14,"./dygraph-tickers":16,"./dygraph-utils":17,"./iframe-tarp":19,"./plugins/annotations":20,"./plugins/axes":21,"./plugins/chart-labels":22,"./plugins/grid":23,"./plugins/legend":24,"./plugins/range-selector":25,"_process":1}],19:[function(require,module,exports){ -/** - * To create a "drag" interaction, you typically register a mousedown event - * handler on the element where the drag begins. In that handler, you register a - * mouseup handler on the window to determine when the mouse is released, - * wherever that release happens. This works well, except when the user releases - * the mouse over an off-domain iframe. In that case, the mouseup event is - * handled by the iframe and never bubbles up to the window handler. - * - * To deal with this issue, we cover iframes with high z-index divs to make sure - * they don't capture mouseup. - * - * Usage: - * element.addEventListener('mousedown', function() { - * var tarper = new IFrameTarp(); - * tarper.cover(); - * var mouseUpHandler = function() { - * ... - * window.removeEventListener(mouseUpHandler); - * tarper.uncover(); - * }; - * window.addEventListener('mouseup', mouseUpHandler); - * }; - * - * @constructor - */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } } - -var _dygraphUtils = require('./dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -function IFrameTarp() { - /** @type {Array.} */ - this.tarps = []; -}; - -/** - * Find all the iframes in the document and cover them with high z-index - * transparent divs. - */ -IFrameTarp.prototype.cover = function () { - var iframes = document.getElementsByTagName("iframe"); - for (var i = 0; i < iframes.length; i++) { - var iframe = iframes[i]; - var pos = utils.findPos(iframe), - x = pos.x, - y = pos.y, - width = iframe.offsetWidth, - height = iframe.offsetHeight; - - var div = document.createElement("div"); - div.style.position = "absolute"; - div.style.left = x + 'px'; - div.style.top = y + 'px'; - div.style.width = width + 'px'; - div.style.height = height + 'px'; - div.style.zIndex = 999; - document.body.appendChild(div); - this.tarps.push(div); - } -}; - -/** - * Remove all the iframe covers. You should call this in a mouseup handler. - */ -IFrameTarp.prototype.uncover = function () { - for (var i = 0; i < this.tarps.length; i++) { - this.tarps[i].parentNode.removeChild(this.tarps[i]); - } - this.tarps = []; -}; - -exports["default"] = IFrameTarp; -module.exports = exports["default"]; - -},{"./dygraph-utils":17}],20:[function(require,module,exports){ -/** - * @license - * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/*global Dygraph:false */ - -"use strict"; - -/** -Current bits of jankiness: -- Uses dygraph.layout_ to get the parsed annotations. -- Uses dygraph.plotter_.area - -It would be nice if the plugin didn't require so much special support inside -the core dygraphs classes, but annotations involve quite a bit of parsing and -layout. - -TODO(danvk): cache DOM elements. -*/ - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var annotations = function annotations() { - this.annotations_ = []; -}; - -annotations.prototype.toString = function () { - return "Annotations Plugin"; -}; - -annotations.prototype.activate = function (g) { - return { - clearChart: this.clearChart, - didDrawChart: this.didDrawChart - }; -}; - -annotations.prototype.detachLabels = function () { - for (var i = 0; i < this.annotations_.length; i++) { - var a = this.annotations_[i]; - if (a.parentNode) a.parentNode.removeChild(a); - this.annotations_[i] = null; - } - this.annotations_ = []; -}; - -annotations.prototype.clearChart = function (e) { - this.detachLabels(); -}; - -annotations.prototype.didDrawChart = function (e) { - var g = e.dygraph; - - // Early out in the (common) case of zero annotations. - var points = g.layout_.annotated_points; - if (!points || points.length === 0) return; - - var containerDiv = e.canvas.parentNode; - - var bindEvt = function bindEvt(eventName, classEventName, pt) { - return function (annotation_event) { - var a = pt.annotation; - if (a.hasOwnProperty(eventName)) { - a[eventName](a, pt, g, annotation_event); - } else if (g.getOption(classEventName)) { - g.getOption(classEventName)(a, pt, g, annotation_event); - } - }; - }; - - // Add the annotations one-by-one. - var area = e.dygraph.getArea(); - - // x-coord to sum of previous annotation's heights (used for stacking). - var xToUsedHeight = {}; - - for (var i = 0; i < points.length; i++) { - var p = points[i]; - if (p.canvasx < area.x || p.canvasx > area.x + area.w || p.canvasy < area.y || p.canvasy > area.y + area.h) { - continue; - } - - var a = p.annotation; - var tick_height = 6; - if (a.hasOwnProperty("tickHeight")) { - tick_height = a.tickHeight; - } - - // TODO: deprecate axisLabelFontSize in favor of CSS - var div = document.createElement("div"); - div.style['fontSize'] = g.getOption('axisLabelFontSize') + "px"; - var className = 'dygraph-annotation'; - if (!a.hasOwnProperty('icon')) { - // camelCase class names are deprecated. - className += ' dygraphDefaultAnnotation dygraph-default-annotation'; - } - if (a.hasOwnProperty('cssClass')) { - className += " " + a.cssClass; - } - div.className = className; - - var width = a.hasOwnProperty('width') ? a.width : 16; - var height = a.hasOwnProperty('height') ? a.height : 16; - if (a.hasOwnProperty('icon')) { - var img = document.createElement("img"); - img.src = a.icon; - img.width = width; - img.height = height; - div.appendChild(img); - } else if (p.annotation.hasOwnProperty('shortText')) { - div.appendChild(document.createTextNode(p.annotation.shortText)); - } - var left = p.canvasx - width / 2; - div.style.left = left + "px"; - var divTop = 0; - if (a.attachAtBottom) { - var y = area.y + area.h - height - tick_height; - if (xToUsedHeight[left]) { - y -= xToUsedHeight[left]; - } else { - xToUsedHeight[left] = 0; - } - xToUsedHeight[left] += tick_height + height; - divTop = y; - } else { - divTop = p.canvasy - height - tick_height; - } - div.style.top = divTop + "px"; - div.style.width = width + "px"; - div.style.height = height + "px"; - div.title = p.annotation.text; - div.style.color = g.colorsMap_[p.name]; - div.style.borderColor = g.colorsMap_[p.name]; - a.div = div; - - g.addAndTrackEvent(div, 'click', bindEvt('clickHandler', 'annotationClickHandler', p, this)); - g.addAndTrackEvent(div, 'mouseover', bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p, this)); - g.addAndTrackEvent(div, 'mouseout', bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p, this)); - g.addAndTrackEvent(div, 'dblclick', bindEvt('dblClickHandler', 'annotationDblClickHandler', p, this)); - - containerDiv.appendChild(div); - this.annotations_.push(div); - - var ctx = e.drawingContext; - ctx.save(); - ctx.strokeStyle = a.hasOwnProperty('tickColor') ? a.tickColor : g.colorsMap_[p.name]; - ctx.lineWidth = a.hasOwnProperty('tickWidth') ? a.tickWidth : g.getOption('strokeWidth'); - ctx.beginPath(); - if (!a.attachAtBottom) { - ctx.moveTo(p.canvasx, p.canvasy); - ctx.lineTo(p.canvasx, p.canvasy - 2 - tick_height); - } else { - var y = divTop + height; - ctx.moveTo(p.canvasx, y); - ctx.lineTo(p.canvasx, y + tick_height); - } - ctx.closePath(); - ctx.stroke(); - ctx.restore(); - } -}; - -annotations.prototype.destroy = function () { - this.detachLabels(); -}; - -exports["default"] = annotations; -module.exports = exports["default"]; - -},{}],21:[function(require,module,exports){ -/** - * @license - * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ - -/*global Dygraph:false */ - -'use strict'; - -/* -Bits of jankiness: -- Direct layout access -- Direct area access -- Should include calculation of ticks, not just the drawing. - -Options left to make axis-friendly. - ('drawAxesAtZero') - ('xAxisHeight') -*/ - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -var _dygraphUtils = require('../dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -/** - * Draws the axes. This includes the labels on the x- and y-axes, as well - * as the tick marks on the axes. - * It does _not_ draw the grid lines which span the entire chart. - */ -var axes = function axes() { - this.xlabels_ = []; - this.ylabels_ = []; -}; - -axes.prototype.toString = function () { - return 'Axes Plugin'; -}; - -axes.prototype.activate = function (g) { - return { - layout: this.layout, - clearChart: this.clearChart, - willDrawChart: this.willDrawChart - }; -}; - -axes.prototype.layout = function (e) { - var g = e.dygraph; - - if (g.getOptionForAxis('drawAxis', 'y')) { - var w = g.getOptionForAxis('axisLabelWidth', 'y') + 2 * g.getOptionForAxis('axisTickSize', 'y'); - e.reserveSpaceLeft(w); - } - - if (g.getOptionForAxis('drawAxis', 'x')) { - var h; - // NOTE: I think this is probably broken now, since g.getOption() now - // hits the dictionary. (That is, g.getOption('xAxisHeight') now always - // has a value.) - if (g.getOption('xAxisHeight')) { - h = g.getOption('xAxisHeight'); - } else { - h = g.getOptionForAxis('axisLabelFontSize', 'x') + 2 * g.getOptionForAxis('axisTickSize', 'x'); - } - e.reserveSpaceBottom(h); - } - - if (g.numAxes() == 2) { - if (g.getOptionForAxis('drawAxis', 'y2')) { - var w = g.getOptionForAxis('axisLabelWidth', 'y2') + 2 * g.getOptionForAxis('axisTickSize', 'y2'); - e.reserveSpaceRight(w); - } - } else if (g.numAxes() > 2) { - g.error('Only two y-axes are supported at this time. (Trying ' + 'to use ' + g.numAxes() + ')'); - } -}; - -axes.prototype.detachLabels = function () { - function removeArray(ary) { - for (var i = 0; i < ary.length; i++) { - var el = ary[i]; - if (el.parentNode) el.parentNode.removeChild(el); - } - } - - removeArray(this.xlabels_); - removeArray(this.ylabels_); - this.xlabels_ = []; - this.ylabels_ = []; -}; - -axes.prototype.clearChart = function (e) { - this.detachLabels(); -}; - -axes.prototype.willDrawChart = function (e) { - var _this = this; - - var g = e.dygraph; - - if (!g.getOptionForAxis('drawAxis', 'x') && !g.getOptionForAxis('drawAxis', 'y') && !g.getOptionForAxis('drawAxis', 'y2')) { - return; - } - - // Round pixels to half-integer boundaries for crisper drawing. - function halfUp(x) { - return Math.round(x) + 0.5; - } - function halfDown(y) { - return Math.round(y) - 0.5; - } - - var context = e.drawingContext; - var containerDiv = e.canvas.parentNode; - var canvasWidth = g.width_; // e.canvas.width is affected by pixel ratio. - var canvasHeight = g.height_; - - var label, x, y, tick, i; - - var makeLabelStyle = function makeLabelStyle(axis) { - return { - position: 'absolute', - fontSize: g.getOptionForAxis('axisLabelFontSize', axis) + 'px', - width: g.getOptionForAxis('axisLabelWidth', axis) + 'px' - }; - }; - - var labelStyles = { - x: makeLabelStyle('x'), - y: makeLabelStyle('y'), - y2: makeLabelStyle('y2') - }; - - var makeDiv = function makeDiv(txt, axis, prec_axis) { - /* - * This seems to be called with the following three sets of axis/prec_axis: - * x: undefined - * y: y1 - * y: y2 - */ - var div = document.createElement('div'); - var labelStyle = labelStyles[prec_axis == 'y2' ? 'y2' : axis]; - utils.update(div.style, labelStyle); - // TODO: combine outer & inner divs - var inner_div = document.createElement('div'); - inner_div.className = 'dygraph-axis-label' + ' dygraph-axis-label-' + axis + (prec_axis ? ' dygraph-axis-label-' + prec_axis : ''); - inner_div.innerHTML = txt; - div.appendChild(inner_div); - return div; - }; - - // axis lines - context.save(); - - var layout = g.layout_; - var area = e.dygraph.plotter_.area; - - // Helper for repeated axis-option accesses. - var makeOptionGetter = function makeOptionGetter(axis) { - return function (option) { - return g.getOptionForAxis(option, axis); - }; - }; - - if (g.getOptionForAxis('drawAxis', 'y')) { - if (layout.yticks && layout.yticks.length > 0) { - var num_axes = g.numAxes(); - var getOptions = [makeOptionGetter('y'), makeOptionGetter('y2')]; - layout.yticks.forEach(function (tick) { - if (tick.label === undefined) return; // this tick only has a grid line. - x = area.x; - var sgn = 1; - var prec_axis = 'y1'; - var getAxisOption = getOptions[0]; - if (tick.axis == 1) { - // right-side y-axis - x = area.x + area.w; - sgn = -1; - prec_axis = 'y2'; - getAxisOption = getOptions[1]; - } - var fontSize = getAxisOption('axisLabelFontSize'); - y = area.y + tick.pos * area.h; - - /* Tick marks are currently clipped, so don't bother drawing them. - context.beginPath(); - context.moveTo(halfUp(x), halfDown(y)); - context.lineTo(halfUp(x - sgn * this.attr_('axisTickSize')), halfDown(y)); - context.closePath(); - context.stroke(); - */ - - label = makeDiv(tick.label, 'y', num_axes == 2 ? prec_axis : null); - var top = y - fontSize / 2; - if (top < 0) top = 0; - - if (top + fontSize + 3 > canvasHeight) { - label.style.bottom = '0'; - } else { - label.style.top = top + 'px'; - } - // TODO: replace these with css classes? - if (tick.axis === 0) { - label.style.left = area.x - getAxisOption('axisLabelWidth') - getAxisOption('axisTickSize') + 'px'; - label.style.textAlign = 'right'; - } else if (tick.axis == 1) { - label.style.left = area.x + area.w + getAxisOption('axisTickSize') + 'px'; - label.style.textAlign = 'left'; - } - label.style.width = getAxisOption('axisLabelWidth') + 'px'; - containerDiv.appendChild(label); - _this.ylabels_.push(label); - }); - - // The lowest tick on the y-axis often overlaps with the leftmost - // tick on the x-axis. Shift the bottom tick up a little bit to - // compensate if necessary. - var bottomTick = this.ylabels_[0]; - // Interested in the y2 axis also? - var fontSize = g.getOptionForAxis('axisLabelFontSize', 'y'); - var bottom = parseInt(bottomTick.style.top, 10) + fontSize; - if (bottom > canvasHeight - fontSize) { - bottomTick.style.top = parseInt(bottomTick.style.top, 10) - fontSize / 2 + 'px'; - } - } - - // draw a vertical line on the left to separate the chart from the labels. - var axisX; - if (g.getOption('drawAxesAtZero')) { - var r = g.toPercentXCoord(0); - if (r > 1 || r < 0 || isNaN(r)) r = 0; - axisX = halfUp(area.x + r * area.w); - } else { - axisX = halfUp(area.x); - } - - context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y'); - context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y'); - - context.beginPath(); - context.moveTo(axisX, halfDown(area.y)); - context.lineTo(axisX, halfDown(area.y + area.h)); - context.closePath(); - context.stroke(); - - // if there's a secondary y-axis, draw a vertical line for that, too. - if (g.numAxes() == 2) { - context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y2'); - context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y2'); - context.beginPath(); - context.moveTo(halfDown(area.x + area.w), halfDown(area.y)); - context.lineTo(halfDown(area.x + area.w), halfDown(area.y + area.h)); - context.closePath(); - context.stroke(); - } - } - - if (g.getOptionForAxis('drawAxis', 'x')) { - if (layout.xticks) { - var getAxisOption = makeOptionGetter('x'); - layout.xticks.forEach(function (tick) { - if (tick.label === undefined) return; // this tick only has a grid line. - x = area.x + tick.pos * area.w; - y = area.y + area.h; - - /* Tick marks are currently clipped, so don't bother drawing them. - context.beginPath(); - context.moveTo(halfUp(x), halfDown(y)); - context.lineTo(halfUp(x), halfDown(y + this.attr_('axisTickSize'))); - context.closePath(); - context.stroke(); - */ - - label = makeDiv(tick.label, 'x'); - label.style.textAlign = 'center'; - label.style.top = y + getAxisOption('axisTickSize') + 'px'; - - var left = x - getAxisOption('axisLabelWidth') / 2; - if (left + getAxisOption('axisLabelWidth') > canvasWidth) { - left = canvasWidth - getAxisOption('axisLabelWidth'); - label.style.textAlign = 'right'; - } - if (left < 0) { - left = 0; - label.style.textAlign = 'left'; - } - - label.style.left = left + 'px'; - label.style.width = getAxisOption('axisLabelWidth') + 'px'; - containerDiv.appendChild(label); - _this.xlabels_.push(label); - }); - } - - context.strokeStyle = g.getOptionForAxis('axisLineColor', 'x'); - context.lineWidth = g.getOptionForAxis('axisLineWidth', 'x'); - context.beginPath(); - var axisY; - if (g.getOption('drawAxesAtZero')) { - var r = g.toPercentYCoord(0, 0); - if (r > 1 || r < 0) r = 1; - axisY = halfDown(area.y + r * area.h); - } else { - axisY = halfDown(area.y + area.h); - } - context.moveTo(halfUp(area.x), axisY); - context.lineTo(halfUp(area.x + area.w), axisY); - context.closePath(); - context.stroke(); - } - - context.restore(); -}; - -exports['default'] = axes; -module.exports = exports['default']; - -},{"../dygraph-utils":17}],22:[function(require,module,exports){ -/** - * @license - * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ -/*global Dygraph:false */ - -"use strict"; - -// TODO(danvk): move chart label options out of dygraphs and into the plugin. -// TODO(danvk): only tear down & rebuild the DIVs when it's necessary. - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var chart_labels = function chart_labels() { - this.title_div_ = null; - this.xlabel_div_ = null; - this.ylabel_div_ = null; - this.y2label_div_ = null; -}; - -chart_labels.prototype.toString = function () { - return "ChartLabels Plugin"; -}; - -chart_labels.prototype.activate = function (g) { - return { - layout: this.layout, - // clearChart: this.clearChart, - didDrawChart: this.didDrawChart - }; -}; - -// QUESTION: should there be a plugin-utils.js? -var createDivInRect = function createDivInRect(r) { - var div = document.createElement('div'); - div.style.position = 'absolute'; - div.style.left = r.x + 'px'; - div.style.top = r.y + 'px'; - div.style.width = r.w + 'px'; - div.style.height = r.h + 'px'; - return div; -}; - -// Detach and null out any existing nodes. -chart_labels.prototype.detachLabels_ = function () { - var els = [this.title_div_, this.xlabel_div_, this.ylabel_div_, this.y2label_div_]; - for (var i = 0; i < els.length; i++) { - var el = els[i]; - if (!el) continue; - if (el.parentNode) el.parentNode.removeChild(el); - } - - this.title_div_ = null; - this.xlabel_div_ = null; - this.ylabel_div_ = null; - this.y2label_div_ = null; -}; - -var createRotatedDiv = function createRotatedDiv(g, box, axis, classes, html) { - // TODO(danvk): is this outer div actually necessary? - var div = document.createElement("div"); - div.style.position = 'absolute'; - if (axis == 1) { - // NOTE: this is cheating. Should be positioned relative to the box. - div.style.left = '0px'; - } else { - div.style.left = box.x + 'px'; - } - div.style.top = box.y + 'px'; - div.style.width = box.w + 'px'; - div.style.height = box.h + 'px'; - div.style.fontSize = g.getOption('yLabelWidth') - 2 + 'px'; - - var inner_div = document.createElement("div"); - inner_div.style.position = 'absolute'; - inner_div.style.width = box.h + 'px'; - inner_div.style.height = box.w + 'px'; - inner_div.style.top = box.h / 2 - box.w / 2 + 'px'; - inner_div.style.left = box.w / 2 - box.h / 2 + 'px'; - // TODO: combine inner_div and class_div. - inner_div.className = 'dygraph-label-rotate-' + (axis == 1 ? 'right' : 'left'); - - var class_div = document.createElement("div"); - class_div.className = classes; - class_div.innerHTML = html; - - inner_div.appendChild(class_div); - div.appendChild(inner_div); - return div; -}; - -chart_labels.prototype.layout = function (e) { - this.detachLabels_(); - - var g = e.dygraph; - var div = e.chart_div; - if (g.getOption('title')) { - // QUESTION: should this return an absolutely-positioned div instead? - var title_rect = e.reserveSpaceTop(g.getOption('titleHeight')); - this.title_div_ = createDivInRect(title_rect); - this.title_div_.style.fontSize = g.getOption('titleHeight') - 8 + 'px'; - - var class_div = document.createElement("div"); - class_div.className = 'dygraph-label dygraph-title'; - class_div.innerHTML = g.getOption('title'); - this.title_div_.appendChild(class_div); - div.appendChild(this.title_div_); - } - - if (g.getOption('xlabel')) { - var x_rect = e.reserveSpaceBottom(g.getOption('xLabelHeight')); - this.xlabel_div_ = createDivInRect(x_rect); - this.xlabel_div_.style.fontSize = g.getOption('xLabelHeight') - 2 + 'px'; - - var class_div = document.createElement("div"); - class_div.className = 'dygraph-label dygraph-xlabel'; - class_div.innerHTML = g.getOption('xlabel'); - this.xlabel_div_.appendChild(class_div); - div.appendChild(this.xlabel_div_); - } - - if (g.getOption('ylabel')) { - // It would make sense to shift the chart here to make room for the y-axis - // label, but the default yAxisLabelWidth is large enough that this results - // in overly-padded charts. The y-axis label should fit fine. If it - // doesn't, the yAxisLabelWidth option can be increased. - var y_rect = e.reserveSpaceLeft(0); - - this.ylabel_div_ = createRotatedDiv(g, y_rect, 1, // primary (left) y-axis - 'dygraph-label dygraph-ylabel', g.getOption('ylabel')); - div.appendChild(this.ylabel_div_); - } - - if (g.getOption('y2label') && g.numAxes() == 2) { - // same logic applies here as for ylabel. - var y2_rect = e.reserveSpaceRight(0); - this.y2label_div_ = createRotatedDiv(g, y2_rect, 2, // secondary (right) y-axis - 'dygraph-label dygraph-y2label', g.getOption('y2label')); - div.appendChild(this.y2label_div_); - } -}; - -chart_labels.prototype.didDrawChart = function (e) { - var g = e.dygraph; - if (this.title_div_) { - this.title_div_.children[0].innerHTML = g.getOption('title'); - } - if (this.xlabel_div_) { - this.xlabel_div_.children[0].innerHTML = g.getOption('xlabel'); - } - if (this.ylabel_div_) { - this.ylabel_div_.children[0].children[0].innerHTML = g.getOption('ylabel'); - } - if (this.y2label_div_) { - this.y2label_div_.children[0].children[0].innerHTML = g.getOption('y2label'); - } -}; - -chart_labels.prototype.clearChart = function () {}; - -chart_labels.prototype.destroy = function () { - this.detachLabels_(); -}; - -exports["default"] = chart_labels; -module.exports = exports["default"]; - -},{}],23:[function(require,module,exports){ -/** - * @license - * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ -/*global Dygraph:false */ - -/* - -Current bits of jankiness: -- Direct layout access -- Direct area access - -*/ - -"use strict"; - -/** - * Draws the gridlines, i.e. the gray horizontal & vertical lines running the - * length of the chart. - * - * @constructor - */ -Object.defineProperty(exports, "__esModule", { - value: true -}); -var grid = function grid() {}; - -grid.prototype.toString = function () { - return "Gridline Plugin"; -}; - -grid.prototype.activate = function (g) { - return { - willDrawChart: this.willDrawChart - }; -}; - -grid.prototype.willDrawChart = function (e) { - // Draw the new X/Y grid. Lines appear crisper when pixels are rounded to - // half-integers. This prevents them from drawing in two rows/cols. - var g = e.dygraph; - var ctx = e.drawingContext; - var layout = g.layout_; - var area = e.dygraph.plotter_.area; - - function halfUp(x) { - return Math.round(x) + 0.5; - } - function halfDown(y) { - return Math.round(y) - 0.5; - } - - var x, y, i, ticks; - if (g.getOptionForAxis('drawGrid', 'y')) { - var axes = ["y", "y2"]; - var strokeStyles = [], - lineWidths = [], - drawGrid = [], - stroking = [], - strokePattern = []; - for (var i = 0; i < axes.length; i++) { - drawGrid[i] = g.getOptionForAxis('drawGrid', axes[i]); - if (drawGrid[i]) { - strokeStyles[i] = g.getOptionForAxis('gridLineColor', axes[i]); - lineWidths[i] = g.getOptionForAxis('gridLineWidth', axes[i]); - strokePattern[i] = g.getOptionForAxis('gridLinePattern', axes[i]); - stroking[i] = strokePattern[i] && strokePattern[i].length >= 2; - } - } - ticks = layout.yticks; - ctx.save(); - // draw grids for the different y axes - ticks.forEach(function (tick) { - if (!tick.has_tick) return; - var axis = tick.axis; - if (drawGrid[axis]) { - ctx.save(); - if (stroking[axis]) { - if (ctx.setLineDash) ctx.setLineDash(strokePattern[axis]); - } - ctx.strokeStyle = strokeStyles[axis]; - ctx.lineWidth = lineWidths[axis]; - - x = halfUp(area.x); - y = halfDown(area.y + tick.pos * area.h); - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x + area.w, y); - ctx.stroke(); - - ctx.restore(); - } - }); - ctx.restore(); - } - - // draw grid for x axis - if (g.getOptionForAxis('drawGrid', 'x')) { - ticks = layout.xticks; - ctx.save(); - var strokePattern = g.getOptionForAxis('gridLinePattern', 'x'); - var stroking = strokePattern && strokePattern.length >= 2; - if (stroking) { - if (ctx.setLineDash) ctx.setLineDash(strokePattern); - } - ctx.strokeStyle = g.getOptionForAxis('gridLineColor', 'x'); - ctx.lineWidth = g.getOptionForAxis('gridLineWidth', 'x'); - ticks.forEach(function (tick) { - if (!tick.has_tick) return; - x = halfUp(area.x + tick.pos * area.w); - y = halfDown(area.y + area.h); - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x, area.y); - ctx.closePath(); - ctx.stroke(); - }); - if (stroking) { - if (ctx.setLineDash) ctx.setLineDash([]); - } - ctx.restore(); - } -}; - -grid.prototype.destroy = function () {}; - -exports["default"] = grid; -module.exports = exports["default"]; - -},{}],24:[function(require,module,exports){ -/** - * @license - * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ -/*global Dygraph:false */ - -/* -Current bits of jankiness: -- Uses two private APIs: - 1. Dygraph.optionsViewForAxis_ - 2. dygraph.plotter_.area -- Registers for a "predraw" event, which should be renamed. -- I call calculateEmWidthInDiv more often than needed. -*/ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } } - -var _dygraphUtils = require('../dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -/** - * Creates the legend, which appears when the user hovers over the chart. - * The legend can be either a user-specified or generated div. - * - * @constructor - */ -var Legend = function Legend() { - this.legend_div_ = null; - this.is_generated_div_ = false; // do we own this div, or was it user-specified? -}; - -Legend.prototype.toString = function () { - return "Legend Plugin"; -}; - -/** - * This is called during the dygraph constructor, after options have been set - * but before the data is available. - * - * Proper tasks to do here include: - * - Reading your own options - * - DOM manipulation - * - Registering event listeners - * - * @param {Dygraph} g Graph instance. - * @return {object.} Mapping of event names to callbacks. - */ -Legend.prototype.activate = function (g) { - var div; - - var userLabelsDiv = g.getOption('labelsDiv'); - if (userLabelsDiv && null !== userLabelsDiv) { - if (typeof userLabelsDiv == "string" || userLabelsDiv instanceof String) { - div = document.getElementById(userLabelsDiv); - } else { - div = userLabelsDiv; - } - } else { - div = document.createElement("div"); - div.className = "dygraph-legend"; - // TODO(danvk): come up with a cleaner way to expose this. - g.graphDiv.appendChild(div); - this.is_generated_div_ = true; - } - - this.legend_div_ = div; - this.one_em_width_ = 10; // just a guess, will be updated. - - return { - select: this.select, - deselect: this.deselect, - // TODO(danvk): rethink the name "predraw" before we commit to it in any API. - predraw: this.predraw, - didDrawChart: this.didDrawChart - }; -}; - -// Needed for dashed lines. -var calculateEmWidthInDiv = function calculateEmWidthInDiv(div) { - var sizeSpan = document.createElement('span'); - sizeSpan.setAttribute('style', 'margin: 0; padding: 0 0 0 1em; border: 0;'); - div.appendChild(sizeSpan); - var oneEmWidth = sizeSpan.offsetWidth; - div.removeChild(sizeSpan); - return oneEmWidth; -}; - -var escapeHTML = function escapeHTML(str) { - return str.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); -}; - -Legend.prototype.select = function (e) { - var xValue = e.selectedX; - var points = e.selectedPoints; - var row = e.selectedRow; - - var legendMode = e.dygraph.getOption('legend'); - if (legendMode === 'never') { - this.legend_div_.style.display = 'none'; - return; - } - - if (legendMode === 'follow') { - // create floating legend div - var area = e.dygraph.plotter_.area; - var labelsDivWidth = this.legend_div_.offsetWidth; - var yAxisLabelWidth = e.dygraph.getOptionForAxis('axisLabelWidth', 'y'); - // determine floating [left, top] coordinates of the legend div - // within the plotter_ area - // offset 50 px to the right and down from the first selection point - // 50 px is guess based on mouse cursor size - var leftLegend = points[0].x * area.w + 50; - var topLegend = points[0].y * area.h - 50; - - // if legend floats to end of the chart area, it flips to the other - // side of the selection point - if (leftLegend + labelsDivWidth + 1 > area.w) { - leftLegend = leftLegend - 2 * 50 - labelsDivWidth - (yAxisLabelWidth - area.x); - } - - e.dygraph.graphDiv.appendChild(this.legend_div_); - this.legend_div_.style.left = yAxisLabelWidth + leftLegend + "px"; - this.legend_div_.style.top = topLegend + "px"; - } - - var html = Legend.generateLegendHTML(e.dygraph, xValue, points, this.one_em_width_, row); - this.legend_div_.innerHTML = html; - this.legend_div_.style.display = ''; -}; - -Legend.prototype.deselect = function (e) { - var legendMode = e.dygraph.getOption('legend'); - if (legendMode !== 'always') { - this.legend_div_.style.display = "none"; - } - - // Have to do this every time, since styles might have changed. - var oneEmWidth = calculateEmWidthInDiv(this.legend_div_); - this.one_em_width_ = oneEmWidth; - - var html = Legend.generateLegendHTML(e.dygraph, undefined, undefined, oneEmWidth, null); - this.legend_div_.innerHTML = html; -}; - -Legend.prototype.didDrawChart = function (e) { - this.deselect(e); -}; - -// Right edge should be flush with the right edge of the charting area (which -// may not be the same as the right edge of the div, if we have two y-axes. -// TODO(danvk): is any of this really necessary? Could just set "right" in "activate". -/** - * Position the labels div so that: - * - its right edge is flush with the right edge of the charting area - * - its top edge is flush with the top edge of the charting area - * @private - */ -Legend.prototype.predraw = function (e) { - // Don't touch a user-specified labelsDiv. - if (!this.is_generated_div_) return; - - // TODO(danvk): only use real APIs for this. - e.dygraph.graphDiv.appendChild(this.legend_div_); - var area = e.dygraph.getArea(); - var labelsDivWidth = this.legend_div_.offsetWidth; - this.legend_div_.style.left = area.x + area.w - labelsDivWidth - 1 + "px"; - this.legend_div_.style.top = area.y + "px"; -}; - -/** - * Called when dygraph.destroy() is called. - * You should null out any references and detach any DOM elements. - */ -Legend.prototype.destroy = function () { - this.legend_div_ = null; -}; - -/** - * Generates HTML for the legend which is displayed when hovering over the - * chart. If no selected points are specified, a default legend is returned - * (this may just be the empty string). - * @param {number} x The x-value of the selected points. - * @param {Object} sel_points List of selected points for the given - * x-value. Should have properties like 'name', 'yval' and 'canvasy'. - * @param {number} oneEmWidth The pixel width for 1em in the legend. Only - * relevant when displaying a legend with no selection (i.e. {legend: - * 'always'}) and with dashed lines. - * @param {number} row The selected row index. - * @private - */ -Legend.generateLegendHTML = function (g, x, sel_points, oneEmWidth, row) { - // Data about the selection to pass to legendFormatter - var data = { - dygraph: g, - x: x, - series: [] - }; - - var labelToSeries = {}; - var labels = g.getLabels(); - if (labels) { - for (var i = 1; i < labels.length; i++) { - var series = g.getPropertiesForSeries(labels[i]); - var strokePattern = g.getOption('strokePattern', labels[i]); - var seriesData = { - dashHTML: generateLegendDashHTML(strokePattern, series.color, oneEmWidth), - label: labels[i], - labelHTML: escapeHTML(labels[i]), - isVisible: series.visible, - color: series.color - }; - - data.series.push(seriesData); - labelToSeries[labels[i]] = seriesData; - } - } - - if (typeof x !== 'undefined') { - var xOptView = g.optionsViewForAxis_('x'); - var xvf = xOptView('valueFormatter'); - data.xHTML = xvf.call(g, x, xOptView, labels[0], g, row, 0); - - var yOptViews = []; - var num_axes = g.numAxes(); - for (var i = 0; i < num_axes; i++) { - // TODO(danvk): remove this use of a private API - yOptViews[i] = g.optionsViewForAxis_('y' + (i ? 1 + i : '')); - } - - var showZeros = g.getOption('labelsShowZeroValues'); - var highlightSeries = g.getHighlightSeries(); - for (i = 0; i < sel_points.length; i++) { - var pt = sel_points[i]; - var seriesData = labelToSeries[pt.name]; - seriesData.y = pt.yval; - - if (pt.yval === 0 && !showZeros || isNaN(pt.canvasy)) { - seriesData.isVisible = false; - continue; - } - - var series = g.getPropertiesForSeries(pt.name); - var yOptView = yOptViews[series.axis - 1]; - var fmtFunc = yOptView('valueFormatter'); - var yHTML = fmtFunc.call(g, pt.yval, yOptView, pt.name, g, row, labels.indexOf(pt.name)); - - utils.update(seriesData, { yHTML: yHTML }); - - if (pt.name == highlightSeries) { - seriesData.isHighlighted = true; - } - } - } - - var formatter = g.getOption('legendFormatter') || Legend.defaultFormatter; - return formatter.call(g, data); -}; - -Legend.defaultFormatter = function (data) { - var g = data.dygraph; - - // TODO(danvk): deprecate this option in place of {legend: 'never'} - // XXX should this logic be in the formatter? - if (g.getOption('showLabelsOnHighlight') !== true) return ''; - - var sepLines = g.getOption('labelsSeparateLines'); - var html; - - if (typeof data.x === 'undefined') { - // TODO: this check is duplicated in generateLegendHTML. Put it in one place. - if (g.getOption('legend') != 'always') { - return ''; - } - - html = ''; - for (var i = 0; i < data.series.length; i++) { - var series = data.series[i]; - if (!series.isVisible) continue; - - if (html !== '') html += sepLines ? '
' : ' '; - html += "" + series.dashHTML + " " + series.labelHTML + ""; - } - return html; - } - - html = data.xHTML + ':'; - for (var i = 0; i < data.series.length; i++) { - var series = data.series[i]; - if (!series.isVisible) continue; - if (sepLines) html += '
'; - var cls = series.isHighlighted ? ' class="highlight"' : ''; - html += " " + series.labelHTML + ": " + series.yHTML + ""; - } - return html; -}; - -/** - * Generates html for the "dash" displayed on the legend when using "legend: always". - * In particular, this works for dashed lines with any stroke pattern. It will - * try to scale the pattern to fit in 1em width. Or if small enough repeat the - * pattern for 1em width. - * - * @param strokePattern The pattern - * @param color The color of the series. - * @param oneEmWidth The width in pixels of 1em in the legend. - * @private - */ -// TODO(danvk): cache the results of this -function generateLegendDashHTML(strokePattern, color, oneEmWidth) { - // Easy, common case: a solid line - if (!strokePattern || strokePattern.length <= 1) { - return "
"; - } - - var i, j, paddingLeft, marginRight; - var strokePixelLength = 0, - segmentLoop = 0; - var normalizedPattern = []; - var loop; - - // Compute the length of the pixels including the first segment twice, - // since we repeat it. - for (i = 0; i <= strokePattern.length; i++) { - strokePixelLength += strokePattern[i % strokePattern.length]; - } - - // See if we can loop the pattern by itself at least twice. - loop = Math.floor(oneEmWidth / (strokePixelLength - strokePattern[0])); - if (loop > 1) { - // This pattern fits at least two times, no scaling just convert to em; - for (i = 0; i < strokePattern.length; i++) { - normalizedPattern[i] = strokePattern[i] / oneEmWidth; - } - // Since we are repeating the pattern, we don't worry about repeating the - // first segment in one draw. - segmentLoop = normalizedPattern.length; - } else { - // If the pattern doesn't fit in the legend we scale it to fit. - loop = 1; - for (i = 0; i < strokePattern.length; i++) { - normalizedPattern[i] = strokePattern[i] / strokePixelLength; - } - // For the scaled patterns we do redraw the first segment. - segmentLoop = normalizedPattern.length + 1; - } - - // Now make the pattern. - var dash = ""; - for (j = 0; j < loop; j++) { - for (i = 0; i < segmentLoop; i += 2) { - // The padding is the drawn segment. - paddingLeft = normalizedPattern[i % normalizedPattern.length]; - if (i < strokePattern.length) { - // The margin is the space segment. - marginRight = normalizedPattern[(i + 1) % normalizedPattern.length]; - } else { - // The repeated first segment has no right margin. - marginRight = 0; - } - dash += "
"; - } - } - return dash; -}; - -exports["default"] = Legend; -module.exports = exports["default"]; - -},{"../dygraph-utils":17}],25:[function(require,module,exports){ -/** - * @license - * Copyright 2011 Paul Felix (paul.eric.felix@gmail.com) - * MIT-licensed (http://opensource.org/licenses/MIT) - */ -/*global Dygraph:false,TouchEvent:false */ - -/** - * @fileoverview This file contains the RangeSelector plugin used to provide - * a timeline range selector widget for dygraphs. - */ - -/*global Dygraph:false */ -"use strict"; - -Object.defineProperty(exports, '__esModule', { - value: true -}); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -var _dygraphUtils = require('../dygraph-utils'); - -var utils = _interopRequireWildcard(_dygraphUtils); - -var _dygraphInteractionModel = require('../dygraph-interaction-model'); - -var _dygraphInteractionModel2 = _interopRequireDefault(_dygraphInteractionModel); - -var _iframeTarp = require('../iframe-tarp'); - -var _iframeTarp2 = _interopRequireDefault(_iframeTarp); - -var rangeSelector = function rangeSelector() { - this.hasTouchInterface_ = typeof TouchEvent != 'undefined'; - this.isMobileDevice_ = /mobile|android/gi.test(navigator.appVersion); - this.interfaceCreated_ = false; -}; - -rangeSelector.prototype.toString = function () { - return "RangeSelector Plugin"; -}; - -rangeSelector.prototype.activate = function (dygraph) { - this.dygraph_ = dygraph; - if (this.getOption_('showRangeSelector')) { - this.createInterface_(); - } - return { - layout: this.reserveSpace_, - predraw: this.renderStaticLayer_, - didDrawChart: this.renderInteractiveLayer_ - }; -}; - -rangeSelector.prototype.destroy = function () { - this.bgcanvas_ = null; - this.fgcanvas_ = null; - this.leftZoomHandle_ = null; - this.rightZoomHandle_ = null; -}; - -//------------------------------------------------------------------ -// Private methods -//------------------------------------------------------------------ - -rangeSelector.prototype.getOption_ = function (name, opt_series) { - return this.dygraph_.getOption(name, opt_series); -}; - -rangeSelector.prototype.setDefaultOption_ = function (name, value) { - this.dygraph_.attrs_[name] = value; -}; - -/** - * @private - * Creates the range selector elements and adds them to the graph. - */ -rangeSelector.prototype.createInterface_ = function () { - this.createCanvases_(); - this.createZoomHandles_(); - this.initInteraction_(); - - // Range selector and animatedZooms have a bad interaction. See issue 359. - if (this.getOption_('animatedZooms')) { - console.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.'); - this.dygraph_.updateOptions({ animatedZooms: false }, true); - } - - this.interfaceCreated_ = true; - this.addToGraph_(); -}; - -/** - * @private - * Adds the range selector to the graph. - */ -rangeSelector.prototype.addToGraph_ = function () { - var graphDiv = this.graphDiv_ = this.dygraph_.graphDiv; - graphDiv.appendChild(this.bgcanvas_); - graphDiv.appendChild(this.fgcanvas_); - graphDiv.appendChild(this.leftZoomHandle_); - graphDiv.appendChild(this.rightZoomHandle_); -}; - -/** - * @private - * Removes the range selector from the graph. - */ -rangeSelector.prototype.removeFromGraph_ = function () { - var graphDiv = this.graphDiv_; - graphDiv.removeChild(this.bgcanvas_); - graphDiv.removeChild(this.fgcanvas_); - graphDiv.removeChild(this.leftZoomHandle_); - graphDiv.removeChild(this.rightZoomHandle_); - this.graphDiv_ = null; -}; - -/** - * @private - * Called by Layout to allow range selector to reserve its space. - */ -rangeSelector.prototype.reserveSpace_ = function (e) { - if (this.getOption_('showRangeSelector')) { - e.reserveSpaceBottom(this.getOption_('rangeSelectorHeight') + 4); - } -}; - -/** - * @private - * Renders the static portion of the range selector at the predraw stage. - */ -rangeSelector.prototype.renderStaticLayer_ = function () { - if (!this.updateVisibility_()) { - return; - } - this.resize_(); - this.drawStaticLayer_(); -}; - -/** - * @private - * Renders the interactive portion of the range selector after the chart has been drawn. - */ -rangeSelector.prototype.renderInteractiveLayer_ = function () { - if (!this.updateVisibility_() || this.isChangingRange_) { - return; - } - this.placeZoomHandles_(); - this.drawInteractiveLayer_(); -}; - -/** - * @private - * Check to see if the range selector is enabled/disabled and update visibility accordingly. - */ -rangeSelector.prototype.updateVisibility_ = function () { - var enabled = this.getOption_('showRangeSelector'); - if (enabled) { - if (!this.interfaceCreated_) { - this.createInterface_(); - } else if (!this.graphDiv_ || !this.graphDiv_.parentNode) { - this.addToGraph_(); - } - } else if (this.graphDiv_) { - this.removeFromGraph_(); - var dygraph = this.dygraph_; - setTimeout(function () { - dygraph.width_ = 0;dygraph.resize(); - }, 1); - } - return enabled; -}; - -/** - * @private - * Resizes the range selector. - */ -rangeSelector.prototype.resize_ = function () { - function setElementRect(canvas, context, rect, pixelRatioOption) { - var canvasScale = pixelRatioOption || utils.getContextPixelRatio(context); - - canvas.style.top = rect.y + 'px'; - canvas.style.left = rect.x + 'px'; - canvas.width = rect.w * canvasScale; - canvas.height = rect.h * canvasScale; - canvas.style.width = rect.w + 'px'; - canvas.style.height = rect.h + 'px'; - - if (canvasScale != 1) { - context.scale(canvasScale, canvasScale); - } - } - - var plotArea = this.dygraph_.layout_.getPlotArea(); - - var xAxisLabelHeight = 0; - if (this.dygraph_.getOptionForAxis('drawAxis', 'x')) { - xAxisLabelHeight = this.getOption_('xAxisHeight') || this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize'); - } - this.canvasRect_ = { - x: plotArea.x, - y: plotArea.y + plotArea.h + xAxisLabelHeight + 4, - w: plotArea.w, - h: this.getOption_('rangeSelectorHeight') - }; - - var pixelRatioOption = this.dygraph_.getNumericOption('pixelRatio'); - setElementRect(this.bgcanvas_, this.bgcanvas_ctx_, this.canvasRect_, pixelRatioOption); - setElementRect(this.fgcanvas_, this.fgcanvas_ctx_, this.canvasRect_, pixelRatioOption); -}; - -/** - * @private - * Creates the background and foreground canvases. - */ -rangeSelector.prototype.createCanvases_ = function () { - this.bgcanvas_ = utils.createCanvas(); - this.bgcanvas_.className = 'dygraph-rangesel-bgcanvas'; - this.bgcanvas_.style.position = 'absolute'; - this.bgcanvas_.style.zIndex = 9; - this.bgcanvas_ctx_ = utils.getContext(this.bgcanvas_); - - this.fgcanvas_ = utils.createCanvas(); - this.fgcanvas_.className = 'dygraph-rangesel-fgcanvas'; - this.fgcanvas_.style.position = 'absolute'; - this.fgcanvas_.style.zIndex = 9; - this.fgcanvas_.style.cursor = 'default'; - this.fgcanvas_ctx_ = utils.getContext(this.fgcanvas_); -}; - -/** - * @private - * Creates the zoom handle elements. - */ -rangeSelector.prototype.createZoomHandles_ = function () { - var img = new Image(); - img.className = 'dygraph-rangesel-zoomhandle'; - img.style.position = 'absolute'; - img.style.zIndex = 10; - img.style.visibility = 'hidden'; // Initially hidden so they don't show up in the wrong place. - img.style.cursor = 'col-resize'; - // TODO: change image to more options - img.width = 9; - img.height = 16; - img.src = 'data:image/png;base64,' + 'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' + 'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' + 'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' + '6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' + 'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII='; - - if (this.isMobileDevice_) { - img.width *= 2; - img.height *= 2; - } - - this.leftZoomHandle_ = img; - this.rightZoomHandle_ = img.cloneNode(false); -}; - -/** - * @private - * Sets up the interaction for the range selector. - */ -rangeSelector.prototype.initInteraction_ = function () { - var self = this; - var topElem = document; - var clientXLast = 0; - var handle = null; - var isZooming = false; - var isPanning = false; - var dynamic = !this.isMobileDevice_; - - // We cover iframes during mouse interactions. See comments in - // dygraph-utils.js for more info on why this is a good idea. - var tarp = new _iframeTarp2['default'](); - - // functions, defined below. Defining them this way (rather than with - // "function foo() {...}" makes JSHint happy. - var toXDataWindow, onZoomStart, onZoom, onZoomEnd, doZoom, isMouseInPanZone, onPanStart, onPan, onPanEnd, doPan, onCanvasHover; - - // Touch event functions - var onZoomHandleTouchEvent, onCanvasTouchEvent, addTouchEvents; - - toXDataWindow = function (zoomHandleStatus) { - var xDataLimits = self.dygraph_.xAxisExtremes(); - var fact = (xDataLimits[1] - xDataLimits[0]) / self.canvasRect_.w; - var xDataMin = xDataLimits[0] + (zoomHandleStatus.leftHandlePos - self.canvasRect_.x) * fact; - var xDataMax = xDataLimits[0] + (zoomHandleStatus.rightHandlePos - self.canvasRect_.x) * fact; - return [xDataMin, xDataMax]; - }; - - onZoomStart = function (e) { - utils.cancelEvent(e); - isZooming = true; - clientXLast = e.clientX; - handle = e.target ? e.target : e.srcElement; - if (e.type === 'mousedown' || e.type === 'dragstart') { - // These events are removed manually. - utils.addEvent(topElem, 'mousemove', onZoom); - utils.addEvent(topElem, 'mouseup', onZoomEnd); - } - self.fgcanvas_.style.cursor = 'col-resize'; - tarp.cover(); - return true; - }; - - onZoom = function (e) { - if (!isZooming) { - return false; - } - utils.cancelEvent(e); - - var delX = e.clientX - clientXLast; - if (Math.abs(delX) < 4) { - return true; - } - clientXLast = e.clientX; - - // Move handle. - var zoomHandleStatus = self.getZoomHandleStatus_(); - var newPos; - if (handle == self.leftZoomHandle_) { - newPos = zoomHandleStatus.leftHandlePos + delX; - newPos = Math.min(newPos, zoomHandleStatus.rightHandlePos - handle.width - 3); - newPos = Math.max(newPos, self.canvasRect_.x); - } else { - newPos = zoomHandleStatus.rightHandlePos + delX; - newPos = Math.min(newPos, self.canvasRect_.x + self.canvasRect_.w); - newPos = Math.max(newPos, zoomHandleStatus.leftHandlePos + handle.width + 3); - } - var halfHandleWidth = handle.width / 2; - handle.style.left = newPos - halfHandleWidth + 'px'; - self.drawInteractiveLayer_(); - - // Zoom on the fly. - if (dynamic) { - doZoom(); - } - return true; - }; - - onZoomEnd = function (e) { - if (!isZooming) { - return false; - } - isZooming = false; - tarp.uncover(); - utils.removeEvent(topElem, 'mousemove', onZoom); - utils.removeEvent(topElem, 'mouseup', onZoomEnd); - self.fgcanvas_.style.cursor = 'default'; - - // If on a slower device, zoom now. - if (!dynamic) { - doZoom(); - } - return true; - }; - - doZoom = function () { - try { - var zoomHandleStatus = self.getZoomHandleStatus_(); - self.isChangingRange_ = true; - if (!zoomHandleStatus.isZoomed) { - self.dygraph_.resetZoom(); - } else { - var xDataWindow = toXDataWindow(zoomHandleStatus); - self.dygraph_.doZoomXDates_(xDataWindow[0], xDataWindow[1]); - } - } finally { - self.isChangingRange_ = false; - } - }; - - isMouseInPanZone = function (e) { - var rect = self.leftZoomHandle_.getBoundingClientRect(); - var leftHandleClientX = rect.left + rect.width / 2; - rect = self.rightZoomHandle_.getBoundingClientRect(); - var rightHandleClientX = rect.left + rect.width / 2; - return e.clientX > leftHandleClientX && e.clientX < rightHandleClientX; - }; - - onPanStart = function (e) { - if (!isPanning && isMouseInPanZone(e) && self.getZoomHandleStatus_().isZoomed) { - utils.cancelEvent(e); - isPanning = true; - clientXLast = e.clientX; - if (e.type === 'mousedown') { - // These events are removed manually. - utils.addEvent(topElem, 'mousemove', onPan); - utils.addEvent(topElem, 'mouseup', onPanEnd); - } - return true; - } - return false; - }; - - onPan = function (e) { - if (!isPanning) { - return false; - } - utils.cancelEvent(e); - - var delX = e.clientX - clientXLast; - if (Math.abs(delX) < 4) { - return true; - } - clientXLast = e.clientX; - - // Move range view - var zoomHandleStatus = self.getZoomHandleStatus_(); - var leftHandlePos = zoomHandleStatus.leftHandlePos; - var rightHandlePos = zoomHandleStatus.rightHandlePos; - var rangeSize = rightHandlePos - leftHandlePos; - if (leftHandlePos + delX <= self.canvasRect_.x) { - leftHandlePos = self.canvasRect_.x; - rightHandlePos = leftHandlePos + rangeSize; - } else if (rightHandlePos + delX >= self.canvasRect_.x + self.canvasRect_.w) { - rightHandlePos = self.canvasRect_.x + self.canvasRect_.w; - leftHandlePos = rightHandlePos - rangeSize; - } else { - leftHandlePos += delX; - rightHandlePos += delX; - } - var halfHandleWidth = self.leftZoomHandle_.width / 2; - self.leftZoomHandle_.style.left = leftHandlePos - halfHandleWidth + 'px'; - self.rightZoomHandle_.style.left = rightHandlePos - halfHandleWidth + 'px'; - self.drawInteractiveLayer_(); - - // Do pan on the fly. - if (dynamic) { - doPan(); - } - return true; - }; - - onPanEnd = function (e) { - if (!isPanning) { - return false; - } - isPanning = false; - utils.removeEvent(topElem, 'mousemove', onPan); - utils.removeEvent(topElem, 'mouseup', onPanEnd); - // If on a slower device, do pan now. - if (!dynamic) { - doPan(); - } - return true; - }; - - doPan = function () { - try { - self.isChangingRange_ = true; - self.dygraph_.dateWindow_ = toXDataWindow(self.getZoomHandleStatus_()); - self.dygraph_.drawGraph_(false); - } finally { - self.isChangingRange_ = false; - } - }; - - onCanvasHover = function (e) { - if (isZooming || isPanning) { - return; - } - var cursor = isMouseInPanZone(e) ? 'move' : 'default'; - if (cursor != self.fgcanvas_.style.cursor) { - self.fgcanvas_.style.cursor = cursor; - } - }; - - onZoomHandleTouchEvent = function (e) { - if (e.type == 'touchstart' && e.targetTouches.length == 1) { - if (onZoomStart(e.targetTouches[0])) { - utils.cancelEvent(e); - } - } else if (e.type == 'touchmove' && e.targetTouches.length == 1) { - if (onZoom(e.targetTouches[0])) { - utils.cancelEvent(e); - } - } else { - onZoomEnd(e); - } - }; - - onCanvasTouchEvent = function (e) { - if (e.type == 'touchstart' && e.targetTouches.length == 1) { - if (onPanStart(e.targetTouches[0])) { - utils.cancelEvent(e); - } - } else if (e.type == 'touchmove' && e.targetTouches.length == 1) { - if (onPan(e.targetTouches[0])) { - utils.cancelEvent(e); - } - } else { - onPanEnd(e); - } - }; - - addTouchEvents = function (elem, fn) { - var types = ['touchstart', 'touchend', 'touchmove', 'touchcancel']; - for (var i = 0; i < types.length; i++) { - self.dygraph_.addAndTrackEvent(elem, types[i], fn); - } - }; - - this.setDefaultOption_('interactionModel', _dygraphInteractionModel2['default'].dragIsPanInteractionModel); - this.setDefaultOption_('panEdgeFraction', 0.0001); - - var dragStartEvent = window.opera ? 'mousedown' : 'dragstart'; - this.dygraph_.addAndTrackEvent(this.leftZoomHandle_, dragStartEvent, onZoomStart); - this.dygraph_.addAndTrackEvent(this.rightZoomHandle_, dragStartEvent, onZoomStart); - - this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousedown', onPanStart); - this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousemove', onCanvasHover); - - // Touch events - if (this.hasTouchInterface_) { - addTouchEvents(this.leftZoomHandle_, onZoomHandleTouchEvent); - addTouchEvents(this.rightZoomHandle_, onZoomHandleTouchEvent); - addTouchEvents(this.fgcanvas_, onCanvasTouchEvent); - } -}; - -/** - * @private - * Draws the static layer in the background canvas. - */ -rangeSelector.prototype.drawStaticLayer_ = function () { - var ctx = this.bgcanvas_ctx_; - ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h); - try { - this.drawMiniPlot_(); - } catch (ex) { - console.warn(ex); - } - - var margin = 0.5; - this.bgcanvas_ctx_.lineWidth = this.getOption_('rangeSelectorBackgroundLineWidth'); - ctx.strokeStyle = this.getOption_('rangeSelectorBackgroundStrokeColor'); - ctx.beginPath(); - ctx.moveTo(margin, margin); - ctx.lineTo(margin, this.canvasRect_.h - margin); - ctx.lineTo(this.canvasRect_.w - margin, this.canvasRect_.h - margin); - ctx.lineTo(this.canvasRect_.w - margin, margin); - ctx.stroke(); -}; - -/** - * @private - * Draws the mini plot in the background canvas. - */ -rangeSelector.prototype.drawMiniPlot_ = function () { - var fillStyle = this.getOption_('rangeSelectorPlotFillColor'); - var fillGradientStyle = this.getOption_('rangeSelectorPlotFillGradientColor'); - var strokeStyle = this.getOption_('rangeSelectorPlotStrokeColor'); - if (!fillStyle && !strokeStyle) { - return; - } - - var stepPlot = this.getOption_('stepPlot'); - - var combinedSeriesData = this.computeCombinedSeriesAndLimits_(); - var yRange = combinedSeriesData.yMax - combinedSeriesData.yMin; - - // Draw the mini plot. - var ctx = this.bgcanvas_ctx_; - var margin = 0.5; - - var xExtremes = this.dygraph_.xAxisExtremes(); - var xRange = Math.max(xExtremes[1] - xExtremes[0], 1.e-30); - var xFact = (this.canvasRect_.w - margin) / xRange; - var yFact = (this.canvasRect_.h - margin) / yRange; - var canvasWidth = this.canvasRect_.w - margin; - var canvasHeight = this.canvasRect_.h - margin; - - var prevX = null, - prevY = null; - - ctx.beginPath(); - ctx.moveTo(margin, canvasHeight); - for (var i = 0; i < combinedSeriesData.data.length; i++) { - var dataPoint = combinedSeriesData.data[i]; - var x = dataPoint[0] !== null ? (dataPoint[0] - xExtremes[0]) * xFact : NaN; - var y = dataPoint[1] !== null ? canvasHeight - (dataPoint[1] - combinedSeriesData.yMin) * yFact : NaN; - - // Skip points that don't change the x-value. Overly fine-grained points - // can cause major slowdowns with the ctx.fill() call below. - if (!stepPlot && prevX !== null && Math.round(x) == Math.round(prevX)) { - continue; - } - - if (isFinite(x) && isFinite(y)) { - if (prevX === null) { - ctx.lineTo(x, canvasHeight); - } else if (stepPlot) { - ctx.lineTo(x, prevY); - } - ctx.lineTo(x, y); - prevX = x; - prevY = y; - } else { - if (prevX !== null) { - if (stepPlot) { - ctx.lineTo(x, prevY); - ctx.lineTo(x, canvasHeight); - } else { - ctx.lineTo(prevX, canvasHeight); - } - } - prevX = prevY = null; - } - } - ctx.lineTo(canvasWidth, canvasHeight); - ctx.closePath(); - - if (fillStyle) { - var lingrad = this.bgcanvas_ctx_.createLinearGradient(0, 0, 0, canvasHeight); - if (fillGradientStyle) { - lingrad.addColorStop(0, fillGradientStyle); - } - lingrad.addColorStop(1, fillStyle); - this.bgcanvas_ctx_.fillStyle = lingrad; - ctx.fill(); - } - - if (strokeStyle) { - this.bgcanvas_ctx_.strokeStyle = strokeStyle; - this.bgcanvas_ctx_.lineWidth = this.getOption_('rangeSelectorPlotLineWidth'); - ctx.stroke(); - } -}; - -/** - * @private - * Computes and returns the combined series data along with min/max for the mini plot. - * The combined series consists of averaged values for all series. - * When series have error bars, the error bars are ignored. - * @return {Object} An object containing combined series array, ymin, ymax. - */ -rangeSelector.prototype.computeCombinedSeriesAndLimits_ = function () { - var g = this.dygraph_; - var logscale = this.getOption_('logscale'); - var i; - - // Select series to combine. By default, all series are combined. - var numColumns = g.numColumns(); - var labels = g.getLabels(); - var includeSeries = new Array(numColumns); - var anySet = false; - var visibility = g.visibility(); - var inclusion = []; - - for (i = 1; i < numColumns; i++) { - var include = this.getOption_('showInRangeSelector', labels[i]); - inclusion.push(include); - if (include !== null) anySet = true; // it's set explicitly for this series - } - - if (anySet) { - for (i = 1; i < numColumns; i++) { - includeSeries[i] = inclusion[i - 1]; - } - } else { - for (i = 1; i < numColumns; i++) { - includeSeries[i] = visibility[i - 1]; - } - } - - // Create a combined series (average of selected series values). - // TODO(danvk): short-circuit if there's only one series. - var rolledSeries = []; - var dataHandler = g.dataHandler_; - var options = g.attributes_; - for (i = 1; i < g.numColumns(); i++) { - if (!includeSeries[i]) continue; - var series = dataHandler.extractSeries(g.rawData_, i, options); - if (g.rollPeriod() > 1) { - series = dataHandler.rollingAverage(series, g.rollPeriod(), options); - } - - rolledSeries.push(series); - } - - var combinedSeries = []; - for (i = 0; i < rolledSeries[0].length; i++) { - var sum = 0; - var count = 0; - for (var j = 0; j < rolledSeries.length; j++) { - var y = rolledSeries[j][i][1]; - if (y === null || isNaN(y)) continue; - count++; - sum += y; - } - combinedSeries.push([rolledSeries[0][i][0], sum / count]); - } - - // Compute the y range. - var yMin = Number.MAX_VALUE; - var yMax = -Number.MAX_VALUE; - for (i = 0; i < combinedSeries.length; i++) { - var yVal = combinedSeries[i][1]; - if (yVal !== null && isFinite(yVal) && (!logscale || yVal > 0)) { - yMin = Math.min(yMin, yVal); - yMax = Math.max(yMax, yVal); - } - } - - // Convert Y data to log scale if needed. - // Also, expand the Y range to compress the mini plot a little. - var extraPercent = 0.25; - if (logscale) { - yMax = utils.log10(yMax); - yMax += yMax * extraPercent; - yMin = utils.log10(yMin); - for (i = 0; i < combinedSeries.length; i++) { - combinedSeries[i][1] = utils.log10(combinedSeries[i][1]); - } - } else { - var yExtra; - var yRange = yMax - yMin; - if (yRange <= Number.MIN_VALUE) { - yExtra = yMax * extraPercent; - } else { - yExtra = yRange * extraPercent; - } - yMax += yExtra; - yMin -= yExtra; - } - - return { data: combinedSeries, yMin: yMin, yMax: yMax }; -}; - -/** - * @private - * Places the zoom handles in the proper position based on the current X data window. - */ -rangeSelector.prototype.placeZoomHandles_ = function () { - var xExtremes = this.dygraph_.xAxisExtremes(); - var xWindowLimits = this.dygraph_.xAxisRange(); - var xRange = xExtremes[1] - xExtremes[0]; - var leftPercent = Math.max(0, (xWindowLimits[0] - xExtremes[0]) / xRange); - var rightPercent = Math.max(0, (xExtremes[1] - xWindowLimits[1]) / xRange); - var leftCoord = this.canvasRect_.x + this.canvasRect_.w * leftPercent; - var rightCoord = this.canvasRect_.x + this.canvasRect_.w * (1 - rightPercent); - var handleTop = Math.max(this.canvasRect_.y, this.canvasRect_.y + (this.canvasRect_.h - this.leftZoomHandle_.height) / 2); - var halfHandleWidth = this.leftZoomHandle_.width / 2; - this.leftZoomHandle_.style.left = leftCoord - halfHandleWidth + 'px'; - this.leftZoomHandle_.style.top = handleTop + 'px'; - this.rightZoomHandle_.style.left = rightCoord - halfHandleWidth + 'px'; - this.rightZoomHandle_.style.top = this.leftZoomHandle_.style.top; - - this.leftZoomHandle_.style.visibility = 'visible'; - this.rightZoomHandle_.style.visibility = 'visible'; -}; - -/** - * @private - * Draws the interactive layer in the foreground canvas. - */ -rangeSelector.prototype.drawInteractiveLayer_ = function () { - var ctx = this.fgcanvas_ctx_; - ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h); - var margin = 1; - var width = this.canvasRect_.w - margin; - var height = this.canvasRect_.h - margin; - var zoomHandleStatus = this.getZoomHandleStatus_(); - - ctx.strokeStyle = this.getOption_('rangeSelectorForegroundStrokeColor'); - ctx.lineWidth = this.getOption_('rangeSelectorForegroundLineWidth'); - if (!zoomHandleStatus.isZoomed) { - ctx.beginPath(); - ctx.moveTo(margin, margin); - ctx.lineTo(margin, height); - ctx.lineTo(width, height); - ctx.lineTo(width, margin); - ctx.stroke(); - } else { - var leftHandleCanvasPos = Math.max(margin, zoomHandleStatus.leftHandlePos - this.canvasRect_.x); - var rightHandleCanvasPos = Math.min(width, zoomHandleStatus.rightHandlePos - this.canvasRect_.x); - - ctx.fillStyle = 'rgba(240, 240, 240, ' + this.getOption_('rangeSelectorAlpha').toString() + ')'; - ctx.fillRect(0, 0, leftHandleCanvasPos, this.canvasRect_.h); - ctx.fillRect(rightHandleCanvasPos, 0, this.canvasRect_.w - rightHandleCanvasPos, this.canvasRect_.h); - - ctx.beginPath(); - ctx.moveTo(margin, margin); - ctx.lineTo(leftHandleCanvasPos, margin); - ctx.lineTo(leftHandleCanvasPos, height); - ctx.lineTo(rightHandleCanvasPos, height); - ctx.lineTo(rightHandleCanvasPos, margin); - ctx.lineTo(width, margin); - ctx.stroke(); - } -}; - -/** - * @private - * Returns the current zoom handle position information. - * @return {Object} The zoom handle status. - */ -rangeSelector.prototype.getZoomHandleStatus_ = function () { - var halfHandleWidth = this.leftZoomHandle_.width / 2; - var leftHandlePos = parseFloat(this.leftZoomHandle_.style.left) + halfHandleWidth; - var rightHandlePos = parseFloat(this.rightZoomHandle_.style.left) + halfHandleWidth; - return { - leftHandlePos: leftHandlePos, - rightHandlePos: rightHandlePos, - isZoomed: leftHandlePos - 1 > this.canvasRect_.x || rightHandlePos + 1 < this.canvasRect_.x + this.canvasRect_.w - }; -}; - -exports['default'] = rangeSelector; -module.exports = exports['default']; - -},{"../dygraph-interaction-model":12,"../dygraph-utils":17,"../iframe-tarp":19}]},{},[18])(18) -}); -//# sourceMappingURL=dygraph.js.map diff --git a/static/frontend/dygraph.min.css b/static/frontend/dygraph.min.css new file mode 100644 index 00000000..219e38d8 --- /dev/null +++ b/static/frontend/dygraph.min.css @@ -0,0 +1,2 @@ +.dygraph-legend{position:absolute;font-size:14px;z-index:10;width:250px;background:#fff;line-height:normal;text-align:left;overflow:hidden}.dygraph-legend[dir=rtl]{text-align:right}.dygraph-legend-line{display:inline-block;position:relative;bottom:.5ex;padding-left:1em;height:1px;border-bottom-width:2px;border-bottom-style:solid}.dygraph-legend-dash{display:inline-block;position:relative;bottom:.5ex;height:1px;border-bottom-width:2px;border-bottom-style:solid}.dygraph-roller{position:absolute;z-index:10}.dygraph-annotation{position:absolute;z-index:10;overflow:hidden}.dygraph-default-annotation{border:1px solid #000;background-color:#fff;text-align:center}.dygraph-axis-label{z-index:10;line-height:normal;overflow:hidden;color:#000}.dygraph-title{font-weight:700;z-index:10;text-align:center}.dygraph-xlabel{text-align:center}.dygraph-label-rotate-left{text-align:center;transform:rotate(90deg);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-o-transform:rotate(90deg);-ms-transform:rotate(90deg)}.dygraph-label-rotate-right{text-align:center;transform:rotate(-90deg);-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-o-transform:rotate(-90deg);-ms-transform:rotate(-90deg)} +/*# sourceMappingURL=dygraph.min.css.map */ \ No newline at end of file diff --git a/static/frontend/dygraph.min.js b/static/frontend/dygraph.min.js index 7df16bef..aac6b5a5 100644 --- a/static/frontend/dygraph.min.js +++ b/static/frontend/dygraph.min.js @@ -1,6 +1,3 @@ -/*! @license Copyright 2017 Dan Vanderkam (danvdk@gmail.com) MIT-licensed (http://opensource.org/licenses/MIT) */ -!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Dygraph=t()}}(function(){return function t(e,a,i){function n(o,s){if(!a[o]){if(!e[o]){var l="function"==typeof require&&require;if(!s&&l)return l(o,!0);if(r)return r(o,!0);var h=new Error("Cannot find module '"+o+"'");throw h.code="MODULE_NOT_FOUND",h}var u=a[o]={exports:{}};e[o][0].call(u.exports,function(t){var a=e[o][1][t];return n(a||t)},u,u.exports,t,e,a,i)}return a[o].exports}for(var r="function"==typeof require&&require,o=0;o1)for(var a=1;a=0){var d=t[l-e];null===d[1]||isNaN(d[1])||(n-=d[2][0],o-=d[1],r-=d[2][1],s-=1)}u[l]=s?[t[l][0],1*o/s,[1*n/s,1*r/s]]:[t[l][0],null,[null,null]]}return u},a.default=r,e.exports=a.default},{"./bars":5}],3:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./bars"),n=function(t){return t&&t.__esModule?t:{default:t}}(i),r=function(){};r.prototype=new n.default,r.prototype.extractSeries=function(t,e,a){for(var i,n,r,o,s=[],l=a.get("sigma"),h=a.get("logscale"),u=0;u=0&&(u-=t[r-e][2][2],d-=t[r-e][2][3]);var c=t[r][0],p=d?u/d:0;if(h)if(d){var g=p<0?0:p,f=d,_=l*Math.sqrt(g*(1-g)/f+l*l/(4*f*f)),v=1+l*l/d;i=(g+l*l/(2*d)-_)/v,n=(g+l*l/(2*d)+_)/v,s[r]=[c,100*g,[100*i,100*n]]}else s[r]=[c,0,[0,0]];else o=d?l*Math.sqrt(p*(1-p)/d):1,s[r]=[c,100*p,[100*(p-o),100*(p+o)]]}return s},a.default=r,e.exports=a.default},{"./bars":5}],5:[function(t,e,a){"use strict";function i(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(a,"__esModule",{value:!0});var n=t("./datahandler"),r=i(n),o=t("../dygraph-layout"),s=i(o),l=function(){r.default.call(this)};l.prototype=new r.default,l.prototype.extractSeries=function(t,e,a){},l.prototype.rollingAverage=function(t,e,a){},l.prototype.onPointsCreated_=function(t,e){for(var a=0;ai&&(l=i),hr)&&(r=h),(null===n||l=0&&(r-=t[i-e][2][0],o-=t[i-e][2][1]);var s=t[i][0],l=o?r/o:0;n[i]=[s,100*l]}return n},a.default=s,e.exports=a.default},{"./datahandler":6,"./default":8}],8:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./datahandler"),n=function(t){return t&&t.__esModule?t:{default:t}}(i),r=function(){};r.prototype=new n.default,r.prototype.extractSeries=function(t,e,a){for(var i=[],n=a.get("logscale"),r=0;rr)&&(r=i),(null===n||i=2,_=t.drawingContext;_.save(),f&&_.setLineDash&&_.setLineDash(i);var v=s._drawSeries(t,g,a,l,r,d,u,e);s._drawPointsOnLine(t,v,o,e,l),f&&_.setLineDash&&_.setLineDash([]),_.restore()},s._drawSeries=function(t,e,a,i,n,r,o,s){var l,h,u=null,d=null,c=null,p=[],g=!0,f=t.drawingContext;f.beginPath(),f.strokeStyle=s,f.lineWidth=a;for(var _=e.array_,v=e.end_,y=e.predicate_,x=e.start_;x0;a--){var i=e[a];if(2==i[0]){var n=e[a-1];n[1]==i[1]&&n[2]==i[2]&&e.splice(a,1)}}for(var a=0;a2&&!t){var r=0;2==e[0][0]&&r++;for(var o=null,s=null,a=r;ae[s][2]&&(s=a)}}var h=e[o],u=e[s];e.splice(r,e.length-r),os?(e.push(u),e.push(h)):e.push(h)}}},o=function(a){r(a);for(var o=0,s=e.length;o1,h=s-a>1;o(l||h),a=s}e.push([t,n,r])};return{moveTo:function(t,e){s(2,t,e)},lineTo:function(t,e){s(1,t,e)},stroke:function(){o(!0),t.stroke()},fill:function(){o(!0),t.fill()},beginPath:function(){o(!0),t.beginPath()},closePath:function(){o(!0),t.closePath()},_count:function(){return n}}},s._fillPlotter=function(t){if(!t.singleSeriesName&&0===t.seriesIndex){for(var e=t.dygraph,a=e.getLabels().slice(1),i=a.length;i>=0;i--)e.visibility()[i]||a.splice(i,1);if(function(){for(var t=0;t=0;n--){var r=i[n];t.lineTo(r[0],r[1])}},_=d-1;_>=0;_--){var v=t.drawingContext,y=a[_];if(e.getBooleanOption("fillGraph",y)){var x=e.getNumericOption("fillAlpha",y),m=e.getBooleanOption("stepPlot",y),b=p[_],w=e.axisPropertiesForSeries(y),A=1+w.minyval*w.yscale;A<0?A=0:A>1&&(A=1),A=h.h*A+h.y;var O,D=u[_],E=n.createIterator(D,0,D.length,s._getIteratorPredicate(e.getBooleanOption("connectSeparatedPoints",y))),L=NaN,T=[-1,-1],S=n.toRGB_(b),P="rgba("+S.r+","+S.g+","+S.b+","+x+")";v.fillStyle=P,v.beginPath();var C,M=!0;(D.length>2*e.width_||o.default.FORCE_FAST_PROXY)&&(v=s._fastCanvasProxy(v));for(var N,F=[];E.hasNext;)if(N=E.next(),n.isOK(N.y)||m){if(c){if(!M&&C==N.xval)continue;M=!1,C=N.xval,r=g[N.canvasx];var k;k=void 0===r?A:l?r[0]:r,O=[N.canvasy,k],m?-1===T[0]?g[N.canvasx]=[N.canvasy,A]:g[N.canvasx]=[N.canvasy,T[0]]:g[N.canvasx]=N.canvasy}else O=isNaN(N.canvasy)&&m?[h.y+h.h,A]:[N.canvasy,A];isNaN(L)?(v.moveTo(N.canvasx,O[1]),v.lineTo(N.canvasx,O[0])):(m?(v.lineTo(N.canvasx,T[0]),v.lineTo(N.canvasx,O[0])):v.lineTo(N.canvasx,O[0]),c&&(F.push([L,T[1]]),l&&r?F.push([N.canvasx,r[1]]):F.push([N.canvasx,O[1]]))),T=O,L=N.canvasx}else f(v,L,T[1],F),F=[],L=NaN,null===N.y_stacked||isNaN(N.y_stacked)||(g[N.canvasx]=h.h*N.y_stacked+h.y);l=m,O&&N&&(f(v,N.canvasx,O[1],F),F=[]),v.fill()}}}},a.default=s,e.exports=a.default},{"./dygraph":18,"./dygraph-utils":17}],10:[function(t,e,a){"use strict";function i(t){return t&&t.__esModule?t:{default:t}}function n(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}Object.defineProperty(a,"__esModule",{value:!0});var r=t("./dygraph-tickers"),o=n(r),s=t("./dygraph-interaction-model"),l=i(s),h=t("./dygraph-canvas"),u=i(h),d=t("./dygraph-utils"),c=n(d),p={highlightCircleSize:3,highlightSeriesOpts:null,highlightSeriesBackgroundAlpha:.5,highlightSeriesBackgroundColor:"rgb(255, 255, 255)",labelsSeparateLines:!1,labelsShowZeroValues:!0,labelsKMB:!1,labelsKMG2:!1,showLabelsOnHighlight:!0,digitsAfterDecimal:2,maxNumberWidth:6,sigFigs:null,strokeWidth:1,strokeBorderWidth:0,strokeBorderColor:"white",axisTickSize:3,axisLabelFontSize:14,rightGap:5,showRoller:!1,xValueParser:void 0,delimiter:",",sigma:2,errorBars:!1,fractions:!1,wilsonInterval:!0,customBars:!1,fillGraph:!1,fillAlpha:.15,connectSeparatedPoints:!1,stackedGraph:!1,stackedGraphNaNFill:"all",hideOverlayOnMouseOut:!0,legend:"onmouseover",stepPlot:!1,xRangePad:0,yRangePad:null,drawAxesAtZero:!1,titleHeight:28,xLabelHeight:18,yLabelWidth:18,axisLineColor:"black",axisLineWidth:.3,gridLineWidth:.3,axisLabelWidth:50,gridLineColor:"rgb(128,128,128)",interactionModel:l.default.defaultModel,animatedZooms:!1,showRangeSelector:!1,rangeSelectorHeight:40,rangeSelectorPlotStrokeColor:"#808FAB",rangeSelectorPlotFillGradientColor:"white",rangeSelectorPlotFillColor:"#A7B1C4",rangeSelectorBackgroundStrokeColor:"gray",rangeSelectorBackgroundLineWidth:1,rangeSelectorPlotLineWidth:1.5,rangeSelectorForegroundStrokeColor:"black",rangeSelectorForegroundLineWidth:1,rangeSelectorAlpha:.6,showInRangeSelector:null,plotter:[u.default._fillPlotter,u.default._errorPlotter,u.default._linePlotter],plugins:[],axes:{x:{pixelsPerLabel:70,axisLabelWidth:60,axisLabelFormatter:c.dateAxisLabelFormatter,valueFormatter:c.dateValueFormatter,drawGrid:!0,drawAxis:!0,independentTicks:!0,ticker:o.dateTicker},y:{axisLabelWidth:50,pixelsPerLabel:30,valueFormatter:c.numberValueFormatter,axisLabelFormatter:c.numberAxisLabelFormatter,drawGrid:!0,drawAxis:!0,independentTicks:!0,ticker:o.numericTicks},y2:{axisLabelWidth:50,pixelsPerLabel:30,valueFormatter:c.numberValueFormatter,axisLabelFormatter:c.numberAxisLabelFormatter,drawAxis:!0,drawGrid:!1,independentTicks:!1,ticker:o.numericTicks}}};a.default=p,e.exports=a.default},{"./dygraph-canvas":9,"./dygraph-interaction-model":12,"./dygraph-tickers":16,"./dygraph-utils":17}],11:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./dygraph"),n=function(t){return t&&t.__esModule?t:{default:t}}(i),r=function(t){this.container=t};r.prototype.draw=function(t,e){this.container.innerHTML="",void 0!==this.date_graph&&this.date_graph.destroy(),this.date_graph=new n.default(this.container,t,e)},r.prototype.setSelection=function(t){var e=!1;t.length&&(e=t[0].row),this.date_graph.setSelection(e)},r.prototype.getSelection=function(){var t=[],e=this.date_graph.getSelection();if(e<0)return t;for(var a=this.date_graph.layout_.points,i=0;ia.boundedDates[1]&&(i-=r-a.boundedDates[1],r=i+a.dateRange),e.getOptionForAxis("logscale","x")?e.dateWindow_=[Math.pow(n.LOG_SCALE,i),Math.pow(n.LOG_SCALE,r)]:e.dateWindow_=[i,r],a.is2DPan)for(var o=a.dragEndY-a.dragStartY,s=0;s=10&&a.dragDirection==n.HORIZONTAL){var o=Math.min(a.dragStartX,a.dragEndX),s=Math.max(a.dragStartX,a.dragEndX);o=Math.max(o,i.x),s=Math.min(s,i.x+i.w),o=10&&a.dragDirection==n.VERTICAL){var l=Math.min(a.dragStartY,a.dragEndY),h=Math.max(a.dragStartY,a.dragEndY);l=Math.max(l,i.y),h=Math.min(h,i.y+i.h),l1&&(a.startTimeForDoubleTapMs=null);for(var i=[],n=0;n=2){a.initialPinchCenter={pageX:.5*(i[0].pageX+i[1].pageX),pageY:.5*(i[0].pageY+i[1].pageY),dataX:.5*(i[0].dataX+i[1].dataX),dataY:.5*(i[0].dataY+i[1].dataY)};var o=180/Math.PI*Math.atan2(a.initialPinchCenter.pageY-i[0].pageY,i[0].pageX-a.initialPinchCenter.pageX);o=Math.abs(o),o>90&&(o=90-o),a.touchDirections={x:o<67.5,y:o>22.5}}a.initialRange={x:e.xAxisRange(),y:e.yAxisRange()}},r.moveTouch=function(t,e,a){a.startTimeForDoubleTapMs=null;var i,n=[];for(i=0;i=2){var g=s[1].pageX-l.pageX;c=(n[1].pageX-o.pageX)/g;var f=s[1].pageY-l.pageY;p=(n[1].pageY-o.pageY)/f}c=Math.min(8,Math.max(.125,c)),p=Math.min(8,Math.max(.125,p));var _=!1;if(a.touchDirections.x&&(e.dateWindow_=[l.dataX-h.dataX+(a.initialRange.x[0]-l.dataX)/c,l.dataX-h.dataX+(a.initialRange.x[1]-l.dataX)/c],_=!0),a.touchDirections.y)for(i=0;i<1;i++){var v=e.axes_[i],y=e.attributes_.getForAxis("logscale",i);y||(v.valueRange=[l.dataY-h.dataY+(a.initialRange.y[0]-l.dataY)/p,l.dataY-h.dataY+(a.initialRange.y[1]-l.dataY)/p],_=!0)}if(e.drawGraph_(!1),_&&n.length>1&&e.getFunctionOption("zoomCallback")){var x=e.xAxisRange();e.getFunctionOption("zoomCallback").call(e,x[0],x[1],e.yAxisRanges())}},r.endTouch=function(t,e,a){if(0!==t.touches.length)r.startTouch(t,e,a);else if(1==t.changedTouches.length){var i=(new Date).getTime(),n=t.changedTouches[0];a.startTimeForDoubleTapMs&&i-a.startTimeForDoubleTapMs<500&&a.doubleTapX&&Math.abs(a.doubleTapX-n.screenX)<50&&a.doubleTapY&&Math.abs(a.doubleTapY-n.screenY)<50?e.resetZoom():(a.startTimeForDoubleTapMs=i,a.doubleTapX=n.screenX,a.doubleTapY=n.screenY)}};var o=function(t,e,a){return ta?t-a:0},s=function(t,e){var a=n.findPos(e.canvas_),i={left:a.x,right:a.x+e.canvas_.offsetWidth,top:a.y,bottom:a.y+e.canvas_.offsetHeight},r={x:n.pageX(t),y:n.pageY(t)},s=o(r.x,i.left,i.right),l=o(r.y,i.top,i.bottom);return Math.max(s,l)};r.defaultModel={mousedown:function(t,e,a){if(!t.button||2!=t.button){a.initializeMouseDown(t,e,a),t.altKey||t.shiftKey?r.startPan(t,e,a):r.startZoom(t,e,a);var i=function(t){if(a.isZooming){s(t,e)<100?r.moveZoom(t,e,a):null!==a.dragEndX&&(a.dragEndX=null,a.dragEndY=null,e.clearZoomRect_())}else a.isPanning&&r.movePan(t,e,a)},o=function t(o){a.isZooming?null!==a.dragEndX?r.endZoom(o,e,a):r.maybeTreatMouseOpAsClick(o,e,a):a.isPanning&&r.endPan(o,e,a),n.removeEvent(document,"mousemove",i),n.removeEvent(document,"mouseup",t),a.destroy()};e.addAndTrackEvent(document,"mousemove",i),e.addAndTrackEvent(document,"mouseup",o)}},willDestroyContextMyself:!0,touchstart:function(t,e,a){r.startTouch(t,e,a)},touchmove:function(t,e,a){r.moveTouch(t,e,a)},touchend:function(t,e,a){r.endTouch(t,e,a)},dblclick:function(t,e,a){if(a.cancelNextDblclick)return void(a.cancelNextDblclick=!1);var i={canvasx:a.dragEndX,canvasy:a.dragEndY,cancelable:!0};e.cascadeEvents_("dblclick",i)||t.altKey||t.shiftKey||e.resetZoom()}},r.nonInteractiveModel_={mousedown:function(t,e,a){a.initializeMouseDown(t,e,a)},mouseup:r.maybeTreatMouseOpAsClick},r.dragIsPanInteractionModel={mousedown:function(t,e,a){a.initializeMouseDown(t,e,a),r.startPan(t,e,a)},mousemove:function(t,e,a){a.isPanning&&r.movePan(t,e,a)},mouseup:function(t,e,a){a.isPanning&&r.endPan(t,e,a)}},a.default=r,e.exports=a.default},{"./dygraph-utils":17}],13:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./dygraph-utils"),n=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(i),r=function(t){this.dygraph_=t,this.points=[],this.setNames=[],this.annotations=[],this.yAxes_=null,this.xTicks_=null,this.yTicks_=null};r.prototype.addDataset=function(t,e){this.points.push(e),this.setNames.push(t)},r.prototype.getPlotArea=function(){return this.area_},r.prototype.computePlotArea=function(){var t={x:0,y:0};t.w=this.dygraph_.width_-t.x-this.dygraph_.getOption("rightGap"),t.h=this.dygraph_.height_;var e={chart_div:this.dygraph_.graphDiv,reserveSpaceLeft:function(e){var a={x:t.x,y:t.y,w:e,h:t.h};return t.x+=e,t.w-=e,a},reserveSpaceRight:function(e){var a={x:t.x+t.w-e,y:t.y,w:e,h:t.h};return t.w-=e,a},reserveSpaceTop:function(e){var a={x:t.x,y:t.y,w:t.w,h:e};return t.y+=e,t.h-=e,a},reserveSpaceBottom:function(e){var a={x:t.x,y:t.y+t.h-e,w:t.w,h:e};return t.h-=e,a},chartRect:function(){return{x:t.x,y:t.y,w:t.w,h:t.h}}};this.dygraph_.cascadeEvents_("layout",e),this.area_=t},r.prototype.setAnnotations=function(t){this.annotations=[];for(var e=this.dygraph_.getOption("xValueParser")||function(t){return t},a=0;a=0&&i<1&&this.xticks.push({pos:i,label:a,has_tick:r});for(this.yticks=[],t=0;t0&&i<=1&&this.yticks.push({axis:t,pos:i,label:a,has_tick:r})},r.prototype._evaluateAnnotations=function(){var t,e={};for(t=0;t1&&o.update(this.yAxes_[1].options,s.y2||{}),o.update(this.xAxis_.options,s.x||{})}},u.prototype.get=function(t){var e=this.getGlobalUser_(t);return null!==e?e:this.getGlobalDefault_(t)},u.prototype.getGlobalUser_=function(t){return this.user_.hasOwnProperty(t)?this.user_[t]:null},u.prototype.getGlobalDefault_=function(t){return this.global_.hasOwnProperty(t)?this.global_[t]:l.default.hasOwnProperty(t)?l.default[t]:null},u.prototype.getForAxis=function(t,e){var a,i;if("number"==typeof e)a=e,i=0===a?"y":"y2";else{if("y1"==e&&(e="y"),"y"==e)a=0;else if("y2"==e)a=1;else{if("x"!=e)throw"Unknown axis "+e;a=-1}i=e}var n=-1==a?this.xAxis_:this.yAxes_[a];if(n){var r=n.options;if(r.hasOwnProperty(t))return r[t]}if("x"!==e||"logscale"!==t){var o=this.getGlobalUser_(t);if(null!==o)return o}var s=l.default.axes[i];return s.hasOwnProperty(t)?s[t]:this.getGlobalDefault_(t)},u.prototype.getForSeries=function(t,e){if(e===this.dygraph_.getHighlightSeries()&&this.highlightSeries_.hasOwnProperty(t))return this.highlightSeries_[t];if(!this.series_.hasOwnProperty(e))throw"Unknown series: "+e;var a=this.series_[e],i=a.options;return i.hasOwnProperty(t)?i[t]:this.getForAxis(t,a.yAxis)},u.prototype.numAxes=function(){return this.yAxes_.length},u.prototype.axisForSeries=function(t){return this.series_[t].yAxis},u.prototype.axisOptions=function(t){return this.yAxes_[t].options},u.prototype.seriesForAxis=function(t){return this.yAxes_[t].series},u.prototype.seriesNames=function(){return this.labels_},void 0!==i);a.default=u,e.exports=a.default}).call(this,t("_process"))},{"./dygraph-default-attrs":10,"./dygraph-options-reference":14,"./dygraph-utils":17,_process:1}],16:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("./dygraph-utils"),n=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(i),r=function(t,e,a,i,n,r){return o(t,e,a,function(t){return"logscale"!==t&&i(t)},n,r)};a.numericLinearTicks=r;var o=function(t,e,a,i,r,o){var s,l,h,u,c=i("pixelsPerLabel"),p=[];if(o)for(s=0;s=u/4){for(var v=f;v>=g;v--){var y=d[v],x=Math.log(y/t)/Math.log(e/t)*a,m={v:y};null===_?_={tickValue:y,pixel_coord:x}:Math.abs(x-_.pixel_coord)>=c?_={tickValue:y,pixel_coord:x}:m.label="",p.push(m)}p.reverse()}}if(0===p.length){var b,w,A=i("labelsKMG2");A?(b=[1,2,4,8,16,32,64,128,256],w=16):(b=[1,2,5,10,20,50,100],w=10);var O,D,E,L=Math.ceil(a/c),T=Math.abs(e-t)/L,S=Math.floor(Math.log(T)/Math.log(w)),P=Math.pow(w,S);for(l=0;lc));l++);for(D>E&&(O*=-1),s=0;s<=u;s++)h=D+s*O,p.push({v:h})}}var C=i("axisLabelFormatter");for(s=0;s=0?g(t,e,o,i,n):[]};a.dateTicker=s;var l={MILLISECONDLY:0,TWO_MILLISECONDLY:1,FIVE_MILLISECONDLY:2,TEN_MILLISECONDLY:3,FIFTY_MILLISECONDLY:4,HUNDRED_MILLISECONDLY:5,FIVE_HUNDRED_MILLISECONDLY:6,SECONDLY:7,TWO_SECONDLY:8,FIVE_SECONDLY:9,TEN_SECONDLY:10,THIRTY_SECONDLY:11,MINUTELY:12,TWO_MINUTELY:13,FIVE_MINUTELY:14,TEN_MINUTELY:15,THIRTY_MINUTELY:16,HOURLY:17,TWO_HOURLY:18,SIX_HOURLY:19,DAILY:20,TWO_DAILY:21,WEEKLY:22,MONTHLY:23,QUARTERLY:24,BIANNUAL:25,ANNUAL:26,DECADAL:27,CENTENNIAL:28,NUM_GRANULARITIES:29};a.Granularity=l;var h={DATEFIELD_Y:0,DATEFIELD_M:1,DATEFIELD_D:2,DATEFIELD_HH:3,DATEFIELD_MM:4,DATEFIELD_SS:5,DATEFIELD_MS:6,NUM_DATEFIELDS:7},u=[];u[l.MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:1,spacing:1},u[l.TWO_MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:2,spacing:2},u[l.FIVE_MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:5,spacing:5},u[l.TEN_MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:10,spacing:10},u[l.FIFTY_MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:50,spacing:50},u[l.HUNDRED_MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:100,spacing:100},u[l.FIVE_HUNDRED_MILLISECONDLY]={datefield:h.DATEFIELD_MS,step:500,spacing:500},u[l.SECONDLY]={datefield:h.DATEFIELD_SS,step:1,spacing:1e3},u[l.TWO_SECONDLY]={datefield:h.DATEFIELD_SS,step:2,spacing:2e3},u[l.FIVE_SECONDLY]={datefield:h.DATEFIELD_SS,step:5,spacing:5e3},u[l.TEN_SECONDLY]={datefield:h.DATEFIELD_SS,step:10,spacing:1e4},u[l.THIRTY_SECONDLY]={datefield:h.DATEFIELD_SS,step:30,spacing:3e4},u[l.MINUTELY]={datefield:h.DATEFIELD_MM,step:1,spacing:6e4},u[l.TWO_MINUTELY]={datefield:h.DATEFIELD_MM,step:2,spacing:12e4},u[l.FIVE_MINUTELY]={datefield:h.DATEFIELD_MM,step:5,spacing:3e5},u[l.TEN_MINUTELY]={datefield:h.DATEFIELD_MM,step:10,spacing:6e5},u[l.THIRTY_MINUTELY]={datefield:h.DATEFIELD_MM,step:30,spacing:18e5},u[l.HOURLY]={datefield:h.DATEFIELD_HH,step:1,spacing:36e5},u[l.TWO_HOURLY]={datefield:h.DATEFIELD_HH,step:2,spacing:72e5},u[l.SIX_HOURLY]={datefield:h.DATEFIELD_HH,step:6,spacing:216e5},u[l.DAILY]={datefield:h.DATEFIELD_D,step:1,spacing:864e5},u[l.TWO_DAILY]={datefield:h.DATEFIELD_D,step:2,spacing:1728e5},u[l.WEEKLY]={datefield:h.DATEFIELD_D,step:7,spacing:6048e5},u[l.MONTHLY]={datefield:h.DATEFIELD_M,step:1,spacing:2629817280},u[l.QUARTERLY]={datefield:h.DATEFIELD_M,step:3,spacing:216e5*365.2524},u[l.BIANNUAL]={datefield:h.DATEFIELD_M,step:6,spacing:432e5*365.2524},u[l.ANNUAL]={datefield:h.DATEFIELD_Y,step:1,spacing:864e5*365.2524},u[l.DECADAL]={datefield:h.DATEFIELD_Y,step:10,spacing:315578073600},u[l.CENTENNIAL]={datefield:h.DATEFIELD_Y,step:100,spacing:3155780736e3};var d=function(){for(var t=[],e=-39;e<=39;e++)for(var a=Math.pow(10,e),i=1;i<=9;i++){var n=a*i;t.push(n)}return t}(),c=function(t,e,a,i){for(var n=i("pixelsPerLabel"),r=0;r=n)return r}return-1},p=function(t,e,a){var i=u[a].spacing;return Math.round(1*(e-t)/i)},g=function(t,e,a,i,r){var o=i("axisLabelFormatter"),s=i("labelsUTC"),d=s?n.DateAccessorsUTC:n.DateAccessorsLocal,c=u[a].datefield,p=u[a].step,g=u[a].spacing,f=new Date(t),_=[];_[h.DATEFIELD_Y]=d.getFullYear(f),_[h.DATEFIELD_M]=d.getMonth(f),_[h.DATEFIELD_D]=d.getDate(f),_[h.DATEFIELD_HH]=d.getHours(f),_[h.DATEFIELD_MM]=d.getMinutes(f),_[h.DATEFIELD_SS]=d.getSeconds(f),_[h.DATEFIELD_MS]=d.getMilliseconds(f);var v=_[c]%p;a==l.WEEKLY&&(v=d.getDay(f)),_[c]-=v;for(var y=c+1;y=l.DAILY||d.getHours(m)%p==0)&&x.push({v:b,label:o.call(r,m,a,i,r)}),_[c]+=p,m=d.makeDate.apply(null,_),b=m.getTime();return x};a.getDateAxis=g},{"./dygraph-utils":17}],17:[function(t,e,a){"use strict";function i(t,e,a){t.removeEventListener(e,a,!1)}function n(t){return t=t||window.event,t.stopPropagation&&t.stopPropagation(),t.preventDefault&&t.preventDefault(),t.cancelBubble=!0,t.cancel=!0,t.returnValue=!1,!1}function r(t,e,a){var i,n,r;if(0===e)i=a,n=a,r=a;else{var o=Math.floor(6*t),s=6*t-o,l=a*(1-e),h=a*(1-e*s),u=a*(1-e*(1-s));switch(o){case 1:i=h,n=a,r=l;break;case 2:i=l,n=a,r=u;break;case 3:i=l,n=h,r=a;break;case 4:i=u,n=l,r=a;break;case 5:i=a,n=l,r=h;break;case 6:case 0:i=a,n=u,r=l}}return i=Math.floor(255*i+.5),n=Math.floor(255*n+.5),r=Math.floor(255*r+.5),"rgb("+i+","+n+","+r+")"}function o(t){var e=t.getBoundingClientRect(),a=window,i=document.documentElement;return{x:e.left+(a.pageXOffset||i.scrollLeft),y:e.top+(a.pageYOffset||i.scrollTop)}}function s(t){return!t.pageX||t.pageX<0?0:t.pageX}function l(t){return!t.pageY||t.pageY<0?0:t.pageY}function h(t,e){return s(t)-e.px}function u(t,e){return l(t)-e.py}function d(t){return!!t&&!isNaN(t)}function c(t,e){return!!t&&(null!==t.yval&&(null!==t.x&&void 0!==t.x&&(null!==t.y&&void 0!==t.y&&!(isNaN(t.x)||!e&&isNaN(t.y)))))}function p(t,e){var a=Math.min(Math.max(1,e||2),21);return Math.abs(t)<.001&&0!==t?t.toExponential(a-1):t.toPrecision(a)}function g(t){return t<10?"0"+t:""+t}function f(t,e,a,i){var n=g(t)+":"+g(e);if(a&&(n+=":"+g(a),i)){var r=""+i;n+="."+("000"+r).substring(r.length)}return n}function _(t,e){var a=e?tt:$,i=new Date(t),n=a.getFullYear(i),r=a.getMonth(i),o=a.getDate(i),s=a.getHours(i),l=a.getMinutes(i),h=a.getSeconds(i),u=a.getMilliseconds(i),d=""+n,c=g(r+1),p=g(o),_=3600*s+60*l+h+.001*u,v=d+"/"+c+"/"+p;return _&&(v+=" "+f(s,l,h,u)),v}function v(t,e){var a=Math.pow(10,e);return Math.round(t*a)/a}function y(t,e,a,i,n){for(var r=!0;r;){var o=t,s=e,l=a,h=i,u=n;if(r=!1,null!==h&&void 0!==h&&null!==u&&void 0!==u||(h=0,u=s.length-1),h>u)return-1;null!==l&&void 0!==l||(l=0);var d,c=function(t){return t>=0&&to){if(l>0&&(d=p-1,c(d)&&s[d]o))return p;t=o,e=s,a=l,i=p+1,n=u,r=!0,c=p=g=d=void 0}}}function x(t){var e,a;if((-1==t.search("-")||-1!=t.search("T")||-1!=t.search("Z"))&&(a=m(t))&&!isNaN(a))return a;if(-1!=t.search("-")){for(e=t.replace("-","/","g");-1!=e.search("-");)e=e.replace("-","/");a=m(e)}else 8==t.length?(e=t.substr(0,4)+"/"+t.substr(4,2)+"/"+t.substr(6,2),a=m(e)):a=m(t);return a&&!isNaN(a)||console.error("Couldn't parse "+t+" as a date"),a}function m(t){return new Date(t).getTime()}function b(t,e){if(void 0!==e&&null!==e)for(var a in e)e.hasOwnProperty(a)&&(t[a]=e[a]);return t}function w(t,e){if(void 0!==e&&null!==e)for(var a in e)e.hasOwnProperty(a)&&(null===e[a]?t[a]=null:A(e[a])?t[a]=e[a].slice():!function(t){return"object"==typeof Node?t instanceof Node:"object"==typeof t&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName}(e[a])&&"object"==typeof e[a]?("object"==typeof t[a]&&null!==t[a]||(t[a]={}),w(t[a],e[a])):t[a]=e[a]);return t}function A(t){var e=typeof t;return("object"==e||"function"==e&&"function"==typeof t.item)&&null!==t&&"number"==typeof t.length&&3!==t.nodeType}function O(t){return"object"==typeof t&&null!==t&&"function"==typeof t.getTime}function D(t){for(var e=[],a=0;a=e||et.call(window,function(){var e=(new Date).getTime(),h=e-o;n=r,r=Math.floor(h/a);var u=r-n;r+u>s||r>=s?(t(s),i()):(0!==u&&t(r),l())})}()}function C(t,e){var a={};if(t)for(var i=1;i=Math.pow(10,r)||Math.abs(t)=0;g--,c/=l)if(d>=c){i=v(t/c,n)+h[g];break}if(s){var f=String(t.toExponential()).split("e-");2===f.length&&f[1]>=3&&f[1]<=24&&(i=f[1]%3>0?v(f[0]/F(10,f[1]%3),n):Number(f[0]).toFixed(2),i+=u[Math.floor(f[1]/3)-1])}}return i}function X(t,e,a){return Y.call(this,t,a)}function V(t,e,a){var i=a("labelsUTC"),n=i?tt:$,r=n.getFullYear(t),o=n.getMonth(t),s=n.getDate(t),l=n.getHours(t),h=n.getMinutes(t),u=n.getSeconds(t),d=n.getMilliseconds(t);if(e>=G.Granularity.DECADAL)return""+r;if(e>=G.Granularity.MONTHLY)return lt[o]+" "+r;if(0===3600*l+60*h+u+.001*d||e>=G.Granularity.DAILY)return g(s)+" "+lt[o];if(eG.Granularity.MINUTELY?f(l,h,u,0):f(l,h,u,d)}function Z(t,e){return _(t,e("labelsUTC"))}Object.defineProperty(a,"__esModule",{value:!0}),a.removeEvent=i,a.cancelEvent=n,a.hsvToRGB=r,a.findPos=o,a.pageX=s,a.pageY=l,a.dragGetX_=h,a.dragGetY_=u,a.isOK=d,a.isValidPoint=c,a.floatFormat=p,a.zeropad=g,a.hmsString_=f,a.dateString_=_,a.round_=v,a.binarySearch=y,a.dateParser=x,a.dateStrToMillis=m,a.update=b,a.updateDeep=w,a.isArrayLike=A,a.isDateLike=O,a.clone=D,a.createCanvas=E,a.getContextPixelRatio=L,a.Iterator=T,a.createIterator=S,a.repeatAndCleanup=P,a.isPixelChangingOptionList=C,a.detectLineDelimiter=M,a.isNodeContainedBy=N,a.pow=F,a.toRGB_=R,a.isCanvasSupported=I,a.parseFloat_=H,a.numberValueFormatter=Y,a.numberAxisLabelFormatter=X,a.dateAxisLabelFormatter=V,a.dateValueFormatter=Z;var B=t("./dygraph-tickers"),G=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(B);a.LOG_SCALE=10;var W=Math.log(10);a.LN_TEN=W;var U=function(t){return Math.log(t)/W};a.log10=U;var z=function(t,e,a){var i=U(t),n=U(e),r=i+a*(n-i);return Math.pow(10,r)};a.logRangeFraction=z;var j=[2,2];a.DOTTED_LINE=j;var K=[7,3];a.DASHED_LINE=K;var q=[7,2,2,2];a.DOT_DASH_LINE=q;a.HORIZONTAL=1;a.VERTICAL=2;var Q=function(t){return t.getContext("2d")};a.getContext=Q;var J=function(t,e,a){t.addEventListener(e,a,!1)};a.addEvent=J;var $={getFullYear:function(t){return t.getFullYear()},getMonth:function(t){return t.getMonth()},getDate:function(t){return t.getDate()},getHours:function(t){return t.getHours()},getMinutes:function(t){return t.getMinutes()},getSeconds:function(t){return t.getSeconds()},getMilliseconds:function(t){return t.getMilliseconds()},getDay:function(t){return t.getDay()},makeDate:function(t,e,a,i,n,r,o){return new Date(t,e,a,i,n,r,o)}};a.DateAccessorsLocal=$;var tt={getFullYear:function(t){return t.getUTCFullYear()},getMonth:function(t){return t.getUTCMonth()},getDate:function(t){return t.getUTCDate()},getHours:function(t){return t.getUTCHours()},getMinutes:function(t){return t.getUTCMinutes()},getSeconds:function(t){return t.getUTCSeconds()},getMilliseconds:function(t){return t.getUTCMilliseconds()},getDay:function(t){return t.getUTCDay()},makeDate:function(t,e,a,i,n,r,o){return new Date(Date.UTC(t,e,a,i,n,r,o))}};a.DateAccessorsUTC=tt,T.prototype.next=function(){if(!this.hasNext)return null;for(var t=this.peek,e=this.nextIdx_+1,a=!1;e=0;n--){var r=i[n][0],o=i[n][1];if(o.call(r,a),a.propagationStopped)break}return a.defaultPrevented},Q.prototype.getPluginInstance_=function(t){for(var e=0;e=0;if(null===t||void 0===t)return e||a;if("y"===t)return a;throw new Error("axis parameter is ["+t+"] must be null, 'x' or 'y'.")},Q.prototype.toString=function(){var t=this.maindiv_;return"[Dygraph "+(t&&t.id?t.id:t)+"]"},Q.prototype.attr_=function(t,e){return e?this.attributes_.getForSeries(t,e):this.attributes_.get(t)},Q.prototype.getOption=function(t,e){return this.attr_(t,e)},Q.prototype.getNumericOption=function(t,e){return this.getOption(t,e)},Q.prototype.getStringOption=function(t,e){return this.getOption(t,e)},Q.prototype.getBooleanOption=function(t,e){return this.getOption(t,e)},Q.prototype.getFunctionOption=function(t,e){return this.getOption(t,e)},Q.prototype.getOptionForAxis=function(t,e){return this.attributes_.getForAxis(t,e)},Q.prototype.optionsViewForAxis_=function(t){var e=this;return function(a){var i=e.user_attrs_.axes;return i&&i[t]&&i[t].hasOwnProperty(a)?i[t][a]:("x"!==t||"logscale"!==a)&&(void 0!==e.user_attrs_[a]?e.user_attrs_[a]:(i=e.attrs_.axes,i&&i[t]&&i[t].hasOwnProperty(a)?i[t][a]:"y"==t&&e.axes_[0].hasOwnProperty(a)?e.axes_[0][a]:"y2"==t&&e.axes_[1].hasOwnProperty(a)?e.axes_[1][a]:e.attr_(a)))}},Q.prototype.rollPeriod=function(){return this.rollPeriod_},Q.prototype.xAxisRange=function(){return this.dateWindow_?this.dateWindow_:this.xAxisExtremes()},Q.prototype.xAxisExtremes=function(){var t=this.getNumericOption("xRangePad")/this.plotter_.area.w;if(0===this.numRows())return[0-t,1+t];var e=this.rawData_[0][0],a=this.rawData_[this.rawData_.length-1][0];if(t){var i=a-e;e-=i*t,a+=i*t}return[e,a]},Q.prototype.yAxisExtremes=function(){var t=this.gatherDatasets_(this.rolledSeries_,null),e=t.extremes,a=this.axes_;this.computeYAxisRanges_(e);var i=this.axes_;return this.axes_=a,i.map(function(t){return t.extremeRange})},Q.prototype.yAxisRange=function(t){if(void 0===t&&(t=0),t<0||t>=this.axes_.length)return null;var e=this.axes_[t];return[e.computedValueRange[0],e.computedValueRange[1]]},Q.prototype.yAxisRanges=function(){for(var t=[],e=0;ethis.rawData_.length?null:e<0||e>this.rawData_[t].length?null:this.rawData_[t][e]},Q.prototype.createInterface_=function(){var t=this.maindiv_;this.graphDiv=document.createElement("div"),this.graphDiv.style.textAlign="left",this.graphDiv.style.position="relative",t.appendChild(this.graphDiv),this.canvas_=x.createCanvas(),this.canvas_.style.position="absolute",this.hidden_=this.createPlotKitCanvas_(this.canvas_),this.canvas_ctx_=x.getContext(this.canvas_),this.hidden_ctx_=x.getContext(this.hidden_),this.resizeElements_(),this.graphDiv.appendChild(this.hidden_),this.graphDiv.appendChild(this.canvas_),this.mouseEventElement_=this.createMouseEventElement_(),this.layout_=new h.default(this);var e=this;this.mouseMoveHandler_=function(t){e.mouseMove_(t)},this.mouseOutHandler_=function(t){var a=t.target||t.fromElement,i=t.relatedTarget||t.toElement;x.isNodeContainedBy(a,e.graphDiv)&&!x.isNodeContainedBy(i,e.graphDiv)&&e.mouseOut_(t)},this.addAndTrackEvent(window,"mouseout",this.mouseOutHandler_),this.addAndTrackEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_),this.resizeHandler_||(this.resizeHandler_=function(t){e.resize()},this.addAndTrackEvent(window,"resize",this.resizeHandler_))},Q.prototype.resizeElements_=function(){this.graphDiv.style.width=this.width_+"px",this.graphDiv.style.height=this.height_+"px";var t=this.getNumericOption("pixelRatio"),e=t||x.getContextPixelRatio(this.canvas_ctx_);this.canvas_.width=this.width_*e,this.canvas_.height=this.height_*e,this.canvas_.style.width=this.width_+"px",this.canvas_.style.height=this.height_+"px",1!==e&&this.canvas_ctx_.scale(e,e);var a=t||x.getContextPixelRatio(this.hidden_ctx_);this.hidden_.width=this.width_*a,this.hidden_.height=this.height_*a,this.hidden_.style.width=this.width_+"px",this.hidden_.style.height=this.height_+"px",1!==a&&this.hidden_ctx_.scale(a,a)},Q.prototype.destroy=function(){this.canvas_ctx_.restore(),this.hidden_ctx_.restore();for(var t=this.plugins_.length-1;t>=0;t--){var e=this.plugins_.pop();e.plugin.destroy&&e.plugin.destroy()}this.removeTrackedEvents_(),x.removeEvent(window,"mouseout",this.mouseOutHandler_),x.removeEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_),x.removeEvent(window,"resize",this.resizeHandler_),this.resizeHandler_=null,function t(e){for(;e.hasChildNodes();)t(e.firstChild),e.removeChild(e.firstChild)}(this.maindiv_);var a=function(t){for(var e in t)"object"==typeof t[e]&&(t[e]=null)};a(this.layout_),a(this.plotter_),a(this)},Q.prototype.createPlotKitCanvas_=function(t){var e=x.createCanvas();return e.style.position="absolute",e.style.top=t.style.top,e.style.left=t.style.left, -e.width=this.width_,e.height=this.height_,e.style.width=this.width_+"px",e.style.height=this.height_+"px",e},Q.prototype.createMouseEventElement_=function(){return this.canvas_},Q.prototype.setColors_=function(){var t=this.getLabels(),e=t.length-1;this.colors_=[],this.colorsMap_={};for(var a=this.getNumericOption("colorSaturation")||1,i=this.getNumericOption("colorValue")||.5,n=Math.ceil(e/2),r=this.getOption("colors"),o=this.visibility(),s=0;s=0;--u)for(var d=this.layout_.points[u],c=0;c=l.length)){var h=l[s];if(x.isValidPoint(h)){var u=h.canvasy;if(t>h.canvasx&&s+10){var p=(t-h.canvasx)/c;u+=p*(d.canvasy-h.canvasy)}}}else if(t0){var g=l[s-1];if(x.isValidPoint(g)){var c=h.canvasx-g.canvasx;if(c>0){var p=(h.canvasx-t)/c;u+=p*(g.canvasy-h.canvasy)}}}(0===r||u=0){var r=0,o=this.attr_("labels");for(e=1;er&&(r=s)}var l=this.previousVerticalX_;a.clearRect(l-r-1,0,2*r+2,this.height_)}if(this.selPoints_.length>0){var h=this.selPoints_[0].canvasx;for(a.save(),e=0;e=0){t!=this.lastRow_&&(i=!0),this.lastRow_=t;for(var n=0;n=0&&o=0&&(i=!0),this.lastRow_=-1;return this.selPoints_.length?this.lastx_=this.selPoints_[0].xval:this.lastx_=-1,void 0!==e&&(this.highlightSet_!==e&&(i=!0),this.highlightSet_=e),void 0!==a&&(this.lockedSet_=a),i&&this.updateSelection_(void 0),i},Q.prototype.mouseOut_=function(t){this.getFunctionOption("unhighlightCallback")&&this.getFunctionOption("unhighlightCallback").call(this,t),this.getBooleanOption("hideOverlayOnMouseOut")&&!this.lockedSet_&&this.clearSelection()},Q.prototype.clearSelection=function(){if(this.cascadeEvents_("deselect",{}),this.lockedSet_=!1,this.fadeLevel)return void this.animateSelection_(-1);this.canvas_ctx_.clearRect(0,0,this.width_,this.height_),this.fadeLevel=0,this.selPoints_=[],this.lastx_=-1,this.lastRow_=-1,this.highlightSet_=null},Q.prototype.getSelection=function(){if(!this.selPoints_||this.selPoints_.length<1)return-1;for(var t=0;t1&&(a=this.dataHandler_.rollingAverage(a,this.rollPeriod_,this.attributes_)),this.rolledSeries_.push(a)}this.drawGraph_();var i=new Date;this.drawingTimeMs_=i-t},Q.PointType=void 0,Q.stackPoints_=function(t,e,a,i){for(var n=null,r=null,o=null,s=-1,l=0;l=e))for(var a=e;aa[1]&&(a[1]=c),c=1;a--)if(this.visibility()[a-1]){if(e){s=t[a];var p=e[0],g=e[1];for(n=null,r=null,i=0;i=p&&null===n&&(n=i),s[i][0]<=g&&(r=i);null===n&&(n=0);for(var f=n,_=!0;_&&f>0;)f--,_=null===s[f][1];null===r&&(r=s.length-1);var v=r;for(_=!0;_&&v0;){var n=this.readyFns_.pop();n(this)}},Q.prototype.computeYAxes_=function(){var t,e,a;for(this.axes_=[],t=0;t0&&(_=0),v<0&&(v=0)),_==1/0&&(_=0),v==-1/0&&(v=1),a=v-_,0===a&&(0!==v?a=Math.abs(v):(v=1,a=1));var m=v,b=_;e&&(u?(m=v+n*a,b=_):(m=v+n*a,b=_-n*a,b<0&&_>=0&&(b=0),m>0&&v<=0&&(m=0))),h.extremeRange=[b,m]}if(h.valueRange){var w=o(h.valueRange[0])?h.extremeRange[0]:h.valueRange[0],A=o(h.valueRange[1])?h.extremeRange[1]:h.valueRange[1];h.computedValueRange=[w,A]}else h.computedValueRange=h.extremeRange;if(!e)if(u){w=h.computedValueRange[0],A=h.computedValueRange[1];var O=n/(2*n-1),D=(n-1)/(2*n-1);h.computedValueRange[0]=x.logRangeFraction(w,A,O),h.computedValueRange[1]=x.logRangeFraction(w,A,D)}else w=h.computedValueRange[0],A=h.computedValueRange[1],a=A-w,h.computedValueRange[0]=w-a*n,h.computedValueRange[1]=A+a*n;if(c){h.independentTicks=c;var E=this.optionsViewForAxis_("y"+(l?"2":"")),L=E("ticker");h.ticks=L(h.computedValueRange[0],h.computedValueRange[1],this.plotter_.area.h,E,this),r||(r=h)}}if(void 0===r)throw'Configuration Error: At least one axis has to have the "independentTicks" option activated.';for(var l=0;l0&&"e"!=t[a-1]&&"E"!=t[a-1]||t.indexOf("/")>=0||isNaN(parseFloat(t))?e=!0:8==t.length&&t>"19700101"&&t<"20371231"&&(e=!0),this.setXAxisOptions_(e)},Q.prototype.setXAxisOptions_=function(t){t?(this.attrs_.xValueParser=x.dateParser,this.attrs_.axes.x.valueFormatter=x.dateValueFormatter,this.attrs_.axes.x.ticker=v.dateTicker,this.attrs_.axes.x.axisLabelFormatter=x.dateAxisLabelFormatter):(this.attrs_.xValueParser=function(t){return parseFloat(t)},this.attrs_.axes.x.valueFormatter=function(t){return t},this.attrs_.axes.x.ticker=v.numericTicks,this.attrs_.axes.x.axisLabelFormatter=this.attrs_.axes.x.valueFormatter)},Q.prototype.parseCSV_=function(t){var e,a,i=[],n=x.detectLineDelimiter(t),r=t.split(n||"\n"),o=this.getStringOption("delimiter");-1==r[0].indexOf(o)&&r[0].indexOf("\t")>=0&&(o="\t");var s=0;"labels"in this.user_attrs_||(s=1,this.attrs_.labels=r[0].split(o),this.attributes_.reparseSeries());for(var l,h=!1,u=this.attr_("labels").length,d=!1,c=s;c0&&f[0]0;)e=String.fromCharCode(65+(t-1)%26)+e.toLowerCase(),t=Math.floor((t-1)/26);return e}(g.length),y.text="";for(var m=0;m0&&f[0]0&&this.setAnnotations(g,!0),this.attributes_.reparseSeries()},Q.prototype.cascadeDataDidUpdateEvent_=function(){this.cascadeEvents_("dataDidUpdate",{})},Q.prototype.start_=function(){var t=this.file_;if("function"==typeof t&&(t=t()),x.isArrayLike(t))this.rawData_=this.parseArray_(t),this.cascadeDataDidUpdateEvent_(),this.predraw_();else if("object"==typeof t&&"function"==typeof t.getColumnRange)this.parseDataTable_(t),this.cascadeDataDidUpdateEvent_(),this.predraw_();else if("string"==typeof t){var e=x.detectLineDelimiter(t);if(e)this.loadedEvent_(t);else{var a;a=window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");var i=this;a.onreadystatechange=function(){4==a.readyState&&(200!==a.status&&0!==a.status||i.loadedEvent_(a.responseText))},a.open("GET",t,!0),a.send(null)}}else console.error("Unknown data format: "+typeof t)},Q.prototype.updateOptions=function(t,e){void 0===e&&(e=!1);var a=t.file,i=Q.copyUserAttrs_(t);"rollPeriod"in i&&(this.rollPeriod_=i.rollPeriod),"dateWindow"in i&&(this.dateWindow_=i.dateWindow);var n=x.isPixelChangingOptionList(this.attr_("labels"),i);x.updateDeep(this.user_attrs_,i),this.attributes_.reparseSeries(),a?(this.cascadeEvents_("dataWillUpdate",{}),this.file_=a,e||this.start_()):e||(n?this.predraw_():this.renderGraph_(!1))},Q.copyUserAttrs_=function(t){var e={};for(var a in t)t.hasOwnProperty(a)&&"file"!=a&&t.hasOwnProperty(a)&&(e[a]=t[a]);return e},Q.prototype.resize=function(t,e){if(!this.resize_lock){this.resize_lock=!0,null===t!=(null===e)&&(console.warn("Dygraph.resize() should be called with zero parameters or two non-NULL parameters. Pretending it was zero."),t=e=null);var a=this.width_,i=this.height_;t?(this.maindiv_.style.width=t+"px",this.maindiv_.style.height=e+"px",this.width_=t,this.height_=e):(this.width_=this.maindiv_.clientWidth,this.height_=this.maindiv_.clientHeight),a==this.width_&&i==this.height_||(this.resizeElements_(),this.predraw_()),this.resize_lock=!1}},Q.prototype.adjustRoll=function(t){this.rollPeriod_=t,this.predraw_()},Q.prototype.visibility=function(){for(this.getOption("visibility")||(this.attrs_.visibility=[]);this.getOption("visibility").length=a.length?console.warn("Invalid series number in setVisibility: "+n):a[n]=t[n]);else for(var n=0;n=a.length?console.warn("Invalid series number in setVisibility: "+n):a[n]=t[n]:t[n]<0||t[n]>=a.length?console.warn("Invalid series number in setVisibility: "+t[n]):a[t[n]]=e;this.predraw_()},Q.prototype.size=function(){return{width:this.width_,height:this.height_}},Q.prototype.setAnnotations=function(t,e){if(this.annotations_=t,!this.layout_)return void console.warn("Tried to setAnnotations before dygraph was ready. Try setting them in a ready() block. See dygraphs.com/tests/annotation.html");this.layout_.setAnnotations(this.annotations_),e||this.predraw_()},Q.prototype.annotations=function(){return this.annotations_},Q.prototype.getLabels=function(){var t=this.attr_("labels");return t?t.slice():null},Q.prototype.indexFromSetName=function(t){return this.setIndexByName_[t]},Q.prototype.getRowForX=function(t){for(var e=0,a=this.numRows()-1;e<=a;){var i=a+e>>1,n=this.getValue(i,0);if(nt)a=i-1;else{if(e==i)return i;a=i}}return null},Q.prototype.ready=function(t){this.is_initial_draw_?this.readyFns_.push(t):t.call(this,this)},Q.prototype.addAndTrackEvent=function(t,e,a){x.addEvent(t,e,a),this.registeredEvents_.push({elem:t,type:e,fn:a})},Q.prototype.removeTrackedEvents_=function(){if(this.registeredEvents_)for(var t=0;tr.x+r.w||l.canvasyr.y+r.h)){var h=l.annotation,u=6;h.hasOwnProperty("tickHeight")&&(u=h.tickHeight);var d=document.createElement("div") -;d.style.fontSize=e.getOption("axisLabelFontSize")+"px";var c="dygraph-annotation";h.hasOwnProperty("icon")||(c+=" dygraphDefaultAnnotation dygraph-default-annotation"),h.hasOwnProperty("cssClass")&&(c+=" "+h.cssClass),d.className=c;var p=h.hasOwnProperty("width")?h.width:16,g=h.hasOwnProperty("height")?h.height:16;if(h.hasOwnProperty("icon")){var f=document.createElement("img");f.src=h.icon,f.width=p,f.height=g,d.appendChild(f)}else l.annotation.hasOwnProperty("shortText")&&d.appendChild(document.createTextNode(l.annotation.shortText));var _=l.canvasx-p/2;d.style.left=_+"px";var v=0;if(h.attachAtBottom){var y=r.y+r.h-g-u;o[_]?y-=o[_]:o[_]=0,o[_]+=u+g,v=y}else v=l.canvasy-g-u;d.style.top=v+"px",d.style.width=p+"px",d.style.height=g+"px",d.title=l.annotation.text,d.style.color=e.colorsMap_[l.name],d.style.borderColor=e.colorsMap_[l.name],h.div=d,e.addAndTrackEvent(d,"click",n("clickHandler","annotationClickHandler",l)),e.addAndTrackEvent(d,"mouseover",n("mouseOverHandler","annotationMouseOverHandler",l)),e.addAndTrackEvent(d,"mouseout",n("mouseOutHandler","annotationMouseOutHandler",l)),e.addAndTrackEvent(d,"dblclick",n("dblClickHandler","annotationDblClickHandler",l)),i.appendChild(d),this.annotations_.push(d);var x=t.drawingContext;if(x.save(),x.strokeStyle=h.hasOwnProperty("tickColor")?h.tickColor:e.colorsMap_[l.name],x.lineWidth=h.hasOwnProperty("tickWidth")?h.tickWidth:e.getOption("strokeWidth"),x.beginPath(),h.attachAtBottom){var y=v+g;x.moveTo(l.canvasx,y),x.lineTo(l.canvasx,y+u)}else x.moveTo(l.canvasx,l.canvasy),x.lineTo(l.canvasx,l.canvasy-2-u);x.closePath(),x.stroke(),x.restore()}}},i.prototype.destroy=function(){this.detachLabels()},a.default=i,e.exports=a.default},{}],21:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=t("../dygraph-utils"),n=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(i),r=function(){this.xlabels_=[],this.ylabels_=[]};r.prototype.toString=function(){return"Axes Plugin"},r.prototype.activate=function(t){return{layout:this.layout,clearChart:this.clearChart,willDrawChart:this.willDrawChart}},r.prototype.layout=function(t){var e=t.dygraph;if(e.getOptionForAxis("drawAxis","y")){var a=e.getOptionForAxis("axisLabelWidth","y")+2*e.getOptionForAxis("axisTickSize","y");t.reserveSpaceLeft(a)}if(e.getOptionForAxis("drawAxis","x")){var i;i=e.getOption("xAxisHeight")?e.getOption("xAxisHeight"):e.getOptionForAxis("axisLabelFontSize","x")+2*e.getOptionForAxis("axisTickSize","x"),t.reserveSpaceBottom(i)}if(2==e.numAxes()){if(e.getOptionForAxis("drawAxis","y2")){var a=e.getOptionForAxis("axisLabelWidth","y2")+2*e.getOptionForAxis("axisTickSize","y2");t.reserveSpaceRight(a)}}else e.numAxes()>2&&e.error("Only two y-axes are supported at this time. (Trying to use "+e.numAxes()+")")},r.prototype.detachLabels=function(){function t(t){for(var e=0;e0){var x=r.numAxes(),m=[y("y"),y("y2")];_.yticks.forEach(function(t){if(void 0!==t.label){s=v.x;var e="y1",a=m[0];1==t.axis&&(s=v.x+v.w,-1,e="y2",a=m[1]);var n=a("axisLabelFontSize");l=v.y+t.pos*v.h,o=f(t.label,"y",2==x?e:null);var r=l-n/2;r<0&&(r=0),r+n+3>c?o.style.bottom="0":o.style.top=r+"px",0===t.axis?(o.style.left=v.x-a("axisLabelWidth")-a("axisTickSize")+"px",o.style.textAlign="right"):1==t.axis&&(o.style.left=v.x+v.w+a("axisTickSize")+"px",o.style.textAlign="left"),o.style.width=a("axisLabelWidth")+"px",u.appendChild(o),i.ylabels_.push(o)}});var b=this.ylabels_[0],w=r.getOptionForAxis("axisLabelFontSize","y");parseInt(b.style.top,10)+w>c-w&&(b.style.top=parseInt(b.style.top,10)-w/2+"px")}var A;if(r.getOption("drawAxesAtZero")){var O=r.toPercentXCoord(0);(O>1||O<0||isNaN(O))&&(O=0),A=e(v.x+O*v.w)}else A=e(v.x);h.strokeStyle=r.getOptionForAxis("axisLineColor","y"),h.lineWidth=r.getOptionForAxis("axisLineWidth","y"),h.beginPath(),h.moveTo(A,a(v.y)),h.lineTo(A,a(v.y+v.h)),h.closePath(),h.stroke(),2==r.numAxes()&&(h.strokeStyle=r.getOptionForAxis("axisLineColor","y2"),h.lineWidth=r.getOptionForAxis("axisLineWidth","y2"),h.beginPath(),h.moveTo(a(v.x+v.w),a(v.y)),h.lineTo(a(v.x+v.w),a(v.y+v.h)),h.closePath(),h.stroke())}if(r.getOptionForAxis("drawAxis","x")){if(_.xticks){var D=y("x");_.xticks.forEach(function(t){if(void 0!==t.label){s=v.x+t.pos*v.w,l=v.y+v.h,o=f(t.label,"x"),o.style.textAlign="center",o.style.top=l+D("axisTickSize")+"px";var e=s-D("axisLabelWidth")/2;e+D("axisLabelWidth")>d&&(e=d-D("axisLabelWidth"),o.style.textAlign="right"),e<0&&(e=0,o.style.textAlign="left"),o.style.left=e+"px",o.style.width=D("axisLabelWidth")+"px",u.appendChild(o),i.xlabels_.push(o)}})}h.strokeStyle=r.getOptionForAxis("axisLineColor","x"),h.lineWidth=r.getOptionForAxis("axisLineWidth","x"),h.beginPath();var E;if(r.getOption("drawAxesAtZero")){var O=r.toPercentYCoord(0,0);(O>1||O<0)&&(O=1),E=a(v.y+O*v.h)}else E=a(v.y+v.h);h.moveTo(e(v.x),E),h.lineTo(e(v.x+v.w),E),h.closePath(),h.stroke()}h.restore()}},a.default=r,e.exports=a.default},{"../dygraph-utils":17}],22:[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});var i=function(){this.title_div_=null,this.xlabel_div_=null,this.ylabel_div_=null,this.y2label_div_=null};i.prototype.toString=function(){return"ChartLabels Plugin"},i.prototype.activate=function(t){return{layout:this.layout,didDrawChart:this.didDrawChart}};var n=function(t){var e=document.createElement("div");return e.style.position="absolute",e.style.left=t.x+"px",e.style.top=t.y+"px",e.style.width=t.w+"px",e.style.height=t.h+"px",e};i.prototype.detachLabels_=function(){for(var t=[this.title_div_,this.xlabel_div_,this.ylabel_div_,this.y2label_div_],e=0;e=2);o=h.yticks,l.save(),o.forEach(function(t){if(t.has_tick){var r=t.axis;g[r]&&(l.save(),f[r]&&l.setLineDash&&l.setLineDash(_[r]),l.strokeStyle=c[r],l.lineWidth=p[r],i=e(u.x),n=a(u.y+t.pos*u.h),l.beginPath(),l.moveTo(i,n),l.lineTo(i+u.w,n),l.stroke(),l.restore())}}),l.restore()}if(s.getOptionForAxis("drawGrid","x")){o=h.xticks,l.save();var _=s.getOptionForAxis("gridLinePattern","x"),f=_&&_.length>=2;f&&l.setLineDash&&l.setLineDash(_),l.strokeStyle=s.getOptionForAxis("gridLineColor","x"),l.lineWidth=s.getOptionForAxis("gridLineWidth","x"),o.forEach(function(t){t.has_tick&&(i=e(u.x+t.pos*u.w),n=a(u.y+u.h),l.beginPath(),l.moveTo(i,n),l.lineTo(i,u.y),l.closePath(),l.stroke())}),f&&l.setLineDash&&l.setLineDash([]),l.restore()}},i.prototype.destroy=function(){},a.default=i,e.exports=a.default},{}],24:[function(t,e,a){"use strict";function i(t,e,a){if(!t||t.length<=1)return'
';var i,n,r,o,s,l=0,h=0,u=[];for(i=0;i<=t.length;i++)l+=t[i%t.length];if((s=Math.floor(a/(l-t[0])))>1){for(i=0;i
';return d}Object.defineProperty(a,"__esModule",{value:!0});var n=t("../dygraph-utils"),r=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(n),o=function(){this.legend_div_=null,this.is_generated_div_=!1};o.prototype.toString=function(){return"Legend Plugin"},o.prototype.activate=function(t){var e,a=t.getOption("labelsDiv");return a&&null!==a?e="string"==typeof a||a instanceof String?document.getElementById(a):a:(e=document.createElement("div"),e.className="dygraph-legend",t.graphDiv.appendChild(e),this.is_generated_div_=!0),this.legend_div_=e,this.one_em_width_=10,{select:this.select,deselect:this.deselect,predraw:this.predraw,didDrawChart:this.didDrawChart}};var s=function(t){var e=document.createElement("span");e.setAttribute("style","margin: 0; padding: 0 0 0 1em; border: 0;"),t.appendChild(e);var a=e.offsetWidth;return t.removeChild(e),a},l=function(t){return t.replace(/&/g,"&").replace(/"/g,""").replace(//g,">")};o.prototype.select=function(t){var e=t.selectedX,a=t.selectedPoints,i=t.selectedRow,n=t.dygraph.getOption("legend");if("never"===n)return void(this.legend_div_.style.display="none");if("follow"===n){var r=t.dygraph.plotter_.area,s=this.legend_div_.offsetWidth,l=t.dygraph.getOptionForAxis("axisLabelWidth","y"),h=a[0].x*r.w+50,u=a[0].y*r.h-50;h+s+1>r.w&&(h=h-100-s-(l-r.x)),t.dygraph.graphDiv.appendChild(this.legend_div_),this.legend_div_.style.left=l+h+"px",this.legend_div_.style.top=u+"px"}var d=o.generateLegendHTML(t.dygraph,e,a,this.one_em_width_,i);this.legend_div_.innerHTML=d,this.legend_div_.style.display=""},o.prototype.deselect=function(t){"always"!==t.dygraph.getOption("legend")&&(this.legend_div_.style.display="none");var e=s(this.legend_div_);this.one_em_width_=e;var a=o.generateLegendHTML(t.dygraph,void 0,void 0,e,null);this.legend_div_.innerHTML=a},o.prototype.didDrawChart=function(t){this.deselect(t)},o.prototype.predraw=function(t){if(this.is_generated_div_){t.dygraph.graphDiv.appendChild(this.legend_div_);var e=t.dygraph.getArea(),a=this.legend_div_.offsetWidth;this.legend_div_.style.left=e.x+e.w-a-1+"px",this.legend_div_.style.top=e.y+"px"}},o.prototype.destroy=function(){this.legend_div_=null},o.generateLegendHTML=function(t,e,a,n,s){var h={dygraph:t,x:e,series:[]},u={},d=t.getLabels();if(d)for(var c=1;c":" "),a+=""+r.dashHTML+" "+r.labelHTML+"")}return a}a=t.xHTML+":";for(var n=0;n");a+=" "+r.labelHTML+": "+r.yHTML+""}}return a},a.default=o,e.exports=a.default},{"../dygraph-utils":17}],25:[function(t,e,a){"use strict";function i(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(a,"__esModule",{value:!0});var n=t("../dygraph-utils"),r=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&(e[a]=t[a]);return e.default=t,e}(n),o=t("../dygraph-interaction-model"),s=i(o),l=t("../iframe-tarp"),h=i(l),u=function(){this.hasTouchInterface_="undefined"!=typeof TouchEvent,this.isMobileDevice_=/mobile|android/gi.test(navigator.appVersion),this.interfaceCreated_=!1};u.prototype.toString=function(){return"RangeSelector Plugin"},u.prototype.activate=function(t){return this.dygraph_=t,this.getOption_("showRangeSelector")&&this.createInterface_(),{layout:this.reserveSpace_,predraw:this.renderStaticLayer_,didDrawChart:this.renderInteractiveLayer_}},u.prototype.destroy=function(){this.bgcanvas_=null,this.fgcanvas_=null,this.leftZoomHandle_=null,this.rightZoomHandle_=null},u.prototype.getOption_=function(t,e){return this.dygraph_.getOption(t,e)},u.prototype.setDefaultOption_=function(t,e){this.dygraph_.attrs_[t]=e},u.prototype.createInterface_=function(){this.createCanvases_(),this.createZoomHandles_(),this.initInteraction_(),this.getOption_("animatedZooms")&&(console.warn("Animated zooms and range selector are not compatible; disabling animatedZooms."),this.dygraph_.updateOptions({animatedZooms:!1},!0)),this.interfaceCreated_=!0,this.addToGraph_()},u.prototype.addToGraph_=function(){var t=this.graphDiv_=this.dygraph_.graphDiv;t.appendChild(this.bgcanvas_),t.appendChild(this.fgcanvas_),t.appendChild(this.leftZoomHandle_),t.appendChild(this.rightZoomHandle_)},u.prototype.removeFromGraph_=function(){var t=this.graphDiv_;t.removeChild(this.bgcanvas_),t.removeChild(this.fgcanvas_),t.removeChild(this.leftZoomHandle_),t.removeChild(this.rightZoomHandle_),this.graphDiv_=null},u.prototype.reserveSpace_=function(t){this.getOption_("showRangeSelector")&&t.reserveSpaceBottom(this.getOption_("rangeSelectorHeight")+4)},u.prototype.renderStaticLayer_=function(){this.updateVisibility_()&&(this.resize_(),this.drawStaticLayer_())},u.prototype.renderInteractiveLayer_=function(){this.updateVisibility_()&&!this.isChangingRange_&&(this.placeZoomHandles_(),this.drawInteractiveLayer_())},u.prototype.updateVisibility_=function(){var t=this.getOption_("showRangeSelector");if(t)this.interfaceCreated_?this.graphDiv_&&this.graphDiv_.parentNode||this.addToGraph_():this.createInterface_();else if(this.graphDiv_){this.removeFromGraph_();var e=this.dygraph_;setTimeout(function(){e.width_=0,e.resize()},1)}return t},u.prototype.resize_=function(){function t(t,e,a,i){var n=i||r.getContextPixelRatio(e);t.style.top=a.y+"px",t.style.left=a.x+"px",t.width=a.w*n,t.height=a.h*n,t.style.width=a.w+"px",t.style.height=a.h+"px",1!=n&&e.scale(n,n)}var e=this.dygraph_.layout_.getPlotArea(),a=0;this.dygraph_.getOptionForAxis("drawAxis","x")&&(a=this.getOption_("xAxisHeight")||this.getOption_("axisLabelFontSize")+2*this.getOption_("axisTickSize")),this.canvasRect_={x:e.x,y:e.y+e.h+a+4,w:e.w,h:this.getOption_("rangeSelectorHeight")};var i=this.dygraph_.getNumericOption("pixelRatio");t(this.bgcanvas_,this.bgcanvas_ctx_,this.canvasRect_,i),t(this.fgcanvas_,this.fgcanvas_ctx_,this.canvasRect_,i)},u.prototype.createCanvases_=function(){this.bgcanvas_=r.createCanvas(),this.bgcanvas_.className="dygraph-rangesel-bgcanvas",this.bgcanvas_.style.position="absolute",this.bgcanvas_.style.zIndex=9,this.bgcanvas_ctx_=r.getContext(this.bgcanvas_),this.fgcanvas_=r.createCanvas(),this.fgcanvas_.className="dygraph-rangesel-fgcanvas",this.fgcanvas_.style.position="absolute",this.fgcanvas_.style.zIndex=9,this.fgcanvas_.style.cursor="default",this.fgcanvas_ctx_=r.getContext(this.fgcanvas_)},u.prototype.createZoomHandles_=function(){var t=new Image;t.className="dygraph-rangesel-zoomhandle",t.style.position="absolute",t.style.zIndex=10,t.style.visibility="hidden",t.style.cursor="col-resize",t.width=9,t.height=16,t.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAzwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7sqSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=",this.isMobileDevice_&&(t.width*=2,t.height*=2),this.leftZoomHandle_=t,this.rightZoomHandle_=t.cloneNode(!1)},u.prototype.initInteraction_=function(){var t,e,a,i,n,o,l,u,d,c,p,g,f,_,v=this,y=document,x=0,m=null,b=!1,w=!1,A=!this.isMobileDevice_,O=new h.default;t=function(t){var e=v.dygraph_.xAxisExtremes(),a=(e[1]-e[0])/v.canvasRect_.w;return[e[0]+(t.leftHandlePos-v.canvasRect_.x)*a,e[0]+(t.rightHandlePos-v.canvasRect_.x)*a]},e=function(t){return r.cancelEvent(t),b=!0,x=t.clientX,m=t.target?t.target:t.srcElement,"mousedown"!==t.type&&"dragstart"!==t.type||(r.addEvent(y,"mousemove",a),r.addEvent(y,"mouseup",i)),v.fgcanvas_.style.cursor="col-resize",O.cover(),!0},a=function(t){if(!b)return!1;r.cancelEvent(t);var e=t.clientX-x;if(Math.abs(e)<4)return!0;x=t.clientX;var a,i=v.getZoomHandleStatus_();m==v.leftZoomHandle_?(a=i.leftHandlePos+e,a=Math.min(a,i.rightHandlePos-m.width-3),a=Math.max(a,v.canvasRect_.x)):(a=i.rightHandlePos+e,a=Math.min(a,v.canvasRect_.x+v.canvasRect_.w),a=Math.max(a,i.leftHandlePos+m.width+3));var o=m.width/2;return m.style.left=a-o+"px",v.drawInteractiveLayer_(),A&&n(),!0},i=function(t){return!!b&&(b=!1,O.uncover(),r.removeEvent(y,"mousemove",a),r.removeEvent(y,"mouseup",i),v.fgcanvas_.style.cursor="default",A||n(),!0)},n=function(){try{var e=v.getZoomHandleStatus_();if(v.isChangingRange_=!0,e.isZoomed){var a=t(e);v.dygraph_.doZoomXDates_(a[0],a[1])}else v.dygraph_.resetZoom()}finally{v.isChangingRange_=!1}},o=function(t){var e=v.leftZoomHandle_.getBoundingClientRect(),a=e.left+e.width/2;e=v.rightZoomHandle_.getBoundingClientRect();var i=e.left+e.width/2;return t.clientX>a&&t.clientX=v.canvasRect_.x+v.canvasRect_.w?(n=v.canvasRect_.x+v.canvasRect_.w,i=n-o):(i+=e,n+=e);var s=v.leftZoomHandle_.width/2;return v.leftZoomHandle_.style.left=i-s+"px",v.rightZoomHandle_.style.left=n-s+"px",v.drawInteractiveLayer_(),A&&c(),!0},d=function(t){return!!w&&(w=!1,r.removeEvent(y,"mousemove",u),r.removeEvent(y,"mouseup",d),A||c(),!0)},c=function(){try{v.isChangingRange_=!0,v.dygraph_.dateWindow_=t(v.getZoomHandleStatus_()),v.dygraph_.drawGraph_(!1)}finally{v.isChangingRange_=!1}},p=function(t){if(!b&&!w){var e=o(t)?"move":"default";e!=v.fgcanvas_.style.cursor&&(v.fgcanvas_.style.cursor=e)}},g=function(t){"touchstart"==t.type&&1==t.targetTouches.length?e(t.targetTouches[0])&&r.cancelEvent(t):"touchmove"==t.type&&1==t.targetTouches.length?a(t.targetTouches[0])&&r.cancelEvent(t):i(t)},f=function(t){"touchstart"==t.type&&1==t.targetTouches.length?l(t.targetTouches[0])&&r.cancelEvent(t):"touchmove"==t.type&&1==t.targetTouches.length?u(t.targetTouches[0])&&r.cancelEvent(t):d(t)},_=function(t,e){for(var a=["touchstart","touchend","touchmove","touchcancel"],i=0;i1&&(g=c.rollingAverage(g,e.rollPeriod(),p)),d.push(g)}var f=[];for(t=0;t0)&&(m=Math.min(m,w),b=Math.max(b,w))}if(a)for(b=r.log10(b),b+=.25*b,m=r.log10(m),t=0;tthis.canvasRect_.x||a+1h[o][2]&&(o=a));var s=h[r],l=h[o];h.splice(t,h.length-t),r2*e.width_||D.default.FORCE_FAST_PROXY)&&(p=E._fastCanvasProxy(p)),[]);m.hasNext;)if(O=m.next(),T.isOK(O.y)||_){if(l){if(!A&&L==O.xval)continue;var S,A=!1,L=O.xval,M=void 0===(S=d[O.canvasx])?x:i?S[0]:S,C=[O.canvasy,M];_?-1===w[0]?d[O.canvasx]=[O.canvasy,x]:d[O.canvasx]=[O.canvasy,w[0]]:d[O.canvasx]=O.canvasy}else C=isNaN(O.canvasy)&&_?[r.y+r.h,x]:[O.canvasy,x];isNaN(b)?(p.moveTo(O.canvasx,C[1]),p.lineTo(O.canvasx,C[0])):(_&&p.lineTo(O.canvasx,w[0]),p.lineTo(O.canvasx,C[0]),l&&(P.push([b,w[1]]),i&&S?P.push([O.canvasx,S[1]]):P.push([O.canvasx,C[1]]))),w=C,b=O.canvasx}else u(p,b,w[1],P),P=[],b=NaN,null===O.y_stacked||isNaN(O.y_stacked)||(d[O.canvasx]=r.h*O.y_stacked+r.y);i=_,C&&O&&(u(p,O.canvasx,C[1],P),P=[]),p.fill()}}}},a.default=E,e.exports=a.default},{"./dygraph":"dygraphs/src/dygraph.js","./dygraph-utils":"dygraphs/src/dygraph-utils.js"}],"dygraphs/src/dygraph-default-attrs.js":[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0}),a.default=void 0;var n=l(t("./dygraph-tickers")),i=o(t("./dygraph-interaction-model")),r=o(t("./dygraph-canvas")),t=l(t("./dygraph-utils"));function o(t){return t&&t.__esModule?t:{default:t}}function s(t){var e,a;return"function"!=typeof WeakMap?null:(e=new WeakMap,a=new WeakMap,(s=function(t){return t?a:e})(t))}function l(t,e){if(!e&&t&&t.__esModule)return t;if(null===t||"object"!=typeof t&&"function"!=typeof t)return{default:t};e=s(e);if(e&&e.has(t))return e.get(t);var a,n,i={},r=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(a in t)"default"!==a&&Object.prototype.hasOwnProperty.call(t,a)&&((n=r?Object.getOwnPropertyDescriptor(t,a):null)&&(n.get||n.set)?Object.defineProperty(i,a,n):i[a]=t[a]);return i.default=t,e&&e.set(t,i),i}i={highlightCircleSize:3,highlightSeriesOpts:null,highlightSeriesBackgroundAlpha:.5,highlightSeriesBackgroundColor:"rgb(255, 255, 255)",labelsSeparateLines:!1,labelsShowZeroValues:!0,labelsKMB:!1,labelsKMG2:!1,showLabelsOnHighlight:!0,digitsAfterDecimal:2,maxNumberWidth:6,sigFigs:null,strokeWidth:1,strokeBorderWidth:0,strokeBorderColor:"white",axisTickSize:3,axisLabelFontSize:14,rightGap:5,showRoller:!1,xValueParser:void 0,delimiter:",",sigma:2,errorBars:!1,fractions:!1,wilsonInterval:!0,customBars:!1,fillGraph:!1,fillAlpha:.15,connectSeparatedPoints:!1,stackedGraph:!1,stackedGraphNaNFill:"all",hideOverlayOnMouseOut:!0,resizable:"no",legend:"onmouseover",legendFollowOffsetX:50,legendFollowOffsetY:-50,stepPlot:!1,xRangePad:0,yRangePad:null,drawAxesAtZero:!1,titleHeight:28,xLabelHeight:18,yLabelWidth:18,axisLineColor:"black",axisLineWidth:.3,gridLineWidth:.3,axisLabelWidth:50,gridLineColor:"rgb(128,128,128)",interactionModel:i.default.defaultModel,animatedZooms:!1,animateBackgroundFade:!0,showRangeSelector:!1,rangeSelectorHeight:40,rangeSelectorPlotStrokeColor:"#808FAB",rangeSelectorPlotFillGradientColor:"white",rangeSelectorPlotFillColor:"#A7B1C4",rangeSelectorBackgroundStrokeColor:"gray",rangeSelectorBackgroundLineWidth:1,rangeSelectorPlotLineWidth:1.5,rangeSelectorForegroundStrokeColor:"black",rangeSelectorForegroundLineWidth:1,rangeSelectorAlpha:.6,showInRangeSelector:null,plotter:[r.default._fillPlotter,r.default._errorPlotter,r.default._linePlotter],plugins:[],axes:{x:{pixelsPerLabel:70,axisLabelWidth:60,axisLabelFormatter:t.dateAxisLabelFormatter,valueFormatter:t.dateValueFormatter,drawGrid:!0,drawAxis:!0,independentTicks:!0,ticker:n.dateTicker},y:{axisLabelWidth:50,pixelsPerLabel:30,valueFormatter:t.numberValueFormatter,axisLabelFormatter:t.numberAxisLabelFormatter,drawGrid:!0,drawAxis:!0,independentTicks:!0,ticker:n.numericTicks},y2:{axisLabelWidth:50,pixelsPerLabel:30,valueFormatter:t.numberValueFormatter,axisLabelFormatter:t.numberAxisLabelFormatter,drawAxis:!0,drawGrid:!1,independentTicks:!1,ticker:n.numericTicks}}};a.default=i,e.exports=a.default},{"./dygraph-canvas":"dygraphs/src/dygraph-canvas.js","./dygraph-interaction-model":"dygraphs/src/dygraph-interaction-model.js","./dygraph-tickers":"dygraphs/src/dygraph-tickers.js","./dygraph-utils":"dygraphs/src/dygraph-utils.js"}],"dygraphs/src/dygraph-gviz.js":[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0}),a.default=void 0;var n=(t=t("./dygraph"))&&t.__esModule?t:{default:t};function i(t){this.container=t}i.prototype.draw=function(t,e){this.container.innerHTML="",void 0!==this.date_graph&&this.date_graph.destroy(),this.date_graph=new n.default(this.container,t,e)},i.prototype.setSelection=function(t){var e=!1;t.length&&(e=t[0].row),this.date_graph.setSelection(e)},i.prototype.getSelection=function(){var t=[],e=this.date_graph.getSelection();if(!(e<0))for(var a=this.date_graph.layout_.points,n=0;na.boundedDates[1]&&(n=(t-=n-a.boundedDates[1])+a.dateRange),e.getOptionForAxis("logscale","x")?e.dateWindow_=[Math.pow(g.LOG_SCALE,t),Math.pow(g.LOG_SCALE,n)]:e.dateWindow_=[t,n],a.is2DPan)for(var i=a.dragEndY-a.dragStartY,r=0;r=o?u={tickValue:p,pixel_coord:g}:f.label="",s.push(f)}s.reverse()}}if(0===s.length){for(var _,y,v,x,d=n("labelsKMG2")?(_=[1,2,4,8,16,32,64,128,256],16):(_=[1,2,5,10,20,50,100],10),m=Math.ceil(a/o),m=Math.abs(e-t)/m,m=Math.floor(Math.log(m)/Math.log(d)),b=Math.pow(d,m),w=0;w<_.length&&(y=b*_[w],v=Math.floor(t/y)*y,x=Math.ceil(e/y)*y,!(oe?s:t(e,a,n,s+1,r)}return-1},e.cancelEvent=function(t){(t=t||window.event).stopPropagation&&t.stopPropagation();t.preventDefault&&t.preventDefault();return t.cancelBubble=!0,t.cancel=!0,t.returnValue=!1},e.clone=function t(e){var a=[];for(var n=0;n=h.Granularity.DECADAL?""+n:e>=h.Granularity.MONTHLY?E[i]+" "+n:0===3600*o+60*s+l+.001*a||e>=h.Granularity.DAILY?d(r)+" "+E[i]:eh.Granularity.MINUTELY?g(o,s,l,0):g(o,s,l,a)},e.dateParser=function(t){var e,a;if((-1==t.search("-")||-1!=t.search("T")||-1!=t.search("Z"))&&(a=l(t))&&!isNaN(a))return a;if(-1!=t.search("-")){for(e=t.replace("-","/","g");-1!=e.search("-");)e=e.replace("-","/");a=l(e)}else a=l(t);a&&!isNaN(a)||console.error("Couldn't parse "+t+" as a date");return a},e.dateStrToMillis=l,e.dateString_=s,e.dateValueFormatter=function(t,e){return s(t,e("labelsUTC"))},e.detectLineDelimiter=function(t){for(var e=0;e=Math.pow(10,i)?t.toExponential(n):f(t/s,n)+h[l]}else if(r<1){for(l=0;l=Math.pow(10,i)||rt.length)&&(e=t.length);for(var a=0,n=new Array(e);a=this.axes_.length?null:[(t=this.axes_[t]).computedValueRange[0],t.computedValueRange[1]]},L.prototype.yAxisRanges=function(){for(var t=[],e=0;e=this.rawData_.length||e<0||e>=this.rawData_[t].length?null:this.rawData_[t][e]},L.prototype.createInterface_=function(){var t,e=this.maindiv_,n=(this.graphDiv=document.createElement("div"),this.graphDiv.style.textAlign="left",this.graphDiv.style.position="relative",e.appendChild(this.graphDiv),this.canvas_=M.createCanvas(),this.canvas_.style.position="absolute",this.canvas_.style.top=0,this.canvas_.style.left=0,this.hidden_=this.createPlotKitCanvas_(this.canvas_),this.canvas_ctx_=M.getContext(this.canvas_),this.hidden_ctx_=M.getContext(this.hidden_),this.resizeElements_(),this.graphDiv.appendChild(this.hidden_),this.graphDiv.appendChild(this.canvas_),this.mouseEventElement_=this.createMouseEventElement_(),this.layout_=new i.default(this),this);this.mouseMoveHandler_=function(t){n.mouseMove_(t)},this.mouseOutHandler_=function(t){var e=t.target||t.fromElement,a=t.relatedTarget||t.toElement;M.isNodeContainedBy(e,n.graphDiv)&&!M.isNodeContainedBy(a,n.graphDiv)&&n.mouseOut_(t)},this.addAndTrackEvent(window,"mouseout",this.mouseOutHandler_),this.addAndTrackEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_),this.resizeHandler_||(this.resizeHandler_=function(t){n.resize()},this.addAndTrackEvent(window,"resize",this.resizeHandler_),this.resizeObserver_=null,t=this.getStringOption("resizable"),"undefined"==typeof ResizeObserver&&"no"!==t&&(console.error("ResizeObserver unavailable; ignoring resizable property"),t="no"),"horizontal"===t||"vertical"===t||"both"===t?e.style.resize=t:"passive"!==t&&(t="no"),"no"!==t&&(window.getComputedStyle(e).overflow,"visible"===window.getComputedStyle(e).overflow&&(e.style.overflow="hidden"),this.resizeObserver_=new ResizeObserver(this.resizeHandler_),this.resizeObserver_.observe(e)))},L.prototype.resizeElements_=function(){this.graphDiv.style.width=this.width_+"px",this.graphDiv.style.height=this.height_+"px";var t=this.getNumericOption("pixelRatio"),e=t||M.getContextPixelRatio(this.canvas_ctx_),e=(this.canvas_.width=this.width_*e,this.canvas_.height=this.height_*e,this.canvas_.style.width=this.width_+"px",this.canvas_.style.height=this.height_+"px",1!==e&&this.canvas_ctx_.scale(e,e),t||M.getContextPixelRatio(this.hidden_ctx_));this.hidden_.width=this.width_*e,this.hidden_.height=this.height_*e,this.hidden_.style.width=this.width_+"px",this.hidden_.style.height=this.height_+"px",1!==e&&this.hidden_ctx_.scale(e,e)},L.prototype.destroy=function(){this.canvas_ctx_.restore(),this.hidden_ctx_.restore();for(var t=this.plugins_.length-1;0<=t;t--){var e=this.plugins_.pop();e.plugin.destroy&&e.plugin.destroy()}function a(t){for(;t.hasChildNodes();)a(t.firstChild),t.removeChild(t.firstChild)}function n(t){for(var e in t)"object"==typeof t[e]&&(t[e]=null)}this.removeTrackedEvents_(),M.removeEvent(window,"mouseout",this.mouseOutHandler_),M.removeEvent(this.mouseEventElement_,"mousemove",this.mouseMoveHandler_),this.resizeObserver_&&(this.resizeObserver_.disconnect(),this.resizeObserver_=null),M.removeEvent(window,"resize",this.resizeHandler_),this.resizeHandler_=null,a(this.maindiv_);n(this.layout_),n(this.plotter_),n(this)},L.prototype.createPlotKitCanvas_=function(t){var e=M.createCanvas();return e.style.position="absolute",e.style.top=t.style.top,e.style.left=t.style.left,e.width=this.width_,e.height=this.height_,e.style.width=this.width_+"px",e.style.height=this.height_+"px",e},L.prototype.createMouseEventElement_=function(){return this.canvas_},L.prototype.setColors_=function(){for(var t,e,a,n=this.getLabels(),i=n.length-1,r=(this.colors_=[],this.colorsMap_={},this.getNumericOption("colorSaturation")||1),o=this.getNumericOption("colorValue")||.5,s=Math.ceil(i/2),l=this.getOption("colors"),h=this.visibility(),d=0;d=u.length||(o=u[d],M.isValidPoint(o)&&(s=o.canvasy,t>o.canvasx&&1+da[1]&&(a[1]=c),c=h&&null===u&&(u=p),l[p][0]<=d&&(c=p);for(var g=u=null===u?0:u,f=!0;f&&0=a.length?console.warn("Invalid series number in setVisibility: "+i):a[i]=t[i]);else for(i=0;i=a.length?console.warn("Invalid series number in setVisibility: "+i):a[i]=t[i]:t[i]<0||t[i]>=a.length?console.warn("Invalid series number in setVisibility: "+t[i]):a[t[i]]=e;this.predraw_()},L.prototype.size=function(){return{width:this.width_,height:this.height_}},L.prototype.setAnnotations=function(t,e){this.annotations_=t,this.layout_?(this.layout_.setAnnotations(this.annotations_),e||this.predraw_()):console.warn("Tried to setAnnotations before dygraph was ready. Try setting them in a ready() block. See dygraphs.com/tests/annotation.html")},L.prototype.annotations=function(){return this.annotations_},L.prototype.getLabels=function(){var t=this.attr_("labels");return t?t.slice():null},L.prototype.indexFromSetName=function(t){return this.setIndexByName_[t]},L.prototype.getRowForX=function(t){for(var e=0,a=this.numRows()-1;e<=a;){var n=a+e>>1,i=this.getValue(n,0);if(ii.x+i.w||_.canvasyi.y+i.h||(h=6,(l=_.annotation).hasOwnProperty("tickHeight")&&(h=l.tickHeight),(d=document.createElement("div")).style.fontSize=r.getOption("axisLabelFontSize")+"px",g="dygraph-annotation",l.hasOwnProperty("icon")||(g+=" dygraphDefaultAnnotation dygraph-default-annotation"),l.hasOwnProperty("cssClass")&&(g+=" "+l.cssClass),d.className=g,g=l.hasOwnProperty("width")?l.width:16,u=l.hasOwnProperty("height")?l.height:16,l.hasOwnProperty("icon")?((c=document.createElement("img")).src=l.icon,c.width=g,c.height=u,d.appendChild(c)):_.annotation.hasOwnProperty("shortText")&&d.appendChild(document.createTextNode(_.annotation.shortText)),c=_.canvasx-g/2,d.style.left=c+"px",p=0,p=l.attachAtBottom?(f=i.y+i.h-u-h,o[c]?f-=o[c]:o[c]=0,o[c]+=h+u,f):_.canvasy-u-h,d.style.top=p+"px",d.style.width=g+"px",d.style.height=u+"px",d.title=_.annotation.text,d.style.color=r.colorsMap_[_.name],d.style.borderColor=r.colorsMap_[_.name],l.div=d,r.addAndTrackEvent(d,"click",n("clickHandler","annotationClickHandler",_)),r.addAndTrackEvent(d,"mouseover",n("mouseOverHandler","annotationMouseOverHandler",_)),r.addAndTrackEvent(d,"mouseout",n("mouseOutHandler","annotationMouseOutHandler",_)),r.addAndTrackEvent(d,"dblclick",n("dblClickHandler","annotationDblClickHandler",_)),a.appendChild(d),this.annotations_.push(d),(g=t.drawingContext).save(),g.strokeStyle=l.hasOwnProperty("tickColor")?l.tickColor:r.colorsMap_[_.name],g.lineWidth=l.hasOwnProperty("tickWidth")?l.tickWidth:r.getOption("strokeWidth"),g.beginPath(),l.attachAtBottom?(g.moveTo(_.canvasx,f=p+u),g.lineTo(_.canvasx,f+h)):(g.moveTo(_.canvasx,_.canvasy),g.lineTo(_.canvasx,_.canvasy-2-h)),g.closePath(),g.stroke(),g.restore())}},n.prototype.destroy=function(){this.detachLabels()},a.default=n,e.exports=a.default},{}],"dygraphs/src/plugins/axes.js":[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0}),a.default=void 0;var b=function(t,e){if(!e&&t&&t.__esModule)return t;if(null===t||"object"!=typeof t&&"function"!=typeof t)return{default:t};e=o(e);if(e&&e.has(t))return e.get(t);var a,n={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(a in t){var r;"default"!==a&&Object.prototype.hasOwnProperty.call(t,a)&&((r=i?Object.getOwnPropertyDescriptor(t,a):null)&&(r.get||r.set)?Object.defineProperty(n,a,r):n[a]=t[a])}n.default=t,e&&e.set(t,n);return n}(t("../dygraph-utils"));function o(t){var e,a;return"function"!=typeof WeakMap?null:(e=new WeakMap,a=new WeakMap,(o=function(t){return t?a:e})(t))}function n(){this.xlabels_=[],this.ylabels_=[]}n.prototype.toString=function(){return"Axes Plugin"},n.prototype.activate=function(t){return{layout:this.layout,clearChart:this.clearChart,willDrawChart:this.willDrawChart}},n.prototype.layout=function(t){var e,a,n=t.dygraph;n.getOptionForAxis("drawAxis","y")&&(a=n.getOptionForAxis("axisLabelWidth","y")+2*n.getOptionForAxis("axisTickSize","y"),t.reserveSpaceLeft(a)),n.getOptionForAxis("drawAxis","x")&&(e=n.getOption("xAxisHeight")?n.getOption("xAxisHeight"):n.getOptionForAxis("axisLabelFontSize","x")+2*n.getOptionForAxis("axisTickSize","x"),t.reserveSpaceBottom(e)),2==n.numAxes()?n.getOptionForAxis("drawAxis","y2")&&(a=n.getOptionForAxis("axisLabelWidth","y2")+2*n.getOptionForAxis("axisTickSize","y2"),t.reserveSpaceRight(a)):2a&&(t=a-f("axisLabelWidth"),o.style.textAlign="right"),t<0&&(t=0,o.style.textAlign="left"),o.style.left=t+"px",o.style.width=f("axisLabelWidth")+"px",i.appendChild(o),c.xlabels_.push(o))})),e.strokeStyle=v.getOptionForAxis("axisLineColor","x"),e.lineWidth=v.getOptionForAxis("axisLineWidth","x"),e.beginPath(),y=v.getOption("drawAxesAtZero")?(_=v.toPercentYCoord(0,0),m(u.y+(_=1<_||_<0?1:_)*u.h)):m(u.y+u.h),e.moveTo(x(u.x),y),e.lineTo(x(u.x+u.w),y),e.closePath(),e.stroke()),e.restore())},a.default=n,e.exports=a.default},{"../dygraph-utils":"dygraphs/src/dygraph-utils.js"}],"dygraphs/src/plugins/chart-labels.js":[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0}),a.default=void 0;function n(){this.title_div_=null,this.xlabel_div_=null,this.ylabel_div_=null,this.y2label_div_=null}function r(t){var e=document.createElement("div");return e.style.position="absolute",e.style.left=t.x+"px",e.style.top=t.y+"px",e.style.width=t.w+"px",e.style.height=t.h+"px",e}function o(t,e,a,n,i){var r=document.createElement("div");return r.style.position="absolute",r.style.left=1==a?"0px":e.x+"px",r.style.top=e.y+"px",r.style.width=e.w+"px",r.style.height=e.h+"px",r.style.fontSize=t.getOption("yLabelWidth")-2+"px",(t=document.createElement("div")).style.position="absolute",t.style.width=e.h+"px",t.style.height=e.w+"px",t.style.top=e.h/2-e.w/2+"px",t.style.left=e.w/2-e.h/2+"px",t.className="dygraph-label-rotate-"+(1==a?"right":"left"),(e=document.createElement("div")).className=n,e.innerHTML=i,t.appendChild(e),r.appendChild(t),r}n.prototype.toString=function(){return"ChartLabels Plugin"},n.prototype.activate=function(t){return{layout:this.layout,didDrawChart:this.didDrawChart}},n.prototype.detachLabels_=function(){for(var t=[this.title_div_,this.xlabel_div_,this.ylabel_div_,this.y2label_div_],e=0;ei.w&&(a=a-2*o-r-(l-i.x)),this.legend_div_.style.left=l+a+"px",this.legend_div_.style.top=n+"px"):"onmouseover"===h&&this.is_generated_div_&&(i=t.dygraph.plotter_.area,r=this.legend_div_.offsetWidth,this.legend_div_.style.left=i.x+i.w-r-1+"px",this.legend_div_.style.top=i.y+"px"))},m.prototype.deselect=function(t){"always"!==t.dygraph.getOption("legend")&&(this.legend_div_.style.display="none"),a=this.legend_div_,(n=document.createElement("span")).setAttribute("style","margin: 0; padding: 0 0 0 1em; border: 0;"),a.appendChild(n),e=n.offsetWidth,a.removeChild(n);var e,a=e,n=(this.one_em_width_=a,m.generateLegendHTML(t.dygraph,void 0,void 0,a,null));n instanceof Node&&n.nodeType===Node.DOCUMENT_FRAGMENT_NODE?(this.legend_div_.innerHTML="",this.legend_div_.appendChild(n)):this.legend_div_.innerHTML=n},m.prototype.didDrawChart=function(t){this.deselect(t)},m.prototype.predraw=function(t){var e;this.is_generated_div_&&(t.dygraph.graphDiv.appendChild(this.legend_div_),t=t.dygraph.plotter_.area,e=this.legend_div_.offsetWidth,this.legend_div_.style.left=t.x+t.w-e-1+"px",this.legend_div_.style.top=t.y+"px")},m.prototype.destroy=function(){this.legend_div_=null},m.generateLegendHTML=function(t,e,a,n,i){var r={dygraph:t,x:e,i:i,series:[]},o={},s=t.getLabels();if(s)for(var l=1;l
');var n,i,r,o,s,l=0,h=0,d=[];for(n=0;n<=t.length;n++)l+=t[n%t.length];if(1<(s=Math.floor(a/(l-t[0])))){for(n=0;n
');return u}(t.getOption("strokePattern",s[l]),h.color,n),label:s[l],labelHTML:s[l].replace(/&/g,"&").replace(/"/g,""").replace(//g,">"),isVisible:h.visible,color:h.color};r.series.push(d),o[s[l]]=d}if(void 0!==e){for(var u=t.optionsViewForAxis_("x"),c=u("valueFormatter"),p=(r.xHTML=c.call(t,e,u,s[0],t,i,0),[]),g=t.numAxes(),l=0;l":" "),n+="").concat(r.dashHTML," ").concat(r.labelHTML,""))}else{n=t.xHTML+":";for(var r,o,i=0;i"),o=r.isHighlighted?' class="highlight"':"",n+=" ").concat(r.labelHTML,": ").concat(r.yHTML,""))}return n},a.default=m,e.exports=a.default},{"../dygraph-utils":"dygraphs/src/dygraph-utils.js"}],"dygraphs/src/plugins/range-selector.js":[function(t,e,a){"use strict";Object.defineProperty(a,"__esModule",{value:!0}),a.default=void 0;var O=function(t,e){if(!e&&t&&t.__esModule)return t;if(null===t||"object"!=typeof t&&"function"!=typeof t)return{default:t};e=o(e);if(e&&e.has(t))return e.get(t);var a,n={},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(a in t){var r;"default"!==a&&Object.prototype.hasOwnProperty.call(t,a)&&((r=i?Object.getOwnPropertyDescriptor(t,a):null)&&(r.get||r.set)?Object.defineProperty(n,a,r):n[a]=t[a])}n.default=t,e&&e.set(t,n);return n}(t("../dygraph-utils")),A=n(t("../dygraph-interaction-model")),P=n(t("../iframe-tarp"));function n(t){return t&&t.__esModule?t:{default:t}}function o(t){var e,a;return"function"!=typeof WeakMap?null:(e=new WeakMap,a=new WeakMap,(o=function(t){return t?a:e})(t))}function i(){this.hasTouchInterface_="undefined"!=typeof TouchEvent,this.isMobileDevice_=/mobile|android/gi.test(navigator.appVersion),this.interfaceCreated_=!1}i.prototype.toString=function(){return"RangeSelector Plugin"},i.prototype.activate=function(t){return this.dygraph_=t,this.getOption_("showRangeSelector")&&this.createInterface_(),{layout:this.reserveSpace_,predraw:this.renderStaticLayer_,didDrawChart:this.renderInteractiveLayer_}},i.prototype.destroy=function(){this.bgcanvas_=null,this.fgcanvas_=null,this.leftZoomHandle_=null,this.rightZoomHandle_=null},i.prototype.getOption_=function(t,e){return this.dygraph_.getOption(t,e)},i.prototype.setDefaultOption_=function(t,e){this.dygraph_.attrs_[t]=e},i.prototype.createInterface_=function(){this.createCanvases_(),this.createZoomHandles_(),this.initInteraction_(),this.getOption_("animatedZooms")&&(console.warn("Animated zooms and range selector are not compatible; disabling animatedZooms."),this.dygraph_.updateOptions({animatedZooms:!1},!0)),this.interfaceCreated_=!0,this.addToGraph_()},i.prototype.addToGraph_=function(){var t=this.graphDiv_=this.dygraph_.graphDiv;t.appendChild(this.bgcanvas_),t.appendChild(this.fgcanvas_),t.appendChild(this.leftZoomHandle_),t.appendChild(this.rightZoomHandle_)},i.prototype.removeFromGraph_=function(){var t=this.graphDiv_;t.removeChild(this.bgcanvas_),t.removeChild(this.fgcanvas_),t.removeChild(this.leftZoomHandle_),t.removeChild(this.rightZoomHandle_),this.graphDiv_=null},i.prototype.reserveSpace_=function(t){this.getOption_("showRangeSelector")&&t.reserveSpaceBottom(this.getOption_("rangeSelectorHeight")+4)},i.prototype.renderStaticLayer_=function(){this.updateVisibility_()&&(this.resize_(),this.drawStaticLayer_())},i.prototype.renderInteractiveLayer_=function(){this.updateVisibility_()&&!this.isChangingRange_&&(this.placeZoomHandles_(),this.drawInteractiveLayer_())},i.prototype.updateVisibility_=function(){var t,e=this.getOption_("showRangeSelector");return e?this.interfaceCreated_?this.graphDiv_&&this.graphDiv_.parentNode||this.addToGraph_():this.createInterface_():this.graphDiv_&&(this.removeFromGraph_(),t=this.dygraph_,setTimeout(function(){t.width_=0,t.resize()},1)),e},i.prototype.resize_=function(){function t(t,e,a,n){n=n||O.getContextPixelRatio(e);t.style.top=a.y+"px",t.style.left=a.x+"px",t.width=a.w*n,t.height=a.h*n,t.style.width=a.w+"px",t.style.height=a.h+"px",1!=n&&e.scale(n,n)}var e=this.dygraph_.layout_.getPlotArea(),a=0,a=(this.dygraph_.getOptionForAxis("drawAxis","x")&&(a=this.getOption_("xAxisHeight")||this.getOption_("axisLabelFontSize")+2*this.getOption_("axisTickSize")),this.canvasRect_={x:e.x,y:e.y+e.h+a+4,w:e.w,h:this.getOption_("rangeSelectorHeight")},this.dygraph_.getNumericOption("pixelRatio"));t(this.bgcanvas_,this.bgcanvas_ctx_,this.canvasRect_,a),t(this.fgcanvas_,this.fgcanvas_ctx_,this.canvasRect_,a)},i.prototype.createCanvases_=function(){this.bgcanvas_=O.createCanvas(),this.bgcanvas_.className="dygraph-rangesel-bgcanvas",this.bgcanvas_.style.position="absolute",this.bgcanvas_.style.zIndex=9,this.bgcanvas_ctx_=O.getContext(this.bgcanvas_),this.fgcanvas_=O.createCanvas(),this.fgcanvas_.className="dygraph-rangesel-fgcanvas",this.fgcanvas_.style.position="absolute",this.fgcanvas_.style.zIndex=9,this.fgcanvas_.style.cursor="default",this.fgcanvas_ctx_=O.getContext(this.fgcanvas_)},i.prototype.createZoomHandles_=function(){var t=new Image;t.className="dygraph-rangesel-zoomhandle",t.style.position="absolute",t.style.zIndex=10,t.style.visibility="hidden",t.style.cursor="col-resize",t.width=9,t.height=16,t.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAzwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7sqSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=",this.isMobileDevice_&&(t.width*=2,t.height*=2),this.leftZoomHandle_=t,this.rightZoomHandle_=t.cloneNode(!1)},i.prototype.initInteraction_=function(){var i=this,e=document,r=0,n=null,o=!1,s=!1,l=!this.isMobileDevice_,a=new P.default,h=function(t){var e=i.dygraph_.xAxisExtremes(),a=(e[1]-e[0])/i.canvasRect_.w;return[e[0]+(t.leftHandlePos-i.canvasRect_.x)*a,e[0]+(t.rightHandlePos-i.canvasRect_.x)*a]},d=function(t){return O.cancelEvent(t),o=!0,r=t.clientX,n=t.target||t.srcElement,"mousedown"!==t.type&&"dragstart"!==t.type||(O.addEvent(e,"mousemove",u),O.addEvent(e,"mouseup",c)),i.fgcanvas_.style.cursor="col-resize",a.cover(),!0},u=function(t){if(!o)return!1;O.cancelEvent(t);var e,a=t.clientX-r;return Math.abs(a)<4||(r=t.clientX,t=i.getZoomHandleStatus_(),e=n==i.leftZoomHandle_?(e=t.leftHandlePos+a,e=Math.min(e,t.rightHandlePos-n.width-3),Math.max(e,i.canvasRect_.x)):(e=t.rightHandlePos+a,e=Math.min(e,i.canvasRect_.x+i.canvasRect_.w),Math.max(e,t.leftHandlePos+n.width+3)),a=n.width/2,n.style.left=e-a+"px",i.drawInteractiveLayer_(),l&&p()),!0},c=function(t){return!!o&&(o=!1,a.uncover(),O.removeEvent(e,"mousemove",u),O.removeEvent(e,"mouseup",c),i.fgcanvas_.style.cursor="default",l||p(),!0)},p=function(){try{var t,e=i.getZoomHandleStatus_();i.isChangingRange_=!0,e.isZoomed?(t=h(e),i.dygraph_.doZoomXDates_(t[0],t[1])):i.dygraph_.resetZoom()}finally{i.isChangingRange_=!1}},g=function(t){var e=i.leftZoomHandle_.getBoundingClientRect(),a=e.left+e.width/2,e=(e=i.rightZoomHandle_.getBoundingClientRect()).left+e.width/2;return t.clientX>a&&t.clientX=i.canvasRect_.x+i.canvasRect_.w?e=(t=i.canvasRect_.x+i.canvasRect_.w)-a:(e+=n,t+=n),a=i.leftZoomHandle_.width/2,i.leftZoomHandle_.style.left=e-a+"px",i.rightZoomHandle_.style.left=t-a+"px",i.drawInteractiveLayer_(),l&&v()),!0},y=function(t){return!!s&&(s=!1,O.removeEvent(e,"mousemove",_),O.removeEvent(e,"mouseup",y),l||v(),!0)},v=function(){try{i.isChangingRange_=!0,i.dygraph_.dateWindow_=h(i.getZoomHandleStatus_()),i.dygraph_.drawGraph_(!1)}finally{i.isChangingRange_=!1}},t=function(t){o||s||(t=g(t)?"move":"default")!=i.fgcanvas_.style.cursor&&(i.fgcanvas_.style.cursor=t)},x=function(t){"touchstart"==t.type&&1==t.targetTouches.length?d(t.targetTouches[0])&&O.cancelEvent(t):"touchmove"==t.type&&1==t.targetTouches.length?u(t.targetTouches[0])&&O.cancelEvent(t):c(t)},m=function(t){"touchstart"==t.type&&1==t.targetTouches.length?f(t.targetTouches[0])&&O.cancelEvent(t):"touchmove"==t.type&&1==t.targetTouches.length?_(t.targetTouches[0])&&O.cancelEvent(t):y(t)},b=function(t,e){for(var a=["touchstart","touchend","touchmove","touchcancel"],n=0;nthis.canvasRect_.x||t+1t.concat(e,e+"-start",e+"-end")),[]),p={left:"right",right:"left",bottom:"top",top:"bottom"};function h(t){return t.replace(/left|right|bottom|top/g,(t=>p[t]))}function y(t,o,r){void 0===r&&(r=!1);const a=e(t),l=i(t),s=n(l);let c="x"===l?a===(r?"end":"start")?"right":"left":"start"===a?"bottom":"top";return o.reference[s]>o.floating[s]&&(c=h(c)),{main:c,cross:h(c)}}const x={start:"end",end:"start"};function w(t){return t.replace(/start|end/g,(t=>x[t]))}function v(t,e){return{top:t.top-e.height,right:t.right-e.width,bottom:t.bottom-e.height,left:t.left-e.width}}function b(t){return d.some((e=>t[e]>=0))}function A(t){const e=f(...t.map((t=>t.left))),n=f(...t.map((t=>t.top)));return{x:e,y:n,width:u(...t.map((t=>t.right)))-e,height:u(...t.map((t=>t.bottom)))-n}}function R(t){return"x"===t?"y":"x"}t.arrow=t=>({name:"arrow",options:t,async fn(o){const{x:r,y:s,placement:c,rects:u,platform:d,elements:g}=o,{element:p,padding:h=0}=a(t,o)||{};if(null==p)return{};const y=l(h),x={x:r,y:s},w=i(c),v=n(w),b=await d.getDimensions(p),A="y"===w,R=A?"top":"left",P=A?"bottom":"right",T=A?"clientHeight":"clientWidth",E=u.reference[v]+u.reference[w]-x[w]-u.floating[v],D=x[w]-u.reference[w],O=await(null==d.getOffsetParent?void 0:d.getOffsetParent(p));let L=O?O[T]:0;L&&await(null==d.isElement?void 0:d.isElement(O))||(L=g.floating[T]||u.floating[v]);const k=E/2-D/2,C=L/2-b[v]/2-1,B=f(y[R],C),H=f(y[P],C),S=B,j=L-b[v]-H,F=L/2-b[v]/2+k,M=m(S,F,j),z=null!=e(c)&&F!=M&&u.reference[v]/2-(Fe(n)===t)),...i.filter((n=>e(n)!==t))]:i.filter((t=>o(t)===t))).filter((o=>!t||e(o)===t||!!n&&w(o)!==o))}(h||null,v,x):x,R=await c(n,b),P=(null==(i=f.autoPlacement)?void 0:i.index)||0,T=A[P];if(null==T)return{};const{main:E,cross:D}=y(T,s,await(null==m.isRTL?void 0:m.isRTL(d.floating)));if(u!==T)return{reset:{placement:A[0]}};const O=[R[o(T)],R[E],R[D]],L=[...(null==(r=f.autoPlacement)?void 0:r.overflows)||[],{placement:T,overflows:O}],k=A[P+1];if(k)return{data:{index:P+1,overflows:L},reset:{placement:k}};const C=L.map((t=>{const n=e(t.placement);return[t.placement,n&&p?t.overflows.slice(0,2).reduce(((t,e)=>t+e),0):t.overflows[0],t.overflows]})).sort(((t,e)=>t[1]-e[1])),B=(null==(l=C.filter((t=>t[2].slice(0,e(t[0])?2:3).every((t=>t<=0))))[0])?void 0:l[0])||C[0][0];return B!==u?{data:{index:P+1,overflows:L},reset:{placement:B}}:{}}}},t.computePosition=async(t,e,n)=>{const{placement:o="bottom",strategy:i="absolute",middleware:a=[],platform:l}=n,s=a.filter(Boolean),c=await(null==l.isRTL?void 0:l.isRTL(e));let f=await l.getElementRects({reference:t,floating:e,strategy:i}),{x:u,y:m}=r(f,o,c),d=o,g={},p=0;for(let n=0;nt+"-"+a)),n&&(l=l.concat(l.map(w)))),l}(f,b,v,T));const D=[f,...E],O=await c(n,A),L=[];let k=(null==(i=l.flip)?void 0:i.overflows)||[];if(d&&L.push(O[R]),g){const{main:t,cross:e}=y(r,s,T);L.push(O[t],O[e])}if(k=[...k,{placement:r,overflows:L}],!L.every((t=>t<=0))){var C,B;const t=((null==(C=l.flip)?void 0:C.index)||0)+1,e=D[t];if(e)return{data:{index:t,overflows:k},reset:{placement:e}};let n=null==(B=k.filter((t=>t.overflows[0]<=0)).sort(((t,e)=>t.overflows[1]-e.overflows[1]))[0])?void 0:B.placement;if(!n)switch(x){case"bestFit":{var H;const t=null==(H=k.map((t=>[t.placement,t.overflows.filter((t=>t>0)).reduce(((t,e)=>t+e),0)])).sort(((t,e)=>t[1]-e[1]))[0])?void 0:H[0];t&&(n=t);break}case"initialPlacement":n=f}if(r!==n)return{reset:{placement:n}}}return{}}}},t.hide=function(t){return void 0===t&&(t={}),{name:"hide",options:t,async fn(e){const{rects:n}=e,{strategy:o="referenceHidden",...i}=a(t,e);switch(o){case"referenceHidden":{const t=v(await c(e,{...i,elementContext:"reference"}),n.reference);return{data:{referenceHiddenOffsets:t,referenceHidden:b(t)}}}case"escaped":{const t=v(await c(e,{...i,altBoundary:!0}),n.floating);return{data:{escapedOffsets:t,escaped:b(t)}}}default:return{}}}}},t.inline=function(t){return void 0===t&&(t={}),{name:"inline",options:t,async fn(e){const{placement:n,elements:r,rects:c,platform:m,strategy:d}=e,{padding:g=2,x:p,y:h}=a(t,e),y=Array.from(await(null==m.getClientRects?void 0:m.getClientRects(r.reference))||[]),x=function(t){const e=t.slice().sort(((t,e)=>t.y-e.y)),n=[];let o=null;for(let t=0;to.height/2?n.push([i]):n[n.length-1].push(i),o=i}return n.map((t=>s(A(t))))}(y),w=s(A(y)),v=l(g);const b=await m.getElementRects({reference:{getBoundingClientRect:function(){if(2===x.length&&x[0].left>x[1].right&&null!=p&&null!=h)return x.find((t=>p>t.left-v.left&&pt.top-v.top&&h=2){if("x"===i(n)){const t=x[0],e=x[x.length-1],i="top"===o(n),r=t.top,a=e.bottom,l=i?t.left:e.left,s=i?t.right:e.right;return{top:r,bottom:a,left:l,right:s,width:s-l,height:a-r,x:l,y:r}}const t="left"===o(n),e=u(...x.map((t=>t.right))),r=f(...x.map((t=>t.left))),a=x.filter((n=>t?n.left===r:n.right===e)),l=a[0].top,s=a[a.length-1].bottom;return{top:l,bottom:s,left:r,right:e,width:e-r,height:s-l,x:r,y:l}}return w}},floating:r.floating,strategy:d});return c.reference.x!==b.reference.x||c.reference.y!==b.reference.y||c.reference.width!==b.reference.width||c.reference.height!==b.reference.height?{reset:{rects:b}}:{}}}},t.limitShift=function(t){return void 0===t&&(t={}),{options:t,fn(e){const{x:n,y:r,placement:l,rects:s,middlewareData:c}=e,{offset:f=0,mainAxis:u=!0,crossAxis:m=!0}=a(t,e),d={x:n,y:r},g=i(l),p=R(g);let h=d[g],y=d[p];const x=a(f,e),w="number"==typeof x?{mainAxis:x,crossAxis:0}:{mainAxis:0,crossAxis:0,...x};if(u){const t="y"===g?"height":"width",e=s.reference[g]-s.floating[t]+w.mainAxis,n=s.reference[g]+s.reference[t]-w.mainAxis;hn&&(h=n)}if(m){var v,b;const t="y"===g?"width":"height",e=["top","left"].includes(o(l)),n=s.reference[p]-s.floating[t]+(e&&(null==(v=c.offset)?void 0:v[p])||0)+(e?0:w.crossAxis),i=s.reference[p]+s.reference[t]+(e?0:(null==(b=c.offset)?void 0:b[p])||0)-(e?w.crossAxis:0);yi&&(y=i)}return{[g]:h,[p]:y}}}},t.offset=function(t){return void 0===t&&(t=0),{name:"offset",options:t,async fn(n){const{x:r,y:l}=n,s=await async function(t,n){const{placement:r,platform:l,elements:s}=t,c=await(null==l.isRTL?void 0:l.isRTL(s.floating)),f=o(r),u=e(r),m="x"===i(r),d=["left","top"].includes(f)?-1:1,g=c&&m?-1:1,p=a(n,t);let{mainAxis:h,crossAxis:y,alignmentAxis:x}="number"==typeof p?{mainAxis:p,crossAxis:0,alignmentAxis:null}:{mainAxis:0,crossAxis:0,alignmentAxis:null,...p};return u&&"number"==typeof x&&(y="end"===u?-1*x:x),m?{x:y*g,y:h*d}:{x:h*d,y:y*g}}(n,t);return{x:r+s.x,y:l+s.y,data:s}}}},t.rectToClientRect=s,t.shift=function(t){return void 0===t&&(t={}),{name:"shift",options:t,async fn(e){const{x:n,y:r,placement:l}=e,{mainAxis:s=!0,crossAxis:f=!1,limiter:u={fn:t=>{let{x:e,y:n}=t;return{x:e,y:n}}},...d}=a(t,e),g={x:n,y:r},p=await c(e,d),h=i(o(l)),y=R(h);let x=g[h],w=g[y];if(s){const t="y"===h?"bottom":"right";x=m(x+p["y"===h?"top":"left"],x,x-p[t])}if(f){const t="y"===y?"bottom":"right";w=m(w+p["y"===y?"top":"left"],w,w-p[t])}const v=u.fn({...e,[h]:x,[y]:w});return{...v,data:{x:v.x-n,y:v.y-r}}}}},t.size=function(t){return void 0===t&&(t={}),{name:"size",options:t,async fn(n){const{placement:r,rects:l,platform:s,elements:m}=n,{apply:d=(()=>{}),...g}=a(t,n),p=await c(n,g),h=o(r),y=e(r),x="x"===i(r),{width:w,height:v}=l.floating;let b,A;"top"===h||"bottom"===h?(b=h,A=y===(await(null==s.isRTL?void 0:s.isRTL(m.floating))?"start":"end")?"left":"right"):(A=h,b="end"===y?"top":"bottom");const R=v-p[b],P=w-p[A],T=!n.middlewareData.shift;let E=R,D=P;if(x){const t=w-p.left-p.right;D=y||T?f(P,t):t}else{const t=v-p.top-p.bottom;E=y||T?f(R,t):t}if(T&&!y){const t=u(p.left,0),e=u(p.right,0),n=u(p.top,0),o=u(p.bottom,0);x?D=w-2*(0!==t||0!==e?t+e:u(p.left,p.right)):E=v-2*(0!==n||0!==o?n+o:u(p.top,p.bottom))}await d({...n,availableWidth:D,availableHeight:E});const O=await s.getDimensions(m.floating);return w!==O.width||v!==O.height?{reset:{rects:!0}}:{}}}},Object.defineProperty(t,"__esModule",{value:!0})})); diff --git a/static/frontend/floating-ui.dom.umd.min.js b/static/frontend/floating-ui.dom.umd.min.js new file mode 100644 index 00000000..8d9058b8 --- /dev/null +++ b/static/frontend/floating-ui.dom.umd.min.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@floating-ui/core")):"function"==typeof define&&define.amd?define(["exports","@floating-ui/core"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).FloatingUIDOM={},t.FloatingUICore)}(this,(function(t,e){"use strict";function n(t){var e;return(null==t||null==(e=t.ownerDocument)?void 0:e.defaultView)||window}function o(t){return n(t).getComputedStyle(t)}function i(t){return t instanceof n(t).Node}function r(t){return i(t)?(t.nodeName||"").toLowerCase():"#document"}function c(t){return t instanceof HTMLElement||t instanceof n(t).HTMLElement}function l(t){return"undefined"!=typeof ShadowRoot&&(t instanceof n(t).ShadowRoot||t instanceof ShadowRoot)}function f(t){const{overflow:e,overflowX:n,overflowY:i,display:r}=o(t);return/auto|scroll|overlay|hidden|clip/.test(e+i+n)&&!["inline","contents"].includes(r)}function s(t){return["table","td","th"].includes(r(t))}function u(t){const e=a(),n=o(t);return"none"!==n.transform||"none"!==n.perspective||!!n.containerType&&"normal"!==n.containerType||!e&&!!n.backdropFilter&&"none"!==n.backdropFilter||!e&&!!n.filter&&"none"!==n.filter||["transform","perspective","filter"].some((t=>(n.willChange||"").includes(t)))||["paint","layout","strict","content"].some((t=>(n.contain||"").includes(t)))}function a(){return!("undefined"==typeof CSS||!CSS.supports)&&CSS.supports("-webkit-backdrop-filter","none")}function d(t){return["html","body","#document"].includes(r(t))}const h=Math.min,p=Math.max,m=Math.round,g=Math.floor,y=t=>({x:t,y:t});function w(t){const e=o(t);let n=parseFloat(e.width)||0,i=parseFloat(e.height)||0;const r=c(t),l=r?t.offsetWidth:n,f=r?t.offsetHeight:i,s=m(n)!==l||m(i)!==f;return s&&(n=l,i=f),{width:n,height:i,$:s}}function b(t){return t instanceof Element||t instanceof n(t).Element}function x(t){return b(t)?t:t.contextElement}function v(t){const e=x(t);if(!c(e))return y(1);const n=e.getBoundingClientRect(),{width:o,height:i,$:r}=w(e);let l=(r?m(n.width):n.width)/o,f=(r?m(n.height):n.height)/i;return l&&Number.isFinite(l)||(l=1),f&&Number.isFinite(f)||(f=1),{x:l,y:f}}const T=y(0);function L(t){const e=n(t);return a()&&e.visualViewport?{x:e.visualViewport.offsetLeft,y:e.visualViewport.offsetTop}:T}function O(t,o,i,r){void 0===o&&(o=!1),void 0===i&&(i=!1);const c=t.getBoundingClientRect(),l=x(t);let f=y(1);o&&(r?b(r)&&(f=v(r)):f=v(t));const s=function(t,e,o){return void 0===e&&(e=!1),!(!o||e&&o!==n(t))&&e}(l,i,r)?L(l):y(0);let u=(c.left+s.x)/f.x,a=(c.top+s.y)/f.y,d=c.width/f.x,h=c.height/f.y;if(l){const t=n(l),e=r&&b(r)?n(r):r;let o=t.frameElement;for(;o&&r&&e!==t;){const t=v(o),e=o.getBoundingClientRect(),i=getComputedStyle(o),r=e.left+(o.clientLeft+parseFloat(i.paddingLeft))*t.x,c=e.top+(o.clientTop+parseFloat(i.paddingTop))*t.y;u*=t.x,a*=t.y,d*=t.x,h*=t.y,u+=r,a+=c,o=n(o).frameElement}}return e.rectToClientRect({width:d,height:h,x:u,y:a})}function R(t){return b(t)?{scrollLeft:t.scrollLeft,scrollTop:t.scrollTop}:{scrollLeft:t.pageXOffset,scrollTop:t.pageYOffset}}function P(t){var e;return null==(e=(i(t)?t.ownerDocument:t.document)||window.document)?void 0:e.documentElement}function C(t){return O(P(t)).left+R(t).scrollLeft}function S(t){if("html"===r(t))return t;const e=t.assignedSlot||t.parentNode||l(t)&&t.host||P(t);return l(e)?e.host:e}function E(t){const e=S(t);return d(e)?t.ownerDocument?t.ownerDocument.body:t.body:c(e)&&f(e)?e:E(e)}function F(t,e){var o;void 0===e&&(e=[]);const i=E(t),r=i===(null==(o=t.ownerDocument)?void 0:o.body),c=n(i);return r?e.concat(c,c.visualViewport||[],f(i)?i:[]):e.concat(i,F(i))}function j(t,i,r){let l;if("viewport"===i)l=function(t,e){const o=n(t),i=P(t),r=o.visualViewport;let c=i.clientWidth,l=i.clientHeight,f=0,s=0;if(r){c=r.width,l=r.height;const t=a();(!t||t&&"fixed"===e)&&(f=r.offsetLeft,s=r.offsetTop)}return{width:c,height:l,x:f,y:s}}(t,r);else if("document"===i)l=function(t){const e=P(t),n=R(t),i=t.ownerDocument.body,r=p(e.scrollWidth,e.clientWidth,i.scrollWidth,i.clientWidth),c=p(e.scrollHeight,e.clientHeight,i.scrollHeight,i.clientHeight);let l=-n.scrollLeft+C(t);const f=-n.scrollTop;return"rtl"===o(i).direction&&(l+=p(e.clientWidth,i.clientWidth)-r),{width:r,height:c,x:l,y:f}}(P(t));else if(b(i))l=function(t,e){const n=O(t,!0,"fixed"===e),o=n.top+t.clientTop,i=n.left+t.clientLeft,r=c(t)?v(t):y(1);return{width:t.clientWidth*r.x,height:t.clientHeight*r.y,x:i*r.x,y:o*r.y}}(i,r);else{const e=L(t);l={...i,x:i.x-e.x,y:i.y-e.y}}return e.rectToClientRect(l)}function D(t,e){const n=S(t);return!(n===e||!b(n)||d(n))&&("fixed"===o(n).position||D(n,e))}function H(t,e,n){const o=c(e),i=P(e),l="fixed"===n,s=O(t,!0,l,e);let u={scrollLeft:0,scrollTop:0};const a=y(0);if(o||!o&&!l)if(("body"!==r(e)||f(i))&&(u=R(e)),c(e)){const t=O(e,!0,l,e);a.x=t.x+e.clientLeft,a.y=t.y+e.clientTop}else i&&(a.x=C(i));return{x:s.left+u.scrollLeft-a.x,y:s.top+u.scrollTop-a.y,width:s.width,height:s.height}}function W(t,e){return c(t)&&"fixed"!==o(t).position?e?e(t):t.offsetParent:null}function M(t,e){const i=n(t);if(!c(t))return i;let l=W(t,e);for(;l&&s(l)&&"static"===o(l).position;)l=W(l,e);return l&&("html"===r(l)||"body"===r(l)&&"static"===o(l).position&&!u(l))?i:l||function(t){let e=S(t);for(;c(e)&&!d(e);){if(u(e))return e;e=S(e)}return null}(t)||i}const z={convertOffsetParentRelativeRectToViewportRelativeRect:function(t){let{rect:e,offsetParent:n,strategy:o}=t;const i=c(n),l=P(n);if(n===l)return e;let s={scrollLeft:0,scrollTop:0},u=y(1);const a=y(0);if((i||!i&&"fixed"!==o)&&(("body"!==r(n)||f(l))&&(s=R(n)),c(n))){const t=O(n);u=v(n),a.x=t.x+n.clientLeft,a.y=t.y+n.clientTop}return{width:e.width*u.x,height:e.height*u.y,x:e.x*u.x-s.scrollLeft*u.x+a.x,y:e.y*u.y-s.scrollTop*u.y+a.y}},getDocumentElement:P,getClippingRect:function(t){let{element:e,boundary:n,rootBoundary:i,strategy:c}=t;const l=[..."clippingAncestors"===n?function(t,e){const n=e.get(t);if(n)return n;let i=F(t).filter((t=>b(t)&&"body"!==r(t))),c=null;const l="fixed"===o(t).position;let s=l?S(t):t;for(;b(s)&&!d(s);){const e=o(s),n=u(s);n||"fixed"!==e.position||(c=null),(l?!n&&!c:!n&&"static"===e.position&&c&&["absolute","fixed"].includes(c.position)||f(s)&&!n&&D(t,s))?i=i.filter((t=>t!==s)):c=e,s=S(s)}return e.set(t,i),i}(e,this._c):[].concat(n),i],s=l[0],a=l.reduce(((t,n)=>{const o=j(e,n,c);return t.top=p(o.top,t.top),t.right=h(o.right,t.right),t.bottom=h(o.bottom,t.bottom),t.left=p(o.left,t.left),t}),j(e,s,c));return{width:a.right-a.left,height:a.bottom-a.top,x:a.left,y:a.top}},getOffsetParent:M,getElementRects:async function(t){let{reference:e,floating:n,strategy:o}=t;const i=this.getOffsetParent||M,r=this.getDimensions;return{reference:H(e,await i(n),o),floating:{x:0,y:0,...await r(n)}}},getClientRects:function(t){return Array.from(t.getClientRects())},getDimensions:function(t){return w(t)},getScale:v,isElement:b,isRTL:function(t){return"rtl"===getComputedStyle(t).direction}};Object.defineProperty(t,"arrow",{enumerable:!0,get:function(){return e.arrow}}),Object.defineProperty(t,"autoPlacement",{enumerable:!0,get:function(){return e.autoPlacement}}),Object.defineProperty(t,"detectOverflow",{enumerable:!0,get:function(){return e.detectOverflow}}),Object.defineProperty(t,"flip",{enumerable:!0,get:function(){return e.flip}}),Object.defineProperty(t,"hide",{enumerable:!0,get:function(){return e.hide}}),Object.defineProperty(t,"inline",{enumerable:!0,get:function(){return e.inline}}),Object.defineProperty(t,"limitShift",{enumerable:!0,get:function(){return e.limitShift}}),Object.defineProperty(t,"offset",{enumerable:!0,get:function(){return e.offset}}),Object.defineProperty(t,"shift",{enumerable:!0,get:function(){return e.shift}}),Object.defineProperty(t,"size",{enumerable:!0,get:function(){return e.size}}),t.autoUpdate=function(t,e,n,o){void 0===o&&(o={});const{ancestorScroll:i=!0,ancestorResize:r=!0,elementResize:c="function"==typeof ResizeObserver,layoutShift:l="function"==typeof IntersectionObserver,animationFrame:f=!1}=o,s=x(t),u=i||r?[...s?F(s):[],...F(e)]:[];u.forEach((t=>{i&&t.addEventListener("scroll",n,{passive:!0}),r&&t.addEventListener("resize",n)}));const a=s&&l?function(t,e){let n,o=null;const i=P(t);function r(){clearTimeout(n),o&&o.disconnect(),o=null}return function c(l,f){void 0===l&&(l=!1),void 0===f&&(f=1),r();const{left:s,top:u,width:a,height:d}=t.getBoundingClientRect();if(l||e(),!a||!d)return;const m={rootMargin:-g(u)+"px "+-g(i.clientWidth-(s+a))+"px "+-g(i.clientHeight-(u+d))+"px "+-g(s)+"px",threshold:p(0,h(1,f))||1};let y=!0;function w(t){const e=t[0].intersectionRatio;if(e!==f){if(!y)return c();e?c(!1,e):n=setTimeout((()=>{c(!1,1e-7)}),100)}y=!1}try{o=new IntersectionObserver(w,{...m,root:i.ownerDocument})}catch(t){o=new IntersectionObserver(w,m)}o.observe(t)}(!0),r}(s,n):null;let d,m=-1,y=null;c&&(y=new ResizeObserver((t=>{let[o]=t;o&&o.target===s&&y&&(y.unobserve(e),cancelAnimationFrame(m),m=requestAnimationFrame((()=>{y&&y.observe(e)}))),n()})),s&&!f&&y.observe(s),y.observe(e));let w=f?O(t):null;return f&&function e(){const o=O(t);!w||o.x===w.x&&o.y===w.y&&o.width===w.width&&o.height===w.height||n();w=o,d=requestAnimationFrame(e)}(),n(),()=>{u.forEach((t=>{i&&t.removeEventListener("scroll",n),r&&t.removeEventListener("resize",n)})),a&&a(),y&&y.disconnect(),y=null,f&&cancelAnimationFrame(d)}},t.computePosition=(t,n,o)=>{const i=new Map,r={platform:z,...o},c={...r.platform,_c:i};return e.computePosition(t,n,{...r,platform:c})},t.getOverflowAncestors=F,t.platform=z})); diff --git a/static/frontend/form_style.css b/static/frontend/form_style.css new file mode 100644 index 00000000..20e92a5d --- /dev/null +++ b/static/frontend/form_style.css @@ -0,0 +1,61 @@ +.solv_select { + min-width: 8em; +} +.wide_select, [name=calc_software], [name=calc_type], [name=calc_theory_level], [name=calc_se_method], [name=calc_xtb_method], [name=calc_driver] { + width: 100%; +} +.field { + margin-top: 0.5em; +} +.pick_bs_div, #edit_structure_modal { + display: none; + position: fixed; + padding-top: 50px; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +} + +.button { + margin-top: 5px; + margin-bottom: 5px; +} +.constraint_btn { + margin-top: 0; + margin-bottom: 0; + width: 40px; + height: 40px; +} +#submit_button { + width: 10em; + margin-top: 1em; +} +#submit_field { + padding-top: 2em; +} + +#calc_charge, +#calc_multiplicity { + width: 50px; +} +#form_error_msg { + font-weight: bold; + color: red; +} +.file-name { + max-width: 100%; + width: 100%; +} +.file-label { + width: 100%; +} +#calc_combine_files { + margin: auto 0; +} +.check_msg { + color: red; + font-size: 10pt; + padding-left: 5px; +} diff --git a/static/frontend/jquery-2.2.4.min.js b/static/frontend/jquery-2.2.4.min.js deleted file mode 100644 index 4024b662..00000000 --- a/static/frontend/jquery-2.2.4.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v2.2.4 | (c) jQuery Foundation | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c; -}catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("